@crouton-kit/crouter 0.3.32 → 0.3.34

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 (852) hide show
  1. package/README.md +10 -2
  2. package/dist/build-root.js +4 -6
  3. package/dist/builtin-memory/crouter-development/marketplaces.md +8 -8
  4. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
  5. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
  6. package/dist/builtin-memory/crouter-development/personas.md +3 -4
  7. package/dist/builtin-memory/crouter-development/plugins.md +31 -31
  8. package/dist/builtin-memory/design.md +4 -1
  9. package/dist/builtin-memory/development.md +4 -1
  10. package/dist/builtin-memory/internal/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +9 -9
  13. package/dist/builtin-memory/internal/storage-tiers.md +5 -5
  14. package/dist/builtin-memory/planning.md +4 -1
  15. package/dist/builtin-memory/product.md +80 -0
  16. package/dist/builtin-memory/spec.md +4 -1
  17. package/dist/builtin-personas/advisor/PERSONA.md +10 -0
  18. package/dist/builtin-personas/design/PERSONA.md +2 -2
  19. package/dist/builtin-personas/design/orchestrator.md +3 -3
  20. package/dist/builtin-personas/developer/PERSONA.md +1 -1
  21. package/dist/builtin-personas/developer/orchestrator.md +2 -2
  22. package/dist/builtin-personas/explore/PERSONA.md +3 -3
  23. package/dist/builtin-personas/explore/orchestrator.md +4 -2
  24. package/dist/builtin-personas/general/PERSONA.md +1 -1
  25. package/dist/builtin-personas/general/orchestrator.md +1 -1
  26. package/dist/builtin-personas/orchestration-kernel.md +8 -15
  27. package/dist/builtin-personas/plan/PERSONA.md +1 -1
  28. package/dist/builtin-personas/plan/orchestrator.md +2 -2
  29. package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
  30. package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
  31. package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
  32. package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
  33. package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
  34. package/dist/builtin-personas/product/PERSONA.md +18 -0
  35. package/dist/builtin-personas/product/orchestrator.md +14 -0
  36. package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
  37. package/dist/builtin-personas/review/PERSONA.md +1 -1
  38. package/dist/builtin-personas/review/orchestrator.md +1 -1
  39. package/dist/builtin-personas/runtime-base.md +5 -0
  40. package/dist/builtin-personas/spec/PERSONA.md +1 -1
  41. package/dist/builtin-personas/spec/orchestrator.md +2 -2
  42. package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
  43. package/dist/builtin-personas/waiting.md +1 -1
  44. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +71 -0
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/crtr-help-gate.test.ts +41 -0
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +663 -0
  47. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +52 -0
  48. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +418 -0
  49. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crouter-help.ts +87 -0
  50. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/filters.json +7 -0
  51. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +174 -0
  52. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-help-gate.ts +408 -0
  53. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +442 -0
  54. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +119 -0
  55. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/nested-context.ts +327 -0
  56. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +574 -0
  57. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +260 -0
  58. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +40 -0
  59. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +19 -0
  60. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/package.json +3 -0
  61. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.js +1 -0
  62. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +479 -0
  63. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/LICENSE +13 -0
  64. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/README.md +172 -0
  65. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/bin.mjs +11 -0
  66. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
  67. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
  68. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
  69. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
  70. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/composer.js +219 -0
  71. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
  72. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
  73. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
  74. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
  75. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
  76. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
  77. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
  78. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
  79. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
  80. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
  81. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
  82. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/Document.js +335 -0
  83. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
  84. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
  85. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
  86. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/directives.js +176 -0
  87. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/errors.js +57 -0
  88. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/index.js +17 -0
  89. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/log.js +11 -0
  90. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
  91. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
  92. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
  93. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
  94. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
  95. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
  96. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
  97. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
  98. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
  99. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
  100. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
  101. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
  102. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
  103. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst.js +98 -0
  104. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
  105. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
  106. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/parser.js +975 -0
  107. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/public-api.js +102 -0
  108. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
  109. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
  110. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
  111. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
  112. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
  113. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
  114. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
  115. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
  116. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
  117. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
  118. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/tags.js +96 -0
  119. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
  120. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
  121. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
  122. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
  123. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
  124. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
  125. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
  126. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
  127. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
  128. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
  129. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
  130. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
  131. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
  132. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
  133. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
  134. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
  135. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
  136. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
  137. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/util.js +11 -0
  138. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/visit.js +233 -0
  139. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/index.js +5 -0
  140. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/package.json +3 -0
  141. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/cli.d.ts +8 -0
  142. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/cli.mjs +201 -0
  143. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
  144. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-collection.js +90 -0
  145. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
  146. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-doc.js +45 -0
  147. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
  148. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-node.js +112 -0
  149. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
  150. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
  151. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/composer.d.ts +63 -0
  152. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/composer.js +224 -0
  153. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
  154. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
  155. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
  156. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
  157. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
  158. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
  159. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
  160. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-end.js +39 -0
  161. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
  162. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
  163. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
  164. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
  165. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
  166. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-props.js +148 -0
  167. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
  168. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
  169. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
  170. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
  171. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
  172. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
  173. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
  174. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
  175. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/Document.d.ts +141 -0
  176. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/Document.js +337 -0
  177. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
  178. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/anchors.js +76 -0
  179. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
  180. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/applyReviver.js +57 -0
  181. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
  182. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/createNode.js +90 -0
  183. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/directives.d.ts +49 -0
  184. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/directives.js +178 -0
  185. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/errors.d.ts +21 -0
  186. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/errors.js +62 -0
  187. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/index.d.ts +25 -0
  188. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/index.js +50 -0
  189. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/log.d.ts +3 -0
  190. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/log.js +19 -0
  191. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
  192. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Alias.js +118 -0
  193. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
  194. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Collection.js +151 -0
  195. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
  196. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Node.js +40 -0
  197. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
  198. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Pair.js +39 -0
  199. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
  200. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Scalar.js +27 -0
  201. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
  202. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
  203. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
  204. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
  205. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
  206. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
  207. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
  208. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/identity.js +53 -0
  209. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
  210. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/toJS.js +39 -0
  211. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/options.d.ts +350 -0
  212. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
  213. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
  214. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
  215. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
  216. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
  217. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-visit.js +99 -0
  218. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst.d.ts +109 -0
  219. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst.js +112 -0
  220. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
  221. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/lexer.js +723 -0
  222. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
  223. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/line-counter.js +41 -0
  224. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/parser.d.ts +84 -0
  225. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/parser.js +980 -0
  226. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/public-api.d.ts +44 -0
  227. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/public-api.js +107 -0
  228. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
  229. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/Schema.js +39 -0
  230. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
  231. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/map.js +19 -0
  232. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
  233. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/null.js +17 -0
  234. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
  235. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/seq.js +19 -0
  236. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
  237. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/string.js +16 -0
  238. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
  239. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/bool.js +21 -0
  240. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
  241. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/float.js +47 -0
  242. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
  243. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/int.js +42 -0
  244. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
  245. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/schema.js +25 -0
  246. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
  247. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json/schema.js +64 -0
  248. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
  249. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/tags.d.ts +48 -0
  250. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/tags.js +99 -0
  251. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/types.d.ts +92 -0
  252. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
  253. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
  254. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
  255. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
  256. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
  257. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
  258. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
  259. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
  260. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
  261. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
  262. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
  263. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
  264. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
  265. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
  266. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
  267. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
  268. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
  269. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
  270. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
  271. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
  272. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
  273. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
  274. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
  275. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringify.js +132 -0
  276. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
  277. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
  278. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
  279. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
  280. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
  281. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
  282. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
  283. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
  284. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
  285. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
  286. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
  287. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
  288. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/test-events.d.ts +4 -0
  289. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/test-events.js +134 -0
  290. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/util.d.ts +16 -0
  291. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/util.js +28 -0
  292. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/visit.d.ts +102 -0
  293. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/visit.js +236 -0
  294. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/package.json +97 -0
  295. package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/util.js +2 -0
  296. package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +3385 -0
  297. package/dist/builtin-pi-packages/pi-crtr-extensions/package.json +25 -0
  298. package/dist/builtin-pi-packages/pi-mode-switch/README.md +64 -0
  299. package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +40 -0
  300. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +422 -0
  301. package/dist/builtin-pi-packages/pi-mode-switch/package.json +13 -0
  302. package/dist/builtin-views/canvas/core.mjs +82 -1
  303. package/dist/builtin-views/canvas/tui.mjs +9 -5
  304. package/dist/builtin-views/canvas/web.jsx +3 -2
  305. package/dist/builtin-views/chat/core.mjs +725 -0
  306. package/dist/builtin-views/chat/text.mjs +101 -0
  307. package/dist/builtin-views/chat/tui.mjs +368 -0
  308. package/dist/builtin-views/chat/web.jsx +367 -0
  309. package/dist/builtin-views/prompt-review/core.mjs +863 -0
  310. package/dist/builtin-views/prompt-review/text.mjs +15 -0
  311. package/dist/builtin-views/prompt-review/tui.mjs +196 -0
  312. package/dist/builtin-views/prompt-review/web.jsx +484 -0
  313. package/dist/builtin-views/settings/core.mjs +397 -0
  314. package/dist/builtin-views/settings/text.mjs +40 -0
  315. package/dist/builtin-views/settings/tui.mjs +95 -0
  316. package/dist/builtin-views/settings/web.jsx +167 -0
  317. package/dist/cli.js +16 -3
  318. package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +55 -0
  319. package/dist/clients/attach/__tests__/crtr-output-render.test.js +2340 -0
  320. package/dist/clients/attach/attach-cmd.d.ts +18 -0
  321. package/dist/clients/attach/attach-cmd.js +1797 -797
  322. package/dist/clients/attach/auth-pickers.js +2 -2
  323. package/dist/clients/attach/canvas-panels.js +3 -3
  324. package/dist/clients/attach/chat-view.d.ts +13 -2
  325. package/dist/clients/attach/chat-view.js +53 -10
  326. package/dist/clients/attach/clipboard-image.js +1 -1
  327. package/dist/clients/attach/clipboard-text.js +1 -1
  328. package/dist/clients/attach/config-load.d.ts +8 -4
  329. package/dist/clients/attach/config-load.js +3 -1
  330. package/dist/clients/attach/crtr-output-render.d.ts +48 -0
  331. package/dist/clients/attach/crtr-output-render.js +646 -0
  332. package/dist/clients/attach/extension-dialogs.d.ts +2 -3
  333. package/dist/clients/attach/extension-dialogs.js +8 -10
  334. package/dist/clients/attach/graph-overlay.js +6 -5
  335. package/dist/clients/attach/input-controller.d.ts +4 -3
  336. package/dist/clients/attach/input-controller.js +25 -4
  337. package/dist/clients/attach/pickers.js +1 -1
  338. package/dist/clients/attach/slash-commands.d.ts +7 -7
  339. package/dist/clients/attach/slash-commands.js +40 -17
  340. package/dist/clients/attach/titled-editor.d.ts +4 -0
  341. package/dist/clients/attach/titled-editor.js +31 -15
  342. package/dist/clients/attach/view-socket.d.ts +2 -1
  343. package/dist/clients/attach/view-socket.js +28 -9
  344. package/dist/clients/web/__tests__/source-cache.test.js +32 -0
  345. package/dist/clients/web/dev-server.js +1 -0
  346. package/dist/clients/web/events.js +9 -11
  347. package/dist/clients/web/server.d.ts +4 -3
  348. package/dist/clients/web/server.js +166 -35
  349. package/dist/clients/web/source-cache.d.ts +10 -0
  350. package/dist/clients/web/source-cache.js +57 -0
  351. package/dist/clients/web/web-cmd.js +32 -13
  352. package/dist/commands/__tests__/human.test.js +30 -28
  353. package/dist/commands/canvas-history/search.js +1 -1
  354. package/dist/commands/canvas-history.js +5 -8
  355. package/dist/commands/canvas-issue.d.ts +2 -0
  356. package/dist/commands/canvas-issue.js +148 -0
  357. package/dist/commands/canvas-prune.js +43 -2
  358. package/dist/commands/canvas-rebuild-index.d.ts +2 -0
  359. package/dist/commands/canvas-rebuild-index.js +57 -0
  360. package/dist/commands/canvas-snapshot.d.ts +2 -0
  361. package/dist/commands/canvas-snapshot.js +51 -0
  362. package/dist/commands/canvas-tmux-spread.d.ts +2 -0
  363. package/dist/commands/canvas-tmux-spread.js +188 -0
  364. package/dist/commands/canvas.d.ts +1 -0
  365. package/dist/commands/canvas.js +16 -1
  366. package/dist/commands/chord.js +143 -48
  367. package/dist/commands/daemon.js +3 -3
  368. package/dist/commands/human/prompts.d.ts +0 -1
  369. package/dist/commands/human/prompts.js +39 -54
  370. package/dist/commands/human/queue.d.ts +12 -1
  371. package/dist/commands/human/queue.js +468 -73
  372. package/dist/commands/human/shared.d.ts +3 -4
  373. package/dist/commands/human/shared.js +3 -3
  374. package/dist/commands/human.js +12 -11
  375. package/dist/commands/memory/find.js +3 -2
  376. package/dist/commands/memory/lint.js +15 -8
  377. package/dist/commands/memory/list.js +4 -3
  378. package/dist/commands/memory/origin.d.ts +1 -0
  379. package/dist/commands/memory/origin.js +88 -0
  380. package/dist/commands/memory/read.js +3 -3
  381. package/dist/commands/memory/shared.d.ts +9 -4
  382. package/dist/commands/memory/shared.js +28 -10
  383. package/dist/commands/memory/write.js +19 -14
  384. package/dist/commands/memory.js +6 -8
  385. package/dist/commands/node-context.d.ts +2 -0
  386. package/dist/commands/node-context.js +172 -0
  387. package/dist/commands/node-snapshot.d.ts +2 -0
  388. package/dist/commands/node-snapshot.js +126 -0
  389. package/dist/commands/node.d.ts +2 -2
  390. package/dist/commands/node.js +987 -603
  391. package/dist/commands/pkg/market-inspect.d.ts +2 -1
  392. package/dist/commands/pkg/market-inspect.js +14 -32
  393. package/dist/commands/pkg/market-manage.d.ts +3 -1
  394. package/dist/commands/pkg/market-manage.js +58 -154
  395. package/dist/commands/pkg/market.js +7 -7
  396. package/dist/commands/pkg/plugin-inspect.d.ts +2 -1
  397. package/dist/commands/pkg/plugin-inspect.js +13 -29
  398. package/dist/commands/pkg/plugin-manage.d.ts +5 -1
  399. package/dist/commands/pkg/plugin-manage.js +246 -172
  400. package/dist/commands/pkg/plugin.js +6 -6
  401. package/dist/commands/pkg.js +7 -7
  402. package/dist/commands/push.d.ts +2 -0
  403. package/dist/commands/push.js +79 -11
  404. package/dist/commands/revive.js +79 -4
  405. package/dist/commands/search/answer.d.ts +1 -0
  406. package/dist/commands/search/answer.js +50 -0
  407. package/dist/commands/search/contents.d.ts +1 -0
  408. package/dist/commands/search/contents.js +96 -0
  409. package/dist/commands/search/exa.d.ts +53 -0
  410. package/dist/commands/search/exa.js +155 -0
  411. package/dist/commands/search/puremd.d.ts +11 -0
  412. package/dist/commands/search/puremd.js +53 -0
  413. package/dist/commands/search/web.d.ts +1 -0
  414. package/dist/commands/search/web.js +65 -0
  415. package/dist/commands/search.d.ts +2 -0
  416. package/dist/commands/search.js +24 -0
  417. package/dist/commands/surface.d.ts +2 -0
  418. package/dist/commands/surface.js +43 -0
  419. package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
  420. package/dist/commands/sys/config.js +59 -10
  421. package/dist/commands/sys/doctor.js +56 -4
  422. package/dist/commands/sys/prompt-review.d.ts +1 -0
  423. package/dist/commands/sys/prompt-review.js +178 -0
  424. package/dist/commands/sys/promptstudio.d.ts +2 -0
  425. package/dist/commands/sys/promptstudio.js +65 -0
  426. package/dist/commands/sys/settings.d.ts +2 -0
  427. package/dist/commands/sys/settings.js +16 -0
  428. package/dist/commands/sys/setup-core.d.ts +88 -0
  429. package/dist/commands/sys/setup-core.js +352 -0
  430. package/dist/commands/sys/setup.d.ts +2 -0
  431. package/dist/commands/sys/setup.js +516 -0
  432. package/dist/commands/sys/sync.js +311 -159
  433. package/dist/commands/sys/sysprompt.d.ts +1 -0
  434. package/dist/commands/sys/sysprompt.js +89 -0
  435. package/dist/commands/sys.js +10 -5
  436. package/dist/commands/view-cycle.js +5 -5
  437. package/dist/commands/view-list.js +3 -3
  438. package/dist/commands/view-new.js +6 -6
  439. package/dist/commands/view-pick.js +6 -6
  440. package/dist/commands/view-run.js +14 -14
  441. package/dist/commands/view.js +3 -3
  442. package/dist/commands/workspace.js +10 -10
  443. package/dist/core/__tests__/boot.test.js +140 -0
  444. package/dist/core/__tests__/broker-preflight.test.js +85 -0
  445. package/dist/core/__tests__/broker-sdk-wiring.test.js +10 -16
  446. package/dist/core/__tests__/canvas.test.js +37 -1
  447. package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
  448. package/dist/core/__tests__/child-death-wake.test.js +11 -2
  449. package/dist/core/__tests__/close.test.js +39 -1
  450. package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
  451. package/dist/core/__tests__/context-intro.test.js +48 -14
  452. package/dist/core/__tests__/daemon-boot.test.js +204 -9
  453. package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
  454. package/dist/core/__tests__/fixtures/fake-engine.js +157 -18
  455. package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
  456. package/dist/core/__tests__/full/broker-attach-limits.test.js +1 -1
  457. package/dist/core/__tests__/full/broker-attach-stream.test.js +1 -1
  458. package/dist/core/__tests__/full/broker-crash-teardown.test.js +10 -5
  459. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +1 -1
  460. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +1 -1
  461. package/dist/core/__tests__/full/broker-pane-resolution.test.js +1 -1
  462. package/dist/core/__tests__/full/cascade-close.test.js +31 -18
  463. package/dist/core/__tests__/full/detach-focus.test.js +2 -2
  464. package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
  465. package/dist/core/__tests__/hearth-bootstrap.test.js +136 -0
  466. package/dist/core/__tests__/helpers/harness.d.ts +1 -1
  467. package/dist/core/__tests__/helpers/harness.js +3 -3
  468. package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
  469. package/dist/core/__tests__/human-surface-target.test.js +1 -1
  470. package/dist/core/__tests__/kickoff.test.js +18 -19
  471. package/dist/core/__tests__/live-mutation-verbs.test.js +10 -10
  472. package/dist/core/__tests__/live-mutation.test.js +8 -8
  473. package/dist/core/__tests__/migration.test.js +172 -13
  474. package/dist/core/__tests__/on-read-dedup-resume.test.js +40 -0
  475. package/dist/core/__tests__/preview-registry-sync.test.js +36 -0
  476. package/dist/core/__tests__/prune-to-limit.test.js +91 -0
  477. package/dist/core/__tests__/push-final-guard.test.js +7 -1
  478. package/dist/core/__tests__/relaunch-root.test.js +9 -9
  479. package/dist/core/__tests__/revive.test.js +100 -27
  480. package/dist/core/__tests__/spawn-root.test.js +24 -1
  481. package/dist/core/__tests__/stop-guard.test.js +10 -0
  482. package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
  483. package/dist/core/__tests__/tmux-surface.test.js +5 -1
  484. package/dist/core/__tests__/trigger-migration.test.js +48 -0
  485. package/dist/core/__tests__/wake-bearings.test.js +23 -21
  486. package/dist/core/__tests__/wake-origin.test.js +36 -33
  487. package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
  488. package/dist/core/artifact.d.ts +2 -33
  489. package/dist/core/artifact.js +27 -87
  490. package/dist/core/bootstrap.js +2 -0
  491. package/dist/core/canvas/boot.d.ts +48 -0
  492. package/dist/core/canvas/boot.js +157 -0
  493. package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
  494. package/dist/core/canvas/browse/app.js +87 -17
  495. package/dist/core/canvas/browse/model.d.ts +10 -1
  496. package/dist/core/canvas/browse/model.js +37 -1
  497. package/dist/core/canvas/browse/render.js +35 -33
  498. package/dist/core/canvas/canvas.d.ts +33 -0
  499. package/dist/core/canvas/canvas.js +138 -8
  500. package/dist/core/canvas/db.js +121 -9
  501. package/dist/core/canvas/index.d.ts +1 -1
  502. package/dist/core/canvas/index.js +1 -1
  503. package/dist/core/canvas/labels.d.ts +6 -8
  504. package/dist/core/canvas/labels.js +8 -11
  505. package/dist/core/canvas/nav-model.d.ts +5 -4
  506. package/dist/core/canvas/nav-model.js +32 -18
  507. package/dist/core/canvas/paths.d.ts +18 -1
  508. package/dist/core/canvas/paths.js +31 -2
  509. package/dist/core/canvas/pid.d.ts +6 -0
  510. package/dist/core/canvas/pid.js +13 -0
  511. package/dist/core/canvas/render.d.ts +25 -0
  512. package/dist/core/canvas/render.js +96 -13
  513. package/dist/core/canvas/status-glyph.d.ts +35 -0
  514. package/dist/core/canvas/status-glyph.js +104 -0
  515. package/dist/core/canvas/triggers.d.ts +108 -0
  516. package/dist/core/canvas/triggers.js +320 -0
  517. package/dist/core/canvas/types.d.ts +130 -48
  518. package/dist/core/command.js +13 -4
  519. package/dist/core/config.js +114 -16
  520. package/dist/core/fault-classifier.d.ts +41 -0
  521. package/dist/core/fault-classifier.js +110 -0
  522. package/dist/core/feed/inbox.d.ts +15 -0
  523. package/dist/core/feed/inbox.js +40 -14
  524. package/dist/core/frontmatter.d.ts +1 -11
  525. package/dist/core/frontmatter.js +1 -55
  526. package/dist/core/hearth/__tests__/model-auth-guest.test.js +151 -0
  527. package/dist/core/hearth/config.d.ts +3 -0
  528. package/dist/core/hearth/config.js +111 -0
  529. package/dist/core/hearth/guest-env.d.ts +9 -0
  530. package/dist/core/hearth/guest-env.js +27 -0
  531. package/dist/core/hearth/index.d.ts +5 -0
  532. package/dist/core/hearth/index.js +5 -0
  533. package/dist/core/hearth/model-auth-guest.d.ts +8 -0
  534. package/dist/core/hearth/model-auth-guest.js +435 -0
  535. package/dist/core/hearth/provider.d.ts +28 -0
  536. package/dist/core/hearth/provider.js +10 -0
  537. package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +12 -0
  538. package/dist/core/hearth/providers/blaxel-bootstrap.js +147 -0
  539. package/dist/core/hearth/providers/blaxel-home.d.ts +15 -0
  540. package/dist/core/hearth/providers/blaxel-home.js +136 -0
  541. package/dist/core/hearth/providers/blaxel.d.ts +31 -0
  542. package/dist/core/hearth/providers/blaxel.js +282 -0
  543. package/dist/core/hearth/providers/types.d.ts +77 -0
  544. package/dist/core/hearth/registry.d.ts +15 -0
  545. package/dist/core/hearth/registry.js +180 -0
  546. package/dist/core/hearth/types.d.ts +156 -0
  547. package/dist/core/help.d.ts +16 -0
  548. package/dist/core/help.js +2 -0
  549. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
  550. package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
  551. package/dist/core/host-exports/builtins.js +68 -0
  552. package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
  553. package/dist/core/{skill-sync → host-exports}/export.js +37 -14
  554. package/dist/core/io.js +1 -1
  555. package/dist/core/log.d.ts +9 -0
  556. package/dist/core/log.js +113 -0
  557. package/dist/core/memory-resolver.d.ts +13 -13
  558. package/dist/core/memory-resolver.js +70 -51
  559. package/dist/core/personas/index.d.ts +4 -4
  560. package/dist/core/personas/index.js +2 -2
  561. package/dist/core/personas/loader.d.ts +51 -0
  562. package/dist/core/personas/loader.js +54 -50
  563. package/dist/core/personas/resolve.d.ts +43 -27
  564. package/dist/core/personas/resolve.js +336 -94
  565. package/dist/core/preview-registry.d.ts +25 -0
  566. package/dist/core/preview-registry.js +2047 -0
  567. package/dist/core/resolver.d.ts +1 -0
  568. package/dist/core/resolver.js +23 -16
  569. package/dist/core/runtime/auth-reload.d.ts +7 -0
  570. package/dist/core/runtime/auth-reload.js +69 -0
  571. package/dist/core/runtime/bearings.d.ts +46 -25
  572. package/dist/core/runtime/bearings.js +313 -52
  573. package/dist/core/runtime/branded-host.d.ts +4 -2
  574. package/dist/core/runtime/branded-host.js +66 -4
  575. package/dist/core/runtime/broker-protocol.d.ts +40 -5
  576. package/dist/core/runtime/broker-protocol.js +2 -2
  577. package/dist/core/runtime/broker.js +343 -18
  578. package/dist/core/runtime/close.js +39 -31
  579. package/dist/core/runtime/connectivity.d.ts +12 -0
  580. package/dist/core/runtime/connectivity.js +73 -0
  581. package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
  582. package/dist/core/runtime/fault-recovery-nudge.js +2 -0
  583. package/dist/core/runtime/fault-recovery.d.ts +23 -0
  584. package/dist/core/runtime/fault-recovery.js +74 -0
  585. package/dist/core/runtime/fault.d.ts +25 -0
  586. package/dist/core/runtime/fault.js +176 -0
  587. package/dist/core/runtime/front-door.d.ts +1 -1
  588. package/dist/core/runtime/front-door.js +2 -2
  589. package/dist/core/runtime/host.d.ts +10 -0
  590. package/dist/core/runtime/host.js +71 -9
  591. package/dist/core/runtime/kickoff.js +32 -46
  592. package/dist/core/runtime/launch.d.ts +41 -21
  593. package/dist/core/runtime/launch.js +165 -52
  594. package/dist/core/runtime/lifecycle.js +7 -7
  595. package/dist/core/runtime/model-swap.d.ts +18 -0
  596. package/dist/core/runtime/model-swap.js +95 -0
  597. package/dist/core/runtime/naming.d.ts +7 -0
  598. package/dist/core/runtime/naming.js +61 -9
  599. package/dist/core/runtime/nodes.js +6 -1
  600. package/dist/core/runtime/persona.js +16 -17
  601. package/dist/core/runtime/placement.d.ts +41 -36
  602. package/dist/core/runtime/placement.js +159 -74
  603. package/dist/core/runtime/promote.d.ts +2 -0
  604. package/dist/core/runtime/promote.js +23 -3
  605. package/dist/core/runtime/recycle.js +13 -11
  606. package/dist/core/runtime/reset.d.ts +6 -3
  607. package/dist/core/runtime/reset.js +5 -5
  608. package/dist/core/runtime/revive-all.d.ts +5 -2
  609. package/dist/core/runtime/revive-all.js +5 -2
  610. package/dist/core/runtime/revive.d.ts +19 -8
  611. package/dist/core/runtime/revive.js +79 -25
  612. package/dist/core/runtime/roadmap.d.ts +1 -1
  613. package/dist/core/runtime/roadmap.js +1 -1
  614. package/dist/core/runtime/spawn.d.ts +3 -3
  615. package/dist/core/runtime/spawn.js +37 -13
  616. package/dist/core/runtime/stop-guard.js +11 -6
  617. package/dist/core/runtime/surface-bg.js +1 -1
  618. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  619. package/dist/core/runtime/tmux-chrome.js +1 -1
  620. package/dist/core/runtime/tmux.d.ts +30 -7
  621. package/dist/core/runtime/tmux.js +138 -84
  622. package/dist/core/runtime/view-socket-client.d.ts +46 -0
  623. package/dist/core/runtime/view-socket-client.js +203 -0
  624. package/dist/core/scope.d.ts +9 -0
  625. package/dist/core/scope.js +37 -15
  626. package/dist/core/substrate/index.d.ts +1 -1
  627. package/dist/core/substrate/index.js +1 -1
  628. package/dist/core/substrate/on-read.js +39 -25
  629. package/dist/core/substrate/render.d.ts +10 -13
  630. package/dist/core/substrate/render.js +56 -51
  631. package/dist/core/tui/host.js +83 -18
  632. package/dist/core/view/__tests__/transport-cache.test.js +62 -0
  633. package/dist/core/view/contract.d.ts +20 -0
  634. package/dist/core/view/loader.js +2 -2
  635. package/dist/core/view/stream-local.d.ts +3 -0
  636. package/dist/core/view/stream-local.js +184 -0
  637. package/dist/core/view/transport-cache.d.ts +8 -0
  638. package/dist/core/view/transport-cache.js +38 -0
  639. package/dist/core/view/transport-local.js +4 -0
  640. package/dist/core/view/transport.d.ts +7 -1
  641. package/dist/core/wake.d.ts +2 -2
  642. package/dist/core/wake.js +7 -7
  643. package/dist/daemon/__tests__/predicate-eval.test.js +109 -0
  644. package/dist/daemon/crtrd.d.ts +3 -19
  645. package/dist/daemon/crtrd.js +360 -280
  646. package/dist/daemon/manage.d.ts +18 -1
  647. package/dist/daemon/manage.js +54 -9
  648. package/dist/daemon/predicate-eval.d.ts +52 -0
  649. package/dist/daemon/predicate-eval.js +248 -0
  650. package/dist/hearth/control-plane/__tests__/node-message.test.js +60 -0
  651. package/dist/hearth/control-plane/__tests__/rate-limit-recurrence.test.js +49 -0
  652. package/dist/hearth/control-plane/__tests__/relay-security.test.js +314 -0
  653. package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.d.ts +1 -0
  654. package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.js +133 -0
  655. package/dist/hearth/control-plane/__tests__/trigger-delivery.test.d.ts +1 -0
  656. package/dist/hearth/control-plane/__tests__/trigger-delivery.test.js +170 -0
  657. package/dist/hearth/control-plane/__tests__/webhook-ingress.test.d.ts +1 -0
  658. package/dist/hearth/control-plane/__tests__/webhook-ingress.test.js +167 -0
  659. package/dist/hearth/control-plane/config.d.ts +21 -0
  660. package/dist/hearth/control-plane/config.js +77 -0
  661. package/dist/hearth/control-plane/db.d.ts +30 -0
  662. package/dist/hearth/control-plane/db.js +534 -0
  663. package/dist/hearth/control-plane/ingress/rate-limit.d.ts +24 -0
  664. package/dist/hearth/control-plane/ingress/rate-limit.js +100 -0
  665. package/dist/hearth/control-plane/ingress/route-store.d.ts +31 -0
  666. package/dist/hearth/control-plane/ingress/route-store.js +61 -0
  667. package/dist/hearth/control-plane/ingress/webhook-delivery-store.d.ts +41 -0
  668. package/dist/hearth/control-plane/ingress/webhook-delivery-store.js +69 -0
  669. package/dist/hearth/control-plane/ingress/webhook-route.d.ts +55 -0
  670. package/dist/hearth/control-plane/ingress/webhook-route.js +285 -0
  671. package/dist/hearth/control-plane/main.d.ts +1 -0
  672. package/dist/hearth/control-plane/main.js +88 -0
  673. package/dist/hearth/control-plane/node-message.d.ts +31 -0
  674. package/dist/hearth/control-plane/node-message.js +98 -0
  675. package/dist/hearth/control-plane/register.d.ts +15 -0
  676. package/dist/hearth/control-plane/register.js +34 -0
  677. package/dist/hearth/control-plane/registry.d.ts +17 -0
  678. package/dist/hearth/control-plane/registry.js +161 -0
  679. package/dist/hearth/control-plane/relay.d.ts +40 -0
  680. package/dist/hearth/control-plane/relay.js +642 -0
  681. package/dist/hearth/control-plane/scheduler/fire-store.d.ts +36 -0
  682. package/dist/hearth/control-plane/scheduler/fire-store.js +73 -0
  683. package/dist/hearth/control-plane/scheduler/recurrence.d.ts +7 -0
  684. package/dist/hearth/control-plane/scheduler/recurrence.js +58 -0
  685. package/dist/hearth/control-plane/scheduler/scan-loop.d.ts +38 -0
  686. package/dist/hearth/control-plane/scheduler/scan-loop.js +138 -0
  687. package/dist/hearth/control-plane/scheduler/schedule-store.d.ts +32 -0
  688. package/dist/hearth/control-plane/scheduler/schedule-store.js +66 -0
  689. package/dist/hearth/control-plane/secrets.d.ts +17 -0
  690. package/dist/hearth/control-plane/secrets.js +113 -0
  691. package/dist/hearth/control-plane/server.d.ts +27 -0
  692. package/dist/hearth/control-plane/server.js +346 -0
  693. package/dist/hearth/control-plane/session.d.ts +68 -0
  694. package/dist/hearth/control-plane/session.js +273 -0
  695. package/dist/hearth/control-plane/triggers/acl.d.ts +14 -0
  696. package/dist/hearth/control-plane/triggers/acl.js +52 -0
  697. package/dist/hearth/control-plane/triggers/audit-store.d.ts +38 -0
  698. package/dist/hearth/control-plane/triggers/audit-store.js +79 -0
  699. package/dist/hearth/control-plane/triggers/deliver.d.ts +43 -0
  700. package/dist/hearth/control-plane/triggers/deliver.js +76 -0
  701. package/dist/hearth/control-plane/triggers/envelope.d.ts +29 -0
  702. package/dist/hearth/control-plane/triggers/envelope.js +38 -0
  703. package/dist/hearth/control-plane/types.d.ts +66 -0
  704. package/dist/hearth/control-plane/types.js +1 -0
  705. package/dist/hearth/control-plane/wake.d.ts +80 -0
  706. package/dist/hearth/control-plane/wake.js +312 -0
  707. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
  708. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
  709. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
  710. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
  711. package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
  712. package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
  713. package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
  714. package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
  715. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
  716. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
  717. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
  718. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
  719. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
  720. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
  721. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
  722. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
  723. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
  724. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
  725. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
  726. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
  727. package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
  728. package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
  729. package/dist/hearth/wake-proxy/auth.d.ts +22 -0
  730. package/dist/hearth/wake-proxy/auth.js +128 -0
  731. package/dist/hearth/wake-proxy/config.d.ts +2 -0
  732. package/dist/hearth/wake-proxy/config.js +151 -0
  733. package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
  734. package/dist/hearth/wake-proxy/guest-source.js +130 -0
  735. package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
  736. package/dist/hearth/wake-proxy/hearth-status.js +267 -0
  737. package/dist/hearth/wake-proxy/home.d.ts +61 -0
  738. package/dist/hearth/wake-proxy/home.js +333 -0
  739. package/dist/hearth/wake-proxy/main.d.ts +1 -0
  740. package/dist/hearth/wake-proxy/main.js +134 -0
  741. package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
  742. package/dist/hearth/wake-proxy/model-auth.js +345 -0
  743. package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
  744. package/dist/hearth/wake-proxy/proxy.js +716 -0
  745. package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
  746. package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
  747. package/dist/hearth/wake-proxy/redact.d.ts +1 -0
  748. package/dist/hearth/wake-proxy/redact.js +17 -0
  749. package/dist/hearth/wake-proxy/server.d.ts +11 -0
  750. package/dist/hearth/wake-proxy/server.js +142 -0
  751. package/dist/hearth/wake-proxy/state.d.ts +18 -0
  752. package/dist/hearth/wake-proxy/state.js +342 -0
  753. package/dist/hearth/wake-proxy/types.d.ts +76 -0
  754. package/dist/hearth/wake-proxy/types.js +1 -0
  755. package/dist/index.js +3 -2
  756. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
  757. package/dist/pi-extensions/canvas-context-intro.js +7 -7
  758. package/dist/pi-extensions/canvas-doc-substrate.js +47 -24
  759. package/dist/pi-extensions/canvas-nav.js +16 -9
  760. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  761. package/dist/pi-extensions/canvas-stophook.js +78 -11
  762. package/dist/prompts/view.js +7 -8
  763. package/dist/types.d.ts +39 -20
  764. package/dist/types.js +38 -13
  765. package/dist/web/runtime.js +141 -42
  766. package/dist/web/transport-http.js +7 -5
  767. package/dist/web/transport-stream.d.ts +3 -0
  768. package/dist/web/transport-stream.js +204 -0
  769. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  770. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  771. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  772. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  773. package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
  774. package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
  775. package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
  776. package/dist/web-client/assets/index-BRKxe-hy.js +80 -0
  777. package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
  778. package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
  779. package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
  780. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  781. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  782. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  783. package/dist/web-client/index.html +3 -2
  784. package/package.json +37 -11
  785. package/scripts/postinstall.mjs +8 -0
  786. package/dist/clients/attach/__tests__/action-parity.test.js +0 -48
  787. package/dist/clients/attach/__tests__/clipboard-image.test.js +0 -76
  788. package/dist/clients/attach/__tests__/editor-newline.test.js +0 -82
  789. package/dist/clients/attach/__tests__/git-info.test.js +0 -31
  790. package/dist/clients/attach/__tests__/onrequest-wired.test.js +0 -20
  791. package/dist/clients/attach/__tests__/reconnect-giveup.test.js +0 -30
  792. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +0 -42
  793. package/dist/clients/attach/__tests__/titled-editor.test.js +0 -39
  794. package/dist/commands/memory/__tests__/lint-schema.test.js +0 -52
  795. package/dist/core/__tests__/argv-parser.test.js +0 -217
  796. package/dist/core/__tests__/draw-style.test.js +0 -258
  797. package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
  798. package/dist/core/__tests__/frame-decoder-perf.test.js +0 -224
  799. package/dist/core/__tests__/keystone.test.js +0 -185
  800. package/dist/core/__tests__/listing-completeness.test.js +0 -31
  801. package/dist/core/__tests__/memory-resolver.test.js +0 -108
  802. package/dist/core/__tests__/memory.test.js +0 -60
  803. package/dist/core/__tests__/pane-column.test.js +0 -153
  804. package/dist/core/__tests__/persona-compose.test.js +0 -53
  805. package/dist/core/__tests__/persona-subkind.test.js +0 -65
  806. package/dist/core/__tests__/persona.test.js +0 -107
  807. package/dist/core/__tests__/unknown-path.test.js +0 -58
  808. package/dist/core/canvas/wakeups.d.ts +0 -76
  809. package/dist/core/canvas/wakeups.js +0 -185
  810. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
  811. package/dist/core/skill-sync/builtins.js +0 -112
  812. package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
  813. package/dist/core/skill-sync/claude-plugins.js +0 -71
  814. package/dist/core/skill-sync/engine.d.ts +0 -42
  815. package/dist/core/skill-sync/engine.js +0 -633
  816. package/dist/core/skill-sync/manifest.d.ts +0 -64
  817. package/dist/core/skill-sync/manifest.js +0 -181
  818. package/dist/core/skill-sync/profile.d.ts +0 -76
  819. package/dist/core/skill-sync/profile.js +0 -173
  820. package/dist/core/skill-sync/snapshot.d.ts +0 -57
  821. package/dist/core/skill-sync/snapshot.js +0 -120
  822. package/dist/pi-extensions/canvas-commands.d.ts +0 -37
  823. package/dist/pi-extensions/canvas-commands.js +0 -113
  824. package/dist/pi-extensions/canvas-resume.d.ts +0 -21
  825. package/dist/pi-extensions/canvas-resume.js +0 -83
  826. package/dist/pi-extensions/canvas-view.d.ts +0 -21
  827. package/dist/pi-extensions/canvas-view.js +0 -76
  828. package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
  829. package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
  830. /package/dist/clients/attach/__tests__/{action-parity.test.d.ts → crtr-output-coverage.test.d.ts} +0 -0
  831. /package/dist/clients/attach/__tests__/{clipboard-image.test.d.ts → crtr-output-render.test.d.ts} +0 -0
  832. /package/dist/clients/{attach/__tests__/editor-newline.test.d.ts → web/__tests__/source-cache.test.d.ts} +0 -0
  833. /package/dist/{clients/attach/__tests__/git-info.test.d.ts → commands/sys/__tests__/sync-import.test.d.ts} +0 -0
  834. /package/dist/{clients/attach/__tests__/onrequest-wired.test.d.ts → core/__tests__/boot.test.d.ts} +0 -0
  835. /package/dist/{clients/attach/__tests__/reconnect-giveup.test.d.ts → core/__tests__/broker-preflight.test.d.ts} +0 -0
  836. /package/dist/{clients/attach/__tests__/slash-quit-copy.test.d.ts → core/__tests__/chat-view-reconnect.test.d.ts} +0 -0
  837. /package/dist/{clients/attach/__tests__/titled-editor.test.d.ts → core/__tests__/connection-reconnect-resume.test.d.ts} +0 -0
  838. /package/dist/{commands/memory/__tests__/lint-schema.test.d.ts → core/__tests__/hearth-bootstrap.test.d.ts} +0 -0
  839. /package/dist/core/__tests__/{argv-parser.test.d.ts → preview-registry-sync.test.d.ts} +0 -0
  840. /package/dist/core/__tests__/{draw-style.test.d.ts → prune-to-limit.test.d.ts} +0 -0
  841. /package/dist/core/__tests__/{error-stall-recycle.test.d.ts → stranded-relaunch.test.d.ts} +0 -0
  842. /package/dist/core/__tests__/{frame-decoder-perf.test.d.ts → trigger-migration.test.d.ts} +0 -0
  843. /package/dist/core/__tests__/{keystone.test.d.ts → yield-ensures-daemon.test.d.ts} +0 -0
  844. /package/dist/core/{__tests__/listing-completeness.test.d.ts → hearth/__tests__/model-auth-guest.test.d.ts} +0 -0
  845. /package/dist/core/{__tests__/memory-resolver.test.d.ts → hearth/providers/types.js} +0 -0
  846. /package/dist/core/{__tests__/memory.test.d.ts → hearth/types.js} +0 -0
  847. /package/dist/core/{__tests__/pane-column.test.d.ts → host-exports/__tests__/export-prunes-boot-skill.test.d.ts} +0 -0
  848. /package/dist/core/{__tests__/persona-compose.test.d.ts → view/__tests__/transport-cache.test.d.ts} +0 -0
  849. /package/dist/{core/__tests__/persona-subkind.test.d.ts → daemon/__tests__/predicate-eval.test.d.ts} +0 -0
  850. /package/dist/{core/__tests__/persona.test.d.ts → hearth/control-plane/__tests__/node-message.test.d.ts} +0 -0
  851. /package/dist/{core/__tests__/unknown-path.test.d.ts → hearth/control-plane/__tests__/rate-limit-recurrence.test.d.ts} +0 -0
  852. /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → hearth/control-plane/__tests__/relay-security.test.d.ts} +0 -0
