@llblab/pi-kit 0.38.3 → 0.39.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 (430) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +108 -20
  4. package/node_modules/@llblab/pi-actors/BACKLOG.md +207 -4
  5. package/node_modules/@llblab/pi-actors/CHANGELOG.md +13 -0
  6. package/node_modules/@llblab/pi-actors/README.md +31 -1
  7. package/node_modules/@llblab/pi-actors/dist/index.js +1 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +37 -0
  9. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +131 -62
  10. package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.d.ts +2 -0
  11. package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.js +3 -2
  12. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.d.ts +37 -0
  13. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +263 -129
  14. package/node_modules/@llblab/pi-actors/dist/lib/execution-files.d.ts +32 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/execution-files.js +128 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/execution-output.js +5 -4
  17. package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.d.ts +19 -1
  18. package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.js +62 -26
  19. package/node_modules/@llblab/pi-actors/dist/lib/execution.d.ts +4 -1
  20. package/node_modules/@llblab/pi-actors/dist/lib/execution.js +6 -1
  21. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +9 -2
  22. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +92 -14
  23. package/node_modules/@llblab/pi-actors/dist/lib/file-state.js +21 -2
  24. package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.js +2 -2
  25. package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.d.ts +25 -7
  26. package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +562 -262
  27. package/node_modules/@llblab/pi-actors/dist/lib/inspector.d.ts +20 -10
  28. package/node_modules/@llblab/pi-actors/dist/lib/inspector.js +80 -91
  29. package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +1 -0
  30. package/node_modules/@llblab/pi-actors/dist/lib/limits.js +1 -0
  31. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +15 -1
  32. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +216 -99
  33. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +5 -4
  34. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +64 -10
  35. package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.d.ts +18 -0
  36. package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.js +220 -0
  37. package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.d.ts +2 -0
  38. package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.js +30 -6
  39. package/node_modules/@llblab/pi-actors/dist/lib/registry.js +270 -36
  40. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +213 -5
  41. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +1909 -40
  42. package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.d.ts +107 -0
  43. package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.js +412 -0
  44. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +9 -2
  45. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +283 -60
  46. package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.d.ts +26 -1
  47. package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.js +68 -2
  48. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.d.ts +451 -0
  49. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.js +2315 -0
  50. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.d.ts +97 -0
  51. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.js +368 -0
  52. package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.js +46 -7
  53. package/node_modules/@llblab/pi-actors/dist/lib/runs-control.d.ts +78 -2
  54. package/node_modules/@llblab/pi-actors/dist/lib/runs-control.js +334 -2
  55. package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.js +4 -1
  56. package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.d.ts +80 -0
  57. package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.js +287 -0
  58. package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.d.ts +110 -0
  59. package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.js +282 -0
  60. package/node_modules/@llblab/pi-actors/dist/lib/runs-index.d.ts +66 -1
  61. package/node_modules/@llblab/pi-actors/dist/lib/runs-index.js +349 -13
  62. package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.d.ts +73 -0
  63. package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.js +227 -0
  64. package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.d.ts +141 -0
  65. package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.js +292 -0
  66. package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.d.ts +10 -1
  67. package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.js +36 -4
  68. package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.d.ts +46 -0
  69. package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.js +114 -0
  70. package/node_modules/@llblab/pi-actors/dist/lib/runs-process.d.ts +34 -1
  71. package/node_modules/@llblab/pi-actors/dist/lib/runs-process.js +158 -26
  72. package/node_modules/@llblab/pi-actors/dist/lib/runs-results.d.ts +116 -0
  73. package/node_modules/@llblab/pi-actors/dist/lib/runs-results.js +518 -0
  74. package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.d.ts +15 -2
  75. package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.js +148 -9
  76. package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.d.ts +95 -0
  77. package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.js +470 -0
  78. package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.d.ts +58 -0
  79. package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.js +92 -0
  80. package/node_modules/@llblab/pi-actors/dist/lib/runs-start.d.ts +2 -0
  81. package/node_modules/@llblab/pi-actors/dist/lib/runs-start.js +44 -3
  82. package/node_modules/@llblab/pi-actors/dist/lib/runs-status.d.ts +15 -1
  83. package/node_modules/@llblab/pi-actors/dist/lib/runs-status.js +337 -13
  84. package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.d.ts +105 -0
  85. package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.js +501 -0
  86. package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.d.ts +1 -0
  87. package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.js +14 -3
  88. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +22 -3
  89. package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +37 -0
  90. package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +59 -3
  91. package/node_modules/@llblab/pi-actors/dist/lib/state-readers.d.ts +4 -1
  92. package/node_modules/@llblab/pi-actors/dist/lib/state-readers.js +14 -8
  93. package/node_modules/@llblab/pi-actors/dist/lib/temp.d.ts +2 -4
  94. package/node_modules/@llblab/pi-actors/dist/lib/temp.js +4 -56
  95. package/node_modules/@llblab/pi-actors/dist/lib/tools-access.d.ts +3 -0
  96. package/node_modules/@llblab/pi-actors/dist/lib/tools-access.js +13 -0
  97. package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.d.ts +1 -0
  98. package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +110 -43
  99. package/node_modules/@llblab/pi-actors/dist/lib/tools-local.d.ts +3 -0
  100. package/node_modules/@llblab/pi-actors/dist/lib/tools-local.js +18 -6
  101. package/node_modules/@llblab/pi-actors/dist/lib/tools-message.d.ts +4 -1
  102. package/node_modules/@llblab/pi-actors/dist/lib/tools-message.js +126 -31
  103. package/node_modules/@llblab/pi-actors/dist/lib/tools-response.d.ts +31 -3
  104. package/node_modules/@llblab/pi-actors/dist/lib/tools-response.js +311 -47
  105. package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.d.ts +3 -0
  106. package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.js +29 -7
  107. package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.d.ts +20 -0
  108. package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.js +192 -43
  109. package/node_modules/@llblab/pi-actors/dist/scripts/async-runner.mjs +272 -194
  110. package/node_modules/@llblab/pi-actors/dist/scripts/conformance.mjs +14 -2
  111. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +46 -2
  112. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/diagnostics.md +40 -0
  113. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +55 -1
  114. package/node_modules/@llblab/pi-actors/docs/README.md +9 -1
  115. package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +84 -15
  116. package/node_modules/@llblab/pi-actors/docs/agent-experience.md +216 -0
  117. package/node_modules/@llblab/pi-actors/docs/async-runs.md +18 -2
  118. package/node_modules/@llblab/pi-actors/docs/composition-transport.md +103 -0
  119. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +204 -4
  120. package/node_modules/@llblab/pi-actors/docs/inspection.md +38 -2
  121. package/node_modules/@llblab/pi-actors/docs/output-projection-map.md +130 -0
  122. package/node_modules/@llblab/pi-actors/docs/releasing.md +19 -2
  123. package/node_modules/@llblab/pi-actors/docs/run-supervision.md +1622 -0
  124. package/node_modules/@llblab/pi-actors/docs/template-recipes.md +4 -0
  125. package/node_modules/@llblab/pi-actors/docs/windows-process-observation.md +248 -0
  126. package/node_modules/@llblab/pi-actors/index.ts +1 -1
  127. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +164 -73
  128. package/node_modules/@llblab/pi-actors/lib/automatic-review-runtime.ts +4 -2
  129. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +287 -138
  130. package/node_modules/@llblab/pi-actors/lib/execution-files.ts +118 -0
  131. package/node_modules/@llblab/pi-actors/lib/execution-output.ts +4 -4
  132. package/node_modules/@llblab/pi-actors/lib/execution-sessions.ts +57 -27
  133. package/node_modules/@llblab/pi-actors/lib/execution.ts +8 -0
  134. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +102 -12
  135. package/node_modules/@llblab/pi-actors/lib/file-state.ts +16 -2
  136. package/node_modules/@llblab/pi-actors/lib/inspector-command.ts +2 -2
  137. package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +524 -261
  138. package/node_modules/@llblab/pi-actors/lib/inspector.ts +101 -130
  139. package/node_modules/@llblab/pi-actors/lib/limits.ts +1 -0
  140. package/node_modules/@llblab/pi-actors/lib/observability.ts +235 -116
  141. package/node_modules/@llblab/pi-actors/lib/pi.ts +59 -11
  142. package/node_modules/@llblab/pi-actors/lib/recipe-projection.ts +232 -0
  143. package/node_modules/@llblab/pi-actors/lib/recipes-context.ts +27 -6
  144. package/node_modules/@llblab/pi-actors/lib/registry.ts +266 -36
  145. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +1737 -49
  146. package/node_modules/@llblab/pi-actors/lib/run-supervision.ts +411 -0
  147. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +237 -73
  148. package/node_modules/@llblab/pi-actors/lib/runs-artifacts.ts +73 -1
  149. package/node_modules/@llblab/pi-actors/lib/runs-context-exports.ts +1871 -0
  150. package/node_modules/@llblab/pi-actors/lib/runs-context-index.ts +372 -0
  151. package/node_modules/@llblab/pi-actors/lib/runs-control-delivery.ts +36 -18
  152. package/node_modules/@llblab/pi-actors/lib/runs-control.ts +297 -1
  153. package/node_modules/@llblab/pi-actors/lib/runs-controls.ts +3 -1
  154. package/node_modules/@llblab/pi-actors/lib/runs-descendant-launches.ts +338 -0
  155. package/node_modules/@llblab/pi-actors/lib/runs-execution-evidence.ts +302 -0
  156. package/node_modules/@llblab/pi-actors/lib/runs-index.ts +345 -17
  157. package/node_modules/@llblab/pi-actors/lib/runs-native-launches.ts +270 -0
  158. package/node_modules/@llblab/pi-actors/lib/runs-outcomes.ts +377 -0
  159. package/node_modules/@llblab/pi-actors/lib/runs-ownership.ts +37 -4
  160. package/node_modules/@llblab/pi-actors/lib/runs-process-windows-mi.ts +113 -0
  161. package/node_modules/@llblab/pi-actors/lib/runs-process.ts +210 -34
  162. package/node_modules/@llblab/pi-actors/lib/runs-results.ts +601 -0
  163. package/node_modules/@llblab/pi-actors/lib/runs-retention.ts +134 -10
  164. package/node_modules/@llblab/pi-actors/lib/runs-session-exports.ts +406 -0
  165. package/node_modules/@llblab/pi-actors/lib/runs-snapshot.ts +143 -0
  166. package/node_modules/@llblab/pi-actors/lib/runs-start.ts +44 -2
  167. package/node_modules/@llblab/pi-actors/lib/runs-status.ts +252 -10
  168. package/node_modules/@llblab/pi-actors/lib/runs-supervision.ts +394 -0
  169. package/node_modules/@llblab/pi-actors/lib/runs-trace.ts +14 -3
  170. package/node_modules/@llblab/pi-actors/lib/runtime.ts +22 -3
  171. package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +76 -4
  172. package/node_modules/@llblab/pi-actors/lib/state-readers.ts +16 -7
  173. package/node_modules/@llblab/pi-actors/lib/temp.ts +3 -56
  174. package/node_modules/@llblab/pi-actors/lib/tools-access.ts +13 -0
  175. package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +110 -51
  176. package/node_modules/@llblab/pi-actors/lib/tools-local.ts +37 -24
  177. package/node_modules/@llblab/pi-actors/lib/tools-message.ts +120 -37
  178. package/node_modules/@llblab/pi-actors/lib/tools-response.ts +273 -50
  179. package/node_modules/@llblab/pi-actors/lib/tools-spawn.ts +31 -10
  180. package/node_modules/@llblab/pi-actors/lib/trace-projection.ts +165 -42
  181. package/node_modules/@llblab/pi-actors/package.json +10 -6
  182. package/node_modules/@llblab/pi-actors/scripts/async-runner.mjs +272 -194
  183. package/node_modules/@llblab/pi-actors/scripts/conformance.mjs +14 -2
  184. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +46 -2
  185. package/node_modules/@llblab/pi-actors/skills/actors/references/diagnostics.md +40 -0
  186. package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +55 -1
  187. package/node_modules/@llblab/pi-codex-usage/AGENTS.md +2 -2
  188. package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +6 -0
  189. package/node_modules/@llblab/pi-codex-usage/README.md +9 -5
  190. package/node_modules/@llblab/pi-codex-usage/lib/query.ts +229 -50
  191. package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +9 -1
  192. package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
  193. package/node_modules/yaml/LICENSE +13 -0
  194. package/node_modules/yaml/README.md +172 -0
  195. package/node_modules/yaml/bin.mjs +11 -0
  196. package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
  197. package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
  198. package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
  199. package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
  200. package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
  201. package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
  202. package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
  203. package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
  204. package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
  205. package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
  206. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
  207. package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
  208. package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
  209. package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
  210. package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
  211. package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
  212. package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
  213. package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
  214. package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
  215. package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
  216. package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
  217. package/node_modules/yaml/browser/dist/errors.js +57 -0
  218. package/node_modules/yaml/browser/dist/index.js +17 -0
  219. package/node_modules/yaml/browser/dist/log.js +11 -0
  220. package/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
  221. package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
  222. package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
  223. package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
  224. package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
  225. package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
  226. package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
  227. package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
  228. package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
  229. package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
  230. package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
  231. package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
  232. package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
  233. package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
  234. package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
  235. package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
  236. package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
  237. package/node_modules/yaml/browser/dist/public-api.js +102 -0
  238. package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
  239. package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
  240. package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
  241. package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
  242. package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
  243. package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
  244. package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
  245. package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
  246. package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
  247. package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
  248. package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
  249. package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
  250. package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
  251. package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
  252. package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
  253. package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
  254. package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
  255. package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
  256. package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
  257. package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
  258. package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
  259. package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
  260. package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
  261. package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
  262. package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
  263. package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
  264. package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
  265. package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
  266. package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
  267. package/node_modules/yaml/browser/dist/util.js +11 -0
  268. package/node_modules/yaml/browser/dist/visit.js +233 -0
  269. package/node_modules/yaml/browser/index.js +5 -0
  270. package/node_modules/yaml/browser/package.json +3 -0
  271. package/node_modules/yaml/dist/cli.d.ts +8 -0
  272. package/node_modules/yaml/dist/cli.mjs +201 -0
  273. package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
  274. package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
  275. package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
  276. package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
  277. package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
  278. package/node_modules/yaml/dist/compose/compose-node.js +112 -0
  279. package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
  280. package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
  281. package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
  282. package/node_modules/yaml/dist/compose/composer.js +224 -0
  283. package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
  284. package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
  285. package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
  286. package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
  287. package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
  288. package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
  289. package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
  290. package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
  291. package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
  292. package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
  293. package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
  294. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
  295. package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
  296. package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
  297. package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
  298. package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
  299. package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
  300. package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
  301. package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
  302. package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
  303. package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
  304. package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
  305. package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
  306. package/node_modules/yaml/dist/doc/Document.js +337 -0
  307. package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
  308. package/node_modules/yaml/dist/doc/anchors.js +76 -0
  309. package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
  310. package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
  311. package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
  312. package/node_modules/yaml/dist/doc/createNode.js +90 -0
  313. package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
  314. package/node_modules/yaml/dist/doc/directives.js +178 -0
  315. package/node_modules/yaml/dist/errors.d.ts +21 -0
  316. package/node_modules/yaml/dist/errors.js +62 -0
  317. package/node_modules/yaml/dist/index.d.ts +25 -0
  318. package/node_modules/yaml/dist/index.js +50 -0
  319. package/node_modules/yaml/dist/log.d.ts +3 -0
  320. package/node_modules/yaml/dist/log.js +19 -0
  321. package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
  322. package/node_modules/yaml/dist/nodes/Alias.js +118 -0
  323. package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
  324. package/node_modules/yaml/dist/nodes/Collection.js +151 -0
  325. package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
  326. package/node_modules/yaml/dist/nodes/Node.js +40 -0
  327. package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
  328. package/node_modules/yaml/dist/nodes/Pair.js +39 -0
  329. package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
  330. package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
  331. package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
  332. package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
  333. package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
  334. package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
  335. package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
  336. package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
  337. package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
  338. package/node_modules/yaml/dist/nodes/identity.js +53 -0
  339. package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
  340. package/node_modules/yaml/dist/nodes/toJS.js +39 -0
  341. package/node_modules/yaml/dist/options.d.ts +350 -0
  342. package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
  343. package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
  344. package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
  345. package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
  346. package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
  347. package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
  348. package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
  349. package/node_modules/yaml/dist/parse/cst.js +112 -0
  350. package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
  351. package/node_modules/yaml/dist/parse/lexer.js +723 -0
  352. package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
  353. package/node_modules/yaml/dist/parse/line-counter.js +41 -0
  354. package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
  355. package/node_modules/yaml/dist/parse/parser.js +980 -0
  356. package/node_modules/yaml/dist/public-api.d.ts +44 -0
  357. package/node_modules/yaml/dist/public-api.js +107 -0
  358. package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
  359. package/node_modules/yaml/dist/schema/Schema.js +39 -0
  360. package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
  361. package/node_modules/yaml/dist/schema/common/map.js +19 -0
  362. package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
  363. package/node_modules/yaml/dist/schema/common/null.js +17 -0
  364. package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
  365. package/node_modules/yaml/dist/schema/common/seq.js +19 -0
  366. package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
  367. package/node_modules/yaml/dist/schema/common/string.js +16 -0
  368. package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
  369. package/node_modules/yaml/dist/schema/core/bool.js +21 -0
  370. package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
  371. package/node_modules/yaml/dist/schema/core/float.js +47 -0
  372. package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
  373. package/node_modules/yaml/dist/schema/core/int.js +42 -0
  374. package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
  375. package/node_modules/yaml/dist/schema/core/schema.js +25 -0
  376. package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
  377. package/node_modules/yaml/dist/schema/json/schema.js +64 -0
  378. package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
  379. package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
  380. package/node_modules/yaml/dist/schema/tags.js +99 -0
  381. package/node_modules/yaml/dist/schema/types.d.ts +92 -0
  382. package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
  383. package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
  384. package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
  385. package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
  386. package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
  387. package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
  388. package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
  389. package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
  390. package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
  391. package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
  392. package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
  393. package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
  394. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
  395. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
  396. package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
  397. package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
  398. package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
  399. package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
  400. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
  401. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
  402. package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
  403. package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
  404. package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
  405. package/node_modules/yaml/dist/stringify/stringify.js +132 -0
  406. package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
  407. package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
  408. package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
  409. package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
  410. package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
  411. package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
  412. package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
  413. package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
  414. package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
  415. package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
  416. package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
  417. package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
  418. package/node_modules/yaml/dist/test-events.d.ts +4 -0
  419. package/node_modules/yaml/dist/test-events.js +134 -0
  420. package/node_modules/yaml/dist/util.d.ts +16 -0
  421. package/node_modules/yaml/dist/util.js +28 -0
  422. package/node_modules/yaml/dist/visit.d.ts +102 -0
  423. package/node_modules/yaml/dist/visit.js +236 -0
  424. package/node_modules/yaml/package.json +97 -0
  425. package/node_modules/yaml/util.js +2 -0
  426. package/package.json +3 -3
  427. package/node_modules/@llblab/pi-actors/banner.jpg +0 -0
  428. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.d.ts +0 -16
  429. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.js +0 -57
  430. package/node_modules/@llblab/pi-actors/lib/inspector-actions.ts +0 -79
