@crouton-kit/crouter 0.3.18 → 0.3.19
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.js +6 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
- package/dist/builtin-memory/crouter-development/personas.md +117 -0
- package/dist/builtin-memory/crouter-development/plugins.md +163 -0
- package/dist/builtin-memory/design.md +60 -0
- package/dist/builtin-memory/development.md +116 -0
- package/dist/builtin-memory/planning.md +66 -0
- package/dist/builtin-memory/spec.md +99 -0
- package/dist/builtin-personas/design/orchestrator.md +1 -1
- package/dist/builtin-personas/developer/orchestrator.md +1 -1
- package/dist/builtin-personas/lifecycle/resident.md +1 -1
- package/dist/builtin-personas/lifecycle/terminal.md +5 -2
- package/dist/builtin-personas/orchestration-kernel.md +15 -24
- package/dist/builtin-personas/plan/orchestrator.md +1 -1
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/orchestrator.md +1 -1
- package/dist/builtin-personas/waiting.md +8 -0
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
- package/dist/builtin-views/_lib/states.mjs +161 -0
- package/dist/builtin-views/canvas/client.mjs +303 -0
- package/dist/builtin-views/canvas/view.mjs +576 -0
- package/dist/builtin-views/git-pr/client.mjs +440 -0
- package/dist/builtin-views/git-pr/view.mjs +675 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
- package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
- package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
- package/dist/builtin-views/inbox/view.mjs +889 -0
- package/dist/builtin-views/linkedin/client.mjs +610 -0
- package/dist/builtin-views/linkedin/view.mjs +1171 -0
- package/dist/clients/attach/attach-cmd.d.ts +2 -0
- package/dist/clients/attach/attach-cmd.js +354 -0
- package/dist/clients/attach/chat-view.d.ts +77 -0
- package/dist/clients/attach/chat-view.js +450 -0
- package/dist/clients/attach/clipboard-image.d.ts +16 -0
- package/dist/clients/attach/clipboard-image.js +113 -0
- package/dist/clients/attach/config-load.d.ts +31 -0
- package/dist/clients/attach/config-load.js +113 -0
- package/dist/clients/attach/extension-dialogs.d.ts +29 -0
- package/dist/clients/attach/extension-dialogs.js +101 -0
- package/dist/clients/attach/input-controller.d.ts +54 -0
- package/dist/clients/attach/input-controller.js +204 -0
- package/dist/clients/attach/slash-commands.d.ts +36 -0
- package/dist/clients/attach/slash-commands.js +200 -0
- package/dist/clients/attach/view-socket.d.ts +48 -0
- package/dist/clients/attach/view-socket.js +126 -0
- package/dist/commands/attention.js +3 -3
- package/dist/commands/canvas-prune.js +1 -1
- package/dist/commands/daemon.js +3 -2
- package/dist/commands/human/prompts.js +1 -1
- package/dist/commands/human/queue.js +43 -8
- package/dist/commands/human/shared.d.ts +2 -0
- package/dist/commands/memory/find.d.ts +1 -0
- package/dist/commands/memory/find.js +180 -0
- package/dist/commands/memory/lint.d.ts +1 -0
- package/dist/commands/memory/lint.js +140 -0
- package/dist/commands/memory/list.d.ts +1 -0
- package/dist/commands/memory/list.js +79 -0
- package/dist/commands/memory/read.js +103 -0
- package/dist/commands/memory/shared.d.ts +30 -0
- package/dist/commands/memory/shared.js +122 -0
- package/dist/commands/memory/write.d.ts +1 -0
- package/dist/commands/memory/write.js +85 -0
- package/dist/commands/memory.d.ts +2 -0
- package/dist/commands/memory.js +27 -0
- package/dist/commands/node.d.ts +3 -2
- package/dist/commands/node.js +607 -61
- package/dist/commands/pkg/market-manage.js +1 -1
- package/dist/commands/push.js +6 -6
- package/dist/commands/revive.js +1 -1
- package/dist/commands/skill/author.js +1 -1
- package/dist/commands/skill/shared.d.ts +1 -8
- package/dist/commands/skill/shared.js +2 -55
- package/dist/commands/skill.js +9 -14
- package/dist/commands/sys/doctor.js +1 -1
- package/dist/commands/sys/update.js +1 -1
- package/dist/commands/view-cycle.d.ts +2 -0
- package/dist/commands/view-cycle.js +125 -0
- package/dist/commands/view-list.d.ts +2 -0
- package/dist/commands/view-list.js +66 -0
- package/dist/commands/view-new.d.ts +2 -0
- package/dist/commands/view-new.js +70 -0
- package/dist/commands/view-pick.d.ts +2 -0
- package/dist/commands/view-pick.js +119 -0
- package/dist/commands/view-run.d.ts +2 -0
- package/dist/commands/view-run.js +191 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +29 -0
- package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +677 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
- package/dist/core/__tests__/cascade-close.test.js +12 -2
- package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
- package/dist/core/__tests__/child-death-wake.test.js +245 -0
- package/dist/core/__tests__/context-intro.test.js +76 -62
- package/dist/core/__tests__/daemon-boot.test.js +14 -5
- package/dist/core/__tests__/daemon-liveness.test.js +34 -9
- package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
- package/dist/core/__tests__/detach-focus.test.js +206 -0
- package/dist/core/__tests__/draw-style.test.d.ts +1 -0
- package/dist/core/__tests__/draw-style.test.js +258 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
- package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
- package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
- package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
- package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
- package/dist/core/__tests__/helpers/harness.d.ts +9 -0
- package/dist/core/__tests__/helpers/harness.js +111 -1
- package/dist/core/__tests__/home-session.test.js +41 -8
- package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
- package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
- package/dist/core/__tests__/kickoff.test.js +37 -3
- package/dist/core/__tests__/live-mutation.test.js +50 -33
- package/dist/core/__tests__/memory.test.js +23 -115
- package/dist/core/__tests__/placement-focus.test.js +5 -0
- package/dist/core/__tests__/placement-teardown.test.js +54 -11
- package/dist/core/__tests__/relaunch.test.js +4 -3
- package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
- package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
- package/dist/core/__tests__/spawn-root.test.js +10 -0
- package/dist/core/__tests__/spike-harness.test.js +1 -0
- package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
- package/dist/core/__tests__/wake-bearings.test.js +156 -0
- package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
- package/dist/core/__tests__/wake-origin.test.js +110 -0
- package/dist/core/bootstrap.js +1 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +24 -2
- package/dist/core/canvas/browse/model.d.ts +38 -2
- package/dist/core/canvas/browse/model.js +134 -10
- package/dist/core/canvas/browse/render.d.ts +6 -12
- package/dist/core/canvas/browse/render.js +72 -104
- package/dist/core/canvas/canvas.js +15 -22
- package/dist/core/canvas/db.js +46 -0
- package/dist/core/canvas/index.d.ts +1 -0
- package/dist/core/canvas/index.js +1 -0
- package/dist/core/canvas/pid.d.ts +4 -0
- package/dist/core/canvas/pid.js +23 -0
- package/dist/core/canvas/render.d.ts +11 -2
- package/dist/core/canvas/render.js +69 -0
- package/dist/core/canvas/types.d.ts +85 -3
- package/dist/core/canvas/wakeups.d.ts +76 -0
- package/dist/core/canvas/wakeups.js +185 -0
- package/dist/core/config.js +4 -1
- package/dist/core/frontmatter.js +37 -124
- package/dist/core/memory-resolver.d.ts +49 -0
- package/dist/core/memory-resolver.js +141 -0
- package/dist/core/personas/loader.d.ts +7 -0
- package/dist/core/personas/loader.js +36 -5
- package/dist/core/personas/resolve.js +13 -3
- package/dist/core/predicate.d.ts +63 -0
- package/dist/core/predicate.js +189 -0
- package/dist/core/resolver.js +26 -5
- package/dist/core/runtime/bearings.d.ts +53 -12
- package/dist/core/runtime/bearings.js +132 -59
- package/dist/core/runtime/broker-cli.d.ts +1 -0
- package/dist/core/runtime/broker-cli.js +46 -0
- package/dist/core/runtime/broker-protocol.d.ts +332 -0
- package/dist/core/runtime/broker-protocol.js +153 -0
- package/dist/core/runtime/broker-sdk.d.ts +48 -0
- package/dist/core/runtime/broker-sdk.js +72 -0
- package/dist/core/runtime/broker.d.ts +55 -0
- package/dist/core/runtime/broker.js +1128 -0
- package/dist/core/runtime/close.js +35 -6
- package/dist/core/runtime/host.d.ts +53 -0
- package/dist/core/runtime/host.js +186 -0
- package/dist/core/runtime/kickoff.d.ts +2 -1
- package/dist/core/runtime/kickoff.js +91 -5
- package/dist/core/runtime/launch.d.ts +45 -2
- package/dist/core/runtime/launch.js +65 -2
- package/dist/core/runtime/lifecycle.js +23 -6
- package/dist/core/runtime/memory.d.ts +2 -42
- package/dist/core/runtime/memory.js +11 -162
- package/dist/core/runtime/nodes.d.ts +33 -0
- package/dist/core/runtime/nodes.js +59 -1
- package/dist/core/runtime/persona.js +21 -11
- package/dist/core/runtime/pi-vendored.d.ts +18 -0
- package/dist/core/runtime/pi-vendored.js +49 -0
- package/dist/core/runtime/placement.d.ts +32 -14
- package/dist/core/runtime/placement.js +206 -52
- package/dist/core/runtime/promote.d.ts +0 -6
- package/dist/core/runtime/promote.js +1 -12
- package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
- package/dist/core/runtime/{demote.js → recycle.js} +27 -11
- package/dist/core/runtime/reset.js +8 -6
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +19 -0
- package/dist/core/runtime/spawn.js +75 -22
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +6 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +28 -0
- package/dist/core/runtime/tmux.js +80 -6
- package/dist/core/scope.d.ts +11 -0
- package/dist/core/scope.js +39 -0
- package/dist/core/spawn.d.ts +5 -0
- package/dist/core/spawn.js +20 -2
- package/dist/core/substrate/gate.d.ts +13 -0
- package/dist/core/substrate/gate.js +21 -0
- package/dist/core/substrate/index.d.ts +7 -0
- package/dist/core/substrate/index.js +18 -0
- package/dist/core/substrate/on-read.d.ts +14 -0
- package/dist/core/substrate/on-read.js +292 -0
- package/dist/core/substrate/render.d.ts +25 -0
- package/dist/core/substrate/render.js +256 -0
- package/dist/core/substrate/schema.d.ts +76 -0
- package/dist/core/substrate/schema.js +124 -0
- package/dist/core/substrate/session-cache.d.ts +30 -0
- package/dist/core/substrate/session-cache.js +77 -0
- package/dist/core/substrate/subject.d.ts +41 -0
- package/dist/core/substrate/subject.js +54 -0
- package/dist/core/tui/contract.d.ts +83 -0
- package/dist/core/tui/contract.js +8 -0
- package/dist/core/tui/draw.d.ts +96 -0
- package/dist/core/tui/draw.js +339 -0
- package/dist/core/tui/host.d.ts +29 -0
- package/dist/core/tui/host.js +379 -0
- package/dist/core/tui/loader.d.ts +16 -0
- package/dist/core/tui/loader.js +94 -0
- package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
- package/dist/core/wake.d.ts +86 -0
- package/dist/core/wake.js +308 -0
- package/dist/daemon/crtrd.d.ts +29 -4
- package/dist/daemon/crtrd.js +662 -46
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
- package/dist/pi-extensions/canvas-commands.d.ts +3 -0
- package/dist/pi-extensions/canvas-commands.js +10 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
- package/dist/pi-extensions/canvas-context-intro.js +55 -15
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
- package/dist/pi-extensions/canvas-nav.d.ts +3 -0
- package/dist/pi-extensions/canvas-nav.js +87 -23
- package/dist/pi-extensions/canvas-stophook.js +17 -8
- package/dist/pi-extensions/canvas-view.d.ts +21 -0
- package/dist/pi-extensions/canvas-view.js +75 -0
- package/dist/prompts/skill.js +19 -26
- package/dist/prompts/view.d.ts +7 -0
- package/dist/prompts/view.js +101 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +1 -0
- package/package.json +9 -4
- package/dist/commands/skill/find.d.ts +0 -4
- package/dist/commands/skill/find.js +0 -257
- package/dist/commands/skill/read.js +0 -91
- /package/dist/commands/{skill → memory}/read.d.ts +0 -0
- /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Standard view states — the four states every crtr view must *design* rather
|
|
4
|
+
* than leave bare (design §4): loading-skeleton, empty-reward, error-keeps-its-
|
|
5
|
+
* shape, and the guided not-ready takeover.
|
|
6
|
+
*
|
|
7
|
+
* Copy-pasteable BODY HELPERS: this is a sibling lib the builtin views import
|
|
8
|
+
* RELATIVELY (`import { loadingState } from '../_lib/states.mjs'`). It imports
|
|
9
|
+
* NOTHING from crtr — the view passes in the `draw` + content `rect` the host
|
|
10
|
+
* already handed it, and these paint the state into `draw`.
|
|
11
|
+
*
|
|
12
|
+
* Discipline (design §2): all color is NUMERIC SGR codes ('32' green, '31' red,
|
|
13
|
+
* '36' cyan, '33' yellow); color NEVER carries meaning alone — every colored
|
|
14
|
+
* element pairs its hue with a glyph or weight so it survives NO_COLOR / dumb
|
|
15
|
+
* terminals. Hue is the framework's job to gate (draw.ts gates fg/bg on caps);
|
|
16
|
+
* structural weight (bold/dim) always renders.
|
|
17
|
+
*
|
|
18
|
+
* @module _lib/states
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** @typedef {import('../../core/tui/draw.js').Draw} Draw */
|
|
22
|
+
/** @typedef {import('../../core/tui/draw.js').Rect} Rect */
|
|
23
|
+
/** @typedef {import('../../core/tui/draw.js').Span} Span */
|
|
24
|
+
/** @typedef {import('../../core/tui/draw.js').Style} Style */
|
|
25
|
+
|
|
26
|
+
// ── internal ──────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/** @param {Span[]} spans @returns {number} */
|
|
29
|
+
function spanWidth(spans) {
|
|
30
|
+
let n = 0;
|
|
31
|
+
for (const s of spans) n += Array.from(s.text).length;
|
|
32
|
+
return n;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @param {string|string[]|undefined|null} v @returns {string[]} */
|
|
36
|
+
function toLines(v) {
|
|
37
|
+
if (v == null) return [];
|
|
38
|
+
if (Array.isArray(v)) return v.map((s) => String(s == null ? '' : s));
|
|
39
|
+
return [String(v)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Place a vertically + horizontally centered stack of span-lines in `rect`.
|
|
44
|
+
* Each entry is one line's spans; rows that fall outside the rect are skipped.
|
|
45
|
+
* @param {Draw} draw @param {Rect} rect @param {Span[][]} lines
|
|
46
|
+
*/
|
|
47
|
+
function centeredStack(draw, rect, lines) {
|
|
48
|
+
if (!rect || rect.width <= 0 || rect.height <= 0 || lines.length === 0) return;
|
|
49
|
+
const start = rect.row + Math.max(0, Math.floor((rect.height - lines.length) / 2));
|
|
50
|
+
lines.forEach((spans, i) => {
|
|
51
|
+
const row = start + i;
|
|
52
|
+
if (row < rect.row || row >= rect.row + rect.height) return;
|
|
53
|
+
const w = spanWidth(spans);
|
|
54
|
+
const col = rect.col + Math.max(0, Math.floor((rect.width - w) / 2));
|
|
55
|
+
draw.spans(row, col, spans, rect.col + rect.width - col);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── states ─────────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Loading — paint a SKELETON of where content will land (dim `····` placeholder
|
|
63
|
+
* rows), not a lone centered "Loading…". The layout is legible before data
|
|
64
|
+
* arrives. Pair with the host's `⟳ working` chip + a `setStatus('Loading…')`.
|
|
65
|
+
*
|
|
66
|
+
* @param {Draw} draw
|
|
67
|
+
* @param {Rect} rect
|
|
68
|
+
* @param {{ rows?: number, label?: string }} [opts]
|
|
69
|
+
* rows — how many skeleton rows (default 4); label — optional dim caption below.
|
|
70
|
+
*/
|
|
71
|
+
export function loadingState(draw, rect, opts = {}) {
|
|
72
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return;
|
|
73
|
+
const n = Math.max(1, Math.min(opts.rows ?? 4, rect.height));
|
|
74
|
+
const snippetLens = [10, 8, 12, 7, 11];
|
|
75
|
+
for (let i = 0; i < n; i++) {
|
|
76
|
+
const row = rect.row + i;
|
|
77
|
+
const label = '·'.repeat(4);
|
|
78
|
+
const snipLen = Math.min(snippetLens[i % snippetLens.length], Math.max(0, rect.width - 8));
|
|
79
|
+
/** @type {Span[]} */
|
|
80
|
+
const spans = [{ text: label, style: { dim: true } }];
|
|
81
|
+
if (snipLen > 0) spans.push({ text: ' ' + '·'.repeat(snipLen), style: { dim: true } });
|
|
82
|
+
draw.spans(row, rect.col + 1, spans, rect.width - 1);
|
|
83
|
+
}
|
|
84
|
+
if (opts.label) {
|
|
85
|
+
const below = { row: rect.row + n + 1, col: rect.col, width: rect.width, height: Math.max(0, rect.height - n - 1) };
|
|
86
|
+
centeredStack(draw, below, [[{ text: opts.label, style: { dim: true } }]]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Empty — the reward state. A small centered stack: a green `✓`, a default-weight
|
|
92
|
+
* headline, then dim secondary lines. Inbox-zero is a reward, not a void.
|
|
93
|
+
*
|
|
94
|
+
* @param {Draw} draw
|
|
95
|
+
* @param {Rect} rect
|
|
96
|
+
* @param {{ glyph?: string, headline?: string, secondary?: string|string[] }} [opts]
|
|
97
|
+
*/
|
|
98
|
+
export function emptyState(draw, rect, opts = {}) {
|
|
99
|
+
const glyph = opts.glyph ?? '✓';
|
|
100
|
+
const headline = opts.headline ?? 'All caught up';
|
|
101
|
+
/** @type {Span[][]} */
|
|
102
|
+
const lines = [
|
|
103
|
+
[{ text: glyph + ' ', style: { fg: '32' } }, { text: headline }], // green glyph + default headline
|
|
104
|
+
[{ text: '' }],
|
|
105
|
+
];
|
|
106
|
+
for (const t of toLines(opts.secondary)) lines.push([{ text: t, style: { dim: true } }]);
|
|
107
|
+
centeredStack(draw, rect, lines);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Error — a centered error block, used ONLY when there is no last-known content
|
|
112
|
+
* to keep (on a refresh error with a still-good view, raise a banner instead).
|
|
113
|
+
* `✗` + headline in red/bold, the cause in default weight, a dim retry hint.
|
|
114
|
+
*
|
|
115
|
+
* @param {Draw} draw
|
|
116
|
+
* @param {Rect} rect
|
|
117
|
+
* @param {{ headline?: string, cause?: string|string[], hint?: string }} [opts]
|
|
118
|
+
*/
|
|
119
|
+
export function errorState(draw, rect, opts = {}) {
|
|
120
|
+
const headline = opts.headline ?? 'Something went wrong';
|
|
121
|
+
const cause = toLines(opts.cause);
|
|
122
|
+
const hint = opts.hint ?? 'Press g to retry.';
|
|
123
|
+
/** @type {Span[][]} */
|
|
124
|
+
const lines = [
|
|
125
|
+
[{ text: '✗ ', style: { fg: '31', bold: true } }, { text: headline, style: { fg: '31', bold: true } }],
|
|
126
|
+
[{ text: '' }],
|
|
127
|
+
];
|
|
128
|
+
for (const t of cause) lines.push([{ text: t }]);
|
|
129
|
+
if (cause.length) lines.push([{ text: '' }]);
|
|
130
|
+
if (hint) lines.push([{ text: hint, style: { dim: true } }]);
|
|
131
|
+
centeredStack(draw, rect, lines);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Not-ready / guided takeover — the full-content recovery substrate (design §5).
|
|
136
|
+
* A centered stack: a state glyph (its hue passed by the caller, mono-safe via
|
|
137
|
+
* the glyph itself), a bold headline naming the state, a dim explanation, and the
|
|
138
|
+
* explicit next step (an auto-progress note the view writes, or a key to press).
|
|
139
|
+
*
|
|
140
|
+
* @param {Draw} draw
|
|
141
|
+
* @param {Rect} rect
|
|
142
|
+
* @param {{ glyph?: string, glyphFg?: string, headline?: string, explanation?: string|string[], nextStep?: string }} [opts]
|
|
143
|
+
* glyphFg — NUMERIC SGR code for the glyph hue (e.g. '36' cyan working, '31' red blocked).
|
|
144
|
+
*/
|
|
145
|
+
export function notReadyState(draw, rect, opts = {}) {
|
|
146
|
+
const glyph = opts.glyph ?? '⊙';
|
|
147
|
+
/** @type {Style} */
|
|
148
|
+
const glyphStyle = opts.glyphFg ? { fg: opts.glyphFg, bold: true } : { bold: true };
|
|
149
|
+
const headline = opts.headline ?? 'Not ready';
|
|
150
|
+
/** @type {Span[][]} */
|
|
151
|
+
const lines = [
|
|
152
|
+
[{ text: glyph + ' ', style: glyphStyle }, { text: headline, style: { bold: true } }],
|
|
153
|
+
[{ text: '' }],
|
|
154
|
+
];
|
|
155
|
+
for (const t of toLines(opts.explanation)) lines.push([{ text: t, style: { dim: true } }]);
|
|
156
|
+
if (opts.nextStep) {
|
|
157
|
+
lines.push([{ text: '' }]);
|
|
158
|
+
lines.push([{ text: opts.nextStep }]); // default weight — the call to action
|
|
159
|
+
}
|
|
160
|
+
centeredStack(draw, rect, lines);
|
|
161
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Canvas data layer for the crtr `canvas` view (the monitor archetype).
|
|
4
|
+
*
|
|
5
|
+
* Self-contained ESM, Node-builtins-only. Imports NOTHING from crtr so it ships
|
|
6
|
+
* verbatim (`cp -R src/builtin-views dist/builtin-views`) and is dynamically
|
|
7
|
+
* `import()`ed by the view at runtime where there is no TS toolchain.
|
|
8
|
+
*
|
|
9
|
+
* It shells the `crtr` binary itself — exactly as the LinkedIn view shells
|
|
10
|
+
* `capture`. Two read-only `--json` leaves give the whole graph:
|
|
11
|
+
* - `crtr node inspect list --json` → every node row, incl. `parent` (so the
|
|
12
|
+
* view rebuilds the forest) + `lifecycle`.
|
|
13
|
+
* - `crtr canvas attention list --json` → which nodes have pending human asks
|
|
14
|
+
* (the "blocked on a human" signal).
|
|
15
|
+
* Both are read-only (query canvas.db) and exit 0. No `--json` affordance was
|
|
16
|
+
* missing — every command we need already emits machine-readable JSON.
|
|
17
|
+
*
|
|
18
|
+
* NOTHING here throws. Every exported function returns a `Result<T>`; failures
|
|
19
|
+
* surface as a typed `ClientError` so the view renders guidance, not a crash.
|
|
20
|
+
*
|
|
21
|
+
* NOTE on the crtr help-gate: that gate is a pi-side `bash` tool-call
|
|
22
|
+
* interceptor — it inspects the AGENT's command string. A `crtr` spawned from
|
|
23
|
+
* inside the view host (a grandchild of `node dist/cli.js view run`) is not a
|
|
24
|
+
* tool call pi can see, so these shell-outs are never gated.
|
|
25
|
+
*
|
|
26
|
+
* @module canvas/client
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { execFile } from 'node:child_process';
|
|
30
|
+
import { existsSync } from 'node:fs';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
|
+
import { dirname, join } from 'node:path';
|
|
33
|
+
|
|
34
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* One canvas node, flattened from `node inspect list --json`.
|
|
38
|
+
* @typedef {Object} CanvasNode
|
|
39
|
+
* @property {string} nodeId Full node id (`<time>-<hash>`).
|
|
40
|
+
* @property {string} name Display name.
|
|
41
|
+
* @property {string} kind Node kind (general/developer/explore/human/…).
|
|
42
|
+
* @property {string} mode base | orchestrator.
|
|
43
|
+
* @property {string} lifecycle resident | terminal.
|
|
44
|
+
* @property {string} status active | idle | done | dead | canceled.
|
|
45
|
+
* @property {string|null} parent Spawn/subscription parent id (null ⇒ a forest root).
|
|
46
|
+
* @property {string} created ISO 8601 birth timestamp (drives child ordering).
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One cwd with pending human asks, from `canvas attention list --json`. The
|
|
51
|
+
* `nodeId` is the cwd's representative node — the node the attention system
|
|
52
|
+
* points at.
|
|
53
|
+
* @typedef {Object} AttentionItem
|
|
54
|
+
* @property {string} nodeId
|
|
55
|
+
* @property {string} name
|
|
56
|
+
* @property {string} cwd
|
|
57
|
+
* @property {number} count Pending ask count.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Typed failure. `kind` drives the guidance banner the view shows.
|
|
62
|
+
* @typedef {{kind:'crtr-missing', message:string}
|
|
63
|
+
* | {kind:'crtr-failed', message:string}
|
|
64
|
+
* | {kind:'parse', message:string}
|
|
65
|
+
* | {kind:'error', message:string}} ClientError
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Never-throw return contract.
|
|
70
|
+
* @template T
|
|
71
|
+
* @typedef {{ok:true, data:T} | {ok:false, error:ClientError}} Result
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
// ── Result helpers ───────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/** @template T @param {T} data @returns {Result<T>} */
|
|
77
|
+
function ok(data) {
|
|
78
|
+
return { ok: true, data };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @param {ClientError} error @returns {{ok:false, error:ClientError}} */
|
|
82
|
+
function fail(error) {
|
|
83
|
+
return { ok: false, error };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Locating the `crtr` binary ───────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* One candidate way to invoke crtr: a binary plus any fixed prefix args.
|
|
90
|
+
* @typedef {Object} Candidate
|
|
91
|
+
* @property {string} bin
|
|
92
|
+
* @property {string[]} prefix
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Candidates, in order: explicit override → PATH `crtr` → PATH `crouter` → the
|
|
97
|
+
* dev fallback `node <dist/cli.js>` resolved relative to THIS file (which, when
|
|
98
|
+
* shipped, lives at `dist/builtin-views/canvas/client.mjs`, so `../../cli.js` is
|
|
99
|
+
* `dist/cli.js`). We try the next only when a candidate fails to spawn (ENOENT);
|
|
100
|
+
* a candidate that spawns and exits nonzero is authoritative.
|
|
101
|
+
* @returns {Candidate[]}
|
|
102
|
+
*/
|
|
103
|
+
function crtrCandidates() {
|
|
104
|
+
/** @type {Candidate[]} */
|
|
105
|
+
const out = [];
|
|
106
|
+
if (process.env.CRTR_BIN) out.push({ bin: process.env.CRTR_BIN, prefix: [] });
|
|
107
|
+
out.push({ bin: 'crtr', prefix: [] });
|
|
108
|
+
out.push({ bin: 'crouter', prefix: [] });
|
|
109
|
+
try {
|
|
110
|
+
const here = dirname(fileURLToPath(import.meta.url)); // …/dist/builtin-views/canvas
|
|
111
|
+
const cli = join(here, '..', '..', 'cli.js'); // …/dist/cli.js
|
|
112
|
+
if (existsSync(cli)) out.push({ bin: process.execPath, prefix: [cli] });
|
|
113
|
+
} catch {
|
|
114
|
+
/* import.meta.url unavailable — skip the dev fallback */
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Process runner (never throws) ────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @typedef {Object} RunResult
|
|
123
|
+
* @property {boolean} spawned False ⇒ no candidate binary could be spawned.
|
|
124
|
+
* @property {number} exitCode Process exit code (0 on success; -1 if not spawned).
|
|
125
|
+
* @property {string} stdout
|
|
126
|
+
* @property {string} stderr
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Run one candidate. Resolves (never rejects). ENOENT ⇒ `spawned:false` so the
|
|
131
|
+
* caller can try the next candidate.
|
|
132
|
+
* @param {Candidate} c
|
|
133
|
+
* @param {string[]} argv
|
|
134
|
+
* @returns {Promise<RunResult>}
|
|
135
|
+
*/
|
|
136
|
+
function runOnce(c, argv) {
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
execFile(
|
|
139
|
+
c.bin,
|
|
140
|
+
[...c.prefix, ...argv],
|
|
141
|
+
{ maxBuffer: 64 * 1024 * 1024, encoding: 'utf8' },
|
|
142
|
+
(err, stdout, stderr) => {
|
|
143
|
+
const out = typeof stdout === 'string' ? stdout : '';
|
|
144
|
+
const errOut = typeof stderr === 'string' ? stderr : '';
|
|
145
|
+
if (err && /** @type {any} */ (err).code === 'ENOENT') {
|
|
146
|
+
resolve({ spawned: false, exitCode: -1, stdout: out, stderr: errOut });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const code = err
|
|
150
|
+
? typeof /** @type {any} */ (err).code === 'number'
|
|
151
|
+
? /** @type {any} */ (err).code
|
|
152
|
+
: 1
|
|
153
|
+
: 0;
|
|
154
|
+
resolve({ spawned: true, exitCode: code, stdout: out, stderr: errOut });
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Run `crtr` with the given argv, walking candidates until one spawns.
|
|
162
|
+
* @param {string[]} argv
|
|
163
|
+
* @returns {Promise<RunResult>}
|
|
164
|
+
*/
|
|
165
|
+
async function runCrtr(argv) {
|
|
166
|
+
/** @type {RunResult} */
|
|
167
|
+
let last = { spawned: false, exitCode: -1, stdout: '', stderr: '' };
|
|
168
|
+
for (const c of crtrCandidates()) {
|
|
169
|
+
last = await runOnce(c, argv);
|
|
170
|
+
if (last.spawned) return last;
|
|
171
|
+
}
|
|
172
|
+
return last; // never spawned
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @returns {ClientError} */
|
|
176
|
+
function crtrMissingError() {
|
|
177
|
+
return {
|
|
178
|
+
kind: 'crtr-missing',
|
|
179
|
+
message:
|
|
180
|
+
"crtr not found — tried CRTR_BIN, PATH 'crtr'/'crouter', and the local cli.js. " +
|
|
181
|
+
'Install crtr or set CRTR_BIN.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Pull a human message out of crtr stderr: prefer the last `ERROR:` line, else
|
|
187
|
+
* the last non-empty line, else a generic fallback.
|
|
188
|
+
* @param {string} stderr
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
function extractMessage(stderr) {
|
|
192
|
+
const lines = String(stderr || '')
|
|
193
|
+
.split(/\r?\n/)
|
|
194
|
+
.map((l) => l.trim())
|
|
195
|
+
.filter(Boolean);
|
|
196
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
197
|
+
if (/^ERROR:/i.test(lines[i])) return lines[i].replace(/^ERROR:\s*/i, '').trim();
|
|
198
|
+
}
|
|
199
|
+
if (lines.length) return lines[lines.length - 1];
|
|
200
|
+
return 'crtr command failed';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Map a failed crtr invocation to a typed {@link ClientError}.
|
|
205
|
+
* @param {string} stderr
|
|
206
|
+
* @returns {ClientError}
|
|
207
|
+
*/
|
|
208
|
+
function classifyError(stderr) {
|
|
209
|
+
const s = String(stderr || '');
|
|
210
|
+
if (/help-gate:\s*blocked/i.test(s)) {
|
|
211
|
+
return { kind: 'error', message: 'crtr help-gate blocked the call (run the command with -h once).' };
|
|
212
|
+
}
|
|
213
|
+
return { kind: 'crtr-failed', message: extractMessage(s) };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Run a crtr `--json` leaf and parse stdout as JSON.
|
|
218
|
+
* @param {string[]} argv
|
|
219
|
+
* @returns {Promise<Result<any>>}
|
|
220
|
+
*/
|
|
221
|
+
async function runJson(argv) {
|
|
222
|
+
const r = await runCrtr(argv);
|
|
223
|
+
if (!r.spawned) return fail(crtrMissingError());
|
|
224
|
+
if (r.exitCode !== 0) return fail(classifyError(r.stderr || r.stdout));
|
|
225
|
+
const out = String(r.stdout || '').trim();
|
|
226
|
+
if (out === '') return ok(null);
|
|
227
|
+
try {
|
|
228
|
+
return ok(JSON.parse(out));
|
|
229
|
+
} catch {
|
|
230
|
+
return fail({ kind: 'parse', message: `could not parse \`crtr ${argv.join(' ')}\` output as JSON` });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── Field mappers ────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
/** @param {unknown} v @returns {string} */
|
|
237
|
+
function str(v) {
|
|
238
|
+
return v == null ? '' : String(v);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** @param {any} n @returns {CanvasNode} */
|
|
242
|
+
function toNode(n) {
|
|
243
|
+
const o = n || {};
|
|
244
|
+
return {
|
|
245
|
+
nodeId: str(o.node_id),
|
|
246
|
+
name: str(o.name) || '(unnamed)',
|
|
247
|
+
kind: str(o.kind) || '?',
|
|
248
|
+
mode: str(o.mode) || '?',
|
|
249
|
+
lifecycle: str(o.lifecycle) || '?',
|
|
250
|
+
status: str(o.status) || '?',
|
|
251
|
+
parent: o.parent ? str(o.parent) : null,
|
|
252
|
+
created: str(o.created),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** @param {any} i @returns {AttentionItem} */
|
|
257
|
+
function toAttention(i) {
|
|
258
|
+
const o = i || {};
|
|
259
|
+
return {
|
|
260
|
+
nodeId: str(o.node_id),
|
|
261
|
+
name: str(o.name),
|
|
262
|
+
cwd: str(o.cwd),
|
|
263
|
+
count: typeof o.count === 'number' ? o.count : 0,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Every node on the canvas (all statuses). The view rebuilds the forest from the
|
|
271
|
+
* `parent` edges and filters to active trees itself.
|
|
272
|
+
* @returns {Promise<Result<CanvasNode[]>>}
|
|
273
|
+
*/
|
|
274
|
+
export async function fetchNodes() {
|
|
275
|
+
const r = await runJson(['node', 'inspect', 'list', '--json']);
|
|
276
|
+
if (!r.ok) return r;
|
|
277
|
+
const arr = r.data && Array.isArray(r.data.nodes) ? r.data.nodes : [];
|
|
278
|
+
return ok(arr.map(toNode));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Pending human asks across the canvas (the "blocked on a human" signal).
|
|
283
|
+
* @returns {Promise<Result<{items:AttentionItem[], total:number}>>}
|
|
284
|
+
*/
|
|
285
|
+
export async function fetchAttention() {
|
|
286
|
+
const r = await runJson(['canvas', 'attention', 'list', '--json']);
|
|
287
|
+
if (!r.ok) return r;
|
|
288
|
+
const items = r.data && Array.isArray(r.data.items) ? r.data.items : [];
|
|
289
|
+
const total = r.data && typeof r.data.total === 'number' ? r.data.total : 0;
|
|
290
|
+
return ok({ items: items.map(toAttention), total });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Introspection helper (not used at runtime): the exact crtr commands this
|
|
295
|
+
* client shells, so the shape can be eyeballed without a live canvas.
|
|
296
|
+
* @returns {Record<string,string>}
|
|
297
|
+
*/
|
|
298
|
+
export function describeCommands() {
|
|
299
|
+
return {
|
|
300
|
+
fetchNodes: 'crtr node inspect list --json',
|
|
301
|
+
fetchAttention: 'crtr canvas attention list --json',
|
|
302
|
+
};
|
|
303
|
+
}
|