@north-light/crouter 0.3.164 → 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 (155) 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 +4 -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/nodes-and-canvas.md +4 -4
  35. package/dist/builtin-memory/internal/plugins.md +61 -45
  36. package/dist/builtin-memory/planning.md +4 -7
  37. package/dist/builtin-memory/spec.md +9 -12
  38. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
  39. package/dist/cli.js +1 -1
  40. package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
  41. package/dist/clients/attach/__tests__/context-message.test.js +31 -20
  42. package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
  43. package/dist/clients/attach/render/chat-view.d.ts +20 -20
  44. package/dist/clients/attach/render/chat-view.js +42 -54
  45. package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
  46. package/dist/clients/attach/render/condensed-history.js +60 -0
  47. package/dist/clients/attach/render/context-message.js +9 -14
  48. package/dist/clients/attach/session/bindings.d.ts +5 -3
  49. package/dist/clients/attach/session/bindings.js +7 -13
  50. package/dist/clients/attach/session/keys.d.ts +2 -0
  51. package/dist/clients/attach/session/keys.js +36 -37
  52. package/dist/clients/attach/session/profile-files.d.ts +8 -0
  53. package/dist/clients/attach/session/profile-files.js +157 -0
  54. package/dist/clients/attach/viewer.js +525 -525
  55. package/dist/commands/node.js +2 -2
  56. package/dist/commands/pkg/plugin-inspect.js +6 -7
  57. package/dist/commands/pkg/plugin-manage.d.ts +1 -1
  58. package/dist/commands/pkg/plugin-manage.js +131 -19
  59. package/dist/commands/pkg/plugin.js +2 -2
  60. package/dist/commands/pkg.js +6 -11
  61. package/dist/commands/profile/env.js +3 -3
  62. package/dist/commands/sys/config.js +17 -76
  63. package/dist/commands/sys/doctor.js +5 -91
  64. package/dist/commands/sys/setup-wizard.d.ts +9 -11
  65. package/dist/commands/sys/setup-wizard.js +47 -81
  66. package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
  67. package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
  68. package/dist/core/__tests__/command-plugins.test.js +36 -16
  69. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  70. package/dist/core/__tests__/tmux-surface.test.js +10 -1
  71. package/dist/core/command-manifests/manifest.d.ts +24 -0
  72. package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
  73. package/dist/core/command-manifests/registry.d.ts +1 -2
  74. package/dist/core/command-manifests/registry.js +2 -2
  75. package/dist/core/command-manifests/schema.d.ts +11 -13
  76. package/dist/core/command-manifests/schema.js +53 -192
  77. package/dist/core/command-plugins/compose.d.ts +0 -6
  78. package/dist/core/command-plugins/compose.js +28 -75
  79. package/dist/core/command-plugins/discovery.d.ts +25 -58
  80. package/dist/core/command-plugins/discovery.js +152 -259
  81. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  82. package/dist/core/command-plugins/endpoint.js +48 -0
  83. package/dist/core/command-plugins/store.d.ts +16 -0
  84. package/dist/core/command-plugins/store.js +64 -0
  85. package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
  86. package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
  87. package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
  88. package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
  89. package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
  90. package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
  91. package/dist/core/command.d.ts +8 -9
  92. package/dist/core/command.js +6 -9
  93. package/dist/core/config.js +6 -10
  94. package/dist/core/env-name.d.ts +6 -0
  95. package/dist/core/env-name.js +9 -0
  96. package/dist/core/io.d.ts +1 -1
  97. package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
  98. package/dist/core/keybindings/attach-control.d.ts +37 -0
  99. package/dist/core/keybindings/attach-control.js +38 -0
  100. package/dist/core/keybindings/catalog.d.ts +5 -4
  101. package/dist/core/keybindings/catalog.js +16 -8
  102. package/dist/core/keybindings/index.d.ts +1 -0
  103. package/dist/core/keybindings/index.js +1 -0
  104. package/dist/core/keybindings/types.d.ts +1 -1
  105. package/dist/core/preview-registry.js +41 -74
  106. package/dist/core/runtime/bearings.d.ts +2 -5
  107. package/dist/core/runtime/bearings.js +2 -5
  108. package/dist/core/runtime/front-door.d.ts +1 -1
  109. package/dist/core/runtime/front-door.js +2 -2
  110. package/dist/core/runtime/kickoff.d.ts +3 -3
  111. package/dist/core/runtime/kickoff.js +4 -3
  112. package/dist/core/runtime/situational-context.d.ts +1 -1
  113. package/dist/core/runtime/situational-context.js +1 -1
  114. package/dist/core/runtime/spawn.js +9 -9
  115. package/dist/core/runtime/tmux.js +29 -2
  116. package/dist/core/scope.d.ts +0 -5
  117. package/dist/core/scope.js +0 -10
  118. package/dist/core/user-settings.d.ts +193 -0
  119. package/dist/core/user-settings.js +252 -0
  120. package/dist/index.d.ts +4 -0
  121. package/dist/index.js +3 -0
  122. package/dist/pi-extensions/canvas-stophook.js +3 -2
  123. package/dist/shared/generated-context.d.ts +34 -0
  124. package/dist/shared/generated-context.js +98 -0
  125. package/dist/types.d.ts +23 -16
  126. package/dist/types.js +3 -14
  127. package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
  128. package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
  129. package/dist/web-client/index.html +2 -2
  130. package/dist/web-client/sw.js +1 -1
  131. package/docs/public-api.md +1 -0
  132. package/package.json +1 -1
  133. package/runtime.lock.json +2 -2
  134. package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
  135. package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
  136. package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
  137. package/dist/builtin-memory/internal/workflow-codification.md +0 -82
  138. package/dist/builtin-memory/product.md +0 -80
  139. package/dist/clients/attach/render/frozen-history.js +0 -100
  140. package/dist/commands/pkg/cli-inspect.d.ts +0 -17
  141. package/dist/commands/pkg/cli-inspect.js +0 -190
  142. package/dist/commands/pkg/cli-manage.d.ts +0 -3
  143. package/dist/commands/pkg/cli-manage.js +0 -206
  144. package/dist/commands/pkg/cli.d.ts +0 -1
  145. package/dist/commands/pkg/cli.js +0 -14
  146. package/dist/core/configured-clis/cache.d.ts +0 -16
  147. package/dist/core/configured-clis/cache.js +0 -57
  148. package/dist/core/configured-clis/compose.d.ts +0 -14
  149. package/dist/core/configured-clis/compose.js +0 -60
  150. package/dist/core/configured-clis/discovery.d.ts +0 -47
  151. package/dist/core/configured-clis/discovery.js +0 -173
  152. package/dist/core/configured-clis/manifest.d.ts +0 -24
  153. package/dist/core/configured-clis/registration.d.ts +0 -40
  154. package/dist/core/configured-clis/registration.js +0 -201
  155. package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
