@crouton-kit/crouter 0.3.14 → 0.3.16
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 +8 -0
- package/dist/build-root.js +30 -0
- package/dist/builtin-personas/design/base.md +3 -7
- package/dist/builtin-personas/design/orchestrator.md +4 -3
- package/dist/builtin-personas/developer/base.md +3 -7
- package/dist/builtin-personas/developer/orchestrator.md +5 -4
- package/dist/builtin-personas/explore/base.md +3 -7
- package/dist/builtin-personas/explore/orchestrator.md +1 -5
- package/dist/builtin-personas/general/base.md +2 -4
- package/dist/builtin-personas/general/orchestrator.md +2 -4
- package/dist/builtin-personas/lifecycle/resident.md +2 -0
- package/dist/builtin-personas/lifecycle/terminal.md +6 -0
- package/dist/builtin-personas/orchestration-kernel.md +42 -3
- package/dist/builtin-personas/plan/base.md +3 -5
- package/dist/builtin-personas/plan/orchestrator.md +5 -4
- package/dist/builtin-personas/plan/reviewers/architecture-fit/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/code-smells/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/security/base.md +9 -0
- package/dist/builtin-personas/review/base.md +3 -5
- package/dist/builtin-personas/review/orchestrator.md +2 -6
- package/dist/builtin-personas/runtime-base.md +3 -19
- package/dist/builtin-personas/spec/base.md +3 -5
- package/dist/builtin-personas/spec/orchestrator.md +4 -3
- package/dist/builtin-personas/spine/has-manager.md +10 -0
- package/dist/builtin-personas/spine/no-manager.md +2 -0
- package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +96 -0
- package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +49 -0
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +49 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +1 -1
- package/dist/builtin-skills/skills/spec/SKILL.md +2 -2
- package/dist/cli.js +6 -29
- package/dist/commands/attention.js +76 -7
- package/dist/commands/canvas-prune.d.ts +2 -0
- package/dist/commands/canvas-prune.js +66 -0
- package/dist/commands/canvas.js +5 -8
- package/dist/commands/chord.d.ts +2 -0
- package/dist/commands/chord.js +143 -0
- package/dist/commands/daemon.js +8 -5
- package/dist/commands/dashboard.js +2 -0
- package/dist/commands/human/prompts.js +28 -27
- package/dist/commands/human/queue.js +30 -14
- package/dist/commands/human/shared.d.ts +26 -21
- package/dist/commands/human/shared.js +45 -67
- package/dist/commands/human.js +4 -14
- package/dist/commands/node.d.ts +11 -0
- package/dist/commands/node.js +221 -99
- package/dist/commands/pkg/market-inspect.js +6 -4
- package/dist/commands/pkg/market-manage.js +10 -6
- package/dist/commands/pkg/market.js +2 -4
- package/dist/commands/pkg/plugin-inspect.js +6 -4
- package/dist/commands/pkg/plugin-manage.js +12 -7
- package/dist/commands/pkg/plugin.js +2 -4
- package/dist/commands/pkg.js +0 -4
- package/dist/commands/push.js +178 -15
- package/dist/commands/revive.js +5 -3
- package/dist/commands/skill/author.js +6 -4
- package/dist/commands/skill/find.js +8 -5
- package/dist/commands/skill/read.js +2 -0
- package/dist/commands/skill/state.js +6 -4
- package/dist/commands/skill.js +0 -6
- package/dist/commands/sys/config.js +21 -7
- package/dist/commands/sys/doctor.js +2 -0
- package/dist/commands/sys/update.js +4 -0
- package/dist/commands/sys.js +0 -6
- package/dist/commands/tmux-spread.d.ts +2 -0
- package/dist/commands/tmux-spread.js +129 -0
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +25 -0
- package/dist/core/__tests__/child-followup.test.d.ts +1 -0
- package/dist/core/__tests__/child-followup.test.js +83 -0
- package/dist/core/__tests__/close.test.d.ts +1 -0
- package/dist/core/__tests__/close.test.js +148 -0
- package/dist/core/__tests__/context-intro.test.d.ts +1 -0
- package/dist/core/__tests__/context-intro.test.js +196 -0
- package/dist/core/__tests__/daemon-boot.test.d.ts +1 -0
- package/dist/core/__tests__/daemon-boot.test.js +93 -0
- package/dist/core/__tests__/daemon-liveness.test.d.ts +1 -0
- package/dist/core/__tests__/daemon-liveness.test.js +223 -0
- package/dist/core/__tests__/focuses.test.d.ts +1 -0
- package/dist/core/__tests__/focuses.test.js +196 -0
- package/dist/core/__tests__/fork.test.d.ts +1 -0
- package/dist/core/__tests__/fork.test.js +91 -0
- package/dist/core/__tests__/home-session.test.d.ts +1 -0
- package/dist/core/__tests__/home-session.test.js +153 -0
- package/dist/core/__tests__/human-cancel-guard.test.d.ts +1 -0
- package/dist/core/__tests__/human-cancel-guard.test.js +49 -0
- package/dist/core/__tests__/keystone.test.d.ts +1 -0
- package/dist/core/__tests__/keystone.test.js +185 -0
- package/dist/core/__tests__/kickoff.test.d.ts +1 -0
- package/dist/core/__tests__/kickoff.test.js +89 -0
- package/dist/core/__tests__/lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/lifecycle.test.js +178 -0
- package/dist/core/__tests__/listing-completeness.test.d.ts +1 -0
- package/dist/core/__tests__/listing-completeness.test.js +31 -0
- package/dist/core/__tests__/memory.test.d.ts +1 -0
- package/dist/core/__tests__/memory.test.js +152 -0
- package/dist/core/__tests__/migration.test.d.ts +1 -0
- package/dist/core/__tests__/migration.test.js +238 -0
- package/dist/core/__tests__/pane-column.test.d.ts +1 -0
- package/dist/core/__tests__/pane-column.test.js +153 -0
- package/dist/core/__tests__/passive-subscription.test.js +24 -1
- package/dist/core/__tests__/persona-compose.test.d.ts +1 -0
- package/dist/core/__tests__/persona-compose.test.js +53 -0
- package/dist/core/__tests__/persona-subkind.test.d.ts +1 -0
- package/dist/core/__tests__/persona-subkind.test.js +62 -0
- package/dist/core/__tests__/persona.test.d.ts +1 -0
- package/dist/core/__tests__/persona.test.js +107 -0
- package/dist/core/__tests__/placement-focus.test.d.ts +1 -0
- package/dist/core/__tests__/placement-focus.test.js +266 -0
- package/dist/core/__tests__/placement-reconcile.test.d.ts +1 -0
- package/dist/core/__tests__/placement-reconcile.test.js +212 -0
- package/dist/core/__tests__/placement-revive.test.d.ts +1 -0
- package/dist/core/__tests__/placement-revive.test.js +238 -0
- package/dist/core/__tests__/placement-teardown.test.d.ts +1 -0
- package/dist/core/__tests__/placement-teardown.test.js +178 -0
- package/dist/core/__tests__/prune.test.d.ts +1 -0
- package/dist/core/__tests__/prune.test.js +116 -0
- package/dist/core/__tests__/push-final-guard.test.d.ts +1 -0
- package/dist/core/__tests__/push-final-guard.test.js +71 -0
- package/dist/core/__tests__/relaunch.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch.test.js +334 -0
- package/dist/core/__tests__/reset.test.js +26 -7
- package/dist/core/__tests__/revive.test.d.ts +1 -0
- package/dist/core/__tests__/revive.test.js +217 -0
- package/dist/core/__tests__/spawn-root.test.d.ts +1 -0
- package/dist/core/__tests__/spawn-root.test.js +73 -0
- package/dist/core/__tests__/steer-note.test.d.ts +1 -0
- package/dist/core/__tests__/steer-note.test.js +39 -0
- package/dist/core/__tests__/stop-guard.test.d.ts +1 -0
- package/dist/core/__tests__/stop-guard.test.js +82 -0
- package/dist/core/__tests__/subcommand-tier.test.js +35 -33
- package/dist/core/__tests__/tmux-surface.test.d.ts +1 -0
- package/dist/core/__tests__/tmux-surface.test.js +105 -0
- package/dist/core/__tests__/unknown-path.test.js +8 -2
- package/dist/core/canvas/attention.d.ts +10 -0
- package/dist/core/canvas/attention.js +40 -0
- package/dist/core/canvas/canvas.d.ts +66 -7
- package/dist/core/canvas/canvas.js +209 -21
- package/dist/core/canvas/db.d.ts +8 -0
- package/dist/core/canvas/db.js +205 -4
- package/dist/core/canvas/focuses.d.ts +22 -0
- package/dist/core/canvas/focuses.js +81 -0
- package/dist/core/canvas/index.d.ts +3 -0
- package/dist/core/canvas/index.js +3 -0
- package/dist/core/canvas/labels.d.ts +27 -0
- package/dist/core/canvas/labels.js +36 -0
- package/dist/core/canvas/render.js +25 -10
- package/dist/core/canvas/telemetry.d.ts +14 -0
- package/dist/core/canvas/telemetry.js +35 -0
- package/dist/core/canvas/types.d.ts +115 -12
- package/dist/core/command.d.ts +25 -1
- package/dist/core/command.js +23 -15
- package/dist/core/config.js +36 -2
- package/dist/core/feed/feed.js +3 -3
- package/dist/core/feed/inbox.d.ts +3 -1
- package/dist/core/feed/inbox.js +45 -5
- package/dist/core/feed/passive.js +24 -11
- package/dist/core/help.d.ts +26 -13
- package/dist/core/help.js +44 -37
- package/dist/core/personas/index.d.ts +1 -1
- package/dist/core/personas/index.js +1 -1
- package/dist/core/personas/loader.d.ts +40 -1
- package/dist/core/personas/loader.js +63 -1
- package/dist/core/personas/resolve.d.ts +13 -6
- package/dist/core/personas/resolve.js +46 -34
- package/dist/core/runtime/bearings.d.ts +20 -0
- package/dist/core/runtime/bearings.js +92 -0
- package/dist/core/runtime/close.d.ts +14 -0
- package/dist/core/runtime/close.js +151 -0
- package/dist/core/runtime/demote.js +24 -12
- package/dist/core/runtime/front-door.js +1 -1
- package/dist/core/runtime/kickoff.d.ts +23 -6
- package/dist/core/runtime/kickoff.js +92 -36
- package/dist/core/runtime/launch.d.ts +26 -12
- package/dist/core/runtime/launch.js +78 -19
- package/dist/core/runtime/lifecycle.d.ts +13 -0
- package/dist/core/runtime/lifecycle.js +86 -0
- package/dist/core/runtime/memory.d.ts +43 -0
- package/dist/core/runtime/memory.js +165 -0
- package/dist/core/runtime/naming.d.ts +22 -0
- package/dist/core/runtime/naming.js +166 -0
- package/dist/core/runtime/nodes.d.ts +39 -1
- package/dist/core/runtime/nodes.js +69 -10
- package/dist/core/runtime/persona.d.ts +25 -0
- package/dist/core/runtime/persona.js +139 -0
- package/dist/core/runtime/placement.d.ts +299 -0
- package/dist/core/runtime/placement.js +688 -0
- package/dist/core/runtime/promote.d.ts +14 -7
- package/dist/core/runtime/promote.js +57 -67
- package/dist/core/runtime/reset.d.ts +47 -4
- package/dist/core/runtime/reset.js +223 -52
- package/dist/core/runtime/revive.d.ts +26 -2
- package/dist/core/runtime/revive.js +166 -39
- package/dist/core/runtime/spawn.d.ts +20 -5
- package/dist/core/runtime/spawn.js +163 -43
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +18 -8
- package/dist/core/runtime/tmux-chrome.d.ts +1 -0
- package/dist/core/runtime/tmux-chrome.js +4 -0
- package/dist/core/runtime/tmux.d.ts +113 -20
- package/dist/core/runtime/tmux.js +221 -39
- package/dist/core/spawn.js +15 -0
- package/dist/daemon/crtrd.d.ts +12 -1
- package/dist/daemon/crtrd.js +152 -34
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +266 -0
- package/dist/pi-extensions/canvas-commands.js +16 -13
- package/dist/pi-extensions/canvas-context-intro.d.ts +70 -0
- package/dist/pi-extensions/canvas-context-intro.js +164 -0
- package/dist/pi-extensions/canvas-goal-capture.d.ts +3 -0
- package/dist/pi-extensions/canvas-goal-capture.js +15 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +11 -0
- package/dist/pi-extensions/canvas-nav.d.ts +12 -4
- package/dist/pi-extensions/canvas-nav.js +594 -262
- package/dist/pi-extensions/canvas-resume.d.ts +22 -0
- package/dist/pi-extensions/canvas-resume.js +173 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +16 -0
- package/dist/pi-extensions/canvas-stophook.js +340 -228
- package/dist/types.d.ts +28 -0
- package/dist/types.js +16 -0
- package/package.json +2 -2
- package/dist/core/runtime/presence.d.ts +0 -38
- package/dist/core/runtime/presence.js +0 -154
package/dist/types.d.ts
CHANGED
|
@@ -53,6 +53,28 @@ export interface AutoUpdateConfig {
|
|
|
53
53
|
content: AutoUpdateMode;
|
|
54
54
|
interval_hours: number;
|
|
55
55
|
}
|
|
56
|
+
/** One canvas-nav action binding: a `crtr` argv string (templated with
|
|
57
|
+
* {id|self|name|manager|subtree}) plus optional confirm gate + menu label.
|
|
58
|
+
* `run` of the sentinel `__graph__` toggles the in-pi GRAPH modal instead of
|
|
59
|
+
* shelling a command. */
|
|
60
|
+
export interface CanvasBind {
|
|
61
|
+
run: string;
|
|
62
|
+
confirm?: boolean;
|
|
63
|
+
desc?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Canvas-nav config (`canvasNav` in config.json). `prefixBinds` are consumed
|
|
66
|
+
* at the tmux alt+c menu layer; `graphBinds` are consumed in-process by the
|
|
67
|
+
* canvas-nav pi extension while in GRAPH. Built-in GRAPH keys
|
|
68
|
+
* (j/k/h/l/g/G/enter/m/e/x/esc) are reserved — `graphBinds` is strictly
|
|
69
|
+
* additive. */
|
|
70
|
+
export interface CanvasNavConfig {
|
|
71
|
+
/** Fallback pi shortcut for GRAPH toggle when NOT in tmux. Default 'alt+g'. */
|
|
72
|
+
prefixKey?: string;
|
|
73
|
+
/** chord key (after alt+c) → action; tmux-menu layer. */
|
|
74
|
+
prefixBinds: Record<string, CanvasBind>;
|
|
75
|
+
/** extra raw key in GRAPH → action; pi-extension layer (additive only). */
|
|
76
|
+
graphBinds: Record<string, CanvasBind>;
|
|
77
|
+
}
|
|
56
78
|
export interface ScopeConfig {
|
|
57
79
|
schema_version: number;
|
|
58
80
|
marketplaces: Record<string, ConfigMarketplaceEntry>;
|
|
@@ -60,6 +82,7 @@ export interface ScopeConfig {
|
|
|
60
82
|
skills: Record<string, ConfigSkillEntry>;
|
|
61
83
|
auto_update: AutoUpdateConfig;
|
|
62
84
|
max_panes_per_window: number;
|
|
85
|
+
canvasNav: CanvasNavConfig;
|
|
63
86
|
}
|
|
64
87
|
export interface ScopeState {
|
|
65
88
|
marketplaces: Record<string, {
|
|
@@ -141,5 +164,10 @@ export declare const AGENTS_DIR = "agents";
|
|
|
141
164
|
export declare const SCOPE_SKILL_PLUGIN = "_";
|
|
142
165
|
export declare const DEFAULT_MAX_PANES_PER_WINDOW = 3;
|
|
143
166
|
export declare function defaultScopeConfig(): ScopeConfig;
|
|
167
|
+
/** Built-in canvas-nav binds so an absent config still gives the intended UX.
|
|
168
|
+
* prefixBinds: g→GRAPH toggle (sentinel), m→focus manager, e→expand to tmux.
|
|
169
|
+
* 1..9 (focus report N) are generated by the menu layer, not literal entries.
|
|
170
|
+
* graphBinds: empty — built-in GRAPH keys are reserved, custom keys add on. */
|
|
171
|
+
export declare function defaultCanvasNavConfig(): CanvasNavConfig;
|
|
144
172
|
export declare function skillConfigKey(plugin: string, name: string): string;
|
|
145
173
|
export declare function defaultScopeState(): ScopeState;
|
package/dist/types.js
CHANGED
|
@@ -36,6 +36,22 @@ export function defaultScopeConfig() {
|
|
|
36
36
|
skills: {},
|
|
37
37
|
auto_update: { crtr: 'notify', content: 'notify', interval_hours: 24 },
|
|
38
38
|
max_panes_per_window: DEFAULT_MAX_PANES_PER_WINDOW,
|
|
39
|
+
canvasNav: defaultCanvasNavConfig(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Built-in canvas-nav binds so an absent config still gives the intended UX.
|
|
43
|
+
* prefixBinds: g→GRAPH toggle (sentinel), m→focus manager, e→expand to tmux.
|
|
44
|
+
* 1..9 (focus report N) are generated by the menu layer, not literal entries.
|
|
45
|
+
* graphBinds: empty — built-in GRAPH keys are reserved, custom keys add on. */
|
|
46
|
+
export function defaultCanvasNavConfig() {
|
|
47
|
+
return {
|
|
48
|
+
prefixKey: 'alt+g',
|
|
49
|
+
prefixBinds: {
|
|
50
|
+
g: { run: '__graph__', desc: 'graph view' },
|
|
51
|
+
m: { run: 'node focus {manager}', desc: 'focus manager' },
|
|
52
|
+
e: { run: 'canvas tmux-spread {self}', desc: 'expand to tmux' },
|
|
53
|
+
},
|
|
54
|
+
graphBinds: {},
|
|
39
55
|
};
|
|
40
56
|
}
|
|
41
57
|
export function skillConfigKey(plugin, name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crouton-kit/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
4
4
|
"description": "crtr — fast access to skills, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@crouton-kit/humanloop": "^0.3.
|
|
39
|
+
"@crouton-kit/humanloop": "^0.3.15",
|
|
40
40
|
"commander": "^13.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { NodeMeta } from '../canvas/index.js';
|
|
2
|
-
/** Persist `nodeId` as the currently focused node. Best-effort; never throws. */
|
|
3
|
-
export declare function setFocus(nodeId: string): void;
|
|
4
|
-
/** Read the currently focused node id, or null if the pointer is absent or
|
|
5
|
-
* empty (no active focus). Best-effort; never throws. */
|
|
6
|
-
export declare function getFocus(): string | null;
|
|
7
|
-
/** True when the node's tmux window is alive. A falsy tmux_session/window
|
|
8
|
-
* always returns false so callers don't need to null-guard. */
|
|
9
|
-
export declare function nodeLive(meta: NodeMeta): boolean;
|
|
10
|
-
/** Bring a node's tmux window to the foreground and record it as focused.
|
|
11
|
-
*
|
|
12
|
-
* Strategy:
|
|
13
|
-
* - If the node has no live window (`nodeLive` is false), still write the
|
|
14
|
-
* focus pointer — the caller (e.g. revive logic) uses `focused:false` to
|
|
15
|
-
* know it needs to open a window first.
|
|
16
|
-
* - Otherwise call `switchClient` (lands us in the right session) then
|
|
17
|
-
* `selectWindow` (picks the right window within it). Both calls are
|
|
18
|
-
* best-effort; the focus pointer is always written regardless.
|
|
19
|
-
*
|
|
20
|
-
* Returns:
|
|
21
|
-
* focused — whether the tmux focus actually succeeded.
|
|
22
|
-
* session — the tmux session name if one was attempted, null otherwise. */
|
|
23
|
-
export declare function focusNode(nodeId: string): {
|
|
24
|
-
focused: boolean;
|
|
25
|
-
session: string | null;
|
|
26
|
-
};
|
|
27
|
-
/** Focus a node IN PLACE: bring its pane into the caller's current pane slot
|
|
28
|
-
* (swap-pane) instead of navigating the client to the node's own window. This
|
|
29
|
-
* is the default for `crtr node focus` and the nav-chrome spine jump — the
|
|
30
|
-
* agent appears where you are.
|
|
31
|
-
*
|
|
32
|
-
* Falls back to window focus when there is no caller pane (not inside tmux) or
|
|
33
|
-
* the target pane can't be resolved. `inPlace` reports which path ran. */
|
|
34
|
-
export declare function focusNodeInPlace(nodeId: string, callerPane?: string, callerNodeId?: string): {
|
|
35
|
-
focused: boolean;
|
|
36
|
-
session: string | null;
|
|
37
|
-
inPlace: boolean;
|
|
38
|
-
};
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// presence.ts — focus pointer + per-node liveness helpers.
|
|
2
|
-
//
|
|
3
|
-
// The focus pointer (`<crtrHome>/focus.ptr`) is a plain-text file holding the
|
|
4
|
-
// node id that currently "has focus" — meaning the user's terminal is showing
|
|
5
|
-
// that node's tmux window. It is written on every explicit `focusNode()` call
|
|
6
|
-
// and read by the dashboard / status-line to highlight the active node.
|
|
7
|
-
//
|
|
8
|
-
// This is intentionally a simple file-based pointer rather than a database
|
|
9
|
-
// column: focus is transient UI state, not durable business data. A crash
|
|
10
|
-
// leaves a stale pointer that the next focusNode() clobbers — harmless.
|
|
11
|
-
//
|
|
12
|
-
// focusNode() does two things:
|
|
13
|
-
// 1. Ensures the user's terminal lands on the right tmux window by calling
|
|
14
|
-
// switchClient (cross-session) then selectWindow (in-session). Both are
|
|
15
|
-
// best-effort; we set the pointer regardless so the dashboard stays in sync.
|
|
16
|
-
// 2. Persists the node id to focus.ptr so any process can quickly read "what
|
|
17
|
-
// is the user looking at?".
|
|
18
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
-
import { dirname } from 'node:path';
|
|
20
|
-
import { join } from 'node:path';
|
|
21
|
-
import { crtrHome, getNode, updateNode } from '../canvas/index.js';
|
|
22
|
-
import { selectWindow, switchClient, windowAlive, currentTmux, paneOfWindow, swapPaneInPlace, windowOfPane } from './tmux.js';
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Focus pointer
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
/** Absolute path to the focus pointer file. */
|
|
27
|
-
function focusPtrPath() {
|
|
28
|
-
return join(crtrHome(), 'focus.ptr');
|
|
29
|
-
}
|
|
30
|
-
/** Persist `nodeId` as the currently focused node. Best-effort; never throws. */
|
|
31
|
-
export function setFocus(nodeId) {
|
|
32
|
-
try {
|
|
33
|
-
const p = focusPtrPath();
|
|
34
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
35
|
-
writeFileSync(p, nodeId, 'utf8');
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
/* focus pointer is best-effort; never surface */
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/** Read the currently focused node id, or null if the pointer is absent or
|
|
42
|
-
* empty (no active focus). Best-effort; never throws. */
|
|
43
|
-
export function getFocus() {
|
|
44
|
-
try {
|
|
45
|
-
const raw = readFileSync(focusPtrPath(), 'utf8').trim();
|
|
46
|
-
return raw !== '' ? raw : null;
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Liveness
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
/** True when the node's tmux window is alive. A falsy tmux_session/window
|
|
56
|
-
* always returns false so callers don't need to null-guard. */
|
|
57
|
-
export function nodeLive(meta) {
|
|
58
|
-
return windowAlive(meta.tmux_session, meta.window);
|
|
59
|
-
}
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Focus
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
/** Bring a node's tmux window to the foreground and record it as focused.
|
|
64
|
-
*
|
|
65
|
-
* Strategy:
|
|
66
|
-
* - If the node has no live window (`nodeLive` is false), still write the
|
|
67
|
-
* focus pointer — the caller (e.g. revive logic) uses `focused:false` to
|
|
68
|
-
* know it needs to open a window first.
|
|
69
|
-
* - Otherwise call `switchClient` (lands us in the right session) then
|
|
70
|
-
* `selectWindow` (picks the right window within it). Both calls are
|
|
71
|
-
* best-effort; the focus pointer is always written regardless.
|
|
72
|
-
*
|
|
73
|
-
* Returns:
|
|
74
|
-
* focused — whether the tmux focus actually succeeded.
|
|
75
|
-
* session — the tmux session name if one was attempted, null otherwise. */
|
|
76
|
-
export function focusNode(nodeId) {
|
|
77
|
-
const meta = getNode(nodeId);
|
|
78
|
-
// Always write the pointer so the dashboard reflects intent even when focus
|
|
79
|
-
// fails (e.g. we're not currently inside tmux).
|
|
80
|
-
setFocus(nodeId);
|
|
81
|
-
if (meta === null || !nodeLive(meta)) {
|
|
82
|
-
// Node not found or window is gone — caller may need to revive.
|
|
83
|
-
return { focused: false, session: meta?.tmux_session ?? null };
|
|
84
|
-
}
|
|
85
|
-
// Both fields are non-null thanks to nodeLive() returning true.
|
|
86
|
-
const session = meta.tmux_session;
|
|
87
|
-
const window = meta.window;
|
|
88
|
-
// Cross-session hop first, then window selection within the session.
|
|
89
|
-
// switchClient may be a no-op when already in the same session but is
|
|
90
|
-
// always safe to call — tmux handles it gracefully.
|
|
91
|
-
const clientOk = switchClient(session);
|
|
92
|
-
const windowOk = selectWindow(session, window);
|
|
93
|
-
return { focused: clientOk && windowOk, session };
|
|
94
|
-
}
|
|
95
|
-
/** Focus a node IN PLACE: bring its pane into the caller's current pane slot
|
|
96
|
-
* (swap-pane) instead of navigating the client to the node's own window. This
|
|
97
|
-
* is the default for `crtr node focus` and the nav-chrome spine jump — the
|
|
98
|
-
* agent appears where you are.
|
|
99
|
-
*
|
|
100
|
-
* Falls back to window focus when there is no caller pane (not inside tmux) or
|
|
101
|
-
* the target pane can't be resolved. `inPlace` reports which path ran. */
|
|
102
|
-
export function focusNodeInPlace(nodeId, callerPane, callerNodeId) {
|
|
103
|
-
const meta = getNode(nodeId);
|
|
104
|
-
// Always write the pointer so the dashboard reflects intent even on failure.
|
|
105
|
-
setFocus(nodeId);
|
|
106
|
-
if (meta === null || !nodeLive(meta)) {
|
|
107
|
-
return { focused: false, session: meta?.tmux_session ?? null, inPlace: false };
|
|
108
|
-
}
|
|
109
|
-
const session = meta.tmux_session;
|
|
110
|
-
const window = meta.window;
|
|
111
|
-
const pane = callerPane ?? process.env['TMUX_PANE'] ?? currentTmux()?.pane;
|
|
112
|
-
// No caller pane (not in tmux) — best we can do is bring the window forefront.
|
|
113
|
-
if (pane === undefined || pane === '') {
|
|
114
|
-
const ok = switchClient(session) && selectWindow(session, window);
|
|
115
|
-
return { focused: ok, session, inPlace: false };
|
|
116
|
-
}
|
|
117
|
-
const targetPane = paneOfWindow(session, window);
|
|
118
|
-
if (targetPane === null) {
|
|
119
|
-
const ok = switchClient(session) && selectWindow(session, window);
|
|
120
|
-
return { focused: ok, session, inPlace: false };
|
|
121
|
-
}
|
|
122
|
-
if (targetPane === pane)
|
|
123
|
-
return { focused: true, session, inPlace: true }; // already here
|
|
124
|
-
// The window the caller's pane currently sits in — the slot the target's pane
|
|
125
|
-
// is about to be swapped INTO.
|
|
126
|
-
const callerWindow = windowOfPane(pane);
|
|
127
|
-
const ok = swapPaneInPlace(targetPane, pane);
|
|
128
|
-
// Keep the canvas window mapping in sync with the physical swap. swap-pane
|
|
129
|
-
// exchanges the two PANES between their windows (pane ids are stable, windows
|
|
130
|
-
// are slots): after the swap the target's pane occupies the caller's window
|
|
131
|
-
// and the caller's pane occupies the target's old window. Without this update
|
|
132
|
-
// meta.window goes stale, and a later paneOfWindow(session, meta.window)
|
|
133
|
-
// resolves the WRONG pane — the bug that made focusing back to a manager a
|
|
134
|
-
// no-op (it kept resolving the pane already in view) and made a focused node's
|
|
135
|
-
// exit collapse the visible window instead of its background one.
|
|
136
|
-
if (ok && callerWindow !== null && callerWindow !== window) {
|
|
137
|
-
try {
|
|
138
|
-
updateNode(nodeId, { window: callerWindow });
|
|
139
|
-
}
|
|
140
|
-
catch { /* best-effort */ }
|
|
141
|
-
// The caller is the node running this focus (its pi process owns callerPane).
|
|
142
|
-
// Its pane moved to the target's old window, so re-point its window there.
|
|
143
|
-
// Prefer an explicit id (the `node cycle` tmux binding runs outside any pi,
|
|
144
|
-
// so CRTR_NODE_ID is unset there) and fall back to the env for `node focus`.
|
|
145
|
-
const cnid = callerNodeId ?? process.env['CRTR_NODE_ID'];
|
|
146
|
-
if (cnid !== undefined && cnid.trim() !== '' && cnid !== nodeId) {
|
|
147
|
-
try {
|
|
148
|
-
updateNode(cnid, { window });
|
|
149
|
-
}
|
|
150
|
-
catch { /* best-effort */ }
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return { focused: ok, session, inPlace: true };
|
|
154
|
-
}
|