@@ -7,9 +7,11 @@
7
7
  import { readFileSync, statSync } from "node:fs";
8
8
  import * as path from "node:path";
9
9
 
10
- import type { Theme } from "@earendil-works/pi-coding-agent";
10
+ import { getMarkdownTheme, type Theme } from "@earendil-works/pi-coding-agent";
11
+ import { Document, isScalar, visit } from "yaml";
11
12
  import {
12
13
  matchesKey,
14
+ Markdown,
13
15
  sliceByColumn,
14
16
  truncateToWidth,
15
17
  visibleWidth,
@@ -22,8 +24,9 @@ import * as RunsControls from "./runs-controls.ts";
22
24
  import * as RunControlDelivery from "./runs-control-delivery.ts";
23
25
  import * as RunsTrace from "./runs-trace.ts";
24
26
  import * as Limits from "./limits.ts";
25
- import { computeRunControlCapacity } from "./run-evidence-policy.ts";
27
+ import { computeRunControlCapacity, runControlStatusTimestamp } from "./run-evidence-policy.ts";
26
28
  import * as RuntimeTriage from "./runtime-triage.ts";
29
+ import { normalizeRecipeControl } from "./recipe-control.ts";
27
30
  import * as TraceProjection from "./trace-projection.ts";
28
31
 
29
32
  export type ActorInspectorTab = "recipe" | "trace" | "control";
@@ -52,6 +55,21 @@ export interface ActorInspectorOverlayOptions {
52
55
  tui: TUI;
53
56
  }
54
57
 
58
+ // Per-render provenance, not parsed from authored text or retained as source metadata.
59
+ interface InspectorDocumentLine {
60
+ text: string;
61
+ label?: string;
62
+ }
63
+
64
+ type AgentDetailSegment = { kind: "metadata"; value: unknown } | { kind: "body"; text: string };
65
+
66
+ interface ControlObservation {
67
+ run: string;
68
+ generation?: string;
69
+ document: Record<string, unknown>;
70
+ rows: Array<{ id: string; value: ControlProjection.ProjectedRunControl; age: number; reason?: string }>;
71
+ }
72
+
55
73
  interface KillConfirmation {
56
74
  run: string;
57
75
  runInstanceId: string;
@@ -84,12 +102,20 @@ export class ActorInspectorOverlay {
84
102
  private documentScroll = 0;
85
103
  private detailScroll = 0;
86
104
  private feedback?: ActorInspectorActionResult;
105
+ private feedbackFromAction = false;
106
+ private feedbackScroll = 0;
107
+ private feedbackPageRows = 1;
108
+ private inspectorFrame?: { width: number; lines: string[] };
87
109
  private focus: InspectorFocus = "runs";
88
110
  private killConfirmation?: KillConfirmation;
89
111
  private killDialogChoice: "cancel" | "kill" = "cancel";
112
+ private killDialogScroll = 0;
113
+ private killDialogPageRows = 1;
90
114
  private rowIndex = 0;
91
115
  private runCache?: ActorInspector.ActorInspectorRunItem[];
92
116
  private selectedTraceId?: string;
117
+ private controlCache?: ControlObservation;
118
+ private controlSelection?: { run: string; generation?: string; id: string };
93
119
  private runIndex = 0;
94
120
  private selectorIndex = 0;
95
121
  private selectorMode?: SelectorMode;
@@ -98,6 +124,7 @@ export class ActorInspectorOverlay {
98
124
  run: string;
99
125
  runInstanceId?: string;
100
126
  items: TraceProjection.TraceItem[];
127
+ journalSummary?: ReturnType<typeof RunsTrace.summarizeRunTraceJournal>;
101
128
  };
102
129
  private traceDetail?: TraceProjection.TraceItem;
103
130
  private traceSourceIndex = 0;
@@ -125,11 +152,10 @@ export class ActorInspectorOverlay {
125
152
  invalidate(): void {
126
153
  this.runCache = undefined;
127
154
  this.traceCache = undefined;
155
+ this.controlCache = undefined;
128
156
  }
129
157
 
130
158
  handleInput(data: string): void {
131
- const runs = this.runs();
132
- this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
133
159
  if (matchesKey(data, "ctrl+c")) {
134
160
  this.done();
135
161
  return;
@@ -139,11 +165,30 @@ export class ActorInspectorOverlay {
139
165
  this.tui.requestRender();
140
166
  return;
141
167
  }
168
+ if (this.feedback) {
169
+ if (matchesKey(data, "escape")) {
170
+ this.feedback = undefined;
171
+ this.feedbackFromAction = false;
172
+ this.feedbackScroll = 0;
173
+ } else if (matchesKey(data, "up")) this.feedbackScroll = Math.max(0, this.feedbackScroll - 1);
174
+ else if (matchesKey(data, "down")) this.feedbackScroll++;
175
+ else if (matchesKey(data, "pageUp")) this.feedbackScroll = Math.max(0, this.feedbackScroll - this.feedbackPageRows);
176
+ else if (matchesKey(data, "pageDown")) this.feedbackScroll += this.feedbackPageRows;
177
+ this.tui.requestRender();
178
+ return;
179
+ }
180
+ const runs = this.runs();
181
+ this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
142
182
  if (data.toLowerCase() === "k") {
143
183
  this.requestKill(runs[this.runIndex]);
144
184
  this.tui.requestRender();
145
185
  return;
146
186
  }
187
+ if (this.tab === "control" && (this.focus === "list" || this.focus === "detail")) {
188
+ this.handleControlInput(data, runs[this.runIndex]);
189
+ this.tui.requestRender();
190
+ return;
191
+ }
147
192
  if (matchesKey(data, "escape")) {
148
193
  if (this.focus === "select") {
149
194
  this.selectorMode = undefined;
@@ -173,7 +218,8 @@ export class ActorInspectorOverlay {
173
218
  return;
174
219
  }
175
220
  if (this.focus === "document") {
176
- if (matchesKey(data, "left")) this.focus = "tabs";
221
+ if (this.tab === "control" && matchesKey(data, "return")) this.openControlList(runs[this.runIndex]);
222
+ else if (matchesKey(data, "left")) this.focus = "tabs";
177
223
  else if (matchesKey(data, "up") && this.documentScroll === 0) this.focus = "tabs";
178
224
  else this.handleScrollableInput(data, "document");
179
225
  this.tui.requestRender();
@@ -204,6 +250,7 @@ export class ActorInspectorOverlay {
204
250
  }
205
251
  } else if (matchesKey(data, "return")) {
206
252
  if (this.tab === "trace") this.openSourceSelector(runs[this.runIndex]);
253
+ else if (this.tab === "control") this.openControlList(runs[this.runIndex]);
207
254
  else this.focus = "document";
208
255
  }
209
256
  } else if (this.focus === "list") {
@@ -244,32 +291,27 @@ export class ActorInspectorOverlay {
244
291
 
245
292
  render(width: number): string[] {
246
293
  const safeWidth = Math.max(24, width);
294
+ const modal = !!(this.killConfirmation || this.feedback);
295
+ if (!modal || !this.inspectorFrame) this.inspectorFrame = { width: safeWidth, lines: this.renderInspector(safeWidth) };
296
+ const lines = this.modalUnderlay(safeWidth);
297
+ if (this.killConfirmation) this.compositePopup(lines, this.renderKillDialog(safeWidth - 2, lines.length), safeWidth);
298
+ else if (this.feedback) this.compositePopup(lines, this.renderFeedbackPopup(safeWidth, lines.length), safeWidth);
299
+ return lines;
300
+ }
301
+
302
+ private renderInspector(safeWidth: number): string[] {
247
303
  const innerWidth = safeWidth - 2;
248
304
  const runs = this.runs();
249
305
  this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
250
306
  const run = runs[this.runIndex];
251
- if (this.killConfirmation) return this.renderKillDialog(innerWidth);
252
-
253
307
  const lines = [this.border("╭", " Actor Inspector ", "╮", innerWidth)];
254
- lines.push(this.row(this.renderRunControl(run, runs.length - this.runIndex - 1), innerWidth));
308
+ lines.push(this.row(this.renderRunControl(run, runs.length - this.runIndex - 1, innerWidth), innerWidth));
255
309
  lines.push(this.row(this.renderTabs(run), innerWidth));
256
310
  lines.push(this.border("├", "", "┤", innerWidth));
257
311
 
258
312
  this.contentSelectedRows.clear();
259
313
  this.contentStripeIndices = [];
260
314
  let content = this.renderContent(run, innerWidth);
261
- if (this.feedback) {
262
- const color = this.feedback.ok
263
- ? "success"
264
- : /^Kill (failed|rejected)/u.test(this.feedback.message)
265
- ? "error"
266
- : "warning";
267
- content = [this.theme.fg(color, ` ${this.feedback.message}`), ...content];
268
- this.contentSelectedRows = new Set(
269
- [...this.contentSelectedRows].map((index) => index + 1),
270
- );
271
- this.contentStripeIndices = [0, ...this.contentStripeIndices];
272
- }
273
315
  const viewportRows = this.contentViewportRows();
274
316
  content = content.slice(0, viewportRows);
275
317
  for (let index = 0; index < viewportRows; index += 1) {
@@ -284,9 +326,9 @@ export class ActorInspectorOverlay {
284
326
  if (this.focus === "select") {
285
327
  const runSelector = this.selectorMode === "run";
286
328
  const startRow = runSelector ? 2 : 3;
287
- const horizontalOffset = runSelector ? 4 : 12;
329
+ const horizontalOffset = runSelector ? 5 : 12;
288
330
  const availableRows = Math.max(3, lines.length - startRow - 1);
289
- const menu = this.renderSelector(run, availableRows);
331
+ const menu = this.renderSelector(run, availableRows, innerWidth);
290
332
  for (const [offset, menuLine] of menu.entries()) {
291
333
  const target = startRow + offset;
292
334
  if (target >= lines.length - 1) break;
@@ -333,12 +375,13 @@ export class ActorInspectorOverlay {
333
375
 
334
376
  private handleScrollableInput(data: string, target: "detail" | "document"): void {
335
377
  const key = target === "detail" ? "detailScroll" : "documentScroll";
378
+ const pageRows = () => this.contentViewportRows();
336
379
  if (matchesKey(data, "up")) this[key] = Math.max(0, this[key] - 1);
337
380
  else if (matchesKey(data, "down")) this[key] += 1;
338
381
  else if (matchesKey(data, "pageUp")) {
339
- this[key] = Math.max(0, this[key] - this.contentViewportRows());
382
+ this[key] = Math.max(0, this[key] - pageRows());
340
383
  } else if (matchesKey(data, "pageDown")) {
341
- this[key] += this.contentViewportRows();
384
+ this[key] += pageRows();
342
385
  } else if (matchesKey(data, "left") && target === "detail") {
343
386
  this.selectedTraceId = this.traceDetail?.id;
344
387
  this.traceDetail = undefined;
@@ -389,6 +432,8 @@ export class ActorInspectorOverlay {
389
432
 
390
433
  private requestKill(run?: ActorInspector.ActorInspectorRunItem): void {
391
434
  this.feedback = undefined;
435
+ this.feedbackFromAction = false;
436
+ this.feedbackScroll = 0;
392
437
  if (this.focus !== "runs") {
393
438
  this.feedback = { ok: false, message: "Focus Run before using Kill." };
394
439
  return;
@@ -415,12 +460,17 @@ export class ActorInspectorOverlay {
415
460
  status: run.status,
416
461
  };
417
462
  this.killDialogChoice = "cancel";
463
+ this.killDialogScroll = 0;
418
464
  }
419
465
 
420
466
  private handleKillInput(data: string): void {
421
467
  const key = data.toLowerCase();
422
- if (matchesKey(data, "escape") || key === "n") {
423
- this.feedback = { ok: false, message: "Kill cancelled." };
468
+ if (matchesKey(data, "pageUp")) {
469
+ this.killDialogScroll = Math.max(0, this.killDialogScroll - this.killDialogPageRows);
470
+ } else if (matchesKey(data, "pageDown")) {
471
+ this.killDialogScroll += this.killDialogPageRows;
472
+ } else if (matchesKey(data, "escape") || key === "n") {
473
+ this.feedback = undefined;
424
474
  this.killConfirmation = undefined;
425
475
  this.killDialogChoice = "cancel";
426
476
  } else if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "tab")) {
@@ -430,7 +480,7 @@ export class ActorInspectorOverlay {
430
480
  } else if (matchesKey(data, "return")) {
431
481
  if (this.killDialogChoice === "kill") this.confirmKill();
432
482
  else {
433
- this.feedback = { ok: false, message: "Kill cancelled." };
483
+ this.feedback = undefined;
434
484
  this.killConfirmation = undefined;
435
485
  }
436
486
  }
@@ -441,52 +491,63 @@ export class ActorInspectorOverlay {
441
491
  this.killConfirmation = undefined;
442
492
  this.killDialogChoice = "cancel";
443
493
  if (!confirmation || !this.killRun) return;
494
+ this.feedbackFromAction = true;
444
495
  try {
445
496
  const result = this.killRun(confirmation.run, confirmation.runInstanceId);
446
497
  this.feedback = result.ok ? undefined : result;
447
498
  if (result.ok) this.invalidate();
448
499
  } catch (error) {
449
500
  const message = error instanceof Error ? error.message : String(error);
501
+ const normalized = message.replaceAll(/\s+/g, " ");
502
+ let selected = normalized.slice(0, 160);
503
+ const clipped = typeof normalized === "string" && normalized.length > 160;
504
+ if (clipped && typeof selected === "string" && /[\uD800-\uDBFF]/u.test(selected.at(-1) ?? "") && /[\uDC00-\uDFFF]/u.test(normalized[160]!)) selected = selected.slice(0, -1);
450
505
  this.feedback = {
451
506
  ok: false,
452
- message: `Kill failed: ${message.replaceAll(/\s+/g, " ").slice(0, 160)}.`,
507
+ message: ActorInspector.formatActorInspectorKillFeedback("failed", `${selected}`, { clipped }),
453
508
  };
454
509
  }
455
510
  }
456
511
 
457
- private renderKillDialog(innerWidth: number): string[] {
512
+ private renderKillDialog(width: number, maxRows: number): string[] {
513
+ const innerWidth = Math.min(width, 70), textWidth = innerWidth - 2;
458
514
  const confirmation = this.killConfirmation!;
459
515
  const cancel = this.killDialogChoice === "cancel"
460
- ? this.theme.bg("selectedBg", this.theme.fg("accent", " Cancel "))
461
- : this.theme.fg("muted", " Cancel ");
516
+ ? this.theme.bg("selectedBg", this.theme.fg("accent", " Cancel "))
517
+ : this.theme.fg("muted", " Cancel ");
462
518
  const kill = this.killDialogChoice === "kill"
463
- ? this.theme.bg("selectedBg", this.theme.fg("error", " Kill actor "))
464
- : this.theme.fg("error", " Kill actor ");
465
- const body = [
466
- "",
467
- this.theme.fg("error", "Kill this running actor?"),
468
- "",
469
- `${this.theme.fg("muted", "Run:")} ${this.theme.fg("accent", `run:${confirmation.run}`)}`,
470
- `${this.theme.fg("muted", "Current status:")} ${this.theme.fg("warning", confirmation.status)}`,
471
- "",
472
- this.theme.fg("text", "This invokes generation-fenced runtime kill."),
473
- "",
474
- `${cancel} ${kill}`,
475
- "",
519
+ ? this.theme.bg("selectedBg", this.theme.fg("error", " Kill actor "))
520
+ : this.theme.fg("error", " Kill actor ");
521
+ const details = this.documentProjection({
522
+ run: confirmation.run,
523
+ state: confirmation.status,
524
+ action: "generation-fenced runtime kill",
525
+ }, textWidth).lines;
526
+ const safety = this.closureWarning(textWidth).map(line => this.theme.fg("warning", line));
527
+ const choices = visibleWidth(cancel) + 2 + visibleWidth(kill) <= textWidth
528
+ ? [this.center(`${cancel} ${kill}`, textWidth)]
529
+ : [this.center(cancel, textWidth), this.center(kill, textWidth)];
530
+ const layout = this.popupLayout(maxRows, safety.length + 1 + choices.length, details.length);
531
+ this.killDialogPageRows = layout.pageRows;
532
+ this.killDialogScroll = Math.min(this.killDialogScroll, Math.max(0, details.length - this.killDialogPageRows));
533
+ const visible = details.slice(this.killDialogScroll, this.killDialogScroll + this.killDialogPageRows);
534
+ const hints = innerWidth < 30 ? this.keyHints(["↵", "pick"], ["esc", "back"])
535
+ : innerWidth < 60 ? this.keyHints(["←→/tab", "choose"], ["↵", "pick"], ["esc", "back"]) : this.renderKeyHints(true);
536
+ return [
537
+ this.border("╭", " Confirm Actor Kill ", "╮", innerWidth),
538
+ ...(layout.top ? [this.row("", innerWidth)] : []),
539
+ ...visible.map(line => this.row(" " + line, innerWidth)),
540
+ ...(layout.paged ? [this.row(" " + this.theme.fg("muted", `${this.killDialogScroll + 1}-${this.killDialogScroll + visible.length}/${details.length} pgup/pgdn`), innerWidth)] : []),
541
+ ...[...safety, "", ...choices].map(line => this.row(" " + line, innerWidth)),
542
+ ...(layout.bottom ? [this.row("", innerWidth)] : []),
543
+ this.footerBorder(hints, innerWidth),
476
544
  ];
477
- const lines = [this.border("╭", " Confirm Actor Kill ", "╮", innerWidth)];
478
- const availableRows = this.contentViewportRows() + 3;
479
- const topPadding = Math.max(0, Math.floor((availableRows - body.length) / 2));
480
- const rows = [...Array.from({ length: topPadding }, () => ""), ...body].slice(0, availableRows);
481
- while (rows.length < availableRows) rows.push("");
482
- for (const line of rows) lines.push(this.row(this.center(line, innerWidth), innerWidth));
483
- lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
484
- return lines;
485
545
  }
486
546
 
487
547
  private renderRunControl(
488
548
  run: ActorInspector.ActorInspectorRunItem | undefined,
489
549
  sequence: number,
550
+ width: number,
490
551
  ): string {
491
552
  const active = this.focus === "runs" || (this.focus === "select" && this.selectorMode === "run");
492
553
  const prefix = active ? this.theme.fg("accent", " ← ") : " ";
@@ -496,11 +557,12 @@ export class ActorInspectorOverlay {
496
557
  return active ? this.theme.bg("selectedBg", empty) : empty;
497
558
  }
498
559
  const value = `${prefix}${this.theme.fg("muted", "Run: ")}${this.theme.fg("text", `#${sequence}`)} ${this.theme.fg("accent", run.run)} ${this.theme.fg(this.statusColor(run.status), run.status)}${suffix}`;
499
- return active ? this.theme.bg("selectedBg", value) : value;
560
+ const bounded = truncateToWidth(value, width, "…");
561
+ return active ? this.theme.bg("selectedBg", bounded) : bounded;
500
562
  }
501
563
 
502
564
  private renderTabs(run?: ActorInspector.ActorInspectorRunItem): string {
503
- const source = this.traceSource(run);
565
+ const source = this.tab === "trace" ? this.traceSource(run) : "all";
504
566
  return TABS.map((tab, index) => {
505
567
  const base = `${tab[0]!.toUpperCase()}${tab.slice(1)}`;
506
568
  const label = tab === "trace" && source !== "all" ? `${base}: ${source}` : base;
@@ -527,13 +589,20 @@ export class ActorInspectorOverlay {
527
589
  width: number,
528
590
  ): string[] {
529
591
  if (!run) return [this.theme.fg("muted", " No owned actor runs")];
592
+ if (this.tab === "control" && (this.focus === "list" || this.focus === "detail")) return this.renderControlContent(run, width);
530
593
  if (this.focus === "detail") return this.renderTraceDetail(width);
531
- if (this.tab === "trace") return this.renderTraceList(run);
532
- const stateDir = path.join(this.stateRoot, run.run);
594
+ if (this.tab === "trace") return this.renderTraceList(run, width);
595
+ const selectedRun = run.run;
596
+ const stateDir = path.join(this.stateRoot, selectedRun);
533
597
  const value = this.tab === "recipe"
534
598
  ? ActorInspector.readActorInspectorRecipe(stateDir)
535
599
  : this.controlDocument(run, stateDir);
536
- const projection = this.documentProjection(value, width);
600
+ const body = this.documentProjection(value, width);
601
+ const identity = this.documentProjection({ selected_run_id: selectedRun }, width);
602
+ const projection = {
603
+ lines: [...identity.lines, ...body.lines],
604
+ stripes: [...identity.stripes, ...body.stripes.map(index => index + 1)],
605
+ };
537
606
  const maxStart = Math.max(0, projection.lines.length - this.contentViewportRows());
538
607
  this.documentScroll = Math.min(this.documentScroll, maxStart);
539
608
  this.contentStripeIndices = projection.stripes.slice(
@@ -546,12 +615,83 @@ export class ActorInspectorOverlay {
546
615
  );
547
616
  }
548
617
 
618
+ private modalUnderlay(width: number): string[] {
619
+ const frame = this.inspectorFrame!;
620
+ const height = this.contentViewportRows() + 5;
621
+ if (frame.width === width && frame.lines.length === height) return frame.lines.slice();
622
+ // Resize the frozen underlay without refreshing evidence behind an open modal.
623
+ const innerWidth = width - 2;
624
+ const content = (index: number) => sliceByColumn(frame.lines[index] ?? "", 1, frame.width - 2, true);
625
+ return [
626
+ this.border("╭", " Actor Inspector ", "╮", innerWidth),
627
+ this.row(content(1), innerWidth),
628
+ this.row(content(2), innerWidth),
629
+ this.border("├", "", "┤", innerWidth),
630
+ ...Array.from({ length: height - 5 }, (_, index) =>
631
+ this.row(index < frame.lines.length - 5 ? content(index + 4) : "", innerWidth)),
632
+ this.footerBorder(content(frame.lines.length - 1), innerWidth),
633
+ ];
634
+ }
635
+
636
+ private compositePopup(lines: string[], popup: string[], width: number): void {
637
+ const popupWidth = visibleWidth(popup[0]!);
638
+ const left = Math.floor((width - popupWidth) / 2);
639
+ const top = Math.floor((lines.length - popup.length) / 2);
640
+ for (const [offset, line] of popup.entries()) {
641
+ const base = lines[top + offset]!;
642
+ const right = left + popupWidth;
643
+ // Terminal-default background masks underlying stripes; selected buttons keep their own fill.
644
+ lines[top + offset] = `${sliceByColumn(base, 0, left, true)}\u001b[49m${line}\u001b[49m${sliceByColumn(base, right, width - right, true)}`;
645
+ }
646
+ }
647
+
648
+ private popupLayout(maxRows: number, fixedRows: number, detailRows: number): { top: boolean; bottom: boolean; pageRows: number; paged: boolean } {
649
+ // Reserve readable text, pinned safety and choices before decorative blank rows.
650
+ const minimum = 2 + fixedRows + Math.min(2, detailRows);
651
+ const paddingRows = Math.min(2, Math.max(0, maxRows - minimum));
652
+ const height = Math.min(maxRows, Math.max(maxRows - 2, minimum + paddingRows));
653
+ const slots = height - 2 - fixedRows - paddingRows;
654
+ const paged = detailRows > slots;
655
+ return { top: paddingRows > 0, bottom: paddingRows > 1, pageRows: Math.min(detailRows, slots - Number(paged)), paged };
656
+ }
657
+
658
+ private renderFeedbackPopup(width: number, maxRows: number): string[] {
659
+ const feedback = this.feedback!;
660
+ const innerWidth = Math.min(70, width - 2), textWidth = innerWidth - 2;
661
+ const color = feedback.ok
662
+ ? "success"
663
+ : /^(?:Kill (?:failed|rejected)|- action: kill\n- result: (?:failed|rejected)\n)/u.test(feedback.message) ? "error" : "warning";
664
+ const message = `${feedback.message}`;
665
+ const details = this.wrapDocument(message.split(/\r\n|\r|\n/u).map(text => ({ text })), textWidth).map(line => line.text);
666
+ const safety = this.feedbackFromAction ? [...this.closureWarning(textWidth), "No automatic retry."] : [];
667
+ const layout = this.popupLayout(maxRows, safety.length, details.length);
668
+ this.feedbackPageRows = layout.pageRows;
669
+ this.feedbackScroll = Math.min(this.feedbackScroll, Math.max(0, details.length - this.feedbackPageRows));
670
+ const visible = details.slice(this.feedbackScroll, this.feedbackScroll + this.feedbackPageRows);
671
+ return [
672
+ this.border("╭", " Notice ", "╮", innerWidth),
673
+ ...(layout.top ? [this.row("", innerWidth)] : []),
674
+ ...visible.map(line => this.row(" " + this.theme.fg(color, line), innerWidth)),
675
+ ...(layout.paged ? [this.row(" " + this.theme.fg("muted", `${this.feedbackScroll + 1}-${this.feedbackScroll + visible.length}/${details.length} ↑↓ pgup/pgdn`), innerWidth)] : []),
676
+ ...safety.map(line => this.row(" " + this.theme.fg("warning", line), innerWidth)),
677
+ ...(layout.bottom ? [this.row("", innerWidth)] : []),
678
+ this.footerBorder(this.keyHints(["esc", "close"]), innerWidth),
679
+ ];
680
+ }
681
+
682
+ private closureWarning(width: number): string[] {
683
+ return width >= visibleWidth("- closure: unconfirmed")
684
+ ? ["- closure: unconfirmed"] : ["- closure:", " unconfirmed"];
685
+ }
686
+
549
687
  private renderSelector(
550
688
  run: ActorInspector.ActorInspectorRunItem,
551
689
  availableRows: number,
690
+ width: number,
552
691
  ): string[] {
692
+ // Run menu reserves five offset columns, two markers, two borders and one right-padding column.
553
693
  const options = this.selectorMode === "run"
554
- ? this.runSelectorOptions()
694
+ ? this.runSelectorOptions(Math.max(0, width - 10))
555
695
  : this.traceSources(run).map((source) => ({
556
696
  content: `${this.theme.fg("accent", "Trace:")} ${this.theme.fg("text", source)}`,
557
697
  preserveColors: true,
@@ -559,7 +699,7 @@ export class ActorInspectorOverlay {
559
699
  return this.renderMenuBox(options, this.selectorIndex, availableRows);
560
700
  }
561
701
 
562
- private runSelectorOptions(): SelectorOption[] {
702
+ private runSelectorOptions(width: number): SelectorOption[] {
563
703
  const runs = this.runs();
564
704
  const sequenceWidth = Math.max(
565
705
  2,
@@ -567,11 +707,11 @@ export class ActorInspectorOverlay {
567
707
  );
568
708
  const runWidth = Math.max(1, ...runs.map((item) => visibleWidth(item.run)));
569
709
  return runs.map((item, index) => ({
570
- content: [
710
+ content: truncateToWidth([
571
711
  this.theme.fg("text", this.fit(`#${runs.length - index - 1}`, sequenceWidth)),
572
712
  this.theme.fg("accent", this.fit(item.run, runWidth)),
573
713
  this.theme.fg(this.statusColor(item.status), item.status),
574
- ].join(" "),
714
+ ].join(" "), width, "…"),
575
715
  preserveColors: true,
576
716
  }));
577
717
  }
@@ -589,33 +729,46 @@ export class ActorInspectorOverlay {
589
729
  const maxStart = Math.max(0, options.length - visibleLimit);
590
730
  const start = Math.max(0, Math.min(focusedIndex - Math.floor(visibleLimit / 2), maxStart));
591
731
  const visible = options.slice(start, start + visibleLimit);
592
- const width = Math.max(8, ...options.map((option) => visibleWidth(option.content) + 4));
593
- const border = (left: string, right: string, marker = "") =>
594
- this.theme.fg("borderAccent", `${left}${marker}${"─".repeat(Math.max(0, width - visibleWidth(marker)))}${right}`);
732
+ const runMenu = this.selectorMode === "run";
733
+ const width = Math.max(8, ...options.map((option) => visibleWidth(option.content) + (runMenu ? 3 : 4)));
734
+ // Reset both menu edges so sliced document backgrounds cannot bleed through.
735
+ const ownBackground = (line: string) => runMenu ? `\u001b[49m${line}\u001b[49m` : line;
736
+ const border = (left: string, right: string, marker = "") => ownBackground(
737
+ this.theme.fg("borderAccent", `${left}${marker}${"─".repeat(Math.max(0, width - visibleWidth(marker)))}${right}`));
595
738
  return [
596
739
  border("╭", "╮", start > 0 ? "↑" : ""),
597
740
  ...visible.map((option, offset) => {
598
741
  const index = start + offset;
599
742
  const marker = index === focusedIndex
600
- ? this.theme.fg("accent", " ▶ ")
601
- : " ";
602
- const row = this.fit(`${marker}${option.content}`, width);
743
+ ? this.theme.fg("accent", runMenu ? "▶ " : " ▶ ")
744
+ : runMenu ? " " : " ";
745
+ const row = this.fit(`${marker}${option.content}`, width - Number(runMenu)) + (runMenu ? " " : "");
603
746
  const colored = index === focusedIndex && !option.preserveColors
604
747
  ? this.theme.fg("accent", row)
605
748
  : row;
606
- const styled = index === focusedIndex ? this.theme.bg("selectedBg", colored) : colored;
607
- return `${this.theme.fg("borderAccent", "│")}${styled}${this.theme.fg("borderAccent", "│")}`;
749
+ const background = index === focusedIndex ? "selectedBg" : runMenu && index % 2 ? "customMessageBg" : undefined;
750
+ // Native truncation emits full SGR resets; repaint each resulting segment.
751
+ const styled = background === undefined ? colored : runMenu
752
+ ? colored.split("\u001b[0m").map(part => this.theme.bg(background, part)).join("\u001b[0m")
753
+ : this.theme.bg(background, colored);
754
+ return ownBackground(`${this.theme.fg("borderAccent", "│")}${styled}${this.theme.fg("borderAccent", "│")}`);
608
755
  }),
609
756
  border("╰", "╯", start + visible.length < options.length ? "↓" : ""),
610
757
  ];
611
758
  }
612
759
 
613
- private renderTraceList(run: ActorInspector.ActorInspectorRunItem): string[] {
760
+ private traceTableLine(text: string, width: number): string {
761
+ const [first = "", ...rest] = text.split(/\r\n|\r|\n/u);
762
+ return truncateToWidth(first + (rest.length ? "…" : ""), width, "…");
763
+ }
764
+
765
+ private renderTraceList(run: ActorInspector.ActorInspectorRunItem, width: number): string[] {
614
766
  const items = this.traceItems(run);
615
- const summary = RunsTrace.summarizeRunTraceJournal(
767
+ const summary = this.traceCache!.journalSummary ??= RunsTrace.summarizeRunTraceJournal(
616
768
  RunsTrace.readRunTraceJournal(path.join(this.stateRoot, run.run)));
617
769
  const banner = summary.history_complete ? [] : [this.theme.fg("warning",
618
- ` ! Trace history incomplete: ${summary.compacted ? `${summary.compactions_total} compaction(s), ` : ""}${summary.dropped_event_count_exact ? summary.dropped_events : "unknown"} dropped event(s), ${summary.dropped_bytes} bytes`)];
770
+ width < visibleWidth(" ! Trace history incomplete:") ? " ! Trace incomplete…" : this.traceTableLine(
771
+ ` ! Trace history incomplete: ${summary.compacted ? `${summary.compactions_total} compaction(s), ` : ""}${summary.dropped_event_count_exact ? summary.dropped_events : "unknown"} dropped event(s), ${summary.dropped_bytes} bytes`, width))];
619
772
  if (items.length === 0) return [...banner, this.theme.fg("muted", " No Trace evidence")];
620
773
  if (this.selectedTraceId) {
621
774
  const selectedIndex = items.findIndex((item) => item.id === this.selectedTraceId);
@@ -645,25 +798,32 @@ export class ActorInspectorOverlay {
645
798
  const detail = item.detail && typeof item.detail === "object"
646
799
  ? item.detail as Record<string, unknown>
647
800
  : {};
648
- const marker = item.level === "error"
649
- ? this.theme.fg("error", "!")
650
- : detail.attention
651
- ? this.theme.fg("warning", "A")
652
- : " ";
801
+ const attention = item.level !== "error" && detail.attention
802
+ ? this.theme.fg("warning", "A") + " "
803
+ : "";
653
804
  const prefix = this.focus === "list" && index === this.rowIndex
654
- ? this.theme.fg("accent", " ▶ ")
655
- : " ";
656
- const sequence = item[TraceProjection.TRACE_ITEM_SEQUENCE] ?? items.length - index - 1;
657
- const row = `${prefix}${this.theme.fg("text", `#${sequence}`)} ${marker} ${this.theme.fg("muted", item.source)}/${this.theme.fg(item.level === "error" ? "error" : "accent", item.kind)} ${this.theme.fg("text", item.summary)}`;
805
+ ? this.theme.fg("accent", "▶ ")
806
+ : " ";
807
+ const chronology = item[TraceProjection.TRACE_ITEM_CHRONOLOGY];
808
+ const sequence = chronology?.state === "available" ? chronology.ordinal : "?";
809
+ const row = `${prefix}${this.theme.fg("text", `#${sequence}`)} ${attention}${this.theme.fg("muted", item.source)}/${this.theme.fg(item.level === "error" ? "error" : "accent", item.kind)} ${this.theme.fg("text", item.summary)}`;
810
+ const preview = this.traceTableLine(row, width);
658
811
  return this.focus === "list" && index === this.rowIndex
659
- ? this.theme.fg("accent", row)
660
- : row;
812
+ ? this.theme.fg("accent", preview)
813
+ : preview;
661
814
  })];
662
815
  }
663
816
 
664
817
  private renderTraceDetail(width: number): string[] {
665
818
  if (!this.traceDetail) return [this.theme.fg("warning", " Trace row is no longer available")];
666
- const projection = this.documentProjection(this.traceDetail, width);
819
+ const sequence = this.traceDetail[TraceProjection.TRACE_ITEM_SEQUENCE];
820
+ const value = {
821
+ ...(sequence !== undefined ? { projection_sequence: sequence } : {}),
822
+ ...this.traceDetail,
823
+ chronology: this.traceDetail[TraceProjection.TRACE_ITEM_CHRONOLOGY] ?? { state: "unavailable", reason: "metadata_unavailable" },
824
+ };
825
+ const projection = this.traceDetail.source === "agent" && this.traceDetail.kind === "agent.turn"
826
+ ? this.agentDetailProjection(value, width) : this.documentProjection(value, width);
667
827
  const maxStart = Math.max(0, projection.lines.length - this.contentViewportRows());
668
828
  this.detailScroll = Math.min(this.detailScroll, maxStart);
669
829
  this.contentStripeIndices = projection.stripes.slice(
@@ -676,37 +836,240 @@ export class ActorInspectorOverlay {
676
836
  );
677
837
  }
678
838
 
839
+ private agentDetailSegments(value: unknown): AgentDetailSegment[] {
840
+ const extractedBody = Symbol("extracted-body");
841
+ const bodies: Array<{ field: Array<string | number>; text: string }> = [];
842
+ // Only typed SessionEvidenceTurn slots are bodies. Never inspect authored text for framing.
843
+ const select = (item: unknown, field: Array<string | number>): unknown => {
844
+ if (typeof item === "string") { bodies.push({ field, text: item }); return extractedBody; }
845
+ return item;
846
+ };
847
+ const record = value as Record<string, unknown>;
848
+ const detail = record.detail;
849
+ let metadata = value;
850
+ if (detail && typeof detail === "object" && !Array.isArray(detail)) {
851
+ const retained = Object.fromEntries(Object.entries(detail).map(([key, item]) => {
852
+ if (["assistantText", "userText", "thinking"].includes(key) && typeof item === "string") {
853
+ return [key, select(item, ["detail", key])];
854
+ }
855
+ if (key !== "toolCalls" || !Array.isArray(item)) return [key, item];
856
+ return [key, item.map((call, callIndex) => {
857
+ if (!call || typeof call !== "object" || Array.isArray(call)) return call;
858
+ // Action-first display order only; retained tool-call data and body rendering stay unchanged.
859
+ const rank = (key: string) => key === "name" ? 0 : key === "arguments" ? 1 : key === "result" ? 3 : 2;
860
+ return Object.fromEntries(Object.entries(call).sort(([left], [right]) => rank(left) - rank(right)).map(([name, result]) => {
861
+ if (name !== "result") return [name, result];
862
+ const field = ["detail", key, callIndex, name];
863
+ if (typeof result === "string") return [name, select(result, field)];
864
+ if (!Array.isArray(result)) return [name, result];
865
+ return [name, result.map((block, blockIndex) => {
866
+ if (!block || typeof block !== "object" || block.type !== "text" || typeof block.text !== "string") return block;
867
+ select(block.text, [...field, blockIndex, "text"]);
868
+ return Object.fromEntries(Object.entries(block).filter(([slot]) => slot !== "text"));
869
+ })];
870
+ }).filter(([, item]) => item !== extractedBody));
871
+ })];
872
+ }).filter(([, item]) => item !== extractedBody));
873
+ metadata = { ...record, detail: retained };
874
+ }
875
+ return [{ kind: "metadata", value: metadata }, ...bodies.flatMap<AgentDetailSegment>(({ field, text }) => [
876
+ { kind: "metadata", value: { field } }, { kind: "body", text },
877
+ ])];
878
+ }
879
+
880
+ private metadataText(value: unknown): string {
881
+ // Copy own selected data, so the library cannot invoke an object's toJSON hook.
882
+ const copy = (item: unknown): unknown => {
883
+ // UI-only value state: keep an own undefined slot visible, distinct from null or literal text.
884
+ if (item === undefined) return { value_state: "undefined" };
885
+ if (typeof item === "function") throw new TypeError("Inspector metadata must be selected own data");
886
+ if (!item || typeof item !== "object") return item;
887
+ if (Array.isArray(item)) return Array.from({ length: item.length }, (_, index) => {
888
+ const descriptor = Object.getOwnPropertyDescriptor(item, String(index));
889
+ if (descriptor && !Object.hasOwn(descriptor, "value")) throw new TypeError("Inspector metadata must be selected own data");
890
+ return copy(descriptor?.value);
891
+ });
892
+ const result = Object.create(null) as Record<string, unknown>;
893
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(item))) {
894
+ if (!descriptor.enumerable) continue;
895
+ if (!Object.hasOwn(descriptor, "value")) throw new TypeError("Inspector metadata must be selected own data");
896
+ result[key] = copy(descriptor.value);
897
+ }
898
+ return result;
899
+ };
900
+ const document = new Document(copy(value), { version: "1.2", schema: "core", aliasDuplicateObjects: false });
901
+ visit(document, { Pair(_key, pair) {
902
+ if (isScalar(pair.key) && typeof pair.key.value === "string" && pair.key.value.includes("\uFEFF")) pair.key.type = "QUOTE_DOUBLE";
903
+ } });
904
+ return document.toString({ collectionStyle: "block", indent: 2, lineWidth: 0, directives: false,
905
+ defaultStringType: "PLAIN", doubleQuotedAsJSON: true });
906
+ }
907
+
908
+ private agentDetailProjection(value: unknown, width: number): { lines: string[]; stripes: number[] } {
909
+ const segments = this.agentDetailSegments(value);
910
+ const lines: string[] = [], stripes: number[] = [];
911
+ const markdownTheme = { ...getMarkdownTheme(), codeBlockIndent: "" };
912
+ const detailStripe = Object.keys(value as object).indexOf("detail");
913
+ const append = (rendered: string[], stripe: number) => {
914
+ lines.push(...rendered); stripes.push(...rendered.map(() => stripe));
915
+ };
916
+ const metadata = (selected: unknown, stripe: number) => {
917
+ // yaml's standard serializer has no completion-wire budget/depth ceiling. Data is already admitted upstream.
918
+ const yaml = this.metadataText(selected).replace(/\n$/u, "");
919
+ append(this.wrapDocument(yaml.split("\n").map(text => ({ text })), width).map(line => this.theme.fg("muted", line.text)), stripe);
920
+ };
921
+ segments.forEach((segment, index) => {
922
+ if (index) append([""], detailStripe);
923
+ if (segment.kind === "body") {
924
+ append(new Markdown(segment.text, 0, 0, markdownTheme, { color: text => this.theme.fg("text", text) }).render(width), detailStripe);
925
+ } else if (index === 0) {
926
+ // Serialize root entries separately to retain root-field stripe provenance without parsing YAML rows.
927
+ Object.entries(segment.value as object).forEach(([key, item], stripe) => metadata({ [key]: item }, stripe));
928
+ } else {
929
+ if (segments[index + 1]?.kind === "body") append([this.theme.fg("muted", "---")], detailStripe);
930
+ metadata(segment.value, detailStripe);
931
+ if (segments[index + 1]?.kind === "body") append([this.theme.fg("muted", "---")], detailStripe);
932
+ }
933
+ });
934
+ return { lines, stripes };
935
+ }
936
+
679
937
  private controlDocument(
680
938
  run: ActorInspector.ActorInspectorRunItem,
681
939
  stateDir: string,
682
940
  ): Record<string, unknown> {
683
- const endpoint = run.runInstanceId
684
- ? RunControlDelivery.readRunControlEndpoint(stateDir, run.runInstanceId)
685
- : undefined;
941
+ return this.controlObservation(run, stateDir).document;
942
+ }
943
+
944
+ private controlObservation(run: ActorInspector.ActorInspectorRunItem, stateDir = path.join(this.stateRoot, run.run)): ControlObservation {
945
+ if (this.controlCache?.run === run.run && this.controlCache.generation === run.runInstanceId) return this.controlCache;
686
946
  const meta = this.readRunMeta(stateDir);
687
- const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
947
+ const bound = Boolean(meta && !Array.isArray(meta) && run.runInstanceId && meta.run === run.run
948
+ && meta.run_instance_id === run.runInstanceId && meta.ownerId === this.ownerId);
949
+ const diagnostics: Array<{ reason: string; line?: number }> = [];
950
+ let actions: string[] = [];
951
+ if (!bound) diagnostics.push({ reason: "metadata_binding_unavailable" });
952
+ else {
953
+ try { actions = normalizeRecipeControl(meta.control) ?? []; }
954
+ catch { diagnostics.push({ reason: "invalid_action_declarations" }); }
955
+ }
956
+ const endpoint = bound ? RunControlDelivery.readRunControlEndpoint(stateDir, run.runInstanceId!) : undefined;
957
+ const journal = bound ? RunsControls.readRunControlJournalFromStateDir(stateDir) : { records: [], diagnostics: [] };
958
+ diagnostics.push(...journal.diagnostics.map(item => item.line === undefined
959
+ ? { reason: "unreadable_control_journal" } : { reason: "invalid_control_json", line: item.line }));
960
+ const now = Date.now();
961
+ const ids = new Map<string, number>();
962
+ for (const value of journal.records) {
963
+ if (value && typeof value === "object" && typeof (value as Record<string, unknown>).id === "string") {
964
+ const id = (value as Record<string, unknown>).id as string;
965
+ ids.set(id, (ids.get(id) ?? 0) + 1);
966
+ }
967
+ }
968
+ const accepted: Array<{ id: string; value: ControlProjection.ProjectedRunControl; age: number; reason?: string; parsedIndex: number }> = [];
969
+ for (const [parsedIndex, value] of journal.records.entries()) {
970
+ const classification = RuntimeTriage.classifyRuntimeControl(run, value, now);
971
+ if (classification.diagnostic) { diagnostics.push({ reason: classification.diagnostic.reason }); continue; }
972
+ const record = value as RunsControls.RunControlRecord;
973
+ if (record.run_instance_id !== run.runInstanceId) { diagnostics.push({ reason: "foreign_generation" }); continue; }
974
+ if (ids.get(record.id) !== 1) { diagnostics.push({ reason: "duplicate_control_id" }); continue; }
975
+ if (["queued_at", "delivered_at", "claimed_at", "handled_at", "failed_at"].some(field => {
976
+ const timestamp = (record as unknown as Record<string, unknown>)[field];
977
+ return (field === "queued_at" || timestamp !== undefined) && (typeof timestamp !== "string" || !Number.isFinite(Date.parse(timestamp)));
978
+ })) {
979
+ diagnostics.push({ reason: "invalid_record_timestamp" }); continue;
980
+ }
981
+ accepted.push({ id: record.id, value: ControlProjection.projectRunControl(record), parsedIndex,
982
+ age: Math.max(0, now - Date.parse(runControlStatusTimestamp(record as unknown as Record<string, unknown>, record.status) as string)),
983
+ reason: classification.stale?.reason });
984
+ }
688
985
  const capacity = computeRunControlCapacity(journal.records);
689
- const stalePending = journal.records.reduce<number>((count, control) => count + Number(Boolean(
690
- RuntimeTriage.classifyRuntimeControl({ run: run.run,
691
- runInstanceId: run.runInstanceId, status: run.status }, control, Date.now()).stale)), 0);
692
- return {
693
- status: run.status,
694
- run_instance_id: run.runInstanceId,
695
- pending: capacity.pending, pending_limit: capacity.limit,
696
- available: capacity.available, backpressured: capacity.backpressured,
697
- journal_bytes: (() => { try { return statSync(RunsControls.runControlsFile(stateDir)).size; } catch { return 0; } })(),
986
+ const partial = diagnostics.length > 0;
987
+ const rows = accepted.filter(row => row.parsedIndex >= journal.records.length - 20).reverse();
988
+ const readiness = partial ? "data_unavailable" : run.status !== "running" ? "not_ready"
989
+ : !actions.length ? "unsupported" : capacity.backpressured ? "backpressure" : endpoint ? "ready_recorded_endpoint" : "not_ready";
990
+ const reason = partial ? "partial_observation" : run.status !== "running" ? "run_terminal"
991
+ : !actions.length ? "no_declared_actor_actions" : capacity.backpressured ? "pending_limit_reached"
992
+ : endpoint ? "recorded_endpoint_only_not_connection_proof" : "recorded_endpoint_unavailable";
993
+ this.controlCache = { run: run.run, generation: run.runInstanceId, rows, document: {
994
+ actor_control_readiness: readiness, reason, actor_actions: actions,
995
+ data_state: partial ? "partial" : "available", pending: bound ? capacity.pending : null,
996
+ pending_limit: capacity.limit, available: partial ? null : capacity.available,
997
+ backpressured: bound ? capacity.backpressured : null,
998
+ runtime_actions: bound ? run.status === "running" ? this.killRun && run.runInstanceId ? ["kill"] : [] : ["archive", "prune"] : [],
999
+ runtime_actions_note: "API capabilities; only existing Run-focus Kill is interactive",
1000
+ request_history: { shown: rows.length, retained: journal.records.length, parsed_suffix_limit: 20,
1001
+ partial: partial || journal.records.length > 20, coverage: "recent parsed journal records; malformed-line coverage unavailable; not complete lifetime history" },
1002
+ request_navigation: "Enter opens requests; Down scrolls overview",
1003
+ evidence: "Saved non-atomic observation; delivered != handled; handled != Run completion; stale never expires requests",
1004
+ status: run.status, run_instance_id: run.runInstanceId,
1005
+ journal_bytes: (() => { try { return statSync(RunsControls.runControlsFile(stateDir)).size; } catch { return null; } })(),
698
1006
  journal_limit: Limits.RUN_CONTROL_JOURNAL_MAX_BYTES,
699
- stale_pending: stalePending,
700
- diagnostics: journal.diagnostics.map((item) => item.line === undefined
701
- ? { reason: "unreadable_control_journal" } : { line: item.line, reason: "invalid_control_json" }),
702
- actor_actions: Array.isArray(meta.control) ? meta.control : [],
703
- runtime_actions: run.status === "running" ? ["kill"] : ["archive", "prune"],
704
- endpoint,
705
- recent_controls: journal.records.slice(-20).reverse().map((control) =>
706
- ControlProjection.projectRunControl(control as RunsControls.RunControlRecord)),
1007
+ stale_pending: accepted.filter(row => row.reason).length, diagnostics,
1008
+ endpoint, recent_controls: rows.map(row => row.value),
1009
+ } };
1010
+ return this.controlCache;
1011
+ }
1012
+
1013
+ private selectedControl(observation: ControlObservation) {
1014
+ const selection = this.controlSelection;
1015
+ return selection?.run === observation.run && selection.generation === observation.generation
1016
+ ? observation.rows.find(row => row.id === selection.id) : undefined;
1017
+ }
1018
+
1019
+ private openControlList(run?: ActorInspector.ActorInspectorRunItem): void {
1020
+ this.focus = "list";
1021
+ this.rowIndex = 0;
1022
+ const observation = run ? this.controlObservation(run) : undefined;
1023
+ const first = observation?.rows[0];
1024
+ this.controlSelection = first ? { run: observation!.run, generation: observation!.generation, id: first.id } : undefined;
1025
+ }
1026
+
1027
+ private handleControlInput(data: string, run?: ActorInspector.ActorInspectorRunItem): void {
1028
+ if (matchesKey(data, "escape") || matchesKey(data, "left")) {
1029
+ this.focus = this.focus === "detail" ? "list" : "tabs";
1030
+ this.detailScroll = 0;
1031
+ return;
1032
+ }
1033
+ if (!run) return;
1034
+ const observation = this.controlObservation(run);
1035
+ const selected = this.selectedControl(observation);
1036
+ if (this.focus === "detail") { this.handleScrollableInput(data, "detail"); return; }
1037
+ const index = selected ? observation.rows.indexOf(selected) : -1;
1038
+ let next = index;
1039
+ if (matchesKey(data, "up")) next = Math.max(0, index - 1);
1040
+ else if (matchesKey(data, "down")) next = Math.min(observation.rows.length - 1, index + 1);
1041
+ else if (matchesKey(data, "pageUp")) next = Math.max(0, index - this.contentViewportRows());
1042
+ else if (matchesKey(data, "pageDown")) next = Math.min(observation.rows.length - 1, index + this.contentViewportRows());
1043
+ else if ((matchesKey(data, "return") || matchesKey(data, "right")) && selected) {
1044
+ this.focus = "detail"; this.detailScroll = 0; return;
1045
+ }
1046
+ if (next !== index && observation.rows[next]) this.controlSelection = {
1047
+ run: observation.run, generation: observation.generation, id: observation.rows[next]!.id,
707
1048
  };
708
1049
  }
709
1050
 
1051
+ private renderControlContent(run: ActorInspector.ActorInspectorRunItem, width: number): string[] {
1052
+ const observation = this.controlObservation(run);
1053
+ const selected = this.selectedControl(observation);
1054
+ if (this.focus === "detail") {
1055
+ const projection = this.documentProjection(selected ? selected.value : { request: "unavailable", reason: "selected Run/generation/id no longer available" }, width);
1056
+ this.detailScroll = Math.min(this.detailScroll, Math.max(0, projection.lines.length - this.contentViewportRows()));
1057
+ this.contentStripeIndices = projection.stripes.slice(this.detailScroll, this.detailScroll + this.contentViewportRows());
1058
+ return projection.lines.slice(this.detailScroll, this.detailScroll + this.contentViewportRows());
1059
+ }
1060
+ const coverage = observation.document.request_history as { shown: number; retained: number; partial: boolean };
1061
+ const banner = this.traceTableLine(` Requests ${coverage.shown}/${coverage.retained} parsed; recent20${coverage.partial ? "; partial" : ""}`, width);
1062
+ if (!observation.rows.length) return [banner, " No requests", this.traceTableLine(" Saved observation; history may be partial", width)];
1063
+ const index = selected ? observation.rows.indexOf(selected) : -1;
1064
+ const count = Math.max(1, this.contentViewportRows() - 2);
1065
+ const start = Math.max(0, index - Math.floor(count / 2));
1066
+ const rows = observation.rows.slice(start, start + count);
1067
+ this.contentStripeIndices = [0, 0, ...rows.map((_row, offset) => start + offset + 1)];
1068
+ if (index >= start) this.contentSelectedRows.add(index - start + 2);
1069
+ return [banner, this.traceTableLine(index < 0 ? " Selection unavailable; arrows select a request" : " Status | action | status age | error/stale", width),
1070
+ ...rows.map(row => this.traceTableLine(`${row === selected ? "▶" : " "} ${row.value.status} | ${row.value.action} | ${Math.floor(row.age / 1000)}s | ${row.value.error ?? row.reason ?? ""}`, width))];
1071
+ }
1072
+
710
1073
  private allTraceItems(run?: ActorInspector.ActorInspectorRunItem): TraceProjection.TraceItem[] {
711
1074
  if (!run) return [];
712
1075
  if (
@@ -752,195 +1115,95 @@ export class ActorInspectorOverlay {
752
1115
  value: unknown,
753
1116
  width: number,
754
1117
  ): { lines: string[]; stripes: number[] } {
755
- const sections = value && typeof value === "object" && !Array.isArray(value)
756
- ? Object.entries(value as Record<string, unknown>).map(([key, item]) =>
757
- this.labeledDocumentLines(key, item)
758
- )
759
- : [this.document(value)];
1118
+ // Copy before enumerating roots: accessors and hooks must never run during projection.
1119
+ const selected = this.metadataText(value);
1120
+ const descriptors = value && typeof value === "object" && !Array.isArray(value)
1121
+ ? Object.entries(Object.getOwnPropertyDescriptors(value)).filter(([, descriptor]) => descriptor.enumerable) : [];
1122
+ const sections = descriptors.length
1123
+ ? descriptors.map(([key, descriptor]) => this.metadataText({ [key]: descriptor.value }))
1124
+ : [selected];
760
1125
  const lines: string[] = [];
761
1126
  const stripes: number[] = [];
762
1127
  sections.forEach((section, sectionIndex) => {
763
- const wrapped = this.wrapDocument(section, width);
764
- lines.push(...wrapped.map((line) => this.styleDocumentLine(line)));
1128
+ const wrapped = this.wrapDocument(section.replace(/\n$/u, "").split("\n").map(text => ({ text })), width);
1129
+ lines.push(...wrapped.map((line) => this.theme.fg("muted", line.text)));
765
1130
  stripes.push(...wrapped.map(() => sectionIndex));
766
1131
  });
767
1132
  return { lines, stripes };
768
1133
  }
769
1134
 
770
- private inlineDocumentValue(value: unknown): string | undefined {
771
- if (Array.isArray(value)) {
772
- if (value.length === 0) return "[]";
773
- if (value.every((item) =>
774
- (item === null || typeof item !== "object") &&
775
- !(typeof item === "string" && /[\r\n]/u.test(item))
776
- )) {
777
- return `[${value.map((item) => String(item ?? "none")).join(", ")}]`;
778
- }
779
- return undefined;
780
- }
781
- if (value && typeof value === "object") {
782
- if (Object.keys(value as Record<string, unknown>).length === 0) return "{}";
783
- }
784
- return undefined;
785
- }
786
-
787
- private labeledDocumentLines(key: string, value: unknown, depth = 0): string[] {
788
- const inline = this.inlineDocumentValue(value);
789
- if (inline !== undefined) return [`${" ".repeat(depth)}${key}: ${inline}`];
790
- if (value && typeof value === "object") {
791
- const indent = " ".repeat(depth);
792
- const nested = this.document(value, depth);
793
- return [`${indent}${key}: ${nested[0]!.slice(indent.length)}`, ...nested.slice(1)];
794
- }
795
- return this.labeledScalarLines(key, value, depth);
796
- }
797
-
798
- private labeledScalarLines(key: string, value: unknown, depth = 0): string[] {
799
- const indent = " ".repeat(depth);
800
- const [first = "", ...rest] = String(value ?? "none").split(/\r?\n/u);
801
- return [
802
- `${indent}${key}: ${first}`,
803
- ...rest.map((line) => `${indent} ${line}`),
804
- ];
805
- }
806
-
807
- private document(value: unknown, depth = 0): string[] {
808
- if (value === null || value === undefined) return [`${" ".repeat(depth)}none`];
809
- const inline = this.inlineDocumentValue(value);
810
- if (inline !== undefined) return [`${" ".repeat(depth)}${inline}`];
811
- if (Array.isArray(value)) {
812
- const indent = " ".repeat(depth);
813
- return [
814
- `${indent}[`,
815
- ...value.flatMap((item, index) => {
816
- const itemDepth = depth + 1;
817
- const itemIndent = " ".repeat(itemDepth);
818
- const marker = `#${index}`;
819
- const itemInline = this.inlineDocumentValue(item);
820
- let lines: string[];
821
- if (itemInline !== undefined) {
822
- lines = [`${itemIndent}${marker}: ${itemInline}`];
823
- } else if (item && typeof item === "object") {
824
- const nested = this.document(item, itemDepth);
825
- lines = [
826
- `${itemIndent}${marker}: ${nested[0]!.slice(itemIndent.length)}`,
827
- ...nested.slice(1),
828
- ];
829
- } else {
830
- lines = this.labeledScalarLines(marker, item, itemDepth);
831
- }
832
- if (index < value.length - 1) lines[lines.length - 1] += ",";
833
- return lines;
834
- }),
835
- `${indent}]`,
836
- ];
837
- }
838
- if (typeof value === "object") {
839
- const indent = " ".repeat(depth);
840
- const entries = Object.entries(value as Record<string, unknown>);
841
- return [
842
- `${indent}{`,
843
- ...entries.flatMap(([key, item], index) => {
844
- const lines = this.labeledDocumentLines(key, item, depth + 1);
845
- if (index < entries.length - 1) lines[lines.length - 1] += ",";
846
- return lines;
847
- }),
848
- `${indent}}`,
849
- ];
850
- }
851
- return String(value)
852
- .split(/\r?\n/u)
853
- .map((line) => `${" ".repeat(depth)}${line}`);
854
- }
855
-
856
- private wrapDocument(lines: string[], width: number): string[] {
1135
+ private wrapDocument(lines: InspectorDocumentLine[], width: number): InspectorDocumentLine[] {
857
1136
  return lines.flatMap((line) => {
858
- const normalized = line.replaceAll("\t", " ");
859
- if (visibleWidth(normalized) <= width) return [normalized];
860
- const leading = normalized.match(/^ */u)?.[0] ?? "";
861
- const continuation = `${leading} `;
862
- const wrapped: string[] = [];
863
- let rest = normalized.slice(leading.length);
864
- let prefix = leading;
865
- while (rest && visibleWidth(`${prefix}${rest}`) > width) {
866
- const available = Math.max(1, width - visibleWidth(prefix));
867
- const candidate = this.visiblePrefix(rest, available);
868
- const minimumBreak = Math.floor(candidate.length * 0.6);
869
- const preferred = Math.max(
870
- candidate.lastIndexOf(" "),
871
- candidate.lastIndexOf("/"),
872
- candidate.lastIndexOf(","),
873
- );
874
- const cut = preferred >= minimumBreak ? preferred + 1 : candidate.length;
875
- wrapped.push(`${prefix}${rest.slice(0, cut).trimEnd()}`);
876
- rest = rest.slice(cut).trimStart();
877
- prefix = continuation;
1137
+ // Keep the existing terminal tab display policy; never alter the source value.
1138
+ const text = line.text.replaceAll("\t", " ");
1139
+ const label = line.label?.replaceAll("\t", " ");
1140
+ if (visibleWidth(text) <= width) return [{ text, label }];
1141
+ const leading = text.match(/^ */u)?.[0] ?? "";
1142
+ const hanging = label === undefined ? leading : `${leading} `;
1143
+ const continuation = hanging.length <= width - 2 ? hanging : "";
1144
+ const wrapped: InspectorDocumentLine[] = [];
1145
+ const columns = visibleWidth(text);
1146
+ let offset = 0;
1147
+ while (offset < columns) {
1148
+ const prefix = offset === 0 ? "" : continuation;
1149
+ const available = width - visibleWidth(prefix);
1150
+ // Native cell slicing keeps graphemes; retain separators instead of trimming authored spaces.
1151
+ let part = sliceByColumn(text, offset, available, true);
1152
+ const preferred = Math.max(part.lastIndexOf(" "), part.lastIndexOf("/"), part.lastIndexOf(","));
1153
+ if (offset + visibleWidth(part) < columns && preferred >= Math.floor(part.length * 0.6)) {
1154
+ part = sliceByColumn(text, offset, visibleWidth(part.slice(0, preferred + 1)), true);
1155
+ }
1156
+ const consumed = visibleWidth(part);
1157
+ const labelPart = label === undefined ? "" : sliceByColumn(label, offset, consumed, true);
1158
+ wrapped.push({ text: prefix + part, ...(labelPart ? { label: prefix + labelPart } : {}) });
1159
+ offset += consumed;
878
1160
  }
879
- if (rest) wrapped.push(`${prefix}${rest}`);
880
1161
  return wrapped;
881
1162
  });
882
1163
  }
883
1164
 
884
- private styleDocumentLine(line: string): string {
885
- const indent = line.match(/^ */u)?.[0] ?? "";
886
- const content = line.slice(indent.length);
887
- if (content.endsWith(":")) {
888
- return `${indent}${this.theme.fg("accent", content)}`;
889
- }
890
- const indexed = /^(#\d+:)(.*)$/u.exec(content);
891
- if (indexed) {
892
- return `${indent}${this.theme.fg("accent", indexed[1])}${this.theme.fg("text", indexed[2])}`;
893
- }
894
- const labeled = /^([^:]+:)(.*)$/u.exec(content);
895
- if (labeled) {
896
- return `${indent}${this.theme.fg("muted", labeled[1])}${this.theme.fg("text", labeled[2])}`;
897
- }
898
- return `${indent}${this.theme.fg(content === "none" || content === "[]" ? "muted" : "text", content)}`;
899
- }
900
-
901
- private visiblePrefix(value: string, width: number): string {
902
- let consumed = 0;
903
- let result = "";
904
- for (const character of value) {
905
- const characterWidth = visibleWidth(character);
906
- if (consumed + characterWidth > width) break;
907
- result += character;
908
- consumed += characterWidth;
909
- }
910
- return result;
911
- }
912
-
913
- private renderKeyHints(): string {
1165
+ private keyHints(...items: Array<[string, string]>): string {
914
1166
  const hint = (keys: string, description: string) =>
915
1167
  `${this.theme.fg("accent", keys)}${this.theme.fg("borderAccent", ` ${description}`)}`;
916
1168
  const divider = this.theme.fg("borderAccent", " ─ ");
917
- const hints = (...items: Array<[string, string]>) =>
918
- items.map(([keys, description]) => hint(keys, description)).join(divider);
919
- if (this.killConfirmation) {
920
- return hints(["←→/tab", "choose"], ["enter/y", "confirm"], ["esc/n", "cancel"]);
1169
+ return items.map(([keys, description]) => hint(keys, description)).join(divider);
1170
+ }
1171
+
1172
+ private renderKeyHints(killConfirmation = false): string {
1173
+ if (killConfirmation) {
1174
+ return this.keyHints(["←→/tab", "choose"], ["enter", "pick"], ["y", "kill"], ["esc/n", "cancel"]);
921
1175
  }
922
1176
  if (this.focus === "select") {
923
- return hints(["↑↓", "option"], ["enter/→", "apply"], ["←/esc", "back"]);
1177
+ return this.keyHints(["↑↓", "option"], ["enter/→", "apply"], ["←/esc", "back"]);
924
1178
  }
925
1179
  if (this.focus === "detail") {
926
- return hints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
1180
+ return this.keyHints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
1181
+ }
1182
+ if (this.tab === "control" && this.focus === "document") {
1183
+ return this.keyHints(["↑↓/pgup/pgdn", "scroll"], ["enter", "requests"], ["←/esc", "tabs"]);
1184
+ }
1185
+ if (this.tab === "control" && this.focus === "list") {
1186
+ return this.keyHints(["↑↓/pgup/pgdn", "row"], ["→/enter", "YAML"], ["←/esc", "tabs"]);
1187
+ }
1188
+ if (this.tab === "control" && this.focus === "tabs") {
1189
+ return this.keyHints(["←→", "tabs"], ["↓", "overview"], ["enter", "requests"], ["esc", "close"]);
927
1190
  }
928
1191
  if (this.focus === "document") {
929
- return hints(["↑↓/pgup/pgdn", "scroll"], ["↑ at top", "tabs"], ["←/esc", "tabs"]);
1192
+ return this.keyHints(["↑↓/pgup/pgdn", "scroll"], ["↑ at top", "tabs"], ["←/esc", "tabs"]);
930
1193
  }
931
1194
  if (this.focus === "list") {
932
- return hints(["↑↓/pgup/pgdn", "row"], ["→/enter", "open"], ["←", "tabs"], ["esc", "close"]);
1195
+ return this.keyHints(["↑↓/pgup/pgdn", "row"], ["→/enter", "open"], ["←", "tabs"], ["esc", "close"]);
933
1196
  }
934
1197
  if (this.focus === "runs") {
935
1198
  const items: Array<[string, string]> = [["←→", "run"], ["↓", "tabs"], ["enter", "list"]];
936
1199
  const run = this.runs()[this.runIndex];
937
1200
  if (this.killRun && run?.status === "running" && run.runInstanceId) items.push(["k", "kill"]);
938
1201
  items.push(["esc", "close"]);
939
- return hints(...items);
1202
+ return this.keyHints(...items);
940
1203
  }
941
1204
  return this.tab === "trace"
942
- ? hints(["←→", "tabs"], ["↑↓", "focus"], ["enter/f", "source"], ["esc", "close"])
943
- : hints(["←→", "tabs"], ["↑↓", "focus"], ["enter", "open"], ["esc", "close"]);
1205
+ ? this.keyHints(["←→", "tabs"], ["↑↓", "focus"], ["enter/f", "source"], ["esc", "close"])
1206
+ : this.keyHints(["←→", "tabs"], ["↑↓", "focus"], ["enter", "open"], ["esc", "close"]);
944
1207
  }
945
1208
 
946
1209
  private statusColor(status: string): "error" | "warning" | "success" | "muted" {