@@ -0,0 +1,2340 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { visibleWidth } from '@earendil-works/pi-tui';
4
+ import { createCrtrBashToolDefinition, isCrtrBashCommand, isCrtrHelpInvocation, renderCrtrOutputText, } from '../crtr-output-render.js';
5
+ import { HELP_ICON, iconForPath } from '../../../core/preview-registry.js';
6
+ const theme = {
7
+ fg(name, text) { return `<${name}>${text}</${name}>`; },
8
+ bg(_name, text) { return text; },
9
+ bold(text) { return `<b>${text}</b>`; },
10
+ };
11
+ const plainTheme = {
12
+ fg(_name, text) { return text; },
13
+ bg(_name, text) { return text; },
14
+ bold(text) { return text; },
15
+ };
16
+ function stripAnsi(text) {
17
+ return text.replace(/\x1b\[[0-9;]*m/g, '');
18
+ }
19
+ test('detects crtr in bash command position, not heredoc body prose', () => {
20
+ assert.equal(isCrtrBashCommand('crtr node new -h'), true);
21
+ assert.equal(isCrtrBashCommand('crtr node inspect snapshot --node abc'), true);
22
+ assert.equal(isCrtrBashCommand('crtr canvas daemon status'), true);
23
+ assert.equal(isCrtrBashCommand('npm test && /usr/local/bin/crouter surface view run inbox'), true);
24
+ assert.equal(isCrtrBashCommand("cat <<'EOF'\nask about crtr node new\nEOF\necho done"), false);
25
+ assert.equal(isCrtrBashCommand('echo crtr node new'), false);
26
+ });
27
+ test('returns a viewer tool definition only for crtr bash commands', () => {
28
+ assert.equal(createCrtrBashToolDefinition({ command: 'echo hi' }), undefined);
29
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
30
+ assert.equal(typeof def.renderCall, 'function');
31
+ const rendered = def.renderCall({ command: 'crtr canvas dashboard' }, theme).render(80).join('\n');
32
+ assert.equal(rendered.trimEnd().split('\n').length, 1);
33
+ assert.match(rendered, new RegExp(iconForPath(['canvas', 'dashboard'])));
34
+ assert.match(rendered, /crtr/);
35
+ assert.match(rendered, /canvas dashboard/);
36
+ assert.doesNotMatch(rendered, /◆/);
37
+ assert.match(def.renderResult({ content: [{ type: 'text', text: '<command name="canvas">' }], details: undefined }, { expanded: true }, theme, {}).render(80).join('\n'), /<accent><b><command/);
38
+ });
39
+ test('styles crtr structural output and status glyphs', () => {
40
+ const rendered = renderCrtrOutputText('<command name="node">\nInput\n● active\nCommand exited with code 1', theme);
41
+ assert.match(rendered, /<accent><b><command/);
42
+ assert.match(rendered, /<toolTitle><b>Input/);
43
+ assert.match(rendered, /<success>● active/);
44
+ assert.match(rendered, /<error>Command exited/);
45
+ });
46
+ // Help output is structural schema text, never a command result, so the
47
+ // viewer detects it from the invocation itself and gives it help-specific
48
+ // collapsed chrome rather than treating it as a normal result preview.
49
+ test('detects a bare -h/--help token in a crtr invocation', () => {
50
+ assert.equal(isCrtrHelpInvocation('crtr node new -h'), true);
51
+ assert.equal(isCrtrHelpInvocation('crtr node inspect snapshot --node abc --help'), true);
52
+ assert.equal(isCrtrHelpInvocation('crtr canvas dashboard'), false);
53
+ assert.equal(isCrtrHelpInvocation('echo crtr node new -h'), false);
54
+ });
55
+ // Help-token detection is scoped to the extracted crtr segment's own tokens,
56
+ // with quote boundaries respected, so unrelated shell siblings and quoted
57
+ // prompt text do not affect the crtr preview.
58
+ test('does not misclassify -h/--help outside the crtr invocation\'s own argv as help', () => {
59
+ assert.equal(isCrtrHelpInvocation('echo -h && crtr canvas snapshot'), false);
60
+ assert.equal(isCrtrHelpInvocation('crtr canvas snapshot && echo --help'), false);
61
+ assert.equal(isCrtrHelpInvocation('crtr node new "write about -h flags"'), false);
62
+ // Sanity: the real thing one token over still trips it.
63
+ assert.equal(isCrtrHelpInvocation('crtr node new -h'), true);
64
+ assert.equal(isCrtrHelpInvocation('crtr node new --help'), true);
65
+ });
66
+ // Realistic leaf schema (renderLeafArgv shape: `name: summary.` then an
67
+ // Input-headed param block) — a stand-in for e.g. `crtr canvas dashboard -h`.
68
+ const LEAF_SCHEMA = 'canvas dashboard: render the canvas as an ASCII subscription tree.\n\n' +
69
+ 'Input\n' +
70
+ ' --root ROOT string, optional. Node id to use as the tree root.\n\n' +
71
+ 'Output (fields carried in the rendered result)\n' +
72
+ ' tree string. Multi-line ASCII tree.\n\n' +
73
+ 'Effects\n' +
74
+ ' Read-only: queries canvas.db.';
75
+ // Realistic branch schema (renderBranch shape: one <command description=...>
76
+ // card with self-closing <subcommand .../> rows) — a stand-in for e.g.
77
+ // `crtr node inspect -h`.
78
+ const BRANCH_SCHEMA = '<command name="node inspect" description="read the canvas graph">\n' +
79
+ '<subcommand name="list" description="list nodes on the canvas" whenToUse="a very long rubric that is not part of the preview at all"/>\n\n' +
80
+ '<subcommand name="show" description="show a node + its spine neighbors" whenToUse="another long rubric"/>\n' +
81
+ '</command>';
82
+ test('collapsed help preview never parrots the call header or dumps the raw schema, and stays within the line cap', () => {
83
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard -h' });
84
+ const collapsed = def
85
+ .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: false }, theme, {})
86
+ .render(80)
87
+ .join('\n');
88
+ // The call header already carries the help glyph header; the collapsed body is
89
+ // JUST the synthesized preview content, no repeated status/help label.
90
+ assert.doesNotMatch(collapsed, new RegExp(`${HELP_ICON} crtr help`));
91
+ assert.match(collapsed, /ctrl\+o to expand/);
92
+ assert.doesNotMatch(collapsed, /<success>✓ crtr done/);
93
+ // No raw schema dump leaking into the body.
94
+ assert.doesNotMatch(collapsed, /<command/);
95
+ // A useful synthesized slice IS present: the leaf's own summary line.
96
+ assert.match(collapsed, /render the canvas as an ASCII subscription/);
97
+ assert.match(collapsed, /Params: --root/);
98
+ assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
99
+ // Full schema remains readable on demand.
100
+ const expanded = def
101
+ .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: true }, theme, {})
102
+ .render(80)
103
+ .join('\n');
104
+ assert.match(expanded, /Node id to use as the tree root/);
105
+ assert.match(expanded, /Read-only: queries canvas\.db/);
106
+ });
107
+ test('collapsed branch/root help synthesizes a subcommand/description preview, not the raw <subcommand> tags', () => {
108
+ const def = createCrtrBashToolDefinition({ command: 'crtr node inspect -h' });
109
+ const collapsed = def
110
+ .renderResult({ content: [{ type: 'text', text: BRANCH_SCHEMA }] }, { expanded: false }, theme, {})
111
+ .render(80)
112
+ .join('\n');
113
+ assert.doesNotMatch(collapsed, /<command/);
114
+ assert.doesNotMatch(collapsed, /<subcommand/);
115
+ assert.doesNotMatch(collapsed, /whenToUse/);
116
+ // Useful preview: branch description plus name — description subcommand rows.
117
+ assert.match(collapsed, /read the canvas graph/);
118
+ assert.match(collapsed, /list.*—.*list nodes on the canvas/s);
119
+ assert.match(collapsed, /show.*—.*show a node \+ its spine neighbors/s);
120
+ assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
121
+ });
122
+ // Realistic root schema (renderRoot shape: a tagline, then one
123
+ // `<command name="X">\n<concept>\nuse when ...` block per subtree, NO
124
+ // description attribute on the tag) — a stand-in for `crtr -h`.
125
+ const ROOT_SCHEMA = 'crtr: agentic runtime.\n\n' +
126
+ '<command name="memory">\n' +
127
+ 'a memory document you read on demand\n' +
128
+ 'use when a task matches stored knowledge or a preference\n' +
129
+ '</command>\n\n' +
130
+ '<command name="node">\n' +
131
+ 'a unit of the agent runtime\n' +
132
+ 'use when delegating work\n' +
133
+ '</command>\n\n' +
134
+ 'Globals\n' +
135
+ ' -h print help\n\n' +
136
+ 'I/O contract: ...';
137
+ test('collapsed root help synthesizes a tagline + name/concept preview, not the raw <command> blocks', () => {
138
+ const def = createCrtrBashToolDefinition({ command: 'crtr -h' });
139
+ const collapsed = def
140
+ .renderResult({ content: [{ type: 'text', text: ROOT_SCHEMA }] }, { expanded: false }, theme, {})
141
+ .render(80)
142
+ .join('\n');
143
+ assert.doesNotMatch(collapsed, /<command/);
144
+ assert.match(collapsed, /crtr: agentic runtime/);
145
+ assert.match(collapsed, /memory.*—.*a memory document you read on demand/s);
146
+ assert.match(collapsed, /node.*—.*a unit of the agent runtime/s);
147
+ assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
148
+ });
149
+ test('a branch help preview with more subcommands than the line cap truncates with an expand hint, never mid-tag', () => {
150
+ const def = createCrtrBashToolDefinition({ command: 'crtr node -h' });
151
+ const manySubs = Array.from({ length: 30 }, (_, i) => `<subcommand name="sub${i}" description="description for subcommand ${i}" whenToUse="long rubric ${i}"/>`).join('\n\n');
152
+ const bigBranchSchema = `<command name="node" description="spawn and navigate canvas nodes">\n${manySubs}\n</command>`;
153
+ const collapsed = def
154
+ .renderResult({ content: [{ type: 'text', text: bigBranchSchema }] }, { expanded: false }, theme, {})
155
+ .render(80)
156
+ .join('\n');
157
+ assert.doesNotMatch(collapsed, /<subcommand/);
158
+ assert.match(collapsed, /ctrl\+o to expand/);
159
+ assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
160
+ });
161
+ // Contract: outer bash redirects are shell plumbing, not crtr argv, so the
162
+ // displayed crtr path and help detection include only the command segment.
163
+ test('a shell redirect on the outer bash call never leaks into the displayed crtr path or help detection', () => {
164
+ const def = createCrtrBashToolDefinition({ command: 'crtr feed -h 2>&1' });
165
+ const rendered = def.renderCall({ command: 'crtr feed -h 2>&1' }, theme).render(80).join('\n');
166
+ assert.match(rendered, new RegExp(HELP_ICON));
167
+ assert.match(rendered, /crtr help/);
168
+ assert.match(rendered, /<b>feed<\/b>/);
169
+ assert.doesNotMatch(rendered, /2>/);
170
+ assert.doesNotMatch(rendered, />&1/);
171
+ assert.equal(isCrtrHelpInvocation('crtr feed -h 2>&1'), true);
172
+ // Other redirect shapes on the outer call, and mid-argv redirects, must
173
+ // never survive into the path either.
174
+ const other = createCrtrBashToolDefinition({ command: 'crtr node new --name foo > /tmp/log 2>&1' });
175
+ const otherRendered = other.renderCall({ command: 'crtr node new --name foo > /tmp/log 2>&1' }, theme).render(80).join('\n');
176
+ assert.match(otherRendered, /node new --name foo/);
177
+ assert.doesNotMatch(otherRendered, /tmp\/log/);
178
+ // Redirect operators glued to their target with NO space (`2>/tmp/out`,
179
+ // `>/tmp/log`) are still shell plumbing and never displayed as path text.
180
+ const noSpace = createCrtrBashToolDefinition({ command: 'crtr feed -h 2>/tmp/out' });
181
+ const noSpaceRendered = noSpace.renderCall({ command: 'crtr feed -h 2>/tmp/out' }, theme).render(80).join('\n');
182
+ assert.match(noSpaceRendered, /<b>feed<\/b>/);
183
+ assert.doesNotMatch(noSpaceRendered, /2>/);
184
+ assert.doesNotMatch(noSpaceRendered, /tmp\/out/);
185
+ const noSpaceMid = createCrtrBashToolDefinition({ command: 'crtr node new --name foo >/tmp/log' });
186
+ const noSpaceMidRendered = noSpaceMid.renderCall({ command: 'crtr node new --name foo >/tmp/log' }, theme).render(80).join('\n');
187
+ assert.match(noSpaceMidRendered, /node new --name foo/);
188
+ assert.doesNotMatch(noSpaceMidRendered, /tmp\/log/);
189
+ });
190
+ // Contract: the call header owns the help glyph label; the collapsed result
191
+ // body contains only synthesized preview content, never a repeated header.
192
+ test('a collapsed help card never shows the help label twice (header + body)', () => {
193
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard -h' });
194
+ const header = def.renderCall({ command: 'crtr canvas dashboard -h' }, theme).render(80).join('\n');
195
+ const body = def
196
+ .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: false }, theme, {})
197
+ .render(80)
198
+ .join('\n');
199
+ const helpLabel = new RegExp(`${HELP_ICON} crtr help`, 'g');
200
+ const headerLabelCount = (header.match(helpLabel) ?? []).length;
201
+ const bodyLabelCount = (body.match(helpLabel) ?? []).length;
202
+ assert.equal(headerLabelCount, 1);
203
+ assert.equal(bodyLabelCount, 0);
204
+ });
205
+ // REQUIREMENT: expanded (ctrl+o) is raw truth — the exact invoked command
206
+ // (redirects and all) plus the exact, unsummarized output, for both help and
207
+ // normal commands.
208
+ test('expanded view shows the original raw command (redirects included) plus the raw output', () => {
209
+ const def = createCrtrBashToolDefinition({ command: 'crtr feed -h 2>&1' });
210
+ const expanded = def
211
+ .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: true }, theme, {})
212
+ .render(80)
213
+ .join('\n');
214
+ assert.match(expanded, /\$ crtr feed -h 2>&1/);
215
+ assert.match(expanded, /render the canvas as an ASCII subscription/);
216
+ assert.match(expanded, /Read-only: queries canvas\.db/);
217
+ // Normal (non-help) command: expanded still carries the raw invocation.
218
+ const normalDef = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
219
+ const normalExpanded = normalDef
220
+ .renderResult({ content: [{ type: 'text', text: 'a normal-sized dashboard body' }] }, { expanded: true }, theme, {})
221
+ .render(80)
222
+ .join('\n');
223
+ assert.match(normalExpanded, /\$ crtr canvas dashboard/);
224
+ assert.match(normalExpanded, /a normal-sized dashboard body/);
225
+ });
226
+ test('a running or failed crtr -h invocation never gets the muted help status', () => {
227
+ const def = createCrtrBashToolDefinition({ command: 'crtr node new -h' });
228
+ const running = def
229
+ .renderResult({ content: [{ type: 'text', text: '' }] }, { expanded: false, isPartial: true }, theme, {})
230
+ .render(80)
231
+ .join('\n');
232
+ assert.match(running, /<warning>⟳ crtr running<\/warning>/);
233
+ });
234
+ // BUG regression (Silas, 2026-07-01): a SUCCESSFUL normal crtr call showed a
235
+ // "✓ crtr done" body status line — pure noise, since the header already names
236
+ // the command and the output is right there. Success (help or normal) carries
237
+ // no body status; only running/failed do.
238
+ test('a successful crtr call shows no "crtr done" body status line', () => {
239
+ const def = createCrtrBashToolDefinition({ command: 'crtr feed' });
240
+ const collapsed = def
241
+ .renderResult({ content: [{ type: 'text', text: 'some feed output' }] }, { expanded: false }, theme, {})
242
+ .render(80)
243
+ .join('\n');
244
+ assert.doesNotMatch(collapsed, /crtr done/);
245
+ assert.match(collapsed, /some feed output/);
246
+ const expanded = def
247
+ .renderResult({ content: [{ type: 'text', text: 'some feed output' }] }, { expanded: true }, theme, {})
248
+ .render(80)
249
+ .join('\n');
250
+ assert.doesNotMatch(expanded, /crtr done/);
251
+ });
252
+ // Command-owned preview: a leaf declares `help.preview.suppressOutput` next
253
+ // to its own definition (e.g. canvas snapshot, node inspect snapshot —
254
+ // machine-readable dumps, not agent glances). The viewer reads this
255
+ // synchronously from the bundle-safe static registry (preview-registry.ts,
256
+ // kept honest by preview-registry-sync.test.ts) keyed by the real leaf path.
257
+ test('command-owned suppressOutput collapses a successful result, never on failure', () => {
258
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas snapshot' });
259
+ const component = def.renderResult({ content: [{ type: 'text', text: '{"nodes":[/* huge */]}' }] }, { expanded: false }, theme, {});
260
+ const collapsed = component.render(80).join('\n');
261
+ // A successful call shows no body status line at all (✓ crtr done is noise).
262
+ assert.doesNotMatch(collapsed, /crtr done/);
263
+ assert.doesNotMatch(collapsed, /huge/);
264
+ // A failed run is never collapsed, even when the leaf opts into suppressOutput.
265
+ const failedComponent = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
266
+ assert.match(failedComponent.render(80).join('\n'), /huge dump on failure too/);
267
+ });
268
+ test('command-owned suppressOutput matches the leaf name even with positional args after it', () => {
269
+ // node inspect snapshot takes a required positional id; the lookup must
270
+ // match the leaf-name prefix and ignore the argument that follows it.
271
+ const def = createCrtrBashToolDefinition({ command: 'crtr node inspect snapshot abc' });
272
+ const component = def.renderResult({ content: [{ type: 'text', text: '{"snapshot":{/* huge */}}' }] }, { expanded: false }, theme, {});
273
+ const collapsed = component.render(80).join('\n');
274
+ assert.doesNotMatch(collapsed, /crtr done/);
275
+ assert.doesNotMatch(collapsed, /huge/);
276
+ });
277
+ // Command-owned preview (Silas, 2026-07-01, option C): a `crtr memory read`
278
+ // collapsed card is a tidy frontmatter glance — `name · kind · scope` identity
279
+ // line (path dropped as noise, follow_up hint dropped) + a short content
280
+ // teaser — not the full doc body, which ctrl+o reveals.
281
+ const MEMORY_READ_OUTPUT = [
282
+ '- name: no-flag-and-defer',
283
+ '- kind: preference',
284
+ '- scope: user',
285
+ '- path: /Users/silasrhyneer/.crouter/memory/no-flag-and-defer.md',
286
+ '',
287
+ '**content:** Fix issues in the same pass you find them — never "flagged for later".',
288
+ '',
289
+ '',
290
+ '**follow_up:** Read the raw file at `path` to view the YAML frontmatter.',
291
+ ].join('\n');
292
+ test('a crtr memory read card previews a tidy frontmatter glance, not the raw block or body dump', () => {
293
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory read no-flag-and-defer' });
294
+ const collapsed = def
295
+ .renderResult({ content: [{ type: 'text', text: MEMORY_READ_OUTPUT }] }, { expanded: false }, theme, {})
296
+ .render(120)
297
+ .join('\n');
298
+ // Curated frontmatter identity line: name · kind · scope, path/follow_up gone.
299
+ assert.match(collapsed, /no-flag-and-defer · preference · user/);
300
+ assert.doesNotMatch(collapsed, /\/Users\//);
301
+ assert.doesNotMatch(collapsed, /follow_up/);
302
+ assert.doesNotMatch(collapsed, /Read the raw file/);
303
+ // A slice of the content is teased, and the raw `**content:**` marker is gone.
304
+ assert.match(collapsed, /Fix issues in the same pass/);
305
+ assert.doesNotMatch(collapsed, /\*\*content:\*\*/);
306
+ // No spurious success status line.
307
+ assert.doesNotMatch(collapsed, /crtr done/);
308
+ assert.match(collapsed, /ctrl\+o to expand/);
309
+ // A long doc body is teased, then clipped with an ellipsis — never dumped whole.
310
+ const longOutput = [
311
+ '- name: pi-frontmatter-rules',
312
+ '- kind: knowledge',
313
+ '- scope: user',
314
+ '- path: /x/pi-frontmatter-rules.md',
315
+ '',
316
+ '**content:** # Title',
317
+ 'line one',
318
+ 'line two',
319
+ 'line three',
320
+ 'line four',
321
+ 'line five',
322
+ ].join('\n');
323
+ const longCollapsed = def
324
+ .renderResult({ content: [{ type: 'text', text: longOutput }] }, { expanded: false }, theme, {})
325
+ .render(120)
326
+ .join('\n');
327
+ assert.match(longCollapsed, /pi-frontmatter-rules · knowledge · user/);
328
+ assert.match(longCollapsed, /…/);
329
+ assert.doesNotMatch(longCollapsed, /line five/);
330
+ });
331
+ // REQUIREMENT: the memory read summarizer must return null (not a blank
332
+ // summary) when its expected metadata/content markers are absent — an error
333
+ // result (missing/ambiguous doc name, etc.) has none of the `- name:`/
334
+ // `**content:**` shape, so it must fall back to the raw output.
335
+ test('a crtr memory read error output falls back to raw, never a blank summary card', () => {
336
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory read nonexistent-doc' });
337
+ const collapsed = def
338
+ .renderResult({ content: [{ type: 'text', text: 'crtr: memory read: no document found matching "nonexistent-doc"' }] }, { expanded: false }, theme, { isError: true })
339
+ .render(120)
340
+ .join('\n');
341
+ assert.match(collapsed, /no document found matching/);
342
+ });
343
+ test('an expanded crtr memory read shows the full raw output, not the frontmatter glance', () => {
344
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory read no-flag-and-defer' });
345
+ const expanded = def
346
+ .renderResult({ content: [{ type: 'text', text: MEMORY_READ_OUTPUT }] }, { expanded: true }, theme, {})
347
+ .render(120)
348
+ .join('\n');
349
+ // Raw truth: the path, the content marker, and the follow_up hint are all present.
350
+ assert.match(expanded, /\$ crtr memory read no-flag-and-defer/);
351
+ assert.match(expanded, /\.crouter\/memory\/no-flag-and-defer\.md/);
352
+ assert.match(expanded, /\*\*follow_up:\*\*/);
353
+ });
354
+ const MEMORY_LINT_CLEAN_OUTPUT = [
355
+ '- checked: 261',
356
+ '- corpora: {"memory_stores":261}',
357
+ '',
358
+ 'Corpus green — zero invalid frontmatter docs.',
359
+ '',
360
+ '0 findings.',
361
+ ].join('\n');
362
+ const MEMORY_LINT_FINDINGS_OUTPUT = [
363
+ 'crtr: memory lint: /tmp/lint-test/.crouter/memory/bad-doc.md: invalid kind: "bogus" (expected knowledge|preference)',
364
+ 'crtr: memory lint: /tmp/lint-test/.crouter/memory/bad-doc2.md: missing `when-and-why-to-read`: one read-routing line',
365
+ '<error code="error">',
366
+ 'memory lint: 2 finding(s) across 229 files',
367
+ 'Next: Fix each doc (quote YAML values containing `: `; use a valid kind/rung/gate), then re-run `crtr memory lint`.',
368
+ '</error>',
369
+ ].join('\n');
370
+ test('a clean crtr memory lint collapses to PASS and docs checked, with semantic tones', () => {
371
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory lint' });
372
+ const collapsed = def
373
+ .renderResult({ content: [{ type: 'text', text: MEMORY_LINT_CLEAN_OUTPUT }] }, { expanded: false }, theme, {})
374
+ .render(120)
375
+ .join('\n');
376
+ assert.match(collapsed, /<success>PASS<\/success>/);
377
+ assert.match(collapsed, /<muted>261 docs checked<\/muted>/);
378
+ assert.doesNotMatch(collapsed, /- checked:/);
379
+ assert.doesNotMatch(collapsed, /- corpora:/);
380
+ assert.doesNotMatch(collapsed, /0 findings\./);
381
+ assert.match(collapsed, /ctrl\+o to expand/);
382
+ const expanded = def
383
+ .renderResult({ content: [{ type: 'text', text: MEMORY_LINT_CLEAN_OUTPUT }] }, { expanded: true }, theme, {})
384
+ .render(120)
385
+ .join('\n');
386
+ assert.match(expanded, /checked: 261/);
387
+ assert.match(expanded, /corpora: \{"memory_stores":261\}/);
388
+ assert.match(expanded, /0 findings\./);
389
+ });
390
+ // REQUIREMENT: a real failed `crtr memory lint` (delivered to the renderer as
391
+ // `{ isError: true }` — see chat-view.ts) must still collapse to the
392
+ // command-owned findings summary, not fall through to the raw failed output;
393
+ // only a command with no summarizer (or a shape neither summarizer branch
394
+ // recognizes) stays raw on failure.
395
+ test('a crtr memory lint with findings collapses to a findings count and warnings even on the real failed-command path', () => {
396
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory lint' });
397
+ const collapsed = def
398
+ .renderResult({ content: [{ type: 'text', text: MEMORY_LINT_FINDINGS_OUTPUT }] }, { expanded: false }, theme, { isError: true })
399
+ .render(120)
400
+ .join('\n');
401
+ assert.match(collapsed, /<error>2 findings<\/error>/);
402
+ assert.match(collapsed, /<warning>[^<]*invalid kind: "bogus"[^<]*<\/warning>/);
403
+ assert.match(collapsed, /<warning>[^<]*missing `when-and-why-to-read`[^<]*<\/warning>/);
404
+ assert.match(collapsed, /<muted>229 docs checked<\/muted>/);
405
+ assert.doesNotMatch(collapsed, /PASS/);
406
+ // No raw error block/XML leaks into the collapsed summary.
407
+ assert.doesNotMatch(collapsed, /code="error"/);
408
+ assert.doesNotMatch(collapsed, /Next: Fix each doc/);
409
+ });
410
+ // Help must look like help at the call header itself, not only once the
411
+ // (possibly collapsed) result renders.
412
+ test('renderCall shows a help-specific header for a -h/--help invocation, no bash chrome', () => {
413
+ const def = createCrtrBashToolDefinition({ command: 'crtr node config -h' });
414
+ const rendered = def.renderCall({ command: 'crtr node config -h' }, theme).render(80).join('\n');
415
+ assert.equal(rendered.trimEnd().split('\n').length, 1);
416
+ assert.match(rendered, new RegExp(HELP_ICON));
417
+ assert.match(rendered, /crtr help/);
418
+ assert.doesNotMatch(rendered, /via bash/);
419
+ assert.doesNotMatch(rendered, /\$ crtr/);
420
+ assert.doesNotMatch(rendered, /-h/);
421
+ assert.match(rendered, /node config/);
422
+ });
423
+ // Table-driven header coverage across every mapped root family (rule 12: real
424
+ // Nerd Font glyphs, never the generic ◆) — includes `memory lint` (the
425
+ // motivating case for the memory-lint summarizer above), not just `canvas`/
426
+ // `node`.
427
+ test('renderCall uses a mapped Nerd Font glyph and one line for every mapped root family, never the generic ◆', () => {
428
+ const cases = [
429
+ { command: 'crtr memory lint', iconTokens: ['memory', 'lint'], path: 'memory lint' },
430
+ { command: 'crtr canvas dashboard', iconTokens: ['canvas', 'dashboard'], path: 'canvas dashboard' },
431
+ { command: 'crtr feed read', iconTokens: ['feed', 'read'], path: 'feed read' },
432
+ { command: 'crtr human ask', iconTokens: ['human', 'ask'], path: 'human ask' },
433
+ { command: 'crtr node config set foo bar', iconTokens: ['node', 'config'], path: 'node config set foo bar' },
434
+ { command: 'crtr pkg plugin list', iconTokens: ['pkg', 'plugin', 'list'], path: 'pkg plugin list' },
435
+ { command: 'crtr push update', iconTokens: ['push', 'update'], path: 'push update' },
436
+ { command: 'crtr search web', iconTokens: ['search', 'web'], path: 'search web' },
437
+ { command: 'crtr surface view list', iconTokens: ['surface', 'view', 'list'], path: 'surface view list' },
438
+ { command: 'crtr sys doctor', iconTokens: ['sys', 'doctor'], path: 'sys doctor' },
439
+ ];
440
+ for (const { command, iconTokens, path } of cases) {
441
+ const def = createCrtrBashToolDefinition({ command });
442
+ // Width 200 (not 80): this test's fake theme wraps each styled span in
443
+ // literal `<tag>...</tag>` text (not real ANSI escapes the wrapper would
444
+ // strip for width purposes), so the visible-width accounting used by the
445
+ // real Text component's word-wrap counts that markup as content width —
446
+ // a generous width keeps every case here well clear of an unrelated wrap.
447
+ const rendered = def.renderCall({ command }, theme).render(200).join('\n');
448
+ assert.equal(rendered.trimEnd().split('\n').length, 1, `expected one line for ${command}`);
449
+ assert.match(rendered, new RegExp(iconForPath(iconTokens)), `expected the ${iconTokens.join(' ')} glyph in ${command}`);
450
+ assert.match(rendered, /crtr/, `expected "crtr" in ${command}`);
451
+ assert.match(rendered, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `expected path "${path}" on the same line`);
452
+ assert.doesNotMatch(rendered, /◆/, `unexpected generic diamond for ${command}`);
453
+ }
454
+ });
455
+ test('renderCall for a normal invocation previews the crtr path, not a raw bash echo', () => {
456
+ const def = createCrtrBashToolDefinition({ command: 'crtr node config set foo bar' });
457
+ const rendered = def.renderCall({ command: 'crtr node config set foo bar' }, theme).render(200).join('\n');
458
+ assert.equal(rendered.trimEnd().split('\n').length, 1);
459
+ assert.match(rendered, new RegExp(iconForPath(['node', 'config'])));
460
+ assert.match(rendered, /crtr/);
461
+ assert.match(rendered, /node config set foo bar/);
462
+ assert.doesNotMatch(rendered, /◆/);
463
+ assert.doesNotMatch(rendered, /via bash/);
464
+ assert.doesNotMatch(rendered, /\$ crtr/);
465
+ });
466
+ test('a leaf that does not declare suppressOutput is never collapsed', () => {
467
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
468
+ const component = def.renderResult({ content: [{ type: 'text', text: 'a normal-sized dashboard body' }] }, { expanded: false }, theme, {});
469
+ assert.match(component.render(80).join('\n'), /a normal-sized dashboard body/);
470
+ });
471
+ test('expanded crtr output clamps long single lines to the terminal width', () => {
472
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
473
+ const longLine = 'x'.repeat(442);
474
+ const lines = def
475
+ .renderResult({ content: [{ type: 'text', text: longLine }] }, { expanded: true }, theme, {})
476
+ .render(70);
477
+ assert.ok(lines.length > 0);
478
+ for (const line of lines) {
479
+ assert.ok(visibleWidth(line) <= 70, `line exceeded width: ${visibleWidth(line)} > 70`);
480
+ }
481
+ });
482
+ test('collapsed crtr output clamps all rendered chrome to the terminal width', () => {
483
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
484
+ const longLine = 'y'.repeat(442);
485
+ const lines = def
486
+ .renderResult({ content: [{ type: 'text', text: longLine }] }, { expanded: false }, theme, {})
487
+ .render(70);
488
+ assert.ok(lines.length > 0);
489
+ for (const line of lines) {
490
+ assert.ok(visibleWidth(line) <= 70, `line exceeded width: ${visibleWidth(line)} > 70`);
491
+ }
492
+ });
493
+ const WIDE_GENERIC_TABLE_OUTPUT = [
494
+ '24 plugins:',
495
+ '',
496
+ '| name | version | description | status |',
497
+ '| --- | --- | --- | --- |',
498
+ ...Array.from({ length: 24 }, (_, i) => `| plugin-${String(i).padStart(2, '0')} | 1.0.${i} | This is a deliberately over-wide description cell that used to wrap the collapsed card mid-cell for row ${i} | ${i % 2 === 0 ? 'enabled' : 'disabled'} |`),
499
+ ].join('\n');
500
+ test('generic collapsed fallback renders wide GFM tables aligned, clipped, and row-capped instead of raw pipe rows', () => {
501
+ const def = createCrtrBashToolDefinition({ command: 'crtr surface view run catalog' });
502
+ const rendered = def
503
+ .renderResult({ content: [{ type: 'text', text: WIDE_GENERIC_TABLE_OUTPUT }] }, { expanded: false }, plainTheme, {})
504
+ .render(58)
505
+ .join('\n');
506
+ assert.doesNotMatch(rendered, /\|/);
507
+ assert.match(rendered, /plugin-00\s+1\.0\.0\s+.*…/);
508
+ assert.match(rendered, /more lines/);
509
+ assert.doesNotMatch(rendered, /plugin-23/);
510
+ assert.ok(rendered.split('\n').length <= 20, `expected <=20 lines, got ${rendered.split('\n').length}`);
511
+ const row0 = rendered.split('\n').find((line) => line.includes('plugin-00')) ?? '';
512
+ const row1 = rendered.split('\n').find((line) => line.includes('plugin-01')) ?? '';
513
+ assert.notEqual(row0, '');
514
+ assert.notEqual(row1, '');
515
+ assert.equal(row0.indexOf('1.0.0'), row1.indexOf('1.0.1'));
516
+ for (const line of rendered.split('\n')) {
517
+ assert.ok(visibleWidth(line) <= 58, `line exceeded width: ${visibleWidth(line)} > 58: ${line}`);
518
+ }
519
+ });
520
+ const HEADERLESS_TABLE_TAIL_OUTPUT = [
521
+ '| mr2iy5tw-a1f5e1f0 | batch-a-implementation | /Users/silasrhyneer/Code/cli/crouter | active |',
522
+ '| mr147q9d-c7681b04 | initial-session-setup | /Users/silasrhyneer/Code/cli/crouter | idle |',
523
+ '| mr2ejzaw-6f70e858 | preview-roadmap | /Users/silasrhyneer/Code/cli/crouter | done |',
524
+ ].join('\n');
525
+ test('generic collapsed fallback aligns a header-less truncated table tail by column count', () => {
526
+ const def = createCrtrBashToolDefinition({ command: 'crtr surface view run catalog' });
527
+ const rendered = def
528
+ .renderResult({ content: [{ type: 'text', text: HEADERLESS_TABLE_TAIL_OUTPUT }] }, { expanded: false }, plainTheme, {})
529
+ .render(64)
530
+ .join('\n');
531
+ assert.doesNotMatch(rendered, /\|/);
532
+ assert.match(rendered, /batch-a-implemen/);
533
+ const rows = rendered.split('\n').filter((line) => line.includes('mr')).map(stripAnsi);
534
+ assert.equal(rows.length, 3);
535
+ assert.equal(rows[0].indexOf('/Users'), rows[1].indexOf('/Users'));
536
+ assert.equal(rows[1].indexOf('/Users'), rows[2].indexOf('/Users'));
537
+ });
538
+ test('generic collapsed fallback renders the pi truncation marker as one muted line', () => {
539
+ const def = createCrtrBashToolDefinition({ command: 'crtr surface view run catalog' });
540
+ const marker = '[Showing lines 951-1000 of 1000 (50.0KB limit). Full output: /tmp/pi-bash-output.log]';
541
+ const rendered = def
542
+ .renderResult({ content: [{ type: 'text', text: `last retained line\n${marker}` }] }, { expanded: false }, theme, {})
543
+ .render(240)
544
+ .join('\n');
545
+ assert.match(rendered, /<muted>\[Showing lines 951-1000 of 1000 \(50\.0KB limit\)\. Full output: \/tmp\/pi-bash-output\.log\]<\/muted>/);
546
+ assert.equal((rendered.match(/Showing lines/g) ?? []).length, 1);
547
+ });
548
+ const NO_EDGE_PIPE_TABLE_OUTPUT = [
549
+ 'name | version | status',
550
+ '--- | --- | ---',
551
+ 'alpha | 1.0.0 | enabled',
552
+ 'beta | 2.1.0 | disabled',
553
+ ].join('\n');
554
+ test('generic collapsed fallback aligns a GFM table whose rows omit both outer pipes', () => {
555
+ const def = createCrtrBashToolDefinition({ command: 'crtr surface view run catalog' });
556
+ const rendered = def
557
+ .renderResult({ content: [{ type: 'text', text: NO_EDGE_PIPE_TABLE_OUTPUT }] }, { expanded: false }, plainTheme, {})
558
+ .render(60)
559
+ .join('\n');
560
+ assert.doesNotMatch(rendered, /\|/);
561
+ assert.doesNotMatch(rendered, /---/);
562
+ const rows = rendered.split('\n').filter((line) => line.trim() !== '');
563
+ const alphaRow = rows.find((line) => line.includes('alpha')) ?? '';
564
+ const betaRow = rows.find((line) => line.includes('beta')) ?? '';
565
+ assert.notEqual(alphaRow, '');
566
+ assert.notEqual(betaRow, '');
567
+ assert.equal(alphaRow.indexOf('1.0.0'), betaRow.indexOf('2.1.0'));
568
+ });
569
+ test('a stray line containing a single literal pipe is not mistaken for a table row', () => {
570
+ const def = createCrtrBashToolDefinition({ command: 'crtr surface view run catalog' });
571
+ const rendered = def
572
+ .renderResult({ content: [{ type: 'text', text: 'warning: use x | y instead' }] }, { expanded: false }, plainTheme, {})
573
+ .render(60)
574
+ .join('\n');
575
+ assert.match(rendered, /warning: use x \| y instead/);
576
+ });
577
+ test('icon lookup uses per-leaf glyphs before family glyphs, with help unchanged', () => {
578
+ assert.equal(iconForPath(['canvas', 'dashboard']), String.fromCodePoint(0xf0e4));
579
+ assert.notEqual(iconForPath(['canvas', 'dashboard']), iconForPath(['canvas']));
580
+ assert.equal(iconForPath(['node', 'inspect', 'list']), String.fromCodePoint(0xf03a));
581
+ assert.notEqual(iconForPath(['node', 'inspect', 'list']), iconForPath(['node']));
582
+ assert.equal(iconForPath(['sys', 'doctor']), String.fromCodePoint(0xf0f1));
583
+ assert.equal(HELP_ICON, String.fromCodePoint(0xf059));
584
+ });
585
+ // ---------------------------------------------------------------------------
586
+ // canvas/node/feed runtime cockpit summaries (first non-memory batch, Silas's
587
+ // 2026-07-01 reframe: collapsed cards are computed status digests, not
588
+ // decorated stdout). Fixtures below are literal renderResult() output shapes
589
+ // (core/render.ts: a `- field: N` scalar bullet, an `N <label>:` count
590
+ // lead-in + GFM table for an array of objects) — not the live command tree,
591
+ // so these stay independent of what actually runs on a real canvas.
592
+ // ---------------------------------------------------------------------------
593
+ const DASHBOARD_OUTPUT = [
594
+ '- nodes: 4',
595
+ '',
596
+ '● root-node [developer/orchestrator] ctx 5k',
597
+ '├─ ✓ child-a ...',
598
+ '└─ ○ child-b ...',
599
+ '',
600
+ '4 rows:',
601
+ '',
602
+ '| node_id | name | status | kind | mode | ctx_tokens | asks | cwd | created |',
603
+ '| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
604
+ '| n1 | root-node | active | developer | orchestrator | 5000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
605
+ '| n2 | child-a | active | developer | base | 12000 | 3 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
606
+ '| n3 | child-b | done | advisor | base | 8000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
607
+ '| n4 | child-c | idle | reviewer | base | 3000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
608
+ ].join('\n');
609
+ test('a crtr canvas dashboard card collapses to a status-count digest, not the raw tree/table', () => {
610
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
611
+ const collapsed = def
612
+ .renderResult({ content: [{ type: 'text', text: DASHBOARD_OUTPUT }] }, { expanded: false }, theme, {})
613
+ .render(120)
614
+ .join('\n');
615
+ assert.match(collapsed, /2 active/);
616
+ assert.match(collapsed, /1 idle/);
617
+ assert.match(collapsed, /1 done/);
618
+ assert.match(collapsed, /<muted>largest ctx: 12k tokens<\/muted>/);
619
+ assert.match(collapsed, /<warning>3 pending asks<\/warning>/);
620
+ assert.doesNotMatch(collapsed, /\| ---/);
621
+ assert.doesNotMatch(collapsed, /node_id/);
622
+ assert.doesNotMatch(collapsed, /crtr done/);
623
+ assert.match(collapsed, /ctrl\+o to expand/);
624
+ const expanded = def
625
+ .renderResult({ content: [{ type: 'text', text: DASHBOARD_OUTPUT }] }, { expanded: true }, theme, {})
626
+ .render(120)
627
+ .join('\n');
628
+ assert.match(expanded, /\$ crtr canvas dashboard/);
629
+ assert.match(expanded, /\| node_id \| name \|/);
630
+ assert.match(expanded, /root-node \[developer\/orchestrator\]/);
631
+ });
632
+ const NODE_LIST_OUTPUT = [
633
+ '4 nodes:',
634
+ '',
635
+ '| node_id | name | status | kind | mode | lifecycle | cwd | created | hanging |',
636
+ '| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
637
+ '| n1 | alpha | active | developer | orchestrator | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
638
+ '| n2 | beta | active | developer | base | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
639
+ '| n3 | gamma | done | advisor | base | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
640
+ '| n4 | delta | idle | reviewer | base | resident | /a | 2026-01-01T00:00:00.000Z | none |',
641
+ ].join('\n');
642
+ test('a crtr node inspect list card collapses to status + kind digests, not the raw table', () => {
643
+ const def = createCrtrBashToolDefinition({ command: 'crtr node inspect list' });
644
+ const collapsed = def
645
+ .renderResult({ content: [{ type: 'text', text: NODE_LIST_OUTPUT }] }, { expanded: false }, theme, {})
646
+ .render(120)
647
+ .join('\n');
648
+ assert.match(collapsed, /2 active/);
649
+ assert.match(collapsed, /1 idle/);
650
+ assert.match(collapsed, /1 done/);
651
+ assert.match(collapsed, /<muted>kinds: developer×2, advisor×1, reviewer×1<\/muted>/);
652
+ assert.doesNotMatch(collapsed, /\| ---/);
653
+ assert.doesNotMatch(collapsed, /node_id/);
654
+ assert.doesNotMatch(collapsed, /crtr done/);
655
+ assert.match(collapsed, /ctrl\+o to expand/);
656
+ });
657
+ test('a crtr canvas attention count card renders a computed health tone, error when pending, success when clear', () => {
658
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention count' });
659
+ const pending = def
660
+ .renderResult({ content: [{ type: 'text', text: '- count: 14' }] }, { expanded: false }, theme, {})
661
+ .render(120)
662
+ .join('\n');
663
+ assert.match(pending, /<error>14 pending asks<\/error>/);
664
+ assert.match(pending, /ctrl\+o to expand/);
665
+ const clear = def
666
+ .renderResult({ content: [{ type: 'text', text: '- count: 0' }] }, { expanded: false }, theme, {})
667
+ .render(120)
668
+ .join('\n');
669
+ assert.match(clear, /<success>PASS · no pending asks<\/success>/);
670
+ assert.doesNotMatch(clear, /0 pending asks/);
671
+ });
672
+ const ATTENTION_LIST_OUTPUT = [
673
+ '- total: 14',
674
+ '',
675
+ '5 items:',
676
+ '',
677
+ '| node_id | name | cwd | count |',
678
+ '| --- | --- | --- | --- |',
679
+ '| mq3geac4-9b947921 | general | /Users/silasrhyneer/Code/alaria-map-site | 7 |',
680
+ '| mqa7c632-24f0e384 | general | /Users/silasrhyneer/Code/cli/crouter | 3 |',
681
+ '| mqa9w9qy-e1cafda1 | general | /Users/silasrhyneer/Code/godot/StarTheory2 | 2 |',
682
+ '| mqt6lz47-09daf181 | developer | /Users/silasrhyneer/Code/godot/StarTheory2-worktrees/escape-pod-selection | 1 |',
683
+ '| mr0v8vxi-580c7572 | general | /Users/silasrhyneer/Code/northlight | 1 |',
684
+ ].join('\n');
685
+ test('a crtr canvas attention list card collapses to total/hottest/digest, not the raw table', () => {
686
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention list' });
687
+ const collapsed = def
688
+ .renderResult({ content: [{ type: 'text', text: ATTENTION_LIST_OUTPUT }] }, { expanded: false }, theme, {})
689
+ .render(120)
690
+ .join('\n');
691
+ assert.match(collapsed, /14 total · 5 cwds/);
692
+ assert.match(collapsed, /<warning>hottest: alaria-map-site \(7\)<\/warning>/);
693
+ assert.match(collapsed, /<muted>other cwds: [^<]*crouter[^<]*<\/muted>/);
694
+ assert.doesNotMatch(collapsed, /\| ---/);
695
+ assert.doesNotMatch(collapsed, /node_id/);
696
+ assert.match(collapsed, /ctrl\+o to expand/);
697
+ const empty = def
698
+ .renderResult({ content: [{ type: 'text', text: ['- total: 0', '', '0 items.'].join('\n') }] }, { expanded: false }, theme, {})
699
+ .render(120)
700
+ .join('\n');
701
+ assert.match(empty, /<success>PASS · no pending asks<\/success>/);
702
+ });
703
+ // Command-owned preview: `node inspect show` inlines a full node meta object
704
+ // (system prompt + every telemetry field) with no useful human-glance slice —
705
+ // same rationale as canvas snapshot / node inspect snapshot, mirrored in the
706
+ // bundle-safe SUPPRESS_OUTPUT_LEAVES registry.
707
+ test('node inspect show is command-owned suppressOutput, never on failure', () => {
708
+ const def = createCrtrBashToolDefinition({ command: 'crtr node inspect show abc' });
709
+ const collapsed = def.renderResult({ content: [{ type: 'text', text: '{"node":{/* huge */}}' }] }, { expanded: false }, theme, {});
710
+ const rendered = collapsed.render(80).join('\n');
711
+ assert.doesNotMatch(rendered, /crtr done/);
712
+ assert.doesNotMatch(rendered, /huge/);
713
+ assert.match(rendered, /suppressed — ctrl\+o to expand/);
714
+ const failed = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
715
+ assert.match(failed.render(80).join('\n'), /huge dump on failure too/);
716
+ });
717
+ // ---------------------------------------------------------------------------
718
+ // Batch B: canvas/node/feed/push runtime cockpit summaries (Silas's
719
+ // 2026-07-01 reframe continued). Fixtures below are literal render() output
720
+ // shapes copied from the real command source (see preview-registry.ts's
721
+ // per-summarizer doc comments for the exact file:line), not the live command
722
+ // tree — these stay independent of what actually runs on a real canvas.
723
+ // ---------------------------------------------------------------------------
724
+ test('a crtr canvas attention map card collapses to a hottest-id digest, PASS when all zero', () => {
725
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention map --view mq1' });
726
+ const collapsed = def
727
+ .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":3,"n3":5}' }] }, { expanded: false }, theme, {})
728
+ .render(120)
729
+ .join('\n');
730
+ assert.match(collapsed, /8 asks across 2 nodes/);
731
+ assert.match(collapsed, /<warning>hottest: n3 \(5\)<\/warning>/);
732
+ const zero = def
733
+ .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":0}' }] }, { expanded: false }, theme, {})
734
+ .render(120)
735
+ .join('\n');
736
+ assert.match(zero, /<success>PASS · no pending asks<\/success>/);
737
+ const zeroExpanded = def
738
+ .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":0}' }] }, { expanded: true }, theme, {})
739
+ .render(120)
740
+ .join('\n');
741
+ assert.match(zeroExpanded, /counts: \{"n1":0,"n2":0\}/);
742
+ const expanded = def
743
+ .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":3,"n3":5}' }] }, { expanded: true }, theme, {})
744
+ .render(120)
745
+ .join('\n');
746
+ assert.match(expanded, /counts: \{"n1":0,"n2":3,"n3":5\}/);
747
+ });
748
+ test('a crtr canvas revive card collapses to a kicked/revived status line, argv-recovered id', () => {
749
+ const kickedDef = createCrtrBashToolDefinition({ command: 'crtr canvas revive mq2jpqz5-87fa626e --now' });
750
+ const kicked = kickedDef
751
+ .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- kicked: true', '', 'Sent SIGTERM to worker-a (mq2jpqz5-87fa626e, pid 456).'].join('\n') }] }, { expanded: false }, theme, {})
752
+ .render(120)
753
+ .join('\n');
754
+ assert.match(kicked, /<warning>kicked — daemon resumes in ~20s<\/warning>/);
755
+ const kickedExpanded = kickedDef
756
+ .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- kicked: true', '', 'Sent SIGTERM to worker-a (mq2jpqz5-87fa626e, pid 456).'].join('\n') }] }, { expanded: true }, theme, {})
757
+ .render(120)
758
+ .join('\n');
759
+ assert.match(kickedExpanded, /Sent SIGTERM to worker-a \(mq2jpqz5-87fa626e, pid 456\)\./);
760
+ const revivedDef = createCrtrBashToolDefinition({ command: 'crtr canvas revive mq2jpqz5-87fa626e' });
761
+ const revived = revivedDef
762
+ .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- resumed: true', '- ready: true'].join('\n') }] }, { expanded: false }, theme, {})
763
+ .render(120)
764
+ .join('\n');
765
+ assert.match(revived, /<success>revived mq2jpqz5-87fa626e<\/success>/);
766
+ assert.doesNotMatch(revived, /not yet attachable/);
767
+ const revivedExpanded = revivedDef
768
+ .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- resumed: true', '- ready: true'].join('\n') }] }, { expanded: true }, theme, {})
769
+ .render(120)
770
+ .join('\n');
771
+ assert.match(revivedExpanded, /session: mysession/);
772
+ const notReady = revivedDef
773
+ .renderResult({ content: [{ type: 'text', text: ['- resumed: false', '- ready: false'].join('\n') }] }, { expanded: false }, theme, {})
774
+ .render(120)
775
+ .join('\n');
776
+ assert.match(notReady, /<warning>not yet attachable \(socket not ready\)<\/warning>/);
777
+ const notReadyExpanded = revivedDef
778
+ .renderResult({ content: [{ type: 'text', text: ['- resumed: false', '- ready: false'].join('\n') }] }, { expanded: true }, theme, {})
779
+ .render(120)
780
+ .join('\n');
781
+ assert.match(notReadyExpanded, /resumed: false/);
782
+ assert.match(notReadyExpanded, /ready: false/);
783
+ // `--all` carries a `- mode:` bullet and is deliberately skipped (null) —
784
+ // falls through to generic truncation, raw `mode` text survives.
785
+ const allPreview = revivedDef
786
+ .renderResult({ content: [{ type: 'text', text: '- mode: preview\n- candidates: mq1' }] }, { expanded: false }, theme, {})
787
+ .render(120)
788
+ .join('\n');
789
+ assert.match(allPreview, /mode: preview/);
790
+ const allPreviewExpanded = revivedDef
791
+ .renderResult({ content: [{ type: 'text', text: '- mode: preview\n- candidates: mq1' }] }, { expanded: true }, theme, {})
792
+ .render(120)
793
+ .join('\n');
794
+ assert.match(allPreviewExpanded, /candidates: mq1/);
795
+ });
796
+ const HISTORY_SEARCH_HITS_OUTPUT = [
797
+ '3 of 10 hits:',
798
+ '',
799
+ '| ref | node | kind | source | ts | snippet |',
800
+ '| --- | --- | --- | --- | --- | --- |',
801
+ '| n1:reports/a.md | alpha (n1) | developer | report:final | 2026-01-01T00:00:00.000Z | did a thing |',
802
+ '| n2:reports/b.md | beta (n2) | developer | doc | 2026-01-02T00:00:00.000Z | did another |',
803
+ '| n3:reports/c.md | gamma (n3) | advisor | report:update | 2026-01-03T00:00:00.000Z | did a third |',
804
+ '',
805
+ '- next_cursor: abc123',
806
+ '- total: 10',
807
+ '',
808
+ 'Read a hit with `canvas history read <ref>`; narrow with `canvas history search -h`.',
809
+ ].join('\n');
810
+ test('a crtr canvas history search card collapses to a hit count + top rows, not the raw table', () => {
811
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas history search foo' });
812
+ const collapsed = def
813
+ .renderResult({ content: [{ type: 'text', text: HISTORY_SEARCH_HITS_OUTPUT }] }, { expanded: false }, theme, {})
814
+ .render(140)
815
+ .join('\n');
816
+ assert.match(collapsed, /10 hits/);
817
+ assert.match(collapsed, /<muted>alpha \(n1\) · developer · 2026-01-01T00:00:00\.000Z<\/muted>/);
818
+ assert.doesNotMatch(collapsed, /\| ref \|/);
819
+ const expanded = def
820
+ .renderResult({ content: [{ type: 'text', text: HISTORY_SEARCH_HITS_OUTPUT }] }, { expanded: true }, theme, {})
821
+ .render(140)
822
+ .join('\n');
823
+ assert.match(expanded, /\| ref \| node \| kind \| source \| ts \| snippet \|/);
824
+ assert.match(expanded, /did a thing/);
825
+ const zero = def
826
+ .renderResult({ content: [{ type: 'text', text: ['0 hits.', '', '- next_cursor: null\n- total: 0', 'Read a hit…'].join('\n\n') }] }, { expanded: false }, theme, {})
827
+ .render(120)
828
+ .join('\n');
829
+ assert.match(zero, /<muted>0 hits<\/muted>/);
830
+ const zeroExpanded = def
831
+ .renderResult({ content: [{ type: 'text', text: ['0 hits.', '', '- next_cursor: null\n- total: 0', 'Read a hit…'].join('\n\n') }] }, { expanded: true }, theme, {})
832
+ .render(120)
833
+ .join('\n');
834
+ assert.match(zeroExpanded, /total: 0/);
835
+ // --full mode shares the same `N of M hits:` lead-in but has no table —
836
+ // deliberately skipped (null), raw survives.
837
+ const full = def
838
+ .renderResult({ content: [{ type: 'text', text: '1 of 1 hits:\n\n### n1:reports/a.md\n- node: alpha (n1) | kind: developer | source: doc | ts: 2026-01-01\n\nfull body here' }] }, { expanded: false }, theme, {})
839
+ .render(120)
840
+ .join('\n');
841
+ assert.match(full, /full body here/);
842
+ const fullExpanded = def
843
+ .renderResult({ content: [{ type: 'text', text: '1 of 1 hits:\n\n### n1:reports/a.md\n- node: alpha (n1) | kind: developer | source: doc | ts: 2026-01-01\n\nfull body here' }] }, { expanded: true }, theme, {})
844
+ .render(120)
845
+ .join('\n');
846
+ assert.match(fullExpanded, /full body here/);
847
+ // Lead-in says 3 displayed hits, but the table only has 2 rows — a
848
+ // truncated/mismatched render is a shape mismatch, not a partial summary:
849
+ // skipped (null), raw survives even collapsed.
850
+ const truncated = def
851
+ .renderResult({
852
+ content: [
853
+ {
854
+ type: 'text',
855
+ text: [
856
+ '3 of 10 hits:',
857
+ '',
858
+ '| ref | node | kind | source | ts | snippet |',
859
+ '| --- | --- | --- | --- | --- | --- |',
860
+ '| n1:reports/a.md | alpha (n1) | developer | report:final | 2026-01-01T00:00:00.000Z | did a thing |',
861
+ '| n2:reports/b.md | beta (n2) | developer | doc | 2026-01-02T00:00:00.000Z | did another |',
862
+ '',
863
+ '- next_cursor: abc123',
864
+ '- total: 10',
865
+ ].join('\n'),
866
+ },
867
+ ],
868
+ }, { expanded: false }, theme, {})
869
+ .render(140)
870
+ .join('\n');
871
+ assert.match(truncated, /3 of 10 hits:/);
872
+ assert.doesNotMatch(truncated, /<muted>alpha \(n1\) · developer/);
873
+ });
874
+ const HISTORY_READ_OUTPUT = [
875
+ 'alpha (n1) | source: report:final | ts: 2026-01-01T00:00:00.000Z | ref: n1:reports/a.md',
876
+ '',
877
+ 'Line one of body.',
878
+ 'Line two of body.',
879
+ 'Line three of body.',
880
+ 'Line four of body.',
881
+ ].join('\n');
882
+ test('a crtr canvas history read card previews an identity line + content teaser', () => {
883
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas history read n1:reports/a.md' });
884
+ const collapsed = def
885
+ .renderResult({ content: [{ type: 'text', text: HISTORY_READ_OUTPUT }] }, { expanded: false }, theme, {})
886
+ .render(120)
887
+ .join('\n');
888
+ assert.match(collapsed, /alpha \(n1\) · report:final · 2026-01-01T00:00:00\.000Z/);
889
+ assert.match(collapsed, /Line one of body\./);
890
+ assert.match(collapsed, /…/);
891
+ assert.doesNotMatch(collapsed, /Line four of body\./);
892
+ const expanded = def
893
+ .renderResult({ content: [{ type: 'text', text: HISTORY_READ_OUTPUT }] }, { expanded: true }, theme, {})
894
+ .render(120)
895
+ .join('\n');
896
+ assert.match(expanded, /Line four of body\./);
897
+ });
898
+ const HISTORY_SHOW_OUTPUT = [
899
+ 'node: alpha (n1)',
900
+ '',
901
+ '4 artifacts:',
902
+ '',
903
+ '| ref | source | ts | detail |',
904
+ '| --- | --- | --- | --- |',
905
+ '| n1:reports/a.md | report:final | 2026-01-01T00:00:00.000Z | Final report (120B) |',
906
+ '| n1:reports/b.md | report:update | 2026-01-02T00:00:00.000Z | Update (80B) |',
907
+ '| n1:docs/design.md | doc | 2026-01-03T00:00:00.000Z | Design doc |',
908
+ '| n1:roadmap.md | roadmap | 2026-01-04T00:00:00.000Z | Roadmap |',
909
+ '',
910
+ 'Read one with `canvas history read <ref>`; reopen the node with `canvas revive n1`.',
911
+ ].join('\n');
912
+ test('a crtr canvas history show card collapses to a node line + type-count digest, not the raw table', () => {
913
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas history show n1' });
914
+ const collapsed = def
915
+ .renderResult({ content: [{ type: 'text', text: HISTORY_SHOW_OUTPUT }] }, { expanded: false }, theme, {})
916
+ .render(120)
917
+ .join('\n');
918
+ assert.match(collapsed, /4 artifacts/);
919
+ assert.match(collapsed, /<muted>reports×2, docs×1, roadmaps×1<\/muted>/);
920
+ assert.doesNotMatch(collapsed, /\| ref \|/);
921
+ const expanded = def
922
+ .renderResult({ content: [{ type: 'text', text: HISTORY_SHOW_OUTPUT }] }, { expanded: true }, theme, {})
923
+ .render(120)
924
+ .join('\n');
925
+ assert.match(expanded, /\| ref \| source \| ts \| detail \|/);
926
+ assert.match(expanded, /Final report {2}\(120B\)/);
927
+ const empty = def
928
+ .renderResult({ content: [{ type: 'text', text: ['node: alpha (n1)', '', '0 artifacts.', '', 'Read one…'].join('\n\n') }] }, { expanded: false }, theme, {})
929
+ .render(120)
930
+ .join('\n');
931
+ assert.match(empty, /<muted>alpha \(n1\) · 0 artifacts<\/muted>/);
932
+ // Lead-in says 4 artifacts, but the table only has 3 rows — a
933
+ // truncated/mismatched render is a shape mismatch, not a partial summary:
934
+ // skipped (null), raw survives even collapsed.
935
+ const truncated = def
936
+ .renderResult({
937
+ content: [
938
+ {
939
+ type: 'text',
940
+ text: [
941
+ 'node: alpha (n1)',
942
+ '',
943
+ '4 artifacts:',
944
+ '',
945
+ '| ref | source | ts | detail |',
946
+ '| --- | --- | --- | --- |',
947
+ '| n1:reports/a.md | report:final | 2026-01-01T00:00:00.000Z | Final report (120B) |',
948
+ '| n1:reports/b.md | report:update | 2026-01-02T00:00:00.000Z | Update (80B) |',
949
+ '| n1:docs/design.md | doc | 2026-01-03T00:00:00.000Z | Design doc |',
950
+ ].join('\n'),
951
+ },
952
+ ],
953
+ }, { expanded: false }, theme, {})
954
+ .render(120)
955
+ .join('\n');
956
+ assert.match(truncated, /4 artifacts:/);
957
+ assert.doesNotMatch(truncated, /<muted>reports×/);
958
+ const emptyExpanded = def
959
+ .renderResult({ content: [{ type: 'text', text: ['node: alpha (n1)', '', '0 artifacts.', '', 'Read one…'].join('\n\n') }] }, { expanded: true }, theme, {})
960
+ .render(120)
961
+ .join('\n');
962
+ assert.match(emptyExpanded, /0 artifacts\./);
963
+ });
964
+ test('crtr canvas daemon start/status/stop cards collapse to a one-line pid status', () => {
965
+ const start = createCrtrBashToolDefinition({ command: 'crtr canvas daemon start' });
966
+ assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<success>started · pid 4242<\/success>/);
967
+ assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: false\n- existing_pid: 99' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<muted>already running · pid 99<\/muted>/);
968
+ assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: false\n- existing_pid: 99' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /existing_pid: 99/);
969
+ const startExpanded = start
970
+ .renderResult({ content: [{ type: 'text', text: '- started: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
971
+ .render(120)
972
+ .join('\n');
973
+ assert.match(startExpanded, /started: true/);
974
+ assert.match(startExpanded, /pid: 4242/);
975
+ const status = createCrtrBashToolDefinition({ command: 'crtr canvas daemon status' });
976
+ assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<success>running · pid 4242<\/success>/);
977
+ assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: false' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<error>not running<\/error>/);
978
+ assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: false' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /running: false/);
979
+ const statusExpanded = status
980
+ .renderResult({ content: [{ type: 'text', text: '- running: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
981
+ .render(120)
982
+ .join('\n');
983
+ assert.match(statusExpanded, /running: true/);
984
+ assert.match(statusExpanded, /pid: 4242/);
985
+ const stop = createCrtrBashToolDefinition({ command: 'crtr canvas daemon stop' });
986
+ assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /stopped · pid 4242/);
987
+ assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: false' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<muted>was not running<\/muted>/);
988
+ assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: false' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /stopped: false/);
989
+ const stopExpanded = stop
990
+ .renderResult({ content: [{ type: 'text', text: '- stopped: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
991
+ .render(120)
992
+ .join('\n');
993
+ assert.match(stopExpanded, /stopped: true/);
994
+ assert.match(stopExpanded, /pid: 4242/);
995
+ });
996
+ test('a crtr canvas issue new card collapses to an issue/note confirmation, not the raw bullets', () => {
997
+ const def = createCrtrBashToolDefinition({ command: 'crtr canvas issue new --type bug' });
998
+ const github = def
999
+ .renderResult({
1000
+ content: [
1001
+ {
1002
+ type: 'text',
1003
+ text: '- created: true\n- target: github\n- type: bug\n- title: Fix the thing\n- url: https://github.com/acme/repo/issues/42',
1004
+ },
1005
+ ],
1006
+ }, { expanded: false }, theme, {})
1007
+ .render(120)
1008
+ .join('\n');
1009
+ assert.match(github, /<success>issue #42<\/success>/);
1010
+ assert.match(github, /<muted>https:\/\/github\.com\/acme\/repo\/issues\/42<\/muted>/);
1011
+ const githubExpanded = def
1012
+ .renderResult({
1013
+ content: [
1014
+ {
1015
+ type: 'text',
1016
+ text: '- created: true\n- target: github\n- type: bug\n- title: Fix the thing\n- url: https://github.com/acme/repo/issues/42',
1017
+ },
1018
+ ],
1019
+ }, { expanded: true }, theme, {})
1020
+ .render(120)
1021
+ .join('\n');
1022
+ assert.match(githubExpanded, /url: https:\/\/github\.com\/acme\/repo\/issues\/42/);
1023
+ const file = def
1024
+ .renderResult({
1025
+ content: [
1026
+ {
1027
+ type: 'text',
1028
+ text: '- created: true\n- target: file\n- type: idea\n- title: Add the thing\n- path: /Users/x/proj/.todos/20260101-add-the-thing.md',
1029
+ },
1030
+ ],
1031
+ }, { expanded: false }, theme, {})
1032
+ .render(120)
1033
+ .join('\n');
1034
+ assert.match(file, /<success>note saved<\/success>/);
1035
+ assert.match(file, /<muted>20260101-add-the-thing\.md<\/muted>/);
1036
+ const fileExpanded = def
1037
+ .renderResult({
1038
+ content: [
1039
+ {
1040
+ type: 'text',
1041
+ text: '- created: true\n- target: file\n- type: idea\n- title: Add the thing\n- path: /Users/x/proj/.todos/20260101-add-the-thing.md',
1042
+ },
1043
+ ],
1044
+ }, { expanded: true }, theme, {})
1045
+ .render(120)
1046
+ .join('\n');
1047
+ assert.match(fileExpanded, /path: \/Users\/x\/proj\/\.todos\/20260101-add-the-thing\.md/);
1048
+ const empty = def
1049
+ .renderResult({ content: [{ type: 'text', text: '- created: false' }] }, { expanded: false }, theme, {})
1050
+ .render(120)
1051
+ .join('\n');
1052
+ assert.match(empty, /<muted>nothing captured<\/muted>/);
1053
+ const emptyExpanded = def
1054
+ .renderResult({ content: [{ type: 'text', text: '- created: false' }] }, { expanded: true }, theme, {})
1055
+ .render(120)
1056
+ .join('\n');
1057
+ assert.match(emptyExpanded, /created: false/);
1058
+ });
1059
+ test('a crtr node new card collapses to a spawned-or-armed status line, not the raw follow_up prose', () => {
1060
+ const def = createCrtrBashToolDefinition({ command: 'crtr node new --kind developer' });
1061
+ const spawned = def
1062
+ .renderResult({ content: [{ type: 'text', text: ['Spawned "worker-a" (mq2jpqz5-87fa626e) — active.', '', 'Independent root spawned — hand it off and move on.'].join('\n') }] }, { expanded: false }, theme, {})
1063
+ .render(120)
1064
+ .join('\n');
1065
+ assert.match(spawned, /<success>spawned worker-a \(mq2jpqz5-87fa626e\) · active<\/success>/);
1066
+ const spawnedExpanded = def
1067
+ .renderResult({ content: [{ type: 'text', text: ['Spawned "worker-a" (mq2jpqz5-87fa626e) — active.', '', 'Independent root spawned — hand it off and move on.'].join('\n') }] }, { expanded: true }, theme, {})
1068
+ .render(120)
1069
+ .join('\n');
1070
+ assert.match(spawnedExpanded, /Independent root spawned — hand it off and move on\./);
1071
+ const armedClock = def
1072
+ .renderResult({
1073
+ content: [
1074
+ {
1075
+ type: 'text',
1076
+ text: ['Armed deferred spawn tr-abc123 — a developer node, fires 2026-07-02T09:00:00.000Z.', '', 'No node exists yet.'].join('\n'),
1077
+ },
1078
+ ],
1079
+ }, { expanded: false }, theme, {})
1080
+ .render(120)
1081
+ .join('\n');
1082
+ assert.match(armedClock, /<accent>trigger armed · fires 2026-07-02T09:00:00\.000Z<\/accent>/);
1083
+ assert.match(armedClock, /<muted>developer · tr-abc123<\/muted>/);
1084
+ const armedClockExpanded = def
1085
+ .renderResult({
1086
+ content: [
1087
+ {
1088
+ type: 'text',
1089
+ text: ['Armed deferred spawn tr-abc123 — a developer node, fires 2026-07-02T09:00:00.000Z.', '', 'No node exists yet.'].join('\n'),
1090
+ },
1091
+ ],
1092
+ }, { expanded: true }, theme, {})
1093
+ .render(120)
1094
+ .join('\n');
1095
+ assert.match(armedClockExpanded, /Armed deferred spawn tr-abc123 — a developer node, fires 2026-07-02T09:00:00\.000Z\./);
1096
+ const armedWhen = def
1097
+ .renderResult({
1098
+ content: [
1099
+ {
1100
+ type: 'text',
1101
+ text: ['Armed deferred spawn tr-xyz — a developer node, next eval 2026-07-01T00:00:00.000Z, timeout 2026-07-08T00:00:00.000Z.', '', 'No node exists yet.'].join('\n'),
1102
+ },
1103
+ ],
1104
+ }, { expanded: false }, theme, {})
1105
+ .render(120)
1106
+ .join('\n');
1107
+ assert.match(armedWhen, /<accent>trigger armed · next eval 2026-07-01T00:00:00\.000Z, timeout 2026-07-08T00:00:00\.000Z<\/accent>/);
1108
+ const armedWhenExpanded = def
1109
+ .renderResult({
1110
+ content: [
1111
+ {
1112
+ type: 'text',
1113
+ text: ['Armed deferred spawn tr-xyz — a developer node, next eval 2026-07-01T00:00:00.000Z, timeout 2026-07-08T00:00:00.000Z.', '', 'No node exists yet.'].join('\n'),
1114
+ },
1115
+ ],
1116
+ }, { expanded: true }, theme, {})
1117
+ .render(200)
1118
+ .join('\n');
1119
+ assert.match(armedWhenExpanded, /Armed deferred spawn tr-xyz — a developer node, next eval 2026-07-01T00:00:00\.000Z, timeout 2026-07-08T00:00:00\.000Z\./);
1120
+ });
1121
+ test('a crtr node msg card collapses to an armed/delivered status line, not the raw guidance prose', () => {
1122
+ const def = createCrtrBashToolDefinition({ command: 'crtr node msg --to mq123' });
1123
+ const armed = def
1124
+ .renderResult({
1125
+ content: [
1126
+ {
1127
+ type: 'text',
1128
+ text: [
1129
+ 'Armed inbox_message trigger tr-1 (every) — fires 2026-07-02T09:00:00.000Z, recurs every 6h, target mq123.',
1130
+ '',
1131
+ 'Message recurrence armed (every 6h); first fire 2026-07-02T09:00:00.000Z. End your turn to go dormant.',
1132
+ ].join('\n'),
1133
+ },
1134
+ ],
1135
+ }, { expanded: false }, theme, {})
1136
+ .render(120)
1137
+ .join('\n');
1138
+ assert.match(armed, /<accent>trigger armed · fires 2026-07-02T09:00:00\.000Z<\/accent>/);
1139
+ assert.match(armed, /<muted>inbox_message · every · → mq123 · tr-1<\/muted>/);
1140
+ const armedExpanded = def
1141
+ .renderResult({
1142
+ content: [
1143
+ {
1144
+ type: 'text',
1145
+ text: [
1146
+ 'Armed inbox_message trigger tr-1 (every) — fires 2026-07-02T09:00:00.000Z, recurs every 6h, target mq123.',
1147
+ '',
1148
+ 'Message recurrence armed (every 6h); first fire 2026-07-02T09:00:00.000Z. End your turn to go dormant.',
1149
+ ].join('\n'),
1150
+ },
1151
+ ],
1152
+ }, { expanded: true }, theme, {})
1153
+ .render(120)
1154
+ .join('\n');
1155
+ assert.match(armedExpanded, /Message recurrence armed \(every 6h\); first fire 2026-07-02T09:00:00\.000Z\. End your turn to go dormant\./);
1156
+ const delivered = def
1157
+ .renderResult({ content: [{ type: 'text', text: 'Delivered.' }] }, { expanded: false }, theme, {})
1158
+ .render(120)
1159
+ .join('\n');
1160
+ assert.match(delivered, /<success>delivered<\/success>/);
1161
+ const deliveredExpanded = def
1162
+ .renderResult({ content: [{ type: 'text', text: 'Delivered.' }] }, { expanded: true }, theme, {})
1163
+ .render(120)
1164
+ .join('\n');
1165
+ assert.match(deliveredExpanded, /Delivered\./);
1166
+ const revived = def
1167
+ .renderResult({ content: [{ type: 'text', text: 'Delivered — the dormant target was revived to receive it.' }] }, { expanded: false }, theme, {})
1168
+ .render(120)
1169
+ .join('\n');
1170
+ assert.match(revived, /<success>delivered · revived<\/success>/);
1171
+ const revivedExpanded = def
1172
+ .renderResult({ content: [{ type: 'text', text: 'Delivered — the dormant target was revived to receive it.' }] }, { expanded: true }, theme, {})
1173
+ .render(120)
1174
+ .join('\n');
1175
+ assert.match(revivedExpanded, /Delivered — the dormant target was revived to receive it\./);
1176
+ const fresh = def
1177
+ .renderResult({ content: [{ type: 'text', text: 'Fresh-revived mq123 now — it resumes a clean window re-reading its roadmap/disk.' }] }, { expanded: false }, theme, {})
1178
+ .render(120)
1179
+ .join('\n');
1180
+ assert.match(fresh, /<success>fresh-revived mq123<\/success>/);
1181
+ const freshExpanded = def
1182
+ .renderResult({ content: [{ type: 'text', text: 'Fresh-revived mq123 now — it resumes a clean window re-reading its roadmap/disk.' }] }, { expanded: true }, theme, {})
1183
+ .render(120)
1184
+ .join('\n');
1185
+ assert.match(freshExpanded, /it resumes a clean window re-reading its roadmap\/disk\./);
1186
+ });
1187
+ test('a crtr node yield card collapses to a refresh-armed status line, not the raw bullets', () => {
1188
+ const def = createCrtrBashToolDefinition({ command: 'crtr node yield' });
1189
+ const withExtras = def
1190
+ .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- promoted: true\n- model: strong\n- will_refresh: true' }] }, { expanded: false }, theme, {})
1191
+ .render(120)
1192
+ .join('\n');
1193
+ assert.match(withExtras, /<accent>yield armed — refreshes on next wake<\/accent>/);
1194
+ assert.match(withExtras, /<muted>promoted → orchestrator · model: strong<\/muted>/);
1195
+ const withExtrasExpanded = def
1196
+ .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- promoted: true\n- model: strong\n- will_refresh: true' }] }, { expanded: true }, theme, {})
1197
+ .render(120)
1198
+ .join('\n');
1199
+ assert.match(withExtrasExpanded, /promoted: true/);
1200
+ assert.match(withExtrasExpanded, /model: strong/);
1201
+ const plain = def
1202
+ .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- will_refresh: true' }] }, { expanded: false }, theme, {})
1203
+ .render(120)
1204
+ .join('\n');
1205
+ assert.match(plain, /<accent>yield armed — refreshes on next wake<\/accent>/);
1206
+ assert.doesNotMatch(plain, /promoted/);
1207
+ const plainExpanded = def
1208
+ .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- will_refresh: true' }] }, { expanded: true }, theme, {})
1209
+ .render(120)
1210
+ .join('\n');
1211
+ assert.match(plainExpanded, /node_id: mq123/);
1212
+ });
1213
+ test('a crtr node lifecycle close card collapses to a cascade digest, not the raw prose sentence', () => {
1214
+ const def = createCrtrBashToolDefinition({ command: 'crtr node lifecycle close --node mq123' });
1215
+ const cascade = def
1216
+ .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 3 node(s) closed, 1 spared (still managed from outside the subtree).' }] }, { expanded: false }, theme, {})
1217
+ .render(120)
1218
+ .join('\n');
1219
+ assert.match(cascade, /<success>closed mq123<\/success>/);
1220
+ assert.match(cascade, /<warning>2 descendants canceled<\/warning>/);
1221
+ assert.match(cascade, /<muted>1 spared \(managed elsewhere\)<\/muted>/);
1222
+ const cascadeExpanded = def
1223
+ .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 3 node(s) closed, 1 spared (still managed from outside the subtree).' }] }, { expanded: true }, theme, {})
1224
+ .render(120)
1225
+ .join('\n');
1226
+ assert.match(cascadeExpanded, /Closed mq123 and its exclusive subtree — 3 node\(s\) closed, 1 spared \(still managed from outside the subtree\)\./);
1227
+ const solo = def
1228
+ .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 1 node(s) closed.' }] }, { expanded: false }, theme, {})
1229
+ .render(120)
1230
+ .join('\n');
1231
+ assert.match(solo, /<success>closed mq123<\/success>/);
1232
+ assert.doesNotMatch(solo, /descendants? canceled/);
1233
+ const soloExpanded = def
1234
+ .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 1 node(s) closed.' }] }, { expanded: true }, theme, {})
1235
+ .render(120)
1236
+ .join('\n');
1237
+ assert.match(soloExpanded, /Closed mq123 and its exclusive subtree — 1 node\(s\) closed\./);
1238
+ });
1239
+ const TRIGGERS_LIST_OUTPUT = [
1240
+ '3 pending trigger(s) in scope self.',
1241
+ '',
1242
+ '| id | owner | target | action | mode | type | next | recur | timeout | label | eval_state | last_eval_at | last_exit | last_stderr | created |',
1243
+ '| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
1244
+ '| tr-1 | self | mq1 | msg | inbox_message | at | 2026-07-02T00:00:00.000Z | | | ping | | | | | 2026-06-01T00:00:00.000Z |',
1245
+ '| tr-2 | self | mq2 | msg | fresh_revive | every | 2026-07-03T00:00:00.000Z | every 6h | | | | | | | 2026-06-01T00:00:00.000Z |',
1246
+ '| tr-3 | self | spawn:developer@/x | new | node_birth | when | 2026-07-01T00:00:00.000Z | | 2026-07-08T00:00:00.000Z | | armed | | | | 2026-06-01T00:00:00.000Z |',
1247
+ '',
1248
+ 'Cancel one with `crtr node triggers cancel <id>`. Fired one-shots are gone; a recurrence shows its NEXT fire/eval, not past slots.',
1249
+ ].join('\n');
1250
+ test('a crtr node triggers list card collapses to a pending-count + top rows, not the raw table', () => {
1251
+ const def = createCrtrBashToolDefinition({ command: 'crtr node triggers list' });
1252
+ const collapsed = def
1253
+ .renderResult({ content: [{ type: 'text', text: TRIGGERS_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1254
+ .render(140)
1255
+ .join('\n');
1256
+ assert.match(collapsed, /3 pending/);
1257
+ assert.match(collapsed, /<muted>at · next 2026-07-02T00:00:00\.000Z · mq1<\/muted>/);
1258
+ assert.match(collapsed, /<muted>when · next 2026-07-01T00:00:00\.000Z · spawn:developer@\/x<\/muted>/);
1259
+ assert.doesNotMatch(collapsed, /\| id \|/);
1260
+ const expanded = def
1261
+ .renderResult({ content: [{ type: 'text', text: TRIGGERS_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1262
+ .render(140)
1263
+ .join('\n');
1264
+ assert.match(expanded, /\| id \| owner \| target \|/);
1265
+ assert.match(expanded, /tr-3 \| self \| spawn:developer@\/x/);
1266
+ const empty = def
1267
+ .renderResult({ content: [{ type: 'text', text: 'No pending triggers in scope self.\n\nCancel one with `crtr node triggers cancel <id>`.' }] }, { expanded: false }, theme, {})
1268
+ .render(120)
1269
+ .join('\n');
1270
+ assert.match(empty, /<muted>none pending<\/muted>/);
1271
+ const emptyExpanded = def
1272
+ .renderResult({ content: [{ type: 'text', text: 'No pending triggers in scope self.\n\nCancel one with `crtr node triggers cancel <id>`.' }] }, { expanded: true }, theme, {})
1273
+ .render(120)
1274
+ .join('\n');
1275
+ assert.match(emptyExpanded, /No pending triggers in scope self\./);
1276
+ });
1277
+ test('a crtr feed read card collapses to a drained-pointer digest, not the raw coalesced sections', () => {
1278
+ const def = createCrtrBashToolDefinition({ command: 'crtr feed read' });
1279
+ const drained = def
1280
+ .renderResult({
1281
+ content: [
1282
+ {
1283
+ type: 'text',
1284
+ text: [
1285
+ "3 unread pointers drained from mq123's inbox.",
1286
+ '',
1287
+ 'From worker-a — 2 updates:',
1288
+ ' [push] did a thing (ref: worker-a:reports/x.md)',
1289
+ ' [push] did another (ref: worker-a:reports/y.md)',
1290
+ '',
1291
+ 'From worker-b — 1 update:',
1292
+ ' [push] finished (ref: worker-b:reports/z.md)',
1293
+ ].join('\n'),
1294
+ },
1295
+ ],
1296
+ }, { expanded: false }, theme, {})
1297
+ .render(140)
1298
+ .join('\n');
1299
+ assert.match(drained, /3 pointers drained from 2 nodes/);
1300
+ assert.match(drained, /<muted>worker-a, worker-b<\/muted>/);
1301
+ assert.doesNotMatch(drained, /ref: worker-a/);
1302
+ const drainedExpanded = def
1303
+ .renderResult({
1304
+ content: [
1305
+ {
1306
+ type: 'text',
1307
+ text: [
1308
+ "3 unread pointers drained from mq123's inbox.",
1309
+ '',
1310
+ 'From worker-a — 2 updates:',
1311
+ ' [push] did a thing (ref: worker-a:reports/x.md)',
1312
+ ' [push] did another (ref: worker-a:reports/y.md)',
1313
+ '',
1314
+ 'From worker-b — 1 update:',
1315
+ ' [push] finished (ref: worker-b:reports/z.md)',
1316
+ ].join('\n'),
1317
+ },
1318
+ ],
1319
+ }, { expanded: true }, theme, {})
1320
+ .render(140)
1321
+ .join('\n');
1322
+ assert.match(drainedExpanded, /ref: worker-a:reports\/x\.md/);
1323
+ const empty = def
1324
+ .renderResult({ content: [{ type: 'text', text: 'No unread pointers for mq123 (unread=0).\n\nYour inbox is empty — nothing has arrived yet.' }] }, { expanded: false }, theme, {})
1325
+ .render(120)
1326
+ .join('\n');
1327
+ assert.match(empty, /<muted>nothing unread<\/muted>/);
1328
+ const emptyExpanded = def
1329
+ .renderResult({ content: [{ type: 'text', text: 'No unread pointers for mq123 (unread=0).\n\nYour inbox is empty — nothing has arrived yet.' }] }, { expanded: true }, theme, {})
1330
+ .render(120)
1331
+ .join('\n');
1332
+ assert.match(emptyExpanded, /Your inbox is empty — nothing has arrived yet\./);
1333
+ });
1334
+ test('a crtr feed message card previews an identity line + body teaser, not the raw label', () => {
1335
+ const def = createCrtrBashToolDefinition({ command: 'crtr feed message m1' });
1336
+ const found = def
1337
+ .renderResult({ content: [{ type: 'text', text: ['Full message m1 (from mq123):', '', 'Line one', 'Line two', 'Line three', 'Line four'].join('\n') }] }, { expanded: false }, theme, {})
1338
+ .render(120)
1339
+ .join('\n');
1340
+ assert.match(found, /m1 · from mq123/);
1341
+ assert.match(found, /Line one/);
1342
+ assert.match(found, /…/);
1343
+ assert.doesNotMatch(found, /Line four/);
1344
+ const foundExpanded = def
1345
+ .renderResult({ content: [{ type: 'text', text: ['Full message m1 (from mq123):', '', 'Line one', 'Line two', 'Line three', 'Line four'].join('\n') }] }, { expanded: true }, theme, {})
1346
+ .render(120)
1347
+ .join('\n');
1348
+ assert.match(foundExpanded, /Line four/);
1349
+ const notFound = def
1350
+ .renderResult({ content: [{ type: 'text', text: "No inbox entry with id m1 in mq123's inbox. Re-read the digest with `crtr feed read --all`." }] }, { expanded: false }, theme, {})
1351
+ .render(120)
1352
+ .join('\n');
1353
+ assert.match(notFound, /No inbox entry with id m1/);
1354
+ const notFoundExpanded = def
1355
+ .renderResult({ content: [{ type: 'text', text: "No inbox entry with id m1 in mq123's inbox. Re-read the digest with `crtr feed read --all`." }] }, { expanded: true }, theme, {})
1356
+ .render(120)
1357
+ .join('\n');
1358
+ assert.match(notFoundExpanded, /Re-read the digest with `crtr feed read --all`\./);
1359
+ });
1360
+ test('a crtr feed peek card collapses to a verdict + working/done/dead counts, not the raw rows', () => {
1361
+ const def = createCrtrBashToolDefinition({ command: 'crtr feed peek' });
1362
+ const zeroClear = def
1363
+ .renderResult({ content: [{ type: 'text', text: 'No nodes below you (0 subscriptions, 0 unread). Nothing will wake you.' }] }, { expanded: false }, theme, {})
1364
+ .render(120)
1365
+ .join('\n');
1366
+ assert.match(zeroClear, /<success>PASS · nothing below<\/success>/);
1367
+ const zeroClearExpanded = def
1368
+ .renderResult({ content: [{ type: 'text', text: 'No nodes below you (0 subscriptions, 0 unread). Nothing will wake you.' }] }, { expanded: true }, theme, {})
1369
+ .render(120)
1370
+ .join('\n');
1371
+ assert.match(zeroClearExpanded, /Nothing will wake you\./);
1372
+ const zeroUnread = def
1373
+ .renderResult({ content: [{ type: 'text', text: 'No nodes below you (0 subscriptions, 4 unread). 4 unread reports sit in your inbox.' }] }, { expanded: false }, theme, {})
1374
+ .render(120)
1375
+ .join('\n');
1376
+ assert.match(zeroUnread, /<warning>0 below · 4 unread<\/warning>/);
1377
+ const zeroUnreadExpanded = def
1378
+ .renderResult({ content: [{ type: 'text', text: 'No nodes below you (0 subscriptions, 4 unread). 4 unread reports sit in your inbox.' }] }, { expanded: true }, theme, {})
1379
+ .render(120)
1380
+ .join('\n');
1381
+ assert.match(zeroUnreadExpanded, /4 unread reports sit in your inbox\./);
1382
+ const withDead = def
1383
+ .renderResult({
1384
+ content: [
1385
+ {
1386
+ type: 'text',
1387
+ text: [
1388
+ '⚠ 1 below you is dead and will NOT wake you.',
1389
+ '',
1390
+ '3 nodes below you — 1 working, 1 done, 1 dead, 2 unread:',
1391
+ '- ● worker-a (mq1) · developer · active · spawned 5m ago · cyc 2 · pushed 1m ago [push]',
1392
+ '- ✓ worker-b (mq2) · developer · done · spawned 10m ago · cyc 3 · pushed 2m ago [push]',
1393
+ '- ✗ worker-c (mq3) · developer · dead · spawned 20m ago · cyc 1 · no push yet',
1394
+ ].join('\n'),
1395
+ },
1396
+ ],
1397
+ }, { expanded: false }, theme, {})
1398
+ .render(140)
1399
+ .join('\n');
1400
+ assert.match(withDead, /1 working · 1 done · 1 dead/);
1401
+ assert.match(withDead, /<warning>1 dead — will not wake you<\/warning>/);
1402
+ assert.doesNotMatch(withDead, /pushed 1m ago/);
1403
+ const withDeadExpanded = def
1404
+ .renderResult({
1405
+ content: [
1406
+ {
1407
+ type: 'text',
1408
+ text: [
1409
+ '⚠ 1 below you is dead and will NOT wake you.',
1410
+ '',
1411
+ '3 nodes below you — 1 working, 1 done, 1 dead, 2 unread:',
1412
+ '- ● worker-a (mq1) · developer · active · spawned 5m ago · cyc 2 · pushed 1m ago [push]',
1413
+ '- ✓ worker-b (mq2) · developer · done · spawned 10m ago · cyc 3 · pushed 2m ago [push]',
1414
+ '- ✗ worker-c (mq3) · developer · dead · spawned 20m ago · cyc 1 · no push yet',
1415
+ ].join('\n'),
1416
+ },
1417
+ ],
1418
+ }, { expanded: true }, theme, {})
1419
+ .render(140)
1420
+ .join('\n');
1421
+ assert.match(withDeadExpanded, /pushed 1m ago/);
1422
+ const noDead = def
1423
+ .renderResult({
1424
+ content: [
1425
+ {
1426
+ type: 'text',
1427
+ text: [
1428
+ 'Safe to yield — 1 worker running async will wake you.',
1429
+ '',
1430
+ '2 nodes below you — 1 working, 1 done, 0 dead, 3 unread:',
1431
+ '- ● worker-a (mq1) · developer · active · spawned 5m ago · cyc 2 · pushed 1m ago [push]',
1432
+ '- ✓ worker-b (mq2) · developer · done · spawned 10m ago · cyc 3 · pushed 2m ago [push]',
1433
+ ].join('\n'),
1434
+ },
1435
+ ],
1436
+ }, { expanded: false }, theme, {})
1437
+ .render(140)
1438
+ .join('\n');
1439
+ assert.match(noDead, /1 working · 1 done/);
1440
+ assert.doesNotMatch(noDead, /0 dead/);
1441
+ assert.match(noDead, /<warning>3 unread<\/warning>/);
1442
+ const noDeadExpanded = def
1443
+ .renderResult({
1444
+ content: [
1445
+ {
1446
+ type: 'text',
1447
+ text: [
1448
+ 'Safe to yield — 1 worker running async will wake you.',
1449
+ '',
1450
+ '2 nodes below you — 1 working, 1 done, 0 dead, 3 unread:',
1451
+ '- ● worker-a (mq1) · developer · active · spawned 5m ago · cyc 2 · pushed 1m ago [push]',
1452
+ '- ✓ worker-b (mq2) · developer · done · spawned 10m ago · cyc 3 · pushed 2m ago [push]',
1453
+ ].join('\n'),
1454
+ },
1455
+ ],
1456
+ }, { expanded: true }, theme, {})
1457
+ .render(140)
1458
+ .join('\n');
1459
+ assert.match(noDeadExpanded, /Safe to yield — 1 worker running async will wake you\./);
1460
+ });
1461
+ test('crtr push urgent/final cards collapse to a one-line outcome + report basename', () => {
1462
+ const urgentDef = createCrtrBashToolDefinition({ command: 'crtr push urgent' });
1463
+ const urgent = urgentDef
1464
+ .renderResult({ content: [{ type: 'text', text: 'Urgent report fanned to 3 subscriber(s) — they are force-woken.\n\n- report: /Users/x/nodes/mq1/reports/20260101-urgent.md' }] }, { expanded: false }, theme, {})
1465
+ .render(120)
1466
+ .join('\n');
1467
+ assert.match(urgent, /<warning>urgent → 3 subscribers woken<\/warning>/);
1468
+ const urgentExpanded = urgentDef
1469
+ .renderResult({ content: [{ type: 'text', text: 'Urgent report fanned to 3 subscriber(s) — they are force-woken.\n\n- report: /Users/x/nodes/mq1/reports/20260101-urgent.md' }] }, { expanded: true }, theme, {})
1470
+ .render(120)
1471
+ .join('\n');
1472
+ assert.match(urgentExpanded, /report: \/Users\/x\/nodes\/mq1\/reports\/20260101-urgent\.md/);
1473
+ const finalDef = createCrtrBashToolDefinition({ command: 'crtr push final' });
1474
+ const final = finalDef
1475
+ .renderResult({
1476
+ content: [
1477
+ {
1478
+ type: 'text',
1479
+ text: 'Result recorded — node finished; its engine shuts down on next stop. Nothing more to do here: STOP your turn immediately. Reply with just "Done." and nothing else.\n\n- report: /Users/x/nodes/mq1/reports/20260101-final.md',
1480
+ },
1481
+ ],
1482
+ }, { expanded: false }, theme, {})
1483
+ .render(120)
1484
+ .join('\n');
1485
+ assert.match(final, /<success>final pushed · node done<\/success>/);
1486
+ assert.match(final, /<muted>20260101-final\.md<\/muted>/);
1487
+ const finalExpanded = finalDef
1488
+ .renderResult({
1489
+ content: [
1490
+ {
1491
+ type: 'text',
1492
+ text: 'Result recorded — node finished; its engine shuts down on next stop. Nothing more to do here: STOP your turn immediately. Reply with just "Done." and nothing else.\n\n- report: /Users/x/nodes/mq1/reports/20260101-final.md',
1493
+ },
1494
+ ],
1495
+ }, { expanded: true }, theme, {})
1496
+ .render(120)
1497
+ .join('\n');
1498
+ assert.match(finalExpanded, /report: \/Users\/x\/nodes\/mq1\/reports\/20260101-final\.md/);
1499
+ });
1500
+ // ---------------------------------------------------------------------------
1501
+ // Batch C — human/memory/pkg summarizers (preview-registry.ts "Batch C")
1502
+ // ---------------------------------------------------------------------------
1503
+ const HUMAN_ASK_DELIVERED_OUTPUT = [
1504
+ '- job_id: mrASK0001',
1505
+ '',
1506
+ '**dir:** /Users/silas/Code/cli/crouter',
1507
+ '',
1508
+ "**follow_up:** The human's answer is delivered to your inbox when they respond — no need to poll.",
1509
+ ].join('\n');
1510
+ const HUMAN_ASK_DRAINED_OUTPUT = [
1511
+ '- job_id: mrASK0002',
1512
+ '',
1513
+ '**dir:** /Users/silas/Code/cli/crouter',
1514
+ '',
1515
+ '**follow_up:** Not in tmux: a human must drain it — run `crtr human inbox` (or re-run inside tmux). The answer then arrives in your inbox.',
1516
+ ].join('\n');
1517
+ test('a crtr human ask card collapses to a posted/drained status + question teaser, not the raw fields', () => {
1518
+ const def = createCrtrBashToolDefinition({ command: 'crtr human ask "Should we ship the preview rollout now?"' });
1519
+ const collapsed = def
1520
+ .renderResult({ content: [{ type: 'text', text: HUMAN_ASK_DELIVERED_OUTPUT }] }, { expanded: false }, theme, {})
1521
+ .render(140)
1522
+ .join('\n');
1523
+ assert.match(collapsed, /<accent>ask posted → waiting on human<\/accent>/);
1524
+ assert.match(collapsed, /<muted>Should we ship the preview rollout now\?<\/muted>/);
1525
+ assert.doesNotMatch(collapsed, /- job_id:/);
1526
+ const expanded = def
1527
+ .renderResult({ content: [{ type: 'text', text: HUMAN_ASK_DELIVERED_OUTPUT }] }, { expanded: true }, theme, {})
1528
+ .render(140)
1529
+ .join('\n');
1530
+ assert.match(expanded, /job_id: mrASK0001/);
1531
+ assert.match(expanded, /no need to poll/);
1532
+ const drained = def
1533
+ .renderResult({ content: [{ type: 'text', text: HUMAN_ASK_DRAINED_OUTPUT }] }, { expanded: false }, theme, {})
1534
+ .render(140)
1535
+ .join('\n');
1536
+ assert.match(drained, /<warning>ask posted — drain via `human inbox`<\/warning>/);
1537
+ assert.match(drained, /<muted>Should we ship the preview rollout now\?<\/muted>/);
1538
+ const drainedExpanded = def
1539
+ .renderResult({ content: [{ type: 'text', text: HUMAN_ASK_DRAINED_OUTPUT }] }, { expanded: true }, theme, {})
1540
+ .render(140)
1541
+ .join('\n');
1542
+ assert.match(drainedExpanded, /Not in tmux: a human must drain it/);
1543
+ });
1544
+ const HUMAN_REVIEW_LIVE_OUTPUT = [
1545
+ '- job_id: mrREV0001',
1546
+ '',
1547
+ '**dir:** /Users/silas/Code/cli/crouter',
1548
+ '',
1549
+ '**output:** /Users/silas/Code/cli/crouter/spec.review.md',
1550
+ '',
1551
+ "**follow_up:** The document is live on the human's screen for anchored, line-by-line review. The pane tracks the file — edit the .md in place and it re-renders on save, so never cancel and re-present just to show a change. Do not poll, verify it opened, or background this call; end your turn. The human reviews on their own time, and their comments (with any line edits) arrive in your inbox and wake you when they submit.",
1552
+ ].join('\n');
1553
+ const HUMAN_REVIEW_DRAINED_OUTPUT = [
1554
+ '- job_id: mrREV0002',
1555
+ '',
1556
+ '**dir:** /Users/silas/Code/cli/crouter',
1557
+ '',
1558
+ '**output:** /Users/silas/Code/cli/crouter/spec.review.md',
1559
+ '',
1560
+ '**follow_up:** Not in tmux: a human must drain it — run `crtr human inbox` (or re-run inside tmux). The answer then arrives in your inbox.',
1561
+ ].join('\n');
1562
+ test('a crtr human review card collapses to a posted/drained status + file basename, not the raw fields', () => {
1563
+ const def = createCrtrBashToolDefinition({ command: 'crtr human review /Users/silas/Code/cli/crouter/spec.md' });
1564
+ const collapsed = def
1565
+ .renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_LIVE_OUTPUT }] }, { expanded: false }, theme, {})
1566
+ .render(140)
1567
+ .join('\n');
1568
+ assert.match(collapsed, /<accent>review posted: spec\.md → waiting on human<\/accent>/);
1569
+ assert.doesNotMatch(collapsed, /- job_id:/);
1570
+ const expanded = def
1571
+ .renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_LIVE_OUTPUT }] }, { expanded: true }, theme, {})
1572
+ .render(140)
1573
+ .join('\n');
1574
+ assert.match(expanded, /job_id: mrREV0001/);
1575
+ assert.match(expanded, /anchored, line-by-line review/);
1576
+ const drained = def
1577
+ .renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_DRAINED_OUTPUT }] }, { expanded: false }, theme, {})
1578
+ .render(140)
1579
+ .join('\n');
1580
+ assert.match(drained, /<warning>review posted: spec\.md — drain via `human inbox`<\/warning>/);
1581
+ });
1582
+ const HUMAN_LIST_OUTPUT = [
1583
+ '- total: 2',
1584
+ '',
1585
+ '2 items:',
1586
+ '',
1587
+ '| id | job_id | dir | title | kind | blocked_since | asking_node_id | asking_node_name | conversation_id | conversation_title | interaction_count |',
1588
+ '| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
1589
+ '| h1 | mrJOB1 | /Users/silas/Code/cli/crouter | Ship the rollout? | decision | 2026-06-30T12:00:00.000Z | mrNODE1 | orchestrator | c1 | Rollout plan | 3 |',
1590
+ '| h2 | mrJOB2 | /Users/silas/Code/cli/crouter | Quick check | | 2026-06-30T12:05:00.000Z | mrNODE2 | reviewer | c2 | Spec review | 1 |',
1591
+ ].join('\n');
1592
+ test('a crtr human list card collapses to a pending count + kind digest, not the raw table', () => {
1593
+ const def = createCrtrBashToolDefinition({ command: 'crtr human list' });
1594
+ const collapsed = def
1595
+ .renderResult({ content: [{ type: 'text', text: HUMAN_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1596
+ .render(140)
1597
+ .join('\n');
1598
+ assert.match(collapsed, /2 pending/);
1599
+ assert.match(collapsed, /<muted>decision×1<\/muted>/);
1600
+ assert.doesNotMatch(collapsed, /\| id \|/);
1601
+ const expanded = def
1602
+ .renderResult({ content: [{ type: 'text', text: HUMAN_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1603
+ .render(140)
1604
+ .join('\n');
1605
+ assert.match(expanded, /\| id \| job_id \| dir \|/);
1606
+ const none = def
1607
+ .renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1608
+ .render(140)
1609
+ .join('\n');
1610
+ assert.match(none, /<success>none pending<\/success>/);
1611
+ // REQUIREMENT: total is nonzero but the page is empty (exhausted
1612
+ // pagination) — must NOT collapse to "none pending" (that's only correct
1613
+ // for a true zero total); fall back to the generic renderer instead.
1614
+ const exhausted = def
1615
+ .renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
1616
+ .render(140)
1617
+ .join('\n');
1618
+ assert.doesNotMatch(exhausted, /none pending/);
1619
+ });
1620
+ const HUMAN_DECK_OUTPUT = [
1621
+ '- id: h1',
1622
+ '- title: Ship the rollout?',
1623
+ '- kind: decision',
1624
+ '- interaction_count: 1',
1625
+ '',
1626
+ '1 interactions:',
1627
+ '',
1628
+ '| id | kind | prompt | options | allow_freetext |',
1629
+ '| --- | --- | --- | --- | --- |',
1630
+ '| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | false |',
1631
+ ].join('\n');
1632
+ const HUMAN_DECK_MULTI_OUTPUT = [
1633
+ '- id: h1',
1634
+ '- title: Ship the rollout?',
1635
+ '- kind: decision',
1636
+ '- interaction_count: 2',
1637
+ '',
1638
+ '2 interactions:',
1639
+ '',
1640
+ '| id | kind | prompt | options | allow_freetext |',
1641
+ '| --- | --- | --- | --- | --- |',
1642
+ '| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | true |',
1643
+ '| i2 | question | Any concerns? | | true |',
1644
+ ].join('\n');
1645
+ test('a crtr human deck card collapses to a prompt teaser + option/freetext digest, not the raw table', () => {
1646
+ const def = createCrtrBashToolDefinition({ command: 'crtr human deck h1' });
1647
+ const collapsed = def
1648
+ .renderResult({ content: [{ type: 'text', text: HUMAN_DECK_OUTPUT }] }, { expanded: false }, theme, {})
1649
+ .render(140)
1650
+ .join('\n');
1651
+ assert.match(collapsed, /Ship it\?/);
1652
+ assert.match(collapsed, /<muted>2 options<\/muted>/);
1653
+ assert.doesNotMatch(collapsed, /\| id \| kind \|/);
1654
+ const expanded = def
1655
+ .renderResult({ content: [{ type: 'text', text: HUMAN_DECK_OUTPUT }] }, { expanded: true }, theme, {})
1656
+ .render(140)
1657
+ .join('\n');
1658
+ assert.match(expanded, /\| id \| kind \| prompt \| options \| allow_freetext \|/);
1659
+ const multi = def
1660
+ .renderResult({ content: [{ type: 'text', text: HUMAN_DECK_MULTI_OUTPUT }] }, { expanded: false }, theme, {})
1661
+ .render(140)
1662
+ .join('\n');
1663
+ assert.match(multi, /Ship it\?/);
1664
+ assert.match(multi, /<muted>2 options · freetext · 2 interactions<\/muted>/);
1665
+ // REQUIREMENT: a malformed/truncated `allow_freetext` cell (neither
1666
+ // `true` nor `false`) must not silently default to "no freetext" — it
1667
+ // must fall back to the generic renderer instead.
1668
+ const malformedFreetext = [
1669
+ '- id: h1',
1670
+ '- title: Ship the rollout?',
1671
+ '- kind: decision',
1672
+ '- interaction_count: 1',
1673
+ '',
1674
+ '1 interactions:',
1675
+ '',
1676
+ '| id | kind | prompt | options | allow_freetext |',
1677
+ '| --- | --- | --- | --- | --- |',
1678
+ '| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | |',
1679
+ ].join('\n');
1680
+ const malformed = def
1681
+ .renderResult({ content: [{ type: 'text', text: malformedFreetext }] }, { expanded: false }, theme, {})
1682
+ .render(140)
1683
+ .join('\n');
1684
+ assert.doesNotMatch(malformed, /<muted>2 options<\/muted>/);
1685
+ });
1686
+ const MEMORY_LIST_OUTPUT = [
1687
+ 'Docs matching your scope are listed below.',
1688
+ '',
1689
+ '3 items:',
1690
+ '',
1691
+ '| name | title | short_form | kind | scope | path | is_dir |',
1692
+ '| --- | --- | --- | --- | --- | --- | --- |',
1693
+ '| ai/x | AI X | AI helpers reference | knowledge | user | /Users/silas/.crouter/memory/ai/x.md | false |',
1694
+ '| taste/y | Taste Y | Taste doc | preference | project | /Users/silas/Code/cli/crouter/.crouter/memory/taste/y.md | false |',
1695
+ '| todos/z | Todo Z | Todo doc | knowledge | node | /path/todos/z.md | false |',
1696
+ ].join('\n');
1697
+ test('a crtr memory list card collapses to a doc count + kind/scope digest, not the raw table', () => {
1698
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory list' });
1699
+ const collapsed = def
1700
+ .renderResult({ content: [{ type: 'text', text: MEMORY_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1701
+ .render(140)
1702
+ .join('\n');
1703
+ assert.match(collapsed, /3 docs/);
1704
+ assert.match(collapsed, /<muted>knowledge×2, preference×1 \/ node×1, project×1, user×1<\/muted>/);
1705
+ assert.doesNotMatch(collapsed, /\| name \|/);
1706
+ const expanded = def
1707
+ .renderResult({ content: [{ type: 'text', text: MEMORY_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1708
+ .render(140)
1709
+ .join('\n');
1710
+ assert.match(expanded, /\| name \| title \| short_form \|/);
1711
+ });
1712
+ const MEMORY_FIND_OUTPUT = [
1713
+ '- query: batch c fixtures',
1714
+ '',
1715
+ 'Matches ranked by relevance.',
1716
+ '',
1717
+ '2 hits:',
1718
+ '',
1719
+ '| name | kind | scope | path | score | short_form |',
1720
+ '| --- | --- | --- | --- | --- | --- |',
1721
+ '| ai/x | knowledge | user | /Users/silas/.crouter/memory/ai/x.md | 0.92 | AI helpers reference |',
1722
+ '| taste/y | preference | project | /Users/silas/Code/cli/crouter/.crouter/memory/taste/y.md | 0.81 | Taste doc |',
1723
+ ].join('\n');
1724
+ const MEMORY_FIND_EMPTY_OUTPUT = ['- query: nonexistent thing', '', 'Matches ranked by relevance.', '', '0 hits.'].join('\n');
1725
+ const MEMORY_FIND_GREP_OUTPUT = [
1726
+ '- query: TODO',
1727
+ '',
1728
+ 'Matches ranked by relevance.',
1729
+ '',
1730
+ '1 hits:',
1731
+ '',
1732
+ '| path | line | text |',
1733
+ '| --- | --- | --- |',
1734
+ '| /Users/silas/Code/cli/crouter/notes/a.md | 12 | TODO: fix this |',
1735
+ ].join('\n');
1736
+ test('a crtr memory find card collapses to a hit count + name digest, not the raw table', () => {
1737
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory find "batch c fixtures"' });
1738
+ const collapsed = def
1739
+ .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_OUTPUT }] }, { expanded: false }, theme, {})
1740
+ .render(140)
1741
+ .join('\n');
1742
+ assert.match(collapsed, /2 hits for "batch c fixtures"/);
1743
+ assert.match(collapsed, /<muted>ai\/x, taste\/y<\/muted>/);
1744
+ assert.doesNotMatch(collapsed, /\| name \|/);
1745
+ const expanded = def
1746
+ .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_OUTPUT }] }, { expanded: true }, theme, {})
1747
+ .render(140)
1748
+ .join('\n');
1749
+ assert.match(expanded, /\| name \| kind \| scope \|/);
1750
+ const empty = def
1751
+ .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_EMPTY_OUTPUT }] }, { expanded: false }, theme, {})
1752
+ .render(140)
1753
+ .join('\n');
1754
+ assert.match(empty, /<muted>0 hits for "nonexistent thing"<\/muted>/);
1755
+ const grepDef = createCrtrBashToolDefinition({ command: 'crtr memory find TODO --grep' });
1756
+ const grepCollapsed = grepDef
1757
+ .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_GREP_OUTPUT }] }, { expanded: false }, theme, {})
1758
+ .render(140)
1759
+ .join('\n');
1760
+ assert.doesNotMatch(grepCollapsed, /hits for "TODO"/);
1761
+ assert.match(grepCollapsed, /TODO: fix this/);
1762
+ });
1763
+ const MEMORY_WRITE_CREATED_OUTPUT = [
1764
+ '- name: taste/batch-c-fixtures',
1765
+ '- kind: knowledge',
1766
+ '- scope: node',
1767
+ '- created: true',
1768
+ '',
1769
+ '**path:** /Users/silas/Code/cli/crouter/.crouter/memory/taste/batch-c-fixtures.md',
1770
+ '',
1771
+ '**follow_up:** Written. Read it back with `crtr memory read taste/batch-c-fixtures`.',
1772
+ ].join('\n');
1773
+ const MEMORY_WRITE_UPDATED_OUTPUT = [
1774
+ '- name: taste/batch-c-fixtures',
1775
+ '- kind: knowledge',
1776
+ '- scope: node',
1777
+ '- created: false',
1778
+ '',
1779
+ '**path:** /Users/silas/Code/cli/crouter/.crouter/memory/taste/batch-c-fixtures.md',
1780
+ '',
1781
+ '**follow_up:** Updated. Read it back with `crtr memory read taste/batch-c-fixtures`.',
1782
+ ].join('\n');
1783
+ test('a crtr memory write card collapses to a wrote/updated status + path, not the raw fields', () => {
1784
+ const def = createCrtrBashToolDefinition({ command: 'crtr memory write --name taste/batch-c-fixtures --kind knowledge' });
1785
+ const collapsed = def
1786
+ .renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_CREATED_OUTPUT }] }, { expanded: false }, theme, {})
1787
+ .render(140)
1788
+ .join('\n');
1789
+ assert.match(collapsed, /<success>wrote taste\/batch-c-fixtures<\/success>/);
1790
+ assert.match(collapsed, /<muted>\/Users\/silas\/Code\/cli\/crouter\/\.crouter\/memory\/taste\/batch-c-fixtures\.md<\/muted>/);
1791
+ assert.doesNotMatch(collapsed, /- created:/);
1792
+ const expanded = def
1793
+ .renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_CREATED_OUTPUT }] }, { expanded: true }, theme, {})
1794
+ .render(140)
1795
+ .join('\n');
1796
+ assert.match(expanded, /created: true/);
1797
+ const updated = def
1798
+ .renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_UPDATED_OUTPUT }] }, { expanded: false }, theme, {})
1799
+ .render(140)
1800
+ .join('\n');
1801
+ assert.match(updated, /<success>updated taste\/batch-c-fixtures<\/success>/);
1802
+ });
1803
+ const PKG_PLUGIN_INSTALL_OUTPUT = ['- name: ai', '- scope: user', '', '/Users/silas/.crouter/plugins/ai'].join('\n');
1804
+ const PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT = ['- name: ai', '- scope: user', '- path: /a'].join('\n');
1805
+ test('a crtr pkg plugin install card collapses to an installed status + scope/path digest, not the raw fields', () => {
1806
+ const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin install ai' });
1807
+ const collapsed = def
1808
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_OUTPUT }] }, { expanded: false }, theme, {})
1809
+ .render(140)
1810
+ .join('\n');
1811
+ assert.match(collapsed, /<success>installed ai<\/success>/);
1812
+ assert.match(collapsed, /<muted>user · \/Users\/silas\/\.crouter\/plugins\/ai<\/muted>/);
1813
+ assert.doesNotMatch(collapsed, /- name:/);
1814
+ const expanded = def
1815
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_OUTPUT }] }, { expanded: true }, theme, {})
1816
+ .render(140)
1817
+ .join('\n');
1818
+ assert.match(expanded, /\/Users\/silas\/\.crouter\/plugins\/ai/);
1819
+ const shortPath = def
1820
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT }] }, { expanded: false }, theme, {})
1821
+ .render(140)
1822
+ .join('\n');
1823
+ assert.match(shortPath, /<muted>user · \/a<\/muted>/);
1824
+ });
1825
+ const PKG_PLUGIN_UPDATE_UPDATED_OUTPUT = ['1 updated:', '', '| name | updated | sha |', '| --- | --- | --- |', '| ai | true | def5678 |'].join('\n');
1826
+ const PKG_PLUGIN_UPDATE_CURRENT_OUTPUT = ['1 updated:', '', '| name | updated | sha |', '| --- | --- | --- |', '| ai | false | abc1234 |'].join('\n');
1827
+ const PKG_PLUGIN_UPDATE_MULTI_OUTPUT = [
1828
+ '3 updated:',
1829
+ '',
1830
+ '| name | updated | sha |',
1831
+ '| --- | --- | --- |',
1832
+ '| ai | true | aaa1111 |',
1833
+ '| beta | false | bbb2222 |',
1834
+ '| gamma | true | ccc3333 |',
1835
+ ].join('\n');
1836
+ test('a crtr pkg plugin update card collapses to an updated/current status + sha, not the raw table', () => {
1837
+ const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin update --name ai' });
1838
+ const collapsed = def
1839
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: false }, theme, {})
1840
+ .render(140)
1841
+ .join('\n');
1842
+ assert.match(collapsed, /<success>updated ai<\/success>/);
1843
+ assert.match(collapsed, /<muted>def5678<\/muted>/);
1844
+ assert.doesNotMatch(collapsed, /\| name \| updated \|/);
1845
+ const expanded = def
1846
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: true }, theme, {})
1847
+ .render(140)
1848
+ .join('\n');
1849
+ assert.match(expanded, /\| name \| updated \| sha \|/);
1850
+ const current = def
1851
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_CURRENT_OUTPUT }] }, { expanded: false }, theme, {})
1852
+ .render(140)
1853
+ .join('\n');
1854
+ assert.match(current, /<muted>ai already current<\/muted>/);
1855
+ assert.match(current, /<muted>abc1234<\/muted>/);
1856
+ const multiDef = createCrtrBashToolDefinition({ command: 'crtr pkg plugin update' });
1857
+ const multi = multiDef
1858
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_MULTI_OUTPUT }] }, { expanded: false }, theme, {})
1859
+ .render(140)
1860
+ .join('\n');
1861
+ assert.match(multi, /2 updated · 1 already current/);
1862
+ const empty = multiDef
1863
+ .renderResult({ content: [{ type: 'text', text: '0 updated.' }] }, { expanded: false }, theme, {})
1864
+ .render(140)
1865
+ .join('\n');
1866
+ assert.match(empty, /<muted>nothing to update<\/muted>/);
1867
+ // REQUIREMENT: a malformed `updated` cell (neither `true` nor `false`) in
1868
+ // a multi-row table must not be silently counted as "already current" —
1869
+ // the multi-row loop must reject exactly like the single-row path does.
1870
+ const malformedUpdate = [
1871
+ '3 updated:',
1872
+ '',
1873
+ '| name | updated | sha |',
1874
+ '| --- | --- | --- |',
1875
+ '| ai | true | aaa1111 |',
1876
+ '| beta | maybe | bbb2222 |',
1877
+ '| gamma | false | ccc3333 |',
1878
+ ].join('\n');
1879
+ const malformed = multiDef
1880
+ .renderResult({ content: [{ type: 'text', text: malformedUpdate }] }, { expanded: false }, theme, {})
1881
+ .render(140)
1882
+ .join('\n');
1883
+ assert.doesNotMatch(malformed, /already current/);
1884
+ });
1885
+ const PKG_PLUGIN_LIST_OUTPUT = [
1886
+ '- total: 3',
1887
+ '',
1888
+ '3 items:',
1889
+ '',
1890
+ '| name | scope | version | enabled | source_marketplace | path |',
1891
+ '| --- | --- | --- | --- | --- | --- |',
1892
+ '| ai | user | 0.7.1 | true | crouter-official-marketplace | /Users/silas/.crouter/plugins/ai |',
1893
+ '| beta | user | 1.0.0 | false | crouter-official-marketplace | /Users/silas/.crouter/plugins/beta |',
1894
+ '| gamma | project | 2.3.0 | true | custom-market | /Users/silas/Code/cli/crouter/.crouter/plugins/gamma |',
1895
+ ].join('\n');
1896
+ test('a crtr pkg plugin list card collapses to a plugin/enabled count + name digest, not the raw table', () => {
1897
+ const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin list' });
1898
+ const collapsed = def
1899
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1900
+ .render(140)
1901
+ .join('\n');
1902
+ assert.match(collapsed, /3 plugins · 2 enabled/);
1903
+ assert.match(collapsed, /<muted>ai, beta, gamma<\/muted>/);
1904
+ assert.doesNotMatch(collapsed, /\| name \| scope \|/);
1905
+ const expanded = def
1906
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1907
+ .render(140)
1908
+ .join('\n');
1909
+ assert.match(expanded, /\| name \| scope \| version \|/);
1910
+ const empty = def
1911
+ .renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1912
+ .render(140)
1913
+ .join('\n');
1914
+ assert.match(empty, /<muted>0 plugins<\/muted>/);
1915
+ // REQUIREMENT: nonzero total with an empty page (exhausted pagination)
1916
+ // must NOT collapse to "0 plugins" — fall back to the generic renderer.
1917
+ const exhausted = def
1918
+ .renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
1919
+ .render(140)
1920
+ .join('\n');
1921
+ assert.doesNotMatch(exhausted, /0 plugins/);
1922
+ });
1923
+ const PKG_PLUGIN_SHOW_OUTPUT = [
1924
+ '- name: ai',
1925
+ '- scope: user',
1926
+ '- path: /Users/silas/.crouter/plugins/ai',
1927
+ '- enabled: true',
1928
+ '- manifest: {"name":"ai","version":"0.7.1","description":"AI helpers"}',
1929
+ '',
1930
+ '2 docs:',
1931
+ '',
1932
+ '| name | path |',
1933
+ '| --- | --- |',
1934
+ '| ai/x | /Users/silas/.crouter/plugins/ai/memory/ai/x.md |',
1935
+ '| ai/y | /Users/silas/.crouter/plugins/ai/memory/ai/y.md |',
1936
+ ].join('\n');
1937
+ const PKG_PLUGIN_SHOW_DISABLED_OUTPUT = [
1938
+ '- name: beta',
1939
+ '- scope: user',
1940
+ '- path: /Users/silas/.crouter/plugins/beta',
1941
+ '- enabled: false',
1942
+ '- manifest: {"name":"beta","version":"1.0.0","description":"Beta helpers"}',
1943
+ '',
1944
+ '0 docs.',
1945
+ ].join('\n');
1946
+ test('a crtr pkg plugin show card collapses to a name@version + enabled status + doc count, not the raw fields', () => {
1947
+ const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin show ai' });
1948
+ const collapsed = def
1949
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_OUTPUT }] }, { expanded: false }, theme, {})
1950
+ .render(140)
1951
+ .join('\n');
1952
+ assert.match(collapsed, /<success>ai@0\.7\.1 · enabled<\/success>/);
1953
+ assert.match(collapsed, /<muted>2 substrate docs<\/muted>/);
1954
+ assert.doesNotMatch(collapsed, /\| name \| path \|/);
1955
+ const expanded = def
1956
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_OUTPUT }] }, { expanded: true }, theme, {})
1957
+ .render(140)
1958
+ .join('\n');
1959
+ assert.match(expanded, /\| name \| path \|/);
1960
+ const disabledDef = createCrtrBashToolDefinition({ command: 'crtr pkg plugin show beta' });
1961
+ const disabled = disabledDef
1962
+ .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_DISABLED_OUTPUT }] }, { expanded: false }, theme, {})
1963
+ .render(140)
1964
+ .join('\n');
1965
+ assert.match(disabled, /<muted>beta@1\.0\.0 · disabled<\/muted>/);
1966
+ assert.match(disabled, /<muted>0 substrate docs<\/muted>/);
1967
+ });
1968
+ const PKG_MARKET_LIST_OUTPUT = [
1969
+ '- total: 2',
1970
+ '',
1971
+ '2 items:',
1972
+ '',
1973
+ '| name | scope | url | ref | path |',
1974
+ '| --- | --- | --- | --- | --- |',
1975
+ '| crouter-official-marketplace | user | https://github.com/crouton-labs/crouter-official-marketplace | main | /Users/silas/.crouter/marketplaces/crouter-official-marketplace |',
1976
+ '| custom-market | project | https://github.com/example/custom-market | main | /Users/silas/Code/cli/crouter/.crouter/marketplaces/custom-market |',
1977
+ ].join('\n');
1978
+ test('a crtr pkg market list card collapses to a marketplace count + name digest, not the raw table', () => {
1979
+ const def = createCrtrBashToolDefinition({ command: 'crtr pkg market list' });
1980
+ const collapsed = def
1981
+ .renderResult({ content: [{ type: 'text', text: PKG_MARKET_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1982
+ .render(140)
1983
+ .join('\n');
1984
+ assert.match(collapsed, /2 marketplaces/);
1985
+ assert.match(collapsed, /<muted>crouter-official-marketplace, custom-market<\/muted>/);
1986
+ assert.doesNotMatch(collapsed, /\| name \| scope \|/);
1987
+ const expanded = def
1988
+ .renderResult({ content: [{ type: 'text', text: PKG_MARKET_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1989
+ .render(140)
1990
+ .join('\n');
1991
+ assert.match(expanded, /\| name \| scope \| url \|/);
1992
+ const empty = def
1993
+ .renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1994
+ .render(140)
1995
+ .join('\n');
1996
+ assert.match(empty, /<muted>0 marketplaces<\/muted>/);
1997
+ // REQUIREMENT: nonzero total with an empty page (exhausted pagination)
1998
+ // must NOT collapse to "0 marketplaces" — fall back to the generic renderer.
1999
+ const exhausted = def
2000
+ .renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
2001
+ .render(140)
2002
+ .join('\n');
2003
+ assert.doesNotMatch(exhausted, /0 marketplaces/);
2004
+ });
2005
+ const PKG_MARKET_BROWSE_ROWS = Array.from({ length: 11 }, (_, i) => {
2006
+ const n = i + 1;
2007
+ return `| p${n} | main | github | 0.${n}.0 | Plugin ${n} | tag${n} | false | |`;
2008
+ });
2009
+ const PKG_MARKET_BROWSE_OUTPUT = [
2010
+ '- marketplace: crouter-official-marketplace',
2011
+ '- total: 11',
2012
+ '',
2013
+ '11 items:',
2014
+ '',
2015
+ '| name | ref | source | version | description | keywords | installed | installed_scope |',
2016
+ '| --- | --- | --- | --- | --- | --- | --- | --- |',
2017
+ ...PKG_MARKET_BROWSE_ROWS,
2018
+ ].join('\n');
2019
+ test('a crtr pkg market browse card collapses to a plugin count in <market> + capped name digest, not the raw table', () => {
2020
+ const def = createCrtrBashToolDefinition({ command: 'crtr pkg market browse crouter-official-marketplace' });
2021
+ const collapsed = def
2022
+ .renderResult({ content: [{ type: 'text', text: PKG_MARKET_BROWSE_OUTPUT }] }, { expanded: false }, theme, {})
2023
+ .render(140)
2024
+ .join('\n');
2025
+ assert.match(collapsed, /11 plugins in crouter-official-marketplace/);
2026
+ assert.match(collapsed, /<muted>p1, p2, p3, p4, p5, p6, p7, p8, … 3 more<\/muted>/);
2027
+ assert.doesNotMatch(collapsed, /\| name \| ref \|/);
2028
+ const expanded = def
2029
+ .renderResult({ content: [{ type: 'text', text: PKG_MARKET_BROWSE_OUTPUT }] }, { expanded: true }, theme, {})
2030
+ .render(140)
2031
+ .join('\n');
2032
+ assert.match(expanded, /\| name \| ref \| source \|/);
2033
+ const empty = def
2034
+ .renderResult({ content: [{ type: 'text', text: '- marketplace: crouter-official-marketplace\n- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
2035
+ .render(140)
2036
+ .join('\n');
2037
+ assert.match(empty, /<muted>0 plugins in crouter-official-marketplace<\/muted>/);
2038
+ // REQUIREMENT: nonzero total with an empty page (exhausted pagination)
2039
+ // must NOT collapse to "0 plugins in <market>" — fall back to generic.
2040
+ const exhausted = def
2041
+ .renderResult({ content: [{ type: 'text', text: '- marketplace: crouter-official-marketplace\n- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
2042
+ .render(140)
2043
+ .join('\n');
2044
+ assert.doesNotMatch(exhausted, /0 plugins in crouter-official-marketplace/);
2045
+ });
2046
+ // ---------------------------------------------------------------------------
2047
+ // Batch D — search/sys/surface summarizers + sys sysprompt suppression.
2048
+ // ---------------------------------------------------------------------------
2049
+ test('sys sysprompt is command-owned suppressOutput, never on failure', () => {
2050
+ const def = createCrtrBashToolDefinition({ command: 'crtr sys sysprompt abc' });
2051
+ const component = def.renderResult({ content: [{ type: 'text', text: '- node_id: abc\n- captured_at: 2026-07-01T00:00:00.000Z\n- chars: 40000\n\n# System prompt\n...(huge prompt dump)...' }] }, { expanded: false }, theme, {});
2052
+ const collapsed = component.render(80).join('\n');
2053
+ assert.doesNotMatch(collapsed, /huge prompt dump/);
2054
+ assert.match(collapsed, /suppressed — ctrl\+o to expand/);
2055
+ const failedComponent = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
2056
+ assert.match(failedComponent.render(80).join('\n'), /huge dump on failure too/);
2057
+ });
2058
+ const SEARCH_WEB_OUTPUT = [
2059
+ '2 results for "test query":',
2060
+ '',
2061
+ '### 1. First Result Title',
2062
+ 'https://example.com/page1 · 2024-01-15 · Jane Doe',
2063
+ '- highlight one text here',
2064
+ '- highlight two text here',
2065
+ '',
2066
+ '### 2. Second Result Title',
2067
+ 'https://other.org/page2',
2068
+ '- another highlight',
2069
+ '',
2070
+ 'Fetch full content for selected result URLs with the contents leaf after checking its schema. No good hits? Broaden the query, drop domain filters, or try --type deep.',
2071
+ ].join('\n');
2072
+ test('a crtr search web card collapses to a result count + title — domain digest, not the raw sections', () => {
2073
+ const def = createCrtrBashToolDefinition({ command: 'crtr search web "test query"' });
2074
+ const collapsed = def
2075
+ .renderResult({ content: [{ type: 'text', text: SEARCH_WEB_OUTPUT }] }, { expanded: false }, theme, {})
2076
+ .render(140)
2077
+ .join('\n');
2078
+ assert.match(collapsed, /2 results/);
2079
+ assert.match(collapsed, /<muted>First Result Title — example\.com<\/muted>/);
2080
+ assert.match(collapsed, /<muted>Second Result Title — other\.org<\/muted>/);
2081
+ assert.doesNotMatch(collapsed, /### 1\./);
2082
+ const expanded = def
2083
+ .renderResult({ content: [{ type: 'text', text: SEARCH_WEB_OUTPUT }] }, { expanded: true }, theme, {})
2084
+ .render(140)
2085
+ .join('\n');
2086
+ assert.match(expanded, /### 1\. First Result Title/);
2087
+ assert.match(expanded, /### 2\. Second Result Title/);
2088
+ const empty = def
2089
+ .renderResult({ content: [{ type: 'text', text: 'No results for "empty query".\n\nFetch full content for selected result URLs with the contents leaf after checking its schema. No good hits? Broaden the query, drop domain filters, or try --type deep.' }] }, { expanded: false }, theme, {})
2090
+ .render(140)
2091
+ .join('\n');
2092
+ assert.match(empty, /<muted>0 results for "empty query"<\/muted>/);
2093
+ });
2094
+ const SEARCH_ANSWER_OUTPUT = [
2095
+ 'The answer is 42, according to the sources below.',
2096
+ '',
2097
+ 'Sources (2):',
2098
+ '1. Deep Thought Paper — https://example.com/paper',
2099
+ '2. Hitchhiker Wiki — https://wiki.example.org/guide',
2100
+ '',
2101
+ 'Read any cited source in full with the contents leaf after checking its schema. Want raw ranked results instead of a synthesized answer? Use the web leaf.',
2102
+ ].join('\n');
2103
+ test('a crtr search answer card collapses to the answer teaser + citation count, not the raw sources list', () => {
2104
+ const def = createCrtrBashToolDefinition({ command: 'crtr search answer "what is the answer"' });
2105
+ const collapsed = def
2106
+ .renderResult({ content: [{ type: 'text', text: SEARCH_ANSWER_OUTPUT }] }, { expanded: false }, theme, {})
2107
+ .render(140)
2108
+ .join('\n');
2109
+ assert.match(collapsed, /The answer is 42, according to the sources below\./);
2110
+ assert.match(collapsed, /<muted>2 citations<\/muted>/);
2111
+ assert.doesNotMatch(collapsed, /Sources \(2\):/);
2112
+ const expanded = def
2113
+ .renderResult({ content: [{ type: 'text', text: SEARCH_ANSWER_OUTPUT }] }, { expanded: true }, theme, {})
2114
+ .render(140)
2115
+ .join('\n');
2116
+ assert.match(expanded, /Sources \(2\):/);
2117
+ assert.match(expanded, /Deep Thought Paper/);
2118
+ const noAnswer = def
2119
+ .renderResult({ content: [{ type: 'text', text: 'No answer was returned.\n\nRead any cited source in full with the contents leaf after checking its schema. Want raw ranked results instead of a synthesized answer? Use the web leaf.' }] }, { expanded: false }, theme, {})
2120
+ .render(140)
2121
+ .join('\n');
2122
+ assert.match(noAnswer, /<muted>no answer returned<\/muted>/);
2123
+ });
2124
+ const SEARCH_CONTENTS_OUTPUT = [
2125
+ 'Extracted 2 URLs:',
2126
+ '',
2127
+ '### 1. Example Page',
2128
+ 'https://example.com/a',
2129
+ 'Some highlight text about the page contents that is reasonably long for testing chars count.',
2130
+ '',
2131
+ '### 2. Another Page',
2132
+ 'https://example.org/b',
2133
+ '- bullet one',
2134
+ '- bullet two',
2135
+ '',
2136
+ 'Stale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.',
2137
+ ].join('\n');
2138
+ test('a crtr search contents card collapses to a per-URL title + char-count digest, not the raw sections', () => {
2139
+ const def = createCrtrBashToolDefinition({ command: 'crtr search contents https://example.com/a,https://example.org/b' });
2140
+ const collapsed = def
2141
+ .renderResult({ content: [{ type: 'text', text: SEARCH_CONTENTS_OUTPUT }] }, { expanded: false }, theme, {})
2142
+ .render(140)
2143
+ .join('\n');
2144
+ assert.match(collapsed, /extracted 2 URLs/);
2145
+ assert.match(collapsed, /<muted>Example Page · \d+ chars<\/muted>/);
2146
+ assert.match(collapsed, /<muted>Another Page · \d+ chars<\/muted>/);
2147
+ assert.doesNotMatch(collapsed, /### 1\./);
2148
+ const expanded = def
2149
+ .renderResult({ content: [{ type: 'text', text: SEARCH_CONTENTS_OUTPUT }] }, { expanded: true }, theme, {})
2150
+ .render(140)
2151
+ .join('\n');
2152
+ assert.match(expanded, /### 1\. Example Page/);
2153
+ const failed = def
2154
+ .renderResult({
2155
+ content: [{
2156
+ type: 'text',
2157
+ text: [
2158
+ 'No content extracted.',
2159
+ 'Failed (2):',
2160
+ '- https://dead.example.com — 404 Not Found',
2161
+ '- https://slow.example.com — timeout',
2162
+ '',
2163
+ 'Stale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.',
2164
+ ].join('\n'),
2165
+ }],
2166
+ }, { expanded: false }, theme, {})
2167
+ .render(140)
2168
+ .join('\n');
2169
+ assert.match(failed, /<error>0 extracted · 2 failed<\/error>/);
2170
+ const nothingAtAll = def
2171
+ .renderResult({ content: [{ type: 'text', text: 'No content extracted.\n\nStale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.' }] }, { expanded: false }, theme, {})
2172
+ .render(140)
2173
+ .join('\n');
2174
+ assert.match(nothingAtAll, /<muted>no content extracted<\/muted>/);
2175
+ });
2176
+ const SYS_SETUP_OUTPUT = [
2177
+ 'crtr sys setup (non-interactive)',
2178
+ '',
2179
+ 'Packages:',
2180
+ '- pi-crtr-extensions — crtr companion extensions. (installed)',
2181
+ '- pi-mode-switch — Alt+M mode cycling. (installed)',
2182
+ '- pi-claude-oauth-adapter — OAuth adapter. (not installed)',
2183
+ '',
2184
+ 'System deps:',
2185
+ '- tmux: ✓ installed',
2186
+ '- nvim: brew install neovim',
2187
+ '',
2188
+ '- Exa key: write to /Users/example/.crouter/exa.key or set EXA_API_KEY',
2189
+ 'Run `crtr sys setup` in a TTY to open the wizard.',
2190
+ ].join('\n');
2191
+ test('a crtr sys setup card collapses to an ok/pending step digest, not the raw sections', () => {
2192
+ const def = createCrtrBashToolDefinition({ command: 'crtr sys setup' });
2193
+ const collapsed = def
2194
+ .renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT }] }, { expanded: false }, theme, {})
2195
+ .render(140)
2196
+ .join('\n');
2197
+ assert.match(collapsed, /<warning>3 ok · 2 pending<\/warning>/);
2198
+ assert.match(collapsed, /<muted>pi-claude-oauth-adapter, nvim<\/muted>/);
2199
+ assert.doesNotMatch(collapsed, /Packages:/);
2200
+ const expanded = def
2201
+ .renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT }] }, { expanded: true }, theme, {})
2202
+ .render(140)
2203
+ .join('\n');
2204
+ assert.match(expanded, /Packages:/);
2205
+ assert.match(expanded, /System deps:/);
2206
+ const allOk = def
2207
+ .renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT.replace('(not installed)', '(installed)').replace('brew install neovim', '✓ installed') }] }, { expanded: false }, theme, {})
2208
+ .render(140)
2209
+ .join('\n');
2210
+ assert.match(allOk, /<success>5 steps ok<\/success>/);
2211
+ });
2212
+ const SYS_DOCTOR_PASS_OUTPUT = [
2213
+ '- ok: true',
2214
+ '- failed: 0',
2215
+ '',
2216
+ '2 checks:',
2217
+ '',
2218
+ '| scope | name | status | message | remediation |',
2219
+ '| --- | --- | --- | --- | --- |',
2220
+ '| user | plugin:ai:dir | pass | directory exists | |',
2221
+ '| user | plugin:ai:manifest | pass | manifest valid | |',
2222
+ ].join('\n');
2223
+ const SYS_DOCTOR_FAIL_OUTPUT = [
2224
+ '- ok: false',
2225
+ '- failed: 1',
2226
+ '',
2227
+ '2 checks:',
2228
+ '',
2229
+ '| scope | name | status | message | remediation |',
2230
+ '| --- | --- | --- | --- | --- |',
2231
+ '| user | plugin:ai:dir | pass | directory exists | |',
2232
+ '| user | plugin:beta:dir | fail | directory missing: /path/to/beta | |',
2233
+ ].join('\n');
2234
+ test('a crtr sys doctor card collapses to PASS or an issue digest, not the raw checks table', () => {
2235
+ const def = createCrtrBashToolDefinition({ command: 'crtr sys doctor' });
2236
+ const pass = def
2237
+ .renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_PASS_OUTPUT }] }, { expanded: false }, theme, {})
2238
+ .render(140)
2239
+ .join('\n');
2240
+ assert.match(pass, /<success>PASS · 2 checks<\/success>/);
2241
+ assert.doesNotMatch(pass, /\| scope \| name \|/);
2242
+ const fail = def
2243
+ .renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_FAIL_OUTPUT }] }, { expanded: false }, theme, {})
2244
+ .render(140)
2245
+ .join('\n');
2246
+ assert.match(fail, /<error>1 issue<\/error>/);
2247
+ assert.match(fail, /<warning>plugin:beta:dir: directory missing: \/path\/to\/beta<\/warning>/);
2248
+ const expanded = def
2249
+ .renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_FAIL_OUTPUT }] }, { expanded: true }, theme, {})
2250
+ .render(140)
2251
+ .join('\n');
2252
+ assert.match(expanded, /\| scope \| name \| status \|/);
2253
+ });
2254
+ const SYS_UPDATE_CHECK_OUTPUT = [
2255
+ '- up_to_date: false',
2256
+ '',
2257
+ '2 updates:',
2258
+ '',
2259
+ '| name | kind | current | latest | up_to_date | unreachable |',
2260
+ '| --- | --- | --- | --- | --- | --- |',
2261
+ '| @crouton-kit/crouter | self | 0.3.34 | 0.3.35 | false | false |',
2262
+ '| some-marketplace | marketplace | abc123 | abc123 | true | false |',
2263
+ ].join('\n');
2264
+ test('a crtr sys update --check card collapses to an outdated/checked digest, not the raw table', () => {
2265
+ const def = createCrtrBashToolDefinition({ command: 'crtr sys update --check' });
2266
+ const collapsed = def
2267
+ .renderResult({ content: [{ type: 'text', text: SYS_UPDATE_CHECK_OUTPUT }] }, { expanded: false }, theme, {})
2268
+ .render(140)
2269
+ .join('\n');
2270
+ assert.match(collapsed, /<warning>1 outdated · 2 checked<\/warning>/);
2271
+ assert.match(collapsed, /<muted>@crouton-kit\/crouter: 0\.3\.34 → 0\.3\.35<\/muted>/);
2272
+ assert.doesNotMatch(collapsed, /\| name \| kind \|/);
2273
+ const expanded = def
2274
+ .renderResult({ content: [{ type: 'text', text: SYS_UPDATE_CHECK_OUTPUT }] }, { expanded: true }, theme, {})
2275
+ .render(140)
2276
+ .join('\n');
2277
+ assert.match(expanded, /\| name \| kind \| current \|/);
2278
+ const upToDate = def
2279
+ .renderResult({ content: [{ type: 'text', text: '- up_to_date: true\n\n1 updates:\n\n| name | kind | current | latest | up_to_date | unreachable |\n| --- | --- | --- | --- | --- | --- |\n| a | self | 1.0.0 | 1.0.0 | true | false |' }] }, { expanded: false }, theme, {})
2280
+ .render(140)
2281
+ .join('\n');
2282
+ assert.match(upToDate, /<success>PASS · 1 up to date<\/success>/);
2283
+ const applyDone = def
2284
+ .renderResult({ content: [{ type: 'text', text: '- target: all\n- status: done' }] }, { expanded: false }, theme, {})
2285
+ .render(140)
2286
+ .join('\n');
2287
+ assert.match(applyDone, /<success>updated · all<\/success>/);
2288
+ const applyFailed = def
2289
+ .renderResult({ content: [{ type: 'text', text: '- target: self\n- status: failed\n- error: network unreachable' }] }, { expanded: false }, theme, {})
2290
+ .render(140)
2291
+ .join('\n');
2292
+ assert.match(applyFailed, /<error>update failed · self<\/error>/);
2293
+ assert.match(applyFailed, /<muted>network unreachable<\/muted>/);
2294
+ });
2295
+ test('a crtr sys feedback card collapses to a filed/saved-locally pointer, not the raw fields', () => {
2296
+ const def = createCrtrBashToolDefinition({ command: 'crtr sys feedback "crtr crashed"' });
2297
+ const filed = def
2298
+ .renderResult({ content: [{ type: 'text', text: '- status: filed\n- url: https://github.com/crouton-labs/crouter/issues/123' }] }, { expanded: false }, theme, {})
2299
+ .render(140)
2300
+ .join('\n');
2301
+ assert.match(filed, /<success>issue filed<\/success>/);
2302
+ assert.match(filed, /<muted>https:\/\/github\.com\/crouton-labs\/crouter\/issues\/123<\/muted>/);
2303
+ const savedText = '- status: saved-locally\n- path: /Users/example/.crouter/feedback/2026-07-01T00-00-00-000Z.md\n- error: gh not available: not found';
2304
+ const saved = def
2305
+ .renderResult({ content: [{ type: 'text', text: savedText }] }, { expanded: false }, theme, {})
2306
+ .render(140)
2307
+ .join('\n');
2308
+ assert.match(saved, /<warning>saved locally — not filed<\/warning>/);
2309
+ assert.match(saved, /<muted>2026-07-01T00-00-00-000Z\.md<\/muted>/);
2310
+ const expanded = def
2311
+ .renderResult({ content: [{ type: 'text', text: savedText }] }, { expanded: true }, theme, {})
2312
+ .render(140)
2313
+ .join('\n');
2314
+ assert.match(expanded, /gh not available: not found/);
2315
+ });
2316
+ const SURFACE_VIEW_LIST_OUTPUT = [
2317
+ 'canvas Canvas [builtin] Live agent graph — who is working, who is blocked',
2318
+ 'chat Chat [builtin] Portable chat-as-a-view core',
2319
+ 'workspace-sidebar Workspace [builtin] Left rail for `crtr workspace` — this graph + other agents in this cwd',
2320
+ ].join('\n');
2321
+ test('a crtr surface view list card collapses to a view count + id digest, not the raw rows', () => {
2322
+ const def = createCrtrBashToolDefinition({ command: 'crtr surface view list' });
2323
+ const collapsed = def
2324
+ .renderResult({ content: [{ type: 'text', text: SURFACE_VIEW_LIST_OUTPUT }] }, { expanded: false }, theme, {})
2325
+ .render(140)
2326
+ .join('\n');
2327
+ assert.match(collapsed, /3 views/);
2328
+ assert.match(collapsed, /<muted>canvas, chat, workspace-sidebar<\/muted>/);
2329
+ assert.doesNotMatch(collapsed, /Live agent graph/);
2330
+ const expanded = def
2331
+ .renderResult({ content: [{ type: 'text', text: SURFACE_VIEW_LIST_OUTPUT }] }, { expanded: true }, theme, {})
2332
+ .render(140)
2333
+ .join('\n');
2334
+ assert.match(expanded, /Live agent graph/);
2335
+ const empty = def
2336
+ .renderResult({ content: [{ type: 'text', text: 'No views found. Scaffold one with the view new leaf.' }] }, { expanded: false }, theme, {})
2337
+ .render(140)
2338
+ .join('\n');
2339
+ assert.match(empty, /No views found/);
2340
+ });