@cydm/pie 1.0.41 → 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-4WGEYDPO.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-OBCFKF6H.js";
73
- import "./chunks/chunk-E3ELBCCX.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,
@@ -106,7 +106,7 @@ import {
106
106
  setLogger,
107
107
  sortToolModelCandidatesByCapability,
108
108
  type_exports
109
- } from "./chunks/chunk-YCPKJCUB.js";
109
+ } from "./chunks/chunk-NGQ3PUOC.js";
110
110
  import {
111
111
  resolveCliProjectRoot
112
112
  } from "./chunks/chunk-NTYHFBUA.js";
@@ -114,7 +114,7 @@ import {
114
114
  __commonJS,
115
115
  __require,
116
116
  __toESM
117
- } from "./chunks/chunk-TG2EQLX2.js";
117
+ } from "./chunks/chunk-LZQXQOPJ.js";
118
118
 
119
119
  // ../../node_modules/color-name/index.js
120
120
  var require_color_name = __commonJS({
@@ -986,15 +986,15 @@ var require_route = __commonJS({
986
986
  };
987
987
  }
988
988
  function wrapConversion(toModel, graph) {
989
- const path31 = [graph[toModel].parent, toModel];
989
+ const path33 = [graph[toModel].parent, toModel];
990
990
  let fn = conversions[graph[toModel].parent][toModel];
991
991
  let cur = graph[toModel].parent;
992
992
  while (graph[cur].parent) {
993
- path31.unshift(graph[cur].parent);
993
+ path33.unshift(graph[cur].parent);
994
994
  fn = link(conversions[graph[cur].parent][cur], fn);
995
995
  cur = graph[cur].parent;
996
996
  }
997
- fn.conversion = path31;
997
+ fn.conversion = path33;
998
998
  return fn;
999
999
  }
1000
1000
  module.exports = function(fromModel) {
@@ -1234,7 +1234,7 @@ var require_has_flag = __commonJS({
1234
1234
  var require_supports_color = __commonJS({
1235
1235
  "../../node_modules/chalk/node_modules/supports-color/index.js"(exports, module) {
1236
1236
  "use strict";
1237
- var os7 = __require("os");
1237
+ var os9 = __require("os");
1238
1238
  var tty = __require("tty");
1239
1239
  var hasFlag2 = require_has_flag();
1240
1240
  var { env } = process;
@@ -1282,7 +1282,7 @@ var require_supports_color = __commonJS({
1282
1282
  return min;
1283
1283
  }
1284
1284
  if (process.platform === "win32") {
1285
- const osRelease = os7.release().split(".");
1285
+ const osRelease = os9.release().split(".");
1286
1286
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
1287
1287
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
1288
1288
  }
@@ -1917,7 +1917,7 @@ var require_core = __commonJS({
1917
1917
  return match && match.index === 0;
1918
1918
  }
1919
1919
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
1920
- function join19(regexps, separator = "|") {
1920
+ function join21(regexps, separator = "|") {
1921
1921
  let numCaptures = 0;
1922
1922
  return regexps.map((regex) => {
1923
1923
  numCaptures += 1;
@@ -2221,7 +2221,7 @@ var require_core = __commonJS({
2221
2221
  this.exec = () => null;
2222
2222
  }
2223
2223
  const terminators = this.regexes.map((el) => el[1]);
2224
- this.matcherRe = langRe(join19(terminators), true);
2224
+ this.matcherRe = langRe(join21(terminators), true);
2225
2225
  this.lastIndex = 0;
2226
2226
  }
2227
2227
  /** @param {string} s */
@@ -43958,8 +43958,8 @@ var require_dist = __commonJS({
43958
43958
  });
43959
43959
 
43960
43960
  // src/cli.ts
43961
- import * as fs27 from "fs";
43962
- import * as path30 from "path";
43961
+ import * as fs28 from "fs";
43962
+ import * as path32 from "path";
43963
43963
 
43964
43964
  // src/cli-args.ts
43965
43965
  var FLAGS_WITH_VALUES = /* @__PURE__ */ new Set(["--session-id", "--extension-path", "--developer-instructions", "--developer-instructions-file"]);
@@ -44374,17 +44374,211 @@ function installInteractiveConsoleInterception(runtimeLogger) {
44374
44374
  // src/doctor.ts
44375
44375
  import { spawnSync } from "node:child_process";
44376
44376
  import { createRequire } from "node:module";
44377
- import * as fs4 from "node:fs";
44377
+ import * as fs5 from "node:fs";
44378
44378
  import * as os from "node:os";
44379
- import * as path4 from "node:path";
44379
+ import * as path5 from "node:path";
44380
44380
  import { delimiter } from "node:path";
44381
44381
 
44382
44382
  // src/core/auth-storage.ts
44383
- 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 {
44384
44547
  providerApiKeys = /* @__PURE__ */ new Map();
44385
44548
  runtimeApiKeys = /* @__PURE__ */ new Map();
44386
44549
  providerEnvVars = /* @__PURE__ */ new Map();
44387
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
+ }
44388
44582
  setLegacyApiKey(apiKey) {
44389
44583
  this.legacyApiKey = apiKey;
44390
44584
  }
@@ -44412,20 +44606,104 @@ var AuthStorage = class {
44412
44606
  getProviderEnvVar(provider) {
44413
44607
  return this.providerEnvVars.get(provider);
44414
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
+ }
44415
44634
  getApiKey(provider, explicitEnvVar) {
44416
44635
  const envVar = explicitEnvVar || this.getProviderEnvVar(provider);
44417
44636
  const envApiKey = envVar && typeof process !== "undefined" ? process.env?.[envVar] : void 0;
44418
- 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;
44419
44647
  }
44420
44648
  };
44421
44649
 
44422
44650
  // src/core/model-registry.ts
44423
- import * as fs2 from "fs";
44424
- import * as path2 from "path";
44651
+ import * as fs3 from "fs";
44652
+ import * as path3 from "path";
44425
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
+ }
44426
44687
  var REMOTE_CATALOG_CACHE_VERSION = 4;
44427
44688
  var SUPPORTED_REMOTE_APIS = /* @__PURE__ */ new Set(["openai-completions", "anthropic-messages"]);
44428
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
+ }
44429
44707
  var REMOTE_REFRESH_LOCK_WAIT_MS = 6e4;
44430
44708
  var REMOTE_REFRESH_LOCK_WRITE_GRACE_MS = 1e3;
44431
44709
  var remoteRefreshTails = /* @__PURE__ */ new Map();
@@ -44449,40 +44727,40 @@ function remoteIdentityDigest(remote) {
44449
44727
  return createHash("sha256").update(JSON.stringify({ version: REMOTE_CATALOG_CACHE_VERSION, ...canonicalRemote(remote) })).digest("hex");
44450
44728
  }
44451
44729
  function remoteCacheDirectory(modelsPath) {
44452
- return path2.join(path2.dirname(modelsPath), "cache", "remote-catalogs");
44730
+ return path3.join(path3.dirname(modelsPath), "cache", "remote-catalogs");
44453
44731
  }
44454
44732
  function safeRemoteCacheDirectory(modelsPath, create) {
44455
- const configDirectory = path2.dirname(modelsPath);
44456
- if (!fs2.existsSync(configDirectory)) {
44733
+ const configDirectory = path3.dirname(modelsPath);
44734
+ if (!fs3.existsSync(configDirectory)) {
44457
44735
  if (!create) return void 0;
44458
- fs2.mkdirSync(configDirectory, { recursive: true, mode: 448 });
44736
+ fs3.mkdirSync(configDirectory, { recursive: true, mode: 448 });
44459
44737
  }
44460
- const configRoot = fs2.realpathSync(configDirectory);
44461
- const cacheRoot = path2.join(configDirectory, "cache");
44738
+ const configRoot = fs3.realpathSync(configDirectory);
44739
+ const cacheRoot = path3.join(configDirectory, "cache");
44462
44740
  const directory = remoteCacheDirectory(modelsPath);
44463
44741
  for (const candidate of [cacheRoot, directory]) {
44464
- if (!fs2.existsSync(candidate)) {
44742
+ if (!fs3.existsSync(candidate)) {
44465
44743
  if (!create) return void 0;
44466
- fs2.mkdirSync(candidate, { mode: 448 });
44744
+ fs3.mkdirSync(candidate, { mode: 448 });
44467
44745
  }
44468
- const stat2 = fs2.lstatSync(candidate);
44746
+ const stat2 = fs3.lstatSync(candidate);
44469
44747
  if (stat2.isSymbolicLink() || !stat2.isDirectory()) throw new Error(`remote catalog cache path must be a real directory: ${candidate}`);
44470
- const canonical = fs2.realpathSync(candidate);
44471
- const relative2 = path2.relative(configRoot, canonical);
44472
- 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)) {
44473
44751
  throw new Error(`remote catalog cache path escapes the config directory: ${candidate}`);
44474
44752
  }
44475
44753
  try {
44476
- fs2.chmodSync(candidate, 448);
44754
+ fs3.chmodSync(candidate, 448);
44477
44755
  } catch {
44478
44756
  }
44479
44757
  }
44480
44758
  return directory;
44481
44759
  }
44482
44760
  function remoteRefreshLockPath(modelsPath) {
44483
- return path2.join(remoteCacheDirectory(modelsPath), ".refresh.lock");
44761
+ return path3.join(remoteCacheDirectory(modelsPath), ".refresh.lock");
44484
44762
  }
44485
- function processIsAlive(pid) {
44763
+ function processIsAlive2(pid) {
44486
44764
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
44487
44765
  try {
44488
44766
  process.kill(pid, 0);
@@ -44492,40 +44770,40 @@ function processIsAlive(pid) {
44492
44770
  }
44493
44771
  }
44494
44772
  function remoteRefreshLockIsStale(lockPath) {
44495
- const stat2 = fs2.lstatSync(lockPath);
44773
+ const stat2 = fs3.lstatSync(lockPath);
44496
44774
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog refresh lock must be a regular file: ${lockPath}`);
44497
44775
  let owner;
44498
44776
  try {
44499
- owner = JSON.parse(fs2.readFileSync(lockPath, "utf8"));
44777
+ owner = JSON.parse(fs3.readFileSync(lockPath, "utf8"));
44500
44778
  } catch {
44501
44779
  }
44502
44780
  const age = Date.now() - stat2.mtimeMs;
44503
44781
  const validOwner = owner?.version === 1 && Number.isSafeInteger(owner.pid) && typeof owner.token === "string" && typeof owner.createdAt === "number";
44504
- if (validOwner && processIsAlive(Number(owner.pid))) return false;
44782
+ if (validOwner && processIsAlive2(Number(owner.pid))) return false;
44505
44783
  return validOwner || age > REMOTE_REFRESH_LOCK_WRITE_GRACE_MS;
44506
44784
  }
44507
44785
  function sameFileIdentity(left, right, leftPath, rightPath) {
44508
44786
  if (left.dev !== right.dev || left.ino !== right.ino) return false;
44509
44787
  if (left.ino !== 0) return true;
44510
- 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));
44511
44789
  }
44512
44790
  function reclaimStaleRemoteRefreshLock(lockPath) {
44513
44791
  if (!remoteRefreshLockIsStale(lockPath)) return false;
44514
44792
  const claimPath = `${lockPath}.reclaim-${process.pid}-${randomBytes(8).toString("hex")}`;
44515
44793
  try {
44516
- fs2.linkSync(lockPath, claimPath);
44517
- const claimed = fs2.lstatSync(claimPath);
44518
- const current = fs2.lstatSync(lockPath);
44794
+ fs3.linkSync(lockPath, claimPath);
44795
+ const claimed = fs3.lstatSync(claimPath);
44796
+ const current = fs3.lstatSync(lockPath);
44519
44797
  if (claimed.isSymbolicLink() || !claimed.isFile() || current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(claimed, current, claimPath, lockPath)) return false;
44520
44798
  if (!remoteRefreshLockIsStale(claimPath)) return false;
44521
- fs2.unlinkSync(lockPath);
44799
+ fs3.unlinkSync(lockPath);
44522
44800
  return true;
44523
44801
  } catch (error) {
44524
44802
  if (["ENOENT", "EEXIST"].includes(error.code ?? "")) return false;
44525
44803
  throw error;
44526
44804
  } finally {
44527
44805
  try {
44528
- fs2.unlinkSync(claimPath);
44806
+ fs3.unlinkSync(claimPath);
44529
44807
  } catch {
44530
44808
  }
44531
44809
  }
@@ -44537,19 +44815,19 @@ async function acquireRemoteRefreshLock(modelsPath) {
44537
44815
  while (true) {
44538
44816
  const token = randomBytes(16).toString("hex");
44539
44817
  try {
44540
- const fd = fs2.openSync(lockPath, "wx", 384);
44818
+ const fd = fs3.openSync(lockPath, "wx", 384);
44541
44819
  try {
44542
- 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() })}
44543
44821
  `);
44544
44822
  } finally {
44545
- fs2.closeSync(fd);
44823
+ fs3.closeSync(fd);
44546
44824
  }
44547
44825
  return () => {
44548
44826
  try {
44549
- const stat2 = fs2.lstatSync(lockPath);
44827
+ const stat2 = fs3.lstatSync(lockPath);
44550
44828
  if (stat2.isSymbolicLink() || !stat2.isFile()) return;
44551
- const current = JSON.parse(fs2.readFileSync(lockPath, "utf8"));
44552
- if (current.token === token) fs2.unlinkSync(lockPath);
44829
+ const current = JSON.parse(fs3.readFileSync(lockPath, "utf8"));
44830
+ if (current.token === token) fs3.unlinkSync(lockPath);
44553
44831
  } catch {
44554
44832
  }
44555
44833
  };
@@ -44562,29 +44840,29 @@ async function acquireRemoteRefreshLock(modelsPath) {
44562
44840
  throw lockError;
44563
44841
  }
44564
44842
  if (Date.now() >= deadline) throw new Error(`timed out waiting for remote catalog refresh lock: ${lockPath}`);
44565
- await new Promise((resolve6) => setTimeout(resolve6, 25));
44843
+ await new Promise((resolve7) => setTimeout(resolve7, 25));
44566
44844
  }
44567
44845
  }
44568
44846
  }
44569
44847
  function readSafeRemoteCacheFile(directory, filepath) {
44570
- const stat2 = fs2.lstatSync(filepath);
44848
+ const stat2 = fs3.lstatSync(filepath);
44571
44849
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog cache entry must be a regular file: ${filepath}`);
44572
- const canonicalDirectory = fs2.realpathSync(directory);
44573
- const canonicalFile = fs2.realpathSync(filepath);
44574
- const relative2 = path2.relative(canonicalDirectory, canonicalFile);
44575
- 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)) {
44576
44854
  throw new Error(`remote catalog cache entry escapes its cache directory: ${filepath}`);
44577
44855
  }
44578
- return fs2.readFileSync(filepath);
44856
+ return fs3.readFileSync(filepath);
44579
44857
  }
44580
44858
  function credentialNamespaceKey(modelsPath, create) {
44581
44859
  const directory = safeRemoteCacheDirectory(modelsPath, create);
44582
44860
  if (!directory) return void 0;
44583
- const keyPath = path2.join(directory, ".credential-namespace-key");
44584
- if (!fs2.existsSync(keyPath) && !create) return void 0;
44585
- 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)) {
44586
44864
  try {
44587
- fs2.writeFileSync(keyPath, randomBytes(32), { flag: "wx", mode: 384 });
44865
+ fs3.writeFileSync(keyPath, randomBytes(32), { flag: "wx", mode: 384 });
44588
44866
  } catch (error) {
44589
44867
  if (!isPlainRecord(error) || error.code !== "EEXIST") throw error;
44590
44868
  }
@@ -44592,7 +44870,7 @@ function credentialNamespaceKey(modelsPath, create) {
44592
44870
  const key = readSafeRemoteCacheFile(directory, keyPath);
44593
44871
  if (key.byteLength !== 32) throw new Error("remote catalog credential namespace key is invalid");
44594
44872
  try {
44595
- fs2.chmodSync(keyPath, 384);
44873
+ fs3.chmodSync(keyPath, 384);
44596
44874
  } catch {
44597
44875
  }
44598
44876
  return key;
@@ -44603,28 +44881,28 @@ function credentialFingerprint(modelsPath, remote, credential, create = false) {
44603
44881
  return createHmac("sha256", key).update(remoteIdentityDigest(remote)).update("\0").update(credential).digest("hex");
44604
44882
  }
44605
44883
  function remoteCachePath(modelsPath, remote, fingerprint) {
44606
- return path2.join(remoteCacheDirectory(modelsPath), `${remoteIdentityDigest(remote)}-${fingerprint}.json`);
44884
+ return path3.join(remoteCacheDirectory(modelsPath), `${remoteIdentityDigest(remote)}-${fingerprint}.json`);
44607
44885
  }
44608
44886
  function pruneLegacyRemoteCatalogCaches(modelsPath) {
44609
44887
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44610
44888
  if (!directory) return;
44611
- for (const name of fs2.readdirSync(directory)) {
44889
+ for (const name of fs3.readdirSync(directory)) {
44612
44890
  if (!name.endsWith(".json")) continue;
44613
- const filepath = path2.join(directory, name);
44891
+ const filepath = path3.join(directory, name);
44614
44892
  try {
44615
44893
  const value = JSON.parse(readSafeRemoteCacheFile(directory, filepath).toString("utf8"));
44616
44894
  if (value.version === REMOTE_CATALOG_CACHE_VERSION) continue;
44617
44895
  } catch {
44618
44896
  let stat2;
44619
44897
  try {
44620
- stat2 = fs2.lstatSync(filepath);
44898
+ stat2 = fs3.lstatSync(filepath);
44621
44899
  } catch {
44622
44900
  continue;
44623
44901
  }
44624
44902
  if (stat2.isSymbolicLink() || !stat2.isFile()) continue;
44625
44903
  }
44626
44904
  try {
44627
- fs2.unlinkSync(filepath);
44905
+ fs3.unlinkSync(filepath);
44628
44906
  } catch {
44629
44907
  }
44630
44908
  }
@@ -44633,7 +44911,7 @@ function scrubActiveSecretsFromRemoteCaches(modelsPath, activeSecrets) {
44633
44911
  if (activeSecrets.length === 0) return;
44634
44912
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44635
44913
  if (!directory) return;
44636
- const handle = fs2.opendirSync(directory);
44914
+ const handle = fs3.opendirSync(directory);
44637
44915
  try {
44638
44916
  let scanned = 0;
44639
44917
  while (true) {
@@ -44643,14 +44921,14 @@ function scrubActiveSecretsFromRemoteCaches(modelsPath, activeSecrets) {
44643
44921
  const isCache = entry.name.endsWith(".json");
44644
44922
  const isOrphanedTemporary = /^\..+\.json\.\d+\.[0-9a-f]+\.tmp$/.test(entry.name);
44645
44923
  if (!entry.isFile() || !isCache && !isOrphanedTemporary) continue;
44646
- const filepath = path2.join(directory, entry.name);
44924
+ const filepath = path3.join(directory, entry.name);
44647
44925
  try {
44648
44926
  const raw = readSafeRemoteCacheFile(directory, filepath).toString("utf8");
44649
44927
  assertNoSecretSubstrings(raw, activeSecrets, "remote catalog cache");
44650
44928
  } catch {
44651
44929
  try {
44652
- const stat2 = fs2.lstatSync(filepath);
44653
- if (!stat2.isSymbolicLink() && stat2.isFile()) fs2.unlinkSync(filepath);
44930
+ const stat2 = fs3.lstatSync(filepath);
44931
+ if (!stat2.isSymbolicLink() && stat2.isFile()) fs3.unlinkSync(filepath);
44654
44932
  } catch {
44655
44933
  }
44656
44934
  }
@@ -44721,7 +44999,7 @@ function readRemoteCache(modelsPath, remote, fingerprint, activeSecrets) {
44721
44999
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44722
45000
  if (!directory) return void 0;
44723
45001
  const tombstone = remoteCacheTombstonePath(modelsPath, remote, fingerprint);
44724
- if (fs2.existsSync(tombstone)) return void 0;
45002
+ if (fs3.existsSync(tombstone)) return void 0;
44725
45003
  const value = JSON.parse(readSafeRemoteCacheFile(directory, remoteCachePath(modelsPath, remote, fingerprint)).toString("utf8"));
44726
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;
44727
45005
  assertNoSecretSubstrings({ etag: value.etag, payload: value.payload }, activeSecrets, "remote catalog cache");
@@ -44741,23 +45019,23 @@ function removeRemoteCache(modelsPath, remote, fingerprint) {
44741
45019
  const directory = safeRemoteCacheDirectory(modelsPath, false);
44742
45020
  if (!directory) return;
44743
45021
  const filepath = remoteCachePath(modelsPath, remote, fingerprint);
44744
- if (fs2.existsSync(filepath)) {
44745
- const stat2 = fs2.lstatSync(filepath);
45022
+ if (fs3.existsSync(filepath)) {
45023
+ const stat2 = fs3.lstatSync(filepath);
44746
45024
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog cache entry is not a regular file: ${filepath}`);
44747
- fs2.unlinkSync(filepath);
45025
+ fs3.unlinkSync(filepath);
44748
45026
  }
44749
45027
  const tombstone = remoteCacheTombstonePath(modelsPath, remote, fingerprint);
44750
- if (fs2.existsSync(tombstone)) fs2.unlinkSync(tombstone);
45028
+ if (fs3.existsSync(tombstone)) fs3.unlinkSync(tombstone);
44751
45029
  return;
44752
45030
  } catch (error) {
44753
45031
  const directory = safeRemoteCacheDirectory(modelsPath, true);
44754
45032
  if (!directory) throw error;
44755
45033
  const tombstone = remoteCacheTombstonePath(modelsPath, remote, fingerprint);
44756
45034
  try {
44757
- fs2.writeFileSync(tombstone, `${JSON.stringify({ version: 1, revoked: true })}
45035
+ fs3.writeFileSync(tombstone, `${JSON.stringify({ version: 1, revoked: true })}
44758
45036
  `, { flag: "wx", mode: 384 });
44759
45037
  } catch (tombstoneError) {
44760
- if (!fs2.existsSync(tombstone)) throw tombstoneError;
45038
+ if (!fs3.existsSync(tombstone)) throw tombstoneError;
44761
45039
  }
44762
45040
  }
44763
45041
  }
@@ -44819,7 +45097,7 @@ function validatedRemoteCatalogs(value) {
44819
45097
  }
44820
45098
  function validateModelsDocumentShape(value) {
44821
45099
  if (!isPlainRecord(value)) throw new Error("models.json must be an object");
44822
- assertOnlyKeys(value, ["$schema", "profiles", "remoteCatalogs", "defaults"], "models.json");
45100
+ assertOnlyKeys(value, ["$schema", "profiles", "remoteCatalogs", "defaults", "modelOverrides"], "models.json");
44823
45101
  if (value.$schema !== void 0 && (typeof value.$schema !== "string" || !value.$schema)) throw new Error("models.json $schema must be a non-empty string");
44824
45102
  const hasProfiles = Object.prototype.hasOwnProperty.call(value, "profiles");
44825
45103
  const hasRemoteCatalogs = Object.prototype.hasOwnProperty.call(value, "remoteCatalogs");
@@ -44832,6 +45110,7 @@ function validateModelsDocumentShape(value) {
44832
45110
  validateProfileConfig(profileId, profile);
44833
45111
  }
44834
45112
  }
45113
+ validateModelOverrides(value.modelOverrides);
44835
45114
  if (value.defaults !== void 0) {
44836
45115
  if (!isPlainRecord(value.defaults)) throw new Error("models.json defaults must be an object");
44837
45116
  assertOnlyKeys(value.defaults, ["provider", "modelId", "modelClasses", "toolModels"], "models.json defaults");
@@ -44916,6 +45195,8 @@ function validateCompat(value, label) {
44916
45195
  "requiresAssistantAfterToolResult",
44917
45196
  "requiresThinkingAsText",
44918
45197
  "supportsStrictMode",
45198
+ "streamRequiresDone",
45199
+ "streamRequiresFinishReason",
44919
45200
  "cacheCapability"
44920
45201
  ], label);
44921
45202
  if (value.forcedReasoningEffort !== void 0 && value.forcedReasoningEffort !== "none") {
@@ -44929,7 +45210,9 @@ function validateCompat(value, label) {
44929
45210
  "requiresToolResultName",
44930
45211
  "requiresAssistantAfterToolResult",
44931
45212
  "requiresThinkingAsText",
44932
- "supportsStrictMode"
45213
+ "supportsStrictMode",
45214
+ "streamRequiresDone",
45215
+ "streamRequiresFinishReason"
44933
45216
  ]) assertOptionalBoolean(value[field], `${label}.${field}`);
44934
45217
  if (value.maxTokensField !== void 0 && value.maxTokensField !== "max_completion_tokens" && value.maxTokensField !== "max_tokens") {
44935
45218
  throw new Error(`${label}.maxTokensField is invalid`);
@@ -45108,6 +45391,46 @@ async function readResponseTextBounded(response, maxBytes) {
45108
45391
  reader.releaseLock();
45109
45392
  }
45110
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
+ }
45111
45434
  function validateProfileConfig(profileId, profileConfig) {
45112
45435
  if (!isPlainRecord(profileConfig)) throw new Error(`Profile "${profileId}" must be an object.`);
45113
45436
  assertOnlyKeys(profileConfig, [
@@ -45202,12 +45525,14 @@ var ModelRegistry = class {
45202
45525
  modelsPath;
45203
45526
  configDefaults;
45204
45527
  modelOrder = [];
45528
+ modelOverrides = {};
45529
+ extensionOAuthProviders = /* @__PURE__ */ new Map();
45205
45530
  usingExternalProviders = false;
45206
45531
  localProfileIds = /* @__PURE__ */ new Set();
45207
45532
  remoteCredentialEnvByProvider = /* @__PURE__ */ new Map();
45208
45533
  remoteCatalogByProvider = /* @__PURE__ */ new Map();
45209
45534
  remoteCredentialFingerprintByCatalog = /* @__PURE__ */ new Map();
45210
- constructor(authStorage, modelsPath = path2.join(getConfigDir(), "models.json")) {
45535
+ constructor(authStorage, modelsPath = path3.join(getConfigDir(), "models.json")) {
45211
45536
  this.authStorage = authStorage;
45212
45537
  this.modelsPath = modelsPath;
45213
45538
  this.load();
@@ -45228,6 +45553,7 @@ var ModelRegistry = class {
45228
45553
  this.loadError = void 0;
45229
45554
  this.models.clear();
45230
45555
  this.modelOrder = [];
45556
+ this.modelOverrides = {};
45231
45557
  this.configDefaults = void 0;
45232
45558
  this.usingExternalProviders = false;
45233
45559
  this.localProfileIds.clear();
@@ -45240,16 +45566,17 @@ var ModelRegistry = class {
45240
45566
  this.loadError = error instanceof Error ? error.message : String(error);
45241
45567
  return;
45242
45568
  }
45243
- if (!fs2.existsSync(this.modelsPath)) {
45569
+ if (!fs3.existsSync(this.modelsPath)) {
45244
45570
  return;
45245
45571
  }
45246
45572
  try {
45247
- const raw = JSON.parse(fs2.readFileSync(this.modelsPath, "utf-8"));
45573
+ const raw = JSON.parse(fs3.readFileSync(this.modelsPath, "utf-8"));
45248
45574
  if (raw && typeof raw === "object" && "providers" in raw) {
45249
45575
  throw new Error('Legacy models.json format is no longer supported. Use top-level "profiles".');
45250
45576
  }
45251
45577
  validateModelsDocumentShape(raw);
45252
45578
  const remotes = validatedRemoteCatalogs(raw.remoteCatalogs);
45579
+ this.modelOverrides = raw.modelOverrides ?? {};
45253
45580
  const activeRemoteSecrets = configuredRemoteCredentialValues(raw);
45254
45581
  scrubActiveSecretsFromRemoteCaches(this.modelsPath, activeRemoteSecrets);
45255
45582
  const configuredProviders = Object.entries(raw.profiles || {});
@@ -45335,10 +45662,40 @@ var ModelRegistry = class {
45335
45662
  this.registerRemoteProfile(provider, profile, identity.apiKeyEnv, owner);
45336
45663
  }
45337
45664
  }
45665
+ this.applyModelOverrides();
45338
45666
  } catch (error) {
45339
45667
  this.loadError = error instanceof Error ? error.message : String(error);
45340
45668
  }
45341
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
+ }
45342
45699
  registerRemoteProfile(provider, providerConfig, credentialEnv, catalogId) {
45343
45700
  if (this.localProfileIds.has(provider)) return;
45344
45701
  const existingCatalog = this.remoteCatalogByProvider.get(provider);
@@ -45398,27 +45755,28 @@ var ModelRegistry = class {
45398
45755
  for (const [provider, profile] of candidates) {
45399
45756
  this.registerRemoteProfile(provider, profile, credentialEnv, catalogId);
45400
45757
  }
45758
+ this.applyModelOverrides();
45401
45759
  }
45402
45760
  writeRemoteCache(remote, fingerprint, value) {
45403
45761
  const directory = safeRemoteCacheDirectory(this.modelsPath, true);
45404
45762
  const target = remoteCachePath(this.modelsPath, remote, fingerprint);
45405
- if (fs2.existsSync(target)) {
45406
- const stat2 = fs2.lstatSync(target);
45763
+ if (fs3.existsSync(target)) {
45764
+ const stat2 = fs3.lstatSync(target);
45407
45765
  if (stat2.isSymbolicLink() || !stat2.isFile()) throw new Error(`remote catalog cache entry must be a regular file: ${target}`);
45408
45766
  }
45409
- 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`);
45410
45768
  try {
45411
- fs2.writeFileSync(temporary, `${JSON.stringify(value)}
45769
+ fs3.writeFileSync(temporary, `${JSON.stringify(value)}
45412
45770
  `, { flag: "wx", mode: 384 });
45413
- fs2.renameSync(temporary, target);
45771
+ fs3.renameSync(temporary, target);
45414
45772
  const tombstone = remoteCacheTombstonePath(this.modelsPath, remote, fingerprint);
45415
- if (fs2.existsSync(tombstone)) fs2.unlinkSync(tombstone);
45773
+ if (fs3.existsSync(tombstone)) fs3.unlinkSync(tombstone);
45416
45774
  } finally {
45417
- if (fs2.existsSync(temporary)) fs2.unlinkSync(temporary);
45775
+ if (fs3.existsSync(temporary)) fs3.unlinkSync(temporary);
45418
45776
  }
45419
45777
  }
45420
45778
  refreshRemoteCatalogs() {
45421
- const queueKey = path2.resolve(this.modelsPath);
45779
+ const queueKey = path3.resolve(this.modelsPath);
45422
45780
  const previous = remoteRefreshTails.get(queueKey) ?? Promise.resolve();
45423
45781
  const refresh = previous.then(() => this.performRemoteCatalogRefresh());
45424
45782
  const tail = refresh.catch(() => void 0);
@@ -45443,14 +45801,14 @@ var ModelRegistry = class {
45443
45801
  this.loadError = error instanceof Error ? error.message : String(error);
45444
45802
  return;
45445
45803
  }
45446
- if (!fs2.existsSync(this.modelsPath)) {
45804
+ if (!fs3.existsSync(this.modelsPath)) {
45447
45805
  for (const owner of new Set(this.remoteCatalogByProvider.values())) this.removeRemoteCatalogProfiles(owner);
45448
45806
  this.remoteCredentialFingerprintByCatalog.clear();
45449
45807
  return;
45450
45808
  }
45451
45809
  let config;
45452
45810
  try {
45453
- const decoded = JSON.parse(fs2.readFileSync(this.modelsPath, "utf8"));
45811
+ const decoded = JSON.parse(fs3.readFileSync(this.modelsPath, "utf8"));
45454
45812
  validateModelsDocumentShape(decoded);
45455
45813
  config = decoded;
45456
45814
  } catch {
@@ -45458,6 +45816,7 @@ var ModelRegistry = class {
45458
45816
  this.remoteCredentialFingerprintByCatalog.clear();
45459
45817
  return;
45460
45818
  }
45819
+ this.modelOverrides = config.modelOverrides ?? {};
45461
45820
  let remotes;
45462
45821
  try {
45463
45822
  remotes = validatedRemoteCatalogs(config.remoteCatalogs);
@@ -45739,11 +46098,131 @@ var ModelRegistry = class {
45739
46098
  if (remoteEnv) return typeof process !== "undefined" ? process.env?.[remoteEnv] : void 0;
45740
46099
  return this.authStorage.getApiKey(provider);
45741
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
+ }
45742
46221
  };
45743
46222
 
45744
46223
  // src/core/settings-manager.ts
45745
- import * as fs3 from "fs";
45746
- import * as path3 from "path";
46224
+ import * as fs4 from "fs";
46225
+ import * as path4 from "path";
45747
46226
  var SettingsManager = class {
45748
46227
  settingsPath;
45749
46228
  legacyConfigPath;
@@ -45757,9 +46236,9 @@ var SettingsManager = class {
45757
46236
  this.legacyConfig = this.loadLegacyConfigFile();
45758
46237
  }
45759
46238
  readJson(filePath) {
45760
- if (!fs3.existsSync(filePath)) return null;
46239
+ if (!fs4.existsSync(filePath)) return null;
45761
46240
  try {
45762
- return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
46241
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
45763
46242
  } catch {
45764
46243
  return null;
45765
46244
  }
@@ -45771,19 +46250,19 @@ var SettingsManager = class {
45771
46250
  return this.readJson(this.legacyConfigPath) || {};
45772
46251
  }
45773
46252
  ensureDir() {
45774
- fs3.mkdirSync(getConfigDir(), { recursive: true });
46253
+ fs4.mkdirSync(getConfigDir(), { recursive: true });
45775
46254
  }
45776
46255
  writeSettings() {
45777
46256
  this.ensureDir();
45778
- fs3.writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2));
46257
+ fs4.writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2));
45779
46258
  }
45780
46259
  writeSettingsValue(settings) {
45781
46260
  this.ensureDir();
45782
- fs3.writeFileSync(this.settingsPath, JSON.stringify(settings, null, 2));
46261
+ fs4.writeFileSync(this.settingsPath, JSON.stringify(settings, null, 2));
45783
46262
  }
45784
46263
  writeLegacyConfig() {
45785
46264
  this.ensureDir();
45786
- fs3.writeFileSync(this.legacyConfigPath, JSON.stringify(this.legacyConfig, null, 2));
46265
+ fs4.writeFileSync(this.legacyConfigPath, JSON.stringify(this.legacyConfig, null, 2));
45787
46266
  }
45788
46267
  getLegacyApiKey() {
45789
46268
  return this.legacyConfig.apiKey;
@@ -45832,6 +46311,9 @@ var SettingsManager = class {
45832
46311
  }
45833
46312
  return "auto";
45834
46313
  }
46314
+ getBellNotificationsEnabled() {
46315
+ return this.settings.notifications?.bell !== false;
46316
+ }
45835
46317
  // ==========================================================================
45836
46318
  // Extension enablement (user ~/.pie/settings.json + project ./.pie/settings.json)
45837
46319
  // ==========================================================================
@@ -45840,8 +46322,8 @@ var SettingsManager = class {
45840
46322
  }
45841
46323
  writeProjectSettings(cwd, settings) {
45842
46324
  const settingsPath = getProjectSettingsPath(cwd);
45843
- fs3.mkdirSync(path3.dirname(settingsPath), { recursive: true });
45844
- 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));
45845
46327
  }
45846
46328
  /** Raw extension enablement map for one scope. */
45847
46329
  getExtensionSettings(scope = "user", cwd = process.cwd()) {
@@ -45915,19 +46397,19 @@ function checkShell() {
45915
46397
  }
45916
46398
  function canWriteDirectory(dir) {
45917
46399
  try {
45918
- fs4.mkdirSync(dir, { recursive: true });
45919
- const probe = path4.join(dir, `.pie-doctor-${process.pid}-${Date.now()}`);
45920
- fs4.writeFileSync(probe, "ok", "utf8");
45921
- 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 });
45922
46404
  return { ok: true };
45923
46405
  } catch (error) {
45924
46406
  return { ok: false, error: error instanceof Error ? error.message : String(error) };
45925
46407
  }
45926
46408
  }
45927
46409
  function isExecutable(candidate) {
45928
- if (!candidate || !fs4.existsSync(candidate)) return false;
46410
+ if (!candidate || !fs5.existsSync(candidate)) return false;
45929
46411
  try {
45930
- fs4.accessSync(candidate, fs4.constants.X_OK);
46412
+ fs5.accessSync(candidate, fs5.constants.X_OK);
45931
46413
  return true;
45932
46414
  } catch {
45933
46415
  return os.platform() === "win32";
@@ -45937,7 +46419,7 @@ function findOnPath(names) {
45937
46419
  for (const dir of (process.env.PATH || "").split(delimiter)) {
45938
46420
  if (!dir) continue;
45939
46421
  for (const name of names) {
45940
- const candidate = path4.join(dir, name);
46422
+ const candidate = path5.join(dir, name);
45941
46423
  if (isExecutable(candidate)) return candidate;
45942
46424
  }
45943
46425
  }
@@ -45953,10 +46435,10 @@ function findDoctorChromeExecutable() {
45953
46435
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
45954
46436
  "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
45955
46437
  ] : os.platform() === "win32" ? [
45956
- path4.join(process.env.PROGRAMFILES || "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
45957
- path4.join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "Google", "Chrome", "Application", "chrome.exe"),
45958
- path4.join(process.env.LOCALAPPDATA || path4.join(home, "AppData", "Local"), "Google", "Chrome", "Application", "chrome.exe"),
45959
- 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")
45960
46442
  ] : [
45961
46443
  "/usr/bin/google-chrome",
45962
46444
  "/usr/bin/google-chrome-stable",
@@ -46005,7 +46487,7 @@ function checkPlaywrightBrowser() {
46005
46487
  } catch {
46006
46488
  executablePath = void 0;
46007
46489
  }
46008
- if (executablePath && fs4.existsSync(executablePath)) {
46490
+ if (executablePath && fs5.existsSync(executablePath)) {
46009
46491
  return {
46010
46492
  id: "playwright_browser",
46011
46493
  label: "Playwright browser",
@@ -46014,7 +46496,7 @@ function checkPlaywrightBrowser() {
46014
46496
  details: { executablePath }
46015
46497
  };
46016
46498
  }
46017
- const skillWrapper = path4.join(getBuiltinDir(), "skills", "browser-tools", "playwright-cli.js");
46499
+ const skillWrapper = path5.join(getBuiltinDir(), "skills", "browser-tools", "playwright-cli.js");
46018
46500
  return {
46019
46501
  id: "playwright_browser",
46020
46502
  label: "Playwright browser",
@@ -46045,8 +46527,8 @@ function isLiveUnityInstance(instance, nowUnix = Math.floor(Date.now() / 1e3)) {
46045
46527
  return isProcessAlive(Number(instance.pid || 0));
46046
46528
  }
46047
46529
  function checkUnityRegistry() {
46048
- const registryDir = path4.join(os.homedir(), ".pie-unity", "instances");
46049
- if (!fs4.existsSync(registryDir)) {
46530
+ const registryDir = path5.join(os.homedir(), ".pie-unity", "instances");
46531
+ if (!fs5.existsSync(registryDir)) {
46050
46532
  return {
46051
46533
  id: "unity_bridge",
46052
46534
  label: "Unity bridge",
@@ -46057,12 +46539,12 @@ function checkUnityRegistry() {
46057
46539
  }
46058
46540
  try {
46059
46541
  const nowUnix = Math.floor(Date.now() / 1e3);
46060
- 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));
46061
46543
  let malformedFiles = 0;
46062
46544
  let invalidFiles = 0;
46063
46545
  const liveInstances = files.map((filePath) => {
46064
46546
  try {
46065
- const parsed = JSON.parse(fs4.readFileSync(filePath, "utf8").replace(/^\uFEFF/, ""));
46547
+ const parsed = JSON.parse(fs5.readFileSync(filePath, "utf8").replace(/^\uFEFF/, ""));
46066
46548
  if (!isRecord(parsed)) {
46067
46549
  invalidFiles += 1;
46068
46550
  return null;
@@ -46136,13 +46618,13 @@ async function runDoctor() {
46136
46618
  const packageDir = getPackageDir();
46137
46619
  let dir = packageDir;
46138
46620
  let lockPath;
46139
- while (dir !== path4.dirname(dir)) {
46140
- const candidate = path4.join(dir, "package-lock.json");
46141
- if (fs4.existsSync(candidate)) {
46621
+ while (dir !== path5.dirname(dir)) {
46622
+ const candidate = path5.join(dir, "package-lock.json");
46623
+ if (fs5.existsSync(candidate)) {
46142
46624
  lockPath = candidate;
46143
46625
  break;
46144
46626
  }
46145
- dir = path4.dirname(dir);
46627
+ dir = path5.dirname(dir);
46146
46628
  }
46147
46629
  checks.push({
46148
46630
  id: "package_lock",
@@ -46164,7 +46646,7 @@ async function runDoctor() {
46164
46646
  message: result.ok ? `${dirPath} is writable.` : `${dirPath} is not writable: ${result.error}`
46165
46647
  });
46166
46648
  }
46167
- const authStorage = new AuthStorage();
46649
+ const authStorage = AuthStorage.create();
46168
46650
  const registry = new ModelRegistry(authStorage);
46169
46651
  const loadError = registry.getLoadError();
46170
46652
  const models = registry.getAll();
@@ -46305,10 +46787,10 @@ Disable or narrow capabilities:
46305
46787
  }
46306
46788
 
46307
46789
  // src/commands/models-config.ts
46308
- import * as fs5 from "node:fs";
46309
- import * as path5 from "node:path";
46790
+ import * as fs6 from "node:fs";
46791
+ import * as path6 from "node:path";
46310
46792
  function getDefaultModelsPath() {
46311
- return path5.join(getConfigDir(), "models.json");
46793
+ return path6.join(getConfigDir(), "models.json");
46312
46794
  }
46313
46795
  function createModelsTemplate() {
46314
46796
  const template = {
@@ -46403,7 +46885,7 @@ function createModelsTemplate() {
46403
46885
  `;
46404
46886
  }
46405
46887
  function resolveModelsPath(input) {
46406
- return input ? path5.resolve(input) : getDefaultModelsPath();
46888
+ return input ? path6.resolve(input) : getDefaultModelsPath();
46407
46889
  }
46408
46890
  function initModelsConfig(options = {}) {
46409
46891
  const targetPath = resolveModelsPath(options.path);
@@ -46411,16 +46893,16 @@ function initModelsConfig(options = {}) {
46411
46893
  if (options.stdout) {
46412
46894
  return { path: targetPath, content, wrote: false };
46413
46895
  }
46414
- if (fs5.existsSync(targetPath) && !options.force) {
46896
+ if (fs6.existsSync(targetPath) && !options.force) {
46415
46897
  throw new Error(`models.json already exists: ${targetPath}. Use --force to overwrite, or --stdout to print the template.`);
46416
46898
  }
46417
- fs5.mkdirSync(path5.dirname(targetPath), { recursive: true });
46418
- fs5.writeFileSync(targetPath, content, "utf8");
46899
+ fs6.mkdirSync(path6.dirname(targetPath), { recursive: true });
46900
+ fs6.writeFileSync(targetPath, content, "utf8");
46419
46901
  return { path: targetPath, content, wrote: true };
46420
46902
  }
46421
46903
  function validateModelsConfig(options = {}) {
46422
46904
  const modelsPath = resolveModelsPath(options.path);
46423
- if (!fs5.existsSync(modelsPath)) {
46905
+ if (!fs6.existsSync(modelsPath)) {
46424
46906
  return {
46425
46907
  ok: false,
46426
46908
  path: modelsPath,
@@ -46429,7 +46911,7 @@ function validateModelsConfig(options = {}) {
46429
46911
  modelCount: 0
46430
46912
  };
46431
46913
  }
46432
- const authStorage = new AuthStorage();
46914
+ const authStorage = AuthStorage.create();
46433
46915
  const registry = new ModelRegistry(authStorage, modelsPath);
46434
46916
  const loadError = registry.getLoadError();
46435
46917
  if (loadError) {
@@ -46476,7 +46958,7 @@ function validateModelsConfig(options = {}) {
46476
46958
  };
46477
46959
  }
46478
46960
  async function probeModelsConfig(options) {
46479
- const registry = new ModelRegistry(new AuthStorage(), resolveModelsPath(options.path));
46961
+ const registry = new ModelRegistry(AuthStorage.create(), resolveModelsPath(options.path));
46480
46962
  const loadError = registry.getLoadError();
46481
46963
  if (loadError) {
46482
46964
  return {
@@ -46569,7 +47051,7 @@ function parseModelsCommandArgs(args) {
46569
47051
  }
46570
47052
 
46571
47053
  // src/commands/sessions.ts
46572
- import fs6 from "node:fs";
47054
+ import fs7 from "node:fs";
46573
47055
 
46574
47056
  // src/session-control.ts
46575
47057
  var SessionControlError = class extends Error {
@@ -46714,15 +47196,15 @@ function buildForkResult(params) {
46714
47196
  }
46715
47197
  function pathToEntry(data, entryId) {
46716
47198
  const byId = new Map(data.entries.map((entry) => [entry.id, entry]));
46717
- const path31 = [];
47199
+ const path33 = [];
46718
47200
  let current = entryId;
46719
47201
  while (current) {
46720
47202
  const entry = byId.get(current);
46721
47203
  if (!entry) return [];
46722
- path31.unshift(entry);
47204
+ path33.unshift(entry);
46723
47205
  current = entry.parentId;
46724
47206
  }
46725
- return path31;
47207
+ return path33;
46726
47208
  }
46727
47209
  function cloneJson(value) {
46728
47210
  return JSON.parse(JSON.stringify(value));
@@ -46968,7 +47450,7 @@ function cwdMatches(sessionCwd, requestedCwd) {
46968
47450
  }
46969
47451
  function realpathOrInput(value) {
46970
47452
  try {
46971
- return fs6.realpathSync.native(value);
47453
+ return fs7.realpathSync.native(value);
46972
47454
  } catch {
46973
47455
  return value;
46974
47456
  }
@@ -47057,9 +47539,9 @@ function isRecord2(value) {
47057
47539
  }
47058
47540
 
47059
47541
  // src/extensions/loader.ts
47060
- import * as fs7 from "node:fs";
47542
+ import * as fs8 from "node:fs";
47061
47543
  import * as os2 from "os";
47062
- import * as path6 from "path";
47544
+ import * as path7 from "path";
47063
47545
  import { pathToFileURL } from "node:url";
47064
47546
 
47065
47547
  // src/command-tree.ts
@@ -47072,19 +47554,19 @@ var CommandTree = class {
47072
47554
  * register(["session", "new"], { description, handler })
47073
47555
  * register(["tools", "subagent", "spawn", "local"], { description, handler })
47074
47556
  */
47075
- register(path31, info) {
47076
- if (path31.length === 0) {
47557
+ register(path33, info) {
47558
+ if (path33.length === 0) {
47077
47559
  throw new Error("Command path cannot be empty");
47078
47560
  }
47079
47561
  let current = this.root;
47080
47562
  const fullPath = [];
47081
- for (let i = 0; i < path31.length; i++) {
47082
- const segment = path31[i];
47563
+ for (let i = 0; i < path33.length; i++) {
47564
+ const segment = path33[i];
47083
47565
  fullPath.push(segment);
47084
47566
  if (!current.children) {
47085
47567
  current.children = [];
47086
47568
  }
47087
- const isLast = i === path31.length - 1;
47569
+ const isLast = i === path33.length - 1;
47088
47570
  let node = current.children.find((n) => n.name === segment);
47089
47571
  if (!node) {
47090
47572
  node = {
@@ -47114,31 +47596,31 @@ var CommandTree = class {
47114
47596
  /**
47115
47597
  * Find a node by path
47116
47598
  */
47117
- find(path31) {
47118
- if (path31.length === 0) {
47599
+ find(path33) {
47600
+ if (path33.length === 0) {
47119
47601
  return this.root;
47120
47602
  }
47121
- const key = path31.join("/");
47603
+ const key = path33.join("/");
47122
47604
  return this.pathCache.get(key) || null;
47123
47605
  }
47124
47606
  /**
47125
47607
  * Get children of a path
47126
47608
  */
47127
- getChildren(path31) {
47128
- const node = this.find(path31);
47609
+ getChildren(path33) {
47610
+ const node = this.find(path33);
47129
47611
  return (node?.children || []).filter((child) => !child.hidden);
47130
47612
  }
47131
47613
  /**
47132
47614
  * Check if a path exists (as category or command)
47133
47615
  */
47134
- exists(path31) {
47135
- return this.find(path31) !== null;
47616
+ exists(path33) {
47617
+ return this.find(path33) !== null;
47136
47618
  }
47137
47619
  /**
47138
47620
  * Check if path is a category (has children)
47139
47621
  */
47140
- isCategory(path31) {
47141
- const node = this.find(path31);
47622
+ isCategory(path33) {
47623
+ const node = this.find(path33);
47142
47624
  return node?.isCategory || false;
47143
47625
  }
47144
47626
  /**
@@ -47217,8 +47699,8 @@ var CommandTree = class {
47217
47699
  * Supports dot notation: "tools.subagent.spawn.local"
47218
47700
  */
47219
47701
  registerFromPath(pathStr, info) {
47220
- const path31 = pathStr.split(/[./]/).filter(Boolean);
47221
- this.register(path31, info);
47702
+ const path33 = pathStr.split(/[./]/).filter(Boolean);
47703
+ this.register(path33, info);
47222
47704
  }
47223
47705
  };
47224
47706
  var commandTree = new CommandTree();
@@ -47332,6 +47814,33 @@ function createExtensionContext(extension, cwd, uiContext, abortFn, isIdleFn, ha
47332
47814
  setSessionMetadata(key, value) {
47333
47815
  actions?.setSessionMetadata?.(key, value);
47334
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
+ },
47335
47844
  get yoloMode() {
47336
47845
  return actions?.yoloMode ?? false;
47337
47846
  }
@@ -47353,19 +47862,19 @@ function createExtension(extensionPath, resolvedPath, identity, source) {
47353
47862
  }
47354
47863
  function expandPath(p) {
47355
47864
  if (p.startsWith("~/")) {
47356
- return path6.join(os2.homedir(), p.slice(2));
47865
+ return path7.join(os2.homedir(), p.slice(2));
47357
47866
  }
47358
47867
  if (p.startsWith("~")) {
47359
- return path6.join(os2.homedir(), p.slice(1));
47868
+ return path7.join(os2.homedir(), p.slice(1));
47360
47869
  }
47361
47870
  return p;
47362
47871
  }
47363
47872
  function resolvePath(extPath, cwd) {
47364
47873
  const expanded = expandPath(extPath);
47365
- if (path6.isAbsolute(expanded)) {
47874
+ if (path7.isAbsolute(expanded)) {
47366
47875
  return expanded;
47367
47876
  }
47368
- return path6.resolve(cwd, expanded);
47877
+ return path7.resolve(cwd, expanded);
47369
47878
  }
47370
47879
  async function loadExtensionModule(extensionPath) {
47371
47880
  try {
@@ -47385,14 +47894,14 @@ function normalizeExtensionImportPath(extensionPath) {
47385
47894
  if (/^[a-zA-Z]:[\\/]/.test(extensionPath)) {
47386
47895
  return `file:///${extensionPath.replace(/\\/g, "/")}`;
47387
47896
  }
47388
- if (path6.isAbsolute(extensionPath)) {
47897
+ if (path7.isAbsolute(extensionPath)) {
47389
47898
  return pathToFileURL(extensionPath).href;
47390
47899
  }
47391
47900
  return extensionPath;
47392
47901
  }
47393
47902
  async function loadExtension(extensionPath, cwd, uiContext, abortFn, isIdleFn, hasUI, actions, discovered) {
47394
47903
  const resolvedPath = resolvePath(extensionPath, cwd);
47395
- if (!fs7.existsSync(resolvedPath)) {
47904
+ if (!fs8.existsSync(resolvedPath)) {
47396
47905
  return { extension: null, error: `Extension not found: ${extensionPath}` };
47397
47906
  }
47398
47907
  try {
@@ -47413,17 +47922,17 @@ async function loadExtension(extensionPath, cwd, uiContext, abortFn, isIdleFn, h
47413
47922
  }
47414
47923
  }
47415
47924
  function discoverExtensionsInDir(dir, source) {
47416
- if (!fs7.existsSync(dir)) {
47925
+ if (!fs8.existsSync(dir)) {
47417
47926
  return [];
47418
47927
  }
47419
47928
  const discovered = [];
47420
47929
  try {
47421
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
47930
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
47422
47931
  for (const entry of entries) {
47423
47932
  if (entry.name.endsWith(".disabled")) {
47424
47933
  continue;
47425
47934
  }
47426
- const entryPath = path6.join(dir, entry.name);
47935
+ const entryPath = path7.join(dir, entry.name);
47427
47936
  if (entry.isFile() && (entry.name.endsWith(".cjs") || entry.name.endsWith(".js") || entry.name.endsWith(".ts"))) {
47428
47937
  discovered.push({
47429
47938
  entryPath,
@@ -47433,13 +47942,13 @@ function discoverExtensionsInDir(dir, source) {
47433
47942
  continue;
47434
47943
  }
47435
47944
  if (entry.isDirectory()) {
47436
- const packageJsonPath = path6.join(entryPath, "package.json");
47945
+ const packageJsonPath = path7.join(entryPath, "package.json");
47437
47946
  let packageIdentity;
47438
47947
  let packageDescription;
47439
47948
  let packageMainPath;
47440
- if (fs7.existsSync(packageJsonPath)) {
47949
+ if (fs8.existsSync(packageJsonPath)) {
47441
47950
  try {
47442
- const pkg = JSON.parse(fs7.readFileSync(packageJsonPath, "utf-8"));
47951
+ const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
47443
47952
  const manifest = pkg.pie && typeof pkg.pie === "object" && !Array.isArray(pkg.pie) ? pkg.pie : void 0;
47444
47953
  packageIdentity = resolveExtensionIdentity({
47445
47954
  pieName: typeof manifest?.name === "string" ? manifest.name : void 0,
@@ -47448,8 +47957,8 @@ function discoverExtensionsInDir(dir, source) {
47448
47957
  });
47449
47958
  packageDescription = typeof pkg.description === "string" ? pkg.description : void 0;
47450
47959
  if (typeof manifest?.main === "string" && manifest.main.trim().length > 0) {
47451
- const candidate = path6.resolve(entryPath, manifest.main);
47452
- if (fs7.existsSync(candidate)) {
47960
+ const candidate = path7.resolve(entryPath, manifest.main);
47961
+ if (fs8.existsSync(candidate)) {
47453
47962
  packageMainPath = candidate;
47454
47963
  }
47455
47964
  }
@@ -47465,8 +47974,8 @@ function discoverExtensionsInDir(dir, source) {
47465
47974
  });
47466
47975
  continue;
47467
47976
  }
47468
- const indexCjs = path6.join(entryPath, "index.cjs");
47469
- if (fs7.existsSync(indexCjs)) {
47977
+ const indexCjs = path7.join(entryPath, "index.cjs");
47978
+ if (fs8.existsSync(indexCjs)) {
47470
47979
  discovered.push({
47471
47980
  entryPath: indexCjs,
47472
47981
  source,
@@ -47475,8 +47984,8 @@ function discoverExtensionsInDir(dir, source) {
47475
47984
  });
47476
47985
  continue;
47477
47986
  }
47478
- const indexJs = path6.join(entryPath, "index.js");
47479
- if (fs7.existsSync(indexJs)) {
47987
+ const indexJs = path7.join(entryPath, "index.js");
47988
+ if (fs8.existsSync(indexJs)) {
47480
47989
  discovered.push({
47481
47990
  entryPath: indexJs,
47482
47991
  source,
@@ -47485,8 +47994,8 @@ function discoverExtensionsInDir(dir, source) {
47485
47994
  });
47486
47995
  continue;
47487
47996
  }
47488
- const indexTs = path6.join(entryPath, "index.ts");
47489
- if (fs7.existsSync(indexTs)) {
47997
+ const indexTs = path7.join(entryPath, "index.ts");
47998
+ if (fs8.existsSync(indexTs)) {
47490
47999
  discovered.push({
47491
48000
  entryPath: indexTs,
47492
48001
  source,
@@ -47507,21 +48016,21 @@ function collectDiscoveredExtensions(configuredPaths, cwd, options) {
47507
48016
  const errors = [];
47508
48017
  if (!options?.noExtensions) {
47509
48018
  if (!options?.noBuiltinExtensions) {
47510
- const builtinExtDir = path6.join(getBuiltinDir(), "extensions");
48019
+ const builtinExtDir = path7.join(getBuiltinDir(), "extensions");
47511
48020
  discovered.push(...discoverExtensionsInDir(builtinExtDir, "builtin"));
47512
48021
  }
47513
- const globalExtDir = path6.join(os2.homedir(), ".pie", "extensions");
48022
+ const globalExtDir = path7.join(os2.homedir(), ".pie", "extensions");
47514
48023
  discovered.push(...discoverExtensionsInDir(globalExtDir, "global"));
47515
- const localExtDir = path6.join(cwd, ".pie", "extensions");
48024
+ const localExtDir = path7.join(cwd, ".pie", "extensions");
47516
48025
  discovered.push(...discoverExtensionsInDir(localExtDir, "project"));
47517
48026
  }
47518
48027
  for (const p of configuredPaths) {
47519
48028
  const resolved = resolvePath(p, cwd);
47520
- if (!fs7.existsSync(resolved)) {
48029
+ if (!fs8.existsSync(resolved)) {
47521
48030
  errors.push({ path: p, error: `Extension path not found: ${p}` });
47522
48031
  continue;
47523
48032
  }
47524
- if (!fs7.statSync(resolved).isDirectory()) {
48033
+ if (!fs8.statSync(resolved).isDirectory()) {
47525
48034
  errors.push({ path: p, error: `Extension path must be a directory: ${p}` });
47526
48035
  continue;
47527
48036
  }
@@ -47612,6 +48121,7 @@ function convertExtensionTools(extensions, customExecutors) {
47612
48121
  label: tool.label ?? name,
47613
48122
  description: tool.description,
47614
48123
  parameters: tool.parameters,
48124
+ renderHint: tool.renderHint,
47615
48125
  execute: async (toolCallId, args, signal, onUpdate) => {
47616
48126
  debugLog("ext-tool", `${name} EXECUTE START`, {
47617
48127
  toolCallId: toolCallId.slice(0, 8),
@@ -47802,6 +48312,9 @@ Enablement overrides are stored sparsely; absent entries default to enabled.
47802
48312
  Project scope writes ./.pie/settings.json and overrides user scope.`);
47803
48313
  }
47804
48314
 
48315
+ // src/interactive-mode.ts
48316
+ import * as os8 from "os";
48317
+
47805
48318
  // ../../packages/tui/src/fuzzy.ts
47806
48319
  function fuzzyMatch(query, text) {
47807
48320
  const queryLower = query.toLowerCase();
@@ -49352,9 +49865,9 @@ var KillRing = class {
49352
49865
  };
49353
49866
 
49354
49867
  // ../../packages/tui/src/tui.ts
49355
- import * as fs8 from "node:fs";
49868
+ import * as fs9 from "node:fs";
49356
49869
  import * as os3 from "node:os";
49357
- import * as path7 from "node:path";
49870
+ import * as path8 from "node:path";
49358
49871
 
49359
49872
  // ../../packages/tui/src/terminal-image.ts
49360
49873
  var cachedCapabilities = null;
@@ -50030,11 +50543,11 @@ var TUI = class extends Container {
50030
50543
  const debugRedraw = process.env.PIE_DEBUG_REDRAW === "1";
50031
50544
  const logRedraw = (reason) => {
50032
50545
  if (!debugRedraw) return;
50033
- const logPath = path7.join(os3.homedir(), ".pie", "logs", "pie-cli.log");
50034
- 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 });
50035
50548
  const msg = `[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] [ui] fullRender: ${reason} (prev=${this.previousLines.length}, new=${newLines.length}, height=${height})
50036
50549
  `;
50037
- fs8.appendFileSync(logPath, msg);
50550
+ fs9.appendFileSync(logPath, msg);
50038
50551
  };
50039
50552
  if (this.previousLines.length === 0 && !widthChanged) {
50040
50553
  logRedraw("first render");
@@ -50127,7 +50640,7 @@ var TUI = class extends Container {
50127
50640
  const line = newLines[i];
50128
50641
  const isImage = isImageLine(line);
50129
50642
  if (!isImage && visibleWidth(line) > width) {
50130
- const crashLogPath = path7.join(os3.homedir(), ".pie", "pie-crash.log");
50643
+ const crashLogPath = path8.join(os3.homedir(), ".pie", "pie-crash.log");
50131
50644
  const crashData = [
50132
50645
  `Crash at ${(/* @__PURE__ */ new Date()).toISOString()}`,
50133
50646
  `Terminal width: ${width}`,
@@ -50137,8 +50650,8 @@ var TUI = class extends Container {
50137
50650
  ...newLines.map((l, idx) => `[${idx}] (w=${visibleWidth(l)}) ${l}`),
50138
50651
  ""
50139
50652
  ].join("\n");
50140
- fs8.mkdirSync(path7.dirname(crashLogPath), { recursive: true });
50141
- fs8.writeFileSync(crashLogPath, crashData);
50653
+ fs9.mkdirSync(path8.dirname(crashLogPath), { recursive: true });
50654
+ fs9.writeFileSync(crashLogPath, crashData);
50142
50655
  this.stop();
50143
50656
  const errorMsg = [
50144
50657
  `Rendered line ${i} exceeds terminal width (${visibleWidth(line)} > ${width}).`,
@@ -50168,8 +50681,8 @@ var TUI = class extends Container {
50168
50681
  buffer += "\x1B[?2026l";
50169
50682
  if (process.env.PIE_TUI_DEBUG === "1") {
50170
50683
  const debugDir = "/tmp/tui";
50171
- fs8.mkdirSync(debugDir, { recursive: true });
50172
- 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`);
50173
50686
  const debugData = [
50174
50687
  `firstChanged: ${firstChanged}`,
50175
50688
  `viewportTop: ${viewportTop}`,
@@ -50192,7 +50705,7 @@ var TUI = class extends Container {
50192
50705
  "=== buffer ===",
50193
50706
  JSON.stringify(buffer)
50194
50707
  ].join("\n");
50195
- fs8.writeFileSync(debugPath, debugData);
50708
+ fs9.writeFileSync(debugPath, debugData);
50196
50709
  }
50197
50710
  this.terminal.write(buffer);
50198
50711
  this.cursorRow = Math.max(0, newLines.length - 1);
@@ -54383,7 +54896,7 @@ var StdinBuffer = class extends EventEmitter {
54383
54896
  };
54384
54897
 
54385
54898
  // ../../packages/tui/src/terminal.ts
54386
- import * as fs9 from "node:fs";
54899
+ import * as fs10 from "node:fs";
54387
54900
  import koffi from "koffi";
54388
54901
  var ProcessTerminal = class {
54389
54902
  wasRaw = false;
@@ -54508,7 +55021,7 @@ var ProcessTerminal = class {
54508
55021
  const timeLeft = endTime - now;
54509
55022
  if (timeLeft <= 0) break;
54510
55023
  if (now - lastDataTime >= idleMs) break;
54511
- await new Promise((resolve6) => setTimeout(resolve6, Math.min(idleMs, timeLeft)));
55024
+ await new Promise((resolve7) => setTimeout(resolve7, Math.min(idleMs, timeLeft)));
54512
55025
  }
54513
55026
  } finally {
54514
55027
  process.stdin.removeListener("data", onData);
@@ -54544,7 +55057,7 @@ var ProcessTerminal = class {
54544
55057
  process.stdout.write(data);
54545
55058
  if (this.writeLogPath) {
54546
55059
  try {
54547
- fs9.appendFileSync(this.writeLogPath, data, { encoding: "utf8" });
55060
+ fs10.appendFileSync(this.writeLogPath, data, { encoding: "utf8" });
54548
55061
  } catch {
54549
55062
  }
54550
55063
  }
@@ -54618,6 +55131,8 @@ var ExtensionRunner = class {
54618
55131
  actions;
54619
55132
  contextActions;
54620
55133
  hasUI;
55134
+ /** Unregister handles created through this runner, revoked on reload/teardown. */
55135
+ unregisterHandles = [];
54621
55136
  constructor(extensions, actions, contextActions, hasUI = true) {
54622
55137
  this.extensions = extensions;
54623
55138
  this.actions = actions;
@@ -54649,6 +55164,39 @@ var ExtensionRunner = class {
54649
55164
  this.uiContext = uiContext;
54650
55165
  this.hasUI = true;
54651
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
+ }
54652
55200
  /**
54653
55201
  * Get all registered tools from all extensions.
54654
55202
  */
@@ -54739,10 +55287,35 @@ var ExtensionRunner = class {
54739
55287
  },
54740
55288
  getAllTools: () => runner.actions.getAllTools(),
54741
55289
  getRuntimeTools: () => runner.actions.getRuntimeTools(),
54742
- resolveModelClass: (modelClass) => runner.actions.resolveModelClass?.(modelClass),
55290
+ resolveModelClass: (modelClass) => runner.actions.resolveModelClass?.(modelClass) ?? {},
54743
55291
  getMessages: () => runner.actions.getMessages(),
54744
55292
  getSessionMetadata: () => runner.actions.getSessionMetadata(),
54745
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
+ },
54746
55319
  get yoloMode() {
54747
55320
  return runner.actions.yoloMode ?? false;
54748
55321
  }
@@ -54822,6 +55395,81 @@ var ExtensionRunner = class {
54822
55395
  isError: currentEvent.isError ?? event.isError
54823
55396
  };
54824
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
+ }
54825
55473
  /**
54826
55474
  * Emit a generic event to all extensions.
54827
55475
  */
@@ -54959,8 +55607,8 @@ function wrapToolsWithExtensions(tools, runner) {
54959
55607
  }
54960
55608
 
54961
55609
  // src/keybindings.ts
54962
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
54963
- import { join as join6 } from "path";
55610
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
55611
+ import { join as join7 } from "path";
54964
55612
  var DEFAULT_APP_KEYBINDINGS = {
54965
55613
  interrupt: "escape",
54966
55614
  clear: "ctrl+c",
@@ -55024,7 +55672,7 @@ var KeybindingsManager = class _KeybindingsManager {
55024
55672
  * Create from config file and set up editor keybindings.
55025
55673
  */
55026
55674
  static create(agentDir = getAgentDir()) {
55027
- const configPath = join6(agentDir, "keybindings.json");
55675
+ const configPath = join7(agentDir, "keybindings.json");
55028
55676
  const config = _KeybindingsManager.loadFromFile(configPath);
55029
55677
  const manager = new _KeybindingsManager(config);
55030
55678
  const editorConfig = {};
@@ -55042,10 +55690,10 @@ var KeybindingsManager = class _KeybindingsManager {
55042
55690
  static inMemory(config = {}) {
55043
55691
  return new _KeybindingsManager(config);
55044
55692
  }
55045
- static loadFromFile(path31) {
55046
- if (!existsSync7(path31)) return {};
55693
+ static loadFromFile(path33) {
55694
+ if (!existsSync8(path33)) return {};
55047
55695
  try {
55048
- return JSON.parse(readFileSync6(path31, "utf-8"));
55696
+ return JSON.parse(readFileSync7(path33, "utf-8"));
55049
55697
  } catch {
55050
55698
  return {};
55051
55699
  }
@@ -55094,19 +55742,19 @@ var KeybindingsManager = class _KeybindingsManager {
55094
55742
  };
55095
55743
 
55096
55744
  // src/project-knowledge.ts
55097
- import fs10 from "node:fs";
55098
- import path8 from "node:path";
55745
+ import fs11 from "node:fs";
55746
+ import path9 from "node:path";
55099
55747
  function collectAncestorAgentsFiles(cwd) {
55100
- const resolvedCwd = path8.resolve(cwd || process.cwd());
55748
+ const resolvedCwd = path9.resolve(cwd || process.cwd());
55101
55749
  const projectRoot = resolveCliProjectRoot(resolvedCwd);
55102
55750
  const candidates = [];
55103
55751
  let current = resolvedCwd;
55104
55752
  while (true) {
55105
- candidates.unshift(path8.join(current, AGENTS_CONTEXT_FILE_NAME));
55753
+ candidates.unshift(path9.join(current, AGENTS_CONTEXT_FILE_NAME));
55106
55754
  if (current === projectRoot) {
55107
55755
  break;
55108
55756
  }
55109
- const parent = path8.dirname(current);
55757
+ const parent = path9.dirname(current);
55110
55758
  if (parent === current) {
55111
55759
  break;
55112
55760
  }
@@ -55114,13 +55762,13 @@ function collectAncestorAgentsFiles(cwd) {
55114
55762
  }
55115
55763
  const files = [];
55116
55764
  for (const candidate of candidates) {
55117
- if (!fs10.existsSync(candidate)) {
55765
+ if (!fs11.existsSync(candidate)) {
55118
55766
  continue;
55119
55767
  }
55120
55768
  try {
55121
55769
  files.push({
55122
- path: path8.relative(resolvedCwd, candidate) || AGENTS_CONTEXT_FILE_NAME,
55123
- content: fs10.readFileSync(candidate, "utf-8")
55770
+ path: path9.relative(resolvedCwd, candidate) || AGENTS_CONTEXT_FILE_NAME,
55771
+ content: fs11.readFileSync(candidate, "utf-8")
55124
55772
  });
55125
55773
  } catch {
55126
55774
  }
@@ -55132,14 +55780,14 @@ function buildCliProjectContextSection(cwd) {
55132
55780
  }
55133
55781
 
55134
55782
  // src/file-access-context.ts
55135
- import * as path9 from "node:path";
55783
+ import * as path10 from "node:path";
55136
55784
  function uniqueNormalizedPaths(paths) {
55137
55785
  const seen = /* @__PURE__ */ new Set();
55138
55786
  const result = [];
55139
55787
  for (const entry of paths) {
55140
55788
  const trimmed = String(entry || "").trim();
55141
55789
  if (!trimmed) continue;
55142
- const normalized = path9.normalize(trimmed);
55790
+ const normalized = path10.normalize(trimmed);
55143
55791
  if (seen.has(normalized)) continue;
55144
55792
  seen.add(normalized);
55145
55793
  result.push(normalized);
@@ -55158,8 +55806,8 @@ function buildCliFileAccessOptions(params) {
55158
55806
  }
55159
55807
 
55160
55808
  // src/theme/theme.ts
55161
- import * as fs11 from "node:fs";
55162
- import * as path10 from "node:path";
55809
+ import * as fs12 from "node:fs";
55810
+ import * as path11 from "node:path";
55163
55811
 
55164
55812
  // ../../node_modules/@sinclair/typebox/build/esm/compiler/compiler.mjs
55165
55813
  var TypeCheck = class {
@@ -56046,11 +56694,11 @@ var BUILTIN_THEMES;
56046
56694
  function getBuiltinThemes() {
56047
56695
  if (!BUILTIN_THEMES) {
56048
56696
  const themesDir = getThemesDir();
56049
- const darkPath = path10.join(themesDir, "dark.json");
56050
- const lightPath = path10.join(themesDir, "light.json");
56697
+ const darkPath = path11.join(themesDir, "dark.json");
56698
+ const lightPath = path11.join(themesDir, "light.json");
56051
56699
  BUILTIN_THEMES = {
56052
- dark: JSON.parse(fs11.readFileSync(darkPath, "utf-8")),
56053
- 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"))
56054
56702
  };
56055
56703
  }
56056
56704
  return BUILTIN_THEMES;
@@ -56058,8 +56706,8 @@ function getBuiltinThemes() {
56058
56706
  function getAvailableThemes() {
56059
56707
  const themes = new Set(Object.keys(getBuiltinThemes()));
56060
56708
  const customThemesDir = getCustomThemesDir();
56061
- if (fs11.existsSync(customThemesDir)) {
56062
- const files = fs11.readdirSync(customThemesDir);
56709
+ if (fs12.existsSync(customThemesDir)) {
56710
+ const files = fs12.readdirSync(customThemesDir);
56063
56711
  for (const file of files) {
56064
56712
  if (file.endsWith(".json")) {
56065
56713
  themes.add(file.slice(0, -5));
@@ -56120,18 +56768,18 @@ function loadThemeJson(name) {
56120
56768
  }
56121
56769
  const registeredTheme = registeredThemes.get(name);
56122
56770
  if (registeredTheme?.sourcePath) {
56123
- const content2 = fs11.readFileSync(registeredTheme.sourcePath, "utf-8");
56771
+ const content2 = fs12.readFileSync(registeredTheme.sourcePath, "utf-8");
56124
56772
  return parseThemeJsonContent(registeredTheme.sourcePath, content2);
56125
56773
  }
56126
56774
  if (registeredTheme) {
56127
56775
  throw new Error(`Theme "${name}" does not have a source path for export`);
56128
56776
  }
56129
56777
  const customThemesDir = getCustomThemesDir();
56130
- const themePath = path10.join(customThemesDir, `${name}.json`);
56131
- if (!fs11.existsSync(themePath)) {
56778
+ const themePath = path11.join(customThemesDir, `${name}.json`);
56779
+ if (!fs12.existsSync(themePath)) {
56132
56780
  throw new Error(`Theme not found: ${name}`);
56133
56781
  }
56134
- const content = fs11.readFileSync(themePath, "utf-8");
56782
+ const content = fs12.readFileSync(themePath, "utf-8");
56135
56783
  return parseThemeJsonContent(name, content);
56136
56784
  }
56137
56785
  function createTheme(themeJson, mode, sourcePath) {
@@ -56244,12 +56892,12 @@ function startThemeWatcher() {
56244
56892
  return;
56245
56893
  }
56246
56894
  const customThemesDir = getCustomThemesDir();
56247
- const themeFile = path10.join(customThemesDir, `${currentThemeName}.json`);
56248
- if (!fs11.existsSync(themeFile)) {
56895
+ const themeFile = path11.join(customThemesDir, `${currentThemeName}.json`);
56896
+ if (!fs12.existsSync(themeFile)) {
56249
56897
  return;
56250
56898
  }
56251
56899
  try {
56252
- themeWatcher = fs11.watch(themeFile, (eventType) => {
56900
+ themeWatcher = fs12.watch(themeFile, (eventType) => {
56253
56901
  if (eventType === "change") {
56254
56902
  setTimeout(() => {
56255
56903
  try {
@@ -56262,7 +56910,7 @@ function startThemeWatcher() {
56262
56910
  }, 100);
56263
56911
  } else if (eventType === "rename") {
56264
56912
  setTimeout(() => {
56265
- if (!fs11.existsSync(themeFile)) {
56913
+ if (!fs12.existsSync(themeFile)) {
56266
56914
  currentThemeName = "dark";
56267
56915
  setGlobalTheme(loadTheme("dark"));
56268
56916
  if (themeWatcher) {
@@ -56442,8 +57090,9 @@ function getSettingsListTheme() {
56442
57090
  }
56443
57091
 
56444
57092
  // src/skills/loader.ts
56445
- import * as fs12 from "fs";
56446
- import * as path11 from "path";
57093
+ import * as fs13 from "fs";
57094
+ import * as os4 from "os";
57095
+ import * as path12 from "path";
56447
57096
  function parseSkillDescription(content) {
56448
57097
  const frontmatterMatch = content.match(/^---\n[\s\S]*?description:\s*["']?(.+?)["']?(?:\n|$)/m);
56449
57098
  if (frontmatterMatch) return frontmatterMatch[1].trim();
@@ -56458,21 +57107,53 @@ function parseSkillDescription(content) {
56458
57107
  }
56459
57108
  return "No description";
56460
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
+ }
56461
57129
  function loadSkillsFromDir(dir, source) {
56462
57130
  const skills = [];
56463
57131
  const diagnostics = [];
56464
- if (!fs12.existsSync(dir)) {
57132
+ if (!fs13.existsSync(dir)) {
56465
57133
  return { skills, diagnostics };
56466
57134
  }
56467
- const entries = fs12.readdirSync(dir, { withFileTypes: true });
57135
+ const entries = fs13.readdirSync(dir, { withFileTypes: true });
56468
57136
  for (const entry of entries) {
56469
57137
  if (entry.name.startsWith(".")) continue;
56470
- const fullPath = path11.join(dir, entry.name);
56471
- if (entry.isDirectory()) {
56472
- const skillMdPath = path11.join(fullPath, "SKILL.md");
56473
- 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)) {
56474
57155
  try {
56475
- const content = fs12.readFileSync(skillMdPath, "utf-8");
57156
+ const content = fs13.readFileSync(skillMdPath, "utf-8");
56476
57157
  const description = parseSkillDescription(content);
56477
57158
  skills.push({
56478
57159
  name: entry.name,
@@ -56480,7 +57161,7 @@ function loadSkillsFromDir(dir, source) {
56480
57161
  content,
56481
57162
  filePath: skillMdPath,
56482
57163
  source,
56483
- baseDir: fullPath
57164
+ baseDir: entryPath
56484
57165
  });
56485
57166
  } catch (e) {
56486
57167
  diagnostics.push({
@@ -56490,64 +57171,72 @@ function loadSkillsFromDir(dir, source) {
56490
57171
  });
56491
57172
  }
56492
57173
  }
56493
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
57174
+ } else if (entryIsFile && entry.name.endsWith(".md")) {
56494
57175
  const name = entry.name.slice(0, -3);
56495
57176
  try {
56496
- const content = fs12.readFileSync(fullPath, "utf-8");
57177
+ const content = fs13.readFileSync(entryPath, "utf-8");
56497
57178
  const description = parseSkillDescription(content);
56498
57179
  skills.push({
56499
57180
  name,
56500
57181
  description,
56501
57182
  content,
56502
- filePath: fullPath,
57183
+ filePath: entryPath,
56503
57184
  source,
56504
- baseDir: dir
57185
+ baseDir: path12.dirname(entryPath)
56505
57186
  });
56506
57187
  } catch (e) {
56507
57188
  diagnostics.push({
56508
57189
  type: "warning",
56509
57190
  message: `Failed to load skill: ${name}`,
56510
- path: fullPath
57191
+ path: entryPath
56511
57192
  });
56512
57193
  }
56513
57194
  }
56514
57195
  }
56515
57196
  return { skills, diagnostics };
56516
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
+ }
56517
57222
  function loadSkills(options) {
56518
- const { builtinDir, userDir, projectDir } = options;
56519
57223
  const skillMap = /* @__PURE__ */ new Map();
56520
57224
  const diagnostics = [];
56521
- const builtinResult = loadSkillsFromDir(builtinDir, "builtin");
56522
- for (const skill of builtinResult.skills) {
56523
- skillMap.set(skill.name, skill);
56524
- }
56525
- diagnostics.push(...builtinResult.diagnostics);
56526
- const userResult = loadSkillsFromDir(userDir, "user");
56527
- for (const skill of userResult.skills) {
56528
- if (skillMap.has(skill.name)) {
56529
- diagnostics.push({
56530
- type: "collision",
56531
- message: `Skill "${skill.name}" collision: user skill overrides builtin`,
56532
- path: skill.filePath
56533
- });
56534
- }
56535
- skillMap.set(skill.name, skill);
56536
- }
56537
- diagnostics.push(...userResult.diagnostics);
56538
- const projectResult = loadSkillsFromDir(projectDir, "project");
56539
- for (const skill of projectResult.skills) {
56540
- 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) {
56541
57228
  const existing = skillMap.get(skill.name);
56542
- diagnostics.push({
56543
- type: "collision",
56544
- message: `Skill "${skill.name}" collision: project skill overrides ${existing.source}`,
56545
- path: skill.filePath
56546
- });
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);
56547
57237
  }
56548
- skillMap.set(skill.name, skill);
57238
+ diagnostics.push(...result.diagnostics);
56549
57239
  }
56550
- diagnostics.push(...projectResult.diagnostics);
56551
57240
  return { skills: Array.from(skillMap.values()), diagnostics };
56552
57241
  }
56553
57242
  function formatSkillsForPrompt(skills) {
@@ -56688,9 +57377,12 @@ function validateSkillName(name) {
56688
57377
  return { valid: true };
56689
57378
  }
56690
57379
 
57380
+ // src/interactive-mode.ts
57381
+ import * as path29 from "path";
57382
+
56691
57383
  // src/capabilities/read-file-understanding.ts
56692
- import * as path20 from "node:path";
56693
- import * as fs20 from "node:fs/promises";
57384
+ import * as path21 from "node:path";
57385
+ import * as fs21 from "node:fs/promises";
56694
57386
 
56695
57387
  // src/core/provider-capabilities.ts
56696
57388
  var PLATFORM_DISPLAY_NAMES = {
@@ -56716,8 +57408,8 @@ function getProviderDisplayName(platform2) {
56716
57408
  }
56717
57409
 
56718
57410
  // src/files/adapters/kimi-adapter.ts
56719
- import * as fs13 from "node:fs";
56720
- import * as path12 from "node:path";
57411
+ import * as fs14 from "node:fs";
57412
+ import * as path13 from "node:path";
56721
57413
 
56722
57414
  // src/files/errors.ts
56723
57415
  var FileErrorMessages = {
@@ -56854,17 +57546,17 @@ var KimiFileAdapter = class {
56854
57546
  // Upload
56855
57547
  // ============================================================================
56856
57548
  async upload(filePath, options = {}) {
56857
- const fileName = options.sourceName || path12.basename(filePath);
57549
+ const fileName = options.sourceName || path13.basename(filePath);
56858
57550
  const mimeType = options.mimeType || this.guessMimeType(fileName);
56859
57551
  const purpose = this.detectPurpose(filePath, options.purpose, mimeType);
56860
- const stats = fs13.statSync(filePath);
57552
+ const stats = fs14.statSync(filePath);
56861
57553
  if (stats.size > this.capabilities.maxFileSize) {
56862
57554
  throw new FileManagerError(
56863
57555
  "FILE_TOO_LARGE" /* FILE_TOO_LARGE */,
56864
57556
  `File size ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds Kimi limit of 100MB`
56865
57557
  );
56866
57558
  }
56867
- const fileBuffer = fs13.readFileSync(filePath);
57559
+ const fileBuffer = fs14.readFileSync(filePath);
56868
57560
  const file = new File([fileBuffer], fileName, { type: mimeType });
56869
57561
  const formData = new FormData();
56870
57562
  formData.append("file", file);
@@ -56916,7 +57608,7 @@ var KimiFileAdapter = class {
56916
57608
  return "file-extract";
56917
57609
  }
56918
57610
  guessMimeType(filename) {
56919
- const ext = path12.extname(filename).toLowerCase();
57611
+ const ext = path13.extname(filename).toLowerCase();
56920
57612
  switch (ext) {
56921
57613
  case ".jpg":
56922
57614
  case ".jpeg":
@@ -57078,8 +57770,8 @@ var KimiFileAdapter = class {
57078
57770
  };
57079
57771
 
57080
57772
  // src/files/adapters/gemini-adapter.ts
57081
- import * as fs14 from "node:fs";
57082
- import * as path13 from "node:path";
57773
+ import * as fs15 from "node:fs";
57774
+ import * as path14 from "node:path";
57083
57775
  import { Readable } from "node:stream";
57084
57776
  var GeminiFileAdapter = class {
57085
57777
  platform = "gemini";
@@ -57136,7 +57828,7 @@ var GeminiFileAdapter = class {
57136
57828
  // Upload
57137
57829
  // ============================================================================
57138
57830
  async upload(filePath, options = {}) {
57139
- const stats = fs14.statSync(filePath);
57831
+ const stats = fs15.statSync(filePath);
57140
57832
  if (stats.size > this.capabilities.maxFileSize) {
57141
57833
  throw new FileManagerError(
57142
57834
  "FILE_TOO_LARGE" /* FILE_TOO_LARGE */,
@@ -57144,7 +57836,7 @@ var GeminiFileAdapter = class {
57144
57836
  );
57145
57837
  }
57146
57838
  const mimeType = options.mimeType || this.guessMimeType(filePath);
57147
- const displayName = options.sourceName || path13.basename(filePath);
57839
+ const displayName = options.sourceName || path14.basename(filePath);
57148
57840
  const startResponse = await fetch(
57149
57841
  `${this.baseUrl}/upload/v1beta/files?key=${this.apiKey}`,
57150
57842
  {
@@ -57175,7 +57867,7 @@ var GeminiFileAdapter = class {
57175
57867
  "No upload URL received"
57176
57868
  );
57177
57869
  }
57178
- const fileStream = Readable.toWeb(fs14.createReadStream(filePath));
57870
+ const fileStream = Readable.toWeb(fs15.createReadStream(filePath));
57179
57871
  const uploadResponse = await fetch(uploadUrl, {
57180
57872
  method: "POST",
57181
57873
  headers: {
@@ -57379,8 +58071,8 @@ var GeminiFileAdapter = class {
57379
58071
  };
57380
58072
 
57381
58073
  // src/files/adapters/openai-compatible-files-adapter.ts
57382
- import * as fs15 from "node:fs";
57383
- import * as path14 from "node:path";
58074
+ import * as fs16 from "node:fs";
58075
+ import * as path15 from "node:path";
57384
58076
  var OpenAICompatibleFilesAdapter = class {
57385
58077
  constructor(apiKey, baseUrl, extraHeaders = {}) {
57386
58078
  this.apiKey = apiKey;
@@ -57406,16 +58098,16 @@ var OpenAICompatibleFilesAdapter = class {
57406
58098
  ]
57407
58099
  };
57408
58100
  async upload(filePath, options = {}) {
57409
- const fileName = options.sourceName || path14.basename(filePath);
58101
+ const fileName = options.sourceName || path15.basename(filePath);
57410
58102
  const mimeType = options.mimeType || guessMimeType(fileName);
57411
- const stats = fs15.statSync(filePath);
58103
+ const stats = fs16.statSync(filePath);
57412
58104
  if (stats.size > this.capabilities.maxFileSize) {
57413
58105
  throw new FileManagerError(
57414
58106
  "FILE_TOO_LARGE" /* FILE_TOO_LARGE */,
57415
58107
  `File size ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds OpenAI-compatible Files limit of ${this.capabilities.maxFileSize / 1024 / 1024}MB`
57416
58108
  );
57417
58109
  }
57418
- const file = new File([fs15.readFileSync(filePath)], fileName, { type: mimeType });
58110
+ const file = new File([fs16.readFileSync(filePath)], fileName, { type: mimeType });
57419
58111
  const formData = new FormData();
57420
58112
  formData.append("file", file);
57421
58113
  formData.append("purpose", options.purpose && options.purpose !== "auto" ? options.purpose : "file-extract");
@@ -57532,7 +58224,7 @@ function extractContentText(raw) {
57532
58224
  return raw;
57533
58225
  }
57534
58226
  function guessMimeType(filename) {
57535
- const ext = path14.extname(filename).toLowerCase();
58227
+ const ext = path15.extname(filename).toLowerCase();
57536
58228
  switch (ext) {
57537
58229
  case ".pdf":
57538
58230
  return "application/pdf";
@@ -57548,13 +58240,13 @@ function guessMimeType(filename) {
57548
58240
  }
57549
58241
 
57550
58242
  // src/files/file-manager.ts
57551
- import * as fs19 from "node:fs";
57552
- import * as path19 from "node:path";
58243
+ import * as fs20 from "node:fs";
58244
+ import * as path20 from "node:path";
57553
58245
  import * as crypto2 from "node:crypto";
57554
58246
 
57555
58247
  // src/files/file-cache.ts
57556
- import * as fs16 from "node:fs";
57557
- import * as path16 from "node:path";
58248
+ import * as fs17 from "node:fs";
58249
+ import * as path17 from "node:path";
57558
58250
  import * as crypto from "node:crypto";
57559
58251
 
57560
58252
  // src/files/logger.ts
@@ -57649,14 +58341,14 @@ var DEFAULT_PIC_OPTIONS = {
57649
58341
  };
57650
58342
 
57651
58343
  // src/files/utils.ts
57652
- import * as path15 from "node:path";
57653
- import { homedir as homedir4 } from "node:os";
58344
+ import * as path16 from "node:path";
58345
+ import { homedir as homedir5 } from "node:os";
57654
58346
  var WINDOWS_ABSOLUTE_PATH_RE = /^(?:[a-zA-Z]:[\\/]|[\\/]{2}[^\\/]+[\\/][^\\/]+)/;
57655
58347
  function isWindowsAbsolutePath(filePath) {
57656
58348
  return WINDOWS_ABSOLUTE_PATH_RE.test(filePath);
57657
58349
  }
57658
58350
  function getHomeDirectory() {
57659
- return homedir4() || process.env.USERPROFILE || process.env.HOME || ".";
58351
+ return homedir5() || process.env.USERPROFILE || process.env.HOME || ".";
57660
58352
  }
57661
58353
  function looksLikeFilePath(inputPath) {
57662
58354
  return inputPath.startsWith("/") || inputPath.startsWith("~") || inputPath.startsWith("./") || inputPath.startsWith("../") || inputPath.startsWith(".\\") || inputPath.startsWith("..\\") || isWindowsAbsolutePath(inputPath);
@@ -57670,13 +58362,13 @@ function formatBytes(bytes) {
57670
58362
  }
57671
58363
  function resolvePath2(inputPath) {
57672
58364
  if (inputPath.startsWith("~")) {
57673
- return path15.join(getHomeDirectory(), inputPath.slice(1));
58365
+ return path16.join(getHomeDirectory(), inputPath.slice(1));
57674
58366
  }
57675
58367
  if (isWindowsAbsolutePath(inputPath)) {
57676
58368
  return inputPath;
57677
58369
  }
57678
- if (!path15.isAbsolute(inputPath)) {
57679
- return path15.resolve(process.cwd(), inputPath);
58370
+ if (!path16.isAbsolute(inputPath)) {
58371
+ return path16.resolve(process.cwd(), inputPath);
57680
58372
  }
57681
58373
  return inputPath;
57682
58374
  }
@@ -57704,8 +58396,8 @@ var FileCache = class _FileCache {
57704
58396
  maxEntries;
57705
58397
  constructor(baseDir, maxEntries = DEFAULT_MAX_CACHE_ENTRIES) {
57706
58398
  this.baseDir = baseDir;
57707
- this.dataDir = path16.join(baseDir, "data");
57708
- this.indexPath = path16.join(baseDir, "index.json");
58399
+ this.dataDir = path17.join(baseDir, "data");
58400
+ this.indexPath = path17.join(baseDir, "index.json");
57709
58401
  this.maxEntries = maxEntries;
57710
58402
  this.index = this.loadIndex();
57711
58403
  this.ensureDirectories();
@@ -57714,22 +58406,22 @@ var FileCache = class _FileCache {
57714
58406
  // Directory Management
57715
58407
  // ============================================================================
57716
58408
  ensureDirectories() {
57717
- if (!fs16.existsSync(this.baseDir)) {
57718
- fs16.mkdirSync(this.baseDir, { recursive: true });
58409
+ if (!fs17.existsSync(this.baseDir)) {
58410
+ fs17.mkdirSync(this.baseDir, { recursive: true });
57719
58411
  }
57720
- if (!fs16.existsSync(this.dataDir)) {
57721
- fs16.mkdirSync(this.dataDir, { recursive: true });
58412
+ if (!fs17.existsSync(this.dataDir)) {
58413
+ fs17.mkdirSync(this.dataDir, { recursive: true });
57722
58414
  }
57723
58415
  }
57724
58416
  // ============================================================================
57725
58417
  // Index Management
57726
58418
  // ============================================================================
57727
58419
  loadIndex() {
57728
- if (!fs16.existsSync(this.indexPath)) {
58420
+ if (!fs17.existsSync(this.indexPath)) {
57729
58421
  return { version: CACHE_VERSION, files: {}, pathHints: {} };
57730
58422
  }
57731
58423
  try {
57732
- const data = JSON.parse(fs16.readFileSync(this.indexPath, "utf-8"));
58424
+ const data = JSON.parse(fs17.readFileSync(this.indexPath, "utf-8"));
57733
58425
  if (data.version !== CACHE_VERSION) {
57734
58426
  cacheLogger.warn(`Index version mismatch: ${data.version} vs ${CACHE_VERSION}`);
57735
58427
  }
@@ -57745,7 +58437,7 @@ var FileCache = class _FileCache {
57745
58437
  }
57746
58438
  saveIndex() {
57747
58439
  try {
57748
- fs16.writeFileSync(this.indexPath, JSON.stringify(this.index, null, 2));
58440
+ fs17.writeFileSync(this.indexPath, JSON.stringify(this.index, null, 2));
57749
58441
  } catch (err) {
57750
58442
  cacheLogger.error(" Failed to save index:", err);
57751
58443
  throw err;
@@ -57755,11 +58447,11 @@ var FileCache = class _FileCache {
57755
58447
  // Hash Computation
57756
58448
  // ============================================================================
57757
58449
  async computeHash(filePath) {
57758
- return new Promise((resolve6, reject) => {
58450
+ return new Promise((resolve7, reject) => {
57759
58451
  const hash = crypto.createHash("sha256");
57760
- const stream = fs16.createReadStream(filePath);
58452
+ const stream = fs17.createReadStream(filePath);
57761
58453
  stream.on("data", (chunk) => hash.update(chunk));
57762
- stream.on("end", () => resolve6(hash.digest("hex")));
58454
+ stream.on("end", () => resolve7(hash.digest("hex")));
57763
58455
  stream.on("error", reject);
57764
58456
  });
57765
58457
  }
@@ -57771,25 +58463,25 @@ var FileCache = class _FileCache {
57771
58463
  // ============================================================================
57772
58464
  getStoragePath(hash) {
57773
58465
  const subdir = hash.slice(0, 2);
57774
- const fullDir = path16.join(this.dataDir, subdir);
57775
- if (!fs16.existsSync(fullDir)) {
57776
- fs16.mkdirSync(fullDir, { recursive: true });
58466
+ const fullDir = path17.join(this.dataDir, subdir);
58467
+ if (!fs17.existsSync(fullDir)) {
58468
+ fs17.mkdirSync(fullDir, { recursive: true });
57777
58469
  }
57778
- return path16.join(fullDir, hash);
58470
+ return path17.join(fullDir, hash);
57779
58471
  }
57780
58472
  getStorageRelativePath(hash) {
57781
58473
  const subdir = hash.slice(0, 2);
57782
- return path16.join(subdir, hash);
58474
+ return path17.join(subdir, hash);
57783
58475
  }
57784
58476
  async storeFile(sourcePath, name, mimeType) {
57785
58477
  const hash = await this.computeHash(sourcePath);
57786
58478
  const storagePath = this.getStoragePath(hash);
57787
58479
  this.rememberPathHint(sourcePath, hash, name);
57788
- if (fs16.existsSync(storagePath)) {
58480
+ if (fs17.existsSync(storagePath)) {
57789
58481
  return { hash, localPath: storagePath };
57790
58482
  }
57791
- fs16.copyFileSync(sourcePath, storagePath);
57792
- const stat2 = fs16.statSync(storagePath);
58483
+ fs17.copyFileSync(sourcePath, storagePath);
58484
+ const stat2 = fs17.statSync(storagePath);
57793
58485
  this.index.files[hash] = {
57794
58486
  hash,
57795
58487
  name,
@@ -57806,10 +58498,10 @@ var FileCache = class _FileCache {
57806
58498
  async storeBuffer(data, name, mimeType) {
57807
58499
  const hash = this.computeHashSync(data);
57808
58500
  const storagePath = this.getStoragePath(hash);
57809
- if (fs16.existsSync(storagePath)) {
58501
+ if (fs17.existsSync(storagePath)) {
57810
58502
  return { hash, localPath: storagePath };
57811
58503
  }
57812
- fs16.writeFileSync(storagePath, data);
58504
+ fs17.writeFileSync(storagePath, data);
57813
58505
  this.index.files[hash] = {
57814
58506
  hash,
57815
58507
  name,
@@ -57841,7 +58533,7 @@ var FileCache = class _FileCache {
57841
58533
  getLocalPath(hash) {
57842
58534
  const entry = this.index.files[hash];
57843
58535
  if (!entry) return void 0;
57844
- return path16.join(this.dataDir, entry.localPath);
58536
+ return path17.join(this.dataDir, entry.localPath);
57845
58537
  }
57846
58538
  exists(hash) {
57847
58539
  return hash in this.index.files;
@@ -57881,7 +58573,7 @@ var FileCache = class _FileCache {
57881
58573
  getPathHint(filePath) {
57882
58574
  try {
57883
58575
  const resolvedPath = this.getResolvedHintPath(filePath);
57884
- const stat2 = fs16.statSync(resolvedPath);
58576
+ const stat2 = fs17.statSync(resolvedPath);
57885
58577
  if (!stat2.isFile()) return void 0;
57886
58578
  const normalizedMtimeMs = this.normalizeHintMtimeMs(stat2.mtimeMs);
57887
58579
  const key = this.getPathHintKey(resolvedPath);
@@ -57911,7 +58603,7 @@ var FileCache = class _FileCache {
57911
58603
  rememberPathHint(filePath, hash, name) {
57912
58604
  try {
57913
58605
  const resolvedPath = this.getResolvedHintPath(filePath);
57914
- const stat2 = fs16.statSync(resolvedPath);
58606
+ const stat2 = fs17.statSync(resolvedPath);
57915
58607
  if (!stat2.isFile()) return;
57916
58608
  const entry = this.index.files[hash];
57917
58609
  const normalizedMtimeMs = this.normalizeHintMtimeMs(stat2.mtimeMs);
@@ -57921,7 +58613,7 @@ var FileCache = class _FileCache {
57921
58613
  this.index.pathHints[this.getPathHintKey(resolvedPath)] = {
57922
58614
  path: resolvedPath,
57923
58615
  hash,
57924
- name: name || entry?.name || path16.basename(resolvedPath),
58616
+ name: name || entry?.name || path17.basename(resolvedPath),
57925
58617
  size: stat2.size,
57926
58618
  mtimeMs: normalizedMtimeMs,
57927
58619
  lastSeenAt: Date.now(),
@@ -57969,9 +58661,9 @@ var FileCache = class _FileCache {
57969
58661
  deleteEntry(hash, entry) {
57970
58662
  entry = entry || this.index.files[hash];
57971
58663
  if (!entry) return;
57972
- const localPath = path16.join(this.dataDir, entry.localPath);
57973
- if (fs16.existsSync(localPath)) {
57974
- fs16.unlinkSync(localPath);
58664
+ const localPath = path17.join(this.dataDir, entry.localPath);
58665
+ if (fs17.existsSync(localPath)) {
58666
+ fs17.unlinkSync(localPath);
57975
58667
  }
57976
58668
  delete this.index.files[hash];
57977
58669
  if (this.index.pathHints) {
@@ -57985,9 +58677,9 @@ var FileCache = class _FileCache {
57985
58677
  }
57986
58678
  clear() {
57987
58679
  for (const entry of Object.values(this.index.files)) {
57988
- const localPath = path16.join(this.dataDir, entry.localPath);
57989
- if (fs16.existsSync(localPath)) {
57990
- fs16.unlinkSync(localPath);
58680
+ const localPath = path17.join(this.dataDir, entry.localPath);
58681
+ if (fs17.existsSync(localPath)) {
58682
+ fs17.unlinkSync(localPath);
57991
58683
  }
57992
58684
  }
57993
58685
  this.index = { version: CACHE_VERSION, files: {}, pathHints: {} };
@@ -58040,11 +58732,11 @@ var FileCache = class _FileCache {
58040
58732
  };
58041
58733
  }
58042
58734
  static getMimeType(fileName) {
58043
- const ext = path16.extname(fileName).toLowerCase();
58735
+ const ext = path17.extname(fileName).toLowerCase();
58044
58736
  return MIME_TYPES[ext] || "application/octet-stream";
58045
58737
  }
58046
58738
  static getFileTypeFromName(fileName) {
58047
- const baseName = path16.basename(fileName);
58739
+ const baseName = path17.basename(fileName);
58048
58740
  if (!baseName || baseName.startsWith(".")) {
58049
58741
  return void 0;
58050
58742
  }
@@ -58075,7 +58767,7 @@ var FileCache = class _FileCache {
58075
58767
  getResolvedHintPath(filePath) {
58076
58768
  const resolvedPath = resolvePath2(filePath);
58077
58769
  try {
58078
- return fs16.realpathSync.native ? fs16.realpathSync.native(resolvedPath) : fs16.realpathSync(resolvedPath);
58770
+ return fs17.realpathSync.native ? fs17.realpathSync.native(resolvedPath) : fs17.realpathSync(resolvedPath);
58079
58771
  } catch {
58080
58772
  return resolvedPath;
58081
58773
  }
@@ -58083,8 +58775,8 @@ var FileCache = class _FileCache {
58083
58775
  };
58084
58776
 
58085
58777
  // src/files/session-file-manager.ts
58086
- import * as fs17 from "node:fs";
58087
- import * as path17 from "node:path";
58778
+ import * as fs18 from "node:fs";
58779
+ import * as path18 from "node:path";
58088
58780
  var INITIAL_REFRESH_BACKOFF_MS = 6e4;
58089
58781
  var MAX_REFRESH_BACKOFF_MS = 60 * 60 * 1e3;
58090
58782
  var SessionFileManager = class {
@@ -58098,7 +58790,7 @@ var SessionFileManager = class {
58098
58790
  constructor(sessionId, sessionsDir, fileCache, authFingerprint) {
58099
58791
  this.sessionId = sessionId;
58100
58792
  this.sessionsDir = sessionsDir;
58101
- this.statePath = path17.join(this.sessionsDir, `${sessionId}-files.json`);
58793
+ this.statePath = path18.join(this.sessionsDir, `${sessionId}-files.json`);
58102
58794
  this.fileCache = fileCache;
58103
58795
  this.authFingerprint = authFingerprint;
58104
58796
  this.state = this.loadState();
@@ -58108,15 +58800,15 @@ var SessionFileManager = class {
58108
58800
  // Directory Management
58109
58801
  // ============================================================================
58110
58802
  ensureDirectories() {
58111
- if (!fs17.existsSync(this.sessionsDir)) {
58112
- fs17.mkdirSync(this.sessionsDir, { recursive: true });
58803
+ if (!fs18.existsSync(this.sessionsDir)) {
58804
+ fs18.mkdirSync(this.sessionsDir, { recursive: true });
58113
58805
  }
58114
58806
  }
58115
58807
  // ============================================================================
58116
58808
  // State Management
58117
58809
  // ============================================================================
58118
58810
  loadState() {
58119
- if (!fs17.existsSync(this.statePath)) {
58811
+ if (!fs18.existsSync(this.statePath)) {
58120
58812
  return {
58121
58813
  sessionId: this.sessionId,
58122
58814
  lastSyncedAt: 0,
@@ -58124,7 +58816,7 @@ var SessionFileManager = class {
58124
58816
  };
58125
58817
  }
58126
58818
  try {
58127
- return JSON.parse(fs17.readFileSync(this.statePath, "utf-8"));
58819
+ return JSON.parse(fs18.readFileSync(this.statePath, "utf-8"));
58128
58820
  } catch (err) {
58129
58821
  sessionLogger.error(" Failed to load state:", err);
58130
58822
  return {
@@ -58137,12 +58829,12 @@ var SessionFileManager = class {
58137
58829
  saveState() {
58138
58830
  try {
58139
58831
  if (Object.keys(this.state.files).length === 0) {
58140
- if (fs17.existsSync(this.statePath)) {
58141
- fs17.unlinkSync(this.statePath);
58832
+ if (fs18.existsSync(this.statePath)) {
58833
+ fs18.unlinkSync(this.statePath);
58142
58834
  }
58143
58835
  return;
58144
58836
  }
58145
- fs17.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
58837
+ fs18.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
58146
58838
  } catch (err) {
58147
58839
  sessionLogger.error(" Failed to save state:", err);
58148
58840
  }
@@ -58288,7 +58980,7 @@ var SessionFileManager = class {
58288
58980
  return { success: false, coolingDown: true };
58289
58981
  }
58290
58982
  const localPath = this.fileCache.getLocalPath(entry.hash);
58291
- if (!localPath || !fs17.existsSync(localPath)) {
58983
+ if (!localPath || !fs18.existsSync(localPath)) {
58292
58984
  sessionLogger.warn(`Local file not found for ${fileId}`);
58293
58985
  entry.status = "expired";
58294
58986
  entry.error = "Local file missing";
@@ -58439,15 +59131,15 @@ var SessionFileManager = class {
58439
59131
  // Static Utilities
58440
59132
  // ============================================================================
58441
59133
  static cleanupOldSessions(sessionsDir, maxAge = 30 * 24 * 60 * 60 * 1e3) {
58442
- if (!fs17.existsSync(sessionsDir)) return;
59134
+ if (!fs18.existsSync(sessionsDir)) return;
58443
59135
  const now = Date.now();
58444
- const files = fs17.readdirSync(sessionsDir);
59136
+ const files = fs18.readdirSync(sessionsDir);
58445
59137
  for (const file of files) {
58446
59138
  if (!file.endsWith(".json")) continue;
58447
- const filePath = path17.join(sessionsDir, file);
58448
- const stat2 = fs17.statSync(filePath);
59139
+ const filePath = path18.join(sessionsDir, file);
59140
+ const stat2 = fs18.statSync(filePath);
58449
59141
  if (now - stat2.mtime.getTime() > maxAge) {
58450
- fs17.unlinkSync(filePath);
59142
+ fs18.unlinkSync(filePath);
58451
59143
  sessionLogger.debug(`Cleaned up old session: ${file}`);
58452
59144
  }
58453
59145
  }
@@ -58455,8 +59147,8 @@ var SessionFileManager = class {
58455
59147
  };
58456
59148
 
58457
59149
  // src/files/ffmpeg-processor.ts
58458
- import * as fs18 from "node:fs";
58459
- import * as path18 from "node:path";
59150
+ import * as fs19 from "node:fs";
59151
+ import * as path19 from "node:path";
58460
59152
  import { spawn } from "node:child_process";
58461
59153
  var FFmpegProcessor = class {
58462
59154
  ffmpegPath = "ffmpeg";
@@ -58469,17 +59161,17 @@ var FFmpegProcessor = class {
58469
59161
  // Detection
58470
59162
  // ============================================================================
58471
59163
  async checkAvailability() {
58472
- return new Promise((resolve6) => {
59164
+ return new Promise((resolve7) => {
58473
59165
  const proc = spawn(this.ffmpegPath, ["-version"]);
58474
- proc.on("error", () => resolve6(false));
58475
- proc.on("exit", (code) => resolve6(code === 0));
59166
+ proc.on("error", () => resolve7(false));
59167
+ proc.on("exit", (code) => resolve7(code === 0));
58476
59168
  });
58477
59169
  }
58478
59170
  // ============================================================================
58479
59171
  // Video Analysis
58480
59172
  // ============================================================================
58481
59173
  async analyzeVideo(videoPath) {
58482
- return new Promise((resolve6, reject) => {
59174
+ return new Promise((resolve7, reject) => {
58483
59175
  const args = [
58484
59176
  "-v",
58485
59177
  "error",
@@ -58508,7 +59200,7 @@ var FFmpegProcessor = class {
58508
59200
  }
58509
59201
  try {
58510
59202
  const info = this.parseProbeOutput(stdout, videoPath);
58511
- resolve6(info);
59203
+ resolve7(info);
58512
59204
  } catch (err) {
58513
59205
  reject(err);
58514
59206
  }
@@ -58560,7 +59252,7 @@ var FFmpegProcessor = class {
58560
59252
  }
58561
59253
  async compressVideo(inputPath, outputDir, info, opts, onProgress) {
58562
59254
  const outputName = `compressed_${Date.now()}.mp4`;
58563
- const outputPath = path18.join(outputDir, outputName);
59255
+ const outputPath = path19.join(outputDir, outputName);
58564
59256
  const adaptiveParams = this.calculateAdaptiveParams(info.duration, opts);
58565
59257
  const args = [
58566
59258
  "-i",
@@ -58591,7 +59283,7 @@ var FFmpegProcessor = class {
58591
59283
  onProgress?.(percent);
58592
59284
  }
58593
59285
  });
58594
- const outputStat = fs18.statSync(outputPath);
59286
+ const outputStat = fs19.statSync(outputPath);
58595
59287
  return {
58596
59288
  type: "video",
58597
59289
  output: outputPath,
@@ -58622,9 +59314,9 @@ var FFmpegProcessor = class {
58622
59314
  }
58623
59315
  async extractKeyframes(inputPath, outputDir, info, opts, onProgress) {
58624
59316
  const interval = Math.floor(info.duration / opts.keyframeCount);
58625
- const framesDir = path18.join(outputDir, `keyframes_${Date.now()}`);
58626
- fs18.mkdirSync(framesDir, { recursive: true });
58627
- 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");
58628
59320
  const args = [
58629
59321
  "-i",
58630
59322
  inputPath,
@@ -58642,10 +59334,10 @@ var FFmpegProcessor = class {
58642
59334
  onProgress?.(percent);
58643
59335
  }
58644
59336
  });
58645
- 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));
58646
59338
  if (frames.length < opts.keyframeCount / 2) {
58647
- fs18.rmSync(framesDir, { recursive: true, force: true });
58648
- fs18.mkdirSync(framesDir, { recursive: true });
59339
+ fs19.rmSync(framesDir, { recursive: true, force: true });
59340
+ fs19.mkdirSync(framesDir, { recursive: true });
58649
59341
  const uniformArgs = [
58650
59342
  "-i",
58651
59343
  inputPath,
@@ -58661,10 +59353,10 @@ var FFmpegProcessor = class {
58661
59353
  onProgress?.(percent);
58662
59354
  }
58663
59355
  });
58664
- 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));
58665
59357
  }
58666
59358
  const totalOutputSize = frames.reduce(
58667
- (sum, f) => sum + fs18.statSync(f).size,
59359
+ (sum, f) => sum + fs19.statSync(f).size,
58668
59360
  0
58669
59361
  );
58670
59362
  return {
@@ -58685,7 +59377,7 @@ var FFmpegProcessor = class {
58685
59377
  const opts = { ...DEFAULT_PIC_OPTIONS, ...options };
58686
59378
  const ext = opts.format === "webp" ? ".webp" : ".jpg";
58687
59379
  const outputName = `optimized_${Date.now()}${ext}`;
58688
- const outputPath = path18.join(outputDir, outputName);
59380
+ const outputPath = path19.join(outputDir, outputName);
58689
59381
  const args = [
58690
59382
  "-i",
58691
59383
  inputPath,
@@ -58703,7 +59395,7 @@ var FFmpegProcessor = class {
58703
59395
  // FFmpeg Execution
58704
59396
  // ============================================================================
58705
59397
  runFFmpeg(args) {
58706
- return new Promise((resolve6, reject) => {
59398
+ return new Promise((resolve7, reject) => {
58707
59399
  const proc = spawn(this.ffmpegPath, args);
58708
59400
  let stderr = "";
58709
59401
  proc.stderr.on("data", (data) => {
@@ -58718,7 +59410,7 @@ var FFmpegProcessor = class {
58718
59410
  )
58719
59411
  );
58720
59412
  } else {
58721
- resolve6();
59413
+ resolve7();
58722
59414
  }
58723
59415
  });
58724
59416
  proc.on("error", (err) => {
@@ -58735,7 +59427,7 @@ var FFmpegProcessor = class {
58735
59427
  // Progress Monitoring (for future use)
58736
59428
  // ============================================================================
58737
59429
  async runWithProgress(args, onProgress) {
58738
- return new Promise((resolve6, reject) => {
59430
+ return new Promise((resolve7, reject) => {
58739
59431
  const progressArgs = [
58740
59432
  ...args,
58741
59433
  "-progress",
@@ -58765,7 +59457,7 @@ var FFmpegProcessor = class {
58765
59457
  )
58766
59458
  );
58767
59459
  } else {
58768
- resolve6();
59460
+ resolve7();
58769
59461
  }
58770
59462
  });
58771
59463
  });
@@ -58860,8 +59552,8 @@ var DIRECTORY_LISTING_TTL_MS = 1500;
58860
59552
  var FileManager = class {
58861
59553
  constructor(options) {
58862
59554
  this.options = options;
58863
- const baseDir = options.baseDir || path19.join(process.env.HOME || ".", ".pie", "files");
58864
- 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");
58865
59557
  this.fileCache = new FileCache(baseDir);
58866
59558
  this.authFingerprint = options.apiKey ? this.buildAuthFingerprint(options.apiKey) : void 0;
58867
59559
  this.sessionManager = new SessionFileManager(
@@ -58872,7 +59564,7 @@ var FileManager = class {
58872
59564
  );
58873
59565
  this.ffmpeg = new FFmpegProcessor(options.ffmpegPath);
58874
59566
  this.platform = normalizeFileManagerPlatform(options.platform);
58875
- this.tempDir = path19.join(baseDir, "temp");
59567
+ this.tempDir = path20.join(baseDir, "temp");
58876
59568
  this.ensureDirectories();
58877
59569
  }
58878
59570
  options;
@@ -58884,8 +59576,8 @@ var FileManager = class {
58884
59576
  directoryListingCache = /* @__PURE__ */ new Map();
58885
59577
  authFingerprint;
58886
59578
  ensureDirectories() {
58887
- if (!fs19.existsSync(this.tempDir)) {
58888
- fs19.mkdirSync(this.tempDir, { recursive: true });
59579
+ if (!fs20.existsSync(this.tempDir)) {
59580
+ fs20.mkdirSync(this.tempDir, { recursive: true });
58889
59581
  }
58890
59582
  }
58891
59583
  // ============================================================================
@@ -58915,8 +59607,8 @@ var FileManager = class {
58915
59607
  // ============================================================================
58916
59608
  async stageLocal(filePath, options = {}) {
58917
59609
  const resolvedPath = resolvePath2(filePath);
58918
- const sourceName = options.sourceName || path19.basename(resolvedPath);
58919
- if (!fs19.existsSync(resolvedPath)) {
59610
+ const sourceName = options.sourceName || path20.basename(resolvedPath);
59611
+ if (!fs20.existsSync(resolvedPath)) {
58920
59612
  throw new FileManagerError(
58921
59613
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
58922
59614
  `File not found: ${filePath}`
@@ -58931,7 +59623,7 @@ var FileManager = class {
58931
59623
  this.fileCache.rememberPathHint(resolvedPath, hash, sourceName);
58932
59624
  this.invalidateDirectoryListingCacheForPath(resolvedPath);
58933
59625
  const entry = this.fileCache.getByHash(hash);
58934
- const size = entry?.size ?? fs19.statSync(resolvedPath).size;
59626
+ const size = entry?.size ?? fs20.statSync(resolvedPath).size;
58935
59627
  return {
58936
59628
  success: true,
58937
59629
  hash,
@@ -58945,8 +59637,8 @@ var FileManager = class {
58945
59637
  }
58946
59638
  async upload(filePath, options = {}) {
58947
59639
  const resolvedPath = resolvePath2(filePath);
58948
- const sourceName = options.sourceName || path19.basename(resolvedPath);
58949
- if (!fs19.existsSync(resolvedPath)) {
59640
+ const sourceName = options.sourceName || path20.basename(resolvedPath);
59641
+ if (!fs20.existsSync(resolvedPath)) {
58950
59642
  throw new FileManagerError(
58951
59643
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
58952
59644
  `File not found: ${filePath}`
@@ -59027,7 +59719,7 @@ var FileManager = class {
59027
59719
  // ============================================================================
59028
59720
  async uploadPic(filePath, options = {}) {
59029
59721
  const resolvedPath = resolvePath2(filePath);
59030
- if (!fs19.existsSync(resolvedPath)) {
59722
+ if (!fs20.existsSync(resolvedPath)) {
59031
59723
  throw new FileManagerError(
59032
59724
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
59033
59725
  `File not found: ${filePath}`
@@ -59035,7 +59727,7 @@ var FileManager = class {
59035
59727
  }
59036
59728
  const cachedResult = await this.tryReuseCachedSourceUpload(
59037
59729
  resolvedPath,
59038
- options.sourceName || path19.basename(resolvedPath),
59730
+ options.sourceName || path20.basename(resolvedPath),
59039
59731
  options.platform || this.platform
59040
59732
  );
59041
59733
  if (cachedResult) {
@@ -59052,12 +59744,12 @@ var FileManager = class {
59052
59744
  const result = await this.upload(optimizedPath, {
59053
59745
  ...options,
59054
59746
  purpose: "image",
59055
- sourceName: path19.basename(resolvedPath)
59747
+ sourceName: path20.basename(resolvedPath)
59056
59748
  });
59057
- this.fileCache.rememberPathHint(resolvedPath, result.hash, path19.basename(resolvedPath));
59749
+ this.fileCache.rememberPathHint(resolvedPath, result.hash, path20.basename(resolvedPath));
59058
59750
  this.invalidateDirectoryListingCacheForPath(resolvedPath);
59059
59751
  try {
59060
- fs19.unlinkSync(optimizedPath);
59752
+ fs20.unlinkSync(optimizedPath);
59061
59753
  } catch {
59062
59754
  }
59063
59755
  return result;
@@ -59067,7 +59759,7 @@ var FileManager = class {
59067
59759
  // ============================================================================
59068
59760
  async uploadVideo(filePath, options = {}) {
59069
59761
  const resolvedPath = resolvePath2(filePath);
59070
- if (!fs19.existsSync(resolvedPath)) {
59762
+ if (!fs20.existsSync(resolvedPath)) {
59071
59763
  throw new FileManagerError(
59072
59764
  "LOCAL_FILE_MISSING" /* LOCAL_FILE_MISSING */,
59073
59765
  `File not found: ${filePath}`
@@ -59075,7 +59767,7 @@ var FileManager = class {
59075
59767
  }
59076
59768
  const cachedResult = await this.tryReuseCachedSourceUpload(
59077
59769
  resolvedPath,
59078
- options.sourceName || path19.basename(resolvedPath),
59770
+ options.sourceName || path20.basename(resolvedPath),
59079
59771
  options.platform || this.platform
59080
59772
  );
59081
59773
  if (cachedResult) {
@@ -59125,9 +59817,9 @@ var FileManager = class {
59125
59817
  result = await this.upload(preprocessResult.output, {
59126
59818
  ...options,
59127
59819
  purpose: "video",
59128
- sourceName: path19.basename(resolvedPath)
59820
+ sourceName: path20.basename(resolvedPath)
59129
59821
  });
59130
- this.fileCache.rememberPathHint(resolvedPath, result.hash, path19.basename(resolvedPath));
59822
+ this.fileCache.rememberPathHint(resolvedPath, result.hash, path20.basename(resolvedPath));
59131
59823
  this.invalidateDirectoryListingCacheForPath(resolvedPath);
59132
59824
  } else {
59133
59825
  throw new FileManagerError(
@@ -59419,7 +60111,7 @@ var FileManager = class {
59419
60111
  * Detect purpose from file path based on extension
59420
60112
  */
59421
60113
  detectPurpose(filePath) {
59422
- const ext = path19.extname(filePath).toLowerCase();
60114
+ const ext = path20.extname(filePath).toLowerCase();
59423
60115
  switch (ext) {
59424
60116
  case ".mp4":
59425
60117
  case ".mov":
@@ -59461,13 +60153,13 @@ var FileManager = class {
59461
60153
  cleanupTempFiles(output) {
59462
60154
  try {
59463
60155
  if (typeof output === "string") {
59464
- if (fs19.existsSync(output)) {
59465
- fs19.unlinkSync(output);
60156
+ if (fs20.existsSync(output)) {
60157
+ fs20.unlinkSync(output);
59466
60158
  }
59467
60159
  } else {
59468
60160
  for (const file of output) {
59469
- if (fs19.existsSync(file)) {
59470
- fs19.unlinkSync(file);
60161
+ if (fs20.existsSync(file)) {
60162
+ fs20.unlinkSync(file);
59471
60163
  }
59472
60164
  }
59473
60165
  }
@@ -59499,7 +60191,7 @@ var FileManager = class {
59499
60191
  }];
59500
60192
  }
59501
60193
  const localPath = this.fileCache.getLocalPath(entry.hash);
59502
- if (localPath && fs19.existsSync(localPath)) {
60194
+ if (localPath && fs20.existsSync(localPath)) {
59503
60195
  return [{
59504
60196
  fileId,
59505
60197
  name: cacheEntry.name,
@@ -59596,12 +60288,12 @@ var FileManager = class {
59596
60288
  directories: []
59597
60289
  };
59598
60290
  try {
59599
- const entries = fs19.readdirSync(resolvedDir, { withFileTypes: true });
60291
+ const entries = fs20.readdirSync(resolvedDir, { withFileTypes: true });
59600
60292
  for (const entry of entries) {
59601
60293
  if (!entry.name || entry.name.startsWith(".")) {
59602
60294
  continue;
59603
60295
  }
59604
- const fullPath = path19.join(resolvedDir, entry.name);
60296
+ const fullPath = path20.join(resolvedDir, entry.name);
59605
60297
  if (entry.isDirectory()) {
59606
60298
  listing.directories.push({ name: entry.name, path: fullPath });
59607
60299
  continue;
@@ -59610,7 +60302,7 @@ var FileManager = class {
59610
60302
  let isFile = entry.isFile();
59611
60303
  if (entry.isSymbolicLink()) {
59612
60304
  try {
59613
- const targetStat = fs19.statSync(fullPath);
60305
+ const targetStat = fs20.statSync(fullPath);
59614
60306
  isDirectory = targetStat.isDirectory();
59615
60307
  isFile = targetStat.isFile();
59616
60308
  } catch {
@@ -59628,7 +60320,7 @@ var FileManager = class {
59628
60320
  if (!type) {
59629
60321
  continue;
59630
60322
  }
59631
- const stat2 = fs19.statSync(fullPath);
60323
+ const stat2 = fs20.statSync(fullPath);
59632
60324
  listing.files.push({
59633
60325
  name: entry.name,
59634
60326
  path: fullPath,
@@ -59642,7 +60334,7 @@ var FileManager = class {
59642
60334
  return listing;
59643
60335
  }
59644
60336
  invalidateDirectoryListingCacheForPath(filePath) {
59645
- this.directoryListingCache.delete(path19.dirname(resolvePath2(filePath)));
60337
+ this.directoryListingCache.delete(path20.dirname(resolvePath2(filePath)));
59646
60338
  }
59647
60339
  buildAuthFingerprint(apiKey) {
59648
60340
  return crypto2.createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
@@ -59691,8 +60383,8 @@ function createFileManagerForModel(params) {
59691
60383
  sessionId: params.sessionId,
59692
60384
  platform: platform2,
59693
60385
  apiKey: params.apiKey,
59694
- baseDir: path20.join(params.configDir, "files"),
59695
- sessionsDir: path20.join(params.configDir, "sessions")
60386
+ baseDir: path21.join(params.configDir, "files"),
60387
+ sessionsDir: path21.join(params.configDir, "sessions")
59696
60388
  });
59697
60389
  switch (getFileManagerAdapterKind(platform2)) {
59698
60390
  case "kimi":
@@ -59727,15 +60419,15 @@ function isInlineMediaRequest(request) {
59727
60419
  return request.kind === "image" || request.kind === "video";
59728
60420
  }
59729
60421
  async function prepareInlineMediaContent(params) {
59730
- const stat2 = await fs20.stat(params.request.absolutePath);
60422
+ const stat2 = await fs21.stat(params.request.absolutePath);
59731
60423
  const limitMb = getCapabilityLimitMb(params.model, params.request.kind);
59732
60424
  const limitBytes = limitMb * 1024 * 1024;
59733
60425
  if (stat2.size > limitBytes) {
59734
60426
  throw new Error(
59735
- `${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.`
59736
60428
  );
59737
60429
  }
59738
- const data = await fs20.readFile(params.request.absolutePath, "base64");
60430
+ const data = await fs21.readFile(params.request.absolutePath, "base64");
59739
60431
  const content = params.request.kind === "video" ? [{ type: "video", data, mimeType: params.request.mimeType }] : [{ type: "image", data, mimeType: params.request.mimeType }];
59740
60432
  return {
59741
60433
  content,
@@ -59755,7 +60447,7 @@ async function waitForReady(params) {
59755
60447
  if (status.status === "error" || status.status === "expired") {
59756
60448
  throw new Error(`Uploaded file is not ready: ${status.status}${status.error ? ` (${status.error})` : ""}`);
59757
60449
  }
59758
- await new Promise((resolve6) => setTimeout(resolve6, 1500));
60450
+ await new Promise((resolve7) => setTimeout(resolve7, 1500));
59759
60451
  }
59760
60452
  throw new Error("Timed out waiting for uploaded file to become ready");
59761
60453
  }
@@ -59771,7 +60463,7 @@ async function prepareFileContent(configDir, params) {
59771
60463
  });
59772
60464
  if (params.request.kind === "document") {
59773
60465
  const upload2 = await manager.upload(params.request.absolutePath, {
59774
- sourceName: path20.basename(params.request.displayPath),
60466
+ sourceName: path21.basename(params.request.displayPath),
59775
60467
  purpose: "file-extract"
59776
60468
  });
59777
60469
  await waitForReady({ manager, fileId: upload2.fileId, timeoutMs: 12e4, signal: params.request.signal });
@@ -59794,8 +60486,8 @@ async function prepareFileContent(configDir, params) {
59794
60486
  }
59795
60487
  };
59796
60488
  }
59797
- 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, {
59798
- 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),
59799
60491
  purpose: params.request.kind === "image" ? "image" : "file-extract"
59800
60492
  });
59801
60493
  if (params.request.kind !== "video") {
@@ -59851,8 +60543,8 @@ function createIsolatedRegexSession(pattern, ignoreCase) {
59851
60543
  let initError;
59852
60544
  let readyResolve;
59853
60545
  let readyReject;
59854
- const ready = new Promise((resolve6, reject) => {
59855
- readyResolve = resolve6;
60546
+ const ready = new Promise((resolve7, reject) => {
60547
+ readyResolve = resolve7;
59856
60548
  readyReject = reject;
59857
60549
  });
59858
60550
  void ready.catch(() => void 0);
@@ -59874,7 +60566,7 @@ function createIsolatedRegexSession(pattern, ignoreCase) {
59874
60566
  if (initError) throw initError;
59875
60567
  if (signal?.aborted) throw new Error("Operation aborted");
59876
60568
  const id = nextId++;
59877
- return new Promise((resolve6, reject) => {
60569
+ return new Promise((resolve7, reject) => {
59878
60570
  let settled = false;
59879
60571
  const finish = (error, matches) => {
59880
60572
  if (settled) return;
@@ -59884,7 +60576,7 @@ function createIsolatedRegexSession(pattern, ignoreCase) {
59884
60576
  worker.off("message", onMessage);
59885
60577
  worker.off("error", onWorkerError);
59886
60578
  if (error) reject(error);
59887
- else resolve6(matches ?? []);
60579
+ else resolve7(matches ?? []);
59888
60580
  };
59889
60581
  const terminate = (error) => {
59890
60582
  disposed = true;
@@ -60214,22 +60906,22 @@ var ExecutionStateManager = class {
60214
60906
  };
60215
60907
 
60216
60908
  // src/session-trace.ts
60217
- import * as fs21 from "node:fs";
60218
- import * as path21 from "node:path";
60909
+ import * as fs22 from "node:fs";
60910
+ import * as path22 from "node:path";
60219
60911
  function saveTraceFileBestEffort(tracePath, content) {
60220
60912
  const tempPath = `${tracePath}.${process.pid}.${Date.now()}.tmp`;
60221
60913
  try {
60222
- fs21.writeFileSync(tempPath, content, "utf8");
60914
+ fs22.writeFileSync(tempPath, content, "utf8");
60223
60915
  try {
60224
- fs21.renameSync(tempPath, tracePath);
60916
+ fs22.renameSync(tempPath, tracePath);
60225
60917
  return;
60226
60918
  } catch {
60227
60919
  try {
60228
- fs21.copyFileSync(tempPath, tracePath);
60920
+ fs22.copyFileSync(tempPath, tracePath);
60229
60921
  } catch {
60230
60922
  } finally {
60231
60923
  try {
60232
- fs21.unlinkSync(tempPath);
60924
+ fs22.unlinkSync(tempPath);
60233
60925
  } catch {
60234
60926
  }
60235
60927
  }
@@ -60242,16 +60934,16 @@ var SessionTraceWriter = class extends SessionTraceSnapshotWriter {
60242
60934
  super({
60243
60935
  getSessionId: () => sessionManager.getActiveSessionId(),
60244
60936
  loadTrace: (sessionId) => {
60245
- const tracePath = path21.join(sessionsDir, `${sessionId}.trace.json`);
60246
- if (!fs21.existsSync(tracePath)) return null;
60937
+ const tracePath = path22.join(sessionsDir, `${sessionId}.trace.json`);
60938
+ if (!fs22.existsSync(tracePath)) return null;
60247
60939
  try {
60248
- return JSON.parse(fs21.readFileSync(tracePath, "utf8"));
60940
+ return JSON.parse(fs22.readFileSync(tracePath, "utf8"));
60249
60941
  } catch {
60250
60942
  return null;
60251
60943
  }
60252
60944
  },
60253
60945
  saveTrace: (trace) => {
60254
- const tracePath = path21.join(sessionsDir, `${trace.sessionId}.trace.json`);
60946
+ const tracePath = path22.join(sessionsDir, `${trace.sessionId}.trace.json`);
60255
60947
  saveTraceFileBestEffort(tracePath, JSON.stringify(trace, null, 2));
60256
60948
  }
60257
60949
  });
@@ -60758,7 +61450,7 @@ function normalizeSessionTitle(raw) {
60758
61450
  }
60759
61451
 
60760
61452
  // src/components/extension-selector.ts
60761
- import * as path22 from "node:path";
61453
+ import * as path23 from "node:path";
60762
61454
  var ExtensionSelectorComponent = class extends Container {
60763
61455
  selectList;
60764
61456
  constructor(title, options, onSelect, onCancel) {
@@ -60828,8 +61520,8 @@ var ExtensionStateSubmenu = class extends Container {
60828
61520
  }
60829
61521
  };
60830
61522
  function unidentifiedLabel(info) {
60831
- const parentName = path22.basename(path22.dirname(info.entryPath));
60832
- const fileName = path22.basename(info.entryPath);
61523
+ const parentName = path23.basename(path23.dirname(info.entryPath));
61524
+ const fileName = path23.basename(info.entryPath);
60833
61525
  return `(unidentified) ${parentName || fileName}`;
60834
61526
  }
60835
61527
  var ExtensionEnablementSelectorComponent = class extends Container {
@@ -61119,7 +61811,7 @@ async function handleCacheTest(host) {
61119
61811
  host.chatContainer.addChild(new Text(theme.fg("dim", ` ${summarizeUsage(result1.usage)}`), 1, 0));
61120
61812
  host.chatContainer.addChild(new Text(theme.fg("dim", " Waiting 2s before replaying identical request..."), 1, 0));
61121
61813
  host.ui.requestRender();
61122
- await new Promise((resolve6) => setTimeout(resolve6, 2e3));
61814
+ await new Promise((resolve7) => setTimeout(resolve7, 2e3));
61123
61815
  host.chatContainer.addChild(new Text(theme.fg("accent", "Request 2/2: replaying identical request..."), 1, 0));
61124
61816
  host.ui.requestRender();
61125
61817
  result2 = await completeSimple(model, context, options);
@@ -61300,10 +61992,10 @@ async function handleDebugPackageCommand(host) {
61300
61992
  }
61301
61993
 
61302
61994
  // src/commands/interactive-session-commands.ts
61303
- import * as os5 from "os";
61995
+ import * as os6 from "os";
61304
61996
 
61305
61997
  // src/components/session-selector.ts
61306
- import * as path23 from "node:path";
61998
+ import * as path24 from "node:path";
61307
61999
  function formatDate(timestamp) {
61308
62000
  const date = new Date(timestamp);
61309
62001
  const now = /* @__PURE__ */ new Date();
@@ -61334,7 +62026,7 @@ function getPathFlavor(value, peer) {
61334
62026
  return "posix";
61335
62027
  }
61336
62028
  function getPathApi(flavor) {
61337
- return flavor === "windows" ? path23.win32 : path23.posix;
62029
+ return flavor === "windows" ? path24.win32 : path24.posix;
61338
62030
  }
61339
62031
  function normalizeForComparison(value, flavor) {
61340
62032
  const pathApi = getPathApi(flavor);
@@ -61354,10 +62046,10 @@ function formatSessionCwdTag(sessionCwd, currentCwd, homeDir) {
61354
62046
  const normalizedSessionCwd = pathApi.normalize(sessionCwd);
61355
62047
  if (homeDir && getPathFlavor(homeDir, sessionCwd) === sessionFlavor) {
61356
62048
  const normalizedHome = pathApi.normalize(homeDir);
61357
- const relative2 = pathApi.relative(normalizedHome, normalizedSessionCwd);
61358
- 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);
61359
62051
  if (!outsideHome) {
61360
- return relative2 ? `~${pathApi.sep}${relative2}` : "~";
62052
+ return relative3 ? `~${pathApi.sep}${relative3}` : "~";
61361
62053
  }
61362
62054
  }
61363
62055
  return normalizedSessionCwd;
@@ -62007,7 +62699,7 @@ var AssistantMessageComponent = class extends Container {
62007
62699
  };
62008
62700
 
62009
62701
  // src/components/tool-execution.ts
62010
- import * as os4 from "node:os";
62702
+ import * as os5 from "node:os";
62011
62703
 
62012
62704
  // ../../node_modules/diff/libesm/diff/base.js
62013
62705
  var Diff = class {
@@ -62109,16 +62801,16 @@ var Diff = class {
62109
62801
  }
62110
62802
  }
62111
62803
  }
62112
- addToPath(path31, added, removed, oldPosInc, options) {
62113
- const last = path31.lastComponent;
62804
+ addToPath(path33, added, removed, oldPosInc, options) {
62805
+ const last = path33.lastComponent;
62114
62806
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
62115
62807
  return {
62116
- oldPos: path31.oldPos + oldPosInc,
62808
+ oldPos: path33.oldPos + oldPosInc,
62117
62809
  lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
62118
62810
  };
62119
62811
  } else {
62120
62812
  return {
62121
- oldPos: path31.oldPos + oldPosInc,
62813
+ oldPos: path33.oldPos + oldPosInc,
62122
62814
  lastComponent: { count: 1, added, removed, previousComponent: last }
62123
62815
  };
62124
62816
  }
@@ -62566,13 +63258,13 @@ function formatSize2(bytes) {
62566
63258
  }
62567
63259
  var DEFAULT_MAX_BYTES = 102400;
62568
63260
  var DEFAULT_MAX_LINES = 100;
62569
- function shortenPath(path31) {
62570
- if (typeof path31 !== "string") return "";
62571
- const home = os4.homedir();
62572
- if (path31.startsWith(home)) {
62573
- 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)}`;
62574
63266
  }
62575
- return path31;
63267
+ return path33;
62576
63268
  }
62577
63269
  function replaceTabs(text) {
62578
63270
  return text.replace(/\t/g, " ");
@@ -62598,11 +63290,13 @@ var ToolExecutionComponent = class extends Container {
62598
63290
  isPartial = true;
62599
63291
  ui;
62600
63292
  result;
63293
+ renderHint;
62601
63294
  constructor(toolName, args, options = {}, ui) {
62602
63295
  super();
62603
63296
  this.toolName = toolName;
62604
63297
  this.args = args;
62605
63298
  this.expanded = options.expanded ?? false;
63299
+ this.renderHint = options.renderHint;
62606
63300
  this.ui = ui;
62607
63301
  this.addChild(new Spacer(1));
62608
63302
  this.contentBox = new Box(1, 1, (text) => theme.bg("toolPendingBg", text));
@@ -62653,14 +63347,14 @@ ${renderManageTodoListResult(this.result, { expanded: this.expanded }, theme)}`;
62653
63347
  }
62654
63348
  } else if (this.toolName === "read_file") {
62655
63349
  const rawPath = str(this.args?.file_path ?? this.args?.path);
62656
- const path31 = rawPath !== null ? shortenPath(rawPath) : null;
63350
+ const path33 = rawPath !== null ? shortenPath(rawPath) : null;
62657
63351
  const offset = typeof this.args?.offset === "number" ? this.args.offset : void 0;
62658
63352
  const limit = typeof this.args?.limit === "number" ? this.args.limit : void 0;
62659
63353
  let pathDisplay;
62660
- if (path31 === null) {
63354
+ if (path33 === null) {
62661
63355
  pathDisplay = invalidArg;
62662
- } else if (path31) {
62663
- pathDisplay = theme.fg("accent", path31);
63356
+ } else if (path33) {
63357
+ pathDisplay = theme.fg("accent", path33);
62664
63358
  } else {
62665
63359
  const partialPath = partialStr(this.args?.file_path ?? this.args?.path);
62666
63360
  pathDisplay = theme.fg("toolOutput", partialPath);
@@ -62707,12 +63401,12 @@ ${renderManageTodoListResult(this.result, { expanded: this.expanded }, theme)}`;
62707
63401
  } else if (this.toolName === "write_file") {
62708
63402
  const rawPath = str(this.args?.file_path ?? this.args?.path);
62709
63403
  const fileContent = str(this.args?.content);
62710
- const path31 = rawPath !== null ? shortenPath(rawPath) : null;
63404
+ const path33 = rawPath !== null ? shortenPath(rawPath) : null;
62711
63405
  let pathDisplay;
62712
- if (path31 === null) {
63406
+ if (path33 === null) {
62713
63407
  pathDisplay = invalidArg;
62714
- } else if (path31) {
62715
- pathDisplay = theme.fg("accent", path31);
63408
+ } else if (path33) {
63409
+ pathDisplay = theme.fg("accent", path33);
62716
63410
  } else {
62717
63411
  const partialPath = partialStr(this.args?.file_path ?? this.args?.path);
62718
63412
  pathDisplay = theme.fg("toolOutput", partialPath);
@@ -62749,12 +63443,12 @@ ${theme.fg("error", errorText)}`;
62749
63443
  }
62750
63444
  } else if (this.toolName === "edit_file") {
62751
63445
  const rawPath = str(this.args?.file_path ?? this.args?.path);
62752
- const path31 = rawPath !== null ? shortenPath(rawPath) : null;
63446
+ const path33 = rawPath !== null ? shortenPath(rawPath) : null;
62753
63447
  let pathDisplay;
62754
- if (path31 === null) {
63448
+ if (path33 === null) {
62755
63449
  pathDisplay = invalidArg;
62756
- } else if (path31) {
62757
- pathDisplay = theme.fg("accent", path31);
63450
+ } else if (path33) {
63451
+ pathDisplay = theme.fg("accent", path33);
62758
63452
  } else {
62759
63453
  const partialPath = partialStr(this.args?.file_path ?? this.args?.path);
62760
63454
  pathDisplay = theme.fg("toolOutput", partialPath);
@@ -62779,7 +63473,7 @@ ${theme.fg("error", errorText)}`;
62779
63473
  const diff = this.result.details.diff;
62780
63474
  const firstChangedLine = this.result.details.firstChangedLine;
62781
63475
  if (firstChangedLine) {
62782
- 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}`)}`;
62783
63477
  }
62784
63478
  const lines = diff.split("\n");
62785
63479
  const maxLines = this.expanded ? lines.length : 15;
@@ -62799,7 +63493,7 @@ ${theme.fg("error", errorText)}`;
62799
63493
  const diff = generateEditDiff(oldText, newText, rawPath);
62800
63494
  const firstChangedLine = findFirstChangedLine(oldText, newText);
62801
63495
  if (firstChangedLine) {
62802
- 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}`)}`;
62803
63497
  }
62804
63498
  const lines = diff.split("\n");
62805
63499
  const maxLines = this.expanded ? lines.length : 15;
@@ -62818,9 +63512,9 @@ ${theme.fg("error", errorText)}`;
62818
63512
  }
62819
63513
  } else if (this.toolName === "list_dir") {
62820
63514
  const rawPath = str(this.args?.path);
62821
- const path31 = rawPath !== null ? shortenPath(rawPath || ".") : null;
63515
+ const path33 = rawPath !== null ? shortenPath(rawPath || ".") : null;
62822
63516
  const limit = this.args?.limit;
62823
- 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)}`;
62824
63518
  if (limit !== void 0) {
62825
63519
  text += theme.fg("toolOutput", ` (limit ${limit})`);
62826
63520
  }
@@ -62856,9 +63550,9 @@ ${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
62856
63550
  } else if (this.toolName === "find_files") {
62857
63551
  const pattern = str(this.args?.pattern);
62858
63552
  const rawPath = str(this.args?.path);
62859
- const path31 = rawPath !== null ? shortenPath(rawPath || ".") : null;
63553
+ const path33 = rawPath !== null ? shortenPath(rawPath || ".") : null;
62860
63554
  const limit = this.args?.limit;
62861
- 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}`);
62862
63556
  if (limit !== void 0) {
62863
63557
  text += theme.fg("toolOutput", ` (limit ${limit})`);
62864
63558
  }
@@ -62894,10 +63588,10 @@ ${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
62894
63588
  } else if (this.toolName === "grep_text") {
62895
63589
  const pattern = str(this.args?.pattern);
62896
63590
  const rawPath = str(this.args?.path);
62897
- const path31 = rawPath !== null ? shortenPath(rawPath || ".") : null;
63591
+ const path33 = rawPath !== null ? shortenPath(rawPath || ".") : null;
62898
63592
  const glob = str(this.args?.glob);
62899
63593
  const limit = this.args?.limit;
62900
- 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}`);
62901
63595
  if (glob) {
62902
63596
  text += theme.fg("toolOutput", ` (${glob})`);
62903
63597
  }
@@ -62977,33 +63671,97 @@ ${theme.fg("warning", `[${warnings.join(". ")}]`)}`;
62977
63671
  }
62978
63672
  }
62979
63673
  } else {
62980
- text = theme.fg("toolTitle", theme.bold(this.toolName));
62981
- if (this.args && Object.keys(this.args).length > 0) {
62982
- const content = JSON.stringify(this.args, null, 2);
62983
- text += `
62984
-
62985
- ${theme.fg("toolOutput", content)}`;
63674
+ const hinted = this.formatWithRenderHint();
63675
+ if (hinted !== null) {
63676
+ text = hinted;
63677
+ } else {
63678
+ text = this.formatGenericTool();
62986
63679
  }
62987
- if (this.result) {
62988
- const output = this.getTextOutput();
62989
- if (output) {
62990
- const lines = output.split("\n");
62991
- const maxLines = this.expanded ? lines.length : 20;
62992
- const displayLines = lines.slice(0, maxLines);
62993
- const remaining = lines.length - maxLines;
62994
- text += "\n\n" + displayLines.map((line) => theme.fg("toolOutput", line)).join("\n");
62995
- if (remaining > 0) {
62996
- 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", `
62997
63718
  ... (${remaining} more lines, press Ctrl+O to expand)`);
63719
+ }
62998
63720
  }
62999
63721
  }
63000
- if (this.result.isError) {
63001
- const errorOutput = this.getTextOutput() || "Unknown error";
63002
- text += `
63722
+ }
63723
+ if (!produced) return null;
63724
+ if (this.result?.isError) {
63725
+ const errorOutput = this.getTextOutput() || "Unknown error";
63726
+ text += `
63003
63727
 
63004
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)`);
63005
63757
  }
63006
63758
  }
63759
+ if (this.result.isError) {
63760
+ const errorOutput = this.getTextOutput() || "Unknown error";
63761
+ text += `
63762
+
63763
+ ${theme.fg("error", "Error: " + errorOutput)}`;
63764
+ }
63007
63765
  }
63008
63766
  return text;
63009
63767
  }
@@ -63048,7 +63806,7 @@ async function handleResumeCommand(host) {
63048
63806
  }
63049
63807
  }, {
63050
63808
  currentCwd: host.options.cwd,
63051
- homeDir: os5.homedir(),
63809
+ homeDir: os6.homedir(),
63052
63810
  refreshMetadata: (sessionId) => host.sessionManager.refreshDerivedName(sessionId),
63053
63811
  requestRender: () => host.ui.requestRender(),
63054
63812
  totalCount
@@ -63057,6 +63815,15 @@ async function handleResumeCommand(host) {
63057
63815
  });
63058
63816
  }
63059
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
+ }
63060
63827
  try {
63061
63828
  const success = await host.sessionManager.loadSession(sessionId);
63062
63829
  if (!success) {
@@ -63351,8 +64118,8 @@ async function forkAtEntry(host, entryId) {
63351
64118
  host.showError("Failed to branch at entry");
63352
64119
  return;
63353
64120
  }
63354
- const path31 = host.sessionManager.getPathToEntry(entryId);
63355
- 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 ");
63356
64123
  const messages = host.sessionManager.getMessages();
63357
64124
  host.syncAgentFromActiveSession(messages);
63358
64125
  await host.syncSessionScopedState(messages);
@@ -63365,7 +64132,7 @@ async function forkAtEntry(host, entryId) {
63365
64132
  host.updatePendingMessagesDisplay();
63366
64133
  host.renderMessageHistory(messages);
63367
64134
  await host.sessionManager.save();
63368
- 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.`);
63369
64136
  } catch (error) {
63370
64137
  host.showError(
63371
64138
  `Failed to branch: ${error instanceof Error ? error.message : String(error)}`
@@ -63393,10 +64160,10 @@ async function navigateToEntry(host, entryId) {
63393
64160
  host.sessionManager.save().catch((err) => {
63394
64161
  console.error("[Session] Save after navigation failed:", err);
63395
64162
  });
63396
- const path31 = host.sessionManager.getPathToEntry(entryId);
64163
+ const path33 = host.sessionManager.getPathToEntry(entryId);
63397
64164
  const entry = host.sessionManager.getEntry(entryId);
63398
64165
  const role = entry?.message?.role || "entry";
63399
- host.showStatus(`Switched to branch: ${role} (${path31.length} messages in path)`);
64166
+ host.showStatus(`Switched to branch: ${role} (${path33.length} messages in path)`);
63400
64167
  } catch (error) {
63401
64168
  host.showError(
63402
64169
  `Failed to navigate: ${error instanceof Error ? error.message : String(error)}`
@@ -63446,9 +64213,9 @@ async function handleForkCommand(host) {
63446
64213
  }
63447
64214
 
63448
64215
  // src/commands/interactive-skill-commands.ts
63449
- import * as fs22 from "fs";
63450
- import * as os6 from "os";
63451
- import * as path24 from "path";
64216
+ import * as fs23 from "fs";
64217
+ import * as os7 from "os";
64218
+ import * as path25 from "path";
63452
64219
 
63453
64220
  // src/commands/subagent-command.ts
63454
64221
  async function handleSubagentCommand(host, args) {
@@ -63633,7 +64400,7 @@ function expandSkillCommand(host, text) {
63633
64400
  const skill = host.skills.find((s) => s.name === skillName);
63634
64401
  if (!skill) return text;
63635
64402
  try {
63636
- const content = fs22.readFileSync(skill.filePath, "utf-8");
64403
+ const content = fs23.readFileSync(skill.filePath, "utf-8");
63637
64404
  const body = host.stripFrontmatter(content).trim();
63638
64405
  const skillBlock = `<skill name="${skill.name}" location="${skill.filePath}">
63639
64406
  References are relative to ${skill.baseDir}.
@@ -63699,17 +64466,17 @@ async function handleSkillsCreate(host, args) {
63699
64466
  if (!description) {
63700
64467
  description = `Skill for ${name}`;
63701
64468
  }
63702
- const skillsDir = path24.join(os6.homedir(), ".pie", "skills");
63703
- const skillDir = path24.join(skillsDir, name);
63704
- 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");
63705
64472
  console.error(`[SkillsCreate] Creating at: ${skillFile}`);
63706
- if (fs22.existsSync(skillDir)) {
64473
+ if (fs23.existsSync(skillDir)) {
63707
64474
  host.showError(`Skill "${name}" already exists at ${skillDir}`);
63708
64475
  return;
63709
64476
  }
63710
- fs22.mkdirSync(skillDir, { recursive: true });
64477
+ fs23.mkdirSync(skillDir, { recursive: true });
63711
64478
  const template = createSkillTemplate(name, description);
63712
- fs22.writeFileSync(skillFile, template, "utf-8");
64479
+ fs23.writeFileSync(skillFile, template, "utf-8");
63713
64480
  console.error(`[SkillsCreate] Successfully created skill`);
63714
64481
  host.chatContainer.addChild(new Spacer(1));
63715
64482
  host.chatContainer.addChild(new Text(theme.fg("accent", `\u2713 Created skill: ${name}`), 1, 0));
@@ -63745,10 +64512,10 @@ async function handleSkillsReload(host) {
63745
64512
  host.chatContainer.addChild(new Text(theme.fg("accent", "Reloading skills..."), 1, 0));
63746
64513
  host.ui.requestRender();
63747
64514
  try {
63748
- const builtinDir = path24.join(getBuiltinDir(), "skills");
63749
- const userDir = path24.join(os6.homedir(), ".pie", "skills");
63750
- const projectDir = path24.join(process.cwd(), ".pie", "skills");
63751
- 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() }) });
63752
64519
  host.skills = result.skills;
63753
64520
  host.skillsSection = formatSkillsForPrompt(host.skills);
63754
64521
  host.applyRuntimeToolMode(host.allTools.length > 0 ? host.allTools : host.agent.state.tools);
@@ -63763,8 +64530,8 @@ async function handleSkillsReload(host) {
63763
64530
  }
63764
64531
  }
63765
64532
  const builtinCount = host.skills.filter((s) => s.source === "builtin").length;
63766
- const userCount = host.skills.filter((s) => s.source === "user").length;
63767
- 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;
63768
64535
  host.chatContainer.addChild(new Text(theme.fg("accent", `\u2713 Reloaded ${host.skills.length} skill(s)`), 1, 0));
63769
64536
  host.chatContainer.addChild(new Text(theme.fg("dim", ` Builtin: ${builtinCount}, User: ${userCount}, Project: ${projectCount}`), 1, 0));
63770
64537
  if (result.diagnostics.length > 0) {
@@ -64135,6 +64902,14 @@ function showThinkingLevelSelector(host) {
64135
64902
  });
64136
64903
  }
64137
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
+ }
64138
64913
  host.setControllerBusy(false);
64139
64914
  host.setWaitingForUser(false);
64140
64915
  host.executionState.clear();
@@ -64433,7 +65208,21 @@ async function handleCompactCommand(host) {
64433
65208
  apiKey: host.options.apiKey,
64434
65209
  systemPrompt: host.agent.state.systemPrompt
64435
65210
  };
64436
- 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
+ }
64437
65226
  const tokensBefore = calculateTotalTokens(existingMessages);
64438
65227
  host.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore);
64439
65228
  const compactedMessages = host.sessionManager.getMessages();
@@ -64496,6 +65285,31 @@ function formatFooterLifecycleEvent(event) {
64496
65285
  }
64497
65286
  }
64498
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
+
64499
65313
  // src/runtime/interactive-runtime-events.ts
64500
65314
  function bindInteractiveRuntimeEvents(host) {
64501
65315
  host.agent.subscribe((event) => {
@@ -64666,6 +65480,7 @@ function handleSemanticAgentEvent(host, event) {
64666
65480
  host.appendWarningSummary(outcome.terminalSummary);
64667
65481
  }
64668
65482
  host.sessionTrace.completeTurn(event.message, false);
65483
+ ringTurnSettledBell(host);
64669
65484
  host._semanticTurnIndex++;
64670
65485
  break;
64671
65486
  }
@@ -64700,6 +65515,7 @@ function handleSemanticAgentEvent(host, event) {
64700
65515
  host.appendWarningSummary(outcome.terminalSummary);
64701
65516
  }
64702
65517
  host.sessionTrace.completeTurn(event.message, true);
65518
+ host.ringAttentionBell?.();
64703
65519
  host._semanticTurnIndex++;
64704
65520
  break;
64705
65521
  }
@@ -64748,6 +65564,7 @@ function handleMessageStart(host, event) {
64748
65564
  host.safeRequestRender();
64749
65565
  }
64750
65566
  function handleMessageUpdate(host, event) {
65567
+ host.noteContinuationActivity?.();
64751
65568
  if (!host.streamingComponent || event.message?.role !== "assistant") return;
64752
65569
  host.sessionTrace.updateAssistantMessage(event.message);
64753
65570
  if (host.isComposing) {
@@ -64784,7 +65601,7 @@ function handleMessageUpdate(host, event) {
64784
65601
  const toolComponent = new ToolExecutionComponent(
64785
65602
  toolCall.name,
64786
65603
  toolCall.arguments,
64787
- { expanded: host.toolOutputExpanded },
65604
+ { expanded: host.toolOutputExpanded, renderHint: findToolRenderHint(host, toolCall.name) },
64788
65605
  host.ui
64789
65606
  );
64790
65607
  host.pendingTools.set(toolId, toolComponent);
@@ -64820,6 +65637,15 @@ function handleMessageUpdate(host, event) {
64820
65637
  });
64821
65638
  host.safeRequestRender();
64822
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
+ }
64823
65649
  function handleToolStart(host, event) {
64824
65650
  host.logDebug("tool:start", { toolName: event.toolName, toolCallId: event.toolCallId });
64825
65651
  host.sessionTrace.startTool(event.toolCallId, event.toolName, event.args);
@@ -64831,7 +65657,7 @@ function handleToolStart(host, event) {
64831
65657
  const newToolComponent = new ToolExecutionComponent(
64832
65658
  event.toolName,
64833
65659
  event.args,
64834
- { expanded: host.toolOutputExpanded },
65660
+ { expanded: host.toolOutputExpanded, renderHint: findToolRenderHint(host, event.toolName) },
64835
65661
  host.ui
64836
65662
  );
64837
65663
  host.pendingTools.set(event.toolCallId, newToolComponent);
@@ -64848,6 +65674,7 @@ function handleToolStart(host, event) {
64848
65674
  host.safeRequestRender();
64849
65675
  }
64850
65676
  function handleToolUpdate(host, event) {
65677
+ host.noteContinuationActivity?.();
64851
65678
  if (event.toolCallId) {
64852
65679
  const toolComponent = host.pendingTools.get(event.toolCallId);
64853
65680
  if (toolComponent) {
@@ -64924,6 +65751,20 @@ function setupInteractiveEditorSubmitHandler(host) {
64924
65751
  host.editor.onSubmit = async (text) => {
64925
65752
  text = text.trim();
64926
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
+ }
64927
65768
  if (text.startsWith("/skills/skill:")) {
64928
65769
  const skillText = text.slice("/skills/".length);
64929
65770
  const expanded = host.expandSkillCommand(skillText);
@@ -65186,7 +66027,7 @@ function setupInteractiveEditorSubmitHandler(host) {
65186
66027
  host.chatContainer.addChild(userMsg);
65187
66028
  host.editor.setText("");
65188
66029
  host.ui.requestRender();
65189
- await new Promise((resolve6) => setImmediate(resolve6));
66030
+ await new Promise((resolve7) => setImmediate(resolve7));
65190
66031
  let finalContent = messageContent;
65191
66032
  const reminderPrefix = host.executionReminder;
65192
66033
  const combinedPrefix = [reminderPrefix].filter((value) => Boolean(value)).join("");
@@ -65222,8 +66063,8 @@ function setupInteractiveEditorSubmitHandler(host) {
65222
66063
  }
65223
66064
 
65224
66065
  // src/input/interactive-file-pipeline.ts
65225
- import * as fs25 from "fs";
65226
- import * as path27 from "path";
66066
+ import * as fs26 from "fs";
66067
+ import * as path28 from "path";
65227
66068
 
65228
66069
  // src/components/file-picker.ts
65229
66070
  var FilePickerComponent = class extends Container {
@@ -65444,8 +66285,8 @@ var FilePickerComponent = class extends Container {
65444
66285
  };
65445
66286
 
65446
66287
  // src/files/commands/file-commands.ts
65447
- import * as fs23 from "node:fs";
65448
- import * as path25 from "node:path";
66288
+ import * as fs24 from "node:fs";
66289
+ import * as path26 from "node:path";
65449
66290
  var FileCommands = class {
65450
66291
  context;
65451
66292
  constructor(context) {
@@ -65461,11 +66302,11 @@ var FileCommands = class {
65461
66302
  return;
65462
66303
  }
65463
66304
  const filePath = resolvePath2(parsed.path);
65464
- if (!fs23.existsSync(filePath)) {
66305
+ if (!fs24.existsSync(filePath)) {
65465
66306
  this.context.showError(`File not found: ${parsed.path}`);
65466
66307
  return;
65467
66308
  }
65468
- this.context.showStatus(`Uploading ${path25.basename(filePath)}...`);
66309
+ this.context.showStatus(`Uploading ${path26.basename(filePath)}...`);
65469
66310
  try {
65470
66311
  const result = await this.context.fileManager.upload(filePath, {
65471
66312
  purpose: parsed.purpose,
@@ -65500,7 +66341,7 @@ var FileCommands = class {
65500
66341
  return;
65501
66342
  }
65502
66343
  const resolvedPath = resolvePath2(filePath);
65503
- if (!fs23.existsSync(resolvedPath)) {
66344
+ if (!fs24.existsSync(resolvedPath)) {
65504
66345
  this.context.showError(`File not found: ${filePath}`);
65505
66346
  return;
65506
66347
  }
@@ -65531,7 +66372,7 @@ var FileCommands = class {
65531
66372
  return;
65532
66373
  }
65533
66374
  const resolvedPath = resolvePath2(parsed.path);
65534
- if (!fs23.existsSync(resolvedPath)) {
66375
+ if (!fs24.existsSync(resolvedPath)) {
65535
66376
  this.context.showError(`File not found: ${parsed.path}`);
65536
66377
  return;
65537
66378
  }
@@ -66009,8 +66850,8 @@ var FileStatusMonitor = class {
66009
66850
  };
66010
66851
 
66011
66852
  // src/files/mention/mention-handler.ts
66012
- import * as fs24 from "node:fs";
66013
- import * as path26 from "node:path";
66853
+ import * as fs25 from "node:fs";
66854
+ import * as path27 from "node:path";
66014
66855
  var MentionHandler = class {
66015
66856
  fileManager;
66016
66857
  onShowPicker;
@@ -66065,7 +66906,7 @@ var MentionHandler = class {
66065
66906
  navigateBrowseParent() {
66066
66907
  const currentDir = this.getBrowserDirectory();
66067
66908
  if (!currentDir) return;
66068
- const parentDir = path26.dirname(currentDir);
66909
+ const parentDir = path27.dirname(currentDir);
66069
66910
  if (parentDir === currentDir) return;
66070
66911
  this.browsingDirectory = parentDir;
66071
66912
  this.showMentionPicker("");
@@ -66085,7 +66926,7 @@ var MentionHandler = class {
66085
66926
  if (option.type === "parent") {
66086
66927
  const currentDir = this.getBrowserDirectory();
66087
66928
  if (currentDir) {
66088
- this.browsingDirectory = path26.dirname(currentDir);
66929
+ this.browsingDirectory = path27.dirname(currentDir);
66089
66930
  this.showMentionPicker("");
66090
66931
  this.isPickerVisible = true;
66091
66932
  }
@@ -66221,7 +67062,7 @@ var MentionHandler = class {
66221
67062
  id: part.content,
66222
67063
  name: part.name || fileInfo.name,
66223
67064
  mimeType: fileInfo.mimeType,
66224
- ext: path26.extname(part.name || fileInfo.name).toLowerCase(),
67065
+ ext: path27.extname(part.name || fileInfo.name).toLowerCase(),
66225
67066
  size: fileInfo.size,
66226
67067
  source: fileInfo.platforms.length > 0 ? "session" : "local",
66227
67068
  fileId: part.content,
@@ -66379,11 +67220,11 @@ var MentionHandler = class {
66379
67220
  }
66380
67221
  if (this.isPath(query)) {
66381
67222
  const resolvedPath = resolvePath2(query);
66382
- if (fs24.existsSync(resolvedPath)) {
66383
- const stats = fs24.statSync(resolvedPath);
67223
+ if (fs25.existsSync(resolvedPath)) {
67224
+ const stats = fs25.statSync(resolvedPath);
66384
67225
  if (stats.isFile()) {
66385
67226
  const type = FileCache.getFileTypeFromName(resolvedPath);
66386
- const name = path26.basename(resolvedPath);
67227
+ const name = path27.basename(resolvedPath);
66387
67228
  if (!seenNames.has(name.toLowerCase())) {
66388
67229
  options.push({
66389
67230
  type: "upload",
@@ -66404,16 +67245,16 @@ var MentionHandler = class {
66404
67245
  return void 0;
66405
67246
  }
66406
67247
  const queryPath = this.isPath(query) ? resolvePath2(query) : void 0;
66407
- 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;
66408
67249
  const entries = this.fileManager.listPathEntries(currentDir, filter);
66409
67250
  const options = [];
66410
67251
  const homeDir = process.env.HOME || "";
66411
67252
  const displayDir = currentDir.startsWith(homeDir) ? `~${currentDir.slice(homeDir.length)}` : currentDir;
66412
- if (path26.dirname(currentDir) !== currentDir) {
67253
+ if (path27.dirname(currentDir) !== currentDir) {
66413
67254
  options.push({
66414
67255
  type: "parent",
66415
67256
  name: "..",
66416
- path: path26.dirname(currentDir),
67257
+ path: path27.dirname(currentDir),
66417
67258
  icon: "\u21A9",
66418
67259
  meta: "Parent directory"
66419
67260
  });
@@ -66445,10 +67286,10 @@ var MentionHandler = class {
66445
67286
  getBrowserDirectory(query = this.currentQuery) {
66446
67287
  if (this.isPath(query)) {
66447
67288
  const resolvedPath = resolvePath2(query);
66448
- if (fs24.existsSync(resolvedPath) && fs24.statSync(resolvedPath).isDirectory()) {
67289
+ if (fs25.existsSync(resolvedPath) && fs25.statSync(resolvedPath).isDirectory()) {
66449
67290
  return resolvedPath;
66450
67291
  }
66451
- return path26.dirname(resolvedPath);
67292
+ return path27.dirname(resolvedPath);
66452
67293
  }
66453
67294
  return this.browsingDirectory;
66454
67295
  }
@@ -66535,8 +67376,8 @@ async function initializeFileManager(host) {
66535
67376
  sessionId,
66536
67377
  platform: platform2,
66537
67378
  apiKey: host.options.apiKey,
66538
- baseDir: path27.join(host.options.configDir, "files"),
66539
- sessionsDir: path27.join(host.options.configDir, "sessions")
67379
+ baseDir: path28.join(host.options.configDir, "files"),
67380
+ sessionsDir: path28.join(host.options.configDir, "sessions")
66540
67381
  });
66541
67382
  switch (getFileManagerAdapterKind(host.options.model?.filePlatform)) {
66542
67383
  case "kimi":
@@ -66809,13 +67650,13 @@ function convertPreparedAttachmentItems(items) {
66809
67650
  break;
66810
67651
  case "image": {
66811
67652
  if (!item.data && !item.path) return void 0;
66812
- const data = item.data ?? fs25.readFileSync(item.path, "base64");
67653
+ const data = item.data ?? fs26.readFileSync(item.path, "base64");
66813
67654
  converted.push({ type: "image", data, mimeType: item.mimeType });
66814
67655
  break;
66815
67656
  }
66816
67657
  case "video": {
66817
67658
  if (!item.data && !item.path) return void 0;
66818
- const data = item.data ?? fs25.readFileSync(item.path, "base64");
67659
+ const data = item.data ?? fs26.readFileSync(item.path, "base64");
66819
67660
  converted.push({ type: "video", data, mimeType: item.mimeType });
66820
67661
  break;
66821
67662
  }
@@ -67036,6 +67877,55 @@ function isCommandLikeInput(text) {
67036
67877
  return text.startsWith("/") || text.startsWith("!!");
67037
67878
  }
67038
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
+
67039
67929
  // src/components/footer.ts
67040
67930
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
67041
67931
  var SPINNER_INTERVAL = 80;
@@ -67056,6 +67946,8 @@ var FooterComponent = class {
67056
67946
  spinnerFrame = 0;
67057
67947
  spinnerTimer = null;
67058
67948
  onRequestRender;
67949
+ /** Keyed ambient status slots for extensions (ctx.ui.setStatus). */
67950
+ extensionStatuses = new ExtensionStatusSlots();
67059
67951
  /** Set callback to request re-render when animation updates (pass true to force) */
67060
67952
  setOnRequestRender(callback) {
67061
67953
  this.onRequestRender = () => callback(true);
@@ -67168,6 +68060,7 @@ var FooterComponent = class {
67168
68060
  const statusWithSpinner = spinner ? `${spinner} ${statusText}` : statusText;
67169
68061
  statsParts.push(theme.fg(statusColor[state.status], statusWithSpinner));
67170
68062
  let statsLeft = statsParts.join(" ");
68063
+ statsLeft += this.extensionStatuses.format();
67171
68064
  const modelName = state.modelName || "no-model";
67172
68065
  let rightSide = modelName;
67173
68066
  if (state.thinkingLevel && state.thinkingLevel !== "off") {
@@ -67223,9 +68116,11 @@ var TodoWidgetComponent = class extends Container {
67223
68116
 
67224
68117
  // src/agent-guidelines.ts
67225
68118
  function buildCodingAgentGuidelinesSection(tools = []) {
67226
- void tools;
68119
+ const hasTodoTool = tools.some((tool) => tool.name === "manage_todo_list");
67227
68120
  const guidelines = [
67228
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."] : [],
67229
68124
  "Work in the current workspace, preserve user changes, and keep edits focused.",
67230
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.",
67231
68126
  "Run the most relevant existing test, build, or check after file changes when practical.",
@@ -67503,6 +68398,9 @@ function setupUI(host) {
67503
68398
  host.todoWidgetContainer = new Container();
67504
68399
  host.todoWidget = new TodoWidgetComponent(host.executionState);
67505
68400
  host.todoWidgetContainer.addChild(host.todoWidget);
68401
+ host.extensionWidgetsContainer = new Container();
68402
+ host.extensionWidgets = new ExtensionWidgetsComponent();
68403
+ host.extensionWidgetsContainer.addChild(host.extensionWidgets);
67506
68404
  const editorTheme = getEditorTheme();
67507
68405
  host.editor = new Editor(host.ui, editorTheme, { paddingX: 1 });
67508
68406
  host.editor.onChange = () => {
@@ -67517,6 +68415,7 @@ function setupUI(host) {
67517
68415
  host.ui.addChild(host.chatContainer);
67518
68416
  host.ui.addChild(host.pendingMessagesContainer);
67519
68417
  host.ui.addChild(host.todoWidgetContainer);
68418
+ host.ui.addChild(host.extensionWidgetsContainer);
67520
68419
  host.ui.addChild(new Spacer());
67521
68420
  host.ui.addChild(host.footer);
67522
68421
  host.ui.addChild(host.editorContainer);
@@ -67846,11 +68745,11 @@ var HierarchicalSelectorComponent = class extends Container {
67846
68745
  // src/runtime/interactive-ui-helpers.ts
67847
68746
  function showHierarchicalMenu(host, initialPath = []) {
67848
68747
  const basePath = [...initialPath];
67849
- const showAtPath = (path31) => {
68748
+ const showAtPath = (path33) => {
67850
68749
  host.showSelector((done) => {
67851
68750
  const selector = new HierarchicalSelectorComponent({
67852
68751
  tree: commandTree,
67853
- path: path31,
68752
+ path: path33,
67854
68753
  basePath,
67855
68754
  onNavigate: (newPath) => {
67856
68755
  done();
@@ -67858,7 +68757,7 @@ function showHierarchicalMenu(host, initialPath = []) {
67858
68757
  },
67859
68758
  onSelect: async (selectedPath, node, searchArgs) => {
67860
68759
  if (node.handler) {
67861
- 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);
67862
68761
  const commandPath = "/" + selectedPath.join("/");
67863
68762
  if (!args && host.isInteractiveCommand(commandPath)) {
67864
68763
  done();
@@ -67952,10 +68851,17 @@ function getContinuationTimeoutMs() {
67952
68851
  if (Number.isFinite(raw) && raw > 0) return raw;
67953
68852
  return 2e4;
67954
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
+ }
67955
68860
  function clearContinuationTimeout(host, reason) {
67956
68861
  if (!host.continuationTimeoutHandle) return;
67957
68862
  clearTimeout(host.continuationTimeoutHandle);
67958
68863
  host.continuationTimeoutHandle = void 0;
68864
+ host.continuationStallSinceMs = void 0;
67959
68865
  host.logDebug("continuation_timeout:cleared", {
67960
68866
  reason,
67961
68867
  awaitingContinuation: host.executionState.read().awaitingContinuation
@@ -67988,6 +68894,36 @@ function appendWarningSummary(host, message) {
67988
68894
  host.chatContainer.addChild(new Spacer(1));
67989
68895
  host.safeRequestRender();
67990
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
+ }
67991
68927
  function scheduleContinuationTimeout(host) {
67992
68928
  host.clearContinuationTimeout("reschedule");
67993
68929
  const timeoutMs = getContinuationTimeoutMs();
@@ -67996,23 +68932,38 @@ function scheduleContinuationTimeout(host) {
67996
68932
  currentStepId: host.executionState.read().currentStepId,
67997
68933
  queueLength: host.agentSessionController?.pendingMessageCount ?? 0
67998
68934
  });
67999
- host.continuationTimeoutHandle = setTimeout(() => {
68935
+ const check = () => {
68000
68936
  host.continuationTimeoutHandle = void 0;
68001
68937
  const state = host.executionState.read();
68002
68938
  if (!state.awaitingContinuation || isWaitingForUserInteraction(host)) {
68939
+ host.continuationStallSinceMs = void 0;
68003
68940
  host.logDebug("continuation_timeout:skip", {
68004
68941
  reason: !state.awaitingContinuation ? "no_longer_awaiting" : "waiting_for_user_interaction"
68005
68942
  });
68006
68943
  return;
68007
68944
  }
68008
68945
  if (host.agent.state.isStreaming || host.isProcessing || host.submitLock || host.pendingTools.size > 0) {
68009
- host.logDebug("continuation_timeout:active_work", {
68010
- isStreaming: host.agent.state.isStreaming,
68011
- isProcessing: host.isProcessing,
68012
- pendingTools: host.pendingTools.size
68013
- });
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);
68014
68964
  return;
68015
68965
  }
68966
+ host.continuationStallSinceMs = void 0;
68016
68967
  const outcome = host.executionState.markAwaitingContinuationStalled();
68017
68968
  host.logDebug("continuation_timeout:expired", {
68018
68969
  currentStepId: state.currentStepId,
@@ -68028,7 +68979,8 @@ function scheduleContinuationTimeout(host) {
68028
68979
  }
68029
68980
  }
68030
68981
  host.agentSessionController?.requestQueueDispatch();
68031
- }, timeoutMs);
68982
+ };
68983
+ host.continuationTimeoutHandle = setTimeout(check, timeoutMs);
68032
68984
  }
68033
68985
 
68034
68986
  // src/runtime/interactive-controller-setup.ts
@@ -68094,12 +69046,29 @@ function createInteractiveAgentSessionController(host) {
68094
69046
  baseDelayMs: getCliRetryBaseDelayMs()
68095
69047
  },
68096
69048
  compaction: {
68097
- compact: async (context) => createCliAutoCompactionResult({
68098
- context,
68099
- model: host.options.model,
68100
- apiKey: host.options.apiKey,
68101
- systemPrompt: host.agent.state.systemPrompt
68102
- })
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
+ }
68103
69072
  },
68104
69073
  onEvent: (event) => {
68105
69074
  host.sessionTrace.noteRuntimeEvent(event.type, event);
@@ -68168,6 +69137,7 @@ function createInteractiveAgentSessionController(host) {
68168
69137
  host.appendWarningSummary(outcome.terminalSummary);
68169
69138
  }
68170
69139
  host.sessionTrace.completeTurn(event.message, false);
69140
+ ringTurnSettledBell(host);
68171
69141
  host._semanticTurnIndex++;
68172
69142
  } else if (event.type === "turn_settled") {
68173
69143
  host.setControllerBusy(false);
@@ -68205,6 +69175,7 @@ function createInteractiveAgentSessionController(host) {
68205
69175
  host.showStatus("Todo list preserved. Send 'continue' when you want to resume it.");
68206
69176
  }
68207
69177
  host.showError(event.failure.message);
69178
+ host.ringAttentionBell?.();
68208
69179
  host.setControllerBusy(false);
68209
69180
  } else if (event.type === "turn_cancelled") {
68210
69181
  const outcome = host.executionState.abort();
@@ -68533,7 +69504,7 @@ async function handleDirectBash(host, command) {
68533
69504
  child.stderr.on("data", (data) => {
68534
69505
  output.append(data.toString());
68535
69506
  });
68536
- return new Promise((resolve6) => {
69507
+ return new Promise((resolve7) => {
68537
69508
  let settled = false;
68538
69509
  const finish = (code) => {
68539
69510
  if (settled) return;
@@ -68546,7 +69517,7 @@ async function handleDirectBash(host, command) {
68546
69517
  }
68547
69518
  host.chatContainer.addChild(new Spacer(1));
68548
69519
  host.ui.requestRender();
68549
- resolve6();
69520
+ resolve7();
68550
69521
  };
68551
69522
  child.on("error", (error) => {
68552
69523
  output.append(error.message);
@@ -68566,7 +69537,15 @@ function createInteractiveExtensionUIContext(host) {
68566
69537
  confirm: (title, message, opts) => showExtensionConfirm(host, title, message, opts),
68567
69538
  input: (title, placeholder, opts) => showExtensionInput(host, title, placeholder, opts),
68568
69539
  editor: (title, prefill) => showExtensionEditor(host, title, prefill),
68569
- 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
+ }
68570
69549
  };
68571
69550
  }
68572
69551
  function installInteractiveInteractionHandler(host) {
@@ -68575,6 +69554,7 @@ function installInteractiveInteractionHandler(host) {
68575
69554
  host.activeInteractionCount++;
68576
69555
  host.setWaitingForUser(true);
68577
69556
  host.footer.setState({ lifecycleEvent: "waiting_for_user_input" });
69557
+ host.ringAttentionBell?.();
68578
69558
  host.safeRequestRender();
68579
69559
  };
68580
69560
  const endWaitingForUser = () => {
@@ -68585,15 +69565,15 @@ function installInteractiveInteractionHandler(host) {
68585
69565
  host.footer.setState({ lifecycleEvent: "tool_execution_start" });
68586
69566
  host.safeRequestRender();
68587
69567
  };
68588
- const withTimeout = async (promise) => {
69568
+ const withTimeout2 = async (promise) => {
68589
69569
  if (!("timeoutMs" in request) || !request.timeoutMs || request.timeoutMs <= 0) {
68590
69570
  return { value: await promise, timedOut: false };
68591
69571
  }
68592
- return await new Promise((resolve6, reject) => {
68593
- const timer = setTimeout(() => resolve6({ timedOut: true }), request.timeoutMs);
69572
+ return await new Promise((resolve7, reject) => {
69573
+ const timer = setTimeout(() => resolve7({ timedOut: true }), request.timeoutMs);
68594
69574
  promise.then((value) => {
68595
69575
  clearTimeout(timer);
68596
- resolve6({ value, timedOut: false });
69576
+ resolve7({ value, timedOut: false });
68597
69577
  }).catch((error) => {
68598
69578
  clearTimeout(timer);
68599
69579
  reject(error);
@@ -68603,7 +69583,7 @@ function installInteractiveInteractionHandler(host) {
68603
69583
  const waitForUser = async (createPromise) => {
68604
69584
  beginWaitingForUser();
68605
69585
  try {
68606
- return await withTimeout(createPromise());
69586
+ return await withTimeout2(createPromise());
68607
69587
  } finally {
68608
69588
  endWaitingForUser();
68609
69589
  }
@@ -68693,15 +69673,15 @@ function showExtensionNotification(host, message, type = "info") {
68693
69673
  host.ui.requestRender();
68694
69674
  }
68695
69675
  function showExtensionSelector2(host, title, options, opts) {
68696
- return new Promise((resolve6) => {
69676
+ return new Promise((resolve7) => {
68697
69677
  host.showStatus(`Waiting for selection: ${title}`);
68698
69678
  if (opts?.signal?.aborted) {
68699
- resolve6(void 0);
69679
+ resolve7(void 0);
68700
69680
  return;
68701
69681
  }
68702
69682
  const onAbort = () => {
68703
69683
  host.restoreEditor();
68704
- resolve6(void 0);
69684
+ resolve7(void 0);
68705
69685
  };
68706
69686
  opts?.signal?.addEventListener("abort", onAbort, { once: true });
68707
69687
  const selector = new ExtensionSelectorComponent(
@@ -68710,12 +69690,12 @@ function showExtensionSelector2(host, title, options, opts) {
68710
69690
  (option) => {
68711
69691
  opts?.signal?.removeEventListener("abort", onAbort);
68712
69692
  host.restoreEditor();
68713
- resolve6(option);
69693
+ resolve7(option);
68714
69694
  },
68715
69695
  () => {
68716
69696
  opts?.signal?.removeEventListener("abort", onAbort);
68717
69697
  host.restoreEditor();
68718
- resolve6(void 0);
69698
+ resolve7(void 0);
68719
69699
  }
68720
69700
  );
68721
69701
  host.editorContainer.clear();
@@ -68731,15 +69711,15 @@ ${message}`, ["Yes", "No"], opts);
68731
69711
  return result === "Yes";
68732
69712
  }
68733
69713
  function showExtensionInput(host, title, placeholder, opts) {
68734
- return new Promise((resolve6) => {
69714
+ return new Promise((resolve7) => {
68735
69715
  host.showStatus(`Waiting for input: ${title}`);
68736
69716
  if (opts?.signal?.aborted) {
68737
- resolve6(void 0);
69717
+ resolve7(void 0);
68738
69718
  return;
68739
69719
  }
68740
69720
  const onAbort = () => {
68741
69721
  host.restoreEditor();
68742
- resolve6(void 0);
69722
+ resolve7(void 0);
68743
69723
  };
68744
69724
  opts?.signal?.addEventListener("abort", onAbort, { once: true });
68745
69725
  const input = new ExtensionInputComponent(
@@ -68748,12 +69728,12 @@ function showExtensionInput(host, title, placeholder, opts) {
68748
69728
  (value) => {
68749
69729
  opts?.signal?.removeEventListener("abort", onAbort);
68750
69730
  host.restoreEditor();
68751
- resolve6(value);
69731
+ resolve7(value);
68752
69732
  },
68753
69733
  () => {
68754
69734
  opts?.signal?.removeEventListener("abort", onAbort);
68755
69735
  host.restoreEditor();
68756
- resolve6(void 0);
69736
+ resolve7(void 0);
68757
69737
  }
68758
69738
  );
68759
69739
  host.editorContainer.clear();
@@ -68764,7 +69744,7 @@ function showExtensionInput(host, title, placeholder, opts) {
68764
69744
  });
68765
69745
  }
68766
69746
  function showExtensionEditor(host, title, prefill) {
68767
- return new Promise((resolve6) => {
69747
+ return new Promise((resolve7) => {
68768
69748
  host.showStatus(`Waiting for input: ${title}`);
68769
69749
  const container = new Container();
68770
69750
  container.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
@@ -68786,12 +69766,12 @@ function showExtensionEditor(host, title, prefill) {
68786
69766
  preview: value.slice(0, 120)
68787
69767
  });
68788
69768
  host.restoreEditor();
68789
- resolve6(value);
69769
+ resolve7(value);
68790
69770
  };
68791
69771
  editor.onEscape = () => {
68792
69772
  host.logDebug("extension:editor:cancel", { title });
68793
69773
  host.restoreEditor();
68794
- resolve6(void 0);
69774
+ resolve7(void 0);
68795
69775
  };
68796
69776
  container.addChild(editor);
68797
69777
  container.addChild(new Spacer(1));
@@ -68805,14 +69785,14 @@ function showExtensionEditor(host, title, prefill) {
68805
69785
  }
68806
69786
  function showExtensionCustom(host, factory, options) {
68807
69787
  const savedText = host.editor.getText();
68808
- return new Promise((resolve6, reject) => {
69788
+ return new Promise((resolve7, reject) => {
68809
69789
  let closed = false;
68810
69790
  const close = (result) => {
68811
69791
  if (closed) return;
68812
69792
  closed = true;
68813
69793
  host.restoreEditor();
68814
69794
  host.editor.setText(savedText);
68815
- resolve6(result);
69795
+ resolve7(result);
68816
69796
  };
68817
69797
  Promise.resolve(factory(host.ui, theme, close)).then((component) => {
68818
69798
  if (closed) return;
@@ -68833,6 +69813,60 @@ function showExtensionCustom(host, factory, options) {
68833
69813
  });
68834
69814
  }
68835
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
+
68836
69870
  // src/attachments/resolver.ts
68837
69871
  var AttachmentResolver = class {
68838
69872
  constructor(extensions) {
@@ -68996,6 +70030,26 @@ async function loadInteractiveExtensions(host) {
68996
70030
  activeSession.metadata[key] = value;
68997
70031
  activeSession.isDirty = true;
68998
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(),
68999
70053
  get yoloMode() {
69000
70054
  return host.yoloMode;
69001
70055
  }
@@ -69023,9 +70077,10 @@ async function loadInteractiveExtensions(host) {
69023
70077
  true
69024
70078
  );
69025
70079
  host.extensionRunner.setUIContext(host.extensionUIContext);
70080
+ await restoreExtensionProviderCredentials(host.options.modelRegistry);
69026
70081
  if (process.env.PIE_VERBOSE) {
69027
- for (const { path: path31, error } of errors) {
69028
- 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}`);
69029
70084
  }
69030
70085
  }
69031
70086
  const extensionTools = convertExtensionTools(extensions);
@@ -69079,6 +70134,8 @@ var InteractiveMode = class {
69079
70134
  editorContainer;
69080
70135
  todoWidgetContainer;
69081
70136
  todoWidget;
70137
+ extensionWidgetsContainer;
70138
+ extensionWidgets;
69082
70139
  executionState = new ExecutionStateManager();
69083
70140
  sessionTrace;
69084
70141
  keybindings;
@@ -69143,10 +70200,24 @@ var InteractiveMode = class {
69143
70200
  lastStatusText;
69144
70201
  lastSigintTime = 0;
69145
70202
  continuationTimeoutHandle;
70203
+ continuationStallSinceMs;
69146
70204
  activeInteractionCount = 0;
69147
70205
  logDebug(message, data) {
69148
70206
  fileLogger.log("interactive", message, data);
69149
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
+ }
69150
70221
  getContinuationTimeoutMs() {
69151
70222
  return getContinuationTimeoutMs();
69152
70223
  }
@@ -69389,7 +70460,7 @@ var InteractiveMode = class {
69389
70460
  if (savedLevel) {
69390
70461
  this.thinkingLevel = savedLevel;
69391
70462
  }
69392
- const { Agent } = await import("./chunks/src-NHRMBQAJ.js");
70463
+ const { Agent } = await import("./chunks/src-TQNFXZGK.js");
69393
70464
  const activeSession = this.sessionManager.getActiveSession();
69394
70465
  this.agent = new Agent({
69395
70466
  initialState: {
@@ -69480,6 +70551,40 @@ var InteractiveMode = class {
69480
70551
  installInteractiveInteractionHandler(this);
69481
70552
  await loadInteractiveExtensions(this.extensionLoaderHost);
69482
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
+ }
69483
70588
  calculateContextUsage() {
69484
70589
  return calculateContextUsage(this.contextUiHost);
69485
70590
  }
@@ -69728,8 +70833,8 @@ var InteractiveMode = class {
69728
70833
  };
69729
70834
 
69730
70835
  // src/app/runtime-composition.ts
69731
- import { existsSync as existsSync17, mkdirSync as mkdirSync12 } from "fs";
69732
- import * as path28 from "path";
70836
+ import { existsSync as existsSync18, mkdirSync as mkdirSync13 } from "fs";
70837
+ import * as path30 from "path";
69733
70838
  var CliMissingApiKeyError = class extends Error {
69734
70839
  constructor(message) {
69735
70840
  super(message);
@@ -69750,13 +70855,13 @@ var CliSessionResumeError = class extends Error {
69750
70855
  function getCliRuntimePaths() {
69751
70856
  const configDir = getConfigDir();
69752
70857
  const sessionsDir = getSessionsDir();
69753
- const skillsDir = path28.join(configDir, "skills");
69754
- const builtinSkillsDir = path28.join(getBuiltinDir(), "skills");
70858
+ const skillsDir = path30.join(configDir, "skills");
70859
+ const builtinSkillsDir = path30.join(getBuiltinDir(), "skills");
69755
70860
  return { configDir, sessionsDir, skillsDir, builtinSkillsDir };
69756
70861
  }
69757
70862
  function ensureCliRuntimePaths(paths = getCliRuntimePaths()) {
69758
- if (!existsSync17(paths.skillsDir)) {
69759
- mkdirSync12(paths.skillsDir, { recursive: true });
70863
+ if (!existsSync18(paths.skillsDir)) {
70864
+ mkdirSync13(paths.skillsDir, { recursive: true });
69760
70865
  }
69761
70866
  return paths;
69762
70867
  }
@@ -69782,7 +70887,7 @@ async function composeCliRuntime(params) {
69782
70887
  const paths = ensureCliRuntimePaths(params.paths);
69783
70888
  const compositionTrace = createCliCompositionTrace();
69784
70889
  const settingsManager = new SettingsManager();
69785
- const authStorage = new AuthStorage();
70890
+ const authStorage = AuthStorage.fromStorage(new FileAuthStorageBackend());
69786
70891
  authStorage.setLegacyApiKey(settingsManager.getLegacyApiKey());
69787
70892
  const modelRegistry = new ModelRegistry(authStorage);
69788
70893
  await modelRegistry.refreshRemoteCatalogs();
@@ -69868,11 +70973,14 @@ async function composeCliRuntime(params) {
69868
70973
  fileLogger.log("cli", `Using API key for provider ${provider}`, {
69869
70974
  keyPrefix: apiKey ? maskApiKey2(apiKey) : "none"
69870
70975
  });
69871
- const projectSkillsDir = path28.join(cwd, ".pie", "skills");
70976
+ const projectSkillsDir = path30.join(cwd, ".pie", "skills");
69872
70977
  const { skills, diagnostics } = loadSkills({
69873
- builtinDir: paths.builtinSkillsDir,
69874
- userDir: paths.skillsDir,
69875
- projectDir: projectSkillsDir
70978
+ dirs: buildSkillDirs({
70979
+ builtinDir: paths.builtinSkillsDir,
70980
+ userDir: paths.skillsDir,
70981
+ projectDir: projectSkillsDir,
70982
+ cwd
70983
+ })
69876
70984
  });
69877
70985
  let interactiveOptions;
69878
70986
  const getCurrentModel = () => interactiveOptions?.model ?? model;
@@ -70063,6 +71171,17 @@ async function loadPrintModeExtensions(params) {
70063
71171
  session.metadata[key] = value;
70064
71172
  session.isDirty = true;
70065
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
+ }),
70066
71185
  yoloMode: false
70067
71186
  };
70068
71187
  const { extensions } = await loadExtensions(
@@ -70085,6 +71204,7 @@ async function loadPrintModeExtensions(params) {
70085
71204
  false
70086
71205
  );
70087
71206
  runner.setUIContext(uiContext);
71207
+ await restoreExtensionProviderCredentials(params.modelRegistry);
70088
71208
  const extensionTools = convertExtensionTools(extensions);
70089
71209
  availableTools = [...availableTools, ...extensionTools];
70090
71210
  return {
@@ -70110,7 +71230,7 @@ async function runPrintMode(params) {
70110
71230
  `);
70111
71231
  console.log("Assistant: ");
70112
71232
  }
70113
- const { Agent: AgentClass } = await import("./chunks/src-NHRMBQAJ.js");
71233
+ const { Agent: AgentClass } = await import("./chunks/src-TQNFXZGK.js");
70114
71234
  let agent;
70115
71235
  const printModeExtensions = await loadPrintModeExtensions({
70116
71236
  cwd: runtime.cwd,
@@ -70126,6 +71246,8 @@ async function runPrintMode(params) {
70126
71246
  allTools: runtime.allTools
70127
71247
  });
70128
71248
  const printModeTools = [...runtime.tools, ...printModeExtensions.extensionTools];
71249
+ const restoredApiKey = runtime.modelRegistry.getApiKey(runtime.model);
71250
+ if (restoredApiKey) runtime.apiKey = restoredApiKey;
70129
71251
  recordCliCompositionStep(runtime.compositionTrace, {
70130
71252
  id: "extensions_tool_wrapping",
70131
71253
  inputSummary: `baseTools=${runtime.tools.length} extensionPaths=${runtime.extensionPaths.length}`,
@@ -70189,12 +71311,26 @@ async function runPrintMode(params) {
70189
71311
  isContextOverflowError: isCliContextOverflowError
70190
71312
  }),
70191
71313
  compaction: {
70192
- compact: async (context) => createCliAutoCompactionResult({
70193
- context,
70194
- model: runtime.model,
70195
- apiKey: runtime.apiKey,
70196
- systemPrompt: agent?.state.systemPrompt ?? baseSystemPrompt
70197
- })
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
+ }
70198
71334
  },
70199
71335
  classifyFailure: (context) => classifyCliFailure(context),
70200
71336
  onEvent: (event) => {
@@ -70516,6 +71652,7 @@ async function loadJsonModeExtensions(params) {
70516
71652
  false
70517
71653
  );
70518
71654
  runner.setUIContext(uiContext);
71655
+ await restoreExtensionProviderCredentials(params.runtime.modelRegistry);
70519
71656
  const extensionTools = convertExtensionTools(extensions);
70520
71657
  availableTools = [...availableTools, ...extensionTools];
70521
71658
  return {
@@ -70549,7 +71686,7 @@ async function runJsonEventsMode(params) {
70549
71686
  return typeof message.sessionId === "string" && message.sessionId !== sessionId;
70550
71687
  }
70551
71688
  function delay(ms) {
70552
- return new Promise((resolve6) => setTimeout(resolve6, Math.max(0, ms)));
71689
+ return new Promise((resolve7) => setTimeout(resolve7, Math.max(0, ms)));
70553
71690
  }
70554
71691
  async function waitForActiveController(timeoutMs = 1e3) {
70555
71692
  const deadline = Date.now() + timeoutMs;
@@ -71060,9 +72197,11 @@ async function runJsonEventsMode(params) {
71060
72197
  if (runtime.initialModel.unconfigured || controller) {
71061
72198
  return;
71062
72199
  }
71063
- const { Agent: AgentClass } = await import("./chunks/src-NHRMBQAJ.js");
72200
+ const { Agent: AgentClass } = await import("./chunks/src-TQNFXZGK.js");
71064
72201
  const jsonModeExtensions = await loadJsonModeExtensions({ runtime, getAgent: () => agent });
71065
72202
  const tools = [...runtime.tools, ...jsonModeExtensions.extensionTools];
72203
+ const restoredApiKey = runtime.modelRegistry.getApiKey(runtime.model);
72204
+ if (restoredApiKey) runtime.apiKey = restoredApiKey;
71066
72205
  recordCliCompositionStep(runtime.compositionTrace, {
71067
72206
  id: "extensions_tool_wrapping",
71068
72207
  inputSummary: `baseTools=${runtime.tools.length} extensionPaths=${runtime.extensionPaths.length}`,
@@ -71494,19 +72633,19 @@ async function runJsonEventsMode(params) {
71494
72633
  }
71495
72634
 
71496
72635
  // src/developer-instructions.ts
71497
- import * as fs26 from "fs";
71498
- import * as path29 from "path";
72636
+ import * as fs27 from "fs";
72637
+ import * as path31 from "path";
71499
72638
  var MAX_DEVELOPER_INSTRUCTIONS_CHARS = 64e3;
71500
72639
  function readTextFileBounded(filePath) {
71501
- const resolved = path29.resolve(filePath);
71502
- const stat2 = fs26.statSync(resolved);
72640
+ const resolved = path31.resolve(filePath);
72641
+ const stat2 = fs27.statSync(resolved);
71503
72642
  if (!stat2.isFile()) {
71504
72643
  throw new Error(`Developer instructions file is not a regular file: ${filePath}`);
71505
72644
  }
71506
72645
  if (stat2.size > MAX_DEVELOPER_INSTRUCTIONS_CHARS * 4) {
71507
72646
  throw new Error(`Developer instructions file is too large: ${filePath}`);
71508
72647
  }
71509
- return fs26.readFileSync(resolved, "utf8");
72648
+ return fs27.readFileSync(resolved, "utf8");
71510
72649
  }
71511
72650
  function collectFlagValues(args, flag) {
71512
72651
  const values = [];
@@ -71584,8 +72723,8 @@ var logError = (msg, err) => {
71584
72723
  const logPath = getRuntimeLogPath();
71585
72724
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
71586
72725
  const stack = err instanceof Error ? err.stack || "" : "";
71587
- fs27.mkdirSync(path30.dirname(logPath), { recursive: true });
71588
- fs27.appendFileSync(logPath, `[${timestamp}] ${msg}
72726
+ fs28.mkdirSync(path32.dirname(logPath), { recursive: true });
72727
+ fs28.appendFileSync(logPath, `[${timestamp}] ${msg}
71589
72728
  ${stack}
71590
72729
 
71591
72730
  `);
@@ -71692,7 +72831,7 @@ async function runWithJsonEventsConsoleIsolation(fn) {
71692
72831
  }
71693
72832
  async function createCliTestStreamFnFromEnv() {
71694
72833
  if (!process.env.PIE_TEST_MOCK_STREAM_SEQUENCE) return void 0;
71695
- return (await import("./chunks/test-stream-I6S23PTW.js")).createTestStreamFnFromSequenceEnv();
72834
+ return (await import("./chunks/test-stream-IPBFEEWH.js")).createTestStreamFnFromSequenceEnv();
71696
72835
  }
71697
72836
  async function startChat(initialPrompt, testCommand) {
71698
72837
  const rawOutput = process.argv.includes("--raw-output");
@@ -71787,9 +72926,9 @@ async function startChat(initialPrompt, testCommand) {
71787
72926
  flushEarlyLogs();
71788
72927
  if (testCommand) {
71789
72928
  console.log(`[TEST] Auto-executing command: ${testCommand}`);
71790
- await new Promise((resolve6) => setTimeout(resolve6, 500));
72929
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
71791
72930
  await interactive.executeCommand(testCommand);
71792
- await new Promise((resolve6) => setTimeout(resolve6, 2e3));
72931
+ await new Promise((resolve7) => setTimeout(resolve7, 2e3));
71793
72932
  console.log("[TEST] Command executed, exiting...");
71794
72933
  process.exit(0);
71795
72934
  return;