@north-light/crouter 0.3.214 → 0.3.215
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/api/dto/nodes.d.ts +2 -1
- package/dist/api/dto/profiles.d.ts +13 -1
- package/dist/builtin-memory/00-runtime-base.md +12 -11
- package/dist/builtin-memory/01-spine/00-has-manager.md +1 -11
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +4 -10
- package/dist/builtin-memory/04-orchestration-kernel.md +9 -35
- package/dist/builtin-memory/05-kinds/design/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/developer/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/general/00-base.md +2 -0
- package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -1
- package/dist/builtin-memory/05-kinds/review/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/review/security-findings.md +12 -0
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
- package/dist/clients/attach/__tests__/context-message.test.js +28 -1
- package/dist/clients/attach/chrome/canvas-panels.d.ts +0 -4
- package/dist/clients/attach/chrome/canvas-panels.js +5 -7
- package/dist/clients/attach/chrome/inbox-strip.d.ts +31 -0
- package/dist/clients/attach/chrome/inbox-strip.js +187 -0
- package/dist/clients/attach/chrome/roster.d.ts +6 -2
- package/dist/clients/attach/chrome/roster.js +13 -52
- package/dist/clients/attach/chrome/ticket-panel.d.ts +29 -0
- package/dist/clients/attach/chrome/ticket-panel.js +236 -0
- package/dist/clients/attach/render/card-presentation.d.ts +8 -3
- package/dist/clients/attach/render/card-presentation.js +48 -10
- package/dist/clients/attach/render/context-message.d.ts +5 -0
- package/dist/clients/attach/render/context-message.js +20 -14
- package/dist/clients/attach/session/context.d.ts +3 -0
- package/dist/clients/attach/session/frame.d.ts +4 -0
- package/dist/clients/attach/session/frame.js +8 -3
- package/dist/clients/attach/session/keys.d.ts +7 -0
- package/dist/clients/attach/session/keys.js +7 -0
- package/dist/clients/attach/session/layout.js +6 -3
- package/dist/clients/attach/session/pane-focus.d.ts +8 -0
- package/dist/clients/attach/session/pane-focus.js +42 -0
- package/dist/clients/attach/viewer.js +793 -789
- package/dist/clients/inbox/controller.d.ts +9 -0
- package/dist/clients/inbox/controller.js +57 -2
- package/dist/clients/inbox/surface.d.ts +2 -0
- package/dist/clients/inbox/surface.js +18 -3
- package/dist/clients/inbox/tui.d.ts +2 -0
- package/dist/clients/inbox/tui.js +11 -1
- package/dist/commands/node/create.js +3 -3
- package/dist/commands/profile/kind.d.ts +2 -0
- package/dist/commands/profile/kind.js +51 -0
- package/dist/commands/profile/list.js +5 -1
- package/dist/commands/profile/meta.d.ts +4 -0
- package/dist/commands/profile/meta.js +67 -0
- package/dist/commands/profile/new.js +33 -3
- package/dist/commands/profile/pause.d.ts +2 -0
- package/dist/commands/profile/pause.js +60 -0
- package/dist/commands/profile/show.js +9 -1
- package/dist/commands/profile.js +5 -8
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +40 -0
- package/dist/core/__tests__/parse-argv-stdin-secret.test.js +28 -0
- package/dist/core/__tests__/seam/dormancy-release.test.js +7 -0
- package/dist/core/command.js +23 -23
- package/dist/core/help.d.ts +1 -1
- package/dist/core/keybindings/attach-control.d.ts +3 -0
- package/dist/core/keybindings/attach-control.js +1 -0
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/profiles/deletion-reservation.d.ts +7 -3
- package/dist/core/profiles/deletion-reservation.js +9 -5
- package/dist/core/profiles/manifest.d.ts +27 -1
- package/dist/core/profiles/manifest.js +122 -4
- package/dist/core/runtime/boot-root.js +3 -3
- package/dist/core/runtime/close.js +10 -5
- package/dist/core/runtime/revive.js +2 -0
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +17 -1
- package/dist/core/termrender/termrender.d.ts +7 -2
- package/dist/core/termrender/termrender.js +11 -6
- package/dist/core/termrender/version.d.ts +1 -1
- package/dist/core/termrender/version.js +1 -1
- package/dist/daemon/api/__tests__/profile-launch-gates.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +111 -0
- package/dist/daemon/api/handlers/messages.js +9 -2
- package/dist/daemon/api/handlers/nodes.d.ts +2 -0
- package/dist/daemon/api/handlers/nodes.js +15 -8
- package/dist/daemon/api/handlers/profiles.js +7 -2
- package/dist/daemon/api/map.js +3 -0
- package/dist/daemon/fleet.js +1 -6
- package/dist/daemon/reconcilers/broker-supervision.js +15 -25
- package/dist/daemon/reconcilers/dormant-inbox.js +10 -10
- package/dist/daemon/reconcilers/live-obligation.d.ts +5 -1
- package/dist/daemon/reconcilers/live-obligation.js +11 -19
- package/dist/pi-extensions/canvas-inbox-watcher.js +15 -0
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/builtin-memory/01-spine/01-no-manager.md +0 -11
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -8
- /package/dist/builtin-memory/05-kinds/advisor/{00-base.md → advice-contract.md} +0 -0
- /package/dist/builtin-memory/05-kinds/plan/reviewers/{00-base.md → lens-contract.md} +0 -0
|
@@ -24,17 +24,55 @@ function extractCardSection(body, section) {
|
|
|
24
24
|
const match = new RegExp(`<${section}\\b[^>]*>([\\s\\S]*?)</${section}>`).exec(body);
|
|
25
25
|
return match ? match[1].trim() : '';
|
|
26
26
|
}
|
|
27
|
+
/** Body with one whole-body wrapping element removed. Envelope bodies carry
|
|
28
|
+
* prose; a tag around all of it is framing the reader does not need. */
|
|
29
|
+
const WHOLE_BODY_ELEMENT = /^<([A-Za-z][\w.:-]*)\b[^>]*>([\s\S]*)<\/\1>$/;
|
|
30
|
+
function unwrapWholeBody(body) {
|
|
31
|
+
const trimmed = body.trim();
|
|
32
|
+
return (WHOLE_BODY_ELEMENT.exec(trimmed)?.[2] ?? trimmed).trim();
|
|
33
|
+
}
|
|
27
34
|
/** The disclosure a card actually offers, given its policy and body. A kind
|
|
28
35
|
* whose section is missing from an old transcript offers nothing to expand. */
|
|
29
|
-
export function cardDisclosure(presentation,
|
|
36
|
+
export function cardDisclosure(presentation, card) {
|
|
30
37
|
switch (presentation.expanded.disclose) {
|
|
31
38
|
case 'never': return '';
|
|
32
|
-
case 'payload': return body;
|
|
33
|
-
case 'section': return extractCardSection(body, presentation.expanded.section);
|
|
39
|
+
case 'payload': return card.body;
|
|
40
|
+
case 'section': return extractCardSection(card.body, presentation.expanded.section);
|
|
41
|
+
case 'render': return presentation.expanded.render(card);
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
|
-
export function cardIsExpandable(presentation,
|
|
37
|
-
return cardDisclosure(presentation,
|
|
44
|
+
export function cardIsExpandable(presentation, card) {
|
|
45
|
+
return cardDisclosure(presentation, card).trim().length > 0;
|
|
46
|
+
}
|
|
47
|
+
/** A sender's entries read by what they are, not by the wire kind, for the two
|
|
48
|
+
* dispositions whose kind describes the delivery rather than the message. */
|
|
49
|
+
function entryLabel(entry) {
|
|
50
|
+
switch (entry.disposition) {
|
|
51
|
+
case 'human-answer': return 'answer';
|
|
52
|
+
case 'human-canceled': return 'canceled';
|
|
53
|
+
default: return entry.kind;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The inbox digest as Markdown: a heading per sender, then each entry's kind
|
|
57
|
+
* and report path above its body. Both levels are headings because the theme
|
|
58
|
+
* paints those and leaves bold at body color, so an entry label set in bold
|
|
59
|
+
* would not separate from the prose under it. Pre-cut scrollback whose
|
|
60
|
+
* `<from>` grammar did not parse keeps its raw body — there is nothing else
|
|
61
|
+
* to show. */
|
|
62
|
+
function inboxDisclosure(card) {
|
|
63
|
+
if (card.senders.length === 0)
|
|
64
|
+
return card.body;
|
|
65
|
+
return card.senders.map((sender) => {
|
|
66
|
+
const blocks = [`## ${sender.id}${sender.finished ? ' · finished' : ''}`];
|
|
67
|
+
for (const entry of sender.entries) {
|
|
68
|
+
const ref = entry.ref === undefined ? '' : ` · \`${entry.ref}\``;
|
|
69
|
+
blocks.push(`### ${entryLabel(entry)}${ref}`);
|
|
70
|
+
const body = entry.body.trim();
|
|
71
|
+
if (body !== '')
|
|
72
|
+
blocks.push(body);
|
|
73
|
+
}
|
|
74
|
+
return blocks.join('\n\n');
|
|
75
|
+
}).join('\n\n');
|
|
38
76
|
}
|
|
39
77
|
function inboxSummary(card) {
|
|
40
78
|
if (card.senders.length === 0)
|
|
@@ -74,7 +112,7 @@ const CORE_CARD_PRESENTATIONS = {
|
|
|
74
112
|
inbox: {
|
|
75
113
|
subject: 'inbox',
|
|
76
114
|
summary: inboxSummary,
|
|
77
|
-
expanded:
|
|
115
|
+
expanded: { disclose: 'render', render: inboxDisclosure },
|
|
78
116
|
groupFolded: 'senders',
|
|
79
117
|
},
|
|
80
118
|
revive: {
|
|
@@ -94,19 +132,19 @@ const CORE_CARD_PRESENTATIONS = {
|
|
|
94
132
|
'stop-guard': {
|
|
95
133
|
subject: 'stop guard',
|
|
96
134
|
summary: stopGuardSummary,
|
|
97
|
-
expanded:
|
|
135
|
+
expanded: PAYLOAD,
|
|
98
136
|
groupFolded: 'event',
|
|
99
137
|
},
|
|
100
138
|
recovery: {
|
|
101
139
|
subject: 'recovery',
|
|
102
140
|
summary: recoverySummary,
|
|
103
|
-
expanded:
|
|
141
|
+
expanded: PAYLOAD,
|
|
104
142
|
groupFolded: 'event',
|
|
105
143
|
},
|
|
106
144
|
'persona-transition': {
|
|
107
145
|
subject: 'persona',
|
|
108
146
|
summary: () => 'role updated',
|
|
109
|
-
expanded:
|
|
147
|
+
expanded: { disclose: 'render', render: (card) => unwrapWholeBody(card.body) },
|
|
110
148
|
groupFolded: 'event',
|
|
111
149
|
},
|
|
112
150
|
'review-approval': {
|
|
@@ -130,7 +168,7 @@ const CORE_CARD_PRESENTATIONS = {
|
|
|
130
168
|
'context-nudge': {
|
|
131
169
|
subject: 'context',
|
|
132
170
|
summary: () => 'context-window guidance',
|
|
133
|
-
expanded:
|
|
171
|
+
expanded: PAYLOAD,
|
|
134
172
|
groupFolded: 'omit',
|
|
135
173
|
},
|
|
136
174
|
bearings: {
|
|
@@ -7,11 +7,16 @@ export declare class ContextMessageComponent implements Component {
|
|
|
7
7
|
private expanded;
|
|
8
8
|
private readonly expandable;
|
|
9
9
|
private readonly labelTag;
|
|
10
|
+
private readonly summaryText;
|
|
10
11
|
private readonly collapsedHint;
|
|
11
12
|
private readonly expandedBody;
|
|
12
13
|
constructor(card: GeneratedCard, presentation: CardPresentation, expanded: boolean, dim: (s: string) => string, label: (s: string) => string);
|
|
13
14
|
/** Ctrl+O global tool-output toggle (ChatView.isExpandable duck-type). */
|
|
14
15
|
setExpanded(expanded: boolean): void;
|
|
16
|
+
/** Paint the [label] tag in the accent color and the rest dim. Truncate
|
|
17
|
+
* BEFORE painting so the ANSI wrappers never inflate the measured width — an
|
|
18
|
+
* over-wide line aborts the whole TUI render. */
|
|
19
|
+
private headerRow;
|
|
15
20
|
render(width: number): string[];
|
|
16
21
|
invalidate(): void;
|
|
17
22
|
}
|
|
@@ -17,39 +17,45 @@ export class ContextMessageComponent {
|
|
|
17
17
|
expanded;
|
|
18
18
|
expandable;
|
|
19
19
|
labelTag;
|
|
20
|
+
summaryText;
|
|
20
21
|
collapsedHint;
|
|
21
22
|
expandedBody;
|
|
22
23
|
constructor(card, presentation, expanded, dim, label) {
|
|
23
24
|
this.dim = dim;
|
|
24
25
|
this.label = label;
|
|
25
|
-
this.expandable = cardIsExpandable(presentation, card
|
|
26
|
+
this.expandable = cardIsExpandable(presentation, card);
|
|
26
27
|
this.expanded = this.expandable && expanded;
|
|
27
28
|
this.labelTag = `[${cardLabel(presentation.subject)}]`;
|
|
29
|
+
this.summaryText = ` ${presentation.summary(card)}`;
|
|
28
30
|
// A card with nothing to disclose must not advertise the toggle.
|
|
29
|
-
this.collapsedHint =
|
|
30
|
-
this.expandedBody = new UserMessageComponent(styleAttachMarkdownSource(cardDisclosure(presentation, card
|
|
31
|
+
this.collapsedHint = `${this.summaryText}${this.expandable ? ' — ctrl+o to expand' : ''}`;
|
|
32
|
+
this.expandedBody = new UserMessageComponent(styleAttachMarkdownSource(cardDisclosure(presentation, card)), attachMarkdownTheme());
|
|
31
33
|
}
|
|
32
34
|
/** Ctrl+O global tool-output toggle (ChatView.isExpandable duck-type). */
|
|
33
35
|
setExpanded(expanded) {
|
|
34
36
|
if (this.expandable)
|
|
35
37
|
this.expanded = expanded;
|
|
36
38
|
}
|
|
39
|
+
/** Paint the [label] tag in the accent color and the rest dim. Truncate
|
|
40
|
+
* BEFORE painting so the ANSI wrappers never inflate the measured width — an
|
|
41
|
+
* over-wide line aborts the whole TUI render. */
|
|
42
|
+
headerRow(w, hint) {
|
|
43
|
+
const labelW = visibleWidth(this.labelTag);
|
|
44
|
+
if (labelW >= w)
|
|
45
|
+
return this.label(truncateToWidth(this.labelTag, w, '…'));
|
|
46
|
+
return this.label(this.labelTag) + this.dim(truncateToWidth(hint, w - labelW, '…'));
|
|
47
|
+
}
|
|
37
48
|
render(width) {
|
|
38
49
|
const w = width > 0 ? width : 80;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const labelW = visibleWidth(this.labelTag);
|
|
45
|
-
if (labelW >= w)
|
|
46
|
-
return ['', this.label(truncateToWidth(this.labelTag, w, '…')), ''];
|
|
47
|
-
return ['', this.label(this.labelTag) + this.dim(truncateToWidth(this.collapsedHint, w - labelW, '…')), ''];
|
|
48
|
-
}
|
|
50
|
+
// The header stays in BOTH states: expanding replaces the card's body, not
|
|
51
|
+
// its identity, and consecutive disclosed cards are otherwise a run of
|
|
52
|
+
// unattributed bubbles.
|
|
53
|
+
if (!this.expanded)
|
|
54
|
+
return ['', this.headerRow(w, this.collapsedHint), ''];
|
|
49
55
|
const lines = this.expandedBody.render(w);
|
|
50
56
|
if (lines.at(-1) !== '')
|
|
51
57
|
lines.push('');
|
|
52
|
-
return lines;
|
|
58
|
+
return ['', this.headerRow(w, this.summaryText), ...lines];
|
|
53
59
|
}
|
|
54
60
|
invalidate() {
|
|
55
61
|
/* stateless — nothing to clear */
|
|
@@ -22,6 +22,9 @@ export interface AttachContainers {
|
|
|
22
22
|
readonly queued: Container;
|
|
23
23
|
/** Native pickers (/model etc.), inline directly under the editor. */
|
|
24
24
|
readonly pickerPanel: Container;
|
|
25
|
+
/** The inline ticket page (chrome/ticket-panel.ts), below the recap header
|
|
26
|
+
* — the expanded form of the strip's notification row. */
|
|
27
|
+
readonly ticketPanel: Container;
|
|
25
28
|
/** Extension widgets placed `belowEditor`. */
|
|
26
29
|
readonly belowWidgets: Container;
|
|
27
30
|
/** The status line. */
|
|
@@ -12,6 +12,10 @@ export declare function cropAroundCursor(lines: readonly string[], alloc: number
|
|
|
12
12
|
export interface ViewerFrameParts {
|
|
13
13
|
/** Rendered above the transcript; 0 rows when empty. */
|
|
14
14
|
header: Component;
|
|
15
|
+
/** The inline ticket page, directly beneath the header — the expanded form
|
|
16
|
+
* of the strip's notification row. It grows downward to whatever the page
|
|
17
|
+
* needs, squeezing the transcript to zero before the cockpit gives a row. */
|
|
18
|
+
ticket: Component;
|
|
15
19
|
viewport: TranscriptViewport;
|
|
16
20
|
/** The bottom slots, in VISUAL order, tagged with their allocation identity
|
|
17
21
|
* so the allocator can apply the §2.1 priority table. */
|
|
@@ -112,17 +112,21 @@ export class ViewerFrame {
|
|
|
112
112
|
let headerLines = this.parts.header.render(width).slice(0, HEADER_MAX_ROWS);
|
|
113
113
|
if (rows - bottomLines.length - headerLines.length < MIN_TRANSCRIPT_ROWS)
|
|
114
114
|
headerLines = [];
|
|
115
|
-
|
|
115
|
+
// The ticket page takes every row the header and cockpit left, before the
|
|
116
|
+
// transcript sees any — a tall page legitimately drives the viewport to 0.
|
|
117
|
+
const ticketAll = this.parts.ticket.render(width);
|
|
118
|
+
const ticketLines = ticketAll.slice(0, Math.max(0, rows - headerLines.length - bottomLines.length));
|
|
119
|
+
const viewportRows = Math.max(0, rows - headerLines.length - ticketLines.length - bottomLines.length);
|
|
116
120
|
this.viewportRows = viewportRows;
|
|
117
121
|
let viewportLines = this.parts.viewport.renderSlice(width, viewportRows);
|
|
118
|
-
let frame = [...headerLines, ...viewportLines, ...bottomLines];
|
|
122
|
+
let frame = [...headerLines, ...ticketLines, ...viewportLines, ...bottomLines];
|
|
119
123
|
// A misbehaving viewport is repaired in its own region only: transcript
|
|
120
124
|
// padding goes directly beneath the header, while excess drops from its top.
|
|
121
125
|
// The cockpit, including the editor's marker row, is never sliced here.
|
|
122
126
|
if (frame.length !== rows) {
|
|
123
127
|
reportInvariant(`frame rendered ${frame.length} rows; expected ${rows}`);
|
|
124
128
|
viewportLines = fitTranscript(viewportLines, viewportRows);
|
|
125
|
-
frame = [...headerLines, ...viewportLines, ...bottomLines];
|
|
129
|
+
frame = [...headerLines, ...ticketLines, ...viewportLines, ...bottomLines];
|
|
126
130
|
}
|
|
127
131
|
if (frame.length !== rows)
|
|
128
132
|
reportInvariant(`frame correction rendered ${frame.length} rows; expected ${rows}`);
|
|
@@ -152,6 +156,7 @@ export class ViewerFrame {
|
|
|
152
156
|
}
|
|
153
157
|
invalidate() {
|
|
154
158
|
this.parts.header.invalidate();
|
|
159
|
+
this.parts.ticket.invalidate();
|
|
155
160
|
for (const { component } of this.parts.bottom)
|
|
156
161
|
component.invalidate();
|
|
157
162
|
this.parts.viewport.invalidate();
|
|
@@ -14,6 +14,11 @@ export interface KeyHooks {
|
|
|
14
14
|
/** Transcript search's chance at a chunk; true when it consumed it. It owns
|
|
15
15
|
* typed characters while editing a query, so it runs ahead of the editor. */
|
|
16
16
|
searchHandleKey: (data: string) => boolean;
|
|
17
|
+
/** The inline ticket panel: while open it consumes EVERY chunk — pages
|
|
18
|
+
* carry text inputs — so it runs ahead of search, scroll, and the lists. */
|
|
19
|
+
panelHandleKey: (data: string) => boolean;
|
|
20
|
+
/** The inbox strip's chance at a chunk; true when it consumed it. */
|
|
21
|
+
stripHandleKey: (data: string) => boolean;
|
|
17
22
|
/** The roster's chance at a chunk; true when it consumed it. */
|
|
18
23
|
rosterHandleKey: (data: string) => boolean;
|
|
19
24
|
setNotice: (message: string) => void;
|
|
@@ -22,6 +27,8 @@ export interface KeyHooks {
|
|
|
22
27
|
toggleHelp: () => void;
|
|
23
28
|
/** Toggle the mounted inbox surface. */
|
|
24
29
|
toggleInbox: () => void;
|
|
30
|
+
/** Enter or leave the top-of-pane pending-request strip. */
|
|
31
|
+
toggleInboxStrip: () => void;
|
|
25
32
|
cycleModelLadder: (direction: 'forward' | 'backward') => void;
|
|
26
33
|
inspectLoadedCommand: () => void;
|
|
27
34
|
/** The file-review chord — open the transcript-file picker + review surface. */
|
|
@@ -33,6 +33,7 @@ export function installGlobalKeys(ctx, hooks) {
|
|
|
33
33
|
'crtr.attach.graph.toggle': hooks.toggleGraph,
|
|
34
34
|
'crtr.attach.help.toggle': hooks.toggleHelp,
|
|
35
35
|
'crtr.attach.inbox.toggle': hooks.toggleInbox,
|
|
36
|
+
'crtr.attach.inbox-strip': hooks.toggleInboxStrip,
|
|
36
37
|
// A plain broker frame, so this works locally and remotely alike; the broker gates observers on controller role.
|
|
37
38
|
'crtr.attach.model-ladder.next': () => hooks.cycleModelLadder('forward'),
|
|
38
39
|
'crtr.attach.model-ladder.previous': () => hooks.cycleModelLadder('backward'),
|
|
@@ -57,12 +58,18 @@ export function installGlobalKeys(ctx, hooks) {
|
|
|
57
58
|
}
|
|
58
59
|
if (hooks.hasExclusiveSurface())
|
|
59
60
|
return undefined;
|
|
61
|
+
if (hooks.panelHandleKey(data))
|
|
62
|
+
return { consume: true };
|
|
60
63
|
if (hooks.searchHandleKey(data))
|
|
61
64
|
return { consume: true };
|
|
62
65
|
// Let focused pickers and overlays retain their native page navigation.
|
|
63
66
|
if (editor.focused && dispatchAttachViewportBinding(hooks.bindings(), data, hooks.viewportActions()))
|
|
64
67
|
return { consume: true };
|
|
65
68
|
// Inline subagent scroll+swap: once the editor hands off at its ↓ boundary, ↑/↓ move over the manager/report roster and Enter dives into a node. An inactive roster never intercepts editor input.
|
|
69
|
+
// The inbox strip while it holds the highlight — ahead of the roster, whose
|
|
70
|
+
// rows the same arrows would otherwise move.
|
|
71
|
+
if (hooks.stripHandleKey(data))
|
|
72
|
+
return { consume: true };
|
|
66
73
|
if (hooks.rosterHandleKey(data))
|
|
67
74
|
return { consume: true };
|
|
68
75
|
return dispatchAttachBinding(hooks.bindings(), data, actions) ? { consume: true } : undefined;
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
// reader asking "where does this line get painted?" needs only this one.
|
|
7
7
|
//
|
|
8
8
|
// The render order is asserted in ONE place (`ViewerFrame`'s bottom list).
|
|
9
|
-
// Top to bottom: pinned recap header ·
|
|
10
|
-
// above-editor extension widgets · queued ·
|
|
11
|
-
// extension widgets · roster · search bar ·
|
|
9
|
+
// Top to bottom: pinned recap header · inline ticket page · virtualized chat ·
|
|
10
|
+
// cockpit padding · fixed activity · above-editor extension widgets · queued ·
|
|
11
|
+
// editor · picker panel · below-editor extension widgets · roster · search bar ·
|
|
12
|
+
// status bar. The frame owns the pane rectangle,
|
|
12
13
|
// so every region remains on screen and none can enter terminal scrollback.
|
|
13
14
|
import { Container, Spacer } from '@earendil-works/pi-tui';
|
|
14
15
|
import { getSelectListTheme } from '@earendil-works/pi-coding-agent';
|
|
@@ -25,6 +26,7 @@ export function createLayout(procTerminal, tui, chat, activity, pal, km, viewpor
|
|
|
25
26
|
reports: new Container(),
|
|
26
27
|
queued: new Container(),
|
|
27
28
|
pickerPanel: new Container(),
|
|
29
|
+
ticketPanel: new Container(),
|
|
28
30
|
belowWidgets: new Container(),
|
|
29
31
|
footer: new Container(),
|
|
30
32
|
};
|
|
@@ -34,6 +36,7 @@ export function createLayout(procTerminal, tui, chat, activity, pal, km, viewpor
|
|
|
34
36
|
const editor = new TitledEditor(tui, editorTheme, km, { paddingX: 1 });
|
|
35
37
|
const frame = new ViewerFrame(procTerminal, {
|
|
36
38
|
header: containers.header,
|
|
39
|
+
ticket: containers.ticketPanel,
|
|
37
40
|
viewport,
|
|
38
41
|
bottom: [
|
|
39
42
|
{ slot: 'cockpitPadding', component: new Spacer(1) },
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TitledEditor } from '../input/titled-editor.js';
|
|
2
|
+
export interface PaneFocus {
|
|
3
|
+
/** Recompute the editor's cursor style from pane focus + the holders. */
|
|
4
|
+
sync(): void;
|
|
5
|
+
/** Report real tmux-pane focus (DECSET 1004). Repaints the editor cursor. */
|
|
6
|
+
setPaneFocused(focused: boolean): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createPaneFocus(editor: TitledEditor, holders: ReadonlyArray<() => boolean>): PaneFocus;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// session/pane-focus.ts — the ONE owner of the editor's hollow-cursor state.
|
|
2
|
+
//
|
|
3
|
+
// The cursor is dimmed whenever the tmux pane is blurred OR any chrome list
|
|
4
|
+
// (the below-editor roster, the top-of-pane inbox strip) holds the highlight:
|
|
5
|
+
// while a list is scrolled the editor is "away", so it must read exactly like
|
|
6
|
+
// an unfocused pane. Both lists report their active state through `holders`;
|
|
7
|
+
// they call `sync()` whenever that state may have flipped, and the viewer
|
|
8
|
+
// feeds real tmux focus (DECSET 1004) in through `setPaneFocused`.
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
/** Seed pane focus from the pane's REAL active state, not an unconditional
|
|
11
|
+
* `true`. tmux only emits DECSET 1004 focus events on a TRANSITION, so a viewer
|
|
12
|
+
* spawned into a non-active pane (a `split -d` beside another node — the
|
|
13
|
+
* auto-viewer for a background child) starts unfocused and NEVER receives a
|
|
14
|
+
* FOCUS_OUT, leaving a solid cursor drawn until the user swaps in and back out.
|
|
15
|
+
* Query `#{pane_active}` for our own pane once at startup so an unfocused split
|
|
16
|
+
* renders hollow immediately; default true when not in tmux or the query fails. */
|
|
17
|
+
function initialPaneActive() {
|
|
18
|
+
const pane = process.env['TMUX_PANE'];
|
|
19
|
+
if (pane === undefined || pane === '')
|
|
20
|
+
return true;
|
|
21
|
+
try {
|
|
22
|
+
const out = execFileSync('tmux', ['display-message', '-p', '-t', pane, '#{pane_active}'], { encoding: 'utf8' }).trim();
|
|
23
|
+
return out !== '0';
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function createPaneFocus(editor, holders) {
|
|
30
|
+
let paneFocused = initialPaneActive();
|
|
31
|
+
const sync = () => {
|
|
32
|
+
editor.paneFocused = paneFocused && !holders.some((holds) => holds());
|
|
33
|
+
};
|
|
34
|
+
sync();
|
|
35
|
+
return {
|
|
36
|
+
sync,
|
|
37
|
+
setPaneFocused: (focused) => {
|
|
38
|
+
paneFocused = focused;
|
|
39
|
+
sync();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|