@aliou/pi-processes 0.9.5 → 0.10.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.
Files changed (174) 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 +29 -10
  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 +519 -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/process-group.ts +4 -0
  114. package/src/utils/shorten-path.ts +11 -0
  115. package/src/utils/signals.ts +22 -0
  116. package/src/commands/clear/command.ts +0 -14
  117. package/src/commands/clear/index.ts +0 -1
  118. package/src/commands/completions.ts +0 -38
  119. package/src/commands/dock/command.ts +0 -29
  120. package/src/commands/dock/index.ts +0 -1
  121. package/src/commands/index.ts +0 -33
  122. package/src/commands/kill/command.ts +0 -69
  123. package/src/commands/kill/index.ts +0 -1
  124. package/src/commands/logs/command.ts +0 -47
  125. package/src/commands/logs/index.ts +0 -1
  126. package/src/commands/pick-process.ts +0 -34
  127. package/src/commands/pin/command.ts +0 -34
  128. package/src/commands/pin/index.ts +0 -1
  129. package/src/commands/processes/command.ts +0 -39
  130. package/src/commands/processes/index.ts +0 -1
  131. package/src/commands/settings/apply-setting-change.ts +0 -72
  132. package/src/commands/settings/build-sections.ts +0 -160
  133. package/src/commands/settings/command.ts +0 -20
  134. package/src/commands/settings/index.ts +0 -1
  135. package/src/components/log-dock-component.test.ts +0 -29
  136. package/src/components/log-dock-component.ts +0 -236
  137. package/src/components/log-file-viewer.ts +0 -317
  138. package/src/components/log-overlay-component.ts +0 -546
  139. package/src/components/panel-helpers.ts +0 -37
  140. package/src/components/process-picker-component.ts +0 -166
  141. package/src/components/processes-component.ts +0 -485
  142. package/src/components/status-format.ts +0 -38
  143. package/src/config.ts +0 -107
  144. package/src/constants/index.ts +0 -16
  145. package/src/constants/types.ts +0 -119
  146. package/src/hooks/background-blocker.ts +0 -66
  147. package/src/hooks/cleanup.ts +0 -10
  148. package/src/hooks/index.ts +0 -32
  149. package/src/hooks/message-renderer.ts +0 -115
  150. package/src/hooks/process-end.ts +0 -69
  151. package/src/hooks/process-watch.ts +0 -85
  152. package/src/hooks/utils.ts +0 -23
  153. package/src/hooks/widget/index.ts +0 -2
  154. package/src/hooks/widget/setup.ts +0 -167
  155. package/src/hooks/widget/status-widget.ts +0 -105
  156. package/src/hooks/widget/types.ts +0 -20
  157. package/src/index.ts +0 -35
  158. package/src/manager.test.ts +0 -331
  159. package/src/manager.ts +0 -758
  160. package/src/tools/actions/clear.ts +0 -20
  161. package/src/tools/actions/debug.ts +0 -155
  162. package/src/tools/actions/index.ts +0 -171
  163. package/src/tools/actions/kill.ts +0 -89
  164. package/src/tools/actions/list.ts +0 -188
  165. package/src/tools/actions/logs.ts +0 -117
  166. package/src/tools/actions/output-truncate.ts +0 -187
  167. package/src/tools/actions/output.test.ts +0 -392
  168. package/src/tools/actions/output.ts +0 -447
  169. package/src/tools/actions/start.ts +0 -245
  170. package/src/tools/actions/write.ts +0 -113
  171. package/src/tools/index.ts +0 -207
  172. package/src/utils/ansi.test.ts +0 -22
  173. package/src/utils/command-executor.test.ts +0 -48
  174. package/src/utils/keybindings.ts +0 -71
