@xopcai/xopc 0.0.84 → 0.0.86

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 (418) 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/plugin.d.ts +2 -0
  4. package/dist/extensions/feishu/src/plugin.js +10 -0
  5. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  6. package/dist/extensions/feishu/src/workflow-progress.d.ts +27 -0
  7. package/dist/extensions/feishu/src/workflow-progress.js +99 -0
  8. package/dist/extensions/feishu/src/workflow-progress.js.map +1 -0
  9. package/dist/extensions/telegram/src/plugin.d.ts +2 -0
  10. package/dist/extensions/telegram/src/plugin.js +11 -1
  11. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  12. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  13. package/dist/extensions/telegram/src/workflow-progress.d.ts +24 -0
  14. package/dist/extensions/telegram/src/workflow-progress.js +73 -0
  15. package/dist/extensions/telegram/src/workflow-progress.js.map +1 -0
  16. package/dist/extensions/telegram/xopc.extension.json +1 -1
  17. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +158 -0
  18. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -0
  19. package/dist/extensions/weixin/src/api/api.js +2 -2
  20. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  21. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  22. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  23. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  24. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  25. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  26. package/dist/extensions/weixin/src/plugin.d.ts +2 -0
  27. package/dist/extensions/weixin/src/plugin.js +11 -1
  28. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  29. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  30. package/dist/extensions/weixin/src/workflow-progress.d.ts +26 -0
  31. package/dist/extensions/weixin/src/workflow-progress.js +99 -0
  32. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -0
  33. package/dist/gateway/static/root/assets/agents-mS3_HpRI.js +222 -0
  34. package/dist/gateway/static/root/assets/apps-page-DrfytjOb.js +1 -0
  35. package/dist/gateway/static/root/assets/channels-settings-BG6b9KrW.js +1 -0
  36. package/dist/gateway/static/root/assets/channels-status-swr-Bs5kMCMI.js +8 -0
  37. package/dist/gateway/static/root/assets/createLucideIcon-DPHK1VkS.js +1 -0
  38. package/dist/gateway/static/root/assets/cron-api-BuVcZ5zR.js +1 -0
  39. package/dist/gateway/static/root/assets/cron-page-BMrloeFH.js +1 -0
  40. package/dist/gateway/static/root/assets/dist-BTWC-BTN.js +45 -0
  41. package/dist/gateway/static/root/assets/{dist-CqNMNhJM.js → dist-CKU1OOTf.js} +1 -1
  42. package/dist/gateway/static/root/assets/{extension-debug-page-gf2L0kY_.js → extension-debug-page-BdW_46sN.js} +1 -1
  43. package/dist/gateway/static/root/assets/extension-page-DW47KI82.js +1 -0
  44. package/dist/gateway/static/root/assets/extension-settings-page-B-W4x2xP.js +1 -0
  45. package/dist/gateway/static/root/assets/fetch-B2MYHbWg.js +1 -0
  46. package/dist/gateway/static/root/assets/{field-primitives-DTtlp-l8.js → field-primitives-DPG-oJmx.js} +1 -1
  47. package/dist/gateway/static/root/assets/heartbeat-config-api-C8dNts9i.js +1 -0
  48. package/dist/gateway/static/root/assets/index-BmVYculr.js +4700 -0
  49. package/dist/gateway/static/root/assets/index-ew_2L2We.css +1 -0
  50. package/dist/gateway/static/root/assets/logs-page-sTsVWz0X.js +1 -0
  51. package/dist/gateway/static/root/assets/sessions-page-FaG_Vlkb.js +1 -0
  52. package/dist/gateway/static/root/assets/settings-form-section-DuvRQW--.js +1 -0
  53. package/dist/gateway/static/root/assets/settings-page-Bet1OerL.js +3 -0
  54. package/dist/gateway/static/root/assets/share-preview-page-BtG2kLDh.js +2 -0
  55. package/dist/gateway/static/root/assets/skills-page-DhUO235y.js +2 -0
  56. package/dist/gateway/static/root/assets/theme-store-DryYl3qD.js +1 -0
  57. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +3 -0
  58. package/dist/gateway/static/root/assets/utils-BY7bU1DT.js +1 -0
  59. package/dist/gateway/static/root/assets/voice-api-key-field-CGEydndO.js +1 -0
  60. package/dist/gateway/static/root/index.html +7 -6
  61. package/dist/package.js +1 -1
  62. package/dist/src/agent/agent-manager.js +7 -7
  63. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  64. package/dist/src/agent/context/workspace-seed.js +3 -3
  65. package/dist/src/agent/embedded/map-stream-events.js +6 -0
  66. package/dist/src/agent/embedded/map-stream-events.js.map +1 -1
  67. package/dist/src/agent/embedded/subscribe-session.js +24 -0
  68. package/dist/src/agent/embedded/subscribe-session.js.map +1 -1
  69. package/dist/src/agent/embedded/types.d.ts +19 -0
  70. package/dist/src/agent/goals/goal-locale.js +2 -2
  71. package/dist/src/agent/goals/goal-run-store.js +4 -4
  72. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  73. package/dist/src/agent/goals/post-turn.js +2 -2
  74. package/dist/src/agent/image/load-image-media.js +2 -2
  75. package/dist/src/agent/ipc/bus.js +1 -1
  76. package/dist/src/agent/ipc/inbox.js +2 -2
  77. package/dist/src/agent/ipc/socket.js +1 -1
  78. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  79. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  80. package/dist/src/agent/memory/dreaming/events.js +1 -1
  81. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  82. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  83. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  84. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  85. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  86. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  87. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  88. package/dist/src/agent/models/manager.js +1 -1
  89. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  90. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  91. package/dist/src/agent/reply/startup-context.d.ts +3 -0
  92. package/dist/src/agent/reply/startup-context.js +25 -2
  93. package/dist/src/agent/reply/startup-context.js.map +1 -1
  94. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  95. package/dist/src/agent/sandbox/path-policy.js +2 -2
  96. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  97. package/dist/src/agent/service.d.ts +1 -0
  98. package/dist/src/agent/service.js +10 -4
  99. package/dist/src/agent/service.js.map +1 -1
  100. package/dist/src/agent/session/session-inspector.js +1 -1
  101. package/dist/src/agent/skills/config.js +1 -1
  102. package/dist/src/agent/skills/hub-hash.js +2 -2
  103. package/dist/src/agent/skills/hub-lock.js +1 -1
  104. package/dist/src/agent/skills/hub-pull.js +3 -3
  105. package/dist/src/agent/skills/index.js +1 -1
  106. package/dist/src/agent/skills/managed-store.js +1 -1
  107. package/dist/src/agent/skills/scanner.js +1 -1
  108. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  109. package/dist/src/agent/skills/skill-manager.js +1 -1
  110. package/dist/src/agent/tools/create-share-tool.d.ts +27 -0
  111. package/dist/src/agent/tools/create-share-tool.js +237 -0
  112. package/dist/src/agent/tools/create-share-tool.js.map +1 -0
  113. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  114. package/dist/src/agent/tools/factory.js +35 -1
  115. package/dist/src/agent/tools/factory.js.map +1 -1
  116. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  117. package/dist/src/agent/tools/index.d.ts +2 -0
  118. package/dist/src/agent/tools/index.js +3 -1
  119. package/dist/src/agent/tools/send-media.js +1 -1
  120. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  121. package/dist/src/agent/tools/workflow-tool.d.ts +41 -0
  122. package/dist/src/agent/tools/workflow-tool.js +271 -0
  123. package/dist/src/agent/tools/workflow-tool.js.map +1 -0
  124. package/dist/src/agent/tools/write.js +1 -1
  125. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +13 -0
  126. package/dist/src/agent/workflow/builtins/audit-repo.js +156 -0
  127. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -0
  128. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
  129. package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
  130. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
  131. package/dist/src/agent/workflow/builtins/index.d.ts +17 -0
  132. package/dist/src/agent/workflow/builtins/index.js +38 -0
  133. package/dist/src/agent/workflow/builtins/index.js.map +1 -0
  134. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +14 -0
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +149 -0
  136. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -0
  137. package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
  138. package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
  139. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
  140. package/dist/src/agent/workflow/builtins/research.d.ts +13 -0
  141. package/dist/src/agent/workflow/builtins/research.js +160 -0
  142. package/dist/src/agent/workflow/builtins/research.js.map +1 -0
  143. package/dist/src/agent/workflow/catalog.d.ts +56 -0
  144. package/dist/src/agent/workflow/catalog.js +159 -0
  145. package/dist/src/agent/workflow/catalog.js.map +1 -0
  146. package/dist/src/agent/workflow/channel-capability.d.ts +76 -0
  147. package/dist/src/agent/workflow/channel-capability.js +1 -0
  148. package/dist/src/agent/workflow/index.d.ts +11 -0
  149. package/dist/src/agent/workflow/index.js +10 -0
  150. package/dist/src/agent/workflow/last-run-memory.d.ts +42 -0
  151. package/dist/src/agent/workflow/last-run-memory.js +60 -0
  152. package/dist/src/agent/workflow/last-run-memory.js.map +1 -0
  153. package/dist/src/agent/workflow/parser.d.ts +20 -0
  154. package/dist/src/agent/workflow/parser.js +146 -0
  155. package/dist/src/agent/workflow/parser.js.map +1 -0
  156. package/dist/src/agent/workflow/progress-broker.d.ts +80 -0
  157. package/dist/src/agent/workflow/progress-broker.js +263 -0
  158. package/dist/src/agent/workflow/progress-broker.js.map +1 -0
  159. package/dist/src/agent/workflow/runtime.d.ts +31 -0
  160. package/dist/src/agent/workflow/runtime.js +301 -0
  161. package/dist/src/agent/workflow/runtime.js.map +1 -0
  162. package/dist/src/agent/workflow/snapshot.d.ts +18 -0
  163. package/dist/src/agent/workflow/snapshot.js +144 -0
  164. package/dist/src/agent/workflow/snapshot.js.map +1 -0
  165. package/dist/src/agent/workflow/structured-output-tool.d.ts +33 -0
  166. package/dist/src/agent/workflow/structured-output-tool.js +58 -0
  167. package/dist/src/agent/workflow/structured-output-tool.js.map +1 -0
  168. package/dist/src/agent/workflow/subagent-runner.d.ts +42 -0
  169. package/dist/src/agent/workflow/subagent-runner.js +104 -0
  170. package/dist/src/agent/workflow/subagent-runner.js.map +1 -0
  171. package/dist/src/agent/workflow/types.d.ts +145 -0
  172. package/dist/src/agent/workflow/types.js +1 -0
  173. package/dist/src/auth/credentials.js +3 -3
  174. package/dist/src/auth/profiles/store.js +1 -1
  175. package/dist/src/auth/sync-provider-auth.js +1 -1
  176. package/dist/src/browser/cache-dir-policy.js +1 -1
  177. package/dist/src/browser/cdp-local-launcher.js +2 -2
  178. package/dist/src/browser/providers/browser-ext-install.js +4 -4
  179. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  180. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  181. package/dist/src/browser/stealth.js +1 -1
  182. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  183. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  184. package/dist/src/channels/outbound/persist-store.js +1 -1
  185. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  186. package/dist/src/channels/pairing/pairing-store.js +2 -2
  187. package/dist/src/chat-commands/builtins/config.js +2 -2
  188. package/dist/src/chat-commands/builtins/model.js +40 -23
  189. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  190. package/dist/src/chat-commands/builtins/system.js +30 -15
  191. package/dist/src/chat-commands/builtins/system.js.map +1 -1
  192. package/dist/src/chat-commands/builtins/workflow.d.ts +18 -0
  193. package/dist/src/chat-commands/builtins/workflow.js +172 -0
  194. package/dist/src/chat-commands/builtins/workflow.js.map +1 -0
  195. package/dist/src/chat-commands/context.js +1 -1
  196. package/dist/src/chat-commands/format-output.d.ts +28 -0
  197. package/dist/src/chat-commands/format-output.js +45 -0
  198. package/dist/src/chat-commands/format-output.js.map +1 -0
  199. package/dist/src/chat-commands/index.d.ts +1 -0
  200. package/dist/src/chat-commands/index.js +3 -1
  201. package/dist/src/chat-commands/index.js.map +1 -1
  202. package/dist/src/cli/commands/config.js +2 -2
  203. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  204. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  205. package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
  206. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  207. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  208. package/dist/src/cli/commands/extension-dev.js +1 -1
  209. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  210. package/dist/src/cli/commands/extension-pack.js +1 -1
  211. package/dist/src/cli/commands/gateway/lifecycle.js +10 -4
  212. package/dist/src/cli/commands/gateway/lifecycle.js.map +1 -1
  213. package/dist/src/cli/commands/gateway/shared.js +1 -1
  214. package/dist/src/cli/commands/image.js +1 -1
  215. package/dist/src/cli/commands/init.js +4 -4
  216. package/dist/src/cli/commands/onboard.js +2 -2
  217. package/dist/src/cli/commands/tunnel.js +2 -2
  218. package/dist/src/cli/utils/gateway-client.js +1 -1
  219. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  220. package/dist/src/config/agent-profile.js +1 -1
  221. package/dist/src/config/gateway-bind.js +1 -1
  222. package/dist/src/config/index.js +5 -5
  223. package/dist/src/config/loader.js +2 -2
  224. package/dist/src/config/models-json.js +2 -2
  225. package/dist/src/config/paths-state.js +1 -1
  226. package/dist/src/config/profile.js +2 -2
  227. package/dist/src/config/public-url.d.ts +28 -0
  228. package/dist/src/config/public-url.js +103 -0
  229. package/dist/src/config/public-url.js.map +1 -0
  230. package/dist/src/config/schema.d.ts +82 -0
  231. package/dist/src/config/schema.js +130 -1
  232. package/dist/src/config/schema.js.map +1 -1
  233. package/dist/src/config/workspace-path.js +1 -1
  234. package/dist/src/cron/executor.js +2 -2
  235. package/dist/src/cron/persistence.js +1 -1
  236. package/dist/src/cron/run-log-store.js +1 -1
  237. package/dist/src/daemon/constants.js +1 -1
  238. package/dist/src/daemon/install-plan.js +3 -3
  239. package/dist/src/daemon/install-plan.js.map +1 -1
  240. package/dist/src/daemon/launchd.js +2 -2
  241. package/dist/src/daemon/schtasks.js +38 -1
  242. package/dist/src/daemon/schtasks.js.map +1 -1
  243. package/dist/src/daemon/systemd.js +2 -2
  244. package/dist/src/extensions/bundle-mcp.js +1 -1
  245. package/dist/src/extensions/discover-extensions.js +1 -1
  246. package/dist/src/extensions/health.js +1 -1
  247. package/dist/src/extensions/loader.js +1 -1
  248. package/dist/src/extensions/lockfile.js +2 -2
  249. package/dist/src/gateway/agents-admin.js +2 -2
  250. package/dist/src/gateway/file-path-classifier.js +2 -2
  251. package/dist/src/gateway/heartbeat/service.js +1 -1
  252. package/dist/src/gateway/hono/app.js +33 -2
  253. package/dist/src/gateway/hono/app.js.map +1 -1
  254. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  255. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  256. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  257. package/dist/src/gateway/hono/oauth.js +1 -1
  258. package/dist/src/gateway/hono/routes/agents.js +1 -1
  259. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  260. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  261. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  262. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  263. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  264. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  265. package/dist/src/gateway/hono/routes/models.js +1 -1
  266. package/dist/src/gateway/hono/routes/shares.js +631 -34
  267. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  268. package/dist/src/gateway/hono/routes/site-shares.d.ts +3 -0
  269. package/dist/src/gateway/hono/routes/site-shares.js +228 -0
  270. package/dist/src/gateway/hono/routes/site-shares.js.map +1 -0
  271. package/dist/src/gateway/hono/routes/tunnel.js +97 -8
  272. package/dist/src/gateway/hono/routes/tunnel.js.map +1 -1
  273. package/dist/src/gateway/hono/routes/workspace.js +5 -5
  274. package/dist/src/gateway/hono/sse.js +2 -2
  275. package/dist/src/gateway/host.d.ts +3 -1
  276. package/dist/src/gateway/host.js +3 -1
  277. package/dist/src/gateway/host.js.map +1 -1
  278. package/dist/src/gateway/lock.js +3 -3
  279. package/dist/src/gateway/ports.d.ts +6 -0
  280. package/dist/src/gateway/ports.js +38 -2
  281. package/dist/src/gateway/ports.js.map +1 -1
  282. package/dist/src/gateway/public-url.d.ts +8 -0
  283. package/dist/src/gateway/public-url.js +10 -0
  284. package/dist/src/gateway/public-url.js.map +1 -0
  285. package/dist/src/gateway/security/origin-check.d.ts +9 -1
  286. package/dist/src/gateway/security/origin-check.js +4 -0
  287. package/dist/src/gateway/security/origin-check.js.map +1 -1
  288. package/dist/src/gateway/server.js +15 -0
  289. package/dist/src/gateway/server.js.map +1 -1
  290. package/dist/src/gateway/service/agent-runner.js +2 -2
  291. package/dist/src/gateway/service/marketplace-service.js +2 -2
  292. package/dist/src/gateway/service/run-gateway-agent.js +2 -2
  293. package/dist/src/gateway/service.js +3 -2
  294. package/dist/src/gateway/service.js.map +1 -1
  295. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  296. package/dist/src/heartbeat/index.js +1 -1
  297. package/dist/src/i18n/goals-bundle.js +1 -1
  298. package/dist/src/i18n/index.d.ts +1 -0
  299. package/dist/src/i18n/index.js +2 -1
  300. package/dist/src/i18n/locales/share-tool.en.js +15 -0
  301. package/dist/src/i18n/locales/share-tool.en.js.map +1 -0
  302. package/dist/src/i18n/locales/share-tool.zh.js +15 -0
  303. package/dist/src/i18n/locales/share-tool.zh.js.map +1 -0
  304. package/dist/src/i18n/share-tool-bundle.d.ts +20 -0
  305. package/dist/src/i18n/share-tool-bundle.js +56 -0
  306. package/dist/src/i18n/share-tool-bundle.js.map +1 -0
  307. package/dist/src/infra/gateway-processes.js +1 -0
  308. package/dist/src/infra/gateway-processes.js.map +1 -1
  309. package/dist/src/infra/restart.js +2 -2
  310. package/dist/src/infra/update-check.js +1 -1
  311. package/dist/src/infra/update-lock.js +3 -3
  312. package/dist/src/infra/update-runner.js +1 -1
  313. package/dist/src/infra/update-startup.js +2 -2
  314. package/dist/src/infra/write-file-atomic.js +2 -2
  315. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  316. package/dist/src/providers/index.js +2 -2
  317. package/dist/src/providers/model-registry.js +1 -1
  318. package/dist/src/session/config-store.js +2 -2
  319. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  320. package/dist/src/session/parity/sessions-json-file.js +1 -1
  321. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  322. package/dist/src/session/parity/transcript-paths.js +1 -1
  323. package/dist/src/session/search-index-cache.js +1 -1
  324. package/dist/src/session/search-index.js +1 -1
  325. package/dist/src/session/session-title.js +3 -2
  326. package/dist/src/session/session-title.js.map +1 -1
  327. package/dist/src/session/store.js +5 -5
  328. package/dist/src/share/share-auto.d.ts +74 -0
  329. package/dist/src/share/share-auto.js +247 -0
  330. package/dist/src/share/share-auto.js.map +1 -0
  331. package/dist/src/share/share-config.js +63 -4
  332. package/dist/src/share/share-config.js.map +1 -1
  333. package/dist/src/share/share-landing.d.ts +28 -2
  334. package/dist/src/share/share-landing.js +155 -34
  335. package/dist/src/share/share-landing.js.map +1 -1
  336. package/dist/src/share/share-store.d.ts +48 -4
  337. package/dist/src/share/share-store.js +322 -51
  338. package/dist/src/share/share-store.js.map +1 -1
  339. package/dist/src/share/share-thumbnail.d.ts +35 -0
  340. package/dist/src/share/share-thumbnail.js +277 -0
  341. package/dist/src/share/share-thumbnail.js.map +1 -0
  342. package/dist/src/share/share-types.d.ts +68 -10
  343. package/dist/src/share/share-types.js +18 -1
  344. package/dist/src/share/share-types.js.map +1 -1
  345. package/dist/src/share/share-url.js +1 -1
  346. package/dist/src/share/share-zip.d.ts +35 -0
  347. package/dist/src/share/share-zip.js +303 -0
  348. package/dist/src/share/share-zip.js.map +1 -0
  349. package/dist/src/share/site-proxy.d.ts +35 -0
  350. package/dist/src/share/site-proxy.js +234 -0
  351. package/dist/src/share/site-proxy.js.map +1 -0
  352. package/dist/src/share/site-share-config.d.ts +11 -0
  353. package/dist/src/share/site-share-config.js +103 -0
  354. package/dist/src/share/site-share-config.js.map +1 -0
  355. package/dist/src/share/site-share-router.d.ts +23 -0
  356. package/dist/src/share/site-share-router.js +147 -0
  357. package/dist/src/share/site-share-router.js.map +1 -0
  358. package/dist/src/share/site-share-store.d.ts +53 -0
  359. package/dist/src/share/site-share-store.js +400 -0
  360. package/dist/src/share/site-share-store.js.map +1 -0
  361. package/dist/src/share/site-share-types.d.ts +103 -0
  362. package/dist/src/share/site-share-types.js +41 -0
  363. package/dist/src/share/site-share-types.js.map +1 -0
  364. package/dist/src/share/site-static-serve.d.ts +10 -0
  365. package/dist/src/share/site-static-serve.js +145 -0
  366. package/dist/src/share/site-static-serve.js.map +1 -0
  367. package/dist/src/tui/clipboard-image.js +3 -3
  368. package/dist/src/tui/theme-manager.js +1 -1
  369. package/dist/src/tui/tui-commands.js +18 -0
  370. package/dist/src/tui/tui-commands.js.map +1 -1
  371. package/dist/src/tui/tui-keybindings-file.js +1 -1
  372. package/dist/src/tui/tui-scoped-models.js +2 -2
  373. package/dist/src/tui/tui-settings.js +1 -1
  374. package/dist/src/tui/tui-workflow-slash.d.ts +32 -0
  375. package/dist/src/tui/tui-workflow-slash.js +63 -0
  376. package/dist/src/tui/tui-workflow-slash.js.map +1 -0
  377. package/dist/src/tui/tui.js +2 -2
  378. package/dist/src/tunnel/enable-lan-pairing.js +1 -1
  379. package/dist/src/tunnel/frpc-binary.js +3 -3
  380. package/dist/src/tunnel/frpc-config.js +1 -1
  381. package/dist/src/tunnel/frpc-extract.js +1 -1
  382. package/dist/src/tunnel/index.js +2 -2
  383. package/dist/src/tunnel/pair-context.d.ts +7 -1
  384. package/dist/src/tunnel/pair-context.js +25 -9
  385. package/dist/src/tunnel/pair-context.js.map +1 -1
  386. package/dist/src/tunnel/pair-url.d.ts +14 -1
  387. package/dist/src/tunnel/pair-url.js +14 -1
  388. package/dist/src/tunnel/pair-url.js.map +1 -1
  389. package/dist/src/tunnel/tunnel-service.js +2 -2
  390. package/dist/src/tunnel/tunnel-state.js +1 -1
  391. package/dist/src/utils/logger/audit.js +1 -1
  392. package/dist/src/utils/logger/log-store.js +1 -1
  393. package/dist/src/utils/logger/rotation.js +1 -1
  394. package/dist/src/voice/tts/audio.js +1 -1
  395. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  396. package/package.json +3 -2
  397. package/dist/gateway/static/root/assets/agents-tR-nNP04.js +0 -222
  398. package/dist/gateway/static/root/assets/apps-page-BDw6SP-d.js +0 -1
  399. package/dist/gateway/static/root/assets/button-KafIU8dx.js +0 -1
  400. package/dist/gateway/static/root/assets/channels-settings-DEFd-jj1.js +0 -1
  401. package/dist/gateway/static/root/assets/channels-status-swr-DI5FHdGe.js +0 -8
  402. package/dist/gateway/static/root/assets/cron-api-BSqY8LwW.js +0 -1
  403. package/dist/gateway/static/root/assets/cron-page-D7lVDjcR.js +0 -1
  404. package/dist/gateway/static/root/assets/dist-C57OMHW8.js +0 -48
  405. package/dist/gateway/static/root/assets/extension-page-CQo2Xsmg.js +0 -1
  406. package/dist/gateway/static/root/assets/extension-settings-page-CZf0WoZg.js +0 -1
  407. package/dist/gateway/static/root/assets/fetch-2iRFmd3n.js +0 -3
  408. package/dist/gateway/static/root/assets/heartbeat-config-api-B0drdQEJ.js +0 -1
  409. package/dist/gateway/static/root/assets/index-0Gt3TG4j.js +0 -4693
  410. package/dist/gateway/static/root/assets/index-BuFldCsB.css +0 -1
  411. package/dist/gateway/static/root/assets/logs-page-DMuORLfC.js +0 -1
  412. package/dist/gateway/static/root/assets/sessions-page-_UO8g6NN.js +0 -1
  413. package/dist/gateway/static/root/assets/settings-form-section-DkmHkknc.js +0 -1
  414. package/dist/gateway/static/root/assets/settings-page-Cz8FoW_A.js +0 -3
  415. package/dist/gateway/static/root/assets/skills-page-HrUOxF7H.js +0 -2
  416. package/dist/gateway/static/root/assets/theme-store-D01dJt95.js +0 -1
  417. package/dist/gateway/static/root/assets/utils-BFwcR6pL.js +0 -1
  418. package/dist/gateway/static/root/assets/voice-api-key-field-JF8-aqc5.js +0 -1
@@ -0,0 +1,145 @@
1
+ import { isPathUnderWorkspace } from "../gateway/workspace-editor-path.js";
2
+ import { resolveMimeType, shareResponseContentType } from "./share-store.js";
3
+ import { join, relative, resolve } from "node:path";
4
+ import { createReadStream } from "node:fs";
5
+ import { readFile, stat } from "node:fs/promises";
6
+ import { Readable } from "node:stream";
7
+ //#region src/share/site-static-serve.ts
8
+ const HASHED_ASSET_RE = /\.[a-f0-9]{8,}\.(?:js|mjs|css|woff2?|ttf|otf|svg|png|jpg|jpeg|gif|webp|avif|ico)$/i;
9
+ const REWRITABLE_TYPES = new Set(["text/html", "text/css"]);
10
+ /**
11
+ * Serve a request for a static site share.
12
+ *
13
+ * `urlPath` is the in-site path (already stripped of any subdomain/subpath base).
14
+ * `basePrefix` is the path prefix that the share is mounted under, used by the
15
+ * HTML rewriter to make root-relative URLs (`/assets/x`) point back into the
16
+ * share. Pass '' when serving on a subdomain (root mode).
17
+ */
18
+ async function serveStaticSiteRequest(record, urlPath, basePrefix) {
19
+ if (record.source.kind !== "static") return new Response("not_static", { status: 400 });
20
+ const source = record.source;
21
+ const sanitized = sanitizePath(urlPath);
22
+ if (sanitized.startsWith("..")) return new Response("forbidden", { status: 403 });
23
+ const absInitial = resolve(source.rootDir, sanitized);
24
+ const inside = await pathInsideRoot(source.rootDir, source.workspaceRoot, absInitial);
25
+ if (!inside.ok) {
26
+ if (source.spaFallback) return serveIndexFallback(source, basePrefix);
27
+ return new Response("not_found", { status: 404 });
28
+ }
29
+ let abs = inside.abs;
30
+ let stats;
31
+ try {
32
+ stats = await stat(abs);
33
+ } catch {
34
+ if (source.spaFallback) return serveIndexFallback(source, basePrefix);
35
+ return new Response("not_found", { status: 404 });
36
+ }
37
+ if (stats.isDirectory()) {
38
+ const indexPath = join(abs, "index.html");
39
+ try {
40
+ const indexStat = await stat(indexPath);
41
+ if (indexStat.isFile()) {
42
+ abs = indexPath;
43
+ stats = indexStat;
44
+ } else if (source.spaFallback) return serveIndexFallback(source, basePrefix);
45
+ else return new Response("not_found", { status: 404 });
46
+ } catch {
47
+ if (source.spaFallback) return serveIndexFallback(source, basePrefix);
48
+ return new Response("not_found", { status: 404 });
49
+ }
50
+ }
51
+ const fileName = abs.split(/[\\/]/).pop() ?? "file";
52
+ const mime = resolveMimeType(fileName);
53
+ const cacheHeader = decideCacheHeader(sanitized, mime);
54
+ if (source.rewriteMode !== "none" && REWRITABLE_TYPES.has(mime)) {
55
+ const rewritten = rewriteAbsolutePaths(await readFile(abs, "utf8"), mime, basePrefix, source.rewriteMode);
56
+ const bytes = Buffer.from(rewritten, "utf8");
57
+ return new Response(bytes, {
58
+ status: 200,
59
+ headers: buildHeaders(mime, bytes.length, cacheHeader, fileName)
60
+ });
61
+ }
62
+ const stream = createReadStream(abs);
63
+ const webStream = Readable.toWeb(stream);
64
+ return new Response(webStream, {
65
+ status: 200,
66
+ headers: buildHeaders(mime, stats.size, cacheHeader, fileName)
67
+ });
68
+ }
69
+ async function serveIndexFallback(source, basePrefix) {
70
+ const indexPath = join(source.rootDir, "index.html");
71
+ try {
72
+ const raw = await readFile(indexPath, "utf8");
73
+ const rewritten = source.rewriteMode !== "none" ? rewriteAbsolutePaths(raw, "text/html", basePrefix, source.rewriteMode) : raw;
74
+ const bytes = Buffer.from(rewritten, "utf8");
75
+ return new Response(bytes, {
76
+ status: 200,
77
+ headers: buildHeaders("text/html", bytes.length, "no-cache", "index.html")
78
+ });
79
+ } catch {
80
+ return new Response("not_found", { status: 404 });
81
+ }
82
+ }
83
+ function buildHeaders(mime, contentLength, cacheControl, fileName) {
84
+ return {
85
+ "Content-Type": shareResponseContentType(mime),
86
+ "Content-Length": String(contentLength),
87
+ "Cache-Control": cacheControl,
88
+ "X-Content-Type-Options": "nosniff",
89
+ "Referrer-Policy": "no-referrer",
90
+ "Content-Disposition": `inline; filename="${fileName.replace(/[^\x20-\x7e]/g, "_").replace(/"/g, "")}"`
91
+ };
92
+ }
93
+ function decideCacheHeader(urlPath, mime) {
94
+ if (HASHED_ASSET_RE.test(urlPath)) return "public, max-age=31536000, immutable";
95
+ if (mime === "text/html") return "no-cache";
96
+ return "public, max-age=600";
97
+ }
98
+ function sanitizePath(input) {
99
+ let p = input.replace(/\\/g, "/");
100
+ p = p.replace(/^\/+/, "");
101
+ if (p.includes("\0")) return "..";
102
+ if (p.split("/").includes("..")) return "..";
103
+ return p;
104
+ }
105
+ async function pathInsideRoot(rootDir, workspaceRoot, abs) {
106
+ const relToRoot = relative(rootDir, abs);
107
+ if (relToRoot.startsWith("..") || relToRoot.split(/[/\\]/).includes("..")) return { ok: false };
108
+ if (!isPathUnderWorkspace(workspaceRoot, abs)) return { ok: false };
109
+ return {
110
+ ok: true,
111
+ abs
112
+ };
113
+ }
114
+ function rewriteAbsolutePaths(body, mime, basePrefix, mode) {
115
+ if (mode === "none" || !basePrefix) return body;
116
+ if (mime === "text/html") return rewriteHtml(body, basePrefix);
117
+ if (mime === "text/css" && mode === "html-css") return rewriteCss(body, basePrefix);
118
+ return body;
119
+ }
120
+ function rewriteHtml(html, basePrefix) {
121
+ let out = html.replace(/\s(href|src|action|poster|formaction|data)\s*=\s*("|')\/([^"']*?)\2/gi, (_match, attr, quote, rest) => {
122
+ if (rest.startsWith("/")) return _match;
123
+ return ` ${attr}=${quote}${basePrefix}/${rest}${quote}`;
124
+ });
125
+ out = out.replace(/\s(srcset|imagesrcset)\s*=\s*("|')([^"']*)\2/gi, (_match, attr, quote, value) => {
126
+ return ` ${attr}=${quote}${value.split(",").map((part) => {
127
+ const trimmed = part.trim();
128
+ const [url, descriptor] = trimmed.split(/\s+/, 2);
129
+ if (!url || !url.startsWith("/") || url.startsWith("//")) return trimmed;
130
+ return descriptor ? `${basePrefix}${url} ${descriptor}` : `${basePrefix}${url}`;
131
+ }).join(", ")}${quote}`;
132
+ });
133
+ out = out.replace(/<head([^>]*)>/i, `<head$1><base href="${basePrefix}/">`);
134
+ return out;
135
+ }
136
+ function rewriteCss(css, basePrefix) {
137
+ return css.replace(/url\(\s*("|'|)\/([^)"']*?)\1\s*\)/g, (_m, quote, rest) => {
138
+ if (rest.startsWith("/")) return _m;
139
+ return `url(${quote}${basePrefix}/${rest}${quote})`;
140
+ });
141
+ }
142
+ //#endregion
143
+ export { serveStaticSiteRequest };
144
+
145
+ //# sourceMappingURL=site-static-serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-static-serve.js","names":["resolvePath","relPathPosix"],"sources":["../../../src/share/site-static-serve.ts"],"sourcesContent":["import { createReadStream } from 'node:fs';\nimport { stat, readFile } from 'node:fs/promises';\nimport { resolve as resolvePath, relative as relPathPosix, join } from 'node:path';\nimport { Readable } from 'node:stream';\n\nimport { isPathUnderWorkspace } from '../gateway/workspace-editor-path.js';\nimport { resolveMimeType, shareResponseContentType } from './share-store.js';\nimport type { SiteShareRecord, SiteStaticSource } from './site-share-types.js';\n\nconst HASHED_ASSET_RE = /\\.[a-f0-9]{8,}\\.(?:js|mjs|css|woff2?|ttf|otf|svg|png|jpg|jpeg|gif|webp|avif|ico)$/i;\nconst REWRITABLE_TYPES = new Set(['text/html', 'text/css']);\n\n/**\n * Serve a request for a static site share.\n *\n * `urlPath` is the in-site path (already stripped of any subdomain/subpath base).\n * `basePrefix` is the path prefix that the share is mounted under, used by the\n * HTML rewriter to make root-relative URLs (`/assets/x`) point back into the\n * share. Pass '' when serving on a subdomain (root mode).\n */\nexport async function serveStaticSiteRequest(\n record: SiteShareRecord,\n urlPath: string,\n basePrefix: string,\n): Promise<Response> {\n if (record.source.kind !== 'static') {\n return new Response('not_static', { status: 400 });\n }\n const source = record.source;\n\n const sanitized = sanitizePath(urlPath);\n if (sanitized.startsWith('..')) return new Response('forbidden', { status: 403 });\n\n const absInitial = resolvePath(source.rootDir, sanitized);\n const inside = await pathInsideRoot(source.rootDir, source.workspaceRoot, absInitial);\n if (!inside.ok) {\n if (source.spaFallback) {\n return serveIndexFallback(source, basePrefix);\n }\n return new Response('not_found', { status: 404 });\n }\n\n let abs = inside.abs;\n let stats;\n try {\n stats = await stat(abs);\n } catch {\n if (source.spaFallback) return serveIndexFallback(source, basePrefix);\n return new Response('not_found', { status: 404 });\n }\n\n if (stats.isDirectory()) {\n const indexPath = join(abs, 'index.html');\n try {\n const indexStat = await stat(indexPath);\n if (indexStat.isFile()) {\n abs = indexPath;\n stats = indexStat;\n } else if (source.spaFallback) {\n return serveIndexFallback(source, basePrefix);\n } else {\n return new Response('not_found', { status: 404 });\n }\n } catch {\n if (source.spaFallback) return serveIndexFallback(source, basePrefix);\n return new Response('not_found', { status: 404 });\n }\n }\n\n const fileName = abs.split(/[\\\\/]/).pop() ?? 'file';\n const mime = resolveMimeType(fileName);\n\n const cacheHeader = decideCacheHeader(sanitized, mime);\n\n if (source.rewriteMode !== 'none' && REWRITABLE_TYPES.has(mime)) {\n const raw = await readFile(abs, 'utf8');\n const rewritten = rewriteAbsolutePaths(raw, mime, basePrefix, source.rewriteMode);\n const bytes = Buffer.from(rewritten, 'utf8');\n return new Response(bytes, {\n status: 200,\n headers: buildHeaders(mime, bytes.length, cacheHeader, fileName),\n });\n }\n\n const stream = createReadStream(abs);\n const webStream = Readable.toWeb(stream) as ReadableStream;\n return new Response(webStream, {\n status: 200,\n headers: buildHeaders(mime, stats.size, cacheHeader, fileName),\n });\n}\n\nasync function serveIndexFallback(source: SiteStaticSource, basePrefix: string): Promise<Response> {\n const indexPath = join(source.rootDir, 'index.html');\n try {\n const raw = await readFile(indexPath, 'utf8');\n const rewritten =\n source.rewriteMode !== 'none' ? rewriteAbsolutePaths(raw, 'text/html', basePrefix, source.rewriteMode) : raw;\n const bytes = Buffer.from(rewritten, 'utf8');\n return new Response(bytes, {\n status: 200,\n headers: buildHeaders('text/html', bytes.length, 'no-cache', 'index.html'),\n });\n } catch {\n return new Response('not_found', { status: 404 });\n }\n}\n\nfunction buildHeaders(\n mime: string,\n contentLength: number,\n cacheControl: string,\n fileName: string,\n): Record<string, string> {\n return {\n 'Content-Type': shareResponseContentType(mime),\n 'Content-Length': String(contentLength),\n 'Cache-Control': cacheControl,\n 'X-Content-Type-Options': 'nosniff',\n 'Referrer-Policy': 'no-referrer',\n // Don't force attachment — site-share is meant to be browsed.\n 'Content-Disposition': `inline; filename=\"${fileName.replace(/[^\\x20-\\x7e]/g, '_').replace(/\"/g, '')}\"`,\n };\n}\n\nfunction decideCacheHeader(urlPath: string, mime: string): string {\n if (HASHED_ASSET_RE.test(urlPath)) {\n return 'public, max-age=31536000, immutable';\n }\n if (mime === 'text/html') return 'no-cache';\n return 'public, max-age=600';\n}\n\nfunction sanitizePath(input: string): string {\n let p = input.replace(/\\\\/g, '/');\n p = p.replace(/^\\/+/, '');\n if (p.includes('\\0')) return '..';\n if (p.split('/').includes('..')) return '..';\n return p;\n}\n\nasync function pathInsideRoot(\n rootDir: string,\n workspaceRoot: string,\n abs: string,\n): Promise<{ ok: true; abs: string } | { ok: false }> {\n const relToRoot = relPathPosix(rootDir, abs);\n if (relToRoot.startsWith('..') || relToRoot.split(/[/\\\\]/).includes('..')) {\n return { ok: false };\n }\n if (!isPathUnderWorkspace(workspaceRoot, abs)) {\n return { ok: false };\n }\n return { ok: true, abs };\n}\n\n// ── HTML/CSS rewriting ────────────────────────────────────────────────────────\n//\n// Best-effort root-relative path prefixing. For SPAs built with `base: '/'`,\n// the produced HTML/CSS contains `/assets/...` references that need to be\n// prefixed with the share's `basePrefix`. JS-generated URLs at runtime are NOT\n// handled here — recommend the user set `base` at build time when possible.\n\nfunction rewriteAbsolutePaths(\n body: string,\n mime: string,\n basePrefix: string,\n mode: 'none' | 'html-only' | 'html-css',\n): string {\n if (mode === 'none' || !basePrefix) return body;\n if (mime === 'text/html') {\n return rewriteHtml(body, basePrefix);\n }\n if (mime === 'text/css' && mode === 'html-css') {\n return rewriteCss(body, basePrefix);\n }\n return body;\n}\n\nfunction rewriteHtml(html: string, basePrefix: string): string {\n // Match attribute values like src=\"/x\", href='/y', srcset=\"/a, /b 2x\"\n const attrRe = /\\s(href|src|action|poster|formaction|data)\\s*=\\s*(\"|')\\/([^\"']*?)\\2/gi;\n let out = html.replace(attrRe, (_match, attr, quote, rest) => {\n if (rest.startsWith('/')) return _match; // protocol-relative '//'\n return ` ${attr}=${quote}${basePrefix}/${rest}${quote}`;\n });\n\n const srcsetRe = /\\s(srcset|imagesrcset)\\s*=\\s*(\"|')([^\"']*)\\2/gi;\n out = out.replace(srcsetRe, (_match, attr, quote, value) => {\n const fixed = value\n .split(',')\n .map((part: string) => {\n const trimmed = part.trim();\n const [url, descriptor] = trimmed.split(/\\s+/, 2);\n if (!url || !url.startsWith('/') || url.startsWith('//')) return trimmed;\n return descriptor ? `${basePrefix}${url} ${descriptor}` : `${basePrefix}${url}`;\n })\n .join(', ');\n return ` ${attr}=${quote}${fixed}${quote}`;\n });\n\n // Inject <base> tag for clarity (helps relative URLs inside the page)\n out = out.replace(/<head([^>]*)>/i, `<head$1><base href=\"${basePrefix}/\">`);\n\n return out;\n}\n\nfunction rewriteCss(css: string, basePrefix: string): string {\n // url(/x) — only absolute-root references\n return css.replace(/url\\(\\s*(\"|'|)\\/([^)\"']*?)\\1\\s*\\)/g, (_m, quote, rest) => {\n if (rest.startsWith('/')) return _m;\n return `url(${quote}${basePrefix}/${rest}${quote})`;\n });\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { randomUUID } from "node:crypto";
2
- import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
1
  import { tmpdir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { randomUUID } from "node:crypto";
5
5
  import { spawnSync } from "node:child_process";
6
6
  //#region src/tui/clipboard-image.ts
7
7
  const SUPPORTED_IMAGE_MIME_TYPES = [
@@ -2,8 +2,8 @@ import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
3
  import { palette } from "./theme/dark.js";
4
4
  import { palette as palette$1 } from "./theme/light.js";
5
- import { existsSync, readFileSync, readdirSync } from "node:fs";
6
5
  import { join } from "node:path";
6
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
7
7
  import chalk from "chalk";
8
8
  //#region src/tui/theme-manager.ts
9
9
  init_paths();
@@ -1,5 +1,6 @@
1
1
  import { getTuiKeybindingsPath } from "./tui-keybindings-file.js";
2
2
  import { formatXopcTuiHotkeys } from "./format-tui-hotkeys.js";
3
+ import { rewriteUnknownSlashAsWorkflow } from "./tui-workflow-slash.js";
3
4
  //#region src/tui/tui-commands.ts
4
5
  function getSlashCommands(_isLocal) {
5
6
  return [
@@ -106,6 +107,14 @@ function getSlashCommands(_isLocal) {
106
107
  {
107
108
  name: "hotkeys",
108
109
  description: "Show resolved keyboard shortcuts (pi-style)"
110
+ },
111
+ {
112
+ name: "workflows",
113
+ description: "List saved workflows (built-in + ~/.xopc/workflows/)"
114
+ },
115
+ {
116
+ name: "workflow",
117
+ description: "Workflow subcommands: list, view <name>, save <name>"
109
118
  }
110
119
  ];
111
120
  }
@@ -220,6 +229,15 @@ function createTuiCommandHandler(deps) {
220
229
  return;
221
230
  default: break;
222
231
  }
232
+ if (input.trimStart().startsWith("/")) {
233
+ const rewritten = rewriteUnknownSlashAsWorkflow(normalizedCommand, commandArgs);
234
+ if (rewritten) {
235
+ chatLog.addSystem(`▶ Running workflow: ${normalizedCommand}`);
236
+ tui.requestRender();
237
+ sendMessage(rewritten);
238
+ return;
239
+ }
240
+ }
223
241
  sendMessage(input);
224
242
  };
225
243
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tui-commands.js","names":[],"sources":["../../../src/tui/tui-commands.ts"],"sourcesContent":["import type { KeybindingsManager, TUI } from '@earendil-works/pi-tui';\n\nimport type { ChatLog } from './components/chat-log.js';\nimport { formatXopcTuiHotkeys } from './format-tui-hotkeys.js';\nimport { getTuiKeybindingsPath } from './tui-keybindings-file.js';\nimport type { StreamAssembler } from './stream-assembler.js';\nimport type { TuiState } from './tui-types.js';\n\ninterface SlashCommandDef {\n name: string;\n description: string;\n}\n\nexport type TuiExtensionSlashCommandEntry = SlashCommandDef & {\n handler: (args: string) => void | Promise<void>;\n};\n\nexport function getSlashCommands(_isLocal: boolean): SlashCommandDef[] {\n return [\n { name: 'help', description: 'Show available commands' },\n { name: 'abort', description: 'Abort active run (or press Escape)' },\n { name: 'tools', description: 'Toggle tool output expanded/collapsed (or Ctrl+O)' },\n { name: 'thinking', description: 'Toggle thinking display (or Ctrl+T)' },\n { name: 'exit', description: 'Exit the TUI' },\n { name: 'models', description: 'List available models' },\n { name: 'switch', description: 'Switch model — copy `provider/model` from /models' },\n { name: 'usage', description: 'Show token usage statistics' },\n { name: 'new', description: 'Start a new session' },\n { name: 'clear', description: 'Clear current session' },\n { name: 'list', description: 'List sessions' },\n { name: 'resume', description: 'Open session picker (or Ctrl+Shift+P)' },\n { name: 'scoped-models', description: 'Choose models for Ctrl+P cycling' },\n { name: 'compact', description: 'Compact session history (local API)' },\n { name: 'think', description: 'Set thinking level (e.g. /think high)' },\n { name: 'reasoning', description: 'Set reasoning visibility (e.g. /reasoning stream)' },\n { name: 'verbose', description: 'Toggle verbose mode' },\n { name: 'status', description: 'Show agent status' },\n { name: 'config', description: 'Show or update configuration' },\n { name: 'context', description: 'Show context budget' },\n { name: 'btw', description: 'Side question without saving to session' },\n { name: 'export', description: 'Export session (markdown/html/json)' },\n { name: 'settings', description: 'Open TUI settings overlay' },\n { name: 'reload-keybindings', description: 'Reload ~/.xopc/keybindings.json' },\n { name: 'start', description: 'Show welcome message' },\n { name: 'hotkeys', description: 'Show resolved keyboard shortcuts (pi-style)' },\n ];\n}\n\nexport function formatTuiHelpText(isLocal: boolean): string {\n const commands = getSlashCommands(isLocal);\n const lines = ['Available commands:'];\n for (const c of commands) {\n lines.push(` /${c.name} — ${c.description}`);\n }\n lines.push('', 'Keyboard shortcuts (defaults align with pi coding-agent where noted):');\n lines.push(' Escape — Abort active run');\n lines.push(' Shift+Tab — Cycle /think level');\n lines.push(' Ctrl+P / Shift+Ctrl+P — Next / previous model (/switch)');\n lines.push(' Ctrl+L — Model picker');\n lines.push(' Ctrl+Shift+P — Session picker (rename/delete)');\n lines.push(' /scoped-models — Limit Ctrl+P model cycle set');\n lines.push(' Ctrl+O — Toggle tool output');\n lines.push(' Ctrl+T — Toggle thinking block display');\n lines.push(' Ctrl+G — Edit draft in $EDITOR');\n lines.push(' Ctrl+Z — Suspend to shell (Unix)');\n lines.push(' Alt+Enter — Queue follow-up while busy (sends when this reply finishes)');\n lines.push(' Alt+Up — Restore queued follow-up messages to editor');\n lines.push(' Enter (while busy) — Steer: inject at next tool boundary');\n lines.push(' Ctrl+V (mac/Linux) / Alt+V (Win) — Paste image from clipboard');\n lines.push(' /settings — Theme, thinking display, terminal progress, …');\n lines.push(' ~/.xopc/keybindings.json — Custom shortcuts (use /reload-keybindings)');\n lines.push(' Ctrl+C — Clear input; repeat within ~0.5s to exit when empty');\n lines.push(' Ctrl+D — Exit when input empty');\n lines.push(' !cmd — Local shell (gated; runs on this machine)');\n lines.push('', 'Use /hotkeys for the resolved binding list from the active keymap.');\n return lines.join('\\n');\n}\n\nexport type CommandHandlerDeps = {\n state: TuiState;\n chatLog: ChatLog;\n tui: TUI;\n assembler: StreamAssembler;\n isLocalMode: boolean;\n abortActive: () => Promise<void>;\n sendMessage: (text: string) => void;\n requestExit: () => void;\n updateFooter: () => void;\n keybindings: KeybindingsManager;\n uiOverlays?: {\n openSessionPicker: () => void;\n openScopedModels: () => void;\n openSettings: () => void;\n reloadKeybindings: () => void;\n };\n runCompaction?: () => void | Promise<void>;\n extensionSlashCommands?: TuiExtensionSlashCommandEntry[];\n};\n\nexport function createTuiCommandHandler(deps: CommandHandlerDeps): (input: string) => void {\n const {\n state,\n chatLog,\n tui,\n assembler,\n isLocalMode,\n abortActive,\n sendMessage,\n requestExit,\n updateFooter,\n keybindings,\n uiOverlays,\n extensionSlashCommands = [],\n } = deps;\n\n return (input: string) => {\n const trimmed = input.replace(/^\\//, '').trim();\n const [commandName, ...restParts] = trimmed.split(/\\s+/);\n const normalizedCommand = (commandName ?? '').toLowerCase();\n const commandArgs = restParts.join(' ');\n\n const extensionCmd = extensionSlashCommands.find((c) => c.name === normalizedCommand);\n if (extensionCmd) {\n void Promise.resolve(extensionCmd.handler(commandArgs)).then(() => {\n tui.requestRender();\n });\n return;\n }\n\n switch (normalizedCommand) {\n case 'help':\n chatLog.addSystem(formatTuiHelpText(isLocalMode));\n tui.requestRender();\n return;\n case 'hotkeys':\n case 'keys':\n chatLog.addSystem(formatXopcTuiHotkeys(keybindings));\n tui.requestRender();\n return;\n case 'exit':\n case 'quit':\n requestExit();\n return;\n case 'abort':\n case 'stop':\n case 'cancel':\n void abortActive().then(() => {\n chatLog.addSystem('Aborted.');\n tui.requestRender();\n });\n return;\n case 'tools':\n state.toolsExpanded = !state.toolsExpanded;\n chatLog.setToolsExpanded(state.toolsExpanded);\n chatLog.addSystem(`Tools: ${state.toolsExpanded ? 'expanded' : 'collapsed'}`);\n tui.requestRender();\n return;\n case 'thinking':\n state.showThinking = !state.showThinking;\n chatLog.addSystem(`Thinking display: ${state.showThinking ? 'on' : 'off'}`);\n updateFooter();\n tui.requestRender();\n return;\n case 'resume':\n case 'sessions':\n uiOverlays?.openSessionPicker();\n return;\n case 'scoped-models':\n case 'scopedmodels':\n uiOverlays?.openScopedModels();\n return;\n case 'settings':\n uiOverlays?.openSettings();\n return;\n case 'reload-keybindings':\n case 'reload-keybind':\n uiOverlays?.reloadKeybindings();\n chatLog.addSystem(`Keybindings reloaded from ${getTuiKeybindingsPath()}`);\n tui.requestRender();\n return;\n case 'compact':\n void deps.runCompaction?.();\n return;\n default:\n break;\n }\n\n switch (normalizedCommand) {\n case 'new':\n case 'reset':\n case 'restart':\n case 'clear': {\n void abortActive().then(() => {\n assembler.clear();\n chatLog.clearAll();\n state.messageFollowUpQueue.length = 0;\n tui.requestRender();\n sendMessage(input);\n });\n return;\n }\n default:\n break;\n }\n\n sendMessage(input);\n };\n}\n"],"mappings":";;;AAiBA,SAAgB,iBAAiB,UAAsC;AACrE,QAAO;EACL;GAAE,MAAM;GAAQ,aAAa;GAA2B;EACxD;GAAE,MAAM;GAAS,aAAa;GAAsC;EACpE;GAAE,MAAM;GAAS,aAAa;GAAqD;EACnF;GAAE,MAAM;GAAY,aAAa;GAAuC;EACxE;GAAE,MAAM;GAAQ,aAAa;GAAgB;EAC7C;GAAE,MAAM;GAAU,aAAa;GAAyB;EACxD;GAAE,MAAM;GAAU,aAAa;GAAqD;EACpF;GAAE,MAAM;GAAS,aAAa;GAA+B;EAC7D;GAAE,MAAM;GAAO,aAAa;GAAuB;EACnD;GAAE,MAAM;GAAS,aAAa;GAAyB;EACvD;GAAE,MAAM;GAAQ,aAAa;GAAiB;EAC9C;GAAE,MAAM;GAAU,aAAa;GAAyC;EACxE;GAAE,MAAM;GAAiB,aAAa;GAAoC;EAC1E;GAAE,MAAM;GAAW,aAAa;GAAuC;EACvE;GAAE,MAAM;GAAS,aAAa;GAAyC;EACvE;GAAE,MAAM;GAAa,aAAa;GAAqD;EACvF;GAAE,MAAM;GAAW,aAAa;GAAuB;EACvD;GAAE,MAAM;GAAU,aAAa;GAAqB;EACpD;GAAE,MAAM;GAAU,aAAa;GAAgC;EAC/D;GAAE,MAAM;GAAW,aAAa;GAAuB;EACvD;GAAE,MAAM;GAAO,aAAa;GAA2C;EACvE;GAAE,MAAM;GAAU,aAAa;GAAuC;EACtE;GAAE,MAAM;GAAY,aAAa;GAA6B;EAC9D;GAAE,MAAM;GAAsB,aAAa;GAAmC;EAC9E;GAAE,MAAM;GAAS,aAAa;GAAwB;EACtD;GAAE,MAAM;GAAW,aAAa;GAA+C;EAChF;;AAGH,SAAgB,kBAAkB,SAA0B;CAC1D,MAAM,WAAW,iBAAiB,QAAQ;CAC1C,MAAM,QAAQ,CAAC,sBAAsB;AACrC,MAAK,MAAM,KAAK,SACd,OAAM,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,cAAc;AAE/C,OAAM,KAAK,IAAI,wEAAwE;AACvF,OAAM,KAAK,8BAA8B;AACzC,OAAM,KAAK,mCAAmC;AAC9C,OAAM,KAAK,4DAA4D;AACvE,OAAM,KAAK,0BAA0B;AACrC,OAAM,KAAK,kDAAkD;AAC7D,OAAM,KAAK,kDAAkD;AAC7D,OAAM,KAAK,gCAAgC;AAC3C,OAAM,KAAK,2CAA2C;AACtD,OAAM,KAAK,mCAAmC;AAC9C,OAAM,KAAK,qCAAqC;AAChD,OAAM,KAAK,4EAA4E;AACvF,OAAM,KAAK,yDAAyD;AACpE,OAAM,KAAK,6DAA6D;AACxE,OAAM,KAAK,kEAAkE;AAC7E,OAAM,KAAK,8DAA8D;AACzE,OAAM,KAAK,0EAA0E;AACrF,OAAM,KAAK,iEAAiE;AAC5E,OAAM,KAAK,mCAAmC;AAC9C,OAAM,KAAK,qDAAqD;AAChE,OAAM,KAAK,IAAI,qEAAqE;AACpF,QAAO,MAAM,KAAK,KAAK;;AAwBzB,SAAgB,wBAAwB,MAAmD;CACzF,MAAM,EACJ,OACA,SACA,KACA,WACA,aACA,aACA,aACA,aACA,cACA,aACA,YACA,yBAAyB,EAAE,KACzB;AAEJ,SAAQ,UAAkB;EAExB,MAAM,CAAC,aAAa,GAAG,aADP,MAAM,QAAQ,OAAO,GAAG,CAAC,MACE,CAAC,MAAM,MAAM;EACxD,MAAM,qBAAqB,eAAe,IAAI,aAAa;EAC3D,MAAM,cAAc,UAAU,KAAK,IAAI;EAEvC,MAAM,eAAe,uBAAuB,MAAM,MAAM,EAAE,SAAS,kBAAkB;AACrF,MAAI,cAAc;AACX,WAAQ,QAAQ,aAAa,QAAQ,YAAY,CAAC,CAAC,WAAW;AACjE,QAAI,eAAe;KACnB;AACF;;AAGF,UAAQ,mBAAR;GACE,KAAK;AACH,YAAQ,UAAU,kBAAkB,YAAY,CAAC;AACjD,QAAI,eAAe;AACnB;GACF,KAAK;GACL,KAAK;AACH,YAAQ,UAAU,qBAAqB,YAAY,CAAC;AACpD,QAAI,eAAe;AACnB;GACF,KAAK;GACL,KAAK;AACH,iBAAa;AACb;GACF,KAAK;GACL,KAAK;GACL,KAAK;AACE,iBAAa,CAAC,WAAW;AAC5B,aAAQ,UAAU,WAAW;AAC7B,SAAI,eAAe;MACnB;AACF;GACF,KAAK;AACH,UAAM,gBAAgB,CAAC,MAAM;AAC7B,YAAQ,iBAAiB,MAAM,cAAc;AAC7C,YAAQ,UAAU,UAAU,MAAM,gBAAgB,aAAa,cAAc;AAC7E,QAAI,eAAe;AACnB;GACF,KAAK;AACH,UAAM,eAAe,CAAC,MAAM;AAC5B,YAAQ,UAAU,qBAAqB,MAAM,eAAe,OAAO,QAAQ;AAC3E,kBAAc;AACd,QAAI,eAAe;AACnB;GACF,KAAK;GACL,KAAK;AACH,gBAAY,mBAAmB;AAC/B;GACF,KAAK;GACL,KAAK;AACH,gBAAY,kBAAkB;AAC9B;GACF,KAAK;AACH,gBAAY,cAAc;AAC1B;GACF,KAAK;GACL,KAAK;AACH,gBAAY,mBAAmB;AAC/B,YAAQ,UAAU,6BAA6B,uBAAuB,GAAG;AACzE,QAAI,eAAe;AACnB;GACF,KAAK;AACE,SAAK,iBAAiB;AAC3B;GACF,QACE;;AAGJ,UAAQ,mBAAR;GACE,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;AACE,iBAAa,CAAC,WAAW;AAC5B,eAAU,OAAO;AACjB,aAAQ,UAAU;AAClB,WAAM,qBAAqB,SAAS;AACpC,SAAI,eAAe;AACnB,iBAAY,MAAM;MAClB;AACF;GAEF,QACE;;AAGJ,cAAY,MAAM"}
1
+ {"version":3,"file":"tui-commands.js","names":[],"sources":["../../../src/tui/tui-commands.ts"],"sourcesContent":["import type { KeybindingsManager, TUI } from '@earendil-works/pi-tui';\n\nimport type { ChatLog } from './components/chat-log.js';\nimport { formatXopcTuiHotkeys } from './format-tui-hotkeys.js';\nimport { getTuiKeybindingsPath } from './tui-keybindings-file.js';\nimport type { StreamAssembler } from './stream-assembler.js';\nimport type { TuiState } from './tui-types.js';\n\nimport { rewriteUnknownSlashAsWorkflow } from './tui-workflow-slash.js';\n\ninterface SlashCommandDef {\n name: string;\n description: string;\n}\n\nexport type TuiExtensionSlashCommandEntry = SlashCommandDef & {\n handler: (args: string) => void | Promise<void>;\n};\n\nexport function getSlashCommands(_isLocal: boolean): SlashCommandDef[] {\n return [\n { name: 'help', description: 'Show available commands' },\n { name: 'abort', description: 'Abort active run (or press Escape)' },\n { name: 'tools', description: 'Toggle tool output expanded/collapsed (or Ctrl+O)' },\n { name: 'thinking', description: 'Toggle thinking display (or Ctrl+T)' },\n { name: 'exit', description: 'Exit the TUI' },\n { name: 'models', description: 'List available models' },\n { name: 'switch', description: 'Switch model — copy `provider/model` from /models' },\n { name: 'usage', description: 'Show token usage statistics' },\n { name: 'new', description: 'Start a new session' },\n { name: 'clear', description: 'Clear current session' },\n { name: 'list', description: 'List sessions' },\n { name: 'resume', description: 'Open session picker (or Ctrl+Shift+P)' },\n { name: 'scoped-models', description: 'Choose models for Ctrl+P cycling' },\n { name: 'compact', description: 'Compact session history (local API)' },\n { name: 'think', description: 'Set thinking level (e.g. /think high)' },\n { name: 'reasoning', description: 'Set reasoning visibility (e.g. /reasoning stream)' },\n { name: 'verbose', description: 'Toggle verbose mode' },\n { name: 'status', description: 'Show agent status' },\n { name: 'config', description: 'Show or update configuration' },\n { name: 'context', description: 'Show context budget' },\n { name: 'btw', description: 'Side question without saving to session' },\n { name: 'export', description: 'Export session (markdown/html/json)' },\n { name: 'settings', description: 'Open TUI settings overlay' },\n { name: 'reload-keybindings', description: 'Reload ~/.xopc/keybindings.json' },\n { name: 'start', description: 'Show welcome message' },\n { name: 'hotkeys', description: 'Show resolved keyboard shortcuts (pi-style)' },\n { name: 'workflows', description: 'List saved workflows (built-in + ~/.xopc/workflows/)' },\n { name: 'workflow', description: 'Workflow subcommands: list, view <name>, save <name>' },\n ];\n}\n\nexport function formatTuiHelpText(isLocal: boolean): string {\n const commands = getSlashCommands(isLocal);\n const lines = ['Available commands:'];\n for (const c of commands) {\n lines.push(` /${c.name} — ${c.description}`);\n }\n lines.push('', 'Keyboard shortcuts (defaults align with pi coding-agent where noted):');\n lines.push(' Escape — Abort active run');\n lines.push(' Shift+Tab — Cycle /think level');\n lines.push(' Ctrl+P / Shift+Ctrl+P — Next / previous model (/switch)');\n lines.push(' Ctrl+L — Model picker');\n lines.push(' Ctrl+Shift+P — Session picker (rename/delete)');\n lines.push(' /scoped-models — Limit Ctrl+P model cycle set');\n lines.push(' Ctrl+O — Toggle tool output');\n lines.push(' Ctrl+T — Toggle thinking block display');\n lines.push(' Ctrl+G — Edit draft in $EDITOR');\n lines.push(' Ctrl+Z — Suspend to shell (Unix)');\n lines.push(' Alt+Enter — Queue follow-up while busy (sends when this reply finishes)');\n lines.push(' Alt+Up — Restore queued follow-up messages to editor');\n lines.push(' Enter (while busy) — Steer: inject at next tool boundary');\n lines.push(' Ctrl+V (mac/Linux) / Alt+V (Win) — Paste image from clipboard');\n lines.push(' /settings — Theme, thinking display, terminal progress, …');\n lines.push(' ~/.xopc/keybindings.json — Custom shortcuts (use /reload-keybindings)');\n lines.push(' Ctrl+C — Clear input; repeat within ~0.5s to exit when empty');\n lines.push(' Ctrl+D — Exit when input empty');\n lines.push(' !cmd — Local shell (gated; runs on this machine)');\n lines.push('', 'Use /hotkeys for the resolved binding list from the active keymap.');\n return lines.join('\\n');\n}\n\nexport type CommandHandlerDeps = {\n state: TuiState;\n chatLog: ChatLog;\n tui: TUI;\n assembler: StreamAssembler;\n isLocalMode: boolean;\n abortActive: () => Promise<void>;\n sendMessage: (text: string) => void;\n requestExit: () => void;\n updateFooter: () => void;\n keybindings: KeybindingsManager;\n uiOverlays?: {\n openSessionPicker: () => void;\n openScopedModels: () => void;\n openSettings: () => void;\n reloadKeybindings: () => void;\n };\n runCompaction?: () => void | Promise<void>;\n extensionSlashCommands?: TuiExtensionSlashCommandEntry[];\n};\n\nexport function createTuiCommandHandler(deps: CommandHandlerDeps): (input: string) => void {\n const {\n state,\n chatLog,\n tui,\n assembler,\n isLocalMode,\n abortActive,\n sendMessage,\n requestExit,\n updateFooter,\n keybindings,\n uiOverlays,\n extensionSlashCommands = [],\n } = deps;\n\n return (input: string) => {\n const trimmed = input.replace(/^\\//, '').trim();\n const [commandName, ...restParts] = trimmed.split(/\\s+/);\n const normalizedCommand = (commandName ?? '').toLowerCase();\n const commandArgs = restParts.join(' ');\n\n const extensionCmd = extensionSlashCommands.find((c) => c.name === normalizedCommand);\n if (extensionCmd) {\n void Promise.resolve(extensionCmd.handler(commandArgs)).then(() => {\n tui.requestRender();\n });\n return;\n }\n\n switch (normalizedCommand) {\n case 'help':\n chatLog.addSystem(formatTuiHelpText(isLocalMode));\n tui.requestRender();\n return;\n case 'hotkeys':\n case 'keys':\n chatLog.addSystem(formatXopcTuiHotkeys(keybindings));\n tui.requestRender();\n return;\n case 'exit':\n case 'quit':\n requestExit();\n return;\n case 'abort':\n case 'stop':\n case 'cancel':\n void abortActive().then(() => {\n chatLog.addSystem('Aborted.');\n tui.requestRender();\n });\n return;\n case 'tools':\n state.toolsExpanded = !state.toolsExpanded;\n chatLog.setToolsExpanded(state.toolsExpanded);\n chatLog.addSystem(`Tools: ${state.toolsExpanded ? 'expanded' : 'collapsed'}`);\n tui.requestRender();\n return;\n case 'thinking':\n state.showThinking = !state.showThinking;\n chatLog.addSystem(`Thinking display: ${state.showThinking ? 'on' : 'off'}`);\n updateFooter();\n tui.requestRender();\n return;\n case 'resume':\n case 'sessions':\n uiOverlays?.openSessionPicker();\n return;\n case 'scoped-models':\n case 'scopedmodels':\n uiOverlays?.openScopedModels();\n return;\n case 'settings':\n uiOverlays?.openSettings();\n return;\n case 'reload-keybindings':\n case 'reload-keybind':\n uiOverlays?.reloadKeybindings();\n chatLog.addSystem(`Keybindings reloaded from ${getTuiKeybindingsPath()}`);\n tui.requestRender();\n return;\n case 'compact':\n void deps.runCompaction?.();\n return;\n default:\n break;\n }\n\n switch (normalizedCommand) {\n case 'new':\n case 'reset':\n case 'restart':\n case 'clear': {\n void abortActive().then(() => {\n assembler.clear();\n chatLog.clearAll();\n state.messageFollowUpQueue.length = 0;\n tui.requestRender();\n sendMessage(input);\n });\n return;\n }\n default:\n break;\n }\n\n // Unknown slash that names a known workflow → rewrite into a natural prompt\n // so the assistant deterministically calls workflow({name}) instead of\n // depending on the model to puzzle out \"/audit_repo\".\n if (input.trimStart().startsWith('/')) {\n const rewritten = rewriteUnknownSlashAsWorkflow(normalizedCommand, commandArgs);\n if (rewritten) {\n chatLog.addSystem(`▶ Running workflow: ${normalizedCommand}`);\n tui.requestRender();\n sendMessage(rewritten);\n return;\n }\n }\n\n sendMessage(input);\n };\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
3
  import { XOPC_TUI_KEYBINDINGS } from "./xopc-tui-keybindings.js";
4
- import { existsSync, readFileSync } from "node:fs";
5
4
  import { join } from "node:path";
5
+ import { existsSync, readFileSync } from "node:fs";
6
6
  import { KeybindingsManager } from "@earendil-works/pi-tui";
7
7
  //#region src/tui/tui-keybindings-file.ts
8
8
  init_paths();
@@ -1,6 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
1
  import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  //#region src/tui/tui-scoped-models.ts
5
5
  const STORE_PATH = join(homedir(), ".xopc", "tui-scoped-models.json");
6
6
  function normalizeWorkspaceKey(cwd) {
@@ -1,7 +1,7 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
3
  import { join } from "node:path";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  //#region src/tui/tui-settings.ts
6
6
  init_paths();
7
7
  const DEFAULT_TUI_SETTINGS = {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Helper for {@link createTuiCommandHandler}: when the user types an unknown
3
+ * slash command in the TUI, check whether the name matches a saved workflow
4
+ * and, if so, rewrite the input into a natural-language prompt that
5
+ * deterministically triggers the `workflow` tool.
6
+ *
7
+ * Why a rewrite and not a direct execution?
8
+ * Slash commands in xopc are handled by the chat-commands framework whose
9
+ * handlers return text shown back to the user — there is no hook to "inject
10
+ * the result as the next user message and start an agent turn". So the
11
+ * shortest reliable path is to turn `/audit_repo` into the same plain-text
12
+ * request the model already knows how to handle.
13
+ *
14
+ * Why catalog the workflow list at the TUI layer?
15
+ * The catalog reads `~/.xopc/workflows/` synchronously (single `readdir`),
16
+ * so the slash dispatch stays sync and the user sees no latency. The lookup
17
+ * only runs for inputs that started with `/` and didn't match any built-in
18
+ * or extension command, so the cost is negligible.
19
+ *
20
+ * Built-in TUI command names that overlap with a workflow file name keep their
21
+ * existing TUI behaviour — the slash dispatcher matches the built-in switch
22
+ * cases before this helper is consulted.
23
+ */
24
+ /** Reset the cache (for tests, or after a `/workflow save`). */
25
+ export declare function resetWorkflowSlashCache(): void;
26
+ /**
27
+ * Returns a rewritten user message when `name` matches a known workflow,
28
+ * otherwise `null`. Args after the slash are passed along as a hint to the
29
+ * model — most workflows expect free-form context, not strict JSON, and the
30
+ * model can shape the call.
31
+ */
32
+ export declare function rewriteUnknownSlashAsWorkflow(name: string, args: string, resolver?: () => Set<string>): string | null;
@@ -0,0 +1,63 @@
1
+ import { createWorkflowCatalog } from "../agent/workflow/catalog.js";
2
+ //#region src/tui/tui-workflow-slash.ts
3
+ /**
4
+ * Helper for {@link createTuiCommandHandler}: when the user types an unknown
5
+ * slash command in the TUI, check whether the name matches a saved workflow
6
+ * and, if so, rewrite the input into a natural-language prompt that
7
+ * deterministically triggers the `workflow` tool.
8
+ *
9
+ * Why a rewrite and not a direct execution?
10
+ * Slash commands in xopc are handled by the chat-commands framework whose
11
+ * handlers return text shown back to the user — there is no hook to "inject
12
+ * the result as the next user message and start an agent turn". So the
13
+ * shortest reliable path is to turn `/audit_repo` into the same plain-text
14
+ * request the model already knows how to handle.
15
+ *
16
+ * Why catalog the workflow list at the TUI layer?
17
+ * The catalog reads `~/.xopc/workflows/` synchronously (single `readdir`),
18
+ * so the slash dispatch stays sync and the user sees no latency. The lookup
19
+ * only runs for inputs that started with `/` and didn't match any built-in
20
+ * or extension command, so the cost is negligible.
21
+ *
22
+ * Built-in TUI command names that overlap with a workflow file name keep their
23
+ * existing TUI behaviour — the slash dispatcher matches the built-in switch
24
+ * cases before this helper is consulted.
25
+ */
26
+ let cachedNames = null;
27
+ let cachedAt = 0;
28
+ const CACHE_TTL_MS = 5e3;
29
+ /** Reset the cache (for tests, or after a `/workflow save`). */
30
+ function resetWorkflowSlashCache() {
31
+ cachedNames = null;
32
+ cachedAt = 0;
33
+ }
34
+ function knownWorkflowNames() {
35
+ const now = Date.now();
36
+ if (cachedNames && now - cachedAt < CACHE_TTL_MS) return cachedNames;
37
+ try {
38
+ const catalog = createWorkflowCatalog();
39
+ cachedNames = new Set(catalog.list().map((e) => e.name));
40
+ } catch {
41
+ cachedNames = /* @__PURE__ */ new Set();
42
+ }
43
+ cachedAt = now;
44
+ return cachedNames;
45
+ }
46
+ /**
47
+ * Returns a rewritten user message when `name` matches a known workflow,
48
+ * otherwise `null`. Args after the slash are passed along as a hint to the
49
+ * model — most workflows expect free-form context, not strict JSON, and the
50
+ * model can shape the call.
51
+ */
52
+ function rewriteUnknownSlashAsWorkflow(name, args, resolver = knownWorkflowNames) {
53
+ const normalized = name.trim();
54
+ if (!normalized) return null;
55
+ if (!resolver().has(normalized)) return null;
56
+ const tail = args.trim();
57
+ if (!tail) return `Run the ${normalized} workflow (call workflow tool with name="${normalized}").`;
58
+ return `Run the ${normalized} workflow (call workflow tool with name="${normalized}").\nUse this argument: ${tail}`;
59
+ }
60
+ //#endregion
61
+ export { resetWorkflowSlashCache, rewriteUnknownSlashAsWorkflow };
62
+
63
+ //# sourceMappingURL=tui-workflow-slash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-workflow-slash.js","names":[],"sources":["../../../src/tui/tui-workflow-slash.ts"],"sourcesContent":["/**\n * Helper for {@link createTuiCommandHandler}: when the user types an unknown\n * slash command in the TUI, check whether the name matches a saved workflow\n * and, if so, rewrite the input into a natural-language prompt that\n * deterministically triggers the `workflow` tool.\n *\n * Why a rewrite and not a direct execution?\n * Slash commands in xopc are handled by the chat-commands framework whose\n * handlers return text shown back to the user — there is no hook to \"inject\n * the result as the next user message and start an agent turn\". So the\n * shortest reliable path is to turn `/audit_repo` into the same plain-text\n * request the model already knows how to handle.\n *\n * Why catalog the workflow list at the TUI layer?\n * The catalog reads `~/.xopc/workflows/` synchronously (single `readdir`),\n * so the slash dispatch stays sync and the user sees no latency. The lookup\n * only runs for inputs that started with `/` and didn't match any built-in\n * or extension command, so the cost is negligible.\n *\n * Built-in TUI command names that overlap with a workflow file name keep their\n * existing TUI behaviour — the slash dispatcher matches the built-in switch\n * cases before this helper is consulted.\n */\n\nimport { createWorkflowCatalog } from '../agent/workflow/catalog.js';\n\nlet cachedNames: Set<string> | null = null;\nlet cachedAt = 0;\nconst CACHE_TTL_MS = 5_000;\n\n/** Reset the cache (for tests, or after a `/workflow save`). */\nexport function resetWorkflowSlashCache(): void {\n cachedNames = null;\n cachedAt = 0;\n}\n\nfunction knownWorkflowNames(): Set<string> {\n const now = Date.now();\n if (cachedNames && now - cachedAt < CACHE_TTL_MS) return cachedNames;\n try {\n const catalog = createWorkflowCatalog();\n cachedNames = new Set(catalog.list().map((e) => e.name));\n } catch {\n cachedNames = new Set();\n }\n cachedAt = now;\n return cachedNames;\n}\n\n/**\n * Returns a rewritten user message when `name` matches a known workflow,\n * otherwise `null`. Args after the slash are passed along as a hint to the\n * model — most workflows expect free-form context, not strict JSON, and the\n * model can shape the call.\n */\nexport function rewriteUnknownSlashAsWorkflow(\n name: string,\n args: string,\n resolver: () => Set<string> = knownWorkflowNames,\n): string | null {\n const normalized = name.trim();\n if (!normalized) return null;\n if (!resolver().has(normalized)) return null;\n\n const tail = args.trim();\n if (!tail) {\n return `Run the ${normalized} workflow (call workflow tool with name=\"${normalized}\").`;\n }\n return (\n `Run the ${normalized} workflow (call workflow tool with name=\"${normalized}\").\\n` +\n `Use this argument: ${tail}`\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,IAAI,cAAkC;AACtC,IAAI,WAAW;AACf,MAAM,eAAe;;AAGrB,SAAgB,0BAAgC;AAC9C,eAAc;AACd,YAAW;;AAGb,SAAS,qBAAkC;CACzC,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,eAAe,MAAM,WAAW,aAAc,QAAO;AACzD,KAAI;EACF,MAAM,UAAU,uBAAuB;AACvC,gBAAc,IAAI,IAAI,QAAQ,MAAM,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC;SAClD;AACN,gCAAc,IAAI,KAAK;;AAEzB,YAAW;AACX,QAAO;;;;;;;;AAST,SAAgB,8BACd,MACA,MACA,WAA8B,oBACf;CACf,MAAM,aAAa,KAAK,MAAM;AAC9B,KAAI,CAAC,WAAY,QAAO;AACxB,KAAI,CAAC,UAAU,CAAC,IAAI,WAAW,CAAE,QAAO;CAExC,MAAM,OAAO,KAAK,MAAM;AACxB,KAAI,CAAC,KACH,QAAO,WAAW,WAAW,2CAA2C,WAAW;AAErF,QACE,WAAW,WAAW,2CAA2C,WAAW,0BACtD"}
@@ -26,9 +26,9 @@ import { TuiBottomBar } from "./components/tui-bottom-bar.js";
26
26
  import { TuiHeader } from "./components/tui-header.js";
27
27
  import { loadExtensionsForTuiLocalMode } from "./extension-host/load-extensions.js";
28
28
  import { createTuiExtensionRuntime } from "./extension-host/runtime.js";
29
- import { mkdtempSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
30
- import { join } from "node:path";
31
29
  import { tmpdir } from "node:os";
30
+ import { join } from "node:path";
31
+ import { mkdtempSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
32
32
  import { spawnSync } from "node:child_process";
33
33
  import { Container, Loader, ProcessTerminal, TUI, setKeybindings } from "@earendil-works/pi-tui";
34
34
  //#region src/tui/tui.ts
@@ -1,7 +1,7 @@
1
1
  import { buildDefaultCorsOrigins } from "../gateway/host.js";
2
2
  import { isNetworkAccessibleBindHost, resolveGatewayBindMode, resolveGatewayEffectiveHost } from "../config/gateway-bind.js";
3
- import { assertGatewayAuthConfigured, resolveGatewayAuth } from "../gateway/auth.js";
4
3
  import { enumerateLanGatewayCandidates } from "./tunnel-qr.js";
4
+ import { assertGatewayAuthConfigured, resolveGatewayAuth } from "../gateway/auth.js";
5
5
  import { assertGatewayRuntimeConfig } from "../gateway/runtime-config.js";
6
6
  //#region src/tunnel/enable-lan-pairing.ts
7
7
  function mergeCorsOriginsForLan(config, port, bindHost) {
@@ -2,10 +2,10 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { init_paths, resolveBinDir } from "../config/paths.js";
4
4
  import { extractFrpcFromReleaseArchive, frpcReleaseArchiveExtension, nodePlatformForFrpTarget } from "./frpc-extract.js";
5
- import { randomBytes } from "node:crypto";
6
- import { chmodSync, createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
7
- import { join } from "node:path";
8
5
  import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { chmodSync, createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
8
+ import { randomBytes } from "node:crypto";
9
9
  import { Readable } from "node:stream";
10
10
  import { pipeline } from "node:stream/promises";
11
11
  //#region src/tunnel/frpc-binary.ts
@@ -1,7 +1,7 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { init_paths } from "../config/paths.js";
3
- import { mkdirSync, writeFileSync } from "node:fs";
4
3
  import { join } from "node:path";
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
5
  //#region src/tunnel/frpc-config.ts
6
6
  init_paths();
7
7
  function resolveFrpcConfigPath(tunnelId) {
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
3
  import { dirname, join } from "node:path";
4
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { spawn } from "node:child_process";
6
6
  import AdmZip from "adm-zip";
7
7
  import { gunzipSync } from "node:zlib";
@@ -1,10 +1,10 @@
1
+ import { loadTunnelState, resolveTunnelStatePath, saveTunnelState } from "./tunnel-state.js";
2
+ import { buildMobileConnectQrPayload, resolveLanGatewayUrl } from "./tunnel-qr.js";
1
3
  import { CURRENT_TUNNEL_CONSENT_VERSION, TUNNEL_CONSENT_REQUIRED_CODE, TUNNEL_RISK_SUMMARY_LINES, TunnelConsentError, assertTunnelMayStart, getTunnelConsentState, hasValidTunnelConsent } from "./consent.js";
2
4
  import { getTunnelRegistrationSecretMeta, isMaskedTunnelSecretPatchValue, isProductionTunnelBroker, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret } from "./env.js";
3
5
  import { TunnelBrokerClient, resolveBrokerApiBase } from "./broker-client.js";
4
6
  import { FRPC_VERSION, clearFrpcPathForProcess, ensureFrpcBinary, publishFrpcPathForProcess } from "./frpc-binary.js";
5
- import { buildMobileConnectQrPayload, resolveLanGatewayUrl } from "./tunnel-qr.js";
6
7
  import { consumePairingSecret, createPairingSecret, resetPairingSessionsForTests } from "./pairing.js";
7
- import { loadTunnelState, resolveTunnelStatePath, saveTunnelState } from "./tunnel-state.js";
8
8
  import { logTunnelAudit } from "./tunnel-audit.js";
9
9
  import { TunnelService, getTunnelService, hashGatewayToken } from "./tunnel-service.js";
10
10
  import { resolveFrpSubdomainHost } from "./frp-subdomain-host.js";
@@ -1,6 +1,6 @@
1
1
  import type { Config } from '../config/schema.js';
2
2
  import type { GatewayBindMode } from '../config/schema.js';
3
- export type MobilePairCandidateKind = 'lan' | 'tunnel';
3
+ export type MobilePairCandidateKind = 'lan' | 'tunnel' | 'reverse-proxy';
4
4
  export type MobilePairBlockReason = 'GATEWAY_LOOPBACK_ONLY' | 'NO_REACHABLE_URL';
5
5
  export type MobilePairCandidate = {
6
6
  kind: MobilePairCandidateKind;
@@ -27,4 +27,10 @@ export declare function buildMobilePairContext(params: {
27
27
  config: Config;
28
28
  tunnelPublicUrl?: string | null;
29
29
  tunnelConnected?: boolean;
30
+ /**
31
+ * Optional override for `gateway.publicUrl`. Falls back to the configured
32
+ * value when omitted; useful for transient candidates (e.g. UI auto-detect
33
+ * from `window.location.origin` before the user persists the value).
34
+ */
35
+ reverseProxyPublicUrl?: string | null;
30
36
  }): MobilePairContext;
@@ -1,7 +1,9 @@
1
+ import { init_public_url, normalizePublicUrlOrNull } from "../config/public-url.js";
1
2
  import { isNetworkAccessibleBindHost, resolveGatewayBindMode, resolveGatewayEffectiveHost } from "../config/gateway-bind.js";
2
3
  import { enumerateLanGatewayCandidates } from "./tunnel-qr.js";
3
4
  import { buildMobileConnectUrlOrder } from "./pair-url.js";
4
5
  //#region src/tunnel/pair-context.ts
6
+ init_public_url();
5
7
  function buildMobilePairContext(params) {
6
8
  const port = params.config.gateway?.port ?? 18790;
7
9
  const bindMode = resolveGatewayBindMode(params.config);
@@ -15,20 +17,31 @@ function buildMobilePairContext(params) {
15
17
  reachable: lanListenReady,
16
18
  ...lanListenReady ? {} : { note: "requires_lan_bind" }
17
19
  }));
18
- const publicUrl = params.tunnelPublicUrl?.trim() || null;
19
- const tunnelConnected = params.tunnelConnected === true && Boolean(publicUrl);
20
- if (tunnelConnected && publicUrl) candidates.unshift({
20
+ const tunnelUrl = params.tunnelPublicUrl?.trim() || null;
21
+ const tunnelConnected = params.tunnelConnected === true && Boolean(tunnelUrl);
22
+ if (tunnelConnected && tunnelUrl) candidates.unshift({
21
23
  kind: "tunnel",
22
- url: publicUrl,
24
+ url: tunnelUrl,
25
+ reachable: true
26
+ });
27
+ const configuredReverseProxy = normalizePublicUrlOrNull(params.config.gateway?.publicUrl);
28
+ const reverseProxyUrl = params.reverseProxyPublicUrl !== void 0 ? normalizePublicUrlOrNull(params.reverseProxyPublicUrl) : configuredReverseProxy;
29
+ if (reverseProxyUrl) candidates.unshift({
30
+ kind: "reverse-proxy",
31
+ url: reverseProxyUrl,
23
32
  reachable: true
24
33
  });
25
34
  let recommended = {
26
35
  mode: null,
27
36
  url: null
28
37
  };
29
- if (tunnelConnected && publicUrl) recommended = {
38
+ if (reverseProxyUrl) recommended = {
39
+ mode: "reverse-proxy",
40
+ url: reverseProxyUrl
41
+ };
42
+ else if (tunnelConnected && tunnelUrl) recommended = {
30
43
  mode: "tunnel",
31
- url: publicUrl
44
+ url: tunnelUrl
32
45
  };
33
46
  else if (lanListenReady && lanEnumerated[0]) recommended = {
34
47
  mode: "lan",
@@ -38,13 +51,16 @@ function buildMobilePairContext(params) {
38
51
  mode: "lan",
39
52
  url: lanEnumerated[0].url
40
53
  };
41
- const pairingReady = Boolean(tunnelConnected || lanListenReady && lanEnumerated.length > 0);
54
+ const pairingReady = Boolean(reverseProxyUrl || tunnelConnected || lanListenReady && lanEnumerated.length > 0);
42
55
  let blockReason;
43
56
  if (!pairingReady) blockReason = !lanListenReady && !tunnelConnected ? "GATEWAY_LOOPBACK_ONLY" : "NO_REACHABLE_URL";
44
57
  const lanUrlForConnect = lanListenReady && lanEnumerated[0] ? lanEnumerated[0].url : null;
58
+ const hasUpstream = Boolean(reverseProxyUrl) || tunnelConnected;
45
59
  const connectUrls = buildMobileConnectUrlOrder({
46
- baseUrl: tunnelConnected && publicUrl ? publicUrl : lanListenReady && recommended.url ? recommended.url : null,
47
- lanUrl: tunnelConnected ? lanUrlForConnect : null
60
+ reverseProxyUrl,
61
+ baseUrl: reverseProxyUrl ? reverseProxyUrl : tunnelConnected && tunnelUrl ? tunnelUrl : lanListenReady && recommended.url ? recommended.url : null,
62
+ lanUrl: hasUpstream ? lanUrlForConnect : null,
63
+ tunnelUrl: tunnelConnected ? tunnelUrl : null
48
64
  });
49
65
  return {
50
66
  port,
@@ -1 +1 @@
1
- {"version":3,"file":"pair-context.js","names":[],"sources":["../../../src/tunnel/pair-context.ts"],"sourcesContent":["import type { Config } from '../config/schema.js';\nimport type { GatewayBindMode } from '../config/schema.js';\nimport {\n isNetworkAccessibleBindHost,\n resolveGatewayBindMode,\n resolveGatewayEffectiveHost,\n} from '../config/gateway-bind.js';\nimport { enumerateLanGatewayCandidates } from './tunnel-qr.js';\nimport { buildMobileConnectUrlOrder } from './pair-url.js';\n\nexport type MobilePairCandidateKind = 'lan' | 'tunnel';\n\nexport type MobilePairBlockReason = 'GATEWAY_LOOPBACK_ONLY' | 'NO_REACHABLE_URL';\n\nexport type MobilePairCandidate = {\n kind: MobilePairCandidateKind;\n url: string;\n label?: string;\n reachable: boolean;\n note?: string;\n};\n\nexport type MobilePairContext = {\n port: number;\n bindMode: GatewayBindMode;\n listenHost: string;\n pairingReady: boolean;\n blockReason?: MobilePairBlockReason;\n candidates: MobilePairCandidate[];\n recommended: {\n mode: MobilePairCandidateKind | null;\n url: string | null;\n };\n /** Ordered URLs for mobile clients (LAN first when available, then tunnel). */\n connectUrls: string[];\n};\n\nexport function buildMobilePairContext(params: {\n config: Config;\n tunnelPublicUrl?: string | null;\n tunnelConnected?: boolean;\n}): MobilePairContext {\n const port = params.config.gateway?.port ?? 18790;\n const bindMode = resolveGatewayBindMode(params.config);\n const listenHost = resolveGatewayEffectiveHost(params.config);\n const lanListenReady = isNetworkAccessibleBindHost(listenHost);\n\n const lanEnumerated = enumerateLanGatewayCandidates(port);\n const candidates: MobilePairCandidate[] = lanEnumerated.map((entry) => ({\n kind: 'lan',\n url: entry.url,\n label: entry.interfaceName,\n reachable: lanListenReady,\n ...(lanListenReady ? {} : { note: 'requires_lan_bind' }),\n }));\n\n const publicUrl = params.tunnelPublicUrl?.trim() || null;\n const tunnelConnected = params.tunnelConnected === true && Boolean(publicUrl);\n if (tunnelConnected && publicUrl) {\n candidates.unshift({\n kind: 'tunnel',\n url: publicUrl,\n reachable: true,\n });\n }\n\n let recommended: MobilePairContext['recommended'] = { mode: null, url: null };\n if (tunnelConnected && publicUrl) {\n recommended = { mode: 'tunnel', url: publicUrl };\n } else if (lanListenReady && lanEnumerated[0]) {\n recommended = { mode: 'lan', url: lanEnumerated[0].url };\n } else if (lanEnumerated[0]) {\n recommended = { mode: 'lan', url: lanEnumerated[0].url };\n }\n\n const pairingReady = Boolean(\n tunnelConnected || (lanListenReady && lanEnumerated.length > 0),\n );\n\n let blockReason: MobilePairBlockReason | undefined;\n if (!pairingReady) {\n blockReason =\n !lanListenReady && !tunnelConnected ? 'GATEWAY_LOOPBACK_ONLY' : 'NO_REACHABLE_URL';\n }\n\n const lanUrlForConnect =\n lanListenReady && lanEnumerated[0] ? lanEnumerated[0].url : null;\n const baseUrlForConnect = tunnelConnected && publicUrl\n ? publicUrl\n : lanListenReady && recommended.url\n ? recommended.url\n : null;\n const connectUrls = buildMobileConnectUrlOrder({\n baseUrl: baseUrlForConnect,\n lanUrl: tunnelConnected ? lanUrlForConnect : null,\n });\n\n return {\n port,\n bindMode,\n listenHost,\n pairingReady,\n ...(blockReason ? { blockReason } : {}),\n candidates,\n recommended,\n connectUrls,\n };\n}\n"],"mappings":";;;;AAqCA,SAAgB,uBAAuB,QAIjB;CACpB,MAAM,OAAO,OAAO,OAAO,SAAS,QAAQ;CAC5C,MAAM,WAAW,uBAAuB,OAAO,OAAO;CACtD,MAAM,aAAa,4BAA4B,OAAO,OAAO;CAC7D,MAAM,iBAAiB,4BAA4B,WAAW;CAE9D,MAAM,gBAAgB,8BAA8B,KAAK;CACzD,MAAM,aAAoC,cAAc,KAAK,WAAW;EACtE,MAAM;EACN,KAAK,MAAM;EACX,OAAO,MAAM;EACb,WAAW;EACX,GAAI,iBAAiB,EAAE,GAAG,EAAE,MAAM,qBAAqB;EACxD,EAAE;CAEH,MAAM,YAAY,OAAO,iBAAiB,MAAM,IAAI;CACpD,MAAM,kBAAkB,OAAO,oBAAoB,QAAQ,QAAQ,UAAU;AAC7E,KAAI,mBAAmB,UACrB,YAAW,QAAQ;EACjB,MAAM;EACN,KAAK;EACL,WAAW;EACZ,CAAC;CAGJ,IAAI,cAAgD;EAAE,MAAM;EAAM,KAAK;EAAM;AAC7E,KAAI,mBAAmB,UACrB,eAAc;EAAE,MAAM;EAAU,KAAK;EAAW;UACvC,kBAAkB,cAAc,GACzC,eAAc;EAAE,MAAM;EAAO,KAAK,cAAc,GAAG;EAAK;UAC/C,cAAc,GACvB,eAAc;EAAE,MAAM;EAAO,KAAK,cAAc,GAAG;EAAK;CAG1D,MAAM,eAAe,QACnB,mBAAoB,kBAAkB,cAAc,SAAS,EAC9D;CAED,IAAI;AACJ,KAAI,CAAC,aACH,eACE,CAAC,kBAAkB,CAAC,kBAAkB,0BAA0B;CAGpE,MAAM,mBACJ,kBAAkB,cAAc,KAAK,cAAc,GAAG,MAAM;CAM9D,MAAM,cAAc,2BAA2B;EAC7C,SANwB,mBAAmB,YACzC,YACA,kBAAkB,YAAY,MAC5B,YAAY,MACZ;EAGJ,QAAQ,kBAAkB,mBAAmB;EAC9C,CAAC;AAEF,QAAO;EACL;EACA;EACA;EACA;EACA,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;EACtC;EACA;EACA;EACD"}
1
+ {"version":3,"file":"pair-context.js","names":[],"sources":["../../../src/tunnel/pair-context.ts"],"sourcesContent":["import type { Config } from '../config/schema.js';\nimport type { GatewayBindMode } from '../config/schema.js';\nimport {\n isNetworkAccessibleBindHost,\n resolveGatewayBindMode,\n resolveGatewayEffectiveHost,\n} from '../config/gateway-bind.js';\nimport { normalizePublicUrlOrNull } from '../config/public-url.js';\nimport { enumerateLanGatewayCandidates } from './tunnel-qr.js';\nimport { buildMobileConnectUrlOrder } from './pair-url.js';\n\nexport type MobilePairCandidateKind = 'lan' | 'tunnel' | 'reverse-proxy';\n\nexport type MobilePairBlockReason = 'GATEWAY_LOOPBACK_ONLY' | 'NO_REACHABLE_URL';\n\nexport type MobilePairCandidate = {\n kind: MobilePairCandidateKind;\n url: string;\n label?: string;\n reachable: boolean;\n note?: string;\n};\n\nexport type MobilePairContext = {\n port: number;\n bindMode: GatewayBindMode;\n listenHost: string;\n pairingReady: boolean;\n blockReason?: MobilePairBlockReason;\n candidates: MobilePairCandidate[];\n recommended: {\n mode: MobilePairCandidateKind | null;\n url: string | null;\n };\n /** Ordered URLs for mobile clients (LAN first when available, then tunnel). */\n connectUrls: string[];\n};\n\nexport function buildMobilePairContext(params: {\n config: Config;\n tunnelPublicUrl?: string | null;\n tunnelConnected?: boolean;\n /**\n * Optional override for `gateway.publicUrl`. Falls back to the configured\n * value when omitted; useful for transient candidates (e.g. UI auto-detect\n * from `window.location.origin` before the user persists the value).\n */\n reverseProxyPublicUrl?: string | null;\n}): MobilePairContext {\n const port = params.config.gateway?.port ?? 18790;\n const bindMode = resolveGatewayBindMode(params.config);\n const listenHost = resolveGatewayEffectiveHost(params.config);\n const lanListenReady = isNetworkAccessibleBindHost(listenHost);\n\n const lanEnumerated = enumerateLanGatewayCandidates(port);\n const candidates: MobilePairCandidate[] = lanEnumerated.map((entry) => ({\n kind: 'lan',\n url: entry.url,\n label: entry.interfaceName,\n reachable: lanListenReady,\n ...(lanListenReady ? {} : { note: 'requires_lan_bind' }),\n }));\n\n const tunnelUrl = params.tunnelPublicUrl?.trim() || null;\n const tunnelConnected = params.tunnelConnected === true && Boolean(tunnelUrl);\n if (tunnelConnected && tunnelUrl) {\n candidates.unshift({\n kind: 'tunnel',\n url: tunnelUrl,\n reachable: true,\n });\n }\n\n const configuredReverseProxy = normalizePublicUrlOrNull(params.config.gateway?.publicUrl);\n const reverseProxyUrl =\n params.reverseProxyPublicUrl !== undefined\n ? normalizePublicUrlOrNull(params.reverseProxyPublicUrl)\n : configuredReverseProxy;\n if (reverseProxyUrl) {\n candidates.unshift({\n kind: 'reverse-proxy',\n url: reverseProxyUrl,\n reachable: true,\n });\n }\n\n let recommended: MobilePairContext['recommended'] = { mode: null, url: null };\n if (reverseProxyUrl) {\n recommended = { mode: 'reverse-proxy', url: reverseProxyUrl };\n } else if (tunnelConnected && tunnelUrl) {\n recommended = { mode: 'tunnel', url: tunnelUrl };\n } else if (lanListenReady && lanEnumerated[0]) {\n recommended = { mode: 'lan', url: lanEnumerated[0].url };\n } else if (lanEnumerated[0]) {\n recommended = { mode: 'lan', url: lanEnumerated[0].url };\n }\n\n const pairingReady = Boolean(\n reverseProxyUrl || tunnelConnected || (lanListenReady && lanEnumerated.length > 0),\n );\n\n let blockReason: MobilePairBlockReason | undefined;\n if (!pairingReady) {\n blockReason =\n !lanListenReady && !tunnelConnected ? 'GATEWAY_LOOPBACK_ONLY' : 'NO_REACHABLE_URL';\n }\n\n const lanUrlForConnect =\n lanListenReady && lanEnumerated[0] ? lanEnumerated[0].url : null;\n // When reverse-proxy or tunnel is active, LAN is the secondary candidate.\n // When only LAN is available, lanUrl IS the baseUrl (don't duplicate it).\n const hasUpstream = Boolean(reverseProxyUrl) || tunnelConnected;\n const baseUrlForConnect = reverseProxyUrl\n ? reverseProxyUrl\n : tunnelConnected && tunnelUrl\n ? tunnelUrl\n : lanListenReady && recommended.url\n ? recommended.url\n : null;\n const connectUrls = buildMobileConnectUrlOrder({\n reverseProxyUrl,\n baseUrl: baseUrlForConnect,\n lanUrl: hasUpstream ? lanUrlForConnect : null,\n tunnelUrl: tunnelConnected ? tunnelUrl : null,\n });\n\n return {\n port,\n bindMode,\n listenHost,\n pairingReady,\n ...(blockReason ? { blockReason } : {}),\n candidates,\n recommended,\n connectUrls,\n };\n}\n"],"mappings":";;;;;iBAOmE;AA+BnE,SAAgB,uBAAuB,QAUjB;CACpB,MAAM,OAAO,OAAO,OAAO,SAAS,QAAQ;CAC5C,MAAM,WAAW,uBAAuB,OAAO,OAAO;CACtD,MAAM,aAAa,4BAA4B,OAAO,OAAO;CAC7D,MAAM,iBAAiB,4BAA4B,WAAW;CAE9D,MAAM,gBAAgB,8BAA8B,KAAK;CACzD,MAAM,aAAoC,cAAc,KAAK,WAAW;EACtE,MAAM;EACN,KAAK,MAAM;EACX,OAAO,MAAM;EACb,WAAW;EACX,GAAI,iBAAiB,EAAE,GAAG,EAAE,MAAM,qBAAqB;EACxD,EAAE;CAEH,MAAM,YAAY,OAAO,iBAAiB,MAAM,IAAI;CACpD,MAAM,kBAAkB,OAAO,oBAAoB,QAAQ,QAAQ,UAAU;AAC7E,KAAI,mBAAmB,UACrB,YAAW,QAAQ;EACjB,MAAM;EACN,KAAK;EACL,WAAW;EACZ,CAAC;CAGJ,MAAM,yBAAyB,yBAAyB,OAAO,OAAO,SAAS,UAAU;CACzF,MAAM,kBACJ,OAAO,0BAA0B,KAAA,IAC7B,yBAAyB,OAAO,sBAAsB,GACtD;AACN,KAAI,gBACF,YAAW,QAAQ;EACjB,MAAM;EACN,KAAK;EACL,WAAW;EACZ,CAAC;CAGJ,IAAI,cAAgD;EAAE,MAAM;EAAM,KAAK;EAAM;AAC7E,KAAI,gBACF,eAAc;EAAE,MAAM;EAAiB,KAAK;EAAiB;UACpD,mBAAmB,UAC5B,eAAc;EAAE,MAAM;EAAU,KAAK;EAAW;UACvC,kBAAkB,cAAc,GACzC,eAAc;EAAE,MAAM;EAAO,KAAK,cAAc,GAAG;EAAK;UAC/C,cAAc,GACvB,eAAc;EAAE,MAAM;EAAO,KAAK,cAAc,GAAG;EAAK;CAG1D,MAAM,eAAe,QACnB,mBAAmB,mBAAoB,kBAAkB,cAAc,SAAS,EACjF;CAED,IAAI;AACJ,KAAI,CAAC,aACH,eACE,CAAC,kBAAkB,CAAC,kBAAkB,0BAA0B;CAGpE,MAAM,mBACJ,kBAAkB,cAAc,KAAK,cAAc,GAAG,MAAM;CAG9D,MAAM,cAAc,QAAQ,gBAAgB,IAAI;CAQhD,MAAM,cAAc,2BAA2B;EAC7C;EACA,SATwB,kBACtB,kBACA,mBAAmB,YACjB,YACA,kBAAkB,YAAY,MAC5B,YAAY,MACZ;EAIN,QAAQ,cAAc,mBAAmB;EACzC,WAAW,kBAAkB,YAAY;EAC1C,CAAC;AAEF,QAAO;EACL;EACA;EACA;EACA;EACA,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;EACtC;EACA;EACA;EACD"}