@cydm/pie 1.0.40 → 1.0.42

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.
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  getThemesDir,
20
20
  migrateConfigFromAgentDir,
21
21
  truncateHeadTail
22
- } from "./chunks/chunk-F6VAOGFL.js";
22
+ } from "./chunks/chunk-VYAW6RAX.js";
23
23
  import {
24
24
  AGENTS_CONTEXT_FILE_NAME,
25
25
  AgentSessionController,
@@ -69,8 +69,8 @@ import {
69
69
  setExtensionEnabledInSettings,
70
70
  shouldPreserveExecutionStateForUserText,
71
71
  supersedeExecutionState
72
- } from "./chunks/chunk-SNSSIR3E.js";
73
- import "./chunks/chunk-7IFMKPHH.js";
72
+ } from "./chunks/chunk-2GVFMYIV.js";
73
+ import "./chunks/chunk-JVLAKTAS.js";
74
74
  import {
75
75
  Deref,
76
76
  EMPTY_ASSISTANT_CONTENT_ERROR,
@@ -87,6 +87,7 @@ import {
87
87
  Kind,
88
88
  MODEL_CLASS_FALLBACK_ORDER,
89
89
  Never,
90
+ OPENAI_COMPAT_PROTOCOL_ERROR_CODE,
90
91
  Ref,
91
92
  TransformDecode,
92
93
  TransformDecodeCheckError,
@@ -105,7 +106,7 @@ import {
105
106
  setLogger,
106
107
  sortToolModelCandidatesByCapability,
107
108
  type_exports
108
- } from "./chunks/chunk-HPTWCKJE.js";
109
+ } from "./chunks/chunk-NGQ3PUOC.js";
109
110
  import {
110
111
  resolveCliProjectRoot
111
112
  } from "./chunks/chunk-NTYHFBUA.js";
@@ -113,7 +114,7 @@ import {
113
114
  __commonJS,
114
115
  __require,
115
116
  __toESM
116
- } from "./chunks/chunk-TG2EQLX2.js";
117
+ } from "./chunks/chunk-LZQXQOPJ.js";
117
118
 
118
119
  // ../../node_modules/color-name/index.js
