@cydm/pie 2.6.0 → 2.6.2

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-AZ336HKF.js";
4
+ } from "../../../chunks/chunk-HT3OYGS7.js";
5
5
  import "../../../chunks/chunk-Y4CYXJR7.js";
6
6
  import "../../../chunks/chunk-O2DKMLNX.js";
7
7
  import "../../../chunks/chunk-XXGYUYXI.js";
@@ -7932,7 +7932,7 @@ function currentLookOrThrow() {
7932
7932
  }
7933
7933
  function ensurePointIsInLookImage(x, y, look, errorPrefix = "Coordinates") {
7934
7934
  if (!look.image?.jpegBase64) {
7935
- throw new Error(`${errorPrefix} require an image-bearing root. This look is outline-only; use an @e ref with a semantic action or observe an image-bearing root.`);
7935
+ throw new Error(`${errorPrefix} require an image-bearing root, but this look is outline-only. If the action already used an @e ref, that element exposes no semantic action here (text input or picture-only) and fell back to coordinates. Call observe_ui with mode "visual" to capture an image, then act again by @e ref or by coordinates from that image.`);
7936
7936
  }
7937
7937
  if (!Number.isFinite(x) || !Number.isFinite(y)) {
7938
7938
  throw new Error(`${errorPrefix} must be finite numbers.`);
@@ -8279,6 +8279,13 @@ function validateCondition(params) {
8279
8279
  if (value && !params.ref) throw new Error("A value UI condition requires an exact ref.");
8280
8280
  return { text, role, value, scopeRef, scopeExact: Boolean(params.ref), gone: params.until === "absent", timeoutMs: normalizeWaitTimeoutMs(params.timeoutMs) };
8281
8281
  }
8282
+ function expectCondition(expect) {
8283
+ if (!expect) return void 0;
8284
+ const hasMatcher = Boolean(trimOrUndefined(expect.text) ?? trimOrUndefined(expect.role) ?? trimOrUndefined(expect.value));
8285
+ const hasScope = Boolean(conditionScopeRef(expect));
8286
+ if (!hasMatcher && !hasScope) return void 0;
8287
+ return validateCondition(expect);
8288
+ }
8282
8289
  function nodeWithinScope(node, scopeRef, scopeExact) {
8283
8290
  if (!scopeRef) return true;
8284
8291
  if (scopeExact) return node.ref === scopeRef;
@@ -8696,7 +8703,7 @@ async function performDesktopTransaction(params, actions, signal) {
8696
8703
  validateStateId(params.stateId);
8697
8704
  const look = currentLookOrThrow();
8698
8705
  const baseView = { stateId: state.currentCapture.stateId, outline: state.currentOutline };
8699
- const condition = params.expect ? validateCondition(params.expect) : void 0;
8706
+ const condition = expectCondition(params.expect);
8700
8707
  const scopeNode = condition ? conditionScopeNode(look.parsedOutline, condition) : void 0;
8701
8708
  const target = await ensureTargetWindowId(await resolveCurrentTarget(signal), signal);
8702
8709
  const noteBefore = state.currentNote;
@@ -8759,7 +8766,7 @@ async function performBrowserTransaction(params, actions, signal) {
8759
8766
  const baseSnapshot = operationState().browserSnapshot;
8760
8767
  if (!baseSnapshot) throw new Error("Browser transaction requires a complete base observation.");
8761
8768
  const baseView = { stateId: baseSnapshot.snapshotId, outline: baseSnapshot.outline };
8762
- const condition = params.expect ? validateCondition(params.expect) : void 0;
8769
+ const condition = expectCondition(params.expect);
8763
8770
  if (condition) conditionScopeNode(restoreOutline(baseSnapshot.outline), condition);
8764
8771
  const prepared = actions.map((action) => {
8765
8772
  if (!BROWSER_TRANSACTION_ACTIONS.has(action.action)) throw new Error(`Browser transactions do not support '${action.action}'.`);
@@ -7,7 +7,7 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-AZ336HKF.js";
10
+ } from "../../../chunks/chunk-HT3OYGS7.js";
11
11
  import "../../../chunks/chunk-Y4CYXJR7.js";
12
12
  import "../../../chunks/chunk-O2DKMLNX.js";
13
13
  import {
@@ -1,11 +1,11 @@
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-G6KCJA6B.js";
4
+ } from "../../../chunks/chunk-INQMGIKU.js";
5
5
  import {
6
6
  createSharedFileSystemTools,
7
7
  createSubagentCapability
8
- } from "../../../chunks/chunk-AZ336HKF.js";
8
+ } from "../../../chunks/chunk-HT3OYGS7.js";
9
9
  import "../../../chunks/chunk-Y4CYXJR7.js";
10
10
  import "../../../chunks/chunk-O2DKMLNX.js";
11
11
  import "../../../chunks/chunk-5353DEEC.js";
@@ -8,7 +8,7 @@ import {
8
8
  executeManageTodoList,
9
9
  executionStateToTodos,
10
10
  restoreExecutionState
11
- } from "../../../chunks/chunk-AZ336HKF.js";
11
+ } from "../../../chunks/chunk-HT3OYGS7.js";
12
12
  import "../../../chunks/chunk-Y4CYXJR7.js";
13
13
  import "../../../chunks/chunk-O2DKMLNX.js";
14
14
  import "../../../chunks/chunk-XXGYUYXI.js";
@@ -1525,6 +1525,9 @@ function buildSubagentPlan(deps, task, name, priority, timeoutSeconds, modelOver
1525
1525
  };
1526
1526
  return { plan, tools: filteredTools, apiKey, model };
1527
1527
  }
1528
+ function subagentOutputBudget(model) {
1529
+ return Math.min(model.maxTokens || 8192, 16384);
1530
+ }
1528
1531
  function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingLevel) {
1529
1532
  if (deps.createAgent) {
1530
1533
  return deps.createAgent({
@@ -1542,7 +1545,14 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
1542
1545
  tools,
1543
1546
  thinkingLevel
1544
1547
  },
1545
- apiKey
1548
+ apiKey,
1549
+ // Subagent turns are single-purpose report generations: request an
1550
+ // explicit output budget (see subagentOutputBudget) instead of leaving
1551
+ // it to endpoint defaults that silently truncate long reports.
1552
+ streamFn: (streamModel, context, options) => streamSimple(streamModel, context, {
1553
+ ...options,
1554
+ maxTokens: options?.maxTokens ?? subagentOutputBudget(streamModel)
1555
+ })
1546
1556
  });
1547
1557
  }
1548
1558
  function extractEvidenceFromToolArgs(toolName, args) {
@@ -1701,7 +1711,20 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
1701
1711
  }
1702
1712
  const messages = subagent.state.messages;
1703
1713
  deps.log(`[Subagent ${task.name}] Total messages: ${messages.length}`);
1704
- const response = collectLastAssistantText(messages) || "[No response]";
1714
+ const lastAssistant = [...messages].reverse().find((m) => m?.role === "assistant");
1715
+ const terminalStopReason = lastAssistant?.stopReason;
1716
+ const terminalError = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
1717
+ const collectedResponse = collectLastAssistantText(messages);
1718
+ if (terminalStopReason === "error" || terminalStopReason === "aborted" || terminalError || !collectedResponse) {
1719
+ const detail = terminalError ? `: ${terminalError.slice(0, 200)}` : "";
1720
+ throw new Error(`Subagent stream interrupted (SSE stream aborted before the report was produced, stopReason=${terminalStopReason ?? "none"}${collectedResponse ? ", partial transcript discarded" : ", no text response"})${detail}`);
1721
+ }
1722
+ let response = collectedResponse;
1723
+ const outputTruncated = terminalStopReason === "length";
1724
+ if (outputTruncated) {
1725
+ task.confidenceSignals.push({ type: "output_truncated", message: "final response hit the provider's output token limit" });
1726
+ response += "\n\n[Incomplete report: the subagent's final response was cut off by the provider's output token limit (stopReason=length). The items above may be a partial list \u2014 rerun the task with a narrower scope, or ask for the remaining items.]";
1727
+ }
1705
1728
  const reportedEvidence = extractEvidenceFromText(response).map((evidence) => ({ ...evidence, source: "reported" }));
1706
1729
  task.evidence.push(...reportedEvidence.map(({ source: _source, ...evidence }) => evidence));
1707
1730
  task.reportedEvidence.push(...reportedEvidence);
@@ -1715,9 +1738,10 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
1715
1738
  task.details = {
1716
1739
  turns: assistantTurns,
1717
1740
  toolCalls,
1718
- durationMs
1741
+ durationMs,
1742
+ ...outputTruncated ? { outputTruncated: true } : {}
1719
1743
  };
1720
- task.parentHandoffRequired = isParentHandoffRequired(task);
1744
+ task.parentHandoffRequired = isParentHandoffRequired(task) || outputTruncated;
1721
1745
  task.completedAt = Date.now();
1722
1746
  reportProgress({ message: `[${task.name}] Completed (${durationMs}ms, ${toolCalls} tools)`, increment: 100 });
1723
1747
  } catch (error) {
@@ -7841,7 +7865,7 @@ var AskUserMultiParamsSchema = Type.Object({
7841
7865
  }))
7842
7866
  });
