@geminixiang/mikan 1.0.0-beta.44 → 1.0.0-beta.46

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 (422) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +12 -19
  3. package/dist/adapter.d.ts +1 -1
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js.map +1 -1
  6. package/dist/adapters/discord/bot.d.ts +3 -1
  7. package/dist/adapters/discord/bot.d.ts.map +1 -1
  8. package/dist/adapters/discord/bot.js +143 -141
  9. package/dist/adapters/discord/bot.js.map +1 -1
  10. package/dist/adapters/discord/context.d.ts +2 -8
  11. package/dist/adapters/discord/context.d.ts.map +1 -1
  12. package/dist/adapters/discord/context.js +1 -1
  13. package/dist/adapters/discord/context.js.map +1 -1
  14. package/dist/adapters/discord/format.d.ts.map +1 -1
  15. package/dist/adapters/discord/format.js +2 -4
  16. package/dist/adapters/discord/format.js.map +1 -1
  17. package/dist/adapters/github/bot.d.ts.map +1 -1
  18. package/dist/adapters/github/bot.js +1 -4
  19. package/dist/adapters/github/bot.js.map +1 -1
  20. package/dist/adapters/github/context.d.ts +2 -9
  21. package/dist/adapters/github/context.d.ts.map +1 -1
  22. package/dist/adapters/github/context.js.map +1 -1
  23. package/dist/adapters/github/github-ops.d.ts +4 -17
  24. package/dist/adapters/github/github-ops.d.ts.map +1 -1
  25. package/dist/adapters/github/github-ops.js +17 -86
  26. package/dist/adapters/github/github-ops.js.map +1 -1
  27. package/dist/adapters/github/tool-pack.d.ts.map +1 -1
  28. package/dist/adapters/github/tool-pack.js +0 -1
  29. package/dist/adapters/github/tool-pack.js.map +1 -1
  30. package/dist/adapters/github/tools/checks.d.ts +0 -1
  31. package/dist/adapters/github/tools/checks.d.ts.map +1 -1
  32. package/dist/adapters/github/tools/checks.js +3 -22
  33. package/dist/adapters/github/tools/checks.js.map +1 -1
  34. package/dist/adapters/github/types.d.ts +0 -33
  35. package/dist/adapters/github/types.d.ts.map +1 -1
  36. package/dist/adapters/github/types.js.map +1 -1
  37. package/dist/adapters/progressive-renderer.d.ts.map +1 -1
  38. package/dist/adapters/progressive-renderer.js +311 -318
  39. package/dist/adapters/progressive-renderer.js.map +1 -1
  40. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  41. package/dist/adapters/slack/blocks.js +1 -4
  42. package/dist/adapters/slack/blocks.js.map +1 -1
  43. package/dist/adapters/slack/bot.d.ts +12 -0
  44. package/dist/adapters/slack/bot.d.ts.map +1 -1
  45. package/dist/adapters/slack/bot.js +158 -178
  46. package/dist/adapters/slack/bot.js.map +1 -1
  47. package/dist/adapters/slack/context.d.ts +2 -8
  48. package/dist/adapters/slack/context.d.ts.map +1 -1
  49. package/dist/adapters/slack/context.js +1 -1
  50. package/dist/adapters/slack/context.js.map +1 -1
  51. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  52. package/dist/adapters/slack/response-lifecycle.js +135 -123
  53. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  54. package/dist/adapters/slack/stream-limits.d.ts +1 -1
  55. package/dist/adapters/slack/stream-limits.js +1 -1
  56. package/dist/adapters/slack/stream-limits.js.map +1 -1
  57. package/dist/adapters/slack/types.d.ts +4 -0
  58. package/dist/adapters/slack/types.d.ts.map +1 -1
  59. package/dist/adapters/slack/types.js.map +1 -1
  60. package/dist/adapters/telegram/context.d.ts +2 -8
  61. package/dist/adapters/telegram/context.d.ts.map +1 -1
  62. package/dist/adapters/telegram/context.js +1 -2
  63. package/dist/adapters/telegram/context.js.map +1 -1
  64. package/dist/adapters/types.d.ts +11 -5
  65. package/dist/adapters/types.d.ts.map +1 -1
  66. package/dist/adapters/types.js.map +1 -1
  67. package/dist/agent/catalog.d.ts +6 -18
  68. package/dist/agent/catalog.d.ts.map +1 -1
  69. package/dist/agent/catalog.js +19 -155
  70. package/dist/agent/catalog.js.map +1 -1
  71. package/dist/agent/execution.d.ts +0 -7
  72. package/dist/agent/execution.d.ts.map +1 -1
  73. package/dist/agent/execution.js +23 -21
  74. package/dist/agent/execution.js.map +1 -1
  75. package/dist/agent/presenter.d.ts +7 -9
  76. package/dist/agent/presenter.d.ts.map +1 -1
  77. package/dist/agent/presenter.js +355 -357
  78. package/dist/agent/presenter.js.map +1 -1
  79. package/dist/agent/prompt.d.ts +5 -12
  80. package/dist/agent/prompt.d.ts.map +1 -1
  81. package/dist/agent/prompt.js +56 -81
  82. package/dist/agent/prompt.js.map +1 -1
  83. package/dist/agent/runner.d.ts +0 -7
  84. package/dist/agent/runner.d.ts.map +1 -1
  85. package/dist/agent/runner.js +255 -308
  86. package/dist/agent/runner.js.map +1 -1
  87. package/dist/agent/types.d.ts +21 -17
  88. package/dist/agent/types.d.ts.map +1 -1
  89. package/dist/agent/types.js +0 -1
  90. package/dist/agent/types.js.map +1 -1
  91. package/dist/cli/boot.d.ts.map +1 -1
  92. package/dist/cli/boot.js +7 -10
  93. package/dist/cli/boot.js.map +1 -1
  94. package/dist/cli/onboard.d.ts +10 -0
  95. package/dist/cli/onboard.d.ts.map +1 -0
  96. package/dist/cli/onboard.js +237 -0
  97. package/dist/cli/onboard.js.map +1 -0
  98. package/dist/cli/types.d.ts +8 -3
  99. package/dist/cli/types.d.ts.map +1 -1
  100. package/dist/cli/types.js.map +1 -1
  101. package/dist/commands/auto-reply.d.ts.map +1 -1
  102. package/dist/commands/auto-reply.js +5 -12
  103. package/dist/commands/auto-reply.js.map +1 -1
  104. package/dist/commands/manifest.d.ts.map +1 -1
  105. package/dist/commands/manifest.js +9 -16
  106. package/dist/commands/manifest.js.map +1 -1
  107. package/dist/commands/new.d.ts.map +1 -1
  108. package/dist/commands/new.js +15 -10
  109. package/dist/commands/new.js.map +1 -1
  110. package/dist/commands/registry.d.ts.map +1 -1
  111. package/dist/commands/registry.js +0 -2
  112. package/dist/commands/registry.js.map +1 -1
  113. package/dist/commands/sandbox.d.ts.map +1 -1
  114. package/dist/commands/sandbox.js +89 -70
  115. package/dist/commands/sandbox.js.map +1 -1
  116. package/dist/commands/session-view.js +9 -2
  117. package/dist/commands/session-view.js.map +1 -1
  118. package/dist/commands/types.d.ts +4 -3
  119. package/dist/commands/types.d.ts.map +1 -1
  120. package/dist/commands/types.js.map +1 -1
  121. package/dist/config.d.ts +13 -17
  122. package/dist/config.d.ts.map +1 -1
  123. package/dist/config.js +39 -37
  124. package/dist/config.js.map +1 -1
  125. package/dist/dream/index.d.ts +20 -0
  126. package/dist/dream/index.d.ts.map +1 -0
  127. package/dist/dream/index.js +253 -0
  128. package/dist/dream/index.js.map +1 -0
  129. package/dist/dream/types.d.ts +30 -0
  130. package/dist/dream/types.d.ts.map +1 -0
  131. package/dist/dream/types.js.map +1 -0
  132. package/dist/env-manifest.d.ts.map +1 -1
  133. package/dist/env-manifest.js +2 -34
  134. package/dist/env-manifest.js.map +1 -1
  135. package/dist/execution-resolver.d.ts +9 -2
  136. package/dist/execution-resolver.d.ts.map +1 -1
  137. package/dist/execution-resolver.js +26 -27
  138. package/dist/execution-resolver.js.map +1 -1
  139. package/dist/harness/event-format.d.ts +1 -2
  140. package/dist/harness/event-format.d.ts.map +1 -1
  141. package/dist/harness/event-format.js +9 -15
  142. package/dist/harness/event-format.js.map +1 -1
  143. package/dist/harness/index.d.ts +1 -7
  144. package/dist/harness/index.d.ts.map +1 -1
  145. package/dist/harness/index.js +0 -4
  146. package/dist/harness/index.js.map +1 -1
  147. package/dist/harness/models.d.ts.map +1 -1
  148. package/dist/harness/models.js +15 -35
  149. package/dist/harness/models.js.map +1 -1
  150. package/dist/harness/runner.d.ts +7 -16
  151. package/dist/harness/runner.d.ts.map +1 -1
  152. package/dist/harness/runner.js +13 -131
  153. package/dist/harness/runner.js.map +1 -1
  154. package/dist/harness/session-store.d.ts +1 -1
  155. package/dist/harness/session-store.d.ts.map +1 -1
  156. package/dist/harness/session-store.js +1 -1
  157. package/dist/harness/session-store.js.map +1 -1
  158. package/dist/harness/subagent-runner.d.ts +2 -2
  159. package/dist/harness/subagent-runner.d.ts.map +1 -1
  160. package/dist/harness/subagent-runner.js +157 -162
  161. package/dist/harness/subagent-runner.js.map +1 -1
  162. package/dist/harness/types.d.ts +1 -13
  163. package/dist/harness/types.d.ts.map +1 -1
  164. package/dist/harness/types.js.map +1 -1
  165. package/dist/index.d.ts +3 -9
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +1 -5
  168. package/dist/index.js.map +1 -1
  169. package/dist/main.js +31 -212
  170. package/dist/main.js.map +1 -1
  171. package/dist/mcp/catalog.d.ts +5 -0
  172. package/dist/mcp/catalog.d.ts.map +1 -0
  173. package/dist/mcp/catalog.js +135 -0
  174. package/dist/mcp/catalog.js.map +1 -0
  175. package/dist/mcp/loader.d.ts.map +1 -1
  176. package/dist/mcp/loader.js +3 -6
  177. package/dist/mcp/loader.js.map +1 -1
  178. package/dist/mcp/types.d.ts +22 -0
  179. package/dist/mcp/types.d.ts.map +1 -1
  180. package/dist/mcp/types.js.map +1 -1
  181. package/dist/office/index.d.ts +136 -8
  182. package/dist/office/index.d.ts.map +1 -1
  183. package/dist/office/index.js +1050 -9
  184. package/dist/office/index.js.map +1 -1
  185. package/dist/office/types.d.ts +19 -1
  186. package/dist/office/types.d.ts.map +1 -1
  187. package/dist/office/types.js.map +1 -1
  188. package/dist/packages/index.d.ts +168 -5
  189. package/dist/packages/index.d.ts.map +1 -1
  190. package/dist/packages/index.js +749 -5
  191. package/dist/packages/index.js.map +1 -1
  192. package/dist/packages/types.d.ts +1 -5
  193. package/dist/packages/types.d.ts.map +1 -1
  194. package/dist/packages/types.js.map +1 -1
  195. package/dist/provisioner.d.ts +2 -9
  196. package/dist/provisioner.d.ts.map +1 -1
  197. package/dist/provisioner.js +3 -12
  198. package/dist/provisioner.js.map +1 -1
  199. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  200. package/dist/runtime/conversation-runtime.js +117 -380
  201. package/dist/runtime/conversation-runtime.js.map +1 -1
  202. package/dist/runtime/session-lifecycle.d.ts +32 -18
  203. package/dist/runtime/session-lifecycle.d.ts.map +1 -1
  204. package/dist/runtime/session-lifecycle.js +159 -46
  205. package/dist/runtime/session-lifecycle.js.map +1 -1
  206. package/dist/runtime/types.d.ts +4 -26
  207. package/dist/runtime/types.d.ts.map +1 -1
  208. package/dist/runtime/types.js.map +1 -1
  209. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  210. package/dist/sandbox/cloudflare.js +2 -3
  211. package/dist/sandbox/cloudflare.js.map +1 -1
  212. package/dist/sandbox/container.d.ts +0 -1
  213. package/dist/sandbox/container.d.ts.map +1 -1
  214. package/dist/sandbox/container.js +2 -3
  215. package/dist/sandbox/container.js.map +1 -1
  216. package/dist/sandbox/host.d.ts.map +1 -1
  217. package/dist/sandbox/host.js +4 -2
  218. package/dist/sandbox/host.js.map +1 -1
  219. package/dist/sandbox/identity.d.ts +10 -19
  220. package/dist/sandbox/identity.d.ts.map +1 -1
  221. package/dist/sandbox/identity.js +8 -8
  222. package/dist/sandbox/identity.js.map +1 -1
  223. package/dist/sandbox/index.d.ts +7 -10
  224. package/dist/sandbox/index.d.ts.map +1 -1
  225. package/dist/sandbox/index.js +45 -16
  226. package/dist/sandbox/index.js.map +1 -1
  227. package/dist/sandbox/types.d.ts +13 -45
  228. package/dist/sandbox/types.d.ts.map +1 -1
  229. package/dist/sandbox/types.js.map +1 -1
  230. package/dist/sandbox/utils.d.ts +5 -0
  231. package/dist/sandbox/utils.d.ts.map +1 -1
  232. package/dist/sandbox/utils.js +10 -0
  233. package/dist/sandbox/utils.js.map +1 -1
  234. package/dist/sessions/chat-history-sync.d.ts +1 -3
  235. package/dist/sessions/chat-history-sync.d.ts.map +1 -1
  236. package/dist/sessions/chat-history-sync.js +58 -11
  237. package/dist/sessions/chat-history-sync.js.map +1 -1
  238. package/dist/sessions/migrate-v3.d.ts +2 -5
  239. package/dist/sessions/migrate-v3.d.ts.map +1 -1
  240. package/dist/sessions/migrate-v3.js +34 -4
  241. package/dist/sessions/migrate-v3.js.map +1 -1
  242. package/dist/sessions/store.d.ts +5 -6
  243. package/dist/sessions/store.d.ts.map +1 -1
  244. package/dist/sessions/store.js +62 -2
  245. package/dist/sessions/store.js.map +1 -1
  246. package/dist/sessions/types.d.ts +9 -0
  247. package/dist/sessions/types.d.ts.map +1 -1
  248. package/dist/sessions/types.js.map +1 -1
  249. package/dist/tools/bash.d.ts.map +1 -1
  250. package/dist/tools/bash.js +2 -10
  251. package/dist/tools/bash.js.map +1 -1
  252. package/dist/tools/read.js +2 -2
  253. package/dist/tools/read.js.map +1 -1
  254. package/dist/tools/sandbox.d.ts.map +1 -1
  255. package/dist/tools/sandbox.js +2 -3
  256. package/dist/tools/sandbox.js.map +1 -1
  257. package/dist/tools/subagent.d.ts +1 -1
  258. package/dist/tools/subagent.js.map +1 -1
  259. package/dist/types.d.ts +38 -127
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/dist/vault/index.d.ts +9 -2
  263. package/dist/vault/index.d.ts.map +1 -1
  264. package/dist/vault/index.js +29 -5
  265. package/dist/vault/index.js.map +1 -1
  266. package/dist/web/admin/portal.d.ts +12 -1
  267. package/dist/web/admin/portal.d.ts.map +1 -1
  268. package/dist/web/admin/portal.js +437 -422
  269. package/dist/web/admin/portal.js.map +1 -1
  270. package/dist/web/admin/types.d.ts +2 -2
  271. package/dist/web/admin/types.d.ts.map +1 -1
  272. package/dist/web/admin/types.js.map +1 -1
  273. package/dist/web/login/portal.d.ts +7 -1
  274. package/dist/web/login/portal.d.ts.map +1 -1
  275. package/dist/web/login/portal.js +143 -78
  276. package/dist/web/login/portal.js.map +1 -1
  277. package/dist/web/portal-shell.d.ts +1 -0
  278. package/dist/web/portal-shell.d.ts.map +1 -1
  279. package/dist/web/portal-shell.js +12 -1
  280. package/dist/web/portal-shell.js.map +1 -1
  281. package/dist/web/server.d.ts +3 -3
  282. package/dist/web/server.d.ts.map +1 -1
  283. package/dist/web/server.js.map +1 -1
  284. package/dist/web/session-view/portal.d.ts +8 -1
  285. package/dist/web/session-view/portal.d.ts.map +1 -1
  286. package/dist/web/session-view/portal.js +609 -132
  287. package/dist/web/session-view/portal.js.map +1 -1
  288. package/dist/web/session-view/types.d.ts +8 -0
  289. package/dist/web/session-view/types.d.ts.map +1 -1
  290. package/dist/web/session-view/types.js.map +1 -1
  291. package/dist/workspace-projection/index.d.ts +14 -2
  292. package/dist/workspace-projection/index.d.ts.map +1 -1
  293. package/dist/workspace-projection/index.js +102 -14
  294. package/dist/workspace-projection/index.js.map +1 -1
  295. package/dist/workspace-projection/types.d.ts +14 -1
  296. package/dist/workspace-projection/types.d.ts.map +1 -1
  297. package/dist/workspace-projection/types.js.map +1 -1
  298. package/package.json +3 -5
  299. package/dist/adapters/github/cloudbuild.d.ts +0 -19
  300. package/dist/adapters/github/cloudbuild.d.ts.map +0 -1
  301. package/dist/adapters/github/cloudbuild.js +0 -71
  302. package/dist/adapters/github/cloudbuild.js.map +0 -1
  303. package/dist/adapters/github/gcp-auth.d.ts +0 -28
  304. package/dist/adapters/github/gcp-auth.d.ts.map +0 -1
  305. package/dist/adapters/github/gcp-auth.js +0 -196
  306. package/dist/adapters/github/gcp-auth.js.map +0 -1
  307. package/dist/agent/index.d.ts +0 -9
  308. package/dist/agent/index.d.ts.map +0 -1
  309. package/dist/agent/index.js +0 -8
  310. package/dist/agent/index.js.map +0 -1
  311. package/dist/cli/ext-dev.d.ts +0 -2
  312. package/dist/cli/ext-dev.d.ts.map +0 -1
  313. package/dist/cli/ext-dev.js +0 -188
  314. package/dist/cli/ext-dev.js.map +0 -1
  315. package/dist/cli/ext.d.ts +0 -2
  316. package/dist/cli/ext.d.ts.map +0 -1
  317. package/dist/cli/ext.js +0 -324
  318. package/dist/cli/ext.js.map +0 -1
  319. package/dist/commands/extensions.d.ts +0 -26
  320. package/dist/commands/extensions.d.ts.map +0 -1
  321. package/dist/commands/extensions.js +0 -103
  322. package/dist/commands/extensions.js.map +0 -1
  323. package/dist/extension-schedules.d.ts +0 -31
  324. package/dist/extension-schedules.d.ts.map +0 -1
  325. package/dist/extension-schedules.js +0 -229
  326. package/dist/extension-schedules.js.map +0 -1
  327. package/dist/harness/auth.d.ts +0 -17
  328. package/dist/harness/auth.d.ts.map +0 -1
  329. package/dist/harness/auth.js +0 -92
  330. package/dist/harness/auth.js.map +0 -1
  331. package/dist/harness/extensions/loader.d.ts +0 -40
  332. package/dist/harness/extensions/loader.d.ts.map +0 -1
  333. package/dist/harness/extensions/loader.js +0 -723
  334. package/dist/harness/extensions/loader.js.map +0 -1
  335. package/dist/harness/extensions/registry.d.ts +0 -155
  336. package/dist/harness/extensions/registry.d.ts.map +0 -1
  337. package/dist/harness/extensions/registry.js +0 -420
  338. package/dist/harness/extensions/registry.js.map +0 -1
  339. package/dist/harness/extensions/types.d.ts +0 -665
  340. package/dist/harness/extensions/types.d.ts.map +0 -1
  341. package/dist/harness/extensions/types.js.map +0 -1
  342. package/dist/office/address.d.ts +0 -31
  343. package/dist/office/address.d.ts.map +0 -1
  344. package/dist/office/address.js +0 -113
  345. package/dist/office/address.js.map +0 -1
  346. package/dist/office/layout.d.ts +0 -27
  347. package/dist/office/layout.d.ts.map +0 -1
  348. package/dist/office/layout.js +0 -97
  349. package/dist/office/layout.js.map +0 -1
  350. package/dist/office/migration.d.ts +0 -44
  351. package/dist/office/migration.d.ts.map +0 -1
  352. package/dist/office/migration.js +0 -255
  353. package/dist/office/migration.js.map +0 -1
  354. package/dist/office/registry.d.ts +0 -75
  355. package/dist/office/registry.d.ts.map +0 -1
  356. package/dist/office/registry.js +0 -646
  357. package/dist/office/registry.js.map +0 -1
  358. package/dist/packages/admin.d.ts +0 -17
  359. package/dist/packages/admin.d.ts.map +0 -1
  360. package/dist/packages/admin.js +0 -98
  361. package/dist/packages/admin.js.map +0 -1
  362. package/dist/packages/inspect.d.ts +0 -11
  363. package/dist/packages/inspect.d.ts.map +0 -1
  364. package/dist/packages/inspect.js +0 -98
  365. package/dist/packages/inspect.js.map +0 -1
  366. package/dist/packages/materialize.d.ts +0 -40
  367. package/dist/packages/materialize.d.ts.map +0 -1
  368. package/dist/packages/materialize.js +0 -297
  369. package/dist/packages/materialize.js.map +0 -1
  370. package/dist/packages/resolve.d.ts +0 -25
  371. package/dist/packages/resolve.d.ts.map +0 -1
  372. package/dist/packages/resolve.js +0 -208
  373. package/dist/packages/resolve.js.map +0 -1
  374. package/dist/packages/source.d.ts +0 -39
  375. package/dist/packages/source.d.ts.map +0 -1
  376. package/dist/packages/source.js +0 -189
  377. package/dist/packages/source.js.map +0 -1
  378. package/dist/sandbox/errors.d.ts +0 -6
  379. package/dist/sandbox/errors.d.ts.map +0 -1
  380. package/dist/sandbox/errors.js +0 -11
  381. package/dist/sandbox/errors.js.map +0 -1
  382. package/dist/sandbox/firecracker.d.ts +0 -18
  383. package/dist/sandbox/firecracker.d.ts.map +0 -1
  384. package/dist/sandbox/firecracker.js +0 -223
  385. package/dist/sandbox/firecracker.js.map +0 -1
  386. package/dist/sandbox/gondolin.d.ts +0 -57
  387. package/dist/sandbox/gondolin.d.ts.map +0 -1
  388. package/dist/sandbox/gondolin.js +0 -764
  389. package/dist/sandbox/gondolin.js.map +0 -1
  390. package/dist/sandbox/image.d.ts +0 -3
  391. package/dist/sandbox/image.d.ts.map +0 -1
  392. package/dist/sandbox/image.js +0 -29
  393. package/dist/sandbox/image.js.map +0 -1
  394. package/dist/sessions/conversation-log.d.ts +0 -9
  395. package/dist/sessions/conversation-log.d.ts.map +0 -1
  396. package/dist/sessions/conversation-log.js +0 -50
  397. package/dist/sessions/conversation-log.js.map +0 -1
  398. package/dist/sessions/rotation.d.ts +0 -2
  399. package/dist/sessions/rotation.d.ts.map +0 -1
  400. package/dist/sessions/rotation.js +0 -41
  401. package/dist/sessions/rotation.js.map +0 -1
  402. package/dist/vault/disabled.d.ts +0 -9
  403. package/dist/vault/disabled.d.ts.map +0 -1
  404. package/dist/vault/disabled.js +0 -25
  405. package/dist/vault/disabled.js.map +0 -1
  406. package/dist/web/admin/store.d.ts +0 -13
  407. package/dist/web/admin/store.d.ts.map +0 -1
  408. package/dist/web/admin/store.js +0 -14
  409. package/dist/web/admin/store.js.map +0 -1
  410. package/dist/web/login/store.d.ts +0 -8
  411. package/dist/web/login/store.d.ts.map +0 -1
  412. package/dist/web/login/store.js +0 -15
  413. package/dist/web/login/store.js.map +0 -1
  414. package/dist/web/session-view/service.d.ts +0 -6
  415. package/dist/web/session-view/service.d.ts.map +0 -1
  416. package/dist/web/session-view/service.js +0 -517
  417. package/dist/web/session-view/service.js.map +0 -1
  418. package/dist/web/session-view/store.d.ts +0 -8
  419. package/dist/web/session-view/store.d.ts.map +0 -1
  420. package/dist/web/session-view/store.js +0 -15
  421. package/dist/web/session-view/store.js.map +0 -1
  422. /package/dist/{harness/extensions → dream}/types.js +0 -0
