@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
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// slash-commands.ts — builtin slash-command parse + dispatch for `crtr attach` (C6).
|
|
2
|
+
//
|
|
3
|
+
// Builtin slash commands are NOT engine-interpreted: `session.prompt('/model')`
|
|
4
|
+
// ships "/model" to the LLM as literal text. So the viewer recognizes the 21
|
|
5
|
+
// vendored `BUILTIN_SLASH_COMMANDS` itself and dispatches the matching
|
|
6
|
+
// `ClientToBroker` command frame; only EXTENSION commands (anything the viewer
|
|
7
|
+
// doesn't recognize) fall through to be sent as a `prompt` for the engine's
|
|
8
|
+
// extension runner to handle.
|
|
9
|
+
//
|
|
10
|
+
// SELECTORS — the Phase-4 reality. The plan called for `/model`, `/resume`,
|
|
11
|
+
// `/fork`, `/tree`, `/settings` to open pi's exported `*SelectorComponent`s. The
|
|
12
|
+
// authoritative 0.78.1 .d.ts shows those components require ENGINE-side state a
|
|
13
|
+
// socket-only viewer does not have (`ModelSelectorComponent` needs the
|
|
14
|
+
// `ModelRegistry` + `SettingsManager`; `SessionSelectorComponent` needs session
|
|
15
|
+
// loaders; `TreeSelectorComponent` needs the `SessionTreeNode[]` tree), and the
|
|
16
|
+
// §1.2 command-frame floor set exposes NO read op to fetch any of it. So in
|
|
17
|
+
// Phase 4 these resolve via a TEXT ARGUMENT — `/model <id>`, `/resume <path>`,
|
|
18
|
+
// `/fork <entryId>`, `/tree <entryId>` — which sends the SAME resolved value the
|
|
19
|
+
// selector would have, i.e. zero engine capability is lost; only the interactive
|
|
20
|
+
// picker UI waits for a future read op. A no-arg invocation shows a one-line
|
|
21
|
+
// notice with the arg form. (This protocol gap is reported up to the parent.)
|
|
22
|
+
//
|
|
23
|
+
// SCOPED OUT (review m2 — no engine method): `/login`, `/logout`, `/share`,
|
|
24
|
+
// `/trust` — a brief "not supported in attach" notice, never a frame.
|
|
25
|
+
import { BUILTIN_SLASH_COMMANDS } from '../../core/runtime/pi-vendored.js';
|
|
26
|
+
/** Builtins with no Phase-4 engine method — scoped out (review m2). */
|
|
27
|
+
const SCOPED_OUT = new Set(['login', 'logout', 'share', 'trust']);
|
|
28
|
+
/** Valid `/settings thinking` levels — the `SetThinkingLevelFrame['level']`
|
|
29
|
+
* union (= AgentSession['thinkingLevel'], pi-agent-core ThinkingLevel),
|
|
30
|
+
* mirrored as a runtime set so an invalid arg is rejected with the options
|
|
31
|
+
* rather than shipped as a frame the engine would throw on. Built from a
|
|
32
|
+
* `Record<level, true>` so the build fails BOTH ways if the union drifts: a
|
|
33
|
+
* missing key (union GAINED a level) or an extra key (union dropped one). */
|
|
34
|
+
const THINKING_LEVELS = new Set(Object.keys({
|
|
35
|
+
off: true,
|
|
36
|
+
minimal: true,
|
|
37
|
+
low: true,
|
|
38
|
+
medium: true,
|
|
39
|
+
high: true,
|
|
40
|
+
xhigh: true,
|
|
41
|
+
}));
|
|
42
|
+
const BUILTIN_NAMES = new Set(BUILTIN_SLASH_COMMANDS.map((c) => c.name));
|
|
43
|
+
/** True if `text` is a leading-slash command (vs. a normal prompt). */
|
|
44
|
+
export function isSlashCommand(text) {
|
|
45
|
+
return text.trimStart().startsWith('/');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse + dispatch a leading-slash command. Returns `true` if it was handled
|
|
49
|
+
* (a recognized builtin or a scoped-out notice) — the caller then clears the
|
|
50
|
+
* editor and sends nothing else. Returns `false` for an UNRECOGNIZED command,
|
|
51
|
+
* which the caller forwards to the engine as a `prompt` (extension command).
|
|
52
|
+
*/
|
|
53
|
+
export function dispatchSlashCommand(text, ctx) {
|
|
54
|
+
const m = /^\/(\S+)\s*([\s\S]*)$/.exec(text.trim());
|
|
55
|
+
if (!m)
|
|
56
|
+
return false;
|
|
57
|
+
const name = m[1];
|
|
58
|
+
const arg = m[2].trim();
|
|
59
|
+
if (SCOPED_OUT.has(name)) {
|
|
60
|
+
ctx.notify(`/${name} is not supported in attach (Phase 4)`);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// Unknown to us AND not a builtin → let the engine's extension runner have it.
|
|
64
|
+
if (!BUILTIN_NAMES.has(name))
|
|
65
|
+
return false;
|
|
66
|
+
switch (name) {
|
|
67
|
+
// --- direct frames -----------------------------------------------------
|
|
68
|
+
case 'new':
|
|
69
|
+
ctx.send({ type: 'new_session' });
|
|
70
|
+
return true;
|
|
71
|
+
case 'reload':
|
|
72
|
+
ctx.send({ type: 'reload' });
|
|
73
|
+
return true;
|
|
74
|
+
case 'compact':
|
|
75
|
+
ctx.send({ type: 'compact', instructions: arg || undefined });
|
|
76
|
+
return true;
|
|
77
|
+
case 'quit':
|
|
78
|
+
// A viewer "quit" DETACHES — the shared engine keeps running (one-writer).
|
|
79
|
+
ctx.send({ type: 'bye' });
|
|
80
|
+
ctx.notify('Detaching — the engine keeps running');
|
|
81
|
+
return true;
|
|
82
|
+
// --- frames needing one argument --------------------------------------
|
|
83
|
+
case 'name':
|
|
84
|
+
if (!arg)
|
|
85
|
+
return usage(ctx, '/name <display name>');
|
|
86
|
+
ctx.send({ type: 'set_session_name', name: arg });
|
|
87
|
+
return true;
|
|
88
|
+
case 'export': {
|
|
89
|
+
const { path, format } = parseExport(arg, ctx.cwd ?? process.cwd());
|
|
90
|
+
ctx.send({ type: 'export', path, format });
|
|
91
|
+
ctx.notify(`Exporting session → ${path}`);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
case 'import':
|
|
95
|
+
// `/import` resumes a session built from a JSONL file → switch_session.
|
|
96
|
+
if (!arg)
|
|
97
|
+
return usage(ctx, '/import <path-to-session.jsonl>');
|
|
98
|
+
ctx.send({ type: 'switch_session', path: arg });
|
|
99
|
+
return true;
|
|
100
|
+
// --- value-picker builtins: arg form (selector deferred, see header) ---
|
|
101
|
+
case 'model':
|
|
102
|
+
if (!arg)
|
|
103
|
+
return pickerHint(ctx, 'model', '/model <id>');
|
|
104
|
+
ctx.send({ type: 'set_model', model: arg });
|
|
105
|
+
return true;
|
|
106
|
+
case 'resume':
|
|
107
|
+
if (!arg)
|
|
108
|
+
return pickerHint(ctx, 'session', '/resume <session-path>');
|
|
109
|
+
ctx.send({ type: 'switch_session', path: arg });
|
|
110
|
+
return true;
|
|
111
|
+
case 'fork':
|
|
112
|
+
if (!arg)
|
|
113
|
+
return pickerHint(ctx, 'fork point', '/fork <entryId>');
|
|
114
|
+
ctx.send({ type: 'fork', entryId: arg });
|
|
115
|
+
return true;
|
|
116
|
+
case 'tree':
|
|
117
|
+
if (!arg)
|
|
118
|
+
return pickerHint(ctx, 'tree', '/tree <entryId>');
|
|
119
|
+
ctx.send({ type: 'navigate_tree', targetId: arg });
|
|
120
|
+
return true;
|
|
121
|
+
// --- settings (sub-command form; interactive menu needs read ops) ------
|
|
122
|
+
case 'settings':
|
|
123
|
+
return handleSettings(arg, ctx);
|
|
124
|
+
// --- read-only / informational ----------------------------------------
|
|
125
|
+
case 'session':
|
|
126
|
+
ctx.notify(describeSession(ctx.state));
|
|
127
|
+
return true;
|
|
128
|
+
// --- recognized builtins with no Phase-4 wire path --------------------
|
|
129
|
+
case 'copy':
|
|
130
|
+
case 'changelog':
|
|
131
|
+
case 'hotkeys':
|
|
132
|
+
case 'scoped-models':
|
|
133
|
+
case 'clone':
|
|
134
|
+
ctx.notify(`/${name} is not available in attach (Phase 4)`);
|
|
135
|
+
return true;
|
|
136
|
+
default:
|
|
137
|
+
// A builtin we listed but didn't special-case: treat as unsupported
|
|
138
|
+
// rather than leak it to the LLM as literal text.
|
|
139
|
+
ctx.notify(`/${name} is not available in attach (Phase 4)`);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Build slash-command autocomplete entries from the merged command list (the
|
|
144
|
+
* broker's `get_commands` result, which T7 may inject) — defaults to the
|
|
145
|
+
* vendored builtins. Shaped for pi-tui's `CombinedAutocompleteProvider`. */
|
|
146
|
+
export function slashCommandList(commands = BUILTIN_SLASH_COMMANDS) {
|
|
147
|
+
return commands.map((c) => ({ name: c.name, description: c.description }));
|
|
148
|
+
}
|
|
149
|
+
/** Same list as flat autocomplete items (value/label/description). */
|
|
150
|
+
export function commandAutocompleteItems(commands = BUILTIN_SLASH_COMMANDS) {
|
|
151
|
+
return commands.map((c) => ({ value: `/${c.name}`, label: `/${c.name}`, description: c.description }));
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
function usage(ctx, form) {
|
|
155
|
+
ctx.notify(`Usage: ${form}`);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
function pickerHint(ctx, what, form) {
|
|
159
|
+
ctx.notify(`Interactive ${what} picker isn't available over view.sock yet — use ${form}`);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
function handleSettings(arg, ctx) {
|
|
163
|
+
const [sub, ...rest] = arg.split(/\s+/);
|
|
164
|
+
const value = rest.join(' ').trim();
|
|
165
|
+
switch (sub) {
|
|
166
|
+
case 'thinking':
|
|
167
|
+
if (!value)
|
|
168
|
+
return usage(ctx, '/settings thinking <level>');
|
|
169
|
+
if (!THINKING_LEVELS.has(value)) {
|
|
170
|
+
ctx.notify(`Invalid thinking level "${value}" — choose one of: ${[...THINKING_LEVELS].join(', ')}`);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
ctx.send({ type: 'set_thinking_level', level: value });
|
|
174
|
+
return true;
|
|
175
|
+
case 'auto-retry':
|
|
176
|
+
ctx.send({ type: 'set_auto_retry', enabled: parseBool(value) });
|
|
177
|
+
return true;
|
|
178
|
+
case 'auto-compaction':
|
|
179
|
+
ctx.send({ type: 'set_auto_compaction', enabled: parseBool(value) });
|
|
180
|
+
return true;
|
|
181
|
+
default:
|
|
182
|
+
ctx.notify('Settings: /settings thinking <level> | auto-retry on|off | auto-compaction on|off ' +
|
|
183
|
+
'(the interactive settings menu needs data not exposed over view.sock in Phase 4)');
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function parseBool(value) {
|
|
188
|
+
return /^(on|true|yes|1|enabled?)$/i.test(value.trim());
|
|
189
|
+
}
|
|
190
|
+
function parseExport(arg, cwd) {
|
|
191
|
+
const path = arg || `${cwd}/pi-export-${Date.now()}.html`;
|
|
192
|
+
const format = path.endsWith('.jsonl') ? 'jsonl' : 'html';
|
|
193
|
+
return { path, format };
|
|
194
|
+
}
|
|
195
|
+
function describeSession(state) {
|
|
196
|
+
if (!state)
|
|
197
|
+
return 'Session info unavailable';
|
|
198
|
+
const name = state.sessionName ? `"${state.sessionName}" ` : '';
|
|
199
|
+
return `Session ${name}${state.sessionId} · model ${state.model ?? 'unknown'} · thinking ${state.thinkingLevel}`;
|
|
200
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type BrokerToClient, type ClientToBroker } from '../../core/runtime/broker-protocol.js';
|
|
3
|
+
/** Surfaced when the node has no reachable broker at connect time (no socket
|
|
4
|
+
* file, or a stale socket with nothing listening). The command catches this to
|
|
5
|
+
* exit non-zero with a focus/revive hint. */
|
|
6
|
+
export declare class BrokerUnavailableError extends Error {
|
|
7
|
+
readonly nodeId: string;
|
|
8
|
+
constructor(nodeId: string);
|
|
9
|
+
}
|
|
10
|
+
/** The plan-fixed interface (Wave 3): `connect()`, `on('frame', …)`,
|
|
11
|
+
* `send(frame)`, `on('close', …)`, plus `connect`/`error` events. */
|
|
12
|
+
export interface ViewSocketClient {
|
|
13
|
+
on(event: 'connect', listener: () => void): this;
|
|
14
|
+
on(event: 'frame', listener: (frame: BrokerToClient) => void): this;
|
|
15
|
+
on(event: 'close', listener: () => void): this;
|
|
16
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
17
|
+
once(event: 'connect', listener: () => void): this;
|
|
18
|
+
once(event: 'frame', listener: (frame: BrokerToClient) => void): this;
|
|
19
|
+
once(event: 'close', listener: () => void): this;
|
|
20
|
+
once(event: 'error', listener: (err: Error) => void): this;
|
|
21
|
+
off(event: 'connect', listener: () => void): this;
|
|
22
|
+
off(event: 'error', listener: (err: Error) => void): this;
|
|
23
|
+
}
|
|
24
|
+
export declare class ViewSocketClient extends EventEmitter {
|
|
25
|
+
private readonly nodeId;
|
|
26
|
+
private socket;
|
|
27
|
+
private readonly decoder;
|
|
28
|
+
private closeEmitted;
|
|
29
|
+
constructor(nodeId: string);
|
|
30
|
+
/** The broker binds `join(nodeDir(id), 'view.sock')` — resolve it the same way. */
|
|
31
|
+
get socketPath(): string;
|
|
32
|
+
/** Open the connection. Wire up listeners; emits `connect` on success or
|
|
33
|
+
* `error` (a {@link BrokerUnavailableError} for ECONNREFUSED/ENOENT) on
|
|
34
|
+
* failure. Idempotent guard is the caller's job — call once. */
|
|
35
|
+
connect(): void;
|
|
36
|
+
/** Encode + write one client→broker frame. No-op on a dead/absent socket
|
|
37
|
+
* (a `close` event drives teardown); never throws. */
|
|
38
|
+
send(frame: ClientToBroker): void;
|
|
39
|
+
/** Detach: destroy the socket. `close` fires → the caller tears down. */
|
|
40
|
+
close(): void;
|
|
41
|
+
private onData;
|
|
42
|
+
private onError;
|
|
43
|
+
private onClose;
|
|
44
|
+
private destroy;
|
|
45
|
+
/** Emit `error` only when a listener exists — a bare EventEmitter `error`
|
|
46
|
+
* with no listener throws, and this client must never throw uncaught. */
|
|
47
|
+
private emitError;
|
|
48
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// view-socket.ts — the socket client half of `crtr attach` (Phase 4, T7).
|
|
2
|
+
//
|
|
3
|
+
// The §0 ONE-WRITER INVARIANT lives in this directory: a viewer has ONLY a
|
|
4
|
+
// socket. ViewSocketClient connects to a node's ALREADY-running headless broker
|
|
5
|
+
// over its unix socket (`nodeDir(id)/view.sock`) and speaks the LOCAL
|
|
6
|
+
// broker-protocol codec (`node:net` + the bounded `FrameDecoder`). It NEVER
|
|
7
|
+
// spawns pi, NEVER opens the session `.jsonl`, NEVER constructs an engine — if
|
|
8
|
+
// the broker is not running, connect fails and `attach-cmd` exits (focus/T9 is
|
|
9
|
+
// what keeps a broker alive, not attach). It also never uses pi's `RpcClient`
|
|
10
|
+
// (which would spawn its own engine).
|
|
11
|
+
//
|
|
12
|
+
// No reconnect: a dropped broker means the engine's turn-cycle settled and the
|
|
13
|
+
// broker disposed+exited; a fresh focus re-runs attach. On EOF/close → emit
|
|
14
|
+
// `close` (the caller renders "broker gone" + exits). On a connect-time
|
|
15
|
+
// ECONNREFUSED/ENOENT → a `BrokerUnavailableError` so the command exits non-zero
|
|
16
|
+
// with a clear message. Decode is bounded by `CLIENT_READ_CAPS`; a broker that
|
|
17
|
+
// somehow sends an oversized frame surfaces as a clean error+exit, not a crash.
|
|
18
|
+
import { EventEmitter } from 'node:events';
|
|
19
|
+
import { createConnection } from 'node:net';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { nodeDir } from '../../core/canvas/paths.js';
|
|
22
|
+
import { CLIENT_READ_CAPS, encodeFrame, FrameDecoder, FrameOverflowError, } from '../../core/runtime/broker-protocol.js';
|
|
23
|
+
/** Surfaced when the node has no reachable broker at connect time (no socket
|
|
24
|
+
* file, or a stale socket with nothing listening). The command catches this to
|
|
25
|
+
* exit non-zero with a focus/revive hint. */
|
|
26
|
+
export class BrokerUnavailableError extends Error {
|
|
27
|
+
nodeId;
|
|
28
|
+
constructor(nodeId) {
|
|
29
|
+
super(`node ${nodeId} has no running broker — focus or revive it first`);
|
|
30
|
+
this.nodeId = nodeId;
|
|
31
|
+
this.name = 'BrokerUnavailableError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class ViewSocketClient extends EventEmitter {
|
|
35
|
+
nodeId;
|
|
36
|
+
socket;
|
|
37
|
+
decoder = new FrameDecoder(CLIENT_READ_CAPS);
|
|
38
|
+
closeEmitted = false;
|
|
39
|
+
constructor(nodeId) {
|
|
40
|
+
super();
|
|
41
|
+
this.nodeId = nodeId;
|
|
42
|
+
}
|
|
43
|
+
/** The broker binds `join(nodeDir(id), 'view.sock')` — resolve it the same way. */
|
|
44
|
+
get socketPath() {
|
|
45
|
+
return join(nodeDir(this.nodeId), 'view.sock');
|
|
46
|
+
}
|
|
47
|
+
/** Open the connection. Wire up listeners; emits `connect` on success or
|
|
48
|
+
* `error` (a {@link BrokerUnavailableError} for ECONNREFUSED/ENOENT) on
|
|
49
|
+
* failure. Idempotent guard is the caller's job — call once. */
|
|
50
|
+
connect() {
|
|
51
|
+
const socket = createConnection(this.socketPath);
|
|
52
|
+
this.socket = socket;
|
|
53
|
+
socket.on('connect', () => this.emit('connect'));
|
|
54
|
+
socket.on('data', (chunk) => this.onData(chunk));
|
|
55
|
+
socket.on('error', (err) => this.onError(err));
|
|
56
|
+
socket.on('close', () => this.onClose());
|
|
57
|
+
}
|
|
58
|
+
/** Encode + write one client→broker frame. No-op on a dead/absent socket
|
|
59
|
+
* (a `close` event drives teardown); never throws. */
|
|
60
|
+
send(frame) {
|
|
61
|
+
const sock = this.socket;
|
|
62
|
+
if (sock === undefined || sock.destroyed)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
sock.write(encodeFrame(frame));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* dead socket — 'close' will fire and drive teardown */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Detach: destroy the socket. `close` fires → the caller tears down. */
|
|
72
|
+
close() {
|
|
73
|
+
this.destroy();
|
|
74
|
+
}
|
|
75
|
+
onData(chunk) {
|
|
76
|
+
let frames;
|
|
77
|
+
try {
|
|
78
|
+
frames = this.decoder.push(chunk);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
// A broker that somehow emits an oversized frame (or a corrupt stream):
|
|
82
|
+
// clean error + drop, never a crash.
|
|
83
|
+
const msg = err instanceof FrameOverflowError
|
|
84
|
+
? `broker sent an oversized frame (${err.message}) — disconnecting`
|
|
85
|
+
: `failed to decode a broker frame: ${String(err)}`;
|
|
86
|
+
this.emitError(new Error(msg));
|
|
87
|
+
this.destroy();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const raw of frames) {
|
|
91
|
+
this.emit('frame', raw);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
onError(err) {
|
|
95
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ENOENT') {
|
|
96
|
+
this.emitError(new BrokerUnavailableError(this.nodeId));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.emitError(new Error(`view socket error: ${err.message}`));
|
|
100
|
+
}
|
|
101
|
+
// 'close' typically follows 'error'; teardown converges there.
|
|
102
|
+
}
|
|
103
|
+
onClose() {
|
|
104
|
+
if (this.closeEmitted)
|
|
105
|
+
return;
|
|
106
|
+
this.closeEmitted = true;
|
|
107
|
+
this.emit('close');
|
|
108
|
+
}
|
|
109
|
+
destroy() {
|
|
110
|
+
const sock = this.socket;
|
|
111
|
+
if (sock !== undefined && !sock.destroyed) {
|
|
112
|
+
try {
|
|
113
|
+
sock.destroy();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* ignore */
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Emit `error` only when a listener exists — a bare EventEmitter `error`
|
|
121
|
+
* with no listener throws, and this client must never throw uncaught. */
|
|
122
|
+
emitError(err) {
|
|
123
|
+
if (this.listenerCount('error') > 0)
|
|
124
|
+
this.emit('error', err);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -21,7 +21,7 @@ import { countAsks, pendingAsksForView, asksAcrossCanvas, asksForNodes, } from '
|
|
|
21
21
|
const attentionCount = defineLeaf({
|
|
22
22
|
name: 'count',
|
|
23
23
|
description: 'total pending ask count (machine-parseable stdout.count)',
|
|
24
|
-
whenToUse: 'getting a single pending-ask count for a script or nav chrome to read
|
|
24
|
+
whenToUse: 'getting a single pending-ask count for a script or nav chrome to read; scope it to a node or a sub-DAG, or default to canvas-wide',
|
|
25
25
|
help: {
|
|
26
26
|
name: 'canvas attention count',
|
|
27
27
|
// stdout.count is parsed directly by the nav chrome — keep the contract.
|
|
@@ -93,7 +93,7 @@ const attentionCount = defineLeaf({
|
|
|
93
93
|
const attentionList = defineLeaf({
|
|
94
94
|
name: 'list',
|
|
95
95
|
description: 'itemised list of cwds with pending asks',
|
|
96
|
-
whenToUse: 'finding which agents are blocked waiting on a human — an itemised list of the cwds with pending asks, oldest first, so you know where to go answer. Scope to a sub-DAG
|
|
96
|
+
whenToUse: 'finding which agents are blocked waiting on a human — an itemised list of the cwds with pending asks, oldest first, so you know where to go answer. Scope it to a sub-DAG or list canvas-wide. Use `canvas attention count` instead when a script just needs the number, or `canvas attention map` for per-node counts to label a UI',
|
|
97
97
|
help: {
|
|
98
98
|
name: 'canvas attention list',
|
|
99
99
|
summary: 'list nodes with pending human asks, grouped by cwd, oldest first',
|
|
@@ -211,7 +211,7 @@ const attentionMap = defineLeaf({
|
|
|
211
211
|
export const attentionBranch = defineBranch({
|
|
212
212
|
name: 'attention',
|
|
213
213
|
description: 'count/list pending human asks across the graph',
|
|
214
|
-
whenToUse: 'checking whether any agent on the canvas is blocked waiting on a human, and where: count the pending asks, list the cwds that have them, or map per-node counts. Scope
|
|
214
|
+
whenToUse: 'checking whether any agent on the canvas is blocked waiting on a human, and where: count the pending asks, list the cwds that have them, or map per-node counts. Scope it to a node or a sub-DAG, or go canvas-wide. Use `canvas dashboard` instead for the graph SHAPE, or `node inspect list` for a plain node roster',
|
|
215
215
|
help: {
|
|
216
216
|
name: 'canvas attention',
|
|
217
217
|
summary: 'aggregate pending human asks across the canvas',
|
|
@@ -13,7 +13,7 @@ const DEFAULT_TTL_DAYS = 14;
|
|
|
13
13
|
export const canvasPruneLeaf = defineLeaf({
|
|
14
14
|
name: 'prune',
|
|
15
15
|
description: 'remove terminal nodes (dead/done/canceled) older than a TTL',
|
|
16
|
-
whenToUse: 'you want to bound the canvas\u2019s on-disk growth \u2014 sweep away nodes that are finished (done), crashed (dead), or closed (canceled) and older than a retention window, reclaiming their rows, edges (cascade-deleted by the schema), and `nodes/<id>/` dirs. Run it as an operator/cron chore; live nodes (active/idle) are never touched
|
|
16
|
+
whenToUse: 'you want to bound the canvas\u2019s on-disk growth \u2014 sweep away nodes that are finished (done), crashed (dead), or closed (canceled) and older than a retention window, reclaiming their rows, edges (cascade-deleted by the schema), and `nodes/<id>/` dirs. Run it as an operator/cron chore; live nodes (active/idle) are never touched, and you can preview the sweep before it deletes anything',
|
|
17
17
|
help: {
|
|
18
18
|
name: 'canvas prune',
|
|
19
19
|
summary: 'delete terminal nodes older than a TTL (edges cascade, dirs removed); --dry-run to preview',
|
package/dist/commands/daemon.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// `crtr canvas daemon` — thin supervisor daemon management.
|
|
2
2
|
//
|
|
3
|
-
// The daemon (crtrd) polls active+idle nodes and handles
|
|
4
|
-
//
|
|
3
|
+
// The daemon (crtrd) polls active+idle nodes and handles engine-container exit
|
|
4
|
+
// (a tmux pane or a headless broker process):
|
|
5
|
+
// • crash (container gone, no intent) → mark 'dead'
|
|
5
6
|
// • refresh-yield (intent=refresh) → fresh respawn
|
|
6
7
|
//
|
|
7
8
|
// This subtree starts, checks, and stops the daemon process.
|
|
@@ -132,7 +132,7 @@ export const humanReview = defineLeaf({
|
|
|
132
132
|
outputKind: 'object',
|
|
133
133
|
effects: [
|
|
134
134
|
'Creates a kind:"human" node under you and writes run.json to the interaction dir.',
|
|
135
|
-
'Spawns a live
|
|
135
|
+
'Spawns a live review surface (detached tmux panes when in tmux): the raw source in a read-only editor for anchoring comments, beside a termrender-rendered view of the doc; both track the file and re-render on save.',
|
|
136
136
|
'Returns instantly; the anchored comments fan into your inbox when the human submits (off-tmux, drain via `crtr human inbox`).',
|
|
137
137
|
],
|
|
138
138
|
},
|
|
@@ -8,7 +8,8 @@ import { transition } from '../../core/runtime/lifecycle.js';
|
|
|
8
8
|
import { appendInbox } from '../../core/feed/inbox.js';
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
-
import {
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import { inbox, scanInbox, parseDeck, deckPath, responsePath, isResolved, atomicWriteJson, ask, launchReview, readJson, display, } from '@crouton-kit/humanloop';
|
|
12
13
|
import { killPane } from './shared.js';
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// inbox (human-invoked, blocking)
|
|
@@ -136,6 +137,13 @@ export const humanCancel = defineLeaf({
|
|
|
136
137
|
const rc = readJson(join(idir, 'run.json'));
|
|
137
138
|
if (rc?.pane_id !== undefined && rc.pane_id !== '')
|
|
138
139
|
killPane(rc.pane_id, idir);
|
|
140
|
+
// A review also opened a live termrender render pane beside the editor —
|
|
141
|
+
// kill it too or it outlives the canceled job. Verified against the
|
|
142
|
+
// reviewed file path, which termrender bakes into the pane's start command
|
|
143
|
+
// (`termrender doc watch ... <file>`), so this can only ever hit our pane.
|
|
144
|
+
if (rc?.render_pane_id !== undefined && rc.render_pane_id !== '' && typeof rc.file === 'string' && rc.file !== '') {
|
|
145
|
+
killPane(rc.render_pane_id, rc.file);
|
|
146
|
+
}
|
|
139
147
|
// (2) Drop it from the human queue: a response.json marks the dir resolved,
|
|
140
148
|
// so scanInbox (human list/inbox) skips it.
|
|
141
149
|
if (existsSync(idir)) {
|
|
@@ -218,13 +226,51 @@ export const humanRun = defineLeaf({
|
|
|
218
226
|
else if (rc.mode === 'review') {
|
|
219
227
|
// The _run worker is already its own dedicated tmux pane with a TTY, so
|
|
220
228
|
// run nvim directly in it (noTmux) instead of letting launchReview
|
|
221
|
-
// split off a SECOND pane and sit polling.
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
// split off a SECOND pane and sit polling.
|
|
230
|
+
//
|
|
231
|
+
// BUG REGRESSION (raw-markdown review): the nvim buffer must stay the
|
|
232
|
+
// RAW source — anchored comments hang off source line numbers — so the
|
|
233
|
+
// termrender render the help promises (panels/callouts/mermaid) lives
|
|
234
|
+
// in its OWN live pane opened beside this worker. `display` spawns the
|
|
235
|
+
// managed termrender binary in watch mode, so it re-renders on every
|
|
236
|
+
// save exactly like the nvim buffer reloads. Best-effort: off-tmux or
|
|
237
|
+
// renderer-unavailable degrades to editor-only. The pane id is merged
|
|
238
|
+
// into run.json so `human cancel` can kill it; the finally clears it on
|
|
239
|
+
// every exit of the editor (submit, quit, or failure).
|
|
240
|
+
let renderPane;
|
|
241
|
+
try {
|
|
242
|
+
renderPane = display(rc.file, { window: 'split' }).paneId;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
/* render pane is best-effort; the review itself must not die */
|
|
246
|
+
}
|
|
247
|
+
// Dock the render pane BESIDE this worker pane. tmux resolves an
|
|
248
|
+
// untargeted split-window against the attached client's current window
|
|
249
|
+
// (which wins over $TMUX_PANE), so the pane can land in whatever window
|
|
250
|
+
// the user happens to be looking at — away from the editor. move-pane
|
|
251
|
+
// with explicit src/dst is deterministic: raw source and rendered doc
|
|
252
|
+
// always sit side by side.
|
|
253
|
+
const selfPane = process.env['TMUX_PANE'];
|
|
254
|
+
if (renderPane !== undefined && selfPane !== undefined && selfPane !== '') {
|
|
255
|
+
spawnSync('tmux', ['move-pane', '-h', '-s', renderPane, '-t', selfPane], { stdio: 'ignore' });
|
|
256
|
+
}
|
|
257
|
+
if (renderPane !== undefined) {
|
|
258
|
+
const rcPath = join(dir, 'run.json');
|
|
259
|
+
const cur = readJson(rcPath);
|
|
260
|
+
if (cur !== null)
|
|
261
|
+
atomicWriteJson(rcPath, { ...cur, render_pane_id: renderPane });
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const res = await launchReview(rc.file, {
|
|
265
|
+
output: rc.output,
|
|
266
|
+
noTmux: true,
|
|
267
|
+
});
|
|
268
|
+
await pushFinal(rc.job_id, JSON.stringify(res));
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
if (renderPane !== undefined)
|
|
272
|
+
killPane(renderPane, rc.file);
|
|
273
|
+
}
|
|
228
274
|
}
|
|
229
275
|
}
|
|
230
276
|
catch (e) {
|
|
@@ -8,6 +8,8 @@ export interface RunRecord {
|
|
|
8
8
|
output?: string;
|
|
9
9
|
/** tmux pane id of the detached TUI, recorded so `human cancel` can kill it. */
|
|
10
10
|
pane_id?: string;
|
|
11
|
+
/** tmux pane id of a review's live termrender render pane, so cancel/finish can clear it. */
|
|
12
|
+
render_pane_id?: string;
|
|
11
13
|
}
|
|
12
14
|
export declare function resolveMaxPanes(): number;
|
|
13
15
|
export declare function pickPlacement(targetPane?: string): 'split-h' | 'new-window';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const findLeaf: import("../../core/command.js").LeafDef;
|