@aliou/pi-processes 0.10.4 → 0.10.5

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.
@@ -2,11 +2,17 @@ import type {
2
2
  ExtensionAPI,
3
3
  MessageRenderOptions,
4
4
  Theme,
5
+ ThemeColor,
5
6
  } from "@earendil-works/pi-coding-agent";
6
7
  import { type Component, Container, Text } from "@earendil-works/pi-tui";
7
8
  import { truncateForDisplay } from "../shared/display-text";
8
9
  import { MESSAGE_TYPE_PROCESS_NOTIFICATION } from "./constants";
9
- import type { ProcessNotificationDetails } from "./notifications/types";
10
+ import type {
11
+ Attention,
12
+ ProcessNotificationDetails,
13
+ ProcessNotificationKind,
14
+ } from "./notifications/types";
15
+ import { formatPatternForDisplay, quoteFilter } from "./tools/components";
10
16
 
11
17
  export function registerProcessNotificationRenderer(pi: ExtensionAPI): void {
12
18
  pi.registerMessageRenderer<ProcessNotificationDetails>(
@@ -25,11 +31,17 @@ export function renderProcessNotificationMessage(
25
31
  return undefined;
26
32
  }
27
33
 
34
+ // outputPad is only populated on Pi 0.82.1+ (see MessageRenderOptions);
35
+ // fall back to 0 so this renderer doesn't break on older Pi builds.
36
+ const outputPad = options.outputPad ?? 0;
37
+
28
38
  const container = new Container();
29
- container.addChild(new Text(formatSummary(details, theme), 0, 0));
39
+ container.addChild(new Text(formatSummary(details, theme), outputPad, 0));
30
40
 
31
41
  if (options.expanded) {
32
- container.addChild(new Text(formatDetailLine(details, theme), 0, 1));
42
+ container.addChild(
43
+ new Text(formatDetailLine(details, theme), outputPad, 0),
44
+ );
33
45
  }
34
46
 
35
47
  return container;
@@ -66,17 +78,100 @@ function formatDetailLine(
66
78
  details: ProcessNotificationDetails,
67
79
  theme: Theme,
68
80
  ): string {
69
- const parts = [
70
- `id: ${details.processId}`,
71
- `attention: ${details.attention}`,
72
- details.status ? `status: ${details.status}` : null,
73
- details.exitCode === undefined || details.exitCode === null
74
- ? null
75
- : `exit: ${details.exitCode}`,
76
- details.endReason ? `reason: ${details.endReason}` : null,
77
- ].filter(Boolean);
78
-
79
- return theme.fg("muted", parts.join(" "));
81
+ const parts: string[] = [
82
+ field("id", details.processId, theme),
83
+ field("notify", formatAttention(details.attention, theme), theme),
84
+ ];
85
+
86
+ if (details.logMatch) {
87
+ const pattern = quoteFilter(
88
+ formatPatternForDisplay(details.logMatch.pattern),
89
+ details.logMatch.mode,
90
+ );
91
+ parts.push(
92
+ field("pattern", theme.fg("accent", pattern), theme),
93
+ field("stream", theme.fg("muted", `[${details.logMatch.stream}]`), theme),
94
+ );
95
+ } else {
96
+ if (details.status) {
97
+ parts.push(
98
+ field(
99
+ "status",
100
+ theme.fg(statusTone(details.kind), details.status),
101
+ theme,
102
+ ),
103
+ );
104
+ }
105
+
106
+ if (details.exitCode !== undefined && details.exitCode !== null) {
107
+ parts.push(
108
+ field(
109
+ "exit",
110
+ theme.fg(statusTone(details.kind), String(details.exitCode)),
111
+ theme,
112
+ ),
113
+ );
114
+ }
115
+
116
+ if (details.endReason) {
117
+ parts.push(field("reason", theme.fg("muted", details.endReason), theme));
118
+ }
119
+
120
+ if (details.signal) {
121
+ const number =
122
+ details.signal.number === null
123
+ ? ""
124
+ : ` (${details.signal.number}, ${details.signal.description})`;
125
+ parts.push(
126
+ field(
127
+ "signal",
128
+ theme.fg("muted", `${details.signal.name}${number}`),
129
+ theme,
130
+ ),
131
+ );
132
+ }
133
+ }
134
+
135
+ return parts.join(" ");
136
+ }
137
+
138
+ /** `label:` in muted, matching `buildField` in tools/utils.ts. */
139
+ function field(label: string, value: string, theme: Theme): string {
140
+ return `${theme.fg("muted", `${label}:`)} ${value}`;
141
+ }
142
+
143
+ /**
144
+ * Plain-language attention level, reusing the tone mapping from
145
+ * `attentionTone` in tools/components/watch.ts (turn/context keep their
146
+ * color; ignore switches from muted to dim to read as "quieter").
147
+ */
148
+ function formatAttention(attention: Attention, theme: Theme): string {
149
+ switch (attention) {
150
+ case "turn":
151
+ return theme.bold(theme.fg("warning", "now"));
152
+ case "context":
153
+ return theme.bold(theme.fg("success", "next turn"));
154
+ default:
155
+ return theme.bold(theme.fg("dim", "silent"));
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Same tone family as `statusColor` in shared/ui.ts: success for a clean
161
+ * exit, error for failure/crash, dim for killed.
162
+ */
163
+ function statusTone(kind: ProcessNotificationKind): ThemeColor {
164
+ switch (kind) {
165
+ case "success":
166
+ return "success";
167
+ case "failure":
168
+ case "crash":
169
+ return "error";
170
+ case "killed":
171
+ return "dim";
172
+ default:
173
+ return "muted";
174
+ }
80
175
  }
81
176
 
82
177
  function summaryVerb(details: ProcessNotificationDetails): string {
@@ -1,4 +1,8 @@
1
1
  export { ProcessActionHeader } from "./process-action-header";
2
2
  export { ProcessActionTitle } from "./process-action-title";
3
3
  export { ToolLayout } from "./tool-layout";
4
- export { buildMatcherLine, formatPatternForDisplay } from "./watch";
4
+ export {
5
+ buildMatcherLine,
6
+ formatPatternForDisplay,
7
+ quoteFilter,
8
+ } from "./watch";
@@ -15,6 +15,8 @@ export function buildMatcherLine(
15
15
  const attentionTone =
16
16
  on === "turn" ? "warning" : on === "context" ? "success" : "muted";
17
17
 
18
+ const pattern = quoteFilter(formatPatternForDisplay(matcher.pattern), mode);
19
+
18
20
  return new Text(
19
21
  [
20
22
  prefix,
@@ -22,13 +24,22 @@ export function buildMatcherLine(
22
24
  " ",
23
25
  theme.bold(theme.fg(attentionTone, `${on}${repeat}`)),
24
26
  " ",
25
- theme.fg("accent", formatPatternForDisplay(matcher.pattern)),
27
+ theme.fg("accent", pattern),
26
28
  ].join(""),
27
29
  0,
28
30
  0,
29
31
  );
30
32
  }
31
33
 
34
+ export function quoteFilter(
35
+ pattern: string,
36
+ mode: "literal" | "regex",
37
+ ): string {
38
+ if (mode === "regex") return `/${pattern}/`;
39
+ if (pattern.includes('"')) return `'${pattern}'`;
40
+ return `"${pattern}"`;
41
+ }
42
+
32
43
  export function formatPatternForDisplay(pattern: string): string {
33
44
  let formatted = "";
34
45
 
@@ -2,7 +2,8 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { Container, Spacer, Text } from "@earendil-works/pi-tui";
3
3
 
4
4
  import { sanitizeForDisplay } from "../../../shared/display-text";
5
- import { ProcessActionHeader } from "../components";
5
+ import { truncateToWidth } from "../../../shared/truncate";
6
+ import { ProcessActionHeader, quoteFilter } from "../components";
6
7
  import type { ProcessesParamsType } from "../schema";
7
8
  import { buildField } from "../utils";
8
9
  import type { OutputDetails } from ".";
@@ -73,9 +74,23 @@ export function buildCollapsed(
73
74
  ),
74
75
  );
75
76
 
77
+ if (details.pattern) {
78
+ const quoted = quoteFilter(details.pattern, details.mode);
79
+ const value = truncateToWidth(sanitizeForDisplay(quoted), 80);
80
+ container.addChild(
81
+ new Text(
82
+ `${theme.fg("muted", "filter:")} ${theme.fg("accent", value)}`,
83
+ 0,
84
+ 0,
85
+ ),
86
+ );
87
+ }
88
+
76
89
  const bodyLines = extractOutputBody(contentText, details);
77
90
  const preview = bodyLines.slice(-2).map(sanitizeForDisplay).join("\n");
78
91
 
92
+ container.addChild(new Spacer(1));
93
+
79
94
  if (preview) {
80
95
  container.addChild(new Text(theme.fg("muted", preview), 0, 0));
81
96
  } else {
@@ -119,9 +134,8 @@ function buildOutputMeta(details: OutputDetails, theme: Theme): Container {
119
134
  container.addChild(buildField("stream", details.stream, theme));
120
135
 
121
136
  if (details.pattern) {
122
- const modeTag = details.mode === "regex" ? " (regex)" : "";
123
137
  container.addChild(
124
- buildField("filter", `${details.pattern}${modeTag}`, theme),
138
+ buildField("filter", quoteFilter(details.pattern, details.mode), theme),
125
139
  );
126
140
  }
127
141
 
@@ -3,9 +3,9 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import { type Component, visibleWidth } from "@earendil-works/pi-tui";
4
4
  import type { ProcessInfo } from "../../../src/types";
5
5
  import { LIVE_STATUSES } from "../../../src/types";
6
- import { renderProcessTab } from "../../process-tabs";
7
6
  import { sanitizeForDisplay } from "../../shared/display-text";
8
7
  import { displayTextOf, renderLogLine } from "../../shared/log-line";
8
+ import { renderProcessTab } from "../../shared/process-tabs";
9
9
  import { truncateToWidth } from "../../shared/truncate";
10
10
  import {
11
11
  clampNameColumn,
@@ -17,8 +17,8 @@ import {
17
17
  import { LIVE_STATUSES, type ProcessInfo } from "../../../src/types";
18
18
  import { formatRuntime } from "../../../src/utils/format";
19
19
  import { isRecord } from "../../../src/utils/is-record";
20
- import { renderProcessTab } from "../../process-tabs";
21
20
  import { truncateForDisplay } from "../../shared/display-text";
21
+ import { renderProcessTab } from "../../shared/process-tabs";
22
22
  import { truncateToWidth } from "../../shared/truncate";
23
23
  import { LineComponent, LinesComponent, RuleComponent } from "../../shared/ui";
24
24
  import { requestProcessList } from "../client";
@@ -1,8 +1,8 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
 
3
- import type { ProcessInfo } from "../src/types";
4
- import { truncateForDisplay } from "./shared/display-text";
5
- import { MAX_TAB_NAME, statusDot } from "./shared/ui";
3
+ import type { ProcessInfo } from "../../src/types";
4
+ import { truncateForDisplay } from "./display-text";
5
+ import { MAX_TAB_NAME, statusDot } from "./ui";
6
6
 
7
7
  export { MAX_TAB_NAME, statusDot as renderProcessTabDot };
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliou/pi-processes",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "private": false,
@@ -23,7 +23,8 @@
23
23
  "skills": [
24
24
  "./skills/pi-processes"
25
25
  ],
26
- "video": "https://assets.aliou.me/pi-extensions/demos/pi-processes.mp4"
26
+ "image": "https://assets.aliou.me/github/aliou/pi-processes/banner-v0.10.x.png",
27
+ "video": "https://assets.aliou.me/pi-extensions/demos/processes/v0.10.0/agent-starts-processes.mp4"
27
28
  },
28
29
  "publishConfig": {
29
30
  "access": "public"
@@ -56,9 +57,9 @@
56
57
  "@aliou/biome-plugins": "^0.8.1",
57
58
  "@biomejs/biome": "^2.4.15",
58
59
  "@changesets/cli": "^2.27.11",
59
- "@earendil-works/pi-ai": "0.80.3",
60
- "@earendil-works/pi-coding-agent": "0.80.3",
61
- "@earendil-works/pi-tui": "0.80.3",
60
+ "@earendil-works/pi-ai": "0.84.1",
61
+ "@earendil-works/pi-coding-agent": "0.84.1",
62
+ "@earendil-works/pi-tui": "0.84.1",
62
63
  "@golevelup/ts-vitest": "^4.0.0",
63
64
  "@types/node": "^25.0.10",
64
65
  "husky": "^9.1.7",
@@ -66,20 +67,24 @@
66
67
  "ts-json-schema-generator": "^2.9.0",
67
68
  "typebox": "1.1.38",
68
69
  "typescript": "^5.9.3",
69
- "vitest": "^4.1.5"
70
+ "vitest": "^4.1.5",
71
+ "vitest-evals": "0.15.0"
70
72
  },
71
73
  "scripts": {
72
74
  "typecheck": "tsc --noEmit",
75
+ "typecheck:evals": "tsc --noEmit -p tests/evals/tsconfig.json",
73
76
  "lint": "biome check",
74
77
  "format": "biome check --write",
75
78
  "gen:schema": "ts-json-schema-generator --path extensions/processes/config/types.ts --type ProcessConfig --no-type-check -o schema.json",
76
79
  "check:schema": "ts-json-schema-generator --path extensions/processes/config/types.ts --type ProcessConfig --no-type-check -o /tmp/pi-processes-schema-check.json && diff -q schema.json /tmp/pi-processes-schema-check.json",
80
+ "check:changesets": "node scripts/check-changesets.ts",
77
81
  "check:lockfile": "pnpm install --frozen-lockfile --ignore-scripts",
78
82
  "prepare": "[ -d .git ] && husky || true",
79
83
  "changeset": "changeset",
80
84
  "version": "changeset version",
81
85
  "test": "vitest run",
82
86
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
87
+ "test:evals": "vitest run --config tests/evals/vitest.config.ts",
83
88
  "release": "pnpm changeset publish"
84
89
  },
85
90
  "packageManager": "pnpm@11.5.1",