@@ -0,0 +1,345 @@
1
+ import type { ManagerEvent } from "../types";
2
+ import type { ManagedProcessRecord } from "./internal-types";
3
+ import {
4
+ MAX_LINE_BYTES,
5
+ MAX_LINES_PER_EMIT,
6
+ MAX_PENDING_LINE_BYTES,
7
+ TRUNCATION_SUFFIX,
8
+ } from "./limits";
9
+ import type { ProcessLogStore } from "./process-log-store";
10
+
11
+ interface ProcessOutputDeps {
12
+ emit: (event: ManagerEvent) => void;
13
+ logStore: ProcessLogStore;
14
+ throttleMs?: number;
15
+ }
16
+
17
+ export class ProcessOutput {
18
+ private emit: (event: ManagerEvent) => void;
19
+ private logStore: ProcessLogStore;
20
+ private throttleMs: number;
21
+
22
+ private lastOutputEmitAt: Map<string, number> = new Map();
23
+ private pendingOutputEmit: Map<string, NodeJS.Timeout> = new Map();
24
+
25
+ constructor(deps: ProcessOutputDeps) {
26
+ this.emit = deps.emit;
27
+ this.logStore = deps.logStore;
28
+ this.throttleMs = deps.throttleMs ?? 100;
29
+ }
30
+
31
+ onStdoutChunk(record: ManagedProcessRecord, data: Buffer): string[] {
32
+ return this.extractCompleteLines(record, "stdout", data, (line) => {
33
+ this.logStore.appendCombinedLine(record.combinedFile, "stdout", line);
34
+ this.appendEventLine(record, "stdout", line);
35
+ });
36
+ }
37
+
38
+ onStderrChunk(record: ManagedProcessRecord, data: Buffer): string[] {
39
+ return this.extractCompleteLines(record, "stderr", data, (line) => {
40
+ this.logStore.appendCombinedLine(record.combinedFile, "stderr", line);
41
+ this.appendEventLine(record, "stderr", line);
42
+ });
43
+ }
44
+
45
+ flush(record: ManagedProcessRecord): void {
46
+ this.flushPendingLines(record);
47
+ this.trimEventBuffer(record);
48
+
49
+ const timeout = this.pendingOutputEmit.get(record.id);
50
+ if (timeout) {
51
+ clearTimeout(timeout);
52
+ this.pendingOutputEmit.delete(record.id);
53
+ }
54
+
55
+ const drained = this.drainAppendedLines(record);
56
+ if (!timeout && !drained) return;
57
+
58
+ this.lastOutputEmitAt.set(record.id, Date.now());
59
+ this.emit({
60
+ type: "process_output_changed",
61
+ id: record.id,
62
+ ...(drained?.appendedText ? { appendedText: drained.appendedText } : {}),
63
+ ...(drained?.droppedLines ? { droppedLines: drained.droppedLines } : {}),
64
+ });
65
+ }
66
+
67
+ clear(id: string): void {
68
+ const timeout = this.pendingOutputEmit.get(id);
69
+ if (timeout) clearTimeout(timeout);
70
+ this.pendingOutputEmit.delete(id);
71
+ this.lastOutputEmitAt.delete(id);
72
+ }
73
+
74
+ clearAll(): void {
75
+ for (const timeout of this.pendingOutputEmit.values()) {
76
+ clearTimeout(timeout);
77
+ }
78
+ this.pendingOutputEmit.clear();
79
+ this.lastOutputEmitAt.clear();
80
+ }
81
+
82
+ private notify(record: ManagedProcessRecord): void {
83
+ this.trimEventBuffer(record);
84
+ const now = Date.now();
85
+ const lastEmit = this.lastOutputEmitAt.get(record.id) ?? 0;
86
+ const elapsed = now - lastEmit;
87
+
88
+ if (elapsed >= this.throttleMs) {
89
+ this.lastOutputEmitAt.set(record.id, now);
90
+ const drained = this.drainAppendedLines(record);
91
+ this.emit({
92
+ type: "process_output_changed",
93
+ id: record.id,
94
+ ...(drained?.appendedText
95
+ ? { appendedText: drained.appendedText }
96
+ : {}),
97
+ ...(drained?.droppedLines
98
+ ? { droppedLines: drained.droppedLines }
99
+ : {}),
100
+ });
101
+ return;
102
+ }
103
+
104
+ if (!this.pendingOutputEmit.has(record.id)) {
105
+ const delay = this.throttleMs - elapsed;
106
+ const timeout = setTimeout(() => {
107
+ this.pendingOutputEmit.delete(record.id);
108
+ const drained = this.drainAppendedLines(record);
109
+ if (!drained) return;
110
+ this.lastOutputEmitAt.set(record.id, Date.now());
111
+ this.emit({
112
+ type: "process_output_changed",
113
+ id: record.id,
114
+ ...(drained.appendedText
115
+ ? { appendedText: drained.appendedText }
116
+ : {}),
117
+ ...(drained.droppedLines
118
+ ? { droppedLines: drained.droppedLines }
119
+ : {}),
120
+ });
121
+ }, delay);
122
+ this.pendingOutputEmit.set(record.id, timeout);
123
+ }
124
+ }
125
+
126
+ private flushPendingLines(record: ManagedProcessRecord): void {
127
+ if (record.stdoutPendingLine.length > 0) {
128
+ const line = record.stdoutPendingLine.toString("utf-8");
129
+ this.logStore.appendCombinedLine(record.combinedFile, "stdout", line);
130
+ this.appendEventLine(record, "stdout", line);
131
+ }
132
+ record.stdoutPendingLine = Buffer.alloc(0);
133
+ record.stdoutLineOverflowed = false;
134
+
135
+ if (record.stderrPendingLine.length > 0) {
136
+ const line = record.stderrPendingLine.toString("utf-8");
137
+ this.logStore.appendCombinedLine(record.combinedFile, "stderr", line);
138
+ this.appendEventLine(record, "stderr", line);
139
+ }
140
+ record.stderrPendingLine = Buffer.alloc(0);
141
+ record.stderrLineOverflowed = false;
142
+ }
143
+
144
+ private drainAppendedLines(record: ManagedProcessRecord):
145
+ | {
146
+ appendedText?: Array<{
147
+ type: "stdout" | "stderr";
148
+ text: string;
149
+ }>;
150
+ droppedLines?: number;
151
+ }
152
+ | undefined {
153
+ if (record.appendedLines.length === 0 && record.droppedLineCount === 0) {
154
+ return undefined;
155
+ }
156
+ const lines = record.appendedLines;
157
+ const droppedLines = record.droppedLineCount;
158
+ record.appendedLines = [];
159
+ record.droppedLineCount = 0;
160
+ return {
161
+ ...(lines.length > 0 ? { appendedText: lines } : {}),
162
+ ...(droppedLines > 0 ? { droppedLines } : {}),
163
+ };
164
+ }
165
+
166
+ private extractCompleteLines(
167
+ record: ManagedProcessRecord,
168
+ source: "stdout" | "stderr",
169
+ data: Buffer,
170
+ onLine: (line: string) => void,
171
+ ): string[] {
172
+ let pending = this.getPending(record, source);
173
+ let overflowed = this.getOverflowed(record, source);
174
+ const completeLines: string[] = [];
175
+ let cursor = 0;
176
+
177
+ if (overflowed) {
178
+ const newline = data.indexOf(0x0a);
179
+ if (newline === -1) {
180
+ this.notify(record);
181
+ return [];
182
+ }
183
+ cursor = newline + 1;
184
+ overflowed = false;
185
+ pending = Buffer.alloc(0);
186
+ }
187
+
188
+ let newline = data.indexOf(0x0a, cursor);
189
+ while (newline !== -1) {
190
+ const line = this.buildBoundedLine(
191
+ pending,
192
+ data.subarray(cursor, newline),
193
+ true,
194
+ );
195
+ onLine(line);
196
+ if (completeLines.length < MAX_LINES_PER_EMIT) completeLines.push(line);
197
+ pending = Buffer.alloc(0);
198
+ cursor = newline + 1;
199
+ newline = data.indexOf(0x0a, cursor);
200
+ }
201
+
202
+ const tail = data.subarray(cursor);
203
+ const tailLength = tail.length;
204
+ if (pending.length + tailLength > MAX_PENDING_LINE_BYTES) {
205
+ const line = this.buildBoundedLine(pending, tail, false);
206
+ onLine(line);
207
+ if (completeLines.length < MAX_LINES_PER_EMIT) completeLines.push(line);
208
+ pending = Buffer.alloc(0);
209
+ overflowed = true;
210
+ // The raw stdout/stderr log still has the complete bytes. Dropping the
211
+ // event-stream tail avoids turning one huge logical line into thousands
212
+ // of synthetic lines in UI buffers.
213
+ } else {
214
+ pending =
215
+ pending.length === 0
216
+ ? Buffer.from(tail)
217
+ : Buffer.concat([pending, tail], pending.length + tail.length);
218
+ }
219
+
220
+ this.setPending(record, source, pending);
221
+ this.setOverflowed(record, source, overflowed);
222
+ this.notify(record);
223
+ return completeLines;
224
+ }
225
+
226
+ private buildBoundedLine(
227
+ pending: Buffer,
228
+ segment: Buffer,
229
+ stripCarriageReturn: boolean,
230
+ ): string {
231
+ let pendingEnd = pending.length;
232
+ let segmentEnd = segment.length;
233
+ if (stripCarriageReturn) {
234
+ if (segmentEnd > 0 && segment[segmentEnd - 1] === 0x0d) segmentEnd--;
235
+ else if (
236
+ segmentEnd === 0 &&
237
+ pendingEnd > 0 &&
238
+ pending[pendingEnd - 1] === 0x0d
239
+ ) {
240
+ pendingEnd--;
241
+ }
242
+ }
243
+
244
+ const totalLength = pendingEnd + segmentEnd;
245
+ if (totalLength <= MAX_PENDING_LINE_BYTES) {
246
+ return Buffer.concat(
247
+ [pending.subarray(0, pendingEnd), segment.subarray(0, segmentEnd)],
248
+ totalLength,
249
+ ).toString("utf-8");
250
+ }
251
+
252
+ const prefix = Buffer.allocUnsafe(MAX_PENDING_LINE_BYTES);
253
+ const pendingLength = Math.min(pendingEnd, MAX_PENDING_LINE_BYTES);
254
+ pending.copy(prefix, 0, 0, pendingLength);
255
+ const segmentLength = MAX_PENDING_LINE_BYTES - pendingLength;
256
+ segment.copy(prefix, pendingLength, 0, segmentLength);
257
+ return `${trimIncompleteUtf8Suffix(prefix).toString("utf-8")}${TRUNCATION_SUFFIX}`;
258
+ }
259
+
260
+ private appendEventLine(
261
+ record: ManagedProcessRecord,
262
+ type: "stdout" | "stderr",
263
+ text: string,
264
+ ): void {
265
+ record.appendedLines.push({ type, text: this.clampLine(text) });
266
+ if (record.appendedLines.length <= MAX_LINES_PER_EMIT * 2) return;
267
+ this.trimEventBuffer(record);
268
+ }
269
+
270
+ private trimEventBuffer(record: ManagedProcessRecord): void {
271
+ if (record.appendedLines.length <= MAX_LINES_PER_EMIT) return;
272
+ const overflow = record.appendedLines.length - MAX_LINES_PER_EMIT;
273
+ record.appendedLines.splice(0, overflow);
274
+ record.droppedLineCount += overflow;
275
+ }
276
+
277
+ private clampLine(text: string): string {
278
+ if (Buffer.byteLength(text) <= MAX_LINE_BYTES) return text;
279
+ const suffixBytes = Buffer.byteLength(TRUNCATION_SUFFIX);
280
+ const prefix = Buffer.from(text).subarray(0, MAX_LINE_BYTES - suffixBytes);
281
+ return `${trimIncompleteUtf8Suffix(prefix).toString("utf-8")}${TRUNCATION_SUFFIX}`;
282
+ }
283
+
284
+ private getPending(
285
+ record: ManagedProcessRecord,
286
+ source: "stdout" | "stderr",
287
+ ): Buffer {
288
+ return source === "stdout"
289
+ ? record.stdoutPendingLine
290
+ : record.stderrPendingLine;
291
+ }
292
+
293
+ private setPending(
294
+ record: ManagedProcessRecord,
295
+ source: "stdout" | "stderr",
296
+ value: Buffer,
297
+ ): void {
298
+ if (source === "stdout") record.stdoutPendingLine = value;
299
+ else record.stderrPendingLine = value;
300
+ }
301
+
302
+ private getOverflowed(
303
+ record: ManagedProcessRecord,
304
+ source: "stdout" | "stderr",
305
+ ): boolean {
306
+ return source === "stdout"
307
+ ? record.stdoutLineOverflowed
308
+ : record.stderrLineOverflowed;
309
+ }
310
+
311
+ private setOverflowed(
312
+ record: ManagedProcessRecord,
313
+ source: "stdout" | "stderr",
314
+ value: boolean,
315
+ ): void {
316
+ if (source === "stdout") record.stdoutLineOverflowed = value;
317
+ else record.stderrLineOverflowed = value;
318
+ }
319
+
320
+ [Symbol.dispose](): void {
321
+ this.clearAll();
322
+ }
323
+ }
324
+
325
+ function trimIncompleteUtf8Suffix(buffer: Buffer): Buffer {
326
+ if (buffer.length === 0) return buffer;
327
+
328
+ let lead = buffer.length - 1;
329
+ while (lead >= 0 && (buffer[lead] & 0xc0) === 0x80) lead--;
330
+ if (lead < 0) return Buffer.alloc(0);
331
+
332
+ const leadByte = buffer[lead];
333
+ const expectedLength =
334
+ leadByte < 0x80
335
+ ? 1
336
+ : (leadByte & 0xe0) === 0xc0
337
+ ? 2
338
+ : (leadByte & 0xf0) === 0xe0
339
+ ? 3
340
+ : (leadByte & 0xf8) === 0xf0
341
+ ? 4
342
+ : 1;
343
+ const actualLength = buffer.length - lead;
344
+ return actualLength < expectedLength ? buffer.subarray(0, lead) : buffer;
345
+ }
@@ -0,0 +1,78 @@
1
+ import { randomBytes } from "node:crypto";
2
+
3
+ import { LIVE_STATUSES, type ProcessInfo } from "../types";
4
+ import type { ManagedProcessRecord } from "./internal-types";
5
+ import { formatProcess } from "./internal-types";
6
+
7
+ export class ProcessRegistry {
8
+ private processes: Map<string, ManagedProcessRecord> = new Map();
9
+
10
+ nextId(): string {
11
+ let id: string;
12
+ do {
13
+ id = `proc_${randomBytes(2).toString("hex")}`;
14
+ } while (this.processes.has(id));
15
+ return id;
16
+ }
17
+
18
+ add(process: ManagedProcessRecord): void {
19
+ this.processes.set(process.id, process);
20
+ }
21
+
22
+ getRecord(id: string): ManagedProcessRecord | undefined {
23
+ return this.processes.get(id);
24
+ }
25
+
26
+ getPublicInfo(id: string): ProcessInfo | null {
27
+ const managed = this.processes.get(id);
28
+ return managed ? formatProcess(managed) : null;
29
+ }
30
+
31
+ rename(id: string, name: string): ProcessInfo | null {
32
+ const managed = this.processes.get(id);
33
+ if (!managed) return null;
34
+ managed.name = name;
35
+ return formatProcess(managed);
36
+ }
37
+
38
+ delete(id: string): boolean {
39
+ return this.processes.delete(id);
40
+ }
41
+
42
+ list(): ProcessInfo[] {
43
+ return Array.from(this.processes.values()).map((p) => formatProcess(p));
44
+ }
45
+
46
+ values(): IterableIterator<ManagedProcessRecord> {
47
+ return this.processes.values();
48
+ }
49
+
50
+ entries(): IterableIterator<[string, ManagedProcessRecord]> {
51
+ return this.processes.entries();
52
+ }
53
+
54
+ has(id: string): boolean {
55
+ return this.processes.has(id);
56
+ }
57
+
58
+ hasAliveishProcesses(): boolean {
59
+ for (const p of this.processes.values()) {
60
+ if (LIVE_STATUSES.has(p.status)) return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ forEachAlive(
66
+ callback: (id: string, managed: ManagedProcessRecord) => void,
67
+ ): void {
68
+ for (const [id, managed] of this.processes) {
69
+ if (LIVE_STATUSES.has(managed.status)) {
70
+ callback(id, managed);
71
+ }
72
+ }
73
+ }
74
+
75
+ [Symbol.dispose](): void {
76
+ this.processes.clear();
77
+ }
78
+ }