119
120
  var require_color_name = __commonJS({
@@ -985,15 +986,15 @@ var require_route = __commonJS({
985
986
  };
986
987
  }
987
988
  function wrapConversion(toModel, graph) {
988
- const path31 = [graph[toModel].parent, toModel];
989
+ const path33 = [graph[toModel].parent, toModel];
989
990
  let fn = conversions[graph[toModel].parent][toModel];
990
991
  let cur = graph[toModel].parent;
991
992
  while (graph[cur].parent) {
992
- path31.unshift(graph[cur].parent);
993
+ path33.unshift(graph[cur].parent);
993
994
  fn = link(conversions[graph[cur].parent][cur], fn);
994
995
  cur = graph[cur].parent;
995
996
  }
996
- fn.conversion = path31;
997
+ fn.conversion = path33;
997
998
  return fn;
998
999
  }
999
1000
  module.exports = function(fromModel) {
@@ -1233,7 +1234,7 @@ var require_has_flag = __commonJS({
1233
1234
  var require_supports_color = __commonJS({
1234
1235
  "../../node_modules/chalk/node_modules/supports-color/index.js"(exports, module) {
1235
1236
  "use strict";
1236
- var os7 = __require("os");
1237
+ var os9 = __require("os");
1237
1238
  var tty = __require("tty");
1238
1239
  var hasFlag2 = require_has_flag();
1239
1240
  var { env } = process;
@@ -1281,7 +1282,7 @@ var require_supports_color = __commonJS({
1281
1282
  return min;
1282
1283
  }
1283
1284
  if (process.platform === "win32") {
1284
- const osRelease = os7.release().split(".");
1285
+ const osRelease = os9.release().split(".");
1285
1286
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
1286
1287
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
1287
1288
  }
@@ -1916,7 +1917,7 @@ var require_core = __commonJS({
1916
1917
  return match && match.index === 0;
1917
1918
  }
1918
1919
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
1919
- function join19(regexps, separator = "|") {
1920
+ function join21(regexps, separator = "|") {
1920
1921
  let numCaptures = 0;
1921
1922
  return regexps.map((regex) => {
1922
1923
  numCaptures += 1;
@@ -2220,7 +2221,7 @@ var require_core = __commonJS({
2220
2221
  this.exec = () => null;
2221
2222
  }
2222
2223
  const terminators = this.regexes.map((el) => el[1]);
2223
- this.matcherRe = langRe(join19(terminators), true);
2224
+ this.matcherRe = langRe(join21(terminators), true);
2224
2225
  this.lastIndex = 0;
2225
2226
  }
2226
2227
  /** @param {string} s */
@@ -43957,8 +43958,8 @@ var require_dist = __commonJS({
43957
43958
  });
43958
43959
 
43959
43960
  // src/cli.ts
43960
- import * as fs27 from "fs";
43961
- import * as path30 from "path";
43961
+ import * as fs28 from "fs";
43962
+ import * as path32 from "path";
43962
43963
 
43963
43964
  // src/cli-args.ts
43964
43965
  var FLAGS_WITH_VALUES = /* @__PURE__ */ new Set(["--session-id", "--extension-path", "--developer-instructions", "--developer-instructions-file"]);
@@ -44373,17 +44374,211 @@ function installInteractiveConsoleInterception(runtimeLogger) {
44373
44374
  // src/doctor.ts
44374
44375
  import { spawnSync } from "node:child_process";
44375
44376
  import { createRequire } from "node:module";
44376
- import * as fs4 from "node:fs";
44377
+ import * as fs5 from "node:fs";
44377
44378
  import * as os from "node:os";
44378
- import * as path4 from "node:path";
44379
+ import * as path5 from "node:path";
44379
44380
  import { delimiter } from "node:path";
44380
44381
 
44381
44382
  // src/core/auth-storage.ts
44382
- var AuthStorage = class {
44383
+ import * as fs2 from "fs";
44384
+ import * as path2 from "path";
44385
+ var AUTH_LOCK_WRITE_GRACE_MS = 1e3;
44386
+ var DEFAULT_AUTH_LOCK_WAIT_MS = 2e3;
44387
+ var OAUTH_EXPIRY_SKEW_MS = 3e4;
44388
+ function syncSleepMs(ms) {
44389
+ if (ms <= 0) return;
44390
+ const cell = new Int32Array(new SharedArrayBuffer(4));
44391
+ Atomics.wait(cell, 0, 0, ms);
44392
+ }
44393
+ function processIsAlive(pid) {
44394
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
44395
+ try {
44396
+ process.kill(pid, 0);
44397
+ return true;
44398
+ } catch (error) {
44399
+ return error.code === "EPERM";
44400
+ }
44401
+ }
44402
+ var InMemoryAuthStorageBackend = class {
44403
+ snapshot = { oauth: {} };
44404
+ read() {
44405
+ return { oauth: { ...this.snapshot.oauth } };
44406
+ }
44407
+ write(snapshot) {
44408
+ this.snapshot = { oauth: { ...snapshot.oauth } };
44409
+ }
44410
+ };
44411
+ var AUTH_FILE_VERSION = 1;
44412
+ var FileAuthStorageBackend = class {
44413
+ constructor(authPath = path2.join(getConfigDir(), "auth.json"), lockWaitMs = DEFAULT_AUTH_LOCK_WAIT_MS) {
44414
+ this.authPath = authPath;
44415
+ this.lockWaitMs = lockWaitMs;
44416
+ }
44417
+ authPath;
44418
+ lockWaitMs;
44419
+ errors = [];
44420
+ read() {
44421
+ try {
44422
+ if (!fs2.existsSync(this.authPath)) return { oauth: {} };
44423
+ const raw = JSON.parse(fs2.readFileSync(this.authPath, "utf8"));
44424
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { oauth: {} };
44425
+ const oauth = raw.oauth;
44426
+ if (!oauth || typeof oauth !== "object" || Array.isArray(oauth)) return { oauth: {} };
44427
+ return { oauth: { ...oauth } };
44428
+ } catch (error) {
44429
+ this.errors.push(error instanceof Error ? error : new Error(String(error)));
44430
+ return { oauth: {} };
44431
+ }
44432
+ }
44433
+ /** A lock whose owner is gone (or that never got populated) may be reclaimed. */
44434
+ lockIsStale(lockPath) {
44435
+ const stat2 = fs2.lstatSync(lockPath);
44436
+ if (stat2.isSymbolicLink() || !stat2.isFile()) return true;
44437
+ let owner;
44438
+ try {
44439
+ owner = JSON.parse(fs2.readFileSync(lockPath, "utf8"));
44440
+ } catch {
44441
+ }
44442
+ const age = Date.now() - stat2.mtimeMs;
44443
+ const validOwner = owner?.version === 1 && Number.isSafeInteger(owner.pid) && typeof owner.token === "string" && typeof owner.createdAt === "number";
44444
+ if (validOwner && processIsAlive(Number(owner.pid))) return false;
44445
+ return validOwner || age > AUTH_LOCK_WRITE_GRACE_MS;
44446
+ }
44447
+ /** Atomically claim a stale lock via hard-link identity comparison before removing it. */
44448
+ reclaimStaleLock(lockPath) {
44449
+ if (!this.lockIsStale(lockPath)) return false;
44450
+ const claimPath = `${lockPath}.reclaim-${process.pid}-${Math.random().toString(36).slice(2)}`;
44451
+ try {
44452
+ fs2.linkSync(lockPath, claimPath);
44453
+ const claimed = fs2.lstatSync(claimPath);
44454
+ const current = fs2.lstatSync(lockPath);
44455
+ if (claimed.isSymbolicLink() || !claimed.isFile() || current.isSymbolicLink() || !current.isFile()) return false;
44456
+ if (claimed.dev !== current.dev || claimed.ino !== current.ino) return false;
44457
+ fs2.unlinkSync(lockPath);
44458
+ return true;
44459
+ } catch (error) {
44460
+ const code = error.code;
44461
+ if (code === "ENOENT" || code === "EEXIST") return false;
44462
+ throw error;
44463
+ } finally {
44464
+ try {
44465
+ fs2.unlinkSync(claimPath);
44466
+ } catch {
44467
+ }
44468
+ }
44469
+ }
44470
+ /** Acquire the fixed-path cross-process lock; the release fn is token-checked. */
44471
+ acquireLock(directory, lockPath) {
44472
+ fs2.mkdirSync(directory, { recursive: true, mode: 448 });
44473
+ const deadline = Date.now() + this.lockWaitMs;
44474
+ while (true) {
44475
+ const token = `${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
44476
+ try {
44477
+ const fd = fs2.openSync(lockPath, "wx", 384);
44478
+ try {
44479
+ fs2.writeFileSync(fd, `${JSON.stringify({ version: 1, pid: process.pid, token, createdAt: Date.now() })}
44480
+ `);
44481
+ } finally {
44482
+ fs2.closeSync(fd);
44483
+ }
44484
+ return () => {
44485
+ try {
44486
+ const owner = JSON.parse(fs2.readFileSync(lockPath, "utf8"));
44487
+ if (owner?.token === token) fs2.unlinkSync(lockPath);
44488
+ } catch {
44489
+ }
44490
+ };
44491
+ } catch (error) {
44492
+ if (error.code !== "EEXIST") throw error;
44493
+ try {
44494
+ if (this.reclaimStaleLock(lockPath)) continue;
44495
+ } catch (reclaimError) {
44496
+ if (reclaimError.code === "ENOENT") continue;
44497
+ throw reclaimError;
44498
+ }
44499
+ if (Date.now() >= deadline) throw new Error(`timed out waiting for auth storage lock: ${lockPath}`);
44500
+ syncSleepMs(25);
44501
+ }
44502
+ }
44503
+ }
44504
+ /**
44505
+ * Merge-on-write under a fixed-path cross-process lock: re-read the file,
44506
+ * apply explicit removals (logout), merge our snapshot's oauth entries
44507
+ * over whatever is on disk (preserving unrelated edits), then write back
44508
+ * atomically with 0600 permissions.
44509
+ */
44510
+ write(snapshot) {
44511
+ const directory = path2.dirname(this.authPath);
44512
+ const lockPath = `${this.authPath}.lock`;
44513
+ let release;
44514
+ try {
44515
+ release = this.acquireLock(directory, lockPath);
44516
+ try {
44517
+ const onDisk = this.read();
44518
+ const mergedOauth = { ...onDisk.oauth };
44519
+ for (const removed of snapshot.removedOauth ?? []) {
44520
+ delete mergedOauth[removed];
44521
+ }
44522
+ Object.assign(mergedOauth, snapshot.oauth);
44523
+ const merged = {
44524
+ version: AUTH_FILE_VERSION,
44525
+ oauth: mergedOauth
44526
+ };
44527
+ const temporary = `${this.authPath}.${process.pid}.tmp`;
44528
+ try {
44529
+ fs2.writeFileSync(temporary, `${JSON.stringify(merged, null, 2)}
44530
+ `, { flag: "wx", mode: 384 });
44531
+ fs2.renameSync(temporary, this.authPath);
44532
+ } finally {
44533
+ if (fs2.existsSync(temporary)) fs2.unlinkSync(temporary);
44534
+ }
44535
+ } finally {
44536
+ release();
44537
+ }
44538
+ } catch (error) {
44539
+ this.errors.push(error instanceof Error ? error : new Error(String(error)));
44540
+ }
44541
+ }
44542
+ drainErrors() {
44543
+ return this.errors.splice(0);
44544
+ }
44545
+ };
44546
+ var AuthStorage = class _AuthStorage {
44383
44547
  providerApiKeys = /* @__PURE__ */ new Map();
44384
44548
  runtimeApiKeys = /* @__PURE__ */ new Map();
44385
44549
  providerEnvVars = /* @__PURE__ */ new Map();
44386
44550
  legacyApiKey;
44551
+ oauthCredentials = /* @__PURE__ */ new Map();
44552
+ removedOauthProviders = /* @__PURE__ */ new Set();
44553
+ backend;
44554
+ constructor(backend) {
44555
+ this.backend = backend;
44556
+ if (backend) {
44557
+ for (const [provider, credentials] of Object.entries(backend.read().oauth)) {
44558
+ this.oauthCredentials.set(provider, credentials);
44559
+ }
44560
+ }
44561
+ }
44562
+ /** Runtime-only storage (no persistence). */
44563
+ static create() {
44564
+ return new _AuthStorage();
44565
+ }
44566
+ /** Storage bound to a persistence backend (OAuth credentials survive restarts). */
44567
+ static fromStorage(backend) {
44568
+ return new _AuthStorage(backend);
44569
+ }
44570
+ /** In-memory backend, for tests and embedders that manage their own persistence. */
44571
+ static inMemory() {
44572
+ return new _AuthStorage(new InMemoryAuthStorageBackend());
44573
+ }
44574
+ /** Drain buffered backend I/O errors (file backend only). */
44575
+ drainErrors() {
44576
+ const backend = this.backend;
44577
+ if (backend && typeof backend.drainErrors === "function") {
44578
+ return backend.drainErrors();
44579
+ }
44580
+ return [];
44581
+ }
44387
44582
  setLegacyApiKey(apiKey) {
44388
44583
  this.legacyApiKey = apiKey;
44389
44584
  }
@@ -44411,20 +44606,104 @@ var AuthStorage = class {
44411
44606
  getProviderEnvVar(provider) {
44412
44607
  return this.providerEnvVars.get(provider);
44413
44608
  }
44609
+ setOAuthCredentials(provider, credentials) {
44610
+ if (!credentials) {
44611
+ this.oauthCredentials.delete(provider);
44612
+ this.removedOauthProviders.add(provider);
44613
+ this.runtimeApiKeys.delete(provider);
44614
+ } else {
44615
+ this.oauthCredentials.set(provider, credentials);
44616
+ this.removedOauthProviders.delete(provider);
44617
+ }
44618
+ this.persistOAuth();
44619
+ }
44620
+ getOAuthCredentials(provider) {
44621
+ return this.oauthCredentials.get(provider);
44622
+ }
44623
+ /** Providers with persisted OAuth credentials (for restore/refresh passes). */
44624
+ getOAuthProviderNames() {
44625
+ return [...this.oauthCredentials.keys()];
44626
+ }
44627
+ persistOAuth() {
44628
+ if (!this.backend) return;
44629
+ this.backend.write({
44630
+ oauth: Object.fromEntries(this.oauthCredentials),
44631
+ removedOauth: [...this.removedOauthProviders]
44632
+ });
44633
+ }
44414
44634
  getApiKey(provider, explicitEnvVar) {
44415
44635
  const envVar = explicitEnvVar || this.getProviderEnvVar(provider);
44416
44636
  const envApiKey = envVar && typeof process !== "undefined" ? process.env?.[envVar] : void 0;
44417
- return this.runtimeApiKeys.get(provider) || this.providerApiKeys.get(provider) || envApiKey || this.legacyApiKey;
44637
+ return this.runtimeApiKeys.get(provider) || this.providerApiKeys.get(provider) || envApiKey || this.usableOAuthAccessToken(provider) || this.legacyApiKey;
44638
+ }
44639
+ /** Persisted OAuth access token, when it exists and has not expired. */
44640
+ usableOAuthAccessToken(provider) {
44641
+ const credentials = this.oauthCredentials.get(provider);
44642
+ if (!credentials?.access) return void 0;
44643
+ if (credentials.expires !== void 0 && credentials.expires <= Date.now() + OAUTH_EXPIRY_SKEW_MS) {
44644
+ return void 0;
44645
+ }
44646
+ return credentials.access;
44418
44647
  }
44419
44648
  };
44420
44649
 
44421
44650
  // src/core/model-registry.ts
44422
- import * as fs2 from "fs";
44423
- import * as path2 from "path";
44651
+ import * as fs3 from "fs";
44652
+ import * as path3 from "path";
44424
44653
  import { createHash, createHmac, randomBytes } from "node:crypto";
44654
+ var MODEL_OVERRIDE_KEYS = [
44655
+ "name",
44656
+ "reasoning",
44657
+ "input",
44658
+ "cost",
44659
+ "contextWindow",
44660
+ "maxTokens",
44661
+ "headers",
44662
+ "compat",
44663
+ "cacheCapability",
44664
+ "webSearch",
44665
+ "fileCapabilities",
44666
+ "displayName"
44667
+ ];
44668
+ function validateExtensionProviderConfig(provider, config) {
44669
+ if (!provider || provider !== provider.trim() || /\s/.test(provider)) throw new Error(`registerProvider: invalid provider id "${provider}"`);
44670
+ if (!config || typeof config !== "object") throw new Error("registerProvider: config must be an object");
44671
+ if (typeof config.baseUrl !== "string" || !config.baseUrl) throw new Error(`registerProvider("${provider}"): baseUrl is required`);
44672
+ try {
44673
+ new URL(config.baseUrl);
44674
+ } catch {
44675
+ throw new Error(`registerProvider("${provider}"): baseUrl must be an absolute URL`);
44676
+ }
44677
+ assertHeadersShape(config.headers, `registerProvider("${provider}")`);
44678
+ for (const model of config.models ?? []) {
44679
+ if (!model || typeof model.id !== "string" || !model.id.trim() || /\s/.test(model.id)) throw new Error(`registerProvider("${provider}"): invalid model id`);
44680
+ assertHeadersShape(model.headers, `registerProvider("${provider}") model "${model.id}"`);
44681
+ validateCompat(model.compat, `registerProvider("${provider}") model "${model.id}" compat`);
44682
+ validateCacheCapability(model.cacheCapability, `registerProvider("${provider}") model "${model.id}" cacheCapability`);
44683
+ validateWebSearchCapability(model.webSearch, `registerProvider("${provider}") model "${model.id}" webSearch`);
44684
+ validateFileCapabilities(model.fileCapabilities, `registerProvider("${provider}") model "${model.id}" fileCapabilities`);
44685
+ }
44686
+ }
44425
44687
  var REMOTE_CATALOG_CACHE_VERSION = 4;
44426
44688
  var SUPPORTED_REMOTE_APIS = /* @__PURE__ */ new Set(["openai-completions", "anthropic-messages"]);
44427
44689
  var MAX_REMOTE_CATALOG_BYTES = 2 * 1024 * 1024;
44690
+ var OAUTH_RESTORE_LEEWAY_MS = 6e4;
44691
+ var OAUTH_REFRESH_TIMEOUT_MS = 3e4;
44692
+ function withTimeout(promise, ms, timeoutMessage) {
44693
+ return new Promise((resolve7, reject) => {
44694
+ const timer = setTimeout(() => reject(new Error(timeoutMessage)), ms);
44695
+ Promise.resolve(promise).then(
44696
+ (value) => {
44697
+ clearTimeout(timer);
44698
+ resolve7(value);
44699
+ },
44700
+ (error) => {
44701
+ clearTimeout(timer);
44702
+ reject(error);
44703
+ }
44704
+ );
44705
+ });
44706
+ }
44428
44707
  var REMOTE_REFRESH_LOCK_WAIT_MS = 6e4;
44429
44708
  var REMOTE_REFRESH_LOCK_WRITE_GRACE_MS = 1e3;
44430
44709
  var remoteRefreshTails = /* @__PURE__ */ new Map();
@@ -44448,40 +44727,40 @@ function remoteIdentityDigest(remote) {
44448
44727
  return createHash("sha256").update(JSON.stringify({ version: REMOTE_CATALOG_CACHE_VERSION, ...canonicalRemote(remote) })).digest("hex");
44449
44728
  }
44450
44729
  function remoteCacheDirectory(modelsPath) {
44451
- return path2.join(path2.dirname(modelsPath), "cache", "remote-catalogs");
44730
+ return path3.join(path3.dirname(modelsPath), "cache", "remote-catalogs");
44452
44731
  }
44453
44732
  function safeRemoteCacheDirectory(modelsPath, create) {
44454
- const configDirectory = path2.dirname(modelsPath);
44455
- if (!fs2.existsSync(configDirectory)) {
44733
+ const configDirectory = path3.dirname(modelsPath);
44734
+ if (!fs3.existsSync(configDirectory)) {
44456
44735
  if (!create) return void 0;
44457
- fs2.mkdirSync(configDirectory, { recursive: true, mode: 448 });
44736
+ fs3.mkdirSync(configDirectory, { recursive: true, mode: 448 });
44458
44737
  }
44459
- const configRoot = fs2.realpathSync(configDirectory);
44460
- const cacheRoot = path2.join(configDirectory, "cache");
44738
+ const configRoot = fs3.realpathSync(configDirectory);
44739
+ const cacheRoot = path3.join(configDirectory, "cache");
44461
44740
  const directory = remoteCacheDirectory(modelsPath);
44462
44741
  for (const candidate of [cacheRoot, directory]) {
44463
- if (!fs2.existsSync(candidate)) {
44742
+ if (!fs3.existsSync(candidate)) {
44464
44743
  if (!create) return void 0;
44465
- fs2.mkdirSync(candidate, { mode: 448 });
44744
+ fs3.mkdirSync(candidate, { mode: 448 });
44466
44745
  }
44467
- const stat2 = fs2.lstatSync(candidate);
44746
+ const stat2 = fs3.lstatSync(candidate);
44468
44747
  if (stat2.isSymbolicLink() || !stat2.isDirectory()) throw new Error(`remote catalog cache path must be a real directory: ${candidate}`);
44469
- const canonical = fs2.realpathSync(candidate);
44470
- const relative2 = path2.relative(configRoot, canonical);
44471
- if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2)) {
44748
+ const canonical = fs3.realpathSync(candidate);
44749
+ const relative3 = path3.relative(configRoot, canonical);
44750
+ if (relative3 === ".." || relative3.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative3)) {
44472
44751
  throw new Error(`remote catalog cache path escapes the config directory: ${candidate}`);
44473
44752
  }
44474
44753
  try {
44475
- fs2.chmodSync(candidate, 448);
44754
+ fs3.chmodSync(candidate, 448);
44476
44755
  } catch {
44477
44756
  }
44478
44757
  }
44479
44758
  return directory;
44480
44759
  }
44481
44760
  function remoteRefreshLockPath(modelsPath) {
44482
- return path2.join(remoteCacheDirectory(modelsPath), ".refresh.lock");
44761
+ return path3.join(remoteCacheDirectory(modelsPath), ".refresh.lock");
44483
44762
  }
44484
- function processIsAlive(pid) {
44763
+ function processIsAlive2(pid) {
44485
44764
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
44486
44765
  try {
44487
44766
  process.kill(pid, 0);
@@ -44491,40 +44770,40 @@ function processIsAlive(pid) {
44491
44770
  }
44492
44771
  }
44493
44772
  function remoteRefreshLockIsStale(lockPath) {
44494
- const stat2 = fs2.lstatSync(lockPath);
44773
+ const stat2 = fs3.lstatSync(lockPath);
44495
44774
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog refresh lock must be a regular file: ${lockPath}`);
44496
44775
  let owner;
44497
44776
  try {
44498
- owner = JSON.parse(fs2.readFileSync(lockPath, "utf8"));
44777
+ owner = JSON.parse(fs3.readFileSync(lockPath, "utf8"));
44499
44778
  } catch {
44500
44779
  }
44501
44780
  const age = Date.now() - stat2.mtimeMs;
44502
44781
  const validOwner = owner?.version === 1 && Number.isSafeInteger(owner.pid) && typeof owner.token === "string" && typeof owner.createdAt === "number";
44503
- if (validOwner && processIsAlive(Number(owner.pid))) return false;
44782
+ if (validOwner && processIsAlive2(Number(owner.pid))) return false;
44504
44783
  return validOwner || age > REMOTE_REFRESH_LOCK_WRITE_GRACE_MS;
44505
44784
  }
44506
44785
  function sameFileIdentity(left, right, leftPath, rightPath) {
44507
44786
  if (left.dev !== right.dev || left.ino !== right.ino) return false;
44508
44787
  if (left.ino !== 0) return true;
44509
- return left.size === right.size && fs2.readFileSync(leftPath).equals(fs2.readFileSync(rightPath));
44788
+ return left.size === right.size && fs3.readFileSync(leftPath).equals(fs3.readFileSync(rightPath));
44510
44789
  }
44511
44790
  function reclaimStaleRemoteRefreshLock(lockPath) {
44512
44791
  if (!remoteRefreshLockIsStale(lockPath)) return false;
44513
44792
  const claimPath = `${lockPath}.reclaim-${process.pid}-${randomBytes(8).toString("hex")}`;
44514
44793
  try {
44515
- fs2.linkSync(lockPath, claimPath);
44516
- const claimed = fs2.lstatSync(claimPath);
44517
- const current = fs2.lstatSync(lockPath);
44794
+ fs3.linkSync(lockPath, claimPath);
44795
+ const claimed = fs3.lstatSync(claimPath);
44796
+ const current = fs3.lstatSync(lockPath);
44518
44797
  if (claimed.isSymbolicLink() || !claimed.isFile() || current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(claimed, current, claimPath, lockPath)) return false;
44519
44798
  if (!remoteRefreshLockIsStale(claimPath)) return false;
44520
- fs2.unlinkSync(lockPath);
44799
+ fs3.unlinkSync(lockPath);
44521
44800
  return true;
44522
44801
  } catch (error) {
44523
44802
  if (["ENOENT", "EEXIST"].includes(error.code ?? "")) return false;
44524
44803
  throw error;
44525
44804
  } finally {
44526
44805
  try {
44527
- fs2.unlinkSync(claimPath);
44806
+ fs3.unlinkSync(claimPath);
44528
44807
  } catch {
44529
44808
  }
44530
44809
  }
@@ -44536,19 +44815,19 @@ async function acquireRemoteRefreshLock(modelsPath) {
44536
44815
  while (true) {
44537
44816
  const token = randomBytes(16).toString("hex");
44538
44817
  try {
44539
- const fd = fs2.openSync(lockPath, "wx", 384);
44818
+ const fd = fs3.openSync(lockPath, "wx", 384);
44540
44819
  try {
44541
- fs2.writeFileSync(fd, `${JSON.stringify({ version: 1, pid: process.pid, token, createdAt: Date.now() })}
44820
+ fs3.writeFileSync(fd, `${JSON.stringify({ version: 1, pid: process.pid, token, createdAt: Date.now() })}
44542
44821
  `);
44543
44822
  } finally {
44544
- fs2.closeSync(fd);
44823
+ fs3.closeSync(fd);
44545
44824
  }
44546
44825
  return () => {
44547
44826
  try {
44548
- const stat2 = fs2.lstatSync(lockPath);
44827
+ const stat2 = fs3.lstatSync(lockPath);
44549
44828
  if (stat2.isSymbolicLink() || !stat2.isFile()) return;
44550
- const current = JSON.parse(fs2.readFileSync(lockPath, "utf8"));
44551
- if (current.token === token) fs2.unlinkSync(lockPath);
44829
+ const current = JSON.parse(fs3.readFileSync(lockPath, "utf8"));
44830
+ if (current.token === token) fs3.unlinkSync(lockPath);
44552
44831
  } catch {
44553
44832
  }
44554
44833
  };
@@ -44561,29 +44840,29 @@ async function acquireRemoteRefreshLock(modelsPath) {
44561
44840
  throw lockError;
44562
44841
  }
44563
44842
  if (Date.now() >= deadline) throw new Error(`timed out waiting for remote catalog refresh lock: ${lockPath}`);
44564
- await new Promise((resolve6) => setTimeout(resolve6, 25));
44843
+ await new Promise((resolve7) => setTimeout(resolve7, 25));
44565
44844
  }
44566
44845
  }
44567
44846
  }
44568
44847
  function readSafeRemoteCacheFile(directory, filepath) {
44569
- const stat2 = fs2.lstatSync(filepath);
44848
+ const stat2 = fs3.lstatSync(filepath);
44570
44849
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog cache entry must be a regular file: ${filepath}`);
44571
- const canonicalDirectory = fs2.realpathSync(directory);
44572
- const canonicalFile = fs2.realpathSync(filepath);
44573
- const relative2 = path2.relative(canonicalDirectory, canonicalFile);
44574
- if (!relative2 || relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2)) {
44850
+ const canonicalDirectory = fs3.realpathSync(directory);
44851
+ const canonicalFile = fs3.realpathSync(filepath);
44852
+ const relative3 = path3.relative(canonicalDirectory, canonicalFile);
44853
+ if (!relative3 || relative3 === ".." || relative3.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative3)) {
44575
44854
  throw new Error(`remote catalog cache entry escapes its cache directory: ${filepath}`);
44576
44855
  }
44577
- return fs2.readFileSync(filepath);
44856
+ return fs3.readFileSync(filepath);
44578
44857
  }
44579
44858
  function credentialNamespaceKey(modelsPath, create) {
44580
44859
  const directory = safeRemoteCacheDirectory(modelsPath, create);
44581
44860
  if (!directory) return void 0;
44582
- const keyPath = path2.join(directory, ".credential-namespace-key");
44583
- if (!fs2.existsSync(keyPath) && !create) return void 0;
44584
- if (create && !fs2.existsSync(keyPath)) {
44861
+ const keyPath = path3.join(directory, ".credential-namespace-key");
44862
+ if (!fs3.existsSync(keyPath) && !create) return void 0;
44863
+ if (create && !fs3.existsSync(keyPath)) {
44585
44864
  try {
44586
- fs2.writeFileSync(keyPath, randomBytes(32), { flag: "wx", mode: 384 });
44865
+ fs3.writeFileSync(keyPath, randomBytes(32), { flag: "wx", mode: 384 });
44587
44866
  } catch (error) {
44588
44867
  if (!isPlainRecord(error) || error.code !== "EEXIST") throw error;
44589
44868
  }
@@ -44591,7 +44870,7 @@ function credentialNamespaceKey(modelsPath, create) {
44591
44870
  const key = readSafeRemoteCacheFile(directory, keyPath);
44592
44871
  if (key.byteLength !== 32) throw new Error("remote catalog credential namespace key is invalid");
44593
44872
  try {
44594
- fs2.chmodSync(keyPath, 384);
44873
+ fs3.chmodSync(keyPath, 384);
44595
44874
  } catch {
44596
44875
  }
44597
44876
  return key;
@@ -44602,28 +44881,28 @@ function credentialFingerprint(modelsPath, remote, credential, create = false) {
44602
44881
  return createHmac("sha256", key).update(remoteIdentityDigest(remote)).update("\0").update(credential).digest("hex");
44603
44882
  }
44604
44883
  function remoteCachePath(modelsPath, remote, fingerprint) {
44605
- return path2.join(remoteCacheDirectory(modelsPath), `${remoteIdentityDigest(remote)}-${fingerprint}.json`);
44884
+ return path3.join(remoteCacheDirectory(modelsPath), `${remoteIdentityDigest(remote)}-${fingerprint}.json`);
44606
44885
  }
44607
44886
  function pruneLegacyRemoteCatalogCaches(modelsPath) {
44608
44887
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44609
44888
  if (!directory) return;
44610
- for (const name of fs2.readdirSync(directory)) {
44889
+ for (const name of fs3.readdirSync(directory)) {
44611
44890
  if (!name.endsWith(".json")) continue;
44612
- const filepath = path2.join(directory, name);
44891
+ const filepath = path3.join(directory, name);
44613
44892
  try {
44614
44893
  const value = JSON.parse(readSafeRemoteCacheFile(directory, filepath).toString("utf8"));
44615
44894
  if (value.version === REMOTE_CATALOG_CACHE_VERSION) continue;
44616
44895
  } catch {
44617
44896
  let stat2;
44618
44897
  try {
44619
- stat2 = fs2.lstatSync(filepath);
44898
+ stat2 = fs3.lstatSync(filepath);
44620
44899
  } catch {
44621
44900
  continue;
44622
44901
  }
44623
44902
  if (stat2.isSymbolicLink() || !stat2.isFile()) continue;
44624
44903
  }
44625
44904
  try {
44626
- fs2.unlinkSync(filepath);
44905
+ fs3.unlinkSync(filepath);
44627
44906
  } catch {
44628
44907
  }
44629
44908
  }
@@ -44632,7 +44911,7 @@ function scrubActiveSecretsFromRemoteCaches(modelsPath, activeSecrets) {
44632
44911
  if (activeSecrets.length === 0) return;
44633
44912
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44634
44913
  if (!directory) return;
44635
- const handle = fs2.opendirSync(directory);
44914
+ const handle = fs3.opendirSync(directory);
44636
44915
  try {
44637
44916
  let scanned = 0;
44638
44917
  while (true) {
@@ -44642,14 +44921,14 @@ function scrubActiveSecretsFromRemoteCaches(modelsPath, activeSecrets) {
44642
44921
  const isCache = entry.name.endsWith(".json");
44643
44922
  const isOrphanedTemporary = /^\..+\.json\.\d+\.[0-9a-f]+\.tmp$/.test(entry.name);
44644
44923
  if (!entry.isFile() || !isCache && !isOrphanedTemporary) continue;
44645
- const filepath = path2.join(directory, entry.name);
44924
+ const filepath = path3.join(directory, entry.name);
44646
44925
  try {
44647
44926
  const raw = readSafeRemoteCacheFile(directory, filepath).toString("utf8");
44648
44927
  assertNoSecretSubstrings(raw, activeSecrets, "remote catalog cache");
44649
44928
  } catch {
44650
44929
  try {
44651
- const stat2 = fs2.lstatSync(filepath);
44652
- if (!stat2.isSymbolicLink() && stat2.isFile()) fs2.unlinkSync(filepath);
44930
+ const stat2 = fs3.lstatSync(filepath);
44931
+ if (!stat2.isSymbolicLink() && stat2.isFile()) fs3.unlinkSync(filepath);
44653
44932
  } catch {
44654
44933
  }
44655
44934
  }
@@ -44720,7 +44999,7 @@ function readRemoteCache(modelsPath, remote, fingerprint, activeSecrets) {
44720
44999
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44721
45000
  if (!directory) return void 0;
44722
45001
  const tombstone = remoteCacheTombstonePath(modelsPath, remote, fingerprint);
44723
- if (fs2.existsSync(tombstone)) return void 0;
45002
+ if (fs3.existsSync(tombstone)) return void 0;
44724
45003
  const value = JSON.parse(readSafeRemoteCacheFile(directory, remoteCachePath(modelsPath, remote, fingerprint)).toString("utf8"));
44725
45004
  if (value.version !== REMOTE_CATALOG_CACHE_VERSION || !sameRemoteIdentity(value.identity, remote) || value.credentialFingerprint !== fingerprint || typeof value.payloadSha256 !== "string" || value.payloadSha256 !== payloadDigest(value.payload)) return void 0;
44726
45005
  assertNoSecretSubstrings({ etag: value.etag, payload: value.payload }, activeSecrets, "remote catalog cache");
@@ -44740,23 +45019,23 @@ function removeRemoteCache(modelsPath, remote, fingerprint) {
44740
45019
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44741
45020
  if (!directory) return;
44742
45021
  const filepath = remoteCachePath(modelsPath, remote, fingerprint);
44743
- if (fs2.existsSync(filepath)) {
44744
- const stat2 = fs2.lstatSync(filepath);
45022
+ if (fs3.existsSync(filepath)) {
45023
+ const stat2 = fs3.lstatSync(filepath);
44745
45024
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog cache entry is not a regular file: ${filepath}`);
44746
- fs2.unlinkSync(filepath);
45025
+ fs3.unlinkSync(filepath);
44747
45026
  }
44748
45027
  const tombstone = remoteCacheTombstonePath(modelsPath, remote, fingerprint);
44749
- if (fs2.existsSync(tombstone)) fs2.unlinkSync(tombstone);
45028
+ if (fs3.existsSync(tombstone)) fs3.unlinkSync(tombstone);
44750
45029
  return;
44751
45030
  } catch (error) {
44752
45031
  const directory = safeRemoteCacheDirectory(modelsPath, true);
44753
45032
  if (!directory) throw error;
44754
45033
  const tombstone = remoteCacheTombstonePath(modelsPath, remote, fingerprint);
44755
45034
  try {
44756
- fs2.writeFileSync(tombstone, `${JSON.stringify({ version: 1, revoked: true })}
45035
+ fs3.writeFileSync(tombstone, `${JSON.stringify({ version: 1, revoked: true })}
44757
45036
  `, { flag: "wx", mode: 384 });
44758
45037
  } catch (tombstoneError) {
44759
- if (!fs2.existsSync(tombstone)) throw tombstoneError;
45038
+ if (!fs3.existsSync(tombstone)) throw tombstoneError;
44760
45039
  }
44761
45040
  }
44762
45041
  }
@@ -44818,7 +45097,7 @@ function validatedRemoteCatalogs(value) {
44818
45097
  }
44819
45098
  function validateModelsDocumentShape(value) {
44820
45099
  if (!isPlainRecord(value)) throw new Error("models.json must be an object");
44821
- assertOnlyKeys(value, ["$schema", "profiles", "remoteCatalogs", "defaults"], "models.json");
45100
+ assertOnlyKeys(value, ["$schema", "profiles", "remoteCatalogs", "defaults", "modelOverrides"], "models.json");
44822
45101
  if (value.$schema !== void 0 && (typeof value.$schema !== "string" || !value.$schema)) throw new Error("models.json $schema must be a non-empty string");
44823
45102
  const hasProfiles = Object.prototype.hasOwnProperty.call(value, "profiles");
44824
45103
  const hasRemoteCatalogs = Object.prototype.hasOwnProperty.call(value, "remoteCatalogs");
@@ -44831,6 +45110,7 @@ function validateModelsDocumentShape(value) {
44831
45110
  validateProfileConfig(profileId, profile);
44832
45111
  }
44833
45112
  }
45113
+ validateModelOverrides(value.modelOverrides);
44834
45114
  if (value.defaults !== void 0) {
44835
45115
  if (!isPlainRecord(value.defaults)) throw new Error("models.json defaults must be an object");
44836
45116
  assertOnlyKeys(value.defaults, ["provider", "modelId", "modelClasses", "toolModels"], "models.json defaults");
@@ -44915,6 +45195,8 @@ function validateCompat(value, label) {
44915
45195
  "requiresAssistantAfterToolResult",
44916
45196
  "requiresThinkingAsText",
44917
45197
  "supportsStrictMode",
45198
+ "streamRequiresDone",
45199
+ "streamRequiresFinishReason",
44918
45200
  "cacheCapability"
44919
45201
  ], label);
44920
45202
  if (value.forcedReasoningEffort !== void 0 && value.forcedReasoningEffort !== "none") {
@@ -44928,7 +45210,9 @@ function validateCompat(value, label) {
44928
45210
  "requiresToolResultName",
44929
45211
  "requiresAssistantAfterToolResult",
44930
45212
  "requiresThinkingAsText",
44931
- "supportsStrictMode"
45213
+ "supportsStrictMode",
45214
+ "streamRequiresDone",
45215
+ "streamRequiresFinishReason"
44932
45216
  ]) assertOptionalBoolean(value[field], `${label}.${field}`);
44933
45217
  if (value.maxTokensField !== void 0 && value.maxTokensField !== "max_completion_tokens" && value.maxTokensField !== "max_tokens") {
44934
45218
  throw new Error(`${label}.maxTokensField is invalid`);
@@ -45107,6 +45391,46 @@ async function readResponseTextBounded(response, maxBytes) {
45107
45391
  reader.releaseLock();
45108
45392
  }
45109
45393
  }
45394
+ function deepMergePlainRecords(base, patch) {
45395
+ const result = isPlainRecord(base) ? { ...base } : {};
45396
+ if (!isPlainRecord(patch)) return result;
45397
+ for (const [key, value] of Object.entries(patch)) {
45398
+ const current = result[key];
45399
+ result[key] = isPlainRecord(current) && isPlainRecord(value) ? deepMergePlainRecords(current, value) : value;
45400
+ }
45401
+ return result;
45402
+ }
45403
+ function validateModelOverrides(value) {
45404
+ if (value === void 0) return;
45405
+ if (!isPlainRecord(value)) throw new Error("models.json modelOverrides must be an object");
45406
+ for (const [key, override] of Object.entries(value)) {
45407
+ if (!/^[^\s/]+\/[^\s/]+$/.test(key)) throw new Error(`models.json modelOverrides key must be "provider/modelId": ${key}`);
45408
+ if (!isPlainRecord(override)) throw new Error(`models.json modelOverrides["${key}"] must be an object`);
45409
+ assertOnlyKeys(override, [...MODEL_OVERRIDE_KEYS], `models.json modelOverrides["${key}"]`);
45410
+ if (override.name !== void 0 && (typeof override.name !== "string" || !override.name.trim())) throw new Error(`models.json modelOverrides["${key}"].name must be a non-empty string`);
45411
+ if (override.displayName !== void 0 && (typeof override.displayName !== "string" || !override.displayName.trim())) throw new Error(`models.json modelOverrides["${key}"].displayName must be a non-empty string`);
45412
+ if (override.reasoning !== void 0 && typeof override.reasoning !== "boolean") throw new Error(`models.json modelOverrides["${key}"].reasoning must be a boolean`);
45413
+ if (override.input !== void 0 && (!Array.isArray(override.input) || override.input.length === 0 || override.input.some((kind) => !["text", "image", "audio", "video"].includes(String(kind))))) throw new Error(`models.json modelOverrides["${key}"].input is invalid`);
45414
+ if (override.contextWindow !== void 0 && (!Number.isInteger(override.contextWindow) || Number(override.contextWindow) < 1)) throw new Error(`models.json modelOverrides["${key}"].contextWindow is invalid`);
45415
+ if (override.maxTokens !== void 0 && (!Number.isInteger(override.maxTokens) || Number(override.maxTokens) < 1)) throw new Error(`models.json modelOverrides["${key}"].maxTokens is invalid`);
45416
+ if (override.cost !== void 0) {
45417
+ if (!isPlainRecord(override.cost)) throw new Error(`models.json modelOverrides["${key}"].cost must be an object`);
45418
+ for (const field of ["input", "output", "cacheRead", "cacheWrite"]) {
45419
+ if (override.cost[field] !== void 0 && (typeof override.cost[field] !== "number" || Number.isNaN(override.cost[field]))) throw new Error(`models.json modelOverrides["${key}"].cost.${field} must be a number`);
45420
+ }
45421
+ }
45422
+ if (override.headers !== void 0) {
45423
+ if (!isPlainRecord(override.headers)) throw new Error(`models.json modelOverrides["${key}"].headers must be an object`);
45424
+ for (const [headerName, headerValue] of Object.entries(override.headers)) {
45425
+ if (typeof headerValue !== "string") throw new Error(`models.json modelOverrides["${key}"].headers.${headerName} must be a string`);
45426
+ }
45427
+ }
45428
+ validateCompat(override.compat, `models.json modelOverrides["${key}"].compat`);
45429
+ validateCacheCapability(override.cacheCapability, `models.json modelOverrides["${key}"].cacheCapability`);
45430
+ validateWebSearchCapability(override.webSearch, `models.json modelOverrides["${key}"].webSearch`);
45431
+ validateFileCapabilities(override.fileCapabilities, `models.json modelOverrides["${key}"].fileCapabilities`);
45432
+ }
45433
+ }
45110
45434
  function validateProfileConfig(profileId, profileConfig) {
45111
45435
  if (!isPlainRecord(profileConfig)) throw new Error(`Profile "${profileId}" must be an object.`);
45112
45436
  assertOnlyKeys(profileConfig, [
@@ -45201,12 +45525,14 @@ var ModelRegistry = class {
45201
45525
  modelsPath;
45202
45526
  configDefaults;
45203
45527
  modelOrder = [];
45528
+ modelOverrides = {};
45529
+ extensionOAuthProviders = /* @__PURE__ */ new Map();
45204
45530
  usingExternalProviders = false;
45205
45531
  localProfileIds = /* @__PURE__ */ new Set();
45206
45532
  remoteCredentialEnvByProvider = /* @__PURE__ */ new Map();
45207
45533
  remoteCatalogByProvider = /* @__PURE__ */ new Map();
45208
45534
  remoteCredentialFingerprintByCatalog = /* @__PURE__ */ new Map();
45209
- constructor(authStorage, modelsPath = path2.join(getConfigDir(), "models.json")) {
45535
+ constructor(authStorage, modelsPath = path3.join(getConfigDir(), "models.json")) {
45210
45536
  this.authStorage = authStorage;
45211
45537
  this.modelsPath = modelsPath;
45212
45538
  this.load();
@@ -45227,6 +45553,7 @@ var ModelRegistry = class {
45227
45553
  this.loadError = void 0;
45228
45554
  this.models.clear();
45229
45555
  this.modelOrder = [];
45556
+ this.modelOverrides = {};
45230
45557
  this.configDefaults = void 0;
45231
45558
  this.usingExternalProviders = false;
45232
45559
  this.localProfileIds.clear();
@@ -45239,16 +45566,17 @@ var ModelRegistry = class {
45239
45566
  this.loadError = error instanceof Error ? error.message : String(error);
45240
45567
  return;
45241
45568
  }
45242
- if (!fs2.existsSync(this.modelsPath)) {
45569
+ if (!fs3.existsSync(this.modelsPath)) {
45243
45570
  return;
45244
45571
  }
45245
45572
  try {
45246
- const raw = JSON.parse(fs2.readFileSync(this.modelsPath, "utf-8"));
45573
+ const raw = JSON.parse(fs3.readFileSync(this.modelsPath, "utf-8"));
45247
45574
  if (raw && typeof raw === "object" && "providers" in raw) {
45248
45575
  throw new Error('Legacy models.json format is no longer supported. Use top-level "profiles".');
45249
45576
  }
45250
45577
  validateModelsDocumentShape(raw);
45251
45578
  const remotes = validatedRemoteCatalogs(raw.remoteCatalogs);
45579
+ this.modelOverrides = raw.modelOverrides ?? {};
45252
45580
  const activeRemoteSecrets = configuredRemoteCredentialValues(raw);
45253
45581
  scrubActiveSecretsFromRemoteCaches(this.modelsPath, activeRemoteSecrets);
45254
45582
  const configuredProviders = Object.entries(raw.profiles || {});
@@ -45334,10 +45662,40 @@ var ModelRegistry = class {
45334
45662
  this.registerRemoteProfile(provider, profile, identity.apiKeyEnv, owner);
45335
45663
  }
45336
45664
  }
45665
+ this.applyModelOverrides();
45337
45666
  } catch (error) {
45338
45667
  this.loadError = error instanceof Error ? error.message : String(error);
45339
45668
  }
45340
45669
  }
45670
+ /**
45671
+ * Re-apply models.json modelOverrides after any (re)materialization pass
45672
+ * (initial load, remote catalog refresh). Overrides patch descriptive
45673
+ * fields only; nested plain objects deep-merge, arrays/scalars replace.
45674
+ */
45675
+ applyModelOverrides() {
45676
+ for (const [key, override] of Object.entries(this.modelOverrides)) {
45677
+ const slash = key.indexOf("/");
45678
+ if (slash <= 0) continue;
45679
+ const provider = key.slice(0, slash);
45680
+ const modelId = key.slice(slash + 1);
45681
+ const existing = this.models.get(provider)?.get(modelId);
45682
+ if (!existing) continue;
45683
+ const patched = { ...existing };
45684
+ if (override.name !== void 0) patched.name = override.name;
45685
+ if (override.displayName !== void 0) patched.displayName = override.displayName;
45686
+ if (override.reasoning !== void 0) patched.reasoning = override.reasoning;
45687
+ if (override.input !== void 0) patched.input = [...override.input];
45688
+ if (override.contextWindow !== void 0) patched.contextWindow = override.contextWindow;
45689
+ if (override.maxTokens !== void 0) patched.maxTokens = override.maxTokens;
45690
+ if (override.cost !== void 0) patched.cost = { ...existing.cost, ...override.cost };
45691
+ if (override.headers !== void 0) patched.headers = { ...existing.headers, ...override.headers };
45692
+ if (override.compat !== void 0) patched.compat = deepMergePlainRecords(existing.compat, override.compat);
45693
+ if (override.cacheCapability !== void 0) patched.cacheCapability = { ...existing.cacheCapability, ...override.cacheCapability };
45694
+ if (override.webSearch !== void 0) patched.webSearch = { ...existing.webSearch, ...override.webSearch };
45695
+ if (override.fileCapabilities !== void 0) patched.fileCapabilities = deepMergePlainRecords(existing.fileCapabilities, override.fileCapabilities);
45696
+ this.upsert(patched);
45697
+ }
45698
+ }
45341
45699
  registerRemoteProfile(provider, providerConfig, credentialEnv, catalogId) {
45342
45700
  if (this.localProfileIds.has(provider)) return;
45343
45701
  const existingCatalog = this.remoteCatalogByProvider.get(provider);
@@ -45397,27 +45755,28 @@ var ModelRegistry = class {
45397
45755
  for (const [provider, profile] of candidates) {
45398
45756
  this.registerRemoteProfile(provider, profile, credentialEnv, catalogId);
45399
45757
  }
45758
+ this.applyModelOverrides();
45400
45759
  }
45401
45760
  writeRemoteCache(remote, fingerprint, value) {
45402
45761
  const directory = safeRemoteCacheDirectory(this.modelsPath, true);
45403
45762
  const target = remoteCachePath(this.modelsPath, remote, fingerprint);
45404
- if (fs2.existsSync(target)) {
45405
- const stat2 = fs2.lstatSync(target);
45763
+ if (fs3.existsSync(target)) {
45764
+ const stat2 = fs3.lstatSync(target);
45406
45765
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog cache entry must be a regular file: ${target}`);
45407
45766
  }
45408
- const temporary = path2.join(directory, `.${path2.basename(target)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
45767
+ const temporary = path3.join(directory, `.${path3.basename(target)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
45409
45768
  try {
45410
- fs2.writeFileSync(temporary, `${JSON.stringify(value)}
45769
+ fs3.writeFileSync(temporary, `${JSON.stringify(value)}
45411
45770
  `, { flag: "wx", mode: 384 });
45412
- fs2.renameSync(temporary, target);
45771
+ fs3.renameSync(temporary, target);
45413
45772
  const tombstone = remoteCacheTombstonePath(this.modelsPath, remote, fingerprint);
45414
- if (fs2.existsSync(tombstone)) fs2.unlinkSync(tombstone);
45773
+ if (fs3.existsSync(tombstone)) fs3.unlinkSync(tombstone);
45415
45774
  } finally {
45416
- if (fs2.existsSync(temporary)) fs2.unlinkSync(temporary);
45775
+ if (fs3.existsSync(temporary)) fs3.unlinkSync(temporary);
45417
45776
  }
45418
45777
  }
45419
45778
  refreshRemoteCatalogs() {
45420
- const queueKey = path2.resolve(this.modelsPath);
45779
+ const queueKey = path3.resolve(this.modelsPath);
45421
45780
  const previous = remoteRefreshTails.get(queueKey) ?? Promise.resolve();
45422
45781
  const refresh = previous.then(() => this.performRemoteCatalogRefresh());
45423
45782
  const tail = refresh.catch(() => void 0);
@@ -45442,14 +45801,14 @@ var ModelRegistry = class {
45442
45801
  this.loadError = error instanceof Error ? error.message : String(error);
45443
45802
  return;
45444
45803
  }
45445
- if (!fs2.existsSync(this.modelsPath)) {
45804
+ if (!fs3.existsSync(this.modelsPath)) {
45446
45805
  for (const owner of new Set(this.remoteCatalogByProvider.values())) this.removeRemoteCatalogProfiles(owner);
45447
45806
  this.remoteCredentialFingerprintByCatalog.clear();
45448
45807
  return;
45449
45808
  }
45450
45809
  let config;
45451
45810
  try {
45452
- const decoded = JSON.parse(fs2.readFileSync(this.modelsPath, "utf8"));
45811
+ const decoded = JSON.parse(fs3.readFileSync(this.modelsPath, "utf8"));
45453
45812
  validateModelsDocumentShape(decoded);
45454
45813
  config = decoded;
45455
45814
  } catch {
@@ -45457,6 +45816,7 @@ var ModelRegistry = class {
45457
45816
  this.remoteCredentialFingerprintByCatalog.clear();
45458
45817
  return;
45459
45818
  }
45819
+ this.modelOverrides = config.modelOverrides ?? {};
45460
45820
  let remotes;
45461
45821
  try {
45462
45822
  remotes = validatedRemoteCatalogs(config.remoteCatalogs);
@@ -45738,11 +46098,131 @@ var ModelRegistry = class {
45738
46098
  if (remoteEnv) return typeof process !== "undefined" ? process.env?.[remoteEnv] : void 0;
45739
46099
  return this.authStorage.getApiKey(provider);
45740
46100
  }
46101
+ /**
46102
+ * Register a provider at runtime (extension API). Not persisted to
46103
+ * models.json: runtime registrations live for the process unless the
46104
+ * extension registers them again. Returns an unregister handle that
46105
+ * restores the provider's previous state (models, order, env binding).
46106
+ *
46107
+ * When the provider already exists (local profile or remote catalog),
46108
+ * models are merged by id: registered models upsert over existing entries
46109
+ * with the same id; existing entries not named in `config.models` survive.
46110
+ * baseUrl/api/headers patch every model of that provider (existing and
46111
+ * new) when provided.
46112
+ */
46113
+ registerProvider(provider, config) {
46114
+ validateExtensionProviderConfig(provider, config);
46115
+ const previousModels = this.models.get(provider) ? new Map(this.models.get(provider)) : void 0;
46116
+ const previousOrder = [...this.modelOrder];
46117
+ const previousEnvVar = this.authStorage.getProviderEnvVar(provider);
46118
+ if (config.apiKey) {
46119
+ this.authStorage.setRuntimeApiKey(provider, config.apiKey);
46120
+ }
46121
+ if (config.apiKeyEnv) {
46122
+ this.authStorage.setProviderEnvVar(provider, config.apiKeyEnv);
46123
+ }
46124
+ const existing = this.models.get(provider);
46125
+ if (existing && (config.baseUrl || config.api || config.headers)) {
46126
+ for (const model of existing.values()) {
46127
+ this.upsert({
46128
+ ...model,
46129
+ baseUrl: config.baseUrl || model.baseUrl,
46130
+ api: config.api || model.api,
46131
+ headers: config.headers ? { ...model.headers, ...config.headers } : model.headers
46132
+ });
46133
+ }
46134
+ }
46135
+ for (const customModel of config.models ?? []) {
46136
+ const baseModel = this.models.get(provider)?.get(customModel.id);
46137
+ this.upsert({
46138
+ id: customModel.id,
46139
+ name: customModel.name || baseModel?.name || customModel.id,
46140
+ api: config.api || baseModel?.api || "openai-completions",
46141
+ provider,
46142
+ baseUrl: config.baseUrl || baseModel?.baseUrl || "",
46143
+ reasoning: customModel.reasoning ?? baseModel?.reasoning ?? false,
46144
+ input: customModel.input || baseModel?.input || ["text"],
46145
+ cost: customModel.cost || baseModel?.cost || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
46146
+ contextWindow: customModel.contextWindow || baseModel?.contextWindow || 128e3,
46147
+ maxTokens: customModel.maxTokens || baseModel?.maxTokens || 8192,
46148
+ apiKeyEnv: config.apiKeyEnv || baseModel?.apiKeyEnv,
46149
+ displayName: customModel.displayName || baseModel?.displayName || provider,
46150
+ headers: config.headers ? { ...baseModel?.headers, ...config.headers, ...customModel.headers } : customModel.headers || baseModel?.headers,
46151
+ compat: customModel.compat || baseModel?.compat,
46152
+ cacheCapability: customModel.cacheCapability || baseModel?.cacheCapability,
46153
+ webSearch: customModel.webSearch || baseModel?.webSearch,
46154
+ fileCapabilities: customModel.fileCapabilities || baseModel?.fileCapabilities
46155
+ });
46156
+ }
46157
+ this.applyModelOverrides();
46158
+ this.usingExternalProviders = true;
46159
+ const registeredOAuth = config.oauth;
46160
+ if (registeredOAuth) this.extensionOAuthProviders.set(provider, registeredOAuth);
46161
+ return () => {
46162
+ if (previousModels) this.models.set(provider, previousModels);
46163
+ else this.models.delete(provider);
46164
+ this.modelOrder = previousOrder;
46165
+ this.authStorage.setRuntimeApiKey(provider, void 0);
46166
+ this.authStorage.setProviderEnvVar(provider, previousEnvVar);
46167
+ if (registeredOAuth) this.extensionOAuthProviders.delete(provider);
46168
+ this.applyModelOverrides();
46169
+ };
46170
+ }
46171
+ /** OAuth flow declared by an extension-registered provider, if any. */
46172
+ getExtensionOAuth(provider) {
46173
+ return this.extensionOAuthProviders.get(provider);
46174
+ }
46175
+ /**
46176
+ * Restore persisted OAuth credentials now that extensions have registered
46177
+ * their providers: refresh expired tokens when the extension declares
46178
+ * refreshToken, and drop the runtime binding when a token cannot be
46179
+ * recovered (getApiKey then reports the provider as unauthenticated).
46180
+ * Never throws; returns per-provider outcomes for the host to surface.
46181
+ */
46182
+ async restorePersistedOAuth() {
46183
+ const authStorage = this.authStorage;
46184
+ const results = [];
46185
+ for (const provider of authStorage.getOAuthProviderNames()) {
46186
+ const credentials = authStorage.getOAuthCredentials(provider);
46187
+ if (!credentials?.access) continue;
46188
+ const expired = credentials.expires !== void 0 && credentials.expires <= Date.now() + OAUTH_RESTORE_LEEWAY_MS;
46189
+ if (!expired) continue;
46190
+ const oauth = this.extensionOAuthProviders.get(provider);
46191
+ if (!oauth?.refreshToken || !credentials.refresh) {
46192
+ authStorage.setRuntimeApiKey(provider, void 0);
46193
+ results.push({ provider, action: "not-refreshable", detail: "token expired and no refresh flow" });
46194
+ continue;
46195
+ }
46196
+ try {
46197
+ const next = await withTimeout(
46198
+ oauth.refreshToken({
46199
+ access: credentials.access,
46200
+ refresh: credentials.refresh,
46201
+ expires: credentials.expires
46202
+ }),
46203
+ OAUTH_REFRESH_TIMEOUT_MS,
46204
+ `${provider} refreshToken timed out after ${OAUTH_REFRESH_TIMEOUT_MS}ms`
46205
+ );
46206
+ const merged = { ...credentials, ...next };
46207
+ authStorage.setOAuthCredentials(provider, merged);
46208
+ authStorage.setRuntimeApiKey(provider, merged.access);
46209
+ results.push({ provider, action: "refreshed" });
46210
+ } catch (error) {
46211
+ authStorage.setRuntimeApiKey(provider, void 0);
46212
+ results.push({ provider, action: "refresh-failed", detail: error instanceof Error ? error.message : String(error) });
46213
+ }
46214
+ }
46215
+ return results;
46216
+ }
46217
+ /** Credential store backing this registry (for OAuth persistence). */
46218
+ getAuthStorage() {
46219
+ return this.authStorage;
46220
+ }
45741
46221
  };
45742
46222
 
45743
46223
  // src/core/settings-manager.ts
45744
- import * as fs3 from "fs";
45745
- import * as path3 from "path";
46224
+ import * as fs4 from "fs";
46225
+ import * as path4 from "path";
45746
46226
  var SettingsManager = class {
45747
46227
  settingsPath;
45748
46228
  legacyConfigPath;
@@ -45756,9 +46236,9 @@ var SettingsManager = class {
45756
46236
  this.legacyConfig = this.loadLegacyConfigFile();
45757
46237
  }
45758
46238
  readJson(filePath) {
45759
- if (!fs3.existsSync(filePath)) return null;
46239
+ if (!fs4.existsSync(filePath)) return null;
45760
46240
  try {
45761
- return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
46241
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
45762
46242
  } catch {
45763
46243
  return null;
45764
46244
  }
@@ -45770,19 +46250,19 @@ var SettingsManager = class {
45770
46250
  return this.readJson(this.legacyConfigPath) || {};
45771
46251
  }
45772
46252
  ensureDir() {
45773
- fs3.mkdirSync(getConfigDir(), { recursive: true });
46253
+ fs4.mkdirSync(getConfigDir(), { recursive: true });
45774
46254
  }
45775
46255
  writeSettings() {
45776
46256
  this.ensureDir();
45777
- fs3.writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2));
46257
+ fs4.writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2));
45778
46258
  }
45779
46259
  writeSettingsValue(settings) {
45780
46260
  this.ensureDir();
45781
- fs3.writeFileSync(this.settingsPath, JSON.stringify(settings, null, 2));
46261
+ fs4.writeFileSync(this.settingsPath, JSON.stringify(settings, null, 2));
45782
46262
  }
45783
46263
  writeLegacyConfig() {
45784
46264
  this.ensureDir();
45785
- fs3.writeFileSync(this.legacyConfigPath, JSON.stringify(this.legacyConfig, null, 2));
46265
+ fs4.writeFileSync(this.legacyConfigPath, JSON.stringify(this.legacyConfig, null, 2));
45786
46266
  }
45787
46267
  getLegacyApiKey() {
45788
46268
  return this.legacyConfig.apiKey;
@@ -45831,6 +46311,9 @@ var SettingsManager = class {
45831
46311
  }
45832
46312
  return "auto";
45833
46313
  }
46314
+ getBellNotificationsEnabled() {
46315
+ return this.settings.notifications?.bell !== false;
46316
+ }
45834
46317
  // ==========================================================================
45835
46318
  // Extension enablement (user ~/.pie/settings.json + project ./.pie/settings.json)
45836
46319
  // ==========================================================================
@@ -45839,8 +46322,8 @@ var SettingsManager = class {
45839
46322
  }
45840
46323
  writeProjectSettings(cwd, settings) {
45841
46324
  const settingsPath = getProjectSettingsPath(cwd);
45842
- fs3.mkdirSync(path3.dirname(settingsPath), { recursive: true });
45843
- fs3.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
46325
+ fs4.mkdirSync(path4.dirname(settingsPath), { recursive: true });
46326
+ fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
45844
46327
  }
45845
46328
  /** Raw extension enablement map for one scope. */
45846
46329
  getExtensionSettings(scope = "user", cwd = process.cwd()) {
@@ -45914,19 +46397,19 @@ function checkShell() {
45914
46397
  }
45915
46398
  function canWriteDirectory(dir) {
45916
46399
  try {
45917
- fs4.mkdirSync(dir, { recursive: true });
45918
- const probe = path4.join(dir, `.pie-doctor-${process.pid}-${Date.now()}`);
45919
- fs4.writeFileSync(probe, "ok", "utf8");
45920
- fs4.rmSync(probe, { force: true });
46400
+ fs5.mkdirSync(dir, { recursive: true });
46401
+ const probe = path5.join(dir, `.pie-doctor-${process.pid}-${Date.now()}`);
46402
+ fs5.writeFileSync(probe, "ok", "utf8");
46403
+ fs5.rmSync(probe, { force: true });
45921
46404
  return { ok: true };
45922
46405
  } catch (error) {
45923
46406
  return { ok: false, error: error instanceof Error ? error.message : String(error) };
45924
46407
  }
45925
46408
  }
45926
46409
  function isExecutable(candidate) {
45927
- if (!candidate || !fs4.existsSync(candidate)) return false;
46410
+ if (!candidate || !fs5.existsSync(candidate)) return false;
45928
46411
  try {
45929
- fs4.accessSync(candidate, fs4.constants.X_OK);
46412
+ fs5.accessSync(candidate, fs5.constants.X_OK);
45930
46413
  return true;
45931
46414
  } catch {
45932
46415
  return os.platform() === "win32";
@@ -45936,7 +46419,7 @@ function findOnPath(names) {
45936
46419
  for (const dir of (process.env.PATH || "").split(delimiter)) {
45937
46420
  if (!dir) continue;
45938
46421
  for (const name of names) {
45939
- const candidate = path4.join(dir, name);
46422
+ const candidate = path5.join(dir, name);
45940
46423
  if (isExecutable(candidate)) return candidate;
45941
46424
  }
45942
46425
  }
@@ -45952,10 +46435,10 @@ function findDoctorChromeExecutable() {
45952
46435
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
45953
46436
  "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
45954
46437
  ] : os.platform() === "win32" ? [
45955
- path4.join(process.env.PROGRAMFILES || "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
45956
- path4.join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "Google", "Chrome", "Application", "chrome.exe"),
45957
- path4.join(process.env.LOCALAPPDATA || path4.join(home, "AppData", "Local"), "Google", "Chrome", "Application", "chrome.exe"),
45958
- path4.join(process.env.PROGRAMFILES || "C:\\Program Files", "Microsoft", "Edge", "Application", "msedge.exe")
46438
+ path5.join(process.env.PROGRAMFILES || "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
46439
+ path5.join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "Google", "Chrome", "Application", "chrome.exe"),
46440
+ path5.join(process.env.LOCALAPPDATA || path5.join(home, "AppData", "Local"), "Google", "Chrome", "Application", "chrome.exe"),
46441
+ path5.join(process.env.PROGRAMFILES || "C:\\Program Files", "Microsoft", "Edge", "Application", "msedge.exe")
45959
46442
  ] : [
45960
46443
  "/usr/bin/google-chrome",
45961
46444
  "/usr/bin/google-chrome-stable",
@@ -46004,7 +46487,7 @@ function checkPlaywrightBrowser() {
46004
46487
  } catch {
46005
46488
  executablePath = void 0;
46006
46489
  }
46007
- if (executablePath && fs4.existsSync(executablePath)) {
46490
+ if (executablePath && fs5.existsSync(executablePath)) {
46008
46491
  return {
46009
46492
  id: "playwright_browser",
46010
46493
  label: "Playwright browser",
@@ -46013,7 +46496,7 @@ function checkPlaywrightBrowser() {
46013
46496
  details: { executablePath }
46014
46497
  };
46015
46498
  }
46016
- const skillWrapper = path4.join(getBuiltinDir(), "skills", "browser-tools", "playwright-cli.js");
46499
+ const skillWrapper = path5.join(getBuiltinDir(), "skills", "browser-tools", "playwright-cli.js");
46017
46500
  return {
46018
46501
  id: "playwright_browser",
46019
46502
  label: "Playwright browser",
@@ -46044,8 +46527,8 @@ function isLiveUnityInstance(instance, nowUnix = Math.floor(Date.now() / 1e3)) {
46044
46527
  return isProcessAlive(Number(instance.pid || 0));
46045
46528
  }
46046
46529
  function checkUnityRegistry() {
46047
- const registryDir = path4.join(os.homedir(), ".pie-unity", "instances");
46048
- if (!fs4.existsSync(registryDir)) {
46530
+ const registryDir = path5.join(os.homedir(), ".pie-unity", "instances");
46531
+ if (!fs5.existsSync(registryDir)) {
46049
46532
  return {
46050
46533
  id: "unity_bridge",
46051
46534
  label: "Unity bridge",
@@ -46056,12 +46539,12 @@ function checkUnityRegistry() {
46056
46539
  }
46057
46540
  try {
46058
46541
  const nowUnix = Math.floor(Date.now() / 1e3);
46059
- const files = fs4.readdirSync(registryDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path4.join(registryDir, entry.name));
46542
+ const files = fs5.readdirSync(registryDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path5.join(registryDir, entry.name));
46060
46543
  let malformedFiles = 0;
46061
46544
  let invalidFiles = 0;
46062
46545
  const liveInstances = files.map((filePath) => {
46063
46546
  try {
46064
- const parsed = JSON.parse(fs4.readFileSync(filePath, "utf8").replace(/^\uFEFF/, ""));
46547
+ const parsed = JSON.parse(fs5.readFileSync(filePath, "utf8").replace(/^\uFEFF/, ""));
46065
46548
  if (!isRecord(parsed)) {
46066
46549
  invalidFiles += 1;
46067
46550
  return null;
@@ -46135,13 +46618,13 @@ async function runDoctor() {
46135
46618
  const packageDir = getPackageDir();
46136
46619
  let dir = packageDir;
46137
46620
  let lockPath;
46138
- while (dir !== path4.dirname(dir)) {
46139
- const candidate = path4.join(dir, "package-lock.json");
46140
- if (fs4.existsSync(candidate)) {
46621
+ while (dir !== path5.dirname(dir)) {
46622
+ const candidate = path5.join(dir, "package-lock.json");
46623
+ if (fs5.existsSync(candidate)) {
46141
46624
  lockPath = candidate;
46142
46625
  break;
46143
46626
  }
46144
- dir = path4.dirname(dir);
46627
+ dir = path5.dirname(dir);
46145
46628
  }
46146
46629
  checks.push({
46147
46630
  id: "package_lock",
@@ -46163,7 +46646,7 @@ async function runDoctor() {
46163
46646
  message: result.ok ? `${dirPath} is writable.` : `${dirPath} is not writable: ${result.error}`
46164
46647
  });
46165
46648
  }
46166
- const authStorage = new AuthStorage();
46649
+ const authStorage = AuthStorage.create();
46167
46650
  const registry = new ModelRegistry(authStorage);
46168
46651
  const loadError = registry.getLoadError();
46169
46652
  const models = registry.getAll();
@@ -46304,10 +46787,10 @@ Disable or narrow capabilities:
46304
46787
  }
46305
46788
 
46306
46789
  // src/commands/models-config.ts
46307
- import * as fs5 from "node:fs";
46308
- import * as path5 from "node:path";
46790
+ import * as fs6 from "node:fs";
46791
+ import * as path6 from "node:path";
46309
46792
  function getDefaultModelsPath() {
46310
- return path5.join(getConfigDir(), "models.json");
46793
+ return path6.join(getConfigDir(), "models.json");
46311
46794
  }
46312
46795
  function createModelsTemplate() {
46313
46796
  const template = {
@@ -46402,7 +46885,7 @@ function createModelsTemplate() {
46402
46885
  `;
46403
46886
  }
46404
46887
  function resolveModelsPath(input) {
46405
- return input ? path5.resolve(input) : getDefaultModelsPath();
46888
+ return input ? path6.resolve(input) : getDefaultModelsPath();
46406
46889
  }
46407
46890
  function initModelsConfig(options = {}) {
46408
46891
  const targetPath = resolveModelsPath(options.path);
@@ -46410,16 +46893,16 @@ function initModelsConfig(options = {}) {
46410
46893
  if (options.stdout) {
46411
46894
  return { path: targetPath, content, wrote: false };
46412
46895
  }
46413
- if (fs5.existsSync(targetPath) && !options.force) {
46896
+ if (fs6.existsSync(targetPath) && !options.force) {
46414
46897
  throw new Error(`models.json already exists: ${targetPath}. Use --force to overwrite, or --stdout to print the template.`);
46415
46898
  }
46416
- fs5.mkdirSync(path5.dirname(targetPath), { recursive: true });
46417
- fs5.writeFileSync(targetPath, content, "utf8");
46899
+ fs6.mkdirSync(path6.dirname(targetPath), { recursive: true });
46900
+ fs6.writeFileSync(targetPath, content, "utf8");
46418
46901
  return { path: targetPath, content, wrote: true };
46419
46902
  }
46420
46903
  function validateModelsConfig(options = {}) {
46421
46904
  const modelsPath = resolveModelsPath(options.path);
46422
- if (!fs5.existsSync(modelsPath)) {
46905
+ if (!fs6.existsSync(modelsPath)) {
46423
46906
  return {
46424
46907
  ok: false,
46425
46908
  path: modelsPath,
@@ -46428,7 +46911,7 @@ function validateModelsConfig(options = {}) {
46428
46911
  modelCount: 0
46429
46912
  };
46430
46913
  }
46431
- const authStorage = new AuthStorage();
46914
+ const authStorage = AuthStorage.create();
46432
46915
  const registry = new ModelRegistry(authStorage, modelsPath);
46433
46916
  const loadError = registry.getLoadError();
46434
46917
  if (loadError) {
@@ -46475,7 +46958,7 @@ function validateModelsConfig(options = {}) {
46475
46958
  };
46476
46959
  }
46477
46960
  async function probeModelsConfig(options) {
46478
- const registry = new ModelRegistry(new AuthStorage(), resolveModelsPath(options.path));
46961
+ const registry = new ModelRegistry(AuthStorage.create(), resolveModelsPath(options.path));
46479
46962
  const loadError = registry.getLoadError();
46480
46963
  if (loadError) {
46481
46964
  return {
@@ -46568,7 +47051,7 @@ function parseModelsCommandArgs(args) {
46568
47051
  }
46569
47052
 
46570
47053
  // src/commands/sessions.ts
46571
- import fs6 from "node:fs";
47054
+ import fs7 from "node:fs";
46572
47055
 
46573
47056
  // src/session-control.ts
46574
47057
  var SessionControlError = class extends Error {
@@ -46713,15 +47196,15 @@ function buildForkResult(params) {
46713
47196
  }
46714
47197
  function pathToEntry(data, entryId) {
46715
47198
  const byId = new Map(data.entries.map((entry) => [entry.id, entry]));
46716
- const path31 = [];
47199
+ const path33 = [];
46717
47200
  let current = entryId;
46718
47201
  while (current) {
46719
47202
  const entry = byId.get(current);
46720
47203
  if (!entry) return [];
46721
- path31.unshift(entry);
47204
+ path33.unshift(entry);
46722
47205
  current = entry.parentId;
46723
47206
  }
46724
- return path31;
47207
+ return path33;
46725
47208
  }
46726
47209
  function cloneJson(value) {
46727
47210
  return JSON.parse(JSON.stringify(value));
@@ -46967,7 +47450,7 @@ function cwdMatches(sessionCwd, requestedCwd) {
46967
47450
  }
46968
47451
  function realpathOrInput(value) {
46969
47452
  try {
46970
- return fs6.realpathSync.native(value);
47453
+ return fs7.realpathSync.native(value);
46971
47454
  } catch {
46972
47455
  return value;
46973
47456
  }
@@ -47056,9 +47539,9 @@ function isRecord2(value) {
47056
47539
  }
47057
47540
 
47058
47541
  // src/extensions/loader.ts
47059
- import * as fs7 from "node:fs";
47542
+ import * as fs8 from "node:fs";
47060
47543
  import * as os2 from "os";
47061
- import * as path6 from "path";
47544
+ import * as path7 from "path";
47062
47545
  import { pathToFileURL } from "node:url";
47063
47546
 
47064
47547
  // src/command-tree.ts
@@ -47071,19 +47554,19 @@ var CommandTree = class {
47071
47554
  * register(["session", "new"], { description, handler })
47072
47555
  * register(["tools", "subagent", "spawn", "local"], { description, handler })
47073
47556
  */
47074
- register(path31, info) {
47075
- if (path31.length === 0) {
47557
+ register(path33, info) {
47558
+ if (path33.length === 0) {
47076
47559
  throw new Error("Command path cannot be empty");
47077
47560
  }
47078
47561
  let current = this.root;
47079
47562
  const fullPath = [];
47080
- for (let i = 0; i < path31.length; i++) {
47081
- const segment = path31[i];
47563
+ for (let i = 0; i < path33.length; i++) {
47564
+ const segment = path33[i];
47082
47565
  fullPath.push(segment);
47083
47566
  if (!current.children) {
47084
47567
  current.children = [];
47085
47568
  }
47086
- const isLast = i === path31.length - 1;
47569
+ const isLast = i === path33.length - 1;
47087
47570
  let node = current.children.find((n) => n.name === segment);
47088
47571
  if (!node) {
47089
47572
  node = {
@@ -47113,31 +47596,31 @@ var CommandTree = class {
47113
47596
  /**
47114
47597
  * Find a node by path
47115
47598
  */
47116
- find(path31) {
47117
- if (path31.length === 0) {
47599
+ find(path33) {
47600
+ if (path33.length === 0) {
47118
47601
  return this.root;
47119
47602
  }
47120
- const key = path31.join("/");
47603
+ const key = path33.join("/");
47121
47604
  return this.pathCache.get(key) || null;
47122
47605
  }
47123
47606
  /**
47124
47607
  * Get children of a path
47125
47608
  */
47126
- getChildren(path31) {
47127
- const node = this.find(path31);
47609
+ getChildren(path33) {
47610
+ const node = this.find(path33);
47128
47611
  return (node?.children || []).filter((child) => !child.hidden);
47129
47612
  }
47130
47613
  /**
47131
47614
  * Check if a path exists (as category or command)
47132
47615
  */
47133
- exists(path31) {
47134
- return this.find(path31) !== null;
47616
+ exists(path33) {
47617
+ return this.find(path33) !== null;
47135
47618
  }
47136
47619
  /**
47137
47620
  * Check if path is a category (has children)
47138
47621
  */
47139
- isCategory(path31) {
47140
- const node = this.find(path31);
47622
+ isCategory(path33) {
47623
+ const node = this.find(path33);
47141
47624
  return node?.isCategory || false;
47142
47625
  }
47143
47626
  /**
@@ -47216,8 +47699,8 @@ var CommandTree = class {
47216
47699
  * Supports dot notation: "tools.subagent.spawn.local"
47217
47700
  */
47218
47701
  registerFromPath(pathStr, info) {
47219
- const path31 = pathStr.split(/[./]/).filter(Boolean);
47220
- this.register(path31, info);
47702
+ const path33 = pathStr.split(/[./]/).filter(Boolean);
47703
+ this.register(path33, info);
47221
47704
  }
47222
47705
  };
47223
47706
  var commandTree = new CommandTree();
@@ -47331,6 +47814,33 @@ function createExtensionContext(extension, cwd, uiContext, abortFn, isIdleFn, ha
47331
47814
  setSessionMetadata(key, value) {
47332
47815
  actions?.setSessionMetadata?.(key, value);
47333
47816
  },
47817
+ appendEntry(customType, data) {
47818
+ if (!actions?.appendEntry) throw new Error("appendEntry is not available in this host mode");
47819
+ return actions.appendEntry(customType, data);
47820
+ },
47821
+ setLabel(entryId, label) {
47822
+ if (!actions?.setLabel) throw new Error("setLabel is not available in this host mode");
47823
+ return actions.setLabel(entryId, label);
47824
+ },
47825
+ setSessionName(name) {
47826
+ if (!actions?.setSessionName) throw new Error("setSessionName is not available in this host mode");
47827
+ return actions.setSessionName(name);
47828
+ },
47829
+ getSessionName() {
47830
+ return actions?.getSessionName?.();
47831
+ },
47832
+ registerProvider(name, config) {
47833
+ if (!actions?.registerProvider) throw new Error("registerProvider is not available in this host mode");
47834
+ return actions.registerProvider(name, config);
47835
+ },
47836
+ async loginProvider(name) {
47837
+ if (!actions?.loginProvider) throw new Error("loginProvider is not available in this host mode");
47838
+ return actions.loginProvider(name);
47839
+ },
47840
+ async reload() {
47841
+ if (!actions?.reload) throw new Error("reload is not available in this host mode");
47842
+ return actions.reload();
47843
+ },
47334
47844
  get yoloMode() {
47335
47845
  return actions?.yoloMode ?? false;
47336
47846
  }
@@ -47352,19 +47862,19 @@ function createExtension(extensionPath, resolvedPath, identity, source) {
47352
47862
  }
47353
47863
  function expandPath(p) {
47354
47864
  if (p.startsWith("~/")) {
47355
- return path6.join(os2.homedir(), p.slice(2));
47865
+ return path7.join(os2.homedir(), p.slice(2));
47356
47866
  }
47357
47867
  if (p.startsWith("~")) {
47358
- return path6.join(os2.homedir(), p.slice(1));
47868
+ return path7.join(os2.homedir(), p.slice(1));
47359
47869
  }
47360
47870
  return p;
47361
47871
  }
47362
47872
  function resolvePath(extPath, cwd) {
47363
47873
  const expanded = expandPath(extPath);
47364
- if (path6.isAbsolute(expanded)) {
47874
+ if (path7.isAbsolute(expanded)) {
47365
47875
  return expanded;
47366
47876
  }
47367
- return path6.resolve(cwd, expanded);
47877
+ return path7.resolve(cwd, expanded);
47368
47878
  }
47369
47879
  async function loadExtensionModule(extensionPath) {
47370
47880
  try {
@@ -47384,14 +47894,14 @@ function normalizeExtensionImportPath(extensionPath) {
47384
47894
  if (/^[a-zA-Z]:[\\/]/.test(extensionPath)) {
47385
47895
  return `file:///${extensionPath.replace(/\\/g, "/")}`;
47386
47896
  }
47387
- if (path6.isAbsolute(extensionPath)) {
47897
+ if (path7.isAbsolute(extensionPath)) {
47388
47898
  return pathToFileURL(extensionPath).href;
47389
47899
  }
47390
47900
  return extensionPath;
47391
47901
  }
47392
47902
  async function loadExtension(extensionPath, cwd, uiContext, abortFn, isIdleFn, hasUI, actions, discovered) {
47393
47903
  const resolvedPath = resolvePath(extensionPath, cwd);
47394
- if (!fs7.existsSync(resolvedPath)) {
47904
+ if (!fs8.existsSync(resolvedPath)) {
47395
47905
  return { extension: null, error: `Extension not found: ${extensionPath}` };
47396
47906
  }
47397
47907
  try {
@@ -47412,17 +47922,17 @@ async function loadExtension(extensionPath, cwd, uiContext, abortFn, isIdleFn, h
47412
47922
  }
47413
47923
  }
47414
47924
  function discoverExtensionsInDir(dir, source) {
47415
- if (!fs7.existsSync(dir)) {
47925
+ if (!fs8.existsSync(dir)) {
47416
47926
  return [];
47417
47927
  }
47418
47928
  const discovered = [];
47419
47929
  try {
47420
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
47930
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
47421
47931
  for (const entry of entries) {
47422
47932
  if (entry.name.endsWith(".disabled")) {
47423
47933
  continue;
47424
47934
  }
47425
- const entryPath = path6.join(dir, entry.name);
47935
+ const entryPath = path7.join(dir, entry.name);
47426
47936
  if (entry.isFile() && (entry.name.endsWith(".cjs") || entry.name.endsWith(".js") || entry.name.endsWith(".ts"))) {
47427
47937
  discovered.push({
47428
47938
  entryPath,
@@ -47432,13 +47942,13 @@ function discoverExtensionsInDir(dir, source) {
47432
47942
  continue;
47433
47943
  }
47434
47944
  if (entry.isDirectory()) {
47435
- const packageJsonPath = path6.join(entryPath, "package.json");
47945
+ const packageJsonPath = path7.join(entryPath, "package.json");
47436
47946
  let packageIdentity;
47437
47947
  let packageDescription;
47438
47948
  let packageMainPath;
47439
- if (fs7.existsSync(packageJsonPath)) {
47949
+ if (fs8.existsSync(packageJsonPath)) {
47440
47950
  try {
47441
- const pkg = JSON.parse(fs7.readFileSync(packageJsonPath, "utf-8"));
47951
+ const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
47442
47952
  const manifest = pkg.pie && typeof pkg.pie === "object" && !Array.isArray(pkg.pie) ? pkg.pie : void 0;
47443
47953
  packageIdentity = resolveExtensionIdentity({
47444
47954
  pieName: typeof manifest?.name === "string" ? manifest.name : void 0,
@@ -47447,8 +47957,8 @@ function discoverExtensionsInDir(dir, source) {
47447
47957
  });
47448
47958
  packageDescription = typeof pkg.description === "string" ? pkg.description : void 0;
47449
47959
  if (typeof manifest?.main === "string" && manifest.main.trim().length > 0) {
47450
- const candidate = path6.resolve(entryPath, manifest.main);
47451
- if (fs7.existsSync(candidate)) {
47960
+ const candidate = path7.resolve(entryPath, manifest.main);
47961
+ if (fs8.existsSync(candidate)) {
47452
47962
  packageMainPath = candidate;
47453
47963
  }
47454
47964
  }
@@ -47464,8 +47974,8 @@ function discoverExtensionsInDir(dir, source) {
47464
47974
  });
47465
47975
  continue;
47466
47976
  }
47467
- const indexCjs = path6.join(entryPath, "index.cjs");
47468
- if (fs7.existsSync(indexCjs)) {
47977
+ const indexCjs = path7.join(entryPath, "index.cjs");
47978
+ if (fs8.existsSync(indexCjs)) {
47469
47979
  discovered.push({
47470
47980
  entryPath: indexCjs,
47471
47981
  source,
@@ -47474,8 +47984,8 @@ function discoverExtensionsInDir(dir, source) {
47474
47984
  });
47475
47985
  continue;
47476
47986
  }
47477
- const indexJs = path6.join(entryPath, "index.js");
47478
- if (fs7.existsSync(indexJs)) {
47987
+ const indexJs = path7.join(entryPath, "index.js");
47988
+ if (fs8.existsSync(indexJs)) {
47479
47989
  discovered.push({
47480
47990
  entryPath: indexJs,
47481
47991
  source,
@@ -47484,8 +47994,8 @@ function discoverExtensionsInDir(dir, source) {
47484
47994
  });
47485
47995
  continue;
47486
47996
  }
47487
- const indexTs = path6.join(entryPath, "index.ts");
47488
- if (fs7.existsSync(indexTs)) {
47997
+ const indexTs = path7.join(entryPath, "index.ts");
47998
+ if (fs8.existsSync(indexTs)) {
47489
47999
  discovered.push({
47490
48000
  entryPath: indexTs,
47491
48001
  source,
@@ -47506,21 +48016,21 @@ function collectDiscoveredExtensions(configuredPaths, cwd, options) {
47506
48016
  const errors = [];
47507
48017
  if (!options?.noExtensions) {
47508
48018
  if (!options?.noBuiltinExtensions) {
47509
- const builtinExtDir = path6.join(getBuiltinDir(), "extensions");
48019
+ const builtinExtDir = path7.join(getBuiltinDir(), "extensions");
47510
48020
  discovered.push(...discoverExtensionsInDir(builtinExtDir, "builtin"));
47511
48021
  }
47512
- const globalExtDir = path6.join(os2.homedir(), ".pie", "extensions");
48022
+ const globalExtDir = path7.join(os2.homedir(), ".pie", "extensions");
47513
48023
  discovered.push(...discoverExtensionsInDir(globalExtDir, "global"));
47514
- const localExtDir = path6.join(cwd, ".pie", "extensions");
48024
+ const localExtDir = path7.join(cwd, ".pie", "extensions");
47515
48025
  discovered.push(...discoverExtensionsInDir(localExtDir, "project"));
47516
48026
  }
47517
48027
  for (const p of configuredPaths) {
47518
48028
  const resolved = resolvePath(p, cwd);
47519
- if (!fs7.existsSync(resolved)) {
48029
+ if (!fs8.existsSync(resolved)) {
47520
48030
  errors.push({ path: p, error: `Extension path not found: ${p}` });
47521
48031
  continue;
47522
48032
  }
47523
- if (!fs7.statSync(resolved).isDirectory()) {
48033
+ if (!fs8.statSync(resolved).isDirectory()) {
47524
48034
  errors.push({ path: p, error: `Extension path must be a directory: ${p}` });
47525
48035
  continue;
47526
48036
  }
@@ -47611,6 +48121,7 @@ function convertExtensionTools(extensions, customExecutors) {
47611
48121
  label: tool.label ?? name,
47612
48122
  description: tool.description,
47613
48123
  parameters: tool.parameters,
48124
+ renderHint: tool.renderHint,
47614
48125
  execute: async (toolCallId, args, signal, onUpdate) => {
47615
48126
  debugLog("ext-tool", `${name} EXECUTE START`, {
47616
48127
  toolCallId: toolCallId.slice(0, 8),
@@ -47801,6 +48312,9 @@ Enablement overrides are stored sparsely; absent entries default to enabled.
47801
48312
  Project scope writes ./.pie/settings.json and overrides user scope.`);
47802
48313
  }
47803
48314
 
48315
+ // src/interactive-mode.ts
48316
+ import * as os8 from "os";
48317
+
47804
48318
  // ../../packages/tui/src/fuzzy.ts
47805
48319
  function fuzzyMatch(query, text) {
47806
48320
  const queryLower = query.toLowerCase();
@@ -49351,9 +49865,9 @@ var KillRing = class {
49351
49865
  };
49352
49866
 
49353
49867
  // ../../packages/tui/src/tui.ts
49354
- import * as fs8 from "node:fs";
49868
+ import * as fs9 from "node:fs";
49355
49869
  import * as os3 from "node:os";
49356
- import * as path7 from "node:path";
49870
+ import * as path8 from "node:path";
49357
49871
 
49358
49872
  // ../../packages/tui/src/terminal-image.ts
49359
49873
  var cachedCapabilities = null;
@@ -50029,11 +50543,11 @@ var TUI = class extends Container {
50029
50543
  const debugRedraw = process.env.PIE_DEBUG_REDRAW === "1";
50030
50544
  const logRedraw = (reason) => {
50031
50545
  if (!debugRedraw) return;
50032
- const logPath = path7.join(os3.homedir(), ".pie", "logs", "pie-cli.log");
50033
- fs8.mkdirSync(path7.dirname(logPath), { recursive: true });
50546
+ const logPath = path8.join(os3.homedir(), ".pie", "logs", "pie-cli.log");
50547
+ fs9.mkdirSync(path8.dirname(logPath), { recursive: true });
50034
50548
  const msg = `[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] [ui] fullRender: ${reason} (prev=${this.previousLines.length}, new=${newLines.length}, height=${height})
50035
50549
  `;
50036
- fs8.appendFileSync(logPath, msg);
50550
+ fs9.appendFileSync(logPath, msg);
50037
50551
  };
50038
50552
  if (this.previousLines.length === 0 && !widthChanged) {
50039
50553
  logRedraw("first render");
@@ -50126,7 +50640,7 @@ var TUI = class extends Container {
50126
50640
  const line = newLines[i];
50127
50641
  const isImage = isImageLine(line);
50128
50642
  if (!isImage && visibleWidth(line) > width) {
50129
- const crashLogPath = path7.join(os3.homedir(), ".pie", "pie-crash.log");
50643
+ const crashLogPath = path8.join(os3.homedir(), ".pie", "pie-crash.log");
50130
50644
  const crashData = [
50131
50645
  `Crash at ${(/* @__PURE__ */ new Date()).toISOString()}`,
50132
50646
  `Terminal width: ${width}`,
@@ -50136,8 +50650,8 @@ var TUI = class extends Container {
50136
50650
  ...newLines.map((l, idx) => `[${idx}] (w=${visibleWidth(l)}) ${l}`),
50137
50651
  ""
50138
50652
  ].join("\n");
50139
- fs8.mkdirSync(path7.dirname(crashLogPath), { recursive: true });
50140
- fs8.writeFileSync(crashLogPath, crashData);
50653
+ fs9.mkdirSync(path8.dirname(crashLogPath), { recursive: true });
50654
+ fs9.writeFileSync(crashLogPath, crashData);
50141
50655
  this.stop();
50142
50656
  const errorMsg = [
50143
50657
  `Rendered line ${i} exceeds terminal width (${visibleWidth(line)} > ${width}).`,
@@ -50167,8 +50681,8 @@ var TUI = class extends Container {
50167
50681
  buffer += "\x1B[?2026l";
50168
50682
  if (process.env.PIE_TUI_DEBUG === "1") {
50169
50683
  const debugDir = "/tmp/tui";
50170
- fs8.mkdirSync(debugDir, { recursive: true });
50171
- const debugPath = path7.join(debugDir, `render-${Date.now()}-${Math.random().toString(36).slice(2)}.log`);
50684
+ fs9.mkdirSync(debugDir, { recursive: true });
50685
+ const debugPath = path8.join(debugDir, `render-${Date.now()}-${Math.random().toString(36).slice(2)}.log`);
50172
50686
  const debugData = [
50173
50687
  `firstChanged: ${firstChanged}`,
50174
50688
  `viewportTop: ${viewportTop}`,
@@ -50191,7 +50705,7 @@ var TUI = class extends Container {
50191
50705
  "=== buffer ===",
50192
50706
  JSON.stringify(buffer)
50193
50707
  ].join("\n");
50194
- fs8.writeFileSync(debugPath, debugData);
50708
+ fs9.writeFileSync(debugPath, debugData);
50195
50709
  }
50196
50710
  this.terminal.write(buffer);
50197
50711
  this.cursorRow = Math.max(0, newLines.length - 1);
@@ -54382,7 +54896,7 @@ var StdinBuffer = class extends EventEmitter {
54382
54896
  };
54383
54897
 
54384
54898
  // ../../packages/tui/src/terminal.ts
54385
- import * as fs9 from "node:fs";
54899
+ import * as fs10 from "node:fs";
54386
54900
  import koffi from "koffi";
54387
54901
  var ProcessTerminal = class {
54388
54902
  wasRaw = false;
@@ -54507,7 +55021,7 @@ var ProcessTerminal = class {
54507
55021
  const timeLeft = endTime - now;
54508
55022
  if (timeLeft <= 0) break;
54509
55023
  if (now - lastDataTime >= idleMs) break;
54510
- await new Promise((resolve6) => setTimeout(resolve6, Math.min(idleMs, timeLeft)));
55024
+ await new Promise((resolve7) => setTimeout(resolve7, Math.min(idleMs, timeLeft)));
54511
55025
  }
54512
55026
  } finally {
54513
55027
  process.stdin.removeListener("data", onData);
@@ -54543,7 +55057,7 @@ var ProcessTerminal = class {
54543
55057
  process.stdout.write(data);
54544
55058
  if (this.writeLogPath) {
54545
55059
  try {
54546
- fs9.appendFileSync(this.writeLogPath, data, { encoding: "utf8" });
55060
+ fs10.appendFileSync(this.writeLogPath, data, { encoding: "utf8" });
54547
55061
  } catch {
54548
55062
  }
54549
55063
  }
@@ -54617,6 +55131,8 @@ var ExtensionRunner = class {
54617
55131
  actions;
54618
55132
  contextActions;
54619
55133
  hasUI;
55134
+ /** Unregister handles created through this runner, revoked on reload/teardown. */
55135
+ unregisterHandles = [];
54620
55136
  constructor(extensions, actions, contextActions, hasUI = true) {
54621
55137
  this.extensions = extensions;
54622
55138
  this.actions = actions;
@@ -54648,6 +55164,39 @@ var ExtensionRunner = class {
54648
55164
  this.uiContext = uiContext;
54649
55165
  this.hasUI = true;
54650
55166
  }
55167
+ /**
55168
+ * Wrap an unregister handle so this runner can revoke it during
55169
+ * reload/teardown even if the extension drops the handle it was returned.
55170
+ * The wrapper is idempotent.
55171
+ */
55172
+ trackUnregisterHandle(unregister) {
55173
+ const entry = { invoked: false, unregister };
55174
+ this.unregisterHandles.push(entry);
55175
+ return () => {
55176
+ if (entry.invoked) return;
55177
+ entry.invoked = true;
55178
+ unregister();
55179
+ const index = this.unregisterHandles.indexOf(entry);
55180
+ if (index >= 0) this.unregisterHandles.splice(index, 1);
55181
+ };
55182
+ }
55183
+ /**
55184
+ * Revoke every provider registration made through this runner (runtime
55185
+ * reload / teardown). Old extension instances must not leave providers,
55186
+ * OAuth flows, or ambient state registered behind them.
55187
+ */
55188
+ revokeRegistrations() {
55189
+ const pending = this.unregisterHandles.splice(0);
55190
+ for (const entry of pending) {
55191
+ if (entry.invoked) continue;
55192
+ entry.invoked = true;
55193
+ try {
55194
+ entry.unregister();
55195
+ } catch (error) {
55196
+ console.warn("[Extension] unregister provider failed during reload:", error);
55197
+ }
55198
+ }
55199
+ }
54651
55200
  /**
54652
55201
  * Get all registered tools from all extensions.
54653
55202
  */
@@ -54738,10 +55287,35 @@ var ExtensionRunner = class {
54738
55287
  },
54739
55288
  getAllTools: () => runner.actions.getAllTools(),
54740
55289
  getRuntimeTools: () => runner.actions.getRuntimeTools(),
54741
- resolveModelClass: (modelClass) => runner.actions.resolveModelClass?.(modelClass),
55290
+ resolveModelClass: (modelClass) => runner.actions.resolveModelClass?.(modelClass) ?? {},
54742
55291
  getMessages: () => runner.actions.getMessages(),
54743
55292
  getSessionMetadata: () => runner.actions.getSessionMetadata(),
54744
55293
  setSessionMetadata: (key, value) => runner.actions.setSessionMetadata(key, value),
55294
+ appendEntry: (customType, data) => {
55295
+ if (!runner.actions.appendEntry) throw new Error("appendEntry is not available in this host mode");
55296
+ return runner.actions.appendEntry(customType, data);
55297
+ },
55298
+ setLabel: (entryId, label) => {
55299
+ if (!runner.actions.setLabel) throw new Error("setLabel is not available in this host mode");
55300
+ return runner.actions.setLabel(entryId, label);
55301
+ },
55302
+ setSessionName: (name) => {
55303
+ if (!runner.actions.setSessionName) throw new Error("setSessionName is not available in this host mode");
55304
+ return runner.actions.setSessionName(name);
55305
+ },
55306
+ getSessionName: () => runner.actions.getSessionName?.(),
55307
+ registerProvider: (name, config) => {
55308
+ if (!runner.actions.registerProvider) throw new Error("registerProvider is not available in this host mode");
55309
+ return runner.trackUnregisterHandle(runner.actions.registerProvider(name, config));
55310
+ },
55311
+ loginProvider: async (name) => {
55312
+ if (!runner.actions.loginProvider) throw new Error("loginProvider is not available in this host mode");
55313
+ return runner.actions.loginProvider(name);
55314
+ },
55315
+ reload: async () => {
55316
+ if (!runner.actions.reload) throw new Error("reload is not available in this host mode");
55317
+ return runner.actions.reload();
55318
+ },
54745
55319
  get yoloMode() {
54746
55320
  return runner.actions.yoloMode ?? false;
54747
55321
  }
@@ -54821,6 +55395,81 @@ var ExtensionRunner = class {
54821
55395
  isError: currentEvent.isError ?? event.isError
54822
55396
  };
54823
55397
  }
55398
+ /**
55399
+ * Emit session:before_switch. The first cancel wins; default allows.
55400
+ */
55401
+ async emitSessionBeforeSwitch(event) {
55402
+ const ctx = this.createContext();
55403
+ for (const ext of this.extensions) {
55404
+ const handlers = ext.handlers.get("session:before_switch");
55405
+ if (!handlers || handlers.length === 0) continue;
55406
+ for (const handler of handlers) {
55407
+ try {
55408
+ const result = await handler(event, ctx);
55409
+ if (result && typeof result === "object" && result.cancel === true) return { cancel: true };
55410
+ } catch (err) {
55411
+ if (process.env.PIE_VERBOSE) {
55412
+ console.warn(`[Runner] session:before_switch handler failed in ${ext.path}:`, err);
55413
+ }
55414
+ }
55415
+ }
55416
+ }
55417
+ return {};
55418
+ }
55419
+ /**
55420
+ * Emit session:before_compact. The first cancel wins; the first custom
55421
+ * summary wins; default runs the built-in summarizer.
55422
+ */
55423
+ async emitSessionBeforeCompact(event) {
55424
+ const ctx = this.createContext();
55425
+ for (const ext of this.extensions) {
55426
+ const handlers = ext.handlers.get("session:before_compact");
55427
+ if (!handlers || handlers.length === 0) continue;
55428
+ for (const handler of handlers) {
55429
+ try {
55430
+ const result = await handler(event, ctx);
55431
+ if (!result || typeof result !== "object") continue;
55432
+ if (result.cancel === true) return { cancel: true };
55433
+ if (typeof result.summary === "string" && result.summary.trim()) return { summary: result.summary };
55434
+ } catch (err) {
55435
+ if (process.env.PIE_VERBOSE) {
55436
+ console.warn(`[Runner] session:before_compact handler failed in ${ext.path}:`, err);
55437
+ }
55438
+ }
55439
+ }
55440
+ }
55441
+ return {};
55442
+ }
55443
+ /**
55444
+ * Emit terminal:input to all extensions. Transforms chain through handlers
55445
+ * in registration order (each handler sees the previous transform); a
55446
+ * consume result stops the chain and wins. Default: continue unchanged.
55447
+ */
55448
+ async emitTerminalInput(event) {
55449
+ const ctx = this.createContext();
55450
+ let current = { action: "continue" };
55451
+ let text = event.text;
55452
+ for (const ext of this.extensions) {
55453
+ const handlers = ext.handlers.get("terminal:input");
55454
+ if (!handlers || handlers.length === 0) continue;
55455
+ for (const handler of handlers) {
55456
+ try {
55457
+ const result = await handler({ ...event, text }, ctx);
55458
+ if (!result || typeof result !== "object" || !("action" in result)) continue;
55459
+ if (result.action === "consume") return { action: "consume" };
55460
+ if (result.action === "transform" && typeof result.text === "string") {
55461
+ text = result.text;
55462
+ current = { action: "transform", text };
55463
+ }
55464
+ } catch (err) {
55465
+ if (process.env.PIE_VERBOSE) {
55466
+ console.warn(`[Runner] terminal:input handler failed in ${ext.path}:`, err);
55467
+ }
55468
+ }
55469
+ }
55470
+ }
55471
+ return current;
55472
+ }
54824
55473
  /**
54825
55474
  * Emit a generic event to all extensions.
54826
55475
  */
@@ -54958,8 +55607,8 @@ function wrapToolsWithExtensions(tools, runner) {
54958
55607
  }
54959
55608
 
54960
55609
  // src/keybindings.ts
54961
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
54962
- import { join as join6 } from "path";
55610
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
55611
+ import { join as join7 } from "path";
54963
55612
  var DEFAULT_APP_KEYBINDINGS = {
54964
55613
  interrupt: "escape",
54965
55614
  clear: "ctrl+c",
@@ -55023,7 +55672,7 @@ var KeybindingsManager = class _KeybindingsManager {
55023
55672
  * Create from config file and set up editor keybindings.
55024
55673
  */
55025
55674
  static create(agentDir = getAgentDir()) {
55026
- const configPath = join6(agentDir, "keybindings.json");
55675
+ const configPath = join7(agentDir, "keybindings.json");
55027
55676
  const config = _KeybindingsManager.loadFromFile(configPath);
55028
55677
  const manager = new _KeybindingsManager(config);
55029
55678
  const editorConfig = {};
@@ -55041,10 +55690,10 @@ var KeybindingsManager = class _KeybindingsManager {
55041
55690
  static inMemory(config = {}) {
55042
55691
  return new _KeybindingsManager(config);
55043
55692
  }
55044
- static loadFromFile(path31) {
55045
- if (!existsSync7(path31)) return {};
55693
+ static loadFromFile(path33) {
55694
+ if (!existsSync8(path33)) return {};
55046
55695
  try {
55047
- return JSON.parse(readFileSync6(path31, "utf-8"));
55696
+ return JSON.parse(readFileSync7(path33, "utf-8"));
55048
55697
  } catch {
55049
55698
  return {};
55050
55699
  }
@@ -55093,19 +55742,19 @@ var KeybindingsManager = class _KeybindingsManager {
55093
55742
  };
55094
55743
 
55095
55744
  // src/project-knowledge.ts
55096
- import fs10 from "node:fs";
55097
- import path8 from "node:path";
55745
+ import fs11 from "node:fs";
55746
+ import path9 from "node:path";
55098
55747
  function collectAncestorAgentsFiles(cwd) {
55099
- const resolvedCwd = path8.resolve(cwd || process.cwd());
55748
+ const resolvedCwd = path9.resolve(cwd || process.cwd());
55100
55749
  const projectRoot = resolveCliProjectRoot(resolvedCwd);
55101
55750
  const candidates = [];
55102
55751
  let current = resolvedCwd;
55103
55752
  while (true) {
55104
- candidates.unshift(path8.join(current, AGENTS_CONTEXT_FILE_NAME));
55753
+ candidates.unshift(path9.join(current, AGENTS_CONTEXT_FILE_NAME));
55105
55754
  if (current === projectRoot) {
55106
55755
  break;
55107
55756
  }
55108
- const parent = path8.dirname(current);
55757
+ const parent = path9.dirname(current);
55109
55758
  if (parent === current) {
55110
55759
  break;
55111
55760
  }
@@ -55113,13 +55762,13 @@ function collectAncestorAgentsFiles(cwd) {
55113
55762
  }
55114
55763
  const files = [];
55115
55764
  for (const candidate of candidates) {
55116
- if (!fs10.existsSync(candidate)) {
55765
+ if (!fs11.existsSync(candidate)) {
55117
55766
  continue;
55118
55767
  }
55119
55768
  try {
55120
55769
  files.push({
55121
- path: path8.relative(resolvedCwd, candidate) || AGENTS_CONTEXT_FILE_NAME,
55122
- content: fs10.readFileSync(candidate, "utf-8")
55770
+ path: path9.relative(resolvedCwd, candidate) || AGENTS_CONTEXT_FILE_NAME,
55771
+ content: fs11.readFileSync(candidate, "utf-8")
55123
55772
  });
55124
55773
  } catch {
55125
55774
  }
@@ -55131,14 +55780,14 @@ function buildCliProjectContextSection(cwd) {
55131
55780
  }
55132
55781
 
55133
55782
  // src/file-access-context.ts
55134
- import * as path9 from "node:path";
55783
+ import * as path10 from "node:path";
55135
55784
  function uniqueNormalizedPaths(paths) {
55136
55785
  const seen = /* @__PURE__ */ new Set();
55137
55786
  const result = [];
55138
55787
  for (const entry of paths) {
55139
55788
  const trimmed = String(entry || "").trim();
55140
55789
  if (!trimmed) continue;
55141
- const normalized = path9.normalize(trimmed);
55790
+ const normalized = path10.normalize(trimmed);
55142
55791
  if (seen.has(normalized)) continue;
55143
55792
  seen.add(normalized);
55144
55793
  result.push(normalized);
@@ -55157,8 +55806,8 @@ function buildCliFileAccessOptions(params) {
55157
55806
  }
55158
55807
 
55159
55808
  // src/theme/theme.ts
55160
- import * as fs11 from "node:fs";
55161
- import * as path10 from "node:path";
55809
+ import * as fs12 from "node:fs";
55810
+ import * as path11 from "node:path";
55162
55811
 
55163
55812
  // ../../node_modules/@sinclair/typebox/build/esm/compiler/compiler.mjs
55164
55813
  var TypeCheck = class {
@@ -56045,11 +56694,11 @@ var BUILTIN_THEMES;
56045
56694
  function getBuiltinThemes() {
56046
56695
  if (!BUILTIN_THEMES) {
56047
56696
  const themesDir = getThemesDir();
56048
- const darkPath = path10.join(themesDir, "dark.json");
56049
- const lightPath = path10.join(themesDir, "light.json");
56697
+ const darkPath = path11.join(themesDir, "dark.json");
56698
+ const lightPath = path11.join(themesDir, "light.json");
56050
56699
  BUILTIN_THEMES = {
56051
- dark: JSON.parse(fs11.readFileSync(darkPath, "utf-8")),
56052
- light: JSON.parse(fs11.readFileSync(lightPath, "utf-8"))
56700
+ dark: JSON.parse(fs12.readFileSync(darkPath, "utf-8")),
56701
+ light: JSON.parse(fs12.readFileSync(lightPath, "utf-8"))
56053
56702
  };
56054
56703
  }
56055
56704
  return BUILTIN_THEMES;
@@ -56057,8 +56706,8 @@ function getBuiltinThemes() {
56057
56706
  function getAvailableThemes() {
56058
56707
  const themes = new Set(Object.keys(getBuiltinThemes()));
56059
56708
  const customThemesDir = getCustomThemesDir();
56060
- if (fs11.existsSync(customThemesDir)) {
56061
- const files = fs11.readdirSync(customThemesDir);
56709
+ if (fs12.existsSync(customThemesDir)) {
56710
+ const files = fs12.readdirSync(customThemesDir);
56062
56711
  for (const file of files) {
56063
56712
  if (file.endsWith(".json")) {
56064
56713
  themes.add(file.slice(0, -5));
@@ -56119,18 +56768,18 @@ function loadThemeJson(name) {
56119
56768
  }
56120
56769
  const registeredTheme = registeredThemes.get(name);
56121
56770
  if (registeredTheme?.sourcePath) {
56122
- const content2 = fs11.readFileSync(registeredTheme.sourcePath, "utf-8");
56771
+ const content2 = fs12.readFileSync(registeredTheme.sourcePath, "utf-8");
56123
56772
  return parseThemeJsonContent(registeredTheme.sourcePath, content2);
56124
56773
  }
56125
56774
  if (registeredTheme) {
56126
56775
  throw new Error(`Theme "${name}" does not have a source path for export`);
56127
56776
  }
56128
56777
  const customThemesDir = getCustomThemesDir();
56129
- const themePath = path10.join(customThemesDir, `${name}.json`);
56130
- if (!fs11.existsSync(themePath)) {
56778
+ const themePath = path11.join(customThemesDir, `${name}.json`);
56779
+ if (!fs12.existsSync(themePath)) {
56131
56780
  throw new Error(`Theme not found: ${name}`);
56132
56781
  }
56133
- const content = fs11.readFileSync(themePath, "utf-8");
56782
+ const content = fs12.readFileSync(themePath, "utf-8");
56134
56783
  return parseThemeJsonContent(name, content);
56135
56784
  }
56136
56785
  function createTheme(themeJson, mode, sourcePath) {
@@ -56243,12 +56892,12 @@ function startThemeWatcher() {
56243
56892
  return;
56244
56893
  }
56245
56894
  const customThemesDir = getCustomThemesDir();
56246
- const themeFile = path10.join(customThemesDir, `${currentThemeName}.json`);
56247
- if (!fs11.existsSync(themeFile)) {
56895
+ const themeFile = path11.join(customThemesDir, `${currentThemeName}.json`);
56896
+ if (!fs12.existsSync(themeFile)) {
56248
56897
  return;
56249
56898
  }
56250
56899
  try {
56251
- themeWatcher = fs11.watch(themeFile, (eventType) => {
56900
+ themeWatcher = fs12.watch(themeFile, (eventType) => {
56252
56901
  if (eventType === "change") {
56253
56902
  setTimeout(() => {
56254
56903
  try {
@@ -56261,7 +56910,7 @@ function startThemeWatcher() {
56261
56910
  }, 100);
56262
56911
  } else if (eventType === "rename") {
56263
56912
  setTimeout(() => {
56264
- if (!fs11.existsSync(themeFile)) {
56913
+ if (!fs12.existsSync(themeFile)) {
56265
56914
  currentThemeName = "dark";
56266
56915
  setGlobalTheme(loadTheme("dark"));
56267
56916
  if (themeWatcher) {
@@ -56441,8 +57090,9 @@ function getSettingsListTheme() {
56441
57090
  }
56442
57091
 
56443
57092
  // src/skills/loader.ts
56444
- import * as fs12 from "fs";
56445
- import * as path11 from "path";
57093
+ import * as fs13 from "fs";
57094
+ import * as os4 from "os";
57095
+ import * as path12 from "path";
56446
57096
  function parseSkillDescription(content) {
56447
57097
  const frontmatterMatch = content.match(/^---\n[\s\S]*?description:\s*["']?(.+?)["']?(?:\n|$)/m);
56448
57098
  if (frontmatterMatch) return frontmatterMatch[1].trim();
@@ -56457,21 +57107,53 @@ function parseSkillDescription(content) {
56457
57107
  }
56458
57108
  return "No description";
56459
57109
  }
57110
+ var USER_CONTROLLED_SOURCES = /* @__PURE__ */ new Set(["user-agents", "user"]);
57111
+ function pathIsInside(child, parent) {
57112
+ const relative3 = path12.relative(parent, child);
57113
+ return relative3 !== "" && !relative3.startsWith(`..${path12.sep}`) && relative3 !== ".." && !path12.isAbsolute(relative3);
57114
+ }
57115
+ function resolveSkillSymlink(fullPath, dir, source) {
57116
+ let target;
57117
+ let stats;
57118
+ try {
57119
+ target = fs13.realpathSync(fullPath);
57120
+ stats = fs13.statSync(target);
57121
+ } catch {
57122
+ return { error: `broken skill symlink: ${fullPath}` };
57123
+ }
57124
+ if (!USER_CONTROLLED_SOURCES.has(source) && !pathIsInside(target, fs13.realpathSync(dir))) {
57125
+ return { error: `skill symlink escapes the "${source}" layer (not loaded): ${fullPath} -> ${target}` };
57126
+ }
57127
+ return { target, isDirectory: stats.isDirectory(), isFile: stats.isFile() };
57128
+ }
56460
57129
  function loadSkillsFromDir(dir, source) {
56461
57130
  const skills = [];
56462
57131
  const diagnostics = [];
56463
- if (!fs12.existsSync(dir)) {
57132
+ if (!fs13.existsSync(dir)) {
56464
57133
  return { skills, diagnostics };
56465
57134
  }
56466
- const entries = fs12.readdirSync(dir, { withFileTypes: true });
57135
+ const entries = fs13.readdirSync(dir, { withFileTypes: true });
56467
57136
  for (const entry of entries) {
56468
57137
  if (entry.name.startsWith(".")) continue;
56469
- const fullPath = path11.join(dir, entry.name);
56470
- if (entry.isDirectory()) {
56471
- const skillMdPath = path11.join(fullPath, "SKILL.md");
56472
- if (fs12.existsSync(skillMdPath)) {
57138
+ const fullPath = path12.join(dir, entry.name);
57139
+ let entryPath = fullPath;
57140
+ let entryIsDirectory = entry.isDirectory();
57141
+ let entryIsFile = entry.isFile();
57142
+ if (entry.isSymbolicLink()) {
57143
+ const resolved = resolveSkillSymlink(fullPath, dir, source);
57144
+ if ("error" in resolved) {
57145
+ diagnostics.push({ type: "warning", message: resolved.error, path: fullPath });
57146
+ continue;
57147
+ }
57148
+ entryPath = resolved.target;
57149
+ entryIsDirectory = resolved.isDirectory;
57150
+ entryIsFile = resolved.isFile;
57151
+ }
57152
+ if (entryIsDirectory) {
57153
+ const skillMdPath = path12.join(entryPath, "SKILL.md");
57154
+ if (fs13.existsSync(skillMdPath)) {
56473
57155
  try {
56474
- const content = fs12.readFileSync(skillMdPath, "utf-8");
57156
+ const content = fs13.readFileSync(skillMdPath, "utf-8");
56475
57157
  const description = parseSkillDescription(content);
56476
57158
  skills.push({
56477
57159
  name: entry.name,
@@ -56479,7 +57161,7 @@ function loadSkillsFromDir(dir, source) {
56479
57161
  content,
56480
57162
  filePath: skillMdPath,
56481
57163
  source,
56482
- baseDir: fullPath
57164
+ baseDir: entryPath
56483
57165
  });
56484
57166
  } catch (e) {
56485
57167
  diagnostics.push({
@@ -56489,64 +57171,72 @@ function loadSkillsFromDir(dir, source) {
56489
57171
  });
56490
57172
  }
56491
57173
  }
56492
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
57174
+ } else if (entryIsFile && entry.name.endsWith(".md")) {
56493
57175
  const name = entry.name.slice(0, -3);
56494
57176
  try {
56495
- const content = fs12.readFileSync(fullPath, "utf-8");
57177
+ const content = fs13.readFileSync(entryPath, "utf-8");
56496
57178
  const description = parseSkillDescription(content);
56497
57179
  skills.push({
56498
57180
  name,
56499
57181
  description,
56500
57182
  content,
56501
- filePath: fullPath,
57183
+ filePath: entryPath,
56502
57184
  source,
56503
- baseDir: dir
57185
+ baseDir: path12.dirname(entryPath)
56504
57186
  });
56505
57187
  } catch (e) {
56506
57188
  diagnostics.push({
56507
57189
  type: "warning",
56508
57190
  message: `Failed to load skill: ${name}`,
56509
- path: fullPath
57191
+ path: entryPath
56510
57192
  });
56511
57193
  }
56512
57194
  }
56513
57195
  }
56514
57196
  return { skills, diagnostics };
56515
57197
  }
57198
+ function discoverAgentsSkillsDirs(cwd) {
57199
+ const resolvedCwd = path12.resolve(cwd || process.cwd());
57200
+ const projectRoot = resolveCliProjectRoot(resolvedCwd);
57201
+ const projectAgentsDirs = [];
57202
+ let current = projectRoot;
57203
+ while (true) {
57204
+ projectAgentsDirs.push(path12.join(current, ".agents", "skills"));
57205
+ if (current === resolvedCwd) break;
57206
+ const child = path12.relative(current, resolvedCwd).split(path12.sep)[0];
57207
+ if (!child) break;
57208
+ current = path12.join(current, child);
57209
+ }
57210
+ return { userAgentsDir: path12.join(os4.homedir(), ".agents", "skills"), projectAgentsDirs };
57211
+ }
57212
+ function buildSkillDirs(options) {
57213
+ const { userAgentsDir, projectAgentsDirs } = discoverAgentsSkillsDirs(options.cwd);
57214
+ return [
57215
+ { dir: options.builtinDir, source: "builtin" },
57216
+ { dir: userAgentsDir, source: "user-agents" },
57217
+ { dir: options.userDir, source: "user" },
57218
+ ...projectAgentsDirs.map((dir) => ({ dir, source: "project-agents" })),
57219
+ { dir: options.projectDir, source: "project" }
57220
+ ];
57221
+ }
56516
57222
  function loadSkills(options) {
56517
- const { builtinDir, userDir, projectDir } = options;
56518
57223
  const skillMap = /* @__PURE__ */ new Map();
56519
57224
  const diagnostics = [];
56520
- const builtinResult = loadSkillsFromDir(builtinDir, "builtin");
56521
- for (const skill of builtinResult.skills) {
56522
- skillMap.set(skill.name, skill);
56523
- }
56524
- diagnostics.push(...builtinResult.diagnostics);
56525
- const userResult = loadSkillsFromDir(userDir, "user");
56526
- for (const skill of userResult.skills) {
56527
- if (skillMap.has(skill.name)) {
56528
- diagnostics.push({
56529
- type: "collision",
56530
- message: `Skill "${skill.name}" collision: user skill overrides builtin`,
56531
- path: skill.filePath
56532
- });
56533
- }
56534
- skillMap.set(skill.name, skill);
56535
- }
56536
- diagnostics.push(...userResult.diagnostics);
56537
- const projectResult = loadSkillsFromDir(projectDir, "project");
56538
- for (const skill of projectResult.skills) {
56539
- if (skillMap.has(skill.name)) {
57225
+ for (const { dir, source } of options.dirs) {
57226
+ const result = loadSkillsFromDir(dir, source);
57227
+ for (const skill of result.skills) {
56540
57228
  const existing = skillMap.get(skill.name);
56541
- diagnostics.push({
56542
- type: "collision",
56543
- message: `Skill "${skill.name}" collision: project skill overrides ${existing.source}`,
56544
- path: skill.filePath
56545
- });
57229
+ if (existing) {
57230
+ diagnostics.push({
57231
+ type: "collision",
57232
+ message: `Skill "${skill.name}" collision: ${source} skill overrides ${existing.source}`,
57233
+ path: skill.filePath
57234
+ });
57235
+ }
57236
+ skillMap.set(skill.name, skill);
56546
57237
  }
56547
- skillMap.set(skill.name, skill);
57238
+ diagnostics.push(...result.diagnostics);
56548
57239
  }
56549
- diagnostics.push(...projectResult.diagnostics);
56550
57240
  return { skills: Array.from(skillMap.values()), diagnostics };
56551
57241
  }
56552
57242
  function formatSkillsForPrompt(skills) {
@@ -56687,9 +57377,12 @@ function validateSkillName(name) {
56687
57377
  return { valid: true };
56688
57378
  }
56689
57379
 
57380
+ // src/interactive-mode.ts
57381
+ import * as path29 from "path";
57382
+
56690
57383
  // src/capabilities/read-file-understanding.ts
56691
- import * as path20 from "node:path";
56692
- import * as fs20 from "node:fs/promises";
57384
+ import * as path21 from "node:path";
57385
+ import * as fs21 from "node:fs/promises";
56693
57386
 
56694
57387
  // src/core/provider-capabilities.ts
56695
57388
  var PLATFORM_DISPLAY_NAMES = {
@@ -56715,8 +57408,8 @@ function getProviderDisplayName(platform2) {
56715
57408
  }
56716
57409
 
56717
57410
  // src/files/adapters/kimi-adapter.ts
56718
- import * as fs13 from "node:fs";
56719
- import * as path12 from "node:path";
57411
+ import * as fs14 from "node:fs";
57412
+ import * as path13 from "node:path";
56720
57413
 
56721
57414
  // src/files/errors.ts
56722
57415
  var FileErrorMessages = {
@@ -56853,17 +57546,17 @@ var KimiFileAdapter = class {
56853
57546
  // Upload
56854
57547
  // ============================================================================
56855
57548
  async upload(filePath, options = {}) {
56856
- const fileName = options.sourceName || path12.basename(filePath);
57549
+ const fileName = options.sourceName || path13.basename(filePath);
56857
57550
  const mimeType = options.mimeType || this.guessMimeType(fileName);
56858
57551
  const purpose = this.detectPurpose(filePath, options.purpose, mimeType);
56859
- const stats = fs13.statSync(filePath);
57552
+ const stats = fs14.statSync(filePath);
56860
57553
  if (stats.size > this.capabilities.maxFileSize) {
56861
57554
  throw new FileManagerError(
56862
57555
  "FILE_TOO_LARGE" /* FILE_TOO_LARGE */,
56863
57556
  `File size ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds Kimi limit of 100MB`
56864
57557
  );
56865
57558
  }
56866
- const fileBuffer = fs13.readFileSync(filePath);
57559
+ const fileBuffer = fs14.readFileSync(filePath);
56867
57560
  const file = new File([fileBuffer], fileName, { type: mimeType });
56868
57561
  const formData = new FormData();
56869
57562
  formData.append("file", file);
@@ -56915,7 +57608,7 @@ var KimiFileAdapter = class {
56915
57608
  return "file-extract";
56916
57609
  }
56917
57610
  guessMimeType(filename) {
56918
- const ext = path12.extname(filename).toLowerCase();
57611
+ const ext = path13.extname(filename).toLowerCase();
56919
57612
  switch (ext) {
56920
57613
  case ".jpg":
56921
57614
  case ".jpeg":
@@ -57077,8 +57770,8 @@ var KimiFileAdapter = class {
57077
57770
  };
57078
57771
 
57079
57772
  // src/files/adapters/gemini-adapter.ts
57080
- import * as fs14 from "node:fs";
57081
- import * as path13 from "node:path";
57773
+ import * as fs15 from "node:fs";
57774
+ import * as path14 from "node:path";
57082
57775
  import { Readable } from "node:stream";
57083
57776
  var GeminiFileAdapter = class {
57084
57777
  platform = "gemini";
@@ -57135,7 +57828,7 @@ var GeminiFileAdapter = class {
57135
57828
  // Upload
57136
57829
  // ============================================================================
57137
57830
  async upload(filePath, options = {}) {
57138
- const stats = fs14.statSync(filePath);
57831
+ const stats = fs15.statSync(filePath);
57139
57832
  if (stats.size > this.capabilities.maxFileSize) {
57140
57833
  throw new FileManagerError(
57141
57834
  "FILE_TOO_LARGE" /* FILE_TOO_LARGE */,
@@ -57143,7 +57836,7 @@ var GeminiFileAdapter = class {
57143
57836
  );
57144
57837
  }
57145
57838
  const mimeType = options.mimeType || this.guessMimeType(filePath);
57146
- const displayName = options.sourceName || path13.basename(filePath);
57839
+ const displayName = options.sourceName || path14.basename(filePath);
57147
57840
  const startResponse = await fetch(
57148
57841
  `${this.baseUrl}/upload/v1beta/files?key=${this.apiKey}`,
57149
57842
  {
@@ -57174,7 +57867,7 @@ var GeminiFileAdapter = class {
57174
57867
  "No upload URL received"
57175
57868
  );
57176
57869
  }
57177
- const fileStream = Readable.toWeb(fs14.createReadStream(filePath));
57870
+ const fileStream = Readable.toWeb(fs15.createReadStream(filePath));
57178
57871
  const uploadResponse = await fetch(uploadUrl, {
57179
57872
  method: "POST",
57180
57873
  headers: {
@@ -57378,8 +58071,8 @@ var GeminiFileAdapter = class {
57378
58071
  };
57379
58072
 
57380
58073
  // src/files/adapters/openai-compatible-files-adapter.ts
57381
- import * as fs15 from "node:fs";
57382
- import * as path14 from "node:path";
58074
+ import * as fs16 from "node:fs";
58075
+ import * as path15 from "node:path";
57383
58076
  var OpenAICompatibleFilesAdapter = class {
57384
58077
  constructor(apiKey, baseUrl, extraHeaders = {}) {
57385
58078
  this.apiKey = apiKey;
@@ -57405,16 +58098,16 @@ var OpenAICompatibleFilesAdapter = class {
57405
58098
  ]
57406
58099
  };
57407
58100
  async upload(filePath, options = {}) {
57408
- const fileName = options.sourceName || path14.basename(filePath);
58101
+ const fileName = options.sourceName || path15.basename(filePath);
57409
58102
  const mimeType = options.mimeType || guessMimeType(fileName);
57410
- const stats = fs15.statSync(filePath);
58103
+ const stats = fs16.statSync(filePath);
57411
58104
  if (stats.size > this.capabilities.maxFileSize) {
57412
58105
  throw new FileManagerError(
57413
58106
  "FILE_TOO_LARGE" /* FILE_TOO_LARGE */,
57414
58107
  `File size ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds OpenAI-compatible Files limit of ${this.capabilities.maxFileSize / 1024 / 1024}MB`
57415
58108
  );
57416
58109
  }
57417
- const file = new File([fs15.readFileSync(filePath)], fileName, { type: mimeType });
58110
+ const file = new File([fs16.readFileSync(filePath)], fileName, { type: mimeType });
57418
58111
  const formData = new FormData();
57419
58112
  formData.append("file", file);
57420
58113
  formData.append("purpose", options.purpose && options.purpose !== "auto" ? options.purpose : "file-extract");
@@ -57531,7 +58224,7 @@ function extractContentText(raw) {
57531
58224
  return raw;
57532
58225
  }
57533
58226
  function guessMimeType(filename) {
57534
- const ext = path14.extname(filename).toLowerCase();
58227
+ const ext = path15.extname(filename).toLowerCase();
57535
58228
  switch (ext) {
57536
58229
  case ".pdf":
57537
58230
  return "application/pdf";
@@ -57547,13 +58240,13 @@ function guessMimeType(filename) {
57547
58240
  }
57548
58241
 
57549
58242
  // src/files/file-manager.ts
57550
- import * as fs19 from "node:fs";
57551
- import * as path19 from "node:path";
58243
+ import * as fs20 from "node:fs";
58244
+ import * as path20 from "node:path";
57552
58245
  import * as crypto2 from "node:crypto";
57553
58246
 
57554
58247
  // src/files/file-cache.ts
57555
- import * as fs16 from "node:fs";
57556
- import * as path16 from "node:path";
58248
+ import * as fs17 from "node:fs";
58249
+ import * as path17 from "node:path";
57557
58250
  import * as crypto from "node:crypto";
57558
58251
 
57559
58252
  // src/files/logger.ts
@@ -57648,14 +58341,14 @@ var DEFAULT_PIC_OPTIONS = {
57648
58341
  };
57649
58342
 
57650
58343
  // src/files/utils.ts
57651
- import * as path15 from "node:path";
57652
- import { homedir as homedir4 } from "node:os";
58344
+ import * as path16 from "node:path";
58345
+ import { homedir as homedir5 } from "node:os";
57653
58346
  var WINDOWS_ABSOLUTE_PATH_RE = /^(?:[a-zA-Z]:[\\/]|[\\/]{2}[^\\/]+[\\/][^\\/]+)/;
57654
58347
  function isWindowsAbsolutePath(filePath) {
57655
58348
  return WINDOWS_ABSOLUTE_PATH_RE.test(filePath);
57656
58349
  }
57657
58350
  function getHomeDirectory() {
57658
- return homedir4() || process.env.USERPROFILE || process.env.HOME || ".";
58351
+ return homedir5() || process.env.USERPROFILE || process.env.HOME || ".";
57659
58352
  }
57660
58353
  function looksLikeFilePath(inputPath) {
57661
58354
  return inputPath.startsWith("/") || inputPath.startsWith("~") || inputPath.startsWith("./") || inputPath.startsWith("../") || inputPath.startsWith(".\\") || inputPath.startsWith("..\\") || isWindowsAbsolutePath(inputPath);
@@ -57669,13 +58362,13 @@ function formatBytes(bytes) {
57669
58362
  }
57670
58363
  function resolvePath2(inputPath) {
57671
58364
  if (inputPath.startsWith("~")) {
57672
- return path15.join(getHomeDirectory(), inputPath.slice(1));
58365
+ return path16.join(getHomeDirectory(), inputPath.slice(1));
57673
58366
  }
57674
58367
  if (isWindowsAbsolutePath(inputPath)) {
57675
58368
  return inputPath;
57676
58369
  }
57677
- if (!path15.isAbsolute(inputPath)) {
57678
- return path15.resolve(process.cwd(), inputPath);
58370
+ if (!path16.isAbsolute(inputPath)) {
58371
+ return path16.resolve(process.cwd(), inputPath);
57679
58372
  }
57680
58373
  return inputPath;
57681
58374
  }
@@ -57703,8 +58396,8 @@ var FileCache = class _FileCache {
57703
58396
  maxEntries;
57704
58397
  constructor(baseDir, maxEntries = DEFAULT_MAX_CACHE_ENTRIES) {
57705
58398
  this.baseDir = baseDir;
57706
- this.dataDir = path16.join(baseDir, "data");
57707
- this.indexPath = path16.join(baseDir, "index.json");
58399
+ this.dataDir = path17.join(baseDir, "data");
58400
+ this.indexPath = path17.join(baseDir, "index.json");
57708
58401
  this.maxEntries = maxEntries;
57709
58402
  this.index = this.loadIndex();
57710
58403
  this.ensureDirectories();
@@ -57713,22 +58406,22 @@ var FileCache = class _FileCache {
57713
58406
  // Directory Management
57714
58407
  // ============================================================================
57715
58408
  ensureDirectories() {
57716
- if (!fs16.existsSync(this.baseDir)) {
57717
- fs16.mkdirSync(this.baseDir, { recursive: true });
58409
+ if (!fs17.existsSync(this.baseDir)) {
58410
+ fs17.mkdirSync(this.baseDir, { recursive: true });
57718
58411
  }
57719
- if (!fs16.existsSync(this.dataDir)) {
57720
- fs16.mkdirSync(this.dataDir, { recursive: true });
58412
+ if (!fs17.existsSync(this.dataDir)) {
58413
+ fs17.mkdirSync(this.dataDir, { recursive: true });
57721
58414
  }
57722
58415
  }
57723
58416
  // ============================================================================
57724
58417
  // Index Management
57725
58418
  // ============================================================================
57726
58419
  loadIndex() {
57727
- if (!fs16.existsSync(this.indexPath)) {
58420
+ if (!fs17.existsSync(this.indexPath)) {
57728
58421
  return { version: CACHE_VERSION, files: {}, pathHints: {} };
57729
58422
  }
57730
58423
  try {
57731
- const data = JSON.parse(fs16.readFileSync(this.indexPath, "utf-8"));
58424
+ const data = JSON.parse(fs17.readFileSync(this.indexPath, "utf-8"));
57732
58425
  if (data.version !== CACHE_VERSION) {
57733
58426
  cacheLogger.warn(`Index version mismatch: ${data.version} vs ${CACHE_VERSION}`);
57734
58427
  }
@@ -57744,7 +58437,7 @@ var FileCache = class _FileCache {
57744
58437
  }
57745
58438
  saveIndex() {
57746
58439
  try {
57747
- fs16.writeFileSync(this.indexPath, JSON.stringify(this.index, null, 2));
58440
+ fs17.writeFileSync(this.indexPath, JSON.stringify(this.index, null, 2));
57748
58441
  } catch (err) {
57749
58442
  cacheLogger.error(" Failed to save index:", err);
57750
58443
  throw err;
@@ -57754,11 +58447,11 @@ var FileCache = class _FileCache {
57754
58447
  // Hash Computation
57755
58448
  // ============================================================================
57756
58449
  async computeHash(filePath) {
57757
- return new Promise((resolve6, reject) => {
58450
+ return new Promise((resolve7, reject) => {
57758
58451
  const hash = crypto.createHash("sha256");
57759
- const stream = fs16.createReadStream(filePath);
58452
+ const stream = fs17.createReadStream(filePath);
57760
58453
  stream.on("data", (chunk) => hash.update(chunk));
57761
- stream.on("end", () => resolve6(hash.digest("hex")));
58454
+ stream.on("end", () => resolve7(hash.digest("hex")));
57762
58455
  stream.on("error", reject);
57763
58456
  });
57764
58457
  }
@@ -57770,25 +58463,25 @@ var FileCache = class _FileCache {
57770
58463
  // ============================================================================
57771
58464
  getStoragePath(hash) {
57772
58465
  const subdir = hash.slice(0, 2);
57773
- const fullDir = path16.join(this.dataDir, subdir);
57774
- if (!fs16.existsSync(fullDir)) {
57775
- fs16.mkdirSync(fullDir, { recursive: true });
58466
+ const fullDir = path17.join(this.dataDir, subdir);
58467
+ if (!fs17.existsSync(fullDir)) {
58468
+ fs17.mkdirSync(fullDir, { recursive: true });
57776
58469
  }
57777
- return path16.join(fullDir, hash);
58470
+ return path17.join(fullDir, hash);
57778
58471
  }
57779
58472
  getStorageRelativePath(hash) {
57780
58473
  const subdir = hash.slice(0, 2);
57781
- return path16.join(subdir, hash);
58474
+ return path17.join(subdir, hash);
57782
58475
  }
57783
58476
  async storeFile(sourcePath, name, mimeType) {
57784
58477
  const hash = await this.computeHash(sourcePath);
57785
58478
  const storagePath = this.getStoragePath(hash);
57786
58479
  this.rememberPathHint(sourcePath, hash, name);
57787
- if (fs16.existsSync(storagePath)) {
58480
+ if (fs17.existsSync(storagePath)) {
57788
58481
  return { hash, localPath: storagePath };
57789
58482
  }
57790
- fs16.copyFileSync(sourcePath, storagePath);
57791
- const stat2 = fs16.statSync(storagePath);
58483
+ fs17.copyFileSync(sourcePath, storagePath);
58484
+ const stat2 = fs17.statSync(storagePath);
57792
58485
  this.index.files[hash] = {
57793
58486
  hash,
57794
58487
  name,
@@ -57805,10 +58498,10 @@ var FileCache = class _FileCache {
57805
58498
  async storeBuffer(data, name, mimeType) {
57806
58499
  const hash = this.computeHashSync(data);
57807
58500
  const storagePath = this.getStoragePath(hash);
57808
- if (fs16.existsSync(storagePath)) {
58501
+ if (fs17.existsSync(storagePath)) {
57809
58502
  return { hash, localPath: storagePath };
57810
58503
  }
57811
- fs16.writeFileSync(storagePath, data);
58504
+ fs17.writeFileSync(storagePath, data);
57812
58505
  this.index.files[hash] = {
57813
58506
  hash,
57814
58507
  name,
@@ -57840,7 +58533,7 @@ var FileCache = class _FileCache {
57840
58533
  getLocalPath(hash) {
57841
58534
  const entry = this.index.files[hash];
57842
58535
  if (!entry) return void 0;
57843
- return path16.join(this.dataDir, entry.localPath);
58536
+ return path17.join(this.dataDir, entry.localPath);
57844
58537
  }
57845
58538
  exists(hash) {
57846
58539
  return hash in this.index.files;
@@ -57880,7 +58573,7 @@ var FileCache = class _FileCache {
57880
58573
  getPathHint(filePath) {
57881
58574
  try {
57882
58575
  const resolvedPath = this.getResolvedHintPath(filePath);
57883
- const stat2 = fs16.statSync(resolvedPath);
58576
+ const stat2 = fs17.statSync(resolvedPath);
57884
58577
  if (!stat2.isFile()) return void 0;
57885
58578
  const normalizedMtimeMs = this.normalizeHintMtimeMs(stat2.mtimeMs);
57886
58579
  const key = this.getPathHintKey(resolvedPath);
@@ -57910,7 +58603,7 @@ var FileCache = class _FileCache {
57910
58603
  rememberPathHint(filePath, hash, name) {
57911
58604
  try {
57912
58605
  const resolvedPath = this.getResolvedHintPath(filePath);
57913
- const stat2 = fs16.statSync(resolvedPath);
58606
+ const stat2 = fs17.statSync(resolvedPath);
57914
58607
  if (!stat2.isFile()) return;
57915
58608
  const entry = this.index.files[hash];
57916
58609
  const normalizedMtimeMs = this.normalizeHintMtimeMs(stat2.mtimeMs);
@@ -57920,7 +58613,7 @@ var FileCache = class _FileCache {
57920
58613
  this.index.pathHints[this.getPathHintKey(resolvedPath)] = {
57921
58614
  path: resolvedPath,
57922
58615
  hash,
57923
- name: name || entry?.name || path16.basename(resolvedPath),
58616
+ name: name || entry?.name || path17.basename(resolvedPath),
57924
58617
  size: stat2.size,
57925
58618
  mtimeMs: normalizedMtimeMs,
57926
58619
  lastSeenAt: Date.now(),
@@ -57968,9 +58661,9 @@ var FileCache = class _FileCache {
57968
58661
  deleteEntry(hash, entry) {
57969
58662
  entry = entry || this.index.files[hash];
57970
58663
  if (!entry) return;
57971
- const localPath = path16.join(this.dataDir, entry.localPath);
57972
- if (fs16.existsSync(localPath)) {
57973
- fs16.unlinkSync(localPath);
58664
+ const localPath = path17.join(this.dataDir, entry.localPath);
58665
+ if (fs17.existsSync(localPath)) {
58666
+ fs17.unlinkSync(localPath);
57974
58667
  }
57975
58668
  delete this.index.files[hash];
57976
58669
  if (this.index.pathHints) {
@@ -57984,9 +58677,9 @@ var FileCache = class _FileCache {
57984
58677
  }
57985
58678
  clear() {
57986
58679
  for (const entry of Object.values(this.index.files)) {
57987
- const localPath = path16.join(this.dataDir, entry.localPath);
57988
- if (fs16.existsSync(localPath)) {
57989
- fs16.unlinkSync(localPath);
58680
+ const localPath = path17.join(this.dataDir, entry.localPath);
58681
+ if (fs17.existsSync(localPath)) {
58682
+ fs17.unlinkSync(localPath);
57990
58683
  }
57991
58684
  }
57992
58685
  this.index = { version: CACHE_VERSION, files: {}, pathHints: {} };
@@ -58039,11 +58732,11 @@ var FileCache = class _FileCache {
58039
58732
  };
58040
58733
  }
58041
58734
  static getMimeType(fileName) {
58042
- const ext = path16.extname(fileName).toLowerCase();
58735
+ const ext = path17.extname(fileName).toLowerCase();
58043
58736
  return MIME_TYPES[ext] || "application/octet-stream";
58044
58737
  }
58045
58738
  static getFileTypeFromName(fileName) {
58046
- const baseName = path16.basename(fileName);
58739
+ const baseName = path17.basename(fileName);
58047
58740
  if (!baseName || baseName.startsWith(".")) {
58048
58741
  return void 0;
58049
58742
  }
@@ -58074,7 +58767,7 @@ var FileCache = class _FileCache {
58074
58767
  getResolvedHintPath(filePath) {
58075
58768
  const resolvedPath = resolvePath2(filePath);
58076
58769
  try {
58077
- return fs16.realpathSync.native ? fs16.realpathSync.native(resolvedPath) : fs16.realpathSync(resolvedPath);
58770
+ return fs17.realpathSync.native ? fs17.realpathSync.native(resolvedPath) : fs17.realpathSync(resolvedPath);
58078
58771
  } catch {
58079
58772
  return resolvedPath;
58080
58773
  }
@@ -58082,8 +58775,8 @@ var FileCache = class _FileCache {
58082
58775
  };
58083
58776
 
58084
58777
  // src/files/session-file-manager.ts
58085
- import * as fs17 from "node:fs";
58086
- import * as path17 from "node:path";
58778
+ import * as fs18 from "node:fs";
58779
+ import * as path18 from "node:path";
58087
58780
  var INITIAL_REFRESH_BACKOFF_MS = 6e4;
58088
58781
  var MAX_REFRESH_BACKOFF_MS = 60 * 60 * 1e3;
58089
58782
  var SessionFileManager = class {
@@ -58097,7 +58790,7 @@ var SessionFileManager = class {
58097
58790
  constructor(sessionId, sessionsDir, fileCache, authFingerprint) {
58098
58791
  this.sessionId = sessionId;
58099
58792
  this.sessionsDir = sessionsDir;
58100
- this.statePath = path17.join(this.sessionsDir, `${sessionId}-files.json`);
58793
+ this.statePath = path18.join(this.sessionsDir, `${sessionId}-files.json`);
58101
58794
  this.fileCache = fileCache;
58102
58795
  this.authFingerprint = authFingerprint;
58103
58796
  this.state = this.loadState();
@@ -58107,15 +58800,15 @@ var SessionFileManager = class {
58107
58800
  // Directory Management
58108
58801
  // ============================================================================
58109
58802
  ensureDirectories() {
58110
- if (!fs17.existsSync(this.sessionsDir)) {
58111
- fs17.mkdirSync(this.sessionsDir, { recursive: true });
58803
+ if (!fs18.existsSync(this.sessionsDir)) {
58804
+ fs18.mkdirSync(this.sessionsDir, { recursive: true });
58112
58805
  }
58113
58806
  }
58114
58807
  // ============================================================================
58115
58808
  // State Management
58116
58809
  // ============================================================================
58117
58810
  loadState() {
58118
- if (!fs17.existsSync(this.statePath)) {
58811
+ if (!fs18.existsSync(this.statePath)) {
58119
58812
  return {
58120
58813
  sessionId: this.sessionId,
58121
58814
  lastSyncedAt: 0,
@@ -58123,7 +58816,7 @@ var SessionFileManager = class {
58123
58816
  };
58124
58817
  }
58125
58818
  try {
58126
- return JSON.parse(fs17.readFileSync(this.statePath, "utf-8"));
58819
+ return JSON.parse(fs18.readFileSync(this.statePath, "utf-8"));
58127
58820
  } catch (err) {
58128
58821
  sessionLogger.error(" Failed to load state:", err);
58129
58822
  return {
@@ -58136,12 +58829,12 @@ var SessionFileManager = class {
58136
58829
  saveState() {
58137
58830
  try {
58138
58831
  if (Object.keys(this.state.files).length === 0) {
58139
- if (fs17.existsSync(this.statePath)) {
58140
- fs17.unlinkSync(this.statePath);
58832
+ if (fs18.existsSync(this.statePath)) {
58833
+ fs18.unlinkSync(this.statePath);
58141
58834
  }
58142
58835
  return;
58143
58836
  }
58144
- fs17.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
58837
+ fs18.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
58145
58838
  } catch (err) {
58146
58839
  sessionLogger.error(" Failed to save state:", err);
58147
58840
  }
@@ -58287,7 +58980,7 @@ var SessionFileManager = class {
58287
58980
  return { success: false, coolingDown: true };
58288
58981
  }
58289
58982
  const localPath = this.fileCache.getLocalPath(entry.hash);
58290
- if (!localPath || !fs17.existsSync(localPath)) {
58983
+ if (!localPath || !fs18.existsSync(localPath)) {
58291
58984
  sessionLogger.warn(`Local file not found for ${fileId}`);
58292
58985
  entry.status = "expired";
58293
58986
  entry.error = "Local file missing";
@@ -58438,15 +59131,15 @@ var SessionFileManager = class {
58438
59131
  // Static Utilities
58439
59132
  // ============================================================================
58440
59133
  static cleanupOldSessions(sessionsDir, maxAge = 30 * 24 * 60 * 60 * 1e3) {
58441
- if (!fs17.existsSync(sessionsDir)) return;
59134
+ if (!fs18.existsSync(sessionsDir)) return;
58442
59135
  const now = Date.now();
58443
- const files = fs17.readdirSync(sessionsDir);
59136
+ const files = fs18.readdirSync(sessionsDir);
58444
59137
  for (const file of files) {
58445
59138
  if (!file.endsWith(".json")) continue;
58446
- const filePath = path17.join(sessionsDir, file);
58447
- const stat2 = fs17.statSync(filePath);
59139
+ const filePath = path18.join(sessionsDir, file);
59140
+ const stat2 = fs18.statSync(filePath);
58448
59141
  if (now - stat2.mtime.getTime() > maxAge) {
58449
- fs17.unlinkSync(filePath);
59142
+ fs18.unlinkSync(filePath);
58450
59143
  sessionLogger.debug(`Cleaned up old session: ${file}`);
58451
59144
  }
58452
59145
  }
@@ -58454,8 +59147,8 @@ var SessionFileManager = class {
58454
59147
  };
58455
59148
 
58456
59149
  // src/files/ffmpeg-processor.ts
58457
- import * as fs18 from "node:fs";
58458
- import * as path18 from "node:path";
59150
+ import * as fs19 from "node:fs";
59151
+ import * as path19 from "node:path";
58459
59152
  import { spawn } from "node:child_process";
58460
59153
  var FFmpegProcessor = class {
58461
59154
  ffmpegPath = "ffmpeg";
@@ -58468,17 +59161,17 @@ var FFmpegProcessor = class {
58468
59161
  // Detection
58469
59162
  // ============================================================================
58470
59163
  async checkAvailability() {
58471
- return new Promise((resolve6) => {
59164
+ return new Promise((resolve7) => {
58472
59165
  const proc = spawn(this.ffmpegPath, ["-version"]);
58473
- proc.on("error", () => resolve6(false));
58474
- proc.on("exit", (code) => resolve6(code === 0));
59166
+ proc.on("error", () => resolve7(false));
59167
+ proc.on("exit", (code) => resolve7(code === 0));
58475
59168
  });
58476
59169
  }
58477
59170
  // ============================================================================
58478
59171
  // Video Analysis
58479
59172
  // ============================================================================
58480
59173
  async analyzeVideo(videoPath) {
58481
- return new Promise((resolve6, reject) => {
59174
+ return new Promise((resolve7, reject) => {
58482
59175
  const args = [
58483
59176
  "-v",
58484
59177
  "error",
@@ -58507,7 +59200,7 @@ var FFmpegProcessor = class {
58507
59200
  }
58508
59201
  try {
58509
59202
  const info = this.parseProbeOutput(stdout, videoPath);
58510
- resolve6(info);
59203
+ resolve7(info);
58511
59204
  } catch (err) {
58512
59205
  reject(err);
58513
59206
  }
@@ -58559,7 +59252,7 @@ var FFmpegProcessor = class {
58559
59252
  }
58560
59253
  async compressVideo(inputPath, outputDir, info, opts, onProgress) {
58561
59254
  const outputName = `compressed_${Date.now()}.mp4`;
58562
- const outputPath = path18.join(outputDir, outputName);
59255
+ const outputPath = path19.join(outputDir, outputName);
58563
59256
  const adaptiveParams = this.calculateAdaptiveParams(info.duration, opts);
58564
59257
  const args = [
58565
59258
  "-i",
@@ -58590,7 +59283,7 @@ var FFmpegProcessor = class {
58590
59283
  onProgress?.(percent);
58591
59284
  }
58592
59285
  });
58593
- const outputStat = fs18.statSync(outputPath);
59286
+ const outputStat = fs19.statSync(outputPath);
58594
59287
  return {
58595
59288
  type: "video",
58596
59289
  output: outputPath,
@@ -58621,9 +59314,9 @@ var FFmpegProcessor = class {
58621
59314
  }
58622
59315
  async extractKeyframes(inputPath, outputDir, info, opts, onProgress) {
58623
59316
  const interval = Math.floor(info.duration / opts.keyframeCount);
58624
- const framesDir = path18.join(outputDir, `keyframes_${Date.now()}`);
58625
- fs18.mkdirSync(framesDir, { recursive: true });
58626
- const outputPattern = path18.join(framesDir, "frame_%03d.jpg");
59317
+ const framesDir = path19.join(outputDir, `keyframes_${Date.now()}`);
59318
+ fs19.mkdirSync(framesDir, { recursive: true });
59319
+ const outputPattern = path19.join(framesDir, "frame_%03d.jpg");
58627
59320
  const args = [
58628
59321
  "-i",
58629
59322
  inputPath,
@@ -58641,10 +59334,10 @@ var FFmpegProcessor = class {
58641
59334
  onProgress?.(percent);
58642
59335
  }
58643
59336
  });
58644
- let frames = fs18.readdirSync(framesDir).filter((f) => f.endsWith(".jpg")).sort().map((f) => path18.join(framesDir, f));
59337
+ let frames = fs19.readdirSync(framesDir).filter((f) => f.endsWith(".jpg")).sort().map((f) => path19.join(framesDir, f));
58645
59338
  if (frames.length < opts.keyframeCount / 2) {
58646
- fs18.rmSync(framesDir, { recursive: true, force: true });
58647
- fs18.mkdirSync(framesDir, { recursive: true });
59339
+ fs19.rmSync(framesDir, { recursive: true, force: true });
59340
+ fs19.mkdirSync(framesDir, { recursive: true });
58648
59341
  const uniformArgs = [
58649
59342
  "-i",
58650
59343
  inputPath,
@@ -58660,10 +59353,10 @@ var FFmpegProcessor = class {
58660
59353
  onProgress?.(percent);
58661
59354
  }
58662
59355
  });
58663
- frames = fs18.readdirSync(framesDir).filter((f) => f.endsWith(".jpg")).sort().map((f) => path18.join(framesDir, f));
59356
+ frames = fs19.readdirSync(framesDir).filter((f) => f.endsWith(".jpg")).sort().map((f) => path19.join(framesDir, f));
58664
59357
  }
58665
59358
  const totalOutputSize = frames.reduce(
58666
- (sum, f) => sum + fs18.statSync(f).size,
59359
+ (sum, f) => sum + fs19.statSync(f).size,
58667
59360
  0
58668
59361
  );
58669
59362
  return {
@@ -58684,7 +59377,7 @@ var FFmpegProcessor = class {
58684
59377
  const opts = { ...DEFAULT_PIC_OPTIONS, ...options };
58685
59378
  const ext = opts.format === "webp" ? ".webp" : ".jpg";
58686
59379
  const outputName = `optimized_${Date.now()}${ext}`;
58687
- const outputPath = path18.join(outputDir, outputName);
59380
+ const outputPath = path19.join(outputDir, outputName);
58688
59381
  const args = [
58689
59382
  "-i",
58690
59383
  inputPath,
@@ -58702,7 +59395,7 @@ var FFmpegProcessor = class {
58702
59395
  // FFmpeg Execution
58703
59396
  // ============================================================================
58704
59397
  runFFmpeg(args) {
58705
- return new Promise((resolve6, reject) => {
59398
+ return new Promise((resolve7, reject) => {
58706
59399
  const proc = spawn(this.ffmpegPath, args);
58707
59400
  let stderr = "";
58708
59401
  proc.stderr.on("data", (data) => {
@@ -58717,7 +59410,7 @@ var FFmpegProcessor = class {
58717
59410
  )
58718
59411
  );
58719
59412
  } else {
58720
- resolve6();
59413
+ resolve7();
58721
59414
  }
58722
59415
  });
58723
59416
  proc.on("error", (err) => {
@@ -58734,7 +59427,7 @@ var FFmpegProcessor = class {
58734
59427
  // Progress Monitoring (for future use)
58735
59428
  // ============================================================================
58736
59429
  async runWithProgress(args, onProgress) {
58737
- return new Promise((resolve6, reject) => {
59430
+ return new Promise((resolve7, reject) => {
58738
59431
  const progressArgs = [
58739
59432
  ...args,
58740
59433
  "-progress",
@@ -58764,7 +59457,7 @@ var FFmpegProcessor = class {
58764
59457
  )
58765
59458
  );
58766
59459
  } else {
58767
- resolve6();
59460
+ resolve7();
58768
59461
  }
58769
59462
  });
58770
59463
  });
@@ -58859,8 +59552,8 @@ var DIRECTORY_LISTING_TTL_MS = 1500;
58859
59552
  var FileManager = class {
58860
59553
  constructor(options) {
58861
59554
  this.options = options;
58862
- const baseDir = options.baseDir || path19.join(process.env.HOME || ".", ".pie", "files");
58863
- const sessionsDir = options.sessionsDir || path19.join(process.env.HOME || ".", ".pie", "sessions");
59555
+ const baseDir = options.baseDir || path20.join(process.env.HOME || ".", ".pie", "files");
59556
+ const sessionsDir = options.sessionsDir || path20.join(process.env.HOME || ".", ".pie", "sessions");
58864
59557
  this.fileCache = new FileCache(baseDir);
58865
59558
  this.authFingerprint = options.apiKey ? this.buildAuthFingerprint(options.apiKey) : void 0;
58866
59559
  this.sessionManager = new SessionFileManager(
@@ -58871,7 +59564,7 @@ var FileManager = class {
58871
59564
  );
58872
59565
  this.ffmpeg = new FFmpegProcessor(options.ffmpegPath);
58873
59566
  this.platform = normalizeFileManagerPlatform(options.platform);
58874
- this.tempDir = path19.join(baseDir, "temp");
59567
+ this.tempDir = path20.join(baseDir, "temp");
58875
59568
  this.ensureDirectories();
58876
59569
  }
58877
59570
  options;
@@ -58883,8 +59576,8 @@ var FileManager = class {
58883
59576
  directoryListingCache = /* @__PURE__ */ new Map();
58884
59577
  authFingerprint;
58885
59578
  ensureDirectories() {
58886
- if (!fs19.existsSync(this.tempDir)) {
58887
- fs19.mkdirSync(this.tempDir, { recursive: true });
59579
+ if (!fs20.existsSync(this.tempDir)) {
59580
+ fs20.mkdirSync(this.tempDir, { recursive: true });
58888
59581
  }
58889
59582
  }
58890
59583
  // ============================================================================
@@ -58914,8 +59607,8 @@ var FileManager = class {
58914
59607
  // ============================================================================
58915
59608
  async stageLocal(filePath, options = {}) {
58916
59609
  const resolvedPath = resolvePath2(filePath);
58917
- const sourceName = options.sourceName || path19.basename(resolvedPath);
58918
- if (!fs19.existsSync(resolvedPath)) {
59610
+ const sourceName = options.sourceName || path20.basename(resolvedPath);
59611
+ if (!fs20.existsSync(resolvedPath)) {
58919
59612
  throw new FileManagerError(
58920
59613
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
58921
59614
  `File not found: ${filePath}`
@@ -58930,7 +59623,7 @@ var FileManager = class {
58930
59623
  this.fileCache.rememberPathHint(resolvedPath, hash, sourceName);
58931
59624
  this.invalidateDirectoryListingCacheForPath(resolvedPath);
58932
59625
  const entry = this.fileCache.getByHash(hash);
58933
- const size = entry?.size ?? fs19.statSync(resolvedPath).size;
59626
+ const size = entry?.size ?? fs20.statSync(resolvedPath).size;
58934
59627
  return {
58935
59628
  success: true,
58936
59629
  hash,
@@ -58944,8 +59637,8 @@ var FileManager = class {
58944
59637
  }
58945
59638
  async upload(filePath, options = {}) {
58946
59639
  const resolvedPath = resolvePath2(filePath);
58947
- const sourceName = options.sourceName || path19.basename(resolvedPath);
58948
- if (!fs19.existsSync(resolvedPath)) {
59640
+ const sourceName = options.sourceName || path20.basename(resolvedPath);
59641
+ if (!fs20.existsSync(resolvedPath)) {
58949
59642
  throw new FileManagerError(
58950
59643
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
58951
59644
  `File not found: ${filePath}`
@@ -59026,7 +59719,7 @@ var FileManager = class {
59026
59719
  // ============================================================================
59027
59720
  async uploadPic(filePath, options = {}) {
59028
59721
  const resolvedPath = resolvePath2(filePath);
59029
- if (!fs19.existsSync(resolvedPath)) {
59722
+ if (!fs20.existsSync(resolvedPath)) {
59030
59723
  throw new FileManagerError(
59031
59724
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
59032
59725
  `File not found: ${filePath}`
@@ -59034,7 +59727,7 @@ var FileManager = class {
59034
59727
  }
59035
59728
  const cachedResult = await this.tryReuseCachedSourceUpload(
59036
59729
  resolvedPath,
59037
- options.sourceName || path19.basename(resolvedPath),
59730
+ options.sourceName || path20.basename(resolvedPath),
59038
59731
  options.platform || this.platform
59039
59732
  );
59040
59733
  if (cachedResult) {
@@ -59051,12 +59744,12 @@ var FileManager = class {
59051
59744
  const result = await this.upload(optimizedPath, {
59052
59745
  ...options,
59053
59746
  purpose: "image",
59054
- sourceName: path19.basename(resolvedPath)
59747
+ sourceName: path20.basename(resolvedPath)
59055
59748
  });
59056
- this.fileCache.rememberPathHint(resolvedPath, result.hash, path19.basename(resolvedPath));
59749
+ this.fileCache.rememberPathHint(resolvedPath, result.hash, path20.basename(resolvedPath));
59057
59750
  this.invalidateDirectoryListingCacheForPath(resolvedPath);
59058
59751
  try {
59059
- fs19.unlinkSync(optimizedPath);
59752
+ fs20.unlinkSync(optimizedPath);
59060
59753
  } catch {
59061
59754
  }
59062
59755
  return result;
@@ -59066,7 +59759,7 @@ var FileManager = class {
59066
59759
  // ============================================================================
59067
59760
  async uploadVideo(filePath, options = {}) {
59068
59761
  const resolvedPath = resolvePath2(filePath);
59069
- if (!fs19.existsSync(resolvedPath)) {
59762
+ if (!fs20.existsSync(resolvedPath)) {
59070
59763
  throw new FileManagerError(
59071
59764
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
59072
59765
  `File not found: ${filePath}`
@@ -59074,7 +59767,7 @@ var FileManager = class {
59074
59767
  }
59075
59768
  const cachedResult = await this.tryReuseCachedSourceUpload(
59076
59769
  resolvedPath,
59077
- options.sourceName || path19.basename(resolvedPath),
59770
+ options.sourceName || path20.basename(resolvedPath),
59078
59771
  options.platform || this.platform
59079
59772
  );
59080
59773
  if (cachedResult) {
@@ -59124,9 +59817,9 @@ var FileManager = class {
59124
59817
  result = await this.upload(preprocessResult.output, {
59125
59818
  ...options,
59126
59819
  purpose: "video",
59127
- sourceName: path19.basename(resolvedPath)
59820
+ sourceName: path20.basename(resolvedPath)
59128
59821
  });
59129
- this.fileCache.rememberPathHint(resolvedPath, result.hash, path19.basename(resolvedPath));
59822
+ this.fileCache.rememberPathHint(resolvedPath, result.hash, path20.basename(resolvedPath));
59130
59823
  this.invalidateDirectoryListingCacheForPath(resolvedPath);
59131
59824
  } else {
59132
59825
  throw new FileManagerError(
@@ -59418,7 +60111,7 @@ var FileManager = class {
59418
60111
  * Detect purpose from file path based on extension
59419
60112
  */
59420
60113
  detectPurpose(filePath) {
59421
- const ext = path19.extname(filePath).toLowerCase();
60114
+ const ext = path20.extname(filePath).toLowerCase();
59422
60115
  switch (ext) {
59423
60116
  case ".mp4":
59424
60117
  case ".mov":
@@ -59460,13 +60153,13 @@ var FileManager = class {
59460
60153
  cleanupTempFiles(output) {
59461
60154
  try {
59462
60155
  if (typeof output === "string") {
59463
- if (fs19.existsSync(output)) {
59464
- fs19.unlinkSync(output);
60156
+ if (fs20.existsSync(output)) {
60157
+ fs20.unlinkSync(output);
59465
60158
  }
59466
60159
  } else {
59467
60160
  for (const file of output) {
59468
- if (fs19.existsSync(file)) {
59469
- fs19.unlinkSync(file);
60161
+ if (fs20.existsSync(file)) {
60162
+ fs20.unlinkSync(file);
59470
60163
  }
59471
60164
  }
59472
60165
  }
@@ -59498,7 +60191,7 @@ var FileManager = class {
59498
60191
  }];
59499
60192
  }
59500
60193
  const localPath = this.fileCache.getLocalPath(entry.hash);
59501
- if (localPath && fs19.existsSync(localPath)) {
60194
+ if (localPath && fs20.existsSync(localPath)) {
59502
60195
  return [{
59503
60196
  fileId,
59504
60197
  name: cacheEntry.name,
@@ -59595,12 +60288,12 @@ var FileManager = class {
59595
60288
  directories: []
59596
60289
  };
59597
60290
  try {
59598
- const entries = fs19.readdirSync(resolvedDir, { withFileTypes: true });
60291
+ const entries = fs20.readdirSync(resolvedDir, { withFileTypes: true });
59599
60292
  for (const entry of entries) {
59600
60293
  if (!entry.name || entry.name.startsWith(".")) {
59601
60294
  continue;
59602
60295
  }
59603
- const fullPath = path19.join(resolvedDir, entry.name);
60296
+ const fullPath = path20.join(resolvedDir, entry.name);
59604
60297
  if (entry.isDirectory()) {
59605
60298
  listing.directories.push({ name: entry.name, path: fullPath });
59606
60299
  continue;
@@ -59609,7 +60302,7 @@ var FileManager = class {
59609
60302
  let isFile = entry.isFile();
59610
60303
  if (entry.isSymbolicLink()) {
59611
60304
  try {
59612
- const targetStat = fs19.statSync(fullPath);
60305
+ const targetStat = fs20.statSync(fullPath);
59613
60306
  isDirectory = targetStat.isDirectory();
59614
60307
  isFile = targetStat.isFile();
59615
60308
  } catch {
@@ -59627,7 +60320,7 @@ var FileManager = class {
59627
60320
  if (!type) {
59628
60321
  continue;
59629
60322
  }
59630
- const stat2 = fs19.statSync(fullPath);
60323
+ const stat2 = fs20.statSync(fullPath);
59631
60324
  listing.files.push({
59632
60325
  name: entry.name,
59633
60326
  path: fullPath,
@@ -59641,7 +60334,7 @@ var FileManager = class {
59641
60334
  return listing;
59642
60335
  }
59643
60336
  invalidateDirectoryListingCacheForPath(filePath) {
59644
- this.directoryListingCache.delete(path19.dirname(resolvePath2(filePath)));
60337
+ this.directoryListingCache.delete(path20.dirname(resolvePath2(filePath)));
59645
60338
  }
59646
60339
  buildAuthFingerprint(apiKey) {
59647
60340
  return crypto2.createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
@@ -59690,8 +60383,8 @@ function createFileManagerForModel(params) {
59690
60383
  sessionId: params.sessionId,
59691
60384
  platform: platform2,
59692
60385
  apiKey: params.apiKey,
59693
- baseDir: path20.join(params.configDir, "files"),
59694
- sessionsDir: path20.join(params.configDir, "sessions")
60386
+ baseDir: path21.join(params.configDir, "files"),
60387
+ sessionsDir: path21.join(params.configDir, "sessions")
59695
60388
  });
59696
60389
  switch (getFileManagerAdapterKind(platform2)) {
59697
60390
  case "kimi":
@@ -59726,15 +60419,15 @@ function isInlineMediaRequest(request) {
59726
60419
  return request.kind === "image" || request.kind === "video";
59727
60420
  }
59728
60421
  async function prepareInlineMediaContent(params) {
59729
- const stat2 = await fs20.stat(params.request.absolutePath);
60422
+ const stat2 = await fs21.stat(params.request.absolutePath);
59730
60423
  const limitMb = getCapabilityLimitMb(params.model, params.request.kind);
59731
60424
  const limitBytes = limitMb * 1024 * 1024;
59732
60425
  if (stat2.size > limitBytes) {
59733
60426
  throw new Error(
59734
- `${params.request.kind} understanding for ${params.model.provider}/${params.model.id} supports inline files up to ${limitMb}MB; ${path20.basename(params.request.displayPath)} is ${(stat2.size / 1024 / 1024).toFixed(1)}MB. Configure a model with a file upload adapter, or compress/trim the file before reading it.`
60427
+ `${params.request.kind} understanding for ${params.model.provider}/${params.model.id} supports inline files up to ${limitMb}MB; ${path21.basename(params.request.displayPath)} is ${(stat2.size / 1024 / 1024).toFixed(1)}MB. Configure a model with a file upload adapter, or compress/trim the file before reading it.`
59735
60428
  );
59736
60429
  }
59737
- const data = await fs20.readFile(params.request.absolutePath, "base64");
60430
+ const data = await fs21.readFile(params.request.absolutePath, "base64");
59738
60431
  const content = params.request.kind === "video" ? [{ type: "video", data, mimeType: params.request.mimeType }] : [{ type: "image", data, mimeType: params.request.mimeType }];
59739
60432
  return {
59740
60433
  content,
@@ -59754,7 +60447,7 @@ async function waitForReady(params) {
59754
60447
  if (status.status === "error" || status.status === "expired") {
59755
60448
  throw new Error(`Uploaded file is not ready: ${status.status}${status.error ? ` (${status.error})` : ""}`);
59756
60449
  }
59757
- await new Promise((resolve6) => setTimeout(resolve6, 1500));
60450
+ await new Promise((resolve7) => setTimeout(resolve7, 1500));
59758
60451
  }
59759
60452
  throw new Error("Timed out waiting for uploaded file to become ready");
59760
60453
  }
@@ -59770,7 +60463,7 @@ async function prepareFileContent(configDir, params) {
59770
60463
  });
59771
60464
  if (params.request.kind === "document") {
59772
60465
  const upload2 = await manager.upload(params.request.absolutePath, {
59773
- sourceName: path20.basename(params.request.displayPath),
60466
+ sourceName: path21.basename(params.request.displayPath),
59774
60467
  purpose: "file-extract"
59775
60468
  });
59776
60469
  await waitForReady({ manager, fileId: upload2.fileId, timeoutMs: 12e4, signal: params.request.signal });
@@ -59793,8 +60486,8 @@ async function prepareFileContent(configDir, params) {
59793
60486
  }
59794
60487
  };
59795
60488
  }
59796
- const upload = params.request.kind === "video" ? await manager.uploadVideo(params.request.absolutePath, { sourceName: path20.basename(params.request.displayPath), strategy: "compress" }) : await manager.upload(params.request.absolutePath, {
59797
- sourceName: path20.basename(params.request.displayPath),
60489
+ const upload = params.request.kind === "video" ? await manager.uploadVideo(params.request.absolutePath, { sourceName: path21.basename(params.request.displayPath), strategy: "compress" }) : await manager.upload(params.request.absolutePath, {
60490
+ sourceName: path21.basename(params.request.displayPath),
59798
60491
  purpose: params.request.kind === "image" ? "image" : "file-extract"
59799
60492
  });
59800
60493
  if (params.request.kind !== "video") {
@@ -59850,8 +60543,8 @@ function createIsolatedRegexSession(pattern, ignoreCase) {
59850
60543
  let initError;
59851
60544
  let readyResolve;
59852
60545
  let readyReject;
59853
- const ready = new Promise((resolve6, reject) => {
59854
- readyResolve = resolve6;
60546
+ const ready = new Promise((resolve7, reject) => {
60547
+ readyResolve = resolve7;
59855
60548
  readyReject = reject;
59856
60549
  });
59857
60550
  void ready.catch(() => void 0);
@@ -59873,7 +60566,7 @@ function createIsolatedRegexSession(pattern, ignoreCase) {
59873
60566
  if (initError) throw initError;
59874
60567
  if (signal?.aborted) throw new Error("Operation aborted");
59875
60568
  const id = nextId++;
59876
- return new Promise((resolve6, reject) => {
60569
+ return new Promise((resolve7, reject) => {
59877
60570
  let settled = false;
59878
60571
  const finish = (error, matches) => {
59879
60572
  if (settled) return;
@@ -59883,7 +60576,7 @@ function createIsolatedRegexSession(pattern, ignoreCase) {
59883
60576
  worker.off("message", onMessage);
59884
60577
  worker.off("error", onWorkerError);
59885
60578
  if (error) reject(error);
59886
- else resolve6(matches ?? []);
60579
+ else resolve7(matches ?? []);
59887
60580
  };
59888
60581
  const terminate = (error) => {
59889
60582
  disposed = true;
@@ -60213,22 +60906,22 @@ var ExecutionStateManager = class {
60213
60906
  };
60214
60907
 
60215
60908
  // src/session-trace.ts
60216
- import * as fs21 from "node:fs";
60217
- import * as path21 from "node:path";
60909
+ import * as fs22 from "node:fs";
60910
+ import * as path22 from "node:path";
60218
60911
  function saveTraceFileBestEffort(tracePath, content) {
60219
60912
  const tempPath = `${tracePath}.${process.pid}.${Date.now()}.tmp`;
60220
60913
  try {
60221
- fs21.writeFileSync(tempPath, content, "utf8");
60914
+ fs22.writeFileSync(tempPath, content, "utf8");
60222
60915
  try {
60223
- fs21.renameSync(tempPath, tracePath);
60916
+ fs22.renameSync(tempPath, tracePath);
60224
60917
  return;
60225
60918
  } catch {
60226
60919
  try {
60227
- fs21.copyFileSync(tempPath, tracePath);
60920
+ fs22.copyFileSync(tempPath, tracePath);
60228
60921
  } catch {
60229
60922
  } finally {
60230
60923
  try {
60231
- fs21.unlinkSync(tempPath);
60924
+ fs22.unlinkSync(tempPath);
60232
60925
  } catch {
60233
60926
  }
60234
60927
  }
@@ -60241,16 +60934,16 @@ var SessionTraceWriter = class extends SessionTraceSnapshotWriter {
60241
60934
  super({
60242
60935
  getSessionId: () => sessionManager.getActiveSessionId(),
60243
60936
  loadTrace: (sessionId) => {
60244
- const tracePath = path21.join(sessionsDir, `${sessionId}.trace.json`);
60245
- if (!fs21.existsSync(tracePath)) return null;
60937
+ const tracePath = path22.join(sessionsDir, `${sessionId}.trace.json`);
60938
+ if (!fs22.existsSync(tracePath)) return null;
60246
60939
  try {
60247
- return JSON.parse(fs21.readFileSync(tracePath, "utf8"));
60940
+ return JSON.parse(fs22.readFileSync(tracePath, "utf8"));
60248
60941
  } catch {
60249
60942
  return null;
60250
60943
  }
60251
60944
  },
60252
60945
  saveTrace: (trace) => {
60253
- const tracePath = path21.join(sessionsDir, `${trace.sessionId}.trace.json`);
60946
+ const tracePath = path22.join(sessionsDir, `${trace.sessionId}.trace.json`);
60254
60947
  saveTraceFileBestEffort(tracePath, JSON.stringify(trace, null, 2));
60255
60948
  }
60256
60949
  });
@@ -60757,7 +61450,7 @@ function normalizeSessionTitle(raw) {
60757
61450
  }
60758
61451
 
60759
61452
  // src/components/extension-selector.ts
60760
- import * as path22 from "node:path";
61453
+ import * as path23 from "node:path";
60761
61454
  var ExtensionSelectorComponent = class extends Container {
60762
61455
  selectList;
60763
61456
  constructor(title, options, onSelect, onCancel) {
@@ -60827,8 +61520,8 @@ var ExtensionStateSubmenu = class extends Container {
60827
61520
  }
60828
61521
  };
60829
61522
  function unidentifiedLabel(info) {
60830
- const parentName = path22.basename(path22.dirname(info.entryPath));
60831
- const fileName = path22.basename(info.entryPath);
61523
+ const parentName = path23.basename(path23.dirname(info.entryPath));
61524
+ const fileName = path23.basename(info.entryPath);
60832
61525
  return `(unidentified) ${parentName || fileName}`;
60833
61526
  }
60834
61527
  var ExtensionEnablementSelectorComponent = class extends Container {
@@ -61118,7 +61811,7 @@ async function handleCacheTest(host) {
61118
61811
  host.chatContainer.addChild(new Text(theme.fg("dim", ` ${summarizeUsage(result1.usage)}`), 1, 0));
61119
61812
  host.chatContainer.addChild(new Text(theme.fg("dim", " Waiting 2s before replaying identical request..."), 1, 0));
61120
61813
  host.ui.requestRender();
61121
- await new Promise((resolve6) => setTimeout(resolve6, 2e3));
61814
+ await new Promise((resolve7) => setTimeout(resolve7, 2e3));
61122
61815
  host.chatContainer.addChild(new Text(theme.fg("accent", "Request 2/2: replaying identical request..."), 1, 0));
61123
61816
  host.ui.requestRender();
61124
61817
  result2 = await completeSimple(model, context, options);
@@ -61299,10 +61992,10 @@ async function handleDebugPackageCommand(host) {
61299
61992
  }
61300
61993
 
61301
61994
  // src/commands/interactive-session-commands.ts
61302
- import * as os5 from "os";
61995
+ import * as os6 from "os";
61303
61996
 
61304
61997
  // src/components/session-selector.ts
61305
- import * as path23 from "node:path";
61998
+ import * as path24 from "node:path";
61306
61999
  function formatDate(timestamp) {
61307
62000
  const date = new Date(timestamp);
61308
62001
  const now = /* @__PURE__ */ new Date();
@@ -61333,7 +62026,7 @@ function getPathFlavor(value, peer) {
61333
62026
  return "posix";
61334
62027
  }
61335
62028
  function getPathApi(flavor) {
61336
- return flavor === "windows" ? path23.win32 : path23.posix;
62029
+ return flavor === "windows" ? path24.win32 : path24.posix;
61337
62030
  }
61338
62031
  function normalizeForComparison(value, flavor) {
61339
62032
  const pathApi = getPathApi(flavor);
@@ -61353,10 +62046,10 @@ function formatSessionCwdTag(sessionCwd, currentCwd, homeDir) {
61353
62046
  const normalizedSessionCwd = pathApi.normalize(sessionCwd);
61354
62047
  if (homeDir && getPathFlavor(homeDir, sessionCwd) === sessionFlavor) {
61355
62048
  const normalizedHome = pathApi.normalize(homeDir);
61356
- const relative2 = pathApi.relative(normalizedHome, normalizedSessionCwd);
61357
- const outsideHome = relative2 === ".." || relative2.startsWith(`..${pathApi.sep}`) || pathApi.isAbsolute(relative2);
62049
+ const relative3 = pathApi.relative(normalizedHome, normalizedSessionCwd);
62050
+ const outsideHome = relative3 === ".." || relative3.startsWith(`..${pathApi.sep}`) || pathApi.isAbsolute(relative3);
61358
62051
  if (!outsideHome) {
61359
- return relative2 ? `~${pathApi.sep}${relative2}` : "~";
62052
+ return relative3 ? `~${pathApi.sep}${relative3}` : "~";
61360
62053
  }
61361
62054
  }
61362
62055
  return normalizedSessionCwd;
@@ -62006,7 +62699,7 @@ var AssistantMessageComponent = class extends Container {
62006
62699
  };
62007
62700
 
62008
62701
  // src/components/tool-execution.ts
62009
- import * as os4 from "node:os";
62702
+ import * as os5 from "node:os";
62010
62703
 
62011
62704
  // ../../node_modules/diff/libesm/diff/base.js
62012
62705
  var Diff = class {
@@ -62108,16 +62801,16 @@ var Diff = class {
62108
62801
  }
62109
62802
  }
62110
62803
  }
62111
- addToPath(path31, added, removed, oldPosInc, options) {
62112
- const last = path31.lastComponent;
62804
+ addToPath(path33, added, removed, oldPosInc, options) {
62805
+ const last = path33.lastComponent;
62113
62806
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
62114
62807
  return {
62115
- oldPos: path31.oldPos + oldPosInc,
62808
+ oldPos: path33.oldPos + oldPosInc,
62116
62809
  lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
62117
62810
  };
62118
62811
  } else {
62119
62812
  return {
62120
- oldPos: path31.oldPos + oldPosInc,
62813
+ oldPos: path33.oldPos + oldPosInc,
62121
62814
  lastComponent: { count: 1, added, removed, previousComponent: last }
62122
62815
  };
62123
62816
  }
@@ -62565,13 +63258,13 @@ function formatSize2(bytes) {
62565
63258
  }
62566
63259
  var DEFAULT_MAX_BYTES = 102400;
62567
63260
  var DEFAULT_MAX_LINES = 100;
62568
- function shortenPath(path31) {
62569
- if (typeof path31 !== "string") return "";
62570
- const home = os4.homedir();
62571
- if (path31.startsWith(home)) {
62572
- return `~${path31.slice(home.length)}`;
63261
+ function shortenPath(path33) {
63262
+ if (typeof path33 !== "string") return "";
63263
+ const home = os5.homedir();
63264
+ if (path33.startsWith(home)) {
63265
+ return `~${path33.slice(home.length)}`;
62573
63266
  }
62574
- return path31;
63267
+ return path33;
62575
63268
  }
62576
63269
  function replaceTabs(text) {
62577
63270
  return text.replace(/\t/g, " ");
@@ -62597,11 +63290,13 @@ var ToolExecutionComponent = class extends Container {
62597
63290
  isPartial = true;
62598
63291
  ui;
62599
63292
  result;
63293
+ renderHint;
62600
63294
  constructor(toolName, args, options = {}, ui) {
62601
63295
  super();
62602
63296
  this.toolName = toolName;
62603
63297
  this.args = args;
62604
63298
  this.expanded = options.expanded ?? false;
63299
+ this.renderHint = options.renderHint;
62605
63300
  this.ui = ui;
62606
63301
  this.addChild(new Spacer(1));
62607
63302
  this.contentBox = new Box(1, 1, (text) => theme.bg("toolPendingBg", text));
@@ -62652,14 +63347,14 @@ ${renderManageTodoListResult(this.result, { expanded: this.expanded }, theme)}`;
62652
63347
  }
62653
63348
  } else if (this.toolName === "read_file") {
62654
63349
  const rawPath = str(this.args?.file_path ?? this.args?.path);
62655
- const path31 = rawPath !== null ? shortenPath(rawPath) : null;
63350
+ const path33 = rawPath !== null ? shortenPath(rawPath) : null;
62656
63351
  const offset = typeof this.args?.offset === "number" ? this.args.offset : void 0;
62657
63352
  const limit = typeof this.args?.limit === "number" ? this.args.limit : void 0;
62658
63353
  let pathDisplay;
62659
- if (path31 === null) {
63354
+ if (path33 === null) {
62660
63355
  pathDisplay = invalidArg;
62661
- } else if (path31) {
62662
- pathDisplay = theme.fg("accent", path31);
63356
+ } else if (path33) {
63357
+ pathDisplay = theme.fg("accent", path33);
62663
63358
  } else {
62664
63359
  const partialPath = partialStr(this.args?.file_path ?? this.args?.path);
62665
63360
  pathDisplay = theme.fg("toolOutput", partialPath);
@@ -62706,12 +63401,12 @@ ${renderManageTodoListResult(this.result, { expanded: this.expanded }, theme)}`;
62706
63401
  } else if (this.toolName === "write_file") {
62707
63402
  const rawPath = str(this.args?.file_path ?? this.args?.path);
62708
63403
  const fileContent = str(this.args?.content);
62709
- const path31 = rawPath !== null ? shortenPath(rawPath) : null;
63404
+ const path33 = rawPath !== null ? shortenPath(rawPath) : null;
62710
63405
  let pathDisplay;
62711
- if (path31 === null) {
63406
+ if (path33 === null) {
62712
63407
  pathDisplay = invalidArg;
62713
- } else if (path31) {
62714
- pathDisplay = theme.fg("accent", path31);
63408
+ } else if (path33) {
63409
+ pathDisplay = theme.fg("accent", path33);
62715
63410
  } else {
62716
63411
  const partialPath = partialStr(this.args?.file_path ?? this.args?.path);
62717
63412
  pathDisplay = theme.fg("toolOutput", partialPath);
@@ -62748,12 +63443,12 @@ ${theme.fg("error", errorText)}`;
62748
63443
  }
62749
63444
  } else if (this.toolName === "edit_file") {
62750
63445
  const rawPath = str(this.args?.file_path ?? this.args?.path);
62751
- const path31 = rawPath !== null ? shortenPath(rawPath) : null;
63446
+ const path33 = rawPath !== null ? shortenPath(rawPath) : null;
62752
63447
  let pathDisplay;
62753
- if (path31 === null) {
63448
+ if (path33 === null) {
62754
63449
  pathDisplay = invalidArg;
62755
- } else if (path31) {
62756
- pathDisplay = theme.fg("accent", path31);
63450
+ } else if (path33) {
63451
+ pathDisplay = theme.fg("accent", path33);
62757
63452
  } else {
62758
63453
  const partialPath = partialStr(this.args?.file_path ?? this.args?.path);
62759
63454
  pathDisplay = theme.fg("toolOutput", partialPath);
@@ -62778,7 +63473,7 @@ ${theme.fg("error", errorText)}`;
62778
63473
  const diff = this.result.details.diff;
62779
63474
  const firstChangedLine = this.result.details.firstChangedLine;
62780
63475
  if (firstChangedLine) {
62781
- text = `${theme.fg("toolTitle", theme.bold("edit_file"))} ${theme.fg("accent", path31 || rawPath)}${theme.fg("warning", `:${firstChangedLine}`)}`;
63476
+ text = `${theme.fg("toolTitle", theme.bold("edit_file"))} ${theme.fg("accent", path33 || rawPath)}${theme.fg("warning", `:${firstChangedLine}`)}`;
62782
63477
  }
62783
63478
  const lines = diff.split("\n");
62784
63479
  const maxLines = this.expanded ? lines.length : 15;
@@ -62798,7 +63493,7 @@ ${theme.fg("error", errorText)}`;
62798
63493
  const diff = generateEditDiff(oldText, newText, rawPath);
62799
63494
  const firstChangedLine = findFirstChangedLine(oldText, newText);
62800
63495
  if (firstChangedLine) {
62801
- text = `${theme.fg("toolTitle", theme.bold("edit_file"))} ${theme.fg("accent", path31 || rawPath)}${theme.fg("warning", `:${firstChangedLine}`)}`;
63496
+ text = `${theme.fg("toolTitle", theme.bold("edit_file"))} ${theme.fg("accent", path33 || rawPath)}${theme.fg("warning", `:${firstChangedLine}`)}`;
62802
63497
  }
62803
63498
  const lines = diff.split("\n");
62804
63499
  const maxLines = this.expanded ? lines.length : 15;
@@ -62817,9 +63512,9 @@ ${theme.fg("error", errorText)}`;
62817
63512
  }
62818
63513
  } else if (this.toolName === "list_dir") {
62819
63514
  const rawPath = str(this.args?.path);
62820
- const path31 = rawPath !== null ? shortenPath(rawPath || ".") : null;
63515
+ const path33 = rawPath !== null ? shortenPath(rawPath || ".") : null;
62821
63516
  const limit = this.args?.limit;
62822
- text = `${theme.fg("toolTitle", theme.bold("list_dir"))} ${path31 === null ? invalidArg : theme.fg("accent", path31)}`;
63517
+ text = `${theme.fg("toolTitle", theme.bold("list_dir"))} ${path33 === null ? invalidArg : theme.fg("accent", path33)}`;
62823
63518
  if (limit !== void 0) {
62824
63519
  text += theme.fg("toolOutput", ` (limit ${limit})`);
62825
63520
  }
@@ -62855,9 +63550,9 @@ ${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
62855
63550
  } else if (this.toolName === "find_files") {
62856
63551
  const pattern = str(this.args?.pattern);
62857
63552
  const rawPath = str(this.args?.path);
62858
- const path31 = rawPath !== null ? shortenPath(rawPath || ".") : null;
63553
+ const path33 = rawPath !== null ? shortenPath(rawPath || ".") : null;
62859
63554
  const limit = this.args?.limit;
62860
- text = theme.fg("toolTitle", theme.bold("find_files")) + " " + (pattern === null ? invalidArg : theme.fg("accent", pattern || "")) + theme.fg("toolOutput", ` in ${path31 === null ? invalidArg : path31}`);
63555
+ text = theme.fg("toolTitle", theme.bold("find_files")) + " " + (pattern === null ? invalidArg : theme.fg("accent", pattern || "")) + theme.fg("toolOutput", ` in ${path33 === null ? invalidArg : path33}`);
62861
63556
  if (limit !== void 0) {
62862
63557
  text += theme.fg("toolOutput", ` (limit ${limit})`);
62863
63558
  }
@@ -62893,10 +63588,10 @@ ${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
62893
63588
  } else if (this.toolName === "grep_text") {
62894
63589
  const pattern = str(this.args?.pattern);
62895
63590
  const rawPath = str(this.args?.path);
62896
- const path31 = rawPath !== null ? shortenPath(rawPath || ".") : null;
63591
+ const path33 = rawPath !== null ? shortenPath(rawPath || ".") : null;
62897
63592
  const glob = str(this.args?.glob);
62898
63593
  const limit = this.args?.limit;
62899
- text = theme.fg("toolTitle", theme.bold("grep_text")) + " " + (pattern === null ? invalidArg : theme.fg("accent", `/${pattern || ""}/`)) + theme.fg("toolOutput", ` in ${path31 === null ? invalidArg : path31}`);
63594
+ text = theme.fg("toolTitle", theme.bold("grep_text")) + " " + (pattern === null ? invalidArg : theme.fg("accent", `/${pattern || ""}/`)) + theme.fg("toolOutput", ` in ${path33 === null ? invalidArg : path33}`);
62900
63595
  if (glob) {
62901
63596
  text += theme.fg("toolOutput", ` (${glob})`);
62902
63597
  }
@@ -62976,33 +63671,97 @@ ${theme.fg("warning", `[${warnings.join(". ")}]`)}`;
62976
63671
  }
62977
63672
  }
62978
63673
  } else {
62979
- text = theme.fg("toolTitle", theme.bold(this.toolName));
62980
- if (this.args && Object.keys(this.args).length > 0) {
62981
- const content = JSON.stringify(this.args, null, 2);
62982
- text += `
62983
-
62984
- ${theme.fg("toolOutput", content)}`;
63674
+ const hinted = this.formatWithRenderHint();
63675
+ if (hinted !== null) {
63676
+ text = hinted;
63677
+ } else {
63678
+ text = this.formatGenericTool();
62985
63679
  }
62986
- if (this.result) {
62987
- const output = this.getTextOutput();
62988
- if (output) {
62989
- const lines = output.split("\n");
62990
- const maxLines = this.expanded ? lines.length : 20;
62991
- const displayLines = lines.slice(0, maxLines);
62992
- const remaining = lines.length - maxLines;
62993
- text += "\n\n" + displayLines.map((line) => theme.fg("toolOutput", line)).join("\n");
62994
- if (remaining > 0) {
62995
- text += theme.fg("muted", `
63680
+ }
63681
+ return text;
63682
+ }
63683
+ /** Render via the extension renderHint; null means fall back. */
63684
+ formatWithRenderHint() {
63685
+ const hint = this.renderHint;
63686
+ if (!hint) return null;
63687
+ try {
63688
+ let text = theme.fg("toolTitle", theme.bold(this.toolName));
63689
+ let produced = false;
63690
+ if (hint.summary) {
63691
+ const summary = hint.summary(this.args);
63692
+ if (typeof summary === "string" && summary.trim()) {
63693
+ text += ` ${theme.fg("accent", summary.trim())}`;
63694
+ produced = true;
63695
+ }
63696
+ }
63697
+ if (this.result && hint.sections) {
63698
+ const sections = hint.sections(
63699
+ this.result,
63700
+ { expanded: this.expanded }
63701
+ );
63702
+ if (Array.isArray(sections)) {
63703
+ for (const section of sections) {
63704
+ if (!section || typeof section.text !== "string" || !section.text) continue;
63705
+ produced = true;
63706
+ if (section.title) {
63707
+ text += `
63708
+
63709
+ ${theme.fg("toolTitle", section.title)}`;
63710
+ }
63711
+ const lines = section.text.split("\n");
63712
+ const maxLines = this.expanded ? lines.length : 20;
63713
+ const displayLines = lines.slice(0, maxLines);
63714
+ const remaining = lines.length - maxLines;
63715
+ text += "\n" + displayLines.map((line) => theme.fg("toolOutput", line)).join("\n");
63716
+ if (remaining > 0) {
63717
+ text += theme.fg("muted", `
62996
63718
  ... (${remaining} more lines, press Ctrl+O to expand)`);
63719
+ }
62997
63720
  }
62998
63721
  }
62999
- if (this.result.isError) {
63000
- const errorOutput = this.getTextOutput() || "Unknown error";
63001
- text += `
63722
+ }
63723
+ if (!produced) return null;
63724
+ if (this.result?.isError) {
63725
+ const errorOutput = this.getTextOutput() || "Unknown error";
63726
+ text += `
63002
63727
 
63003
63728
  ${theme.fg("error", "Error: " + errorOutput)}`;
63729
+ }
63730
+ return text;
63731
+ } catch (error) {
63732
+ if (process.env.PIE_VERBOSE) {
63733
+ console.warn(`[ToolExecution] renderHint for ${this.toolName} failed, falling back:`, error);
63734
+ }
63735
+ return null;
63736
+ }
63737
+ }
63738
+ formatGenericTool() {
63739
+ let text = theme.fg("toolTitle", theme.bold(this.toolName));
63740
+ if (this.args && Object.keys(this.args).length > 0) {
63741
+ const content = JSON.stringify(this.args, null, 2);
63742
+ text += `
63743
+
63744
+ ${theme.fg("toolOutput", content)}`;
63745
+ }
63746
+ if (this.result) {
63747
+ const output = this.getTextOutput();
63748
+ if (output) {
63749
+ const lines = output.split("\n");
63750
+ const maxLines = this.expanded ? lines.length : 20;
63751
+ const displayLines = lines.slice(0, maxLines);
63752
+ const remaining = lines.length - maxLines;
63753
+ text += "\n\n" + displayLines.map((line) => theme.fg("toolOutput", line)).join("\n");
63754
+ if (remaining > 0) {
63755
+ text += theme.fg("muted", `
63756
+ ... (${remaining} more lines, press Ctrl+O to expand)`);
63004
63757
  }
63005
63758
  }
63759
+ if (this.result.isError) {
63760
+ const errorOutput = this.getTextOutput() || "Unknown error";
63761
+ text += `
63762
+
63763
+ ${theme.fg("error", "Error: " + errorOutput)}`;
63764
+ }
63006
63765
  }
63007
63766
  return text;
63008
63767
  }
@@ -63047,7 +63806,7 @@ async function handleResumeCommand(host) {
63047
63806
  }
63048
63807
  }, {
63049
63808
  currentCwd: host.options.cwd,
63050
- homeDir: os5.homedir(),
63809
+ homeDir: os6.homedir(),
63051
63810
  refreshMetadata: (sessionId) => host.sessionManager.refreshDerivedName(sessionId),
63052
63811
  requestRender: () => host.ui.requestRender(),
63053
63812
  totalCount
@@ -63056,6 +63815,15 @@ async function handleResumeCommand(host) {
63056
63815
  });
63057
63816
  }
63058
63817
  async function loadSession(host, sessionId) {
63818
+ const beforeSwitch = await host.extensionRunner?.emitSessionBeforeSwitch({
63819
+ type: "session:before_switch",
63820
+ reason: "resume",
63821
+ targetSessionId: sessionId
63822
+ });
63823
+ if (beforeSwitch?.cancel) {
63824
+ host.showStatus("Session switch cancelled by an extension");
63825
+ return;
63826
+ }
63059
63827
  try {
63060
63828
  const success = await host.sessionManager.loadSession(sessionId);
63061
63829
  if (!success) {
@@ -63350,8 +64118,8 @@ async function forkAtEntry(host, entryId) {
63350
64118
  host.showError("Failed to branch at entry");
63351
64119
  return;
63352
64120
  }
63353
- const path31 = host.sessionManager.getPathToEntry(entryId);
63354
- const pathNames = path31.map((e) => e.message?.role || "entry").join(" \u2192 ");
64121
+ const path33 = host.sessionManager.getPathToEntry(entryId);
64122
+ const pathNames = path33.map((e) => e.message?.role || "entry").join(" \u2192 ");
63355
64123
  const messages = host.sessionManager.getMessages();
63356
64124
  host.syncAgentFromActiveSession(messages);
63357
64125
  await host.syncSessionScopedState(messages);
@@ -63364,7 +64132,7 @@ async function forkAtEntry(host, entryId) {
63364
64132
  host.updatePendingMessagesDisplay();
63365
64133
  host.renderMessageHistory(messages);
63366
64134
  await host.sessionManager.save();
63367
- host.showStatus(`Branched at: ${pathNames} (${path31.length} messages). Continue to add to this branch.`);
64135
+ host.showStatus(`Branched at: ${pathNames} (${path33.length} messages). Continue to add to this branch.`);
63368
64136
  } catch (error) {
63369
64137
  host.showError(
63370
64138
  `Failed to branch: ${error instanceof Error ? error.message : String(error)}`
@@ -63392,10 +64160,10 @@ async function navigateToEntry(host, entryId) {
63392
64160
  host.sessionManager.save().catch((err) => {
63393
64161
  console.error("[Session] Save after navigation failed:", err);
63394
64162
  });
63395
- const path31 = host.sessionManager.getPathToEntry(entryId);
64163
+ const path33 = host.sessionManager.getPathToEntry(entryId);
63396
64164
  const entry = host.sessionManager.getEntry(entryId);
63397
64165
  const role = entry?.message?.role || "entry";
63398
- host.showStatus(`Switched to branch: ${role} (${path31.length} messages in path)`);
64166
+ host.showStatus(`Switched to branch: ${role} (${path33.length} messages in path)`);
63399
64167
  } catch (error) {
63400
64168
  host.showError(
63401
64169
  `Failed to navigate: ${error instanceof Error ? error.message : String(error)}`
@@ -63445,9 +64213,9 @@ async function handleForkCommand(host) {
63445
64213
  }
63446
64214
 
63447
64215
  // src/commands/interactive-skill-commands.ts
63448
- import * as fs22 from "fs";
63449
- import * as os6 from "os";
63450
- import * as path24 from "path";
64216
+ import * as fs23 from "fs";
64217
+ import * as os7 from "os";
64218
+ import * as path25 from "path";
63451
64219
 
63452
64220
  // src/commands/subagent-command.ts
63453
64221
  async function handleSubagentCommand(host, args) {
@@ -63632,7 +64400,7 @@ function expandSkillCommand(host, text) {
63632
64400
  const skill = host.skills.find((s) => s.name === skillName);
63633
64401
  if (!skill) return text;
63634
64402
  try {
63635
- const content = fs22.readFileSync(skill.filePath, "utf-8");
64403
+ const content = fs23.readFileSync(skill.filePath, "utf-8");
63636
64404
  const body = host.stripFrontmatter(content).trim();
63637
64405
  const skillBlock = `<skill name="${skill.name}" location="${skill.filePath}">
63638
64406
  References are relative to ${skill.baseDir}.
@@ -63698,17 +64466,17 @@ async function handleSkillsCreate(host, args) {
63698
64466
  if (!description) {
63699
64467
  description = `Skill for ${name}`;
63700
64468
  }
63701
- const skillsDir = path24.join(os6.homedir(), ".pie", "skills");
63702
- const skillDir = path24.join(skillsDir, name);
63703
- const skillFile = path24.join(skillDir, "SKILL.md");
64469
+ const skillsDir = path25.join(os7.homedir(), ".pie", "skills");
64470
+ const skillDir = path25.join(skillsDir, name);
64471
+ const skillFile = path25.join(skillDir, "SKILL.md");
63704
64472
  console.error(`[SkillsCreate] Creating at: ${skillFile}`);
63705
- if (fs22.existsSync(skillDir)) {
64473
+ if (fs23.existsSync(skillDir)) {
63706
64474
  host.showError(`Skill "${name}" already exists at ${skillDir}`);
63707
64475
  return;
63708
64476
  }
63709
- fs22.mkdirSync(skillDir, { recursive: true });
64477
+ fs23.mkdirSync(skillDir, { recursive: true });
63710
64478
  const template = createSkillTemplate(name, description);
63711
- fs22.writeFileSync(skillFile, template, "utf-8");
64479
+ fs23.writeFileSync(skillFile, template, "utf-8");
63712
64480
  console.error(`[SkillsCreate] Successfully created skill`);
63713
64481
  host.chatContainer.addChild(new Spacer(1));
63714
64482
  host.chatContainer.addChild(new Text(theme.fg("accent", `\u2713 Created skill: ${name}`), 1, 0));
@@ -63744,10 +64512,10 @@ async function handleSkillsReload(host) {
63744
64512
  host.chatContainer.addChild(new Text(theme.fg("accent", "Reloading skills..."), 1, 0));
63745
64513
  host.ui.requestRender();
63746
64514
  try {
63747
- const builtinDir = path24.join(getBuiltinDir(), "skills");
63748
- const userDir = path24.join(os6.homedir(), ".pie", "skills");
63749
- const projectDir = path24.join(process.cwd(), ".pie", "skills");
63750
- const result = loadSkills({ builtinDir, userDir, projectDir });
64515
+ const builtinDir = path25.join(getBuiltinDir(), "skills");
64516
+ const userDir = path25.join(os7.homedir(), ".pie", "skills");
64517
+ const projectDir = path25.join(process.cwd(), ".pie", "skills");
64518
+ const result = loadSkills({ dirs: buildSkillDirs({ builtinDir, userDir, projectDir, cwd: process.cwd() }) });
63751
64519
  host.skills = result.skills;
63752
64520
  host.skillsSection = formatSkillsForPrompt(host.skills);
63753
64521
  host.applyRuntimeToolMode(host.allTools.length > 0 ? host.allTools : host.agent.state.tools);
@@ -63762,8 +64530,8 @@ async function handleSkillsReload(host) {
63762
64530
  }
63763
64531
  }
63764
64532
  const builtinCount = host.skills.filter((s) => s.source === "builtin").length;
63765
- const userCount = host.skills.filter((s) => s.source === "user").length;
63766
- const projectCount = host.skills.filter((s) => s.source === "project").length;
64533
+ const userCount = host.skills.filter((s) => s.source === "user" || s.source === "user-agents").length;
64534
+ const projectCount = host.skills.filter((s) => s.source === "project" || s.source === "project-agents").length;
63767
64535
  host.chatContainer.addChild(new Text(theme.fg("accent", `\u2713 Reloaded ${host.skills.length} skill(s)`), 1, 0));
63768
64536
  host.chatContainer.addChild(new Text(theme.fg("dim", ` Builtin: ${builtinCount}, User: ${userCount}, Project: ${projectCount}`), 1, 0));
63769
64537
  if (result.diagnostics.length > 0) {
@@ -64134,6 +64902,14 @@ function showThinkingLevelSelector(host) {
64134
64902
  });
64135
64903
  }
64136
64904
  async function handleClearCommand(host) {
64905
+ const beforeSwitch = await host.extensionRunner?.emitSessionBeforeSwitch({
64906
+ type: "session:before_switch",
64907
+ reason: "new"
64908
+ });
64909
+ if (beforeSwitch?.cancel) {
64910
+ host.showStatus("Session switch cancelled by an extension");
64911
+ return;
64912
+ }
64137
64913
  host.setControllerBusy(false);
64138
64914
  host.setWaitingForUser(false);
64139
64915
  host.executionState.clear();
@@ -64432,7 +65208,21 @@ async function handleCompactCommand(host) {
64432
65208
  apiKey: host.options.apiKey,
64433
65209
  systemPrompt: host.agent.state.systemPrompt
64434
65210
  };
64435
- const summary = await createCompactionSummary(existingMessages, compactOptions);
65211
+ let summary;
65212
+ const beforeCompact = await host.extensionRunner?.emitSessionBeforeCompact({
65213
+ type: "session:before_compact",
65214
+ reason: "manual",
65215
+ messageCount: existingMessages.length
65216
+ });
65217
+ if (beforeCompact?.cancel) {
65218
+ host.showStatus("Compaction cancelled by an extension");
65219
+ return;
65220
+ }
65221
+ if (beforeCompact?.summary) {
65222
+ summary = beforeCompact.summary;
65223
+ } else {
65224
+ summary = await createCompactionSummary(existingMessages, compactOptions);
65225
+ }
64436
65226
  const tokensBefore = calculateTotalTokens(existingMessages);
64437
65227
  host.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore);
64438
65228
  const compactedMessages = host.sessionManager.getMessages();
@@ -64495,6 +65285,31 @@ function formatFooterLifecycleEvent(event) {
64495
65285
  }
64496
65286
  }
64497
65287
 
65288
+ // src/runtime/terminal-bell.ts
65289
+ var BEL = "\x07";
65290
+ var DEFAULT_MIN_INTERVAL_MS = 500;
65291
+ var lastRingAtMs = 0;
65292
+ function ringTerminalBell(options = {}) {
65293
+ const {
65294
+ enabled = true,
65295
+ stdout = process.stdout,
65296
+ now = Date.now,
65297
+ minIntervalMs = DEFAULT_MIN_INTERVAL_MS
65298
+ } = options;
65299
+ if (!enabled) return false;
65300
+ if (!stdout.isTTY) return false;
65301
+ const atMs = now();
65302
+ if (atMs - lastRingAtMs < minIntervalMs) return false;
65303
+ lastRingAtMs = atMs;
65304
+ stdout.write(BEL);
65305
+ return true;
65306
+ }
65307
+ function ringTurnSettledBell(host) {
65308
+ if ((host.agentSessionController?.pendingMessageCount ?? 0) > 0) return;
65309
+ if (host.executionState.read().awaitingContinuation) return;
65310
+ host.ringAttentionBell?.();
65311
+ }
65312
+
64498
65313
  // src/runtime/interactive-runtime-events.ts
64499
65314
  function bindInteractiveRuntimeEvents(host) {
64500
65315
  host.agent.subscribe((event) => {
@@ -64665,6 +65480,7 @@ function handleSemanticAgentEvent(host, event) {
64665
65480
  host.appendWarningSummary(outcome.terminalSummary);
64666
65481
  }
64667
65482
  host.sessionTrace.completeTurn(event.message, false);
65483
+ ringTurnSettledBell(host);
64668
65484
  host._semanticTurnIndex++;
64669
65485
  break;
64670
65486
  }
@@ -64699,6 +65515,7 @@ function handleSemanticAgentEvent(host, event) {
64699
65515
  host.appendWarningSummary(outcome.terminalSummary);
64700
65516
  }
64701
65517
  host.sessionTrace.completeTurn(event.message, true);
65518
+ host.ringAttentionBell?.();
64702
65519
  host._semanticTurnIndex++;
64703
65520
  break;
64704
65521
  }
@@ -64747,6 +65564,7 @@ function handleMessageStart(host, event) {
64747
65564
  host.safeRequestRender();
64748
65565
  }
64749
65566
  function handleMessageUpdate(host, event) {
65567
+ host.noteContinuationActivity?.();
64750
65568
  if (!host.streamingComponent || event.message?.role !== "assistant") return;
64751
65569
  host.sessionTrace.updateAssistantMessage(event.message);
64752
65570
  if (host.isComposing) {
@@ -64783,7 +65601,7 @@ function handleMessageUpdate(host, event) {
64783
65601
  const toolComponent = new ToolExecutionComponent(
64784
65602
  toolCall.name,
64785
65603
  toolCall.arguments,
64786
- { expanded: host.toolOutputExpanded },
65604
+ { expanded: host.toolOutputExpanded, renderHint: findToolRenderHint(host, toolCall.name) },
64787
65605
  host.ui
64788
65606
  );
64789
65607
  host.pendingTools.set(toolId, toolComponent);
@@ -64819,6 +65637,15 @@ function handleMessageUpdate(host, event) {
64819
65637
  });
64820
65638
  host.safeRequestRender();
64821
65639
  }
65640
+ function findToolRenderHint(host, toolName) {
65641
+ const tools = host.agent?.state?.tools ?? [];
65642
+ for (const tool of tools) {
65643
+ if (tool.name !== toolName) continue;
65644
+ const hint = tool.renderHint;
65645
+ if (hint) return hint;
65646
+ }
65647
+ return void 0;
65648
+ }
64822
65649
  function handleToolStart(host, event) {
64823
65650
  host.logDebug("tool:start", { toolName: event.toolName, toolCallId: event.toolCallId });
64824
65651
  host.sessionTrace.startTool(event.toolCallId, event.toolName, event.args);
@@ -64830,7 +65657,7 @@ function handleToolStart(host, event) {
64830
65657
  const newToolComponent = new ToolExecutionComponent(
64831
65658
  event.toolName,
64832
65659
  event.args,
64833
- { expanded: host.toolOutputExpanded },
65660
+ { expanded: host.toolOutputExpanded, renderHint: findToolRenderHint(host, event.toolName) },
64834
65661
  host.ui
64835
65662
  );
64836
65663
  host.pendingTools.set(event.toolCallId, newToolComponent);
@@ -64847,6 +65674,7 @@ function handleToolStart(host, event) {
64847
65674
  host.safeRequestRender();
64848
65675
  }
64849
65676
  function handleToolUpdate(host, event) {
65677
+ host.noteContinuationActivity?.();
64850
65678
  if (event.toolCallId) {
64851
65679
  const toolComponent = host.pendingTools.get(event.toolCallId);
64852
65680
  if (toolComponent) {
@@ -64923,6 +65751,20 @@ function setupInteractiveEditorSubmitHandler(host) {
64923
65751
  host.editor.onSubmit = async (text) => {
64924
65752
  text = text.trim();
64925
65753
  if (!text) return;
65754
+ if (host.extensionRunner) {
65755
+ const decision = await host.extensionRunner.emitTerminalInput({ type: "terminal:input", text });
65756
+ if (decision.action === "consume") {
65757
+ host.editor.setText("");
65758
+ return;
65759
+ }
65760
+ if (decision.action === "transform") {
65761
+ text = decision.text.trim();
65762
+ if (!text) {
65763
+ host.editor.setText("");
65764
+ return;
65765
+ }
65766
+ }
65767
+ }
64926
65768
  if (text.startsWith("/skills/skill:")) {
64927
65769
  const skillText = text.slice("/skills/".length);
64928
65770
  const expanded = host.expandSkillCommand(skillText);
@@ -65185,7 +66027,7 @@ function setupInteractiveEditorSubmitHandler(host) {
65185
66027
  host.chatContainer.addChild(userMsg);
65186
66028
  host.editor.setText("");
65187
66029
  host.ui.requestRender();
65188
- await new Promise((resolve6) => setImmediate(resolve6));
66030
+ await new Promise((resolve7) => setImmediate(resolve7));
65189
66031
  let finalContent = messageContent;
65190
66032
  const reminderPrefix = host.executionReminder;
65191
66033
  const combinedPrefix = [reminderPrefix].filter((value) => Boolean(value)).join("");
@@ -65221,8 +66063,8 @@ function setupInteractiveEditorSubmitHandler(host) {
65221
66063
  }
65222
66064
 
65223
66065
  // src/input/interactive-file-pipeline.ts
65224
- import * as fs25 from "fs";
65225
- import * as path27 from "path";
66066
+ import * as fs26 from "fs";
66067
+ import * as path28 from "path";
65226
66068
 
65227
66069
  // src/components/file-picker.ts
65228
66070
  var FilePickerComponent = class extends Container {
@@ -65443,8 +66285,8 @@ var FilePickerComponent = class extends Container {
65443
66285
  };
65444
66286
 
65445
66287
  // src/files/commands/file-commands.ts
65446
- import * as fs23 from "node:fs";
65447
- import * as path25 from "node:path";
66288
+ import * as fs24 from "node:fs";
66289
+ import * as path26 from "node:path";
65448
66290
  var FileCommands = class {
65449
66291
  context;
65450
66292
  constructor(context) {
@@ -65460,11 +66302,11 @@ var FileCommands = class {
65460
66302
  return;
65461
66303
  }
65462
66304
  const filePath = resolvePath2(parsed.path);
65463
- if (!fs23.existsSync(filePath)) {
66305
+ if (!fs24.existsSync(filePath)) {
65464
66306
  this.context.showError(`File not found: ${parsed.path}`);
65465
66307
  return;
65466
66308
  }
65467
- this.context.showStatus(`Uploading ${path25.basename(filePath)}...`);
66309
+ this.context.showStatus(`Uploading ${path26.basename(filePath)}...`);
65468
66310
  try {
65469
66311
  const result = await this.context.fileManager.upload(filePath, {
65470
66312
  purpose: parsed.purpose,
@@ -65499,7 +66341,7 @@ var FileCommands = class {
65499
66341
  return;
65500
66342
  }
65501
66343
  const resolvedPath = resolvePath2(filePath);
65502
- if (!fs23.existsSync(resolvedPath)) {
66344
+ if (!fs24.existsSync(resolvedPath)) {
65503
66345
  this.context.showError(`File not found: ${filePath}`);
65504
66346
  return;
65505
66347
  }
@@ -65530,7 +66372,7 @@ var FileCommands = class {
65530
66372
  return;
65531
66373
  }
65532
66374
  const resolvedPath = resolvePath2(parsed.path);
65533
- if (!fs23.existsSync(resolvedPath)) {
66375
+ if (!fs24.existsSync(resolvedPath)) {
65534
66376
  this.context.showError(`File not found: ${parsed.path}`);
65535
66377
  return;
65536
66378
  }
@@ -66008,8 +66850,8 @@ var FileStatusMonitor = class {
66008
66850
  };
66009
66851
 
66010
66852
  // src/files/mention/mention-handler.ts
66011
- import * as fs24 from "node:fs";
66012
- import * as path26 from "node:path";
66853
+ import * as fs25 from "node:fs";
66854
+ import * as path27 from "node:path";
66013
66855
  var MentionHandler = class {
66014
66856
  fileManager;
66015
66857
  onShowPicker;
@@ -66064,7 +66906,7 @@ var MentionHandler = class {
66064
66906
  navigateBrowseParent() {
66065
66907
  const currentDir = this.getBrowserDirectory();
66066
66908
  if (!currentDir) return;
66067
- const parentDir = path26.dirname(currentDir);
66909
+ const parentDir = path27.dirname(currentDir);
66068
66910
  if (parentDir === currentDir) return;
66069
66911
  this.browsingDirectory = parentDir;
66070
66912
  this.showMentionPicker("");
@@ -66084,7 +66926,7 @@ var MentionHandler = class {
66084
66926
  if (option.type === "parent") {
66085
66927
  const currentDir = this.getBrowserDirectory();
66086
66928
  if (currentDir) {
66087
- this.browsingDirectory = path26.dirname(currentDir);
66929
+ this.browsingDirectory = path27.dirname(currentDir);
66088
66930
  this.showMentionPicker("");
66089
66931
  this.isPickerVisible = true;
66090
66932
  }
@@ -66220,7 +67062,7 @@ var MentionHandler = class {
66220
67062
  id: part.content,
66221
67063
  name: part.name || fileInfo.name,
66222
67064
  mimeType: fileInfo.mimeType,
66223
- ext: path26.extname(part.name || fileInfo.name).toLowerCase(),
67065
+ ext: path27.extname(part.name || fileInfo.name).toLowerCase(),
66224
67066
  size: fileInfo.size,
66225
67067
  source: fileInfo.platforms.length > 0 ? "session" : "local",
66226
67068
  fileId: part.content,
@@ -66378,11 +67220,11 @@ var MentionHandler = class {
66378
67220
  }
66379
67221
  if (this.isPath(query)) {
66380
67222
  const resolvedPath = resolvePath2(query);
66381
- if (fs24.existsSync(resolvedPath)) {
66382
- const stats = fs24.statSync(resolvedPath);
67223
+ if (fs25.existsSync(resolvedPath)) {
67224
+ const stats = fs25.statSync(resolvedPath);
66383
67225
  if (stats.isFile()) {
66384
67226
  const type = FileCache.getFileTypeFromName(resolvedPath);
66385
- const name = path26.basename(resolvedPath);
67227
+ const name = path27.basename(resolvedPath);
66386
67228
  if (!seenNames.has(name.toLowerCase())) {
66387
67229
  options.push({
66388
67230
  type: "upload",
@@ -66403,16 +67245,16 @@ var MentionHandler = class {
66403
67245
  return void 0;
66404
67246
  }
66405
67247
  const queryPath = this.isPath(query) ? resolvePath2(query) : void 0;
66406
- const filter = queryPath ? fs24.existsSync(queryPath) && fs24.statSync(queryPath).isDirectory() ? "" : path26.basename(queryPath) : query;
67248
+ const filter = queryPath ? fs25.existsSync(queryPath) && fs25.statSync(queryPath).isDirectory() ? "" : path27.basename(queryPath) : query;
66407
67249
  const entries = this.fileManager.listPathEntries(currentDir, filter);
66408
67250
  const options = [];
66409
67251
  const homeDir = process.env.HOME || "";
66410
67252
  const displayDir = currentDir.startsWith(homeDir) ? `~${currentDir.slice(homeDir.length)}` : currentDir;
66411
- if (path26.dirname(currentDir) !== currentDir) {
67253
+ if (path27.dirname(currentDir) !== currentDir) {
66412
67254
  options.push({
66413
67255
  type: "parent",
66414
67256
  name: "..",
66415
- path: path26.dirname(currentDir),
67257
+ path: path27.dirname(currentDir),
66416
67258
  icon: "\u21A9",
66417
67259
  meta: "Parent directory"
66418
67260
  });
@@ -66444,10 +67286,10 @@ var MentionHandler = class {
66444
67286
  getBrowserDirectory(query = this.currentQuery) {
66445
67287
  if (this.isPath(query)) {
66446
67288
  const resolvedPath = resolvePath2(query);
66447
- if (fs24.existsSync(resolvedPath) && fs24.statSync(resolvedPath).isDirectory()) {
67289
+ if (fs25.existsSync(resolvedPath) && fs25.statSync(resolvedPath).isDirectory()) {
66448
67290
  return resolvedPath;
66449
67291
  }
66450
- return path26.dirname(resolvedPath);
67292
+ return path27.dirname(resolvedPath);
66451
67293
  }
66452
67294
  return this.browsingDirectory;
66453
67295
  }
@@ -66534,8 +67376,8 @@ async function initializeFileManager(host) {
66534
67376
  sessionId,
66535
67377
  platform: platform2,
66536
67378
  apiKey: host.options.apiKey,
66537
- baseDir: path27.join(host.options.configDir, "files"),
66538
- sessionsDir: path27.join(host.options.configDir, "sessions")
67379
+ baseDir: path28.join(host.options.configDir, "files"),
67380
+ sessionsDir: path28.join(host.options.configDir, "sessions")
66539
67381
  });
66540
67382
  switch (getFileManagerAdapterKind(host.options.model?.filePlatform)) {
66541
67383
  case "kimi":
@@ -66808,13 +67650,13 @@ function convertPreparedAttachmentItems(items) {
66808
67650
  break;
66809
67651
  case "image": {
66810
67652
  if (!item.data && !item.path) return void 0;
66811
- const data = item.data ?? fs25.readFileSync(item.path, "base64");
67653
+ const data = item.data ?? fs26.readFileSync(item.path, "base64");
66812
67654
  converted.push({ type: "image", data, mimeType: item.mimeType });
66813
67655
  break;
66814
67656
  }
66815
67657
  case "video": {
66816
67658
  if (!item.data && !item.path) return void 0;
66817
- const data = item.data ?? fs25.readFileSync(item.path, "base64");
67659
+ const data = item.data ?? fs26.readFileSync(item.path, "base64");
66818
67660
  converted.push({ type: "video", data, mimeType: item.mimeType });
66819
67661
  break;
66820
67662
  }
@@ -67035,6 +67877,55 @@ function isCommandLikeInput(text) {
67035
67877
  return text.startsWith("/") || text.startsWith("!!");
67036
67878
  }
67037
67879
 
67880
+ // src/components/extension-widgets.ts
67881
+ var ExtensionWidgetsComponent = class extends Container {
67882
+ slots = /* @__PURE__ */ new Map();
67883
+ setWidget(key, lines) {
67884
+ const normalized = (lines ?? []).map((line) => String(line ?? "")).filter((line) => line.trim().length > 0);
67885
+ if (normalized.length === 0) {
67886
+ this.slots.delete(key);
67887
+ } else {
67888
+ this.slots.set(key, normalized.slice(0, 3));
67889
+ }
67890
+ this.rebuild();
67891
+ }
67892
+ clearAll() {
67893
+ if (this.slots.size === 0) return;
67894
+ this.slots.clear();
67895
+ this.rebuild();
67896
+ }
67897
+ rebuild() {
67898
+ this.clear();
67899
+ for (const lines of this.slots.values()) {
67900
+ for (const line of lines) {
67901
+ this.addChild(new Text(theme.fg("dim", `\xB7 ${line}`), 1, 0));
67902
+ }
67903
+ }
67904
+ }
67905
+ };
67906
+ var ExtensionStatusSlots = class {
67907
+ slots = /* @__PURE__ */ new Map();
67908
+ set(key, text) {
67909
+ const normalized = String(text ?? "").trim();
67910
+ if (!normalized) {
67911
+ if (!this.slots.delete(key)) return false;
67912
+ return true;
67913
+ }
67914
+ if (this.slots.get(key) === normalized) return false;
67915
+ this.slots.set(key, normalized);
67916
+ return true;
67917
+ }
67918
+ clear() {
67919
+ this.slots.clear();
67920
+ }
67921
+ /** Render-ready suffix for the footer line, e.g. " · git:clean · ci:ok". */
67922
+ format(maxWidth = 60) {
67923
+ if (this.slots.size === 0) return "";
67924
+ const joined = [...this.slots.values()].join(" \xB7 ");
67925
+ return ` \xB7 ${truncateToWidth(joined, maxWidth, "")}`;
67926
+ }
67927
+ };
67928
+
67038
67929
  // src/components/footer.ts
67039
67930
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
67040
67931
  var SPINNER_INTERVAL = 80;
@@ -67055,6 +67946,8 @@ var FooterComponent = class {
67055
67946
  spinnerFrame = 0;
67056
67947
  spinnerTimer = null;
67057
67948
  onRequestRender;
67949
+ /** Keyed ambient status slots for extensions (ctx.ui.setStatus). */
67950
+ extensionStatuses = new ExtensionStatusSlots();
67058
67951
  /** Set callback to request re-render when animation updates (pass true to force) */
67059
67952
  setOnRequestRender(callback) {
67060
67953
  this.onRequestRender = () => callback(true);
@@ -67167,6 +68060,7 @@ var FooterComponent = class {
67167
68060
  const statusWithSpinner = spinner ? `${spinner} ${statusText}` : statusText;
67168
68061
  statsParts.push(theme.fg(statusColor[state.status], statusWithSpinner));
67169
68062
  let statsLeft = statsParts.join(" ");
68063
+ statsLeft += this.extensionStatuses.format();
67170
68064
  const modelName = state.modelName || "no-model";
67171
68065
  let rightSide = modelName;
67172
68066
  if (state.thinkingLevel && state.thinkingLevel !== "off") {
@@ -67222,9 +68116,11 @@ var TodoWidgetComponent = class extends Container {
67222
68116
 
67223
68117
  // src/agent-guidelines.ts
67224
68118
  function buildCodingAgentGuidelinesSection(tools = []) {
67225
- void tools;
68119
+ const hasTodoTool = tools.some((tool) => tool.name === "manage_todo_list");
67226
68120
  const guidelines = [
67227
68121
  "Handle coding tasks by analyzing, planning, or making changes as the available tools and user request allow; do not stop at analysis when execution is expected.",
68122
+ `Keep going until the user's task is completely resolved before ending your turn. If you announce a next step (for example, "now run the tests"), execute it in the same turn instead of describing it; only end your turn when the task is done or you genuinely need user input.`,
68123
+ ...hasTodoTool ? ["For multi-step tasks, set up progress tracking with manage_todo_list before starting, and keep exactly one step in progress until every step is complete."] : [],
67228
68124
  "Work in the current workspace, preserve user changes, and keep edits focused.",
67229
68125
  "Inspect the relevant code before changing it. Use file tools for workspace writes; do not use shell redirection or heredocs to bypass write_file/edit_file guards.",
67230
68126
  "Run the most relevant existing test, build, or check after file changes when practical.",
@@ -67414,6 +68310,12 @@ function classifyCliFailure(context) {
67414
68310
  terminal: false
67415
68311
  });
67416
68312
  }
68313
+ if (errorMessage3.includes(OPENAI_COMPAT_PROTOCOL_ERROR_CODE)) {
68314
+ return createClassification("provider_transient", errorMessage3, {
68315
+ retryable: true,
68316
+ terminal: false
68317
+ });
68318
+ }
67417
68319
  if (PROVIDER_TRANSIENT_ERROR_PATTERN.test(errorMessage3)) {
67418
68320
  return createClassification("provider_transient", errorMessage3, {
67419
68321
  retryable: true,
@@ -67496,6 +68398,9 @@ function setupUI(host) {
67496
68398
  host.todoWidgetContainer = new Container();
67497
68399
  host.todoWidget = new TodoWidgetComponent(host.executionState);
67498
68400
  host.todoWidgetContainer.addChild(host.todoWidget);
68401
+ host.extensionWidgetsContainer = new Container();
68402
+ host.extensionWidgets = new ExtensionWidgetsComponent();
68403
+ host.extensionWidgetsContainer.addChild(host.extensionWidgets);
67499
68404
  const editorTheme = getEditorTheme();
67500
68405
  host.editor = new Editor(host.ui, editorTheme, { paddingX: 1 });
67501
68406
  host.editor.onChange = () => {
@@ -67510,6 +68415,7 @@ function setupUI(host) {
67510
68415
  host.ui.addChild(host.chatContainer);
67511
68416
  host.ui.addChild(host.pendingMessagesContainer);
67512
68417
  host.ui.addChild(host.todoWidgetContainer);
68418
+ host.ui.addChild(host.extensionWidgetsContainer);
67513
68419
  host.ui.addChild(new Spacer());
67514
68420
  host.ui.addChild(host.footer);
67515
68421
  host.ui.addChild(host.editorContainer);
@@ -67839,11 +68745,11 @@ var HierarchicalSelectorComponent = class extends Container {
67839
68745
  // src/runtime/interactive-ui-helpers.ts
67840
68746
  function showHierarchicalMenu(host, initialPath = []) {
67841
68747
  const basePath = [...initialPath];
67842
- const showAtPath = (path31) => {
68748
+ const showAtPath = (path33) => {
67843
68749
  host.showSelector((done) => {
67844
68750
  const selector = new HierarchicalSelectorComponent({
67845
68751
  tree: commandTree,
67846
- path: path31,
68752
+ path: path33,
67847
68753
  basePath,
67848
68754
  onNavigate: (newPath) => {
67849
68755
  done();
@@ -67851,7 +68757,7 @@ function showHierarchicalMenu(host, initialPath = []) {
67851
68757
  },
67852
68758
  onSelect: async (selectedPath, node, searchArgs) => {
67853
68759
  if (node.handler) {
67854
- const args = searchArgs || (selectedPath.length > path31.length ? selectedPath.slice(path31.length).join(" ") : void 0);
68760
+ const args = searchArgs || (selectedPath.length > path33.length ? selectedPath.slice(path33.length).join(" ") : void 0);
67855
68761
  const commandPath = "/" + selectedPath.join("/");
67856
68762
  if (!args && host.isInteractiveCommand(commandPath)) {
67857
68763
  done();
@@ -67945,10 +68851,17 @@ function getContinuationTimeoutMs() {
67945
68851
  if (Number.isFinite(raw) && raw > 0) return raw;
67946
68852
  return 2e4;
67947
68853
  }
68854
+ var STALL_RECHECK_MS = 1e3;
68855
+ function getHardStallTimeoutMs() {
68856
+ const raw = Number(process.env.PIE_INTERACTIVE_HARD_STALL_TIMEOUT_MS || "");
68857
+ if (Number.isFinite(raw) && raw > 0) return raw;
68858
+ return 30 * 6e4;
68859
+ }
67948
68860
  function clearContinuationTimeout(host, reason) {
67949
68861
  if (!host.continuationTimeoutHandle) return;
67950
68862
  clearTimeout(host.continuationTimeoutHandle);
67951
68863
  host.continuationTimeoutHandle = void 0;
68864
+ host.continuationStallSinceMs = void 0;
67952
68865
  host.logDebug("continuation_timeout:cleared", {
67953
68866
  reason,
67954
68867
  awaitingContinuation: host.executionState.read().awaitingContinuation
@@ -67981,6 +68894,36 @@ function appendWarningSummary(host, message) {
67981
68894
  host.chatContainer.addChild(new Spacer(1));
67982
68895
  host.safeRequestRender();
67983
68896
  }
68897
+ async function recoverHardStalledTurn(host, stalledMs) {
68898
+ host.continuationStallSinceMs = void 0;
68899
+ host.logDebug("continuation_timeout:hard_stall_recovery", { stalledMs });
68900
+ host.agentSessionController?.abort();
68901
+ try {
68902
+ await Promise.race([
68903
+ host.agentSessionController?.waitForIdle() ?? Promise.resolve(),
68904
+ new Promise((resolve7) => setTimeout(resolve7, 2e3))
68905
+ ]);
68906
+ } catch (error) {
68907
+ host.logDebug("continuation_timeout:hard_stall_wait_failed", {
68908
+ error: error instanceof Error ? error.message : String(error)
68909
+ });
68910
+ }
68911
+ host.agent.resetProcessingState();
68912
+ host.isProcessing = false;
68913
+ host.setSubmitLock(false);
68914
+ host.streamingComponent = void 0;
68915
+ host.pendingTools.clear();
68916
+ const abortOutcome = host.executionState.abort();
68917
+ host.persistExecutionState();
68918
+ void host.refreshTodoWidget();
68919
+ host.footer.setState({ status: "ready" });
68920
+ host.safeRequestRender();
68921
+ host.ringAttentionBell?.();
68922
+ appendWarningSummary(host, "Recovered from a stalled turn (no progress for " + Math.round(stalledMs / 1e3) + "s). The previous turn was interrupted; review its partial output before continuing.");
68923
+ if (abortOutcome.terminalSummary) {
68924
+ appendWarningSummary(host, abortOutcome.terminalSummary);
68925
+ }
68926
+ }
67984
68927
  function scheduleContinuationTimeout(host) {
67985
68928
  host.clearContinuationTimeout("reschedule");
67986
68929
  const timeoutMs = getContinuationTimeoutMs();
@@ -67989,23 +68932,38 @@ function scheduleContinuationTimeout(host) {
67989
68932
  currentStepId: host.executionState.read().currentStepId,
67990
68933
  queueLength: host.agentSessionController?.pendingMessageCount ?? 0
67991
68934
  });
67992
- host.continuationTimeoutHandle = setTimeout(() => {
68935
+ const check = () => {
67993
68936
  host.continuationTimeoutHandle = void 0;
67994
68937
  const state = host.executionState.read();
67995
68938
  if (!state.awaitingContinuation || isWaitingForUserInteraction(host)) {
68939
+ host.continuationStallSinceMs = void 0;
67996
68940
  host.logDebug("continuation_timeout:skip", {
67997
68941
  reason: !state.awaitingContinuation ? "no_longer_awaiting" : "waiting_for_user_interaction"
67998
68942
  });
67999
68943
  return;
68000
68944
  }
68001
68945
  if (host.agent.state.isStreaming || host.isProcessing || host.submitLock || host.pendingTools.size > 0) {
68002
- host.logDebug("continuation_timeout:active_work", {
68003
- isStreaming: host.agent.state.isStreaming,
68004
- isProcessing: host.isProcessing,
68005
- pendingTools: host.pendingTools.size
68006
- });
68946
+ const now = Date.now();
68947
+ const stallSince = host.continuationStallSinceMs ?? now;
68948
+ host.continuationStallSinceMs = stallSince;
68949
+ const stalledMs = now - stallSince;
68950
+ const hardTimeoutMs = getHardStallTimeoutMs();
68951
+ if (stalledMs < hardTimeoutMs) {
68952
+ host.logDebug("continuation_timeout:active_work", {
68953
+ isStreaming: host.agent.state.isStreaming,
68954
+ isProcessing: host.isProcessing,
68955
+ pendingTools: host.pendingTools.size,
68956
+ stalledMs,
68957
+ hardTimeoutMs
68958
+ });
68959
+ const nextCheckMs = Math.max(50, Math.min(STALL_RECHECK_MS, hardTimeoutMs - stalledMs));
68960
+ host.continuationTimeoutHandle = setTimeout(check, nextCheckMs);
68961
+ return;
68962
+ }
68963
+ void recoverHardStalledTurn(host, stalledMs);
68007
68964
  return;
68008
68965
  }
68966
+ host.continuationStallSinceMs = void 0;
68009
68967
  const outcome = host.executionState.markAwaitingContinuationStalled();
68010
68968
  host.logDebug("continuation_timeout:expired", {
68011
68969
  currentStepId: state.currentStepId,
@@ -68021,7 +68979,8 @@ function scheduleContinuationTimeout(host) {
68021
68979
  }
68022
68980
  }
68023
68981
  host.agentSessionController?.requestQueueDispatch();
68024
- }, timeoutMs);
68982
+ };
68983
+ host.continuationTimeoutHandle = setTimeout(check, timeoutMs);
68025
68984
  }
68026
68985
 
68027
68986
  // src/runtime/interactive-controller-setup.ts
@@ -68087,12 +69046,29 @@ function createInteractiveAgentSessionController(host) {
68087
69046
  baseDelayMs: getCliRetryBaseDelayMs()
68088
69047
  },
68089
69048
  compaction: {
68090
- compact: async (context) => createCliAutoCompactionResult({
68091
- context,
68092
- model: host.options.model,
68093
- apiKey: host.options.apiKey,
68094
- systemPrompt: host.agent.state.systemPrompt
68095
- })
69049
+ compact: async (context) => {
69050
+ const beforeCompact = await host.extensionRunner?.emitSessionBeforeCompact({
69051
+ type: "session:before_compact",
69052
+ reason: context.reason === "overflow" ? "overflow" : "threshold",
69053
+ messageCount: context.messages.length
69054
+ });
69055
+ if (beforeCompact?.cancel) {
69056
+ host.showStatus("Auto-compaction cancelled by an extension");
69057
+ return { aborted: true };
69058
+ }
69059
+ if (beforeCompact?.summary) {
69060
+ const activeEntryId = context.sessionManager.getActiveEntryId();
69061
+ const firstKeptEntryId = activeEntryId ? findFirstKeptEntryId(context.sessionManager.getPathToEntry(activeEntryId)) : void 0;
69062
+ if (!firstKeptEntryId) return { aborted: true };
69063
+ return { summary: beforeCompact.summary, firstKeptEntryId };
69064
+ }
69065
+ return createCliAutoCompactionResult({
69066
+ context,
69067
+ model: host.options.model,
69068
+ apiKey: host.options.apiKey,
69069
+ systemPrompt: host.agent.state.systemPrompt
69070
+ });
69071
+ }
68096
69072
  },
68097
69073
  onEvent: (event) => {
68098
69074
  host.sessionTrace.noteRuntimeEvent(event.type, event);
@@ -68161,6 +69137,7 @@ function createInteractiveAgentSessionController(host) {
68161
69137
  host.appendWarningSummary(outcome.terminalSummary);
68162
69138
  }
68163
69139
  host.sessionTrace.completeTurn(event.message, false);
69140
+ ringTurnSettledBell(host);
68164
69141
  host._semanticTurnIndex++;
68165
69142
  } else if (event.type === "turn_settled") {
68166
69143
  host.setControllerBusy(false);
@@ -68198,6 +69175,7 @@ function createInteractiveAgentSessionController(host) {
68198
69175
  host.showStatus("Todo list preserved. Send 'continue' when you want to resume it.");
68199
69176
  }
68200
69177
  host.showError(event.failure.message);
69178
+ host.ringAttentionBell?.();
68201
69179
  host.setControllerBusy(false);
68202
69180
  } else if (event.type === "turn_cancelled") {
68203
69181
  const outcome = host.executionState.abort();
@@ -68526,7 +69504,7 @@ async function handleDirectBash(host, command) {
68526
69504
  child.stderr.on("data", (data) => {
68527
69505
  output.append(data.toString());
68528
69506
  });
68529
- return new Promise((resolve6) => {
69507
+ return new Promise((resolve7) => {
68530
69508
  let settled = false;
68531
69509
  const finish = (code) => {
68532
69510
  if (settled) return;
@@ -68539,7 +69517,7 @@ async function handleDirectBash(host, command) {
68539
69517
  }
68540
69518
  host.chatContainer.addChild(new Spacer(1));
68541
69519
  host.ui.requestRender();
68542
- resolve6();
69520
+ resolve7();
68543
69521
  };
68544
69522
  child.on("error", (error) => {
68545
69523
  output.append(error.message);
@@ -68559,7 +69537,15 @@ function createInteractiveExtensionUIContext(host) {
68559
69537
  confirm: (title, message, opts) => showExtensionConfirm(host, title, message, opts),
68560
69538
  input: (title, placeholder, opts) => showExtensionInput(host, title, placeholder, opts),
68561
69539
  editor: (title, prefill) => showExtensionEditor(host, title, prefill),
68562
- custom: (factory, options) => showExtensionCustom(host, factory, options)
69540
+ custom: (factory, options) => showExtensionCustom(host, factory, options),
69541
+ setStatus: (key, text) => {
69542
+ host.footer.extensionStatuses.set(key, text);
69543
+ host.ui.requestRender();
69544
+ },
69545
+ setWidget: (key, lines) => {
69546
+ host.extensionWidgets.setWidget(key, lines);
69547
+ host.ui.requestRender();
69548
+ }
68563
69549
  };
68564
69550
  }
68565
69551
  function installInteractiveInteractionHandler(host) {
@@ -68568,6 +69554,7 @@ function installInteractiveInteractionHandler(host) {
68568
69554
  host.activeInteractionCount++;
68569
69555
  host.setWaitingForUser(true);
68570
69556
  host.footer.setState({ lifecycleEvent: "waiting_for_user_input" });
69557
+ host.ringAttentionBell?.();
68571
69558
  host.safeRequestRender();
68572
69559
  };
68573
69560
  const endWaitingForUser = () => {
@@ -68578,15 +69565,15 @@ function installInteractiveInteractionHandler(host) {
68578
69565
  host.footer.setState({ lifecycleEvent: "tool_execution_start" });
68579
69566
  host.safeRequestRender();
68580
69567
  };
68581
- const withTimeout = async (promise) => {
69568
+ const withTimeout2 = async (promise) => {
68582
69569
  if (!("timeoutMs" in request) || !request.timeoutMs || request.timeoutMs <= 0) {
68583
69570
  return { value: await promise, timedOut: false };
68584
69571
  }
68585
- return await new Promise((resolve6, reject) => {
68586
- const timer = setTimeout(() => resolve6({ timedOut: true }), request.timeoutMs);
69572
+ return await new Promise((resolve7, reject) => {
69573
+ const timer = setTimeout(() => resolve7({ timedOut: true }), request.timeoutMs);
68587
69574
  promise.then((value) => {
68588
69575
  clearTimeout(timer);
68589
- resolve6({ value, timedOut: false });
69576
+ resolve7({ value, timedOut: false });
68590
69577
  }).catch((error) => {
68591
69578
  clearTimeout(timer);
68592
69579
  reject(error);
@@ -68596,7 +69583,7 @@ function installInteractiveInteractionHandler(host) {
68596
69583
  const waitForUser = async (createPromise) => {
68597
69584
  beginWaitingForUser();
68598
69585
  try {
68599
- return await withTimeout(createPromise());
69586
+ return await withTimeout2(createPromise());
68600
69587
  } finally {
68601
69588
  endWaitingForUser();
68602
69589
  }
@@ -68686,15 +69673,15 @@ function showExtensionNotification(host, message, type = "info") {
68686
69673
  host.ui.requestRender();
68687
69674
  }
68688
69675
  function showExtensionSelector2(host, title, options, opts) {
68689
- return new Promise((resolve6) => {
69676
+ return new Promise((resolve7) => {
68690
69677
  host.showStatus(`Waiting for selection: ${title}`);
68691
69678
  if (opts?.signal?.aborted) {
68692
- resolve6(void 0);
69679
+ resolve7(void 0);
68693
69680
  return;
68694
69681
  }
68695
69682
  const onAbort = () => {
68696
69683
  host.restoreEditor();
68697
- resolve6(void 0);
69684
+ resolve7(void 0);
68698
69685
  };
68699
69686
  opts?.signal?.addEventListener("abort", onAbort, { once: true });
68700
69687
  const selector = new ExtensionSelectorComponent(
@@ -68703,12 +69690,12 @@ function showExtensionSelector2(host, title, options, opts) {
68703
69690
  (option) => {
68704
69691
  opts?.signal?.removeEventListener("abort", onAbort);
68705
69692
  host.restoreEditor();
68706
- resolve6(option);
69693
+ resolve7(option);
68707
69694
  },
68708
69695
  () => {
68709
69696
  opts?.signal?.removeEventListener("abort", onAbort);
68710
69697
  host.restoreEditor();
68711
- resolve6(void 0);
69698
+ resolve7(void 0);
68712
69699
  }
68713
69700
  );
68714
69701
  host.editorContainer.clear();
@@ -68724,15 +69711,15 @@ ${message}`, ["Yes", "No"], opts);
68724
69711
  return result === "Yes";
68725
69712
  }
68726
69713
  function showExtensionInput(host, title, placeholder, opts) {
68727
- return new Promise((resolve6) => {
69714
+ return new Promise((resolve7) => {
68728
69715
  host.showStatus(`Waiting for input: ${title}`);
68729
69716
  if (opts?.signal?.aborted) {
68730
- resolve6(void 0);
69717
+ resolve7(void 0);
68731
69718
  return;
68732
69719
  }
68733
69720
  const onAbort = () => {
68734
69721
  host.restoreEditor();
68735
- resolve6(void 0);
69722
+ resolve7(void 0);
68736
69723
  };
68737
69724
  opts?.signal?.addEventListener("abort", onAbort, { once: true });
68738
69725
  const input = new ExtensionInputComponent(
@@ -68741,12 +69728,12 @@ function showExtensionInput(host, title, placeholder, opts) {
68741
69728
  (value) => {
68742
69729
  opts?.signal?.removeEventListener("abort", onAbort);
68743
69730
  host.restoreEditor();
68744
- resolve6(value);
69731
+ resolve7(value);
68745
69732
  },
68746
69733
  () => {
68747
69734
  opts?.signal?.removeEventListener("abort", onAbort);
68748
69735
  host.restoreEditor();
68749
- resolve6(void 0);
69736
+ resolve7(void 0);
68750
69737
  }
68751
69738
  );
68752
69739
  host.editorContainer.clear();
@@ -68757,7 +69744,7 @@ function showExtensionInput(host, title, placeholder, opts) {
68757
69744
  });
68758
69745
  }
68759
69746
  function showExtensionEditor(host, title, prefill) {
68760
- return new Promise((resolve6) => {
69747
+ return new Promise((resolve7) => {
68761
69748
  host.showStatus(`Waiting for input: ${title}`);
68762
69749
  const container = new Container();
68763
69750
  container.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
@@ -68779,12 +69766,12 @@ function showExtensionEditor(host, title, prefill) {
68779
69766
  preview: value.slice(0, 120)
68780
69767
  });
68781
69768
  host.restoreEditor();
68782
- resolve6(value);
69769
+ resolve7(value);
68783
69770
  };
68784
69771
  editor.onEscape = () => {
68785
69772
  host.logDebug("extension:editor:cancel", { title });
68786
69773
  host.restoreEditor();
68787
- resolve6(void 0);
69774
+ resolve7(void 0);
68788
69775
  };
68789
69776
  container.addChild(editor);
68790
69777
  container.addChild(new Spacer(1));
@@ -68798,14 +69785,14 @@ function showExtensionEditor(host, title, prefill) {
68798
69785
  }
68799
69786
  function showExtensionCustom(host, factory, options) {
68800
69787
  const savedText = host.editor.getText();
68801
- return new Promise((resolve6, reject) => {
69788
+ return new Promise((resolve7, reject) => {
68802
69789
  let closed = false;
68803
69790
  const close = (result) => {
68804
69791
  if (closed) return;
68805
69792
  closed = true;
68806
69793
  host.restoreEditor();
68807
69794
  host.editor.setText(savedText);
68808
- resolve6(result);
69795
+ resolve7(result);
68809
69796
  };
68810
69797
  Promise.resolve(factory(host.ui, theme, close)).then((component) => {
68811
69798
  if (closed) return;
@@ -68826,6 +69813,60 @@ function showExtensionCustom(host, factory, options) {
68826
69813
  });
68827
69814
  }
68828
69815
 
69816
+ // src/extensions/provider-login.ts
69817
+ var inFlightLogins = /* @__PURE__ */ new Map();
69818
+ async function loginExtensionProvider(options) {
69819
+ const { provider } = options;
69820
+ const existing = inFlightLogins.get(provider);
69821
+ if (existing) return existing;
69822
+ const attempt = runExtensionProviderLogin(options).finally(() => {
69823
+ if (inFlightLogins.get(provider) === attempt) inFlightLogins.delete(provider);
69824
+ });
69825
+ inFlightLogins.set(provider, attempt);
69826
+ return attempt;
69827
+ }
69828
+ async function runExtensionProviderLogin(options) {
69829
+ const { provider, modelRegistry, authStorage, requestInteraction: requestInteraction2 } = options;
69830
+ const oauth = modelRegistry.getExtensionOAuth(provider);
69831
+ if (!oauth) return false;
69832
+ const credentials = await oauth.login({
69833
+ onAuth: ({ url }) => {
69834
+ void requestInteraction2({
69835
+ type: "notify",
69836
+ message: `${oauth.name}: open this URL to sign in:
69837
+ ${url}`,
69838
+ level: "info"
69839
+ });
69840
+ },
69841
+ onPrompt: async ({ message, placeholder }) => {
69842
+ const response = await requestInteraction2({
69843
+ type: "text_input",
69844
+ id: `oauth:${provider}`,
69845
+ prompt: `${oauth.name}: ${message}`,
69846
+ placeholder
69847
+ });
69848
+ if (response.type !== "text_input" || response.timedOut || response.skipped || response.value === void 0) {
69849
+ throw new Error(`${oauth.name} login cancelled.`);
69850
+ }
69851
+ return response.value;
69852
+ }
69853
+ });
69854
+ authStorage.setOAuthCredentials(provider, credentials);
69855
+ authStorage.setRuntimeApiKey(provider, credentials.access);
69856
+ const errors = authStorage.drainErrors();
69857
+ if (errors.length > 0) {
69858
+ throw new Error(`Failed to persist ${oauth.name} credentials: ${errors[0].message}`);
69859
+ }
69860
+ return true;
69861
+ }
69862
+ async function restoreExtensionProviderCredentials(modelRegistry) {
69863
+ for (const result of await modelRegistry.restorePersistedOAuth()) {
69864
+ if (process.env.PIE_VERBOSE) {
69865
+ console.error(`[Extension] OAuth restore ${result.provider}: ${result.action}${result.detail ? ` (${result.detail})` : ""}`);
69866
+ }
69867
+ }
69868
+ }
69869
+
68829
69870
  // src/attachments/resolver.ts
68830
69871
  var AttachmentResolver = class {
68831
69872
  constructor(extensions) {
@@ -68989,6 +70030,26 @@ async function loadInteractiveExtensions(host) {
68989
70030
  activeSession.metadata[key] = value;
68990
70031
  activeSession.isDirty = true;
68991
70032
  },
70033
+ appendEntry: (customType, data) => host.sessionManager.appendCustomEntry(customType, data).id,
70034
+ setLabel: (entryId, label) => host.sessionManager.setLabel(entryId, label),
70035
+ setSessionName: (name) => host.sessionManager.setSessionName(name),
70036
+ getSessionName: () => host.sessionManager.getSessionName(),
70037
+ registerProvider: (name, config) => {
70038
+ const unregister = host.options.modelRegistry.registerProvider(name, config);
70039
+ host.noteRuntimeModeChanged("extension_register_provider");
70040
+ return unregister;
70041
+ },
70042
+ loginProvider: async (name) => {
70043
+ const loggedIn = await loginExtensionProvider({
70044
+ provider: name,
70045
+ modelRegistry: host.options.modelRegistry,
70046
+ authStorage: host.options.modelRegistry.getAuthStorage(),
70047
+ requestInteraction
70048
+ });
70049
+ if (loggedIn) host.noteRuntimeModeChanged("extension_login_provider");
70050
+ return loggedIn;
70051
+ },
70052
+ reload: () => host.reloadRuntime(),
68992
70053
  get yoloMode() {
68993
70054
  return host.yoloMode;
68994
70055
  }
@@ -69016,9 +70077,10 @@ async function loadInteractiveExtensions(host) {
69016
70077
  true
69017
70078
  );
69018
70079
  host.extensionRunner.setUIContext(host.extensionUIContext);
70080
+ await restoreExtensionProviderCredentials(host.options.modelRegistry);
69019
70081
  if (process.env.PIE_VERBOSE) {
69020
- for (const { path: path31, error } of errors) {
69021
- console.error(`[Extension] Failed to load ${path31}: ${error}`);
70082
+ for (const { path: path33, error } of errors) {
70083
+ console.error(`[Extension] Failed to load ${path33}: ${error}`);
69022
70084
  }
69023
70085
  }
69024
70086
  const extensionTools = convertExtensionTools(extensions);
@@ -69072,6 +70134,8 @@ var InteractiveMode = class {
69072
70134
  editorContainer;
69073
70135
  todoWidgetContainer;
69074
70136
  todoWidget;
70137
+ extensionWidgetsContainer;
70138
+ extensionWidgets;
69075
70139
  executionState = new ExecutionStateManager();
69076
70140
  sessionTrace;
69077
70141
  keybindings;
@@ -69136,10 +70200,24 @@ var InteractiveMode = class {
69136
70200
  lastStatusText;
69137
70201
  lastSigintTime = 0;
69138
70202
  continuationTimeoutHandle;
70203
+ continuationStallSinceMs;
69139
70204
  activeInteractionCount = 0;
69140
70205
  logDebug(message, data) {
69141
70206
  fileLogger.log("interactive", message, data);
69142
70207
  }
70208
+ /** Streams and tool updates prove the turn is alive; restart the no-progress clock. */
70209
+ noteContinuationActivity() {
70210
+ this.continuationStallSinceMs = void 0;
70211
+ }
70212
+ /**
70213
+ * Ring the terminal bell (BEL) when a turn needs the user's attention:
70214
+ * interaction prompts, turn completion/failure, and stall recovery. The
70215
+ * bell itself enforces a cross-call-site cooldown; the settings gate here
70216
+ * is the single opt-out point (settings.json `notifications.bell`).
70217
+ */
70218
+ ringAttentionBell() {
70219
+ ringTerminalBell({ enabled: this.options.settingsManager.getBellNotificationsEnabled() });
70220
+ }
69143
70221
  getContinuationTimeoutMs() {
69144
70222
  return getContinuationTimeoutMs();
69145
70223
  }
@@ -69382,7 +70460,7 @@ var InteractiveMode = class {
69382
70460
  if (savedLevel) {
69383
70461
  this.thinkingLevel = savedLevel;
69384
70462
  }
69385
- const { Agent } = await import("./chunks/src-N6MKSRB3.js");
70463
+ const { Agent } = await import("./chunks/src-TQNFXZGK.js");
69386
70464
  const activeSession = this.sessionManager.getActiveSession();
69387
70465
  this.agent = new Agent({
69388
70466
  initialState: {
@@ -69473,6 +70551,40 @@ var InteractiveMode = class {
69473
70551
  installInteractiveInteractionHandler(this);
69474
70552
  await loadInteractiveExtensions(this.extensionLoaderHost);
69475
70553
  }
70554
+ /**
70555
+ * Full runtime reload (extension API `ctx.reload()`): re-discover skills
70556
+ * and extensions from disk and rebuild the tool surface and system prompt.
70557
+ * Rejects while a turn is processing. Listener lifecycle contract: the old
70558
+ * extension instances are dropped entirely (handlers, tools, commands),
70559
+ * the tool surface is reset to the pristine composition baseline, and the
70560
+ * fresh ExtensionRunner re-wraps from that baseline — no stale handlers
70561
+ * and no double-wrapped tools survive a reload.
70562
+ */
70563
+ async reloadRuntime() {
70564
+ if (this.isProcessing) {
70565
+ throw new Error("Cannot reload while a response is being processed. Wait for the turn to finish or abort it first.");
70566
+ }
70567
+ const builtinDir = path29.join(getBuiltinDir(), "skills");
70568
+ const userDir = path29.join(os8.homedir(), ".pie", "skills");
70569
+ const projectDir = path29.join(this.options.cwd, ".pie", "skills");
70570
+ const { skills, diagnostics } = loadSkills({
70571
+ dirs: buildSkillDirs({ builtinDir, userDir, projectDir, cwd: this.options.cwd })
70572
+ });
70573
+ this.skills = skills;
70574
+ this.skillsSection = formatSkillsForPrompt(skills);
70575
+ for (const diag of diagnostics.filter((entry) => entry.type === "collision")) {
70576
+ this.chatContainer.addChild(new Text(theme.fg("dim", `[reload] ${diag.message}`), 1, 0));
70577
+ }
70578
+ this.allTools = [...this.options.allTools];
70579
+ this.originalTools = [...this.options.allTools];
70580
+ this.agent.state.tools = [...this.options.allTools];
70581
+ this.extensionRunner?.revokeRegistrations();
70582
+ this.extensionWidgets?.clearAll();
70583
+ await this.loadExtensions();
70584
+ this.applyRuntimeToolMode();
70585
+ this.noteRuntimeModeChanged("extension_reload");
70586
+ this.ui.requestRender();
70587
+ }
69476
70588
  calculateContextUsage() {
69477
70589
  return calculateContextUsage(this.contextUiHost);
69478
70590
  }
@@ -69721,8 +70833,8 @@ var InteractiveMode = class {
69721
70833
  };
69722
70834
 
69723
70835
  // src/app/runtime-composition.ts
69724
- import { existsSync as existsSync17, mkdirSync as mkdirSync12 } from "fs";
69725
- import * as path28 from "path";
70836
+ import { existsSync as existsSync18, mkdirSync as mkdirSync13 } from "fs";
70837
+ import * as path30 from "path";
69726
70838
  var CliMissingApiKeyError = class extends Error {
69727
70839
  constructor(message) {
69728
70840
  super(message);
@@ -69743,13 +70855,13 @@ var CliSessionResumeError = class extends Error {
69743
70855
  function getCliRuntimePaths() {
69744
70856
  const configDir = getConfigDir();
69745
70857
  const sessionsDir = getSessionsDir();
69746
- const skillsDir = path28.join(configDir, "skills");
69747
- const builtinSkillsDir = path28.join(getBuiltinDir(), "skills");
70858
+ const skillsDir = path30.join(configDir, "skills");
70859
+ const builtinSkillsDir = path30.join(getBuiltinDir(), "skills");
69748
70860
  return { configDir, sessionsDir, skillsDir, builtinSkillsDir };
69749
70861
  }
69750
70862
  function ensureCliRuntimePaths(paths = getCliRuntimePaths()) {
69751
- if (!existsSync17(paths.skillsDir)) {
69752
- mkdirSync12(paths.skillsDir, { recursive: true });
70863
+ if (!existsSync18(paths.skillsDir)) {
70864
+ mkdirSync13(paths.skillsDir, { recursive: true });
69753
70865
  }
69754
70866
  return paths;
69755
70867
  }
@@ -69775,7 +70887,7 @@ async function composeCliRuntime(params) {
69775
70887
  const paths = ensureCliRuntimePaths(params.paths);
69776
70888
  const compositionTrace = createCliCompositionTrace();
69777
70889
  const settingsManager = new SettingsManager();
69778
- const authStorage = new AuthStorage();
70890
+ const authStorage = AuthStorage.fromStorage(new FileAuthStorageBackend());
69779
70891
  authStorage.setLegacyApiKey(settingsManager.getLegacyApiKey());
69780
70892
  const modelRegistry = new ModelRegistry(authStorage);
69781
70893
  await modelRegistry.refreshRemoteCatalogs();
@@ -69861,11 +70973,14 @@ async function composeCliRuntime(params) {
69861
70973
  fileLogger.log("cli", `Using API key for provider ${provider}`, {
69862
70974
  keyPrefix: apiKey ? maskApiKey2(apiKey) : "none"
69863
70975
  });
69864
- const projectSkillsDir = path28.join(cwd, ".pie", "skills");
70976
+ const projectSkillsDir = path30.join(cwd, ".pie", "skills");
69865
70977
  const { skills, diagnostics } = loadSkills({
69866
- builtinDir: paths.builtinSkillsDir,
69867
- userDir: paths.skillsDir,
69868
- projectDir: projectSkillsDir
70978
+ dirs: buildSkillDirs({
70979
+ builtinDir: paths.builtinSkillsDir,
70980
+ userDir: paths.skillsDir,
70981
+ projectDir: projectSkillsDir,
70982
+ cwd
70983
+ })
69869
70984
  });
69870
70985
  let interactiveOptions;
69871
70986
  const getCurrentModel = () => interactiveOptions?.model ?? model;
@@ -70056,6 +71171,17 @@ async function loadPrintModeExtensions(params) {
70056
71171
  session.metadata[key] = value;
70057
71172
  session.isDirty = true;
70058
71173
  },
71174
+ appendEntry: (customType, data) => params.sessionManager.appendCustomEntry(customType, data).id,
71175
+ setLabel: (entryId, label) => params.sessionManager.setLabel(entryId, label),
71176
+ setSessionName: (name) => params.sessionManager.setSessionName(name),
71177
+ getSessionName: () => params.sessionManager.getSessionName(),
71178
+ registerProvider: (name, config) => params.modelRegistry.registerProvider(name, config),
71179
+ loginProvider: (name) => loginExtensionProvider({
71180
+ provider: name,
71181
+ modelRegistry: params.modelRegistry,
71182
+ authStorage: params.modelRegistry.getAuthStorage(),
71183
+ requestInteraction
71184
+ }),
70059
71185
  yoloMode: false
70060
71186
  };
70061
71187
  const { extensions } = await loadExtensions(
@@ -70078,6 +71204,7 @@ async function loadPrintModeExtensions(params) {
70078
71204
  false
70079
71205
  );
70080
71206
  runner.setUIContext(uiContext);
71207
+ await restoreExtensionProviderCredentials(params.modelRegistry);
70081
71208
  const extensionTools = convertExtensionTools(extensions);
70082
71209
  availableTools = [...availableTools, ...extensionTools];
70083
71210
  return {
@@ -70103,7 +71230,7 @@ async function runPrintMode(params) {
70103
71230
  `);
70104
71231
  console.log("Assistant: ");
70105
71232
  }
70106
- const { Agent: AgentClass } = await import("./chunks/src-N6MKSRB3.js");
71233
+ const { Agent: AgentClass } = await import("./chunks/src-TQNFXZGK.js");
70107
71234
  let agent;
70108
71235
  const printModeExtensions = await loadPrintModeExtensions({
70109
71236
  cwd: runtime.cwd,
@@ -70119,6 +71246,8 @@ async function runPrintMode(params) {
70119
71246
  allTools: runtime.allTools
70120
71247
  });
70121
71248
  const printModeTools = [...runtime.tools, ...printModeExtensions.extensionTools];
71249
+ const restoredApiKey = runtime.modelRegistry.getApiKey(runtime.model);
71250
+ if (restoredApiKey) runtime.apiKey = restoredApiKey;
70122
71251
  recordCliCompositionStep(runtime.compositionTrace, {
70123
71252
  id: "extensions_tool_wrapping",
70124
71253
  inputSummary: `baseTools=${runtime.tools.length} extensionPaths=${runtime.extensionPaths.length}`,
@@ -70182,12 +71311,26 @@ async function runPrintMode(params) {
70182
71311
  isContextOverflowError: isCliContextOverflowError
70183
71312
  }),
70184
71313
  compaction: {
70185
- compact: async (context) => createCliAutoCompactionResult({
70186
- context,
70187
- model: runtime.model,
70188
- apiKey: runtime.apiKey,
70189
- systemPrompt: agent?.state.systemPrompt ?? baseSystemPrompt
70190
- })
71314
+ compact: async (context) => {
71315
+ const beforeCompact = await printModeExtensions.runner.emitSessionBeforeCompact({
71316
+ type: "session:before_compact",
71317
+ reason: context.reason === "overflow" ? "overflow" : "threshold",
71318
+ messageCount: context.messages.length
71319
+ });
71320
+ if (beforeCompact.cancel) return { aborted: true };
71321
+ if (beforeCompact.summary) {
71322
+ const activeEntryId = context.sessionManager.getActiveEntryId();
71323
+ const firstKeptEntryId = activeEntryId ? findFirstKeptEntryId(context.sessionManager.getPathToEntry(activeEntryId)) : void 0;
71324
+ if (!firstKeptEntryId) return { aborted: true };
71325
+ return { summary: beforeCompact.summary, firstKeptEntryId };
71326
+ }
71327
+ return createCliAutoCompactionResult({
71328
+ context,
71329
+ model: runtime.model,
71330
+ apiKey: runtime.apiKey,
71331
+ systemPrompt: agent?.state.systemPrompt ?? baseSystemPrompt
71332
+ });
71333
+ }
70191
71334
  },
70192
71335
  classifyFailure: (context) => classifyCliFailure(context),
70193
71336
  onEvent: (event) => {
@@ -70509,6 +71652,7 @@ async function loadJsonModeExtensions(params) {
70509
71652
  false
70510
71653
  );
70511
71654
  runner.setUIContext(uiContext);
71655
+ await restoreExtensionProviderCredentials(params.runtime.modelRegistry);
70512
71656
  const extensionTools = convertExtensionTools(extensions);
70513
71657
  availableTools = [...availableTools, ...extensionTools];
70514
71658
  return {
@@ -70542,7 +71686,7 @@ async function runJsonEventsMode(params) {
70542
71686
  return typeof message.sessionId === "string" && message.sessionId !== sessionId;
70543
71687
  }
70544
71688
  function delay(ms) {
70545
- return new Promise((resolve6) => setTimeout(resolve6, Math.max(0, ms)));
71689
+ return new Promise((resolve7) => setTimeout(resolve7, Math.max(0, ms)));
70546
71690
  }
70547
71691
  async function waitForActiveController(timeoutMs = 1e3) {
70548
71692
  const deadline = Date.now() + timeoutMs;
@@ -71053,9 +72197,11 @@ async function runJsonEventsMode(params) {
71053
72197
  if (runtime.initialModel.unconfigured || controller) {
71054
72198
  return;
71055
72199
  }
71056
- const { Agent: AgentClass } = await import("./chunks/src-N6MKSRB3.js");
72200
+ const { Agent: AgentClass } = await import("./chunks/src-TQNFXZGK.js");
71057
72201
  const jsonModeExtensions = await loadJsonModeExtensions({ runtime, getAgent: () => agent });
71058
72202
  const tools = [...runtime.tools, ...jsonModeExtensions.extensionTools];
72203
+ const restoredApiKey = runtime.modelRegistry.getApiKey(runtime.model);
72204
+ if (restoredApiKey) runtime.apiKey = restoredApiKey;
71059
72205
  recordCliCompositionStep(runtime.compositionTrace, {
71060
72206
  id: "extensions_tool_wrapping",
71061
72207
  inputSummary: `baseTools=${runtime.tools.length} extensionPaths=${runtime.extensionPaths.length}`,
@@ -71487,19 +72633,19 @@ async function runJsonEventsMode(params) {
71487
72633
  }
71488
72634
 
71489
72635
  // src/developer-instructions.ts
71490
- import * as fs26 from "fs";
71491
- import * as path29 from "path";
72636
+ import * as fs27 from "fs";
72637
+ import * as path31 from "path";
71492
72638
  var MAX_DEVELOPER_INSTRUCTIONS_CHARS = 64e3;
71493
72639
  function readTextFileBounded(filePath) {
71494
- const resolved = path29.resolve(filePath);
71495
- const stat2 = fs26.statSync(resolved);
72640
+ const resolved = path31.resolve(filePath);
72641
+ const stat2 = fs27.statSync(resolved);
71496
72642
  if (!stat2.isFile()) {
71497
72643
  throw new Error(`Developer instructions file is not a regular file: ${filePath}`);
71498
72644
  }
71499
72645
  if (stat2.size > MAX_DEVELOPER_INSTRUCTIONS_CHARS * 4) {
71500
72646
  throw new Error(`Developer instructions file is too large: ${filePath}`);
71501
72647
  }
71502
- return fs26.readFileSync(resolved, "utf8");
72648
+ return fs27.readFileSync(resolved, "utf8");
71503
72649
  }
71504
72650
  function collectFlagValues(args, flag) {
71505
72651
  const values = [];
@@ -71577,8 +72723,8 @@ var logError = (msg, err) => {
71577
72723
  const logPath = getRuntimeLogPath();
71578
72724
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
71579
72725
  const stack = err instanceof Error ? err.stack || "" : "";
71580
- fs27.mkdirSync(path30.dirname(logPath), { recursive: true });
71581
- fs27.appendFileSync(logPath, `[${timestamp}] ${msg}
72726
+ fs28.mkdirSync(path32.dirname(logPath), { recursive: true });
72727
+ fs28.appendFileSync(logPath, `[${timestamp}] ${msg}
71582
72728
  ${stack}
71583
72729
 
71584
72730
  `);
@@ -71685,7 +72831,7 @@ async function runWithJsonEventsConsoleIsolation(fn) {
71685
72831
  }
71686
72832
  async function createCliTestStreamFnFromEnv() {
71687
72833
  if (!process.env.PIE_TEST_MOCK_STREAM_SEQUENCE) return void 0;
71688
- return (await import("./chunks/test-stream-6F6VZ44U.js")).createTestStreamFnFromSequenceEnv();
72834
+ return (await import("./chunks/test-stream-IPBFEEWH.js")).createTestStreamFnFromSequenceEnv();
71689
72835
  }
71690
72836
  async function startChat(initialPrompt, testCommand) {
71691
72837
  const rawOutput = process.argv.includes("--raw-output");
@@ -71780,9 +72926,9 @@ async function startChat(initialPrompt, testCommand) {
71780
72926
  flushEarlyLogs();
71781
72927
  if (testCommand) {
71782
72928
  console.log(`[TEST] Auto-executing command: ${testCommand}`);
71783
- await new Promise((resolve6) => setTimeout(resolve6, 500));
72929
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
71784
72930
  await interactive.executeCommand(testCommand);
71785
- await new Promise((resolve6) => setTimeout(resolve6, 2e3));
72931
+ await new Promise((resolve7) => setTimeout(resolve7, 2e3));
71786
72932
  console.log("[TEST] Command executed, exiting...");
71787
72933
  process.exit(0);
71788
72934
  return;