7843
7867
  function buildUnsupportedResult(error) {
7844
- const message = error instanceof InteractionUnavailableError ? error.message : error instanceof Error ? error.message : String(error);
7868
+ const message = error instanceof InteractionUnavailableError ? `${error.message}. This host has no interactive UI registered (headless or print run) \u2014 do not retry ask_user tools here; proceed without asking, or state the question as plain text.` : error instanceof Error ? error.message : String(error);
7845
7869
  return {
7846
7870
  content: [{ type: "text", text: `Interaction unavailable: ${message}` }],
7847
7871
  details: { unsupported: true, error: message },
@@ -8057,6 +8081,13 @@ function createAskUserCapability() {
8057
8081
  parameters: AskUserMultiParamsSchema,
8058
8082
  async execute(toolCallIdOrArgs, maybeArgs) {
8059
8083
  try {
8084
+ if (!hasInteractionHandler()) {
8085
+ return {
8086
+ content: [{ type: "text", text: "Interaction unavailable: this host has no interactive UI registered (headless or print run). Do not retry ask_user_multi here \u2014 proceed without asking, or state the question as plain text." }],
8087
+ details: { unsupported: true, error: "no interaction handler" },
8088
+ isError: true
8089
+ };
8090
+ }
8060
8091
  const args = typeof toolCallIdOrArgs === "string" && maybeArgs !== void 0 ? maybeArgs : toolCallIdOrArgs;
8061
8092
  const normalized = normalizeAskUserArgs(args);
8062
8093
  if ("error" in normalized) {
@@ -12844,7 +12875,7 @@ function createGrepTextTool(cwd, options) {
12844
12875
  Type.Boolean({ description: "Treat pattern as literal string instead of regex (default: false)" })
12845
12876
  ),
12846
12877
  context: Type.Optional(
12847
- Type.Integer({ minimum: 0, maximum: MAX_GREP_CONTEXT_LINES, description: `Number of lines to show before and after each match (default: 0, maximum: ${MAX_GREP_CONTEXT_LINES})` })
12878
+ Type.Integer({ minimum: 0, maximum: MAX_GREP_CONTEXT_LINES * 10, description: `Number of lines to show before and after each match (default: 0; values above ${MAX_GREP_CONTEXT_LINES} are clamped to ${MAX_GREP_CONTEXT_LINES})` })
12848
12879
  ),
12849
12880
  outputMode: Type.Optional(Type.Union([
12850
12881
  Type.Literal("content"),
@@ -12899,8 +12930,7 @@ function createGrepTextTool(cwd, options) {
12899
12930
  const offset = normalizeNonNegativeInteger(rawOffset, 0, "offset");
12900
12931
  const effectiveLimit = normalizeNonNegativeInteger(limit, DEFAULT_LIMIT2, "limit");
12901
12932
  if (effectiveLimit < 1 || effectiveLimit > MAX_GREP_RESULT_LIMIT) throw new Error(`Invalid grep_text arguments: limit must be between 1 and ${MAX_GREP_RESULT_LIMIT}.`);
12902
- const ctx = normalizeNonNegativeInteger(contextLines, 0, "context");
12903
- if (ctx > MAX_GREP_CONTEXT_LINES) throw new Error(`Invalid grep_text arguments: context must be at most ${MAX_GREP_CONTEXT_LINES}.`);
12933
+ const ctx = Math.min(normalizeNonNegativeInteger(contextLines, 0, "context"), MAX_GREP_CONTEXT_LINES);
12904
12934
  let isDir;
12905
12935
  try {
12906
12936
  const stat = await fs2.stat(searchPath);
@@ -15343,9 +15373,18 @@ var webResearchSchema = Type.Object({
15343
15373
  description: "Maximum credible sources to fetch and cite. Defaults to 3."
15344
15374
  }))
15345
15375
  });
15376
+ function httpStatusFromFailureText(text) {
15377
+ const match2 = /\bHTTP\s?(\d{3})\b/i.exec(text);
15378
+ return match2 ? Number(match2[1]) : void 0;
15379
+ }
15346
15380
  var DEFAULT_WEB_RESEARCH_SEARCH_ATTEMPT_TIMEOUT_MS = 45e3;
15347
- var DEFAULT_WEB_RESEARCH_FETCH_ATTEMPT_TIMEOUT_MS = 3e4;
15381
+ var DEFAULT_WEB_RESEARCH_FETCH_ATTEMPT_TIMEOUT_MS = 45e3;
15348
15382
  var DEFAULT_WEB_RESEARCH_BROWSER_ATTEMPT_TIMEOUT_MS = 45e3;
15383
+ function isTransientFetchFailure(category, text) {
15384
+ if (category === "timeout" || category === "provider_error") return true;
15385
+ if (category === "http_error") return /\bHTTP\s?(5\d\d|429)\b/i.test(text);
15386
+ return false;
15387
+ }
15349
15388
  function isRecord5(value) {
15350
15389
  return !!value && typeof value === "object" && !Array.isArray(value);
15351
15390
  }
@@ -16083,36 +16122,48 @@ function createSharedWebResearchTool(deps) {
16083
16122
  return true;
16084
16123
  });
16085
16124
  const fetchScoredSource = async (scored) => {
16086
- const startedAt = Date.now();
16087
16125
  onUpdate?.({
16088
16126
  content: [{ type: "text", text: `Fetching source: ${scored.source.url}` }],
16089
16127
  details: { query, actualQueries: [...new Set(actualQueries)], sources: dedupedSources, fetchedSources, providerAttempts, attempts, qualityScore: qualityScore(fetchedSources, scoredSources), warnings, browserRequiredSources }
16090
16128
  });
16091
- const fetchResult = await runWithDeadline(
16092
- (childSignal) => webFetchTool.execute("web_research_fetch", { url: scored.source.url }, childSignal),
16093
- fetchAttemptTimeoutMs,
16094
- `web_research fetch attempt timed out after ${Math.ceil(fetchAttemptTimeoutMs / 1e3)} seconds`,
16095
- signal
16096
- ).catch((error) => ({
16097
- content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
16098
- details: {
16099
- url: scored.source.url,
16100
- sourceUrl: scored.source.url,
16101
- durationMs: Date.now() - startedAt,
16102
- failureCategory: "timeout"
16103
- },
16104
- isError: true
16105
- }));
16129
+ const fetchOnce = async () => {
16130
+ const startedAt = Date.now();
16131
+ const result = await runWithDeadline(
16132
+ (childSignal) => webFetchTool.execute("web_research_fetch", { url: scored.source.url }, childSignal),
16133
+ fetchAttemptTimeoutMs,
16134
+ `web_research fetch attempt timed out after ${Math.ceil(fetchAttemptTimeoutMs / 1e3)} seconds`,
16135
+ signal
16136
+ ).catch((error) => ({
16137
+ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
16138
+ details: {
16139
+ url: scored.source.url,
16140
+ sourceUrl: scored.source.url,
16141
+ durationMs: Date.now() - startedAt,
16142
+ failureCategory: "timeout"
16143
+ },
16144
+ isError: true
16145
+ }));
16146
+ return { result, startedAt, endedAt: Date.now() };
16147
+ };
16148
+ let attempt = await fetchOnce();
16149
+ let attemptTotalMs = attempt.endedAt - attempt.startedAt;
16150
+ if (attempt.result.isError && isTransientFetchFailure(contentDetails(attempt.result).failureCategory, textFromResult(attempt.result))) {
16151
+ attempts.push({ phase: "fetch", url: scored.source.url, status: "failed", failureCategory: contentDetails(attempt.result).failureCategory, failureStatus: httpStatusFromFailureText(textFromResult(attempt.result)), durationMs: attemptTotalMs });
16152
+ attempt = await fetchOnce();
16153
+ attemptTotalMs += attempt.endedAt - attempt.startedAt;
16154
+ }
16155
+ const fetchResult = attempt.result;
16106
16156
  const details2 = contentDetails(fetchResult);
16107
16157
  const text = textFromResult(fetchResult);
16108
- fetchDurationMs += Date.now() - startedAt;
16109
- sourceReadDurations.push(Date.now() - startedAt);
16158
+ fetchDurationMs += attemptTotalMs;
16159
+ sourceReadDurations.push(attemptTotalMs);
16110
16160
  attempts.push({
16111
16161
  phase: "fetch",
16112
16162
  url: scored.source.url,
16113
16163
  status: fetchResult.isError ? "failed" : "completed",
16114
16164
  failureCategory: details2.failureCategory,
16115
- durationMs: Date.now() - startedAt
16165
+ ...fetchResult.isError ? { failureStatus: httpStatusFromFailureText(text) } : {},
16166
+ durationMs: attemptTotalMs
16116
16167
  });
16117
16168
  if (details2.requiresBrowser) {
16118
16169
  const browserEntry = {
@@ -16219,9 +16270,12 @@ function createSharedWebResearchTool(deps) {
16219
16270
  }
16220
16271
  };
16221
16272
  if (fetchedSources.length === 0) {
16273
+ const fetchFailures = details.attempts.filter((attempt) => attempt.phase === "fetch" && attempt.status === "failed").slice(0, 6);
16274
+ const failureSummary = fetchFailures.length ? `
16275
+ Fetch failures: ${fetchFailures.map((attempt) => `${attempt.url} (${attempt.failureCategory || "fetch_failed"}${attempt.failureStatus !== void 0 ? ` ${attempt.failureStatus}` : ""})`).join("; ")}` : "";
16222
16276
  const reason = browserRequiredSources.length ? "web_research found candidate sources, but they require browser-rendered reading and no host browser reader completed." : "web_research could not fetch any credible source for this query.";
16223
16277
  return {
16224
- content: [{ type: "text", text: `${reason}
16278
+ content: [{ type: "text", text: `${reason}${failureSummary}
16225
16279
 
16226
16280
  Queries tried: ${details.actualQueries.join("; ") || query}` }],
16227
16281
  details,
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-AZ336HKF.js";
11
+ } from "./chunk-HT3OYGS7.js";
12
12
  import {
13
13
  getBuiltinDir,
14
14
  getConfigDir
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createCliWebResearchHealthStore,
8
8
  formatSize,
9
9
  truncateHeadTail
10
- } from "./chunks/chunk-G6KCJA6B.js";
10
+ } from "./chunks/chunk-INQMGIKU.js";
11
11
  import {
12
12
  AGENTS_CONTEXT_FILE_NAME,
13
13
  AgentSessionController,
@@ -60,7 +60,7 @@ import {
60
60
  setExtensionEnabledInSettings,
61
61
  shouldPreserveExecutionStateForUserText,
62
62
  supersedeExecutionState
63
- } from "./chunks/chunk-AZ336HKF.js";
63
+ } from "./chunks/chunk-HT3OYGS7.js";
64
64
  import "./chunks/chunk-Y4CYXJR7.js";
65
65
  import {
66
66
  Deref,
@@ -53581,17 +53581,40 @@ function filterEnabledTools(tools, settingsManager) {
53581
53581
  const disabled = getDisabledToolNames(settingsManager);
53582
53582
  return disabled.size > 0 ? tools.filter((tool) => !disabled.has(tool.name)) : [...tools];
53583
53583
  }
53584
+ function mergeSnapshotDelta(atLoad, current, onDisk) {
53585
+ const merged = { ...onDisk };
53586
+ const load = atLoad;
53587
+ const cur = current;
53588
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(load), ...Object.keys(cur)]);
53589
+ for (const key of allKeys) {
53590
+ const before = JSON.stringify(load[key]);
53591
+ const after = JSON.stringify(cur[key]);
53592
+ if (before === after) continue;
53593
+ if (after === void 0) delete merged[key];
53594
+ else merged[key] = cur[key];
53595
+ }
53596
+ return merged;
53597
+ }
53584
53598
  var SettingsManager = class {
53585
53599
  settingsPath;
53586
53600
  legacyConfigPath;
53587
53601
  settings;
53588
53602
  legacyConfig;
53603
+ /** Last state this instance successfully persisted (starts at the load
53604
+ * snapshot). The delta vs THIS — not vs the load snapshot — is what a
53605
+ * write may persist: a load-time baseline misses in-process
53606
+ * write-after-write (e.g. disable then re-enable must prune the key, not
53607
+ * 'revert to load state = untouched'). */
53608
+ settingsLastWritten;
53609
+ legacyConfigLastWritten;
53589
53610
  constructor() {
53590
53611
  const configDir = getConfigDir();
53591
53612
  this.settingsPath = getSettingsPath();
53592
53613
  this.legacyConfigPath = `${configDir}/config.json`;
53593
53614
  this.settings = this.loadSettingsFile();
53615
+ this.settingsLastWritten = structuredClone(this.settings);
53594
53616
  this.legacyConfig = this.loadLegacyConfigFile();
53617
+ this.legacyConfigLastWritten = structuredClone(this.legacyConfig);
53595
53618
  }
53596
53619
  readJson(filePath) {
53597
53620
  if (!fs4.existsSync(filePath)) return null;
@@ -53612,7 +53635,38 @@ var SettingsManager = class {
53612
53635
  }
53613
53636
  writeSettings() {
53614
53637
  this.ensureDir();
53615
- fs4.writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2));
53638
+ const written = this.writeMergedOrFull(() => this.settingsPath, () => this.settingsLastWritten, () => this.settings, (value) => this.writeSettingsValue(value));
53639
+ this.settings = structuredClone(written);
53640
+ this.settingsLastWritten = structuredClone(written);
53641
+ }
53642
+ /** Merged write for an explicit candidate state (setters that build a
53643
+ * nextSettings object): writes FIRST (a failed write leaves this
53644
+ * instance's cached state untouched), then returns the value actually
53645
+ * written — callers assign THAT (not their candidate, which lacks keys
53646
+ * preserved from disk) so state and lastWritten never diverge. */
53647
+ writeSettingsWith(next) {
53648
+ const written = this.writeMergedOrFull(() => this.settingsPath, () => this.settingsLastWritten, () => next, (value) => this.writeSettingsValue(value));
53649
+ this.settingsLastWritten = structuredClone(written);
53650
+ return written;
53651
+ }
53652
+ /** Shared write path: merge our delta onto the current on-disk object and
53653
+ * return the value actually written. When the file EXISTS but does not
53654
+ * parse (another process mid-write over a non-atomic writeFileSync),
53655
+ * merging onto {} would silently delete every unrelated key — fall back
53656
+ * to the full candidate write instead. */
53657
+ writeMergedOrFull(path34, lastWritten, current, write) {
53658
+ let onDisk = null;
53659
+ let corrupt = false;
53660
+ if (fs4.existsSync(path34())) {
53661
+ try {
53662
+ onDisk = JSON.parse(fs4.readFileSync(path34(), "utf-8"));
53663
+ } catch {
53664
+ corrupt = true;
53665
+ }
53666
+ }
53667
+ const value = corrupt ? current() : mergeSnapshotDelta(lastWritten(), current(), onDisk ?? {});
53668
+ write(value);
53669
+ return value;
53616
53670
  }
53617
53671
  writeSettingsValue(settings) {
53618
53672
  this.ensureDir();
@@ -53620,7 +53674,10 @@ var SettingsManager = class {
53620
53674
  }
53621
53675
  writeLegacyConfig() {
53622
53676
  this.ensureDir();
53623
- fs4.writeFileSync(this.legacyConfigPath, JSON.stringify(this.legacyConfig, null, 2));
53677
+ this.writeMergedOrFull(() => this.legacyConfigPath, () => this.legacyConfigLastWritten, () => this.legacyConfig, (value) => {
53678
+ fs4.writeFileSync(this.legacyConfigPath, JSON.stringify(value, null, 2));
53679
+ this.legacyConfigLastWritten = structuredClone(value);
53680
+ });
53624
53681
  }
53625
53682
  getLegacyApiKey() {
53626
53683
  return this.legacyConfig.apiKey;
@@ -53690,6 +53747,13 @@ var SettingsManager = class {
53690
53747
  this.settings.calmMode = enabled;
53691
53748
  this.writeSettings();
53692
53749
  }
53750
+ getToolOutputExpanded() {
53751
+ return this.settings.toolOutputExpanded === true;
53752
+ }
53753
+ setToolOutputExpanded(expanded) {
53754
+ this.settings.toolOutputExpanded = expanded;
53755
+ this.writeSettings();
53756
+ }
53693
53757
  // ==========================================================================
53694
53758
  // Extension enablement (user ~/.pie/settings.json + project ./.pie/settings.json)
53695
53759
  // ==========================================================================
@@ -53734,8 +53798,7 @@ var SettingsManager = class {
53734
53798
  extensions: this.settings.extensions ? { ...this.settings.extensions } : void 0
53735
53799
  };
53736
53800
  setExtensionEnabledInSettings(nextSettings, identity, enabled);
53737
- this.writeSettingsValue(nextSettings);
53738
- this.settings = nextSettings;
53801
+ this.settings = this.writeSettingsWith(nextSettings);
53739
53802
  }
53740
53803
  /** Raw skill enablement map. */
53741
53804
  getSkillSettings() {
@@ -53749,8 +53812,7 @@ var SettingsManager = class {
53749
53812
  setSkillEnabled(name, enabled) {
53750
53813
  const nextSettings = { ...this.settings };
53751
53814
  setEnablementInMap(nextSettings, "skills", name, enabled);
53752
- this.writeSettingsValue(nextSettings);
53753
- this.settings = nextSettings;
53815
+ this.settings = this.writeSettingsWith(nextSettings);
53754
53816
  }
53755
53817
  /** Disabled runtime tool names (may contain names no longer installed). */
53756
53818
  getDisabledTools() {
@@ -53772,8 +53834,7 @@ var SettingsManager = class {
53772
53834
  if (Object.keys(nextTools).length === 0) {
53773
53835
  delete nextSettings.tools;
53774
53836
  }
53775
- this.writeSettingsValue(nextSettings);
53776
- this.settings = nextSettings;
53837
+ this.settings = this.writeSettingsWith(nextSettings);
53777
53838
  }
53778
53839
  };
53779
53840
 
@@ -63776,11 +63837,15 @@ function sanitizeNotificationText(text) {
63776
63837
  function buildDesktopNotificationSequences(title, body) {
63777
63838
  const sanitizedTitle = sanitizeNotificationText(title);
63778
63839
  const sanitizedBody = sanitizeNotificationText(body);
63779
- return [
63780
- `\x1B]9;${sanitizedTitle}: ${sanitizedBody}\x07`,
63781
- `\x1B]777;notify;${sanitizedTitle};${sanitizedBody}\x07`,
63782
- `\x1B]99;i=1:d=0;${sanitizedBody}\x07`
63783
- ];
63840
+ const program = process.env.TERM_PROGRAM ?? "";
63841
+ const term = process.env.TERM ?? "";
63842
+ if (/kitty/i.test(program) || /^xterm-kitty/.test(term)) {
63843
+ return [`\x1B]99;i=1:d=0;${sanitizedBody}\x07`];
63844
+ }
63845
+ if (/rxvt|terminology/i.test(program) || /^rxvt-unicode/.test(term)) {
63846
+ return [`\x1B]777;notify;${sanitizedTitle};${sanitizedBody}\x07`];
63847
+ }
63848
+ return [`\x1B]9;${sanitizedTitle}: ${sanitizedBody}\x07`];
63784
63849
  }
63785
63850
 
63786
63851
  // src/extensions/runner.ts
@@ -72844,12 +72909,6 @@ async function loadSession(host, sessionId) {
72844
72909
  host.agentSessionController?.drainQueuedInputsForEditor();
72845
72910
  host.updatePendingMessagesDisplay();
72846
72911
  renderChatHistory(host, messages);
72847
- if (messages.length > 50) {
72848
- host.chatContainer.addChild(new Spacer(1));
72849
- host.chatContainer.addChild(
72850
- new Text(theme.fg("dim", `... (${messages.length - 50} earlier messages hidden)`), 1, 0)
72851
- );
72852
- }
72853
72912
  host.chatContainer.addChild(new Spacer(1));
72854
72913
  host.chatContainer.addChild(
72855
72914
  new Text(theme.fg("accent", `\u2713 Loaded session: ${sessionId.slice(0, 8)}...`), 1, 0)
@@ -73073,7 +73132,7 @@ async function navigateToEntry(host, entryId) {
73073
73132
  }
73074
73133
  }
73075
73134
  function renderChatHistory(host, messages) {
73076
- const messagesToShow = messages.slice(-50);
73135
+ const messagesToShow = messages;
73077
73136
  const toolResultMap = /* @__PURE__ */ new Map();
73078
73137
  for (const msg of messages) {
73079
73138
  if (msg.role === "toolResult") {
@@ -78441,6 +78500,7 @@ function createInteractiveAgentSessionController(host) {
78441
78500
  }
78442
78501
  let retryElapsedTicker;
78443
78502
  let retryElapsedStartedAt = 0;
78503
+ let pendingSettleKind;
78444
78504
  const stopRetryElapsedTicker = () => {
78445
78505
  if (retryElapsedTicker !== void 0) {
78446
78506
  clearInterval(retryElapsedTicker);
@@ -78584,10 +78644,14 @@ function createInteractiveAgentSessionController(host) {
78584
78644
  host.appendWarningSummary(outcome.terminalSummary);
78585
78645
  }
78586
78646
  host.sessionTrace.completeTurn(event.message, false);
78587
- ringTurnSettledBell(host);
78647
+ pendingSettleKind = "completed";
78588
78648
  host._semanticTurnIndex++;
78589
78649
  } else if (event.type === "turn_settled") {
78590
78650
  host.setControllerBusy(false);
78651
+ if (pendingSettleKind === "completed") {
78652
+ ringTurnSettledBell(host);
78653
+ }
78654
+ pendingSettleKind = void 0;
78591
78655
  } else if (event.type === "turn_failed_terminal") {
78592
78656
  host.clearContinuationTimeout("turn_failed_terminal");
78593
78657
  if (event.message) {
@@ -78623,6 +78687,7 @@ function createInteractiveAgentSessionController(host) {
78623
78687
  }
78624
78688
  host.showError(event.failure.message);
78625
78689
  host.ringAttentionBell?.("turn-failed");
78690
+ pendingSettleKind = "failed";
78626
78691
  host.setControllerBusy(false);
78627
78692
  } else if (event.type === "turn_cancelled") {
78628
78693
  const outcome = host.executionState.abort();
@@ -78639,6 +78704,7 @@ function createInteractiveAgentSessionController(host) {
78639
78704
  if (outcome.terminalSummary) {
78640
78705
  host.appendWarningSummary(outcome.terminalSummary);
78641
78706
  }
78707
+ pendingSettleKind = "cancelled";
78642
78708
  host.setControllerBusy(false);
78643
78709
  } else if (event.type === "runtime_guard_advisory") {
78644
78710
  host.noteRuntimeGuardAdvisory(event);
@@ -79839,6 +79905,7 @@ var InteractiveMode = class {
79839
79905
  this.skillsSection = formatSkillsForPrompt(options.skills);
79840
79906
  this.knowledgeSection = buildCliProjectContextSection(options.cwd);
79841
79907
  this.calmMode = options.settingsManager.getCalmModeEnabled();
79908
+ this.toolOutputExpanded = options.settingsManager.getToolOutputExpanded();
79842
79909
  }
79843
79910
  options;
79844
79911
  get runtimeEventHost() {
@@ -80391,7 +80458,8 @@ var InteractiveMode = class {
80391
80458
  return interactiveCommands.some((cmd) => commandPath.startsWith(cmd));
80392
80459
  }
80393
80460
  toggleToolsExpansion() {
80394
- return toggleToolsExpansion(this.uiHelperHost);
80461
+ toggleToolsExpansion(this.uiHelperHost);
80462
+ this.options.settingsManager.setToolOutputExpanded(this.toolOutputExpanded);
80395
80463
  }
80396
80464
  showSelector(create) {
80397
80465
  return showSelector(this.uiHelperHost, create);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {