@north-light/crouter 0.3.163 → 0.3.165

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist/build-root.d.ts +6 -6
  2. package/dist/build-root.js +12 -13
  3. package/dist/builtin-memory/00-runtime-base.md +3 -4
  4. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  5. package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
  6. package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
  7. package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
  8. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
  9. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
  10. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
  11. package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
  12. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
  13. package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
  14. package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
  15. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
  16. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
  17. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
  18. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
  20. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
  21. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
  22. package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
  23. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
  24. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
  26. package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
  27. package/dist/builtin-memory/advisor/council.md +40 -0
  28. package/dist/builtin-memory/design.md +8 -11
  29. package/dist/builtin-memory/development.md +6 -9
  30. package/dist/builtin-memory/internal/INDEX.md +5 -5
  31. package/dist/builtin-memory/internal/agent-shaping.md +5 -7
  32. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
  33. package/dist/builtin-memory/internal/marketplaces.md +13 -14
  34. package/dist/builtin-memory/internal/memory-loading.md +45 -0
  35. package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
  36. package/dist/builtin-memory/internal/plugins.md +61 -45
  37. package/dist/builtin-memory/planning.md +4 -7
  38. package/dist/builtin-memory/spec.md +9 -12
  39. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
  40. package/dist/cli.js +1 -1
  41. package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
  42. package/dist/clients/attach/__tests__/context-message.test.js +31 -20
  43. package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
  44. package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
  45. package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
  46. package/dist/clients/attach/render/chat-view.d.ts +20 -20
  47. package/dist/clients/attach/render/chat-view.js +88 -89
  48. package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
  49. package/dist/clients/attach/render/condensed-history.js +60 -0
  50. package/dist/clients/attach/render/context-message.js +9 -14
  51. package/dist/clients/attach/render/crtr-output.d.ts +2 -1
  52. package/dist/clients/attach/render/crtr-output.js +4 -4
  53. package/dist/clients/attach/render/edit-diff.d.ts +21 -6
  54. package/dist/clients/attach/render/edit-diff.js +90 -91
  55. package/dist/clients/attach/render/tool-calls.d.ts +14 -18
  56. package/dist/clients/attach/render/tool-calls.js +66 -41
  57. package/dist/clients/attach/session/bindings.d.ts +5 -3
  58. package/dist/clients/attach/session/bindings.js +7 -13
  59. package/dist/clients/attach/session/keys.d.ts +2 -0
  60. package/dist/clients/attach/session/keys.js +36 -37
  61. package/dist/clients/attach/session/profile-files.d.ts +8 -0
  62. package/dist/clients/attach/session/profile-files.js +157 -0
  63. package/dist/clients/attach/viewer.js +530 -528
  64. package/dist/commands/memory/lint.js +1 -1
  65. package/dist/commands/memory/write.js +1 -1
  66. package/dist/commands/node.js +2 -2
  67. package/dist/commands/pkg/plugin-inspect.js +6 -7
  68. package/dist/commands/pkg/plugin-manage.d.ts +1 -1
  69. package/dist/commands/pkg/plugin-manage.js +131 -19
  70. package/dist/commands/pkg/plugin.js +2 -2
  71. package/dist/commands/pkg.js +6 -11
  72. package/dist/commands/profile/env.js +3 -3
  73. package/dist/commands/sys/config.js +17 -76
  74. package/dist/commands/sys/doctor.js +5 -91
  75. package/dist/commands/sys/setup-wizard.d.ts +9 -11
  76. package/dist/commands/sys/setup-wizard.js +47 -81
  77. package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
  78. package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
  79. package/dist/core/__tests__/command-plugins.test.js +36 -16
  80. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  81. package/dist/core/__tests__/tmux-surface.test.js +10 -1
  82. package/dist/core/command-manifests/manifest.d.ts +24 -0
  83. package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
  84. package/dist/core/command-manifests/registry.d.ts +1 -2
  85. package/dist/core/command-manifests/registry.js +2 -2
  86. package/dist/core/command-manifests/schema.d.ts +11 -13
  87. package/dist/core/command-manifests/schema.js +53 -192
  88. package/dist/core/command-plugins/compose.d.ts +0 -6
  89. package/dist/core/command-plugins/compose.js +28 -75
  90. package/dist/core/command-plugins/discovery.d.ts +25 -58
  91. package/dist/core/command-plugins/discovery.js +152 -259
  92. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  93. package/dist/core/command-plugins/endpoint.js +48 -0
  94. package/dist/core/command-plugins/store.d.ts +16 -0
  95. package/dist/core/command-plugins/store.js +64 -0
  96. package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
  97. package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
  98. package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
  99. package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
  100. package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
  101. package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
  102. package/dist/core/command.d.ts +8 -9
  103. package/dist/core/command.js +6 -9
  104. package/dist/core/config.js +6 -10
  105. package/dist/core/env-name.d.ts +6 -0
  106. package/dist/core/env-name.js +9 -0
  107. package/dist/core/io.d.ts +1 -1
  108. package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
  109. package/dist/core/keybindings/attach-control.d.ts +37 -0
  110. package/dist/core/keybindings/attach-control.js +38 -0
  111. package/dist/core/keybindings/catalog.d.ts +5 -4
  112. package/dist/core/keybindings/catalog.js +16 -8
  113. package/dist/core/keybindings/index.d.ts +1 -0
  114. package/dist/core/keybindings/index.js +1 -0
  115. package/dist/core/keybindings/types.d.ts +1 -1
  116. package/dist/core/preview-registry.js +41 -74
  117. package/dist/core/runtime/bearings.d.ts +2 -5
  118. package/dist/core/runtime/bearings.js +2 -5
  119. package/dist/core/runtime/front-door.d.ts +1 -1
  120. package/dist/core/runtime/front-door.js +2 -2
  121. package/dist/core/runtime/kickoff.d.ts +3 -3
  122. package/dist/core/runtime/kickoff.js +4 -3
  123. package/dist/core/runtime/situational-context.d.ts +1 -1
  124. package/dist/core/runtime/situational-context.js +1 -1
  125. package/dist/core/runtime/spawn.js +9 -9
  126. package/dist/core/runtime/tmux.js +29 -2
  127. package/dist/core/scope.d.ts +0 -5
  128. package/dist/core/scope.js +0 -10
  129. package/dist/core/user-settings.d.ts +193 -0
  130. package/dist/core/user-settings.js +252 -0
  131. package/dist/index.d.ts +4 -0
  132. package/dist/index.js +3 -0
  133. package/dist/pi-extensions/canvas-stophook.js +3 -2
  134. package/dist/pi-extensions/canvas-tool-guide.js +4 -1
  135. package/dist/shared/generated-context.d.ts +34 -0
  136. package/dist/shared/generated-context.js +98 -0
  137. package/dist/types.d.ts +23 -16
  138. package/dist/types.js +3 -14
  139. package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
  140. package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
  141. package/dist/web-client/index.html +2 -2
  142. package/dist/web-client/sw.js +1 -1
  143. package/docs/public-api.md +1 -0
  144. package/package.json +2 -2
  145. package/runtime.lock.json +6 -6
  146. package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
  147. package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
  148. package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
  149. package/dist/builtin-memory/internal/workflow-codification.md +0 -82
  150. package/dist/builtin-memory/product.md +0 -80
  151. package/dist/clients/attach/render/frozen-history.js +0 -100
  152. package/dist/commands/pkg/cli-inspect.d.ts +0 -17
  153. package/dist/commands/pkg/cli-inspect.js +0 -190
  154. package/dist/commands/pkg/cli-manage.d.ts +0 -3
  155. package/dist/commands/pkg/cli-manage.js +0 -206
  156. package/dist/commands/pkg/cli.d.ts +0 -1
  157. package/dist/commands/pkg/cli.js +0 -14
  158. package/dist/core/configured-clis/cache.d.ts +0 -16
  159. package/dist/core/configured-clis/cache.js +0 -57
  160. package/dist/core/configured-clis/compose.d.ts +0 -14
  161. package/dist/core/configured-clis/compose.js +0 -60
  162. package/dist/core/configured-clis/discovery.d.ts +0 -47
  163. package/dist/core/configured-clis/discovery.js +0 -173
  164. package/dist/core/configured-clis/manifest.d.ts +0 -24
  165. package/dist/core/configured-clis/registration.d.ts +0 -40
  166. package/dist/core/configured-clis/registration.js +0 -201
  167. package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
