@aliou/pi-processes 0.9.4 → 0.10.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.
Files changed (171) hide show
  1. package/CONTRIBUTING.md +40 -42
  2. package/README.md +54 -84
  3. package/extensions/process-tabs.ts +26 -0
  4. package/extensions/processes/client.ts +194 -0
  5. package/extensions/processes/commands/clear.ts +29 -0
  6. package/extensions/processes/commands/kill.ts +152 -0
  7. package/extensions/processes/commands/overview.ts +102 -0
  8. package/extensions/processes/components/overview-component.ts +729 -0
  9. package/extensions/processes/components/overview-panel.ts +177 -0
  10. package/extensions/processes/config/defaults.ts +28 -0
  11. package/extensions/processes/config/index.ts +13 -0
  12. package/extensions/processes/config/loader.ts +62 -0
  13. package/extensions/processes/config/migrations/000-import-legacy-process-config.ts +113 -0
  14. package/extensions/processes/config/migrations/001-v0-9-4-to-v0-10-0-config.ts +184 -0
  15. package/extensions/processes/config/migrations/002-stamp-v0-10-0-config-version.ts +21 -0
  16. package/extensions/processes/config/migrations/index.ts +25 -0
  17. package/extensions/processes/config/schema.ts +9 -0
  18. package/extensions/processes/config/types.ts +72 -0
  19. package/extensions/processes/constants.ts +1 -0
  20. package/extensions/processes/handlers/commands.ts +113 -0
  21. package/extensions/processes/handlers/kill-process.ts +31 -0
  22. package/extensions/processes/handlers/notifications.ts +147 -0
  23. package/extensions/processes/handlers/requests.ts +114 -0
  24. package/extensions/processes/handlers/subscriptions.ts +144 -0
  25. package/extensions/processes/hooks/background-blocker.ts +201 -0
  26. package/extensions/processes/hooks/cleanup.ts +43 -0
  27. package/extensions/processes/hooks/event-bridge.ts +32 -0
  28. package/extensions/processes/i18n/index.ts +3 -0
  29. package/extensions/processes/i18n/messages.ts +27 -0
  30. package/extensions/processes/i18n/translator.ts +49 -0
  31. package/extensions/processes/index.ts +109 -0
  32. package/extensions/processes/message-renderer.ts +117 -0
  33. package/extensions/processes/notification-sender.ts +43 -0
  34. package/extensions/processes/notifications/classify.ts +27 -0
  35. package/extensions/processes/notifications/log-matchers.ts +146 -0
  36. package/extensions/processes/notifications/registry.ts +225 -0
  37. package/extensions/processes/notifications/render-content.ts +122 -0
  38. package/extensions/processes/notifications/service.ts +336 -0
  39. package/extensions/processes/notifications/types.ts +11 -0
  40. package/extensions/processes/settings/apply-setting-change.ts +30 -0
  41. package/extensions/processes/settings/build-sections.ts +420 -0
  42. package/extensions/processes/settings/index.ts +42 -0
  43. package/extensions/processes/tools/clear/index.ts +21 -0
  44. package/extensions/processes/tools/clear/render.ts +40 -0
  45. package/extensions/processes/tools/components/index.ts +4 -0
  46. package/extensions/processes/tools/components/process-action-header.ts +31 -0
  47. package/extensions/processes/tools/components/process-action-title.ts +17 -0
  48. package/extensions/processes/tools/components/tool-layout.ts +70 -0
  49. package/extensions/processes/tools/components/watch.ts +53 -0
  50. package/extensions/processes/tools/index.ts +265 -0
  51. package/extensions/processes/tools/list/index.ts +157 -0
  52. package/extensions/processes/tools/list/render.ts +211 -0
  53. package/extensions/processes/tools/notify.ts +191 -0
  54. package/extensions/processes/tools/output/index.ts +307 -0
  55. package/extensions/processes/tools/output/render.ts +229 -0
  56. package/extensions/processes/tools/schema.ts +263 -0
  57. package/extensions/processes/tools/start/index.ts +64 -0
  58. package/extensions/processes/tools/start/render.ts +67 -0
  59. package/extensions/processes/tools/stop/index.ts +35 -0
  60. package/extensions/processes/tools/stop/render.ts +43 -0
  61. package/extensions/processes/tools/update/index.ts +320 -0
  62. package/extensions/processes/tools/update/render.ts +251 -0
  63. package/extensions/processes/tools/utils.ts +112 -0
  64. package/extensions/processes/tools/watch-format.ts +14 -0
  65. package/extensions/processes/tools/write/index.ts +75 -0
  66. package/extensions/processes/tools/write/render.ts +68 -0
  67. package/extensions/processes/utils/truncate.ts +285 -0
  68. package/extensions/processes-dock/client.ts +60 -0
  69. package/extensions/processes-dock/commands/dock.ts +70 -0
  70. package/extensions/processes-dock/commands/pin.ts +166 -0
  71. package/extensions/processes-dock/components/log-dock-component.ts +375 -0
  72. package/extensions/processes-dock/dock-state.ts +33 -0
  73. package/extensions/processes-dock/index.ts +28 -0
  74. package/extensions/processes-dock/logs-client.ts +5 -0
  75. package/extensions/processes-dock/widget/setup.ts +520 -0
  76. package/extensions/processes-dock/widget/status.ts +149 -0
  77. package/extensions/processes-dock/widget/types.ts +16 -0
  78. package/extensions/processes-logs/client.ts +51 -0
  79. package/extensions/processes-logs/commands/logs.ts +108 -0
  80. package/extensions/processes-logs/completions.ts +31 -0
  81. package/extensions/processes-logs/components/log-file-viewer.ts +274 -0
  82. package/extensions/processes-logs/components/log-overlay-component.ts +685 -0
  83. package/extensions/processes-logs/index.ts +27 -0
  84. package/extensions/processes-logs/logs-client.ts +101 -0
  85. package/extensions/shared/line-buffer.ts +35 -0
  86. package/extensions/shared/output-payload.ts +28 -0
  87. package/extensions/shared/ui.ts +146 -0
  88. package/package.json +47 -20
  89. package/schema.json +134 -0
  90. package/skills/pi-processes/SKILL.md +496 -25
  91. package/src/get-manager.ts +15 -0
  92. package/src/manager/index.ts +182 -0
  93. package/src/manager/internal-types.ts +130 -0
  94. package/src/manager/limits.ts +15 -0
  95. package/src/manager/process-log-store.ts +422 -0
  96. package/src/manager/process-output.ts +345 -0
  97. package/src/manager/process-registry.ts +78 -0
  98. package/src/manager/process-runtime-controller.ts +499 -0
  99. package/src/protocol/broadcasts.ts +14 -0
  100. package/src/protocol/channels.ts +30 -0
  101. package/src/protocol/commands.ts +24 -0
  102. package/src/protocol/index.ts +34 -0
  103. package/src/protocol/logs.ts +23 -0
  104. package/src/protocol/notifications.ts +48 -0
  105. package/src/protocol/requests.ts +76 -0
  106. package/src/types.ts +66 -0
  107. package/src/utils/ansi.ts +2 -4
  108. package/src/utils/format.ts +24 -32
  109. package/src/utils/index.ts +5 -1
  110. package/src/utils/is-record.ts +3 -0
  111. package/src/utils/match-line.ts +32 -0
  112. package/src/utils/platform.ts +13 -0
  113. package/src/utils/shorten-path.ts +11 -0
  114. package/src/utils/signals.ts +22 -0
  115. package/src/commands/clear/command.ts +0 -14
  116. package/src/commands/clear/index.ts +0 -1
  117. package/src/commands/completions.ts +0 -38
  118. package/src/commands/dock/command.ts +0 -29
  119. package/src/commands/dock/index.ts +0 -1
  120. package/src/commands/index.ts +0 -33
  121. package/src/commands/kill/command.ts +0 -69
  122. package/src/commands/kill/index.ts +0 -1
  123. package/src/commands/logs/command.ts +0 -47
  124. package/src/commands/logs/index.ts +0 -1
  125. package/src/commands/pick-process.ts +0 -34
  126. package/src/commands/pin/command.ts +0 -34
  127. package/src/commands/pin/index.ts +0 -1
  128. package/src/commands/processes/command.ts +0 -39
  129. package/src/commands/processes/index.ts +0 -1
  130. package/src/commands/settings/apply-setting-change.ts +0 -72
  131. package/src/commands/settings/build-sections.ts +0 -160
  132. package/src/commands/settings/command.ts +0 -20
  133. package/src/commands/settings/index.ts +0 -1
  134. package/src/components/log-dock-component.test.ts +0 -29
  135. package/src/components/log-dock-component.ts +0 -236
  136. package/src/components/log-file-viewer.ts +0 -317
  137. package/src/components/log-overlay-component.ts +0 -546
  138. package/src/components/panel-helpers.ts +0 -37
  139. package/src/components/process-picker-component.ts +0 -166
  140. package/src/components/processes-component.ts +0 -485
  141. package/src/components/status-format.ts +0 -38
  142. package/src/config.ts +0 -107
  143. package/src/constants/index.ts +0 -16
  144. package/src/constants/types.ts +0 -106
  145. package/src/hooks/background-blocker.ts +0 -66
  146. package/src/hooks/cleanup.ts +0 -10
  147. package/src/hooks/index.ts +0 -32
  148. package/src/hooks/message-renderer.ts +0 -115
  149. package/src/hooks/process-end.ts +0 -69
  150. package/src/hooks/process-watch.ts +0 -85
  151. package/src/hooks/utils.ts +0 -23
  152. package/src/hooks/widget/index.ts +0 -2
  153. package/src/hooks/widget/setup.ts +0 -167
  154. package/src/hooks/widget/status-widget.ts +0 -105
  155. package/src/hooks/widget/types.ts +0 -20
  156. package/src/index.ts +0 -35
  157. package/src/manager.test.ts +0 -331
  158. package/src/manager.ts +0 -758
  159. package/src/tools/actions/clear.ts +0 -20
  160. package/src/tools/actions/debug.ts +0 -148
  161. package/src/tools/actions/index.ts +0 -171
  162. package/src/tools/actions/kill.ts +0 -89
  163. package/src/tools/actions/list.ts +0 -188
  164. package/src/tools/actions/logs.ts +0 -117
  165. package/src/tools/actions/output.ts +0 -269
  166. package/src/tools/actions/start.ts +0 -245
  167. package/src/tools/actions/write.ts +0 -113
  168. package/src/tools/index.ts +0 -207
  169. package/src/utils/ansi.test.ts +0 -22
  170. package/src/utils/command-executor.test.ts +0 -48
  171. package/src/utils/keybindings.ts +0 -71
