@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/CONTRIBUTING.md CHANGED
@@ -8,6 +8,11 @@ Keep development details, testing notes, internal tool guidance, and docs build
8
8
 
9
9
  ## Development
10
10
 
11
+ Requirements:
12
+
13
+ - Node.js `22.19.0` or newer
14
+ - pnpm `10.26.1`
15
+
11
16
  Install dependencies:
12
17
 
13
18
  ```bash
@@ -17,20 +22,34 @@ pnpm install
17
22
  Run checks:
18
23
 
19
24
  ```bash
20
- pnpm lint
21
25
  pnpm typecheck
26
+ pnpm lint
27
+ pnpm test
28
+ pnpm test:e2e
22
29
  ```
23
30
 
24
31
  ## Repository layout
25
32
 
26
- - `src/` - extension source
27
- - `src/tools/` - `process` tool and actions
28
- - `src/commands/` - `/ps` commands and settings UI
29
- - `src/hooks/` - lifecycle hooks, blocker, message rendering, widgets
30
- - `src/components/` - TUI components
33
+ - `src/` - Pi-agnostic process management, types, protocol, and utilities
34
+ - `extensions/processes/` - core extension, process tool (start, list, output, update, write, stop, clear), settings, lifecycle hooks, notifications, protocol handlers, `/ps`, `/ps:kill`, `/ps:clear`, and `/ps:settings`
35
+ - `extensions/processes-logs/` - `/ps:logs` command and log overlay
36
+ - `extensions/processes-dock/` - `/ps:dock`, `/ps:pin`, the dock widget, and the status widget
37
+ - `extensions/shared/` - shared UI helpers (`statusDot`, `processStatusTone`, `LineComponent`, etc.) used across all three extensions
31
38
  - `skills/` - shipped package skills
32
39
  - `.agents/skills/` - local repo-only skills for development workflows
33
- - `.github/docs-site/` - isolated docs page build
40
+
41
+ ## Package metadata
42
+
43
+ The package targets Pi `0.80.3`.
44
+
45
+ Pi bundles core packages for extensions. Keep direct imports of these packages in `peerDependencies` with `"*"` ranges and exact local versions in `devDependencies`:
46
+
47
+ - `@earendil-works/pi-ai`
48
+ - `@earendil-works/pi-coding-agent`
49
+ - `@earendil-works/pi-tui`
50
+ - `typebox`
51
+
52
+ Keep normal third-party runtime dependencies in `dependencies`.
34
53
 
35
54
  ## Internal behavior
36
55
 
@@ -58,15 +77,17 @@ pnpm lint
58
77
  pnpm typecheck
59
78
  ```
60
79
 
61
- Useful manual process scripts:
80
+ Useful manual process scripts live under `tests/e2e/scripts/`:
62
81
 
63
82
  ```bash
64
- ./test/test-output.sh
65
- ./test/test-exit-success.sh 5
66
- ./test/test-exit-failure.sh 5
67
- ./test/test-exit-crash.sh 5
83
+ ./tests/e2e/scripts/continuous-output.sh # long-running stdout
84
+ ./tests/e2e/scripts/error-log.sh # interleaved info/error on stdout+stderr
85
+ ./tests/e2e/scripts/exited-task.sh # finite, exits 0
86
+ ./tests/e2e/scripts/crash-on-file.sh <name> # waits for a marker file then crashes
68
87
  ```
69
88
 
89
+ See `.agents/skills/pi-processes-testing/SKILL.md` for the full manual QA workflow and the complete fixture script reference.
90
+
70
91
  ## Docs conventions
71
92
 
72
93
  ### README
@@ -86,38 +107,15 @@ Avoid putting these in `README.md`:
86
107
  - detailed tool-call schemas
87
108
  - release workflow notes
88
109
 