@@ -5,8 +5,8 @@ when-and-why-to-read: When creating a crtr plugin, packaging memory docs for
5
5
  debugging install/resolution, this knowledge should be read so installs resolve
6
6
  predictably across scopes and command surfaces do not fail from manifest drift or protocol mistakes.
7
7
  short-form: How to author a crtr plugin — plugin.json manifest, directory
8
- layout, scopes, install mechanics, versioning, and command plugins (top-level
9
- CLI commands via commands.json + one executable). Use when creating a plugin,
8
+ layout, scopes, install mechanics, versioning, and command-capable plugins
9
+ (commands.json plus an exec or HTTP transport). Use when creating a plugin,
10
10
  packaging memory docs, contributing commands, or debugging install/resolution.
11
11
  system-prompt-visibility: name
12
12
  file-read-visibility: none
@@ -42,7 +42,7 @@ If it's a one-off note for yourself, scope-owned memory docs are simpler. Promot
42
42
  └── <name>.md
43
43
  ```
44
44
 
45
- The `<plugin-name>` directory IS the plugin. The manifest's `name` field must match the directory name (install renames if needed). Sibling dirs (`rules/`, `agents/`, and future `hooks/`) hold the other artifact types. A plugin that contributes CLI commands adds a `commands.json` manifest plus its executable — see [Command plugins](#command-plugins).
45
+ The `<plugin-name>` directory IS the plugin. The manifest's `name` field must match the directory name (install renames if needed). Sibling dirs (`rules/`, `agents/`, and future `hooks/`) hold the other artifact types. A plugin that contributes CLI commands adds a `commands.json` manifest plus a `transport` declaration — see [Plugin commands](#plugin-commands).
46
46
 
47
47
  ## The manifest
48
48
 
@@ -68,7 +68,8 @@ The `<plugin-name>` directory IS the plugin. The manifest's `name` field must ma
68
68
  | `description` | yes | One sentence. |
69
69
  | `source` | recommended | Git URL where the plugin lives. Used by `crtr pkg plugin update --name <name>`. |
70
70
  | `owner` | optional | Author info. |
71
- | `commands` | optional | Plugin-root-relative path to a `commands.json` command manifest. Present ⇒ the plugin contributes top-level `crtr` commands — see [Command plugins](#command-plugins). |
71
+ | `commands` | optional | Plugin-root-relative path to a `commands.json` command manifest. It must appear with `transport`; together they make the plugin contribute `crtr` commands — see [Plugin commands](#plugin-commands). |
72
+ | `transport` | optional | Required exactly when `commands` is present. `{ "kind": "exec", "executable": "bin/cmd.js" }` runs a local executable; `{ "kind": "http", "endpoint": "https://…", "authEnv": "TOKEN_NAME" }` calls a remote HTTP command surface. |
72
73
 
73
74
  ## Scopes
74
75
 
@@ -83,7 +84,7 @@ Project-scope plugins outrank user-scope on resolution. Both outrank marketplace
83
84
 
84
85
  ## Install mechanics
85
86
 
86
- Three ways a plugin lands in a scope:
87
+ Four ways a plugin lands in a scope:
87
88
 
88
89
  1. **From a git URL** (`crtr pkg plugin install <url> --scope user`):
89
90
  - Clones into `<scope>/plugins/<name>/` using the manifest's name.
@@ -91,11 +92,15 @@ Three ways a plugin lands in a scope:
91
92
  - Independent of any marketplace.
92
93
 
93
94
  2. **From a marketplace** (`crtr pkg plugin install <mkt>/<name>`):
94
- - **Symlinks** the marketplace's `plugins/<name>/` into `<scope>/plugins/<name>/`.
95
- - `crtr pkg market update --name <mkt>` pulls updates for every installed plugin from that marketplace.
95
+ - A marketplace-relative `source` is symlinked from the marketplace checkout; a remote Git `source` is cloned into `<scope>/plugins/<name>/`.
96
+ - `crtr pkg market update --name <mkt>` refreshes the marketplace and every installed plugin it sources; relative sources follow the checkout, while remote-source checkouts pull their own Git remote.
96
97
  - See [[internal/marketplaces]].
97
98
 
98
- 3. **Authored in place** (you're writing the plugin in a working repo):
99
+ 3. **From an HTTP endpoint** (`crtr pkg plugin install --endpoint <url> --name <name> --scope user`):
100
+ - Fetches the served `commands.json`, then writes the plugin manifest and exact fetched bytes into the selected scope. The endpoint must use `https:`; `http:` is accepted only for `localhost`, `127.0.0.1`, `[::1]`, `::1`, or `host.docker.internal`, with no credentials or fragment.
101
+ - The install fails loudly and writes nothing when that fetch fails. Reinstalling the same HTTP plugin replaces its endpoint/auth-env declaration and stored manifest; it conflicts with an existing non-HTTP plugin of the same name.
102
+
103
+ 4. **Authored in place** (you're writing the plugin in a working repo):
99
104
  - Symlink for tight dev loop: `ln -s $(pwd) ~/.crouter/plugins/<name>`.
100
105
  - Or `crtr pkg plugin install file://$(pwd) --scope project` to clone-install.
101
106
 
@@ -131,7 +136,7 @@ Standard semver:
131
136
  | New doc, new section, new example | minor (0.1.0 → 0.2.0) |
132
137
  | Removed doc, renamed doc, changed manifest schema | major (0.1.0 → 1.0.0) |
133
138
 
134
- `crtr pkg plugin update --name <name>` reads the new version after pulling and updates the local config. Plugins published through a marketplace may have their `version` field bumped automatically by CI — see [[internal/marketplaces]].
139
+ `crtr pkg plugin update --name <name>` pulls source updates for ordinary and exec-transport plugins, while an HTTP-transport plugin unconditionally refetches and replaces its stored `commands.json`. A failed HTTP refresh preserves the prior bytes and exits nonzero. Plugins published through a marketplace may have their `version` field bumped automatically by CI — see [[internal/marketplaces]].
135
140
 
136
141
  ## Enable/disable
137
142
 
@@ -154,45 +159,63 @@ Bad plugin scope:
154
159
 
155
160
  If your memory doc conceptually depends on another plugin's doc, link via `## Related` with `` `<plugin>/<doc>` ``. Don't fork content; link it.
156
161
 
157
- ## Command plugins
162
+ ## Plugin commands
163
+
164
+ Beyond docs, a plugin may contribute **top-level `crtr` commands** — new noun branches with their own leaves. It does so through one `commands` pointer and one `transport` declaration. `transport.kind` selects how every leaf runs: `exec` direct-spawns a local executable; `http` calls the remote command surface. crtr owns parsing, native help, rendering, and errors for both.
158
165
 
