@cydm/pie 2.2.0 → 2.3.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.
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createAskUserCapability
4
- } from "../../../chunks/chunk-HH2Q2SNX.js";
4
+ } from "../../../chunks/chunk-UPPICLVS.js";
5
5
  import "../../../chunks/chunk-QJH6YN2W.js";
6
6
  import "../../../chunks/chunk-IM43UYVV.js";
7
7
  import "../../../chunks/chunk-XXGYUYXI.js";
@@ -3776,10 +3776,26 @@ function containsEditable(node) {
3776
3776
  if (node.canSetValue || node.role.toLowerCase().includes("text")) return true;
3777
3777
  return node.children.some(containsEditable);
3778
3778
  }
3779
+ function wireableInputDescendants(node) {
3780
+ const matches = [];
3781
+ const visit = (current) => {
3782
+ for (const child of current.children) {
3783
+ if (child.isTextInput && child.wireRef && !child.pictureOnly && !child.offscreen) matches.push(child);
3784
+ visit(child);
3785
+ }
3786
+ };
3787
+ visit(node);
3788
+ return matches;
3789
+ }
3779
3790
  function recordActionFocus(action, state, node, coordinateFocus = false) {
3780
3791
  if (!["click", "press", "keypress", "typeText"].includes(action.action) || !action.ref && action.action !== "click") return;
3781
3792
  state.currentFocus = Boolean(node?.isTextInput && !node.pictureOnly) || coordinateFocus;
3782
3793
  state.focusRef = node?.isTextInput && node.wireRef && !node.pictureOnly ? node.ref : void 0;
3794
+ if (!state.currentFocus && node && !node.pictureOnly && action.ref && (action.action === "click" || action.action === "press") && containsEditable(node)) {
3795
+ const wireable = wireableInputDescendants(node);
3796
+ state.currentFocus = true;
3797
+ state.focusRef = wireable.length === 1 ? wireable[0].ref : void 0;
3798
+ }
3783
3799
  }
