@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.
Files changed (171) hide show
  1. package/CONTRIBUTING.md +40 -42
  2. package/README.md +54 -84
  3. package/extensions/process-tabs.ts +26 -0
  4. package/extensions/processes/client.ts +194 -0
  5. package/extensions/processes/commands/clear.ts +29 -0
  6. package/extensions/processes/commands/kill.ts +152 -0
  7. package/extensions/processes/commands/overview.ts +102 -0
  8. package/extensions/processes/components/overview-component.ts +729 -0
  9. package/extensions/processes/components/overview-panel.ts +177 -0
  10. package/extensions/processes/config/defaults.ts +28 -0
  11. package/extensions/processes/config/index.ts +13 -0
  12. package/extensions/processes/config/loader.ts +62 -0
  13. package/extensions/processes/config/migrations/000-import-legacy-process-config.ts +113 -0
  14. package/extensions/processes/config/migrations/001-v0-9-4-to-v0-10-0-config.ts +184 -0
  15. package/extensions/processes/config/migrations/002-stamp-v0-10-0-config-version.ts +21 -0
  16. package/extensions/processes/config/migrations/index.ts +25 -0
  17. package/extensions/processes/config/schema.ts +9 -0
  18. package/extensions/processes/config/types.ts +72 -0
  19. package/extensions/processes/constants.ts +1 -0
  20. package/extensions/processes/handlers/commands.ts +113 -0
  21. package/extensions/processes/handlers/kill-process.ts +31 -0
  22. package/extensions/processes/handlers/notifications.ts +147 -0
  23. package/extensions/processes/handlers/requests.ts +114 -0
  24. package/extensions/processes/handlers/subscriptions.ts +144 -0
  25. package/extensions/processes/hooks/background-blocker.ts +201 -0
  26. package/extensions/processes/hooks/cleanup.ts +43 -0
  27. package/extensions/processes/hooks/event-bridge.ts +32 -0
  28. package/extensions/processes/i18n/index.ts +3 -0
  29. package/extensions/processes/i18n/messages.ts +27 -0
  30. package/extensions/processes/i18n/translator.ts +49 -0
  31. package/extensions/processes/index.ts +109 -0
  32. package/extensions/processes/message-renderer.ts +117 -0
  33. package/extensions/processes/notification-sender.ts +43 -0
  34. package/extensions/processes/notifications/classify.ts +27 -0
  35. package/extensions/processes/notifications/log-matchers.ts +146 -0
  36. package/extensions/processes/notifications/registry.ts +225 -0
  37. package/extensions/processes/notifications/render-content.ts +122 -0
  38. package/extensions/processes/notifications/service.ts +336 -0
  39. package/extensions/processes/notifications/types.ts +11 -0
  40. package/extensions/processes/settings/apply-setting-change.ts +30 -0
  41. package/extensions/processes/settings/build-sections.ts +420 -0
  42. package/extensions/processes/settings/index.ts +42 -0
  43. package/extensions/processes/tools/clear/index.ts +21 -0
  44. package/extensions/processes/tools/clear/render.ts +40 -0
  45. package/extensions/processes/tools/components/index.ts +4 -0
  46. package/extensions/processes/tools/components/process-action-header.ts +31 -0
  47. package/extensions/processes/tools/components/process-action-title.ts +17 -0
  48. package/extensions/processes/tools/components/tool-layout.ts +70 -0
  49. package/extensions/processes/tools/components/watch.ts +53 -0
  50. package/extensions/processes/tools/index.ts +265 -0
  51. package/extensions/processes/tools/list/index.ts +157 -0
  52. package/extensions/processes/tools/list/render.ts +211 -0
  53. package/extensions/processes/tools/notify.ts +191 -0
  54. package/extensions/processes/tools/output/index.ts +307 -0
  55. package/extensions/processes/tools/output/render.ts +229 -0
  56. package/extensions/processes/tools/schema.ts +263 -0
  57. package/extensions/processes/tools/start/index.ts +64 -0
  58. package/extensions/processes/tools/start/render.ts +67 -0
  59. package/extensions/processes/tools/stop/index.ts +35 -0
  60. package/extensions/processes/tools/stop/render.ts +43 -0
  61. package/extensions/processes/tools/update/index.ts +320 -0
  62. package/extensions/processes/tools/update/render.ts +251 -0
  63. package/extensions/processes/tools/utils.ts +112 -0
  64. package/extensions/processes/tools/watch-format.ts +14 -0
  65. package/extensions/processes/tools/write/index.ts +75 -0
  66. package/extensions/processes/tools/write/render.ts +68 -0
  67. package/extensions/processes/utils/truncate.ts +285 -0
  68. package/extensions/processes-dock/client.ts +60 -0
  69. package/extensions/processes-dock/commands/dock.ts +70 -0
  70. package/extensions/processes-dock/commands/pin.ts +166 -0
  71. package/extensions/processes-dock/components/log-dock-component.ts +375 -0
  72. package/extensions/processes-dock/dock-state.ts +33 -0
  73. package/extensions/processes-dock/index.ts +28 -0
  74. package/extensions/processes-dock/logs-client.ts +5 -0
  75. package/extensions/processes-dock/widget/setup.ts +520 -0
  76. package/extensions/processes-dock/widget/status.ts +149 -0
  77. package/extensions/processes-dock/widget/types.ts +16 -0
  78. package/extensions/processes-logs/client.ts +51 -0
  79. package/extensions/processes-logs/commands/logs.ts +108 -0
  80. package/extensions/processes-logs/completions.ts +31 -0
  81. package/extensions/processes-logs/components/log-file-viewer.ts +274 -0
  82. package/extensions/processes-logs/components/log-overlay-component.ts +685 -0
  83. package/extensions/processes-logs/index.ts +27 -0
  84. package/extensions/processes-logs/logs-client.ts +101 -0
  85. package/extensions/shared/line-buffer.ts +35 -0
  86. package/extensions/shared/output-payload.ts +28 -0
  87. package/extensions/shared/ui.ts +146 -0
  88. package/package.json +47 -20
  89. package/schema.json +134 -0
  90. package/skills/pi-processes/SKILL.md +496 -25
  91. package/src/get-manager.ts +15 -0
  92. package/src/manager/index.ts +182 -0
  93. package/src/manager/internal-types.ts +130 -0
  94. package/src/manager/limits.ts +15 -0
  95. package/src/manager/process-log-store.ts +422 -0
  96. package/src/manager/process-output.ts +345 -0
  97. package/src/manager/process-registry.ts +78 -0
  98. package/src/manager/process-runtime-controller.ts +499 -0
  99. package/src/protocol/broadcasts.ts +14 -0
  100. package/src/protocol/channels.ts +30 -0
  101. package/src/protocol/commands.ts +24 -0
  102. package/src/protocol/index.ts +34 -0
  103. package/src/protocol/logs.ts +23 -0
  104. package/src/protocol/notifications.ts +48 -0
  105. package/src/protocol/requests.ts +76 -0
  106. package/src/types.ts +66 -0
  107. package/src/utils/ansi.ts +2 -4
  108. package/src/utils/format.ts +24 -32
  109. package/src/utils/index.ts +5 -1
  110. package/src/utils/is-record.ts +3 -0
  111. package/src/utils/match-line.ts +32 -0
  112. package/src/utils/platform.ts +13 -0
  113. package/src/utils/shorten-path.ts +11 -0
  114. package/src/utils/signals.ts +22 -0
  115. package/src/commands/clear/command.ts +0 -14
  116. package/src/commands/clear/index.ts +0 -1
  117. package/src/commands/completions.ts +0 -38
  118. package/src/commands/dock/command.ts +0 -29
  119. package/src/commands/dock/index.ts +0 -1
  120. package/src/commands/index.ts +0 -33
  121. package/src/commands/kill/command.ts +0 -69
  122. package/src/commands/kill/index.ts +0 -1
  123. package/src/commands/logs/command.ts +0 -47
  124. package/src/commands/logs/index.ts +0 -1
  125. package/src/commands/pick-process.ts +0 -34
  126. package/src/commands/pin/command.ts +0 -34
  127. package/src/commands/pin/index.ts +0 -1
  128. package/src/commands/processes/command.ts +0 -39
  129. package/src/commands/processes/index.ts +0 -1
  130. package/src/commands/settings/apply-setting-change.ts +0 -72
  131. package/src/commands/settings/build-sections.ts +0 -160
  132. package/src/commands/settings/command.ts +0 -20
  133. package/src/commands/settings/index.ts +0 -1
  134. package/src/components/log-dock-component.test.ts +0 -29
  135. package/src/components/log-dock-component.ts +0 -236
  136. package/src/components/log-file-viewer.ts +0 -317
  137. package/src/components/log-overlay-component.ts +0 -546
  138. package/src/components/panel-helpers.ts +0 -37
  139. package/src/components/process-picker-component.ts +0 -166
  140. package/src/components/processes-component.ts +0 -485
  141. package/src/components/status-format.ts +0 -38
  142. package/src/config.ts +0 -107
  143. package/src/constants/index.ts +0 -16
  144. package/src/constants/types.ts +0 -106
  145. package/src/hooks/background-blocker.ts +0 -66
  146. package/src/hooks/cleanup.ts +0 -10
  147. package/src/hooks/index.ts +0 -32
  148. package/src/hooks/message-renderer.ts +0 -115
  149. package/src/hooks/process-end.ts +0 -69
  150. package/src/hooks/process-watch.ts +0 -85
  151. package/src/hooks/utils.ts +0 -23
  152. package/src/hooks/widget/index.ts +0 -2
  153. package/src/hooks/widget/setup.ts +0 -167
  154. package/src/hooks/widget/status-widget.ts +0 -105
  155. package/src/hooks/widget/types.ts +0 -20
  156. package/src/index.ts +0 -35
  157. package/src/manager.test.ts +0 -331
  158. package/src/manager.ts +0 -758
  159. package/src/tools/actions/clear.ts +0 -20
  160. package/src/tools/actions/debug.ts +0 -148
  161. package/src/tools/actions/index.ts +0 -171
  162. package/src/tools/actions/kill.ts +0 -89
  163. package/src/tools/actions/list.ts +0 -188
  164. package/src/tools/actions/logs.ts +0 -117
  165. package/src/tools/actions/output.ts +0 -269
  166. package/src/tools/actions/start.ts +0 -245
  167. package/src/tools/actions/write.ts +0 -113
  168. package/src/tools/index.ts +0 -207
  169. package/src/utils/ansi.test.ts +0 -22
  170. package/src/utils/command-executor.test.ts +0 -48
  171. 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
- });
@@ -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";
@@ -1,106 +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
- export interface ProcessesDetails {
93
- action: ProcessAction;
94
- success: boolean;
95
- message: string;
96
- process?: ProcessInfo;
97
- processes?: ProcessInfo[];
98
- output?: { stdout: string[]; stderr: string[]; status: string };
99
- logFiles?: { stdoutFile: string; stderrFile: string };
100
- cleared?: number;
101
- }
102
-
103
- export interface ExecuteResult {
104
- content: Array<{ type: "text"; text: string }>;
105
- details: ProcessesDetails;
106
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -1,2 +0,0 @@
1
- export { setupProcessWidget } from "./setup";
2
- export type { DockActions } from "./types";