@cydm/pie 2.5.1 → 2.6.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.
Files changed (26) hide show
  1. package/dist/builtin/extensions/ask-user/index.js +3 -3
  2. package/dist/builtin/extensions/computer-use/index.js +17 -2
  3. package/dist/builtin/extensions/computer-use/prebuilt/macos/arm64/pie-computer-use +0 -0
  4. package/dist/builtin/extensions/computer-use/prebuilt/macos/universal/pie-computer-use.app/Contents/MacOS/pie-computer-use +0 -0
  5. package/dist/builtin/extensions/computer-use/prebuilt/macos/x64/pie-computer-use +0 -0
  6. package/dist/builtin/extensions/computer-use/scripts/setup-helper.mjs +90 -9
  7. package/dist/builtin/extensions/mcp-adapter/index.js +135 -27
  8. package/dist/builtin/extensions/plan-mode/index.js +3 -3
  9. package/dist/builtin/extensions/subagent/index.js +8 -5
  10. package/dist/builtin/extensions/todo/index.js +3 -3
  11. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +199 -40
  12. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +5 -2
  13. package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
  14. package/dist/builtin/skills/chrome-devtools-axi/{cli-P4VG6ZZG.js → cli-GB37NZHQ.js} +78 -29
  15. package/dist/builtin/skills/tui-use/SKILL.md +44 -0
  16. package/dist/builtin/skills/tui-use/tui-use-cli.cjs +40 -16
  17. package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +7 -1
  18. package/dist/chunks/{chunk-REP6QANY.js → chunk-AZ336HKF.js} +405 -70
  19. package/dist/chunks/{chunk-N2MK46AE.js → chunk-G6KCJA6B.js} +7 -2
  20. package/dist/chunks/{chunk-YRXXRLMQ.js → chunk-O2DKMLNX.js} +152 -8
  21. package/dist/chunks/{chunk-ZKHMNEXL.js → chunk-Y4CYXJR7.js} +27 -2
  22. package/dist/chunks/src-VCWCE7YV.js +20 -0
  23. package/dist/chunks/{test-stream-OD3B57AS.js → test-stream-GOSEWMJ3.js} +1 -1
  24. package/dist/cli.js +67 -17
  25. package/package.json +2 -2
  26. package/dist/chunks/src-SXUQ7OY6.js +0 -14
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-REP6QANY.js";
11
+ } from "./chunk-AZ336HKF.js";
12
12
  import {
13
13
  getBuiltinDir,
14
14
  getConfigDir
@@ -1176,7 +1176,12 @@ function createCliRenderedPageReader() {
1176
1176
  networkRequests: linesOf(stripHelpHints(networkOutput))
1177
1177
  };
1178
1178
  } finally {
1179
- runAxi(["stop"], { env, timeoutMs: 1e4 }).catch(() => void 0);
1179
+ try {
1180
+ await runAxi(["stop"], { env, timeoutMs: 15e3 });
1181
+ } catch (error) {
1182
+ process.stderr.write(`[rendered-page-reader] axi stop after read failed: ${error instanceof Error ? error.message : String(error)}
1183
+ `);
1184
+ }
1180
1185
  }
1181
1186
  };
1182
1187
  }
@@ -2191,15 +2191,120 @@ var FileSystemGateway = class {
2191
2191
  return true;
2192
2192
  }
2193
2193
  }
2194
- return false;
2194
+ return this.allowedViaCaseAlias(path, normalized);
2195
2195
  }
2196
2196
  /**
2197
- * Normalize path for comparison
2197
+ * Normalize path for comparison.
2198
+ *
2199
+ * Both sides of every comparison get the identical structural
2200
+ * transformation (backslashes to slashes, repeated separators collapsed,
2201
+ * trailing separators trimmed) so boundary checks stay segment-exact.
2202
+ * Case is folded only where the compared path is Windows-shaped: a drive
2203
+ * letter, a backslash UNC root (`\\server\share`), or any path on a win32
2204
+ * host. POSIX paths keep their case — on a case-sensitive volume a
2205
+ * different-case spelling is a different directory, and a leading "//" on
2206
+ * a POSIX host is an ordinary POSIX path, not a UNC share, so it must never
2207
+ * earn case-insensitive treatment there (that would let `//Tmp/...`-style
2208
+ * spellings borrow the identity of case-distinct siblings).
2198
2209
  */
