@crouton-kit/crouter 0.3.18 → 0.3.20
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/PERSONA.md +1 -0
- package/dist/builtin-personas/design/orchestrator.md +2 -1
- package/dist/builtin-personas/developer/PERSONA.md +1 -0
- package/dist/builtin-personas/developer/orchestrator.md +2 -1
- package/dist/builtin-personas/explore/PERSONA.md +1 -0
- package/dist/builtin-personas/explore/orchestrator.md +4 -0
- package/dist/builtin-personas/general/PERSONA.md +1 -0
- package/dist/builtin-personas/general/orchestrator.md +4 -0
- 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/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/orchestrator.md +2 -1
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -0
- package/dist/builtin-personas/review/orchestrator.md +4 -0
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/PERSONA.md +1 -0
- package/dist/builtin-personas/spec/orchestrator.md +2 -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 +54 -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 +610 -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-attach-limits.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-limits.test.js +157 -0
- package/dist/core/__tests__/broker-attach-stream.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-stream.test.js +125 -0
- package/dist/core/__tests__/broker-crash-teardown.test.d.ts +1 -0
- package/dist/core/__tests__/broker-crash-teardown.test.js +116 -0
- package/dist/core/__tests__/broker-dialogs.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dialogs.test.js +126 -0
- package/dist/core/__tests__/broker-dormant-wake.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dormant-wake.test.js +51 -0
- package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +87 -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__/canvas-inbox-watcher-hold.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +102 -0
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +2 -36
- 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/broker-clients.d.ts +43 -0
- package/dist/core/__tests__/helpers/broker-clients.js +178 -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-verbs.test.d.ts +1 -0
- package/dist/core/__tests__/live-mutation-verbs.test.js +175 -0
- package/dist/core/__tests__/live-mutation.test.js +6 -132
- 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 +16 -23
- 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 +91 -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 +54 -4
- package/dist/core/runtime/launch.js +85 -5
- 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 +37 -0
- package/dist/core/runtime/nodes.js +60 -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 +4 -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 +11 -8
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +23 -0
- package/dist/core/runtime/spawn.js +77 -23
- 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
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
// Every hue (fg / bg color) is gated on `caps.color`; structural SGR (bold/dim/
|
|
12
12
|
// reverse) is allowed always. See detectColorCaps() for the gate, and the
|
|
13
13
|
// canvas-browse color spec for the rationale + palette.
|
|
14
|
-
import { TABS, matchIndices } from './model.js';
|
|
14
|
+
import { TABS, matchIndices, promptText, previewSnippet } from './model.js';
|
|
15
|
+
// Span/color primitives live in core/tui/draw.ts (one copy, shared with the
|
|
16
|
+
// `crtr view` host). Re-export the color caps so browse's importers + tests keep
|
|
17
|
+
// resolving them from this module.
|
|
18
|
+
import { clip, assemble, detectColorCaps } from '../../tui/draw.js';
|
|
19
|
+
export { detectColorCaps };
|
|
15
20
|
// Fixed chrome heights, shared with app.ts so its viewport math never drifts
|
|
16
21
|
// from what renderFrame actually draws.
|
|
17
22
|
// header = title + tab bar + status line + separator (+ search input when searching)
|
|
@@ -52,16 +57,6 @@ const STATUS_COLOR = {
|
|
|
52
57
|
dead: FG_RED,
|
|
53
58
|
canceled: FG_GRAY,
|
|
54
59
|
};
|
|
55
|
-
/** Detect color capability. Honors `NO_COLOR` and `TERM=dumb`, and only emits
|
|
56
|
-
* hue when stdout is a TTY. `color256` additionally requires a 256/truecolor
|
|
57
|
-
* terminal (for the cursor-row background; otherwise we fall back to reverse). */
|
|
58
|
-
export function detectColorCaps(stream = process.stdout, env = process.env) {
|
|
59
|
-
const term = env['TERM'] ?? '';
|
|
60
|
-
const color = stream.isTTY === true && !env['NO_COLOR'] && term !== 'dumb';
|
|
61
|
-
const colorTerm = env['COLORTERM'] ?? '';
|
|
62
|
-
const color256 = color && (/256|direct/i.test(term) || /truecolor|24bit/i.test(colorTerm));
|
|
63
|
-
return { color, color256 };
|
|
64
|
-
}
|
|
65
60
|
function fmtCtx(tokens) {
|
|
66
61
|
if (tokens <= 0)
|
|
67
62
|
return '0k';
|
|
@@ -76,12 +71,6 @@ function ctxColorCode(tokens) {
|
|
|
76
71
|
return FG_YELLOW;
|
|
77
72
|
return undefined;
|
|
78
73
|
}
|
|
79
|
-
/** Truncate to `max` visible cols (plain text, no ANSI). */
|
|
80
|
-
function clip(text, max) {
|
|
81
|
-
if (max <= 0)
|
|
82
|
-
return '';
|
|
83
|
-
return text.length <= max ? text : text.slice(0, Math.max(0, max - 1)) + '…';
|
|
84
|
-
}
|
|
85
74
|
/** Compact relative age, e.g. `45s` `12m` `3h` `5d` `2w` `4mo`. Empty on a bad
|
|
86
75
|
* timestamp. Drives the per-row recency cue + the preview meta line. */
|
|
87
76
|
function relAge(created, now) {
|
|
@@ -110,74 +99,13 @@ function baseDir(cwd) {
|
|
|
110
99
|
const parts = cwd.replace(/\/+$/, '').split('/');
|
|
111
100
|
return parts[parts.length - 1] || cwd;
|
|
112
101
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
for (const w of words) {
|
|
121
|
-
const next = cur === '' ? w : `${cur} ${w}`;
|
|
122
|
-
if (next.length <= width) {
|
|
123
|
-
cur = next;
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (cur !== '')
|
|
127
|
-
out.push(cur);
|
|
128
|
-
cur = w;
|
|
129
|
-
if (out.length >= maxLines)
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
if (out.length < maxLines && cur !== '')
|
|
133
|
-
out.push(cur);
|
|
134
|
-
return out.slice(0, maxLines).map((l) => clip(l, width));
|
|
135
|
-
}
|
|
136
|
-
/** Style one span. Hue is gated on `color`; bold/dim are not. After the span we
|
|
137
|
-
* return to `lineBase` (not a bare reset) so a row-level background/dim persists
|
|
138
|
-
* across the span instead of bleeding or being cleared. */
|
|
139
|
-
function styleSpan(text, span, color, lineBase) {
|
|
140
|
-
if (text === '')
|
|
141
|
-
return '';
|
|
142
|
-
let pre = '';
|
|
143
|
-
if (span.dim)
|
|
144
|
-
pre += DIM;
|
|
145
|
-
if (span.bold)
|
|
146
|
-
pre += BOLD;
|
|
147
|
-
if (color && span.fg)
|
|
148
|
-
pre += `${ESC}${span.fg}m`;
|
|
149
|
-
if (pre === '')
|
|
150
|
-
return text; // inherits lineBase / default
|
|
151
|
-
return `${pre}${text}${RESET}${lineBase}`;
|
|
152
|
-
}
|
|
153
|
-
/** Assemble styled spans into one line clipped to `width` visible cols. When
|
|
154
|
-
* `fill`, pad the remainder with spaces (under `lineBase`) so a cursor-row
|
|
155
|
-
* background spans the full width. Always RESET-terminated so no color bleeds
|
|
156
|
-
* into the next line. */
|
|
157
|
-
function assemble(spans, width, color, lineBase, fill) {
|
|
158
|
-
let used = 0;
|
|
159
|
-
let body = '';
|
|
160
|
-
for (const span of spans) {
|
|
161
|
-
if (used >= width)
|
|
162
|
-
break;
|
|
163
|
-
if (span.text === '')
|
|
164
|
-
continue;
|
|
165
|
-
let t = span.text;
|
|
166
|
-
const remaining = width - used;
|
|
167
|
-
let cut = false;
|
|
168
|
-
if (t.length > remaining) {
|
|
169
|
-
t = t.slice(0, Math.max(0, remaining - 1)) + '…';
|
|
170
|
-
cut = true;
|
|
171
|
-
}
|
|
172
|
-
body += styleSpan(t, span, color, lineBase);
|
|
173
|
-
used += t.length;
|
|
174
|
-
if (cut)
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
if (fill && used < width)
|
|
178
|
-
body += ' '.repeat(width - used);
|
|
179
|
-
return lineBase === '' ? body : `${lineBase}${body}${RESET}`;
|
|
180
|
-
}
|
|
102
|
+
// ── Styled spans ──────────────────────────────────────────────────────────────
|
|
103
|
+
//
|
|
104
|
+
// A row is built from styled spans, then assembled to a width-clipped line. This
|
|
105
|
+
// lets each cell carry its own hue (status glyph, ctx tier, asks, match highlight)
|
|
106
|
+
// while clipping by VISIBLE width — ANSI bytes don't count toward the column
|
|
107
|
+
// budget. `fg` is hue (gated on `color`); `bold`/`dim` are structural (always).
|
|
108
|
+
// Span / styleSpan / assemble now live in core/tui/draw.ts (imported above).
|
|
181
109
|
/** Status tallies across the whole canvas, for the right-aligned header. Memoized
|
|
182
110
|
* per tree — the snapshot is immutable for a browse session, so counting is done
|
|
183
111
|
* once, not O(N) on every keystroke (the "massive canvas" target). */
|
|
@@ -213,7 +141,7 @@ const EMPTY_HI = new Set();
|
|
|
213
141
|
function nameSpans(name, query, style) {
|
|
214
142
|
const hi = query === '' ? EMPTY_HI : matchIndices(query, name);
|
|
215
143
|
if (hi.size === 0)
|
|
216
|
-
return [{ text: name, dim: style.dim, bold: style.bold }];
|
|
144
|
+
return [{ text: name, style: { dim: style.dim, bold: style.bold } }];
|
|
217
145
|
const out = [];
|
|
218
146
|
let buf = '';
|
|
219
147
|
let bufHi = false;
|
|
@@ -221,9 +149,9 @@ function nameSpans(name, query, style) {
|
|
|
221
149
|
if (buf === '')
|
|
222
150
|
return;
|
|
223
151
|
if (bufHi)
|
|
224
|
-
out.push({ text: buf, fg: FG_BRIGHT_CYAN, bold: true });
|
|
152
|
+
out.push({ text: buf, style: { fg: FG_BRIGHT_CYAN, bold: true } });
|
|
225
153
|
else
|
|
226
|
-
out.push({ text: buf, dim: style.dim, bold: style.bold });
|
|
154
|
+
out.push({ text: buf, style: { dim: style.dim, bold: style.bold } });
|
|
227
155
|
buf = '';
|
|
228
156
|
};
|
|
229
157
|
for (let i = 0; i < name.length; i++) {
|
|
@@ -255,19 +183,19 @@ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
|
|
|
255
183
|
const nameStyle = { dim: !isCursor && terminal, bold: isCursor };
|
|
256
184
|
const spans = [
|
|
257
185
|
{ text: `${indent}${collapse} ` },
|
|
258
|
-
{ text: glyph, fg: STATUS_COLOR[r.status] }, // load-bearing status hue
|
|
186
|
+
{ text: glyph, style: { fg: STATUS_COLOR[r.status] } }, // load-bearing status hue
|
|
259
187
|
{ text: ' ' },
|
|
260
188
|
...nameSpans(r.name, query, nameStyle),
|
|
261
|
-
{ text: ` [${r.kind}/${r.mode}]`, fg: FG_GRAY }, // recedes
|
|
262
|
-
{ text: ' ctx ', dim: true },
|
|
263
|
-
{ text: ctxStr, fg: ctxFg, dim: ctxFg === undefined }, // tiered budget cue
|
|
189
|
+
{ text: ` [${r.kind}/${r.mode}]`, style: { fg: FG_GRAY } }, // recedes
|
|
190
|
+
{ text: ' ctx ', style: { dim: true } },
|
|
191
|
+
{ text: ctxStr, style: { fg: ctxFg, dim: ctxFg === undefined } }, // tiered budget cue
|
|
264
192
|
];
|
|
265
193
|
if (age !== '')
|
|
266
|
-
spans.push({ text: ` ${age}`, dim: true }); // recency cue
|
|
194
|
+
spans.push({ text: ` ${age}`, style: { dim: true } }); // recency cue
|
|
267
195
|
if (showCwd)
|
|
268
|
-
spans.push({ text: ` ~${baseDir(r.cwd)}`, fg: FG_GRAY }); // project cue (All dirs)
|
|
196
|
+
spans.push({ text: ` ~${baseDir(r.cwd)}`, style: { fg: FG_GRAY } }); // project cue (All dirs)
|
|
269
197
|
if (r.asks > 0)
|
|
270
|
-
spans.push({ text: ` ⚑${r.asks}`, fg: FG_BRIGHT_YELLOW, bold: true }); // attention
|
|
198
|
+
spans.push({ text: ` ⚑${r.asks}`, style: { fg: FG_BRIGHT_YELLOW, bold: true } }); // attention
|
|
271
199
|
// Row base: cursor → subtle bg (256) or reverse fallback (also covers !color);
|
|
272
200
|
// non-matched ancestor → whole-row dim for tree context (keep prior behavior).
|
|
273
201
|
let lineBase = '';
|
|
@@ -288,15 +216,44 @@ function statusLine(state) {
|
|
|
288
216
|
const dim = (s) => `${DIM}${s}${RESET}`;
|
|
289
217
|
const scope = state.cwdScope === null ? 'all dirs' : baseDir(state.cwdScope);
|
|
290
218
|
const segs = [`${dim('scope')} ${scope}`, `${dim('sort')} ${state.sort}`];
|
|
219
|
+
if (state.residentsOnly)
|
|
220
|
+
segs.push(`${dim('show')} residents`);
|
|
291
221
|
if (!state.search && state.query !== '')
|
|
292
222
|
segs.push(`${dim('filter')} ${state.query}`);
|
|
293
223
|
return segs.join(dim(' · '));
|
|
294
224
|
}
|
|
225
|
+
/** One snippet line → a rendered string with the query-matched columns in bold
|
|
226
|
+
* bright-cyan (mirrors the row name highlight). Unmatched text stays default. */
|
|
227
|
+
function snippetLine(ln, width, caps) {
|
|
228
|
+
if (ln.hi.size === 0)
|
|
229
|
+
return assemble([{ text: ln.text }], width, caps.color, '', false);
|
|
230
|
+
const spans = [];
|
|
231
|
+
let buf = '';
|
|
232
|
+
let bufHi = false;
|
|
233
|
+
const flush = () => {
|
|
234
|
+
if (buf === '')
|
|
235
|
+
return;
|
|
236
|
+
spans.push(bufHi ? { text: buf, style: { fg: FG_BRIGHT_CYAN, bold: true } } : { text: buf });
|
|
237
|
+
buf = '';
|
|
238
|
+
};
|
|
239
|
+
for (let i = 0; i < ln.text.length; i++) {
|
|
240
|
+
const h = ln.hi.has(i);
|
|
241
|
+
if (h !== bufHi) {
|
|
242
|
+
flush();
|
|
243
|
+
bufHi = h;
|
|
244
|
+
}
|
|
245
|
+
buf += ln.text[i];
|
|
246
|
+
}
|
|
247
|
+
flush();
|
|
248
|
+
return assemble(spans, width, caps.color, '', false);
|
|
249
|
+
}
|
|
295
250
|
/** The bottom preview panel — exactly PREVIEW_HEIGHT lines: a separator, a meta
|
|
296
|
-
* line (status · kind/mode · project · age · ctx · asks), then the selected
|
|
297
|
-
*
|
|
251
|
+
* line (status · kind/mode · project · age · ctx · asks), then the selected node's
|
|
252
|
+
* prompt wrapped to PREVIEW_BODY lines. Under a live query the body is WINDOWED to
|
|
253
|
+
* the matching prompt (anywhere in the conversation) with the match highlighted;
|
|
254
|
+
* with no query it shows the spawn prompt from the start. The "which one was this?"
|
|
298
255
|
* answer — paired with super-search. Always full height so viewport math holds. */
|
|
299
|
-
function previewPanel(r, width, caps, now) {
|
|
256
|
+
function previewPanel(r, width, caps, now, query) {
|
|
300
257
|
const out = [`${DIM}${'─'.repeat(width)}${RESET}`];
|
|
301
258
|
if (r === undefined) {
|
|
302
259
|
while (out.length < PREVIEW_HEIGHT)
|
|
@@ -311,10 +268,21 @@ function previewPanel(r, width, caps, now) {
|
|
|
311
268
|
metaPieces.push(`⚑${r.asks}`);
|
|
312
269
|
const metaText = clip(metaPieces.filter((p) => p !== '').join(' · '), Math.max(0, width - 2));
|
|
313
270
|
out.push(caps.color ? `${glyph} ${DIM}${metaText}${RESET}` : `${glyph} ${metaText}`);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
271
|
+
// With a query, window+highlight the matching prompt from the WHOLE conversation;
|
|
272
|
+
// otherwise show the spawn prompt from the start.
|
|
273
|
+
const sourceText = query !== '' ? promptText(r) : (r.goal ?? '');
|
|
274
|
+
const snippet = previewSnippet(query, sourceText, width, PREVIEW_BODY);
|
|
275
|
+
if (snippet.length === 0) {
|
|
276
|
+
out.push(`${DIM}(no spawn prompt)${RESET}`);
|
|
277
|
+
for (let i = 1; i < PREVIEW_BODY; i++)
|
|
278
|
+
out.push('');
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
for (let i = 0; i < PREVIEW_BODY; i++) {
|
|
282
|
+
const ln = snippet[i];
|
|
283
|
+
out.push(ln === undefined ? '' : snippetLine(ln, width, caps));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
318
286
|
return out;
|
|
319
287
|
}
|
|
320
288
|
/**
|
|
@@ -373,13 +341,13 @@ export function renderFrame(state, size, caps = { color: false, color256: false
|
|
|
373
341
|
if (previewOn) {
|
|
374
342
|
const sel = state.visible[state.cursor];
|
|
375
343
|
const r = sel !== undefined ? state.tree.nodes.get(sel.id)?.row : undefined;
|
|
376
|
-
for (const l of previewPanel(r, width, caps, now))
|
|
344
|
+
for (const l of previewPanel(r, width, caps, now, state.query))
|
|
377
345
|
lines.push(l);
|
|
378
346
|
}
|
|
379
347
|
// footer.
|
|
380
348
|
const footer = state.search
|
|
381
349
|
? '⏎ commit Esc cancel ⌫ delete'
|
|
382
|
-
: '↑↓ move →/← tree ⏎ resume Tab tabs / search s sort c cwd p preview q quit';
|
|
350
|
+
: '↑↓ move →/← tree ⏎ resume Tab tabs / search s sort c cwd r residents p preview q quit';
|
|
383
351
|
lines.push(`${DIM}${clip(footer, width)}${RESET}`);
|
|
384
352
|
// Assemble: home, each line cleared to EOL, then clear below.
|
|
385
353
|
const body = lines.map((l) => `${l}${ESC}K`).join('\r\n');
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, renameSync, readdirSync, rmSync, } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { openDb } from './db.js';
|
|
10
|
+
import { isPidAlive } from './pid.js';
|
|
10
11
|
import { ensureHome, ensureNodeDirs, nodeMetaPath, nodeDir, nodesRoot, } from './paths.js';
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// meta.json (durable identity — the source of truth for what PERSISTS)
|
|
@@ -19,9 +20,9 @@ import { ensureHome, ensureNodeDirs, nodeMetaPath, nodeDir, nodesRoot, } from '.
|
|
|
19
20
|
/** The identity keys meta.json persists. Listed explicitly so no runtime field
|
|
20
21
|
* can ever leak onto disk even when a fully-hydrated NodeMeta is handed in. */
|
|
21
22
|
const IDENTITY_KEYS = [
|
|
22
|
-
'node_id', 'name', 'description', 'cycles', 'created', 'cwd', 'kind', 'mode',
|
|
23
|
-
'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'passive_default',
|
|
24
|
-
'home_session', 'pi_session_id', 'pi_session_file', 'launch',
|
|
23
|
+
'node_id', 'name', 'description', 'cycles', 'created', 'cwd', 'host_kind', 'kind', 'mode',
|
|
24
|
+
'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'fork_from', 'passive_default',
|
|
25
|
+
'home_session', 'pi_session_id', 'pi_session_file', 'model_override', 'launch',
|
|
25
26
|
];
|
|
26
27
|
/** Project any node object down to its durable-identity subset. */
|
|
27
28
|
function toIdentity(m) {
|
|
@@ -60,12 +61,13 @@ function writeMeta(meta) {
|
|
|
60
61
|
* state. A fresh insert takes the schema defaults for runtime. */
|
|
61
62
|
function upsertRow(meta) {
|
|
62
63
|
openDb()
|
|
63
|
-
.prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, parent, created)
|
|
64
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
64
|
+
.prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, host_kind, parent, created)
|
|
65
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
65
66
|
ON CONFLICT(node_id) DO UPDATE SET
|
|
66
67
|
name=excluded.name, kind=excluded.kind, mode=excluded.mode,
|
|
67
|
-
lifecycle=excluded.lifecycle, cwd=excluded.cwd,
|
|
68
|
-
|
|
68
|
+
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
69
|
+
parent=excluded.parent`)
|
|
70
|
+
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.parent ?? null, meta.created);
|
|
69
71
|
}
|
|
70
72
|
/** Seed a node's row at BIRTH: identity columns + runtime columns taken from the
|
|
71
73
|
* incoming meta (defaults: status='active', the rest null). The only writer
|
|
@@ -74,16 +76,17 @@ function upsertRow(meta) {
|
|
|
74
76
|
function seedRow(meta) {
|
|
75
77
|
openDb()
|
|
76
78
|
.prepare(`INSERT INTO nodes
|
|
77
|
-
(node_id, name, kind, mode, lifecycle, cwd, parent, created,
|
|
79
|
+
(node_id, name, kind, mode, lifecycle, cwd, host_kind, parent, created,
|
|
78
80
|
status, intent, pi_pid, "window", tmux_session, pane)
|
|
79
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
81
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
80
82
|
ON CONFLICT(node_id) DO UPDATE SET
|
|
81
83
|
name=excluded.name, kind=excluded.kind, mode=excluded.mode,
|
|
82
|
-
lifecycle=excluded.lifecycle, cwd=excluded.cwd,
|
|
84
|
+
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
85
|
+
parent=excluded.parent,
|
|
83
86
|
status=excluded.status, intent=excluded.intent, pi_pid=excluded.pi_pid,
|
|
84
87
|
"window"=excluded."window", tmux_session=excluded.tmux_session,
|
|
85
88
|
pane=excluded.pane`)
|
|
86
|
-
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.parent ?? null, meta.created, meta.status ?? 'active', meta.intent ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
89
|
+
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.parent ?? null, meta.created, meta.status ?? 'active', meta.intent ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
87
90
|
}
|
|
88
91
|
function rowFrom(r) {
|
|
89
92
|
return {
|
|
@@ -94,6 +97,7 @@ function rowFrom(r) {
|
|
|
94
97
|
lifecycle: r['lifecycle'],
|
|
95
98
|
status: r['status'],
|
|
96
99
|
cwd: r['cwd'],
|
|
100
|
+
host_kind: r['host_kind'] ?? null,
|
|
97
101
|
parent: r['parent'] ?? null,
|
|
98
102
|
created: r['created'],
|
|
99
103
|
intent: r['intent'] ?? null,
|
|
@@ -352,17 +356,6 @@ export function rebuildIndex() {
|
|
|
352
356
|
recordSpawn(meta.node_id, prov);
|
|
353
357
|
}
|
|
354
358
|
}
|
|
355
|
-
/** Is `pid` a live process? `kill(pid, 0)` sends no signal — it only probes
|
|
356
|
-
* existence/permission. ESRCH ⇒ gone; EPERM ⇒ alive but not ours (still alive). */
|
|
357
|
-
function pidAlive(pid) {
|
|
358
|
-
try {
|
|
359
|
-
process.kill(pid, 0);
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
catch (e) {
|
|
363
|
-
return e.code === 'EPERM';
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
359
|
/** Retention sweep: remove TERMINAL nodes (status dead | done | canceled) whose
|
|
367
360
|
* `created` is older than `ttlDays`, bounding the otherwise-unbounded growth of
|
|
368
361
|
* node rows + dirs. The edges→nodes FK (`ON DELETE CASCADE`, migration v4) GCs
|
|
@@ -406,7 +399,7 @@ export function pruneNodes(opts) {
|
|
|
406
399
|
if (r['node_id'] === selfId)
|
|
407
400
|
return false;
|
|
408
401
|
const pid = r['pi_pid'];
|
|
409
|
-
return
|
|
402
|
+
return !isPidAlive(pid);
|
|
410
403
|
})
|
|
411
404
|
.map((r) => ({
|
|
412
405
|
node_id: r['node_id'],
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -208,6 +208,50 @@ CREATE TABLE IF NOT EXISTS focuses (
|
|
|
208
208
|
);
|
|
209
209
|
`);
|
|
210
210
|
}
|
|
211
|
+
/** v7 — the `wakeups` table: durable store for scheduled wake-ups (the
|
|
212
|
+
* scheduled-wakeups feature). One row per armed wake. `node_id` is the TARGET
|
|
213
|
+
* and the node-anchored cancel anchor — it carries a `ON DELETE CASCADE` FK to
|
|
214
|
+
* `nodes(node_id)` so a pruned/deleted node's node-anchored wakes are reaped in
|
|
215
|
+
* the same transaction (mirroring the edges v4 cascade; works because
|
|
216
|
+
* `PRAGMA foreign_keys = ON` is set at every open). NULL `node_id` = a
|
|
217
|
+
* canvas-detached wake (deferred spawn / spawn-cron) that survives.
|
|
218
|
+
*
|
|
219
|
+
* `owner_id` is the ARMER, set on EVERY row, and is deliberately a PLAIN indexed
|
|
220
|
+
* column with NO FK (design D1/D2): an FK on `owner_id` would cascade-drop a
|
|
221
|
+
* crashed-then-pruned armer's detached cron, violating Invariant E. A detached
|
|
222
|
+
* wake is reaped only by the explicit `cancelWakesFor` DELETE, never by cascade.
|
|
223
|
+
*
|
|
224
|
+
* `fire_at` is an ISO 8601 UTC string for the NEXT occurrence, so the per-tick
|
|
225
|
+
* due query is a lexicographic `<=` compare. The partial unique index
|
|
226
|
+
* `idx_wakeups_deadline` enforces ≤1 deadline per node. Additive, forward-only. */
|
|
227
|
+
function appendWakeupsTable(db) {
|
|
228
|
+
db.exec(`
|
|
229
|
+
CREATE TABLE IF NOT EXISTS wakeups (
|
|
230
|
+
wakeup_id TEXT PRIMARY KEY,
|
|
231
|
+
node_id TEXT, -- TARGET + node-anchored cancel anchor; NULL = canvas-detached
|
|
232
|
+
owner_id TEXT NOT NULL, -- ARMER; set on EVERY row. PLAIN column, NO FK (design D1/D2)
|
|
233
|
+
fire_at TEXT NOT NULL, -- ISO 8601 UTC; NEXT occurrence; lexicographic <=
|
|
234
|
+
kind TEXT NOT NULL, -- 'bare' | 'noted' | 'deadline' | 'spawn'
|
|
235
|
+
recur TEXT, -- NULL = one-shot; else the pinned recur JSON
|
|
236
|
+
payload TEXT, -- JSON per kind (shared contracts)
|
|
237
|
+
created TEXT NOT NULL,
|
|
238
|
+
FOREIGN KEY (node_id) REFERENCES nodes(node_id) ON DELETE CASCADE
|
|
239
|
+
);
|
|
240
|
+
CREATE INDEX IF NOT EXISTS idx_wakeups_fire ON wakeups(fire_at); -- per-tick due query
|
|
241
|
+
CREATE INDEX IF NOT EXISTS idx_wakeups_node ON wakeups(node_id); -- by-node cancel / list
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_wakeups_owner ON wakeups(owner_id); -- by-owner reap (ruling A)
|
|
243
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_wakeups_deadline ON wakeups(node_id) WHERE kind = 'deadline';
|
|
244
|
+
`);
|
|
245
|
+
}
|
|
246
|
+
/** v8 — additive identity column `host_kind`: which HOST launches + supervises a
|
|
247
|
+
* node — `'tmux'` (a pane) or `'broker'` (the headless in-process engine). It is
|
|
248
|
+
* durable IDENTITY mirrored from meta.json like `cwd` (NOT runtime like `pane`),
|
|
249
|
+
* so it survives an index rebuild via the `IDENTITY_KEYS`/`upsertRow` path.
|
|
250
|
+
* Defaults NULL ⇒ `'tmux'` (every existing node is tmux-hosted); no data
|
|
251
|
+
* backfill. Additive, forward-only. */
|
|
252
|
+
function addHostKindColumn(db) {
|
|
253
|
+
db.exec(`ALTER TABLE nodes ADD COLUMN host_kind TEXT;`);
|
|
254
|
+
}
|
|
211
255
|
/** The ordered migration list. Index `i` is migration version `i + 1`; the db's
|
|
212
256
|
* `user_version` tracks how many have been applied. Append only. */
|
|
213
257
|
export const MIGRATIONS = [
|
|
@@ -217,6 +261,8 @@ export const MIGRATIONS = [
|
|
|
217
261
|
/* v4 */ edgesForeignKeyCascade,
|
|
218
262
|
/* v5 */ addPaneColumn,
|
|
219
263
|
/* v6 */ addFocusesTable,
|
|
264
|
+
/* v7 */ appendWakeupsTable,
|
|
265
|
+
/* v8 */ addHostKindColumn,
|
|
220
266
|
];
|
|
221
267
|
/** Bring `db` up to the latest schema version. Reads `user_version`, runs each
|
|
222
268
|
* pending migration in order, and bumps `user_version` after each so the work
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** True if a process with `pid` is currently alive (signal-0 probe). `kill(pid,
|
|
2
|
+
* 0)` throws ESRCH when the process is gone; EPERM means it exists but isn't
|
|
3
|
+
* ours — still alive. A null/undefined pid (legacy / never-booted) reads dead. */
|
|
4
|
+
export declare function isPidAlive(pid: number | null | undefined): boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/core/canvas/pid.ts
|
|
2
|
+
//
|
|
3
|
+
// The ONE shared signal-0 liveness probe. It lives at the canvas/ layer — the
|
|
4
|
+
// LOWEST shared layer — so canvas/, runtime/, AND daemon/ can all import it
|
|
5
|
+
// "down" without a reverse-layer violation (runtime/ sits above canvas/, daemon/
|
|
6
|
+
// above runtime/). It is a pure process-existence utility, NOT data-model access,
|
|
7
|
+
// so it is exempt from the "only canvas.ts touches the db" rule. Collapses the
|
|
8
|
+
// four near-identical copies that used to live in canvas.ts, revive.ts,
|
|
9
|
+
// placement.ts, and crtrd.ts into one (Phase 3 review reuse MINOR-1).
|
|
10
|
+
/** True if a process with `pid` is currently alive (signal-0 probe). `kill(pid,
|
|
11
|
+
* 0)` throws ESRCH when the process is gone; EPERM means it exists but isn't
|
|
12
|
+
* ours — still alive. A null/undefined pid (legacy / never-booted) reads dead. */
|
|
13
|
+
export function isPidAlive(pid) {
|
|
14
|
+
if (pid == null)
|
|
15
|
+
return false;
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 0);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return e.code === 'EPERM';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NodeStatus } from './types.js';
|
|
1
|
+
import type { NodeStatus, Lifecycle } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Render the subscription sub-DAG rooted at `rootId` as an ASCII tree.
|
|
4
4
|
* The root is the first line (no connector prefix); children are indented.
|
|
@@ -28,11 +28,20 @@ export interface DashboardRow {
|
|
|
28
28
|
cwd: string;
|
|
29
29
|
/** ISO 8601 birth timestamp — drives the recency sort + the relative-age cue. */
|
|
30
30
|
created: string;
|
|
31
|
+
/** terminal = one-shot worker (finalizes on push --final); resident = persistent
|
|
32
|
+
* agent you come back to. Drives the browser's resident-only lifecycle filter.
|
|
33
|
+
* Only populated by dashboardRowsAll (the browser snapshot). */
|
|
34
|
+
lifecycle?: Lifecycle;
|
|
31
35
|
/** The node's spawn prompt (context/initial-prompt.md), trimmed + capped. Only
|
|
32
36
|
* populated by dashboardRowsAll (the browser snapshot) — the dashboard leaf
|
|
33
37
|
* leaves it undefined to avoid a file read per node. Indexed by super-search
|
|
34
|
-
* and shown in the preview panel. */
|
|
38
|
+
* and shown in the preview panel when there is no live query. */
|
|
35
39
|
goal?: string;
|
|
40
|
+
/** EVERY user prompt across the node's pi session — the whole conversation, not
|
|
41
|
+
* just the spawn prompt — joined + capped. Populated by dashboardRowsAll, undefined
|
|
42
|
+
* for never-revived nodes (no session file). Powers whole-conversation super-search
|
|
43
|
+
* + the windowed match snippet in the preview. */
|
|
44
|
+
prompts?: string;
|
|
36
45
|
}
|
|
37
46
|
/** One row per node visible in the sub-DAG of `rootId` (including root). */
|
|
38
47
|
export declare function dashboardRows(rootId: string): DashboardRow[];
|
|
@@ -181,6 +181,73 @@ function readGoalText(nodeId) {
|
|
|
181
181
|
return undefined;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
/** EVERY user prompt across a node's pi session — the whole conversation, not just
|
|
185
|
+
* the spawn prompt. Streams the session jsonl off disk, prefiltering to user-role
|
|
186
|
+
* lines so the big assistant/toolResult lines are never JSON-parsed, extracts each
|
|
187
|
+
* user message's text, joins newline-separated, and caps total + per-message so a
|
|
188
|
+
* long session can't bloat the snapshot. Never throws; returns undefined when there
|
|
189
|
+
* is no session file yet (a node that was never revived). */
|
|
190
|
+
const CONVO_CAP = 8192;
|
|
191
|
+
const CONVO_MSG_CAP = 2048;
|
|
192
|
+
function readConversationPrompts(sessionFile) {
|
|
193
|
+
if (sessionFile === undefined || sessionFile === null || sessionFile === '')
|
|
194
|
+
return undefined;
|
|
195
|
+
try {
|
|
196
|
+
if (!existsSync(sessionFile))
|
|
197
|
+
return undefined;
|
|
198
|
+
const raw = readFileSync(sessionFile, 'utf8');
|
|
199
|
+
const parts = [];
|
|
200
|
+
let total = 0;
|
|
201
|
+
for (const line of raw.split('\n')) {
|
|
202
|
+
// Cheap prefilter: skip every line that isn't a user-role message before the
|
|
203
|
+
// (relatively costly) JSON.parse. Pi writes compact JSON (no spaces), but
|
|
204
|
+
// tolerate the spaced form too.
|
|
205
|
+
if (line === '' || (line.indexOf('"role":"user"') === -1 && line.indexOf('"role": "user"') === -1))
|
|
206
|
+
continue;
|
|
207
|
+
let rec;
|
|
208
|
+
try {
|
|
209
|
+
rec = JSON.parse(line);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (rec.type !== 'message' || rec.message?.role !== 'user')
|
|
215
|
+
continue;
|
|
216
|
+
const text = extractUserText(rec.message.content);
|
|
217
|
+
if (text === '')
|
|
218
|
+
continue;
|
|
219
|
+
const capped = text.length > CONVO_MSG_CAP ? text.slice(0, CONVO_MSG_CAP) : text;
|
|
220
|
+
parts.push(capped);
|
|
221
|
+
total += capped.length + 1;
|
|
222
|
+
if (total >= CONVO_CAP)
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
if (parts.length === 0)
|
|
226
|
+
return undefined;
|
|
227
|
+
const joined = parts.join('\n');
|
|
228
|
+
return joined.length > CONVO_CAP ? joined.slice(0, CONVO_CAP) : joined;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** Concatenate the `text` blocks of one pi user message's content. Content is
|
|
235
|
+
* usually an array of `{type,text}` blocks but may be a bare string. */
|
|
236
|
+
function extractUserText(content) {
|
|
237
|
+
if (typeof content === 'string')
|
|
238
|
+
return content.trim();
|
|
239
|
+
if (!Array.isArray(content))
|
|
240
|
+
return '';
|
|
241
|
+
const out = [];
|
|
242
|
+
for (const block of content) {
|
|
243
|
+
if (block !== null && typeof block === 'object') {
|
|
244
|
+
const b = block;
|
|
245
|
+
if (b.type === 'text' && typeof b.text === 'string')
|
|
246
|
+
out.push(b.text);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out.join(' ').trim();
|
|
250
|
+
}
|
|
184
251
|
/** One row per node visible in the sub-DAG of `rootId` (including root). */
|
|
185
252
|
export function dashboardRows(rootId) {
|
|
186
253
|
const ids = [rootId, ...view(rootId)];
|
|
@@ -219,7 +286,9 @@ export function dashboardRowsAll() {
|
|
|
219
286
|
asks: countAsks(row.node_id),
|
|
220
287
|
cwd: row.cwd,
|
|
221
288
|
created: row.created,
|
|
289
|
+
lifecycle: row.lifecycle,
|
|
222
290
|
goal: readGoalText(row.node_id),
|
|
291
|
+
prompts: readConversationPrompts(meta?.pi_session_file),
|
|
223
292
|
}];
|
|
224
293
|
});
|
|
225
294
|
}
|