@zerwiz/ymir 0.1.3 → 0.1.5

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 (850) 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/backends/cmux.sh +649 -0
  27. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  28. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  29. package/.agents/backend/backends/herdr.sh +3386 -0
  30. package/.agents/backend/backends/orca.sh +290 -0
  31. package/.agents/backend/backends/tmux.sh +357 -0
  32. package/.agents/backend/backends/zellij.sh +700 -0
  33. package/.agents/backend/fm-afk-launch.sh +654 -0
  34. package/.agents/backend/fm-afk-return.sh +241 -0
  35. package/.agents/backend/fm-afk-start.sh +172 -0
  36. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  37. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  38. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  39. package/.agents/backend/fm-backend.sh +981 -0
  40. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  41. package/.agents/backend/fm-backlog-receive.sh +187 -0
  42. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  43. package/.agents/backend/fm-bearings-board.sh +204 -0
  44. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  45. package/.agents/backend/fm-bootstrap.sh +1568 -0
  46. package/.agents/backend/fm-branch-outcome.sh +203 -0
  47. package/.agents/backend/fm-branch-prompt.sh +101 -0
  48. package/.agents/backend/fm-brief.sh +452 -0
  49. package/.agents/backend/fm-busy-event.sh +234 -0
  50. package/.agents/backend/fm-busy-lib.sh +986 -0
  51. package/.agents/backend/fm-captain-hold.sh +1001 -0
  52. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  53. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  54. package/.agents/backend/fm-check-lib.sh +74 -0
  55. package/.agents/backend/fm-check-register.sh +42 -0
  56. package/.agents/backend/fm-check-unregister.sh +52 -0
  57. package/.agents/backend/fm-classify-lib.sh +1770 -0
  58. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  59. package/.agents/backend/fm-composer-lib.sh +1416 -0
  60. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  61. package/.agents/backend/fm-config-push.sh +254 -0
  62. package/.agents/backend/fm-control-lib.sh +251 -0
  63. package/.agents/backend/fm-control.sh +878 -0
  64. package/.agents/backend/fm-crew-state.sh +625 -0
  65. package/.agents/backend/fm-cursor-lib.sh +243 -0
  66. package/.agents/backend/fm-decision-hold.sh +232 -0
  67. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  68. package/.agents/backend/fm-dod-lib.sh +67 -0
  69. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  70. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  71. package/.agents/backend/fm-extension.mjs +2577 -0
  72. package/.agents/backend/fm-extension.sh +16 -0
  73. package/.agents/backend/fm-ff-lib.sh +420 -0
  74. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  75. package/.agents/backend/fm-fleet-sync.sh +458 -0
  76. package/.agents/backend/fm-fleet-view.sh +96 -0
  77. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  78. package/.agents/backend/fm-guard.sh +242 -0
  79. package/.agents/backend/fm-harness.sh +195 -0
  80. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  81. package/.agents/backend/fm-herdr-lab.sh +346 -0
  82. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  83. package/.agents/backend/fm-home-seed.sh +984 -0
  84. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  85. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  86. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  87. package/.agents/backend/fm-inbox.sh +399 -0
  88. package/.agents/backend/fm-install-actionlint.sh +84 -0
  89. package/.agents/backend/fm-install-herdr.sh +96 -0
  90. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  91. package/.agents/backend/fm-install-treehouse.sh +95 -0
  92. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  93. package/.agents/backend/fm-lease-lib.sh +218 -0
  94. package/.agents/backend/fm-lease.sh +189 -0
  95. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  96. package/.agents/backend/fm-lint-workflows.sh +137 -0
  97. package/.agents/backend/fm-lint.sh +591 -0
  98. package/.agents/backend/fm-lock-lib.sh +104 -0
  99. package/.agents/backend/fm-lock.sh +107 -0
  100. package/.agents/backend/fm-marker-lib.sh +12 -0
  101. package/.agents/backend/fm-merge-local.sh +74 -0
  102. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  103. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  104. package/.agents/backend/fm-on.sh +125 -0
  105. package/.agents/backend/fm-operational-input.sh +253 -0
  106. package/.agents/backend/fm-peek.sh +46 -0
  107. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  108. package/.agents/backend/fm-pr-check.sh +135 -0
  109. package/.agents/backend/fm-pr-lib.sh +1016 -0
  110. package/.agents/backend/fm-pr-merge.sh +707 -0
  111. package/.agents/backend/fm-pr-poll.sh +110 -0
  112. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  113. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  114. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  115. package/.agents/backend/fm-procevent-lib.sh +892 -0
  116. package/.agents/backend/fm-procevent-quota.sh +290 -0
  117. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  118. package/.agents/backend/fm-procevent-when.sh +504 -0
  119. package/.agents/backend/fm-procevent.sh +1466 -0
  120. package/.agents/backend/fm-project-mode.sh +92 -0
  121. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  122. package/.agents/backend/fm-promote.sh +282 -0
  123. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  124. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  125. package/.agents/backend/fm-public-followup.sh +1330 -0
  126. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  127. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  128. package/.agents/backend/fm-quota-choose.sh +384 -0
  129. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  130. package/.agents/backend/fm-remote-doctor.sh +800 -0
  131. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  132. package/.agents/backend/fm-remote-file.sh +234 -0
  133. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  134. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  135. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  136. package/.agents/backend/fm-remote-inherit.sh +179 -0
  137. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  138. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  139. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  140. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  141. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  142. package/.agents/backend/fm-review-diff.sh +158 -0
  143. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  144. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  145. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  146. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  147. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  148. package/.agents/backend/fm-secondmate-report.sh +88 -0
  149. package/.agents/backend/fm-send.sh +1059 -0
  150. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  151. package/.agents/backend/fm-session-start.sh +970 -0
  152. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  153. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  154. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  155. package/.agents/backend/fm-spawn.sh +3153 -0
  156. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  157. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  158. package/.agents/backend/fm-startup-network.sh +642 -0
  159. package/.agents/backend/fm-stow-cascade.sh +251 -0
  160. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  161. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  162. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  163. package/.agents/backend/fm-supervision-lib.sh +90 -0
  164. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  165. package/.agents/backend/fm-tangle-lib.sh +53 -0
  166. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  167. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  168. package/.agents/backend/fm-teardown.sh +2922 -0
  169. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  170. package/.agents/backend/fm-test-run.sh +2191 -0
  171. package/.agents/backend/fm-timeout-lib.sh +141 -0
  172. package/.agents/backend/fm-timing-lib.sh +179 -0
  173. package/.agents/backend/fm-tmux-lib.sh +291 -0
  174. package/.agents/backend/fm-tool-update-check.sh +898 -0
  175. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  176. package/.agents/backend/fm-transition-lib.sh +103 -0
  177. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  178. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  179. package/.agents/backend/fm-turnend-guard.sh +432 -0
  180. package/.agents/backend/fm-update.sh +110 -0
  181. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  182. package/.agents/backend/fm-voice-client.py +1373 -0
  183. package/.agents/backend/fm-voice-relay.py +1256 -0
  184. package/.agents/backend/fm-wake-drain.sh +592 -0
  185. package/.agents/backend/fm-wake-grant.sh +127 -0
  186. package/.agents/backend/fm-wake-lib.sh +1849 -0
  187. package/.agents/backend/fm-watch-arm.sh +602 -0
  188. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  189. package/.agents/backend/fm-watch.sh +1962 -0
  190. package/.agents/backend/fm-x-dismiss.sh +116 -0
  191. package/.agents/backend/fm-x-followup.sh +286 -0
  192. package/.agents/backend/fm-x-lib.sh +1000 -0
  193. package/.agents/backend/fm-x-link.sh +234 -0
  194. package/.agents/backend/fm-x-poll.sh +185 -0
  195. package/.agents/backend/fm-x-reply.sh +385 -0
  196. package/.agents/backend/fm_voice_frame.py +166 -0
  197. package/.agents/backend/fm_voice_records.py +574 -0
  198. package/.agents/backend/model-bridge.py +290 -0
  199. package/.agents/backend/opencode-go-bridge.py +30 -0
  200. package/.agents/bus/README.md +12 -0
  201. package/.agents/bus/messages.json +1 -0
  202. package/.agents/config/agents.machine.example.yaml +16 -0
  203. package/.agents/config/agents.yaml +50 -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 +9 -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/config/tailscale-sync.yaml +27 -0
  211. package/.agents/filebrowser/.filebrowser.json +6 -0
  212. package/.agents/filebrowser/README.md +11 -0
  213. package/.agents/gateway/README.md +12 -0
  214. package/.agents/github/README.md +15 -0
  215. package/.agents/github/webhooks/.gitkeep +0 -0
  216. package/.agents/github/workflows/.gitkeep +0 -0
  217. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  218. package/.agents/harness/opencode/plugins/package.json +4 -0
  219. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  220. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  221. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  222. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  223. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  224. package/.agents/memory/README.md +46 -0
  225. package/.agents/memory/kaia.engram +0 -0
  226. package/.agents/memory/kaia.engram-shm +0 -0
  227. package/.agents/memory/kaia.engram-wal +0 -0
  228. package/.agents/memory/runes_audit.md +5 -0
  229. package/.agents/memory/well/episodes.jsonl +367 -0
  230. package/.agents/memory/well/workspace.jsonl +27 -0
  231. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  232. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  233. package/.agents/migrations/0003-private-data-separation.sh +111 -0
  234. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  235. package/.agents/sandbox/Dockerfile.utgard +19 -0
  236. package/.agents/sandbox/README.md +14 -0
  237. package/.agents/sandbox/utgard.config.json +14 -0
  238. package/.agents/skills/README.md +59 -0
  239. package/.agents/skills/bragi-marketing/README.md +3 -0
  240. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  241. package/.agents/skills/eindri-homes/README.md +3 -0
  242. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  243. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  244. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  245. package/.agents/skills/frigg-consent/README.md +3 -0
  246. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  247. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  248. package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  276. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  277. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  278. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  279. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  280. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
  281. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  283. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  284. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
  285. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  286. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  287. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  288. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  289. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  290. package/.agents/skills/groa-update/SKILL.md +70 -0
  291. package/.agents/skills/hamr-adapters/README.md +3 -0
  292. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  293. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  294. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  295. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  296. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  297. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  298. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  299. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  300. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  301. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  302. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  303. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  304. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  305. package/.agents/skills/herdr-panes/README.md +3 -0
  306. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  307. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  308. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  309. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  310. package/.agents/skills/hnoss-design/README.md +3 -0
  311. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  312. package/.agents/skills/hvild-afk/README.md +3 -0
  313. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  314. package/.agents/skills/jord-projects/README.md +3 -0
  315. package/.agents/skills/jord-projects/SKILL.md +92 -0
  316. package/.agents/skills/lifecycle/SKILL.md +56 -0
  317. package/.agents/skills/lifecycle/smoke_test.sh +52 -0
  318. package/.agents/skills/lifecycle/start.sh +21 -0
  319. package/.agents/skills/lifecycle/status.sh +35 -0
  320. package/.agents/skills/lifecycle/stop.sh +16 -0
  321. package/.agents/skills/muninn-stow/README.md +3 -0
  322. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  323. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  324. package/.agents/skills/nornir-schedule/README.md +3 -0
  325. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  326. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  327. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  328. package/.agents/skills/nsr-compliance/README.md +3 -0
  329. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  431. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  432. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  433. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  434. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  435. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  453. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  454. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  455. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  456. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  457. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  458. package/.agents/skills/pr-ops/README.md +3 -0
  459. package/.agents/skills/pr-ops/SKILL.md +109 -0
  460. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  461. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  462. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  463. package/.agents/skills/saga-bearings/README.md +3 -0
  464. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  465. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  466. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  467. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  468. package/.agents/skills/syn-recovery/README.md +3 -0
  469. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  470. package/.agents/skills/tyr-check/README.md +3 -0
  471. package/.agents/skills/tyr-check/SKILL.md +218 -0
  472. package/.agents/skills/urdh-hold/README.md +3 -0
  473. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  474. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  475. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  476. package/.agents/skills/vor-diagnostics/README.md +3 -0
  477. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  478. package/.agents/skills/ymir-host/README.md +3 -0
  479. package/.agents/skills/ymir-host/SKILL.md +34 -0
  480. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  481. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  482. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  483. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  484. package/.agents/skills/ymir-host/assets/update.md +64 -0
  485. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  486. package/.agents/tests/cmux-test-safety.sh +51 -0
  487. package/.agents/tests/fixtures.sh +291 -0
  488. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  489. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  490. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  491. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  492. package/.agents/tests/fm-afk-return.test.sh +283 -0
  493. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  494. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  495. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  496. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  497. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  498. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  499. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  500. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  501. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  502. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  503. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  504. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  505. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  506. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  507. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  508. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  509. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  510. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  511. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  512. package/.agents/tests/fm-backend.test.sh +1142 -0
  513. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  514. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  515. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  516. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  517. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  518. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  519. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  520. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  521. package/.agents/tests/fm-brief.test.sh +785 -0
  522. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  523. package/.agents/tests/fm-busy-state.test.sh +465 -0
  524. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  525. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  526. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  527. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  528. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  529. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  530. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  531. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  532. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  533. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  534. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  535. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  536. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  537. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  538. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  539. package/.agents/tests/fm-control.test.sh +910 -0
  540. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  541. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  542. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  543. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  544. package/.agents/tests/fm-daemon.test.sh +2740 -0
  545. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  546. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  547. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  548. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  549. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  550. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  551. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  552. package/.agents/tests/fm-gotmp.test.sh +230 -0
  553. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  554. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  555. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  556. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  557. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  558. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  559. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  560. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  561. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  562. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  563. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  564. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  565. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  566. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  567. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  568. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  569. package/.agents/tests/fm-lint.test.sh +1023 -0
  570. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  571. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  572. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  573. package/.agents/tests/fm-on.test.sh +507 -0
  574. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  575. package/.agents/tests/fm-operational-input.test.sh +160 -0
  576. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  577. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  578. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  579. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  580. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  581. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  582. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  583. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  584. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  585. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  586. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  587. package/.agents/tests/fm-procevent.test.sh +1745 -0
  588. package/.agents/tests/fm-project-origin.test.sh +110 -0
  589. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  590. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  591. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  592. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  593. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  594. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  595. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  596. package/.agents/tests/fm-remote-job.test.sh +768 -0
  597. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  598. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  599. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  600. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  601. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  602. package/.agents/tests/fm-review-diff.test.sh +176 -0
  603. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  604. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  605. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  606. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  607. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  608. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  609. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  610. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  611. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  612. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  613. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  614. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  615. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  616. package/.agents/tests/fm-send-settle.test.sh +147 -0
  617. package/.agents/tests/fm-send-strict.test.sh +241 -0
  618. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  619. package/.agents/tests/fm-session-start.test.sh +2515 -0
  620. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  621. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  622. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  623. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  624. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  625. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  626. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  627. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  628. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  629. package/.agents/tests/fm-startup-network.test.sh +712 -0
  630. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  631. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  632. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  633. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  634. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  635. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  636. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  637. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  638. package/.agents/tests/fm-teardown.test.sh +2665 -0
  639. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  640. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  641. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  642. package/.agents/tests/fm-test-run.test.sh +1196 -0
  643. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  644. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  645. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  646. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  647. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  648. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  649. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  650. package/.agents/tests/fm-update.test.sh +304 -0
  651. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  652. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  653. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  654. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  655. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  656. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  657. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  658. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  659. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  660. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  661. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  662. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  663. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  664. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  665. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  666. package/.agents/tests/herdr-test-safety.sh +42 -0
  667. package/.agents/tests/lib.sh +315 -0
  668. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  669. package/.agents/tests/secondmate-helpers.sh +193 -0
  670. package/.agents/tests/smoke.test.sh +93 -0
  671. package/.agents/tests/wake-helpers.sh +338 -0
  672. package/.agents/tests/zellij-test-safety.sh +60 -0
  673. package/.agents/tools/README.md +10 -0
  674. package/.agents/tools/bin/tasks-cli +267 -0
  675. package/.agents/tools/tasks-cli.ts +216 -0
  676. package/AGENTS.md +454 -0
  677. package/CHANGELOG.md +881 -0
  678. package/LICENSE +1 -1
  679. package/NOTICE +63 -0
  680. package/README.md +113 -39
  681. package/RULES/01-domains.md +51 -0
  682. package/RULES/02-agents.md +78 -0
  683. package/RULES/03-houses.md +61 -0
  684. package/RULES/04-hoard.md +65 -0
  685. package/RULES/05-platforms.md +67 -0
  686. package/RULES/06-append-only.md +58 -0
  687. package/RULES/07-config.md +45 -0
  688. package/RULES/08-delivery-gate.md +75 -0
  689. package/RULES/README.md +20 -0
  690. package/bin/README.md +107 -0
  691. package/bin/a2a-mcp.sh +98 -0
  692. package/bin/a2a-serve.py +103 -0
  693. package/bin/a2a-serve.sh +8 -0
  694. package/bin/a2a-talk.sh +50 -0
  695. package/bin/a2abridge-ensure.sh +108 -0
  696. package/bin/agent-run.sh +43 -0
  697. package/bin/agents-config.sh +233 -0
  698. package/bin/apodex-smoke-test.sh +92 -0
  699. package/bin/bifrost-bridge.sh +112 -0
  700. package/bin/bifrost-ingress.sh +77 -0
  701. package/bin/bootstrap-macos.sh +107 -0
  702. package/bin/bootstrap-windows.ps1 +112 -0
  703. package/bin/branch-guard.sh +64 -0
  704. package/bin/brokk +73 -0
  705. package/bin/brokk-classify-lib.sh +1770 -0
  706. package/bin/brokk-lease-lib.sh +222 -0
  707. package/bin/brokk-lease.sh +193 -0
  708. package/bin/brokk-lint.sh +65 -0
  709. package/bin/brokk-send.sh +38 -0
  710. package/bin/brokk-update.sh +9 -0
  711. package/bin/brokk-wake-grant.sh +127 -0
  712. package/bin/brokk-wake-lib.sh +1849 -0
  713. package/bin/changelog-guard.sh +165 -0
  714. package/bin/crash-sense.sh +160 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +207 -0
  718. package/bin/eindri-acclaim.sh +34 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seen.sh +41 -0
  722. package/bin/eindri-send.sh +15 -0
  723. package/bin/eindri-start.sh +85 -0
  724. package/bin/eindri-watch.sh +57 -0
  725. package/bin/einherjar-spawn.sh +576 -0
  726. package/bin/eir-doctor.sh +112 -0
  727. package/bin/erindi-brief.sh +271 -0
  728. package/bin/fleet-apply.sh +68 -0
  729. package/bin/github-deploy.sh +81 -0
  730. package/bin/gjallarhorn-expose.sh +102 -0
  731. package/bin/gjallarhorn-notify.sh +58 -0
  732. package/bin/gjallarhorn-purge.sh +54 -0
  733. package/bin/gjallarhorn-tunnel.sh +72 -0
  734. package/bin/gleipnir-lock-lib.sh +284 -0
  735. package/bin/groa-update.sh +122 -0
  736. package/bin/hall-snapshot.sh +92 -0
  737. package/bin/hamr-harness.sh +246 -0
  738. package/bin/heimdall-ssh-keys.sh +374 -0
  739. package/bin/herdr-agents.py +44 -0
  740. package/bin/herdr-ensure.sh +104 -0
  741. package/bin/herdr-run.sh +431 -0
  742. package/bin/hermes-ensure.sh +88 -0
  743. package/bin/hoard-lib.sh +25 -0
  744. package/bin/hodd.sh +78 -0
  745. package/bin/host-sense.sh +199 -0
  746. package/bin/huginn-research-worker.sh +108 -0
  747. package/bin/local-model-lock.sh +51 -0
  748. package/bin/mimir-bridge.py +226 -0
  749. package/bin/mimir-bridge.sh +87 -0
  750. package/bin/mimir-ingest.sh +131 -0
  751. package/bin/mimir.sh +113 -0
  752. package/bin/mjollnir-webhook.sh +59 -0
  753. package/bin/mjollnir.sh +85 -0
  754. package/bin/model-hardware.sh +82 -0
  755. package/bin/model-resolve.sh +147 -0
  756. package/bin/models-detect.sh +178 -0
  757. package/bin/nornir-cron-start.sh +162 -0
  758. package/bin/nornir-job-daily-briefing.sh +191 -0
  759. package/bin/nornir-job-git-sync.sh +152 -0
  760. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  761. package/bin/nornir-job-observer.sh +166 -0
  762. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  763. package/bin/omarchy-hook-install.sh +60 -0
  764. package/bin/omarchy-install.sh +148 -0
  765. package/bin/omarchy-plugins.sh +174 -0
  766. package/bin/omarchy-sense.sh +149 -0
  767. package/bin/perm-guard.sh +30 -0
  768. package/bin/pi-agent.sh +68 -0
  769. package/bin/pi-ensure.sh +77 -0
  770. package/bin/pi-local.sh +37 -0
  771. package/bin/pi-seat.sh +63 -0
  772. package/bin/prereq-ensure.sh +166 -0
  773. package/bin/private-guard.sh +38 -0
  774. package/bin/project-git.sh +55 -0
  775. package/bin/public-guard.sh +70 -0
  776. package/bin/ratatoskr.sh +49 -0
  777. package/bin/realm-lib.sh +32 -0
  778. package/bin/repo-scrub.sh +62 -0
  779. package/bin/rodd-operational-input.sh +250 -0
  780. package/bin/runes-append.sh +198 -0
  781. package/bin/saga-session-start.sh +172 -0
  782. package/bin/saga-sessionstart-run.sh +79 -0
  783. package/bin/saga-wake-drain.sh +41 -0
  784. package/bin/secret-guard.sh +63 -0
  785. package/bin/sessrumnir-ensure.sh +100 -0
  786. package/bin/sessrumnir-sync.sh +237 -0
  787. package/bin/sessrumnir.sh +91 -0
  788. package/bin/skillopt-setup.sh +76 -0
  789. package/bin/skuld-branch-outcome.sh +203 -0
  790. package/bin/skuld-branch-prompt.sh +101 -0
  791. package/bin/smidja-bootstrap.sh +90 -0
  792. package/bin/smidja-observe.sh +86 -0
  793. package/bin/syn-arm-pretool-check.sh +57 -0
  794. package/bin/syn-asset-pretool-check.sh +76 -0
  795. package/bin/syn-cd-pretool-check.sh +28 -0
  796. package/bin/syn-guard-pretool-check.sh +83 -0
  797. package/bin/syn-turnend-guard.sh +36 -0
  798. package/bin/syn-watch-arm.sh +99 -0
  799. package/bin/tailscale-sync.sh +190 -0
  800. package/bin/telegram-bot.sh +69 -0
  801. package/bin/toolchain.sh +74 -0
  802. package/bin/utgard.sh +111 -0
  803. package/bin/valhalla.sh +115 -0
  804. package/bin/valknut-load.sh +260 -0
  805. package/bin/vor-crew-state.sh +124 -0
  806. package/bin/wedge-notify.sh +47 -0
  807. package/bin/workspace-provision.sh +80 -0
  808. package/bin/workspace-rag.sh +150 -0
  809. package/bin/wyrd-db.sh +47 -0
  810. package/bin/yggdrasil.sh +152 -0
  811. package/bin/ymir-edit.sh +145 -0
  812. package/bin/ymir-install.sh +641 -0
  813. package/bin/ymir-invite.sh +93 -0
  814. package/bin/ymir-isolation.sh +88 -0
  815. package/bin/ymir-migrate.sh +61 -0
  816. package/bin/ymir-platform.sh +348 -0
  817. package/bin/ymir-say.sh +97 -0
  818. package/bin/ymir-setup-auth.sh +108 -0
  819. package/bin/ymir-validate.sh +173 -0
  820. package/bin/ymir.js +74 -16
  821. package/docs/Architecture.md +339 -0
  822. package/docs/design.md +351 -0
  823. package/docs/installations/README.md +63 -0
  824. package/docs/installations/ymir-home.md +93 -0
  825. package/docs/integration.md +105 -0
  826. package/docs/lore.md +1056 -0
  827. package/docs/research/agent-harnesses.md +290 -0
  828. package/docs/runbooks/README.md +19 -0
  829. package/docs/runbooks/a2a.md +91 -0
  830. package/docs/runbooks/agent-permissions.md +99 -0
  831. package/docs/runbooks/agents.md +57 -0
  832. package/docs/runbooks/models.md +69 -0
  833. package/docs/runbooks/realm-onboarding.md +60 -0
  834. package/docs/runbooks/secrets-and-hoard.md +60 -0
  835. package/docs/runbooks/updates-and-migrations.md +110 -0
  836. package/docs/runbooks/worktrees-isolation.md +72 -0
  837. package/docs/session-start.md +105 -0
  838. package/docs/skillopt-integration.md +321 -0
  839. package/docs/sync-system.md +66 -0
  840. package/docs/workspaces.md +104 -0
  841. package/docs/ymir-rut.md +260 -0
  842. package/package.json +44 -14
  843. package/packaging/build.sh +74 -0
  844. package/packaging/macos/Ymir Installer.command +43 -0
  845. package/packaging/windows/ymir-setup.nsi +74 -0
  846. package/scripts/README.md +22 -0
  847. package/scripts/electron.sh +210 -0
  848. package/scripts/start.sh +167 -0
  849. package/scripts/stop.sh +113 -0
  850. package/install.sh +0 -35
