@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.
- package/CONTRIBUTING.md +40 -42
- package/README.md +54 -84
- package/extensions/process-tabs.ts +26 -0
- package/extensions/processes/client.ts +194 -0
- package/extensions/processes/commands/clear.ts +29 -0
- package/extensions/processes/commands/kill.ts +152 -0
- package/extensions/processes/commands/overview.ts +102 -0
- package/extensions/processes/components/overview-component.ts +729 -0
- package/extensions/processes/components/overview-panel.ts +177 -0
- package/extensions/processes/config/defaults.ts +28 -0
- package/extensions/processes/config/index.ts +13 -0
- package/extensions/processes/config/loader.ts +62 -0
- package/extensions/processes/config/migrations/000-import-legacy-process-config.ts +113 -0
- package/extensions/processes/config/migrations/001-v0-9-4-to-v0-10-0-config.ts +184 -0
- package/extensions/processes/config/migrations/002-stamp-v0-10-0-config-version.ts +21 -0
- package/extensions/processes/config/migrations/index.ts +25 -0
- package/extensions/processes/config/schema.ts +9 -0
- package/extensions/processes/config/types.ts +72 -0
- package/extensions/processes/constants.ts +1 -0
- package/extensions/processes/handlers/commands.ts +113 -0
- package/extensions/processes/handlers/kill-process.ts +31 -0
- package/extensions/processes/handlers/notifications.ts +147 -0
- package/extensions/processes/handlers/requests.ts +114 -0
- package/extensions/processes/handlers/subscriptions.ts +144 -0
- package/extensions/processes/hooks/background-blocker.ts +201 -0
- package/extensions/processes/hooks/cleanup.ts +43 -0
- package/extensions/processes/hooks/event-bridge.ts +32 -0
- package/extensions/processes/i18n/index.ts +3 -0
- package/extensions/processes/i18n/messages.ts +27 -0
- package/extensions/processes/i18n/translator.ts +49 -0
- package/extensions/processes/index.ts +109 -0
- package/extensions/processes/message-renderer.ts +117 -0
- package/extensions/processes/notification-sender.ts +43 -0
- package/extensions/processes/notifications/classify.ts +27 -0
- package/extensions/processes/notifications/log-matchers.ts +146 -0
- package/extensions/processes/notifications/registry.ts +225 -0
- package/extensions/processes/notifications/render-content.ts +122 -0
- package/extensions/processes/notifications/service.ts +336 -0
- package/extensions/processes/notifications/types.ts +11 -0
- package/extensions/processes/settings/apply-setting-change.ts +30 -0
- package/extensions/processes/settings/build-sections.ts +420 -0
- package/extensions/processes/settings/index.ts +42 -0
- package/extensions/processes/tools/clear/index.ts +21 -0
- package/extensions/processes/tools/clear/render.ts +40 -0
- package/extensions/processes/tools/components/index.ts +4 -0
- package/extensions/processes/tools/components/process-action-header.ts +31 -0
- package/extensions/processes/tools/components/process-action-title.ts +17 -0
- package/extensions/processes/tools/components/tool-layout.ts +70 -0
- package/extensions/processes/tools/components/watch.ts +53 -0
- package/extensions/processes/tools/index.ts +265 -0
- package/extensions/processes/tools/list/index.ts +157 -0
- package/extensions/processes/tools/list/render.ts +211 -0
- package/extensions/processes/tools/notify.ts +191 -0
- package/extensions/processes/tools/output/index.ts +307 -0
- package/extensions/processes/tools/output/render.ts +229 -0
- package/extensions/processes/tools/schema.ts +263 -0
- package/extensions/processes/tools/start/index.ts +64 -0
- package/extensions/processes/tools/start/render.ts +67 -0
- package/extensions/processes/tools/stop/index.ts +35 -0
- package/extensions/processes/tools/stop/render.ts +43 -0
- package/extensions/processes/tools/update/index.ts +320 -0
- package/extensions/processes/tools/update/render.ts +251 -0
- package/extensions/processes/tools/utils.ts +112 -0
- package/extensions/processes/tools/watch-format.ts +14 -0
- package/extensions/processes/tools/write/index.ts +75 -0
- package/extensions/processes/tools/write/render.ts +68 -0
- package/extensions/processes/utils/truncate.ts +285 -0
- package/extensions/processes-dock/client.ts +60 -0
- package/extensions/processes-dock/commands/dock.ts +70 -0
- package/extensions/processes-dock/commands/pin.ts +166 -0
- package/extensions/processes-dock/components/log-dock-component.ts +375 -0
- package/extensions/processes-dock/dock-state.ts +33 -0
- package/extensions/processes-dock/index.ts +28 -0
- package/extensions/processes-dock/logs-client.ts +5 -0
- package/extensions/processes-dock/widget/setup.ts +520 -0
- package/extensions/processes-dock/widget/status.ts +149 -0
- package/extensions/processes-dock/widget/types.ts +16 -0
- package/extensions/processes-logs/client.ts +51 -0
- package/extensions/processes-logs/commands/logs.ts +108 -0
- package/extensions/processes-logs/completions.ts +31 -0
- package/extensions/processes-logs/components/log-file-viewer.ts +274 -0
- package/extensions/processes-logs/components/log-overlay-component.ts +685 -0
- package/extensions/processes-logs/index.ts +27 -0
- package/extensions/processes-logs/logs-client.ts +101 -0
- package/extensions/shared/line-buffer.ts +35 -0
- package/extensions/shared/output-payload.ts +28 -0
- package/extensions/shared/ui.ts +146 -0
- package/package.json +47 -20
- package/schema.json +134 -0
- package/skills/pi-processes/SKILL.md +496 -25
- package/src/get-manager.ts +15 -0
- package/src/manager/index.ts +182 -0
- package/src/manager/internal-types.ts +130 -0
- package/src/manager/limits.ts +15 -0
- package/src/manager/process-log-store.ts +422 -0
- package/src/manager/process-output.ts +345 -0
- package/src/manager/process-registry.ts +78 -0
- package/src/manager/process-runtime-controller.ts +499 -0
- package/src/protocol/broadcasts.ts +14 -0
- package/src/protocol/channels.ts +30 -0
- package/src/protocol/commands.ts +24 -0
- package/src/protocol/index.ts +34 -0
- package/src/protocol/logs.ts +23 -0
- package/src/protocol/notifications.ts +48 -0
- package/src/protocol/requests.ts +76 -0
- package/src/types.ts +66 -0
- package/src/utils/ansi.ts +2 -4
- package/src/utils/format.ts +24 -32
- package/src/utils/index.ts +5 -1
- package/src/utils/is-record.ts +3 -0
- package/src/utils/match-line.ts +32 -0
- package/src/utils/platform.ts +13 -0
- package/src/utils/shorten-path.ts +11 -0
- package/src/utils/signals.ts +22 -0
- package/src/commands/clear/command.ts +0 -14
- package/src/commands/clear/index.ts +0 -1
- package/src/commands/completions.ts +0 -38
- package/src/commands/dock/command.ts +0 -29
- package/src/commands/dock/index.ts +0 -1
- package/src/commands/index.ts +0 -33
- package/src/commands/kill/command.ts +0 -69
- package/src/commands/kill/index.ts +0 -1
- package/src/commands/logs/command.ts +0 -47
- package/src/commands/logs/index.ts +0 -1
- package/src/commands/pick-process.ts +0 -34
- package/src/commands/pin/command.ts +0 -34
- package/src/commands/pin/index.ts +0 -1
- package/src/commands/processes/command.ts +0 -39
- package/src/commands/processes/index.ts +0 -1
- package/src/commands/settings/apply-setting-change.ts +0 -72
- package/src/commands/settings/build-sections.ts +0 -160
- package/src/commands/settings/command.ts +0 -20
- package/src/commands/settings/index.ts +0 -1
- package/src/components/log-dock-component.test.ts +0 -29
- package/src/components/log-dock-component.ts +0 -236
- package/src/components/log-file-viewer.ts +0 -317
- package/src/components/log-overlay-component.ts +0 -546
- package/src/components/panel-helpers.ts +0 -37
- package/src/components/process-picker-component.ts +0 -166
- package/src/components/processes-component.ts +0 -485
- package/src/components/status-format.ts +0 -38
- package/src/config.ts +0 -107
- package/src/constants/index.ts +0 -16
- package/src/constants/types.ts +0 -106
- package/src/hooks/background-blocker.ts +0 -66
- package/src/hooks/cleanup.ts +0 -10
- package/src/hooks/index.ts +0 -32
- package/src/hooks/message-renderer.ts +0 -115
- package/src/hooks/process-end.ts +0 -69
- package/src/hooks/process-watch.ts +0 -85
- package/src/hooks/utils.ts +0 -23
- package/src/hooks/widget/index.ts +0 -2
- package/src/hooks/widget/setup.ts +0 -167
- package/src/hooks/widget/status-widget.ts +0 -105
- package/src/hooks/widget/types.ts +0 -20
- package/src/index.ts +0 -35
- package/src/manager.test.ts +0 -331
- package/src/manager.ts +0 -758
- package/src/tools/actions/clear.ts +0 -20
- package/src/tools/actions/debug.ts +0 -148
- package/src/tools/actions/index.ts +0 -171
- package/src/tools/actions/kill.ts +0 -89
- package/src/tools/actions/list.ts +0 -188
- package/src/tools/actions/logs.ts +0 -117
- package/src/tools/actions/output.ts +0 -269
- package/src/tools/actions/start.ts +0 -245
- package/src/tools/actions/write.ts +0 -113
- package/src/tools/index.ts +0 -207
- package/src/utils/ansi.test.ts +0 -22
- package/src/utils/command-executor.test.ts +0 -48
- package/src/utils/keybindings.ts +0 -71
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
import { ProcessActionHeader } from "../components";
|
|
5
|
+
import type { ProcessesParamsType } from "../schema";
|
|
6
|
+
import { buildField } from "../utils";
|
|
7
|
+
import type { OutputDetails } from ".";
|
|
8
|
+
|
|
9
|
+
export function buildHeader(
|
|
10
|
+
args: ProcessesParamsType,
|
|
11
|
+
theme: Theme,
|
|
12
|
+
options?: { expanded?: boolean },
|
|
13
|
+
): Container {
|
|
14
|
+
const suffix = args.id ? theme.fg("accent", `(${args.id})`) : undefined;
|
|
15
|
+
return new ProcessActionHeader(args, theme, {
|
|
16
|
+
action: "output",
|
|
17
|
+
expanded: options?.expanded,
|
|
18
|
+
suffix,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildExpanded(
|
|
23
|
+
contentText: string,
|
|
24
|
+
details: OutputDetails,
|
|
25
|
+
theme: Theme,
|
|
26
|
+
): Container {
|
|
27
|
+
const container = new Container();
|
|
28
|
+
container.addChild(buildOutputMeta(details, theme));
|
|
29
|
+
|
|
30
|
+
const bodyLines = extractOutputBody(contentText, details);
|
|
31
|
+
|
|
32
|
+
if (bodyLines.length > 0) {
|
|
33
|
+
container.addChild(new Spacer(1));
|
|
34
|
+
for (const line of bodyLines) {
|
|
35
|
+
container.addChild(new Text(line, 0, 0));
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
container.addChild(new Spacer(1));
|
|
39
|
+
container.addChild(
|
|
40
|
+
new Text(theme.fg("muted", emptyMessage(details)), 0, 0),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (details.truncation) {
|
|
45
|
+
container.addChild(new Spacer(1));
|
|
46
|
+
container.addChild(
|
|
47
|
+
new Text(theme.fg("muted", buildTruncationSummary(details)), 0, 0),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return container;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildCollapsed(
|
|
55
|
+
contentText: string,
|
|
56
|
+
details: OutputDetails,
|
|
57
|
+
theme: Theme,
|
|
58
|
+
): Container {
|
|
59
|
+
const container = new Container();
|
|
60
|
+
|
|
61
|
+
container.addChild(
|
|
62
|
+
new Text(
|
|
63
|
+
[
|
|
64
|
+
details.processName,
|
|
65
|
+
theme.fg("accent", details.id),
|
|
66
|
+
theme.fg(getStatusTone(details), details.processStatus),
|
|
67
|
+
]
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.join(" "),
|
|
70
|
+
0,
|
|
71
|
+
0,
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const bodyLines = extractOutputBody(contentText, details);
|
|
76
|
+
const preview = bodyLines.slice(-2).join("\n");
|
|
77
|
+
|
|
78
|
+
if (preview) {
|
|
79
|
+
container.addChild(new Text(theme.fg("muted", preview), 0, 0));
|
|
80
|
+
} else {
|
|
81
|
+
container.addChild(
|
|
82
|
+
new Text(theme.fg("muted", emptyMessage(details)), 0, 0),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return container;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function buildFooter(
|
|
90
|
+
details: OutputDetails,
|
|
91
|
+
options: { expanded?: boolean },
|
|
92
|
+
theme: Theme,
|
|
93
|
+
): Container | null {
|
|
94
|
+
if (!options.expanded) return null;
|
|
95
|
+
|
|
96
|
+
const container = new Container();
|
|
97
|
+
container.addChild(new Text(theme.fg("muted", "logs:"), 0, 0));
|
|
98
|
+
container.addChild(
|
|
99
|
+
new Text(` ${theme.fg("accent", details.stdoutFile)}`, 0, 0),
|
|
100
|
+
);
|
|
101
|
+
container.addChild(
|
|
102
|
+
new Text(` ${theme.fg("accent", details.stderrFile)}`, 0, 0),
|
|
103
|
+
);
|
|
104
|
+
return container;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildOutputMeta(details: OutputDetails, theme: Theme): Container {
|
|
108
|
+
const container = new Container();
|
|
109
|
+
container.addChild(buildField("name", details.processName, theme));
|
|
110
|
+
container.addChild(buildField("id", details.id, theme));
|
|
111
|
+
container.addChild(
|
|
112
|
+
buildField(
|
|
113
|
+
"status",
|
|
114
|
+
theme.fg(getStatusTone(details), details.processStatus),
|
|
115
|
+
theme,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
container.addChild(buildField("stream", details.stream, theme));
|
|
119
|
+
|
|
120
|
+
if (details.pattern) {
|
|
121
|
+
const modeTag = details.mode === "regex" ? " (regex)" : "";
|
|
122
|
+
container.addChild(
|
|
123
|
+
buildField("filter", `${details.pattern}${modeTag}`, theme),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return container;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function emptyMessage(details: OutputDetails): string {
|
|
131
|
+
return details.pattern ? "No matching lines found" : "No output yet";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildTruncationSummary(details: OutputDetails): string {
|
|
135
|
+
const truncation = details.truncation;
|
|
136
|
+
if (!truncation) return "";
|
|
137
|
+
|
|
138
|
+
const partialNote = truncation.lastLinePartial ? " · partial final line" : "";
|
|
139
|
+
return `Preview truncated · ${truncation.outputLines}/${truncation.totalLines} lines${partialNote}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getStatusTone(
|
|
143
|
+
details: OutputDetails,
|
|
144
|
+
): "success" | "warning" | "error" | "muted" {
|
|
145
|
+
if (details.processStatus === "running") return "success";
|
|
146
|
+
if (details.processStatus === "killed") return "warning";
|
|
147
|
+
return "muted";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Extract process output from the tool-result content text.
|
|
152
|
+
*
|
|
153
|
+
* The content text is structured as:
|
|
154
|
+
* - a one- or two-line header with process metadata and filter;
|
|
155
|
+
* - the bounded output body;
|
|
156
|
+
* - optional running guidance and truncation notice;
|
|
157
|
+
* - a final `[Complete currently-retained logs: ...]` footer.
|
|
158
|
+
*
|
|
159
|
+
* The renderer uses details for metadata, guidance, truncation state, and log
|
|
160
|
+
* paths. Only process output is returned here. Legacy content that has lost
|
|
161
|
+
* its header through tail truncation is also accepted.
|
|
162
|
+
*/
|
|
163
|
+
function extractOutputBody(
|
|
164
|
+
contentText: string,
|
|
165
|
+
details: OutputDetails,
|
|
166
|
+
): string[] {
|
|
167
|
+
const lines = contentText.split("\n");
|
|
168
|
+
|
|
169
|
+
const expectedHeader = `"${details.processName}" (${details.id}) [${details.processStatus}]`;
|
|
170
|
+
let bodyStart = lines[0] === expectedHeader ? 1 : 0;
|
|
171
|
+
if (lines[bodyStart]?.startsWith("filter: ")) {
|
|
172
|
+
bodyStart++;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Use the final exact marker so process output containing the same text does
|
|
176
|
+
// not terminate the rendered preview.
|
|
177
|
+
const footerStart = findLastLineIndex(
|
|
178
|
+
lines,
|
|
179
|
+
(line) => line === "[Complete currently-retained logs:",
|
|
180
|
+
);
|
|
181
|
+
let bodyEnd = footerStart >= 0 ? footerStart : lines.length;
|
|
182
|
+
|
|
183
|
+
if (details.truncation) {
|
|
184
|
+
const truncationNotice = findLastLineIndex(
|
|
185
|
+
lines,
|
|
186
|
+
(line, index) =>
|
|
187
|
+
index < bodyEnd && line.startsWith("[Preview truncated by "),
|
|
188
|
+
);
|
|
189
|
+
if (truncationNotice >= bodyStart) {
|
|
190
|
+
bodyEnd = truncationNotice;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (details.processStatus === "running") {
|
|
195
|
+
const guidance = findLastLineIndex(
|
|
196
|
+
lines,
|
|
197
|
+
(line, index) =>
|
|
198
|
+
index < bodyEnd &&
|
|
199
|
+
line === "Process is still running. Use watches instead of polling.",
|
|
200
|
+
);
|
|
201
|
+
if (guidance >= bodyStart) {
|
|
202
|
+
bodyEnd = guidance;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const body = lines.slice(bodyStart, bodyEnd);
|
|
207
|
+
|
|
208
|
+
// Trim leading/trailing blank lines while preserving internal blank lines.
|
|
209
|
+
let start = 0;
|
|
210
|
+
while (start < body.length && body[start] === "") {
|
|
211
|
+
start++;
|
|
212
|
+
}
|
|
213
|
+
let end = body.length;
|
|
214
|
+
while (end > start && body[end - 1] === "") {
|
|
215
|
+
end--;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return body.slice(start, end);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function findLastLineIndex(
|
|
222
|
+
lines: string[],
|
|
223
|
+
predicate: (line: string, index: number) => boolean,
|
|
224
|
+
): number {
|
|
225
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
226
|
+
if (predicate(lines[index] ?? "", index)) return index;
|
|
227
|
+
}
|
|
228
|
+
return -1;
|
|
229
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type Static, Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
import { MAX_NOTIFY_LOG_MATCHERS, MAX_NOTIFY_PATTERN_LENGTH } from "./notify";
|
|
5
|
+
|
|
6
|
+
// --- Output action constants ---
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_OUTPUT_TAIL_LINES = 100;
|
|
9
|
+
export const MAX_OUTPUT_TAIL_LINES = 2000;
|
|
10
|
+
export const MAX_OUTPUT_SCAN_LINES = 5000;
|
|
11
|
+
export const MAX_OUTPUT_BYTES = 50 * 1024;
|
|
12
|
+
export const MAX_OUTPUT_PATTERN_LENGTH = MAX_NOTIFY_PATTERN_LENGTH;
|
|
13
|
+
|
|
14
|
+
export const PROCESS_OUTPUT_STREAMS = ["stdout", "stderr", "both"] as const;
|
|
15
|
+
|
|
16
|
+
export const PROCESS_OUTPUT_MATCH_MODES = ["literal", "regex"] as const;
|
|
17
|
+
|
|
18
|
+
export const PROCESS_WATCH_UPDATE_MODES = [
|
|
19
|
+
"append",
|
|
20
|
+
"replace",
|
|
21
|
+
"remove",
|
|
22
|
+
"clear",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export const PROCESS_LIST_STATUS_FILTERS = [
|
|
26
|
+
"all",
|
|
27
|
+
"running",
|
|
28
|
+
"finished",
|
|
29
|
+
"failed",
|
|
30
|
+
"terminating",
|
|
31
|
+
"terminate_timeout",
|
|
32
|
+
"killed",
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
export const PROCESS_LIST_SORTS = [
|
|
36
|
+
"startTime_desc",
|
|
37
|
+
"startTime_asc",
|
|
38
|
+
"name_asc",
|
|
39
|
+
"name_desc",
|
|
40
|
+
"status_asc",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
export const PROCESS_NOTIFY_ATTENTIONS = ["turn", "context", "ignore"] as const;
|
|
44
|
+
export const PROCESS_NOTIFY_LOG_MATCH_MODES = ["literal", "regex"] as const;
|
|
45
|
+
export const PROCESS_NOTIFY_LOG_MATCH_STREAMS = [
|
|
46
|
+
"stdout",
|
|
47
|
+
"stderr",
|
|
48
|
+
"both",
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
const NotifyLogMatchParams = Type.Object({
|
|
52
|
+
pattern: Type.String({
|
|
53
|
+
maxLength: MAX_NOTIFY_PATTERN_LENGTH,
|
|
54
|
+
description:
|
|
55
|
+
"Log pattern to match. Limited to 500 characters. Literal by default; regex only when mode is regex.",
|
|
56
|
+
}),
|
|
57
|
+
mode: Type.Optional(
|
|
58
|
+
StringEnum(PROCESS_NOTIFY_LOG_MATCH_MODES, {
|
|
59
|
+
description: "Pattern matching mode. Defaults to literal.",
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
stream: Type.Optional(
|
|
63
|
+
StringEnum(PROCESS_NOTIFY_LOG_MATCH_STREAMS, {
|
|
64
|
+
description: "Output stream to inspect. Defaults to both.",
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
repeat: Type.Optional(
|
|
68
|
+
Type.Boolean({
|
|
69
|
+
description:
|
|
70
|
+
"Whether this matcher can notify more than once. Defaults to false.",
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
on: Type.Optional(
|
|
74
|
+
StringEnum(PROCESS_NOTIFY_ATTENTIONS, {
|
|
75
|
+
description: "Agent attention for this log match. Defaults to turn.",
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const WatchUpdateItemParams = Type.Object({
|
|
81
|
+
index: Type.Optional(
|
|
82
|
+
Type.Integer({
|
|
83
|
+
minimum: 0,
|
|
84
|
+
description:
|
|
85
|
+
"Matcher index to remove. Only for remove mode. If pattern is also provided, index takes precedence.",
|
|
86
|
+
}),
|
|
87
|
+
),
|
|
88
|
+
pattern: Type.Optional(
|
|
89
|
+
Type.String({
|
|
90
|
+
maxLength: MAX_NOTIFY_PATTERN_LENGTH,
|
|
91
|
+
description:
|
|
92
|
+
"Log pattern. Required for append and replace modes. Optional for remove mode (use index or pattern).",
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
mode: Type.Optional(
|
|
96
|
+
StringEnum(PROCESS_NOTIFY_LOG_MATCH_MODES, {
|
|
97
|
+
description: "Pattern matching mode. Defaults to literal.",
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
stream: Type.Optional(
|
|
101
|
+
StringEnum(PROCESS_NOTIFY_LOG_MATCH_STREAMS, {
|
|
102
|
+
description: "Output stream to inspect. Defaults to both.",
|
|
103
|
+
}),
|
|
104
|
+
),
|
|
105
|
+
repeat: Type.Optional(
|
|
106
|
+
Type.Boolean({
|
|
107
|
+
description:
|
|
108
|
+
"Whether this matcher can notify more than once. Defaults to false.",
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
111
|
+
on: Type.Optional(
|
|
112
|
+
StringEnum(PROCESS_NOTIFY_ATTENTIONS, {
|
|
113
|
+
description: "Agent attention for this log match. Defaults to turn.",
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const NotifyParams = Type.Object({
|
|
119
|
+
onSuccess: Type.Optional(
|
|
120
|
+
StringEnum(PROCESS_NOTIFY_ATTENTIONS, {
|
|
121
|
+
description:
|
|
122
|
+
"Agent attention when the process exits successfully. Defaults to context.",
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
onFailure: Type.Optional(
|
|
126
|
+
StringEnum(PROCESS_NOTIFY_ATTENTIONS, {
|
|
127
|
+
description:
|
|
128
|
+
"Agent attention when the process fails or crashes. Defaults to turn.",
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
onKilled: Type.Optional(
|
|
132
|
+
StringEnum(PROCESS_NOTIFY_ATTENTIONS, {
|
|
133
|
+
description:
|
|
134
|
+
"Agent attention when the process is killed. Defaults to context.",
|
|
135
|
+
}),
|
|
136
|
+
),
|
|
137
|
+
logMatches: Type.Optional(
|
|
138
|
+
Type.Array(NotifyLogMatchParams, {
|
|
139
|
+
maxItems: MAX_NOTIFY_LOG_MATCHERS,
|
|
140
|
+
description:
|
|
141
|
+
"Log match notifications. Supports at most 20 matchers, with each pattern limited to 500 characters.",
|
|
142
|
+
}),
|
|
143
|
+
),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export const ProcessesParams = Type.Object({
|
|
147
|
+
action: StringEnum(
|
|
148
|
+
["start", "list", "stop", "output", "write", "update", "clear"] as const,
|
|
149
|
+
{
|
|
150
|
+
description: "Action to perform.",
|
|
151
|
+
},
|
|
152
|
+
),
|
|
153
|
+
name: Type.Optional(
|
|
154
|
+
Type.String({ description: "Process name. Required for start." }),
|
|
155
|
+
),
|
|
156
|
+
command: Type.Optional(
|
|
157
|
+
Type.String({ description: "Shell command to run. Required for start." }),
|
|
158
|
+
),
|
|
159
|
+
cwd: Type.Optional(
|
|
160
|
+
Type.String({
|
|
161
|
+
description:
|
|
162
|
+
"Working directory for start. Defaults to the agent's current working directory. Only for start.",
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
notify: Type.Optional(NotifyParams),
|
|
166
|
+
id: Type.Optional(
|
|
167
|
+
Type.String({
|
|
168
|
+
description:
|
|
169
|
+
"Opaque process ID returned by start or list (for example, proc_ab12). Required for stop, output, write, and update. Process names are not accepted.",
|
|
170
|
+
}),
|
|
171
|
+
),
|
|
172
|
+
limit: Type.Optional(
|
|
173
|
+
Type.Number({ description: "Maximum number of processes to list." }),
|
|
174
|
+
),
|
|
175
|
+
sortBy: Type.Optional(
|
|
176
|
+
StringEnum(PROCESS_LIST_SORTS, {
|
|
177
|
+
description: "Sort order for process list results.",
|
|
178
|
+
}),
|
|
179
|
+
),
|
|
180
|
+
statuses: Type.Optional(
|
|
181
|
+
Type.Array(StringEnum(PROCESS_LIST_STATUS_FILTERS), {
|
|
182
|
+
description:
|
|
183
|
+
"Process list status filters. Use all for no filtering; finished means exited successfully; failed means exited unsuccessfully or terminate_timeout.",
|
|
184
|
+
}),
|
|
185
|
+
),
|
|
186
|
+
stream: Type.Optional(
|
|
187
|
+
StringEnum(PROCESS_OUTPUT_STREAMS, {
|
|
188
|
+
description:
|
|
189
|
+
"Output stream to return. Defaults to both. Only for output action.",
|
|
190
|
+
}),
|
|
191
|
+
),
|
|
192
|
+
tailLines: Type.Optional(
|
|
193
|
+
Type.Integer({
|
|
194
|
+
minimum: 1,
|
|
195
|
+
maximum: MAX_OUTPUT_TAIL_LINES,
|
|
196
|
+
description:
|
|
197
|
+
"Maximum matching lines to return per selected stream. Defaults to 100. Only for output action.",
|
|
198
|
+
}),
|
|
199
|
+
),
|
|
200
|
+
pattern: Type.Optional(
|
|
201
|
+
Type.String({
|
|
202
|
+
maxLength: MAX_OUTPUT_PATTERN_LENGTH,
|
|
203
|
+
description:
|
|
204
|
+
"Optional output filter. Literal by default; regex only when mode is regex. Only for output action.",
|
|
205
|
+
}),
|
|
206
|
+
),
|
|
207
|
+
input: Type.Optional(
|
|
208
|
+
Type.String({
|
|
209
|
+
description:
|
|
210
|
+
"Text to write to the process stdin. Only for write action. Defaults to an empty string when closing stdin with end.",
|
|
211
|
+
}),
|
|
212
|
+
),
|
|
213
|
+
end: Type.Optional(
|
|
214
|
+
Type.Boolean({
|
|
215
|
+
description:
|
|
216
|
+
"Close stdin after writing. Use to signal end-of-input (EOF). Only for write action.",
|
|
217
|
+
}),
|
|
218
|
+
),
|
|
219
|
+
mode: Type.Optional(
|
|
220
|
+
StringEnum(PROCESS_OUTPUT_MATCH_MODES, {
|
|
221
|
+
description:
|
|
222
|
+
"Pattern matching mode for output filter. Defaults to literal. Only for output action.",
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
watches: Type.Optional(
|
|
226
|
+
Type.Object(
|
|
227
|
+
{
|
|
228
|
+
mode: StringEnum(PROCESS_WATCH_UPDATE_MODES, {
|
|
229
|
+
description: "How to update log watches.",
|
|
230
|
+
}),
|
|
231
|
+
items: Type.Optional(
|
|
232
|
+
Type.Array(WatchUpdateItemParams, {
|
|
233
|
+
maxItems: MAX_NOTIFY_LOG_MATCHERS,
|
|
234
|
+
description:
|
|
235
|
+
"Watch entries. For append/replace, provide full matcher definitions. For remove, provide index or pattern to identify matchers to remove. Ignored for clear.",
|
|
236
|
+
}),
|
|
237
|
+
),
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
description:
|
|
241
|
+
"Update log watches on a running process. Only for update action.",
|
|
242
|
+
},
|
|
243
|
+
),
|
|
244
|
+
),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
export type ProcessesParamsType = Static<typeof ProcessesParams>;
|
|
248
|
+
|
|
249
|
+
export type ProcessAction = ProcessesParamsType["action"];
|
|
250
|
+
export type ProcessListStatusFilter =
|
|
251
|
+
(typeof PROCESS_LIST_STATUS_FILTERS)[number];
|
|
252
|
+
export type ProcessListSort = (typeof PROCESS_LIST_SORTS)[number];
|
|
253
|
+
export type ProcessNotifyAttention = (typeof PROCESS_NOTIFY_ATTENTIONS)[number];
|
|
254
|
+
export type ProcessNotifyLogMatchMode =
|
|
255
|
+
(typeof PROCESS_NOTIFY_LOG_MATCH_MODES)[number];
|
|
256
|
+
export type ProcessNotifyLogMatchStream =
|
|
257
|
+
(typeof PROCESS_NOTIFY_LOG_MATCH_STREAMS)[number];
|
|
258
|
+
|
|
259
|
+
export type ProcessOutputStream = (typeof PROCESS_OUTPUT_STREAMS)[number];
|
|
260
|
+
export type ProcessOutputMatchMode =
|
|
261
|
+
(typeof PROCESS_OUTPUT_MATCH_MODES)[number];
|
|
262
|
+
export type ProcessWatchUpdateMode =
|
|
263
|
+
(typeof PROCESS_WATCH_UPDATE_MODES)[number];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import type { ProcessManager } from "../../../../src/manager";
|
|
4
|
+
import type { ProcessInfo } from "../../../../src/types";
|
|
5
|
+
import type {
|
|
6
|
+
NotificationRegistry,
|
|
7
|
+
NotifyConfig,
|
|
8
|
+
} from "../../notifications/registry";
|
|
9
|
+
import { normalizeNotifyConfig } from "../notify";
|
|
10
|
+
import type { ProcessesParamsType } from "../schema";
|
|
11
|
+
import { formatMatcherForModel } from "../watch-format";
|
|
12
|
+
|
|
13
|
+
export interface StartDetails {
|
|
14
|
+
action: "start";
|
|
15
|
+
process: ProcessInfo;
|
|
16
|
+
notify: NotifyConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function executeStart(
|
|
20
|
+
params: ProcessesParamsType,
|
|
21
|
+
manager: ProcessManager,
|
|
22
|
+
ctx: ExtensionContext,
|
|
23
|
+
notifications: NotificationRegistry,
|
|
24
|
+
): StartDetails {
|
|
25
|
+
if (!params.name) {
|
|
26
|
+
throw new Error("process start requires name");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!params.command) {
|
|
30
|
+
throw new Error("process start requires command");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const notify = normalizeNotifyConfig(params.notify);
|
|
34
|
+
|
|
35
|
+
const cwd = params.cwd ?? ctx.cwd;
|
|
36
|
+
const process = manager.start(params.name, params.command, cwd);
|
|
37
|
+
notifications.register(process.id, notify);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
action: "start",
|
|
41
|
+
process,
|
|
42
|
+
notify,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function formatStartDetails(details: StartDetails): string {
|
|
47
|
+
const process = details.process;
|
|
48
|
+
const parts = [
|
|
49
|
+
`Started process ${process.name} (${process.id}) with pid ${process.pid}.`,
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
if (details.notify.logMatches && details.notify.logMatches.length > 0) {
|
|
53
|
+
parts.push(
|
|
54
|
+
`Watches:\n${details.notify.logMatches
|
|
55
|
+
.map((matcher) => formatMatcherForModel(matcher))
|
|
56
|
+
.join("\n")}`,
|
|
57
|
+
);
|
|
58
|
+
parts.push(
|
|
59
|
+
"Continue other work; watch notifications will trigger follow-up.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return parts.join(" ");
|
|
64
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { buildMatcherLine, ProcessActionHeader } from "../components";
|
|
4
|
+
import type { ProcessesParamsType } from "../schema";
|
|
5
|
+
import {
|
|
6
|
+
buildCompactProcessLine,
|
|
7
|
+
buildField,
|
|
8
|
+
buildProcessDetails,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
import type { StartDetails } from ".";
|
|
11
|
+
|
|
12
|
+
export function buildHeader(
|
|
13
|
+
args: ProcessesParamsType,
|
|
14
|
+
theme: Theme,
|
|
15
|
+
options?: { expanded?: boolean },
|
|
16
|
+
): Container {
|
|
17
|
+
return new ProcessActionHeader(args, theme, {
|
|
18
|
+
action: "start",
|
|
19
|
+
expanded: options?.expanded,
|
|
20
|
+
suffix: args.name ? theme.fg("accent", `\`${args.name}\``) : undefined,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildExpanded(details: StartDetails, theme: Theme): Container {
|
|
25
|
+
const container = buildProcessDetails(details.process, theme, {
|
|
26
|
+
runtime: false,
|
|
27
|
+
});
|
|
28
|
+
const watches = details.notify.logMatches ?? [];
|
|
29
|
+
|
|
30
|
+
if (watches.length > 0) {
|
|
31
|
+
container.addChild(
|
|
32
|
+
new Text(
|
|
33
|
+
theme.bold(
|
|
34
|
+
theme.fg(
|
|
35
|
+
"text",
|
|
36
|
+
`watches ${theme.fg("muted", `(${watches.length})`)}`,
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
0,
|
|
40
|
+
0,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
for (const matcher of watches) {
|
|
44
|
+
container.addChild(buildMatcherLine(matcher, theme, " "));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return container;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildCollapsed(details: StartDetails, theme: Theme): Container {
|
|
52
|
+
const container = new Container();
|
|
53
|
+
container.addChild(buildCompactProcessLine(details.process, theme));
|
|
54
|
+
const watchCount = details.notify.logMatches?.length ?? 0;
|
|
55
|
+
if (watchCount > 0) {
|
|
56
|
+
container.addChild(buildField("watches", watchCount, theme));
|
|
57
|
+
}
|
|
58
|
+
return container;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildFooter(
|
|
62
|
+
_details: StartDetails,
|
|
63
|
+
_options: { expanded?: boolean },
|
|
64
|
+
_theme: Theme,
|
|
65
|
+
): Container | null {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ProcessManager } from "../../../../src/manager";
|
|
2
|
+
import type { KillResult } from "../../../../src/types";
|
|
3
|
+
import { killIntentionally } from "../../handlers/kill-process";
|
|
4
|
+
import type { NotificationRegistry } from "../../notifications/registry";
|
|
5
|
+
import type { ProcessesParamsType } from "../schema";
|
|
6
|
+
|
|
7
|
+
export interface StopDetails {
|
|
8
|
+
action: "stop";
|
|
9
|
+
result: KillResult;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function executeStop(
|
|
13
|
+
params: ProcessesParamsType,
|
|
14
|
+
manager: ProcessManager,
|
|
15
|
+
notifications: NotificationRegistry,
|
|
16
|
+
): Promise<StopDetails> {
|
|
17
|
+
if (!params.id) {
|
|
18
|
+
throw new Error("process stop requires id");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = await killIntentionally(manager, notifications, params.id);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
action: "stop",
|
|
25
|
+
result,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function formatStopDetails(details: StopDetails): string {
|
|
30
|
+
if (details.result.ok) {
|
|
31
|
+
return `Stopped process ${details.result.info.name} (${details.result.info.id}).`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return `Failed to stop process ${details.result.info.id}: ${details.result.reason}.`;
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Container } from "@earendil-works/pi-tui";
|
|
3
|
+
import { ProcessActionHeader } from "../components";
|
|
4
|
+
import type { ProcessesParamsType } from "../schema";
|
|
5
|
+
import {
|
|
6
|
+
buildCompactProcessLine,
|
|
7
|
+
buildField,
|
|
8
|
+
buildProcessDetails,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
import type { StopDetails } from ".";
|
|
11
|
+
|
|
12
|
+
export function buildHeader(
|
|
13
|
+
args: ProcessesParamsType,
|
|
14
|
+
theme: Theme,
|
|
15
|
+
options?: { expanded?: boolean },
|
|
16
|
+
): Container {
|
|
17
|
+
return new ProcessActionHeader(args, theme, {
|
|
18
|
+
action: "stop",
|
|
19
|
+
expanded: options?.expanded,
|
|
20
|
+
suffix: args.id ? theme.fg("accent", `(${args.id})`) : undefined,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildExpanded(details: StopDetails, theme: Theme): Container {
|
|
25
|
+
return buildProcessDetails(details.result.info, theme);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildCollapsed(details: StopDetails, theme: Theme): Container {
|
|
29
|
+
const container = new Container();
|
|
30
|
+
container.addChild(buildCompactProcessLine(details.result.info, theme));
|
|
31
|
+
if (!details.result.ok) {
|
|
32
|
+
container.addChild(buildField("reason", details.result.reason, theme));
|
|
33
|
+
}
|
|
34
|
+
return container;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildFooter(
|
|
38
|
+
_details: StopDetails,
|
|
39
|
+
_options: { expanded?: boolean },
|
|
40
|
+
_theme: Theme,
|
|
41
|
+
): Container | null {
|
|
42
|
+
return null;
|
|
43
|
+
}
|