@aliou/pi-processes 0.10.3 → 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,5 @@
1
1
  import { compileLineMatcher } from "../../../src/utils/match-line";
2
+ import { plainTextForDisplay } from "../../shared/display-text";
2
3
  import type { NotifyConfig } from "./registry";
3
4
  import type { Attention } from "./types";
4
5
 
@@ -130,8 +131,12 @@ function splitAppendedIntoLines(
130
131
  for (const entry of appended) {
131
132
  const split = entry.text.split("\n");
132
133
  for (const line of split) {
133
- if (line.length === 0) continue;
134
- lines.push({ type: entry.type, text: line });
134
+ // Match the same per-line visible text the log views render. This keeps
135
+ // CR-overwritten progress and invisible escape bytes from firing
136
+ // watches, without making watches depend on viewport width.
137
+ const displayLine = plainTextForDisplay(line);
138
+ if (displayLine.length === 0) continue;
139
+ lines.push({ type: entry.type, text: displayLine });
135
140
  }
136
141
  }
137
142
 
@@ -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,
@@ -1,8 +1,15 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { visibleWidth } from "@earendil-works/pi-tui";
3
- import { sanitizeForDisplay } from "../../shared/display-text";
3
+ import {
4
+ plainTextForDisplay,
5
+ sanitizeForDisplay,
6
+ } from "../../shared/display-text";
4
7
  import { trimToBudget } from "../../shared/line-buffer";
5
- import { type LogLineEmphasis, renderLogLine } from "../../shared/log-line";
8
+ import {
9
+ displayTextOf,
10
+ type LogLineEmphasis,
11
+ renderLogLine,
12
+ } from "../../shared/log-line";
6
13
  import { truncateToWidth } from "../../shared/truncate";
7
14
  import type { ProcessLogLine } from "../logs-client";
8
15
 
@@ -122,8 +129,9 @@ export class LogFileViewer {
122
129
  * priority (search current match > search match > notify match > stream).
123
130
  */
124
131
  addNotifyMatch(match: { line: string }): void {
125
- // Stored sanitized so it can be compared against sanitized buffer lines.
126
- const line = sanitizeForDisplay(match.line);
132
+ // Stored as plain visible text so invisible escape bytes cannot be what
133
+ // makes a notification marker match.
134
+ const line = plainTextForDisplay(match.line);
127
135
  if (line) this.notifyLines.add(line);
128
136
  }
129
137
 
@@ -192,7 +200,8 @@ export class LogFileViewer {
192
200
  ? "search-current"
193
201
  : matchSet.has(visibleIndex)
194
202
  ? "search"
195
- : this.notifyLines.has(line.text)
203
+ : this.notifyLines.has(line.text) ||
204
+ this.notifyLines.has(displayTextOf(line))
196
205
  ? "notify"
197
206
  : "none";
198
207
  return renderLogLine(line, { theme: this.theme, width, emphasis });
@@ -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";
@@ -13,10 +13,17 @@ const ESC = String.fromCodePoint(0x001b);
13
13
  const BEL = String.fromCodePoint(0x0007);
14
14
  const ST = String.fromCodePoint(0x009c);
15
15
  const RESET = `${ESC}[0m`;
16
+ const C1_DCS = String.fromCodePoint(0x0090);
17
+ const C1_SOS = String.fromCodePoint(0x0098);
18
+ const C1_OSC = String.fromCodePoint(0x009d);
19
+ const C1_PM = String.fromCodePoint(0x009e);
20
+ const C1_APC = String.fromCodePoint(0x009f);
21
+ const C1_STRING_INTRODUCERS = [C1_DCS, C1_SOS, C1_OSC, C1_PM, C1_APC];
16
22
 
17
- // Control characters a single display row must never contain. Tabs are handled
18
- // separately; newlines are dropped because they would shift the whole frame.
19
- // C1 controls are included: a raw \u009b is an alias for CSI on some terminals.
23
+ // Control characters a single display row must never contain. Tabs and
24
+ // carriage returns are handled separately; newlines are dropped because they
25
+ // would shift the whole frame. C1 controls are included: a raw \u009b is an
26
+ // alias for CSI on some terminals.
20
27
  // biome-ignore lint/suspicious/noControlCharactersInRegex: this regex intentionally targets terminal control characters.
21
28
  const DISPLAY_CONTROL_CHARS = /[\u0000-\u0008\u000a-\u001f\u007f-\u009f]/gu;
22
29
 
@@ -41,7 +48,9 @@ const TAB_WIDTH = 8;
41
48
  * into the rest of the frame.
42
49
  */
43
50
  export function sanitizeForDisplay(text: string): string {
44
- if (!text.includes(ESC)) return cleanPlainText(text, 0).text;
51
+ if (!text.includes(ESC) && !hasC1StringIntroducer(text)) {
52
+ return cleanPlainText(text, 0).text;
53
+ }
45
54
 
46
55
  let out = "";
47
56
  let cursor = 0;
@@ -49,21 +58,34 @@ export function sanitizeForDisplay(text: string): string {
49
58
  let keptSgr = false;
50
59
 
51
60
  while (cursor < text.length) {
52
- const escapeAt = text.indexOf(ESC, cursor);
53
- if (escapeAt === -1) {
54
- out += cleanPlainText(text.slice(cursor), column).text;
61
+ const sequenceAt = findNextSequenceStart(text, cursor);
62
+ if (sequenceAt === -1) {
63
+ const chunk = cleanPlainText(text.slice(cursor), column);
64
+ if (chunk.resetsRow) {
65
+ out = "";
66
+ keptSgr = false;
67
+ }
68
+ out += chunk.text;
55
69
  break;
56
70
  }
57
- const chunk = cleanPlainText(text.slice(cursor, escapeAt), column);
71
+ const chunk = cleanPlainText(text.slice(cursor, sequenceAt), column);
72
+ if (chunk.resetsRow) {
73
+ out = "";
74
+ keptSgr = false;
75
+ }
58
76
  out += chunk.text;
59
77
  column = chunk.column;
60
78
 
61
- const sequence = readEscapeSequence(text, escapeAt);
62
- if (sequence.isSgr) {
63
- out += text.slice(escapeAt, sequence.end);
64
- keptSgr = true;
79
+ if (text[sequenceAt] === ESC) {
80
+ const sequence = readEscapeSequence(text, sequenceAt);
81
+ if (sequence.isSgr) {
82
+ out += text.slice(sequenceAt, sequence.end);
83
+ keptSgr = true;
84
+ }
85
+ cursor = sequence.end;
86
+ } else {
87
+ cursor = readC1StringSequence(text, sequenceAt).end;
65
88
  }
66
- cursor = sequence.end;
67
89
  }
68
90
 
69
91
  if (!keptSgr || out.endsWith(RESET)) return out;
@@ -79,6 +101,22 @@ export function truncateForDisplay(text: string, width: number): string {
79
101
  return closeSgr(truncateToWidth(sanitizeForDisplay(text), width, "…"));
80
102
  }
81
103
 
104
+ /**
105
+ * Return the unstyled text a user can actually read after terminal controls
106
+ * have been interpreted/dropped. Use this for comparisons such as search,
107
+ * notify markers, and log watches so invisible escape bytes cannot match.
108
+ */
109
+ export function plainTextForDisplay(text: string): string {
110
+ return stripSgr(sanitizeForDisplay(text));
111
+ }
112
+
113
+ const SGR = new RegExp(`${ESC}\\[[0-9;:]*m`, "gu");
114
+
115
+ /** Drop the SGR sequences `sanitizeForDisplay` kept for rendering colors. */
116
+ export function stripSgr(text: string): string {
117
+ return text.replace(SGR, "");
118
+ }
119
+
82
120
  /**
83
121
  * Re-close colors after truncation. `sanitizeForDisplay` ends a colored string
84
122
  * with a reset, but truncating can cut that reset off and let the color bleed
@@ -97,21 +135,42 @@ export function closeSgr(text: string): string {
97
135
  function cleanPlainText(
98
136
  text: string,
99
137
  column: number,
100
- ): { text: string; column: number } {
101
- const clean = text.replace(DISPLAY_CONTROL_CHARS, "");
138
+ ): { text: string; column: number; resetsRow: boolean } {
139
+ const carriageReturn = text.lastIndexOf("\r");
140
+ const resetsRow = carriageReturn !== -1;
141
+ const visibleText = resetsRow ? text.slice(carriageReturn + 1) : text;
142
+ const startColumn = resetsRow ? 0 : column;
143
+ const clean = visibleText.replace(DISPLAY_CONTROL_CHARS, "");
102
144
  if (!clean.includes("\t")) {
103
- return { text: clean, column: column + visibleWidth(clean) };
145
+ return {
146
+ text: clean,
147
+ column: startColumn + visibleWidth(clean),
148
+ resetsRow,
149
+ };
104
150
  }
105
151
 
106
152
  const parts = clean.split("\t");
107
153
  let out = parts[0] ?? "";
108
- let col = column + visibleWidth(out);
154
+ let col = startColumn + visibleWidth(out);
109
155
  for (const part of parts.slice(1)) {
110
156
  const spaces = TAB_WIDTH - (col % TAB_WIDTH);
111
157
  out += " ".repeat(spaces) + part;
112
158
  col += spaces + visibleWidth(part);
113
159
  }
114
- return { text: out, column: col };
160
+ return { text: out, column: col, resetsRow };
161
+ }
162
+
163
+ function findNextSequenceStart(text: string, cursor: number): number {
164
+ let next = text.indexOf(ESC, cursor);
165
+ for (const introducer of C1_STRING_INTRODUCERS) {
166
+ const index = text.indexOf(introducer, cursor);
167
+ if (index !== -1 && (next === -1 || index < next)) next = index;
168
+ }
169
+ return next;
170
+ }
171
+
172
+ function hasC1StringIntroducer(text: string): boolean {
173
+ return C1_STRING_INTRODUCERS.some((introducer) => text.includes(introducer));
115
174
  }
116
175
 
117
176
  /**
@@ -151,6 +210,12 @@ function readEscapeSequence(
151
210
  return { end: start + 2, isSgr: false };
152
211
  }
153
212
 
213
+ function readC1StringSequence(text: string, start: number): { end: number } {
214
+ const introducer = text[start];
215
+ const allowBel = introducer === C1_OSC || introducer === C1_APC;
216
+ return { end: findStringTerminator(text, start + 1, allowBel) };
217
+ }
218
+
154
219
  /** Index just past the terminator of a string sequence, else end of input. */
155
220
  function findStringTerminator(
156
221
  text: string,
@@ -10,7 +10,12 @@
10
10
  import type { Theme } from "@earendil-works/pi-coding-agent";
11
11
  import { visibleWidth } from "@earendil-works/pi-tui";
12
12
 
13
- import { closeSgr, sanitizeForDisplay } from "./display-text";
13
+ import {
14
+ closeSgr,
15
+ plainTextForDisplay,
16
+ sanitizeForDisplay,
17
+ stripSgr,
18
+ } from "./display-text";
14
19
  import { truncateToWidth } from "./truncate";
15
20
 
16
21
  export interface DisplayLogLine {
@@ -60,7 +65,7 @@ export function renderLogLine(
60
65
 
61
66
  /** Text of a log line as the views display it, for match comparisons. */
62
67
  export function displayTextOf(line: DisplayLogLine): string {
63
- return sanitizeForDisplay(line.text);
68
+ return plainTextForDisplay(line.text);
64
69
  }
65
70
 
66
71
  function toneLogText(
@@ -75,10 +80,3 @@ function toneLogText(
75
80
  if (type === "stderr") return theme.fg("warning", text);
76
81
  return text;
77
82
  }
78
-
79
- const SGR = new RegExp(`${String.fromCodePoint(0x001b)}\\[[0-9;:]*m`, "gu");
80
-
81
- /** Drop the SGR sequences `sanitizeForDisplay` kept. */
82
- function stripSgr(text: string): string {
83
- return text.replace(SGR, "");
84
- }
@@ -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.3",
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",