privateer-agent 0.14.6 → 0.15.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,8 +1,8 @@
1
1
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/config.js b/node_modules/@earendil-works/pi-coding-agent/dist/config.js
2
- index 872dd55..edd61db 100644
2
+ index 62eb303..b5e56ff 100644
3
3
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/config.js
4
4
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/config.js
5
- @@ -399,9 +399,72 @@ catch (e) {
5
+ @@ -469,9 +469,72 @@ catch (e) {
6
6
  const piConfigName = pkg.piConfig?.name;
7
7
  export const PACKAGE_NAME = pkg.name || "@earendil-works/pi-coding-agent";
8
8
  export const APP_NAME = piConfigName || "pi";
@@ -77,13 +77,13 @@ index 872dd55..edd61db 100644
77
77
  export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
78
78
  export const ENV_SESSION_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_SESSION_DIR`;
79
79
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
80
- index 638646d..ba1da98 100644
80
+ index 11ab140..46acdfd 100644
81
81
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
82
82
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
83
- @@ -39,6 +39,129 @@ import { createLocalBashOperations } from "./tools/bash.js";
84
- import { createAllToolDefinitions } from "./tools/index.js";
83
+ @@ -46,6 +46,129 @@ import { createAllToolDefinitions } from "./tools/index.js";
85
84
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
86
- import { addUsageToTotals, createUsageTotals } from "./usage-totals.js";
85
+ import { addUsageToTotals, combineUsage, createUsageTotals } from "./usage-totals.js";
86
+ import { findLatestResponse, getBranchSelection, getVirtualModelState, isVirtualModel, VIRTUAL_MODEL_STATE_ENTRY, } from "./virtual-models.js";
87
87
  +// ============================================================================
88
88
  +// Privateer patch: provider errors that aren't API responses
89
89
  +//
@@ -210,16 +210,16 @@ index 638646d..ba1da98 100644
210
210
  /**
211
211
  * Parse a skill block from message text.
212
212
  * Returns null if the text doesn't contain a skill block.
213
- @@ -101,6 +224,8 @@ export class AgentSession {
213
+ @@ -108,6 +231,8 @@ export class AgentSession {
214
214
  // Retry state
215
215
  _retryAbortController = undefined;
216
216
  _retryAttempt = 0;
217
217
  + // Privateer patch: when the provider last exhausted the retry budget on a 429.
218
218
  + _pvThrottledAt = 0;
219
- // Bash execution state
220
- _bashAbortControllers = new Set();
221
- _pendingBashMessages = [];
222
- @@ -192,6 +317,14 @@ export class AgentSession {
219
+ /**
220
+ * Failed response that the next request repeats, set by auto-retry and overflow recovery. The
221
+ * retry is routed with it as `failed`, since the context no longer contains it.
222
+ @@ -240,6 +365,14 @@ export class AgentSession {
223
223
  }
224
224
  const isOAuth = this._modelRuntime.isUsingOAuth(model.provider);
225
225
  if (isOAuth) {
@@ -234,7 +234,7 @@ index 638646d..ba1da98 100644
234
234
  throw new Error(`Authentication failed for "${model.provider}". ` +
235
235
  `Credentials may have expired or network is unavailable. ` +
236
236
  `Run '/login ${model.provider}' to re-authenticate.`);
237
- @@ -401,6 +534,16 @@ export class AgentSession {
237
+ @@ -749,6 +882,16 @@ export class AgentSession {
238
238
  }
239
239
  }
240
240
  }
@@ -248,11 +248,26 @@ index 638646d..ba1da98 100644
248
248
  + typeof event.message.errorMessage === "string") {
249
249
  + event.message.errorMessage = compactProviderError(event.message.errorMessage);
250
250
  + }
251
- // Emit to extensions first
251
+ // Emit to extensions first, then notify public listeners.
252
252
  await this._emitExtensionEvent(event);
253
- // Notify all listeners
254
- @@ -904,6 +1047,33 @@ export class AgentSession {
255
- finalError: msg.errorMessage,
253
+ this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);
254
+ @@ -1450,7 +1593,13 @@ export class AgentSession {
255
+ // Check if we need to compact before sending (catches aborted responses).
256
+ // The starting message is sent below, so do not call agent.continue() here.
257
+ const lastAssistant = this._findLastAssistantMessage();
258
+ - if (lastAssistant) {
259
+ + // Privateer patch: skip this while the provider is still throttling us.
260
+ + // Compaction here is another LLM call to the endpoint that just spent our
261
+ + // whole retry budget on 429s; it cannot succeed, and its failure is reported
262
+ + // as "Auto-compaction cancelled" — burying the user's actual prompt. One
263
+ + // rate-limit window of patience, then we try again as normal. The context is
264
+ + // not lost: the next turn re-checks compaction once the window has passed.
265
+ + if (lastAssistant && Date.now() - this._pvThrottledAt >= PV_MAX_RETRY_DELAY_MS) {
266
+ await this._checkCompaction(lastAssistant, false);
267
+ }
268
+ const selectedToolsBefore = this._baseSystemPromptOptions.selectedTools;
269
+ @@ -1526,6 +1675,33 @@ export class AgentSession {
270
+ finalError: message.errorMessage,
256
271
  });
257
272
  this._retryAttempt = 0;
258
273
  + // Privateer patch: remember that the endpoint is throttling us. The
@@ -260,7 +275,7 @@ index 638646d..ba1da98 100644
260
275
  + // and firing it now just spends the summarizer budget on another 429 —
261
276
  + // which is what answered the user's next message with "Auto-compaction
262
277
  + // cancelled" instead of a reply. See the guard in prompt().
263
- + if (isThrottleFailure(msg.errorMessage)) {
278
+ + if (isThrottleFailure(message.errorMessage)) {
264
279
  + this._pvThrottledAt = Date.now();
265
280
  + }
266
281
  + // Privateer patch: a transient error that survived the full retry budget
@@ -280,15 +295,15 @@ index 638646d..ba1da98 100644
280
295
  + // classifies the raw HTML body as transient (it contains "500"/"502"), so the
281
296
  + // agent looks hung — retrying a compaction that can never succeed — until the
282
297
  + // summarizer budget burns. Context overflow never matches isHardHttpFailure.
283
- + if (msg.stopReason === "error" && isHardHttpFailure(msg.errorMessage)) {
298
+ + if (message.stopReason === "error" && isHardHttpFailure(message.errorMessage)) {
284
299
  + return false;
285
300
  }
286
- if (await this._checkCompaction(msg)) {
301
+ if (await this._checkCompaction(message, true, toolResults)) {
287
302
  return !this._agentRunAbortRequested;
288
- @@ -991,6 +1161,14 @@ export class AgentSession {
289
- if (!hasConfiguredAuth) {
290
- const isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);
291
- if (isOAuth) {
303
+ @@ -1655,6 +1831,14 @@ export class AgentSession {
304
+ if (!hasConfiguredAuth) {
305
+ const isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);
306
+ if (isOAuth) {
292
307
  + // Privateer patch: the account channel has no API key that could expire —
293
308
  + // the terminal is simply not signed in yet (a fresh install now boots on
294
309
  + // `privateer/*`, which is the model it will run once logged in). Stock Pi's
@@ -297,27 +312,12 @@ index 638646d..ba1da98 100644
297
312
  + if (this.model.provider === "privateer") {
298
313
  + throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
299
314
  + }
300
- throw new Error(`Authentication failed for "${this.model.provider}". ` +
301
- `Credentials may have expired or network is unavailable. ` +
302
- `Run '/login ${this.model.provider}' to re-authenticate.`);
303
- @@ -1000,7 +1178,13 @@ export class AgentSession {
304
- // Check if we need to compact before sending (catches aborted responses).
305
- // The user's new prompt is sent below, so do not call agent.continue() here.
306
- const lastAssistant = this._findLastAssistantMessage();
307
- - if (lastAssistant) {
308
- + // Privateer patch: skip this while the provider is still throttling us.
309
- + // Compaction here is another LLM call to the endpoint that just spent our
310
- + // whole retry budget on 429s; it cannot succeed, and its failure is reported
311
- + // as "Auto-compaction cancelled" — burying the user's actual prompt. One
312
- + // rate-limit window of patience, then we try again as normal. The context is
313
- + // not lost: the next turn re-checks compaction once the window has passed.
314
- + if (lastAssistant && Date.now() - this._pvThrottledAt >= PV_MAX_RETRY_DELAY_MS) {
315
- await this._checkCompaction(lastAssistant, false);
316
- }
317
- // Build messages array (custom message if any, then user message)
318
- @@ -2351,6 +2535,34 @@ export class AgentSession {
315
+ throw new Error(`Authentication failed for "${this.model.provider}". ` +
316
+ `Credentials may have expired or network is unavailable. ` +
317
+ `Run '/login ${this.model.provider}' to re-authenticate.`);
318
+ @@ -3012,6 +3196,34 @@ export class AgentSession {
319
319
  // Context overflow is handled by compaction, not retry.
320
- if (isContextOverflow(message, this.model?.contextWindow ?? 0))
320
+ if (isContextOverflow(message, (this._modelForMessage(message) ?? this.model)?.contextWindow ?? 0))
321
321
  return false;
322
322
  + // Privateer patch: an HTTP status is a fact; a substring of the body is a guess.
323
323
  + // pi classifies by regex over the whole error text, so a 403 whose body merely
@@ -350,7 +350,7 @@ index 638646d..ba1da98 100644
350
350
  return isRetryableAssistantError(message);
351
351
  }
352
352
  /**
353
- @@ -2408,7 +2620,10 @@ export class AgentSession {
353
+ @@ -3069,7 +3281,10 @@ export class AgentSession {
354
354
  this._retryAttempt--;
355
355
  return false;
356
356
  }
@@ -408,7 +408,7 @@ index 197bccc..bc9ac3f 100644
408
408
  //# sourceMappingURL=auth-guidance.js.map
409
409
 
410
410
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js
411
- index be748bf..378f4c7 100644
411
+ index f9292e8..07c79da 100644
412
412
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js
413
413
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js
414
414
  @@ -6,7 +6,7 @@ import * as fs from "node:fs";
@@ -420,7 +420,7 @@ index be748bf..378f4c7 100644
420
420
  import { resolvePath } from "../../utils/paths.js";
421
421
  import { createEventBus } from "../event-bus.js";
422
422
  import { execCommand } from "../exec.js";
423
- @@ -401,12 +401,15 @@ async function loadExtensionModule(extensionPath, cacheToken) {
423
+ @@ -468,12 +468,15 @@ async function loadExtensionModule(extensionPath, cacheToken) {
424
424
  const createJitiImpl = await getCreateJiti();
425
425
  // Compiled binaries and the bundled Node distribution use embedded modules.
426
426
  // Source TypeScript reuses host modules and root tsconfig paths. Unbundled
@@ -438,7 +438,7 @@ index be748bf..378f4c7 100644
438
438
  const jiti = createJitiImpl(import.meta.url, {
439
439
  moduleCache: false,
440
440
  ...resolutionOptions,
441
- @@ -607,9 +610,23 @@ export async function discoverAndLoadExtensions(configuredPaths, cwd, agentDir =
441
+ @@ -674,9 +677,23 @@ export async function discoverAndLoadExtensions(configuredPaths, cwd, agentDir =
442
442
  }
443
443
  }
444
444
  };
@@ -466,7 +466,7 @@ index be748bf..378f4c7 100644
466
466
  const globalExtDir = path.join(resolvedAgentDir, "extensions");
467
467
  addPaths(discoverExtensionsInDir(globalExtDir));
468
468
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js
469
- index 696e98c..7585930 100644
469
+ index 4d54e22..3c59e90 100644
470
470
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js
471
471
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js
472
472
  @@ -24,7 +24,7 @@ import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -477,8 +477,8 @@ index 696e98c..7585930 100644
477
477
  +import { projectConfigDirs, projectConfigWriteDir, resolveProjectConfigPath } from "../config.js";
478
478
  import { spawnProcess, spawnProcessSync } from "../utils/child-process.js";
479
479
  import { parseGitUrl } from "../utils/git.js";
480
- import { canonicalizePath, isLocalPath, markPathIgnoredByCloudSync, resolvePath } from "../utils/paths.js";
481
- @@ -711,23 +711,29 @@ export class DefaultPackageManager {
480
+ import { canonicalizePath, isLocalPath, markPathIgnoredByCloudSync, normalizePath, resolvePath, } from "../utils/paths.js";
481
+ @@ -717,28 +717,36 @@ export class DefaultPackageManager {
482
482
  const packageSources = this.dedupePackages(allPackages);
483
483
  await this.resolvePackageSources(packageSources, accumulator, onMissing);
484
484
  const globalBaseDir = this.agentDir;
@@ -512,10 +512,18 @@ index 696e98c..7585930 100644
512
512
  }
513
513
  - this.addAutoDiscoveredResources(accumulator, globalSettings, projectSettings, globalBaseDir, projectBaseDir);
514
514
  + this.addAutoDiscoveredResources(accumulator, globalSettings, projectSettings, globalBaseDir, projectBaseDirs);
515
+ // Built-in extensions are enabled unless the user `extensions` setting excludes them, for example
516
+ // with `-builtin:mcp`. A matching `+`, `-`, or `!` entry in the project setting overrides that.
517
+ + // Privateer patch: built-in paths are not relative, so the first (preferred)
518
+ + // project config dir is the base for their override patterns.
519
+ for (const name of this.builtinExtensions) {
520
+ const path = `${BUILTIN_PATH_PREFIX}${name}`;
521
+ - const projectEnabled = applyAutoloadDisabledPatterns([path], getOverridePatterns(projectSettings.extensions ?? []), projectBaseDir).get(path);
522
+ + const projectEnabled = applyAutoloadDisabledPatterns([path], getOverridePatterns(projectSettings.extensions ?? []), projectBaseDirs[0]).get(path);
523
+ this.addResource(accumulator.extensions, path, { source: "builtin", scope: projectEnabled === undefined ? "user" : "project", origin: "top-level" }, projectEnabled ?? isEnabledByOverrides(path, globalSettings.extensions ?? [], globalBaseDir));
524
+ }
515
525
  return this.toResolvedPaths(accumulator);
516
- }
517
- async resolveExtensionSources(sources, options) {
518
- @@ -1681,13 +1687,30 @@ export class DefaultPackageManager {
526
+ @@ -1727,13 +1735,30 @@ export class DefaultPackageManager {
519
527
  writeFileSync(ignorePath, "*\n!.gitignore\n", "utf-8");
520
528
  }
521
529
  }
@@ -547,7 +555,7 @@ index 696e98c..7585930 100644
547
555
  }
548
556
  return join(this.agentDir, "npm");
549
557
  }
550
- @@ -1726,7 +1749,9 @@ export class DefaultPackageManager {
558
+ @@ -1772,7 +1797,9 @@ export class DefaultPackageManager {
551
559
  }
552
560
  if (scope === "project") {
553
561
  this.assertProjectTrustedForScope(scope);
@@ -558,7 +566,7 @@ index 696e98c..7585930 100644
558
566
  }
559
567
  return join(this.agentDir, "npm", "node_modules", source.name);
560
568
  }
561
- @@ -1762,7 +1787,7 @@ export class DefaultPackageManager {
569
+ @@ -1809,7 +1836,7 @@ export class DefaultPackageManager {
562
570
  }
563
571
  if (scope === "project") {
564
572
  this.assertProjectTrustedForScope(scope);
@@ -567,7 +575,7 @@ index 696e98c..7585930 100644
567
575
  }
568
576
  return join(this.agentDir, "git");
569
577
  }
570
- @@ -1785,7 +1810,7 @@ export class DefaultPackageManager {
578
+ @@ -1832,7 +1859,7 @@ export class DefaultPackageManager {
571
579
  getBaseDirForScope(scope) {
572
580
  if (scope === "project") {
573
581
  this.assertProjectTrustedForScope(scope);
@@ -576,7 +584,7 @@ index 696e98c..7585930 100644
576
584
  }
577
585
  if (scope === "user") {
578
586
  return this.agentDir;
579
- @@ -1796,7 +1821,30 @@ export class DefaultPackageManager {
587
+ @@ -1843,7 +1870,30 @@ export class DefaultPackageManager {
580
588
  return resolvePath(input, this.cwd, { homeDir: getHomeDir(), trim: true });
581
589
  }
582
590
  resolvePathFromBase(input, baseDir) {
@@ -608,7 +616,7 @@ index 696e98c..7585930 100644
608
616
  }
609
617
  collectPackageResources(packageRoot, accumulator, filter, metadata) {
610
618
  if (filter) {
611
- @@ -1936,19 +1984,24 @@ export class DefaultPackageManager {
619
+ @@ -1983,19 +2033,24 @@ export class DefaultPackageManager {
612
620
  this.addResource(target, f, metadata, enabledPaths.has(f));
613
621
  }
614
622
  }
@@ -637,7 +645,7 @@ index 696e98c..7585930 100644
637
645
  const userOverrides = {
638
646
  extensions: (globalSettings.extensions ?? []),
639
647
  skills: (globalSettings.skills ?? []),
640
- @@ -1967,12 +2020,12 @@ export class DefaultPackageManager {
648
+ @@ -2014,12 +2069,12 @@ export class DefaultPackageManager {
641
649
  prompts: join(globalBaseDir, "prompts"),
642
650
  themes: join(globalBaseDir, "themes"),
643
651
  };
@@ -656,7 +664,7 @@ index 696e98c..7585930 100644
656
664
  const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
657
665
  const projectTrusted = this.settingsManager.isProjectTrusted();
658
666
  const projectAgentsSkillDirs = projectTrusted
659
- @@ -1986,10 +2039,13 @@ export class DefaultPackageManager {
667
+ @@ -2033,10 +2088,13 @@ export class DefaultPackageManager {
660
668
  }
661
669
  };
662
670
  if (projectTrusted) {
@@ -674,7 +682,7 @@ index 696e98c..7585930 100644
674
682
  }
675
683
  // Project skills from .agents/ (each with its own baseDir)
676
684
  for (const agentsSkillsDir of projectAgentsSkillDirs) {
677
- @@ -2001,8 +2057,11 @@ export class DefaultPackageManager {
685
+ @@ -2048,8 +2106,11 @@ export class DefaultPackageManager {
678
686
  addResources("skills", collectAutoSkillEntries(agentsSkillsDir, "agents"), agentsMetadata, projectOverrides.skills, agentsBaseDir);
679
687
  }
680
688
  if (projectTrusted) {
@@ -689,13 +697,14 @@ index 696e98c..7585930 100644
689
697
  // User extensions from ~/.pi/agent/
690
698
  addResources("extensions", collectAutoExtensionEntries(userDirs.extensions), userMetadata, userOverrides.extensions, globalBaseDir);
691
699
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js
692
- index 2f85768..328b27b 100644
700
+ index 0ff35ba..2bf0d59 100644
693
701
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js
694
702
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js
695
- @@ -1,8 +1,8 @@
703
+ @@ -1,9 +1,9 @@
696
704
  -import { APP_NAME, CONFIG_DIR_NAME } from "../config.js";
697
705
  +import { APP_NAME, formatProjectConfigDirNames } from "../config.js";
698
706
  import { emitProjectTrustEvent } from "./extensions/runner.js";
707
+ import { SETTINGS_DEFAULTS } from "./settings-defaults.js";
699
708
  import { getProjectTrustOptions, hasTrustRequiringProjectResources, } from "./trust-manager.js";
700
709
  function formatProjectTrustPrompt(cwd) {
701
710
  - return `Trust project folder?\n${cwd}\n\nThis allows ${APP_NAME} to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
@@ -704,7 +713,7 @@ index 2f85768..328b27b 100644
704
713
  async function selectProjectTrustOption(cwd, ctx) {
705
714
  const options = getProjectTrustOptions(cwd, { includeSessionOnly: true });
706
715
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js
707
- index 3700c08..79cb85e 100644
716
+ index c37ea83..3043d61 100644
708
717
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js
709
718
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js
710
719
  @@ -1,6 +1,6 @@
@@ -715,9 +724,9 @@ index 3700c08..79cb85e 100644
715
724
  import { parseFrontmatter } from "../utils/frontmatter.js";
716
725
  import { resolvePath } from "../utils/paths.js";
717
726
  import { createSyntheticSourceInfo } from "./source-info.js";
718
- @@ -157,7 +157,8 @@ export function loadPromptTemplates(options) {
719
- const includeDefaults = options.includeDefaults;
720
- const templates = [];
727
+ @@ -186,7 +186,8 @@ export function loadPromptTemplates(options) {
728
+ diagnostics.push(...result.diagnostics);
729
+ };
721
730
  const globalPromptsDir = join(resolvedAgentDir, "prompts");
722
731
  - const projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
723
732
  + // Privateer patch: `.privateer/prompts` then `.pi/prompts`.
@@ -725,7 +734,7 @@ index 3700c08..79cb85e 100644
725
734
  const isUnderPath = (target, root) => {
726
735
  const normalizedRoot = resolve(root);
727
736
  if (target === normalizedRoot) {
728
- @@ -174,7 +175,8 @@ export function loadPromptTemplates(options) {
737
+ @@ -203,7 +204,8 @@ export function loadPromptTemplates(options) {
729
738
  baseDir: globalPromptsDir,
730
739
  });
731
740
  }
@@ -735,31 +744,31 @@ index 3700c08..79cb85e 100644
735
744
  return createSyntheticSourceInfo(resolvedPath, {
736
745
  source: "local",
737
746
  scope: "project",
738
- @@ -188,7 +190,9 @@ export function loadPromptTemplates(options) {
747
+ @@ -217,7 +219,9 @@ export function loadPromptTemplates(options) {
739
748
  };
740
749
  if (includeDefaults) {
741
- templates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));
742
- - templates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));
750
+ addResult(loadTemplatesFromDir(globalPromptsDir, getSourceInfo));
751
+ - addResult(loadTemplatesFromDir(projectPromptsDir, getSourceInfo));
743
752
  + for (const projectPromptsDir of projectPromptsDirs) {
744
- + templates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));
753
+ + addResult(loadTemplatesFromDir(projectPromptsDir, getSourceInfo));
745
754
  + }
746
755
  }
747
756
  // 3. Load explicit prompt paths
748
757
  for (const rawPath of promptPaths) {
749
758
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js
750
- index c5ed4d7..d9f50ae 100644
759
+ index 563d7d5..4f669b6 100644
751
760
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js
752
761
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js
753
- @@ -1,7 +1,7 @@
754
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
762
+ @@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
755
763
  import { basename, dirname, join, resolve, sep } from "node:path";
764
+ import { detectCapabilities, getTerminalColorMode } from "@earendil-works/pi-tui";
756
765
  import chalk from "chalk";
757
766
  -import { CONFIG_DIR_NAME } from "../config.js";
758
767
  +import { findProjectConfigPath, projectConfigDirCandidates, projectConfigDirs } from "../config.js";
759
768
  import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
760
769
  import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
761
770
  import { stripBom } from "../utils/text.js";
762
- @@ -625,12 +625,14 @@ export class DefaultResourceLoader {
771
+ @@ -759,12 +759,14 @@ export class DefaultResourceLoader {
763
772
  join(this.agentDir, "themes"),
764
773
  join(this.agentDir, "extensions"),
765
774
  ];
@@ -780,7 +789,7 @@ index c5ed4d7..d9f50ae 100644
780
789
  for (const root of agentRoots) {
781
790
  if (this.isUnderPath(normalizedPath, root)) {
782
791
  return { path: filePath, source: "local", scope: "user", origin: "top-level", baseDir: root };
783
- @@ -669,7 +671,11 @@ export class DefaultResourceLoader {
792
+ @@ -803,7 +805,11 @@ export class DefaultResourceLoader {
784
793
  const themes = [];
785
794
  const diagnostics = [];
786
795
  if (includeDefaults) {
@@ -791,9 +800,9 @@ index c5ed4d7..d9f50ae 100644
791
800
  + ...projectConfigDirs(this.cwd).map((dir) => join(dir, "themes")),
792
801
  + ];
793
802
  for (const dir of defaultDirs) {
794
- this.loadThemesFromDir(dir, themes, diagnostics);
803
+ this.loadThemesFromDir(dir, themes, diagnostics, colorMode);
795
804
  }
796
- @@ -807,8 +813,9 @@ export class DefaultResourceLoader {
805
+ @@ -942,8 +948,9 @@ export class DefaultResourceLoader {
797
806
  return { themes: Array.from(seen.values()), diagnostics };
798
807
  }
799
808
  discoverSystemPromptFile() {
@@ -805,7 +814,7 @@ index c5ed4d7..d9f50ae 100644
805
814
  return projectPath;
806
815
  }
807
816
  const globalPath = join(this.agentDir, "SYSTEM.md");
808
- @@ -818,8 +825,9 @@ export class DefaultResourceLoader {
817
+ @@ -953,8 +960,9 @@ export class DefaultResourceLoader {
809
818
  return undefined;
810
819
  }
811
820
  discoverAppendSystemPromptFile() {
@@ -818,10 +827,10 @@ index c5ed4d7..d9f50ae 100644
818
827
  }
819
828
  const globalPath = join(this.agentDir, "APPEND_SYSTEM.md");
820
829
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js
821
- index ed9d8d2..34d344c 100644
830
+ index d5ce9cd..a456908 100644
822
831
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js
823
832
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js
824
- @@ -3,7 +3,7 @@ import { randomUUID } from "crypto";
833
+ @@ -2,7 +2,7 @@ import { randomUUID } from "crypto";
825
834
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
826
835
  import { dirname, join } from "path";
827
836
  import lockfile from "proper-lockfile";
@@ -830,7 +839,7 @@ index ed9d8d2..34d344c 100644
830
839
  import { normalizePath, resolvePath } from "../utils/paths.js";
831
840
  import { stripBom } from "../utils/text.js";
832
841
  import { DEFAULT_HTTP_IDLE_TIMEOUT_MS, parseHttpIdleTimeoutMs } from "./http-dispatcher.js";
833
- @@ -52,14 +52,112 @@ function toSettingsError(scope, error, path) {
842
+ @@ -114,14 +114,112 @@ function toSettingsError(scope, error, path) {
834
843
  error: error instanceof Error ? error : new Error(String(error)),
835
844
  };
836
845
  }
@@ -944,7 +953,7 @@ index ed9d8d2..34d344c 100644
944
953
  }
945
954
  acquireLockSyncWithRetry(path) {
946
955
  const maxAttempts = 10;
947
- @@ -86,7 +184,13 @@ export class FileSettingsStorage {
956
+ @@ -148,7 +246,13 @@ export class FileSettingsStorage {
948
957
  throw lastError ?? new Error("Failed to acquire settings lock");
949
958
  }
950
959
  withLock(scope, fn) {
@@ -959,7 +968,7 @@ index ed9d8d2..34d344c 100644
959
968
  const dir = dirname(path);
960
969
  let release;
961
970
  try {
962
- @@ -164,7 +268,7 @@ export class SettingsManager {
971
+ @@ -226,7 +330,7 @@ export class SettingsManager {
963
972
  const storage = new FileSettingsStorage(resolvedCwd, resolvedAgentDir);
964
973
  return SettingsManager.fromStorageWithPaths(storage, options, {
965
974
  global: join(resolvedAgentDir, "settings.json"),
@@ -968,7 +977,7 @@ index ed9d8d2..34d344c 100644
968
977
  });
969
978
  }
970
979
  /** Create a SettingsManager from an arbitrary storage backend */
971
- @@ -646,7 +750,15 @@ export class SettingsManager {
980
+ @@ -723,7 +827,15 @@ export class SettingsManager {
972
981
  getProviderRetrySettings() {
973
982
  return {
974
983
  timeoutMs: this.settings.retry?.provider?.timeoutMs,
@@ -982,11 +991,11 @@ index ed9d8d2..34d344c 100644
982
991
  + // 2 here is deliberately small: it is a *per-attempt* budget that nests inside
983
992
  + // the session retry, and each of its waits already honours the server's number.
984
993
  + maxRetries: this.settings.retry?.provider?.maxRetries ?? 2,
985
- maxRetryDelayMs: this.settings.retry?.provider?.maxRetryDelayMs ?? 60000,
994
+ maxRetryDelayMs: this.settings.retry?.provider?.maxRetryDelayMs ?? SETTINGS_DEFAULTS.retry.provider.maxRetryDelayMs,
986
995
  };
987
996
  }
988
997
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/skills.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/skills.js
989
- index d101003..540b972 100644
998
+ index 65aa0b1..51537ef 100644
990
999
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/skills.js
991
1000
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/skills.js
992
1001
  @@ -1,7 +1,7 @@
@@ -998,7 +1007,7 @@ index d101003..540b972 100644
998
1007
  import { parseFrontmatter } from "../utils/frontmatter.js";
999
1008
  import { canonicalizePath, resolvePath } from "../utils/paths.js";
1000
1009
  import { createSyntheticSourceInfo } from "./source-info.js";
1001
- @@ -348,10 +348,15 @@ export function loadSkills(options) {
1010
+ @@ -353,10 +353,15 @@ export function loadSkills(options) {
1002
1011
  }
1003
1012
  if (includeDefaults) {
1004
1013
  addSkills(loadSkillsFromDirInternal(join(resolvedAgentDir, "skills"), "user", true));
@@ -1016,7 +1025,7 @@ index d101003..540b972 100644
1016
1025
  const isUnderPath = (target, root) => {
1017
1026
  const normalizedRoot = resolve(root);
1018
1027
  if (target === normalizedRoot) {
1019
- @@ -364,7 +369,7 @@ export function loadSkills(options) {
1028
+ @@ -369,7 +374,7 @@ export function loadSkills(options) {
1020
1029
  if (!includeDefaults) {
1021
1030
  if (isUnderPath(resolvedPath, userSkillsDir))
1022
1031
  return "user";
@@ -1026,23 +1035,23 @@ index d101003..540b972 100644
1026
1035
  }
1027
1036
  return "path";
1028
1037
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js
1029
- index 170a066..b535f72 100644
1038
+ index 25cc1c5..8a90edd 100644
1030
1039
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js
1031
1040
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js
1032
1041
  @@ -8,7 +8,7 @@ export const BUILTIN_SLASH_COMMANDS = [
1033
1042
  { name: "export", description: "Export session (HTML default, or specify path: .html/.jsonl)" },
1034
1043
  { name: "import", description: "Import and resume a session from a JSONL file" },
1035
- { name: "share", description: "Share session as a secret GitHub gist" },
1044
+ { name: "share", description: "Share session via a Radius artifact or a GitHub gist" },
1036
1045
  - { name: "bug", description: "Report a bug to the Pi developers", argumentHint: "<description>" },
1037
1046
  + { name: "bug", description: "Export a bug report to email to Privateer support", argumentHint: "<description>" },
1038
1047
  { name: "copy", description: "Copy last agent message to clipboard" },
1039
1048
  { name: "name", description: "Set session display name" },
1040
1049
  { name: "session", description: "Show session info and stats" },
1041
1050
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js
1042
- index 7241668..7b79305 100644
1051
+ index 640d225..a376a21 100644
1043
1052
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js
1044
1053
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js
1045
- @@ -10,6 +10,30 @@ function defaultTempFilePath(prefix) {
1054
+ @@ -4,6 +4,30 @@ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateTail } from "./truncate.j
1046
1055
  function byteLength(text) {
1047
1056
  return Buffer.byteLength(text, "utf-8");
1048
1057
  }
@@ -1073,7 +1082,7 @@ index 7241668..7b79305 100644
1073
1082
  /**
1074
1083
  * Incrementally tracks streaming output with bounded memory.
1075
1084
  *
1076
- @@ -32,6 +56,11 @@ export class OutputAccumulator {
1085
+ @@ -26,6 +50,11 @@ export class OutputAccumulator {
1077
1086
  completedLines = 0;
1078
1087
  totalLines = 0;
1079
1088
  currentLineBytes = 0;
@@ -1085,7 +1094,7 @@ index 7241668..7b79305 100644
1085
1094
  hasOpenLine = false;
1086
1095
  finished = false;
1087
1096
  tempFilePath;
1088
- @@ -40,6 +69,8 @@ export class OutputAccumulator {
1097
+ @@ -34,6 +63,8 @@ export class OutputAccumulator {
1089
1098
  this.maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
1090
1099
  this.maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
1091
1100
  this.maxRollingBytes = Math.max(this.maxBytes * 2, 1);
@@ -1094,7 +1103,7 @@ index 7241668..7b79305 100644
1094
1103
  this.tempFilePrefix = options.tempFilePrefix ?? "pi-output";
1095
1104
  }
1096
1105
  append(data) {
1097
- @@ -122,8 +153,11 @@ export class OutputAccumulator {
1106
+ @@ -151,8 +182,11 @@ export class OutputAccumulator {
1098
1107
  }
1099
1108
  const bytes = byteLength(text);
1100
1109
  this.totalDecodedBytes += bytes;
@@ -1108,7 +1117,7 @@ index 7241668..7b79305 100644
1108
1117
  if (this.tailBytes > this.maxRollingBytes * 2) {
1109
1118
  this.trimTail();
1110
1119
  }
1111
- @@ -145,6 +179,42 @@ export class OutputAccumulator {
1120
+ @@ -174,6 +208,42 @@ export class OutputAccumulator {
1112
1121
  }
1113
1122
  this.totalLines = this.completedLines + (this.hasOpenLine ? 1 : 0);
1114
1123
  }
@@ -1152,7 +1161,7 @@ index 7241668..7b79305 100644
1152
1161
  const buffer = Buffer.from(this.tailText, "utf-8");
1153
1162
  if (buffer.length <= this.maxRollingBytes) {
1154
1163
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js
1155
- index 0877ee7..faa2559 100644
1164
+ index 5562460..12516b9 100644
1156
1165
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js
1157
1166
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js
1158
1167
  @@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -1164,7 +1173,7 @@ index 0877ee7..faa2559 100644
1164
1173
  import { canonicalizePath, resolvePath } from "../utils/paths.js";
1165
1174
  import { stripBom } from "../utils/text.js";
1166
1175
  const TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES = [
1167
- @@ -151,9 +151,13 @@ export function hasTrustRequiringProjectResources(cwd) {
1176
+ @@ -152,9 +152,13 @@ export function hasTrustRequiringProjectResources(cwd) {
1168
1177
  const homeDir = canonicalizePath(resolvePath(process.env.HOME || homedir()));
1169
1178
  const userAgentsSkillsDir = join(homeDir, ".agents", "skills");
1170
1179
  let currentDir = canonicalizePath(resolvePath(cwd));
@@ -1181,8 +1190,24 @@ index 0877ee7..faa2559 100644
1181
1190
  }
1182
1191
  while (true) {
1183
1192
  const agentsSkillsDir = join(currentDir, ".agents", "skills");
1193
+ diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/extensions/mcp/cli.js b/node_modules/@earendil-works/pi-coding-agent/dist/extensions/mcp/cli.js
1194
+ index 8f0d990..8c7931f 100644
1195
+ --- a/node_modules/@earendil-works/pi-coding-agent/dist/extensions/mcp/cli.js
1196
+ +++ b/node_modules/@earendil-works/pi-coding-agent/dist/extensions/mcp/cli.js
1197
+ @@ -8,7 +8,10 @@ import { existsSync } from "node:fs";
1198
+ import { join } from "node:path";
1199
+ import { createInterface } from "node:readline/promises";
1200
+ import chalk from "chalk";
1201
+ -import { APP_NAME, CONFIG_DIR_NAME } from "../../config.js";
1202
+ +import { APP_NAME as PI_APP_NAME, CONFIG_DIR_NAME } from "../../config.js";
1203
+ +// Privateer patch: the commands this prints are ones the user types, so they name the
1204
+ +// command the user actually ran (`privateer`, set by the launcher), not Pi's.
1205
+ +const APP_NAME = process.env.PRIVATEER_CMD || PI_APP_NAME;
1206
+ import { validateMcpServerConfig } from "../../core/mcp-servers.js";
1207
+ import { ProjectTrustStore } from "../../core/trust-manager.js";
1208
+ import { openBrowser } from "../../utils/open-browser.js";
1184
1209
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts b/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts
1185
- index 205144d..14080a2 100644
1210
+ index 3821d9f..b58133c 100644
1186
1211
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts
1187
1212
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts
1188
1213
  @@ -1,7 +1,7 @@
@@ -1195,7 +1220,7 @@ index 205144d..14080a2 100644
1195
1220
  export { type BranchPreparation, type BranchSummaryResult, type CollectEntriesResult, type CompactionResult, type CutPointResult, calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, type FileOperations, findCutPoint, findTurnStartIndex, type GenerateBranchSummaryOptions, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.ts";
1196
1221
  export { createEventBus, type EventBus, type EventBusController } from "./core/event-bus.ts";
1197
1222
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/index.js b/node_modules/@earendil-works/pi-coding-agent/dist/index.js
1198
- index 01017cd..c2b8722 100644
1223
+ index ab5cfcc..09fb05b 100644
1199
1224
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/index.js
1200
1225
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/index.js
1201
1226
  @@ -3,7 +3,7 @@ export { parseArgs } from "./cli/args.js";
@@ -1208,10 +1233,10 @@ index 01017cd..c2b8722 100644
1208
1233
  export { calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, findCutPoint, findTurnStartIndex, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.js";
1209
1234
  export { createEventBus } from "./core/event-bus.js";
1210
1235
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/main.js b/node_modules/@earendil-works/pi-coding-agent/dist/main.js
1211
- index be10307..636fbf4 100644
1236
+ index e4fb658..8af5e0d 100644
1212
1237
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/main.js
1213
1238
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/main.js
1214
- @@ -802,7 +802,58 @@ export async function main(args, options) {
1239
+ @@ -821,7 +821,58 @@ export async function main(args, options) {
1215
1240
  if (exitCode !== 0) {
1216
1241
  process.exitCode = exitCode;
1217
1242
  }
@@ -1306,10 +1331,10 @@ index c30d6b8..ad1654a 100644
1306
1331
  return warnings;
1307
1332
  }
1308
1333
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.d.ts b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.d.ts
1309
- index 7eae594..b21fae3 100644
1334
+ index d7b6c7b..10af81f 100644
1310
1335
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.d.ts
1311
1336
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.d.ts
1312
- @@ -8,7 +8,7 @@ interface BugReportContext {
1337
+ @@ -10,7 +10,7 @@ interface BugReportContext {
1313
1338
  showStatus: (message: string) => void;
1314
1339
  showError: (message: string) => void;
1315
1340
  }
@@ -1320,10 +1345,10 @@ index 7eae594..b21fae3 100644
1320
1345
  //# sourceMappingURL=bug-report.d.ts.map
1321
1346
 
1322
1347
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.js
1323
- index 59ae447..68cf842 100644
1348
+ index 3918684..42db58d 100644
1324
1349
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.js
1325
1350
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/bug-report.js
1326
- @@ -1,18 +1,21 @@
1351
+ @@ -1,28 +1,27 @@
1327
1352
  import * as path from "node:path";
1328
1353
  -import { getAuthCredential } from "../../cli/auth-command.js";
1329
1354
  import { BUG_REPORT_CUSTOM_ENTRY_TYPE, bugReportArchiveFileName, collectBugReportDiagnostics, collectBugReportMetadata, writeBugReportArchive, } from "../../core/bug-report.js";
@@ -1332,7 +1357,7 @@ index 59ae447..68cf842 100644
1332
1357
  -import { getRadiusGatewayUrl, RADIUS_PROVIDER_ID } from "../../core/radius.js";
1333
1358
  import { serializeSessionBranch } from "../../core/session-export.js";
1334
1359
  import { BorderedLoader } from "./components/bordered-loader.js";
1335
- import { ExtensionInputComponent } from "./components/extension-input.js";
1360
+ import { ExtensionEditorComponent } from "./components/extension-editor.js";
1336
1361
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
1337
1362
  import { createShareTrailingEntries } from "./session-share.js";
1338
1363
  import { theme } from "./theme/theme.js";
@@ -1350,7 +1375,17 @@ index 59ae447..68cf842 100644
1350
1375
  export async function reportBug(context, initialHint) {
1351
1376
  const options = await promptForOptions(context, initialHint);
1352
1377
  if (!options) {
1353
- @@ -47,16 +50,6 @@ export async function reportBug(context, initialHint) {
1378
+ context.showStatus("Bug report cancelled");
1379
+ return;
1380
+ }
1381
+ - if (options.delivery === "upload" && process.env.PI_OFFLINE) {
1382
+ - context.showError("Uploading bug reports requires online mode. Use Export as Zip instead.");
1383
+ - return;
1384
+ - }
1385
+ let summary;
1386
+ if (options.includeSummary) {
1387
+ const loader = showLoader(context, `Writing summary with ${context.session.model?.name ?? "the current model"}...`);
1388
+ @@ -51,16 +50,6 @@ export async function reportBug(context, initialHint) {
1354
1389
  context.showError(`Failed to build bug report: ${errorMessage(error)}`);
1355
1390
  return;
1356
1391
  }
@@ -1367,7 +1402,7 @@ index 59ae447..68cf842 100644
1367
1402
  await exportZip(context, bundle);
1368
1403
  }
1369
1404
  async function promptForOptions(context, initialHint) {
1370
- @@ -76,14 +69,13 @@ async function promptForOptions(context, initialHint) {
1405
+ @@ -80,14 +69,13 @@ async function promptForOptions(context, initialHint) {
1371
1406
  includeSummary = summary !== "No";
1372
1407
  }
1373
1408
  const description = hint.trim();
@@ -1384,7 +1419,7 @@ index 59ae447..68cf842 100644
1384
1419
  };
1385
1420
  }
1386
1421
  function buildBundle(session, options, summary) {
1387
- @@ -111,28 +103,6 @@ function buildBundle(session, options, summary) {
1422
+ @@ -115,28 +103,6 @@ function buildBundle(session, options, summary) {
1388
1423
  : undefined,
1389
1424
  };
1390
1425
  }
@@ -1413,7 +1448,7 @@ index 59ae447..68cf842 100644
1413
1448
  async function exportZip(context, bundle) {
1414
1449
  const archivePath = path.join(process.cwd(), bugReportArchiveFileName(bundle.metadata.id));
1415
1450
  try {
1416
- @@ -143,7 +113,7 @@ async function exportZip(context, bundle) {
1451
+ @@ -147,7 +113,7 @@ async function exportZip(context, bundle) {
1417
1452
  return;
1418
1453
  }
1419
1454
  recordInSession(context.session, bundle, { delivery: "zip", path: archivePath });
@@ -1423,7 +1458,7 @@ index 59ae447..68cf842 100644
1423
1458
  function recordInSession(session, bundle, delivery) {
1424
1459
  session.sessionManager.appendCustomEntry(BUG_REPORT_CUSTOM_ENTRY_TYPE, {
1425
1460
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js
1426
- index 6f7ed6a..b0ed0ae 100644
1461
+ index 3825648..9bd656b 100644
1427
1462
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js
1428
1463
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js
1429
1464
  @@ -4,7 +4,7 @@
@@ -1432,10 +1467,10 @@ index 6f7ed6a..b0ed0ae 100644
1432
1467
  import { Container, getKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
1433
1468
  -import { CONFIG_DIR_NAME } from "../../../config.js";
1434
1469
  +import { CONFIG_DIR_NAME, PROJECT_CONFIG_DIR_NAMES, formatProjectConfigDirNames, projectConfigDirs, projectConfigWriteDir } from "../../../config.js";
1470
+ import { BUILTIN_PATH_PREFIX } from "../../../core/source-info.js";
1435
1471
  import { canonicalizePath, isLocalPath, resolvePath } from "../../../utils/paths.js";
1436
1472
  import { theme } from "../theme/theme.js";
1437
- import { DynamicBorder } from "./dynamic-border.js";
1438
- @@ -43,7 +43,7 @@ function getGroupLabel(metadata, agentDir) {
1473
+ @@ -47,7 +47,7 @@ function getGroupLabel(metadata, agentDir) {
1439
1474
  ? `User (${formatBaseDir(metadata.baseDir)})`
1440
1475
  : `Project (${formatBaseDir(metadata.baseDir)})`;
1441
1476
  }
@@ -1444,7 +1479,7 @@ index 6f7ed6a..b0ed0ae 100644
1444
1479
  }
1445
1480
  return metadata.scope === "user" ? "User settings" : "Project settings";
1446
1481
  }
1447
- @@ -141,7 +141,7 @@ class ConfigSelectorHeader {
1482
+ @@ -148,7 +148,7 @@ class ConfigSelectorHeader {
1448
1483
  const hint = switchHint + actionHint + sep + rawKeyHint("esc", "close");
1449
1484
  const spacing = Math.max(1, width - visibleWidth(title) - visibleWidth(hint));
1450
1485
  const scopeHint = this.writeScope === "project"
@@ -1453,7 +1488,7 @@ index 6f7ed6a..b0ed0ae 100644
1453
1488
  : theme.fg("muted", `~/${CONFIG_DIR_NAME}/agent/settings.json`);
1454
1489
  return [
1455
1490
  truncateToWidth(`${title}${" ".repeat(spacing)}${hint}`, width, ""),
1456
- @@ -695,7 +695,12 @@ class ResourceList {
1491
+ @@ -703,7 +703,12 @@ class ResourceList {
1457
1492
  return item.metadata.scope === "project" ? "project" : "user";
1458
1493
  }
1459
1494
  getTopLevelBaseDir(scope) {
@@ -1466,9 +1501,9 @@ index 6f7ed6a..b0ed0ae 100644
1466
1501
  + return projectConfigDirs(this.cwd)[0] ?? projectConfigWriteDir(this.cwd);
1467
1502
  }
1468
1503
  getResourcePattern(item) {
1469
- const scope = item.metadata.scope;
1504
+ if (item.metadata.source === "builtin")
1470
1505
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
1471
- index 0e2811e..c0326e3 100644
1506
+ index cf40733..c31e867 100644
1472
1507
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
1473
1508
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
1474
1509
  @@ -1,7 +1,6 @@
@@ -1524,7 +1559,7 @@ index 0e2811e..c0326e3 100644
1524
1559
  /**
1525
1560
  * Format token counts for compact footer display.
1526
1561
  */
1527
- @@ -41,8 +77,11 @@ export function formatCwdForFooter(cwd, home) {
1562
+ @@ -41,14 +77,16 @@ export function formatCwdForFooter(cwd, home) {
1528
1563
  return relativeToHome === "" ? "~" : `~${sep}${relativeToHome}`;
1529
1564
  }
1530
1565
  /**
@@ -1538,14 +1573,40 @@ index 0e2811e..c0326e3 100644
1538
1573
  */
1539
1574
  export class FooterComponent {
1540
1575
  autoCompactEnabled = true;
1541
- @@ -74,32 +113,9 @@ export class FooterComponent {
1576
+ session;
1577
+ footerData;
1578
+ - sessionStats;
1579
+ constructor(session, footerData) {
1580
+ this.session = session;
1581
+ this.footerData = footerData;
1582
+ @@ -73,67 +111,10 @@ export class FooterComponent {
1583
+ dispose() {
1584
+ // Git watcher cleanup handled by provider
1542
1585
  }
1543
- render(width) {
1544
- const state = this.session.state;
1586
+ - /**
1587
+ - * Usage totals and context usage scan the whole session, and the footer renders on every frame.
1588
+ - * Entries are append-only and every append moves the leaf, so the results only change with the
1589
+ - * session, leaf, entry count, or the model whose context window applies.
1590
+ - */
1591
+ - getSessionStats() {
1592
+ - const sessionManager = this.session.sessionManager;
1593
+ - const entryCount = sessionManager.getEntryCount();
1594
+ - const sessionId = sessionManager.getSessionId();
1595
+ - const leafId = sessionManager.getLeafId();
1596
+ - const limitsModel = this.session.routedModel?.model ?? this.session.model;
1597
+ - const cached = this.sessionStats;
1598
+ - if (cached &&
1599
+ - cached.session === this.session &&
1600
+ - cached.sessionId === sessionId &&
1601
+ - cached.leafId === leafId &&
1602
+ - cached.entryCount === entryCount &&
1603
+ - cached.limitsModel === limitsModel) {
1604
+ - return cached;
1605
+ - }
1545
1606
  - // Calculate cumulative usage from ALL session entries (not just post-compaction messages)
1546
1607
  - const usageTotals = createUsageTotals();
1547
1608
  - let latestCacheHitRate;
1548
- - for (const entry of this.session.sessionManager.getEntries()) {
1609
+ - for (const entry of sessionManager.getEntries()) {
1549
1610
  - if (entry.type === "usage") {
1550
1611
  - addUsageToTotals(usageTotals, entry.usage);
1551
1612
  - }
@@ -1565,16 +1626,30 @@ index 0e2811e..c0326e3 100644
1565
1626
  - // Calculate context usage from session (handles compaction correctly).
1566
1627
  - // After compaction, tokens are unknown until the next LLM response.
1567
1628
  - const contextUsage = this.session.getContextUsage();
1629
+ - this.sessionStats = {
1630
+ - session: this.session,
1631
+ - sessionId,
1632
+ - leafId,
1633
+ - entryCount,
1634
+ - limitsModel,
1635
+ - usageTotals,
1636
+ - latestCacheHitRate,
1637
+ - contextUsage,
1638
+ - };
1639
+ - return this.sessionStats;
1640
+ - }
1641
+ render(width) {
1642
+ const state = this.session.state;
1643
+ - const { usageTotals, latestCacheHitRate, contextUsage } = this.getSessionStats();
1568
1644
  - const contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;
1569
1645
  - const contextPercentValue = contextUsage?.percent ?? 0;
1570
1646
  - const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
1571
1647
  + // Privateer: no token counters, cache-hit rate, cost or context gauge in the
1572
- + // footer — so the accumulation that fed them is gone too. Upstream walked EVERY
1573
- + // session entry on EVERY render to build numbers we no longer draw.
1648
+ + // footer — so upstream's getSessionStats() scan that fed them is gone too.
1574
1649
  // Replace home directory with ~
1575
1650
  let pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);
1576
1651
  // Add git branch if available
1577
- @@ -112,56 +128,24 @@ export class FooterComponent {
1652
+ @@ -146,56 +127,24 @@ export class FooterComponent {
1578
1653
  if (sessionName) {
1579
1654
  pwd = `${pwd} • ${sessionName}`;
1580
1655
  }
@@ -1646,7 +1721,7 @@ index 0e2811e..c0326e3 100644
1646
1721
  const minPadding = 2;
1647
1722
  // Add thinking level indicator if model supports reasoning
1648
1723
  let rightSideWithoutProvider = modelName;
1649
- @@ -174,50 +158,48 @@ export class FooterComponent {
1724
+ @@ -214,50 +163,48 @@ export class FooterComponent {
1650
1725
  let rightSide = rightSideWithoutProvider;
1651
1726
  if (this.footerData.getAvailableProviderCount() > 1 && state.model) {
1652
1727
  rightSide = `(${state.model.provider}) ${rightSideWithoutProvider}`;
@@ -1716,14 +1791,14 @@ index 0e2811e..c0326e3 100644
1716
1791
  return lines;
1717
1792
  }
1718
1793
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js
1719
- index 6d0356c..ca0aa98 100644
1794
+ index d447db5..8f7de42 100644
1720
1795
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js
1721
1796
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js
1722
1797
  @@ -1,9 +1,36 @@
1723
1798
  -import { Box, Container, getCapabilities, Image, MouseRegion, Spacer, Text, } from "@earendil-works/pi-tui";
1724
1799
  +import { Box, Container, getCapabilities, Image, MouseRegion, Spacer, visibleWidth, Text, } from "@earendil-works/pi-tui";
1725
- import { getTextOutput as getRenderedTextOutput } from "../../../core/tools/render-utils.js";
1726
- import { convertToPng } from "../../../utils/image-convert.js";
1800
+ import { formatToolCallWithArgs, getTextOutput as getRenderedTextOutput } from "../../../core/tools/render-utils.js";
1801
+ import { ensurePngTranscoder } from "../../../utils/image-convert.js";
1727
1802
  import { theme } from "../theme/theme.js";
1728
1803
  import { keyHint } from "./keybinding-hints.js";
1729
1804
  const FALLBACK_PREVIEW_LINES = 10;
@@ -1757,7 +1832,7 @@ index 6d0356c..ca0aa98 100644
1757
1832
  export class ToolExecutionComponent extends Container {
1758
1833
  contentBox;
1759
1834
  contentText;
1760
- @@ -44,8 +71,13 @@ export class ToolExecutionComponent extends Container {
1835
+ @@ -47,8 +74,13 @@ export class ToolExecutionComponent extends Container {
1761
1836
  // Always create all shell variants. contentBox is used for default renderer-based composition.
1762
1837
  // selfRenderContainer is used when the tool renders its own framing.
1763
1838
  // contentText is reserved for generic fallback rendering when no tool definition exists.
@@ -1773,9 +1848,9 @@ index 6d0356c..ca0aa98 100644
1773
1848
  this.contentTextRegion = this.createResultRegion(this.contentText);
1774
1849
  this.selfRenderContainer = new Container();
1775
1850
  if (this.hasRendererDefinition()) {
1776
- @@ -90,6 +122,13 @@ export class ToolExecutionComponent extends Container {
1851
+ @@ -95,6 +127,13 @@ export class ToolExecutionComponent extends Container {
1777
1852
  createCallFallback() {
1778
- return new Text(theme.fg("toolTitle", theme.bold(this.toolName)), 0, 0);
1853
+ return new Text(formatToolCallWithArgs(this.toolName, this.args, theme, this.expanded), 0, 0);
1779
1854
  }
1780
1855
  + /** Privateer: red-flag a failed call, pass a pending/succeeded one straight through. */
1781
1856
  + decorateCall(component) {
@@ -1787,7 +1862,7 @@ index 6d0356c..ca0aa98 100644
1787
1862
  createResultFallback() {
1788
1863
  const output = this.getTextOutput();
1789
1864
  if (!output) {
1790
- @@ -237,19 +276,22 @@ export class ToolExecutionComponent extends Container {
1865
+ @@ -213,19 +252,22 @@ export class ToolExecutionComponent extends Container {
1791
1866
  renderContainer.clear();
1792
1867
  const callRenderer = this.getCallRenderer();
1793
1868
  if (!callRenderer) {
@@ -1813,7 +1888,7 @@ index 6d0356c..ca0aa98 100644
1813
1888
  hasContent = true;
1814
1889
  }
1815
1890
  }
1816
- @@ -322,7 +364,8 @@ export class ToolExecutionComponent extends Container {
1891
+ @@ -309,7 +351,8 @@ export class ToolExecutionComponent extends Container {
1817
1892
  return getRenderedTextOutput(this.result, this.showImages);
1818
1893
  }
1819
1894
  formatToolExecution() {
@@ -1824,19 +1899,19 @@ index 6d0356c..ca0aa98 100644
1824
1899
  if (content) {
1825
1900
  text += `\n\n${content}`;
1826
1901
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
1827
- index d82182c..08bec2d 100644
1902
+ index d2343a5..8487713 100644
1828
1903
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
1829
1904
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
1830
- @@ -10,7 +10,7 @@ import * as TuiLayouts from "@earendil-works/pi-tui";
1905
+ @@ -11,7 +11,7 @@ import * as TuiLayouts from "@earendil-works/pi-tui";
1831
1906
  import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, Spacer, setCapabilityOverrides, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
1832
1907
  import chalk from "chalk";
1833
1908
  import { spawn } from "child_process";
1834
- -import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, VERSION, } from "../../config.js";
1835
- +import { APP_NAME, APP_TITLE, formatProjectConfigDirNames, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, VERSION, } from "../../config.js";
1909
+ -import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, detectInstallChange, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, VERSION, } from "../../config.js";
1910
+ +import { APP_NAME, APP_TITLE, detectInstallChange, formatProjectConfigDirNames, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, VERSION, } from "../../config.js";
1836
1911
  import { parseSkillBlock } from "../../core/agent-session.js";
1837
1912
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
1838
1913
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
1839
- @@ -141,7 +141,12 @@ export function formatResumeCommand(sessionManager) {
1914
+ @@ -177,7 +177,12 @@ export function formatResumeCommand(sessionManager) {
1840
1915
  const sessionFile = sessionManager.getSessionFile();
1841
1916
  if (!sessionFile || !fs.existsSync(sessionFile))
1842
1917
  return undefined;
@@ -1850,8 +1925,8 @@ index d82182c..08bec2d 100644
1850
1925
  if (!sessionManager.usesDefaultSessionDir()) {
1851
1926
  args.push("--session-dir", quoteIfNeeded(sessionManager.getSessionDir()));
1852
1927
  }
1853
- @@ -192,6 +197,77 @@ function formatLoginProviderCompletionDescription(provider) {
1854
- const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
1928
+ @@ -232,6 +237,77 @@ function formatLoginProviderCompletionDescription(provider) {
1929
+ .join("/");
1855
1930
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
1856
1931
  }
1857
1932
  +// Privateer patch: describe an error we only have the TEXT of. Mirrors
@@ -1928,7 +2003,7 @@ index d82182c..08bec2d 100644
1928
2003
  export class InteractiveMode {
1929
2004
  runtimeHost;
1930
2005
  renderer;
1931
- @@ -404,9 +480,15 @@ export class InteractiveMode {
2006
+ @@ -451,9 +527,15 @@ export class InteractiveMode {
1932
2007
  }
1933
2008
  getBuiltInCommandConflictDiagnostics(extensionRunner) {
1934
2009
  const builtinNames = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));
@@ -1945,7 +2020,7 @@ index d82182c..08bec2d 100644
1945
2020
  .map((command) => ({
1946
2021
  type: "warning",
1947
2022
  message: command.invocationName === command.name
1948
- @@ -775,20 +857,18 @@ export class InteractiveMode {
2023
+ @@ -841,20 +923,18 @@ export class InteractiveMode {
1949
2024
  this.showNewVersionNotification(newRelease);
1950
2025
  }
1951
2026
  });
@@ -1978,16 +2053,16 @@ index d82182c..08bec2d 100644
1978
2053
  // Check tmux keyboard setup asynchronously
1979
2054
  this.checkTmuxKeyboardSetup().then((warning) => {
1980
2055
  if (warning) {
1981
- @@ -1568,7 +1648,7 @@ export class InteractiveMode {
2056
+ @@ -1651,7 +1731,7 @@ export class InteractiveMode {
1982
2057
  if (this.bugReportHintShown)
1983
2058
  return;
1984
2059
  this.bugReportHintShown = true;
1985
- - this.chatContainer.addChild(new Text(theme.fg("muted", `If this looks like a ${APP_NAME} bug, /bug sends a report to the developers.`), this.outputPad, 0));
1986
- + this.chatContainer.addChild(new Text(theme.fg("muted", `If this looks like a ${APP_NAME} bug, /bug exports a report you can email to support@privateer.pro.`), this.outputPad, 0));
2060
+ - this.chatContainer.addChild(new ThemedText(() => theme.fg("muted", `If this looks like a ${APP_NAME} bug, /bug sends a report to the developers.`), this.outputPad, 0));
2061
+ + this.chatContainer.addChild(new ThemedText(() => theme.fg("muted", `If this looks like a ${APP_NAME} bug, /bug exports a report you can email to support@privateer.pro.`), this.outputPad, 0));
1987
2062
  this.ui.requestRender();
1988
2063
  }
1989
- renderCurrentSessionState() {
1990
- @@ -2419,7 +2499,17 @@ export class InteractiveMode {
2064
+ maybeSuggestBugReport(message) {
2065
+ @@ -2558,7 +2638,17 @@ export class InteractiveMode {
1991
2066
  if (text === "/model" || text.startsWith("/model ")) {
1992
2067
  const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
1993
2068
  this.editor.setText("");
@@ -2006,7 +2081,7 @@ index d82182c..08bec2d 100644
2006
2081
  return;
2007
2082
  }
2008
2083
  if (text === "/thinking" || text.startsWith("/thinking ")) {
2009
- @@ -2497,12 +2587,42 @@ export class InteractiveMode {
2084
+ @@ -2636,12 +2726,42 @@ export class InteractiveMode {
2010
2085
  if (text === "/login" || text.startsWith("/login ")) {
2011
2086
  const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
2012
2087
  this.editor.setText("");
@@ -2051,7 +2126,7 @@ index d82182c..08bec2d 100644
2051
2126
  return;
2052
2127
  }
2053
2128
  if (text === "/new") {
2054
- @@ -2827,9 +2947,19 @@ export class InteractiveMode {
2129
+ @@ -3006,9 +3126,19 @@ export class InteractiveMode {
2055
2130
  if (entries[0]?.type !== "compaction") {
2056
2131
  throw new Error("Completed compaction is missing from the session context");
2057
2132
  }
@@ -2074,20 +2149,20 @@ index d82182c..08bec2d 100644
2074
2149
  this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
2075
2150
  if (event.result.usage) {
2076
2151
  this.addCompactionCostNotice({
2077
- @@ -3253,7 +3383,7 @@ export class InteractiveMode {
2152
+ @@ -3441,7 +3571,7 @@ export class InteractiveMode {
2078
2153
  if (this.chatContainer.children.length > 0) {
2079
2154
  this.chatContainer.addChild(new Spacer(1));
2080
2155
  }
2081
- - this.chatContainer.addChild(new Text(theme.fg("warning", `This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart pi.`), 1, 0));
2082
- + this.chatContainer.addChild(new Text(theme.fg("warning", `This project is not trusted. Project ${formatProjectConfigDirNames()} resources and packages are ignored. Use /trust to save a trust decision, then restart ${process.env.PRIVATEER_CMD || APP_NAME}.`), 1, 0));
2156
+ - this.chatContainer.addChild(new ThemedText(() => theme.fg("warning", `This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart pi.`), 1, 0));
2157
+ + this.chatContainer.addChild(new ThemedText(() => theme.fg("warning", `This project is not trusted. Project ${formatProjectConfigDirNames()} resources and packages are ignored. Use /trust to save a trust decision, then restart ${process.env.PRIVATEER_CMD || APP_NAME}.`), 1, 0));
2083
2158
  }
2084
2159
  async getUserInput() {
2085
2160
  const queuedInput = this.pendingUserInputs.shift();
2086
- @@ -3597,7 +3727,17 @@ export class InteractiveMode {
2161
+ @@ -3795,7 +3925,17 @@ export class InteractiveMode {
2087
2162
  }
2088
2163
  showError(errorMessage) {
2089
2164
  this.chatContainer.addChild(new Spacer(1));
2090
- - this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
2165
+ - this.chatContainer.addChild(new ThemedText(() => theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
2091
2166
  + // Privateer patch: by the time an error reaches here it is only a string — the
2092
2167
  + // structured fields describeError() reads are long gone, so the user was shown
2093
2168
  + // the SDK's own words ("429 status code (no body)"), which say nothing about
@@ -2095,49 +2170,65 @@ index d82182c..08bec2d 100644
2095
2170
  + // it and say what describeError would. Mirrors describeErrorText in
2096
2171
  + // src/engine/errors.ts. Anything without a leading status prints unchanged.
2097
2172
  + const described = pvDescribeErrorText(errorMessage);
2098
- + this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${described?.message ?? errorMessage}`), this.outputPad, 0));
2173
+ + this.chatContainer.addChild(new ThemedText(() => theme.fg("error", `Error: ${described?.message ?? errorMessage}`), this.outputPad, 0));
2099
2174
  + if (described?.hint) {
2100
- + this.chatContainer.addChild(new Text(theme.fg("muted", described.hint), this.outputPad, 0));
2175
+ + this.chatContainer.addChild(new ThemedText(() => theme.fg("muted", described.hint), this.outputPad, 0));
2101
2176
  + }
2102
2177
  this.ui.requestRender();
2103
2178
  }
2104
2179
  showWarning(warningMessage) {
2105
2180
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json
2106
- index 3f4c698..e0152d0 100644
2181
+ index 0a82c28..f9c106b 100644
2107
2182
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json
2108
2183
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/dark.json
2109
- @@ -42,9 +42,9 @@
2110
- "customMessageBg": "customMsgBg",
2111
- "customMessageText": "text",
2112
- "customMessageLabel": "#9575cd",
2113
- - "toolPendingBg": "toolPendingBg",
2114
- - "toolSuccessBg": "toolSuccessBg",
2115
- - "toolErrorBg": "toolErrorBg",
2184
+ @@ -34,9 +34,9 @@
2185
+ "customMessageBg": "okhsl(295 42% 24%)",
2186
+ "customMessageText": "muted",
2187
+ "customMessageLabel": "violet",
2188
+ - "toolPendingBg": "okhsl(229 5% 24%)",
2189
+ - "toolSuccessBg": "okhsl(158 46% 25%)",
2190
+ - "toolErrorBg": "okhsl(19 54% 25%)",
2116
2191
  + "toolPendingBg": "",
2117
2192
  + "toolSuccessBg": "",
2118
2193
  + "toolErrorBg": "",
2119
2194
  "toolTitle": "text",
2120
- "toolOutput": "gray",
2121
-
2195
+ "toolOutput": "muted",
2196
+ "mdHeading": "yellow",
2122
2197
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json
2123
- index 20f6783..49fc151 100644
2198
+ index 3d870c0..57a2cb5 100644
2124
2199
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json
2125
2200
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/light.json
2126
- @@ -41,9 +41,9 @@
2127
- "customMessageBg": "customMsgBg",
2128
- "customMessageText": "text",
2129
- "customMessageLabel": "#7e57c2",
2130
- - "toolPendingBg": "toolPendingBg",
2131
- - "toolSuccessBg": "toolSuccessBg",
2132
- - "toolErrorBg": "toolErrorBg",
2201
+ @@ -34,9 +34,9 @@
2202
+ "customMessageBg": "okhsl(295 25% 91%)",
2203
+ "customMessageText": "muted",
2204
+ "customMessageLabel": "violet",
2205
+ - "toolPendingBg": "okhsl(248 3% 91%)",
2206
+ - "toolSuccessBg": "okhsl(156 21% 91%)",
2207
+ - "toolErrorBg": "okhsl(24 23% 91%)",
2133
2208
  + "toolPendingBg": "",
2134
2209
  + "toolSuccessBg": "",
2135
2210
  + "toolErrorBg": "",
2136
2211
  "toolTitle": "text",
2137
- "toolOutput": "mediumGray",
2138
-
2212
+ "toolOutput": "muted",
2213
+ "mdHeading": "yellow",
2214
+ diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js
2215
+ index d50acea..0ff5f27 100644
2216
+ --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js
2217
+ +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js
2218
+ @@ -409,6 +409,11 @@ function createSystemTheme(mode) {
2219
+ saturation: terminalColorsPending ? 0 : 1,
2220
+ appearanceHint: getTerminalTheme(),
2221
+ });
2222
+ + // Privateer patch: flat tool calls — no tinted panel behind pending/success/error tool
2223
+ + // blocks, matching dark.json/light.json. "" means the terminal's own background, so a
2224
+ + // tool call reads as part of the transcript; the red ✗ marks a failure on its own.
2225
+ + for (const token of ["toolPendingBg", "toolSuccessBg", "toolErrorBg"])
2226
+ + generated.colors[token] = "";
2227
+ const { fgColors, bgColors } = splitThemeColors(withThemeTokenFallbacks(generated.colors));
2228
+ return new Theme(fgColors, bgColors, mode ?? getTerminalColorMode(), {
2229
+ name: SYSTEM_THEME_NAME,
2139
2230
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js b/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
2140
- index 3f101e6..51c7f73 100644
2231
+ index 6f56150..a68a510 100644
2141
2232
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
2142
2233
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
2143
2234
  @@ -5,7 +5,7 @@ import chalk from "chalk";
@@ -2149,7 +2240,7 @@ index 3f101e6..51c7f73 100644
2149
2240
  import { ModelRuntime } from "./core/model-runtime.js";
2150
2241
  import { DefaultPackageManager } from "./core/package-manager.js";
2151
2242
  import { resolveProjectTrusted } from "./core/project-trust.js";
2152
- @@ -221,7 +221,7 @@ Without -l, starts in global settings (~/${CONFIG_DIR_NAME}/agent/settings.json)
2243
+ @@ -245,7 +245,7 @@ Without -l, starts in global settings (~/${CONFIG_DIR_NAME}/agent/settings.json)
2153
2244
  Press Tab in the TUI to switch between global and project-local modes.
2154
2245
 
2155
2246
  Options:
@@ -2158,7 +2249,7 @@ index 3f101e6..51c7f73 100644
2158
2249
  -a, --approve Trust project-local files for this command with -l
2159
2250
  -na, --no-approve Ignore project-local files for this command with -l
2160
2251
  `);
2161
- @@ -235,7 +235,7 @@ function printPackageCommandHelp(command) {
2252
+ @@ -259,7 +259,7 @@ function printPackageCommandHelp(command) {
2162
2253
  Install a package and add it to settings.
2163
2254
 
2164
2255
  Options:
@@ -2167,7 +2258,7 @@ index 3f101e6..51c7f73 100644
2167
2258
  -a, --approve Trust project-local files for this command
2168
2259
  -na, --no-approve Ignore project-local files for this command
2169
2260
 
2170
- @@ -256,7 +256,7 @@ Remove a package and its source from settings.
2261
+ @@ -280,7 +280,7 @@ Remove a package and its source from settings.
2171
2262
  Alias: ${APP_NAME} uninstall <source> [-l]
2172
2263
 
2173
2264
  Options: