@crouton-kit/crouter 0.3.32 → 0.3.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/build-root.js +4 -6
- package/dist/builtin-memory/crouter-development/marketplaces.md +8 -8
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
- package/dist/builtin-memory/crouter-development/personas.md +3 -4
- package/dist/builtin-memory/crouter-development/plugins.md +31 -31
- package/dist/builtin-memory/design.md +4 -1
- package/dist/builtin-memory/development.md +4 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +9 -9
- package/dist/builtin-memory/internal/storage-tiers.md +5 -5
- package/dist/builtin-memory/planning.md +4 -1
- package/dist/builtin-memory/product.md +80 -0
- package/dist/builtin-memory/spec.md +4 -1
- package/dist/builtin-personas/advisor/PERSONA.md +10 -0
- package/dist/builtin-personas/design/PERSONA.md +2 -2
- package/dist/builtin-personas/design/orchestrator.md +3 -3
- package/dist/builtin-personas/developer/PERSONA.md +1 -1
- package/dist/builtin-personas/developer/orchestrator.md +2 -2
- package/dist/builtin-personas/explore/PERSONA.md +3 -3
- package/dist/builtin-personas/explore/orchestrator.md +4 -2
- package/dist/builtin-personas/general/PERSONA.md +1 -1
- package/dist/builtin-personas/general/orchestrator.md +1 -1
- package/dist/builtin-personas/orchestration-kernel.md +8 -15
- package/dist/builtin-personas/plan/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/orchestrator.md +2 -2
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
- package/dist/builtin-personas/product/PERSONA.md +18 -0
- package/dist/builtin-personas/product/orchestrator.md +14 -0
- package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -1
- package/dist/builtin-personas/review/orchestrator.md +1 -1
- package/dist/builtin-personas/runtime-base.md +5 -0
- package/dist/builtin-personas/spec/PERSONA.md +1 -1
- package/dist/builtin-personas/spec/orchestrator.md +2 -2
- package/dist/builtin-personas/spec/requirements/PERSONA.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/builtin-views/canvas/core.mjs +82 -1
- package/dist/builtin-views/canvas/tui.mjs +9 -5
- package/dist/builtin-views/canvas/web.jsx +3 -2
- package/dist/builtin-views/chat/core.mjs +725 -0
- package/dist/builtin-views/chat/text.mjs +101 -0
- package/dist/builtin-views/chat/tui.mjs +368 -0
- package/dist/builtin-views/chat/web.jsx +367 -0
- package/dist/builtin-views/prompt-review/core.mjs +863 -0
- package/dist/builtin-views/prompt-review/text.mjs +15 -0
- package/dist/builtin-views/prompt-review/tui.mjs +196 -0
- package/dist/builtin-views/prompt-review/web.jsx +484 -0
- package/dist/builtin-views/settings/core.mjs +397 -0
- package/dist/builtin-views/settings/text.mjs +40 -0
- package/dist/builtin-views/settings/tui.mjs +95 -0
- package/dist/builtin-views/settings/web.jsx +167 -0
- package/dist/cli.js +16 -3
- 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.d.ts +18 -0
- package/dist/clients/attach/attach-cmd.js +1797 -797
- package/dist/clients/attach/auth-pickers.js +2 -2
- package/dist/clients/attach/canvas-panels.js +3 -3
- package/dist/clients/attach/chat-view.d.ts +13 -2
- package/dist/clients/attach/chat-view.js +53 -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.d.ts +8 -4
- package/dist/clients/attach/config-load.js +3 -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.d.ts +2 -3
- package/dist/clients/attach/extension-dialogs.js +8 -10
- package/dist/clients/attach/graph-overlay.js +6 -5
- package/dist/clients/attach/input-controller.d.ts +4 -3
- package/dist/clients/attach/input-controller.js +25 -4
- package/dist/clients/attach/pickers.js +1 -1
- package/dist/clients/attach/slash-commands.d.ts +7 -7
- package/dist/clients/attach/slash-commands.js +40 -17
- package/dist/clients/attach/titled-editor.d.ts +4 -0
- package/dist/clients/attach/titled-editor.js +31 -15
- package/dist/clients/attach/view-socket.d.ts +2 -1
- package/dist/clients/attach/view-socket.js +28 -9
- package/dist/clients/web/__tests__/source-cache.test.js +32 -0
- package/dist/clients/web/dev-server.js +1 -0
- package/dist/clients/web/events.js +9 -11
- package/dist/clients/web/server.d.ts +4 -3
- package/dist/clients/web/server.js +166 -35
- package/dist/clients/web/source-cache.d.ts +10 -0
- package/dist/clients/web/source-cache.js +57 -0
- package/dist/clients/web/web-cmd.js +32 -13
- package/dist/commands/__tests__/human.test.js +30 -28
- package/dist/commands/canvas-history/search.js +1 -1
- package/dist/commands/canvas-history.js +5 -8
- package/dist/commands/canvas-issue.d.ts +2 -0
- package/dist/commands/canvas-issue.js +148 -0
- package/dist/commands/canvas-prune.js +43 -2
- package/dist/commands/canvas-rebuild-index.d.ts +2 -0
- package/dist/commands/canvas-rebuild-index.js +57 -0
- package/dist/commands/canvas-snapshot.d.ts +2 -0
- package/dist/commands/canvas-snapshot.js +51 -0
- package/dist/commands/canvas-tmux-spread.d.ts +2 -0
- package/dist/commands/canvas-tmux-spread.js +188 -0
- package/dist/commands/canvas.d.ts +1 -0
- package/dist/commands/canvas.js +16 -1
- package/dist/commands/chord.js +143 -48
- package/dist/commands/daemon.js +3 -3
- package/dist/commands/human/prompts.d.ts +0 -1
- package/dist/commands/human/prompts.js +39 -54
- package/dist/commands/human/queue.d.ts +12 -1
- package/dist/commands/human/queue.js +468 -73
- package/dist/commands/human/shared.d.ts +3 -4
- package/dist/commands/human/shared.js +3 -3
- package/dist/commands/human.js +12 -11
- package/dist/commands/memory/find.js +3 -2
- package/dist/commands/memory/lint.js +15 -8
- package/dist/commands/memory/list.js +4 -3
- package/dist/commands/memory/origin.d.ts +1 -0
- package/dist/commands/memory/origin.js +88 -0
- package/dist/commands/memory/read.js +3 -3
- package/dist/commands/memory/shared.d.ts +9 -4
- package/dist/commands/memory/shared.js +28 -10
- package/dist/commands/memory/write.js +19 -14
- package/dist/commands/memory.js +6 -8
- package/dist/commands/node-context.d.ts +2 -0
- package/dist/commands/node-context.js +172 -0
- package/dist/commands/node-snapshot.d.ts +2 -0
- package/dist/commands/node-snapshot.js +126 -0
- package/dist/commands/node.d.ts +2 -2
- package/dist/commands/node.js +987 -603
- 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 +13 -29
- package/dist/commands/pkg/plugin-manage.d.ts +5 -1
- package/dist/commands/pkg/plugin-manage.js +246 -172
- package/dist/commands/pkg/plugin.js +6 -6
- package/dist/commands/pkg.js +7 -7
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +79 -11
- package/dist/commands/revive.js +79 -4
- package/dist/commands/search/answer.d.ts +1 -0
- package/dist/commands/search/answer.js +50 -0
- package/dist/commands/search/contents.d.ts +1 -0
- package/dist/commands/search/contents.js +96 -0
- package/dist/commands/search/exa.d.ts +53 -0
- package/dist/commands/search/exa.js +155 -0
- package/dist/commands/search/puremd.d.ts +11 -0
- package/dist/commands/search/puremd.js +53 -0
- package/dist/commands/search/web.d.ts +1 -0
- package/dist/commands/search/web.js +65 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +24 -0
- package/dist/commands/surface.d.ts +2 -0
- package/dist/commands/surface.js +43 -0
- package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
- package/dist/commands/sys/config.js +59 -10
- package/dist/commands/sys/doctor.js +56 -4
- package/dist/commands/sys/prompt-review.d.ts +1 -0
- package/dist/commands/sys/prompt-review.js +178 -0
- package/dist/commands/sys/promptstudio.d.ts +2 -0
- package/dist/commands/sys/promptstudio.js +65 -0
- package/dist/commands/sys/settings.d.ts +2 -0
- package/dist/commands/sys/settings.js +16 -0
- 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/sync.js +311 -159
- package/dist/commands/sys/sysprompt.d.ts +1 -0
- package/dist/commands/sys/sysprompt.js +89 -0
- package/dist/commands/sys.js +10 -5
- package/dist/commands/view-cycle.js +5 -5
- package/dist/commands/view-list.js +3 -3
- package/dist/commands/view-new.js +6 -6
- 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-preflight.test.js +85 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +10 -16
- package/dist/core/__tests__/canvas.test.js +37 -1
- package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
- package/dist/core/__tests__/child-death-wake.test.js +11 -2
- package/dist/core/__tests__/close.test.js +39 -1
- package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
- package/dist/core/__tests__/context-intro.test.js +48 -14
- package/dist/core/__tests__/daemon-boot.test.js +204 -9
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +157 -18
- package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -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__/full/human-new-window-regression.test.js +1 -1
- package/dist/core/__tests__/hearth-bootstrap.test.js +136 -0
- package/dist/core/__tests__/helpers/harness.d.ts +1 -1
- package/dist/core/__tests__/helpers/harness.js +3 -3
- package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
- package/dist/core/__tests__/human-surface-target.test.js +1 -1
- package/dist/core/__tests__/kickoff.test.js +18 -19
- 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 +172 -13
- 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__/push-final-guard.test.js +7 -1
- package/dist/core/__tests__/relaunch-root.test.js +9 -9
- package/dist/core/__tests__/revive.test.js +100 -27
- package/dist/core/__tests__/spawn-root.test.js +24 -1
- package/dist/core/__tests__/stop-guard.test.js +10 -0
- package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
- package/dist/core/__tests__/tmux-surface.test.js +5 -1
- 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/__tests__/yield-ensures-daemon.test.js +54 -0
- package/dist/core/artifact.d.ts +2 -33
- package/dist/core/artifact.js +27 -87
- package/dist/core/bootstrap.js +2 -0
- package/dist/core/canvas/boot.d.ts +48 -0
- package/dist/core/canvas/boot.js +157 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
- package/dist/core/canvas/browse/app.js +87 -17
- package/dist/core/canvas/browse/model.d.ts +10 -1
- package/dist/core/canvas/browse/model.js +37 -1
- package/dist/core/canvas/browse/render.js +35 -33
- package/dist/core/canvas/canvas.d.ts +33 -0
- package/dist/core/canvas/canvas.js +138 -8
- package/dist/core/canvas/db.js +121 -9
- package/dist/core/canvas/index.d.ts +1 -1
- package/dist/core/canvas/index.js +1 -1
- package/dist/core/canvas/labels.d.ts +6 -8
- package/dist/core/canvas/labels.js +8 -11
- package/dist/core/canvas/nav-model.d.ts +5 -4
- package/dist/core/canvas/nav-model.js +32 -18
- package/dist/core/canvas/paths.d.ts +18 -1
- package/dist/core/canvas/paths.js +31 -2
- package/dist/core/canvas/pid.d.ts +6 -0
- package/dist/core/canvas/pid.js +13 -0
- package/dist/core/canvas/render.d.ts +25 -0
- package/dist/core/canvas/render.js +96 -13
- package/dist/core/canvas/status-glyph.d.ts +35 -0
- package/dist/core/canvas/status-glyph.js +104 -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 +130 -48
- package/dist/core/command.js +13 -4
- package/dist/core/config.js +114 -16
- package/dist/core/fault-classifier.d.ts +41 -0
- package/dist/core/fault-classifier.js +110 -0
- package/dist/core/feed/inbox.d.ts +15 -0
- package/dist/core/feed/inbox.js +40 -14
- package/dist/core/frontmatter.d.ts +1 -11
- package/dist/core/frontmatter.js +1 -55
- package/dist/core/hearth/__tests__/model-auth-guest.test.js +151 -0
- package/dist/core/hearth/config.d.ts +3 -0
- package/dist/core/hearth/config.js +111 -0
- package/dist/core/hearth/guest-env.d.ts +9 -0
- package/dist/core/hearth/guest-env.js +27 -0
- package/dist/core/hearth/index.d.ts +5 -0
- package/dist/core/hearth/index.js +5 -0
- 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 +28 -0
- package/dist/core/hearth/provider.js +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +12 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.js +147 -0
- package/dist/core/hearth/providers/blaxel-home.d.ts +15 -0
- package/dist/core/hearth/providers/blaxel-home.js +136 -0
- package/dist/core/hearth/providers/blaxel.d.ts +31 -0
- package/dist/core/hearth/providers/blaxel.js +282 -0
- package/dist/core/hearth/providers/types.d.ts +77 -0
- package/dist/core/hearth/registry.d.ts +15 -0
- package/dist/core/hearth/registry.js +180 -0
- package/dist/core/hearth/types.d.ts +156 -0
- package/dist/core/help.d.ts +16 -0
- package/dist/core/help.js +2 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
- package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
- package/dist/core/host-exports/builtins.js +68 -0
- package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
- package/dist/core/{skill-sync → host-exports}/export.js +37 -14
- package/dist/core/io.js +1 -1
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +113 -0
- package/dist/core/memory-resolver.d.ts +13 -13
- package/dist/core/memory-resolver.js +70 -51
- package/dist/core/personas/index.d.ts +4 -4
- package/dist/core/personas/index.js +2 -2
- package/dist/core/personas/loader.d.ts +51 -0
- package/dist/core/personas/loader.js +54 -50
- package/dist/core/personas/resolve.d.ts +43 -27
- package/dist/core/personas/resolve.js +336 -94
- 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/auth-reload.d.ts +7 -0
- package/dist/core/runtime/auth-reload.js +69 -0
- package/dist/core/runtime/bearings.d.ts +46 -25
- package/dist/core/runtime/bearings.js +313 -52
- package/dist/core/runtime/branded-host.d.ts +4 -2
- package/dist/core/runtime/branded-host.js +66 -4
- package/dist/core/runtime/broker-protocol.d.ts +40 -5
- package/dist/core/runtime/broker-protocol.js +2 -2
- package/dist/core/runtime/broker.js +343 -18
- package/dist/core/runtime/close.js +39 -31
- package/dist/core/runtime/connectivity.d.ts +12 -0
- package/dist/core/runtime/connectivity.js +73 -0
- package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
- package/dist/core/runtime/fault-recovery-nudge.js +2 -0
- package/dist/core/runtime/fault-recovery.d.ts +23 -0
- package/dist/core/runtime/fault-recovery.js +74 -0
- package/dist/core/runtime/fault.d.ts +25 -0
- package/dist/core/runtime/fault.js +176 -0
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/host.d.ts +10 -0
- package/dist/core/runtime/host.js +71 -9
- package/dist/core/runtime/kickoff.js +32 -46
- package/dist/core/runtime/launch.d.ts +41 -21
- package/dist/core/runtime/launch.js +165 -52
- package/dist/core/runtime/lifecycle.js +7 -7
- package/dist/core/runtime/model-swap.d.ts +18 -0
- package/dist/core/runtime/model-swap.js +95 -0
- package/dist/core/runtime/naming.d.ts +7 -0
- package/dist/core/runtime/naming.js +61 -9
- package/dist/core/runtime/nodes.js +6 -1
- package/dist/core/runtime/persona.js +16 -17
- package/dist/core/runtime/placement.d.ts +41 -36
- package/dist/core/runtime/placement.js +159 -74
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +23 -3
- package/dist/core/runtime/recycle.js +13 -11
- package/dist/core/runtime/reset.d.ts +6 -3
- package/dist/core/runtime/reset.js +5 -5
- 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 +19 -8
- package/dist/core/runtime/revive.js +79 -25
- package/dist/core/runtime/roadmap.d.ts +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/runtime/spawn.d.ts +3 -3
- package/dist/core/runtime/spawn.js +37 -13
- package/dist/core/runtime/stop-guard.js +11 -6
- package/dist/core/runtime/surface-bg.js +1 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +30 -7
- package/dist/core/runtime/tmux.js +138 -84
- package/dist/core/runtime/view-socket-client.d.ts +46 -0
- package/dist/core/runtime/view-socket-client.js +203 -0
- package/dist/core/scope.d.ts +9 -0
- package/dist/core/scope.js +37 -15
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/on-read.js +39 -25
- package/dist/core/substrate/render.d.ts +10 -13
- package/dist/core/substrate/render.js +56 -51
- package/dist/core/tui/host.js +83 -18
- package/dist/core/view/__tests__/transport-cache.test.js +62 -0
- package/dist/core/view/contract.d.ts +20 -0
- package/dist/core/view/loader.js +2 -2
- package/dist/core/view/stream-local.d.ts +3 -0
- package/dist/core/view/stream-local.js +184 -0
- package/dist/core/view/transport-cache.d.ts +8 -0
- package/dist/core/view/transport-cache.js +38 -0
- package/dist/core/view/transport-local.js +4 -0
- package/dist/core/view/transport.d.ts +7 -1
- 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.d.ts +3 -19
- package/dist/daemon/crtrd.js +360 -280
- package/dist/daemon/manage.d.ts +18 -1
- package/dist/daemon/manage.js +54 -9
- 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.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/scheduler-scan-loop.test.js +133 -0
- package/dist/hearth/control-plane/__tests__/trigger-delivery.test.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/trigger-delivery.test.js +170 -0
- package/dist/hearth/control-plane/__tests__/webhook-ingress.test.d.ts +1 -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.d.ts +1 -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/types.js +1 -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__/anthropic-oauth.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
- package/dist/hearth/wake-proxy/auth.d.ts +22 -0
- package/dist/hearth/wake-proxy/auth.js +128 -0
- package/dist/hearth/wake-proxy/config.d.ts +2 -0
- package/dist/hearth/wake-proxy/config.js +151 -0
- package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
- package/dist/hearth/wake-proxy/guest-source.js +130 -0
- package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
- package/dist/hearth/wake-proxy/hearth-status.js +267 -0
- package/dist/hearth/wake-proxy/home.d.ts +61 -0
- package/dist/hearth/wake-proxy/home.js +333 -0
- package/dist/hearth/wake-proxy/main.d.ts +1 -0
- package/dist/hearth/wake-proxy/main.js +134 -0
- package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
- package/dist/hearth/wake-proxy/model-auth.js +345 -0
- package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
- package/dist/hearth/wake-proxy/proxy.js +716 -0
- package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
- package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
- package/dist/hearth/wake-proxy/redact.d.ts +1 -0
- package/dist/hearth/wake-proxy/redact.js +17 -0
- package/dist/hearth/wake-proxy/server.d.ts +11 -0
- package/dist/hearth/wake-proxy/server.js +142 -0
- package/dist/hearth/wake-proxy/state.d.ts +18 -0
- package/dist/hearth/wake-proxy/state.js +342 -0
- package/dist/hearth/wake-proxy/types.d.ts +76 -0
- package/dist/hearth/wake-proxy/types.js +1 -0
- package/dist/index.js +3 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
- package/dist/pi-extensions/canvas-context-intro.js +7 -7
- package/dist/pi-extensions/canvas-doc-substrate.js +47 -24
- package/dist/pi-extensions/canvas-nav.js +16 -9
- package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
- package/dist/pi-extensions/canvas-stophook.js +78 -11
- package/dist/prompts/view.js +7 -8
- package/dist/types.d.ts +39 -20
- package/dist/types.js +38 -13
- package/dist/web/runtime.js +141 -42
- package/dist/web/transport-http.js +7 -5
- package/dist/web/transport-stream.d.ts +3 -0
- package/dist/web/transport-stream.js +204 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
- package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
- package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
- package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
- package/dist/web-client/assets/index-BRKxe-hy.js +80 -0
- package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
- package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
- package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
- package/dist/web-client/index.html +3 -2
- package/package.json +37 -11
- package/scripts/postinstall.mjs +8 -0
- package/dist/clients/attach/__tests__/action-parity.test.js +0 -48
- 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 -39
- 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__/draw-style.test.js +0 -258
- package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
- package/dist/core/__tests__/frame-decoder-perf.test.js +0 -224
- package/dist/core/__tests__/keystone.test.js +0 -185
- package/dist/core/__tests__/listing-completeness.test.js +0 -31
- package/dist/core/__tests__/memory-resolver.test.js +0 -108
- package/dist/core/__tests__/memory.test.js +0 -60
- package/dist/core/__tests__/pane-column.test.js +0 -153
- package/dist/core/__tests__/persona-compose.test.js +0 -53
- package/dist/core/__tests__/persona-subkind.test.js +0 -65
- package/dist/core/__tests__/persona.test.js +0 -107
- 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/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
- package/dist/core/skill-sync/builtins.js +0 -112
- package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
- package/dist/core/skill-sync/claude-plugins.js +0 -71
- package/dist/core/skill-sync/engine.d.ts +0 -42
- package/dist/core/skill-sync/engine.js +0 -633
- package/dist/core/skill-sync/manifest.d.ts +0 -64
- package/dist/core/skill-sync/manifest.js +0 -181
- package/dist/core/skill-sync/profile.d.ts +0 -76
- package/dist/core/skill-sync/profile.js +0 -173
- package/dist/core/skill-sync/snapshot.d.ts +0 -57
- package/dist/core/skill-sync/snapshot.js +0 -120
- package/dist/pi-extensions/canvas-commands.d.ts +0 -37
- package/dist/pi-extensions/canvas-commands.js +0 -113
- package/dist/pi-extensions/canvas-resume.d.ts +0 -21
- package/dist/pi-extensions/canvas-resume.js +0 -83
- package/dist/pi-extensions/canvas-view.d.ts +0 -21
- package/dist/pi-extensions/canvas-view.js +0 -76
- package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
- package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
- /package/dist/clients/attach/__tests__/{action-parity.test.d.ts → crtr-output-coverage.test.d.ts} +0 -0
- /package/dist/clients/attach/__tests__/{clipboard-image.test.d.ts → crtr-output-render.test.d.ts} +0 -0
- /package/dist/clients/{attach/__tests__/editor-newline.test.d.ts → web/__tests__/source-cache.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/git-info.test.d.ts → commands/sys/__tests__/sync-import.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/onrequest-wired.test.d.ts → core/__tests__/boot.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/reconnect-giveup.test.d.ts → core/__tests__/broker-preflight.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/slash-quit-copy.test.d.ts → core/__tests__/chat-view-reconnect.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/titled-editor.test.d.ts → core/__tests__/connection-reconnect-resume.test.d.ts} +0 -0
- /package/dist/{commands/memory/__tests__/lint-schema.test.d.ts → core/__tests__/hearth-bootstrap.test.d.ts} +0 -0
- /package/dist/core/__tests__/{argv-parser.test.d.ts → preview-registry-sync.test.d.ts} +0 -0
- /package/dist/core/__tests__/{draw-style.test.d.ts → prune-to-limit.test.d.ts} +0 -0
- /package/dist/core/__tests__/{error-stall-recycle.test.d.ts → stranded-relaunch.test.d.ts} +0 -0
- /package/dist/core/__tests__/{frame-decoder-perf.test.d.ts → trigger-migration.test.d.ts} +0 -0
- /package/dist/core/__tests__/{keystone.test.d.ts → yield-ensures-daemon.test.d.ts} +0 -0
- /package/dist/core/{__tests__/listing-completeness.test.d.ts → hearth/__tests__/model-auth-guest.test.d.ts} +0 -0
- /package/dist/core/{__tests__/memory-resolver.test.d.ts → hearth/providers/types.js} +0 -0
- /package/dist/core/{__tests__/memory.test.d.ts → hearth/types.js} +0 -0
- /package/dist/core/{__tests__/pane-column.test.d.ts → host-exports/__tests__/export-prunes-boot-skill.test.d.ts} +0 -0
- /package/dist/core/{__tests__/persona-compose.test.d.ts → view/__tests__/transport-cache.test.d.ts} +0 -0
- /package/dist/{core/__tests__/persona-subkind.test.d.ts → daemon/__tests__/predicate-eval.test.d.ts} +0 -0
- /package/dist/{core/__tests__/persona.test.d.ts → hearth/control-plane/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/{core/__tests__/unknown-path.test.d.ts → hearth/control-plane/__tests__/rate-limit-recurrence.test.d.ts} +0 -0
- /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → hearth/control-plane/__tests__/relay-security.test.d.ts} +0 -0
package/dist/commands/node.js
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// A node is the unit of the runtime: an agent with its own identity, context
|
|
4
4
|
// dir, and pi vehicle, pinned to a cwd. This subtree spawns terminal workers
|
|
5
|
-
// onto the canvas (`new`), inspects
|
|
6
|
-
// the spine (`
|
|
5
|
+
// onto the canvas (`new`), inspects nodes (`inspect`), messages any node and
|
|
6
|
+
// wires the subscribes_to spine (`msg`/`subscribe`/`unsubscribe`), changes
|
|
7
|
+
// lifecycle (`lifecycle`), reconfigures node settings (`config`), and
|
|
8
|
+
// walks/focuses the graph (`focus`/`cycle`). The push/feed half lives under
|
|
9
|
+
// `crtr push`, and pending trigger inspection lives under `triggers`.
|
|
7
10
|
import { defineLeaf, defineBranch } from '../core/command.js';
|
|
8
11
|
import { InputError } from '../core/io.js';
|
|
9
12
|
import { spawnChild } from '../core/runtime/spawn.js';
|
|
@@ -11,16 +14,21 @@ import { promote, requestYield } from '../core/runtime/promote.js';
|
|
|
11
14
|
import { writeYieldMessage, readGoal } from '../core/runtime/kickoff.js';
|
|
12
15
|
import { reviveNode } from '../core/runtime/revive.js';
|
|
13
16
|
import { newNodeId } from '../core/runtime/nodes.js';
|
|
14
|
-
import { readRoadmap } from '../core/runtime/roadmap.js';
|
|
17
|
+
import { readRoadmap, hasRoadmap, seedRoadmap } from '../core/runtime/roadmap.js';
|
|
15
18
|
import { parseWhen, parseCadence, cadenceDisplay } from '../core/wake.js';
|
|
19
|
+
import { nodeSnapshotLeaf } from './node-snapshot.js';
|
|
20
|
+
import { nodeContextLeaf } from './node-context.js';
|
|
16
21
|
import { recycleNode } from '../core/runtime/recycle.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
22
|
+
import { readFaultAsync } from '../core/runtime/fault.js';
|
|
23
|
+
import { activeFaultForDisplay } from '../core/canvas/render.js';
|
|
24
|
+
import { detachToBackground, focus as placementFocus, windowAlive, windowOfPane, currentTmux, getPaneOption, renameWindow } from '../core/runtime/placement.js';
|
|
25
|
+
import { buildLaunchSpec, normalizeModel } from '../core/runtime/launch.js';
|
|
19
26
|
import { closeNode } from '../core/runtime/close.js';
|
|
27
|
+
import { isBrokerLive, setModelLive, persistDormantModel } from '../core/runtime/model-swap.js';
|
|
20
28
|
import { appendInbox } from '../core/feed/inbox.js';
|
|
21
|
-
import { availableKinds, kindWhenToUse
|
|
29
|
+
import { availableKinds, kindWhenToUse } from '../core/personas/index.js';
|
|
22
30
|
import { stateBlock } from '../core/help.js';
|
|
23
|
-
import { getNode, updateNode, listNodes, subscribe, unsubscribe, subscriptionsOf, subscribersOf, readContextTokens, view,
|
|
31
|
+
import { getNode, updateNode, listNodes, subscribe, unsubscribe, subscriptionsOf, subscribersOf, readContextTokens, view, armTrigger, listTriggers, cancelTrigger, TriggerArmError, fullName, } from '../core/canvas/index.js';
|
|
24
32
|
// Past this much context, an ORCHESTRATOR that spawns a managed child is better
|
|
25
33
|
// off yielding than holding its fat window open for the child's result: the
|
|
26
34
|
// child's finish revives it fresh against its roadmap, so a clean window absorbs
|
|
@@ -56,11 +64,8 @@ function assertKind(kind) {
|
|
|
56
64
|
* it reflects the caller's cwd/project scope. Appended to `node new -h` and
|
|
57
65
|
* `node promote -h` so custom kinds appear in help. Soft-fails via the renderer.
|
|
58
66
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* block is appended right after — the specialist sub-personas THAT kind may
|
|
62
|
-
* spawn (full kind string + whenToUse). Everything but the top-level `<kinds>`
|
|
63
|
-
* block soft-fails to omission (caller block wrapped in its own try/catch). */
|
|
67
|
+
* Keep this block cheap and caller-agnostic: help should not depend on the
|
|
68
|
+
* running node's current persona env, which can lag after polymorph. */
|
|
64
69
|
function kindsStateBlock() {
|
|
65
70
|
const kinds = availableKinds();
|
|
66
71
|
const lines = kinds
|
|
@@ -69,70 +74,55 @@ function kindsStateBlock() {
|
|
|
69
74
|
return w ? `${k} — ${w}` : k;
|
|
70
75
|
})
|
|
71
76
|
.join('\n');
|
|
72
|
-
|
|
73
|
-
const sub = callerSubPersonasBlock();
|
|
74
|
-
return sub ? `${block}\n${sub}` : block;
|
|
75
|
-
}
|
|
76
|
-
/** When the caller is a live node whose kind has sub-personas available to it,
|
|
77
|
-
* render a `<sub-personas for="<kind>" count=M>` block — one
|
|
78
|
-
* `<full-kind-string> — <whenToUse>` line per sub-persona spawnable BY that
|
|
79
|
-
* kind (e.g. `plan/reviewers/security`). Returns '' (so the second block is
|
|
80
|
-
* omitted) when CRTR_NODE_ID is unset, getNode returns null, the caller kind
|
|
81
|
-
* has no sub-personas, or anything throws — this is help output, never error. */
|
|
82
|
-
function callerSubPersonasBlock() {
|
|
83
|
-
try {
|
|
84
|
-
const id = process.env['CRTR_NODE_ID'];
|
|
85
|
-
if (id === undefined || id === '')
|
|
86
|
-
return '';
|
|
87
|
-
const node = getNode(id);
|
|
88
|
-
if (node === null)
|
|
89
|
-
return '';
|
|
90
|
-
const subs = subPersonasFor(node.kind);
|
|
91
|
-
if (subs.length === 0)
|
|
92
|
-
return '';
|
|
93
|
-
const lines = subs.map((s) => (s.whenToUse ? `${s.kind} — ${s.whenToUse}` : s.kind)).join('\n');
|
|
94
|
-
return stateBlock('sub-personas', { for: node.kind, count: subs.length }, lines);
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return '';
|
|
98
|
-
}
|
|
77
|
+
return stateBlock('kinds', { count: kinds.length }, lines);
|
|
99
78
|
}
|
|
100
79
|
// ---------------------------------------------------------------------------
|
|
101
80
|
// node new — spawn a terminal worker as a detached headless broker under the root
|
|
102
81
|
// ---------------------------------------------------------------------------
|
|
103
82
|
const nodeNew = defineLeaf({
|
|
104
83
|
name: 'new',
|
|
105
|
-
description: 'spawn a node — a managed child (default),
|
|
106
|
-
whenToUse: 'you have a self-contained unit of work — reach for this instead of doing it inline, so the reading and the tokens land in a fresh window and only the conclusion comes back: mapping an unfamiliar part of the codebase, writing a spec, designing an approach, breaking a job into a plan, implementing a change, or running a review. Fan independent units out as concurrent children. Most spawns are managed children whose finish wakes you; reach here too when a unit is itself too big for one window (boot it directly as its own sub-orchestrator rather than hope a base worker promotes itself) or when you are handing off an INDEPENDENT node you will neither manage nor be woken by, e.g. one a human will sit and drive',
|
|
84
|
+
description: 'spawn a node now — a managed child (default), an independent root with --root, or arm a trigger that births it later',
|
|
85
|
+
whenToUse: 'you have a self-contained unit of work — reach for this instead of doing it inline, so the reading and the tokens land in a fresh window and only the conclusion comes back: mapping an unfamiliar part of the codebase, writing a spec, designing an approach, breaking a job into a plan, implementing a change, or running a review. Fan independent units out as concurrent children. Most spawns are managed children whose finish wakes you; reach here too when a unit is itself too big for one window (boot it directly as its own sub-orchestrator rather than hope a base worker promotes itself) or when you are handing off an INDEPENDENT node you will neither manage nor be woken by, e.g. one a human will sit and drive. Add `--at`/`--every` to defer or recur the birth instead of spawning now, or `--when` to birth it once an external predicate exits 0',
|
|
107
86
|
tier: 'important',
|
|
108
87
|
help: {
|
|
109
88
|
name: 'node new',
|
|
110
|
-
summary: 'spawn a terminal worker onto the canvas as a detached broker — returns its node id',
|
|
89
|
+
summary: 'spawn a terminal worker onto the canvas as a detached broker — returns its node id. With --at/--every/--when, arms a node_birth trigger instead: nothing is spawned now, and the full recipe is stored/re-derived at fire time',
|
|
111
90
|
params: [
|
|
112
91
|
{ kind: 'stdin', name: 'prompt', required: true, constraint: 'First user message for the spawned node. Deliver it on stdin from a quoted heredoc (`<<\'EOF\'`) or a file (`< prompt.md`).' },
|
|
113
|
-
{ kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind — match the work to the kind. The <kinds> list below names every installable kind and when to use each
|
|
92
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind — match the work to the kind. The <kinds> list below names every installable kind and when to use each. For an exact composed persona list, use `crtr sys prompt-review --list --kind <kind>`.' },
|
|
114
93
|
{ kind: 'flag', name: 'mode', type: 'enum', choices: ['base', 'orchestrator'], required: false, default: 'base', constraint: 'Persona mode. base for a worker that finishes in one window; orchestrator to create the child directly as a sub-orchestrator (it boots with the orchestrator persona + a seeded roadmap and fans its scope out) — use it when the unit is too large for one window, e.g. a big review, instead of spawning a base worker and counting on it to promote itself.' },
|
|
115
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Dir the node is pinned to. Defaults to the caller cwd.' },
|
|
94
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Dir the node is pinned to. Defaults to the caller cwd, resolved now — for a triggered spawn it must still exist at fire time or the spawn fails loud.' },
|
|
116
95
|
{ kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Display name (tmux window + resume picker). Defaults to the kind.' },
|
|
117
96
|
{ kind: 'flag', name: 'parent', type: 'string', required: false, constraint: 'Parent node id. Defaults to the calling node (CRTR_NODE_ID).' },
|
|
118
97
|
{ kind: 'flag', name: 'root', type: 'bool', required: false, constraint: 'Spawn an INDEPENDENT root instead of a managed child: no parent (top-level on the canvas), NO subscription back to you (you are NOT woken by it), resident lifecycle. It records spawned_by=you for provenance and is brought forefront so it can be driven directly. Use for a node you hand off and do not manage (e.g. a sub-orchestrator a human will discuss with).' },
|
|
119
98
|
{ kind: 'flag', name: 'fork-from', type: 'string', required: false, constraint: 'FORK the new node from an existing pi conversation instead of starting it fresh: pass a node id (forks from that node\'s session), an absolute session `.jsonl` path, or a partial pi session uuid. pi copies that whole history into a NEW session for the child (the source is untouched), then the prompt is delivered as the next message — i.e. the child wakes up as a continuation of that conversation. Use to branch exploratory work off a node that already built up the context you need, instead of re-deriving it. One-shot at birth: the fork resumes its own session thereafter.' },
|
|
120
|
-
{ kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong', 'medium', 'light'], required: false, constraint: 'Override the model the node runs on, by capability TIER: ultra (frontier), strong (opus), medium (sonnet), light (haiku). Omit to use the kind\'s persona default (explore=light,
|
|
99
|
+
{ kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong', 'medium', 'light'], required: false, constraint: 'Override the model the node runs on, by capability TIER: ultra (frontier), strong (opus), medium (sonnet), light (haiku). Omit to use the kind\'s persona default (advisor=ultra, explore=light, most other builtins=strong). The override is durable — it survives revives and polymorphs (promote/demote).' },
|
|
100
|
+
{ kind: 'flag', name: 'at', type: 'string', required: false, constraint: 'DEFER the birth to a one-shot clock trigger at this future time instead of spawning now — a duration ("5m","1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00", host-local or in --tz). Required unless --every is given (the cadence then sets the first fire). Mutually exclusive with --when.' },
|
|
101
|
+
{ kind: 'flag', name: 'every', type: 'string', required: false, constraint: 'SPAWN-CRON: re-birth a fresh node on this cadence — a duration ("6h","30m") or a 5-field cron / @alias ("0 9 * * *","@daily"). Combine with --at to anchor the first fire, then recur normally. Min cadence 60s. With --when, this instead sets the PREDICATE EVALUATION cadence (required), not a repeating birth.' },
|
|
102
|
+
{ kind: 'flag', name: 'when', type: 'string', required: false, constraint: 'Arm a daemon-evaluated bash predicate instead of a clock trigger: exit 0 births the node once, non-zero leaves it armed for the next evaluation. Requires --every (eval cadence) and --timeout (wall-clock bound). Mutually exclusive with --at.' },
|
|
103
|
+
{ kind: 'flag', name: 'timeout', type: 'string', required: false, constraint: 'Wall-clock bound for a --when predicate — a duration or absolute time after which the trigger is consumed and the owner gets one urgent notice with no node born. Required with --when; rejected otherwise.' },
|
|
104
|
+
{ kind: 'flag', name: 'tz', type: 'string', required: false, constraint: 'IANA zone for a calendar --every or a bare-ISO --at (default: host-local). Valid only alongside --at/--every; rejected with --when, and rejected when spawning now (no trigger flags) since neither has anything to parse it against.' },
|
|
121
105
|
],
|
|
122
106
|
output: [
|
|
123
|
-
{ name: 'node_id', type: 'string', required:
|
|
124
|
-
{ name: 'name', type: 'string', required:
|
|
125
|
-
{ name: 'window', type: 'string', required: false, constraint: 'tmux window id of the viewer opened for an in-tmux --root (a
|
|
126
|
-
{ name: 'session', type: 'string', required: false, constraint: 'The tmux session a --root\'s viewer opened into (your current session); absent for a managed child
|
|
127
|
-
{ name: 'status', type: 'string', required:
|
|
128
|
-
{ name: '
|
|
107
|
+
{ name: 'node_id', type: 'string', required: false, constraint: 'The new node id. Present only when spawning now (no trigger flags).' },
|
|
108
|
+
{ name: 'name', type: 'string', required: false, constraint: 'Display name. Present only when spawning now (no trigger flags).' },
|
|
109
|
+
{ name: 'window', type: 'string', required: false, constraint: 'tmux window id of the viewer opened for an in-tmux --root (a surface attach view onto the node\'s broker); absent for a managed child (opens no viewer on spawn) and absent whenever armed.' },
|
|
110
|
+
{ name: 'session', type: 'string', required: false, constraint: 'The tmux session a --root\'s viewer opened into (your current session); absent for a managed child, for a --root spawned outside tmux, and absent whenever armed.' },
|
|
111
|
+
{ name: 'status', type: 'string', required: false, constraint: 'Always "active". Present only when spawning now (no trigger flags).' },
|
|
112
|
+
{ name: 'armed', type: 'boolean', required: true, constraint: 'True when a node_birth trigger was armed for later (--at/--every/--when) instead of spawning now.' },
|
|
113
|
+
{ name: 'id', type: 'string', required: false, constraint: 'The trigger id (inspect/cancel it via `node triggers`), present only when armed=true.' },
|
|
114
|
+
{ name: 'trigger_type', type: 'string', required: false, constraint: '"at", "every", or "when", present only when armed=true.' },
|
|
115
|
+
{ name: 'fires_at', type: 'string', required: false, constraint: 'Absolute UTC birth time, present only for an armed clock trigger.' },
|
|
116
|
+
{ name: 'next_eval_at', type: 'string', required: false, constraint: 'The next predicate-evaluation instant (always "now" at arm time), present only for an armed --when trigger.' },
|
|
117
|
+
{ name: 'timeout_at', type: 'string', required: false, constraint: 'Absolute UTC wall-clock bound, present only for an armed --when trigger.' },
|
|
118
|
+
{ name: 'recur', type: 'string', required: false, constraint: 'Cadence display ("every 6h", cron, or eval cadence for --when), present only when --every was given.' },
|
|
119
|
+
{ name: 'follow_up', type: 'string', required: true, constraint: 'Decision road sign for the caller. Spawned now: the child runs independently and its finish wakes you on its own, so never wait or poll on it — either pick up other work now or end your turn (an orchestrator already deep in context is instead steered to `crtr node yield`). Armed: no node exists yet — inspect/cancel via `crtr node triggers list`/`cancel <id>`, then pick up other work or end your turn.' },
|
|
129
120
|
],
|
|
130
121
|
dynamicState: () => kindsStateBlock(),
|
|
131
122
|
outputKind: 'object',
|
|
132
123
|
effects: [
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
'Launches the node\'s engine as a detached broker (the only host). A managed child opens NO viewer — it runs headless and you open one on demand via `crtr node focus` / `crtr attach`. With --root inside tmux, opens one viewer window in your CURRENT session and switches the client to it so the root can be driven directly; outside tmux a --root opens no viewer.',
|
|
124
|
+
'Spawning now: creates a node under ~/.crouter/canvas/nodes/<id>/ and indexes it in canvas.db. Default (managed child): parent auto-subscribes (active) and is woken on the child\'s pushes. With --root: no subscription — records a spawned_by edge for provenance only. Launches the node\'s engine as a detached broker (the only host); a managed child opens NO viewer, a --root opens one in your current tmux session.',
|
|
125
|
+
'Armed (--at/--every/--when): inserts one detached `new`/`node_birth` triggers row (node_id NULL, owner=you, parent/cwd resolved now); NO node and NO window exist until fire time. The daemon spawns from the stored recipe at fire time, re-deriving the launch spec live.',
|
|
136
126
|
],
|
|
137
127
|
},
|
|
138
128
|
run: async (input) => {
|
|
@@ -148,19 +138,154 @@ const nodeNew = defineLeaf({
|
|
|
148
138
|
const root = input['root'] === true;
|
|
149
139
|
const forkFrom = input['forkFrom'];
|
|
150
140
|
const model = input['model'];
|
|
151
|
-
const
|
|
141
|
+
const at = input['at']?.trim();
|
|
142
|
+
const every = input['every']?.trim();
|
|
143
|
+
const whenRaw = input['when'];
|
|
144
|
+
const hasWhen = whenRaw !== undefined && whenRaw.trim() !== '';
|
|
145
|
+
const timeout = input['timeout']?.trim();
|
|
146
|
+
const hasTimeout = timeout !== undefined && timeout !== '';
|
|
147
|
+
const tz = input['tz'];
|
|
148
|
+
const hasTz = tz !== undefined && tz.trim() !== '';
|
|
149
|
+
const hasAt = at !== undefined && at !== '';
|
|
150
|
+
const hasEvery = every !== undefined && every !== '';
|
|
151
|
+
const triggered = hasAt || hasEvery || hasWhen;
|
|
152
|
+
if (hasTz && hasWhen) {
|
|
153
|
+
throw new InputError({ error: 'tz_not_valid_with_when', message: '--tz has no effect with --when (predicate evaluation parses no calendar timezone)', field: 'tz', next: 'Drop --tz.' });
|
|
154
|
+
}
|
|
155
|
+
if (hasTz && !hasAt && !hasEvery) {
|
|
156
|
+
throw new InputError({ error: 'tz_needs_clock', message: '--tz has no effect without --at or --every to parse it against', field: 'tz', next: 'Drop --tz, or pass --at/--every.' });
|
|
157
|
+
}
|
|
158
|
+
if (!triggered) {
|
|
159
|
+
const res = await spawnChild({ kind, mode, cwd, name, prompt, parent, root, forkFrom, model });
|
|
160
|
+
return {
|
|
161
|
+
node_id: res.node.node_id,
|
|
162
|
+
name: res.node.name,
|
|
163
|
+
window: res.window ?? undefined,
|
|
164
|
+
session: res.session ?? undefined,
|
|
165
|
+
status: res.node.status,
|
|
166
|
+
armed: false,
|
|
167
|
+
follow_up: root
|
|
168
|
+
? "Independent root spawned — it is NOT under you. You are not subscribed, so its finish will NOT wake you and it does not hold you alive; it carries spawned_by=you for lineage only. Inside tmux it opened a viewer in your current session (the client switched to it) so it can be driven directly. Hand it off and move on — you will not be notified of its progress."
|
|
169
|
+
: childFollowUp(parent ?? process.env['CRTR_NODE_ID']),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (hasWhen && hasAt) {
|
|
173
|
+
throw new InputError({ error: 'bad_when_combo', message: '--when cannot combine with --at', next: 'A trigger is either clock-driven (--at/--every) or predicate-driven (--when).' });
|
|
174
|
+
}
|
|
175
|
+
if (hasWhen && !hasEvery) {
|
|
176
|
+
throw new InputError({ error: 'when_needs_every', message: '--when requires --every (the evaluation cadence)', next: 'Pass --every <cadence> alongside --when.' });
|
|
177
|
+
}
|
|
178
|
+
if (hasWhen && !hasTimeout) {
|
|
179
|
+
throw new InputError({ error: 'when_needs_timeout', message: '--when requires --timeout (the wall-clock bound)', next: 'Pass --timeout <duration> alongside --when.' });
|
|
180
|
+
}
|
|
181
|
+
if (hasTimeout && !hasWhen) {
|
|
182
|
+
throw new InputError({ error: 'timeout_needs_when', message: '--timeout is only valid with --when', next: 'Drop --timeout, or pass --when to make this a predicate trigger.' });
|
|
183
|
+
}
|
|
184
|
+
const ownerId = armerId();
|
|
185
|
+
const recipeParent = parent ?? ownerId;
|
|
186
|
+
const recipe = {
|
|
187
|
+
kind,
|
|
188
|
+
mode,
|
|
189
|
+
cwd,
|
|
190
|
+
prompt,
|
|
191
|
+
parent: recipeParent,
|
|
192
|
+
...(name !== undefined ? { name } : {}),
|
|
193
|
+
...(root ? { root: true } : {}),
|
|
194
|
+
...(forkFrom !== undefined ? { forkFrom } : {}),
|
|
195
|
+
...(model !== undefined ? { model } : {}),
|
|
196
|
+
};
|
|
197
|
+
const id = `tr-${newNodeId()}`;
|
|
198
|
+
if (hasWhen) {
|
|
199
|
+
const now = new Date();
|
|
200
|
+
const cad = parseCadence(every, parseOpts(now, undefined));
|
|
201
|
+
if ('error' in cad)
|
|
202
|
+
throwTimeError(cad.error);
|
|
203
|
+
const t = parseWhen(timeout, { now });
|
|
204
|
+
if ('error' in t)
|
|
205
|
+
throwTimeError(t.error);
|
|
206
|
+
assertUnderCap(ownerId);
|
|
207
|
+
armTriggerOrThrow({
|
|
208
|
+
trigger_id: id,
|
|
209
|
+
node_id: null,
|
|
210
|
+
owner_id: ownerId,
|
|
211
|
+
action: 'new',
|
|
212
|
+
action_mode: 'node_birth',
|
|
213
|
+
trigger_type: 'when',
|
|
214
|
+
when_script: whenRaw.trim(),
|
|
215
|
+
eval_every: cad.recur,
|
|
216
|
+
next_eval_at: now.toISOString(),
|
|
217
|
+
timeout_at: t.fireAt,
|
|
218
|
+
payload: recipe,
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
armed: true,
|
|
222
|
+
id,
|
|
223
|
+
kind,
|
|
224
|
+
trigger_type: 'when',
|
|
225
|
+
recur: cadenceDisplay(cad.recur),
|
|
226
|
+
next_eval_at: now.toISOString(),
|
|
227
|
+
timeout_at: t.fireAt,
|
|
228
|
+
follow_up: `Predicate spawn armed — evaluated ${cadenceDisplay(cad.recur)} (first check now), bounded by a wall-clock timeout at ${t.fireAt}. No node exists yet; inspect/cancel via \`crtr node triggers list\` / \`crtr node triggers cancel ${id}\`. Pick up other work or end your turn.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const now = new Date();
|
|
232
|
+
let recur;
|
|
233
|
+
let fireAt;
|
|
234
|
+
if (hasEvery) {
|
|
235
|
+
const cad = parseCadence(every, parseOpts(now, tz));
|
|
236
|
+
if ('error' in cad)
|
|
237
|
+
throwTimeError(cad.error);
|
|
238
|
+
recur = cad.recur;
|
|
239
|
+
fireAt = cad.firstFireAt;
|
|
240
|
+
// BOTH --at and --every (fixed-interval or cron): --at anchors the first
|
|
241
|
+
// fire; recurrence thereafter advances from wall-clock `now` at settle
|
|
242
|
+
// time (nextSlotAfter), independent of the anchor, so this holds for
|
|
243
|
+
// both cadence shapes.
|
|
244
|
+
if (hasAt) {
|
|
245
|
+
const w = parseWhen(at, parseOpts(now, tz));
|
|
246
|
+
if ('error' in w)
|
|
247
|
+
throwTimeError(w.error);
|
|
248
|
+
fireAt = w.fireAt;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const w = parseWhen(at, parseOpts(now, tz));
|
|
253
|
+
if ('error' in w)
|
|
254
|
+
throwTimeError(w.error);
|
|
255
|
+
fireAt = w.fireAt;
|
|
256
|
+
}
|
|
257
|
+
assertUnderCap(ownerId);
|
|
258
|
+
armTriggerOrThrow({
|
|
259
|
+
trigger_id: id,
|
|
260
|
+
node_id: null,
|
|
261
|
+
owner_id: ownerId,
|
|
262
|
+
action: 'new',
|
|
263
|
+
action_mode: 'node_birth',
|
|
264
|
+
trigger_type: recur !== undefined ? 'every' : 'at',
|
|
265
|
+
fire_at: fireAt,
|
|
266
|
+
...(recur !== undefined ? { recur } : {}),
|
|
267
|
+
payload: recipe,
|
|
268
|
+
});
|
|
269
|
+
const eta = etaHint(fireAt, now);
|
|
270
|
+
const follow_up = recur !== undefined
|
|
271
|
+
? `Spawn-cron armed (${cadenceDisplay(recur)}): a fresh ${kind} node is born each fire, first at ${fireAt}${eta}. No node exists yet; the runtime keeps spawning even across your crash/finalize — inspect/cancel via \`crtr node triggers list\` / \`crtr node triggers cancel ${id}\`.`
|
|
272
|
+
: `Deferred spawn armed: a fresh ${kind} node is born at ${fireAt}${eta}. No node exists yet — inspect/cancel via \`crtr node triggers list\` / \`crtr node triggers cancel ${id}\`. Pick up other work or end your turn.`;
|
|
152
273
|
return {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
: childFollowUp(parent ?? process.env['CRTR_NODE_ID']),
|
|
274
|
+
armed: true,
|
|
275
|
+
id,
|
|
276
|
+
kind,
|
|
277
|
+
trigger_type: recur !== undefined ? 'every' : 'at',
|
|
278
|
+
fires_at: fireAt,
|
|
279
|
+
...(recur !== undefined ? { recur: cadenceDisplay(recur) } : {}),
|
|
280
|
+
follow_up,
|
|
161
281
|
};
|
|
162
282
|
},
|
|
163
283
|
render: (r) => {
|
|
284
|
+
if (r['armed'] === true) {
|
|
285
|
+
const recur = r['recur'] ? `, recurs ${r['recur']}` : '';
|
|
286
|
+
const when = r['fires_at'] ? `fires ${r['fires_at']}${recur}` : `next eval ${r['next_eval_at']}, timeout ${r['timeout_at']}`;
|
|
287
|
+
return `Armed deferred spawn ${r['id']} — a ${r['kind']} node, ${when}.\n\n${r['follow_up']}`;
|
|
288
|
+
}
|
|
164
289
|
const win = r['window'] ? ` in window ${r['window']}` : '';
|
|
165
290
|
const lines = [`Spawned "${r['name']}" (${r['node_id']}) — ${r['status']}${win}.`];
|
|
166
291
|
if (r['session'])
|
|
@@ -170,33 +295,77 @@ const nodeNew = defineLeaf({
|
|
|
170
295
|
},
|
|
171
296
|
});
|
|
172
297
|
// ---------------------------------------------------------------------------
|
|
173
|
-
// node list — the active canvas (or a status slice)
|
|
298
|
+
// node inspect list — the active canvas (or a status slice)
|
|
174
299
|
// ---------------------------------------------------------------------------
|
|
175
300
|
const nodeList = defineLeaf({
|
|
176
301
|
name: 'list',
|
|
177
302
|
description: 'list nodes on the canvas',
|
|
178
|
-
whenToUse: 'you want a flat roster of the nodes on the canvas,
|
|
303
|
+
whenToUse: 'you want a flat roster of the nodes on the canvas, sliced by any combination of attribute filters (status, kind, mode, lifecycle, cwd, hanging) and graph scope (--under a node, for that node plus its subscription descendants). The filters AND together, so any attribute filter can be paired with any other, and with --under, to narrow by both criteria and subtree at once. Use `node inspect show` instead to drill into one node and its spine neighbors, `canvas dashboard` for the tree SHAPE, and `canvas attention` to find which nodes are blocked on a human',
|
|
179
304
|
help: {
|
|
180
305
|
name: 'node inspect list',
|
|
181
|
-
summary: 'list nodes on the canvas,
|
|
306
|
+
summary: 'list nodes on the canvas, filtered by any combination of attribute slices and a graph-scope (--under) that AND together',
|
|
182
307
|
params: [
|
|
183
|
-
{ kind: 'flag', name: 'status', type: 'string', required: false, constraint: 'Filter: active
|
|
308
|
+
{ kind: 'flag', name: 'status', type: 'string', required: false, constraint: 'Filter status (comma-separated for several; accepted values: active, idle, done, dead, canceled). NOTE: `active` means the engine process is live (never closed), NOT that the node is generating right now — an active node is usually dormant between turns. To tell working-vs-idle, check the pi session-file mtime or CPU, not status.' },
|
|
309
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Filter by persona kind (comma-separated for several). The <kinds> list below names every installable kind.' },
|
|
310
|
+
{ kind: 'flag', name: 'mode', type: 'string', required: false, constraint: 'Filter by mode (comma-separated for several; accepted values: base, orchestrator).' },
|
|
311
|
+
{ kind: 'flag', name: 'lifecycle', type: 'string', required: false, constraint: 'Filter by lifecycle (comma-separated for several; accepted values: terminal, resident).' },
|
|
312
|
+
{ kind: 'flag', name: 'cwd', type: 'string', required: false, constraint: 'Filter to nodes pinned to this exact cwd (absolute path). Roster is canvas-wide (all cwds) by default.' },
|
|
313
|
+
{ kind: 'flag', name: 'under', type: 'string', required: false, constraint: 'Graph scope: restrict to this node and its subscription descendants (the sub-DAG reachable downward — its reports, theirs, …). This is "one initiative / my subtree". ANDs with every attribute filter above, narrowing any of them to this subtree.' },
|
|
314
|
+
{ kind: 'flag', name: 'hanging', type: 'bool', required: false, constraint: 'Only nodes PARKED on an active fault (live broker + marker when provider-owned). Use to find stuck/rate-limited nodes awaiting auto-revive.' },
|
|
184
315
|
],
|
|
185
316
|
output: [
|
|
186
|
-
{ name: 'nodes', type: 'object[]', required: true, constraint: 'Rows: {node_id, name, kind, mode, lifecycle, status, cwd, parent, created}.' },
|
|
317
|
+
{ name: 'nodes', type: 'object[]', required: true, constraint: 'Rows: {node_id, name, kind, mode, lifecycle, status, cwd, parent, created, hanging}. `hanging` is null normally, or {kind, message, since} when the node is PARKED on an active fault (rate-limit/overloaded/connection/other); the canvas-graph views render it with ⚠ + a countdown or state cue.' },
|
|
187
318
|
],
|
|
188
319
|
outputKind: 'object',
|
|
189
|
-
|
|
320
|
+
dynamicState: () => kindsStateBlock(),
|
|
321
|
+
effects: ['Read-only: queries canvas.db (+ an active-fault marker read).'],
|
|
190
322
|
},
|
|
191
323
|
run: async (input) => {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
324
|
+
const csv = (k) => {
|
|
325
|
+
const raw = input[k];
|
|
326
|
+
return raw !== undefined && raw !== '' ? raw.split(',').map((s) => s.trim()).filter((s) => s !== '') : undefined;
|
|
327
|
+
};
|
|
328
|
+
const status = csv('status');
|
|
329
|
+
const kinds = csv('kind');
|
|
330
|
+
const modes = csv('mode');
|
|
331
|
+
const lifecycles = csv('lifecycle');
|
|
332
|
+
const cwd = input['cwd'];
|
|
333
|
+
const under = input['under'];
|
|
334
|
+
const hangingOnly = input['hanging'] === true;
|
|
335
|
+
// Graph scope (--under): self + subscription descendants. Validate the anchor
|
|
336
|
+
// so a typo'd id is a structured error, not a silently-empty roster.
|
|
337
|
+
let scope;
|
|
338
|
+
if (under !== undefined && under !== '') {
|
|
339
|
+
if (getNode(under) === null) {
|
|
340
|
+
throw new InputError({ error: 'not_found', message: `no node: ${under}`, next: 'List nodes with `crtr node inspect list`, then pass a real id to --under.' });
|
|
341
|
+
}
|
|
342
|
+
scope = new Set([under, ...view(under)]);
|
|
343
|
+
}
|
|
344
|
+
const rows = listNodes(status !== undefined ? { status } : undefined).filter((row) => {
|
|
345
|
+
if (scope !== undefined && !scope.has(row.node_id))
|
|
346
|
+
return false;
|
|
347
|
+
if (kinds !== undefined && !kinds.includes(row.kind))
|
|
348
|
+
return false;
|
|
349
|
+
if (modes !== undefined && !modes.includes(row.mode))
|
|
350
|
+
return false;
|
|
351
|
+
if (lifecycles !== undefined && !lifecycles.includes(row.lifecycle))
|
|
352
|
+
return false;
|
|
353
|
+
if (cwd !== undefined && cwd !== '' && row.cwd !== cwd)
|
|
354
|
+
return false;
|
|
355
|
+
return true;
|
|
356
|
+
});
|
|
357
|
+
// Enrich each row with its active-fault state. Daemon-owned provider faults
|
|
358
|
+
// are suppressed when the broker pid is gone; manual/client faults stay
|
|
359
|
+
// visible until cleared.
|
|
360
|
+
const nodes = await Promise.all(rows.map(async (row) => {
|
|
361
|
+
const hanging = activeFaultForDisplay(row, await readFaultAsync(row.node_id));
|
|
362
|
+
return { ...row, hanging };
|
|
363
|
+
}));
|
|
364
|
+
return { nodes: nodes.filter((n) => !hangingOnly || n.hanging !== null) };
|
|
196
365
|
},
|
|
197
366
|
});
|
|
198
367
|
// ---------------------------------------------------------------------------
|
|
199
|
-
// node show — a node + its place in the spine
|
|
368
|
+
// node inspect show — a node + its place in the spine
|
|
200
369
|
// ---------------------------------------------------------------------------
|
|
201
370
|
const nodeShow = defineLeaf({
|
|
202
371
|
name: 'show',
|
|
@@ -215,6 +384,11 @@ const nodeShow = defineLeaf({
|
|
|
215
384
|
],
|
|
216
385
|
outputKind: 'object',
|
|
217
386
|
effects: ['Read-only: reads the node meta + canvas.db edges.'],
|
|
387
|
+
// The node meta object inlines the full system prompt + every telemetry
|
|
388
|
+
// field — a machine-readable dump with no useful human-glance slice to
|
|
389
|
+
// synthesize (unlike dashboard/list's tabular rows), same rationale as
|
|
390
|
+
// canvas snapshot / node inspect snapshot.
|
|
391
|
+
preview: { suppressOutput: true },
|
|
218
392
|
},
|
|
219
393
|
run: async (input) => {
|
|
220
394
|
const id = input['node'];
|
|
@@ -226,17 +400,17 @@ const nodeShow = defineLeaf({
|
|
|
226
400
|
},
|
|
227
401
|
});
|
|
228
402
|
// ---------------------------------------------------------------------------
|
|
229
|
-
// node inspect — read the graph (list
|
|
403
|
+
// node inspect — read the graph (list, show, snapshot, context)
|
|
230
404
|
// ---------------------------------------------------------------------------
|
|
231
405
|
const nodeInspect = defineBranch({
|
|
232
406
|
name: 'inspect',
|
|
233
|
-
description: 'read the graph (list
|
|
234
|
-
whenToUse: 'reading the graph: enumerate
|
|
407
|
+
description: 'read the graph (list, show, snapshot, context)',
|
|
408
|
+
whenToUse: 'reading the graph: enumerate nodes on the canvas (`list`), inspect one and its spine neighbors (`show`), or pull a dormant session snapshot with `node inspect snapshot` / browse on-disk context dirs with `node inspect context` while reading a node. Use `canvas dashboard` instead to render the tree SHAPE, or `canvas attention` to find which nodes are blocked on a human',
|
|
235
409
|
help: {
|
|
236
410
|
name: 'node inspect',
|
|
237
|
-
summary: 'read the canvas graph — enumerate nodes
|
|
411
|
+
summary: 'read the canvas graph — enumerate nodes, inspect one, or pull snapshot/context views for a node (including dormant session snapshots and on-disk context dirs)',
|
|
238
412
|
},
|
|
239
|
-
children: [nodeList, nodeShow],
|
|
413
|
+
children: [nodeList, nodeShow, nodeSnapshotLeaf, nodeContextLeaf],
|
|
240
414
|
});
|
|
241
415
|
// ---------------------------------------------------------------------------
|
|
242
416
|
// node focus — bring a node's viewer forefront (across roots if needed)
|
|
@@ -244,10 +418,10 @@ const nodeInspect = defineBranch({
|
|
|
244
418
|
const nodeFocus = defineLeaf({
|
|
245
419
|
name: 'focus',
|
|
246
420
|
description: 'bring a node\'s viewer forefront',
|
|
247
|
-
whenToUse: 'you want to bring a specific node into view — open or move its viewer beside your current pane — to watch or steer it directly, reviving its broker first if dormant. Use `node cycle` instead to walk neighbors one viewer at a time rather than jump to a named node, and `node msg` to steer a node without leaving where you are',
|
|
421
|
+
whenToUse: 'you want to bring a specific node into view — open or move its viewer beside your current pane — to watch or steer it directly, reviving its broker first if dormant. Use `node cycle` instead to walk neighbors one viewer at a time rather than jump to a named node, and `node msg --to` to steer a node without leaving where you are',
|
|
248
422
|
help: {
|
|
249
423
|
name: 'node focus',
|
|
250
|
-
summary: 'bring a node\'s viewer forefront — open a
|
|
424
|
+
summary: 'bring a node\'s viewer forefront — open a surface attach view onto the node\'s broker beside your current pane, or navigate to its existing viewer. The engine runs on the broker (the only host); the pane is just a window onto it.',
|
|
251
425
|
params: [
|
|
252
426
|
{ kind: 'positional', name: 'node', required: true, constraint: 'Node id to focus.' },
|
|
253
427
|
{ kind: 'flag', name: 'new-pane', type: 'bool', required: false, constraint: 'Always open a NEW viewer SIDE-BY-SIDE with your current pane instead of moving/reusing the node\'s existing viewer. Two agents on screen at once (F4).' },
|
|
@@ -260,7 +434,7 @@ const nodeFocus = defineLeaf({
|
|
|
260
434
|
{ name: 'in_place', type: 'boolean', required: true, constraint: 'True when an existing viewer was navigated to (no new pane); false when a fresh viewer pane was opened or moved beside you.' },
|
|
261
435
|
],
|
|
262
436
|
outputKind: 'object',
|
|
263
|
-
effects: ['Ensures the node\'s broker engine is alive (revives it if dormant) and opens/moves its one viewer pane (a
|
|
437
|
+
effects: ['Ensures the node\'s broker engine is alive (revives it if dormant) and opens/moves its one viewer pane (a surface attach view) beside the caller, or navigates to it if a live viewer already sits in the caller\'s session.', 'With --new-pane: always splits a fresh viewer beside the caller (two agents on screen) instead of reusing the existing viewer.'],
|
|
264
438
|
},
|
|
265
439
|
run: async (input) => {
|
|
266
440
|
const id = input['node'];
|
|
@@ -282,19 +456,19 @@ const nodeFocus = defineLeaf({
|
|
|
282
456
|
// (revive it if dormant), then open/move its one viewer pane beside the
|
|
283
457
|
// caller (or split a fresh viewer with --new-pane). The reviver is injected
|
|
284
458
|
// so placement need not import revive.ts.
|
|
285
|
-
const res = placementFocus(id, {
|
|
459
|
+
const res = await placementFocus(id, {
|
|
286
460
|
pane: input['pane'],
|
|
287
461
|
newPane: input['newPane'] === true,
|
|
288
|
-
revive: (nid) =>
|
|
462
|
+
revive: (nid) => reviveNode(nid, { resume: true }),
|
|
289
463
|
});
|
|
290
464
|
return { focused: res.focused, session: res.session, revived: res.revived, in_place: res.inPlace };
|
|
291
465
|
},
|
|
292
466
|
});
|
|
293
467
|
// ---------------------------------------------------------------------------
|
|
294
|
-
// node recycle — FINALIZE the agent in your pane + recycle it into a fresh root
|
|
468
|
+
// node lifecycle recycle — FINALIZE the agent in your pane + recycle it into a fresh root
|
|
295
469
|
// (push its last report as a `final` → mark it done, then boot a fresh resident
|
|
296
470
|
// `crtr` root in the SAME pane; see recycleNode in runtime/recycle.ts). NOT
|
|
297
|
-
// bound to any Alt+C key/menu — d/D there route to `node demote` (±--detach),
|
|
471
|
+
// bound to any Alt+C key/menu — d/D there route to `node lifecycle demote` (±--detach),
|
|
298
472
|
// the flip-to-terminal-IN-PLACE action that keeps the agent running.
|
|
299
473
|
// ---------------------------------------------------------------------------
|
|
300
474
|
/** First live node whose window id is `win` (each node owns one window). The
|
|
@@ -308,11 +482,11 @@ function nodeByWindow(win) {
|
|
|
308
482
|
}
|
|
309
483
|
/** The live node "in front of you" in a tmux pane, HOST-AGNOSTIC. Defaults to
|
|
310
484
|
* $TMUX_PANE / the caller's current pane when `pane` is omitted — shared by
|
|
311
|
-
* `node recycle` / `node demote` / `node lifecycle` / `node close` / `node
|
|
485
|
+
* `node lifecycle recycle` / `node lifecycle demote` / `node config --lifecycle` / `node lifecycle close` / `node
|
|
312
486
|
* cycle`, and the `canvas chord` leaf.
|
|
313
487
|
*
|
|
314
488
|
* Two resolutions, tried in order:
|
|
315
|
-
* 1. VIEWER pane (broker host): a `crtr attach` viewer self-tags its pane with
|
|
489
|
+
* 1. VIEWER pane (broker host): a `crtr surface attach` viewer self-tags its pane with
|
|
316
490
|
* the node it views (`@crtr_node`). A broker engine runs DETACHED with
|
|
317
491
|
* window=null, so the window→node lookup never finds it — the pane tag is
|
|
318
492
|
* the only handle. Checked FIRST because the tag is pane-precise, whereas a
|
|
@@ -337,9 +511,9 @@ export function nodeInPane(pane) {
|
|
|
337
511
|
const nodeRecycle = defineLeaf({
|
|
338
512
|
name: 'recycle',
|
|
339
513
|
description: 'finish the agent in your pane + recycle it into a fresh root',
|
|
340
|
-
whenToUse: 'you are at an agent\'s pane and done with it: finish it cleanly and recycle the pane in one move — push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The human-driver way to end an agent and immediately start over in place. Use `node demote` instead to put it on a finishing track IN PLACE (flip terminal, keep it running) without ending it now, `node close` to tear a node and its subtree down WITHOUT finishing (no report, revivable), and `push final` when the agent should finish ITSELF from inside its own turn',
|
|
514
|
+
whenToUse: 'you are at an agent\'s pane and done with it: finish it cleanly and recycle the pane in one move — push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The human-driver way to end an agent and immediately start over in place. Use `node lifecycle demote` instead to put it on a finishing track IN PLACE (flip terminal, keep it running) without ending it now, `node lifecycle close` to tear a node and its subtree down WITHOUT finishing (no report, revivable), and `push final` when the agent should finish ITSELF from inside its own turn',
|
|
341
515
|
help: {
|
|
342
|
-
name: 'node recycle',
|
|
516
|
+
name: 'node lifecycle recycle',
|
|
343
517
|
summary: 'finish the agent in your current pane and recycle the pane — push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane',
|
|
344
518
|
params: [
|
|
345
519
|
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to finish. Defaults to the node occupying --pane (or your current pane).' },
|
|
@@ -390,14 +564,14 @@ const nodeRecycle = defineLeaf({
|
|
|
390
564
|
},
|
|
391
565
|
});
|
|
392
566
|
// ---------------------------------------------------------------------------
|
|
393
|
-
// node close — close a node + cascade-cancel its exclusive subtree (Alt+C → x)
|
|
567
|
+
// node lifecycle close — close a node + cascade-cancel its exclusive subtree (Alt+C → x)
|
|
394
568
|
// ---------------------------------------------------------------------------
|
|
395
569
|
const nodeClose = defineLeaf({
|
|
396
570
|
name: 'close',
|
|
397
571
|
description: 'close a node + cascade-cancel its exclusive subtree (revivable)',
|
|
398
|
-
whenToUse: 'you want to tear a node down WITHOUT finishing it, cascade-cancelling every descendant it exclusively owns: abandoning a line of work, killing a stuck or wrong-turn subtree, clearing a branch you no longer need. Windows die but nothing is deleted — each closed node keeps its pi session and can be revived later (`canvas revive`). Use `node recycle` instead to FINISH the agent in your pane with a final report, and `push final` when a worker should end its own work normally (Alt+C → x)',
|
|
572
|
+
whenToUse: 'you want to tear a node down WITHOUT finishing it, cascade-cancelling every descendant it exclusively owns: abandoning a line of work, killing a stuck or wrong-turn subtree, clearing a branch you no longer need. Windows die but nothing is deleted — each closed node keeps its pi session and can be revived later (`canvas revive`). Use `node lifecycle recycle` instead to FINISH the agent in your pane with a final report, and `push final` when a worker should end its own work normally (Alt+C → x)',
|
|
399
573
|
help: {
|
|
400
|
-
name: 'node close',
|
|
574
|
+
name: 'node lifecycle close',
|
|
401
575
|
summary: 'close a node and cascade-cancel its subtree — kill its tmux window plus those of every descendant it EXCLUSIVELY owns (down the subscribes_to spine), mark them all canceled, and leave each a notice it reads on resume. A descendant still subscribed to by a manager outside the subtree is left running. Nothing is deleted: every closed node keeps its pi session and can be revived later (`crtr canvas revive`)',
|
|
402
576
|
params: [
|
|
403
577
|
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to close. Defaults to the node occupying --pane (or your current pane).' },
|
|
@@ -439,6 +613,160 @@ const nodeClose = defineLeaf({
|
|
|
439
613
|
},
|
|
440
614
|
});
|
|
441
615
|
// ---------------------------------------------------------------------------
|
|
616
|
+
// node config — reconfigure a node's settings headlessly (model/lifecycle/kind/mode/name)
|
|
617
|
+
// ---------------------------------------------------------------------------
|
|
618
|
+
const nodeConfig = defineLeaf({
|
|
619
|
+
name: 'config',
|
|
620
|
+
description: 'reconfigure a node\'s settings headlessly — model, lifecycle (terminal/resident), kind, mode, name',
|
|
621
|
+
whenToUse: 'you want to reconfigure a node\'s settings headlessly after spawn — swap its model/provider, flip it terminal↔resident (interactable), respecialize its kind, switch base↔orchestrator mode, or rename it — without opening its viewer. The durable attribute setter; reuses the same recipes a revive reads. Use `node promote` for the opinionated self-promotion path, and `node lifecycle` for teardown actions (recycle/close/demote).',
|
|
622
|
+
help: {
|
|
623
|
+
name: 'node config',
|
|
624
|
+
summary: 'reconfigure a node\'s settings headlessly — model, lifecycle, kind, mode, name',
|
|
625
|
+
params: [
|
|
626
|
+
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Target node id. Defaults to the node in --pane, else CRTR_NODE_ID.' },
|
|
627
|
+
{ kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id to resolve the node from. Defaults to $TMUX_PANE.' },
|
|
628
|
+
{ kind: 'flag', name: 'model', type: 'string', required: false, constraint: 'Model spec: exact provider/id, capability tier (ultra|strong|medium|light), family alias (opus|sonnet|haiku), or a free-text substring when the broker is live. Dormant nodes require a provider/id, tier, or alias.' },
|
|
629
|
+
{ kind: 'flag', name: 'lifecycle', type: 'enum', choices: ['terminal', 'resident'], required: false, constraint: 'Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable and wakes on inbox/human.' },
|
|
630
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Persona kind. The <kinds> list below names every installable kind and when to use each.' },
|
|
631
|
+
{ kind: 'flag', name: 'mode', type: 'enum', choices: ['base', 'orchestrator'], required: false, constraint: 'Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator seeds a roadmap scaffold if absent.' },
|
|
632
|
+
{ kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Rename the node; if the node has a live window, also rename that viewer window.' },
|
|
633
|
+
],
|
|
634
|
+
output: [
|
|
635
|
+
{ name: 'node_id', type: 'string', required: true, constraint: 'The reconfigured node.' },
|
|
636
|
+
{ name: 'model', type: 'string', required: false, constraint: 'The resolved model, present only when --model changed it.' },
|
|
637
|
+
{ name: 'lifecycle', type: 'string', required: false, constraint: 'The new lifecycle, present only when --lifecycle changed it.' },
|
|
638
|
+
{ name: 'kind', type: 'string', required: false, constraint: 'The new kind, present only when --kind changed it.' },
|
|
639
|
+
{ name: 'mode', type: 'string', required: false, constraint: 'The new mode, present only when --mode changed it.' },
|
|
640
|
+
{ name: 'name', type: 'string', required: false, constraint: 'The new name, present only when --name changed it.' },
|
|
641
|
+
{ name: 'applied', type: 'string[]', required: false, constraint: 'The fields that changed, in application order.' },
|
|
642
|
+
],
|
|
643
|
+
outputKind: 'object',
|
|
644
|
+
effects: [
|
|
645
|
+
'Model change first: live broker switches over its socket; dormant node persists the durable recipe (model_override + launch.model).',
|
|
646
|
+
'Kind, mode, and lifecycle changes rebuild the launch spec from the fresh node meta; setting mode to orchestrator seeds a roadmap scaffold if absent.',
|
|
647
|
+
'Name changes update the row and, when the node already has a live window, rename that viewer window to the new full name.',
|
|
648
|
+
],
|
|
649
|
+
dynamicState: () => kindsStateBlock(),
|
|
650
|
+
},
|
|
651
|
+
run: async (input) => {
|
|
652
|
+
const pane = input['pane'] ?? process.env['TMUX_PANE'];
|
|
653
|
+
const nodeId = resolveLifecycleNode(input, pane);
|
|
654
|
+
const applied = [];
|
|
655
|
+
const modelSpec = input['model']?.trim();
|
|
656
|
+
if (modelSpec !== undefined) {
|
|
657
|
+
if (modelSpec === '') {
|
|
658
|
+
throw new InputError({ error: 'empty_spec', message: 'a model spec is required', field: 'model', next: 'Pass a `provider/id`, a tier (ultra|strong|medium|light), an alias (opus|sonnet|haiku), or a substring.' });
|
|
659
|
+
}
|
|
660
|
+
let resolved;
|
|
661
|
+
const meta = getNode(nodeId);
|
|
662
|
+
if (meta === null)
|
|
663
|
+
throw new InputError({ error: 'not_found', message: `no node: ${nodeId}`, next: 'List nodes with `crtr node inspect list`.' });
|
|
664
|
+
if (isBrokerLive(meta)) {
|
|
665
|
+
try {
|
|
666
|
+
resolved = await setModelLive(nodeId, modelSpec);
|
|
667
|
+
}
|
|
668
|
+
catch (err) {
|
|
669
|
+
throw new InputError({ error: 'switch_failed', message: `live model switch failed: ${err instanceof Error ? err.message : String(err)}`, next: 'Check the spec resolves (`provider/id`, tier, alias, or a substring of a registered model).' });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
const normalized = normalizeModel(modelSpec);
|
|
674
|
+
if (!normalized.includes('/')) {
|
|
675
|
+
throw new InputError({ error: 'unresolvable_dormant', message: `'${modelSpec}' does not resolve to a concrete model for a dormant node`, field: 'model', next: 'Pass an exact `provider/id` (e.g. openai-codex/gpt-5.5) or a tier/alias — the live registry search is only available while the node\'s broker is up.' });
|
|
676
|
+
}
|
|
677
|
+
persistDormantModel(nodeId, normalized);
|
|
678
|
+
resolved = normalized;
|
|
679
|
+
}
|
|
680
|
+
applied.push('model');
|
|
681
|
+
}
|
|
682
|
+
let meta = getNode(nodeId);
|
|
683
|
+
if (meta === null) {
|
|
684
|
+
throw new InputError({ error: 'not_found', message: `no node: ${nodeId}`, next: 'List nodes with `crtr node inspect list`.' });
|
|
685
|
+
}
|
|
686
|
+
const kindSpec = input['kind']?.trim();
|
|
687
|
+
const lifecycleSpec = input['lifecycle']?.trim();
|
|
688
|
+
const modeSpec = input['mode']?.trim();
|
|
689
|
+
if (kindSpec !== undefined || lifecycleSpec !== undefined || modeSpec !== undefined) {
|
|
690
|
+
if (kindSpec !== undefined)
|
|
691
|
+
assertKind(kindSpec);
|
|
692
|
+
let newLifecycle = meta.lifecycle;
|
|
693
|
+
if (lifecycleSpec !== undefined) {
|
|
694
|
+
const value = lifecycleSpec.toLowerCase();
|
|
695
|
+
if (value !== 'terminal' && value !== 'resident') {
|
|
696
|
+
throw new InputError({ error: 'bad_lifecycle', message: `invalid lifecycle: ${lifecycleSpec}`, field: 'lifecycle', next: 'Pass `terminal` or `resident`.' });
|
|
697
|
+
}
|
|
698
|
+
newLifecycle = value;
|
|
699
|
+
}
|
|
700
|
+
let newMode = meta.mode;
|
|
701
|
+
if (modeSpec !== undefined) {
|
|
702
|
+
const value = modeSpec.toLowerCase();
|
|
703
|
+
if (value !== 'base' && value !== 'orchestrator') {
|
|
704
|
+
throw new InputError({ error: 'bad_mode', message: `invalid mode: ${modeSpec}`, field: 'mode', next: 'Pass `base` or `orchestrator`.' });
|
|
705
|
+
}
|
|
706
|
+
newMode = value;
|
|
707
|
+
}
|
|
708
|
+
const newKind = kindSpec ?? meta.kind;
|
|
709
|
+
if (newMode === 'orchestrator' && !hasRoadmap(nodeId)) {
|
|
710
|
+
const goal = readGoal(nodeId);
|
|
711
|
+
seedRoadmap(nodeId, goal !== null && goal.trim() !== '' ? { goal: goal.trim() } : {});
|
|
712
|
+
}
|
|
713
|
+
const { launch } = buildLaunchSpec(newKind, newMode, {
|
|
714
|
+
lifecycle: newLifecycle,
|
|
715
|
+
hasManager: meta.parent !== null,
|
|
716
|
+
model: meta.model_override ?? undefined,
|
|
717
|
+
});
|
|
718
|
+
meta = updateNode(nodeId, {
|
|
719
|
+
...(kindSpec !== undefined ? { kind: newKind } : {}),
|
|
720
|
+
...(lifecycleSpec !== undefined ? { lifecycle: newLifecycle } : {}),
|
|
721
|
+
...(modeSpec !== undefined ? { mode: newMode } : {}),
|
|
722
|
+
launch,
|
|
723
|
+
});
|
|
724
|
+
if (kindSpec !== undefined)
|
|
725
|
+
applied.push('kind');
|
|
726
|
+
if (lifecycleSpec !== undefined)
|
|
727
|
+
applied.push('lifecycle');
|
|
728
|
+
if (modeSpec !== undefined)
|
|
729
|
+
applied.push('mode');
|
|
730
|
+
}
|
|
731
|
+
const nameSpec = input['name'];
|
|
732
|
+
if (nameSpec !== undefined) {
|
|
733
|
+
meta = updateNode(nodeId, { name: nameSpec });
|
|
734
|
+
if (meta.window)
|
|
735
|
+
renameWindow(meta.window, fullName(meta));
|
|
736
|
+
applied.push('name');
|
|
737
|
+
}
|
|
738
|
+
if (applied.length === 0) {
|
|
739
|
+
throw new InputError({ error: 'no_change', message: 'no config changes requested', next: 'Pass at least one of --model, --lifecycle, --kind, --mode, --name.' });
|
|
740
|
+
}
|
|
741
|
+
const result = { node_id: nodeId, applied };
|
|
742
|
+
if (getNode(nodeId)?.model_override !== undefined && applied.includes('model'))
|
|
743
|
+
result.model = getNode(nodeId).model_override;
|
|
744
|
+
if (applied.includes('lifecycle'))
|
|
745
|
+
result.lifecycle = getNode(nodeId).lifecycle;
|
|
746
|
+
if (applied.includes('kind'))
|
|
747
|
+
result.kind = getNode(nodeId).kind;
|
|
748
|
+
if (applied.includes('mode'))
|
|
749
|
+
result.mode = getNode(nodeId).mode;
|
|
750
|
+
if (applied.includes('name'))
|
|
751
|
+
result.name = getNode(nodeId).name;
|
|
752
|
+
return result;
|
|
753
|
+
},
|
|
754
|
+
render: (r) => {
|
|
755
|
+
const bits = [];
|
|
756
|
+
if (r['model'] !== undefined)
|
|
757
|
+
bits.push(`model=${r['model']}`);
|
|
758
|
+
if (r['lifecycle'] !== undefined)
|
|
759
|
+
bits.push(`lifecycle=${r['lifecycle']}`);
|
|
760
|
+
if (r['kind'] !== undefined)
|
|
761
|
+
bits.push(`kind=${r['kind']}`);
|
|
762
|
+
if (r['mode'] !== undefined)
|
|
763
|
+
bits.push(`mode=${r['mode']}`);
|
|
764
|
+
if (r['name'] !== undefined)
|
|
765
|
+
bits.push(`name=${r['name']}`);
|
|
766
|
+
return `Configured ${r['node_id']} — ${bits.join(', ')}`;
|
|
767
|
+
},
|
|
768
|
+
});
|
|
769
|
+
// ---------------------------------------------------------------------------
|
|
442
770
|
// node cycle — DFS-walk the canvas one window at a time (Alt+] / Alt+[)
|
|
443
771
|
// ---------------------------------------------------------------------------
|
|
444
772
|
/** Every live node in DFS pre-order across the whole forest. The spawn tree is
|
|
@@ -513,9 +841,9 @@ const nodeCycle = defineLeaf({
|
|
|
513
841
|
return { focused: false, from: fromId };
|
|
514
842
|
// Placement brings the neighbor's viewer forefront beside the caller,
|
|
515
843
|
// relaunching its broker engine first if it was dormant.
|
|
516
|
-
const res = placementFocus(targetId, {
|
|
844
|
+
const res = await placementFocus(targetId, {
|
|
517
845
|
pane,
|
|
518
|
-
revive: (nid) =>
|
|
846
|
+
revive: (nid) => reviveNode(nid, { resume: true }),
|
|
519
847
|
});
|
|
520
848
|
return { focused: res.focused, node_id: targetId, name: target.name, from: fromId };
|
|
521
849
|
},
|
|
@@ -524,50 +852,403 @@ const nodeCycle = defineLeaf({
|
|
|
524
852
|
: 'No other live node to focus.',
|
|
525
853
|
});
|
|
526
854
|
// ---------------------------------------------------------------------------
|
|
527
|
-
// node msg —
|
|
855
|
+
// node msg — message any node now, or arm a trigger (clock or predicate) that
|
|
856
|
+
// messages/revives it later. See the node-trigger-grammar spec's §Command
|
|
857
|
+
// surface / §node msg.
|
|
528
858
|
// ---------------------------------------------------------------------------
|
|
859
|
+
/** Max pending triggers a single owner may hold. */
|
|
860
|
+
const TRIGGER_CAP = 100;
|
|
861
|
+
/** Resolve the calling node (the armer/owner of a trigger). CRTR_NODE_ID is
|
|
862
|
+
* mandatory — a trigger is owned by the node that arms it (owner_id). */
|
|
863
|
+
function armerId() {
|
|
864
|
+
const id = process.env['CRTR_NODE_ID'];
|
|
865
|
+
if (id === undefined || id === '') {
|
|
866
|
+
throw new InputError({ error: 'no_node', message: 'no node to arm a trigger (CRTR_NODE_ID unset)', next: 'Run from inside a node.' });
|
|
867
|
+
}
|
|
868
|
+
return id;
|
|
869
|
+
}
|
|
870
|
+
/** Build ParseOpts, including `tz` only when actually provided. */
|
|
871
|
+
function parseOpts(now, tz) {
|
|
872
|
+
return tz !== undefined && tz.trim() !== '' ? { tz, now } : { now };
|
|
873
|
+
}
|
|
874
|
+
/** Map a time-grammar error (parseWhen/parseCadence) to the rendered error block. */
|
|
875
|
+
function throwTimeError(e) {
|
|
876
|
+
const next = {
|
|
877
|
+
wake_in_past: 'Pick a future instant — a positive duration ("5m","2h") or an ISO time later than now.',
|
|
878
|
+
bad_when: 'Use a duration ("5m","1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00").',
|
|
879
|
+
bad_cadence: 'Use a duration ("6h"), a 5-field cron ("0 9 * * *"), or an @alias ("@daily").',
|
|
880
|
+
unknown_zone: 'Pass --tz with an IANA zone name (e.g. "America/New_York").',
|
|
881
|
+
cadence_too_fast: 'Use a cadence of at least 60s (e.g. "1m","5m","1h").',
|
|
882
|
+
};
|
|
883
|
+
throw new InputError({ error: e.code, message: e.message, received: e.received, next: next[e.code] ?? 'Fix the time value and retry.' });
|
|
884
|
+
}
|
|
885
|
+
/** Run armTrigger, mapping its thrown integrity backstop (TriggerArmError) to a
|
|
886
|
+
* rendered error block. The surface validates these cases up front, so a throw
|
|
887
|
+
* here is a backstop, not the primary path. */
|
|
888
|
+
function armTriggerOrThrow(spec) {
|
|
889
|
+
try {
|
|
890
|
+
return armTrigger(spec);
|
|
891
|
+
}
|
|
892
|
+
catch (e) {
|
|
893
|
+
if (e instanceof TriggerArmError) {
|
|
894
|
+
const next = {
|
|
895
|
+
empty_body: 'Provide a real body, or use --fresh for a fresh revive with no message.',
|
|
896
|
+
deadline_cannot_recur: 'Drop --every — a deadline cannot recur.',
|
|
897
|
+
bad_action_mode: 'This is a crtr bug — report it.',
|
|
898
|
+
};
|
|
899
|
+
throw new InputError({ error: e.code, message: e.message, next: next[e.code] ?? 'Fix the trigger spec and retry.' });
|
|
900
|
+
}
|
|
901
|
+
throw e;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/** Reject an arm that would push this owner past the pending-triggers cap. */
|
|
905
|
+
function assertUnderCap(ownerId) {
|
|
906
|
+
const pending = listTriggers({ owner: ownerId }).length;
|
|
907
|
+
if (pending >= TRIGGER_CAP) {
|
|
908
|
+
throw new InputError({
|
|
909
|
+
error: 'cap_exceeded',
|
|
910
|
+
message: `you hold ${pending} pending triggers (cap ${TRIGGER_CAP}).`,
|
|
911
|
+
next: 'Reap stale triggers with `crtr node triggers cancel <id>` (see `crtr node triggers list`) before arming more.',
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/** A fresh revive resumes a window with no memory beyond disk, so it needs
|
|
916
|
+
* durable state to wake INTO. Accept it only when the target has a goal
|
|
917
|
+
* (initial-prompt.md) or roadmap (roadmap.md) on disk. */
|
|
918
|
+
function hasRecoverableState(nodeId) {
|
|
919
|
+
const goal = readGoal(nodeId);
|
|
920
|
+
if (goal !== null && goal.trim() !== '')
|
|
921
|
+
return true;
|
|
922
|
+
const roadmap = readRoadmap(nodeId);
|
|
923
|
+
return roadmap !== null && roadmap.trim() !== '';
|
|
924
|
+
}
|
|
925
|
+
/** A human ETA hint like " (~5m)" from a fire-at ISO relative to now. '' if past. */
|
|
926
|
+
function etaHint(fireAt, now) {
|
|
927
|
+
const ms = new Date(fireAt).getTime() - now.getTime();
|
|
928
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
929
|
+
return '';
|
|
930
|
+
const mins = Math.round(ms / 60_000);
|
|
931
|
+
if (mins < 60)
|
|
932
|
+
return ` (~${mins}m)`;
|
|
933
|
+
const hrs = Math.round(ms / 3_600_000);
|
|
934
|
+
if (hrs < 48)
|
|
935
|
+
return ` (~${hrs}h)`;
|
|
936
|
+
return ` (~${Math.round(ms / 86_400_000)}d)`;
|
|
937
|
+
}
|
|
938
|
+
/** Resolve a `node msg` target: exactly one of --to/--self. */
|
|
939
|
+
function resolveMsgTarget(input) {
|
|
940
|
+
const to = input['to']?.trim();
|
|
941
|
+
const self = input['self'] === true;
|
|
942
|
+
const hasTo = to !== undefined && to !== '';
|
|
943
|
+
if (hasTo === self) {
|
|
944
|
+
throw new InputError({
|
|
945
|
+
error: 'bad_target',
|
|
946
|
+
message: hasTo && self ? '--to and --self are mutually exclusive' : 'exactly one of --to or --self is required',
|
|
947
|
+
next: 'Pass --to <node-id> or --self.',
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
if (self) {
|
|
951
|
+
const id = process.env['CRTR_NODE_ID'];
|
|
952
|
+
if (id === undefined || id === '') {
|
|
953
|
+
throw new InputError({ error: 'no_node', message: 'no node to target with --self (CRTR_NODE_ID unset)', next: 'Run from inside a node, or pass --to <node-id>.' });
|
|
954
|
+
}
|
|
955
|
+
return { targetId: id, isSelf: true };
|
|
956
|
+
}
|
|
957
|
+
if (getNode(to) === null) {
|
|
958
|
+
throw new InputError({ error: 'not_found', message: `no node: ${to}`, field: 'to', next: 'List nodes with `crtr node inspect list`.' });
|
|
959
|
+
}
|
|
960
|
+
return { targetId: to, isSelf: false };
|
|
961
|
+
}
|
|
529
962
|
const nodeMsg = defineLeaf({
|
|
530
963
|
name: 'msg',
|
|
531
|
-
description: '
|
|
532
|
-
whenToUse: 'you want to address a specific node directly — steer it mid-flight, hand it a correction, ping it,
|
|
964
|
+
description: 'message any node now, or arm a trigger that messages/revives it later',
|
|
965
|
+
whenToUse: 'you want to address a specific node directly — steer it mid-flight, hand it a correction, ping it, pass it new information, or schedule any of that for later — regardless of subscriptions. Default delivers immediately and revives a dormant target; `--at`/`--every` arm a clock trigger instead of acting now; `--fresh` revives with no inbox entry; `--self --until` puts a deadline backstop on your current inbox-wait; `--when` arms a daemon-evaluated predicate for an external condition (CI, a deploy) you cannot otherwise be pushed for. Use `node subscribe` instead to wire ONGOING push delivery rather than a one-off, `push` to report UP your own spine, and `node new` to schedule the BIRTH of a node rather than message an existing one',
|
|
533
966
|
help: {
|
|
534
967
|
name: 'node msg',
|
|
535
|
-
summary: '
|
|
968
|
+
summary: 'message a node now, or arm a trigger (clock or predicate) that messages/revives it later. No trigger flag → immediate inbox delivery (or immediate fresh revive with --fresh). --at/--every → clock trigger. --self --until → self-only deadline backstop on the current inbox-wait. --when → daemon-evaluated predicate, fires once on exit 0',
|
|
536
969
|
params: [
|
|
537
|
-
{ kind: '
|
|
538
|
-
{ kind: '
|
|
539
|
-
{ kind: 'flag', name: '
|
|
970
|
+
{ kind: 'stdin', name: 'body', required: false, constraint: 'Message body. Positional or stdin. Required for a plain or clock-triggered message and for --until. Forbidden with --fresh. Optional with --when — if omitted, the predicate\'s captured stdout becomes the body when it fires.' },
|
|
971
|
+
{ kind: 'flag', name: 'to', type: 'string', required: false, constraint: 'Target an existing node by id. Exactly one of --to/--self is required.' },
|
|
972
|
+
{ kind: 'flag', name: 'self', type: 'bool', required: false, constraint: 'Target the calling node (CRTR_NODE_ID). Exactly one of --to/--self is required. Required for --until.' },
|
|
973
|
+
{ kind: 'flag', name: 'tier', type: 'enum', choices: ['critical', 'urgent', 'normal', 'deferred'], required: false, constraint: 'Inbox delivery urgency: critical = interrupt + new turn; urgent = steer mid-turn; normal = follow-up; deferred = read on next cycle. Defaults to normal when omitted. Valid for a plain or --when inbox message (immediate or clock-triggered); forbidden with --fresh or --until (--until is always urgent).' },
|
|
974
|
+
{ kind: 'flag', name: 'fresh', type: 'bool', required: false, constraint: 'Fresh-revive the target instead of delivering a message: no inbox entry, just a clean revive (resume:false). Valid alone (revives now) or with --at/--every (arms a clock-triggered fresh revive). Forbidden with a body, --tier, --until, or --when. Rejected if the target has no goal/roadmap on disk (would resume amnesiac).' },
|
|
975
|
+
{ kind: 'flag', name: 'at', type: 'string', required: false, constraint: 'Arm a one-shot clock trigger at this future time instead of acting now — a duration ("5m","1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00", host-local or in --tz). Required unless --every is given (the cadence then sets the first fire). Mutually exclusive with --when.' },
|
|
976
|
+
{ kind: 'flag', name: 'every', type: 'string', required: false, constraint: 'Arm a recurring clock trigger on this cadence — a duration ("6h","30m") or a 5-field cron / @alias ("0 9 * * *","@daily"). Combine with --at to anchor the first fire, then recur normally. Min cadence 60s. With --when, this instead sets the PREDICATE EVALUATION cadence (required), not a repeating action.' },
|
|
977
|
+
{ kind: 'flag', name: 'when', type: 'string', required: false, constraint: 'Arm a daemon-evaluated bash predicate instead of a clock trigger: exit 0 fires the message once, non-zero leaves it armed for the next evaluation. Requires --every (eval cadence) and --timeout (wall-clock bound). Mutually exclusive with --at, --fresh, and --until.' },
|
|
978
|
+
{ kind: 'flag', name: 'until', type: 'string', required: false, constraint: 'Self-only: bind a deadline to your current dormancy — you wake on the first inbox message, or at this time if none arrives (whichever fires first cancels the other). One-shot, urgent tier, ≤ 1 per node (a new --until replaces any prior). Mutually exclusive with --at, --every, --when, --fresh, --tier, and --timeout. Requires --self and a body.' },
|
|
979
|
+
{ kind: 'flag', name: 'timeout', type: 'string', required: false, constraint: 'Wall-clock bound for a --when predicate — a duration or absolute time after which the trigger is consumed and the owner gets one urgent notice. Required with --when; rejected otherwise.' },
|
|
980
|
+
{ kind: 'flag', name: 'tz', type: 'string', required: false, constraint: 'IANA zone for a calendar --every, a bare-ISO --at, or a bare-ISO --until (default: host-local). Valid only alongside --at/--every/--until; rejected with --when, and rejected when acting immediately (no trigger flags) since none of those have anything to parse it against.' },
|
|
540
981
|
],
|
|
541
982
|
output: [
|
|
542
|
-
{ name: '
|
|
543
|
-
{ name: '
|
|
544
|
-
{ name: '
|
|
983
|
+
{ name: 'mode', type: 'string', required: true, constraint: '"inbox_message", "fresh_revive", or "deadline_message" — what this message enacts.' },
|
|
984
|
+
{ name: 'armed', type: 'boolean', required: true, constraint: 'True when a trigger row was armed for later (--at/--every/--when/--until); false when the action happened immediately.' },
|
|
985
|
+
{ name: 'target', type: 'string', required: true, constraint: '"self" or the target node id.' },
|
|
986
|
+
{ name: 'delivered', type: 'boolean', required: false, constraint: 'True when an inbox message was delivered immediately (armed=false, mode=inbox_message).' },
|
|
987
|
+
{ name: 'revived', type: 'boolean', required: false, constraint: 'True when a dormant target was revived to receive an immediate delivery, or when an immediate --fresh fired the revive now.' },
|
|
988
|
+
{ name: 'id', type: 'string', required: false, constraint: 'The trigger id (cancel/list it via `node triggers`), present only when armed=true.' },
|
|
989
|
+
{ name: 'trigger_type', type: 'string', required: false, constraint: '"at", "every", "when", or "until", present only when armed=true.' },
|
|
990
|
+
{ name: 'fires_at', type: 'string', required: false, constraint: 'Absolute UTC fire time, present only for an armed clock or deadline trigger.' },
|
|
991
|
+
{ name: 'next_eval_at', type: 'string', required: false, constraint: 'The next predicate-evaluation instant (always "now" at arm time), present only for an armed --when trigger.' },
|
|
992
|
+
{ name: 'timeout_at', type: 'string', required: false, constraint: 'Absolute UTC wall-clock bound, present only for an armed --when trigger.' },
|
|
993
|
+
{ name: 'recur', type: 'string', required: false, constraint: 'Cadence display ("every 6h", cron, or eval cadence for --when), present only when --every was given.' },
|
|
994
|
+
{ name: 'guidance', type: 'string', required: true, constraint: 'What to do now — end your turn to go dormant when armed; delivery confirmation when immediate.' },
|
|
545
995
|
],
|
|
546
996
|
outputKind: 'object',
|
|
547
|
-
effects: [
|
|
997
|
+
effects: [
|
|
998
|
+
'Immediate inbox_message: appends a message entry to the target inbox.jsonl and best-effort revives the target if dormant.',
|
|
999
|
+
'Immediate fresh_revive: revives the target now with no inbox entry (resume:false).',
|
|
1000
|
+
'Armed (--at/--every/--when/--until): inserts one triggers row; nothing fires/evaluates until the daemon\'s next pass. No pi spawn, no transition — end your turn separately to go dormant.',
|
|
1001
|
+
'--until upserts the node\'s single deadline trigger, replacing any prior.',
|
|
1002
|
+
],
|
|
548
1003
|
},
|
|
549
1004
|
run: async (input) => {
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const
|
|
1005
|
+
const { targetId, isSelf } = resolveMsgTarget(input);
|
|
1006
|
+
const bodyRaw = input['body'];
|
|
1007
|
+
const body = bodyRaw !== undefined ? bodyRaw.trim() : undefined;
|
|
1008
|
+
const hasBody = body !== undefined && body !== '';
|
|
1009
|
+
const tierRaw = input['tier'];
|
|
1010
|
+
const hasTier = tierRaw !== undefined && tierRaw !== '';
|
|
1011
|
+
const fresh = input['fresh'] === true;
|
|
1012
|
+
const at = input['at']?.trim();
|
|
1013
|
+
const every = input['every']?.trim();
|
|
1014
|
+
const whenRaw = input['when'];
|
|
1015
|
+
const hasWhen = whenRaw !== undefined && whenRaw.trim() !== '';
|
|
1016
|
+
const until = input['until']?.trim();
|
|
1017
|
+
const hasUntil = until !== undefined && until !== '';
|
|
1018
|
+
const timeout = input['timeout']?.trim();
|
|
1019
|
+
const hasTimeout = timeout !== undefined && timeout !== '';
|
|
1020
|
+
const tz = input['tz'];
|
|
1021
|
+
const hasAt = at !== undefined && at !== '';
|
|
1022
|
+
const hasEvery = every !== undefined && every !== '';
|
|
1023
|
+
// --- Validation per the approved flag matrix ---
|
|
1024
|
+
if (hasUntil && !isSelf) {
|
|
1025
|
+
throw new InputError({ error: 'until_self_only', message: '--until is self-only', field: 'until', next: 'Use --self, or drop --until.' });
|
|
1026
|
+
}
|
|
1027
|
+
if (hasUntil && (hasAt || hasEvery || hasWhen || fresh || hasTier || hasTimeout)) {
|
|
1028
|
+
throw new InputError({ error: 'bad_until_combo', message: '--until cannot combine with --at, --every, --when, --fresh, --tier, or --timeout', next: 'Drop the conflicting flag(s), or remove --until for a different trigger.' });
|
|
1029
|
+
}
|
|
1030
|
+
if (hasWhen && (hasAt || fresh)) {
|
|
1031
|
+
throw new InputError({ error: 'bad_when_combo', message: '--when cannot combine with --at or --fresh', next: 'A trigger is either clock-driven (--at/--every/--fresh) or predicate-driven (--when).' });
|
|
1032
|
+
}
|
|
1033
|
+
if (hasWhen && !hasEvery) {
|
|
1034
|
+
throw new InputError({ error: 'when_needs_every', message: '--when requires --every (the evaluation cadence)', next: 'Pass --every <cadence> alongside --when.' });
|
|
1035
|
+
}
|
|
1036
|
+
if (hasWhen && !hasTimeout) {
|
|
1037
|
+
throw new InputError({ error: 'when_needs_timeout', message: '--when requires --timeout (the wall-clock bound)', next: 'Pass --timeout <duration> alongside --when.' });
|
|
1038
|
+
}
|
|
1039
|
+
if (hasTimeout && !hasWhen) {
|
|
1040
|
+
throw new InputError({ error: 'timeout_needs_when', message: '--timeout is only valid with --when', next: 'Drop --timeout, or pass --when to make this a predicate trigger.' });
|
|
1041
|
+
}
|
|
1042
|
+
if (fresh && (hasBody || hasTier || hasUntil || hasWhen)) {
|
|
1043
|
+
throw new InputError({ error: 'bad_fresh_combo', message: '--fresh cannot combine with a body, --tier, --until, or --when', next: 'Drop the conflicting flag(s) — --fresh is a fresh revive, not a message.' });
|
|
1044
|
+
}
|
|
1045
|
+
const hasTz = tz !== undefined && tz.trim() !== '';
|
|
1046
|
+
if (hasTz && hasWhen) {
|
|
1047
|
+
throw new InputError({ error: 'tz_not_valid_with_when', message: '--tz has no effect with --when (predicate evaluation parses no calendar timezone)', field: 'tz', next: 'Drop --tz.' });
|
|
1048
|
+
}
|
|
1049
|
+
if (hasTz && !hasAt && !hasEvery && !hasUntil) {
|
|
1050
|
+
throw new InputError({ error: 'tz_needs_trigger', message: '--tz has no effect without --at, --every, or --until to parse it against', field: 'tz', next: 'Drop --tz, or pass --at/--every/--until.' });
|
|
1051
|
+
}
|
|
1052
|
+
const target = isSelf ? 'self' : targetId;
|
|
1053
|
+
// --- Deadline (--until): self-only, one-shot, urgent ---
|
|
1054
|
+
if (hasUntil) {
|
|
1055
|
+
if (!hasBody) {
|
|
1056
|
+
throw new InputError({ error: 'empty_body', message: 'a body is required for --until', field: 'body', next: 'Pass a timeout playbook after the target flags or on stdin.' });
|
|
1057
|
+
}
|
|
1058
|
+
const owner = armerId();
|
|
1059
|
+
const now = new Date();
|
|
1060
|
+
const w = parseWhen(until, parseOpts(now, tz));
|
|
1061
|
+
if ('error' in w)
|
|
1062
|
+
throwTimeError(w.error);
|
|
1063
|
+
assertUnderCap(owner);
|
|
1064
|
+
const payload = { body: body, timeout: true, label: body.split('\n')[0].slice(0, 120) };
|
|
1065
|
+
const id = `tr-${newNodeId()}`;
|
|
1066
|
+
armTriggerOrThrow({
|
|
1067
|
+
trigger_id: id,
|
|
1068
|
+
node_id: targetId,
|
|
1069
|
+
owner_id: owner,
|
|
1070
|
+
action: 'msg',
|
|
1071
|
+
action_mode: 'deadline_message',
|
|
1072
|
+
trigger_type: 'until',
|
|
1073
|
+
fire_at: w.fireAt,
|
|
1074
|
+
payload,
|
|
1075
|
+
});
|
|
1076
|
+
const eta = etaHint(w.fireAt, now);
|
|
1077
|
+
const guidance = `Deadline armed. You wake on the first inbox message, or at ${w.fireAt}${eta} if none arrives. Now delegate / end your turn to go dormant — whichever fires first cancels the other.`;
|
|
1078
|
+
return { mode: 'deadline_message', armed: true, id, target, trigger_type: 'until', fires_at: w.fireAt, guidance };
|
|
1079
|
+
}
|
|
1080
|
+
// --- Predicate (--when) ---
|
|
1081
|
+
if (hasWhen) {
|
|
1082
|
+
const owner = armerId();
|
|
1083
|
+
const now = new Date();
|
|
1084
|
+
const cad = parseCadence(every, parseOpts(now, undefined));
|
|
1085
|
+
if ('error' in cad)
|
|
1086
|
+
throwTimeError(cad.error);
|
|
1087
|
+
const t = parseWhen(timeout, { now });
|
|
1088
|
+
if ('error' in t)
|
|
1089
|
+
throwTimeError(t.error);
|
|
1090
|
+
assertUnderCap(owner);
|
|
1091
|
+
// A supplied body is delivered as-is; omitted body means stdout becomes the
|
|
1092
|
+
// body when the predicate fires (spec's stdout-as-body rule) — armTrigger
|
|
1093
|
+
// accepts a body-less payload only for this trigger_type='when' case.
|
|
1094
|
+
const payload = {
|
|
1095
|
+
...(hasBody ? { body: body } : {}),
|
|
1096
|
+
label: hasBody ? body.split('\n')[0].slice(0, 120) : `(stdout) ${whenRaw.trim().split('\n')[0].slice(0, 110)}`,
|
|
1097
|
+
...(hasTier ? { tier: tierRaw } : {}),
|
|
1098
|
+
};
|
|
1099
|
+
const id = `tr-${newNodeId()}`;
|
|
1100
|
+
armTriggerOrThrow({
|
|
1101
|
+
trigger_id: id,
|
|
1102
|
+
node_id: targetId,
|
|
1103
|
+
owner_id: owner,
|
|
1104
|
+
action: 'msg',
|
|
1105
|
+
action_mode: 'inbox_message',
|
|
1106
|
+
trigger_type: 'when',
|
|
1107
|
+
when_script: whenRaw.trim(),
|
|
1108
|
+
eval_every: cad.recur,
|
|
1109
|
+
next_eval_at: now.toISOString(),
|
|
1110
|
+
timeout_at: t.fireAt,
|
|
1111
|
+
payload,
|
|
1112
|
+
});
|
|
1113
|
+
const guidance = `Predicate trigger armed — evaluated ${cadenceDisplay(cad.recur)} (first check now), bounded by a wall-clock timeout at ${t.fireAt}. Fires once the predicate exits 0; non-zero leaves it armed. End your turn to go dormant; do not push final.`;
|
|
1114
|
+
return {
|
|
1115
|
+
mode: 'inbox_message',
|
|
1116
|
+
armed: true,
|
|
1117
|
+
id,
|
|
1118
|
+
target,
|
|
1119
|
+
trigger_type: 'when',
|
|
1120
|
+
next_eval_at: now.toISOString(),
|
|
1121
|
+
timeout_at: t.fireAt,
|
|
1122
|
+
recur: cadenceDisplay(cad.recur),
|
|
1123
|
+
guidance,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
// --- Clock trigger (--at / --every), no --when/--until ---
|
|
1127
|
+
if (hasAt || hasEvery) {
|
|
1128
|
+
if (fresh) {
|
|
1129
|
+
if (!hasRecoverableState(targetId)) {
|
|
1130
|
+
throw new InputError({
|
|
1131
|
+
error: 'fresh_no_recoverable_state',
|
|
1132
|
+
message: `node ${targetId} has no goal/roadmap on disk — a fresh revive would resume amnesiac.`,
|
|
1133
|
+
next: 'Seed a goal/roadmap first, or drop --fresh so the woken context carries a message.',
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
else if (!hasBody) {
|
|
1138
|
+
throw new InputError({ error: 'empty_body', message: 'a message body is required (or --fresh for a clock-triggered revive)', field: 'body', next: 'Pass the message after the target flags or on stdin.' });
|
|
1139
|
+
}
|
|
1140
|
+
const owner = armerId();
|
|
1141
|
+
const now = new Date();
|
|
1142
|
+
let recur;
|
|
1143
|
+
let fireAt;
|
|
1144
|
+
if (hasEvery) {
|
|
1145
|
+
const cad = parseCadence(every, parseOpts(now, tz));
|
|
1146
|
+
if ('error' in cad)
|
|
1147
|
+
throwTimeError(cad.error);
|
|
1148
|
+
recur = cad.recur;
|
|
1149
|
+
fireAt = cad.firstFireAt;
|
|
1150
|
+
// BOTH --at and --every (fixed-interval or cron): --at anchors the first
|
|
1151
|
+
// fire; recurrence thereafter advances from wall-clock `now` at settle
|
|
1152
|
+
// time (nextSlotAfter), independent of the anchor, so this holds for
|
|
1153
|
+
// both cadence shapes.
|
|
1154
|
+
if (hasAt) {
|
|
1155
|
+
const w = parseWhen(at, parseOpts(now, tz));
|
|
1156
|
+
if ('error' in w)
|
|
1157
|
+
throwTimeError(w.error);
|
|
1158
|
+
fireAt = w.fireAt;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
const w = parseWhen(at, parseOpts(now, tz));
|
|
1163
|
+
if ('error' in w)
|
|
1164
|
+
throwTimeError(w.error);
|
|
1165
|
+
fireAt = w.fireAt;
|
|
1166
|
+
}
|
|
1167
|
+
assertUnderCap(owner);
|
|
1168
|
+
const actionMode = fresh ? 'fresh_revive' : 'inbox_message';
|
|
1169
|
+
const payload = fresh
|
|
1170
|
+
? null
|
|
1171
|
+
: { body: body, label: body.split('\n')[0].slice(0, 120), ...(hasTier ? { tier: tierRaw } : {}) };
|
|
1172
|
+
const id = `tr-${newNodeId()}`;
|
|
1173
|
+
armTriggerOrThrow({
|
|
1174
|
+
trigger_id: id,
|
|
1175
|
+
node_id: targetId,
|
|
1176
|
+
owner_id: owner,
|
|
1177
|
+
action: 'msg',
|
|
1178
|
+
action_mode: actionMode,
|
|
1179
|
+
trigger_type: recur !== undefined ? 'every' : 'at',
|
|
1180
|
+
fire_at: fireAt,
|
|
1181
|
+
...(recur !== undefined ? { recur } : {}),
|
|
1182
|
+
payload,
|
|
1183
|
+
});
|
|
1184
|
+
const eta = etaHint(fireAt, now);
|
|
1185
|
+
const guidance = recur !== undefined
|
|
1186
|
+
? `${fresh ? 'Fresh-revive' : 'Message'} recurrence armed (${cadenceDisplay(recur)}); first fire ${fireAt}${eta}. End your turn to go dormant; do not push final. The runtime keeps firing it even across your crash/finalize — cancel with \`crtr node triggers cancel ${id}\`.`
|
|
1187
|
+
: fresh
|
|
1188
|
+
? `Fresh-revive armed. The target wakes in a fresh window at ${fireAt}${eta}, re-reading its roadmap. End your turn now to go dormant; do not push final.`
|
|
1189
|
+
: `Message armed. It is delivered at ${fireAt}${eta}. End your turn now to go dormant; do not push final.`;
|
|
1190
|
+
return {
|
|
1191
|
+
mode: actionMode,
|
|
1192
|
+
armed: true,
|
|
1193
|
+
id,
|
|
1194
|
+
target,
|
|
1195
|
+
trigger_type: recur !== undefined ? 'every' : 'at',
|
|
1196
|
+
fires_at: fireAt,
|
|
1197
|
+
...(recur !== undefined ? { recur: cadenceDisplay(recur) } : {}),
|
|
1198
|
+
guidance,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
// --- Immediate (no trigger flag) ---
|
|
1202
|
+
if (fresh) {
|
|
1203
|
+
if (!hasRecoverableState(targetId)) {
|
|
1204
|
+
throw new InputError({
|
|
1205
|
+
error: 'fresh_no_recoverable_state',
|
|
1206
|
+
message: `node ${targetId} has no goal/roadmap on disk — a fresh revive would resume amnesiac.`,
|
|
1207
|
+
next: 'Seed a goal/roadmap first, or drop --fresh so the woken context carries a message.',
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
reviveNode(targetId, { resume: false });
|
|
1211
|
+
return {
|
|
1212
|
+
mode: 'fresh_revive',
|
|
1213
|
+
armed: false,
|
|
1214
|
+
target,
|
|
1215
|
+
revived: true,
|
|
1216
|
+
guidance: `Fresh-revived ${isSelf ? 'yourself' : targetId} now — it resumes a clean window re-reading its roadmap/disk.`,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
if (!hasBody) {
|
|
1220
|
+
throw new InputError({ error: 'empty_body', message: 'a message body is required (or --fresh / --when)', field: 'body', next: 'Pass the message after the target flags or on stdin.' });
|
|
1221
|
+
}
|
|
1222
|
+
const targetMeta = getNode(targetId);
|
|
1223
|
+
if (targetMeta === null) {
|
|
1224
|
+
throw new InputError({ error: 'not_found', message: `no node: ${targetId}`, next: 'List nodes with `crtr node inspect list`.' });
|
|
1225
|
+
}
|
|
1226
|
+
const tier = (hasTier ? tierRaw : 'normal');
|
|
558
1227
|
const from = process.env['CRTR_NODE_ID'] ?? 'human';
|
|
559
|
-
|
|
1228
|
+
// A long body clips in the receiver's digest; appendInbox mints a short `id`
|
|
1229
|
+
// for it so the receiver can read the full text back via `feed message <id>`.
|
|
1230
|
+
appendInbox(targetId, { from, tier, kind: 'message', label: body.split('\n')[0].slice(0, 120), data: { body: body } });
|
|
560
1231
|
// A direct message wakes any node: if the target has no live window
|
|
561
1232
|
// (done/dead/idle-released), revive it so its inbox-watcher delivers this.
|
|
562
1233
|
let woke = false;
|
|
563
|
-
if (!windowAlive(
|
|
1234
|
+
if (!windowAlive(targetMeta.tmux_session, targetMeta.window)) {
|
|
564
1235
|
try {
|
|
565
|
-
reviveNode(
|
|
1236
|
+
void reviveNode(targetId, { resume: true });
|
|
566
1237
|
woke = true;
|
|
567
1238
|
}
|
|
568
|
-
catch {
|
|
1239
|
+
catch {
|
|
1240
|
+
/* best-effort wake */
|
|
1241
|
+
}
|
|
569
1242
|
}
|
|
570
|
-
return { delivered: true,
|
|
1243
|
+
return { mode: 'inbox_message', armed: false, target, delivered: true, revived: woke, guidance: woke ? 'Delivered — the dormant target was revived to receive it.' : 'Delivered.' };
|
|
1244
|
+
},
|
|
1245
|
+
render: (r) => {
|
|
1246
|
+
if (r['armed'] === true) {
|
|
1247
|
+
const recur = r['recur'] ? `, recurs ${r['recur']}` : '';
|
|
1248
|
+
const when = r['fires_at'] ? `fires ${r['fires_at']}${recur}` : `next eval ${r['next_eval_at']}, timeout ${r['timeout_at']}`;
|
|
1249
|
+
return `Armed ${r['mode']} trigger ${r['id']} (${r['trigger_type']}) — ${when}, target ${r['target']}.\n\n${r['guidance']}`;
|
|
1250
|
+
}
|
|
1251
|
+
return `${r['guidance']}`;
|
|
571
1252
|
},
|
|
572
1253
|
});
|
|
573
1254
|
// ---------------------------------------------------------------------------
|
|
@@ -657,7 +1338,7 @@ const nodePromote = defineLeaf({
|
|
|
657
1338
|
name: 'node promote',
|
|
658
1339
|
summary: 'promote yourself to an orchestrator — do this when your task outgrows one context window (many phases to delegate and persist across refreshes); not for work that fits one window, and not merely because you spawned a child. Mode only — lifecycle stays as-is unless you pass --resident',
|
|
659
1340
|
params: [
|
|
660
|
-
{ kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator. The <kinds> list below names every installable kind and when to use each
|
|
1341
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator. The <kinds> list below names every installable kind and when to use each. For an exact composed persona list, use `crtr sys prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one — it sets the orchestrator persona you revive into.' },
|
|
661
1342
|
{ kind: 'flag', name: 'resident', type: 'bool', required: false, constraint: 'ALSO flip lifecycle→resident: make the node interactable — it stays dormant, woken by inbox/human, and is never forced to submit a final. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done).' },
|
|
662
1343
|
{ kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong'], required: false, constraint: 'Change the model you run on, by capability tier: `ultra` (frontier — reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus — regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives.' },
|
|
663
1344
|
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to promote. Defaults to the caller (CRTR_NODE_ID).' },
|
|
@@ -691,10 +1372,11 @@ const nodePromote = defineLeaf({
|
|
|
691
1372
|
});
|
|
692
1373
|
// ---------------------------------------------------------------------------
|
|
693
1374
|
// Shared lifecycle plumbing — the single implementation behind BOTH `node
|
|
694
|
-
// demote` (the friendly terminal-only verb that pairs with `node
|
|
695
|
-
// `node lifecycle` (the low-level orthogonal flip, which
|
|
1375
|
+
// lifecycle demote` (the friendly terminal-only verb that pairs with `node
|
|
1376
|
+
// promote`) and `node config --lifecycle` (the low-level orthogonal flip, which
|
|
1377
|
+
// also does resident).
|
|
696
1378
|
// ---------------------------------------------------------------------------
|
|
697
|
-
/** Resolve the node a demote/
|
|
1379
|
+
/** Resolve the node a demote/config command acts on: explicit --node, else
|
|
698
1380
|
* the node occupying --pane (the Alt+C menu passes #{pane_id}), else the caller
|
|
699
1381
|
* (CRTR_NODE_ID). Throws a rendered error when none resolves or it is unknown. */
|
|
700
1382
|
function resolveLifecycleNode(input, pane) {
|
|
@@ -714,8 +1396,8 @@ function resolveLifecycleNode(input, pane) {
|
|
|
714
1396
|
* revive comes back with the new lifecycle's prompt baked in (the live session
|
|
715
1397
|
* is steered by the persona injector; this fixes the static prompt the daemon
|
|
716
1398
|
* replays). Spine is fixed by parent-ness, so it carries through unchanged.
|
|
717
|
-
* The ONE implementation `node demote` (always terminal) and
|
|
718
|
-
* (the passed value) both call — no duplication. */
|
|
1399
|
+
* The ONE implementation `node lifecycle demote` (always terminal) and
|
|
1400
|
+
* `node config --lifecycle` (the passed value) both call — no duplication. */
|
|
719
1401
|
function setLifecycle(id, value, opts) {
|
|
720
1402
|
const target = getNode(id);
|
|
721
1403
|
const { launch } = buildLaunchSpec(target.kind, target.mode, {
|
|
@@ -733,18 +1415,18 @@ function setLifecycle(id, value, opts) {
|
|
|
733
1415
|
return { node_id: meta.node_id, lifecycle: meta.lifecycle, detached };
|
|
734
1416
|
}
|
|
735
1417
|
// ---------------------------------------------------------------------------
|
|
736
|
-
// node demote — flip a node to TERMINAL in place (the friendly half
|
|
737
|
-
// promote/demote pair; bound to Alt+C → d, and → D with --detach). It
|
|
738
|
-
// focused and running but now owes a final up the spine. A terminal-only
|
|
739
|
-
// over the shared setLifecycle plumbing; `node lifecycle` is the
|
|
740
|
-
// low-level command (it also does resident). See vision F5.
|
|
1418
|
+
// node lifecycle demote — flip a node to TERMINAL in place (the friendly half
|
|
1419
|
+
// of the promote/demote pair; bound to Alt+C → d, and → D with --detach). It
|
|
1420
|
+
// stays focused and running but now owes a final up the spine. A terminal-only
|
|
1421
|
+
// skin over the shared setLifecycle plumbing; `node config --lifecycle` is the
|
|
1422
|
+
// orthogonal low-level command (it also does resident). See vision F5.
|
|
741
1423
|
// ---------------------------------------------------------------------------
|
|
742
1424
|
const nodeDemote = defineLeaf({
|
|
743
1425
|
name: 'demote',
|
|
744
1426
|
description: 'demote a node to terminal in place; it stays focused and running but now owes a final; --detach also closes its viewer (broker keeps running off-screen)',
|
|
745
|
-
whenToUse: 'you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE — it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node lifecycle` for the orthogonal low-level flip (incl. terminal→resident)',
|
|
1427
|
+
whenToUse: 'you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE — it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node lifecycle recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node config --lifecycle` for the orthogonal low-level flip (incl. terminal→resident)',
|
|
746
1428
|
help: {
|
|
747
|
-
name: 'node demote',
|
|
1429
|
+
name: 'node lifecycle demote',
|
|
748
1430
|
summary: 'demote a node to terminal IN PLACE — it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode↑); `--detach` also closes its viewer (the broker engine keeps running off-screen)',
|
|
749
1431
|
params: [
|
|
750
1432
|
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to demote. Defaults to the node in --pane, else the caller (CRTR_NODE_ID).' },
|
|
@@ -768,41 +1450,17 @@ const nodeDemote = defineLeaf({
|
|
|
768
1450
|
render: (r) => `Demoted ${r['node_id']} — lifecycle now ${r['lifecycle']} (in place)${r['detached'] === true ? ', viewer closed (broker still running off-screen)' : ''}.`,
|
|
769
1451
|
});
|
|
770
1452
|
// ---------------------------------------------------------------------------
|
|
771
|
-
// node lifecycle —
|
|
772
|
-
// of mode. The persona injector delivers the transition guidance.
|
|
1453
|
+
// node lifecycle — recycle, close, or demote a node
|
|
773
1454
|
// ---------------------------------------------------------------------------
|
|
774
|
-
const nodeLifecycle =
|
|
1455
|
+
const nodeLifecycle = defineBranch({
|
|
775
1456
|
name: 'lifecycle',
|
|
776
|
-
description: '
|
|
777
|
-
whenToUse: 'you
|
|
1457
|
+
description: 'lifecycle actions — recycle, close, demote a node',
|
|
1458
|
+
whenToUse: 'you are ending or transitioning a node\'s life: finish the agent in your pane and reboot a fresh root (`recycle`), tear a node + its exclusive subtree down without finishing (`close`), or flip a node onto a finishing track in place (`demote`). To merely SET the terminal/resident attribute headlessly use `node config --lifecycle`; to FINISH from inside a node\'s own turn use `crtr push final`.',
|
|
778
1459
|
help: {
|
|
779
1460
|
name: 'node lifecycle',
|
|
780
|
-
summary: '
|
|
781
|
-
params: [
|
|
782
|
-
{ kind: 'positional', name: 'lifecycle', required: true, constraint: 'terminal | resident.' },
|
|
783
|
-
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to change. Defaults to the node in --pane, else the caller (CRTR_NODE_ID).' },
|
|
784
|
-
{ kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id whose node to change, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you.' },
|
|
785
|
-
{ kind: 'flag', name: 'detach', type: 'bool', required: false, constraint: 'After flipping lifecycle, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and — now terminal — pushes a final up the spine when done; a later `focus` reattaches a viewer. The human-driver "I am done foregrounding this" move (Alt+C → D).' },
|
|
786
|
-
],
|
|
787
|
-
output: [
|
|
788
|
-
{ name: 'node_id', type: 'string', required: true, constraint: 'The node.' },
|
|
789
|
-
{ name: 'lifecycle', type: 'string', required: true, constraint: 'Its new lifecycle (terminal | resident).' },
|
|
790
|
-
{ name: 'detached', type: 'boolean', required: false, constraint: 'True when --detach closed the agent\'s viewer pane (broker keeps running).' },
|
|
791
|
-
],
|
|
792
|
-
outputKind: 'object',
|
|
793
|
-
effects: ['Sets lifecycle on the node meta and rebuilds its launch spec so a future revive boots with the new lifecycle\'s prompt baked in.', 'The persona injector delivers the transition guidance at the next turn boundary (or on the node\'s next revive if it is dormant).', 'With --detach: closes the agent\'s viewer pane WITHOUT touching the broker engine — it keeps generating off-screen, reconnectable by a later `focus`.'],
|
|
794
|
-
},
|
|
795
|
-
run: async (input) => {
|
|
796
|
-
const value = input['lifecycle']?.trim().toLowerCase();
|
|
797
|
-
if (value !== 'terminal' && value !== 'resident') {
|
|
798
|
-
throw new InputError({ error: 'bad_lifecycle', message: `invalid lifecycle: ${value ?? ''}`, field: 'lifecycle', next: 'Pass `terminal` or `resident`.' });
|
|
799
|
-
}
|
|
800
|
-
const pane = input['pane'] ?? process.env['TMUX_PANE'];
|
|
801
|
-
const id = resolveLifecycleNode(input, pane);
|
|
802
|
-
const res = setLifecycle(id, value, { pane, detach: input['detach'] === true });
|
|
803
|
-
return { node_id: res.node_id, lifecycle: res.lifecycle, detached: res.detached };
|
|
1461
|
+
summary: 'end or transition a node — recycle the pane, close + cascade-cancel a subtree, or demote to terminal in place',
|
|
804
1462
|
},
|
|
805
|
-
|
|
1463
|
+
children: [nodeRecycle, nodeClose, nodeDemote],
|
|
806
1464
|
});
|
|
807
1465
|
// ---------------------------------------------------------------------------
|
|
808
1466
|
// node yield — refresh: discard context, revive fresh against the roadmap
|
|
@@ -846,395 +1504,38 @@ const nodeYield = defineLeaf({
|
|
|
846
1504
|
},
|
|
847
1505
|
});
|
|
848
1506
|
// ---------------------------------------------------------------------------
|
|
849
|
-
// node
|
|
850
|
-
//
|
|
851
|
-
// grammar (parseWhen/parseCadence). The surface ONLY arms a durable row and
|
|
852
|
-
// introspects/cancels — it never spawns pi, drives a transition, or fires a wake
|
|
853
|
-
// (those are the daemon's at fire time). T2's armWake carries only integrity
|
|
854
|
-
// backstops (empty body / recur-on-deadline / unknown kind); the target-
|
|
855
|
-
// resolvability, bare-recoverable-state, and per-owner cap checks live HERE
|
|
856
|
-
// (Min-6), so every armer must route through this surface.
|
|
1507
|
+
// node triggers — inspect/cancel pending triggers (clock + predicate) armed by
|
|
1508
|
+
// `node msg` or `node new`.
|
|
857
1509
|
// ---------------------------------------------------------------------------
|
|
858
|
-
|
|
859
|
-
const WAKE_CAP = 100;
|
|
860
|
-
/** Default timeout body for a note-less `until`, so the deadline row is never
|
|
861
|
-
* empty (T2 rejects an empty body) and Maj-8's rendered timeout signal has text. */
|
|
862
|
-
const DEFAULT_DEADLINE_BODY = 'Deadline reached — no report arrived; reassess / chase / escalate.';
|
|
863
|
-
/** Resolve the calling node (the armer/owner). CRTR_NODE_ID is mandatory — a
|
|
864
|
-
* wake is owned by the node that arms it (owner_id). */
|
|
865
|
-
function armerId() {
|
|
866
|
-
const id = process.env['CRTR_NODE_ID'];
|
|
867
|
-
if (id === undefined || id === '') {
|
|
868
|
-
throw new InputError({ error: 'no_node', message: 'no node to arm a wake (CRTR_NODE_ID unset)', next: 'Run from inside a node.' });
|
|
869
|
-
}
|
|
870
|
-
return id;
|
|
871
|
-
}
|
|
872
|
-
/** Build ParseOpts, including `tz` only when actually provided. */
|
|
873
|
-
function parseOpts(now, tz) {
|
|
874
|
-
return tz !== undefined && tz.trim() !== '' ? { tz, now } : { now };
|
|
875
|
-
}
|
|
876
|
-
/** Map a T3 typed time-grammar error to the rendered AC-N3/N4 error block. */
|
|
877
|
-
function throwWakeError(e) {
|
|
878
|
-
const next = {
|
|
879
|
-
wake_in_past: 'Pick a future instant — a positive duration ("5m","2h") or an ISO time later than now.',
|
|
880
|
-
bad_when: 'Use a duration ("5m","1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00").',
|
|
881
|
-
bad_cadence: 'Use a duration ("6h"), a 5-field cron ("0 9 * * *"), or an @alias ("@daily").',
|
|
882
|
-
unknown_zone: 'Pass --tz with an IANA zone name (e.g. "America/New_York").',
|
|
883
|
-
cadence_too_fast: 'Use a cadence of at least 60s (e.g. "1m","5m","1h").',
|
|
884
|
-
};
|
|
885
|
-
throw new InputError({ error: e.code, message: e.message, received: e.received, next: next[e.code] ?? 'Fix the time value and retry.' });
|
|
886
|
-
}
|
|
887
|
-
/** Run armWake, mapping its thrown integrity backstop (WakeArmError) to a
|
|
888
|
-
* rendered error block. The surface validates these cases up front, so a throw
|
|
889
|
-
* here is a backstop, not the primary path. */
|
|
890
|
-
function armOrThrow(spec) {
|
|
891
|
-
try {
|
|
892
|
-
return armWake(spec);
|
|
893
|
-
}
|
|
894
|
-
catch (e) {
|
|
895
|
-
if (e instanceof WakeArmError) {
|
|
896
|
-
const next = {
|
|
897
|
-
empty_note: 'Provide a real --note, or omit it for a bare wake.',
|
|
898
|
-
deadline_cannot_recur: 'Drop --every. For a recurring self-alarm use `crtr node wake at --every <cadence>`.',
|
|
899
|
-
bad_kind: 'This is a crtr bug — report it.',
|
|
900
|
-
};
|
|
901
|
-
throw new InputError({ error: e.code, message: e.message, next: next[e.code] ?? 'Fix the wake spec and retry.' });
|
|
902
|
-
}
|
|
903
|
-
throw e;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
/** Reject an arm that would push this owner past the pending-wakes cap (AC-N4),
|
|
907
|
-
* counted via the {owner} listWakes variant. */
|
|
908
|
-
function assertUnderCap(ownerId) {
|
|
909
|
-
const pending = listWakes({ owner: ownerId }).length;
|
|
910
|
-
if (pending >= WAKE_CAP) {
|
|
911
|
-
throw new InputError({
|
|
912
|
-
error: 'cap_exceeded',
|
|
913
|
-
message: `you hold ${pending} pending wakes (cap ${WAKE_CAP}).`,
|
|
914
|
-
next: 'Reap stale wakes with `crtr node wake cancel <id>` (see `crtr node wake list`) before arming more.',
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
/** A bare wake resumes a fresh window with no memory beyond disk, so it needs
|
|
919
|
-
* durable state to wake INTO. Accept it only when the target has a goal
|
|
920
|
-
* (initial-prompt.md) or roadmap (roadmap.md) on disk — located via the
|
|
921
|
-
* codebase's own writeGoal/roadmap convention, not a new file (AC-N3). */
|
|
922
|
-
function hasRecoverableState(nodeId) {
|
|
923
|
-
const goal = readGoal(nodeId);
|
|
924
|
-
if (goal !== null && goal.trim() !== '')
|
|
925
|
-
return true;
|
|
926
|
-
const roadmap = readRoadmap(nodeId);
|
|
927
|
-
return roadmap !== null && roadmap.trim() !== '';
|
|
928
|
-
}
|
|
929
|
-
/** True when a stored recur is a fixed interval (vs a calendar cron). */
|
|
930
|
-
function isFixedInterval(recur) {
|
|
931
|
-
try {
|
|
932
|
-
return typeof JSON.parse(recur).every === 'string';
|
|
933
|
-
}
|
|
934
|
-
catch {
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
/** A human ETA hint like " (~5m)" from a fire-at ISO relative to now. '' if past. */
|
|
939
|
-
function etaHint(fireAt, now) {
|
|
940
|
-
const ms = new Date(fireAt).getTime() - now.getTime();
|
|
941
|
-
if (!Number.isFinite(ms) || ms <= 0)
|
|
942
|
-
return '';
|
|
943
|
-
const mins = Math.round(ms / 60_000);
|
|
944
|
-
if (mins < 60)
|
|
945
|
-
return ` (~${mins}m)`;
|
|
946
|
-
const hrs = Math.round(ms / 3_600_000);
|
|
947
|
-
if (hrs < 48)
|
|
948
|
-
return ` (~${hrs}h)`;
|
|
949
|
-
return ` (~${Math.round(ms / 86_400_000)}d)`;
|
|
950
|
-
}
|
|
951
|
-
// node wake at ---------------------------------------------------------------
|
|
952
|
-
const nodeWakeAt = defineLeaf({
|
|
953
|
-
name: 'at',
|
|
954
|
-
description: 'arm a self-alarm — wake yourself (or another node) at a future time',
|
|
955
|
-
whenToUse: 'you have a standing or recurring task on a CLOCK, or nothing to do until a known future time — run a job each morning, loop a health check on a cadence (--every), re-check an EXTERNAL poll (CI, a deploy, a rate-limit window) after a backoff, or hand your future self a timed reminder. Time-driven work ONLY: do NOT arm a timer to watch your own agents — you auto-subscribe to every child you spawn, so its finish already wakes you; setting a wake to "check if it is done yet" just burns a window the subscription would have woken anyway. The bare form is the time-triggered twin of `node yield`: a fresh window re-reading your roadmap, right for standing/recurring work; the noted form instead wakes you into your saved conversation with a pointer this moment needs. Use `node wake until` to put a single DEADLINE backstop on an inbox-wait (still event-first, not polling); `node wake spawn` to defer spawning a NEW node; `node yield` to refresh NOW rather than at a future T',
|
|
956
|
-
help: {
|
|
957
|
-
name: 'node wake at',
|
|
958
|
-
summary: 'arm a self-alarm: wake yourself (default) or another node (--node) at a future time. Bare ⇒ fresh window re-reading roadmap/disk; --note ⇒ saved conversation with the note as new context; --every ⇒ a declarative recurrence the runtime keeps firing even across your crash/finalize',
|
|
959
|
-
params: [
|
|
960
|
-
{ kind: 'positional', name: 'when', required: false, constraint: 'Fire time — a duration ("5m","1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00", host-local). Required UNLESS --every is given (the cadence then sets the first fire).' },
|
|
961
|
-
{ kind: 'flag', name: 'note', type: 'string', required: false, constraint: 'A non-empty note delivered into the woken context. Present ⇒ NOTED wake (resume your saved conversation, note as new context). Absent ⇒ BARE wake (fresh window re-reading roadmap/disk). A bare wake on a node with no goal/roadmap on disk is rejected.' },
|
|
962
|
-
{ kind: 'flag', name: 'every', type: 'string', required: false, constraint: 'Make it a declarative recurrence the runtime fires on schedule even if you crash/finalize (node-anchored revive-cron): a duration ("6h","30m") or a 5-field cron / @alias ("0 9 * * *","@daily"). A fixed-interval --every WITH <when> uses <when> as the first fire; a cron --every ignores <when>. Min cadence 60s.' },
|
|
963
|
-
{ kind: 'flag', name: 'tz', type: 'string', required: false, constraint: 'IANA zone for a calendar --every or a bare-ISO <when> (default: host-local).' },
|
|
964
|
-
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Arm the wake for ANOTHER existing node (a parent waking a child / a timed message to it). Default: self (CRTR_NODE_ID).' },
|
|
965
|
-
],
|
|
966
|
-
output: [
|
|
967
|
-
{ name: 'id', type: 'string', required: true, constraint: 'The new wakeup id (cancel/list by it).' },
|
|
968
|
-
{ name: 'kind', type: 'string', required: true, constraint: '"bare" or "noted".' },
|
|
969
|
-
{ name: 'fires_at', type: 'string', required: true, constraint: 'Absolute UTC fire time (the first fire for a recurrence).' },
|
|
970
|
-
{ name: 'recur', type: 'string', required: true, constraint: '"none", or the cadence (every 6h / cron `0 9 * * *` <zone>).' },
|
|
971
|
-
{ name: 'target', type: 'string', required: true, constraint: '"self" or the --node id.' },
|
|
972
|
-
{ name: 'guidance', type: 'string', required: true, constraint: 'What to do now — end your turn to go dormant; do not push final.' },
|
|
973
|
-
],
|
|
974
|
-
outputKind: 'object',
|
|
975
|
-
effects: [
|
|
976
|
-
'Inserts one wakeups row (kind bare/noted); nothing fires before fire_at.',
|
|
977
|
-
'No pi spawn, no transition — arming is a pure durable side-effect. End your turn separately to go dormant.',
|
|
978
|
-
],
|
|
979
|
-
},
|
|
980
|
-
run: async (input) => {
|
|
981
|
-
const ownerId = armerId();
|
|
982
|
-
const targetId = (input['node'] ?? '').trim() || ownerId;
|
|
983
|
-
if (getNode(targetId) === null) {
|
|
984
|
-
throw new InputError({ error: 'not_found', message: `no node: ${targetId}`, field: 'node', next: 'List nodes with `crtr node inspect list`.' });
|
|
985
|
-
}
|
|
986
|
-
const when = input['when']?.trim();
|
|
987
|
-
const every = input['every']?.trim();
|
|
988
|
-
const tz = input['tz'];
|
|
989
|
-
const noteRaw = input['note'];
|
|
990
|
-
const hasNote = noteRaw !== undefined;
|
|
991
|
-
if ((when === undefined || when === '') && (every === undefined || every === '')) {
|
|
992
|
-
throw new InputError({ error: 'bad_when', message: 'a <when> time is required (or --every for a recurrence).', next: 'Pass a duration ("5m"), an ISO time, or --every <cadence>.' });
|
|
993
|
-
}
|
|
994
|
-
if (hasNote && noteRaw.trim() === '') {
|
|
995
|
-
throw new InputError({ error: 'empty_note', message: '--note must be non-empty for a noted wake.', received: noteRaw, next: 'Provide a real note ("re-check CI #4821; deploy was pending"), or omit --note for a bare wake.' });
|
|
996
|
-
}
|
|
997
|
-
const kind = hasNote ? 'noted' : 'bare';
|
|
998
|
-
if (kind === 'bare' && !hasRecoverableState(targetId)) {
|
|
999
|
-
throw new InputError({ error: 'bare_no_recoverable_state', message: `node ${targetId} has no goal/roadmap on disk — a bare wake would resume amnesiac.`, next: 'Pass --note "<why this moment matters>" so the woken context carries a pointer, or arm the bare wake only once the node has a roadmap/goal.' });
|
|
1000
|
-
}
|
|
1001
|
-
const now = new Date();
|
|
1002
|
-
let recur;
|
|
1003
|
-
let fireAt;
|
|
1004
|
-
if (every !== undefined && every !== '') {
|
|
1005
|
-
const cad = parseCadence(every, parseOpts(now, tz));
|
|
1006
|
-
if ('error' in cad)
|
|
1007
|
-
throwWakeError(cad.error);
|
|
1008
|
-
recur = cad.recur;
|
|
1009
|
-
fireAt = cad.firstFireAt;
|
|
1010
|
-
// BOTH <when> and a FIXED-interval --every: <when> overrides the first fire
|
|
1011
|
-
// (Min-12). A cron --every ignores <when> by design.
|
|
1012
|
-
if (when !== undefined && when !== '' && isFixedInterval(cad.recur)) {
|
|
1013
|
-
const w = parseWhen(when, parseOpts(now, tz));
|
|
1014
|
-
if ('error' in w)
|
|
1015
|
-
throwWakeError(w.error);
|
|
1016
|
-
fireAt = w.fireAt;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
const w = parseWhen(when, parseOpts(now, tz));
|
|
1021
|
-
if ('error' in w)
|
|
1022
|
-
throwWakeError(w.error);
|
|
1023
|
-
fireAt = w.fireAt;
|
|
1024
|
-
}
|
|
1025
|
-
assertUnderCap(ownerId);
|
|
1026
|
-
let payload = null;
|
|
1027
|
-
if (kind === 'noted') {
|
|
1028
|
-
const body = noteRaw;
|
|
1029
|
-
payload = { body, label: body.split('\n')[0].slice(0, 120) };
|
|
1030
|
-
}
|
|
1031
|
-
const id = `wk-${newNodeId()}`;
|
|
1032
|
-
armOrThrow({
|
|
1033
|
-
wakeup_id: id,
|
|
1034
|
-
node_id: targetId,
|
|
1035
|
-
owner_id: ownerId,
|
|
1036
|
-
fire_at: fireAt,
|
|
1037
|
-
kind,
|
|
1038
|
-
...(recur !== undefined ? { recur } : {}),
|
|
1039
|
-
payload,
|
|
1040
|
-
});
|
|
1041
|
-
const target = targetId === ownerId ? 'self' : targetId;
|
|
1042
|
-
const eta = etaHint(fireAt, now);
|
|
1043
|
-
const guidance = recur !== undefined
|
|
1044
|
-
? `${kind === 'noted' ? 'Noted' : 'Bare'} recurrence armed (${cadenceDisplay(recur)}); first fire ${fireAt}${eta}. End your turn to go dormant; do not push final. The runtime keeps firing it even across your crash/finalize — cancel with \`crtr node wake cancel ${id}\`.`
|
|
1045
|
-
: kind === 'noted'
|
|
1046
|
-
? `Noted wake armed. You wake into your saved conversation at ${fireAt}${eta} with your note. End your turn now to go dormant; do not push final.`
|
|
1047
|
-
: `Bare self-alarm armed. You wake in a fresh window at ${fireAt}${eta}. End your turn now to go dormant; do not push final. The wake re-reads your roadmap.`;
|
|
1048
|
-
return { id, kind, fires_at: fireAt, recur: cadenceDisplay(recur), target, guidance };
|
|
1049
|
-
},
|
|
1050
|
-
render: (r) => {
|
|
1051
|
-
const recur = r['recur'] !== 'none' ? `, recurs ${r['recur']}` : '';
|
|
1052
|
-
return `Armed ${r['kind']} wake ${r['id']} — fires ${r['fires_at']}${recur}, target ${r['target']}.\n\n${r['guidance']}`;
|
|
1053
|
-
},
|
|
1054
|
-
});
|
|
1055
|
-
// node wake until ------------------------------------------------------------
|
|
1056
|
-
const nodeWakeUntil = defineLeaf({
|
|
1057
|
-
name: 'until',
|
|
1058
|
-
description: 'bind a deadline to your current inbox-wait (self only)',
|
|
1059
|
-
whenToUse: 'rarely — only to put a deadline on an inbox-wait whose event the runtime genuinely cannot guarantee to deliver: you are open to a message AND backstopping an UNPUSHABLE external (a human who may never reply, an outside system with no spine-push). NOT for delegates — a child finishing, crashing, or being closed already wakes you on its own, so a deadline to "chase" a delegate is exactly the belt-and-suspenders the runtime makes redundant. Use `node wake at` instead for an unconditional timed wake unrelated to an inbox event',
|
|
1060
|
-
help: {
|
|
1061
|
-
name: 'node wake until',
|
|
1062
|
-
summary: 'arm a deadline on your current dormancy: you wake on the first inbox message, or at <when> if none arrives — whichever fires first wins, the loser is canceled. Self only; ≤1 deadline per node (a new `until` replaces the prior); cancel-on-wake (any genuine revive drops it)',
|
|
1063
|
-
params: [
|
|
1064
|
-
{ kind: 'positional', name: 'when', required: true, constraint: 'Deadline time — a duration ("30m"), a zoned ISO, or a bare ISO (host-local).' },
|
|
1065
|
-
{ kind: 'flag', name: 'note', type: 'string', required: false, constraint: 'Your timeout playbook — what to do if you woke because the wait expired (delivered with a timeout marker so you can tell a timeout from a real report). Omit and a default timeout note is supplied.' },
|
|
1066
|
-
{ kind: 'flag', name: 'every', type: 'string', required: false, constraint: 'NOT ALLOWED — a deadline cannot recur. Passing it is rejected (deadline_cannot_recur); use `crtr node wake at --every` for a recurring self-alarm.' },
|
|
1067
|
-
],
|
|
1068
|
-
output: [
|
|
1069
|
-
{ name: 'id', type: 'string', required: true, constraint: 'The new deadline wakeup id.' },
|
|
1070
|
-
{ name: 'fires_at', type: 'string', required: true, constraint: 'Absolute UTC deadline.' },
|
|
1071
|
-
{ name: 'target', type: 'string', required: true, constraint: 'Always "self".' },
|
|
1072
|
-
{ name: 'guidance', type: 'string', required: true, constraint: 'What to do now — delegate / end your turn to go dormant.' },
|
|
1073
|
-
],
|
|
1074
|
-
outputKind: 'object',
|
|
1075
|
-
effects: [
|
|
1076
|
-
"Upserts the node's single deadline wakeups row (replacing any prior).",
|
|
1077
|
-
'No pi spawn, no transition — arm, then end your turn to go dormant. A genuine revive (an inbox event or any other wake) cancels it.',
|
|
1078
|
-
],
|
|
1079
|
-
},
|
|
1080
|
-
run: async (input) => {
|
|
1081
|
-
const ownerId = armerId();
|
|
1082
|
-
const every = input['every']?.trim();
|
|
1083
|
-
if (every !== undefined && every !== '') {
|
|
1084
|
-
throw new InputError({ error: 'deadline_cannot_recur', message: 'a deadline cannot recur.', next: 'Drop --every. For a recurring self-alarm use `crtr node wake at --every <cadence>`.' });
|
|
1085
|
-
}
|
|
1086
|
-
const when = input['when'].trim();
|
|
1087
|
-
const noteRaw = input['note'];
|
|
1088
|
-
if (noteRaw !== undefined && noteRaw.trim() === '') {
|
|
1089
|
-
throw new InputError({ error: 'empty_note', message: '--note must be non-empty when given.', received: noteRaw, next: 'Provide a real timeout playbook, or omit --note for the default timeout note.' });
|
|
1090
|
-
}
|
|
1091
|
-
const now = new Date();
|
|
1092
|
-
const w = parseWhen(when, { now });
|
|
1093
|
-
if ('error' in w)
|
|
1094
|
-
throwWakeError(w.error);
|
|
1095
|
-
assertUnderCap(ownerId);
|
|
1096
|
-
const body = noteRaw !== undefined && noteRaw.trim() !== '' ? noteRaw : DEFAULT_DEADLINE_BODY;
|
|
1097
|
-
const label = body.split('\n')[0].slice(0, 120);
|
|
1098
|
-
const payload = { body, timeout: true, label };
|
|
1099
|
-
const id = `wk-${newNodeId()}`;
|
|
1100
|
-
armOrThrow({ wakeup_id: id, node_id: ownerId, owner_id: ownerId, fire_at: w.fireAt, kind: 'deadline', payload });
|
|
1101
|
-
const eta = etaHint(w.fireAt, now);
|
|
1102
|
-
const guidance = `Deadline armed. You wake on the first inbox message, or at ${w.fireAt}${eta} if none arrives. Now delegate / end your turn to go dormant — whichever fires first cancels the other.`;
|
|
1103
|
-
return { id, fires_at: w.fireAt, target: 'self', guidance };
|
|
1104
|
-
},
|
|
1105
|
-
render: (r) => `Armed deadline ${r['id']} — fires ${r['fires_at']}, target ${r['target']}.\n\n${r['guidance']}`,
|
|
1106
|
-
});
|
|
1107
|
-
// node wake spawn -----------------------------------------------------------
|
|
1108
|
-
const nodeWakeSpawn = defineLeaf({
|
|
1109
|
-
name: 'spawn',
|
|
1110
|
-
description: 'defer or recur the BIRTH of a new node',
|
|
1111
|
-
whenToUse: 'you want a node spawned LATER or on a repeating cadence rather than right now: a one-shot deferred birth at a future time (--at), or a spawn-cron that re-births a fresh node every interval/cron even across your crash/finalize (--every) — standing work like a nightly review, a periodic health check, a morning digest, or a timed reminder that acts. Use `node new` instead to spawn immediately, and `node wake at` to re-wake an EXISTING node on a timer rather than birth a new one',
|
|
1112
|
-
help: {
|
|
1113
|
-
name: 'node wake spawn',
|
|
1114
|
-
summary: 'defer or recur a node birth — arm a wake that spawns a fresh node at a future time (--at) or on a repeating cadence (--every), instead of `node new` spawning it now. Returns a wakeup id, not a node id; the full spawn recipe is stored on the wake and re-derived live at fire time',
|
|
1115
|
-
params: [
|
|
1116
|
-
{ kind: 'stdin', name: 'prompt', required: true, constraint: 'First user message for the node to be born. Deliver it on stdin from a quoted heredoc (`<<\'EOF\'`) or a file (`< prompt.md`).' },
|
|
1117
|
-
{ kind: 'flag', name: 'at', type: 'string', required: false, constraint: 'DEFER a one-shot birth at <when> — a duration ("5m"), a zoned ISO, or a bare ISO (host-local, or in --tz). Mutually exclusive with --every; exactly one of --at/--every is required.' },
|
|
1118
|
-
{ kind: 'flag', name: 'every', type: 'string', required: false, constraint: 'SPAWN-CRON: re-birth a fresh node on this declarative cadence even after a prior instance reaped itself or crashed — a duration ("6h") or a 5-field cron / @alias ("0 9 * * *","@daily"). Canvas-anchored (survives your crash/finalize), reaped by your deliberate close or `node wake cancel`. Min cadence 60s. Mutually exclusive with --at.' },
|
|
1119
|
-
{ kind: 'flag', name: 'tz', type: 'string', required: false, constraint: 'IANA zone for a calendar --every (or a bare-ISO --at); default host-local. Makes "every 9am" mean 9am there, DST-correct.' },
|
|
1120
|
-
{ kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind for the node to be born — match the work to the kind (the <kinds> list below names each).' },
|
|
1121
|
-
{ kind: 'flag', name: 'mode', type: 'enum', choices: ['base', 'orchestrator'], required: false, default: 'base', constraint: 'Persona mode: base (finishes in one window) or orchestrator (boots with a seeded roadmap and fans its scope out).' },
|
|
1122
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Dir the born node is pinned to. Defaults to the caller cwd, resolved NOW — it must still exist at fire time or the spawn fails loud.' },
|
|
1123
|
-
{ kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Display name for the born node (tmux window + resume picker). Defaults to the kind.' },
|
|
1124
|
-
{ kind: 'flag', name: 'parent', type: 'string', required: false, constraint: 'Parent node id for the born node. Defaults to the calling node (CRTR_NODE_ID).' },
|
|
1125
|
-
{ kind: 'flag', name: 'root', type: 'bool', required: false, constraint: 'Birth an INDEPENDENT root instead of a managed child: no parent on the spine, NO subscription back to you, resident lifecycle (records spawned_by=you for provenance).' },
|
|
1126
|
-
{ kind: 'flag', name: 'fork-from', type: 'string', required: false, constraint: 'Fork the born node from an existing pi conversation instead of starting it fresh: a node id, an absolute session `.jsonl` path, or a partial pi session uuid. pi copies that history into a NEW session, then the prompt is the next message. One-shot at birth.' },
|
|
1127
|
-
],
|
|
1128
|
-
output: [
|
|
1129
|
-
{ name: 'id', type: 'string', required: true, constraint: 'The wakeup id (inspect/cancel by it via `node wake list`/`cancel`).' },
|
|
1130
|
-
{ name: 'kind', type: 'string', required: true, constraint: 'The persona kind of the deferred node.' },
|
|
1131
|
-
{ name: 'fires_at', type: 'string', required: true, constraint: 'Absolute UTC birth time (the first fire for a cron).' },
|
|
1132
|
-
{ name: 'recur', type: 'string', required: true, constraint: '"none" (one-shot --at) or the cadence (every 6h / cron `0 9 * * *` <zone>).' },
|
|
1133
|
-
{ name: 'guidance', type: 'string', required: true, constraint: 'What to do now — no node exists yet; pick up other work or end your turn.' },
|
|
1134
|
-
],
|
|
1135
|
-
dynamicState: () => kindsStateBlock(),
|
|
1136
|
-
outputKind: 'object',
|
|
1137
|
-
effects: [
|
|
1138
|
-
'Inserts one detached `spawn` wakeups row (node_id NULL, owner=you, parent/cwd resolved now); NO node and NO window exist until fire time.',
|
|
1139
|
-
'At fire time the daemon spawns the node from the stored recipe, re-deriving the launch spec live (persona prose is never stale). Best-effort: if the cwd/parent is gone at fire it fails LOUD (an urgent push to you, or a daemon-log line if you are gone).',
|
|
1140
|
-
],
|
|
1141
|
-
},
|
|
1142
|
-
run: async (input) => {
|
|
1143
|
-
const prompt = input['prompt'] ?? '';
|
|
1144
|
-
if (prompt.trim() === '') {
|
|
1145
|
-
throw new InputError({ error: 'empty_prompt', message: 'a prompt is required (stdin or positional)', next: 'Pipe a task on stdin or pass it as an argument.' });
|
|
1146
|
-
}
|
|
1147
|
-
const at = input['at']?.trim();
|
|
1148
|
-
const every = input['every']?.trim();
|
|
1149
|
-
const tz = input['tz'];
|
|
1150
|
-
const hasAt = at !== undefined && at !== '';
|
|
1151
|
-
const hasEvery = every !== undefined && every !== '';
|
|
1152
|
-
if (!hasAt && !hasEvery) {
|
|
1153
|
-
throw new InputError({ error: 'no_schedule', message: 'node wake spawn needs a schedule: --at <when> or --every <cadence>.', next: 'Pass --at for a one-shot deferred spawn, --every for a spawn-cron, or use `crtr node new` to spawn now.' });
|
|
1154
|
-
}
|
|
1155
|
-
if (hasAt && hasEvery) {
|
|
1156
|
-
throw new InputError({ error: 'at_and_every', message: '--at and --every are mutually exclusive.', next: 'Use --at for a one-shot deferred spawn, or --every for a spawn-cron.' });
|
|
1157
|
-
}
|
|
1158
|
-
const kind = input['kind'] ?? 'general';
|
|
1159
|
-
const mode = (input['mode'] ?? 'base');
|
|
1160
|
-
const cwd = input['cwd'] ?? process.cwd();
|
|
1161
|
-
const name = input['name'];
|
|
1162
|
-
const parent = input['parent'];
|
|
1163
|
-
const root = input['root'] === true;
|
|
1164
|
-
const forkFrom = input['forkFrom'];
|
|
1165
|
-
const ownerId = armerId();
|
|
1166
|
-
// The recipe's `parent` is the resolved armer — NON-NULL on EVERY payload,
|
|
1167
|
-
// INCLUDING --root: spawnChild throws at fire time on a null parent (the
|
|
1168
|
-
// daemon has no CRTR_NODE_ID); for a root it internally nulls the spine
|
|
1169
|
-
// parent while keeping `spawner` for provenance, so a non-null value is right.
|
|
1170
|
-
const recipeParent = parent ?? ownerId;
|
|
1171
|
-
const recipe = {
|
|
1172
|
-
kind,
|
|
1173
|
-
mode,
|
|
1174
|
-
cwd,
|
|
1175
|
-
prompt,
|
|
1176
|
-
parent: recipeParent,
|
|
1177
|
-
...(name !== undefined ? { name } : {}),
|
|
1178
|
-
...(root ? { root: true } : {}),
|
|
1179
|
-
...(forkFrom !== undefined ? { forkFrom } : {}),
|
|
1180
|
-
};
|
|
1181
|
-
const now = new Date();
|
|
1182
|
-
let recur;
|
|
1183
|
-
let fireAt;
|
|
1184
|
-
if (hasEvery) {
|
|
1185
|
-
const cad = parseCadence(every, parseOpts(now, tz));
|
|
1186
|
-
if ('error' in cad)
|
|
1187
|
-
throwWakeError(cad.error);
|
|
1188
|
-
recur = cad.recur;
|
|
1189
|
-
fireAt = cad.firstFireAt;
|
|
1190
|
-
}
|
|
1191
|
-
else {
|
|
1192
|
-
const w = parseWhen(at, parseOpts(now, tz));
|
|
1193
|
-
if ('error' in w)
|
|
1194
|
-
throwWakeError(w.error);
|
|
1195
|
-
fireAt = w.fireAt;
|
|
1196
|
-
}
|
|
1197
|
-
assertUnderCap(ownerId);
|
|
1198
|
-
const id = `wk-${newNodeId()}`;
|
|
1199
|
-
armOrThrow({ wakeup_id: id, node_id: null, owner_id: ownerId, fire_at: fireAt, kind: 'spawn', ...(recur !== undefined ? { recur } : {}), payload: recipe });
|
|
1200
|
-
const eta = etaHint(fireAt, now);
|
|
1201
|
-
const guidance = recur !== undefined
|
|
1202
|
-
? `Spawn-cron armed (${cadenceDisplay(recur)}): a fresh ${kind} node is born each fire, first at ${fireAt}${eta}. No node exists yet; the runtime keeps spawning even across your crash/finalize — inspect/cancel via \`crtr node wake list\` / \`crtr node wake cancel ${id}\`.`
|
|
1203
|
-
: `Deferred spawn armed: a fresh ${kind} node is born at ${fireAt}${eta}. No node exists yet — inspect/cancel via \`crtr node wake list\` / \`crtr node wake cancel ${id}\`. Pick up other work or end your turn.`;
|
|
1204
|
-
return { id, kind, fires_at: fireAt, recur: cadenceDisplay(recur), guidance };
|
|
1205
|
-
},
|
|
1206
|
-
render: (r) => {
|
|
1207
|
-
const recur = r['recur'] !== 'none' ? `, recurs ${r['recur']}` : '';
|
|
1208
|
-
return `Armed deferred spawn ${r['id']} — a ${r['kind']} node, fires ${r['fires_at']}${recur}.\n\n${r['guidance']}`;
|
|
1209
|
-
},
|
|
1210
|
-
});
|
|
1211
|
-
// node wake list -------------------------------------------------------------
|
|
1212
|
-
const nodeWakeList = defineLeaf({
|
|
1510
|
+
const nodeTriggersList = defineLeaf({
|
|
1213
1511
|
name: 'list',
|
|
1214
|
-
description: 'list pending
|
|
1215
|
-
whenToUse: 'before re-arming or finishing, to see what you already have armed and reap stale ones. Defaults to your own
|
|
1512
|
+
description: 'list pending triggers for a scope (default self)',
|
|
1513
|
+
whenToUse: 'before re-arming or finishing, to see what you already have armed and reap stale ones. Defaults to your own triggers, with a --node or --canvas scope for anything wider',
|
|
1216
1514
|
help: {
|
|
1217
|
-
name: 'node
|
|
1218
|
-
summary: 'list pending
|
|
1515
|
+
name: 'node triggers list',
|
|
1516
|
+
summary: 'list pending triggers (clock and predicate, incl. deferred spawns) for a scope — id, owner, target, action/mode, type, next fire/eval, cadence, timeout, label, predicate diagnostics. Fired one-shots are gone; a recurrence shows its NEXT fire/eval, not past slots',
|
|
1219
1517
|
params: [
|
|
1220
|
-
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'List the
|
|
1221
|
-
{ kind: 'flag', name: 'canvas', type: 'bool', required: false, constraint: 'List EVERY
|
|
1222
|
-
{ kind: 'flag', name: 'subtree', type: 'string', required: false, constraint: 'List the wakes anchored to this node AND its descendants (the subscription sub-DAG). Mutually exclusive with --node/--canvas.' },
|
|
1518
|
+
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'List the triggers owned by this node instead of yourself. Mutually exclusive with --canvas.' },
|
|
1519
|
+
{ kind: 'flag', name: 'canvas', type: 'bool', required: false, constraint: 'List EVERY trigger on the canvas. Mutually exclusive with --node.' },
|
|
1223
1520
|
],
|
|
1224
1521
|
output: [
|
|
1225
|
-
{ name: 'scope', type: 'string', required: true, constraint: 'The scope listed (self / <id> / canvas
|
|
1226
|
-
{
|
|
1522
|
+
{ name: 'scope', type: 'string', required: true, constraint: 'The scope listed (self / <id> / canvas).' },
|
|
1523
|
+
{
|
|
1524
|
+
name: 'triggers',
|
|
1525
|
+
type: 'object[]',
|
|
1526
|
+
required: true,
|
|
1527
|
+
constraint: 'Rows: {id, owner, target, action ( "msg"|"new" ), action_mode, trigger_type ( "at"|"every"|"when"|"until" ), next (fire_at or next_eval_at), recur (cadence display), timeout (timeout_at, predicate only), label, eval_state, last_eval_at, last_exit, last_stderr, created}.',
|
|
1528
|
+
},
|
|
1227
1529
|
],
|
|
1228
1530
|
outputKind: 'object',
|
|
1229
|
-
effects: ['Read-only: queries the
|
|
1531
|
+
effects: ['Read-only: queries the triggers table.'],
|
|
1230
1532
|
},
|
|
1231
1533
|
run: async (input) => {
|
|
1232
1534
|
const nodeFlag = input['node']?.trim();
|
|
1233
1535
|
const canvas = input['canvas'] === true;
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
throw new InputError({ error: 'bad_scope', message: 'choose at most one of --node, --canvas, --subtree.', next: 'Pass a single scope flag, or none for your own wakes.' });
|
|
1536
|
+
const hasNode = nodeFlag !== undefined && nodeFlag !== '';
|
|
1537
|
+
if (hasNode && canvas) {
|
|
1538
|
+
throw new InputError({ error: 'bad_scope', message: 'choose at most one of --node, --canvas.', next: 'Pass a single scope flag, or none for your own triggers.' });
|
|
1238
1539
|
}
|
|
1239
1540
|
const viewer = process.env['CRTR_NODE_ID'];
|
|
1240
1541
|
let scope;
|
|
@@ -1243,114 +1544,197 @@ const nodeWakeList = defineLeaf({
|
|
|
1243
1544
|
scope = { canvas: true };
|
|
1244
1545
|
scopeLabel = 'canvas';
|
|
1245
1546
|
}
|
|
1246
|
-
else if (
|
|
1247
|
-
if (getNode(
|
|
1248
|
-
throw new InputError({ error: 'not_found', message: `no node: ${
|
|
1249
|
-
scope = {
|
|
1250
|
-
scopeLabel = `subtree:${subtree}`;
|
|
1251
|
-
}
|
|
1252
|
-
else if (nodeFlag !== undefined && nodeFlag !== '') {
|
|
1253
|
-
scope = { node: nodeFlag };
|
|
1547
|
+
else if (hasNode) {
|
|
1548
|
+
if (getNode(nodeFlag) === null)
|
|
1549
|
+
throw new InputError({ error: 'not_found', message: `no node: ${nodeFlag}`, field: 'node', next: 'List nodes with `crtr node inspect list`.' });
|
|
1550
|
+
scope = { owner: nodeFlag };
|
|
1254
1551
|
scopeLabel = viewer !== undefined && nodeFlag === viewer ? 'self' : nodeFlag;
|
|
1255
1552
|
}
|
|
1256
1553
|
else {
|
|
1257
|
-
// Default "self" scope is OWNER-based, not node-anchored: it is "what YOU
|
|
1258
|
-
// armed" (§3.5 whenToUse), so the detached spawn wakes you own via
|
|
1259
|
-
// `node new --at/--every` (node_id NULL, owner_id self) DO surface here
|
|
1260
|
-
// with their `spawn:<kind>@<cwd>` target — §3.5/§3.7 require that. (Use
|
|
1261
|
-
// --node <id> for the node-ANCHORED view of a target's wakes.)
|
|
1262
1554
|
scope = { owner: armerId() };
|
|
1263
1555
|
scopeLabel = 'self';
|
|
1264
1556
|
}
|
|
1265
|
-
const rel = (id) =>
|
|
1266
|
-
|
|
1267
|
-
return '';
|
|
1268
|
-
return viewer !== undefined && viewer !== '' && id === viewer ? 'self' : id;
|
|
1269
|
-
};
|
|
1270
|
-
const wakes = listWakes(scope).map((w) => {
|
|
1557
|
+
const rel = (id) => (id !== null && viewer !== undefined && viewer !== '' && id === viewer ? 'self' : (id ?? ''));
|
|
1558
|
+
const triggers = listTriggers(scope).map((t) => {
|
|
1271
1559
|
let target;
|
|
1272
|
-
if (
|
|
1273
|
-
target = rel(
|
|
1560
|
+
if (t.node_id !== null) {
|
|
1561
|
+
target = rel(t.node_id);
|
|
1274
1562
|
}
|
|
1275
|
-
else if (
|
|
1276
|
-
const recipe =
|
|
1563
|
+
else if (t.action === 'new' && t.payload !== null) {
|
|
1564
|
+
const recipe = t.payload;
|
|
1277
1565
|
target = `spawn:${recipe.kind}@${recipe.cwd}`;
|
|
1278
1566
|
}
|
|
1279
1567
|
else {
|
|
1280
1568
|
target = 'detached';
|
|
1281
1569
|
}
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
:
|
|
1285
|
-
|
|
1570
|
+
const label = t.payload !== null && typeof t.payload.label === 'string' ? t.payload.label : '';
|
|
1571
|
+
return {
|
|
1572
|
+
id: t.trigger_id,
|
|
1573
|
+
owner: rel(t.owner_id),
|
|
1574
|
+
target,
|
|
1575
|
+
action: t.action,
|
|
1576
|
+
action_mode: t.action_mode,
|
|
1577
|
+
trigger_type: t.trigger_type,
|
|
1578
|
+
next: t.trigger_type === 'when' ? (t.next_eval_at ?? '') : (t.fire_at ?? ''),
|
|
1579
|
+
recur: cadenceDisplay(t.trigger_type === 'when' ? t.eval_every : t.recur),
|
|
1580
|
+
timeout: t.timeout_at ?? '',
|
|
1581
|
+
label,
|
|
1582
|
+
eval_state: t.eval_state,
|
|
1583
|
+
last_eval_at: t.last_eval_at ?? '',
|
|
1584
|
+
last_exit: t.last_exit,
|
|
1585
|
+
last_stderr: t.last_stderr ?? '',
|
|
1586
|
+
created: t.created,
|
|
1587
|
+
};
|
|
1286
1588
|
});
|
|
1287
|
-
return { scope: scopeLabel,
|
|
1589
|
+
return { scope: scopeLabel, triggers };
|
|
1288
1590
|
},
|
|
1289
1591
|
render: (r) => {
|
|
1290
|
-
const
|
|
1291
|
-
const follow = 'Cancel one with `crtr node
|
|
1292
|
-
if (
|
|
1293
|
-
return `No pending
|
|
1294
|
-
const cell = (v) => String(v).replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
1295
|
-
const head = '| id |
|
|
1296
|
-
const rows =
|
|
1297
|
-
return `${
|
|
1592
|
+
const triggers = r['triggers'];
|
|
1593
|
+
const follow = 'Cancel one with `crtr node triggers cancel <id>`. Fired one-shots are gone; a recurrence shows its NEXT fire/eval, not past slots.';
|
|
1594
|
+
if (triggers.length === 0)
|
|
1595
|
+
return `No pending triggers in scope ${r['scope']}.\n\n${follow}`;
|
|
1596
|
+
const cell = (v) => String(v ?? '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
1597
|
+
const head = '| id | owner | target | action | mode | type | next | recur | timeout | label | eval_state | last_eval_at | last_exit | last_stderr | created |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |';
|
|
1598
|
+
const rows = triggers.map((t) => `| ${cell(t['id'])} | ${cell(t['owner'])} | ${cell(t['target'])} | ${cell(t['action'])} | ${cell(t['action_mode'])} | ${cell(t['trigger_type'])} | ${cell(t['next'])} | ${cell(t['recur'])} | ${cell(t['timeout'])} | ${cell(t['label'])} | ${cell(t['eval_state'])} | ${cell(t['last_eval_at'])} | ${cell(t['last_exit'])} | ${cell(t['last_stderr'])} | ${cell(t['created'])} |`);
|
|
1599
|
+
return `${triggers.length} pending trigger(s) in scope ${r['scope']}.\n\n${head}\n${rows.join('\n')}\n\n${follow}`;
|
|
1298
1600
|
},
|
|
1299
1601
|
});
|
|
1300
|
-
|
|
1301
|
-
const nodeWakeCancel = defineLeaf({
|
|
1602
|
+
const nodeTriggersCancel = defineLeaf({
|
|
1302
1603
|
name: 'cancel',
|
|
1303
|
-
description: 'cancel a pending
|
|
1304
|
-
whenToUse: 'a wait you no longer need — a poll whose goal is met, a deferred spawn you reconsidered, a deadline you are replacing. Idempotent: canceling an already-fired or already-canceled id is a no-op. Closing/reaping a node already reaps its own
|
|
1604
|
+
description: 'cancel a pending trigger by id (idempotent)',
|
|
1605
|
+
whenToUse: 'a wait you no longer need — a poll whose goal is met, a deferred spawn you reconsidered, a deadline you are replacing. Idempotent: canceling an already-fired or already-canceled id is a no-op. Closing/reaping a node already reaps its own triggers and the detached ones it armed; reach for this to drop a single trigger, or to reap a detached spawn/cron a finished or crashed node left running',
|
|
1305
1606
|
help: {
|
|
1306
|
-
name: 'node
|
|
1307
|
-
summary: 'cancel a pending
|
|
1308
|
-
params: [
|
|
1309
|
-
{ kind: 'positional', name: 'wakeup-id', required: true, constraint: 'The wakeup id to cancel (from `node wake list`).' },
|
|
1310
|
-
],
|
|
1607
|
+
name: 'node triggers cancel',
|
|
1608
|
+
summary: 'cancel a pending trigger by id — it never fires/evaluates again and leaves the list. Idempotent (canceling an already-gone id is a no-op, not an error)',
|
|
1609
|
+
params: [{ kind: 'positional', name: 'trigger-id', required: true, constraint: 'The trigger id to cancel (from `node triggers list`).' }],
|
|
1311
1610
|
output: [
|
|
1312
|
-
{ name: 'id', type: 'string', required: true, constraint: 'The canceled
|
|
1611
|
+
{ name: 'id', type: 'string', required: true, constraint: 'The canceled trigger id.' },
|
|
1313
1612
|
{ name: 'was_pending', type: 'boolean', required: true, constraint: 'True when a pending row was removed; false when it was already gone.' },
|
|
1314
1613
|
],
|
|
1315
1614
|
outputKind: 'object',
|
|
1316
|
-
effects: ['Deletes the
|
|
1615
|
+
effects: ['Deletes the trigger row (idempotent — no error if already gone).'],
|
|
1317
1616
|
},
|
|
1318
1617
|
run: async (input) => {
|
|
1319
|
-
const id = input['
|
|
1320
|
-
const wasPending =
|
|
1321
|
-
|
|
1618
|
+
const id = input['triggerId'].trim();
|
|
1619
|
+
const wasPending = listTriggers({ canvas: true }).some((t) => t.trigger_id === id);
|
|
1620
|
+
cancelTrigger(id);
|
|
1322
1621
|
return { id, was_pending: wasPending };
|
|
1323
1622
|
},
|
|
1324
|
-
render: (r) => `Canceled
|
|
1623
|
+
render: (r) => `Canceled trigger ${r['id']}${r['was_pending'] === true ? '' : ' (already gone — no-op)'}.`,
|
|
1325
1624
|
});
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
whenToUse: 'you want to schedule work on a CLOCK — a long-horizon or recurring task that fires at a future time or loops on a cadence: a standing job, a recurring cron, a deferred or repeating node birth, or a poll against EXTERNAL state (CI, a deploy, a clock) after a backoff. This is the TIME trigger, NOT a way to watch your own agents — to monitor a child you arm NOTHING: you auto-subscribe on spawn, so its finish, crash, or close wakes you on its own; a deadline to chase a delegate is the belt-and-suspenders the runtime makes redundant. `at` arms a self-alarm (the timed twin of `node yield`), `until` bounds an inbox-wait with a deadline, `spawn` defers or repeats the BIRTH of a new node, `list`/`cancel` inspect and reap',
|
|
1625
|
+
const nodeTriggers = defineBranch({
|
|
1626
|
+
name: 'triggers',
|
|
1627
|
+
description: 'inspect/cancel pending triggers (clock + predicate)',
|
|
1628
|
+
whenToUse: 'an agent wants to see/reap its own or the canvas\'s pending triggers — clock or predicate — armed by `node msg` or `node new`',
|
|
1331
1629
|
help: {
|
|
1332
|
-
name: 'node
|
|
1333
|
-
summary: 'the pending-
|
|
1334
|
-
model: 'Time is the second trigger that stirs a dormant node — the first is an inbox message, and a scheduled wake is just a future delivery on that same channel. At the moment you set, the runtime brings you (or, for a deferred birth, a fresh node) back through the ordinary revive path: no new window, nothing the focus model learns. Reach for it to schedule CLOCK-driven work — a standing job, a recurring loop (--every), a deferred birth, or a poll against EXTERNAL state after a backoff. It is NOT how you watch your own agents: you auto-subscribe to every child you spawn, so its finish already wakes you — arm nothing and stay dormant rather than set a timer to "check if it is done" — a child finishing, crashing, or being closed all wake you on their own. (`until` is a narrow exception: a deadline backstop on an inbox-wait the runtime CANNOT guarantee to deliver — an unpushable external, never a delegate, whose outcome already wakes you.) Use this to wait CHEAPLY — end your turn, go dormant (free: no window, no compute), and be woken only when it is worth acting again. `at` arms a self-alarm; `until` bounds an inbox-wait with a deadline; `spawn` defers or repeats the BIRTH of a new node (the full recipe stored on the wake); `list`/`cancel` inspect and reap. Arming is a pure side-effect: it writes a durable row, it does NOT end your turn — end your turn separately to go dormant.',
|
|
1630
|
+
name: 'node triggers',
|
|
1631
|
+
summary: 'the pending-triggers namespace — inspect or cancel a trigger armed by `node msg` or `node new`',
|
|
1335
1632
|
},
|
|
1336
|
-
children: [
|
|
1633
|
+
children: [nodeTriggersList, nodeTriggersCancel],
|
|
1337
1634
|
});
|
|
1635
|
+
// ── /pyramid slash command ──────────────────────────────────────────────────────────────────────────────
|
|
1636
|
+
// Rides on the `node` subtree, not because pyramid is a `node` verb, but because
|
|
1637
|
+
// (1) the workflow IS node orchestration — it fans out explore agents — and
|
|
1638
|
+
// (2) provisionExports lazy-loads only the invoked subtree (build-root.ts), so a
|
|
1639
|
+
// slash spec only gets (re)written to the host's command dirs when its host
|
|
1640
|
+
// subtree runs; `node` is the most-invoked subtree, so `/pyramid` provisions
|
|
1641
|
+
// reliably. The body is a self-contained agent workflow prompt (pi prompt
|
|
1642
|
+
// template / Claude Code command), exported one-way by host-exports.
|
|
1643
|
+
const pyramidSlash = {
|
|
1644
|
+
name: 'pyramid',
|
|
1645
|
+
description: 'Explain a topic as a live-building Minto pyramid diagram, backed by parallel explore agents.',
|
|
1646
|
+
argumentHint: '<topic to explain>',
|
|
1647
|
+
body: [
|
|
1648
|
+
'# Pyramid explanation',
|
|
1649
|
+
'',
|
|
1650
|
+
'Explain **$ARGUMENTS** as a *pyramid*: lead with the answer, support it with a few key pillars, ground each pillar in evidence. You build this as a LIVE termrender diagram that fills in as parallel research lands — the user watches the skeleton grow into a complete picture.',
|
|
1651
|
+
'',
|
|
1652
|
+
'## The pyramid (Minto principle)',
|
|
1653
|
+
'- **Apex** — ONE crisp sentence: the single governing answer/claim, the thing worth remembering.',
|
|
1654
|
+
'- **Pillars** — 2–4 MECE supporting arguments that together prove the apex.',
|
|
1655
|
+
'- **Base** — concrete evidence under each pillar (mechanisms, `file:line` refs, numbers, sources).',
|
|
1656
|
+
'',
|
|
1657
|
+
'Top-down: the reader gets the conclusion first, then descends for as much support as they want.',
|
|
1658
|
+
'',
|
|
1659
|
+
'## Workflow',
|
|
1660
|
+
'',
|
|
1661
|
+
'1. **Frame it.** Restate the topic in one line and commit to a *working* apex hypothesis (you will sharpen it). Name the 2–4 pillars you expect to need.',
|
|
1662
|
+
'',
|
|
1663
|
+
'2. **Scaffold the live diagram FIRST.** Write the termrender skeleton below to `./pyramid-<slug>.md` (apex = your hypothesis, pillars = titles, every base entry marked `[?]` pending). Then open it live so the user watches it build:',
|
|
1664
|
+
' ```bash',
|
|
1665
|
+
' termrender pane open ./pyramid-<slug>.md --watch # detached tmux side-pane; re-renders on every save',
|
|
1666
|
+
' ```',
|
|
1667
|
+
' Not in tmux? Tell the user to run `termrender doc watch ./pyramid-<slug>.md` in another terminal, then continue regardless.',
|
|
1668
|
+
'',
|
|
1669
|
+
'3. **Investigate in parallel — do not block.** Fan out ONE explore agent per pillar/unknown AND dig into what you can answer yourself, concurrently:',
|
|
1670
|
+
' ```bash',
|
|
1671
|
+
' crtr node new "Investigate: <focused question for this pillar>. Report findings with sources / file:line refs." --kind explore',
|
|
1672
|
+
' ```',
|
|
1673
|
+
' You auto-subscribe; each finish wakes you. Never poll or wait — kick them all off, then start filling in what you already know.',
|
|
1674
|
+
'',
|
|
1675
|
+
'4. **Live-update as findings land.** Every time you learn something — your own digging or an explore report — EDIT the diagram file: flip a `[?]` to `[x]` (or `[!]` for a caveat/risk), add evidence under the right pillar, sharpen or rewrite the apex, drop a pillar that did not hold. Each save re-renders the pane. The diagram visibly grows skeleton → complete.',
|
|
1676
|
+
'',
|
|
1677
|
+
'5. **Land it.** When the apex is one crisp sentence, the pillars are MECE, and the base is concrete, do a final pass for accuracy, then give the user a 3–4 sentence spoken summary that mirrors apex → pillars. Tell them the diagram file path.',
|
|
1678
|
+
'',
|
|
1679
|
+
'## termrender skeleton (write this verbatim, then keep editing it)',
|
|
1680
|
+
'',
|
|
1681
|
+
'Apex panel on top, a divider, then the pillars as a row of boxes below — a literal pyramid: 1 claim over N supports.',
|
|
1682
|
+
'```markdown',
|
|
1683
|
+
':::panel{title="<TOPIC>" color="cyan"}',
|
|
1684
|
+
'**<one-line apex answer — your governing claim>**',
|
|
1685
|
+
':::',
|
|
1686
|
+
'',
|
|
1687
|
+
':::divider{label="why"}',
|
|
1688
|
+
':::',
|
|
1689
|
+
'',
|
|
1690
|
+
'::::::columns',
|
|
1691
|
+
':::::col{width="33%"}',
|
|
1692
|
+
'::::panel{title="Pillar 1 — <claim>" color="green"}',
|
|
1693
|
+
':::tree',
|
|
1694
|
+
'evidence A [x]',
|
|
1695
|
+
'evidence B [?]',
|
|
1696
|
+
':::',
|
|
1697
|
+
'::::',
|
|
1698
|
+
':::::',
|
|
1699
|
+
':::::col{width="33%"}',
|
|
1700
|
+
'::::panel{title="Pillar 2 — <claim>" color="green"}',
|
|
1701
|
+
':::tree',
|
|
1702
|
+
'evidence C [?]',
|
|
1703
|
+
':::',
|
|
1704
|
+
'::::',
|
|
1705
|
+
':::::',
|
|
1706
|
+
':::::col{width="34%"}',
|
|
1707
|
+
'::::panel{title="Pillar 3 — <claim>" color="green"}',
|
|
1708
|
+
':::tree',
|
|
1709
|
+
'evidence D [?]',
|
|
1710
|
+
':::',
|
|
1711
|
+
'::::',
|
|
1712
|
+
':::::',
|
|
1713
|
+
'::::::',
|
|
1714
|
+
'```',
|
|
1715
|
+
'',
|
|
1716
|
+
'**Nesting rule (termrender will reject the file otherwise): an OUTER directive needs strictly MORE colons than the directive nested inside it.** Hence `::::::columns` (6) wraps `:::::col` (5) wraps `::::panel` (4) wraps `:::tree` (3). Every block closes with a bare run of its OWN colon count on its own line. Run `termrender doc check ./pyramid-<slug>.md` after each edit — if it errors, fix the colon counts before re-saving.',
|
|
1717
|
+
'',
|
|
1718
|
+
'Markers inside a tree: `[x]` → ✔ confirmed, `[!]` → ⚠ caveat/risk; `[?]` is not a marker and stays literal — use it for still-pending. Write structure only — termrender draws every box, border, and guide line; never hand-draw ASCII. Keep the apex to one sentence; push all detail into the base.',
|
|
1719
|
+
].join('\n'),
|
|
1720
|
+
};
|
|
1338
1721
|
export function registerNode() {
|
|
1339
1722
|
return defineBranch({
|
|
1340
1723
|
name: 'node',
|
|
1724
|
+
slash: pyramidSlash,
|
|
1341
1725
|
rootEntry: {
|
|
1342
1726
|
concept: 'a unit of the agent runtime — an agent on the canvas with its own context dir and pi vehicle. Spawning nodes is how work gets done here, not an optimization you reach for late',
|
|
1343
1727
|
desc: 'spawn, inspect, and navigate nodes on the canvas',
|
|
1344
|
-
useWhen: 'delegating work, or walking the subscription graph. CRITICAL, and your default reflex — spawning a child is the first move on almost any task, not a fallback. Your own context window is the scarce resource: before you read files into it to understand, implement, debug, or change code you do not already know, hand that to a fresh node and get back just the conclusion, matching the node kind to the work. Keep inline only a fact you already know. Fan out independent units as CONCURRENT children — a wake with idle workers is wasted; serialize only true dependencies, and never let two live children edit the same files. Once you delegate a unit, do not also run it yourself: you auto-subscribe on spawn, so its finish wakes you.
|
|
1728
|
+
useWhen: 'delegating work, or walking the subscription graph. CRITICAL, and your default reflex — spawning a child is the first move on almost any task, not a fallback. Your own context window is the scarce resource: before you read files into it to understand, implement, debug, or change code you do not already know, hand that to a fresh node and get back just the conclusion, matching the node kind to the work. Keep inline only a fact you already know. Fan out independent units as CONCURRENT children — a wake with idle workers is wasted; serialize only true dependencies, and never let two live children edit the same files. Once you delegate a unit, do not also run it yourself: you auto-subscribe on spawn, so its finish wakes you. Use the help surface for the exact node contract before invoking it.'
|
|
1345
1729
|
},
|
|
1346
1730
|
help: {
|
|
1347
1731
|
name: 'node',
|
|
1348
1732
|
summary: 'spawn and navigate canvas nodes',
|
|
1349
1733
|
model: 'Every agent is a node in one directed graph (the canvas); `subscribes_to` is the spine — spawn a child and you auto-subscribe (active) to it, so its finish wakes you.\n\n' +
|
|
1350
1734
|
'WHEN TO DELEGATE: hand any self-contained unit of work to a child instead of doing it inline — it keeps your own context window (your scarce resource) free for steering. Spawn independent units in PARALLEL; a wake with idle workers is wasted. Serialize only true dependencies, and never let two live children edit the same files.\n\n' +
|
|
1351
|
-
'HOW:
|
|
1352
|
-
'FINISH: a worker ends its own work with
|
|
1735
|
+
'HOW: the new leaf returns a node id immediately and runs the worker as a detached headless broker; no viewer opens unless a surface asks for one. Match the kind to the work and read the leaf schema before invoking. You are woken when a child finishes — the wake message ALREADY IS the coalesced digest, so dereference the report paths in that digest that matter instead of acting on a one-line label. Integrate, then either delegate the next units or finish.\n\n' +
|
|
1736
|
+
'FINISH: a worker ends its own work with a final push, which writes the canonical result, marks done, and closes the window; stopping without it is not finishing. For a job too big for one context window, promote to an orchestrator; when context fills, yield to refresh against that roadmap.',
|
|
1353
1737
|
},
|
|
1354
|
-
children: [nodeNew,
|
|
1738
|
+
children: [nodeNew, nodeMsg, nodeSubscribe, nodeUnsubscribe, nodePromote, nodeYield, nodeFocus, nodeCycle, nodeInspect, nodeLifecycle, nodeConfig, nodeTriggers],
|
|
1355
1739
|
});
|
|
1356
1740
|
}
|