@linxiraos/pi-tui 1.3.2 → 1.4.1
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/CHANGELOG.md +181 -181
- package/dist/types/apps/session-picker.d.ts +13 -1
- package/dist/types/autocomplete.d.ts +6 -0
- package/dist/types/components/sparkline.d.ts +34 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/native/describe.d.ts +7 -1
- package/dist/types/native/overlay.d.ts +2 -0
- package/dist/types/native/tone.d.ts +2 -0
- package/dist/types/overlays/agent-hub.d.ts +3 -3
- package/dist/types/overlays/approval-dialog.d.ts +15 -1
- package/dist/types/overlays/jobs-panel.d.ts +3 -0
- package/dist/types/overlays/model-browser.d.ts +1 -1
- package/dist/types/overlays/resume-picker.d.ts +293 -0
- package/dist/types/overlays/resume-tree.d.ts +353 -0
- package/dist/types/overlays/session-selector.d.ts +22 -4
- package/dist/types/render/output-block.d.ts +2 -0
- package/dist/types/render/tool-card.d.ts +2 -0
- package/dist/types/sixel.d.ts +28 -0
- package/dist/types/status-line/host.d.ts +3 -0
- package/dist/types/terminal-capabilities.d.ts +6 -1
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +3 -0
- package/dist/types/tools/eval.d.ts +9 -8
- package/dist/types/tools/hub.d.ts +3 -1
- package/dist/types/tools/irc.d.ts +11 -1
- package/dist/types/tools/job-widget.d.ts +226 -0
- package/dist/types/tools/json-tree.d.ts +2 -0
- package/dist/types/tools/line-ranges.d.ts +8 -1
- package/dist/types/tools/mcp.d.ts +23 -0
- package/dist/types/tools/output-meta.d.ts +1 -1
- package/dist/types/tools/read.d.ts +8 -2
- package/dist/types/tools/renderer.d.ts +8 -2
- package/dist/types/tools/result-card.d.ts +1 -1
- package/dist/types/tools/streaming-output.d.ts +1 -0
- package/dist/types/tools/task.d.ts +12 -0
- package/dist/types/tools/wait.d.ts +11 -0
- package/package.json +9 -9
- package/src/apps/session-picker.ts +20 -2
- package/src/autocomplete.d.ts +6 -0
- package/src/autocomplete.ts +21 -6
- package/src/chat/skill-message.ts +16 -17
- package/src/chat/tool-execution.ts +35 -0
- package/src/chrome/transcript-container.ts +85 -13
- package/src/components/editor.ts +17 -7
- package/src/components/image.ts +5 -4
- package/src/components/markdown.ts +11 -3
- package/src/components/sparkline.d.ts +34 -0
- package/src/components/sparkline.ts +89 -0
- package/src/index.d.ts +1 -0
- package/src/index.ts +1 -0
- package/src/native/backend.ts +32 -6
- package/src/native/describe.d.ts +7 -1
- package/src/native/describe.ts +18 -1
- package/src/native/overlay.d.ts +2 -0
- package/src/native/overlay.ts +3 -0
- package/src/native/tone.d.ts +2 -0
- package/src/native/tone.ts +22 -0
- package/src/overlays/agent-hub.ts +14 -10
- package/src/overlays/agent-transcript-viewer.ts +2 -2
- package/src/overlays/agents-hub.ts +2 -1
- package/src/overlays/approval-dialog.ts +59 -13
- package/src/overlays/hub-frame.ts +2 -1
- package/src/overlays/jobs-panel.ts +24 -2
- package/src/overlays/model-browser.ts +3 -0
- package/src/overlays/model-hub.ts +2 -1
- package/src/overlays/resume-picker.ts +706 -0
- package/src/overlays/resume-tree.ts +1901 -0
- package/src/overlays/session-selector.ts +579 -39
- package/src/prompt/attachment-chips.ts +1 -1
- package/src/render/output-block.d.ts +2 -0
- package/src/render/output-block.ts +25 -12
- package/src/render/tool-card.d.ts +2 -0
- package/src/render/tool-card.ts +6 -0
- package/src/sixel.d.ts +28 -0
- package/src/sixel.ts +185 -0
- package/src/status-line/component.ts +19 -2
- package/src/status-line/host.ts +3 -0
- package/src/terminal-capabilities.d.ts +6 -1
- package/src/terminal-capabilities.ts +54 -3
- package/src/theme/symbols.ts +5 -3
- package/src/tools/bash.d.ts +2 -0
- package/src/tools/bash.ts +25 -9
- package/src/tools/default-renderer.d.ts +3 -0
- package/src/tools/default-renderer.ts +54 -17
- package/src/tools/eval.d.ts +9 -8
- package/src/tools/eval.ts +32 -14
- package/src/tools/find.ts +4 -5
- package/src/tools/glob.ts +4 -6
- package/src/tools/grep.ts +2 -4
- package/src/tools/hub.ts +4 -1
- package/src/tools/irc.d.ts +11 -1
- package/src/tools/irc.ts +11 -1
- package/src/tools/job-widget.d.ts +226 -0
- package/src/tools/job-widget.ts +1036 -0
- package/src/tools/json-tree.d.ts +2 -0
- package/src/tools/json-tree.ts +8 -0
- package/src/tools/line-ranges.d.ts +8 -1
- package/src/tools/line-ranges.ts +23 -13
- package/src/tools/mcp.d.ts +23 -0
- package/src/tools/mcp.ts +44 -1
- package/src/tools/output-meta.d.ts +1 -1
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/proc-render.ts +1 -0
- package/src/tools/read.d.ts +8 -2
- package/src/tools/read.ts +64 -21
- package/src/tools/renderer.d.ts +8 -2
- package/src/tools/renderer.ts +8 -2
- package/src/tools/result-card.d.ts +1 -1
- package/src/tools/result-card.ts +1 -2
- package/src/tools/streaming-output.d.ts +1 -0
- package/src/tools/streaming-output.ts +11 -9
- package/src/tools/task.d.ts +12 -0
- package/src/tools/task.ts +25 -1
- package/src/tools/wait.d.ts +11 -0
- package/src/tools/wait.ts +296 -147
- package/src/tui.ts +45 -5
package/src/native/tone.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface BackgroundChrome {
|
|
|
17
17
|
}
|
|
18
18
|
/** Map an omp background token to card tone/role. */
|
|
19
19
|
export declare function backgroundChrome(bg: ThemeBg | undefined): BackgroundChrome;
|
|
20
|
+
/** The omp theme colour an ANSI renderer draws a TSP tone with (the inverse of {@link colorTone}). */
|
|
21
|
+
export declare function toneColor(tone: TspTone): ThemeColor;
|
|
20
22
|
/**
|
|
21
23
|
* Tone of an omp colour used as a severity or border colour; undefined for
|
|
22
24
|
* colours that carry no tone (callers pick their own fallback).
|
package/src/native/tone.ts
CHANGED
|
@@ -50,6 +50,28 @@ export function backgroundChrome(bg: ThemeBg | undefined): BackgroundChrome {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** The omp theme colour an ANSI renderer draws a TSP tone with (the inverse of {@link colorTone}). */
|
|
54
|
+
export function toneColor(tone: TspTone): ThemeColor {
|
|
55
|
+
switch (tone) {
|
|
56
|
+
case "neutral":
|
|
57
|
+
return "text";
|
|
58
|
+
case "accent":
|
|
59
|
+
case "info":
|
|
60
|
+
return "accent";
|
|
61
|
+
case "success":
|
|
62
|
+
return "success";
|
|
63
|
+
case "warning":
|
|
64
|
+
case "pending":
|
|
65
|
+
return "warning";
|
|
66
|
+
case "error":
|
|
67
|
+
return "error";
|
|
68
|
+
case "muted":
|
|
69
|
+
return "muted";
|
|
70
|
+
case "user":
|
|
71
|
+
return "customMessageLabel";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
/**
|
|
54
76
|
* Tone of an omp colour used as a severity or border colour; undefined for
|
|
55
77
|
* colours that carry no tone (callers pick their own fallback).
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* - Table view: every registered agent except Main (Main IS the ambient
|
|
6
6
|
* chat), live from the global AgentHubRegistry — status, unread irc count,
|
|
7
7
|
* current/last task, last activity. Navigate with keys, wheel, hover, and
|
|
8
|
-
* click; `r` revives a parked agent, `x` aborts + releases one.
|
|
8
|
+
* click; `r` revives a parked or killed agent, `x` aborts + releases one.
|
|
9
9
|
* - Chat view: per-agent transcript (incremental session-file tail, absorbed
|
|
10
10
|
* from the old session observer overlay) plus an input line. Submitting
|
|
11
|
-
* revives a parked agent, then prompts/steers it; the message
|
|
12
|
-
* agent's persisted history via the normal prompt path.
|
|
11
|
+
* revives a parked or killed agent, then prompts/steers it; the message
|
|
12
|
+
* lands in the agent's persisted history via the normal prompt path.
|
|
13
13
|
*
|
|
14
14
|
* Replaces the old SessionObserverOverlayComponent (ctrl+s observer).
|
|
15
15
|
*/
|
|
@@ -894,7 +894,11 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
894
894
|
id: "revive",
|
|
895
895
|
label: "Revive",
|
|
896
896
|
keys: ["r"],
|
|
897
|
-
disabled:
|
|
897
|
+
disabled:
|
|
898
|
+
readOnly ??
|
|
899
|
+
(selected?.status === "parked" || selected?.status === "aborted"
|
|
900
|
+
? undefined
|
|
901
|
+
: "Only parked or killed agents can be revived"),
|
|
898
902
|
},
|
|
899
903
|
{ id: "kill", label: "Kill", keys: ["x"], danger: true, disabled: readOnly ?? (selected ? undefined : true) },
|
|
900
904
|
{ id: "view", label: "By parent", keys: ["t"], on: this.#viewMode === "tree" },
|
|
@@ -2498,15 +2502,15 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
2498
2502
|
#activateAgent(ref: TRecord): void {
|
|
2499
2503
|
this.#notice = undefined;
|
|
2500
2504
|
const focusAgent = this.#focusAgent;
|
|
2501
|
-
//
|
|
2502
|
-
//
|
|
2503
|
-
if (ref.kind === "advisor" ||
|
|
2505
|
+
// Advisor refs are read-only transcripts with no revivable session; open
|
|
2506
|
+
// the in-hub viewer instead of failing ensureLive.
|
|
2507
|
+
if (ref.kind === "advisor" || this.#remote || !focusAgent) {
|
|
2504
2508
|
this.openChat(ref.id);
|
|
2505
2509
|
return;
|
|
2506
2510
|
}
|
|
2507
2511
|
void (async () => {
|
|
2508
2512
|
try {
|
|
2509
|
-
await focusAgent(ref.id); // ensureLive inside revives parked
|
|
2513
|
+
await focusAgent(ref.id); // ensureLive inside revives parked and killed agents
|
|
2510
2514
|
this.#onDone();
|
|
2511
2515
|
} catch (error) {
|
|
2512
2516
|
this.#notice = error instanceof Error ? error.message : String(error);
|
|
@@ -2523,8 +2527,8 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
2523
2527
|
this.#requestRender();
|
|
2524
2528
|
return;
|
|
2525
2529
|
}
|
|
2526
|
-
if (ref.status !== "parked") {
|
|
2527
|
-
this.#notice = `Agent "${ref.id}" is ${ref.status} — only parked agents can be revived.`;
|
|
2530
|
+
if (ref.status !== "parked" && ref.status !== "aborted") {
|
|
2531
|
+
this.#notice = `Agent "${ref.id}" is ${ref.status} — only parked or killed agents can be revived.`;
|
|
2528
2532
|
this.#requestRender();
|
|
2529
2533
|
return;
|
|
2530
2534
|
}
|
|
@@ -235,10 +235,10 @@ export class AgentTranscriptViewer implements Component {
|
|
|
235
235
|
this.#pollTimer.unref?.();
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
/** Advisor
|
|
238
|
+
/** Advisor transcripts are read-only; every other agent revives on send. */
|
|
239
239
|
get #sendable(): boolean {
|
|
240
240
|
const ref = this.#deps.registry.get(this.#deps.agentId);
|
|
241
|
-
if (!ref || ref.kind === "advisor"
|
|
241
|
+
if (!ref || ref.kind === "advisor") return false;
|
|
242
242
|
return Boolean(this.#deps.remote || this.#deps.lifecycle);
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -1583,7 +1583,8 @@ export class AgentsHubComponent implements Component {
|
|
|
1583
1583
|
} else {
|
|
1584
1584
|
children.push(...this.#describeList());
|
|
1585
1585
|
}
|
|
1586
|
-
|
|
1586
|
+
// Zero basis: the body takes the space left of the sidebar instead of sizing to its widest line.
|
|
1587
|
+
return node("col", { gap: "sm", grow: 1, basis: 0, min: { w: 0 } }, children, "body");
|
|
1587
1588
|
}
|
|
1588
1589
|
|
|
1589
1590
|
#statusSpans(): TspSpan[] {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool-approval prompt in the composer's place, presented like the ask
|
|
3
3
|
* dialog: the approval title and its details over a list of choices —
|
|
4
|
-
*
|
|
4
|
+
* optional grants (led by a toggle that keeps them across sessions), Approve,
|
|
5
|
+
* an optional "approve for this session", and Deny — and an action
|
|
5
6
|
* bar. The arrows move between the choices and Enter takes the highlighted
|
|
6
7
|
* one; `app.approval.approve` (Ctrl+Enter by default) approves this call
|
|
7
8
|
* outright and Esc denies. Enter and the approve chord only arm
|
|
@@ -45,7 +46,8 @@ export const APPROVE_ARM_MS = 500;
|
|
|
45
46
|
/** The Deny row while a composer draft holds Enter: sending it denies the call and steers the turn. */
|
|
46
47
|
const DENY_AND_SEND_LABEL = "Deny and send message";
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
/** `grant<N>` takes the Nth of {@link ApprovalDialogOptions.grants}; `remember` toggles their lifetime. */
|
|
50
|
+
type ApprovalAnswer = "once" | "session" | "deny" | "remember" | `grant${number}`;
|
|
49
51
|
|
|
50
52
|
interface ApprovalChoice {
|
|
51
53
|
readonly label: string;
|
|
@@ -57,6 +59,14 @@ interface ApprovalDialogCallbacks {
|
|
|
57
59
|
onDeny(): void;
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/** A choice that approves the call and remembers more than it: a folder, or the exact target. */
|
|
63
|
+
export interface ApprovalDialogGrant {
|
|
64
|
+
/** Label while the grant is remembered for this session. */
|
|
65
|
+
readonly label: string;
|
|
66
|
+
/** Label once the remember toggle keeps it across sessions. */
|
|
67
|
+
readonly alwaysLabel: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
/** The composer mounted beneath the dialog: it receives every key the dialog does not own. */
|
|
61
71
|
export interface ApprovalDialogDraft {
|
|
62
72
|
/** True while the composer holds a draft — text, attachments, or a paste still landing: keys then edit and send it. */
|
|
@@ -80,6 +90,12 @@ interface ApprovalDialogOptions {
|
|
|
80
90
|
draft?: ApprovalDialogDraft;
|
|
81
91
|
/** Offer to approve this and later like calls for the rest of the session, as the row between Approve and Deny. */
|
|
82
92
|
session?: { label: string; onApprove(): void };
|
|
93
|
+
/**
|
|
94
|
+
* Grants to offer above Approve, led by a toggle that keeps them across sessions; Approve then
|
|
95
|
+
* reads "Approve Once". `onGrant` gets the chosen grant and whether the toggle was on.
|
|
96
|
+
*/
|
|
97
|
+
grants?: readonly ApprovalDialogGrant[];
|
|
98
|
+
onGrant?(grant: ApprovalDialogGrant, always: boolean): void;
|
|
83
99
|
}
|
|
84
100
|
|
|
85
101
|
/** A prompt's detail rows: every line after the title, trailing blanks dropped. */
|
|
@@ -100,9 +116,11 @@ export class ApprovalDialogComponent implements Component {
|
|
|
100
116
|
readonly #footer = new PanelRows();
|
|
101
117
|
#scrollOffset = 0;
|
|
102
118
|
#bodyRows = MAX_DETAIL_ROWS;
|
|
103
|
-
/** In list order
|
|
104
|
-
|
|
119
|
+
/** In list order: the remember toggle and grants (when offered), Approve (highlighted on mount), the session row, Deny last. */
|
|
120
|
+
#choices: readonly ApprovalChoice[];
|
|
105
121
|
#choice = 0;
|
|
122
|
+
/** The remember toggle: grants are kept across sessions while it is on. */
|
|
123
|
+
#rememberAlways = false;
|
|
106
124
|
#closed = false;
|
|
107
125
|
#native: NativeNode | undefined;
|
|
108
126
|
/** Whether a draft held the keys when {@link #native} was described: it moves the highlight. */
|
|
@@ -122,11 +140,8 @@ export class ApprovalDialogComponent implements Component {
|
|
|
122
140
|
this.#summary = promptDetails(lines);
|
|
123
141
|
this.#full =
|
|
124
142
|
options.fullPrompt === undefined ? undefined : promptDetails(sanitizeDisplayLines(options.fullPrompt));
|
|
125
|
-
this.#choices =
|
|
126
|
-
|
|
127
|
-
...(options.session ? [{ label: options.session.label, answer: "session" as const }] : []),
|
|
128
|
-
{ label: "Deny", answer: "deny" },
|
|
129
|
-
];
|
|
143
|
+
this.#choices = this.#buildChoices();
|
|
144
|
+
this.#choice = this.#choices.findIndex(choice => choice.answer === "once");
|
|
130
145
|
this.#panel = new OverlayPanel(title, "zeta.overlay.approval");
|
|
131
146
|
this.#footer.setHeight(1);
|
|
132
147
|
this.#panel.addChild(this.#body);
|
|
@@ -149,6 +164,33 @@ export class ApprovalDialogComponent implements Component {
|
|
|
149
164
|
}
|
|
150
165
|
}
|
|
151
166
|
|
|
167
|
+
#buildChoices(): ApprovalChoice[] {
|
|
168
|
+
const grants = this.options.grants ?? [];
|
|
169
|
+
const glyph = this.#rememberAlways ? theme.checkbox.checked : theme.checkbox.unchecked;
|
|
170
|
+
return [
|
|
171
|
+
...(grants.length > 0 ? [{ label: `${glyph} Remember across sessions`, answer: "remember" as const }] : []),
|
|
172
|
+
...grants.map((grant, index) => ({
|
|
173
|
+
label: this.#rememberAlways ? grant.alwaysLabel : grant.label,
|
|
174
|
+
answer: `grant${index}` as const,
|
|
175
|
+
})),
|
|
176
|
+
{ label: grants.length > 0 ? "Approve Once" : "Approve", answer: "once" },
|
|
177
|
+
...(this.options.session ? [{ label: this.options.session.label, answer: "session" as const }] : []),
|
|
178
|
+
{ label: "Deny", answer: "deny" },
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Run a choice: the remember toggle flips the grants' lifetime in place, every other choice answers. */
|
|
183
|
+
#take(answer: ApprovalAnswer): void {
|
|
184
|
+
if (answer !== "remember") {
|
|
185
|
+
this.#finish(answer);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.#rememberAlways = !this.#rememberAlways;
|
|
189
|
+
this.#choices = this.#buildChoices();
|
|
190
|
+
this.#native = undefined;
|
|
191
|
+
this.options.tui?.requestRender();
|
|
192
|
+
}
|
|
193
|
+
|
|
152
194
|
invalidate(): void {
|
|
153
195
|
this.#native = undefined;
|
|
154
196
|
this.#panel.invalidate();
|
|
@@ -191,7 +233,7 @@ export class ApprovalDialogComponent implements Component {
|
|
|
191
233
|
else if (matchesSelectPageUp(keyData)) this.#scroll(-page);
|
|
192
234
|
else if (matchesSelectPageDown(keyData)) this.#scroll(page);
|
|
193
235
|
else if (getKeybindings().matches(keyData, "tui.select.confirm")) {
|
|
194
|
-
if (performance.now() >= this.#armedAt) this.#
|
|
236
|
+
if (performance.now() >= this.#armedAt) this.#take(this.#choices[this.#choice]!.answer);
|
|
195
237
|
}
|
|
196
238
|
// Enter never reaches an empty composer: it would submit nothing, or interrupt the run.
|
|
197
239
|
else if (draft && !getKeybindings().matches(keyData, "tui.input.submit")) this.#editDraft(draft, keyData);
|
|
@@ -295,7 +337,7 @@ export class ApprovalDialogComponent implements Component {
|
|
|
295
337
|
tone: "error",
|
|
296
338
|
title: `Deny this call${cancelKey ? ` ${formatTooltipKey(cancelKey)}` : ""}`,
|
|
297
339
|
});
|
|
298
|
-
const approve = actionButton("Approve", "approve", {
|
|
340
|
+
const approve = actionButton(this.options.grants?.length ? "Approve Once" : "Approve", "approve", {
|
|
299
341
|
...(approveKey ? { keys: approveKey } : {}),
|
|
300
342
|
tone: "accent",
|
|
301
343
|
title: `Approve this call${approveKey ? ` ${formatTooltipKey(approveKey)}` : ""}`,
|
|
@@ -350,7 +392,7 @@ export class ApprovalDialogComponent implements Component {
|
|
|
350
392
|
}
|
|
351
393
|
if ((event.type !== "select" && event.type !== "activate") || leafKey(event.key) !== "choices") return;
|
|
352
394
|
const choice = this.#choices.find(candidate => candidate.answer === event.item);
|
|
353
|
-
if (choice) this.#
|
|
395
|
+
if (choice) this.#take(choice.answer);
|
|
354
396
|
}
|
|
355
397
|
|
|
356
398
|
/** Detail rows at `width`: one clipped row per detail, or wrapped rows once expanded. */
|
|
@@ -384,6 +426,10 @@ export class ApprovalDialogComponent implements Component {
|
|
|
384
426
|
this.#countdown?.dispose();
|
|
385
427
|
if (answer === "once") this.callbacks.onApprove();
|
|
386
428
|
else if (answer === "session") this.options.session?.onApprove();
|
|
387
|
-
else
|
|
429
|
+
else if (answer.startsWith("grant")) {
|
|
430
|
+
const grant = this.options.grants?.[Number(answer.slice("grant".length))];
|
|
431
|
+
if (grant) this.options.onGrant?.(grant, this.#rememberAlways);
|
|
432
|
+
else this.callbacks.onDeny();
|
|
433
|
+
} else this.callbacks.onDeny();
|
|
388
434
|
}
|
|
389
435
|
}
|
|
@@ -96,7 +96,8 @@ export function describeHubSidebar<TEntry extends SidebarEntry<string>>(
|
|
|
96
96
|
),
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
// The sidebar keeps its width: a body line wider than the pane must not squeeze it.
|
|
100
|
+
return node("list", { selected: selectedId, shrink: 0 }, items, key);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/**
|
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
* (cwd, live pids, exit code, artifact) and live output.
|
|
7
7
|
*/
|
|
8
8
|
import type { TspSpan, TspTone } from "@linxiraos/pi-wire";
|
|
9
|
-
import { formatDuration } from "@linxiraos/pi-utils";
|
|
9
|
+
import { formatDuration, sanitizeText } from "@linxiraos/pi-utils";
|
|
10
10
|
import type { Component } from "../tui";
|
|
11
11
|
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
|
|
12
12
|
import { matchesKey } from "../keys";
|
|
13
|
-
import { truncateToWidth } from "../utils";
|
|
13
|
+
import { replaceTabs, truncateToWidth, wrapTextWithAnsi } from "../utils";
|
|
14
14
|
import { collapseCommand } from "../apps/ps-data";
|
|
15
|
+
import { PREVIEW_LIMITS, shortenEmbeddedPaths } from "../render/render-utils";
|
|
15
16
|
import { ansi, code, col, compact, item, node, row, span, stableKey, text } from "../native/describe";
|
|
16
17
|
import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
|
|
17
18
|
import { actionBar, actionButton, selectList } from "../native/overlay";
|
|
18
19
|
import { theme } from "../theme/theme";
|
|
19
20
|
import { buildJobTree, type JobTreeAgent, type JobTreeRow, jobTreeGuide } from "./job-tree";
|
|
21
|
+
import { describeJobWidget, type JobWidgetView, renderJobWidgetLines } from "../tools/job-widget";
|
|
20
22
|
|
|
21
23
|
/** One async job as the session snapshots it. */
|
|
22
24
|
export interface JobsPanelJob {
|
|
@@ -54,6 +56,8 @@ export interface JobsPanelDetail {
|
|
|
54
56
|
readonly output?: string;
|
|
55
57
|
/** Artifact holding the full output when `output` is cut. */
|
|
56
58
|
readonly artifactId?: string;
|
|
59
|
+
/** The job's live widget (frozen once it settles), drawn above the output. */
|
|
60
|
+
readonly widget?: JobWidgetView;
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
/** The session behind a {@link JobsSheet}. */
|
|
@@ -149,6 +153,7 @@ export class JobsSheet implements Component {
|
|
|
149
153
|
detail?.pids.join(" "),
|
|
150
154
|
detail?.exitCode,
|
|
151
155
|
detail?.artifactId,
|
|
156
|
+
detail?.widget ? `${detail.widget.updatedAt}:${detail.widget.state}` : "",
|
|
152
157
|
jobs.some(job => job.status === "running") ? Math.floor(now / 1000) : "",
|
|
153
158
|
].join("|");
|
|
154
159
|
const output = detail?.output;
|
|
@@ -198,6 +203,17 @@ export class JobsSheet implements Component {
|
|
|
198
203
|
}
|
|
199
204
|
const detail = selected && this.#source.inspect(selected.id);
|
|
200
205
|
if (detail) {
|
|
206
|
+
// Rows show the cut label; the selected job's command wraps here,
|
|
207
|
+
// bounded so a long script never pushes the facts and output off the sheet.
|
|
208
|
+
if (detail.command) {
|
|
209
|
+
const commandRows = wrapTextWithAnsi(
|
|
210
|
+
shortenEmbeddedPaths(replaceTabs(sanitizeText(detail.command))),
|
|
211
|
+
Math.max(1, width - 3),
|
|
212
|
+
);
|
|
213
|
+
for (const line of commandRows.slice(0, PREVIEW_LIMITS.EXPANDED_LINES)) lines.push(` ${line}`);
|
|
214
|
+
const hidden = commandRows.length - PREVIEW_LIMITS.EXPANDED_LINES;
|
|
215
|
+
if (hidden > 0) lines.push(truncateToWidth(` … ${hidden} more command lines`, width));
|
|
216
|
+
}
|
|
201
217
|
const facts = compact([
|
|
202
218
|
detail.cwd && `cwd ${detail.cwd}`,
|
|
203
219
|
detail.pids.length > 0 && `pid ${detail.pids.join(", ")}`,
|
|
@@ -205,6 +221,11 @@ export class JobsSheet implements Component {
|
|
|
205
221
|
detail.artifactId && `artifact://${detail.artifactId}`,
|
|
206
222
|
]);
|
|
207
223
|
if (facts.length > 0) lines.push(truncateToWidth(` ${facts.join(" · ")}`, width));
|
|
224
|
+
if (detail.widget) {
|
|
225
|
+
for (const line of renderJobWidgetLines(detail.widget, Math.max(0, width - 3), theme)) {
|
|
226
|
+
lines.push(` ${line}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
208
229
|
for (const line of this.#tailLines(selected.id, detail.output ?? "")) {
|
|
209
230
|
if (line) lines.push(truncateToWidth(` ${line}`, width));
|
|
210
231
|
}
|
|
@@ -413,6 +434,7 @@ function describeDetail(
|
|
|
413
434
|
? code(command, { lang: "bash", wrap: true, preview: { lines: 4 }, role: "zeta.jobs.command" })
|
|
414
435
|
: text(job.label, { role: "zeta.jobs.title", wrap: "word" }),
|
|
415
436
|
facts.length > 0 && node("kv", { items: facts, role: "zeta.jobs.facts" }),
|
|
437
|
+
detail?.widget && node("col", { role: "zeta.jobs.widget" }, [describeJobWidget(detail.widget)], "widget"),
|
|
416
438
|
node(
|
|
417
439
|
"col",
|
|
418
440
|
{ role: "zeta.jobs.out", gap: "sm" },
|
|
@@ -63,6 +63,7 @@ export type ModelRole =
|
|
|
63
63
|
| "memory"
|
|
64
64
|
| "task"
|
|
65
65
|
| "advisor"
|
|
66
|
+
| "widget"
|
|
66
67
|
| "image"
|
|
67
68
|
| "web"
|
|
68
69
|
| "speech"
|
|
@@ -79,6 +80,7 @@ export const MODEL_ROLE_IDS: ModelRole[] = [
|
|
|
79
80
|
"memory",
|
|
80
81
|
"task",
|
|
81
82
|
"advisor",
|
|
83
|
+
"widget",
|
|
82
84
|
"image",
|
|
83
85
|
"web",
|
|
84
86
|
"speech",
|
|
@@ -96,6 +98,7 @@ export const CHAT_MODEL_ROLE_IDS: ModelRole[] = [
|
|
|
96
98
|
"memory",
|
|
97
99
|
"task",
|
|
98
100
|
"advisor",
|
|
101
|
+
"widget",
|
|
99
102
|
];
|
|
100
103
|
export const KIND_ROLE_IDS: ModelRole[] = ["image", "web", "speech", "dictation", "judge"];
|
|
101
104
|
|
|
@@ -3046,7 +3046,8 @@ export class ModelHubComponent implements Component {
|
|
|
3046
3046
|
this.#browser,
|
|
3047
3047
|
);
|
|
3048
3048
|
}
|
|
3049
|
-
|
|
3049
|
+
// Zero basis: the body takes the space left of the sidebar instead of sizing to its widest line.
|
|
3050
|
+
return node("col", { gap: "sm", grow: 1, basis: 0, min: { w: 0 } }, children, "body");
|
|
3050
3051
|
}
|
|
3051
3052
|
|
|
3052
3053
|
#describeStatus(): NativeNode {
|