@linxiraos/pi-tui 1.2.1 → 1.3.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 +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
|
@@ -1,24 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* One-line text prompt an extension (hook) asks the user.
|
|
3
3
|
*/
|
|
4
|
-
import { Spacer, type TUI } from "../index";
|
|
4
|
+
import { type Component, Spacer, Text, type TUI } from "../index";
|
|
5
5
|
import { matchesAppInterrupt } from "../keybinding-matchers";
|
|
6
6
|
import { CountdownTimer } from "../chrome/countdown-timer";
|
|
7
7
|
import { formTheme } from "../chrome/form-theme";
|
|
8
8
|
import { OverlayPanel } from "../chrome/overlay-box";
|
|
9
9
|
import { Form, TextFormField } from "../components/form";
|
|
10
|
-
import { editorKey, interruptKey } from "../chrome/keybinding-hints";
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
10
|
+
import { boundKeys, editorKey, interruptKey } from "../chrome/keybinding-hints";
|
|
11
|
+
import { getKeybindings } from "../keybindings";
|
|
12
|
+
import { col, node, span } from "../native/describe";
|
|
13
|
+
import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
|
|
14
|
+
import { actionBar, actionButton } from "../native/overlay";
|
|
14
15
|
import { plainText } from "../native/spans";
|
|
15
16
|
|
|
17
|
+
/** Role prefix of the sheet's parts (`zeta.hook-input.head`, `.title`, `.timer`, `.detail`, `.field`). */
|
|
18
|
+
const PART = "zeta.hook-input";
|
|
19
|
+
|
|
16
20
|
export interface HookInputOptions {
|
|
17
21
|
tui?: TUI;
|
|
18
22
|
timeout?: number;
|
|
19
23
|
onTimeout?: () => void;
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
/** Terminal-only key legend under the field; a native terminal puts the keys in the buttons' tooltips. */
|
|
27
|
+
class KeyLegend implements Component {
|
|
28
|
+
readonly #text = new Text("", 0, 0);
|
|
29
|
+
|
|
30
|
+
constructor(private readonly legend: string) {
|
|
31
|
+
this.invalidate();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render(width: number): readonly string[] {
|
|
35
|
+
return this.#text.render(width);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
invalidate(): void {
|
|
39
|
+
this.#text.setText(formTheme.hint(this.legend));
|
|
40
|
+
this.#text.invalidate();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface HookInputNativeMemo {
|
|
45
|
+
countdown: NativeNode | undefined;
|
|
46
|
+
error: boolean;
|
|
47
|
+
node: NativeNode;
|
|
48
|
+
}
|
|
49
|
+
|
|
22
50
|
export class HookInputComponent extends OverlayPanel {
|
|
23
51
|
#field: TextFormField;
|
|
24
52
|
#form: Form;
|
|
@@ -26,11 +54,12 @@ export class HookInputComponent extends OverlayPanel {
|
|
|
26
54
|
#onCancelCallback: () => void;
|
|
27
55
|
#baseTitle: string;
|
|
28
56
|
#countdown: CountdownTimer | undefined;
|
|
29
|
-
#nativeMemo:
|
|
57
|
+
#nativeMemo: HookInputNativeMemo | undefined;
|
|
58
|
+
#nativeActions: NativeNode | undefined;
|
|
30
59
|
|
|
31
60
|
constructor(
|
|
32
61
|
title: string,
|
|
33
|
-
|
|
62
|
+
placeholder: string | undefined,
|
|
34
63
|
onSubmit: (value: string) => void,
|
|
35
64
|
onCancel: () => void,
|
|
36
65
|
opts?: HookInputOptions,
|
|
@@ -55,17 +84,19 @@ export class HookInputComponent extends OverlayPanel {
|
|
|
55
84
|
|
|
56
85
|
this.#field = new TextFormField({
|
|
57
86
|
theme: formTheme,
|
|
58
|
-
hint: `${editorKey("tui.input.submit")} submit ${interruptKey()} cancel`,
|
|
59
87
|
empty: "submit",
|
|
60
88
|
onSubmit: value => this.#onSubmitCallback(value),
|
|
61
89
|
onCancel: () => this.#onCancelCallback(),
|
|
62
90
|
});
|
|
91
|
+
// Native terminals draw it while the value is empty; the terminal row stays bare.
|
|
92
|
+
this.#field.input.placeholder = placeholder?.trim() || undefined;
|
|
63
93
|
this.#form = new Form({
|
|
64
94
|
fields: [this.#field],
|
|
65
95
|
onCancel: () => this.#onCancelCallback(),
|
|
66
96
|
isCancel: matchesAppInterrupt,
|
|
67
97
|
});
|
|
68
98
|
this.addChild(this.#form);
|
|
99
|
+
this.addChild(new KeyLegend(`${editorKey("tui.input.submit")} submit ${interruptKey()} cancel`));
|
|
69
100
|
this.addChild(new Spacer(1));
|
|
70
101
|
}
|
|
71
102
|
|
|
@@ -83,24 +114,79 @@ export class HookInputComponent extends OverlayPanel {
|
|
|
83
114
|
this.#form.pasteText(text);
|
|
84
115
|
}
|
|
85
116
|
|
|
117
|
+
/** Submit and Cancel run what Enter and the interrupt key run. */
|
|
118
|
+
handleNativeEvent(event: NativeUiEvent): void {
|
|
119
|
+
if (event.type !== "action") return;
|
|
120
|
+
if (event.act === "submit") {
|
|
121
|
+
this.#countdown?.reset();
|
|
122
|
+
this.#field.submit();
|
|
123
|
+
} else if (event.act === "cancel") {
|
|
124
|
+
this.#onCancelCallback();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
86
128
|
override dispose(): void {
|
|
87
129
|
this.#countdown?.dispose();
|
|
88
130
|
super.dispose();
|
|
89
131
|
}
|
|
90
132
|
|
|
91
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* A sheet in the composer's place, built like the extension editor's: the
|
|
135
|
+
* title's first line (the countdown at its end while a timeout runs) over
|
|
136
|
+
* any further lines as detail, the field written on the sheet (tinted, the
|
|
137
|
+
* reason under it, while a submit was refused), and Cancel · Submit, whose
|
|
138
|
+
* keys live in their tooltips.
|
|
139
|
+
*/
|
|
92
140
|
override describe(_cx: DescribeContext): NativeNode {
|
|
93
141
|
const countdown = this.#countdown?.describe();
|
|
142
|
+
const error = this.#field.hasError;
|
|
94
143
|
const memo = this.#nativeMemo;
|
|
95
|
-
if (memo && memo.countdown === countdown) return memo.node;
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
144
|
+
if (memo && memo.countdown === countdown && memo.error === error) return memo.node;
|
|
145
|
+
const [titleLine = "", ...detailLines] = plainText(this.#baseTitle).trim().split(/\r?\n/);
|
|
146
|
+
const titleRow: NativeChild[] = [
|
|
147
|
+
node(
|
|
148
|
+
"text",
|
|
149
|
+
{ spans: [span(titleLine.trim())], role: `${PART}.title`, wrap: "word", grow: 1 },
|
|
150
|
+
undefined,
|
|
151
|
+
"title",
|
|
152
|
+
),
|
|
153
|
+
];
|
|
154
|
+
if (countdown) {
|
|
155
|
+
titleRow.push(
|
|
156
|
+
node(
|
|
157
|
+
"row",
|
|
158
|
+
{ role: `${PART}.timer`, title: "Cancels when the time runs out; typing restarts it" },
|
|
159
|
+
[countdown],
|
|
160
|
+
"timer",
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const head: NativeChild[] = [node("row", { gap: "md", align: "baseline" }, titleRow, "line")];
|
|
165
|
+
const detail = detailLines.join("\n").trim();
|
|
166
|
+
if (detail) {
|
|
167
|
+
head.push(node("text", { spans: [span(detail)], role: `${PART}.detail`, wrap: "word" }, undefined, "detail"));
|
|
168
|
+
}
|
|
169
|
+
const root = col(
|
|
170
|
+
[
|
|
171
|
+
node("col", { role: `${PART}.head`, gap: "xs" }, head, "head"),
|
|
172
|
+
node("col", { role: `${PART}.field`, ...(error ? { tone: "error" as const } : {}) }, [this.#form], "field"),
|
|
173
|
+
this.#describeActions(),
|
|
174
|
+
],
|
|
175
|
+
{ role: this.nativeRole },
|
|
176
|
+
);
|
|
177
|
+
this.#nativeMemo = { countdown, error, node: root };
|
|
104
178
|
return root;
|
|
105
179
|
}
|
|
180
|
+
|
|
181
|
+
#describeActions(): NativeNode {
|
|
182
|
+
if (this.#nativeActions) return this.#nativeActions;
|
|
183
|
+
const submitKey = getKeybindings().getKeys("tui.input.submit")[0];
|
|
184
|
+
const cancelKey = boundKeys("app.interrupt", ["escape"])[0];
|
|
185
|
+
this.#nativeActions = actionBar([
|
|
186
|
+
null,
|
|
187
|
+
actionButton("Cancel", "cancel", cancelKey ? { keys: cancelKey } : {}),
|
|
188
|
+
actionButton("Submit", "submit", { tone: "accent", ...(submitKey ? { keys: submitKey } : {}) }),
|
|
189
|
+
]);
|
|
190
|
+
return this.#nativeActions;
|
|
191
|
+
}
|
|
106
192
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The jobs of an agent and its subagents as a tree, shared by the jobs sheet
|
|
3
|
+
* and the `/jobs` report. The viewing agent's own jobs sit at the top level;
|
|
4
|
+
* a subagent's jobs nest under the task job that runs it, or under a header
|
|
5
|
+
* naming the subagent when that job is no longer listed (a foreground task, or
|
|
6
|
+
* one evicted from the recent list). Siblings sort branchiest first: the more
|
|
7
|
+
* rows a subtree holds the higher it sits, so leaf jobs settle to the bottom of
|
|
8
|
+
* their level; ties keep running before settled, then oldest first.
|
|
9
|
+
*/
|
|
10
|
+
import { buildTreePrefix, getTreeBranch } from "../render/utils";
|
|
11
|
+
import type { Theme } from "../theme/theme";
|
|
12
|
+
|
|
13
|
+
/** What the tree reads off a job. */
|
|
14
|
+
export interface JobTreeJob {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly status: "running" | "completed" | "failed" | "cancelled";
|
|
17
|
+
readonly startTime: number;
|
|
18
|
+
/** Registry id of the agent that owns the job; absent or the root id: a top-level job. */
|
|
19
|
+
readonly ownerId?: string;
|
|
20
|
+
/** Registry id of the subagent a task job runs: the job heads that agent's subtree. */
|
|
21
|
+
readonly agentId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A subagent in the tree, by registry id: an owner of listed jobs, or an ancestor linking one to the root. */
|
|
25
|
+
export interface JobTreeAgent {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly parentId?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Where a row sits: its depth and the rails drawn left of it. */
|
|
31
|
+
export interface JobTreePlace {
|
|
32
|
+
/** 0 for the top level. */
|
|
33
|
+
readonly depth: number;
|
|
34
|
+
/** Per enclosing level below the top, whether that level's rail continues past this row. */
|
|
35
|
+
readonly rails: readonly boolean[];
|
|
36
|
+
/** Whether this row is the last of its siblings. */
|
|
37
|
+
readonly last: boolean;
|
|
38
|
+
/** Whether rows nest under this one. */
|
|
39
|
+
readonly parent: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One tree row: a job (a task job also heads its subagent), or a header for a subagent with no listed task job. */
|
|
43
|
+
export type JobTreeRow<J extends JobTreeJob> =
|
|
44
|
+
| (JobTreePlace & { readonly kind: "job"; readonly job: J; readonly agent?: JobTreeAgent })
|
|
45
|
+
| (JobTreePlace & { readonly kind: "agent"; readonly agent: JobTreeAgent });
|
|
46
|
+
|
|
47
|
+
interface TreeNode<J> {
|
|
48
|
+
job?: J;
|
|
49
|
+
agent?: JobTreeAgent;
|
|
50
|
+
readonly children: TreeNode<J>[];
|
|
51
|
+
/** Rows nested under this node. */
|
|
52
|
+
size: number;
|
|
53
|
+
running: boolean;
|
|
54
|
+
startTime: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `jobs` as tree rows in display order. `rootId` is the viewing agent: its
|
|
59
|
+
* jobs, ownerless jobs and jobs of owners outside `agents` sit at the top
|
|
60
|
+
* level, as do agents whose parent is the root or unknown.
|
|
61
|
+
*/
|
|
62
|
+
export function buildJobTree<J extends JobTreeJob>(
|
|
63
|
+
jobs: readonly J[],
|
|
64
|
+
agents: readonly JobTreeAgent[],
|
|
65
|
+
rootId: string | undefined,
|
|
66
|
+
): JobTreeRow<J>[] {
|
|
67
|
+
const top: TreeNode<J>[] = [];
|
|
68
|
+
const agentNodes = new Map<string, TreeNode<J>>();
|
|
69
|
+
for (const agent of agents) {
|
|
70
|
+
if (agent.id === rootId) continue;
|
|
71
|
+
agentNodes.set(agent.id, { agent, children: [], size: 0, running: false, startTime: Infinity });
|
|
72
|
+
}
|
|
73
|
+
// A task job heads its subagent; with several, the running then newest one.
|
|
74
|
+
for (const job of jobs) {
|
|
75
|
+
const node = job.agentId === undefined ? undefined : agentNodes.get(job.agentId);
|
|
76
|
+
if (!node) continue;
|
|
77
|
+
const held = node.job;
|
|
78
|
+
if (!held || (held.status !== "running" && (job.status === "running" || job.startTime > held.startTime))) {
|
|
79
|
+
node.job = job;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const node of agentNodes.values()) {
|
|
83
|
+
(parentNode(agentNodes, node.agent!)?.children ?? top).push(node);
|
|
84
|
+
}
|
|
85
|
+
for (const job of jobs) {
|
|
86
|
+
if (job.agentId !== undefined && agentNodes.get(job.agentId)?.job === job) continue;
|
|
87
|
+
const owner = job.ownerId === undefined || job.ownerId === rootId ? undefined : agentNodes.get(job.ownerId);
|
|
88
|
+
(owner?.children ?? top).push({ job, children: [], size: 0, running: false, startTime: Infinity });
|
|
89
|
+
}
|
|
90
|
+
settle(top);
|
|
91
|
+
return flatten(top);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** `agent`'s parent node, or undefined for the top level: the root, an unknown agent, or a link that would close a cycle. */
|
|
95
|
+
function parentNode<J>(agentNodes: ReadonlyMap<string, TreeNode<J>>, agent: JobTreeAgent): TreeNode<J> | undefined {
|
|
96
|
+
const parent = agent.parentId === undefined ? undefined : agentNodes.get(agent.parentId);
|
|
97
|
+
if (!parent) return undefined;
|
|
98
|
+
const seen = new Set<string>();
|
|
99
|
+
for (let cursor = agent.parentId; cursor !== undefined && !seen.has(cursor);) {
|
|
100
|
+
if (cursor === agent.id) return undefined;
|
|
101
|
+
seen.add(cursor);
|
|
102
|
+
cursor = agentNodes.get(cursor)?.agent?.parentId;
|
|
103
|
+
}
|
|
104
|
+
return parent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Size, liveness and start time of each subtree, then siblings branchiest first; drops subagents with nothing listed. */
|
|
108
|
+
function settle<J extends JobTreeJob>(top: TreeNode<J>[]): void {
|
|
109
|
+
// Post-order without recursion: lineage depth is not ours to bound.
|
|
110
|
+
const order: TreeNode<J>[] = [];
|
|
111
|
+
const stack = [...top];
|
|
112
|
+
while (stack.length > 0) {
|
|
113
|
+
const node = stack.pop()!;
|
|
114
|
+
order.push(node);
|
|
115
|
+
stack.push(...node.children);
|
|
116
|
+
}
|
|
117
|
+
const prune = (nodes: TreeNode<J>[]): void => {
|
|
118
|
+
const kept = nodes.filter(node => node.job !== undefined || node.children.length > 0);
|
|
119
|
+
nodes.splice(0, nodes.length, ...kept);
|
|
120
|
+
nodes.sort(compareNodes);
|
|
121
|
+
};
|
|
122
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
123
|
+
const node = order[i];
|
|
124
|
+
prune(node.children);
|
|
125
|
+
node.size = 0;
|
|
126
|
+
node.running = node.job?.status === "running";
|
|
127
|
+
node.startTime = node.job?.startTime ?? Infinity;
|
|
128
|
+
for (const child of node.children) {
|
|
129
|
+
node.size += 1 + child.size;
|
|
130
|
+
node.running ||= child.running;
|
|
131
|
+
node.startTime = Math.min(node.startTime, child.startTime);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
prune(top);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Branchiest first: the larger subtree, then running before settled, then the older. */
|
|
138
|
+
function compareNodes<J>(a: TreeNode<J>, b: TreeNode<J>): number {
|
|
139
|
+
return b.size - a.size || Number(!a.running) - Number(!b.running) || a.startTime - b.startTime;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Depth-first rows of `top`, iteratively so lineage depth stays unbounded. */
|
|
143
|
+
function flatten<J extends JobTreeJob>(top: readonly TreeNode<J>[]): JobTreeRow<J>[] {
|
|
144
|
+
const out: JobTreeRow<J>[] = [];
|
|
145
|
+
const stack: { node: TreeNode<J>; depth: number; rails: readonly boolean[]; last: boolean }[] = [];
|
|
146
|
+
const push = (level: readonly TreeNode<J>[], depth: number, rails: readonly boolean[]): void => {
|
|
147
|
+
for (let i = level.length - 1; i >= 0; i--) {
|
|
148
|
+
stack.push({ node: level[i], depth, rails, last: i === level.length - 1 });
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
push(top, 0, []);
|
|
152
|
+
while (stack.length > 0) {
|
|
153
|
+
const { node, depth, rails, last } = stack.pop()!;
|
|
154
|
+
const place = { depth, rails, last, parent: node.children.length > 0 };
|
|
155
|
+
if (node.job) out.push({ ...place, kind: "job", job: node.job, agent: node.agent });
|
|
156
|
+
else if (node.agent) out.push({ ...place, kind: "agent", agent: node.agent });
|
|
157
|
+
// The top level draws no rail, so its children's rails start empty.
|
|
158
|
+
push(node.children, depth + 1, depth === 0 ? [] : [...rails, !last]);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The connector drawn before a row's head: nothing at the top level, else rails then a branch. */
|
|
164
|
+
export function jobTreeGuide(row: JobTreePlace, theme: Theme): string {
|
|
165
|
+
if (row.depth === 0) return "";
|
|
166
|
+
return `${buildTreePrefix(row.rails, theme)}${getTreeBranch(row.last, theme)} `;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The prefix of lines under a row's head (its label, a wrapped command): the
|
|
171
|
+
* rails that continue past it, then the rail down to its children or blank,
|
|
172
|
+
* two columns wide so a top-level leaf's lines indent by two.
|
|
173
|
+
*/
|
|
174
|
+
export function jobTreeIndent(row: JobTreePlace, theme: Theme): string {
|
|
175
|
+
const rails = row.depth === 0 ? "" : buildTreePrefix([...row.rails, !row.last], theme);
|
|
176
|
+
return `${rails}${row.parent ? `${theme.tree.vertical} ` : " "}`;
|
|
177
|
+
}
|