2199
2210
  normalizePath(path) {
2200
- const normalized = path.replace(/\\/g, "/").replace(/\/+/g, "/");
2201
- if (normalized === "/") return normalized;
2202
- return normalized.replace(/\/+$/, "").toLowerCase();
2211
+ const slashed = path.replace(/\\/g, "/");
2212
+ const collapsed = slashed.replace(/\/+/g, "/");
2213
+ const trimmed = collapsed === "/" ? collapsed : collapsed.replace(/\/+$/, "");
2214
+ return this.usesCaseInsensitiveComparison(path, trimmed) ? trimmed.toLowerCase() : trimmed;
2215
+ }
2216
+ /**
2217
+ * Windows compares all path names case-insensitively; UNC roots are
2218
+ * recognized by their backslash form. Forward-slash "//" prefixes on a
2219
+ * POSIX host stay case-sensitive POSIX paths.
2220
+ */
2221
+ usesCaseInsensitiveComparison(rawPath, normalizedPath) {
2222
+ if (rawPath.startsWith("\\\\")) return true;
2223
+ if (/^[a-zA-Z]:/.test(normalizedPath)) return true;
2224
+ return typeof process !== "undefined" && process.platform === "win32";
2225
+ }
2226
+ /**
2227
+ * Slow path for POSIX hosts: preserve legitimate case aliases (the default
2228
+ * macOS volume resolves `/Users/alice` and `/Users/Alice` to the same
2229
+ * directory) without blanket darwin case-folding and without probing the
2230
+ * filesystem with writes. A candidate is allowed only when the canonical
2231
+ * identity of its nearest existing ancestor — rebuilt segment by segment
2232
+ * with read-only stat/readdir probes — lands inside the canonical identity
2233
+ * of an allowed root. On a case-sensitive volume a case-variant spelling of
2234
+ * a different directory never resolves through this walk (the exact
2235
+ * spelling wins whenever it exists), so it stays denied. Windows-shaped
2236
+ * candidates skip this path: their comparison is already case-folded.
2237
+ */
2238
+ allowedViaCaseAlias(rawPath, normalizedPath) {
2239
+ if (!normalizedPath.startsWith("/")) return false;
2240
+ if (this.usesCaseInsensitiveComparison(rawPath, normalizedPath)) return false;
2241
+ if (typeof process !== "undefined" && process.platform === "win32") return false;
2242
+ if (detectPlatform() === "puerts") return false;
2243
+ let fs;
2244
+ try {
2245
+ fs = nodeRequire("fs");
2246
+ } catch {
2247
+ return false;
2248
+ }
2249
+ let resolved;
2250
+ try {
2251
+ resolved = this.resolveCaseAlias(fs, normalizedPath);
2252
+ } catch {
2253
+ return false;
2254
+ }
2255
+ for (const root of this.allowedRoots) {
2256
+ const normalizedRoot = this.normalizePath(root);
2257
+ if (!normalizedRoot.startsWith("/")) continue;
2258
+ try {
2259
+ const resolvedRoot = this.resolveCaseAlias(fs, normalizedRoot);
2260
+ if (resolved === resolvedRoot || resolved.startsWith(this.withTrailingSep(resolvedRoot))) {
2261
+ return true;
2262
+ }
2263
+ } catch {
2264
+ }
2265
+ }
2266
+ return false;
2267
+ }
2268
+ /**
2269
+ * Rebuild a POSIX path with its true on-disk case. The canonical prefix
2270
+ * grows only through segments the filesystem itself accepts in the
2271
+ * requested spelling (a read-only stat probe): on a case-insensitive
2272
+ * volume a different-case spelling still resolves and is rewritten to the
2273
+ * directory's actual entry name; on a case-sensitive volume it does not
2274
+ * resolve, the walk stops, and the remaining segments stay verbatim — so
2275
+ * they can never borrow a sibling directory's identity.
2276
+ */
2277
+ resolveCaseAlias(fs, normalizedPath) {
2278
+ const segments = normalizedPath.slice(1).split("/").filter((segment) => segment.length > 0 && segment !== ".");
2279
+ let canonical = "";
2280
+ let index = 0;
2281
+ while (index < segments.length) {
2282
+ const segment = segments[index];
2283
+ const probe = `${canonical}/${segment}`;
2284
+ if (!fs.statSync(probe, { throwIfNoEntry: false })) break;
2285
+ canonical = `${canonical}/${this.trueCaseName(fs, canonical, segment)}`;
2286
+ index += 1;
2287
+ }
2288
+ const tail = segments.slice(index).join("/");
2289
+ return tail ? `${canonical}/${tail}` : canonical;
2290
+ }
2291
+ /**
2292
+ * Actual directory-entry name for a segment the filesystem already
2293
+ * resolved. Exact spelling wins; a different-case entry can only be the
2294
+ * same file (case-insensitive volume), otherwise statSync above would have
2295
+ * failed. Read-only; never falls back to writing a probe.
2296
+ */
2297
+ trueCaseName(fs, directory, name) {
2298
+ try {
2299
+ const entries = fs.readdirSync(directory === "" ? "/" : directory);
2300
+ if (entries.includes(name)) return name;
2301
+ const lowered = name.toLowerCase();
2302
+ for (const entry of entries) {
2303
+ if (entry.toLowerCase() === lowered) return entry;
2304
+ }
2305
+ } catch {
2306
+ }
2307
+ return name;
2203
2308
  }