@@ -0,0 +1,1059 @@
1
+ #!/usr/bin/env bash
2
+ # Steer a task by durable record: write the message into the task's steering
3
+ # inbox and ring a constant doorbell line into its terminal, best-effort.
4
+ # Usage: fm-send.sh <target> [--resolve-key <key>]... [--fire-and-forget <delivery-id>] <text...>
5
+ # <target> may be an exact task id, a legacy fm-<id> task label resolved
6
+ # through this home's state/<id>.meta, or an explicit well-formed backend
7
+ # target. fm-send refuses unresolved guesses rather than falling back to a
8
+ # tmux window search, because a "successful" send to the wrong endpoint is
9
+ # worse than a loud failure.
10
+ # Special keys instead of text: fm-send.sh <target> --key Enter
11
+ # Key support is backend-specific: tmux/herdr support Escape, Enter, and C-c;
12
+ # Orca currently supports Enter and C-c only, and rejects Escape.
13
+ #
14
+ # Two data planes:
15
+ #
16
+ # INBOX - the default for text to a task recorded in this home, local and
17
+ # remote alike. The message is appended as a durable sequenced record under
18
+ # the task's steering inbox (newlines are legal) - state/<id>.inbox/ for a
19
+ # local task, or the remote home's host-local inbox reached through fm-on.sh
20
+ # for a remote secondmate - and the terminal receives only one short constant
21
+ # self-describing doorbell line plus Enter, best-effort. The durable record IS
22
+ # the delivery, so the record's fate alone governs the exit: 0 = the steer is
23
+ # durably sent (recorded); nonzero = nothing was confirmed delivered and a
24
+ # resend is appropriate (unresolvable target, an endpoint that cannot be
25
+ # locked and revalidated or that retired or changed, an unwritable record, a
26
+ # failed or lost remote transport) or a decision-close append failed after
27
+ # delivery (the error then carries the exact manual close). The remote enqueue
28
+ # is idempotent: the remote leg deduplicates an exact re-run of the same
29
+ # request onto the existing record (bin/fm-task-inbox-lib.sh), so after a lost
30
+ # transport (ssh exit 255, completion unknown) fm-send retries the same leg
31
+ # once itself. For an ordinary reply-bearing request, a later re-run is
32
+ # idempotent only through the printed FM_PENDING_REPLY_EXISTING_CORR=<corr>
33
+ # command: it preserves the same correlation, body, and record, while a plain
34
+ # re-run mints a new correlation and delivers a separate record. An explicit
35
+ # fire-and-forget request instead retries with its same caller-supplied delivery
36
+ # id. A still-unconfirmed reply-bearing request keeps its reply expectation
37
+ # preserved for the record that may have landed.
38
+ # Pending-reply bookkeeping trouble after a durable enqueue NEVER exits
39
+ # nonzero: with the recovery marker stored the watcher reconciles it silently,
40
+ # and with both the commit and the marker lost the send prints a distinct
41
+ # "reply-tracking-degraded (steer delivered, do not resend)" warning instead,
42
+ # because a resend-inviting status there would duplicate a delivered
43
+ # instruction. There is no delivered-unconfirmed
44
+ # outcome on this plane: "did the doorbell land" is no longer the question -
45
+ # "was the message acted on" is, and that is answered asynchronously for an
46
+ # ordinary record by the worker's acknowledgement move into handled/, with the
47
+ # watcher re-ringing an unacknowledged message and escalating a stuck one. An
48
+ # explicit fire-and-forget record is excluded from that ladder.
49
+ # bin/fm-task-inbox-lib.sh owns the record format, the doorbell line, and the
50
+ # re-ring ladder. The composer pre-check before the ring is ADVISORY only: when
51
+ # the composer visibly holds pending text the ring is skipped with a notice and
52
+ # the watcher re-rings an ordinary record later; no composer verdict is
53
+ # delivery proof on this plane, and a failed ring never fails the send.
54
+ #
55
+ # TYPED - the LOCAL text that must reach the terminal itself: a harness-native
56
+ # invocation (a leading "/", or a leading "$" to a codex target) must reach
57
+ # the harness's own parser, and an explicit backend target names an endpoint,
58
+ # not a task, so it stays typed even when local metadata happens to match it
59
+ # (the same boundary that keeps it unmarked and outside --resolve-key). These
60
+ # type the literal
61
+ # text through the target backend's verified submit core: typed ONCE, then
62
+ # Enter retried (never retyped) until the backend confirms a submit or reports
63
+ # an inconclusive send. Typed-plane exit contract: 0 = submit confirmed;
64
+ # 3 = the text was typed into the live endpoint and
65
+ # Enter was sent, but the submit read-back stayed unconfirmed (verify the pane
66
+ # before any resend, and never re-type blindly; a marked request's
67
+ # pending-reply expectation stays armed because this outcome is not a proven
68
+ # failure); any other nonzero = the send failed and nothing may be assumed
69
+ # delivered. Submission dispatches through the target's recorded backend; the
70
+ # tmux adapter shares its composer/submit core with the away-mode daemon via
71
+ # bin/fm-tmux-lib.sh. Tune with FM_SEND_RETRIES (default 3) / FM_SEND_SLEEP
72
+ # (0.4). Slash commands, and codex `$...` skill invocations resolved through
73
+ # harness meta, get a longer pre-Enter settle so completion popups do not
74
+ # swallow Enter. A remote secondmate target has no typed text plane at all:
75
+ # every remote text steer rides the inbox (a marked secondmate request already
76
+ # reaches the harness as marker-prefixed chat rather than a parser command, so
77
+ # routing a remote "/..." or "$..." through the record changes nothing the
78
+ # parser would have seen); only --key still crosses to the remote pane as a
79
+ # keystroke.
80
+ #
81
+ # Stage-1 compatibility boundary: classification uses the original pre-marker
82
+ # text, but secondmate marking still precedes every typed submission. Therefore
83
+ # a marked parser-native secondmate invocation intentionally reaches the harness
84
+ # as marker-prefixed chat rather than executing as a parser command. This is a
85
+ # pre-existing interaction retained for byte compatibility in this local-inbox
86
+ # stage; do not move the marker behind the invocation or omit it here. Follow-up
87
+ # fm-send-secondmate-harness-invocation-r1 owns that behavior.
88
+ #
89
+ # From-firstmate marker: when the resolved target is a task selector whose meta
90
+ # records kind=secondmate, the message uses the live-charter-compatible
91
+ # from-firstmate carrier owned by bin/fm-operational-input.sh so the secondmate
92
+ # routes its reply via its status file or a status-pointed doc instead of
93
+ # stranding it in chat the main firstmate never reads. On the inbox plane the
94
+ # marker travels verbatim inside the recorded body. A crewmate/scout target,
95
+ # an explicit backend-target escape-hatch target, and the --key path are never
96
+ # marked - their behavior is unchanged.
97
+ #
98
+ # Parent-owned pending-reply expectation: every newly marked secondmate request
99
+ # except an explicit --fire-and-forget delivery receives a privacy-safe
100
+ # correlation id and a durable parent record under state/pending-replies/ before
101
+ # delivery (bin/fm-pending-reply-lib.sh). Delivery
102
+ # success and reply success are separate facts: delivery never resolves the
103
+ # expectation. On the inbox plane the durable enqueue IS delivery to the task's
104
+ # record, so the expectation is marked delivered at enqueue time; when that
105
+ # bookkeeping commit fails after its durable recovery marker is stored, the
106
+ # send remains successful and watcher reconciliation owns the repair, and when
107
+ # the commit and marker are BOTH lost the send still remains successful with a
108
+ # reply-tracking-degraded warning naming the expectation an operator must
109
+ # inspect (it can no longer reconcile or escalate on its own). Only a
110
+ # failed enqueue discards the expectation. On the typed plane an unconfirmed submit (exit 3) keeps
111
+ # it armed rather than dropping it, and only a proven send failure discards it.
112
+ # Set FM_PENDING_REPLY_EXISTING_CORR=<id> when re-sending a recovery request
113
+ # for an already-open expectation so a second record is not created. Direct
114
+ # unmarked captain input never creates one. A marked secondmate instruction
115
+ # sent with --fire-and-forget <16-hex-delivery-id> uses the same inbox transport
116
+ # without creating a reply expectation; its delivery id makes uncertain retries
117
+ # idempotent while allowing a later identical instruction to be distinct.
118
+ #
119
+ # Remote secondmate delivery: the send crosses fm-on.sh to a host-local leg
120
+ # (bin/fm-remote-secondmate-control.sh cmd_send) that writes the message as a
121
+ # durable record into the remote home's steering inbox and rings the remote
122
+ # doorbell, best-effort. The remote record is the delivery, exactly as it is
123
+ # locally: leg exit 0 means durably recorded (fm-send then exits 0, marks the
124
+ # pending-reply expectation delivered, and closes any --resolve-key
125
+ # decisions), and any real remote failure fails loudly with the remote leg's
126
+ # own stderr attached. Transport loss (ssh exit 255) means completion unknown,
127
+ # so fm-send retries the identical leg once - safe because the remote write
128
+ # deduplicates the same request onto the same record - and a still-lost
129
+ # transport exits nonzero while preserving a reply-bearing marked request's
130
+ # expectation, since the record may have landed. Its error prints the exact
131
+ # FM_PENDING_REPLY_EXISTING_CORR=<id> resend command that preserves the body
132
+ # and makes a later remote enqueue deduplicate onto that same record. An
133
+ # unconfirmed fire-and-forget request exits 3 and names the same delivery id to
134
+ # retry. Every remote transport attempt is bounded by FM_SEND_REMOTE_BUDGET
135
+ # seconds (default 30, and any override must be a positive integer): a bound
136
+ # hit is completion-unknown and exits through this same unconfirmed contract
137
+ # instead of waiting out a busy remote queue.
138
+ # The remote host runs no re-ring ladder of its own: a swallowed ordinary
139
+ # doorbell surfaces through the parent's pending-reply recovery and escalation,
140
+ # whose recovery request re-rings the remote doorbell when it is enqueued;
141
+ # fire-and-forget delivery deliberately arms neither mechanism. Internal
142
+ # semantic callers may set FM_SEND_EXPECTED_SPAWN_GEN or
143
+ # FM_SEND_EXPECTED_REMOTE_HOST to require that sampled identity to still match
144
+ # during the final locked remote-route validation; unset or empty guards do not
145
+ # change ordinary sends.
146
+ #
147
+ # Decision closure (answerer-closes): pass --resolve-key <key> (repeatable,
148
+ # before the message) when this send answers an open keyed needs-decision: or
149
+ # blocked: record in the target task's state/<id>.status. fm-send itself
150
+ # appends the closing "resolved [key=<key>]: answered: <capped excerpt>" line
151
+ # to that status file, so the captain-facing OPEN DECISIONS record closes at
152
+ # answer time and never depends on the busy worker writing a matching resolved
153
+ # line. On the inbox plane the close happens at ENQUEUE time, because enqueue
154
+ # is durable delivery to the task's record; the worker reading the answer late
155
+ # is covered by the acknowledgement re-ring ladder. On the typed plane it
156
+ # still waits for the confirmed submit. The close is a LOCAL append for every
157
+ # target kind - crewmate, scout, local secondmate, and remote secondmate alike
158
+ # - because the open-decision ledger fm-wake-drain folds lives in this home's
159
+ # own state dir (a remote mate's escalations reach it through the
160
+ # parent-replies ingest); only the answer message crosses the backend or
161
+ # remote transport.
162
+ #
163
+ # Chat is also a channel that carries keyed captain answers, so the same flag
164
+ # feeds bin/fm-captain-hold.sh's one keyed-answer intake for any key that names
165
+ # a captain-held task in this home - the key as a task id itself, or through
166
+ # the legacy `<task>-decision-<key>` identity for pre-collapse rows. fm-send
167
+ # closes nothing itself; it hands the intake `<task-id>\t<answer>\t<label>`
168
+ # exactly as every other channel does, and the intake owns what that means.
169
+ # This is what lets an answer reach a decision that has already been
170
+ # transferred from the live status log to its durable captain-held task, which
171
+ # the status ledger alone can no longer close.
172
+ #
173
+ # Each named key must therefore currently be open in ONE of the two ledgers: open
174
+ # in this home's status log per status_open_decisions (bin/fm-classify-lib.sh), or
175
+ # a still-open captain-held task resolved as above. A key in neither is refused
176
+ # before sending, so a mistyped key cannot deliver an answer while silently
177
+ # orphaning the decision. A failed or unconfirmed send never closes a key; a
178
+ # delivered answer whose closing append fails exits nonzero with the exact
179
+ # manual close command, leaving the decision open to re-surface (the safe
180
+ # direction). A send without the flag never closes anything: a routine steer,
181
+ # working:, or done: event still cannot clear a captain decision. The flag is
182
+ # refused with --key, with an explicit backend target (no task ledger in this
183
+ # home), and with an empty message.
184
+ #
185
+ # After a successful TYPED-plane submit fm-send pauses FM_SEND_SETTLE seconds
186
+ # (default 1, 0 disables) before returning: submit confirmation only proves the
187
+ # text was accepted, but the harness needs a beat to spin up the turn before its
188
+ # busy footer appears, so an immediate peek would otherwise see the stale idle
189
+ # pane. The pause is typed-plane-only; the inbox plane, the shared submit core
190
+ # (used by the away-mode daemon, which only needs "submitted"), and the --key
191
+ # path do not pay it.
192
+ set -eu
193
+
194
+ FM_SEND_ORIGINAL_ARGS=("$@")
195
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
196
+ FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
197
+
198
+ # shellcheck source=bin/fm-gate-refuse-lib.sh
199
+ . "$SCRIPT_DIR/fm-gate-refuse-lib.sh"
200
+ # Fail closed before any fleet mutation: a no-mistakes gate agent must never steer
201
+ # a crewmate (see bin/fm-gate-refuse-lib.sh).
202
+ fm_refuse_if_gate_agent
203
+
204
+ if [ -z "${FM_HOME+x}" ] || [ -z "${FM_HOME:-}" ]; then
205
+ echo "error: FM_HOME is not set; fm-send refuses to resolve targets without an explicit firstmate home" >&2
206
+ exit 1
207
+ fi
208
+
209
+ STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
210
+ if [ ! -d "$FM_HOME" ]; then
211
+ echo "error: FM_HOME '$FM_HOME' is not a directory; fm-send cannot resolve this home's state" >&2
212
+ exit 1
213
+ fi
214
+ if [ ! -d "$STATE" ]; then
215
+ echo "error: state dir '$STATE' is missing; fm-send cannot resolve targets for FM_HOME '$FM_HOME'" >&2
216
+ exit 1
217
+ fi
218
+
219
+ # shellcheck source=bin/fm-backend.sh
220
+ . "$SCRIPT_DIR/fm-backend.sh"
221
+ # shellcheck source=bin/fm-control-lib.sh
222
+ . "$SCRIPT_DIR/fm-control-lib.sh"
223
+ # shellcheck source=bin/fm-marker-lib.sh
224
+ . "$SCRIPT_DIR/fm-marker-lib.sh"
225
+ # shellcheck source=bin/fm-pending-reply-lib.sh
226
+ . "$SCRIPT_DIR/fm-pending-reply-lib.sh"
227
+ # shellcheck source=bin/fm-classify-lib.sh
228
+ . "$SCRIPT_DIR/fm-classify-lib.sh"
229
+ # shellcheck source=bin/fm-line-cap-lib.sh
230
+ . "$SCRIPT_DIR/fm-line-cap-lib.sh"
231
+ # shellcheck source=bin/fm-wake-lib.sh
232
+ . "$SCRIPT_DIR/fm-wake-lib.sh"
233
+ # shellcheck source=bin/fm-task-inbox-lib.sh
234
+ . "$SCRIPT_DIR/fm-task-inbox-lib.sh"
235
+ # shellcheck source=bin/fm-timeout-lib.sh
236
+ . "$SCRIPT_DIR/fm-timeout-lib.sh"
237
+
238
+ FM_GUARD_CONTINUE_LINE='This is a supervision warning only; the requested message WILL still be sent.' "$SCRIPT_DIR/fm-guard.sh" || true
239
+
240
+ fm_send_id_from_meta() { # <meta-file>
241
+ local base
242
+ base=${1##*/}
243
+ printf '%s' "${base%.meta}"
244
+ }
245
+
246
+ # fm_send_clear_after_interrupt: muse RESTORES the interrupted prompt back into
247
+ # the composer when Escape cancels a turn, as real bright text (verified: fg
248
+ # 38;2;204;211;219, luminance ~210, muse 0.1.0-R708.1), not de-emphasised ghost
249
+ # text. Classifying that as pending input is correct - the text really is
250
+ # unsubmitted - but leaving it there means the NEXT steer types onto the end of
251
+ # it and submits both as one garbled message. Ctrl-U clears the composer
252
+ # (verified), so the interrupt is not complete until it has been sent. A failed
253
+ # clear is loud rather than silent, because the alternative is a corrupted steer.
254
+ # WHICH adapters need that clear, and which key clears them, comes from the one
255
+ # control-plane capability table (bin/fm-control-lib.sh) rather than a second
256
+ # copy here - the same table bin/fm-control.sh's interrupt verb reads.
257
+ fm_send_clear_after_interrupt() { # <key>
258
+ local key=$1 family clear
259
+ [ "$key" = Escape ] || return 0
260
+ family=$(fm_control_harness_family "$TARGET_HARNESS") || return 0
261
+ clear=$(fm_control_interrupt_clear_key "$family") || return 0
262
+ [ -n "$clear" ] || return 0
263
+ [ "$TARGET_BACKEND" != remote ] || return 0
264
+ if ! fm_backend_send_key "$TARGET_BACKEND" "$T" "$clear" "$EXPECTED_LABEL"; then
265
+ echo "error: Escape reached $T, but the $TARGET_HARNESS composer could not be cleared; it still holds the restored prompt. Clear it before sending the next message." >&2
266
+ return 1
267
+ fi
268
+ }
269
+
270
+ fm_send_normalize_key() { # <key>
271
+ case "$1" in
272
+ Escape|escape|Esc|esc) printf '%s' Escape ;;
273
+ *) printf '%s' "$1" ;;
274
+ esac
275
+ }
276
+
277
+ fm_send_record_interrupt() { # <key>
278
+ local key=$1 id gen
279
+ [ "$key" = Escape ] || return 0
280
+ case "$TARGET_HARNESS" in claude*) : ;; *) return 0 ;; esac
281
+ [ -n "$TARGET_META" ] || return 0
282
+ id=$(fm_send_id_from_meta "$TARGET_META")
283
+ [ -f "$STATE/$id.busy-gen" ] || return 0
284
+ gen=$(fm_meta_get "$TARGET_META" busy_gen)
285
+ if [ -n "$gen" ]; then
286
+ "$FM_ROOT/bin/fm-busy-event.sh" apply "$STATE" "$id" idle \
287
+ --gen "$gen" --source fm-interrupt --event interrupt
288
+ else
289
+ "$FM_ROOT/bin/fm-busy-event.sh" apply "$STATE" "$id" idle \
290
+ --current-gen --source fm-interrupt --event interrupt
291
+ fi || {
292
+ echo "error: key '$key' reached $T, but the Claude interrupt state could not be recorded for $id" >&2
293
+ return 1
294
+ }
295
+ }
296
+
297
+ fm_send_meta_for_key_value() { # <state-dir> <key> <value>
298
+ local state=$1 key=$2 value=$3 meta got
299
+ for meta in "$state"/*.meta; do
300
+ [ -e "$meta" ] || continue
301
+ got=$(fm_meta_get "$meta" "$key")
302
+ [ "$got" = "$value" ] || continue
303
+ printf '%s' "$meta"
304
+ return 0
305
+ done
306
+ return 1
307
+ }
308
+
309
+ fm_send_count_colons() { # <string>
310
+ local s=$1 no_colons
311
+ no_colons=${s//:/}
312
+ printf '%s' $(( ${#s} - ${#no_colons} ))
313
+ }
314
+
315
+ fm_send_resolve_target() { # <raw-target>
316
+ local raw=$1 meta pane_meta target backend assumed colons id session hint
317
+
318
+ RESOLVED_TARGET=""
319
+ TARGET_BACKEND=""
320
+ TARGET_HARNESS=""
321
+ EXPECTED_LABEL=""
322
+ TARGET_META=""
323
+ TARGET_SELECTOR=""
324
+ TARGET_REMOTE_ID=""
325
+ TARGET_REMOTE_HOST=""
326
+ RESOLUTION_TRIED=""
327
+
328
+ meta=$(fm_backend_meta_for_selector "$raw" "$STATE" 2>/dev/null || true)
329
+ if [ -n "$meta" ]; then
330
+ if [ -n "$(fm_meta_get "$meta" remote_host)" ]; then
331
+ id=$(fm_send_id_from_meta "$meta")
332
+ RESOLVED_TARGET="remote:$id"
333
+ TARGET_BACKEND=remote
334
+ TARGET_META=$meta
335
+ TARGET_HARNESS=$(fm_meta_get "$meta" harness)
336
+ EXPECTED_LABEL="fm-$id"
337
+ TARGET_SELECTOR=1
338
+ TARGET_REMOTE_ID=$id
339
+ TARGET_REMOTE_HOST=$(fm_meta_get "$meta" remote_host)
340
+ RESOLUTION_TRIED="meta=$meta; placement=remote"
341
+ return 0
342
+ fi
343
+ RESOLUTION_TRIED="meta=$meta; backend=from-meta"
344
+ target=$(fm_backend_target_of_meta "$meta")
345
+ if [ -z "$target" ]; then
346
+ echo "error: no backend target recorded in $meta (tried $RESOLUTION_TRIED)" >&2
347
+ return 1
348
+ fi
349
+ backend=$(fm_backend_of_meta "$meta")
350
+ RESOLVED_TARGET=$target
351
+ TARGET_BACKEND=$backend
352
+ TARGET_META=$meta
353
+ TARGET_HARNESS=$(fm_meta_get "$meta" harness)
354
+ EXPECTED_LABEL=$(fm_backend_expected_label_of_selector "$raw" "$STATE")
355
+ TARGET_SELECTOR=1
356
+ return 0
357
+ fi
358
+
359
+ case "$raw" in
360
+ fm-*:*)
361
+ # A named Herdr session may itself begin with "fm-". Keep that explicit
362
+ # session:pane target on the validated backend-target path below rather
363
+ # than mistaking it for an unresolved task selector.
364
+ ;;
365
+ fm-*)
366
+ RESOLUTION_TRIED="meta=$STATE/$raw.meta; legacy-meta=$STATE/${raw#fm-}.meta; backend=none"
367
+ echo "error: no metadata for $raw in $STATE (tried $RESOLUTION_TRIED); pass a well-formed explicit backend target only when targeting outside this firstmate home" >&2
368
+ return 1
369
+ ;;
370
+ esac
371
+
372
+ pane_meta=$(fm_send_meta_for_key_value "$STATE" herdr_pane_id "$raw" 2>/dev/null || true)
373
+ if [ -n "$pane_meta" ]; then
374
+ session=$(fm_meta_get "$pane_meta" herdr_session)
375
+ hint="${session:-<herdr-session>}:$raw"
376
+ id=$(fm_send_id_from_meta "$pane_meta")
377
+ echo "error: target '$raw' matches herdr_pane_id in $pane_meta but is missing its herdr session prefix; expected <herdr-session>:<pane-id> such as '$hint' or use 'fm-$id' (tried meta=$STATE/$raw.meta; backend=herdr)" >&2
378
+ return 1
379
+ fi
380
+
381
+ meta=$(fm_backend_meta_for_window "$raw" "$STATE" 2>/dev/null || true)
382
+ if [ -n "$meta" ]; then
383
+ target=$(fm_backend_target_of_meta "$meta")
384
+ if [ -z "$target" ]; then
385
+ echo "error: no backend target recorded in $meta (tried explicit target '$raw' via recorded window/terminal; backend=from-meta)" >&2
386
+ return 1
387
+ fi
388
+ RESOLVED_TARGET=$target
389
+ TARGET_BACKEND=$(fm_backend_of_meta "$meta")
390
+ TARGET_META=$meta
391
+ TARGET_HARNESS=$(fm_meta_get "$meta" harness)
392
+ RESOLUTION_TRIED="explicit target '$raw' matched $meta; backend=$TARGET_BACKEND"
393
+ return 0
394
+ fi
395
+
396
+ case "$raw" in
397
+ *:*)
398
+ colons=$(fm_send_count_colons "$raw")
399
+ if [ "$colons" -ge 2 ]; then
400
+ assumed=herdr
401
+ else
402
+ assumed=tmux
403
+ fi
404
+ if ! fm_backend_target_exists "$assumed" "$raw"; then
405
+ echo "error: explicit target '$raw' is not a live $assumed endpoint (tried meta=$STATE/$raw.meta; metadata window/terminal lookup; backend=$assumed). Use fm-<id> for a recorded task/lane, or pass a target whose backend endpoint can be verified." >&2
406
+ return 1
407
+ fi
408
+ RESOLVED_TARGET=$raw
409
+ TARGET_BACKEND=$assumed
410
+ RESOLUTION_TRIED="meta=$STATE/$raw.meta; metadata window/terminal lookup; backend=$assumed; endpoint=verified"
411
+ return 0
412
+ ;;
413
+ esac
414
+
415
+ echo "error: target '$raw' is not resolvable (tried meta=$STATE/$raw.meta; metadata window/terminal lookup; backend=none). Use fm-$raw for a recorded task/lane, or pass a well-formed explicit backend target such as session:window." >&2
416
+ return 1
417
+ }
418
+
419
+ RAW_TARGET=$1
420
+ fm_send_resolve_target "$RAW_TARGET" || exit 1
421
+ T=$RESOLVED_TARGET
422
+ shift
423
+
424
+ # Supervision lease guard: a steer is overlap territory between the two Pi
425
+ # supervision actors, so refuse while the OTHER actor holds this task's live
426
+ # lease. A home with no supervision branch has no lease files and passes
427
+ # untouched (contract: bin/fm-lease-lib.sh).
428
+ # shellcheck source=bin/fm-lease-lib.sh
429
+ . "$SCRIPT_DIR/fm-lease-lib.sh"
430
+ if [ -n "$TARGET_META" ]; then
431
+ LEASE_GUARD_TASK=$(fm_send_id_from_meta "$TARGET_META")
432
+ if [ -n "$LEASE_GUARD_TASK" ]; then
433
+ fm_lease_guard "$LEASE_GUARD_TASK" "steer (fm-send)"
434
+ trap 'fm_lease_guard_release' EXIT
435
+ fi
436
+ fi
437
+
438
+ # Collect --resolve-key flags (answerer-closes; see the header contract). They
439
+ # must precede --key or the message text; everything after the last flag is the
440
+ # message exactly as before, so ordinary sends are byte-identical.
441
+ RESOLVE_KEYS=
442
+ FIRE_AND_FORGET_ID=
443
+ fm_send_add_resolve_key() { # <key>
444
+ local k=$1
445
+ case "$k" in
446
+ ''|*[!A-Za-z0-9._-]*)
447
+ echo "error: --resolve-key '$k' is not a valid decision key (allowed: A-Z a-z 0-9 . _ -)" >&2
448
+ return 1
449
+ ;;
450
+ esac
451
+ case " $RESOLVE_KEYS " in
452
+ *" $k "*)
453
+ echo "error: duplicate --resolve-key '$k'" >&2
454
+ return 1
455
+ ;;
456
+ esac
457
+ RESOLVE_KEYS="${RESOLVE_KEYS}${RESOLVE_KEYS:+ }$k"
458
+ }
459
+ while :; do
460
+ case "${1:-}" in
461
+ --resolve-key)
462
+ [ $# -ge 2 ] || { echo "error: --resolve-key requires a key" >&2; exit 1; }
463
+ fm_send_add_resolve_key "$2" || exit 1
464
+ shift 2
465
+ ;;
466
+ --resolve-key=*)
467
+ fm_send_add_resolve_key "${1#--resolve-key=}" || exit 1
468
+ shift
469
+ ;;
470
+ --fire-and-forget)
471
+ [ $# -ge 2 ] || { echo "error: --fire-and-forget requires a delivery id" >&2; exit 1; }
472
+ [ -z "$FIRE_AND_FORGET_ID" ] || { echo "error: duplicate --fire-and-forget" >&2; exit 1; }
473
+ FIRE_AND_FORGET_ID=$2
474
+ shift 2
475
+ ;;
476
+ --fire-and-forget=*)
477
+ [ -z "$FIRE_AND_FORGET_ID" ] || { echo "error: duplicate --fire-and-forget" >&2; exit 1; }
478
+ FIRE_AND_FORGET_ID=${1#--fire-and-forget=}
479
+ shift
480
+ ;;
481
+ *) break ;;
482
+ esac
483
+ done
484
+
485
+ if [ "$TARGET_BACKEND" != remote ]; then
486
+ fm_backend_validate "$TARGET_BACKEND" || exit 1
487
+ fi
488
+
489
+ # Classify a from-firstmate -> secondmate request. Only a task selector resolved
490
+ # through this home's meta whose authoritative kind is secondmate is marked: the
491
+ # secondmate then routes its reply via the status path (see fm-marker-lib.sh).
492
+ # An explicit backend target (the escape hatch for endpoints outside this home)
493
+ # and any crewmate/scout target are left unmarked, and so is the --key path.
494
+ MARK_FROM_FIRSTMATE=0
495
+ PENDING_REPLY_CORR=
496
+ PENDING_REPLY_CREATED=0
497
+ TARGET_TASK_ID=
498
+ fm_send_known_undelivered_cleanup() {
499
+ [ -n "$PENDING_REPLY_CORR" ] || return 0
500
+ if [ "$PENDING_REPLY_CREATED" = 1 ]; then
501
+ fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR"
502
+ else
503
+ fm_pending_reply_reset_known_undelivered "$STATE" "$PENDING_REPLY_CORR"
504
+ fi
505
+ }
506
+ if [ -n "$TARGET_SELECTOR" ] && [ -n "$TARGET_META" ] && [ "$(fm_meta_get "$TARGET_META" kind)" = secondmate ]; then
507
+ MARK_FROM_FIRSTMATE=1
508
+ TARGET_TASK_ID=$(fm_send_id_from_meta "$TARGET_META")
509
+ fi
510
+
511
+ # Validate the answerer-closes request before any durable mutation or send: the
512
+ # target must have a task ledger in THIS home, the send must carry an answer
513
+ # message, and every named key must be open right now in that ledger per the
514
+ # ONE authoritative fold (status_open_decisions). Refusing here, before the
515
+ # send, is what keeps a mistyped key loud instead of delivering an answer that
516
+ # silently leaves its decision open.
517
+ RESOLVE_STATUS_FILE=
518
+ # Which ledger each answered key belongs to. A key still open in the status log
519
+ # is owned by the status log: fm-captain-hold's `complete` closes that live copy
520
+ # at the moment it transfers a decision to its durable captain-held task, so
521
+ # "still open in status" and "already held" are the two sides of one transfer,
522
+ # never both at once. Checking the backlog only for keys the status log no
523
+ # longer owns also keeps the common path free of any backlog read.
524
+ RESOLVE_STATUS_KEYS=
525
+ RESOLVE_HOLD_KEYS=
526
+
527
+ # Resolve a --resolve-key key that the status log no longer owns to the
528
+ # captain-held task that carries it: the key as a task id itself (the collapsed
529
+ # identity - a captain call IS a task held for the captain), then the legacy
530
+ # derived `<task>-decision-<key>` identity for pre-collapse rows. Answerable
531
+ # means not closed and still carrying the captain-hold annotations tasks-axi
532
+ # preserves even past a hold-until date.
533
+ fm_send_hold_resolved_id() { # <task-id> <decision-key>
534
+ local show id state hold_kind
535
+ command -v tasks-axi >/dev/null 2>&1 || return 1
536
+ for id in "$2" "$1-decision-$2"; do
537
+ show=$( (cd "$FM_HOME" && tasks-axi show "$id" --full) 2>/dev/null ) || continue
538
+ state=$(printf '%s\n' "$show" | sed -n 's/^ state: //p' | head -1)
539
+ hold_kind=$(printf '%s\n' "$show" | sed -n 's/^ hold_kind: //p' | head -1)
540
+ [ "$state" != "done" ] || continue
541
+ [ "$hold_kind" = captain ] || continue
542
+ printf '%s\n' "$id"
543
+ return 0
544
+ done
545
+ return 1
546
+ }
547
+
548
+ if [ -n "$FIRE_AND_FORGET_ID" ]; then
549
+ printf '%s' "$FIRE_AND_FORGET_ID" | grep -Eq '^[a-f0-9]{16}$' \
550
+ || { echo "error: --fire-and-forget delivery id must be 16 lowercase hex characters" >&2; exit 1; }
551
+ [ "$MARK_FROM_FIRSTMATE" = 1 ] \
552
+ || { echo "error: --fire-and-forget requires a recorded secondmate task selector" >&2; exit 1; }
553
+ [ -z "$RESOLVE_KEYS" ] \
554
+ || { echo "error: --fire-and-forget cannot accompany --resolve-key" >&2; exit 1; }
555
+ fi
556
+
557
+ if [ -n "$RESOLVE_KEYS" ]; then
558
+ if [ -z "$TARGET_SELECTOR" ] || [ -z "$TARGET_META" ]; then
559
+ echo "error: --resolve-key needs a task selector resolved through this home's metadata; an explicit backend target has no decision ledger here" >&2
560
+ exit 1
561
+ fi
562
+ if [ "${1:-}" = "--key" ]; then
563
+ echo "error: --resolve-key cannot accompany --key; answering a decision requires a text answer" >&2
564
+ exit 1
565
+ fi
566
+ if [ -z "$*" ]; then
567
+ echo "error: --resolve-key requires a nonempty answer message" >&2
568
+ exit 1
569
+ fi
570
+ RESOLVE_TASK_ID=$(fm_send_id_from_meta "$TARGET_META")
571
+ RESOLVE_STATUS_FILE="$STATE/$RESOLVE_TASK_ID.status"
572
+ resolve_open_set=$(status_open_decisions "$RESOLVE_STATUS_FILE")
573
+ for k in $RESOLVE_KEYS; do
574
+ case "$resolve_open_set" in
575
+ "$k"$'\t'*|*$'\n'"$k"$'\t'*)
576
+ RESOLVE_STATUS_KEYS="${RESOLVE_STATUS_KEYS}${RESOLVE_STATUS_KEYS:+ }$k"
577
+ continue
578
+ ;;
579
+ esac
580
+ # Not open in the status log. A decision already transferred to its durable
581
+ # captain-held task is exactly this case, and it is answerable - just
582
+ # through the other ledger - so check there before refusing.
583
+ if resolved_hold_id=$(fm_send_hold_resolved_id "$RESOLVE_TASK_ID" "$k"); then
584
+ RESOLVE_HOLD_KEYS="${RESOLVE_HOLD_KEYS}${RESOLVE_HOLD_KEYS:+ }$resolved_hold_id"
585
+ continue
586
+ fi
587
+ echo "error: --resolve-key '$k': no open decision or blocker with that key in $RESOLVE_STATUS_FILE, and no captain-held task '$k' or '$RESOLVE_TASK_ID-decision-$k' still open (already closed or mistyped). Re-check the OPEN DECISIONS listing, then resend without that key or with the right one; nothing was sent." >&2
588
+ exit 1
589
+ done
590
+ fi
591
+
592
+ # Close each answered decision in this home's ledger, only after the answer is
593
+ # durably sent: enqueued on the inbox plane, submit-confirmed on the typed
594
+ # plane. An append failure exits nonzero with the manual close
595
+ # command; the decision then stays open and re-surfaces, never silently lost.
596
+ # The close is this home's own bookkeeping, written by the very turn that
597
+ # answered the decision, so it goes through the guarded self-announced append
598
+ # (bin/fm-wake-lib.sh) and does not wake this same session again; any
599
+ # concurrent foreign status bytes leave the watcher's wake path untouched.
600
+ fm_send_close_resolved_keys() { # <answer-text>
601
+ local note=$1 k line append_rc
602
+ note=$(printf '%s' "$note" | tr '\n\r\t' ' ' | LC_ALL=C tr -d '\000-\037\177')
603
+ for k in $RESOLVE_STATUS_KEYS; do
604
+ line="resolved [key=$k]: answered: $note"
605
+ fm_cap_line_var "$line"
606
+ append_rc=0
607
+ fm_wake_status_append_self_announced "$STATE" "$RESOLVE_STATUS_FILE" "$FM_LINE_CAP_LINE" || append_rc=$?
608
+ if [ "$append_rc" -eq 2 ]; then
609
+ echo "error: the answer was delivered to $T, but decision key '$k' could not be closed in $RESOLVE_STATUS_FILE. Close it manually with: echo 'resolved [key=$k]: <how it was answered>' >> $RESOLVE_STATUS_FILE - do not resend the answer." >&2
610
+ return 1
611
+ fi
612
+ done
613
+ }
614
+
615
+ # Feed the answered captain-held tasks to the ONE keyed-answer intake, as keyed
616
+ # lines, exactly the way every other channel does. fm-send decides nothing here:
617
+ # it does not build a decision record or choose a close path; the keys were
618
+ # already resolved to task ids above, so the intake needs no legacy origin.
619
+ fm_send_feed_resolved_holds() { # <answer-text>
620
+ local note=$1 k lines=''
621
+ [ -n "$RESOLVE_HOLD_KEYS" ] || return 0
622
+ note=$(printf '%s' "$note" | tr '\n\r\t' ' ' | LC_ALL=C tr -d '\000-\037\177')
623
+ for k in $RESOLVE_HOLD_KEYS; do
624
+ lines="${lines}${k}"$'\t'"${note}"$'\t'$'\n'
625
+ done
626
+ if ! printf '%s' "$lines" | "$SCRIPT_DIR/fm-captain-hold.sh" answers \
627
+ --source "a firstmate answer sent to $RESOLVE_TASK_ID" >/dev/null 2>&1; then
628
+ echo "error: the answer was delivered to $T, but this captain-held task could not be closed: ${RESOLVE_HOLD_KEYS}. Close it with fm-captain-hold.sh answer - do not resend the answer." >&2
629
+ return 1
630
+ fi
631
+ }
632
+
633
+ # Resolve the target's harness from its meta (recorded by fm-spawn), used only to
634
+ # scope the codex `$<skill>` popup-settle below. A task selector carries
635
+ # meta; an explicit backend-target escape hatch has none, so its harness is
636
+ # unknown and treated as non-codex (the safe default that keeps the fast path).
637
+ # The target's BACKEND comes from selector meta, from matching an explicit target
638
+ # back to recorded meta, or from strict explicit-target shape validation.
639
+ # Do not add a separate passive liveness preflight here. Active send paths own
640
+ # backend readiness: herdr, for example, must route through its session-aware
641
+ # target_ready path before sending, while zellij verifies pane labels in its
642
+ # send implementation. A failed backend send is still surfaced below as a hard
643
+ # error with the attempted resolution attached.
644
+
645
+ if [ "${1:-}" = "--key" ]; then
646
+ [ -z "$FIRE_AND_FORGET_ID" ] \
647
+ || { echo "error: --fire-and-forget cannot accompany --key" >&2; exit 1; }
648
+ case "$*" in
649
+ *--resolve-key*)
650
+ echo "error: --resolve-key cannot accompany --key; answering a decision requires a text answer" >&2
651
+ exit 1
652
+ ;;
653
+ esac
654
+ key=$2
655
+ semantic_key=$(fm_send_normalize_key "$key")
656
+ if [ "$TARGET_BACKEND" = remote ]; then
657
+ FM_SEND_REMOTE_BUDGET=${FM_SEND_REMOTE_BUDGET:-30}
658
+ case "$FM_SEND_REMOTE_BUDGET" in
659
+ ''|*[!0-9]*|0)
660
+ echo "error: FM_SEND_REMOTE_BUDGET must be a positive integer: $FM_SEND_REMOTE_BUDGET" >&2
661
+ exit 1
662
+ ;;
663
+ esac
664
+ if ! fm_run_timed "$FM_SEND_REMOTE_BUDGET" "$SCRIPT_DIR/fm-on.sh" "$TARGET_REMOTE_ID" \
665
+ fm-remote-secondmate-control.sh key "$TARGET_REMOTE_ID" "$key" < /dev/null; then
666
+ echo "error: key '$key' not sent to remote secondmate $TARGET_REMOTE_ID; completion may be unknown" >&2
667
+ exit 1
668
+ fi
669
+ elif ! fm_backend_send_key "$TARGET_BACKEND" "$T" "$key" "$EXPECTED_LABEL"; then
670
+ echo "error: key '$key' not sent to $T ($TARGET_BACKEND send failed; tried $RESOLUTION_TRIED)" >&2
671
+ exit 1
672
+ fi
673
+ fm_send_clear_after_interrupt "$semantic_key" || exit 1
674
+ fm_send_record_interrupt "$semantic_key" || exit 1
675
+ else
676
+ MESSAGE=$*
677
+ if [ "$TARGET_BACKEND" = remote ]; then
678
+ FM_SEND_REMOTE_BUDGET=${FM_SEND_REMOTE_BUDGET:-30}
679
+ case "$FM_SEND_REMOTE_BUDGET" in
680
+ ''|*[!0-9]*|0)
681
+ echo "error: FM_SEND_REMOTE_BUDGET must be a positive integer: $FM_SEND_REMOTE_BUDGET" >&2
682
+ exit 1
683
+ ;;
684
+ esac
685
+ fi
686
+ # The pre-marker answer text, kept for the closing resolved note so the
687
+ # durable ledger records the plain answer without marker or corr bytes.
688
+ RESOLVE_ANSWER_TEXT=$MESSAGE
689
+ if [ "$MARK_FROM_FIRSTMATE" = 1 ] && [ -n "$FIRE_AND_FORGET_ID" ]; then
690
+ fm_message_mark_from_firstmate "$MESSAGE" MESSAGE
691
+ MESSAGE="${FM_FROMFIRST_MARK}delivery=${FIRE_AND_FORGET_ID} ${MESSAGE#"$FM_FROMFIRST_MARK"}"
692
+ FM_SEND_IDEMPOTENT=1
693
+ elif [ "$MARK_FROM_FIRSTMATE" = 1 ]; then
694
+ # Reuse an existing correlation id for recovery resends; otherwise create a
695
+ # durable parent expectation before delivery. Transport success never
696
+ # resolves that expectation (see fm-pending-reply-lib.sh).
697
+ existing_corr_explicit=0
698
+ if [ "${FM_PENDING_REPLY_EXISTING_CORR+x}" = x ]; then
699
+ existing_corr_explicit=1
700
+ existing_corr=$FM_PENDING_REPLY_EXISTING_CORR
701
+ else
702
+ existing_corr=$(fm_pending_reply_extract_corr "$MESSAGE")
703
+ fi
704
+ if [ -n "$existing_corr" ] \
705
+ && fm_pending_reply_corr_reusable "$STATE" "$existing_corr" "$TARGET_TASK_ID"; then
706
+ PENDING_REPLY_CORR=$existing_corr
707
+ else
708
+ if [ "$existing_corr_explicit" = 1 ]; then
709
+ echo "error: explicitly requested pending-reply correlation '${existing_corr:-empty}' is not reusable for $TARGET_TASK_ID; refusing to mint a replacement correlation" >&2
710
+ exit 1
711
+ fi
712
+ if [ -z "$TARGET_TASK_ID" ]; then
713
+ echo "error: cannot create pending-reply expectation without a resolvable secondmate task id" >&2
714
+ exit 1
715
+ fi
716
+ PENDING_REPLY_CORR=$(fm_pending_reply_create "$FM_HOME" "$STATE" "$TARGET_TASK_ID" "$MESSAGE") \
717
+ || { echo "error: failed to create parent pending-reply expectation for $TARGET_TASK_ID" >&2; exit 1; }
718
+ PENDING_REPLY_CREATED=1
719
+ fi
720
+ fm_pending_reply_embed_corr "$MESSAGE" "$PENDING_REPLY_CORR" MESSAGE
721
+ if [ "$PENDING_REPLY_CREATED" != 1 ] \
722
+ && fm_pending_reply_delivery_attempt_unresolved "$STATE" "$PENDING_REPLY_CORR"; then
723
+ if [ "$TARGET_BACKEND" = remote ]; then
724
+ if ! fm_pending_reply_reset_known_undelivered "$STATE" "$PENDING_REPLY_CORR"; then
725
+ echo "error: pending-reply delivery for $TARGET_TASK_ID could not be reset for an idempotent remote resend of correlation $PENDING_REPLY_CORR" >&2
726
+ exit 1
727
+ fi
728
+ else
729
+ echo "error: pending-reply delivery for $TARGET_TASK_ID is unresolved; refusing to resend correlation $PENDING_REPLY_CORR" >&2
730
+ exit 1
731
+ fi
732
+ fi
733
+ if ! fm_pending_reply_prepare_delivery "$STATE" "$PENDING_REPLY_CORR"; then
734
+ [ "$PENDING_REPLY_CREATED" != 1 ] \
735
+ || fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR" || true
736
+ echo "error: failed to durably prepare pending-reply delivery for $TARGET_TASK_ID" >&2
737
+ exit 1
738
+ fi
739
+ fi
740
+ # Data-plane selection (see the header): text addressed to a task selector
741
+ # resolved through this home's metadata rides the inbox plane, unless it is
742
+ # a LOCAL harness-native invocation that must reach the harness's own parser
743
+ # - a leading "/" (slash command), or a leading "$" to a codex target (skill
744
+ # invocation). A remote secondmate selector always rides the inbox: its
745
+ # requests are marked, and a marked request reaches the harness as
746
+ # marker-prefixed chat rather than a parser command anyway, so no remote
747
+ # text has a typed plane to lose. An explicit backend target stays typed
748
+ # even when it happens to match local metadata: it names an endpoint, not a
749
+ # task, the same boundary that keeps it unmarked and outside --resolve-key.
750
+ # Classification reads the pre-marker text so a marked secondmate request
751
+ # and a plain crewmate steer classify identically. It deliberately does NOT
752
+ # promise that a marked parser-native secondmate request executes as a parser
753
+ # command: the pre-existing marker-first wire bytes are retained in stage 1.
754
+ INBOX_PLANE=0
755
+ if [ -n "$TARGET_SELECTOR" ]; then
756
+ if [ -n "$FIRE_AND_FORGET_ID" ] || [ "$TARGET_BACKEND" = remote ]; then
757
+ INBOX_PLANE=1
758
+ else
759
+ case "$RESOLVE_ANSWER_TEXT" in
760
+ /*) ;;
761
+ \$*) [ "$TARGET_HARNESS" = codex ] || INBOX_PLANE=1 ;;
762
+ *) INBOX_PLANE=1 ;;
763
+ esac
764
+ fi
765
+ fi
766
+ if [ "$INBOX_PLANE" = 1 ] && [ "$TARGET_BACKEND" = remote ]; then
767
+ # Remote inbox leg: the message becomes a durable record in the remote
768
+ # home's steering inbox, written idempotently by the host-local leg, then
769
+ # the remote doorbell rings, best-effort. One identical retry after ssh
770
+ # 255 is safe by that idempotence; a still-lost transport preserves a
771
+ # reply-bearing request's expectation, while fire-and-forget reports the
772
+ # delivery id that must be reused, because the record may have landed.
773
+ # Every transport attempt is bounded by FM_SEND_REMOTE_BUDGET seconds
774
+ # (default 30, overridable) so a busy remote queue cannot hold this send
775
+ # open indefinitely; a bound hit exits through the same
776
+ # unconfirmed-delivery contract.
777
+ REMOTE_META_LOCK=$(fm_meta_lock_path "$TARGET_META") || exit 1
778
+ if ! fm_task_inbox_lock_acquire "$REMOTE_META_LOCK"; then
779
+ if [ "$PENDING_REPLY_CREATED" = 1 ] && [ -n "$PENDING_REPLY_CORR" ]; then
780
+ fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR" || true
781
+ fi
782
+ echo "error: steer not sent to remote secondmate $TARGET_REMOTE_ID: its parent task metadata could not be locked for final delivery validation" >&2
783
+ exit 1
784
+ fi
785
+ CURRENT_REMOTE_ID=
786
+ CURRENT_REMOTE_HOST=
787
+ CURRENT_REMOTE_SPAWN_GEN=
788
+ if [ -f "$TARGET_META" ]; then
789
+ CURRENT_REMOTE_ID=$(fm_send_id_from_meta "$TARGET_META")
790
+ CURRENT_REMOTE_HOST=$(fm_meta_get "$TARGET_META" remote_host)
791
+ CURRENT_REMOTE_SPAWN_GEN=$(fm_meta_get "$TARGET_META" spawn_gen)
792
+ fi
793
+ if [ "$CURRENT_REMOTE_ID" != "$TARGET_REMOTE_ID" ] \
794
+ || { [ -n "${FM_SEND_EXPECTED_SPAWN_GEN:-}" ] \
795
+ && [ "$CURRENT_REMOTE_SPAWN_GEN" != "$FM_SEND_EXPECTED_SPAWN_GEN" ]; } \
796
+ || { [ -n "${FM_SEND_EXPECTED_REMOTE_HOST:-}" ] \
797
+ && [ "$CURRENT_REMOTE_HOST" != "$FM_SEND_EXPECTED_REMOTE_HOST" ]; } \
798
+ || [ -z "$CURRENT_REMOTE_HOST" ] \
799
+ || [ "$CURRENT_REMOTE_HOST" != "$TARGET_REMOTE_HOST" ]; then
800
+ fm_lock_release "$REMOTE_META_LOCK"
801
+ if [ "$PENDING_REPLY_CREATED" = 1 ] && [ -n "$PENDING_REPLY_CORR" ]; then
802
+ fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR" || true
803
+ fi
804
+ echo "error: steer not sent to remote secondmate $TARGET_REMOTE_ID: its parent task retired or changed route during target resolution" >&2
805
+ exit 1
806
+ fi
807
+ remote_rc=0
808
+ remote_completion_unknown=0
809
+ REMOTE_SEND_ARGS=("$TARGET_REMOTE_ID" "$MESSAGE")
810
+ [ -z "$FIRE_AND_FORGET_ID" ] || REMOTE_SEND_ARGS+=(fire-and-forget)
811
+ # Each transport attempt is bounded by FM_SEND_REMOTE_BUDGET seconds.
812
+ # fm_run_timed's 124 means the attempt was killed at the bound with remote
813
+ # completion unknown - the enqueue may have landed - so it exits through
814
+ # the same unconfirmed-delivery contract as a lost transport, without a
815
+ # retry that would only wait out the same busy remote queue again. (A
816
+ # remote job's own timeout also relays as 124; treating it as unconfirmed
817
+ # stays safe because the remote enqueue deduplicates.)
818
+ fm_run_timed "$FM_SEND_REMOTE_BUDGET" "$SCRIPT_DIR/fm-on.sh" "$TARGET_REMOTE_ID" \
819
+ fm-remote-secondmate-control.sh send "${REMOTE_SEND_ARGS[@]}" < /dev/null || remote_rc=$?
820
+ if [ "$remote_rc" -eq 124 ]; then
821
+ remote_completion_unknown=1
822
+ elif [ "$remote_rc" -eq 255 ]; then
823
+ remote_completion_unknown=1
824
+ remote_rc=0
825
+ fm_run_timed "$FM_SEND_REMOTE_BUDGET" "$SCRIPT_DIR/fm-on.sh" "$TARGET_REMOTE_ID" \
826
+ fm-remote-secondmate-control.sh send "${REMOTE_SEND_ARGS[@]}" < /dev/null || remote_rc=$?
827
+ fi
828
+ fm_lock_release "$REMOTE_META_LOCK"
829
+ if [ "$remote_rc" -ne 0 ] && [ "$remote_completion_unknown" -eq 1 ]; then
830
+ if [ -n "$FIRE_AND_FORGET_ID" ]; then
831
+ echo "error: fire-and-forget steer to remote secondmate $TARGET_REMOTE_ID is unconfirmed (delivery-id=$FIRE_AND_FORGET_ID); retry only with the same delivery id" >&2
832
+ exit 3
833
+ fi
834
+ if [ -n "$PENDING_REPLY_CORR" ]; then
835
+ fm_pending_reply_mark_delivery_unknown "$STATE" "$PENDING_REPLY_CORR" || true
836
+ fi
837
+ if [ "$remote_rc" -eq 255 ]; then
838
+ echo "error: steer to remote secondmate $TARGET_REMOTE_ID is unconfirmed (transport lost twice; remote completion unknown). Only the correlation-reusing resend below is idempotent and lands on the same remote inbox record:" >&2
839
+ elif [ "$remote_rc" -eq 124 ]; then
840
+ echo "error: steer to remote secondmate $TARGET_REMOTE_ID is unconfirmed (the remote transport did not complete within its ${FM_SEND_REMOTE_BUDGET}s budget; remote completion unknown). Only the correlation-reusing resend below is idempotent and lands on the same remote inbox record:" >&2
841
+ else
842
+ echo "error: steer to remote secondmate $TARGET_REMOTE_ID is unconfirmed (the first transport attempt had unknown completion and the retry failed). Only the correlation-reusing resend below is idempotent and lands on the same remote inbox record:" >&2
843
+ fi
844
+ resend_home=$(cd "$FM_HOME" 2>/dev/null && pwd) || resend_home=$FM_HOME
845
+ printf 'FM_HOME=%q ' "$resend_home" >&2
846
+ if [ "${FM_STATE_OVERRIDE+x}" = x ]; then
847
+ resend_state=$(cd "$STATE" 2>/dev/null && pwd) || resend_state=$STATE
848
+ printf 'FM_STATE_OVERRIDE=%q ' "$resend_state" >&2
849
+ fi
850
+ printf 'FM_PENDING_REPLY_EXISTING_CORR=%q %q' "$PENDING_REPLY_CORR" "$SCRIPT_DIR/fm-send.sh" >&2
851
+ for resend_arg in "${FM_SEND_ORIGINAL_ARGS[@]}"; do
852
+ printf ' %q' "$resend_arg" >&2
853
+ done
854
+ printf '\n' >&2
855
+ exit 1
856
+ fi
857
+ if [ "$remote_rc" -ne 0 ]; then
858
+ fm_send_known_undelivered_cleanup || \
859
+ echo "error: known-undelivered pending-reply state could not be reset for $TARGET_TASK_ID" >&2
860
+ echo "error: steer not sent to remote secondmate $TARGET_REMOTE_ID (the remote steering-inbox record could not be written; the remote leg's stderr above has the reason)" >&2
861
+ exit 1
862
+ fi
863
+ # The remote record is durable delivery, exactly as a local enqueue is.
864
+ if [ -n "$PENDING_REPLY_CORR" ]; then
865
+ if fm_pending_reply_confirm_delivery "$STATE" "$PENDING_REPLY_CORR"; then
866
+ :
867
+ else
868
+ delivery_commit_status=$?
869
+ if [ "$delivery_commit_status" = 2 ]; then
870
+ echo "notice: the steer was durably recorded in the remote inbox, but its pending-reply delivery commit failed; a durable recovery marker was stored and the watcher will reconcile it. Do not resend." >&2
871
+ else
872
+ echo "warning: reply-tracking-degraded (steer delivered, do not resend): the steer was durably recorded in the remote inbox, but its pending-reply delivery commit and recovery marker both failed, so the reply expectation for this request may not reconcile on its own. Inspect $STATE." >&2
873
+ fi
874
+ fi
875
+ fi
876
+ if [ -n "$RESOLVE_KEYS" ]; then
877
+ fm_send_close_resolved_keys "$RESOLVE_ANSWER_TEXT" || exit 1
878
+ fm_send_feed_resolved_holds "$RESOLVE_ANSWER_TEXT" || exit 1
879
+ fi
880
+ exit 0
881
+ fi
882
+ if [ "$INBOX_PLANE" = 1 ]; then
883
+ INBOX_TASK_ID=$(fm_send_id_from_meta "$TARGET_META")
884
+ INBOX_META_LOCK=$(fm_meta_lock_path "$TARGET_META") || exit 1
885
+ if ! fm_task_inbox_lock_acquire "$INBOX_META_LOCK"; then
886
+ if [ "$PENDING_REPLY_CREATED" = 1 ] && [ -n "$PENDING_REPLY_CORR" ]; then
887
+ fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR" || true
888
+ fi
889
+ echo "error: steer not sent to $INBOX_TASK_ID: its task metadata could not be locked for final delivery validation" >&2
890
+ exit 1
891
+ fi
892
+ CURRENT_INBOX_TARGET=
893
+ CURRENT_INBOX_BACKEND=
894
+ CURRENT_INBOX_SPAWN_GEN=
895
+ if [ -f "$TARGET_META" ]; then
896
+ CURRENT_INBOX_TARGET=$(fm_backend_target_of_meta "$TARGET_META")
897
+ CURRENT_INBOX_BACKEND=$(fm_backend_of_meta "$TARGET_META")
898
+ CURRENT_INBOX_SPAWN_GEN=$(fm_meta_get "$TARGET_META" spawn_gen)
899
+ fi
900
+ if [ "$CURRENT_INBOX_TARGET" != "$T" ] \
901
+ || [ "$CURRENT_INBOX_BACKEND" != "$TARGET_BACKEND" ] \
902
+ || { [ -n "${FM_SEND_EXPECTED_SPAWN_GEN:-}" ] \
903
+ && [ "$CURRENT_INBOX_SPAWN_GEN" != "$FM_SEND_EXPECTED_SPAWN_GEN" ]; } \
904
+ || [ -n "$(fm_meta_get "$TARGET_META" remote_host)" ]; then
905
+ fm_lock_release "$INBOX_META_LOCK"
906
+ if [ "$PENDING_REPLY_CREATED" = 1 ] && [ -n "$PENDING_REPLY_CORR" ]; then
907
+ fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR" || true
908
+ fi
909
+ echo "error: steer not sent to $INBOX_TASK_ID: the task retired or changed endpoint during target resolution" >&2
910
+ exit 1
911
+ fi
912
+ if [ "${FM_SEND_IDEMPOTENT:-0}" = 1 ]; then
913
+ INBOX_RECORD=$(fm_task_inbox_write_idempotent "$STATE" "$INBOX_TASK_ID" "$MESSAGE" \
914
+ "${FIRE_AND_FORGET_ID:+fire-and-forget}") || inbox_write_rc=$?
915
+ else
916
+ INBOX_RECORD=$(fm_task_inbox_write "$STATE" "$INBOX_TASK_ID" "$MESSAGE" \
917
+ "${FIRE_AND_FORGET_ID:+fire-and-forget}") || inbox_write_rc=$?
918
+ fi
919
+ if [ "${inbox_write_rc:-0}" -ne 0 ]; then
920
+ fm_lock_release "$INBOX_META_LOCK"
921
+ if [ "$PENDING_REPLY_CREATED" = 1 ] && [ -n "$PENDING_REPLY_CORR" ]; then
922
+ fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR" || true
923
+ fi
924
+ echo "error: steer not sent to $INBOX_TASK_ID: its inbox record could not be written under $STATE/$INBOX_TASK_ID.inbox" >&2
925
+ exit 1
926
+ fi
927
+ fm_lock_release "$INBOX_META_LOCK"
928
+ # Enqueue IS durable delivery to the task's record: mark the pending
929
+ # expectation delivered now, without resolving it - only a correlated
930
+ # parent report acknowledges the request.
931
+ if [ -n "$PENDING_REPLY_CORR" ]; then
932
+ if fm_pending_reply_confirm_delivery "$STATE" "$PENDING_REPLY_CORR"; then
933
+ :
934
+ else
935
+ delivery_commit_status=$?
936
+ if [ "$delivery_commit_status" = 2 ]; then
937
+ echo "notice: the steer was recorded at $INBOX_RECORD, but its pending-reply delivery commit failed; a durable recovery marker was stored and the watcher will reconcile it. Do not resend." >&2
938
+ else
939
+ # Both the commit and its recovery marker failed. The durable inbox
940
+ # record is what delivers the steer, so the send still SUCCEEDED:
941
+ # a nonzero here would read as undelivered to every automated caller
942
+ # and invite a duplicate enqueue - the exact defect this plane
943
+ # removes. Surface the degradation as its own distinct,
944
+ # non-resend-inviting condition instead: reply tracking for this
945
+ # request may not resolve or escalate on its own until an operator
946
+ # inspects it.
947
+ echo "warning: reply-tracking-degraded (steer delivered, do not resend): the steer was durably recorded at $INBOX_RECORD, but its pending-reply delivery commit and recovery marker both failed, so the reply expectation for this request may not reconcile on its own. Inspect $STATE." >&2
948
+ fi
949
+ fi
950
+ fi
951
+ # The answer is durably sent: close each answered decision at enqueue time
952
+ # (answerer-closes; see the header contract).
953
+ if [ -n "$RESOLVE_KEYS" ]; then
954
+ fm_send_close_resolved_keys "$RESOLVE_ANSWER_TEXT" || exit 1
955
+ fm_send_feed_resolved_holds "$RESOLVE_ANSWER_TEXT" || exit 1
956
+ fi
957
+ # Ring the doorbell, best-effort: no ring outcome changes the exit status,
958
+ # because the watcher's re-ring ladder owns loss detection from here.
959
+ ring_rc=0
960
+ fm_task_inbox_ring "$TARGET_BACKEND" "$T" "$INBOX_RECORD" "$EXPECTED_LABEL" || ring_rc=$?
961
+ case "$ring_rc" in
962
+ 1) echo "fm-send: doorbell skipped (composer visibly holds pending text); the steer is durably recorded at $INBOX_RECORD and the watcher will re-ring" >&2 ;;
963
+ 2) echo "fm-send: doorbell did not reach $T; the steer is durably recorded at $INBOX_RECORD and the watcher will re-ring" >&2 ;;
964
+ esac
965
+ exit 0
966
+ fi
967
+ # Slash commands open a completion popup in some TUIs (verified on codex);
968
+ # submitting too fast selects nothing, so give the popup time to settle before
969
+ # the (retried) Enter. Codex opens the same kind of popup for a `$<skill>`
970
+ # invocation, so a `$...` message to a codex target gets the same settle. That
971
+ # `$` case is scoped to codex on purpose: unlike `/`, a leading `$` commonly
972
+ # starts ordinary text ("$5/month", "$HOME"), so a universal `$` rule would
973
+ # needlessly slow plain text to claude/opencode/pi. The target backend's
974
+ # verified submit retry still backs the settle up either way.
975
+ case "$*" in
976
+ /*) settle=1.2 ;;
977
+ \$*)
978
+ if [ "$TARGET_HARNESS" = codex ]; then settle=1.2; else settle=0.3; fi
979
+ ;;
980
+ *) settle=0.3 ;;
981
+ esac
982
+ retries=${FM_SEND_RETRIES:-3}
983
+ sleep_s=${FM_SEND_SLEEP:-0.4}
984
+ # Type once, submit, verify. Only exact empty confirms delivery; every other
985
+ # verdict preserves the loud refusal boundary. Only LOCAL targets reach this
986
+ # block: remote text rides the inbox leg above, and remote --key exits
987
+ # earlier.
988
+ send_rc=0
989
+ if verdict=$(fm_backend_send_text_submit "$TARGET_BACKEND" "$T" "$MESSAGE" "$retries" "$sleep_s" "$settle" "$EXPECTED_LABEL"); then
990
+ :
991
+ else
992
+ send_rc=$?
993
+ fi
994
+ if [ "$send_rc" -ne 0 ]; then
995
+ fm_send_known_undelivered_cleanup || \
996
+ echo "error: known-undelivered pending-reply state could not be reset for $TARGET_TASK_ID" >&2
997
+ echo "error: text not sent to $T ($TARGET_BACKEND send failed; tried $RESOLUTION_TRIED)" >&2
998
+ exit 1
999
+ fi
1000
+ case "$verdict" in
1001
+ empty)
1002
+ ;;
1003
+ send-failed)
1004
+ fm_send_known_undelivered_cleanup || \
1005
+ echo "error: known-undelivered pending-reply state could not be reset for $TARGET_TASK_ID" >&2
1006
+ echo "error: text not sent to $T ($TARGET_BACKEND send failed; tried $RESOLUTION_TRIED)" >&2
1007
+ exit 1
1008
+ ;;
1009
+ pending)
1010
+ # The text was typed into the live target and Enter was sent; only the
1011
+ # submit read-back stayed unconfirmed (e.g. a busy harness queues the
1012
+ # steer and keeps rendering it). That is not a proven failure, so never
1013
+ # re-type the message: verify the pane instead. Exit 3 is the documented
1014
+ # delivered-unconfirmed status.
1015
+ # The pending-reply expectation is deliberately NOT discarded here:
1016
+ # dropping it would silently stop tracking a marked request that very
1017
+ # likely landed. It stays armed on its unconfirmed-delivery marker, so a
1018
+ # correlated report still resolves it and an unanswered one still
1019
+ # surfaces through the library's own reconciliation
1020
+ # (bin/fm-pending-reply-lib.sh).
1021
+ echo "fm-send: text delivered to $T but submission is unconfirmed (verdict=pending; tried $RESOLUTION_TRIED); do not retype or blindly resend - verify with fm-peek.sh, then re-send '--key Enter' only if the composer still holds the text" >&2
1022
+ exit 3
1023
+ ;;
1024
+ *)
1025
+ if [ "$PENDING_REPLY_CREATED" = 1 ] && [ -n "$PENDING_REPLY_CORR" ]; then
1026
+ fm_pending_reply_discard_undelivered "$STATE" "$PENDING_REPLY_CORR" || true
1027
+ fi
1028
+ echo "error: text not submitted to $T (delivery unconfirmed; verdict=${verdict:-unknown}; tried $RESOLUTION_TRIED)" >&2
1029
+ exit 1
1030
+ ;;
1031
+ esac
1032
+ # Delivery confirmed. Mark the pending expectation delivered without resolving
1033
+ # it: only a correlated parent report acknowledges the request.
1034
+ if [ -n "$PENDING_REPLY_CORR" ]; then
1035
+ if fm_pending_reply_confirm_delivery "$STATE" "$PENDING_REPLY_CORR"; then
1036
+ :
1037
+ else
1038
+ delivery_commit_status=$?
1039
+ if [ "$delivery_commit_status" = 2 ]; then
1040
+ echo "error: text was delivered to $T, but its pending-reply delivery commit failed; a durable recovery marker was stored and the watcher will reconcile it. Do not resend." >&2
1041
+ else
1042
+ echo "error: text was delivered to $T, but its pending-reply delivery commit and recovery marker both failed. Do not resend; inspect $STATE manually." >&2
1043
+ fi
1044
+ exit 1
1045
+ fi
1046
+ fi
1047
+ # Delivery is fully confirmed: close each answered decision in this home's
1048
+ # ledger (answerer-closes; see the header contract).
1049
+ if [ -n "$RESOLVE_KEYS" ]; then
1050
+ fm_send_close_resolved_keys "$RESOLVE_ANSWER_TEXT" || exit 1
1051
+ fm_send_feed_resolved_holds "$RESOLVE_ANSWER_TEXT" || exit 1
1052
+ fi
1053
+ # Submit landed with exact empty. Confirmation only proves the text was
1054
+ # accepted; the harness still needs a beat to spin up the
1055
+ # turn before its busy footer shows. Pause so an immediate peek catches the
1056
+ # crewmate actually working instead of the stale idle pane. FM_SEND_SETTLE=0
1057
+ # disables it. Scoped to this path only, never the shared submit core.
1058
+ [ "${FM_SEND_SETTLE:-1}" = 0 ] || sleep "${FM_SEND_SETTLE:-1}"
1059
+ fi