@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
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
//
|
|
44
44
|
// Plain TS-with-types — no imports from @earendil-works/* so this compiles
|
|
45
45
|
// inside crouter's own tsc build without a dep on the pi packages.
|
|
46
|
-
import { execFile
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import { getNode, subscribersOf, subscriptionsOf, jobDir, fullName, listFocuses } from '../core/canvas/index.js';
|
|
46
|
+
import { execFile } from 'node:child_process';
|
|
47
|
+
import { fullName } from '../core/canvas/index.js';
|
|
48
|
+
import { beginFrame, cNode, managerOf, liveReports, sortedChildIds, subtreeIds, climbRoot, computeSubtreeActivity, buildGraphModel, renderGraphRow, navLabel, coloredGlyph, truncate, tokensCell, cycleBadge, childBadge, liveBelowBadge, askBadge, activityCell, focusedNodeIds, graphWidgetBudget, fetchAsksMap, shortId, DIM, RESET, BOLD, YELLOW, GRAPH_HINT, } from '../core/canvas/nav-model.js';
|
|
50
49
|
import { readConfig } from '../core/config.js';
|
|
50
|
+
import { onNavRerender } from './widget-order-bus.js';
|
|
51
51
|
// ---------------------------------------------------------------------------
|
|
52
52
|
// Module-level state — persists across /reload so guards don't stack and fold
|
|
53
53
|
// state / current view survive a hot-swap.
|
|
@@ -65,6 +65,10 @@ let view = 'base';
|
|
|
65
65
|
* userExpanded. Both survive renders AND BASE↔GRAPH toggles. */
|
|
66
66
|
const userCollapsed = new Set();
|
|
67
67
|
const userExpanded = new Set();
|
|
68
|
+
/** A live view of the manual fold overrides for the pure nav-model layer; the
|
|
69
|
+
* Sets above are mutated in place, so this reference stays current across
|
|
70
|
+
* renders and BASE↔GRAPH toggles. */
|
|
71
|
+
const folds = { userExpanded, userCollapsed };
|
|
68
72
|
/** GRAPH cursor (a node id, not an index — indices shift as topology changes). */
|
|
69
73
|
let cursorId;
|
|
70
74
|
/** GRAPH viewport scroll offset (row index of the top visible row). */
|
|
@@ -78,366 +82,6 @@ let asksMap = {};
|
|
|
78
82
|
// ---------------------------------------------------------------------------
|
|
79
83
|
const ASK_POLL_MS = 5_000;
|
|
80
84
|
const RENDER_DEBOUNCE_MS = 150;
|
|
81
|
-
/** pi's InteractiveMode.MAX_WIDGET_LINES — the hard cap on lines in a string
|
|
82
|
-
* array widget; anything beyond it pi truncates with its own "... (widget
|
|
83
|
-
* truncated)". Our GRAPH viewport stays at/under this and scrolls internally. */
|
|
84
|
-
const PI_MAX_WIDGET_LINES = 10;
|
|
85
|
-
const VIEWPORT_FALLBACK_ROWS = 30;
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// ANSI styling. pi renders embedded escapes in widget lines and measures width
|
|
88
|
-
// ANSI-aware, so raw escapes are safe and need no pi-tui dependency. The cursor
|
|
89
|
-
// (selected row) uses a theme-agnostic ATTRIBUTE (reverse), so it reads under
|
|
90
|
-
// NO_COLOR; the attached-row tint is a background COLOUR, while the dot glyph
|
|
91
|
-
// (●/○/✓/✗) carries the running signal independently, even where colour is
|
|
92
|
-
// stripped.
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
const ESC = '\x1b[';
|
|
95
|
-
const RESET = `${ESC}0m`;
|
|
96
|
-
const BOLD = `${ESC}1m`;
|
|
97
|
-
const DIM = `${ESC}2m`;
|
|
98
|
-
const REVERSE = `${ESC}7m`;
|
|
99
|
-
/** Dark-green background bar marking an ATTACHED node (a human is currently
|
|
100
|
-
* watching it) — distinct from the cursor's reverse-video bar; chosen so
|
|
101
|
-
* default-fg text stays readable. */
|
|
102
|
-
const BG_ATTACHED = `${ESC}48;5;22m`;
|
|
103
|
-
const GREEN = `${ESC}32m`;
|
|
104
|
-
const RED = `${ESC}31m`;
|
|
105
|
-
const YELLOW = `${ESC}33m`;
|
|
106
|
-
const CYAN = `${ESC}36m`;
|
|
107
|
-
const GRAY = `${ESC}90m`;
|
|
108
|
-
/** Status glyph colored by state: active green, idle dim, done cyan, dead red. */
|
|
109
|
-
function coloredGlyph(node) {
|
|
110
|
-
if (node === null)
|
|
111
|
-
return '?';
|
|
112
|
-
switch (node.status) {
|
|
113
|
-
case 'active': return `${GREEN}●${RESET}`;
|
|
114
|
-
case 'idle': return `${GRAY}○${RESET}`;
|
|
115
|
-
case 'done': return `${CYAN}✓${RESET}`;
|
|
116
|
-
case 'dead': return `${RED}✗${RESET}`;
|
|
117
|
-
case 'canceled': return `${YELLOW}⊘${RESET}`;
|
|
118
|
-
default: return '?';
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
122
|
-
/** Visible width, ignoring ANSI escapes. */
|
|
123
|
-
function visibleWidth(s) {
|
|
124
|
-
return s.replace(ANSI_RE, '').length;
|
|
125
|
-
}
|
|
126
|
-
/** Truncate to `max` VISIBLE columns: escape sequences are copied through
|
|
127
|
-
* verbatim (so a cut never lands mid-escape) and the result always ends in
|
|
128
|
-
* RESET, so a clipped style can't bleed into the editor below. */
|
|
129
|
-
function truncate(s, max = fillWidth()) {
|
|
130
|
-
if (visibleWidth(s) <= max)
|
|
131
|
-
return s;
|
|
132
|
-
let out = '';
|
|
133
|
-
let w = 0;
|
|
134
|
-
let i = 0;
|
|
135
|
-
while (i < s.length && w < max - 1) {
|
|
136
|
-
if (s[i] === '\x1b') {
|
|
137
|
-
const m = /^\x1b\[[0-9;]*m/.exec(s.slice(i));
|
|
138
|
-
if (m) {
|
|
139
|
-
out += m[0];
|
|
140
|
-
i += m[0].length;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
out += s[i];
|
|
145
|
-
w++;
|
|
146
|
-
i++;
|
|
147
|
-
}
|
|
148
|
-
return `${out}…${RESET}`;
|
|
149
|
-
}
|
|
150
|
-
/** Visible columns available to ONE widget line — the cap for every line we
|
|
151
|
-
* emit, and the width a full-width reverse-video SELF bar fills to.
|
|
152
|
-
*
|
|
153
|
-
* pi does NOT clip widget lines; it WRAPS them. Each string line is wrapped in
|
|
154
|
-
* a `Text(paddingX = 1)` inside a full-terminal-width container, so the usable
|
|
155
|
-
* content width is `columns - 2` (a 1-col margin on each side). A line wider
|
|
156
|
-
* than that wraps, and the overflow spills onto a second row as a stray
|
|
157
|
-
* reverse-video block (the bug this guards against). Clamp to `columns - 2`. */
|
|
158
|
-
function fillWidth() {
|
|
159
|
-
return Math.max(20, Math.min((process.stdout.columns ?? 80) - 2, 180));
|
|
160
|
-
}
|
|
161
|
-
/** Wrap `content` in a full-width background bar opened by `open` (REVERSE for
|
|
162
|
-
* the cursor, BG_ATTACHED for a human-watched node). `open` is re-asserted after every
|
|
163
|
-
* embedded RESET so a coloured cell (the status dot) can't punch a hole in the
|
|
164
|
-
* bar; the visible width is padded out to `width`; the line closes with a real
|
|
165
|
-
* RESET so the style never bleeds into the editor below. */
|
|
166
|
-
function fillBar(content, width, open) {
|
|
167
|
-
const clipped = truncate(content, width);
|
|
168
|
-
const reasserted = clipped.replace(/\x1b\[0m/g, `${RESET}${open}`);
|
|
169
|
-
const pad = Math.max(0, width - visibleWidth(clipped));
|
|
170
|
-
return `${open}${reasserted}${' '.repeat(pad)}${RESET}`;
|
|
171
|
-
}
|
|
172
|
-
function readTelemetry(nodeId) {
|
|
173
|
-
try {
|
|
174
|
-
const p = join(jobDir(nodeId), 'telemetry.json');
|
|
175
|
-
if (!existsSync(p))
|
|
176
|
-
return {};
|
|
177
|
-
return JSON.parse(readFileSync(p, 'utf8'));
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
return {};
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function fmtTokens(n) {
|
|
184
|
-
return n < 1_000 ? `${n}` : `${Math.round(n / 1_000)}k`;
|
|
185
|
-
}
|
|
186
|
-
function tokensCell(id) {
|
|
187
|
-
return fmtTokens(readTelemetry(id).tokens_in ?? 0);
|
|
188
|
-
}
|
|
189
|
-
function shortId(id) {
|
|
190
|
-
return id.slice(0, 8);
|
|
191
|
-
}
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
|
-
// Attachment — is a human currently WATCHING a node? A separate axis from
|
|
194
|
-
// running (status 'active' = the engine is live on its host, which may be an
|
|
195
|
-
// unwatched backstage pane or a paneless broker). Two hosts, two signals:
|
|
196
|
-
// tmux — a `focuses` row points at the node (one cheap sqlite read per
|
|
197
|
-
// render pass; pane-existence alone is NOT the signal).
|
|
198
|
-
// broker — the broker persists its helloed-viewer count to job/attach.json
|
|
199
|
-
// on every viewer change (src/core/runtime/broker.ts). Trusted only
|
|
200
|
-
// while the node is 'active': a broker crash can leave a stale file.
|
|
201
|
-
// ---------------------------------------------------------------------------
|
|
202
|
-
/** Node ids currently shown in a tmux focus viewport. Built once per render. */
|
|
203
|
-
function focusedNodeIds() {
|
|
204
|
-
try {
|
|
205
|
-
return new Set(listFocuses().map((f) => f.node_id));
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
return new Set();
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
/** True when a human is watching `id` right now (tmux focus or broker viewer). */
|
|
212
|
-
function isAttached(id, node, focused) {
|
|
213
|
-
if (focused.has(id))
|
|
214
|
-
return true;
|
|
215
|
-
if (node?.status !== 'active')
|
|
216
|
-
return false; // stale attach.json from a crash
|
|
217
|
-
try {
|
|
218
|
-
const p = join(jobDir(id), 'attach.json');
|
|
219
|
-
if (!existsSync(p))
|
|
220
|
-
return false;
|
|
221
|
-
const rec = JSON.parse(readFileSync(p, 'utf8'));
|
|
222
|
-
return typeof rec.viewers === 'number' && rec.viewers > 0;
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
// Per-node ask counts — ONE shell-out per poll. `crtr canvas attention map`
|
|
230
|
-
// buckets a whole sub-DAG's pending asks by node in a single process, so the
|
|
231
|
-
// timer stays cheap (< 2 s) regardless of how many nodes are visible. --json
|
|
232
|
-
// gives a parseable {counts} blob (the default render is XML chrome).
|
|
233
|
-
// ---------------------------------------------------------------------------
|
|
234
|
-
function fetchAsksMap(rootId) {
|
|
235
|
-
try {
|
|
236
|
-
const raw = execFileSync('crtr', ['canvas', 'attention', 'map', '--view', rootId, '--json'], {
|
|
237
|
-
timeout: 2_500,
|
|
238
|
-
encoding: 'utf8',
|
|
239
|
-
});
|
|
240
|
-
const parsed = JSON.parse(raw.trim());
|
|
241
|
-
return parsed.counts ?? {};
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
return {};
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
// Graph queries (dependency-free, straight off the canvas db)
|
|
249
|
-
// ---------------------------------------------------------------------------
|
|
250
|
-
/** First manager (by created) — the UP step for the ancestry spine. */
|
|
251
|
-
function managerOf(id) {
|
|
252
|
-
try {
|
|
253
|
-
return subscribersOf(id)[0]?.node_id;
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
return undefined;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
/** A kind:'human' node is a control-plane ASK (a humanloop deck on the human's
|
|
260
|
-
* screen), NOT a pi conversation — it has no session, so focusing/reviving it
|
|
261
|
-
* boots a confused blank "you have been revived" pi. Its pending-ask signal
|
|
262
|
-
* already rides the ⚑ badge on the ASKING node (attention.ts attributes asks by
|
|
263
|
-
* source.nodeId, never to the human node), so the row carries no signal of its
|
|
264
|
-
* own. Drop it from every navigable list (the tree, BASE reports, child counts,
|
|
265
|
-
* subtree expansion) so it can never be selected. */
|
|
266
|
-
function isHumanAsk(id) {
|
|
267
|
-
return getNode(id)?.kind === 'human';
|
|
268
|
-
}
|
|
269
|
-
/** A node's direct children that are navigable conversations — human-ask nodes
|
|
270
|
-
* dropped. The one place the nav chrome enumerates children. */
|
|
271
|
-
function convoChildIds(id) {
|
|
272
|
-
try {
|
|
273
|
-
return subscriptionsOf(id).map((s) => s.node_id).filter((cid) => !isHumanAsk(cid));
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
return [];
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
/** Live reports (active|idle) of a node — the DOWN set in BASE. */
|
|
280
|
-
function liveReports(id) {
|
|
281
|
-
return convoChildIds(id).filter((cid) => {
|
|
282
|
-
const st = getNode(cid)?.status;
|
|
283
|
-
return st === 'active' || st === 'idle';
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
/** Direct navigable children — used for the ⤳ badge and fold counts (human-ask
|
|
287
|
-
* nodes excluded, so the count matches what the tree actually shows). */
|
|
288
|
-
function childCount(id) {
|
|
289
|
-
return convoChildIds(id).length;
|
|
290
|
-
}
|
|
291
|
-
/** Climb first-manager edges from `self` to the ancestry root (cycle-guarded). */
|
|
292
|
-
function climbRoot(self) {
|
|
293
|
-
let cur = self;
|
|
294
|
-
const seen = new Set([cur]);
|
|
295
|
-
for (;;) {
|
|
296
|
-
const mgr = managerOf(cur);
|
|
297
|
-
if (mgr === undefined || seen.has(mgr))
|
|
298
|
-
break;
|
|
299
|
-
seen.add(mgr);
|
|
300
|
-
cur = mgr;
|
|
301
|
-
}
|
|
302
|
-
return cur;
|
|
303
|
-
}
|
|
304
|
-
/** Space-joined ids of a node's subtree (cursor-relative {subtree} var). */
|
|
305
|
-
function subtreeIds(root) {
|
|
306
|
-
const out = [];
|
|
307
|
-
const seen = new Set([root]);
|
|
308
|
-
const q = convoChildIds(root);
|
|
309
|
-
while (q.length > 0) {
|
|
310
|
-
const id = q.shift();
|
|
311
|
-
if (seen.has(id))
|
|
312
|
-
continue;
|
|
313
|
-
seen.add(id);
|
|
314
|
-
out.push(id);
|
|
315
|
-
for (const cid of convoChildIds(id))
|
|
316
|
-
if (!seen.has(cid))
|
|
317
|
-
q.push(cid);
|
|
318
|
-
}
|
|
319
|
-
return out;
|
|
320
|
-
}
|
|
321
|
-
// ---------------------------------------------------------------------------
|
|
322
|
-
// Shared cell builders
|
|
323
|
-
// ---------------------------------------------------------------------------
|
|
324
|
-
/** ⤳M direct-children badge — only on orchestrator rows. */
|
|
325
|
-
function childBadge(node) {
|
|
326
|
-
if (node === null || node.mode !== 'orchestrator')
|
|
327
|
-
return '';
|
|
328
|
-
const m = childCount(node.node_id);
|
|
329
|
-
return m > 0 ? ` ${DIM}⤳${m}${RESET}` : '';
|
|
330
|
-
}
|
|
331
|
-
/** ⚑K pending-asks badge for a node, read from the cached map. */
|
|
332
|
-
function askBadge(id) {
|
|
333
|
-
const k = asksMap[id] ?? 0;
|
|
334
|
-
return k > 0 ? ` ${YELLOW}⚑${k}${RESET}` : '';
|
|
335
|
-
}
|
|
336
|
-
/** Sort rank for sibling ordering — live nodes (active, then idle) ahead of
|
|
337
|
-
* terminal ones, so sessions still running surface at the TOP of each child
|
|
338
|
-
* group instead of being buried under finished/failed ones. */
|
|
339
|
-
function statusRank(id) {
|
|
340
|
-
switch (getNode(id)?.status) {
|
|
341
|
-
case 'active': return 0;
|
|
342
|
-
case 'idle': return 1;
|
|
343
|
-
case 'done': return 2;
|
|
344
|
-
case 'canceled': return 3;
|
|
345
|
-
case 'dead': return 4;
|
|
346
|
-
default: return 5;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
/** Direct children, live-first — the sibling order used both when flattening
|
|
350
|
-
* the tree and when stepping into a subtree (`l`). Array.sort is stable, so
|
|
351
|
-
* equal-status siblings keep their creation order. */
|
|
352
|
-
function sortedChildIds(id) {
|
|
353
|
-
return convoChildIds(id).sort((a, b) => statusRank(a) - statusRank(b));
|
|
354
|
-
}
|
|
355
|
-
/** Default fold policy: which nodes auto-EXPAND. A node expands only when one
|
|
356
|
-
* of its child subtrees holds a running ('active') agent or self — so the path
|
|
357
|
-
* to any live agent (and to you) is revealed while quiescent branches stay
|
|
358
|
-
* folded. One bottom-up O(N) pass from the ancestry root; cycle-guarded. */
|
|
359
|
-
function computeDefaultExpanded(root, self) {
|
|
360
|
-
const expand = new Set();
|
|
361
|
-
const seen = new Set();
|
|
362
|
-
// Returns whether subtree(id), INCLUDING id, holds an active node or self.
|
|
363
|
-
const visit = (id) => {
|
|
364
|
-
if (seen.has(id))
|
|
365
|
-
return id === self || getNode(id)?.status === 'active';
|
|
366
|
-
seen.add(id);
|
|
367
|
-
let childRevealing = false;
|
|
368
|
-
for (const c of convoChildIds(id))
|
|
369
|
-
if (visit(c))
|
|
370
|
-
childRevealing = true;
|
|
371
|
-
if (childRevealing)
|
|
372
|
-
expand.add(id); // a descendant is worth revealing → unfold id
|
|
373
|
-
return childRevealing || id === self || getNode(id)?.status === 'active';
|
|
374
|
-
};
|
|
375
|
-
visit(root);
|
|
376
|
-
return expand;
|
|
377
|
-
}
|
|
378
|
-
function buildGraphModel(self) {
|
|
379
|
-
const rootId = climbRoot(self);
|
|
380
|
-
const defaultExpanded = computeDefaultExpanded(rootId, self);
|
|
381
|
-
// userExpanded / userCollapsed override the auto policy; absent → policy decides.
|
|
382
|
-
const isFolded = (id) => userExpanded.has(id) ? false : userCollapsed.has(id) ? true : !defaultExpanded.has(id);
|
|
383
|
-
const rows = [];
|
|
384
|
-
const visited = new Set();
|
|
385
|
-
const walk = (id, prefix, isRoot, isLast) => {
|
|
386
|
-
if (visited.has(id)) {
|
|
387
|
-
const connector = isRoot ? '' : isLast ? '└─ ' : '├─ ';
|
|
388
|
-
rows.push({ id, hasKids: false, isSelf: id === self, branch: prefix + connector, cycle: true, collapsed: false });
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
visited.add(id);
|
|
392
|
-
const kids = sortedChildIds(id);
|
|
393
|
-
const folded = isFolded(id);
|
|
394
|
-
const connector = isRoot ? '' : isLast ? '└─ ' : '├─ ';
|
|
395
|
-
rows.push({ id, hasKids: kids.length > 0, isSelf: id === self, branch: prefix + connector, cycle: false, collapsed: folded });
|
|
396
|
-
if (folded)
|
|
397
|
-
return; // folded — don't descend
|
|
398
|
-
const childPrefix = isRoot ? '' : prefix + (isLast ? ' ' : '│ ');
|
|
399
|
-
for (let i = 0; i < kids.length; i++)
|
|
400
|
-
walk(kids[i], childPrefix, false, i === kids.length - 1);
|
|
401
|
-
};
|
|
402
|
-
walk(rootId, '', true, true);
|
|
403
|
-
return rows;
|
|
404
|
-
}
|
|
405
|
-
/** Render one GRAPH row. CURSOR (selected) → reverse-video bar; an ATTACHED
|
|
406
|
-
* (human-watched) node → a coloured background bar; SELF → bold name. The
|
|
407
|
-
* cursor outranks the attached tint when both land on the same row. Running
|
|
408
|
-
* is signaled by the dot glyph alone (● green = active engine). */
|
|
409
|
-
function renderGraphRow(r, isCursor, focused) {
|
|
410
|
-
const wrap = (line, attached) => isCursor ? fillBar(line, fillWidth(), REVERSE)
|
|
411
|
-
: attached ? fillBar(line, fillWidth(), BG_ATTACHED)
|
|
412
|
-
: truncate(line);
|
|
413
|
-
if (r.cycle) {
|
|
414
|
-
const line = `${r.branch} ${DIM}↺ ${shortId(r.id)}${RESET}`;
|
|
415
|
-
return wrap(line, false);
|
|
416
|
-
}
|
|
417
|
-
const node = getNode(r.id);
|
|
418
|
-
const dot = coloredGlyph(node);
|
|
419
|
-
const rawName = node !== null ? fullName(node) : shortId(r.id);
|
|
420
|
-
const name = r.isSelf ? `${BOLD}${rawName}${RESET}` : rawName;
|
|
421
|
-
const kind = `${DIM}${node?.kind ?? ''}${RESET}`;
|
|
422
|
-
const tokens = `${DIM}${tokensCell(r.id)}${RESET}`;
|
|
423
|
-
// ▸ marks an expandable (collapsed-with-kids) row. The cursor row gets no
|
|
424
|
-
// caret — its reverse-video bar already distinguishes it — so the triangle
|
|
425
|
-
// reads purely as "this unfolds".
|
|
426
|
-
const expandable = r.hasKids && r.collapsed;
|
|
427
|
-
const caret = !isCursor && expandable ? `${DIM}▸${RESET} ` : ' ';
|
|
428
|
-
const fold = expandable ? ` ${DIM}[+${childCount(r.id)}]${RESET}` : '';
|
|
429
|
-
const line = `${r.branch}${caret}${dot} ${name} ${kind} ${tokens}${childBadge(node)}${fold}${askBadge(r.id)}`;
|
|
430
|
-
return wrap(line, isAttached(r.id, node, focused));
|
|
431
|
-
}
|
|
432
|
-
/** Total lines the GRAPH widget may emit. pi hard-caps extension widgets at
|
|
433
|
-
* MAX_WIDGET_LINES — anything past that pi truncates itself, eating our own
|
|
434
|
-
* scroll chrome — so never exceed it (and shrink on a very short terminal).
|
|
435
|
-
* The viewport scrolls WITHIN this cap as the cursor moves. */
|
|
436
|
-
function graphWidgetBudget() {
|
|
437
|
-
const rows = process.stdout.rows ?? VIEWPORT_FALLBACK_ROWS;
|
|
438
|
-
return Math.max(4, Math.min(PI_MAX_WIDGET_LINES, rows - 4));
|
|
439
|
-
}
|
|
440
|
-
const GRAPH_HINT = `${DIM}jk move · hl fold · ↵ focus · e expand · x kill · m mgr · esc${RESET}`;
|
|
441
85
|
// ---------------------------------------------------------------------------
|
|
442
86
|
// Key decoding — recognizers tolerant of legacy, kitty/CSI-u and
|
|
443
87
|
// modifyOtherKeys encodings (pi enables the kitty / modifyOtherKeys protocols,
|
|
@@ -552,31 +196,31 @@ export function registerCanvasNav(pi) {
|
|
|
552
196
|
const renderBase = () => {
|
|
553
197
|
if (ui === undefined)
|
|
554
198
|
return;
|
|
199
|
+
// One subtree-activity pass (rooted at the ancestry root) feeds the ⇣N
|
|
200
|
+
// live-work-below badge on both the manager line and every report row.
|
|
201
|
+
const activity = computeSubtreeActivity(climbRoot(nodeId), nodeId);
|
|
555
202
|
const mgr = managerOf(nodeId);
|
|
556
203
|
if (mgr === undefined) {
|
|
557
204
|
// Root node: no manager → drop the widget rather than show "↑ (root)" chrome.
|
|
558
205
|
ui.setWidget('crtr-managers', undefined, { placement: 'aboveEditor' });
|
|
559
206
|
}
|
|
560
207
|
else {
|
|
561
|
-
const mn =
|
|
562
|
-
const name =
|
|
563
|
-
const mgrLine = truncate(`↑ ${name} ${coloredGlyph(mn)} ${DIM}${mn?.kind ?? ''}${RESET} ${DIM}${tokensCell(mgr)}${RESET}${childBadge(mn)}${askBadge(mgr)}`);
|
|
208
|
+
const mn = cNode(mgr);
|
|
209
|
+
const name = navLabel(mn, mgr);
|
|
210
|
+
const mgrLine = truncate(`↑ ${name} ${coloredGlyph(mn)} ${DIM}${mn?.kind ?? ''}${RESET} ${DIM}${tokensCell(mgr)}${RESET}${cycleBadge(mn)}${childBadge(mn)}${liveBelowBadge(mn, activity.activeBelow)}${askBadge(mgr, asksMap)}${activityCell(mgr, mn)}`);
|
|
564
211
|
ui.setWidget('crtr-managers', [mgrLine], { placement: 'aboveEditor' });
|
|
565
212
|
}
|
|
566
213
|
const reports = liveReports(nodeId);
|
|
567
214
|
const lines = [];
|
|
568
215
|
// Report rows only — no "↓ reports (N)" header (the label carries no signal).
|
|
569
216
|
if (reports.length > 0) {
|
|
570
|
-
const nameW = Math.min(20, Math.max(...reports.map((id) =>
|
|
571
|
-
const n = getNode(id);
|
|
572
|
-
return (n !== null ? fullName(n) : shortId(id)).length;
|
|
573
|
-
})));
|
|
217
|
+
const nameW = Math.min(20, Math.max(...reports.map((id) => navLabel(cNode(id), id).length)));
|
|
574
218
|
for (const id of reports) {
|
|
575
|
-
const n =
|
|
576
|
-
const name = (n
|
|
219
|
+
const n = cNode(id);
|
|
220
|
+
const name = navLabel(n, id).padEnd(nameW);
|
|
577
221
|
const kind = `${DIM}${(n?.kind ?? '').padEnd(6)}${RESET}`;
|
|
578
222
|
const tokens = `${DIM}${tokensCell(id).padStart(5)}${RESET}`;
|
|
579
|
-
lines.push(truncate(` ${coloredGlyph(n)} ${name} ${kind} ${tokens}${childBadge(n)}${askBadge(id)}`));
|
|
223
|
+
lines.push(truncate(` ${coloredGlyph(n)} ${name} ${kind} ${tokens}${cycleBadge(n)}${childBadge(n)}${liveBelowBadge(n, activity.activeBelow)}${askBadge(id, asksMap)}${activityCell(id, n)}`));
|
|
580
224
|
}
|
|
581
225
|
}
|
|
582
226
|
// Self's own pending asks (no self row in BASE) → a trailing inline line.
|
|
@@ -592,7 +236,10 @@ export function registerCanvasNav(pi) {
|
|
|
592
236
|
const renderGraph = () => {
|
|
593
237
|
if (ui === undefined)
|
|
594
238
|
return;
|
|
595
|
-
|
|
239
|
+
// One subtree-activity pass feeds BOTH the fold policy (which rows show) and
|
|
240
|
+
// the ⇣N live-work-below badge — computed once here, never re-walked per row.
|
|
241
|
+
const activity = computeSubtreeActivity(climbRoot(nodeId), nodeId);
|
|
242
|
+
const rows = buildGraphModel(nodeId, folds, activity.expand);
|
|
596
243
|
// Re-resolve the cursor id → row (it may have vanished under a fold or a
|
|
597
244
|
// close); clamp to nearest visible row.
|
|
598
245
|
let cursorIdx = rows.findIndex((r) => r.id === cursorId);
|
|
@@ -634,7 +281,7 @@ export function registerCanvasNav(pi) {
|
|
|
634
281
|
if (scrollTop > 0)
|
|
635
282
|
lines.push(`${DIM} ↑ ${scrollTop} more${RESET}`);
|
|
636
283
|
for (let i = scrollTop; i < end; i++)
|
|
637
|
-
lines.push(renderGraphRow(rows[i], i === cursorIdx, focused));
|
|
284
|
+
lines.push(renderGraphRow(rows[i], i === cursorIdx, focused, activity.activeBelow, asksMap));
|
|
638
285
|
if (end < rows.length)
|
|
639
286
|
lines.push(`${DIM} ↓ ${rows.length - end} more${RESET}`);
|
|
640
287
|
const hint = pendingConfirm !== undefined
|
|
@@ -649,6 +296,9 @@ export function registerCanvasNav(pi) {
|
|
|
649
296
|
const render = () => {
|
|
650
297
|
if (ui === undefined)
|
|
651
298
|
return;
|
|
299
|
+
// Fresh snapshot per render: drop last frame's memoized node/telemetry/edge
|
|
300
|
+
// reads so this paint reflects current disk+db state, then read-once within it.
|
|
301
|
+
beginFrame();
|
|
652
302
|
try {
|
|
653
303
|
if (view === 'graph')
|
|
654
304
|
renderGraph();
|
|
@@ -668,6 +318,10 @@ export function registerCanvasNav(pi) {
|
|
|
668
318
|
render();
|
|
669
319
|
}, RENDER_DEBOUNCE_MS);
|
|
670
320
|
};
|
|
321
|
+
// Let canvas-recap ask us to re-assert the manager line so its recap card
|
|
322
|
+
// stays the topmost aboveEditor chrome (pi's widget store is insertion-
|
|
323
|
+
// ordered; re-setting crtr-managers drops it below crtr-recap).
|
|
324
|
+
onNavRerender(scheduleRender);
|
|
671
325
|
// -------------------------------------------------------------------------
|
|
672
326
|
// Actions (all shell out; the extension stays tmux/revive-free)
|
|
673
327
|
// -------------------------------------------------------------------------
|
|
@@ -697,7 +351,7 @@ export function registerCanvasNav(pi) {
|
|
|
697
351
|
view = 'graph';
|
|
698
352
|
pendingConfirm = undefined;
|
|
699
353
|
scrollTop = 0;
|
|
700
|
-
if (cursorId === undefined ||
|
|
354
|
+
if (cursorId === undefined || cNode(cursorId) === null)
|
|
701
355
|
cursorId = nodeId;
|
|
702
356
|
render();
|
|
703
357
|
};
|
|
@@ -714,7 +368,7 @@ export function registerCanvasNav(pi) {
|
|
|
714
368
|
};
|
|
715
369
|
/** Template vars for a graphBind, resolved against the CURSOR node. */
|
|
716
370
|
const graphVars = (cur) => {
|
|
717
|
-
const cn =
|
|
371
|
+
const cn = cNode(cur);
|
|
718
372
|
return {
|
|
719
373
|
id: cur,
|
|
720
374
|
self: nodeId,
|
|
@@ -749,7 +403,7 @@ export function registerCanvasNav(pi) {
|
|
|
749
403
|
exitGraph();
|
|
750
404
|
return { consume: true };
|
|
751
405
|
}
|
|
752
|
-
const rows = buildGraphModel(nodeId);
|
|
406
|
+
const rows = buildGraphModel(nodeId, folds);
|
|
753
407
|
let idx = rows.findIndex((r) => r.id === cursorId);
|
|
754
408
|
if (idx < 0)
|
|
755
409
|
idx = Math.max(0, rows.findIndex((r) => r.id === nodeId));
|
|
@@ -815,13 +469,9 @@ export function registerCanvasNav(pi) {
|
|
|
815
469
|
render();
|
|
816
470
|
return { consume: true };
|
|
817
471
|
}
|
|
818
|
-
if (isPlain(data, 'e')) {
|
|
819
|
-
shellCrtr(['canvas', 'tmux-spread', nodeId]);
|
|
820
|
-
return { consume: true };
|
|
821
|
-
}
|
|
822
472
|
if (isPlain(data, 'x')) {
|
|
823
473
|
const target = cursorId ?? nodeId;
|
|
824
|
-
const n =
|
|
474
|
+
const n = cNode(target);
|
|
825
475
|
const nm = n !== null ? fullName(n) : shortId(target);
|
|
826
476
|
pendingConfirm = { label: `kill ${nm}?`, action: () => shellCrtr(['node', 'close', '--node', target], render) };
|
|
827
477
|
render();
|
|
@@ -838,7 +488,7 @@ export function registerCanvasNav(pi) {
|
|
|
838
488
|
if (argv.length === 0)
|
|
839
489
|
return { consume: true };
|
|
840
490
|
if (bind.confirm === true) {
|
|
841
|
-
const n =
|
|
491
|
+
const n = cNode(target);
|
|
842
492
|
const nm = n !== null ? fullName(n) : shortId(target);
|
|
843
493
|
pendingConfirm = { label: `${bind.desc ?? bind.run} ${nm}?`, action: () => shellCrtr(argv, render) };
|
|
844
494
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface TextContentLike {
|
|
2
|
+
type: string;
|
|
3
|
+
text?: string;
|
|
4
|
+
}
|
|
5
|
+
interface MessageLike {
|
|
6
|
+
role: string;
|
|
7
|
+
content: string | TextContentLike[];
|
|
8
|
+
}
|
|
9
|
+
interface SessionEntryLike {
|
|
10
|
+
type: string;
|
|
11
|
+
message?: MessageLike;
|
|
12
|
+
}
|
|
13
|
+
interface SessionManagerLike {
|
|
14
|
+
getEntries(): SessionEntryLike[];
|
|
15
|
+
}
|
|
16
|
+
interface UIContextLike {
|
|
17
|
+
setWidget(key: string, content: string[] | undefined, options?: {
|
|
18
|
+
placement?: 'aboveEditor' | 'belowEditor';
|
|
19
|
+
}): void;
|
|
20
|
+
}
|
|
21
|
+
interface ExtensionCtxLike {
|
|
22
|
+
ui?: UIContextLike;
|
|
23
|
+
mode?: string;
|
|
24
|
+
sessionManager?: SessionManagerLike;
|
|
25
|
+
}
|
|
26
|
+
type PiEvents = 'session_start' | 'input' | 'turn_end' | 'session_shutdown';
|
|
27
|
+
interface PiLike {
|
|
28
|
+
on(event: PiEvents, handler: (event: any, ctx: ExtensionCtxLike) => void): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register the inactivity-recap chrome on `pi`.
|
|
32
|
+
*
|
|
33
|
+
* Returns immediately (inert) when CRTR_NODE_ID is absent — a plain pi session
|
|
34
|
+
* or legacy job agent loads it as a no-op.
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerCanvasRecap(pi: PiLike): void;
|
|
37
|
+
export default registerCanvasRecap;
|