@zerwiz/ymir 0.1.4 → 0.1.7

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 (865) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/README.md +74 -0
  27. package/.agents/backend/backends/cmux.sh +649 -0
  28. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  29. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  30. package/.agents/backend/backends/herdr.sh +3386 -0
  31. package/.agents/backend/backends/orca.sh +290 -0
  32. package/.agents/backend/backends/tmux.sh +357 -0
  33. package/.agents/backend/backends/zellij.sh +700 -0
  34. package/.agents/backend/fm-afk-launch.sh +654 -0
  35. package/.agents/backend/fm-afk-return.sh +241 -0
  36. package/.agents/backend/fm-afk-start.sh +172 -0
  37. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  38. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  39. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  40. package/.agents/backend/fm-backend.sh +981 -0
  41. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  42. package/.agents/backend/fm-backlog-receive.sh +187 -0
  43. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  44. package/.agents/backend/fm-bearings-board.sh +204 -0
  45. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  46. package/.agents/backend/fm-bootstrap.sh +1568 -0
  47. package/.agents/backend/fm-branch-outcome.sh +203 -0
  48. package/.agents/backend/fm-branch-prompt.sh +101 -0
  49. package/.agents/backend/fm-brief.sh +452 -0
  50. package/.agents/backend/fm-busy-event.sh +234 -0
  51. package/.agents/backend/fm-busy-lib.sh +986 -0
  52. package/.agents/backend/fm-captain-hold.sh +1001 -0
  53. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  54. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  55. package/.agents/backend/fm-check-lib.sh +74 -0
  56. package/.agents/backend/fm-check-register.sh +42 -0
  57. package/.agents/backend/fm-check-unregister.sh +52 -0
  58. package/.agents/backend/fm-classify-lib.sh +1770 -0
  59. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  60. package/.agents/backend/fm-composer-lib.sh +1416 -0
  61. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  62. package/.agents/backend/fm-config-push.sh +254 -0
  63. package/.agents/backend/fm-control-lib.sh +251 -0
  64. package/.agents/backend/fm-control.sh +878 -0
  65. package/.agents/backend/fm-crew-state.sh +625 -0
  66. package/.agents/backend/fm-cursor-lib.sh +243 -0
  67. package/.agents/backend/fm-decision-hold.sh +232 -0
  68. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  69. package/.agents/backend/fm-dod-lib.sh +67 -0
  70. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  71. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  72. package/.agents/backend/fm-extension.mjs +2577 -0
  73. package/.agents/backend/fm-extension.sh +16 -0
  74. package/.agents/backend/fm-ff-lib.sh +420 -0
  75. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  76. package/.agents/backend/fm-fleet-sync.sh +458 -0
  77. package/.agents/backend/fm-fleet-view.sh +96 -0
  78. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  79. package/.agents/backend/fm-guard.sh +242 -0
  80. package/.agents/backend/fm-harness.sh +195 -0
  81. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  82. package/.agents/backend/fm-herdr-lab.sh +346 -0
  83. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  84. package/.agents/backend/fm-home-seed.sh +984 -0
  85. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  86. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  87. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  88. package/.agents/backend/fm-inbox.sh +399 -0
  89. package/.agents/backend/fm-install-actionlint.sh +84 -0
  90. package/.agents/backend/fm-install-herdr.sh +96 -0
  91. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  92. package/.agents/backend/fm-install-treehouse.sh +95 -0
  93. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  94. package/.agents/backend/fm-lease-lib.sh +218 -0
  95. package/.agents/backend/fm-lease.sh +189 -0
  96. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  97. package/.agents/backend/fm-lint-workflows.sh +137 -0
  98. package/.agents/backend/fm-lint.sh +591 -0
  99. package/.agents/backend/fm-lock-lib.sh +104 -0
  100. package/.agents/backend/fm-lock.sh +107 -0
  101. package/.agents/backend/fm-marker-lib.sh +12 -0
  102. package/.agents/backend/fm-merge-local.sh +74 -0
  103. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  104. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  105. package/.agents/backend/fm-on.sh +125 -0
  106. package/.agents/backend/fm-operational-input.sh +253 -0
  107. package/.agents/backend/fm-peek.sh +46 -0
  108. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  109. package/.agents/backend/fm-pr-check.sh +135 -0
  110. package/.agents/backend/fm-pr-lib.sh +1016 -0
  111. package/.agents/backend/fm-pr-merge.sh +707 -0
  112. package/.agents/backend/fm-pr-poll.sh +110 -0
  113. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  114. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  115. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  116. package/.agents/backend/fm-procevent-lib.sh +892 -0
  117. package/.agents/backend/fm-procevent-quota.sh +290 -0
  118. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  119. package/.agents/backend/fm-procevent-when.sh +504 -0
  120. package/.agents/backend/fm-procevent.sh +1466 -0
  121. package/.agents/backend/fm-project-mode.sh +92 -0
  122. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  123. package/.agents/backend/fm-promote.sh +282 -0
  124. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  125. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  126. package/.agents/backend/fm-public-followup.sh +1330 -0
  127. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  128. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  129. package/.agents/backend/fm-quota-choose.sh +384 -0
  130. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  131. package/.agents/backend/fm-remote-doctor.sh +800 -0
  132. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  133. package/.agents/backend/fm-remote-file.sh +234 -0
  134. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  135. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  136. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  137. package/.agents/backend/fm-remote-inherit.sh +179 -0
  138. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  139. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  140. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  141. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  142. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  143. package/.agents/backend/fm-review-diff.sh +158 -0
  144. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  145. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  146. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  147. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  148. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  149. package/.agents/backend/fm-secondmate-report.sh +88 -0
  150. package/.agents/backend/fm-send.sh +1059 -0
  151. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  152. package/.agents/backend/fm-session-start.sh +970 -0
  153. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  154. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  155. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  156. package/.agents/backend/fm-spawn.sh +3153 -0
  157. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  158. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  159. package/.agents/backend/fm-startup-network.sh +642 -0
  160. package/.agents/backend/fm-stow-cascade.sh +251 -0
  161. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  162. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  163. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  164. package/.agents/backend/fm-supervision-lib.sh +90 -0
  165. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  166. package/.agents/backend/fm-tangle-lib.sh +53 -0
  167. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  168. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  169. package/.agents/backend/fm-teardown.sh +2922 -0
  170. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  171. package/.agents/backend/fm-test-run.sh +2191 -0
  172. package/.agents/backend/fm-timeout-lib.sh +141 -0
  173. package/.agents/backend/fm-timing-lib.sh +179 -0
  174. package/.agents/backend/fm-tmux-lib.sh +291 -0
  175. package/.agents/backend/fm-tool-update-check.sh +898 -0
  176. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  177. package/.agents/backend/fm-transition-lib.sh +103 -0
  178. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  179. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  180. package/.agents/backend/fm-turnend-guard.sh +432 -0
  181. package/.agents/backend/fm-update.sh +110 -0
  182. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  183. package/.agents/backend/fm-voice-client.py +1373 -0
  184. package/.agents/backend/fm-voice-relay.py +1256 -0
  185. package/.agents/backend/fm-wake-drain.sh +592 -0
  186. package/.agents/backend/fm-wake-grant.sh +127 -0
  187. package/.agents/backend/fm-wake-lib.sh +1849 -0
  188. package/.agents/backend/fm-watch-arm.sh +602 -0
  189. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  190. package/.agents/backend/fm-watch.sh +1962 -0
  191. package/.agents/backend/fm-x-dismiss.sh +116 -0
  192. package/.agents/backend/fm-x-followup.sh +286 -0
  193. package/.agents/backend/fm-x-lib.sh +1000 -0
  194. package/.agents/backend/fm-x-link.sh +234 -0
  195. package/.agents/backend/fm-x-poll.sh +185 -0
  196. package/.agents/backend/fm-x-reply.sh +385 -0
  197. package/.agents/backend/fm_voice_frame.py +166 -0
  198. package/.agents/backend/fm_voice_records.py +574 -0
  199. package/.agents/backend/model-bridge.py +360 -0
  200. package/.agents/backend/opencode-go-bridge.py +30 -0
  201. package/.agents/bus/README.md +12 -0
  202. package/.agents/bus/messages.json +1 -0
  203. package/.agents/config/agents.machine.example.yaml +16 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +11 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/filebrowser/.filebrowser.json +6 -0
  211. package/.agents/filebrowser/README.md +11 -0
  212. package/.agents/gateway/README.md +12 -0
  213. package/.agents/github/README.md +15 -0
  214. package/.agents/github/webhooks/.gitkeep +0 -0
  215. package/.agents/github/workflows/.gitkeep +0 -0
  216. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  217. package/.agents/harness/opencode/plugins/package.json +4 -0
  218. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  219. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  220. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  221. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  222. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  223. package/.agents/memory/README.md +46 -0
  224. package/.agents/memory/runes_audit.md +5 -0
  225. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  226. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  227. package/.agents/migrations/0003-private-data-separation.sh +121 -0
  228. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  229. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  230. package/.agents/sandbox/Dockerfile.utgard +19 -0
  231. package/.agents/sandbox/README.md +14 -0
  232. package/.agents/sandbox/utgard.config.json +14 -0
  233. package/.agents/skills/README.md +59 -0
  234. package/.agents/skills/bragi-marketing/README.md +3 -0
  235. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  236. package/.agents/skills/eindri-homes/README.md +3 -0
  237. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  238. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  239. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  240. package/.agents/skills/frigg-consent/README.md +3 -0
  241. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  242. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  243. package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
  244. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  245. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  246. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  247. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  248. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
  276. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  277. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  278. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  279. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
  280. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  281. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  283. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  284. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  285. package/.agents/skills/groa-update/SKILL.md +90 -0
  286. package/.agents/skills/hamr-adapters/README.md +3 -0
  287. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  288. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  289. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  290. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  291. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  292. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  293. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  294. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  295. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  296. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  297. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  298. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  299. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  300. package/.agents/skills/herdr-panes/README.md +3 -0
  301. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  302. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  303. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  304. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  305. package/.agents/skills/hnoss-design/README.md +3 -0
  306. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  307. package/.agents/skills/hvild-afk/README.md +3 -0
  308. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  309. package/.agents/skills/jord-projects/README.md +3 -0
  310. package/.agents/skills/jord-projects/SKILL.md +92 -0
  311. package/.agents/skills/lifecycle/SKILL.md +56 -0
  312. package/.agents/skills/lifecycle/smoke_test.sh +57 -0
  313. package/.agents/skills/lifecycle/start.sh +21 -0
  314. package/.agents/skills/lifecycle/status.sh +35 -0
  315. package/.agents/skills/lifecycle/stop.sh +16 -0
  316. package/.agents/skills/muninn-stow/README.md +3 -0
  317. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  318. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  319. package/.agents/skills/nornir-schedule/README.md +3 -0
  320. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  321. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  322. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  323. package/.agents/skills/nsr-compliance/README.md +3 -0
  324. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  325. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  326. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  327. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  328. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  329. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  431. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  432. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  433. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  434. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  435. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  453. package/.agents/skills/pr-ops/README.md +3 -0
  454. package/.agents/skills/pr-ops/SKILL.md +109 -0
  455. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  456. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  457. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  458. package/.agents/skills/saga-bearings/README.md +3 -0
  459. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  460. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  461. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  462. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  463. package/.agents/skills/syn-recovery/README.md +3 -0
  464. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  465. package/.agents/skills/tyr-check/README.md +3 -0
  466. package/.agents/skills/tyr-check/SKILL.md +218 -0
  467. package/.agents/skills/urdh-hold/README.md +3 -0
  468. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  469. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  470. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  471. package/.agents/skills/vor-diagnostics/README.md +3 -0
  472. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  473. package/.agents/skills/ymir-host/README.md +3 -0
  474. package/.agents/skills/ymir-host/SKILL.md +35 -0
  475. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  476. package/.agents/skills/ymir-host/assets/install.md +69 -0
  477. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  478. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  479. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  480. package/.agents/skills/ymir-host/assets/update.md +64 -0
  481. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  482. package/.agents/tests/cmux-test-safety.sh +51 -0
  483. package/.agents/tests/fixtures.sh +291 -0
  484. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  485. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  486. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  487. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  488. package/.agents/tests/fm-afk-return.test.sh +283 -0
  489. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  490. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  491. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  492. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  493. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  494. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  495. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  496. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  497. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  498. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  499. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  500. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  501. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  502. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  503. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  504. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  505. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  506. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  507. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  508. package/.agents/tests/fm-backend.test.sh +1142 -0
  509. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  510. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  511. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  512. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  513. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  514. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  515. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  516. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  517. package/.agents/tests/fm-brief.test.sh +785 -0
  518. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  519. package/.agents/tests/fm-busy-state.test.sh +465 -0
  520. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  521. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  522. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  523. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  524. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  525. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  526. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  527. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  528. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  529. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  530. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  531. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  532. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  533. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  534. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  535. package/.agents/tests/fm-control.test.sh +910 -0
  536. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  537. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  538. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  539. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  540. package/.agents/tests/fm-daemon.test.sh +2740 -0
  541. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  542. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  543. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  544. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  545. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  546. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  547. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  548. package/.agents/tests/fm-gotmp.test.sh +230 -0
  549. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  550. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  551. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  552. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  553. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  554. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  555. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  556. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  557. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  558. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  559. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  560. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  561. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  562. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  563. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  564. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  565. package/.agents/tests/fm-lint.test.sh +1023 -0
  566. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  567. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  568. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  569. package/.agents/tests/fm-on.test.sh +507 -0
  570. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  571. package/.agents/tests/fm-operational-input.test.sh +160 -0
  572. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  573. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  574. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  575. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  576. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  577. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  578. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  579. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  580. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  581. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  582. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  583. package/.agents/tests/fm-procevent.test.sh +1745 -0
  584. package/.agents/tests/fm-project-origin.test.sh +110 -0
  585. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  586. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  587. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  588. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  589. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  590. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  591. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  592. package/.agents/tests/fm-remote-job.test.sh +768 -0
  593. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  594. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  595. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  596. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  597. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  598. package/.agents/tests/fm-review-diff.test.sh +176 -0
  599. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  600. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  601. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  602. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  603. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  604. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  605. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  606. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  607. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  608. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  609. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  610. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  611. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  612. package/.agents/tests/fm-send-settle.test.sh +147 -0
  613. package/.agents/tests/fm-send-strict.test.sh +241 -0
  614. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  615. package/.agents/tests/fm-session-start.test.sh +2515 -0
  616. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  617. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  618. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  619. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  620. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  621. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  622. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  623. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  624. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  625. package/.agents/tests/fm-startup-network.test.sh +712 -0
  626. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  627. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  628. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  629. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  630. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  631. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  632. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  633. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  634. package/.agents/tests/fm-teardown.test.sh +2665 -0
  635. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  636. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  637. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  638. package/.agents/tests/fm-test-run.test.sh +1196 -0
  639. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  640. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  641. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  642. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  643. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  644. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  645. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  646. package/.agents/tests/fm-update.test.sh +304 -0
  647. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  648. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  649. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  650. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  651. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  652. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  653. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  654. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  655. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  656. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  657. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  658. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  659. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  660. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  661. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  662. package/.agents/tests/herdr-test-safety.sh +42 -0
  663. package/.agents/tests/lib.sh +315 -0
  664. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  665. package/.agents/tests/secondmate-helpers.sh +193 -0
  666. package/.agents/tests/smoke.test.sh +93 -0
  667. package/.agents/tests/wake-helpers.sh +338 -0
  668. package/.agents/tests/zellij-test-safety.sh +60 -0
  669. package/.agents/tools/README.md +10 -0
  670. package/.agents/tools/bin/tasks-cli +267 -0
  671. package/.agents/tools/tasks-cli.ts +216 -0
  672. package/AGENTS.md +498 -0
  673. package/CHANGELOG.md +1913 -0
  674. package/LICENSE +1 -1
  675. package/NOTICE +63 -0
  676. package/README.md +114 -60
  677. package/RULES/01-domains.md +51 -0
  678. package/RULES/02-agents.md +78 -0
  679. package/RULES/03-houses.md +61 -0
  680. package/RULES/04-hoard.md +97 -0
  681. package/RULES/05-platforms.md +67 -0
  682. package/RULES/06-append-only.md +108 -0
  683. package/RULES/07-config.md +101 -0
  684. package/RULES/08-delivery-gate.md +75 -0
  685. package/RULES/09-electron.md +58 -0
  686. package/RULES/README.md +22 -0
  687. package/bin/README.md +107 -0
  688. package/bin/a2a-mcp.sh +133 -0
  689. package/bin/a2a-serve.py +103 -0
  690. package/bin/a2a-serve.sh +8 -0
  691. package/bin/a2a-talk.sh +50 -0
  692. package/bin/a2abridge-ensure.sh +108 -0
  693. package/bin/agent-run.sh +43 -0
  694. package/bin/agents-config.sh +256 -0
  695. package/bin/apodex-smoke-test.sh +105 -0
  696. package/bin/bifrost-bridge.sh +131 -0
  697. package/bin/bifrost-ingress.sh +77 -0
  698. package/bin/bootstrap-macos.sh +112 -0
  699. package/bin/bootstrap-windows.ps1 +112 -0
  700. package/bin/branch-guard.sh +64 -0
  701. package/bin/brokk +73 -0
  702. package/bin/brokk-classify-lib.sh +1770 -0
  703. package/bin/brokk-lease-lib.sh +222 -0
  704. package/bin/brokk-lease.sh +193 -0
  705. package/bin/brokk-lint.sh +65 -0
  706. package/bin/brokk-send.sh +51 -0
  707. package/bin/brokk-update.sh +9 -0
  708. package/bin/brokk-wake-grant.sh +127 -0
  709. package/bin/brokk-wake-lib.sh +1849 -0
  710. package/bin/changelog-assemble.sh +154 -0
  711. package/bin/changelog-guard.sh +197 -0
  712. package/bin/crash-sense.sh +173 -0
  713. package/bin/design-check.sh +80 -0
  714. package/bin/design-icon.sh +153 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +225 -0
  718. package/bin/eindri-acclaim.sh +47 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seat.sh +83 -0
  722. package/bin/eindri-seen.sh +54 -0
  723. package/bin/eindri-send.sh +15 -0
  724. package/bin/eindri-start.sh +98 -0
  725. package/bin/eindri-watch.sh +57 -0
  726. package/bin/einherjar-spawn.sh +576 -0
  727. package/bin/eir-doctor.sh +158 -0
  728. package/bin/erindi-brief.sh +271 -0
  729. package/bin/feature-inventory.sh +73 -0
  730. package/bin/fleet-apply.sh +81 -0
  731. package/bin/github-deploy.sh +94 -0
  732. package/bin/gjallarhorn-expose.sh +136 -0
  733. package/bin/gjallarhorn-notify.sh +71 -0
  734. package/bin/gjallarhorn-purge.sh +67 -0
  735. package/bin/gjallarhorn-tunnel.sh +85 -0
  736. package/bin/gleipnir-lock-lib.sh +284 -0
  737. package/bin/groa-update.sh +130 -0
  738. package/bin/hall-snapshot.sh +111 -0
  739. package/bin/hamr-harness.sh +246 -0
  740. package/bin/heimdall-ssh-keys.sh +374 -0
  741. package/bin/herdr-agents.py +44 -0
  742. package/bin/herdr-ensure.sh +104 -0
  743. package/bin/herdr-run.sh +452 -0
  744. package/bin/hermes-ensure.sh +88 -0
  745. package/bin/hlidskjalf-agents.sh +172 -0
  746. package/bin/hlidskjalf-usage.sh +196 -0
  747. package/bin/hoard-guard.sh +135 -0
  748. package/bin/hoard-lib.sh +105 -0
  749. package/bin/hodd.sh +102 -0
  750. package/bin/host-sense.sh +199 -0
  751. package/bin/huginn-research-worker.sh +121 -0
  752. package/bin/local-model-lock.sh +70 -0
  753. package/bin/mimir-bridge.py +259 -0
  754. package/bin/mimir-bridge.sh +100 -0
  755. package/bin/mimir-ingest.sh +131 -0
  756. package/bin/mimir-reflect.sh +27 -0
  757. package/bin/mimir.sh +113 -0
  758. package/bin/mjollnir-webhook.sh +72 -0
  759. package/bin/mjollnir.sh +104 -0
  760. package/bin/model-hardware.sh +96 -0
  761. package/bin/model-resolve.sh +160 -0
  762. package/bin/models-detect.sh +178 -0
  763. package/bin/nornir-cron-start.sh +162 -0
  764. package/bin/nornir-job-daily-briefing.sh +191 -0
  765. package/bin/nornir-job-git-sync.sh +152 -0
  766. package/bin/nornir-job-hall-snapshot.sh +28 -0
  767. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  768. package/bin/nornir-job-nsr-compliance.sh +54 -0
  769. package/bin/nornir-job-observer.sh +166 -0
  770. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  771. package/bin/npm-publish.sh +136 -0
  772. package/bin/omarchy-hook-install.sh +60 -0
  773. package/bin/omarchy-install.sh +161 -0
  774. package/bin/omarchy-plugins.sh +187 -0
  775. package/bin/omarchy-sense.sh +162 -0
  776. package/bin/perm-guard.sh +30 -0
  777. package/bin/pi-agent.sh +68 -0
  778. package/bin/pi-ensure.sh +77 -0
  779. package/bin/pi-local.sh +37 -0
  780. package/bin/pi-model-agnostic.sh +71 -0
  781. package/bin/pi-seat.sh +63 -0
  782. package/bin/prereq-ensure.sh +166 -0
  783. package/bin/private-guard.sh +58 -0
  784. package/bin/project-git.sh +69 -0
  785. package/bin/public-guard.sh +71 -0
  786. package/bin/ratatoskr.sh +49 -0
  787. package/bin/realm-lib.sh +32 -0
  788. package/bin/repo-scrub.sh +62 -0
  789. package/bin/rodd-operational-input.sh +250 -0
  790. package/bin/runes-append.sh +198 -0
  791. package/bin/saga-session-start.sh +172 -0
  792. package/bin/saga-sessionstart-run.sh +79 -0
  793. package/bin/saga-wake-drain.sh +41 -0
  794. package/bin/secret-guard.sh +63 -0
  795. package/bin/sessrumnir-ensure.sh +100 -0
  796. package/bin/sessrumnir-sync.sh +237 -0
  797. package/bin/sessrumnir.sh +104 -0
  798. package/bin/skillopt-setup.sh +76 -0
  799. package/bin/skuld-branch-outcome.sh +203 -0
  800. package/bin/skuld-branch-prompt.sh +101 -0
  801. package/bin/smidja-bootstrap.sh +94 -0
  802. package/bin/smidja-observe.sh +86 -0
  803. package/bin/syn-arm-pretool-check.sh +57 -0
  804. package/bin/syn-asset-pretool-check.sh +89 -0
  805. package/bin/syn-cd-pretool-check.sh +28 -0
  806. package/bin/syn-guard-pretool-check.sh +83 -0
  807. package/bin/syn-spawn-pretool-check.sh +58 -0
  808. package/bin/syn-turnend-guard.sh +36 -0
  809. package/bin/syn-watch-arm.sh +99 -0
  810. package/bin/tailscale-sync.sh +203 -0
  811. package/bin/telegram-bot.sh +82 -0
  812. package/bin/toolchain.sh +74 -0
  813. package/bin/ui-truth-check.sh +75 -0
  814. package/bin/utgard.sh +111 -0
  815. package/bin/valhalla.sh +115 -0
  816. package/bin/valknut-load.sh +298 -0
  817. package/bin/vor-crew-state.sh +124 -0
  818. package/bin/wedge-notify.sh +60 -0
  819. package/bin/workspace-provision.sh +80 -0
  820. package/bin/workspace-rag.sh +163 -0
  821. package/bin/wyrd-db.sh +47 -0
  822. package/bin/yggdrasil.sh +152 -0
  823. package/bin/ymir-edit.sh +145 -0
  824. package/bin/ymir-install.sh +806 -0
  825. package/bin/ymir-invite.sh +93 -0
  826. package/bin/ymir-isolation.sh +88 -0
  827. package/bin/ymir-migrate.sh +61 -0
  828. package/bin/ymir-plan.sh +356 -0
  829. package/bin/ymir-platform.sh +348 -0
  830. package/bin/ymir-say.sh +110 -0
  831. package/bin/ymir-setup-auth.sh +119 -0
  832. package/bin/ymir-validate.sh +193 -0
  833. package/bin/ymir.js +74 -16
  834. package/docs/Architecture.md +339 -0
  835. package/docs/design.md +351 -0
  836. package/docs/installations/README.md +63 -0
  837. package/docs/installations/ymir-home.md +93 -0
  838. package/docs/integration.md +105 -0
  839. package/docs/lore.md +1060 -0
  840. package/docs/research/agent-harnesses.md +290 -0
  841. package/docs/runbooks/README.md +19 -0
  842. package/docs/runbooks/a2a.md +91 -0
  843. package/docs/runbooks/agent-permissions.md +99 -0
  844. package/docs/runbooks/agents.md +57 -0
  845. package/docs/runbooks/models.md +69 -0
  846. package/docs/runbooks/realm-onboarding.md +60 -0
  847. package/docs/runbooks/secrets-and-hoard.md +60 -0
  848. package/docs/runbooks/updates-and-migrations.md +110 -0
  849. package/docs/runbooks/worktrees-isolation.md +72 -0
  850. package/docs/session-start.md +105 -0
  851. package/docs/skillopt-integration.md +321 -0
  852. package/docs/sync-system.md +66 -0
  853. package/docs/workspaces.md +104 -0
  854. package/docs/ymir-rut.md +260 -0
  855. package/package.json +54 -14
  856. package/packaging/build.sh +74 -0
  857. package/packaging/macos/Ymir Installer.command +43 -0
  858. package/packaging/windows/ymir-setup.nsi +74 -0
  859. package/scripts/README.md +22 -0
  860. package/scripts/electron.sh +269 -0
  861. package/scripts/lower.sh +34 -0
  862. package/scripts/raise.sh +42 -0
  863. package/scripts/start.sh +217 -0
  864. package/scripts/stop.sh +113 -0
  865. package/install.sh +0 -35