@@ -1,11 +1,25 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { basename, join, resolve as pathResolve, sep as pathSep } from "node:path";
3
- import { defaultExtensionDirs, listInstalledExtensions, MikanModels, parseFrontmatter, SessionStore, validateEventFilename, } from "../../harness/index.js";
3
+ import { MikanModels, parseFrontmatter, SessionStore, validateEventFilename, } from "../../harness/index.js";
4
+ import { InMemoryTokenStore } from "../token-store.js";
5
+ const ADMIN_TOKEN_TTL_MS = 30 * 60 * 1000;
6
+ export class InMemoryAdminTokenStore extends InMemoryTokenStore {
7
+ create(args) {
8
+ this.deleteWhere((token) => token.platform === args.platform && token.platformUserId === args.platformUserId);
9
+ return this.createRecord(ADMIN_TOKEN_TTL_MS, {
10
+ platform: args.platform,
11
+ platformUserId: args.platformUserId,
12
+ ...(args.platformUserName ? { platformUserName: args.platformUserName } : {}),
13
+ conversationId: args.conversationId,
14
+ });
15
+ }
16
+ }
4
17
  import { loadConversationAutoReplyConfig, loadConversationWorkspaceOverride, loadGlobalSettings, loadScopeMcpServers, resolveConversationSettings, saveConversationAutoReplyConfig, } from "../../config.js";
