@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.
- package/dist/build-root.d.ts +6 -6
- package/dist/build-root.js +12 -13
- package/dist/builtin-memory/00-runtime-base.md +3 -4
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
- package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
- package/dist/builtin-memory/advisor/council.md +40 -0
- package/dist/builtin-memory/design.md +8 -11
- package/dist/builtin-memory/development.md +6 -9
- package/dist/builtin-memory/internal/INDEX.md +5 -5
- package/dist/builtin-memory/internal/agent-shaping.md +5 -7
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/marketplaces.md +13 -14
- package/dist/builtin-memory/internal/memory-loading.md +45 -0
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
- package/dist/builtin-memory/internal/plugins.md +61 -45
- package/dist/builtin-memory/planning.md +4 -7
- package/dist/builtin-memory/spec.md +9 -12
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
- package/dist/cli.js +1 -1
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
- package/dist/clients/attach/__tests__/context-message.test.js +31 -20
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
- package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
- package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
- package/dist/clients/attach/render/chat-view.d.ts +20 -20
- package/dist/clients/attach/render/chat-view.js +88 -89
- package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
- package/dist/clients/attach/render/condensed-history.js +60 -0
- package/dist/clients/attach/render/context-message.js +9 -14
- package/dist/clients/attach/render/crtr-output.d.ts +2 -1
- package/dist/clients/attach/render/crtr-output.js +4 -4
- package/dist/clients/attach/render/edit-diff.d.ts +21 -6
- package/dist/clients/attach/render/edit-diff.js +90 -91
- package/dist/clients/attach/render/tool-calls.d.ts +14 -18
- package/dist/clients/attach/render/tool-calls.js +66 -41
- package/dist/clients/attach/session/bindings.d.ts +5 -3
- package/dist/clients/attach/session/bindings.js +7 -13
- package/dist/clients/attach/session/keys.d.ts +2 -0
- package/dist/clients/attach/session/keys.js +36 -37
- package/dist/clients/attach/session/profile-files.d.ts +8 -0
- package/dist/clients/attach/session/profile-files.js +157 -0
- package/dist/clients/attach/viewer.js +530 -528
- package/dist/commands/memory/lint.js +1 -1
- package/dist/commands/memory/write.js +1 -1
- package/dist/commands/node.js +2 -2
- package/dist/commands/pkg/plugin-inspect.js +6 -7
- package/dist/commands/pkg/plugin-manage.d.ts +1 -1
- package/dist/commands/pkg/plugin-manage.js +131 -19
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +6 -11
- package/dist/commands/profile/env.js +3 -3
- package/dist/commands/sys/config.js +17 -76
- package/dist/commands/sys/doctor.js +5 -91
- package/dist/commands/sys/setup-wizard.d.ts +9 -11
- package/dist/commands/sys/setup-wizard.js +47 -81
- package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
- package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
- package/dist/core/__tests__/command-plugins.test.js +36 -16
- package/dist/core/__tests__/review-model-floor.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -1
- package/dist/core/command-manifests/manifest.d.ts +24 -0
- package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
- package/dist/core/command-manifests/registry.d.ts +1 -2
- package/dist/core/command-manifests/registry.js +2 -2
- package/dist/core/command-manifests/schema.d.ts +11 -13
- package/dist/core/command-manifests/schema.js +53 -192
- package/dist/core/command-plugins/compose.d.ts +0 -6
- package/dist/core/command-plugins/compose.js +28 -75
- package/dist/core/command-plugins/discovery.d.ts +25 -58
- package/dist/core/command-plugins/discovery.js +152 -259
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +48 -0
- package/dist/core/command-plugins/store.d.ts +16 -0
- package/dist/core/command-plugins/store.js +64 -0
- package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
- package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
- package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
- package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
- package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
- package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
- package/dist/core/command.d.ts +8 -9
- package/dist/core/command.js +6 -9
- package/dist/core/config.js +6 -10
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/io.d.ts +1 -1
- package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
- package/dist/core/keybindings/attach-control.d.ts +37 -0
- package/dist/core/keybindings/attach-control.js +38 -0
- package/dist/core/keybindings/catalog.d.ts +5 -4
- package/dist/core/keybindings/catalog.js +16 -8
- package/dist/core/keybindings/index.d.ts +1 -0
- package/dist/core/keybindings/index.js +1 -0
- package/dist/core/keybindings/types.d.ts +1 -1
- package/dist/core/preview-registry.js +41 -74
- package/dist/core/runtime/bearings.d.ts +2 -5
- package/dist/core/runtime/bearings.js +2 -5
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +4 -3
- package/dist/core/runtime/situational-context.d.ts +1 -1
- package/dist/core/runtime/situational-context.js +1 -1
- package/dist/core/runtime/spawn.js +9 -9
- package/dist/core/runtime/tmux.js +29 -2
- package/dist/core/scope.d.ts +0 -5
- package/dist/core/scope.js +0 -10
- package/dist/core/user-settings.d.ts +193 -0
- package/dist/core/user-settings.js +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/pi-extensions/canvas-stophook.js +3 -2
- package/dist/pi-extensions/canvas-tool-guide.js +4 -1
- package/dist/shared/generated-context.d.ts +34 -0
- package/dist/shared/generated-context.js +98 -0
- package/dist/types.d.ts +23 -16
- package/dist/types.js +3 -14
- package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
- package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/public-api.md +1 -0
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
- package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
- package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
- package/dist/builtin-memory/internal/workflow-codification.md +0 -82
- package/dist/builtin-memory/product.md +0 -80
- package/dist/clients/attach/render/frozen-history.js +0 -100
- package/dist/commands/pkg/cli-inspect.d.ts +0 -17
- package/dist/commands/pkg/cli-inspect.js +0 -190
- package/dist/commands/pkg/cli-manage.d.ts +0 -3
- package/dist/commands/pkg/cli-manage.js +0 -206
- package/dist/commands/pkg/cli.d.ts +0 -1
- package/dist/commands/pkg/cli.js +0 -14
- package/dist/core/configured-clis/cache.d.ts +0 -16
- package/dist/core/configured-clis/cache.js +0 -57
- package/dist/core/configured-clis/compose.d.ts +0 -14
- package/dist/core/configured-clis/compose.js +0 -60
- package/dist/core/configured-clis/discovery.d.ts +0 -47
- package/dist/core/configured-clis/discovery.js +0 -173
- package/dist/core/configured-clis/manifest.d.ts +0 -24
- package/dist/core/configured-clis/registration.d.ts +0 -40
- package/dist/core/configured-clis/registration.js +0 -201
- 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
|
|
9
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
-
|
|
95
|
-
- `crtr pkg market update --name <mkt>`
|
|
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. **
|
|
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>`
|
|
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
|
-
##
|
|
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
|
-
|
|
166
|
+
### The manifest pointer and transport
|
|
160
167
|
|
|
161
|
-
|
|
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
|
-
|
|
180
|
+
An HTTP-transport plugin replaces that declaration with:
|
|
164
181
|
|
|
165
182
|
```json
|
|
166
|
-
|
|
183
|
+
"transport": { "kind": "http", "endpoint": "https://example.com/v1/cli/manifest", "authEnv": "DEPLOY_TOKEN" }
|
|
167
184
|
```
|
|
168
185
|
|
|
169
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
210
|
+
### Execution and trust boundaries
|
|
188
211
|
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
216
|
+
### Exec protocol
|
|
194
217
|
|
|
195
|
-
crtr writes exactly one JSON request to
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
244
|
+
### Validation and command collisions
|
|
231
245
|
|
|
232
|
-
|
|
246
|
+
crtr validates command manifests statically; it never executes an exec binary to inspect its command surface:
|
|
233
247
|
|
|
234
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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)
|
|
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:
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
13
|
+
## Recover the process that is actually stuck
|
|
14
14
|
|
|
15
|
-
- **A subprocess exists
|
|
16
|
-
- **No subprocess exists
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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('
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
assert.
|
|
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/);
|