@aliou/pi-processes 0.6.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/package.json +6 -5
- package/src/commands/kill/command.ts +6 -6
- package/src/commands/logs/command.ts +1 -1
- package/src/commands/pin/command.ts +2 -1
- package/src/components/log-dock-component.ts +0 -11
- package/src/components/log-overlay-component.ts +0 -9
- package/src/components/processes-component.ts +32 -16
- package/src/constants/index.ts +4 -0
- package/src/constants/types.ts +36 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/message-renderer.ts +35 -3
- package/src/hooks/process-end.ts +2 -0
- package/src/hooks/process-watch.ts +83 -0
- package/src/hooks/widget/setup.ts +0 -4
- package/src/manager.test.ts +331 -0
- package/src/manager.ts +214 -30
- package/src/tools/actions/debug.ts +148 -0
- package/src/tools/actions/index.ts +126 -10
- package/src/tools/actions/kill.ts +14 -1
- package/src/tools/actions/list.ts +153 -2
- package/src/tools/actions/logs.ts +61 -3
- package/src/tools/actions/output.ts +129 -4
- package/src/tools/actions/start.ts +197 -8
- package/src/tools/actions/write.ts +28 -2
- package/src/tools/index.ts +95 -247
- package/src/utils/command-executor.ts +3 -0
- package/src/utils/format.ts +32 -0
- package/src/utils/index.ts +7 -1
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { ToolBody, ToolCallHeader } from "@aliou/pi-utils-ui";
|
|
2
|
+
import type {
|
|
3
|
+
AgentToolResult,
|
|
4
|
+
Theme,
|
|
5
|
+
ToolRenderResultOptions,
|
|
6
|
+
} from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
1
8
|
import { configLoader } from "../../config";
|
|
2
|
-
import type { ExecuteResult } from "../../constants";
|
|
9
|
+
import type { ExecuteResult, ProcessesDetails } from "../../constants";
|
|
3
10
|
import type { ProcessManager } from "../../manager";
|
|
4
|
-
import { formatStatus, stripAnsi } from "../../utils";
|
|
11
|
+
import { formatStatus, hasAnsi, stripAnsi } from "../../utils";
|
|
5
12
|
|
|
6
13
|
const MAX_BYTES = 50 * 1024; // 50KB
|
|
7
14
|
|
|
@@ -9,6 +16,118 @@ interface OutputParams {
|
|
|
9
16
|
id?: string;
|
|
10
17
|
}
|
|
11
18
|
|
|
19
|
+
export function renderOutputCall(
|
|
20
|
+
args: OutputParams,
|
|
21
|
+
theme: Theme,
|
|
22
|
+
): ToolCallHeader {
|
|
23
|
+
return new ToolCallHeader(
|
|
24
|
+
{
|
|
25
|
+
toolName: "Process",
|
|
26
|
+
action: "output",
|
|
27
|
+
mainArg: args.id,
|
|
28
|
+
},
|
|
29
|
+
theme,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function renderOutputResult(
|
|
34
|
+
result: AgentToolResult<ProcessesDetails>,
|
|
35
|
+
options: ToolRenderResultOptions,
|
|
36
|
+
theme: Theme,
|
|
37
|
+
): ToolBody {
|
|
38
|
+
const { details } = result;
|
|
39
|
+
|
|
40
|
+
if (!details.output) {
|
|
41
|
+
return new ToolBody(
|
|
42
|
+
{
|
|
43
|
+
fields: [
|
|
44
|
+
{
|
|
45
|
+
label: "Error",
|
|
46
|
+
value: "Missing output details",
|
|
47
|
+
showCollapsed: true,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
options,
|
|
52
|
+
theme,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const lines: string[] = [theme.fg("muted", details.message)];
|
|
57
|
+
let hadAnsi = false;
|
|
58
|
+
|
|
59
|
+
if (details.output.stdout.length > 0) {
|
|
60
|
+
lines.push("", theme.fg("accent", "stdout:"));
|
|
61
|
+
for (const line of details.output.stdout.slice(-20)) {
|
|
62
|
+
if (!hadAnsi && hasAnsi(line)) hadAnsi = true;
|
|
63
|
+
lines.push(stripAnsi(line));
|
|
64
|
+
}
|
|
65
|
+
if (details.output.stdout.length > 20) {
|
|
66
|
+
lines.push(
|
|
67
|
+
theme.fg(
|
|
68
|
+
"muted",
|
|
69
|
+
`... (${details.output.stdout.length - 20} more lines)`,
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (details.output.stderr.length > 0) {
|
|
76
|
+
lines.push("", theme.fg("warning", "stderr:"));
|
|
77
|
+
for (const line of details.output.stderr.slice(-10)) {
|
|
78
|
+
if (!hadAnsi && hasAnsi(line)) hadAnsi = true;
|
|
79
|
+
lines.push(theme.fg("warning", stripAnsi(line)));
|
|
80
|
+
}
|
|
81
|
+
if (details.output.stderr.length > 10) {
|
|
82
|
+
lines.push(
|
|
83
|
+
theme.fg(
|
|
84
|
+
"muted",
|
|
85
|
+
`... (${details.output.stderr.length - 10} more lines)`,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (details.logFiles) {
|
|
92
|
+
lines.push(
|
|
93
|
+
"",
|
|
94
|
+
theme.fg("success", "Log files:"),
|
|
95
|
+
` stdout: ${theme.fg("accent", details.logFiles.stdoutFile)}`,
|
|
96
|
+
` stderr: ${theme.fg("accent", details.logFiles.stderrFile)}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (hadAnsi) {
|
|
101
|
+
lines.push(
|
|
102
|
+
"",
|
|
103
|
+
theme.fg("muted", "ANSI escape codes were stripped from output"),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const fields: Array<
|
|
108
|
+
{ label: string; value: string; showCollapsed?: boolean } | Text
|
|
109
|
+
> = [new Text(lines.join("\n"), 0, 0)];
|
|
110
|
+
|
|
111
|
+
// Collapsed summary
|
|
112
|
+
const previewSource =
|
|
113
|
+
details.output.stdout.length > 0
|
|
114
|
+
? details.output.stdout
|
|
115
|
+
: details.output.stderr;
|
|
116
|
+
const preview = previewSource
|
|
117
|
+
.slice(-2)
|
|
118
|
+
.map((l) => stripAnsi(l))
|
|
119
|
+
.join("\n");
|
|
120
|
+
fields.push({
|
|
121
|
+
label: "Output",
|
|
122
|
+
value: preview
|
|
123
|
+
? `${theme.fg("muted", preview)}`
|
|
124
|
+
: theme.fg("muted", "(empty)"),
|
|
125
|
+
showCollapsed: true,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return new ToolBody({ fields }, options, theme);
|
|
129
|
+
}
|
|
130
|
+
|
|
12
131
|
export function executeOutput(
|
|
13
132
|
params: OutputParams,
|
|
14
133
|
manager: ProcessManager,
|
|
@@ -24,7 +143,7 @@ export function executeOutput(
|
|
|
24
143
|
};
|
|
25
144
|
}
|
|
26
145
|
|
|
27
|
-
const proc = manager.
|
|
146
|
+
const proc = manager.get(params.id);
|
|
28
147
|
if (!proc) {
|
|
29
148
|
const message = `Process not found: ${params.id}`;
|
|
30
149
|
return {
|
|
@@ -40,7 +159,7 @@ export function executeOutput(
|
|
|
40
159
|
const { defaultTailLines } = configLoader.getConfig().output;
|
|
41
160
|
const output = manager.getOutput(proc.id, defaultTailLines);
|
|
42
161
|
if (!output) {
|
|
43
|
-
const message = `Could not read output for
|
|
162
|
+
const message = `Could not read output for "${proc.name}" (${proc.id})`;
|
|
44
163
|
return {
|
|
45
164
|
content: [{ type: "text", text: message }],
|
|
46
165
|
details: {
|
|
@@ -79,6 +198,12 @@ export function executeOutput(
|
|
|
79
198
|
success: true,
|
|
80
199
|
message,
|
|
81
200
|
output,
|
|
201
|
+
logFiles: logFiles
|
|
202
|
+
? {
|
|
203
|
+
stdoutFile: logFiles.stdoutFile,
|
|
204
|
+
stderrFile: logFiles.stderrFile,
|
|
205
|
+
}
|
|
206
|
+
: undefined,
|
|
82
207
|
},
|
|
83
208
|
};
|
|
84
209
|
}
|
|
@@ -1,13 +1,123 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
1
|
+
import { ToolBody, ToolCallHeader } from "@aliou/pi-utils-ui";
|
|
2
|
+
import type {
|
|
3
|
+
AgentToolResult,
|
|
4
|
+
ExtensionContext,
|
|
5
|
+
Theme,
|
|
6
|
+
ToolRenderResultOptions,
|
|
7
|
+
} from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
9
|
+
import type { ExecuteResult, ProcessesDetails } from "../../constants";
|
|
3
10
|
import type { ProcessManager } from "../../manager";
|
|
4
11
|
|
|
12
|
+
type WatchStream = "stdout" | "stderr" | "both";
|
|
13
|
+
|
|
14
|
+
interface StartLogWatch {
|
|
15
|
+
pattern: string;
|
|
16
|
+
stream?: WatchStream;
|
|
17
|
+
repeat?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
interface StartParams {
|
|
6
21
|
name?: string;
|
|
7
22
|
command?: string;
|
|
8
23
|
alertOnSuccess?: boolean;
|
|
9
24
|
alertOnFailure?: boolean;
|
|
10
25
|
alertOnKill?: boolean;
|
|
26
|
+
logWatches?: StartLogWatch[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderStartCall(
|
|
30
|
+
args: StartParams,
|
|
31
|
+
theme: Theme,
|
|
32
|
+
): ToolCallHeader {
|
|
33
|
+
const longArgs: Array<{ label?: string; value: string }> = [];
|
|
34
|
+
const optionArgs: Array<{ label: string; value: string }> = [];
|
|
35
|
+
let mainArg: string | undefined;
|
|
36
|
+
|
|
37
|
+
if (args.name) {
|
|
38
|
+
mainArg = `"${args.name}"`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.command) {
|
|
42
|
+
if (!mainArg && args.command.length <= 60) {
|
|
43
|
+
mainArg = args.command;
|
|
44
|
+
} else if (args.command.length <= 60) {
|
|
45
|
+
optionArgs.push({ label: "command", value: args.command });
|
|
46
|
+
} else {
|
|
47
|
+
longArgs.push({ label: "command", value: args.command });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (args.logWatches && args.logWatches.length > 0) {
|
|
52
|
+
optionArgs.push({
|
|
53
|
+
label: "watches",
|
|
54
|
+
value: String(args.logWatches.length),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new ToolCallHeader(
|
|
59
|
+
{
|
|
60
|
+
toolName: "Process",
|
|
61
|
+
action: "start",
|
|
62
|
+
mainArg,
|
|
63
|
+
optionArgs,
|
|
64
|
+
longArgs,
|
|
65
|
+
},
|
|
66
|
+
theme,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function renderStartResult(
|
|
71
|
+
result: AgentToolResult<ProcessesDetails>,
|
|
72
|
+
options: ToolRenderResultOptions,
|
|
73
|
+
theme: Theme,
|
|
74
|
+
): ToolBody {
|
|
75
|
+
const { details } = result;
|
|
76
|
+
const process = details.process;
|
|
77
|
+
|
|
78
|
+
if (!process) {
|
|
79
|
+
return new ToolBody(
|
|
80
|
+
{
|
|
81
|
+
fields: [
|
|
82
|
+
{
|
|
83
|
+
label: "Error",
|
|
84
|
+
value: "Missing process details",
|
|
85
|
+
showCollapsed: true,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
options,
|
|
90
|
+
theme,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const fields: Array<
|
|
95
|
+
{ label: string; value: string; showCollapsed?: boolean } | Text
|
|
96
|
+
> = [
|
|
97
|
+
new Text(
|
|
98
|
+
[
|
|
99
|
+
theme.fg("success", "Started process"),
|
|
100
|
+
` name: ${theme.fg("accent", process.name)}`,
|
|
101
|
+
` command: ${process.command}`,
|
|
102
|
+
` id: ${theme.fg("accent", process.id)}`,
|
|
103
|
+
` pid: ${String(process.pid)}`,
|
|
104
|
+
" Log files:",
|
|
105
|
+
` - stdout: ${theme.fg("accent", process.stdoutFile)}`,
|
|
106
|
+
` - stderr: ${theme.fg("accent", process.stderrFile)}`,
|
|
107
|
+
].join("\n"),
|
|
108
|
+
0,
|
|
109
|
+
0,
|
|
110
|
+
),
|
|
111
|
+
{
|
|
112
|
+
label: "Status",
|
|
113
|
+
value:
|
|
114
|
+
theme.fg("success", "Started") +
|
|
115
|
+
` ${theme.fg("accent", `"${process.name}"`)} (${process.id}, PID: ${process.pid})`,
|
|
116
|
+
showCollapsed: true,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
return new ToolBody({ fields }, options, theme);
|
|
11
121
|
}
|
|
12
122
|
|
|
13
123
|
export function executeStart(
|
|
@@ -36,13 +146,47 @@ export function executeStart(
|
|
|
36
146
|
};
|
|
37
147
|
}
|
|
38
148
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
149
|
+
const watchValidationError = validateLogWatches(params.logWatches);
|
|
150
|
+
if (watchValidationError) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: "text", text: watchValidationError }],
|
|
153
|
+
details: {
|
|
154
|
+
action: "start",
|
|
155
|
+
success: false,
|
|
156
|
+
message: watchValidationError,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let proc: ReturnType<ProcessManager["start"]>;
|
|
162
|
+
try {
|
|
163
|
+
proc = manager.start(params.name, params.command, ctx.cwd, {
|
|
164
|
+
alertOnSuccess: params.alertOnSuccess,
|
|
165
|
+
alertOnFailure: params.alertOnFailure,
|
|
166
|
+
alertOnKill: params.alertOnKill,
|
|
167
|
+
logWatches: params.logWatches,
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const message =
|
|
171
|
+
error instanceof Error
|
|
172
|
+
? `Invalid start options: ${error.message}`
|
|
173
|
+
: "Invalid start options";
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: message }],
|
|
176
|
+
details: {
|
|
177
|
+
action: "start",
|
|
178
|
+
success: false,
|
|
179
|
+
message,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
44
183
|
|
|
45
|
-
const message =
|
|
184
|
+
const message = [
|
|
185
|
+
`Started "${proc.name}" (${proc.id}, PID: ${proc.pid})`,
|
|
186
|
+
"Log files:",
|
|
187
|
+
` stdout: ${proc.stdoutFile}`,
|
|
188
|
+
` stderr: ${proc.stderrFile}`,
|
|
189
|
+
].join("\n");
|
|
46
190
|
return {
|
|
47
191
|
content: [{ type: "text", text: message }],
|
|
48
192
|
details: {
|
|
@@ -53,3 +197,48 @@ export function executeStart(
|
|
|
53
197
|
},
|
|
54
198
|
};
|
|
55
199
|
}
|
|
200
|
+
|
|
201
|
+
function validateLogWatches(watches?: StartLogWatch[]): string | null {
|
|
202
|
+
if (!watches) return null;
|
|
203
|
+
|
|
204
|
+
if (!Array.isArray(watches)) {
|
|
205
|
+
return "Invalid parameter: logWatches must be an array";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const [index, watch] of watches.entries()) {
|
|
209
|
+
if (!watch || typeof watch !== "object") {
|
|
210
|
+
return `Invalid logWatches[${index}]: expected an object`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (
|
|
214
|
+
typeof watch.pattern !== "string" ||
|
|
215
|
+
watch.pattern.trim().length === 0
|
|
216
|
+
) {
|
|
217
|
+
return `Invalid logWatches[${index}].pattern: expected non-empty string`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// Validate regex syntax at process start for fast feedback.
|
|
222
|
+
new RegExp(watch.pattern);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
const message =
|
|
225
|
+
error instanceof Error ? error.message : "invalid regular expression";
|
|
226
|
+
return `Invalid logWatches[${index}].pattern: ${message}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
watch.stream !== undefined &&
|
|
231
|
+
watch.stream !== "stdout" &&
|
|
232
|
+
watch.stream !== "stderr" &&
|
|
233
|
+
watch.stream !== "both"
|
|
234
|
+
) {
|
|
235
|
+
return `Invalid logWatches[${index}].stream: expected stdout, stderr, or both`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (watch.repeat !== undefined && typeof watch.repeat !== "boolean") {
|
|
239
|
+
return `Invalid logWatches[${index}].repeat: expected boolean`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ToolCallHeader } from "@aliou/pi-utils-ui";
|
|
2
|
+
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
1
3
|
import type { ExecuteResult } from "../../constants";
|
|
2
4
|
import type { ProcessManager } from "../../manager";
|
|
3
5
|
|
|
@@ -7,6 +9,30 @@ interface WriteParams {
|
|
|
7
9
|
end?: boolean;
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
export function renderWriteCall(
|
|
13
|
+
args: WriteParams,
|
|
14
|
+
theme: Theme,
|
|
15
|
+
): ToolCallHeader {
|
|
16
|
+
const optionArgs: Array<{ label: string; value: string }> = [];
|
|
17
|
+
|
|
18
|
+
if (args.input) {
|
|
19
|
+
optionArgs.push({ label: "input", value: args.input });
|
|
20
|
+
if (args.end) {
|
|
21
|
+
optionArgs.push({ label: "end", value: "true" });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new ToolCallHeader(
|
|
26
|
+
{
|
|
27
|
+
toolName: "Process",
|
|
28
|
+
action: "write",
|
|
29
|
+
mainArg: args.id,
|
|
30
|
+
optionArgs,
|
|
31
|
+
},
|
|
32
|
+
theme,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
export function executeWrite(
|
|
11
37
|
params: WriteParams,
|
|
12
38
|
manager: ProcessManager,
|
|
@@ -35,7 +61,7 @@ export function executeWrite(
|
|
|
35
61
|
};
|
|
36
62
|
}
|
|
37
63
|
|
|
38
|
-
const process = manager.
|
|
64
|
+
const process = manager.get(id);
|
|
39
65
|
if (!process) {
|
|
40
66
|
return {
|
|
41
67
|
content: [{ type: "text", text: `Process not found: ${id}` }],
|
|
@@ -75,7 +101,7 @@ export function executeWrite(
|
|
|
75
101
|
content: [
|
|
76
102
|
{
|
|
77
103
|
type: "text",
|
|
78
|
-
text: `Wrote ${input.length} bytes to ${process.id}${suffix}`,
|
|
104
|
+
text: `Wrote ${input.length} bytes to "${process.name}" (${process.id})${suffix}`,
|
|
79
105
|
},
|
|
80
106
|
],
|
|
81
107
|
details: {
|