@aliou/pi-processes 0.9.5 → 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 +34 -9
- 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 -119
- 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 -155
- 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-truncate.ts +0 -187
- package/src/tools/actions/output.test.ts +0 -392
- package/src/tools/actions/output.ts +0 -447
- 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
package/src/config.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration for the processes extension.
|
|
3
|
-
*
|
|
4
|
-
* Global: ~/.pi/agent/extensions/process.json
|
|
5
|
-
* Memory: ephemeral overrides via /ps:settings
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { ConfigLoader } from "@aliou/pi-utils-settings";
|
|
9
|
-
import type { ProcessesKeybindings } from "./utils/keybindings";
|
|
10
|
-
import { DEFAULT_KEYBINDINGS } from "./utils/keybindings";
|
|
11
|
-
|
|
12
|
-
export interface ProcessesConfig {
|
|
13
|
-
processList?: {
|
|
14
|
-
/** Max visible processes in the /ps TUI list. */
|
|
15
|
-
maxVisibleProcesses?: number;
|
|
16
|
-
/** Max log preview lines shown below the selected process. */
|
|
17
|
-
maxPreviewLines?: number;
|
|
18
|
-
};
|
|
19
|
-
output?: {
|
|
20
|
-
/** Default number of tail lines returned to the agent. */
|
|
21
|
-
defaultTailLines?: number;
|
|
22
|
-
/** Hard cap on output lines returned to the agent. */
|
|
23
|
-
maxOutputLines?: number;
|
|
24
|
-
};
|
|
25
|
-
execution?: {
|
|
26
|
-
/** Absolute shell path override. Leave unset to auto-resolve. */
|
|
27
|
-
shellPath?: string;
|
|
28
|
-
};
|
|
29
|
-
widget?: {
|
|
30
|
-
/** Show the status widget below the editor. */
|
|
31
|
-
showStatusWidget?: boolean;
|
|
32
|
-
/** Default dock state when follow mode is enabled. */
|
|
33
|
-
dockDefaultState?: "hidden" | "collapsed";
|
|
34
|
-
/** Height of the dock in lines when open. */
|
|
35
|
-
dockHeight?: number;
|
|
36
|
-
};
|
|
37
|
-
follow?: {
|
|
38
|
-
/** Enable follow mode by default when starting processes. */
|
|
39
|
-
enabledByDefault?: boolean;
|
|
40
|
-
/** Auto-hide dock when all processes finish. */
|
|
41
|
-
autoHideOnFinish?: boolean;
|
|
42
|
-
};
|
|
43
|
-
keybindings?: Partial<ProcessesKeybindings>;
|
|
44
|
-
interception?: {
|
|
45
|
-
/** Block background bash commands (&, nohup, disown, setsid) and guide the model to use the process tool. */
|
|
46
|
-
blockBackgroundCommands?: boolean;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface ResolvedProcessesConfig {
|
|
51
|
-
processList: {
|
|
52
|
-
maxVisibleProcesses: number;
|
|
53
|
-
maxPreviewLines: number;
|
|
54
|
-
};
|
|
55
|
-
output: {
|
|
56
|
-
defaultTailLines: number;
|
|
57
|
-
maxOutputLines: number;
|
|
58
|
-
};
|
|
59
|
-
execution: {
|
|
60
|
-
shellPath?: string;
|
|
61
|
-
};
|
|
62
|
-
widget: {
|
|
63
|
-
showStatusWidget: boolean;
|
|
64
|
-
dockDefaultState: "hidden" | "collapsed";
|
|
65
|
-
dockHeight: number;
|
|
66
|
-
};
|
|
67
|
-
follow: {
|
|
68
|
-
enabledByDefault: boolean;
|
|
69
|
-
autoHideOnFinish: boolean;
|
|
70
|
-
};
|
|
71
|
-
keybindings: ProcessesKeybindings;
|
|
72
|
-
interception: {
|
|
73
|
-
blockBackgroundCommands: boolean;
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const DEFAULT_CONFIG: ResolvedProcessesConfig = {
|
|
78
|
-
processList: {
|
|
79
|
-
maxVisibleProcesses: 8,
|
|
80
|
-
maxPreviewLines: 12,
|
|
81
|
-
},
|
|
82
|
-
output: {
|
|
83
|
-
defaultTailLines: 100,
|
|
84
|
-
maxOutputLines: 200,
|
|
85
|
-
},
|
|
86
|
-
execution: {},
|
|
87
|
-
widget: {
|
|
88
|
-
showStatusWidget: false,
|
|
89
|
-
dockDefaultState: "collapsed",
|
|
90
|
-
dockHeight: 12,
|
|
91
|
-
},
|
|
92
|
-
follow: {
|
|
93
|
-
enabledByDefault: true,
|
|
94
|
-
autoHideOnFinish: true,
|
|
95
|
-
},
|
|
96
|
-
keybindings: DEFAULT_KEYBINDINGS,
|
|
97
|
-
interception: {
|
|
98
|
-
blockBackgroundCommands: false,
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export const configLoader = new ConfigLoader<
|
|
103
|
-
ProcessesConfig,
|
|
104
|
-
ResolvedProcessesConfig
|
|
105
|
-
>("process", DEFAULT_CONFIG, {
|
|
106
|
-
scopes: ["global", "memory"],
|
|
107
|
-
});
|
package/src/constants/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
ExecuteResult,
|
|
3
|
-
KillResult,
|
|
4
|
-
LogWatch,
|
|
5
|
-
LogWatchMatchEvent,
|
|
6
|
-
LogWatchStream,
|
|
7
|
-
ManagerEvent,
|
|
8
|
-
ProcessAction,
|
|
9
|
-
ProcessesDetails,
|
|
10
|
-
ProcessInfo,
|
|
11
|
-
ProcessStatus,
|
|
12
|
-
StartOptions,
|
|
13
|
-
WriteResult,
|
|
14
|
-
} from "./types";
|
|
15
|
-
|
|
16
|
-
export { LIVE_STATUSES, MESSAGE_TYPE_PROCESS_UPDATE } from "./types";
|
package/src/constants/types.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// Custom message type for process update notifications
|
|
2
|
-
export const MESSAGE_TYPE_PROCESS_UPDATE = "ad-process:update";
|
|
3
|
-
|
|
4
|
-
export type ProcessAction =
|
|
5
|
-
| "start"
|
|
6
|
-
| "list"
|
|
7
|
-
| "output"
|
|
8
|
-
| "logs"
|
|
9
|
-
| "kill"
|
|
10
|
-
| "clear"
|
|
11
|
-
| "write"
|
|
12
|
-
| "debug_preview";
|
|
13
|
-
|
|
14
|
-
export type ProcessStatus =
|
|
15
|
-
| "running"
|
|
16
|
-
| "terminating"
|
|
17
|
-
| "terminate_timeout"
|
|
18
|
-
| "exited"
|
|
19
|
-
| "killed";
|
|
20
|
-
|
|
21
|
-
export const LIVE_STATUSES: ReadonlySet<ProcessStatus> = new Set([
|
|
22
|
-
"running",
|
|
23
|
-
"terminating",
|
|
24
|
-
"terminate_timeout",
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
export type LogWatchStream = "stdout" | "stderr" | "both";
|
|
28
|
-
|
|
29
|
-
export interface LogWatch {
|
|
30
|
-
pattern: string;
|
|
31
|
-
stream?: LogWatchStream;
|
|
32
|
-
repeat?: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ProcessInfo {
|
|
36
|
-
id: string;
|
|
37
|
-
name: string;
|
|
38
|
-
pid: number; // On Unix, this is also the PGID (process group leader)
|
|
39
|
-
command: string;
|
|
40
|
-
cwd: string;
|
|
41
|
-
startTime: number;
|
|
42
|
-
endTime: number | null;
|
|
43
|
-
status: ProcessStatus;
|
|
44
|
-
exitCode: number | null;
|
|
45
|
-
success: boolean | null; // null if running, true if exit code 0, false otherwise
|
|
46
|
-
stdoutFile: string;
|
|
47
|
-
stderrFile: string;
|
|
48
|
-
alertOnSuccess: boolean;
|
|
49
|
-
alertOnFailure: boolean;
|
|
50
|
-
alertOnKill: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface LogWatchMatchEvent {
|
|
54
|
-
processId: string;
|
|
55
|
-
processName: string;
|
|
56
|
-
processCommand: string;
|
|
57
|
-
source: "stdout" | "stderr";
|
|
58
|
-
line: string;
|
|
59
|
-
watch: {
|
|
60
|
-
index: number;
|
|
61
|
-
pattern: string;
|
|
62
|
-
stream: LogWatchStream;
|
|
63
|
-
repeat: boolean;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export type ManagerEvent =
|
|
68
|
-
| { type: "process_started"; info: ProcessInfo }
|
|
69
|
-
| { type: "process_ended"; info: ProcessInfo }
|
|
70
|
-
| { type: "process_output_changed"; id: string }
|
|
71
|
-
| { type: "process_watch_matched"; match: LogWatchMatchEvent }
|
|
72
|
-
| { type: "processes_changed" };
|
|
73
|
-
|
|
74
|
-
export type KillResult =
|
|
75
|
-
| { ok: true; info: ProcessInfo }
|
|
76
|
-
| { ok: false; info: ProcessInfo; reason: "not_found" | "timeout" | "error" };
|
|
77
|
-
|
|
78
|
-
export type WriteResult =
|
|
79
|
-
| { ok: true }
|
|
80
|
-
| {
|
|
81
|
-
ok: false;
|
|
82
|
-
reason: "not_found" | "process_exited" | "stdin_closed" | "write_error";
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export interface StartOptions {
|
|
86
|
-
alertOnSuccess?: boolean;
|
|
87
|
-
alertOnFailure?: boolean;
|
|
88
|
-
alertOnKill?: boolean;
|
|
89
|
-
logWatches?: LogWatch[];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
import type { TruncationDetails } from "../tools/actions/output-truncate";
|
|
93
|
-
|
|
94
|
-
export interface ProcessesDetails {
|
|
95
|
-
action: ProcessAction;
|
|
96
|
-
success: boolean;
|
|
97
|
-
message: string;
|
|
98
|
-
process?: ProcessInfo;
|
|
99
|
-
processes?: ProcessInfo[];
|
|
100
|
-
/**
|
|
101
|
-
* Legacy-only raw output arrays retained so historical session results can
|
|
102
|
-
* still render. New executions must never populate this field; the bounded
|
|
103
|
-
* preview lives in tool-result `content` and a truncation summary lives in
|
|
104
|
-
* `truncation`.
|
|
105
|
-
*/
|
|
106
|
-
output?: { stdout: string[]; stderr: string[]; status: string };
|
|
107
|
-
logFiles?: { stdoutFile: string; stderrFile: string };
|
|
108
|
-
/**
|
|
109
|
-
* Metadata only (no raw output) describing how `content` was bounded.
|
|
110
|
-
* Present when the body exceeded the byte or line limits.
|
|
111
|
-
*/
|
|
112
|
-
truncation?: TruncationDetails;
|
|
113
|
-
cleared?: number;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export interface ExecuteResult {
|
|
117
|
-
content: Array<{ type: "text"; text: string }>;
|
|
118
|
-
details: ProcessesDetails;
|
|
119
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Blocks background bash commands (e.g. `cmd &`, `nohup cmd`) and guides
|
|
3
|
-
* the model to use the process tool instead.
|
|
4
|
-
*
|
|
5
|
-
* Opt-in via config: `interception.blockBackgroundCommands`.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { parse } from "@aliou/sh";
|
|
9
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import { walkCommands, wordToString } from "../utils/shell-utils";
|
|
11
|
-
|
|
12
|
-
const BACKGROUND_CMD_NAMES = new Set(["nohup", "disown", "setsid"]);
|
|
13
|
-
const BACKGROUND_PATTERN = /&\s*$/;
|
|
14
|
-
|
|
15
|
-
export function setupBackgroundBlocker(pi: ExtensionAPI): void {
|
|
16
|
-
pi.on("tool_call", async (event, ctx) => {
|
|
17
|
-
if (event.toolName !== "bash") return;
|
|
18
|
-
|
|
19
|
-
const command = String(event.input.command ?? "");
|
|
20
|
-
|
|
21
|
-
let hasBackground = false;
|
|
22
|
-
try {
|
|
23
|
-
const { ast } = parse(command);
|
|
24
|
-
|
|
25
|
-
// Check statement-level background flag (cmd &)
|
|
26
|
-
for (const stmt of ast.body) {
|
|
27
|
-
if (stmt.background) {
|
|
28
|
-
hasBackground = true;
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Check for nohup/disown/setsid as command names
|
|
34
|
-
if (!hasBackground) {
|
|
35
|
-
walkCommands(ast, (cmd) => {
|
|
36
|
-
const name = cmd.words?.[0] ? wordToString(cmd.words[0]) : undefined;
|
|
37
|
-
if (name && BACKGROUND_CMD_NAMES.has(name)) {
|
|
38
|
-
hasBackground = true;
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
return false;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
} catch {
|
|
45
|
-
// Fallback to regex on parse failure
|
|
46
|
-
hasBackground = BACKGROUND_PATTERN.test(command);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (hasBackground) {
|
|
50
|
-
ctx.ui?.notify(
|
|
51
|
-
"Blocked background command. Use the process tool instead.",
|
|
52
|
-
"warning",
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
block: true,
|
|
57
|
-
reason:
|
|
58
|
-
"Background commands (&, nohup, disown, setsid) are not supported in bash. " +
|
|
59
|
-
'Use the "process" tool with action "start" to run commands in the background. ' +
|
|
60
|
-
'Example: process({ action: "start", name: "my-server", command: "npm run dev" })',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return;
|
|
65
|
-
});
|
|
66
|
-
}
|
package/src/hooks/cleanup.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { ProcessManager } from "../manager";
|
|
3
|
-
|
|
4
|
-
export function setupCleanupHook(pi: ExtensionAPI, manager: ProcessManager) {
|
|
5
|
-
pi.on("session_shutdown", () => {
|
|
6
|
-
manager.stopWatcher();
|
|
7
|
-
manager.shutdownKillAll();
|
|
8
|
-
manager.cleanup();
|
|
9
|
-
});
|
|
10
|
-
}
|
package/src/hooks/index.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { ResolvedProcessesConfig } from "../config";
|
|
3
|
-
import type { ProcessManager } from "../manager";
|
|
4
|
-
import { setupBackgroundBlocker } from "./background-blocker";
|
|
5
|
-
import { setupCleanupHook } from "./cleanup";
|
|
6
|
-
import { setupMessageRenderer } from "./message-renderer";
|
|
7
|
-
import { setupProcessEndHook } from "./process-end";
|
|
8
|
-
import { setupProcessWatchHook } from "./process-watch";
|
|
9
|
-
import { type DockActions, setupProcessWidget } from "./widget";
|
|
10
|
-
|
|
11
|
-
export type { DockActions };
|
|
12
|
-
|
|
13
|
-
export function setupProcessesHooks(
|
|
14
|
-
pi: ExtensionAPI,
|
|
15
|
-
manager: ProcessManager,
|
|
16
|
-
config: ResolvedProcessesConfig,
|
|
17
|
-
): { update: () => void; dockActions: DockActions } {
|
|
18
|
-
setupCleanupHook(pi, manager);
|
|
19
|
-
setupProcessEndHook(pi, manager);
|
|
20
|
-
setupProcessWatchHook(pi, manager);
|
|
21
|
-
|
|
22
|
-
if (config.interception.blockBackgroundCommands) {
|
|
23
|
-
setupBackgroundBlocker(pi);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Set up widget AFTER process-end so it chains onto the existing callback
|
|
27
|
-
const widget = setupProcessWidget(pi, manager, config);
|
|
28
|
-
|
|
29
|
-
setupMessageRenderer(pi);
|
|
30
|
-
|
|
31
|
-
return widget;
|
|
32
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
MessageRenderOptions,
|
|
4
|
-
Theme,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
7
|
-
import { MESSAGE_TYPE_PROCESS_UPDATE } from "../constants";
|
|
8
|
-
|
|
9
|
-
interface ProcessLifecycleDetails {
|
|
10
|
-
kind?: "lifecycle";
|
|
11
|
-
processId: string;
|
|
12
|
-
processName: string;
|
|
13
|
-
command: string;
|
|
14
|
-
status: "exited" | "killed";
|
|
15
|
-
exitCode: number | null;
|
|
16
|
-
success: boolean;
|
|
17
|
-
runtime: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ProcessWatchMatchDetails {
|
|
21
|
-
kind: "watch_matched";
|
|
22
|
-
processId: string;
|
|
23
|
-
processName: string;
|
|
24
|
-
command: string;
|
|
25
|
-
source: "stdout" | "stderr";
|
|
26
|
-
line: string;
|
|
27
|
-
watch: {
|
|
28
|
-
index: number;
|
|
29
|
-
pattern: string;
|
|
30
|
-
stream: "stdout" | "stderr" | "both";
|
|
31
|
-
repeat: boolean;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface ProcessUpdateMessage {
|
|
36
|
-
customType: string;
|
|
37
|
-
content: string | Array<{ type: string; text?: string }>;
|
|
38
|
-
details?: ProcessLifecycleDetails | ProcessWatchMatchDetails;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getContentText(
|
|
42
|
-
content: string | Array<{ type: string; text?: string }>,
|
|
43
|
-
): string {
|
|
44
|
-
if (typeof content === "string") {
|
|
45
|
-
return content;
|
|
46
|
-
}
|
|
47
|
-
return content
|
|
48
|
-
.filter((c) => c.type === "text" && c.text)
|
|
49
|
-
.map((c) => c.text as string)
|
|
50
|
-
.join("");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function setupMessageRenderer(pi: ExtensionAPI) {
|
|
54
|
-
pi.registerMessageRenderer<
|
|
55
|
-
ProcessLifecycleDetails | ProcessWatchMatchDetails
|
|
56
|
-
>(
|
|
57
|
-
MESSAGE_TYPE_PROCESS_UPDATE,
|
|
58
|
-
(
|
|
59
|
-
message: ProcessUpdateMessage,
|
|
60
|
-
_options: MessageRenderOptions,
|
|
61
|
-
theme: Theme,
|
|
62
|
-
) => {
|
|
63
|
-
const details = message.details;
|
|
64
|
-
|
|
65
|
-
if (!details) {
|
|
66
|
-
return new Text(getContentText(message.content), 0, 0);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (details.kind === "watch_matched") {
|
|
70
|
-
const streamColor = details.source === "stderr" ? "warning" : "accent";
|
|
71
|
-
const text =
|
|
72
|
-
theme.fg("success", "* ") +
|
|
73
|
-
theme.fg("accent", `"${details.processName}"`) +
|
|
74
|
-
theme.fg("muted", ` (${details.processId}) `) +
|
|
75
|
-
theme.fg("success", "watch matched ") +
|
|
76
|
-
theme.fg("muted", `/${details.watch.pattern}/ `) +
|
|
77
|
-
theme.fg(streamColor, `[${details.source}]`) +
|
|
78
|
-
theme.fg("muted", ` ${details.line}`);
|
|
79
|
-
|
|
80
|
-
return new Text(text, 0, 0);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
let icon: string;
|
|
84
|
-
let color: "success" | "error" | "warning";
|
|
85
|
-
|
|
86
|
-
if (details.status === "killed") {
|
|
87
|
-
icon = "\u2717"; // x mark
|
|
88
|
-
color = "warning";
|
|
89
|
-
} else if (details.success) {
|
|
90
|
-
icon = "\u2713"; // check mark
|
|
91
|
-
color = "success";
|
|
92
|
-
} else {
|
|
93
|
-
icon = "\u2717"; // x mark
|
|
94
|
-
color = "error";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const statusText =
|
|
98
|
-
details.status === "killed"
|
|
99
|
-
? "terminated"
|
|
100
|
-
: details.success
|
|
101
|
-
? "completed"
|
|
102
|
-
: `exited(${details.exitCode ?? "?"})`;
|
|
103
|
-
|
|
104
|
-
const text =
|
|
105
|
-
theme.fg(color, `${icon} `) +
|
|
106
|
-
theme.fg("accent", `"${details.processName}"`) +
|
|
107
|
-
theme.fg("muted", ` (${details.processId})`) +
|
|
108
|
-
" " +
|
|
109
|
-
theme.fg(color, statusText) +
|
|
110
|
-
theme.fg("muted", ` ${details.runtime}`);
|
|
111
|
-
|
|
112
|
-
return new Text(text, 0, 0);
|
|
113
|
-
},
|
|
114
|
-
);
|
|
115
|
-
}
|
package/src/hooks/process-end.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { MESSAGE_TYPE_PROCESS_UPDATE, type ProcessInfo } from "../constants";
|
|
3
|
-
import type { ProcessManager } from "../manager";
|
|
4
|
-
import { formatRuntime } from "../utils";
|
|
5
|
-
import { safeSendMessage } from "./utils";
|
|
6
|
-
|
|
7
|
-
interface ProcessUpdateDetails {
|
|
8
|
-
kind: "lifecycle";
|
|
9
|
-
processId: string;
|
|
10
|
-
processName: string;
|
|
11
|
-
command: string;
|
|
12
|
-
status: "exited" | "killed";
|
|
13
|
-
exitCode: number | null;
|
|
14
|
-
success: boolean;
|
|
15
|
-
runtime: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function setupProcessEndHook(pi: ExtensionAPI, manager: ProcessManager) {
|
|
19
|
-
manager.onEvent((event) => {
|
|
20
|
-
if (event.type !== "process_ended") return;
|
|
21
|
-
|
|
22
|
-
const info: ProcessInfo = event.info;
|
|
23
|
-
|
|
24
|
-
// Determine if the agent should get a turn to react to this process ending.
|
|
25
|
-
// When true, the agent receives the message in its context and can respond
|
|
26
|
-
// (e.g. check results, fix code, restart the process).
|
|
27
|
-
const triggerAgentTurn =
|
|
28
|
-
(info.status === "killed" && info.alertOnKill) ||
|
|
29
|
-
(info.status === "exited" && info.success && info.alertOnSuccess) ||
|
|
30
|
-
(info.status === "exited" && !info.success && info.alertOnFailure);
|
|
31
|
-
|
|
32
|
-
const runtime = formatRuntime(info.startTime, info.endTime);
|
|
33
|
-
|
|
34
|
-
// Build message
|
|
35
|
-
let message: string;
|
|
36
|
-
|
|
37
|
-
if (info.status === "killed") {
|
|
38
|
-
message = `Process '${info.name}' was terminated (${runtime})`;
|
|
39
|
-
} else if (info.success) {
|
|
40
|
-
message = `Process '${info.name}' completed successfully (${runtime})`;
|
|
41
|
-
} else {
|
|
42
|
-
message = `Process '${info.name}' crashed with exit code ${info.exitCode ?? "?"} (${runtime})`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Send the message to the conversation - displayed via custom renderer in UI
|
|
46
|
-
// Only trigger an agent turn when the notification preferences say so.
|
|
47
|
-
const details: ProcessUpdateDetails = {
|
|
48
|
-
kind: "lifecycle",
|
|
49
|
-
processId: info.id,
|
|
50
|
-
processName: info.name,
|
|
51
|
-
command: info.command,
|
|
52
|
-
status: info.status as "exited" | "killed",
|
|
53
|
-
exitCode: info.exitCode,
|
|
54
|
-
success: info.success ?? false,
|
|
55
|
-
runtime,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
safeSendMessage(
|
|
59
|
-
pi,
|
|
60
|
-
{
|
|
61
|
-
customType: MESSAGE_TYPE_PROCESS_UPDATE,
|
|
62
|
-
content: message,
|
|
63
|
-
display: true,
|
|
64
|
-
details,
|
|
65
|
-
},
|
|
66
|
-
{ triggerTurn: triggerAgentTurn },
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { MESSAGE_TYPE_PROCESS_UPDATE } from "../constants";
|
|
3
|
-
import type { ProcessManager } from "../manager";
|
|
4
|
-
import { safeSendMessage } from "./utils";
|
|
5
|
-
|
|
6
|
-
interface ProcessWatchUpdateDetails {
|
|
7
|
-
kind: "watch_matched";
|
|
8
|
-
processId: string;
|
|
9
|
-
processName: string;
|
|
10
|
-
command: string;
|
|
11
|
-
source: "stdout" | "stderr";
|
|
12
|
-
line: string;
|
|
13
|
-
watch: {
|
|
14
|
-
index: number;
|
|
15
|
-
pattern: string;
|
|
16
|
-
stream: "stdout" | "stderr" | "both";
|
|
17
|
-
repeat: boolean;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const REPEAT_WATCH_TURN_COOLDOWN_MS = 5000;
|
|
22
|
-
|
|
23
|
-
export function setupProcessWatchHook(
|
|
24
|
-
pi: ExtensionAPI,
|
|
25
|
-
manager: ProcessManager,
|
|
26
|
-
) {
|
|
27
|
-
const lastRepeatTurnAt = new Map<string, number>();
|
|
28
|
-
|
|
29
|
-
manager.onEvent((event) => {
|
|
30
|
-
if (event.type === "process_ended") {
|
|
31
|
-
// Cleanup cooldown state for this process.
|
|
32
|
-
const prefix = `${event.info.id}:`;
|
|
33
|
-
for (const key of lastRepeatTurnAt.keys()) {
|
|
34
|
-
if (key.startsWith(prefix)) {
|
|
35
|
-
lastRepeatTurnAt.delete(key);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (event.type !== "process_watch_matched") return;
|
|
42
|
-
|
|
43
|
-
const match = event.match;
|
|
44
|
-
const message =
|
|
45
|
-
`Watch matched for '${match.processName}' (${match.processId}) ` +
|
|
46
|
-
`[${match.source}] /${match.watch.pattern}/`;
|
|
47
|
-
|
|
48
|
-
const details: ProcessWatchUpdateDetails = {
|
|
49
|
-
kind: "watch_matched",
|
|
50
|
-
processId: match.processId,
|
|
51
|
-
processName: match.processName,
|
|
52
|
-
command: match.processCommand,
|
|
53
|
-
source: match.source,
|
|
54
|
-
line: match.line,
|
|
55
|
-
watch: {
|
|
56
|
-
index: match.watch.index,
|
|
57
|
-
pattern: match.watch.pattern,
|
|
58
|
-
stream: match.watch.stream,
|
|
59
|
-
repeat: match.watch.repeat,
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
let triggerTurn = true;
|
|
64
|
-
if (match.watch.repeat) {
|
|
65
|
-
const watchKey = `${match.processId}:${match.watch.index}`;
|
|
66
|
-
const now = Date.now();
|
|
67
|
-
const last = lastRepeatTurnAt.get(watchKey) ?? 0;
|
|
68
|
-
triggerTurn = now - last >= REPEAT_WATCH_TURN_COOLDOWN_MS;
|
|
69
|
-
if (triggerTurn) {
|
|
70
|
-
lastRepeatTurnAt.set(watchKey, now);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
safeSendMessage(
|
|
75
|
-
pi,
|
|
76
|
-
{
|
|
77
|
-
customType: MESSAGE_TYPE_PROCESS_UPDATE,
|
|
78
|
-
content: message,
|
|
79
|
-
display: true,
|
|
80
|
-
details,
|
|
81
|
-
},
|
|
82
|
-
{ triggerTurn },
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
}
|
package/src/hooks/utils.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Send a message via pi, swallowing stale-context errors.
|
|
5
|
-
*
|
|
6
|
-
* After /new, /resume, or /fork, the pi proxy is invalidated and all
|
|
7
|
-
* method calls throw. This wrapper catches that specific error so the
|
|
8
|
-
* extension doesn't crash — the event is simply lost for the old session.
|
|
9
|
-
*/
|
|
10
|
-
export function safeSendMessage(
|
|
11
|
-
pi: ExtensionAPI,
|
|
12
|
-
message: Parameters<ExtensionAPI["sendMessage"]>[0],
|
|
13
|
-
options?: Parameters<ExtensionAPI["sendMessage"]>[1],
|
|
14
|
-
): void {
|
|
15
|
-
try {
|
|
16
|
-
pi.sendMessage(message, options);
|
|
17
|
-
} catch (err: unknown) {
|
|
18
|
-
if (err instanceof Error && err.message.includes("stale")) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
throw err;
|
|
22
|
-
}
|
|
23
|
-
}
|