@crouton-kit/crouter 0.3.17 → 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/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/design/orchestrator.md +1 -1
- package/dist/builtin-personas/developer/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/developer/orchestrator.md +1 -1
- package/dist/builtin-personas/explore/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/general/{base.md → PERSONA.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/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/plan/orchestrator.md +1 -1
- package/dist/builtin-personas/plan/reviewers/architecture-fit/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/code-smells/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/security/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/review/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/{base.md → PERSONA.md} +4 -0
- 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/SKILL.md +24 -14
- package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +4 -4
- 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 +4 -3
- package/dist/commands/human/prompts.js +4 -10
- package/dist/commands/human/queue.js +43 -8
- package/dist/commands/human/shared.d.ts +28 -1
- package/dist/commands/human/shared.js +48 -10
- 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 +660 -65
- 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__/human-surface-target.test.d.ts +1 -0
- package/dist/core/__tests__/human-surface-target.test.js +98 -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__/persona-subkind.test.js +18 -15
- 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/paths.d.ts +4 -1
- package/dist/core/canvas/paths.js +10 -4
- 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/types.js +2 -2
- 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/help.d.ts +6 -0
- package/dist/core/help.js +7 -0
- package/dist/core/memory-resolver.d.ts +49 -0
- package/dist/core/memory-resolver.js +141 -0
- package/dist/core/personas/index.d.ts +4 -3
- package/dist/core/personas/index.js +3 -2
- package/dist/core/personas/loader.d.ts +41 -16
- package/dist/core/personas/loader.js +133 -29
- package/dist/core/personas/resolve.d.ts +4 -4
- package/dist/core/personas/resolve.js +28 -16
- 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 +42 -14
- package/dist/core/runtime/placement.js +228 -38
- 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 +25 -1
- package/dist/core/spawn.js +72 -7
- 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 +145 -34
- 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
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
import { existsSync, rmSync } from 'node:fs';
|
|
23
23
|
import { getNode, updateNode, setPresence, clearPid, setFocusOccupant, subscriptionsOf, unsubscribe, view, reportsDir, inboxPath, nodeDir, openDb, } from '../canvas/index.js';
|
|
24
24
|
import { transition } from './lifecycle.js';
|
|
25
|
-
import {
|
|
25
|
+
import { hostFor } from './host.js';
|
|
26
|
+
import { paneLocation, focusOf } from './placement.js';
|
|
26
27
|
import { buildLaunchSpec } from './launch.js';
|
|
27
28
|
import { roadmapPath } from './roadmap.js';
|
|
28
29
|
import { spawnNode, newNodeId, nodeSession } from './nodes.js';
|
|
@@ -49,11 +50,12 @@ export function reapDescendants(rootId) {
|
|
|
49
50
|
try {
|
|
50
51
|
// Reap BEFORE tearing down the placement (the crash-safety invariant the
|
|
51
52
|
// `cancel` event encodes): a non-supervised status + cleared intent first, so
|
|
52
|
-
// the daemon can't revive a descendant mid-teardown.
|
|
53
|
-
// closes any focus row it held,
|
|
54
|
-
// LOCATION
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
// the daemon can't revive a descendant mid-teardown. Teardown then routes
|
|
54
|
+
// through the Host seam (hostFor): a tmux node closes any focus row it held,
|
|
55
|
+
// kills its pane (pane-keyed), and nulls its LOCATION; a broker node sends
|
|
56
|
+
// the `shutdown` frame so its PROCESS exits and releases the .jsonl writer.
|
|
57
|
+
const m = transition(id, 'cancel');
|
|
58
|
+
hostFor(m).teardown(id);
|
|
57
59
|
reaped.push(id);
|
|
58
60
|
}
|
|
59
61
|
catch {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type NodeMeta } from '../canvas/index.js';
|
|
2
|
+
import type { WakeOrigin } from './bearings.js';
|
|
2
3
|
import { type RespawnPaneOpts } from './placement.js';
|
|
3
4
|
/** Pick the `--session` source for a revive. resume=true prefers the absolute
|
|
4
5
|
* session-file path (immune to cwd; pi opens it directly) and keeps the bare
|
|
@@ -25,6 +26,7 @@ export interface ReviveResult {
|
|
|
25
26
|
*/
|
|
26
27
|
export declare function reviveNode(nodeId: string, opts: {
|
|
27
28
|
resume: boolean;
|
|
29
|
+
wakeReason?: WakeOrigin;
|
|
28
30
|
}): ReviveResult;
|
|
29
31
|
/** Re-exec a node's pi FRESH in its EXISTING tmux pane (the refresh-yield
|
|
30
32
|
* path). Unlike `reviveNode`, this opens no new window: the pane's current pi
|
|
@@ -14,26 +14,15 @@
|
|
|
14
14
|
// preferring the absolute session-file path (cwd-immune) over
|
|
15
15
|
// the bare session id.
|
|
16
16
|
// resume=false → fresh pi invocation — the node re-reads its roadmap/context dir.
|
|
17
|
-
import { getNode, updateNode, setPresence, clearPid, fullName, } from '../canvas/index.js';
|
|
17
|
+
import { getNode, updateNode, setPresence, clearPid, fullName, cancelDeadlinesFor, } from '../canvas/index.js';
|
|
18
18
|
import { transition } from './lifecycle.js';
|
|
19
19
|
import { buildPiArgv } from './launch.js';
|
|
20
20
|
import { buildReviveKickoff, drainBearings } from './kickoff.js';
|
|
21
21
|
import { FRONT_DOOR_ENV } from './front-door.js';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
function pidAlive(pid) {
|
|
27
|
-
if (pid == null)
|
|
28
|
-
return false;
|
|
29
|
-
try {
|
|
30
|
-
process.kill(pid, 0);
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
catch (e) {
|
|
34
|
-
return e.code === 'EPERM';
|
|
35
|
-
}
|
|
36
|
-
}
|
|
22
|
+
import { reconcile, piCommand, respawnPane, } from './placement.js';
|
|
23
|
+
import { hostFor } from './host.js';
|
|
24
|
+
import { nodeSession, childBackstageOf, rootOfSpine } from './nodes.js';
|
|
25
|
+
import { isPidAlive } from '../canvas/pid.js';
|
|
37
26
|
// ---------------------------------------------------------------------------
|
|
38
27
|
// resumeArgs — which session source a revive resumes from
|
|
39
28
|
// ---------------------------------------------------------------------------
|
|
@@ -73,7 +62,7 @@ export function reviveNode(nodeId, opts) {
|
|
|
73
62
|
// frozen pane), so the guard gates on pi liveness too, not pane-existence alone.
|
|
74
63
|
reconcile(nodeId);
|
|
75
64
|
const live = getNode(nodeId) ?? meta;
|
|
76
|
-
if (
|
|
65
|
+
if (hostFor(live).isAlive(nodeId) && isPidAlive(live.pi_pid)) {
|
|
77
66
|
return {
|
|
78
67
|
window: live.window ?? null,
|
|
79
68
|
session: live.tmux_session ?? nodeSession(),
|
|
@@ -98,8 +87,12 @@ export function reviveNode(nodeId, opts) {
|
|
|
98
87
|
inv = buildPiArgv(meta, resume);
|
|
99
88
|
}
|
|
100
89
|
else {
|
|
90
|
+
// A fresh revive: drain the one-shot bearings, then build the kickoff. When
|
|
91
|
+
// a scheduled bare self-alarm drove this revive, opts.wakeReason carries the
|
|
92
|
+
// wake provenance so the kickoff leads with a <crtr-wake> block ("a timer
|
|
93
|
+
// woke you"); every other reviveNode caller passes nothing → no block.
|
|
101
94
|
const bearings = drainBearings(meta);
|
|
102
|
-
inv = buildPiArgv(meta, { prompt: buildReviveKickoff(meta, bearings) });
|
|
95
|
+
inv = buildPiArgv(meta, { prompt: buildReviveKickoff(meta, bearings, opts.wakeReason) });
|
|
103
96
|
}
|
|
104
97
|
// Placement owns WHERE this revive lands (§1.4): resume into a live focus pane
|
|
105
98
|
// if the node occupies one, else a fresh window in its home_session (the
|
|
@@ -108,9 +101,14 @@ export function reviveNode(nodeId, opts) {
|
|
|
108
101
|
// around it (the crash-safety ordering, unchanged). THIS is the bug-kill: a
|
|
109
102
|
// non-focused background revive can no longer new-window into a user session.
|
|
110
103
|
transition(nodeId, 'revive');
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
// Cancel-on-wake (design §6.4, AC-E1): every revive-for-any-reason (an inbox
|
|
105
|
+
// event, a different wake, a manual focus) drops this node's pending deadline,
|
|
106
|
+
// so the deadline always belongs to the dormancy being left. Writes only the
|
|
107
|
+
// wakeups table, after the atomic transition above. (reviveInPlace's
|
|
108
|
+
// transition('boot') needs NO hook — a refresh is the same node continuing,
|
|
109
|
+
// not leaving a dormancy.)
|
|
110
|
+
cancelDeadlinesFor(nodeId);
|
|
111
|
+
const placed = hostFor(meta).launch(nodeId, inv, {
|
|
114
112
|
cwd: meta.cwd,
|
|
115
113
|
name: fullName(meta),
|
|
116
114
|
resuming,
|
|
@@ -150,14 +148,16 @@ export function reviveInPlace(nodeId, pane, respawn = respawnPane) {
|
|
|
150
148
|
// then build purely.
|
|
151
149
|
const bearings = drainBearings(meta);
|
|
152
150
|
const inv = buildPiArgv(meta, { prompt: buildReviveKickoff(meta, bearings) });
|
|
153
|
-
// CRTR_ROOT_SESSION is the backstage this node's CHILDREN spawn into —
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// session
|
|
160
|
-
|
|
151
|
+
// CRTR_ROOT_SESSION is the backstage this node's CHILDREN spawn into — NOT the
|
|
152
|
+
// pane's live `session`. A FOCUSED child's pane is in a USER session (focus
|
|
153
|
+
// taints meta.tmux_session), so sourcing it from `session` would land any
|
|
154
|
+
// child it spawns in the user's session, re-tainting that child's
|
|
155
|
+
// home_session (A-MAJOR-1). childBackstageOf is the taint-immune backstage:
|
|
156
|
+
// home_session for a managed child, but `nodeSession()` for a ROOT (whose
|
|
157
|
+
// home_session may itself be a user session it adopted via the inline front
|
|
158
|
+
// door — sourcing children from it would re-point the root's whole subtree
|
|
159
|
+
// into the user's session on every refresh-yield).
|
|
160
|
+
const env = { ...inv.env, CRTR_ROOT_SESSION: childBackstageOf(nodeId), CRTR_SUBTREE: rootOfSpine(nodeId) };
|
|
161
161
|
const ok = respawn({ pane, cwd: meta.cwd, env, command: piCommand(inv.argv) });
|
|
162
162
|
if (!ok) {
|
|
163
163
|
throw new Error(`reviveInPlace: respawn-pane dispatch failed for ${nodeId}`);
|
|
@@ -199,11 +199,11 @@ export function relaunchRootInPane(nodeId, pane) {
|
|
|
199
199
|
}
|
|
200
200
|
// No prompt, no resume → a brand-new root conversation at cycle 0.
|
|
201
201
|
const inv = buildPiArgv(meta, {});
|
|
202
|
-
// Source CRTR_ROOT_SESSION from the
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// —
|
|
206
|
-
const env = { ...inv.env, CRTR_ROOT_SESSION:
|
|
202
|
+
// Source CRTR_ROOT_SESSION from childBackstageOf, the same backstage rule as
|
|
203
|
+
// reviveInPlace. relaunchRootInPane runs only on a root, whose children must
|
|
204
|
+
// flow to the shared backstage `nodeSession()` — never the root's own adopted
|
|
205
|
+
// user session — so both in-pane revive paths stay sourced identically.
|
|
206
|
+
const env = { ...inv.env, CRTR_ROOT_SESSION: childBackstageOf(nodeId), CRTR_SUBTREE: rootOfSpine(nodeId), [FRONT_DOOR_ENV]: '1' };
|
|
207
207
|
const ok = respawnPane({ pane, cwd: meta.cwd, env, command: piCommand(inv.argv) });
|
|
208
208
|
if (!ok) {
|
|
209
209
|
throw new Error(`relaunchRootInPane: respawn-pane dispatch failed for ${nodeId}`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WakeOrigin } from './bearings.js';
|
|
1
2
|
import { type NodeMeta, type Mode } from '../canvas/index.js';
|
|
2
3
|
export interface BootRootOpts {
|
|
3
4
|
cwd: string;
|
|
@@ -27,6 +28,17 @@ export interface SpawnChildOpts {
|
|
|
27
28
|
* new session for the child — the source is untouched — then `prompt` is the
|
|
28
29
|
* next message. A one-shot at birth; the child resumes its own session after. */
|
|
29
30
|
forkFrom?: string;
|
|
31
|
+
/** Set ONLY by the daemon when a `spawn`/spawn-cron wake births this node: the
|
|
32
|
+
* provenance of the timer that fired (see WakeOrigin). In-memory only — it is
|
|
33
|
+
* NOT part of the stored recipe (the daemon spreads it in at fire time via
|
|
34
|
+
* `spawnChild({ ...recipe, wakeOrigin })`); `node new` never sets it. When set,
|
|
35
|
+
* a <crtr-wake> block is prepended to the kickoff so the newborn knows a clock
|
|
36
|
+
* birthed it. */
|
|
37
|
+
wakeOrigin?: WakeOrigin;
|
|
38
|
+
/** Which HOST launches + supervises this node: a tmux pane (default) or the
|
|
39
|
+
* headless broker. Persisted as `host_kind` at birth (resolved from
|
|
40
|
+
* `--headless` / the `headless` config default by the caller). */
|
|
41
|
+
hostKind?: 'tmux' | 'broker';
|
|
30
42
|
}
|
|
31
43
|
/** Resolve a `--fork-from` value to the source pi gets as `--fork <path|id>`.
|
|
32
44
|
* A live node id resolves to its captured session FILE (absolute, cwd-immune),
|
|
@@ -38,6 +50,13 @@ export interface SpawnChildResult {
|
|
|
38
50
|
window: string | null;
|
|
39
51
|
session: string;
|
|
40
52
|
}
|
|
53
|
+
/** Resolve who a spawn is attributed to. A managed child needs a spine parent
|
|
54
|
+
* (explicit `--parent` or the calling node's CRTR_NODE_ID). A --root spawn
|
|
55
|
+
* does not: it is top-level by definition and the spawner identity is
|
|
56
|
+
* provenance only — a human shell with no CRTR_NODE_ID is a legitimate root
|
|
57
|
+
* spawner (regression: `crtr node new --root` from outside a node used to
|
|
58
|
+
* throw here). */
|
|
59
|
+
export declare function resolveSpawner(parent: string | undefined, ctxNodeId: string | null, root: boolean): string | null;
|
|
41
60
|
/** Spawn a node from a live node. By default a managed terminal worker in a
|
|
42
61
|
* background window, with the spawner auto-subscribed (active) via spawnNode.
|
|
43
62
|
* With `root`: an independent resident root — parent=null, NO subscription back
|
|
@@ -10,16 +10,17 @@
|
|
|
10
10
|
// provenance via spawned_by) brought forefront for direct driving.
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
import { FRONT_DOOR_ENV } from './front-door.js';
|
|
13
|
-
import { spawnNode, currentNodeContext, resolveBirthSession, nodeSession } from './nodes.js';
|
|
13
|
+
import { spawnNode, currentNodeContext, resolveBirthSession, nodeSession, rootOfSpine } from './nodes.js';
|
|
14
14
|
import { buildLaunchSpec, buildPiArgv } from './launch.js';
|
|
15
15
|
import { writeGoal } from './kickoff.js';
|
|
16
16
|
import { hasRoadmap, seedRoadmap } from './roadmap.js';
|
|
17
|
-
import { seedMemory, seedUserMemory, seedProjectMemory } from './memory.js';
|
|
18
17
|
import { generateSessionName } from './naming.js';
|
|
19
|
-
import {
|
|
18
|
+
import { buildIdentityAssertion, buildWakeBearings } from './bearings.js';
|
|
19
|
+
import { installMenuBinding, installNavBindings, installViewNavBindings } from './tmux-chrome.js';
|
|
20
20
|
import { setPresence, updateNode, getNode, fullName } from '../canvas/index.js';
|
|
21
21
|
import { registerRootFocus, ensureSession, openNodeWindow, piCommand, currentTmux, inTmux, focusWindow, } from './placement.js';
|
|
22
22
|
import { transition } from './lifecycle.js';
|
|
23
|
+
import { headlessBrokerHost } from './host.js';
|
|
23
24
|
import { ensureDaemon } from '../../daemon/manage.js';
|
|
24
25
|
/** Create a root node and bring up its pi. Returns the node; for 'inline' this
|
|
25
26
|
* only returns after pi exits (it took over the terminal). */
|
|
@@ -57,8 +58,8 @@ export function bootRoot(opts) {
|
|
|
57
58
|
// Every node window — root or child — lives in the one shared session.
|
|
58
59
|
const session = nodeSession();
|
|
59
60
|
ensureSession(session, opts.cwd);
|
|
60
|
-
// Make the Alt+C action menu + Alt+] / Alt+[ nav keys
|
|
61
|
-
// (idempotent, in-tmux only).
|
|
61
|
+
// Make the Alt+C action menu + Alt+] / Alt+[ node-nav keys + Alt+V then ]/[
|
|
62
|
+
// view-nav chord live on this server (idempotent, in-tmux only).
|
|
62
63
|
if (inTmux()) {
|
|
63
64
|
try {
|
|
64
65
|
installMenuBinding();
|
|
@@ -68,6 +69,10 @@ export function bootRoot(opts) {
|
|
|
68
69
|
installNavBindings();
|
|
69
70
|
}
|
|
70
71
|
catch { /* best-effort */ }
|
|
72
|
+
try {
|
|
73
|
+
installViewNavBindings();
|
|
74
|
+
}
|
|
75
|
+
catch { /* best-effort */ }
|
|
71
76
|
}
|
|
72
77
|
// inline: the root's pi takes over THIS terminal, so its own window stays
|
|
73
78
|
// where the user is (its tmux_session tracks that real pane so supervision
|
|
@@ -93,7 +98,7 @@ export function bootRoot(opts) {
|
|
|
93
98
|
}
|
|
94
99
|
const withSession = getNode(meta.node_id);
|
|
95
100
|
const inv = buildPiArgv(withSession, { prompt: opts.prompt });
|
|
96
|
-
const env = { ...process.env, ...inv.env, CRTR_ROOT_SESSION: session, [FRONT_DOOR_ENV]: '1' };
|
|
101
|
+
const env = { ...process.env, ...inv.env, CRTR_ROOT_SESSION: session, CRTR_SUBTREE: rootOfSpine(meta.node_id), [FRONT_DOOR_ENV]: '1' };
|
|
97
102
|
const r = spawnSync('pi', inv.argv, { stdio: 'inherit', env });
|
|
98
103
|
process.exit(r.status ?? 0);
|
|
99
104
|
}
|
|
@@ -120,6 +125,19 @@ export function resolveForkSource(value) {
|
|
|
120
125
|
// Not a known node — treat as a bare/partial pi session id for pi to resolve.
|
|
121
126
|
return v;
|
|
122
127
|
}
|
|
128
|
+
/** Resolve who a spawn is attributed to. A managed child needs a spine parent
|
|
129
|
+
* (explicit `--parent` or the calling node's CRTR_NODE_ID). A --root spawn
|
|
130
|
+
* does not: it is top-level by definition and the spawner identity is
|
|
131
|
+
* provenance only — a human shell with no CRTR_NODE_ID is a legitimate root
|
|
132
|
+
* spawner (regression: `crtr node new --root` from outside a node used to
|
|
133
|
+
* throw here). */
|
|
134
|
+
export function resolveSpawner(parent, ctxNodeId, root) {
|
|
135
|
+
const spawner = parent ?? ctxNodeId ?? null;
|
|
136
|
+
if (!root && spawner === null) {
|
|
137
|
+
throw new Error('spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent');
|
|
138
|
+
}
|
|
139
|
+
return spawner;
|
|
140
|
+
}
|
|
123
141
|
/** Spawn a node from a live node. By default a managed terminal worker in a
|
|
124
142
|
* background window, with the spawner auto-subscribed (active) via spawnNode.
|
|
125
143
|
* With `root`: an independent resident root — parent=null, NO subscription back
|
|
@@ -131,11 +149,8 @@ export function spawnChild(opts) {
|
|
|
131
149
|
}
|
|
132
150
|
catch { /* daemon is best-effort */ }
|
|
133
151
|
const ctx = currentNodeContext();
|
|
134
|
-
const spawner = opts.parent ?? ctx.nodeId;
|
|
135
|
-
if (spawner === null || spawner === undefined) {
|
|
136
|
-
throw new Error('spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent');
|
|
137
|
-
}
|
|
138
152
|
const root = opts.root === true;
|
|
153
|
+
const spawner = resolveSpawner(opts.parent, ctx.nodeId ?? null, root);
|
|
139
154
|
const mode = opts.mode ?? 'base';
|
|
140
155
|
// Lifecycle keys on ROOT-ness only, independent of mode: an independent root
|
|
141
156
|
// (or `--root`) is resident (a conversation that persists, woken by inbox/
|
|
@@ -156,9 +171,15 @@ export function spawnChild(opts) {
|
|
|
156
171
|
name: opts.name ?? opts.kind,
|
|
157
172
|
description: generateSessionName(opts.prompt),
|
|
158
173
|
// A root has no spine parent (top-level, nobody subscribes); it still
|
|
159
|
-
// records spawned_by=spawner
|
|
174
|
+
// records spawned_by=spawner when a node (not a human shell) spawned it.
|
|
175
|
+
// A child's parent IS its manager.
|
|
160
176
|
parent: root ? null : spawner,
|
|
161
|
-
spawnedBy: root ? spawner : undefined,
|
|
177
|
+
spawnedBy: root ? (spawner ?? undefined) : undefined,
|
|
178
|
+
// Persist the RAW fork reference (not the resolved path) as provenance, so
|
|
179
|
+
// the boot intro can detect this is a fork and re-assert the node's own
|
|
180
|
+
// identity over the source's copied-in conversation.
|
|
181
|
+
forkFrom: opts.forkFrom,
|
|
182
|
+
hostKind: opts.hostKind,
|
|
162
183
|
launch,
|
|
163
184
|
});
|
|
164
185
|
// Persist the task as the child's goal for a fresh revive to re-read.
|
|
@@ -168,6 +189,19 @@ export function spawnChild(opts) {
|
|
|
168
189
|
// — not in buildPiArgv — so a bad reference fails the spawn loudly before any
|
|
169
190
|
// window opens, rather than after pi is already booting.
|
|
170
191
|
const forkFrom = opts.forkFrom !== undefined ? resolveForkSource(opts.forkFrom) : undefined;
|
|
192
|
+
// A fork inherits the SOURCE node's entire first-person conversation, so the
|
|
193
|
+
// identity re-assertion must ride the STRONGEST channel too, not only the
|
|
194
|
+
// session-start bearings (a trailing custom_message the model weighs only by
|
|
195
|
+
// recency). Prepend it to the kickoff PROMPT — the message that actually
|
|
196
|
+
// triggers the fork's first turn, so the model acts on "you are node X, a
|
|
197
|
+
// FORK of <source>, NOT them" as live instruction. When the daemon births this
|
|
198
|
+
// node from a scheduled `spawn` wake, the <crtr-wake> provenance block leads
|
|
199
|
+
// (so "a timer birthed you" precedes the task) — mirroring the fork-identity
|
|
200
|
+
// prepend. Only the pi argv prompt is reframed; the persisted goal (writeGoal
|
|
201
|
+
// above) keeps the raw task.
|
|
202
|
+
const wakeBlock = opts.wakeOrigin !== undefined ? `${buildWakeBearings(opts.wakeOrigin)}\n\n` : '';
|
|
203
|
+
const idBlock = forkFrom !== undefined ? `${buildIdentityAssertion(meta.node_id)}\n\n` : '';
|
|
204
|
+
const kickoff = `${wakeBlock}${idBlock}${opts.prompt}`;
|
|
171
205
|
// A child created DIRECTLY as an orchestrator (mode='orchestrator') boots
|
|
172
206
|
// with the orchestrator persona but bypasses promote(), which is where a
|
|
173
207
|
// roadmap scaffold would normally be seeded. Lay one down here (goal
|
|
@@ -176,14 +210,8 @@ export function spawnChild(opts) {
|
|
|
176
210
|
if (mode === 'orchestrator' && !hasRoadmap(meta.node_id)) {
|
|
177
211
|
seedRoadmap(meta.node_id, { goal: opts.prompt.trim() });
|
|
178
212
|
}
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
// (keyed off the child's cwd), and node-local. Each guarded against clobber.
|
|
182
|
-
if (mode === 'orchestrator') {
|
|
183
|
-
seedUserMemory();
|
|
184
|
-
seedProjectMemory(opts.cwd);
|
|
185
|
-
seedMemory(meta.node_id);
|
|
186
|
-
}
|
|
213
|
+
// (The three scoped long-term memory stores are seeded for EVERY node at birth
|
|
214
|
+
// in spawnNode — no orchestrator-gated seeding needed here.)
|
|
187
215
|
// A managed CHILD lands in the shared global session: inherited from the
|
|
188
216
|
// parent's CRTR_ROOT_SESSION, else the default node session. A --root spawned
|
|
189
217
|
// from inside tmux instead opens its window in the CALLER'S CURRENT session,
|
|
@@ -205,8 +233,33 @@ export function spawnChild(opts) {
|
|
|
205
233
|
// backstage, never a user session — this is what keeps a background revive
|
|
206
234
|
// off the user's screen (the focus taint cannot reach it).
|
|
207
235
|
updateNode(meta.node_id, { home_session: session });
|
|
208
|
-
const inv = buildPiArgv(meta, { prompt:
|
|
209
|
-
|
|
236
|
+
const inv = buildPiArgv(meta, { prompt: kickoff, forkFrom });
|
|
237
|
+
// Belt-and-suspenders backstage routing emitted at EVERY launch site: the
|
|
238
|
+
// authoritative CRTR_ROOT_SESSION (this subtree's shared backstage) +
|
|
239
|
+
// CRTR_SUBTREE (this node's spine root). Hoisted ABOVE the host branch so the
|
|
240
|
+
// broker launch and the tmux command consume ONE authoritative env — the
|
|
241
|
+
// broker host merges inv.env, so a --headless --root broker would otherwise
|
|
242
|
+
// inherit the SPAWNER's subtree id via the lossy nodeEnv passthrough (review
|
|
243
|
+
// reuse MINOR-2). FRONT_DOOR is added per-consumer below (the broker host sets
|
|
244
|
+
// it itself; the tmux env const adds it).
|
|
245
|
+
inv.env = { ...inv.env, CRTR_ROOT_SESSION: childSession, CRTR_SUBTREE: rootOfSpine(meta.node_id) };
|
|
246
|
+
// Birth LAUNCH branch on the PERSISTED host_kind (set at birth by spawnNode).
|
|
247
|
+
// A broker birth diverts AWAY from the tmux pane: the headless broker host
|
|
248
|
+
// launches a detached broker process (which records its own pid as pi_pid via
|
|
249
|
+
// the stophook) and returns placement fields all null — so we open NO tmux
|
|
250
|
+
// window and write NO tmux placement (presence stays null for a broker). The
|
|
251
|
+
// tmux path below is left completely UNCHANGED (byte-identical) for every
|
|
252
|
+
// non-broker spawn. Mirrors reviveNode's `hostFor(meta).launch(...)` shape.
|
|
253
|
+
if (meta.host_kind === 'broker') {
|
|
254
|
+
const placed = headlessBrokerHost.launch(meta.node_id, inv, {
|
|
255
|
+
cwd: meta.cwd,
|
|
256
|
+
name: fullName(meta),
|
|
257
|
+
resuming: false,
|
|
258
|
+
});
|
|
259
|
+
const saved = getNode(meta.node_id);
|
|
260
|
+
return { node: saved, window: placed.window, session: placed.session };
|
|
261
|
+
}
|
|
262
|
+
const env = { ...inv.env, [FRONT_DOOR_ENV]: '1' };
|
|
210
263
|
const command = piCommand(inv.argv);
|
|
211
264
|
// openNodeWindow now returns {window, pane}; pane is unused until the
|
|
212
265
|
// placement layer lands, so destructure the window and proceed unchanged.
|
|
@@ -6,7 +6,7 @@ export interface StopSignals {
|
|
|
6
6
|
}
|
|
7
7
|
export type StopAction = {
|
|
8
8
|
action: 'allow';
|
|
9
|
-
reason: 'awaiting' | 'finished' | 'escalated' | 'dormant';
|
|
9
|
+
reason: 'awaiting' | 'scheduled' | 'finished' | 'escalated' | 'dormant';
|
|
10
10
|
} | {
|
|
11
11
|
action: 'reprompt';
|
|
12
12
|
reason: 'stalled';
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// Also fine.
|
|
17
17
|
// • otherwise → a TERMINAL node with nothing live to wait for and no
|
|
18
18
|
// final pushed. Re-prompt it to finish or escalate.
|
|
19
|
-
import { hasActiveLiveSubscription, getNode } from '../canvas/index.js';
|
|
19
|
+
import { hasActiveLiveSubscription, hasPendingSelfWake, getNode } from '../canvas/index.js';
|
|
20
20
|
export const STALL_REPROMPT = "You've stopped but you're not waiting on anyone and haven't finished. " +
|
|
21
21
|
'Run `crtr push final "<result>"` if the work is done, or `crtr human ask` if you are blocked or need the user.';
|
|
22
22
|
/** Decide what to do when a node stops. Pure given the canvas + this turn's
|
|
@@ -35,6 +35,11 @@ export function evaluateStop(nodeId, signals) {
|
|
|
35
35
|
if (node !== null && node.lifecycle === 'resident') {
|
|
36
36
|
return { action: 'allow', reason: 'dormant' };
|
|
37
37
|
}
|
|
38
|
+
// A terminal node with a pending self-scheduled wakeup is legitimately
|
|
39
|
+
// waiting on the clock: the daemon's wakeup sweep resumes it when the wake
|
|
40
|
+
// fires, so it should idle-release rather than busy-wait holding pi/window.
|
|
41
|
+
if (hasPendingSelfWake(nodeId))
|
|
42
|
+
return { action: 'allow', reason: 'scheduled' };
|
|
38
43
|
// A terminal node holding something live to wake it is legitimately awaiting.
|
|
39
44
|
if (hasActiveLiveSubscription(nodeId))
|
|
40
45
|
return { action: 'allow', reason: 'awaiting' };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { installMenuBinding, installNavBindings, sendKeysEnter } from './tmux.js';
|
|
1
|
+
export { installMenuBinding, installNavBindings, installViewNavBindings, sendKeysEnter } from './tmux.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// tmux-chrome.ts — chrome seam (§2.1): stateless keybind/input verbs.
|
|
2
2
|
// The ONLY non-placement module allowed to import the tmux driver, per the
|
|
3
3
|
// §5.1 lint. Re-exports the menu/nav/send-keys verbs callers (spawn, chord) need.
|
|
4
|
-
export { installMenuBinding, installNavBindings, sendKeysEnter } from './tmux.js';
|
|
4
|
+
export { installMenuBinding, installNavBindings, installViewNavBindings, sendKeysEnter } from './tmux.js';
|
|
@@ -96,6 +96,26 @@ export declare function paneLocation(pane: string): {
|
|
|
96
96
|
* liveness. We therefore require the echoed `#{pane_id}` to equal the requested
|
|
97
97
|
* pane: a live pane echoes its own id, a gone/bogus one yields empty. */
|
|
98
98
|
export declare function paneExists(pane: string): boolean;
|
|
99
|
+
/** Every live pane id on the server (`list-panes -a`), as a Set for membership
|
|
100
|
+
* probes. Returns null when tmux is unreachable (no server / transient failure)
|
|
101
|
+
* so callers can tell "no panes" apart from "can't tell" — a GC pass must skip,
|
|
102
|
+
* never mass-delete, on a failed probe. One subprocess call total, so batch
|
|
103
|
+
* liveness sweeps (e.g. the daemon's stale-focus GC) don't pay a per-pane
|
|
104
|
+
* display-message each. */
|
|
105
|
+
export declare function listLivePanes(): Set<string> | null;
|
|
106
|
+
/** The working directory of a pane (`display-message -p -t <pane>
|
|
107
|
+
* '#{pane_current_path}'`). Used to preserve a view monitor's cwd across a
|
|
108
|
+
* view-cycle respawn so project-scoped views still resolve. Null if tmux fails. */
|
|
109
|
+
export declare function paneCurrentPath(pane: string): string | null;
|
|
110
|
+
/** Set a PANE-scoped tmux option (`tmux set-option -p -t <pane> <name> <value>`).
|
|
111
|
+
* Used to tag a pane with the view id it currently hosts (`@crtr_view`) so the
|
|
112
|
+
* view-nav cycle can read it back and switch to the next/prev view in place.
|
|
113
|
+
* Best-effort; never throws. */
|
|
114
|
+
export declare function setPaneOption(pane: string, name: string, value: string): boolean;
|
|
115
|
+
/** Read a PANE-scoped tmux option value (`tmux show-options -p -t <pane> -q -v
|
|
116
|
+
* <name>`): `-v` prints only the value, `-q` suppresses the unknown-option
|
|
117
|
+
* error so an unset option yields an empty string. undefined if tmux fails. */
|
|
118
|
+
export declare function getPaneOption(pane: string, name: string): string | undefined;
|
|
99
119
|
/** Relocate a pane into another session as its own window WITHOUT killing the
|
|
100
120
|
* process in it — `break-pane -d` moves the pane out of its current window (the
|
|
101
121
|
* pi keeps generating) into a fresh window in `session`; `-d` leaves the caller's
|
|
@@ -197,3 +217,11 @@ export declare function installMenuBinding(): boolean;
|
|
|
197
217
|
* terminal/tmux disambiguate it from a raw CSI introducer (`extended-keys on`).
|
|
198
218
|
*/
|
|
199
219
|
export declare function installNavBindings(): boolean;
|
|
220
|
+
/** Bind Alt+V → (], [) to the view-monitor cycle. `M-v` enters the private
|
|
221
|
+
* `crtr-view` key table (switch-client -T), then ] cycles next / [ cycles prev.
|
|
222
|
+
* Best-effort; false if any of the three binds fail. Deliberately distinct from
|
|
223
|
+
* installNavBindings' bare Alt+]/Alt+[ (node cycle): the two cycles coexist on
|
|
224
|
+
* the same server — brackets alone walk the node graph, Alt+V-then-bracket flips
|
|
225
|
+
* view monitors. The bracket keys are bound in the private table, so they NEVER
|
|
226
|
+
* shadow the user's root ]/[ and carry no CSI-introducer ambiguity. */
|
|
227
|
+
export declare function installViewNavBindings(): boolean;
|
|
@@ -191,6 +191,39 @@ export function paneExists(pane) {
|
|
|
191
191
|
const r = tmux(['display-message', '-p', '-t', pane, '#{pane_id}']);
|
|
192
192
|
return r.ok && r.stdout === pane;
|
|
193
193
|
}
|
|
194
|
+
/** Every live pane id on the server (`list-panes -a`), as a Set for membership
|
|
195
|
+
* probes. Returns null when tmux is unreachable (no server / transient failure)
|
|
196
|
+
* so callers can tell "no panes" apart from "can't tell" — a GC pass must skip,
|
|
197
|
+
* never mass-delete, on a failed probe. One subprocess call total, so batch
|
|
198
|
+
* liveness sweeps (e.g. the daemon's stale-focus GC) don't pay a per-pane
|
|
199
|
+
* display-message each. */
|
|
200
|
+
export function listLivePanes() {
|
|
201
|
+
const r = tmux(['list-panes', '-a', '-F', '#{pane_id}']);
|
|
202
|
+
if (!r.ok)
|
|
203
|
+
return null;
|
|
204
|
+
return new Set(r.stdout.split('\n').filter((p) => p !== ''));
|
|
205
|
+
}
|
|
206
|
+
/** The working directory of a pane (`display-message -p -t <pane>
|
|
207
|
+
* '#{pane_current_path}'`). Used to preserve a view monitor's cwd across a
|
|
208
|
+
* view-cycle respawn so project-scoped views still resolve. Null if tmux fails. */
|
|
209
|
+
export function paneCurrentPath(pane) {
|
|
210
|
+
const r = tmux(['display-message', '-p', '-t', pane, '#{pane_current_path}']);
|
|
211
|
+
return r.ok && r.stdout !== '' ? r.stdout : null;
|
|
212
|
+
}
|
|
213
|
+
/** Set a PANE-scoped tmux option (`tmux set-option -p -t <pane> <name> <value>`).
|
|
214
|
+
* Used to tag a pane with the view id it currently hosts (`@crtr_view`) so the
|
|
215
|
+
* view-nav cycle can read it back and switch to the next/prev view in place.
|
|
216
|
+
* Best-effort; never throws. */
|
|
217
|
+
export function setPaneOption(pane, name, value) {
|
|
218
|
+
return tmux(['set-option', '-p', '-t', pane, name, value]).ok;
|
|
219
|
+
}
|
|
220
|
+
/** Read a PANE-scoped tmux option value (`tmux show-options -p -t <pane> -q -v
|
|
221
|
+
* <name>`): `-v` prints only the value, `-q` suppresses the unknown-option
|
|
222
|
+
* error so an unset option yields an empty string. undefined if tmux fails. */
|
|
223
|
+
export function getPaneOption(pane, name) {
|
|
224
|
+
const r = tmux(['show-options', '-p', '-t', pane, '-q', '-v', name]);
|
|
225
|
+
return r.ok ? r.stdout : undefined;
|
|
226
|
+
}
|
|
194
227
|
/** Relocate a pane into another session as its own window WITHOUT killing the
|
|
195
228
|
* process in it — `break-pane -d` moves the pane out of its current window (the
|
|
196
229
|
* pi keeps generating) into a fresh window in `session`; `-d` leaves the caller's
|
|
@@ -375,12 +408,13 @@ export function installMenuBinding() {
|
|
|
375
408
|
// a whole-canvas picker (incl. dormant nodes) and revives the choice via
|
|
376
409
|
// `crtr node focus` — the only sync-safe open (routes through reviveNode).
|
|
377
410
|
{ name: 'resume node', key: 'r', cmd: `send-keys -t '#{pane_id}' '/resume-node' Enter` },
|
|
378
|
-
// `d`
|
|
379
|
-
// running where it is, and because it is now terminal it
|
|
380
|
-
// final up the spine when it finishes. `D`
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
{ name: '
|
|
411
|
+
// `d` runs `node demote`: flip the agent to TERMINAL in place — no finalize,
|
|
412
|
+
// no kill — it keeps running where it is, and because it is now terminal it
|
|
413
|
+
// is forced to push a final up the spine when it finishes. `D` runs `node
|
|
414
|
+
// demote --detach`, which ALSO detaches it to the background `crtr` session
|
|
415
|
+
// (frees the pane; the pi keeps generating). Neither ends it.
|
|
416
|
+
{ name: 'demote to terminal', key: 'd', cmd: `run-shell "crtr node demote --pane '#{pane_id}' >/dev/null 2>&1"` },
|
|
417
|
+
{ name: 'detach to background', key: 'D', cmd: `run-shell "crtr node demote --pane '#{pane_id}' --detach >/dev/null 2>&1"` },
|
|
384
418
|
// Close cascades down the subscribes_to spine (kills the subtree's windows,
|
|
385
419
|
// marks them canceled); revivable. Output discarded — the keypress just acts.
|
|
386
420
|
{ name: 'close agent + subtree', key: 'x', cmd: `run-shell "crtr node close --pane '#{pane_id}' >/dev/null 2>&1"` },
|
|
@@ -455,3 +489,43 @@ export function installNavBindings() {
|
|
|
455
489
|
]).ok;
|
|
456
490
|
return next && prev;
|
|
457
491
|
}
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
// View-nav bindings — Alt+V then ] / [ cycle the view hosted in a MONITOR pane
|
|
494
|
+
// to the next/prev available view, in place. A VIEW-PREFIXED CHORD, not a bare
|
|
495
|
+
// Alt pair: Alt+V switches into a private one-shot `crtr-view` key table, then
|
|
496
|
+
// ] (next) / [ (prev) fire the cycle — mirroring node cycle's bracket DIRECTION
|
|
497
|
+
// grammar (Alt+] next / Alt+[ prev) while NAMESPACING the brackets so they can
|
|
498
|
+
// never shadow root bindings. This is collision-proof: only ONE root key (M-v)
|
|
499
|
+
// is claimed, and bare Alt pairs are a minefield in real configs (e.g. Alt+,/.
|
|
500
|
+
// and Alt+]/.[ commonly bound to window/pane nav). The bracket lives in the
|
|
501
|
+
// private table, so it also sidesteps the M-[ vs CSI-introducer ambiguity that
|
|
502
|
+
// dogs installNavBindings' Alt+[. Each key shells `crtr view cycle`, passing the
|
|
503
|
+
// active pane; cycle reads the pane's @crtr_view tag and respawns it on the
|
|
504
|
+
// next/prev view. Output discarded so the keypress never pops a results view.
|
|
505
|
+
// Installed at root boot alongside the node nav + Alt+C menu.
|
|
506
|
+
//
|
|
507
|
+
// NOTE: `M-v` is a GLOBAL root binding — it intercepts Alt+V in every pane/app
|
|
508
|
+
// (e.g. swallowed from the pi editor), the same tradeoff crtr already takes for
|
|
509
|
+
// M-c / M-] / M-[. Intended: crtr owns a small set of Alt chords server-wide.
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
/** Bind Alt+V → (], [) to the view-monitor cycle. `M-v` enters the private
|
|
512
|
+
* `crtr-view` key table (switch-client -T), then ] cycles next / [ cycles prev.
|
|
513
|
+
* Best-effort; false if any of the three binds fail. Deliberately distinct from
|
|
514
|
+
* installNavBindings' bare Alt+]/Alt+[ (node cycle): the two cycles coexist on
|
|
515
|
+
* the same server — brackets alone walk the node graph, Alt+V-then-bracket flips
|
|
516
|
+
* view monitors. The bracket keys are bound in the private table, so they NEVER
|
|
517
|
+
* shadow the user's root ]/[ and carry no CSI-introducer ambiguity. */
|
|
518
|
+
export function installViewNavBindings() {
|
|
519
|
+
const enter = tmux([
|
|
520
|
+
'bind-key', '-n', 'M-v', 'switch-client', '-T', 'crtr-view',
|
|
521
|
+
]).ok;
|
|
522
|
+
const next = tmux([
|
|
523
|
+
'bind-key', '-T', 'crtr-view', ']', 'run-shell',
|
|
524
|
+
`crtr view cycle --dir next --pane '#{pane_id}' >/dev/null 2>&1`,
|
|
525
|
+
]).ok;
|
|
526
|
+
const prev = tmux([
|
|
527
|
+
'bind-key', '-T', 'crtr-view', '[', 'run-shell',
|
|
528
|
+
`crtr view cycle --dir prev --pane '#{pane_id}' >/dev/null 2>&1`,
|
|
529
|
+
]).ok;
|
|
530
|
+
return enter && next && prev;
|
|
531
|
+
}
|
package/dist/core/scope.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Scope } from '../types.js';
|
|
2
2
|
export declare function builtinSkillsRoot(): string;
|
|
3
|
+
export declare function builtinViewsRoot(): string;
|
|
4
|
+
export declare function builtinMemoryRoot(): string;
|
|
3
5
|
export declare function userScopeRoot(): string;
|
|
4
6
|
export declare function findProjectScopeRoot(startDir?: string): string | null;
|
|
5
7
|
export declare function projectScopeRoot(startDir?: string): string | null;
|
|
@@ -9,6 +11,15 @@ export declare function ensureProjectScopeRoot(startDir?: string): string;
|
|
|
9
11
|
export declare function pluginsDir(scope: Scope): string | null;
|
|
10
12
|
export declare function marketplacesDir(scope: Scope): string | null;
|
|
11
13
|
export declare function scopeSkillsDir(scope: Scope): string | null;
|
|
14
|
+
/** Where substrate memory documents live per scope. Builtin memory is the
|
|
15
|
+
* special case — its own package dir (builtinMemoryRoot, a sibling of
|
|
16
|
+
* builtin-skills), NOT scopeRoot('builtin')/memory — mirroring viewsDir's
|
|
17
|
+
* builtin handling. User and project memory live at scopeRoot(scope)/memory. */
|
|
18
|
+
export declare function scopeMemoryDir(scope: Scope): string | null;
|
|
19
|
+
/** Where view definition dirs live per scope. Builtin views sit directly under
|
|
20
|
+
* builtinViewsRoot() (no `views/` segment — they ARE the builtin views dir),
|
|
21
|
+
* matching the loader's `<root>/<name>/view.mjs` resolution. */
|
|
22
|
+
export declare function viewsDir(scope: Scope): string | null;
|
|
12
23
|
export declare function resolveScopeArg(scopeArg: string | undefined): Scope | 'all';
|
|
13
24
|
export declare function listScopes(scopeArg: string | undefined): Scope[];
|
|
14
25
|
export declare function resetScopeCache(): void;
|