@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
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/placement-teardown.test.ts
|
|
2
|
-
//
|
|
3
|
-
// STEP 7 of the placement/focus migration: the lifecycle-successor + teardown
|
|
4
|
-
// verbs (§1.6/§2.3, flow (e)). Two PURE surfaces (no tmux — pane ids below are
|
|
5
|
-
// never realized, so closePane no-ops and only the DB effects are asserted):
|
|
6
|
-
//
|
|
7
|
-
// • handFocusToManager(focusId, managerId) — the §1.6 manager-takeover, a PURE
|
|
8
|
-
// DB occupant swap. Returns true (TAKEOVER) only when a successor will
|
|
9
|
-
// GENUINELY claim the frozen pane — a live manager (swapped in) or a dormant
|
|
10
|
-
// manager the daemon will revive (status='idle' && intent='idle-release').
|
|
11
|
-
// Returns false (caller disarms the freeze + closes the focus) for every
|
|
12
|
-
// other case; each guard is asserted distinctly.
|
|
13
|
-
// • tearDownNode(nodeId) — close/reset teardown: close the focus row it
|
|
14
|
-
// occupies and null its LOCATION.
|
|
15
|
-
import { test, before, after, beforeEach } from 'node:test';
|
|
16
|
-
import assert from 'node:assert/strict';
|
|
17
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
18
|
-
import { tmpdir } from 'node:os';
|
|
19
|
-
import { join } from 'node:path';
|
|
20
|
-
import { spawnSync } from 'node:child_process';
|
|
21
|
-
import { createNode, getNode } from '../../canvas/canvas.js';
|
|
22
|
-
import { openFocusRow, getFocusByNode, getFocusById } from '../../canvas/focuses.js';
|
|
23
|
-
import { closeDb } from '../../canvas/db.js';
|
|
24
|
-
import { handFocusToManager, tearDownNode } from '../../runtime/placement.js';
|
|
25
|
-
let home;
|
|
26
|
-
function node(id, over = {}) {
|
|
27
|
-
return {
|
|
28
|
-
node_id: id,
|
|
29
|
-
name: id,
|
|
30
|
-
created: new Date().toISOString(),
|
|
31
|
-
cwd: '/tmp/work',
|
|
32
|
-
kind: 'developer',
|
|
33
|
-
mode: 'base',
|
|
34
|
-
lifecycle: 'terminal',
|
|
35
|
-
status: 'active',
|
|
36
|
-
...over,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function hasTmux() {
|
|
40
|
-
return spawnSync('tmux', ['-V'], { stdio: 'ignore' }).status === 0;
|
|
41
|
-
}
|
|
42
|
-
function tmuxOut(args) {
|
|
43
|
-
return (spawnSync('tmux', args, { encoding: 'utf8' }).stdout ?? '').trim();
|
|
44
|
-
}
|
|
45
|
-
function paneSession(pane) {
|
|
46
|
-
return tmuxOut(['display-message', '-p', '-t', pane, '#{session_name}']);
|
|
47
|
-
}
|
|
48
|
-
function windowCount(session) {
|
|
49
|
-
return tmuxOut(['list-windows', '-t', session, '-F', '#{window_id}']).split('\n').filter((s) => s !== '').length;
|
|
50
|
-
}
|
|
51
|
-
before(() => {
|
|
52
|
-
home = mkdtempSync(join(tmpdir(), 'crtr-placement-teardown-'));
|
|
53
|
-
process.env['CRTR_HOME'] = home;
|
|
54
|
-
});
|
|
55
|
-
beforeEach(() => {
|
|
56
|
-
closeDb();
|
|
57
|
-
rmSync(home, { recursive: true, force: true });
|
|
58
|
-
});
|
|
59
|
-
after(() => {
|
|
60
|
-
closeDb();
|
|
61
|
-
rmSync(home, { recursive: true, force: true });
|
|
62
|
-
delete process.env['CRTR_HOME'];
|
|
63
|
-
});
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// handFocusToManager (pure DB) — §1.6 manager-takeover + its three false-guards.
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
test('handFocusToManager: dormant idle-release manager → TAKEOVER (repoints the row, returns true)', () => {
|
|
68
|
-
openFocusRow('f', '%m', 'Sa', 'M');
|
|
69
|
-
// The daemon's superviseTick second pass revives a node ONLY when it is idle +
|
|
70
|
-
// idle-release (crtrd.ts ~309) — that is the ONLY dormant manager that will be
|
|
71
|
-
// brought into the frozen %m, so it is the ONLY dormant takeover that returns true.
|
|
72
|
-
createNode(node('mgr', { status: 'idle', intent: 'idle-release' }));
|
|
73
|
-
assert.equal(handFocusToManager('f', 'mgr'), true, 'a manager the daemon WILL revive takes the focus');
|
|
74
|
-
assert.equal(getFocusByNode('mgr')?.focus_id, 'f', 'the row now shows the manager');
|
|
75
|
-
assert.equal(getFocusByNode('M'), null, 'the finished node no longer occupies it');
|
|
76
|
-
// Non-vacuous: a no-op impl returns false / leaves M as occupant → both the
|
|
77
|
-
// return value and the occupant assert fail.
|
|
78
|
-
});
|
|
79
|
-
test('handFocusToManager: managerId null → false, occupant UNCHANGED', () => {
|
|
80
|
-
openFocusRow('f', '%m', 'Sa', 'M');
|
|
81
|
-
assert.equal(handFocusToManager('f', null), false, 'no manager → caller must close the focus');
|
|
82
|
-
assert.equal(getFocusById('f')?.node_id, 'M', 'occupant unchanged');
|
|
83
|
-
// Non-vacuous: an impl that skipped the null guard would either return true or
|
|
84
|
-
// setFocusOccupant(f, null) → NOT NULL violation; the clean false + unchanged
|
|
85
|
-
// occupant fail against both.
|
|
86
|
-
});
|
|
87
|
-
test('handFocusToManager: manager IS the focus occupant → false (no self-handoff)', () => {
|
|
88
|
-
openFocusRow('f', '%m', 'Sa', 'M');
|
|
89
|
-
assert.equal(handFocusToManager('f', 'M'), false, 'handing a node its own focus is a no-op false');
|
|
90
|
-
assert.equal(getFocusById('f')?.node_id, 'M', 'occupant unchanged');
|
|
91
|
-
// Non-vacuous: an impl missing the `managerId === f.node_id` guard would return
|
|
92
|
-
// true.
|
|
93
|
-
});
|
|
94
|
-
test('handFocusToManager: manager already focused ELSEWHERE → false, neither focus moved (UNIQUE node_id)', () => {
|
|
95
|
-
openFocusRow('fM', '%m', 'Sa', 'M');
|
|
96
|
-
openFocusRow('fMgr', '%g', 'Sb', 'mgr'); // mgr already occupies its own viewport
|
|
97
|
-
assert.equal(handFocusToManager('fM', 'mgr'), false, 'a busy manager is not moved — caller closes');
|
|
98
|
-
assert.equal(getFocusById('fM')?.node_id, 'M', "M's focus is not handed over");
|
|
99
|
-
assert.equal(getFocusByNode('mgr')?.focus_id, 'fMgr', "mgr's other focus is untouched");
|
|
100
|
-
// Non-vacuous: an impl missing the already-focused guard would setFocusOccupant
|
|
101
|
-
// (fM, mgr) → UNIQUE(node_id) violation (mgr already in fMgr), so the expected
|
|
102
|
-
// clean false + both rows intact fail.
|
|
103
|
-
});
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// MAJOR 1 — the LIVE-vs-DORMANT manager split inside handFocusToManager.
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
test('handFocusToManager: LIVE backstage manager → swaps its pane INTO the focus slot (MAJOR 1)', { skip: !hasTmux() }, () => {
|
|
108
|
-
// Two real sessions: `user` (the viewport %m sits frozen in) + `back` (where
|
|
109
|
-
// the manager's pi runs live, the normal multi-child state). The swap is a
|
|
110
|
-
// real tmux op, so this needs a live server (gated like the other §5.2 tests).
|
|
111
|
-
const user = `crtr-hfm-user-${process.pid}`;
|
|
112
|
-
const back = `crtr-hfm-back-${process.pid}`;
|
|
113
|
-
spawnSync('tmux', ['new-session', '-d', '-s', user, '-c', '/tmp', 'sleep 600']);
|
|
114
|
-
spawnSync('tmux', ['new-session', '-d', '-s', back, '-c', '/tmp', 'sleep 600']);
|
|
115
|
-
try {
|
|
116
|
-
const userWindow = tmuxOut(['list-windows', '-t', user, '-F', '#{window_id}']).split('\n')[0];
|
|
117
|
-
const backWindow = tmuxOut(['list-windows', '-t', back, '-F', '#{window_id}']).split('\n')[0];
|
|
118
|
-
const focusPane = tmuxOut(['display-message', '-p', '-t', `${user}:${userWindow}`, '#{pane_id}']); // M's frozen focus pane (%m, the viewport)
|
|
119
|
-
const mgrPane = tmuxOut(['split-window', '-d', '-P', '-F', '#{pane_id}', '-t', `${back}:${backWindow}`, 'sleep 600']); // mgr's LIVE backstage pane
|
|
120
|
-
createNode(node('M', { pane: focusPane, tmux_session: user, window: userWindow, status: 'done' }));
|
|
121
|
-
createNode(node('mgr', { pane: mgrPane, tmux_session: back, window: backWindow, status: 'active', pi_pid: process.pid }));
|
|
122
|
-
openFocusRow('f', focusPane, user, 'M');
|
|
123
|
-
const userWinsBefore = windowCount(user);
|
|
124
|
-
assert.equal(handFocusToManager('f', 'mgr'), true, 'a LIVE manager takes the focus');
|
|
125
|
-
// Pure-DB takeover (also covered ungated above): occupant repointed M → mgr.
|
|
126
|
-
assert.equal(getFocusByNode('mgr')?.focus_id, 'f', 'the manager occupies the focus row');
|
|
127
|
-
assert.equal(getFocusByNode('M'), null, 'the finished node no longer occupies it');
|
|
128
|
-
// The synchronous swap (MAJOR 1): the focus row re-anchors to mgr's pane,
|
|
129
|
-
// which has physically moved into the user viewport; the old focus pane (%m)
|
|
130
|
-
// swapped down into the backstage slot; mgr's presence is the OLD focus slot.
|
|
131
|
-
assert.equal(getFocusById('f')?.pane, mgrPane, 'focus row re-anchored to the manager pane (swap)');
|
|
132
|
-
assert.equal(paneSession(mgrPane), user, 'the manager pane physically moved into the user viewport');
|
|
133
|
-
assert.equal(getNode('mgr').window, userWindow, 'manager presence window == the OLD focus slot window');
|
|
134
|
-
assert.equal(getNode('mgr').tmux_session, user, 'manager presence session == the viewport');
|
|
135
|
-
assert.equal(paneSession(focusPane), back, '%m (the dead node\'s pane) swapped DOWN into the backstage slot');
|
|
136
|
-
assert.equal(windowCount(user), userWinsBefore, 'no new user window — a swap, not an open (screen position invariant)');
|
|
137
|
-
// Non-vacuous: the pre-MAJOR-1 impl only repointed the occupant and deferred
|
|
138
|
-
// the physical revive to the daemon — but the daemon revives ONLY dormant
|
|
139
|
-
// managers, so a LIVE one is never brought in. That impl leaves the focus row
|
|
140
|
-
// pane == focusPane, mgrPane still in `back`, %m frozen in the viewport, and
|
|
141
|
-
// mgr's presence at the backstage — every swap assert above fails against it.
|
|
142
|
-
}
|
|
143
|
-
finally {
|
|
144
|
-
spawnSync('tmux', ['kill-session', '-t', user], { stdio: 'ignore' });
|
|
145
|
-
spawnSync('tmux', ['kill-session', '-t', back], { stdio: 'ignore' });
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
test('handFocusToManager: DORMANT idle-release manager (dead pane) → occupant repointed, NO swap, focus pane UNCHANGED', () => {
|
|
149
|
-
openFocusRow('f', '%focus', 'Suser', 'M');
|
|
150
|
-
// The manager is dormant + idle-release (the ONLY dormant manager the daemon
|
|
151
|
-
// revives): its pane is recorded but NOT a live tmux pane (its dead-pi window
|
|
152
|
-
// collapsed) and its pi is dead. isNodePaneAlive(mgr) is therefore false, so the
|
|
153
|
-
// live-swap is skipped — the external daemon later respawns it into the frozen
|
|
154
|
-
// %focus, exactly because status='idle' && intent='idle-release'.
|
|
155
|
-
createNode(node('mgr', { pane: '%mgr-dead', tmux_session: 'back', window: '@wb', pi_pid: null, status: 'idle', intent: 'idle-release' }));
|
|
156
|
-
assert.equal(handFocusToManager('f', 'mgr'), true, 'still a takeover (occupant repointed)');
|
|
157
|
-
assert.equal(getFocusByNode('mgr')?.focus_id, 'f', 'the manager occupies the focus row (DB repoint)');
|
|
158
|
-
assert.equal(getFocusByNode('M'), null, 'the finished node no longer occupies it');
|
|
159
|
-
// The split: with no physical swap, the focus row stays anchored on the ORIGINAL
|
|
160
|
-
// frozen focus pane (the pane the daemon will respawn the manager INTO) and the
|
|
161
|
-
// manager's backstage presence is left untouched.
|
|
162
|
-
assert.equal(getFocusById('f')?.pane, '%focus', 'focus row pane NOT re-anchored to the manager pane (no swap)');
|
|
163
|
-
assert.equal(getNode('mgr').pane, '%mgr-dead', 'manager presence pane unchanged');
|
|
164
|
-
assert.equal(getNode('mgr').window, '@wb', 'manager presence window unchanged (never moved into a viewport)');
|
|
165
|
-
// Non-vacuous: an impl that re-anchored the focus row to the manager pane in
|
|
166
|
-
// the DB (or wrote the manager's presence to the viewport) regardless of a real
|
|
167
|
-
// physical swap would set f.pane='%mgr-dead' and mgr.window=the focus window —
|
|
168
|
-
// both asserts fail. Proves the swap gates on PANE LIVENESS, not merely on the
|
|
169
|
-
// manager having a pane.
|
|
170
|
-
});
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
// BUG REGRESSION (dead-focus-pane): handFocusToManager must return FALSE — so
|
|
173
|
-
// the stophook caller runs closeFocusToShell to DISARM remain-on-exit and the
|
|
174
|
-
// pane REAPS on exit — whenever NO successor will actually claim the frozen pane.
|
|
175
|
-
// Pre-fix it repointed the occupant and returned true for ANY non-null,
|
|
176
|
-
// not-already-focused manager (even done/dead/canceled, idle-but-not-idle-
|
|
177
|
-
// release, or a live-but-paneless inline root). The daemon revives NONE of those
|
|
178
|
-
// into the frozen pane, so closeFocusToShell was skipped, remain-on-exit stayed
|
|
179
|
-
// ON, and the focus pane FROZE forever as a dead pane with no reaper.
|
|
180
|
-
// Diagnosis: nodes/mq32wjve-68de0c31/context/dead-focus-pane-fix.md
|
|
181
|
-
// ---------------------------------------------------------------------------
|
|
182
|
-
test('handFocusToManager (BUG REGRESSION): a manager the daemon will NEVER revive → false, occupant UNCHANGED (caller disarms + reaps)', () => {
|
|
183
|
-
openFocusRow('f', '%focus', 'Suser', 'M'); // M occupies the frozen focus pane
|
|
184
|
-
// (a) DONE manager — the daemon ignores done nodes entirely (the most common
|
|
185
|
-
// trigger: demoting a child to terminal whose manager already finished).
|
|
186
|
-
createNode(node('mgrDone', { status: 'done', pane: '%d', tmux_session: 'back', window: '@wd' }));
|
|
187
|
-
assert.equal(handFocusToManager('f', 'mgrDone'), false, 'a DONE manager will never be revived → caller must disarm + reap');
|
|
188
|
-
assert.equal(getFocusByNode('mgrDone'), null, 'a DONE manager is NOT repointed into the focus row');
|
|
189
|
-
// (b) DORMANT but NOT idle-release — idle with a different intent; crtrd's
|
|
190
|
-
// second pass (idle && idle-release) skips it, so it never enters the pane.
|
|
191
|
-
createNode(node('mgrIdleDone', { status: 'idle', intent: 'done', pane: '%i', tmux_session: 'back', window: '@wi' }));
|
|
192
|
-
assert.equal(handFocusToManager('f', 'mgrIdleDone'), false, 'idle but NOT idle-release → daemon never revives it → false');
|
|
193
|
-
assert.equal(getFocusByNode('mgrIdleDone'), null, 'a non-idle-release manager is NOT repointed');
|
|
194
|
-
// (c) LIVE-but-PANELESS manager (an inline root, pane == null) — the live-swap
|
|
195
|
-
// branch is skipped (no pane to swap) and, being active not idle-release,
|
|
196
|
-
// the daemon never revives it either.
|
|
197
|
-
createNode(node('mgrPaneless', { status: 'active', pi_pid: process.pid, pane: null }));
|
|
198
|
-
assert.equal(handFocusToManager('f', 'mgrPaneless'), false, 'a live-but-paneless inline root cannot claim the pane → false');
|
|
199
|
-
assert.equal(getFocusByNode('mgrPaneless'), null, 'a paneless manager is NOT repointed');
|
|
200
|
-
// The occupant is untouched across all three → the caller (canvas-stophook
|
|
201
|
-
// agent_end done-branch) runs closeFocusToShell, disarming the freeze.
|
|
202
|
-
assert.equal(getFocusById('f')?.node_id, 'M', 'M still occupies its focus → caller disarms remain-on-exit + closes the row');
|
|
203
|
-
// Non-vacuous: the pre-fix impl repointed the occupant + returned true for each
|
|
204
|
-
// of (a)/(b)/(c), so every false return AND every "NOT repointed" assert fails
|
|
205
|
-
// against it — and that stray true would have stranded the pane frozen forever.
|
|
206
|
-
});
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
// tearDownNode (pure DB; no tmux — pane is null so closePane never runs).
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
test('tearDownNode: closes the focus row M occupied and nulls its LOCATION', () => {
|
|
211
|
-
createNode(node('M', { pane: null, window: null }));
|
|
212
|
-
openFocusRow('fM', null, 'Sa', 'M'); // M occupies a focus row
|
|
213
|
-
tearDownNode('M');
|
|
214
|
-
assert.equal(getFocusByNode('M'), null, 'the focus row M occupied is closed');
|
|
215
|
-
const m = getNode('M');
|
|
216
|
-
assert.equal(m.pane ?? null, null, 'pane nulled');
|
|
217
|
-
assert.equal(m.window ?? null, null, 'window nulled');
|
|
218
|
-
assert.equal(m.tmux_session ?? null, null, 'session nulled');
|
|
219
|
-
// Non-vacuous: an impl that skips closeFocusRow leaves fM → getFocusByNode('M')
|
|
220
|
-
// is non-null.
|
|
221
|
-
});
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/home-session.test.ts
|
|
2
|
-
//
|
|
3
|
-
// STEP 1 of the placement/focus migration: the durable REVIVE-HOME field.
|
|
4
|
-
// `home_session` separates a node's revive target from its live LOCATION so a
|
|
5
|
-
// later step can kill the focus taint. This step only ADDS + POPULATES +
|
|
6
|
-
// DEFAULTS the field — no behavior change. Covers:
|
|
7
|
-
// - home_session round-trips through meta.json (it IS durable identity)
|
|
8
|
-
// - the birth-session decision (`resolveBirthSession`) for the child /
|
|
9
|
-
// inline-root / --root cases each site sets home_session from
|
|
10
|
-
// - recycle + relaunch (pane-recycle) births populate home_session
|
|
11
|
-
// - a legacy meta with NO home_session defaults to tmux_session ?? nodeSession()
|
|
12
|
-
import { test, before, after, beforeEach } from 'node:test';
|
|
13
|
-
import assert from 'node:assert/strict';
|
|
14
|
-
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
-
import { tmpdir } from 'node:os';
|
|
16
|
-
import { join } from 'node:path';
|
|
17
|
-
import { createNode, getNode, updateNode } from '../canvas/canvas.js';
|
|
18
|
-
import { nodeMetaPath } from '../canvas/paths.js';
|
|
19
|
-
import { closeDb } from '../canvas/db.js';
|
|
20
|
-
import { resolveBirthSession, homeSessionOf, childBackstageOf } from '../runtime/nodes.js';
|
|
21
|
-
import { nodeSession } from '../runtime/nodes.js';
|
|
22
|
-
import { relaunchRoot } from '../runtime/reset.js';
|
|
23
|
-
import { recycleNode } from '../runtime/recycle.js';
|
|
24
|
-
let home;
|
|
25
|
-
function node(id, over = {}) {
|
|
26
|
-
return {
|
|
27
|
-
node_id: id,
|
|
28
|
-
name: id,
|
|
29
|
-
created: new Date().toISOString(),
|
|
30
|
-
cwd: '/tmp/work',
|
|
31
|
-
kind: 'general',
|
|
32
|
-
mode: 'base',
|
|
33
|
-
lifecycle: 'resident',
|
|
34
|
-
status: 'active',
|
|
35
|
-
...over,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
/** Make ensureDaemon (called by recycleNode) a no-op by faking a live daemon
|
|
39
|
-
* pidfile pointing at THIS test process — so no real daemon is ever spawned. */
|
|
40
|
-
function fakeLiveDaemon() {
|
|
41
|
-
writeFileSync(join(home, 'crtrd.pid'), String(process.pid), 'utf8');
|
|
42
|
-
}
|
|
43
|
-
before(() => {
|
|
44
|
-
home = mkdtempSync(join(tmpdir(), 'crtr-homesession-'));
|
|
45
|
-
process.env['CRTR_HOME'] = home;
|
|
46
|
-
});
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
closeDb();
|
|
49
|
-
rmSync(home, { recursive: true, force: true });
|
|
50
|
-
delete process.env['CRTR_ROOT_SESSION'];
|
|
51
|
-
delete process.env['CRTR_NODE_SESSION'];
|
|
52
|
-
});
|
|
53
|
-
after(() => {
|
|
54
|
-
closeDb();
|
|
55
|
-
rmSync(home, { recursive: true, force: true });
|
|
56
|
-
delete process.env['CRTR_HOME'];
|
|
57
|
-
delete process.env['CRTR_ROOT_SESSION'];
|
|
58
|
-
delete process.env['CRTR_NODE_SESSION'];
|
|
59
|
-
});
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Persistence: home_session is DURABLE IDENTITY (meta.json), round-tripped by
|
|
62
|
-
// createNode / getNode / updateNode — and NOT a runtime field.
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
test('home_session round-trips through meta.json and the hydrated view', () => {
|
|
65
|
-
createNode(node('n', { home_session: 'crtr', tmux_session: 'user-sess', window: '@1' }));
|
|
66
|
-
// On disk: meta.json carries home_session (identity), NOT the runtime LOCATION.
|
|
67
|
-
const raw = JSON.parse(readFileSync(nodeMetaPath('n'), 'utf8'));
|
|
68
|
-
assert.equal(raw['home_session'], 'crtr', 'home_session persisted to meta.json (durable identity)');
|
|
69
|
-
assert.ok(!('tmux_session' in raw), 'tmux_session stays a runtime field, not in meta.json');
|
|
70
|
-
// Hydrated view returns it; the live LOCATION is independent.
|
|
71
|
-
const m = getNode('n');
|
|
72
|
-
assert.equal(m?.home_session, 'crtr', 'getNode hydrates home_session');
|
|
73
|
-
assert.equal(m?.tmux_session, 'user-sess', 'home_session is distinct from the live LOCATION');
|
|
74
|
-
});
|
|
75
|
-
test('updateNode patches home_session (the demote rewriter path) and preserves it', () => {
|
|
76
|
-
createNode(node('n', { home_session: 'crtr' }));
|
|
77
|
-
updateNode('n', { home_session: 'recycled-sess' });
|
|
78
|
-
assert.equal(getNode('n')?.home_session, 'recycled-sess', 'home_session rewritten by updateNode');
|
|
79
|
-
// An unrelated identity patch leaves home_session intact (RMW round-trip).
|
|
80
|
-
updateNode('n', { description: 'a-handle' });
|
|
81
|
-
assert.equal(getNode('n')?.home_session, 'recycled-sess', 'home_session survives an unrelated identity edit');
|
|
82
|
-
});
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// The legacy / back-compat DEFAULT: a meta with no home_session reads back
|
|
85
|
-
// tmux_session ?? nodeSession().
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
test('homeSessionOf: a present home_session is returned verbatim', () => {
|
|
88
|
-
createNode(node('n', { home_session: 'home-sess', tmux_session: 'live-sess' }));
|
|
89
|
-
assert.equal(homeSessionOf('n'), 'home-sess', 'the stored revive-home wins over the live LOCATION');
|
|
90
|
-
});
|
|
91
|
-
test('homeSessionOf: legacy meta (no home_session) defaults to tmux_session', () => {
|
|
92
|
-
createNode(node('legacy', { tmux_session: 'live-sess' }));
|
|
93
|
-
assert.equal(getNode('legacy')?.home_session, undefined, 'no home_session on a legacy node');
|
|
94
|
-
assert.equal(homeSessionOf('legacy'), 'live-sess', 'defaults to the last live LOCATION');
|
|
95
|
-
});
|
|
96
|
-
test('homeSessionOf: legacy meta with no LOCATION either defaults to nodeSession()', () => {
|
|
97
|
-
createNode(node('legacy', { tmux_session: null }));
|
|
98
|
-
assert.equal(homeSessionOf('legacy'), nodeSession(), 'falls through to the shared backstage');
|
|
99
|
-
});
|
|
100
|
-
test('homeSessionOf: the backstage default honors CRTR_NODE_SESSION', () => {
|
|
101
|
-
process.env['CRTR_NODE_SESSION'] = 'my-backstage';
|
|
102
|
-
createNode(node('legacy'));
|
|
103
|
-
assert.equal(homeSessionOf('legacy'), 'my-backstage', 'nodeSession() default is env-overridable');
|
|
104
|
-
});
|
|
105
|
-
test('homeSessionOf: unknown node falls back to the backstage', () => {
|
|
106
|
-
assert.equal(homeSessionOf('ghost'), nodeSession());
|
|
107
|
-
});
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// childBackstageOf — the session a node's CHILDREN spawn into (their
|
|
110
|
-
// CRTR_ROOT_SESSION). REGRESSION for the front-door-root subtree-exile bug:
|
|
111
|
-
// a refreshed inline root (home_session = a USER session it adopted) was
|
|
112
|
-
// sourcing children's CRTR_ROOT_SESSION from home_session, so every yield
|
|
113
|
-
// re-pointed its entire subtree into the user's working session. A root's
|
|
114
|
-
// children must always flow to the shared backstage `nodeSession()`, never the
|
|
115
|
-
// user session, while a managed child's children inherit its backstage
|
|
116
|
-
// home_session. (Bug: node mq2u219p spawned all 13 children into `cli`.)
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
test('childBackstageOf: a managed child uses its backstage home_session', () => {
|
|
119
|
-
// A child's home_session is ALWAYS the backstage it was born into; its live
|
|
120
|
-
// tmux_session may be a user session (focus taint) but must NOT leak to kids.
|
|
121
|
-
createNode(node('child', { parent: 'p', home_session: 'crtr', tmux_session: 'user-sess' }));
|
|
122
|
-
assert.equal(childBackstageOf('child'), 'crtr', 'children inherit the backstage, not the tainted LOCATION');
|
|
123
|
-
});
|
|
124
|
-
test('childBackstageOf: a front-door ROOT routes children to the backstage, NOT its adopted user session', () => {
|
|
125
|
-
// The bug: an inline root adopts the user's session as home_session ('cli').
|
|
126
|
-
// Sourcing children's CRTR_ROOT_SESSION from home_session exiled the whole
|
|
127
|
-
// subtree into 'cli' on every refresh-yield. A root (parent === null) must
|
|
128
|
-
// route children to nodeSession() instead.
|
|
129
|
-
createNode(node('root', { parent: null, home_session: 'cli', tmux_session: 'cli' }));
|
|
130
|
-
assert.equal(childBackstageOf('root'), nodeSession(), "a root's children flow to the backstage");
|
|
131
|
-
assert.notEqual(childBackstageOf('root'), 'cli', 'NEVER the user session the root pane adopted');
|
|
132
|
-
});
|
|
133
|
-
test('childBackstageOf: a root honors CRTR_NODE_SESSION for the backstage', () => {
|
|
134
|
-
process.env['CRTR_NODE_SESSION'] = 'my-backstage';
|
|
135
|
-
createNode(node('root', { parent: null, home_session: 'cli' }));
|
|
136
|
-
assert.equal(childBackstageOf('root'), 'my-backstage', 'the backstage default is env-overridable');
|
|
137
|
-
});
|
|
138
|
-
test('childBackstageOf: unknown node falls back to the backstage', () => {
|
|
139
|
-
assert.equal(childBackstageOf('ghost'), nodeSession());
|
|
140
|
-
});
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
// The birth-session decision each site sets home_session from. Pure, so the
|
|
143
|
-
// child / inline-root / --root births are testable without a live tmux (the
|
|
144
|
-
// real spawnChild/bootRoot are tmux + pi + process.exit coupled).
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
test('birth: a managed child homes to the shared backstage (nodeSession), never a user session', () => {
|
|
147
|
-
// No CRTR_ROOT_SESSION inherited, not adopting the caller.
|
|
148
|
-
assert.equal(resolveBirthSession({ adoptCaller: false, here: { session: 'user-sess' }, rootSession: undefined }), nodeSession(), 'a child ignores the caller session and homes to crtr');
|
|
149
|
-
});
|
|
150
|
-
test('birth: a managed child inherits CRTR_ROOT_SESSION as its backstage', () => {
|
|
151
|
-
assert.equal(resolveBirthSession({ adoptCaller: false, here: null, rootSession: 'crtr-subtree' }), 'crtr-subtree', 'the inherited root session is the child backstage');
|
|
152
|
-
});
|
|
153
|
-
test('birth: an independent --root inside tmux homes to the caller current session', () => {
|
|
154
|
-
assert.equal(resolveBirthSession({ adoptCaller: true, here: { session: 'user-sess' }, rootSession: 'crtr' }), 'user-sess', 'a --root adopts the caller session where the spawner is working');
|
|
155
|
-
});
|
|
156
|
-
test('birth: a --root NOT inside tmux falls back to the backstage', () => {
|
|
157
|
-
assert.equal(resolveBirthSession({ adoptCaller: true, here: null, rootSession: undefined }), nodeSession(), 'no caller session → the backstage');
|
|
158
|
-
});
|
|
159
|
-
test('birth: the inline front door (bootRoot) homes to its adopted session', () => {
|
|
160
|
-
// bootRoot adopts the caller session when inside tmux, else nodeSession().
|
|
161
|
-
assert.equal(resolveBirthSession({ adoptCaller: true, here: { session: 'term-sess' }, rootSession: undefined }), 'term-sess', 'inline root adopts the terminal it took over');
|
|
162
|
-
assert.equal(resolveBirthSession({ adoptCaller: true, here: null, rootSession: undefined }), nodeSession(), 'inline root with no tmux homes to the backstage');
|
|
163
|
-
});
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
// Pane-recycle births populate home_session. relaunchRoot (option C) is fully
|
|
166
|
-
// unit-testable (injected respawn, no tmux); recycleNode runs to completion with
|
|
167
|
-
// no tmux (respawn dispatch just fails) once the daemon spawn is neutralized.
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
test('relaunch birth: the fresh root homes to the recycled pane session', () => {
|
|
170
|
-
createNode(node('root', { parent: null, lifecycle: 'resident', tmux_session: 'crtr', window: '@7' }));
|
|
171
|
-
// No live tmux → paneLocation(pane) is null → loc falls back to the old root's
|
|
172
|
-
// tmux_session ('crtr'); home_session must adopt it.
|
|
173
|
-
const res = relaunchRoot('root', 'test-pane', { relaunchRootInPane: () => { } });
|
|
174
|
-
assert.ok(res !== null, 'relaunchRoot minted a fresh root');
|
|
175
|
-
assert.equal(getNode(res.newNodeId)?.home_session, 'crtr', 'fresh root homes to the recycled pane session');
|
|
176
|
-
});
|
|
177
|
-
test('recycle birth: the recycled root populates home_session (backstage when no pane location)', async () => {
|
|
178
|
-
createNode(node('M', { parent: null, lifecycle: 'resident', tmux_session: 'crtr', window: '@3' }));
|
|
179
|
-
fakeLiveDaemon(); // createNode ensured the home dir; now neutralize ensureDaemon
|
|
180
|
-
// No live tmux → paneLocation('%0') is null → home_session defaults to the
|
|
181
|
-
// backstage. The respawn dispatch fails (no tmux), but the fresh root is still
|
|
182
|
-
// born — and must carry a populated home_session.
|
|
183
|
-
const res = await recycleNode('M', '%0');
|
|
184
|
-
assert.ok(res.newRoot !== null, 'recycle minted a fresh root');
|
|
185
|
-
assert.equal(getNode(res.newRoot)?.home_session, nodeSession(), 'recycled root homes to the backstage');
|
|
186
|
-
});
|