@@ -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
  });
@@ -1568,15 +1568,22 @@ test('node and package mutations share compact outcome-first receipts', () => {
1568
1568
  assert.match(collapsed, /ctrl\+o to expand/, command);
1569
1569
  }
1570
1570
  });
1571
- const PKG_PLUGIN_INSTALL_OUTPUT = ['- name: ai', '- scope: user', '', '/Users/silas/.crouter/plugins/ai'].join('\n');
1572
- const PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT = ['- name: ai', '- scope: user', '- path: /a'].join('\n');
1571
+ const PKG_PLUGIN_INSTALL_OUTPUT = [
1572
+ '- name: ai',
1573
+ '- scope: user',
1574
+ '- transport: exec',
1575
+ '- commands: {"mounts":["app"],"issues":[]}',
1576
+ '',
1577
+ '/Users/silas/.crouter/plugins/ai',
1578
+ ].join('\n');
1579
+ const PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT = ['- name: ai', '- scope: user', '- transport: http', '- path: /a'].join('\n');
1573
1580
  test('a crtr pkg plugin install card collapses to an installed status + scope/path digest, not the raw fields', () => {
1574
1581
  const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin install ai' });
1575
1582
  const collapsed = def
1576
1583
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_OUTPUT }] }, { expanded: false }, theme, {})
1577
1584
  .render(140)
1578
1585
  .join('\n');
1579
- assert.match(collapsed, /<success>installed ai<\/success>/);
1586
+ assert.match(collapsed, /<success>installed ai · exec<\/success>/);
1580
1587
  assert.match(collapsed, /<muted>user · \/Users\/silas\/\.crouter\/plugins\/ai<\/muted>/);
1581
1588
  assert.doesNotMatch(collapsed, /- name:/);
1582
1589
  const expanded = def
@@ -1588,33 +1595,34 @@ test('a crtr pkg plugin install card collapses to an installed status + scope/pa
1588
1595
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT }] }, { expanded: false }, theme, {})
1589
1596
  .render(140)
1590
1597
  .join('\n');
1598
+ assert.match(shortPath, /<success>installed ai · http<\/success>/);
1591
1599
  assert.match(shortPath, /<muted>user · \/a<\/muted>/);
1592
1600
  });
