@xopcai/xopc 0.0.87 → 0.0.88

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 (385) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  4. package/dist/extensions/telegram/src/plugin.js +1 -1
  5. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  6. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/extensions/weixin/src/api/api.js +2 -2
  9. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  10. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  11. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  12. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  13. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  14. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  15. package/dist/extensions/weixin/src/plugin.js +1 -1
  16. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  17. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  18. package/dist/gateway/static/root/assets/agents-CRxETUZx.js +222 -0
  19. package/dist/gateway/static/root/assets/{apps-page-Dg8R-Szf.js → apps-page-wKWf3l57.js} +1 -1
  20. package/dist/gateway/static/root/assets/channels-settings-DDbqVNkx.js +1 -0
  21. package/dist/gateway/static/root/assets/{channels-status-swr-BSHqqCF1.js → channels-status-swr-DIsl75Y3.js} +1 -1
  22. package/dist/gateway/static/root/assets/copy-SxMW6Xpc.js +1 -0
  23. package/dist/gateway/static/root/assets/{cron-api-0h_QT8U3.js → cron-api-N9hvuRrn.js} +1 -1
  24. package/dist/gateway/static/root/assets/{cron-page-BkfKFfFk.js → cron-page-tlNGNxhP.js} +1 -1
  25. package/dist/gateway/static/root/assets/{dist-Cmjp2APP.js → dist-CJwfHYvT.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-debug-page-CFa9z_1N.js → extension-debug-page-BVJohZoZ.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-page-BI8eaTPq.js → extension-page-BT2tmElC.js} +1 -1
  28. package/dist/gateway/static/root/assets/extension-settings-page-BSS47c2j.js +1 -0
  29. package/dist/gateway/static/root/assets/{fetch-DRqwef_Q.js → fetch-BaFNUtkE.js} +1 -1
  30. package/dist/gateway/static/root/assets/{field-primitives-BiNHBo2Y.js → field-primitives-QwYEq6Hz.js} +1 -1
  31. package/dist/gateway/static/root/assets/{heartbeat-config-api-ZRb8qhuz.js → heartbeat-config-api-BVSidEDJ.js} +1 -1
  32. package/dist/gateway/static/root/assets/index-CqZzHNEg.css +1 -0
  33. package/dist/gateway/static/root/assets/{index-Cu7bKuUi.js → index-qNrVJp-y.js} +97 -97
  34. package/dist/gateway/static/root/assets/{logs-page-BFZ8GgCv.js → logs-page-DDonPVLn.js} +1 -1
  35. package/dist/gateway/static/root/assets/sessions-page-DKt-Wmib.js +1 -0
  36. package/dist/gateway/static/root/assets/{settings-form-section-DiqqVs6m.js → settings-form-section-B8N3A3Zo.js} +1 -1
  37. package/dist/gateway/static/root/assets/settings-page-DcJjvvw4.js +3 -0
  38. package/dist/gateway/static/root/assets/{share-preview-page-n1Gprylk.js → share-preview-page-Q7KqkO-u.js} +1 -1
  39. package/dist/gateway/static/root/assets/skills-page-DuJ4BTO3.js +2 -0
  40. package/dist/gateway/static/root/assets/{theme-store-CZOh1nT3.js → theme-store-BbRc5ugR.js} +1 -1
  41. package/dist/gateway/static/root/assets/url-D6jvVYIA.js +7 -0
  42. package/dist/gateway/static/root/assets/{utils-CkWBfxs4.js → utils-CxDGduqK.js} +1 -1
  43. package/dist/gateway/static/root/assets/voice-api-key-field-CTyHz7L_.js +1 -0
  44. package/dist/gateway/static/root/assets/workflows-page-GacJ41Fv.js +27 -0
  45. package/dist/gateway/static/root/index.html +6 -5
  46. package/dist/package.js +1 -1
  47. package/dist/src/agent/agent-manager.js +7 -7
  48. package/dist/src/agent/agent-scope.js +1 -1
  49. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  50. package/dist/src/agent/child-agent-factory.d.ts +15 -0
  51. package/dist/src/agent/child-agent-factory.js +35 -2
  52. package/dist/src/agent/child-agent-factory.js.map +1 -1
  53. package/dist/src/agent/client-error-format.d.ts +20 -0
  54. package/dist/src/agent/client-error-format.js +97 -0
  55. package/dist/src/agent/client-error-format.js.map +1 -0
  56. package/dist/src/agent/context/workspace-seed.js +2 -2
  57. package/dist/src/agent/embedded/run-turn.js +23 -4
  58. package/dist/src/agent/embedded/run-turn.js.map +1 -1
  59. package/dist/src/agent/goals/goal-locale.d.ts +1 -1
  60. package/dist/src/agent/goals/goal-run-store.js +4 -4
  61. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  62. package/dist/src/agent/goals/post-turn.js +2 -2
  63. package/dist/src/agent/image/load-image-media.js +2 -2
  64. package/dist/src/agent/inbound/turn-dispatcher.js +1 -1
  65. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  66. package/dist/src/agent/ipc/bus.js +1 -1
  67. package/dist/src/agent/ipc/inbox.js +2 -2
  68. package/dist/src/agent/ipc/socket.js +1 -1
  69. package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
  70. package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
  71. package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
  72. package/dist/src/agent/mcp/mcp-transport.js +1 -1
  73. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  74. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  75. package/dist/src/agent/memory/dreaming/events.js +1 -1
  76. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  77. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  78. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  79. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  80. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  81. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  82. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  83. package/dist/src/agent/models/manager.js +1 -1
  84. package/dist/src/agent/orchestration/llm-turn-retry.d.ts +2 -0
  85. package/dist/src/agent/orchestration/llm-turn-retry.js +9 -1
  86. package/dist/src/agent/orchestration/llm-turn-retry.js.map +1 -1
  87. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  88. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  89. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  90. package/dist/src/agent/sandbox/path-policy.js +2 -2
  91. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  92. package/dist/src/agent/service/process-direct-streaming.js +19 -3
  93. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  94. package/dist/src/agent/service/webchat-tts.d.ts +1 -2
  95. package/dist/src/agent/service/webchat-tts.js +1 -1
  96. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  97. package/dist/src/agent/service.js +4 -4
  98. package/dist/src/agent/session/session-inspector.js +1 -1
  99. package/dist/src/agent/skills/config.js +1 -1
  100. package/dist/src/agent/skills/hub-hash.js +2 -2
  101. package/dist/src/agent/skills/hub-lock.js +1 -1
  102. package/dist/src/agent/skills/hub-pull.js +2 -2
  103. package/dist/src/agent/skills/index.js +1 -1
  104. package/dist/src/agent/skills/managed-store.js +1 -1
  105. package/dist/src/agent/skills/scanner.js +1 -1
  106. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  107. package/dist/src/agent/skills/skill-manager.js +1 -1
  108. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  109. package/dist/src/agent/tools/factory.js +1 -1
  110. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  111. package/dist/src/agent/tools/send-media.js +1 -1
  112. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  113. package/dist/src/agent/tools/workflow-tool.js +64 -16
  114. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  115. package/dist/src/agent/tools/write.js +1 -1
  116. package/dist/src/agent/workflow/agent-progress.d.ts +5 -0
  117. package/dist/src/agent/workflow/agent-progress.js +65 -0
  118. package/dist/src/agent/workflow/agent-progress.js.map +1 -0
  119. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +1 -1
  120. package/dist/src/agent/workflow/builtins/audit-repo.js +14 -0
  121. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  122. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  123. package/dist/src/agent/workflow/builtins/debug-incident.js +14 -0
  124. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  125. package/dist/src/agent/workflow/builtins/implementation-plan.d.ts +12 -0
  126. package/dist/src/agent/workflow/builtins/implementation-plan.js +175 -0
  127. package/dist/src/agent/workflow/builtins/implementation-plan.js.map +1 -0
  128. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  129. package/dist/src/agent/workflow/builtins/index.js +11 -1
  130. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  131. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +1 -1
  132. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +14 -0
  133. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  134. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  135. package/dist/src/agent/workflow/builtins/pr-review.js +14 -0
  136. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  137. package/dist/src/agent/workflow/builtins/release-check.d.ts +11 -0
  138. package/dist/src/agent/workflow/builtins/release-check.js +165 -0
  139. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -0
  140. package/dist/src/agent/workflow/builtins/research.d.ts +1 -1
  141. package/dist/src/agent/workflow/builtins/research.js +14 -0
  142. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  143. package/dist/src/agent/workflow/catalog.js +1 -1
  144. package/dist/src/agent/workflow/index.d.ts +2 -1
  145. package/dist/src/agent/workflow/index.js +3 -2
  146. package/dist/src/agent/workflow/meta-locale.d.ts +12 -0
  147. package/dist/src/agent/workflow/meta-locale.js +62 -0
  148. package/dist/src/agent/workflow/meta-locale.js.map +1 -0
  149. package/dist/src/agent/workflow/parser.js +3 -0
  150. package/dist/src/agent/workflow/parser.js.map +1 -1
  151. package/dist/src/agent/workflow/runtime.d.ts +2 -2
  152. package/dist/src/agent/workflow/runtime.js +21 -14
  153. package/dist/src/agent/workflow/runtime.js.map +1 -1
  154. package/dist/src/agent/workflow/snapshot.js +2 -12
  155. package/dist/src/agent/workflow/snapshot.js.map +1 -1
  156. package/dist/src/agent/workflow/step-labels.d.ts +8 -0
  157. package/dist/src/agent/workflow/step-labels.js +48 -0
  158. package/dist/src/agent/workflow/step-labels.js.map +1 -0
  159. package/dist/src/agent/workflow/subagent-runner.js +46 -1
  160. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  161. package/dist/src/agent/workflow/types.d.ts +74 -1
  162. package/dist/src/auth/credentials.d.ts +5 -0
  163. package/dist/src/auth/credentials.js +12 -3
  164. package/dist/src/auth/credentials.js.map +1 -1
  165. package/dist/src/auth/profiles/store.js +1 -1
  166. package/dist/src/auth/sync-provider-auth.js +1 -1
  167. package/dist/src/browser/cache-dir-policy.js +1 -1
  168. package/dist/src/browser/cdp-local-launcher.js +2 -2
  169. package/dist/src/browser/providers/browser-ext-install.js +3 -3
  170. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  171. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  172. package/dist/src/browser/stealth.js +1 -1
  173. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  174. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  175. package/dist/src/channels/outbound/persist-store.js +1 -1
  176. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  177. package/dist/src/channels/pairing/pairing-store.js +2 -2
  178. package/dist/src/chat-commands/builtins/config.js +2 -2
  179. package/dist/src/chat-commands/context.js +1 -1
  180. package/dist/src/cli/commands/config.js +1 -1
  181. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  182. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  183. package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
  184. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  185. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  186. package/dist/src/cli/commands/extension-dev.js +1 -1
  187. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  188. package/dist/src/cli/commands/extension-pack.js +1 -1
  189. package/dist/src/cli/commands/gateway/logs.js +1 -1
  190. package/dist/src/cli/commands/image.js +1 -1
  191. package/dist/src/cli/commands/init.js +4 -4
  192. package/dist/src/cli/commands/onboard.js +1 -1
  193. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  194. package/dist/src/config/agent-profile.js +1 -1
  195. package/dist/src/config/agent-typed-models.d.ts +18 -0
  196. package/dist/src/config/agent-typed-models.js +53 -0
  197. package/dist/src/config/agent-typed-models.js.map +1 -0
  198. package/dist/src/config/gateway-bind.js +1 -1
  199. package/dist/src/config/index.js +6 -6
  200. package/dist/src/config/loader.js +2 -2
  201. package/dist/src/config/models-json.js +2 -2
  202. package/dist/src/config/paths-state.js +1 -1
  203. package/dist/src/config/profile.js +2 -2
  204. package/dist/src/config/schema.d.ts +52 -0
  205. package/dist/src/config/schema.js +39 -3
  206. package/dist/src/config/schema.js.map +1 -1
  207. package/dist/src/config/voice.d.ts +3 -28
  208. package/dist/src/config/voice.js +27 -261
  209. package/dist/src/config/voice.js.map +1 -1
  210. package/dist/src/config/workspace-path.js +1 -1
  211. package/dist/src/cron/executor.js +2 -2
  212. package/dist/src/cron/persistence.js +1 -1
  213. package/dist/src/cron/run-log-store.js +1 -1
  214. package/dist/src/daemon/constants.js +1 -1
  215. package/dist/src/daemon/install-plan.js +2 -2
  216. package/dist/src/daemon/launchd.js +2 -2
  217. package/dist/src/daemon/schtasks.js +2 -2
  218. package/dist/src/daemon/systemd.js +2 -2
  219. package/dist/src/extensions/bundle-mcp.js +1 -1
  220. package/dist/src/extensions/discover-extensions.js +1 -1
  221. package/dist/src/extensions/health.js +1 -1
  222. package/dist/src/extensions/loader.js +1 -1
  223. package/dist/src/extensions/lockfile.js +2 -2
  224. package/dist/src/gateway/agents-admin.d.ts +9 -0
  225. package/dist/src/gateway/agents-admin.js +18 -2
  226. package/dist/src/gateway/agents-admin.js.map +1 -1
  227. package/dist/src/gateway/config-tools-web.js +3 -2
  228. package/dist/src/gateway/config-tools-web.js.map +1 -1
  229. package/dist/src/gateway/file-path-classifier.js +2 -2
  230. package/dist/src/gateway/hono/lib/agent-model.d.ts +7 -0
  231. package/dist/src/gateway/hono/lib/agent-model.js +36 -1
  232. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  233. package/dist/src/gateway/hono/lib/config-payload.js +28 -5
  234. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  235. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  236. package/dist/src/gateway/hono/lib/mask-secret-length.d.ts +6 -0
  237. package/dist/src/gateway/hono/lib/mask-secret-length.js +16 -0
  238. package/dist/src/gateway/hono/lib/mask-secret-length.js.map +1 -0
  239. package/dist/src/gateway/hono/lib/safe-providers-config.d.ts +1 -1
  240. package/dist/src/gateway/hono/lib/safe-providers-config.js +2 -1
  241. package/dist/src/gateway/hono/lib/safe-providers-config.js.map +1 -1
  242. package/dist/src/gateway/hono/lib/safe-voice-config.js +2 -1
  243. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  244. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  245. package/dist/src/gateway/hono/oauth.js +1 -1
  246. package/dist/src/gateway/hono/routes/agents.js +2 -2
  247. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  248. package/dist/src/gateway/hono/routes/config-patch/agents.js +8 -2
  249. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  250. package/dist/src/gateway/hono/routes/config-patch/gateway.js +3 -2
  251. package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
  252. package/dist/src/gateway/hono/routes/config-patch/misc.js +8 -3
  253. package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
  254. package/dist/src/gateway/hono/routes/config.js +59 -0
  255. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  256. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  257. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  258. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  259. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  260. package/dist/src/gateway/hono/routes/models.js +75 -12
  261. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  262. package/dist/src/gateway/hono/routes/shares.js +1 -1
  263. package/dist/src/gateway/hono/routes/voice.js +75 -0
  264. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  265. package/dist/src/gateway/hono/routes/workflows.d.ts +3 -0
  266. package/dist/src/gateway/hono/routes/workflows.js +347 -0
  267. package/dist/src/gateway/hono/routes/workflows.js.map +1 -0
  268. package/dist/src/gateway/hono/routes/workspace.js +4 -4
  269. package/dist/src/gateway/lock.js +3 -3
  270. package/dist/src/gateway/ports.js +1 -1
  271. package/dist/src/gateway/service/agent-runner.js +2 -2
  272. package/dist/src/gateway/service/marketplace-service.js +2 -2
  273. package/dist/src/gateway/service/run-gateway-agent.js +2 -20
  274. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  275. package/dist/src/gateway/service.d.ts +3 -0
  276. package/dist/src/gateway/service.js +7 -1
  277. package/dist/src/gateway/service.js.map +1 -1
  278. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  279. package/dist/src/infra/restart.js +2 -2
  280. package/dist/src/infra/update-check.js +1 -1
  281. package/dist/src/infra/update-global.js +1 -1
  282. package/dist/src/infra/update-lock.js +3 -3
  283. package/dist/src/infra/update-runner.js +1 -1
  284. package/dist/src/infra/update-startup.js +2 -2
  285. package/dist/src/infra/write-file-atomic.js +2 -2
  286. package/dist/src/mcp/channel-bridge.js +1 -1
  287. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  288. package/dist/src/providers/index.js +2 -2
  289. package/dist/src/providers/model-registry.js +1 -1
  290. package/dist/src/session/config-store.js +2 -2
  291. package/dist/src/session/init-session-turn.js +2 -2
  292. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  293. package/dist/src/session/parity/sessions-json-file.js +1 -1
  294. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  295. package/dist/src/session/parity/transcript-paths.js +1 -1
  296. package/dist/src/session/resolve-session.js +4 -4
  297. package/dist/src/session/search-index-cache.js +1 -1
  298. package/dist/src/session/search-index.js +1 -1
  299. package/dist/src/session/session-title.js +2 -2
  300. package/dist/src/session/store.js +5 -5
  301. package/dist/src/share/share-auto.js +2 -2
  302. package/dist/src/share/share-store.js +3 -3
  303. package/dist/src/share/share-thumbnail.js +2 -2
  304. package/dist/src/share/share-zip.js +1 -1
  305. package/dist/src/share/site-share-store.js +3 -3
  306. package/dist/src/share/site-static-serve.js +1 -1
  307. package/dist/src/tui/clipboard-image.js +3 -3
  308. package/dist/src/tui/theme-manager.js +1 -1
  309. package/dist/src/tui/tui-agent-events.js +2 -1
  310. package/dist/src/tui/tui-agent-events.js.map +1 -1
  311. package/dist/src/tui/tui-keybindings-file.js +1 -1
  312. package/dist/src/tui/tui-scoped-models.js +2 -2
  313. package/dist/src/tui/tui-settings.js +1 -1
  314. package/dist/src/tui/tui.js +3 -3
  315. package/dist/src/tunnel/frpc-binary.js +3 -3
  316. package/dist/src/tunnel/frpc-config.js +1 -1
  317. package/dist/src/tunnel/frpc-extract.js +1 -1
  318. package/dist/src/tunnel/tunnel-state.js +1 -1
  319. package/dist/src/utils/logger/audit.js +1 -1
  320. package/dist/src/utils/logger/log-store.js +1 -1
  321. package/dist/src/utils/logger/rotation.js +1 -1
  322. package/dist/src/voice/metadata/builtin.d.ts +2 -0
  323. package/dist/src/voice/metadata/builtin.js +420 -0
  324. package/dist/src/voice/metadata/builtin.js.map +1 -0
  325. package/dist/src/voice/metadata/index.d.ts +4 -0
  326. package/dist/src/voice/metadata/index.js +3 -0
  327. package/dist/src/voice/metadata/registry.d.ts +5 -0
  328. package/dist/src/voice/metadata/registry.js +34 -0
  329. package/dist/src/voice/metadata/registry.js.map +1 -0
  330. package/dist/src/voice/metadata/types.d.ts +41 -0
  331. package/dist/src/voice/metadata/types.js +1 -0
  332. package/dist/src/voice/stt/list-providers.d.ts +3 -3
  333. package/dist/src/voice/stt/list-providers.js +41 -6
  334. package/dist/src/voice/stt/list-providers.js.map +1 -1
  335. package/dist/src/voice/tts/audio.js +1 -1
  336. package/dist/src/voice/tts/list-providers.d.ts +3 -3
  337. package/dist/src/voice/tts/list-providers.js +41 -6
  338. package/dist/src/voice/tts/list-providers.js.map +1 -1
  339. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  340. package/dist/src/workflows/domain/command.d.ts +18 -0
  341. package/dist/src/workflows/domain/command.js +1 -0
  342. package/dist/src/workflows/domain/definition.d.ts +62 -0
  343. package/dist/src/workflows/domain/definition.js +1 -0
  344. package/dist/src/workflows/domain/event.d.ts +67 -0
  345. package/dist/src/workflows/domain/event.js +1 -0
  346. package/dist/src/workflows/domain/index.d.ts +5 -0
  347. package/dist/src/workflows/domain/index.js +2 -0
  348. package/dist/src/workflows/domain/result.d.ts +65 -0
  349. package/dist/src/workflows/domain/result.js +1 -0
  350. package/dist/src/workflows/domain/run.d.ts +120 -0
  351. package/dist/src/workflows/domain/run.js +14 -0
  352. package/dist/src/workflows/domain/run.js.map +1 -0
  353. package/dist/src/workflows/engine/index.d.ts +2 -0
  354. package/dist/src/workflows/engine/index.js +3 -0
  355. package/dist/src/workflows/engine/projector.d.ts +3 -0
  356. package/dist/src/workflows/engine/projector.js +205 -0
  357. package/dist/src/workflows/engine/projector.js.map +1 -0
  358. package/dist/src/workflows/engine/workflow-engine.d.ts +31 -0
  359. package/dist/src/workflows/engine/workflow-engine.js +188 -0
  360. package/dist/src/workflows/engine/workflow-engine.js.map +1 -0
  361. package/dist/src/workflows/index.d.ts +6 -0
  362. package/dist/src/workflows/index.js +11 -0
  363. package/dist/src/workflows/runtime/index.d.ts +1 -0
  364. package/dist/src/workflows/runtime/index.js +4 -0
  365. package/dist/src/workflows/runtime/script-runtime.d.ts +3 -0
  366. package/dist/src/workflows/runtime/script-runtime.js +3 -0
  367. package/dist/src/workflows/store/event-store.d.ts +17 -0
  368. package/dist/src/workflows/store/event-store.js +83 -0
  369. package/dist/src/workflows/store/event-store.js.map +1 -0
  370. package/dist/src/workflows/store/paths.d.ts +7 -0
  371. package/dist/src/workflows/store/paths.js +26 -0
  372. package/dist/src/workflows/store/paths.js.map +1 -0
  373. package/dist/src/workflows/store/run-store.d.ts +13 -0
  374. package/dist/src/workflows/store/run-store.js +68 -0
  375. package/dist/src/workflows/store/run-store.js.map +1 -0
  376. package/package.json +5 -5
  377. package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +0 -222
  378. package/dist/gateway/static/root/assets/channels-settings-yohw9YSu.js +0 -1
  379. package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +0 -1
  380. package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +0 -1
  381. package/dist/gateway/static/root/assets/sessions-page-CD7AfB-2.js +0 -1
  382. package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +0 -3
  383. package/dist/gateway/static/root/assets/skills-page-CcN_gj--.js +0 -2
  384. package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +0 -3
  385. package/dist/gateway/static/root/assets/voice-api-key-field-O6awz9hi.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"agents-admin.js","names":["pathResolve"],"sources":["../../../src/gateway/agents-admin.ts"],"sourcesContent":["/**\n * Gateway REST helpers for multi-agent management.\n */\n\nimport { mkdir, readFile, realpath, stat, unlink, writeFile } from 'node:fs/promises';\nimport { join, resolve as pathResolve } from 'node:path';\n\nimport {\n DEFAULT_AGENT_ID,\n listAgentEntries,\n normalizeAgentId,\n resolveAgentDir,\n resolveAgentProfileDir,\n resolveAgentWorkspaceDir,\n resolveDefaultAgentId,\n resolveUserPath,\n validateAgentIdForNewAgent,\n} from '../agent/agent-scope.js';\nimport { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from '../agent/context/workspace.js';\nimport { seedAgentProfileMarkdownFiles } from '../agent/context/workspace-seed.js';\nimport {\n applyAgentConfig,\n findAgentEntryIndex,\n pruneAgentConfig,\n removeAgentDirsFromDisk,\n} from '../commands/agents.config.js';\nimport type { Config } from '../config/schema.js';\nimport { WORKSPACE_FILES } from '../config/paths.js';\nimport { resolveEffectiveAgentProfile } from '../config/agent-profile.js';\nimport { GATEWAY_BUILTIN_TOOL_IDS } from './agent-builtin-tools.js';\nimport { isPathUnderWorkspace, resolveWorkspaceSafePath } from './workspace-editor-path.js';\n\nconst EDITABLE_PROFILE_MARKDOWN_NAMES = new Set<string>([\n ...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES,\n WORKSPACE_FILES.BOOTSTRAP,\n]);\n\nexport type GatewayAgentRow = {\n id: string;\n name?: string;\n description?: string;\n /** Value from `IDENTITY.md` **Avatar:** line when present (may be URL, `xopc:…`, etc.). */\n avatar?: string;\n workspace: string;\n /** Absolute directory for profile Markdown (`SOUL.md`, …) and gateway avatars: `agents/<id>/profile/`. */\n profileDir: string;\n model?: { primary?: string; fallbacks?: string[] };\n isDefault: boolean;\n skills: {\n defaults: string[];\n entry?: string[];\n effectiveAllowlist?: string[];\n };\n tools: {\n defaultsDisable: string[];\n entryDisable: string[];\n effectiveDisable: string[];\n };\n};\n\nexport type GatewayAgentsListResponse = {\n defaultId: string;\n agents: GatewayAgentRow[];\n builtinToolIds: string[];\n};\n\nfunction collectAgentIdsForList(cfg: Config): string[] {\n const entries = listAgentEntries(cfg).filter((e) => e.enabled !== false);\n const defaultId = resolveDefaultAgentId(cfg);\n if (entries.length === 0) {\n return [defaultId];\n }\n const ids = new Set<string>();\n for (const e of entries) {\n ids.add(normalizeAgentId(e.id));\n }\n ids.add(defaultId);\n return [...ids];\n}\n\n/** Extract `**Avatar:**` value from profile IDENTITY.md (same line shape as the gateway console parser). */\nexport function extractAvatarFromIdentityMarkdown(content: string): string | undefined {\n for (const line of content.split('\\n')) {\n const match = line.match(/^[-*]\\s+\\*\\*Avatar:\\*\\*\\s*(.*)/i);\n if (match) {\n const v = match[1]?.trim() ?? '';\n return v.length > 0 ? v : undefined;\n }\n }\n return undefined;\n}\n\nexport async function listGatewayAgents(cfg: Config): Promise<GatewayAgentsListResponse> {\n const defaultId = resolveDefaultAgentId(cfg);\n const agents: GatewayAgentRow[] = [];\n const defaultsSkills = cfg.agents?.defaults?.skills;\n const defaultsDisable = cfg.agents?.defaults?.tools?.disable ?? [];\n for (const id of collectAgentIdsForList(cfg)) {\n const profile = resolveEffectiveAgentProfile(cfg, id);\n const entry = listAgentEntries(cfg).find((e) => normalizeAgentId(e.id) === id);\n const model =\n profile.primaryModelRef || profile.fallbacks.length > 0\n ? {\n ...(profile.primaryModelRef ? { primary: profile.primaryModelRef } : {}),\n ...(profile.fallbacks.length > 0 ? { fallbacks: profile.fallbacks } : {}),\n }\n : undefined;\n const entrySkills = entry?.skills;\n const entryDisable = entry?.tools?.disable ?? [];\n let avatar: string | undefined;\n try {\n const identityPath = join(resolveAgentProfileDir(cfg, id), WORKSPACE_FILES.IDENTITY);\n const content = await readFile(identityPath, 'utf-8');\n avatar = extractAvatarFromIdentityMarkdown(content);\n } catch {\n /* missing IDENTITY.md or unreadable */\n }\n agents.push({\n id,\n ...(entry?.name?.trim() ? { name: entry.name.trim() } : {}),\n ...(entry?.description?.trim() ? { description: entry.description.trim() } : {}),\n ...(avatar ? { avatar } : {}),\n workspace: profile.resolvedWorkspacePath,\n profileDir: resolveAgentProfileDir(cfg, id),\n ...(model ? { model } : {}),\n isDefault: id === defaultId,\n skills: {\n defaults: defaultsSkills ? [...defaultsSkills] : [],\n ...(entrySkills !== undefined ? { entry: [...entrySkills] } : {}),\n ...(profile.skillsAllowlist !== undefined\n ? { effectiveAllowlist: [...profile.skillsAllowlist] }\n : {}),\n },\n tools: {\n defaultsDisable: [...defaultsDisable],\n entryDisable: [...entryDisable],\n effectiveDisable: [...profile.tools.disable].sort((a, b) => a.localeCompare(b)),\n },\n });\n }\n agents.sort((a, b) => a.id.localeCompare(b.id));\n return { defaultId, agents, builtinToolIds: [...GATEWAY_BUILTIN_TOOL_IDS] };\n}\n\nexport type CreateAgentBody = {\n /** Display name stored on the agent entry. */\n name: string;\n /** Optional id seed; normalized agent id defaults from `name` when omitted. */\n id?: string;\n workspace: string;\n model?: string;\n agentDir?: string;\n description?: string;\n /** Initial `agents.list[].tools.disable` for the new entry. */\n toolsDisable?: string[];\n /** Profile markdown files to write after seeding (e.g. `IDENTITY.md`, `SOUL.md`). */\n profileFiles?: Record<string, string>;\n};\n\nexport type AgentAdminHttpStatus = 400 | 404 | 409;\n\nexport type AgentAdminResult<T> =\n | { ok: true; data: T }\n | { ok: false; error: string; status?: AgentAdminHttpStatus };\n\nfunction requireNonMain(id: string): AgentAdminResult<never> | null {\n if (normalizeAgentId(id) === DEFAULT_AGENT_ID) {\n return { ok: false, error: `\"${DEFAULT_AGENT_ID}\" is reserved`, status: 400 };\n }\n return null;\n}\n\nexport function prepareCreateAgent(\n cfg: Config,\n body: CreateAgentBody,\n): AgentAdminResult<{ nextConfig: Config; agentId: string; workspace: string }> {\n const name = body.name?.trim() ?? '';\n if (!name) {\n return { ok: false, error: 'name is required', status: 400 };\n }\n const workspace = body.workspace?.trim() ?? '';\n if (!workspace) {\n return { ok: false, error: 'workspace is required', status: 400 };\n }\n const idRes = validateAgentIdForNewAgent(body.id, name);\n if (idRes.ok === false) {\n return { ok: false, error: idRes.error, status: 400 };\n }\n const agentId = idRes.agentId;\n if (findAgentEntryIndex(listAgentEntries(cfg), agentId) >= 0) {\n return { ok: false, error: `agent \"${agentId}\" already exists`, status: 409 };\n }\n if (body.profileFiles !== undefined) {\n if (typeof body.profileFiles !== 'object' || body.profileFiles === null || Array.isArray(body.profileFiles)) {\n return { ok: false, error: 'profileFiles must be an object', status: 400 };\n }\n for (const [name, content] of Object.entries(body.profileFiles)) {\n const bad = assertAllowedFile(name);\n if (bad) {\n return bad;\n }\n if (typeof content !== 'string') {\n return { ok: false, error: `profileFiles[\"${name}\"] must be a string`, status: 400 };\n }\n }\n }\n\n const wsAbs = resolveUserPath(workspace);\n let next = applyAgentConfig(cfg, {\n agentId,\n name,\n workspace: wsAbs,\n ...(body.model?.trim() ? { model: body.model.trim() } : {}),\n ...(body.agentDir?.trim() ? { agentDir: body.agentDir.trim() } : {}),\n ...(body.description?.trim() ? { description: body.description.trim() } : {}),\n });\n\n if (body.toolsDisable !== undefined) {\n const list = [...listAgentEntries(next)];\n const idx = findAgentEntryIndex(list, agentId);\n if (idx >= 0) {\n type Entry = (typeof list)[number];\n const entry: Entry = { ...list[idx] };\n const disable = body.toolsDisable.map((s) => String(s).trim()).filter(Boolean);\n entry.tools = { ...entry.tools, disable };\n list[idx] = entry;\n next = {\n ...next,\n agents: {\n ...next.agents,\n list,\n },\n };\n }\n }\n\n return { ok: true, data: { nextConfig: next, agentId, workspace: wsAbs } };\n}\n\nexport type PreparedBatchCreateAgent = {\n agentId: string;\n profileFiles?: Record<string, string>;\n};\n\nexport function prepareCreateAgentsBatch(\n cfg: Config,\n bodies: CreateAgentBody[],\n): AgentAdminResult<{ nextConfig: Config; created: PreparedBatchCreateAgent[] }> {\n if (!Array.isArray(bodies) || bodies.length === 0) {\n return { ok: false, error: 'agents must be a non-empty array', status: 400 };\n }\n let next = cfg;\n const created: PreparedBatchCreateAgent[] = [];\n for (const body of bodies) {\n const prep = prepareCreateAgent(next, body);\n if (prep.ok === false) {\n return prep;\n }\n next = prep.data.nextConfig;\n created.push({\n agentId: prep.data.agentId,\n ...(body.profileFiles !== undefined ? { profileFiles: body.profileFiles } : {}),\n });\n }\n return { ok: true, data: { nextConfig: next, created } };\n}\n\nexport async function finalizeCreateAgentDirs(\n cfg: Config,\n agentId: string,\n opts?: { profileFiles?: Record<string, string> },\n): Promise<AgentAdminResult<void>> {\n const wsPath = resolveAgentWorkspaceDir(cfg, agentId);\n const profilePath = resolveAgentProfileDir(cfg, agentId);\n const adPath = resolveAgentDir(cfg, agentId);\n await mkdir(wsPath, { recursive: true });\n await mkdir(profilePath, { recursive: true });\n await mkdir(adPath, { recursive: true });\n const id = normalizeAgentId(agentId);\n const entry = listAgentEntries(cfg).find((e) => normalizeAgentId(e.id) === id);\n const displayName = entry?.name?.trim() || id;\n seedAgentProfileMarkdownFiles(profilePath, wsPath, { displayName });\n\n const profileFiles = opts?.profileFiles;\n if (profileFiles && Object.keys(profileFiles).length > 0) {\n for (const [name, content] of Object.entries(profileFiles)) {\n const written = await writeAgentProfileFile(cfg, agentId, name, content);\n if (written.ok === false) {\n return written;\n }\n }\n }\n\n return { ok: true, data: undefined };\n}\n\nexport type UpdateAgentBody = {\n name?: string;\n description?: string | null;\n workspace?: string;\n model?: string | null;\n agentDir?: string | null;\n setDefault?: boolean;\n /** Replace `agents.list[].skills`; `null` removes the key (inherit defaults). */\n skills?: string[] | null;\n /** Replace `agents.list[].tools.disable`; `null` clears entry-level disables. */\n toolsDisable?: string[] | null;\n};\n\nexport function prepareUpdateAgent(\n cfg: Config,\n agentIdRaw: string,\n body: UpdateAgentBody,\n): AgentAdminResult<{ nextConfig: Config }> {\n const agentId = normalizeAgentId(agentIdRaw);\n let list = [...listAgentEntries(cfg)];\n let idx = findAgentEntryIndex(list, agentId);\n if (idx < 0 && agentId === resolveDefaultAgentId(cfg)) {\n list = [...list, { id: agentId, enabled: true as const }];\n idx = list.length - 1;\n }\n if (idx < 0) {\n return { ok: false, error: `agent \"${agentId}\" not found`, status: 404 };\n }\n\n type Entry = (typeof list)[number];\n const entry: Entry = { ...list[idx] };\n\n if (body.name !== undefined) {\n const n = body.name.trim();\n if (n) {\n entry.name = n;\n }\n }\n if (body.description !== undefined) {\n if (body.description === null || String(body.description).trim() === '') {\n delete entry.description;\n } else {\n entry.description = String(body.description).trim();\n }\n }\n if (body.workspace !== undefined) {\n const w = body.workspace.trim();\n if (w) {\n entry.workspace = resolveUserPath(w);\n }\n }\n if (body.model !== undefined) {\n if (body.model === null) {\n delete entry.model;\n } else {\n const trimmed = String(body.model).trim();\n if (!trimmed) {\n return { ok: false, error: 'model must be a non-empty string or null', status: 400 };\n }\n entry.model = { primary: trimmed };\n }\n }\n if (body.agentDir !== undefined) {\n if (body.agentDir === null || String(body.agentDir).trim() === '') {\n delete entry.agentDir;\n } else {\n entry.agentDir = String(body.agentDir).trim();\n }\n }\n\n if (body.skills !== undefined) {\n if (body.skills === null) {\n delete entry.skills;\n } else {\n const next = body.skills.map((s) => String(s).trim()).filter(Boolean);\n if (next.length === 0) {\n entry.skills = [];\n } else {\n entry.skills = next;\n }\n }\n }\n\n if (body.toolsDisable !== undefined) {\n if (body.toolsDisable === null) {\n if (entry.tools) {\n delete entry.tools.disable;\n if (Object.keys(entry.tools).length === 0) {\n delete entry.tools;\n }\n }\n } else {\n const next = body.toolsDisable.map((s) => String(s).trim()).filter(Boolean);\n entry.tools = { ...entry.tools, disable: next };\n }\n }\n\n list[idx] = entry;\n let next: Config = {\n ...cfg,\n agents: {\n ...cfg.agents,\n list,\n },\n };\n\n if (body.setDefault === true) {\n next = {\n ...next,\n agents: {\n ...next.agents,\n default: agentId,\n },\n };\n }\n return { ok: true, data: { nextConfig: next } };\n}\n\nexport function prepareDeleteAgent(\n cfg: Config,\n agentIdRaw: string,\n): AgentAdminResult<{ nextConfig: Config; agentId: string }> {\n const agentId = normalizeAgentId(agentIdRaw);\n const reserved = requireNonMain(agentId);\n if (reserved) {\n return reserved;\n }\n if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) {\n return { ok: false, error: `agent \"${agentId}\" not found`, status: 404 };\n }\n const { config: pruned } = pruneAgentConfig(cfg, agentId);\n return { ok: true, data: { nextConfig: pruned, agentId } };\n}\n\nexport async function runAfterDeletePurge(cfg: Config, agentId: string): Promise<void> {\n await removeAgentDirsFromDisk(cfg, agentId);\n}\n\nexport type AgentFileEntry = {\n name: string;\n missing: boolean;\n size?: number;\n updatedAtMs?: number;\n};\n\nasync function profileMarkdownRootReal(cfg: Config, agentId: string): Promise<string> {\n const dir = resolveAgentProfileDir(cfg, agentId);\n await mkdir(dir, { recursive: true });\n try {\n return await realpath(dir);\n } catch {\n return pathResolve(dir);\n }\n}\n\nfunction assertAllowedFile(name: string): AgentAdminResult<never> | null {\n if (!name || name.includes('/') || name.includes('\\\\') || !EDITABLE_PROFILE_MARKDOWN_NAMES.has(name)) {\n return { ok: false, error: `unsupported file \"${name}\"`, status: 400 };\n }\n return null;\n}\n\nexport async function listAgentProfileFiles(\n cfg: Config,\n agentId: string,\n): Promise<AgentAdminResult<{ agentId: string; profileDir: string; files: AgentFileEntry[] }>> {\n const id = normalizeAgentId(agentId);\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n const root = await profileMarkdownRootReal(cfg, id);\n const names = [...EDITABLE_PROFILE_MARKDOWN_NAMES];\n const files: AgentFileEntry[] = [];\n for (const name of names.sort((a, b) => a.localeCompare(b))) {\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n continue;\n }\n try {\n const st = await stat(abs);\n if (!st.isFile()) {\n continue;\n }\n files.push({\n name,\n missing: false,\n size: st.size,\n updatedAtMs: st.mtimeMs,\n });\n } catch {\n files.push({ name, missing: true });\n }\n }\n files.sort((a, b) => a.name.localeCompare(b.name));\n return { ok: true, data: { agentId: id, profileDir: root, files } };\n}\n\nexport async function readAgentProfileFile(\n cfg: Config,\n agentId: string,\n name: string,\n): Promise<AgentAdminResult<{ agentId: string; content: string; path: string }>> {\n const bad = assertAllowedFile(name);\n if (bad) {\n return bad;\n }\n const id = normalizeAgentId(agentId);\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n const root = await profileMarkdownRootReal(cfg, id);\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n return { ok: false, error: 'invalid path', status: 400 };\n }\n try {\n const content = await readFile(abs, 'utf-8');\n return { ok: true, data: { agentId: id, content, path: abs } };\n } catch {\n return { ok: false, error: 'file not found', status: 404 };\n }\n}\n\nexport async function writeAgentProfileFile(\n cfg: Config,\n agentId: string,\n name: string,\n content: string,\n): Promise<AgentAdminResult<{ agentId: string; path: string }>> {\n const bad = assertAllowedFile(name);\n if (bad) {\n return bad;\n }\n const id = normalizeAgentId(agentId);\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n const root = await profileMarkdownRootReal(cfg, id);\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n return { ok: false, error: 'invalid path', status: 400 };\n }\n const rootReal = await profileMarkdownRootReal(cfg, id);\n if (!isPathUnderWorkspace(rootReal, abs)) {\n return { ok: false, error: 'path escapes profile markdown root', status: 400 };\n }\n await writeFile(abs, content, 'utf-8');\n return { ok: true, data: { agentId: id, path: abs } };\n}\n\n// ---------------------------------------------------------------------------\n// Binary agent avatar (profile markdown root dir, not a SOUL/IDENTITY markdown file)\n// ---------------------------------------------------------------------------\n\nconst AGENT_AVATAR_MAX_BYTES = 512 * 1024;\nconst AGENT_AVATAR_BASENAME = 'agent-avatar';\n\nconst AGENT_AVATAR_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp'] as const;\n\nfunction agentAvatarFilenames(): string[] {\n return AGENT_AVATAR_EXTENSIONS.map((ext) => `${AGENT_AVATAR_BASENAME}${ext}`);\n}\n\nfunction mimeToExt(mime: string): '.png' | '.jpg' | '.jpeg' | '.webp' | null {\n const m = mime.toLowerCase().trim();\n if (m === 'image/png') return '.png';\n if (m === 'image/jpeg' || m === 'image/jpg') return '.jpg';\n if (m === 'image/webp') return '.webp';\n return null;\n}\n\nfunction detectImageMimeFromBytes(buf: Uint8Array): 'image/png' | 'image/jpeg' | 'image/webp' | null {\n if (buf.length >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) {\n return 'image/png';\n }\n if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {\n return 'image/jpeg';\n }\n if (\n buf.length >= 12 &&\n buf[0] === 0x52 &&\n buf[1] === 0x49 &&\n buf[2] === 0x46 &&\n buf[3] === 0x46 &&\n buf[8] === 0x57 &&\n buf[9] === 0x45 &&\n buf[10] === 0x42 &&\n buf[11] === 0x50\n ) {\n return 'image/webp';\n }\n return null;\n}\n\nfunction assertAgentExistsForAvatar(cfg: Config, id: string): AgentAdminResult<never> | null {\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n return null;\n}\n\nexport async function readAgentAvatarFile(\n cfg: Config,\n agentId: string,\n): Promise<AgentAdminResult<{ agentId: string; buffer: Buffer; contentType: string; path: string }>> {\n const missingAgent = assertAgentExistsForAvatar(cfg, agentId);\n if (missingAgent) {\n return missingAgent;\n }\n const id = normalizeAgentId(agentId);\n const root = await profileMarkdownRootReal(cfg, id);\n for (const name of agentAvatarFilenames()) {\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n continue;\n }\n try {\n const st = await stat(abs);\n if (!st.isFile() || st.size <= 0 || st.size > AGENT_AVATAR_MAX_BYTES) {\n continue;\n }\n const buffer = await readFile(abs);\n const detected = detectImageMimeFromBytes(buffer);\n if (!detected) {\n continue;\n }\n return { ok: true, data: { agentId: id, buffer, contentType: detected, path: abs } };\n } catch {\n /* try next */\n }\n }\n return { ok: false, error: 'avatar not found', status: 404 };\n}\n\nexport async function writeAgentAvatarFromBase64(\n cfg: Config,\n agentId: string,\n base64: string,\n mimeType: string,\n): Promise<AgentAdminResult<{ agentId: string; path: string }>> {\n const missingAgent = assertAgentExistsForAvatar(cfg, agentId);\n if (missingAgent) {\n return missingAgent;\n }\n const id = normalizeAgentId(agentId);\n const ext = mimeToExt(mimeType);\n if (!ext) {\n return { ok: false, error: 'unsupported mimeType (use image/png, image/jpeg, or image/webp)', status: 400 };\n }\n let raw: Buffer;\n try {\n raw = Buffer.from(base64, 'base64');\n } catch {\n return { ok: false, error: 'invalid base64', status: 400 };\n }\n if (raw.length === 0 || raw.length > AGENT_AVATAR_MAX_BYTES) {\n return { ok: false, error: `avatar must be non-empty and at most ${AGENT_AVATAR_MAX_BYTES} bytes`, status: 400 };\n }\n const detected = detectImageMimeFromBytes(raw);\n if (!detected || !extMatchesDetectedMime(ext, detected)) {\n return { ok: false, error: 'file content does not match declared image type', status: 400 };\n }\n\n const root = await profileMarkdownRootReal(cfg, id);\n const rootReal = await profileMarkdownRootReal(cfg, id);\n const targetName = `${AGENT_AVATAR_BASENAME}${ext}`;\n const abs = resolveWorkspaceSafePath(root, targetName);\n if (!abs || !isPathUnderWorkspace(rootReal, abs)) {\n return { ok: false, error: 'invalid path', status: 400 };\n }\n for (const name of agentAvatarFilenames()) {\n if (name === targetName) {\n continue;\n }\n const other = resolveWorkspaceSafePath(root, name);\n if (other && isPathUnderWorkspace(rootReal, other)) {\n try {\n await unlink(other);\n } catch {\n /* absent */\n }\n }\n }\n await writeFile(abs, raw);\n return { ok: true, data: { agentId: id, path: abs } };\n}\n\nfunction mimeToExtToMime(ext: '.png' | '.jpg' | '.jpeg' | '.webp'): 'image/png' | 'image/jpeg' | 'image/webp' {\n if (ext === '.png') return 'image/png';\n if (ext === '.webp') return 'image/webp';\n return 'image/jpeg';\n}\n\nfunction extMatchesDetectedMime(\n ext: '.png' | '.jpg' | '.jpeg' | '.webp',\n detected: 'image/png' | 'image/jpeg' | 'image/webp',\n): boolean {\n return detected === mimeToExtToMime(ext);\n}\n\n/** Remove any `agent-avatar.*` in the agent profile markdown root. Idempotent: ok even when no file existed. */\nexport async function deleteAgentAvatarFile(cfg: Config, agentId: string): Promise<AgentAdminResult<{ agentId: string }>> {\n const missingAgent = assertAgentExistsForAvatar(cfg, agentId);\n if (missingAgent) {\n return missingAgent;\n }\n const id = normalizeAgentId(agentId);\n const root = await profileMarkdownRootReal(cfg, id);\n const rootReal = await profileMarkdownRootReal(cfg, id);\n for (const name of agentAvatarFilenames()) {\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs || !isPathUnderWorkspace(rootReal, abs)) {\n continue;\n }\n try {\n await unlink(abs);\n } catch {\n /* absent */\n }\n }\n return { ok: true, data: { agentId: id } };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agents-admin.js","names":["pathResolve"],"sources":["../../../src/gateway/agents-admin.ts"],"sourcesContent":["/**\n * Gateway REST helpers for multi-agent management.\n */\n\nimport { mkdir, readFile, realpath, stat, unlink, writeFile } from 'node:fs/promises';\nimport { join, resolve as pathResolve } from 'node:path';\n\nimport {\n DEFAULT_AGENT_ID,\n listAgentEntries,\n normalizeAgentId,\n resolveAgentDir,\n resolveAgentProfileDir,\n resolveAgentWorkspaceDir,\n resolveDefaultAgentId,\n resolveUserPath,\n validateAgentIdForNewAgent,\n} from '../agent/agent-scope.js';\nimport { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from '../agent/context/workspace.js';\nimport { seedAgentProfileMarkdownFiles } from '../agent/context/workspace-seed.js';\nimport {\n applyAgentConfig,\n findAgentEntryIndex,\n pruneAgentConfig,\n removeAgentDirsFromDisk,\n} from '../commands/agents.config.js';\nimport type { Config } from '../config/schema.js';\nimport { WORKSPACE_FILES } from '../config/paths.js';\nimport { resolveEffectiveAgentProfile } from '../config/agent-profile.js';\nimport { resolveEffectiveTypedModels } from '../config/agent-typed-models.js';\nimport type { AgentTypedModel } from '../config/schema.js';\nimport { normalizePatchTypedModels } from './hono/lib/agent-model.js';\nimport { GATEWAY_BUILTIN_TOOL_IDS } from './agent-builtin-tools.js';\nimport { isPathUnderWorkspace, resolveWorkspaceSafePath } from './workspace-editor-path.js';\n\nconst EDITABLE_PROFILE_MARKDOWN_NAMES = new Set<string>([\n ...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES,\n WORKSPACE_FILES.BOOTSTRAP,\n]);\n\nexport type GatewayAgentTypedModelsInfo = {\n defaults: AgentTypedModel[];\n entry?: AgentTypedModel[];\n effective: AgentTypedModel[];\n};\n\nexport type GatewayAgentRow = {\n id: string;\n name?: string;\n description?: string;\n /** Value from `IDENTITY.md` **Avatar:** line when present (may be URL, `xopc:…`, etc.). */\n avatar?: string;\n workspace: string;\n /** Absolute directory for profile Markdown (`SOUL.md`, …) and gateway avatars: `agents/<id>/profile/`. */\n profileDir: string;\n model?: { primary?: string; fallbacks?: string[] };\n typedModels: GatewayAgentTypedModelsInfo;\n isDefault: boolean;\n skills: {\n defaults: string[];\n entry?: string[];\n effectiveAllowlist?: string[];\n };\n tools: {\n defaultsDisable: string[];\n entryDisable: string[];\n effectiveDisable: string[];\n };\n};\n\nexport type GatewayAgentsListResponse = {\n defaultId: string;\n agents: GatewayAgentRow[];\n builtinToolIds: string[];\n};\n\nfunction collectAgentIdsForList(cfg: Config): string[] {\n const entries = listAgentEntries(cfg).filter((e) => e.enabled !== false);\n const defaultId = resolveDefaultAgentId(cfg);\n if (entries.length === 0) {\n return [defaultId];\n }\n const ids = new Set<string>();\n for (const e of entries) {\n ids.add(normalizeAgentId(e.id));\n }\n ids.add(defaultId);\n return [...ids];\n}\n\n/** Extract `**Avatar:**` value from profile IDENTITY.md (same line shape as the gateway console parser). */\nexport function extractAvatarFromIdentityMarkdown(content: string): string | undefined {\n for (const line of content.split('\\n')) {\n const match = line.match(/^[-*]\\s+\\*\\*Avatar:\\*\\*\\s*(.*)/i);\n if (match) {\n const v = match[1]?.trim() ?? '';\n return v.length > 0 ? v : undefined;\n }\n }\n return undefined;\n}\n\nexport async function listGatewayAgents(cfg: Config): Promise<GatewayAgentsListResponse> {\n const defaultId = resolveDefaultAgentId(cfg);\n const agents: GatewayAgentRow[] = [];\n const defaultsSkills = cfg.agents?.defaults?.skills;\n const defaultsDisable = cfg.agents?.defaults?.tools?.disable ?? [];\n const defaultsTypedModels = cfg.agents?.defaults?.models ?? [];\n for (const id of collectAgentIdsForList(cfg)) {\n const profile = resolveEffectiveAgentProfile(cfg, id);\n const entry = listAgentEntries(cfg).find((e) => normalizeAgentId(e.id) === id);\n const model =\n profile.primaryModelRef || profile.fallbacks.length > 0\n ? {\n ...(profile.primaryModelRef ? { primary: profile.primaryModelRef } : {}),\n ...(profile.fallbacks.length > 0 ? { fallbacks: profile.fallbacks } : {}),\n }\n : undefined;\n const entrySkills = entry?.skills;\n const entryDisable = entry?.tools?.disable ?? [];\n const entryTypedModels = entry?.models;\n const effectiveTypedMap = resolveEffectiveTypedModels(cfg, id);\n const effectiveTypedModels = [...effectiveTypedMap.values()];\n let avatar: string | undefined;\n try {\n const identityPath = join(resolveAgentProfileDir(cfg, id), WORKSPACE_FILES.IDENTITY);\n const content = await readFile(identityPath, 'utf-8');\n avatar = extractAvatarFromIdentityMarkdown(content);\n } catch {\n /* missing IDENTITY.md or unreadable */\n }\n agents.push({\n id,\n ...(entry?.name?.trim() ? { name: entry.name.trim() } : {}),\n ...(entry?.description?.trim() ? { description: entry.description.trim() } : {}),\n ...(avatar ? { avatar } : {}),\n workspace: profile.resolvedWorkspacePath,\n profileDir: resolveAgentProfileDir(cfg, id),\n ...(model ? { model } : {}),\n typedModels: {\n defaults: [...defaultsTypedModels],\n ...(entryTypedModels !== undefined ? { entry: [...entryTypedModels] } : {}),\n effective: effectiveTypedModels,\n },\n isDefault: id === defaultId,\n skills: {\n defaults: defaultsSkills ? [...defaultsSkills] : [],\n ...(entrySkills !== undefined ? { entry: [...entrySkills] } : {}),\n ...(profile.skillsAllowlist !== undefined\n ? { effectiveAllowlist: [...profile.skillsAllowlist] }\n : {}),\n },\n tools: {\n defaultsDisable: [...defaultsDisable],\n entryDisable: [...entryDisable],\n effectiveDisable: [...profile.tools.disable].sort((a, b) => a.localeCompare(b)),\n },\n });\n }\n agents.sort((a, b) => a.id.localeCompare(b.id));\n return { defaultId, agents, builtinToolIds: [...GATEWAY_BUILTIN_TOOL_IDS] };\n}\n\nexport type CreateAgentBody = {\n /** Display name stored on the agent entry. */\n name: string;\n /** Optional id seed; normalized agent id defaults from `name` when omitted. */\n id?: string;\n workspace: string;\n model?: string;\n agentDir?: string;\n description?: string;\n /** Initial `agents.list[].tools.disable` for the new entry. */\n toolsDisable?: string[];\n /** Profile markdown files to write after seeding (e.g. `IDENTITY.md`, `SOUL.md`). */\n profileFiles?: Record<string, string>;\n};\n\nexport type AgentAdminHttpStatus = 400 | 404 | 409;\n\nexport type AgentAdminResult<T> =\n | { ok: true; data: T }\n | { ok: false; error: string; status?: AgentAdminHttpStatus };\n\nfunction requireNonMain(id: string): AgentAdminResult<never> | null {\n if (normalizeAgentId(id) === DEFAULT_AGENT_ID) {\n return { ok: false, error: `\"${DEFAULT_AGENT_ID}\" is reserved`, status: 400 };\n }\n return null;\n}\n\nexport function prepareCreateAgent(\n cfg: Config,\n body: CreateAgentBody,\n): AgentAdminResult<{ nextConfig: Config; agentId: string; workspace: string }> {\n const name = body.name?.trim() ?? '';\n if (!name) {\n return { ok: false, error: 'name is required', status: 400 };\n }\n const workspace = body.workspace?.trim() ?? '';\n if (!workspace) {\n return { ok: false, error: 'workspace is required', status: 400 };\n }\n const idRes = validateAgentIdForNewAgent(body.id, name);\n if (idRes.ok === false) {\n return { ok: false, error: idRes.error, status: 400 };\n }\n const agentId = idRes.agentId;\n if (findAgentEntryIndex(listAgentEntries(cfg), agentId) >= 0) {\n return { ok: false, error: `agent \"${agentId}\" already exists`, status: 409 };\n }\n if (body.profileFiles !== undefined) {\n if (typeof body.profileFiles !== 'object' || body.profileFiles === null || Array.isArray(body.profileFiles)) {\n return { ok: false, error: 'profileFiles must be an object', status: 400 };\n }\n for (const [name, content] of Object.entries(body.profileFiles)) {\n const bad = assertAllowedFile(name);\n if (bad) {\n return bad;\n }\n if (typeof content !== 'string') {\n return { ok: false, error: `profileFiles[\"${name}\"] must be a string`, status: 400 };\n }\n }\n }\n\n const wsAbs = resolveUserPath(workspace);\n let next = applyAgentConfig(cfg, {\n agentId,\n name,\n workspace: wsAbs,\n ...(body.model?.trim() ? { model: body.model.trim() } : {}),\n ...(body.agentDir?.trim() ? { agentDir: body.agentDir.trim() } : {}),\n ...(body.description?.trim() ? { description: body.description.trim() } : {}),\n });\n\n if (body.toolsDisable !== undefined) {\n const list = [...listAgentEntries(next)];\n const idx = findAgentEntryIndex(list, agentId);\n if (idx >= 0) {\n type Entry = (typeof list)[number];\n const entry: Entry = { ...list[idx] };\n const disable = body.toolsDisable.map((s) => String(s).trim()).filter(Boolean);\n entry.tools = { ...entry.tools, disable };\n list[idx] = entry;\n next = {\n ...next,\n agents: {\n ...next.agents,\n list,\n },\n };\n }\n }\n\n return { ok: true, data: { nextConfig: next, agentId, workspace: wsAbs } };\n}\n\nexport type PreparedBatchCreateAgent = {\n agentId: string;\n profileFiles?: Record<string, string>;\n};\n\nexport function prepareCreateAgentsBatch(\n cfg: Config,\n bodies: CreateAgentBody[],\n): AgentAdminResult<{ nextConfig: Config; created: PreparedBatchCreateAgent[] }> {\n if (!Array.isArray(bodies) || bodies.length === 0) {\n return { ok: false, error: 'agents must be a non-empty array', status: 400 };\n }\n let next = cfg;\n const created: PreparedBatchCreateAgent[] = [];\n for (const body of bodies) {\n const prep = prepareCreateAgent(next, body);\n if (prep.ok === false) {\n return prep;\n }\n next = prep.data.nextConfig;\n created.push({\n agentId: prep.data.agentId,\n ...(body.profileFiles !== undefined ? { profileFiles: body.profileFiles } : {}),\n });\n }\n return { ok: true, data: { nextConfig: next, created } };\n}\n\nexport async function finalizeCreateAgentDirs(\n cfg: Config,\n agentId: string,\n opts?: { profileFiles?: Record<string, string> },\n): Promise<AgentAdminResult<void>> {\n const wsPath = resolveAgentWorkspaceDir(cfg, agentId);\n const profilePath = resolveAgentProfileDir(cfg, agentId);\n const adPath = resolveAgentDir(cfg, agentId);\n await mkdir(wsPath, { recursive: true });\n await mkdir(profilePath, { recursive: true });\n await mkdir(adPath, { recursive: true });\n const id = normalizeAgentId(agentId);\n const entry = listAgentEntries(cfg).find((e) => normalizeAgentId(e.id) === id);\n const displayName = entry?.name?.trim() || id;\n seedAgentProfileMarkdownFiles(profilePath, wsPath, { displayName });\n\n const profileFiles = opts?.profileFiles;\n if (profileFiles && Object.keys(profileFiles).length > 0) {\n for (const [name, content] of Object.entries(profileFiles)) {\n const written = await writeAgentProfileFile(cfg, agentId, name, content);\n if (written.ok === false) {\n return written;\n }\n }\n }\n\n return { ok: true, data: undefined };\n}\n\nexport type UpdateAgentBody = {\n name?: string;\n description?: string | null;\n workspace?: string;\n model?: string | null;\n agentDir?: string | null;\n setDefault?: boolean;\n /** Replace `agents.list[].skills`; `null` removes the key (inherit defaults). */\n skills?: string[] | null;\n /** Replace `agents.list[].tools.disable`; `null` clears entry-level disables. */\n toolsDisable?: string[] | null;\n /** Replace `agents.list[].models`; `null` removes entry overrides (inherit defaults). */\n models?: AgentTypedModel[] | null;\n};\n\nexport function prepareUpdateAgent(\n cfg: Config,\n agentIdRaw: string,\n body: UpdateAgentBody,\n): AgentAdminResult<{ nextConfig: Config }> {\n const agentId = normalizeAgentId(agentIdRaw);\n let list = [...listAgentEntries(cfg)];\n let idx = findAgentEntryIndex(list, agentId);\n if (idx < 0 && agentId === resolveDefaultAgentId(cfg)) {\n list = [...list, { id: agentId, enabled: true as const }];\n idx = list.length - 1;\n }\n if (idx < 0) {\n return { ok: false, error: `agent \"${agentId}\" not found`, status: 404 };\n }\n\n type Entry = (typeof list)[number];\n const entry: Entry = { ...list[idx] };\n\n if (body.name !== undefined) {\n const n = body.name.trim();\n if (n) {\n entry.name = n;\n }\n }\n if (body.description !== undefined) {\n if (body.description === null || String(body.description).trim() === '') {\n delete entry.description;\n } else {\n entry.description = String(body.description).trim();\n }\n }\n if (body.workspace !== undefined) {\n const w = body.workspace.trim();\n if (w) {\n entry.workspace = resolveUserPath(w);\n }\n }\n if (body.model !== undefined) {\n if (body.model === null) {\n delete entry.model;\n } else {\n const trimmed = String(body.model).trim();\n if (!trimmed) {\n return { ok: false, error: 'model must be a non-empty string or null', status: 400 };\n }\n entry.model = { primary: trimmed };\n }\n }\n if (body.agentDir !== undefined) {\n if (body.agentDir === null || String(body.agentDir).trim() === '') {\n delete entry.agentDir;\n } else {\n entry.agentDir = String(body.agentDir).trim();\n }\n }\n\n if (body.skills !== undefined) {\n if (body.skills === null) {\n delete entry.skills;\n } else {\n const next = body.skills.map((s) => String(s).trim()).filter(Boolean);\n if (next.length === 0) {\n entry.skills = [];\n } else {\n entry.skills = next;\n }\n }\n }\n\n if (body.toolsDisable !== undefined) {\n if (body.toolsDisable === null) {\n if (entry.tools) {\n delete entry.tools.disable;\n if (Object.keys(entry.tools).length === 0) {\n delete entry.tools;\n }\n }\n } else {\n const next = body.toolsDisable.map((s) => String(s).trim()).filter(Boolean);\n entry.tools = { ...entry.tools, disable: next };\n }\n }\n\n if (body.models !== undefined) {\n if (body.models === null) {\n delete entry.models;\n } else {\n const normalized = normalizePatchTypedModels(body.models);\n if (normalized === null || normalized === undefined) {\n delete entry.models;\n } else {\n entry.models = normalized;\n }\n }\n }\n\n list[idx] = entry;\n let next: Config = {\n ...cfg,\n agents: {\n ...cfg.agents,\n list,\n },\n };\n\n if (body.setDefault === true) {\n next = {\n ...next,\n agents: {\n ...next.agents,\n default: agentId,\n },\n };\n }\n return { ok: true, data: { nextConfig: next } };\n}\n\nexport function prepareDeleteAgent(\n cfg: Config,\n agentIdRaw: string,\n): AgentAdminResult<{ nextConfig: Config; agentId: string }> {\n const agentId = normalizeAgentId(agentIdRaw);\n const reserved = requireNonMain(agentId);\n if (reserved) {\n return reserved;\n }\n if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) {\n return { ok: false, error: `agent \"${agentId}\" not found`, status: 404 };\n }\n const { config: pruned } = pruneAgentConfig(cfg, agentId);\n return { ok: true, data: { nextConfig: pruned, agentId } };\n}\n\nexport async function runAfterDeletePurge(cfg: Config, agentId: string): Promise<void> {\n await removeAgentDirsFromDisk(cfg, agentId);\n}\n\nexport type AgentFileEntry = {\n name: string;\n missing: boolean;\n size?: number;\n updatedAtMs?: number;\n};\n\nasync function profileMarkdownRootReal(cfg: Config, agentId: string): Promise<string> {\n const dir = resolveAgentProfileDir(cfg, agentId);\n await mkdir(dir, { recursive: true });\n try {\n return await realpath(dir);\n } catch {\n return pathResolve(dir);\n }\n}\n\nfunction assertAllowedFile(name: string): AgentAdminResult<never> | null {\n if (!name || name.includes('/') || name.includes('\\\\') || !EDITABLE_PROFILE_MARKDOWN_NAMES.has(name)) {\n return { ok: false, error: `unsupported file \"${name}\"`, status: 400 };\n }\n return null;\n}\n\nexport async function listAgentProfileFiles(\n cfg: Config,\n agentId: string,\n): Promise<AgentAdminResult<{ agentId: string; profileDir: string; files: AgentFileEntry[] }>> {\n const id = normalizeAgentId(agentId);\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n const root = await profileMarkdownRootReal(cfg, id);\n const names = [...EDITABLE_PROFILE_MARKDOWN_NAMES];\n const files: AgentFileEntry[] = [];\n for (const name of names.sort((a, b) => a.localeCompare(b))) {\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n continue;\n }\n try {\n const st = await stat(abs);\n if (!st.isFile()) {\n continue;\n }\n files.push({\n name,\n missing: false,\n size: st.size,\n updatedAtMs: st.mtimeMs,\n });\n } catch {\n files.push({ name, missing: true });\n }\n }\n files.sort((a, b) => a.name.localeCompare(b.name));\n return { ok: true, data: { agentId: id, profileDir: root, files } };\n}\n\nexport async function readAgentProfileFile(\n cfg: Config,\n agentId: string,\n name: string,\n): Promise<AgentAdminResult<{ agentId: string; content: string; path: string }>> {\n const bad = assertAllowedFile(name);\n if (bad) {\n return bad;\n }\n const id = normalizeAgentId(agentId);\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n const root = await profileMarkdownRootReal(cfg, id);\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n return { ok: false, error: 'invalid path', status: 400 };\n }\n try {\n const content = await readFile(abs, 'utf-8');\n return { ok: true, data: { agentId: id, content, path: abs } };\n } catch {\n return { ok: false, error: 'file not found', status: 404 };\n }\n}\n\nexport async function writeAgentProfileFile(\n cfg: Config,\n agentId: string,\n name: string,\n content: string,\n): Promise<AgentAdminResult<{ agentId: string; path: string }>> {\n const bad = assertAllowedFile(name);\n if (bad) {\n return bad;\n }\n const id = normalizeAgentId(agentId);\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n const root = await profileMarkdownRootReal(cfg, id);\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n return { ok: false, error: 'invalid path', status: 400 };\n }\n const rootReal = await profileMarkdownRootReal(cfg, id);\n if (!isPathUnderWorkspace(rootReal, abs)) {\n return { ok: false, error: 'path escapes profile markdown root', status: 400 };\n }\n await writeFile(abs, content, 'utf-8');\n return { ok: true, data: { agentId: id, path: abs } };\n}\n\n// ---------------------------------------------------------------------------\n// Binary agent avatar (profile markdown root dir, not a SOUL/IDENTITY markdown file)\n// ---------------------------------------------------------------------------\n\nconst AGENT_AVATAR_MAX_BYTES = 512 * 1024;\nconst AGENT_AVATAR_BASENAME = 'agent-avatar';\n\nconst AGENT_AVATAR_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp'] as const;\n\nfunction agentAvatarFilenames(): string[] {\n return AGENT_AVATAR_EXTENSIONS.map((ext) => `${AGENT_AVATAR_BASENAME}${ext}`);\n}\n\nfunction mimeToExt(mime: string): '.png' | '.jpg' | '.jpeg' | '.webp' | null {\n const m = mime.toLowerCase().trim();\n if (m === 'image/png') return '.png';\n if (m === 'image/jpeg' || m === 'image/jpg') return '.jpg';\n if (m === 'image/webp') return '.webp';\n return null;\n}\n\nfunction detectImageMimeFromBytes(buf: Uint8Array): 'image/png' | 'image/jpeg' | 'image/webp' | null {\n if (buf.length >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) {\n return 'image/png';\n }\n if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {\n return 'image/jpeg';\n }\n if (\n buf.length >= 12 &&\n buf[0] === 0x52 &&\n buf[1] === 0x49 &&\n buf[2] === 0x46 &&\n buf[3] === 0x46 &&\n buf[8] === 0x57 &&\n buf[9] === 0x45 &&\n buf[10] === 0x42 &&\n buf[11] === 0x50\n ) {\n return 'image/webp';\n }\n return null;\n}\n\nfunction assertAgentExistsForAvatar(cfg: Config, id: string): AgentAdminResult<never> | null {\n if (collectAgentIdsForList(cfg).every((x) => x !== id)) {\n return { ok: false, error: `agent \"${id}\" not found`, status: 404 };\n }\n return null;\n}\n\nexport async function readAgentAvatarFile(\n cfg: Config,\n agentId: string,\n): Promise<AgentAdminResult<{ agentId: string; buffer: Buffer; contentType: string; path: string }>> {\n const missingAgent = assertAgentExistsForAvatar(cfg, agentId);\n if (missingAgent) {\n return missingAgent;\n }\n const id = normalizeAgentId(agentId);\n const root = await profileMarkdownRootReal(cfg, id);\n for (const name of agentAvatarFilenames()) {\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs) {\n continue;\n }\n try {\n const st = await stat(abs);\n if (!st.isFile() || st.size <= 0 || st.size > AGENT_AVATAR_MAX_BYTES) {\n continue;\n }\n const buffer = await readFile(abs);\n const detected = detectImageMimeFromBytes(buffer);\n if (!detected) {\n continue;\n }\n return { ok: true, data: { agentId: id, buffer, contentType: detected, path: abs } };\n } catch {\n /* try next */\n }\n }\n return { ok: false, error: 'avatar not found', status: 404 };\n}\n\nexport async function writeAgentAvatarFromBase64(\n cfg: Config,\n agentId: string,\n base64: string,\n mimeType: string,\n): Promise<AgentAdminResult<{ agentId: string; path: string }>> {\n const missingAgent = assertAgentExistsForAvatar(cfg, agentId);\n if (missingAgent) {\n return missingAgent;\n }\n const id = normalizeAgentId(agentId);\n const ext = mimeToExt(mimeType);\n if (!ext) {\n return { ok: false, error: 'unsupported mimeType (use image/png, image/jpeg, or image/webp)', status: 400 };\n }\n let raw: Buffer;\n try {\n raw = Buffer.from(base64, 'base64');\n } catch {\n return { ok: false, error: 'invalid base64', status: 400 };\n }\n if (raw.length === 0 || raw.length > AGENT_AVATAR_MAX_BYTES) {\n return { ok: false, error: `avatar must be non-empty and at most ${AGENT_AVATAR_MAX_BYTES} bytes`, status: 400 };\n }\n const detected = detectImageMimeFromBytes(raw);\n if (!detected || !extMatchesDetectedMime(ext, detected)) {\n return { ok: false, error: 'file content does not match declared image type', status: 400 };\n }\n\n const root = await profileMarkdownRootReal(cfg, id);\n const rootReal = await profileMarkdownRootReal(cfg, id);\n const targetName = `${AGENT_AVATAR_BASENAME}${ext}`;\n const abs = resolveWorkspaceSafePath(root, targetName);\n if (!abs || !isPathUnderWorkspace(rootReal, abs)) {\n return { ok: false, error: 'invalid path', status: 400 };\n }\n for (const name of agentAvatarFilenames()) {\n if (name === targetName) {\n continue;\n }\n const other = resolveWorkspaceSafePath(root, name);\n if (other && isPathUnderWorkspace(rootReal, other)) {\n try {\n await unlink(other);\n } catch {\n /* absent */\n }\n }\n }\n await writeFile(abs, raw);\n return { ok: true, data: { agentId: id, path: abs } };\n}\n\nfunction mimeToExtToMime(ext: '.png' | '.jpg' | '.jpeg' | '.webp'): 'image/png' | 'image/jpeg' | 'image/webp' {\n if (ext === '.png') return 'image/png';\n if (ext === '.webp') return 'image/webp';\n return 'image/jpeg';\n}\n\nfunction extMatchesDetectedMime(\n ext: '.png' | '.jpg' | '.jpeg' | '.webp',\n detected: 'image/png' | 'image/jpeg' | 'image/webp',\n): boolean {\n return detected === mimeToExtToMime(ext);\n}\n\n/** Remove any `agent-avatar.*` in the agent profile markdown root. Idempotent: ok even when no file existed. */\nexport async function deleteAgentAvatarFile(cfg: Config, agentId: string): Promise<AgentAdminResult<{ agentId: string }>> {\n const missingAgent = assertAgentExistsForAvatar(cfg, agentId);\n if (missingAgent) {\n return missingAgent;\n }\n const id = normalizeAgentId(agentId);\n const root = await profileMarkdownRootReal(cfg, id);\n const rootReal = await profileMarkdownRootReal(cfg, id);\n for (const name of agentAvatarFilenames()) {\n const abs = resolveWorkspaceSafePath(root, name);\n if (!abs || !isPathUnderWorkspace(rootReal, abs)) {\n continue;\n }\n try {\n await unlink(abs);\n } catch {\n /* absent */\n }\n }\n return { ok: true, data: { agentId: id } };\n}\n"],"mappings":"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"}
