@aliou/pi-processes 0.10.8 → 0.11.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 +40 -0
- package/extensions/processes/client.ts +24 -2
- package/extensions/processes/commands/overview.ts +1 -1
- package/extensions/processes/components/overview-component.ts +121 -51
- package/extensions/processes/config/migrations/001-v0-9-4-to-v0-10-0-config.ts +5 -8
- package/extensions/processes/config/types.ts +1 -1
- package/extensions/processes/handlers/commands.ts +21 -41
- package/extensions/processes/handlers/notifications.ts +3 -37
- package/extensions/processes/handlers/requests.ts +1 -54
- package/extensions/processes/handlers/subscriptions.ts +2 -35
- package/extensions/processes/hooks/event-bridge.ts +1 -1
- package/extensions/processes/index.ts +2 -2
- package/extensions/processes/notifications/service.ts +4 -1
- package/extensions/processes/notifications/types.ts +2 -2
- package/extensions/processes/tools/notify.ts +37 -130
- package/extensions/processes/tools/schema.ts +5 -2
- package/extensions/processes/tools/update/index.ts +15 -40
- package/extensions/processes-debug/index.ts +67 -0
- package/extensions/processes-dock/client.ts +2 -2
- package/extensions/processes-dock/widget/setup.ts +12 -37
- package/extensions/processes-logs/client.ts +2 -2
- package/extensions/processes-logs/commands/logs.ts +1 -1
- package/extensions/processes-logs/components/log-file-viewer.ts +135 -8
- package/extensions/processes-logs/components/log-overlay-component.ts +156 -82
- package/extensions/processes-logs/logs-client.ts +2 -23
- package/extensions/shared/log-line.ts +49 -2
- package/{src → extensions/shared}/protocol/broadcasts.ts +1 -1
- package/{src → extensions/shared}/protocol/channels.ts +1 -0
- package/{src → extensions/shared}/protocol/commands.ts +12 -1
- package/{src → extensions/shared}/protocol/index.ts +2 -0
- package/{src → extensions/shared}/protocol/notifications.ts +1 -1
- package/{src → extensions/shared}/protocol/requests.ts +1 -1
- package/extensions/shared/shortcut-hints.ts +150 -0
- package/extensions/shared/shortcuts-overlay.ts +229 -0
- package/extensions/shared/truncate.ts +189 -0
- package/package.json +4 -4
- package/src/utils/command-executor.ts +2 -1
- package/src/utils/shell-utils.ts +44 -4
- package/extensions/shared/output-payload.ts +0 -28
- package/src/get-manager.ts +0 -15
- package/src/utils/is-record.ts +0 -3
- /package/{src → extensions/shared}/protocol/logs.ts +0 -0
package/README.md
CHANGED
|
@@ -117,6 +117,46 @@ Available settings include:
|
|
|
117
117
|
- Linux: supported
|
|
118
118
|
- Windows: not supported
|
|
119
119
|
|
|
120
|
+
## Similar but different
|
|
121
|
+
|
|
122
|
+
Pi has several process, terminal, and background-task extensions. pi-processes focuses on explicit LLM-managed background processes, log inspection, watches that can wake the agent, and Pi UI surfaces for `/ps`, logs, dock, and status.
|
|
123
|
+
|
|
124
|
+
See [pi.dev/packages](https://pi.dev/packages) for the full registry of Pi extensions.
|
|
125
|
+
|
|
126
|
+
### Background command managers
|
|
127
|
+
|
|
128
|
+
These packages are closest when you want shell commands, dev servers, watchers, or logs to keep running while Pi continues the conversation.
|
|
129
|
+
|
|
130
|
+
- [pi-background-tasks](https://pi.dev/packages/pi-background-tasks): durable background shell tasks plus delegated child Pi workflows.
|
|
131
|
+
- [@99percentpeople/pi-background-tasks](https://pi.dev/packages/%4099percentpeople/pi-background-tasks): background commands and attachable PTY/TUI sessions, with SSH Remote integration.
|
|
132
|
+
- [@richardgill/pi-background-bash](https://github.com/richardgill/pi-extensions/tree/main/extensions/background-bash): replaces `bash` with session-owned local process groups and adds `bash_process` for list, peek, and kill.
|
|
133
|
+
- [pi-bash-bg](https://pi.dev/packages/pi-bash-bg): minimal `&` support for Pi's bash tool, detaching background processes and keeping output out of the context window.
|
|
134
|
+
- [pi-tian-background-terminals](https://pi.dev/packages/pi-tian-background-terminals): replaces Pi's built-in Bash with automatic background yielding, completion notifications, and a `/ps` viewer.
|
|
135
|
+
- [pi-better-background-tasks](https://pi.dev/packages/pi-better-background-tasks): durable background shell tasks, watchers, logs, and status inspection.
|
|
136
|
+
- [pi-patty-bg-tasks](https://pi.dev/packages/pi-patty-bg-tasks): Claude Code-style background tasks with auto-backgrounding, attach, file-backed output, and cooperative steering.
|
|
137
|
+
- [@mjakl/pi-processes](https://pi.dev/packages/%40mjakl/pi-processes): stripped-down pi-processes fork with the `process` tool, a single `/ps` overlay, a compact status line, and fewer commands/settings.
|
|
138
|
+
- [@haemmid/pi-processes](https://pi.dev/packages/%40haemmid/pi-processes): pi-web-focused pi-processes fork for dev-server automation, with `ensure`, `restart`, and `wait` actions for Astro/Vite-style workflows.
|
|
139
|
+
- [pi-processes-git-bash](https://pi.dev/packages/pi-processes-git-bash): pi-processes fork for Windows users through Git Bash.
|
|
140
|
+
|
|
141
|
+
### Terminal and shell replacements
|
|
142
|
+
|
|
143
|
+
These packages are a better fit when you want a different shell substrate, PTY behavior, or platform-specific terminal support.
|
|
144
|
+
|
|
145
|
+
- [pi-unified-exec](https://pi.dev/packages/pi-unified-exec): Codex-style long-lived shell sessions with stdin, PTY, REPL, SSH, dev-server, and disk-log support.
|
|
146
|
+
- [pi-live-terminal](https://pi.dev/packages/pi-live-terminal): tmux-backed command runner with a live terminal widget.
|
|
147
|
+
- [@aliaksei-raketski/pi-tmux-bash](https://pi.dev/packages/%40aliaksei-raketski/pi-tmux-bash): runs model-facing shell commands in managed tmux windows.
|
|
148
|
+
- [@4fu/pi-pwsh](https://pi.dev/packages/%404fu/pi-pwsh): persistent PowerShell tasks for Pi on Windows, with ConPTY sessions and user requests.
|
|
149
|
+
- [pi-pwsh-notify](https://pi.dev/packages/pi-pwsh-notify): PowerShell shell with background jobs and completion/server-ready notifications.
|
|
150
|
+
|
|
151
|
+
### Monitors, schedulers, and visibility
|
|
152
|
+
|
|
153
|
+
These packages are more about watching, waking, or surfacing process state than replacing pi-processes directly.
|
|
154
|
+
|
|
155
|
+
- [pi-event-monitor](https://pi.dev/packages/pi-event-monitor): event-driven shell-stream and file watchers that wake the session on process exit, matching output, or file writes.
|
|
156
|
+
- [pi-monitor-plugin](https://pi.dev/packages/pi-monitor-plugin): background jobs, monitors, loops, schedules, and idle-aware notifications.
|
|
157
|
+
- [pi-tripwire](https://pi.dev/packages/pi-tripwire): footer visibility for agent-spawned localhost servers; not a process runner itself.
|
|
158
|
+
- [@cortexkit/aft-pi](https://pi.dev/packages/%40cortexkit/aft-pi): broader Agent File Tools package that includes background bash tasks, PTY sessions, and output compression alongside code-analysis tools.
|
|
159
|
+
|
|
120
160
|
## Troubleshooting
|
|
121
161
|
|
|
122
162
|
### Pi started something and I want to see more output
|
|
@@ -8,22 +8,44 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { EventBus } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { KillResult, ProcessInfo } from "../../src/types";
|
|
11
12
|
import {
|
|
12
13
|
CHANNELS,
|
|
13
14
|
type CommandClearPayload,
|
|
14
15
|
type CommandKillPayload,
|
|
15
16
|
type CommandPinPayload,
|
|
16
17
|
type CommandPinResult,
|
|
18
|
+
type CommandStartPayload,
|
|
19
|
+
type CommandStartResult,
|
|
17
20
|
type ProcessProtocolConfig,
|
|
18
21
|
type RequestCombinedOutputPayload,
|
|
19
22
|
type RequestConfigPayload,
|
|
20
23
|
type RequestGetPayload,
|
|
21
24
|
type RequestListPayload,
|
|
22
|
-
} from "
|
|
23
|
-
import type { KillResult, ProcessInfo } from "../../src/types";
|
|
25
|
+
} from "../shared/protocol";
|
|
24
26
|
|
|
25
27
|
export type ProcessLogLine = { type: "stdout" | "stderr"; text: string };
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Start a managed process via the core extension's protocol channel. Resolves
|
|
31
|
+
* when the core handler replies. The started process is fully visible to the
|
|
32
|
+
* agent via the `process` tool (list, output, stop, etc.).
|
|
33
|
+
*/
|
|
34
|
+
export function requestStart(
|
|
35
|
+
events: EventBus,
|
|
36
|
+
options: { name: string; command: string; cwd?: string },
|
|
37
|
+
): Promise<CommandStartResult> {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const payload: CommandStartPayload = {
|
|
40
|
+
name: options.name,
|
|
41
|
+
command: options.command,
|
|
42
|
+
cwd: options.cwd,
|
|
43
|
+
reply: (result) => resolve(result),
|
|
44
|
+
};
|
|
45
|
+
events.emit(CHANNELS.COMMAND_START, payload);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
export function requestProcessList(events: EventBus): ProcessInfo[] {
|
|
28
50
|
let processes: ProcessInfo[] = [];
|
|
29
51
|
const payload: RequestListPayload = {
|
|
@@ -4,9 +4,9 @@ import type {
|
|
|
4
4
|
ExtensionCommandContext,
|
|
5
5
|
Theme,
|
|
6
6
|
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import type { ProcessProtocolConfig } from "../../../src/protocol";
|
|
8
7
|
import type { ProcessInfo } from "../../../src/types";
|
|
9
8
|
import { sanitizeForDisplay } from "../../shared/display-text";
|
|
9
|
+
import type { ProcessProtocolConfig } from "../../shared/protocol";
|
|
10
10
|
import { requestConfig, requestProcess, requestProcessList } from "../client";
|
|
11
11
|
import { OverviewComponent } from "../components/overview-component";
|
|
12
12
|
|
|
@@ -4,11 +4,10 @@ import {
|
|
|
4
4
|
type Component,
|
|
5
5
|
Input,
|
|
6
6
|
Key,
|
|
7
|
-
|
|
7
|
+
parseKey,
|
|
8
8
|
type TUI,
|
|
9
9
|
visibleWidth,
|
|
10
10
|
} from "@earendil-works/pi-tui";
|
|
11
|
-
import { CHANNELS, type ProcessProtocolConfig } from "../../../src/protocol";
|
|
12
11
|
import { LIVE_STATUSES, type ProcessInfo } from "../../../src/types";
|
|
13
12
|
import { formatRuntime } from "../../../src/utils/format";
|
|
14
13
|
import {
|
|
@@ -17,7 +16,20 @@ import {
|
|
|
17
16
|
} from "../../shared/display-text";
|
|
18
17
|
import { buildDroppedOutputLine, trimToBudget } from "../../shared/line-buffer";
|
|
19
18
|
import { renderLogLine } from "../../shared/log-line";
|
|
20
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
CHANNELS,
|
|
21
|
+
type ProcessesOutputChangedPayload,
|
|
22
|
+
type ProcessProtocolConfig,
|
|
23
|
+
} from "../../shared/protocol";
|
|
24
|
+
import {
|
|
25
|
+
SHORTCUTS_KEY,
|
|
26
|
+
type ShortcutHint,
|
|
27
|
+
ShortcutHintsComponent,
|
|
28
|
+
} from "../../shared/shortcut-hints";
|
|
29
|
+
import {
|
|
30
|
+
type ShortcutGroup,
|
|
31
|
+
showShortcutsOverlay,
|
|
32
|
+
} from "../../shared/shortcuts-overlay";
|
|
21
33
|
import { truncateToWidth } from "../../shared/truncate";
|
|
22
34
|
import { LineComponent, LinesComponent, statusColor } from "../../shared/ui";
|
|
23
35
|
import {
|
|
@@ -89,6 +101,8 @@ export class OverviewComponent implements Component {
|
|
|
89
101
|
private runningProcessCount = 0;
|
|
90
102
|
private readonly disposers: Array<() => void> = [];
|
|
91
103
|
private disposed = false;
|
|
104
|
+
/** Disposer for the "?" shortcuts overlay, when open. */
|
|
105
|
+
private shortcutsHelp: (() => void) | null = null;
|
|
92
106
|
|
|
93
107
|
constructor(private readonly opts: OverviewOptions) {
|
|
94
108
|
this.configureFilterInput();
|
|
@@ -120,6 +134,34 @@ export class OverviewComponent implements Component {
|
|
|
120
134
|
return panel.render(width);
|
|
121
135
|
}
|
|
122
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Key dispatch for normal mode, keyed by parsed key id. Close keys are
|
|
139
|
+
* handled in `handleInput` before the table is consulted.
|
|
140
|
+
*/
|
|
141
|
+
private readonly keyActions: Record<string, () => void> = {
|
|
142
|
+
[Key.down]: () => this.moveSelection(1),
|
|
143
|
+
[Key.up]: () => this.moveSelection(-1),
|
|
144
|
+
[Key.enter]: () => void this.pinSelected(),
|
|
145
|
+
j: () => this.moveSelection(1),
|
|
146
|
+
k: () => this.moveSelection(-1),
|
|
147
|
+
J: () => this.scrollPreview(1),
|
|
148
|
+
K: () => this.scrollPreview(-1),
|
|
149
|
+
g: () => {
|
|
150
|
+
this.previewOffset = 0;
|
|
151
|
+
this.requestRender();
|
|
152
|
+
},
|
|
153
|
+
G: () => {
|
|
154
|
+
this.previewOffset = Math.max(0, this.previewLines.length - 1);
|
|
155
|
+
this.requestRender();
|
|
156
|
+
},
|
|
157
|
+
x: () => this.killSelected(),
|
|
158
|
+
c: () => this.clearFinished(),
|
|
159
|
+
s: () => this.cycleSort(),
|
|
160
|
+
f: () => this.cycleFilter(),
|
|
161
|
+
"/": () => this.startQuickFilter(),
|
|
162
|
+
[SHORTCUTS_KEY]: () => this.openShortcutsHelp(),
|
|
163
|
+
};
|
|
164
|
+
|
|
123
165
|
handleInput(data: string): void {
|
|
124
166
|
if (this.mode === "filter-typing") {
|
|
125
167
|
this.filterInput.handleInput?.(data);
|
|
@@ -127,30 +169,13 @@ export class OverviewComponent implements Component {
|
|
|
127
169
|
return;
|
|
128
170
|
}
|
|
129
171
|
|
|
130
|
-
|
|
172
|
+
const key = parseKey(data);
|
|
173
|
+
if (key === "escape" || key === "ctrl+c" || key === "q" || key === "Q") {
|
|
131
174
|
this.close();
|
|
132
175
|
return;
|
|
133
176
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (matchesKey(data, Key.down) || data === "j") this.moveSelection(1);
|
|
139
|
-
else if (matchesKey(data, Key.up) || data === "k") this.moveSelection(-1);
|
|
140
|
-
else if (data === "J") this.scrollPreview(1);
|
|
141
|
-
else if (data === "K") this.scrollPreview(-1);
|
|
142
|
-
else if (data === "g") {
|
|
143
|
-
this.previewOffset = 0;
|
|
144
|
-
this.requestRender();
|
|
145
|
-
} else if (data === "G") {
|
|
146
|
-
this.previewOffset = Math.max(0, this.previewLines.length - 1);
|
|
147
|
-
this.requestRender();
|
|
148
|
-
} else if (data === "x") this.killSelected();
|
|
149
|
-
else if (data === "c") this.clearFinished();
|
|
150
|
-
else if (data === "s") this.cycleSort();
|
|
151
|
-
else if (data === "f") this.cycleFilter();
|
|
152
|
-
else if (data === "/") this.startQuickFilter();
|
|
153
|
-
else if (matchesKey(data, Key.enter)) void this.pinSelected();
|
|
177
|
+
|
|
178
|
+
this.keyActions[key ?? ""]?.();
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
invalidate(): void {}
|
|
@@ -158,6 +183,8 @@ export class OverviewComponent implements Component {
|
|
|
158
183
|
dispose(): void {
|
|
159
184
|
if (this.disposed) return;
|
|
160
185
|
this.disposed = true;
|
|
186
|
+
this.shortcutsHelp?.();
|
|
187
|
+
this.shortcutsHelp = null;
|
|
161
188
|
for (const dispose of this.disposers.splice(0)) dispose();
|
|
162
189
|
}
|
|
163
190
|
|
|
@@ -195,8 +222,8 @@ export class OverviewComponent implements Component {
|
|
|
195
222
|
this.requestRender();
|
|
196
223
|
}
|
|
197
224
|
|
|
198
|
-
private handleOutputChanged(
|
|
199
|
-
|
|
225
|
+
private handleOutputChanged(rawPayload: unknown): void {
|
|
226
|
+
const payload = rawPayload as ProcessesOutputChangedPayload;
|
|
200
227
|
const selected = this.selectedProcess();
|
|
201
228
|
if (!selected || selected.id !== payload.id) return;
|
|
202
229
|
const appended = [
|
|
@@ -447,6 +474,19 @@ export class OverviewComponent implements Component {
|
|
|
447
474
|
this.opts.onClose();
|
|
448
475
|
}
|
|
449
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Open the "?" shortcuts overlay on top of the overview. While open it
|
|
479
|
+
* captures input; closing it restores focus here. Disposed with the
|
|
480
|
+
* overview so a background close cannot leave it behind.
|
|
481
|
+
*/
|
|
482
|
+
private openShortcutsHelp(): void {
|
|
483
|
+
if (this.shortcutsHelp) return;
|
|
484
|
+
this.shortcutsHelp = showShortcutsOverlay(this.opts.tui, {
|
|
485
|
+
theme: this.opts.theme,
|
|
486
|
+
groups: this.shortcutGroups(),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
450
490
|
private requestRender(): void {
|
|
451
491
|
this.opts.tui.requestRender();
|
|
452
492
|
}
|
|
@@ -658,35 +698,65 @@ export class OverviewComponent implements Component {
|
|
|
658
698
|
}
|
|
659
699
|
|
|
660
700
|
private buildFooter(): Component {
|
|
661
|
-
return new LineComponent((width) => this.renderFooterLine(width));
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
private renderFooterLine(width: number): string {
|
|
665
|
-
const t = this.opts.theme;
|
|
666
|
-
const dim = (value: string) => t.fg("dim", value);
|
|
667
|
-
|
|
668
701
|
if (this.mode === "filter-typing") {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
702
|
+
return new LineComponent((width) => {
|
|
703
|
+
const dim = (value: string) => this.opts.theme.fg("dim", value);
|
|
704
|
+
const rendered = this.filterInput.render(60)[0] ?? "";
|
|
705
|
+
return truncateToWidth(
|
|
706
|
+
`${dim("/")}${rendered} ${dim("enter")} apply ${dim("esc")} cancel`,
|
|
707
|
+
width,
|
|
708
|
+
"",
|
|
709
|
+
true,
|
|
710
|
+
);
|
|
711
|
+
});
|
|
676
712
|
}
|
|
713
|
+
return new ShortcutHintsComponent(
|
|
714
|
+
() => this.overviewHints(),
|
|
715
|
+
this.opts.theme,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** Footer hint list for the overview. */
|
|
720
|
+
private overviewHints(): ShortcutHint[] {
|
|
721
|
+
return [
|
|
722
|
+
{ key: "j/k", label: "move" },
|
|
723
|
+
{ key: "J/K", label: "scroll" },
|
|
724
|
+
{ key: "enter", label: this.renderPinHint() },
|
|
725
|
+
{ key: "x", label: "kill" },
|
|
726
|
+
{ key: "c", label: "clear" },
|
|
727
|
+
{ key: "s", label: "sort" },
|
|
728
|
+
{ key: "f", label: "filter" },
|
|
729
|
+
{ key: "/", label: "find" },
|
|
730
|
+
{ key: "g/G", label: "top/bot" },
|
|
731
|
+
{ key: "q", label: "close" },
|
|
732
|
+
];
|
|
733
|
+
}
|
|
677
734
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
735
|
+
/** Groups for the "?" shortcuts overlay. */
|
|
736
|
+
private shortcutGroups(): ShortcutGroup[] {
|
|
737
|
+
return [
|
|
738
|
+
{
|
|
739
|
+
title: "list",
|
|
740
|
+
rows: [
|
|
741
|
+
{ keys: "j / k", description: "move selection" },
|
|
742
|
+
{ keys: "g / G", description: "preview top / bottom" },
|
|
743
|
+
{ keys: "enter", description: this.renderPinHint() },
|
|
744
|
+
{ keys: "x", description: "kill" },
|
|
745
|
+
{ keys: "c", description: "clear finished" },
|
|
746
|
+
{ keys: "s", description: "sort" },
|
|
747
|
+
{ keys: "f", description: "filter" },
|
|
748
|
+
{ keys: "/", description: "find by name" },
|
|
749
|
+
],
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
title: "preview",
|
|
753
|
+
rows: [{ keys: "J / K", description: "scroll output" }],
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
title: "general",
|
|
757
|
+
rows: [{ keys: "q", description: "close" }],
|
|
758
|
+
},
|
|
688
759
|
];
|
|
689
|
-
return truncateToWidth(keys.join(" "), width, "", true);
|
|
690
760
|
}
|
|
691
761
|
}
|
|
692
762
|
|
|
@@ -73,10 +73,6 @@ export interface ConfigV0100 {
|
|
|
73
73
|
* - widget.showStatusWidget is preserved (still controls the status widget).
|
|
74
74
|
*/
|
|
75
75
|
|
|
76
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
77
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
76
|
function setIfDefined<T extends object, K extends keyof T>(
|
|
81
77
|
target: T,
|
|
82
78
|
key: K,
|
|
@@ -120,10 +116,11 @@ export function needsConfigV094ToV0100Migration(
|
|
|
120
116
|
return false;
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
// Widen to string | undefined: the incoming config predates the migration,
|
|
120
|
+
// so its dockDefaultState can be "hidden" even though the static type says
|
|
121
|
+
// it cannot.
|
|
122
|
+
const dockDefaultState: string | undefined = config.widget?.dockDefaultState;
|
|
123
|
+
return dockDefaultState === "hidden";
|
|
127
124
|
}
|
|
128
125
|
|
|
129
126
|
export function migrateConfigV094ToV0100(config: ConfigV094): ConfigV0100 {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Runtime config uses the shared protocol shape returned by REQUEST_CONFIG.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ProcessProtocolConfig } from "
|
|
8
|
+
import type { ProcessProtocolConfig } from "../../shared/protocol";
|
|
9
9
|
|
|
10
10
|
export interface ExecutionConfig {
|
|
11
11
|
/** Absolute shell executable used to run process commands. */
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { EventBus } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import type { ProcessManager } from "../../../src/manager";
|
|
4
|
+
import type { KillResult } from "../../../src/types";
|
|
4
5
|
import {
|
|
5
6
|
CHANNELS,
|
|
6
7
|
type CommandClearPayload,
|
|
7
8
|
type CommandKillPayload,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { isRecord } from "../../../src/utils/is-record";
|
|
9
|
+
type CommandStartPayload,
|
|
10
|
+
} from "../../shared/protocol";
|
|
11
11
|
import type { NotificationRegistry } from "../notifications/registry";
|
|
12
12
|
import { killIntentionally } from "./kill-process";
|
|
13
13
|
|
|
@@ -17,9 +17,26 @@ export function registerCommandHandlers(
|
|
|
17
17
|
notifications: NotificationRegistry,
|
|
18
18
|
): () => void {
|
|
19
19
|
const disposers = [
|
|
20
|
+
events.on(CHANNELS.COMMAND_START, (payload) => {
|
|
21
|
+
const command = payload as CommandStartPayload;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const info = manager.start(
|
|
25
|
+
command.name,
|
|
26
|
+
command.command,
|
|
27
|
+
command.cwd ?? process.cwd(),
|
|
28
|
+
);
|
|
29
|
+
notifications.register(info.id, {});
|
|
30
|
+
safeReply(command.reply, { ok: true, process: info });
|
|
31
|
+
} catch (err) {
|
|
32
|
+
safeReply(command.reply, {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: err instanceof Error ? err.message : String(err),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}),
|
|
20
38
|
events.on(CHANNELS.COMMAND_KILL, (payload) => {
|
|
21
39
|
const command = payload as CommandKillPayload;
|
|
22
|
-
if (!isCommandKillPayload(command)) return;
|
|
23
40
|
|
|
24
41
|
void killIntentionally(manager, notifications, command.id, {
|
|
25
42
|
signal: command.signal,
|
|
@@ -31,7 +48,6 @@ export function registerCommandHandlers(
|
|
|
31
48
|
}),
|
|
32
49
|
events.on(CHANNELS.COMMAND_CLEAR, (payload) => {
|
|
33
50
|
const command = payload as CommandClearPayload;
|
|
34
|
-
if (!isCommandClearPayload(command)) return;
|
|
35
51
|
|
|
36
52
|
safeReply(command.reply, manager.clearFinished());
|
|
37
53
|
}),
|
|
@@ -42,42 +58,6 @@ export function registerCommandHandlers(
|
|
|
42
58
|
};
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
function isCommandKillPayload(
|
|
46
|
-
payload: CommandKillPayload,
|
|
47
|
-
): payload is CommandKillPayload {
|
|
48
|
-
return (
|
|
49
|
-
isRecord(payload) &&
|
|
50
|
-
typeof payload.id === "string" &&
|
|
51
|
-
isOptionalSignal(payload.signal) &&
|
|
52
|
-
isOptionalNumber(payload.timeoutMs) &&
|
|
53
|
-
isReply(payload)
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isCommandClearPayload(
|
|
58
|
-
payload: CommandClearPayload,
|
|
59
|
-
): payload is CommandClearPayload {
|
|
60
|
-
return isRecord(payload) && isReply(payload);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function isReply(
|
|
64
|
-
payload: unknown,
|
|
65
|
-
): payload is { reply: (...args: never[]) => void } {
|
|
66
|
-
return isRecord(payload) && typeof payload.reply === "function";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isOptionalSignal(
|
|
70
|
-
signal: unknown,
|
|
71
|
-
): signal is NodeJS.Signals | undefined {
|
|
72
|
-
return signal === undefined || typeof signal === "string";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function isOptionalNumber(value: unknown): value is number | undefined {
|
|
76
|
-
return (
|
|
77
|
-
value === undefined || (typeof value === "number" && Number.isFinite(value))
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
61
|
function safeReply<T>(reply: (result: T) => void, result: T): void {
|
|
82
62
|
try {
|
|
83
63
|
reply(result);
|
|
@@ -3,8 +3,7 @@ import type { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import {
|
|
4
4
|
CHANNELS,
|
|
5
5
|
type ProcessProtocolNotificationPayload,
|
|
6
|
-
} from "
|
|
7
|
-
import { isRecord } from "../../../src/utils/is-record";
|
|
6
|
+
} from "../../shared/protocol";
|
|
8
7
|
import {
|
|
9
8
|
attentionToSendOptions,
|
|
10
9
|
sendProcessNotificationMessage,
|
|
@@ -83,8 +82,8 @@ export function registerNotificationDelivery(
|
|
|
83
82
|
|
|
84
83
|
const disposeListener = events.on(
|
|
85
84
|
CHANNELS.NOTIFICATION,
|
|
86
|
-
(
|
|
87
|
-
|
|
85
|
+
(rawPayload: unknown) => {
|
|
86
|
+
const payload = rawPayload as ProcessProtocolNotificationPayload;
|
|
88
87
|
|
|
89
88
|
if (payload.kind === "log_match") {
|
|
90
89
|
const now = performance.now();
|
|
@@ -113,36 +112,3 @@ export function registerNotificationDelivery(
|
|
|
113
112
|
resetWindow();
|
|
114
113
|
};
|
|
115
114
|
}
|
|
116
|
-
|
|
117
|
-
function isNotificationPayload(
|
|
118
|
-
payload: unknown,
|
|
119
|
-
): payload is ProcessProtocolNotificationPayload {
|
|
120
|
-
if (!isRecord(payload)) return false;
|
|
121
|
-
if (!isNotificationKind(payload.kind)) return false;
|
|
122
|
-
if (typeof payload.processId !== "string") return false;
|
|
123
|
-
if (typeof payload.processName !== "string") return false;
|
|
124
|
-
if (typeof payload.command !== "string") return false;
|
|
125
|
-
if (typeof payload.timestamp !== "number") return false;
|
|
126
|
-
if (typeof payload.summary !== "string") return false;
|
|
127
|
-
if (
|
|
128
|
-
payload.attention !== "turn" &&
|
|
129
|
-
payload.attention !== "context" &&
|
|
130
|
-
payload.attention !== "ignore"
|
|
131
|
-
) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function isNotificationKind(
|
|
138
|
-
value: unknown,
|
|
139
|
-
): value is ProcessProtocolNotificationPayload["kind"] {
|
|
140
|
-
return (
|
|
141
|
-
value === "success" ||
|
|
142
|
-
value === "failure" ||
|
|
143
|
-
value === "crash" ||
|
|
144
|
-
value === "killed" ||
|
|
145
|
-
value === "log_match" ||
|
|
146
|
-
value === "log_match_suppressed"
|
|
147
|
-
);
|
|
148
|
-
}
|
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
type RequestListPayload,
|
|
10
10
|
type RequestLogFilesPayload,
|
|
11
11
|
type RequestOutputPayload,
|
|
12
|
-
} from "
|
|
13
|
-
import { isRecord } from "../../../src/utils/is-record";
|
|
12
|
+
} from "../../shared/protocol";
|
|
14
13
|
import type { ProcessProtocolConfig } from "../config";
|
|
15
14
|
|
|
16
15
|
export function registerRequestHandlers(
|
|
@@ -21,43 +20,36 @@ export function registerRequestHandlers(
|
|
|
21
20
|
const disposers = [
|
|
22
21
|
events.on(CHANNELS.REQUEST_LIST, (payload) => {
|
|
23
22
|
const request = payload as RequestListPayload;
|
|
24
|
-
if (!isRequestListPayload(request)) return;
|
|
25
23
|
|
|
26
24
|
request.reply(manager.list());
|
|
27
25
|
}),
|
|
28
26
|
events.on(CHANNELS.REQUEST_GET, (payload) => {
|
|
29
27
|
const request = payload as RequestGetPayload;
|
|
30
|
-
if (!isIdRequest(request)) return;
|
|
31
28
|
|
|
32
29
|
request.reply(manager.get(request.id));
|
|
33
30
|
}),
|
|
34
31
|
events.on(CHANNELS.REQUEST_OUTPUT, (payload) => {
|
|
35
32
|
const request = payload as RequestOutputPayload;
|
|
36
|
-
if (!isIdRequest(request)) return;
|
|
37
33
|
|
|
38
34
|
request.reply(manager.getOutput(request.id, request.tailLines));
|
|
39
35
|
}),
|
|
40
36
|
events.on(CHANNELS.REQUEST_COMBINED_OUTPUT, (payload) => {
|
|
41
37
|
const request = payload as RequestCombinedOutputPayload;
|
|
42
|
-
if (!isIdRequest(request)) return;
|
|
43
38
|
|
|
44
39
|
request.reply(manager.getCombinedOutput(request.id, request.tailLines));
|
|
45
40
|
}),
|
|
46
41
|
events.on(CHANNELS.REQUEST_LOG_FILES, (payload) => {
|
|
47
42
|
const request = payload as RequestLogFilesPayload;
|
|
48
|
-
if (!isIdRequest(request)) return;
|
|
49
43
|
|
|
50
44
|
request.reply(manager.getLogFiles(request.id));
|
|
51
45
|
}),
|
|
52
46
|
events.on(CHANNELS.REQUEST_FILE_SIZE, (payload) => {
|
|
53
47
|
const request = payload as RequestFileSizePayload;
|
|
54
|
-
if (!isIdRequest(request)) return;
|
|
55
48
|
|
|
56
49
|
request.reply(manager.getFileSize(request.id));
|
|
57
50
|
}),
|
|
58
51
|
events.on(CHANNELS.REQUEST_CONFIG, (payload) => {
|
|
59
52
|
const request = payload as RequestConfigPayload;
|
|
60
|
-
if (!isRequestConfigPayload(request)) return;
|
|
61
53
|
|
|
62
54
|
request.reply(getConfig());
|
|
63
55
|
}),
|
|
@@ -67,48 +59,3 @@ export function registerRequestHandlers(
|
|
|
67
59
|
for (const dispose of disposers) dispose();
|
|
68
60
|
};
|
|
69
61
|
}
|
|
70
|
-
|
|
71
|
-
function isRequestListPayload(
|
|
72
|
-
payload: RequestListPayload,
|
|
73
|
-
): payload is RequestListPayload {
|
|
74
|
-
return isRecord(payload) && isReply(payload);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isRequestConfigPayload(
|
|
78
|
-
payload: RequestConfigPayload,
|
|
79
|
-
): payload is RequestConfigPayload {
|
|
80
|
-
return isRecord(payload) && isReply(payload);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isIdRequest(
|
|
84
|
-
payload:
|
|
85
|
-
| RequestGetPayload
|
|
86
|
-
| RequestOutputPayload
|
|
87
|
-
| RequestCombinedOutputPayload
|
|
88
|
-
| RequestLogFilesPayload
|
|
89
|
-
| RequestFileSizePayload,
|
|
90
|
-
): payload is
|
|
91
|
-
| RequestGetPayload
|
|
92
|
-
| RequestOutputPayload
|
|
93
|
-
| RequestCombinedOutputPayload
|
|
94
|
-
| RequestLogFilesPayload
|
|
95
|
-
| RequestFileSizePayload {
|
|
96
|
-
return (
|
|
97
|
-
isRecord(payload) &&
|
|
98
|
-
typeof payload.id === "string" &&
|
|
99
|
-
isOptionalNumber(payload.tailLines) &&
|
|
100
|
-
isReply(payload)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function isReply(
|
|
105
|
-
payload: unknown,
|
|
106
|
-
): payload is { reply: (...args: never[]) => void } {
|
|
107
|
-
return isRecord(payload) && typeof payload.reply === "function";
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function isOptionalNumber(value: unknown): value is number | undefined {
|
|
111
|
-
return (
|
|
112
|
-
value === undefined || (typeof value === "number" && Number.isFinite(value))
|
|
113
|
-
);
|
|
114
|
-
}
|