@cydm/pie 2.1.1 → 2.2.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-LU7RKALH.js";
4
+ } from "../../../chunks/chunk-HH2Q2SNX.js";
5
5
  import "../../../chunks/chunk-QJH6YN2W.js";
6
6
  import "../../../chunks/chunk-IM43UYVV.js";
7
7
  import "../../../chunks/chunk-XXGYUYXI.js";
@@ -6901,17 +6901,28 @@ async function releaseOwnedBrowser(child, port, profile) {
6901
6901
  if (port) await requestBrowserClose(port).catch(() => void 0);
6902
6902
  if (!await waitForExit(child, 700)) {
6903
6903
  if (process.platform === "win32" && child.pid) {
6904
- await new Promise((resolve, reject) => execFile("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
6905
- windowsHide: true,
6906
- timeout: 2e3
6907
- }, (error) => {
6908
- if (error && child.exitCode === null) reject(error);
6909
- else resolve();
6910
- }));
6904
+ const pid = child.pid;
6905
+ await new Promise((resolve) => {
6906
+ execFile("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
6907
+ windowsHide: true,
6908
+ timeout: 2e3
6909
+ }, () => resolve());
6910
+ });
6911
+ if (!await waitForExit(child, 1500)) {
6912
+ try {
6913
+ process.kill(pid);
6914
+ } catch {
6915
+ }
6916
+ if (!await waitForExit(child, 2e3)) {
6917
+ throw new Error(`Owned browser process ${pid} survived taskkill escalation.`);
6918
+ }
6919
+ }
6911
6920
  } else child.kill("SIGTERM");
6912
- if (!await waitForExit(child, 2e3)) {
6913
- if (process.platform !== "win32") child.kill("SIGKILL");
6914
- if (!await waitForExit(child, 2e3)) throw new Error("Owned browser did not exit.");
6921
+ if (process.platform !== "win32") {
6922
+ if (!await waitForExit(child, 2e3)) {
6923
+ child.kill("SIGKILL");
6924
+ if (!await waitForExit(child, 2e3)) throw new Error("Owned browser did not exit.");
6925
+ }
6915
6926
  }
6916
6927
  }
6917
6928
  }
@@ -7206,16 +7217,24 @@ function shutdownComputerUseSession() {
7206
7217
  }
