@crouton-kit/crouter 0.3.27 → 0.3.29
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 +4 -2
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +8 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +3 -3
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +445 -41
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +12 -0
- package/dist/clients/attach/canvas-panels.js +78 -0
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +43 -0
- package/dist/clients/attach/graph-overlay.js +306 -0
- package/dist/clients/attach/input-controller.d.ts +81 -13
- package/dist/clients/attach/input-controller.js +307 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +44 -1
- package/dist/clients/attach/slash-commands.js +250 -27
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +37 -2
- package/dist/clients/attach/view-socket.js +131 -7
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +1 -1
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/human/prompts.js +3 -3
- package/dist/commands/human/queue.d.ts +17 -0
- package/dist/commands/human/queue.js +111 -4
- package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
- package/dist/commands/memory/lint.d.ts +5 -4
- package/dist/commands/memory/lint.js +23 -7
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +20 -7
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/feedback.d.ts +1 -0
- package/dist/commands/sys/feedback.js +163 -0
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +4 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
- package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
- package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
- package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.d.ts +162 -0
- package/dist/core/canvas/nav-model.js +487 -0
- package/dist/core/canvas/paths.d.ts +7 -0
- package/dist/core/canvas/paths.js +9 -0
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker-sdk.d.ts +0 -12
- package/dist/core/runtime/broker-sdk.js +77 -6
- package/dist/core/runtime/broker.d.ts +5 -1
- package/dist/core/runtime/broker.js +520 -59
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +14 -7
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -334
- package/dist/core/runtime/placement.js +262 -828
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +45 -135
- package/dist/core/runtime/spawn.d.ts +15 -11
- package/dist/core/runtime/spawn.js +168 -136
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +15 -43
- package/dist/core/runtime/tmux.js +38 -80
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/spawn.d.ts +14 -0
- package/dist/core/spawn.js +29 -9
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +7 -7
- package/dist/core/substrate/injected-store.d.ts +10 -0
- package/dist/core/substrate/injected-store.js +55 -0
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +16 -11
- package/dist/core/substrate/schema.js +32 -31
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
- package/dist/pi-extensions/canvas-nav.js +35 -385
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
- package/dist/pi-extensions/canvas-stophook.js +119 -103
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
// a running pi process on the canvas. Composes canvas (birth + spine), persona
|
|
3
3
|
// (resolve), launch (pi argv), and tmux (placement).
|
|
4
4
|
//
|
|
5
|
-
// bootRoot — the user-opened front door (bare `crtr`). Resident;
|
|
6
|
-
//
|
|
5
|
+
// bootRoot — the user-opened front door (bare `crtr`). Resident; launches the
|
|
6
|
+
// root's broker engine, then execs `crtr attach` inline so THIS
|
|
7
|
+
// terminal becomes the broker's controller-viewer.
|
|
7
8
|
// spawnChild — a node spawned by a live node (`crtr node new`). A managed,
|
|
8
9
|
// terminal background worker by default; with `root`, an
|
|
9
10
|
// INDEPENDENT resident root (no subscription back to the spawner,
|
|
10
11
|
// provenance via spawned_by) brought forefront for direct driving.
|
|
11
12
|
import { spawnSync } from 'node:child_process';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
13
|
+
import { readdirSync, existsSync } from 'node:fs';
|
|
14
|
+
import { isAbsolute, resolve, join } from 'node:path';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { spawnNode, currentNodeContext, rootOfSpine } from './nodes.js';
|
|
14
17
|
import { buildLaunchSpec, buildPiArgv } from './launch.js';
|
|
15
18
|
import { writeGoal } from './kickoff.js';
|
|
16
19
|
import { hasRoadmap, seedRoadmap } from './roadmap.js';
|
|
17
20
|
import { buildIdentityAssertion, buildWakeBearings } from './bearings.js';
|
|
18
21
|
import { installMenuBinding, installNavBindings, installViewNavBindings } from './tmux-chrome.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
22
|
+
import { getNode, fullName } from '../canvas/index.js';
|
|
23
|
+
import { registerViewerFocus, openViewerWindow, waitForBrokerViewSocket, viewerSplitEnv, windowOfPane, currentTmux, inTmux, focusWindow, } from './placement.js';
|
|
21
24
|
import { transition } from './lifecycle.js';
|
|
22
25
|
import { headlessBrokerHost } from './host.js';
|
|
23
26
|
import { ensureDaemon } from '../../daemon/manage.js';
|
|
24
|
-
/** Create
|
|
25
|
-
*
|
|
27
|
+
/** Create the front-door root: launch its broker engine, then exec `crtr attach`
|
|
28
|
+
* inline so THIS terminal becomes the broker's controller-viewer. Does not
|
|
29
|
+
* return — it process.exit()s when the inline attach exits (detach leaves the
|
|
30
|
+
* resident broker running, to be reconnected later). */
|
|
26
31
|
export function bootRoot(opts) {
|
|
32
|
+
// crtr is tmux-only: the front door execs `crtr attach` inline in THIS pane to
|
|
33
|
+
// become the root's controller-viewer, so there must be a tmux pane to anchor
|
|
34
|
+
// on. Outside tmux there is no viewer surface — throw with a friendly message
|
|
35
|
+
// rather than boot a root nobody can see.
|
|
36
|
+
if (!inTmux()) {
|
|
37
|
+
throw new Error('crtr must be started from inside a tmux session — start tmux first (e.g. `tmux new -s work`), then run `crtr` there.');
|
|
38
|
+
}
|
|
27
39
|
// The thin supervisor must be up before any node exists, so a refresh-yield
|
|
28
40
|
// or crash can be reaped/revived. Idempotent.
|
|
29
41
|
try {
|
|
@@ -51,75 +63,134 @@ export function bootRoot(opts) {
|
|
|
51
63
|
// mandate (bare `crtr` has none — writeGoal no-ops on empty).
|
|
52
64
|
if (opts.prompt !== undefined)
|
|
53
65
|
writeGoal(meta.node_id, opts.prompt);
|
|
54
|
-
// Every node window — root or child — lives in the one shared session.
|
|
55
|
-
const session = nodeSession();
|
|
56
|
-
ensureSession(session, opts.cwd);
|
|
57
66
|
// Make the Alt+C action menu + Alt+] / Alt+[ node-nav keys + Alt+V then ]/[
|
|
58
|
-
// view-nav chord live on this server (idempotent
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
installNavBindings();
|
|
66
|
-
}
|
|
67
|
-
catch { /* best-effort */ }
|
|
68
|
-
try {
|
|
69
|
-
installViewNavBindings();
|
|
70
|
-
}
|
|
71
|
-
catch { /* best-effort */ }
|
|
67
|
+
// view-nav chord live on this server (idempotent; guaranteed in tmux above).
|
|
68
|
+
try {
|
|
69
|
+
installMenuBinding();
|
|
70
|
+
}
|
|
71
|
+
catch { /* best-effort */ }
|
|
72
|
+
try {
|
|
73
|
+
installNavBindings();
|
|
72
74
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
catch { /* best-effort */ }
|
|
76
|
+
try {
|
|
77
|
+
installViewNavBindings();
|
|
78
|
+
}
|
|
79
|
+
catch { /* best-effort */ }
|
|
80
|
+
// This terminal becomes the root's attach VIEWER in the user's CURRENT session,
|
|
81
|
+
// so its window stays where the user already is. The node row carries NO
|
|
82
|
+
// presence — a broker node's pane/window/tmux_session stay NULL; the viewer
|
|
83
|
+
// pane lives in the focuses table (below).
|
|
77
84
|
const here = currentTmux();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
updateNode(meta.node_id, { home_session: adopted });
|
|
84
|
-
// Root boot registers focus #1 (§2.6): the FOREGROUND inline root owns the
|
|
85
|
-
// user's viewport, so its OWN pane becomes a durable focus (remain-on-exit so
|
|
86
|
-
// a clean exit freezes rather than detaching the terminal). A background
|
|
87
|
-
// `--root` (spawnChild) does NOT — it stays a plain window until the user
|
|
88
|
-
// focuses it (§6). Only possible inside tmux (a pane to anchor on).
|
|
89
|
-
if (here) {
|
|
85
|
+
// The FOREGROUND front-door terminal is the root's one viewer: record its pane
|
|
86
|
+
// as the node's viewer focus row so focus/nav can find it. The `crtr attach`
|
|
87
|
+
// exec below makes the terminal a controller-viewer of the broker; detaching
|
|
88
|
+
// leaves the resident broker running.
|
|
89
|
+
if (here !== null) {
|
|
90
90
|
try {
|
|
91
|
-
|
|
91
|
+
registerViewerFocus(meta.node_id, here.pane, here.session, here.window);
|
|
92
92
|
}
|
|
93
93
|
catch { /* best-effort */ }
|
|
94
94
|
}
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
const inv = buildPiArgv(meta, { prompt: opts.prompt });
|
|
96
|
+
// Broker is the only host: the root runs as a detached broker engine (so its
|
|
97
|
+
// first refresh/revive doesn't strand this terminal — the daemon revives the
|
|
98
|
+
// broker and the viewer reconnects), and THIS terminal execs `crtr attach`
|
|
99
|
+
// inline to become its controller-viewer. The host sets CRTR_FRONT_DOOR.
|
|
100
|
+
inv.env = { ...inv.env, CRTR_SUBTREE: rootOfSpine(meta.node_id) };
|
|
101
|
+
const placed = headlessBrokerHost.launch(meta.node_id, inv, {
|
|
102
|
+
cwd: opts.cwd,
|
|
103
|
+
name: fullName(meta),
|
|
104
|
+
resuming: false,
|
|
105
|
+
});
|
|
106
|
+
// No broker pid ⇒ the root has no engine. Crash it (so the daemon won't watch a
|
|
107
|
+
// zombie 'active') and throw — there is nothing to attach to.
|
|
108
|
+
if (placed.pid === null) {
|
|
109
|
+
transition(meta.node_id, 'crash');
|
|
110
|
+
throw new Error(`failed to launch the broker engine for the front-door root ${meta.node_id} — nothing to attach to.`);
|
|
111
|
+
}
|
|
112
|
+
// The terminal has nothing to attach to until view.sock accepts. The root is
|
|
113
|
+
// useless without its engine, so a socket timeout IS fatal here (unlike a
|
|
114
|
+
// background child's optional viewer, which is skipped on timeout).
|
|
115
|
+
if (!waitForBrokerViewSocket(meta.node_id)) {
|
|
116
|
+
throw new Error(`the front-door root broker ${meta.node_id} never bound its view socket — cannot attach.`);
|
|
117
|
+
}
|
|
118
|
+
const attachEnv = { ...process.env, ...viewerSplitEnv() };
|
|
119
|
+
const r = spawnSync('crtr', ['attach', 'to', meta.node_id], { stdio: 'inherit', env: attachEnv });
|
|
99
120
|
process.exit(r.status ?? 0);
|
|
100
121
|
}
|
|
101
|
-
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
122
|
+
/** pi's sessions root, VENDORED from pi `config.getSessionsDir()` (= `<agentDir>/
|
|
123
|
+
* sessions`). pi's package `exports` map is `.`-only, so config.js can't be
|
|
124
|
+
* deep-imported, and a ROOT import of `getAgentDir` would eager-load the entire
|
|
125
|
+
* heavy pi SDK index on crtr's front-door hot path (the reason broker-sdk.ts
|
|
126
|
+
* dynamic-imports the engine). Mirrors pi: `PI_CODING_AGENT_DIR` env, else
|
|
127
|
+
* `~/.pi/agent` (APP_NAME defaults to 'pi'). Re-sync on a pi SDK bump that moves
|
|
128
|
+
* the sessions dir — same vendoring rationale as pi-vendored.ts. */
|
|
129
|
+
function piSessionsRoot() {
|
|
130
|
+
const env = process.env['PI_CODING_AGENT_DIR'];
|
|
131
|
+
const agentDir = env !== undefined && env !== '' ? env : join(homedir(), '.pi', 'agent');
|
|
132
|
+
return join(agentDir, 'sessions');
|
|
133
|
+
}
|
|
134
|
+
/** Resolve a bare/partial pi session uuid to its ABSOLUTE `.jsonl` path by
|
|
135
|
+
* scanning pi's sessions store the way pi's own CLI does (exact id, else a
|
|
136
|
+
* unique prefix). The broker fork seam (`SessionManager.forkFrom`) loads a FILE
|
|
137
|
+
* and does NOT resolve a uuid, so a bare id must be resolved here or the fork
|
|
138
|
+
* throws at boot. Session files are `<sessions>/<project>/<ts>_<id>.jsonl`. */
|
|
139
|
+
function resolveSessionUuid(uuid) {
|
|
140
|
+
const sessionsRoot = piSessionsRoot();
|
|
141
|
+
if (!existsSync(sessionsRoot)) {
|
|
142
|
+
throw new Error(`--fork-from '${uuid}': no pi sessions store at ${sessionsRoot} to resolve it from — pass a node id or an absolute .jsonl path.`);
|
|
143
|
+
}
|
|
144
|
+
const matches = [];
|
|
145
|
+
for (const dir of readdirSync(sessionsRoot, { withFileTypes: true })) {
|
|
146
|
+
if (!dir.isDirectory())
|
|
147
|
+
continue;
|
|
148
|
+
const projDir = join(sessionsRoot, dir.name);
|
|
149
|
+
for (const f of readdirSync(projDir)) {
|
|
150
|
+
if (!f.endsWith('.jsonl'))
|
|
151
|
+
continue;
|
|
152
|
+
const stem = f.slice(0, -'.jsonl'.length);
|
|
153
|
+
const id = stem.slice(stem.lastIndexOf('_') + 1);
|
|
154
|
+
matches.push({ id, path: join(projDir, f) });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const exact = matches.filter((m) => m.id === uuid);
|
|
158
|
+
const hit = exact.length > 0 ? exact : matches.filter((m) => m.id.startsWith(uuid));
|
|
159
|
+
if (hit.length === 0)
|
|
160
|
+
throw new Error(`--fork-from '${uuid}': no pi session with that id found.`);
|
|
161
|
+
if (hit.length > 1) {
|
|
162
|
+
throw new Error(`--fork-from '${uuid}': ambiguous — ${hit.length} sessions match that id prefix; pass the full id or the .jsonl path.`);
|
|
163
|
+
}
|
|
164
|
+
return hit[0].path;
|
|
165
|
+
}
|
|
166
|
+
/** Resolve a `--fork-from` value to an ABSOLUTE `.jsonl` source path for the
|
|
167
|
+
* broker fork (`SessionManager.forkFrom`, which loads a file — never a bare id).
|
|
168
|
+
* A live node id resolves to its captured session FILE (absolute, cwd-immune); a
|
|
169
|
+
* relative path is made absolute; a bare/partial uuid is resolved against pi's
|
|
170
|
+
* sessions store. Throws when a known node has no captured session file, or when
|
|
171
|
+
* a uuid resolves to zero or multiple sessions. */
|
|
105
172
|
export function resolveForkSource(value) {
|
|
106
173
|
const v = value.trim();
|
|
107
174
|
if (v === '')
|
|
108
175
|
throw new Error('--fork-from requires a node id, session file, or session uuid.');
|
|
109
|
-
// A path (contains `/` or ends `.jsonl`) is a session file —
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
176
|
+
// A path (contains `/` or ends `.jsonl`) is a session file — ensure it is
|
|
177
|
+
// ABSOLUTE so the broker, launched in a different cwd, still resolves it.
|
|
178
|
+
if (v.includes('/') || v.endsWith('.jsonl')) {
|
|
179
|
+
return isAbsolute(v) ? v : resolve(process.cwd(), v);
|
|
180
|
+
}
|
|
181
|
+
// A live node id — fork from the conversation it has accumulated. Its captured
|
|
182
|
+
// session FILE is absolute; the broker cannot fork a bare session id.
|
|
113
183
|
const n = getNode(v);
|
|
114
184
|
if (n !== null) {
|
|
115
|
-
const src = n.pi_session_file
|
|
185
|
+
const src = n.pi_session_file;
|
|
116
186
|
if (src === undefined || src === null || src === '') {
|
|
117
|
-
throw new Error(`node ${v} has no pi session yet — it has not
|
|
187
|
+
throw new Error(`node ${v} has no pi session yet — it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`);
|
|
118
188
|
}
|
|
119
189
|
return src;
|
|
120
190
|
}
|
|
121
|
-
// Not a known node —
|
|
122
|
-
|
|
191
|
+
// Not a known node — a bare/partial pi session uuid; resolve it to an absolute
|
|
192
|
+
// .jsonl (the broker fork won't resolve a uuid).
|
|
193
|
+
return resolveSessionUuid(v);
|
|
123
194
|
}
|
|
124
195
|
/** Resolve who a spawn is attributed to. A managed child needs a spine parent
|
|
125
196
|
* (explicit `--parent` or the calling node's CRTR_NODE_ID). A --root spawn
|
|
@@ -176,7 +247,6 @@ export function spawnChild(opts) {
|
|
|
176
247
|
// the boot intro can detect this is a fork and re-assert the node's own
|
|
177
248
|
// identity over the source's copied-in conversation.
|
|
178
249
|
forkFrom: opts.forkFrom,
|
|
179
|
-
hostKind: opts.hostKind,
|
|
180
250
|
modelOverride: opts.model,
|
|
181
251
|
launch,
|
|
182
252
|
});
|
|
@@ -210,90 +280,52 @@ export function spawnChild(opts) {
|
|
|
210
280
|
}
|
|
211
281
|
// (The three scoped long-term memory stores are seeded for EVERY node at birth
|
|
212
282
|
// in spawnNode — no orchestrator-gated seeding needed here.)
|
|
213
|
-
// A
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
// so it appears where the spawner is working rather than exiled to a separate
|
|
217
|
-
// crtr session. Either way the root's OWN descendants still flow to the shared
|
|
218
|
-
// session (childSession) via CRTR_ROOT_SESSION, to keep the subtree from
|
|
219
|
-
// cluttering the user's session.
|
|
220
|
-
const rootSessionEnv = process.env['CRTR_ROOT_SESSION'];
|
|
283
|
+
// A --root spawned from inside tmux opens its one viewer in the CALLER'S
|
|
284
|
+
// CURRENT session (below), so it appears where the spawner is working; a
|
|
285
|
+
// managed child is a headless broker with NO viewer at all.
|
|
221
286
|
const here = root ? currentTmux() : null;
|
|
222
|
-
// The shared backstage the whole subtree flows into (this child's own
|
|
223
|
-
// CRTR_ROOT_SESSION): the inherited root session, else the default `crtr`.
|
|
224
|
-
const childSession = resolveBirthSession({ adoptCaller: false, here, rootSession: rootSessionEnv });
|
|
225
|
-
// Where THIS node's window opens — and its durable REVIVE-HOME. A managed
|
|
226
|
-
// child lands in the backstage; a --root adopts the caller's current session
|
|
227
|
-
// when inside tmux, so it appears where the spawner is working.
|
|
228
|
-
const session = resolveBirthSession({ adoptCaller: root, here, rootSession: rootSessionEnv });
|
|
229
|
-
ensureSession(session, opts.cwd);
|
|
230
|
-
// REVIVE-HOME set once at birth: a managed child's revive target is the
|
|
231
|
-
// backstage, never a user session — this is what keeps a background revive
|
|
232
|
-
// off the user's screen (the focus taint cannot reach it).
|
|
233
|
-
updateNode(meta.node_id, { home_session: session });
|
|
234
287
|
const inv = buildPiArgv(meta, { prompt: kickoff, forkFrom });
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Birth LAUNCH branch on the PERSISTED host_kind (set at birth by spawnNode).
|
|
245
|
-
// A broker birth diverts AWAY from the tmux pane: the headless broker host
|
|
246
|
-
// launches a detached broker process (which records its own pid as pi_pid via
|
|
247
|
-
// the stophook) and returns placement fields all null — so we open NO tmux
|
|
248
|
-
// window and write NO tmux placement (presence stays null for a broker). The
|
|
249
|
-
// tmux path below is left completely UNCHANGED (byte-identical) for every
|
|
250
|
-
// non-broker spawn. Mirrors reviveNode's `hostFor(meta).launch(...)` shape.
|
|
251
|
-
if (meta.host_kind === 'broker') {
|
|
252
|
-
const placed = headlessBrokerHost.launch(meta.node_id, inv, {
|
|
253
|
-
cwd: meta.cwd,
|
|
254
|
-
name: fullName(meta),
|
|
255
|
-
resuming: false,
|
|
256
|
-
});
|
|
257
|
-
const saved = getNode(meta.node_id);
|
|
258
|
-
return { node: saved, window: placed.window, session: placed.session };
|
|
259
|
-
}
|
|
260
|
-
const env = { ...inv.env, [FRONT_DOOR_ENV]: '1' };
|
|
261
|
-
const command = piCommand(inv.argv);
|
|
262
|
-
// openNodeWindow now returns {window, pane}; pane is unused until the
|
|
263
|
-
// placement layer lands, so destructure the window and proceed unchanged.
|
|
264
|
-
const opened = openNodeWindow({
|
|
265
|
-
session,
|
|
288
|
+
// CRTR_SUBTREE (the spine root) rides the detached broker's env so it can group
|
|
289
|
+
// the subtree; the host sets CRTR_FRONT_DOOR itself.
|
|
290
|
+
inv.env = { ...inv.env, CRTR_SUBTREE: rootOfSpine(meta.node_id) };
|
|
291
|
+
// Broker is the only host: launch the detached broker ENGINE. A managed child
|
|
292
|
+
// gets NO viewer — spawning it returns once the engine is launched and a human
|
|
293
|
+
// opens a viewer on demand via `crtr node focus` / `crtr attach`. A --root gets
|
|
294
|
+
// one foreground viewer below.
|
|
295
|
+
const placed = headlessBrokerHost.launch(meta.node_id, inv, {
|
|
296
|
+
cwd: meta.cwd,
|
|
266
297
|
name: fullName(meta),
|
|
267
|
-
|
|
268
|
-
env,
|
|
269
|
-
command,
|
|
298
|
+
resuming: false,
|
|
270
299
|
});
|
|
271
|
-
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
// is still 'active' from spawnNode, so transition('crash') is a legal from-LIVE
|
|
277
|
-
// move — the last scattered node-status write, now through the lifecycle machine.
|
|
278
|
-
//
|
|
279
|
-
// pi BOOTING inside the window, by contrast, is inherently slow (and slower
|
|
280
|
-
// under load), so we stay optimistic and return status='active' the instant
|
|
281
|
-
// the window exists. A vehicle that then dies before its first session_start
|
|
282
|
-
// is caught by the daemon — it surfaces the boot failure up the spine rather
|
|
283
|
-
// than letting the node die silently (see crtrd.ts surfaceBootFailure).
|
|
284
|
-
if (window === null) {
|
|
300
|
+
// Definitive failure: no broker pid ⇒ the node has no engine. Crash it (so the
|
|
301
|
+
// daemon doesn't watch a zombie 'active') and throw so `crtr node new` exits
|
|
302
|
+
// non-zero. transition('crash') is a legal from-LIVE move (still 'active' from
|
|
303
|
+
// spawnNode). Mirrors the old window===null crash.
|
|
304
|
+
if (placed.pid === null) {
|
|
285
305
|
transition(meta.node_id, 'crash');
|
|
286
|
-
throw new Error(`failed to
|
|
306
|
+
throw new Error(`failed to launch the broker engine for ${meta.node_id} (${meta.name}) — the node was not started.`);
|
|
287
307
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
308
|
+
// A --root is spawned to be DRIVEN directly: when the caller is inside tmux,
|
|
309
|
+
// open ONE foreground viewer in the caller's CURRENT session and bring it
|
|
310
|
+
// forefront so whoever asked for it picks up the conversation. Outside tmux a
|
|
311
|
+
// --root opens no viewer (an agent handing a root off) — never a throw. A
|
|
312
|
+
// managed child opens nothing. Non-fatal if tmux refuses (openViewerWindow
|
|
313
|
+
// returns null) or the socket never binds — `focus` opens a viewer later.
|
|
314
|
+
let window = null;
|
|
315
|
+
let session = null;
|
|
316
|
+
if (root && here !== null && waitForBrokerViewSocket(meta.node_id)) {
|
|
317
|
+
const viewer = openViewerWindow(meta.node_id, here.session, { name: fullName(meta), cwd: opts.cwd });
|
|
318
|
+
if (viewer !== null && viewer.pane !== null && viewer.pane !== '') {
|
|
319
|
+
session = here.session;
|
|
320
|
+
window = windowOfPane(viewer.pane);
|
|
321
|
+
if (window !== null) {
|
|
322
|
+
try {
|
|
323
|
+
focusWindow(here.session, window);
|
|
324
|
+
}
|
|
325
|
+
catch { /* best-effort */ }
|
|
326
|
+
}
|
|
295
327
|
}
|
|
296
|
-
catch { /* best-effort */ }
|
|
297
328
|
}
|
|
329
|
+
const saved = getNode(meta.node_id);
|
|
298
330
|
return { node: saved, window, session };
|
|
299
331
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The SGR escape that turns ON the distinct-surface background (theme
|
|
2
|
+
* `selectedBg`), e.g. `\x1b[48;2;58;58;74m`. Pair with `\x1b[49m` to reset just
|
|
3
|
+
* the background. Throws if no process has initialized the theme — callers on a
|
|
4
|
+
* render path (the attach overlay) always have, by construction. */
|
|
5
|
+
export declare function surfaceBgAnsi(): string;
|
|
6
|
+
/** tmux flags that frame a `display-menu` / `display-popup` on the distinct
|
|
7
|
+
* surface background — a rounded border + the surface bg on the body and border.
|
|
8
|
+
* Empty when the theme is unavailable in this process (chrome stays default; the
|
|
9
|
+
* attach viewer re-installs the menu with these once it has themed). One knob:
|
|
10
|
+
* every crtr tmux float shares this single definition. */
|
|
11
|
+
export declare function surfaceTmuxStyleArgs(): string[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// surface-bg.ts — the "distinct popup surface" background, theme-derived.
|
|
2
|
+
//
|
|
3
|
+
// crtr's overlays and popups must read as a SEPARATE surface from the normal one
|
|
4
|
+
// (CTO ruling, taste/inline-ui-placement #11): a popup whose background matches
|
|
5
|
+
// the surface has invisible edges, so you can't tell where the float begins. We
|
|
6
|
+
// paint them with the active pi theme's `selectedBg` role — theme-derived, so it
|
|
7
|
+
// adapts to light/dark and is ONE centralized knob, not a scattered hardcode.
|
|
8
|
+
//
|
|
9
|
+
// pi 0.79 does not re-export `selectedBg` through its public API (the `.` entry
|
|
10
|
+
// omits `getResolvedThemeColors` and the live `theme` singleton). It DOES publish
|
|
11
|
+
// the live `Theme` instance on a process-global symbol once `initTheme()` has run
|
|
12
|
+
// — the same hook pi's own SDK reads (see broker.ts `ctx.ui.theme`). We read it
|
|
13
|
+
// there and use the public `Theme.bg()` / `Theme.getBgAnsi()` methods.
|
|
14
|
+
//
|
|
15
|
+
// CRITICAL: this module must NOT statically import `@earendil-works/pi-coding-agent`
|
|
16
|
+
// — that package costs ~400ms to load and would land on the cold `crtr` front
|
|
17
|
+
// door (which never otherwise loads pi). We only READ the process-global symbol;
|
|
18
|
+
// any process that needs styling (the attach viewer, a node's broker) has already
|
|
19
|
+
// called `initTheme()` and populated it. Where the symbol is absent (the front
|
|
20
|
+
// door, before it execs into the themed `crtr attach`), the tmux helpers return
|
|
21
|
+
// no style args and the chrome stays at its default — the viewer re-themes it.
|
|
22
|
+
const THEME_SYMBOL = Symbol.for('@earendil-works/pi-coding-agent:theme');
|
|
23
|
+
/** The live pi Theme if a process has already run `initTheme()`, else undefined.
|
|
24
|
+
* Read-only: never initializes (that would pull pi into the front door). */
|
|
25
|
+
function liveTheme() {
|
|
26
|
+
const t = globalThis[THEME_SYMBOL];
|
|
27
|
+
return (t ?? undefined);
|
|
28
|
+
}
|
|
29
|
+
/** The SGR escape that turns ON the distinct-surface background (theme
|
|
30
|
+
* `selectedBg`), e.g. `\x1b[48;2;58;58;74m`. Pair with `\x1b[49m` to reset just
|
|
31
|
+
* the background. Throws if no process has initialized the theme — callers on a
|
|
32
|
+
* render path (the attach overlay) always have, by construction. */
|
|
33
|
+
export function surfaceBgAnsi() {
|
|
34
|
+
const t = liveTheme();
|
|
35
|
+
if (t === undefined)
|
|
36
|
+
throw new Error('surfaceBgAnsi: pi theme not initialized in this process');
|
|
37
|
+
return t.getBgAnsi('selectedBg');
|
|
38
|
+
}
|
|
39
|
+
/** The distinct-surface background as a tmux colour token (`#rrggbb` on a
|
|
40
|
+
* truecolor theme, `colourN` on a 256-colour theme), or undefined when the theme
|
|
41
|
+
* is not yet loaded in this process OR defines no selection background. tmux
|
|
42
|
+
* cannot read pi's theme, so we translate the SGR pi resolved for us. */
|
|
43
|
+
function surfaceBgTmux() {
|
|
44
|
+
const t = liveTheme();
|
|
45
|
+
if (t === undefined)
|
|
46
|
+
return undefined;
|
|
47
|
+
const ansi = t.getBgAnsi('selectedBg');
|
|
48
|
+
const tc = /48;2;(\d+);(\d+);(\d+)/.exec(ansi);
|
|
49
|
+
if (tc !== null) {
|
|
50
|
+
const hex = (n) => Number(n).toString(16).padStart(2, '0');
|
|
51
|
+
return `#${hex(tc[1])}${hex(tc[2])}${hex(tc[3])}`;
|
|
52
|
+
}
|
|
53
|
+
const c256 = /48;5;(\d+)/.exec(ansi);
|
|
54
|
+
if (c256 !== null)
|
|
55
|
+
return `colour${c256[1]}`;
|
|
56
|
+
return undefined; // empty/default selectedBg → nothing distinct to paint
|
|
57
|
+
}
|
|
58
|
+
/** tmux flags that frame a `display-menu` / `display-popup` on the distinct
|
|
59
|
+
* surface background — a rounded border + the surface bg on the body and border.
|
|
60
|
+
* Empty when the theme is unavailable in this process (chrome stays default; the
|
|
61
|
+
* attach viewer re-installs the menu with these once it has themed). One knob:
|
|
62
|
+
* every crtr tmux float shares this single definition. */
|
|
63
|
+
export function surfaceTmuxStyleArgs() {
|
|
64
|
+
const bg = surfaceBgTmux();
|
|
65
|
+
if (bg === undefined)
|
|
66
|
+
return [];
|
|
67
|
+
return ['-b', 'rounded', '-s', `bg=${bg}`, '-S', `bg=${bg}`];
|
|
68
|
+
}
|
|
@@ -46,6 +46,12 @@ export interface SplitWindowOpts {
|
|
|
46
46
|
command: string;
|
|
47
47
|
/** Stack the new pane below instead of beside (default: beside, `-h`). */
|
|
48
48
|
vertical?: boolean;
|
|
49
|
+
/** Place the new pane BEFORE the target (left for `-h`, above for `-v`) via
|
|
50
|
+
* `-b`, instead of after it (default: after — right/below). */
|
|
51
|
+
before?: boolean;
|
|
52
|
+
/** Fixed size of the new pane in the split axis' cells (columns for `-h`, rows
|
|
53
|
+
* for `-v`) via `-l`. Omit ⇒ tmux's default even split. */
|
|
54
|
+
size?: number;
|
|
49
55
|
}
|
|
50
56
|
/** Split `targetPane`'s window, opening a NEW pane beside it running `command`,
|
|
51
57
|
* and return the new pane id (the durable `%id`). The ONLY new-pane-beside verb
|
|
@@ -62,6 +68,10 @@ export declare function splitWindow(targetPane: string, opts: SplitWindowOpts):
|
|
|
62
68
|
export declare function focusWindow(session: string, window: string): boolean;
|
|
63
69
|
/** Close a node's window (drop it from the UI). */
|
|
64
70
|
export declare function closeWindow(window: string): boolean;
|
|
71
|
+
/** Rename a window (`tmux rename-window -t <window> <name>`). Used by the
|
|
72
|
+
* root relaunch to re-title the viewer window when its pane is re-pointed at
|
|
73
|
+
* the freshly-minted node. Best-effort; false if tmux fails. */
|
|
74
|
+
export declare function renameWindow(window: string, name: string): boolean;
|
|
65
75
|
/** Close a single PANE. Its window closes automatically once this was the last
|
|
66
76
|
* pane, but sibling panes survive — so co-located nodes (several agents sharing
|
|
67
77
|
* one window via swap-pane focus) are torn down one at a time instead of all
|
|
@@ -96,6 +106,11 @@ export declare function paneLocation(pane: string): {
|
|
|
96
106
|
* liveness. We therefore require the echoed `#{pane_id}` to equal the requested
|
|
97
107
|
* pane: a live pane echoes its own id, a gone/bogus one yields empty. */
|
|
98
108
|
export declare function paneExists(pane: string): boolean;
|
|
109
|
+
/** Does this pane exist AND have its command still RUNNING (`#{pane_dead}` = 0)?
|
|
110
|
+
* Distinguishes a pane genuinely hosting a live process from a remain-on-exit
|
|
111
|
+
* corpse frozen after exit (`pane_dead` = 1). Node viewer panes run `crtr attach`
|
|
112
|
+
* as the pane command, so pane-running ⟹ a live viewer occupies it. */
|
|
113
|
+
export declare function paneRunning(pane: string): boolean;
|
|
99
114
|
/** Every live pane id on the server (`list-panes -a`), as a Set for membership
|
|
100
115
|
* probes. Returns null when tmux is unreachable (no server / transient failure)
|
|
101
116
|
* so callers can tell "no panes" apart from "can't tell" — a GC pass must skip,
|
|
@@ -116,21 +131,6 @@ export declare function setPaneOption(pane: string, name: string, value: string)
|
|
|
116
131
|
* <name>`): `-v` prints only the value, `-q` suppresses the unknown-option
|
|
117
132
|
* error so an unset option yields an empty string. undefined if tmux fails. */
|
|
118
133
|
export declare function getPaneOption(pane: string, name: string): string | undefined;
|
|
119
|
-
/** Relocate a pane into another session as its own window WITHOUT killing the
|
|
120
|
-
* process in it — `break-pane -d` moves the pane out of its current window (the
|
|
121
|
-
* pi keeps generating) into a fresh window in `session`; `-d` leaves the caller's
|
|
122
|
-
* client where it is rather than following the pane to the background, and `-a`
|
|
123
|
-
* allocates the next free window index (same dodge as openNodeWindow). The
|
|
124
|
-
* "detach to background" driver behind `node lifecycle --detach`. Best-effort;
|
|
125
|
-
* false if tmux refuses (e.g. the pane is gone). The caller reconciles presence
|
|
126
|
-
* so the canvas follows the move. */
|
|
127
|
-
export declare function breakPaneToSession(pane: string, session: string): boolean;
|
|
128
|
-
/** Swap `targetPane` into `callerPane`'s layout slot, IN PLACE. `-d` keeps the
|
|
129
|
-
* caller's window active, so the target's pane appears where the caller is
|
|
130
|
-
* rather than navigating the client off to the target's window. The caller's
|
|
131
|
-
* old pane lives on in the target's former window — the move is reversible
|
|
132
|
-
* (focusing back swaps it in again). Best-effort; never throws. */
|
|
133
|
-
export declare function swapPaneInPlace(targetPane: string, callerPane: string): boolean;
|
|
134
134
|
export interface RespawnPaneOpts {
|
|
135
135
|
/** Target pane id (e.g. `%3`) — the pane to re-exec in place. */
|
|
136
136
|
pane: string;
|
|
@@ -139,21 +139,11 @@ export interface RespawnPaneOpts {
|
|
|
139
139
|
/** The full command to run in the pane (already a shell string). */
|
|
140
140
|
command: string;
|
|
141
141
|
}
|
|
142
|
-
/** Re-exec a command in an EXISTING pane, in place — DETACHED. Spawned in its own
|
|
143
|
-
* process group (unref'd) so the request reaches the tmux server even though
|
|
144
|
-
* `-k` tears down the caller's own pi mid-flight. Used when a node respawns ITS
|
|
145
|
-
* OWN pane (refresh-yield): the dispatch can't be awaited because it kills the
|
|
146
|
-
* awaiter. Returns true once the request was dispatched. */
|
|
147
|
-
export declare function respawnPaneDetached(opts: RespawnPaneOpts): boolean;
|
|
148
142
|
/** Re-exec a command in an EXISTING pane, in place — SYNCHRONOUS. Runs the
|
|
149
143
|
* `respawn-pane` to completion and reports the real exit status. Used when the
|
|
150
144
|
* caller is NOT the pane being respawned (e.g. the daemon resuming a frozen
|
|
151
145
|
* focus pane), so it can confirm the respawn landed. Returns true on success. */
|
|
152
146
|
export declare function respawnPaneSync(opts: RespawnPaneOpts): boolean;
|
|
153
|
-
/** @deprecated Use respawnPaneDetached. Retained so existing refresh-yield
|
|
154
|
-
* callers stay green while the placement layer migrates onto the explicit
|
|
155
|
-
* sync/detached split. */
|
|
156
|
-
export declare function respawnPane(opts: RespawnPaneOpts): boolean;
|
|
157
147
|
/** Turn a pi argv array into a single shell command string.
|
|
158
148
|
*
|
|
159
149
|
* The binary defaults to `CRTR_PI_BINARY` when that env var is set, else the
|
|
@@ -183,24 +173,6 @@ export declare function selectWindow(session: string, window: string): boolean;
|
|
|
183
173
|
* `tmux switch-client -t <session>`. Best-effort; never throws. The caller is
|
|
184
174
|
* responsible for following up with selectWindow to land on the right window. */
|
|
185
175
|
export declare function switchClient(session: string): boolean;
|
|
186
|
-
/** Move a source pane into a destination window (`tmux join-pane`). The source
|
|
187
|
-
* pane's running process (e.g. a child's live pi) is preserved; its now-empty
|
|
188
|
-
* source window auto-closes. Best-effort; false if tmux fails. */
|
|
189
|
-
export declare function joinPane(srcPane: string, dstWindow: string): boolean;
|
|
190
|
-
/** Apply a named tmux layout to a window (`tmux select-layout`). Use
|
|
191
|
-
* `main-vertical` for one wide pane on the left + the rest stacked right.
|
|
192
|
-
* Best-effort; never throws. */
|
|
193
|
-
export declare function selectLayout(window: string, layout: string): boolean;
|
|
194
|
-
/** Set a tmux window option (`tmux set-window-option`). Used to size the main
|
|
195
|
-
* pane (`main-pane-width`) before a main-vertical layout. Best-effort. */
|
|
196
|
-
export declare function setWindowOption(window: string, name: string, value: string): boolean;
|
|
197
|
-
/** Toggle `remain-on-exit` on a window (§1.5 F3). `on` keeps a focus pane on
|
|
198
|
-
* screen after its pi exits — the viewport survives (F1), the final transcript
|
|
199
|
-
* is preserved, and `respawn-pane -k` can resurrect the node into the SAME pane
|
|
200
|
-
* id. NOTE (§1.5/§2.5, spike-confirmed): a dead/frozen pane is reaped only by
|
|
201
|
-
* `kill-pane`/`respawn-pane`, NEVER by toggling this off — the toggle does not
|
|
202
|
-
* reap an already-dead pane. Best-effort; never throws. */
|
|
203
|
-
export declare function setRemainOnExit(window: string, on: boolean): boolean;
|
|
204
176
|
/** Type a literal (e.g. a `/graph` slash command) into a pane and press Enter
|
|
205
177
|
* (`tmux send-keys -t <pane> '<text>' Enter`). Requires the pane's editor be
|
|
206
178
|
* empty, same limitation as the menu's `/promote` item. Best-effort. */
|