@north-light/crouter 0.3.164 → 0.3.166
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 +4 -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/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 +62 -22
- package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
- package/dist/clients/attach/render/chat-view.d.ts +20 -20
- package/dist/clients/attach/render/chat-view.js +40 -54
- 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.d.ts +1 -1
- package/dist/clients/attach/render/context-message.js +18 -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 +528 -529
- package/dist/commands/node-context.js +3 -2
- 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 +75 -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 +28 -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 +32 -75
- package/dist/core/command-plugins/discovery.d.ts +25 -58
- package/dist/core/command-plugins/discovery.js +171 -261
- 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/broker.js +3 -2
- 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/lifecycle.js +2 -2
- 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/prompts/review.js +4 -2
- 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-BgLGlZ3D.css +2 -0
- package/dist/web-client/assets/{index-NIuSCOHM.js → index-CmoNqcCv.js} +19 -19
- 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 +1 -1
- package/runtime.lock.json +2 -2
- 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
|
@@ -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,35 +1,55 @@
|
|
|
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';
|
|
10
|
+
import { UserMessageComponent } from '@earendil-works/pi-coding-agent';
|
|
11
|
+
import { Container } from '@earendil-works/pi-tui';
|
|
12
|
+
import { applyTheme, attachMarkdownTheme } from '../config.js';
|
|
13
|
+
import { ChatView } from '../render/chat-view.js';
|
|
20
14
|
import { ContextMessageComponent } from '../render/context-message.js';
|
|
15
|
+
import { styleAttachMarkdownHeadings } from '../render/markdown-headings.js';
|
|
16
|
+
import { CONTEXT_NUDGE_CUSTOM_TYPE, REVIVE_KICKOFF_SENTINEL, generatedContextPresentation, } from '../../../shared/generated-context.js';
|
|
21
17
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
22
18
|
const CHAT_VIEW = readFileSync(join(HERE, '..', 'render/chat-view.ts'), 'utf8');
|
|
23
19
|
const identity = (s) => s;
|
|
20
|
+
applyTheme();
|
|
24
21
|
test('collapsed by default: body text is not rendered until expanded', () => {
|
|
25
22
|
const body = '<situational-context>\nthe user is mid-checkout\n</situational-context>';
|
|
26
23
|
const collapsed = new ContextMessageComponent(body, false, identity, identity, 'situational context', ' hint');
|
|
27
|
-
const
|
|
24
|
+
const collapsedRender = collapsed.render(80);
|
|
25
|
+
const collapsedLines = collapsedRender.join('\n');
|
|
26
|
+
assert.equal(collapsedRender.length, 1, 'a collapsed row has no surrounding blank line');
|
|
28
27
|
assert.match(collapsedLines, /\[situational context\]/);
|
|
29
28
|
assert.doesNotMatch(collapsedLines, /mid-checkout/, 'collapsed state must never leak the body');
|
|
30
29
|
collapsed.setExpanded(true);
|
|
31
|
-
const
|
|
30
|
+
const expandedRender = collapsed.render(80);
|
|
31
|
+
const expandedLines = expandedRender.join('\n');
|
|
32
32
|
assert.match(expandedLines, /mid-checkout/, 'expanded state (Ctrl+O) still shows the full body — the LLM-visible content is never lost, only the default view folds it');
|
|
33
|
+
const normalUserRender = new UserMessageComponent(styleAttachMarkdownHeadings(body), attachMarkdownTheme()).render(80);
|
|
34
|
+
assert.deepEqual(expandedRender.slice(0, -1), normalUserRender, 'expanded generated context uses the ordinary user-message surface');
|
|
35
|
+
assert.equal(expandedRender.at(-1), '', 'an expanded block leaves one blank line below its body');
|
|
36
|
+
});
|
|
37
|
+
test('generated rows add no leading spacer after prior transcript content', async () => {
|
|
38
|
+
const container = new Container();
|
|
39
|
+
const tui = { requestRender() { }, terminal: { rows: 40 } };
|
|
40
|
+
const view = new ChatView(tui, container, { toolOutputExpanded: false });
|
|
41
|
+
await view.applySnapshot({
|
|
42
|
+
messages: [
|
|
43
|
+
{ role: 'assistant', content: [{ type: 'text', text: 'before' }], stopReason: 'stop' },
|
|
44
|
+
{ role: 'user', content: `${REVIVE_KICKOFF_SENTINEL} — full disk bearings` },
|
|
45
|
+
],
|
|
46
|
+
stats: {},
|
|
47
|
+
state: { isStreaming: false },
|
|
48
|
+
});
|
|
49
|
+
const lines = container.render(80).map((line) => line.replace(/\x1b\[[0-9;]*m/g, ''));
|
|
50
|
+
const generatedIndex = lines.findIndex((line) => line.includes('[crtr revive]'));
|
|
51
|
+
assert.notEqual(generatedIndex, -1);
|
|
52
|
+
assert.match(lines[generatedIndex - 1] ?? '', /before/, 'the collapsed row follows prior content directly');
|
|
33
53
|
});
|
|
34
54
|
test('the crtr-context label/hint keep their defaults when callers omit the new params', () => {
|
|
35
55
|
const bearings = new ContextMessageComponent('some bearings body', false, identity, identity);
|
|
@@ -37,10 +57,30 @@ test('the crtr-context label/hint keep their defaults when callers omit the new
|
|
|
37
57
|
assert.match(line, /\[crtr context\]/);
|
|
38
58
|
assert.match(line, /orienting bearings/);
|
|
39
59
|
});
|
|
40
|
-
test('
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
assert.
|
|
60
|
+
test('fresh-revive kickoff and both context-nudge roles classify as generated context', () => {
|
|
61
|
+
const revive = generatedContextPresentation({
|
|
62
|
+
role: 'user',
|
|
63
|
+
content: `${REVIVE_KICKOFF_SENTINEL} — full disk bearings`,
|
|
64
|
+
});
|
|
65
|
+
assert.equal(revive?.label, 'crtr revive');
|
|
66
|
+
assert.equal(revive?.summary, 'fresh context kickoff');
|
|
67
|
+
const body = '[crtr] Context ~150k. Yield when substantial work remains.';
|
|
68
|
+
const steered = generatedContextPresentation({ role: 'user', content: body });
|
|
69
|
+
const held = generatedContextPresentation({
|
|
70
|
+
role: 'custom',
|
|
71
|
+
customType: CONTEXT_NUDGE_CUSTOM_TYPE,
|
|
72
|
+
content: body,
|
|
73
|
+
});
|
|
74
|
+
assert.equal(steered?.summary, 'Context ~150k');
|
|
75
|
+
assert.equal(held?.summary, 'Context ~150k');
|
|
76
|
+
const inbox = generatedContextPresentation({
|
|
77
|
+
role: 'user',
|
|
78
|
+
content: 'From child-node-123 — 2 updates:\n [update] still working',
|
|
79
|
+
});
|
|
80
|
+
assert.equal(inbox?.label, 'crtr inbox');
|
|
81
|
+
assert.equal(inbox?.summary, '2 updates from child-node-123');
|
|
82
|
+
});
|
|
83
|
+
test('render/chat-view.ts routes the shared generated-context classification through the foldable component', () => {
|
|
84
|
+
assert.match(CHAT_VIEW, /generatedContextPresentation\(message\)/);
|
|
85
|
+
assert.match(CHAT_VIEW, /new ContextMessageComponent\(/);
|
|
46
86
|
});
|
|
@@ -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 = [
|
|
1572
|
-
|
|
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 |
|
|
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 |
|
|
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>
|
|
1611
|
-
assert.match(collapsed, /<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 |
|
|
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
|
|
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
|
|
32
|
-
* the user's `condensed_history` config setting, itself defaulting
|
|
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
|
|
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
|
-
*
|
|
118
|
-
*
|
|
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`
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
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
|
|
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
|
|
145
|
-
*
|
|
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
|
|
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
|
|
189
|
+
* appended to (live history or the nested condensed region). */
|
|
190
190
|
private chatChildCount;
|
|
191
191
|
/** Clear all chat content. */
|
|
192
192
|
private resetChat;
|