3784
3800
  function editorAtPoint(nodes, x, y) {
3785
3801
  const matches = nodes.filter((node) => node.isTextInput && node.wireRef && !node.pictureOnly && !node.offscreen && node.rect && x >= node.rect.x && x < node.rect.x + node.rect.w && y >= node.rect.y && y < node.rect.y + node.rect.h);
@@ -3790,24 +3806,23 @@ function prepareAction(action, state, env) {
3790
3806
  const focusedKeyboard = !env.headless && state.currentFocus && !action.ref && (operation === "typeText" || operation === "keypress");
3791
3807
  const usesCurrentFocus = focusedKeyboard && !state.focusRef;
3792
3808
  const target = usesCurrentFocus ? focusedTarget(env) : nativeTarget(focusedKeyboard && state.focusRef ? { ...action, ref: state.focusRef } : action, operation, env);
3793
- const establishesFocus = !env.headless && Boolean(action.ref) && (operation === "click" || operation === "press") && containsEditable(env.node(action.ref));
3794
3809
  const needsForeground = !env.headless && (operation === "click" || operation === "press") && "x" in target;
3795
3810
  switch (operation) {
3796
3811
  case "press":
3797
3812
  case "click":
3798
- return { action: operation, target, params: { button: mouseButton(action.button), clickCount: clickCount(action.clickCount) }, establishesFocus, usesCurrentFocus: false, needsForeground };
3813
+ return { action: operation, target, params: { button: mouseButton(action.button), clickCount: clickCount(action.clickCount) }, usesCurrentFocus: false, needsForeground };
3799
3814
  case "setText":
3800
- return { action: operation, target, params: { text: action.text ?? "" }, establishesFocus: false, usesCurrentFocus: false, needsForeground: false };
3815
+ return { action: operation, target, params: { text: action.text ?? "" }, usesCurrentFocus: false, needsForeground: false };
3801
3816
  case "typeText":
3802
- return { action: operation, target, params: { text: action.text ?? "" }, establishesFocus: false, usesCurrentFocus, needsForeground: false };
3817
+ return { action: operation, target, params: { text: action.text ?? "" }, usesCurrentFocus, needsForeground: false };
3803
3818
  case "keypress":
3804
- return { action: operation, target, params: { keys: keys(action.keys) }, establishesFocus: false, usesCurrentFocus, needsForeground: false };
3819
+ return { action: operation, target, params: { keys: keys(action.keys) }, usesCurrentFocus, needsForeground: false };
3805
3820
  case "scroll":
3806
- return { action: operation, target, params: { scrollX: scrollDelta(action.scrollX), scrollY: scrollDelta(action.scrollY) }, establishesFocus: false, usesCurrentFocus: false, needsForeground: false };
3821
+ return { action: operation, target, params: { scrollX: scrollDelta(action.scrollX), scrollY: scrollDelta(action.scrollY) }, usesCurrentFocus: false, needsForeground: false };
3807
3822
  case "drag":
3808
- return { action: operation, target, params: { path: path(action.path, env) }, establishesFocus: false, usesCurrentFocus: false, needsForeground: false };
3823
+ return { action: operation, target, params: { path: path(action.path, env) }, usesCurrentFocus: false, needsForeground: false };
3809
3824
  case "moveMouse":
3810
- return { action: operation, target, params: {}, establishesFocus: false, usesCurrentFocus: false, needsForeground: false };
3825
+ return { action: operation, target, params: {}, usesCurrentFocus: false, needsForeground: false };
3811
3826
  }
3812
3827
  }
3813
3828
  function canRetryInForeground(action, outcome, headless) {
@@ -0,0 +1,272 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+ import "../../../chunks/chunk-LZQXQOPJ.js";
3
+
4
+ // builtin/extensions/herdr-agent-state/index.ts
5
+ import { spawn } from "node:child_process";
6
+ var AGENT_LABEL = "pie";
7
+ var SOURCE_ID = "pie";
8
+ var BLOCKING_TOOL_NAME = "ask_user_multi";
9
+ var REPORT_TIMEOUT_MS = 3e3;
10
+ var MAX_RESUME_ARGS = 64;
11
+ var MAX_RESUME_TOTAL_CHARS = 8192;
12
+ var RESUME_ARG_PATTERN = /^[A-Za-z0-9._:/=@,%+-]+$/;
13
+ var MESSAGE_MAX_CHARS = 120;
14
+ function readHerdrEnvironment(env = process.env) {
15
+ if (env.HERDR_ENV !== "1") return void 0;
16
+ const paneId = env.HERDR_PANE_ID;
17
+ const binPath = env.HERDR_BIN_PATH;
18
+ if (typeof paneId !== "string" || !paneId) return void 0;
19
+ if (typeof binPath !== "string" || !binPath) return void 0;
20
+ return { paneId, binPath };
21
+ }
22
+ function createReportSequencer(now = Date.now) {
23
+ let seq = now() * 1e3;
24
+ return () => {
25
+ seq += 1;
26
+ return seq;
27
+ };
28
+ }
29
+ function summarizeAskUserQuestion(args) {
30
+ if (!args || typeof args !== "object") return void 0;
31
+ const record = args;
32
+ let text = record.question;
33
+ if (typeof text !== "string" || !text.trim()) {
34
+ const questions = record.questions;
35
+ if (Array.isArray(questions) && questions.length > 0) {
36
+ const first = questions[0];
37
+ if (first && typeof first === "object") {
38
+ text = first.question;
39
+ }
40
+ }
41
+ }
42
+ if (typeof text !== "string") return void 0;
43
+ const cleaned = text.replace(/\s+/g, " ").trim();
44
+ if (!cleaned) return void 0;
45
+ return cleaned.length > MESSAGE_MAX_CHARS ? `${cleaned.slice(0, MESSAGE_MAX_CHARS - 3)}...` : cleaned;
46
+ }
47
+ function buildResumeArgv(sessionId) {
48
+ const argv = ["pie", "chat", "--session-id", sessionId];
49
+ if (argv.length > MAX_RESUME_ARGS) return void 0;
50
+ if (argv.join(" ").length > MAX_RESUME_TOTAL_CHARS) return void 0;
51
+ for (const arg of argv) {
52
+ if (!RESUME_ARG_PATTERN.test(arg)) return void 0;
53
+ }
54
+ return argv;
55
+ }
56
+ function buildStateReportArgs(params) {
57
+ const args = [
58
+ "pane",
59
+ "report-agent",
60
+ params.paneId,
61
+ "--source",
62
+ params.source,
63
+ "--agent",
64
+ params.agent,
65
+ "--state",
66
+ params.state,
67
+ "--seq",
68
+ String(params.seq)
69
+ ];
70
+ if (params.message) {
71
+ args.push("--message", params.message);
72
+ }
73
+ if (params.agentSessionId) {
74
+ args.push("--agent-session-id", params.agentSessionId);
75
+ }
76
+ if (params.resumeArgv && params.resumeArgv.length > 0) {
77
+ args.push("--", ...params.resumeArgv);
78
+ }
79
+ return args;
80
+ }
81
+ function buildSessionReportArgs(params) {
82
+ const args = [
83
+ "pane",
84
+ "report-agent-session",
85
+ params.paneId,
86
+ "--source",
87
+ params.source,
88
+ "--agent",
89
+ params.agent,
90
+ "--seq",
91
+ String(params.seq),
92
+ "--agent-session-id",
93
+ params.agentSessionId
94
+ ];
95
+ if (params.resumeArgv?.length) args.push("--", ...params.resumeArgv);
96
+ return args;
97
+ }
98
+ function buildReleaseReportArgs(params) {
99
+ return [
100
+ "pane",
101
+ "release-agent",
102
+ params.paneId,
103
+ "--source",
104
+ params.source,
105
+ "--agent",
106
+ params.agent,
107
+ "--seq",
108
+ String(params.seq)
109
+ ];
110
+ }
111
+ function spawnHerdrReport(binPath, args, timeoutMs = REPORT_TIMEOUT_MS) {
112
+ try {
113
+ const child = spawn(binPath, args, { detached: true, stdio: "ignore" });
114
+ let killTimer;
115
+ const clearTimers = () => {
116
+ clearTimeout(timer);
117
+ if (killTimer) clearTimeout(killTimer);
118
+ };
119
+ const timer = setTimeout(() => {
120
+ try {
121
+ child.kill("SIGTERM");
122
+ killTimer = setTimeout(() => {
123
+ try {
124
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
125
+ } catch {
126
+ }
127
+ }, 1e3);
128
+ killTimer.unref?.();
129
+ } catch {
130
+ }
131
+ }, timeoutMs);
132
+ timer.unref?.();
133
+ child.on("error", clearTimers);
134
+ child.on("exit", clearTimers);
135
+ child.unref();
136
+ } catch {
137
+ }
138
+ }
139
+ function attachHerdrReporter(host, herdr, transport = (args) => spawnHerdrReport(herdr.binPath, args), sequencer = createReportSequencer()) {
140
+ const nextSeq = sequencer;
141
+ let agentActive = false;
142
+ const blockingToolCalls = /* @__PURE__ */ new Map();
143
+ let blockedMessage;
144
+ let heldPane = false;
145
+ let lastSessionId;
146
+ let lastState;
147
+ let lastMessage;
148
+ function report(args) {
149
+ transport(args);
150
+ }
151
+ function publishState(options) {
152
+ const next = desiredState();
153
+ const unchanged = next.state === lastState && next.message === lastMessage && !options?.force && !options?.resumeArgv;
154
+ if (unchanged) return;
155
+ lastState = next.state;
156
+ lastMessage = next.message;
157
+ heldPane = true;
158
+ report(
159
+ buildStateReportArgs({
160
+ paneId: herdr.paneId,
161
+ source: SOURCE_ID,
162
+ agent: AGENT_LABEL,
163
+ state: next.state,
164
+ seq: nextSeq(),
165
+ message: next.message,
166
+ agentSessionId: lastSessionId,
167
+ resumeArgv: options?.resumeArgv
168
+ })
169
+ );
170
+ }
171
+ function publishSession(sessionId) {
172
+ const resumeArgv = buildResumeArgv(sessionId);
173
+ if (!heldPane) {
174
+ return;
175
+ }
176
+ report(
177
+ buildSessionReportArgs({
178
+ paneId: herdr.paneId,
179
+ source: SOURCE_ID,
180
+ agent: AGENT_LABEL,
181
+ seq: nextSeq(),
182
+ agentSessionId: sessionId,
183
+ resumeArgv
184
+ })
185
+ );
186
+ }
187
+ function desiredState() {
188
+ if (blockingToolCalls.size > 0) {
189
+ return { state: "blocked", message: blockedMessage };
190
+ }
191
+ if (agentActive) {
192
+ return { state: "working" };
193
+ }
194
+ return { state: "idle" };
195
+ }
196
+ host.on("session:changed", (event) => {
197
+ const sessionId = typeof event.sessionId === "string" && event.sessionId ? event.sessionId : void 0;
198
+ const sessionIdChanged = sessionId !== lastSessionId;
199
+ lastSessionId = sessionId;
200
+ if (!heldPane) {
201
+ publishState({
202
+ force: true,
203
+ resumeArgv: sessionId ? buildResumeArgv(sessionId) : void 0
204
+ });
205
+ return;
206
+ }
207
+ if (sessionIdChanged && sessionId) {
208
+ publishSession(sessionId);
209
+ }
210
+ });
211
+ host.on("turn:start", () => {
212
+ agentActive = true;
213
+ publishState();
214
+ });
215
+ host.on("agent:end", () => {
216
+ agentActive = false;
217
+ blockingToolCalls.clear();
218
+ blockedMessage = void 0;
219
+ publishState();
220
+ });
221
+ host.on("tool:execution:start", (event) => {
222
+ if (event.toolName !== BLOCKING_TOOL_NAME) return;
223
+ const message = summarizeAskUserQuestion(event.args);
224
+ blockingToolCalls.set(event.toolCallId, message);
225
+ if (message) {
226
+ blockedMessage = message;
227
+ } else if (!blockedMessage) {
228
+ blockedMessage = "waiting for your answer";
229
+ }
230
+ publishState();
231
+ });
232
+ host.on("tool:execution:end", (event) => {
233
+ if (event.toolName !== BLOCKING_TOOL_NAME) return;
234
+ blockingToolCalls.delete(event.toolCallId);
235
+ blockedMessage = void 0;
236
+ for (const message of blockingToolCalls.values()) {
237
+ if (message) blockedMessage = message;
238
+ }
239
+ publishState();
240
+ });
241
+ host.registerCleanup?.((reason) => {
242
+ if (reason !== "shutdown") return;
243
+ if (!heldPane) return;
244
+ report(
245
+ buildReleaseReportArgs({
246
+ paneId: herdr.paneId,
247
+ source: SOURCE_ID,
248
+ agent: AGENT_LABEL,
249
+ seq: nextSeq()
250
+ })
251
+ );
252
+ });
253
+ }
254
+ function herdrAgentStateExtension(ctx) {
255
+ const herdr = readHerdrEnvironment();
256
+ if (!herdr) return;
257
+ if (!ctx.hasUI) return;
258
+ ctx.log("herdr agent-state reporter enabled");
259
+ attachHerdrReporter(ctx, herdr);
260
+ }
261
+ export {
262
+ attachHerdrReporter,
263
+ buildReleaseReportArgs,
264
+ buildResumeArgv,
265
+ buildSessionReportArgs,
266
+ buildStateReportArgs,
267
+ createReportSequencer,
268
+ herdrAgentStateExtension as default,
269
+ readHerdrEnvironment,
270
+ spawnHerdrReport,
271
+ summarizeAskUserQuestion
272
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "herdr-agent-state-extension",
3
+ "version": "1.0.0",
4
+ "description": "Report Pie interactive agent lifecycle state (working/idle/blocked), session identity, and resume command to the Herdr terminal workspace manager.",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "pie": {
8
+ "name": "herdr-agent-state",
9
+ "main": "index.js"
10
+ }
11
+ }
@@ -544,6 +544,7 @@ function normalizeEntry(name, raw, sourcePath, sourceScope, homeDir) {
544
544
  const hasStdio = typeof raw.command === "string" && raw.command.length > 0;
545
545
  const hasHttp = typeof raw.url === "string" && raw.url.length > 0;
546
546
  if (!hasStdio && !hasHttp) return null;
547
+ const directTools = normalizeDirectTools(raw.directTools);
547
548
  return {
548
549
  name,
549
550
  command: hasStdio ? expandHome(raw.command, homeDir) : void 0,
@@ -554,8 +555,8 @@ function normalizeEntry(name, raw, sourcePath, sourceScope, homeDir) {
554
555
  headers: asStringRecord(raw.headers),
555
556
  inheritEnv: raw.inheritEnv !== false,
556
557
  connectTimeoutMs: typeof raw.connectTimeoutMs === "number" && Number.isFinite(raw.connectTimeoutMs) && raw.connectTimeoutMs > 0 ? raw.connectTimeoutMs : void 0,
557
- directTools: normalizeDirectTools(raw.directTools).mode,
558
- directToolsWhitelist: normalizeDirectTools(raw.directTools).whitelist,
558
+ directTools: directTools.mode,
559
+ directToolsWhitelist: directTools.whitelist,
559
560
  disabled: raw.disabled === true,
560
561
  sourcePath,
561
562
  sourceScope
@@ -615,8 +616,8 @@ function resolveMcpAdapterConfig(cwd, configDir, homeDir = os.homedir()) {
615
616
  continue;
616
617
  }
617
618
  const existing = merged.get(name);
618
- if (existing && raw.disabled === true) {
619
- merged.set(name, { ...existing, disabled: true });
619
+ if (existing && (raw.disabled === true || raw.disabled === false)) {
620
+ merged.set(name, { ...existing, disabled: raw.disabled });
620
621
  }
621
622
  }
622
623
  }
@@ -24438,6 +24439,7 @@ import * as path2 from "node:path";
24438
24439
  var CONNECT_TIMEOUT_MS = 3e4;
24439
24440
  var PACKAGE_MANAGER_CONNECT_TIMEOUT_MS = 12e4;
24440
24441
  var PACKAGE_MANAGER_COMMANDS = /* @__PURE__ */ new Set(["npx", "npm", "bunx", "uvx", "uv", "pnpm", "pnpmx"]);
24442
+ var METADATA_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
24441
24443
  var CALL_TIMEOUT_MS = 12e4;
24442
24444
  var STDERR_TAIL_LIMIT = 4e3;
24443
24445
  function connectTimeoutFor(definition) {
@@ -24445,6 +24447,17 @@ function connectTimeoutFor(definition) {
24445
24447
  const base = path2.basename(definition.command ?? "");
24446
24448
  return PACKAGE_MANAGER_COMMANDS.has(base) ? PACKAGE_MANAGER_CONNECT_TIMEOUT_MS : CONNECT_TIMEOUT_MS;
24447
24449
  }
24450
+ function unpinnedWarnings(definitions) {
24451
+ const commandPattern = new RegExp(`(^|\\s)(?:${[...PACKAGE_MANAGER_COMMANDS].join("|")})\\s`);
24452
+ const warnings = [];
24453
+ for (const definition of definitions) {
24454
+ const spec = [definition.command ?? "", ...definition.args].join(" ");
24455
+ if (commandPattern.test(spec) && /@latest\b/.test(spec)) {
24456
+ warnings.push(`${definition.name}: unpinned "@latest" fetch \u2014 pin an exact version (e.g. pkg@1.2.3).`);
24457
+ }
24458
+ }
24459
+ return warnings;
24460
+ }
24448
24461
  function appendTail(tail, chunk) {
24449
24462
  return (tail + String(chunk)).slice(-STDERR_TAIL_LIMIT);
24450
24463
  }
@@ -24487,15 +24500,8 @@ function withTimeout(promise, timeoutMs, label, signal) {
24487
24500
  });
24488
24501
  }
24489
24502
  var McpServerManager = class {
24490
- runtimes = /* @__PURE__ */ new Map();
24491
- definitions;
24492
- cachePath;
24493
- log;
24494
- /** Set by dispose(); after it, no new connects and no late attach. */
24495
- disposed = false;
24496
- /** Prefixed names claimed by more than one server in the last loadCatalog(); callTool fail-closes on them. */
24497
- conflictedNames = /* @__PURE__ */ new Set();
24498
- constructor(definitions, cachePath, log) {
24503
+ constructor(definitions, cacheDir, log, now = Date.now) {
24504
+ this.now = now;
24499
24505
  this.definitions = new Map(definitions.map((definition) => [sanitizeName(definition.name), definition]));
24500
24506
  if (this.definitions.size !== definitions.length) {
24501
24507
  const counts = /* @__PURE__ */ new Map();
@@ -24510,9 +24516,22 @@ var McpServerManager = class {
24510
24516
  );
24511
24517
  }
24512
24518
  }
24513
- this.cachePath = cachePath;
24519
+ this.cacheDir = cacheDir;
24514
24520
  this.log = log;
24515
24521
  }
24522
+ now;
24523
+ runtimes = /* @__PURE__ */ new Map();
24524
+ definitions;
24525
+ // Cache directory holding one <server>.json shard per connected server. A
24526
+ // legacy pre-sharding single file (<configDir>/mcp-metadata.json) is
24527
+ // deliberately ignored — the cache is disposable and shards rebuild on the
24528
+ // next connect.
24529
+ cacheDir;
24530
+ log;
24531
+ /** Set by dispose(); after it, no new connects and no late attach. */
24532
+ disposed = false;
24533
+ /** Prefixed names claimed by more than one server in the last loadCatalog(); callTool fail-closes on them. */
24534
+ conflictedNames = /* @__PURE__ */ new Set();
24516
24535
  getDefinition(server) {
24517
24536
  return this.definitions.get(sanitizeName(server));
24518
24537
  }
@@ -24614,13 +24633,11 @@ var McpServerManager = class {
24614
24633
  }
24615
24634
  seen.add(tool.prefixedName);
24616
24635
  }
24617
- for (const [otherKey, otherRuntime] of this.runtimes) {
24636
+ for (const otherRuntime of this.runtimes.values()) {
24618
24637
  if (otherRuntime === runtime || !otherRuntime.tools) continue;
24619
24638
  for (const otherTool of otherRuntime.tools) {
24620
24639
  if (seen.has(otherTool.prefixedName)) {
24621
- throw new Error(
24622
- `tool name collision between "${runtime.definition.name}" and "${otherKey}": ${otherTool.prefixedName}`
24623
- );
24640
+ this.log(`mcp: tool name "${otherTool.prefixedName}" is claimed by more than one server \u2014 disabled until the servers are renamed (fail-closed)`);
24624
24641
  }
24625
24642
  }
24626
24643
  }
@@ -24629,23 +24646,45 @@ var McpServerManager = class {
24629
24646
  }
24630
24647
  persistMetadata() {
24631
24648
  try {
24632
- const snapshot = { ...this.readCacheFile() };
24649
+ fs2.mkdirSync(this.cacheDir, { recursive: true });
24633
24650
  for (const [key, runtime] of this.runtimes) {
24634
- if (runtime.tools) snapshot[key] = runtime.tools;
24635
- }
24636
- fs2.mkdirSync(path2.dirname(this.cachePath), { recursive: true });
24637
- const tempPath = `${this.cachePath}.${process.pid}.tmp`;
24638
- fs2.writeFileSync(tempPath, JSON.stringify({ updatedAt: Date.now(), tools: snapshot }, null, 2), "utf8");
24639
- try {
24640
- fs2.renameSync(tempPath, this.cachePath);
24641
- } catch (error2) {
24642
- fs2.unlinkSync(tempPath);
24643
- throw error2;
24651
+ if (!runtime.tools) continue;
24652
+ const shardPath = path2.join(this.cacheDir, `${key}.json`);
24653
+ const tempPath = `${shardPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
24654
+ fs2.writeFileSync(tempPath, JSON.stringify({ updatedAt: this.now(), tools: runtime.tools }, null, 2), "utf8");
24655
+ try {
24656
+ fs2.renameSync(tempPath, shardPath);
24657
+ } catch (error2) {
24658
+ fs2.unlinkSync(tempPath);
24659
+ throw error2;
24660
+ }
24644
24661
  }
24662
+ this.deleteExpiredShards();
24645
24663
  } catch (error2) {
24646
24664
  this.log(`mcp: metadata cache write failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
24647
24665
  }
24648
24666
  }
24667
+ /** Delete shard files older than METADATA_TTL_MS (stale entries of offline servers age out). */
24668
+ deleteExpiredShards() {
24669
+ let names;
24670
+ try {
24671
+ names = fs2.readdirSync(this.cacheDir);
24672
+ } catch {
24673
+ return;
24674
+ }
24675
+ for (const name of names) {
24676
+ if (!name.endsWith(".json")) continue;
24677
+ const filePath = path2.join(this.cacheDir, name);
24678
+ try {
24679
+ const mtimeMs = fs2.statSync(filePath).mtimeMs;
24680
+ const parsed = JSON.parse(fs2.readFileSync(filePath, "utf8"));
24681
+ if (typeof parsed.updatedAt !== "number" || this.now() - parsed.updatedAt > METADATA_TTL_MS) {
24682
+ if (fs2.statSync(filePath).mtimeMs === mtimeMs) fs2.unlinkSync(filePath);
24683
+ }
24684
+ } catch {
24685
+ }
24686
+ }
24687
+ }
24649
24688
  /** Cached descriptors for one server (from the metadata cache file). */
24650
24689
  cachedToolsFor(server) {
24651
24690
  return this.loadCatalog().filter((tool) => tool.server === sanitizeName(server));
@@ -24681,27 +24720,60 @@ var McpServerManager = class {
24681
24720
  this.conflictedNames = conflicts;
24682
24721
  return [...catalog.values()].sort((a, b) => a.prefixedName.localeCompare(b.prefixedName));
24683
24722
  }
24684
- cacheMtime;
24723
+ cacheSignature;
24685
24724
  cacheSnapshot = {};
24686
- /** Read the metadata cache file, re-reading only when the mtime changed. */
24725
+ cacheUpdatedAt = {};
24726
+ pruneExpiredSnapshot() {
24727
+ for (const [key, updatedAt] of Object.entries(this.cacheUpdatedAt)) {
24728
+ if (this.now() - updatedAt > METADATA_TTL_MS) {
24729
+ delete this.cacheSnapshot[key];
24730
+ delete this.cacheUpdatedAt[key];
24731
+ }
24732
+ }
24733
+ return this.cacheSnapshot;
24734
+ }
24735
+ /**
24736
+ * Read the sharded metadata cache (<cacheDir>/<server>.json), merging every
24737
+ * shard's tools by server key (the file name is the sanitized server key).
24738
+ * Re-reads only when the signature changed; the signature must cover each
24739
+ * shard's name + mtime — the directory's own mtime is NOT enough, because a
24740
+ * rename replacing an existing shard leaves it unchanged.
24741
+ */
24687
24742
  readCacheFile() {
24688
- let mtime;
24743
+ let names;
24689
24744
  try {
24690
- mtime = fs2.statSync(this.cachePath).mtimeMs;
24745
+ names = fs2.readdirSync(this.cacheDir).sort();
24691
24746
  } catch {
24692
- this.cacheMtime = void 0;
24747
+ this.cacheSignature = void 0;
24693
24748
  this.cacheSnapshot = {};
24749
+ this.cacheUpdatedAt = {};
24694
24750
  return this.cacheSnapshot;
24695
24751
  }
24696
- if (mtime === this.cacheMtime) return this.cacheSnapshot;
24697
- try {
24698
- const parsed = JSON.parse(fs2.readFileSync(this.cachePath, "utf8"));
24699
- this.cacheSnapshot = parsed.tools ?? {};
24700
- this.cacheMtime = mtime;
24701
- } catch {
24702
- this.cacheSnapshot = {};
24703
- this.cacheMtime = mtime;
24752
+ let signature = "";
24753
+ for (const name of names) {
24754
+ try {
24755
+ signature += `${name}:${fs2.statSync(path2.join(this.cacheDir, name)).mtimeMs};`;
24756
+ } catch {
24757
+ }
24704
24758
  }
24759
+ if (signature === this.cacheSignature) return this.pruneExpiredSnapshot();
24760
+ const snapshot = {};
24761
+ const updatedAt = {};
24762
+ for (const name of names) {
24763
+ if (!name.endsWith(".json")) continue;
24764
+ try {
24765
+ const parsed = JSON.parse(fs2.readFileSync(path2.join(this.cacheDir, name), "utf8"));
24766
+ if (typeof parsed.updatedAt !== "number" || this.now() - parsed.updatedAt > METADATA_TTL_MS) continue;
24767
+ if (!Array.isArray(parsed.tools)) continue;
24768
+ const key = name.slice(0, -".json".length);
24769
+ snapshot[key] = parsed.tools;
24770
+ updatedAt[key] = parsed.updatedAt;
24771
+ } catch {
24772
+ }
24773
+ }
24774
+ this.cacheSnapshot = snapshot;
24775
+ this.cacheUpdatedAt = updatedAt;
24776
+ this.cacheSignature = signature;
24705
24777
  return this.cacheSnapshot;
24706
24778
  }
24707
24779
  async callTool(prefixedName, args, signal) {
@@ -24960,7 +25032,7 @@ function createMcpProxyTool(getManager, ensureTrusted, options = {}) {
24960
25032
  return {
24961
25033
  name: "mcp",
24962
25034
  label: "MCP",
24963
- description: 'Call tools on configured MCP servers without loading their definitions. Discover: mcp({ search: "screenshot" }) lists matching <server>_<tool> names with compact parameter hints (cached metadata; servers do not start for search). Inspect: mcp({ tool: "<name>", describe: true }) returns the full input schema. Call: mcp({ tool: "<name>", args: { ... } }) \u2014 the server starts lazily on first use. args may be a JSON object or JSON string. If a result is marked [outcome unknown], the tool may have executed: do not retry automatically \u2014 verify state with a read-only call first.',
25035
+ description: 'Call tools on configured MCP servers without loading their definitions. Discover: mcp({ search: "screenshot" }) lists matching <server>_<tool> names with compact parameter hints (cached metadata; servers do not start for search). Inspect: mcp({ tool: "<name>", describe: true }) returns the full input schema. Call: mcp({ tool: "<name>", args: { ... } }) \u2014 the server starts lazily on first use. args may be a JSON object or JSON string. Tool lists are a snapshot taken when a server connects: tools added or removed by a running server only appear after /reload refreshes the session. If a result is marked [outcome unknown], the tool may have executed: do not retry automatically \u2014 verify state with a read-only call first.',
24964
25036
  parameters: Type.Object({
24965
25037
  search: Type.Optional(Type.String({ description: "Query text to discover MCP tools by name/description" })),
24966
25038
  tool: Type.Optional(Type.String({ description: "Prefixed tool name <server>_<tool> to describe or call" })),
@@ -25160,10 +25232,10 @@ function formatServerForTrustPrompt(definition) {
25160
25232
  function mcpAdapter(ctx) {
25161
25233
  const configDir = resolveConfigDir();
25162
25234
  const trustStore = new McpTrustStore(path4.join(configDir, "mcp-trust.json"));
25163
- const metadataCachePath = path4.join(configDir, "mcp-metadata.json");
25235
+ const metadataCacheDir = path4.join(configDir, "mcp-metadata");
25164
25236
  let manager = new McpServerManager(
25165
25237
  resolveMcpAdapterConfig(ctx.cwd, configDir).servers,
25166
- metadataCachePath,
25238
+ metadataCacheDir,
25167
25239
  ctx.log
25168
25240
  );
25169
25241
  async function reloadManager() {
@@ -25171,23 +25243,29 @@ function mcpAdapter(ctx) {
25171
25243
  try {
25172
25244
  manager = new McpServerManager(
25173
25245
  resolveMcpAdapterConfig(ctx.cwd, configDir).servers,
25174
- metadataCachePath,
25246
+ metadataCacheDir,
25175
25247
  ctx.log
25176
25248
  );
25177
25249
  } catch (error2) {
25178
25250
  const message = error2 instanceof Error ? error2.message : String(error2);
25179
25251
  ctx.log(`mcp: config reload failed: ${message}`);
25180
25252
  if (ctx.hasUI) ctx.ui.notify(`MCP adapter config error: ${message}`, "error");
25181
- manager = new McpServerManager([], metadataCachePath, ctx.log);
25253
+ manager = new McpServerManager([], metadataCacheDir, ctx.log);
25182
25254
  }
25183
25255
  }
25184
- function writeProjectOverride(serverName, disabled) {
25256
+ ctx.registerCleanup?.(() => manager.dispose().catch(() => void 0));
25257
+ function readProjectOverride() {
25185
25258
  const filePath = path4.join(ctx.cwd, ".pie", "mcp-adapter.json");
25186
25259
  let parsed = {};
25187
25260
  try {
25188
25261
  parsed = JSON.parse(fs4.readFileSync(filePath, "utf8"));
25189
25262
  } catch {
25190
25263
  }
25264
+ return parsed;
25265
+ }
25266
+ function writeProjectOverride(serverName, disabled) {
25267
+ const filePath = path4.join(ctx.cwd, ".pie", "mcp-adapter.json");
25268
+ const parsed = readProjectOverride();
25191
25269
  const servers = parsed.mcpServers && typeof parsed.mcpServers === "object" && !Array.isArray(parsed.mcpServers) ? { ...parsed.mcpServers } : {};
25192
25270
  if (disabled === null) {
25193
25271
  delete servers[serverName];
@@ -25229,16 +25307,6 @@ function mcpAdapter(ctx) {
25229
25307
  if (eagerRegistered.length > 0) {
25230
25308
  ctx.log(`mcp: registered ${eagerRegistered.length} direct tool(s) from metadata cache: ${eagerRegistered.join(", ")}`);
25231
25309
  }
25232
- function unpinnedWarnings() {
25233
- const warnings = [];
25234
- for (const definition of manager.listDefinitions()) {
25235
- const spec = [definition.command ?? "", ...definition.args].join(" ");
25236
- if (/(^|\s)(npx|bunx|uvx)\s/.test(spec) && /@latest\b/.test(spec)) {
25237
- warnings.push(`${definition.name}: unpinned "@latest" fetch \u2014 pin an exact version (e.g. pkg@1.2.3).`);
25238
- }
25239
- }
25240
- return warnings;
25241
- }
25242
25310
  ctx.registerCommand({
25243
25311
  name: "mcp",
25244
25312
  description: "MCP adapter: status (default), enable/disable <server>",
@@ -25254,10 +25322,21 @@ function mcpAdapter(ctx) {
25254
25322
  commandCtx.ui.notify(`Unknown MCP server "${target}". Run /mcp for the configured list.`, "warning");
25255
25323
  return;
25256
25324
  }
25257
- const filePath = writeProjectOverride(definition.name, sub === "disable" ? true : null);
25325
+ const overrides = readProjectOverride().mcpServers;
25326
+ const forceEnabled = overrides?.[definition.name]?.disabled === false;
25327
+ const filePath = writeProjectOverride(
25328
+ definition.name,
25329
+ // enable must lift a source-level disabled:true with an explicit
25330
+ // {disabled:false} force-override: deleting the override alone
25331
+ // would leave the source-level disable in force. Preserve an
25332
+ // existing force-enable on repeated enable commands too.
25333
+ sub === "disable" ? true : definition.disabled || forceEnabled ? false : null
25334
+ );
25258
25335
  await reloadManager();
25336
+ const effectiveDisabled = manager.getDefinition(definition.name)?.disabled ?? false;
25337
+ const stateLabel = effectiveDisabled ? "disabled" : sub === "disable" ? "still enabled" : "enabled";
25259
25338
  commandCtx.ui.notify(
25260
- `MCP server "${definition.name}" ${sub}d (wrote ${filePath}). ` + (eagerRegistered.length > 0 ? "Restart or /reload to resync direct tools." : "Effective immediately."),
25339
+ `MCP server "${definition.name}" ${stateLabel} (wrote ${filePath}). ` + (eagerRegistered.length > 0 ? "Restart or /reload to resync direct tools." : "Effective immediately."),
25261
25340
  "info"
25262
25341
  );
25263
25342
  return;
@@ -25279,7 +25358,7 @@ function mcpAdapter(ctx) {
25279
25358
  lines.push(`- ${sanitizeName(definition.name)} [${manager.statusOf(definition.name)}, ${scopeLabel}] ${spec}`);
25280
25359
  }
25281
25360
  }
25282
- const warnings = unpinnedWarnings();
25361
+ const warnings = unpinnedWarnings(manager.listDefinitions());
25283
25362
  if (warnings.length > 0) lines.push("", "Warnings:", ...warnings.map((warning) => `- ${warning}`));
25284
25363
  const config2 = resolveMcpAdapterConfig(commandCtx.cwd, configDir);
25285
25364
  if (config2.problems.length > 0) lines.push("", "Config problems:", ...config2.problems.map((problem) => `- ${problem}`));
@@ -7,7 +7,7 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-HH2Q2SNX.js";
10
+ } from "../../../chunks/chunk-UPPICLVS.js";
11
11
  import "../../../chunks/chunk-QJH6YN2W.js";
12
12
  import "../../../chunks/chunk-IM43UYVV.js";
13
13
  import {
@@ -1,14 +1,14 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createCliHostCapabilities
4
- } from "../../../chunks/chunk-G3SE3I2L.js";
5
- import "../../../chunks/chunk-5353DEEC.js";
4
+ } from "../../../chunks/chunk-P3QPJXS7.js";
6
5
  import {
7
6
  createSharedFileSystemTools,
8
7
  createSubagentCapability
9
- } from "../../../chunks/chunk-HH2Q2SNX.js";
8
+ } from "../../../chunks/chunk-UPPICLVS.js";
10
9
  import "../../../chunks/chunk-QJH6YN2W.js";
11
10
  import "../../../chunks/chunk-IM43UYVV.js";
11
+ import "../../../chunks/chunk-5353DEEC.js";
12
12
  import "../../../chunks/chunk-XXGYUYXI.js";
13
13
  import "../../../chunks/chunk-LZQXQOPJ.js";
14
14
 
@@ -8,7 +8,7 @@ import {
8
8
  executeManageTodoList,
9
9
  executionStateToTodos,
10
10
  restoreExecutionState
11
- } from "../../../chunks/chunk-HH2Q2SNX.js";
11
+ } from "../../../chunks/chunk-UPPICLVS.js";
12
12
  import "../../../chunks/chunk-QJH6YN2W.js";
13
13
  import "../../../chunks/chunk-IM43UYVV.js";
14
14
  import "../../../chunks/chunk-XXGYUYXI.js";
@@ -1,8 +1,4 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
- import {
3
- getBuiltinDir,
4
- getConfigDir
5
- } from "./chunk-5353DEEC.js";
6
2
  import {
7
3
  classifyShellCommand,
8
4
  createCodeIntelTool,
@@ -12,7 +8,11 @@ import {
12
8
  createSharedWebSearchTool,
13
9
  interpretShellExit,
14
10
  requestInteraction
15
- } from "./chunk-HH2Q2SNX.js";
11
+ } from "./chunk-UPPICLVS.js";
12
+ import {
13
+ getBuiltinDir,
14
+ getConfigDir
15
+ } from "./chunk-5353DEEC.js";
16
16
  import {
17
17
  Type
18
18
  } from "./chunk-XXGYUYXI.js";
@@ -4918,7 +4918,7 @@ var AgentSessionRetryController = class {
4918
4918
  userAbort: false,
4919
4919
  terminal: true
4920
4920
  });
4921
- const maxRetries = this.retry?.maxRetries ?? 3;
4921
+ const maxRetries = (failure !== void 0 ? this.retry?.maxRetriesForFailure?.(failure) : void 0) ?? this.retry?.maxRetries ?? 3;
4922
4922
  this.emit({ type: "retry_exhausted", errorMessage: terminalFailure.message, maxRetries });
4923
4923
  this.emit({ type: "turn_failed_terminal", failure: terminalFailure });
4924
4924
  this.emit({ type: "turn_settled" });
package/dist/cli.js CHANGED
@@ -7,25 +7,7 @@ import {
7
7
  createCliWebResearchHealthStore,
8
8
  formatSize,
9
9
  truncateHeadTail
10
- } from "./chunks/chunk-G3SE3I2L.js";
11
- import {
12
- VERSION,
13
- getAgentDir,
14
- getBuiltinDir,
15
- getConfigDir,
16
- getCustomThemesDir,
17
- getLogsDir,
18
- getPackageDir,
19
- getProjectSettingsPath,
20
- getRuntimeLogPath,
21
- getSessionsDir,
22
- getSettingsPath,
23
- getThemesDir,
24
- migrateConfigFromAgentDir
25
- } from "./chunks/chunk-5353DEEC.js";
26
- import {
27
- resolveCliProjectRoot
28
- } from "./chunks/chunk-WLFOBECN.js";
10
+ } from "./chunks/chunk-P3QPJXS7.js";
29
11
  import {
30
12
  AGENTS_CONTEXT_FILE_NAME,
31
13
  AgentSessionController,
@@ -78,7 +60,7 @@ import {
78
60
  setExtensionEnabledInSettings,
79
61
  shouldPreserveExecutionStateForUserText,
80
62
  supersedeExecutionState
81
- } from "./chunks/chunk-HH2Q2SNX.js";
63
+ } from "./chunks/chunk-UPPICLVS.js";
82
64
  import "./chunks/chunk-QJH6YN2W.js";
83
65
  import {
84
66
  Deref,
@@ -104,6 +86,24 @@ import {
104
86
  sortToolModelCandidatesByCapability,
105
87
  type_exports
106
88
  } from "./chunks/chunk-IM43UYVV.js";
89
+ import {
90
+ VERSION,
91
+ getAgentDir,
92
+ getBuiltinDir,
93
+ getConfigDir,
94
+ getCustomThemesDir,
95
+ getLogsDir,
96
+ getPackageDir,
97
+ getProjectSettingsPath,
98
+ getRuntimeLogPath,
99
+ getSessionsDir,
100
+ getSettingsPath,
101
+ getThemesDir,
102
+ migrateConfigFromAgentDir
103
+ } from "./chunks/chunk-5353DEEC.js";
104
+ import {
105
+ resolveCliProjectRoot
106
+ } from "./chunks/chunk-WLFOBECN.js";
107
107
  import {
108
108
  IsArray,
109
109
  IsBigInt,
@@ -79622,6 +79622,7 @@ var InteractiveMode = class {
79622
79622
  await this.loadExtensions();
79623
79623
  this.applyRuntimeToolMode();
79624
79624
  this.noteRuntimeModeChanged("extension_reload");
79625
+ await this.syncSessionScopedState();
79625
79626
  this.ui.requestRender();
79626
79627
  }
79627
79628
  calculateContextUsage() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,7 @@
19
19
  "pretest:offline": "npm run build --workspace @pie/agent-framework && npm run build --workspace @pie/shared-headless-capabilities && npm run build && npm run test:skill-regressions",
20
20
  "test:skill-regressions": "node --test --import tsx test/bash-lifecycle.test.ts test/skill-creator-runner.test.ts test/tui-use-contract.test.ts test/tui-use-runtime.test.ts test/browser-runtime.test.ts test/computer-evidence.test.ts test/interactive-lifecycle.test.ts",
21
21
  "posttest:offline": "node --test --import tsx test/remote-model-catalog.test.ts",
22
- "test:offline": "node --test --test-concurrency=4 --import tsx test/builtin-extensions.test.ts test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-provider.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/mcp-adapter.test.ts test/extensions-enablement.test.ts test/extension-selector-shortcuts.test.ts test/skill-tool-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/rendered-page-reader.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/bash-yolo.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/ctrl-d-exit.test.ts test/session-display-title.test.ts test/calm-mode.test.ts test/project-root.test.ts test/guard-advisory-context.test.ts test/clear-command-usage.test.ts test/daily-agent-smoke.terminal.test.ts test/slash-menu-toggles.terminal.test.ts test/cli-args.test.ts test/extensions-configured-paths.test.ts test/extensions-loader-path.test.ts test/file-picker.test.ts test/interrupt-recovery.test.ts test/mention-insert-regression.test.ts test/mention-picker-navigation.test.ts test/pie-unity-rpc-skill.test.ts test/skill-creator-scripts.test.ts",
22
+ "test:offline": "node --test --test-concurrency=4 --import tsx test/builtin-extensions.test.ts test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-provider.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/mcp-adapter.test.ts test/herdr-agent-state.test.ts test/extensions-enablement.test.ts test/extension-selector-shortcuts.test.ts test/skill-tool-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/rendered-page-reader.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/bash-yolo.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/ctrl-d-exit.test.ts test/session-display-title.test.ts test/calm-mode.test.ts test/project-root.test.ts test/guard-advisory-context.test.ts test/clear-command-usage.test.ts test/daily-agent-smoke.terminal.test.ts test/slash-menu-toggles.terminal.test.ts test/cli-args.test.ts test/extensions-configured-paths.test.ts test/extensions-loader-path.test.ts test/file-picker.test.ts test/interrupt-recovery.test.ts test/mention-insert-regression.test.ts test/mention-picker-navigation.test.ts test/pie-unity-rpc-skill.test.ts test/skill-creator-scripts.test.ts",
23
23
  "test:all": "npm run test:offline && npm run test:terminal:vitest",
24
24
  "test:terminal:vitest": "vitest run test/mention-file-select.terminal.test.ts test/tool-partial-display.terminal.test.ts test/footer-plan-mode.test.ts test/viewport-position-final.test.ts test/resume-header.terminal.test.ts --no-file-parallelism",
25
25
  "test:todo-tui": "node ../../scripts/run-with-env.mjs PIE_REAL_TODO_TUI=1 -- --test --import tsx test/todo-closure.terminal.test.ts",