@@ -1,13 +1,14 @@
1
+ import { isMaskedSecretPatchValue, maskSecretLength } from "./hono/lib/mask-secret-length.js";
1
2
  //#region src/gateway/config-tools-web.ts
2
3
  function isMaskedApiKey(v) {
3
- return v === "***" || v === "••••••••••••";
4
+ return typeof v === "string" && isMaskedSecretPatchValue(v);
4
5
  }
5
6
  function safeToolsWebForGet(config) {
6
7
  const web = config.tools?.web;
7
8
  const search = web?.search;
8
9
  const providersOut = (search?.providers ?? []).map((p) => ({
9
10
  type: p.type,
10
- apiKey: p.apiKey && p.apiKey.trim().length > 0 ? "***" : "",
11
+ apiKey: p.apiKey && p.apiKey.trim().length > 0 ? maskSecretLength(p.apiKey) : "",
11
12
  url: typeof p.url === "string" ? p.url : "",
12
13
  disabled: Boolean(p.disabled)
13
14
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"config-tools-web.js","names":[],"sources":["../../../src/gateway/config-tools-web.ts"],"sourcesContent":["import type { Config, SearchProviderEntry } from '../config/schema.js';\n\nexport function isMaskedApiKey(v: unknown): boolean {\n return v === '***' || v === '••••••••••••';\n}\n\nexport function safeToolsWebForGet(config: Config): {\n web: {\n region: 'cn' | 'global' | null;\n search: {\n maxResults: number;\n providers: Array<{ type: string; apiKey: string; url: string; disabled: boolean }>;\n };\n blocklist: {\n enabled: boolean;\n domains: string[];\n };\n };\n} {\n const web = config.tools?.web;\n const search = web?.search;\n const providers = search?.providers ?? [];\n const providersOut = providers.map((p) => ({\n type: p.type,\n apiKey: p.apiKey && p.apiKey.trim().length > 0 ? '***' : '',\n url: typeof p.url === 'string' ? p.url : '',\n disabled: Boolean(p.disabled),\n }));\n const blocklist = web?.blocklist;\n return {\n web: {\n region: web?.region ?? null,\n search: {\n maxResults: search?.maxResults ?? 5,\n providers: providersOut,\n },\n blocklist: {\n enabled: blocklist?.enabled === true,\n domains: Array.isArray(blocklist?.domains)\n ? blocklist.domains.filter((d): d is string => typeof d === 'string' && d.trim().length > 0)\n : [],\n },\n },\n };\n}\n\n/**\n * Merge `body.tools.web` into config. Returns an error message on validation failure.\n */\nexport function applyToolsWebPatch(config: Config, body: Record<string, unknown>): string | undefined {\n const tools = body.tools;\n if (!tools || typeof tools !== 'object') return undefined;\n const tw = (tools as { web?: unknown }).web;\n if (!tw || typeof tw !== 'object') return undefined;\n\n const incoming = tw as Record<string, unknown>;\n\n if (!config.tools) {\n config.tools = { web: {} };\n }\n if (!config.tools.web) {\n config.tools.web = {};\n }\n if (!config.tools.web.search) {\n config.tools.web.search = { maxResults: 5, providers: [] };\n }\n\n const prevSearch = config.tools.web.search;\n const prevProviders = prevSearch.providers;\n\n if (incoming.region !== undefined) {\n if (incoming.region === null || incoming.region === '' || incoming.region === 'auto') {\n delete config.tools.web.region;\n } else if (incoming.region === 'cn' || incoming.region === 'global') {\n config.tools.web.region = incoming.region;\n } else {\n return 'Invalid tools.web.region';\n }\n }\n\n if (incoming.blocklist !== undefined) {\n if (incoming.blocklist === null) {\n delete config.tools.web.blocklist;\n } else if (typeof incoming.blocklist !== 'object') {\n return 'Invalid tools.web.blocklist';\n } else {\n const bl = incoming.blocklist as Record<string, unknown>;\n const next = {\n ...(config.tools.web.blocklist ?? { enabled: false, domains: [] }),\n };\n if (bl.enabled !== undefined) {\n next.enabled = Boolean(bl.enabled);\n }\n if (bl.domains !== undefined) {\n if (!Array.isArray(bl.domains)) {\n return 'tools.web.blocklist.domains must be an array';\n }\n next.domains = bl.domains\n .filter((d): d is string => typeof d === 'string' && d.trim().length > 0)\n .map((d) => d.trim());\n }\n config.tools.web.blocklist = next;\n }\n }\n\n const sr = incoming.search;\n if (sr === undefined) return undefined;\n if (typeof sr !== 'object' || sr === null) {\n return 'Invalid tools.web.search';\n }\n const s = sr as Record<string, unknown>;\n\n if (s.maxResults !== undefined) {\n const n = typeof s.maxResults === 'number' ? s.maxResults : Number(s.maxResults);\n if (!Number.isFinite(n) || n < 1 || n > 50) {\n return 'tools.web.search.maxResults must be between 1 and 50';\n }\n config.tools.web.search.maxResults = Math.floor(n);\n }\n\n if (!('providers' in s)) {\n return undefined;\n }\n\n if (!Array.isArray(s.providers)) {\n return 'tools.web.search.providers must be an array';\n }\n\n const merged: SearchProviderEntry[] = [];\n for (let i = 0; i < s.providers.length; i++) {\n const rawRow = s.providers[i];\n if (!rawRow || typeof rawRow !== 'object') {\n return 'Invalid search provider entry';\n }\n const row = rawRow as Record<string, unknown>;\n const type = row.type;\n if (type !== 'brave' && type !== 'tavily' && type !== 'bing' && type !== 'searxng') {\n return `Invalid search provider type: ${String(type)}`;\n }\n const prev = prevProviders?.[i];\n let apiKey = typeof row.apiKey === 'string' ? row.apiKey : '';\n if (isMaskedApiKey(apiKey) && prev?.apiKey) {\n apiKey = prev.apiKey;\n }\n const urlRaw = typeof row.url === 'string' ? row.url.trim().replace(/\\/+$/, '') : '';\n const entry: SearchProviderEntry = { type };\n if (type === 'searxng') {\n if (urlRaw) entry.url = urlRaw;\n if (apiKey) entry.apiKey = apiKey;\n } else {\n if (apiKey) entry.apiKey = apiKey;\n }\n if (row.disabled === true) {\n entry.disabled = true;\n }\n merged.push(entry);\n }\n config.tools.web.search.providers = merged;\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-tools-web.js","names":[],"sources":["../../../src/gateway/config-tools-web.ts"],"sourcesContent":["import type { Config, SearchProviderEntry } from '../config/schema.js';\nimport {\n isMaskedSecretPatchValue,\n maskSecretLength,\n} from './hono/lib/mask-secret-length.js';\n\nexport function isMaskedApiKey(v: unknown): boolean {\n return typeof v === 'string' && isMaskedSecretPatchValue(v);\n}\n\nexport function safeToolsWebForGet(config: Config): {\n web: {\n region: 'cn' | 'global' | null;\n search: {\n maxResults: number;\n providers: Array<{ type: string; apiKey: string; url: string; disabled: boolean }>;\n };\n blocklist: {\n enabled: boolean;\n domains: string[];\n };\n };\n} {\n const web = config.tools?.web;\n const search = web?.search;\n const providers = search?.providers ?? [];\n const providersOut = providers.map((p) => ({\n type: p.type,\n apiKey: p.apiKey && p.apiKey.trim().length > 0 ? maskSecretLength(p.apiKey) : '',\n url: typeof p.url === 'string' ? p.url : '',\n disabled: Boolean(p.disabled),\n }));\n const blocklist = web?.blocklist;\n return {\n web: {\n region: web?.region ?? null,\n search: {\n maxResults: search?.maxResults ?? 5,\n providers: providersOut,\n },\n blocklist: {\n enabled: blocklist?.enabled === true,\n domains: Array.isArray(blocklist?.domains)\n ? blocklist.domains.filter((d): d is string => typeof d === 'string' && d.trim().length > 0)\n : [],\n },\n },\n };\n}\n\n/**\n * Merge `body.tools.web` into config. Returns an error message on validation failure.\n */\nexport function applyToolsWebPatch(config: Config, body: Record<string, unknown>): string | undefined {\n const tools = body.tools;\n if (!tools || typeof tools !== 'object') return undefined;\n const tw = (tools as { web?: unknown }).web;\n if (!tw || typeof tw !== 'object') return undefined;\n\n const incoming = tw as Record<string, unknown>;\n\n if (!config.tools) {\n config.tools = { web: {} };\n }\n if (!config.tools.web) {\n config.tools.web = {};\n }\n if (!config.tools.web.search) {\n config.tools.web.search = { maxResults: 5, providers: [] };\n }\n\n const prevSearch = config.tools.web.search;\n const prevProviders = prevSearch.providers;\n\n if (incoming.region !== undefined) {\n if (incoming.region === null || incoming.region === '' || incoming.region === 'auto') {\n delete config.tools.web.region;\n } else if (incoming.region === 'cn' || incoming.region === 'global') {\n config.tools.web.region = incoming.region;\n } else {\n return 'Invalid tools.web.region';\n }\n }\n\n if (incoming.blocklist !== undefined) {\n if (incoming.blocklist === null) {\n delete config.tools.web.blocklist;\n } else if (typeof incoming.blocklist !== 'object') {\n return 'Invalid tools.web.blocklist';\n } else {\n const bl = incoming.blocklist as Record<string, unknown>;\n const next = {\n ...(config.tools.web.blocklist ?? { enabled: false, domains: [] }),\n };\n if (bl.enabled !== undefined) {\n next.enabled = Boolean(bl.enabled);\n }\n if (bl.domains !== undefined) {\n if (!Array.isArray(bl.domains)) {\n return 'tools.web.blocklist.domains must be an array';\n }\n next.domains = bl.domains\n .filter((d): d is string => typeof d === 'string' && d.trim().length > 0)\n .map((d) => d.trim());\n }\n config.tools.web.blocklist = next;\n }\n }\n\n const sr = incoming.search;\n if (sr === undefined) return undefined;\n if (typeof sr !== 'object' || sr === null) {\n return 'Invalid tools.web.search';\n }\n const s = sr as Record<string, unknown>;\n\n if (s.maxResults !== undefined) {\n const n = typeof s.maxResults === 'number' ? s.maxResults : Number(s.maxResults);\n if (!Number.isFinite(n) || n < 1 || n > 50) {\n return 'tools.web.search.maxResults must be between 1 and 50';\n }\n config.tools.web.search.maxResults = Math.floor(n);\n }\n\n if (!('providers' in s)) {\n return undefined;\n }\n\n if (!Array.isArray(s.providers)) {\n return 'tools.web.search.providers must be an array';\n }\n\n const merged: SearchProviderEntry[] = [];\n for (let i = 0; i < s.providers.length; i++) {\n const rawRow = s.providers[i];\n if (!rawRow || typeof rawRow !== 'object') {\n return 'Invalid search provider entry';\n }\n const row = rawRow as Record<string, unknown>;\n const type = row.type;\n if (type !== 'brave' && type !== 'tavily' && type !== 'bing' && type !== 'searxng') {\n return `Invalid search provider type: ${String(type)}`;\n }\n const prev = prevProviders?.[i];\n let apiKey = typeof row.apiKey === 'string' ? row.apiKey : '';\n if (isMaskedApiKey(apiKey) && prev?.apiKey) {\n apiKey = prev.apiKey;\n }\n const urlRaw = typeof row.url === 'string' ? row.url.trim().replace(/\\/+$/, '') : '';\n const entry: SearchProviderEntry = { type };\n if (type === 'searxng') {\n if (urlRaw) entry.url = urlRaw;\n if (apiKey) entry.apiKey = apiKey;\n } else {\n if (apiKey) entry.apiKey = apiKey;\n }\n if (row.disabled === true) {\n entry.disabled = true;\n }\n merged.push(entry);\n }\n config.tools.web.search.providers = merged;\n return undefined;\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  import { init_paths_state, resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_agent_scope, resolveAgentProfileDir } from "../agent/agent-scope.js";
3
- import { extractProfileAgentId } from "../config/agent-profile.js";
4
3
  import { init_paths, resolveConfigPath, resolveSessionsDir, resolveSkillsDir } from "../config/paths.js";
4
+ import { extractProfileAgentId } from "../config/agent-profile.js";
5
5
  import { isBareProfileMarkdownFileName, resolveProfileMarkdownPathIfBareName } from "../agent/tools/tool-paths.js";
6
6
  import { isPathUnderWorkspace, resolveWorkspaceSafePath } from "./workspace-editor-path.js";
7
- import { basename, isAbsolute, resolve } from "node:path";
8
7
  import { stat } from "node:fs/promises";
8
+ import { basename, isAbsolute, resolve } from "node:path";
9
9
  //#region src/gateway/file-path-classifier.ts
10
10
  init_agent_scope();
11
11
  init_paths();
@@ -1,3 +1,10 @@
1
+ import type { AgentTypedModel } from '../../../config/schema.js';
2
+ /**
3
+ * Coerce PATCH body `models[]` into validated typed model entries.
4
+ * Returns `null` to clear, `undefined` when input should be skipped, or cleaned array.
5
+ * Empty array after filtering → `null` (same as clear).
6
+ */
7
+ export declare function normalizePatchTypedModels(v: unknown): AgentTypedModel[] | null | undefined;
1
8
  /** Read `primary` from an `AgentModelConfig` object. */
2
9
  export declare function agentModelRefToString(ref: unknown): string | undefined;
3
10
  export declare function agentModelFallbacksToArray(ref: unknown): string[];
@@ -1,4 +1,39 @@
1
1
  //#region src/gateway/hono/lib/agent-model.ts
2
+ const TYPED_MODEL_ID_RE = /^[a-z][a-z0-9_-]{0,63}$/;
3
+ function isValidProviderModelRef(ref) {
4
+ const trimmed = ref.trim();
5
+ const idx = trimmed.indexOf("/");
6
+ return idx > 0 && idx < trimmed.length - 1;
7
+ }
8
+ /**
9
+ * Coerce PATCH body `models[]` into validated typed model entries.
10
+ * Returns `null` to clear, `undefined` when input should be skipped, or cleaned array.
11
+ * Empty array after filtering → `null` (same as clear).
12
+ */
13
+ function normalizePatchTypedModels(v) {
14
+ if (v === void 0) return void 0;
15
+ if (v === null) return null;
16
+ if (!Array.isArray(v)) return void 0;
17
+ const byId = /* @__PURE__ */ new Map();
18
+ for (const raw of v) {
19
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
20
+ const o = raw;
21
+ const id = typeof o.id === "string" ? o.id.trim() : "";
22
+ const model = typeof o.model === "string" ? o.model.trim() : "";
23
+ if (!id || !TYPED_MODEL_ID_RE.test(id) || !model || !isValidProviderModelRef(model)) continue;
24
+ const description = typeof o.description === "string" && o.description.trim() ? o.description.trim().slice(0, 500) : void 0;
25
+ byId.set(id, description ? {
26
+ id,
27
+ description,
28
+ model
29
+ } : {
30
+ id,
31
+ model
32
+ });
33
+ }
34
+ if (byId.size === 0) return null;
35
+ return [...byId.values()];
36
+ }
2
37
  /** Read `primary` from an `AgentModelConfig` object. */
3
38
  function agentModelRefToString(ref) {
4
39
  if (!ref || typeof ref !== "object" || Array.isArray(ref)) return void 0;
@@ -57,6 +92,6 @@ function normalizePatchAgentImageGenerationModel(v) {
57
92
  return out;
58
93
  }
59
94
  //#endregion
60
- export { agentImageGenerationModelAutoProviderFallback, agentImageGenerationModelTimeoutMs, agentModelFallbacksToArray, agentModelRefToString, normalizePatchAgentImageGenerationModel, normalizePatchAgentModel };
95
+ export { agentImageGenerationModelAutoProviderFallback, agentImageGenerationModelTimeoutMs, agentModelFallbacksToArray, agentModelRefToString, normalizePatchAgentImageGenerationModel, normalizePatchAgentModel, normalizePatchTypedModels };
61
96
 
62
97
  //# sourceMappingURL=agent-model.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-model.js","names":[],"sources":["../../../../../src/gateway/hono/lib/agent-model.ts"],"sourcesContent":["/** Read `primary` from an `AgentModelConfig` object. */\nexport function agentModelRefToString(ref: unknown): string | undefined {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return undefined;\n const p = (ref as { primary?: string }).primary;\n return typeof p === 'string' && p.trim() ? p : undefined;\n}\n\nexport function agentModelFallbacksToArray(ref: unknown): string[] {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return [];\n const f = (ref as { fallbacks?: unknown }).fallbacks;\n if (!Array.isArray(f)) return [];\n return f.filter((x): x is string => typeof x === 'string' && x.trim().length > 0);\n}\n\n/**\n * Coerce a PATCH body `model` value into the canonical `{ primary, fallbacks? }`\n * object form. Returns `undefined` when the input has no usable primary so the\n * caller can skip the assignment.\n */\nexport function normalizePatchAgentModel(v: unknown): { primary: string; fallbacks?: string[] } | undefined {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n const o = v as Record<string, unknown>;\n const primary = typeof o.primary === 'string' ? o.primary.trim() : '';\n if (!primary) return undefined;\n const fallbacks = Array.isArray(o.fallbacks)\n ? o.fallbacks.filter((x): x is string => typeof x === 'string' && x.trim().length > 0)\n : [];\n return fallbacks.length > 0 ? { primary, fallbacks } : { primary };\n}\n\n/** Read `timeoutMs` from an image-generation model ref (only set when ref is an object). */\nexport function agentImageGenerationModelTimeoutMs(ref: unknown): number | null {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return null;\n const v = (ref as { timeoutMs?: unknown }).timeoutMs;\n return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : null;\n}\n\n/** Read `autoProviderFallback` from an image-generation model ref. Defaults to false. */\nexport function agentImageGenerationModelAutoProviderFallback(ref: unknown): boolean {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return false;\n return (ref as { autoProviderFallback?: unknown }).autoProviderFallback === true;\n}\n\n/**\n * PATCH body normalizer for `agents.defaults.imageGenerationModel`. Always\n * emits `{ primary, fallbacks?, timeoutMs?, autoProviderFallback? }`. Returns\n * `undefined` when the input has no usable primary so the caller can skip the\n * assignment.\n */\nexport function normalizePatchAgentImageGenerationModel(\n v: unknown,\n): { primary: string; fallbacks?: string[]; timeoutMs?: number; autoProviderFallback?: true } | undefined {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n const o = v as Record<string, unknown>;\n const primary = typeof o.primary === 'string' ? o.primary.trim() : '';\n if (!primary) return undefined;\n const out: { primary: string; fallbacks?: string[]; timeoutMs?: number; autoProviderFallback?: true } = { primary };\n const fallbacks = Array.isArray(o.fallbacks)\n ? o.fallbacks\n .filter((x): x is string => typeof x === 'string' && x.trim().length > 0)\n .map((x) => x.trim())\n : [];\n if (fallbacks.length > 0) out.fallbacks = fallbacks;\n if (typeof o.timeoutMs === 'number' && Number.isFinite(o.timeoutMs) && o.timeoutMs > 0) {\n out.timeoutMs = Math.floor(o.timeoutMs);\n }\n if (o.autoProviderFallback === true) out.autoProviderFallback = true;\n return out;\n}\n"],"mappings":";;AACA,SAAgB,sBAAsB,KAAkC;AACtE,KAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CAAE,QAAO,KAAA;CAClE,MAAM,IAAK,IAA6B;AACxC,QAAO,OAAO,MAAM,YAAY,EAAE,MAAM,GAAG,IAAI,KAAA;;AAGjD,SAAgB,2BAA2B,KAAwB;AACjE,KAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CAAE,QAAO,EAAE;CACpE,MAAM,IAAK,IAAgC;AAC3C,KAAI,CAAC,MAAM,QAAQ,EAAE,CAAE,QAAO,EAAE;AAChC,QAAO,EAAE,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE;;;;;;;AAQnF,SAAgB,yBAAyB,GAAmE;AAC1G,KAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,EAAE,CAAE,QAAO,KAAA;CAC5D,MAAM,IAAI;CACV,MAAM,UAAU,OAAO,EAAE,YAAY,WAAW,EAAE,QAAQ,MAAM,GAAG;AACnE,KAAI,CAAC,QAAS,QAAO,KAAA;CACrB,MAAM,YAAY,MAAM,QAAQ,EAAE,UAAU,GACxC,EAAE,UAAU,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,GACpF,EAAE;AACN,QAAO,UAAU,SAAS,IAAI;EAAE;EAAS;EAAW,GAAG,EAAE,SAAS;;;AAIpE,SAAgB,mCAAmC,KAA6B;AAC9E,KAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CAAE,QAAO;CAClE,MAAM,IAAK,IAAgC;AAC3C,QAAO,OAAO,MAAM,YAAY,OAAO,SAAS,EAAE,IAAI,IAAI,IAAI,IAAI;;;AAIpE,SAAgB,8CAA8C,KAAuB;AACnF,KAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CAAE,QAAO;AAClE,QAAQ,IAA2C,yBAAyB;;;;;;;;AAS9E,SAAgB,wCACd,GACwG;AACxG,KAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,EAAE,CAAE,QAAO,KAAA;CAC5D,MAAM,IAAI;CACV,MAAM,UAAU,OAAO,EAAE,YAAY,WAAW,EAAE,QAAQ,MAAM,GAAG;AACnE,KAAI,CAAC,QAAS,QAAO,KAAA;CACrB,MAAM,MAAkG,EAAE,SAAS;CACnH,MAAM,YAAY,MAAM,QAAQ,EAAE,UAAU,GACxC,EAAE,UACC,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,CACxE,KAAK,MAAM,EAAE,MAAM,CAAC,GACvB,EAAE;AACN,KAAI,UAAU,SAAS,EAAG,KAAI,YAAY;AAC1C,KAAI,OAAO,EAAE,cAAc,YAAY,OAAO,SAAS,EAAE,UAAU,IAAI,EAAE,YAAY,EACnF,KAAI,YAAY,KAAK,MAAM,EAAE,UAAU;AAEzC,KAAI,EAAE,yBAAyB,KAAM,KAAI,uBAAuB;AAChE,QAAO"}
1
+ {"version":3,"file":"agent-model.js","names":[],"sources":["../../../../../src/gateway/hono/lib/agent-model.ts"],"sourcesContent":["import type { AgentTypedModel } from '../../../config/schema.js';\n\nconst TYPED_MODEL_ID_RE = /^[a-z][a-z0-9_-]{0,63}$/;\n\nfunction isValidProviderModelRef(ref: string): boolean {\n const trimmed = ref.trim();\n const idx = trimmed.indexOf('/');\n return idx > 0 && idx < trimmed.length - 1;\n}\n\n/**\n * Coerce PATCH body `models[]` into validated typed model entries.\n * Returns `null` to clear, `undefined` when input should be skipped, or cleaned array.\n * Empty array after filtering → `null` (same as clear).\n */\nexport function normalizePatchTypedModels(v: unknown): AgentTypedModel[] | null | undefined {\n if (v === undefined) return undefined;\n if (v === null) return null;\n if (!Array.isArray(v)) return undefined;\n\n const byId = new Map<string, AgentTypedModel>();\n for (const raw of v) {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue;\n const o = raw as Record<string, unknown>;\n const id = typeof o.id === 'string' ? o.id.trim() : '';\n const model = typeof o.model === 'string' ? o.model.trim() : '';\n if (!id || !TYPED_MODEL_ID_RE.test(id) || !model || !isValidProviderModelRef(model)) continue;\n const description =\n typeof o.description === 'string' && o.description.trim()\n ? o.description.trim().slice(0, 500)\n : undefined;\n byId.set(id, description ? { id, description, model } : { id, model });\n }\n\n if (byId.size === 0) return null;\n return [...byId.values()];\n}\n\n/** Read `primary` from an `AgentModelConfig` object. */\nexport function agentModelRefToString(ref: unknown): string | undefined {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return undefined;\n const p = (ref as { primary?: string }).primary;\n return typeof p === 'string' && p.trim() ? p : undefined;\n}\n\nexport function agentModelFallbacksToArray(ref: unknown): string[] {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return [];\n const f = (ref as { fallbacks?: unknown }).fallbacks;\n if (!Array.isArray(f)) return [];\n return f.filter((x): x is string => typeof x === 'string' && x.trim().length > 0);\n}\n\n/**\n * Coerce a PATCH body `model` value into the canonical `{ primary, fallbacks? }`\n * object form. Returns `undefined` when the input has no usable primary so the\n * caller can skip the assignment.\n */\nexport function normalizePatchAgentModel(v: unknown): { primary: string; fallbacks?: string[] } | undefined {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n const o = v as Record<string, unknown>;\n const primary = typeof o.primary === 'string' ? o.primary.trim() : '';\n if (!primary) return undefined;\n const fallbacks = Array.isArray(o.fallbacks)\n ? o.fallbacks.filter((x): x is string => typeof x === 'string' && x.trim().length > 0)\n : [];\n return fallbacks.length > 0 ? { primary, fallbacks } : { primary };\n}\n\n/** Read `timeoutMs` from an image-generation model ref (only set when ref is an object). */\nexport function agentImageGenerationModelTimeoutMs(ref: unknown): number | null {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return null;\n const v = (ref as { timeoutMs?: unknown }).timeoutMs;\n return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : null;\n}\n\n/** Read `autoProviderFallback` from an image-generation model ref. Defaults to false. */\nexport function agentImageGenerationModelAutoProviderFallback(ref: unknown): boolean {\n if (!ref || typeof ref !== 'object' || Array.isArray(ref)) return false;\n return (ref as { autoProviderFallback?: unknown }).autoProviderFallback === true;\n}\n\n/**\n * PATCH body normalizer for `agents.defaults.imageGenerationModel`. Always\n * emits `{ primary, fallbacks?, timeoutMs?, autoProviderFallback? }`. Returns\n * `undefined` when the input has no usable primary so the caller can skip the\n * assignment.\n */\nexport function normalizePatchAgentImageGenerationModel(\n v: unknown,\n): { primary: string; fallbacks?: string[]; timeoutMs?: number; autoProviderFallback?: true } | undefined {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n const o = v as Record<string, unknown>;\n const primary = typeof o.primary === 'string' ? o.primary.trim() : '';\n if (!primary) return undefined;\n const out: { primary: string; fallbacks?: string[]; timeoutMs?: number; autoProviderFallback?: true } = { primary };\n const fallbacks = Array.isArray(o.fallbacks)\n ? o.fallbacks\n .filter((x): x is string => typeof x === 'string' && x.trim().length > 0)\n .map((x) => x.trim())\n : [];\n if (fallbacks.length > 0) out.fallbacks = fallbacks;\n if (typeof o.timeoutMs === 'number' && Number.isFinite(o.timeoutMs) && o.timeoutMs > 0) {\n out.timeoutMs = Math.floor(o.timeoutMs);\n }\n if (o.autoProviderFallback === true) out.autoProviderFallback = true;\n return out;\n}\n"],"mappings":"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"}
@@ -1,18 +1,41 @@
1
1
  import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveDefaultAgentId } from "../../../agent/agent-scope.js";
2
+ import { resolveModelsJsonPath } from "../../../config/paths.js";
3
+ import { CredentialResolver, init_credentials } from "../../../auth/credentials.js";
4
+ import { init_models_json, loadModelsJson } from "../../../config/models-json.js";
5
+ import { getProviderRegistry, init_plugin_registry } from "../../../providers/plugin-registry.js";
2
6
  import { getAllProviders, init_providers, isProviderConfigured } from "../../../providers/index.js";
3
7
  import { resolveShareConfig } from "../../../share/share-config.js";
4
8
  import { normalizeConfiguredMcpServers } from "../../../config/mcp-config-normalize.js";
5
9
  import { bundledChannelPlugins } from "../../../generated/bundled-channel-plugins.js";
6
10
  import { listChannelPlugins, syncChannelPluginsFromManager } from "../../../channels/plugins/registry.js";
7
11
  import { resolveCronConfigForWeb, resolveGoalsConfigForWeb, resolveSessionConfigForWeb, resolveUpdateConfigForWeb } from "../../../config/web-patch.js";
12
+ import { agentImageGenerationModelAutoProviderFallback, agentImageGenerationModelTimeoutMs, agentModelFallbacksToArray, agentModelRefToString } from "./agent-model.js";
13
+ import { GENERIC_MASKED_SECRET, maskSecretLength } from "./mask-secret-length.js";
8
14
  import { safeToolsWebForGet } from "../../config-tools-web.js";
9
15
  import { maskTunnelSecretForWeb } from "../../../tunnel/env.js";
10
- import { agentImageGenerationModelAutoProviderFallback, agentImageGenerationModelTimeoutMs, agentModelFallbacksToArray, agentModelRefToString } from "./agent-model.js";
11
16
  import { buildSafeProvidersConfigForWeb } from "./safe-providers-config.js";
12
17
  import { maskSttConfigForWeb, maskTtsConfigForWeb } from "./safe-voice-config.js";
13
18
  //#region src/gateway/hono/lib/config-payload.ts
14
19
  init_agent_scope();
20
+ init_credentials();
21
+ init_models_json();
15
22
  init_providers();
23
+ init_plugin_registry();
24
+ function readModelsJsonProviderApiKey(providerId) {
25
+ const { config } = loadModelsJson(resolveModelsJsonPath());
26
+ const key = (config.providers?.[providerId])?.apiKey;
27
+ return typeof key === "string" && key.trim() ? key.trim() : void 0;
28
+ }
29
+ /** Length-preserving mask for LLM provider keys in GET `/api/config`. */
30
+ async function maskLlmProviderApiKeyForWeb(provider) {
31
+ const stored = await new CredentialResolver().revealGatewayStoredApiKey(provider);
32
+ if (stored) return maskSecretLength(stored);
33
+ const fromModelsJson = readModelsJsonProviderApiKey(provider);
34
+ if (fromModelsJson) return maskSecretLength(fromModelsJson);
35
+ if (getProviderRegistry().has(provider)) return "";
36
+ if (await isProviderConfigured(provider)) return GENERIC_MASKED_SECRET;
37
+ return "";
38
+ }
16
39
  /** MCP block for GET/PATCH `/api/config` (authenticated console editing). */
17
40
  function buildSafeMcpConfigForWeb(config) {
18
41
  const mcp = config.mcp;
@@ -26,7 +49,7 @@ function maskBrowserCloudConfigForWeb(cloud) {
26
49
  if (!cloud || typeof cloud !== "object" || Array.isArray(cloud)) return null;
27
50
  const raw = cloud;
28
51
  const safe = {};
29
- if (typeof raw.apiKey === "string" && raw.apiKey.trim()) safe.apiKey = "***";
52
+ if (typeof raw.apiKey === "string" && raw.apiKey.trim()) safe.apiKey = maskSecretLength(raw.apiKey);
30
53
  if (typeof raw.projectId === "string" && raw.projectId.trim()) safe.projectId = raw.projectId.trim();
31
54
  if (typeof raw.region === "string" && raw.region.trim()) safe.region = raw.region.trim();
32
55
  return Object.keys(safe).length > 0 ? safe : null;
@@ -120,7 +143,7 @@ async function buildSafeWebConfigPayload(service) {
120
143
  }
121
144
  },
122
145
  channels: channelsPayload,
123
- providers: Object.fromEntries(await Promise.all(getAllProviders().map(async (provider) => [provider, await isProviderConfigured(provider) ? "***" : ""]))),
146
+ providers: Object.fromEntries(await Promise.all(getAllProviders().map(async (provider) => [provider, await maskLlmProviderApiKeyForWeb(provider)]))),
124
147
  /** Masked `cfg.providers` for capability keys (image / STT / etc.). */
125
148
  providersConfig: buildSafeProvidersConfigForWeb(config.providers),
126
149
  gateway: {
@@ -134,8 +157,8 @@ async function buildSafeWebConfigPayload(service) {
134
157
  security: { strict: config.gateway?.security?.strict === true },
135
158
  auth: {
136
159
  mode: config.gateway?.auth?.mode || "token",
137
- token: config.gateway?.auth?.token || "",
138
- password: config.gateway?.auth?.password ? "••••••••••••" : "",
160
+ token: config.gateway?.auth?.token ? maskSecretLength(config.gateway.auth.token) : "",
161
+ password: config.gateway?.auth?.password ? maskSecretLength(config.gateway.auth.password) : "",
139
162
  trustedProxy: config.gateway?.auth?.trustedProxy ? {
140
163
  userHeader: config.gateway.auth.trustedProxy.userHeader,
141
164
  requiredHeaders: config.gateway.auth.trustedProxy.requiredHeaders ?? [],
@@ -1 +1 @@
1
- {"version":3,"file":"config-payload.js","names":[],"sources":["../../../../../src/gateway/hono/lib/config-payload.ts"],"sourcesContent":["import {\n listAgentEntries,\n normalizeAgentId,\n resolveDefaultAgentId,\n} from '../../../agent/agent-scope.js';\nimport {\n listChannelPlugins,\n syncChannelPluginsFromManager,\n} from '../../../channels/plugins/registry.js';\nimport { normalizeConfiguredMcpServers } from '../../../config/mcp-config-normalize.js';\nimport type { Config } from '../../../config/schema.js';\nimport { maskTunnelSecretForWeb } from '../../../tunnel/env.js';\nimport { resolveShareConfig } from '../../../share/share-config.js';\nimport {\n resolveCronConfigForWeb,\n resolveGoalsConfigForWeb,\n resolveSessionConfigForWeb,\n resolveUpdateConfigForWeb,\n} from '../../../config/web-patch.js';\nimport { bundledChannelPlugins } from '../../../generated/bundled-channel-plugins.js';\nimport { getAllProviders, isProviderConfigured } from '../../../providers/index.js';\nimport type { GatewayService } from '../../service.js';\nimport { safeToolsWebForGet } from '../../config-tools-web.js';\nimport {\n agentImageGenerationModelAutoProviderFallback,\n agentImageGenerationModelTimeoutMs,\n agentModelFallbacksToArray,\n agentModelRefToString,\n} from './agent-model.js';\nimport { buildSafeProvidersConfigForWeb } from './safe-providers-config.js';\nimport { maskSttConfigForWeb, maskTtsConfigForWeb } from './safe-voice-config.js';\n\n/** MCP block for GET/PATCH `/api/config` (authenticated console editing). */\nexport function buildSafeMcpConfigForWeb(config: Config) {\n const mcp = config.mcp;\n if (!mcp) {\n return { servers: {} as Record<string, Record<string, unknown>> };\n }\n return {\n ...(mcp.sessionIdleTtlMs !== undefined ? { sessionIdleTtlMs: mcp.sessionIdleTtlMs } : {}),\n servers: normalizeConfiguredMcpServers(mcp.servers),\n };\n}\n\nfunction maskBrowserCloudConfigForWeb(cloud: unknown): Record<string, unknown> | null {\n if (!cloud || typeof cloud !== 'object' || Array.isArray(cloud)) {\n return null;\n }\n const raw = cloud as Record<string, unknown>;\n const safe: Record<string, unknown> = {};\n if (typeof raw.apiKey === 'string' && raw.apiKey.trim()) {\n safe.apiKey = '***';\n }\n if (typeof raw.projectId === 'string' && raw.projectId.trim()) {\n safe.projectId = raw.projectId.trim();\n }\n if (typeof raw.region === 'string' && raw.region.trim()) {\n safe.region = raw.region.trim();\n }\n return Object.keys(safe).length > 0 ? safe : null;\n}\n\nexport function buildSafeBrowserConfigForWeb(browser: Config['agents']['defaults']['browser'] | undefined) {\n if (!browser || typeof browser !== 'object') {\n return {\n enabled: false,\n headless: false,\n allowPrivateUrls: false,\n commandTimeout: null,\n backend: null,\n cloudProvider: null,\n cloud: null,\n cdpUrl: null,\n extension: null,\n cloakbrowser: null,\n humanize: null,\n humanPreset: null,\n dialogPolicy: null,\n dialogTimeoutSeconds: null,\n };\n }\n\n return {\n enabled: browser.enabled !== false,\n headless: browser.headless === true,\n allowPrivateUrls: browser.allowPrivateUrls === true,\n commandTimeout:\n typeof browser.commandTimeout === 'number' && Number.isFinite(browser.commandTimeout)\n ? Math.floor(browser.commandTimeout)\n : null,\n backend:\n browser.backend === 'local' ||\n browser.backend === 'cdp' ||\n browser.backend === 'cloud' ||\n browser.backend === 'extension' ||\n browser.backend === 'cloakbrowser'\n ? browser.backend\n : null,\n cloudProvider:\n browser.cloudProvider === 'browserbase' || browser.cloudProvider === 'browser-use'\n ? browser.cloudProvider\n : null,\n cloud: maskBrowserCloudConfigForWeb(browser.cloud),\n cdpUrl: typeof browser.cdpUrl === 'string' && browser.cdpUrl.trim() ? browser.cdpUrl.trim() : null,\n extension: browser.extension && typeof browser.extension === 'object' && !Array.isArray(browser.extension)\n ? browser.extension\n : null,\n cloakbrowser:\n browser.cloakbrowser && typeof browser.cloakbrowser === 'object' && !Array.isArray(browser.cloakbrowser)\n ? browser.cloakbrowser\n : null,\n humanize: typeof browser.humanize === 'boolean' ? browser.humanize : null,\n humanPreset: browser.humanPreset === 'default' || browser.humanPreset === 'careful' ? browser.humanPreset : null,\n dialogPolicy:\n browser.dialogPolicy === 'must_respond' ||\n browser.dialogPolicy === 'auto_accept' ||\n browser.dialogPolicy === 'auto_dismiss'\n ? browser.dialogPolicy\n : null,\n dialogTimeoutSeconds:\n typeof browser.dialogTimeoutSeconds === 'number' && Number.isFinite(browser.dialogTimeoutSeconds)\n ? Math.floor(browser.dialogTimeoutSeconds)\n : null,\n };\n}\n\n/** Sanitized config snapshot for GET/PATCH `/api/config` (matches persisted `service.currentConfig`). */\nexport async function buildSafeWebConfigPayload(service: GatewayService) {\n const config = service.currentConfig;\n if (listChannelPlugins().length === 0) {\n syncChannelPluginsFromManager(bundledChannelPlugins);\n }\n const channelsPayload = Object.fromEntries(\n listChannelPlugins().map((plugin) => {\n if (plugin.configSurface) {\n return [plugin.id, plugin.configSurface.buildConfigSurface(config)];\n }\n const channelCfg = config.channels?.[plugin.id] as Record<string, unknown> | undefined;\n return [\n plugin.id,\n {\n enabled: channelCfg?.enabled ?? false,\n configured: plugin.config.listAccountIds(config).length > 0,\n },\n ];\n }),\n );\n return {\n agents: {\n defaultId: resolveDefaultAgentId(config),\n list: listAgentEntries(config)\n .filter((e) => e.enabled !== false)\n .map((e) => ({\n id: normalizeAgentId(e.id),\n ...(typeof e.name === 'string' && e.name.trim() ? { name: e.name.trim() } : {}),\n })),\n defaults: {\n model: agentModelRefToString(config.agents?.defaults?.model) ?? '',\n modelFallbacks: agentModelFallbacksToArray(config.agents?.defaults?.model),\n imageModel: agentModelRefToString(config.agents?.defaults?.imageModel) ?? null,\n imageModelFallbacks: agentModelFallbacksToArray(config.agents?.defaults?.imageModel),\n imageGenerationModel: agentModelRefToString(config.agents?.defaults?.imageGenerationModel) ?? null,\n imageGenerationModelFallbacks: agentModelFallbacksToArray(\n config.agents?.defaults?.imageGenerationModel,\n ),\n imageGenerationModelTimeoutMs: agentImageGenerationModelTimeoutMs(\n config.agents?.defaults?.imageGenerationModel,\n ),\n imageGenerationModelAutoProviderFallback: agentImageGenerationModelAutoProviderFallback(\n config.agents?.defaults?.imageGenerationModel,\n ),\n mediaMaxMb: config.agents?.defaults?.mediaMaxMb,\n maxTokens: config.agents?.defaults?.maxTokens,\n temperature: config.agents?.defaults?.temperature,\n maxToolIterations: config.agents?.defaults?.maxToolIterations,\n workspace: config.agents?.defaults?.workspace,\n thinkingDefault: config.agents?.defaults?.thinkingDefault,\n reasoningDefault: config.agents?.defaults?.reasoningDefault,\n verboseDefault: config.agents?.defaults?.verboseDefault,\n browser: buildSafeBrowserConfigForWeb(config.agents?.defaults?.browser),\n maxTaskDurationMs: config.agents?.defaults?.maxTaskDurationMs,\n maxRequestsPerTurn: config.agents?.defaults?.maxRequestsPerTurn,\n maxToolFailuresPerTurn: config.agents?.defaults?.maxToolFailuresPerTurn,\n compaction: config.agents?.defaults?.compaction,\n pruning: config.agents?.defaults?.pruning,\n memory: config.agents?.defaults?.memory,\n sessionSearch: config.agents?.defaults?.sessionSearch,\n backgroundReview: config.agents?.defaults?.backgroundReview,\n webExtract: config.agents?.defaults?.webExtract,\n delegate: config.agents?.defaults?.delegate,\n executeCode: config.agents?.defaults?.executeCode,\n systemPromptOverride: config.agents?.defaults?.systemPromptOverride,\n skills: config.agents?.defaults?.skills,\n tools: config.agents?.defaults?.tools,\n params: config.agents?.defaults?.params,\n },\n },\n channels: channelsPayload,\n providers: Object.fromEntries(\n await Promise.all(\n getAllProviders().map(async (provider) => [\n provider,\n (await isProviderConfigured(provider)) ? '***' : '',\n ]),\n ),\n ),\n /** Masked `cfg.providers` for capability keys (image / STT / etc.). */\n providersConfig: buildSafeProvidersConfigForWeb(config.providers),\n gateway: {\n bind: config.gateway?.bind ?? 'loopback',\n customBindHost: config.gateway?.customBindHost,\n port: config.gateway?.port,\n corsOrigins: Array.isArray(config.gateway?.corsOrigins) ? config.gateway.corsOrigins : [],\n trustedProxies: Array.isArray(config.gateway?.trustedProxies)\n ? config.gateway.trustedProxies\n : [],\n allowRealIpFallback: config.gateway?.allowRealIpFallback === true,\n dangerouslyAllowHostHeaderOriginFallback:\n config.gateway?.dangerouslyAllowHostHeaderOriginFallback === true,\n security: {\n strict: config.gateway?.security?.strict === true,\n },\n auth: {\n mode: config.gateway?.auth?.mode || 'token',\n token: config.gateway?.auth?.token || '',\n password: config.gateway?.auth?.password ? '••••••••••••' : '',\n trustedProxy: config.gateway?.auth?.trustedProxy\n ? {\n userHeader: config.gateway.auth.trustedProxy.userHeader,\n requiredHeaders: config.gateway.auth.trustedProxy.requiredHeaders ?? [],\n allowUsers: config.gateway.auth.trustedProxy.allowUsers ?? [],\n allowLoopback: config.gateway.auth.trustedProxy.allowLoopback === true,\n }\n : undefined,\n rateLimit: {\n enabled: config.gateway?.auth?.rateLimit?.enabled !== false,\n maxAttempts:\n typeof config.gateway?.auth?.rateLimit?.maxAttempts === 'number'\n ? config.gateway.auth.rateLimit.maxAttempts\n : 5,\n windowMs:\n typeof config.gateway?.auth?.rateLimit?.windowMs === 'number'\n ? config.gateway.auth.rateLimit.windowMs\n : 900_000,\n blockDurationMs:\n typeof config.gateway?.auth?.rateLimit?.blockDurationMs === 'number'\n ? config.gateway.auth.rateLimit.blockDurationMs\n : typeof config.gateway?.auth?.rateLimit?.lockoutMs === 'number'\n ? config.gateway.auth.rateLimit.lockoutMs\n : 300_000,\n exemptLoopback: config.gateway?.auth?.rateLimit?.exemptLoopback !== false,\n },\n },\n heartbeat: {\n enabled: config.gateway?.heartbeat?.enabled,\n intervalMs: config.gateway?.heartbeat?.intervalMs,\n includeSystemPromptSection: config.gateway?.heartbeat?.includeSystemPromptSection === true,\n target: config.gateway?.heartbeat?.target,\n targetChatId: config.gateway?.heartbeat?.targetChatId,\n prompt: config.gateway?.heartbeat?.prompt,\n ackMaxChars: config.gateway?.heartbeat?.ackMaxChars,\n isolatedSession: config.gateway?.heartbeat?.isolatedSession,\n activeHours: config.gateway?.heartbeat?.activeHours,\n },\n maxSseConnections:\n typeof config.gateway?.maxSseConnections === 'number'\n ? config.gateway.maxSseConnections\n : 100,\n channelConnectDeferMode: config.gateway?.channelConnectDeferMode ?? 'auto',\n channelConnectDeferIds: Array.isArray(config.gateway?.channelConnectDeferIds)\n ? config.gateway.channelConnectDeferIds\n : [],\n channelConnectDeferSkipIds: Array.isArray(config.gateway?.channelConnectDeferSkipIds)\n ? config.gateway.channelConnectDeferSkipIds\n : [],\n share: resolveShareConfig(config.gateway?.share),\n skillsMarketplaceProvider: config.gateway?.skillsMarketplaceProvider ?? 'skillhub',\n skillsStoreBaseUrl: config.gateway?.skillsStoreBaseUrl ?? 'https://store.xopc.ai',\n },\n cron: resolveCronConfigForWeb(config),\n goals: resolveGoalsConfigForWeb(config),\n session: resolveSessionConfigForWeb(config),\n tunnel: {\n enabled: config.tunnel?.enabled === true,\n autoStart: config.tunnel?.autoStart === true,\n brokerUrl: config.tunnel?.brokerUrl ?? 'https://frp.xopc.ai/api',\n registrationSecret: config.tunnel?.registrationSecret\n ? maskTunnelSecretForWeb(config.tunnel.registrationSecret)\n : '',\n consent: config.tunnel?.consent\n ? {\n version: config.tunnel.consent.version,\n acceptedAt: config.tunnel.consent.acceptedAt,\n }\n : undefined,\n transport: { tls: 'broker_terminated' as const },\n },\n update: {\n ...resolveUpdateConfigForWeb(config),\n },\n stt: maskSttConfigForWeb(config.tools?.media?.audio),\n tts: maskTtsConfigForWeb(config.messages?.tts),\n tools: safeToolsWebForGet(config),\n bindings: Array.isArray(config.bindings) ? config.bindings : [],\n mcp: buildSafeMcpConfigForWeb(config),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-payload.js","names":["getModelsJsonPath"],"sources":["../../../../../src/gateway/hono/lib/config-payload.ts"],"sourcesContent":["import {\n listAgentEntries,\n normalizeAgentId,\n resolveDefaultAgentId,\n} from '../../../agent/agent-scope.js';\nimport {\n listChannelPlugins,\n syncChannelPluginsFromManager,\n} from '../../../channels/plugins/registry.js';\nimport { normalizeConfiguredMcpServers } from '../../../config/mcp-config-normalize.js';\nimport type { Config } from '../../../config/schema.js';\nimport { bundledChannelPlugins } from '../../../generated/bundled-channel-plugins.js';\nimport {\n GENERIC_MASKED_SECRET,\n maskSecretLength,\n} from './mask-secret-length.js';\nimport { maskTunnelSecretForWeb } from '../../../tunnel/env.js';\nimport { resolveShareConfig } from '../../../share/share-config.js';\nimport {\n resolveCronConfigForWeb,\n resolveGoalsConfigForWeb,\n resolveSessionConfigForWeb,\n resolveUpdateConfigForWeb,\n} from '../../../config/web-patch.js';\nimport { CredentialResolver } from '../../../auth/credentials.js';\nimport { loadModelsJson, getModelsJsonPath } from '../../../config/models-json.js';\nimport { getAllProviders, isProviderConfigured } from '../../../providers/index.js';\nimport { getProviderRegistry } from '../../../providers/plugin-registry.js';\nimport type { GatewayService } from '../../service.js';\nimport { safeToolsWebForGet } from '../../config-tools-web.js';\nimport {\n agentImageGenerationModelAutoProviderFallback,\n agentImageGenerationModelTimeoutMs,\n agentModelFallbacksToArray,\n agentModelRefToString,\n} from './agent-model.js';\nimport { buildSafeProvidersConfigForWeb } from './safe-providers-config.js';\nimport { maskSttConfigForWeb, maskTtsConfigForWeb } from './safe-voice-config.js';\n\nfunction readModelsJsonProviderApiKey(providerId: string): string | undefined {\n const { config } = loadModelsJson(getModelsJsonPath());\n const entry = config.providers?.[providerId];\n const key = entry?.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\n/** Length-preserving mask for LLM provider keys in GET `/api/config`. */\nasync function maskLlmProviderApiKeyForWeb(provider: string): Promise<string> {\n const resolver = new CredentialResolver();\n const stored = await resolver.revealGatewayStoredApiKey(provider);\n if (stored) return maskSecretLength(stored);\n\n const fromModelsJson = readModelsJsonProviderApiKey(provider);\n if (fromModelsJson) return maskSecretLength(fromModelsJson);\n\n // Extension plugins manage their own auth; don't show a fake gateway key mask.\n if (getProviderRegistry().has(provider)) return '';\n\n if (await isProviderConfigured(provider)) return GENERIC_MASKED_SECRET;\n return '';\n}\n\n/** MCP block for GET/PATCH `/api/config` (authenticated console editing). */\nexport function buildSafeMcpConfigForWeb(config: Config) {\n const mcp = config.mcp;\n if (!mcp) {\n return { servers: {} as Record<string, Record<string, unknown>> };\n }\n return {\n ...(mcp.sessionIdleTtlMs !== undefined ? { sessionIdleTtlMs: mcp.sessionIdleTtlMs } : {}),\n servers: normalizeConfiguredMcpServers(mcp.servers),\n };\n}\n\nfunction maskBrowserCloudConfigForWeb(cloud: unknown): Record<string, unknown> | null {\n if (!cloud || typeof cloud !== 'object' || Array.isArray(cloud)) {\n return null;\n }\n const raw = cloud as Record<string, unknown>;\n const safe: Record<string, unknown> = {};\n if (typeof raw.apiKey === 'string' && raw.apiKey.trim()) {\n safe.apiKey = maskSecretLength(raw.apiKey);\n }\n if (typeof raw.projectId === 'string' && raw.projectId.trim()) {\n safe.projectId = raw.projectId.trim();\n }\n if (typeof raw.region === 'string' && raw.region.trim()) {\n safe.region = raw.region.trim();\n }\n return Object.keys(safe).length > 0 ? safe : null;\n}\n\nexport function buildSafeBrowserConfigForWeb(browser: Config['agents']['defaults']['browser'] | undefined) {\n if (!browser || typeof browser !== 'object') {\n return {\n enabled: false,\n headless: false,\n allowPrivateUrls: false,\n commandTimeout: null,\n backend: null,\n cloudProvider: null,\n cloud: null,\n cdpUrl: null,\n extension: null,\n cloakbrowser: null,\n humanize: null,\n humanPreset: null,\n dialogPolicy: null,\n dialogTimeoutSeconds: null,\n };\n }\n\n return {\n enabled: browser.enabled !== false,\n headless: browser.headless === true,\n allowPrivateUrls: browser.allowPrivateUrls === true,\n commandTimeout:\n typeof browser.commandTimeout === 'number' && Number.isFinite(browser.commandTimeout)\n ? Math.floor(browser.commandTimeout)\n : null,\n backend:\n browser.backend === 'local' ||\n browser.backend === 'cdp' ||\n browser.backend === 'cloud' ||\n browser.backend === 'extension' ||\n browser.backend === 'cloakbrowser'\n ? browser.backend\n : null,\n cloudProvider:\n browser.cloudProvider === 'browserbase' || browser.cloudProvider === 'browser-use'\n ? browser.cloudProvider\n : null,\n cloud: maskBrowserCloudConfigForWeb(browser.cloud),\n cdpUrl: typeof browser.cdpUrl === 'string' && browser.cdpUrl.trim() ? browser.cdpUrl.trim() : null,\n extension: browser.extension && typeof browser.extension === 'object' && !Array.isArray(browser.extension)\n ? browser.extension\n : null,\n cloakbrowser:\n browser.cloakbrowser && typeof browser.cloakbrowser === 'object' && !Array.isArray(browser.cloakbrowser)\n ? browser.cloakbrowser\n : null,\n humanize: typeof browser.humanize === 'boolean' ? browser.humanize : null,\n humanPreset: browser.humanPreset === 'default' || browser.humanPreset === 'careful' ? browser.humanPreset : null,\n dialogPolicy:\n browser.dialogPolicy === 'must_respond' ||\n browser.dialogPolicy === 'auto_accept' ||\n browser.dialogPolicy === 'auto_dismiss'\n ? browser.dialogPolicy\n : null,\n dialogTimeoutSeconds:\n typeof browser.dialogTimeoutSeconds === 'number' && Number.isFinite(browser.dialogTimeoutSeconds)\n ? Math.floor(browser.dialogTimeoutSeconds)\n : null,\n };\n}\n\n/** Sanitized config snapshot for GET/PATCH `/api/config` (matches persisted `service.currentConfig`). */\nexport async function buildSafeWebConfigPayload(service: GatewayService) {\n const config = service.currentConfig;\n if (listChannelPlugins().length === 0) {\n syncChannelPluginsFromManager(bundledChannelPlugins);\n }\n const channelsPayload = Object.fromEntries(\n listChannelPlugins().map((plugin) => {\n if (plugin.configSurface) {\n return [plugin.id, plugin.configSurface.buildConfigSurface(config)];\n }\n const channelCfg = config.channels?.[plugin.id] as Record<string, unknown> | undefined;\n return [\n plugin.id,\n {\n enabled: channelCfg?.enabled ?? false,\n configured: plugin.config.listAccountIds(config).length > 0,\n },\n ];\n }),\n );\n return {\n agents: {\n defaultId: resolveDefaultAgentId(config),\n list: listAgentEntries(config)\n .filter((e) => e.enabled !== false)\n .map((e) => ({\n id: normalizeAgentId(e.id),\n ...(typeof e.name === 'string' && e.name.trim() ? { name: e.name.trim() } : {}),\n })),\n defaults: {\n model: agentModelRefToString(config.agents?.defaults?.model) ?? '',\n modelFallbacks: agentModelFallbacksToArray(config.agents?.defaults?.model),\n imageModel: agentModelRefToString(config.agents?.defaults?.imageModel) ?? null,\n imageModelFallbacks: agentModelFallbacksToArray(config.agents?.defaults?.imageModel),\n imageGenerationModel: agentModelRefToString(config.agents?.defaults?.imageGenerationModel) ?? null,\n imageGenerationModelFallbacks: agentModelFallbacksToArray(\n config.agents?.defaults?.imageGenerationModel,\n ),\n imageGenerationModelTimeoutMs: agentImageGenerationModelTimeoutMs(\n config.agents?.defaults?.imageGenerationModel,\n ),\n imageGenerationModelAutoProviderFallback: agentImageGenerationModelAutoProviderFallback(\n config.agents?.defaults?.imageGenerationModel,\n ),\n mediaMaxMb: config.agents?.defaults?.mediaMaxMb,\n maxTokens: config.agents?.defaults?.maxTokens,\n temperature: config.agents?.defaults?.temperature,\n maxToolIterations: config.agents?.defaults?.maxToolIterations,\n workspace: config.agents?.defaults?.workspace,\n thinkingDefault: config.agents?.defaults?.thinkingDefault,\n reasoningDefault: config.agents?.defaults?.reasoningDefault,\n verboseDefault: config.agents?.defaults?.verboseDefault,\n browser: buildSafeBrowserConfigForWeb(config.agents?.defaults?.browser),\n maxTaskDurationMs: config.agents?.defaults?.maxTaskDurationMs,\n maxRequestsPerTurn: config.agents?.defaults?.maxRequestsPerTurn,\n maxToolFailuresPerTurn: config.agents?.defaults?.maxToolFailuresPerTurn,\n compaction: config.agents?.defaults?.compaction,\n pruning: config.agents?.defaults?.pruning,\n memory: config.agents?.defaults?.memory,\n sessionSearch: config.agents?.defaults?.sessionSearch,\n backgroundReview: config.agents?.defaults?.backgroundReview,\n webExtract: config.agents?.defaults?.webExtract,\n delegate: config.agents?.defaults?.delegate,\n executeCode: config.agents?.defaults?.executeCode,\n systemPromptOverride: config.agents?.defaults?.systemPromptOverride,\n skills: config.agents?.defaults?.skills,\n tools: config.agents?.defaults?.tools,\n params: config.agents?.defaults?.params,\n },\n },\n channels: channelsPayload,\n providers: Object.fromEntries(\n await Promise.all(\n getAllProviders().map(async (provider) => [\n provider,\n await maskLlmProviderApiKeyForWeb(provider),\n ]),\n ),\n ),\n /** Masked `cfg.providers` for capability keys (image / STT / etc.). */\n providersConfig: buildSafeProvidersConfigForWeb(config.providers),\n gateway: {\n bind: config.gateway?.bind ?? 'loopback',\n customBindHost: config.gateway?.customBindHost,\n port: config.gateway?.port,\n corsOrigins: Array.isArray(config.gateway?.corsOrigins) ? config.gateway.corsOrigins : [],\n trustedProxies: Array.isArray(config.gateway?.trustedProxies)\n ? config.gateway.trustedProxies\n : [],\n allowRealIpFallback: config.gateway?.allowRealIpFallback === true,\n dangerouslyAllowHostHeaderOriginFallback:\n config.gateway?.dangerouslyAllowHostHeaderOriginFallback === true,\n security: {\n strict: config.gateway?.security?.strict === true,\n },\n auth: {\n mode: config.gateway?.auth?.mode || 'token',\n token: config.gateway?.auth?.token ? maskSecretLength(config.gateway.auth.token) : '',\n password: config.gateway?.auth?.password\n ? maskSecretLength(config.gateway.auth.password)\n : '',\n trustedProxy: config.gateway?.auth?.trustedProxy\n ? {\n userHeader: config.gateway.auth.trustedProxy.userHeader,\n requiredHeaders: config.gateway.auth.trustedProxy.requiredHeaders ?? [],\n allowUsers: config.gateway.auth.trustedProxy.allowUsers ?? [],\n allowLoopback: config.gateway.auth.trustedProxy.allowLoopback === true,\n }\n : undefined,\n rateLimit: {\n enabled: config.gateway?.auth?.rateLimit?.enabled !== false,\n maxAttempts:\n typeof config.gateway?.auth?.rateLimit?.maxAttempts === 'number'\n ? config.gateway.auth.rateLimit.maxAttempts\n : 5,\n windowMs:\n typeof config.gateway?.auth?.rateLimit?.windowMs === 'number'\n ? config.gateway.auth.rateLimit.windowMs\n : 900_000,\n blockDurationMs:\n typeof config.gateway?.auth?.rateLimit?.blockDurationMs === 'number'\n ? config.gateway.auth.rateLimit.blockDurationMs\n : typeof config.gateway?.auth?.rateLimit?.lockoutMs === 'number'\n ? config.gateway.auth.rateLimit.lockoutMs\n : 300_000,\n exemptLoopback: config.gateway?.auth?.rateLimit?.exemptLoopback !== false,\n },\n },\n heartbeat: {\n enabled: config.gateway?.heartbeat?.enabled,\n intervalMs: config.gateway?.heartbeat?.intervalMs,\n includeSystemPromptSection: config.gateway?.heartbeat?.includeSystemPromptSection === true,\n target: config.gateway?.heartbeat?.target,\n targetChatId: config.gateway?.heartbeat?.targetChatId,\n prompt: config.gateway?.heartbeat?.prompt,\n ackMaxChars: config.gateway?.heartbeat?.ackMaxChars,\n isolatedSession: config.gateway?.heartbeat?.isolatedSession,\n activeHours: config.gateway?.heartbeat?.activeHours,\n },\n maxSseConnections:\n typeof config.gateway?.maxSseConnections === 'number'\n ? config.gateway.maxSseConnections\n : 100,\n channelConnectDeferMode: config.gateway?.channelConnectDeferMode ?? 'auto',\n channelConnectDeferIds: Array.isArray(config.gateway?.channelConnectDeferIds)\n ? config.gateway.channelConnectDeferIds\n : [],\n channelConnectDeferSkipIds: Array.isArray(config.gateway?.channelConnectDeferSkipIds)\n ? config.gateway.channelConnectDeferSkipIds\n : [],\n share: resolveShareConfig(config.gateway?.share),\n skillsMarketplaceProvider: config.gateway?.skillsMarketplaceProvider ?? 'skillhub',\n skillsStoreBaseUrl: config.gateway?.skillsStoreBaseUrl ?? 'https://store.xopc.ai',\n },\n cron: resolveCronConfigForWeb(config),\n goals: resolveGoalsConfigForWeb(config),\n session: resolveSessionConfigForWeb(config),\n tunnel: {\n enabled: config.tunnel?.enabled === true,\n autoStart: config.tunnel?.autoStart === true,\n brokerUrl: config.tunnel?.brokerUrl ?? 'https://frp.xopc.ai/api',\n registrationSecret: config.tunnel?.registrationSecret\n ? maskTunnelSecretForWeb(config.tunnel.registrationSecret)\n : '',\n consent: config.tunnel?.consent\n ? {\n version: config.tunnel.consent.version,\n acceptedAt: config.tunnel.consent.acceptedAt,\n }\n : undefined,\n transport: { tls: 'broker_terminated' as const },\n },\n update: {\n ...resolveUpdateConfigForWeb(config),\n },\n stt: maskSttConfigForWeb(config.tools?.media?.audio),\n tts: maskTtsConfigForWeb(config.messages?.tts),\n tools: safeToolsWebForGet(config),\n bindings: Array.isArray(config.bindings) ? config.bindings : [],\n mcp: buildSafeMcpConfigForWeb(config),\n };\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { init_write_file_atomic, writeTextAtomic } from "../../../infra/write-file-atomic.js";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
2
  import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
5
  //#region src/gateway/hono/lib/extension-store.ts
6
6
  init_write_file_atomic();
7
7
  /** Extension UI: write-through JSON KV per namespace under ~/.xopc/extensions/{namespace}/storage.json */
@@ -0,0 +1,6 @@
1
+ /** Fallback mask when the real secret length is unknown (e.g. env-sourced keys). */
2
+ export declare const GENERIC_MASKED_SECRET = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
3
+ /** Length-preserving mask for secrets returned to the web console. */
4
+ export declare function maskSecretLength(secret: string): string;
5
+ /** True when a PATCH body carries a masked sentinel instead of a new secret. */
6
+ export declare function isMaskedSecretPatchValue(value: string): boolean;
@@ -0,0 +1,16 @@
1
+ //#region src/gateway/hono/lib/mask-secret-length.ts
2
+ /** Fallback mask when the real secret length is unknown (e.g. env-sourced keys). */
3
+ const GENERIC_MASKED_SECRET = "••••••••••••";
4
+ /** Length-preserving mask for secrets returned to the web console. */
5
+ function maskSecretLength(secret) {
6
+ const trimmed = secret.trim();
7
+ return trimmed ? "•".repeat(trimmed.length) : "";
8
+ }
9
+ /** True when a PATCH body carries a masked sentinel instead of a new secret. */
10
+ function isMaskedSecretPatchValue(value) {
11
+ return value === "***" || value === "••••••••••••" || /^•+$/.test(value);
12
+ }
13
+ //#endregion
14
+ export { GENERIC_MASKED_SECRET, isMaskedSecretPatchValue, maskSecretLength };
15
+
16
+ //# sourceMappingURL=mask-secret-length.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask-secret-length.js","names":[],"sources":["../../../../../src/gateway/hono/lib/mask-secret-length.ts"],"sourcesContent":["/** Fallback mask when the real secret length is unknown (e.g. env-sourced keys). */\nexport const GENERIC_MASKED_SECRET = '••••••••••••';\n\n/** Length-preserving mask for secrets returned to the web console. */\nexport function maskSecretLength(secret: string): string {\n const trimmed = secret.trim();\n return trimmed ? '•'.repeat(trimmed.length) : '';\n}\n\n/** True when a PATCH body carries a masked sentinel instead of a new secret. */\nexport function isMaskedSecretPatchValue(value: string): boolean {\n return value === '***' || value === GENERIC_MASKED_SECRET || /^•+$/.test(value);\n}\n"],"mappings":";;AACA,MAAa,wBAAwB;;AAGrC,SAAgB,iBAAiB,QAAwB;CACvD,MAAM,UAAU,OAAO,MAAM;AAC7B,QAAO,UAAU,IAAI,OAAO,QAAQ,OAAO,GAAG;;;AAIhD,SAAgB,yBAAyB,OAAwB;AAC/D,QAAO,UAAU,SAAS,UAAA,kBAAmC,OAAO,KAAK,MAAM"}
@@ -1,7 +1,7 @@
1
1
  import type { Config } from '../../../config/schema.js';
2
2
  /**
3
3
  * Per-vendor slice of {@link Config.providers} safe for GET `/api/config`.
4
- * Secrets are never sent verbatim; `apiKey` is `***` when set, else empty.
4
+ * Secrets are never sent verbatim; `apiKey` is length-preserving bullets when set, else empty.
5
5
  */
6
6
  export type SafeProviderAuthEntry = {
7
7
  apiKey: string;
@@ -1,3 +1,4 @@
1
+ import { maskSecretLength } from "./mask-secret-length.js";
1
2
  //#region src/gateway/hono/lib/safe-providers-config.ts
2
3
  /**
3
4
  * Build a redacted `providersConfig` map for the gateway console (image / audio / video keys).
@@ -9,7 +10,7 @@ function buildSafeProvidersConfigForWeb(providers) {
9
10
  if (!id) continue;
10
11
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
11
12
  const o = raw;
12
- const entry = { apiKey: typeof o.apiKey === "string" && o.apiKey.trim() ? "***" : "" };
13
+ const entry = { apiKey: typeof o.apiKey === "string" && o.apiKey.trim() ? maskSecretLength(o.apiKey) : "" };
13
14
  if (typeof o.region === "string" && o.region.trim()) entry.region = o.region.trim();
14
15
  if (typeof o.baseUrl === "string" && o.baseUrl.trim()) entry.baseUrl = o.baseUrl.trim();
15
16
  if (typeof o.imageBaseUrl === "string" && o.imageBaseUrl.trim()) entry.imageBaseUrl = o.imageBaseUrl.trim();