@crouton-kit/crouter 0.3.28 → 0.3.30
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.d.ts +15 -7
- package/dist/build-root.js +48 -34
- 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 +5 -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 +22 -17
- 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__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +282 -46
- 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 +4 -2
- package/dist/clients/attach/canvas-panels.js +45 -17
- 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 +13 -4
- package/dist/clients/attach/graph-overlay.js +59 -19
- package/dist/clients/attach/input-controller.d.ts +75 -13
- package/dist/clients/attach/input-controller.js +305 -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 +22 -0
- package/dist/clients/attach/slash-commands.js +91 -25
- 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 +18 -1
- package/dist/clients/attach/view-socket.js +70 -1
- 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 +4 -25
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
- package/dist/commands/memory/lint.js +14 -2
- 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 +8 -4
- 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/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +3 -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__/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 +17 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +22 -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__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/listing-completeness.test.js +2 -2
- 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.js +2 -2
- 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.js +8 -7
- 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.d.ts +4 -1
- package/dist/core/runtime/broker.js +494 -58
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +6 -14
- 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 -335
- package/dist/core/runtime/placement.js +291 -858
- 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 +40 -139
- package/dist/core/runtime/spawn.d.ts +15 -16
- package/dist/core/runtime/spawn.js +165 -194
- 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 -52
- package/dist/core/runtime/tmux.js +35 -99
- 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/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.js +3 -3
- 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 +10 -3
- package/dist/core/substrate/schema.js +6 -3
- 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 +16 -16
- package/dist/pi-extensions/canvas-nav.js +5 -0
- 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 +14 -12
- package/dist/pi-extensions/canvas-stophook.js +87 -101
- 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 -297
- 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__/slash-quit-copy.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
|
@@ -2,31 +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 {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { spawnNode, currentNodeContext, resolveBirthSession, nodeSession, rootOfSpine } from './nodes.js';
|
|
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';
|
|
17
17
|
import { buildLaunchSpec, buildPiArgv } from './launch.js';
|
|
18
18
|
import { writeGoal } from './kickoff.js';
|
|
19
19
|
import { hasRoadmap, seedRoadmap } from './roadmap.js';
|
|
20
20
|
import { buildIdentityAssertion, buildWakeBearings } from './bearings.js';
|
|
21
21
|
import { installMenuBinding, installNavBindings, installViewNavBindings } from './tmux-chrome.js';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { getNode, fullName } from '../canvas/index.js';
|
|
23
|
+
import { registerViewerFocus, openViewerWindow, waitForBrokerViewSocket, viewerSplitEnv, windowOfPane, currentTmux, inTmux, focusWindow, } from './placement.js';
|
|
24
24
|
import { transition } from './lifecycle.js';
|
|
25
25
|
import { headlessBrokerHost } from './host.js';
|
|
26
26
|
import { ensureDaemon } from '../../daemon/manage.js';
|
|
27
|
-
/** Create
|
|
28
|
-
*
|
|
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). */
|
|
29
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
|
+
}
|
|
30
39
|
// The thin supervisor must be up before any node exists, so a refresh-yield
|
|
31
40
|
// or crash can be reaped/revived. Idempotent.
|
|
32
41
|
try {
|
|
@@ -34,10 +43,6 @@ export function bootRoot(opts) {
|
|
|
34
43
|
}
|
|
35
44
|
catch { /* daemon is best-effort */ }
|
|
36
45
|
const kind = opts.kind ?? 'general';
|
|
37
|
-
// Host precedence: explicit --headless/--no-headless flag > config `headless`
|
|
38
|
-
// default > tmux. Mirrors `crtr node new`'s resolution (commands/node.ts).
|
|
39
|
-
const headless = opts.headless ?? readConfig('user').headless === true;
|
|
40
|
-
const hostKind = headless ? 'broker' : 'tmux';
|
|
41
46
|
// A born-resident root starts in base mode; it earns the orchestrator persona
|
|
42
47
|
// the first time it delegates (or on promotion). Resident lifecycle either way.
|
|
43
48
|
const { launch } = buildLaunchSpec(kind, 'base', { lifecycle: 'resident', hasManager: false });
|
|
@@ -52,135 +57,140 @@ export function bootRoot(opts) {
|
|
|
52
57
|
cwd: opts.cwd,
|
|
53
58
|
name: opts.name ?? kind,
|
|
54
59
|
parent: null,
|
|
55
|
-
hostKind,
|
|
56
60
|
launch,
|
|
57
61
|
});
|
|
58
62
|
// Persist the spawning prompt as the goal so a fresh revive can re-read its
|
|
59
63
|
// mandate (bare `crtr` has none — writeGoal no-ops on empty).
|
|
60
64
|
if (opts.prompt !== undefined)
|
|
61
65
|
writeGoal(meta.node_id, opts.prompt);
|
|
62
|
-
// Every node window — root or child — lives in the one shared session.
|
|
63
|
-
const session = nodeSession();
|
|
64
|
-
ensureSession(session, opts.cwd);
|
|
65
66
|
// Make the Alt+C action menu + Alt+] / Alt+[ node-nav keys + Alt+V then ]/[
|
|
66
|
-
// view-nav chord live on this server (idempotent
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
installMenuBinding();
|
|
70
|
-
}
|
|
71
|
-
catch { /* best-effort */ }
|
|
72
|
-
try {
|
|
73
|
-
installNavBindings();
|
|
74
|
-
}
|
|
75
|
-
catch { /* best-effort */ }
|
|
76
|
-
try {
|
|
77
|
-
installViewNavBindings();
|
|
78
|
-
}
|
|
79
|
-
catch { /* best-effort */ }
|
|
67
|
+
// view-nav chord live on this server (idempotent; guaranteed in tmux above).
|
|
68
|
+
try {
|
|
69
|
+
installMenuBinding();
|
|
80
70
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
// where the user is
|
|
92
|
-
//
|
|
93
|
-
//
|
|
71
|
+
catch { /* best-effort */ }
|
|
72
|
+
try {
|
|
73
|
+
installNavBindings();
|
|
74
|
+
}
|
|
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).
|
|
94
84
|
const here = currentTmux();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
updateNode(meta.node_id, { home_session: adopted });
|
|
101
|
-
// Root boot registers focus #1 (§2.6): the FOREGROUND inline root owns the
|
|
102
|
-
// user's viewport, so its OWN pane becomes a durable focus (remain-on-exit so
|
|
103
|
-
// a clean exit freezes rather than detaching the terminal). A background
|
|
104
|
-
// `--root` (spawnChild) does NOT — it stays a plain window until the user
|
|
105
|
-
// focuses it (§6). Only possible inside tmux (a pane to anchor on).
|
|
106
|
-
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) {
|
|
107
90
|
try {
|
|
108
|
-
|
|
91
|
+
registerViewerFocus(meta.node_id, here.pane, here.session, here.window);
|
|
109
92
|
}
|
|
110
93
|
catch { /* best-effort */ }
|
|
111
94
|
}
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 });
|
|
116
120
|
process.exit(r.status ?? 0);
|
|
117
121
|
}
|
|
118
|
-
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// timeout here does NOT mean the broker died — the engine is a resident root
|
|
140
|
-
// that keeps booting; we just can't attach yet, so point the user at re-focus.
|
|
141
|
-
if (!waitForBrokerViewSocket(meta.node_id)) {
|
|
142
|
-
process.stderr.write(`crtr: ${meta.node_id} is still starting its broker — it is running, but its viewer socket did not come up in time. ` +
|
|
143
|
-
`Re-focus the node to attach (\`crtr node focus ${meta.node_id}\`); see ${join(jobDir(meta.node_id), 'broker.log')} if it never appears.\n`);
|
|
144
|
-
process.exit(1);
|
|
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.`);
|
|
145
143
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
156
|
}
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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;
|
|
161
165
|
}
|
|
162
|
-
/** Resolve a `--fork-from` value to
|
|
163
|
-
*
|
|
164
|
-
*
|
|
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. */
|
|
166
172
|
export function resolveForkSource(value) {
|
|
167
173
|
const v = value.trim();
|
|
168
174
|
if (v === '')
|
|
169
175
|
throw new Error('--fork-from requires a node id, session file, or session uuid.');
|
|
170
|
-
// A path (contains `/` or ends `.jsonl`) is a session file —
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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.
|
|
174
183
|
const n = getNode(v);
|
|
175
184
|
if (n !== null) {
|
|
176
|
-
const src = n.pi_session_file
|
|
185
|
+
const src = n.pi_session_file;
|
|
177
186
|
if (src === undefined || src === null || src === '') {
|
|
178
|
-
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).`);
|
|
179
188
|
}
|
|
180
189
|
return src;
|
|
181
190
|
}
|
|
182
|
-
// Not a known node —
|
|
183
|
-
|
|
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);
|
|
184
194
|
}
|
|
185
195
|
/** Resolve who a spawn is attributed to. A managed child needs a spine parent
|
|
186
196
|
* (explicit `--parent` or the calling node's CRTR_NODE_ID). A --root spawn
|
|
@@ -237,7 +247,6 @@ export function spawnChild(opts) {
|
|
|
237
247
|
// the boot intro can detect this is a fork and re-assert the node's own
|
|
238
248
|
// identity over the source's copied-in conversation.
|
|
239
249
|
forkFrom: opts.forkFrom,
|
|
240
|
-
hostKind: opts.hostKind,
|
|
241
250
|
modelOverride: opts.model,
|
|
242
251
|
launch,
|
|
243
252
|
});
|
|
@@ -271,90 +280,52 @@ export function spawnChild(opts) {
|
|
|
271
280
|
}
|
|
272
281
|
// (The three scoped long-term memory stores are seeded for EVERY node at birth
|
|
273
282
|
// in spawnNode — no orchestrator-gated seeding needed here.)
|
|
274
|
-
// A
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
// so it appears where the spawner is working rather than exiled to a separate
|
|
278
|
-
// crtr session. Either way the root's OWN descendants still flow to the shared
|
|
279
|
-
// session (childSession) via CRTR_ROOT_SESSION, to keep the subtree from
|
|
280
|
-
// cluttering the user's session.
|
|
281
|
-
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.
|
|
282
286
|
const here = root ? currentTmux() : null;
|
|
283
|
-
// The shared backstage the whole subtree flows into (this child's own
|
|
284
|
-
// CRTR_ROOT_SESSION): the inherited root session, else the default `crtr`.
|
|
285
|
-
const childSession = resolveBirthSession({ adoptCaller: false, here, rootSession: rootSessionEnv });
|
|
286
|
-
// Where THIS node's window opens — and its durable REVIVE-HOME. A managed
|
|
287
|
-
// child lands in the backstage; a --root adopts the caller's current session
|
|
288
|
-
// when inside tmux, so it appears where the spawner is working.
|
|
289
|
-
const session = resolveBirthSession({ adoptCaller: root, here, rootSession: rootSessionEnv });
|
|
290
|
-
ensureSession(session, opts.cwd);
|
|
291
|
-
// REVIVE-HOME set once at birth: a managed child's revive target is the
|
|
292
|
-
// backstage, never a user session — this is what keeps a background revive
|
|
293
|
-
// off the user's screen (the focus taint cannot reach it).
|
|
294
|
-
updateNode(meta.node_id, { home_session: session });
|
|
295
287
|
const inv = buildPiArgv(meta, { prompt: kickoff, forkFrom });
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// Birth LAUNCH branch on the PERSISTED host_kind (set at birth by spawnNode).
|
|
306
|
-
// A broker birth diverts AWAY from the tmux pane: the headless broker host
|
|
307
|
-
// launches a detached broker process (which records its own pid as pi_pid via
|
|
308
|
-
// the stophook) and returns placement fields all null — so we open NO tmux
|
|
309
|
-
// window and write NO tmux placement (presence stays null for a broker). The
|
|
310
|
-
// tmux path below is left completely UNCHANGED (byte-identical) for every
|
|
311
|
-
// non-broker spawn. Mirrors reviveNode's `hostFor(meta).launch(...)` shape.
|
|
312
|
-
if (meta.host_kind === 'broker') {
|
|
313
|
-
const placed = headlessBrokerHost.launch(meta.node_id, inv, {
|
|
314
|
-
cwd: meta.cwd,
|
|
315
|
-
name: fullName(meta),
|
|
316
|
-
resuming: false,
|
|
317
|
-
});
|
|
318
|
-
const saved = getNode(meta.node_id);
|
|
319
|
-
return { node: saved, window: placed.window, session: placed.session };
|
|
320
|
-
}
|
|
321
|
-
const env = { ...inv.env, [FRONT_DOOR_ENV]: '1' };
|
|
322
|
-
const command = piCommand(inv.argv);
|
|
323
|
-
// openNodeWindow now returns {window, pane}; pane is unused until the
|
|
324
|
-
// placement layer lands, so destructure the window and proceed unchanged.
|
|
325
|
-
const opened = openNodeWindow({
|
|
326
|
-
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,
|
|
327
297
|
name: fullName(meta),
|
|
328
|
-
|
|
329
|
-
env,
|
|
330
|
-
command,
|
|
298
|
+
resuming: false,
|
|
331
299
|
});
|
|
332
|
-
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
// is still 'active' from spawnNode, so transition('crash') is a legal from-LIVE
|
|
338
|
-
// move — the last scattered node-status write, now through the lifecycle machine.
|
|
339
|
-
//
|
|
340
|
-
// pi BOOTING inside the window, by contrast, is inherently slow (and slower
|
|
341
|
-
// under load), so we stay optimistic and return status='active' the instant
|
|
342
|
-
// the window exists. A vehicle that then dies before its first session_start
|
|
343
|
-
// is caught by the daemon — it surfaces the boot failure up the spine rather
|
|
344
|
-
// than letting the node die silently (see crtrd.ts surfaceBootFailure).
|
|
345
|
-
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) {
|
|
346
305
|
transition(meta.node_id, 'crash');
|
|
347
|
-
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.`);
|
|
348
307
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
}
|
|
356
327
|
}
|
|
357
|
-
catch { /* best-effort */ }
|
|
358
328
|
}
|
|
329
|
+
const saved = getNode(meta.node_id);
|
|
359
330
|
return { node: saved, window, session };
|
|
360
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
|
+
}
|