@symerian/symi 2.6.42 → 2.7.1

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 (204) hide show
  1. package/dist/{acp-cli-CrEIx-c7.js → acp-cli-B2f0qBiM.js} +2 -2
  2. package/dist/{acp-cli-BLUeUUA5.js → acp-cli-D-9rzaOI.js} +2 -2
  3. package/dist/{agents-DDRDszOI.js → agents-Z8ruJPz2.js} +5 -5
  4. package/dist/{agents.config-BEVb1Pyx.js → agents.config-B9BZoM2m.js} +1 -1
  5. package/dist/{agents.config-BKCY6F2A.js → agents.config-C951ocyh.js} +1 -1
  6. package/dist/{audio-preflight-DD18zIZd.js → audio-preflight-HILy3i0m.js} +4 -4
  7. package/dist/{audit-DB9YkTVX.js → audit-BgdM9XZl.js} +2 -2
  8. package/dist/{audit-BjI_Yyr5.js → audit-xSagQWH8.js} +2 -2
  9. package/dist/{auth-choice-D2xXwBeN.js → auth-choice-BmYi6pBy.js} +2 -2
  10. package/dist/{auth-choice-BbzReh6k.js → auth-choice-D_VfXxhG.js} +2 -2
  11. package/dist/{banner-DqqRwplt.js → banner-Bucr6qZ5.js} +1 -1
  12. package/dist/{browser-cli-kDUw45Y0.js → browser-cli-Bc_JVdTj.js} +3 -3
  13. package/dist/{browser-cli-Ctn9do4z.js → browser-cli-Dr0yx7-7.js} +3 -3
  14. package/dist/build-info.json +3 -3
  15. package/dist/bundled/boot-md/handler.js +1 -1
  16. package/dist/bundled/session-memory/handler.js +1 -1
  17. package/dist/{call-DwpGquzW.js → call-CX0cs106.js} +1 -1
  18. package/dist/{call-DKi-hnaF.js → call-DeCQ2DhS.js} +1 -1
  19. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  20. package/dist/{channel-options-DQcpDVCx.js → channel-options-BMDryXHq.js} +1 -1
  21. package/dist/{channel-options-DcazVJQf.js → channel-options-z8-WeZyO.js} +1 -1
  22. package/dist/{channel-web-u5yMYO4j.js → channel-web-D_YxZAHT.js} +1 -1
  23. package/dist/{channels-cli-C3iJE8eg.js → channels-cli-397NgC51.js} +7 -7
  24. package/dist/{channels-cli-Bog4Kn3X.js → channels-cli-CV7_JUru.js} +7 -7
  25. package/dist/{chrome-BxwUEWrH.js → chrome-CiOKaTN-.js} +7 -7
  26. package/dist/cli/daemon-cli.js +1 -1
  27. package/dist/{cli-eBtEEHLK.js → cli-4XU7pNlM.js} +4 -4
  28. package/dist/{cli-QybdZdE7.js → cli-Dq95udFO.js} +4 -4
  29. package/dist/{client-jI1oE0_k.js → client-B8xmq-Pw.js} +13 -1
  30. package/dist/{client-T3qcxXru.js → client-qUlxXXVJ.js} +13 -1
  31. package/dist/{command-registry-BWnZSpbL.js → command-registry-Cx9T7GYx.js} +10 -10
  32. package/dist/{completion-cli-BhJFXaVU.js → completion-cli-WlSuySPZ.js} +1 -1
  33. package/dist/{completion-cli-CUKm7Hb6.js → completion-cli-cyVjlhls.js} +2 -2
  34. package/dist/{config-cli-B796xZBe.js → config-cli-CMymzmrI.js} +1 -1
  35. package/dist/{config-cli-CjnQyv45.js → config-cli-EdmbBExA.js} +1 -1
  36. package/dist/{configure-D3wIQ0yI.js → configure-DRMAIBBf.js} +6 -6
  37. package/dist/{configure-DUqT7Dyn.js → configure-DkFujfiY.js} +6 -6
  38. package/dist/control-ui/css/style.css +165 -0
  39. package/dist/control-ui/js/app.js +39 -0
  40. package/dist/control-ui/js/gateway.js +2 -0
  41. package/dist/control-ui/js/history.js +220 -85
  42. package/dist/control-ui/js/settings.js +71 -8
  43. package/dist/{cron-cli-DCpsXkrw.js → cron-cli-BHxfvWeT.js} +3 -3
  44. package/dist/{cron-cli-QCTdVZuP.js → cron-cli-DacyI1Pw.js} +3 -3
  45. package/dist/{daemon-cli-DSDcmWOi.js → daemon-cli-BLhFbih0.js} +2 -2
  46. package/dist/{daemon-cli-DhJYEAoL.js → daemon-cli-BxKtiouf.js} +2 -2
  47. package/dist/daemon-cli.js +12 -0
  48. package/dist/{deliver-DtuY4Wgl.js → deliver-D6IcPfbt.js} +1 -1
  49. package/dist/{devices-cli-BNG-fvdl.js → devices-cli-1kiO08aL.js} +2 -2
  50. package/dist/{devices-cli-x3jIP4Ih.js → devices-cli-B8Q8AzbX.js} +2 -2
  51. package/dist/{doctor-completion-DBE1IVKj.js → doctor-completion-BDzhB9pE.js} +1 -1
  52. package/dist/{doctor-completion-D3oUNFQM.js → doctor-completion-CfeROjIz.js} +1 -1
  53. package/dist/entry.js +1 -1
  54. package/dist/{exec-approvals-cli-CmndFjEA.js → exec-approvals-cli-BRx2oxs5.js} +4 -4
  55. package/dist/{exec-approvals-cli-BSgxsdMt.js → exec-approvals-cli-Cj6Z-vxL.js} +4 -4
  56. package/dist/extensionAPI.js +6 -6
  57. package/dist/{gateway-cli-BguO9yV4.js → gateway-cli-C-G0QrTd.js} +185 -81
  58. package/dist/{gateway-cli-Dc1RT_b9.js → gateway-cli-Cpn7JyAw.js} +185 -81
  59. package/dist/{gateway-rpc-BMdVa3QV.js → gateway-rpc-BWTK7g8B.js} +1 -1
  60. package/dist/{gateway-rpc-5fREQe9Q.js → gateway-rpc-WM-HXUbl.js} +1 -1
  61. package/dist/{glass-ui-ws-DoIZyE9O.js → glass-ui-ws-BI81Lh6Z.js} +32 -13
  62. package/dist/{glass-ui-ws-D0UYleBF.js → glass-ui-ws-jAbqZVc9.js} +32 -13
  63. package/dist/{health-D3w4YMlC.js → health-B1nih5LD.js} +2 -2
  64. package/dist/{health-cVvZdeDu.js → health-CsTxt66a.js} +2 -2
  65. package/dist/{hooks-cli-te5hzOAB.js → hooks-cli-BhxMFkKe.js} +5 -5
  66. package/dist/{hooks-cli-DZIbjfOA.js → hooks-cli-CIlvtt2M.js} +5 -5
  67. package/dist/{image-lp19FlzF.js → image-D7Fl08gG.js} +1 -1
  68. package/dist/index.js +9 -9
  69. package/dist/llm-slug-generator.js +1 -1
  70. package/dist/{logs-cli-DtlrLf7u.js → logs-cli-BZV7dq_U.js} +3 -3
  71. package/dist/{logs-cli-CfGj-dOg.js → logs-cli-DyCcDkRs.js} +3 -3
  72. package/dist/{models-BCfPhQ4m.js → models-DhMe2rzL.js} +3 -3
  73. package/dist/{models-cli-BFAtjNIF.js → models-cli-DKM5sCQM.js} +5 -5
  74. package/dist/{models-cli-Cj2CwbPw.js → models-cli-Dewg24W2.js} +6 -6
  75. package/dist/{node-cli-DnfDOh1d.js → node-cli-4eMRkmrw.js} +1 -1
  76. package/dist/{node-cli-eHn9-YfJ.js → node-cli-Dfs0UmnJ.js} +1 -1
  77. package/dist/{nodes-cli-Ct4M7JOe.js → nodes-cli-BKb61OjH.js} +3 -3
  78. package/dist/{nodes-cli-Ci3oxw8k.js → nodes-cli-DxfP9DIz.js} +3 -3
  79. package/dist/{onboard-BMq8qc1m.js → onboard-Dw_nX2RD.js} +3 -3
  80. package/dist/{onboard-channels-B5wgcffj.js → onboard-channels-8ynJSZL6.js} +1 -1
  81. package/dist/{onboard-channels-CiOun4k1.js → onboard-channels-ChxtmhFY.js} +1 -1
  82. package/dist/{onboard-helpers-DFvWPFhU.js → onboard-helpers-CueJ7O_B.js} +1 -1
  83. package/dist/{onboard-helpers-BBtPmJEe.js → onboard-helpers-PpGrZIGw.js} +1 -1
  84. package/dist/{onboard-remote-BURDxwoE.js → onboard-remote-Cd9kIiSG.js} +1 -1
  85. package/dist/{onboard-remote-D-B4EiOE.js → onboard-remote-DCdpB2HH.js} +1 -1
  86. package/dist/{onboard-skills-BDCzVjxb.js → onboard-skills-CC2CV5Er.js} +1 -1
  87. package/dist/{onboard-skills-CbzgBT1W.js → onboard-skills-DRICcBwa.js} +1 -1
  88. package/dist/{onboard-VcdBF5TB.js → onboard-yrgbC1mu.js} +3 -3
  89. package/dist/{onboarding-Dp7p6zKa.js → onboarding-BJcYsOFw.js} +7 -7
  90. package/dist/{onboarding-CGNpTyg2.js → onboarding-e-RSXpUi.js} +7 -7
  91. package/dist/{onboarding.finalize-B6DOu8vp.js → onboarding.finalize-CKNdiZEZ.js} +9 -9
  92. package/dist/{onboarding.finalize-CrsE5P43.js → onboarding.finalize-DenYTWUu.js} +10 -10
  93. package/dist/{onboarding.gateway-config-DKwJ5bWd.js → onboarding.gateway-config-DTWQ-Bqq.js} +3 -3
  94. package/dist/{onboarding.gateway-config-B9zGPfCl.js → onboarding.gateway-config-Dj68bVBt.js} +3 -3
  95. package/dist/{pi-embedded-DQe8vBwU.js → pi-embedded-BzS693I1.js} +72 -21
  96. package/dist/{pi-embedded-helpers-Dij3O6ox.js → pi-embedded-helpers-DXAoe1Bx.js} +4 -4
  97. package/dist/{plugin-registry-BzIbwU3K.js → plugin-registry-Bzz7mUyl.js} +1 -1
  98. package/dist/{plugin-registry-D3TUplW8.js → plugin-registry-SUa2FFIj.js} +1 -1
  99. package/dist/plugin-sdk/{channel-web-B6m8UOCl.js → channel-web-DRabhx1e.js} +1 -1
  100. package/dist/plugin-sdk/gateway/protocol/index.d.ts +13 -2
  101. package/dist/plugin-sdk/gateway/protocol/schema/sessions.d.ts +11 -0
  102. package/dist/plugin-sdk/gateway/protocol/schema/types.d.ts +4 -1
  103. package/dist/plugin-sdk/index.js +2 -2
  104. package/dist/plugin-sdk/{reply-CGoJQT_s.js → reply-Cw3shPk2.js} +58 -7
  105. package/dist/plugin-sdk/{web-CJjSOTnI.js → web-CH43nBP0.js} +2 -2
  106. package/dist/{plugins-cli-BD1Jb2Ml.js → plugins-cli-DwtoU3xk.js} +5 -5
  107. package/dist/{plugins-cli-DFyRjAtJ.js → plugins-cli-qG50__1A.js} +5 -5
  108. package/dist/{program-context-45vPEw2G.js → program-context-CZwy-bh6.js} +29 -29
  109. package/dist/{program-qGm2M9PG.js → program-mZOVTSdQ.js} +10 -10
  110. package/dist/{prompt-select-styled-DRIS9jSv.js → prompt-select-styled-BZnuv8jI.js} +6 -6
  111. package/dist/{prompt-select-styled-DRraJGwA.js → prompt-select-styled-CtNg5KGU.js} +6 -6
  112. package/dist/{provider-auth-helpers-BNOWsjW5.js → provider-auth-helpers-CEYCb2mJ.js} +1 -1
  113. package/dist/{provider-auth-helpers-BREVjR7R.js → provider-auth-helpers-D60nbSLq.js} +1 -1
  114. package/dist/{push-apns-CYC70eKy.js → push-apns-B5txhDVi.js} +1 -1
  115. package/dist/{push-apns-CObVeJLC.js → push-apns-CoG1P_P0.js} +1 -1
  116. package/dist/{pw-ai-BO5cSmwD.js → pw-ai-CANiWpMA.js} +1 -1
  117. package/dist/{register.agent-DzzY8jON.js → register.agent-BY_Df_Me.js} +9 -9
  118. package/dist/{register.agent-DRrHvYcz.js → register.agent-CopGIf9e.js} +8 -8
  119. package/dist/{register.configure-DlXkwkNR.js → register.configure-Bu_XKu7Z.js} +11 -11
  120. package/dist/{register.configure-BxtTIFTa.js → register.configure-CoYQhQBT.js} +11 -11
  121. package/dist/{register.maintenance-LEf8Q_5p.js → register.maintenance-BtqQ3hDN.js} +11 -11
  122. package/dist/{register.maintenance-CXLXouZV.js → register.maintenance-VqCqhB7y.js} +10 -10
  123. package/dist/{register.message-DsPa-xHV.js → register.message-DZCCEK01.js} +5 -5
  124. package/dist/{register.message-C4ifV9vG.js → register.message-dd1xYBZT.js} +5 -5
  125. package/dist/{register.onboard-CrY4PxLP.js → register.onboard-B6EL1d-C.js} +7 -7
  126. package/dist/{register.onboard-DLRPa_4B.js → register.onboard-lzwHKufI.js} +7 -7
  127. package/dist/{register.setup-fBG_dJfi.js → register.setup-BJsr5_qp.js} +7 -7
  128. package/dist/{register.setup-By37g1vN.js → register.setup-D_b_HW5D.js} +7 -7
  129. package/dist/{register.status-health-sessions-DG7KGnD0.js → register.status-health-sessions-Dvj4mlom.js} +7 -7
  130. package/dist/{register.status-health-sessions-Ckw86-gn.js → register.status-health-sessions-jeiqtgl7.js} +7 -7
  131. package/dist/{register.subclis-C_xKLugM.js → register.subclis-DdPRkz29.js} +20 -20
  132. package/dist/{reply-DyjXROKp.js → reply-XaXqDK9F.js} +49 -10
  133. package/dist/{rpc-Co5PQ3IJ.js → rpc-N1nf_c1A.js} +1 -1
  134. package/dist/{rpc-D0FiEinj.js → rpc-QHo-8pCM.js} +1 -1
  135. package/dist/{run-main-Cg3ecTbO.js → run-main-MhL-xG3_.js} +17 -17
  136. package/dist/{runner-Ct0suQrd.js → runner-BS9gGL5i.js} +1 -1
  137. package/dist/{security-cli-D5BWzqEJ.js → security-cli-B-4J6enu.js} +3 -3
  138. package/dist/{security-cli-CG1uSlRK.js → security-cli-CSMUEGPK.js} +3 -3
  139. package/dist/{server-methods-CWcLut3F.js → server-methods-0ksPFnev.js} +471 -88
  140. package/dist/{server-methods-CW8eFTGD.js → server-methods-Bu1RjLe6.js} +471 -88
  141. package/dist/{server-node-events-C2h9OPo7.js → server-node-events-DCbzFyQF.js} +5 -5
  142. package/dist/{server-node-events-CDB0u8PP.js → server-node-events-DZAN3hWE.js} +5 -5
  143. package/dist/{status-B2Qt-5kL.js → status-CiHkFbIh.js} +1 -1
  144. package/dist/{status-ZPW5EACm.js → status-DOTlXdcN.js} +5 -5
  145. package/dist/{status-CMx3GAax.js → status-IWqSLXLR.js} +1 -1
  146. package/dist/{status-DfPfMVNZ.js → status-qkmUAeWo.js} +5 -5
  147. package/dist/{subagent-registry-DQHg3jUV.js → subagent-registry-DCoU9xIE.js} +49 -10
  148. package/dist/{system-cli-DFZNGx0i.js → system-cli-DY7ov1OT.js} +3 -3
  149. package/dist/{system-cli-Z7uzO8qd.js → system-cli-LFURKpwS.js} +3 -3
  150. package/dist/{tui-DjPsMdL6.js → tui-BH7JwqvB.js} +2 -2
  151. package/dist/{tui-DrATGNms.js → tui-C_5HG495.js} +2 -2
  152. package/dist/{tui-cli-D19-MCXt.js → tui-cli-B65PKZMj.js} +3 -3
  153. package/dist/{tui-cli-pWD_NrUR.js → tui-cli-CJnfmuxq.js} +3 -3
  154. package/dist/{unified-runner-DvOFqcrw.js → unified-runner-CApgXtVC.js} +58 -7
  155. package/dist/{update-cli-Df9rh_aN.js → update-cli-Bi6iMCXr.js} +11 -11
  156. package/dist/{update-cli-DBasZeVl.js → update-cli-BjQ3o9gQ.js} +12 -12
  157. package/dist/{update-runner-DVa6cMqp.js → update-runner-BhVsdpxm.js} +1 -1
  158. package/dist/{update-runner-BRKFzAwV.js → update-runner-FhW6n-Nv.js} +1 -1
  159. package/dist/{web-Dd05xbUr.js → web-ConlA_Ch.js} +4 -4
  160. package/dist/{web-Cw3bFAiP.js → web-CthDLVSz.js} +1 -1
  161. package/dist/{web-CGh5tw__.js → web-Db_-ahjX.js} +6 -6
  162. package/dist/{web-KiuDkd0x.js → web-Dpb_tv_j.js} +5 -5
  163. package/extensions/bluebubbles/package.json +1 -1
  164. package/extensions/copilot-proxy/package.json +1 -1
  165. package/extensions/diagnostics-otel/package.json +1 -1
  166. package/extensions/discord/package.json +1 -1
  167. package/extensions/feishu/package.json +1 -1
  168. package/extensions/google-antigravity-auth/package.json +1 -1
  169. package/extensions/google-gemini-cli-auth/package.json +1 -1
  170. package/extensions/googlechat/package.json +1 -1
  171. package/extensions/imessage/package.json +1 -1
  172. package/extensions/irc/package.json +1 -1
  173. package/extensions/learning-loop/package.json +1 -1
  174. package/extensions/line/package.json +1 -1
  175. package/extensions/llm-task/package.json +1 -1
  176. package/extensions/matrix/CHANGELOG.md +12 -0
  177. package/extensions/matrix/package.json +1 -1
  178. package/extensions/mattermost/package.json +1 -1
  179. package/extensions/memory-core/package.json +1 -1
  180. package/extensions/memory-lancedb/package.json +1 -1
  181. package/extensions/minimax-portal-auth/package.json +1 -1
  182. package/extensions/msteams/CHANGELOG.md +12 -0
  183. package/extensions/msteams/package.json +1 -1
  184. package/extensions/nextcloud-talk/package.json +1 -1
  185. package/extensions/nostr/CHANGELOG.md +12 -0
  186. package/extensions/nostr/package.json +1 -1
  187. package/extensions/open-prose/package.json +1 -1
  188. package/extensions/outlook/package.json +1 -1
  189. package/extensions/pipeline/package.json +1 -1
  190. package/extensions/signal/package.json +1 -1
  191. package/extensions/slack/package.json +1 -1
  192. package/extensions/telegram/package.json +1 -1
  193. package/extensions/tlon/package.json +1 -1
  194. package/extensions/twitch/CHANGELOG.md +12 -0
  195. package/extensions/twitch/package.json +1 -1
  196. package/extensions/voice-call/CHANGELOG.md +12 -0
  197. package/extensions/voice-call/package.json +1 -1
  198. package/extensions/whatsapp/package.json +1 -1
  199. package/extensions/zalo/CHANGELOG.md +12 -0
  200. package/extensions/zalo/package.json +1 -1
  201. package/extensions/zalouser/CHANGELOG.md +12 -0
  202. package/extensions/zalouser/package.json +1 -1
  203. package/package.json +1 -1
  204. package/skills/long-task/scripts/detach-task.sh +91 -14
