@crouton-kit/crouter 0.3.33 → 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.
- package/README.md +2 -2
- package/dist/build-root.js +3 -6
- package/dist/builtin-memory/crouter-development/marketplaces.md +6 -6
- package/dist/builtin-memory/crouter-development/plugins.md +13 -13
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
- package/dist/builtin-personas/developer/PERSONA.md +1 -1
- package/dist/builtin-personas/orchestration-kernel.md +1 -1
- package/dist/builtin-personas/waiting.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/crtr-help-gate.test.ts +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +663 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +52 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +418 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crouter-help.ts +87 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/filters.json +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +174 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-help-gate.ts +408 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +442 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +119 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/nested-context.ts +327 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +574 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +260 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +40 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/package.json +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.js +1 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +479 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/LICENSE +13 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/README.md +172 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/bin.mjs +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/composer.js +219 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/Document.js +335 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/doc/directives.js +176 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/errors.js +57 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/index.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/log.js +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/cst.js +98 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/parse/parser.js +975 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/public-api.js +102 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/tags.js +96 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/util.js +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/dist/visit.js +233 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/index.js +5 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/browser/package.json +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/cli.d.ts +8 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/cli.mjs +201 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/composer.d.ts +63 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/composer.js +224 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/Document.d.ts +141 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/Document.js +337 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/directives.d.ts +49 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/doc/directives.js +178 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/errors.d.ts +21 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/errors.js +62 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/index.d.ts +25 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/index.js +50 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/log.d.ts +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/log.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Alias.js +118 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/options.d.ts +350 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst.d.ts +109 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/cst.js +112 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/parser.d.ts +84 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/parse/parser.js +980 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/public-api.d.ts +44 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/public-api.js +107 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/tags.d.ts +48 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/tags.js +99 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/types.d.ts +92 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/test-events.d.ts +4 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/test-events.js +134 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/util.d.ts +16 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/util.js +28 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/visit.d.ts +102 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/dist/visit.js +236 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/package.json +97 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/node_modules/yaml/util.js +2 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +3385 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/package.json +25 -0
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +64 -0
- package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +40 -0
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +422 -0
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +13 -0
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +55 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +2340 -0
- package/dist/clients/attach/attach-cmd.js +826 -754
- package/dist/clients/attach/auth-pickers.js +2 -2
- package/dist/clients/attach/canvas-panels.js +1 -1
- package/dist/clients/attach/chat-view.d.ts +3 -2
- package/dist/clients/attach/chat-view.js +30 -10
- package/dist/clients/attach/clipboard-image.js +1 -1
- package/dist/clients/attach/clipboard-text.js +1 -1
- package/dist/clients/attach/config-load.js +1 -1
- package/dist/clients/attach/crtr-output-render.d.ts +48 -0
- package/dist/clients/attach/crtr-output-render.js +646 -0
- package/dist/clients/attach/extension-dialogs.js +1 -1
- package/dist/clients/attach/graph-overlay.js +3 -3
- package/dist/clients/attach/input-controller.js +1 -1
- package/dist/clients/attach/pickers.js +1 -1
- package/dist/clients/attach/slash-commands.js +5 -6
- package/dist/clients/attach/titled-editor.d.ts +4 -0
- package/dist/clients/attach/titled-editor.js +13 -1
- package/dist/clients/attach/view-socket.js +1 -1
- package/dist/clients/web/__tests__/source-cache.test.js +5 -5
- package/dist/clients/web/server.js +29 -5
- package/dist/clients/web/source-cache.js +3 -3
- package/dist/clients/web/web-cmd.js +7 -7
- package/dist/commands/canvas-issue.js +1 -1
- package/dist/commands/canvas-prune.js +24 -2
- package/dist/commands/canvas-snapshot.js +3 -0
- package/dist/commands/canvas-tmux-spread.js +3 -3
- package/dist/commands/human.js +1 -1
- package/dist/commands/memory/lint.js +15 -8
- package/dist/commands/memory/origin.d.ts +1 -0
- package/dist/commands/memory/origin.js +88 -0
- package/dist/commands/memory/shared.d.ts +8 -0
- package/dist/commands/memory/shared.js +22 -1
- package/dist/commands/memory/write.js +17 -12
- package/dist/commands/memory.js +4 -3
- package/dist/commands/node-context.js +4 -4
- package/dist/commands/node-snapshot.js +4 -1
- package/dist/commands/node.d.ts +2 -2
- package/dist/commands/node.js +814 -606
- package/dist/commands/pkg/market-inspect.d.ts +2 -1
- package/dist/commands/pkg/market-inspect.js +14 -32
- package/dist/commands/pkg/market-manage.d.ts +3 -1
- package/dist/commands/pkg/market-manage.js +58 -154
- package/dist/commands/pkg/market.js +7 -7
- package/dist/commands/pkg/plugin-inspect.d.ts +2 -1
- package/dist/commands/pkg/plugin-inspect.js +8 -24
- package/dist/commands/pkg/plugin-manage.d.ts +5 -1
- package/dist/commands/pkg/plugin-manage.js +245 -171
- package/dist/commands/pkg/plugin.js +6 -6
- package/dist/commands/pkg.js +5 -5
- package/dist/commands/revive.js +5 -3
- package/dist/commands/search/answer.js +1 -1
- package/dist/commands/search/contents.js +1 -1
- package/dist/commands/search/web.js +2 -2
- package/dist/commands/search.js +1 -1
- package/dist/commands/surface.d.ts +2 -0
- package/dist/commands/surface.js +43 -0
- package/dist/commands/sys/promptstudio.js +1 -1
- package/dist/commands/sys/settings.js +2 -2
- package/dist/commands/sys/setup-core.d.ts +88 -0
- package/dist/commands/sys/setup-core.js +352 -0
- package/dist/commands/sys/setup.d.ts +2 -0
- package/dist/commands/sys/setup.js +516 -0
- package/dist/commands/sys/sysprompt.js +3 -0
- package/dist/commands/sys.js +3 -2
- package/dist/commands/view-cycle.js +5 -5
- package/dist/commands/view-list.js +3 -3
- package/dist/commands/view-new.js +5 -5
- package/dist/commands/view-pick.js +6 -6
- package/dist/commands/view-run.js +14 -14
- package/dist/commands/view.js +3 -3
- package/dist/commands/workspace.js +10 -10
- package/dist/core/__tests__/boot.test.js +140 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +6 -27
- package/dist/core/__tests__/daemon-boot.test.js +22 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +1 -1
- package/dist/core/__tests__/full/broker-attach-limits.test.js +1 -1
- package/dist/core/__tests__/full/broker-attach-stream.test.js +1 -1
- package/dist/core/__tests__/full/broker-crash-teardown.test.js +10 -5
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +1 -1
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +1 -1
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +1 -1
- package/dist/core/__tests__/full/cascade-close.test.js +31 -18
- package/dist/core/__tests__/full/detach-focus.test.js +2 -2
- package/dist/core/__tests__/hearth-bootstrap.test.js +136 -0
- package/dist/core/__tests__/helpers/harness.js +1 -1
- package/dist/core/__tests__/live-mutation-verbs.test.js +10 -10
- package/dist/core/__tests__/live-mutation.test.js +8 -8
- package/dist/core/__tests__/migration.test.js +124 -0
- package/dist/core/__tests__/on-read-dedup-resume.test.js +40 -0
- package/dist/core/__tests__/preview-registry-sync.test.js +36 -0
- package/dist/core/__tests__/prune-to-limit.test.js +91 -0
- package/dist/core/__tests__/revive.test.js +64 -21
- package/dist/core/__tests__/trigger-migration.test.js +48 -0
- package/dist/core/__tests__/wake-bearings.test.js +23 -21
- package/dist/core/__tests__/wake-origin.test.js +36 -33
- package/dist/core/canvas/boot.d.ts +48 -0
- package/dist/core/canvas/boot.js +157 -0
- package/dist/core/canvas/canvas.d.ts +27 -1
- package/dist/core/canvas/canvas.js +86 -5
- package/dist/core/canvas/db.js +86 -1
- package/dist/core/canvas/index.d.ts +1 -1
- package/dist/core/canvas/index.js +1 -1
- package/dist/core/canvas/nav-model.js +1 -1
- package/dist/core/canvas/pid.d.ts +6 -0
- package/dist/core/canvas/pid.js +13 -0
- package/dist/core/canvas/triggers.d.ts +108 -0
- package/dist/core/canvas/triggers.js +320 -0
- package/dist/core/canvas/types.d.ts +128 -46
- package/dist/core/command.js +13 -4
- package/dist/core/hearth/__tests__/model-auth-guest.test.js +151 -0
- package/dist/core/hearth/config.d.ts +1 -0
- package/dist/core/hearth/config.js +36 -0
- package/dist/core/hearth/guest-env.d.ts +7 -0
- package/dist/core/hearth/guest-env.js +17 -1
- package/dist/core/hearth/model-auth-guest.d.ts +8 -0
- package/dist/core/hearth/model-auth-guest.js +435 -0
- package/dist/core/hearth/provider.d.ts +13 -6
- package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +2 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.js +127 -19
- package/dist/core/hearth/providers/blaxel-home.d.ts +8 -4
- package/dist/core/hearth/providers/blaxel-home.js +85 -75
- package/dist/core/hearth/providers/blaxel.d.ts +5 -0
- package/dist/core/hearth/providers/blaxel.js +86 -12
- package/dist/core/hearth/providers/types.d.ts +6 -1
- package/dist/core/hearth/registry.js +2 -1
- package/dist/core/hearth/types.d.ts +35 -0
- package/dist/core/help.d.ts +16 -0
- package/dist/core/help.js +2 -0
- package/dist/core/memory-resolver.d.ts +8 -8
- package/dist/core/memory-resolver.js +58 -26
- package/dist/core/preview-registry.d.ts +25 -0
- package/dist/core/preview-registry.js +2047 -0
- package/dist/core/resolver.d.ts +1 -0
- package/dist/core/resolver.js +23 -16
- package/dist/core/runtime/bearings.d.ts +11 -10
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/broker-protocol.d.ts +2 -2
- package/dist/core/runtime/broker-protocol.js +2 -2
- package/dist/core/runtime/close.js +2 -2
- package/dist/core/runtime/kickoff.js +1 -1
- package/dist/core/runtime/launch.d.ts +9 -3
- package/dist/core/runtime/launch.js +17 -9
- package/dist/core/runtime/lifecycle.js +7 -7
- package/dist/core/runtime/model-swap.js +1 -1
- package/dist/core/runtime/placement.d.ts +5 -5
- package/dist/core/runtime/placement.js +10 -10
- package/dist/core/runtime/recycle.js +7 -7
- package/dist/core/runtime/reset.d.ts +1 -1
- package/dist/core/runtime/reset.js +3 -3
- package/dist/core/runtime/revive-all.d.ts +5 -2
- package/dist/core/runtime/revive-all.js +5 -2
- package/dist/core/runtime/revive.d.ts +15 -8
- package/dist/core/runtime/revive.js +52 -16
- package/dist/core/runtime/spawn.d.ts +1 -1
- package/dist/core/runtime/spawn.js +7 -7
- package/dist/core/runtime/stop-guard.js +5 -5
- package/dist/core/runtime/surface-bg.js +1 -1
- package/dist/core/runtime/tmux.d.ts +1 -1
- package/dist/core/runtime/tmux.js +4 -4
- package/dist/core/scope.d.ts +9 -0
- package/dist/core/scope.js +37 -15
- package/dist/core/substrate/on-read.js +23 -21
- package/dist/core/substrate/render.js +3 -3
- package/dist/core/view/loader.js +2 -2
- package/dist/core/wake.d.ts +2 -2
- package/dist/core/wake.js +7 -7
- package/dist/daemon/__tests__/predicate-eval.test.js +109 -0
- package/dist/daemon/crtrd.js +222 -103
- package/dist/daemon/predicate-eval.d.ts +52 -0
- package/dist/daemon/predicate-eval.js +248 -0
- package/dist/hearth/control-plane/__tests__/node-message.test.js +60 -0
- package/dist/hearth/control-plane/__tests__/rate-limit-recurrence.test.js +49 -0
- package/dist/hearth/control-plane/__tests__/relay-security.test.js +314 -0
- package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.js +133 -0
- package/dist/hearth/control-plane/__tests__/trigger-delivery.test.js +170 -0
- package/dist/hearth/control-plane/__tests__/webhook-ingress.test.js +167 -0
- package/dist/hearth/control-plane/config.d.ts +21 -0
- package/dist/hearth/control-plane/config.js +77 -0
- package/dist/hearth/control-plane/db.d.ts +30 -0
- package/dist/hearth/control-plane/db.js +534 -0
- package/dist/hearth/control-plane/ingress/rate-limit.d.ts +24 -0
- package/dist/hearth/control-plane/ingress/rate-limit.js +100 -0
- package/dist/hearth/control-plane/ingress/route-store.d.ts +31 -0
- package/dist/hearth/control-plane/ingress/route-store.js +61 -0
- package/dist/hearth/control-plane/ingress/webhook-delivery-store.d.ts +41 -0
- package/dist/hearth/control-plane/ingress/webhook-delivery-store.js +69 -0
- package/dist/hearth/control-plane/ingress/webhook-route.d.ts +55 -0
- package/dist/hearth/control-plane/ingress/webhook-route.js +285 -0
- package/dist/hearth/control-plane/main.js +88 -0
- package/dist/hearth/control-plane/node-message.d.ts +31 -0
- package/dist/hearth/control-plane/node-message.js +98 -0
- package/dist/hearth/control-plane/register.d.ts +15 -0
- package/dist/hearth/control-plane/register.js +34 -0
- package/dist/hearth/control-plane/registry.d.ts +17 -0
- package/dist/hearth/control-plane/registry.js +161 -0
- package/dist/hearth/control-plane/relay.d.ts +40 -0
- package/dist/hearth/control-plane/relay.js +642 -0
- package/dist/hearth/control-plane/scheduler/fire-store.d.ts +36 -0
- package/dist/hearth/control-plane/scheduler/fire-store.js +73 -0
- package/dist/hearth/control-plane/scheduler/recurrence.d.ts +7 -0
- package/dist/hearth/control-plane/scheduler/recurrence.js +58 -0
- package/dist/hearth/control-plane/scheduler/scan-loop.d.ts +38 -0
- package/dist/hearth/control-plane/scheduler/scan-loop.js +138 -0
- package/dist/hearth/control-plane/scheduler/schedule-store.d.ts +32 -0
- package/dist/hearth/control-plane/scheduler/schedule-store.js +66 -0
- package/dist/hearth/control-plane/secrets.d.ts +17 -0
- package/dist/hearth/control-plane/secrets.js +113 -0
- package/dist/hearth/control-plane/server.d.ts +27 -0
- package/dist/hearth/control-plane/server.js +346 -0
- package/dist/hearth/control-plane/session.d.ts +68 -0
- package/dist/hearth/control-plane/session.js +273 -0
- package/dist/hearth/control-plane/triggers/acl.d.ts +14 -0
- package/dist/hearth/control-plane/triggers/acl.js +52 -0
- package/dist/hearth/control-plane/triggers/audit-store.d.ts +38 -0
- package/dist/hearth/control-plane/triggers/audit-store.js +79 -0
- package/dist/hearth/control-plane/triggers/deliver.d.ts +43 -0
- package/dist/hearth/control-plane/triggers/deliver.js +76 -0
- package/dist/hearth/control-plane/triggers/envelope.d.ts +29 -0
- package/dist/hearth/control-plane/triggers/envelope.js +38 -0
- package/dist/hearth/control-plane/types.d.ts +66 -0
- package/dist/hearth/control-plane/wake.d.ts +80 -0
- package/dist/hearth/control-plane/wake.js +312 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +2 -2
- package/dist/hearth/wake-proxy/home.d.ts +2 -8
- package/dist/hearth/wake-proxy/home.js +48 -12
- package/dist/pi-extensions/canvas-doc-substrate.js +28 -2
- package/dist/pi-extensions/canvas-nav.js +1 -1
- package/dist/prompts/view.js +7 -8
- package/dist/types.js +4 -4
- package/dist/web-client/assets/{index-D36PNBj4.js → index-BRKxe-hy.js} +2 -2
- package/dist/web-client/index.html +1 -1
- package/package.json +12 -8
- package/scripts/postinstall.mjs +8 -0
- package/dist/clients/attach/__tests__/action-parity.test.js +0 -48
- package/dist/clients/attach/__tests__/autocomplete-and-bash-mode.test.js +0 -96
- package/dist/clients/attach/__tests__/bash-bang-routing.test.js +0 -58
- package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.js +0 -56
- package/dist/clients/attach/__tests__/clipboard-image.test.js +0 -76
- package/dist/clients/attach/__tests__/editor-newline.test.js +0 -82
- package/dist/clients/attach/__tests__/git-info.test.js +0 -31
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +0 -20
- package/dist/clients/attach/__tests__/reconnect-giveup.test.js +0 -30
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +0 -42
- package/dist/clients/attach/__tests__/titled-editor.test.js +0 -60
- package/dist/commands/memory/__tests__/lint-schema.test.js +0 -52
- package/dist/core/__tests__/argv-parser.test.js +0 -217
- package/dist/core/__tests__/artifact-paths.test.js +0 -44
- package/dist/core/__tests__/draw-style.test.js +0 -258
- package/dist/core/__tests__/editor-label.test.js +0 -26
- package/dist/core/__tests__/fault-marker.test.js +0 -112
- package/dist/core/__tests__/fault-retry-rewind.test.d.ts +0 -1
- package/dist/core/__tests__/fault-retry-rewind.test.js +0 -123
- package/dist/core/__tests__/frame-decoder-perf.test.d.ts +0 -1
- package/dist/core/__tests__/frame-decoder-perf.test.js +0 -224
- package/dist/core/__tests__/keystone.test.d.ts +0 -1
- package/dist/core/__tests__/keystone.test.js +0 -185
- package/dist/core/__tests__/listing-completeness.test.d.ts +0 -1
- package/dist/core/__tests__/listing-completeness.test.js +0 -31
- package/dist/core/__tests__/memory-resolver.test.d.ts +0 -1
- package/dist/core/__tests__/memory-resolver.test.js +0 -119
- package/dist/core/__tests__/memory.test.d.ts +0 -1
- package/dist/core/__tests__/memory.test.js +0 -60
- package/dist/core/__tests__/model-ladders.test.d.ts +0 -1
- package/dist/core/__tests__/model-ladders.test.js +0 -160
- package/dist/core/__tests__/node-env.test.d.ts +0 -1
- package/dist/core/__tests__/node-env.test.js +0 -26
- package/dist/core/__tests__/pane-column.test.d.ts +0 -1
- package/dist/core/__tests__/pane-column.test.js +0 -153
- package/dist/core/__tests__/persona-compose.test.d.ts +0 -1
- package/dist/core/__tests__/persona-compose.test.js +0 -53
- package/dist/core/__tests__/persona-subkind.test.d.ts +0 -1
- package/dist/core/__tests__/persona-subkind.test.js +0 -65
- package/dist/core/__tests__/persona.test.d.ts +0 -1
- package/dist/core/__tests__/persona.test.js +0 -107
- package/dist/core/__tests__/unknown-path.test.d.ts +0 -1
- package/dist/core/__tests__/unknown-path.test.js +0 -58
- package/dist/core/canvas/wakeups.d.ts +0 -76
- package/dist/core/canvas/wakeups.js +0 -185
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +0 -1
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +0 -455
- /package/dist/clients/attach/__tests__/{action-parity.test.d.ts → crtr-output-coverage.test.d.ts} +0 -0
- /package/dist/clients/attach/__tests__/{autocomplete-and-bash-mode.test.d.ts → crtr-output-render.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/bash-bang-routing.test.d.ts → core/__tests__/boot.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/bundle-pi-tui-dedup.test.d.ts → core/__tests__/hearth-bootstrap.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/clipboard-image.test.d.ts → core/__tests__/preview-registry-sync.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/editor-newline.test.d.ts → core/__tests__/prune-to-limit.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/git-info.test.d.ts → core/__tests__/trigger-migration.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/onrequest-wired.test.d.ts → core/hearth/__tests__/model-auth-guest.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/reconnect-giveup.test.d.ts → daemon/__tests__/predicate-eval.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/slash-quit-copy.test.d.ts → hearth/control-plane/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/titled-editor.test.d.ts → hearth/control-plane/__tests__/rate-limit-recurrence.test.d.ts} +0 -0
- /package/dist/{commands/memory/__tests__/lint-schema.test.d.ts → hearth/control-plane/__tests__/relay-security.test.d.ts} +0 -0
- /package/dist/{core/__tests__/argv-parser.test.d.ts → hearth/control-plane/__tests__/scheduler-scan-loop.test.d.ts} +0 -0
- /package/dist/{core/__tests__/artifact-paths.test.d.ts → hearth/control-plane/__tests__/trigger-delivery.test.d.ts} +0 -0
- /package/dist/{core/__tests__/draw-style.test.d.ts → hearth/control-plane/__tests__/webhook-ingress.test.d.ts} +0 -0
- /package/dist/{core/__tests__/editor-label.test.d.ts → hearth/control-plane/main.d.ts} +0 -0
- /package/dist/{core/__tests__/fault-marker.test.d.ts → hearth/control-plane/types.js} +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
|
+
});
|