@agimon-ai/po-runner 0.0.0-alpha.21 → 0.0.0-alpha.22
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/{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/native.mjs +212 -203
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +2 -2
- package/dist/extensions/server.mjs +2 -2
- package/dist/extensions/web.mjs +334 -500
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/{headless-Dko_yBiy.cjs → headless-C4Bvl2B-.cjs} +8 -4
- package/dist/headless-C4Bvl2B-.cjs.map +1 -0
- package/dist/{headless-DOlCixyf.mjs → headless-DidUBSSf.mjs} +8 -4
- package/dist/headless-DidUBSSf.mjs.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-BzeXJk6V.d.mts → index-DJkjYgir.d.mts} +3 -3
- package/dist/{index-BzeXJk6V.d.mts.map → index-DJkjYgir.d.mts.map} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/responseEnvelope.d.mts +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-vkpJfxZZ.cjs} +2 -2
- package/dist/{runnerDependencies-DSun3xO-.cjs.map → runnerDependencies-vkpJfxZZ.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/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 { AnsiLine, AnsiText, ArrowRightIcon, Button, Dot, EmptyState, FileIcon, FolderIcon, Input, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, PlayCircleIcon, 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
|
-
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
5
|
import { sealedTransport } from "@agimon-ai/po-web-security/browser";
|
|
7
|
-
|
|
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
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
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";
|
|
@@ -174,141 +125,62 @@ const runnerRunsChannel = runners.channel({
|
|
|
174
125
|
}
|
|
175
126
|
});
|
|
176
127
|
//#endregion
|
|
177
|
-
//#region src/extensions/workspaces/sessions/(frontend)/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
128
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/format.ts
|
|
129
|
+
/**
|
|
130
|
+
* Local copy of the runtime's formatUptime (src/services/bashResult/index.ts):
|
|
131
|
+
* the web plugin may reach only src/types, so the one helper it needs lives here.
|
|
132
|
+
*/
|
|
133
|
+
function formatRunnerUptime(startedAt, now) {
|
|
134
|
+
const started = Date.parse(startedAt);
|
|
135
|
+
if (Number.isNaN(started)) return "unknown";
|
|
136
|
+
const seconds = Math.max(0, Math.floor((now - started) / 1e3));
|
|
137
|
+
if (seconds < 60) return `${seconds}s`;
|
|
138
|
+
const minutes = Math.floor(seconds / 60);
|
|
139
|
+
if (minutes < 60) return `${minutes}m`;
|
|
140
|
+
const hours = Math.floor(minutes / 60);
|
|
141
|
+
return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
|
|
185
142
|
}
|
|
186
143
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* session will parse.
|
|
144
|
+
* The lines a log view shows: the slice's text, then whatever the follow
|
|
145
|
+
* stream appended, bounded to the newest `max`.
|
|
190
146
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
147
|
+
* A log file ends with a newline, and the reader hands its text back with that
|
|
148
|
+
* newline intact, so a naive split reports one blank line more than the file
|
|
149
|
+
* has. Dropping exactly one trailing empty entry keeps the count the reader
|
|
150
|
+
* sees equal to the count the server reported.
|
|
194
151
|
*/
|
|
195
|
-
function
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const [
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
interactive
|
|
205
|
-
};
|
|
206
|
-
const problems = launchProblems(request);
|
|
207
|
-
const line = runnerLaunchLine(request);
|
|
208
|
-
const launch = () => {
|
|
209
|
-
if (problems.length > 0) return;
|
|
210
|
-
requestRunnerStart(sendSessionFrame, sessionId, request);
|
|
211
|
-
onClose();
|
|
152
|
+
function logViewLines(text, appended, max, options = {}) {
|
|
153
|
+
const split = text.split("\n");
|
|
154
|
+
if (split.length > 0 && split[split.length - 1] === "") split.pop();
|
|
155
|
+
if (options.dropPartialTail === true && split.length > 0) split.pop();
|
|
156
|
+
const all = [...split, ...appended];
|
|
157
|
+
const lines = all.slice(-max);
|
|
158
|
+
return {
|
|
159
|
+
lines,
|
|
160
|
+
hidden: all.length - lines.length
|
|
212
161
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}),
|
|
236
|
-
/* @__PURE__ */ jsxs("div", {
|
|
237
|
-
className: "flex flex-col gap-2.5 sm:flex-row",
|
|
238
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
239
|
-
className: "flex min-w-0 flex-1 flex-col gap-1",
|
|
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
|
|
277
|
-
})]
|
|
278
|
-
}),
|
|
279
|
-
problems.length > 0 ? /* @__PURE__ */ jsx("p", {
|
|
280
|
-
"data-testid": "runner-launch-problem",
|
|
281
|
-
className: "text-base text-po-red",
|
|
282
|
-
children: problems.join(" ")
|
|
283
|
-
}) : null,
|
|
284
|
-
/* @__PURE__ */ jsxs(DialogFooter, {
|
|
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, {
|
|
293
|
-
variant: "outline",
|
|
294
|
-
size: "xs",
|
|
295
|
-
"data-testid": "runner-launch-cancel",
|
|
296
|
-
onClick: onClose,
|
|
297
|
-
children: "cancel"
|
|
298
|
-
}),
|
|
299
|
-
/* @__PURE__ */ jsx(Button, {
|
|
300
|
-
variant: "primary",
|
|
301
|
-
size: "xs",
|
|
302
|
-
"data-testid": "runner-launch-submit",
|
|
303
|
-
disabled: problems.length > 0,
|
|
304
|
-
onClick: launch,
|
|
305
|
-
children: "launch"
|
|
306
|
-
})
|
|
307
|
-
]
|
|
308
|
-
})
|
|
309
|
-
] })]
|
|
310
|
-
})
|
|
311
|
-
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Absolute 1-based numbers for a tail, counted back from the last line.
|
|
165
|
+
*
|
|
166
|
+
* A tail is contiguous, so only its end has to be known. Grep results are not
|
|
167
|
+
* contiguous and carry their own numbers from the reader instead.
|
|
168
|
+
*/
|
|
169
|
+
function tailLineNumbers(lastLineNumber, count) {
|
|
170
|
+
const first = Math.max(1, lastLineNumber - count + 1);
|
|
171
|
+
return Array.from({ length: count }, (_, index) => first + index);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Whether the view is actually following a growing log.
|
|
175
|
+
*
|
|
176
|
+
* Three things must hold: the reader asked to follow, no query is set (a
|
|
177
|
+
* filtered view is a snapshot of the whole file, which a stream cannot
|
|
178
|
+
* assemble a chunk at a time), and the runner is still writing. Without the
|
|
179
|
+
* last one a finished run would claim to be tailing a file nothing will ever
|
|
180
|
+
* append to.
|
|
181
|
+
*/
|
|
182
|
+
function isFollowingLive(following, filtering, running) {
|
|
183
|
+
return following && !filtering && running;
|
|
312
184
|
}
|
|
313
185
|
//#endregion
|
|
314
186
|
//#region src/constants/webRunnerLog.ts
|
|
@@ -496,9 +368,9 @@ function followRunnerLog(sessionId, runId, from, handlers) {
|
|
|
496
368
|
}
|
|
497
369
|
//#endregion
|
|
498
370
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnerLogPanel.tsx
|
|
499
|
-
const TICK_MS$
|
|
371
|
+
const TICK_MS$1 = 1e4;
|
|
500
372
|
/** The tab id doubles as the URL segment, so it stays plain and unique across plugins. */
|
|
501
|
-
const TAB_ID_PREFIX
|
|
373
|
+
const TAB_ID_PREFIX = "runner-log-";
|
|
502
374
|
/** Lines held in the view; a follow that ran all day must not grow without bound. */
|
|
503
375
|
const MAX_VIEW_LINES = 2e3;
|
|
504
376
|
/** Context lines each side of a match, the same either-side window grep would use. */
|
|
@@ -513,7 +385,7 @@ const BYTES_PER_MB$1 = BYTES_PER_KB$1 * BYTES_PER_KB$1;
|
|
|
513
385
|
function runnerLogTab(run) {
|
|
514
386
|
const runId = run.id;
|
|
515
387
|
return {
|
|
516
|
-
id: `${TAB_ID_PREFIX
|
|
388
|
+
id: `${TAB_ID_PREFIX}${runId}`,
|
|
517
389
|
label: `log · ${run.name}`,
|
|
518
390
|
panel: (props) => /* @__PURE__ */ jsx(RunnerLogPanel, {
|
|
519
391
|
...props,
|
|
@@ -552,19 +424,6 @@ function isMatch(line, needle, ignoreCase) {
|
|
|
552
424
|
if (needle === "") return false;
|
|
553
425
|
return ignoreCase ? line.toLowerCase().includes(needle.toLowerCase()) : line.includes(needle);
|
|
554
426
|
}
|
|
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
427
|
/**
|
|
569
428
|
* One rendered log line: its number, whether it matched, and the text.
|
|
570
429
|
*
|
|
@@ -573,24 +432,24 @@ function MetaRow({ label, value }) {
|
|
|
573
432
|
* keyed on the joined string; keyed by line number instead, an append only
|
|
574
433
|
* costs the line that appended.
|
|
575
434
|
*/
|
|
576
|
-
function LogRow({ number, line, matched }) {
|
|
435
|
+
function LogRow({ number, line, matched, dimmed }) {
|
|
577
436
|
return /* @__PURE__ */ jsxs("div", {
|
|
578
437
|
"data-testid": "runner-log-row",
|
|
579
438
|
"data-matched": matched,
|
|
580
439
|
className: "flex min-w-0 gap-2",
|
|
581
440
|
children: [
|
|
582
441
|
number === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
583
|
-
className: "w-12 shrink-0 select-none text-right text-
|
|
442
|
+
className: "w-12 shrink-0 select-none text-right font-mono text-sm leading-relaxed text-po-faint",
|
|
584
443
|
children: number
|
|
585
444
|
}),
|
|
586
445
|
/* @__PURE__ */ jsx("span", {
|
|
587
446
|
"aria-hidden": true,
|
|
588
|
-
className: `w-4 shrink-0 select-none text-
|
|
447
|
+
className: `w-4 shrink-0 select-none font-mono text-sm leading-relaxed ${matched ? "text-po-yellow" : "text-po-faint"}`,
|
|
589
448
|
children: matched ? ">>" : ""
|
|
590
449
|
}),
|
|
591
450
|
/* @__PURE__ */ jsx(AnsiLine, {
|
|
592
451
|
line,
|
|
593
|
-
className: `min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-
|
|
452
|
+
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
453
|
})
|
|
595
454
|
]
|
|
596
455
|
});
|
|
@@ -604,7 +463,7 @@ function LogRow({ number, line, matched }) {
|
|
|
604
463
|
* rather than moving a cursor through it, and following pauses while one is
|
|
605
464
|
* set because a filtered view is a snapshot of a file that is still growing.
|
|
606
465
|
*/
|
|
607
|
-
function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
466
|
+
function RunnerLogPanel({ sessionId, runId, sendSessionFrame, onTerminal }) {
|
|
608
467
|
const { runs, stopRequested } = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
609
468
|
const run = runs.find((candidate) => candidate.id === runId);
|
|
610
469
|
const [now, setNow] = useState(() => Date.now());
|
|
@@ -624,7 +483,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
624
483
|
const filtering = query !== "";
|
|
625
484
|
const live = isFollowingLive(following, filtering, slice?.running === true);
|
|
626
485
|
useEffect(() => {
|
|
627
|
-
const timer = setInterval(() => setNow(Date.now()), TICK_MS$
|
|
486
|
+
const timer = setInterval(() => setNow(Date.now()), TICK_MS$1);
|
|
628
487
|
return () => clearInterval(timer);
|
|
629
488
|
}, []);
|
|
630
489
|
useEffect(() => {
|
|
@@ -720,6 +579,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
720
579
|
}),
|
|
721
580
|
/* @__PURE__ */ jsx(StatusBadge, {
|
|
722
581
|
tone: badge.tone,
|
|
582
|
+
size: "md",
|
|
723
583
|
"data-testid": "runner-log-state",
|
|
724
584
|
children: badge.label
|
|
725
585
|
}),
|
|
@@ -735,60 +595,71 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
735
595
|
children: "no longer listed; its log stays readable"
|
|
736
596
|
}),
|
|
737
597
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
598
|
+
onTerminal ? /* @__PURE__ */ jsx(Button, {
|
|
599
|
+
variant: "outline",
|
|
600
|
+
size: "sm",
|
|
601
|
+
"data-testid": "runner-log-terminal",
|
|
602
|
+
onClick: onTerminal,
|
|
603
|
+
children: "Back to terminal"
|
|
604
|
+
}) : null,
|
|
738
605
|
run !== void 0 && run.state === "running" && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
|
|
739
606
|
variant: stopping ? "outline" : "danger-outline",
|
|
740
|
-
size: "
|
|
607
|
+
size: "sm",
|
|
741
608
|
"data-testid": "runner-log-stop",
|
|
742
609
|
disabled: stopping,
|
|
743
610
|
title: stopping ? "stop requested; the runner reports its own exit" : "ask the runtime to stop this runner",
|
|
744
611
|
onClick: () => requestRunnerStop(sendSessionFrame, sessionId, run.id),
|
|
745
|
-
|
|
746
|
-
children: stopping ? "stopping…" : "stop"
|
|
612
|
+
children: stopping ? "Stopping…" : "Stop"
|
|
747
613
|
}) : null
|
|
748
614
|
]
|
|
749
615
|
}),
|
|
750
|
-
run ? /* @__PURE__ */
|
|
751
|
-
className: "
|
|
752
|
-
children:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
616
|
+
run ? /* @__PURE__ */ jsx("div", {
|
|
617
|
+
className: "shrink-0 border-b border-po-border-soft px-3 py-3 sm:px-[26px]",
|
|
618
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
619
|
+
className: "flex flex-col gap-1 rounded-md bg-po-deep px-3 py-2",
|
|
620
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
621
|
+
className: "break-all font-mono text-base text-po-hi",
|
|
622
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
623
|
+
"aria-hidden": true,
|
|
624
|
+
className: "select-none text-po-faint",
|
|
625
|
+
children: ["$", " "]
|
|
626
|
+
}), run.command]
|
|
627
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
628
|
+
className: "break-all font-mono text-sm text-po-dim",
|
|
629
|
+
children: ["in ", run.cwd]
|
|
630
|
+
})]
|
|
631
|
+
})
|
|
759
632
|
}) : null,
|
|
760
633
|
/* @__PURE__ */ jsxs("div", {
|
|
761
|
-
className: "flex min-h-
|
|
634
|
+
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
635
|
children: [
|
|
763
636
|
/* @__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: "
|
|
637
|
+
className: "relative flex min-w-0 flex-1 items-center sm:w-72 sm:flex-none",
|
|
638
|
+
children: [/* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute left-2.5 h-3.5 w-3.5 text-po-faint" }), /* @__PURE__ */ jsx(Input, {
|
|
639
|
+
size: "md",
|
|
767
640
|
"data-testid": "runner-log-search",
|
|
768
641
|
value: typed,
|
|
769
|
-
placeholder: "
|
|
642
|
+
placeholder: "Search output",
|
|
770
643
|
onChange: (event) => setTyped(event.target.value),
|
|
771
|
-
className: "pl-
|
|
644
|
+
className: "w-full pl-8"
|
|
772
645
|
})]
|
|
773
646
|
}),
|
|
774
647
|
/* @__PURE__ */ jsx(Button, {
|
|
775
648
|
variant: ignoreCase ? "subtle" : "outline",
|
|
776
|
-
size: "
|
|
649
|
+
size: "sm",
|
|
777
650
|
"data-testid": "runner-log-ignore-case",
|
|
778
651
|
"aria-pressed": ignoreCase,
|
|
779
652
|
title: "match regardless of case",
|
|
780
653
|
onClick: () => setIgnoreCase((current) => !current),
|
|
781
|
-
|
|
782
|
-
children: "aA"
|
|
654
|
+
children: ignoreCase ? "Any case" : "Match case"
|
|
783
655
|
}),
|
|
784
656
|
/* @__PURE__ */ jsx(Button, {
|
|
785
657
|
variant: context > 0 && filtering ? "subtle" : "outline",
|
|
786
|
-
size: "
|
|
658
|
+
size: "sm",
|
|
787
659
|
"data-testid": "runner-log-context",
|
|
788
660
|
disabled: !filtering,
|
|
789
661
|
title: "lines of context kept either side of a match",
|
|
790
662
|
onClick: () => setContext((current) => current === 0 ? CONTEXT_LINES : 0),
|
|
791
|
-
className: "px-2 ",
|
|
792
663
|
children: `± ${String(context)} lines`
|
|
793
664
|
}),
|
|
794
665
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
@@ -799,14 +670,13 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
799
670
|
}),
|
|
800
671
|
/* @__PURE__ */ jsx(Button, {
|
|
801
672
|
variant: live ? "subtle" : "outline",
|
|
802
|
-
size: "
|
|
673
|
+
size: "sm",
|
|
803
674
|
"data-testid": "runner-log-follow",
|
|
804
675
|
"aria-pressed": live,
|
|
805
676
|
disabled: filtering || slice?.running !== true,
|
|
806
677
|
title: filtering ? "a filtered view is a snapshot; clear the query to follow" : "follow the log as it grows",
|
|
807
678
|
onClick: () => setFollowing((current) => !current),
|
|
808
|
-
|
|
809
|
-
children: filtering ? "follow · paused" : "follow"
|
|
679
|
+
children: filtering ? "Paused while searching" : live ? "Following" : "Follow"
|
|
810
680
|
})
|
|
811
681
|
]
|
|
812
682
|
}),
|
|
@@ -819,7 +689,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
819
689
|
className: "h-full overflow-auto bg-po-deep px-3 py-3 sm:px-[26px]",
|
|
820
690
|
children: [error !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
821
691
|
"data-testid": "runner-log-error",
|
|
822
|
-
className: "text-
|
|
692
|
+
className: "text-sm text-po-red",
|
|
823
693
|
children: error
|
|
824
694
|
}) : slice === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
825
695
|
"data-testid": "runner-log-reading",
|
|
@@ -827,10 +697,10 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
827
697
|
children: "reading…"
|
|
828
698
|
}) : slice.exists === false ? /* @__PURE__ */ jsx("p", {
|
|
829
699
|
className: "text-sm text-po-dim",
|
|
830
|
-
children: "
|
|
700
|
+
children: "This runner has not printed anything yet."
|
|
831
701
|
}) : view.lines.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
832
702
|
className: "text-sm text-po-dim",
|
|
833
|
-
children: filtering ? "
|
|
703
|
+
children: filtering ? "Nothing matched." : "The log is empty."
|
|
834
704
|
}) : view.lines.map((line, index) => {
|
|
835
705
|
const number = numbers[index];
|
|
836
706
|
const previous = numbers[index - 1];
|
|
@@ -838,25 +708,26 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
|
|
|
838
708
|
return /* @__PURE__ */ jsxs(Fragment, { children: [gap ? /* @__PURE__ */ jsx("div", {
|
|
839
709
|
"data-testid": "runner-log-gap",
|
|
840
710
|
"aria-hidden": true,
|
|
841
|
-
className: "select-none py-0.5 pl-
|
|
711
|
+
className: "select-none py-0.5 pl-14 text-sm leading-relaxed text-po-faint",
|
|
842
712
|
children: "⋯"
|
|
843
713
|
}) : null, /* @__PURE__ */ jsx(LogRow, {
|
|
844
714
|
number,
|
|
845
715
|
line,
|
|
846
|
-
matched: filtering && isMatch(line, query, ignoreCase)
|
|
716
|
+
matched: filtering && isMatch(line, query, ignoreCase),
|
|
717
|
+
dimmed: filtering && !isMatch(line, query, ignoreCase)
|
|
847
718
|
})] }, number ?? `row-${String(index)}`);
|
|
848
719
|
}), /* @__PURE__ */ jsx("div", { ref: bottom })]
|
|
849
720
|
}), live && !pinned ? /* @__PURE__ */ jsx(Button, {
|
|
850
721
|
variant: "subtle",
|
|
851
|
-
size: "
|
|
722
|
+
size: "sm",
|
|
852
723
|
"data-testid": "runner-log-jump",
|
|
853
724
|
title: "scroll back to the newest lines and keep following",
|
|
854
725
|
onClick: () => {
|
|
855
726
|
setPinned(true);
|
|
856
727
|
bottom.current?.scrollIntoView({ block: "end" });
|
|
857
728
|
},
|
|
858
|
-
className: "absolute bottom-3 right-3
|
|
859
|
-
children: "
|
|
729
|
+
className: "absolute bottom-3 right-3",
|
|
730
|
+
children: "Jump to latest"
|
|
860
731
|
}) : null]
|
|
861
732
|
}),
|
|
862
733
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -955,21 +826,8 @@ async function sendRunnerInput(sessionId, runId, text) {
|
|
|
955
826
|
}
|
|
956
827
|
//#endregion
|
|
957
828
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnerShellPanel.tsx
|
|
958
|
-
const TAB_ID_PREFIX = "runner-shell-";
|
|
959
829
|
/** Keystrokes are gathered for this long and sent as one request. */
|
|
960
830
|
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
831
|
/**
|
|
974
832
|
* An interactive runner's pane, attached.
|
|
975
833
|
*
|
|
@@ -981,9 +839,13 @@ function runnerShellTab(run) {
|
|
|
981
839
|
*
|
|
982
840
|
* xterm is loaded when the tab opens rather than with the plugin. Most sessions
|
|
983
841
|
* never attach to anything, and an emulator is not a small thing to carry.
|
|
842
|
+
*
|
|
843
|
+
* Search reads the scrubbed log the sink writes beside the raw bytes, so it
|
|
844
|
+
* hands over to the log view: `onSearch` when the panel is embedded, the
|
|
845
|
+
* runner's log tab otherwise.
|
|
984
846
|
*/
|
|
985
|
-
function RunnerShellPanel({ sessionId, runId }) {
|
|
986
|
-
const { runs } = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
847
|
+
function RunnerShellPanel({ sessionId, runId, sendSessionFrame, openTransientTab, onSearch }) {
|
|
848
|
+
const { runs, stopRequested } = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
987
849
|
const run = runs.find((candidate) => candidate.id === runId);
|
|
988
850
|
const [ended, setEnded] = useState(false);
|
|
989
851
|
const [lost, setLost] = useState(false);
|
|
@@ -1021,12 +883,13 @@ function RunnerShellPanel({ sessionId, runId }) {
|
|
|
1021
883
|
}, FLUSH_MS);
|
|
1022
884
|
};
|
|
1023
885
|
const live = run?.state === "running" && !ended;
|
|
886
|
+
const stopping = stopRequested.includes(runId);
|
|
1024
887
|
return /* @__PURE__ */ jsxs("div", {
|
|
1025
888
|
"data-testid": "runner-shell-panel",
|
|
1026
889
|
className: "flex min-h-0 flex-1 flex-col",
|
|
1027
890
|
children: [
|
|
1028
891
|
/* @__PURE__ */ jsxs("div", {
|
|
1029
|
-
className: "flex min-h-11 shrink-0 items-center gap-2
|
|
892
|
+
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
893
|
children: [
|
|
1031
894
|
/* @__PURE__ */ jsx("span", {
|
|
1032
895
|
"data-testid": "runner-shell-name",
|
|
@@ -1035,23 +898,45 @@ function RunnerShellPanel({ sessionId, runId }) {
|
|
|
1035
898
|
}),
|
|
1036
899
|
/* @__PURE__ */ jsx(StatusBadge, {
|
|
1037
900
|
tone: live ? "running" : "neutral",
|
|
901
|
+
size: "md",
|
|
1038
902
|
"data-testid": "runner-shell-state",
|
|
1039
|
-
children: live ? "
|
|
903
|
+
children: live ? "Running" : "Ended"
|
|
1040
904
|
}),
|
|
905
|
+
run ? /* @__PURE__ */ jsx("span", {
|
|
906
|
+
className: "min-w-0 truncate font-mono text-sm text-po-dim",
|
|
907
|
+
children: run.command
|
|
908
|
+
}) : null,
|
|
1041
909
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
1042
910
|
lost ? /* @__PURE__ */ jsx("span", {
|
|
1043
911
|
"data-testid": "runner-shell-lost",
|
|
1044
|
-
className: "shrink-0 text-
|
|
1045
|
-
children: "
|
|
912
|
+
className: "shrink-0 text-sm text-po-red",
|
|
913
|
+
children: "The connection to this terminal dropped. Reopen the runner to attach again."
|
|
914
|
+
}) : null,
|
|
915
|
+
run ? /* @__PURE__ */ jsxs(Button, {
|
|
916
|
+
variant: "outline",
|
|
917
|
+
size: "sm",
|
|
918
|
+
"data-testid": "runner-shell-search",
|
|
919
|
+
title: "search everything this runner has printed",
|
|
920
|
+
onClick: () => onSearch ? onSearch() : openTransientTab(runnerLogTab(run)),
|
|
921
|
+
className: "gap-1.5",
|
|
922
|
+
children: [/* @__PURE__ */ jsx(SearchIcon, { className: "h-3.5 w-3.5" }), "Search output"]
|
|
1046
923
|
}) : null,
|
|
1047
924
|
live ? /* @__PURE__ */ jsx(Button, {
|
|
1048
925
|
variant: "outline",
|
|
1049
|
-
size: "
|
|
926
|
+
size: "sm",
|
|
1050
927
|
"data-testid": "runner-shell-interrupt",
|
|
1051
928
|
title: "send ctrl-c",
|
|
1052
929
|
onClick: () => send(""),
|
|
1053
|
-
|
|
1054
|
-
|
|
930
|
+
children: "Ctrl-C"
|
|
931
|
+
}) : null,
|
|
932
|
+
live && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
|
|
933
|
+
variant: stopping ? "outline" : "danger-outline",
|
|
934
|
+
size: "sm",
|
|
935
|
+
"data-testid": "runner-shell-stop",
|
|
936
|
+
disabled: stopping,
|
|
937
|
+
title: stopping ? "stop requested; the runner reports its own exit" : "ask the runtime to stop this runner",
|
|
938
|
+
onClick: () => requestRunnerStop(sendSessionFrame, sessionId, runId),
|
|
939
|
+
children: stopping ? "Stopping…" : "Stop"
|
|
1055
940
|
}) : null
|
|
1056
941
|
]
|
|
1057
942
|
}),
|
|
@@ -1072,13 +957,185 @@ function RunnerShellPanel({ sessionId, runId }) {
|
|
|
1072
957
|
className: "flex h-8 shrink-0 items-center border-t border-po-border-soft px-3 sm:px-[26px]",
|
|
1073
958
|
children: /* @__PURE__ */ jsx("span", {
|
|
1074
959
|
className: "min-w-0 flex-1 truncate text-sm text-po-dim",
|
|
1075
|
-
children: live ?
|
|
960
|
+
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
961
|
})
|
|
1077
962
|
})
|
|
1078
963
|
]
|
|
1079
964
|
});
|
|
1080
965
|
}
|
|
1081
966
|
//#endregion
|
|
967
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/RunnersPanel.tsx
|
|
968
|
+
const LAUNCH_TIMEOUT_MS = 3e4;
|
|
969
|
+
const RUNNERS_TAB_ID = "runner-runs";
|
|
970
|
+
/** The temporary tab the dock's runners group opens from its own name. */
|
|
971
|
+
function runnersTab() {
|
|
972
|
+
return {
|
|
973
|
+
id: RUNNERS_TAB_ID,
|
|
974
|
+
label: "runners",
|
|
975
|
+
panel: RunnersPanel
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Everything this session has up, and the one place to start another.
|
|
980
|
+
*
|
|
981
|
+
* A runner is a shell: the bar takes a command and runs it on its own
|
|
982
|
+
* terminal, or opens a plain shell when nothing was typed. The selected
|
|
983
|
+
* runner is shown in place, as its terminal when it has one and as its log
|
|
984
|
+
* when it does not (a command the agent backgrounded has no pane).
|
|
985
|
+
*
|
|
986
|
+
* Running runners only, the same rule the rail follows. A finished run leaves
|
|
987
|
+
* the moment it exits; its log stays readable from the tool message that
|
|
988
|
+
* started it, and `po-runner list` is where history lives.
|
|
989
|
+
*/
|
|
990
|
+
function RunnersPanel(props) {
|
|
991
|
+
const { sessionId, sendSessionFrame } = props;
|
|
992
|
+
const session = useStore(runners.store, (state) => runners.select(state, sessionId));
|
|
993
|
+
const running = session.runs.filter((run) => run.state === "running");
|
|
994
|
+
const [command, setCommand] = useState("");
|
|
995
|
+
const [cwd, setCwd] = useState("");
|
|
996
|
+
const [selectedId, setSelectedId] = useState(void 0);
|
|
997
|
+
/** The terminal runner whose log is being searched instead of shown live. */
|
|
998
|
+
const [searchingId, setSearchingId] = useState(void 0);
|
|
999
|
+
const launchBaseline = useRef(null);
|
|
1000
|
+
const launchTimeout = useRef(void 0);
|
|
1001
|
+
const selected = running.find((run) => run.id === selectedId) ?? running[0];
|
|
1002
|
+
const launch = () => {
|
|
1003
|
+
if (sessionId === null) return;
|
|
1004
|
+
launchBaseline.current = new Set(session.runs.map((run) => run.id));
|
|
1005
|
+
clearTimeout(launchTimeout.current);
|
|
1006
|
+
launchTimeout.current = setTimeout(() => {
|
|
1007
|
+
launchBaseline.current = null;
|
|
1008
|
+
}, LAUNCH_TIMEOUT_MS);
|
|
1009
|
+
requestRunnerStart(sendSessionFrame, sessionId, runnerRunRequest(command, cwd));
|
|
1010
|
+
setCommand("");
|
|
1011
|
+
};
|
|
1012
|
+
useEffect(() => {
|
|
1013
|
+
const baseline = launchBaseline.current;
|
|
1014
|
+
if (baseline === null) return;
|
|
1015
|
+
const started = session.runs.find((run) => run.state === "running" && !baseline.has(run.id));
|
|
1016
|
+
if (!started) return;
|
|
1017
|
+
launchBaseline.current = null;
|
|
1018
|
+
clearTimeout(launchTimeout.current);
|
|
1019
|
+
setSelectedId(started.id);
|
|
1020
|
+
}, [session.runs]);
|
|
1021
|
+
useEffect(() => () => clearTimeout(launchTimeout.current), []);
|
|
1022
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1023
|
+
"data-testid": "runners-panel",
|
|
1024
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
1025
|
+
children: [
|
|
1026
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1027
|
+
className: "flex h-11 shrink-0 items-center gap-2.5 border-b border-po-border-soft px-3 sm:px-[26px]",
|
|
1028
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1029
|
+
className: "text-lg font-semibold text-po-hi",
|
|
1030
|
+
children: "Runners"
|
|
1031
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1032
|
+
"data-testid": "runners-panel-count",
|
|
1033
|
+
className: "text-sm text-po-dim",
|
|
1034
|
+
children: running.length === 0 ? "Nothing running" : `${String(running.length)} running`
|
|
1035
|
+
})]
|
|
1036
|
+
}),
|
|
1037
|
+
sessionId === null ? null : /* @__PURE__ */ jsxs("div", {
|
|
1038
|
+
className: "flex shrink-0 flex-col gap-1.5 border-b border-po-border-soft px-3 py-3 sm:px-[26px]",
|
|
1039
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1040
|
+
className: "flex flex-wrap items-center gap-2 sm:flex-nowrap",
|
|
1041
|
+
children: [
|
|
1042
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1043
|
+
className: "relative flex min-w-0 flex-1 basis-full items-center sm:basis-auto",
|
|
1044
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1045
|
+
"aria-hidden": true,
|
|
1046
|
+
className: "pointer-events-none absolute left-3 font-mono text-base text-po-faint",
|
|
1047
|
+
children: "$"
|
|
1048
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
1049
|
+
size: "lg",
|
|
1050
|
+
"data-testid": "runners-panel-command",
|
|
1051
|
+
"aria-label": "Command to run",
|
|
1052
|
+
value: command,
|
|
1053
|
+
placeholder: "a command, like pnpm dev",
|
|
1054
|
+
autoComplete: "off",
|
|
1055
|
+
spellCheck: false,
|
|
1056
|
+
onChange: (event) => setCommand(event.target.value),
|
|
1057
|
+
onKeyDown: (event) => {
|
|
1058
|
+
if (event.key === "Enter") launch();
|
|
1059
|
+
},
|
|
1060
|
+
className: "w-full pl-7 font-mono"
|
|
1061
|
+
})]
|
|
1062
|
+
}),
|
|
1063
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1064
|
+
className: "relative flex min-w-0 flex-1 items-center sm:w-56 sm:flex-none",
|
|
1065
|
+
children: [/* @__PURE__ */ jsx(FolderIcon, { className: "pointer-events-none absolute left-2.5 h-3.5 w-3.5 text-po-faint" }), /* @__PURE__ */ jsx(Input, {
|
|
1066
|
+
size: "lg",
|
|
1067
|
+
"data-testid": "runners-panel-cwd",
|
|
1068
|
+
"aria-label": "Runs in",
|
|
1069
|
+
value: cwd,
|
|
1070
|
+
placeholder: "session directory",
|
|
1071
|
+
spellCheck: false,
|
|
1072
|
+
onChange: (event) => setCwd(event.target.value),
|
|
1073
|
+
className: "w-full pl-8 font-mono"
|
|
1074
|
+
})]
|
|
1075
|
+
}),
|
|
1076
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1077
|
+
variant: "primary",
|
|
1078
|
+
size: "lg",
|
|
1079
|
+
"data-testid": "runners-panel-run",
|
|
1080
|
+
onClick: launch,
|
|
1081
|
+
children: command.trim() === "" ? "Open shell" : "Run"
|
|
1082
|
+
})
|
|
1083
|
+
]
|
|
1084
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1085
|
+
className: "text-sm text-po-dim",
|
|
1086
|
+
children: "Runs in its own terminal and keeps going after Po's turn ends. Leave it empty for a plain shell."
|
|
1087
|
+
})]
|
|
1088
|
+
}),
|
|
1089
|
+
selected === void 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
1090
|
+
"data-testid": "runners-panel-empty",
|
|
1091
|
+
className: "px-4 py-8",
|
|
1092
|
+
title: "Nothing running yet",
|
|
1093
|
+
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."
|
|
1094
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1095
|
+
"data-testid": "runners-panel-picker",
|
|
1096
|
+
className: "flex shrink-0 flex-wrap gap-1.5 px-3 pt-3 sm:px-[26px]",
|
|
1097
|
+
children: running.map((run) => /* @__PURE__ */ jsxs(Button, {
|
|
1098
|
+
variant: run === selected ? "subtle" : "ghost",
|
|
1099
|
+
size: "sm",
|
|
1100
|
+
"data-testid": `runners-panel-pick-${run.id}`,
|
|
1101
|
+
"aria-pressed": run === selected,
|
|
1102
|
+
title: run.command,
|
|
1103
|
+
onClick: () => setSelectedId(run.id),
|
|
1104
|
+
className: `gap-1.5 ${run === selected ? "text-po-hi" : ""}`,
|
|
1105
|
+
children: [/* @__PURE__ */ jsx(Dot, {
|
|
1106
|
+
tone: "yellow",
|
|
1107
|
+
pulse: true
|
|
1108
|
+
}), run.name]
|
|
1109
|
+
}, run.id))
|
|
1110
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1111
|
+
className: "mt-3 flex min-h-0 flex-1 flex-col border-t border-po-border-soft",
|
|
1112
|
+
children: selected.interactive && searchingId !== selected.id ? /* @__PURE__ */ jsx(RunnerShellPanel, {
|
|
1113
|
+
...props,
|
|
1114
|
+
runId: selected.id,
|
|
1115
|
+
onSearch: () => setSearchingId(selected.id)
|
|
1116
|
+
}, selected.id) : /* @__PURE__ */ jsx(RunnerLogPanel, {
|
|
1117
|
+
...props,
|
|
1118
|
+
runId: selected.id,
|
|
1119
|
+
...selected.interactive ? { onTerminal: () => setSearchingId(void 0) } : {}
|
|
1120
|
+
}, selected.id)
|
|
1121
|
+
})] })
|
|
1122
|
+
]
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
//#endregion
|
|
1126
|
+
//#region src/extensions/workspaces/sessions/(frontend)/activity-group/runners.web.ts
|
|
1127
|
+
var runners_web_default = defineActivityGroup({
|
|
1128
|
+
keys: "r l",
|
|
1129
|
+
icon: PlayCircleIcon,
|
|
1130
|
+
statusKey: "po-runner-runners",
|
|
1131
|
+
activeSource: runnerActivitySource,
|
|
1132
|
+
transientTab: runnersTab,
|
|
1133
|
+
order: 20
|
|
1134
|
+
});
|
|
1135
|
+
//#endregion
|
|
1136
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/runner-runs.web.ts
|
|
1137
|
+
var runner_runs_web_default = defineChannelFile(runnerRunsChannel);
|
|
1138
|
+
//#endregion
|
|
1082
1139
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/runnerTail.ts
|
|
1083
1140
|
/**
|
|
1084
1141
|
* The newest line a running runner has written, for a row that has one line to
|
|
@@ -1154,229 +1211,6 @@ function useRunnerTail(sessionId, runId, running) {
|
|
|
1154
1211
|
return running && tail?.runId === runId ? tail.text : void 0;
|
|
1155
1212
|
}
|
|
1156
1213
|
//#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
1214
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/RunnersActivitySection.tsx
|
|
1381
1215
|
const TICK_MS = 1e4;
|
|
1382
1216
|
/**
|