@agimon-ai/po-runner 0.0.0-alpha.21 → 0.0.0-alpha.23
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/apiContracts.cjs +8 -1
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.d.mts.map +1 -1
- package/dist/apiContracts.mjs +8 -1
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/{bashRunService-Ci0cGdYO.d.mts → bashRunService-BL4JIUi2.d.mts} +6 -1
- package/dist/bashRunService-BL4JIUi2.d.mts.map +1 -0
- package/dist/{bashRunService-DjOH5kye.cjs → bashRunService-CJhMNrPz.cjs} +8 -4
- package/dist/bashRunService-CJhMNrPz.cjs.map +1 -0
- package/dist/{bashRunService-Cl3pYccr.mjs → bashRunService-DbYsj1zc.mjs} +8 -4
- package/dist/bashRunService-DbYsj1zc.mjs.map +1 -0
- package/dist/bashRunService.cjs +1 -1
- package/dist/bashRunService.d.mts +1 -1
- package/dist/bashRunService.d.mts.map +1 -1
- package/dist/bashRunService.mjs +1 -1
- package/dist/bashTool.cjs +1 -1
- package/dist/bashTool.d.mts +2 -2
- package/dist/bashTool.mjs +1 -1
- package/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/extensions/mcp.cjs +1 -0
- package/dist/extensions/mcp.cjs.map +1 -1
- package/dist/extensions/mcp.mjs +1 -0
- package/dist/extensions/mcp.mjs.map +1 -1
- package/dist/extensions/native.mjs +533 -256
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +24 -6
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +23 -5
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +459 -517
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/{headless-Dko_yBiy.cjs → headless-BqYKFHwy.cjs} +31 -6
- package/dist/headless-BqYKFHwy.cjs.map +1 -0
- package/dist/{headless-DOlCixyf.mjs → headless-CV-eb5rz.mjs} +31 -6
- package/dist/headless-CV-eb5rz.mjs.map +1 -0
- package/dist/{index-BzeXJk6V.d.mts → index-BciAgV5f.d.mts} +11 -4
- package/dist/index-BciAgV5f.d.mts.map +1 -0
- package/dist/{index--xF7DvsE.d.mts → index-CXbcmf-G.d.mts} +2 -2
- package/dist/{index--xF7DvsE.d.mts.map → index-CXbcmf-G.d.mts.map} +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/responseEnvelope.d.mts +1 -1
- package/dist/{rmuxBackend-Dh0CYVAL.cjs → rmuxBackend-Cod2H85V.cjs} +2 -2
- package/dist/{rmuxBackend-Dh0CYVAL.cjs.map → rmuxBackend-Cod2H85V.cjs.map} +1 -1
- package/dist/rmuxBackend.cjs +1 -1
- package/dist/{rtkProcessor-DSGbM-Cm.cjs → rtkProcessor-xwARIxhe.cjs} +2 -2
- package/dist/{rtkProcessor-DSGbM-Cm.cjs.map → rtkProcessor-xwARIxhe.cjs.map} +1 -1
- package/dist/rtkProcessor.cjs +1 -1
- package/dist/{runnerDependencies-7h_cxOva.mjs → runnerDependencies-CamKwphq.mjs} +2 -2
- package/dist/{runnerDependencies-7h_cxOva.mjs.map → runnerDependencies-CamKwphq.mjs.map} +1 -1
- package/dist/{runnerDependencies-DSun3xO-.cjs → runnerDependencies-HIqMQp9b.cjs} +4 -4
- package/dist/{runnerDependencies-DSun3xO-.cjs.map → runnerDependencies-HIqMQp9b.cjs.map} +1 -1
- package/dist/runnerDependencies.cjs +1 -1
- package/dist/runnerDependencies.d.mts +1 -1
- package/dist/runnerDependencies.mjs +1 -1
- package/dist/{type-Do0YxwVQ.d.mts → type-BQ2shEB5.d.mts} +2 -2
- package/dist/{type-Do0YxwVQ.d.mts.map → type-BQ2shEB5.d.mts.map} +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +17 -17
- package/dist/bashRunService-Ci0cGdYO.d.mts.map +0 -1
- package/dist/bashRunService-Cl3pYccr.mjs.map +0 -1
- package/dist/bashRunService-DjOH5kye.cjs.map +0 -1
- package/dist/headless-DOlCixyf.mjs.map +0 -1
- package/dist/headless-Dko_yBiy.cjs.map +0 -1
- package/dist/index-BzeXJk6V.d.mts.map +0 -1
package/dist/extensions/web.mjs
CHANGED
|
@@ -1,70 +1,12 @@
|
|
|
1
1
|
import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineSessionStore, defineToolRenderer, defineWebPlugin, sessionApiAddress } from "@agimon-ai/po-core/web";
|
|
2
|
-
import { AnsiLine, AnsiText, ArrowRightIcon, Button,
|
|
2
|
+
import { AlertIcon, AnsiLine, AnsiText, ArrowRightIcon, Button, ChevronDownIcon, Dot, EmptyState, FileIcon, FolderIcon, Input, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, PickerContent, PlayCircleIcon, Popover, PopoverTrigger, SearchIcon, StatusBadge, SyntaxText, TerminalView, ansiSpans, toolTone } from "@agimon-ai/po-web-components";
|
|
3
3
|
import { useStore } from "@tanstack/react-store";
|
|
4
4
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { sealedTransport } from "@agimon-ai/po-web-security/browser";
|
|
7
|
-
//#region src/extensions/workspaces/sessions/(frontend)/_lib/format.ts
|
|
8
|
-
/**
|
|
9
|
-
* Local copy of the runtime's formatUptime (src/services/bashResult/index.ts):
|
|
10
|
-
* the web plugin may reach only src/types, so the one helper it needs lives here.
|
|
11
|
-
*/
|
|
12
|
-
function formatRunnerUptime(startedAt, now) {
|
|
13
|
-
const started = Date.parse(startedAt);
|
|
14
|
-
if (Number.isNaN(started)) return "unknown";
|
|
15
|
-
const seconds = Math.max(0, Math.floor((now - started) / 1e3));
|
|
16
|
-
if (seconds < 60) return `${seconds}s`;
|
|
17
|
-
const minutes = Math.floor(seconds / 60);
|
|
18
|
-
if (minutes < 60) return `${minutes}m`;
|
|
19
|
-
const hours = Math.floor(minutes / 60);
|
|
20
|
-
return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* The lines a log view shows: the slice's text, then whatever the follow
|
|
24
|
-
* stream appended, bounded to the newest `max`.
|
|
25
|
-
*
|
|
26
|
-
* A log file ends with a newline, and the reader hands its text back with that
|
|
27
|
-
* newline intact, so a naive split reports one blank line more than the file
|
|
28
|
-
* has. Dropping exactly one trailing empty entry keeps the count the reader
|
|
29
|
-
* sees equal to the count the server reported.
|
|
30
|
-
*/
|
|
31
|
-
function logViewLines(text, appended, max, options = {}) {
|
|
32
|
-
const split = text.split("\n");
|
|
33
|
-
if (split.length > 0 && split[split.length - 1] === "") split.pop();
|
|
34
|
-
if (options.dropPartialTail === true && split.length > 0) split.pop();
|
|
35
|
-
const all = [...split, ...appended];
|
|
36
|
-
const lines = all.slice(-max);
|
|
37
|
-
return {
|
|
38
|
-
lines,
|
|
39
|
-
hidden: all.length - lines.length
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Absolute 1-based numbers for a tail, counted back from the last line.
|
|
44
|
-
*
|
|
45
|
-
* A tail is contiguous, so only its end has to be known. Grep results are not
|
|
46
|
-
* contiguous and carry their own numbers from the reader instead.
|
|
47
|
-
*/
|
|
48
|
-
function tailLineNumbers(lastLineNumber, count) {
|
|
49
|
-
const first = Math.max(1, lastLineNumber - count + 1);
|
|
50
|
-
return Array.from({ length: count }, (_, index) => first + index);
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Whether the view is actually following a growing log.
|
|
54
|
-
*
|
|
55
|
-
* Three things must hold: the reader asked to follow, no query is set (a
|
|
56
|
-
* filtered view is a snapshot of the whole file, which a stream cannot
|
|
57
|
-
* assemble a chunk at a time), and the runner is still writing. Without the
|
|
58
|
-
* last one a finished run would claim to be tailing a file nothing will ever
|
|
59
|
-
* append to.
|
|
60
|
-
*/
|
|
61
|
-
function isFollowingLive(following, filtering, running) {
|
|
62
|
-
return following && !filtering && running;
|
|
63
|
-
}
|
|
64
|
-
//#endregion
|
|
65
7
|
//#region src/extensions/workspaces/sessions/(frontend)/_lib/launchLine.ts
|
|
66
8
|
/**
|
|
67
|
-
* The `/runners start` line the
|
|
9
|
+
* The `/runners start` line the runners panel's command bar sends.
|
|
68
10
|
*
|
|
69
11
|
* WHY THE BUILDER LIVES HERE AND THE PARSER DOES NOT:
|
|
70
12
|
* Cockpit code is compiled into the host bundle and may import only this
|
|
@@ -80,6 +22,21 @@ const RUNNER_SHELL_REQUEST = {
|
|
|
80
22
|
name: "shell",
|
|
81
23
|
interactive: true
|
|
82
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* What the runners panel's Run sends: the typed command on a terminal when
|
|
27
|
+
* one is available, or a login shell when nothing was typed.
|
|
28
|
+
*/
|
|
29
|
+
function runnerRunRequest(command, cwd) {
|
|
30
|
+
const where = cwd.trim() === "" ? {} : { cwd };
|
|
31
|
+
return command.trim() === "" ? {
|
|
32
|
+
...RUNNER_SHELL_REQUEST,
|
|
33
|
+
...where
|
|
34
|
+
} : {
|
|
35
|
+
command,
|
|
36
|
+
...where,
|
|
37
|
+
preferInteractive: true
|
|
38
|
+
};
|
|
39
|
+
}
|
|
83
40
|
/** Quotes a value that carries whitespace, which is how a value survives tokenizing. */
|
|
84
41
|
function quote(value) {
|
|
85
42
|
return /\s/u.test(value) ? `"${value}"` : value;
|
|
@@ -90,7 +47,7 @@ function runnerLaunchLine(request) {
|
|
|
90
47
|
const pairs = [
|
|
91
48
|
...name === "" ? [] : [`name=${quote(name)}`],
|
|
92
49
|
...cwd === "" ? [] : [`cwd=${quote(cwd)}`],
|
|
93
|
-
...request.interactive === true ? ["interactive=true"] : []
|
|
50
|
+
...request.interactive === true ? ["interactive=true"] : request.preferInteractive === true ? ["interactive=prefer"] : []
|
|
94
51
|
];
|
|
95
52
|
return [
|
|
96
53
|
RUNNERS_START_LINE,
|
|
@@ -99,12 +56,6 @@ function runnerLaunchLine(request) {
|
|
|
99
56
|
request.command.trim()
|
|
100
57
|
].join(" ");
|
|
101
58
|
}
|
|
102
|
-
/** What the form will not let a reader send, worded the way the session would answer. */
|
|
103
|
-
function launchProblems(request) {
|
|
104
|
-
const problems = [];
|
|
105
|
-
if (request.command.trim() === "") problems.push("A runner needs a command to run.");
|
|
106
|
-
return problems;
|
|
107
|
-
}
|
|
108
59
|
//#endregion
|
|
109
60
|
//#region src/constants/webRunners.ts
|
|
110
61
|
const RUNNER_RUNS_TYPE = "runner_runs";
|
|
@@ -163,151 +114,233 @@ const runnerRunsChannel = runners.channel({
|
|
|
163
114
|
channel: RUNNER_RUNS_TYPE,
|
|
164
115
|
parse(input) {
|
|
165
116
|
if (!isRecord$3(input) || !Array.isArray(input.runs)) return null;
|
|
166
|
-
|
|
117
|
+
const failure = input.startFailure;
|
|
118
|
+
return {
|
|
119
|
+
runs: input.runs.filter(isRecord$3),
|
|
120
|
+
...typeof input.sessionDirectory === "string" ? { sessionDirectory: input.sessionDirectory } : {},
|
|
121
|
+
...isRecord$3(failure) && typeof failure.id === "string" && typeof failure.message === "string" ? { startFailure: {
|
|
122
|
+
id: failure.id,
|
|
123
|
+
message: failure.message
|
|
124
|
+
} } : {}
|
|
125
|
+
};
|
|
167
126
|
},
|
|
168
|
-
reduce(current,
|
|
169
|
-
const stillRunning = new Set(runs.filter((run) => run.state === "running").map((run) => run.id));
|
|
127
|
+
reduce(current, payload) {
|
|
128
|
+
const stillRunning = new Set(payload.runs.filter((run) => run.state === "running").map((run) => run.id));
|
|
170
129
|
return {
|
|
171
|
-
|
|
130
|
+
...payload,
|
|
172
131
|
stopRequested: current.stopRequested.filter((id) => stillRunning.has(id))
|
|
173
132
|
};
|
|
174
133
|
}
|
|
175
134
|
});
|
|
176
135
|
//#endregion
|
|
177
|
-
//#region src/extensions/workspaces/sessions/(frontend)/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
136
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/format.ts
|
|
137
|
+
/**
|
|
138
|
+
* Local copy of the runtime's formatUptime (src/services/bashResult/index.ts):
|
|
139
|
+
* the web plugin may reach only src/types, so the one helper it needs lives here.
|
|
140
|
+
*/
|
|
141
|
+
function formatRunnerUptime(startedAt, now) {
|
|
142
|
+
const started = Date.parse(startedAt);
|
|
143
|
+
if (Number.isNaN(started)) return "unknown";
|
|
144
|
+
const seconds = Math.max(0, Math.floor((now - started) / 1e3));
|
|
145
|
+
if (seconds < 60) return `${seconds}s`;
|
|
146
|
+
const minutes = Math.floor(seconds / 60);
|
|
147
|
+
if (minutes < 60) return `${minutes}m`;
|
|
148
|
+
const hours = Math.floor(minutes / 60);
|
|
149
|
+
return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
|
|
185
150
|
}
|
|
186
151
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* session will parse.
|
|
152
|
+
* The lines a log view shows: the slice's text, then whatever the follow
|
|
153
|
+
* stream appended, bounded to the newest `max`.
|
|
190
154
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
155
|
+
* A log file ends with a newline, and the reader hands its text back with that
|
|
156
|
+
* newline intact, so a naive split reports one blank line more than the file
|
|
157
|
+
* has. Dropping exactly one trailing empty entry keeps the count the reader
|
|
158
|
+
* sees equal to the count the server reported.
|
|
194
159
|
*/
|
|
195
|
-
function
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const [
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
interactive
|
|
160
|
+
function logViewLines(text, appended, max, options = {}) {
|
|
161
|
+
const split = text.split("\n");
|
|
162
|
+
if (split.length > 0 && split[split.length - 1] === "") split.pop();
|
|
163
|
+
if (options.dropPartialTail === true && split.length > 0) split.pop();
|
|
164
|
+
const all = [...split, ...appended];
|
|
165
|
+
const lines = all.slice(-max);
|
|
166
|
+
return {
|
|
167
|
+
lines,
|
|
168
|
+
hidden: all.length - lines.length
|
|
205
169
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Absolute 1-based numbers for a tail, counted back from the last line.
|
|
173
|
+
*
|
|
174
|
+
* A tail is contiguous, so only its end has to be known. Grep results are not
|
|
175
|
+
* contiguous and carry their own numbers from the reader instead.
|
|
176
|
+
*/
|
|
177
|
+
function tailLineNumbers(lastLineNumber, count) {
|
|
178
|
+
const first = Math.max(1, lastLineNumber - count + 1);
|
|
179
|
+
return Array.from({ length: count }, (_, index) => first + index);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Whether the view is actually following a growing log.
|
|
183
|
+
*
|
|
184
|
+
* Three things must hold: the reader asked to follow, no query is set (a
|
|
185
|
+
* filtered view is a snapshot of the whole file, which a stream cannot
|
|
186
|
+
* assemble a chunk at a time), and the runner is still writing. Without the
|
|
187
|
+
* last one a finished run would claim to be tailing a file nothing will ever
|
|
188
|
+
* append to.
|
|
189
|
+
*/
|
|
190
|
+
function isFollowingLive(following, filtering, running) {
|
|
191
|
+
return following && !filtering && running;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* A folder as the command bar names it: the session directory by its last
|
|
195
|
+
* segment, a folder inside it relative to it, anything else as written.
|
|
196
|
+
*/
|
|
197
|
+
function runnerFolderLabel(folder, sessionDirectory) {
|
|
198
|
+
if (folder === "" || folder === sessionDirectory) return sessionDirectory === void 0 ? "session directory" : sessionDirectory.split("/").pop() ?? sessionDirectory;
|
|
199
|
+
if (sessionDirectory !== void 0 && folder.startsWith(`${sessionDirectory}/`)) return folder.slice(sessionDirectory.length + 1);
|
|
200
|
+
return folder;
|
|
201
|
+
}
|
|
202
|
+
/** The folders the picker offers past the session directory: those in use, then the one in effect. */
|
|
203
|
+
function runnerFolders(inUse, current, sessionDirectory) {
|
|
204
|
+
const folders = /* @__PURE__ */ new Set([...inUse, current]);
|
|
205
|
+
folders.delete("");
|
|
206
|
+
if (sessionDirectory !== void 0) folders.delete(sessionDirectory);
|
|
207
|
+
return [...folders];
|
|
208
|
+
}
|
|
209
|
+
/** The badge a run wears: its state while running, then how it ended. */
|
|
210
|
+
function runnerBadge(run) {
|
|
211
|
+
if (run === void 0) return {
|
|
212
|
+
tone: "neutral",
|
|
213
|
+
label: "gone"
|
|
212
214
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
if (run.state === "running") return {
|
|
216
|
+
tone: "running",
|
|
217
|
+
label: "running"
|
|
218
|
+
};
|
|
219
|
+
if (run.exit === void 0 || run.exit.reason === "stopped") return {
|
|
220
|
+
tone: "neutral",
|
|
221
|
+
label: "stopped"
|
|
222
|
+
};
|
|
223
|
+
return run.exit.reason === "completed" && (run.exit.code === 0 || run.exit.code === null) ? {
|
|
224
|
+
tone: "ok",
|
|
225
|
+
label: "done"
|
|
226
|
+
} : {
|
|
227
|
+
tone: "error",
|
|
228
|
+
label: run.exit.reason.replace("_", " ")
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnerCommandBar.tsx
|
|
233
|
+
/**
|
|
234
|
+
* The one place a runner starts: a command, the folder it runs in, and Run.
|
|
235
|
+
* An empty command opens a plain shell in that folder instead.
|
|
236
|
+
*/
|
|
237
|
+
function RunnerCommandBar({ command, onCommandChange, cwd, onCwdChange, sessionDirectory, folders, problem, onLaunch, defaultFolderOpen = false }) {
|
|
238
|
+
const [folderOpen, setFolderOpen] = useState(defaultFolderOpen);
|
|
239
|
+
const [path, setPath] = useState("");
|
|
240
|
+
const label = runnerFolderLabel(cwd, sessionDirectory);
|
|
241
|
+
const pick = (next) => {
|
|
242
|
+
onCwdChange(next);
|
|
243
|
+
setPath("");
|
|
244
|
+
setFolderOpen(false);
|
|
245
|
+
};
|
|
246
|
+
const items = [{
|
|
247
|
+
value: "",
|
|
248
|
+
title: "Session directory",
|
|
249
|
+
subtitle: sessionDirectory ?? "Where this session started"
|
|
250
|
+
}, ...runnerFolders(folders, cwd, sessionDirectory).map((folder) => ({
|
|
251
|
+
value: folder,
|
|
252
|
+
title: runnerFolderLabel(folder, sessionDirectory),
|
|
253
|
+
subtitle: folder
|
|
254
|
+
}))];
|
|
255
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
256
|
+
className: "flex shrink-0 flex-col gap-1.5 border-b border-po-border-soft px-3 py-3 sm:px-[26px]",
|
|
257
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
258
|
+
className: "flex flex-wrap items-center gap-2 sm:flex-nowrap",
|
|
259
|
+
children: [
|
|
260
|
+
/* @__PURE__ */ jsxs("span", {
|
|
261
|
+
className: "relative flex min-w-0 flex-1 basis-full items-center sm:basis-auto",
|
|
262
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
263
|
+
"aria-hidden": true,
|
|
264
|
+
className: "pointer-events-none absolute left-3 font-mono text-base text-po-faint",
|
|
265
|
+
children: "$"
|
|
266
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
267
|
+
size: "lg",
|
|
268
|
+
"data-testid": "runners-panel-command",
|
|
269
|
+
"aria-label": "Command to run",
|
|
231
270
|
value: command,
|
|
232
|
-
placeholder: "pnpm
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
className: "
|
|
240
|
-
children: [/* @__PURE__ */ jsx(FieldLabel, { children: "Cwd" }), /* @__PURE__ */ jsx(Input, {
|
|
241
|
-
size: "sm",
|
|
242
|
-
"data-testid": "runner-launch-cwd",
|
|
243
|
-
value: cwd,
|
|
244
|
-
placeholder: "the session's own directory",
|
|
245
|
-
onChange: (event) => setCwd(event.target.value)
|
|
246
|
-
})]
|
|
247
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
248
|
-
className: "flex min-w-0 flex-1 flex-col gap-1",
|
|
249
|
-
children: [/* @__PURE__ */ jsx(FieldLabel, { children: "Name" }), /* @__PURE__ */ jsx(Input, {
|
|
250
|
-
size: "sm",
|
|
251
|
-
"data-testid": "runner-launch-name",
|
|
252
|
-
value: name,
|
|
253
|
-
placeholder: "chosen for you when empty",
|
|
254
|
-
onChange: (event) => setName(event.target.value)
|
|
255
|
-
})]
|
|
256
|
-
})]
|
|
257
|
-
}),
|
|
258
|
-
/* @__PURE__ */ jsxs("div", {
|
|
259
|
-
className: "flex items-center gap-2",
|
|
260
|
-
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
261
|
-
id: INTERACTIVE_FIELD_ID,
|
|
262
|
-
"data-testid": "runner-launch-interactive",
|
|
263
|
-
checked: interactive,
|
|
264
|
-
onCheckedChange: (checked) => setInteractive(checked === true)
|
|
265
|
-
}), /* @__PURE__ */ jsx("label", {
|
|
266
|
-
htmlFor: INTERACTIVE_FIELD_ID,
|
|
267
|
-
className: "text-base text-po-dim",
|
|
268
|
-
children: "needs a terminal, for a command that prompts (requires rmux or tmux)"
|
|
269
|
-
})]
|
|
270
|
-
}),
|
|
271
|
-
/* @__PURE__ */ jsxs("div", {
|
|
272
|
-
className: "flex flex-col gap-1 rounded-md border border-po-border bg-po-deep px-3 py-2",
|
|
273
|
-
children: [/* @__PURE__ */ jsx(FieldLabel, { children: "Sent to the session" }), /* @__PURE__ */ jsx("pre", {
|
|
274
|
-
"data-testid": "runner-launch-line",
|
|
275
|
-
className: "whitespace-pre-wrap break-words font-mono text-sm text-po-green",
|
|
276
|
-
children: line
|
|
271
|
+
placeholder: "a command, like pnpm dev",
|
|
272
|
+
autoComplete: "off",
|
|
273
|
+
spellCheck: false,
|
|
274
|
+
onChange: (event) => onCommandChange(event.target.value),
|
|
275
|
+
onKeyDown: (event) => {
|
|
276
|
+
if (event.key === "Enter") onLaunch();
|
|
277
|
+
},
|
|
278
|
+
className: "w-full pl-7 font-mono"
|
|
277
279
|
})]
|
|
278
280
|
}),
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
children:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
className: "flex-wrap sm:flex-nowrap",
|
|
286
|
-
children: [
|
|
287
|
-
/* @__PURE__ */ jsx("span", {
|
|
288
|
-
className: "w-full text-sm text-po-dim sm:w-auto",
|
|
289
|
-
children: "it appears above once the runtime starts it"
|
|
290
|
-
}),
|
|
291
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
292
|
-
/* @__PURE__ */ jsx(Button, {
|
|
281
|
+
/* @__PURE__ */ jsxs(Popover, {
|
|
282
|
+
open: folderOpen,
|
|
283
|
+
onOpenChange: setFolderOpen,
|
|
284
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
285
|
+
asChild: true,
|
|
286
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
293
287
|
variant: "outline",
|
|
294
|
-
size: "
|
|
295
|
-
"data-testid": "
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
288
|
+
size: "lg",
|
|
289
|
+
"data-testid": "runners-panel-cwd",
|
|
290
|
+
"aria-label": `Runs in ${label}`,
|
|
291
|
+
"aria-invalid": problem === void 0 ? void 0 : true,
|
|
292
|
+
title: cwd === "" ? sessionDirectory : cwd,
|
|
293
|
+
className: `max-w-56 gap-1.5 px-3 font-mono ${problem === void 0 ? "" : "border-po-red"}`,
|
|
294
|
+
children: [
|
|
295
|
+
/* @__PURE__ */ jsx(FolderIcon, { className: "h-3.5 w-3.5 shrink-0 text-po-faint" }),
|
|
296
|
+
/* @__PURE__ */ jsx("span", {
|
|
297
|
+
className: "truncate",
|
|
298
|
+
children: label
|
|
299
|
+
}),
|
|
300
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5 shrink-0 opacity-60" })
|
|
301
|
+
]
|
|
302
|
+
})
|
|
303
|
+
}), /* @__PURE__ */ jsx(PickerContent, {
|
|
304
|
+
side: "bottom",
|
|
305
|
+
align: "start",
|
|
306
|
+
title: "Runs in",
|
|
307
|
+
items,
|
|
308
|
+
value: [cwd],
|
|
309
|
+
onSelect: pick,
|
|
310
|
+
footer: /* @__PURE__ */ jsxs("span", {
|
|
311
|
+
className: "relative flex items-center px-3 py-2",
|
|
312
|
+
children: [/* @__PURE__ */ jsx(FolderIcon, { className: "pointer-events-none absolute left-5.5 h-3.5 w-3.5 text-po-faint" }), /* @__PURE__ */ jsx(Input, {
|
|
313
|
+
"data-testid": "runners-panel-cwd-path",
|
|
314
|
+
"aria-label": "Another folder",
|
|
315
|
+
value: path,
|
|
316
|
+
placeholder: "another folder, like packages/web",
|
|
317
|
+
spellCheck: false,
|
|
318
|
+
onChange: (event) => setPath(event.target.value),
|
|
319
|
+
onKeyDown: (event) => {
|
|
320
|
+
if (event.key === "Enter" && path.trim() !== "") pick(path.trim());
|
|
321
|
+
},
|
|
322
|
+
className: "w-full pl-8 font-mono"
|
|
323
|
+
})]
|
|
306
324
|
})
|
|
307
|
-
]
|
|
325
|
+
})]
|
|
326
|
+
}),
|
|
327
|
+
/* @__PURE__ */ jsx(Button, {
|
|
328
|
+
variant: "primary",
|
|
329
|
+
size: "lg",
|
|
330
|
+
"data-testid": "runners-panel-run",
|
|
331
|
+
onClick: onLaunch,
|
|
332
|
+
children: command.trim() === "" ? "Open shell" : "Run"
|
|
308
333
|
})
|
|
309
|
-
]
|
|
310
|
-
})
|
|
334
|
+
]
|
|
335
|
+
}), problem === void 0 ? /* @__PURE__ */ jsx("span", {
|
|
336
|
+
className: "text-sm text-po-dim",
|
|
337
|
+
children: "Runs in its own terminal and keeps going after Po's turn ends. Leave it empty for a plain shell."
|
|
338
|
+
}) : /* @__PURE__ */ jsxs("span", {
|
|
339
|
+
role: "alert",
|
|
340
|
+
"data-testid": "runners-panel-problem",
|
|
341
|
+
className: "flex items-center gap-1.5 text-sm text-po-red",
|
|
342
|
+
children: [/* @__PURE__ */ jsx(AlertIcon, { className: "size-3.5 shrink-0" }), problem]
|
|
343
|
+
})]
|
|
311
344
|
});
|
|
312
345
|
}
|
|
313
346
|
//#endregion
|
|
@@ -496,9 +529,9 @@ function followRunnerLog(sessionId, runId, from, handlers) {
|
|
|
496
529
|
}
|
|
497
530
|
//#endregion
|
|
498
531
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnerLogPanel.tsx
|
|
499
|
-
const TICK_MS$
|
|
532
|
+
const TICK_MS$1 = 1e4;
|
|
500
533
|
/** The tab id doubles as the URL segment, so it stays plain and unique across plugins. */
|
|
501
|
-
const TAB_ID_PREFIX
|
|
534
|
+
const TAB_ID_PREFIX = "runner-log-";
|
|
502
535
|
/** Lines held in the view; a follow that ran all day must not grow without bound. */
|
|
503
536
|
const MAX_VIEW_LINES = 2e3;
|
|
504
537
|
/** Context lines each side of a match, the same either-side window grep would use. */
|
|
@@ -513,7 +546,7 @@ const BYTES_PER_MB$1 = BYTES_PER_KB$1 * BYTES_PER_KB$1;
|
|
|
513
546
|
function runnerLogTab(run) {
|
|
514
547
|
const runId = run.id;
|
|
515
548
|
return {
|
|
516
|
-
id: `${TAB_ID_PREFIX
|
|
549
|
+
id: `${TAB_ID_PREFIX}${runId}`,
|
|
517
550
|
label: `log · ${run.name}`,
|
|
518
551
|
panel: (props) => /* @__PURE__ */ jsx(RunnerLogPanel, {
|
|
519
552
|
...props,
|
|
@@ -526,45 +559,11 @@ function formatSize$1(bytes) {
|
|
|
526
559
|
if (bytes < BYTES_PER_MB$1) return `${(bytes / BYTES_PER_KB$1).toFixed(1)} KB`;
|
|
527
560
|
return `${(bytes / BYTES_PER_MB$1).toFixed(1)} MB`;
|
|
528
561
|
}
|
|
529
|
-
function badgeOf(run) {
|
|
530
|
-
if (run === void 0) return {
|
|
531
|
-
tone: "neutral",
|
|
532
|
-
label: "gone"
|
|
533
|
-
};
|
|
534
|
-
if (run.state === "running") return {
|
|
535
|
-
tone: "running",
|
|
536
|
-
label: "running"
|
|
537
|
-
};
|
|
538
|
-
if (run.exit === void 0 || run.exit.reason === "stopped") return {
|
|
539
|
-
tone: "neutral",
|
|
540
|
-
label: "stopped"
|
|
541
|
-
};
|
|
542
|
-
return run.exit.reason === "completed" && (run.exit.code === 0 || run.exit.code === null) ? {
|
|
543
|
-
tone: "ok",
|
|
544
|
-
label: "done"
|
|
545
|
-
} : {
|
|
546
|
-
tone: "error",
|
|
547
|
-
label: run.exit.reason.replace("_", " ")
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
562
|
/** Whether one line is a match rather than the context printed around one. */
|
|
551
563
|
function isMatch(line, needle, ignoreCase) {
|
|
552
564
|
if (needle === "") return false;
|
|
553
565
|
return ignoreCase ? line.toLowerCase().includes(needle.toLowerCase()) : line.includes(needle);
|
|
554
566
|
}
|
|
555
|
-
/** One fact of the runner, on its own line so a long command is never elided. */
|
|
556
|
-
function MetaRow({ label, value }) {
|
|
557
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
558
|
-
className: "flex min-w-0 gap-2.5",
|
|
559
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
560
|
-
className: "w-14 shrink-0 text-base text-po-dim",
|
|
561
|
-
children: label
|
|
562
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
563
|
-
className: "min-w-0 flex-1 break-all font-mono text-sm text-po-dim",
|
|
564
|
-
children: value
|
|
565
|
-
})]
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
567
|
/**
|
|
569
568
|
* One rendered log line: its number, whether it matched, and the text.
|
|
570
569
|
*
|
|
@@ -573,24 +572,24 @@ function MetaRow({ label, value }) {
|
|
|
573
572
|
* keyed on the joined string; keyed by line number instead, an append only
|
|
574
573
|
* costs the line that appended.
|
|
575
574
|
*/
|
|
576
|
-
function LogRow({ number, line, matched }) {
|
|
575
|
+
function LogRow({ number, line, matched, dimmed }) {
|
|
577
576
|
return /* @__PURE__ */ jsxs("div", {
|
|
578
577
|
"data-testid": "runner-log-row",
|
|
579
578
|
"data-matched": matched,
|
|
580
579
|
className: "flex min-w-0 gap-2",
|
|
581
580
|
children: [
|
|
582
581
|
number === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
583
|
-
className: "w-12 shrink-0 select-none text-right text-
|
|
582
|
+
className: "w-12 shrink-0 select-none text-right font-mono text-sm leading-relaxed text-po-faint",
|
|
584
583
|
children: number
|
|
585
584
|
}),
|
|
586
585
|
/* @__PURE__ */ jsx("span", {
|
|
587
586
|
"aria-hidden": true,
|
|
588
|
-
className: `w-4 shrink-0 select-none text-
|
|
587
|
+
className: `w-4 shrink-0 select-none font-mono text-sm leading-relaxed ${matched ? "text-po-yellow" : "text-po-faint"}`,
|
|
589
588
|
children: matched ? ">>" : ""
|
|
590
589
|
}),
|
|
591
590
|
/* @__PURE__ */ jsx(AnsiLine, {
|
|
592
591
|
line,
|
|
593
|
-
className: `min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-
|
|
592
|
+
className: `min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-sm leading-relaxed ${dimmed ? "text-po-dim" : "text-po-text"}`
|
|
594
593
|
})
|
|
595
594
|
]
|
|
596
595
|
});
|
|
@@ -604,7 +603,7 @@ function LogRow({ number, line, matched }) {
|
|
|
604
603
|
* rather than moving a cursor through it, and following pauses while one is
|
|
605
604
|
* set because a filtered view is a snapshot of a file that is still growing.
|
|
606
605
|
*/
|
|
607
|
-
function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
606
|
+
function RunnerLogPanel({ sessionId, runId, sendSessionFrame, onTerminal }) {
|
|
608
607
|
const { runs, stopRequested } = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
609
608
|
const run = runs.find((candidate) => candidate.id === runId);
|
|
610
609
|
const [now, setNow] = useState(() => Date.now());
|
|
@@ -624,7 +623,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
624
623
|
const filtering = query !== "";
|
|
625
624
|
const live = isFollowingLive(following, filtering, slice?.running === true);
|
|
626
625
|
useEffect(() => {
|
|
627
|
-
const timer = setInterval(() => setNow(Date.now()), TICK_MS$
|
|
626
|
+
const timer = setInterval(() => setNow(Date.now()), TICK_MS$1);
|
|
628
627
|
return () => clearInterval(timer);
|
|
629
628
|
}, []);
|
|
630
629
|
useEffect(() => {
|
|
@@ -703,7 +702,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
703
702
|
if (element === null) return;
|
|
704
703
|
setPinned(element.scrollHeight - element.scrollTop - element.clientHeight <= NEAR_BOTTOM_PX);
|
|
705
704
|
};
|
|
706
|
-
const badge =
|
|
705
|
+
const badge = runnerBadge(run);
|
|
707
706
|
const stopping = run !== void 0 && stopRequested.includes(run.id);
|
|
708
707
|
const elapsed = run === void 0 ? "" : formatRunnerUptime(run.startedAt, run.exit ? Date.parse(run.exit.finishedAt) : now);
|
|
709
708
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -720,6 +719,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
720
719
|
}),
|
|
721
720
|
/* @__PURE__ */ jsx(StatusBadge, {
|
|
722
721
|
tone: badge.tone,
|
|
722
|
+
size: "md",
|
|
723
723
|
"data-testid": "runner-log-state",
|
|
724
724
|
children: badge.label
|
|
725
725
|
}),
|
|
@@ -735,60 +735,71 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
735
735
|
children: "no longer listed; its log stays readable"
|
|
736
736
|
}),
|
|
737
737
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
738
|
+
onTerminal ? /* @__PURE__ */ jsx(Button, {
|
|
739
|
+
variant: "outline",
|
|
740
|
+
size: "sm",
|
|
741
|
+
"data-testid": "runner-log-terminal",
|
|
742
|
+
onClick: onTerminal,
|
|
743
|
+
children: "Back to terminal"
|
|
744
|
+
}) : null,
|
|
738
745
|
run !== void 0 && run.state === "running" && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
|
|
739
746
|
variant: stopping ? "outline" : "danger-outline",
|
|
740
|
-
size: "
|
|
747
|
+
size: "sm",
|
|
741
748
|
"data-testid": "runner-log-stop",
|
|
742
749
|
disabled: stopping,
|
|
743
750
|
title: stopping ? "stop requested; the runner reports its own exit" : "ask the runtime to stop this runner",
|
|
744
751
|
onClick: () => requestRunnerStop(sendSessionFrame, sessionId, run.id),
|
|
745
|
-
|
|
746
|
-
children: stopping ? "stopping…" : "stop"
|
|
752
|
+
children: stopping ? "Stopping…" : "Stop"
|
|
747
753
|
}) : null
|
|
748
754
|
]
|
|
749
755
|
}),
|
|
750
|
-
run ? /* @__PURE__ */
|
|
751
|
-
className: "
|
|
752
|
-
children:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
756
|
+
run ? /* @__PURE__ */ jsx("div", {
|
|
757
|
+
className: "shrink-0 border-b border-po-border-soft px-3 py-3 sm:px-[26px]",
|
|
758
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
759
|
+
className: "flex flex-col gap-1 rounded-md bg-po-deep px-3 py-2",
|
|
760
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
761
|
+
className: "break-all font-mono text-base text-po-hi",
|
|
762
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
763
|
+
"aria-hidden": true,
|
|
764
|
+
className: "select-none text-po-faint",
|
|
765
|
+
children: ["$", " "]
|
|
766
|
+
}), run.command]
|
|
767
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
768
|
+
className: "break-all font-mono text-sm text-po-dim",
|
|
769
|
+
children: ["in ", run.cwd]
|
|
770
|
+
})]
|
|
771
|
+
})
|
|
759
772
|
}) : null,
|
|
760
773
|
/* @__PURE__ */ jsxs("div", {
|
|
761
|
-
className: "flex min-h-
|
|
774
|
+
className: "flex min-h-11 shrink-0 flex-wrap items-center gap-2 border-b border-po-border-soft px-3 py-2 sm:flex-nowrap sm:gap-2.5 sm:px-[26px]",
|
|
762
775
|
children: [
|
|
763
776
|
/* @__PURE__ */ jsxs("span", {
|
|
764
|
-
className: "relative flex min-w-0 flex-1 items-center sm:w-
|
|
765
|
-
children: [/* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute left-2.5 h-3 w-3 text-po-faint" }), /* @__PURE__ */ jsx(Input, {
|
|
766
|
-
size: "
|
|
777
|
+
className: "relative flex min-w-0 flex-1 items-center sm:w-72 sm:flex-none",
|
|
778
|
+
children: [/* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute left-2.5 h-3.5 w-3.5 text-po-faint" }), /* @__PURE__ */ jsx(Input, {
|
|
779
|
+
size: "md",
|
|
767
780
|
"data-testid": "runner-log-search",
|
|
768
781
|
value: typed,
|
|
769
|
-
placeholder: "
|
|
782
|
+
placeholder: "Search output",
|
|
770
783
|
onChange: (event) => setTyped(event.target.value),
|
|
771
|
-
className: "pl-
|
|
784
|
+
className: "w-full pl-8"
|
|
772
785
|
})]
|
|
773
786
|
}),
|
|
774
787
|
/* @__PURE__ */ jsx(Button, {
|
|
775
788
|
variant: ignoreCase ? "subtle" : "outline",
|
|
776
|
-
size: "
|
|
789
|
+
size: "sm",
|
|
777
790
|
"data-testid": "runner-log-ignore-case",
|
|
778
791
|
"aria-pressed": ignoreCase,
|
|
779
792
|
title: "match regardless of case",
|
|
780
793
|
onClick: () => setIgnoreCase((current) => !current),
|
|
781
|
-
|
|
782
|
-
children: "aA"
|
|
794
|
+
children: ignoreCase ? "Any case" : "Match case"
|
|
783
795
|
}),
|
|
784
796
|
/* @__PURE__ */ jsx(Button, {
|
|
785
797
|
variant: context > 0 && filtering ? "subtle" : "outline",
|
|
786
|
-
size: "
|
|
798
|
+
size: "sm",
|
|
787
799
|
"data-testid": "runner-log-context",
|
|
788
800
|
disabled: !filtering,
|
|
789
801
|
title: "lines of context kept either side of a match",
|
|
790
802
|
onClick: () => setContext((current) => current === 0 ? CONTEXT_LINES : 0),
|
|
791
|
-
className: "px-2 ",
|
|
792
803
|
children: `± ${String(context)} lines`
|
|
793
804
|
}),
|
|
794
805
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
@@ -799,14 +810,13 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
799
810
|
}),
|
|
800
811
|
/* @__PURE__ */ jsx(Button, {
|
|
801
812
|
variant: live ? "subtle" : "outline",
|
|
802
|
-
size: "
|
|
813
|
+
size: "sm",
|
|
803
814
|
"data-testid": "runner-log-follow",
|
|
804
815
|
"aria-pressed": live,
|
|
805
816
|
disabled: filtering || slice?.running !== true,
|
|
806
817
|
title: filtering ? "a filtered view is a snapshot; clear the query to follow" : "follow the log as it grows",
|
|
807
818
|
onClick: () => setFollowing((current) => !current),
|
|
808
|
-
|
|
809
|
-
children: filtering ? "follow · paused" : "follow"
|
|
819
|
+
children: filtering ? "Paused while searching" : live ? "Following" : "Follow"
|
|
810
820
|
})
|
|
811
821
|
]
|
|
812
822
|
}),
|
|
@@ -819,7 +829,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
819
829
|
className: "h-full overflow-auto bg-po-deep px-3 py-3 sm:px-[26px]",
|
|
820
830
|
children: [error !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
821
831
|
"data-testid": "runner-log-error",
|
|
822
|
-
className: "text-
|
|
832
|
+
className: "text-sm text-po-red",
|
|
823
833
|
children: error
|
|
824
834
|
}) : slice === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
825
835
|
"data-testid": "runner-log-reading",
|
|
@@ -827,10 +837,10 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
827
837
|
children: "reading…"
|
|
828
838
|
}) : slice.exists === false ? /* @__PURE__ */ jsx("p", {
|
|
829
839
|
className: "text-sm text-po-dim",
|
|
830
|
-
children: "
|
|
840
|
+
children: "This runner has not printed anything yet."
|
|
831
841
|
}) : view.lines.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
832
842
|
className: "text-sm text-po-dim",
|
|
833
|
-
children: filtering ? "
|
|
843
|
+
children: filtering ? "Nothing matched." : "The log is empty."
|
|
834
844
|
}) : view.lines.map((line, index) => {
|
|
835
845
|
const number = numbers[index];
|
|
836
846
|
const previous = numbers[index - 1];
|
|
@@ -838,25 +848,26 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
838
848
|
return /* @__PURE__ */ jsxs(Fragment, { children: [gap ? /* @__PURE__ */ jsx("div", {
|
|
839
849
|
"data-testid": "runner-log-gap",
|
|
840
850
|
"aria-hidden": true,
|
|
841
|
-
className: "select-none py-0.5 pl-
|
|
851
|
+
className: "select-none py-0.5 pl-14 text-sm leading-relaxed text-po-faint",
|
|
842
852
|
children: "⋯"
|
|
843
853
|
}) : null, /* @__PURE__ */ jsx(LogRow, {
|
|
844
854
|
number,
|
|
845
855
|
line,
|
|
846
|
-
matched: filtering && isMatch(line, query, ignoreCase)
|
|
856
|
+
matched: filtering && isMatch(line, query, ignoreCase),
|
|
857
|
+
dimmed: filtering && !isMatch(line, query, ignoreCase)
|
|
847
858
|
})] }, number ?? `row-${String(index)}`);
|
|
848
859
|
}), /* @__PURE__ */ jsx("div", { ref: bottom })]
|
|
849
860
|
}), live && !pinned ? /* @__PURE__ */ jsx(Button, {
|
|
850
861
|
variant: "subtle",
|
|
851
|
-
size: "
|
|
862
|
+
size: "sm",
|
|
852
863
|
"data-testid": "runner-log-jump",
|
|
853
864
|
title: "scroll back to the newest lines and keep following",
|
|
854
865
|
onClick: () => {
|
|
855
866
|
setPinned(true);
|
|
856
867
|
bottom.current?.scrollIntoView({ block: "end" });
|
|
857
868
|
},
|
|
858
|
-
className: "absolute bottom-3 right-3
|
|
859
|
-
children: "
|
|
869
|
+
className: "absolute bottom-3 right-3",
|
|
870
|
+
children: "Jump to latest"
|
|
860
871
|
}) : null]
|
|
861
872
|
}),
|
|
862
873
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -955,21 +966,8 @@ async function sendRunnerInput(sessionId, runId, text) {
|
|
|
955
966
|
}
|
|
956
967
|
//#endregion
|
|
957
968
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnerShellPanel.tsx
|
|
958
|
-
const TAB_ID_PREFIX = "runner-shell-";
|
|
959
969
|
/** Keystrokes are gathered for this long and sent as one request. */
|
|
960
970
|
const FLUSH_MS = 30;
|
|
961
|
-
/** The temporary tab holding one interactive runner's pane. */
|
|
962
|
-
function runnerShellTab(run) {
|
|
963
|
-
const runId = run.id;
|
|
964
|
-
return {
|
|
965
|
-
id: `${TAB_ID_PREFIX}${runId}`,
|
|
966
|
-
label: `shell · ${run.name}`,
|
|
967
|
-
panel: (props) => /* @__PURE__ */ jsx(RunnerShellPanel, {
|
|
968
|
-
...props,
|
|
969
|
-
runId
|
|
970
|
-
})
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
971
|
/**
|
|
974
972
|
* An interactive runner's pane, attached.
|
|
975
973
|
*
|
|
@@ -981,9 +979,13 @@ function runnerShellTab(run) {
|
|
|
981
979
|
*
|
|
982
980
|
* xterm is loaded when the tab opens rather than with the plugin. Most sessions
|
|
983
981
|
* never attach to anything, and an emulator is not a small thing to carry.
|
|
982
|
+
*
|
|
983
|
+
* Search reads the scrubbed log the sink writes beside the raw bytes, so it
|
|
984
|
+
* hands over to the log view: `onSearch` when the panel is embedded, the
|
|
985
|
+
* runner's log tab otherwise.
|
|
984
986
|
*/
|
|
985
|
-
function RunnerShellPanel({ sessionId, runId }) {
|
|
986
|
-
const { runs } = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
987
|
+
function RunnerShellPanel({ sessionId, runId, sendSessionFrame, openTransientTab, onSearch }) {
|
|
988
|
+
const { runs, stopRequested } = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
987
989
|
const run = runs.find((candidate) => candidate.id === runId);
|
|
988
990
|
const [ended, setEnded] = useState(false);
|
|
989
991
|
const [lost, setLost] = useState(false);
|
|
@@ -1021,12 +1023,13 @@ function RunnerShellPanel({ sessionId, runId }) {
|
|
|
1021
1023
|
}, FLUSH_MS);
|
|
1022
1024
|
};
|
|
1023
1025
|
const live = run?.state === "running" && !ended;
|
|
1026
|
+
const stopping = stopRequested.includes(runId);
|
|
1024
1027
|
return /* @__PURE__ */ jsxs("div", {
|
|
1025
1028
|
"data-testid": "runner-shell-panel",
|
|
1026
1029
|
className: "flex min-h-0 flex-1 flex-col",
|
|
1027
1030
|
children: [
|
|
1028
1031
|
/* @__PURE__ */ jsxs("div", {
|
|
1029
|
-
className: "flex min-h-11 shrink-0 items-center gap-2
|
|
1032
|
+
className: "flex min-h-11 shrink-0 flex-wrap items-center gap-2 border-b border-po-border-soft px-3 py-2 sm:flex-nowrap sm:gap-2.5 sm:px-[26px] sm:py-1.5",
|
|
1030
1033
|
children: [
|
|
1031
1034
|
/* @__PURE__ */ jsx("span", {
|
|
1032
1035
|
"data-testid": "runner-shell-name",
|
|
@@ -1035,23 +1038,45 @@ function RunnerShellPanel({ sessionId, runId }) {
|
|
|
1035
1038
|
}),
|
|
1036
1039
|
/* @__PURE__ */ jsx(StatusBadge, {
|
|
1037
1040
|
tone: live ? "running" : "neutral",
|
|
1041
|
+
size: "md",
|
|
1038
1042
|
"data-testid": "runner-shell-state",
|
|
1039
|
-
children: live ? "
|
|
1043
|
+
children: live ? "Running" : "Ended"
|
|
1040
1044
|
}),
|
|
1045
|
+
run ? /* @__PURE__ */ jsx("span", {
|
|
1046
|
+
className: "min-w-0 truncate font-mono text-sm text-po-dim",
|
|
1047
|
+
children: run.command
|
|
1048
|
+
}) : null,
|
|
1041
1049
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
1042
1050
|
lost ? /* @__PURE__ */ jsx("span", {
|
|
1043
1051
|
"data-testid": "runner-shell-lost",
|
|
1044
|
-
className: "shrink-0 text-
|
|
1045
|
-
children: "
|
|
1052
|
+
className: "shrink-0 text-sm text-po-red",
|
|
1053
|
+
children: "The connection to this terminal dropped. Reopen the runner to attach again."
|
|
1054
|
+
}) : null,
|
|
1055
|
+
run ? /* @__PURE__ */ jsxs(Button, {
|
|
1056
|
+
variant: "outline",
|
|
1057
|
+
size: "sm",
|
|
1058
|
+
"data-testid": "runner-shell-search",
|
|
1059
|
+
title: "search everything this runner has printed",
|
|
1060
|
+
onClick: () => onSearch ? onSearch() : openTransientTab(runnerLogTab(run)),
|
|
1061
|
+
className: "gap-1.5",
|
|
1062
|
+
children: [/* @__PURE__ */ jsx(SearchIcon, { className: "h-3.5 w-3.5" }), "Search output"]
|
|
1046
1063
|
}) : null,
|
|
1047
1064
|
live ? /* @__PURE__ */ jsx(Button, {
|
|
1048
1065
|
variant: "outline",
|
|
1049
|
-
size: "
|
|
1066
|
+
size: "sm",
|
|
1050
1067
|
"data-testid": "runner-shell-interrupt",
|
|
1051
1068
|
title: "send ctrl-c",
|
|
1052
1069
|
onClick: () => send(""),
|
|
1053
|
-
|
|
1054
|
-
|
|
1070
|
+
children: "Ctrl-C"
|
|
1071
|
+
}) : null,
|
|
1072
|
+
live && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
|
|
1073
|
+
variant: stopping ? "outline" : "danger-outline",
|
|
1074
|
+
size: "sm",
|
|
1075
|
+
"data-testid": "runner-shell-stop",
|
|
1076
|
+
disabled: stopping,
|
|
1077
|
+
title: stopping ? "stop requested; the runner reports its own exit" : "ask the runtime to stop this runner",
|
|
1078
|
+
onClick: () => requestRunnerStop(sendSessionFrame, sessionId, runId),
|
|
1079
|
+
children: stopping ? "Stopping…" : "Stop"
|
|
1055
1080
|
}) : null
|
|
1056
1081
|
]
|
|
1057
1082
|
}),
|
|
@@ -1072,13 +1097,153 @@ function RunnerShellPanel({ sessionId, runId }) {
|
|
|
1072
1097
|
className: "flex h-8 shrink-0 items-center border-t border-po-border-soft px-3 sm:px-[26px]",
|
|
1073
1098
|
children: /* @__PURE__ */ jsx("span", {
|
|
1074
1099
|
className: "min-w-0 flex-1 truncate text-sm text-po-dim",
|
|
1075
|
-
children: live ?
|
|
1100
|
+
children: live ? `Typing goes straight to ${run?.name ?? "the runner"}. Ctrl-C stops what is running.` : "This runner has ended. Its output stays searchable."
|
|
1076
1101
|
})
|
|
1077
1102
|
})
|
|
1078
1103
|
]
|
|
1079
1104
|
});
|
|
1080
1105
|
}
|
|
1081
1106
|
//#endregion
|
|
1107
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnersPanel.tsx
|
|
1108
|
+
const LAUNCH_TIMEOUT_MS = 3e4;
|
|
1109
|
+
const RUNNERS_TAB_ID = "runner-runs";
|
|
1110
|
+
/** The temporary tab the dock's runners group opens from its own name. */
|
|
1111
|
+
function runnersTab() {
|
|
1112
|
+
return {
|
|
1113
|
+
id: RUNNERS_TAB_ID,
|
|
1114
|
+
label: "runners",
|
|
1115
|
+
panel: RunnersPanel
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Everything this session has up, and the one place to start another.
|
|
1120
|
+
*
|
|
1121
|
+
* A runner is a shell: the bar takes a command and runs it on its own
|
|
1122
|
+
* terminal, or opens a plain shell when nothing was typed. The selected
|
|
1123
|
+
* runner is shown in place, as its terminal when it has one and as its log
|
|
1124
|
+
* when it does not (a command the agent backgrounded has no pane).
|
|
1125
|
+
*
|
|
1126
|
+
* Running runners only, the same rule the rail follows. A finished run leaves
|
|
1127
|
+
* the moment it exits; its log stays readable from the tool message that
|
|
1128
|
+
* started it, and `po-runner list` is where history lives.
|
|
1129
|
+
*/
|
|
1130
|
+
function RunnersPanel(props) {
|
|
1131
|
+
const { sessionId, sendSessionFrame } = props;
|
|
1132
|
+
const session = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
1133
|
+
const running = session.runs.filter((run) => run.state === "running");
|
|
1134
|
+
const [command, setCommand] = useState("");
|
|
1135
|
+
const [cwd, setCwd] = useState("");
|
|
1136
|
+
const [selectedId, setSelectedId] = useState(void 0);
|
|
1137
|
+
/** The terminal runner whose log is being searched instead of shown live. */
|
|
1138
|
+
const [searchingId, setSearchingId] = useState(void 0);
|
|
1139
|
+
const launchBaseline = useRef(null);
|
|
1140
|
+
/** What the pending launch ran, put back if the start fails. */
|
|
1141
|
+
const launchedCommand = useRef("");
|
|
1142
|
+
const launchTimeout = useRef(void 0);
|
|
1143
|
+
const selected = running.find((run) => run.id === selectedId) ?? running[0];
|
|
1144
|
+
const launch = () => {
|
|
1145
|
+
if (sessionId === null) return;
|
|
1146
|
+
launchBaseline.current = new Set(session.runs.map((run) => run.id));
|
|
1147
|
+
clearTimeout(launchTimeout.current);
|
|
1148
|
+
launchTimeout.current = setTimeout(() => {
|
|
1149
|
+
launchBaseline.current = null;
|
|
1150
|
+
}, LAUNCH_TIMEOUT_MS);
|
|
1151
|
+
requestRunnerStart(sendSessionFrame, sessionId, runnerRunRequest(command, cwd));
|
|
1152
|
+
launchedCommand.current = command;
|
|
1153
|
+
setCommand("");
|
|
1154
|
+
};
|
|
1155
|
+
useEffect(() => {
|
|
1156
|
+
const baseline = launchBaseline.current;
|
|
1157
|
+
if (baseline === null) return;
|
|
1158
|
+
const started = session.runs.find((run) => run.state === "running" && !baseline.has(run.id));
|
|
1159
|
+
if (!started) return;
|
|
1160
|
+
launchBaseline.current = null;
|
|
1161
|
+
clearTimeout(launchTimeout.current);
|
|
1162
|
+
setSelectedId(started.id);
|
|
1163
|
+
}, [session.runs]);
|
|
1164
|
+
const failureId = session.startFailure?.id;
|
|
1165
|
+
useEffect(() => {
|
|
1166
|
+
if (failureId === void 0 || launchBaseline.current === null) return;
|
|
1167
|
+
launchBaseline.current = null;
|
|
1168
|
+
clearTimeout(launchTimeout.current);
|
|
1169
|
+
setCommand(launchedCommand.current);
|
|
1170
|
+
}, [failureId]);
|
|
1171
|
+
useEffect(() => () => clearTimeout(launchTimeout.current), []);
|
|
1172
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1173
|
+
"data-testid": "runners-panel",
|
|
1174
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
1175
|
+
children: [
|
|
1176
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1177
|
+
className: "flex h-11 shrink-0 items-center gap-2.5 border-b border-po-border-soft px-3 sm:px-[26px]",
|
|
1178
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1179
|
+
className: "text-lg font-semibold text-po-hi",
|
|
1180
|
+
children: "Runners"
|
|
1181
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1182
|
+
"data-testid": "runners-panel-count",
|
|
1183
|
+
className: "text-sm text-po-dim",
|
|
1184
|
+
children: running.length === 0 ? "Nothing running" : `${String(running.length)} running`
|
|
1185
|
+
})]
|
|
1186
|
+
}),
|
|
1187
|
+
sessionId === null ? null : /* @__PURE__ */ jsx(RunnerCommandBar, {
|
|
1188
|
+
command,
|
|
1189
|
+
onCommandChange: setCommand,
|
|
1190
|
+
cwd,
|
|
1191
|
+
onCwdChange: setCwd,
|
|
1192
|
+
folders: running.map((run) => run.cwd),
|
|
1193
|
+
...session.sessionDirectory === void 0 ? {} : { sessionDirectory: session.sessionDirectory },
|
|
1194
|
+
...session.startFailure === void 0 ? {} : { problem: session.startFailure.message },
|
|
1195
|
+
onLaunch: launch
|
|
1196
|
+
}),
|
|
1197
|
+
selected === void 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
1198
|
+
"data-testid": "runners-panel-empty",
|
|
1199
|
+
className: "px-4 py-8",
|
|
1200
|
+
title: "Nothing running yet",
|
|
1201
|
+
description: sessionId === null ? "Open a session to start a runner." : "Type a command above, like pnpm dev, and it opens here as a live terminal."
|
|
1202
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1203
|
+
"data-testid": "runners-panel-picker",
|
|
1204
|
+
className: "flex shrink-0 flex-wrap gap-1.5 px-3 pt-3 sm:px-[26px]",
|
|
1205
|
+
children: running.map((run) => /* @__PURE__ */ jsxs(Button, {
|
|
1206
|
+
variant: run === selected ? "subtle" : "ghost",
|
|
1207
|
+
size: "sm",
|
|
1208
|
+
"data-testid": `runners-panel-pick-${run.id}`,
|
|
1209
|
+
"aria-pressed": run === selected,
|
|
1210
|
+
title: run.command,
|
|
1211
|
+
onClick: () => setSelectedId(run.id),
|
|
1212
|
+
className: `gap-1.5 ${run === selected ? "text-po-hi" : ""}`,
|
|
1213
|
+
children: [/* @__PURE__ */ jsx(Dot, {
|
|
1214
|
+
tone: "yellow",
|
|
1215
|
+
pulse: true
|
|
1216
|
+
}), run.name]
|
|
1217
|
+
}, run.id))
|
|
1218
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1219
|
+
className: "mt-3 flex min-h-0 flex-1 flex-col border-t border-po-border-soft",
|
|
1220
|
+
children: selected.interactive && searchingId !== selected.id ? /* @__PURE__ */ jsx(RunnerShellPanel, {
|
|
1221
|
+
...props,
|
|
1222
|
+
runId: selected.id,
|
|
1223
|
+
onSearch: () => setSearchingId(selected.id)
|
|
1224
|
+
}, selected.id) : /* @__PURE__ */ jsx(RunnerLogPanel, {
|
|
1225
|
+
...props,
|
|
1226
|
+
runId: selected.id,
|
|
1227
|
+
...selected.interactive ? { onTerminal: () => setSearchingId(void 0) } : {}
|
|
1228
|
+
}, selected.id)
|
|
1229
|
+
})] })
|
|
1230
|
+
]
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
//#endregion
|
|
1234
|
+
//#region src/extensions/workspaces/sessions/(frontend)/activity-group/runners.web.ts
|
|
1235
|
+
var runners_web_default = defineActivityGroup({
|
|
1236
|
+
keys: "r l",
|
|
1237
|
+
icon: PlayCircleIcon,
|
|
1238
|
+
statusKey: "po-runner-runners",
|
|
1239
|
+
activeSource: runnerActivitySource,
|
|
1240
|
+
transientTab: runnersTab,
|
|
1241
|
+
order: 20
|
|
1242
|
+
});
|
|
1243
|
+
//#endregion
|
|
1244
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/runner-runs.web.ts
|
|
1245
|
+
var runner_runs_web_default = defineChannelFile(runnerRunsChannel);
|
|
1246
|
+
//#endregion
|
|
1082
1247
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/runnerTail.ts
|
|
1083
1248
|
/**
|
|
1084
1249
|
* The newest line a running runner has written, for a row that has one line to
|
|
@@ -1154,229 +1319,6 @@ function useRunnerTail(sessionId, runId, running) {
|
|
|
1154
1319
|
return running && tail?.runId === runId ? tail.text : void 0;
|
|
1155
1320
|
}
|
|
1156
1321
|
//#endregion
|
|
1157
|
-
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnersPanel.tsx
|
|
1158
|
-
const TICK_MS$1 = 1e4;
|
|
1159
|
-
const SHELL_LAUNCH_TIMEOUT_MS = 3e4;
|
|
1160
|
-
const RUNNERS_TAB_ID = "runner-runs";
|
|
1161
|
-
/** The temporary tab the dock's runners group opens from its own name. */
|
|
1162
|
-
function runnersTab() {
|
|
1163
|
-
return {
|
|
1164
|
-
id: RUNNERS_TAB_ID,
|
|
1165
|
-
label: "runners",
|
|
1166
|
-
panel: RunnersPanel
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Everything this session has up, one card each.
|
|
1171
|
-
*
|
|
1172
|
-
* The dock's rail gives a runner one line, which is enough to notice it and
|
|
1173
|
-
* not enough to work with it. This is the same set with room: the command in
|
|
1174
|
-
* full, where it runs, and the last thing it said.
|
|
1175
|
-
*
|
|
1176
|
-
* Running runners only, the same rule the rail follows. A finished run leaves
|
|
1177
|
-
* the moment it exits, because this answers "what is happening"; its log stays
|
|
1178
|
-
* readable from its own tab, and `po-runner list` is where history lives.
|
|
1179
|
-
*/
|
|
1180
|
-
function RunnersPanel({ sessionId, sendSessionFrame, openTransientTab }) {
|
|
1181
|
-
const session = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
1182
|
-
const running = session.runs.filter((run) => run.state === "running");
|
|
1183
|
-
const [now, setNow] = useState(() => Date.now());
|
|
1184
|
-
const [launching, setLaunching] = useState(false);
|
|
1185
|
-
const shellBaseline = useRef(null);
|
|
1186
|
-
const shellLaunchTimeout = useRef(void 0);
|
|
1187
|
-
const defaultCwd = running[0]?.cwd;
|
|
1188
|
-
const startShell = () => {
|
|
1189
|
-
if (sessionId === null) return;
|
|
1190
|
-
shellBaseline.current = new Set(session.runs.map((run) => run.id));
|
|
1191
|
-
clearTimeout(shellLaunchTimeout.current);
|
|
1192
|
-
shellLaunchTimeout.current = setTimeout(() => {
|
|
1193
|
-
shellBaseline.current = null;
|
|
1194
|
-
}, SHELL_LAUNCH_TIMEOUT_MS);
|
|
1195
|
-
requestRunnerStart(sendSessionFrame, sessionId, RUNNER_SHELL_REQUEST);
|
|
1196
|
-
};
|
|
1197
|
-
useEffect(() => {
|
|
1198
|
-
const timer = setInterval(() => setNow(Date.now()), TICK_MS$1);
|
|
1199
|
-
return () => clearInterval(timer);
|
|
1200
|
-
}, []);
|
|
1201
|
-
useEffect(() => {
|
|
1202
|
-
const baseline = shellBaseline.current;
|
|
1203
|
-
if (baseline === null) return;
|
|
1204
|
-
const shell = session.runs.find((run) => run.state === "running" && run.interactive && !baseline.has(run.id));
|
|
1205
|
-
if (!shell) return;
|
|
1206
|
-
shellBaseline.current = null;
|
|
1207
|
-
clearTimeout(shellLaunchTimeout.current);
|
|
1208
|
-
openTransientTab(runnerShellTab(shell));
|
|
1209
|
-
}, [openTransientTab, session.runs]);
|
|
1210
|
-
useEffect(() => () => clearTimeout(shellLaunchTimeout.current), []);
|
|
1211
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1212
|
-
"data-testid": "runners-panel",
|
|
1213
|
-
className: "flex min-h-0 flex-1 flex-col overflow-auto",
|
|
1214
|
-
children: [
|
|
1215
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1216
|
-
className: "flex min-h-11 shrink-0 items-center gap-2.5 border-b border-po-border-soft px-3 sm:h-11 sm:px-[26px]",
|
|
1217
|
-
children: [
|
|
1218
|
-
/* @__PURE__ */ jsx("span", {
|
|
1219
|
-
className: "text-lg font-semibold text-po-hi",
|
|
1220
|
-
children: "runners"
|
|
1221
|
-
}),
|
|
1222
|
-
/* @__PURE__ */ jsx("span", {
|
|
1223
|
-
"data-testid": "runners-panel-count",
|
|
1224
|
-
className: "text-sm text-po-dim",
|
|
1225
|
-
children: running.length === 0 ? "nothing running" : `${String(running.length)} running`
|
|
1226
|
-
}),
|
|
1227
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
1228
|
-
sessionId === null ? null : /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
1229
|
-
asChild: true,
|
|
1230
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
1231
|
-
variant: "ghost",
|
|
1232
|
-
size: "icon",
|
|
1233
|
-
"data-testid": "runners-panel-launch",
|
|
1234
|
-
title: "launch a runner",
|
|
1235
|
-
"aria-label": "launch a runner",
|
|
1236
|
-
className: "text-po-faint hover:text-po-hi",
|
|
1237
|
-
children: /* @__PURE__ */ jsx(PlusIcon, { className: "h-3 w-3" })
|
|
1238
|
-
})
|
|
1239
|
-
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
1240
|
-
align: "end",
|
|
1241
|
-
"data-testid": "runners-panel-launch-menu",
|
|
1242
|
-
children: [/* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
1243
|
-
"data-testid": "runners-panel-start-shell",
|
|
1244
|
-
onSelect: startShell,
|
|
1245
|
-
children: "start shell"
|
|
1246
|
-
}), /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
1247
|
-
"data-testid": "runners-panel-run-command",
|
|
1248
|
-
onSelect: () => setLaunching(true),
|
|
1249
|
-
children: "run command"
|
|
1250
|
-
})]
|
|
1251
|
-
})] })
|
|
1252
|
-
]
|
|
1253
|
-
}),
|
|
1254
|
-
running.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
1255
|
-
"data-testid": "runners-panel-empty",
|
|
1256
|
-
className: "px-4 py-8",
|
|
1257
|
-
title: "nothing running",
|
|
1258
|
-
description: "a command that outlives its turn moves to a runner and appears here while it works.",
|
|
1259
|
-
children: sessionId === null ? null : /* @__PURE__ */ jsx(Button, {
|
|
1260
|
-
variant: "outline",
|
|
1261
|
-
size: "xs",
|
|
1262
|
-
"data-testid": "runners-panel-launch-empty",
|
|
1263
|
-
onClick: () => setLaunching(true),
|
|
1264
|
-
className: "px-2 ",
|
|
1265
|
-
children: "launch a runner"
|
|
1266
|
-
})
|
|
1267
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
1268
|
-
"data-testid": "runners-panel-grid",
|
|
1269
|
-
className: "grid grid-cols-1 gap-2.5 px-3 py-3 sm:grid-cols-2 sm:px-[26px] xl:grid-cols-3",
|
|
1270
|
-
children: running.map((run) => /* @__PURE__ */ jsx(RunnerCard, {
|
|
1271
|
-
run,
|
|
1272
|
-
now,
|
|
1273
|
-
stopRequested: session.stopRequested.includes(run.id),
|
|
1274
|
-
sessionId,
|
|
1275
|
-
sendSessionFrame,
|
|
1276
|
-
openTransientTab
|
|
1277
|
-
}, run.id))
|
|
1278
|
-
}),
|
|
1279
|
-
launching && sessionId !== null ? /* @__PURE__ */ jsx(LaunchRunnerDialog, {
|
|
1280
|
-
sessionId,
|
|
1281
|
-
sendSessionFrame,
|
|
1282
|
-
...defaultCwd === void 0 ? {} : { defaultCwd },
|
|
1283
|
-
onClose: () => setLaunching(false)
|
|
1284
|
-
}) : null
|
|
1285
|
-
]
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
|
-
* One running runner. Its own component because the tail line is a
|
|
1290
|
-
* subscription per card, and a card that leaves the grid closes it.
|
|
1291
|
-
*/
|
|
1292
|
-
function RunnerCard({ run, now, stopRequested, sessionId, sendSessionFrame, openTransientTab }) {
|
|
1293
|
-
const tail = useRunnerTail(sessionId, run.id, run.state === "running");
|
|
1294
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1295
|
-
"data-testid": `runners-card-${run.id}`,
|
|
1296
|
-
className: "flex min-w-0 flex-col gap-1.5 rounded-md border border-po-border-soft bg-po-panel p-2.5",
|
|
1297
|
-
children: [
|
|
1298
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1299
|
-
className: "flex min-w-0 items-center gap-1.5",
|
|
1300
|
-
children: [
|
|
1301
|
-
/* @__PURE__ */ jsx(Dot, {
|
|
1302
|
-
tone: "yellow",
|
|
1303
|
-
pulse: true
|
|
1304
|
-
}),
|
|
1305
|
-
/* @__PURE__ */ jsx("span", {
|
|
1306
|
-
className: "min-w-0 flex-1 truncate text-base font-semibold text-po-hi",
|
|
1307
|
-
children: run.name
|
|
1308
|
-
}),
|
|
1309
|
-
/* @__PURE__ */ jsxs("span", {
|
|
1310
|
-
className: "shrink-0 text-sm text-po-dim",
|
|
1311
|
-
children: [formatRunnerUptime(run.startedAt, now), run.interactive ? " · tty" : ""]
|
|
1312
|
-
})
|
|
1313
|
-
]
|
|
1314
|
-
}),
|
|
1315
|
-
/* @__PURE__ */ jsx("span", {
|
|
1316
|
-
className: "min-w-0 break-all font-mono text-sm leading-normal text-po-dim",
|
|
1317
|
-
children: run.command
|
|
1318
|
-
}),
|
|
1319
|
-
/* @__PURE__ */ jsx("span", {
|
|
1320
|
-
className: "min-w-0 truncate font-mono text-sm text-po-dim",
|
|
1321
|
-
children: run.cwd
|
|
1322
|
-
}),
|
|
1323
|
-
/* @__PURE__ */ jsx("span", {
|
|
1324
|
-
"data-testid": `runners-card-detail-${run.id}`,
|
|
1325
|
-
"data-detail": tail === void 0 ? "command" : "tail",
|
|
1326
|
-
className: "min-w-0 truncate text-sm text-po-dim",
|
|
1327
|
-
children: tail ?? "…"
|
|
1328
|
-
}),
|
|
1329
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1330
|
-
className: "flex items-center gap-1.5 pt-0.5",
|
|
1331
|
-
children: [
|
|
1332
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1333
|
-
variant: "outline",
|
|
1334
|
-
size: "xs",
|
|
1335
|
-
"data-testid": `runners-card-log-${run.id}`,
|
|
1336
|
-
title: "open this runner's log",
|
|
1337
|
-
onClick: () => openTransientTab(runnerLogTab(run)),
|
|
1338
|
-
className: "px-2 ",
|
|
1339
|
-
children: "log"
|
|
1340
|
-
}),
|
|
1341
|
-
run.interactive && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
|
|
1342
|
-
variant: "outline",
|
|
1343
|
-
size: "xs",
|
|
1344
|
-
"data-testid": `runners-card-shell-${run.id}`,
|
|
1345
|
-
title: "attach to this runner's terminal",
|
|
1346
|
-
onClick: () => openTransientTab(runnerShellTab(run)),
|
|
1347
|
-
className: "px-2 ",
|
|
1348
|
-
children: "shell"
|
|
1349
|
-
}) : null,
|
|
1350
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
1351
|
-
sessionId !== null ? /* @__PURE__ */ jsx(Button, {
|
|
1352
|
-
variant: stopRequested ? "outline" : "danger-outline",
|
|
1353
|
-
size: "xs",
|
|
1354
|
-
"data-testid": `runners-card-stop-${run.id}`,
|
|
1355
|
-
disabled: stopRequested,
|
|
1356
|
-
title: stopRequested ? "stop requested; the runner reports its own exit" : "ask the runtime to stop this runner",
|
|
1357
|
-
onClick: () => requestRunnerStop(sendSessionFrame, sessionId, run.id),
|
|
1358
|
-
className: "px-2 ",
|
|
1359
|
-
children: stopRequested ? "stopping…" : "stop"
|
|
1360
|
-
}) : null
|
|
1361
|
-
]
|
|
1362
|
-
})
|
|
1363
|
-
]
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
//#endregion
|
|
1367
|
-
//#region src/extensions/workspaces/sessions/(frontend)/activity-group/runners.web.ts
|
|
1368
|
-
var runners_web_default = defineActivityGroup({
|
|
1369
|
-
keys: "r l",
|
|
1370
|
-
icon: PlayCircleIcon,
|
|
1371
|
-
statusKey: "po-runner-runners",
|
|
1372
|
-
activeSource: runnerActivitySource,
|
|
1373
|
-
transientTab: runnersTab,
|
|
1374
|
-
order: 20
|
|
1375
|
-
});
|
|
1376
|
-
//#endregion
|
|
1377
|
-
//#region src/extensions/workspaces/sessions/(frontend)/channel/runner-runs.web.ts
|
|
1378
|
-
var runner_runs_web_default = defineChannelFile(runnerRunsChannel);
|
|
1379
|
-
//#endregion
|
|
1380
1322
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/RunnersActivitySection.tsx
|
|
1381
1323
|
const TICK_MS = 1e4;
|
|
1382
1324
|
/**
|