@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,34 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The async background jobs of a session as {@link JobsSheet}: the dismissable
|
|
3
|
-
* sheet the native jobs pill and `/jobs` open.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* pids, exit code, output
|
|
3
|
+
* sheet the native jobs pill and `/jobs` open. A master–detail split lists
|
|
4
|
+
* every job of the session's agent and its subagents as a tree (see
|
|
5
|
+
* {@link buildJobTree}) beside the selected one's status, command, facts
|
|
6
|
+
* (cwd, live pids, exit code, artifact) and live output.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { TspSpan, TspTone } from "@linxiraos/pi-wire";
|
|
9
9
|
import { formatDuration } 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
13
|
import { truncateToWidth } from "../utils";
|
|
14
14
|
import { collapseCommand } from "../apps/ps-data";
|
|
15
|
-
import {
|
|
16
|
-
ansi,
|
|
17
|
-
card,
|
|
18
|
-
code,
|
|
19
|
-
col,
|
|
20
|
-
compact,
|
|
21
|
-
elapsed,
|
|
22
|
-
item,
|
|
23
|
-
kv,
|
|
24
|
-
node,
|
|
25
|
-
row,
|
|
26
|
-
span,
|
|
27
|
-
stableKey,
|
|
28
|
-
text,
|
|
29
|
-
} from "../native/describe";
|
|
15
|
+
import { ansi, code, col, compact, item, node, row, span, stableKey, text } from "../native/describe";
|
|
30
16
|
import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
|
|
31
17
|
import { actionBar, actionButton, selectList } from "../native/overlay";
|
|
18
|
+
import { theme } from "../theme/theme";
|
|
19
|
+
import { buildJobTree, type JobTreeAgent, type JobTreeRow, jobTreeGuide } from "./job-tree";
|
|
32
20
|
|
|
33
21
|
/** One async job as the session snapshots it. */
|
|
34
22
|
export interface JobsPanelJob {
|
|
@@ -38,14 +26,20 @@ export interface JobsPanelJob {
|
|
|
38
26
|
readonly label: string;
|
|
39
27
|
readonly startTime: number;
|
|
40
28
|
readonly endTime?: number;
|
|
29
|
+
/** Registry id of the agent that owns the job. */
|
|
30
|
+
readonly ownerId?: string;
|
|
41
31
|
/** Registry id of the subagent a task job runs. */
|
|
42
32
|
readonly agentId?: string;
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
/** Running and recently settled jobs. */
|
|
35
|
+
/** Running and recently settled jobs of an agent and its subagents. */
|
|
46
36
|
export interface JobsPanelSnapshot {
|
|
47
37
|
readonly running: readonly JobsPanelJob[];
|
|
48
38
|
readonly recent: readonly JobsPanelJob[];
|
|
39
|
+
/** The agent the sheet is for: its jobs sit at the top level, and only they can be cancelled. */
|
|
40
|
+
readonly rootId?: string;
|
|
41
|
+
/** Subagents owning listed jobs, with the ancestors linking them to the root. */
|
|
42
|
+
readonly agents?: readonly JobTreeAgent[];
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
/** What the sheet shows about the selected job beyond its row. */
|
|
@@ -68,37 +62,57 @@ export interface JobsSheetSource {
|
|
|
68
62
|
load(): JobsPanelSnapshot;
|
|
69
63
|
/** The selected job's detail; runs once per frame, undefined once the job is gone. */
|
|
70
64
|
inspect(id: string): JobsPanelDetail | undefined;
|
|
71
|
-
/**
|
|
72
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Cancel a running job. Omitted by a read-only sheet (a focused subagent
|
|
67
|
+
* view, which only watches that agent): it lists the jobs and offers no way
|
|
68
|
+
* to stop them.
|
|
69
|
+
*/
|
|
70
|
+
cancel?(id: string): void;
|
|
73
71
|
/** Dismiss the sheet. */
|
|
74
72
|
close(): void;
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
/** A job's status as the sheet names it. */
|
|
76
|
+
const STATUS_LABEL: Record<JobsPanelJob["status"], string> = {
|
|
77
|
+
running: "Running",
|
|
78
|
+
completed: "Done",
|
|
79
|
+
failed: "Failed",
|
|
80
|
+
cancelled: "Cancelled",
|
|
82
81
|
};
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
/** A job's status color: the list row's icon and the detail's status pill. */
|
|
84
|
+
const STATUS_TONE: Record<JobsPanelJob["status"], TspTone> = {
|
|
85
|
+
running: "pending",
|
|
86
|
+
completed: "success",
|
|
87
|
+
failed: "error",
|
|
88
|
+
cancelled: "muted",
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
/** The status pill's lead glyph; a running job's pulses. */
|
|
92
|
+
const STATUS_GLYPH: Record<JobsPanelJob["status"], TspSpan> = {
|
|
93
|
+
running: { t: "●", fx: "pulse" },
|
|
94
|
+
completed: { t: "✓" },
|
|
95
|
+
failed: { t: "✕" },
|
|
96
|
+
cancelled: { t: "⊘" },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** The list row's icon per job type (Tern icon names); other types read as a generic job. */
|
|
100
|
+
const TYPE_ICON: Record<string, string> = { bash: "bash", task: "agent" };
|
|
101
|
+
|
|
91
102
|
/** Output tail rows the terminal fallback shows under the selected job. */
|
|
92
103
|
const FALLBACK_TAIL_LINES = 8;
|
|
93
104
|
|
|
94
105
|
/**
|
|
95
106
|
* The jobs pill's sheet: a centred `lg` glass sheet titled Background jobs.
|
|
96
|
-
* A
|
|
97
|
-
* job's detail
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
107
|
+
* A master–detail split (it stacks in a narrow pane): the selectable job tree
|
|
108
|
+
* beside the selected job's detail — a status pill with live elapsed and
|
|
109
|
+
* Cancel job, its command or task, facts (agent, cwd, live pids, exit code,
|
|
110
|
+
* artifact) and a tail-following output pane. With no jobs it shows an empty
|
|
111
|
+
* state. It re-reads the session every frame, so the caller re-renders it on
|
|
112
|
+
* a timer while it is open. ↑/↓ select, X cancels the selected running job,
|
|
113
|
+
* Esc or Close dismisses; nothing enters the transcript. A source without
|
|
114
|
+
* `cancel` renders read-only, and a subagent's job never offers Cancel: only
|
|
115
|
+
* the root's own jobs can be stopped here.
|
|
102
116
|
*/
|
|
103
117
|
export class JobsSheet implements Component {
|
|
104
118
|
readonly nativeOverlay = {
|
|
@@ -117,17 +131,20 @@ export class JobsSheet implements Component {
|
|
|
117
131
|
this.#source = source;
|
|
118
132
|
}
|
|
119
133
|
|
|
120
|
-
describe(
|
|
121
|
-
const
|
|
122
|
-
const { jobs, selected } = this.#current();
|
|
134
|
+
describe(_cx: DescribeContext): NativeNode {
|
|
135
|
+
const { rows, jobs, selected, cancellable } = this.#current();
|
|
123
136
|
const detail = selected && this.#source.inspect(selected.id);
|
|
124
137
|
const now = Date.now();
|
|
125
138
|
// Elapsed nodes tick on their own; list values and the output pane redraw
|
|
126
|
-
// when a job starts, settles or
|
|
127
|
-
// grows, or (while one runs) once a second.
|
|
139
|
+
// when a job starts, settles, leaves or moves in the tree, its process
|
|
140
|
+
// changes, its output grows, or (while one runs) once a second.
|
|
128
141
|
const key = [
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
rows
|
|
143
|
+
.map(
|
|
144
|
+
entry =>
|
|
145
|
+
`${jobTreeGuide(entry, theme)}${entry.kind === "job" ? `${entry.job.id}:${entry.job.status}` : entry.agent.id}`,
|
|
146
|
+
)
|
|
147
|
+
.join(","),
|
|
131
148
|
selected?.id,
|
|
132
149
|
detail?.pids.join(" "),
|
|
133
150
|
detail?.exitCode,
|
|
@@ -136,38 +153,47 @@ export class JobsSheet implements Component {
|
|
|
136
153
|
].join("|");
|
|
137
154
|
const output = detail?.output;
|
|
138
155
|
if (this.#native?.key === key && this.#native.output === output) return this.#native.node;
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
);
|
|
156
|
+
const close = actionButton("Close", "close", { keys: "escape" });
|
|
157
|
+
const described = selected
|
|
158
|
+
? col([
|
|
159
|
+
node(
|
|
160
|
+
"row",
|
|
161
|
+
{ role: "zeta.jobs.split" },
|
|
162
|
+
[
|
|
163
|
+
node("col", { role: "zeta.jobs.master" }, [describeJobList(rows, selected, now)], "master"),
|
|
164
|
+
describeDetail(selected, detail, now, cancellable),
|
|
165
|
+
],
|
|
166
|
+
"split",
|
|
167
|
+
),
|
|
168
|
+
actionBar([describeSummary(jobs), null, close]),
|
|
169
|
+
])
|
|
170
|
+
: col([describeEmpty(), actionBar([null, close])]);
|
|
155
171
|
this.#native = { key, output, node: described };
|
|
156
172
|
return described;
|
|
157
173
|
}
|
|
158
174
|
|
|
159
|
-
/** Rows for a terminal without the native surface:
|
|
175
|
+
/** Rows for a terminal without the native surface: the job tree, then the selected job's facts and output tail. */
|
|
160
176
|
render(width: number): readonly string[] {
|
|
161
|
-
const { jobs, selected } = this.#current();
|
|
177
|
+
const { rows, jobs, selected } = this.#current();
|
|
162
178
|
const now = Date.now();
|
|
163
179
|
const running = jobs.filter(job => job.status === "running").length;
|
|
164
180
|
const lines = [truncateToWidth(` Background jobs · ${running} running`, width)];
|
|
165
181
|
if (jobs.length === 0) lines.push(" No background jobs");
|
|
166
|
-
for (const
|
|
182
|
+
for (const entry of rows) {
|
|
183
|
+
const guide = jobTreeGuide(entry, theme);
|
|
184
|
+
if (entry.kind === "agent") {
|
|
185
|
+
lines.push(truncateToWidth(` ${guide}${entry.agent.id}`, width));
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const { job } = entry;
|
|
167
189
|
const cursor = job === selected ? ">" : " ";
|
|
168
190
|
const age = formatDuration(jobAge(job, now));
|
|
191
|
+
const agent = entry.agent && entry.agent.id !== job.label ? `${entry.agent.id}: ` : "";
|
|
169
192
|
lines.push(
|
|
170
|
-
truncateToWidth(
|
|
193
|
+
truncateToWidth(
|
|
194
|
+
`${cursor} ${guide}${job.status} ${job.type} ${age} ${agent}${collapseCommand(job.label)}`,
|
|
195
|
+
width,
|
|
196
|
+
),
|
|
171
197
|
);
|
|
172
198
|
}
|
|
173
199
|
const detail = selected && this.#source.inspect(selected.id);
|
|
@@ -204,13 +230,24 @@ export class JobsSheet implements Component {
|
|
|
204
230
|
else if (event.type === "action" && event.act === "cancel") this.#cancelSelected();
|
|
205
231
|
}
|
|
206
232
|
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
233
|
+
/**
|
|
234
|
+
* The job tree, its jobs in tree order, the selected one (the first when
|
|
235
|
+
* the selection left) and whether the sheet can stop it: a job the root
|
|
236
|
+
* owns, with a source that cancels.
|
|
237
|
+
*/
|
|
238
|
+
#current(): {
|
|
239
|
+
rows: readonly JobTreeRow<JobsPanelJob>[];
|
|
240
|
+
jobs: readonly JobsPanelJob[];
|
|
241
|
+
selected: JobsPanelJob | undefined;
|
|
242
|
+
cancellable: boolean;
|
|
243
|
+
} {
|
|
244
|
+
const { running, recent, rootId, agents } = this.#source.load();
|
|
245
|
+
const rows = buildJobTree([...running, ...recent], agents ?? [], rootId);
|
|
246
|
+
const jobs = rows.flatMap(entry => (entry.kind === "job" ? [entry.job] : []));
|
|
211
247
|
const selected = jobs.find(job => job.id === this.#selectedId) ?? jobs[0];
|
|
212
248
|
this.#selectedId = selected?.id;
|
|
213
|
-
|
|
249
|
+
const owned = selected?.ownerId === undefined || selected.ownerId === rootId;
|
|
250
|
+
return { rows, jobs, selected, cancellable: this.#source.cancel !== undefined && owned };
|
|
214
251
|
}
|
|
215
252
|
|
|
216
253
|
#tailLines(id: string, output: string): readonly string[] {
|
|
@@ -229,8 +266,8 @@ export class JobsSheet implements Component {
|
|
|
229
266
|
}
|
|
230
267
|
|
|
231
268
|
#cancelSelected(): void {
|
|
232
|
-
const { selected } = this.#current();
|
|
233
|
-
if (selected?.status === "running") this.#source.cancel(selected.id);
|
|
269
|
+
const { selected, cancellable } = this.#current();
|
|
270
|
+
if (cancellable && selected?.status === "running") this.#source.cancel?.(selected.id);
|
|
234
271
|
}
|
|
235
272
|
}
|
|
236
273
|
|
|
@@ -256,94 +293,137 @@ function tailLines(text: string, count: number): string[] {
|
|
|
256
293
|
return lines.reverse();
|
|
257
294
|
}
|
|
258
295
|
|
|
259
|
-
/**
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
296
|
+
/**
|
|
297
|
+
* The master list: the job tree, connectors leading each label. A subagent
|
|
298
|
+
* without a listed task job heads its jobs as a quiet label (a plain text
|
|
299
|
+
* child: no row behavior, so ↑/↓ and clicks pass it by).
|
|
300
|
+
*/
|
|
301
|
+
function describeJobList(rows: readonly JobTreeRow<JobsPanelJob>[], selected: JobsPanelJob, nowMs: number): NativeNode {
|
|
302
|
+
const children: NativeChild[] = rows.map(entry => {
|
|
303
|
+
const guide = jobTreeGuide(entry, theme);
|
|
304
|
+
if (entry.kind === "job") return describeJobItem(entry.job, entry.agent?.id, guide, nowMs);
|
|
264
305
|
return node(
|
|
265
|
-
"
|
|
306
|
+
"text",
|
|
266
307
|
{
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
status: AGENT_STATUS[job.status],
|
|
271
|
-
stats: settled ? { took: age } : { age },
|
|
308
|
+
role: "zeta.jobs.group",
|
|
309
|
+
spans: compact([guide !== "" && span(guide, "mono dim"), span(entry.agent.id)]),
|
|
310
|
+
wrap: "none",
|
|
272
311
|
},
|
|
273
312
|
undefined,
|
|
274
|
-
|
|
313
|
+
`agent-${stableKey(entry.agent.id)}`,
|
|
275
314
|
);
|
|
276
|
-
}
|
|
277
|
-
return
|
|
278
|
-
"row",
|
|
279
|
-
{ role: "zeta.jobs.row", gap: "sm", align: "center", title: `${job.id} · ${job.type} · ${job.status}` },
|
|
280
|
-
[
|
|
281
|
-
text([DOT[job.status]], { aria: job.status, shrink: 0 }),
|
|
282
|
-
text([span(collapseCommand(job.label))], { wrap: "none", truncate: "end", grow: 1 }),
|
|
283
|
-
text([span(job.type, "dim")], { wrap: "none", shrink: 0 }),
|
|
284
|
-
elapsed(age, settled),
|
|
285
|
-
],
|
|
286
|
-
job.id,
|
|
287
|
-
);
|
|
315
|
+
});
|
|
316
|
+
return selectList("jobs", children, { selected: selected.id, max: { lines: 12 } });
|
|
288
317
|
}
|
|
289
318
|
|
|
290
|
-
/**
|
|
291
|
-
|
|
319
|
+
/**
|
|
320
|
+
* One list row: the type icon tinted by status, the tree connector, the
|
|
321
|
+
* subagent a task job runs, the one-line label (a process job's command in
|
|
322
|
+
* mono, a task's work in prose) and how long it ran.
|
|
323
|
+
*/
|
|
324
|
+
function describeJobItem(job: JobsPanelJob, agentName: string | undefined, guide: string, nowMs: number): NativeNode {
|
|
325
|
+
const status = STATUS_LABEL[job.status];
|
|
326
|
+
const age = formatDuration(jobAge(job, nowMs));
|
|
292
327
|
return item(job.id, {
|
|
293
|
-
label: [
|
|
294
|
-
|
|
295
|
-
|
|
328
|
+
label: compact([
|
|
329
|
+
guide !== "" && span(guide, "mono dim"),
|
|
330
|
+
agentName !== undefined && agentName !== job.label && span(`${agentName} `, "strong"),
|
|
331
|
+
job.type === "task" ? span(job.label) : span(collapseCommand(job.label), "mono"),
|
|
332
|
+
]),
|
|
333
|
+
value: [span(age, "mono")],
|
|
334
|
+
icon: TYPE_ICON[job.type] ?? "job",
|
|
335
|
+
tone: STATUS_TONE[job.status],
|
|
336
|
+
title: `${job.label}\n${status} · ${job.type} · ${job.id}`,
|
|
337
|
+
aria: `${job.label}, ${status.toLowerCase()}, ${age}`,
|
|
296
338
|
});
|
|
297
339
|
}
|
|
298
340
|
|
|
299
|
-
/** The
|
|
341
|
+
/** The footer's at-a-glance count. */
|
|
342
|
+
function describeSummary(jobs: readonly JobsPanelJob[]): NativeNode {
|
|
343
|
+
const running = jobs.filter(job => job.status === "running").length;
|
|
344
|
+
const parts = compact([
|
|
345
|
+
running > 0 && `${running} running`,
|
|
346
|
+
jobs.length > running && `${jobs.length - running} recent`,
|
|
347
|
+
]);
|
|
348
|
+
return node("text", { role: "zeta.jobs.summary", text: parts.join(" · "), wrap: "none" }, undefined, "summary");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** No jobs: what the sheet is for, in place of the split. */
|
|
352
|
+
function describeEmpty(): NativeNode {
|
|
353
|
+
return node(
|
|
354
|
+
"col",
|
|
355
|
+
{ role: "zeta.jobs.empty", align: "center" },
|
|
356
|
+
[
|
|
357
|
+
node("icon", { name: "jobs" }),
|
|
358
|
+
text("No background jobs", { role: "zeta.jobs.empty.title" }),
|
|
359
|
+
text("Commands and subagent tasks started in the background show up here while they run.", {
|
|
360
|
+
role: "zeta.jobs.empty.note",
|
|
361
|
+
wrap: "word",
|
|
362
|
+
}),
|
|
363
|
+
],
|
|
364
|
+
"empty",
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The selected job: a status line (pill, live elapsed, type · id, Cancel job
|
|
370
|
+
* when `cancellable`), its task or full command, facts and the output pane (or
|
|
371
|
+
* its empty state).
|
|
372
|
+
*/
|
|
300
373
|
function describeDetail(
|
|
301
374
|
job: JobsPanelJob,
|
|
302
375
|
detail: JobsPanelDetail | undefined,
|
|
303
376
|
nowMs: number,
|
|
304
|
-
|
|
377
|
+
cancellable: boolean,
|
|
305
378
|
): NativeNode {
|
|
306
379
|
const settled = job.status !== "running";
|
|
307
380
|
const exit = detail?.exitCode;
|
|
381
|
+
const age = Math.round(jobAge(job, nowMs));
|
|
382
|
+
const command = detail?.command ?? (job.type === "task" ? undefined : job.label);
|
|
383
|
+
const pids = detail?.pids ?? [];
|
|
384
|
+
const facts = compact([
|
|
385
|
+
job.agentId ? { k: "Agent", v: [span(job.agentId, "mono")] } : undefined,
|
|
386
|
+
detail?.cwd ? { k: "Directory", v: [span(detail.cwd, "path")] } : undefined,
|
|
387
|
+
pids.length > 0 && { k: pids.length > 1 ? "PIDs" : "PID", v: [span(pids.join(", "), "num")] },
|
|
388
|
+
exit !== undefined && { k: "Exit code", v: [span(String(exit), exit === 0 ? "success" : "error")] },
|
|
389
|
+
detail?.artifactId ? { k: "Full output", v: [span(`artifact://${detail.artifactId}`, "mono")] } : undefined,
|
|
390
|
+
]);
|
|
308
391
|
const children = compact<NativeChild>([
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
),
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
["Directory", detail.cwd && [span(detail.cwd, "path")]],
|
|
327
|
-
["PID", detail.pids.length > 0 ? [span(detail.pids.join(", "), "num")] : undefined],
|
|
328
|
-
["Exit", exit === undefined ? undefined : [span(String(exit), exit === 0 ? "success" : "error")]],
|
|
329
|
-
["Full output", detail.artifactId && [span(`artifact://${detail.artifactId}`, "mono")]],
|
|
392
|
+
row(
|
|
393
|
+
compact<NativeChild>([
|
|
394
|
+
text([STATUS_GLYPH[job.status], span(` ${STATUS_LABEL[job.status]}`)], {
|
|
395
|
+
role: "zeta.jobs.state",
|
|
396
|
+
tone: STATUS_TONE[job.status],
|
|
397
|
+
wrap: "none",
|
|
398
|
+
}),
|
|
399
|
+
node("elapsed", {
|
|
400
|
+
age,
|
|
401
|
+
...(settled ? { stopped: age } : {}),
|
|
402
|
+
format: "short",
|
|
403
|
+
role: "zeta.jobs.age",
|
|
404
|
+
title: settled ? "Ran for" : "Running for",
|
|
405
|
+
}),
|
|
406
|
+
text([span(`${job.type} · ${job.id}`, "mono")], { role: "zeta.jobs.id", wrap: "none", truncate: "end" }),
|
|
407
|
+
node("spacer", { grow: 1 }),
|
|
408
|
+
cancellable && !settled && actionButton("Cancel job", "cancel", { keys: "x", tone: "error" }),
|
|
330
409
|
]),
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
410
|
+
{ role: "zeta.jobs.status", gap: "sm", align: "center" },
|
|
411
|
+
),
|
|
412
|
+
command
|
|
413
|
+
? code(command, { lang: "bash", wrap: true, preview: { lines: 4 }, role: "zeta.jobs.command" })
|
|
414
|
+
: text(job.label, { role: "zeta.jobs.title", wrap: "word" }),
|
|
415
|
+
facts.length > 0 && node("kv", { items: facts, role: "zeta.jobs.facts" }),
|
|
416
|
+
node(
|
|
417
|
+
"col",
|
|
418
|
+
{ role: "zeta.jobs.out", gap: "sm" },
|
|
419
|
+
[
|
|
420
|
+
text("Output", { role: "zeta.jobs.label" }),
|
|
421
|
+
detail?.output
|
|
422
|
+
? ansi(detail.output, { follow: !settled, role: "zeta.jobs.output", max: { h: "16lines" } })
|
|
423
|
+
: text(settled ? "No output" : "No output yet", { role: "zeta.jobs.nooutput" }),
|
|
424
|
+
],
|
|
425
|
+
"out",
|
|
426
|
+
),
|
|
347
427
|
]);
|
|
348
|
-
return node("col", {
|
|
428
|
+
return node("col", { role: "zeta.jobs.detail" }, children, stableKey(job.id));
|
|
349
429
|
}
|