@openrig/cli 0.4.8 → 0.5.0

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 (344) hide show
  1. package/daemon/assets/claude-statusline-context.cjs +40 -8
  2. package/daemon/assets/plugins/openrig-core/hooks/scripts/activity-relay.cjs +0 -0
  3. package/daemon/assets/plugins/openrig-core/skills/mission-slice-sop/SKILL.md +7 -4
  4. package/daemon/assets/plugins/openrig-core/skills/openrig-user/SKILL.md +68 -17
  5. package/daemon/assets/plugins/openrig-core/skills/queue-handoff/SKILL.md +2 -0
  6. package/daemon/assets/plugins/openrig-core/skills/seat-continuity-and-handover/SKILL.md +2 -1
  7. package/daemon/assets/plugins/openrig-core/skills/session-compaction-and-restore/SKILL.md +1 -0
  8. package/daemon/dist/adapters/claude-code-adapter.d.ts +36 -0
  9. package/daemon/dist/adapters/claude-code-adapter.d.ts.map +1 -1
  10. package/daemon/dist/adapters/claude-code-adapter.js +203 -3
  11. package/daemon/dist/adapters/claude-code-adapter.js.map +1 -1
  12. package/daemon/dist/adapters/codex-runtime-adapter.d.ts +12 -0
  13. package/daemon/dist/adapters/codex-runtime-adapter.d.ts.map +1 -1
  14. package/daemon/dist/adapters/codex-runtime-adapter.js +24 -2
  15. package/daemon/dist/adapters/codex-runtime-adapter.js.map +1 -1
  16. package/daemon/dist/build-info.js +3 -3
  17. package/daemon/dist/domain/agent-activity-store.d.ts +1 -0
  18. package/daemon/dist/domain/agent-activity-store.d.ts.map +1 -1
  19. package/daemon/dist/domain/agent-activity-store.js +2 -2
  20. package/daemon/dist/domain/agent-activity-store.js.map +1 -1
  21. package/daemon/dist/domain/claude-activity-hooks.d.ts +33 -0
  22. package/daemon/dist/domain/claude-activity-hooks.d.ts.map +1 -0
  23. package/daemon/dist/domain/claude-activity-hooks.js +60 -0
  24. package/daemon/dist/domain/claude-activity-hooks.js.map +1 -0
  25. package/daemon/dist/domain/context-packs/bundle-assembler.d.ts +30 -0
  26. package/daemon/dist/domain/context-packs/bundle-assembler.d.ts.map +1 -1
  27. package/daemon/dist/domain/context-packs/bundle-assembler.js +31 -1
  28. package/daemon/dist/domain/context-packs/bundle-assembler.js.map +1 -1
  29. package/daemon/dist/domain/context-packs/context-pack-library-service.d.ts +69 -14
  30. package/daemon/dist/domain/context-packs/context-pack-library-service.d.ts.map +1 -1
  31. package/daemon/dist/domain/context-packs/context-pack-library-service.js +263 -48
  32. package/daemon/dist/domain/context-packs/context-pack-library-service.js.map +1 -1
  33. package/daemon/dist/domain/context-packs/context-pack-types.d.ts +2 -2
  34. package/daemon/dist/domain/context-packs/context-pack-types.d.ts.map +1 -1
  35. package/daemon/dist/domain/context-packs/context-pack-types.js.map +1 -1
  36. package/daemon/dist/domain/context-packs/manifest-parser.d.ts.map +1 -1
  37. package/daemon/dist/domain/context-packs/manifest-parser.js +11 -0
  38. package/daemon/dist/domain/context-packs/manifest-parser.js.map +1 -1
  39. package/daemon/dist/domain/context-packs/ref-safety.d.ts +11 -0
  40. package/daemon/dist/domain/context-packs/ref-safety.d.ts.map +1 -0
  41. package/daemon/dist/domain/context-packs/ref-safety.js +46 -0
  42. package/daemon/dist/domain/context-packs/ref-safety.js.map +1 -0
  43. package/daemon/dist/domain/context-packs/token-estimate.d.ts +3 -0
  44. package/daemon/dist/domain/context-packs/token-estimate.d.ts.map +1 -0
  45. package/daemon/dist/domain/context-packs/token-estimate.js +5 -0
  46. package/daemon/dist/domain/context-packs/token-estimate.js.map +1 -0
  47. package/daemon/dist/domain/plugin-vendor-service.d.ts +14 -0
  48. package/daemon/dist/domain/plugin-vendor-service.d.ts.map +1 -1
  49. package/daemon/dist/domain/plugin-vendor-service.js +26 -2
  50. package/daemon/dist/domain/plugin-vendor-service.js.map +1 -1
  51. package/daemon/dist/domain/provider/claude-usage-reader.d.ts +44 -0
  52. package/daemon/dist/domain/provider/claude-usage-reader.d.ts.map +1 -0
  53. package/daemon/dist/domain/provider/claude-usage-reader.js +112 -0
  54. package/daemon/dist/domain/provider/claude-usage-reader.js.map +1 -0
  55. package/daemon/dist/domain/provider/codex-auth-reader.d.ts +18 -0
  56. package/daemon/dist/domain/provider/codex-auth-reader.d.ts.map +1 -0
  57. package/daemon/dist/domain/provider/codex-auth-reader.js +64 -0
  58. package/daemon/dist/domain/provider/codex-auth-reader.js.map +1 -0
  59. package/daemon/dist/domain/provider/host-usage-rollup.d.ts +51 -0
  60. package/daemon/dist/domain/provider/host-usage-rollup.d.ts.map +1 -0
  61. package/daemon/dist/domain/provider/host-usage-rollup.js +162 -0
  62. package/daemon/dist/domain/provider/host-usage-rollup.js.map +1 -0
  63. package/daemon/dist/domain/provider/provider-collect.d.ts +22 -0
  64. package/daemon/dist/domain/provider/provider-collect.d.ts.map +1 -0
  65. package/daemon/dist/domain/provider/provider-collect.js +74 -0
  66. package/daemon/dist/domain/provider/provider-collect.js.map +1 -0
  67. package/daemon/dist/domain/provider/provider-policy.d.ts +38 -0
  68. package/daemon/dist/domain/provider/provider-policy.d.ts.map +1 -0
  69. package/daemon/dist/domain/provider/provider-policy.js +76 -0
  70. package/daemon/dist/domain/provider/provider-policy.js.map +1 -0
  71. package/daemon/dist/domain/provider/provider-read-model.d.ts +28 -0
  72. package/daemon/dist/domain/provider/provider-read-model.d.ts.map +1 -0
  73. package/daemon/dist/domain/provider/provider-read-model.js +68 -0
  74. package/daemon/dist/domain/provider/provider-read-model.js.map +1 -0
  75. package/daemon/dist/domain/provider/provider-service-impl.d.ts +26 -0
  76. package/daemon/dist/domain/provider/provider-service-impl.d.ts.map +1 -0
  77. package/daemon/dist/domain/provider/provider-service-impl.js +83 -0
  78. package/daemon/dist/domain/provider/provider-service-impl.js.map +1 -0
  79. package/daemon/dist/domain/provider/provider-service.d.ts +33 -0
  80. package/daemon/dist/domain/provider/provider-service.d.ts.map +1 -0
  81. package/daemon/dist/domain/provider/provider-service.js +6 -0
  82. package/daemon/dist/domain/provider/provider-service.js.map +1 -0
  83. package/daemon/dist/domain/provider/provider-signals.d.ts +76 -0
  84. package/daemon/dist/domain/provider/provider-signals.d.ts.map +1 -0
  85. package/daemon/dist/domain/provider/provider-signals.js +144 -0
  86. package/daemon/dist/domain/provider/provider-signals.js.map +1 -0
  87. package/daemon/dist/domain/provider/provider-types.d.ts +101 -0
  88. package/daemon/dist/domain/provider/provider-types.d.ts.map +1 -0
  89. package/daemon/dist/domain/provider/provider-types.js +7 -0
  90. package/daemon/dist/domain/provider/provider-types.js.map +1 -0
  91. package/daemon/dist/domain/provider/reactive-tap.d.ts +29 -0
  92. package/daemon/dist/domain/provider/reactive-tap.d.ts.map +1 -0
  93. package/daemon/dist/domain/provider/reactive-tap.js +80 -0
  94. package/daemon/dist/domain/provider/reactive-tap.js.map +1 -0
  95. package/daemon/dist/domain/rigspec-instantiator.d.ts +7 -23
  96. package/daemon/dist/domain/rigspec-instantiator.d.ts.map +1 -1
  97. package/daemon/dist/domain/rigspec-instantiator.js +3 -61
  98. package/daemon/dist/domain/rigspec-instantiator.js.map +1 -1
  99. package/daemon/dist/domain/rigspec-preflight.d.ts +11 -0
  100. package/daemon/dist/domain/rigspec-preflight.d.ts.map +1 -1
  101. package/daemon/dist/domain/rigspec-preflight.js +43 -1
  102. package/daemon/dist/domain/rigspec-preflight.js.map +1 -1
  103. package/daemon/dist/domain/runtime-adapter.d.ts +2 -11
  104. package/daemon/dist/domain/runtime-adapter.d.ts.map +1 -1
  105. package/daemon/dist/domain/runtime-adapter.js.map +1 -1
  106. package/daemon/dist/domain/scope/attestation-lineage.generated.d.ts +14 -0
  107. package/daemon/dist/domain/scope/attestation-lineage.generated.d.ts.map +1 -0
  108. package/daemon/dist/domain/scope/attestation-lineage.generated.js +26 -0
  109. package/daemon/dist/domain/scope/attestation-lineage.generated.js.map +1 -0
  110. package/daemon/dist/domain/scope/scope-approve.d.ts +13 -0
  111. package/daemon/dist/domain/scope/scope-approve.d.ts.map +1 -1
  112. package/daemon/dist/domain/scope/scope-approve.js +50 -10
  113. package/daemon/dist/domain/scope/scope-approve.js.map +1 -1
  114. package/daemon/dist/domain/startup-validation.d.ts.map +1 -1
  115. package/daemon/dist/domain/startup-validation.js +18 -54
  116. package/daemon/dist/domain/startup-validation.js.map +1 -1
  117. package/daemon/dist/domain/stream-store.d.ts +4 -0
  118. package/daemon/dist/domain/stream-store.d.ts.map +1 -1
  119. package/daemon/dist/domain/stream-store.js +15 -2
  120. package/daemon/dist/domain/stream-store.js.map +1 -1
  121. package/daemon/dist/domain/types.d.ts +2 -9
  122. package/daemon/dist/domain/types.d.ts.map +1 -1
  123. package/daemon/dist/domain/types.js.map +1 -1
  124. package/daemon/dist/domain/workspace/default-workspace-scaffold.js +1 -1
  125. package/daemon/dist/routes/context-packs.d.ts.map +1 -1
  126. package/daemon/dist/routes/context-packs.js +157 -75
  127. package/daemon/dist/routes/context-packs.js.map +1 -1
  128. package/daemon/dist/routes/provider.d.ts +3 -0
  129. package/daemon/dist/routes/provider.d.ts.map +1 -0
  130. package/daemon/dist/routes/provider.js +124 -0
  131. package/daemon/dist/routes/provider.js.map +1 -0
  132. package/daemon/dist/routes/scope-approve.d.ts.map +1 -1
  133. package/daemon/dist/routes/scope-approve.js +6 -3
  134. package/daemon/dist/routes/scope-approve.js.map +1 -1
  135. package/daemon/dist/routes/scope-audit.d.ts.map +1 -1
  136. package/daemon/dist/routes/scope-audit.js +4 -1
  137. package/daemon/dist/routes/scope-audit.js.map +1 -1
  138. package/daemon/dist/routes/stream.d.ts.map +1 -1
  139. package/daemon/dist/routes/stream.js +71 -1
  140. package/daemon/dist/routes/stream.js.map +1 -1
  141. package/daemon/dist/server.d.ts +3 -0
  142. package/daemon/dist/server.d.ts.map +1 -1
  143. package/daemon/dist/server.js +5 -0
  144. package/daemon/dist/server.js.map +1 -1
  145. package/daemon/dist/startup.d.ts.map +1 -1
  146. package/daemon/dist/startup.js +21 -11
  147. package/daemon/dist/startup.js.map +1 -1
  148. package/daemon/docs/reference/developing.md +39 -0
  149. package/daemon/specs/agents/apps/vault-specialist/agent.yaml +1 -1
  150. package/daemon/specs/agents/conveyor/builder/agent.yaml +1 -1
  151. package/daemon/specs/agents/conveyor/lead/agent.yaml +1 -1
  152. package/daemon/specs/agents/conveyor/planner/agent.yaml +1 -1
  153. package/daemon/specs/agents/conveyor/reviewer/agent.yaml +1 -1
  154. package/daemon/specs/agents/design/product-designer/agent.yaml +1 -1
  155. package/daemon/specs/agents/development/implementer/agent.yaml +1 -1
  156. package/daemon/specs/agents/development/qa/agent.yaml +1 -1
  157. package/daemon/specs/agents/factory-rsi/dogfood/agent.yaml +1 -1
  158. package/daemon/specs/agents/factory-rsi/release-manager/agent.yaml +1 -1
  159. package/daemon/specs/agents/orchestration/orchestrator/agent.yaml +1 -1
  160. package/daemon/specs/agents/product-management/pm/agent.yaml +1 -0
  161. package/daemon/specs/agents/research/analyst/agent.yaml +1 -1
  162. package/daemon/specs/agents/research/synthesizer/agent.yaml +1 -1
  163. package/daemon/specs/agents/review/independent-reviewer/agent.yaml +1 -1
  164. package/daemon/specs/agents/shared/agent.yaml +6 -0
  165. package/daemon/specs/agents/shared/runtime/claude-activity-hooks.json +4 -0
  166. package/daemon/specs/agents/shared/skills/core/watchdog/SKILL.md +1 -0
  167. package/dist/bin-wrapper.d.ts.map +1 -1
  168. package/dist/bin-wrapper.js +7 -0
  169. package/dist/bin-wrapper.js.map +1 -1
  170. package/dist/build-info.js +3 -3
  171. package/dist/commands/agent-image.d.ts.map +1 -1
  172. package/dist/commands/agent-image.js +2 -1
  173. package/dist/commands/agent-image.js.map +1 -1
  174. package/dist/commands/broadcast.d.ts.map +1 -1
  175. package/dist/commands/broadcast.js +37 -2
  176. package/dist/commands/broadcast.js.map +1 -1
  177. package/dist/commands/context.d.ts +2 -7
  178. package/dist/commands/context.d.ts.map +1 -1
  179. package/dist/commands/context.js +391 -213
  180. package/dist/commands/context.js.map +1 -1
  181. package/dist/commands/provider.d.ts +4 -0
  182. package/dist/commands/provider.d.ts.map +1 -0
  183. package/dist/commands/provider.js +218 -0
  184. package/dist/commands/provider.js.map +1 -0
  185. package/dist/commands/ps.d.ts +7 -0
  186. package/dist/commands/ps.d.ts.map +1 -1
  187. package/dist/commands/ps.js +34 -4
  188. package/dist/commands/ps.js.map +1 -1
  189. package/dist/commands/queue.d.ts.map +1 -1
  190. package/dist/commands/queue.js +38 -8
  191. package/dist/commands/queue.js.map +1 -1
  192. package/dist/commands/scope.d.ts.map +1 -1
  193. package/dist/commands/scope.js +48 -4
  194. package/dist/commands/scope.js.map +1 -1
  195. package/dist/commands/send.d.ts.map +1 -1
  196. package/dist/commands/send.js +70 -11
  197. package/dist/commands/send.js.map +1 -1
  198. package/dist/commands/stream.d.ts +1 -0
  199. package/dist/commands/stream.d.ts.map +1 -1
  200. package/dist/commands/stream.js +93 -0
  201. package/dist/commands/stream.js.map +1 -1
  202. package/dist/commands/tui.d.ts +4 -0
  203. package/dist/commands/tui.d.ts.map +1 -0
  204. package/dist/commands/tui.js +29 -0
  205. package/dist/commands/tui.js.map +1 -0
  206. package/dist/commands/walk.d.ts +19 -0
  207. package/dist/commands/walk.d.ts.map +1 -0
  208. package/dist/commands/walk.js +139 -0
  209. package/dist/commands/walk.js.map +1 -0
  210. package/dist/context-resolve.d.ts +14 -0
  211. package/dist/context-resolve.d.ts.map +1 -0
  212. package/dist/context-resolve.js +40 -0
  213. package/dist/context-resolve.js.map +1 -0
  214. package/dist/front-door.d.ts +31 -0
  215. package/dist/front-door.d.ts.map +1 -0
  216. package/dist/front-door.js +114 -0
  217. package/dist/front-door.js.map +1 -0
  218. package/dist/index.d.ts +3 -1
  219. package/dist/index.d.ts.map +1 -1
  220. package/dist/index.js +19 -6
  221. package/dist/index.js.map +1 -1
  222. package/dist/lib/scope/attestation-lineage.d.ts +14 -0
  223. package/dist/lib/scope/attestation-lineage.d.ts.map +1 -0
  224. package/dist/lib/scope/attestation-lineage.js +34 -0
  225. package/dist/lib/scope/attestation-lineage.js.map +1 -0
  226. package/package.json +4 -2
  227. package/tui/dist/daemon-client.d.ts +56 -0
  228. package/tui/dist/daemon-client.d.ts.map +1 -0
  229. package/tui/dist/daemon-client.js +99 -0
  230. package/tui/dist/daemon-client.js.map +1 -0
  231. package/tui/dist/demo-data.d.ts +3 -0
  232. package/tui/dist/demo-data.d.ts.map +1 -0
  233. package/tui/dist/demo-data.js +52 -0
  234. package/tui/dist/demo-data.js.map +1 -0
  235. package/tui/dist/detail.d.ts +34 -0
  236. package/tui/dist/detail.d.ts.map +1 -0
  237. package/tui/dist/detail.js +47 -0
  238. package/tui/dist/detail.js.map +1 -0
  239. package/tui/dist/grammar.d.ts +3 -0
  240. package/tui/dist/grammar.d.ts.map +1 -0
  241. package/tui/dist/grammar.js +66 -0
  242. package/tui/dist/grammar.js.map +1 -0
  243. package/tui/dist/hydrate.d.ts +78 -0
  244. package/tui/dist/hydrate.d.ts.map +1 -0
  245. package/tui/dist/hydrate.js +294 -0
  246. package/tui/dist/hydrate.js.map +1 -0
  247. package/tui/dist/index.d.ts +12 -0
  248. package/tui/dist/index.d.ts.map +1 -0
  249. package/tui/dist/index.js +12 -0
  250. package/tui/dist/index.js.map +1 -0
  251. package/tui/dist/input.d.ts +21 -0
  252. package/tui/dist/input.d.ts.map +1 -0
  253. package/tui/dist/input.js +128 -0
  254. package/tui/dist/input.js.map +1 -0
  255. package/tui/dist/live.d.ts +20 -0
  256. package/tui/dist/live.d.ts.map +1 -0
  257. package/tui/dist/live.js +67 -0
  258. package/tui/dist/live.js.map +1 -0
  259. package/tui/dist/main.d.ts +3 -0
  260. package/tui/dist/main.d.ts.map +1 -0
  261. package/tui/dist/main.js +184 -0
  262. package/tui/dist/main.js.map +1 -0
  263. package/tui/dist/motion.d.ts +13 -0
  264. package/tui/dist/motion.d.ts.map +1 -0
  265. package/tui/dist/motion.js +31 -0
  266. package/tui/dist/motion.js.map +1 -0
  267. package/tui/dist/navigator.d.ts +21 -0
  268. package/tui/dist/navigator.d.ts.map +1 -0
  269. package/tui/dist/navigator.js +168 -0
  270. package/tui/dist/navigator.js.map +1 -0
  271. package/tui/dist/refresh.d.ts +3 -0
  272. package/tui/dist/refresh.d.ts.map +1 -0
  273. package/tui/dist/refresh.js +9 -0
  274. package/tui/dist/refresh.js.map +1 -0
  275. package/tui/dist/render.d.ts +21 -0
  276. package/tui/dist/render.d.ts.map +1 -0
  277. package/tui/dist/render.js +807 -0
  278. package/tui/dist/render.js.map +1 -0
  279. package/tui/dist/sections.d.ts +4 -0
  280. package/tui/dist/sections.d.ts.map +1 -0
  281. package/tui/dist/sections.js +19 -0
  282. package/tui/dist/sections.js.map +1 -0
  283. package/tui/dist/socket-server.d.ts +24 -0
  284. package/tui/dist/socket-server.d.ts.map +1 -0
  285. package/tui/dist/socket-server.js +85 -0
  286. package/tui/dist/socket-server.js.map +1 -0
  287. package/tui/dist/state.d.ts +51 -0
  288. package/tui/dist/state.d.ts.map +1 -0
  289. package/tui/dist/state.js +480 -0
  290. package/tui/dist/state.js.map +1 -0
  291. package/tui/dist/stylize.d.ts +4 -0
  292. package/tui/dist/stylize.d.ts.map +1 -0
  293. package/tui/dist/stylize.js +248 -0
  294. package/tui/dist/stylize.js.map +1 -0
  295. package/tui/dist/theme.d.ts +19 -0
  296. package/tui/dist/theme.d.ts.map +1 -0
  297. package/tui/dist/theme.js +86 -0
  298. package/tui/dist/theme.js.map +1 -0
  299. package/tui/dist/topology/canvas.d.ts +43 -0
  300. package/tui/dist/topology/canvas.d.ts.map +1 -0
  301. package/tui/dist/topology/canvas.js +131 -0
  302. package/tui/dist/topology/canvas.js.map +1 -0
  303. package/tui/dist/topology/fixture.d.ts +4 -0
  304. package/tui/dist/topology/fixture.d.ts.map +1 -0
  305. package/tui/dist/topology/fixture.js +71 -0
  306. package/tui/dist/topology/fixture.js.map +1 -0
  307. package/tui/dist/topology/glyphs.d.ts +21 -0
  308. package/tui/dist/topology/glyphs.d.ts.map +1 -0
  309. package/tui/dist/topology/glyphs.js +49 -0
  310. package/tui/dist/topology/glyphs.js.map +1 -0
  311. package/tui/dist/topology/graph-types.d.ts +39 -0
  312. package/tui/dist/topology/graph-types.d.ts.map +1 -0
  313. package/tui/dist/topology/graph-types.js +2 -0
  314. package/tui/dist/topology/graph-types.js.map +1 -0
  315. package/tui/dist/topology/layout.d.ts +52 -0
  316. package/tui/dist/topology/layout.d.ts.map +1 -0
  317. package/tui/dist/topology/layout.js +118 -0
  318. package/tui/dist/topology/layout.js.map +1 -0
  319. package/tui/dist/topology/render-graph.d.ts +8 -0
  320. package/tui/dist/topology/render-graph.d.ts.map +1 -0
  321. package/tui/dist/topology/render-graph.js +20 -0
  322. package/tui/dist/topology/render-graph.js.map +1 -0
  323. package/tui/dist/topology/runtime-marks.d.ts +53 -0
  324. package/tui/dist/topology/runtime-marks.d.ts.map +1 -0
  325. package/tui/dist/topology/runtime-marks.js +185 -0
  326. package/tui/dist/topology/runtime-marks.js.map +1 -0
  327. package/tui/dist/topology/styles/braille.d.ts +5 -0
  328. package/tui/dist/topology/styles/braille.d.ts.map +1 -0
  329. package/tui/dist/topology/styles/braille.js +84 -0
  330. package/tui/dist/topology/styles/braille.js.map +1 -0
  331. package/tui/dist/topology/styles/hatchet.d.ts +41 -0
  332. package/tui/dist/topology/styles/hatchet.d.ts.map +1 -0
  333. package/tui/dist/topology/styles/hatchet.js +146 -0
  334. package/tui/dist/topology/styles/hatchet.js.map +1 -0
  335. package/tui/dist/types.d.ts +362 -0
  336. package/tui/dist/types.d.ts.map +1 -0
  337. package/tui/dist/types.js +6 -0
  338. package/tui/dist/types.js.map +1 -0
  339. package/ui/dist/assets/{index-DkNi0RT8.js → index-Dv5VaLCN.js} +135 -135
  340. package/ui/dist/index.html +1 -1
  341. package/dist/commands/context-pack.d.ts +0 -4
  342. package/dist/commands/context-pack.d.ts.map +0 -1
  343. package/dist/commands/context-pack.js +0 -357
  344. package/dist/commands/context-pack.js.map +0 -1