1593
- const PKG_PLUGIN_UPDATE_UPDATED_OUTPUT = ['1 updated:', '', '| name | updated | sha |', '| --- | --- | --- |', '| ai | true | def5678 |'].join('\n');
1594
- const PKG_PLUGIN_UPDATE_CURRENT_OUTPUT = ['1 updated:', '', '| name | updated | sha |', '| --- | --- | --- |', '| ai | false | abc1234 |'].join('\n');
1601
+ const PKG_PLUGIN_UPDATE_UPDATED_OUTPUT = ['1 updated:', '', '| name | transport | updated | sha |', '| --- | --- | --- | --- |', '| ai | http | true | |'].join('\n');
1602
+ const PKG_PLUGIN_UPDATE_CURRENT_OUTPUT = ['1 updated:', '', '| name | transport | updated | sha |', '| --- | --- | --- | --- |', '| ai | exec | false | abc1234 |'].join('\n');
1595
1603
  const PKG_PLUGIN_UPDATE_MULTI_OUTPUT = [
1596
1604
  '3 updated:',
1597
1605
  '',
1598
- '| name | updated | sha |',
1599
- '| --- | --- | --- |',
1600
- '| ai | true | aaa1111 |',
1601
- '| beta | false | bbb2222 |',
1602
- '| gamma | true | ccc3333 |',
1606
+ '| name | transport | updated | sha |',
1607
+ '| --- | --- | --- | --- |',
1608
+ '| ai | exec | true | aaa1111 |',
1609
+ '| beta | exec | false | bbb2222 |',
1610
+ '| gamma | http | true | |',
1603
1611
  ].join('\n');
1604
- test('a crtr pkg plugin update card collapses to an updated/current status + sha, not the raw table', () => {
1612
+ test('a crtr pkg plugin update card collapses to an updated/current transport-aware status + sha, not the raw table', () => {
1605
1613
  const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin update --name ai' });
1606
1614
  const collapsed = def
1607
1615
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: false }, theme, {})
1608
1616
  .render(140)
1609
1617
  .join('\n');
1610
- assert.match(collapsed, /<success>updated ai<\/success>/);
1611
- assert.match(collapsed, /<muted>def5678<\/muted>/);
1612
- assert.doesNotMatch(collapsed, /\| name \| updated \|/);
1618
+ assert.match(collapsed, /<success>refreshed ai<\/success>/);
1619
+ assert.match(collapsed, /<muted>http<\/muted>/);
1620
+ assert.doesNotMatch(collapsed, /\| name \| transport \| updated \|/);
1613
1621
  const expanded = def
1614
1622
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: true }, theme, {})
1615
1623
  .render(140)
1616
1624
  .join('\n');
1617
- assert.match(expanded, /\| name \| updated \| sha \|/);
1625
+ assert.match(expanded, /\| name \| transport \| updated \| sha \|/);
1618
1626
  const current = def
1619
1627
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_CURRENT_OUTPUT }] }, { expanded: false }, theme, {})
1620
1628
  .render(140)
@@ -1626,7 +1634,7 @@ test('a crtr pkg plugin update card collapses to an updated/current status + sha
1626
1634
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_MULTI_OUTPUT }] }, { expanded: false }, theme, {})
1627
1635
  .render(140)
1628
1636
  .join('\n');
1629
- assert.match(multi, /2 updated · 1 already current/);
1637
+ assert.match(multi, /2 updated · 1 already current · 1 HTTP/);
1630
1638
  const empty = multiDef
1631
1639
  .renderResult({ content: [{ type: 'text', text: '0 updated.' }] }, { expanded: false }, theme, {})
1632
1640
  .render(140)