2204
2309
  /**
2205
2310
  * Normalize root path for prefix comparison
@@ -2914,7 +3019,7 @@ function convertMessages(model, context, compat) {
2914
3019
  if (typeof msg.content === "string") {
2915
3020
  params.push({ role: "user", content: msg.content });
2916
3021
  } else {
2917
- const content = msg.content.flatMap((item) => convertUserContentItem(item));
3022
+ const content = msg.content.flatMap((item) => convertCheckedContent(item, model));
2918
3023
  const filteredContent = content.filter((c) => {
2919
3024
  if (c.type === "text") return true;
2920
3025
  if (c.type === "image_url") return model.input.includes("image");
@@ -2960,6 +3065,7 @@ function convertMessages(model, context, compat) {
2960
3065
  const hasContent = content !== null && content !== void 0 && (typeof content === "string" ? content.length > 0 : Array.isArray(content) && content.length > 0);
2961
3066
  if (hasContent || assistantMsg.tool_calls) params.push(assistantMsg);
2962
3067
  } else if (msg.role === "toolResult") {
3068
+ const attachments = [];
2963
3069
  for (; i < transformedMessages.length && transformedMessages[i].role === "toolResult"; i++) {
2964
3070
  const toolMsg = transformedMessages[i];
2965
3071
  const textResult = toolMsg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
@@ -2970,12 +3076,50 @@ function convertMessages(model, context, compat) {
2970
3076
  };
2971
3077
  if (compat.requiresToolResultName && toolMsg.toolName) toolResultMsg.name = toolMsg.toolName;
2972
3078
  params.push(toolResultMsg);
3079
+ {
3080
+ const images = toolMsg.content.filter((c) => c.type !== "text");
3081
+ if (images.length > 0) {
3082
+ attachments.push({ type: "text", text: `Images from tool ${toolMsg.toolName ?? "unknown"} (call ${toolMsg.toolCallId}):` });
3083
+ for (const image of images) {
3084
+ const kind = image.type;
3085
+ const capability = kind === "audio" || kind === "video" ? model.fileCapabilities?.[kind] : void 0;
3086
+ if ((kind === "audio" || kind === "video") && !(capability && typeof capability === "object" && capability.supported !== false && capability.nativeInline === true)) {
3087
+ attachments.push({ type: "text", text: `[${kind} attachment omitted: endpoint nativeInline transport is not enabled. Re-read with a compatible model.]` });
3088
+ } else attachments.push(...convertCheckedContent(image, model));
3089
+ }
3090
+ }
3091
+ }
2973
3092
  }
3093
+ if (attachments.length > 0) params.push({ role: "user", content: attachments });
2974
3094
  i -= 1;
2975
3095
  }
2976
3096
  }
2977
3097
  return params;
2978
3098
  }
3099
+ function convertCheckedContent(item, model) {
3100
+ const modality = item.type === "fileRef" ? item.modality : item.type;
3101
+ if (modality === "image" || modality === "video" || modality === "audio") {
3102
+ if (!model.input.includes(modality)) return [{ type: "text", text: `[${modality} attachment omitted: current model does not support this modality. Re-read with a compatible model.]` }];
3103
+ if (item.type === "audio") {
3104
+ const audio = item;
3105
+ if (audioFormatFor(audio.mimeType) === void 0) {
3106
+ return [{ type: "text", text: `[audio attachment omitted: unsupported format ${audio.mimeType}. Convert to WAV or MP3 first.]` }];
3107
+ }
3108
+ }
3109
+ }
3110
+ return convertUserContentItem(item);
3111
+ }
3112
+ function audioFormatFor(mimeType) {
3113
+ const mime = mimeType.toLowerCase().split(";")[0].trim();
3114
+ if (["audio/wav", "audio/x-wav", "audio/wave", "audio/vnd.wave"].includes(mime)) return "wav";
3115
+ if (["audio/mpeg", "audio/mp3"].includes(mime)) return "mp3";
3116
+ return void 0;
3117
+ }
3118
+ function audioFormat(mimeType) {
3119
+ const format = audioFormatFor(mimeType);
3120
+ if (format === void 0) throw new Error(`Unsupported audio attachment format: ${mimeType}. Convert to WAV or MP3 first.`);
3121
+ return format;
3122
+ }
2979
3123
  function convertUserContentItem(item) {
2980
3124
  if (item.type === "text") return [{ type: "text", text: item.text }];
2981
3125
  if (item.type === "image") return [{ type: "image_url", image_url: { url: `data:${item.mimeType};base64,${item.data}` } }];
@@ -2985,7 +3129,7 @@ function convertUserContentItem(item) {
2985
3129
  }
2986
3130
  if (item.type === "audio") {
2987
3131
  const audio = item;
2988
- return [{ type: "input_audio", input_audio: { data: audio.data, format: audio.mimeType.includes("wav") ? "wav" : "mp3" } }];
3132
+ return [{ type: "input_audio", input_audio: { data: audio.data, format: audioFormat(audio.mimeType) } }];
2989
3133
  }
2990
3134
  if (item.type === "fileRef") {
2991
3135
  const ref = item;
@@ -3632,7 +3776,7 @@ function mapStopReason(reason) {
3632
3776
  }
3633
3777
  }
3634
3778
  function convertContentBlocks(content) {
3635
- const supportedBlocks = content.filter((block) => block.type === "text" || block.type === "image");
3779
+ const supportedBlocks = content.map((block) => block.type === "text" || block.type === "image" ? block : { type: "text", text: `[${block.type} attachment omitted: unsupported by this provider. Re-read with a compatible model.]` });
3636
3780
  const hasImages = supportedBlocks.some((c) => c.type === "image");
3637
3781
  if (!hasImages) {
3638
3782
  const text = supportedBlocks.filter((c) => c.type === "text").map((c) => c.text).join("\n");
@@ -3,7 +3,7 @@ import {
3
3
  EventStream,
4
4
  streamSimple,
5
5
  validateToolArguments
6
- } from "./chunk-YRXXRLMQ.js";
6
+ } from "./chunk-O2DKMLNX.js";
7
7
 
8
8
  // ../../packages/agent-core/src/agent-loop.ts
9
9
  function createLoopErrorMessage(error, config, signal) {
@@ -409,6 +409,19 @@ function skipToolCall(toolCall, stream) {
409
409
  return toolResultMessage;
410
410
  }
411
411
 
412
+ // ../../packages/agent-core/src/tool-caller-identity.ts
413
+ var kToolCallerIdentity = /* @__PURE__ */ Symbol("pie.toolCallerIdentity");
414
+ function createToolCallerIdentity() {
415
+ return { kind: "pie.toolCallerIdentity" };
416
+ }
417
+ function bindToolCallerIdentity(signal, identity) {
418
+ signal[kToolCallerIdentity] = identity;
419
+ }
420
+ function getToolCallerIdentity(signal) {
421
+ if (!signal) return void 0;
422
+ return signal[kToolCallerIdentity];
423
+ }
424
+
412
425
  // ../../packages/agent-core/src/agent.ts
