@north-light/crouter 0.3.277 → 0.3.278
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/clients/attach/input/capabilities.js +2 -1
- package/dist/clients/attach/slash/dispatch.js +42 -19
- package/dist/clients/attach/viewer.js +523 -523
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +32 -7
- package/dist/core/canvas/browse/render.d.ts +17 -2
- package/dist/core/canvas/browse/render.js +342 -242
- package/dist/core/canvas/render-source.d.ts +26 -0
- package/dist/core/canvas/render-source.js +73 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -23,14 +23,14 @@ export const browseLeaf = defineLeaf({
|
|
|
23
23
|
name: 'return-pane',
|
|
24
24
|
type: 'string',
|
|
25
25
|
required: false,
|
|
26
|
-
constraint: 'tmux pane id
|
|
26
|
+
constraint: 'tmux pane id the chosen node REPLACES on Enter — that pane is respawned onto its viewer (set by the /resume popup, so resuming swaps the pane you invoked it from). Default: focus into this pane without replacing it.',
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
kind: 'flag',
|
|
30
30
|
name: 'cwd',
|
|
31
31
|
type: 'string',
|
|
32
32
|
required: false,
|
|
33
|
-
constraint: 'directory used when you toggle the optional cwd filter (c). The navigator opens with All dirs; /resume
|
|
33
|
+
constraint: 'directory used when you toggle the optional cwd filter (c). The navigator opens with All dirs; /resume passes the launching node\'s cwd. Default: the invocation cwd.',
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
kind: 'flag',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CanvasSource } from '../source.js';
|
|
2
2
|
import { type BindingId, type BindingResolution, type RawTerminalInput } from '../../keybindings/index.js';
|
|
3
|
-
declare const BROWSE_NAV_ACTIONS: readonly ["crtr.browse.quit", "crtr.browse.down", "crtr.browse.up", "crtr.browse.first", "crtr.browse.last", "crtr.browse.expand-or-child", "crtr.browse.collapse-or-parent", "crtr.browse.toggle-fold", "crtr.browse.tab-next", "crtr.browse.tab-previous", "crtr.browse.tab.1", "crtr.browse.tab.2", "crtr.browse.tab.3", "crtr.browse.tab.4", "crtr.browse.tab.5", "crtr.browse.close-subtree", "crtr.browse.sort", "crtr.browse.scope", "crtr.browse.profile", "crtr.browse.residents", "crtr.browse.preview", "crtr.browse.pin", "crtr.browse.search", "crtr.browse.revive-now", "crtr.browse.focus", "crtr.browse.focus-background", "crtr.browse.yank-id"];
|
|
3
|
+
declare const BROWSE_NAV_ACTIONS: readonly ["crtr.browse.quit", "crtr.browse.down", "crtr.browse.up", "crtr.browse.first", "crtr.browse.last", "crtr.browse.expand-or-child", "crtr.browse.collapse-or-parent", "crtr.browse.toggle-fold", "crtr.browse.tab-next", "crtr.browse.tab-previous", "crtr.browse.tab.1", "crtr.browse.tab.2", "crtr.browse.tab.3", "crtr.browse.tab.4", "crtr.browse.tab.5", "crtr.browse.close-subtree", "crtr.browse.sort", "crtr.browse.scope", "crtr.browse.profile", "crtr.browse.residents", "crtr.browse.preview", "crtr.browse.report", "crtr.browse.pin", "crtr.browse.search", "crtr.browse.revive-now", "crtr.browse.focus", "crtr.browse.focus-background", "crtr.browse.yank-id"];
|
|
4
4
|
declare const BROWSE_SEARCH_ACTIONS: readonly ["crtr.browse.search.cancel", "crtr.browse.search.commit", "crtr.browse.search.delete", "crtr.browse.search.quit"];
|
|
5
5
|
export declare function resolveBrowseNavAction(bindings: BindingResolution<BindingId>, raw: RawTerminalInput): (typeof BROWSE_NAV_ACTIONS)[number] | null;
|
|
6
6
|
export declare function resolveBrowseSearchAction(bindings: BindingResolution<BindingId>, raw: RawTerminalInput): (typeof BROWSE_SEARCH_ACTIONS)[number] | null;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { envNodeId } from '../../../shared/env.js';
|
|
11
11
|
import { execFileSync } from 'node:child_process';
|
|
12
12
|
import { resolve } from 'node:path';
|
|
13
|
-
import { dashboardRowsAllFromSource, enrichRowsFromSource, loadPreviewFromSource, renderForestFromSource } from '../render-source.js';
|
|
13
|
+
import { dashboardRowsAllFromSource, enrichRowsFromSource, loadPreviewFromSource, loadTranscriptFromSource, renderForestFromSource } from '../render-source.js';
|
|
14
14
|
import { fullName } from '../labels.js';
|
|
15
15
|
import { setupTerminal, restoreTerminal, getTerminalSize, parseKeypress, } from '../../tui/terminal.js';
|
|
16
16
|
import { buildTree, flatten, pruneNode, TABS } from './model.js';
|
|
@@ -22,7 +22,7 @@ const BROWSE_NAV_ACTIONS = [
|
|
|
22
22
|
'crtr.browse.tab-next', 'crtr.browse.tab-previous', 'crtr.browse.tab.1', 'crtr.browse.tab.2',
|
|
23
23
|
'crtr.browse.tab.3', 'crtr.browse.tab.4', 'crtr.browse.tab.5', 'crtr.browse.close-subtree', 'crtr.browse.sort',
|
|
24
24
|
'crtr.browse.scope', 'crtr.browse.profile', 'crtr.browse.residents', 'crtr.browse.preview',
|
|
25
|
-
'crtr.browse.pin', 'crtr.browse.search',
|
|
25
|
+
'crtr.browse.report', 'crtr.browse.pin', 'crtr.browse.search',
|
|
26
26
|
'crtr.browse.revive-now', 'crtr.browse.focus', 'crtr.browse.focus-background', 'crtr.browse.yank-id',
|
|
27
27
|
];
|
|
28
28
|
const BROWSE_SEARCH_ACTIONS = [
|
|
@@ -120,7 +120,8 @@ export async function runBrowse(opts) {
|
|
|
120
120
|
cwdScope: null, // default: All dirs
|
|
121
121
|
profileScope: launchProfileId,
|
|
122
122
|
sort: 'attention', // default: attention ordering (attached/streaming/live first)
|
|
123
|
-
preview:
|
|
123
|
+
preview: true, // default ON — the hovered node's conversation is the point
|
|
124
|
+
showReport: false,
|
|
124
125
|
residentsOnly: true, // default ON: hide one-shot workers (decision)
|
|
125
126
|
pendingClose: null,
|
|
126
127
|
flash: null,
|
|
@@ -168,12 +169,24 @@ export async function runBrowse(opts) {
|
|
|
168
169
|
/* best-effort — the SIGTERM is what matters; the daemon owns the resume */
|
|
169
170
|
}
|
|
170
171
|
};
|
|
171
|
-
/** Open the chosen node — the ONLY sanctioned path (reviveNode via surface node focus).
|
|
172
|
+
/** Open the chosen node — the ONLY sanctioned path (reviveNode via surface node focus).
|
|
173
|
+
* With a return pane (the `/resume` popup), the chosen node REPLACES that pane:
|
|
174
|
+
* `--in-place` respawns it onto the node, so resuming swaps the viewer you came
|
|
175
|
+
* from instead of splitting another one beside it. The respawn kills that pane's
|
|
176
|
+
* process tree, so it is dispatched through `tmux run-shell -b` — the tmux server
|
|
177
|
+
* context — and never as a child of this popup, which the same teardown can take
|
|
178
|
+
* with it mid-call. Without a return pane, the caller's own pane hosts the viewer
|
|
179
|
+
* as before. */
|
|
172
180
|
const selectAndFocus = (id) => {
|
|
173
181
|
cleanup();
|
|
174
|
-
const
|
|
182
|
+
const returnPane = opts.returnPane !== undefined && opts.returnPane !== '' ? opts.returnPane : undefined;
|
|
175
183
|
try {
|
|
176
|
-
|
|
184
|
+
if (returnPane !== undefined) {
|
|
185
|
+
execFileSync('tmux', ['run-shell', '-b', `crtr surface node focus ${id} --pane ${returnPane} --in-place >/dev/null 2>&1`], { stdio: 'ignore' });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
execFileSync('crtr', ['surface', 'node', 'focus', id], { stdio: 'inherit' });
|
|
189
|
+
}
|
|
177
190
|
}
|
|
178
191
|
catch {
|
|
179
192
|
// `surface node focus` swaps panes out from under us; a sync call can be
|
|
@@ -244,13 +257,20 @@ export async function runBrowse(opts) {
|
|
|
244
257
|
const cur = curRow();
|
|
245
258
|
if (cur !== undefined) {
|
|
246
259
|
const r = rowOf(cur.id);
|
|
247
|
-
if (r !== undefined)
|
|
260
|
+
if (r !== undefined) {
|
|
248
261
|
await loadPreviewFromSource(source, r);
|
|
262
|
+
// The chat transcript is the side panel's body, so it is read for the
|
|
263
|
+
// SELECTED row only — never on the background warm pass, where one ordered
|
|
264
|
+
// transcript per node would hold the whole canvas's conversations in memory.
|
|
265
|
+
if (previewLayout(size.cols, previewOn).mode === 'side')
|
|
266
|
+
await loadTranscriptFromSource(source, r);
|
|
267
|
+
}
|
|
249
268
|
}
|
|
250
269
|
const frame = renderFrame({
|
|
251
270
|
tree, visible, tab: state.tab, cursor: state.cursor, scrollOffset: state.scrollOffset,
|
|
252
271
|
query: state.query, search: state.search, totalNodes,
|
|
253
272
|
cwdScope: state.cwdScope, profileScope: state.profileScope, sort: state.sort, preview: state.preview,
|
|
273
|
+
showReport: state.showReport,
|
|
254
274
|
residentsOnly: state.residentsOnly, pendingClose: state.pendingClose, flash: state.flash,
|
|
255
275
|
pinned: state.pinned, help: state.help,
|
|
256
276
|
rename: state.rename, renameBuf: state.renameBuf,
|
|
@@ -704,6 +724,11 @@ export async function runBrowse(opts) {
|
|
|
704
724
|
scheduleFlush();
|
|
705
725
|
return;
|
|
706
726
|
}
|
|
727
|
+
if (action === 'crtr.browse.report') {
|
|
728
|
+
state.showReport = !state.showReport;
|
|
729
|
+
scheduleFlush();
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
707
732
|
// Help overlay.
|
|
708
733
|
if (input === '?') {
|
|
709
734
|
state.help = true;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { TranscriptTurn } from '../render-source.js';
|
|
1
2
|
import type { Tab, Tree, VisibleRow, SortMode } from './model.js';
|
|
2
|
-
import { detectColorCaps, type ColorCaps } from '../../tui/draw.js';
|
|
3
|
+
import { detectColorCaps, type Span, type ColorCaps } from '../../tui/draw.js';
|
|
3
4
|
export { detectColorCaps };
|
|
4
5
|
export type { ColorCaps };
|
|
5
6
|
export declare const RECAP_LINES = 2;
|
|
@@ -33,8 +34,12 @@ export interface RenderState {
|
|
|
33
34
|
profileScope: string | undefined;
|
|
34
35
|
/** Active ordering — status line + (flat modes) row presentation. */
|
|
35
36
|
sort: SortMode;
|
|
36
|
-
/** Whether the
|
|
37
|
+
/** Whether the preview panel is drawn. */
|
|
37
38
|
preview: boolean;
|
|
39
|
+
/** Side panel shows the node's newest report instead of the goal/chat/state
|
|
40
|
+
* briefing. Ignored while a search query is live (the transcript owns the panel)
|
|
41
|
+
* and for a node that has pushed nothing. */
|
|
42
|
+
showReport?: boolean;
|
|
38
43
|
/** Lifecycle filter: when true, `terminal` (one-shot worker) nodes are hidden
|
|
39
44
|
* — surfaced as a status-line cue. */
|
|
40
45
|
residentsOnly: boolean;
|
|
@@ -72,6 +77,16 @@ export declare function previewAllocation(hasRecap: boolean, hasReport: boolean)
|
|
|
72
77
|
reply: number;
|
|
73
78
|
report: number;
|
|
74
79
|
};
|
|
80
|
+
/** Re-style every case-insensitive occurrence of `query` inside already-rendered
|
|
81
|
+
* spans, splitting runs at the match edges. Working on the RENDERED text is what
|
|
82
|
+
* lets a transcript keep its markdown and still highlight: the markup is already
|
|
83
|
+
* gone, so no column can drift out from under a highlight. */
|
|
84
|
+
export declare function highlightSpans(spans: Span[], query: string): {
|
|
85
|
+
spans: Span[];
|
|
86
|
+
hits: number;
|
|
87
|
+
};
|
|
88
|
+
/** How many turns contain the query — the count the section rule advertises. */
|
|
89
|
+
export declare function transcriptHitTurns(turns: readonly TranscriptTurn[], query: string): number;
|
|
75
90
|
/**
|
|
76
91
|
* Render the whole frame. Returns a single string that, written as-is, repaints
|
|
77
92
|
* the screen in place. `caps` gates all hue (defaults to a no-color frame so
|