@crouton-kit/crouter 0.3.28 → 0.3.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-root.d.ts +15 -7
- package/dist/build-root.js +48 -34
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +5 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +22 -17
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +282 -46
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +4 -2
- package/dist/clients/attach/canvas-panels.js +45 -17
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +13 -4
- package/dist/clients/attach/graph-overlay.js +59 -19
- package/dist/clients/attach/input-controller.d.ts +75 -13
- package/dist/clients/attach/input-controller.js +305 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +22 -0
- package/dist/clients/attach/slash-commands.js +91 -25
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +18 -1
- package/dist/clients/attach/view-socket.js +70 -1
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +4 -25
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
- package/dist/commands/memory/lint.js +14 -2
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +8 -4
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +3 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/listing-completeness.test.js +2 -2
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.js +8 -7
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker.d.ts +4 -1
- package/dist/core/runtime/broker.js +494 -58
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +6 -14
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -335
- package/dist/core/runtime/placement.js +291 -858
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +40 -139
- package/dist/core/runtime/spawn.d.ts +15 -16
- package/dist/core/runtime/spawn.js +165 -194
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +15 -52
- package/dist/core/runtime/tmux.js +35 -99
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.js +3 -3
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +10 -3
- package/dist/core/substrate/schema.js +6 -3
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
- package/dist/pi-extensions/canvas-nav.js +5 -0
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
- package/dist/pi-extensions/canvas-stophook.js +87 -101
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Run with: node --import tsx/esm --test src/core/__tests__/reset.test.ts
|
|
2
|
+
//
|
|
3
|
+
// resetRoot now handles ONLY the non-root child `/new` (a session-id refresh).
|
|
4
|
+
// A root's `/new` mints a fresh node via relaunchRoot — see relaunch-root.test.ts.
|
|
2
5
|
import { test, before, after, beforeEach } from 'node:test';
|
|
3
6
|
import assert from 'node:assert/strict';
|
|
4
|
-
import { mkdtempSync, rmSync
|
|
7
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
5
8
|
import { tmpdir } from 'node:os';
|
|
6
9
|
import { join } from 'node:path';
|
|
7
|
-
import { createNode, getNode, subscribe
|
|
8
|
-
import { openFocusRow, getFocusByNode } from '../canvas/focuses.js';
|
|
10
|
+
import { createNode, getNode, subscribe } from '../canvas/canvas.js';
|
|
9
11
|
import { closeDb } from '../canvas/db.js';
|
|
10
|
-
import { reportsDir, inboxPath } from '../canvas/paths.js';
|
|
11
|
-
import { roadmapPath } from '../runtime/roadmap.js';
|
|
12
12
|
import { resetRoot } from '../runtime/reset.js';
|
|
13
13
|
let home;
|
|
14
14
|
function node(id, over = {}) {
|
|
@@ -37,76 +37,29 @@ after(() => {
|
|
|
37
37
|
rmSync(home, { recursive: true, force: true });
|
|
38
38
|
delete process.env['CRTR_HOME'];
|
|
39
39
|
});
|
|
40
|
-
test('resetRoot
|
|
41
|
-
// root → child → grandchild (mirrors a parent that subscribes to its workers)
|
|
42
|
-
createNode(node('root', { parent: null, lifecycle: 'resident', mode: 'orchestrator', pi_session_id: 'old-sess' }));
|
|
43
|
-
createNode(node('child', { parent: 'root' }));
|
|
44
|
-
createNode(node('grand', { parent: 'child' }));
|
|
45
|
-
subscribe('root', 'child', true);
|
|
46
|
-
subscribe('child', 'grand', true);
|
|
47
|
-
// Root accumulated working state.
|
|
48
|
-
writeFileSync(roadmapPath('root'), '# Roadmap\nold goal\n');
|
|
49
|
-
writeFileSync(inboxPath('root'), '{"ts":"x","from":"child","tier":"normal","kind":"update","label":"hi"}\n');
|
|
50
|
-
writeFileSync(join(reportsDir('root'), '20260101T000000-update.md'), 'stale report');
|
|
51
|
-
assert.equal(view('root').length, 2, 'precondition: root sees 2 descendants');
|
|
52
|
-
const res = resetRoot('root', 'new-sess', '/abs/sessions/new.jsonl');
|
|
53
|
-
assert.equal(res.reset, true);
|
|
54
|
-
assert.deepEqual(res.detached, ['child'], 'root detaches its direct subscription');
|
|
55
|
-
assert.deepEqual(res.reaped.sort(), ['child', 'grand'], 'whole sub-DAG reaped');
|
|
56
|
-
// Graph is empty from the root's view.
|
|
57
|
-
assert.equal(view('root').length, 0, 'root view is empty after reset');
|
|
58
|
-
assert.equal(subscriptionsOf('root').length, 0, 'no outgoing edges remain');
|
|
59
|
-
// Descendants are CANCELED (A5, human-confirmed 2026-06-06): an externally-
|
|
60
|
-
// reaped node — via reset/relaunch OR close — did not finish its OWN work, so
|
|
61
|
-
// it unifies on `canceled`; `done` is reserved for finalize. Daemon skips them.
|
|
62
|
-
assert.equal(getNode('child')?.status, 'canceled');
|
|
63
|
-
assert.equal(getNode('grand')?.status, 'canceled');
|
|
64
|
-
// Regression: a reset-reaped descendant ends EXACTLY {canceled, null} — byte-
|
|
65
|
-
// for-byte identical to the close path (mirrors cascade-close.test.ts's tuple).
|
|
66
|
-
assert.deepEqual({ status: getNode('grand')?.status, intent: getNode('grand')?.intent ?? null }, { status: 'canceled', intent: null }, 'reset-reaped descendant: (status,intent) === (canceled, null), same as close');
|
|
67
|
-
// Working state wiped.
|
|
68
|
-
assert.equal(existsSync(roadmapPath('root')), false, 'roadmap wiped');
|
|
69
|
-
assert.equal(existsSync(inboxPath('root')), false, 'inbox wiped');
|
|
70
|
-
// Root reset to a pristine base resident, rebound to the new session id.
|
|
71
|
-
const root = getNode('root');
|
|
72
|
-
assert.equal(root?.mode, 'base');
|
|
73
|
-
assert.equal(root?.lifecycle, 'resident');
|
|
74
|
-
assert.equal(root?.status, 'active');
|
|
75
|
-
assert.equal(root?.intent, null);
|
|
76
|
-
assert.equal(root?.pi_session_id, 'new-sess');
|
|
77
|
-
assert.equal(root?.pi_session_file, '/abs/sessions/new.jsonl', 'session FILE rebound too');
|
|
78
|
-
assert.ok(root?.launch, 'a fresh base launch spec was written');
|
|
79
|
-
});
|
|
80
|
-
test('resetRoot on a non-root only refreshes the session id (no reap)', () => {
|
|
40
|
+
test('resetRoot on a non-root child refreshes the session id (no reap, no reset)', () => {
|
|
81
41
|
createNode(node('root', { parent: null }));
|
|
82
42
|
createNode(node('child', { parent: 'root', pi_session_id: 'old' }));
|
|
83
43
|
subscribe('root', 'child', true);
|
|
84
|
-
subscribe('child', 'root', false); // contrived: ensure child has an outgoing edge
|
|
85
44
|
const res = resetRoot('child', 'fresh', '/abs/sessions/fresh.jsonl');
|
|
86
|
-
assert.equal(res.reset, false, 'a
|
|
45
|
+
assert.equal(res.reset, false, 'a child `/new` is not a graph reset');
|
|
87
46
|
assert.deepEqual(res.reaped, []);
|
|
88
47
|
assert.deepEqual(res.detached, []);
|
|
89
|
-
assert.equal(getNode('child')?.pi_session_id, 'fresh', 'session id
|
|
48
|
+
assert.equal(getNode('child')?.pi_session_id, 'fresh', 'session id refreshed');
|
|
90
49
|
assert.equal(getNode('child')?.pi_session_file, '/abs/sessions/fresh.jsonl', 'session FILE refreshed too');
|
|
91
50
|
assert.equal(getNode('child')?.status, 'active', 'child not reaped');
|
|
92
|
-
|
|
93
|
-
assert.equal(getNode('root')?.status, 'active');
|
|
51
|
+
assert.equal(getNode('root')?.status, 'active', 'the subscribing root is untouched');
|
|
94
52
|
});
|
|
95
|
-
test('
|
|
96
|
-
createNode(node('root', { parent: null, lifecycle: 'resident',
|
|
97
|
-
createNode(node('
|
|
98
|
-
subscribe('root', '
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
assert.equal(getFocusByNode('desc'), null, 'descendant focus row closed by tearDownNode');
|
|
106
|
-
const d = getNode('desc');
|
|
107
|
-
assert.equal(d.status, 'canceled', 'descendant reaped (canceled — A5 unified)');
|
|
108
|
-
assert.equal(d.pane ?? null, null, 'descendant pane nulled');
|
|
109
|
-
assert.equal(d.tmux_session ?? null, null, 'descendant session nulled');
|
|
53
|
+
test('resetRoot on a ROOT is a no-op (roots route to relaunchRoot, not here)', () => {
|
|
54
|
+
createNode(node('root', { parent: null, lifecycle: 'resident', pi_session_id: 'keep' }));
|
|
55
|
+
createNode(node('child', { parent: 'root' }));
|
|
56
|
+
subscribe('root', 'child', true);
|
|
57
|
+
const res = resetRoot('root', 'new-sess');
|
|
58
|
+
assert.equal(res.reset, false);
|
|
59
|
+
assert.deepEqual(res.reaped, [], 'a root `/new` reaps nothing here');
|
|
60
|
+
assert.equal(getNode('root')?.status, 'active', 'root left untouched');
|
|
61
|
+
assert.equal(getNode('root')?.pi_session_id, 'keep', 'a root session id is NOT refreshed here');
|
|
62
|
+
assert.equal(getNode('child')?.status, 'active', 'descendant left untouched');
|
|
110
63
|
});
|
|
111
64
|
test('resetRoot is a no-op for an unknown node', () => {
|
|
112
65
|
const res = resetRoot('ghost', 'x');
|
|
@@ -114,16 +67,3 @@ test('resetRoot is a no-op for an unknown node', () => {
|
|
|
114
67
|
assert.deepEqual(res.reaped, []);
|
|
115
68
|
assert.deepEqual(res.detached, []);
|
|
116
69
|
});
|
|
117
|
-
test('reaped descendants keep their meta on disk (orphaned, not deleted)', () => {
|
|
118
|
-
createNode(node('root', { parent: null }));
|
|
119
|
-
createNode(node('child', { parent: 'root' }));
|
|
120
|
-
subscribe('root', 'child', true);
|
|
121
|
-
setStatus('child', 'idle');
|
|
122
|
-
resetRoot('root', 'new');
|
|
123
|
-
// The node record persists (we detach + mark canceled, we don't delete the node).
|
|
124
|
-
const child = getNode('child');
|
|
125
|
-
assert.ok(child, 'child meta still on disk');
|
|
126
|
-
assert.equal(child?.status, 'canceled');
|
|
127
|
-
// It is just unreachable from the root.
|
|
128
|
-
assert.equal(view('root').length, 0);
|
|
129
|
-
});
|
|
@@ -18,16 +18,18 @@ test('an orchestrator (resident) gets the same roadmap/yield steering as a termi
|
|
|
18
18
|
const r = steerNote(150_000, 'resident', 'orchestrator');
|
|
19
19
|
assert.equal(r, t, 'mode drives the message, not lifecycle');
|
|
20
20
|
});
|
|
21
|
-
test('a terminal BASE worker is steered to
|
|
21
|
+
test('a terminal BASE worker is steered to yield-or-finish, never at a bare promote', () => {
|
|
22
22
|
const msg = steerNote(170_000, 'terminal', 'base');
|
|
23
|
-
assert.match(msg, /node
|
|
24
|
-
assert.match(msg, /push final/, 'and to finish with push final when
|
|
25
|
-
assert.doesNotMatch(msg, /
|
|
23
|
+
assert.match(msg, /node yield/, 'a base worker is told to yield to continue (which auto-promotes under the hood)');
|
|
24
|
+
assert.match(msg, /push final/, 'and to finish with push final when close');
|
|
25
|
+
assert.doesNotMatch(msg, /node promote/, 'never the heavy "become an orchestrator" framing that bred yield-avoidance');
|
|
26
|
+
assert.doesNotMatch(msg, /roadmap\.md/, 'a base worker has no roadmap.md to checkpoint');
|
|
26
27
|
});
|
|
27
|
-
test('a resident BASE root is steered to
|
|
28
|
+
test('a resident BASE root is steered to yield-or-wrap-up, never at a bare promote or roadmap', () => {
|
|
28
29
|
const msg = steerNote(150_000, 'resident', 'base');
|
|
29
|
-
assert.match(msg, /node
|
|
30
|
-
assert.doesNotMatch(msg, /
|
|
30
|
+
assert.match(msg, /node yield/, 'a root growing into a job is told to yield (which auto-promotes)');
|
|
31
|
+
assert.doesNotMatch(msg, /node promote/, 'never the heavy "become an orchestrator" framing');
|
|
32
|
+
assert.doesNotMatch(msg, /roadmap\.md/, 'a root has no roadmap.md to point at');
|
|
31
33
|
assert.doesNotMatch(msg, /push final/, 'a resident node never finishes with push final');
|
|
32
34
|
});
|
|
33
35
|
test('below the first band there is no nudge boundary issue — pushy escalation kicks in at 185k', () => {
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
// STEP 2 of the placement/focus migration: guards on the tmux Surface (driver).
|
|
4
4
|
//
|
|
5
5
|
// 1. The §2.2 HARD DRIVER INVARIANT (GREEN now): every create/placement verb
|
|
6
|
-
// in the driver —
|
|
7
|
-
//
|
|
6
|
+
// in the driver — after the broker-host cut gutted the engine-in-pane
|
|
7
|
+
// relocate verbs (U9 deleted swap-pane/break-pane/join-pane/move-pane),
|
|
8
|
+
// the survivors are new-window / split-window / respawn-pane — MUST pass an
|
|
9
|
+
// explicit `-t` target.
|
|
8
10
|
// Omitting `-t` lets tmux resolve against its GLOBAL current session, which
|
|
9
11
|
// can leak a pane into a user session — the exact unbidden-window bug this
|
|
10
12
|
// redesign kills. This guards the bug's blast radius and should pass today.
|
|
@@ -21,7 +23,10 @@ import { dirname, join, basename } from 'node:path';
|
|
|
21
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
24
|
const SRC_ROOT = join(__dirname, '..', '..'); // .../src
|
|
23
25
|
const TMUX_TS = join(SRC_ROOT, 'core', 'runtime', 'tmux.ts');
|
|
24
|
-
/** The placement verbs the §2.2 invariant governs.
|
|
26
|
+
/** The placement verbs the §2.2 invariant governs. (swap-pane/break-pane/
|
|
27
|
+
* join-pane/move-pane were deleted with the engine-in-pane host in the broker
|
|
28
|
+
* cut; kept in the match set so a regression that re-introduces an untargeted
|
|
29
|
+
* one is still caught.) */
|
|
25
30
|
const PLACEMENT_VERBS = [
|
|
26
31
|
'new-window',
|
|
27
32
|
'split-window',
|
|
@@ -67,8 +72,9 @@ test('§2.2 driver invariant: every placement verb in tmux.ts passes an explicit
|
|
|
67
72
|
`instance of the unbidden-window bug (§2.2). Offending args array:\n${arr}`);
|
|
68
73
|
}
|
|
69
74
|
// Sanity: the driver really does contain placement verbs (so a refactor that
|
|
70
|
-
// renames them can't make this assertion vacuously pass).
|
|
71
|
-
|
|
75
|
+
// renames them can't make this assertion vacuously pass). The broker cut left
|
|
76
|
+
// three (new-window / split-window / respawn-pane).
|
|
77
|
+
assert.ok(found >= 3, `expected to scan ≥3 placement verbs, saw ${found}`);
|
|
72
78
|
});
|
|
73
79
|
// ---------------------------------------------------------------------------
|
|
74
80
|
// Lint guard — §5.1, ENFORCED. The driver (tmux.ts) is imported ONLY by
|
|
@@ -92,8 +98,8 @@ function allTsFiles(dir) {
|
|
|
92
98
|
const ALLOWED_IMPORTERS = new Set(['tmux.ts', 'placement.ts', 'tmux-chrome.ts']);
|
|
93
99
|
function importsDriver(file) {
|
|
94
100
|
const src = readFileSync(file, 'utf8');
|
|
95
|
-
// A specifier whose basename is exactly `tmux.js` (so `tmux-chrome.js`
|
|
96
|
-
//
|
|
101
|
+
// A specifier whose basename is exactly `tmux.js` (so `tmux-chrome.js` is NOT
|
|
102
|
+
// matched). Covers `from '...'` and `import('...')`.
|
|
97
103
|
return [...src.matchAll(/(?:from|import\s*\()\s*'([^']+)'/g)].some((m) => basename(m[1]) === 'tmux.js');
|
|
98
104
|
}
|
|
99
105
|
test('§5.1 lint: only placement.ts / tmux-chrome.ts import the tmux driver', () => {
|
package/dist/core/bootstrap.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { RootDef } from './command.js';
|
|
2
1
|
export declare const OFFICIAL_MARKETPLACE_NAME = "crouter-official-marketplace";
|
|
3
2
|
export declare const OFFICIAL_MARKETPLACE_URL = "https://github.com/crouton-labs/crouter-official-marketplace.git";
|
|
4
3
|
export declare const OFFICIAL_MARKETPLACE_REF = "main";
|
|
5
|
-
export declare function ensureBootSkill(argv: string[]): void;
|
|
6
|
-
export declare function ensureSlashCommands(root: RootDef, argv: string[]): void;
|
|
7
4
|
export declare function ensureOfficialMarketplace(argv: string[]): void;
|
|
8
5
|
export declare function ensureProjectScope(argv: string[]): void;
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { writeFileSync } from 'node:fs';
|
|
2
1
|
import { homedir } from 'node:os';
|
|
3
2
|
import { join } from 'node:path';
|
|
4
3
|
import { findProjectScopeRoot, resetScopeCache, userScopeRoot } from './scope.js';
|
|
5
|
-
import { ensureDir, pathExists,
|
|
4
|
+
import { ensureDir, pathExists, removePath, nowIso } from './fs-utils.js';
|
|
6
5
|
import { readConfig, readState, updateConfig, updateState, ensureScopeInitialized } from './config.js';
|
|
7
6
|
import { clone } from './git.js';
|
|
8
7
|
import { readMarketplaceManifest } from './manifest.js';
|
|
9
|
-
import { collectSlashSpecs } from './command.js';
|
|
10
8
|
import { CRTR_DIR_NAME } from '../types.js';
|
|
11
9
|
export const OFFICIAL_MARKETPLACE_NAME = 'crouter-official-marketplace';
|
|
12
10
|
export const OFFICIAL_MARKETPLACE_URL = 'https://github.com/crouton-labs/crouter-official-marketplace.git';
|
|
@@ -24,146 +22,6 @@ function shouldSkipForArgv(argv) {
|
|
|
24
22
|
return true;
|
|
25
23
|
return SKIP_SUBCOMMANDS.has(sub);
|
|
26
24
|
}
|
|
27
|
-
const BOOT_SKILL_NAME = 'crtr-skills';
|
|
28
|
-
const BOOT_SKILL_MARKER = '<!-- crtr-boot-skill v2 -->';
|
|
29
|
-
const BOOT_SKILL_MARKER_PREFIX = '<!-- crtr-boot-skill v';
|
|
30
|
-
function bootSkillBody() {
|
|
31
|
-
return `---
|
|
32
|
-
name: crtr-skills
|
|
33
|
-
description: 'Author, list, search, and load crtr memory documents (skills, references, preferences) via the crtr CLI. These are durable agent memory — markdown future LLM sessions load on demand. Use when the user wants to remember/save knowledge, build a context primer, or recall a previously saved doc. Triggers: "save", "remember", "build context for", "what skills do we have", "skill for X".'
|
|
34
|
-
argument-hint: [topic or verb]
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
${BOOT_SKILL_MARKER}
|
|
38
|
-
|
|
39
|
-
# /crtr-skills — crtr memory router
|
|
40
|
-
|
|
41
|
-
crtr memory documents = durable agent memory (kind: skill, reference, or
|
|
42
|
-
preference). Written for **future LLM sessions**, not the user. \`crtr memory\`
|
|
43
|
-
is the surface — author, discover, and load them.
|
|
44
|
-
|
|
45
|
-
## Route by intent
|
|
46
|
-
|
|
47
|
-
- **Capture** ("save", "remember", "build context for", "make a skill"):
|
|
48
|
-
\`crtr memory write <name> --kind <skill|reference|preference>\` with the
|
|
49
|
-
body piped on stdin — e.g.
|
|
50
|
-
\`crtr memory write $ARGUMENTS --kind skill <<'EOF' … EOF\`. Run
|
|
51
|
-
\`crtr memory write -h\` for the full frontmatter schema
|
|
52
|
-
(--when-and-why-to-read, --short-form, --scope, visibility rungs), then
|
|
53
|
-
\`crtr memory lint\` to validate what you wrote.
|
|
54
|
-
- **Find** ("what do we have on X"): \`crtr memory find "$ARGUMENTS"\` →
|
|
55
|
-
\`crtr memory read <name>\` on the best hit (add --body/--grep to search
|
|
56
|
-
bodies).
|
|
57
|
-
- **Load by name**: \`crtr memory read <name>\`.
|
|
58
|
-
- **List all**: \`crtr memory list\`.
|
|
59
|
-
|
|
60
|
-
If \`$ARGUMENTS\` is empty, ask the user what they want before running.
|
|
61
|
-
|
|
62
|
-
## Rules
|
|
63
|
-
|
|
64
|
-
- CLI stdout is the prompt — act on it, don't paraphrase to the user.
|
|
65
|
-
- Append \`-h\` at any leaf (\`crtr memory write -h\`) for its full schema
|
|
66
|
-
before authoring.
|
|
67
|
-
- If \`crtr\` is not on PATH, tell the user and stop.
|
|
68
|
-
`;
|
|
69
|
-
}
|
|
70
|
-
export function ensureBootSkill(argv) {
|
|
71
|
-
try {
|
|
72
|
-
if (process.env.CRTR_NO_BOOT_SKILL === '1')
|
|
73
|
-
return;
|
|
74
|
-
if (shouldSkipForArgv(argv))
|
|
75
|
-
return;
|
|
76
|
-
const claudeSkillsRoot = join(homedir(), '.claude', 'skills');
|
|
77
|
-
// Only install if the user actually uses Claude Code (the dir exists or is
|
|
78
|
-
// creatable). We won't create ~/.claude itself; that's not our directory.
|
|
79
|
-
if (!pathExists(join(homedir(), '.claude')))
|
|
80
|
-
return;
|
|
81
|
-
const skillDir = join(claudeSkillsRoot, BOOT_SKILL_NAME);
|
|
82
|
-
const skillFile = join(skillDir, 'SKILL.md');
|
|
83
|
-
if (pathExists(skillFile)) {
|
|
84
|
-
const existing = readText(skillFile);
|
|
85
|
-
// If the user customized (no boot-skill marker at all), don't clobber.
|
|
86
|
-
if (!existing.includes(BOOT_SKILL_MARKER_PREFIX))
|
|
87
|
-
return;
|
|
88
|
-
// Any marker version present → roll forward to current. Skip if identical.
|
|
89
|
-
if (existing === bootSkillBody())
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
ensureDir(skillDir);
|
|
93
|
-
writeFileSync(skillFile, bootSkillBody(), 'utf8');
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
if (process.env.CRTR_DEBUG === '1') {
|
|
97
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
98
|
-
process.stderr.write(`crtr: boot-skill error: ${msg}\n`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
// Slash commands (editor prompt templates) auto-installed for opted-in nodes.
|
|
104
|
-
//
|
|
105
|
-
// Any command that declares a `slash` SlashSpec is rendered to a markdown
|
|
106
|
-
// template and dropped into the host's command dirs on each crtr run — pi reads
|
|
107
|
-
// `~/.pi/agent/prompts/<name>.md`, Claude Code reads `~/.claude/commands/<name>.md`,
|
|
108
|
-
// so `/name` becomes available. Marker-guarded (never clobbers a user-edited
|
|
109
|
-
// file) and version-rolled like the boot skill. Kill switch: CRTR_NO_MODE_CMDS=1.
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
const SLASH_CMD_MARKER = '<!-- crtr-mode-cmd v1 -->';
|
|
112
|
-
const SLASH_CMD_MARKER_PREFIX = '<!-- crtr-mode-cmd v';
|
|
113
|
-
/** Render a SlashSpec to a full template file (frontmatter + marker + body). */
|
|
114
|
-
function renderSlashTemplate(spec) {
|
|
115
|
-
const hint = spec.argumentHint !== undefined
|
|
116
|
-
? `argument-hint: ${JSON.stringify(spec.argumentHint)}\n`
|
|
117
|
-
: '';
|
|
118
|
-
return `---\ndescription: ${spec.description}\n${hint}---\n\n${SLASH_CMD_MARKER}\n\n${spec.body}\n`;
|
|
119
|
-
}
|
|
120
|
-
/** Write `content` to `file` unless a user-customized file is already there.
|
|
121
|
-
* Rolls forward our own (marker-bearing) versions; skips if identical. */
|
|
122
|
-
function writeSlashFileIfOurs(dir, name, content) {
|
|
123
|
-
const file = join(dir, `${name}.md`);
|
|
124
|
-
if (pathExists(file)) {
|
|
125
|
-
const existing = readText(file);
|
|
126
|
-
if (!existing.includes(SLASH_CMD_MARKER_PREFIX))
|
|
127
|
-
return; // user's own file
|
|
128
|
-
if (existing === content)
|
|
129
|
-
return; // already current
|
|
130
|
-
}
|
|
131
|
-
ensureDir(dir);
|
|
132
|
-
writeFileSync(file, content, 'utf8');
|
|
133
|
-
}
|
|
134
|
-
export function ensureSlashCommands(root, argv) {
|
|
135
|
-
try {
|
|
136
|
-
if (process.env.CRTR_NO_MODE_CMDS === '1')
|
|
137
|
-
return;
|
|
138
|
-
if (shouldSkipForArgv(argv))
|
|
139
|
-
return;
|
|
140
|
-
const specs = collectSlashSpecs(root);
|
|
141
|
-
if (specs.length === 0)
|
|
142
|
-
return;
|
|
143
|
-
// Target each host's command dir, but only when that host is actually in use
|
|
144
|
-
// (its root dir exists). We never create ~/.pi or ~/.claude ourselves.
|
|
145
|
-
const targets = [];
|
|
146
|
-
if (pathExists(join(homedir(), '.pi', 'agent'))) {
|
|
147
|
-
targets.push(join(homedir(), '.pi', 'agent', 'prompts'));
|
|
148
|
-
}
|
|
149
|
-
if (pathExists(join(homedir(), '.claude'))) {
|
|
150
|
-
targets.push(join(homedir(), '.claude', 'commands'));
|
|
151
|
-
}
|
|
152
|
-
if (targets.length === 0)
|
|
153
|
-
return;
|
|
154
|
-
for (const spec of specs) {
|
|
155
|
-
const content = renderSlashTemplate(spec);
|
|
156
|
-
for (const dir of targets)
|
|
157
|
-
writeSlashFileIfOurs(dir, spec.name, content);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
catch (e) {
|
|
161
|
-
if (process.env.CRTR_DEBUG === '1') {
|
|
162
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
163
|
-
process.stderr.write(`crtr: slash-command error: ${msg}\n`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
25
|
export function ensureOfficialMarketplace(argv) {
|
|
168
26
|
try {
|
|
169
27
|
if (process.env.CRTR_NO_BOOTSTRAP === '1')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { buildTree, flatten, fuzzyMatch, tabPredicate, TABS } from '../model.js';
|
|
3
|
+
import { buildTree, flatten, fuzzyMatch, tabPredicate, TABS, attentionTier, attentionMtime } from '../model.js';
|
|
4
4
|
// ── Fixture canvas ───────────────────────────────────────────────────────────
|
|
5
5
|
// root1 (active) ← rank 0
|
|
6
6
|
// child-a (idle)
|
|
@@ -12,6 +12,9 @@ import { buildTree, flatten, fuzzyMatch, tabPredicate, TABS } from '../model.js'
|
|
|
12
12
|
function row(node_id, name, status, asks = 0) {
|
|
13
13
|
return { node_id, name, status, kind: 'general', mode: 'base', ctx_tokens: 0, asks, cwd: '/tmp/proj', created: '2026-01-01T00:00:00.000Z' };
|
|
14
14
|
}
|
|
15
|
+
function lrow(node_id, name, status, lifecycle) {
|
|
16
|
+
return { ...row(node_id, name, status), lifecycle };
|
|
17
|
+
}
|
|
15
18
|
const ROWS = [
|
|
16
19
|
row('root1', 'root-one', 'active'),
|
|
17
20
|
row('child-a', 'child-a', 'idle'),
|
|
@@ -140,3 +143,85 @@ test('flatten: query with no matches yields nothing', () => {
|
|
|
140
143
|
const v = flatten(tree(), { collapsed: allCollapsed(), tab: 'All', query: 'zzzznope' });
|
|
141
144
|
assert.deepEqual(v, []);
|
|
142
145
|
});
|
|
146
|
+
// ── flatten: residentsOnly fold-reveal ───────────────────────────────────────
|
|
147
|
+
// resident-root → terminal-worker (child) → terminal-grand (grandchild)
|
|
148
|
+
// resident-only must hide the terminals from the TOP LEVEL + flat search, yet
|
|
149
|
+
// still reveal them once you expand the resident fold that owns them.
|
|
150
|
+
const RES_ROWS = [
|
|
151
|
+
lrow('res-root', 'resident-root', 'idle', 'resident'),
|
|
152
|
+
lrow('term-a', 'worker-a', 'active', 'terminal'),
|
|
153
|
+
lrow('term-g', 'worker-grand', 'active', 'terminal'),
|
|
154
|
+
lrow('term-root', 'lone-worker', 'active', 'terminal'), // pure-terminal root
|
|
155
|
+
];
|
|
156
|
+
const RES_CHILDREN = { 'res-root': ['term-a'], 'term-a': ['term-g'] };
|
|
157
|
+
function resTree() {
|
|
158
|
+
return buildTree(RES_ROWS, ['res-root', 'term-root'], (id) => RES_CHILDREN[id] ?? []);
|
|
159
|
+
}
|
|
160
|
+
test('flatten: residentsOnly hides terminal top-level rows (incl. terminal roots)', () => {
|
|
161
|
+
// Everything collapsed → only the resident root shows; the lone terminal root
|
|
162
|
+
// and the worker subtree are hidden at the top level.
|
|
163
|
+
const v = flatten(resTree(), { collapsed: new Set(['res-root', 'term-a']), tab: 'All', query: '', residentsOnly: true });
|
|
164
|
+
assert.deepEqual(v.map((r) => r.id), ['res-root']);
|
|
165
|
+
});
|
|
166
|
+
test('flatten: residentsOnly reveals terminal children once their fold is expanded', () => {
|
|
167
|
+
// Expand the resident root → its terminal worker appears (still collapsed, so
|
|
168
|
+
// its own terminal grandchild stays hidden).
|
|
169
|
+
const v1 = flatten(resTree(), { collapsed: new Set(['term-a']), tab: 'All', query: '', residentsOnly: true });
|
|
170
|
+
assert.deepEqual(v1.map((r) => r.id), ['res-root', 'term-a']);
|
|
171
|
+
// Expand the worker too → the terminal grandchild is revealed.
|
|
172
|
+
const v2 = flatten(resTree(), { collapsed: new Set(), tab: 'All', query: '', residentsOnly: true });
|
|
173
|
+
assert.deepEqual(v2.map((r) => r.id), ['res-root', 'term-a', 'term-g']);
|
|
174
|
+
});
|
|
175
|
+
test('flatten: residentsOnly keeps terminals out of flat (relevance) search results', () => {
|
|
176
|
+
// A query that matches the workers must not surface them in the flat search —
|
|
177
|
+
// resident-only de-clutters search even though the fold would reveal them.
|
|
178
|
+
const v = flatten(resTree(), { collapsed: new Set(), tab: 'All', query: 'worker', residentsOnly: true, sort: 'relevance' });
|
|
179
|
+
assert.deepEqual(v.map((r) => r.id), []);
|
|
180
|
+
});
|
|
181
|
+
test('flatten: residentsOnly off shows every lifecycle at the top level', () => {
|
|
182
|
+
// Roots are live-first: term-root (active) ranks ahead of res-root (idle).
|
|
183
|
+
const v = flatten(resTree(), { collapsed: new Set(['res-root', 'term-a']), tab: 'All', query: '', residentsOnly: false });
|
|
184
|
+
assert.deepEqual(v.map((r) => r.id), ['term-root', 'res-root']);
|
|
185
|
+
});
|
|
186
|
+
function arow(node_id, status, opts) {
|
|
187
|
+
return { ...row(node_id, node_id, status), ...opts };
|
|
188
|
+
}
|
|
189
|
+
test('attentionTier: classifies each cheap-field combination', () => {
|
|
190
|
+
assert.equal(attentionTier(arow('x', 'active', { viewed: true, streaming: true })), 0);
|
|
191
|
+
assert.equal(attentionTier(arow('x', 'active', { viewed: true })), 1);
|
|
192
|
+
assert.equal(attentionTier(arow('x', 'active', { streaming: true, viewed: false })), 2);
|
|
193
|
+
assert.equal(attentionTier(arow('x', 'active', {})), 3);
|
|
194
|
+
assert.equal(attentionTier(arow('x', 'idle', {})), 3);
|
|
195
|
+
assert.equal(attentionTier(arow('x', 'done', {})), 4);
|
|
196
|
+
assert.equal(attentionTier(arow('x', 'dead', {})), 4);
|
|
197
|
+
assert.equal(attentionTier(arow('x', 'canceled', {})), 4);
|
|
198
|
+
});
|
|
199
|
+
test('attentionMtime: prefers mtimeMs, falls back to created', () => {
|
|
200
|
+
assert.equal(attentionMtime(arow('x', 'active', { mtimeMs: 1234 })), 1234);
|
|
201
|
+
// No mtimeMs → epoch-ms of the ISO `created` (fixture is 2026-01-01T00:00:00Z).
|
|
202
|
+
assert.equal(attentionMtime(arow('x', 'active', {})), Date.parse('2026-01-01T00:00:00.000Z'));
|
|
203
|
+
});
|
|
204
|
+
test('flatten: attention sort tiers rows (attached/streaming/live first)', () => {
|
|
205
|
+
// One row per tier, deliberately shuffled in input order.
|
|
206
|
+
const rows = [
|
|
207
|
+
arow('dormant', 'done', { mtimeMs: 100 }), // T4
|
|
208
|
+
arow('live', 'active', { mtimeMs: 100 }), // T3
|
|
209
|
+
arow('streaming', 'active', { streaming: true, mtimeMs: 100 }), // T2
|
|
210
|
+
arow('attached', 'idle', { viewed: true, mtimeMs: 100 }), // T1
|
|
211
|
+
arow('both', 'active', { viewed: true, streaming: true, mtimeMs: 100 }), // T0
|
|
212
|
+
];
|
|
213
|
+
const t = buildTree(rows, [], () => []);
|
|
214
|
+
const v = flatten(t, { collapsed: new Set(), tab: 'All', query: '', sort: 'attention' });
|
|
215
|
+
assert.deepEqual(v.map((r) => r.id), ['both', 'attached', 'streaming', 'live', 'dormant']);
|
|
216
|
+
});
|
|
217
|
+
test('flatten: attention sort orders newest-message-first WITHIN a tier', () => {
|
|
218
|
+
// All dormant (T4) → ordering is purely by mtime, newest first.
|
|
219
|
+
const rows = [
|
|
220
|
+
arow('old', 'done', { mtimeMs: 1000 }),
|
|
221
|
+
arow('newest', 'done', { mtimeMs: 3000 }),
|
|
222
|
+
arow('mid', 'done', { mtimeMs: 2000 }),
|
|
223
|
+
];
|
|
224
|
+
const t = buildTree(rows, [], () => []);
|
|
225
|
+
const v = flatten(t, { collapsed: new Set(), tab: 'All', query: '', sort: 'attention' });
|
|
226
|
+
assert.deepEqual(v.map((r) => r.id), ['newest', 'mid', 'old']);
|
|
227
|
+
});
|
|
@@ -55,7 +55,8 @@ test('color on: each status glyph carries its hue, glyph kept', () => {
|
|
|
55
55
|
});
|
|
56
56
|
test('color on: ctx ≥100k row is red; asks are bright-yellow', () => {
|
|
57
57
|
const out = renderFrame(state({ cursor: -1 }), SIZE, ON);
|
|
58
|
-
|
|
58
|
+
// ctx is a right-aligned fixed-width column now, so the red span carries padding.
|
|
59
|
+
assert.ok(out.includes(`${ESC}31m`) && out.includes('120k'), 'ctx ≥100k → red');
|
|
59
60
|
assert.ok(out.includes(`${ESC}93m`), 'asks → bright-yellow');
|
|
60
61
|
assert.ok(out.includes('⚑3'), 'flag glyph kept');
|
|
61
62
|
});
|