@aliou/pi-processes 0.4.7 → 0.6.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/README.md +83 -13
- package/package.json +10 -4
- package/src/commands/clear/command.ts +14 -0
- package/src/commands/clear/index.ts +1 -0
- package/src/commands/completions.ts +38 -0
- package/src/commands/dock/command.ts +29 -0
- package/src/commands/dock/index.ts +1 -0
- package/src/commands/index.ts +26 -327
- package/src/commands/kill/command.ts +69 -0
- package/src/commands/kill/index.ts +1 -0
- package/src/commands/logs/command.ts +47 -0
- package/src/commands/logs/index.ts +1 -0
- package/src/commands/pick-process.ts +34 -0
- package/src/commands/pin/command.ts +33 -0
- package/src/commands/pin/index.ts +1 -0
- package/src/commands/processes/command.ts +39 -0
- package/src/commands/processes/index.ts +1 -0
- package/src/commands/settings/apply-setting-change.ts +72 -0
- package/src/commands/settings/build-sections.ts +160 -0
- package/src/commands/settings/command.ts +20 -0
- package/src/commands/settings/index.ts +1 -0
- package/src/components/log-dock-component.ts +238 -0
- package/src/components/log-file-viewer.ts +317 -0
- package/src/components/log-overlay-component.ts +555 -0
- package/src/components/process-picker-component.ts +14 -2
- package/src/components/processes-component.ts +41 -21
- package/src/config.ts +38 -1
- package/src/constants/index.ts +1 -0
- package/src/constants/types.ts +7 -1
- package/src/hooks/background-blocker.ts +66 -0
- package/src/hooks/index.ts +15 -3
- package/src/hooks/process-end.ts +3 -30
- package/src/hooks/widget/index.ts +2 -0
- package/src/hooks/widget/setup.ts +168 -0
- package/src/hooks/{widget.ts → widget/status-widget.ts} +27 -68
- package/src/hooks/widget/types.ts +21 -0
- package/src/index.ts +9 -3
- package/src/manager.ts +61 -9
- package/src/tools/actions/index.ts +5 -0
- package/src/tools/actions/write.ts +87 -0
- package/src/tools/index.ts +82 -14
- package/src/utils/command-executor.test.ts +2 -1
- package/src/utils/command-executor.ts +1 -1
- package/src/utils/keybindings.ts +76 -0
- package/src/utils/shell-utils.ts +133 -0
- package/src/commands/settings-command.ts +0 -157
- package/src/components/log-stream-component.ts +0 -149
- package/src/test/test-exit-crash.sh +0 -19
- package/src/test/test-exit-failure.sh +0 -17
- package/src/test/test-exit-success.sh +0 -16
- package/src/test/test-output.sh +0 -28
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
registerSettingsCommand,
|
|
3
|
-
type SettingsSection,
|
|
4
|
-
} from "@aliou/pi-utils-settings";
|
|
5
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import type { ProcessesConfig, ResolvedProcessesConfig } from "../config";
|
|
7
|
-
import { configLoader } from "../config";
|
|
8
|
-
|
|
9
|
-
export function registerProcessesSettings(
|
|
10
|
-
pi: ExtensionAPI,
|
|
11
|
-
onSave?: () => void,
|
|
12
|
-
): void {
|
|
13
|
-
registerSettingsCommand<ProcessesConfig, ResolvedProcessesConfig>(pi, {
|
|
14
|
-
commandName: "process:settings",
|
|
15
|
-
title: "Processes Settings",
|
|
16
|
-
configStore: configLoader,
|
|
17
|
-
buildSections: (
|
|
18
|
-
tabConfig: ProcessesConfig | null,
|
|
19
|
-
resolved: ResolvedProcessesConfig,
|
|
20
|
-
): SettingsSection[] => {
|
|
21
|
-
return [
|
|
22
|
-
{
|
|
23
|
-
label: "Process List",
|
|
24
|
-
items: [
|
|
25
|
-
{
|
|
26
|
-
id: "processList.maxVisibleProcesses",
|
|
27
|
-
label: "Max visible processes",
|
|
28
|
-
description:
|
|
29
|
-
"Maximum processes shown in the /processes list before scrolling",
|
|
30
|
-
currentValue: String(
|
|
31
|
-
tabConfig?.processList?.maxVisibleProcesses ??
|
|
32
|
-
resolved.processList.maxVisibleProcesses,
|
|
33
|
-
),
|
|
34
|
-
values: ["4", "6", "8", "12", "16"],
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "processList.maxPreviewLines",
|
|
38
|
-
label: "Max preview lines",
|
|
39
|
-
description: "Log preview lines shown below the selected process",
|
|
40
|
-
currentValue: String(
|
|
41
|
-
tabConfig?.processList?.maxPreviewLines ??
|
|
42
|
-
resolved.processList.maxPreviewLines,
|
|
43
|
-
),
|
|
44
|
-
values: ["6", "8", "12", "16", "24"],
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
label: "Output Limits",
|
|
50
|
-
items: [
|
|
51
|
-
{
|
|
52
|
-
id: "output.defaultTailLines",
|
|
53
|
-
label: "Default tail lines",
|
|
54
|
-
description:
|
|
55
|
-
"Number of tail lines returned to the agent by default",
|
|
56
|
-
currentValue: String(
|
|
57
|
-
tabConfig?.output?.defaultTailLines ??
|
|
58
|
-
resolved.output.defaultTailLines,
|
|
59
|
-
),
|
|
60
|
-
values: ["50", "100", "200", "500"],
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: "output.maxOutputLines",
|
|
64
|
-
label: "Max output lines",
|
|
65
|
-
description: "Hard cap on output lines returned to the agent",
|
|
66
|
-
currentValue: String(
|
|
67
|
-
tabConfig?.output?.maxOutputLines ??
|
|
68
|
-
resolved.output.maxOutputLines,
|
|
69
|
-
),
|
|
70
|
-
values: ["100", "200", "500", "1000"],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
label: "Execution",
|
|
76
|
-
items: [
|
|
77
|
-
{
|
|
78
|
-
id: "execution.shellPath",
|
|
79
|
-
label: "Shell path",
|
|
80
|
-
description:
|
|
81
|
-
"Absolute shell path override used to execute commands",
|
|
82
|
-
currentValue:
|
|
83
|
-
tabConfig?.execution?.shellPath ??
|
|
84
|
-
resolved.execution.shellPath ??
|
|
85
|
-
"auto",
|
|
86
|
-
values: [
|
|
87
|
-
"auto",
|
|
88
|
-
"/run/current-system/sw/bin/bash",
|
|
89
|
-
"/bin/bash",
|
|
90
|
-
"/usr/bin/bash",
|
|
91
|
-
"/usr/local/bin/bash",
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
label: "Widget",
|
|
98
|
-
items: [
|
|
99
|
-
{
|
|
100
|
-
id: "widget.showStatusWidget",
|
|
101
|
-
label: "Show status widget",
|
|
102
|
-
description: "Show process status widget below the editor",
|
|
103
|
-
currentValue:
|
|
104
|
-
(tabConfig?.widget?.showStatusWidget ??
|
|
105
|
-
resolved.widget.showStatusWidget)
|
|
106
|
-
? "on"
|
|
107
|
-
: "off",
|
|
108
|
-
values: ["on", "off"],
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
];
|
|
113
|
-
},
|
|
114
|
-
onSettingChange: (id, newValue, config) => {
|
|
115
|
-
const updated = structuredClone(config);
|
|
116
|
-
// Boolean fields.
|
|
117
|
-
if (id === "widget.showStatusWidget") {
|
|
118
|
-
if (!updated.widget) updated.widget = {};
|
|
119
|
-
updated.widget.showStatusWidget = newValue === "on";
|
|
120
|
-
return updated;
|
|
121
|
-
}
|
|
122
|
-
if (id === "execution.shellPath") {
|
|
123
|
-
if (!updated.execution) updated.execution = {};
|
|
124
|
-
updated.execution.shellPath =
|
|
125
|
-
newValue === "auto" ? undefined : newValue;
|
|
126
|
-
return updated;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Numeric fields.
|
|
130
|
-
const num = Number.parseInt(newValue, 10);
|
|
131
|
-
if (Number.isNaN(num)) return null;
|
|
132
|
-
|
|
133
|
-
switch (id) {
|
|
134
|
-
case "processList.maxVisibleProcesses":
|
|
135
|
-
if (!updated.processList) updated.processList = {};
|
|
136
|
-
updated.processList.maxVisibleProcesses = num;
|
|
137
|
-
break;
|
|
138
|
-
case "processList.maxPreviewLines":
|
|
139
|
-
if (!updated.processList) updated.processList = {};
|
|
140
|
-
updated.processList.maxPreviewLines = num;
|
|
141
|
-
break;
|
|
142
|
-
case "output.defaultTailLines":
|
|
143
|
-
if (!updated.output) updated.output = {};
|
|
144
|
-
updated.output.defaultTailLines = num;
|
|
145
|
-
break;
|
|
146
|
-
case "output.maxOutputLines":
|
|
147
|
-
if (!updated.output) updated.output = {};
|
|
148
|
-
updated.output.maxOutputLines = num;
|
|
149
|
-
break;
|
|
150
|
-
default:
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
return updated;
|
|
154
|
-
},
|
|
155
|
-
onSave,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createPanelPadder,
|
|
3
|
-
renderPanelRule,
|
|
4
|
-
renderPanelTitleLine,
|
|
5
|
-
} from "@aliou/pi-utils-ui";
|
|
6
|
-
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
7
|
-
import {
|
|
8
|
-
type Component,
|
|
9
|
-
truncateToWidth,
|
|
10
|
-
visibleWidth,
|
|
11
|
-
} from "@mariozechner/pi-tui";
|
|
12
|
-
import type { ProcessManager } from "../manager";
|
|
13
|
-
import { stripAnsi } from "../utils";
|
|
14
|
-
import { statusIcon, statusLabel } from "./status-format";
|
|
15
|
-
|
|
16
|
-
const MAX_LOG_LINES = 16;
|
|
17
|
-
const POLL_INTERVAL_MS = 500;
|
|
18
|
-
|
|
19
|
-
export class LogStreamComponent implements Component {
|
|
20
|
-
private tui: { requestRender: () => void };
|
|
21
|
-
private theme: Theme;
|
|
22
|
-
private manager: ProcessManager;
|
|
23
|
-
private processId: string;
|
|
24
|
-
private timer: ReturnType<typeof setInterval> | null = null;
|
|
25
|
-
private unsubscribe: (() => void) | null = null;
|
|
26
|
-
private cachedLines: string[] = [];
|
|
27
|
-
private cachedWidth = 0;
|
|
28
|
-
|
|
29
|
-
constructor(
|
|
30
|
-
tui: { requestRender: () => void },
|
|
31
|
-
theme: Theme,
|
|
32
|
-
manager: ProcessManager,
|
|
33
|
-
processId: string,
|
|
34
|
-
) {
|
|
35
|
-
this.tui = tui;
|
|
36
|
-
this.theme = theme;
|
|
37
|
-
this.manager = manager;
|
|
38
|
-
this.processId = processId;
|
|
39
|
-
|
|
40
|
-
// Poll log file for new output.
|
|
41
|
-
this.timer = setInterval(() => {
|
|
42
|
-
this.invalidate();
|
|
43
|
-
this.tui.requestRender();
|
|
44
|
-
}, POLL_INTERVAL_MS);
|
|
45
|
-
|
|
46
|
-
// Also re-render on process events (status changes, etc.).
|
|
47
|
-
this.unsubscribe = this.manager.onEvent(() => {
|
|
48
|
-
this.invalidate();
|
|
49
|
-
this.tui.requestRender();
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
handleInput(_data: string): boolean {
|
|
54
|
-
// Widget doesn't handle input — the editor is still active.
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
invalidate(): void {
|
|
59
|
-
this.cachedWidth = 0;
|
|
60
|
-
this.cachedLines = [];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
render(width: number): string[] {
|
|
64
|
-
if (width === this.cachedWidth && this.cachedLines.length > 0) {
|
|
65
|
-
return this.cachedLines;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const theme = this.theme;
|
|
69
|
-
const dim = (s: string) => theme.fg("dim", s);
|
|
70
|
-
const warning = (s: string) => theme.fg("warning", s);
|
|
71
|
-
const innerWidth = width - 2;
|
|
72
|
-
|
|
73
|
-
const basePadLine = createPanelPadder(width);
|
|
74
|
-
const padLine = (content: string): string => {
|
|
75
|
-
const contentWidth = visibleWidth(content);
|
|
76
|
-
return basePadLine(
|
|
77
|
-
contentWidth > innerWidth
|
|
78
|
-
? truncateToWidth(content, innerWidth)
|
|
79
|
-
: content,
|
|
80
|
-
);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const lines: string[] = [];
|
|
84
|
-
const proc = this.manager.get(this.processId);
|
|
85
|
-
|
|
86
|
-
if (!proc) {
|
|
87
|
-
lines.push(renderPanelRule(width, theme));
|
|
88
|
-
lines.push(padLine(warning("Process not found")));
|
|
89
|
-
lines.push(renderPanelRule(width, theme));
|
|
90
|
-
this.cachedLines = lines;
|
|
91
|
-
this.cachedWidth = width;
|
|
92
|
-
return this.cachedLines;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Header
|
|
96
|
-
const icon = statusIcon(proc.status, proc.success);
|
|
97
|
-
const label = statusLabel(proc);
|
|
98
|
-
lines.push(
|
|
99
|
-
renderPanelTitleLine(
|
|
100
|
-
`Process: ${proc.name} (${proc.id}) ${icon} ${label}`,
|
|
101
|
-
width,
|
|
102
|
-
theme,
|
|
103
|
-
),
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
// Log lines (interleaved stdout + stderr in temporal order).
|
|
107
|
-
const logLines = this.manager.getCombinedOutput(
|
|
108
|
-
this.processId,
|
|
109
|
-
MAX_LOG_LINES,
|
|
110
|
-
);
|
|
111
|
-
if (logLines && logLines.length > 0) {
|
|
112
|
-
for (const line of logLines) {
|
|
113
|
-
const cleaned = stripAnsi(line.text);
|
|
114
|
-
const display = truncateToWidth(cleaned, innerWidth - 2);
|
|
115
|
-
if (line.type === "stderr") {
|
|
116
|
-
lines.push(padLine(warning(display)));
|
|
117
|
-
} else {
|
|
118
|
-
lines.push(padLine(display));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
lines.push(padLine(dim("(no output yet)")));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Pad to MAX_LOG_LINES for stable height.
|
|
126
|
-
const renderedLogLines = lines.length - 1; // minus header
|
|
127
|
-
for (let i = renderedLogLines; i < MAX_LOG_LINES; i++) {
|
|
128
|
-
lines.push(padLine(""));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Footer hint
|
|
132
|
-
lines.push(renderPanelRule(width, theme));
|
|
133
|
-
lines.push(padLine(dim("Run /process:stream to dismiss")));
|
|
134
|
-
lines.push(renderPanelRule(width, theme));
|
|
135
|
-
|
|
136
|
-
this.cachedLines = lines;
|
|
137
|
-
this.cachedWidth = width;
|
|
138
|
-
return this.cachedLines;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
dispose(): void {
|
|
142
|
-
if (this.timer) {
|
|
143
|
-
clearInterval(this.timer);
|
|
144
|
-
this.timer = null;
|
|
145
|
-
}
|
|
146
|
-
this.unsubscribe?.();
|
|
147
|
-
this.unsubscribe = null;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test script that simulates a crash (exit code 137 - like SIGKILL)
|
|
3
|
-
# Usage: ./test-exit-crash.sh [seconds]
|
|
4
|
-
|
|
5
|
-
WAIT_SECONDS=${1:-17}
|
|
6
|
-
|
|
7
|
-
echo "Starting unstable task..."
|
|
8
|
-
echo "Will crash in ${WAIT_SECONDS} seconds"
|
|
9
|
-
|
|
10
|
-
for i in $(seq 1 $WAIT_SECONDS); do
|
|
11
|
-
echo "[$(date '+%H:%M:%S')] Running... ($i/$WAIT_SECONDS)"
|
|
12
|
-
if [ $i -eq $((WAIT_SECONDS - 1)) ]; then
|
|
13
|
-
echo "[WARN] Memory pressure detected" >&2
|
|
14
|
-
fi
|
|
15
|
-
sleep 1
|
|
16
|
-
done
|
|
17
|
-
|
|
18
|
-
echo "FATAL: Segmentation fault (core dumped)" >&2
|
|
19
|
-
exit 137
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test script that exits with failure (exit code 1)
|
|
3
|
-
# Usage: ./test-exit-failure.sh [seconds]
|
|
4
|
-
|
|
5
|
-
WAIT_SECONDS=${1:-15}
|
|
6
|
-
|
|
7
|
-
echo "Starting failing task..."
|
|
8
|
-
echo "Will fail in ${WAIT_SECONDS} seconds"
|
|
9
|
-
|
|
10
|
-
for i in $(seq 1 $WAIT_SECONDS); do
|
|
11
|
-
echo "[$(date '+%H:%M:%S')] Processing... ($i/$WAIT_SECONDS)"
|
|
12
|
-
sleep 1
|
|
13
|
-
done
|
|
14
|
-
|
|
15
|
-
echo "ERROR: Task failed!" >&2
|
|
16
|
-
echo "Something went wrong!" >&2
|
|
17
|
-
exit 1
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test script that exits successfully (exit code 0)
|
|
3
|
-
# Usage: ./test-exit-success.sh [seconds]
|
|
4
|
-
|
|
5
|
-
WAIT_SECONDS=${1:-13}
|
|
6
|
-
|
|
7
|
-
echo "Starting successful task..."
|
|
8
|
-
echo "Will complete in ${WAIT_SECONDS} seconds"
|
|
9
|
-
|
|
10
|
-
for i in $(seq 1 $WAIT_SECONDS); do
|
|
11
|
-
echo "[$(date '+%H:%M:%S')] Working... ($i/$WAIT_SECONDS)"
|
|
12
|
-
sleep 1
|
|
13
|
-
done
|
|
14
|
-
|
|
15
|
-
echo "Task completed successfully!"
|
|
16
|
-
exit 0
|
package/src/test/test-output.sh
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test script for processes extension
|
|
3
|
-
# Writes 80 characters every second, empty line every 10 seconds
|
|
4
|
-
|
|
5
|
-
counter=0
|
|
6
|
-
while true; do
|
|
7
|
-
counter=$((counter + 1))
|
|
8
|
-
|
|
9
|
-
# Generate 80 characters: timestamp + padding
|
|
10
|
-
timestamp=$(date '+%H:%M:%S')
|
|
11
|
-
line=$(printf "[%s] Line %05d: " "$timestamp" "$counter")
|
|
12
|
-
# Pad to 80 chars with random chars
|
|
13
|
-
padding_len=$((80 - ${#line}))
|
|
14
|
-
padding=$(head -c $padding_len /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' 2>/dev/null || printf '%*s' "$padding_len" '' | tr ' ' 'x')
|
|
15
|
-
echo "${line}${padding}"
|
|
16
|
-
|
|
17
|
-
# Every 10 seconds, print an empty line
|
|
18
|
-
if [ $((counter % 10)) -eq 0 ]; then
|
|
19
|
-
echo ""
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
# Every 5 lines, write something to stderr
|
|
23
|
-
if [ $((counter % 5)) -eq 0 ]; then
|
|
24
|
-
echo "[WARN] Counter reached $counter" >&2
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
sleep 1
|
|
28
|
-
done
|