89
- ### Video placeholders
90
-
91
- Use HTML comments in `README.md`:
92
-
93
- ```md
94
- <!-- VIDEO: {"id":"process-panel","title":"Browse and manage processes from the panel"} -->
95
- ```
96
-
97
- GitHub ignores these comments. The docs page build turns them into video blocks.
98
-
99
- Add one placeholder for each feature section.
100
-
101
- ## Docs page build
102
-
103
- The generated docs page lives under `.github/docs-site/` and is isolated from the extension source.
104
-
105
- It reads `README.md`, converts markdown into structured content, replaces video placeholders, highlights code with Shiki, and builds a static page with Vite and Tailwind.
106
-
107
- The GitHub Actions workflow for this lives in `.github/workflows/docs-page.yml`.
110
+ ### Docs page build
108
111
 
109
- ## Demo pattern
112
+ The docs page is generated from `README.md` by an external build, not in this repo. Keep `README.md` self-contained with only standard markdown.
110
113
 
111
- For demo recording, use a small self-contained project with a realistic workflow.
114
+ ## Future design notes
112
115
 
113
- The best pattern used for this extension was a fake Northwind API project where Pi:
116
+ Unimplemented design notes live in `docs/`:
114
117
 
115
- 1. starts a server in the background
116
- 2. runs tests and sees failures
117
- 3. runs migrations
118
- 4. checks server logs
119
- 5. updates seed data
120
- 6. reruns tests
121
- 7. cleans up the process
118
+ - `docs/future-cleanup-hooks.md` - planned `cleanup` support for `process start` / `process stop`
119
+ - `docs/future-persistent-manager.md` - planned cross-session process persistence
122
120
 