@@ -10,18 +10,28 @@
10
10
  # task-<id>.cmd — literal command line (for future agent turns to see)
11
11
  # task-<id>.started — ISO-8601 start timestamp
12
12
  # task-<id>.log — combined stdout + stderr (grows as task runs)
13
- # task-<id>.status — "running" while alive, "complete rc=<N>" after exit
13
+ # task-<id>.status — "running pid=<N> started=<ISO>"
14
+ # → "complete rc=<N> ended=<ISO>" on clean exit
15
+ # → "aborted rc=<N> signal=<NAME>" on SIGTERM/INT/HUP
14
16
  #
15
- # Exit code: 0 on successful spawn (PID recorded), 1 on invalid args.
17
+ # Exit code: 0 on successful spawn, 1 on missing args, 2 on invalid args,
18
+ # 3 on workdir creation failure.
19
+ #
20
+ # Hardening notes (2.7.0):
21
+ # - task-id is validated against an allow-list ([a-zA-Z0-9._-], 1–60 chars,
22
+ # must not start with '.' or '-'). Prevents path traversal via task-id.
23
+ # - workdir must be an absolute path.
24
+ # - Status file is written via tmp + mv so concurrent readers never see a
25
+ # half-written file and the "stuck at running after SIGTERM" failure mode
26
+ # is eliminated.
27
+ # - Child shell has a TERM/INT/HUP trap that records an "aborted" status
28
+ # before exiting, so external kill signals leave a deterministic record.
16
29
  #
