@aliou/pi-processes 0.4.3 → 0.4.5
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/README.md +2 -17
- package/package.json +28 -13
- package/src/index.ts +27 -0
- package/commands/index.ts +0 -334
- package/commands/settings-command.ts +0 -128
- package/components/log-stream-component.ts +0 -149
- package/components/process-picker-component.ts +0 -155
- package/components/processes-component.ts +0 -450
- package/components/status-format.ts +0 -38
- package/config.ts +0 -62
- package/constants/index.ts +0 -11
- package/constants/types.ts +0 -65
- package/hooks/cleanup.ts +0 -10
- package/hooks/index.ts +0 -18
- package/hooks/message-renderer.ts +0 -83
- package/hooks/process-end.ts +0 -92
- package/hooks/widget.ts +0 -146
- package/index.ts +0 -27
- package/manager.ts +0 -519
- package/tools/actions/clear.ts +0 -20
- package/tools/actions/index.ts +0 -49
- package/tools/actions/kill.ts +0 -76
- package/tools/actions/list.ts +0 -37
- package/tools/actions/logs.ts +0 -59
- package/tools/actions/output.ts +0 -144
- package/tools/actions/start.ts +0 -55
- package/tools/index.ts +0 -280
- package/utils/ansi.ts +0 -36
- package/utils/format.ts +0 -42
- package/utils/index.ts +0 -3
- package/utils/process-group.ts +0 -22
package/README.md
CHANGED
|
@@ -8,29 +8,14 @@ Manage background processes from Pi. Start long-running commands (dev servers, b
|
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
-
Install via npm:
|
|
12
|
-
|
|
13
11
|
```bash
|
|
14
12
|
pi install npm:@aliou/pi-processes
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
Or
|
|
15
|
+
Or from git:
|
|
18
16
|
|
|
19
17
|
```bash
|
|
20
|
-
pi install git:github.com/aliou/pi-
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Or selectively in your `settings.json`:
|
|
24
|
-
|
|
25
|
-
```json
|
|
26
|
-
{
|
|
27
|
-
"packages": [
|
|
28
|
-
{
|
|
29
|
-
"source": "git:github.com/aliou/pi-extensions",
|
|
30
|
-
"extensions": ["extensions/processes"]
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
}
|
|
18
|
+
pi install git:github.com/aliou/pi-processes
|
|
34
19
|
```
|
|
35
20
|
|
|
36
21
|
## Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-processes",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"private": false,
|
|
6
7
|
"keywords": [
|
|
@@ -11,11 +12,11 @@
|
|
|
11
12
|
],
|
|
12
13
|
"repository": {
|
|
13
14
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/aliou/pi-
|
|
15
|
+
"url": "https://github.com/aliou/pi-processes"
|
|
15
16
|
},
|
|
16
17
|
"pi": {
|
|
17
18
|
"extensions": [
|
|
18
|
-
"./index.ts"
|
|
19
|
+
"./src/index.ts"
|
|
19
20
|
],
|
|
20
21
|
"video": "https://assets.aliou.me/pi-extensions/demos/pi-processes.mp4"
|
|
21
22
|
},
|
|
@@ -23,21 +24,35 @@
|
|
|
23
24
|
"access": "public"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
|
-
"
|
|
27
|
-
"commands",
|
|
28
|
-
"components",
|
|
29
|
-
"constants",
|
|
30
|
-
"hooks",
|
|
31
|
-
"tools",
|
|
32
|
-
"utils",
|
|
27
|
+
"src",
|
|
33
28
|
"README.md"
|
|
34
29
|
],
|
|
35
30
|
"dependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@aliou/pi-utils-
|
|
38
|
-
"@
|
|
31
|
+
"@aliou/pi-utils-settings": "^0.4.0",
|
|
32
|
+
"@aliou/pi-utils-ui": "^0.1.0",
|
|
33
|
+
"@sinclair/typebox": "^0.34.41"
|
|
39
34
|
},
|
|
40
35
|
"peerDependencies": {
|
|
41
36
|
"@mariozechner/pi-coding-agent": ">=0.51.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@aliou/pi-evals": "^0.3.0",
|
|
40
|
+
"@biomejs/biome": "^2.3.13",
|
|
41
|
+
"@changesets/cli": "^2.27.11",
|
|
42
|
+
"@mariozechner/pi-ai": "0.52.7",
|
|
43
|
+
"@mariozechner/pi-coding-agent": "0.52.7",
|
|
44
|
+
"@mariozechner/pi-tui": "0.52.7",
|
|
45
|
+
"@types/node": "^25.0.10",
|
|
46
|
+
"husky": "^9.1.7",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"vitest": "^4.0.18"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"lint": "biome check",
|
|
53
|
+
"format": "biome check --write",
|
|
54
|
+
"changeset": "changeset",
|
|
55
|
+
"version": "changeset version",
|
|
56
|
+
"release": "pnpm changeset publish"
|
|
42
57
|
}
|
|
43
58
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
const MARKER_DIR = join(homedir(), ".pi", "agent", "extensions", "migrations");
|
|
7
|
+
const MARKER_FILE = join(MARKER_DIR, "processes-moved");
|
|
8
|
+
|
|
9
|
+
export default async function (pi: ExtensionAPI) {
|
|
10
|
+
if (existsSync(MARKER_FILE)) return;
|
|
11
|
+
|
|
12
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
13
|
+
if (existsSync(MARKER_FILE)) return;
|
|
14
|
+
|
|
15
|
+
if (ctx.hasUI) {
|
|
16
|
+
ctx.ui.notify(
|
|
17
|
+
"@aliou/pi-processes has moved to its own repo. " +
|
|
18
|
+
"Run: pi install npm:@aliou/pi-processes -- " +
|
|
19
|
+
"then remove it from the pi-extensions package config.",
|
|
20
|
+
"warning",
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
mkdirSync(MARKER_DIR, { recursive: true });
|
|
25
|
+
writeFileSync(MARKER_FILE, new Date().toISOString());
|
|
26
|
+
});
|
|
27
|
+
}
|
package/commands/index.ts
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
4
|
-
Theme,
|
|
5
|
-
} from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import { LogStreamComponent } from "../components/log-stream-component";
|
|
7
|
-
import { ProcessPickerComponent } from "../components/process-picker-component";
|
|
8
|
-
import { ProcessesComponent } from "../components/processes-component";
|
|
9
|
-
import { LIVE_STATUSES, type ProcessInfo } from "../constants";
|
|
10
|
-
import type { ProcessManager } from "../manager";
|
|
11
|
-
|
|
12
|
-
const LOG_STREAM_WIDGET_ID = "processes-log-stream";
|
|
13
|
-
|
|
14
|
-
function runningProcessCompletions(manager: ProcessManager) {
|
|
15
|
-
return (prefix: string) => {
|
|
16
|
-
const processes = manager.list();
|
|
17
|
-
const lower = prefix.toLowerCase();
|
|
18
|
-
return processes
|
|
19
|
-
.filter(
|
|
20
|
-
(p) =>
|
|
21
|
-
LIVE_STATUSES.has(p.status) &&
|
|
22
|
-
(p.id.toLowerCase().startsWith(lower) ||
|
|
23
|
-
p.name.toLowerCase().startsWith(lower)),
|
|
24
|
-
)
|
|
25
|
-
.map((p) => ({
|
|
26
|
-
value: p.id,
|
|
27
|
-
label: p.id,
|
|
28
|
-
description: p.name,
|
|
29
|
-
}));
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function allProcessCompletions(manager: ProcessManager) {
|
|
34
|
-
return (prefix: string) => {
|
|
35
|
-
const processes = manager.list();
|
|
36
|
-
const lower = prefix.toLowerCase();
|
|
37
|
-
return processes
|
|
38
|
-
.filter(
|
|
39
|
-
(p) =>
|
|
40
|
-
p.id.toLowerCase().startsWith(lower) ||
|
|
41
|
-
p.name.toLowerCase().startsWith(lower),
|
|
42
|
-
)
|
|
43
|
-
.map((p) => ({
|
|
44
|
-
value: p.id,
|
|
45
|
-
label: p.id,
|
|
46
|
-
description: p.name,
|
|
47
|
-
}));
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function setupProcessesCommands(
|
|
52
|
-
pi: ExtensionAPI,
|
|
53
|
-
manager: ProcessManager,
|
|
54
|
-
): void {
|
|
55
|
-
let streamingProcessId: string | null = null;
|
|
56
|
-
|
|
57
|
-
// ── /process:list ──────────────────────────────────────────────────
|
|
58
|
-
// Registered first so it appears first in autocomplete.
|
|
59
|
-
pi.registerCommand("process:list", {
|
|
60
|
-
description: "View and manage background processes",
|
|
61
|
-
handler: async (_args, ctx) => {
|
|
62
|
-
if (!ctx.hasUI) {
|
|
63
|
-
ctx.ui.notify("/process:list requires interactive mode", "error");
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If currently streaming, dismiss the stream widget and show the list.
|
|
68
|
-
if (streamingProcessId) {
|
|
69
|
-
ctx.ui.setWidget(LOG_STREAM_WIDGET_ID, undefined);
|
|
70
|
-
streamingProcessId = null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result = await ctx.ui.custom<string | null>(
|
|
74
|
-
(tui, theme, _keybindings, done) => {
|
|
75
|
-
return new ProcessesComponent(
|
|
76
|
-
tui,
|
|
77
|
-
theme,
|
|
78
|
-
(processId?: string) => {
|
|
79
|
-
if (processId) {
|
|
80
|
-
done(processId);
|
|
81
|
-
} else {
|
|
82
|
-
done(null);
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
manager,
|
|
86
|
-
);
|
|
87
|
-
},
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// RPC fallback.
|
|
91
|
-
if (result === undefined) {
|
|
92
|
-
ctx.ui.notify("/process:list requires interactive mode", "info");
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// User dismissed with Escape/q.
|
|
97
|
-
if (result === null) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// User selected a process — start streaming its logs.
|
|
102
|
-
startStreaming(ctx.ui, manager, result);
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ── /process:stream [id|name] ──────────────────────────────────────
|
|
107
|
-
pi.registerCommand("process:stream", {
|
|
108
|
-
description: "Stream logs from a running process",
|
|
109
|
-
getArgumentCompletions: runningProcessCompletions(manager),
|
|
110
|
-
handler: async (args, ctx) => {
|
|
111
|
-
const arg = args.trim();
|
|
112
|
-
|
|
113
|
-
// Explicit argument: stream that process.
|
|
114
|
-
if (arg) {
|
|
115
|
-
const proc = manager.find(arg);
|
|
116
|
-
if (!proc) {
|
|
117
|
-
ctx.ui.notify(`Process not found: ${arg}`, "error");
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (!LIVE_STATUSES.has(proc.status)) {
|
|
121
|
-
ctx.ui.notify(
|
|
122
|
-
`${proc.name} (${proc.id}) is not running (${proc.status})`,
|
|
123
|
-
"info",
|
|
124
|
-
);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
startStreaming(ctx.ui, manager, proc.id);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// No argument + currently streaming: dismiss.
|
|
132
|
-
if (streamingProcessId) {
|
|
133
|
-
ctx.ui.setWidget(LOG_STREAM_WIDGET_ID, undefined);
|
|
134
|
-
streamingProcessId = null;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// No argument + not streaming: pick from running processes.
|
|
139
|
-
const running = manager.list().filter((p) => LIVE_STATUSES.has(p.status));
|
|
140
|
-
|
|
141
|
-
// No running processes.
|
|
142
|
-
if (running.length === 0) {
|
|
143
|
-
ctx.ui.notify("No running processes", "info");
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Single running process: auto-select.
|
|
148
|
-
if (running.length === 1 && running[0]) {
|
|
149
|
-
startStreaming(ctx.ui, manager, running[0].id);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Multiple running processes: show picker.
|
|
154
|
-
const processId = await pickProcess(
|
|
155
|
-
ctx,
|
|
156
|
-
manager,
|
|
157
|
-
"Select process to stream",
|
|
158
|
-
(p) => LIVE_STATUSES.has(p.status),
|
|
159
|
-
);
|
|
160
|
-
if (!processId) return;
|
|
161
|
-
startStreaming(ctx.ui, manager, processId);
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// ── /process:logs [id|name] ────────────────────────────────────────
|
|
166
|
-
pi.registerCommand("process:logs", {
|
|
167
|
-
description: "Show log file paths for a process",
|
|
168
|
-
getArgumentCompletions: allProcessCompletions(manager),
|
|
169
|
-
handler: async (args, ctx) => {
|
|
170
|
-
const arg = args.trim();
|
|
171
|
-
|
|
172
|
-
let processId: string | undefined;
|
|
173
|
-
|
|
174
|
-
if (arg) {
|
|
175
|
-
const proc = manager.find(arg);
|
|
176
|
-
if (!proc) {
|
|
177
|
-
ctx.ui.notify(`Process not found: ${arg}`, "error");
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
processId = proc.id;
|
|
181
|
-
} else {
|
|
182
|
-
// No argument: show picker.
|
|
183
|
-
processId = await pickProcess(ctx, manager, "Select process for logs");
|
|
184
|
-
if (!processId) return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const logFiles = manager.getLogFiles(processId);
|
|
188
|
-
const proc = manager.get(processId);
|
|
189
|
-
if (!logFiles || !proc) {
|
|
190
|
-
ctx.ui.notify(`Process not found: ${processId}`, "error");
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
ctx.ui.notify(
|
|
195
|
-
`${proc.name} (${proc.id})\nstdout: ${logFiles.stdoutFile}\nstderr: ${logFiles.stderrFile}`,
|
|
196
|
-
"info",
|
|
197
|
-
);
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// ── /process:kill [id|name] ────────────────────────────────────────
|
|
202
|
-
pi.registerCommand("process:kill", {
|
|
203
|
-
description: "Kill a running background process",
|
|
204
|
-
getArgumentCompletions: runningProcessCompletions(manager),
|
|
205
|
-
handler: async (args, ctx) => {
|
|
206
|
-
const arg = args.trim();
|
|
207
|
-
|
|
208
|
-
let processId: string | undefined;
|
|
209
|
-
|
|
210
|
-
if (arg) {
|
|
211
|
-
const proc = manager.find(arg);
|
|
212
|
-
if (!proc) {
|
|
213
|
-
ctx.ui.notify(`Process not found: ${arg}`, "error");
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (!LIVE_STATUSES.has(proc.status)) {
|
|
217
|
-
ctx.ui.notify(
|
|
218
|
-
`${proc.name} (${proc.id}) is not running (${proc.status})`,
|
|
219
|
-
"info",
|
|
220
|
-
);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
processId = proc.id;
|
|
224
|
-
} else {
|
|
225
|
-
// No argument: show picker (only running processes).
|
|
226
|
-
const running = manager
|
|
227
|
-
.list()
|
|
228
|
-
.filter((p) => LIVE_STATUSES.has(p.status));
|
|
229
|
-
|
|
230
|
-
if (running.length === 0) {
|
|
231
|
-
ctx.ui.notify("No running processes to kill", "info");
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (running.length === 1 && running[0]) {
|
|
236
|
-
processId = running[0].id;
|
|
237
|
-
} else {
|
|
238
|
-
processId = await pickProcess(
|
|
239
|
-
ctx,
|
|
240
|
-
manager,
|
|
241
|
-
"Select process to kill",
|
|
242
|
-
(p) => LIVE_STATUSES.has(p.status),
|
|
243
|
-
);
|
|
244
|
-
if (!processId) return;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const proc = manager.get(processId);
|
|
249
|
-
if (!proc) {
|
|
250
|
-
ctx.ui.notify(`Process not found: ${processId}`, "error");
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const signal =
|
|
255
|
-
proc.status === "terminate_timeout" ? "SIGKILL" : "SIGTERM";
|
|
256
|
-
const timeoutMs = signal === "SIGKILL" ? 200 : 3000;
|
|
257
|
-
const result = await manager.kill(processId, { signal, timeoutMs });
|
|
258
|
-
|
|
259
|
-
if (result.ok) {
|
|
260
|
-
ctx.ui.notify(`Killed ${proc.name} (${proc.id})`, "info");
|
|
261
|
-
} else {
|
|
262
|
-
ctx.ui.notify(
|
|
263
|
-
`Failed to kill ${proc.name} (${proc.id}): ${result.reason}`,
|
|
264
|
-
"error",
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
// ── /process:clear ─────────────────────────────────────────────────
|
|
271
|
-
pi.registerCommand("process:clear", {
|
|
272
|
-
description: "Clear finished processes",
|
|
273
|
-
handler: async (_args, ctx) => {
|
|
274
|
-
const cleared = manager.clearFinished();
|
|
275
|
-
if (cleared > 0) {
|
|
276
|
-
ctx.ui.notify(
|
|
277
|
-
`Cleared ${cleared} finished process${cleared > 1 ? "es" : ""}`,
|
|
278
|
-
"info",
|
|
279
|
-
);
|
|
280
|
-
} else {
|
|
281
|
-
ctx.ui.notify("No finished processes to clear", "info");
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// ── Helpers ────────────────────────────────────────────────────────
|
|
287
|
-
|
|
288
|
-
function startStreaming(
|
|
289
|
-
ui: ExtensionCommandContext["ui"],
|
|
290
|
-
mgr: ProcessManager,
|
|
291
|
-
processId: string,
|
|
292
|
-
) {
|
|
293
|
-
streamingProcessId = processId;
|
|
294
|
-
ui.setWidget(
|
|
295
|
-
LOG_STREAM_WIDGET_ID,
|
|
296
|
-
(tui: { requestRender: () => void }, theme: Theme) => {
|
|
297
|
-
return new LogStreamComponent(tui, theme, mgr, processId);
|
|
298
|
-
},
|
|
299
|
-
{ placement: "aboveEditor" },
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async function pickProcess(
|
|
305
|
-
ctx: ExtensionCommandContext,
|
|
306
|
-
manager: ProcessManager,
|
|
307
|
-
title: string,
|
|
308
|
-
filter?: (proc: ProcessInfo) => boolean,
|
|
309
|
-
): Promise<string | undefined> {
|
|
310
|
-
if (!ctx.hasUI) {
|
|
311
|
-
ctx.ui.notify("Interactive mode required", "error");
|
|
312
|
-
return undefined;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
316
|
-
return new ProcessPickerComponent(
|
|
317
|
-
tui,
|
|
318
|
-
theme,
|
|
319
|
-
(processId?: string) => {
|
|
320
|
-
done(processId ?? null);
|
|
321
|
-
},
|
|
322
|
-
manager,
|
|
323
|
-
title,
|
|
324
|
-
filter,
|
|
325
|
-
);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// RPC fallback or user cancelled.
|
|
329
|
-
if (result === undefined || result === null) {
|
|
330
|
-
return undefined;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
registerSettingsCommand,
|
|
3
|
-
type SettingsSection,
|
|
4
|
-
} from "@aliou/pi-utils-settings";
|
|
5
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import type { ProcessesConfig, ResolvedProcessesConfig } from "../config";
|
|
7
|
-
import { configLoader } from "../config";
|
|
8
|
-
|
|
9
|
-
export function registerProcessesSettings(
|
|
10
|
-
pi: ExtensionAPI,
|
|
11
|
-
onSave?: () => void,
|
|
12
|
-
): void {
|
|
13
|
-
registerSettingsCommand<ProcessesConfig, ResolvedProcessesConfig>(pi, {
|
|
14
|
-
commandName: "process:settings",
|
|
15
|
-
title: "Processes Settings",
|
|
16
|
-
configStore: configLoader,
|
|
17
|
-
buildSections: (
|
|
18
|
-
tabConfig: ProcessesConfig | null,
|
|
19
|
-
resolved: ResolvedProcessesConfig,
|
|
20
|
-
): SettingsSection[] => {
|
|
21
|
-
return [
|
|
22
|
-
{
|
|
23
|
-
label: "Process List",
|
|
24
|
-
items: [
|
|
25
|
-
{
|
|
26
|
-
id: "processList.maxVisibleProcesses",
|
|
27
|
-
label: "Max visible processes",
|
|
28
|
-
description:
|
|
29
|
-
"Maximum processes shown in the /processes list before scrolling",
|
|
30
|
-
currentValue: String(
|
|
31
|
-
tabConfig?.processList?.maxVisibleProcesses ??
|
|
32
|
-
resolved.processList.maxVisibleProcesses,
|
|
33
|
-
),
|
|
34
|
-
values: ["4", "6", "8", "12", "16"],
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "processList.maxPreviewLines",
|
|
38
|
-
label: "Max preview lines",
|
|
39
|
-
description: "Log preview lines shown below the selected process",
|
|
40
|
-
currentValue: String(
|
|
41
|
-
tabConfig?.processList?.maxPreviewLines ??
|
|
42
|
-
resolved.processList.maxPreviewLines,
|
|
43
|
-
),
|
|
44
|
-
values: ["6", "8", "12", "16", "24"],
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
label: "Output Limits",
|
|
50
|
-
items: [
|
|
51
|
-
{
|
|
52
|
-
id: "output.defaultTailLines",
|
|
53
|
-
label: "Default tail lines",
|
|
54
|
-
description:
|
|
55
|
-
"Number of tail lines returned to the agent by default",
|
|
56
|
-
currentValue: String(
|
|
57
|
-
tabConfig?.output?.defaultTailLines ??
|
|
58
|
-
resolved.output.defaultTailLines,
|
|
59
|
-
),
|
|
60
|
-
values: ["50", "100", "200", "500"],
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: "output.maxOutputLines",
|
|
64
|
-
label: "Max output lines",
|
|
65
|
-
description: "Hard cap on output lines returned to the agent",
|
|
66
|
-
currentValue: String(
|
|
67
|
-
tabConfig?.output?.maxOutputLines ??
|
|
68
|
-
resolved.output.maxOutputLines,
|
|
69
|
-
),
|
|
70
|
-
values: ["100", "200", "500", "1000"],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
label: "Widget",
|
|
76
|
-
items: [
|
|
77
|
-
{
|
|
78
|
-
id: "widget.showStatusWidget",
|
|
79
|
-
label: "Show status widget",
|
|
80
|
-
description: "Show process status widget below the editor",
|
|
81
|
-
currentValue:
|
|
82
|
-
(tabConfig?.widget?.showStatusWidget ??
|
|
83
|
-
resolved.widget.showStatusWidget)
|
|
84
|
-
? "on"
|
|
85
|
-
: "off",
|
|
86
|
-
values: ["on", "off"],
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
},
|
|
90
|
-
];
|
|
91
|
-
},
|
|
92
|
-
onSettingChange: (id, newValue, config) => {
|
|
93
|
-
const updated = structuredClone(config);
|
|
94
|
-
// Boolean fields.
|
|
95
|
-
if (id === "widget.showStatusWidget") {
|
|
96
|
-
if (!updated.widget) updated.widget = {};
|
|
97
|
-
updated.widget.showStatusWidget = newValue === "on";
|
|
98
|
-
return updated;
|
|
99
|
-
}
|
|
100
|
-
// Numeric fields.
|
|
101
|
-
const num = Number.parseInt(newValue, 10);
|
|
102
|
-
if (Number.isNaN(num)) return null;
|
|
103
|
-
|
|
104
|
-
switch (id) {
|
|
105
|
-
case "processList.maxVisibleProcesses":
|
|
106
|
-
if (!updated.processList) updated.processList = {};
|
|
107
|
-
updated.processList.maxVisibleProcesses = num;
|
|
108
|
-
break;
|
|
109
|
-
case "processList.maxPreviewLines":
|
|
110
|
-
if (!updated.processList) updated.processList = {};
|
|
111
|
-
updated.processList.maxPreviewLines = num;
|
|
112
|
-
break;
|
|
113
|
-
case "output.defaultTailLines":
|
|
114
|
-
if (!updated.output) updated.output = {};
|
|
115
|
-
updated.output.defaultTailLines = num;
|
|
116
|
-
break;
|
|
117
|
-
case "output.maxOutputLines":
|
|
118
|
-
if (!updated.output) updated.output = {};
|
|
119
|
-
updated.output.maxOutputLines = num;
|
|
120
|
-
break;
|
|
121
|
-
default:
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
return updated;
|
|
125
|
-
},
|
|
126
|
-
onSave,
|
|
127
|
-
});
|
|
128
|
-
}
|