@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,20 +0,0 @@
1
- import type { ExecuteResult } from "../../constants";
2
- import type { ProcessManager } from "../../manager";
3
-
4
- export function executeClear(manager: ProcessManager): ExecuteResult {
5
- const cleared = manager.clearFinished();
6
- const message =
7
- cleared > 0
8
- ? `Cleared ${cleared} finished process(es)`
9
- : "No finished processes to clear";
10
-
11
- return {
12
- content: [{ type: "text", text: message }],
13
- details: {
14
- action: "clear",
15
- success: true,
16
- message,
17
- cleared,
18
- },
19
- };
20
- }
@@ -1,148 +0,0 @@
1
- import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@earendil-works/pi-coding-agent";
3
- import type { ExecuteResult, ProcessInfo } from "../../constants";
4
-
5
- interface DebugParams {
6
- preview?: "start" | "list" | "output" | "logs" | "error";
7
- }
8
-
9
- export function renderDebugCall(
10
- args: DebugParams,
11
- theme: Theme,
12
- ): ToolCallHeader {
13
- return new ToolCallHeader(
14
- {
15
- toolName: "Process",
16
- action: "debug_preview",
17
- mainArg: args.preview,
18
- },
19
- theme,
20
- );
21
- }
22
-
23
- function mockProcess(overrides?: Partial<ProcessInfo>): ProcessInfo {
24
- const now = Date.now();
25
- return {
26
- id: "proc_42",
27
- name: "demo-server",
28
- pid: 4242,
29
- command: "pnpm dev --port 3000",
30
- cwd: "/tmp/demo",
31
- startTime: now - 12_000,
32
- endTime: null,
33
- status: "running",
34
- exitCode: null,
35
- success: null,
36
- stdoutFile: "/tmp/pi-processes-demo/proc_42-stdout.log",
37
- stderrFile: "/tmp/pi-processes-demo/proc_42-stderr.log",
38
- alertOnSuccess: false,
39
- alertOnFailure: true,
40
- alertOnKill: false,
41
- ...overrides,
42
- };
43
- }
44
-
45
- /**
46
- * Temporary no-side-effect previews for process tool renderers.
47
- * Remove before release.
48
- */
49
- export function executeDebugPreview(params: DebugParams): ExecuteResult {
50
- const preview = params.preview ?? "start";
51
-
52
- if (preview === "start") {
53
- const process = mockProcess();
54
- const message = [
55
- `Started "${process.name}" (${process.id}, PID: ${process.pid})`,
56
- "Log files:",
57
- ` stdout: ${process.stdoutFile}`,
58
- ` stderr: ${process.stderrFile}`,
59
- ].join("\n");
60
- return {
61
- content: [{ type: "text", text: message }],
62
- details: {
63
- action: "start",
64
- success: true,
65
- message,
66
- process,
67
- },
68
- };
69
- }
70
-
71
- if (preview === "list") {
72
- const processes = [
73
- mockProcess(),
74
- mockProcess({
75
- id: "proc_11",
76
- name: "builder",
77
- pid: 1011,
78
- command: "pnpm build --watch",
79
- }),
80
- mockProcess({
81
- id: "proc_10",
82
- name: "tests",
83
- pid: 1010,
84
- command: "pnpm test",
85
- status: "exited",
86
- success: false,
87
- endTime: Date.now() - 3_000,
88
- exitCode: 1,
89
- }),
90
- ];
91
-
92
- return {
93
- content: [{ type: "text", text: "Debug preview: list" }],
94
- details: {
95
- action: "list",
96
- success: true,
97
- message: "Debug preview: list",
98
- processes,
99
- },
100
- };
101
- }
102
-
103
- if (preview === "output") {
104
- return {
105
- content: [{ type: "text", text: "Debug preview: output" }],
106
- details: {
107
- action: "output",
108
- success: true,
109
- message:
110
- '"demo-server" (proc_42) [running]: 4 stdout lines, 2 stderr lines',
111
- output: {
112
- status: "running",
113
- stdout: [
114
- "starting...",
115
- "loading config",
116
- "ready on http://localhost:3000",
117
- "watching for changes",
118
- ],
119
- stderr: [
120
- "warn: deprecated option in config",
121
- "error: simulated stack trace line",
122
- ],
123
- },
124
- logFiles: {
125
- stdoutFile: "/tmp/pi-processes-demo/proc_42-stdout.log",
126
- stderrFile: "/tmp/pi-processes-demo/proc_42-stderr.log",
127
- },
128
- },
129
- };
130
- }
131
-
132
- if (preview === "logs") {
133
- return {
134
- content: [{ type: "text", text: "Debug preview: logs" }],
135
- details: {
136
- action: "logs",
137
- success: true,
138
- message: "Debug preview: logs",
139
- logFiles: {
140
- stdoutFile: "/tmp/pi-processes-demo/proc_42-stdout.log",
141
- stderrFile: "/tmp/pi-processes-demo/proc_42-stderr.log",
142
- },
143
- },
144
- };
145
- }
146
-
147
- throw new Error("Invalid logWatches[0].pattern: Unterminated group");
148
- }
@@ -1,171 +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 type { Component } from "@earendil-works/pi-tui";
9
- import type {
10
- ExecuteResult,
11
- ProcessAction,
12
- ProcessesDetails,
13
- } from "../../constants";
14
- import type { ProcessManager } from "../../manager";
15
- import { executeClear } from "./clear";
16
- import { executeDebugPreview, renderDebugCall } from "./debug";
17
- import { executeKill, renderKillCall } from "./kill";
18
- import { executeList, renderListResult } from "./list";
19
- import { executeLogs, renderLogsCall, renderLogsResult } from "./logs";
20
- import { executeOutput, renderOutputCall, renderOutputResult } from "./output";
21
- import { executeStart, renderStartCall, renderStartResult } from "./start";
22
- import { executeWrite, renderWriteCall } from "./write";
23
-
24
- const DEBUG_PREVIEW_ENABLED = process.env.PI_PROCESSES_DEBUG_PREVIEW === "1";
25
-
26
- interface ActionParams {
27
- action: ProcessAction | string;
28
- command?: string;
29
- name?: string;
30
- id?: string;
31
- input?: string;
32
- end?: boolean;
33
- cwd?: string;
34
- alertOnSuccess?: boolean;
35
- alertOnFailure?: boolean;
36
- alertOnKill?: boolean;
37
- logWatches?: Array<{
38
- pattern: string;
39
- stream?: "stdout" | "stderr" | "both";
40
- repeat?: boolean;
41
- }>;
42
- preview?: "start" | "list" | "output" | "logs" | "error";
43
- }
44
-
45
- export async function executeAction(
46
- params: ActionParams,
47
- manager: ProcessManager,
48
- ctx: ExtensionContext,
49
- ): Promise<ExecuteResult> {
50
- switch (params.action) {
51
- case "start":
52
- return executeStart(params, manager, ctx);
53
- case "list":
54
- return executeList(manager);
55
- case "output":
56
- return executeOutput(params, manager);
57
- case "logs":
58
- return executeLogs(params, manager);
59
- case "kill":
60
- return executeKill(params, manager);
61
- case "clear":
62
- return executeClear(manager);
63
- case "write":
64
- return executeWrite(params, manager);
65
- case "debug_preview":
66
- if (!DEBUG_PREVIEW_ENABLED) {
67
- throw new Error(
68
- "Action 'debug_preview' is disabled. Set PI_PROCESSES_DEBUG_PREVIEW=1 to enable.",
69
- );
70
- }
71
- return executeDebugPreview(params);
72
- default:
73
- return {
74
- content: [{ type: "text", text: `Unknown action: ${params.action}` }],
75
- details: {
76
- action: params.action as ProcessAction,
77
- success: false,
78
- message: `Unknown action: ${params.action}`,
79
- },
80
- };
81
- }
82
- }
83
-
84
- export function renderActionCall(args: ActionParams, theme: Theme): Component {
85
- switch (args.action) {
86
- case "start":
87
- return renderStartCall(args, theme);
88
- case "output":
89
- return renderOutputCall(args, theme);
90
- case "logs":
91
- return renderLogsCall(args, theme);
92
- case "kill":
93
- return renderKillCall(args, theme);
94
- case "write":
95
- return renderWriteCall(args, theme);
96
- case "debug_preview":
97
- return renderDebugCall(args, theme);
98
- default:
99
- return new ToolCallHeader(
100
- { toolName: "Process", action: args.action },
101
- theme,
102
- );
103
- }
104
- }
105
-
106
- export function renderActionResult(
107
- result: AgentToolResult<ProcessesDetails>,
108
- options: ToolRenderResultOptions,
109
- theme: Theme,
110
- ): Component {
111
- const { details } = result;
112
-
113
- if (!details) {
114
- return new ToolBody(
115
- {
116
- fields: [
117
- {
118
- label: "Result",
119
- value: "No result details available.",
120
- showCollapsed: true,
121
- },
122
- ],
123
- },
124
- options,
125
- theme,
126
- );
127
- }
128
-
129
- switch (details.action) {
130
- case "start":
131
- return renderStartResult(result, options, theme);
132
- case "list":
133
- return renderListResult(result, options, theme);
134
- case "output":
135
- return renderOutputResult(result, options, theme);
136
- case "logs":
137
- return renderLogsResult(result, options, theme);
138
- case "kill":
139
- case "write":
140
- case "clear":
141
- case "debug_preview":
142
- // Default rendering for these actions
143
- return new ToolBody(
144
- {
145
- fields: [
146
- {
147
- label: "Result",
148
- value: details.message,
149
- showCollapsed: true,
150
- },
151
- ],
152
- },
153
- options,
154
- theme,
155
- );
156
- default:
157
- return new ToolBody(
158
- {
159
- fields: [
160
- {
161
- label: "Result",
162
- value: details.message,
163
- showCollapsed: true,
164
- },
165
- ],
166
- },
167
- options,
168
- theme,
169
- );
170
- }
171
- }
@@ -1,89 +0,0 @@
1
- import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@earendil-works/pi-coding-agent";
3
- import type { ExecuteResult } from "../../constants";
4
- import type { ProcessManager } from "../../manager";
5
-
6
- interface KillParams {
7
- id?: string;
8
- }
9
-
10
- export function renderKillCall(args: KillParams, theme: Theme): ToolCallHeader {
11
- return new ToolCallHeader(
12
- {
13
- toolName: "Process",
14
- action: "kill",
15
- mainArg: args.id,
16
- },
17
- theme,
18
- );
19
- }
20
-
21
- export async function executeKill(
22
- params: KillParams,
23
- manager: ProcessManager,
24
- ): Promise<ExecuteResult> {
25
- if (!params.id) {
26
- return {
27
- content: [{ type: "text", text: "Missing required parameter: id" }],
28
- details: {
29
- action: "kill",
30
- success: false,
31
- message: "Missing required parameter: id",
32
- },
33
- };
34
- }
35
-
36
- const proc = manager.get(params.id);
37
- if (!proc) {
38
- const message = `Process not found: ${params.id}`;
39
- return {
40
- content: [{ type: "text", text: message }],
41
- details: {
42
- action: "kill",
43
- success: false,
44
- message,
45
- },
46
- };
47
- }
48
-
49
- const result = await manager.kill(proc.id, {
50
- signal: "SIGTERM",
51
- timeoutMs: 3000,
52
- });
53
-
54
- if (result.ok) {
55
- const message = `Terminated "${proc.name}" (${proc.id})`;
56
- return {
57
- content: [{ type: "text", text: message }],
58
- details: {
59
- action: "kill",
60
- success: true,
61
- message,
62
- },
63
- };
64
- }
65
-
66
- if (result.reason === "timeout") {
67
- const message =
68
- `SIGTERM timed out for "${proc.name}" (${proc.id}). ` +
69
- "Run /ps and press x on terminate_timeout to force kill (SIGKILL).";
70
- return {
71
- content: [{ type: "text", text: message }],
72
- details: {
73
- action: "kill",
74
- success: false,
75
- message,
76
- },
77
- };
78
- }
79
-
80
- const message = `Failed to terminate "${proc.name}" (${proc.id})`;
81
- return {
82
- content: [{ type: "text", text: message }],
83
- details: {
84
- action: "kill",
85
- success: false,
86
- message,
87
- },
88
- };
89
- }
@@ -1,188 +0,0 @@
1
- import { ToolBody, ToolFooter } 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
- import {
11
- formatRuntime,
12
- formatStatus,
13
- formatStatusTag,
14
- formatTimestamp,
15
- truncateCmd,
16
- } from "../../utils";
17
-
18
- export function executeList(manager: ProcessManager): ExecuteResult {
19
- const processes = manager.list();
20
-
21
- if (processes.length === 0) {
22
- return {
23
- content: [{ type: "text", text: "No background processes running" }],
24
- details: {
25
- action: "list",
26
- success: true,
27
- message: "No background processes running",
28
- processes: [],
29
- },
30
- };
31
- }
32
-
33
- const summary = processes
34
- .map(
35
- (p) =>
36
- `${p.id} "${p.name}": ${truncateCmd(p.command)} [${formatStatus(p)}] ${formatRuntime(p.startTime, p.endTime)}`,
37
- )
38
- .join("\n");
39
-
40
- const message = `${processes.length} process(es):\n${summary}`;
41
- return {
42
- content: [{ type: "text", text: message }],
43
- details: {
44
- action: "list",
45
- success: true,
46
- message,
47
- processes,
48
- },
49
- };
50
- }
51
-
52
- export function renderListResult(
53
- result: AgentToolResult<ProcessesDetails>,
54
- options: ToolRenderResultOptions,
55
- theme: Theme,
56
- ): ToolBody {
57
- const { details } = result;
58
-
59
- if (!details.processes || details.processes.length === 0) {
60
- return new ToolBody(
61
- {
62
- fields: [
63
- {
64
- label: "Processes",
65
- value: "No background processes running",
66
- showCollapsed: true,
67
- },
68
- ],
69
- },
70
- options,
71
- theme,
72
- );
73
- }
74
-
75
- const processes = [...details.processes];
76
-
77
- const statusRank = (status: string): number => {
78
- switch (status) {
79
- case "running":
80
- return 0;
81
- case "terminating":
82
- return 1;
83
- case "terminate_timeout":
84
- return 2;
85
- case "killed":
86
- return 3;
87
- case "exited":
88
- return 4;
89
- default:
90
- return 5;
91
- }
92
- };
93
-
94
- processes.sort((a, b) => {
95
- const rankDiff = statusRank(a.status) - statusRank(b.status);
96
- if (rankDiff !== 0) return rankDiff;
97
- return b.startTime - a.startTime;
98
- });
99
-
100
- const runningCount = processes.filter(
101
- (p) => p.status === "running" || p.status === "terminating",
102
- ).length;
103
-
104
- const lines: string[] = [
105
- theme.fg(
106
- "success",
107
- `${processes.length} process(es), ${runningCount} running/terminating`,
108
- ),
109
- ];
110
-
111
- for (const process of processes) {
112
- const status = formatStatusTag(process, theme);
113
- lines.push(
114
- [
115
- `- ${theme.fg("accent", process.name)} ${theme.fg("muted", `(${process.id})`)}`,
116
- ` pid: ${process.pid} status: ${status}`,
117
- ` started: ${theme.fg("muted", formatTimestamp(process.startTime))}`,
118
- ` ended: ${theme.fg("muted", formatTimestamp(process.endTime))}`,
119
- ` runtime: ${theme.fg("muted", formatRuntime(process.startTime, process.endTime))}`,
120
- ].join("\n"),
121
- );
122
- }
123
-
124
- const fields: Array<
125
- { label: string; value: string; showCollapsed?: boolean } | Text
126
- > = [new Text(lines.join("\n"), 0, 0)];
127
-
128
- const running = details.processes.filter(
129
- (p) => p.status === "running" || p.status === "terminating",
130
- );
131
- const finishedOk = details.processes.filter(
132
- (p) => p.status === "exited" && p.success,
133
- ).length;
134
- const failed = details.processes.filter(
135
- (p) => p.status === "exited" && !p.success,
136
- ).length;
137
- const killed = details.processes.filter((p) => p.status === "killed").length;
138
-
139
- const runningSummary =
140
- running.length > 0
141
- ? running
142
- .slice(0, 3)
143
- .map(
144
- (p) =>
145
- `${theme.fg("accent", `"${p.name}"`)} [${formatStatusTag(p, theme)}]`,
146
- )
147
- .join(", ")
148
- : theme.fg("muted", "no running process");
149
-
150
- const restParts: string[] = [];
151
- if (finishedOk > 0) restParts.push(`${finishedOk} finished`);
152
- if (failed > 0) restParts.push(`${failed} failed`);
153
- if (killed > 0) restParts.push(`${killed} killed`);
154
- const restSummary =
155
- restParts.length > 0 ? theme.fg("muted", ` + ${restParts.join(", ")}`) : "";
156
-
157
- fields.push({
158
- label: "Processes",
159
- value: runningSummary + restSummary,
160
- showCollapsed: true,
161
- });
162
-
163
- const footerItems: Array<{
164
- label: string;
165
- value: string;
166
- }> = [];
167
- if (runningCount > 0) {
168
- footerItems.push({ label: "running", value: String(runningCount) });
169
- }
170
- if (failed > 0) {
171
- footerItems.push({ label: "failed", value: String(failed) });
172
- }
173
- if (killed > 0) {
174
- footerItems.push({ label: "killed", value: String(killed) });
175
- }
176
-
177
- return new ToolBody(
178
- {
179
- fields,
180
- footer:
181
- footerItems.length > 0
182
- ? new ToolFooter(theme, { items: footerItems, separator: " | " })
183
- : undefined,
184
- },
185
- options,
186
- theme,
187
- );
188
- }