18
+ import { findMcpPreset, listMcpPresets, materializeMcpPreset } from "../../mcp/catalog.js";
5
19
  import { applyConversationSettings, applyConversationWorkspacePolicy, applyGlobalSettings, applyGlobalWorkspacePolicy, } from "../../settings-mutation.js";
6
20
  import { addPackage, inspectConversationPackages, refreshPackage, removePackage, } from "../../packages/index.js";
7
- import { escapeHtml, readJsonBody, renderPortalShell } from "../portal-shell.js";
8
- import { resolveExistingSessionFile } from "../session-view/service.js";
21
+ import { escapeHtml, jsonResponse as jsonRes, readJsonBody, renderPortalShell, } from "../portal-shell.js";
22
+ import { resolveExistingSessionFile } from "../session-view/portal.js";
9
23
  import { PRODUCT_NAME } from "../../platform-messages.js";
10
24
  import { credentialAuthorizationKey } from "../../sandbox/identity.js";
11
25
  import { resolveWorkspaceProjection } from "../../workspace-projection/index.js";
@@ -40,152 +54,93 @@ export async function handleAdminRequest(req, res, url, services) {
40
54
  // ── API routing ────────────────────────────────────────────────────────────────
41
55
  async function routeApiRequest(req, res, url, services) {
42
56
  if (req.method === "GET") {
43
- const token = services.adminTokenStore.peek(url.searchParams.get("token") ?? "");
44
- if (!token) {
45
- jsonRes(res, 403, { error: "Unauthorized" });
46
- return;
47
- }
48
- if (url.pathname === "/admin/api/me") {
49
- serveMe(res, token);
50
- return;
51
- }
52
- if (url.pathname === "/admin/api/conversations") {
53
- serveConversationsList(res, services);
54
- return;
55
- }
56
- if (url.pathname === "/admin/api/session-usage") {
57
- await serveSessionUsage(res, services);
58
- return;
59
- }
60
- if (url.pathname === "/admin/api/conversation-usage") {
61
- await serveConversationUsage(res, url, services);
62
- return;
63
- }
64
- if (url.pathname === "/admin/api/conversation-state") {
65
- serveConversationState(res, url, services, token);
66
- return;
67
- }
68
- if (url.pathname === "/admin/api/settings/global") {
69
- serveGlobalSettings(res);
70
- return;
71
- }
72
- if (url.pathname === "/admin/api/models") {
73
- void serveModelsList(res);
74
- return;
75
- }
76
- if (url.pathname === "/admin/api/workspace/tree") {
77
- serveWorkspaceTree(res, url, services, token);
78
- return;
79
- }
80
- if (url.pathname === "/admin/api/workspace/file") {
81
- serveWorkspaceFile(res, url, services, token);
82
- return;
83
- }
84
- if (url.pathname === "/admin/api/skills") {
85
- serveSkillsList(res, url, services, token);
86
- return;
87
- }
88
- if (url.pathname === "/admin/api/skills/file") {
89
- serveSkillFile(res, url, services, token);
90
- return;
91
- }
92
- if (url.pathname === "/admin/api/packages") {
93
- await servePackagesList(res, url, services, token);
94
- return;
95
- }
96
- if (url.pathname === "/admin/api/extension-secrets") {
97
- serveExtensionSecretsList(res, url, services, token);
98
- return;
99
- }
100
- if (url.pathname === "/admin/api/mcp-servers") {
101
- serveMcpServersList(res, url, services, token);
102
- return;
103
- }
104
- if (url.pathname === "/admin/api/events") {
105
- await serveEventsList(res, services);
106
- return;
107
- }
108
- if (url.pathname === "/admin/api/events/file") {
109
- serveEventsFile(res, url, services);
110
- return;
111
- }
112
- if (url.pathname === "/admin/api/conversations/events") {
113
- await serveConversationEventsList(res, url, services, token);
114
- return;
115
- }
116
- jsonRes(res, 404, { error: "Not found" });
57
+ await routeGetApiRequest(res, url, services);
117
58
  return;
118
59
  }
119
60
  if (req.method !== "POST") {
120
61
  jsonRes(res, 405, { error: "Method not allowed" });
121
62
  return;
122
63
  }
123
- const body = await readJsonBody(req, res, 32 * 1024);
124
- if (!body)
125
- return;
126
- const rawToken = typeof body.token === "string" ? body.token : "";
127
- const token = services.adminTokenStore.peek(rawToken);
64
+ await routePostApiRequest(req, res, url, services);
65
+ }
66
+ async function routeGetApiRequest(res, url, services) {
67
+ const token = services.adminTokenStore.peek(url.searchParams.get("token") ?? "");
128
68
  if (!token) {
129
69
  jsonRes(res, 403, { error: "Unauthorized" });
130
70
  return;
131
71
  }
132
- if (url.pathname === "/admin/api/conversations/model") {
133
- serveConversationModelUpdate(res, body, services, token);
134
- return;
135
- }
136
- if (url.pathname === "/admin/api/conversations/sandbox") {
137
- serveConversationSandboxUpdate(res, body, services, token);
138
- return;
139
- }
140
- if (url.pathname === "/admin/api/conversations/auto-reply") {
141
- serveConversationAutoReplyUpdate(res, body, services, token);
142
- return;
143
- }
144
- if (url.pathname === "/admin/api/conversations/slack") {
145
- serveConversationSlackUpdate(res, body, services, token);
146
- return;
147
- }
148
- if (url.pathname === "/admin/api/conversations/session-link") {
149
- serveConversationSessionLink(res, body, services, token);
150
- return;
151
- }
152
- if (url.pathname === "/admin/api/conversations/login-link") {
153
- serveConversationLoginLink(res, body, services, token);
154
- return;
155
- }
156
- if (url.pathname === "/admin/api/conversations/events/delete") {
157
- await serveConversationEventDelete(res, body, services, token);
158
- return;
159
- }
160
- if (url.pathname === "/admin/api/mcp-servers/mutate") {
161
- serveMcpServerMutation(res, body, services, token);
162
- return;
163
- }
164
- if (url.pathname === "/admin/api/packages/mutate") {
165
- servePackageMutation(res, body, services, token);
166
- return;
167
- }
168
- if (url.pathname === "/admin/api/extension-secrets/mutate") {
169
- serveExtensionSecretMutation(res, body, services);
170
- return;
171
- }
172
- if (url.pathname === "/admin/api/settings/model") {
173
- serveGlobalModelUpdate(res, body, services);
174
- return;
72
+ switch (url.pathname) {
73
+ case "/admin/api/conversations":
74
+ return serveConversationsList(res, services);
75
+ case "/admin/api/session-usage":
76
+ return serveSessionUsage(res, services);
77
+ case "/admin/api/conversation-usage":
78
+ return serveConversationUsage(res, url, services);
79
+ case "/admin/api/conversation-state":
80
+ return serveConversationState(res, url, services, token);
81
+ case "/admin/api/settings/global":
82
+ return serveGlobalSettings(res);
83
+ case "/admin/api/models":
84
+ return serveModelsList(res);
85
+ case "/admin/api/workspace/tree":
86
+ return serveWorkspaceTree(res, url, services, token);
87
+ case "/admin/api/workspace/file":
88
+ return serveWorkspaceFile(res, url, services, token);
89
+ case "/admin/api/skills":
90
+ return serveSkillsList(res, url, services, token);
91
+ case "/admin/api/skills/file":
92
+ return serveSkillFile(res, url, services, token);
93
+ case "/admin/api/packages":
94
+ return servePackagesList(res, url, services, token);
95
+ case "/admin/api/mcp-servers":
96
+ return serveMcpServersList(res, url, services, token);
97
+ case "/admin/api/events":
98
+ return serveEventsList(res, services);
99
+ case "/admin/api/conversations/events":
100
+ return serveConversationEventsList(res, url, services, token);
101
+ default:
102
+ jsonRes(res, 404, { error: "Not found" });
175
103
  }
176
- if (url.pathname === "/admin/api/settings/workspace") {
177
- serveGlobalWorkspaceUpdate(res, body, services);
104
+ }
105
+ async function routePostApiRequest(req, res, url, services) {
106
+ const body = await readJsonBody(req, res, 32 * 1024);
107
+ if (!body)
178
108
  return;
179
- }
180
- if (url.pathname === "/admin/api/settings/sandbox") {
181
- serveGlobalSandboxUpdate(res, body, services);
109
+ const token = services.adminTokenStore.peek(typeof body.token === "string" ? body.token : "");
110
+ if (!token) {
111
+ jsonRes(res, 403, { error: "Unauthorized" });
182
112
  return;
183
113
  }
184
- if (url.pathname === "/admin/api/settings/slack") {
185
- serveGlobalSlackUpdate(res, body, services);
186
- return;
114
+ switch (url.pathname) {
115
+ case "/admin/api/conversations/model":
116
+ return serveConversationModelUpdate(res, body, services, token);
117
+ case "/admin/api/conversations/sandbox":
118
+ return serveConversationSandboxUpdate(res, body, services, token);
119
+ case "/admin/api/conversations/auto-reply":
120
+ return serveConversationAutoReplyUpdate(res, body, services, token);
121
+ case "/admin/api/conversations/slack":
122
+ return serveConversationSlackUpdate(res, body, services, token);
123
+ case "/admin/api/conversations/session-link":
124
+ return serveConversationSessionLink(res, body, services, token);
125
+ case "/admin/api/conversations/login-link":
126
+ return serveConversationLoginLink(res, body, services, token);
127
+ case "/admin/api/conversations/events/delete":
128
+ return serveConversationEventDelete(res, body, services, token);
129
+ case "/admin/api/mcp-servers/mutate":
130
+ return serveMcpServerMutation(res, body, services, token);
131
+ case "/admin/api/packages/mutate":
132
+ return servePackageMutation(res, body, services, token);
133
+ case "/admin/api/settings/model":
134
+ return serveGlobalModelUpdate(res, body, services);
135
+ case "/admin/api/settings/workspace":
136
+ return serveGlobalWorkspaceUpdate(res, body, services);
137
+ case "/admin/api/settings/sandbox":
138
+ return serveGlobalSandboxUpdate(res, body, services);
139
+ case "/admin/api/settings/slack":
140
+ return serveGlobalSlackUpdate(res, body, services);
141
+ default:
142
+ jsonRes(res, 404, { error: "Not found" });
187
143
  }
188
- jsonRes(res, 404, { error: "Not found" });
189
144
  }
190
145
  /**
191
146
  * Admin scope is a full office address. The platform defaults to the token's
@@ -219,15 +174,6 @@ function requireAdminWorkspace(res, services) {
219
174
  return services.workspace;
220
175
  }
221
176
  // ── API handlers ───────────────────────────────────────────────────────────────
222
- function serveMe(res, token) {
223
- jsonRes(res, 200, {
224
- platform: token.platform,
225
- platformUserId: token.platformUserId,
226
- platformUserName: token.platformUserName ?? null,
227
- conversationId: token.conversationId,
228
- expiresAt: token.expiresAt,
229
- });
230
- }
231
177
  /**
232
178
  * Office directories are office-key named and not reversible to raw ids, so
233
179
  * enumeration reads the office registry — the durable raw-id ↔ office
@@ -503,9 +449,11 @@ function serveConversationState(res, url, services, token) {
503
449
  globalThinkingLevel: globalConfig.thinkingLevel,
504
450
  workspaceDoorPolicy: conversationWorkspace.doorPolicy,
505
451
  workspaceLayout: conversationWorkspace.layout,
452
+ workspaceVisibility: conversationWorkspace.visibility,
506
453
  workspaceOverride: doorPolicyChoiceKey(loadConversationWorkspaceOverride(office)),
507
454
  globalWorkspaceDoorPolicy: globalWorkspaceSettings?.doorPolicy ?? "isolated",
508
455
  globalWorkspaceLayout: globalWorkspaceSettings?.layout ?? "conversation",
456
+ globalWorkspaceVisibility: globalWorkspaceSettings?.visibility ?? "public",
509
457
  autoReplyEnabled: autoReply.enabled,
510
458
  autoReplyRules: autoReply.rules,
511
459
  slack: {
@@ -525,8 +473,9 @@ function serveGlobalSettings(res) {
525
473
  sandboxMemory: config.sandbox?.memory ?? null,
526
474
  sandboxBoostCpus: config.sandbox?.boost?.cpus ?? null,
527
475
  sandboxBoostMemory: config.sandbox?.boost?.memory ?? null,
528
- workspaceDoorPolicy: config.sandbox?.workspace?.doorPolicy ?? "isolated",
529
- workspaceLayout: config.sandbox?.workspace?.layout ?? "conversation",
476
+ workspaceDoorPolicy: config.sandbox?.workspace?.doorPolicy ?? null,
477
+ workspaceLayout: config.sandbox?.workspace?.layout ?? null,
478
+ workspaceVisibility: config.sandbox?.workspace?.visibility ?? null,
530
479
  defaultSharedVault: config.sandbox?.defaultSharedVault ?? null,
531
480
  slack: {
532
481
  replyMode: config.slack?.replyMode ?? "top-level",
@@ -595,7 +544,13 @@ function serveConversationModelUpdate(res, body, services, token) {
595
544
  jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
596
545
  }
597
546
  }
598
- /** Wire values for a door-policy selection; "default" clears the office's override. */
547
+ /**
548
+ * Wire values for a door-policy selection; "default" clears the office's
549
+ * override. `trusted-shared-support` keeps the historical read-write shared
550
+ * MEMORY.md; `trusted-shared-support-private` is the same layout with the
551
+ * shared MEMORY.md mounted read-only (modeled on Claude Tag's private-
552
+ * channel memory: read the shared pool, never write into it).
553
+ */
599
554
  function parseDoorPolicyChoice(value) {
600
555
  switch (value) {
601
556
  case "default":
@@ -603,7 +558,11 @@ function parseDoorPolicyChoice(value) {
603
558
  case "isolated":
604
559
  return { choice: { doorPolicy: "isolated" } };
605
560
  case "trusted-shared-support":
606
- return { choice: { doorPolicy: "trusted", layout: "shared-support" } };
561
+ return { choice: { doorPolicy: "trusted", layout: "shared-support", visibility: "public" } };
562
+ case "trusted-shared-support-private":
563
+ return {
564
+ choice: { doorPolicy: "trusted", layout: "shared-support", visibility: "private" },
565
+ };
607
566
  case "trusted-full":
608
567
  return { choice: { doorPolicy: "trusted", layout: "full" } };
609
568
  default:
@@ -615,13 +574,17 @@ function doorPolicyChoiceKey(choice) {
615
574
  return "default";
616
575
  if (choice.doorPolicy === "isolated")
617
576
  return "isolated";
618
- return choice.layout === "full" ? "trusted-full" : "trusted-shared-support";
577
+ if (choice.layout === "full")
578
+ return "trusted-full";
579
+ return choice.visibility === "private"
580
+ ? "trusted-shared-support-private"
581
+ : "trusted-shared-support";
619
582
  }
620
583
  function serveConversationSandboxUpdate(res, body, services, token) {
621
584
  const parsed = parseDoorPolicyChoice(body.doorPolicy);
622
585
  if (!parsed) {
623
586
  jsonRes(res, 400, {
624
- error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
587
+ error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', 'trusted-shared-support-private', or 'trusted-full'",
625
588
  });
626
589
  return;
627
590
  }
@@ -649,7 +612,7 @@ function serveGlobalWorkspaceUpdate(res, body, services) {
649
612
  const parsed = parseDoorPolicyChoice(body.doorPolicy);
650
613
  if (!parsed) {
651
614
  jsonRes(res, 400, {
652
- error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', or 'trusted-full'",
615
+ error: "doorPolicy must be 'default', 'isolated', 'trusted-shared-support', 'trusted-shared-support-private', or 'trusted-full'",
653
616
  });
654
617
  return;
655
618
  }
@@ -739,7 +702,14 @@ function serveConversationSessionLink(res, body, services, token) {
739
702
  return;
740
703
  }
741
704
  try {
742
- const { token: viewToken } = services.sessionViewTokenStore.create(token.platform, token.platformUserId, scope.conversationId, scope.conversationId, sessionFile, token.platformUserName);
705
+ const { token: viewToken } = services.sessionViewTokenStore.create({
706
+ platform: token.platform,
707
+ platformUserId: token.platformUserId,
708
+ conversationId: scope.conversationId,
709
+ sessionKey: scope.conversationId,
710
+ sessionFile,
711
+ platformUserName: token.platformUserName,
712
+ });
743
713
  const url = `${services.portalBaseUrl}/session?token=${encodeURIComponent(viewToken)}`;
744
714
  jsonRes(res, 200, { ok: true, url });
745
715
  }
@@ -1157,77 +1127,6 @@ async function servePackagesList(res, url, services, token) {
1157
1127
  jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
1158
1128
  }
1159
1129
  }
1160
- const EXTENSION_SLUG_PATTERN = /^[a-z0-9_-]{1,64}$/;
1161
- const EXTENSION_SECRET_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
1162
- /**
1163
- * Declared + provisioned secrets for every extension visible to the selected
1164
- * conversation (global scope plus the conversation's own installs, deduped by
1165
- * slug — the vault is keyed by slug, so two scopes share one secret set).
1166
- * Values never leave the server; only key names and provisioning status do.
1167
- */
1168
- function serveExtensionSecretsList(res, url, services, token) {
1169
- const scope = resolveConversationFromQuery(url, token);
1170
- if (scope.error) {
1171
- jsonRes(res, 403, { error: scope.error });
1172
- return;
1173
- }
1174
- const workspace = requireAdminWorkspace(res, services);
1175
- if (!workspace)
1176
- return;
1177
- const infosBySlug = new Map();
1178
- for (const info of listInstalledExtensions(defaultExtensionDirs(scope.address, workspace.stateDir))) {
1179
- infosBySlug.set(info.slug, info);
1180
- }
1181
- const extensions = [...infosBySlug.values()]
1182
- .toSorted((left, right) => left.slug.localeCompare(right.slug))
1183
- .map((info) => {
1184
- const provisioned = Object.keys(services.vaultManager.resolve(`extensions/${info.slug}`)?.env ?? {}).toSorted();
1185
- const declaredKeys = new Set(info.secrets.map((secret) => secret.key));
1186
- return {
1187
- slug: info.slug,
1188
- name: info.name,
1189
- declared: info.secrets.map((secret) => ({
1190
- ...secret,
1191
- provisioned: provisioned.includes(secret.key),
1192
- })),
1193
- extraProvisioned: provisioned.filter((key) => !declaredKeys.has(key)),
1194
- };
1195
- });
1196
- jsonRes(res, 200, { extensions });
1197
- }
1198
- /** Set or delete one extension secret. Values are write-only through here. */
1199
- function serveExtensionSecretMutation(res, body, services) {
1200
- const action = body.action;
1201
- if (action !== "set" && action !== "delete") {
1202
- jsonRes(res, 400, { error: "action must be 'set' or 'delete'" });
1203
- return;
1204
- }
1205
- const slug = typeof body.slug === "string" ? body.slug : "";
1206
- if (!EXTENSION_SLUG_PATTERN.test(slug)) {
1207
- jsonRes(res, 400, { error: "invalid extension slug" });
1208
- return;
1209
- }
1210
- const key = typeof body.key === "string" ? body.key : "";
1211
- if (!EXTENSION_SECRET_KEY_PATTERN.test(key)) {
1212
- jsonRes(res, 400, { error: "invalid secret key (expected an env-style name)" });
1213
- return;
1214
- }
1215
- const vaultKey = `extensions/${slug}`;
1216
- if (action === "set") {
1217
- const value = typeof body.value === "string" ? body.value : "";
1218
- if (!value) {
1219
- jsonRes(res, 400, { error: "value is required" });
1220
- return;
1221
- }
1222
- services.vaultManager.upsertEnv(vaultKey, { [key]: value });
1223
- }
1224
- else if (!services.vaultManager.deleteEnvKey(vaultKey, key)) {
1225
- jsonRes(res, 404, { error: "Not provisioned." });
1226
- return;
1227
- }
1228
- const provisioned = Object.keys(services.vaultManager.resolve(vaultKey)?.env ?? {}).toSorted();
1229
- jsonRes(res, 200, { ok: true, provisioned });
1230
- }
1231
1130
  /**
1232
1131
  * Add / remove / refresh, in one route because they share validation and all
1233
1132
  * three answer with the freshly re-read inventory: the panel never has to
@@ -1294,6 +1193,7 @@ function serveMcpServersList(res, url, services, token) {
1294
1193
  const servers = loadScopeMcpServers(workspace.office(scope.address));
1295
1194
  jsonRes(res, 200, {
1296
1195
  conversationId: scope.conversationId,
1196
+ presets: listMcpPresets(),
1297
1197
  global: redactMcpServers(servers.global),
1298
1198
  conversation: redactMcpServers(servers.conversation),
1299
1199
  });
@@ -1358,12 +1258,19 @@ function parseMcpServerEntry(raw) {
1358
1258
  */
1359
1259
  function serveMcpServerMutation(res, body, services, token) {
1360
1260
  const action = body.action;
1361
- if (action !== "set" && action !== "remove" && action !== "toggle") {
1362
- jsonRes(res, 400, { error: "action must be 'set', 'remove', or 'toggle'" });
1261
+ if (action !== "set" && action !== "install" && action !== "remove" && action !== "toggle") {
1262
+ jsonRes(res, 400, { error: "action must be 'set', 'install', 'remove', or 'toggle'" });
1263
+ return;
1264
+ }
1265
+ const preset = action === "install" && typeof body.presetId === "string"
1266
+ ? findMcpPreset(body.presetId)
1267
+ : undefined;
1268
+ if (action === "install" && !preset) {
1269
+ jsonRes(res, 400, { error: "unknown MCP preset" });
1363
1270
  return;
1364
1271
  }
1365
1272
  const mutationScope = body.scope === "global" ? "global" : "conversation";
1366
- const name = typeof body.name === "string" ? body.name.trim() : "";
1273
+ const name = preset?.serverName ?? (typeof body.name === "string" ? body.name.trim() : "");
1367
1274
  if (!MCP_SERVER_NAME_PATTERN.test(name)) {
1368
1275
  jsonRes(res, 400, {
1369
1276
  error: "invalid server name (letters, digits, '_' or '-', starting with a letter)",
@@ -1399,6 +1306,15 @@ function serveMcpServerMutation(res, body, services, token) {
1399
1306
  : {}),
1400
1307
  };
1401
1308
  }
1309
+ else if (action === "install") {
1310
+ try {
1311
+ next[name] = materializeMcpPreset(preset, mcpStringMap(body.credentials) ?? {});
1312
+ }
1313
+ catch (err) {
1314
+ jsonRes(res, 400, { error: err instanceof Error ? err.message : String(err) });
1315
+ return;
1316
+ }
1317
+ }
1402
1318
  else if (action === "remove") {
1403
1319
  if (!(name in next)) {
1404
1320
  jsonRes(res, 404, { error: "Not declared here." });
@@ -1470,7 +1386,6 @@ function serveSkillFile(res, url, services, token) {
1470
1386
  servePreviewFile(res, safe.absolute, { source, directory }, "Skill file not found");
1471
1387
  }
1472
1388
  // ── Events ─────────────────────────────────────────────────────────────────────
1473
- const EVENTS_FILE_MAX_BYTES = 64 * 1024;
1474
1389
  /**
1475
1390
  * List events through the owning store, whose payloads are validated by the
1476
1391
  * event-format module (files that fail validation stay visible with a null
@@ -1520,45 +1435,6 @@ async function serveConversationEventsList(res, url, services, token) {
1520
1435
  const events = (await listAllEvents(store)).filter((e) => e.conversationId === scope.conversationId);
1521
1436
  jsonRes(res, 200, { conversationId: scope.conversationId, events });
1522
1437
  }
1523
- function serveEventsFile(res, url, services) {
1524
- const workspace = requireAdminWorkspace(res, services);
1525
- if (!workspace)
1526
- return;
1527
- let name;
1528
- try {
1529
- name = validateEventFilename(url.searchParams.get("name") ?? "");
1530
- }
1531
- catch {
1532
- jsonRes(res, 400, { error: "Invalid name" });
1533
- return;
1534
- }
1535
- const filePath = join(workspace.eventsDir, name);
1536
- let stats;
1537
- try {
1538
- stats = statSync(filePath);
1539
- }
1540
- catch {
1541
- jsonRes(res, 404, { error: "Not found" });
1542
- return;
1543
- }
1544
- if (!stats.isFile()) {
1545
- jsonRes(res, 400, { error: "Not a file" });
1546
- return;
1547
- }
1548
- if (stats.size > EVENTS_FILE_MAX_BYTES) {
1549
- jsonRes(res, 413, { error: "File too large" });
1550
- return;
1551
- }
1552
- let raw;
1553
- try {
1554
- raw = readFileSync(filePath, "utf-8");
1555
- }
1556
- catch (err) {
1557
- jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });
1558
- return;
1559
- }
1560
- jsonRes(res, 200, { name, content: raw });
1561
- }
1562
1438
  /** Delete a single event file scoped to the caller's conversation. */
1563
1439
  async function serveConversationEventDelete(res, body, services, token) {
1564
1440
  const scope = resolveTargetConversation(body, token);
@@ -1599,18 +1475,9 @@ async function serveConversationEventDelete(res, body, services, token) {
1599
1475
  }
1600
1476
  }
1601
1477
  // ── Utilities ──────────────────────────────────────────────────────────────────
1602
- function jsonRes(res, status, body) {
1603
- res.writeHead(status, {
1604
- "Content-Type": "application/json; charset=utf-8",
1605
- "Cache-Control": "no-store",
1606
- });
1607
- res.end(JSON.stringify(body));
1608
- }
1609
1478
  const esc = escapeHtml;
1610
1479
  // ── HTML ───────────────────────────────────────────────────────────────────────
1611
- function renderAdminPage(token) {
1612
- const userLabel = token.platformUserName ?? token.platformUserId;
1613
- const body = `<nav class="tab-nav" role="tablist" aria-label="Admin sections">
1480
+ const adminViewBody = `<nav class="tab-nav" role="tablist" aria-label="Admin sections">
1614
1481
  <button class="tab-btn active" role="tab" aria-selected="true" aria-controls="panel-conversation" data-tab="conversation">Conversation</button>
1615
1482
  <button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-global" data-tab="global">Global</button>
1616
1483
  </nav>
@@ -1658,8 +1525,8 @@ function renderAdminPage(token) {
1658
1525
  <section class="card sect" id="sect-packages" data-section="packages">
1659
1526
  <header class="sect-head">
1660
1527
  <div>
1661
- <p class="eyebrow">Extensions</p>
1662
- <h2 class="card-title">此對話的 extension 套件</h2>
1528
+ <p class="eyebrow">Skill Packages</p>
1529
+ <h2 class="card-title">此對話的 skill 套件</h2>
1663
1530
  </div>
1664
1531
  <button class="refresh-btn" onclick="loadPackages()">↻</button>
1665
1532
  </header>
@@ -1766,8 +1633,8 @@ function renderAdminPage(token) {
1766
1633
  <section class="card sect">
1767
1634
  <header class="sect-head">
1768
1635
  <div>
1769
- <p class="eyebrow">Global Extensions</p>
1770
- <h2 class="card-title">所有對話都會載入的 extension 套件</h2>
1636
+ <p class="eyebrow">Global Skill Packages</p>
1637
+ <h2 class="card-title">所有對話都會載入的 skill 套件</h2>
1771
1638
  </div>
1772
1639
  <button class="refresh-btn" onclick="loadPackages()">↻</button>
1773
1640
  </header>
@@ -1794,18 +1661,6 @@ function renderAdminPage(token) {
1794
1661
  <div id="mcp-global-content"><div class="loading-msg">Loading…</div></div>
1795
1662
  </section>
1796
1663
 
1797
- <section class="card sect">
1798
- <header class="sect-head">
1799
- <div>
1800
- <p class="eyebrow">Extension Secrets</p>
1801
- <h2 class="card-title">Extension 密鑰(依 slug 全域共用)</h2>
1802
- </div>
1803
- <button class="refresh-btn" onclick="loadExtensionSecrets()">↻</button>
1804
- </header>
1805
- <div id="ext-secrets-msg" class="pkg-msg" style="display:none"></div>
1806
- <div id="ext-secrets-content"><div class="loading-msg">Loading…</div></div>
1807
- </section>
1808
-
1809
1664
  <section class="card sect">
1810
1665
  <header class="sect-head">
1811
1666
  <div>
@@ -1827,13 +1682,24 @@ function renderAdminPage(token) {
1827
1682
  </header>
1828
1683
  <div id="global-events-content"><div class="loading-msg">Loading…</div></div>
1829
1684
  </section>
1830
- </div>`;
1831
- const script = `
1832
- const adminToken = ${JSON.stringify(token.token)};
1833
- // Conversation ids never contain ":" (session-key grammar), so
1834
- // "platform:id" is a safe composite scope key for the UI.
1835
- const defaultConversationKey = ${JSON.stringify(`${token.platform}:${token.conversationId}`)};
1836
- let activeConversationKey = defaultConversationKey;
1685
+ </div>
1686
+
1687
+ <dialog id="mcp-install-dialog" class="mcp-dialog" aria-labelledby="mcp-dialog-title">
1688
+ <div class="mcp-dialog-head">
1689
+ <div>
1690
+ <p class="eyebrow">Install MCP preset</p>
1691
+ <h2 id="mcp-dialog-title" class="card-title"></h2>
1692
+ </div>
1693
+ <button class="mcp-dialog-close" type="button" aria-label="Close" onclick="closeMcpInstall()">×</button>
1694
+ </div>
1695
+ <div id="mcp-dialog-content"></div>
1696
+ <div id="mcp-dialog-error" class="inline-result err" style="display:none"></div>
1697
+ <div class="mcp-dialog-actions">
1698
+ <button class="pkg-btn" type="button" onclick="closeMcpInstall()">Cancel</button>
1699
+ <button id="mcp-dialog-install" class="primary-action-btn" type="button" onclick="installMcpPreset(this)">Install preset</button>
1700
+ </div>
1701
+ </dialog>`;
1702
+ const adminViewScript = ` let activeConversationKey = defaultConversationKey;
1837
1703
  function scopeOf(key) {
1838
1704
  const sep = key.indexOf(':');
1839
1705
  return { platform: key.slice(0, sep), conversationId: key.slice(sep + 1) };
@@ -1849,6 +1715,9 @@ function renderAdminPage(token) {
1849
1715
  }
1850
1716
  let availableModels = [];
1851
1717
  let modelsLoaded = false;
1718
+ let mcpPresets = [];
1719
+ let mcpServersByScope = { conversation: {}, global: {} };
1720
+ let pendingMcpInstall = null;
1852
1721
 
1853
1722
  // ── Helpers ──────────────────────────────────────────────────────────────────
1854
1723
 
@@ -1865,6 +1734,30 @@ function renderAdminPage(token) {
1865
1734
  async function copyToClipboard(text) {
1866
1735
  try { await navigator.clipboard.writeText(text); } catch { prompt('Copy this link:', text); }
1867
1736
  }
1737
+ document.addEventListener('click', (event) => {
1738
+ const target = event.target instanceof Element
1739
+ ? event.target.closest('[data-admin-action]')
1740
+ : null;
1741
+ if (!target) return;
1742
+ switch (target.dataset.adminAction) {
1743
+ case 'preview-file':
1744
+ void previewFile(target.dataset.filePath || '');
1745
+ break;
1746
+ case 'copy-link':
1747
+ void copyToClipboard(target.dataset.copyText || '');
1748
+ break;
1749
+ case 'delete-event':
1750
+ void deleteEvent(target.dataset.eventName || '', target);
1751
+ break;
1752
+ case 'select-conversation':
1753
+ setActiveConversation(target.dataset.conversationId || '');
1754
+ switchTab('conversation');
1755
+ break;
1756
+ case 'toggle-timeline-filter':
1757
+ toggleTimelineFilter(target.dataset.filterKey || '');
1758
+ break;
1759
+ }
1760
+ });
1868
1761
  async function apiGet(path) {
1869
1762
  const url = path + (path.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(adminToken);
1870
1763
  const r = await fetch(url);
@@ -2008,9 +1901,10 @@ function renderAdminPage(token) {
2008
1901
  const globalModel = [data.globalProvider, data.globalModel].filter(Boolean).join('/');
2009
1902
  const globalModelLabel = globalModel + (data.globalThinkingLevel ? ':' + data.globalThinkingLevel : '');
2010
1903
  const doorPolicyChoices = [
2011
- ['default', 'Global default (' + data.globalWorkspaceDoorPolicy + ' / ' + data.globalWorkspaceLayout + ')'],
1904
+ ['default', 'Automatic — follows the platform channel (public shares, private reads only, DMs isolated)'],
2012
1905
  ['isolated', 'isolated — own office only'],
2013
- ['trusted-shared-support', 'trusted / shared-support — office + shared memory, skills, events'],
1906
+ ['trusted-shared-support', 'trusted / shared-support (public) — office + shared memory, skills, events — read-write'],
1907
+ ['trusted-shared-support-private', 'trusted / shared-support (private) — same, but shared MEMORY.md is read-only'],
2014
1908
  ['trusted-full', 'trusted / full — entire workspace'],
2015
1909
  ];
2016
1910
  const doorPolicyOpts = doorPolicyChoices.map(([value, label]) =>
@@ -2036,7 +1930,7 @@ function renderAdminPage(token) {
2036
1930
  '<div class="config-block">',
2037
1931
  '<h3 class="card-subtitle">Office data policy</h3>',
2038
1932
  '<div class="config-row"><label>Door policy</label><select id="m-door-policy">' + doorPolicyOpts + '</select></div>',
2039
- '<p class="muted-note">Effective: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout) + '</p>',
1933
+ '<p class="muted-note">Effective: ' + escHtml(data.workspaceDoorPolicy + ' / ' + data.workspaceLayout + ' / ' + (data.workspaceVisibility || 'public')) + '</p>',
2040
1934
  '<p class="muted-note">Changing the policy rebuilds the office sandbox container with the new mounts on its next message; the container contents are preserved.</p>',
2041
1935
  '<button class="primary-action-btn" onclick="saveDoorPolicy(this)">Save door policy</button>',
2042
1936
  '<div id="mount-save-result" class="inline-result" style="display:none"></div>',
@@ -2149,14 +2043,14 @@ function renderAdminPage(token) {
2149
2043
 
2150
2044
  function renderTreeChildren(node) {
2151
2045
  if (node.type === 'file') {
2152
- return '<li><button class="tree-file" onclick="previewFile(\\'' + escAttr(node.path) + '\\')">' + escHtml(node.name) + '</button></li>';
2046
+ return '<li><button class="tree-file" data-admin-action="preview-file" data-file-path="' + escAttr(node.path) + '">' + escHtml(node.name) + '</button></li>';
2153
2047
  }
2154
2048
  if (!node.children || node.children.length === 0) {
2155
2049
  return '<li><span class="tree-dir empty">' + escHtml(node.name || '.') + '/</span></li>';
2156
2050
  }
2157
2051
  const inner = node.children.map((c) =>
2158
2052
  c.type === 'file'
2159
- ? '<li><button class="tree-file" onclick="previewFile(\\'' + escAttr(c.path) + '\\')">' + escHtml(c.name) + '</button></li>'
2053
+ ? '<li><button class="tree-file" data-admin-action="preview-file" data-file-path="' + escAttr(c.path) + '">' + escHtml(c.name) + '</button></li>'
2160
2054
  : '<li><details open><summary class="tree-dir">' + escHtml(c.name) + '/</summary><ul>' + renderTreeChildren(c) + '</ul></details></li>'
2161
2055
  ).join('');
2162
2056
  return inner;
@@ -2199,16 +2093,11 @@ function renderAdminPage(token) {
2199
2093
  return;
2200
2094
  }
2201
2095
  container.innerHTML = '<div class="pkg-list">' + rows.map((p) => {
2202
- const provides = []
2203
- .concat(p.extensions.map((s) => 'ext: ' + s))
2204
- .concat(p.skills.map((s) => 'skill: ' + s));
2096
+ const provides = p.skills.map((s) => 'skill: ' + s);
2205
2097
  const status = p.error
2206
2098
  ? '<span class="pkg-badge pkg-badge-err">' + escHtml(p.error) + '</span>'
2207
2099
  : p.shadowed
2208
2100
  ? '<span class="pkg-badge pkg-badge-warn">被此對話的同名套件覆蓋,不會載入</span>'
2209
- // "downloaded", not "ready": this status means the checkout is on
2210
- // disk and was inspected, which is not the same as activated. The
2211
- // extension loads on the conversation's next harness instance.
2212
2101
  : '<span class="pkg-badge pkg-badge-ok">已下載</span>'
2213
2102
  + '<span class="pkg-badge pkg-badge-warn">需 /pi-new 才會載入</span>';
2214
2103
  return '<div class="pkg-row">' +
@@ -2217,7 +2106,7 @@ function renderAdminPage(token) {
2217
2106
  '</div>' +
2218
2107
  (provides.length > 0
2219
2108
  ? '<div class="pkg-provides">' + provides.map((t) => '<span class="pkg-chip">' + escHtml(t) + '</span>').join('') + '</div>'
2220
- : '<div class="pkg-provides pkg-provides-empty">此套件沒有提供 extension 或 skill</div>') +
2109
+ : '<div class="pkg-provides pkg-provides-empty">此套件沒有提供 skill</div>') +
2221
2110
  '<div class="pkg-actions">' +
2222
2111
  '<button class="pkg-btn" data-pkg-action="refresh" data-pkg-scope="' + scope + '" data-pkg-source="' + escAttr(p.source) + '">Update</button>' +
2223
2112
  '<button class="pkg-btn pkg-btn-danger" data-pkg-action="remove" data-pkg-scope="' + scope + '" data-pkg-source="' + escAttr(p.source) + '">Remove</button>' +
@@ -2294,10 +2183,14 @@ function renderAdminPage(token) {
2294
2183
  el.textContent = text;
2295
2184
  }
2296
2185
 
2186
+ function mcpTransport(server) {
2187
+ return server.command
2188
+ ? server.command + (server.args && server.args.length ? ' ' + server.args.join(' ') : '')
2189
+ : (server.url || '');
2190
+ }
2191
+
2297
2192
  function renderMcpServer(scope, name, server) {
2298
- const transport = server.command
2299
- ? 'stdio: ' + server.command + (server.args && server.args.length ? ' ' + server.args.join(' ') : '')
2300
- : 'http: ' + (server.url || '');
2193
+ const transport = (server.command ? 'stdio: ' : 'http: ') + mcpTransport(server);
2301
2194
  const keys = [];
2302
2195
  if (server.envKeys && server.envKeys.length) keys.push('env: ' + server.envKeys.join(', '));
2303
2196
  if (server.headerKeys && server.headerKeys.length) keys.push('headers: ' + server.headerKeys.join(', '));
@@ -2313,16 +2206,41 @@ function renderAdminPage(token) {
2313
2206
  '</div>';
2314
2207
  }
2315
2208
 
2209
+ function renderMcpPreset(scope, preset, servers) {
2210
+ const installed = Boolean(servers[preset.serverName]);
2211
+ const transport = preset.server.command ? 'Runs on host' : 'Remote service';
2212
+ return '<article class="mcp-preset">' +
2213
+ '<div class="mcp-preset-top">' +
2214
+ '<span class="mcp-preset-category">' + escHtml(preset.category) + '</span>' +
2215
+ (installed ? '<span class="pkg-badge pkg-badge-ok">Installed here</span>' : '') +
2216
+ '</div>' +
2217
+ '<h3>' + escHtml(preset.name) + '</h3>' +
2218
+ '<p>' + escHtml(preset.description) + '</p>' +
2219
+ '<div class="mcp-preset-meta">' + escHtml(transport) + ' · <code>' + escHtml(preset.serverName) + '</code></div>' +
2220
+ '<div class="mcp-preset-actions">' +
2221
+ '<a href="' + escAttr(preset.sourceUrl) + '" target="_blank" rel="noopener">Source ↗</a>' +
2222
+ '<button class="primary-action-btn" data-mcp-action="preset" data-mcp-scope="' + scope + '" data-mcp-preset="' + escAttr(preset.id) + '">' + (installed ? 'Review' : 'Install') + '</button>' +
2223
+ '</div>' +
2224
+ '</article>';
2225
+ }
2226
+
2316
2227
  function renderMcpScope(el, scope, servers) {
2228
+ const presets = mcpPresets.map((preset) => renderMcpPreset(scope, preset, servers)).join('');
2317
2229
  const rows = Object.entries(servers).map(([name, server]) => renderMcpServer(scope, name, server)).join('');
2318
2230
  el.innerHTML =
2319
- '<div class="pkg-list">' + (rows || '<div class="pkg-provides-empty">No MCP servers</div>') + '</div>' +
2320
- '<div class="pkg-add" style="margin-top:10px">' +
2321
- '<input id="mcp-' + scope + '-name" class="pkg-input pkg-input-ref" type="text" spellcheck="false" placeholder="name" />' +
2322
- '<input id="mcp-' + scope + '-target" class="pkg-input" type="text" spellcheck="false" placeholder="npx -y @modelcontextprotocol/server-github 或 https://host/mcp" />' +
2323
- '<input id="mcp-' + scope + '-env" class="pkg-input" type="text" spellcheck="false" placeholder="KEY=value KEY2=value2(可留空;HTTP 則為 header)" />' +
2324
- '<button class="primary-action-btn" data-mcp-action="add" data-mcp-scope="' + scope + '">Add</button>' +
2325
- '</div>';
2231
+ '<div class="mcp-market-head"><div><h3>Explore presets</h3><p>Reviewed recipes that install into this scope.</p></div><span>' + mcpPresets.length + ' available</span></div>' +
2232
+ '<div class="mcp-preset-grid">' + presets + '</div>' +
2233
+ '<details class="mcp-installed" open><summary>Installed in this scope</summary>' +
2234
+ '<div class="pkg-list">' + (rows || '<div class="pkg-provides-empty">No MCP servers installed here</div>') + '</div>' +
2235
+ '</details>' +
2236
+ '<details class="mcp-manual"><summary>Advanced: add a custom server</summary>' +
2237
+ '<div class="pkg-add">' +
2238
+ '<input id="mcp-' + scope + '-name" class="pkg-input pkg-input-ref" type="text" spellcheck="false" placeholder="name" />' +
2239
+ '<input id="mcp-' + scope + '-target" class="pkg-input" type="text" spellcheck="false" placeholder="npx -y package@version 或 https://host/mcp" />' +
2240
+ '<input id="mcp-' + scope + '-env" class="pkg-input" type="text" spellcheck="false" placeholder="KEY=value(HTTP 則為 header)" />' +
2241
+ '<button class="primary-action-btn" data-mcp-action="add" data-mcp-scope="' + scope + '">Add custom</button>' +
2242
+ '</div>' +
2243
+ '</details>';
2326
2244
  }
2327
2245
 
2328
2246
  async function loadMcpServers() {
@@ -2332,8 +2250,10 @@ function renderAdminPage(token) {
2332
2250
  if (globalEl) globalEl.innerHTML = '<div class="loading-msg">Loading…</div>';
2333
2251
  try {
2334
2252
  const data = await apiGet('/admin/api/mcp-servers?' + scopeQuery());
2335
- if (convEl) renderMcpScope(convEl, 'conversation', data.conversation);
2336
- if (globalEl) renderMcpScope(globalEl, 'global', data.global);
2253
+ mcpPresets = Array.isArray(data.presets) ? data.presets : [];
2254
+ mcpServersByScope = { conversation: data.conversation || {}, global: data.global || {} };
2255
+ if (convEl) renderMcpScope(convEl, 'conversation', mcpServersByScope.conversation);
2256
+ if (globalEl) renderMcpScope(globalEl, 'global', mcpServersByScope.global);
2337
2257
  } catch (err) {
2338
2258
  if (convEl) convEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
2339
2259
  if (globalEl) globalEl.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
@@ -2357,6 +2277,68 @@ function renderAdminPage(token) {
2357
2277
  }
2358
2278
  }
2359
2279
 
2280
+ function openMcpPreset(scope, presetId) {
2281
+ const preset = mcpPresets.find((item) => item.id === presetId);
2282
+ if (!preset) return;
2283
+ pendingMcpInstall = { scope: scope, preset: preset };
2284
+ const local = Boolean(preset.server.command);
2285
+ const installed = Boolean(mcpServersByScope[scope][preset.serverName]);
2286
+ const credentials = preset.credentials.map((credential, index) =>
2287
+ '<label class="mcp-credential"><span>' + escHtml(credential.label) + (credential.required ? ' *' : '') + '</span>' +
2288
+ '<input data-mcp-credential="' + index + '" type="' + (credential.secret ? 'password' : 'text') + '" autocomplete="off" />' +
2289
+ '<small>' + escHtml(credential.description) + '</small></label>'
2290
+ ).join('');
2291
+ document.getElementById('mcp-dialog-title').textContent = preset.name;
2292
+ const dialogError = document.getElementById('mcp-dialog-error');
2293
+ dialogError.style.display = 'none';
2294
+ dialogError.textContent = '';
2295
+ document.getElementById('mcp-dialog-content').innerHTML =
2296
+ '<p class="mcp-dialog-desc">' + escHtml(preset.description) + '</p>' +
2297
+ '<div class="mcp-install-preview"><span>' + (local ? 'Host command' : 'Remote endpoint') + '</span><code>' + escHtml(mcpTransport(preset.server)) + '</code></div>' +
2298
+ '<div class="mcp-security-note ' + (local ? 'local' : 'remote') + '">' +
2299
+ (local
2300
+ ? '<strong>Host code execution.</strong> This command runs outside the conversation sandbox with the mikan process user permissions.'
2301
+ : '<strong>External service.</strong> Tool calls and selected data will be sent to this remote origin.') +
2302
+ '</div>' +
2303
+ (installed ? '<div class="mcp-replace-note">Installing again replaces the existing <code>' + escHtml(preset.serverName) + '</code> entry in this scope.</div>' : '') +
2304
+ (credentials || '<p class="mcp-no-credentials">No credentials required.</p>') +
2305
+ '<p class="mcp-secret-note">These values are stored in host-private settings and are not shown to the model or sandbox.</p>' +
2306
+ '<a class="mcp-setup-link" href="' + escAttr(preset.setupUrl) + '" target="_blank" rel="noopener">Setup documentation ↗</a>';
2307
+ document.getElementById('mcp-install-dialog').showModal();
2308
+ }
2309
+
2310
+ function closeMcpInstall() {
2311
+ pendingMcpInstall = null;
2312
+ document.getElementById('mcp-install-dialog').close();
2313
+ }
2314
+
2315
+ async function installMcpPreset(btn) {
2316
+ if (!pendingMcpInstall) return;
2317
+ const { scope, preset } = pendingMcpInstall;
2318
+ const credentials = {};
2319
+ document.querySelectorAll('[data-mcp-credential]').forEach((input) => {
2320
+ const descriptor = preset.credentials[Number(input.dataset.mcpCredential)];
2321
+ if (descriptor) credentials[descriptor.key] = input.value;
2322
+ });
2323
+ btn.disabled = true;
2324
+ btn.textContent = 'Installing…';
2325
+ try {
2326
+ await apiPost('/admin/api/mcp-servers/mutate', {
2327
+ action: 'install', scope: scope, presetId: preset.id, credentials: credentials, ...scopeBody(),
2328
+ });
2329
+ closeMcpInstall();
2330
+ mcpMessage(scope, preset.name + ' installed. It will be available on the next response.', 'ok');
2331
+ await loadMcpServers();
2332
+ } catch (err) {
2333
+ const dialogError = document.getElementById('mcp-dialog-error');
2334
+ dialogError.textContent = err.message;
2335
+ dialogError.style.display = 'block';
2336
+ } finally {
2337
+ btn.disabled = false;
2338
+ btn.textContent = 'Install preset';
2339
+ }
2340
+ }
2341
+
2360
2342
  function addMcpServer(scope) {
2361
2343
  const name = document.getElementById('mcp-' + scope + '-name').value.trim();
2362
2344
  const target = document.getElementById('mcp-' + scope + '-target').value.trim();
@@ -2384,97 +2366,10 @@ function renderAdminPage(token) {
2384
2366
  const btn = event.target.closest('[data-mcp-action]');
2385
2367
  if (!btn) return;
2386
2368
  if (btn.dataset.mcpAction === 'add') { addMcpServer(btn.dataset.mcpScope); return; }
2369
+ if (btn.dataset.mcpAction === 'preset') { openMcpPreset(btn.dataset.mcpScope, btn.dataset.mcpPreset); return; }
2387
2370
  void mutateMcpServer(btn.dataset.mcpScope, btn.dataset.mcpAction, btn.dataset.mcpName);
2388
2371
  });
2389
2372
 
2390
- // ── Extension secrets ───────────────────────────────────────────────────────
2391
-
2392
- function extSecretsMessage(text, kind) {
2393
- const el = document.getElementById('ext-secrets-msg');
2394
- if (!el) return;
2395
- el.style.display = 'block';
2396
- el.className = 'pkg-msg pkg-msg-' + kind;
2397
- el.textContent = text;
2398
- }
2399
-
2400
- function renderSecretRow(slug, key, meta) {
2401
- return '<div class="pkg-row">' +
2402
- '<div class="pkg-row-main"><span class="pkg-source">' + escHtml(key) + '</span>' +
2403
- (meta.required ? '<span class="pkg-badge pkg-badge-warn">required</span>' : '') +
2404
- (meta.provisioned
2405
- ? '<span class="pkg-badge pkg-badge-ok">provisioned</span>'
2406
- : '<span class="pkg-badge pkg-badge-err">missing</span>') +
2407
- '</div>' +
2408
- (meta.description ? '<div class="skill-desc">' + escHtml(meta.description) + '</div>' : '') +
2409
- '<div class="pkg-actions">' +
2410
- '<button class="pkg-btn" data-secret-action="set" data-secret-slug="' + escAttr(slug) + '" data-secret-key="' + escAttr(key) + '">Set</button>' +
2411
- (meta.provisioned
2412
- ? '<button class="pkg-btn pkg-btn-danger" data-secret-action="delete" data-secret-slug="' + escAttr(slug) + '" data-secret-key="' + escAttr(key) + '">Delete</button>'
2413
- : '') +
2414
- '</div>' +
2415
- '</div>';
2416
- }
2417
-
2418
- async function loadExtensionSecrets() {
2419
- const container = document.getElementById('ext-secrets-content');
2420
- if (!container) return;
2421
- container.innerHTML = '<div class="loading-msg">Loading…</div>';
2422
- try {
2423
- const data = await apiGet('/admin/api/extension-secrets?' + scopeQuery());
2424
- if (data.extensions.length === 0) {
2425
- container.innerHTML = '<div class="empty-state">No extensions installed</div>';
2426
- return;
2427
- }
2428
- container.innerHTML = data.extensions.map((ext) => {
2429
- const declared = ext.declared.map((s) => renderSecretRow(ext.slug, s.key, s)).join('');
2430
- const extras = ext.extraProvisioned
2431
- .map((key) => renderSecretRow(ext.slug, key, { provisioned: true }))
2432
- .join('');
2433
- return '<div class="pkg-group">' +
2434
- '<div class="pkg-group-head"><strong>' + escHtml(ext.name) + '</strong>' +
2435
- '<span class="pkg-source">' + escHtml(ext.slug) + '</span>' +
2436
- '<button class="pkg-btn" data-secret-action="add" data-secret-slug="' + escAttr(ext.slug) + '">Add key</button>' +
2437
- '</div>' +
2438
- '<div class="pkg-list">' +
2439
- (declared + extras || '<div class="pkg-provides-empty">No secrets declared</div>') +
2440
- '</div>' +
2441
- '</div>';
2442
- }).join('');
2443
- } catch (err) {
2444
- container.innerHTML = '<div class="err-msg">' + escHtml(err.message) + '</div>';
2445
- }
2446
- }
2447
-
2448
- document.addEventListener('click', async (event) => {
2449
- const btn = event.target.closest('[data-secret-action]');
2450
- if (!btn) return;
2451
- const slug = btn.dataset.secretSlug;
2452
- const action = btn.dataset.secretAction;
2453
- let key = btn.dataset.secretKey;
2454
- if (action === 'add') {
2455
- key = (prompt('Secret key for ' + slug + '(e.g. SLACK_BOT_TOKEN)') || '').trim();
2456
- if (!key) return;
2457
- }
2458
- if (action === 'delete' && !confirm('Delete ' + key + ' from ' + slug + '?')) return;
2459
- let value;
2460
- if (action !== 'delete') {
2461
- value = prompt('Value for ' + key + '(' + slug + ')');
2462
- if (!value) return;
2463
- }
2464
- try {
2465
- await apiPost('/admin/api/extension-secrets/mutate', {
2466
- action: action === 'delete' ? 'delete' : 'set',
2467
- slug: slug,
2468
- key: key,
2469
- value: value,
2470
- });
2471
- extSecretsMessage('已更新。對話輸入 /pi-new 生效。', 'ok');
2472
- await loadExtensionSecrets();
2473
- } catch (err) {
2474
- extSecretsMessage(err.message, 'err');
2475
- }
2476
- });
2477
-
2478
2373
  async function loadSkills() {
2479
2374
  const container = document.getElementById('skills-content');
2480
2375
  const previewEl = document.getElementById('skills-preview');
@@ -2533,7 +2428,7 @@ function renderAdminPage(token) {
2533
2428
  result.innerHTML =
2534
2429
  '<span class="link-vault">vault: <code>' + escHtml(data.vaultId) + '</code></span>' +
2535
2430
  '<a href="' + escAttr(data.url) + '" target="_blank" rel="noopener">' + escHtml(data.url) + '</a>' +
2536
- '<button class="copy-link-btn" onclick="copyToClipboard(' + JSON.stringify(data.url) + ')">Copy</button>';
2431
+ '<button class="copy-link-btn" data-admin-action="copy-link" data-copy-text="' + escAttr(data.url) + '">Copy</button>';
2537
2432
  frame.src = data.url; frame.style.display = 'block';
2538
2433
  } catch (err) {
2539
2434
  result.className = 'link-result err'; result.textContent = err.message;
@@ -2553,7 +2448,7 @@ function renderAdminPage(token) {
2553
2448
  result.className = 'link-result ok';
2554
2449
  result.innerHTML =
2555
2450
  '<a href="' + escAttr(data.url) + '" target="_blank" rel="noopener">' + escHtml(data.url) + '</a>' +
2556
- '<button class="copy-link-btn" onclick="copyToClipboard(' + JSON.stringify(data.url) + ')">Copy</button>';
2451
+ '<button class="copy-link-btn" data-admin-action="copy-link" data-copy-text="' + escAttr(data.url) + '">Copy</button>';
2557
2452
  frame.src = data.url; frame.style.display = 'block';
2558
2453
  } catch (err) {
2559
2454
  result.className = 'link-result err'; result.textContent = err.message;
@@ -2602,7 +2497,7 @@ function renderAdminPage(token) {
2602
2497
  .filter(Boolean).map(escHtml).join(' · ');
2603
2498
  const preview = e.text ? '<div class="event-text">' + escHtml(e.text.length > 240 ? e.text.slice(0, 237) + '…' : e.text) + '</div>' : '';
2604
2499
  const deleteBtn = allowDelete
2605
- ? '<button class="event-delete-btn" onclick="deleteEvent(\\'' + escAttr(e.name) + '\\', this)">Delete</button>'
2500
+ ? '<button class="event-delete-btn" data-admin-action="delete-event" data-event-name="' + escAttr(e.name) + '">Delete</button>'
2606
2501
  : '';
2607
2502
  return '<div class="event-row">' +
2608
2503
  '<div class="event-row-top">' +
@@ -2639,7 +2534,6 @@ function renderAdminPage(token) {
2639
2534
  loadGlobalSettings();
2640
2535
  loadGlobalSkills();
2641
2536
  loadEvents();
2642
- loadExtensionSecrets();
2643
2537
  loadMcpServers();
2644
2538
  }
2645
2539
 
@@ -2654,7 +2548,7 @@ function renderAdminPage(token) {
2654
2548
  }
2655
2549
  container.innerHTML = '<div class="conv-list">' + data.conversations.map((c) => {
2656
2550
  const last = c.lastActivityAt ? new Date(c.lastActivityAt).toLocaleString() : '—';
2657
- return '<button class="conv-row-btn" onclick="setActiveConversation(\\'' + escAttr(c.conversationId) + '\\'); switchTab(\\'conversation\\');">' +
2551
+ return '<button class="conv-row-btn" data-admin-action="select-conversation" data-conversation-id="' + escAttr(c.conversationId) + '">' +
2658
2552
  '<span class="conv-id">' + escHtml(c.label || c.conversationId) + '</span>' +
2659
2553
  (c.running ? '<span class="status-pill running">running</span>' : '') +
2660
2554
  '<span class="conv-last">' + escHtml(last) + '</span>' +
@@ -2794,7 +2688,7 @@ function renderAdminPage(token) {
2794
2688
  const legend = '<div class="tl-legend">' + TL_SERIES.map((s) => {
2795
2689
  const cls = 'tl-legend-item' +
2796
2690
  (active && active.key === s.key ? ' active' : (active ? ' dim' : ''));
2797
- return '<span class="' + cls + '" onclick="toggleTimelineFilter(\\'' + s.key + '\\')">' +
2691
+ return '<span class="' + cls + '" data-admin-action="toggle-timeline-filter" data-filter-key="' + escAttr(s.key) + '">' +
2798
2692
  '<i class="sw ' + s.sw + '"></i>' + s.label + '</span>';
2799
2693
  }).join('') + '</div>';
2800
2694
 
@@ -2854,12 +2748,18 @@ function renderAdminPage(token) {
2854
2748
  const replyModeOpts = replyModes.map((m) =>
2855
2749
  '<option value="' + m + '"' + (((data.slack && data.slack.replyMode) || 'top-level') === m ? ' selected' : '') + '>' + m + '</option>'
2856
2750
  ).join('');
2857
- const gDoorKey = data.workspaceDoorPolicy === 'isolated'
2858
- ? 'isolated'
2859
- : (data.workspaceLayout === 'full' ? 'trusted-full' : 'trusted-shared-support');
2751
+ const gDoorKey = !data.workspaceDoorPolicy
2752
+ ? 'default'
2753
+ : (data.workspaceDoorPolicy === 'isolated'
2754
+ ? 'isolated'
2755
+ : (data.workspaceLayout === 'full'
2756
+ ? 'trusted-full'
2757
+ : ((data.workspaceVisibility || 'public') === 'private' ? 'trusted-shared-support-private' : 'trusted-shared-support')));
2860
2758
  const gDoorPolicyChoices = [
2861
- ['isolated', 'isolated — own office only (built-in default)'],
2862
- ['trusted-shared-support', 'trusted / shared-support — office + shared memory, skills, events'],
2759
+ ['default', 'Automatic — follows each platform channel (public shares, private reads only, DMs isolated)'],
2760
+ ['isolated', 'isolated — own office only'],
2761
+ ['trusted-shared-support', 'trusted / shared-support (public) — office + shared memory, skills, events — read-write'],
2762
+ ['trusted-shared-support-private', 'trusted / shared-support (private) — same, but shared MEMORY.md is read-only'],
2863
2763
  ['trusted-full', 'trusted / full — entire workspace'],
2864
2764
  ];
2865
2765
  const gDoorPolicyOpts = gDoorPolicyChoices.map(([value, label]) =>
@@ -3001,12 +2901,20 @@ function renderAdminPage(token) {
3001
2901
  loadConversationEvents();
3002
2902
  });
3003
2903
  `;
2904
+ function renderAdminPage(token) {
2905
+ const userLabel = token.platformUserName ?? token.platformUserId;
2906
+ const script = `
2907
+ const adminToken = ${JSON.stringify(token.token)};
2908
+ // Conversation ids never contain ":" (session-key grammar), so
2909
+ // "platform:id" is a safe composite scope key for the UI.
2910
+ const defaultConversationKey = ${JSON.stringify(`${token.platform}:${token.conversationId}`)};
2911
+ ${adminViewScript}`;
3004
2912
  return renderPortalShell({
3005
2913
  activeView: "admin",
3006
2914
  pageTitle: "Admin",
3007
2915
  identity: { primary: token.platform, secondary: userLabel },
3008
2916
  conversationSwitcher: { currentId: token.conversationId },
3009
- body,
2917
+ body: adminViewBody,
3010
2918
  extraStyles: adminViewStyles,
3011
2919
  inlineScript: script,
3012
2920
  });
@@ -3234,6 +3142,111 @@ const adminViewStyles = `
3234
3142
  }
3235
3143
  .pkg-group-head .pkg-btn { margin-left: auto; }
3236
3144
 
3145
+ /* ── MCP marketplace ───────────────────────────────────────────────── */
3146
+
3147
+ .mcp-market-head {
3148
+ display: flex; align-items: end; justify-content: space-between; gap: 16px;
3149
+ margin-bottom: 12px;
3150
+ }
3151
+ .mcp-market-head h3 { margin: 0; font-size: 1rem; }
3152
+ .mcp-market-head p { margin: 3px 0 0; color: var(--muted); font-size: 0.8rem; }
3153
+ .mcp-market-head > span {
3154
+ color: var(--subtle); font: 500 0.72rem/1.2 'JetBrains Mono', ui-monospace, monospace;
3155
+ }
3156
+ .mcp-preset-grid {
3157
+ display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;
3158
+ }
3159
+ .mcp-preset {
3160
+ min-width: 0; padding: 14px; border: 1px solid var(--border); border-radius: 12px;
3161
+ background:
3162
+ linear-gradient(145deg, rgba(255,255,255,0.85), rgba(247,245,238,0.72)),
3163
+ var(--card);
3164
+ display: flex; flex-direction: column; min-height: 190px;
3165
+ }
3166
+ .mcp-preset-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
3167
+ .mcp-preset-category {
3168
+ color: var(--accent); font-size: 0.66rem; font-weight: 700;
3169
+ letter-spacing: 0.09em; text-transform: uppercase;
3170
+ }
3171
+ .mcp-preset h3 { margin: 14px 0 5px; font-size: 1.06rem; letter-spacing: -0.02em; }
3172
+ .mcp-preset p { margin: 0; color: var(--muted); font-size: 0.8rem; line-height: 1.5; }
3173
+ .mcp-preset-meta {
3174
+ margin-top: 12px; color: var(--subtle); font-size: 0.72rem;
3175
+ }
3176
+ .mcp-preset-meta code { color: var(--text); background: transparent; }
3177
+ .mcp-preset-actions {
3178
+ margin-top: auto; padding-top: 16px; display: flex; align-items: center;
3179
+ justify-content: space-between; gap: 10px;
3180
+ }
3181
+ .mcp-preset-actions a, .mcp-setup-link {
3182
+ color: var(--muted); font-size: 0.76rem; text-decoration: none;
3183
+ }
3184
+ .mcp-preset-actions a:hover, .mcp-setup-link:hover { color: var(--text); text-decoration: underline; }
3185
+ .mcp-preset-actions .primary-action-btn { padding: 6px 12px; }
3186
+ .mcp-installed, .mcp-manual {
3187
+ margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px;
3188
+ }
3189
+ .mcp-installed > summary, .mcp-manual > summary {
3190
+ cursor: pointer; color: var(--muted); font-size: 0.8rem; font-weight: 600;
3191
+ margin-bottom: 10px;
3192
+ }
3193
+ .mcp-manual .pkg-add { margin: 12px 0 0; }
3194
+
3195
+ .mcp-dialog {
3196
+ width: min(620px, calc(100vw - 28px)); max-height: calc(100vh - 40px);
3197
+ border: 1px solid var(--border); border-radius: 18px; padding: 22px;
3198
+ color: var(--text); background: #fbfaf6; box-shadow: 0 28px 90px rgba(18,18,16,0.24);
3199
+ }
3200
+ .mcp-dialog::backdrop { background: rgba(20,20,18,0.5); backdrop-filter: blur(3px); }
3201
+ .mcp-dialog-head {
3202
+ display: flex; align-items: flex-start; justify-content: space-between; gap: 18px;
3203
+ padding-bottom: 14px; border-bottom: 1px solid var(--border);
3204
+ }
3205
+ .mcp-dialog-head .card-title { margin: 4px 0 0; }
3206
+ .mcp-dialog-close {
3207
+ border: 0; background: transparent; color: var(--muted); cursor: pointer;
3208
+ font-size: 1.6rem; line-height: 1; padding: 2px 5px;
3209
+ }
3210
+ .mcp-dialog-desc { color: var(--muted); line-height: 1.55; font-size: 0.88rem; }
3211
+ .mcp-install-preview {
3212
+ display: grid; gap: 6px; padding: 12px; border-radius: 10px;
3213
+ background: #1d211f; color: #f5f2e9;
3214
+ }
3215
+ .mcp-install-preview span {
3216
+ color: #aeb8b1; font-size: 0.66rem; font-weight: 700;
3217
+ letter-spacing: 0.08em; text-transform: uppercase;
3218
+ }
3219
+ .mcp-install-preview code {
3220
+ color: #f5f2e9; background: transparent; word-break: break-all; font-size: 0.78rem;
3221
+ }
3222
+ .mcp-security-note, .mcp-replace-note {
3223
+ margin-top: 12px; padding: 10px 12px; border-radius: 9px;
3224
+ font-size: 0.78rem; line-height: 1.5;
3225
+ }
3226
+ .mcp-security-note.local {
3227
+ background: rgba(220,38,38,0.08); border: 1px solid rgba(185,28,28,0.18); color: #991b1b;
3228
+ }
3229
+ .mcp-security-note.remote {
3230
+ background: rgba(59,130,246,0.08); border: 1px solid rgba(29,78,216,0.16); color: #1e40af;
3231
+ }
3232
+ .mcp-replace-note { background: rgba(217,119,6,0.1); color: #92400e; }
3233
+ .mcp-credential { display: grid; gap: 5px; margin-top: 14px; }
3234
+ .mcp-credential span { font-size: 0.78rem; font-weight: 650; }
3235
+ .mcp-credential input {
3236
+ width: 100%; padding: 9px 10px; border: 1px solid var(--border); border-radius: 8px;
3237
+ background: #fff; color: var(--text); font: 0.8rem/1.3 'JetBrains Mono', ui-monospace, monospace;
3238
+ }
3239
+ .mcp-credential small, .mcp-secret-note, .mcp-no-credentials {
3240
+ color: var(--subtle); font-size: 0.72rem; line-height: 1.45;
3241
+ }
3242
+ .mcp-secret-note { margin: 14px 0 8px; }
3243
+ .mcp-no-credentials { margin: 14px 0 0; }
3244
+ #mcp-dialog-error { margin-top: 14px; }
3245
+ .mcp-dialog-actions {
3246
+ display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px;
3247
+ padding-top: 14px; border-top: 1px solid var(--border);
3248
+ }
3249
+
3237
3250
  /* ── Events ─────────────────────────────────────────────────────────── */
3238
3251
 
3239
3252
  .events-list { display: flex; flex-direction: column; gap: 8px; }
@@ -3368,6 +3381,8 @@ const adminViewStyles = `
3368
3381
  .portal-frame { min-height: 520px; }
3369
3382
  .workspace-split { grid-template-columns: 1fr; }
3370
3383
  .workspace-tree, .workspace-preview { max-height: 260px; }
3384
+ .mcp-preset-grid { grid-template-columns: 1fr; }
3385
+ .mcp-dialog { padding: 18px; }
3371
3386
  }
3372
3387
  `;
3373
3388
  //# sourceMappingURL=portal.js.map