7207
7218
  async function releaseComputerUseSession() {
7208
7219
  const drain = resourceScheduler.close();
7209
- await disconnectCdp();
7210
- await currentPlatformBackend.shutdown?.();
7211
- await drain;
7212
- await releaseOwnedBrowserState(runtimeState);
7220
+ const failures = [];
7221
+ const attempt = (step, work) => work().catch((error) => {
7222
+ failures.push(error instanceof Error ? new Error(`${step}: ${error.message}`) : error);
7223
+ });
7224
+ await attempt("disconnect CDP", () => disconnectCdp());
7225
+ await attempt("platform shutdown", async () => {
7226
+ await currentPlatformBackend.shutdown?.();
7227
+ });
7228
+ await attempt("drain scheduler", () => drain);
7229
+ await attempt("release owned browser", () => releaseOwnedBrowserState(runtimeState));
7213
7230
  if (runtimeState.managedBrowserCdpPort && process.env.PIE_COMPUTER_USE_CDP_PORT === runtimeState.managedBrowserCdpPort) {
7214
7231
  if (runtimeState.previousCdpPort === void 0) delete process.env.PIE_COMPUTER_USE_CDP_PORT;
7215
7232
  else process.env.PIE_COMPUTER_USE_CDP_PORT = runtimeState.previousCdpPort;
7216
7233
  }
7217
- runtimeState.managedBrowserCdpPort = void 0;
7218
7234
  runtimeState.previousCdpPort = void 0;
7235
+ if (runtimeState.managedBrowser === void 0) {
7236
+ runtimeState.managedBrowserCdpPort = void 0;
7237
+ }
7219
7238
  savedStates.clear();
7220
7239
  clearStoredOutputs();
7221
7240
  runtimeState.windowRefs.clear();
@@ -7227,6 +7246,8 @@ async function releaseComputerUseSession() {
7227
7246
  runtimeState.helperDiagnostics = void 0;
7228
7247
  runtimeState.lastPermissionCheckAt = 0;
7229
7248
  resourceScheduler = new ResourceScheduler();
7249
+ if (failures.length === 1) throw failures[0];
7250
+ if (failures.length > 1) throw new AggregateError(failures, "computer-use session release had partial failures (state was reset)");
7230
7251
  }
7231
7252
  function currentRuntimeMode() {
7232
7253
  return isHeadlessMode() ? "stealth" : "default";
@@ -24491,6 +24491,10 @@ var McpServerManager = class {
24491
24491
  definitions;
24492
24492
  cachePath;
24493
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();
24494
24498
  constructor(definitions, cachePath, log) {
24495
24499
  this.definitions = new Map(definitions.map((definition) => [sanitizeName(definition.name), definition]));
24496
24500
  if (this.definitions.size !== definitions.length) {
@@ -24549,6 +24553,7 @@ var McpServerManager = class {
24549
24553
  }
24550
24554
  /** Connect lazily. Concurrent callers share one in-flight connect. */
24551
24555
  async connect(server, signal) {
24556
+ if (this.disposed) throw new Error("MCP server manager is disposed");
24552
24557
  const key = sanitizeName(server);
24553
24558
  const definition = this.definitions.get(key);
24554
24559
  if (!definition) throw new Error(`unknown MCP server "${server}"`);
@@ -24561,12 +24566,17 @@ var McpServerManager = class {
24561
24566
  runtime.connecting = (async () => {
24562
24567
  this.log(`mcp: connecting to "${key}" (${definition.command ?? definition.url})`);
24563
24568
  const { client, transport } = this.startTransport(definition, runtime);
24569
+ runtime.transport = transport;
24564
24570
  client.onclose = () => {
24565
24571
  this.log(`mcp: server "${key}" connection closed`);
24566
24572
  runtime.client = void 0;
24567
24573
  };
24568
24574
  try {
24569
24575
  await withTimeout(client.connect(transport), connectTimeoutFor(definition), `connect "${key}"`, signal);
24576
+ if (this.disposed) {
24577
+ await transport.close().catch(() => void 0);
24578
+ throw new Error(`MCP manager disposed while connecting to "${key}"`);
24579
+ }
24570
24580
  runtime.client = client;
24571
24581
  await this.refreshTools(runtime, connectTimeoutFor(definition));
24572
24582
  return runtime;
@@ -24576,6 +24586,7 @@ var McpServerManager = class {
24576
24586
  }
24577
24587
  })().finally(() => {
24578
24588
  runtime.connecting = void 0;
24589
+ runtime.transport = void 0;
24579
24590
  });
24580
24591
  try {
24581
24592
  return await runtime.connecting;
@@ -24623,7 +24634,14 @@ var McpServerManager = class {
24623
24634
  if (runtime.tools) snapshot[key] = runtime.tools;
24624
24635
  }
24625
24636
  fs2.mkdirSync(path2.dirname(this.cachePath), { recursive: true });
24626
- fs2.writeFileSync(this.cachePath, JSON.stringify({ updatedAt: Date.now(), tools: snapshot }, null, 2), "utf8");
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;
24644
+ }
24627
24645
  } catch (error2) {
24628
24646
  this.log(`mcp: metadata cache write failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
24629
24647
  }
@@ -24635,20 +24653,32 @@ var McpServerManager = class {
24635
24653
  /** Metadata catalog: live runtimes plus the last persisted snapshot. */
24636
24654
  loadCatalog() {
24637
24655
  const catalog = /* @__PURE__ */ new Map();
24638
- const cached2 = this.readCacheFile();
24639
- for (const tools of Object.values(cached2)) {
24656
+ const ownerOf = /* @__PURE__ */ new Map();
24657
+ const conflicts = /* @__PURE__ */ new Set();
24658
+ const claim2 = (tool, live) => {
24659
+ const owner = ownerOf.get(tool.prefixedName);
24660
+ if (owner !== void 0 && owner !== tool.server) {
24661
+ conflicts.add(tool.prefixedName);
24662
+ return;
24663
+ }
24664
+ if (owner !== void 0 && !live) return;
24665
+ catalog.set(tool.prefixedName, tool);
24666
+ ownerOf.set(tool.prefixedName, tool.server);
24667
+ };
24668
+ for (const tools of Object.values(this.readCacheFile())) {
24640
24669
  for (const tool of tools) {
24641
24670
  if (!this.definitions.has(tool.server)) continue;
24642
- if (catalog.has(tool.prefixedName)) {
24643
- this.log(`mcp: metadata cache collision on ${tool.prefixedName} (server "${tool.server}", first seen from another server) \u2014 keeping the first entry`);
24644
- continue;
24645
- }
24646
- catalog.set(tool.prefixedName, tool);
24671
+ claim2(tool, false);
24647
24672
  }
24648
24673
  }
24649
24674
  for (const runtime of this.runtimes.values()) {
24650
- for (const tool of runtime.tools ?? []) catalog.set(tool.prefixedName, tool);
24675
+ for (const tool of runtime.tools ?? []) claim2(tool, true);
24651
24676
  }
24677
+ for (const name of conflicts) {
24678
+ catalog.delete(name);
24679
+ this.log(`mcp: tool name "${name}" is claimed by more than one server \u2014 disabled until the servers are renamed (fail-closed)`);
24680
+ }
24681
+ this.conflictedNames = conflicts;
24652
24682
  return [...catalog.values()].sort((a, b) => a.prefixedName.localeCompare(b.prefixedName));
24653
24683
  }
24654
24684
  cacheMtime;
@@ -24676,6 +24706,12 @@ var McpServerManager = class {
24676
24706
  }
24677
24707
  async callTool(prefixedName, args, signal) {
24678
24708
  const catalog = this.loadCatalog();
24709
+ if (this.conflictedNames.has(prefixedName)) {
24710
+ return {
24711
+ delivery: "not_sent",
24712
+ error: `MCP tool name "${prefixedName}" is claimed by more than one configured server. Rename one of the servers so every tool name is unambiguous; the call was NOT sent.`
24713
+ };
24714
+ }
24679
24715
  const descriptor = catalog.find((tool) => tool.prefixedName === prefixedName);
24680
24716
  let server = descriptor?.server;
24681
24717
  let toolName = descriptor?.tool;
@@ -24746,12 +24782,17 @@ ${tail}` : "")
24746
24782
  }
24747
24783
  /** Close every client and reap every child process. */
24748
24784
  async dispose() {
24785
+ this.disposed = true;
24749
24786
  for (const runtime of this.runtimes.values()) {
24750
24787
  try {
24751
24788
  await runtime.client?.close();
24752
24789
  } catch {
24753
24790
  }
24791
+ if (!runtime.client && runtime.transport) {
24792
+ await runtime.transport.close().catch(() => void 0);
24793
+ }
24754
24794
  runtime.client = void 0;
24795
+ runtime.transport = void 0;
24755
24796
  }
24756
24797
  this.runtimes.clear();
24757
24798
  }
@@ -24820,10 +24861,14 @@ var DEFAULT_OUTPUT_MAX_BYTES = 8192;
24820
24861
  var SEARCH_RESULT_LIMIT = 20;
24821
24862
  function truncate(text, maxBytes) {
24822
24863
  if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
24823
- const cut = Buffer.from(text, "utf8").subarray(0, maxBytes).toString("utf8");
24824
- return `${cut}
24864
+ const notice = `
24825
24865
 
24826
24866
  \u2026[truncated: result exceeded ${maxBytes} bytes. Narrow the call arguments or read the resource directly.]`;
24867
+ const noticeBytes = Buffer.byteLength(notice, "utf8");
24868
+ const bodyBudget = Math.max(1, maxBytes - noticeBytes);
24869
+ let cut = Buffer.from(text, "utf8").subarray(0, bodyBudget).toString("utf8");
24870
+ if (cut.endsWith("\uFFFD")) cut = cut.slice(0, -1);
24871
+ return `${cut}${notice}`;
24827
24872
  }
24828
24873
  function summarizeParams(schema) {
24829
24874
  if (!schema || typeof schema !== "object") return "";
@@ -25241,8 +25286,8 @@ function mcpAdapter(ctx) {
25241
25286
  commandCtx.ui.notify(lines.join("\n"), "info");
25242
25287
  }
25243
25288
  });
25244
- ctx.on("session:changed", () => {
25245
- void reloadManager().catch(() => void 0);
25289
+ ctx.on("session:changed", async () => {
25290
+ await reloadManager();
25246
25291
  });
25247
25292
  }
25248
25293
  export {
@@ -7,7 +7,7 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-LU7RKALH.js";
10
+ } from "../../../chunks/chunk-HH2Q2SNX.js";
11
11
  import "../../../chunks/chunk-QJH6YN2W.js";
12
12
  import "../../../chunks/chunk-IM43UYVV.js";
13
13
  import {
@@ -1,12 +1,12 @@
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-TJ2F4FL5.js";
4
+ } from "../../../chunks/chunk-G3SE3I2L.js";
5
5
  import "../../../chunks/chunk-5353DEEC.js";
6
6
  import {
7
7
  createSharedFileSystemTools,
8
8
  createSubagentCapability
9
- } from "../../../chunks/chunk-LU7RKALH.js";
9
+ } from "../../../chunks/chunk-HH2Q2SNX.js";
10
10
  import "../../../chunks/chunk-QJH6YN2W.js";
11
11
  import "../../../chunks/chunk-IM43UYVV.js";
12
12
  import "../../../chunks/chunk-XXGYUYXI.js";
@@ -8,7 +8,7 @@ import {
8
8
  executeManageTodoList,
9
9
  executionStateToTodos,
10
10
  restoreExecutionState
11
- } from "../../../chunks/chunk-LU7RKALH.js";
11
+ } from "../../../chunks/chunk-HH2Q2SNX.js";
12
12
  import "../../../chunks/chunk-QJH6YN2W.js";
13
13
  import "../../../chunks/chunk-IM43UYVV.js";
14
14
  import "../../../chunks/chunk-XXGYUYXI.js";
@@ -9999,6 +9999,16 @@ function validateRequest(value) {
9999
9999
  string("command", true, 32768);
10000
10000
  string("cwd");
10001
10001
  string("label", false, 1024);
10002
+ if (r.env !== void 0) {
10003
+ if (!r.env || typeof r.env !== "object" || Array.isArray(r.env)) throw new Error("Invalid env: expected an object of string to string.");
10004
+ const entries = Object.entries(r.env);
10005
+ if (entries.length > 512) throw new Error("Invalid env: at most 512 variables.");
10006
+ for (const [k, v] of entries) {
10007
+ if (!k.length || k.length > 256 || k.includes("\0") || typeof v !== "string" || v.length > 65536 || v.includes("\0")) {
10008
+ throw new Error(`Invalid env entry ${JSON.stringify(k)}: expected string key and value without NUL bytes.`);
10009
+ }
10010
+ }
10011
+ }
10002
10012
  if (r.argv !== void 0 && (!Array.isArray(r.argv) || r.argv.length < 1 || r.argv.length > 256 || r.argv.some((a) => typeof a !== "string" || a.includes("\0") || a.length > 32768) || !r.argv[0])) throw new Error("Invalid argv: expected a non-empty array of executable and arguments.");
10003
10013
  // Fall through to the shared PTY size limits.
10004
10014
  case "resize":
@@ -10294,6 +10304,13 @@ var SUPPORTED_KEYS = Object.keys(KEY_MAP);
10294
10304
  var import_node_fs2 = require("node:fs");
10295
10305
  var import_node_path3 = __toESM(require("node:path"));
10296
10306
  var program2 = new Command();
10307
+ function callerEnv() {
10308
+ const env = {};
10309
+ for (const [key, value] of Object.entries(process.env)) {
10310
+ if (typeof value === "string") env[key] = value;
10311
+ }
10312
+ return env;
10313
+ }
10297
10314
  program2.name("tui-use").description("TUI automation for AI agents \u2014 like BrowserUse, but for the terminal").option("--session <id>", "Target this session explicitly (safe for concurrent tasks)").version(version);
10298
10315
  program2.command("start [args...]").description("Start an interactive program in a PTY session").option("--cwd <dir>", "Working directory for the command").option("--label <name>", "Human-readable label for this session").option("--cols <n>", "Terminal width (default: 120)", "120").option("--rows <n>", "Terminal height (default: 30)", "30").option("--argv-stdin", "Read a JSON array of executable and arguments from UTF-8 stdin; bypass the shell").action(async (args, opts) => {
10299
10316
  let argv;
@@ -10311,6 +10328,7 @@ program2.command("start [args...]").description("Start an interactive program in
10311
10328
  type: "start",
10312
10329
  command,
10313
10330
  cwd: import_node_path3.default.resolve(opts.cwd ?? process.cwd()),
10331
+ env: callerEnv(),
10314
10332
  label: opts.label,
10315
10333
  cols: Number(opts.cols),
10316
10334
  rows: Number(opts.rows),
@@ -7267,7 +7267,10 @@ var Session = class {
7267
7267
  rows,
7268
7268
  useConptyDll: process.platform === "win32",
7269
7269
  cwd: options.cwd ?? process.cwd(),
7270
- env: process.env
7270
+ // The caller's env, not the daemon starter's: a persistent daemon
7271
+ // outlives many projects, and each session must see the environment
7272
+ // of the shell that started it (PATH setups, PIE_* overrides, ...).
7273
+ env: options.env ?? process.env
7271
7274
  });
7272
7275
  } catch (error) {
7273
7276
  this.terminal.dispose();
@@ -7606,6 +7609,16 @@ function validateRequest(value) {
7606
7609
  string("command", true, 32768);
7607
7610
  string("cwd");
7608
7611
  string("label", false, 1024);
7612
+ if (r.env !== void 0) {
7613
+ if (!r.env || typeof r.env !== "object" || Array.isArray(r.env)) throw new Error("Invalid env: expected an object of string to string.");
7614
+ const entries = Object.entries(r.env);
7615
+ if (entries.length > 512) throw new Error("Invalid env: at most 512 variables.");
7616
+ for (const [k, v] of entries) {
7617
+ if (!k.length || k.length > 256 || k.includes("\0") || typeof v !== "string" || v.length > 65536 || v.includes("\0")) {
7618
+ throw new Error(`Invalid env entry ${JSON.stringify(k)}: expected string key and value without NUL bytes.`);
7619
+ }
7620
+ }
7621
+ }
7609
7622
  if (r.argv !== void 0 && (!Array.isArray(r.argv) || r.argv.length < 1 || r.argv.length > 256 || r.argv.some((a) => typeof a !== "string" || a.includes("\0") || a.length > 32768) || !r.argv[0])) throw new Error("Invalid argv: expected a non-empty array of executable and arguments.");
7610
7623
  // Fall through to the shared PTY size limits.
7611
7624
  case "resize":
@@ -12,7 +12,7 @@ import {
12
12
  createSharedWebSearchTool,
13
13
  interpretShellExit,
14
14
  requestInteraction
15
- } from "./chunk-LU7RKALH.js";
15
+ } from "./chunk-HH2Q2SNX.js";
16
16
  import {
17
17
  Type
18
18
  } from "./chunk-XXGYUYXI.js";
@@ -4856,7 +4856,7 @@ var AgentSessionRetryController = class {
4856
4856
  }
4857
4857
  return;
4858
4858
  }
4859
- const maxRetries = this.retry.maxRetries ?? 3;
4859
+ const maxRetries = this.retry.maxRetriesForFailure?.(failure) ?? this.retry.maxRetries ?? 3;
4860
4860
  this.retryAttempt += 1;
4861
4861
  if (this.retryAttempt > maxRetries) {
4862
4862
  const terminalFailure = asTerminalFailure(failure);
@@ -4869,7 +4869,8 @@ var AgentSessionRetryController = class {
4869
4869
  return;
4870
4870
  }
4871
4871
  const attempt = this.retryAttempt;
4872
- const delayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
4872
+ const rawDelayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
4873
+ const delayMs = this.retry.maxDelayMs !== void 0 ? Math.min(rawDelayMs, this.retry.maxDelayMs) : rawDelayMs;
4873
4874
  this.ensurePending();
4874
4875
  this.setRecoveryState("retry_pending");
4875
4876
  this.emit({ type: "turn_recovery_pending", recovery: "retry", failure, attempt, maxRetries, delayMs });
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createCliWebResearchHealthStore,
8
8
  formatSize,
9
9
  truncateHeadTail
10
- } from "./chunks/chunk-TJ2F4FL5.js";
10
+ } from "./chunks/chunk-G3SE3I2L.js";
11
11
  import {
12
12
  VERSION,
13
13
  getAgentDir,
@@ -78,7 +78,7 @@ import {
78
78
  setExtensionEnabledInSettings,
79
79
  shouldPreserveExecutionStateForUserText,
80
80
  supersedeExecutionState
81
- } from "./chunks/chunk-LU7RKALH.js";
81
+ } from "./chunks/chunk-HH2Q2SNX.js";
82
82
  import "./chunks/chunk-QJH6YN2W.js";
83
83
  import {
84
84
  Deref,
@@ -77119,7 +77119,7 @@ function stripStaleUsageBeforeCompaction(messages, latestCompactionTimestamp) {
77119
77119
  }
77120
77120
 
77121
77121
  // src/runtime/retry-policy.ts
77122
- var TRANSIENT_NETWORK_ERROR_PATTERN = /sse stream aborted|sse stream closed before end|sse stream ended before response completed|(?:read\s+)?ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|ECONNREFUSED|socket hang up|request timeout|client network socket disconnected before secure TLS connection was established|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated/i;
77122
+ var TRANSIENT_NETWORK_ERROR_PATTERN = /sse stream aborted|sse stream closed before end|sse stream ended before response completed|(?:read\s+)?ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|ECONNREFUSED|(?:read\s+|write\s+)?EPIPE|socket hang up|request timeout|client network socket disconnected before secure TLS connection was established|certificate (?:has )?expired|unable to verify (?:the first certificate|certificate)|self[- ]signed cert|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated/i;
77123
77123
  var PROVIDER_TRANSIENT_ERROR_PATTERN = /overloaded|rate.?limit|too many requests|\b429\b|\b500\b|\b502\b|\b503\b|\b504\b|service.?unavailable|server error|internal error|retry delay/i;
77124
77124
  var USER_ABORT_PATTERN = /request was aborted|local abort signal|user abort|user_interrupt|interrupted by user|esc pressed|ctrl\+c/i;
77125
77125
  var AUTH_CONFIG_PATTERN = /missing api key|invalid api key|api key.*invalid|unauthorized|\b401\b|\b403\b|forbidden|authentication failed|no configured model|no models? are configured/i;
@@ -77127,6 +77127,8 @@ var INVALID_FILE_REFERENCE_PATTERN = /not the owner of this file|owner of this f
77127
77127
  var INVALID_REQUEST_PATTERN = /bad request|\b400\b|invalid request|invalid schema|schema validation|invalid parameter|unsupported parameter|malformed/i;
77128
77128
  var CLI_RETRY_MAX_RETRIES = 3;
77129
77129
  var CLI_RETRY_BASE_DELAY_MS = 2e3;
77130
+ var CLI_TRANSIENT_NETWORK_MAX_RETRIES = 10;
77131
+ var CLI_RETRY_MAX_DELAY_MS = 6e4;
77130
77132
  function isCliContextOverflowError(errorMessage4) {
77131
77133
  return /context length exceeded|context_length_exceeded|maximum context length|maximum context size|too many tokens|input.*too long|prompt.*too long|context window|token limit exceeded|exceeds.{0,20}context/i.test(
77132
77134
  errorMessage4
@@ -77203,6 +77205,31 @@ function getCliRetryBaseDelayMs() {
77203
77205
  const parsed = Number(override);
77204
77206
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : CLI_RETRY_BASE_DELAY_MS;
77205
77207
  }
77208
+ function getCliTransientNetworkMaxRetries() {
77209
+ const override = process.env.PIE_RETRY_TRANSIENT_NETWORK_MAX_RETRIES;
77210
+ if (!override) {
77211
+ return CLI_TRANSIENT_NETWORK_MAX_RETRIES;
77212
+ }
77213
+ const parsed = Number(override);
77214
+ return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : CLI_TRANSIENT_NETWORK_MAX_RETRIES;
77215
+ }
77216
+ function getCliRetryMaxDelayMs() {
77217
+ const override = process.env.PIE_RETRY_MAX_DELAY_MS;
77218
+ if (!override) {
77219
+ return CLI_RETRY_MAX_DELAY_MS;
77220
+ }
77221
+ const parsed = Number(override);
77222
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : CLI_RETRY_MAX_DELAY_MS;
77223
+ }
77224
+ function createCliRetryPolicy(isRetryableErrorOverride) {
77225
+ return {
77226
+ isRetryableError: isRetryableErrorOverride ?? isCliRetryableError,
77227
+ maxRetries: CLI_RETRY_MAX_RETRIES,
77228
+ baseDelayMs: getCliRetryBaseDelayMs(),
77229
+ maxDelayMs: getCliRetryMaxDelayMs(),
77230
+ maxRetriesForFailure: (failure) => failure.kind === "transient_network" ? getCliTransientNetworkMaxRetries() : CLI_RETRY_MAX_RETRIES
77231
+ };
77232
+ }
77206
77233
 
77207
77234
  // src/runtime/interactive-context-ui.ts
77208
77235
  function calculateContextUsage(host) {
@@ -77921,11 +77948,9 @@ function createInteractiveAgentSessionController(host) {
77921
77948
  },
77922
77949
  getAutoContinueMessage: () => host.getTodoAutoContinueMessage(),
77923
77950
  classifyFailure: (context) => classifyCliFailure(context),
77924
- retry: {
77925
- isRetryableError: (errorMessage4) => !host._isContextOverflowError(errorMessage4) && isCliRetryableError(errorMessage4),
77926
- maxRetries: CLI_RETRY_MAX_RETRIES,
77927
- baseDelayMs: getCliRetryBaseDelayMs()
77928
- },
77951
+ retry: createCliRetryPolicy(
77952
+ (errorMessage4) => !host._isContextOverflowError(errorMessage4) && isCliRetryableError(errorMessage4)
77953
+ ),
77929
77954
  compaction: {
77930
77955
  compact: async (context) => {
77931
77956
  const beforeCompact = await host.extensionRunner?.emitSessionBeforeCompact({
@@ -80152,6 +80177,24 @@ async function composeCliRuntime(params) {
80152
80177
  };
80153
80178
  }
80154
80179
 
80180
+ // src/app/print-mode-retry-status.ts
80181
+ function createPrintModeRetryStatusReporter() {
80182
+ let exhaustedRetryBudget;
80183
+ return (event) => {
80184
+ if (event.type === "turn_recovery_pending" && event.recovery === "retry") {
80185
+ console.error(`Transient request error: ${event.failure.message}. Retrying in ${(event.delayMs ?? 0) / 1e3}s (${event.attempt}/${event.maxRetries}).`);
80186
+ } else if (event.type === "turn_retry_started") {
80187
+ console.error(`Retrying request (${event.attempt}/${event.maxRetries})...`);
80188
+ } else if (event.type === "turn_retry_succeeded") {
80189
+ console.error(`Retry succeeded after ${event.attempts} attempt${event.attempts === 1 ? "" : "s"}.`);
80190
+ } else if (event.type === "retry_exhausted") {
80191
+ exhaustedRetryBudget = event.maxRetries;
80192
+ } else if (event.type === "turn_failed_terminal" && (event.failure.kind === "transient_network" || event.failure.kind === "provider_transient")) {
80193
+ console.error(`Request failed after ${exhaustedRetryBudget ?? CLI_RETRY_MAX_RETRIES} retries: ${event.failure.message}`);
80194
+ }
80195
+ };
80196
+ }
80197
+
80155
80198
  // src/app/print-mode.ts
80156
80199
  function createNoOpExtensionUIContext() {
80157
80200
  return {
@@ -80329,6 +80372,7 @@ async function runPrintMode(params) {
80329
80372
  streamFn: params.streamFn
80330
80373
  });
80331
80374
  const sessionTrace = new SessionTraceWriter(runtime.paths.sessionsDir, runtime.sessionManager);
80375
+ const reportRetryStatus = createPrintModeRetryStatusReporter();
80332
80376
  const agentSessionController = new AgentSessionController({
80333
80377
  agent,
80334
80378
  sessionManager: runtime.sessionManager,
@@ -80349,11 +80393,7 @@ async function runPrintMode(params) {
80349
80393
  messages: result.messages?.length ? [...context.messages, ...result.messages] : context.messages
80350
80394
  };
80351
80395
  },
80352
- retry: {
80353
- isRetryableError: isCliRetryableError,
80354
- maxRetries: CLI_RETRY_MAX_RETRIES,
80355
- baseDelayMs: getCliRetryBaseDelayMs()
80356
- },
80396
+ retry: createCliRetryPolicy(),
80357
80397
  getAutoCompactDecision: ({ message, messages }) => createCliAutoCompactDecision({
80358
80398
  message,
80359
80399
  messages,
@@ -80388,20 +80428,14 @@ async function runPrintMode(params) {
80388
80428
  classifyFailure: (context) => classifyCliFailure(context),
80389
80429
  onEvent: (event) => {
80390
80430
  sessionTrace.noteRuntimeEvent(event.type, event);
80391
- if (event.type === "turn_recovery_pending" && event.recovery === "retry") {
80392
- console.error(`Transient request error: ${event.failure.message}. Retrying in ${(event.delayMs ?? 0) / 1e3}s (${event.attempt}/${event.maxRetries}).`);
80393
- } else if (event.type === "turn_recovery_pending" && event.recovery === "compaction") {
80431
+ if (event.type === "turn_recovery_pending" && event.recovery === "compaction") {
80394
80432
  console.error(event.failure.kind === "context_overflow" ? "Context overflow detected, compacting..." : "Auto-compacting context before retry...");
80395
80433
  } else if (event.type === "auto_compaction_start") {
80396
80434
  console.error(event.reason === "overflow" ? "Context overflow detected, compacting..." : "Auto-compacting context...");
80397
80435
  } else if (event.type === "auto_compaction_end" && !event.errorMessage) {
80398
80436
  runtime.fileObservationState.clear();
80399
- } else if (event.type === "turn_retry_started") {
80400
- console.error(`Retrying request (${event.attempt}/${event.maxRetries})...`);
80401
- } else if (event.type === "turn_retry_succeeded") {
80402
- console.error(`Retry succeeded after ${event.attempts} attempt${event.attempts === 1 ? "" : "s"}.`);
80403
- } else if (event.type === "turn_failed_terminal" && (event.failure.kind === "transient_network" || event.failure.kind === "provider_transient")) {
80404
- console.error(`Request failed after ${CLI_RETRY_MAX_RETRIES} retries: ${event.failure.message}`);
80437
+ } else {
80438
+ reportRetryStatus(event);
80405
80439
  }
80406
80440
  }
80407
80441
  });
@@ -81518,11 +81552,7 @@ async function runJsonEventsMode(params) {
81518
81552
  messages: result?.messages?.length ? [...context.messages, ...result.messages] : context.messages
81519
81553
  };
81520
81554
  },
81521
- retry: {
81522
- isRetryableError: isCliRetryableError,
81523
- maxRetries: CLI_RETRY_MAX_RETRIES,
81524
- baseDelayMs: getCliRetryBaseDelayMs()
81525
- },
81555
+ retry: createCliRetryPolicy(),
81526
81556
  getAutoCompactDecision: ({ message, messages }) => createCliAutoCompactDecision({
81527
81557
  message,
81528
81558
  messages,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {