@xopcai/xopc 0.0.85 → 0.0.87

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 (407) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
  3. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
  4. package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
  5. package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
  6. package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
  7. package/dist/extensions/telegram/src/routing-integration.js +1 -0
  8. package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
  9. package/dist/extensions/telegram/xopc.extension.json +1 -1
  10. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
  11. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
  12. package/dist/extensions/weixin/src/api/api.js +2 -2
  13. package/dist/extensions/weixin/src/api/api.js.map +1 -1
  14. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  15. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  16. package/dist/extensions/weixin/src/delivery-to.js +2 -2
  17. package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
  18. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  19. package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
  20. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  21. package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
  22. package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
  23. package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
  24. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  25. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  26. package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +222 -0
  27. package/dist/gateway/static/root/assets/{apps-page-D7v7649T.js → apps-page-Dg8R-Szf.js} +1 -1
  28. package/dist/gateway/static/root/assets/{channels-settings-nCaMb0a7.js → channels-settings-yohw9YSu.js} +1 -1
  29. package/dist/gateway/static/root/assets/{channels-status-swr-C1gZBcJV.js → channels-status-swr-BSHqqCF1.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-api-CoYK0hlm.js → cron-api-0h_QT8U3.js} +1 -1
  31. package/dist/gateway/static/root/assets/{cron-page-DeGo-Vjc.js → cron-page-BkfKFfFk.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dist-DaK4dsss.js → dist-Cmjp2APP.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-BZngZWbO.js → extension-debug-page-CFa9z_1N.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-D6JSyV27.js → extension-page-BI8eaTPq.js} +1 -1
  35. package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +1 -0
  36. package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-DRqwef_Q.js} +1 -1
  37. package/dist/gateway/static/root/assets/{field-primitives-Zzl22MvN.js → field-primitives-BiNHBo2Y.js} +1 -1
  38. package/dist/gateway/static/root/assets/{heartbeat-config-api-BtIcpG0O.js → heartbeat-config-api-ZRb8qhuz.js} +1 -1
  39. package/dist/gateway/static/root/assets/{index-D4vM3-P7.js → index-Cu7bKuUi.js} +96 -94
  40. package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +1 -0
  41. package/dist/gateway/static/root/assets/{logs-page-_d4UJ-qQ.js → logs-page-BFZ8GgCv.js} +1 -1
  42. package/dist/gateway/static/root/assets/{sessions-page-5N4aF2Wk.js → sessions-page-CD7AfB-2.js} +1 -1
  43. package/dist/gateway/static/root/assets/settings-form-section-DiqqVs6m.js +1 -0
  44. package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +3 -0
  45. package/dist/gateway/static/root/assets/{share-preview-page-D4EG_vM1.js → share-preview-page-n1Gprylk.js} +1 -1
  46. package/dist/gateway/static/root/assets/{skills-page-sPAXhh8w.js → skills-page-CcN_gj--.js} +1 -1
  47. package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-CZOh1nT3.js} +1 -1
  48. package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +3 -0
  49. package/dist/gateway/static/root/assets/{utils-CYO9eTCM.js → utils-CkWBfxs4.js} +1 -1
  50. package/dist/gateway/static/root/assets/{voice-api-key-field-Ds51havm.js → voice-api-key-field-O6awz9hi.js} +1 -1
  51. package/dist/gateway/static/root/index.html +5 -5
  52. package/dist/package.js +1 -1
  53. package/dist/src/agent/agent-scope.d.ts +4 -0
  54. package/dist/src/agent/agent-scope.js +53 -10
  55. package/dist/src/agent/agent-scope.js.map +1 -1
  56. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
  57. package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
  58. package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
  59. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  60. package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
  61. package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
  62. package/dist/src/agent/fallback/candidates.js +2 -2
  63. package/dist/src/agent/fallback/candidates.js.map +1 -1
  64. package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
  65. package/dist/src/agent/goals/persistent-goal-service.js +0 -1
  66. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  67. package/dist/src/agent/image/generation/normalization.js +2 -12
  68. package/dist/src/agent/image/generation/normalization.js.map +1 -1
  69. package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
  70. package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
  71. package/dist/src/agent/image/generation/runtime.d.ts +2 -2
  72. package/dist/src/agent/image/generation/runtime.js.map +1 -1
  73. package/dist/src/agent/image/generation/types.d.ts +0 -18
  74. package/dist/src/agent/image/image-helpers.js +6 -1
  75. package/dist/src/agent/image/image-helpers.js.map +1 -1
  76. package/dist/src/agent/image/index.d.ts +1 -1
  77. package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
  78. package/dist/src/agent/inbound/inbound-loop.js +41 -10
  79. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  80. package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
  81. package/dist/src/agent/inbound/turn-dispatcher.js +6 -4
  82. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  83. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
  84. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  85. package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
  86. package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
  87. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
  88. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  89. package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
  90. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  91. package/dist/src/agent/mcp/mcp-transport.js +2 -1
  92. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  93. package/dist/src/agent/media-generation/runtime-shared.js +2 -9
  94. package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
  95. package/dist/src/agent/messaging/command-handler.d.ts +6 -0
  96. package/dist/src/agent/messaging/command-handler.js +5 -0
  97. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  98. package/dist/src/agent/prompt/safety.d.ts +0 -7
  99. package/dist/src/agent/prompt/safety.js +1 -20
  100. package/dist/src/agent/prompt/safety.js.map +1 -1
  101. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  102. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  103. package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
  104. package/dist/src/agent/service/direct-turn-helpers.js +6 -1
  105. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  106. package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
  107. package/dist/src/agent/service/process-direct-one-shot.js +15 -2
  108. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  109. package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
  110. package/dist/src/agent/service/process-direct-streaming.js +34 -4
  111. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  112. package/dist/src/agent/service/webchat-tts.js +1 -1
  113. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  114. package/dist/src/agent/service.d.ts +8 -0
  115. package/dist/src/agent/service.js +21 -1
  116. package/dist/src/agent/service.js.map +1 -1
  117. package/dist/src/agent/tools/create-share-tool.js +27 -20
  118. package/dist/src/agent/tools/create-share-tool.js.map +1 -1
  119. package/dist/src/agent/tools/factory.js +1 -1
  120. package/dist/src/agent/tools/index.d.ts +0 -1
  121. package/dist/src/agent/tools/index.js +4 -5
  122. package/dist/src/agent/tools/shell.js +0 -13
  123. package/dist/src/agent/tools/shell.js.map +1 -1
  124. package/dist/src/agent/tools/workflow-tool.js +10 -4
  125. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  126. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +5 -1
  127. package/dist/src/agent/workflow/builtins/audit-repo.js +52 -11
  128. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  129. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
  130. package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
  131. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
  132. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  133. package/dist/src/agent/workflow/builtins/index.js +11 -1
  134. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +6 -1
  136. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +66 -30
  137. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  138. package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
  139. package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
  140. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
  141. package/dist/src/agent/workflow/builtins/research.d.ts +5 -1
  142. package/dist/src/agent/workflow/builtins/research.js +37 -6
  143. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  144. package/dist/src/agent/workflow/catalog.d.ts +5 -0
  145. package/dist/src/agent/workflow/catalog.js +6 -2
  146. package/dist/src/agent/workflow/catalog.js.map +1 -1
  147. package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
  148. package/dist/src/agent/workflow/index.d.ts +1 -1
  149. package/dist/src/agent/workflow/lint.d.ts +38 -0
  150. package/dist/src/agent/workflow/lint.js +74 -0
  151. package/dist/src/agent/workflow/lint.js.map +1 -0
  152. package/dist/src/agent/workflow/parser.js +13 -1
  153. package/dist/src/agent/workflow/parser.js.map +1 -1
  154. package/dist/src/agent/workflow/runtime.d.ts +3 -0
  155. package/dist/src/agent/workflow/runtime.js +76 -3
  156. package/dist/src/agent/workflow/runtime.js.map +1 -1
  157. package/dist/src/agent/workflow/types.d.ts +11 -1
  158. package/dist/src/browser/index.js +4 -4
  159. package/dist/src/browser/manager.d.ts +1 -3
  160. package/dist/src/browser/manager.js +0 -6
  161. package/dist/src/browser/manager.js.map +1 -1
  162. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
  163. package/dist/src/browser/providers/browser-ext-install.js +38 -85
  164. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  165. package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
  166. package/dist/src/browser/providers/cloakbrowser.js +2 -55
  167. package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
  168. package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
  169. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  170. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  171. package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
  172. package/dist/src/channels/pairing/pairing-store.js +6 -6
  173. package/dist/src/channels/pairing/pairing-store.js.map +1 -1
  174. package/dist/src/chat-commands/builtins/session.js +1 -1
  175. package/dist/src/chat-commands/builtins/session.js.map +1 -1
  176. package/dist/src/chat-commands/builtins/tts.js +2 -2
  177. package/dist/src/chat-commands/builtins/tts.js.map +1 -1
  178. package/dist/src/chat-commands/builtins/workflow.js +7 -2
  179. package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
  180. package/dist/src/chat-commands/context.d.ts +3 -0
  181. package/dist/src/chat-commands/context.js +21 -3
  182. package/dist/src/chat-commands/context.js.map +1 -1
  183. package/dist/src/chat-commands/session-key.d.ts +4 -37
  184. package/dist/src/chat-commands/session-key.js +49 -85
  185. package/dist/src/chat-commands/session-key.js.map +1 -1
  186. package/dist/src/chat-commands/types.d.ts +2 -0
  187. package/dist/src/cli/commands/agent/interactive.js +2 -2
  188. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  189. package/dist/src/cli/commands/agent/sessions.js +2 -2
  190. package/dist/src/cli/commands/agent/sessions.js.map +1 -1
  191. package/dist/src/cli/commands/agent.js +4 -5
  192. package/dist/src/cli/commands/agent.js.map +1 -1
  193. package/dist/src/cli/commands/channels.js +1 -5
  194. package/dist/src/cli/commands/channels.js.map +1 -1
  195. package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
  196. package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
  197. package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
  198. package/dist/src/cli/commands/gateway/logs.js +50 -17
  199. package/dist/src/cli/commands/gateway/logs.js.map +1 -1
  200. package/dist/src/cli/commands/image.js +22 -21
  201. package/dist/src/cli/commands/image.js.map +1 -1
  202. package/dist/src/cli/commands/session/utils.js +2 -2
  203. package/dist/src/cli/commands/session/utils.js.map +1 -1
  204. package/dist/src/cli/commands/update.js +26 -46
  205. package/dist/src/cli/commands/update.js.map +1 -1
  206. package/dist/src/cli/utils/session.d.ts +0 -5
  207. package/dist/src/cli/utils/session.js +1 -6
  208. package/dist/src/cli/utils/session.js.map +1 -1
  209. package/dist/src/commands/agents.config.js +1 -1
  210. package/dist/src/commands/agents.config.js.map +1 -1
  211. package/dist/src/config/agent-profile.js +5 -27
  212. package/dist/src/config/agent-profile.js.map +1 -1
  213. package/dist/src/config/index.js +2 -2
  214. package/dist/src/config/model-input.js +2 -5
  215. package/dist/src/config/model-input.js.map +1 -1
  216. package/dist/src/config/schema.d.ts +201 -217
  217. package/dist/src/config/schema.js +54 -39
  218. package/dist/src/config/schema.js.map +1 -1
  219. package/dist/src/config/workspace-path-helpers.d.ts +1 -2
  220. package/dist/src/config/workspace-path-helpers.js.map +1 -1
  221. package/dist/src/daemon/install-plan.js +25 -1
  222. package/dist/src/daemon/install-plan.js.map +1 -1
  223. package/dist/src/daemon/launchd.d.ts +8 -0
  224. package/dist/src/daemon/launchd.js +5 -12
  225. package/dist/src/daemon/launchd.js.map +1 -1
  226. package/dist/src/daemon/schtasks.d.ts +25 -0
  227. package/dist/src/daemon/schtasks.js +166 -46
  228. package/dist/src/daemon/schtasks.js.map +1 -1
  229. package/dist/src/daemon/service.js +5 -4
  230. package/dist/src/daemon/service.js.map +1 -1
  231. package/dist/src/daemon/systemd.d.ts +6 -0
  232. package/dist/src/daemon/systemd.js +18 -3
  233. package/dist/src/daemon/systemd.js.map +1 -1
  234. package/dist/src/extensions/activation-context.js +0 -1
  235. package/dist/src/extensions/activation-context.js.map +1 -1
  236. package/dist/src/extensions/normalize-manifest.js +0 -1
  237. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  238. package/dist/src/extensions/types/manifest.d.ts +0 -2
  239. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  240. package/dist/src/gateway/agent-builtin-tools.js +1 -0
  241. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  242. package/dist/src/gateway/agents-admin.js +10 -2
  243. package/dist/src/gateway/agents-admin.js.map +1 -1
  244. package/dist/src/gateway/heartbeat/service.js +1 -1
  245. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  246. package/dist/src/gateway/hono/app.js +1 -1
  247. package/dist/src/gateway/hono/lib/agent-model.d.ts +18 -10
  248. package/dist/src/gateway/hono/lib/agent-model.js +24 -35
  249. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  250. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  251. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  252. package/dist/src/gateway/hono/lib/safe-voice-config.js +14 -53
  253. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  254. package/dist/src/gateway/hono/routes/config-patch/agents.js +17 -5
  255. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  256. package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
  257. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  258. package/dist/src/gateway/hono/routes/goals.js +1 -1
  259. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  260. package/dist/src/gateway/hono/routes/sessions.js +28 -7
  261. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  262. package/dist/src/gateway/hono/routes/shares.js +14 -12
  263. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  264. package/dist/src/gateway/hono/routes/tunnel.js +1 -1
  265. package/dist/src/gateway/hono/routes/update.js +4 -2
  266. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  267. package/dist/src/gateway/hono/sse.js +16 -33
  268. package/dist/src/gateway/hono/sse.js.map +1 -1
  269. package/dist/src/gateway/lock.js +10 -10
  270. package/dist/src/gateway/lock.js.map +1 -1
  271. package/dist/src/gateway/ports.js +6 -6
  272. package/dist/src/gateway/ports.js.map +1 -1
  273. package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
  274. package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
  275. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
  276. package/dist/src/gateway/service/run-gateway-agent.js +27 -11
  277. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  278. package/dist/src/gateway/service/sessions-api.d.ts +3 -0
  279. package/dist/src/gateway/service/sessions-api.js +8 -0
  280. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  281. package/dist/src/gateway/service.d.ts +0 -2
  282. package/dist/src/gateway/service.js +2 -7
  283. package/dist/src/gateway/service.js.map +1 -1
  284. package/dist/src/gateway/session-reset-service.d.ts +20 -0
  285. package/dist/src/gateway/session-reset-service.js +54 -0
  286. package/dist/src/gateway/session-reset-service.js.map +1 -0
  287. package/dist/src/gateway/startup-readiness.d.ts +1 -1
  288. package/dist/src/gateway/startup-readiness.js +1 -0
  289. package/dist/src/gateway/startup-readiness.js.map +1 -1
  290. package/dist/src/infra/gateway-processes.js +2 -2
  291. package/dist/src/infra/gateway-processes.js.map +1 -1
  292. package/dist/src/infra/run-command.d.ts +16 -0
  293. package/dist/src/infra/run-command.js +67 -0
  294. package/dist/src/infra/run-command.js.map +1 -0
  295. package/dist/src/infra/update-global.d.ts +45 -0
  296. package/dist/src/infra/update-global.js +224 -0
  297. package/dist/src/infra/update-global.js.map +1 -0
  298. package/dist/src/mcp/channel-bridge.js +1 -1
  299. package/dist/src/mcp/channel-shared.js +2 -1
  300. package/dist/src/mcp/channel-shared.js.map +1 -1
  301. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  302. package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
  303. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
  304. package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
  305. package/dist/src/providers/auth-runtime/types.d.ts +6 -12
  306. package/dist/src/routing/agent-session-key.d.ts +58 -0
  307. package/dist/src/routing/agent-session-key.js +164 -0
  308. package/dist/src/routing/agent-session-key.js.map +1 -0
  309. package/dist/src/routing/index.d.ts +1 -1
  310. package/dist/src/routing/index.js +4 -2
  311. package/dist/src/routing/index.js.map +1 -1
  312. package/dist/src/routing/resolve-route.d.ts +15 -0
  313. package/dist/src/routing/resolve-route.js +41 -20
  314. package/dist/src/routing/resolve-route.js.map +1 -1
  315. package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
  316. package/dist/src/routing/resolve-tui-session-key.js +54 -0
  317. package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
  318. package/dist/src/routing/session-key-utils.d.ts +24 -0
  319. package/dist/src/routing/session-key-utils.js +92 -0
  320. package/dist/src/routing/session-key-utils.js.map +1 -0
  321. package/dist/src/routing/session-key.d.ts +19 -49
  322. package/dist/src/routing/session-key.js +143 -116
  323. package/dist/src/routing/session-key.js.map +1 -1
  324. package/dist/src/session/index.d.ts +6 -0
  325. package/dist/src/session/index.js +7 -1
  326. package/dist/src/session/init-session-turn.d.ts +30 -0
  327. package/dist/src/session/init-session-turn.js +102 -0
  328. package/dist/src/session/init-session-turn.js.map +1 -0
  329. package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
  330. package/dist/src/session/lifecycle-timestamps.js +16 -0
  331. package/dist/src/session/lifecycle-timestamps.js.map +1 -0
  332. package/dist/src/session/manager.d.ts +7 -1
  333. package/dist/src/session/manager.js +8 -1
  334. package/dist/src/session/manager.js.map +1 -1
  335. package/dist/src/session/parity/transcript-paths.js +2 -2
  336. package/dist/src/session/parity/transcript-paths.js.map +1 -1
  337. package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
  338. package/dist/src/session/reset-policy.d.ts +32 -0
  339. package/dist/src/session/reset-policy.js +65 -0
  340. package/dist/src/session/reset-policy.js.map +1 -0
  341. package/dist/src/session/reset-triggers.d.ts +20 -0
  342. package/dist/src/session/reset-triggers.js +63 -0
  343. package/dist/src/session/reset-triggers.js.map +1 -0
  344. package/dist/src/session/reset-type.d.ts +12 -0
  345. package/dist/src/session/reset-type.js +25 -0
  346. package/dist/src/session/reset-type.js.map +1 -0
  347. package/dist/src/session/resolve-session.d.ts +30 -0
  348. package/dist/src/session/resolve-session.js +93 -0
  349. package/dist/src/session/resolve-session.js.map +1 -0
  350. package/dist/src/session/session-title.js +3 -2
  351. package/dist/src/session/session-title.js.map +1 -1
  352. package/dist/src/session/store.d.ts +11 -4
  353. package/dist/src/session/store.js +57 -6
  354. package/dist/src/session/store.js.map +1 -1
  355. package/dist/src/session/transcript-events.js +2 -1
  356. package/dist/src/session/transcript-events.js.map +1 -1
  357. package/dist/src/share/share-url.d.ts +33 -0
  358. package/dist/src/share/share-url.js +56 -14
  359. package/dist/src/share/share-url.js.map +1 -1
  360. package/dist/src/tui/backends/embedded-backend.js +4 -9
  361. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  362. package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
  363. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  364. package/dist/src/tui/components/chat-log.js +3 -3
  365. package/dist/src/tui/components/chat-log.js.map +1 -1
  366. package/dist/src/tui/theme.d.ts +0 -2
  367. package/dist/src/tui/theme.js +1 -3
  368. package/dist/src/tui/theme.js.map +1 -1
  369. package/dist/src/tui/tui-commands.d.ts +3 -0
  370. package/dist/src/tui/tui-commands.js +45 -10
  371. package/dist/src/tui/tui-commands.js.map +1 -1
  372. package/dist/src/tui/tui-keybindings-file.js +1 -21
  373. package/dist/src/tui/tui-keybindings-file.js.map +1 -1
  374. package/dist/src/tui/tui-session-actions.d.ts +28 -0
  375. package/dist/src/tui/tui-session-actions.js +88 -0
  376. package/dist/src/tui/tui-session-actions.js.map +1 -0
  377. package/dist/src/tui/tui.js +52 -47
  378. package/dist/src/tui/tui.js.map +1 -1
  379. package/dist/src/utils/string-coerce.d.ts +2 -0
  380. package/dist/src/utils/string-coerce.js +10 -1
  381. package/dist/src/utils/string-coerce.js.map +1 -1
  382. package/dist/src/voice/stt/config-slice.d.ts +2 -5
  383. package/dist/src/voice/stt/config-slice.js +5 -26
  384. package/dist/src/voice/stt/config-slice.js.map +1 -1
  385. package/dist/src/voice/stt/types.d.ts +1 -18
  386. package/dist/src/voice/stt/types.js +4 -2
  387. package/dist/src/voice/stt/types.js.map +1 -1
  388. package/dist/src/voice/tts/config-slice.d.ts +3 -7
  389. package/dist/src/voice/tts/config-slice.js +7 -38
  390. package/dist/src/voice/tts/config-slice.js.map +1 -1
  391. package/dist/src/voice/tts/merge-config.js +2 -48
  392. package/dist/src/voice/tts/merge-config.js.map +1 -1
  393. package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
  394. package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
  395. package/dist/src/voice/tts/types.d.ts +1 -29
  396. package/dist/src/voice/tts/types.js +19 -17
  397. package/dist/src/voice/tts/types.js.map +1 -1
  398. package/package.json +1 -4
  399. package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +0 -222
  400. package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +0 -1
  401. package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
  402. package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +0 -1
  403. package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +0 -3
  404. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
  405. package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
  406. package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
  407. package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-path-helpers.js","names":[],"sources":["../../../src/config/workspace-path-helpers.ts"],"sourcesContent":["/**\n * Workspace-path domain helpers that need both `Config` (from `./schema.js`)\n * and agent-scope helpers (from `../agent/agent-scope.js`).\n *\n * Lives in this file rather than `./schema.js` because schema.ts is the leaf\n * type module — letting it import agent-scope creates a circular cycle\n * (`schema → agent-scope → schema`). Callers import via the `./index.js`\n * barrel; backward-compat re-export keeps existing call sites working.\n */\n\nimport { getDefaultWorkspacePath } from '../agent/agent-scope.js';\nimport type { Config } from './schema.js';\n\n/**\n * Default agent's resolved Markdown workspace root\n * (`resolveAgentWorkspaceDir` for the default agent id).\n */\nexport function getWorkspacePath(config: Config): string {\n return getDefaultWorkspacePath(config);\n}\n"],"mappings":";;kBAUkE;;;;;AAOlE,SAAgB,iBAAiB,QAAwB;AACvD,QAAO,wBAAwB,OAAO"}