123
- That pattern shows why background processes matter in a normal task instead of showing features one by one.
121
+ These describe intended future behavior, not current behavior. Active implementation work goes under `.agents/plans/`.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![banner](https://assets.aliou.me/github/aliou/pi-processes/banner.png)
1
+ ![banner](https://assets.aliou.me/github/aliou/pi-processes/banner-v0.10.x.png)
2
2
 
3
3
  # pi-processes
4
4
 
@@ -10,6 +10,8 @@ This extension lets Pi keep long-running commands alive while the conversation c
10
10
 
11
11
  When a task needs a long-running command, Pi can start it in the background by itself and keep helping with the rest of the work.
12
12
 
13
+ [![Pi starts a long-running process and keeps working](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/agent-starts-processes.gif)](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/agent-starts-processes.mp4)
14
+
13
15
  That means Pi can, for example:
14
16
 
15
17
  - start a dev server and keep coding
@@ -17,9 +19,7 @@ That means Pi can, for example:
17
19
  - run a local API while it inspects logs
18
20
  - watch build output without blocking the conversation
19
21
 
20
- You can then inspect, pin, or stop those processes from the UI.
21
-
22
- <!-- VIDEO: {"id":"agent-starts-processes","title":"Pi starts a long-running process and keeps working"} -->
22
+ You can then inspect, pin, stop, or clear those processes from the UI.
23
23
 
24
24
  ## Installation
25
25
 
@@ -35,9 +35,19 @@ From git:
35
35
  pi install git:github.com/aliou/pi-processes
36
36
  ```
37
37
 
38
+ ## How Pi stays in the loop
39
+
40
+ Pi does not wait around for a background process. After it starts one, it keeps helping with the rest of the work and gets brought back automatically when something happens:
41
+
42
+ - a readiness marker appears in the logs (a server prints "ready")
43
+ - an error appears in the logs (a build prints a type error)
44
+ - the process exits, whether it succeeded, failed, or was killed
45
+
46
+ That is how Pi can start a dev server and then keep coding, or run a test watcher and react when a test fails, without sleeping or polling. If a watch fires too often, Pi can quiet it without restarting the process.
47
+
38
48
  ## Open the process panel
39
49
 
40
- Use `/ps` to open the main process panel.
50
+ Use `/ps` to open the main process panel. It shows running and finished processes, with the most recent output preview. The preview opens on the newest page so you can see live activity without scrolling.
41
51
 
42
52
  From there you can:
43
53
 
@@ -47,53 +57,45 @@ From there you can:
47
57
  - kill a running process
48
58
  - clear finished entries
49
59
 
50
- Keys:
51
-
52
- - `j/k` or arrow keys: move selection
53
- - `J/K`: scroll preview
54
- - `enter`: pin selected process to the dock
55
- - `x`: kill selected process
56
- - `c`: clear finished processes
57
- - `q` or `esc`: close
58
-
59
- <!-- VIDEO: {"id":"process-panel","title":"Browse and manage processes from the panel"} -->
60
-
61
60
  ## Inspect logs
62
61
 
63
- Use `/ps:logs [id|name]` to open the log overlay for one process.
62
+ Use `/ps:logs [id]` to open the log overlay for one process. The viewer is cached per process, so switching tabs preserves scroll position and follow mode.
64
63
 
65
64
  This is useful when Pi started a server, watcher, or local API and you want to follow what it is doing in more detail.
66
65
 
67
- Keys:
66
+ ## Control the dock
68
67
 
69
- - `tab` / `shift+tab`: switch process tabs
70
- - `g/G`: jump to top or bottom
71
- - `j/k` or arrow keys: scroll
72
- - `s`: switch between combined, stdout, and stderr
73
- - `f`: toggle follow mode
74
- - `/`: search
75
- - `n/N`: move between search matches
76
- - `q` or `esc`: close
68
+ Use `/ps:dock [expand|collapse|close]` to control dock visibility.
77
69
 
78
- <!-- VIDEO: {"id":"inspect-logs","title":"Open the log overlay and inspect output"} -->
70
+ The dock gives you a compact live view without leaving the conversation.
79
71
 
80
72
  ## Pin one process
81
73
 
82
- Use `/ps:pin [id|name]` to keep the dock focused on one process.
74
+ Use `/ps:pin [id]` to keep the dock focused on one process.
83
75
 
84
76
  This is useful when one process matters more than the others, such as a dev server or a test watcher.
85
77
 
86
78
  Without arguments, Pi shows a picker.
87
79
 
88
- <!-- VIDEO: {"id":"pin-process","title":"Pin the dock to one process"} -->
80
+ ## Stop and clear processes
89
81
 
90
- ## Control the dock
82
+ Use `/ps:kill [id]` to stop a running process, and `/ps:clear` to remove finished entries from the panel and free their log storage.
91
83
 
92
- Use `/ps:dock [show|hide|toggle]` to control dock visibility.
84
+ `/ps:kill` waits for the process to actually exit (or time out), so the result it reports reflects what happened. Without arguments, Pi shows a picker.
93
85
 
94
- The dock gives you a compact live view without leaving the conversation.
86
+ `/ps:clear` never touches live processes.
87
+
88
+ ## Keep a status line in view
89
+
90
+ Enable the status widget in `/ps:settings` to show a compact line of running processes below the editor. Each process shows a status dot, its name, and its state, with `+N more` overflow when the line does not fit.
95
91
 
96
- <!-- VIDEO: {"id":"dock-control","title":"Show, hide, and use the dock"} -->
92
+ It is disabled by default. The widget reflows on resize and clears itself when the process list is empty.
93
+
94
+ ## Send input to a process
95
+
96
+ Use the `process` tool with `action: "write"` to send bytes to a running process's stdin. This is how you drive interactive servers, REPLs, and CLIs that expect input after they start.
97
+
98
+ Pass `input` for the bytes to write, and set `end: true` to close stdin (for example to signal EOF to a waiting process).
97
99
 
98
100
  ## Adjust settings
99
101
 
@@ -106,78 +108,46 @@ Available settings include:
106
108
  - shell path override
107
109
  - dock defaults
108
110
  - follow mode behavior
111
+ - status widget toggle
109
112
  - optional background command interception
110
113
 
111
- <!-- VIDEO: {"id":"settings","title":"Adjust process extension settings"} -->
112
-
113
114
  ## Platform support
114
115
 
115
116
  - macOS: supported
116
117
  - Linux: supported
117
118
  - Windows: not supported
118
119
 
119
- ## Runtime log watch alerts
120
+ ## Troubleshooting
120
121
 
121
- Use `process` tool `start` with `logWatches` to trigger immediate alerts while the process is still running.
122
+ ### Pi started something and I want to see more output
122
123
 
123
- - default behavior: each watch fires once (`repeat: false`)
124
- - set `repeat: true` to trigger on every match
125
- - scope by stream (`stdout`, `stderr`, `both`) to reduce noise
124
+ Open `/ps` for a quick overview, or use `/ps:logs` for full logs.
126
125
 
127
- Example: server ready marker (one-time default)
126
+ ### I want one process to stay visible
128
127
 
129
- ```json
130
- {
131
- "action": "start",
132
- "name": "dev-server",
133
- "command": "pnpm dev",
134
- "logWatches": [
135
- { "pattern": "ready on http://localhost:3000" }
136
- ]
137
- }
138
- ```
128
+ Use `/ps:pin` to focus the dock on that process.
139
129
 
140
- Example: error marker from stderr
141
-
142
- ```json
143
- {
144
- "action": "start",
145
- "name": "builder",
146
- "command": "pnpm build --watch",
147
- "logWatches": [
148
- { "pattern": "TypeError|ReferenceError", "stream": "stderr" }
149
- ]
150
- }
151
- ```
130
+ ### I want Pi to avoid shell background tricks
152
131
 
153
- Example: repeatable watch on stdout only
154
-
155
- ```json
156
- {
157
- "action": "start",
158
- "name": "worker",
159
- "command": "pnpm worker",
160
- "logWatches": [
161
- { "pattern": "job completed", "stream": "stdout", "repeat": true }
162
- ]
163
- }
164
- ```
132
+ Enable background command interception in `/ps:settings`. When enabled, Pi avoids normal shell background patterns and uses the process workflow instead.
165
133
 
166
- Invalid regex patterns fail fast at process start with a clear error.
134
+ ## Feature demos
167
135
 
168
- ## Troubleshooting
136
+ **Watch a file-backed log and recover from an error**
169
137
 
170
- ### Pi started something and I want to see more output
138
+ [![Watch a file-backed log and recover from an error](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/debug-from-log.gif)](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/debug-from-log.mp4)
171
139
 
172
- Open `/ps` for a quick overview, or use `/ps:logs` for full logs.
140
+ **Open the log overlay and inspect output**
173
141
 
174
- ### I want one process to stay visible
142
+ [![Open the log overlay and inspect output](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/inspect-logs.gif)](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/inspect-logs.mp4)
175
143
 
176
- Use `/ps:pin` to focus the dock on that process.
144
+ **Stop and clear processes**
177
145
 
178
- ### I want Pi to avoid shell background tricks
146
+ [![Stop and clear processes](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/stop-and-clear.gif)](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/stop-and-clear.mp4)
179
147
 
180
- Enable background command interception in `/ps:settings`. When enabled, Pi avoids normal shell background patterns and uses the process workflow instead.
148
+ **Send input to a running process**
149
+
150
+ [![Send input to a running process](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/send-input.gif)](https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/send-input.mp4)
181
151
 
182
152
  ## Contributing
183
153
 
@@ -185,4 +155,4 @@ For development, testing, docs generation, and extension internals, see [CONTRIB
185
155
 
186
156
  ## License
187
157
 
188
- MIT
158
+ MIT
@@ -0,0 +1,26 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+
3
+ import type { ProcessInfo } from "../src/types";
4
+ import { truncateCmd } from "../src/utils/format";
5
+ import { MAX_TAB_NAME, statusDot } from "./shared/ui";
6
+
7
+ export { MAX_TAB_NAME, statusDot as renderProcessTabDot };
8
+
9
+ /**
10
+ * Render a process tab: ` dot label ` with active highlight.
11
+ *
12
+ * Truncates the name to `MAX_TAB_NAME` without padding, so tabs stay compact
13
+ * (`● api` not `● api `). The active tab is rendered on a selected
14
+ * background; inactive tabs use a dim label.
15
+ */
16
+ export function renderProcessTab(
17
+ process: ProcessInfo,
18
+ active: boolean,
19
+ theme: Theme,
20
+ ): string {
21
+ const dot = statusDot(process, active, theme);
22
+ const label = truncateCmd(process.name, MAX_TAB_NAME);
23
+ return active
24
+ ? theme.bg("selectedBg", ` ${dot} ${theme.fg("accent", label)} `)
25
+ : ` ${dot} ${theme.fg("dim", label)} `;
26
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Core-extension request/command helpers for the `/ps` overview panel.
3
+ *
4
+ * The overview panel prefers the existing `pi.events` protocol channels over
5
+ * calling the manager directly so a future split-out of the panel stays cheap.
6
+ * These helpers are the core equivalent of `extensions/processes-logs/client.ts`
7
+ * and `extensions/processes-dock/client.ts`.
8
+ */
9
+
10
+ import type { EventBus } from "@earendil-works/pi-coding-agent";
11
+ import {
12
+ CHANNELS,
13
+ type CommandClearPayload,
14
+ type CommandKillPayload,
15
+ type CommandPinPayload,
16
+ type CommandPinResult,
17
+ type ProcessProtocolConfig,
18
+ type RequestCombinedOutputPayload,
19
+ type RequestConfigPayload,
20
+ type RequestGetPayload,
21
+ type RequestListPayload,
22
+ } from "../../src/protocol";
23
+ import type { KillResult, ProcessInfo } from "../../src/types";
24
+
25
+ export type ProcessLogLine = { type: "stdout" | "stderr"; text: string };
26
+
27
+ export function requestProcessList(events: EventBus): ProcessInfo[] {
28
+ let processes: ProcessInfo[] = [];
29
+ const payload: RequestListPayload = {
30
+ reply: (result) => {
31
+ processes = result;
32
+ },
33
+ };
34
+ events.emit(CHANNELS.REQUEST_LIST, payload);
35
+ return processes;
36
+ }
37
+
38
+ export function requestProcess(
39
+ events: EventBus,
40
+ id: string,
41
+ ): ProcessInfo | null {
42
+ let process: ProcessInfo | null = null;
43
+ const payload: RequestGetPayload = {
44
+ id,
45
+ reply: (result) => {
46
+ process = result;
47
+ },
48
+ };
49
+ events.emit(CHANNELS.REQUEST_GET, payload);
50
+ return process;
51
+ }
52
+
53
+ export function requestConfig(events: EventBus): ProcessProtocolConfig {
54
+ let config: ProcessProtocolConfig | null = null;
55
+ const payload: RequestConfigPayload = {
56
+ reply: (result) => {
57
+ config = result;
58
+ },
59
+ };
60
+ events.emit(CHANNELS.REQUEST_CONFIG, payload);
61
+ if (!config) {
62
+ throw new Error("processes core extension did not reply to config request");
63
+ }
64
+ return config;
65
+ }
66
+
67
+ export function requestCombinedOutput(
68
+ events: EventBus,
69
+ id: string,
70
+ tailLines?: number,
71
+ ): ProcessLogLine[] {
72
+ let lines: ProcessLogLine[] | null = null;
73
+ const payload: RequestCombinedOutputPayload = {
74
+ id,
75
+ tailLines,
76
+ reply: (result) => {
77
+ lines = result;
78
+ },
79
+ };
80
+ events.emit(CHANNELS.REQUEST_COMBINED_OUTPUT, payload);
81
+ return lines ?? [];
82
+ }
83
+
84
+ /**
85
+ * Kill a managed process. Resolves when the kill handler replies, or with a
86
+ * timeout error result if no listener responds.
87
+ *
88
+ * The kill handler runs `killIntentionally` asynchronously, so its reply fires
89
+ * on a later microtask rather than during the emit. That is why this returns a
90
+ * Promise (unlike the synchronous `requestClear`/`requestProcessList`): a
91
+ * synchronous read of `result` would always miss the reply and report failure.
92
+ *
93
+ * The safety timeout defaults to the kill timeout plus headroom so a successful
94
+ * slow kill still resolves first. The timer is unref'd so it never keeps the
95
+ * event loop alive.
96
+ */
97
+ export function requestKill(
98
+ events: EventBus,
99
+ id: string,
100
+ options?: { signal?: NodeJS.Signals; timeoutMs?: number },
101
+ ): Promise<KillResult> {
102
+ return new Promise((resolve) => {
103
+ let settled = false;
104
+ const killTimeoutMs = options?.timeoutMs ?? 3000;
105
+ const payload: CommandKillPayload = {
106
+ id,
107
+ signal: options?.signal,
108
+ timeoutMs: killTimeoutMs,
109
+ reply: (result) => {
110
+ if (settled) return;
111
+ settled = true;
112
+ clearTimeout(timer);
113
+ resolve(result);
114
+ },
115
+ };
116
+ const timer = setTimeout(() => {
117
+ if (settled) return;
118
+ settled = true;
119
+ resolve(killTimeoutResult(id));
120
+ }, killTimeoutMs + 2000);
121
+ // Unref so a parked timer never keeps the event loop alive.
122
+ timer.unref?.();
123
+ events.emit(CHANNELS.COMMAND_KILL, payload);
124
+ });
125
+ }
126
+
127
+ function killTimeoutResult(id: string): KillResult {
128
+ return {
129
+ ok: false,
130
+ reason: "error",
131
+ info: {
132
+ id,
133
+ name: "(unknown)",
134
+ pid: -1,
135
+ command: "",
136
+ cwd: "",
137
+ startTime: 0,
138
+ endTime: null,
139
+ status: "exited",
140
+ exitCode: null,
141
+ success: false,
142
+ stdoutFile: "",
143
+ stderrFile: "",
144
+ endReason: null,
145
+ signal: null,
146
+ errorMessage: "No kill handler replied",
147
+ },
148
+ };
149
+ }
150
+
151
+ export function requestClear(events: EventBus): number {
152
+ let cleared = 0;
153
+ const payload: CommandClearPayload = {
154
+ reply: (value) => {
155
+ cleared = value;
156
+ },
157
+ };
158
+ events.emit(CHANNELS.COMMAND_CLEAR, payload);
159
+ return cleared;
160
+ }
161
+
162
+ /**
163
+ * Pin a process to the dock. Resolves when the dock extension replies, or
164
+ * rejects if no dock handler responds within `timeoutMs`.
165
+ *
166
+ * The dock extension must be loaded for this to succeed. If it is not
167
+ * registered, no listener will reply and the promise rejects with a timeout.
168
+ */
169
+ export function requestPin(
170
+ events: EventBus,
171
+ id: string | null,
172
+ timeoutMs = 200,
173
+ ): Promise<CommandPinResult> {
174
+ return new Promise((resolve) => {
175
+ let settled = false;
176
+ const payload: CommandPinPayload = {
177
+ id,
178
+ reply: (result) => {
179
+ if (settled) return;
180
+ settled = true;
181
+ clearTimeout(timer);
182
+ resolve(result);
183
+ },
184
+ };
185
+ const timer = setTimeout(() => {
186
+ if (settled) return;
187
+ settled = true;
188
+ resolve({ ok: false, error: "Dock extension is not available" });
189
+ }, timeoutMs);
190
+ // The timeout needs to be unref'd so it never keeps the event loop alive.
191
+ timer.unref?.();
192
+ events.emit(CHANNELS.COMMAND_PIN, payload);
193
+ });
194
+ }
@@ -0,0 +1,29 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionCommandContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { requestClear } from "../client";
6
+
7
+ /**
8
+ * Register `/ps:clear`.
9
+ *
10
+ * Removes finished processes from the list via the clear protocol channel,
11
+ * mirroring main's behavior. Returns the count of cleared processes.
12
+ */
13
+ export function registerClearCommand(pi: ExtensionAPI): void {
14
+ const events = pi.events;
15
+ pi.registerCommand("ps:clear", {
16
+ description: "Remove finished processes from the list.",
17
+ handler: async (_args: string, ctx: ExtensionCommandContext) => {
18
+ const cleared = requestClear(events);
19
+ if (cleared === 0) {
20
+ ctx.ui.notify("No finished processes to clear.", "info");
21
+ return;
22
+ }
23
+ ctx.ui.notify(
24
+ `Cleared ${cleared} finished process${cleared === 1 ? "" : "es"}.`,
25
+ "info",
26
+ );
27
+ },
28
+ });
29
+ }