@aliou/pi-processes 0.8.1 → 0.9.1

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 (40) hide show
  1. package/README.md +1 -1
  2. package/package.json +18 -17
  3. package/src/commands/clear/command.ts +1 -1
  4. package/src/commands/dock/command.ts +1 -1
  5. package/src/commands/index.ts +1 -1
  6. package/src/commands/kill/command.ts +1 -1
  7. package/src/commands/logs/command.ts +1 -1
  8. package/src/commands/pick-process.ts +1 -1
  9. package/src/commands/pin/command.ts +1 -1
  10. package/src/commands/processes/command.ts +1 -1
  11. package/src/commands/settings/command.ts +1 -1
  12. package/src/components/log-dock-component.ts +20 -16
  13. package/src/components/log-file-viewer.ts +2 -2
  14. package/src/components/log-overlay-component.ts +2 -2
  15. package/src/components/panel-helpers.ts +37 -0
  16. package/src/components/process-picker-component.ts +8 -9
  17. package/src/components/processes-component.ts +9 -10
  18. package/src/hooks/background-blocker.ts +1 -1
  19. package/src/hooks/cleanup.ts +1 -1
  20. package/src/hooks/index.ts +1 -1
  21. package/src/hooks/message-renderer.ts +2 -2
  22. package/src/hooks/process-end.ts +4 -2
  23. package/src/hooks/process-watch.ts +4 -2
  24. package/src/hooks/utils.ts +23 -0
  25. package/src/hooks/widget/setup.ts +1 -1
  26. package/src/hooks/widget/status-widget.ts +2 -2
  27. package/src/index.ts +1 -1
  28. package/src/manager.ts +2 -2
  29. package/src/tools/actions/debug.ts +1 -1
  30. package/src/tools/actions/index.ts +2 -2
  31. package/src/tools/actions/kill.ts +1 -1
  32. package/src/tools/actions/list.ts +2 -2
  33. package/src/tools/actions/logs.ts +2 -2
  34. package/src/tools/actions/output.ts +2 -2
  35. package/src/tools/actions/start.ts +2 -2
  36. package/src/tools/actions/write.ts +1 -1
  37. package/src/tools/index.ts +3 -3
  38. package/src/utils/ansi.test.ts +22 -0
  39. package/src/utils/ansi.ts +28 -19
  40. package/src/utils/format.ts +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![banner](https://assets.aliou.me/pi-extensions/banners/pi-processes.png)
1
+ ![banner](https://assets.aliou.me/github/aliou/pi-processes/banner.png)
2
2
 
3
3
  # pi-processes
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliou/pi-processes",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "private": false,
@@ -21,7 +21,7 @@
21
21
  "skills": [
22
22
  "./skills/pi-processes"
23
23
  ],
24
- "video": "https://assets.aliou.me/pi-extensions/demos/pi-processes.mp4"
24
+ "video": "https://assets.aliou.me/github/aliou/pi-processes/demo.mp4"
25
25
  },
26
26
  "publishConfig": {
27
27
  "access": "public"
@@ -33,31 +33,32 @@
33
33
  "CONTRIBUTING.md"
34
34
  ],
35
35
  "dependencies": {
36
- "@aliou/pi-utils-settings": "^0.10.0",
37
- "@aliou/pi-utils-ui": "^0.1.0",
36
+ "@aliou/pi-utils-settings": "^0.15.1",
37
+ "@aliou/pi-utils-ui": "^0.4.1",
38
38
  "@aliou/sh": "^0.1.0",
39
- "typebox": "^1.0.0"
39
+ "typebox": "^1.1.38"
40
40
  },
41
41
  "peerDependencies": {
42
- "@mariozechner/pi-coding-agent": "0.69.0",
43
- "@mariozechner/pi-tui": "0.69.0"
42
+ "@earendil-works/pi-coding-agent": "^0.75.3",
43
+ "@earendil-works/pi-tui": "^0.75.3"
44
44
  },
45
45
  "devDependencies": {
46
- "@aliou/biome-plugins": "^0.3.2",
47
- "@biomejs/biome": "^2.3.13",
48
- "@changesets/cli": "^2.27.11",
49
- "@mariozechner/pi-ai": "0.69.0",
50
- "@mariozechner/pi-coding-agent": "0.69.0",
51
- "@types/node": "^25.0.10",
46
+ "@aliou/biome-plugins": "^0.8.1",
47
+ "@biomejs/biome": "^2.4.15",
48
+ "@changesets/cli": "^2.31.0",
49
+ "@types/node": "^25.9.1",
52
50
  "husky": "^9.1.7",
53
- "typescript": "^5.9.3",
54
- "vitest": "^4.0.18"
51
+ "typescript": "^6.0.3",
52
+ "vitest": "^4.1.7",
53
+ "@earendil-works/pi-ai": "^0.75.3",
54
+ "@earendil-works/pi-coding-agent": "^0.75.3",
55
+ "@earendil-works/pi-tui": "^0.75.3"
55
56
  },
56
57
  "peerDependenciesMeta": {
57
- "@mariozechner/pi-coding-agent": {
58
+ "@earendil-works/pi-coding-agent": {
58
59
  "optional": true
59
60
  },
60
- "@mariozechner/pi-tui": {
61
+ "@earendil-works/pi-tui": {
61
62
  "optional": true
62
63
  }
63
64
  },
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProcessManager } from "../../manager";
3
3
 
4
4
  export function registerPsClearCommand(
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { DockActions } from "../../hooks/widget";
3
3
 
4
4
  export function registerPsDockCommand(
@@ -9,7 +9,7 @@
9
9
  * /ps:dock [show|hide|toggle] - Control dock visibility
10
10
  */
11
11
 
12
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
12
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
13
  import type { DockActions } from "../hooks/widget";
14
14
  import type { ProcessManager } from "../manager";
15
15
  import { registerPsClearCommand } from "./clear";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { LIVE_STATUSES } from "../../constants";
3
3
  import type { DockActions } from "../../hooks/widget";
4
4
  import type { ProcessManager } from "../../manager";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { LogOverlayComponent } from "../../components/log-overlay-component";
3
3
  import type { ProcessManager } from "../../manager";
4
4
  import { allProcessCompletions } from "../completions";
@@ -1,4 +1,4 @@
1
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import { ProcessPickerComponent } from "../components/process-picker-component";
3
3
  import type { ProcessInfo } from "../constants";
4
4
  import type { ProcessManager } from "../manager";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { DockActions } from "../../hooks/widget";
3
3
  import type { ProcessManager } from "../../manager";
4
4
  import { allProcessCompletions } from "../completions";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { ProcessesComponent } from "../../components/processes-component";
3
3
  import type { DockActions } from "../../hooks/widget";
4
4
  import type { ProcessManager } from "../../manager";
@@ -1,5 +1,5 @@
1
1
  import { registerSettingsCommand } from "@aliou/pi-utils-settings";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import type { ProcessesConfig, ResolvedProcessesConfig } from "../../config";
4
4
  import { configLoader } from "../../config";
5
5
  import { applySettingChange } from "./apply-setting-change";
@@ -5,17 +5,18 @@
5
5
  * Open view: LogFileViewer for the focused process (or first running), follow mode on.
6
6
  */
7
7
 
8
+ import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
9
+ import type { Component } from "@earendil-works/pi-tui";
10
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
11
+ import { LIVE_STATUSES } from "../constants";
12
+ import type { ProcessManager } from "../manager";
13
+ import { stripAnsi } from "../utils";
14
+ import { LogFileViewer } from "./log-file-viewer";
8
15
  import {
9
16
  createPanelPadder,
10
17
  renderPanelRule,
11
18
  renderPanelTitleLine,
12
- } from "@aliou/pi-utils-ui";
13
- import type { Theme, ThemeColor } from "@mariozechner/pi-coding-agent";
14
- import type { Component } from "@mariozechner/pi-tui";
15
- import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
16
- import { LIVE_STATUSES } from "../constants";
17
- import type { ProcessManager } from "../manager";
18
- import { LogFileViewer } from "./log-file-viewer";
19
+ } from "./panel-helpers";
19
20
 
20
21
  const PROCESS_COLORS: ThemeColor[] = [
21
22
  "accent",
@@ -121,9 +122,10 @@ export class LogDockComponent implements Component {
121
122
  const processes = this.manager.list();
122
123
  const innerWidth = width - 2;
123
124
  const padLine = (content: string) => {
124
- const w = visibleWidth(content);
125
125
  const line =
126
- w > innerWidth ? truncateToWidth(content, innerWidth) : content;
126
+ visibleWidth(content) > innerWidth
127
+ ? truncateToWidth(content, innerWidth, "", true)
128
+ : content;
127
129
  return ` ${line}${" ".repeat(Math.max(0, width - 1 - visibleWidth(line)))}`;
128
130
  };
129
131
 
@@ -146,15 +148,17 @@ export class LogDockComponent implements Component {
146
148
  const firstLine = parts.join(" | ");
147
149
  const lines = [
148
150
  renderPanelRule(width, theme),
149
- padLine(truncateToWidth(firstLine, innerWidth)),
151
+ padLine(truncateToWidth(firstLine, innerWidth, "", true)),
150
152
  ];
151
153
 
152
154
  if (running.length > 0) {
153
155
  const lastLogs = this.manager.getCombinedOutput(running[0].id, 1);
154
156
  if (lastLogs && lastLogs.length > 0) {
155
157
  const lastLog = truncateToWidth(
156
- lastLogs[lastLogs.length - 1].text,
158
+ stripAnsi(lastLogs[lastLogs.length - 1].text),
157
159
  innerWidth,
160
+ "",
161
+ true,
158
162
  );
159
163
  lines.push(padLine(dim(lastLog)));
160
164
  }
@@ -169,12 +173,12 @@ export class LogDockComponent implements Component {
169
173
 
170
174
  const innerWidth = width - 2;
171
175
  const basePadLine = createPanelPadder(width);
172
- const padLine = (content: string): string => {
173
- const w = visibleWidth(content);
174
- return basePadLine(
175
- w > innerWidth ? truncateToWidth(content, innerWidth) : content,
176
+ const padLine = (content: string): string =>
177
+ basePadLine(
178
+ visibleWidth(content) > innerWidth
179
+ ? truncateToWidth(content, innerWidth, "", true)
180
+ : content,
176
181
  );
177
- };
178
182
 
179
183
  const processes = this.manager.list();
180
184
  const running = processes.filter((p) => LIVE_STATUSES.has(p.status));
@@ -8,8 +8,8 @@
8
8
  */
9
9
 
10
10
  import { readFileSync } from "node:fs";
11
- import type { Theme } from "@mariozechner/pi-coding-agent";
12
- import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
11
+ import type { Theme } from "@earendil-works/pi-coding-agent";
12
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
13
13
  import { stripAnsi } from "../utils";
14
14
 
15
15
  export type StreamFilter = "combined" | "stdout" | "stderr";
@@ -14,7 +14,7 @@
14
14
  * ╰───────────────────────────────────────╯
15
15
  */
16
16
 
17
- import type { Theme } from "@mariozechner/pi-coding-agent";
17
+ import type { Theme } from "@earendil-works/pi-coding-agent";
18
18
  import {
19
19
  type Component,
20
20
  Input,
@@ -22,7 +22,7 @@ import {
22
22
  type TUI,
23
23
  truncateToWidth,
24
24
  visibleWidth,
25
- } from "@mariozechner/pi-tui";
25
+ } from "@earendil-works/pi-tui";
26
26
  import { LIVE_STATUSES, type ProcessInfo } from "../constants";
27
27
  import type { ProcessManager } from "../manager";
28
28
  import { LogFileViewer } from "./log-file-viewer";
@@ -0,0 +1,37 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
+
4
+ export function createPanelPadder(width: number): (content: string) => string {
5
+ const innerWidth = Math.max(0, width - 2);
6
+
7
+ return (content: string): string => {
8
+ const truncated = truncateToWidth(content, innerWidth, "", true);
9
+ const padding = " ".repeat(
10
+ Math.max(0, innerWidth - visibleWidth(truncated)),
11
+ );
12
+ return `│${truncated}${padding}│`;
13
+ };
14
+ }
15
+
16
+ export function renderPanelRule(width: number, theme: Theme): string {
17
+ if (width <= 0) return "";
18
+ if (width === 1) return theme.fg("dim", "─");
19
+
20
+ return theme.fg("dim", `├${"─".repeat(Math.max(0, width - 2))}┤`);
21
+ }
22
+
23
+ export function renderPanelTitleLine(
24
+ title: string,
25
+ width: number,
26
+ theme: Theme,
27
+ ): string {
28
+ if (width <= 0) return "";
29
+ if (width === 1) return theme.fg("dim", "─");
30
+
31
+ const innerWidth = Math.max(0, width - 2);
32
+ const label = ` ${title} `;
33
+ const truncated = truncateToWidth(label, innerWidth, "", true);
34
+ const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
35
+
36
+ return theme.fg("dim", `┌${truncated}${"─".repeat(remaining)}┐`);
37
+ }
@@ -1,17 +1,17 @@
1
- import {
2
- createPanelPadder,
3
- renderPanelRule,
4
- renderPanelTitleLine,
5
- } from "@aliou/pi-utils-ui";
6
- import type { Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
7
2
  import {
8
3
  type Component,
9
4
  matchesKey,
10
5
  truncateToWidth,
11
6
  visibleWidth,
12
- } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-tui";
13
8
  import type { ProcessInfo } from "../constants";
14
9
  import type { ProcessManager } from "../manager";
10
+ import {
11
+ createPanelPadder,
12
+ renderPanelRule,
13
+ renderPanelTitleLine,
14
+ } from "./panel-helpers";
15
15
  import { statusIcon, statusLabel } from "./status-format";
16
16
 
17
17
  /**
@@ -121,7 +121,7 @@ export class ProcessPickerComponent implements Component {
121
121
  const padLine = (content: string): string =>
122
122
  basePadLine(
123
123
  visibleWidth(content) > innerWidth
124
- ? truncateToWidth(content, innerWidth)
124
+ ? truncateToWidth(content, innerWidth, "", true)
125
125
  : content,
126
126
  );
127
127
 
@@ -151,7 +151,6 @@ export class ProcessPickerComponent implements Component {
151
151
  lines.push(padLine(""));
152
152
  }
153
153
 
154
- // Footer
155
154
  lines.push(renderPanelRule(width, theme));
156
155
  lines.push(
157
156
  padLine(
@@ -1,19 +1,19 @@
1
- import {
2
- createPanelPadder,
3
- renderPanelRule,
4
- renderPanelTitleLine,
5
- } from "@aliou/pi-utils-ui";
6
- import type { Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
7
2
  import {
8
3
  type Component,
9
4
  matchesKey,
10
5
  truncateToWidth,
11
6
  visibleWidth,
12
- } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-tui";
13
8
  import { configLoader } from "../config";
14
9
  import type { ProcessInfo } from "../constants";
15
10
  import type { ProcessManager } from "../manager";
16
11
  import { stripAnsi } from "../utils";
12
+ import {
13
+ createPanelPadder,
14
+ renderPanelRule,
15
+ renderPanelTitleLine,
16
+ } from "./panel-helpers";
17
17
  import { statusIcon, statusLabel } from "./status-format";
18
18
 
19
19
  function formatRuntime(startTime: number, endTime: number | null): string {
@@ -236,7 +236,7 @@ export class ProcessesComponent implements Component {
236
236
  const padLine = (content: string): string =>
237
237
  basePadLine(
238
238
  visibleWidth(content) > innerWidth
239
- ? truncateToWidth(content, innerWidth)
239
+ ? truncateToWidth(content, innerWidth, "", true)
240
240
  : content,
241
241
  );
242
242
 
@@ -417,8 +417,6 @@ export class ProcessesComponent implements Component {
417
417
  }
418
418
  }
419
419
 
420
- lines.push(renderPanelRule(width, theme));
421
-
422
420
  const footerLeft =
423
421
  `${dim("enter")} stream ` +
424
422
  `${dim("j/k")} select ` +
@@ -448,6 +446,7 @@ export class ProcessesComponent implements Component {
448
446
  footer = truncateToWidth(footer, innerWidth);
449
447
  }
450
448
 
449
+ lines.push(renderPanelRule(width, theme));
451
450
  lines.push(padLine(footer));
452
451
 
453
452
  this.cachedLines = lines;
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { parse } from "@aliou/sh";
9
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
10
  import { walkCommands, wordToString } from "../utils/shell-utils";
11
11
 
12
12
  const BACKGROUND_CMD_NAMES = new Set(["nohup", "disown", "setsid"]);
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProcessManager } from "../manager";
3
3
 
4
4
  export function setupCleanupHook(pi: ExtensionAPI, manager: ProcessManager) {
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { ResolvedProcessesConfig } from "../config";
3
3
  import type { ProcessManager } from "../manager";
4
4
  import { setupBackgroundBlocker } from "./background-blocker";
@@ -2,8 +2,8 @@ import type {
2
2
  ExtensionAPI,
3
3
  MessageRenderOptions,
4
4
  Theme,
5
- } from "@mariozechner/pi-coding-agent";
6
- import { Text } from "@mariozechner/pi-tui";
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import { Text } from "@earendil-works/pi-tui";
7
7
  import { MESSAGE_TYPE_PROCESS_UPDATE } from "../constants";
8
8
 
9
9
  interface ProcessLifecycleDetails {
@@ -1,7 +1,8 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { MESSAGE_TYPE_PROCESS_UPDATE, type ProcessInfo } from "../constants";
3
3
  import type { ProcessManager } from "../manager";
4
4
  import { formatRuntime } from "../utils";
5
+ import { safeSendMessage } from "./utils";
5
6
 
6
7
  interface ProcessUpdateDetails {
7
8
  kind: "lifecycle";
@@ -54,7 +55,8 @@ export function setupProcessEndHook(pi: ExtensionAPI, manager: ProcessManager) {
54
55
  runtime,
55
56
  };
56
57
 
57
- pi.sendMessage(
58
+ safeSendMessage(
59
+ pi,
58
60
  {
59
61
  customType: MESSAGE_TYPE_PROCESS_UPDATE,
60
62
  content: message,
@@ -1,6 +1,7 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { MESSAGE_TYPE_PROCESS_UPDATE } from "../constants";
3
3
  import type { ProcessManager } from "../manager";
4
+ import { safeSendMessage } from "./utils";
4
5
 
5
6
  interface ProcessWatchUpdateDetails {
6
7
  kind: "watch_matched";
@@ -70,7 +71,8 @@ export function setupProcessWatchHook(
70
71
  }
71
72
  }
72
73
 
73
- pi.sendMessage(
74
+ safeSendMessage(
75
+ pi,
74
76
  {
75
77
  customType: MESSAGE_TYPE_PROCESS_UPDATE,
76
78
  content: message,
@@ -0,0 +1,23 @@
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,7 +1,7 @@
1
1
  import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
- } from "@mariozechner/pi-coding-agent";
4
+ } from "@earendil-works/pi-coding-agent";
5
5
  import { LogDockComponent } from "../../components/log-dock-component";
6
6
  import { configLoader, type ResolvedProcessesConfig } from "../../config";
7
7
  import { LIVE_STATUSES } from "../../constants";
@@ -1,5 +1,5 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
- import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
3
  import type { ProcessInfo } from "../../constants";
4
4
 
5
5
  function formatProcessStatus(
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { setupProcessesCommands } from "./commands";
3
3
  import { registerProcessesSettings } from "./commands/settings";
4
4
  import { configLoader } from "./config";
package/src/manager.ts CHANGED
@@ -750,9 +750,9 @@ export class ProcessManager {
750
750
  }
751
751
 
752
752
  export type {
753
+ KillResult,
754
+ ManagerEvent,
753
755
  ProcessInfo,
754
756
  ProcessStatus,
755
- ManagerEvent,
756
- KillResult,
757
757
  WriteResult,
758
758
  };
@@ -1,5 +1,5 @@
1
1
  import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@mariozechner/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { ExecuteResult, ProcessInfo } from "../../constants";
4
4
 
5
5
  interface DebugParams {
@@ -4,8 +4,8 @@ import type {
4
4
  ExtensionContext,
5
5
  Theme,
6
6
  ToolRenderResultOptions,
7
- } from "@mariozechner/pi-coding-agent";
8
- import type { Component } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import type { Component } from "@earendil-works/pi-tui";
9
9
  import type {
10
10
  ExecuteResult,
11
11
  ProcessAction,
@@ -1,5 +1,5 @@
1
1
  import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@mariozechner/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { ExecuteResult } from "../../constants";
4
4
  import type { ProcessManager } from "../../manager";
5
5
 
@@ -3,8 +3,8 @@ import type {
3
3
  AgentToolResult,
4
4
  Theme,
5
5
  ToolRenderResultOptions,
6
- } from "@mariozechner/pi-coding-agent";
7
- import { Text } from "@mariozechner/pi-tui";
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Text } from "@earendil-works/pi-tui";
8
8
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
9
9
  import type { ProcessManager } from "../../manager";
10
10
  import {
@@ -3,8 +3,8 @@ import type {
3
3
  AgentToolResult,
4
4
  Theme,
5
5
  ToolRenderResultOptions,
6
- } from "@mariozechner/pi-coding-agent";
7
- import { Text } from "@mariozechner/pi-tui";
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Text } from "@earendil-works/pi-tui";
8
8
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
9
9
  import type { ProcessManager } from "../../manager";
10
10
 
@@ -3,8 +3,8 @@ import type {
3
3
  AgentToolResult,
4
4
  Theme,
5
5
  ToolRenderResultOptions,
6
- } from "@mariozechner/pi-coding-agent";
7
- import { Text } from "@mariozechner/pi-tui";
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Text } from "@earendil-works/pi-tui";
8
8
  import { configLoader } from "../../config";
9
9
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
10
10
  import type { ProcessManager } from "../../manager";
@@ -4,8 +4,8 @@ import type {
4
4
  ExtensionContext,
5
5
  Theme,
6
6
  ToolRenderResultOptions,
7
- } from "@mariozechner/pi-coding-agent";
8
- import { Text } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import { Text } from "@earendil-works/pi-tui";
9
9
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
10
10
  import type { ProcessManager } from "../../manager";
11
11
 
@@ -1,5 +1,5 @@
1
1
  import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@mariozechner/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { ExecuteResult } from "../../constants";
4
4
  import type { ProcessManager } from "../../manager";
5
5
 
@@ -1,12 +1,12 @@
1
1
  import { ToolBody } from "@aliou/pi-utils-ui";
2
- import { StringEnum } from "@mariozechner/pi-ai";
2
+ import { StringEnum } from "@earendil-works/pi-ai";
3
3
  import type {
4
4
  AgentToolResult,
5
5
  ExtensionAPI,
6
6
  Theme,
7
7
  ToolRenderResultOptions,
8
- } from "@mariozechner/pi-coding-agent";
9
- import { Text } from "@mariozechner/pi-tui";
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { Text } from "@earendil-works/pi-tui";
10
10
  import { type Static, Type } from "typebox";
11
11
  import type { ProcessesDetails } from "../constants";
12
12
  import type { ProcessManager } from "../manager";
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { stripAnsi } from "./ansi";
3
+
4
+ describe("stripAnsi", () => {
5
+ it("strips CSI styling sequences", () => {
6
+ expect(stripAnsi("\u001b[31mred\u001b[0m")).toBe("red");
7
+ });
8
+
9
+ it("strips generic OSC sequences", () => {
10
+ expect(stripAnsi("\u001b]0;title\u0007hello")).toBe("hello");
11
+ });
12
+
13
+ it("strips carriage returns and other control chars that can corrupt TUI rendering", () => {
14
+ expect(stripAnsi("step 1\rstep 2\b\b done")).toBe("step 1step 2 done");
15
+ expect(stripAnsi("null\u0000byte")).toBe("nullbyte");
16
+ expect(stripAnsi("delete\u007fchar")).toBe("deletechar");
17
+ });
18
+
19
+ it("preserves tabs and newlines", () => {
20
+ expect(stripAnsi("one\ttwo\nthree")).toBe("one\ttwo\nthree");
21
+ });
22
+ });
package/src/utils/ansi.ts CHANGED
@@ -1,36 +1,45 @@
1
1
  /**
2
- * Strip ANSI escape codes from a string.
2
+ * Strip ANSI escape codes and other terminal control characters from a string.
3
3
  *
4
4
  * Removes:
5
5
  * - All CSI sequences (\x1b[...X) - SGR, cursor movement, erase, scroll, etc.
6
- * - OSC 8 hyperlinks (\x1b]8;;URL\x07)
6
+ * - OSC sequences (\x1b]...\x07 or \x1b]...\x1b\\)
7
7
  * - APC sequences (\x1b_...\x07 or \x1b_...\x1b\\)
8
+ * - Remaining C0 control chars except tab/newline
8
9
  */
10
+ const ESC = String.fromCodePoint(0x001b);
11
+ const BEL = String.fromCodePoint(0x0007);
12
+
13
+ const ANSI_REPLACEMENTS: RegExp[] = [
14
+ // CSI sequences: SGR, cursor movement, erase, scroll, etc.
15
+ new RegExp(`${ESC}\\[[0-9;]*[A-Za-z]`, "gu"),
16
+ // OSC sequences: ESC]...<BEL> or ESC]...<ESC>\\.
17
+ new RegExp(`${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "gu"),
18
+ // APC sequences: ESC_...<BEL> or ESC_...<ESC>\\.
19
+ new RegExp(`${ESC}_[^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "gu"),
20
+ ];
21
+
22
+ // Strip C0 terminal control characters that can corrupt TUI layout when
23
+ // rendered back into pi, such as carriage return and backspace. Keep tabs and
24
+ // newlines because logs use them as printable whitespace/line breaks.
25
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: this regex intentionally targets terminal control characters.
26
+ const TERMINAL_CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f]/gu;
27
+
9
28
  /**
10
29
  * Check if a string contains ANSI escape codes.
11
30
  */
12
31
  export function hasAnsi(str: string): boolean {
13
- return str.includes(String.fromCodePoint(0x001b));
32
+ return str.includes(ESC);
14
33
  }
15
34
 
16
35
  export function stripAnsi(str: string): string {
17
- // ESC = \u001b, BEL = \u0007
18
- const ESC = String.fromCodePoint(0x001b);
19
- const BEL = String.fromCodePoint(0x0007);
36
+ let clean = str;
20
37
 
21
- if (!str.includes(ESC)) {
22
- return str;
38
+ if (str.includes(ESC)) {
39
+ for (const pattern of ANSI_REPLACEMENTS) {
40
+ clean = clean.replace(pattern, "");
41
+ }
23
42
  }
24
43
 
25
- // Strip all CSI sequences (ESC[...X where X is any letter)
26
- let clean = str.replace(new RegExp(`${ESC}\\[[0-9;]*[A-Za-z]`, "gu"), "");
27
- // Strip OSC 8 hyperlinks: ESC]8;;URL<BEL> and ESC]8;;<BEL>
28
- clean = clean.replace(new RegExp(`${ESC}\\]8;;[^${BEL}]*${BEL}`, "gu"), "");
29
- // Strip APC sequences: ESC_...<BEL> or ESC_...<ESC>\\ (used for cursor marker)
30
- clean = clean.replace(
31
- new RegExp(`${ESC}_[^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "gu"),
32
- "",
33
- );
34
-
35
- return clean;
44
+ return clean.replace(TERMINAL_CONTROL_CHARS, "");
36
45
  }
@@ -1,4 +1,4 @@
1
- import type { Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProcessInfo } from "../constants";
3
3
 
4
4
  export function formatRuntime(