159
- Beyond docs, a plugin may contribute **top-level `crtr` commands** — new noun branches with their own leaves. You declare one pointer in the manifest and ship one executable; crtr owns parsing, help, rendering, and errors, and direct-spawns your executable once per leaf invocation.
166
+ ### The manifest pointer and transport
160
167
 
161
- ### The manifest pointer
168
+ `commands` and `transport` appear together in `plugin.json`; a docs-only plugin declares neither:
169
+
170
+ ```json
171
+ {
172
+ "name": "deploy-tools",
173
+ "version": "0.1.0",
174
+ "description": "...",
175
+ "commands": "commands.json",
176
+ "transport": { "kind": "exec", "executable": "bin/cmd.js" }
177
+ }
178
+ ```
162
179
 
163
- Add `commands` to `plugin.json` — a plugin-root-relative path to one static manifest:
180
+ An HTTP-transport plugin replaces that declaration with:
164
181
 
165
182
  ```json
166
- { "name": "deploy-tools", "version": "0.1.0", "description": "...", "commands": "commands.json" }
183
+ "transport": { "kind": "http", "endpoint": "https://example.com/v1/cli/manifest", "authEnv": "DEPLOY_TOKEN" }
167
184
  ```
168
185
 
169
- Only an installed, **enabled** plugin's `commands` manifest contributes. Discovery is per-invocation: enable/disable/update/remove takes effect on the very next `crtr` call — no daemon restart, no cache to clear.
186
+ For `exec`, `executable` is plugin-root-relative, resolves inside the plugin root to a regular file, and carries the POSIX exec bit. For `http`, `endpoint` is an absolute endpoint and `authEnv` is optional. crtr stores only the environment variable **name**, never its credential; it reads the credential when fetching the manifest or invoking a leaf.
187
+
188
+ Only an installed, **enabled** plugin's command manifest contributes. Discovery is per-invocation: enable, disable, update, and remove take effect on the next `crtr` call — no daemon restart or cache clearing.
170
189
 
171
190
  ### commands.json shape
172
191
 
192
+ Both transports use a strict, static `commands.json` with `schemaVersion: 1` and a non-empty `mounts` array:
193
+
173
194
  ```json
174
195
  {
175
196
  "schemaVersion": 1,
176
- "executable": "bin/cmd.js",
177
197
  "mounts": [
178
- { "parent": [], "node": { "kind": "branch", "name": "app", "...": "..." } }
198
+ { "parent": [], "node": { "kind": "branch", "name": "app", "...": "..." } },
199
+ { "parent": ["app"], "node": { "kind": "branch", "name": "deploy", "...": "..." } }
179
200
  ]
180
201
  }
181
202
  ```
182
203
 
183
- - `schemaVersion` — exactly the integer `1`. Anything else rejects the whole manifest.
184
- - `executable` — plugin-root-relative path to the one command binary. Must resolve inside the plugin root, be a regular file, and carry the POSIX exec bit.
185
- - `mounts[]` — each `{ parent: [], node }`. v1 supports **top-level mounts only**: `parent` must be `[]`.
204
+ Each mount is `{ parent, node }`. `parent: []` contributes a top-level branch with `rootEntry { concept, description, whenToUse }`; a non-empty parent path attaches a node below a branch in the same plugin's manifest. Inline children and nested mounts form one forest. A nested mount whose parent is in a top-level inline tree resolves regardless of mount order; when one nested mount supplies another's parent, the parent mount comes first. Branches may have empty `children`, so another mount can fill them.
205
+
206
+ An exec manifest accepts only `schemaVersion` and `mounts`. Its leaves declare `outputKind: "object"`; branches may declare `passthrough`. An HTTP manifest additionally accepts optional absolute HTTP(S) `baseUrl` and positive-integer `timeouts { connectMs?, requestMs?, streamIdleMs? }`. Its leaves declare a `rest` mapping for method, path, parameter placement, and optional NDJSON streaming. The REST mapping is transport detail: generated `-h` presents HTTP and exec commands like native commands.
207
+
208
+ Every branch child is a branch (without `rootEntry`) or leaf. A leaf declares `params`, `output` (array of `{ name, type, required, constraint }`), and a non-empty `effects` array. Params use crtr's public vocabulary — one positional max, long-form `flag`s (types `string|int|bool|path|enum`, `choices` for enum), `stdin`, `context-file`; kebab-case names, no aliases. Two client-side affordances ride on a `positional` or `flag`: `encoding: "text"|"base64"` on a `type: "path"` param sends the named local FILE's content instead of the path string, and `defaultFromEnv: "UPPER_SNAKE"` fills an omitted `string`/`path` param from that environment variable on the calling machine, counting as supplied (so it satisfies `required` and is sent) — unlike a static `default`, which is a parse convenience only and never ships. `defaultFromEnv` is rejected alongside `default` or `repeatable`. The declaration mirrors crtr's stable help descriptors, not its internal TypeScript defs — no closures, dynamic state, or renderers.
186
209
 
187
- Every top-level `node` is a **branch** (`kind: "branch"`) carrying a `rootEntry { concept, description, whenToUse }` — the representation crtr renders at root help. Branch children are nested branches (no rootEntry) or leaves. A leaf declares `params`, `output` (array of `{ name, type, required, constraint }`), `outputKind: "object"`, and a non-empty `effects` array. Params use crtr's public vocabulary — one positional max, long-form `flag`s (types `string|int|bool|path|enum`, `choices` for enum), `stdin`, `context-file`; kebab-case names, no aliases. Two client-side affordances ride on a `positional` or `flag`: `encoding: "text"|"base64"` on a `type: "path"` param sends the named local FILE's content instead of the path string, and `defaultFromEnv: "UPPER_SNAKE"` fills an omitted `string`/`path` param from that environment variable on the calling machine, counting as supplied (so it satisfies `required` and is sent) — unlike a static `default`, which is a parse convenience only and never ships. `defaultFromEnv` is rejected alongside `default` or `repeatable`. The declaration mirrors crtr's stable help descriptors, not its internal TypeScript defs — no closures, no dynamic state, no renderers.
210
+ ### Execution and trust boundaries
188
211
 
189
- ### Execution: the trust boundary
212
+ An exec leaf direct-spawns its executable (no shell) only on explicit invocation — never on install, help, or discovery — with `--crtr-command-protocol 1`, the caller's cwd, and the full environment. It is **trusted local code running with the caller's authority**: crtr does not sandbox it, filter the environment, mint a credential, or interpret its backend authentication. This is an execution trust boundary, not a sandbox.
190
213
 
191
- On explicit leaf invocation — **never on install, help, or discovery** — crtr direct-spawns your executable (no shell) with `--crtr-command-protocol 1`, the caller's cwd, and the full environment. Installed command plugins are **trusted local code running with the caller's authority**: crtr does not sandbox them, filter the environment, mint a credential, or interpret your backend's auth. Your executable owns authentication to its own backend. This is an execution trust boundary, not a sandbox — installed code can already read the caller's files, so env filtering would only imply a security guarantee that does not exist.
214
+ An HTTP leaf is **definition plus HTTP only**: crtr fetches its manifest, renders native help from it, and executes the declared REST mapping. There is no local binary to spawn or sandbox. HTTP leaf calls retain their declared timeouts, bearer-token handling, NDJSON streaming, and structured HTTP error-envelope handling.
192
215
 
193
- ### The protocol
216
+ ### Exec protocol
194
217
 
195
- crtr writes exactly one JSON request to your executable's stdin:
218
+ For an exec leaf, crtr writes exactly one JSON request to the executable's stdin:
196
219
 