1
+ {"version":3,"file":"workspace-path-helpers.js","names":[],"sources":["../../../src/config/workspace-path-helpers.ts"],"sourcesContent":["/**\n * Workspace-path domain helpers that need both `Config` (from `./schema.js`)\n * and agent-scope helpers (from `../agent/agent-scope.js`).\n *\n * Lives in this file rather than `./schema.js` because schema.ts is the leaf\n * type module — letting it import agent-scope creates a circular cycle\n * (`schema → agent-scope → schema`). Callers import via the `./index.js` barrel.\n */\n\nimport { getDefaultWorkspacePath } from '../agent/agent-scope.js';\nimport type { Config } from './schema.js';\n\n/**\n * Default agent's resolved Markdown workspace root\n * (`resolveAgentWorkspaceDir` for the default agent id).\n */\nexport function getWorkspacePath(config: Config): string {\n return getDefaultWorkspacePath(config);\n}\n"],"mappings":";;kBASkE;;;;;AAOlE,SAAgB,iBAAiB,QAAwB;AACvD,QAAO,wBAAwB,OAAO"}
@@ -34,8 +34,32 @@ function isSEABinary() {
34
34
  function resolveEntryPoint() {
35
35
  if (isSEABinary()) return process.execPath;
36
36
  const thisDir = path.dirname(new URL(import.meta.url).pathname);
37
+ const sourceEntryPoint = path.join(thisDir, "..", "cli", "bin.ts");
38
+ if (existsSync(sourceEntryPoint)) return sourceEntryPoint;
37
39
  return path.join(thisDir, "..", "cli", "bin.js");
38
40
  }
41
+ function resolveServiceNodeArgs() {
42
+ const serviceNodeArgs = [];
43
+ const skipValueFlags = new Set([
44
+ "-e",
45
+ "--eval",
46
+ "-p",
47
+ "--print",
48
+ "--test-name-pattern"
49
+ ]);
50
+ for (let index = 0; index < process.execArgv.length; index += 1) {
51
+ const arg = process.execArgv[index];
52
+ if (!arg) continue;
53
+ if (arg.startsWith("--inspect") || arg.startsWith("--debug")) continue;
54
+ if (skipValueFlags.has(arg)) {
55
+ index += 1;
56
+ continue;
57
+ }
58
+ if ([...skipValueFlags].some((flag) => arg.startsWith(`${flag}=`))) continue;
59
+ serviceNodeArgs.push(arg);
60
+ }
61
+ return serviceNodeArgs;
62
+ }
39
63
  function buildGatewayInstallPlan(params) {
40
64
  const configPath = resolveDefaultConfigPath();
41
65
  const workspace = resolveDefaultWorkspace();
@@ -49,7 +73,7 @@ function buildGatewayInstallPlan(params) {
49
73
  params.port.toString()
50
74
  ];
51
75
  else {
52
- const nodeArgs = process.execArgv.filter((arg) => !arg.startsWith("--inspect") && !arg.startsWith("--debug"));
76
+ const nodeArgs = resolveServiceNodeArgs();
53
77
  const entryPoint = resolveEntryPoint();
54
78
  programArguments = [
55
79
  process.execPath,
@@ -1 +1 @@
1
- {"version":3,"file":"install-plan.js","names":[],"sources":["../../../src/daemon/install-plan.ts"],"sourcesContent":["/**\n * Install Plan Builder - Build gateway installation configuration\n *\n * Aligned with OpenClaw: adds XOPC_SERVICE_VERSION, StandardOut/ErrorPath,\n * supports binary runtime detection, and --foreground arg passing.\n */\n\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { homedir } from 'node:os';\nimport { createLogger } from '../utils/logger.js';\nimport { SERVICE_VERSION_ENV_KEY, formatGatewayServiceDescription } from './constants.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport type { GatewayServiceInstallArgs, GatewayServiceEnv } from './types.js';\n\nconst log = createLogger('InstallPlan');\n\nexport interface InstallPlan {\n programArguments: string[];\n workingDirectory: string;\n environment: Record<string, string>;\n description: string;\n}\n\n// ─── Path Resolution ───\n\nfunction resolveDefaultConfigPath(): string {\n const envConfig = process.env.XOPC_CONFIG || process.env.XOPC_CONFIG_PATH;\n if (envConfig) return envConfig;\n return path.join(homedir(), '.xopc', 'xopc.json');\n}\n\nfunction resolveDefaultWorkspace(): string {\n const envWorkspace = process.env.XOPC_WORKSPACE;\n if (envWorkspace) return envWorkspace;\n return path.join(homedir(), '.xopc', 'workspace');\n}\n\nfunction resolveLogDir(): string {\n return path.join(homedir(), '.xopc', 'logs');\n}\n\n// ─── Binary Detection ───\n\nfunction isSEABinary(): boolean {\n // Node.js SEA (Single Executable Application) detection\n return !!(process as NodeJS.Process & { pkg?: unknown }).pkg ||\n process.execPath.endsWith('xopc') ||\n process.execPath.endsWith('xopc.exe');\n}\n\nfunction resolveEntryPoint(): string {\n if (isSEABinary()) {\n return process.execPath;\n }\n // Must match package.json \"bin\" (cli/bin.js). index.js only exports runCli and exits\n // immediately when executed directly — systemd/LaunchAgent need the real entry.\n const thisDir = path.dirname(new URL(import.meta.url).pathname);\n return path.join(thisDir, '..', 'cli', 'bin.js');\n}\n\n// ─── Plan Builder ───\n\nexport function buildGatewayInstallPlan(params: {\n port: number;\n bind?: import('../config/schema.js').GatewayBindMode;\n token?: string;\n env?: GatewayServiceEnv;\n runtime?: 'node' | 'binary';\n version?: string;\n}): InstallPlan {\n const configPath = resolveDefaultConfigPath();\n const workspace = resolveDefaultWorkspace();\n const version = params.version || PACKAGE_VERSION;\n\n // Determine program + arguments\n let programArguments: string[];\n\n if (params.runtime === 'binary' || isSEABinary()) {\n // SEA binary: direct execution\n const binaryPath = process.execPath;\n programArguments = [binaryPath, 'gateway', '--foreground', '--port', params.port.toString()];\n } else {\n // Node.js runtime\n const nodeArgs = process.execArgv.filter(\n (arg) => !arg.startsWith('--inspect') && !arg.startsWith('--debug'),\n );\n const entryPoint = resolveEntryPoint();\n programArguments = [\n process.execPath,\n ...nodeArgs,\n entryPoint,\n 'gateway',\n '--foreground',\n '--port', params.port.toString(),\n ];\n }\n\n if (params.bind && params.bind !== 'loopback') {\n programArguments.push('--bind', params.bind);\n }\n\n // Build environment\n const environment: Record<string, string> = {\n XOPC_CONFIG: configPath,\n XOPC_WORKSPACE: workspace,\n XOPC_LOG_LEVEL: process.env.XOPC_LOG_LEVEL || 'info',\n XOPC_LOG_FILE: 'true',\n XOPC_LOG_CONSOLE: 'false',\n XOPC_LOG_DIR: resolveLogDir(),\n [SERVICE_VERSION_ENV_KEY]: version,\n XOPC_SERVICE_MARKER: '1',\n };\n\n if (params.token) {\n environment.XOPC_GATEWAY_TOKEN = params.token;\n }\n\n // Copy relevant API key env vars\n const relevantEnvVars = [\n 'OPENAI_API_KEY',\n 'ANTHROPIC_API_KEY',\n 'GOOGLE_API_KEY',\n 'BRAVE_API_KEY',\n 'DASHSCOPE_API_KEY',\n 'XOPC_LOG_RETENTION_DAYS',\n ];\n\n for (const key of relevantEnvVars) {\n if (process.env[key]) {\n environment[key] = process.env[key]!;\n }\n }\n\n // Add custom env from params (non-XOPC keys only; XOPC keys already handled)\n if (params.env) {\n for (const [key, value] of Object.entries(params.env)) {\n if (value !== undefined && !key.startsWith('XOPC_')) {\n environment[key] = value;\n }\n }\n }\n\n // Service description\n const profile = params.env?.XOPC_PROFILE?.trim() || undefined;\n const description = formatGatewayServiceDescription({ profile, version });\n\n log.info({\n programArguments: programArguments.slice(0, 3),\n envKeys: Object.keys(environment),\n version,\n }, 'Built gateway install plan');\n\n return {\n programArguments,\n workingDirectory: path.dirname(configPath),\n environment,\n description,\n };\n}\n\n// ─── Install Args Builder ───\n\nexport function buildGatewayInstallArgs(params: {\n port: number;\n bind?: import('../config/schema.js').GatewayBindMode;\n token?: string;\n env?: GatewayServiceEnv;\n runtime?: 'node' | 'binary';\n version?: string;\n}): GatewayServiceInstallArgs {\n const plan = buildGatewayInstallPlan(params);\n\n return {\n env: params.env || process.env,\n programArguments: plan.programArguments,\n workingDirectory: plan.workingDirectory,\n environment: plan.environment,\n description: plan.description,\n };\n}\n\n// ─── Validation ───\n\n/** Check if the program in an install plan still exists on disk */\nexport function validateInstallPlanProgram(programArguments: string[]): boolean {\n if (programArguments.length === 0) return false;\n const program = programArguments[0];\n return existsSync(program);\n}\n"],"mappings":";;;;;;;;;;;;;;aAUkD;sBAEM;AAGxD,MAAM,MAAM,aAAa,cAAc;AAWvC,SAAS,2BAAmC;CAC1C,MAAM,YAAY,QAAQ,IAAI,eAAe,QAAQ,IAAI;AACzD,KAAI,UAAW,QAAO;AACtB,QAAO,KAAK,KAAK,SAAS,EAAE,SAAS,YAAY;;AAGnD,SAAS,0BAAkC;CACzC,MAAM,eAAe,QAAQ,IAAI;AACjC,KAAI,aAAc,QAAO;AACzB,QAAO,KAAK,KAAK,SAAS,EAAE,SAAS,YAAY;;AAGnD,SAAS,gBAAwB;AAC/B,QAAO,KAAK,KAAK,SAAS,EAAE,SAAS,OAAO;;AAK9C,SAAS,cAAuB;AAE9B,QAAO,CAAC,CAAE,QAA+C,OACvD,QAAQ,SAAS,SAAS,OAAO,IACjC,QAAQ,SAAS,SAAS,WAAW;;AAGzC,SAAS,oBAA4B;AACnC,KAAI,aAAa,CACf,QAAO,QAAQ;CAIjB,MAAM,UAAU,KAAK,QAAQ,IAAI,IAAI,OAAO,KAAK,IAAI,CAAC,SAAS;AAC/D,QAAO,KAAK,KAAK,SAAS,MAAM,OAAO,SAAS;;AAKlD,SAAgB,wBAAwB,QAOxB;CACd,MAAM,aAAa,0BAA0B;CAC7C,MAAM,YAAY,yBAAyB;CAC3C,MAAM,UAAU,OAAO,WAAW;CAGlC,IAAI;AAEJ,KAAI,OAAO,YAAY,YAAY,aAAa,CAG9C,oBAAmB;EADA,QAAQ;EACK;EAAW;EAAgB;EAAU,OAAO,KAAK,UAAU;EAAC;MACvF;EAEL,MAAM,WAAW,QAAQ,SAAS,QAC/B,QAAQ,CAAC,IAAI,WAAW,YAAY,IAAI,CAAC,IAAI,WAAW,UAAU,CACpE;EACD,MAAM,aAAa,mBAAmB;AACtC,qBAAmB;GACjB,QAAQ;GACR,GAAG;GACH;GACA;GACA;GACA;GAAU,OAAO,KAAK,UAAU;GACjC;;AAGH,KAAI,OAAO,QAAQ,OAAO,SAAS,WACjC,kBAAiB,KAAK,UAAU,OAAO,KAAK;CAI9C,MAAM,cAAsC;EAC1C,aAAa;EACb,gBAAgB;EAChB,gBAAgB,QAAQ,IAAI,kBAAkB;EAC9C,eAAe;EACf,kBAAkB;EAClB,cAAc,eAAe;GAC5B,0BAA0B;EAC3B,qBAAqB;EACtB;AAED,KAAI,OAAO,MACT,aAAY,qBAAqB,OAAO;AAa1C,MAAK,MAAM,OAAO;EARhB;EACA;EACA;EACA;EACA;EACA;EAG+B,CAC/B,KAAI,QAAQ,IAAI,KACd,aAAY,OAAO,QAAQ,IAAI;AAKnC,KAAI,OAAO;OACJ,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,IAAI,CACnD,KAAI,UAAU,KAAA,KAAa,CAAC,IAAI,WAAW,QAAQ,CACjD,aAAY,OAAO;;CAOzB,MAAM,cAAc,gCAAgC;EAAE,SADtC,OAAO,KAAK,cAAc,MAAM,IAAI,KAAA;EACW;EAAS,CAAC;AAEzE,KAAI,KAAK;EACP,kBAAkB,iBAAiB,MAAM,GAAG,EAAE;EAC9C,SAAS,OAAO,KAAK,YAAY;EACjC;EACD,EAAE,6BAA6B;AAEhC,QAAO;EACL;EACA,kBAAkB,KAAK,QAAQ,WAAW;EAC1C;EACA;EACD;;AAKH,SAAgB,wBAAwB,QAOV;CAC5B,MAAM,OAAO,wBAAwB,OAAO;AAE5C,QAAO;EACL,KAAK,OAAO,OAAO,QAAQ;EAC3B,kBAAkB,KAAK;EACvB,kBAAkB,KAAK;EACvB,aAAa,KAAK;EAClB,aAAa,KAAK;EACnB;;;AAMH,SAAgB,2BAA2B,kBAAqC;AAC9E,KAAI,iBAAiB,WAAW,EAAG,QAAO;CAC1C,MAAM,UAAU,iBAAiB;AACjC,QAAO,WAAW,QAAQ"}
1
+ {"version":3,"file":"install-plan.js","names":[],"sources":["../../../src/daemon/install-plan.ts"],"sourcesContent":["/**\n * Install Plan Builder - Build gateway installation configuration\n *\n * Aligned with OpenClaw: adds XOPC_SERVICE_VERSION, StandardOut/ErrorPath,\n * supports binary runtime detection, and --foreground arg passing.\n */\n\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { homedir } from 'node:os';\nimport { createLogger } from '../utils/logger.js';\nimport { SERVICE_VERSION_ENV_KEY, formatGatewayServiceDescription } from './constants.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport type { GatewayServiceInstallArgs, GatewayServiceEnv } from './types.js';\n\nconst log = createLogger('InstallPlan');\n\nexport interface InstallPlan {\n programArguments: string[];\n workingDirectory: string;\n environment: Record<string, string>;\n description: string;\n}\n\n// ─── Path Resolution ───\n\nfunction resolveDefaultConfigPath(): string {\n const envConfig = process.env.XOPC_CONFIG || process.env.XOPC_CONFIG_PATH;\n if (envConfig) return envConfig;\n return path.join(homedir(), '.xopc', 'xopc.json');\n}\n\nfunction resolveDefaultWorkspace(): string {\n const envWorkspace = process.env.XOPC_WORKSPACE;\n if (envWorkspace) return envWorkspace;\n return path.join(homedir(), '.xopc', 'workspace');\n}\n\nfunction resolveLogDir(): string {\n return path.join(homedir(), '.xopc', 'logs');\n}\n\n// ─── Binary Detection ───\n\nfunction isSEABinary(): boolean {\n // Node.js SEA (Single Executable Application) detection\n return !!(process as NodeJS.Process & { pkg?: unknown }).pkg ||\n process.execPath.endsWith('xopc') ||\n process.execPath.endsWith('xopc.exe');\n}\n\nfunction resolveEntryPoint(): string {\n if (isSEABinary()) {\n return process.execPath;\n }\n // Must match package.json \"bin\" in built packages, while source/dev runs use bin.ts.\n // index.js only exports runCli and exits immediately when executed directly.\n const thisDir = path.dirname(new URL(import.meta.url).pathname);\n const sourceEntryPoint = path.join(thisDir, '..', 'cli', 'bin.ts');\n if (existsSync(sourceEntryPoint)) {\n return sourceEntryPoint;\n }\n return path.join(thisDir, '..', 'cli', 'bin.js');\n}\n\nfunction resolveServiceNodeArgs(): string[] {\n const serviceNodeArgs: string[] = [];\n const skipValueFlags = new Set(['-e', '--eval', '-p', '--print', '--test-name-pattern']);\n\n for (let index = 0; index < process.execArgv.length; index += 1) {\n const arg = process.execArgv[index];\n if (!arg) continue;\n if (arg.startsWith('--inspect') || arg.startsWith('--debug')) continue;\n if (skipValueFlags.has(arg)) {\n index += 1;\n continue;\n }\n if ([...skipValueFlags].some((flag) => arg.startsWith(`${flag}=`))) continue;\n serviceNodeArgs.push(arg);\n }\n\n return serviceNodeArgs;\n}\n\n// ─── Plan Builder ───\n\nexport function buildGatewayInstallPlan(params: {\n port: number;\n bind?: import('../config/schema.js').GatewayBindMode;\n token?: string;\n env?: GatewayServiceEnv;\n runtime?: 'node' | 'binary';\n version?: string;\n}): InstallPlan {\n const configPath = resolveDefaultConfigPath();\n const workspace = resolveDefaultWorkspace();\n const version = params.version || PACKAGE_VERSION;\n\n // Determine program + arguments\n let programArguments: string[];\n\n if (params.runtime === 'binary' || isSEABinary()) {\n // SEA binary: direct execution\n const binaryPath = process.execPath;\n programArguments = [binaryPath, 'gateway', '--foreground', '--port', params.port.toString()];\n } else {\n // Node.js runtime\n const nodeArgs = resolveServiceNodeArgs();\n const entryPoint = resolveEntryPoint();\n programArguments = [\n process.execPath,\n ...nodeArgs,\n entryPoint,\n 'gateway',\n '--foreground',\n '--port', params.port.toString(),\n ];\n }\n\n if (params.bind && params.bind !== 'loopback') {\n programArguments.push('--bind', params.bind);\n }\n\n // Build environment\n const environment: Record<string, string> = {\n XOPC_CONFIG: configPath,\n XOPC_WORKSPACE: workspace,\n XOPC_LOG_LEVEL: process.env.XOPC_LOG_LEVEL || 'info',\n XOPC_LOG_FILE: 'true',\n XOPC_LOG_CONSOLE: 'false',\n XOPC_LOG_DIR: resolveLogDir(),\n [SERVICE_VERSION_ENV_KEY]: version,\n XOPC_SERVICE_MARKER: '1',\n };\n\n if (params.token) {\n environment.XOPC_GATEWAY_TOKEN = params.token;\n }\n\n // Copy relevant API key env vars\n const relevantEnvVars = [\n 'OPENAI_API_KEY',\n 'ANTHROPIC_API_KEY',\n 'GOOGLE_API_KEY',\n 'BRAVE_API_KEY',\n 'DASHSCOPE_API_KEY',\n 'XOPC_LOG_RETENTION_DAYS',\n ];\n\n for (const key of relevantEnvVars) {\n if (process.env[key]) {\n environment[key] = process.env[key]!;\n }\n }\n\n // Add custom env from params (non-XOPC keys only; XOPC keys already handled)\n if (params.env) {\n for (const [key, value] of Object.entries(params.env)) {\n if (value !== undefined && !key.startsWith('XOPC_')) {\n environment[key] = value;\n }\n }\n }\n\n // Service description\n const profile = params.env?.XOPC_PROFILE?.trim() || undefined;\n const description = formatGatewayServiceDescription({ profile, version });\n\n log.info({\n programArguments: programArguments.slice(0, 3),\n envKeys: Object.keys(environment),\n version,\n }, 'Built gateway install plan');\n\n return {\n programArguments,\n workingDirectory: path.dirname(configPath),\n environment,\n description,\n };\n}\n\n// ─── Install Args Builder ───\n\nexport function buildGatewayInstallArgs(params: {\n port: number;\n bind?: import('../config/schema.js').GatewayBindMode;\n token?: string;\n env?: GatewayServiceEnv;\n runtime?: 'node' | 'binary';\n version?: string;\n}): GatewayServiceInstallArgs {\n const plan = buildGatewayInstallPlan(params);\n\n return {\n env: params.env || process.env,\n programArguments: plan.programArguments,\n workingDirectory: plan.workingDirectory,\n environment: plan.environment,\n description: plan.description,\n };\n}\n\n// ─── Validation ───\n\n/** Check if the program in an install plan still exists on disk */\nexport function validateInstallPlanProgram(programArguments: string[]): boolean {\n if (programArguments.length === 0) return false;\n const program = programArguments[0];\n return existsSync(program);\n}\n"],"mappings":"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"}
@@ -10,5 +10,13 @@
10
10
  */
11
11
  import type { GatewayService, GatewayServiceEnv } from './types.js';
12
12
  export declare function resolveLaunchAgentPlistPath(env?: GatewayServiceEnv): string;
13
+ export declare function buildLaunchAgentPlist(params: {
14
+ label: string;
15
+ programArguments: string[];
16
+ workingDirectory?: string;
17
+ environment: Record<string, string>;
18
+ stdoutPath?: string;
19
+ stderrPath?: string;
20
+ }): string;
13
21
  export declare function isLaunchdAvailable(): boolean;
14
22
  export declare const launchdService: GatewayService;
@@ -71,10 +71,7 @@ ${envDict}
71
71
  plist += ` <key>RunAtLoad</key>
72
72
  <true/>
73
73
  <key>KeepAlive</key>
74
- <dict>
75
- <key>SuccessfulExit</key>
76
- <false/>
77
- </dict>
74
+ <true/>
78
75
  <key>ThrottleInterval</key>
79
76
  <integer>10</integer>
80
77
  <key>ExitTimeOut</key>
@@ -220,15 +217,11 @@ const launchdService = {
220
217
  log.info("LaunchAgent stopped and disabled (plist removed)");
221
218
  } else {
222
219
  try {
223
- await launchctlExec([
224
- "kill",
225
- "SIGTERM",
226
- serviceTarget
227
- ]);
220
+ await launchctlExec(["bootout", serviceTarget]);
228
221
  } catch {
229
- log.debug("LaunchAgent kill SIGTERM failed (may not be running)");
222
+ log.debug("LaunchAgent bootout failed (may not be loaded)");
230
223
  }
231
- log.info("LaunchAgent stop signal sent");
224
+ log.info("LaunchAgent stopped and unloaded");
232
225
  }
233
226
  },
234
227
  async restart(args) {
@@ -300,6 +293,6 @@ const launchdService = {
300
293
  }
301
294
  };
302
295
  //#endregion
303
- export { isLaunchdAvailable, launchdService, resolveLaunchAgentPlistPath };
296
+ export { buildLaunchAgentPlist, isLaunchdAvailable, launchdService, resolveLaunchAgentPlistPath };
304
297
 
305
298
  //# sourceMappingURL=launchd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"launchd.js","names":["resolvePlistPathFromConstants"],"sources":["../../../src/daemon/launchd.ts"],"sourcesContent":["/**\n * LaunchAgent Service - macOS user service management\n *\n * Aligned with OpenClaw launchd implementation:\n * - KeepAlive with SuccessfulExit=false\n * - ThrottleInterval for restart throttling\n * - ExitTimeOut for graceful shutdown\n * - launchctl bootstrap/bootout for modern service management\n * - launchctl kickstart -k for restart\n */\n\nimport { writeFile, mkdir, readFile, rm } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { spawn, spawnSync } from 'node:child_process';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { createLogger } from '../utils/logger.js';\nimport {\n resolveGatewayLaunchAgentLabel,\n resolveLaunchAgentPlistPath as resolvePlistPathFromConstants,\n LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS,\n LAUNCH_AGENT_EXIT_TIMEOUT_SECONDS,\n} from './constants.js';\nimport type {\n GatewayService,\n GatewayServiceInstallArgs,\n GatewayServiceControlArgs,\n GatewayServiceEnvArgs,\n GatewayServiceRuntime,\n GatewayServiceCommandConfig,\n GatewayServiceEnv,\n GatewayServiceRestartResult,\n} from './types.js';\n\nconst log = createLogger('LaunchdService');\n\n// ─── Domain / Path Resolution ───\n\nfunction resolveGuiDomain(): string {\n const uid = typeof process.getuid === 'function' ? process.getuid() : 501;\n return `gui/${uid}`;\n}\n\nfunction resolveProfileFromEnv(env?: GatewayServiceEnv): string | undefined {\n return env?.XOPC_PROFILE?.trim() || undefined;\n}\n\nexport function resolveLaunchAgentPlistPath(env?: GatewayServiceEnv): string {\n return resolvePlistPathFromConstants(resolveProfileFromEnv(env));\n}\n\nfunction resolveLabelFromEnv(env?: GatewayServiceEnv): string {\n return resolveGatewayLaunchAgentLabel(resolveProfileFromEnv(env));\n}\n\nfunction resolveServiceTarget(env?: GatewayServiceEnv): string {\n return `${resolveGuiDomain()}/${resolveLabelFromEnv(env)}`;\n}\n\nfunction resolveLogDir(): string {\n return path.join(os.homedir(), '.xopc', 'logs');\n}\n\n// ─── Plist Generation ───\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\nfunction buildLaunchAgentPlist(params: {\n label: string;\n programArguments: string[];\n workingDirectory?: string;\n environment: Record<string, string>;\n stdoutPath?: string;\n stderrPath?: string;\n}): string {\n const envDict = Object.entries(params.environment)\n .map(([k, v]) => ` <key>${k}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n const programArgs = params.programArguments\n .map((arg) => ` <string>${escapeXml(arg)}</string>`)\n .join('\\n');\n\n let plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${escapeXml(params.label)}</string>\n <key>ProgramArguments</key>\n <array>\n${programArgs}\n </array>\n`;\n\n if (params.workingDirectory) {\n plist += ` <key>WorkingDirectory</key>\n <string>${escapeXml(params.workingDirectory)}</string>\n`;\n }\n\n if (Object.keys(params.environment).length > 0) {\n plist += ` <key>EnvironmentVariables</key>\n <dict>\n${envDict}\n </dict>\n`;\n }\n\n if (params.stdoutPath) {\n plist += ` <key>StandardOutPath</key>\n <string>${escapeXml(params.stdoutPath)}</string>\n`;\n }\n\n if (params.stderrPath) {\n plist += ` <key>StandardErrorPath</key>\n <string>${escapeXml(params.stderrPath)}</string>\n`;\n }\n\n plist += ` <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n <key>ThrottleInterval</key>\n <integer>${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}</integer>\n <key>ExitTimeOut</key>\n <integer>${LAUNCH_AGENT_EXIT_TIMEOUT_SECONDS}</integer>\n <key>ProcessType</key>\n <string>Interactive</string>\n</dict>\n</plist>`;\n\n return plist;\n}\n\n// ─── launchctl Execution ───\n\ninterface LaunchctlResult {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\nasync function launchctl(args: string[]): Promise<LaunchctlResult> {\n return new Promise<LaunchctlResult>((resolve, reject) => {\n const child = spawn('launchctl', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n\n child.stdout?.on('data', (data) => { stdout += data.toString(); });\n child.stderr?.on('data', (data) => { stderr += data.toString(); });\n\n child.on('close', (code) => {\n resolve({ stdout, stderr, exitCode: code });\n });\n child.on('error', (err) => {\n reject(new Error(`launchctl spawn failed: ${err.message}`));\n });\n });\n}\n\nasync function launchctlExec(args: string[]): Promise<string> {\n const result = await launchctl(args);\n if (result.stderr.trim()) {\n log.debug({ stderr: result.stderr.trim(), args }, 'launchctl stderr');\n }\n return result.stdout;\n}\n\n// ─── Availability Check ───\n\nexport function isLaunchdAvailable(): boolean {\n if (process.platform !== 'darwin') return false;\n try {\n const result = spawnSync('launchctl', ['version'], {\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 3000,\n });\n return result.status === 0;\n } catch {\n return false;\n }\n}\n\n// ─── Plist Parsing ───\n\nfunction parsePlistProgramArguments(content: string): string[] {\n const argsMatch = content.match(\n /<key>ProgramArguments<\\/key>\\s*<array>([\\s\\S]*?)<\\/array>/,\n );\n if (!argsMatch) return [];\n\n const programArgs: string[] = [];\n const stringMatches = argsMatch[1].matchAll(/<string>([\\s\\S]*?)<\\/string>/g);\n for (const m of stringMatches) {\n programArgs.push(unescapeXml(m[1]));\n }\n return programArgs;\n}\n\nfunction parsePlistEnvironment(content: string): Record<string, string> {\n const envMatch = content.match(\n /<key>EnvironmentVariables<\\/key>\\s*<dict>([\\s\\S]*?)<\\/dict>/,\n );\n if (!envMatch) return {};\n\n const environment: Record<string, string> = {};\n const pairs = envMatch[1].matchAll(\n /<key>([\\s\\S]*?)<\\/key>\\s*<string>([\\s\\S]*?)<\\/string>/g,\n );\n for (const pair of pairs) {\n environment[unescapeXml(pair[1])] = unescapeXml(pair[2]);\n }\n return environment;\n}\n\nfunction parsePlistStringValue(content: string, key: string): string | undefined {\n const regex = new RegExp(`<key>${key}</key>\\\\s*<string>([\\\\s\\\\S]*?)</string>`);\n const match = content.match(regex);\n return match ? unescapeXml(match[1]) : undefined;\n}\n\nfunction unescapeXml(str: string): string {\n return str\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\");\n}\n\n// ─── Service Implementation ───\n\nexport const launchdService: GatewayService = {\n label: resolveGatewayLaunchAgentLabel(),\n loadedText: 'LaunchAgent (loaded)',\n notLoadedText: 'LaunchAgent (not loaded)',\n\n async install(args: GatewayServiceInstallArgs): Promise<void> {\n const label = resolveLabelFromEnv(args.env);\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n const logDir = resolveLogDir();\n\n // Ensure directories exist\n await mkdir(path.dirname(plistPath), { recursive: true });\n await mkdir(logDir, { recursive: true });\n\n // Build environment\n const environment: Record<string, string> = {};\n if (args.environment) {\n Object.assign(environment, args.environment);\n }\n\n // Build plist\n const plist = buildLaunchAgentPlist({\n label,\n programArguments: args.programArguments,\n workingDirectory: args.workingDirectory,\n environment,\n stdoutPath: path.join(logDir, 'gateway.log'),\n stderrPath: path.join(logDir, 'gateway.err.log'),\n });\n\n // Write plist file\n await writeFile(plistPath, plist, 'utf8');\n args.stdout?.write(`Written: ${plistPath}\\n`);\n\n // Bootstrap the service\n const domain = resolveGuiDomain();\n const result = await launchctl(['bootstrap', domain, plistPath]);\n if (result.exitCode !== 0 && result.exitCode !== 37) {\n // exit 37 = already loaded, acceptable\n const detail = result.stderr.trim() || result.stdout.trim();\n if (detail) {\n log.warn({ detail, exitCode: result.exitCode }, 'launchctl bootstrap warning');\n }\n }\n\n log.info({ label, plistPath }, 'LaunchAgent installed and bootstrapped');\n },\n\n async uninstall(args: GatewayServiceControlArgs): Promise<void> {\n const serviceTarget = resolveServiceTarget(args.env);\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n\n // Bootout the service (stops + unloads)\n try {\n await launchctlExec(['bootout', serviceTarget]);\n } catch {\n // Ignore if not loaded\n }\n\n // Remove plist file\n if (existsSync(plistPath)) {\n await rm(plistPath);\n args.stdout?.write(`Removed: ${plistPath}\\n`);\n }\n\n log.info({ plistPath }, 'LaunchAgent uninstalled');\n },\n\n async stop(args: GatewayServiceControlArgs): Promise<void> {\n const serviceTarget = resolveServiceTarget(args.env);\n\n if (args.disable) {\n // Disable + bootout: service won't respawn\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n try {\n await launchctlExec(['bootout', serviceTarget]);\n } catch {\n // Ignore\n }\n if (existsSync(plistPath)) {\n await rm(plistPath);\n }\n log.info('LaunchAgent stopped and disabled (plist removed)');\n } else {\n // Send SIGTERM via launchctl kill\n try {\n await launchctlExec(['kill', 'SIGTERM', serviceTarget]);\n } catch {\n // Service might not be running\n log.debug('LaunchAgent kill SIGTERM failed (may not be running)');\n }\n log.info('LaunchAgent stop signal sent');\n }\n },\n\n async restart(args: GatewayServiceControlArgs): Promise<GatewayServiceRestartResult> {\n const serviceTarget = resolveServiceTarget(args.env);\n\n // Use kickstart -k for reliable restart (kills current + starts new)\n const result = await launchctl(['kickstart', '-k', serviceTarget]);\n\n if (result.exitCode === 0) {\n log.info('LaunchAgent restarted via kickstart');\n return { outcome: 'restarted' };\n }\n\n // Fallback: bootout + bootstrap\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n const domain = resolveGuiDomain();\n\n try {\n await launchctlExec(['bootout', serviceTarget]);\n } catch {\n // Ignore\n }\n\n const bootstrapResult = await launchctl(['bootstrap', domain, plistPath]);\n if (bootstrapResult.exitCode === 0 || bootstrapResult.exitCode === 37) {\n log.info('LaunchAgent restarted via bootout+bootstrap');\n return { outcome: 'restarted' };\n }\n\n throw new Error(\n `Failed to restart LaunchAgent: ${bootstrapResult.stderr.trim() || 'unknown error'}`,\n );\n },\n\n async isLoaded(args: GatewayServiceEnvArgs): Promise<boolean> {\n const serviceTarget = resolveServiceTarget(args.env);\n const result = await launchctl(['print', serviceTarget]);\n return result.exitCode === 0;\n },\n\n async readRuntime(env?: GatewayServiceEnv): Promise<GatewayServiceRuntime> {\n const serviceTarget = resolveServiceTarget(env);\n\n try {\n const result = await launchctl(['print', serviceTarget]);\n if (result.exitCode !== 0) {\n return { status: 'stopped' };\n }\n\n const output = result.stdout;\n\n // Parse PID\n let pid: number | undefined;\n const pidMatch = output.match(/pid\\s*=\\s*(\\d+)/);\n if (pidMatch) {\n const parsed = parseInt(pidMatch[1], 10);\n if (parsed > 0) pid = parsed;\n }\n\n // Parse last exit status\n let lastExitStatus: number | undefined;\n const exitMatch = output.match(/last exit code\\s*=\\s*(\\d+)/i);\n if (exitMatch) {\n lastExitStatus = parseInt(exitMatch[1], 10);\n }\n\n // Determine status from PID presence\n const status = pid ? 'running' : 'stopped';\n\n return { status, pid, lastExitStatus };\n } catch {\n return { status: 'unknown' };\n }\n },\n\n async readCommand(env?: GatewayServiceEnv): Promise<GatewayServiceCommandConfig | null> {\n const plistPath = resolveLaunchAgentPlistPath(env);\n if (!existsSync(plistPath)) return null;\n\n const content = await readFile(plistPath, 'utf8');\n\n const programArguments = parsePlistProgramArguments(content);\n if (programArguments.length === 0) return null;\n\n const environment = parsePlistEnvironment(content);\n const workingDirectory = parsePlistStringValue(content, 'WorkingDirectory');\n\n return {\n programArguments,\n workingDirectory,\n environment,\n sourcePath: plistPath,\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;aAgBkD;AAkBlD,MAAM,MAAM,aAAa,iBAAiB;AAI1C,SAAS,mBAA2B;AAElC,QAAO,OADK,OAAO,QAAQ,WAAW,aAAa,QAAQ,QAAQ,GAAG;;AAIxE,SAAS,sBAAsB,KAA6C;AAC1E,QAAO,KAAK,cAAc,MAAM,IAAI,KAAA;;AAGtC,SAAgB,4BAA4B,KAAiC;AAC3E,QAAOA,8BAA8B,sBAAsB,IAAI,CAAC;;AAGlE,SAAS,oBAAoB,KAAiC;AAC5D,QAAO,+BAA+B,sBAAsB,IAAI,CAAC;;AAGnE,SAAS,qBAAqB,KAAiC;AAC7D,QAAO,GAAG,kBAAkB,CAAC,GAAG,oBAAoB,IAAI;;AAG1D,SAAS,gBAAwB;AAC/B,QAAO,KAAK,KAAK,GAAG,SAAS,EAAE,SAAS,OAAO;;AAKjD,SAAS,UAAU,KAAqB;AACtC,QAAO,IACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,SAAS;;AAG5B,SAAS,sBAAsB,QAOpB;CACT,MAAM,UAAU,OAAO,QAAQ,OAAO,YAAY,CAC/C,KAAK,CAAC,GAAG,OAAO,gBAAgB,EAAE,0BAA0B,UAAU,EAAE,CAAC,WAAW,CACpF,KAAK,KAAK;CAEb,MAAM,cAAc,OAAO,iBACxB,KAAK,QAAQ,mBAAmB,UAAU,IAAI,CAAC,WAAW,CAC1D,KAAK,KAAK;CAEb,IAAI,QAAQ;;;;;cAKA,UAAU,OAAO,MAAM,CAAC;;;EAGpC,YAAY;;;AAIZ,KAAI,OAAO,iBACT,UAAS;cACC,UAAU,OAAO,iBAAiB,CAAC;;AAI/C,KAAI,OAAO,KAAK,OAAO,YAAY,CAAC,SAAS,EAC3C,UAAS;;EAEX,QAAQ;;;AAKR,KAAI,OAAO,WACT,UAAS;cACC,UAAU,OAAO,WAAW,CAAC;;AAIzC,KAAI,OAAO,WACT,UAAS;cACC,UAAU,OAAO,WAAW,CAAC;;AAIzC,UAAS;;;;;;;;;;;;;;;AAgBT,QAAO;;AAWT,eAAe,UAAU,MAA0C;AACjE,QAAO,IAAI,SAA0B,SAAS,WAAW;EACvD,MAAM,QAAQ,MAAM,aAAa,MAAM,EACrC,OAAO;GAAC;GAAU;GAAQ;GAAO,EAClC,CAAC;EAEF,IAAI,SAAS;EACb,IAAI,SAAS;AAEb,QAAM,QAAQ,GAAG,SAAS,SAAS;AAAE,aAAU,KAAK,UAAU;IAAI;AAClE,QAAM,QAAQ,GAAG,SAAS,SAAS;AAAE,aAAU,KAAK,UAAU;IAAI;AAElE,QAAM,GAAG,UAAU,SAAS;AAC1B,WAAQ;IAAE;IAAQ;IAAQ,UAAU;IAAM,CAAC;IAC3C;AACF,QAAM,GAAG,UAAU,QAAQ;AACzB,0BAAO,IAAI,MAAM,2BAA2B,IAAI,UAAU,CAAC;IAC3D;GACF;;AAGJ,eAAe,cAAc,MAAiC;CAC5D,MAAM,SAAS,MAAM,UAAU,KAAK;AACpC,KAAI,OAAO,OAAO,MAAM,CACtB,KAAI,MAAM;EAAE,QAAQ,OAAO,OAAO,MAAM;EAAE;EAAM,EAAE,mBAAmB;AAEvE,QAAO,OAAO;;AAKhB,SAAgB,qBAA8B;AAC5C,KAAI,QAAQ,aAAa,SAAU,QAAO;AAC1C,KAAI;AAKF,SAJe,UAAU,aAAa,CAAC,UAAU,EAAE;GACjD,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CACY,CAAC,WAAW;SACnB;AACN,SAAO;;;AAMX,SAAS,2BAA2B,SAA2B;CAC7D,MAAM,YAAY,QAAQ,MACxB,4DACD;AACD,KAAI,CAAC,UAAW,QAAO,EAAE;CAEzB,MAAM,cAAwB,EAAE;CAChC,MAAM,gBAAgB,UAAU,GAAG,SAAS,gCAAgC;AAC5E,MAAK,MAAM,KAAK,cACd,aAAY,KAAK,YAAY,EAAE,GAAG,CAAC;AAErC,QAAO;;AAGT,SAAS,sBAAsB,SAAyC;CACtE,MAAM,WAAW,QAAQ,MACvB,8DACD;AACD,KAAI,CAAC,SAAU,QAAO,EAAE;CAExB,MAAM,cAAsC,EAAE;CAC9C,MAAM,QAAQ,SAAS,GAAG,SACxB,yDACD;AACD,MAAK,MAAM,QAAQ,MACjB,aAAY,YAAY,KAAK,GAAG,IAAI,YAAY,KAAK,GAAG;AAE1D,QAAO;;AAGT,SAAS,sBAAsB,SAAiB,KAAiC;CAC/E,MAAM,QAAQ,IAAI,OAAO,QAAQ,IAAI,yCAAyC;CAC9E,MAAM,QAAQ,QAAQ,MAAM,MAAM;AAClC,QAAO,QAAQ,YAAY,MAAM,GAAG,GAAG,KAAA;;AAGzC,SAAS,YAAY,KAAqB;AACxC,QAAO,IACJ,QAAQ,UAAU,IAAI,CACtB,QAAQ,SAAS,IAAI,CACrB,QAAQ,SAAS,IAAI,CACrB,QAAQ,WAAW,KAAI,CACvB,QAAQ,WAAW,IAAI;;AAK5B,MAAa,iBAAiC;CAC5C,OAAO,gCAAgC;CACvC,YAAY;CACZ,eAAe;CAEf,MAAM,QAAQ,MAAgD;EAC5D,MAAM,QAAQ,oBAAoB,KAAK,IAAI;EAC3C,MAAM,YAAY,4BAA4B,KAAK,IAAI;EACvD,MAAM,SAAS,eAAe;AAG9B,QAAM,MAAM,KAAK,QAAQ,UAAU,EAAE,EAAE,WAAW,MAAM,CAAC;AACzD,QAAM,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;EAGxC,MAAM,cAAsC,EAAE;AAC9C,MAAI,KAAK,YACP,QAAO,OAAO,aAAa,KAAK,YAAY;AAc9C,QAAM,UAAU,WAVF,sBAAsB;GAClC;GACA,kBAAkB,KAAK;GACvB,kBAAkB,KAAK;GACvB;GACA,YAAY,KAAK,KAAK,QAAQ,cAAc;GAC5C,YAAY,KAAK,KAAK,QAAQ,kBAAkB;GACjD,CAG+B,EAAE,OAAO;AACzC,OAAK,QAAQ,MAAM,YAAY,UAAU,IAAI;EAI7C,MAAM,SAAS,MAAM,UAAU;GAAC;GADjB,kBACoC;GAAE;GAAU,CAAC;AAChE,MAAI,OAAO,aAAa,KAAK,OAAO,aAAa,IAAI;GAEnD,MAAM,SAAS,OAAO,OAAO,MAAM,IAAI,OAAO,OAAO,MAAM;AAC3D,OAAI,OACF,KAAI,KAAK;IAAE;IAAQ,UAAU,OAAO;IAAU,EAAE,8BAA8B;;AAIlF,MAAI,KAAK;GAAE;GAAO;GAAW,EAAE,yCAAyC;;CAG1E,MAAM,UAAU,MAAgD;EAC9D,MAAM,gBAAgB,qBAAqB,KAAK,IAAI;EACpD,MAAM,YAAY,4BAA4B,KAAK,IAAI;AAGvD,MAAI;AACF,SAAM,cAAc,CAAC,WAAW,cAAc,CAAC;UACzC;AAKR,MAAI,WAAW,UAAU,EAAE;AACzB,SAAM,GAAG,UAAU;AACnB,QAAK,QAAQ,MAAM,YAAY,UAAU,IAAI;;AAG/C,MAAI,KAAK,EAAE,WAAW,EAAE,0BAA0B;;CAGpD,MAAM,KAAK,MAAgD;EACzD,MAAM,gBAAgB,qBAAqB,KAAK,IAAI;AAEpD,MAAI,KAAK,SAAS;GAEhB,MAAM,YAAY,4BAA4B,KAAK,IAAI;AACvD,OAAI;AACF,UAAM,cAAc,CAAC,WAAW,cAAc,CAAC;WACzC;AAGR,OAAI,WAAW,UAAU,CACvB,OAAM,GAAG,UAAU;AAErB,OAAI,KAAK,mDAAmD;SACvD;AAEL,OAAI;AACF,UAAM,cAAc;KAAC;KAAQ;KAAW;KAAc,CAAC;WACjD;AAEN,QAAI,MAAM,uDAAuD;;AAEnE,OAAI,KAAK,+BAA+B;;;CAI5C,MAAM,QAAQ,MAAuE;EACnF,MAAM,gBAAgB,qBAAqB,KAAK,IAAI;AAKpD,OAAI,MAFiB,UAAU;GAAC;GAAa;GAAM;GAAc,CAAC,EAEvD,aAAa,GAAG;AACzB,OAAI,KAAK,sCAAsC;AAC/C,UAAO,EAAE,SAAS,aAAa;;EAIjC,MAAM,YAAY,4BAA4B,KAAK,IAAI;EACvD,MAAM,SAAS,kBAAkB;AAEjC,MAAI;AACF,SAAM,cAAc,CAAC,WAAW,cAAc,CAAC;UACzC;EAIR,MAAM,kBAAkB,MAAM,UAAU;GAAC;GAAa;GAAQ;GAAU,CAAC;AACzE,MAAI,gBAAgB,aAAa,KAAK,gBAAgB,aAAa,IAAI;AACrE,OAAI,KAAK,8CAA8C;AACvD,UAAO,EAAE,SAAS,aAAa;;AAGjC,QAAM,IAAI,MACR,kCAAkC,gBAAgB,OAAO,MAAM,IAAI,kBACpE;;CAGH,MAAM,SAAS,MAA+C;AAG5D,UAAO,MADc,UAAU,CAAC,SADV,qBAAqB,KAAK,IACM,CAAC,CAAC,EAC1C,aAAa;;CAG7B,MAAM,YAAY,KAAyD;EACzE,MAAM,gBAAgB,qBAAqB,IAAI;AAE/C,MAAI;GACF,MAAM,SAAS,MAAM,UAAU,CAAC,SAAS,cAAc,CAAC;AACxD,OAAI,OAAO,aAAa,EACtB,QAAO,EAAE,QAAQ,WAAW;GAG9B,MAAM,SAAS,OAAO;GAGtB,IAAI;GACJ,MAAM,WAAW,OAAO,MAAM,kBAAkB;AAChD,OAAI,UAAU;IACZ,MAAM,SAAS,SAAS,SAAS,IAAI,GAAG;AACxC,QAAI,SAAS,EAAG,OAAM;;GAIxB,IAAI;GACJ,MAAM,YAAY,OAAO,MAAM,8BAA8B;AAC7D,OAAI,UACF,kBAAiB,SAAS,UAAU,IAAI,GAAG;AAM7C,UAAO;IAAE,QAFM,MAAM,YAAY;IAEhB;IAAK;IAAgB;UAChC;AACN,UAAO,EAAE,QAAQ,WAAW;;;CAIhC,MAAM,YAAY,KAAsE;EACtF,MAAM,YAAY,4BAA4B,IAAI;AAClD,MAAI,CAAC,WAAW,UAAU,CAAE,QAAO;EAEnC,MAAM,UAAU,MAAM,SAAS,WAAW,OAAO;EAEjD,MAAM,mBAAmB,2BAA2B,QAAQ;AAC5D,MAAI,iBAAiB,WAAW,EAAG,QAAO;EAE1C,MAAM,cAAc,sBAAsB,QAAQ;AAGlD,SAAO;GACL;GACA,kBAJuB,sBAAsB,SAAS,mBAItC;GAChB;GACA,YAAY;GACb;;CAEJ"}
1
+ {"version":3,"file":"launchd.js","names":["resolvePlistPathFromConstants"],"sources":["../../../src/daemon/launchd.ts"],"sourcesContent":["/**\n * LaunchAgent Service - macOS user service management\n *\n * Aligned with OpenClaw launchd implementation:\n * - KeepAlive with SuccessfulExit=false\n * - ThrottleInterval for restart throttling\n * - ExitTimeOut for graceful shutdown\n * - launchctl bootstrap/bootout for modern service management\n * - launchctl kickstart -k for restart\n */\n\nimport { writeFile, mkdir, readFile, rm } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { spawn, spawnSync } from 'node:child_process';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { createLogger } from '../utils/logger.js';\nimport {\n resolveGatewayLaunchAgentLabel,\n resolveLaunchAgentPlistPath as resolvePlistPathFromConstants,\n LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS,\n LAUNCH_AGENT_EXIT_TIMEOUT_SECONDS,\n} from './constants.js';\nimport type {\n GatewayService,\n GatewayServiceInstallArgs,\n GatewayServiceControlArgs,\n GatewayServiceEnvArgs,\n GatewayServiceRuntime,\n GatewayServiceCommandConfig,\n GatewayServiceEnv,\n GatewayServiceRestartResult,\n} from './types.js';\n\nconst log = createLogger('LaunchdService');\n\n// ─── Domain / Path Resolution ───\n\nfunction resolveGuiDomain(): string {\n const uid = typeof process.getuid === 'function' ? process.getuid() : 501;\n return `gui/${uid}`;\n}\n\nfunction resolveProfileFromEnv(env?: GatewayServiceEnv): string | undefined {\n return env?.XOPC_PROFILE?.trim() || undefined;\n}\n\nexport function resolveLaunchAgentPlistPath(env?: GatewayServiceEnv): string {\n return resolvePlistPathFromConstants(resolveProfileFromEnv(env));\n}\n\nfunction resolveLabelFromEnv(env?: GatewayServiceEnv): string {\n return resolveGatewayLaunchAgentLabel(resolveProfileFromEnv(env));\n}\n\nfunction resolveServiceTarget(env?: GatewayServiceEnv): string {\n return `${resolveGuiDomain()}/${resolveLabelFromEnv(env)}`;\n}\n\nfunction resolveLogDir(): string {\n return path.join(os.homedir(), '.xopc', 'logs');\n}\n\n// ─── Plist Generation ───\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\nexport function buildLaunchAgentPlist(params: {\n label: string;\n programArguments: string[];\n workingDirectory?: string;\n environment: Record<string, string>;\n stdoutPath?: string;\n stderrPath?: string;\n}): string {\n const envDict = Object.entries(params.environment)\n .map(([k, v]) => ` <key>${k}</key>\\n <string>${escapeXml(v)}</string>`)\n .join('\\n');\n\n const programArgs = params.programArguments\n .map((arg) => ` <string>${escapeXml(arg)}</string>`)\n .join('\\n');\n\n let plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${escapeXml(params.label)}</string>\n <key>ProgramArguments</key>\n <array>\n${programArgs}\n </array>\n`;\n\n if (params.workingDirectory) {\n plist += ` <key>WorkingDirectory</key>\n <string>${escapeXml(params.workingDirectory)}</string>\n`;\n }\n\n if (Object.keys(params.environment).length > 0) {\n plist += ` <key>EnvironmentVariables</key>\n <dict>\n${envDict}\n </dict>\n`;\n }\n\n if (params.stdoutPath) {\n plist += ` <key>StandardOutPath</key>\n <string>${escapeXml(params.stdoutPath)}</string>\n`;\n }\n\n if (params.stderrPath) {\n plist += ` <key>StandardErrorPath</key>\n <string>${escapeXml(params.stderrPath)}</string>\n`;\n }\n\n plist += ` <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>ThrottleInterval</key>\n <integer>${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}</integer>\n <key>ExitTimeOut</key>\n <integer>${LAUNCH_AGENT_EXIT_TIMEOUT_SECONDS}</integer>\n <key>ProcessType</key>\n <string>Interactive</string>\n</dict>\n</plist>`;\n\n return plist;\n}\n\n// ─── launchctl Execution ───\n\ninterface LaunchctlResult {\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\nasync function launchctl(args: string[]): Promise<LaunchctlResult> {\n return new Promise<LaunchctlResult>((resolve, reject) => {\n const child = spawn('launchctl', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n\n child.stdout?.on('data', (data) => { stdout += data.toString(); });\n child.stderr?.on('data', (data) => { stderr += data.toString(); });\n\n child.on('close', (code) => {\n resolve({ stdout, stderr, exitCode: code });\n });\n child.on('error', (err) => {\n reject(new Error(`launchctl spawn failed: ${err.message}`));\n });\n });\n}\n\nasync function launchctlExec(args: string[]): Promise<string> {\n const result = await launchctl(args);\n if (result.stderr.trim()) {\n log.debug({ stderr: result.stderr.trim(), args }, 'launchctl stderr');\n }\n return result.stdout;\n}\n\n// ─── Availability Check ───\n\nexport function isLaunchdAvailable(): boolean {\n if (process.platform !== 'darwin') return false;\n try {\n const result = spawnSync('launchctl', ['version'], {\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 3000,\n });\n return result.status === 0;\n } catch {\n return false;\n }\n}\n\n// ─── Plist Parsing ───\n\nfunction parsePlistProgramArguments(content: string): string[] {\n const argsMatch = content.match(\n /<key>ProgramArguments<\\/key>\\s*<array>([\\s\\S]*?)<\\/array>/,\n );\n if (!argsMatch) return [];\n\n const programArgs: string[] = [];\n const stringMatches = argsMatch[1].matchAll(/<string>([\\s\\S]*?)<\\/string>/g);\n for (const m of stringMatches) {\n programArgs.push(unescapeXml(m[1]));\n }\n return programArgs;\n}\n\nfunction parsePlistEnvironment(content: string): Record<string, string> {\n const envMatch = content.match(\n /<key>EnvironmentVariables<\\/key>\\s*<dict>([\\s\\S]*?)<\\/dict>/,\n );\n if (!envMatch) return {};\n\n const environment: Record<string, string> = {};\n const pairs = envMatch[1].matchAll(\n /<key>([\\s\\S]*?)<\\/key>\\s*<string>([\\s\\S]*?)<\\/string>/g,\n );\n for (const pair of pairs) {\n environment[unescapeXml(pair[1])] = unescapeXml(pair[2]);\n }\n return environment;\n}\n\nfunction parsePlistStringValue(content: string, key: string): string | undefined {\n const regex = new RegExp(`<key>${key}</key>\\\\s*<string>([\\\\s\\\\S]*?)</string>`);\n const match = content.match(regex);\n return match ? unescapeXml(match[1]) : undefined;\n}\n\nfunction unescapeXml(str: string): string {\n return str\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\");\n}\n\n// ─── Service Implementation ───\n\nexport const launchdService: GatewayService = {\n label: resolveGatewayLaunchAgentLabel(),\n loadedText: 'LaunchAgent (loaded)',\n notLoadedText: 'LaunchAgent (not loaded)',\n\n async install(args: GatewayServiceInstallArgs): Promise<void> {\n const label = resolveLabelFromEnv(args.env);\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n const logDir = resolveLogDir();\n\n // Ensure directories exist\n await mkdir(path.dirname(plistPath), { recursive: true });\n await mkdir(logDir, { recursive: true });\n\n // Build environment\n const environment: Record<string, string> = {};\n if (args.environment) {\n Object.assign(environment, args.environment);\n }\n\n // Build plist\n const plist = buildLaunchAgentPlist({\n label,\n programArguments: args.programArguments,\n workingDirectory: args.workingDirectory,\n environment,\n stdoutPath: path.join(logDir, 'gateway.log'),\n stderrPath: path.join(logDir, 'gateway.err.log'),\n });\n\n // Write plist file\n await writeFile(plistPath, plist, 'utf8');\n args.stdout?.write(`Written: ${plistPath}\\n`);\n\n // Bootstrap the service\n const domain = resolveGuiDomain();\n const result = await launchctl(['bootstrap', domain, plistPath]);\n if (result.exitCode !== 0 && result.exitCode !== 37) {\n // exit 37 = already loaded, acceptable\n const detail = result.stderr.trim() || result.stdout.trim();\n if (detail) {\n log.warn({ detail, exitCode: result.exitCode }, 'launchctl bootstrap warning');\n }\n }\n\n log.info({ label, plistPath }, 'LaunchAgent installed and bootstrapped');\n },\n\n async uninstall(args: GatewayServiceControlArgs): Promise<void> {\n const serviceTarget = resolveServiceTarget(args.env);\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n\n // Bootout the service (stops + unloads)\n try {\n await launchctlExec(['bootout', serviceTarget]);\n } catch {\n // Ignore if not loaded\n }\n\n // Remove plist file\n if (existsSync(plistPath)) {\n await rm(plistPath);\n args.stdout?.write(`Removed: ${plistPath}\\n`);\n }\n\n log.info({ plistPath }, 'LaunchAgent uninstalled');\n },\n\n async stop(args: GatewayServiceControlArgs): Promise<void> {\n const serviceTarget = resolveServiceTarget(args.env);\n\n if (args.disable) {\n // Disable + bootout: service won't respawn\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n try {\n await launchctlExec(['bootout', serviceTarget]);\n } catch {\n // Ignore\n }\n if (existsSync(plistPath)) {\n await rm(plistPath);\n }\n log.info('LaunchAgent stopped and disabled (plist removed)');\n } else {\n try {\n await launchctlExec(['bootout', serviceTarget]);\n } catch {\n // Service might not be running or loaded.\n log.debug('LaunchAgent bootout failed (may not be loaded)');\n }\n log.info('LaunchAgent stopped and unloaded');\n }\n },\n\n async restart(args: GatewayServiceControlArgs): Promise<GatewayServiceRestartResult> {\n const serviceTarget = resolveServiceTarget(args.env);\n\n // Use kickstart -k for reliable restart (kills current + starts new)\n const result = await launchctl(['kickstart', '-k', serviceTarget]);\n\n if (result.exitCode === 0) {\n log.info('LaunchAgent restarted via kickstart');\n return { outcome: 'restarted' };\n }\n\n // Fallback: bootout + bootstrap\n const plistPath = resolveLaunchAgentPlistPath(args.env);\n const domain = resolveGuiDomain();\n\n try {\n await launchctlExec(['bootout', serviceTarget]);\n } catch {\n // Ignore\n }\n\n const bootstrapResult = await launchctl(['bootstrap', domain, plistPath]);\n if (bootstrapResult.exitCode === 0 || bootstrapResult.exitCode === 37) {\n log.info('LaunchAgent restarted via bootout+bootstrap');\n return { outcome: 'restarted' };\n }\n\n throw new Error(\n `Failed to restart LaunchAgent: ${bootstrapResult.stderr.trim() || 'unknown error'}`,\n );\n },\n\n async isLoaded(args: GatewayServiceEnvArgs): Promise<boolean> {\n const serviceTarget = resolveServiceTarget(args.env);\n const result = await launchctl(['print', serviceTarget]);\n return result.exitCode === 0;\n },\n\n async readRuntime(env?: GatewayServiceEnv): Promise<GatewayServiceRuntime> {\n const serviceTarget = resolveServiceTarget(env);\n\n try {\n const result = await launchctl(['print', serviceTarget]);\n if (result.exitCode !== 0) {\n return { status: 'stopped' };\n }\n\n const output = result.stdout;\n\n // Parse PID\n let pid: number | undefined;\n const pidMatch = output.match(/pid\\s*=\\s*(\\d+)/);\n if (pidMatch) {\n const parsed = parseInt(pidMatch[1], 10);\n if (parsed > 0) pid = parsed;\n }\n\n // Parse last exit status\n let lastExitStatus: number | undefined;\n const exitMatch = output.match(/last exit code\\s*=\\s*(\\d+)/i);\n if (exitMatch) {\n lastExitStatus = parseInt(exitMatch[1], 10);\n }\n\n // Determine status from PID presence\n const status = pid ? 'running' : 'stopped';\n\n return { status, pid, lastExitStatus };\n } catch {\n return { status: 'unknown' };\n }\n },\n\n async readCommand(env?: GatewayServiceEnv): Promise<GatewayServiceCommandConfig | null> {\n const plistPath = resolveLaunchAgentPlistPath(env);\n if (!existsSync(plistPath)) return null;\n\n const content = await readFile(plistPath, 'utf8');\n\n const programArguments = parsePlistProgramArguments(content);\n if (programArguments.length === 0) return null;\n\n const environment = parsePlistEnvironment(content);\n const workingDirectory = parsePlistStringValue(content, 'WorkingDirectory');\n\n return {\n programArguments,\n workingDirectory,\n environment,\n sourcePath: plistPath,\n };\n },\n};\n"],"mappings":"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"}
@@ -7,5 +7,30 @@
7
7
  * - Proper start/stop/restart lifecycle
8
8
  */
9
9
  import type { GatewayService } from './types.js';
10
+ declare function resolveTaskCommandSidecarPath(taskName: string): string;
11
+ declare function resolveTaskWrapperPath(taskName: string): string;
12
+ declare function resolveTaskXmlPath(taskName: string): string;
13
+ declare function escapeCmdSetValue(value: string): string;
14
+ declare function quoteCmdArg(value: string): string;
15
+ declare function buildTaskWrapperContent(params: {
16
+ programArguments: string[];
17
+ workingDirectory?: string;
18
+ environment?: Record<string, string>;
19
+ }): string;
20
+ declare function buildTaskXml(params: {
21
+ description: string;
22
+ wrapperPath: string;
23
+ workingDirectory?: string;
24
+ }): string;
25
+ export declare const schtasksTestInternals: {
26
+ buildTaskWrapperContent: typeof buildTaskWrapperContent;
27
+ buildTaskXml: typeof buildTaskXml;
28
+ escapeCmdSetValue: typeof escapeCmdSetValue;
29
+ quoteCmdArg: typeof quoteCmdArg;
30
+ resolveTaskCommandSidecarPath: typeof resolveTaskCommandSidecarPath;
31
+ resolveTaskWrapperPath: typeof resolveTaskWrapperPath;
32
+ resolveTaskXmlPath: typeof resolveTaskXmlPath;
33
+ };
10
34
  export declare function isSchtasksAvailable(): boolean;
11
35
  export declare const schtasksService: GatewayService;
36
+ export {};
@@ -24,14 +24,11 @@ function resolveTaskName(env) {
24
24
  }
25
25
  async function schtasks(args) {
26
26
  return new Promise((resolve, reject) => {
27
- const child = spawn("schtasks", args, {
28
- stdio: [
29
- "ignore",
30
- "pipe",
31
- "pipe"
32
- ],
33
- shell: true
34
- });
27
+ const child = spawn("schtasks", args, { stdio: [
28
+ "ignore",
29
+ "pipe",
30
+ "pipe"
31
+ ] });
35
32
  let stdout = "";
36
33
  let stderr = "";
37
34
  child.stdout?.on("data", (data) => {
@@ -60,9 +57,87 @@ async function schtasksExec(args) {
60
57
  }
61
58
  return result.stdout;
62
59
  }
60
+ function resolveTaskDaemonDir() {
61
+ return path.join(os.homedir(), ".xopc", "daemon");
62
+ }
63
63
  function resolveTaskEnvSidecarPath(taskName) {
64
- const configDir = path.join(os.homedir(), ".xopc", "daemon");
65
- return path.join(configDir, `${taskName}.env.json`);
64
+ return path.join(resolveTaskDaemonDir(), `${taskName}.env.json`);
65
+ }
66
+ function resolveTaskCommandSidecarPath(taskName) {
67
+ return path.join(resolveTaskDaemonDir(), `${taskName}.command.json`);
68
+ }
69
+ function resolveTaskWrapperPath(taskName) {
70
+ return path.join(resolveTaskDaemonDir(), `${taskName}.cmd`);
71
+ }
72
+ function resolveTaskXmlPath(taskName) {
73
+ return path.join(resolveTaskDaemonDir(), `${taskName}.xml`);
74
+ }
75
+ function escapeCmdSetValue(value) {
76
+ return value.replace(/\^/g, "^^").replace(/%/g, "%%").replace(/"/g, "^\"").replace(/&/g, "^&").replace(/</g, "^<").replace(/>/g, "^>").replace(/\|/g, "^|");
77
+ }
78
+ function quoteCmdArg(value) {
79
+ return `"${value.replace(/"/g, "\\\"")}"`;
80
+ }
81
+ function escapeXmlValue(value) {
82
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
83
+ }
84
+ function buildTaskWrapperContent(params) {
85
+ const lines = ["@echo off"];
86
+ for (const [key, value] of Object.entries(params.environment ?? {})) lines.push(`set "${key}=${escapeCmdSetValue(value)}"`);
87
+ if (params.workingDirectory) lines.push(`cd /d ${quoteCmdArg(params.workingDirectory)}`);
88
+ lines.push(params.programArguments.map(quoteCmdArg).join(" "));
89
+ return `${lines.join("\r\n")}\r\n`;
90
+ }
91
+ function buildTaskXml(params) {
92
+ const escapedDescription = escapeXmlValue(params.description);
93
+ const escapedWrapperPath = escapeXmlValue(params.wrapperPath);
94
+ const escapedWorkingDirectory = params.workingDirectory ? escapeXmlValue(params.workingDirectory) : "";
95
+ return `<?xml version="1.0" encoding="UTF-16"?>
96
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
97
+ <RegistrationInfo>
98
+ <Description>${escapedDescription}</Description>
99
+ </RegistrationInfo>
100
+ <Triggers>
101
+ <LogonTrigger>
102
+ <Enabled>true</Enabled>
103
+ </LogonTrigger>
104
+ </Triggers>
105
+ <Principals>
106
+ <Principal id="Author">
107
+ <LogonType>InteractiveToken</LogonType>
108
+ <RunLevel>LeastPrivilege</RunLevel>
109
+ </Principal>
110
+ </Principals>
111
+ <Settings>
112
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
113
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
114
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
115
+ <AllowHardTerminate>true</AllowHardTerminate>
116
+ <StartWhenAvailable>true</StartWhenAvailable>
117
+ <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
118
+ <IdleSettings>
119
+ <StopOnIdleEnd>false</StopOnIdleEnd>
120
+ <RestartOnIdle>false</RestartOnIdle>
121
+ </IdleSettings>
122
+ <AllowStartOnDemand>true</AllowStartOnDemand>
123
+ <Enabled>true</Enabled>
124
+ <Hidden>false</Hidden>
125
+ <RunOnlyIfIdle>false</RunOnlyIfIdle>
126
+ <WakeToRun>false</WakeToRun>
127
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
128
+ <Priority>7</Priority>
129
+ <RestartOnFailure>
130
+ <Interval>PT1M</Interval>
131
+ <Count>999</Count>
132
+ </RestartOnFailure>
133
+ </Settings>
134
+ <Actions Context="Author">
135
+ <Exec>
136
+ <Command>${escapedWrapperPath}</Command>${escapedWorkingDirectory ? `\n <WorkingDirectory>${escapedWorkingDirectory}</WorkingDirectory>` : ""}
137
+ </Exec>
138
+ </Actions>
139
+ </Task>
140
+ `;
66
141
  }
67
142
  function writeTaskEnvSidecar(taskName, environment) {
68
143
  const sidecarPath = resolveTaskEnvSidecarPath(taskName);
@@ -84,12 +159,72 @@ function readTaskEnvSidecar(taskName) {
84
159
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
85
160
  } catch {}
86
161
  }
87
- function removeTaskEnvSidecar(taskName) {
88
- const sidecarPath = resolveTaskEnvSidecarPath(taskName);
162
+ function writeTaskCommandSidecar(taskName, args) {
163
+ const sidecarPath = resolveTaskCommandSidecarPath(taskName);
89
164
  try {
90
- rmSync(sidecarPath, { force: true });
165
+ writeFileSync(sidecarPath, JSON.stringify({
166
+ programArguments: args.programArguments,
167
+ workingDirectory: args.workingDirectory
168
+ }, null, 2), "utf8");
169
+ } catch (err) {
170
+ log.warn({
171
+ err,
172
+ sidecarPath
173
+ }, "Failed to write task command sidecar");
174
+ }
175
+ }
176
+ function readTaskCommandSidecar(taskName) {
177
+ const sidecarPath = resolveTaskCommandSidecarPath(taskName);
178
+ try {
179
+ const raw = readFileSync(sidecarPath, "utf8");
180
+ const parsed = JSON.parse(raw);
181
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.programArguments) && parsed.programArguments.every((arg) => typeof arg === "string")) return {
182
+ programArguments: parsed.programArguments,
183
+ workingDirectory: typeof parsed.workingDirectory === "string" ? parsed.workingDirectory : void 0
184
+ };
91
185
  } catch {}
186
+ return null;
187
+ }
188
+ function writeTaskSupportFiles(taskName, args) {
189
+ const wrapperPath = resolveTaskWrapperPath(taskName);
190
+ const xmlPath = resolveTaskXmlPath(taskName);
191
+ mkdirSync(resolveTaskDaemonDir(), { recursive: true });
192
+ writeFileSync(wrapperPath, buildTaskWrapperContent({
193
+ programArguments: args.programArguments,
194
+ workingDirectory: args.workingDirectory,
195
+ environment: args.environment
196
+ }), "utf8");
197
+ writeFileSync(xmlPath, buildTaskXml({
198
+ description: args.description || "xopc Gateway Service",
199
+ wrapperPath,
200
+ workingDirectory: args.workingDirectory
201
+ }), "utf16le");
202
+ writeTaskEnvSidecar(taskName, args.environment ?? {});
203
+ writeTaskCommandSidecar(taskName, args);
204
+ return {
205
+ wrapperPath,
206
+ xmlPath
207
+ };
92
208
  }
209
+ function removeTaskSupportFiles(taskName) {
210
+ for (const filePath of [
211
+ resolveTaskEnvSidecarPath(taskName),
212
+ resolveTaskCommandSidecarPath(taskName),
213
+ resolveTaskWrapperPath(taskName),
214
+ resolveTaskXmlPath(taskName)
215
+ ]) try {
216
+ rmSync(filePath, { force: true });
217
+ } catch {}
218
+ }
219
+ const schtasksTestInternals = {
220
+ buildTaskWrapperContent,
221
+ buildTaskXml,
222
+ escapeCmdSetValue,
223
+ quoteCmdArg,
224
+ resolveTaskCommandSidecarPath,
225
+ resolveTaskWrapperPath,
226
+ resolveTaskXmlPath
227
+ };
93
228
  function isSchtasksAvailable() {
94
229
  if (process.platform !== "win32") return false;
95
230
  try {
@@ -99,7 +234,6 @@ function isSchtasksAvailable() {
99
234
  "ignore",
100
235
  "ignore"
101
236
  ],
102
- shell: true,
103
237
  timeout: 3e3
104
238
  }).status === 0;
105
239
  } catch {
@@ -112,8 +246,6 @@ const schtasksService = {
112
246
  notLoadedText: "Scheduled Task (not installed)",
113
247
  async install(args) {
114
248
  const taskName = resolveTaskName(args.env);
115
- const program = args.programArguments[0];
116
- const programArgs = args.programArguments.slice(1).join(" ");
117
249
  try {
118
250
  await schtasks([
119
251
  "/delete",
@@ -122,23 +254,22 @@ const schtasksService = {
122
254
  "/f"
123
255
  ]);
124
256
  } catch {}
257
+ const { wrapperPath, xmlPath } = writeTaskSupportFiles(taskName, args);
125
258
  await schtasksExec([
126
259
  "/create",
127
260
  "/tn",
128
261
  taskName,
129
- "/tr",
130
- `"${program}" ${programArgs}`,
131
- "/sc",
132
- "ONLOGON",
133
- "/rl",
134
- "LIMITED",
262
+ "/xml",
263
+ xmlPath,
135
264
  "/f"
136
265
  ]);
137
- if (args.environment && Object.keys(args.environment).length > 0) writeTaskEnvSidecar(taskName, args.environment);
138
266
  args.stdout?.write(`Created scheduled task: ${taskName}\n`);
139
- args.stdout?.write(` Program: ${program}\n`);
140
- args.stdout?.write(` Args: ${programArgs}\n`);
141
- log.info({ taskName }, "Scheduled task installed");
267
+ args.stdout?.write(` Wrapper: ${wrapperPath}\n`);
268
+ args.stdout?.write(` Command: ${args.programArguments.join(" ")}\n`);
269
+ log.info({
270
+ taskName,
271
+ wrapperPath
272
+ }, "Scheduled task installed");
142
273
  },
143
274
  async uninstall(args) {
144
275
  const taskName = resolveTaskName(args.env);
@@ -160,7 +291,7 @@ const schtasksService = {
160
291
  } catch (err) {
161
292
  log.debug({ err }, "Uninstall task not found");
162
293
  }
163
- removeTaskEnvSidecar(taskName);
294
+ removeTaskSupportFiles(taskName);
164
295
  log.info({ taskName }, "Scheduled task uninstalled");
165
296
  },
166
297
  async stop(args) {
@@ -243,31 +374,20 @@ const schtasksService = {
243
374
  async readCommand(env) {
244
375
  const taskName = resolveTaskName(env);
245
376
  try {
246
- const result = await schtasks([
377
+ if ((await schtasks([
247
378
  "/query",
248
379
  "/tn",
249
380
  taskName,
250
381
  "/fo",
251
382
  "list",
252
383
  "/v"
253
- ]);
254
- if (result.exitCode !== 0) return null;
255
- const output = result.stdout;
256
- const taskRunMatch = output.match(/Task To Run:\s*(.+)/i);
257
- const workDirMatch = output.match(/Start In:\s*(.+)/i);
258
- if (!taskRunMatch) return null;
259
- const taskRun = taskRunMatch[1].trim();
260
- let programArguments;
261
- if (taskRun.startsWith("\"")) {
262
- const closeQuote = taskRun.indexOf("\"", 1);
263
- if (closeQuote > 0) programArguments = [taskRun.slice(1, closeQuote), ...taskRun.slice(closeQuote + 1).trim().split(/\s+/).filter(Boolean)];
264
- else programArguments = taskRun.split(/\s+/);
265
- } else programArguments = taskRun.split(/\s+/);
266
- const environment = readTaskEnvSidecar(taskName);
384
+ ])).exitCode !== 0) return null;
385
+ const commandSidecar = readTaskCommandSidecar(taskName);
386
+ if (!commandSidecar) return null;
267
387
  return {
268
- programArguments,
269
- workingDirectory: workDirMatch?.[1]?.trim() || void 0,
270
- environment
388
+ programArguments: commandSidecar.programArguments,
389
+ workingDirectory: commandSidecar.workingDirectory,
390
+ environment: readTaskEnvSidecar(taskName)
271
391
  };
272
392
  } catch {
273
393
  return null;
@@ -275,6 +395,6 @@ const schtasksService = {
275
395
  }
276
396
  };
277
397
  //#endregion
278
- export { isSchtasksAvailable, schtasksService };
398
+ export { isSchtasksAvailable, schtasksService, schtasksTestInternals };
279
399
 
280
400
  //# sourceMappingURL=schtasks.js.map