413
426
  function defaultConvertToLlm(messages) {
414
427
  return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult");
@@ -443,11 +456,16 @@ var Agent = class {
443
456
  _thinkingBudgets;
444
457
  _maxRetryDelayMs;
445
458
  hooks;
459
+ /** Stable identity for this agent instance, bound onto every run signal
460
+ * handed to tools (signals themselves are per-run; see tool-caller-identity.ts). */
461
+ toolCallerIdentity = createToolCallerIdentity();
446
462
  statusSnapshot = {
447
463
  phase: "idle",
448
464
  turnState: "completed",
449
465
  hasPendingToolContinuation: false
450
466
  };
467
+ /** Assistant message already reported as turn_failed during this run. */
468
+ reportedFailureMessage;
451
469
  constructor(opts = {}) {
452
470
  this._state = {
453
471
  systemPrompt: "",
@@ -729,6 +747,7 @@ var Agent = class {
729
747
  const model = this._state.model;
730
748
  if (!model) throw new Error("No model configured");
731
749
  this.abortController = new AbortController();
750
+ bindToolCallerIdentity(this.abortController.signal, this.toolCallerIdentity);
732
751
  this._state.isStreaming = true;
733
752
  this._state.streamMessage = null;
734
753
  this._state.error = void 0;
@@ -954,6 +973,7 @@ var Agent = class {
954
973
  const stopReason = event.message.role === "assistant" ? event.message.stopReason : void 0;
955
974
  const hasToolCalls = event.message.role === "assistant" && Array.isArray(event.message.content) && event.message.content.some((block) => block.type === "toolCall");
956
975
  if (stopReason === "error" || stopReason === "aborted") {
976
+ this.reportedFailureMessage = event.message;
957
977
  this.updateStatusSnapshot({
958
978
  phase: "failed",
959
979
  turnState: "failed",
@@ -997,7 +1017,9 @@ var Agent = class {
997
1017
  }
998
1018
  case "agent_end": {
999
1019
  const lastMessage = event.messages[event.messages.length - 1];
1000
- if (lastMessage?.role === "assistant" && (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
1020
+ const alreadyReported = lastMessage !== void 0 && lastMessage === this.reportedFailureMessage;
1021
+ this.reportedFailureMessage = void 0;
1022
+ if (!alreadyReported && lastMessage?.role === "assistant" && (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
1001
1023
  this.updateStatusSnapshot({
1002
1024
  phase: "failed",
1003
1025
  turnState: "failed",
@@ -1025,5 +1047,8 @@ var Agent = class {
1025
1047
  export {
1026
1048
  agentLoop,
1027
1049
  agentLoopContinue,
1050
+ createToolCallerIdentity,
1051
+ bindToolCallerIdentity,
1052
+ getToolCallerIdentity,
1028
1053
  Agent
1029
1054
  };
@@ -0,0 +1,20 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ Agent,
4
+ agentLoop,
5
+ agentLoopContinue,
6
+ bindToolCallerIdentity,
7
+ createToolCallerIdentity,
8
+ getToolCallerIdentity
9
+ } from "./chunk-Y4CYXJR7.js";
10
+ import "./chunk-O2DKMLNX.js";
11
+ import "./chunk-XXGYUYXI.js";
12
+ import "./chunk-LZQXQOPJ.js";
13
+ export {
14
+ Agent,
15
+ agentLoop,
16
+ agentLoopContinue,
17
+ bindToolCallerIdentity,
18
+ createToolCallerIdentity,
19
+ getToolCallerIdentity
20
+ };
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  EventStream
4
- } from "./chunk-YRXXRLMQ.js";
4
+ } from "./chunk-O2DKMLNX.js";
5
5
  import "./chunk-XXGYUYXI.js";
6
6
  import "./chunk-LZQXQOPJ.js";
7
7
 
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createCliWebResearchHealthStore,
8
8
  formatSize,
9
9
  truncateHeadTail
10
- } from "./chunks/chunk-N2MK46AE.js";
10
+ } from "./chunks/chunk-G6KCJA6B.js";
11
11
  import {
12
12
  AGENTS_CONTEXT_FILE_NAME,
13
13
  AgentSessionController,
@@ -60,8 +60,8 @@ import {
60
60
  setExtensionEnabledInSettings,
61
61
  shouldPreserveExecutionStateForUserText,
62
62
  supersedeExecutionState
63
- } from "./chunks/chunk-REP6QANY.js";
64
- import "./chunks/chunk-ZKHMNEXL.js";
63
+ } from "./chunks/chunk-AZ336HKF.js";
64
+ import "./chunks/chunk-Y4CYXJR7.js";
65
65
  import {
66
66
  Deref,
67
67
  EMPTY_ASSISTANT_CONTENT_ERROR,
@@ -85,7 +85,7 @@ import {
85
85
  setLogger,
86
86
  sortToolModelCandidatesByCapability,
87
87
  type_exports
88
- } from "./chunks/chunk-YRXXRLMQ.js";
88
+ } from "./chunks/chunk-O2DKMLNX.js";
89
89
  import {
90
90
  VERSION,
91
91
  getAgentDir,
@@ -52476,8 +52476,9 @@ function validateFileCapabilities(value, label) {
52476
52476
  if (!["image", "video", "audio", "document"].includes(kind)) throw new Error(`${label}.${kind} is unsupported`);
52477
52477
  if (typeof capability === "boolean") continue;
52478
52478
  if (!isPlainRecord(capability)) throw new Error(`${label}.${kind} is invalid`);
52479
- assertOnlyKeys(capability, ["supported", "formats", "maxFileSizeMb", "maxDurationSeconds"], `${label}.${kind}`);
52479
+ assertOnlyKeys(capability, ["supported", "formats", "maxFileSizeMb", "maxDurationSeconds", "nativeInline"], `${label}.${kind}`);
52480
52480
  assertOptionalBoolean(capability.supported, `${label}.${kind}.supported`);
52481
+ assertOptionalBoolean(capability.nativeInline, `${label}.${kind}.nativeInline`);
52481
52482
  if (capability.formats !== void 0 && (!Array.isArray(capability.formats) || capability.formats.some((item) => typeof item !== "string" || !item))) {
52482
52483
  throw new Error(`${label}.${kind}.formats is invalid`);
52483
52484
  }
@@ -55210,8 +55211,11 @@ function publishJournalPath(sessionsDir, safeTargetId) {
55210
55211
  return path7.join(sessionsDir, PUBLISH_JOURNAL_DIR, `${safeTargetId}.json`);
55211
55212
  }
55212
55213
  var FSYNC_UNSUPPORTED_CODES = /* @__PURE__ */ new Set(["EPERM", "EINVAL", "ENOTSUP", "EOPNOTSUPP", "EACCES", "EBADF"]);
55214
+ var fsyncDirectoryFaultForTests = {};
55213
55215
  function fsyncDirectory(dirPath) {
55214
55216
  try {
55217
+ const injected = fsyncDirectoryFaultForTests.fault?.(dirPath);
55218
+ if (injected) throw injected;
55215
55219
  const fd = fs7.openSync(dirPath, "r");
55216
55220
  try {
55217
55221
  fs7.fsyncSync(fd);
@@ -55430,11 +55434,21 @@ async function forkStoredSession(params) {
55430
55434
  if (hadPreviousTarget) {
55431
55435
  try {
55432
55436
  params.store.moveSession(params.targetSessionId, backupId);
55433
- if (params.sessionsDir) fsyncDirectory(params.sessionsDir);
55434
55437
  } catch (error) {
55435
55438
  clearPublishJournal(journalPath);
55436
55439
  throw error;
55437
55440
  }
55441
+ try {
55442
+ if (params.sessionsDir) fsyncDirectory(params.sessionsDir);
55443
+ } catch (error) {
55444
+ markPublishJournalFailed(journalPath);
55445
+ preserveStagingForRecovery = true;
55446
+ throw new SessionControlError(
55447
+ "session_io_error",
55448
+ `Fork publish durability failure after moving the previous session aside; it is preserved as ${backupId} and startup recovery will restore it: ${String(error)}`,
55449
+ false
55450
+ );
55451
+ }
55438
55452
  }
55439
55453
  try {
55440
55454
  params.store.moveSession(stagingId, params.targetSessionId);
@@ -70137,22 +70151,40 @@ var SettingsSelectorComponent = class extends Container {
70137
70151
  };
70138
70152
 
70139
70153
  // src/core/session-display.ts
70154
+ function looksAutoGeneratedSessionName(name) {
70155
+ return /^Session (\d|[a-z0-9]{6,8}$)/i.test(name.trim());
70156
+ }
70157
+ function persistedNameIsAuthoritative(metadata) {
70158
+ if (metadata.nameSource === "derived") return true;
70159
+ if (metadata.nameSource === void 0) return !looksAutoGeneratedSessionName(metadata.name);
70160
+ return false;
70161
+ }
70140
70162
  function deriveSessionTitle(options) {
70141
70163
  const { activeSession, messages } = options;
70142
70164
  if (!activeSession) {
70143
70165
  return "No session";
70144
70166
  }
70167
+ if (activeSession.metadata.nameSource === "manual") {
70168
+ const manual = normalizeSessionTitle(activeSession.metadata.name);
70169
+ return manual || activeSession.metadata.name.trim() || `S:${activeSession.id.slice(-6)}`;
70170
+ }
70171
+ if (persistedNameIsAuthoritative(activeSession.metadata)) {
70172
+ const persisted = normalizeSessionTitle(activeSession.metadata.name);
70173
+ if (persisted) return persisted;
70174
+ }
70145
70175
  for (const msg of messages) {
70146
70176
  if (msg.role !== "user") continue;
70147
70177
  const text = extractUserText(msg);
70148
70178
  if (isCompactSummaryMessage(text.trimStart())) continue;
70149
70179
  const normalized = normalizeSessionTitle(text);
70150
70180
  if (normalized) {
70181
+ activeSession.metadata.name = normalized;
70182
+ activeSession.metadata.nameSource = "derived";
70151
70183
  return normalized;
70152
70184
  }
70153
70185
  }
70154
70186
  const metadataName = normalizeSessionTitle(activeSession.metadata.name);
70155
- if (metadataName && !metadataName.startsWith("Session ")) {
70187
+ if (metadataName && persistedNameIsAuthoritative(activeSession.metadata)) {
70156
70188
  return metadataName;
70157
70189
  }
70158
70190
  return `S:${activeSession.id.slice(-6)}`;
@@ -77094,7 +77126,9 @@ function updatePendingMessagesDisplay(host) {
77094
77126
  host.pendingMessagesContainer.addChild(new Text(text, 1, 0));
77095
77127
  }
77096
77128
  const dequeueHint = host.keybindings.getKeys("dequeue")[0] || "alt+up";
77097
- const hintText = theme.fg("dim", `\u21B3 Press ${dequeueHint} to edit all queued messages`);
77129
+ const steerHint = host.keybindings.getKeys("steer")[0];
77130
+ const promotionHint = followUpMessages.length > 0 && !followUpMessages[followUpMessages.length - 1].deliveredToAgent && steerHint ? `${steerHint} with empty editor: steer latest follow-up \xB7 ` : "";
77131
+ const hintText = theme.fg("dim", `\u21B3 ${promotionHint}${dequeueHint}: edit all queued messages`);
77098
77132
  host.pendingMessagesContainer.addChild(new Text(hintText, 1, 0));
77099
77133
  }
77100
77134
  function dequeueMessages(host) {
@@ -77150,7 +77184,7 @@ function queueMessage(host, text, mode, options = {}) {
77150
77184
  }
77151
77185
  updatePendingMessagesDisplay(host);
77152
77186
  host.showStatus(
77153
- options.statusMessage || `Queued message for ${mode === "steer" ? "steering" : "follow-up"}`
77187
+ options.statusMessage || (mode === "steer" ? "Queued message for steering" : `Queued for follow-up after the current task.${host.keybindings.getKeys("steer")[0] ? ` With empty editor, press ${host.keybindings.getKeys("steer")[0]} to steer the current task instead.` : ""}`)
77154
77188
  );
77155
77189
  host.ui.requestRender();
77156
77190
  }
@@ -77169,7 +77203,22 @@ function handleFollowUp(host) {
77169
77203
  }
77170
77204
  function handleSteer(host) {
77171
77205
  const text = host.editor.getText().trim();
77172
- if (!text) return;
77206
+ if (!text) {
77207
+ const promoted = host.agentSessionController?.promoteLatestFollowUpToSteer();
77208
+ if (!promoted) {
77209
+ host.showStatus("No waiting follow-up to steer (it may already be processing)");
77210
+ return;
77211
+ }
77212
+ if (host.executionState.applyUserTextLifecycle(promoted.displayText)) {
77213
+ host.persistExecutionState();
77214
+ void host.refreshTodoWidget();
77215
+ }
77216
+ host.logDebug("queue:promote", { id: promoted.id, mode: promoted.mode });
77217
+ updatePendingMessagesDisplay(host);
77218
+ host.showStatus("Latest follow-up changed to steer; it will guide the current task at the next message boundary");
77219
+ host.ui.requestRender();
77220
+ return;
77221
+ }
77173
77222
  if (isCommandLikeInput(text)) {
77174
77223
  host.editor.onSubmit?.(text);
77175
77224
  return;
@@ -77593,9 +77642,10 @@ function stripStaleUsageBeforeCompaction(messages, latestCompactionTimestamp) {
77593
77642
 
77594
77643
  // src/runtime/retry-policy.ts
77595
77644
  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;
77596
- 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;
77645
+ 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|quota (?:has been |is )?exhausted/i;
77597
77646
  var USER_ABORT_PATTERN = /request was aborted|local abort signal|user abort|user_interrupt|interrupted by user|esc pressed|ctrl\+c/i;
77598
77647
  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;
77648
+ var QUOTA_EXHAUSTED_PATTERN = /insufficient[_ ]quota|exceeded[_ ]current[_ ]quota|exceeded your current quota|billing[_ ]hard[_ ]limit|insufficient[_ ]balance|credit balance is too low|\barrearage\b|余额不足|\b(?:account|monthly|billing) quota (?:has been |is )?exhausted/i;
77599
77649
  var INVALID_FILE_REFERENCE_PATTERN = /not the owner of this file|owner of this file|invalid_authentication_error/i;
77600
77650
  var INVALID_REQUEST_PATTERN = /bad request|\b400\b|invalid request|invalid schema|schema validation|invalid parameter|unsupported parameter|malformed/i;
77601
77651
  var CLI_RETRY_MAX_RETRIES = 3;
@@ -77635,7 +77685,7 @@ function classifyCliFailure(context) {
77635
77685
  if (INVALID_FILE_REFERENCE_PATTERN.test(errorMessage4)) {
77636
77686
  return createClassification("invalid_file_reference", errorMessage4);
77637
77687
  }
77638
- if (AUTH_CONFIG_PATTERN.test(errorMessage4)) {
77688
+ if (AUTH_CONFIG_PATTERN.test(errorMessage4) || QUOTA_EXHAUSTED_PATTERN.test(errorMessage4)) {
77639
77689
  return createClassification("auth_config", errorMessage4);
77640
77690
  }
77641
77691
  if (TRANSIENT_NETWORK_ERROR_PATTERN.test(errorMessage4)) {
@@ -79986,7 +80036,7 @@ var InteractiveMode = class {
79986
80036
  async init() {
79987
80037
  const activeSession = this.sessionManager.getActiveSession();
79988
80038
  this.thinkingLevel = resolveInitialThinkingLevel({ session: activeSession, settingsManager: this.options.settingsManager });
79989
- const { Agent } = await import("./chunks/src-SXUQ7OY6.js");
80039
+ const { Agent } = await import("./chunks/src-VCWCE7YV.js");
79990
80040
  this.agent = new Agent({
79991
80041
  initialState: {
79992
80042
  systemPrompt: this.buildSystemPrompt(),
@@ -80820,7 +80870,7 @@ async function runPrintMode(params) {
80820
80870
  `);
80821
80871
  console.log("Assistant: ");
80822
80872
  }
80823
- const { Agent: AgentClass } = await import("./chunks/src-SXUQ7OY6.js");
80873
+ const { Agent: AgentClass } = await import("./chunks/src-VCWCE7YV.js");
80824
80874
  let agent;
80825
80875
  const printModeExtensions = await loadPrintModeExtensions({
80826
80876
  cwd: runtime.cwd,
@@ -81681,7 +81731,7 @@ async function runJsonEventsMode(params) {
81681
81731
  return;
81682
81732
  }
81683
81733
  const parent = agent;
81684
- const { Agent: AgentClass } = await import("./chunks/src-SXUQ7OY6.js");
81734
+ const { Agent: AgentClass } = await import("./chunks/src-VCWCE7YV.js");
81685
81735
  const sideAgent = new AgentClass({
81686
81736
  initialState: {
81687
81737
  systemPrompt: parent.state.systemPrompt,
@@ -82025,7 +82075,7 @@ async function runJsonEventsMode(params) {
82025
82075
  if (runtime.initialModel.unconfigured || controller) {
82026
82076
  return;
82027
82077
  }
82028
- const { Agent: AgentClass } = await import("./chunks/src-SXUQ7OY6.js");
82078
+ const { Agent: AgentClass } = await import("./chunks/src-VCWCE7YV.js");
82029
82079
  const jsonModeExtensions = await loadJsonModeExtensions({ runtime, getAgent: () => agent });
82030
82080
  extensionRunner = jsonModeExtensions.runner;
82031
82081
  const tools = [...runtime.tools, ...jsonModeExtensions.extensionTools];
@@ -82614,7 +82664,7 @@ async function runWithJsonEventsConsoleIsolation(fn) {
82614
82664
  }
82615
82665
  async function createCliTestStreamFnFromEnv() {
82616
82666
  if (!process.env.PIE_TEST_MOCK_STREAM_SEQUENCE) return void 0;
82617
- return (await import("./chunks/test-stream-OD3B57AS.js")).createTestStreamFnFromSequenceEnv();
82667
+ return (await import("./chunks/test-stream-GOSEWMJ3.js")).createTestStreamFnFromSequenceEnv();
82618
82668
  }
82619
82669
  function resolveJsonEventsDetailFlag() {
82620
82670
  for (let index = 0; index < process.argv.length; index += 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "validate:models": "tsx scripts/validate-models-config.ts",
18
18
  "test": "npm run test:offline",
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
- "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",
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/axi-stop-semantics.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
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/auto-compaction-ui.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/tree-selector-pagination.test.ts test/session-control.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/bash-process-group.test.ts test/bash-artifact-sink.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",
@@ -1,14 +0,0 @@
1
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
- import {
3
- Agent,
4
- agentLoop,
5
- agentLoopContinue
6
- } from "./chunk-ZKHMNEXL.js";
7
- import "./chunk-YRXXRLMQ.js";
8
- import "./chunk-XXGYUYXI.js";
9
- import "./chunk-LZQXQOPJ.js";
10
- export {
11
- Agent,
12
- agentLoop,
13
- agentLoopContinue
14
- };