197
220
  ```json
198
221
  {
@@ -203,44 +226,37 @@ crtr writes exactly one JSON request to your executable's stdin:
203
226
  }
204
227
  ```
205
228
 
206
- `input` keys are the parser's camelCase form; a declared `stdin` param arrives as an `input` string, not a second stream. Your executable writes exactly one JSON envelope to stdout and nothing else — diagnostics go to stderr:
229
+ `input` keys are the parser's camelCase form; a declared `stdin` param arrives as an `input` string, not a second stream. The executable writes exactly one JSON envelope to stdout and nothing else — diagnostics go to stderr:
207
230
 
208
231
  ```json
209
232
  { "protocolVersion": 1, "ok": true, "result": { "app_id": "app_123" } }
210
233
  { "protocolVersion": 1, "ok": false, "error": { "code": "authentication_required", "message": "...", "field": "session", "next": "..." } }
211
234
  ```
212
235
 
213
- `ok` is the source of truth (a valid envelope is honored regardless of exit code). crtr validates `result` against your declared `output` (top-level field presence + type) and renders it; `--json` mirrors the same object. An error envelope becomes a normal crtr error with your `code` — lowercase snake_case, never crtr-reserved `internal`, `unknown_path`, `command_collision`, or `plugin_protocol_error`. No envelope at all (invalid JSON, empty, extra stdout, output over 10 MiB, signal kill) becomes `plugin_protocol_error`.
214
-
215
- ### Two rules that prevent silent breakage
216
-
217
- - **Generate `commands.json` from your command definitions — never hand-write it.** The manifest must stay in lockstep with the executable's actual command surface; a hand-maintained copy drifts, and a drifted param or output field surfaces as a validation issue or a `plugin_protocol_error` at invocation. Emit it from the same source your executable dispatches on.
218
- - **A required output field must be non-null.** The adapter treats an explicit `null` for a declared-required field as *absent* → `plugin_protocol_error`. If a value is genuinely optional, declare it `required: false`; if it's required, always return a real value.
219
-
220
- ### Validating your command manifest
221
-
222
- crtr validates command manifests **statically — it never executes your binary** to check them:
236
+ `ok` is the source of truth (a valid envelope is honored regardless of exit code). crtr validates `result` against the declared `output` (top-level field presence + type) and renders it; `--json` mirrors the same object. An error envelope becomes a normal crtr error with its lowercase snake_case `code`, except crtr-reserved `internal`, `unknown_path`, `command_collision`, and `plugin_protocol_error`. No envelope at all (invalid JSON, empty, extra stdout, output over 10 MiB, signal kill) becomes `plugin_protocol_error`.
223
237
 
224
- - `crtr pkg plugin show <name>` — inventories the manifest path, executable, accepted top-level command names, and every current validation issue (each with received/expected/next).
225
- - `crtr sys doctor` — validates the manifest + executable path for every effective command plugin and reports structured remediation (disable/update/remove). `--fix` never chmods or rewrites plugin content.
226
- - `crtr pkg plugin install` / `update` — report the accepted top-level commands and any issues in their result.
238
+ ### Keeping command surfaces valid
227
239
 
228
- A fixed manifest goes live on the next invocation; there is nothing to restart.
240
+ - **Generate an exec plugin's `commands.json` from its command definitions — never hand-write it.** The manifest must stay in lockstep with the executable's command surface; drifted params or output fields surface as a validation issue or `plugin_protocol_error` at invocation.
241
+ - **A required output field must be non-null.** The adapter treats an explicit `null` for a declared-required field as absent → `plugin_protocol_error`. If a value is optional, declare it `required: false`; if it is required, return a real value.
242
+ - **HTTP plugin manifests are fetched bytes.** `crtr pkg plugin install --endpoint <url> --name <name>` fetches before writing the plugin, then stores the exact response at the plugin's declared `commands` path. `crtr pkg plugin update` unconditionally refetches HTTP plugins; failures preserve the prior bytes and exit nonzero. The stored manifest is authoritative with no TTL, ETag, or revalidation. If its file is missing or unparseable, an unknown-first-token miss fetches each affected HTTP plugin once; diagnostics name `crtr pkg plugin update <name>`.
229
243
 
230
- ## Configured CLIs
244
+ ### Validation and command collisions
231
245
 
232
- A **configured CLI** is the other way a contributor adds `crtr` commands — the CLI analogue of an MCP client. Where a command plugin ships a local **executable**, a configured CLI is **definition + HTTP only**: crtr fetches a manifest from a remote endpoint, stores it locally, renders native `-h` help from it, and runs each leaf as a declarative REST call. There is no local binary to spawn and nothing to sandbox — a strictly narrower trust surface than a command plugin.
246
+ crtr validates command manifests statically; it never executes an exec binary to inspect its command surface:
233
247
 
234
- The manifest **is** the command-plugin `commands.json` schema (same `schemaVersion 1`, same node/param/output vocabulary, same reject-unknown-keys strictness), differing in exactly three ways: it drops `executable`, allows non-empty mount `parent` paths (a CLI builds a self-contained forest and may mount at depth), and every leaf carries a `rest` mapping (method/path/param placement/streaming flag) instead of `outputKind`. The `rest` mapping is transport — it never appears in generated `-h`, so a configured command is indistinguishable from a native one.
248
+ - `crtr pkg plugin show <name>` inventories the plugin manifest, command-manifest path, accepted top-level command names, and validation issues (each with received/expected/next).
249
+ - `crtr sys doctor` validates the command manifest and transport declaration for every effective plugin, including executable-path checks for exec transport, and reports structured remediation (disable, update, remove). `--fix` never chmods or rewrites plugin content.
250
+ - `crtr pkg plugin install` and `update` report accepted top-level command names and validation issues.
235
251
 
236
- Manage them under `crtr pkg cli` — `register` (records `{name, endpoint, auth-env NAME}` in scope config and fetches+stores the manifest), `remove`, `list`, `show`, `refresh`. crtr stores only the env var **name** holding the bearer token, never the credential. The stored manifest is authoritative with **zero freshness machinery** (no TTL, ETag, or revalidation): it reloads only on a re-register (the guest-boot path — a byte-identical re-register still re-fetches) or an explicit `refresh`, and a registration with no stored manifest (its register-time fetch failed) triggers a one-time hydration fetch on an unknown-first-token miss, the sole moment an absent store is detected. Inspect a CLI's accepted commands and validation/collision issues with `crtr pkg cli show <name>` and `crtr sys doctor`; both read the same unified snapshot dispatch uses. A configured CLI may never mount onto a core, plugin, or other-CLI path — core always wins, cross-contributor path clashes drop all claimants with a `command_collision` issue.
252
+ Core always wins a path collision. A cross-plugin collision drops every claimant with a `command_collision` issue. A fixed manifest goes live on the next invocation; there is nothing to restart.
237
253
 
238
254
  ## Validation
239
255
 
240
256
  `crtr sys doctor` checks each plugin's manifest:
241
257
  - Manifest exists and is valid JSON.
242
258
  - Manifest `name` matches the directory name.