@@ -1638,11 +1646,11 @@ test('a crtr pkg plugin update card collapses to an updated/current status + sha
1638
1646
  const malformedUpdate = [
1639
1647
  '3 updated:',
1640
1648
  '',
1641
- '| name | updated | sha |',
1642
- '| --- | --- | --- |',
1643
- '| ai | true | aaa1111 |',
1644
- '| beta | maybe | bbb2222 |',
1645
- '| gamma | false | ccc3333 |',
1649
+ '| name | transport | updated | sha |',
1650
+ '| --- | --- | --- | --- |',
1651
+ '| ai | exec | true | aaa1111 |',
1652
+ '| beta | exec | maybe | bbb2222 |',
1653
+ '| gamma | http | false | |',
1646
1654
  ].join('\n');
1647
1655
  const malformed = multiDef
1648
1656
  .renderResult({ content: [{ type: 'text', text: malformedUpdate }] }, { expanded: false }, theme, {})
@@ -1655,11 +1663,11 @@ const PKG_PLUGIN_LIST_OUTPUT = [
1655
1663
  '',
1656
1664
  '3 items:',
1657
1665
  '',
1658
- '| name | scope | version | enabled | source_marketplace | path |',
1659
- '| --- | --- | --- | --- | --- | --- |',
1660
- '| ai | user | 0.7.1 | true | crouter-official-marketplace | /Users/silas/.crouter/plugins/ai |',
1661
- '| beta | user | 1.0.0 | false | crouter-official-marketplace | /Users/silas/.crouter/plugins/beta |',
1662
- '| gamma | project | 2.3.0 | true | custom-market | /Users/silas/Code/cli/crouter/.crouter/plugins/gamma |',
1666
+ '| name | scope | version | enabled | transport | source_marketplace | path |',
1667
+ '| --- | --- | --- | --- | --- | --- | --- |',
1668
+ '| ai | user | 0.7.1 | true | exec | crouter-official-marketplace | /Users/silas/.crouter/plugins/ai |',
1669
+ '| beta | user | 1.0.0 | false | | crouter-official-marketplace | /Users/silas/.crouter/plugins/beta |',
1670
+ '| gamma | project | 2.3.0 | true | http | custom-market | /Users/silas/Code/cli/crouter/.crouter/plugins/gamma |',
1663
1671
  ].join('\n');
1664
1672
  test('a crtr pkg plugin list card collapses to a plugin/enabled count + name digest, not the raw table', () => {
1665
1673
  const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin list' });
@@ -1668,13 +1676,13 @@ test('a crtr pkg plugin list card collapses to a plugin/enabled count + name dig
1668
1676
  .render(140)
1669
1677
  .join('\n');
1670
1678
  assert.match(collapsed, /3 plugins · 2 enabled/);
1671
- assert.match(collapsed, /<muted>ai, beta, gamma<\/muted>/);
1679
+ assert.match(collapsed, /<muted>ai \(exec\), beta, gamma \(http\)<\/muted>/);
1672
1680
  assert.doesNotMatch(collapsed, /\| name \| scope \|/);
1673
1681
  const expanded = def
1674
1682
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1675
1683
  .render(140)
1676
1684
  .join('\n');
1677
- assert.match(expanded, /\| name \| scope \| version \|/);
1685
+ assert.match(expanded, /\| name \| scope \| version \| enabled \| transport \|/);
1678
1686
  const empty = def
1679
1687
  .renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1680
1688
  .render(140)
@@ -1693,7 +1701,8 @@ const PKG_PLUGIN_SHOW_OUTPUT = [
1693
1701
  '- scope: user',
1694
1702
  '- path: /Users/silas/.crouter/plugins/ai',
1695
1703
  '- enabled: true',
1696
- '- manifest: {"name":"ai","version":"0.7.1","description":"AI helpers"}',
1704
+ '- manifest: {"name":"ai","version":"0.7.1","description":"AI helpers","commands":"commands.json","transport":{"kind":"exec","executable":"bin/ai.js"}}',
1705
+ '- commands: {"manifestPath":"/Users/silas/.crouter/plugins/ai/commands.json","mounts":["ai"],"issues":[]}',
1697
1706
  '',
1698
1707
  '2 docs:',
1699
1708
  '',
@@ -1717,7 +1726,7 @@ test('a crtr pkg plugin show card collapses to a name@version + enabled status +
1717
1726
  .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_OUTPUT }] }, { expanded: false }, theme, {})
1718
1727
  .render(140)
1719
1728
  .join('\n');
1720
- assert.match(collapsed, /<success>ai@0\.7\.1 · enabled<\/success>/);
1729
+ assert.match(collapsed, /<success>ai@0\.7\.1 · exec · enabled<\/success>/);
1721
1730
  assert.match(collapsed, /<muted>2 substrate docs<\/muted>/);
1722
1731
  assert.doesNotMatch(collapsed, /\| name \| path \|/);
1723
1732
  const expanded = def
@@ -25,12 +25,12 @@ export interface ChatViewOptions {
25
25
  * the module defaults (faint + ANSI red) are used. */
26
26
  palette?: AttachPalette;
27
27
  /** Trailing refresh-yield cycles rendered at full fidelity; older cycles are
28
- * condensed to prose and frozen (see frozen-history.ts). Absent → the user's
29
- * `live_cycles` config setting, itself defaulting to 2. */
28
+ * condensed to lightweight, reflowing content (see condensed-history.ts).
29
+ * Absent → the user's `live_cycles` config setting, itself defaulting to 2. */
30
30
  liveCycles?: number;
31
- /** Which messages of a condensed cycle survive into the frozen block. Absent →
32
- * the user's `condensed_history` config setting, itself defaulting to `none`
33
- * (cycle dividers only). */
31
+ /** Which messages of a condensed cycle survive into the condensed region.
32
+ * Absent → the user's `condensed_history` config setting, itself defaulting
33
+ * to `none` (cycle dividers only). */
34
34
  condensedHistory?: CondensedHistoryMode;
35
35
  /** Pre-colored banner lines pinned as the FIRST child of the chat container,
36
36
  * above history (the crouton wordmark). Sits at the very top of the transcript
@@ -47,8 +47,8 @@ export declare class ChatView {
47
47
  /** Activity/status area below history. Holds working/compaction/retry loaders. */
48
48
  private readonly statusContainer;
49
49
  /** Where `append()` puts a component. Normally `historyContainer`; retargeted to
50
- * a scratch container while a frozen block is being built, so the very same
51
- * message-rendering path serves both. */
50
+ * a nested container while condensed history is being built, so the same
51
+ * message-rendering path serves both regions. */
52
52
  private appendTarget;
53
53
  /** Trailing cycles kept at full fidelity (config `live_cycles`). */
54
54
  private readonly liveCycles;
@@ -114,17 +114,17 @@ export declare class ChatView {
114
114
  * reflow-on-resize is automatic — no scrollback reconstruction needed. */
115
115
  applySnapshot(snapshot: BrokerSnapshot): Promise<void>;
116
116
  /**
117
- * Render the condensed form of the already-finished cycles ONCE and hand back a
118
- * frozen line block (or undefined when nothing survived condensation).
117
+ * Build the condensed form of the already-finished cycles as a nested component
118
+ * container, or return undefined when nothing survives condensation.
119
119
  *
120
- * Reuses `addMessageToChat` verbatim by retargeting `append()` at a scratch
121
- * container, so condensed history is styled exactly like live history — the same
122
- * renderer, with the tool traffic filtered out and the result flattened to text.
123
- * Yields on the same cadence as the live build so a very long history still lets
124
- * queued keystrokes through, and abandons itself when a newer welcome supersedes
125
- * this generation.
120
+ * Reuses `addMessageToChat` by retargeting `append()` at the nested container, so
121
+ * condensed history uses the same renderer as live history with tool traffic
122
+ * filtered out. The outer history cache keeps stable-width frames cheap, while
123
+ * preserving the components lets pane-width changes reflow their content. The
124
+ * build yields on the live cadence and abandons itself when a newer welcome
125
+ * supersedes this generation.
126
126
  */
127
- private buildFrozenBlock;
127
+ private buildCondensedBlock;
128
128
  /** Defer a live broker frame when a snapshot build is in flight. Returns true
129
129
  * (and enqueues the replay closure) while building so the caller returns
130
130
  * without mutating the half-rebuilt view; false when the caller may proceed.
@@ -141,8 +141,8 @@ export declare class ChatView {
141
141
  dispose(): void;
142
142
  /** Ctrl+O / app.tools.expand — flip global tool-output expansion and apply it to
143
143
  * every rendered component that supports it. Returns the new expanded state so
144
- * the caller can surface a notice. Condensed cycles hold no tool components and
145
- * are frozen as text, so they are unaffected by design. */
144
+ * the caller can surface a notice. Condensed cycles hold no tool components, so
145
+ * they are unaffected by design. */
146
146
  toggleToolsExpanded(): boolean;
147
147
  /** The `fold finished tool calls` view setting (`/fold-tools`, Alt+C → w → f).
148
148
  * Returns the new state so the caller can surface a notice. */
@@ -179,14 +179,14 @@ export declare class ChatView {
179
179
  /** Tool component with native viewer renderers for crouter-owned tool output. */
180
180
  private makeToolComponent;
181
181
  /** Append conversation content above the independent live status area — or into
182
- * the scratch container while a frozen block is under construction. */
182
+ * the nested container while condensed history is under construction. */
183
183
  private append;
184
184
  /** Approximate render width for inline mermaid diagrams. Only affects
185
185
  * termrender's trailing padding (stripped in the transform), so the terminal
186
186
  * column count is close enough without threading the live pane width here. */
187
187
  private renderWidth;
188
188
  /** Number of rendered conversation components in the container currently being
189
- * appended to (history, or the frozen block's scratch container). */
189
+ * appended to (live history or the nested condensed region). */
190
190
  private chatChildCount;
191
191
  /** Clear all chat content. */
192
192
  private resetChat;