17
30
  # Design notes:
18
31
  # - setsid + nohup + disown: triple-armor against SIGHUP when the parent
19
32
  # shell exits. The subprocess gets its own session.
20
33
  # - stdin closed, stdout/stderr redirected to the log file. No terminal.
21
34
  # - The wrapper shell exits immediately after spawning; does NOT wait.
22
- # - On process exit, an inner wrapper writes "complete rc=<N>" to the
23
- # status file so a future agent turn can detect completion without
24
- # racing with the cron monitor's polling.
25
35
 
26
36
  set -euo pipefail
27
37
 
@@ -30,29 +40,96 @@ if [[ $# -lt 3 ]]; then
30
40
  exit 1
31
41
  fi
32
42
 
33
- task_id="$1"
43
+ raw_task_id="$1"
34
44
  workdir="$2"
35
45
  shift 2
36
46
 
37
- mkdir -p "$workdir"
47
+ # --- task-id validation -----------------------------------------------------
48
+ if [[ -z "$raw_task_id" ]]; then
49
+ echo "detach-task: task-id must not be empty" >&2
50
+ exit 2
51
+ fi
52
+ if [[ ${#raw_task_id} -gt 60 ]]; then
53
+ echo "detach-task: task-id exceeds 60 chars" >&2
54
+ exit 2
55
+ fi
56
+ if [[ "$raw_task_id" =~ [^a-zA-Z0-9._-] ]]; then
57
+ echo "detach-task: task-id contains illegal chars (allowed: alphanumeric, dot, dash, underscore): $raw_task_id" >&2
58
+ exit 2
59
+ fi
60
+ case "$raw_task_id" in
61
+ .* | -*)
62
+ echo "detach-task: task-id must not start with dot or dash: $raw_task_id" >&2
63
+ exit 2
64
+ ;;
65
+ esac
66
+ task_id="$raw_task_id"
67
+
68
+ # --- workdir validation -----------------------------------------------------
69
+ if [[ "$workdir" != /* ]]; then
70
+ echo "detach-task: workdir must be an absolute path: $workdir" >&2
71
+ exit 2
72
+ fi
73
+ if ! mkdir -p "$workdir"; then
74
+ echo "detach-task: cannot create workdir: $workdir" >&2
75
+ exit 3
76
+ fi
77
+
38
78
  pid_file="$workdir/task-${task_id}.pid"
39
79
  cmd_file="$workdir/task-${task_id}.cmd"
40
80
  started_file="$workdir/task-${task_id}.started"
41
81
  log_file="$workdir/task-${task_id}.log"
42
82
  status_file="$workdir/task-${task_id}.status"
43
83
 
84
+ # --- atomic status writer (parent) -----------------------------------------
85
+ write_status_atomic() {
86
+ local _payload="$1"
87
+ local _tmp="${status_file}.$$.${RANDOM}.tmp"
88
+ printf '%s\n' "$_payload" > "$_tmp"
89
+ mv -f "$_tmp" "$status_file"
90
+ }
91
+
44
92
  printf '%s\n' "$*" > "$cmd_file"
45
- date -u -Iseconds > "$started_file"
46
- echo "running" > "$status_file"
93
+ started_at="$(date -u -Iseconds)"
94
+ printf '%s\n' "$started_at" > "$started_file"
95
+ write_status_atomic "running started=$started_at"
47
96
 
48
- setsid nohup bash -c "
49
- \"\$@\" > '$log_file' 2>&1
50
- rc=\$?
51
- echo \"complete rc=\$rc\" > '$status_file'
52
- " -- "$@" < /dev/null > /dev/null 2>&1 &
97
+ # --- detached child --------------------------------------------------------
98
+ # The inner `bash -c` inherits status/log paths as positional args so we
99
+ # never string-interpolate paths into the script body. The inner script
100
+ # re-implements the atomic writer (functions don't cross process boundaries
101
+ # via `bash -c`). A TERM/INT/HUP trap catches external kill signals and
102
+ # writes a terminal "aborted" status before exiting with the signal's code.
103
+ setsid nohup bash -c '
104
+ set -u
105
+ _status="$1"
106
+ _log="$2"
107
+ shift 2
108
+ _write() {
109
+ local _tmp="${_status}.$$.${RANDOM}.tmp"
110
+ printf "%s\n" "$1" > "$_tmp" && mv -f "$_tmp" "$_status"
111
+ }
112
+ _on_signal() {
113
+ local _sig="$1"
114
+ local _rc=$?
115
+ _write "aborted rc=$_rc signal=$_sig ended=$(date -u -Iseconds)" 2>/dev/null || true
116
+ exit "$_rc"
117
+ }
118
+ trap "_on_signal TERM" TERM
119
+ trap "_on_signal INT" INT
120
+ trap "_on_signal HUP" HUP
121
+ "$@" > "$_log" 2>&1
122
+ rc=$?
123
+ _write "complete rc=$rc ended=$(date -u -Iseconds)"
124
+ ' -- "$status_file" "$log_file" "$@" < /dev/null > /dev/null 2>&1 &
53
125
 
54
126
  pid=$!
55
127
  disown "$pid" 2>/dev/null || true
56
128
  printf '%d\n' "$pid" > "$pid_file"
57
129
 
130
+ # Finalise the parent status with the PID once known. The child may race us
131
+ # and overwrite with "complete" if the command was very fast — that's fine,
132
+ # the mv is atomic and the final state wins.
133
+ write_status_atomic "running pid=$pid started=$started_at"
134
+
58
135
  echo "spawned task=$task_id pid=$pid log=$log_file status=$status_file"