243
- - When the plugin declares `commands`, its command manifest + executable path are validated statically (never executed) — see [Command plugins](#command-plugins).
259
+ - When the plugin declares `commands`, its command manifest and transport declaration are validated statically; an exec executable is never executed during validation — see [Plugin commands](#plugin-commands).
244
260
 
245
261
  `crtr memory lint` checks the docs under `memory/`: frontmatter parses, valid `kind`, both visibility rungs set. Run `crtr memory write -h` for the authoring + routing guide. Other sibling artifact dirs (`rules/`, `agents/`, `hooks/`) are validated by their respective specs as those land.
246
262
 
@@ -2,12 +2,9 @@
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When shaping a planning roadmap, deciding plan structure, or preparing a consequential plan for implementation, this knowledge should be read so implementation receives a right-sized, parallel-safe execution map whose gaps are caught while they are still cheap to fix.
4
4
  short-form: Use when shaping a planning roadmap, deciding plan structure, or preparing a consequential plan for implementation.
5
- system-prompt-visibility: name
5
+ system-prompt-visibility: preview
6
6
  file-read-visibility: none
7
- gate:
8
- kind:
9
- imatches: '^plan($|/)'
10
- needs-refinement: true
7
+ gate: {kind: plan}
11
8
  rationale: >-
12
9
  The original playbook required five parallel plan reviewers before every consequential implementation and made “passes all five lenses” the ready bar. Combined with the plan persona's re-review loop, this turned lenses into agents and resolution into reviewer polling rather than plan-owner judgment.
13
10
  ---
@@ -18,9 +15,9 @@ rationale: >-
18
15
 
19
16
  Every planning effort produces either a flat plan or a decomposed plan (index + part-plans). Choosing the wrong shape wastes a cycle — a flat plan that is too large forces an implementer to hold too much at once; a decomposed plan for something small adds overhead for no gain.
20
17
 
21
- **Use a flat plan** when the work is a single coherent domain, involves fewer than ~6 files, and can be written at consistent task granularity without exceeding roughly 150–200 lines. A flat plan has an overview, ordered phases, and a verification section. No sub-plans. One file.
18
+ **Use a flat plan** when the work is a single coherent domain and can be written at consistent task granularity in one plan. A flat plan has an overview, ordered phases, and a verification section. No sub-plans. One file.
22
19
 
23
- **Use a decomposed plan** when the change spans multiple domains (e.g., data layer, API surface, UI), involves 6+ files, or would require a master plan that cannot be written at consistent granularity without ballooning. In this case: produce an index plan (the navigable master) and delegate each domain slice to a `plan`-kind child node, giving each child its slice scope, the relevant portion of the spec, and its place in the dependency graph. A slice that itself decomposes further — multiple sub-domains, more than one window's worth of planning — goes to a `plan` sub-orchestrator created directly (`crtr node new --kind plan --mode orchestrator`), not a base child relied on to promote itself. The index plan is the synthesis artifact — it lists all sub-plans by path, defines phases and their dependencies, and contains a task table the implementation orchestrator can execute directly. Detail lives in sub-plans; the master is not allowed to carry it.
20
+ **Use a decomposed plan** when the change spans multiple domains (e.g., data layer, API surface, UI) or would require a master plan that cannot be written at consistent granularity without ballooning. In this case: produce an index plan (the navigable master) and delegate each domain slice to a `plan`-kind child node, giving each child its slice scope, the relevant portion of the spec, and its place in the dependency graph. A slice that itself decomposes further — multiple sub-domains, more than one window's worth of planning — goes to a `plan` sub-orchestrator created directly (`crtr node new --kind plan --mode orchestrator`), not a base child relied on to promote itself. The index plan is the synthesis artifact — it lists all sub-plans by path, defines phases and their dependencies, and contains a task table the implementation orchestrator can execute directly. Detail lives in sub-plans; the master is not allowed to carry it.
24
21
 
25
22
  **The decomposition trigger is domain boundary, not size alone.** Three backend files and three frontend files are two domains even if the total count is modest — plan them separately and synthesize, because the integration seam is where bugs live and one agent reading both halves won't catch them as cleanly as two agents each going deep.
26
23
 
@@ -9,12 +9,9 @@ short-form: Use when running a specification effort, shaping a spec roadmap, or
9
9
  shape→design→requirements methodology, when to delegate design to a child
10
10
  node, the isolation principle behind the design/requirements split, and what a
11
11
  finished spec contains.
12
- system-prompt-visibility: name
12
+ system-prompt-visibility: preview
13
13
  file-read-visibility: none
14
- gate:
15
- kind:
16
- imatches: '^spec($|/)'
17
- needs-refinement: true
14
+ gate: {kind: spec}
18
15
  ---
19
16
 
20
17
  ## The Three Stages
@@ -25,9 +22,9 @@ A specification effort runs in exactly this order: **SHAPE** → **DESIGN** →
25
22
 
26
23
  Shape is the discovery stage — the one place this effort is genuinely interactive. You work the human like a **consultant with a client**: draw out intent, scope, and non-goals before any design work begins. The deliverable is not an artifact — it is a shared mental model sufficient to write a sharp design brief.
27
24
 
28
- Run a discovery loop with `crtr human ask`: name the most important open question, form a provisional take, offer 2–4 concrete options, get a decision, repeat. Two rules keep it sharp. **Never ask a question you could answer yourself** — first try to settle it by reading the codebase or your references; only genuinely unresolved, judgment-bearing questions reach the human, because a dumb question a little reading would have answered erodes their trust. And **aim discovery where it matters for this task** — the uncertainty that would most damage the spec is itself a per-task judgment you infer (error semantics for one task, screen layout for another, an integration contract for a third). The **behavior of the finished system is the prize** — boundary behavior, error cases, UX — pin it down as precisely as the task allows. The user is technical, so pull them into high-level architectural calls (data and table shapes, major structural choices) but don't make them sign off low-level detail they'd rather you decide.
25
+ Run a discovery loop with `crtr human ask`: name the most important open question, form a provisional take, offer concrete options, get a decision, repeat. Two rules keep it sharp. **Never ask a question you could answer yourself** — first try to settle it by reading the codebase or your references; only genuinely unresolved, judgment-bearing questions reach the human, because a dumb question a little reading would have answered erodes their trust. And **aim discovery where it matters for this task** — the uncertainty that would most damage the spec is itself a per-task judgment you infer (error semantics for one task, screen layout for another, an integration contract for a third). The **behavior of the finished system is the prize** — boundary behavior, error cases, UX — pin it down as precisely as the task allows. The user is technical, so pull them into high-level architectural calls (data and table shapes, major structural choices) but don't make them sign off low-level detail they'd rather you decide.
29
26
 
30
- Track these turns carefully. The shape stage is done when: (1) 3–7 named components or functional areas are identified, (2) the user's intent can be restated without correction, and (3) no unresolved contradictions remain between the user's goal and the existing codebase. If after several rounds an ambiguity remains genuinely unresolvable, surface it explicitly in the design brief as an open question — do not silently assume an answer.
27
+ Track these turns carefully. The shape stage is done when: (1) the named components or functional areas are identified, (2) the user's intent can be restated without correction, and (3) no unresolved contradictions remain between the user's goal and the existing codebase. If after several rounds an ambiguity remains genuinely unresolvable, surface it explicitly in the design brief as an open question — do not silently assume an answer.
31
28
 
32
29
  Gate: human confirms readiness to proceed to design.
33
30
 
@@ -35,7 +32,7 @@ Gate: human confirms readiness to proceed to design.
35
32
 
36
33
  Design produces the blueprint: components and their topology, end-to-end flows, files and directories affected, locked decisions, and open questions resolved. The altitude is infra/services — no function signatures, no algorithm descriptions, no implementation ordering. Design answers "what shape does this take?" — planning answers "how is it built?"
37
34
 
38
- Small or simple design work (one surface, clear scope, few components) can be done by a single `design`-kind child node. Large or complex design work — multi-surface features, multiple interacting subsystems, significant architectural choices — must be delegated to a **design orchestrator** (a `design`-kind node created directly with `--mode orchestrator`), which decomposes the design internally and returns a finished artifact. The trigger for spawning a design orchestrator rather than a base design node: if the design effort has more than one distinct phase or more than ~5 interacting components, use an orchestrator.
35
+ Small or simple design work (one surface, clear scope, few components) can be done by a single `design`-kind child node. Large or complex design work — multi-surface features, multiple interacting subsystems, significant architectural choices — must be delegated to a **design orchestrator** (a `design`-kind node created directly with `--mode orchestrator`), which decomposes the design internally and returns a finished artifact. The trigger for spawning a design orchestrator rather than a base design node: if the design effort has distinct phases or interacting components that need separate design treatment, use an orchestrator.
39
36
 
40
37
  Gate: human approves the rendered design artifact.
41
38
 
@@ -61,9 +58,9 @@ The isolation is structural, not stylistic. The requirements writer receives: th
61
58
 
62
59
  After the design is approved, the spec orchestrator runs `crtr node yield` before starting requirements work. This is mandatory, not optional.
63
60
 
64
- Why: the design conversation fills context with reasoning about tradeoffs, rejected alternatives, and design intent. That context biases delegation — it causes the orchestrator to frame the requirements task with assumptions from the design discussion. After yielding, the orchestrator revives fresh against `context/roadmap.md`, which records the finished design artifact path. It reads the design artifact cold and delegates the requirements work from that clean window, anchored on the rendered design rather than on the design conversation.
61
+ Why: the design conversation fills context with reasoning about tradeoffs, rejected alternatives, and design intent. That context biases delegation — it causes the orchestrator to frame the requirements task with assumptions from the design discussion. After yielding, the orchestrator revives fresh against `$CRTR_CONTEXT_DIR/roadmap.md`, which records the finished design artifact path. It reads the design artifact cold and delegates the requirements work from that clean window, anchored on the rendered design rather than on the design conversation.
65
62
 
66
- The roadmap must record the design artifact path and the current stage before yielding. On revive, the first action is to read `context/roadmap.md`, confirm the design is landed, and delegate requirements work.
63
+ The roadmap must record the design artifact path and the current stage before yielding. On revive, the first action is to read `$CRTR_CONTEXT_DIR/roadmap.md`, confirm the design is landed, and delegate requirements work.
67
64
 
68
65
  ---
69
66
 
@@ -77,9 +74,9 @@ After yield-and-revive, `## Strategy / phases` plus `## Active context` must let
77
74
 
78
75
  ## Delegating Design: Base Node vs. Orchestrator
79
76
 
80
- Spawn a base `design` node (terminal) when: the design surface is bounded, one component or subsystem, no multi-phase structure required. The node produces `context/design.md` and `context/design.json` and returns.
77
+ Spawn a base `design` node when the design surface is bounded: one component or subsystem with no multi-phase structure required. The child writes `design-<subject>.md` in its own context directory and reports that absolute path.
81
78
 
82
- Spawn a `design` orchestrator (resident) when: the feature spans multiple subsystems, has distinct implementation phases that need separate design treatment, or the design effort is itself likely to fill one context window before it's finished. Create it directly as an orchestrator — `crtr node new --kind design --mode orchestrator` — rather than spawning a base design node and counting on it to promote itself once it discovers the surface is too big; self-promotion is unreliable, and a node born an orchestrator is strictly more capable than one hoping to become one. Pass it the shape brief as its goal; it owns the decomposition and integration internally and reports a finished design artifact when done.
79
+ Spawn a terminal `design` orchestrator when the feature spans multiple subsystems, has distinct implementation phases that need separate design treatment, or the design effort is itself likely to fill one context window before it is finished. Create it directly as an orchestrator — `crtr node new --kind design --mode orchestrator` — so it owns decomposition and integration from the start. Pass it the shape brief as its goal; it writes the integrated `design-<subject>.md` in its own context directory and reports that absolute path when done.
83
80
 
84
81
  In either case, the spec orchestrator waits for the design to land and the human to approve it before proceeding.
85
82
 
@@ -1,34 +1,20 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When a node is marked wedged or remains active without progress while its inbox accumulates, this reference should be read so recovery restores progress without losing context or killing the wrong process.
4
- short-form: a child stuck mid-turn (runaway bash, or a stalled broker with no subprocess) is AUTO-DETECTED by the daemon (⚠ wedged + a doctrine wake) — kill the subprocess if one exists; if none, the daemon SIGTERM-kicks the broker and automatically starts a fresh recovery cycle
4
+ short-form: A node stuck mid-turn is detected from a stale busy heartbeat plus near-zero process-tree CPU. Kill a runaway subprocess when one exists; when the broker itself is stuck, the daemon SIGTERMs it and applies bounded, conditional recovery.
5
5
  system-prompt-visibility: preview
6
6
  file-read-visibility: none
7
7
  ---
8
8
 
9
- A base node can wedge INDEFINITELY on a single runaway bash command (classic: `grep -rln "..." /` — a recursive grep from filesystem root that scans all of disk and never returns; also any unbounded find/scan over `/` or a network mount). The node's pi turn is blocked waiting on the bash call, so it never `push`es and never finishes.
9
+ A base node can wedge mid-turn on a runaway bash command (for example, an unbounded recursive scan from `/`). Its pi turn cannot push or finish while the tool call is blocked.
10
10
 
11
- **Detection is automatic (crtrd, issue #110).** The daemon's supervision tick corroborates two signals on every live/busy node: the busy-marker heartbeat (re-touched on every tool start/stream/end and turn end) gone quiet for 20+ minutes, AND the broker's whole process tree (broker pid + every descendant) reading near-zero CPU. Only when BOTH hold does it fire — routine long tool calls that are genuinely still producing output or burning CPU never trip it. On a 'wedged' verdict the daemon records a `daemon→node`/`wedged` Fault (the local canvas graph and browse UI show ⚠ "wedged · needs you", the same surface provider-fault stalls use) and fans a doctrine wake to the node's subscribers — ONE notice per wedge episode, not a repeat every tick. In the local browse UI, the hanging-node recovery action runs `node lifecycle revive <id> --now`; there is no hanging filter on `node inspect list`.
11
+ **Detection is automatic.** The daemon reports a wedge only after both the busy heartbeat has been quiet for 20+ minutes and the broker's whole process tree is near-zero CPU. It records a `daemon→node` `wedged` fault and sends one doctrine wake to subscribers per wedge episode. The canvas marks the node as needing attention.
12
12
 
13
- **Remediation branches on whether the tree has a descendant (issue #119):**
13
+ ## Recover the process that is actually stuck
14
14
 
15
- - **A subprocess exists (the common case)** — a runaway bash/find/grep is the likely cause. The daemon does NOT touch the engine here; you kill the subprocess yourself (see below).
16
- - **No subprocess exists** — the broker pid stands alone in its own tree (the engine itself stalled, e.g. mid a `model_change`, with nothing a human could kill). The daemon performs the ONLY remaining remediation itself: it SIGTERMs the broker (the same on-demand kick `node lifecycle revive --now` does). The surviving `job/busy` marker proves the process died mid-turn, so its exit-policy job starts a fresh cycle on the saved session tree. That cycle automatically receives the normal roadmap/feed/context kickoff; it does not reopen the dead turn at an idle prompt, and no manual nudge is required.
15
+ - **A subprocess exists.** The daemon leaves the broker alone because a runaway bash/find/grep is the likely cause. Inspect the broker process tree, kill the scanner subprocess rather than its shell wrapper or node, and let the tool call return. The pi turn then resumes with its context intact. Tell the owning orchestrator if you discovered the wedge before its doctrine wake.
16
+ - **No subprocess exists.** The broker itself is stalled. The daemon SIGTERMs it; its live-exit policy then makes a bounded recovery attempt. A broker-certified clean abort resumes the saved session with a continuation. A dirty interrupted turn, pending refresh, or pending cycle starts a new recovery cycle from the saved session instead. A broker that never established a session terminalizes rather than entering a respawn loop.
17
17
 
18
- **Why it used to be invisible to the orchestrator:** the wake spine only fires on a child's push/finalize/crash. A child wedged *mid-turn* does none of those — its row stays `status=active` and the dashboard shows it `●` working. Before #110, the parent got no wake at all, so an entire wave could stall for hours looking healthy; cli-verbs-B2 sat wedged ~2h14m on a `grep /` before a safety-deadline inspection caught it. The daemon-side detection above closes that gap; you should no longer need to notice a wedge by elapsed-time vibes alone — but if you do (the daemon hasn't caught up yet, or the wedge is younger than its grace window), the manual inspection below still works.
18
+ Recovery is not unconditional. Repeated short-lived exits or a boot failure terminalize the node as `dead`; no daemon respawn follows that terminal state. Reopen it explicitly with `crtr node lifecycle revive <id>` (use `--reopen` if it finalized), or deliver an inbox wake where that is the intended control path. If the daemon is down, it cannot detect, signal, or respawn a wedge; restore the daemon before relying on lifecycle recovery.
19
19
 
20
- ## Subprocess case: kill the runaway command
21
-
22
- **How to inspect further / confirm:** `tmux capture-pane -p -t <pane>` shows the live turn (look for `Elapsed NNNNs ⠼ Working...` on one bash command); `pgrep -P <pi_pid>` + `ps -axo pid,ppid,etime,command` reveals the runaway subprocess and how long it's run.
23
-
24
- **How to apply:** kill the runaway SUBPROCESS, not the node — `kill <grep_pid>`. The bash call then returns and the pi turn resumes on its own, no context lost, no respawn. (Killing the bash `-c` wrapper can orphan the grep; kill the actual scanner PID. A pipeline `grep / | head` resumes fastest if you kill the first-stage grep — the pipe closes and bash proceeds.) Then notify the OWNING orchestrator (deferred tier) so it steers the child to completion and knows the lost time — the doctrine wake above already does this automatically for a detected wedge, but do it yourself if you found the wedge before the daemon did. Related: the `revive-stuck-orchestrator` doc covers the orchestrator-level false-finish failure mode.
25
-
26
- ## No-subprocess case: broker wedged mid-stream (`streaming:true` stuck)
27
-
28
- The wedge can outlive any subprocess, or exist with none at all. A node (including a top-level **resident orchestrator**, not just a child) can sit stuck with the canvas snapshot showing `streaming: true` and a stale `last_activity` for **days**, while the broker pid is alive but doing nothing (0% CPU) and NO subprocess exists to kill — the engine deadlocked mid-stream (often right after a bash call returned, or after a `model_change`) and never closed the turn. Tells: `job/busy` lockfile present with an old mtime, `job/telemetry.json` `updated_at` frozen, `job/broker.log` stopped growing, inbox piling up (`queued (N)` in the dashboard) because the turn never ends. The `busy` marker alone is harmless (it is always AND-ed with `pidAlive`, so a stale one from a dead pid is ignored) — do not bother deleting it.
29
-
30
- **This is now the daemon's own remediation (issue #119) — it auto-kicks the broker for you.** If it hasn't caught up yet (younger than the wedge grace, or the daemon was down), do it manually:
31
-
32
- 1. `kill -TERM <broker_pid>` — the wedged broker. Confirm it dies (`ps -p <pid>`) and that no orphan subprocess lingers (`ps -o pid,ppid,command | awk '$2==<pid>'`).
33
- 2. The **daemon auto-resumes** it within ~20s on the saved session — a fresh broker pid appears (`node inspect show`), `status=active`, `intent=null`, `view.sock` accepts connections. (No manual `node lifecycle revive` needed if the daemon is up; do it manually only if it doesn't come back.)
34
- 3. **The daemon starts the replacement as a fresh recovery cycle** — the stale `job/busy` marker distinguishes an interrupted turn from an idle crash, so the daemon selects `resume:false`. The saved session tree remains on disk, while the new cycle re-orients from `context/roadmap.md`, feed, reports, and context automatically. Confirm success by watching `telemetry.json` `updated_at` go current and the node run tools again. A manual `--tier critical` message is now only a fallback if the replacement broker itself fails to boot or provider access is still unhealthy.
20
+ Do not kill a healthy node merely because it has been working for a long time. The detector intentionally requires both a silent heartbeat and an idle process tree; a long-running tool that is still producing output or consuming CPU is not wedged.
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ async function main() {
54
54
  ensureProjectScope(process.argv);
55
55
  maybeAutoUpdate(process.argv);
56
56
  // Pass the one-shot unknown-first-token retry seam: on a first-token miss with
57
- // absent configured-CLI stores, hydrate them once, rebuild, and re-walk (§5.1).
57
+ // absent HTTP-plugin stores, hydrate them once, rebuild, and re-walk.
58
58
  // A recognized core first token resolves in `resolveRoot`'s fast path and never
59
59
  // reaches a first-token miss, so the seam stays off the core fast path.
60
60
  mark('cli.dispatch_start');
@@ -1,7 +1,7 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { resolveKeybindings } from '../../../core/keybindings/index.js';
4
- import { detachHint, inboxOpenHint, dispatchAttachBinding, matchesAttachBinding, refreshAttachBindingSnapshot, } from '../session/bindings.js';
3
+ import { decodeAttachControlInput, encodeAttachControlInput, extractAttachControlInput, resolveKeybindings, } from '../../../core/keybindings/index.js';
4
+ import { detachHint, inboxOpenHint, dispatchAttachAction, dispatchAttachBinding, matchesAttachBinding, refreshAttachBindingSnapshot, } from '../session/bindings.js';
5
5
  import { GraphOverlay } from '../overlays/graph.js';
6
6
  const PALETTE = {
7
7
  accent: (s) => s,
@@ -23,7 +23,7 @@ function fakeTui() {
23
23
  requestRender: () => { },
24
24
  };
25
25
  }
26
- test('attach input dispatches only the current configured action', () => {
26
+ test('attach direct and private control input dispatch through the same action map', () => {
27
27
  const bindings = resolveKeybindings({
28
28
  'crtr.attach.detach': ['ctrl+x'],
29
29
  'crtr.attach.clear-or-detach': [],
@@ -33,6 +33,7 @@ test('attach input dispatches only the current configured action', () => {
33
33
  'crtr.attach.model-ladder.previous': ['alt+y'],
34
34
  'crtr.attach.command.inspect': ['alt+shift+h'],
35
35
  'crtr.attach.file-review': [],
36
+ 'crtr.attach.profile-files': [],
36
37
  });
37
38
  const calls = [];
38
39
  const actions = {
@@ -44,6 +45,7 @@ test('attach input dispatches only the current configured action', () => {
44
45
  'crtr.attach.model-ladder.previous': () => calls.push('ladder-back'),
45
46
  'crtr.attach.command.inspect': () => calls.push('inspect'),
46
47
  'crtr.attach.file-review': () => calls.push('review'),
48
+ 'crtr.attach.profile-files': () => calls.push('profile-files'),
47
49
  };
48
50
  assert.equal(dispatchAttachBinding(bindings, '\x18', actions), true);
49
51
  assert.equal(dispatchAttachBinding(bindings, '\x04', actions), false, 'old detach must not reach the listener action');
@@ -55,7 +57,18 @@ test('attach input dispatches only the current configured action', () => {
55
57
  assert.equal(dispatchAttachBinding(bindings, '\x1by', actions), true);
56
58
  assert.equal(dispatchAttachBinding(bindings, '\x1bM', actions), false, 'old ladder-back key must not reach the listener action');
57
59
  assert.equal(dispatchAttachBinding(bindings, '\x1b[72;4u', actions), true);
58
- assert.deepEqual(calls, ['detach', 'mode', 'ladder', 'ladder-back', 'inspect']);
60
+ assert.equal(dispatchAttachBinding(bindings, '\x06', actions), false, 'disabled direct binding must stay disabled');
61
+ const privateInput = encodeAttachControlInput('crtr.attach.profile-files');
62
+ const privateAction = decodeAttachControlInput(privateInput);
63
+ assert.equal(privateAction, 'crtr.attach.profile-files');
64
+ dispatchAttachAction(privateAction, actions);
65
+ assert.deepEqual(calls, ['detach', 'mode', 'ladder', 'ladder-back', 'inspect', 'profile-files']);
66
+ assert.equal(decodeAttachControlInput(`${privateInput}x`), undefined, 'whole-input decoding must be exact');
67
+ assert.equal(decodeAttachControlInput('\x1b_crtr;crtr.attach.not-real\x1b\\'), undefined, 'unknown actions must stay ordinary input');
68
+ assert.deepEqual(extractAttachControlInput(`before${privateInput}after`), {
69
+ actionId: 'crtr.attach.profile-files',
70
+ remaining: 'beforeafter',
71
+ });
59
72
  assert.equal(detachHint(bindings), 'Ctrl+X', 'disabled clear-or-detach must fall back to enabled detach');
60
73
  const noDetach = resolveKeybindings({ 'crtr.attach.detach': [], 'crtr.attach.clear-or-detach': [] });
61
74
  assert.equal(detachHint(noDetach), undefined, 'a disabled pair must not render "Disabled to detach"');
@@ -1,23 +1,14 @@
1
- // Regression coverage for the attach viewer's collapsed hidden-context
2
- // rendering (render/context-message.ts + its dispatch in render/chat-view.ts).
3
- //
4
- // Both hidden-context custom types — `crtr-context` (fresh-birth bearings) and
5
- // `crtr-situational-context` (an existing node's ambient update, sent by
6
- // canvas-inbox-watcher.ts) — must render through the same collapsed
7
- // ContextMessageComponent, never the generic always-expanded
8
- // CustomMessageComponent: body text stays hidden until the user expands it
9
- // (Ctrl+O). These tests pin (a) the collapsed component itself never leaks
10
- // body text before expansion, for both labels it serves, and (b)
11
- // render/chat-view.ts's source wires the situational customType to that same
12
- // collapsed component (a full ChatView needs a live TUI/Container from
13
- // pi-tui, so this source-contract check is the smallest meaningful assertion
14
- // of the dispatch).
1
+ // Regression coverage for the attach viewer's display-only folding of
2
+ // crouter-generated context. The body must stay agent-visible and expandable,
3
+ // while bearings, situational updates, fresh-revive kickoffs, context nudges,
4
+ // and node inbox digests share one compact renderer by default.
15
5
  import assert from 'node:assert/strict';
16
6
  import { readFileSync } from 'node:fs';
17
7
  import { dirname, join } from 'node:path';
18
8
  import test from 'node:test';
19
9
  import { fileURLToPath } from 'node:url';
20
10
  import { ContextMessageComponent } from '../render/context-message.js';
11
+ import { CONTEXT_NUDGE_CUSTOM_TYPE, REVIVE_KICKOFF_SENTINEL, generatedContextPresentation, } from '../../../shared/generated-context.js';
21
12
  const HERE = dirname(fileURLToPath(import.meta.url));
22
13
  const CHAT_VIEW = readFileSync(join(HERE, '..', 'render/chat-view.ts'), 'utf8');
23
14
  const identity = (s) => s;
@@ -37,10 +28,30 @@ test('the crtr-context label/hint keep their defaults when callers omit the new
37
28
  assert.match(line, /\[crtr context\]/);
38
29
  assert.match(line, /orienting bearings/);
39
30
  });
40
- test('render/chat-view.ts dispatches the situational customType to the collapsed component, not the generic fallback', () => {
41
- assert.match(CHAT_VIEW, /SITUATIONAL_CONTEXT_CUSTOM_TYPE/, 'must reference the constant, not a duplicated literal');
42
- const situationalBranch = CHAT_VIEW.slice(CHAT_VIEW.indexOf('customType === SITUATIONAL_CONTEXT_CUSTOM_TYPE'));
43
- const nextBraceClose = situationalBranch.indexOf('\n }');
44
- const branchBody = situationalBranch.slice(0, nextBraceClose === -1 ? undefined : nextBraceClose);
45
- assert.match(branchBody, /new ContextMessageComponent\(/, 'must render collapsed via the same component crtr-context uses, not CustomMessageComponent');
31
+ test('fresh-revive kickoff and both context-nudge roles classify as generated context', () => {
32
+ const revive = generatedContextPresentation({
33
+ role: 'user',
34
+ content: `${REVIVE_KICKOFF_SENTINEL} — full disk bearings`,
35
+ });
36
+ assert.equal(revive?.label, 'crtr revive');
37
+ assert.equal(revive?.summary, 'fresh context kickoff');
38
+ const body = '[crtr] Context ~150k. Yield when substantial work remains.';
39
+ const steered = generatedContextPresentation({ role: 'user', content: body });
40
+ const held = generatedContextPresentation({
41
+ role: 'custom',
42
+ customType: CONTEXT_NUDGE_CUSTOM_TYPE,
43
+ content: body,
44
+ });
45
+ assert.equal(steered?.summary, 'Context ~150k');
46
+ assert.equal(held?.summary, 'Context ~150k');
47
+ const inbox = generatedContextPresentation({
48
+ role: 'user',
49
+ content: 'From child-node-123 — 2 updates:\n [update] still working',
50
+ });
51
+ assert.equal(inbox?.label, 'crtr inbox');
52
+ assert.equal(inbox?.summary, '2 updates from child-node-123');
53
+ });
54
+ test('render/chat-view.ts routes the shared generated-context classification through the foldable component', () => {
55
+ assert.match(CHAT_VIEW, /generatedContextPresentation\(message\)/);
56
+ assert.match(CHAT_VIEW, /new ContextMessageComponent\(/);
46
57
  });
@@ -50,7 +50,7 @@ test('the attach viewer covers crtr bash tool calls and crtr-output messages nat
50
50
  const chatView = read(join(ROOT, 'clients', 'attach', 'render/chat-view.ts'));
51
51
  assert.match(chatView, /CRTR_OUTPUT_CUSTOM_TYPE/);
52
52
  assert.match(chatView, /new CrtrOutputMessageComponent\(/);
53
- assert.match(chatView, /createCrtrBashToolDefinition\(args\)/);
53
+ assert.match(chatView, /createCrtrBashToolDefinition\(args,\s*fold\)/);
54
54
  // MinimizableToolComponent is the viewer's ToolExecutionComponent subclass (it
55
55
  // adds the Ctrl+O minimized state); the crtr tool definition still threads through it.
56
56
  assert.match(chatView, /new MinimizableToolComponent\([\s\S]*toolDefinition as never/);