@aliou/pi-processes 0.1.1 → 0.2.0
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 +27 -0
- package/commands/index.ts +3 -465
- package/hooks/cleanup.ts +3 -1
- package/hooks/process-end.ts +9 -21
- package/hooks/widget.ts +33 -62
- package/index.ts +8 -0
- package/manager.ts +254 -181
- package/package.json +6 -2
- package/tools/actions/clear.ts +20 -0
- package/tools/actions/index.ts +49 -0
- package/tools/actions/kill.ts +76 -0
- package/tools/actions/list.ts +37 -0
- package/tools/actions/logs.ts +59 -0
- package/tools/actions/output.ts +73 -0
- package/tools/actions/start.ts +55 -0
- package/tools/index.ts +29 -312
- package/constants.ts +0 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ExecuteResult } from "../../constants";
|
|
2
|
+
import type { ProcessManager } from "../../manager";
|
|
3
|
+
|
|
4
|
+
interface KillParams {
|
|
5
|
+
id?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function executeKill(
|
|
9
|
+
params: KillParams,
|
|
10
|
+
manager: ProcessManager,
|
|
11
|
+
): Promise<ExecuteResult> {
|
|
12
|
+
if (!params.id) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: "Missing required parameter: id" }],
|
|
15
|
+
details: {
|
|
16
|
+
action: "kill",
|
|
17
|
+
success: false,
|
|
18
|
+
message: "Missing required parameter: id",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const proc = manager.find(params.id);
|
|
24
|
+
if (!proc) {
|
|
25
|
+
const message = `Process not found: ${params.id}`;
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: message }],
|
|
28
|
+
details: {
|
|
29
|
+
action: "kill",
|
|
30
|
+
success: false,
|
|
31
|
+
message,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = await manager.kill(proc.id, {
|
|
37
|
+
signal: "SIGTERM",
|
|
38
|
+
timeoutMs: 3000,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (result.ok) {
|
|
42
|
+
const message = `Terminated "${proc.name}" (${proc.id})`;
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: message }],
|
|
45
|
+
details: {
|
|
46
|
+
action: "kill",
|
|
47
|
+
success: true,
|
|
48
|
+
message,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (result.reason === "timeout") {
|
|
54
|
+
const message =
|
|
55
|
+
`SIGTERM timed out for "${proc.name}" (${proc.id}). ` +
|
|
56
|
+
"Run /processes and press x on terminate_timeout to force kill (SIGKILL).";
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: message }],
|
|
59
|
+
details: {
|
|
60
|
+
action: "kill",
|
|
61
|
+
success: false,
|
|
62
|
+
message,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const message = `Failed to terminate "${proc.name}" (${proc.id})`;
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: message }],
|
|
70
|
+
details: {
|
|
71
|
+
action: "kill",
|
|
72
|
+
success: false,
|
|
73
|
+
message,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ExecuteResult } from "../../constants";
|
|
2
|
+
import type { ProcessManager } from "../../manager";
|
|
3
|
+
import { formatRuntime, formatStatus, truncateCmd } from "../../utils";
|
|
4
|
+
|
|
5
|
+
export function executeList(manager: ProcessManager): ExecuteResult {
|
|
6
|
+
const processes = manager.list();
|
|
7
|
+
|
|
8
|
+
if (processes.length === 0) {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: "No background processes running" }],
|
|
11
|
+
details: {
|
|
12
|
+
action: "list",
|
|
13
|
+
success: true,
|
|
14
|
+
message: "No background processes running",
|
|
15
|
+
processes: [],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const summary = processes
|
|
21
|
+
.map(
|
|
22
|
+
(p) =>
|
|
23
|
+
`${p.id} "${p.name}": ${truncateCmd(p.command)} [${formatStatus(p)}] ${formatRuntime(p.startTime, p.endTime)}`,
|
|
24
|
+
)
|
|
25
|
+
.join("\n");
|
|
26
|
+
|
|
27
|
+
const message = `${processes.length} process(es):\n${summary}`;
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: message }],
|
|
30
|
+
details: {
|
|
31
|
+
action: "list",
|
|
32
|
+
success: true,
|
|
33
|
+
message,
|
|
34
|
+
processes,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ExecuteResult } from "../../constants";
|
|
2
|
+
import type { ProcessManager } from "../../manager";
|
|
3
|
+
|
|
4
|
+
interface LogsParams {
|
|
5
|
+
id?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function executeLogs(
|
|
9
|
+
params: LogsParams,
|
|
10
|
+
manager: ProcessManager,
|
|
11
|
+
): ExecuteResult {
|
|
12
|
+
if (!params.id) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: "Missing required parameter: id" }],
|
|
15
|
+
details: {
|
|
16
|
+
action: "logs",
|
|
17
|
+
success: false,
|
|
18
|
+
message: "Missing required parameter: id",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const proc = manager.find(params.id);
|
|
24
|
+
if (!proc) {
|
|
25
|
+
const message = `Process not found: ${params.id}`;
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: message }],
|
|
28
|
+
details: {
|
|
29
|
+
action: "logs",
|
|
30
|
+
success: false,
|
|
31
|
+
message,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const logFiles = manager.getLogFiles(proc.id);
|
|
37
|
+
if (!logFiles) {
|
|
38
|
+
const message = `Could not get log files for: ${proc.id}`;
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: message }],
|
|
41
|
+
details: {
|
|
42
|
+
action: "logs",
|
|
43
|
+
success: false,
|
|
44
|
+
message,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const message = `Log files for "${proc.name}" (${proc.id}):\n stdout: ${logFiles.stdoutFile}\n stderr: ${logFiles.stderrFile}\n\nUse the read tool to inspect these files.`;
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: message }],
|
|
52
|
+
details: {
|
|
53
|
+
action: "logs",
|
|
54
|
+
success: true,
|
|
55
|
+
message,
|
|
56
|
+
logFiles,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ExecuteResult } from "../../constants";
|
|
2
|
+
import type { ProcessManager } from "../../manager";
|
|
3
|
+
import { formatStatus } from "../../utils";
|
|
4
|
+
|
|
5
|
+
interface OutputParams {
|
|
6
|
+
id?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function executeOutput(
|
|
10
|
+
params: OutputParams,
|
|
11
|
+
manager: ProcessManager,
|
|
12
|
+
): ExecuteResult {
|
|
13
|
+
if (!params.id) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: "text", text: "Missing required parameter: id" }],
|
|
16
|
+
details: {
|
|
17
|
+
action: "output",
|
|
18
|
+
success: false,
|
|
19
|
+
message: "Missing required parameter: id",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const proc = manager.find(params.id);
|
|
25
|
+
if (!proc) {
|
|
26
|
+
const message = `Process not found: ${params.id}`;
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: message }],
|
|
29
|
+
details: {
|
|
30
|
+
action: "output",
|
|
31
|
+
success: false,
|
|
32
|
+
message,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const output = manager.getOutput(proc.id);
|
|
38
|
+
if (!output) {
|
|
39
|
+
const message = `Could not read output for: ${proc.id}`;
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: message }],
|
|
42
|
+
details: {
|
|
43
|
+
action: "output",
|
|
44
|
+
success: false,
|
|
45
|
+
message,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const stdoutLines = output.stdout.length;
|
|
51
|
+
const stderrLines = output.stderr.length;
|
|
52
|
+
const message = `"${proc.name}" (${proc.id}) [${formatStatus(proc)}]: ${stdoutLines} stdout lines, ${stderrLines} stderr lines`;
|
|
53
|
+
|
|
54
|
+
const outputParts: string[] = [message];
|
|
55
|
+
if (output.stdout.length > 0) {
|
|
56
|
+
outputParts.push("\n--- stdout (last 100 lines) ---");
|
|
57
|
+
outputParts.push(...output.stdout.slice(-100));
|
|
58
|
+
}
|
|
59
|
+
if (output.stderr.length > 0) {
|
|
60
|
+
outputParts.push("\n--- stderr (last 100 lines) ---");
|
|
61
|
+
outputParts.push(...output.stderr.slice(-100));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: outputParts.join("\n") }],
|
|
66
|
+
details: {
|
|
67
|
+
action: "output",
|
|
68
|
+
success: true,
|
|
69
|
+
message,
|
|
70
|
+
output,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { ExecuteResult } from "../../constants";
|
|
3
|
+
import type { ProcessManager } from "../../manager";
|
|
4
|
+
|
|
5
|
+
interface StartParams {
|
|
6
|
+
name?: string;
|
|
7
|
+
command?: string;
|
|
8
|
+
notifyOnSuccess?: boolean;
|
|
9
|
+
notifyOnFailure?: boolean;
|
|
10
|
+
notifyOnKill?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function executeStart(
|
|
14
|
+
params: StartParams,
|
|
15
|
+
manager: ProcessManager,
|
|
16
|
+
ctx: ExtensionContext,
|
|
17
|
+
): ExecuteResult {
|
|
18
|
+
if (!params.name) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: "Missing required parameter: name" }],
|
|
21
|
+
details: {
|
|
22
|
+
action: "start",
|
|
23
|
+
success: false,
|
|
24
|
+
message: "Missing required parameter: name",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (!params.command) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: "Missing required parameter: command" }],
|
|
31
|
+
details: {
|
|
32
|
+
action: "start",
|
|
33
|
+
success: false,
|
|
34
|
+
message: "Missing required parameter: command",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const proc = manager.start(params.name, params.command, ctx.cwd, {
|
|
40
|
+
notifyOnSuccess: params.notifyOnSuccess,
|
|
41
|
+
notifyOnFailure: params.notifyOnFailure,
|
|
42
|
+
notifyOnKill: params.notifyOnKill,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const message = `Started "${proc.name}" (${proc.id}, PID: ${proc.pid})\nLogs: ${proc.stdoutFile}`;
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: message }],
|
|
48
|
+
details: {
|
|
49
|
+
action: "start",
|
|
50
|
+
success: true,
|
|
51
|
+
message,
|
|
52
|
+
process: proc,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
package/tools/index.ts
CHANGED
|
@@ -2,13 +2,15 @@ import { StringEnum } from "@mariozechner/pi-ai";
|
|
|
2
2
|
import type {
|
|
3
3
|
AgentToolResult,
|
|
4
4
|
ExtensionAPI,
|
|
5
|
-
ExtensionContext,
|
|
6
5
|
Theme,
|
|
7
6
|
ToolRenderResultOptions,
|
|
8
7
|
} from "@mariozechner/pi-coding-agent";
|
|
9
8
|
import { Text } from "@mariozechner/pi-tui";
|
|
10
9
|
import { type Static, Type } from "@sinclair/typebox";
|
|
11
|
-
import type {
|
|
10
|
+
import type { ProcessesDetails } from "../constants";
|
|
11
|
+
import type { ProcessManager } from "../manager";
|
|
12
|
+
import { formatRuntime, truncateCmd } from "../utils";
|
|
13
|
+
import { executeAction } from "./actions";
|
|
12
14
|
|
|
13
15
|
const ProcessesParams = Type.Object({
|
|
14
16
|
action: StringEnum(
|
|
@@ -24,7 +26,7 @@ const ProcessesParams = Type.Object({
|
|
|
24
26
|
name: Type.Optional(
|
|
25
27
|
Type.String({
|
|
26
28
|
description:
|
|
27
|
-
"Friendly name for the process (
|
|
29
|
+
"Friendly name for the process (required for start, e.g. 'backend-dev', 'test-runner')",
|
|
28
30
|
}),
|
|
29
31
|
),
|
|
30
32
|
id: Type.Optional(
|
|
@@ -55,56 +57,12 @@ const ProcessesParams = Type.Object({
|
|
|
55
57
|
|
|
56
58
|
type ProcessesParamsType = Static<typeof ProcessesParams>;
|
|
57
59
|
|
|
58
|
-
interface ProcessesDetails {
|
|
59
|
-
action: string;
|
|
60
|
-
success: boolean;
|
|
61
|
-
message: string;
|
|
62
|
-
process?: ProcessInfo;
|
|
63
|
-
processes?: ProcessInfo[];
|
|
64
|
-
output?: { stdout: string[]; stderr: string[]; status: string };
|
|
65
|
-
logFiles?: { stdoutFile: string; stderrFile: string };
|
|
66
|
-
cleared?: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface ExecuteResult {
|
|
70
|
-
content: Array<{ type: "text"; text: string }>;
|
|
71
|
-
details: ProcessesDetails;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function formatRuntime(startTime: number, endTime: number | null): string {
|
|
75
|
-
const end = endTime ?? Date.now();
|
|
76
|
-
const ms = end - startTime;
|
|
77
|
-
const seconds = Math.floor(ms / 1000);
|
|
78
|
-
const minutes = Math.floor(seconds / 60);
|
|
79
|
-
const hours = Math.floor(minutes / 60);
|
|
80
|
-
|
|
81
|
-
if (hours > 0) {
|
|
82
|
-
return `${hours}h ${minutes % 60}m`;
|
|
83
|
-
}
|
|
84
|
-
if (minutes > 0) {
|
|
85
|
-
return `${minutes}m ${seconds % 60}s`;
|
|
86
|
-
}
|
|
87
|
-
return `${seconds}s`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function formatStatus(proc: ProcessInfo): string {
|
|
91
|
-
if (proc.status === "running") return "running";
|
|
92
|
-
if (proc.status === "killed") return "killed";
|
|
93
|
-
if (proc.success) return "exited(0)";
|
|
94
|
-
return `exited(${proc.exitCode ?? "?"})`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function truncateCmd(cmd: string, max = 40): string {
|
|
98
|
-
if (cmd.length <= max) return cmd;
|
|
99
|
-
return `${cmd.slice(0, max - 3)}...`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
60
|
export function setupProcessesTools(pi: ExtensionAPI, manager: ProcessManager) {
|
|
103
61
|
pi.registerTool<typeof ProcessesParams, ProcessesDetails>({
|
|
104
62
|
name: "processes",
|
|
105
63
|
label: "Processes",
|
|
106
64
|
description: `Manage background processes. Actions:
|
|
107
|
-
- start: Run command in background (requires '
|
|
65
|
+
- start: Run command in background (requires 'name' and 'command')
|
|
108
66
|
- notifyOnSuccess (default: false): Get notified when process completes successfully
|
|
109
67
|
- notifyOnFailure (default: true): Get notified when process crashes/fails
|
|
110
68
|
- notifyOnKill (default: false): Get notified if killed by external signal (killing via tool never notifies)
|
|
@@ -120,265 +78,8 @@ Note: User always sees notifications in UI. Notification preferences only contro
|
|
|
120
78
|
|
|
121
79
|
parameters: ProcessesParams,
|
|
122
80
|
|
|
123
|
-
async execute(
|
|
124
|
-
|
|
125
|
-
params: ProcessesParamsType,
|
|
126
|
-
_onUpdate: unknown,
|
|
127
|
-
ctx: ExtensionContext,
|
|
128
|
-
_signal?: AbortSignal,
|
|
129
|
-
): Promise<ExecuteResult> {
|
|
130
|
-
switch (params.action) {
|
|
131
|
-
case "start": {
|
|
132
|
-
if (!params.command) {
|
|
133
|
-
return {
|
|
134
|
-
content: [
|
|
135
|
-
{ type: "text", text: "Missing required parameter: command" },
|
|
136
|
-
],
|
|
137
|
-
details: {
|
|
138
|
-
action: "start",
|
|
139
|
-
success: false,
|
|
140
|
-
message: "Missing required parameter: command",
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
const proc = manager.start(params.command, ctx.cwd, params.name, {
|
|
145
|
-
notifyOnSuccess: params.notifyOnSuccess,
|
|
146
|
-
notifyOnFailure: params.notifyOnFailure,
|
|
147
|
-
notifyOnKill: params.notifyOnKill,
|
|
148
|
-
});
|
|
149
|
-
const message = `Started "${proc.name}" (${proc.id}, PID: ${proc.pid})\nLogs: ${proc.stdoutFile}`;
|
|
150
|
-
return {
|
|
151
|
-
content: [{ type: "text", text: message }],
|
|
152
|
-
details: {
|
|
153
|
-
action: "start",
|
|
154
|
-
success: true,
|
|
155
|
-
message,
|
|
156
|
-
process: proc,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
case "list": {
|
|
162
|
-
const processes = manager.list();
|
|
163
|
-
if (processes.length === 0) {
|
|
164
|
-
return {
|
|
165
|
-
content: [
|
|
166
|
-
{ type: "text", text: "No background processes running" },
|
|
167
|
-
],
|
|
168
|
-
details: {
|
|
169
|
-
action: "list",
|
|
170
|
-
success: true,
|
|
171
|
-
message: "No background processes running",
|
|
172
|
-
processes: [],
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const summary = processes
|
|
177
|
-
.map(
|
|
178
|
-
(p) =>
|
|
179
|
-
`${p.id} "${p.name}": ${truncateCmd(p.command)} [${formatStatus(p)}] ${formatRuntime(p.startTime, p.endTime)}`,
|
|
180
|
-
)
|
|
181
|
-
.join("\n");
|
|
182
|
-
const message = `${processes.length} process(es):\n${summary}`;
|
|
183
|
-
return {
|
|
184
|
-
content: [{ type: "text", text: message }],
|
|
185
|
-
details: {
|
|
186
|
-
action: "list",
|
|
187
|
-
success: true,
|
|
188
|
-
message,
|
|
189
|
-
processes,
|
|
190
|
-
},
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
case "output": {
|
|
195
|
-
if (!params.id) {
|
|
196
|
-
return {
|
|
197
|
-
content: [
|
|
198
|
-
{ type: "text", text: "Missing required parameter: id" },
|
|
199
|
-
],
|
|
200
|
-
details: {
|
|
201
|
-
action: "output",
|
|
202
|
-
success: false,
|
|
203
|
-
message: "Missing required parameter: id",
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
const proc = manager.find(params.id);
|
|
208
|
-
if (!proc) {
|
|
209
|
-
const message = `Process not found: ${params.id}`;
|
|
210
|
-
return {
|
|
211
|
-
content: [{ type: "text", text: message }],
|
|
212
|
-
details: {
|
|
213
|
-
action: "output",
|
|
214
|
-
success: false,
|
|
215
|
-
message,
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
const output = manager.getOutput(proc.id);
|
|
220
|
-
if (!output) {
|
|
221
|
-
const message = `Could not read output for: ${proc.id}`;
|
|
222
|
-
return {
|
|
223
|
-
content: [{ type: "text", text: message }],
|
|
224
|
-
details: {
|
|
225
|
-
action: "output",
|
|
226
|
-
success: false,
|
|
227
|
-
message,
|
|
228
|
-
},
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
const stdoutLines = output.stdout.length;
|
|
232
|
-
const stderrLines = output.stderr.length;
|
|
233
|
-
const message = `"${proc.name}" (${proc.id}) [${formatStatus(proc)}]: ${stdoutLines} stdout lines, ${stderrLines} stderr lines`;
|
|
234
|
-
|
|
235
|
-
const outputParts: string[] = [message];
|
|
236
|
-
if (output.stdout.length > 0) {
|
|
237
|
-
outputParts.push("\n--- stdout (last 100 lines) ---");
|
|
238
|
-
outputParts.push(...output.stdout.slice(-100));
|
|
239
|
-
}
|
|
240
|
-
if (output.stderr.length > 0) {
|
|
241
|
-
outputParts.push("\n--- stderr (last 100 lines) ---");
|
|
242
|
-
outputParts.push(...output.stderr.slice(-100));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
content: [{ type: "text", text: outputParts.join("\n") }],
|
|
247
|
-
details: {
|
|
248
|
-
action: "output",
|
|
249
|
-
success: true,
|
|
250
|
-
message,
|
|
251
|
-
output,
|
|
252
|
-
},
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
case "logs": {
|
|
257
|
-
if (!params.id) {
|
|
258
|
-
return {
|
|
259
|
-
content: [
|
|
260
|
-
{ type: "text", text: "Missing required parameter: id" },
|
|
261
|
-
],
|
|
262
|
-
details: {
|
|
263
|
-
action: "logs",
|
|
264
|
-
success: false,
|
|
265
|
-
message: "Missing required parameter: id",
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
const proc = manager.find(params.id);
|
|
270
|
-
if (!proc) {
|
|
271
|
-
const message = `Process not found: ${params.id}`;
|
|
272
|
-
return {
|
|
273
|
-
content: [{ type: "text", text: message }],
|
|
274
|
-
details: {
|
|
275
|
-
action: "logs",
|
|
276
|
-
success: false,
|
|
277
|
-
message,
|
|
278
|
-
},
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
const logFiles = manager.getLogFiles(proc.id);
|
|
282
|
-
if (!logFiles) {
|
|
283
|
-
const message = `Could not get log files for: ${proc.id}`;
|
|
284
|
-
return {
|
|
285
|
-
content: [{ type: "text", text: message }],
|
|
286
|
-
details: {
|
|
287
|
-
action: "logs",
|
|
288
|
-
success: false,
|
|
289
|
-
message,
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
const message = `Log files for "${proc.name}" (${proc.id}):\n stdout: ${logFiles.stdoutFile}\n stderr: ${logFiles.stderrFile}\n\nUse the read tool to inspect these files.`;
|
|
294
|
-
return {
|
|
295
|
-
content: [{ type: "text", text: message }],
|
|
296
|
-
details: {
|
|
297
|
-
action: "logs",
|
|
298
|
-
success: true,
|
|
299
|
-
message,
|
|
300
|
-
logFiles,
|
|
301
|
-
},
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
case "kill": {
|
|
306
|
-
if (!params.id) {
|
|
307
|
-
return {
|
|
308
|
-
content: [
|
|
309
|
-
{ type: "text", text: "Missing required parameter: id" },
|
|
310
|
-
],
|
|
311
|
-
details: {
|
|
312
|
-
action: "kill",
|
|
313
|
-
success: false,
|
|
314
|
-
message: "Missing required parameter: id",
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
const proc = manager.find(params.id);
|
|
319
|
-
if (!proc) {
|
|
320
|
-
const message = `Process not found: ${params.id}`;
|
|
321
|
-
return {
|
|
322
|
-
content: [{ type: "text", text: message }],
|
|
323
|
-
details: {
|
|
324
|
-
action: "kill",
|
|
325
|
-
success: false,
|
|
326
|
-
message,
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
const killed = manager.kill(proc.id);
|
|
331
|
-
if (killed) {
|
|
332
|
-
const message = `Killed "${proc.name}" (${proc.id})`;
|
|
333
|
-
return {
|
|
334
|
-
content: [{ type: "text", text: message }],
|
|
335
|
-
details: {
|
|
336
|
-
action: "kill",
|
|
337
|
-
success: true,
|
|
338
|
-
message,
|
|
339
|
-
},
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
const message = `Failed to kill "${proc.name}" (${proc.id})`;
|
|
343
|
-
return {
|
|
344
|
-
content: [{ type: "text", text: message }],
|
|
345
|
-
details: {
|
|
346
|
-
action: "kill",
|
|
347
|
-
success: false,
|
|
348
|
-
message,
|
|
349
|
-
},
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
case "clear": {
|
|
354
|
-
const cleared = manager.clearFinished();
|
|
355
|
-
const message =
|
|
356
|
-
cleared > 0
|
|
357
|
-
? `Cleared ${cleared} finished process(es)`
|
|
358
|
-
: "No finished processes to clear";
|
|
359
|
-
return {
|
|
360
|
-
content: [{ type: "text", text: message }],
|
|
361
|
-
details: {
|
|
362
|
-
action: "clear",
|
|
363
|
-
success: true,
|
|
364
|
-
message,
|
|
365
|
-
cleared,
|
|
366
|
-
},
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
default:
|
|
371
|
-
return {
|
|
372
|
-
content: [
|
|
373
|
-
{ type: "text", text: `Unknown action: ${params.action}` },
|
|
374
|
-
],
|
|
375
|
-
details: {
|
|
376
|
-
action: params.action,
|
|
377
|
-
success: false,
|
|
378
|
-
message: `Unknown action: ${params.action}`,
|
|
379
|
-
},
|
|
380
|
-
};
|
|
381
|
-
}
|
|
81
|
+
async execute(_toolCallId, params, _onUpdate, ctx) {
|
|
82
|
+
return executeAction(params, manager, ctx);
|
|
382
83
|
},
|
|
383
84
|
|
|
384
85
|
renderCall(args: ProcessesParamsType, theme: Theme): Text {
|
|
@@ -491,12 +192,28 @@ Note: User always sees notifications in UI. Notification preferences only contro
|
|
|
491
192
|
theme.fg("success", `${details.processes.length} process(es):`),
|
|
492
193
|
);
|
|
493
194
|
for (const p of details.processes) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
195
|
+
let status: string;
|
|
196
|
+
switch (p.status) {
|
|
197
|
+
case "running":
|
|
198
|
+
status = theme.fg("accent", "running");
|
|
199
|
+
break;
|
|
200
|
+
case "terminating":
|
|
201
|
+
status = theme.fg("warning", "terminating");
|
|
202
|
+
break;
|
|
203
|
+
case "terminate_timeout":
|
|
204
|
+
status = theme.fg("error", "terminate_timeout");
|
|
205
|
+
break;
|
|
206
|
+
case "killed":
|
|
207
|
+
status = theme.fg("warning", "killed");
|
|
208
|
+
break;
|
|
209
|
+
case "exited":
|
|
210
|
+
status = p.success
|
|
498
211
|
? theme.fg("success", "exit(0)")
|
|
499
|
-
: theme.fg("error", `exit(${p.exitCode})`);
|
|
212
|
+
: theme.fg("error", `exit(${p.exitCode ?? "?"})`);
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
status = theme.fg("muted", p.status);
|
|
216
|
+
}
|
|
500
217
|
lines.push(
|
|
501
218
|
` ${p.id} ${theme.fg("accent", `"${p.name}"`)}: ${truncateCmd(p.command)} [${status}] ${formatRuntime(p.startTime, p.endTime)}`,
|
|
502
219
|
);
|
package/constants.ts
DELETED