@@ -0,0 +1,1124 @@
1
+ #!/bin/bash
2
+ # Long-lived per-account worker for remote fm-on jobs.
3
+ #
4
+ # This process is launched by the Firstmate-owned dev.firstmate.remote-job
5
+ # LaunchAgent on macOS and by a detached restart supervisor on Linux. It claims
6
+ # only complete 0700 records staged by fm-remote-job-lib.sh under the fixed
7
+ # account queue, refuses symlinks and malformed records, and executes only a
8
+ # tracked non-symlink fm-*.sh under this worker's configured FM_ROOT/bin.
9
+ #
10
+ # Each child runs under env -i with the shared filesystem-composed PATH, HOME,
11
+ # FM_HOME, FM_ROOT_OVERRIDE, and FM_REMOTE_JOB_ACTIVE=1. Commands receive their
12
+ # captured stdin and have a 360-second default timeout. Their stdout and stderr
13
+ # are independently constrained to the job library's 1048576-byte bound. A
14
+ # record is marked done only after its bounded outputs and numeric exit status
15
+ # have been committed. The library header owns the exact record fields and
16
+ # lifecycle.
17
+ #
18
+ # The shared library header owns lane selection, FIFO, and caller-cancellation
19
+ # contracts. This serving loop implements each active lane as a tracked,
20
+ # top-level --lane process that claims one job, records itself as the claim's
21
+ # supervisor, and runs it to publication. Shutdown stops every tracked lane and
22
+ # its recorded command group, leaving interrupted records for the replacement
23
+ # worker's orphan recovery.
24
+ #
25
+ # The worker is abandoned when its configured FM_ROOT stops being a genuine
26
+ # Firstmate checkout - the state a pruned no-mistakes gate worktree, a returned
27
+ # pooled worktree, or a removed test fixture root leaves behind. It can never
28
+ # validate or execute another job from a root that is gone, so both the serving
29
+ # loop and the Linux restart supervisor stop instead of polling forever
30
+ # reparented to init. FM_REMOTE_JOB_ORPHAN_GRACE_SECONDS is how long the root
31
+ # must stay missing before that counts, so an ordinary transient never stops a
32
+ # healthy worker. The supervisor additionally bounds how many times it restarts
33
+ # a failing child, whether or not the failures are immediate: it backs off
34
+ # between immediate failures up to
35
+ # FM_REMOTE_JOB_SUPERVISOR_MAX_BACKOFF_SECONDS and gives up after
36
+ # FM_REMOTE_JOB_SUPERVISOR_MAX_RESTARTS failed children in total, since a
37
+ # restart loop only burns CPU and grows its log without bound. A child that
38
+ # stays up for FM_REMOTE_JOB_SUPERVISOR_HEALTHY_SECONDS clears the
39
+ # consecutive-failure backoff, but not that total restart guard, so a child
40
+ # that dies just past the healthy threshold cannot restart without bound
41
+ # either. fm-on's ensure path restarts a worker that gave up.
42
+ set -u
43
+
44
+ # A non-numeric override falls back to the default rather than crashing the
45
+ # arithmetic that bounds these loops.
46
+ worker_bounded_setting() { # <value> <default>
47
+ case "$1" in ''|*[!0-9]*) printf '%s\n' "$2" ;; *) printf '%s\n' "$1" ;; esac
48
+ }
49
+ FM_REMOTE_JOB_ORPHAN_GRACE_SECONDS=$(worker_bounded_setting "${FM_REMOTE_JOB_ORPHAN_GRACE_SECONDS:-}" 5)
50
+ FM_REMOTE_JOB_SUPERVISOR_MAX_RESTARTS=$(worker_bounded_setting "${FM_REMOTE_JOB_SUPERVISOR_MAX_RESTARTS:-}" 20)
51
+ FM_REMOTE_JOB_SUPERVISOR_MAX_BACKOFF_SECONDS=$(worker_bounded_setting "${FM_REMOTE_JOB_SUPERVISOR_MAX_BACKOFF_SECONDS:-}" 5)
52
+ FM_REMOTE_JOB_SUPERVISOR_HEALTHY_SECONDS=$(worker_bounded_setting "${FM_REMOTE_JOB_SUPERVISOR_HEALTHY_SECONDS:-}" 10)
53
+
54
+ SCRIPT_DIR=$(CDPATH='' cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
55
+ FM_ROOT=${FM_ROOT_OVERRIDE:-$(CDPATH='' cd "$SCRIPT_DIR/.." && pwd -P)}
56
+
57
+ # shellcheck source=bin/fm-remote-job-lib.sh
58
+ . "$SCRIPT_DIR/fm-remote-job-lib.sh"
59
+
60
+ WORKER_LOCK=
61
+ WORKER_LOCK_HELD=0
62
+ WORKER_RELEASE_OWNERSHIP=1
63
+ WORKER_SUPERVISED_PID=
64
+ WORKER_PREEMPTIBLE=0
65
+ WORKER_PREEMPTED=0
66
+ WORKER_LANE_HOME=
67
+ WORKER_LANE_HOMES=()
68
+ WORKER_LANE_PIDS=()
69
+ WORKER_LANE_STARTS=()
70
+ WORKER_LANE_JOBS=()
71
+
72
+ worker_error() { printf 'remote-job-worker: %s\n' "$1" >&2; }
73
+
74
+ worker_account_home() {
75
+ local home=${HOME:-}
76
+ if [ -n "$home" ]; then
77
+ fm_remote_job_canonical_existing_dir "$home" && return 0
78
+ fi
79
+ unset HOME
80
+ CDPATH='' cd ~ 2>/dev/null && pwd -P
81
+ }
82
+
83
+ worker_write_heartbeat() {
84
+ local ready tmp
85
+ ready=$(fm_remote_job_worker_ready_path)
86
+ tmp=$(umask 077; mktemp "$FM_REMOTE_JOB_STATE/.ready.XXXXXX") || return 1
87
+ printf '%s\n' "${BASHPID:-$$}" > "$tmp" || { rm -f -- "$tmp"; return 1; }
88
+ chmod 600 "$tmp" || { rm -f -- "$tmp"; return 1; }
89
+ mv -f -- "$tmp" "$ready"
90
+ }
91
+
92
+ worker_publish_pid() {
93
+ local pid_file tmp
94
+ pid_file=$(fm_remote_job_worker_pid_path)
95
+ tmp=$(umask 077; mktemp "$FM_REMOTE_JOB_STATE/.pid.XXXXXX") || return 1
96
+ printf '%s\n' "${BASHPID:-$$}" > "$tmp" || { rm -f -- "$tmp"; return 1; }
97
+ chmod 600 "$tmp" || { rm -f -- "$tmp"; return 1; }
98
+ mv -f -- "$tmp" "$pid_file"
99
+ }
100
+
101
+ worker_publish_identity() {
102
+ local account_home=$1 identity identity_file tmp
103
+ identity=$(fm_remote_job_code_identity "$FM_ROOT" "$account_home") || return 1
104
+ identity_file=$(fm_remote_job_worker_identity_path)
105
+ tmp=$(umask 077; mktemp "$FM_REMOTE_JOB_STATE/.identity.XXXXXX") || return 1
106
+ printf '%s\n' "$identity" > "$tmp" || { rm -f -- "$tmp"; return 1; }
107
+ chmod 600 "$tmp" || { rm -f -- "$tmp"; return 1; }
108
+ mv -f -- "$tmp" "$identity_file"
109
+ }
110
+
111
+ worker_publish_lock_owner() {
112
+ local pid start command pid_tmp start_tmp command_tmp
113
+ pid=${BASHPID:-$$}
114
+ start=$(fm_remote_job_process_start "$pid") || return 1
115
+ command=$(fm_remote_job_process_command "$pid") || return 1
116
+ pid_tmp=$(umask 077; mktemp "$WORKER_LOCK/.pid.XXXXXX") || return 1
117
+ start_tmp=$(umask 077; mktemp "$WORKER_LOCK/.start.XXXXXX") || { rm -f -- "$pid_tmp"; return 1; }
118
+ command_tmp=$(umask 077; mktemp "$WORKER_LOCK/.command.XXXXXX") || { rm -f -- "$pid_tmp" "$start_tmp"; return 1; }
119
+ printf '%s\n' "$pid" > "$pid_tmp" || { rm -f -- "$pid_tmp" "$start_tmp" "$command_tmp"; return 1; }
120
+ printf '%s\n' "$start" > "$start_tmp" || { rm -f -- "$pid_tmp" "$start_tmp" "$command_tmp"; return 1; }
121
+ printf '%s\n' "$command" > "$command_tmp" || { rm -f -- "$pid_tmp" "$start_tmp" "$command_tmp"; return 1; }
122
+ chmod 600 "$pid_tmp" "$start_tmp" "$command_tmp" || { rm -f -- "$pid_tmp" "$start_tmp" "$command_tmp"; return 1; }
123
+ mv -f -- "$command_tmp" "$WORKER_LOCK/command" || { rm -f -- "$pid_tmp" "$start_tmp" "$command_tmp"; return 1; }
124
+ mv -f -- "$start_tmp" "$WORKER_LOCK/start" || { rm -f -- "$pid_tmp" "$start_tmp" "$WORKER_LOCK/command"; return 1; }
125
+ mv -f -- "$pid_tmp" "$WORKER_LOCK/pid" || { rm -f -- "$pid_tmp" "$WORKER_LOCK/start" "$WORKER_LOCK/command"; return 1; }
126
+ }
127
+
128
+ worker_lock_recent() {
129
+ local mtime now
130
+ mtime=$(fm_remote_job_path_mtime "$WORKER_LOCK" 2>/dev/null || true)
131
+ case "$mtime" in ''|*[!0-9]*) return 0 ;; esac
132
+ now=$(date +%s)
133
+ [ $((now - mtime)) -le 10 ]
134
+ }
135
+
136
+ worker_quarantined_execution_stopped() { # <account-home>
137
+ local account_home=$1 job state kind file pid
138
+ fm_remote_job_regular_bounded "$WORKER_LOCK/quarantine" 256 || return 1
139
+ fm_remote_job_lock_owner_matches_process "$account_home" && return 1
140
+ for job in "$FM_REMOTE_JOB_JOBS"/job-*; do
141
+ [ -d "$job" ] && [ ! -L "$job" ] || continue
142
+ state=$(fm_remote_job_read_state "$job" 2>/dev/null || true)
143
+ [ "$state" = running ] || continue
144
+ for kind in process group; do
145
+ case "$kind" in process) file="$job/.claim/supervisor" ;; group) file="$job/.claim/group" ;; esac
146
+ [ ! -e "$file" ] && [ ! -L "$file" ] && continue
147
+ [ ! -L "$file" ] || return 1
148
+ pid=$(worker_read_process_id "$file") || return 1
149
+ worker_recorded_execution_alive "$job" "$kind" "$pid" && return 1
150
+ done
151
+ done
152
+ }
153
+
154
+ worker_recover_quarantine() { # <account-home>
155
+ worker_quarantined_execution_stopped "$1" || return 1
156
+ [ ! -L "$WORKER_LOCK/quarantine" ] || return 1
157
+ rm -f -- "$WORKER_LOCK/quarantine"
158
+ }
159
+
160
+ worker_acquire_lock() {
161
+ local account_home=$1 attempt=0
162
+ while [ "$attempt" -lt 150 ]; do
163
+ if (umask 077; mkdir "$WORKER_LOCK") 2>/dev/null; then
164
+ WORKER_LOCK_HELD=1
165
+ worker_publish_lock_owner || return 1
166
+ return 0
167
+ fi
168
+ [ -d "$WORKER_LOCK" ] && [ ! -L "$WORKER_LOCK" ] || return 1
169
+ if [ -e "$WORKER_LOCK/quarantine" ] || [ -L "$WORKER_LOCK/quarantine" ]; then
170
+ worker_recover_quarantine "$account_home" || return 3
171
+ continue
172
+ fi
173
+ if fm_remote_job_lock_owner_matches_process "$account_home"; then return 2; fi
174
+ if fm_remote_job_probe "$account_home" || worker_lock_recent; then
175
+ attempt=$((attempt + 1))
176
+ sleep 0.1
177
+ continue
178
+ fi
179
+ [ ! -L "$WORKER_LOCK/pid" ] && [ ! -L "$WORKER_LOCK/start" ] && [ ! -L "$WORKER_LOCK/command" ] || return 1
180
+ rm -f -- "$WORKER_LOCK/pid" "$WORKER_LOCK/start" "$WORKER_LOCK/command" || return 1
181
+ rmdir "$WORKER_LOCK" || return 1
182
+ done
183
+ return 1
184
+ }
185
+
186
+ worker_publish_quarantine() {
187
+ local tmp
188
+ [ "$WORKER_LOCK_HELD" -eq 1 ] || return 1
189
+ tmp=$(umask 077; mktemp "$WORKER_LOCK/.quarantine.XXXXXX") || return 1
190
+ printf 'active execution could not be confirmed stopped\n' > "$tmp" || { rm -f -- "$tmp"; return 1; }
191
+ chmod 600 "$tmp" || { rm -f -- "$tmp"; return 1; }
192
+ mv -f -- "$tmp" "$WORKER_LOCK/quarantine"
193
+ }
194
+
195
+ worker_clear_quarantine() {
196
+ [ ! -L "$WORKER_LOCK/quarantine" ] || return 1
197
+ rm -f -- "$WORKER_LOCK/quarantine"
198
+ }
199
+
200
+ worker_cleanup() {
201
+ local pid_file ready identity owner_pid
202
+ [ "$WORKER_LOCK_HELD" -eq 1 ] && [ "$WORKER_RELEASE_OWNERSHIP" -eq 1 ] || return 0
203
+ owner_pid=$(fm_remote_job_read_single_line "$WORKER_LOCK/pid" 64 2>/dev/null || true)
204
+ if [ -z "$owner_pid" ]; then
205
+ [ ! -L "$WORKER_LOCK/start" ] && [ ! -L "$WORKER_LOCK/command" ] &&
206
+ rm -f -- "$WORKER_LOCK/start" "$WORKER_LOCK/command" 2>/dev/null || true
207
+ rmdir "$WORKER_LOCK" 2>/dev/null || true
208
+ WORKER_LOCK_HELD=0
209
+ return 0
210
+ fi
211
+ [ "$owner_pid" = "${BASHPID:-$$}" ] || return 0
212
+ pid_file=$(fm_remote_job_worker_pid_path)
213
+ ready=$(fm_remote_job_worker_ready_path)
214
+ identity=$(fm_remote_job_worker_identity_path)
215
+ [ ! -L "$pid_file" ] && rm -f -- "$pid_file" 2>/dev/null || true
216
+ [ ! -L "$ready" ] && rm -f -- "$ready" 2>/dev/null || true
217
+ [ ! -L "$identity" ] && rm -f -- "$identity" 2>/dev/null || true
218
+ rm -f -- "$WORKER_LOCK/pid" "$WORKER_LOCK/start" "$WORKER_LOCK/command" 2>/dev/null || true
219
+ rmdir "$WORKER_LOCK" 2>/dev/null || true
220
+ WORKER_LOCK_HELD=0
221
+ }
222
+
223
+ # The configured code root is gone and stayed gone across the grace window, so
224
+ # this worker has nothing left to serve. Confirming over the window keeps a
225
+ # momentary read during an ordinary checkout operation from stopping a healthy
226
+ # worker.
227
+ worker_code_root_abandoned() {
228
+ local waited=0
229
+ fm_remote_job_root_is_live "$FM_ROOT" && return 1
230
+ while [ "$waited" -lt "$FM_REMOTE_JOB_ORPHAN_GRACE_SECONDS" ]; do
231
+ sleep 1
232
+ waited=$((waited + 1))
233
+ fm_remote_job_root_is_live "$FM_ROOT" && return 1
234
+ done
235
+ return 0
236
+ }
237
+
238
+ worker_read_process_id() { # <file>
239
+ local file=$1 pid
240
+ fm_remote_job_regular_bounded "$file" 64 || return 1
241
+ pid=$(tr -d '\n' < "$file")
242
+ case "$pid" in ''|*[!0-9]*) return 1 ;; esac
243
+ [ "$pid" -gt 1 ] || return 1
244
+ printf '%s\n' "$pid"
245
+ }
246
+
247
+ worker_process_or_group_alive() { # process|group <pid>
248
+ case "$1" in
249
+ process) kill -0 "$2" 2>/dev/null ;;
250
+ group) kill -0 -- "-$2" 2>/dev/null ;;
251
+ *) return 1 ;;
252
+ esac
253
+ }
254
+
255
+ worker_signal_process_or_group() { # process|group <signal> <pid>
256
+ case "$1" in
257
+ process) kill "-$2" "$3" 2>/dev/null || true ;;
258
+ group) kill "-$2" -- "-$3" 2>/dev/null || true ;;
259
+ esac
260
+ }
261
+
262
+ worker_supervisor_identity_status() { # <job-dir> <pid>
263
+ local job=$1 pid=$2 recorded_start actual_start
264
+ recorded_start=$(fm_remote_job_read_single_line "$job/.claim/supervisor_start" 256 2>/dev/null) || return 2
265
+ actual_start=$(fm_remote_job_process_start "$pid" 2>/dev/null) || {
266
+ worker_process_or_group_alive process "$pid" && return 2
267
+ return 1
268
+ }
269
+ [ "$recorded_start" = "$actual_start" ] && return 0
270
+ return 1
271
+ }
272
+
273
+ # A leaderless live group still belongs to the recorded execution: its PGID
274
+ # cannot be reused while any old member survives, so it remains safe to signal.
275
+ # A live leader whose start identity mismatches proves PID reuse and makes the
276
+ # recorded group stale; an unreadable live leader stays indeterminate so the
277
+ # stop loop retries rather than signaling or declaring the group dead.
278
+ worker_group_identity_status() { # <job-dir> <pid>
279
+ local job=$1 pid=$2 recorded_start actual_start file="$1/.claim/group_start"
280
+ [ -e "$file" ] || [ -L "$file" ] || return 3
281
+ recorded_start=$(fm_remote_job_read_single_line "$file" 256 2>/dev/null) || return 2
282
+ actual_start=$(fm_remote_job_process_start "$pid" 2>/dev/null) || {
283
+ kill -0 "$pid" 2>/dev/null && return 2
284
+ worker_process_or_group_alive group "$pid" && return 0
285
+ return 1
286
+ }
287
+ [ "$recorded_start" = "$actual_start" ] && return 0
288
+ return 1
289
+ }
290
+
291
+ worker_recorded_execution_alive() { # <job-dir> process|group <pid>
292
+ local job=$1 kind=$2 pid=$3 identity_status
293
+ if [ "$kind" = process ]; then
294
+ worker_supervisor_identity_status "$job" "$pid"
295
+ identity_status=$?
296
+ case "$identity_status" in
297
+ 0) ;;
298
+ 1) return 1 ;;
299
+ 2) worker_process_or_group_alive process "$pid"; return ;;
300
+ esac
301
+ else
302
+ worker_group_identity_status "$job" "$pid"
303
+ identity_status=$?
304
+ case "$identity_status" in
305
+ 0|3) ;;
306
+ 1) return 1 ;;
307
+ 2) worker_process_or_group_alive group "$pid"; return ;;
308
+ esac
309
+ fi
310
+ worker_process_or_group_alive "$kind" "$pid"
311
+ }
312
+
313
+ worker_signal_recorded_execution() { # <job-dir> process|group <signal> <pid>
314
+ local job=$1 kind=$2 signal=$3 pid=$4 identity_status
315
+ if [ "$kind" = process ]; then
316
+ worker_supervisor_identity_status "$job" "$pid" || return 0
317
+ else
318
+ worker_group_identity_status "$job" "$pid"
319
+ identity_status=$?
320
+ case "$identity_status" in 0|3) ;; *) return 0 ;; esac
321
+ fi
322
+ worker_signal_process_or_group "$kind" "$signal" "$pid"
323
+ }
324
+
325
+ worker_stop_recorded_execution() { # <job-dir>
326
+ local job=$1 kind file pid attempt still_alive
327
+ for kind in process group; do
328
+ case "$kind" in process) file="$job/.claim/supervisor" ;; group) file="$job/.claim/group" ;; esac
329
+ [ ! -e "$file" ] && [ ! -L "$file" ] && continue
330
+ [ ! -L "$file" ] || return 1
331
+ pid=$(worker_read_process_id "$file") || return 1
332
+ worker_signal_recorded_execution "$job" "$kind" TERM "$pid"
333
+ worker_signal_recorded_execution "$job" "$kind" KILL "$pid"
334
+ wait "$pid" 2>/dev/null || true
335
+ done
336
+ attempt=0
337
+ while [ "$attempt" -lt 100 ]; do
338
+ attempt=$((attempt + 1))
339
+ still_alive=0
340
+ for kind in process group; do
341
+ case "$kind" in process) file="$job/.claim/supervisor" ;; group) file="$job/.claim/group" ;; esac
342
+ [ -e "$file" ] || continue
343
+ pid=$(worker_read_process_id "$file") || return 1
344
+ if worker_recorded_execution_alive "$job" "$kind" "$pid"; then
345
+ still_alive=1
346
+ worker_signal_recorded_execution "$job" "$kind" TERM "$pid"
347
+ worker_signal_recorded_execution "$job" "$kind" KILL "$pid"
348
+ fi
349
+ done
350
+ [ "$still_alive" -eq 1 ] || break
351
+ sleep 0.01
352
+ done
353
+ [ "$still_alive" -eq 0 ] || return 1
354
+ rm -f -- "$job/.claim/supervisor" "$job/.claim/supervisor_start" \
355
+ "$job/.claim/group" "$job/.claim/group_start" "$job/.claim/armed"
356
+ }
357
+
358
+ # Stop every tracked lane process and its recorded command execution. The lane
359
+ # is signalled first so it cannot dispatch further work, then the job's
360
+ # recorded supervisor and group are verified stopped; a job interrupted here
361
+ # stays running-with-a-dead-owner for the replacement worker's orphan recovery,
362
+ # exactly as a crashed single-process worker's job did.
363
+ worker_lane_identity_matches() { # <pid> <start>
364
+ local pid=$1 start=$2 actual_start
365
+ [ -n "$start" ] || return 1
366
+ actual_start=$(fm_remote_job_process_start "$pid" 2>/dev/null) || return 1
367
+ [ "$actual_start" = "$start" ]
368
+ }
369
+
370
+ worker_stop_active_execution() {
371
+ local i=0 count=${#WORKER_LANE_PIDS[@]} job pid start failed=0
372
+ while [ "$i" -lt "$count" ]; do
373
+ pid=${WORKER_LANE_PIDS[$i]}
374
+ start=${WORKER_LANE_STARTS[$i]}
375
+ job=${WORKER_LANE_JOBS[$i]}
376
+ if worker_lane_identity_matches "$pid" "$start"; then kill -TERM "$pid" 2>/dev/null || true; fi
377
+ if worker_lane_identity_matches "$pid" "$start"; then kill -KILL "$pid" 2>/dev/null || true; fi
378
+ wait "$pid" 2>/dev/null || true
379
+ if [ -d "$job" ] && [ ! -L "$job" ]; then
380
+ worker_stop_recorded_execution "$job" || failed=1
381
+ fi
382
+ i=$((i + 1))
383
+ done
384
+ WORKER_LANE_HOMES=()
385
+ WORKER_LANE_PIDS=()
386
+ WORKER_LANE_STARTS=()
387
+ WORKER_LANE_JOBS=()
388
+ [ "$failed" -eq 0 ]
389
+ }
390
+
391
+ # Ignore, rather than restore the default disposition for, the signals this
392
+ # handler answers. A replacement stops a Linux worker by signalling its whole
393
+ # isolated group, and the supervisor in that group forwards a second stop signal
394
+ # to this same serving child, so a repeat is the normal case and not an
395
+ # exception. Restoring the default let that second signal kill the shutdown part
396
+ # way through, which left the ownership lock behind holding a half-written temp
397
+ # file that no later worker could clear, so every replacement then failed to
398
+ # report ready. A shutdown that hangs is still stopped: the caller escalates to
399
+ # KILL, which no disposition can block.
400
+ worker_shutdown() {
401
+ trap '' HUP INT TERM
402
+ worker_publish_quarantine || {
403
+ worker_error "cannot guard worker ownership for shutdown"
404
+ trap worker_shutdown HUP INT TERM
405
+ return 0
406
+ }
407
+ worker_stop_active_execution || {
408
+ worker_error "could not stop the active command tree"
409
+ WORKER_RELEASE_OWNERSHIP=0
410
+ exit 125
411
+ }
412
+ worker_clear_quarantine || {
413
+ worker_error "could not clear guarded worker ownership after shutdown"
414
+ WORKER_RELEASE_OWNERSHIP=0
415
+ exit 125
416
+ }
417
+ exit 0
418
+ }
419
+
420
+ worker_exit_cleanup() {
421
+ if [ "$WORKER_RELEASE_OWNERSHIP" -eq 1 ] && ! worker_stop_active_execution; then
422
+ worker_error "could not stop the active command tree during exit"
423
+ worker_publish_quarantine || worker_error "could not quarantine failed exit ownership"
424
+ WORKER_RELEASE_OWNERSHIP=0
425
+ fi
426
+ worker_cleanup
427
+ }
428
+
429
+ worker_claim() { # <job-dir>
430
+ local job=$1 claim pid start pid_tmp start_tmp
431
+ claim="$job/.claim"
432
+ [ ! -e "$claim" ] && [ ! -L "$claim" ] || return 1
433
+ (umask 077; mkdir "$claim") || return 1
434
+ pid=${BASHPID:-$$}
435
+ start=$(fm_remote_job_process_start "$pid") || { rmdir "$claim" 2>/dev/null || true; return 1; }
436
+ pid_tmp=$(umask 077; mktemp "$claim/.owner.XXXXXX") || { rmdir "$claim" 2>/dev/null || true; return 1; }
437
+ start_tmp=$(umask 077; mktemp "$claim/.owner_start.XXXXXX") || {
438
+ rm -f -- "$pid_tmp"
439
+ rmdir "$claim" 2>/dev/null || true
440
+ return 1
441
+ }
442
+ if ! printf '%s\n' "$pid" > "$pid_tmp" || ! printf '%s\n' "$start" > "$start_tmp" \
443
+ || ! chmod 600 "$pid_tmp" "$start_tmp" || ! mv -f -- "$start_tmp" "$claim/owner_start" \
444
+ || ! mv -f -- "$pid_tmp" "$claim/owner"; then
445
+ rm -f -- "$pid_tmp" "$start_tmp" "$claim/owner" "$claim/owner_start"
446
+ rmdir "$claim" 2>/dev/null || true
447
+ return 1
448
+ fi
449
+ }
450
+
451
+ worker_claim_owner_alive() { # <job-dir>
452
+ local job=$1 claim="$1/.claim" owner pid recorded_start actual_start
453
+ [ -d "$claim" ] && [ ! -L "$claim" ] || return 1
454
+ owner="$claim/owner"
455
+ fm_remote_job_regular_bounded "$owner" 64 || return 1
456
+ pid=$(tr -d '\n' < "$owner")
457
+ case "$pid" in ''|*[!0-9]*) return 1 ;; esac
458
+ if [ -e "$claim/owner_start" ] || [ -L "$claim/owner_start" ]; then
459
+ recorded_start=$(fm_remote_job_read_single_line "$claim/owner_start" 256 2>/dev/null) || return 1
460
+ actual_start=$(fm_remote_job_process_start "$pid" 2>/dev/null) || return 1
461
+ [ "$recorded_start" = "$actual_start" ]
462
+ return
463
+ fi
464
+ kill -0 "$pid" 2>/dev/null
465
+ }
466
+
467
+ worker_clear_dead_claim() { # <job-dir>
468
+ local job=$1 claim="$1/.claim"
469
+ [ -e "$claim" ] || [ -L "$claim" ] || return 0
470
+ worker_claim_owner_alive "$job" && return 1
471
+ [ -d "$claim" ] && [ ! -L "$claim" ] || return 1
472
+ [ ! -e "$claim/owner" ] || [ ! -L "$claim/owner" ] || return 1
473
+ fm_remote_job_remove_claim_records "$claim" || return 1
474
+ rmdir "$claim"
475
+ }
476
+
477
+ # Reclaim a running job this serving loop does not own: a record left by a
478
+ # crashed worker, whether its lane process died with it or survived it. The
479
+ # recorded execution is stopped either way - a surviving foreign lane is not
480
+ # supervised by any owner and a second lane for its home must never start
481
+ # beside it - and the record publishes unknown completion, exactly as a
482
+ # crashed single-process worker's job always has.
483
+ worker_reclaim_running_job() { # <job-dir>
484
+ local job=$1 file state
485
+ worker_stop_recorded_execution "$job" || return 1
486
+ state=$(fm_remote_job_read_state "$job" 2>/dev/null) || return 1
487
+ worker_clear_dead_claim "$job" || return 1
488
+ [ "$state" = 'done' ] && return 0
489
+ [ "$state" = running ] || return 1
490
+ for file in .stdout.pipe .stderr.pipe; do
491
+ [ ! -e "$job/$file" ] && [ ! -L "$job/$file" ] || {
492
+ [ ! -L "$job/$file" ] || return 1
493
+ rm -f -- "$job/$file" || return 1
494
+ }
495
+ done
496
+ for file in stdout stderr; do
497
+ [ -f "$job/$file" ] && [ ! -L "$job/$file" ] || return 1
498
+ done
499
+ : > "$job/stdout"
500
+ printf 'remote job worker stopped before this job completed\n' > "$job/stderr"
501
+ worker_publish_result "$job" 125
502
+ }
503
+
504
+ worker_read_text() { # <job-dir> <field> <max>
505
+ local job=$1 field=$2 max=$3 value extra
506
+ fm_remote_job_regular_bounded "$job/$field" "$max" || return 1
507
+ IFS= read -r value < "$job/$field" || return 1
508
+ if IFS= read -r extra < <(tail -n +2 "$job/$field"); then
509
+ : "$extra"
510
+ return 1
511
+ fi
512
+ [ -n "$value" ] || return 1
513
+ [ "$(LC_ALL=C tr -cd '\000\015' < "$job/$field" | LC_ALL=C wc -c | tr -d ' ')" -eq 0 ] || return 1
514
+ printf '%s\n' "$value"
515
+ }
516
+
517
+ worker_publish_result() { # <job-dir> <exit>
518
+ local job=$1 exit_status=$2 tmp account_home
519
+ case "$exit_status" in ''|*[!0-9]*) exit_status=125 ;; esac
520
+ [ "$exit_status" -le 255 ] || exit_status=125
521
+ for tmp in stdout stderr; do
522
+ fm_remote_job_regular_bounded "$job/$tmp" "$FM_REMOTE_JOB_MAX_BYTES" || return 1
523
+ done
524
+ tmp=$(umask 077; mktemp "$job/.exit.XXXXXX") || return 1
525
+ printf '%s\n' "$exit_status" > "$tmp" || { rm -f -- "$tmp"; return 1; }
526
+ chmod 600 "$tmp" || { rm -f -- "$tmp"; return 1; }
527
+ mv -f -- "$tmp" "$job/exit" || { rm -f -- "$tmp"; return 1; }
528
+ fm_remote_job_write_state "$job" 'done' || return 1
529
+ if fm_remote_job_cancelled "$job"; then
530
+ account_home=$(worker_account_home 2>/dev/null || true)
531
+ if [ -n "$account_home" ]; then
532
+ fm_remote_job_reap "$account_home" "${job##*/}" 2>/dev/null || true
533
+ fi
534
+ fi
535
+ }
536
+
537
+ worker_run_with_timeout() { # <job-dir> <seconds> <command> [args...]
538
+ local job=$1 timeout=$2 group_file group_start_file armed_file group_pid group_start
539
+ local group_tmp group_start_tmp rc tmp deadline next_check attempt timed_out=0 cancelled=0
540
+ WORKER_PREEMPTED=0
541
+ shift 2
542
+ group_file="$job/.claim/group"
543
+ group_start_file="$job/.claim/group_start"
544
+ armed_file="$job/.claim/armed"
545
+ set -m
546
+ (
547
+ while [ ! -f "$armed_file" ] || [ -L "$armed_file" ]; do
548
+ [ -d "$job/.claim" ] && [ ! -L "$job/.claim" ] || exit 125
549
+ sleep 0.01
550
+ done
551
+ exec "$@"
552
+ ) &
553
+ group_pid=$!
554
+ set +m
555
+ group_start=$(fm_remote_job_process_start "$group_pid") || {
556
+ worker_signal_process_or_group group KILL "$group_pid"
557
+ wait "$group_pid" 2>/dev/null || true
558
+ return 125
559
+ }
560
+ group_tmp=$(umask 077; mktemp "$job/.claim/.group.XXXXXX") || {
561
+ worker_signal_process_or_group group KILL "$group_pid"
562
+ wait "$group_pid" 2>/dev/null || true
563
+ return 125
564
+ }
565
+ group_start_tmp=$(umask 077; mktemp "$job/.claim/.group_start.XXXXXX") || {
566
+ rm -f -- "$group_tmp"
567
+ worker_signal_process_or_group group KILL "$group_pid"
568
+ wait "$group_pid" 2>/dev/null || true
569
+ return 125
570
+ }
571
+ if ! printf '%s\n' "$group_pid" > "$group_tmp" \
572
+ || ! printf '%s\n' "$group_start" > "$group_start_tmp" \
573
+ || ! chmod 600 "$group_tmp" "$group_start_tmp" \
574
+ || ! mv -f -- "$group_start_tmp" "$group_start_file" \
575
+ || ! mv -f -- "$group_tmp" "$group_file"; then
576
+ rm -f -- "$group_tmp" "$group_start_tmp" "$group_file" "$group_start_file"
577
+ worker_signal_process_or_group group KILL "$group_pid"
578
+ wait "$group_pid" 2>/dev/null || true
579
+ return 125
580
+ fi
581
+ tmp=$(umask 077; mktemp "$job/.claim/.armed.XXXXXX") || {
582
+ worker_signal_process_or_group group KILL "$group_pid"
583
+ wait "$group_pid" 2>/dev/null || true
584
+ rm -f -- "$group_file" "$group_start_file"
585
+ return 125
586
+ }
587
+ if ! chmod 600 "$tmp" || ! mv -f -- "$tmp" "$armed_file"; then
588
+ rm -f -- "$tmp"
589
+ worker_signal_process_or_group group KILL "$group_pid"
590
+ wait "$group_pid" 2>/dev/null || true
591
+ rm -f -- "$group_file" "$group_start_file"
592
+ return 125
593
+ fi
594
+ deadline=$((SECONDS + timeout))
595
+ next_check=$((SECONDS + 1))
596
+ while worker_process_or_group_alive group "$group_pid"; do
597
+ if [ "$SECONDS" -ge "$deadline" ]; then
598
+ worker_signal_process_or_group group TERM "$group_pid"
599
+ worker_signal_process_or_group group KILL "$group_pid"
600
+ timed_out=1
601
+ break
602
+ fi
603
+ if [ "$SECONDS" -ge "$next_check" ]; then
604
+ if fm_remote_job_cancelled "$job"; then
605
+ worker_signal_process_or_group group TERM "$group_pid"
606
+ attempt=0
607
+ while worker_process_or_group_alive group "$group_pid" && [ "$attempt" -lt 20 ]; do
608
+ attempt=$((attempt + 1))
609
+ sleep 0.05
610
+ done
611
+ worker_signal_process_or_group group KILL "$group_pid"
612
+ cancelled=1
613
+ break
614
+ fi
615
+ if [ "$WORKER_PREEMPTIBLE" -eq 1 ] && worker_preempting_waiter_exists "$WORKER_LANE_HOME"; then
616
+ worker_signal_process_or_group group TERM "$group_pid"
617
+ attempt=0
618
+ while worker_process_or_group_alive group "$group_pid" && [ "$attempt" -lt 20 ]; do
619
+ attempt=$((attempt + 1))
620
+ sleep 0.05
621
+ done
622
+ worker_signal_process_or_group group KILL "$group_pid"
623
+ WORKER_PREEMPTED=1
624
+ break
625
+ fi
626
+ next_check=$((SECONDS + 1))
627
+ fi
628
+ sleep "$FM_REMOTE_JOB_POLL_SECONDS"
629
+ done
630
+ wait "$group_pid" 2>/dev/null
631
+ rc=$?
632
+ rm -f -- "$group_file" "$group_start_file" "$armed_file"
633
+ [ "$timed_out" -eq 0 ] || return 124
634
+ [ "$cancelled" -eq 0 ] || return 130
635
+ [ "$WORKER_PREEMPTED" -eq 0 ] || return "$FM_REMOTE_JOB_PREEMPTED_EXIT"
636
+ return "$rc"
637
+ }
638
+
639
+ worker_job_command() { # <job-dir>; the first argv element of a staged record
640
+ local job=$1 first=
641
+ fm_remote_job_regular_bounded "$job/argv" "$FM_REMOTE_JOB_MAX_BYTES" || return 1
642
+ IFS= read -r -d '' first < "$job/argv" || [ -n "$first" ] || return 1
643
+ printf '%s\n' "$first"
644
+ }
645
+
646
+ worker_preempting_waiter_exists() { # <lane-home>
647
+ local lane_home=$1 job state command job_home
648
+ for job in "$FM_REMOTE_JOB_JOBS"/job-*; do
649
+ [ -d "$job" ] && [ ! -L "$job" ] || continue
650
+ state=$(fm_remote_job_read_state "$job" 2>/dev/null || true)
651
+ [ "$state" = queued ] || continue
652
+ fm_remote_job_cancelled "$job" && continue
653
+ # Lanes are per home, so only a waiter for this lane's own home may
654
+ # preempt; another home's queue drains through its own lane.
655
+ job_home=$(worker_read_text "$job" home 8192 2>/dev/null || true)
656
+ [ "$job_home" = "$lane_home" ] || continue
657
+ command=$(worker_job_command "$job" 2>/dev/null || true)
658
+ fm_remote_job_command_preemptible "$command" || return 0
659
+ done
660
+ return 1
661
+ }
662
+
663
+ worker_cleanup_output_capture() { # <job-dir> <stdout-reader> <stderr-reader>
664
+ local job=$1 stdout_reader=$2 stderr_reader=$3
665
+ kill "$stdout_reader" "$stderr_reader" 2>/dev/null || true
666
+ wait "$stdout_reader" 2>/dev/null || true
667
+ wait "$stderr_reader" 2>/dev/null || true
668
+ rm -f -- "$job/.stdout.pipe" "$job/.stderr.pipe"
669
+ }
670
+
671
+ worker_capture_output() { # <fifo> <destination>
672
+ local fifo=$1 destination=$2
673
+ {
674
+ head -c "$FM_REMOTE_JOB_MAX_BYTES"
675
+ cat >/dev/null
676
+ } < "$fifo" > "$destination"
677
+ }
678
+
679
+ worker_run_job() { # <account-home> <job-dir>
680
+ local account_home=$1 job=$2 root home command command_path git_bin rc deadline remaining
681
+ local stdout_pipe stderr_pipe stdout_reader stderr_reader preemptible=0
682
+ local -a argv child_env
683
+ root=$(worker_read_text "$job" root 8192) || { worker_publish_result "$job" 126; return; }
684
+ home=$(worker_read_text "$job" home 8192) || { worker_publish_result "$job" 126; return; }
685
+ root=$(fm_remote_job_canonical_existing_dir "$root") || { worker_publish_result "$job" 126; return; }
686
+ home=$(fm_remote_job_canonical_home "$home") || { worker_publish_result "$job" 126; return; }
687
+ # The lane key everywhere - dispatch and the preemption scan - is the staged
688
+ # home field's exact text, so this comparison value is read the same way.
689
+ WORKER_LANE_HOME=$(worker_read_text "$job" home 8192 2>/dev/null || true)
690
+ [ "$root" = "$FM_ROOT" ] || { worker_publish_result "$job" 126; return; }
691
+ [ -f "$root/AGENTS.md" ] && [ ! -L "$root/AGENTS.md" ] &&
692
+ [ -d "$root/bin" ] && [ ! -L "$root/bin" ] || { worker_publish_result "$job" 126; return; }
693
+ fm_remote_job_regular_bounded "$job/argv" "$FM_REMOTE_JOB_MAX_BYTES" || { worker_publish_result "$job" 126; return; }
694
+ fm_remote_job_regular_bounded "$job/stdin" "$FM_REMOTE_JOB_MAX_BYTES" || { worker_publish_result "$job" 126; return; }
695
+ deadline=$(fm_remote_job_read_deadline "$job") || { worker_publish_result "$job" 126; return; }
696
+ remaining=$((deadline - $(date +%s)))
697
+ [ "$remaining" -gt 0 ] || { worker_publish_result "$job" 124; return; }
698
+ argv=()
699
+ while IFS= read -r -d '' command; do argv+=("$command"); done < "$job/argv"
700
+ [ "${#argv[@]}" -ge 1 ] || { worker_publish_result "$job" 126; return; }
701
+ command=${argv[0]}
702
+ case "$command" in fm-*.sh) ;; *) worker_publish_result "$job" 126; return ;; esac
703
+ case "$command" in */*|*..*) worker_publish_result "$job" 126; return ;; esac
704
+ if fm_remote_job_command_preemptible "$command"; then preemptible=1; fi
705
+ command_path="$root/bin/$command"
706
+ [ -f "$command_path" ] && [ ! -L "$command_path" ] && [ -x "$command_path" ] || {
707
+ worker_publish_result "$job" 126
708
+ return
709
+ }
710
+ fm_remote_job_compose_operator_path "$account_home" >/dev/null
711
+ git_bin=$(fm_remote_job_operator_tool git 2>/dev/null || true)
712
+ [ -n "$git_bin" ] || { worker_publish_result "$job" 126; return; }
713
+ remaining=$((deadline - $(date +%s)))
714
+ [ "$remaining" -gt 0 ] || { worker_publish_result "$job" 124; return; }
715
+ set +e
716
+ worker_run_with_timeout "$job" "$remaining" \
717
+ "$git_bin" -C "$root" ls-files --error-unmatch "bin/$command" >/dev/null 2>&1
718
+ rc=$?
719
+ set -e
720
+ case "$rc" in
721
+ 0) ;;
722
+ 124) worker_publish_result "$job" 124; return ;;
723
+ 125) worker_publish_result "$job" 125; return ;;
724
+ *) worker_publish_result "$job" 126; return ;;
725
+ esac
726
+ fm_remote_job_build_child_path "$root" >/dev/null
727
+ for command in stdin stdout stderr; do
728
+ [ -f "$job/$command" ] && [ ! -L "$job/$command" ] || { worker_publish_result "$job" 126; return; }
729
+ done
730
+ stdout_pipe="$job/.stdout.pipe"
731
+ stderr_pipe="$job/.stderr.pipe"
732
+ [ ! -e "$stdout_pipe" ] && [ ! -L "$stdout_pipe" ] && [ ! -e "$stderr_pipe" ] && [ ! -L "$stderr_pipe" ] || {
733
+ worker_publish_result "$job" 125
734
+ return
735
+ }
736
+ mkfifo "$stdout_pipe" "$stderr_pipe" || { worker_publish_result "$job" 125; return; }
737
+ chmod 600 "$stdout_pipe" "$stderr_pipe" || {
738
+ rm -f -- "$stdout_pipe" "$stderr_pipe"
739
+ worker_publish_result "$job" 125
740
+ return
741
+ }
742
+ worker_capture_output "$stdout_pipe" "$job/stdout" &
743
+ stdout_reader=$!
744
+ worker_capture_output "$stderr_pipe" "$job/stderr" &
745
+ stderr_reader=$!
746
+ child_env=(
747
+ /usr/bin/env -i
748
+ "PATH=$FM_REMOTE_JOB_CHILD_PATH"
749
+ "HOME=$account_home"
750
+ "FM_HOME=$home"
751
+ "FM_ROOT_OVERRIDE=$root"
752
+ FM_REMOTE_JOB_ACTIVE=1
753
+ )
754
+ if [ -n "${FM_REMOTE_JOB_PLATFORM_OVERRIDE:-}" ]; then
755
+ child_env+=("FM_REMOTE_JOB_PLATFORM_OVERRIDE=$FM_REMOTE_JOB_PLATFORM_OVERRIDE")
756
+ fi
757
+ remaining=$((deadline - $(date +%s)))
758
+ [ "$remaining" -gt 0 ] || {
759
+ worker_cleanup_output_capture "$job" "$stdout_reader" "$stderr_reader"
760
+ worker_publish_result "$job" 124
761
+ return
762
+ }
763
+ set +e
764
+ WORKER_PREEMPTIBLE=$preemptible
765
+ worker_run_with_timeout "$job" "$remaining" "${child_env[@]}" \
766
+ "$command_path" "${argv[@]:1}" < "$job/stdin" > "$stdout_pipe" 2> "$stderr_pipe"
767
+ rc=$?
768
+ WORKER_PREEMPTIBLE=0
769
+ wait "$stdout_reader"
770
+ wait "$stderr_reader"
771
+ rm -f -- "$stdout_pipe" "$stderr_pipe"
772
+ set -e
773
+ if [ "$WORKER_PREEMPTED" -eq 1 ]; then
774
+ : > "$job/stdout"
775
+ : > "$job/stderr"
776
+ fi
777
+ worker_publish_result "$job" "$rc" || worker_error "could not publish result for ${job##*/}"
778
+ }
779
+
780
+ # Finalize a cancelled record nobody waits on: publish the interrupt result so
781
+ # the record is complete, then reap it because its caller is gone.
782
+ worker_finalize_cancelled() { # <account-home> <job-dir>
783
+ local account_home=$1 job=$2
784
+ : > "$job/stdout" 2>/dev/null || true
785
+ printf 'remote job cancelled after its caller disconnected\n' > "$job/stderr" 2>/dev/null || true
786
+ worker_publish_result "$job" 130 || return 1
787
+ fm_remote_job_reap "$account_home" "${job##*/}" || true
788
+ }
789
+
790
+ worker_lane_busy() { # <home>
791
+ local home=$1 i=0 count=${#WORKER_LANE_HOMES[@]}
792
+ while [ "$i" -lt "$count" ]; do
793
+ [ "${WORKER_LANE_HOMES[$i]}" != "$home" ] || return 0
794
+ i=$((i + 1))
795
+ done
796
+ return 1
797
+ }
798
+
799
+ worker_lane_owns_job() { # <job-dir>
800
+ local job=$1 i=0 count=${#WORKER_LANE_JOBS[@]}
801
+ while [ "$i" -lt "$count" ]; do
802
+ [ "${WORKER_LANE_JOBS[$i]}" != "$job" ] || return 0
803
+ i=$((i + 1))
804
+ done
805
+ return 1
806
+ }
807
+
808
+ worker_reap_finished_lanes() {
809
+ local i=0 count=${#WORKER_LANE_PIDS[@]} pid start
810
+ local live_homes=() live_pids=() live_starts=() live_jobs=()
811
+ while [ "$i" -lt "$count" ]; do
812
+ pid=${WORKER_LANE_PIDS[$i]}
813
+ start=${WORKER_LANE_STARTS[$i]}
814
+ if worker_lane_identity_matches "$pid" "$start"; then
815
+ live_homes+=("${WORKER_LANE_HOMES[$i]}")
816
+ live_pids+=("$pid")
817
+ live_starts+=("$start")
818
+ live_jobs+=("${WORKER_LANE_JOBS[$i]}")
819
+ else
820
+ wait "$pid" 2>/dev/null || true
821
+ fi
822
+ i=$((i + 1))
823
+ done
824
+ WORKER_LANE_HOMES=()
825
+ WORKER_LANE_PIDS=()
826
+ WORKER_LANE_STARTS=()
827
+ WORKER_LANE_JOBS=()
828
+ i=0
829
+ count=${#live_pids[@]}
830
+ while [ "$i" -lt "$count" ]; do
831
+ WORKER_LANE_HOMES+=("${live_homes[$i]}")
832
+ WORKER_LANE_PIDS+=("${live_pids[$i]}")
833
+ WORKER_LANE_STARTS+=("${live_starts[$i]}")
834
+ WORKER_LANE_JOBS+=("${live_jobs[$i]}")
835
+ i=$((i + 1))
836
+ done
837
+ }
838
+
839
+ # One lane's whole execution of one job, run as a background lane process:
840
+ # claim, record this process as the claim supervisor, honor a cancel that
841
+ # arrived before running, establish the deadline, run to publication, and reap
842
+ # the record when its caller cancelled and can no longer reap it.
843
+ worker_lane_execute() { # <account-home> <job-dir>
844
+ local account_home=$1 job=$2 timeout queue_deadline deadline
845
+ local supervisor_pid supervisor_start pid_tmp start_tmp
846
+ worker_claim "$job" || return 0
847
+ supervisor_pid=${BASHPID:-$$}
848
+ supervisor_start=$(fm_remote_job_process_start "$supervisor_pid") || {
849
+ worker_publish_result "$job" 125 || true
850
+ return 0
851
+ }
852
+ pid_tmp=$(umask 077; mktemp "$job/.claim/.supervisor.XXXXXX") || {
853
+ worker_publish_result "$job" 125 || true
854
+ return 0
855
+ }
856
+ start_tmp=$(umask 077; mktemp "$job/.claim/.supervisor_start.XXXXXX") || {
857
+ rm -f -- "$pid_tmp"
858
+ worker_publish_result "$job" 125 || true
859
+ return 0
860
+ }
861
+ if ! printf '%s\n' "$supervisor_pid" > "$pid_tmp" \
862
+ || ! printf '%s\n' "$supervisor_start" > "$start_tmp" \
863
+ || ! chmod 600 "$pid_tmp" "$start_tmp" \
864
+ || ! mv -f -- "$start_tmp" "$job/.claim/supervisor_start" \
865
+ || ! mv -f -- "$pid_tmp" "$job/.claim/supervisor"; then
866
+ rm -f -- "$pid_tmp" "$start_tmp" "$job/.claim/supervisor_start"
867
+ worker_publish_result "$job" 125 || true
868
+ return 0
869
+ fi
870
+ if fm_remote_job_cancelled "$job"; then
871
+ worker_finalize_cancelled "$account_home" "$job" || true
872
+ return 0
873
+ fi
874
+ queue_deadline=$(fm_remote_job_read_number "$job" queue_deadline 2>/dev/null || true)
875
+ case "$queue_deadline" in ''|*[!0-9]*) worker_publish_result "$job" 126 || true; return 0 ;; esac
876
+ if [ "$(date +%s)" -ge "$queue_deadline" ]; then
877
+ worker_publish_result "$job" 124 || true
878
+ return 0
879
+ fi
880
+ timeout=$(fm_remote_job_read_number "$job" timeout 2>/dev/null || true)
881
+ case "$timeout" in ''|*[!0-9]*) worker_publish_result "$job" 126 || true; return 0 ;; esac
882
+ if [ "$timeout" -gt 3600 ]; then
883
+ worker_publish_result "$job" 126 || true
884
+ return 0
885
+ fi
886
+ # The deadline is measured in whole seconds from a truncated clock read, so
887
+ # the +1 keeps the granted window at least the recorded timeout instead of
888
+ # silently shaving up to a second off it.
889
+ deadline=$(( $(date +%s) + timeout + 1 ))
890
+ fm_remote_job_write_number "$job" deadline "$deadline" || {
891
+ worker_publish_result "$job" 125 || true
892
+ return 0
893
+ }
894
+ fm_remote_job_write_state "$job" running || {
895
+ worker_publish_result "$job" 125 || true
896
+ return 0
897
+ }
898
+ worker_run_job "$account_home" "$job"
899
+ if fm_remote_job_cancelled "$job"; then
900
+ fm_remote_job_reap "$account_home" "${job##*/}" || true
901
+ fi
902
+ }
903
+
904
+ # Each lane runs as its own top-level worker process (--lane), not a
905
+ # backgrounded subshell: a bash subshell does not reliably reap its dead
906
+ # children, and a zombie group leader keeps its process group signalable, so a
907
+ # subshell-hosted monitor loop can believe a finished command is still running
908
+ # until the job deadline. A top-level shell is the context the monitor loop
909
+ # has always run in.
910
+ worker_start_lane() { # <job-dir> <home>
911
+ local job=$1 home=$2 lane_pid lane_start
912
+ "$SCRIPT_DIR/fm-remote-job-worker.sh" --lane "${job##*/}" &
913
+ lane_pid=$!
914
+ lane_start=$(fm_remote_job_process_start "$lane_pid" 2>/dev/null || true)
915
+ WORKER_LANE_HOMES+=("$home")
916
+ WORKER_LANE_PIDS+=("$lane_pid")
917
+ WORKER_LANE_STARTS+=("$lane_start")
918
+ WORKER_LANE_JOBS+=("$job")
919
+ }
920
+
921
+ worker_lane_main() { # <job-id>
922
+ local account_home job
923
+ fm_remote_job_safe_id "$1" || { worker_error "invalid lane job id"; exit 2; }
924
+ account_home=$(worker_account_home) || { worker_error "cannot resolve account home"; exit 1; }
925
+ FM_ROOT=$(fm_remote_job_canonical_existing_dir "$FM_ROOT") || { worker_error "configured FM_ROOT is unsafe"; exit 1; }
926
+ fm_remote_job_prepare_state "$account_home" || { worker_error "$FM_REMOTE_JOB_ERROR"; exit 1; }
927
+ job=$(fm_remote_job_job_dir "$1" 2>/dev/null) || exit 0
928
+ worker_lane_execute "$account_home" "$job"
929
+ }
930
+
931
+ worker_process_once() { # <account-home>
932
+ local account_home=$1 job id state queue_deadline home seq candidates=''
933
+ local reserved_index reserved_count home_reserved
934
+ local reserved_homes=()
935
+ worker_reap_finished_lanes
936
+ for job in "$FM_REMOTE_JOB_JOBS"/job-*; do
937
+ [ -d "$job" ] && [ ! -L "$job" ] || continue
938
+ id=${job##*/}
939
+ fm_remote_job_safe_id "$id" || continue
940
+ job=$(fm_remote_job_job_dir "$id" 2>/dev/null || true)
941
+ [ -n "$job" ] || continue
942
+ state=$(fm_remote_job_read_state "$job" 2>/dev/null || true)
943
+ case "$state" in
944
+ queued)
945
+ worker_lane_owns_job "$job" && continue
946
+ if ! worker_clear_dead_claim "$job"; then
947
+ if worker_claim_owner_alive "$job"; then
948
+ home=$(worker_read_text "$job" home 8192 2>/dev/null || true)
949
+ [ -n "$home" ] && reserved_homes+=("$home")
950
+ fi
951
+ continue
952
+ fi
953
+ if fm_remote_job_cancelled "$job"; then
954
+ worker_finalize_cancelled "$account_home" "$job" || true
955
+ continue
956
+ fi
957
+ queue_deadline=$(fm_remote_job_read_number "$job" queue_deadline 2>/dev/null || true)
958
+ case "$queue_deadline" in ''|*[!0-9]*) worker_publish_result "$job" 126 || true; continue ;; esac
959
+ if [ "$(date +%s)" -ge "$queue_deadline" ]; then
960
+ worker_publish_result "$job" 124 || true
961
+ continue
962
+ fi
963
+ home=$(worker_read_text "$job" home 8192 2>/dev/null || true)
964
+ [ -n "$home" ] || { worker_publish_result "$job" 126 || true; continue; }
965
+ # A record staged by an older library has no seq; order it ahead of
966
+ # sequenced work as the older job it is.
967
+ seq=$(fm_remote_job_read_number "$job" seq 2>/dev/null || true)
968
+ case "$seq" in ''|*[!0-9]*) seq=0 ;; esac
969
+ candidates="$candidates$seq"$'\t'"$id"$'\t'"$home"$'\n'
970
+ ;;
971
+ running)
972
+ worker_lane_owns_job "$job" || worker_reclaim_running_job "$job" || true
973
+ continue
974
+ ;;
975
+ *) continue ;;
976
+ esac
977
+ done
978
+ [ -n "$candidates" ] || return 0
979
+ while IFS=$'\t' read -r seq id home; do
980
+ [ -n "$id" ] || continue
981
+ worker_lane_busy "$home" && continue
982
+ home_reserved=0
983
+ reserved_index=0
984
+ reserved_count=${#reserved_homes[@]}
985
+ while [ "$reserved_index" -lt "$reserved_count" ]; do
986
+ if [ "${reserved_homes[$reserved_index]}" = "$home" ]; then
987
+ home_reserved=1
988
+ break
989
+ fi
990
+ reserved_index=$((reserved_index + 1))
991
+ done
992
+ [ "$home_reserved" -eq 0 ] || continue
993
+ job=$(fm_remote_job_job_dir "$id" 2>/dev/null || true)
994
+ [ -n "$job" ] || continue
995
+ [ "$(fm_remote_job_read_state "$job" 2>/dev/null || true)" = queued ] || continue
996
+ worker_start_lane "$job" "$home"
997
+ done < <(printf '%s' "$candidates" | sort -t $'\t' -k1,1n -k2,2)
998
+ }
999
+
1000
+ main() {
1001
+ local account_home lock_status
1002
+ account_home=$(worker_account_home) || { worker_error "cannot resolve account home"; exit 1; }
1003
+ FM_ROOT=$(fm_remote_job_canonical_existing_dir "$FM_ROOT") || { worker_error "configured FM_ROOT is unsafe"; exit 1; }
1004
+ [ -f "$FM_ROOT/AGENTS.md" ] && [ ! -L "$FM_ROOT/AGENTS.md" ] || { worker_error "FM_ROOT is not a Firstmate checkout"; exit 1; }
1005
+ fm_remote_job_prepare_state "$account_home" || { worker_error "$FM_REMOTE_JOB_ERROR"; exit 1; }
1006
+ WORKER_LOCK=$(fm_remote_job_worker_lock_path)
1007
+ trap worker_exit_cleanup EXIT
1008
+ worker_acquire_lock "$account_home"
1009
+ lock_status=$?
1010
+ case "$lock_status" in
1011
+ 0) ;;
1012
+ 2) exit 0 ;;
1013
+ 3) worker_error "worker ownership is quarantined after an unconfirmed shutdown"; exit 75 ;;
1014
+ *) worker_error "cannot acquire or safely reclaim worker ownership"; exit 1 ;;
1015
+ esac
1016
+ trap worker_shutdown HUP INT TERM
1017
+ worker_publish_identity "$account_home" || { worker_error "cannot publish worker code identity"; exit 1; }
1018
+ worker_publish_pid || { worker_error "cannot publish worker pid"; exit 1; }
1019
+ while :; do
1020
+ worker_write_heartbeat || { worker_error "cannot update worker heartbeat"; exit 1; }
1021
+ # Checked right after a fresh heartbeat, so the grace window cannot make a
1022
+ # still-healthy worker read as unready to a concurrent probe.
1023
+ if worker_code_root_abandoned; then
1024
+ worker_error "configured FM_ROOT $FM_ROOT no longer exists; stopping the abandoned worker"
1025
+ exit 0
1026
+ fi
1027
+ worker_reap=0
1028
+ if [ "$worker_reap" -eq 0 ]; then
1029
+ fm_remote_job_reap_stale "$account_home" || true
1030
+ worker_reap=1
1031
+ fi
1032
+ worker_process_once "$account_home"
1033
+ sleep "$FM_REMOTE_JOB_POLL_SECONDS"
1034
+ done
1035
+ }
1036
+
1037
+ worker_supervisor_cleanup_dead_child() { # <account-home> <pid>
1038
+ local account_home=$1 pid=$2 lock recorded pid_file ready identity
1039
+ fm_remote_job_prepare_state "$account_home" || return 1
1040
+ lock=$(fm_remote_job_worker_lock_path)
1041
+ [ -d "$lock" ] && [ ! -L "$lock" ] || return 1
1042
+ [ ! -e "$lock/quarantine" ] && [ ! -L "$lock/quarantine" ] || return 1
1043
+ recorded=$(fm_remote_job_read_single_line "$lock/pid" 64) || return 1
1044
+ [ "$recorded" = "$pid" ] || return 1
1045
+ pid_file=$(fm_remote_job_worker_pid_path)
1046
+ ready=$(fm_remote_job_worker_ready_path)
1047
+ identity=$(fm_remote_job_worker_identity_path)
1048
+ [ ! -L "$pid_file" ] && rm -f -- "$pid_file" || return 1
1049
+ [ ! -L "$ready" ] && rm -f -- "$ready" || return 1
1050
+ [ ! -L "$identity" ] && rm -f -- "$identity" || return 1
1051
+ [ ! -L "$lock/start" ] && [ ! -L "$lock/command" ] || return 1
1052
+ rm -f -- "$lock/pid" "$lock/start" "$lock/command" || return 1
1053
+ rmdir "$lock"
1054
+ }
1055
+
1056
+ worker_supervisor_shutdown() {
1057
+ local pid=${WORKER_SUPERVISED_PID:-}
1058
+ trap - HUP INT TERM
1059
+ if [ -n "$pid" ]; then
1060
+ kill -TERM "$pid" 2>/dev/null || true
1061
+ wait "$pid" 2>/dev/null || true
1062
+ fi
1063
+ exit 0
1064
+ }
1065
+
1066
+ worker_supervise_linux() {
1067
+ local account_home child_status started failures=0 restarts=0 backoff
1068
+ account_home=$(worker_account_home) || { worker_error "cannot resolve account home"; return 1; }
1069
+ FM_ROOT=$(fm_remote_job_canonical_existing_dir "$FM_ROOT") || { worker_error "configured FM_ROOT is unsafe"; return 1; }
1070
+ [ -f "$FM_ROOT/AGENTS.md" ] && [ ! -L "$FM_ROOT/AGENTS.md" ] || { worker_error "FM_ROOT is not a Firstmate checkout"; return 1; }
1071
+ fm_remote_job_prepare_state "$account_home" || { worker_error "$FM_REMOTE_JOB_ERROR"; return 1; }
1072
+ trap worker_supervisor_shutdown HUP INT TERM
1073
+ while :; do
1074
+ if worker_code_root_abandoned; then
1075
+ worker_error "configured FM_ROOT $FM_ROOT no longer exists; stopping the abandoned worker supervisor"
1076
+ return 0
1077
+ fi
1078
+ started=$SECONDS
1079
+ "$SCRIPT_DIR/fm-remote-job-worker.sh" --serve &
1080
+ WORKER_SUPERVISED_PID=$!
1081
+ wait "$WORKER_SUPERVISED_PID" 2>/dev/null
1082
+ child_status=$?
1083
+ if [ "$child_status" -eq 0 ]; then
1084
+ WORKER_SUPERVISED_PID=
1085
+ return 0
1086
+ fi
1087
+ if [ "$child_status" -eq 75 ]; then
1088
+ WORKER_SUPERVISED_PID=
1089
+ return 75
1090
+ fi
1091
+ worker_supervisor_cleanup_dead_child "$account_home" "$WORKER_SUPERVISED_PID" || true
1092
+ WORKER_SUPERVISED_PID=
1093
+ restarts=$((restarts + 1))
1094
+ if [ "$restarts" -ge "$FM_REMOTE_JOB_SUPERVISOR_MAX_RESTARTS" ]; then
1095
+ worker_error "remote job worker exited $restarts times; stopping the supervisor"
1096
+ return 1
1097
+ fi
1098
+ if [ $((SECONDS - started)) -ge "$FM_REMOTE_JOB_SUPERVISOR_HEALTHY_SECONDS" ]; then
1099
+ failures=0
1100
+ sleep 0.1
1101
+ continue
1102
+ fi
1103
+ failures=$((failures + 1))
1104
+ backoff=$failures
1105
+ [ "$backoff" -le "$FM_REMOTE_JOB_SUPERVISOR_MAX_BACKOFF_SECONDS" ] ||
1106
+ backoff=$FM_REMOTE_JOB_SUPERVISOR_MAX_BACKOFF_SECONDS
1107
+ sleep "$backoff"
1108
+ done
1109
+ }
1110
+
1111
+ case "${1:-}" in
1112
+ --serve)
1113
+ [ "$#" -eq 1 ] || { worker_error "unexpected worker arguments"; exit 2; }
1114
+ main
1115
+ ;;
1116
+ --lane)
1117
+ [ "$#" -eq 2 ] || { worker_error "unexpected worker arguments"; exit 2; }
1118
+ worker_lane_main "$2"
1119
+ ;;
1120
+ '')
1121
+ if [ "$(fm_remote_job_platform)" = linux ]; then worker_supervise_linux; else main; fi
1122
+ ;;
1123
+ *) worker_error "unexpected worker arguments"; exit 2 ;;
1124
+ esac