@@ -1,117 +0,0 @@
1
- import { ToolBody, ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type {
3
- AgentToolResult,
4
- Theme,
5
- ToolRenderResultOptions,
6
- } from "@earendil-works/pi-coding-agent";
7
- import { Text } from "@earendil-works/pi-tui";
8
- import type { ExecuteResult, ProcessesDetails } from "../../constants";
9
- import type { ProcessManager } from "../../manager";
10
-
11
- interface LogsParams {
12
- id?: string;
13
- }
14
-
15
- export function renderLogsCall(args: LogsParams, theme: Theme): ToolCallHeader {
16
- return new ToolCallHeader(
17
- {
18
- toolName: "Process",
19
- action: "logs",
20
- mainArg: args.id,
21
- },
22
- theme,
23
- );
24
- }
25
-
26
- export function renderLogsResult(
27
- result: AgentToolResult<ProcessesDetails>,
28
- options: ToolRenderResultOptions,
29
- theme: Theme,
30
- ): ToolBody {
31
- const { details } = result;
32
-
33
- if (!details.logFiles) {
34
- return new ToolBody(
35
- {
36
- fields: [
37
- {
38
- label: "Error",
39
- value: "Missing log file details",
40
- showCollapsed: true,
41
- },
42
- ],
43
- },
44
- options,
45
- theme,
46
- );
47
- }
48
-
49
- const fields: Array<
50
- { label: string; value: string; showCollapsed?: boolean } | Text
51
- > = [
52
- new Text(
53
- [
54
- theme.fg("success", "Log files:"),
55
- ` stdout: ${theme.fg("accent", details.logFiles.stdoutFile)}`,
56
- ` stderr: ${theme.fg("accent", details.logFiles.stderrFile)}`,
57
- ].join("\n"),
58
- 0,
59
- 0,
60
- ),
61
- ];
62
-
63
- return new ToolBody({ fields }, options, theme);
64
- }
65
-
66
- export function executeLogs(
67
- params: LogsParams,
68
- manager: ProcessManager,
69
- ): ExecuteResult {
70
- if (!params.id) {
71
- return {
72
- content: [{ type: "text", text: "Missing required parameter: id" }],
73
- details: {
74
- action: "logs",
75
- success: false,
76
- message: "Missing required parameter: id",
77
- },
78
- };
79
- }
80
-
81
- const proc = manager.get(params.id);
82
- if (!proc) {
83
- const message = `Process not found: ${params.id}`;
84
- return {
85
- content: [{ type: "text", text: message }],
86
- details: {
87
- action: "logs",
88
- success: false,
89
- message,
90
- },
91
- };
92
- }
93
-
94
- const logFiles = manager.getLogFiles(proc.id);
95
- if (!logFiles) {
96
- const message = `Could not get log files for "${proc.name}" (${proc.id})`;
97
- return {
98
- content: [{ type: "text", text: message }],
99
- details: {
100
- action: "logs",
101
- success: false,
102
- message,
103
- },
104
- };
105
- }
106
-
107
- 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.`;
108
- return {
109
- content: [{ type: "text", text: message }],
110
- details: {
111
- action: "logs",
112
- success: true,
113
- message,
114
- logFiles,
115
- },
116
- };
117
- }
@@ -1,269 +0,0 @@
1
- import { ToolBody, ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type {
3
- AgentToolResult,
4
- Theme,
5
- ToolRenderResultOptions,
6
- } from "@earendil-works/pi-coding-agent";
7
- import { Text } from "@earendil-works/pi-tui";
8
- import { configLoader } from "../../config";
9
- import type { ExecuteResult, ProcessesDetails } from "../../constants";
10
- import type { ProcessManager } from "../../manager";
11
- import { formatStatus, hasAnsi, stripAnsi } from "../../utils";
12
-
13
- const MAX_BYTES = 50 * 1024; // 50KB
14
-
15
- interface OutputParams {
16
- id?: string;
17
- }
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
-
131
- export function executeOutput(
132
- params: OutputParams,
133
- manager: ProcessManager,
134
- ): ExecuteResult {
135
- if (!params.id) {
136
- return {
137
- content: [{ type: "text", text: "Missing required parameter: id" }],
138
- details: {
139
- action: "output",
140
- success: false,
141
- message: "Missing required parameter: id",
142
- },
143
- };
144
- }
145
-
146
- const proc = manager.get(params.id);
147
- if (!proc) {
148
- const message = `Process not found: ${params.id}`;
149
- return {
150
- content: [{ type: "text", text: message }],
151
- details: {
152
- action: "output",
153
- success: false,
154
- message,
155
- },
156
- };
157
- }
158
-
159
- const { defaultTailLines } = configLoader.getConfig().output;
160
- const output = manager.getOutput(proc.id, defaultTailLines);
161
- if (!output) {
162
- const message = `Could not read output for "${proc.name}" (${proc.id})`;
163
- return {
164
- content: [{ type: "text", text: message }],
165
- details: {
166
- action: "output",
167
- success: false,
168
- message,
169
- },
170
- };
171
- }
172
-
173
- const logFiles = manager.getLogFiles(proc.id);
174
- const stdoutLines = output.stdout.length;
175
- const stderrLines = output.stderr.length;
176
- const message = `"${proc.name}" (${proc.id}) [${formatStatus(proc)}]: ${stdoutLines} stdout lines, ${stderrLines} stderr lines`;
177
-
178
- // Build the full text content (ANSI-stripped), then truncate from the tail
179
- // like bash does, so the agent sees the most recent output.
180
- const outputParts: string[] = [message];
181
- if (output.stdout.length > 0) {
182
- outputParts.push("\nstdout:");
183
- outputParts.push(...output.stdout.map(stripAnsi));
184
- }
185
- if (output.stderr.length > 0) {
186
- outputParts.push("\nstderr:");
187
- outputParts.push(...output.stderr.map(stripAnsi));
188
- }
189
-
190
- const fullText = outputParts.join("\n");
191
- const { maxOutputLines } = configLoader.getConfig().output;
192
- const contentText = truncateTail(fullText, logFiles, maxOutputLines);
193
-
194
- return {
195
- content: [{ type: "text", text: contentText }],
196
- details: {
197
- action: "output",
198
- success: true,
199
- message,
200
- output,
201
- logFiles: logFiles
202
- ? {
203
- stdoutFile: logFiles.stdoutFile,
204
- stderrFile: logFiles.stderrFile,
205
- }
206
- : undefined,
207
- },
208
- };
209
- }
210
-
211
- /**
212
- * Truncate text from the tail (keep last N lines / MAX_BYTES), matching
213
- * the behaviour of pi's built-in bash tool. When truncated, appends a
214
- * notice pointing the agent to the full log files.
215
- */
216
- function truncateTail(
217
- text: string,
218
- logFiles: { stdoutFile: string; stderrFile: string } | null,
219
- maxLines: number,
220
- ): string {
221
- const totalBytes = Buffer.byteLength(text, "utf-8");
222
- const lines = text.split("\n");
223
- const totalLines = lines.length;
224
-
225
- if (totalLines <= maxLines && totalBytes <= MAX_BYTES) {
226
- return text;
227
- }
228
-
229
- // Work backwards, collecting lines that fit
230
- const kept: string[] = [];
231
- let keptBytes = 0;
232
- let hitBytes = false;
233
-
234
- for (let i = lines.length - 1; i >= 0 && kept.length < maxLines; i--) {
235
- const line = lines[i] ?? "";
236
- const lineBytes =
237
- Buffer.byteLength(line, "utf-8") + (kept.length > 0 ? 1 : 0);
238
-
239
- if (keptBytes + lineBytes > MAX_BYTES) {
240
- hitBytes = true;
241
- break;
242
- }
243
-
244
- kept.unshift(line);
245
- keptBytes += lineBytes;
246
- }
247
-
248
- let result = kept.join("\n");
249
-
250
- // Append a notice so the agent knows output was truncated
251
- const shownLines = kept.length;
252
- const startLine = totalLines - shownLines + 1;
253
- const sizeNote = hitBytes ? ` (${formatSize(MAX_BYTES)} limit)` : "";
254
- result += `\n\n[Showing lines ${startLine}-${totalLines} of ${totalLines}${sizeNote}.`;
255
-
256
- if (logFiles) {
257
- result += ` Full logs: ${logFiles.stdoutFile} , ${logFiles.stderrFile}`;
258
- }
259
-
260
- result += "]";
261
-
262
- return result;
263
- }
264
-
265
- function formatSize(bytes: number): string {
266
- if (bytes < 1024) return `${bytes}B`;
267
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
268
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
269
- }
@@ -1,245 +0,0 @@
1
- import { ToolBody, ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type {
3
- AgentToolResult,
4
- ExtensionContext,
5
- Theme,
6
- ToolRenderResultOptions,
7
- } from "@earendil-works/pi-coding-agent";
8
- import { Text } from "@earendil-works/pi-tui";
9
- import type { ExecuteResult, ProcessesDetails } from "../../constants";
10
- import type { ProcessManager } from "../../manager";
11
-
12
- type WatchStream = "stdout" | "stderr" | "both";
13
-
14
- interface StartLogWatch {
15
- pattern: string;
16
- stream?: WatchStream;
17
- repeat?: boolean;
18
- }
19
-
20
- interface StartParams {
21
- name?: string;
22
- command?: string;
23
- cwd?: string;
24
- alertOnSuccess?: boolean;
25
- alertOnFailure?: boolean;
26
- alertOnKill?: boolean;
27
- logWatches?: StartLogWatch[];
28
- }
29
-
30
- export function renderStartCall(
31
- args: StartParams,
32
- theme: Theme,
33
- ): ToolCallHeader {
34
- const longArgs: Array<{ label?: string; value: string }> = [];
35
- const optionArgs: Array<{ label: string; value: string }> = [];
36
- let mainArg: string | undefined;
37
-
38
- if (args.name) {
39
- mainArg = `"${args.name}"`;
40
- }
41
-
42
- if (args.command) {
43
- if (!mainArg && args.command.length <= 60) {
44
- mainArg = args.command;
45
- } else if (args.command.length <= 60) {
46
- optionArgs.push({ label: "command", value: args.command });
47
- } else {
48
- longArgs.push({ label: "command", value: args.command });
49
- }
50
- }
51
-
52
- if (args.logWatches && args.logWatches.length > 0) {
53
- optionArgs.push({
54
- label: "watches",
55
- value: String(args.logWatches.length),
56
- });
57
- }
58
-
59
- return new ToolCallHeader(
60
- {
61
- toolName: "Process",
62
- action: "start",
63
- mainArg,
64
- optionArgs,
65
- longArgs,
66
- },
67
- theme,
68
- );
69
- }
70
-
71
- export function renderStartResult(
72
- result: AgentToolResult<ProcessesDetails>,
73
- options: ToolRenderResultOptions,
74
- theme: Theme,
75
- ): ToolBody {
76
- const { details } = result;
77
- const process = details.process;
78
-
79
- if (!process) {
80
- return new ToolBody(
81
- {
82
- fields: [
83
- {
84
- label: "Error",
85
- value: "Missing process details",
86
- showCollapsed: true,
87
- },
88
- ],
89
- },
90
- options,
91
- theme,
92
- );
93
- }
94
-
95
- const fields: Array<
96
- { label: string; value: string; showCollapsed?: boolean } | Text
97
- > = [
98
- new Text(
99
- [
100
- theme.fg("success", "Started process"),
101
- ` name: ${theme.fg("accent", process.name)}`,
102
- ` command: ${process.command}`,
103
- ` id: ${theme.fg("accent", process.id)}`,
104
- ` pid: ${String(process.pid)}`,
105
- " Log files:",
106
- ` - stdout: ${theme.fg("accent", process.stdoutFile)}`,
107
- ` - stderr: ${theme.fg("accent", process.stderrFile)}`,
108
- ].join("\n"),
109
- 0,
110
- 0,
111
- ),
112
- {
113
- label: "Status",
114
- value:
115
- theme.fg("success", "Started") +
116
- ` ${theme.fg("accent", `"${process.name}"`)} (${process.id}, PID: ${process.pid})`,
117
- showCollapsed: true,
118
- },
119
- ];
120
-
121
- return new ToolBody({ fields }, options, theme);
122
- }
123
-
124
- export function executeStart(
125
- params: StartParams,
126
- manager: ProcessManager,
127
- ctx: ExtensionContext,
128
- ): ExecuteResult {
129
- if (!params.name) {
130
- return {
131
- content: [{ type: "text", text: "Missing required parameter: name" }],
132
- details: {
133
- action: "start",
134
- success: false,
135
- message: "Missing required parameter: name",
136
- },
137
- };
138
- }
139
- if (!params.command) {
140
- return {
141
- content: [{ type: "text", text: "Missing required parameter: command" }],
142
- details: {
143
- action: "start",
144
- success: false,
145
- message: "Missing required parameter: command",
146
- },
147
- };
148
- }
149
-
150
- const watchValidationError = validateLogWatches(params.logWatches);
151
- if (watchValidationError) {
152
- return {
153
- content: [{ type: "text", text: watchValidationError }],
154
- details: {
155
- action: "start",
156
- success: false,
157
- message: watchValidationError,
158
- },
159
- };
160
- }
161
-
162
- let proc: ReturnType<ProcessManager["start"]>;
163
- try {
164
- proc = manager.start(params.name, params.command, params.cwd ?? ctx.cwd, {
165
- alertOnSuccess: params.alertOnSuccess,
166
- alertOnFailure: params.alertOnFailure,
167
- alertOnKill: params.alertOnKill,
168
- logWatches: params.logWatches,
169
- });
170
- } catch (error) {
171
- const message =
172
- error instanceof Error
173
- ? `Invalid start options: ${error.message}`
174
- : "Invalid start options";
175
- return {
176
- content: [{ type: "text", text: message }],
177
- details: {
178
- action: "start",
179
- success: false,
180
- message,
181
- },
182
- };
183
- }
184
-
185
- const message = [
186
- `Started "${proc.name}" (${proc.id}, PID: ${proc.pid})`,
187
- "Log files:",
188
- ` stdout: ${proc.stdoutFile}`,
189
- ` stderr: ${proc.stderrFile}`,
190
- ].join("\n");
191
- return {
192
- content: [{ type: "text", text: message }],
193
- details: {
194
- action: "start",
195
- success: true,
196
- message,
197
- process: proc,
198
- },
199
- };
200
- }
201
-
202
- function validateLogWatches(watches?: StartLogWatch[]): string | null {
203
- if (!watches) return null;
204
-
205
- if (!Array.isArray(watches)) {
206
- return "Invalid parameter: logWatches must be an array";
207
- }
208
-
209
- for (const [index, watch] of watches.entries()) {
210
- if (!watch || typeof watch !== "object") {
211
- return `Invalid logWatches[${index}]: expected an object`;
212
- }
213
-
214
- if (
215
- typeof watch.pattern !== "string" ||
216
- watch.pattern.trim().length === 0
217
- ) {
218
- return `Invalid logWatches[${index}].pattern: expected non-empty string`;
219
- }
220
-
221
- try {
222
- // Validate regex syntax at process start for fast feedback.
223
- new RegExp(watch.pattern);
224
- } catch (error) {
225
- const message =
226
- error instanceof Error ? error.message : "invalid regular expression";
227
- return `Invalid logWatches[${index}].pattern: ${message}`;
228
- }
229
-
230
- if (
231
- watch.stream !== undefined &&
232
- watch.stream !== "stdout" &&
233
- watch.stream !== "stderr" &&
234
- watch.stream !== "both"
235
- ) {
236
- return `Invalid logWatches[${index}].stream: expected stdout, stderr, or both`;
237
- }
238
-
239
- if (watch.repeat !== undefined && typeof watch.repeat !== "boolean") {
240
- return `Invalid logWatches[${index}].repeat: expected boolean`;
241
- }
242
- }
243
-
244
- return null;
245
- }