@@ -0,0 +1,807 @@
1
+ // Hand-rolled ANSI renderer (Phase-0 substrate decision). Pure function:
2
+ // (state, snapshot) → {lines, hitMap, explorerRows}. BOTH panes emit hit
3
+ // targets — explorer rows AND content-pane surfaces (table rows, view tabs,
4
+ // agent-refs, Needs-You items) — so a mouse click anywhere resolves to the
5
+ // SAME semantic actions commands produce (PIN 1). Isolated seam: a substrate
6
+ // swap touches only this module (spike verdict revisit trigger).
7
+ import { computeExplorerRows, findAgent, findSpec, findAgentBySession, agentsRunningSpec, agentsRunningSpecTargets, specDetailArrowsScroll } from "./state.js";
8
+ import { navigatorDisplay } from "./navigator.js";
9
+ import { renderGraphStyle } from "./topology/render-graph.js";
10
+ import { runtimeMarkSegs } from "./topology/runtime-marks.js";
11
+ import { barCells, flashActive, reducedMotion, spinnerFrame } from "./motion.js";
12
+ import { detailPage, fieldLine, sectionRule, listItem, alignedRow, LABEL_W } from "./detail.js";
13
+ const EXPL_W = 30;
14
+ function pad(text, width) {
15
+ const t = String(text ?? "");
16
+ if (t.length <= width)
17
+ return t + " ".repeat(width - t.length);
18
+ // never hard-clip mid-word: any truncation reads as an ellipsis (glance honesty)
19
+ return t.slice(0, Math.max(width - 1, 0)) + "…";
20
+ }
21
+ function padLeft(text, width) {
22
+ const t = String(text ?? "");
23
+ if (t.length <= width)
24
+ return " ".repeat(width - t.length) + t;
25
+ return t.slice(0, Math.max(width - 1, 0)) + "…";
26
+ }
27
+ const FULL_AGENT_COLS = [
28
+ ["rig", "RIG", 18, "left"],
29
+ ["pod", "POD", 8, "left"],
30
+ ["agent", "AGENT", 16, "left"],
31
+ ["runtime", "RUNTIME", 13, "left"],
32
+ ["context", "CTX%", 4, "right"],
33
+ ["tokens", "TOKENS", 7, "right"],
34
+ ["status", "STATUS", 17, "left"],
35
+ ["actions", "ACTIONS", 14, "left"],
36
+ ];
37
+ function columnsWidth(columns) {
38
+ return columns.reduce((total, [, , width]) => total + width + 1, -1);
39
+ }
40
+ function agentColumns(contentWidth) {
41
+ if (columnsWidth(FULL_AGENT_COLS) <= contentWidth)
42
+ return FULL_AGENT_COLS;
43
+ // At the 120-column fallback the content pane is 88 cells. Preserve the
44
+ // operable identity/status/action path; telemetry cells yield first.
45
+ const compactRest = [
46
+ ["pod", "POD", 8, "left"],
47
+ ["agent", "AGENT", 16, "left"],
48
+ ["runtime", "RUNTIME", 13, "left"],
49
+ ["status", "STATUS", 17, "left"],
50
+ ["actions", "ACTIONS", 14, "left"],
51
+ ];
52
+ const rigWidth = Math.max(6, Math.min(18, contentWidth - columnsWidth(compactRest) - 1));
53
+ return [["rig", "RIG", rigWidth, "left"], ...compactRest];
54
+ }
55
+ function tableRow(columns, cells) {
56
+ return columns.map(([key, , width, align]) => align === "right" ? padLeft(cells[key], width) : pad(cells[key], width)).join(" ");
57
+ }
58
+ function tabsLine(state, suffix) {
59
+ // slice-17: three tabs, each its own click zone (the first zone starts at
60
+ // content col 0, preserving the focus-marker floor); `tab graph` = the
61
+ // topology graph view (frame-01 hatchet mainline)
62
+ const labels = [
63
+ ["table", state.viewTab === "table" ? "[ TABLE ]" : " TABLE "],
64
+ ["overview", state.viewTab === "overview" ? "[ OVERVIEW ]" : " OVERVIEW "],
65
+ ["graph", state.viewTab === "graph" ? "[ GRAPH ]" : " GRAPH "],
66
+ ];
67
+ const text = `${labels.map(([, label]) => label).join("")} ${suffix}`;
68
+ const zones = [];
69
+ let at = 0;
70
+ for (const [tab, label] of labels) {
71
+ zones.push({ start: at, end: at + label.length, action: { type: "tab", tab } });
72
+ at += label.length;
73
+ }
74
+ return [{ text, zones }];
75
+ }
76
+ function specTabsLine(state, name) {
77
+ const active = state.viewTab === "topology" || state.viewTab === "yaml" ? state.viewTab : "configuration";
78
+ const labels = ["topology", "configuration", "yaml"];
79
+ const parts = labels.map((tab) => (tab === active ? `[ ${tab.toUpperCase()} ]` : ` ${tab.toUpperCase()} `));
80
+ const text = `rig spec ${name} ${parts.join(" ")}`;
81
+ return {
82
+ text,
83
+ zones: labels.map((tab, index) => {
84
+ const label = parts[index];
85
+ const start = text.indexOf(label);
86
+ return { start, end: start + label.length, action: { type: "tab", tab } };
87
+ }),
88
+ };
89
+ }
90
+ function needsLine(prefix, item, snap) {
91
+ // aligned columns (glance speed): kind · host · target · detail — same fact,
92
+ // same visual place, every row
93
+ const found = findAgentBySession(snap, item.target, item.hostId);
94
+ const cols = alignedRow([
95
+ [item.kind, 16],
96
+ [item.hostId ? `[${item.hostId}]` : "", 11],
97
+ [item.target, 34],
98
+ ]);
99
+ return {
100
+ text: `${prefix}${cols} ${item.detail}${found ? " (open ▸)" : ""}`,
101
+ ...(found ? { action: { type: "drill", resource: "agent", name: found.agent.name, target: { host: found.host.name, rig: found.rig.name, pod: found.pod.name } } } : {}),
102
+ };
103
+ }
104
+ function sourceProvenance(spec) {
105
+ if (spec.sourceType === "builtin")
106
+ return "built-in library";
107
+ if (spec.sourceType === "user_file")
108
+ return "user library";
109
+ return spec.sourceState === "library_item" ? "library" : "source unknown";
110
+ }
111
+ function displayPath(path, max = 68) {
112
+ if (path.length <= max)
113
+ return path;
114
+ const parts = path.split("/").filter(Boolean);
115
+ const kept = [];
116
+ while (parts.length > 0) {
117
+ const candidate = [parts.at(-1), ...kept];
118
+ if (`…/${candidate.join("/")}`.length > max)
119
+ break;
120
+ kept.unshift(parts.pop());
121
+ }
122
+ return `…/${kept.join("/")}`;
123
+ }
124
+ function wrappedList(prefix, values, max = 84) {
125
+ if (values.length === 0)
126
+ return [{ text: `${prefix}(none)` }];
127
+ const lines = [];
128
+ const indent = " ".repeat(prefix.length);
129
+ let current = prefix;
130
+ for (const value of values) {
131
+ const addition = `${current === prefix ? "" : ", "}${value}`;
132
+ if (current !== prefix && current.length + addition.length > max) {
133
+ lines.push({ text: current });
134
+ current = `${indent}${value}`;
135
+ }
136
+ else {
137
+ current += addition;
138
+ }
139
+ }
140
+ lines.push({ text: current });
141
+ return lines;
142
+ }
143
+ /** field row whose value list wraps at the value column (label rhythm kept) */
144
+ function fieldWrapped(label, values) {
145
+ if (values.length === 0)
146
+ return [fieldLine({ label, value: "(none)" })];
147
+ const valueCol = 2 + 12 + 1; // indent + LABEL_W + gap — where field values start
148
+ const wrapped = wrappedList(" ".repeat(valueCol), values, 92);
149
+ const first = wrapped[0].text.slice(valueCol);
150
+ return [fieldLine({ label, value: first }), ...wrapped.slice(1)];
151
+ }
152
+ function contentLines(state, snap, contentWidth, motion) {
153
+ const contentWidthForGraph = contentWidth;
154
+ void contentWidthForGraph;
155
+ const lines = [];
156
+ if (state.section === "topology") {
157
+ if (state.runningOf) {
158
+ const seats = agentsRunningSpecTargets(snap, state.runningOf);
159
+ return detailPage({ text: `seats running spec ${state.runningOf}` }, [
160
+ {
161
+ lines: seats.length === 0
162
+ ? [{ text: " (no seats currently run it)" }]
163
+ : seats.map((seat) => listItem(`${seat.agent.name} · ${seat.rig.name} / ${seat.pod.name} · ${seat.agent.status}`, {
164
+ type: "drill",
165
+ resource: "agent",
166
+ name: seat.agent.name,
167
+ target: { host: seat.host.name, rig: seat.rig.name, pod: seat.pod.name },
168
+ })),
169
+ },
170
+ ]);
171
+ }
172
+ const leaf = state.drill.at(-1);
173
+ if (leaf?.kind === "agent") {
174
+ const hostName = state.drill.find((part) => part.kind === "host")?.name;
175
+ const rigName = state.drill.find((part) => part.kind === "rig")?.name;
176
+ const podName = state.drill.find((part) => part.kind === "pod")?.name;
177
+ const found = hostName ? findAgent(snap, leaf.name, { host: hostName, rig: rigName, pod: podName }) : findAgent(snap, leaf.name);
178
+ if (!found)
179
+ return [{ text: `agent "${leaf.name}" not in the current snapshot` }];
180
+ const { agent, rig, pod } = found;
181
+ const specInLibrary = !!agent.spec && !!findSpec(snap, agent.spec);
182
+ // a4c9548a — S19 FOLLOW-ON FOUNDER RULING (binding, bounds the S19 marks
183
+ // ruling): where there is ROOM, WRITE THE NAME — an icon NEVER replaces text
184
+ // as the value. The detail page has room, so the runtime NAME is the VALUE
185
+ // (honest placeholder when unserved); the mark's OWN token segments still ride
186
+ // the seg channel (ROUND-4: colors/background survive stylization) but only
187
+ // ACCOMPANY the name decoratively, never substitute. Topology cards remain
188
+ // mark-only by space (layout.ts/hatchet.ts) — the ruling is bounded, not reversed.
189
+ // LEG-7 LOW 2 (dead-arm cleanup, wave qitem 79159e6f): the daemon coerces a null runtime to the
190
+ // string "unknown" BEFORE the wire (the icon-fix QA at 5a70e200 proved the old "— (not served)"
191
+ // arm unreachable), so the defensive fallback matches that coercion — honest, never a misleading
192
+ // placeholder. (The cwd field below keeps its own "— (not served)" — a legitimately-servable value.)
193
+ const rtName = agent.runtime ?? "unknown";
194
+ const rtSegs = [
195
+ { text: " " },
196
+ { text: "runtime:", token: "dim" },
197
+ { text: " ".repeat(LABEL_W - "runtime:".length + 1) },
198
+ { text: rtName },
199
+ { text: " " },
200
+ ...runtimeMarkSegs(agent.runtime),
201
+ ];
202
+ const runtimeLine = { text: rtSegs.map((g) => g.text).join(""), segs: rtSegs };
203
+ return detailPage({ text: `agent ${agent.name}` }, [
204
+ {
205
+ title: "seat",
206
+ fields: [
207
+ { label: "state", value: agent.status },
208
+ { label: "host", value: found.host.name },
209
+ { label: "rig", value: rig.name },
210
+ { label: "pod", value: pod.name },
211
+ ],
212
+ lines: [
213
+ runtimeLine,
214
+ fieldLine({
215
+ label: "context",
216
+ // ROUND-3 mr7: a quiet determinate bar rides REAL fractions only
217
+ value: agent.context == null
218
+ ? "— (not yet known)"
219
+ : `${agent.context}% used · ${agent.tokens ?? "—"} tokens ${barCells(agent.context / 100, 10)}`,
220
+ }),
221
+ // S19 MR4 (§D9, founder: "very important"): the FULL absolute
222
+ // working directory, verbatim; honest — when not served
223
+ fieldLine({ label: "cwd", value: agent.cwd ?? "— (not served)" }),
224
+ ],
225
+ },
226
+ {
227
+ title: "spec",
228
+ fields: [
229
+ specInLibrary
230
+ ? {
231
+ label: "spec",
232
+ value: agent.spec,
233
+ link: { type: "cross", kind: "spec-of", name: agent.name, target: { host: found.host.name, rig: rig.name, pod: pod.name } },
234
+ }
235
+ : { label: "spec", value: agent.spec ? `${agent.spec} (not in library)` : "—" },
236
+ ],
237
+ },
238
+ {
239
+ title: "actions",
240
+ fields: [
241
+ ...(agent.attach ? [{ label: "attach", value: agent.attach }] : []),
242
+ {
243
+ label: "terminal",
244
+ value: `term ▸ pod ${pod.name}`,
245
+ link: { type: "act", act: "open-terminal", view: `pod:${rig.name}/${pod.name}` },
246
+ },
247
+ ],
248
+ },
249
+ ]);
250
+ }
251
+ const rigName = state.drill.find((d) => d.kind === "rig")?.name ?? snap.hosts[0]?.rigs[0]?.name;
252
+ const hostName = state.drill.find((d) => d.kind === "host")?.name;
253
+ const host = (hostName ? snap.hosts.find((candidate) => candidate.name === hostName) : snap.hosts[0]);
254
+ const rig = host?.rigs.find((candidate) => candidate.name === rigName);
255
+ if (!rig || !host) {
256
+ // round-6 (guard): the ROOT topology branch consumes the OWNER's load
257
+ // truth like every other read surface — a real in-flight cold start
258
+ // renders the spinner; after settlement only a NAMED rigs-summary
259
+ // failure or the proven no-rigs truth may render, never "waiting"
260
+ if (motion.loading) {
261
+ if (!motion.reduced)
262
+ motion.used = true;
263
+ return [{ text: `${motion.frame} topology read pending — waiting on the daemon rigs read (honest-empty, not fabricated)` }];
264
+ }
265
+ if (snap.readErrors.some((e) => e.startsWith("rigs-summary"))) {
266
+ return [{ text: "✕ rigs read failed — named in the status line (honest-empty, not fabricated)" }];
267
+ }
268
+ return [{ text: "(no rigs served — proven empty, not fabricated)" }];
269
+ }
270
+ const podFilter = leaf?.kind === "pod" ? leaf.name : null;
271
+ const all = rig.pods.flatMap((p) => p.agents.map((a) => ({ pod: p.name, ...a })));
272
+ const rows = all
273
+ .filter((a) => !podFilter || a.pod === podFilter)
274
+ .filter((a) => !state.filter || a.name.includes(state.filter) || a.pod.includes(state.filter));
275
+ const suffix = `rig ${rig.name}${podFilter ? ` · pod ${podFilter}` : ""}${state.filter ? ` · filter "${state.filter}"` : ""}`;
276
+ lines.push(...tabsLine(state, suffix));
277
+ if (state.viewTab === "graph") {
278
+ // slice-17 topology view (frame-01): the rig's SERVED /graph projection
279
+ // rendered by the style registry; honest-empty until the read answers.
280
+ if (!rig.graph) {
281
+ lines.push({ text: "" });
282
+ // round-5 (guard): the spinner rides the OWNER's in-flight state only;
283
+ // settled absence renders the honest static truth — a NAMED failure or
284
+ // a proven-empty read — and never spins
285
+ if (motion.loading) {
286
+ if (!motion.reduced)
287
+ motion.used = true;
288
+ lines.push({ text: ` ${motion.frame} topology graph read pending (honest-empty, never fabricated)` });
289
+ }
290
+ else if (snap.readErrors.some((e) => e.startsWith(`graph(${rig.name})`))) {
291
+ lines.push({ text: " ✕ topology graph read failed — named in the status line (honest-empty, never fabricated)" });
292
+ }
293
+ else {
294
+ lines.push({ text: " (no topology graph served — honest-empty, never fabricated)" });
295
+ }
296
+ return lines;
297
+ }
298
+ // PER-VIEW zoom (PM b7f95c4b): a pod drill scopes the SAME projection to
299
+ // that pod's containment subgraph — nodes clipped at rig scale become
300
+ // visible AND eligible here; eligibility is always the current view's
301
+ // clipped hit-zone truth, never a global filter.
302
+ let graphView = rig.graph;
303
+ if (podFilter) {
304
+ const podGroup = rig.graph.nodes.find((n) => n.type === "podGroup" && (n.data.podNamespace ?? n.data.logicalId) === podFilter);
305
+ const memberIds = new Set(rig.graph.nodes.filter((n) => n.parentId && n.parentId === podGroup?.id).map((n) => n.id));
306
+ graphView = {
307
+ nodes: rig.graph.nodes.filter((n) => n === podGroup || memberIds.has(n.id)),
308
+ edges: rig.graph.edges.filter((e) => memberIds.has(e.source) && memberIds.has(e.target)),
309
+ };
310
+ }
311
+ const canvas = renderGraphStyle(state.graphStyle, graphView, { host: host.name, rig: rig.name, selected: null }, contentWidth);
312
+ const plain = canvas.plainLines();
313
+ const segs = canvas.segLines();
314
+ for (let row = 0; row < plain.length; row++) {
315
+ lines.push({
316
+ text: plain[row],
317
+ segs: segs[row],
318
+ zones: canvas.zones.filter((z) => z.y === row).map((z) => ({ start: z.start, end: z.end, action: z.action })),
319
+ });
320
+ }
321
+ lines.push({ text: "" });
322
+ lines.push({ text: ` style: ${state.graphStyle} · style hatchet|braille|braille-fallback rides the command bar` });
323
+ return lines;
324
+ }
325
+ lines.push({ text: state.filter ? `/ filter agents: ${state.filter}` : "/ filter agents…" });
326
+ if (state.viewTab === "overview") {
327
+ lines.push(...detailPage({ text: `rig ${rig.name}` }, [
328
+ {
329
+ title: "rig",
330
+ fields: [
331
+ { label: "host", value: host.name },
332
+ { label: "shape", value: `${rig.pods.length} pods · ${all.length} agents` },
333
+ ...(rig.lifecycleState ? [{ label: "state", value: rig.lifecycleState }] : []),
334
+ ],
335
+ },
336
+ {
337
+ title: "pods",
338
+ lines: rig.pods.map((pod) => listItem(alignedRow([[pod.name, 14], [`${pod.agents.length} agents`, 10], [pod.agents.map((a) => a.status).filter((s, i, arr) => arr.indexOf(s) === i).join(" · "), 40]]), { type: "drill", resource: "pod", name: pod.name, target: { host: host.name, rig: rig.name } })),
339
+ },
340
+ ]));
341
+ return lines;
342
+ }
343
+ const agentCols = agentColumns(contentWidth);
344
+ lines.push({
345
+ text: tableRow(agentCols, {
346
+ rig: "RIG", pod: "POD", agent: "AGENT", runtime: "RUNTIME",
347
+ context: "CTX%", tokens: "TOKENS", status: "STATUS", actions: "ACTIONS",
348
+ }),
349
+ });
350
+ lines.push({ text: "─".repeat(columnsWidth(agentCols)) });
351
+ const actionsIndex = agentCols.findIndex(([key]) => key === "actions");
352
+ const actionsColStart = agentCols.slice(0, actionsIndex).reduce((n, [, , width]) => n + width + 1, 0);
353
+ for (const a of rows) {
354
+ // ACTIONS = drive-structure ONLY (BR-9), each mapped to an EXISTING
355
+ // write contract: `run ▸` = the rig-restore write (rendered only where
356
+ // it applies — the seat is not running); `term ▸` = the terminal-open
357
+ // view contract (pod-scoped, the web's granularity). No false affordance.
358
+ const canRun = a.canRun ?? !a.live;
359
+ const actionsCell = canRun ? "run ▸ · term ▸" : "term ▸";
360
+ const zones = [];
361
+ const termOffset = actionsColStart + actionsCell.indexOf("term ▸");
362
+ zones.push({ start: termOffset, end: termOffset + "term ▸".length, action: { type: "act", act: "open-terminal", view: `pod:${rig.name}/${a.pod}` } });
363
+ if (canRun)
364
+ zones.push({
365
+ start: actionsColStart,
366
+ end: actionsColStart + "run ▸".length,
367
+ action: { type: "act", act: "run", rigId: rig.id ?? rig.name, agent: a.name },
368
+ });
369
+ lines.push({
370
+ // the WHOLE row is the hit surface (not a testid'd control): clicking
371
+ // any visible cell opens the agent; the ACTIONS zones override.
372
+ text: tableRow(agentCols, {
373
+ rig: rig.name,
374
+ pod: a.pod,
375
+ agent: a.name,
376
+ runtime: a.runtime,
377
+ context: a.context == null ? "—" : `${a.context}%`,
378
+ tokens: a.tokens ?? "—",
379
+ status: a.status,
380
+ actions: actionsCell,
381
+ }),
382
+ action: { type: "drill", resource: "agent", name: a.name, target: { host: host.name, rig: rig.name, pod: a.pod } },
383
+ zones,
384
+ });
385
+ }
386
+ lines.push({ text: "" });
387
+ lines.push({ text: `${rows.length} of ${all.length} / ${rows.filter((agent) => agent.status === "idle").length} idle` });
388
+ return lines;
389
+ }
390
+ if (state.section === "specs") {
391
+ const leaf = state.drill.at(-1);
392
+ if (leaf?.kind === "spec") {
393
+ const spec = findSpec(snap, leaf.name);
394
+ if (!spec)
395
+ return [{ text: `spec "${leaf.name}" not in the current snapshot` }];
396
+ if (spec.kind === "rig") {
397
+ lines.push(specTabsLine(state, spec.name));
398
+ if (spec.sourcePath)
399
+ lines.push(fieldLine({ label: "source", value: `${displayPath(spec.sourcePath, 56)} · ${sourceProvenance(spec)}` }));
400
+ if (state.viewTab === "topology") {
401
+ // ROUND-4 item 1: the established table treatment, not unformatted rows
402
+ const nodes = spec.graph?.nodes ?? [];
403
+ const graphEdges = spec.graph?.edges ?? [];
404
+ const NODE_COLS = [["NODE", 16], ["LABEL", 24], ["POD", 12], ["RUNTIME", 14]];
405
+ lines.push(fieldLine({ label: "shape", value: `${nodes.length} nodes · ${graphEdges.length} edges` }));
406
+ lines.push({ text: "" });
407
+ if (nodes.length === 0) {
408
+ lines.push({ text: " (topology projection is empty)" });
409
+ return lines;
410
+ }
411
+ lines.push({ text: ` ${alignedRow(NODE_COLS)}` });
412
+ lines.push({ text: ` ${"─".repeat(NODE_COLS.reduce((n, [, w]) => n + w + 1, -1))}` });
413
+ for (const node of nodes)
414
+ lines.push({ text: ` ${alignedRow([[node.id, 16], [node.label, 24], [node.pod ?? "—", 12], [node.runtime, 14]])}` });
415
+ if (graphEdges.length > 0) {
416
+ lines.push({ text: "" });
417
+ lines.push(sectionRule("edges"));
418
+ for (const edge of graphEdges)
419
+ lines.push({ text: ` ${alignedRow([[edge.source, 16], ["→", 2], [edge.target, 20]])} (${edge.kind})` });
420
+ }
421
+ return lines;
422
+ }
423
+ if (state.viewTab === "yaml") {
424
+ for (const rawLine of (spec.raw ?? "# raw YAML unavailable").split("\n"))
425
+ lines.push({ text: ` ${rawLine}` });
426
+ return lines;
427
+ }
428
+ const members = spec.pods?.reduce((count, pod) => count + pod.members.length, 0) ?? spec.legacyNodes?.length ?? 0;
429
+ const edges = (spec.edges?.length ?? 0) + (spec.pods?.reduce((count, pod) => count + pod.edges.length, 0) ?? 0);
430
+ lines.push(...detailPage({ text: "" }, [
431
+ {
432
+ fields: [
433
+ ...(spec.format ? [{ label: "format", value: spec.format.replace("_", "-") }] : []),
434
+ { label: "shape", value: `${spec.pods?.length ?? 0} pods · ${members} members · ${edges} edges` },
435
+ ],
436
+ },
437
+ ...(spec.pods ?? []).map((pod) => ({
438
+ title: `pod ${pod.namespace ?? pod.id}${pod.label ? ` — ${pod.label}` : ""}`,
439
+ lines: [
440
+ ...pod.members.map((member) => listItem(`${alignedRow([[member.id, 12], [member.agentRef, 34], [member.runtime, 12]])}${member.profile ? ` profile ${member.profile}` : ""}`, { type: "drill", resource: "spec", name: member.agentRef })),
441
+ ...pod.edges.map((edge) => ({ text: ` ${edge.from} → ${edge.to} (${edge.kind})` })),
442
+ // an empty pod still exists — render it honestly, never skip it
443
+ ...(pod.members.length === 0 && pod.edges.length === 0 ? [{ text: " (no members)" }] : []),
444
+ ],
445
+ })),
446
+ ...(spec.legacyNodes?.length
447
+ ? [{ title: "nodes", lines: spec.legacyNodes.map((node) => listItem(`${alignedRow([[node.id, 16], [node.runtime, 12]])}${node.role ? ` ${node.role}` : ""}`)) }]
448
+ : []),
449
+ ...((spec.edges?.length ?? 0) > 0
450
+ ? [{ title: "cross-pod edges", lines: (spec.edges ?? []).map((edge) => ({ text: ` ${edge.from} → ${edge.to} (${edge.kind})` })) }]
451
+ : []),
452
+ ]).slice(1));
453
+ }
454
+ else if (spec.kind === "workflow") {
455
+ lines.push(...detailPage({ text: `workflow spec ${spec.name}${spec.version ? ` · v${spec.version}` : ""}` }, [
456
+ {
457
+ title: "workflow",
458
+ fields: [
459
+ { label: "roles", value: spec.rolesCount != null ? String(spec.rolesCount) : "—" },
460
+ { label: "steps", value: spec.stepsCount != null ? String(spec.stepsCount) : "—" },
461
+ { label: "status", value: spec.workflowStatus ?? "—" },
462
+ ],
463
+ },
464
+ {
465
+ title: "source",
466
+ fields: [{ label: "source", value: spec.sourcePath ? `${displayPath(spec.sourcePath)} · ${sourceProvenance(spec)}` : "—" }],
467
+ },
468
+ ]));
469
+ }
470
+ else {
471
+ // the mockup's agent-spec frame IS the field-grid reference — recreate it
472
+ const seats = agentsRunningSpec(snap, spec.name);
473
+ const resources = [
474
+ spec.resources?.guidance.length ? `guidance ${spec.resources.guidance.join(", ")}` : "",
475
+ spec.resources?.plugins.length ? `plugins ${spec.resources.plugins.join(", ")}` : "",
476
+ spec.resources?.subagents.length ? `subagents ${spec.resources.subagents.join(", ")}` : "",
477
+ ].filter(Boolean);
478
+ lines.push(...detailPage({ text: `agent spec ${spec.name}${spec.version ? ` · v${spec.version}` : ""}` }, [
479
+ {
480
+ title: "spec",
481
+ fields: [
482
+ ...(spec.description ? [{ label: "about", value: spec.description }] : []),
483
+ { label: "runtime", value: spec.runtime ?? "—" },
484
+ ],
485
+ lines: spec.skills ? fieldWrapped("skills", spec.skills) : [],
486
+ },
487
+ {
488
+ title: "startup",
489
+ fields: [
490
+ ...(spec.hasGuidance != null ? [{ label: "guidance", value: spec.hasGuidance ? "yes" : "no" }] : []),
491
+ ...(spec.startupFiles ?? []).map((f) => ({ label: "startup", value: `${f.path}${f.required ? " (required)" : ""}` })),
492
+ ...(spec.profiles?.length ? [{ label: "profiles", value: spec.profiles.join(", ") }] : []),
493
+ ...(spec.resources ? [{ label: "resources", value: resources.join(" · ") || "(none beyond skills)" }] : []),
494
+ ],
495
+ },
496
+ {
497
+ title: "source",
498
+ fields: [{ label: "source", value: spec.sourcePath ? `${displayPath(spec.sourcePath, 56)} · ${sourceProvenance(spec)}` : "—" }],
499
+ },
500
+ {
501
+ title: "where it runs",
502
+ fields: [
503
+ ...((spec.usedByRigs?.length ?? 0) === 0
504
+ ? [{ label: "used by", value: "—" }]
505
+ : (spec.usedByRigs ?? []).map((rig) => ({
506
+ label: "used by",
507
+ value: `rig ${rig}`,
508
+ link: { type: "drill", resource: "spec", name: rig },
509
+ }))),
510
+ {
511
+ label: "seats now",
512
+ value: seats.join(", ") || "(none)",
513
+ link: { type: "cross", kind: "running", name: spec.name },
514
+ },
515
+ ],
516
+ },
517
+ ]));
518
+ }
519
+ return lines;
520
+ }
521
+ lines.push({ text: "SPEC LIBRARY" });
522
+ lines.push({ text: state.filter ? `/ filter specs: ${state.filter}` : "/ filter specs…" });
523
+ // mirrors the explorer exactly (same grouping, same expansion state, same
524
+ // filter-overrides-collapse rule) — glance consistency across panes
525
+ const expandedSet = new Set(state.expanded);
526
+ for (const kind of ["rig", "agent", "workflow"]) {
527
+ const shown = snap.specs.filter((s) => s.kind === kind).filter((s) => !state.filter || s.name.includes(state.filter));
528
+ if (shown.length === 0 && !snap.specs.some((s) => s.kind === kind))
529
+ continue;
530
+ lines.push({ text: ` ${kind.toUpperCase()} (${shown.length})` });
531
+ if (kind !== "agent") {
532
+ for (const s of shown)
533
+ lines.push({ text: ` ▪ ${s.name}`, action: { type: "drill", resource: "spec", name: s.name } });
534
+ continue;
535
+ }
536
+ const groups = new Map();
537
+ for (const s of shown) {
538
+ const namespace = s.namespace ?? "(root)";
539
+ groups.set(namespace, [...(groups.get(namespace) ?? []), s]);
540
+ }
541
+ for (const [namespace, specs] of [...groups.entries()].sort(([a], [b]) => a.localeCompare(b))) {
542
+ const open = namespace === "(root)" || expandedSet.has(`folder:${namespace}`) || !!state.filter;
543
+ if (namespace !== "(root)")
544
+ lines.push({
545
+ text: ` ${open ? "▾" : "▸"} ${namespace}/ (${specs.length})`,
546
+ action: { type: "toggle-expand", key: `folder:${namespace}` },
547
+ });
548
+ if (!open)
549
+ continue;
550
+ for (const s of specs)
551
+ lines.push({
552
+ text: `${namespace === "(root)" ? " " : " "}▪ ${s.name}`,
553
+ action: { type: "drill", resource: "spec", name: s.name },
554
+ });
555
+ }
556
+ }
557
+ if (snap.specs.length === 0) {
558
+ // round-5 (guard): spin only while the owner is loading; settled empty
559
+ // is PROVEN empty and a settled failure is named — neither spins
560
+ if (motion.loading) {
561
+ if (!motion.reduced)
562
+ motion.used = true;
563
+ lines.push({ text: ` ${motion.frame} library read pending — honest-empty` });
564
+ }
565
+ else if (snap.readErrors.some((e) => e.startsWith("specs-library"))) {
566
+ lines.push({ text: " ✕ library read failed — named in the status line" });
567
+ }
568
+ else {
569
+ lines.push({ text: " (library empty — proven, no specs served)" });
570
+ }
571
+ }
572
+ return lines;
573
+ }
574
+ if (state.section === "needs") {
575
+ lines.push({ text: "NEEDS-YOU" });
576
+ for (const item of snap.needs) {
577
+ // open/navigate is the ONLY in-TUI action (B3): the click target joins
578
+ // the item's session back to topology; unresolvable targets never
579
+ // advertise a control that can only fail.
580
+ lines.push(needsLine(item.source === "agent" ? " ☐ " : " ⚑ ", item, snap));
581
+ }
582
+ if (snap.needs.length === 0 && snap.humanQueueProbed)
583
+ lines.push({ text: " (no fleet attention items right now)" });
584
+ if (snap.hostsDown.length > 0) {
585
+ // composed BESIDE the items (a separate shipped read), never into the item shape
586
+ lines.push({ text: "" });
587
+ lines.push({ text: " hosts/rigs down:" });
588
+ // NB: glyphs here must be single-cell — U+26D4 ⛔ is emoji-width (2 cells)
589
+ // and wraps a full-width padded line, shearing every row below it.
590
+ for (const h of snap.hostsDown)
591
+ lines.push({ text: ` ✖ ${alignedRow([[h.hostId, 28], [h.status, 26]])} ${h.error ?? ""}`.trimEnd() });
592
+ }
593
+ lines.push({ text: "" });
594
+ if (!snap.humanQueueProbed) {
595
+ if (motion.loading) {
596
+ // region discipline (max ONE persistent animation): while a derived ⚑
597
+ // item pulses on this page, the pending spinner degrades to the honest
598
+ // static dot instead of animating beside it
599
+ const pulseVisible = snap.needs.some((item) => item.source === "derived") && !motion.reduced;
600
+ const spin = pulseVisible ? "·" : motion.frame;
601
+ if (!pulseVisible && !motion.reduced)
602
+ motion.used = true;
603
+ lines.push({ text: ` ${spin} human-queue: not yet known (read pending)` });
604
+ }
605
+ else {
606
+ // round-5 (guard): settled-unprobed is a static truth (hosts down or
607
+ // registry unavailable) — "(read pending)" would be a false claim
608
+ lines.push({ text: " human-queue: not yet known (hosts unreachable or registry unavailable)" });
609
+ }
610
+ }
611
+ else if (!snap.needs.some((item) => item.source === "agent"))
612
+ lines.push({ text: " human-queue: no items (proven empty — surfacing adoption pending)" });
613
+ return lines;
614
+ }
615
+ return [{ text: `(${state.section})` }];
616
+ }
617
+ /** replace ONE character at a plain-text position inside a token-segment row
618
+ * with the keyboard focus marker (accent, bold) — keeps plain(segs) equal to
619
+ * the spliced content text (R2 HIGH-3) */
620
+ function spliceMarkerIntoSegs(segs, pos) {
621
+ const out = [];
622
+ let at = 0;
623
+ for (const seg of segs) {
624
+ const end = at + seg.text.length;
625
+ if (pos >= at && pos < end) {
626
+ const off = pos - at;
627
+ if (off > 0)
628
+ out.push({ ...seg, text: seg.text.slice(0, off) });
629
+ out.push({ text: "›", token: "accent", bold: true });
630
+ if (off + 1 < seg.text.length)
631
+ out.push({ ...seg, text: seg.text.slice(off + 1) });
632
+ }
633
+ else {
634
+ out.push(seg);
635
+ }
636
+ at = end;
637
+ }
638
+ return out;
639
+ }
640
+ function paneRule(cols, joint, leftTitle, rightTitle) {
641
+ const left = leftTitle ? `─ ${leftTitle} ` : "";
642
+ const right = rightTitle ? `─ ${rightTitle} ` : "";
643
+ const leftPart = (left + "─".repeat(EXPL_W)).slice(0, EXPL_W);
644
+ const rightPart = (right + "─".repeat(cols)).slice(0, Math.max(cols - EXPL_W - 1, 0));
645
+ return `${leftPart}${joint}${rightPart}`;
646
+ }
647
+ function keybindHints(state) {
648
+ // Affordance surfaces on REAL scrollability (contentMaxOffset), never gated
649
+ // behind already-being-content-focused — that gate was the catch-22 (the
650
+ // hint hid exactly where it was needed). When ↑↓ themselves scroll (a
651
+ // scrollable spec detail), the nav label says so; otherwise ↑↓ move and the
652
+ // page keys carry the scroll.
653
+ const arrowsScroll = specDetailArrowsScroll(state);
654
+ const nav = arrowsScroll ? "↑↓ scroll" : "↑↓ move";
655
+ const pageScroll = state.contentMaxOffset > 0 && !arrowsScroll ? "⇞⇟ scroll · " : "";
656
+ return `${nav} · ←→ pane · ⏎ open · ${pageScroll}: command · / filter · f footer · q quit`;
657
+ }
658
+ export function renderScreen(state, snap, options = {}, inputLine = "") {
659
+ const { cols = 120, rows = 32, nowMs = 0 } = options;
660
+ // S19 round-5 (guard): one spinner frame per render pass from caller time;
661
+ // `loading` comes from the refresh OWNER (omitted = settled — demo/fixture
662
+ // data IS the answer); reduced-motion kills all of it
663
+ const reduced = reducedMotion();
664
+ const load = options.load ?? { inFlight: false, settled: true };
665
+ const motion = {
666
+ frame: spinnerFrame(Math.floor(nowMs / 120), options.colorMode ?? "truecolor", reduced),
667
+ reduced,
668
+ used: false,
669
+ loading: load.inFlight || !load.settled,
670
+ };
671
+ const lines = [];
672
+ const hitMap = [];
673
+ // S19 MR5a (guard-corrected): ONE ▊ insertion cell renders at the bar's
674
+ // current insertion point for EMPTY and non-empty buffers alike — the
675
+ // shell accepts typing from the empty state, so the honest readiness
676
+ // affordance must show BEFORE the first key (no new focus state; stylize
677
+ // gives the cell SGR blink; zero effect on hit geometry).
678
+ lines.push(pad(`cmd ▸ ${inputLine}▊`, cols));
679
+ const sectionTitle = { topology: "TOPOLOGY", specs: "SPECS", needs: "NEEDS-YOU" }[state.section] ?? state.section.toUpperCase();
680
+ // active-pane emphasis (k9s-class chrome): the focused pane's title is bracketed
681
+ const explorerTitle = state.focusedPane === "explorer" ? "[ EXPLORER ]" : "EXPLORER";
682
+ const contentTitle = state.focusedPane === "content" ? `[ ${sectionTitle} ]` : sectionTitle;
683
+ lines.push(paneRule(cols, "┬", explorerTitle, contentTitle));
684
+ const explorer = computeExplorerRows(state, snap);
685
+ // Slice-17: the file-tree re-skin is a DISPLAY transform only — rows, keys,
686
+ // actions, and the hit-map all keep resolving against the row model above.
687
+ const { labels: explorerDisplay, metas: explorerMetas } = navigatorDisplay(explorer, snap, EXPL_W - 1);
688
+ const content = contentLines(state, snap, Math.max(cols - EXPL_W - 2, 0), motion);
689
+ const footer = state.footerOn ? snap.stream.at(-1) : undefined;
690
+ // round-5 (guard): the tmux-style ONE-SHOT activity flash targets the
691
+ // flashed agent's EXPLORER row — per-seat pane-output events from the
692
+ // refresh owner, windowed here. The ambient rig-stream footer is NOT an
693
+ // event source and never flashes. round-6 (guard finding 2): the SGR
694
+ // inverse is the animation (killed under reduced motion), while the
695
+ // acknowledgement WINDOW itself ignores reduced — the plain-layer "≈"
696
+ // marker-slot glyph is the stable static signal reduced motion (and
697
+ // NO_COLOR) keeps, expiring with the same bounded window.
698
+ const liveFlashes = (options.rowFlashes ?? []).filter((f) => flashActive(f.at, nowMs, 600, reduced));
699
+ const ackFlashes = (options.rowFlashes ?? []).filter((f) => flashActive(f.at, nowMs, 600, false));
700
+ const chromeRows = footer ? 4 : 3; // bottom rule + hint bar + status line (+ footer)
701
+ const bodyRows = Math.max(rows - 2 - chromeRows, 1);
702
+ const explorerStart = Math.min(Math.max(state.selection - bodyRows + 1, 0), Math.max(explorer.length - bodyRows, 0));
703
+ const contentRows = content.length > bodyRows ? Math.max(bodyRows - 1, 0) : bodyRows;
704
+ const maxContentOffset = Math.max(content.length - contentRows, 0);
705
+ const contentStart = Math.min(state.contentOffset, maxContentOffset);
706
+ const visibleContent = content.slice(contentStart, contentStart + contentRows);
707
+ if (content.length > bodyRows) {
708
+ const scrollText = `scroll ↑/↓ · ${contentStart + 1}-${contentStart + visibleContent.length} of ${content.length}`;
709
+ const up = scrollText.indexOf("↑");
710
+ const down = scrollText.indexOf("↓");
711
+ visibleContent.push({
712
+ text: scrollText,
713
+ zones: [
714
+ { start: up, end: up + 1, action: { type: "content-scroll", delta: -10 } },
715
+ { start: down, end: down + 1, action: { type: "content-scroll", delta: 10 } },
716
+ ],
717
+ });
718
+ }
719
+ const explorerRows = [];
720
+ const contentTargets = [];
721
+ const segRows = {};
722
+ const explorerMeta = {};
723
+ const flashRows = [];
724
+ let flashAck = false;
725
+ for (let i = 0; i < bodyRows; i++) {
726
+ const y = lines.length + 1; // 1-based terminal row this line will occupy
727
+ const explorerIndex = explorerStart + i;
728
+ const row = explorer[explorerIndex];
729
+ // round-6/7 (guard): the fresh-output ack rides the marker slot (zero
730
+ // geometry drift). Collision matrix: a SELECTED flashed row shows "»" —
731
+ // still unmistakably the selection chevron, while visibly distinct from
732
+ // both the plain "›" baseline and the unselected "≈" ack — so neither
733
+ // signal is lost under reduced motion / NO_COLOR; expiry returns the
734
+ // exact "›" baseline
735
+ const flashed = row?.key != null && ackFlashes.some((f) => f.key === row.key);
736
+ if (flashed)
737
+ flashAck = true;
738
+ const marker = explorerIndex === state.selection && row ? (flashed ? "»" : "›") : flashed ? "≈" : " ";
739
+ const left = pad(row ? `${marker}${explorerDisplay[explorerIndex] ?? row.label}` : "", EXPL_W);
740
+ const item = visibleContent[i];
741
+ const targetIndex = contentTargets.length;
742
+ const zones = item?.zones ?? [];
743
+ const selectedOnLine = state.focusedPane === "content" ? state.contentSelection - targetIndex : -1;
744
+ const selectedZone = selectedOnLine >= 0 && selectedOnLine < zones.length ? zones[selectedOnLine] : undefined;
745
+ const selectedAction = !!item?.action && selectedOnLine === zones.length;
746
+ let contentText = item?.text ?? "";
747
+ let contentMarker = selectedAction ? "›" : " ";
748
+ let rowSegs = item?.segs;
749
+ if (selectedZone) {
750
+ if (selectedZone.start > 0) {
751
+ contentText = `${contentText.slice(0, selectedZone.start - 1)}›${contentText.slice(selectedZone.start)}`;
752
+ // R2 HIGH-3: a segs row's paint source must carry the SAME splice the
753
+ // plain text carries, or stylization erases the keyboard focus marker
754
+ if (rowSegs)
755
+ rowSegs = spliceMarkerIntoSegs(rowSegs, selectedZone.start - 1);
756
+ }
757
+ else
758
+ contentMarker = "›";
759
+ }
760
+ lines.push(pad(`${left}│${contentMarker}${contentText}`, cols));
761
+ if (row) {
762
+ hitMap.push({ y, x1: 1, x2: EXPL_W, action: row.action });
763
+ explorerRows.push({ ...row, y });
764
+ const em = explorerMetas[explorerIndex];
765
+ if (em && em.length)
766
+ explorerMeta[y] = em.map((run) => ({ start: 1 + run.start, segs: run.segs })); // +1 = marker slot
767
+ if (row.key && liveFlashes.some((f) => f.key === row.key))
768
+ flashRows.push(y);
769
+ }
770
+ // zones first: hit lookup takes the first match, so a zone wins over the row-wide action
771
+ for (const z of zones) {
772
+ const target = { y, x1: EXPL_W + 3 + z.start, x2: EXPL_W + 2 + z.end, action: z.action };
773
+ hitMap.push(target);
774
+ contentTargets.push(target);
775
+ }
776
+ if (item?.action) {
777
+ const target = { y, x1: EXPL_W + 3, x2: cols, action: item.action };
778
+ hitMap.push(target);
779
+ contentTargets.push(target);
780
+ }
781
+ if (rowSegs)
782
+ segRows[y] = rowSegs;
783
+ }
784
+ if (footer)
785
+ lines.push(pad(`≋ ${footer.tsEmitted.slice(11, 16)} ${footer.sourceSession}: ${footer.body}`, cols));
786
+ const drillPath = state.drill.map((d) => d.name).join(" → ");
787
+ const readWarn = snap.readErrors.length > 0 ? ` ⚠ ${snap.readErrors.length} read(s) failed: ${snap.readErrors[0]}` : "";
788
+ lines.push(paneRule(cols, "┴"));
789
+ lines.push(pad(keybindHints(state), cols));
790
+ lines.push(pad(`[${state.instanceId}] ${state.section}${drillPath ? " · " + drillPath : ""}${state.lastError ? " ✗ " + state.lastError : ""}${state.notice ? " ▸ " + state.notice : ""}${readWarn}`, cols));
791
+ while (lines.length < rows)
792
+ lines.push("");
793
+ return {
794
+ lines: lines.slice(0, rows),
795
+ hitMap,
796
+ contentTargets,
797
+ contentMaxOffset: maxContentOffset,
798
+ explorerRows,
799
+ segRows,
800
+ explorerMeta,
801
+ flashRows,
802
+ // an un-expired ack (even the static reduced-motion glyph) schedules the
803
+ // bounded expiry redraw — the acknowledgement must settle cleanly
804
+ motionActive: motion.used || flashRows.length > 0 || flashAck,
805
+ };
806
+ }
807
+ //# sourceMappingURL=render.js.map