@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,336 @@
1
+ import type { EventBus } from "@earendil-works/pi-coding-agent";
2
+
3
+ import type { ProcessManager } from "../../../src/manager";
4
+ import { CHANNELS } from "../../../src/protocol";
5
+ import type { ManagerEvent, ProcessInfo } from "../../../src/types";
6
+ import { classifyProcessEnd } from "./classify";
7
+ import {
8
+ type CompiledLogMatcher,
9
+ compileLogMatchers,
10
+ evaluateLogMatchers,
11
+ type LogMatchResult,
12
+ } from "./log-matchers";
13
+ import type { NotifyConfig, WatchState } from "./registry";
14
+ import {
15
+ createNotificationRegistry,
16
+ type NotificationRegistry,
17
+ } from "./registry";
18
+ import type {
19
+ Attention,
20
+ ProcessNotificationDetails,
21
+ ProcessNotificationKind,
22
+ } from "./types";
23
+
24
+ const DEFAULT_ATTENTION: Record<
25
+ "onSuccess" | "onFailure" | "onKilled",
26
+ Attention
27
+ > = {
28
+ onSuccess: "context",
29
+ onFailure: "turn",
30
+ // External kills surface as context by default (see DEFAULT_NOTIFY_CONFIG
31
+ // in tools/notify.ts for rationale). Intentional stops are classified
32
+ // separately and never reach this branch.
33
+ onKilled: "context",
34
+ };
35
+
36
+ export interface NotificationServiceDeps {
37
+ /**
38
+ * Event bus used to fan out notification events on CHANNELS.NOTIFICATION.
39
+ * The service never calls pi.sendMessage directly; a core delivery listener
40
+ * converts the emitted payload into a persisted custom message. This keeps
41
+ * notification flow event-driven and lets UI extensions observe the same
42
+ * events (e.g. for log-match highlighting).
43
+ */
44
+ events: EventBus;
45
+ manager: ProcessManager;
46
+ registry: NotificationRegistry;
47
+ getProcess: (id: string) => ProcessInfo | null;
48
+ }
49
+
50
+ interface ProcessMatcherState {
51
+ matchers: CompiledLogMatcher[];
52
+ revision: number;
53
+ generation: number;
54
+ }
55
+
56
+ export function createNotificationService(deps: NotificationServiceDeps): {
57
+ dispose: () => void;
58
+ } {
59
+ const { events, manager, registry, getProcess } = deps;
60
+ let disposed = false;
61
+ const matcherStates = new Map<string, ProcessMatcherState>();
62
+
63
+ const unsubscribe = manager.onEvent(handleEvent);
64
+
65
+ function handleEvent(event: ManagerEvent): void {
66
+ if (disposed) return;
67
+
68
+ if (event.type === "process_ended") {
69
+ // Defer to a microtask so that tool code (e.g. executeStart) can
70
+ // register the notify config in the registry before we read it.
71
+ // manager.start() may synchronously emit process_ended for missing_pid.
72
+ queueMicrotask(() => {
73
+ if (disposed) return;
74
+ handleProcessEnded(event.info);
75
+ });
76
+ return;
77
+ }
78
+
79
+ if (event.type === "process_output_changed") {
80
+ handleOutputChanged(event);
81
+ return;
82
+ }
83
+ }
84
+
85
+ function handleProcessEnded(info: ProcessInfo): void {
86
+ const isIntentionalStop = registry.consumeIntentionalStop(info.id);
87
+ const kind = classifyProcessEnd(info);
88
+
89
+ if (isIntentionalStop) {
90
+ cleanupMatcherState(info.id);
91
+ registry.unregister(info.id);
92
+ return;
93
+ }
94
+
95
+ const config = registry.get(info.id);
96
+ const attention = resolveAttention(kind, config);
97
+
98
+ const shouldForceDisplay = kind === "crash" || kind === "failure";
99
+
100
+ if (attention === "ignore" && !shouldForceDisplay) {
101
+ cleanupMatcherState(info.id);
102
+ registry.unregister(info.id);
103
+ return;
104
+ }
105
+
106
+ const effectiveAttention: Attention =
107
+ attention === "ignore" && shouldForceDisplay ? "context" : attention;
108
+
109
+ const details = buildLifecycleDetails(info, kind, effectiveAttention);
110
+ events.emit(CHANNELS.NOTIFICATION, details);
111
+
112
+ cleanupMatcherState(info.id);
113
+ registry.unregister(info.id);
114
+ }
115
+
116
+ function handleOutputChanged(
117
+ event: ManagerEvent & { type: "process_output_changed" },
118
+ ): void {
119
+ if (!event.appendedText || event.appendedText.length === 0) return;
120
+
121
+ const watchState = registry.getWatchState(event.id);
122
+ if (!watchState) return;
123
+
124
+ const state = syncMatcherState(event.id, watchState);
125
+ if (!state) return;
126
+
127
+ const matchTime = performance.now();
128
+ const matches = evaluateLogMatchers(
129
+ state.matchers,
130
+ event.appendedText,
131
+ matchTime,
132
+ );
133
+
134
+ const processInfo = getProcess(event.id);
135
+ const timestamp = Date.now();
136
+
137
+ for (const match of matches) {
138
+ const attention = match.on;
139
+ const details = buildLogMatchDetails(
140
+ event.id,
141
+ processInfo,
142
+ match,
143
+ attention,
144
+ timestamp,
145
+ );
146
+ events.emit(CHANNELS.NOTIFICATION, details);
147
+ }
148
+ }
149
+
150
+ function resolveAttention(
151
+ kind: ProcessNotificationKind,
152
+ config: NotifyConfig | null,
153
+ ): Attention {
154
+ switch (kind) {
155
+ case "success":
156
+ return config?.onSuccess ?? DEFAULT_ATTENTION.onSuccess;
157
+ case "failure":
158
+ case "crash":
159
+ return config?.onFailure ?? DEFAULT_ATTENTION.onFailure;
160
+ case "killed":
161
+ return config?.onKilled ?? DEFAULT_ATTENTION.onKilled;
162
+ case "log_match":
163
+ return "turn";
164
+ case "log_match_suppressed":
165
+ return "context";
166
+ }
167
+ }
168
+
169
+ function buildLifecycleDetails(
170
+ info: ProcessInfo,
171
+ kind: ProcessNotificationKind,
172
+ attention: Attention,
173
+ ): ProcessNotificationDetails {
174
+ const elapsed =
175
+ info.endTime !== null && info.startTime > 0
176
+ ? Math.round((info.endTime - info.startTime) / 1000)
177
+ : null;
178
+
179
+ let summary: string;
180
+ if (kind === "success") {
181
+ summary =
182
+ elapsed !== null
183
+ ? `Process "${info.name}" succeeded after ${elapsed}s.`
184
+ : `Process "${info.name}" succeeded.`;
185
+ } else if (kind === "killed" && info.signal) {
186
+ const number =
187
+ info.signal.number === null
188
+ ? ""
189
+ : ` (${info.signal.number}, ${info.signal.description})`;
190
+ summary = `Process "${info.name}" ended after receiving ${info.signal.name}${number}.`;
191
+ } else if (info.exitCode !== null && info.exitCode !== 0) {
192
+ summary =
193
+ elapsed !== null
194
+ ? `Process "${info.name}" failed with exit code ${info.exitCode} after ${elapsed}s.`
195
+ : `Process "${info.name}" failed with exit code ${info.exitCode}.`;
196
+ } else {
197
+ summary = `Process "${info.name}" failed.`;
198
+ }
199
+
200
+ return {
201
+ kind,
202
+ processId: info.id,
203
+ processName: info.name,
204
+ command: info.command,
205
+ timestamp: info.endTime ?? Date.now(),
206
+ summary,
207
+ status: info.status,
208
+ exitCode: info.exitCode,
209
+ endReason: info.endReason,
210
+ signal: info.signal,
211
+ attention,
212
+ };
213
+ }
214
+
215
+ function buildLogMatchDetails(
216
+ processId: string,
217
+ processInfo: ProcessInfo | null,
218
+ match: LogMatchResult,
219
+ attention: Attention,
220
+ timestamp: number,
221
+ ): ProcessNotificationDetails {
222
+ const name = processInfo?.name ?? processId;
223
+ const command = processInfo?.command ?? "";
224
+ const truncatedLine =
225
+ match.line.length > 160 ? `${match.line.slice(0, 157)}...` : match.line;
226
+
227
+ return {
228
+ kind: "log_match",
229
+ processId,
230
+ processName: name,
231
+ command,
232
+ timestamp,
233
+ summary: `Process "${name}" matched log pattern "${match.pattern}" on ${match.stream}.`,
234
+ logMatch: {
235
+ pattern: match.pattern,
236
+ mode: match.mode,
237
+ stream: match.stream,
238
+ line: truncatedLine,
239
+ matcherIndex: match.matcherIndex,
240
+ },
241
+ attention,
242
+ };
243
+ }
244
+
245
+ function syncMatcherState(
246
+ processId: string,
247
+ watchState: WatchState,
248
+ ): ProcessMatcherState | null {
249
+ const existing = matcherStates.get(processId);
250
+
251
+ if (!existing) {
252
+ const matchers = compileLogMatchers({
253
+ logMatches: watchState.logMatches,
254
+ });
255
+ if (matchers.length === 0) return null;
256
+ const state: ProcessMatcherState = {
257
+ matchers,
258
+ revision: watchState.revision,
259
+ generation: watchState.generation,
260
+ };
261
+ matcherStates.set(processId, state);
262
+ return state;
263
+ }
264
+
265
+ if (existing.revision === watchState.revision) return existing;
266
+
267
+ const next = compileLogMatchers({ logMatches: watchState.logMatches });
268
+
269
+ if (existing.generation === watchState.generation) {
270
+ preserveMatcherRuntimeState(existing.matchers, next);
271
+ }
272
+
273
+ if (next.length === 0) {
274
+ matcherStates.delete(processId);
275
+ return null;
276
+ }
277
+
278
+ const state: ProcessMatcherState = {
279
+ matchers: next,
280
+ revision: watchState.revision,
281
+ generation: watchState.generation,
282
+ };
283
+ matcherStates.set(processId, state);
284
+ return state;
285
+ }
286
+
287
+ function preserveMatcherRuntimeState(
288
+ prev: CompiledLogMatcher[],
289
+ next: CompiledLogMatcher[],
290
+ ): void {
291
+ const byKey = new Map<string, CompiledLogMatcher[]>();
292
+ for (const m of prev) {
293
+ const key = matcherIdentityKey(m);
294
+ const bucket = byKey.get(key);
295
+ if (bucket) {
296
+ bucket.push(m);
297
+ } else {
298
+ byKey.set(key, [m]);
299
+ }
300
+ }
301
+
302
+ for (const m of next) {
303
+ const key = matcherIdentityKey(m);
304
+ const bucket = byKey.get(key);
305
+ if (!bucket || bucket.length === 0) continue;
306
+ const donor = bucket.shift();
307
+ if (!donor) continue;
308
+ m.fired = donor.fired;
309
+ m.lastMatchTime = donor.lastMatchTime;
310
+ }
311
+ }
312
+
313
+ function matcherIdentityKey(m: {
314
+ pattern: string;
315
+ mode: string;
316
+ stream: string;
317
+ repeat: boolean;
318
+ on: string;
319
+ }): string {
320
+ return JSON.stringify([m.pattern, m.mode, m.stream, m.repeat, m.on]);
321
+ }
322
+
323
+ function cleanupMatcherState(processId: string): void {
324
+ matcherStates.delete(processId);
325
+ }
326
+
327
+ return {
328
+ dispose() {
329
+ disposed = true;
330
+ unsubscribe();
331
+ matcherStates.clear();
332
+ },
333
+ };
334
+ }
335
+
336
+ export { createNotificationRegistry };
@@ -0,0 +1,11 @@
1
+ // Re-export the protocol-safe notification types so the core extension and the
2
+ // protocol layer cannot drift apart. The canonical shape lives in
3
+ // `src/protocol/notifications.ts`; these names keep existing import sites
4
+ // stable while guaranteeing structural compatibility with the events emitted on
5
+ // CHANNELS.NOTIFICATION.
6
+ export type {
7
+ ProcessProtocolAttention as Attention,
8
+ ProcessProtocolNotificationKind as ProcessNotificationKind,
9
+ ProcessProtocolNotificationLogMatch as ProcessNotificationLogMatchDetails,
10
+ ProcessProtocolNotificationPayload as ProcessNotificationDetails,
11
+ } from "../../../src/protocol";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Apply a setting change to the core processes config.
3
+ *
4
+ * Logs/overview/dock settings are handled by their owning extensions.
5
+ */
6
+
7
+ import { setNestedValue } from "@aliou/pi-utils-settings";
8
+
9
+ import type { ProcessConfig } from "../config";
10
+
11
+ const BOOLEAN_FIELDS = new Set([
12
+ "interception.blockBackgroundCommands",
13
+ "widget.showStatusWidget",
14
+ ]);
15
+
16
+ export function applySettingChange(
17
+ id: string,
18
+ newValue: string,
19
+ config: ProcessConfig,
20
+ ): ProcessConfig | null {
21
+ const updated = structuredClone(config);
22
+
23
+ if (BOOLEAN_FIELDS.has(id)) {
24
+ setNestedValue(updated, id, newValue === "on");
25
+ return updated;
26
+ }
27
+
28
+ // Unknown field: fall through to default string storage
29
+ return null;
30
+ }