indusagi-coding-agent 0.2.13 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +19 -0
  2. package/dist/entry.js +1762 -958
  3. package/dist/guardrails.js +145 -35
  4. package/dist/index.js +1810 -1006
  5. package/dist/review-agent.js +344 -60
  6. package/dist/review-server.js +346 -62
  7. package/package.json +12 -3
  8. package/src/boot/auth-vault.ts +4 -5
  9. package/src/boot/runners/addon-wiring.ts +111 -3
  10. package/src/boot/runners/benchmark-integrity.ts +107 -0
  11. package/src/boot/runners/repl-runner.ts +2 -1
  12. package/src/boot/runners/session.ts +18 -8
  13. package/src/boot/server-token.ts +20 -11
  14. package/src/briefing/context-docs.ts +144 -12
  15. package/src/channels/oneshot.ts +6 -0
  16. package/src/conductor/conductor.ts +67 -12
  17. package/src/conductor/contract.ts +4 -3
  18. package/src/conductor/transcript-store/store.ts +4 -8
  19. package/src/console/auth-status.ts +9 -4
  20. package/src/console/components/AgentsView.ts +2 -2
  21. package/src/console/components/BackgroundAgents.ts +15 -2
  22. package/src/console/components/Banner.ts +20 -9
  23. package/src/console/components/Composer.ts +57 -82
  24. package/src/console/components/ScrollViewport.ts +46 -0
  25. package/src/console/components/TerminalConsole.ts +165 -162
  26. package/src/console/components/WorkingIndicator.ts +6 -5
  27. package/src/console/components/banner-sweep.ts +5 -1
  28. package/src/console/components/welcome.ts +3 -3
  29. package/src/console/contract.ts +10 -5
  30. package/src/console/index.ts +7 -7
  31. package/src/console/input/complete.ts +2 -2
  32. package/src/console/input/dir-reader.ts +3 -1
  33. package/src/console/input/graphemes.ts +22 -0
  34. package/src/console/input/keymap.ts +6 -4
  35. package/src/console/mount.ts +45 -3
  36. package/src/console/overlays/auth.ts +28 -8
  37. package/src/console/overlays/boards.ts +13 -40
  38. package/src/console/overlays/host.ts +10 -0
  39. package/src/console/overlays/pickers.ts +35 -30
  40. package/src/console/reducer.ts +12 -5
  41. package/src/console/slash/builtins.ts +4 -3
  42. package/src/console/slash/commands/integrations.ts +13 -14
  43. package/src/console/slash/commands/transcript.ts +3 -8
  44. package/src/console/slash/commands/workbench.ts +2 -2
  45. package/src/console/test-support.ts +48 -0
  46. package/src/integrations/sarvam/config.ts +15 -13
  47. package/src/kit/external-editor.ts +17 -1
  48. package/src/kit/run-shell.ts +31 -0
  49. package/src/launch/invocation/flags.ts +12 -0
  50. package/src/launch/login.ts +23 -10
  51. package/src/launch/oauth.ts +3 -0
  52. package/src/models.ts +78 -2
  53. package/src/security/private-state.ts +122 -0
  54. package/src/window-budget/summarize/condense.ts +4 -1
  55. package/src/workspace/brand.ts +1 -1
  56. package/src/workspace/locator.ts +2 -2
package/dist/entry.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/entry.ts
4
4
  import { argv as processArgv, env as processEnv } from "node:process";
5
- import { realpathSync as realpathSync3 } from "node:fs";
5
+ import { realpathSync as realpathSync4 } from "node:fs";
6
6
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7
7
 
8
8
  // src/workspace/brand.ts
@@ -18,7 +18,7 @@ var BRAND = Object.freeze({
18
18
  envShareViewer: "INDUSAGI_SHARE_VIEWER_URL",
19
19
  shareViewerUrl: "https://buildwithindusagi.ai/session/"
20
20
  });
21
- var VERSION = "0.2.12";
21
+ var VERSION = "0.2.14";
22
22
 
23
23
  // src/launch/credentials.ts
24
24
  import { createInterface as createInterface2 } from "node:readline/promises";
@@ -77,26 +77,153 @@ import {
77
77
 
78
78
  // src/launch/login.ts
79
79
  import { createInterface } from "node:readline/promises";
80
+ import { setTimeout as delay } from "node:timers/promises";
80
81
  import chalk from "chalk";
81
82
 
82
83
  // src/boot/server-token.ts
83
- import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
84
+ import { unlink } from "node:fs/promises";
84
85
  import os from "node:os";
85
86
  import path from "node:path";
87
+
88
+ // src/security/private-state.ts
89
+ import {
90
+ chmodSync,
91
+ closeSync,
92
+ constants,
93
+ fchmodSync,
94
+ fstatSync,
95
+ lstatSync,
96
+ mkdirSync,
97
+ openSync,
98
+ readFileSync,
99
+ renameSync,
100
+ rmSync,
101
+ writeFileSync
102
+ } from "node:fs";
103
+ import { randomUUID } from "node:crypto";
104
+ import { dirname } from "node:path";
105
+ var PRIVATE_DIRECTORY_MODE = 448;
106
+ var PRIVATE_FILE_MODE = 384;
107
+ var NO_FOLLOW = constants.O_NOFOLLOW ?? 0;
108
+ function isPosix() {
109
+ return process.platform !== "win32";
110
+ }
111
+ function assertOwned(uid, path5) {
112
+ if (!isPosix() || process.getuid === void 0) return;
113
+ if (uid !== process.getuid()) {
114
+ throw new Error(`Refusing sensitive path not owned by the current user: ${path5}`);
115
+ }
116
+ }
117
+ function ensurePrivateDirectorySync(path5) {
118
+ try {
119
+ const current = lstatSync(path5);
120
+ if (current.isSymbolicLink() || !current.isDirectory()) {
121
+ throw new Error(`Refusing non-directory sensitive state path: ${path5}`);
122
+ }
123
+ assertOwned(current.uid, path5);
124
+ } catch (error) {
125
+ if (error.code !== "ENOENT") throw error;
126
+ mkdirSync(path5, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
127
+ }
128
+ if (isPosix()) chmodSync(path5, PRIVATE_DIRECTORY_MODE);
129
+ }
130
+ function validateExistingFile(path5) {
131
+ try {
132
+ const current = lstatSync(path5);
133
+ if (current.isSymbolicLink() || !current.isFile()) {
134
+ throw new Error(`Refusing non-regular sensitive file: ${path5}`);
135
+ }
136
+ assertOwned(current.uid, path5);
137
+ } catch (error) {
138
+ if (error.code === "ENOENT") return;
139
+ throw error;
140
+ }
141
+ }
142
+ function readPrivateFileSync(path5) {
143
+ ensurePrivateDirectorySync(dirname(path5));
144
+ try {
145
+ validateExistingFile(path5);
146
+ const fd = openSync(path5, constants.O_RDONLY | NO_FOLLOW);
147
+ try {
148
+ const current = fstatSync(fd);
149
+ if (!current.isFile()) throw new Error(`Refusing non-regular sensitive file: ${path5}`);
150
+ assertOwned(current.uid, path5);
151
+ if (isPosix()) fchmodSync(fd, PRIVATE_FILE_MODE);
152
+ return readFileSync(fd, "utf8");
153
+ } finally {
154
+ closeSync(fd);
155
+ }
156
+ } catch (error) {
157
+ if (error.code === "ENOENT") return null;
158
+ throw error;
159
+ }
160
+ }
161
+ function atomicWritePrivateFileSync(path5, content) {
162
+ const parent = dirname(path5);
163
+ ensurePrivateDirectorySync(parent);
164
+ validateExistingFile(path5);
165
+ const temporary = `${path5}.tmp-${process.pid}-${randomUUID()}`;
166
+ let fd;
167
+ try {
168
+ fd = openSync(
169
+ temporary,
170
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NO_FOLLOW,
171
+ PRIVATE_FILE_MODE
172
+ );
173
+ writeFileSync(fd, content, "utf8");
174
+ if (isPosix()) fchmodSync(fd, PRIVATE_FILE_MODE);
175
+ closeSync(fd);
176
+ fd = void 0;
177
+ renameSync(temporary, path5);
178
+ if (isPosix()) chmodSync(path5, PRIVATE_FILE_MODE);
179
+ } catch (error) {
180
+ if (fd !== void 0) closeSync(fd);
181
+ try {
182
+ rmSync(temporary, { force: true });
183
+ } catch {
184
+ }
185
+ throw error;
186
+ }
187
+ }
188
+ function appendPrivateFileSync(path5, content) {
189
+ ensurePrivateDirectorySync(dirname(path5));
190
+ validateExistingFile(path5);
191
+ const fd = openSync(
192
+ path5,
193
+ constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | NO_FOLLOW,
194
+ PRIVATE_FILE_MODE
195
+ );
196
+ try {
197
+ const current = fstatSync(fd);
198
+ if (!current.isFile()) throw new Error(`Refusing non-regular sensitive file: ${path5}`);
199
+ assertOwned(current.uid, path5);
200
+ if (isPosix()) fchmodSync(fd, PRIVATE_FILE_MODE);
201
+ writeFileSync(fd, content, "utf8");
202
+ } finally {
203
+ closeSync(fd);
204
+ }
205
+ }
206
+
207
+ // src/boot/server-token.ts
86
208
  var INDUS_SERVER_URL = process.env.INDUS_SERVER_URL?.replace(/\/+$/, "") ?? "https://indus-server.vercel.app";
87
209
  var CONFIG_DIR = path.join(os.homedir(), ".indusagi", "agent");
88
210
  var TOKEN_FILE = path.join(CONFIG_DIR, "server-token.json");
89
- async function getServerToken() {
211
+ function resolveTokenPaths(paths = {}) {
212
+ const configDir = paths.configDir ?? CONFIG_DIR;
213
+ return { configDir, tokenFile: paths.tokenFile ?? path.join(configDir, "server-token.json") };
214
+ }
215
+ async function getServerToken(paths = {}) {
90
216
  try {
91
- const data = await readFile(TOKEN_FILE, "utf8");
217
+ const data = readPrivateFileSync(resolveTokenPaths(paths).tokenFile);
218
+ if (data === null) return null;
92
219
  return JSON.parse(data);
93
220
  } catch {
94
221
  return null;
95
222
  }
96
223
  }
97
- async function storeServerToken(token) {
224
+ async function storeServerToken(token, paths = {}) {
98
225
  try {
99
- await mkdir(CONFIG_DIR, { recursive: true });
226
+ const { tokenFile } = resolveTokenPaths(paths);
100
227
  const tokenData = {
101
228
  access_token: token.access_token,
102
229
  refresh_token: token.refresh_token,
@@ -105,20 +232,17 @@ async function storeServerToken(token) {
105
232
  expires_at: token.expires_in !== void 0 ? new Date(Date.now() + token.expires_in * 1e3).toISOString() : null,
106
233
  created_at: (/* @__PURE__ */ new Date()).toISOString()
107
234
  };
108
- await writeFile(TOKEN_FILE, `${JSON.stringify(tokenData, null, 2)}
109
- `, {
110
- encoding: "utf8",
111
- mode: 384
112
- });
235
+ atomicWritePrivateFileSync(tokenFile, `${JSON.stringify(tokenData, null, 2)}
236
+ `);
113
237
  return true;
114
238
  } catch (error) {
115
239
  console.error("Failed to store server token:", error.message);
116
240
  return false;
117
241
  }
118
242
  }
119
- async function clearServerToken() {
243
+ async function clearServerToken(paths = {}) {
120
244
  try {
121
- await unlink(TOKEN_FILE);
245
+ await unlink(resolveTokenPaths(paths).tokenFile);
122
246
  return true;
123
247
  } catch {
124
248
  return false;
@@ -155,10 +279,10 @@ function err(line) {
155
279
  process.stderr.write(`${line}
156
280
  `);
157
281
  }
158
- function sleep(ms) {
159
- return new Promise((resolve9) => setTimeout(resolve9, ms));
282
+ function sleep(ms, signal) {
283
+ return delay(ms, void 0, { signal });
160
284
  }
161
- async function postJson(url, body) {
285
+ async function postJson(url, body, signal) {
162
286
  const res = await fetch(url, {
163
287
  method: "POST",
164
288
  headers: {
@@ -166,7 +290,8 @@ async function postJson(url, body) {
166
290
  accept: "application/json",
167
291
  "user-agent": USER_AGENT
168
292
  },
169
- body: JSON.stringify(body)
293
+ body: JSON.stringify(body),
294
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
170
295
  });
171
296
  const data = await res.json().catch(() => ({}));
172
297
  return { status: res.status, data };
@@ -202,11 +327,11 @@ async function confirm(prompt, defaultValue) {
202
327
  rl.close();
203
328
  }
204
329
  }
205
- async function requestDeviceCode(serverUrl, clientId) {
330
+ async function requestDeviceCode(serverUrl, clientId, signal) {
206
331
  const { status, data } = await postJson(`${serverUrl}/api/auth/device/code`, {
207
332
  client_id: clientId,
208
333
  scope: DEVICE_SCOPE
209
- });
334
+ }, signal);
210
335
  if (status >= 400 || typeof data.device_code !== "string") {
211
336
  throw new LoginError(
212
337
  `Authorization request failed: ${data.error_description ?? data.error ?? data.message ?? `HTTP ${status}`}`
@@ -214,18 +339,18 @@ async function requestDeviceCode(serverUrl, clientId) {
214
339
  }
215
340
  return data;
216
341
  }
217
- async function pollForToken(serverUrl, clientId, device) {
342
+ async function pollForToken(serverUrl, clientId, device, signal) {
218
343
  let intervalMs = Math.max(1, device.interval ?? 5) * 1e3;
219
344
  const deadline = Date.now() + (device.expires_in ?? 0) * 1e3;
220
345
  while (Date.now() < deadline) {
221
- await sleep(intervalMs);
346
+ await sleep(intervalMs, signal);
222
347
  const { data } = await postJson(`${serverUrl}/api/auth/device/token`, {
223
348
  grant_type: DEVICE_GRANT_TYPE,
224
349
  device_code: device.device_code,
225
350
  client_id: clientId
226
- });
351
+ }, signal);
227
352
  if (typeof data.access_token === "string") {
228
- return data;
353
+ return { ...data, access_token: data.access_token };
229
354
  }
230
355
  const code = data.error;
231
356
  switch (code) {
@@ -248,14 +373,17 @@ async function pollForToken(serverUrl, clientId, device) {
248
373
  throw new LoginError("Timed out waiting for authorization. Run `indus login` again.");
249
374
  }
250
375
  async function runServerTierLogin(callbacks, serverUrl = INDUS_SERVER_URL, clientId = INDUS_CLIENT_ID) {
251
- const device = await requestDeviceCode(serverUrl, clientId);
376
+ callbacks.signal?.throwIfAborted();
377
+ const device = await requestDeviceCode(serverUrl, clientId, callbacks.signal);
378
+ callbacks.signal?.throwIfAborted();
252
379
  callbacks.onAuth({
253
380
  url: device.verification_uri_complete ?? device.verification_uri ?? "",
254
381
  instructions: `Enter code ${device.user_code} to authorize this device.`
255
382
  });
256
383
  callbacks.onProgress?.("Waiting for authorization...");
257
- const token = await pollForToken(serverUrl, clientId, device);
258
- await storeServerToken(token);
384
+ const token = await pollForToken(serverUrl, clientId, device, callbacks.signal);
385
+ callbacks.signal?.throwIfAborted();
386
+ if (!await storeServerToken(token)) throw new LoginError("Could not save the server token.");
259
387
  callbacks.onProgress?.("Server sign-in complete.");
260
388
  }
261
389
  async function doLogin(args) {
@@ -466,12 +594,12 @@ function openLoginUrl(url) {
466
594
  const tryAt = (index) => {
467
595
  if (index >= attempts.length) return Promise.resolve(false);
468
596
  const { command, args } = attempts[index];
469
- return new Promise((resolve9) => {
597
+ return new Promise((resolve11) => {
470
598
  let settled = false;
471
599
  const finish = (ok) => {
472
600
  if (settled) return;
473
601
  settled = true;
474
- resolve9(ok);
602
+ resolve11(ok);
475
603
  };
476
604
  try {
477
605
  const child = spawn(command, [...args], {
@@ -504,8 +632,11 @@ async function startOAuthLogin(providerId, callbacks, vault, account = "default"
504
632
  const hint = envVar !== void 0 ? `set ${envVar} and restart` : "configure its OAuth client id";
505
633
  throw new Error(`Browser sign-in for "${providerId}" is not configured: ${hint}.`);
506
634
  }
635
+ callbacks.signal?.throwIfAborted();
507
636
  const credentials = await provider.login(callbacks);
637
+ callbacks.signal?.throwIfAborted();
508
638
  const existing = await vault.listAccounts(providerId);
639
+ callbacks.signal?.throwIfAborted();
509
640
  const madeDefault = existing.length === 0;
510
641
  await vault.putOAuth(providerId, account, credentials, madeDefault);
511
642
  return { provider: providerId, account, madeDefault };
@@ -1247,6 +1378,18 @@ var FLAG_SPECS = [
1247
1378
  group: "context",
1248
1379
  describe: "Continue the most recent session in this directory."
1249
1380
  },
1381
+ {
1382
+ name: "--trust-project-addons",
1383
+ kind: "boolean",
1384
+ group: "context",
1385
+ describe: "Trust executable project addons for the canonical workspace and their current content digest."
1386
+ },
1387
+ {
1388
+ name: "--allow-context-import",
1389
+ kind: "list",
1390
+ group: "context",
1391
+ describe: "Explicitly approve an exact external context file (comma-separated or repeated). Secret-like files remain blocked."
1392
+ },
1250
1393
  /* ---- Tools ----------------------------------------------------------- */
1251
1394
  {
1252
1395
  name: "--tools",
@@ -1358,7 +1501,74 @@ var GPT_6_ASTRA_BASE = {
1358
1501
  }
1359
1502
  }
1360
1503
  };
1504
+ var GPT_6_SOL_BASE = {
1505
+ id: "gpt-6-sol",
1506
+ name: "GPT-6 Sol",
1507
+ api: "openai-responses",
1508
+ reasoning: true,
1509
+ reasoningEfforts: ["high"],
1510
+ input: ["text", "image"],
1511
+ cost: {
1512
+ input: 10,
1513
+ cacheRead: 1,
1514
+ cacheWrite: 12.5,
1515
+ output: 50
1516
+ },
1517
+ contextWindow: 105e4,
1518
+ maxTokens: 128e3,
1519
+ longContextPricing: {
1520
+ threshold: 272e3,
1521
+ multipliers: {
1522
+ input: 2,
1523
+ cacheRead: 2,
1524
+ cacheWrite: 2,
1525
+ output: 1.5
1526
+ }
1527
+ }
1528
+ };
1529
+ var GPT_6_LUNA_BASE = {
1530
+ id: "gpt-6-luna",
1531
+ name: "GPT-6 Luna",
1532
+ api: "openai-responses",
1533
+ reasoning: true,
1534
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
1535
+ input: ["text", "image"],
1536
+ cost: {
1537
+ input: 0.1,
1538
+ cacheRead: 0.01,
1539
+ cacheWrite: 0.125,
1540
+ output: 0.5
1541
+ },
1542
+ contextWindow: 105e4,
1543
+ maxTokens: 128e3,
1544
+ longContextPricing: {
1545
+ threshold: 272e3,
1546
+ multipliers: { input: 2, cacheRead: 2, cacheWrite: 2, output: 1.5 }
1547
+ }
1548
+ };
1361
1549
  var CODING_AGENT_MODELS = [
1550
+ {
1551
+ ...GPT_6_SOL_BASE,
1552
+ provider: "openai",
1553
+ baseUrl: "https://api.openai.com/v1"
1554
+ },
1555
+ {
1556
+ ...GPT_6_SOL_BASE,
1557
+ api: "openai-codex-responses",
1558
+ provider: "openai-codex",
1559
+ baseUrl: "https://chatgpt.com/backend-api"
1560
+ },
1561
+ {
1562
+ ...GPT_6_LUNA_BASE,
1563
+ provider: "openai",
1564
+ baseUrl: "https://api.openai.com/v1"
1565
+ },
1566
+ {
1567
+ ...GPT_6_LUNA_BASE,
1568
+ api: "openai-codex-responses",
1569
+ provider: "openai-codex",
1570
+ baseUrl: "https://chatgpt.com/backend-api"
1571
+ },
1362
1572
  {
1363
1573
  ...GPT_6_ASTRA_BASE,
1364
1574
  provider: "openai",
@@ -1397,8 +1607,15 @@ function createCodingAgentModelRegistry() {
1397
1607
  return applyCodingAgentModelOverlay(new ModelRegistry());
1398
1608
  }
1399
1609
  function isReasoningEffortSupported(modelId, effort) {
1400
- if (modelId?.split("/").at(-1) !== "gpt-6-astra") return true;
1401
- return GPT_6_ASTRA_BASE.reasoningEfforts.includes(effort);
1610
+ const id = modelId?.split("/").at(-1);
1611
+ if (id === "gpt-6-astra") {
1612
+ return GPT_6_ASTRA_BASE.reasoningEfforts.includes(effort);
1613
+ }
1614
+ if (id === "gpt-6-sol") return effort === "high";
1615
+ if (id === "gpt-6-luna") {
1616
+ return GPT_6_LUNA_BASE.reasoningEfforts.includes(effort);
1617
+ }
1618
+ return true;
1402
1619
  }
1403
1620
 
1404
1621
  // src/launch/invocation/read.ts
@@ -1771,13 +1988,13 @@ function defaultResumeDeps() {
1771
1988
  },
1772
1989
  mountPicker(sessions, totalCount) {
1773
1990
  const byPath = new Map(sessions.map((s) => [s.path, s]));
1774
- return new Promise((resolve9, reject) => {
1991
+ return new Promise((resolve11, reject) => {
1775
1992
  let settled = false;
1776
1993
  const settle = (value) => {
1777
1994
  if (settled) return;
1778
1995
  settled = true;
1779
1996
  instance.unmount();
1780
- resolve9(value);
1997
+ resolve11(value);
1781
1998
  };
1782
1999
  let instance;
1783
2000
  try {
@@ -1796,7 +2013,7 @@ function defaultResumeDeps() {
1796
2013
  void instance.waitUntilExit().then(() => {
1797
2014
  if (!settled) {
1798
2015
  settled = true;
1799
- resolve9(null);
2016
+ resolve11(null);
1800
2017
  }
1801
2018
  });
1802
2019
  });
@@ -1901,7 +2118,7 @@ import { join as join2 } from "node:path";
1901
2118
 
1902
2119
  // src/boot/upgrade/upgrades.ts
1903
2120
  import { promises as fs, constants as fsConstants } from "node:fs";
1904
- import { join, dirname, basename } from "node:path";
2121
+ import { join, dirname as dirname2, basename } from "node:path";
1905
2122
  var SECURE_FILE_MODE = 384;
1906
2123
  var RETIRED_SUFFIX = ".retired";
1907
2124
  async function pathExists(target) {
@@ -1934,7 +2151,7 @@ async function readJsonOrUndefined(target) {
1934
2151
  }
1935
2152
  }
1936
2153
  async function writeJsonWithMode(target, value, mode) {
1937
- await fs.mkdir(dirname(target), { recursive: true });
2154
+ await fs.mkdir(dirname2(target), { recursive: true });
1938
2155
  await fs.writeFile(target, `${JSON.stringify(value, null, 2)}
1939
2156
  `, { mode });
1940
2157
  await fs.chmod(target, mode);
@@ -1942,7 +2159,7 @@ async function writeJsonWithMode(target, value, mode) {
1942
2159
  async function relocateWithoutClobber(from, into) {
1943
2160
  if (!await pathExists(from)) return false;
1944
2161
  if (await pathExists(into)) return false;
1945
- await fs.mkdir(dirname(into), { recursive: true });
2162
+ await fs.mkdir(dirname2(into), { recursive: true });
1946
2163
  await fs.rename(from, into);
1947
2164
  return true;
1948
2165
  }
@@ -2099,7 +2316,6 @@ async function applyUpgrades(ws) {
2099
2316
  }
2100
2317
 
2101
2318
  // src/workspace/locator.ts
2102
- import { mkdirSync } from "node:fs";
2103
2319
  import { homedir } from "node:os";
2104
2320
  import { isAbsolute, join as join3, resolve } from "node:path";
2105
2321
  var LAYOUT = {
@@ -2163,7 +2379,7 @@ function createWorkspace(overrides = {}) {
2163
2379
  }
2164
2380
  function ensureDirs(workspace) {
2165
2381
  for (const key of DIRECTORY_KEYS) {
2166
- mkdirSync(workspace[key], { recursive: true });
2382
+ ensurePrivateDirectorySync(workspace[key]);
2167
2383
  }
2168
2384
  return workspace;
2169
2385
  }
@@ -2260,6 +2476,30 @@ var EMPTY_CONSOLE_STATE = {
2260
2476
  tick: 0
2261
2477
  };
2262
2478
 
2479
+ // src/console/input/graphemes.ts
2480
+ var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
2481
+ function previousGrapheme(text, at) {
2482
+ let previous = 0;
2483
+ for (const part of segmenter.segment(text)) {
2484
+ if (part.index >= at) break;
2485
+ previous = part.index;
2486
+ }
2487
+ return previous;
2488
+ }
2489
+ function nextGrapheme(text, at) {
2490
+ for (const part of segmenter.segment(text)) if (part.index > at) return part.index;
2491
+ return text.length;
2492
+ }
2493
+ function moveGrapheme(text, at, delta) {
2494
+ let next = Math.max(0, Math.min(at, text.length));
2495
+ for (let i = 0; i < Math.abs(delta); i++) next = delta < 0 ? previousGrapheme(text, next) : nextGrapheme(text, next);
2496
+ return next;
2497
+ }
2498
+ function snapGrapheme(text, at) {
2499
+ const clamped = Math.max(0, Math.min(at, text.length));
2500
+ return clamped === text.length ? clamped : previousGrapheme(text, clamped + 1);
2501
+ }
2502
+
2263
2503
  // src/console/reducer.ts
2264
2504
  function clamp(offset, max) {
2265
2505
  if (offset < 0) return 0;
@@ -2283,13 +2523,14 @@ function insertBuffer(state, text) {
2283
2523
  function erasePrev(state) {
2284
2524
  const at = clamp(state.caret, state.buffer.length);
2285
2525
  if (at === 0) return state;
2286
- const next = state.buffer.slice(0, at - 1) + state.buffer.slice(at);
2287
- return withBuffer(state, next, at - 1);
2526
+ const previous = previousGrapheme(state.buffer, at);
2527
+ const next = state.buffer.slice(0, previous) + state.buffer.slice(at);
2528
+ return withBuffer(state, next, previous);
2288
2529
  }
2289
2530
  function eraseNext(state) {
2290
2531
  const at = clamp(state.caret, state.buffer.length);
2291
2532
  if (at >= state.buffer.length) return state;
2292
- const next = state.buffer.slice(0, at) + state.buffer.slice(at + 1);
2533
+ const next = state.buffer.slice(0, at) + state.buffer.slice(nextGrapheme(state.buffer, at));
2293
2534
  return withBuffer(state, next, at);
2294
2535
  }
2295
2536
  function pushHistory(state, text) {
@@ -2332,6 +2573,10 @@ function patchRow(state, id, text) {
2332
2573
  }
2333
2574
  function consoleReducer(state, event) {
2334
2575
  switch (event.type) {
2576
+ case "view:reset":
2577
+ return { ...state, rows: [], blocks: [], buffer: "", caret: 0, historyAt: null, stash: "", modal: NO_MODAL, busy: false };
2578
+ case "display:set":
2579
+ return { ...state, [event.key]: event.value };
2335
2580
  // Composer buffer & caret
2336
2581
  case "buffer:set":
2337
2582
  return setBuffer(state, event.text);
@@ -2350,9 +2595,9 @@ function consoleReducer(state, event) {
2350
2595
  case "buffer:eraseNext":
2351
2596
  return eraseNext(state);
2352
2597
  case "caret:move":
2353
- return { ...state, caret: clamp(state.caret + event.delta, state.buffer.length) };
2598
+ return { ...state, caret: moveGrapheme(state.buffer, state.caret, event.delta) };
2354
2599
  case "caret:jump":
2355
- return { ...state, caret: clamp(event.offset, state.buffer.length) };
2600
+ return { ...state, caret: snapGrapheme(state.buffer, event.offset) };
2356
2601
  // History ring
2357
2602
  case "history:push":
2358
2603
  return pushHistory(state, event.text);
@@ -2582,7 +2827,7 @@ function resolveTheme(scheme) {
2582
2827
  var THEME_SCHEMES = Object.keys(THEMES);
2583
2828
 
2584
2829
  // src/console/slash/builtins.ts
2585
- import { homedir as homedir3 } from "node:os";
2830
+ import { homedir as homedir2 } from "node:os";
2586
2831
  import { join as join12 } from "node:path";
2587
2832
 
2588
2833
  // src/briefing/contract.ts
@@ -2767,7 +3012,7 @@ function composeBriefing(input) {
2767
3012
  }
2768
3013
 
2769
3014
  // src/briefing/macros.ts
2770
- import { readFileSync, readdirSync, statSync } from "node:fs";
3015
+ import { readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
2771
3016
  import { basename as basename2, join as join4 } from "node:path";
2772
3017
  var decimal = (ch) => ch.charCodeAt(0) >= 48 && ch.charCodeAt(0) <= 57;
2773
3018
  var bareword = (ch) => {
@@ -2979,13 +3224,13 @@ function loadMacros(dir, opts = {}) {
2979
3224
  const macros = [];
2980
3225
  for (const entry of entries.sort()) {
2981
3226
  if (entry.startsWith(".") || !entry.toLowerCase().endsWith(".md")) continue;
2982
- const path4 = join4(dir, entry);
3227
+ const path5 = join4(dir, entry);
2983
3228
  try {
2984
- if (!statSync(path4).isFile()) continue;
3229
+ if (!statSync(path5).isFile()) continue;
2985
3230
  } catch {
2986
3231
  continue;
2987
3232
  }
2988
- const macro = readMacroFile(path4, origin, label);
3233
+ const macro = readMacroFile(path5, origin, label);
2989
3234
  if (!seen.has(macro.name)) {
2990
3235
  seen.add(macro.name);
2991
3236
  macros.push(macro);
@@ -2993,17 +3238,17 @@ function loadMacros(dir, opts = {}) {
2993
3238
  }
2994
3239
  return macros;
2995
3240
  }
2996
- function readMacroFile(path4, origin, label) {
3241
+ function readMacroFile(path5, origin, label) {
2997
3242
  let text;
2998
3243
  try {
2999
- text = readFileSync(path4, "utf8");
3244
+ text = readFileSync2(path5, "utf8");
3000
3245
  } catch (cause) {
3001
- throw briefingFault("macro_invalid", `could not read macro file at ${path4}`, cause);
3246
+ throw briefingFault("macro_invalid", `could not read macro file at ${path5}`, cause);
3002
3247
  }
3003
3248
  const { frontmatter, body } = splitFrontmatter(text);
3004
- const name = basename2(path4).replace(/\.md$/i, "");
3249
+ const name = basename2(path5).replace(/\.md$/i, "");
3005
3250
  const declared = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
3006
- return { name, description: declared ? `${declared} (${label})` : deriveDescription(body, label), body, origin, source: path4 };
3251
+ return { name, description: declared ? `${declared} (${label})` : deriveDescription(body, label), body, origin, source: path5 };
3007
3252
  }
3008
3253
  function deriveDescription(body, label) {
3009
3254
  const first = body.split("\n").find((line) => line.trim())?.trim() ?? "";
@@ -3029,8 +3274,8 @@ function unquoteScalar(value) {
3029
3274
  }
3030
3275
 
3031
3276
  // src/briefing/skills.ts
3032
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, realpathSync, statSync as statSync2 } from "node:fs";
3033
- import { basename as basename3, dirname as dirname2, join as join5 } from "node:path";
3277
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, realpathSync, statSync as statSync2 } from "node:fs";
3278
+ import { basename as basename3, dirname as dirname3, join as join5 } from "node:path";
3034
3279
  var SKILL_MANIFEST = "SKILL.md";
3035
3280
  var PRUNED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
3036
3281
  var KNOWN_FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["name", "description", "license", "compatibility", "metadata", "allowed-tools", "disable-model-invocation"]);
@@ -3065,7 +3310,7 @@ function* walkLevel(dir, atRoot, seen) {
3065
3310
  seen.add(real);
3066
3311
  yield* walkLevel(full, false, seen);
3067
3312
  }
3068
- } else if (stats.isFile() && (atRoot && entry.toLowerCase().endsWith(".md") || !atRoot && entry === SKILL_MANIFEST)) yield { path: full, dirName: basename3(dirname2(full)) };
3313
+ } else if (stats.isFile() && (atRoot && entry.toLowerCase().endsWith(".md") || !atRoot && entry === SKILL_MANIFEST)) yield { path: full, dirName: basename3(dirname3(full)) };
3069
3314
  }
3070
3315
  }
3071
3316
  function checkName(name, dirName, fromDir) {
@@ -3104,7 +3349,7 @@ function describeCause(cause) {
3104
3349
  function parseCandidate(candidate, origin) {
3105
3350
  let text;
3106
3351
  try {
3107
- text = readFileSync2(candidate.path, "utf8");
3352
+ text = readFileSync3(candidate.path, "utf8");
3108
3353
  } catch (cause) {
3109
3354
  return { card: null, diagnostic: diag("invalid", candidate.path, `could not read the skill file (${describeCause(cause)})`) };
3110
3355
  }
@@ -3155,49 +3400,52 @@ function modelInvocableCards(cards) {
3155
3400
  }
3156
3401
 
3157
3402
  // src/briefing/context-docs.ts
3158
- import { existsSync, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
3159
- import { dirname as dirname3, extname, isAbsolute as isAbsolute2, join as join6, parse, resolve as resolve2 } from "node:path";
3403
+ import { closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync4, realpathSync as realpathSync2 } from "node:fs";
3404
+ import { basename as basename4, dirname as dirname4, extname, isAbsolute as isAbsolute2, join as join6, parse, resolve as resolve2, sep } from "node:path";
3160
3405
  var DEFAULT_MAX_IMPORT_DEPTH = 5;
3161
3406
  var DEFAULT_MAX_BYTES_PER_DOC = 4e4;
3162
3407
  var CANDIDATE_FILES = ["AGENTS.md", "CLAUDE.md", "INDUSAGI.md", "CLAUDE.local.md", "AGENTS.local.md", join6(".indusagi", "CLAUDE.md"), join6(".claude", "CLAUDE.md")];
3163
- var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".json", ".jsonc", ".yaml", ".yml", ".toml", ".xml", ".csv", ".html", ".htm", ".css", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".py", ".rb", ".go", ".rs", ".sh", ".env"]);
3408
+ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".json", ".jsonc", ".yaml", ".yml", ".toml", ".xml", ".csv", ".html", ".htm", ".css", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".py", ".rb", ".go", ".rs", ".sh"]);
3164
3409
  var IMPORT_REGEX = /(?:^|\s)@((?:[^\s\\]|\\ )+)/g;
3165
- function shortenPath(path4, cwd, home) {
3166
- if (cwd && path4.startsWith(cwd)) {
3167
- const rest = path4.slice(cwd.length).replace(/^[/\\]+/, "");
3410
+ var NO_FOLLOW2 = constants2.O_NOFOLLOW ?? 0;
3411
+ var SECRET_BASENAMES = /* @__PURE__ */ new Set([
3412
+ ".env",
3413
+ "credentials",
3414
+ "credentials.json",
3415
+ "id_rsa",
3416
+ "id_dsa",
3417
+ "id_ecdsa",
3418
+ "id_ed25519"
3419
+ ]);
3420
+ var SECRET_EXTENSIONS = /* @__PURE__ */ new Set([".key", ".pem", ".p12", ".pfx"]);
3421
+ function shortenPath(path5, cwd, home) {
3422
+ if (cwd && (path5 === cwd || path5.startsWith(`${cwd}${sep}`))) {
3423
+ const rest = path5.slice(cwd.length).replace(/^[/\\]+/, "");
3168
3424
  return rest ? `./${rest}` : ".";
3169
3425
  }
3170
- return home && path4.startsWith(home) ? `~${path4.slice(home.length)}` : path4;
3171
- }
3172
- function dirChain(cwd) {
3173
- const chain = [];
3174
- let current = cwd;
3175
- for (let i = 0; i < 256; i++) {
3176
- chain.push(current);
3177
- const parent = dirname3(current);
3178
- if (parent === current || current === parse(current).root) break;
3179
- current = parent;
3180
- }
3181
- return chain.reverse();
3182
- }
3183
- function safeRealpath(path4) {
3184
- try {
3185
- return realpathSync2(path4);
3186
- } catch {
3187
- return path4;
3188
- }
3426
+ return home && (path5 === home || path5.startsWith(`${home}${sep}`)) ? `~${path5.slice(home.length)}` : path5;
3189
3427
  }
3190
- function isTextFile(path4) {
3191
- const extension = extname(path4).toLowerCase();
3428
+ function isTextFile(path5) {
3429
+ const extension = extname(path5).toLowerCase();
3192
3430
  return !extension || TEXT_FILE_EXTENSIONS.has(extension);
3193
3431
  }
3194
- function readFileBody(path4) {
3195
- if (!existsSync(path4)) return null;
3432
+ function isLikelySecretFile(path5) {
3433
+ const name = basename4(path5).toLowerCase();
3434
+ return SECRET_BASENAMES.has(name) || name.startsWith(".env.") || SECRET_EXTENSIONS.has(extname(name));
3435
+ }
3436
+ function readFileBody(path5) {
3437
+ let fd;
3196
3438
  try {
3197
- const body = readFileSync3(path4, "utf8").trim();
3439
+ const before = lstatSync2(path5);
3440
+ if (before.isSymbolicLink() || !before.isFile()) return null;
3441
+ fd = openSync2(path5, constants2.O_RDONLY | NO_FOLLOW2);
3442
+ if (!fstatSync2(fd).isFile()) return null;
3443
+ const body = readFileSync4(fd, "utf8").trim();
3198
3444
  return body || null;
3199
3445
  } catch {
3200
3446
  return null;
3447
+ } finally {
3448
+ if (fd !== void 0) closeSync2(fd);
3201
3449
  }
3202
3450
  }
3203
3451
  function extractImports(body, baseDir, home) {
@@ -3216,23 +3464,46 @@ function extractImports(body, baseDir, home) {
3216
3464
  }
3217
3465
  return out2;
3218
3466
  }
3219
- function loadDocTree(path4, cwd, home, seen, depth, maxDepth, maxBytes, out2) {
3220
- const real = safeRealpath(path4);
3221
- if (seen.has(real)) return;
3467
+ function canonicalExistingFile(path5) {
3468
+ try {
3469
+ const before = lstatSync2(path5);
3470
+ if (before.isSymbolicLink() || !before.isFile()) return null;
3471
+ return realpathSync2(path5);
3472
+ } catch {
3473
+ return null;
3474
+ }
3475
+ }
3476
+ function isWithin(root, candidate) {
3477
+ return candidate === root || candidate.startsWith(`${root}${sep}`);
3478
+ }
3479
+ function loadTrustedDocTree(path5, cwd, home, seen, depth, maxDepth, maxBytes, out2, policy) {
3480
+ if (isLikelySecretFile(path5)) return;
3481
+ const real = canonicalExistingFile(path5);
3482
+ if (real === null || seen.has(real) || isLikelySecretFile(real)) return;
3483
+ if (!isWithin(policy.root, real) && !policy.approvedExternal.has(real)) return;
3222
3484
  seen.add(real);
3223
- const body = readFileBody(path4);
3485
+ const body = readFileBody(real);
3224
3486
  if (body === null) return;
3225
- out2.push({ path: shortenPath(path4, cwd, home), body: body.length > maxBytes ? body.slice(0, maxBytes) : body });
3487
+ out2.push({ path: shortenPath(real, cwd, home), body: body.length > maxBytes ? body.slice(0, maxBytes) : body });
3226
3488
  if (depth >= maxDepth) return;
3227
- for (const child of extractImports(body, dirname3(path4), home)) loadDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out2);
3489
+ for (const child of extractImports(body, dirname4(real), home)) {
3490
+ loadTrustedDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out2, policy);
3491
+ }
3228
3492
  }
3229
- function gatherContextDocs(cwd, home, opts) {
3493
+ function gatherContextDocs(cwd, home, opts = {}) {
3230
3494
  const out2 = [];
3231
3495
  try {
3496
+ const root = realpathSync2(cwd);
3497
+ const approvedExternal = /* @__PURE__ */ new Set();
3498
+ for (const path5 of opts.approvedExternalPaths ?? []) {
3499
+ const real = canonicalExistingFile(path5);
3500
+ if (real !== null && !isLikelySecretFile(real)) approvedExternal.add(real);
3501
+ }
3502
+ const policy = { root, approvedExternal };
3232
3503
  const seen = /* @__PURE__ */ new Set();
3233
- const roots = dirChain(cwd);
3234
- if (home) roots.push(home);
3235
- for (const dir of roots) for (const name of CANDIDATE_FILES) loadDocTree(join6(dir, name), cwd, home, seen, 0, opts?.maxImportDepth ?? DEFAULT_MAX_IMPORT_DEPTH, opts?.maxBytesPerDoc ?? DEFAULT_MAX_BYTES_PER_DOC, out2);
3504
+ for (const name of CANDIDATE_FILES) {
3505
+ loadTrustedDocTree(join6(root, name), root, home, seen, 0, opts.maxImportDepth ?? DEFAULT_MAX_IMPORT_DEPTH, opts.maxBytesPerDoc ?? DEFAULT_MAX_BYTES_PER_DOC, out2, policy);
3506
+ }
3236
3507
  } catch {
3237
3508
  return out2;
3238
3509
  }
@@ -3345,9 +3616,9 @@ function buildDynamicCommands(sources = {}) {
3345
3616
 
3346
3617
  // src/console/slash/commands/integrations.ts
3347
3618
  import { spawnSync as spawnSync3 } from "node:child_process";
3348
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
3349
- import { homedir as homedir2 } from "node:os";
3350
- import { join as join10, resolve as resolve5 } from "node:path";
3619
+ import { getEnvApiKey as getEnvApiKey2 } from "indusagi/ai";
3620
+ import { writeFileSync as writeFileSync4 } from "node:fs";
3621
+ import { resolve as resolve5 } from "node:path";
3351
3622
  import { createMCPAgentToolFactory, loadMCPConfig, MCPClientPool } from "indusagi/mcp";
3352
3623
  import { createComposioGateway } from "indusagi/connectors-saas";
3353
3624
 
@@ -4467,14 +4738,14 @@ var DaemonTable = class {
4467
4738
  if (handle.state !== "running") return;
4468
4739
  const child = handle.child;
4469
4740
  child.kill("SIGTERM");
4470
- await new Promise((resolve9) => {
4741
+ await new Promise((resolve11) => {
4471
4742
  const timer = setTimeout(() => {
4472
4743
  if (handle.state === "running") child.kill("SIGKILL");
4473
- resolve9();
4744
+ resolve11();
4474
4745
  }, graceMs);
4475
4746
  child.once("exit", () => {
4476
4747
  clearTimeout(timer);
4477
- resolve9();
4748
+ resolve11();
4478
4749
  });
4479
4750
  });
4480
4751
  }
@@ -5218,7 +5489,7 @@ function createLimiter(limit) {
5218
5489
  queue.shift()?.();
5219
5490
  };
5220
5491
  return async (fn) => {
5221
- if (active >= limit) await new Promise((resolve9) => queue.push(resolve9));
5492
+ if (active >= limit) await new Promise((resolve11) => queue.push(resolve11));
5222
5493
  active++;
5223
5494
  try {
5224
5495
  return await fn();
@@ -5284,6 +5555,56 @@ import { Agent } from "indusagi/agent";
5284
5555
  import { createZeroUsage as createZeroUsage2 } from "indusagi/ai";
5285
5556
  import * as path3 from "node:path";
5286
5557
 
5558
+ // src/kit/run-shell.ts
5559
+ import { spawn as spawn3 } from "node:child_process";
5560
+ function runShell(command, cwd, signal) {
5561
+ if (signal?.aborted) return Promise.resolve({ output: "Command cancelled.", exitCode: 130 });
5562
+ return new Promise((resolve11) => {
5563
+ let output = "", truncated = false, settled = false;
5564
+ let forceKill;
5565
+ const child = spawn3(command, { cwd, shell: true, detached: process.platform !== "win32" });
5566
+ const capture = (chunk) => {
5567
+ output += String(chunk);
5568
+ if (output.length > 1024 * 1024) {
5569
+ output = output.slice(-1024 * 1024);
5570
+ truncated = true;
5571
+ }
5572
+ };
5573
+ const kill = (signalName) => {
5574
+ try {
5575
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, signalName);
5576
+ else child.kill(signalName);
5577
+ } catch {
5578
+ try {
5579
+ child.kill(signalName);
5580
+ } catch {
5581
+ }
5582
+ }
5583
+ };
5584
+ const abort = () => {
5585
+ kill("SIGTERM");
5586
+ forceKill = setTimeout(() => kill("SIGKILL"), 1e3);
5587
+ forceKill.unref();
5588
+ };
5589
+ const finish = (exitCode) => {
5590
+ if (settled) return;
5591
+ settled = true;
5592
+ signal?.removeEventListener("abort", abort);
5593
+ if (forceKill) clearTimeout(forceKill);
5594
+ resolve11({ output: (truncated ? "[older output truncated]\n" : "") + output, exitCode: signal?.aborted ? 130 : exitCode });
5595
+ };
5596
+ child.stdout?.on("data", capture);
5597
+ child.stderr?.on("data", capture);
5598
+ child.once("error", (error) => {
5599
+ capture(error.message);
5600
+ finish(1);
5601
+ });
5602
+ child.once("close", (code) => finish(code ?? 1));
5603
+ signal?.addEventListener("abort", abort, { once: true });
5604
+ if (signal?.aborted) abort();
5605
+ });
5606
+ }
5607
+
5287
5608
  // src/conductor/catalog/catalog.ts
5288
5609
  import { z } from "zod";
5289
5610
  import { getModels, getProviders, modelRegistry } from "indusagi/ai";
@@ -5711,30 +6032,21 @@ function memoryBackend() {
5711
6032
  }
5712
6033
  function fsBackend(dir) {
5713
6034
  const fileOf = async (sessionId) => {
5714
- const path4 = await import("node:path");
5715
- return path4.join(dir, `${sessionId}.ndjson`);
6035
+ const path5 = await import("node:path");
6036
+ return path5.join(dir, `${sessionId}.ndjson`);
5716
6037
  };
5717
6038
  return {
5718
6039
  locate: (sessionId) => `${dir}/${sessionId}.ndjson`,
5719
6040
  read: async (sessionId) => {
5720
- const fs3 = await import("node:fs/promises");
5721
- try {
5722
- return await fs3.readFile(await fileOf(sessionId), "utf8");
5723
- } catch {
5724
- return null;
5725
- }
6041
+ return readPrivateFileSync(await fileOf(sessionId));
5726
6042
  },
5727
6043
  append: async (sessionId, line) => {
5728
- const fs3 = await import("node:fs/promises");
5729
- await fs3.mkdir(dir, { recursive: true });
5730
- await fs3.appendFile(await fileOf(sessionId), `${line}
5731
- `, "utf8");
6044
+ appendPrivateFileSync(await fileOf(sessionId), `${line}
6045
+ `);
5732
6046
  },
5733
6047
  rewrite: async (sessionId, text) => {
5734
- const fs3 = await import("node:fs/promises");
5735
- await fs3.mkdir(dir, { recursive: true });
5736
- await fs3.writeFile(await fileOf(sessionId), text.length === 0 ? "" : `${text}
5737
- `, "utf8");
6048
+ atomicWritePrivateFileSync(await fileOf(sessionId), text.length === 0 ? "" : `${text}
6049
+ `);
5738
6050
  }
5739
6051
  };
5740
6052
  }
@@ -6372,7 +6684,10 @@ async function summarize(messages, deps = {}) {
6372
6684
  const digest2 = localFallbackDigest(messages, deps.priorDigest);
6373
6685
  return { message: toSummaryMessage(digest2, scope), coveredCount };
6374
6686
  }
6375
- const prompt = buildSummaryPrompt(messages, scope, deps.priorDigest);
6687
+ const prompt = buildSummaryPrompt(messages, scope, deps.priorDigest) + (deps.instructions ? `
6688
+
6689
+ User compaction guidance:
6690
+ ${deps.instructions}` : "");
6376
6691
  const context = {
6377
6692
  systemPrompt: CONDENSER_BRIEF,
6378
6693
  messages: [{ role: "user", content: prompt, timestamp: Date.now() }]
@@ -6496,8 +6811,8 @@ function collectVisibleReadPaths(kept, readToolName) {
6496
6811
  if (!isRecord4(block2) || block2.type !== "toolCall") continue;
6497
6812
  if (block2.name !== readToolName) continue;
6498
6813
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
6499
- const path4 = args && typeof args.path === "string" ? args.path : "";
6500
- if (path4.length > 0) paths.add(path4);
6814
+ const path5 = args && typeof args.path === "string" ? args.path : "";
6815
+ if (path5.length > 0) paths.add(path5);
6501
6816
  }
6502
6817
  }
6503
6818
  return paths;
@@ -6513,8 +6828,8 @@ function collectDroppedReads(dropped, readToolName) {
6513
6828
  if (block2.name !== readToolName) continue;
6514
6829
  const id = typeof block2.id === "string" ? block2.id : "";
6515
6830
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
6516
- const path4 = args && typeof args.path === "string" ? args.path : "";
6517
- if (id.length > 0 && path4.length > 0) calls.set(id, { path: path4, order: i });
6831
+ const path5 = args && typeof args.path === "string" ? args.path : "";
6832
+ if (id.length > 0 && path5.length > 0) calls.set(id, { path: path5, order: i });
6518
6833
  }
6519
6834
  }
6520
6835
  if (calls.size === 0) return [];
@@ -6565,7 +6880,7 @@ ${read.body}`,
6565
6880
 
6566
6881
  // src/conductor/diagnostics.ts
6567
6882
  import * as path2 from "node:path";
6568
- import { existsSync as existsSync2 } from "node:fs";
6883
+ import { existsSync } from "node:fs";
6569
6884
  var MAX_PER_FILE = 10;
6570
6885
  var MAX_TOTAL = 30;
6571
6886
  var DELIVERED_KEYS_CAP = 500;
@@ -6622,18 +6937,18 @@ async function loadSpawn() {
6622
6937
  return void 0;
6623
6938
  }
6624
6939
  }
6625
- function captureOutput(spawn3, bin, args, cwd, signal) {
6626
- return new Promise((resolve9) => {
6940
+ function captureOutput(spawn4, bin, args, cwd, signal) {
6941
+ return new Promise((resolve11) => {
6627
6942
  let settled = false;
6628
6943
  const finish = (value) => {
6629
6944
  if (!settled) {
6630
6945
  settled = true;
6631
- resolve9(value);
6946
+ resolve11(value);
6632
6947
  }
6633
6948
  };
6634
6949
  let child;
6635
6950
  try {
6636
- child = spawn3(bin, args, { cwd, shell: false });
6951
+ child = spawn4(bin, args, { cwd, shell: false });
6637
6952
  } catch {
6638
6953
  finish();
6639
6954
  return;
@@ -6662,9 +6977,9 @@ function captureOutput(spawn3, bin, args, cwd, signal) {
6662
6977
  });
6663
6978
  }
6664
6979
  async function runTscDiagnostics(cwd, signal) {
6665
- const spawn3 = await loadSpawn();
6666
- if (!spawn3 || signal?.aborted) return [];
6667
- const output = await captureOutput(spawn3, path2.join(cwd, "node_modules", ".bin", "tsc"), ["--noEmit", "--pretty", "false"], cwd, signal);
6980
+ const spawn4 = await loadSpawn();
6981
+ if (!spawn4 || signal?.aborted) return [];
6982
+ const output = await captureOutput(spawn4, path2.join(cwd, "node_modules", ".bin", "tsc"), ["--noEmit", "--pretty", "false"], cwd, signal);
6668
6983
  return output === void 0 ? [] : parseTscOutput(output, cwd);
6669
6984
  }
6670
6985
  function parseTscOutput(output, cwd) {
@@ -6684,9 +6999,9 @@ function parseTscOutput(output, cwd) {
6684
6999
  }
6685
7000
  async function runEslintDiagnostics(files, cwd, signal) {
6686
7001
  if (files.length === 0) return [];
6687
- const spawn3 = await loadSpawn();
6688
- if (!spawn3 || signal?.aborted) return [];
6689
- const output = await captureOutput(spawn3, path2.join(cwd, "node_modules", ".bin", "eslint"), ["--format", "json", ...files], cwd, signal);
7002
+ const spawn4 = await loadSpawn();
7003
+ if (!spawn4 || signal?.aborted) return [];
7004
+ const output = await captureOutput(spawn4, path2.join(cwd, "node_modules", ".bin", "eslint"), ["--format", "json", ...files], cwd, signal);
6690
7005
  return output === void 0 ? [] : parseEslintOutput(output, cwd);
6691
7006
  }
6692
7007
  function parseEslintOutput(output, cwd) {
@@ -6793,14 +7108,14 @@ function isTypeScriptOrJs(filePath) {
6793
7108
  }
6794
7109
  function hasTsConfig(cwd) {
6795
7110
  try {
6796
- return existsSync2(path2.join(cwd, "tsconfig.json"));
7111
+ return existsSync(path2.join(cwd, "tsconfig.json"));
6797
7112
  } catch {
6798
7113
  return false;
6799
7114
  }
6800
7115
  }
6801
7116
  function hasEslintConfig(cwd) {
6802
7117
  try {
6803
- return ["eslint.config.js", "eslint.config.mjs", "eslint.config.cjs", ".eslintrc", ".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc.yml", ".eslintrc.yaml"].some((name) => existsSync2(path2.join(cwd, name)));
7118
+ return ["eslint.config.js", "eslint.config.mjs", "eslint.config.cjs", ".eslintrc", ".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc.yml", ".eslintrc.yaml"].some((name) => existsSync(path2.join(cwd, name)));
6804
7119
  } catch {
6805
7120
  return false;
6806
7121
  }
@@ -6951,6 +7266,8 @@ var SessionConductorImpl = class {
6951
7266
  #matcher;
6952
7267
  #state;
6953
7268
  #busy = false;
7269
+ #activeWork;
7270
+ #shellAbort;
6954
7271
  #draining = false;
6955
7272
  #queue = [];
6956
7273
  #boundModel;
@@ -7010,6 +7327,7 @@ var SessionConductorImpl = class {
7010
7327
  abort() {
7011
7328
  this.clearQueue();
7012
7329
  this.#condenseAbort?.abort();
7330
+ this.#shellAbort?.abort();
7013
7331
  this.agent.abort();
7014
7332
  const fault = conductorFault("aborted", "turn aborted by caller");
7015
7333
  this.#dispatch({ type: "fault", fault });
@@ -7022,13 +7340,22 @@ var SessionConductorImpl = class {
7022
7340
  return this.#state;
7023
7341
  }
7024
7342
  this.#busy = true;
7025
- try {
7026
- await this.#runTurn(input);
7027
- await this.#drainQueue();
7028
- return this.#state;
7029
- } finally {
7030
- this.#busy = false;
7031
- }
7343
+ const work = (async () => {
7344
+ try {
7345
+ await this.#runTurn(input);
7346
+ await this.#drainQueue();
7347
+ return this.#state;
7348
+ } finally {
7349
+ this.#busy = false;
7350
+ }
7351
+ })();
7352
+ this.#activeWork = work;
7353
+ return work;
7354
+ }
7355
+ async #stopWork() {
7356
+ if (!this.#busy) return;
7357
+ this.abort();
7358
+ await this.#activeWork;
7032
7359
  }
7033
7360
  async #drainQueue() {
7034
7361
  if (this.#draining) return;
@@ -7066,7 +7393,7 @@ var SessionConductorImpl = class {
7066
7393
  }
7067
7394
  if (attempt < (this.deps.retry ?? DEFAULT_RETRY).maxAttempts && isTransient(error)) {
7068
7395
  this.#hub.emit({ kind: "fault", fault: conductorFault("model", `transient model fault; retry ${attempt + 1}`, error), transient: true });
7069
- await (this.deps.sleep ?? ((ms) => new Promise((resolve9) => setTimeout(resolve9, ms))))((this.deps.retry ?? DEFAULT_RETRY).baseDelayMs * 2 ** attempt);
7396
+ await (this.deps.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms))))((this.deps.retry ?? DEFAULT_RETRY).baseDelayMs * 2 ** attempt);
7070
7397
  continue;
7071
7398
  }
7072
7399
  if (isOverloadFault(error) && this.options.fallbackModelId) this.selectModel(this.options.fallbackModelId);
@@ -7117,8 +7444,16 @@ var SessionConductorImpl = class {
7117
7444
  }
7118
7445
  this.#applyModel(card);
7119
7446
  }
7120
- cycleModel(id) {
7121
- this.selectModel(id);
7447
+ cycleModel(id, direction = 1) {
7448
+ if (id.trim()) {
7449
+ this.selectModel(id);
7450
+ return;
7451
+ }
7452
+ const cards = this.availableModels();
7453
+ if (!cards.length) return;
7454
+ const at = cards.findIndex((card) => card.id === this.#state.modelId);
7455
+ const index = at < 0 ? 0 : (at + direction + cards.length) % cards.length;
7456
+ this.selectModel(cards[index].id);
7122
7457
  }
7123
7458
  #applyModel(card) {
7124
7459
  const model = withGatewayBaseUrl(card.model, this.#gatewayBaseUrls);
@@ -7133,28 +7468,36 @@ var SessionConductorImpl = class {
7133
7468
  registerTools(tools) {
7134
7469
  this.agent.setTools([...this.#deckTools, ...tools]);
7135
7470
  }
7136
- async condense() {
7471
+ async condense(instructions) {
7137
7472
  if (this.#busy) return "busy";
7138
7473
  this.#busy = true;
7139
7474
  this.#condenseAbort = new AbortController();
7140
- try {
7141
- const result = await this.#runCondense(true, this.#condenseAbort.signal);
7142
- await this.#drainQueue();
7143
- return result;
7144
- } finally {
7145
- this.#busy = false;
7146
- this.#condenseAbort = void 0;
7147
- }
7475
+ const work = (async () => {
7476
+ try {
7477
+ const result = await this.#runCondense(true, this.#condenseAbort.signal, instructions);
7478
+ await this.#drainQueue();
7479
+ return result;
7480
+ } finally {
7481
+ this.#busy = false;
7482
+ this.#condenseAbort = void 0;
7483
+ }
7484
+ })();
7485
+ this.#activeWork = work;
7486
+ return work;
7148
7487
  }
7149
7488
  cancelCondense() {
7150
7489
  this.#condenseAbort?.abort();
7151
7490
  }
7152
- async #runCondense(force, signal) {
7491
+ async #runCondense(force, signal, instructions) {
7153
7492
  const before = [...this.agent.state.messages];
7154
7493
  this.#dispatch({ type: "phase", phase: "condensing" });
7155
7494
  try {
7156
- const after = await (this.deps.condense ?? noopCondense)(before, { force, model: this.#boundModel, contextTokens: this.#state.contextTokens, signal });
7157
- if (signal?.aborted) return "cancelled";
7495
+ const after = await (this.deps.condense ?? noopCondense)(before, { force, model: this.#boundModel, contextTokens: this.#state.contextTokens, signal, instructions });
7496
+ if (signal?.aborted) {
7497
+ this.#dispatch({ type: "settled" });
7498
+ this.#hub.emit({ kind: "idle" });
7499
+ return "cancelled";
7500
+ }
7158
7501
  if (after.length >= before.length) {
7159
7502
  this.#dispatch({ type: "settled" });
7160
7503
  return "nothing";
@@ -7181,10 +7524,12 @@ var SessionConductorImpl = class {
7181
7524
  }
7182
7525
  async #move(id, message) {
7183
7526
  try {
7527
+ await this.#stopWork();
7184
7528
  await this.store.branchAt(id);
7185
7529
  this.agent.replaceMessages(this.store.messages());
7186
7530
  this.#dispatch({ type: "head", head: this.store.head });
7187
7531
  this.#dispatch({ type: "settled" });
7532
+ this.#hub.emit({ kind: "reset" });
7188
7533
  this.#hub.emit({ kind: "idle" });
7189
7534
  } catch (error) {
7190
7535
  const fault = conductorFault("persistence", message, error);
@@ -7199,18 +7544,33 @@ var SessionConductorImpl = class {
7199
7544
  return { canRestore: this.options.checkpoint?.hasSnapshot(id) ?? false };
7200
7545
  }
7201
7546
  async executeBash(command, opts = {}) {
7202
- const { spawn: spawn3 } = await import("node:child_process");
7203
- const outcome = await new Promise((resolve9) => {
7204
- const child = spawn3(command, { cwd: this.#workspace, shell: true });
7205
- let output = "";
7206
- child.stdout?.on("data", (chunk) => output += String(chunk));
7207
- child.stderr?.on("data", (chunk) => output += String(chunk));
7208
- child.on("error", (error) => resolve9({ output: output + errorText(error), exitCode: 1 }));
7209
- child.on("close", (code) => resolve9({ output, exitCode: code ?? 1 }));
7210
- });
7211
- if (!opts.excludeFromContext) await this.store.append({ role: "user", content: `$ ${command}
7212
- ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
7213
- return outcome;
7547
+ if (this.#busy) throw new Error("A turn is running \u2014 stop it before running a shell command.");
7548
+ this.#busy = true;
7549
+ this.#shellAbort = new AbortController();
7550
+ const signal = opts.signal ? AbortSignal.any([opts.signal, this.#shellAbort.signal]) : this.#shellAbort.signal;
7551
+ const work = (async () => {
7552
+ try {
7553
+ this.#dispatch({ type: "phase", phase: "tooling" });
7554
+ const outcome = await runShell(command, this.#workspace, signal);
7555
+ if (!opts.excludeFromContext) {
7556
+ const message = { role: "user", content: `$ ${command}
7557
+ ${outcome.output}`.trimEnd(), timestamp: Date.now() };
7558
+ const entry = await this.store.append(message, "note");
7559
+ this.agent.replaceMessages([...this.agent.state.messages, message]);
7560
+ this.#dispatch({ type: "head", head: this.store.head });
7561
+ this.#hub.emit({ kind: "persisted", entryId: entry.id });
7562
+ }
7563
+ this.#dispatch({ type: "settled" });
7564
+ this.#hub.emit({ kind: "idle" });
7565
+ await this.#drainQueue();
7566
+ return outcome;
7567
+ } finally {
7568
+ this.#busy = false;
7569
+ this.#shellAbort = void 0;
7570
+ }
7571
+ })();
7572
+ this.#activeWork = work;
7573
+ return work;
7214
7574
  }
7215
7575
  stats() {
7216
7576
  const messages = this.agent.state.messages;
@@ -7280,6 +7640,7 @@ ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
7280
7640
  this.#sessionName = name;
7281
7641
  }
7282
7642
  async resume(id) {
7643
+ await this.#stopWork();
7283
7644
  if (!await this.store.load(id)) {
7284
7645
  const fault = conductorFault("persistence", `no session "${id}" to resume`);
7285
7646
  this.#dispatch({ type: "fault", fault });
@@ -7288,19 +7649,23 @@ ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
7288
7649
  }
7289
7650
  this.agent.replaceMessages(this.store.messages());
7290
7651
  this.#state = { ...this.#state, head: this.store.head, usage: this.store.usage ?? createZeroUsage2(), phase: "idle" };
7652
+ this.#hub.emit({ kind: "reset" });
7291
7653
  this.#hub.emit({ kind: "idle" });
7292
7654
  }
7293
7655
  async newSession() {
7294
- this.agent.abort();
7656
+ await this.#stopWork();
7295
7657
  this.agent.replaceMessages([]);
7296
7658
  this.store.startNewSession(newSessionId());
7297
7659
  this.#queue.length = 0;
7298
7660
  this.#sessionName = void 0;
7299
7661
  this.#state = { phase: "idle", head: this.store.head, usage: createZeroUsage2(), contextTokens: 0, modelId: this.#state.modelId };
7662
+ this.#hub.emit({ kind: "reset" });
7300
7663
  this.#hub.emit({ kind: "idle" });
7301
7664
  }
7302
7665
  #dispatch(action) {
7666
+ const phase = this.#state.phase;
7303
7667
  this.#state = reduceState(this.#state, action);
7668
+ if (phase !== this.#state.phase) this.#hub.emit({ kind: "phase", phase: this.#state.phase });
7304
7669
  }
7305
7670
  };
7306
7671
 
@@ -8068,7 +8433,7 @@ var SETTING_KEYS = Object.freeze(
8068
8433
 
8069
8434
  // src/kit/clipboard-image.ts
8070
8435
  import { spawnSync } from "node:child_process";
8071
- import { readFileSync as readFileSync4, writeFileSync } from "node:fs";
8436
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
8072
8437
  import { tmpdir } from "node:os";
8073
8438
  import { join as join8 } from "node:path";
8074
8439
  var READ_TIMEOUT_MS = 3e3;
@@ -8113,10 +8478,10 @@ function captureViaOsascript() {
8113
8478
  { timeout: READ_TIMEOUT_MS, encoding: "utf8" }
8114
8479
  );
8115
8480
  if (result.error || result.status !== 0) return null;
8116
- const path4 = String(result.stdout ?? "").trim();
8117
- if (path4.length === 0) return null;
8481
+ const path5 = String(result.stdout ?? "").trim();
8482
+ if (path5.length === 0) return null;
8118
8483
  try {
8119
- const bytes = readFileSync4(path4);
8484
+ const bytes = readFileSync5(path5);
8120
8485
  return bytes.length > 0 ? bytes : null;
8121
8486
  } catch {
8122
8487
  return null;
@@ -8147,10 +8512,10 @@ function readClipboardImage(options = {}) {
8147
8512
  if (bytes === null) return null;
8148
8513
  const dir = options.dir ?? tmpdir();
8149
8514
  const name = `indus-clipboard-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.png`;
8150
- const path4 = join8(dir, name);
8515
+ const path5 = join8(dir, name);
8151
8516
  try {
8152
- writeFileSync(path4, bytes);
8153
- return path4;
8517
+ writeFileSync2(path5, bytes);
8518
+ return path5;
8154
8519
  } catch {
8155
8520
  return null;
8156
8521
  }
@@ -8158,7 +8523,7 @@ function readClipboardImage(options = {}) {
8158
8523
 
8159
8524
  // src/kit/external-editor.ts
8160
8525
  import { spawnSync as spawnSync2 } from "node:child_process";
8161
- import { readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
8526
+ import { readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
8162
8527
  import { tmpdir as tmpdir2 } from "node:os";
8163
8528
  import { join as join9 } from "node:path";
8164
8529
  var FALLBACK_EDITOR = "vi";
@@ -8171,18 +8536,28 @@ function openInExternalEditor(buffer, options = {}) {
8171
8536
  const env2 = options.env ?? process.env;
8172
8537
  const dir = options.dir ?? tmpdir2();
8173
8538
  const name = `indus-editor-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.md`;
8174
- const path4 = join9(dir, name);
8539
+ const path5 = join9(dir, name);
8175
8540
  const [editor, ...editorArgs] = resolveEditor(env2);
8176
8541
  try {
8177
- writeFileSync2(path4, buffer, "utf8");
8178
- const result = spawnSync2(editor, [...editorArgs, path4], { stdio: "inherit" });
8542
+ writeFileSync3(path5, buffer, "utf8");
8543
+ const terminal = options.terminal ?? { stdin: process.stdin, stdout: process.stdout };
8544
+ const wasRaw = terminal.stdin.isRaw === true;
8545
+ let result;
8546
+ try {
8547
+ terminal.stdin.setRawMode?.(false);
8548
+ if (terminal.stdout.isTTY) terminal.stdout.write("\x1B[?1049h\x1B[?25h");
8549
+ result = spawnSync2(editor, [...editorArgs, path5], { stdio: "inherit" });
8550
+ } finally {
8551
+ if (terminal.stdout.isTTY) terminal.stdout.write("\x1B[?1049l\x1B[?25l");
8552
+ terminal.stdin.setRawMode?.(wasRaw);
8553
+ }
8179
8554
  if (result.error || result.status !== 0) return null;
8180
- return readFileSync5(path4, "utf8").replace(/\n$/, "");
8555
+ return readFileSync6(path5, "utf8").replace(/\n$/, "");
8181
8556
  } catch {
8182
8557
  return null;
8183
8558
  } finally {
8184
8559
  try {
8185
- unlinkSync(path4);
8560
+ unlinkSync(path5);
8186
8561
  } catch {
8187
8562
  }
8188
8563
  }
@@ -8832,6 +9207,7 @@ function lastAssistantText(messages) {
8832
9207
  return "";
8833
9208
  }
8834
9209
  function toPublishRole(role) {
9210
+ if (role === "toolResult") return "tool";
8835
9211
  return PUBLISH_ROLES.has(role) ? role : "note";
8836
9212
  }
8837
9213
  function toPublishEntries(messages) {
@@ -8862,7 +9238,7 @@ function exportTranscriptHtml(ctx, target) {
8862
9238
  const html = publishTranscript(toPublishEntries(messages), {
8863
9239
  title: "Session Transcript"
8864
9240
  });
8865
- writeFileSync3(outPath, html, "utf-8");
9241
+ writeFileSync4(outPath, html, "utf-8");
8866
9242
  return { ok: true, path: outPath };
8867
9243
  } catch (cause) {
8868
9244
  const message = cause instanceof Error ? cause.message : String(cause);
@@ -9253,13 +9629,11 @@ function runLogin2(ctx) {
9253
9629
  }
9254
9630
  return HANDLED;
9255
9631
  }
9256
- function hasLocalProviderKey(provider) {
9257
- if (provider.length === 0) return false;
9632
+ async function hasLocalProviderKey(provider, ctx) {
9633
+ if (!provider) return false;
9258
9634
  try {
9259
- const file = join10(homedir2(), ".indusagi", "agent", "auth.json");
9260
- const vault = JSON.parse(readFileSync6(file, "utf8"));
9261
- const entry = vault[provider];
9262
- return entry !== null && typeof entry === "object" && Object.keys(entry).length > 0;
9635
+ if (getEnvApiKey2(provider)) return true;
9636
+ return (await ctx.services?.vault.listAccounts(provider) ?? []).length > 0;
9263
9637
  } catch {
9264
9638
  return false;
9265
9639
  }
@@ -9275,7 +9649,7 @@ function runStatus(ctx) {
9275
9649
  signedIn = false;
9276
9650
  }
9277
9651
  const eligible = isGatewayEligibleProvider(provider);
9278
- const localKey = hasLocalProviderKey(provider);
9652
+ const localKey = await hasLocalProviderKey(provider, ctx);
9279
9653
  const viaServer = signedIn && eligible && !localKey;
9280
9654
  if (viaServer) {
9281
9655
  ctx.setStatus(info(`\u25CF server tier \u2014 ${modelId} routing via ${INDUS_SERVER_URL} (signed in)`));
@@ -9456,12 +9830,7 @@ var compactCommand = {
9456
9830
  ctx.setStatus({ kind: "busy", text: busyText });
9457
9831
  void (async () => {
9458
9832
  try {
9459
- if (instructions.length > 0) {
9460
- await ctx.conductor.executeBash(
9461
- `printf '%s' ${JSON.stringify(`Compaction guidance: ${instructions}`)}`
9462
- );
9463
- }
9464
- switch (await ctx.conductor.condense()) {
9833
+ switch (await ctx.conductor.condense(instructions || void 0)) {
9465
9834
  case "condensed":
9466
9835
  ctx.setStatus(
9467
9836
  info(
@@ -9636,7 +10005,7 @@ var transcriptCommands = [
9636
10005
  ];
9637
10006
 
9638
10007
  // src/console/slash/commands/workbench.ts
9639
- import { mkdtempSync, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
10008
+ import { mkdtempSync, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
9640
10009
  import { tmpdir as tmpdir3 } from "node:os";
9641
10010
  import { join as join11 } from "node:path";
9642
10011
  function block(slug, title, markdown) {
@@ -9672,7 +10041,7 @@ function hotkeysBlock() {
9672
10041
  function readChangelog(cwd) {
9673
10042
  for (const name of CHANGELOG_NAMES) {
9674
10043
  try {
9675
- const text = readFileSync7(join11(cwd, name), "utf8");
10044
+ const text = readFileSync8(join11(cwd, name), "utf8");
9676
10045
  if (text.trim().length > 0) return text;
9677
10046
  } catch {
9678
10047
  }
@@ -9710,9 +10079,9 @@ function runDebug(ctx) {
9710
10079
  const jsonl = records.map((r) => JSON.stringify(r)).join("\n") + "\n";
9711
10080
  try {
9712
10081
  const dir = mkdtempSync(join11(tmpdir3(), "indus-debug-"));
9713
- const path4 = join11(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
9714
- writeFileSync4(path4, jsonl, "utf8");
9715
- ctx.setStatus(info(`Diagnostics written to ${path4}`));
10082
+ const path5 = join11(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
10083
+ writeFileSync5(path5, jsonl, "utf8");
10084
+ ctx.setStatus(info(`Diagnostics written to ${path5}`));
9716
10085
  } catch (cause) {
9717
10086
  const detail = cause instanceof Error ? cause.message : String(cause);
9718
10087
  ctx.setStatus(warn(`Could not write the diagnostics log (${detail}).`));
@@ -9836,7 +10205,7 @@ var workbenchCommands = [
9836
10205
  {
9837
10206
  name: "whats-new",
9838
10207
  summary: "Show what changed in this build",
9839
- aliases: ["changelog"],
10208
+ aliases: ["changelog", "release-notes"],
9840
10209
  run: runChangelog
9841
10210
  },
9842
10211
  {
@@ -9862,7 +10231,7 @@ var workbenchCommands = [
9862
10231
  // src/console/slash/builtins.ts
9863
10232
  var PROJECT_DIR = ".indusagi";
9864
10233
  function dynamicRoots() {
9865
- const home = homedir3();
10234
+ const home = homedir2();
9866
10235
  const cwd = process.cwd();
9867
10236
  return {
9868
10237
  skills: [
@@ -10000,7 +10369,7 @@ function readKey(input, key) {
10000
10369
  case "n":
10001
10370
  return { verb: "flow:cycleModel" };
10002
10371
  case "p":
10003
- return { verb: "flow:cycleModel" };
10372
+ return { verb: "flow:cycleModel", direction: -1 };
10004
10373
  case "t":
10005
10374
  return { verb: "view:toggleReasoning" };
10006
10375
  case "v":
@@ -10025,15 +10394,15 @@ function readKey(input, key) {
10025
10394
  }
10026
10395
  if (key.escape) return { verb: "flow:dismiss" };
10027
10396
  if (key.tab) return key.shift ? { verb: "mode:cyclePermission" } : { verb: "flow:accept" };
10028
- if (key.backspace || key.delete || input === "\x7F" || input === "\b") {
10397
+ if (key.delete) return { verb: "edit:eraseNext" };
10398
+ if (key.backspace || input === "\x7F" || input === "\b") {
10029
10399
  return { verb: "edit:erasePrev" };
10030
10400
  }
10031
10401
  if (key.leftArrow) return { verb: "nav:left" };
10032
10402
  if (key.rightArrow) return { verb: "nav:right" };
10033
10403
  if (key.upArrow) return { verb: "nav:up" };
10034
10404
  if (key.downArrow) return { verb: "nav:down" };
10035
- if (key.pageUp) return { verb: "nav:home" };
10036
- if (key.pageDown) return { verb: "nav:end" };
10405
+ if (key.pageUp || key.pageDown) return NO_INTENT;
10037
10406
  return isPrintable(input) ? typed(input) : NO_INTENT;
10038
10407
  }
10039
10408
  var NO_CHORD = { armed: null };
@@ -10153,12 +10522,12 @@ function splitPathStem(stem) {
10153
10522
  const body = stem.slice(sigil.length);
10154
10523
  const slash = body.lastIndexOf("/");
10155
10524
  if (slash < 0) return { dir: ".", leaf: body, sigil };
10156
- return { dir: body.slice(0, slash) || ".", leaf: body.slice(slash + 1), sigil };
10525
+ return { dir: body.slice(0, slash) || "/", leaf: body.slice(slash + 1), sigil };
10157
10526
  }
10158
10527
  function completePath(stem, readDir) {
10159
10528
  const { dir, leaf, sigil } = splitPathStem(stem);
10160
10529
  const lowerLeaf = leaf.toLowerCase();
10161
- const prefix = dir === "." ? "" : `${dir}/`;
10530
+ const prefix = dir === "." ? "" : dir === "/" ? "/" : `${dir}/`;
10162
10531
  const matches = readDir(dir).filter((e) => e.name.toLowerCase().startsWith(lowerLeaf)).sort((a, b) => {
10163
10532
  if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
10164
10533
  return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
@@ -10194,6 +10563,7 @@ function applySuggestion(buffer, span, suggestion) {
10194
10563
  // src/console/input/dir-reader.ts
10195
10564
  import { readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
10196
10565
  import { resolve as resolve6 } from "node:path";
10566
+ import { homedir as homedir3 } from "node:os";
10197
10567
  function entryIsDir(base, name, isDirent, isSymlink) {
10198
10568
  if (isDirent) return true;
10199
10569
  if (!isSymlink) return false;
@@ -10205,7 +10575,8 @@ function entryIsDir(base, name, isDirent, isSymlink) {
10205
10575
  }
10206
10576
  function createDirReader(cwd = process.cwd()) {
10207
10577
  return (dirPath) => {
10208
- const target = resolve6(cwd, dirPath);
10578
+ const expanded = dirPath === "~" ? homedir3() : dirPath.startsWith("~/") ? resolve6(homedir3(), dirPath.slice(2)) : dirPath;
10579
+ const target = resolve6(cwd, expanded);
10209
10580
  try {
10210
10581
  const dirents = readdirSync3(target, { withFileTypes: true });
10211
10582
  return dirents.map((d) => ({
@@ -10220,11 +10591,12 @@ function createDirReader(cwd = process.cwd()) {
10220
10591
 
10221
10592
  // src/console/components/TerminalConsole.ts
10222
10593
  import { execFileSync } from "node:child_process";
10223
- import { homedir as homedir7 } from "node:os";
10224
- import React3, { useEffect as useEffect7, useMemo as useMemo2, useState as useState8 } from "indusagi/react-host";
10225
- import { Box as Box10, Text as Text10, useInput as useInput4 } from "indusagi/react-host/ink";
10594
+ import { homedir as homedir6 } from "node:os";
10595
+ import React4, { useEffect as useEffect9, useMemo as useMemo2, useState as useState10 } from "indusagi/react-host";
10596
+ import { Box as Box12, Text as Text11, useInput as useInput6 } from "indusagi/react-host/ink";
10597
+ import { useStdin, useStdout as useStdout2 } from "ink";
10226
10598
  import { MessageList, StreamingMarkdown, TaskPanel } from "indusagi/react-ink";
10227
- import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs10 } from "indusagi/react-host/jsx-runtime";
10599
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs10 } from "indusagi/react-host/jsx-runtime";
10228
10600
 
10229
10601
  // src/workspace/runtime-detect.ts
10230
10602
  var BUN_VIRTUAL_FS_MARKERS = ["$bunfs", "~BUN", "%7EBUN"];
@@ -10239,13 +10611,13 @@ function isPackagedBinary() {
10239
10611
  }
10240
10612
 
10241
10613
  // src/settings/manager.ts
10242
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
10243
- import { dirname as dirname4, join as join13 } from "node:path";
10614
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
10615
+ import { dirname as dirname5, join as join13 } from "node:path";
10244
10616
  function loadTier(filePath) {
10245
- if (!existsSync3(filePath)) return {};
10617
+ if (!existsSync2(filePath)) return {};
10246
10618
  let raw;
10247
10619
  try {
10248
- raw = readFileSync8(filePath, "utf-8");
10620
+ raw = readFileSync9(filePath, "utf-8");
10249
10621
  } catch {
10250
10622
  return {};
10251
10623
  }
@@ -10261,9 +10633,9 @@ function loadTier(filePath) {
10261
10633
  return { ...parsed };
10262
10634
  }
10263
10635
  function writeTier(filePath, tier) {
10264
- const dir = dirname4(filePath);
10265
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
10266
- writeFileSync5(filePath, `${JSON.stringify(tier, null, 2)}
10636
+ const dir = dirname5(filePath);
10637
+ if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
10638
+ writeFileSync6(filePath, `${JSON.stringify(tier, null, 2)}
10267
10639
  `, "utf-8");
10268
10640
  }
10269
10641
  var PreferenceStore = class _PreferenceStore {
@@ -10336,14 +10708,14 @@ var IMAGE_SNIFF_BYTES = PNG_SIGNATURE.length;
10336
10708
 
10337
10709
  // src/sessions/library.ts
10338
10710
  import {
10339
- mkdir as mkdir2,
10340
- readFile as readFile2,
10711
+ mkdir,
10712
+ readFile,
10341
10713
  readdir,
10342
10714
  rename,
10343
10715
  rm,
10344
10716
  stat
10345
10717
  } from "node:fs/promises";
10346
- import { basename as basename4, join as join14 } from "node:path";
10718
+ import { basename as basename5, join as join14 } from "node:path";
10347
10719
  var SESSION_FILE_EXT = ".ndjson";
10348
10720
  var PREVIEW_LIMIT = 72;
10349
10721
  var SessionLibrary = class {
@@ -10375,14 +10747,14 @@ var SessionLibrary = class {
10375
10747
  if (!await this.#missing(toPath)) {
10376
10748
  throw new Error(`SessionLibrary.rename: session "${toId}" already exists`);
10377
10749
  }
10378
- await mkdir2(this.#dir, { recursive: true });
10750
+ await mkdir(this.#dir, { recursive: true });
10379
10751
  await rename(fromPath, toPath);
10380
10752
  return toPath;
10381
10753
  }
10382
10754
  async remove(id) {
10383
- const path4 = this.pathOf(id);
10384
- if (await this.#missing(path4)) return false;
10385
- await rm(path4, { force: true });
10755
+ const path5 = this.pathOf(id);
10756
+ if (await this.#missing(path5)) return false;
10757
+ await rm(path5, { force: true });
10386
10758
  return true;
10387
10759
  }
10388
10760
  pathOf(id) {
@@ -10437,41 +10809,41 @@ var SessionLibrary = class {
10437
10809
  } catch {
10438
10810
  return [];
10439
10811
  }
10440
- return names.filter((name) => name.endsWith(SESSION_FILE_EXT)).map((name) => basename4(name, SESSION_FILE_EXT));
10812
+ return names.filter((name) => name.endsWith(SESSION_FILE_EXT)).map((name) => basename5(name, SESSION_FILE_EXT));
10441
10813
  }
10442
10814
  async #shallowRow(id) {
10443
- const path4 = this.pathOf(id);
10444
- const meta = await this.#fileMeta(path4);
10445
- return { id, path: path4, ...meta };
10815
+ const path5 = this.pathOf(id);
10816
+ const meta = await this.#fileMeta(path5);
10817
+ return { id, path: path5, ...meta };
10446
10818
  }
10447
10819
  async #deepRow(id) {
10448
- const path4 = this.pathOf(id);
10449
- const meta = await this.#fileMeta(path4);
10820
+ const path5 = this.pathOf(id);
10821
+ const meta = await this.#fileMeta(path5);
10450
10822
  const store = await this.open(id);
10451
- if (store === null) return { id, path: path4, ...meta };
10823
+ if (store === null) return { id, path: path5, ...meta };
10452
10824
  const branch = store.pathTo();
10453
10825
  const conversational = branch.filter(isConversational);
10454
10826
  const opener = conversational.find((entry) => entry.role === "user");
10455
10827
  const preview2 = opener ? previewOf(messageText(opener.content)) : void 0;
10456
10828
  return {
10457
10829
  id,
10458
- path: path4,
10830
+ path: path5,
10459
10831
  ...meta,
10460
10832
  messageCount: conversational.length,
10461
10833
  ...preview2 ? { preview: preview2, name: preview2 } : {}
10462
10834
  };
10463
10835
  }
10464
- async #fileMeta(path4) {
10836
+ async #fileMeta(path5) {
10465
10837
  try {
10466
- const info2 = await stat(path4);
10838
+ const info2 = await stat(path5);
10467
10839
  return { size: info2.size, lastModified: info2.mtimeMs };
10468
10840
  } catch {
10469
10841
  return {};
10470
10842
  }
10471
10843
  }
10472
- async #missing(path4) {
10844
+ async #missing(path5) {
10473
10845
  try {
10474
- await readFile2(path4);
10846
+ await readFile(path5);
10475
10847
  return false;
10476
10848
  } catch {
10477
10849
  return true;
@@ -10630,9 +11002,9 @@ function resolveSpecifierPath(specifier) {
10630
11002
  function buildAliasMap() {
10631
11003
  const map = {};
10632
11004
  for (const specifier of BUNDLED_NAMESPACES) {
10633
- const path4 = resolveSpecifierPath(specifier);
10634
- if (path4 !== void 0) {
10635
- map[specifier] = path4;
11005
+ const path5 = resolveSpecifierPath(specifier);
11006
+ if (path5 !== void 0) {
11007
+ map[specifier] = path5;
10636
11008
  }
10637
11009
  }
10638
11010
  return map;
@@ -10680,8 +11052,8 @@ function createJitiLoader(options = {}) {
10680
11052
  const base = options.base ?? process.cwd();
10681
11053
  const home = options.home ?? homedir4();
10682
11054
  return {
10683
- async load(path4) {
10684
- const absolute = resolvePath(path4, base, home);
11055
+ async load(path5) {
11056
+ const absolute = resolvePath(path5, base, home);
10685
11057
  const specifier = pathToFileURL(absolute).href;
10686
11058
  const imported = await jiti.import(specifier);
10687
11059
  const manifest = toManifest(imported);
@@ -10696,8 +11068,8 @@ function createJitiLoader(options = {}) {
10696
11068
  }
10697
11069
 
10698
11070
  // src/addons/manifest.ts
10699
- import { readFileSync as readFileSync9, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
10700
- import { basename as basename5, extname as extname3, isAbsolute as isAbsolute5, join as join15, resolve as resolve8 } from "node:path";
11071
+ import { readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
11072
+ import { basename as basename6, extname as extname3, isAbsolute as isAbsolute5, join as join15, resolve as resolve8 } from "node:path";
10701
11073
  var ENTRY_EXTENSIONS = [
10702
11074
  ".ts",
10703
11075
  ".tsx",
@@ -10714,9 +11086,9 @@ function isHidden(name) {
10714
11086
  function hasEntryExtension(name) {
10715
11087
  return ENTRY_EXTENSIONS.includes(extname3(name).toLowerCase());
10716
11088
  }
10717
- function entryKind(path4) {
11089
+ function entryKind(path5) {
10718
11090
  try {
10719
- const stat2 = statSync4(path4);
11091
+ const stat2 = statSync4(path5);
10720
11092
  if (stat2.isDirectory()) return "directory";
10721
11093
  if (stat2.isFile()) return "file";
10722
11094
  return void 0;
@@ -10737,7 +11109,7 @@ function readManifestEntry(packageDir) {
10737
11109
  return void 0;
10738
11110
  }
10739
11111
  try {
10740
- const parsed = JSON.parse(readFileSync9(manifestPath, "utf8"));
11112
+ const parsed = JSON.parse(readFileSync10(manifestPath, "utf8"));
10741
11113
  if (typeof parsed !== "object" || parsed === null) {
10742
11114
  return void 0;
10743
11115
  }
@@ -10769,13 +11141,13 @@ function resolveTopLevelEntry(dir, name) {
10769
11141
  if (isHidden(name)) {
10770
11142
  return void 0;
10771
11143
  }
10772
- const path4 = join15(dir, name);
10773
- const kind = entryKind(path4);
11144
+ const path5 = join15(dir, name);
11145
+ const kind = entryKind(path5);
10774
11146
  if (kind === "file") {
10775
- return hasEntryExtension(name) ? path4 : void 0;
11147
+ return hasEntryExtension(name) ? path5 : void 0;
10776
11148
  }
10777
11149
  if (kind === "directory") {
10778
- return resolvePackageEntry(path4);
11150
+ return resolvePackageEntry(path5);
10779
11151
  }
10780
11152
  return void 0;
10781
11153
  }
@@ -10790,9 +11162,9 @@ function discoverAddons(dir) {
10790
11162
  return entries.sort();
10791
11163
  }
10792
11164
  function deriveAddonId(entryPath) {
10793
- const file = basename5(entryPath);
11165
+ const file = basename6(entryPath);
10794
11166
  const stem = file.slice(0, file.length - extname3(file).length);
10795
- const label = INDEX_BASENAMES.includes(stem) ? basename5(join15(entryPath, "..")) : stem;
11167
+ const label = INDEX_BASENAMES.includes(stem) ? basename6(join15(entryPath, "..")) : stem;
10796
11168
  return addonId(label.length > 0 ? label : entryPath);
10797
11169
  }
10798
11170
  function addonsDirFor(workspace, dir = ADDONS_DIR) {
@@ -10809,12 +11181,12 @@ function discoverSources(discovery) {
10809
11181
  }
10810
11182
  const seen = /* @__PURE__ */ new Set();
10811
11183
  const sources = [];
10812
- for (const path4 of paths) {
10813
- if (seen.has(path4)) {
11184
+ for (const path5 of paths) {
11185
+ if (seen.has(path5)) {
10814
11186
  continue;
10815
11187
  }
10816
- seen.add(path4);
10817
- sources.push({ id: deriveAddonId(path4), path: path4 });
11188
+ seen.add(path5);
11189
+ sources.push({ id: deriveAddonId(path5), path: path5 });
10818
11190
  }
10819
11191
  return sources;
10820
11192
  }
@@ -11383,6 +11755,7 @@ function ndjsonStrategy() {
11383
11755
  },
11384
11756
  onSignal(signal, ctx) {
11385
11757
  const s = signal;
11758
+ if (s.kind === "tool_update") return;
11386
11759
  ctx.out.write(ctx.framer.encodeLine({ type: "signal", name: s.kind, body: s }));
11387
11760
  },
11388
11761
  finish(state, ctx) {
@@ -11489,16 +11862,16 @@ function createDialogBridge(deps) {
11489
11862
  ask(kind, payload, fallback) {
11490
11863
  const id = nextAskId(seq);
11491
11864
  const frame = { type: "ask", id, kind, payload };
11492
- return new Promise((resolve9) => {
11865
+ return new Promise((resolve11) => {
11493
11866
  const timer = setTimeout(() => {
11494
11867
  pending.delete(id);
11495
- resolve9(fallback);
11868
+ resolve11(fallback);
11496
11869
  }, deps.timings.dialogMs);
11497
11870
  if (typeof timer.unref === "function") {
11498
11871
  timer.unref();
11499
11872
  }
11500
11873
  pending.set(id, {
11501
- settle: (value) => resolve9(value),
11874
+ settle: (value) => resolve11(value),
11502
11875
  timer
11503
11876
  });
11504
11877
  emit(frame);
@@ -12127,6 +12500,7 @@ import { jsx as jsx2 } from "indusagi/react-host/jsx-runtime";
12127
12500
 
12128
12501
  // src/console/auth-status.ts
12129
12502
  import { useEffect, useState } from "indusagi/react-host";
12503
+ import { getEnvApiKey as getEnvApiKey3 } from "indusagi/ai";
12130
12504
  function useAuthenticatedProviders(services, refreshKey) {
12131
12505
  const [authed, setAuthed] = useState(null);
12132
12506
  useEffect(() => {
@@ -12135,10 +12509,14 @@ function useAuthenticatedProviders(services, refreshKey) {
12135
12509
  void (async () => {
12136
12510
  const found = /* @__PURE__ */ new Set();
12137
12511
  try {
12138
- for (const provider of services.listLoginProviders()) {
12512
+ const providers = /* @__PURE__ */ new Set([
12513
+ ...services.listLoginProviders().map((provider) => provider.id),
12514
+ ...services.conductor.availableModels().map((model) => model.provider)
12515
+ ]);
12516
+ for (const provider of providers) {
12139
12517
  try {
12140
- if ((await services.vault.listAccounts(provider.id)).length > 0) {
12141
- found.add(provider.id);
12518
+ if (getEnvApiKey3(provider) || (await services.vault.listAccounts(provider)).length > 0) {
12519
+ found.add(provider);
12142
12520
  }
12143
12521
  } catch {
12144
12522
  }
@@ -12156,15 +12534,12 @@ function useAuthenticatedProviders(services, refreshKey) {
12156
12534
  return () => {
12157
12535
  live3 = false;
12158
12536
  };
12159
- }, [refreshKey]);
12537
+ }, [services, refreshKey]);
12160
12538
  return authed;
12161
12539
  }
12162
12540
 
12163
12541
  // src/console/overlays/boards.ts
12164
12542
  import chalk2 from "chalk";
12165
- import { readFileSync as readFileSync10 } from "node:fs";
12166
- import { homedir as homedir5 } from "node:os";
12167
- import { join as join16 } from "node:path";
12168
12543
  import { useEffect as useEffect2, useMemo, useState as useState2 } from "indusagi/react-host";
12169
12544
  import { Box, Text, useInput } from "indusagi/react-host/ink";
12170
12545
  import {
@@ -12172,28 +12547,6 @@ import {
12172
12547
  wrapSelectionIndex
12173
12548
  } from "indusagi/react-ink";
12174
12549
  import { Fragment, jsx, jsxs } from "indusagi/react-host/jsx-runtime";
12175
- function readServerTierInfo() {
12176
- let signedIn = false;
12177
- try {
12178
- const file = join16(homedir5(), ".indusagi", "agent", "server-token.json");
12179
- const token = JSON.parse(readFileSync10(file, "utf8"));
12180
- signedIn = typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1e3;
12181
- } catch {
12182
- signedIn = false;
12183
- }
12184
- const localKeys = /* @__PURE__ */ new Set();
12185
- try {
12186
- const file = join16(homedir5(), ".indusagi", "agent", "auth.json");
12187
- const vault = JSON.parse(readFileSync10(file, "utf8"));
12188
- for (const [provider, entry] of Object.entries(vault)) {
12189
- if (entry !== null && typeof entry === "object" && Object.keys(entry).length > 0) {
12190
- localKeys.add(provider);
12191
- }
12192
- }
12193
- } catch {
12194
- }
12195
- return { signedIn, localKeys };
12196
- }
12197
12550
  function truncate(text, width) {
12198
12551
  if (text.length <= width) return text;
12199
12552
  if (width <= 1) return text.slice(0, width);
@@ -12290,6 +12643,7 @@ function nextValue(values, current, direction) {
12290
12643
  return values[(at + direction + values.length) % values.length];
12291
12644
  }
12292
12645
  function SettingsBoard({ theme, items, onClose }) {
12646
+ const [error, setError] = useState2();
12293
12647
  const [query, setQuery] = useState2("");
12294
12648
  const [selectedIndex, setSelectedIndex] = useState2(0);
12295
12649
  const filtered = useMemo(
@@ -12306,7 +12660,15 @@ function SettingsBoard({ theme, items, onClose }) {
12306
12660
  const item = filtered[selectedIndex];
12307
12661
  if (!item) return;
12308
12662
  const value = nextValue(item.values, item.value, direction);
12309
- if (value && value !== item.value) void item.onChange(value);
12663
+ if (value && value !== item.value) {
12664
+ setError(void 0);
12665
+ const failed = (error2) => setError(error2 instanceof Error ? error2.message : String(error2));
12666
+ try {
12667
+ void Promise.resolve(item.onChange(value)).catch(failed);
12668
+ } catch (error2) {
12669
+ failed(error2);
12670
+ }
12671
+ }
12310
12672
  };
12311
12673
  useInput((input, key) => {
12312
12674
  if (key.escape) return onClose();
@@ -12321,7 +12683,7 @@ function SettingsBoard({ theme, items, onClose }) {
12321
12683
  const { start, budget, below } = windowAround(filtered.length, selectedIndex);
12322
12684
  const visible = filtered.slice(start, start + budget);
12323
12685
  const selected = filtered[selectedIndex];
12324
- const detail = selected ? `${selected.description} \xB7 ${selected.values.join(" / ")}` : void 0;
12686
+ const detail = error ?? (selected ? `${selected.description} \xB7 ${selected.values.join(" / ")}` : void 0);
12325
12687
  const list = items.length === 0 ? jsx(Text, { children: theme.muted("No settings are available.") }) : filtered.length === 0 ? jsx(Text, { children: theme.muted("No settings matched the search.") }) : jsx(Box, {
12326
12688
  flexDirection: "column",
12327
12689
  children: visible.map((item, index) => {
@@ -12361,12 +12723,7 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
12361
12723
  const [query, setQuery] = useState2(search ?? "");
12362
12724
  const [selectedIndex, setSelectedIndex] = useState2(0);
12363
12725
  useEffect2(() => setQuery(search ?? ""), [search]);
12364
- const serverTier = useMemo(readServerTierInfo, []);
12365
- const isServerTier = (provider) => serverTier.signedIn && isGatewayEligibleProvider(provider) && !serverTier.localKeys.has(provider);
12366
- const available = useMemo(
12367
- () => models.filter((model) => serverTier.localKeys.has(model.provider) || isServerTier(model.provider)),
12368
- [models, serverTier]
12369
- );
12726
+ const available = models;
12370
12727
  const filtered = useMemo(() => available.filter((model) => modelMatches(model, query)), [available, query]);
12371
12728
  useEffect2(() => setSelectedIndex(0), [query]);
12372
12729
  useEffect2(() => {
@@ -12402,10 +12759,10 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
12402
12759
  jsx(Box, { width: 2, children: jsx(Text, { children: isSelected ? theme.color("accent", "\u203A") : " " }) }),
12403
12760
  jsx(Text, { bold: isSelected, children: theme.color("text", model.id) }),
12404
12761
  model.name ? jsx(Text, { children: theme.dim(` ${model.name}`) }) : null,
12405
- isServerTier(model.provider) ? jsx(Text, { children: theme.color("accent", " \u2AE1 server") }) : null
12762
+ jsx(Text, { children: theme.dim(` ${model.provider}`) })
12406
12763
  ]
12407
12764
  },
12408
- model.id
12765
+ `${model.provider}/${model.id}`
12409
12766
  );
12410
12767
  })
12411
12768
  });
@@ -12422,6 +12779,19 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
12422
12779
 
12423
12780
  // src/console/overlays/pickers.ts
12424
12781
  var TOGGLE_VALUES = ["on", "off"];
12782
+ function saveSetting(settings, key, value) {
12783
+ const previous = settings.get(key);
12784
+ try {
12785
+ settings.set(key, value);
12786
+ settings.save();
12787
+ } catch (error) {
12788
+ settings.set(key, previous);
12789
+ throw error;
12790
+ }
12791
+ }
12792
+ function reportError(dispatch2, error) {
12793
+ dispatch2({ type: "status:set", status: { kind: "error", text: error instanceof Error ? error.message : String(error) } });
12794
+ }
12425
12795
  function refToModel(ref) {
12426
12796
  return {
12427
12797
  id: ref.id,
@@ -12444,7 +12814,7 @@ function listModels(services) {
12444
12814
  }
12445
12815
  }
12446
12816
  function ModelPicker(props) {
12447
- const { services, theme, closeModal } = props;
12817
+ const { services, theme, closeModal, dispatch: dispatch2 } = props;
12448
12818
  const authed = useAuthenticatedProviders(services);
12449
12819
  if (!services) return null;
12450
12820
  const all = listModels(services);
@@ -12455,7 +12825,8 @@ function ModelPicker(props) {
12455
12825
  onSelect: (model) => {
12456
12826
  try {
12457
12827
  services.conductor.selectModel(model.id);
12458
- } catch {
12828
+ } catch (error) {
12829
+ reportError(dispatch2, error);
12459
12830
  }
12460
12831
  closeModal();
12461
12832
  },
@@ -12476,8 +12847,7 @@ function ScopedModelPicker(props) {
12476
12847
  useEffect3(() => {
12477
12848
  if (!services || payload.intent !== "reset") return;
12478
12849
  try {
12479
- services.settings.set("enabledModels", []);
12480
- services.settings.save();
12850
+ saveSetting(services.settings, "enabledModels", []);
12481
12851
  dispatch2({
12482
12852
  type: "status:set",
12483
12853
  status: { kind: "info", text: "Per-scope model overrides cleared." }
@@ -12507,9 +12877,9 @@ function ScopedModelPicker(props) {
12507
12877
  try {
12508
12878
  const all = models.map((model) => model.id);
12509
12879
  const next = ids.length === all.length ? [] : ids;
12510
- services.settings.set("enabledModels", next);
12511
- services.settings.save();
12512
- } catch {
12880
+ saveSetting(services.settings, "enabledModels", next);
12881
+ } catch (error) {
12882
+ reportError(dispatch2, error);
12513
12883
  }
12514
12884
  closeModal();
12515
12885
  },
@@ -12536,11 +12906,11 @@ function ThemePicker(props) {
12536
12906
  themes: [...THEME_SCHEMES],
12537
12907
  onSelect: (name) => {
12538
12908
  if (isThemeScheme(name)) {
12539
- dispatch2({ type: "scheme:set", scheme: name });
12540
12909
  try {
12541
- services.settings.set("colourScheme", name);
12542
- services.settings.save();
12543
- } catch {
12910
+ saveSetting(services.settings, "colourScheme", name);
12911
+ dispatch2({ type: "scheme:set", scheme: name });
12912
+ } catch (error) {
12913
+ reportError(dispatch2, error);
12544
12914
  }
12545
12915
  }
12546
12916
  closeModal();
@@ -12562,9 +12932,8 @@ function SettingsPicker(props) {
12562
12932
  value: onOff(settings.get(key)),
12563
12933
  values: [...TOGGLE_VALUES],
12564
12934
  onChange: (value) => {
12565
- settings.set(key, value === "on");
12566
- settings.save();
12567
- live3?.();
12935
+ saveSetting(settings, key, value === "on");
12936
+ live3?.(value === "on");
12568
12937
  bump();
12569
12938
  }
12570
12939
  });
@@ -12576,8 +12945,7 @@ function SettingsPicker(props) {
12576
12945
  values: [...DELIVERY_MODES],
12577
12946
  onChange: (value) => {
12578
12947
  if (isDeliveryMode(value)) {
12579
- settings.set(key, value);
12580
- settings.save();
12948
+ saveSetting(settings, key, value);
12581
12949
  }
12582
12950
  bump();
12583
12951
  }
@@ -12591,16 +12959,15 @@ function SettingsPicker(props) {
12591
12959
  value: settings.get("colourScheme"),
12592
12960
  values: [...THEME_SCHEMES],
12593
12961
  onChange: (value) => {
12594
- settings.set("colourScheme", value);
12595
- settings.save();
12962
+ saveSetting(settings, "colourScheme", value);
12596
12963
  if (isThemeScheme(value)) dispatch2({ type: "scheme:set", scheme: value });
12597
12964
  bump();
12598
12965
  }
12599
12966
  });
12600
- items.push(toggleRow("show-images", "showImages", "Show images", "Render inline image content in the transcript.", () => dispatch2({ type: "toggle:images" })));
12967
+ items.push(toggleRow("show-images", "showImages", "Show images", "Render inline image content in the transcript.", (value) => dispatch2({ type: "display:set", key: "showImages", value })));
12601
12968
  items.push(toggleRow("image-auto-resize", "imageAutoResize", "Auto-resize images", "Shrink oversized images to fit before handing them to a provider."));
12602
12969
  items.push(toggleRow("block-images", "blockImages", "Block images", "Withhold image content from providers entirely."));
12603
- items.push(toggleRow("show-reasoning", "showReasoning", "Show reasoning", "Stream the model's reasoning / thinking text as it arrives.", () => dispatch2({ type: "toggle:reasoning" })));
12970
+ items.push(toggleRow("show-reasoning", "showReasoning", "Show reasoning", "Stream the model's reasoning / thinking text as it arrives.", (value) => dispatch2({ type: "display:set", key: "showReasoning", value })));
12604
12971
  items.push(toggleRow("skill-commands", "enableSkillCommands", "Skill commands", "Surface discovered skills as their own slash commands."));
12605
12972
  items.push(deliveryRow("steering-mode", "steeringMode", "Steering mode", "Whether queued steering corrections release one at a time or all at once."));
12606
12973
  items.push(deliveryRow("follow-up-mode", "followUpMode", "Follow-up mode", "Whether queued follow-up prompts release one at a time or all at once."));
@@ -12609,7 +12976,6 @@ function SettingsPicker(props) {
12609
12976
  items.push(toggleRow("quiet-startup", "quietStartup", "Quiet startup", "Suppress the banner and tips shown on a normal interactive launch."));
12610
12977
  items.push(toggleRow("logo-sweep", "logoSweep", "Logo sweep", "Tint the startup wordmark and emblem along a static colour gradient."));
12611
12978
  items.push(toggleRow("reduced-motion", "reducedMotion", "Reduced motion", "Suppress motion-flavoured flourishes such as the logo colour sweep."));
12612
- items.push(toggleRow("hardware-cursor", "showHardwareCursor", "Hardware cursor", "Reveal the terminal's own cursor instead of the software-drawn caret."));
12613
12979
  items.push({
12614
12980
  id: "editor-padding",
12615
12981
  label: "Editor padding",
@@ -12619,8 +12985,7 @@ function SettingsPicker(props) {
12619
12985
  onChange: (value) => {
12620
12986
  const parsed = Number.parseInt(value, 10);
12621
12987
  if (Number.isFinite(parsed)) {
12622
- settings.set("editorPaddingX", parsed);
12623
- settings.save();
12988
+ saveSetting(settings, "editorPaddingX", parsed);
12624
12989
  }
12625
12990
  bump();
12626
12991
  }
@@ -12874,7 +13239,7 @@ function SignInDialog(props) {
12874
13239
  var SERVER_TIER_ACCOUNT_ID = "server-session";
12875
13240
  var SERVER_TIER_PROVIDER_ID2 = "indus-server";
12876
13241
  function SignOutDialog(props) {
12877
- const { services, closeModal } = props;
13242
+ const { services, closeModal, dispatch: dispatch2 } = props;
12878
13243
  const [accounts, setAccounts] = useState5([]);
12879
13244
  useEffect5(() => {
12880
13245
  let live3 = true;
@@ -12926,7 +13291,10 @@ function SignOutDialog(props) {
12926
13291
  } else {
12927
13292
  await services.vault.remove(account.provider, account.accountId);
12928
13293
  }
12929
- } catch {
13294
+ await services.refreshGatewayRouting();
13295
+ dispatch2({ type: "status:set", status: { kind: "info", text: "Signed out. Model routing refreshed." } });
13296
+ } catch (error) {
13297
+ dispatch2({ type: "status:set", status: { kind: "error", text: error instanceof Error ? error.message : "Sign-out failed." } });
12930
13298
  }
12931
13299
  closeModal();
12932
13300
  }
@@ -12964,15 +13332,24 @@ function OAuthFlowDialog(props) {
12964
13332
  })();
12965
13333
  const [state, setState] = useState5(() => seedOAuthState(providerId, providerName, mode));
12966
13334
  const pending = useRef(null);
13335
+ const cancelPrompt = useRef(null);
13336
+ const mounted = useRef(true);
13337
+ const saving = useRef(false);
13338
+ useEffect5(() => () => {
13339
+ mounted.current = false;
13340
+ }, []);
12967
13341
  const merge = (next) => setState((prev) => ({ ...prev, ...next }));
12968
13342
  useEffect5(() => {
12969
13343
  if (mode !== "oauth") return;
12970
13344
  let live3 = true;
13345
+ const controller = new AbortController();
12971
13346
  const safeMerge = (next) => {
12972
13347
  if (live3) merge(next);
12973
13348
  };
12974
13349
  const callbacks = {
13350
+ signal: controller.signal,
12975
13351
  onAuth: (info2) => {
13352
+ if (!live3) return;
12976
13353
  safeMerge({ authInfo: info2, progress: "Opening your browser to sign in\u2026" });
12977
13354
  void services.openLoginUrl(info2.url).then((opened) => {
12978
13355
  safeMerge({
@@ -12980,12 +13357,28 @@ function OAuthFlowDialog(props) {
12980
13357
  });
12981
13358
  });
12982
13359
  },
12983
- onPrompt: (prompt) => new Promise((resolve9) => {
12984
- pending.current = resolve9;
13360
+ onPrompt: (prompt) => new Promise((resolve11, reject) => {
13361
+ if (!live3) {
13362
+ reject(new DOMException("Sign-in cancelled", "AbortError"));
13363
+ return;
13364
+ }
13365
+ cancelPrompt.current = () => reject(new DOMException("Sign-in cancelled", "AbortError"));
13366
+ pending.current = (value) => {
13367
+ cancelPrompt.current = null;
13368
+ resolve11(value);
13369
+ };
12985
13370
  safeMerge({ prompt, inputLabel: prompt.message, inputValue: "" });
12986
13371
  }),
12987
- onManualCodeInput: () => new Promise((resolve9) => {
12988
- pending.current = resolve9;
13372
+ onManualCodeInput: () => new Promise((resolve11, reject) => {
13373
+ if (!live3) {
13374
+ reject(new DOMException("Sign-in cancelled", "AbortError"));
13375
+ return;
13376
+ }
13377
+ cancelPrompt.current = () => reject(new DOMException("Sign-in cancelled", "AbortError"));
13378
+ pending.current = (value) => {
13379
+ cancelPrompt.current = null;
13380
+ resolve11(value);
13381
+ };
12989
13382
  safeMerge({
12990
13383
  prompt: { message: "Paste the authorization code or complete redirect URL:" },
12991
13384
  inputLabel: "Authorization code or redirect URL",
@@ -12999,7 +13392,9 @@ function OAuthFlowDialog(props) {
12999
13392
  void (async () => {
13000
13393
  try {
13001
13394
  await services.startOAuthLogin(providerId, callbacks, services.vault);
13395
+ if (!live3) return;
13002
13396
  await services.refreshGatewayRouting();
13397
+ if (!live3) return;
13003
13398
  selectProviderModel(services, providerId);
13004
13399
  if (live3) {
13005
13400
  dispatch2({
@@ -13014,15 +13409,23 @@ function OAuthFlowDialog(props) {
13014
13409
  })();
13015
13410
  return () => {
13016
13411
  live3 = false;
13412
+ controller.abort();
13413
+ cancelPrompt.current?.();
13414
+ cancelPrompt.current = null;
13415
+ pending.current = null;
13017
13416
  };
13018
13417
  }, []);
13019
13418
  const saveApiKey = () => {
13020
13419
  const key = state.inputValue.trim();
13021
- if (key.length === 0) return;
13420
+ if (key.length === 0 || saving.current) return;
13421
+ saving.current = true;
13022
13422
  const account = state.accountId ?? "default";
13023
13423
  void (async () => {
13024
13424
  try {
13025
13425
  await services.vault.putApiKey(providerId, account, key, true);
13426
+ if (!mounted.current) return;
13427
+ await services.refreshGatewayRouting();
13428
+ if (!mounted.current) return;
13026
13429
  selectProviderModel(services, providerId);
13027
13430
  dispatch2({
13028
13431
  type: "status:set",
@@ -13045,12 +13448,12 @@ function OAuthFlowDialog(props) {
13045
13448
  saveApiKey();
13046
13449
  return;
13047
13450
  }
13048
- const resolve9 = pending.current;
13049
- if (resolve9 === null) return;
13451
+ const resolve11 = pending.current;
13452
+ if (resolve11 === null) return;
13050
13453
  pending.current = null;
13051
13454
  const value = state.inputValue;
13052
13455
  merge({ inputValue: "", prompt: void 0 });
13053
- resolve9(value);
13456
+ resolve11(value);
13054
13457
  },
13055
13458
  onClose: closeModal
13056
13459
  });
@@ -13205,12 +13608,12 @@ function summarizeInput(input) {
13205
13608
  }
13206
13609
  function ApprovalDialog(props) {
13207
13610
  const { payload, closeModal } = props;
13208
- const { request, resolve: resolve9 } = payload;
13611
+ const { request, resolve: resolve11 } = payload;
13209
13612
  let settled = false;
13210
13613
  const settle = (choice) => {
13211
13614
  if (settled) return;
13212
13615
  settled = true;
13213
- resolve9(choice);
13616
+ resolve11(choice);
13214
13617
  closeModal();
13215
13618
  };
13216
13619
  const args = summarizeInput(request.input);
@@ -13247,8 +13650,18 @@ function ApprovalOverlays(props) {
13247
13650
  }
13248
13651
 
13249
13652
  // src/console/overlays/host.ts
13653
+ import { Box as Box4, Text as Text4, useInput as useInput3 } from "indusagi/react-host/ink";
13654
+ function UnavailableOverlay({ closeModal }) {
13655
+ useInput3((_input, key) => {
13656
+ if (key.escape || key.return) closeModal();
13657
+ });
13658
+ return jsx6(Box4, { children: jsx6(Text4, { children: "This dialog is unavailable in this host. Esc / Enter to return." }) });
13659
+ }
13250
13660
  function OverlayHost(props) {
13251
13661
  if (props.modal.kind === "none") return null;
13662
+ if (!props.services && props.modal.kind !== "approval" && props.modal.kind !== "plugin") {
13663
+ return jsx6(UnavailableOverlay, { closeModal: props.closeModal });
13664
+ }
13252
13665
  const forwarded = {
13253
13666
  modal: props.modal,
13254
13667
  services: props.services,
@@ -13267,31 +13680,31 @@ function OverlayHost(props) {
13267
13680
  }
13268
13681
 
13269
13682
  // src/console/startup.ts
13270
- import { existsSync as existsSync4, readFileSync as readFileSync11 } from "node:fs";
13271
- import { join as join17 } from "node:path";
13683
+ import { existsSync as existsSync3, readFileSync as readFileSync11 } from "node:fs";
13684
+ import { join as join16 } from "node:path";
13272
13685
  var PROFILE_DIR = ".indusagi";
13273
13686
  var CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md"];
13274
13687
  function skillRoots(cwd, home) {
13275
13688
  return [
13276
- { dir: join17(cwd, PROFILE_DIR, "skills"), origin: "project" },
13277
- { dir: join17(home, PROFILE_DIR, "skills"), origin: "user" }
13689
+ { dir: join16(cwd, PROFILE_DIR, "skills"), origin: "project" },
13690
+ { dir: join16(home, PROFILE_DIR, "skills"), origin: "user" }
13278
13691
  ];
13279
13692
  }
13280
13693
  function macroRoots(cwd, home) {
13281
13694
  return [
13282
- { dir: join17(cwd, PROFILE_DIR, "commands"), origin: "project", label: "project" },
13283
- { dir: join17(home, PROFILE_DIR, "commands"), origin: "user", label: "user" }
13695
+ { dir: join16(cwd, PROFILE_DIR, "commands"), origin: "project", label: "project" },
13696
+ { dir: join16(home, PROFILE_DIR, "commands"), origin: "user", label: "user" }
13284
13697
  ];
13285
13698
  }
13286
- function shortenPath2(path4, cwd, home) {
13287
- if (cwd.length > 0 && path4.startsWith(cwd)) {
13288
- const rest = path4.slice(cwd.length).replace(/^[/\\]+/, "");
13699
+ function shortenPath2(path5, cwd, home) {
13700
+ if (cwd.length > 0 && path5.startsWith(cwd)) {
13701
+ const rest = path5.slice(cwd.length).replace(/^[/\\]+/, "");
13289
13702
  return rest.length > 0 ? `./${rest}` : ".";
13290
13703
  }
13291
- if (home.length > 0 && path4.startsWith(home)) {
13292
- return `~${path4.slice(home.length)}`;
13704
+ if (home.length > 0 && path5.startsWith(home)) {
13705
+ return `~${path5.slice(home.length)}`;
13293
13706
  }
13294
- return path4;
13707
+ return path5;
13295
13708
  }
13296
13709
  var MAX_SECTION_LINES = 6;
13297
13710
  function capLines(lines) {
@@ -13310,10 +13723,10 @@ function gatherContext(cwd, home) {
13310
13723
  for (const root of [cwd, home]) {
13311
13724
  if (root.length === 0) continue;
13312
13725
  for (const name of CONTEXT_FILES) {
13313
- const path4 = join17(root, name);
13314
- if (seen.has(path4)) continue;
13315
- seen.add(path4);
13316
- if (existsSync4(path4)) out2.push(shortenPath2(path4, cwd, home));
13726
+ const path5 = join16(root, name);
13727
+ if (seen.has(path5)) continue;
13728
+ seen.add(path5);
13729
+ if (existsSync3(path5)) out2.push(shortenPath2(path5, cwd, home));
13317
13730
  }
13318
13731
  }
13319
13732
  return out2;
@@ -13377,12 +13790,12 @@ function mascotRows(face) {
13377
13790
  ];
13378
13791
  }
13379
13792
  var TIPS = [
13380
- "Run /init to create a CLAUDE.md file with instructions for IndusCode",
13793
+ "Run /help to explore commands and configured skill helpers",
13381
13794
  'Ask in plain English \u2014 try "fix the failing tests" or "explain src/agent.ts"',
13382
- "Shift+Tab cycles permission modes: auto \u2192 plan \u2192 accept-edits",
13795
+ "Shift+Tab cycles: default \u2192 accept-edits \u2192 plan \u2192 bypass",
13383
13796
  "Type @ to attach a file, or paste an image straight into the prompt",
13384
13797
  "Run /status to check your model, account, and connectivity",
13385
- "Use /skills to discover specialized helpers for the task at hand"
13798
+ "Use /help to discover specialized commands for the task at hand"
13386
13799
  ];
13387
13800
  var WHATS_NEW = [
13388
13801
  "Ctrl+X runs your newest queued message NOW \u2014 the current run stops cleanly",
@@ -13402,148 +13815,21 @@ function pickWelcomeVariant(seed) {
13402
13815
  var PINNED_WELCOME_SEED = 12;
13403
13816
 
13404
13817
  // src/console/components/AgentsView.ts
13405
- import { useState as useState6 } from "indusagi/react-host";
13406
- import { Box as Box4, Text as Text4, useInput as useInput3 } from "indusagi/react-host/ink";
13818
+ import { useState as useState7 } from "indusagi/react-host";
13819
+ import { Box as Box6, Text as Text6, useInput as useInput4 } from "indusagi/react-host/ink";
13407
13820
  import { wrapSelectionIndex as wrapSelectionIndex2 } from "indusagi/react-ink";
13821
+ import { jsx as jsx8, jsxs as jsxs5 } from "indusagi/react-host/jsx-runtime";
13822
+
13823
+ // src/console/components/BackgroundAgents.ts
13824
+ import { Box as Box5, Text as Text5 } from "indusagi/react-host/ink";
13825
+ import { useEffect as useEffect6, useState as useState6 } from "indusagi/react-host";
13826
+ import { splitAssistantMessage } from "indusagi/react-ink";
13408
13827
  import { jsx as jsx7, jsxs as jsxs4 } from "indusagi/react-host/jsx-runtime";
13409
- function formatTokens2(tokens) {
13410
- return tokens >= 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : String(tokens);
13411
- }
13412
- function clampLine(text, width) {
13413
- const flat = text.replace(/\s+/g, " ").trim();
13414
- if (flat.length <= width) return flat;
13415
- if (width <= 1) return flat.slice(0, width);
13416
- return `${flat.slice(0, width - 1)}\u2026`;
13417
- }
13418
- function runStatusLine(run, now, tokens) {
13419
- let head;
13420
- if (run.status === "error") head = "failed";
13421
- else if (run.status === "done") head = "done";
13422
- else if (run.startedAt > 0 && now >= run.startedAt) {
13423
- head = `${Math.max(0, Math.round((now - run.startedAt) / 1e3))}s`;
13424
- } else head = "working";
13425
- const tok = tokens !== void 0 && tokens > 0 ? ` \xB7 \u2193 ${formatTokens2(tokens)} tokens` : "";
13426
- return `${head}${tok}`;
13427
- }
13428
- var NAME_WIDTH = 12;
13429
- function ActivityRow({ theme, line }) {
13430
- if (line.tone === "tool") {
13431
- return jsxs4(Box4, {
13432
- children: [
13433
- jsx7(Text4, { children: theme.color("success", "\u25CF ") }),
13434
- jsx7(Text4, { children: theme.color("text", clampLine(line.text, 96)) })
13435
- ]
13436
- });
13437
- }
13438
- if (line.tone === "result") {
13439
- return jsx7(Box4, { marginLeft: 2, children: jsx7(Text4, { children: theme.dim(`\u2514 ${clampLine(line.text, 92)}`) }) });
13440
- }
13441
- return jsxs4(Box4, {
13442
- children: [
13443
- jsx7(Text4, { children: theme.color("accent", "\u25CF ") }),
13444
- jsx7(Text4, { children: theme.color("text", clampLine(line.text, 96)) })
13445
- ]
13446
- });
13447
- }
13448
- function AgentsView({ theme, runs, activity, tokens, now, onExit }) {
13449
- const clock = now ?? Date.now();
13450
- const [selectedIndex, setSelectedIndex] = useState6(runs.length > 0 ? 1 : 0);
13451
- const rowCount = runs.length + 1;
13452
- const index = Math.min(selectedIndex, rowCount - 1);
13453
- const viewed = index > 0 ? runs[index - 1] : void 0;
13454
- useInput3((input, key) => {
13455
- if (key.escape || input === "x") return onExit();
13456
- if (key.upArrow) return setSelectedIndex((i) => wrapSelectionIndex2(Math.min(i, rowCount - 1), -1, rowCount));
13457
- if (key.downArrow) return setSelectedIndex((i) => wrapSelectionIndex2(Math.min(i, rowCount - 1), 1, rowCount));
13458
- if (key.return && index === 0) onExit();
13459
- });
13460
- const lines = viewed ? activity[viewed.id] ?? [] : [];
13461
- return jsxs4(Box4, {
13462
- flexDirection: "column",
13463
- marginTop: 1,
13464
- children: [
13465
- viewed ? jsxs4(Box4, {
13466
- flexDirection: "column",
13467
- children: [
13468
- jsxs4(Box4, {
13469
- marginBottom: 1,
13470
- children: [
13471
- jsx7(Text4, { children: theme.color("accent", "\u2736 ") }),
13472
- jsx7(Text4, { bold: true, children: theme.color("accent", viewed.name) }),
13473
- jsx7(Text4, { children: theme.dim(` ${runStatusLine(viewed, clock, tokens[viewed.id])}`) })
13474
- ]
13475
- }),
13476
- lines.length === 0 ? jsx7(Text4, { children: theme.muted(" Waiting for the first step\u2026") }) : lines.map((line, i) => jsx7(ActivityRow, { theme, line }, i)),
13477
- jsx7(Box4, {
13478
- marginTop: 1,
13479
- children: jsx7(Text4, { wrap: "truncate", children: theme.muted(`\u2514 ${clampLine(viewed.objective, 100)}`) })
13480
- })
13481
- ]
13482
- }) : jsxs4(Box4, {
13483
- children: [
13484
- jsx7(Text4, { children: theme.muted("Select a sub-agent to view its live activity, or press Enter on ") }),
13485
- jsx7(Text4, { bold: true, children: theme.color("text", "main") }),
13486
- jsx7(Text4, { children: theme.muted(" to return.") })
13487
- ]
13488
- }),
13489
- jsxs4(Box4, {
13490
- flexDirection: "column",
13491
- marginTop: 1,
13492
- borderStyle: "single",
13493
- borderColor: theme.colors["borderMuted"],
13494
- borderBottom: false,
13495
- borderLeft: false,
13496
- borderRight: false,
13497
- paddingTop: 0,
13498
- children: [
13499
- jsxs4(Box4, {
13500
- children: [
13501
- jsx7(Text4, { children: index === 0 ? theme.color("accent", "\u203A ") : " " }),
13502
- jsx7(Text4, { children: index === 0 ? theme.color("text", "\u25CF ") : theme.dim("\u25CB ") }),
13503
- jsx7(Text4, { bold: index === 0, children: theme.color("text", "main") })
13504
- ]
13505
- }),
13506
- runs.map((run, i) => {
13507
- const selected = index === i + 1;
13508
- const dot = run.status === "running" ? theme.color("accent", "\u25D0") : run.status === "error" ? theme.color("error", "\u25CF") : theme.color("success", "\u25CF");
13509
- return jsxs4(
13510
- Box4,
13511
- {
13512
- children: [
13513
- jsx7(Text4, { children: selected ? theme.color("accent", "\u203A ") : " " }),
13514
- jsx7(Box4, { width: 2, flexShrink: 0, children: jsx7(Text4, { children: dot }) }),
13515
- jsx7(Box4, {
13516
- width: NAME_WIDTH,
13517
- flexShrink: 0,
13518
- children: jsx7(Text4, { bold: selected, children: theme.color("accent", clampLine(run.name, NAME_WIDTH - 1)) })
13519
- }),
13520
- jsx7(Box4, {
13521
- flexGrow: 1,
13522
- flexShrink: 1,
13523
- children: jsx7(Text4, { wrap: "truncate", children: theme.muted(clampLine(run.objective, 56)) })
13524
- }),
13525
- jsx7(Text4, { children: theme.dim(` ${runStatusLine(run, clock, tokens[run.id])}`) })
13526
- ]
13527
- },
13528
- run.id
13529
- );
13530
- })
13531
- ]
13532
- }),
13533
- jsx7(Box4, { marginTop: 1, children: jsx7(Text4, { children: theme.dim("\u2191/\u2193 select \xB7 Enter to view \xB7 Enter on main / Esc to return") }) })
13534
- ]
13535
- });
13536
- }
13537
-
13538
- // src/console/components/BackgroundAgents.ts
13539
- import { Box as Box5, Text as Text5 } from "indusagi/react-host/ink";
13540
- import { splitAssistantMessage } from "indusagi/react-ink";
13541
- import { jsx as jsx8, jsxs as jsxs5 } from "indusagi/react-host/jsx-runtime";
13542
- function readString2(args, key) {
13543
- const value = args[key];
13544
- if (typeof value !== "string") return void 0;
13545
- const trimmed = value.trim();
13546
- return trimmed.length > 0 ? trimmed : void 0;
13828
+ function readString2(args, key) {
13829
+ const value = args[key];
13830
+ if (typeof value !== "string") return void 0;
13831
+ const trimmed = value.trim();
13832
+ return trimmed.length > 0 ? trimmed : void 0;
13547
13833
  }
13548
13834
  function deriveAgentRuns(messages) {
13549
13835
  const runs = [];
@@ -13574,7 +13860,7 @@ function deriveAgentRuns(messages) {
13574
13860
  return runs.map((run) => ({ ...run, status: status.get(run.id) ?? run.status }));
13575
13861
  }
13576
13862
  var MAX_VISIBLE_RUNS = 8;
13577
- var NAME_WIDTH2 = 12;
13863
+ var NAME_WIDTH = 12;
13578
13864
  function clamp2(text, width) {
13579
13865
  if (text.length <= width) return text;
13580
13866
  if (width <= 1) return text.slice(0, width);
@@ -13584,7 +13870,7 @@ var OBJECTIVE_WIDTH = 64;
13584
13870
  function oneLine(text) {
13585
13871
  return text.replace(/\s+/g, " ").trim();
13586
13872
  }
13587
- function formatTokens3(tokens) {
13873
+ function formatTokens2(tokens) {
13588
13874
  if (tokens >= 1e3) return `${(tokens / 1e3).toFixed(1)}k`;
13589
13875
  return String(tokens);
13590
13876
  }
@@ -13596,65 +13882,206 @@ function statusText(run, now, tokens) {
13596
13882
  head = `${Math.max(0, Math.round((now - run.startedAt) / 1e3))}s`;
13597
13883
  } else head = "running";
13598
13884
  if (tokens !== void 0 && tokens > 0) {
13599
- return `${head} \xB7 \u2193 ${formatTokens3(tokens)} tokens`;
13885
+ return `${head} \xB7 \u2193 ${formatTokens2(tokens)} tokens`;
13600
13886
  }
13601
13887
  return head;
13602
13888
  }
13889
+ function useAgentClock(active, supplied) {
13890
+ const [now, setNow] = useState6(Date.now);
13891
+ useEffect6(() => {
13892
+ if (!active || supplied !== void 0) return;
13893
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
13894
+ return () => clearInterval(timer);
13895
+ }, [active, supplied]);
13896
+ return supplied ?? now;
13897
+ }
13603
13898
  function BackgroundAgents({ theme, runs, tokens, now }) {
13604
- const clock = now ?? Date.now();
13899
+ const clock = useAgentClock(runs.some((run) => run.status === "running"), now);
13605
13900
  const runningCount = runs.reduce((n, run) => run.status === "running" ? n + 1 : n, 0);
13606
13901
  if (runningCount === 0) return null;
13607
- const visible = runs.slice(-MAX_VISIBLE_RUNS);
13608
- return jsxs5(Box5, {
13902
+ const running = runs.filter((run) => run.status === "running");
13903
+ const recentDone = runs.filter((run) => run.status !== "running").slice(-Math.max(0, MAX_VISIBLE_RUNS - running.length));
13904
+ const visible = [...running, ...recentDone].slice(0, MAX_VISIBLE_RUNS);
13905
+ return jsxs4(Box5, {
13609
13906
  flexDirection: "column",
13610
13907
  marginTop: 1,
13611
13908
  children: [
13612
- jsxs5(Box5, {
13909
+ jsxs4(Box5, {
13613
13910
  children: [
13614
- jsx8(Text5, { children: theme.color("text", "\u25CF ") }),
13615
- jsx8(Text5, { bold: true, children: theme.color("text", "main") })
13911
+ jsx7(Text5, { children: theme.color("text", "\u25CF ") }),
13912
+ jsx7(Text5, { bold: true, children: theme.color("text", "main") })
13616
13913
  ]
13617
13914
  }),
13618
13915
  visible.map((run) => {
13619
13916
  const dot = run.status === "running" ? theme.color("accent", "\u25D0") : run.status === "error" ? theme.color("error", "\u25CF") : theme.color("success", "\u25CF");
13620
- return jsxs5(
13917
+ return jsxs4(
13621
13918
  Box5,
13622
13919
  {
13623
13920
  children: [
13624
- jsx8(Box5, { width: 2, flexShrink: 0, children: jsx8(Text5, { children: dot }) }),
13625
- jsx8(Box5, {
13626
- width: NAME_WIDTH2,
13921
+ jsx7(Box5, { width: 2, flexShrink: 0, children: jsx7(Text5, { children: dot }) }),
13922
+ jsx7(Box5, {
13923
+ width: NAME_WIDTH,
13627
13924
  flexShrink: 0,
13628
- children: jsx8(Text5, { bold: true, children: theme.color("accent", clamp2(run.name, NAME_WIDTH2 - 1)) })
13925
+ children: jsx7(Text5, { bold: true, children: theme.color("accent", clamp2(run.name, NAME_WIDTH - 1)) })
13629
13926
  }),
13630
- jsx8(Box5, {
13927
+ jsx7(Box5, {
13631
13928
  flexGrow: 1,
13632
13929
  flexShrink: 1,
13633
- children: jsx8(Text5, { wrap: "truncate", children: theme.muted(clamp2(oneLine(run.objective), OBJECTIVE_WIDTH)) })
13930
+ children: jsx7(Text5, { wrap: "truncate", children: theme.muted(clamp2(oneLine(run.objective), OBJECTIVE_WIDTH)) })
13634
13931
  }),
13635
- jsx8(Text5, { children: theme.dim(` ${statusText(run, clock, tokens?.[run.id])}`) })
13932
+ jsx7(Text5, { children: theme.dim(` ${statusText(run, clock, tokens?.[run.id])}`) })
13636
13933
  ]
13637
13934
  },
13638
13935
  run.id
13639
13936
  );
13640
13937
  }),
13641
- jsxs5(Box5, {
13938
+ jsxs4(Box5, {
13642
13939
  marginTop: 1,
13643
13940
  children: [
13644
- jsx8(Text5, { children: theme.color("accent", "\u2736 ") }),
13645
- jsx8(Text5, { children: theme.muted(`Waiting for ${runningCount} background agent${runningCount === 1 ? "" : "s"} to finish`) })
13941
+ jsx7(Text5, { children: theme.color("accent", "\u2736 ") }),
13942
+ jsx7(Text5, { children: theme.muted(`Waiting for ${runningCount} background agent${runningCount === 1 ? "" : "s"} to finish`) })
13646
13943
  ]
13647
13944
  })
13648
13945
  ]
13649
13946
  });
13650
13947
  }
13651
13948
 
13949
+ // src/console/components/AgentsView.ts
13950
+ function formatTokens3(tokens) {
13951
+ return tokens >= 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : String(tokens);
13952
+ }
13953
+ function clampLine(text, width) {
13954
+ const flat = text.replace(/\s+/g, " ").trim();
13955
+ if (flat.length <= width) return flat;
13956
+ if (width <= 1) return flat.slice(0, width);
13957
+ return `${flat.slice(0, width - 1)}\u2026`;
13958
+ }
13959
+ function runStatusLine(run, now, tokens) {
13960
+ let head;
13961
+ if (run.status === "error") head = "failed";
13962
+ else if (run.status === "done") head = "done";
13963
+ else if (run.startedAt > 0 && now >= run.startedAt) {
13964
+ head = `${Math.max(0, Math.round((now - run.startedAt) / 1e3))}s`;
13965
+ } else head = "working";
13966
+ const tok = tokens !== void 0 && tokens > 0 ? ` \xB7 \u2193 ${formatTokens3(tokens)} tokens` : "";
13967
+ return `${head}${tok}`;
13968
+ }
13969
+ var NAME_WIDTH2 = 12;
13970
+ function ActivityRow({ theme, line }) {
13971
+ if (line.tone === "tool") {
13972
+ return jsxs5(Box6, {
13973
+ children: [
13974
+ jsx8(Text6, { children: theme.color("success", "\u25CF ") }),
13975
+ jsx8(Text6, { children: theme.color("text", clampLine(line.text, 96)) })
13976
+ ]
13977
+ });
13978
+ }
13979
+ if (line.tone === "result") {
13980
+ return jsx8(Box6, { marginLeft: 2, children: jsx8(Text6, { children: theme.dim(`\u2514 ${clampLine(line.text, 92)}`) }) });
13981
+ }
13982
+ return jsxs5(Box6, {
13983
+ children: [
13984
+ jsx8(Text6, { children: theme.color("accent", "\u25CF ") }),
13985
+ jsx8(Text6, { children: theme.color("text", clampLine(line.text, 96)) })
13986
+ ]
13987
+ });
13988
+ }
13989
+ function AgentsView({ theme, runs, activity, tokens, now, onExit }) {
13990
+ const clock = useAgentClock(runs.some((run) => run.status === "running"), now);
13991
+ const [selectedIndex, setSelectedIndex] = useState7(runs.length > 0 ? 1 : 0);
13992
+ const rowCount = runs.length + 1;
13993
+ const index = Math.min(selectedIndex, rowCount - 1);
13994
+ const viewed = index > 0 ? runs[index - 1] : void 0;
13995
+ useInput4((input, key) => {
13996
+ if (key.escape || input === "x") return onExit();
13997
+ if (key.upArrow) return setSelectedIndex((i) => wrapSelectionIndex2(Math.min(i, rowCount - 1), -1, rowCount));
13998
+ if (key.downArrow) return setSelectedIndex((i) => wrapSelectionIndex2(Math.min(i, rowCount - 1), 1, rowCount));
13999
+ if (key.return && index === 0) onExit();
14000
+ });
14001
+ const lines = viewed ? activity[viewed.id] ?? [] : [];
14002
+ return jsxs5(Box6, {
14003
+ flexDirection: "column",
14004
+ marginTop: 1,
14005
+ children: [
14006
+ viewed ? jsxs5(Box6, {
14007
+ flexDirection: "column",
14008
+ children: [
14009
+ jsxs5(Box6, {
14010
+ marginBottom: 1,
14011
+ children: [
14012
+ jsx8(Text6, { children: theme.color("accent", "\u2736 ") }),
14013
+ jsx8(Text6, { bold: true, children: theme.color("accent", viewed.name) }),
14014
+ jsx8(Text6, { children: theme.dim(` ${runStatusLine(viewed, clock, tokens[viewed.id])}`) })
14015
+ ]
14016
+ }),
14017
+ lines.length === 0 ? jsx8(Text6, { children: theme.muted(" Waiting for the first step\u2026") }) : lines.map((line, i) => jsx8(ActivityRow, { theme, line }, i)),
14018
+ jsx8(Box6, {
14019
+ marginTop: 1,
14020
+ children: jsx8(Text6, { wrap: "truncate", children: theme.muted(`\u2514 ${clampLine(viewed.objective, 100)}`) })
14021
+ })
14022
+ ]
14023
+ }) : jsxs5(Box6, {
14024
+ children: [
14025
+ jsx8(Text6, { children: theme.muted("Select a sub-agent to view its live activity, or press Enter on ") }),
14026
+ jsx8(Text6, { bold: true, children: theme.color("text", "main") }),
14027
+ jsx8(Text6, { children: theme.muted(" to return.") })
14028
+ ]
14029
+ }),
14030
+ jsxs5(Box6, {
14031
+ flexDirection: "column",
14032
+ marginTop: 1,
14033
+ borderStyle: "single",
14034
+ borderColor: theme.colors["borderMuted"],
14035
+ borderBottom: false,
14036
+ borderLeft: false,
14037
+ borderRight: false,
14038
+ paddingTop: 0,
14039
+ children: [
14040
+ jsxs5(Box6, {
14041
+ children: [
14042
+ jsx8(Text6, { children: index === 0 ? theme.color("accent", "\u203A ") : " " }),
14043
+ jsx8(Text6, { children: index === 0 ? theme.color("text", "\u25CF ") : theme.dim("\u25CB ") }),
14044
+ jsx8(Text6, { bold: index === 0, children: theme.color("text", "main") })
14045
+ ]
14046
+ }),
14047
+ runs.map((run, i) => {
14048
+ const selected = index === i + 1;
14049
+ const dot = run.status === "running" ? theme.color("accent", "\u25D0") : run.status === "error" ? theme.color("error", "\u25CF") : theme.color("success", "\u25CF");
14050
+ return jsxs5(
14051
+ Box6,
14052
+ {
14053
+ children: [
14054
+ jsx8(Text6, { children: selected ? theme.color("accent", "\u203A ") : " " }),
14055
+ jsx8(Box6, { width: 2, flexShrink: 0, children: jsx8(Text6, { children: dot }) }),
14056
+ jsx8(Box6, {
14057
+ width: NAME_WIDTH2,
14058
+ flexShrink: 0,
14059
+ children: jsx8(Text6, { bold: selected, children: theme.color("accent", clampLine(run.name, NAME_WIDTH2 - 1)) })
14060
+ }),
14061
+ jsx8(Box6, {
14062
+ flexGrow: 1,
14063
+ flexShrink: 1,
14064
+ children: jsx8(Text6, { wrap: "truncate", children: theme.muted(clampLine(run.objective, 56)) })
14065
+ }),
14066
+ jsx8(Text6, { children: theme.dim(` ${runStatusLine(run, clock, tokens[run.id])}`) })
14067
+ ]
14068
+ },
14069
+ run.id
14070
+ );
14071
+ })
14072
+ ]
14073
+ }),
14074
+ jsx8(Box6, { marginTop: 1, children: jsx8(Text6, { children: theme.dim("\u2191/\u2193 select \xB7 Enter to view \xB7 Enter on main / Esc to return") }) })
14075
+ ]
14076
+ });
14077
+ }
14078
+
13652
14079
  // src/console/components/Banner.ts
13653
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs6 } from "indusagi/react-host/jsx-runtime";
13654
- import { Box as Box6, Text as Text6 } from "indusagi/react-host/ink";
14080
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs6 } from "indusagi/react-host/jsx-runtime";
14081
+ import { Box as Box8, Text as Text7 } from "indusagi/react-host/ink";
13655
14082
  import { ChangelogBlock } from "indusagi/react-ink";
13656
- import { homedir as homedir6 } from "node:os";
13657
- import { basename as basename6 } from "node:path";
14083
+ import { homedir as homedir5 } from "node:os";
14084
+ import { basename as basename7 } from "node:path";
13658
14085
 
13659
14086
  // src/console/components/banner-sweep.ts
13660
14087
  var WELCOME_FALLBACK = "Welcome back!";
@@ -13665,6 +14092,70 @@ function welcomeLine(name) {
13665
14092
  if (trimmed.length === 0 || trimmed.length > MAX_NAME) return WELCOME_FALLBACK;
13666
14093
  return `Welcome back, ${trimmed}!`;
13667
14094
  }
14095
+ function rowGradient(rowIndex, rowCount, primaryHex, secondaryHex) {
14096
+ if (rowCount <= 1) return primaryHex;
14097
+ const t = Math.max(0, Math.min(1, rowIndex / (rowCount - 1)));
14098
+ const a = Number.parseInt(primaryHex.replace("#", ""), 16);
14099
+ const b = Number.parseInt(secondaryHex.replace("#", ""), 16);
14100
+ const channel = (shift) => Math.round((a >> shift & 255) * (1 - t) + (b >> shift & 255) * t).toString(16).padStart(2, "0");
14101
+ return `#${channel(16)}${channel(8)}${channel(0)}`;
14102
+ }
14103
+
14104
+ // src/console/components/Banner.ts
14105
+ import chalk3 from "chalk";
14106
+
14107
+ // src/console/components/ScrollViewport.ts
14108
+ import React2, { useEffect as useEffect7, useState as useState8 } from "indusagi/react-host";
14109
+ import { Box as Box7, useInput as useInput5 } from "indusagi/react-host/ink";
14110
+ import { measureElement, useStdout } from "ink";
14111
+ import { jsx as jsx9 } from "indusagi/react-host/jsx-runtime";
14112
+ function useTerminalSize() {
14113
+ const { stdout } = useStdout();
14114
+ const read = () => ({ columns: Math.max(1, stdout.columns || 80), rows: Math.max(1, stdout.rows || 24) });
14115
+ const [size, setSize] = useState8(read);
14116
+ useEffect7(() => {
14117
+ const resize = () => setSize(read());
14118
+ stdout.on("resize", resize);
14119
+ return () => {
14120
+ stdout.off("resize", resize);
14121
+ };
14122
+ }, [stdout]);
14123
+ return size;
14124
+ }
14125
+ function ScrollViewport({ children, active = true, follow = true }) {
14126
+ const outer = React2.useRef(null);
14127
+ const inner = React2.useRef(null);
14128
+ const [metrics, setMetrics] = useState8({ height: 1, content: 1 });
14129
+ const [offset, setOffset] = useState8(0);
14130
+ useEffect7(() => {
14131
+ if (!outer.current || !inner.current) return;
14132
+ const height = measureElement(outer.current).height;
14133
+ const content = measureElement(inner.current).height;
14134
+ if (metrics.height !== height || metrics.content !== content) {
14135
+ const oldMax = Math.max(0, metrics.content - metrics.height);
14136
+ const newMax = Math.max(0, content - height);
14137
+ if (follow) setOffset((n) => n === 0 ? 0 : Math.max(0, Math.min(newMax, n + newMax - oldMax)));
14138
+ setMetrics({ height, content });
14139
+ }
14140
+ });
14141
+ const max = Math.max(0, metrics.content - metrics.height);
14142
+ const scroll = Math.min(max, offset);
14143
+ useInput5((_input, key) => {
14144
+ const step = Math.max(1, metrics.height - 2);
14145
+ if (key.pageUp) setOffset((n) => Math.min(max, n + (follow ? step : -step)));
14146
+ if (key.pageDown) setOffset((n) => Math.max(0, Math.min(max, n + (follow ? -step : step))));
14147
+ }, { isActive: active });
14148
+ const top = follow ? Math.max(0, max - scroll) : Math.max(0, scroll);
14149
+ return jsx9(Box7, {
14150
+ ref: outer,
14151
+ flexDirection: "column",
14152
+ flexGrow: 1,
14153
+ flexBasis: 0,
14154
+ minHeight: 1,
14155
+ overflow: "hidden",
14156
+ children: jsx9(Box7, { ref: inner, flexDirection: "column", flexShrink: 0, marginTop: -top, children })
14157
+ });
14158
+ }
13668
14159
 
13669
14160
  // src/console/components/Banner.ts
13670
14161
  var BRAND2 = "IndusCode";
@@ -13677,12 +14168,16 @@ var WORDMARK_ROWS = [
13677
14168
  "\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551",
13678
14169
  "\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
13679
14170
  ];
14171
+ function StartupSummary({ theme, startup }) {
14172
+ if (!startup?.sections.length) return null;
14173
+ return jsx10(Text7, { wrap: "truncate-end", children: theme.muted(startup.sections.map((section2) => `${section2.title}: ${section2.count}`).join(" \xB7 ")) });
14174
+ }
13680
14175
  function StartupNotices({ theme, notices }) {
13681
14176
  if (notices.length === 0) return null;
13682
- return jsx9(Box6, {
14177
+ return jsx10(Box8, {
13683
14178
  flexDirection: "column",
13684
14179
  marginBottom: 1,
13685
- children: notices.map((notice, index) => jsx9(Text6, { children: paintNotice(theme, notice) }, index))
14180
+ children: notices.map((notice, index) => jsx10(Text7, { children: paintNotice(theme, notice) }, index))
13686
14181
  });
13687
14182
  }
13688
14183
  function paintNotice(theme, notice) {
@@ -13698,15 +14193,15 @@ function paintNotice(theme, notice) {
13698
14193
  }
13699
14194
  function StartupChangelogBlock({ theme, changelog }) {
13700
14195
  if (changelog.mode === "full" && changelog.markdown !== void 0) {
13701
- return jsx9(ChangelogBlock, { markdown: changelog.markdown, theme, title: "What is new" });
14196
+ return jsx10(ChangelogBlock, { markdown: changelog.markdown, theme, title: "What is new" });
13702
14197
  }
13703
14198
  if (changelog.mode === "condensed") {
13704
- return jsxs6(Box6, {
14199
+ return jsxs6(Box8, {
13705
14200
  marginTop: 1,
13706
14201
  children: [
13707
- jsx9(Text6, { children: theme.muted(`Updated to v${changelog.version}. Use `) }),
13708
- jsx9(Text6, { children: theme.color("accent", "/changelog") }),
13709
- jsx9(Text6, { children: theme.muted(" to view the full changelog.") })
14202
+ jsx10(Text7, { children: theme.muted(`Updated to v${changelog.version}. Use `) }),
14203
+ jsx10(Text7, { children: theme.color("accent", "/changelog") }),
14204
+ jsx10(Text7, { children: theme.muted(" to view the full changelog.") })
13710
14205
  ]
13711
14206
  });
13712
14207
  }
@@ -13716,46 +14211,46 @@ function WelcomeLine({ theme, name, tone = "accent" }) {
13716
14211
  const paint = (text) => tone === "muted" ? theme.muted(text) : theme.color("accent", text);
13717
14212
  const line = welcomeLine(name);
13718
14213
  if (name !== void 0 && line !== "Welcome back!") {
13719
- return jsxs6(Box6, {
14214
+ return jsxs6(Box8, {
13720
14215
  children: [
13721
- jsx9(Text6, { children: paint("Welcome back, ") }),
13722
- jsx9(Text6, { bold: true, children: name.trim() }),
13723
- jsx9(Text6, { children: paint("!") })
14216
+ jsx10(Text7, { children: paint("Welcome back, ") }),
14217
+ jsx10(Text7, { bold: true, children: name.trim() }),
14218
+ jsx10(Text7, { children: paint("!") })
13724
14219
  ]
13725
14220
  });
13726
14221
  }
13727
- return jsx9(Box6, { children: jsx9(Text6, { children: paint(line) }) });
14222
+ return jsx10(Box8, { children: jsx10(Text7, { children: paint(line) }) });
13728
14223
  }
13729
14224
  function ShellLine({ theme, workspace, branch }) {
13730
- const dir = basename6(workspace);
13731
- return jsxs6(Box6, {
14225
+ const dir = basename7(workspace);
14226
+ return jsxs6(Box8, {
13732
14227
  children: [
13733
- jsx9(Text6, { children: theme.color("customMessage", "\u279C") }),
13734
- jsx9(Text6, { children: " " }),
13735
- jsx9(Text6, { bold: true, children: theme.color("text", dir) }),
14228
+ jsx10(Text7, { children: theme.color("customMessage", "\u279C") }),
14229
+ jsx10(Text7, { children: " " }),
14230
+ jsx10(Text7, { bold: true, children: theme.color("text", dir) }),
13736
14231
  branch !== void 0 && branch !== null && branch.length > 0 ? jsxs6(Fragment3, {
13737
14232
  children: [
13738
- jsx9(Text6, { children: " " }),
13739
- jsx9(Text6, { children: theme.color("accent", "git:(") }),
13740
- jsx9(Text6, { children: theme.color("customMessage", branch) }),
13741
- jsx9(Text6, { children: theme.color("accent", ")") })
14233
+ jsx10(Text7, { children: " " }),
14234
+ jsx10(Text7, { children: theme.color("accent", "git:(") }),
14235
+ jsx10(Text7, { children: theme.color("customMessage", branch) }),
14236
+ jsx10(Text7, { children: theme.color("accent", ")") })
13742
14237
  ]
13743
14238
  }) : null,
13744
- jsx9(Text6, { children: theme.dim(" indus") })
14239
+ jsx10(Text7, { children: theme.dim(" indus") })
13745
14240
  ]
13746
14241
  });
13747
14242
  }
13748
- function Wordmark({ theme }) {
13749
- return jsx9(Box6, {
14243
+ function Wordmark({ theme, sweep }) {
14244
+ return jsx10(Box8, {
13750
14245
  flexDirection: "column",
13751
- children: WORDMARK_ROWS.map((line, index) => jsx9(Text6, { children: theme.color("accent", line) }, index))
14246
+ children: WORDMARK_ROWS.map((line, index) => jsx10(Text7, { children: sweep ? chalk3.hex(rowGradient(index, WORDMARK_ROWS.length, theme.colors.accent, theme.colors.customMessage))(line) : theme.color("accent", line) }, index))
13752
14247
  });
13753
14248
  }
13754
14249
  function SessionFact({ theme, label, value }) {
13755
- return jsxs6(Box6, {
14250
+ return jsxs6(Box8, {
13756
14251
  children: [
13757
- jsx9(Text6, { children: theme.dim(`${label.padEnd(7)} `) }),
13758
- jsx9(Text6, { children: theme.muted(value) })
14252
+ jsx10(Text7, { children: theme.dim(`${label.padEnd(7)} `) }),
14253
+ jsx10(Text7, { children: theme.muted(value) })
13759
14254
  ]
13760
14255
  });
13761
14256
  }
@@ -13771,17 +14266,17 @@ function paintMascot(theme, tone, text) {
13771
14266
  return theme.dim(text);
13772
14267
  }
13773
14268
  }
13774
- function Mascot({ theme, face }) {
13775
- return jsx9(Box6, {
14269
+ function Mascot({ theme, face, sweep }) {
14270
+ return jsx10(Box8, {
13776
14271
  flexDirection: "column",
13777
14272
  alignItems: "center",
13778
14273
  marginY: 1,
13779
14274
  children: mascotRows(face).map(
13780
- (row, rowIndex) => jsx9(
13781
- Box6,
14275
+ (row, rowIndex) => jsx10(
14276
+ Box8,
13782
14277
  {
13783
14278
  children: row.map(
13784
- (seg, segIndex) => jsx9(Text6, { children: paintMascot(theme, seg.tone, seg.text) }, segIndex)
14279
+ (seg, segIndex) => jsx10(Text7, { children: sweep && seg.tone === "head" ? chalk3.hex(rowGradient(rowIndex, 5, theme.colors.accent, theme.colors.customMessage))(seg.text) : paintMascot(theme, seg.tone, seg.text) }, segIndex)
13785
14280
  )
13786
14281
  },
13787
14282
  rowIndex
@@ -13790,13 +14285,14 @@ function Mascot({ theme, face }) {
13790
14285
  });
13791
14286
  }
13792
14287
  function shortenWorkspace(workspace) {
13793
- const home = homedir6();
14288
+ const home = homedir5();
13794
14289
  if (home.length > 0 && workspace.startsWith(home)) {
13795
14290
  return `~${workspace.slice(home.length)}`;
13796
14291
  }
13797
14292
  return workspace;
13798
14293
  }
13799
14294
  function Banner(props) {
14295
+ const { columns } = useTerminalSize();
13800
14296
  const {
13801
14297
  theme,
13802
14298
  modelId,
@@ -13810,71 +14306,75 @@ function Banner(props) {
13810
14306
  variantSeed,
13811
14307
  whatsNew,
13812
14308
  notices,
13813
- changelog
14309
+ changelog,
14310
+ sweep,
14311
+ startup
13814
14312
  } = props;
13815
14313
  const noticeList = notices ?? [];
13816
- if (quiet || compact) {
13817
- return jsxs6(Box6, {
14314
+ if (quiet || compact || columns < 76) {
14315
+ return jsxs6(Box8, {
13818
14316
  flexDirection: "column",
13819
14317
  marginBottom: 1,
13820
14318
  children: [
13821
- jsx9(StartupNotices, { theme, notices: noticeList }),
13822
- jsxs6(Box6, {
14319
+ jsx10(StartupNotices, { theme, notices: noticeList }),
14320
+ jsx10(StartupSummary, { theme, startup }),
14321
+ jsxs6(Box8, {
13823
14322
  children: [
13824
14323
  compact ? jsxs6(Fragment3, {
13825
14324
  children: [
13826
- jsx9(Text6, { children: theme.color("accent", COMPACT_EMBLEM.slice(0, 2)) }),
13827
- jsx9(Text6, { children: theme.color("customMessage", COMPACT_EMBLEM.slice(2)) }),
13828
- jsx9(Text6, { children: " " })
14325
+ jsx10(Text7, { children: theme.color("accent", COMPACT_EMBLEM.slice(0, 2)) }),
14326
+ jsx10(Text7, { children: theme.color("customMessage", COMPACT_EMBLEM.slice(2)) }),
14327
+ jsx10(Text7, { children: " " })
13829
14328
  ]
13830
14329
  }) : null,
13831
- jsx9(Text6, { bold: true, children: theme.color("accent", BRAND2) }),
13832
- jsx9(Text6, { children: " " }),
13833
- jsx9(Text6, { children: theme.muted(`v${version}`) }),
13834
- jsx9(Text6, { children: " " }),
13835
- jsx9(Text6, { children: theme.dim(modelId) })
14330
+ jsx10(Text7, { bold: true, children: theme.color("accent", BRAND2) }),
14331
+ jsx10(Text7, { children: " " }),
14332
+ jsx10(Text7, { children: theme.muted(`v${version}`) }),
14333
+ jsx10(Text7, { children: " " }),
14334
+ jsx10(Text7, { children: theme.dim(modelId) })
13836
14335
  ]
13837
14336
  }),
13838
- jsx9(WelcomeLine, { theme, name, tone: "muted" }),
13839
- changelog ? jsx9(StartupChangelogBlock, { theme, changelog }) : null
14337
+ jsx10(WelcomeLine, { theme, name, tone: "muted" }),
14338
+ changelog ? jsx10(StartupChangelogBlock, { theme, changelog }) : null
13840
14339
  ]
13841
14340
  });
13842
14341
  }
13843
14342
  if (started) {
13844
- return jsxs6(Box6, {
14343
+ return jsxs6(Box8, {
13845
14344
  flexDirection: "column",
13846
14345
  marginBottom: 1,
13847
14346
  children: [
13848
- jsx9(StartupNotices, { theme, notices: noticeList }),
13849
- jsx9(ShellLine, { theme, workspace, branch }),
13850
- jsx9(Box6, { marginTop: 1, children: jsx9(Wordmark, { theme }) }),
13851
- jsxs6(Box6, {
14347
+ jsx10(StartupNotices, { theme, notices: noticeList }),
14348
+ jsx10(StartupSummary, { theme, startup }),
14349
+ jsx10(ShellLine, { theme, workspace, branch }),
14350
+ jsx10(Box8, { marginTop: 1, children: jsx10(Wordmark, { theme, sweep }) }),
14351
+ jsxs6(Box8, {
13852
14352
  marginTop: 1,
13853
14353
  flexDirection: "column",
13854
14354
  children: [
13855
- jsxs6(Box6, {
14355
+ jsxs6(Box8, {
13856
14356
  children: [
13857
- jsx9(Text6, { bold: true, children: theme.color("accent", "indus console") }),
13858
- jsx9(Text6, { children: " " }),
13859
- jsx9(Text6, { children: theme.dim(`v${version}`) })
14357
+ jsx10(Text7, { bold: true, children: theme.color("accent", "indus console") }),
14358
+ jsx10(Text7, { children: " " }),
14359
+ jsx10(Text7, { children: theme.dim(`v${version}`) })
13860
14360
  ]
13861
14361
  }),
13862
- jsx9(WelcomeLine, { theme, name, tone: "accent" })
14362
+ jsx10(WelcomeLine, { theme, name, tone: "accent" })
13863
14363
  ]
13864
14364
  }),
13865
- jsxs6(Box6, {
14365
+ jsxs6(Box8, {
13866
14366
  flexDirection: "column",
13867
14367
  borderStyle: "round",
13868
14368
  borderColor: theme.colors["accent"],
13869
14369
  paddingX: 1,
13870
14370
  marginTop: 1,
13871
14371
  children: [
13872
- jsx9(Text6, { bold: true, children: theme.color("accent", "Session") }),
13873
- jsx9(SessionFact, { theme, label: "model", value: modelId }),
13874
- jsx9(SessionFact, { theme, label: "cwd", value: shortenWorkspace(workspace) })
14372
+ jsx10(Text7, { bold: true, children: theme.color("accent", "Session") }),
14373
+ jsx10(SessionFact, { theme, label: "model", value: modelId }),
14374
+ jsx10(SessionFact, { theme, label: "cwd", value: shortenWorkspace(workspace) })
13875
14375
  ]
13876
14376
  }),
13877
- changelog ? jsx9(StartupChangelogBlock, { theme, changelog }) : null
14377
+ changelog ? jsx10(StartupChangelogBlock, { theme, changelog }) : null
13878
14378
  ]
13879
14379
  });
13880
14380
  }
@@ -13882,43 +14382,44 @@ function Banner(props) {
13882
14382
  const greeting = welcomeLine(name);
13883
14383
  const news = whatsNew && whatsNew.length > 0 ? whatsNew : WHATS_NEW;
13884
14384
  const metaLine = modelId;
13885
- return jsxs6(Box6, {
14385
+ return jsxs6(Box8, {
13886
14386
  flexDirection: "column",
13887
14387
  marginBottom: 1,
13888
14388
  children: [
13889
- jsx9(StartupNotices, { theme, notices: noticeList }),
13890
- jsxs6(Box6, {
14389
+ jsx10(StartupNotices, { theme, notices: noticeList }),
14390
+ jsx10(StartupSummary, { theme, startup }),
14391
+ jsxs6(Box8, {
13891
14392
  flexDirection: "column",
13892
14393
  borderStyle: "round",
13893
14394
  borderColor: theme.colors["accent"],
13894
14395
  paddingX: 2,
13895
14396
  paddingY: 1,
13896
14397
  children: [
13897
- jsxs6(Box6, {
14398
+ jsxs6(Box8, {
13898
14399
  children: [
13899
- jsx9(Text6, { children: theme.color("customMessage", "\u25CF ") }),
13900
- jsx9(Text6, { bold: true, children: theme.color("accent", `${BRAND2} `) }),
13901
- jsx9(Text6, { children: theme.dim(`v${version}`) })
14400
+ jsx10(Text7, { children: theme.color("customMessage", "\u25CF ") }),
14401
+ jsx10(Text7, { bold: true, children: theme.color("accent", `${BRAND2} `) }),
14402
+ jsx10(Text7, { children: theme.dim(`v${version}`) })
13902
14403
  ]
13903
14404
  }),
13904
- jsxs6(Box6, {
14405
+ jsxs6(Box8, {
13905
14406
  marginTop: 1,
13906
14407
  flexDirection: "row",
13907
14408
  children: [
13908
- jsxs6(Box6, {
14409
+ jsxs6(Box8, {
13909
14410
  flexDirection: "column",
13910
14411
  alignItems: "center",
13911
14412
  width: 32,
13912
14413
  flexShrink: 0,
13913
14414
  marginRight: 2,
13914
14415
  children: [
13915
- jsx9(Text6, { bold: true, children: theme.color("text", greeting) }),
13916
- jsx9(Mascot, { theme, face: variant.mascot }),
13917
- jsx9(Text6, { children: theme.dim(metaLine) }),
13918
- jsx9(Text6, { children: theme.dim(shortenWorkspace(workspace)) })
14416
+ jsx10(Text7, { bold: true, children: theme.color("text", greeting) }),
14417
+ jsx10(Mascot, { theme, face: variant.mascot, sweep }),
14418
+ jsx10(Text7, { children: theme.dim(metaLine) }),
14419
+ jsx10(Text7, { children: theme.dim(shortenWorkspace(workspace)) })
13919
14420
  ]
13920
14421
  }),
13921
- jsxs6(Box6, {
14422
+ jsxs6(Box8, {
13922
14423
  flexDirection: "column",
13923
14424
  flexGrow: 1,
13924
14425
  borderStyle: "single",
@@ -13928,108 +14429,96 @@ function Banner(props) {
13928
14429
  borderBottom: false,
13929
14430
  paddingLeft: 2,
13930
14431
  children: [
13931
- jsx9(Text6, { bold: true, children: theme.color("accent", "Tips for getting started") }),
13932
- jsx9(Text6, { children: theme.color("text", variant.tip) }),
13933
- jsx9(Box6, { marginTop: 1, children: jsx9(Text6, { bold: true, children: theme.color("accent", "What's new") }) }),
14432
+ jsx10(Text7, { bold: true, children: theme.color("accent", "Tips for getting started") }),
14433
+ jsx10(Text7, { children: theme.color("text", variant.tip) }),
14434
+ jsx10(Box8, { marginTop: 1, children: jsx10(Text7, { bold: true, children: theme.color("accent", "What's new") }) }),
13934
14435
  news.map(
13935
- (line, index) => jsxs6(Box6, {
14436
+ (line, index) => jsxs6(Box8, {
13936
14437
  flexDirection: "row",
13937
14438
  children: [
13938
- jsx9(Box6, { width: 2, flexShrink: 0, children: jsx9(Text6, { children: theme.color("customMessage", "\u25B8") }) }),
13939
- jsx9(Box6, { flexGrow: 1, children: jsx9(Text6, { children: theme.color("text", line) }) })
14439
+ jsx10(Box8, { width: 2, flexShrink: 0, children: jsx10(Text7, { children: theme.color("customMessage", "\u25B8") }) }),
14440
+ jsx10(Box8, { flexGrow: 1, children: jsx10(Text7, { children: theme.color("text", line) }) })
13940
14441
  ]
13941
14442
  }, index)
13942
14443
  ),
13943
- jsx9(Box6, { marginTop: 1, children: jsx9(Text6, { italic: true, children: theme.muted("/release-notes for more") }) })
14444
+ jsx10(Box8, { marginTop: 1, children: jsx10(Text7, { italic: true, children: theme.muted("/release-notes for more") }) })
13944
14445
  ]
13945
14446
  })
13946
14447
  ]
13947
14448
  })
13948
14449
  ]
13949
14450
  }),
13950
- changelog ? jsx9(StartupChangelogBlock, { theme, changelog }) : null
14451
+ changelog ? jsx10(StartupChangelogBlock, { theme, changelog }) : null
13951
14452
  ]
13952
14453
  });
13953
14454
  }
13954
14455
 
13955
14456
  // src/console/components/Composer.ts
13956
- import { Box as Box7, Text as Text7 } from "indusagi/react-host/ink";
13957
- import { jsx as jsx10, jsxs as jsxs7 } from "indusagi/react-host/jsx-runtime";
13958
- var PROMPT_GLYPH = "\u203A ";
13959
- var SUGGESTION_WINDOW = 8;
13960
- var NAME_COLUMN_CAP = 24;
13961
- var NAME_COLUMN_FLOOR = 8;
13962
- function clampName(label, width) {
13963
- if (label.length <= width) return label;
13964
- if (width <= 1) return label.slice(0, width);
13965
- return `${label.slice(0, width - 1)}\u2026`;
13966
- }
13967
- function bufferWithCaret(buffer, caret) {
13968
- const before = buffer.slice(0, caret);
13969
- const under = caret < buffer.length ? buffer[caret] : " ";
13970
- const after = caret < buffer.length ? buffer.slice(caret + 1) : "";
13971
- return jsxs7(Text7, {
13972
- children: [before, jsx10(Text7, { inverse: true, children: under }), after]
13973
- });
14457
+ import { Box as Box9, Text as Text8 } from "indusagi/react-host/ink";
14458
+ import { jsx as jsx11, jsxs as jsxs7 } from "indusagi/react-host/jsx-runtime";
14459
+ import stringWidth from "string-width";
14460
+ var segmenter2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
14461
+ function editorLines(buffer, caret, width) {
14462
+ const lines = [[]];
14463
+ let column = 0;
14464
+ caret = snapGrapheme(buffer, caret);
14465
+ for (const { segment, index } of segmenter2.segment(buffer + " ")) {
14466
+ const cursor = index === caret;
14467
+ if (segment === "\n") {
14468
+ if (cursor) lines.at(-1).push({ text: " ", caret: true });
14469
+ lines.push([]);
14470
+ column = 0;
14471
+ continue;
14472
+ }
14473
+ const text = segment === " " ? " " : segment.replace(/[\x00-\x1f\x7f]/g, "");
14474
+ const cells2 = stringWidth(text);
14475
+ if (column + cells2 > width && column > 0) {
14476
+ lines.push([]);
14477
+ column = 0;
14478
+ }
14479
+ lines.at(-1).push({ text: cells2 > width ? "?" : text, caret: cursor });
14480
+ column += Math.min(width, cells2);
14481
+ }
14482
+ return lines;
13974
14483
  }
13975
- function Composer({ theme, buffer, caret, busy, suggestions, highlight }) {
13976
- const shown = suggestions.slice(0, SUGGESTION_WINDOW);
13977
- const borderColor = theme.colors.borderMuted ?? theme.colors.accent ?? "gray";
13978
- const nameWidth = shown.length > 0 ? Math.max(NAME_COLUMN_FLOOR, Math.min(NAME_COLUMN_CAP, Math.max(...shown.map((s) => s.label.length)))) : 0;
13979
- return jsxs7(Box7, {
13980
- flexDirection: "column",
13981
- children: [
13982
- jsxs7(Box7, {
13983
- borderStyle: "round",
13984
- borderColor,
13985
- paddingX: 1,
13986
- children: [
13987
- jsxs7(Box7, {
13988
- flexGrow: 1,
13989
- children: [
13990
- jsx10(Text7, { children: theme.color("customMessage", PROMPT_GLYPH) }),
13991
- bufferWithCaret(buffer, caret)
13992
- ]
13993
- }),
13994
- jsx10(Text7, { children: theme.dim("\u21B5 send") })
13995
- ]
13996
- }),
13997
- shown.length > 0 ? jsx10(Box7, {
13998
- flexDirection: "column",
13999
- marginTop: 1,
14000
- children: shown.map((suggestion, index) => {
14001
- const active = index === highlight;
14002
- const name = clampName(suggestion.label, nameWidth);
14003
- return jsxs7(
14004
- Box7,
14005
- {
14006
- children: [
14007
- jsx10(Box7, {
14008
- width: 2,
14009
- flexShrink: 0,
14010
- children: jsx10(Text7, { children: active ? theme.color("accent", "\u25B8") : " " })
14011
- }),
14012
- jsx10(Box7, {
14013
- width: nameWidth,
14014
- flexShrink: 0,
14015
- children: jsx10(Text7, { bold: active, children: theme.color("customMessage", name) })
14016
- }),
14017
- jsx10(Text7, { children: " " }),
14018
- jsx10(Text7, { children: theme.muted(suggestion.detail) })
14019
- ]
14020
- },
14021
- suggestion.value
14022
- );
14023
- })
14024
- }) : null
14025
- ]
14026
- });
14484
+ function Composer({ theme, buffer, caret, suggestions, highlight, paddingX = 1 }) {
14485
+ const { columns, rows } = useTerminalSize();
14486
+ const padding = Math.max(0, Math.min(3, Math.floor(paddingX), Math.floor((columns - 8) / 2)));
14487
+ const sendWidth = columns >= 32 ? 7 : 0;
14488
+ const width = Math.max(1, columns - 2 - padding * 2 - 2 - sendWidth);
14489
+ const allLines = editorLines(buffer, caret, width);
14490
+ const caretRow = Math.max(0, allLines.findIndex((line) => line.some((cell) => cell.caret)));
14491
+ const editorBudget = Math.max(1, Math.min(6, Math.floor(rows / 4)));
14492
+ const top = Math.max(0, Math.min(caretRow - editorBudget + 1, allLines.length - editorBudget));
14493
+ const lines = allLines.slice(top, top + editorBudget);
14494
+ const budget = Math.max(1, Math.min(8, rows - lines.length - 12));
14495
+ const start = Math.max(0, Math.min(highlight - budget + 1, suggestions.length - budget));
14496
+ const shown = suggestions.slice(start, start + budget);
14497
+ const nameWidth = Math.max(1, Math.min(24, Math.floor(columns / 3)));
14498
+ return jsxs7(Box9, { flexDirection: "column", flexShrink: 0, children: [
14499
+ jsx11(Box9, {
14500
+ borderStyle: "round",
14501
+ borderColor: theme.colors.borderMuted ?? "gray",
14502
+ paddingX: padding,
14503
+ flexDirection: "column",
14504
+ children: lines.map((line, index) => jsxs7(Box9, { height: 1, flexShrink: 0, children: [
14505
+ jsx11(Box9, { width: 2, flexShrink: 0, children: jsx11(Text8, { children: theme.color("customMessage", index + top === 0 ? "\u203A " : "\xB7 ") }) }),
14506
+ jsx11(Box9, { width, flexShrink: 0, children: jsx11(Text8, { wrap: "truncate-end", children: line.map((cell, i) => jsx11(Text8, { inverse: cell.caret, children: cell.text }, i)) }) }),
14507
+ sendWidth ? jsx11(Box9, { width: sendWidth, flexShrink: 0, children: jsx11(Text8, { children: index === 0 ? theme.dim(" \u21B5 send") : "" }) }) : null
14508
+ ] }, index))
14509
+ }),
14510
+ shown.length ? jsx11(Box9, { flexDirection: "column", marginTop: 1, children: shown.map((suggestion, index) => jsxs7(Box9, { height: 1, children: [
14511
+ jsx11(Box9, { width: 2, flexShrink: 0, children: jsx11(Text8, { children: start + index === highlight ? theme.color("accent", "\u25B8") : " " }) }),
14512
+ jsx11(Box9, { width: nameWidth, flexShrink: 0, children: jsx11(Text8, { wrap: "truncate-end", bold: start + index === highlight, children: theme.color("customMessage", suggestion.label) }) }),
14513
+ jsx11(Box9, { width: Math.max(1, columns - nameWidth - 2), children: jsx11(Text8, { wrap: "truncate-end", children: theme.muted(" " + suggestion.detail) }) })
14514
+ ] }, suggestion.value)) }) : null
14515
+ ] });
14027
14516
  }
14028
14517
 
14029
14518
  // src/console/components/StatusBar.ts
14030
- import { Box as Box8, Text as Text8 } from "indusagi/react-host/ink";
14519
+ import { Box as Box10, Text as Text9 } from "indusagi/react-host/ink";
14031
14520
  import { Footer, StatusLine } from "indusagi/react-ink";
14032
- import { jsx as jsx11, jsxs as jsxs8 } from "indusagi/react-host/jsx-runtime";
14521
+ import { jsx as jsx12, jsxs as jsxs8 } from "indusagi/react-host/jsx-runtime";
14033
14522
  function permissionBadge(mode) {
14034
14523
  switch (mode) {
14035
14524
  case "acceptEdits":
@@ -14046,26 +14535,26 @@ function permissionBadge(mode) {
14046
14535
  }
14047
14536
  function StatusBar({ theme, snapshot, branch, providerCount, status, permissionMode }) {
14048
14537
  const badge = permissionMode !== void 0 ? permissionBadge(permissionMode) : null;
14049
- return jsxs8(Box8, {
14538
+ return jsxs8(Box10, {
14050
14539
  flexDirection: "column",
14051
14540
  children: [
14052
- jsx11(StatusLine, { theme, snapshot, status, showBusyText: false }),
14053
- badge !== null ? jsx11(Box8, {
14054
- children: jsxs8(Text8, {
14541
+ jsx12(StatusLine, { theme, snapshot, status, showBusyText: false }),
14542
+ badge !== null ? jsx12(Box10, {
14543
+ children: jsxs8(Text9, {
14055
14544
  color: badge.color,
14056
14545
  children: ["\u25CF permission: ", badge.label]
14057
14546
  })
14058
14547
  }) : null,
14059
- jsx11(Footer, { theme, branch, snapshot, availableProviderCount: providerCount })
14548
+ jsx12(Footer, { theme, branch, snapshot, availableProviderCount: providerCount })
14060
14549
  ]
14061
14550
  });
14062
14551
  }
14063
14552
 
14064
14553
  // src/console/components/WorkingIndicator.ts
14065
- import React2, { useEffect as useEffect6, useState as useState7 } from "indusagi/react-host";
14066
- import { Box as Box9, Text as Text9 } from "indusagi/react-host/ink";
14067
- import { jsx as jsx12, jsxs as jsxs9 } from "indusagi/react-host/jsx-runtime";
14068
- var { useRef: useRef2 } = React2;
14554
+ import React3, { useEffect as useEffect8, useState as useState9 } from "indusagi/react-host";
14555
+ import { Box as Box11, Text as Text10 } from "indusagi/react-host/ink";
14556
+ import { jsx as jsx13, jsxs as jsxs9 } from "indusagi/react-host/jsx-runtime";
14557
+ var { useRef: useRef2 } = React3;
14069
14558
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
14070
14559
  var FRAME_MS = 120;
14071
14560
  var WORD_MS = 4e3;
@@ -14115,11 +14604,11 @@ function phaseLabel(phase) {
14115
14604
  return null;
14116
14605
  }
14117
14606
  }
14118
- function WorkingIndicator({ theme, busy, phase }) {
14119
- const [frame, setFrame] = useState7(0);
14607
+ function WorkingIndicator({ theme, busy, phase, reducedMotion = false }) {
14608
+ const [frame, setFrame] = useState9(0);
14120
14609
  const startRef = useRef2(null);
14121
14610
  const wordOffsetRef = useRef2(0);
14122
- useEffect6(() => {
14611
+ useEffect8(() => {
14123
14612
  if (!busy) {
14124
14613
  startRef.current = null;
14125
14614
  return;
@@ -14128,28 +14617,27 @@ function WorkingIndicator({ theme, busy, phase }) {
14128
14617
  startRef.current = Date.now();
14129
14618
  wordOffsetRef.current = Math.floor(Math.random() * WORKING_WORDS.length);
14130
14619
  }
14131
- const id = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), FRAME_MS);
14620
+ const id = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), reducedMotion ? 1e3 : FRAME_MS);
14132
14621
  return () => clearInterval(id);
14133
- }, [busy]);
14622
+ }, [busy, reducedMotion]);
14134
14623
  if (!busy) return null;
14135
14624
  const elapsedMs = startRef.current !== null ? Date.now() - startRef.current : 0;
14136
14625
  const elapsedSec = Math.floor(elapsedMs / 1e3);
14137
- const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
14138
- const label = phaseLabel(phase) ?? pickWord(wordOffsetRef.current + Math.floor(elapsedMs / WORD_MS));
14139
- return jsxs9(Box9, {
14626
+ const spinner = reducedMotion ? "\u2022" : SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
14627
+ const label = phaseLabel(phase) ?? (reducedMotion ? "Working" : pickWord(wordOffsetRef.current + Math.floor(elapsedMs / WORD_MS)));
14628
+ return jsxs9(Box11, {
14140
14629
  marginTop: 1,
14141
14630
  children: [
14142
- jsx12(Text9, { children: theme.color("accent", `${spinner} ${label}\u2026 `) }),
14143
- jsx12(Text9, { children: theme.muted(`(${elapsedSec}s \xB7 esc to interrupt)`) })
14631
+ jsx13(Text10, { children: theme.color("accent", `${spinner} ${label}\u2026 `) }),
14632
+ jsx13(Text10, { children: theme.muted(`(${elapsedSec}s \xB7 esc to interrupt)`) })
14144
14633
  ]
14145
14634
  });
14146
14635
  }
14147
14636
 
14148
14637
  // src/console/components/TerminalConsole.ts
14149
- var { useReducer, useRef: useRef3, useCallback } = React3;
14638
+ var { useReducer, useRef: useRef3, useCallback } = React4;
14150
14639
  var CHORD_WINDOW_MS = 600;
14151
14640
  var CTRL_C_EXIT_WINDOW_MS = 500;
14152
- var FALLBACK_TERMINAL_ROWS = 40;
14153
14641
  var CHROME_ROWS = 14;
14154
14642
  var MODAL_CHROME_ROWS = 24;
14155
14643
  var MIN_VISIBLE_ROWS = 8;
@@ -14290,8 +14778,39 @@ function permissionModeStatusText(mode) {
14290
14778
  }
14291
14779
  function TerminalConsole(props) {
14292
14780
  const { conductor, theme, slash, verbose, onExit, initialInput, services } = props;
14293
- const adapter = theme.adapter;
14294
- const [state, dispatch2] = useReducer(consoleReducer, initConsoleState({ scheme: theme.scheme }));
14781
+ const { stdin, internal_eventEmitter } = useStdin();
14782
+ const { stdout } = useStdout2();
14783
+ const rawKey = useRef3("");
14784
+ useEffect9(() => {
14785
+ const capture = (data) => {
14786
+ rawKey.current = String(data);
14787
+ };
14788
+ internal_eventEmitter.prependListener("input", capture);
14789
+ return () => {
14790
+ internal_eventEmitter.removeListener("input", capture);
14791
+ };
14792
+ }, [internal_eventEmitter]);
14793
+ const [state, dispatch2] = useReducer(consoleReducer, theme.scheme, (scheme) => {
14794
+ let stored;
14795
+ try {
14796
+ stored = services?.settings.get("colourScheme");
14797
+ } catch {
14798
+ }
14799
+ const flag = (key, fallback) => {
14800
+ try {
14801
+ const value = services?.settings.get(key);
14802
+ return typeof value === "boolean" ? value : fallback;
14803
+ } catch {
14804
+ return fallback;
14805
+ }
14806
+ };
14807
+ return initConsoleState({
14808
+ scheme: typeof stored === "string" && isThemeScheme(stored) ? stored : scheme,
14809
+ showReasoning: flag("showReasoning", false),
14810
+ showImages: flag("showImages", true)
14811
+ });
14812
+ });
14813
+ const adapter = resolveTheme(state.scheme).adapter;
14295
14814
  const chord = useRef3(NO_CHORD);
14296
14815
  const chordTimer = useRef3(null);
14297
14816
  const paste = useRef3(EMPTY_PASTE_VAULT);
@@ -14302,34 +14821,39 @@ function TerminalConsole(props) {
14302
14821
  const liveReason = useRef3(null);
14303
14822
  const rowSeed = useRef3(0);
14304
14823
  const ctrlCAt = useRef3(0);
14305
- const [highlight, setHighlight] = useState8(0);
14306
- const [toolExecutions, setToolExecutions] = useState8({});
14307
- const [taskTokens, setTaskTokens] = useState8({});
14308
- const [subagentActivity, setSubagentActivity] = useState8({});
14309
- const [expandTools, setExpandTools] = useState8(false);
14310
- const [agentsOpen, setAgentsOpen] = useState8(false);
14311
- const [terminalRows, setTerminalRows] = useState8(() => process.stdout.rows ?? FALLBACK_TERMINAL_ROWS);
14312
- const [, forceTick] = useState8(0);
14824
+ const resumeHandler = useRef3(null);
14825
+ useEffect9(() => () => {
14826
+ if (resumeHandler.current) process.off("SIGCONT", resumeHandler.current);
14827
+ }, []);
14828
+ const [highlight, setHighlight] = useState10(0);
14829
+ const [toolExecutions, setToolExecutions] = useState10({});
14830
+ const [taskTokens, setTaskTokens] = useState10({});
14831
+ const [subagentActivity, setSubagentActivity] = useState10({});
14832
+ const [expandTools, setExpandTools] = useState10(false);
14833
+ const [agentsOpen, setAgentsOpen] = useState10(false);
14834
+ const { rows: terminalRows, columns: terminalColumns } = useTerminalSize();
14835
+ const renderRows = Math.max(1, terminalRows - 1);
14836
+ const [, forceTick] = useState10(0);
14313
14837
  const pulse = useCallback(() => forceTick((n) => n + 1), []);
14314
14838
  const dirReader = useMemo2(() => createDirReader(process.cwd()), []);
14315
14839
  const [approvalQueue, dispatchApproval] = useReducer(approvalQueueReducer, EMPTY_APPROVAL_QUEUE);
14316
14840
  const approvalSeed = useRef3(0);
14317
14841
  const approvalResolvers = useRef3(/* @__PURE__ */ new Map());
14318
14842
  const settleApproval = useCallback((id, choice) => {
14319
- const resolve9 = approvalResolvers.current.get(id);
14320
- if (resolve9 === void 0) return;
14843
+ const resolve11 = approvalResolvers.current.get(id);
14844
+ if (resolve11 === void 0) return;
14321
14845
  approvalResolvers.current.delete(id);
14322
14846
  dispatchApproval({ type: "settle", id });
14323
- resolve9(choice);
14847
+ resolve11(choice);
14324
14848
  }, []);
14325
14849
  const denyAllApprovals = useCallback(() => {
14326
14850
  const pending = [...approvalResolvers.current.entries()];
14327
14851
  approvalResolvers.current.clear();
14328
14852
  dispatchApproval({ type: "clear" });
14329
- for (const [, resolve9] of pending) resolve9("deny");
14853
+ for (const [, resolve11] of pending) resolve11("deny");
14330
14854
  }, []);
14331
14855
  const requestApproval = useCallback(
14332
- (toolName, input, opts) => new Promise((resolve9) => {
14856
+ (toolName, input, opts) => new Promise((resolve11) => {
14333
14857
  approvalSeed.current += 1;
14334
14858
  const id = `ap_${approvalSeed.current}`;
14335
14859
  const request = {
@@ -14337,21 +14861,26 @@ function TerminalConsole(props) {
14337
14861
  input,
14338
14862
  suggestions: allowAlwaysRuleStrings(toolName, input)
14339
14863
  };
14340
- approvalResolvers.current.set(id, resolve9);
14341
14864
  const signal = opts.signal;
14865
+ const onAbort = () => settleApproval(id, "deny");
14866
+ const finish = (choice) => {
14867
+ signal?.removeEventListener("abort", onAbort);
14868
+ resolve11(choice);
14869
+ };
14870
+ approvalResolvers.current.set(id, finish);
14342
14871
  if (signal !== void 0) {
14343
14872
  if (signal.aborted) {
14344
14873
  approvalResolvers.current.delete(id);
14345
- resolve9("deny");
14874
+ resolve11("deny");
14346
14875
  return;
14347
14876
  }
14348
- signal.addEventListener("abort", () => settleApproval(id, "deny"), { once: true });
14877
+ signal.addEventListener("abort", onAbort, { once: true });
14349
14878
  }
14350
- dispatchApproval({ type: "enqueue", entry: { id, request, resolve: resolve9 } });
14879
+ dispatchApproval({ type: "enqueue", entry: { id, request, resolve: finish } });
14351
14880
  }),
14352
14881
  [settleApproval]
14353
14882
  );
14354
- useEffect7(() => {
14883
+ useEffect9(() => {
14355
14884
  conductor.setApprovalResolver(requestApproval);
14356
14885
  return () => {
14357
14886
  conductor.setApprovalResolver(void 0);
@@ -14361,7 +14890,7 @@ function TerminalConsole(props) {
14361
14890
  const head = activeApproval(approvalQueue);
14362
14891
  const headId = head?.id;
14363
14892
  const approvalModalOpen = useRef3(false);
14364
- useEffect7(() => {
14893
+ useEffect9(() => {
14365
14894
  if (head === void 0) {
14366
14895
  if (approvalModalOpen.current) {
14367
14896
  approvalModalOpen.current = false;
@@ -14392,11 +14921,53 @@ function TerminalConsole(props) {
14392
14921
  const answerText = useRef3("");
14393
14922
  const reasonText = useRef3("");
14394
14923
  const visibleFrom = useRef3(0);
14395
- const [compactNote, setCompactNote] = useState8(null);
14396
- useEffect7(() => {
14924
+ const [compactNote, setCompactNote] = useState10(null);
14925
+ const sessionId = conductor.snapshot().head.sessionId;
14926
+ const priorSession = useRef3(sessionId);
14927
+ const resetView = useCallback(() => {
14928
+ liveAnswer.current = null;
14929
+ liveReason.current = null;
14930
+ answerText.current = "";
14931
+ reasonText.current = "";
14932
+ visibleFrom.current = 0;
14933
+ activePaste.current = null;
14934
+ paste.current = EMPTY_PASTE_VAULT;
14935
+ burst.current = "";
14936
+ if (burstTimer.current !== null) {
14937
+ clearTimeout(burstTimer.current);
14938
+ burstTimer.current = null;
14939
+ }
14940
+ setToolExecutions({});
14941
+ setTaskTokens({});
14942
+ setSubagentActivity({});
14943
+ setCompactNote(null);
14944
+ setAgentsOpen(false);
14945
+ denyAllApprovals();
14946
+ dispatch2({ type: "view:reset" });
14947
+ }, [denyAllApprovals]);
14948
+ useEffect9(() => {
14949
+ if (priorSession.current !== sessionId) {
14950
+ priorSession.current = sessionId;
14951
+ resetView();
14952
+ }
14953
+ }, [sessionId, resetView]);
14954
+ useEffect9(() => {
14397
14955
  const off = conductor.subscribe((signal) => {
14398
14956
  switch (signal.kind) {
14957
+ case "reset": {
14958
+ resetView();
14959
+ break;
14960
+ }
14961
+ case "phase": {
14962
+ dispatch2({ type: "busy:set", busy: signal.phase === "streaming" || signal.phase === "tooling" || signal.phase === "condensing" });
14963
+ break;
14964
+ }
14399
14965
  case "prompt": {
14966
+ liveAnswer.current = null;
14967
+ liveReason.current = null;
14968
+ answerText.current = "";
14969
+ reasonText.current = "";
14970
+ dispatch2({ type: "busy:set", busy: true });
14400
14971
  break;
14401
14972
  }
14402
14973
  case "text": {
@@ -14404,10 +14975,8 @@ function TerminalConsole(props) {
14404
14975
  const id = freshRowId(rowSeed.current++);
14405
14976
  liveAnswer.current = id;
14406
14977
  answerText.current = signal.delta;
14407
- dispatch2({ type: "rows:append", row: { id, kind: "answer", text: answerText.current } });
14408
14978
  } else {
14409
14979
  answerText.current += signal.delta;
14410
- dispatch2({ type: "rows:patch", id: liveAnswer.current, text: answerText.current });
14411
14980
  }
14412
14981
  break;
14413
14982
  }
@@ -14416,19 +14985,14 @@ function TerminalConsole(props) {
14416
14985
  const id = freshRowId(rowSeed.current++);
14417
14986
  liveReason.current = id;
14418
14987
  reasonText.current = signal.delta;
14419
- dispatch2({ type: "rows:append", row: { id, kind: "reason", text: reasonText.current } });
14420
14988
  } else {
14421
14989
  reasonText.current += signal.delta;
14422
- dispatch2({ type: "rows:patch", id: liveReason.current, text: reasonText.current });
14423
14990
  }
14424
14991
  break;
14425
14992
  }
14426
14993
  case "tool_start": {
14427
14994
  liveAnswer.current = null;
14428
14995
  liveReason.current = null;
14429
- const id = `tool-${signal.id}`;
14430
- const row = { id, kind: "toolRun", text: signal.name, runId: signal.id };
14431
- dispatch2({ type: "rows:append", row });
14432
14996
  setToolExecutions((current) => ({
14433
14997
  ...current,
14434
14998
  [signal.id]: {
@@ -14473,11 +15037,6 @@ function TerminalConsole(props) {
14473
15037
  break;
14474
15038
  }
14475
15039
  case "tool_end": {
14476
- dispatch2({
14477
- type: "rows:patch",
14478
- id: `tool-${signal.id}`,
14479
- text: signal.ok ? "done" : "failed"
14480
- });
14481
15040
  setToolExecutions((current) => {
14482
15041
  const prior = current[signal.id];
14483
15042
  if (prior === void 0) return current;
@@ -14502,7 +15061,7 @@ function TerminalConsole(props) {
14502
15061
  if (signal.manual) {
14503
15062
  const carried = [];
14504
15063
  for (const message of conductor.messages()) {
14505
- const content = message.content;
15064
+ const content = "content" in message ? message.content : void 0;
14506
15065
  if (typeof content === "string" && content.startsWith(RESTORED_FILE_PREFIX)) {
14507
15066
  const firstLine = content.slice(RESTORED_FILE_PREFIX.length).split("\n", 1)[0];
14508
15067
  if (firstLine) carried.push(firstLine.trim());
@@ -14514,7 +15073,7 @@ function TerminalConsole(props) {
14514
15073
  liveReason.current = null;
14515
15074
  answerText.current = "";
14516
15075
  reasonText.current = "";
14517
- dispatch2({ type: "rows:set", rows: [] });
15076
+ dispatch2({ type: "blocks:clear" });
14518
15077
  }
14519
15078
  dispatch2({
14520
15079
  type: "status:set",
@@ -14541,6 +15100,8 @@ function TerminalConsole(props) {
14541
15100
  break;
14542
15101
  }
14543
15102
  case "idle": {
15103
+ liveAnswer.current = null;
15104
+ liveReason.current = null;
14544
15105
  dispatch2({ type: "busy:set", busy: false });
14545
15106
  break;
14546
15107
  }
@@ -14550,19 +15111,17 @@ function TerminalConsole(props) {
14550
15111
  pulse();
14551
15112
  });
14552
15113
  return off;
14553
- }, [conductor, pulse]);
15114
+ }, [conductor, pulse, resetView]);
14554
15115
  const completion = useMemo2(
14555
15116
  () => completeAt(state.buffer, state.caret, slash, dirReader),
14556
15117
  [state.buffer, state.caret, slash, dirReader]
14557
15118
  );
14558
15119
  const suggestions = completion.suggestions;
14559
- useEffect7(() => {
15120
+ useEffect9(() => {
14560
15121
  if (highlight >= suggestions.length) setHighlight(0);
14561
15122
  }, [suggestions.length, highlight]);
14562
15123
  const runTurn = useCallback(
14563
15124
  async (text) => {
14564
- const id = freshRowId(rowSeed.current++);
14565
- dispatch2({ type: "rows:append", row: { id, kind: "prompt", text } });
14566
15125
  dispatch2({ type: "busy:set", busy: true });
14567
15126
  if (conductor.snapshot().phase === "condensing") {
14568
15127
  dispatch2({
@@ -14572,8 +15131,6 @@ function TerminalConsole(props) {
14572
15131
  }
14573
15132
  setCompactNote(null);
14574
15133
  activePaste.current = null;
14575
- answerText.current = "";
14576
- reasonText.current = "";
14577
15134
  try {
14578
15135
  await conductor.submit(text);
14579
15136
  } catch (err2) {
@@ -14592,15 +15149,29 @@ function TerminalConsole(props) {
14592
15149
  const text = hasMarkers(raw, paste.current) ? expandMarkers(raw, paste.current) : raw;
14593
15150
  const trimmed = text.trim();
14594
15151
  dispatch2({ type: "buffer:set", text: "" });
14595
- paste.current = EMPTY_PASTE_VAULT;
15152
+ paste.current = { bodies: /* @__PURE__ */ new Map(), minted: paste.current.minted };
15153
+ activePaste.current = null;
14596
15154
  if (trimmed.length === 0) return;
14597
- dispatch2({ type: "history:push", text: raw });
15155
+ dispatch2({ type: "history:push", text });
14598
15156
  if (trimmed.startsWith("!")) {
14599
15157
  const excludeFromContext = trimmed.startsWith("!!");
14600
15158
  const command = trimmed.slice(excludeFromContext ? 2 : 1).trim();
14601
15159
  if (command.length === 0) return;
14602
15160
  dispatch2({ type: "status:set", status: { kind: "busy", text: `Running: ${command}` } });
14603
15161
  void conductor.executeBash(command, { excludeFromContext }).then((outcome) => {
15162
+ const output = String(outcome.output ?? "");
15163
+ const fence = "`".repeat(Math.max(3, ...Array.from(output.matchAll(/`+/g), (m) => m[0].length + 1)));
15164
+ const recorded = conductor.messages().some((message) => message.role === "user" && message.content === `$ ${command}
15165
+ ${output}`.trimEnd());
15166
+ if (!recorded || excludeFromContext) dispatch2({ type: "block:append", block: {
15167
+ id: freshRowId(rowSeed.current++),
15168
+ kind: "changelog",
15169
+ title: `$ ${command}`,
15170
+ markdown: `${fence}text
15171
+ ${output || "(no output)"}
15172
+ ${fence}`,
15173
+ timestamp: Date.now()
15174
+ } });
14604
15175
  dispatch2({
14605
15176
  type: "status:set",
14606
15177
  status: {
@@ -14618,9 +15189,10 @@ function TerminalConsole(props) {
14618
15189
  }
14619
15190
  const resolution = resolveSlash(trimmed, slash);
14620
15191
  if (resolution.kind === "match") {
14621
- const outcome = resolution.command.run({
15192
+ void Promise.resolve().then(() => resolution.command.run({
14622
15193
  args: resolution.args,
14623
15194
  conductor,
15195
+ services,
14624
15196
  dispatch: dispatch2,
14625
15197
  openModal: (kind, payload) => dispatch2({ type: "modal:open", kind, payload }),
14626
15198
  closeModal: () => dispatch2({ type: "modal:close" }),
@@ -14628,10 +15200,9 @@ function TerminalConsole(props) {
14628
15200
  setStatus: (status) => dispatch2({ type: "status:set", status }),
14629
15201
  setBuffer: (value) => dispatch2({ type: "buffer:set", text: value }),
14630
15202
  appendBlock: (block2) => dispatch2({ type: "block:append", block: block2 })
14631
- });
14632
- void Promise.resolve(outcome).then((settled) => {
15203
+ })).then((settled) => {
14633
15204
  if (settled.kind === "prompt") void runTurn(settled.text);
14634
- });
15205
+ }).catch((error) => dispatch2({ type: "status:set", status: { kind: "error", text: error instanceof Error ? error.message : String(error) } }));
14635
15206
  return;
14636
15207
  }
14637
15208
  if (resolution.kind === "miss") {
@@ -14643,7 +15214,7 @@ function TerminalConsole(props) {
14643
15214
  }
14644
15215
  void runTurn(text);
14645
15216
  },
14646
- [slash, conductor, onExit, runTurn]
15217
+ [slash, conductor, onExit, runTurn, services]
14647
15218
  );
14648
15219
  const flushBurst = useCallback(() => {
14649
15220
  if (burstTimer.current !== null) {
@@ -14691,22 +15262,15 @@ function TerminalConsole(props) {
14691
15262
  chordTimer.current = null;
14692
15263
  }
14693
15264
  }, []);
14694
- useEffect7(
15265
+ useEffect9(
14695
15266
  () => () => {
14696
15267
  if (chordTimer.current !== null) clearTimeout(chordTimer.current);
14697
15268
  if (burstTimer.current !== null) clearTimeout(burstTimer.current);
14698
15269
  },
14699
15270
  []
14700
15271
  );
14701
- useEffect7(() => {
14702
- const onResize = () => setTerminalRows(process.stdout.rows ?? FALLBACK_TERMINAL_ROWS);
14703
- process.stdout.on("resize", onResize);
14704
- return () => {
14705
- process.stdout.off("resize", onResize);
14706
- };
14707
- }, []);
14708
15272
  const titleName = conductor.sessionName();
14709
- useEffect7(() => {
15273
+ useEffect9(() => {
14710
15274
  writeTerminalTitle(titleName && titleName.length > 0 ? titleName : "indus console");
14711
15275
  }, [titleName]);
14712
15276
  const requestExit = useCallback(() => {
@@ -14721,11 +15285,12 @@ function TerminalConsole(props) {
14721
15285
  setHighlight(0);
14722
15286
  return true;
14723
15287
  }, [suggestions, highlight, state.buffer, completion.span]);
14724
- useInput4(
15288
+ useInput6(
14725
15289
  (rawInput, key) => {
14726
15290
  const input = rawInput.replace(BRACKETED_PASTE, "");
14727
15291
  if (input.length === 0 && input !== rawInput) return;
14728
- const intent = readKey(input, key);
15292
+ const isBackspaceByte = rawKey.current === "\x7F" || rawKey.current === "\b";
15293
+ const intent = readKey(input, { ...key, backspace: key.backspace || isBackspaceByte, delete: key.delete && !isBackspaceByte });
14729
15294
  if (intent.verb === "text:type") {
14730
15295
  burst.current += intent.text ?? "";
14731
15296
  armChord(NO_CHORD);
@@ -14862,7 +15427,8 @@ function TerminalConsole(props) {
14862
15427
  return;
14863
15428
  }
14864
15429
  case "flow:cycleModel":
14865
- conductor.cycleModel?.("");
15430
+ conductor.cycleModel?.("", intent.direction ?? 1);
15431
+ pulse();
14866
15432
  return;
14867
15433
  case "model:cycleThinking": {
14868
15434
  const level = conductor.cycleThinkingLevel();
@@ -14905,10 +15471,10 @@ function TerminalConsole(props) {
14905
15471
  return;
14906
15472
  }
14907
15473
  case "input:pasteImage": {
14908
- const path4 = readClipboardImage();
14909
- if (path4 !== null) {
14910
- const sep = state.buffer.length > 0 && !/\s$/.test(state.buffer) ? " " : "";
14911
- dispatch2({ type: "buffer:insert", text: `${sep}${path4}` });
15474
+ const path5 = readClipboardImage();
15475
+ if (path5 !== null) {
15476
+ const sep2 = state.buffer.length > 0 && !/\s$/.test(state.buffer) ? " " : "";
15477
+ dispatch2({ type: "buffer:insert", text: `${sep2}${path5}` });
14912
15478
  } else {
14913
15479
  dispatch2({
14914
15480
  type: "status:set",
@@ -14918,7 +15484,7 @@ function TerminalConsole(props) {
14918
15484
  return;
14919
15485
  }
14920
15486
  case "input:externalEditor": {
14921
- const edited = openInExternalEditor(state.buffer);
15487
+ const edited = openInExternalEditor(expandMarkers(state.buffer, paste.current), { terminal: { stdin, stdout } });
14922
15488
  if (edited !== null) {
14923
15489
  dispatch2({ type: "buffer:set", text: edited });
14924
15490
  }
@@ -14932,9 +15498,30 @@ function TerminalConsole(props) {
14932
15498
  case "view:expandTools":
14933
15499
  setExpandTools((current) => !current);
14934
15500
  return;
14935
- case "flow:suspend":
14936
- process.kill(process.pid, "SIGTSTP");
15501
+ case "flow:suspend": {
15502
+ if (process.platform === "win32") {
15503
+ dispatch2({ type: "status:set", status: { kind: "warning", text: "Process suspension is not supported on Windows." } });
15504
+ return;
15505
+ }
15506
+ const wasRaw = stdin.isRaw === true;
15507
+ const resume = () => {
15508
+ process.off("SIGCONT", resume);
15509
+ resumeHandler.current = null;
15510
+ stdin.setRawMode?.(wasRaw);
15511
+ if (stdout.isTTY) stdout.write("\x1B[?25l");
15512
+ dispatch2({ type: "status:set", status: { kind: "info", text: "Resumed console." } });
15513
+ };
15514
+ resumeHandler.current = resume;
15515
+ process.once("SIGCONT", resume);
15516
+ stdin.setRawMode?.(false);
15517
+ if (stdout.isTTY) stdout.write("\x1B[?25h");
15518
+ try {
15519
+ process.kill(process.pid, "SIGTSTP");
15520
+ } catch {
15521
+ resume();
15522
+ }
14937
15523
  return;
15524
+ }
14938
15525
  case "none":
14939
15526
  default:
14940
15527
  return;
@@ -14942,14 +15529,23 @@ function TerminalConsole(props) {
14942
15529
  },
14943
15530
  { isActive: state.modal.kind === "none" && !agentsOpen }
14944
15531
  );
14945
- useInput4(
15532
+ useInput6((input, key) => {
15533
+ if (!key.ctrl || input !== "c") return;
15534
+ if (state.busy || conductor.isBusy?.() || conductor.snapshot().phase === "condensing") {
15535
+ denyAllApprovals();
15536
+ conductor.abort();
15537
+ }
15538
+ dispatch2({ type: "modal:close" });
15539
+ setAgentsOpen(false);
15540
+ }, { isActive: state.modal.kind !== "none" || agentsOpen });
15541
+ useInput6(
14946
15542
  (_rawInput, key) => {
14947
15543
  if (key.tab && key.shift) cyclePermission();
14948
15544
  },
14949
15545
  { isActive: state.modal.kind === "approval" }
14950
15546
  );
14951
15547
  const seeded = useRef3(false);
14952
- useEffect7(() => {
15548
+ useEffect9(() => {
14953
15549
  if (seeded.current) return;
14954
15550
  seeded.current = true;
14955
15551
  const opening = conductor.permissionMode();
@@ -14983,7 +15579,7 @@ function TerminalConsole(props) {
14983
15579
  const taskPanelRows = showTaskPanel ? 2 + Math.min(pendingMessages.length, 8) : 0;
14984
15580
  const workingIndicatorRows = state.busy ? 2 : 0;
14985
15581
  const maxItems = transcriptHeight(
14986
- terminalRows,
15582
+ renderRows,
14987
15583
  modalOpen,
14988
15584
  agentPanelRows(agentRuns) + streamingAnswerRows + taskPanelRows + workingIndicatorRows
14989
15585
  );
@@ -14992,7 +15588,7 @@ function TerminalConsole(props) {
14992
15588
  const startupMap = useMemo2(
14993
15589
  () => gatherStartup({
14994
15590
  cwd: process.cwd(),
14995
- home: homedir7(),
15591
+ home: homedir6(),
14996
15592
  version: VERSION,
14997
15593
  lastSeenVersion
14998
15594
  }),
@@ -15001,8 +15597,8 @@ function TerminalConsole(props) {
15001
15597
  const quietStartup = (services?.settings.get("quietStartup") ?? false) && !verbose;
15002
15598
  const sweep = (services?.settings.get("logoSweep") ?? false) && !(services?.settings.get("reducedMotion") ?? false) && Boolean(process.stdout.isTTY);
15003
15599
  const welcomeSeed = PINNED_WELCOME_SEED;
15004
- const [welcomeName, setWelcomeName] = useState8(void 0);
15005
- useEffect7(() => {
15600
+ const [welcomeName, setWelcomeName] = useState10(void 0);
15601
+ useEffect9(() => {
15006
15602
  if (!services) return;
15007
15603
  let live3 = true;
15008
15604
  const provider = services.settings.get("defaultProvider");
@@ -15014,119 +15610,127 @@ function TerminalConsole(props) {
15014
15610
  live3 = false;
15015
15611
  };
15016
15612
  }, [services]);
15017
- const authedProviders = useAuthenticatedProviders(services, state.status);
15613
+ const authRefreshKey = useMemo2(() => ({ status: state.status, modal: state.modal.kind }), [state.status, state.modal.kind]);
15614
+ const authedProviders = useAuthenticatedProviders(services, authRefreshKey);
15018
15615
  const boundModelId = conductor.snapshot().modelId;
15019
15616
  const boundProvider = boundModelId.includes("/") ? boundModelId.split("/")[0] : boundModelId;
15020
15617
  const modelAuthed = authedProviders === null || authedProviders.has(boundProvider);
15021
15618
  const bannerModelId = modelAuthed ? boundModelId : "no authenticated model \u2014 run /login";
15022
15619
  const statusBarSnapshot = modelAuthed || !snapshot.model ? snapshot : { ...snapshot, model: { ...snapshot.model, provider: "no model", id: "run /login" } };
15023
- return jsxs10(Box10, {
15620
+ return jsx14(Box12, {
15024
15621
  flexDirection: "column",
15025
- children: [
15026
- agentsOpen ? jsx13(AgentsView, {
15027
- theme: adapter,
15028
- runs: agentRuns,
15029
- activity: subagentActivity,
15030
- tokens: taskTokens,
15031
- onExit: () => setAgentsOpen(false)
15032
- }) : jsxs10(Fragment4, {
15033
- children: [
15034
- jsx13(Banner, {
15035
- theme: adapter,
15036
- modelId: bannerModelId,
15037
- workspace: process.cwd(),
15038
- version: VERSION,
15039
- verbose,
15040
- quiet: quietStartup,
15041
- compact: false,
15042
- started: liveMessages.length > 0,
15043
- branch,
15044
- name: welcomeName,
15045
- sweep,
15046
- startup: startupMap,
15047
- variantSeed: welcomeSeed
15048
- }),
15049
- compactNote !== null ? jsxs10(Box10, {
15050
- flexDirection: "column",
15051
- borderStyle: "round",
15052
- borderColor: adapter.colors["borderMuted"],
15053
- paddingX: 1,
15054
- marginBottom: 1,
15055
- children: [
15056
- jsx13(Text10, {
15057
- children: adapter.color("accent", "\u25C6 Context condensed \u2014 fresh session, history summarized")
15058
- }),
15059
- compactNote.length > 0 ? jsxs10(Fragment4, {
15060
- children: [
15061
- jsx13(Text10, { children: adapter.muted("Files carried into the new context:") }),
15062
- compactNote.map(
15063
- (file, index) => jsx13(Text10, { children: adapter.muted(` \xB7 ${file}`) }, index)
15064
- )
15065
- ]
15066
- }) : jsx13(Text10, {
15067
- children: adapter.muted("No files re-attached; the summary digest carries the history.")
15068
- })
15069
- ]
15070
- }) : null,
15071
- jsx13(MessageList, {
15072
- messages: liveMessages,
15073
- theme: adapter,
15074
- showThinking: state.showReasoning,
15075
- showImages: state.showImages,
15076
- expandToolOutputs: expandTools,
15077
- maxItems,
15078
- displayBlocks: [...state.blocks]
15079
- }),
15080
- liveAnswer.current !== null && answerText.current ? jsx13(Box10, {
15081
- flexDirection: "column",
15082
- marginBottom: 1,
15083
- children: jsx13(StreamingMarkdown, { theme: adapter, children: answerText.current })
15084
- }) : null,
15085
- showTaskPanel ? jsx13(TaskPanel, {
15086
- theme: adapter,
15087
- snapshot,
15088
- toolExecutions: SHOW_TOOL_ACTIVITY ? toolExecutions : {},
15089
- expandToolOutputs: expandTools,
15090
- pendingMessages
15091
- }) : null,
15092
- jsx13(WorkingIndicator, {
15093
- theme: adapter,
15094
- busy: state.busy || conductor.snapshot().phase === "condensing",
15095
- phase: conductor.snapshot().phase
15096
- }),
15097
- jsx13(Composer, {
15098
- theme: adapter,
15099
- buffer: state.buffer,
15100
- caret: state.caret,
15101
- busy: state.busy,
15102
- suggestions,
15103
- highlight
15104
- }),
15105
- jsx13(StatusBar, {
15106
- theme: adapter,
15107
- snapshot: statusBarSnapshot,
15108
- branch,
15109
- providerCount,
15110
- status: state.status,
15111
- permissionMode: conductor.permissionMode()
15112
- }),
15113
- jsx13(BackgroundAgents, { theme: adapter, runs: agentRuns, tokens: taskTokens })
15114
- ]
15115
- }),
15116
- jsx13(OverlayHost, {
15117
- modal: state.modal,
15118
- services,
15622
+ width: terminalColumns,
15623
+ height: renderRows,
15624
+ overflow: "hidden",
15625
+ children: modalOpen ? jsx14(ScrollViewport, { follow: false, children: jsx14(OverlayHost, {
15626
+ modal: state.modal,
15627
+ services,
15628
+ theme: adapter,
15629
+ dispatch: dispatch2,
15630
+ closeModal: () => dispatch2({ type: "modal:close" })
15631
+ }) }, state.modal.kind) : agentsOpen ? jsx14(ScrollViewport, { follow: false, children: jsx14(AgentsView, {
15632
+ theme: adapter,
15633
+ runs: agentRuns,
15634
+ activity: subagentActivity,
15635
+ tokens: taskTokens,
15636
+ onExit: () => setAgentsOpen(false)
15637
+ }) }, "agents") : jsxs10(Fragment4, { children: [
15638
+ terminalRows >= 18 ? jsx14(Box12, { flexShrink: 0, flexDirection: "column", children: jsx14(Banner, {
15119
15639
  theme: adapter,
15120
- dispatch: dispatch2,
15121
- closeModal: () => dispatch2({ type: "modal:close" })
15122
- })
15123
- ]
15640
+ modelId: bannerModelId,
15641
+ workspace: process.cwd(),
15642
+ version: VERSION,
15643
+ verbose,
15644
+ quiet: quietStartup,
15645
+ compact: liveMessages.length > 0 || terminalRows < 32,
15646
+ started: liveMessages.length > 0,
15647
+ branch,
15648
+ name: welcomeName,
15649
+ sweep,
15650
+ startup: startupMap,
15651
+ variantSeed: welcomeSeed
15652
+ }) }) : null,
15653
+ jsx14(ScrollViewport, { children: jsxs10(Fragment4, { children: [
15654
+ compactNote !== null ? jsx14(Text11, { children: adapter.muted(
15655
+ `Context condensed.${compactNote.length ? " Files carried: " + compactNote.join(", ") : ""}`
15656
+ ) }) : null,
15657
+ jsx14(MessageList, {
15658
+ messages: liveMessages,
15659
+ theme: adapter,
15660
+ showThinking: state.showReasoning,
15661
+ showImages: state.showImages,
15662
+ expandToolOutputs: expandTools,
15663
+ maxItems: liveMessages.length + state.blocks.length,
15664
+ displayBlocks: [...state.blocks]
15665
+ }),
15666
+ state.showReasoning && liveReason.current !== null && reasonText.current ? jsx14(Box12, {
15667
+ flexDirection: "column",
15668
+ marginBottom: 1,
15669
+ children: jsx14(StreamingMarkdown, { theme: adapter, dim: true, children: reasonText.current })
15670
+ }) : null,
15671
+ liveAnswer.current !== null && answerText.current ? jsx14(Box12, {
15672
+ flexDirection: "column",
15673
+ marginBottom: 1,
15674
+ children: jsx14(StreamingMarkdown, { theme: adapter, children: answerText.current })
15675
+ }) : null,
15676
+ showTaskPanel ? jsx14(TaskPanel, {
15677
+ theme: adapter,
15678
+ snapshot,
15679
+ toolExecutions: SHOW_TOOL_ACTIVITY ? toolExecutions : {},
15680
+ expandToolOutputs: expandTools,
15681
+ pendingMessages
15682
+ }) : null,
15683
+ jsx14(BackgroundAgents, { theme: adapter, runs: agentRuns, tokens: taskTokens })
15684
+ ] }) }, snapshot.sessionId),
15685
+ jsx14(Box12, { flexDirection: "column", flexShrink: 0, children: jsxs10(Fragment4, { children: [
15686
+ jsx14(WorkingIndicator, { theme: adapter, busy: state.busy || conductor.snapshot().phase === "condensing", phase: conductor.snapshot().phase, reducedMotion: services?.settings.get("reducedMotion") ?? false }),
15687
+ jsx14(Composer, {
15688
+ theme: adapter,
15689
+ buffer: state.buffer,
15690
+ caret: state.caret,
15691
+ busy: state.busy,
15692
+ suggestions,
15693
+ highlight,
15694
+ paddingX: services?.settings.get("editorPaddingX") ?? 1
15695
+ }),
15696
+ terminalRows >= 12 ? jsx14(StatusBar, {
15697
+ theme: adapter,
15698
+ snapshot: statusBarSnapshot,
15699
+ branch,
15700
+ providerCount,
15701
+ status: state.status,
15702
+ permissionMode: conductor.permissionMode()
15703
+ }) : null
15704
+ ] }) })
15705
+ ] })
15124
15706
  });
15125
15707
  }
15126
15708
 
15127
15709
  // src/console/mount.ts
15128
15710
  import { createElement as createElement2 } from "indusagi/react-host";
15129
15711
  import { render as render2 } from "indusagi/react-host/ink";
15712
+ var SYNC_OUTPUT_BEGIN = "\x1B[?2026h";
15713
+ var SYNC_OUTPUT_END = "\x1B[?2026l";
15714
+ function supportsSynchronizedOutput(stream) {
15715
+ if (!stream.isTTY || process.env["INDUS_TUI_SYNC_OUTPUT"] === "0") return false;
15716
+ const term = process.env["TERM"] ?? "";
15717
+ return process.env["TMUX"] === void 0 && process.env["STY"] === void 0 && !term.startsWith("screen") && !term.startsWith("tmux");
15718
+ }
15719
+ function synchronizedOutputStream(stream, enabled = supportsSynchronizedOutput(stream)) {
15720
+ if (!enabled) return stream;
15721
+ const write = (chunk, encodingOrCallback, callback) => {
15722
+ const framed = typeof chunk === "string" ? `${SYNC_OUTPUT_BEGIN}${chunk}${SYNC_OUTPUT_END}` : Buffer.concat([Buffer.from(SYNC_OUTPUT_BEGIN), Buffer.from(chunk), Buffer.from(SYNC_OUTPUT_END)]);
15723
+ if (typeof encodingOrCallback === "function") return stream.write(framed, encodingOrCallback);
15724
+ return stream.write(framed, encodingOrCallback, callback);
15725
+ };
15726
+ return new Proxy(stream, {
15727
+ get(target, property) {
15728
+ if (property === "write") return write;
15729
+ const value = Reflect.get(target, property, target);
15730
+ return typeof value === "function" ? value.bind(target) : value;
15731
+ }
15732
+ });
15733
+ }
15130
15734
  async function mountConsole(conductor, opts = {}) {
15131
15735
  const theme = resolveTheme(opts.scheme ?? null);
15132
15736
  const slash = opts.slash ?? DEFAULT_SLASH_REGISTRY;
@@ -15144,15 +15748,23 @@ async function mountConsole(conductor, opts = {}) {
15144
15748
  instance.unmount();
15145
15749
  }
15146
15750
  };
15147
- const instance = render2(createElement2(TerminalConsole, props));
15148
- await instance.waitUntilExit();
15149
- return { clean: exited };
15751
+ const wasRaw = process.stdin.isRaw === true;
15752
+ const stdout = synchronizedOutputStream(process.stdout);
15753
+ const instance = render2(createElement2(TerminalConsole, props), { exitOnCtrlC: false, stdout });
15754
+ try {
15755
+ await instance.waitUntilExit();
15756
+ return { clean: exited };
15757
+ } finally {
15758
+ instance.unmount();
15759
+ if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw);
15760
+ if (process.stdout.isTTY) process.stdout.write("\x1B[?25h");
15761
+ }
15150
15762
  }
15151
15763
 
15152
15764
  // src/boot/runners/session.ts
15153
- import { existsSync as existsSync9, readFileSync as readFileSync17 } from "node:fs";
15154
- import { join as join22 } from "node:path";
15155
- import { homedir as homedir11 } from "node:os";
15765
+ import { existsSync as existsSync8, readFileSync as readFileSync17 } from "node:fs";
15766
+ import { isAbsolute as isAbsolute6, join as join22, resolve as resolve10 } from "node:path";
15767
+ import { homedir as homedir10 } from "node:os";
15156
15768
  import { createMCPAgentToolFactory as createMCPAgentToolFactory4, loadMCPConfig as loadMCPConfig2, MCPClientPool as MCPClientPool2 } from "indusagi/mcp";
15157
15769
 
15158
15770
  // src/boot/runners/delegate-runner.ts
@@ -15209,7 +15821,7 @@ async function acquireTaskSlot() {
15209
15821
  if (activeTasks < MAX_PARALLEL_TASKS) {
15210
15822
  activeTasks++;
15211
15823
  } else {
15212
- await new Promise((resolve9) => taskQueue.push(resolve9));
15824
+ await new Promise((resolve11) => taskQueue.push(resolve11));
15213
15825
  }
15214
15826
  let released = false;
15215
15827
  return () => {
@@ -15314,7 +15926,7 @@ ${request.context}` : request.objective;
15314
15926
  }
15315
15927
 
15316
15928
  // src/boot/runners/checkpoint.ts
15317
- import { mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
15929
+ import { mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
15318
15930
  import { dirname as dirname22, normalize as normalize2 } from "node:path";
15319
15931
  var CHECKPOINT_HANDLE_KEY = "checkpoint";
15320
15932
  var ROOT_NODE_ID = "__root__";
@@ -15382,7 +15994,7 @@ var CheckpointStore = class {
15382
15994
  snapshotFor(nodeId) {
15383
15995
  const paths = this.byNode.get(nodeId);
15384
15996
  if (paths === void 0) return [];
15385
- return [...paths].map(([path4, previous]) => ({ path: path4, previous }));
15997
+ return [...paths].map(([path5, previous]) => ({ path: path5, previous }));
15386
15998
  }
15387
15999
  /** Whether a node has ANY recorded file snapshot (the picker's restore gate). */
15388
16000
  hasSnapshot(nodeId) {
@@ -15408,15 +16020,15 @@ var CheckpointStore = class {
15408
16020
  restore(nodeId) {
15409
16021
  const snapshots = this.snapshotFor(nodeId);
15410
16022
  const restored = [];
15411
- for (const { path: path4, previous } of snapshots) {
16023
+ for (const { path: path5, previous } of snapshots) {
15412
16024
  try {
15413
16025
  if (previous === null) {
15414
- rmSync(path4, { force: true });
16026
+ rmSync2(path5, { force: true });
15415
16027
  } else {
15416
- mkdirSync3(dirname22(path4), { recursive: true });
15417
- writeFileSync6(path4, previous, "utf8");
16028
+ mkdirSync3(dirname22(path5), { recursive: true });
16029
+ writeFileSync7(path5, previous, "utf8");
15418
16030
  }
15419
- restored.push(path4);
16031
+ restored.push(path5);
15420
16032
  } catch {
15421
16033
  }
15422
16034
  }
@@ -15463,15 +16075,15 @@ function createReadStateStore() {
15463
16075
  }
15464
16076
 
15465
16077
  // src/integrations/zoho/config.ts
15466
- import { existsSync as existsSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync4 } from "node:fs";
15467
- import { homedir as homedir8 } from "node:os";
15468
- import { dirname as dirname5, join as join18 } from "node:path";
16078
+ import { existsSync as existsSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4 } from "node:fs";
16079
+ import { homedir as homedir7 } from "node:os";
16080
+ import { dirname as dirname6, join as join17 } from "node:path";
15469
16081
  var ENV_URL = "ZOHO_MCP_URL";
15470
16082
  var ENV_DISABLE = "ZOHO_DISABLED";
15471
- function readJsonFile(path4) {
16083
+ function readJsonFile(path5) {
15472
16084
  try {
15473
- if (!existsSync5(path4)) return void 0;
15474
- const raw = readFileSync12(path4, "utf8");
16085
+ if (!existsSync4(path5)) return void 0;
16086
+ const raw = readFileSync12(path5, "utf8");
15475
16087
  const parsed = JSON.parse(raw);
15476
16088
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
15477
16089
  return void 0;
@@ -15490,11 +16102,11 @@ function configFromRecord(rec) {
15490
16102
  const auth = rec.auth === "none" || rec.auth === "oauth" || rec.auth === "auto" ? rec.auth : "auto";
15491
16103
  return { url, id, enabled, auth };
15492
16104
  }
15493
- function profileZohoPath(home = homedir8()) {
15494
- return join18(home, ".indusagi", "agent", "zoho.json");
16105
+ function profileZohoPath(home = homedir7()) {
16106
+ return join17(home, ".indusagi", "agent", "zoho.json");
15495
16107
  }
15496
16108
  function projectZohoPath(cwd) {
15497
- return join18(cwd, ".indusagi", "zoho.json");
16109
+ return join17(cwd, ".indusagi", "zoho.json");
15498
16110
  }
15499
16111
  function resolveZohoConfig(opts = {}) {
15500
16112
  const env2 = opts.env ?? process.env;
@@ -15506,7 +16118,7 @@ function resolveZohoConfig(opts = {}) {
15506
16118
  const fromEnv = env2[ENV_URL]?.trim();
15507
16119
  const fromProject = configFromRecord(readJsonFile(projectZohoPath(cwd)));
15508
16120
  const fromProfile = configFromRecord(
15509
- readJsonFile(profileZohoPath(opts.home ?? homedir8()))
16121
+ readJsonFile(profileZohoPath(opts.home ?? homedir7()))
15510
16122
  );
15511
16123
  const base = (fromArg ? { url: fromArg, id: "zoho", enabled: true, auth: "auto" } : void 0) ?? (fromEnv ? { url: fromEnv, id: "zoho", enabled: true, auth: "auto" } : void 0) ?? fromProject ?? fromProfile;
15512
16124
  if (!base) return void 0;
@@ -15518,9 +16130,9 @@ function resolveZohoConfig(opts = {}) {
15518
16130
  }
15519
16131
 
15520
16132
  // src/integrations/zoho/attach.ts
15521
- import { existsSync as existsSync6, readFileSync as readFileSync13 } from "node:fs";
15522
- import { homedir as homedir9 } from "node:os";
15523
- import { join as join19 } from "node:path";
16133
+ import { existsSync as existsSync5, readFileSync as readFileSync13 } from "node:fs";
16134
+ import { homedir as homedir8 } from "node:os";
16135
+ import { join as join18 } from "node:path";
15524
16136
  import { connectZohoMcp } from "indusagi/zoho";
15525
16137
  import { createMCPAgentToolFactory as createMCPAgentToolFactory2 } from "indusagi/mcp";
15526
16138
  var live = null;
@@ -15529,15 +16141,15 @@ var cancelConnect = false;
15529
16141
  var lastAuthUrl;
15530
16142
  function zohoTokenPath(id = "zoho") {
15531
16143
  const xdg = process.env.XDG_CONFIG_HOME;
15532
- const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir9();
15533
- const dir = xdg && xdg.length > 0 ? join19(xdg, "indusagi", "credentials", "mcp") : join19(home, ".config", "indusagi", "credentials", "mcp");
15534
- return join19(dir, `${id.replace(/[^a-zA-Z0-9._-]/g, "-")}.json`);
16144
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir8();
16145
+ const dir = xdg && xdg.length > 0 ? join18(xdg, "indusagi", "credentials", "mcp") : join18(home, ".config", "indusagi", "credentials", "mcp");
16146
+ return join18(dir, `${id.replace(/[^a-zA-Z0-9._-]/g, "-")}.json`);
15535
16147
  }
15536
16148
  function hasUsableZohoTokens(id = "zoho") {
15537
- const path4 = zohoTokenPath(id);
16149
+ const path5 = zohoTokenPath(id);
15538
16150
  try {
15539
- if (!existsSync6(path4)) return false;
15540
- const raw = readFileSync13(path4, "utf8");
16151
+ if (!existsSync5(path5)) return false;
16152
+ const raw = readFileSync13(path5, "utf8");
15541
16153
  const parsed = JSON.parse(raw);
15542
16154
  return typeof parsed?.tokens?.access_token === "string" && parsed.tokens.access_token.length > 0;
15543
16155
  } catch {
@@ -15676,17 +16288,18 @@ async function loadZohoAgentTools(opts) {
15676
16288
  }
15677
16289
 
15678
16290
  // src/integrations/sarvam/config.ts
15679
- import { existsSync as existsSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "node:fs";
15680
- import { homedir as homedir10 } from "node:os";
15681
- import { dirname as dirname6, join as join20 } from "node:path";
16291
+ import { existsSync as existsSync6, readFileSync as readFileSync14 } from "node:fs";
16292
+ import { homedir as homedir9 } from "node:os";
16293
+ import { join as join19 } from "node:path";
15682
16294
  var ENV_KEY = "SARVAM_API_KEY";
15683
16295
  var ENV_DISABLE2 = "SARVAM_DISABLED";
15684
16296
  var ENV_BASE_URL = "SARVAM_API_BASE_URL";
15685
16297
  var ENV_BASE_PATH = "SARVAM_MCP_BASE_PATH";
15686
- function readJsonFile2(path4) {
16298
+ function readJsonFile2(path5, sensitive = false) {
15687
16299
  try {
15688
- if (!existsSync7(path4)) return void 0;
15689
- const raw = readFileSync14(path4, "utf8");
16300
+ if (!sensitive && !existsSync6(path5)) return void 0;
16301
+ const raw = sensitive ? readPrivateFileSync(path5) : readFileSync14(path5, "utf8");
16302
+ if (raw === null) return void 0;
15690
16303
  const parsed = JSON.parse(raw);
15691
16304
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
15692
16305
  return void 0;
@@ -15696,11 +16309,11 @@ function readJsonFile2(path4) {
15696
16309
  return void 0;
15697
16310
  }
15698
16311
  }
15699
- function configFromRecord2(rec) {
16312
+ function configFromRecord2(rec, includeApiKey = true) {
15700
16313
  if (!rec) return void 0;
15701
16314
  const enabled = rec.enabled === false ? false : true;
15702
16315
  const id = typeof rec.id === "string" && rec.id.trim() ? rec.id.trim() : "sarvam";
15703
- const apiKey = typeof rec.apiKey === "string" ? rec.apiKey.trim() : void 0;
16316
+ const apiKey = includeApiKey && typeof rec.apiKey === "string" ? rec.apiKey.trim() : void 0;
15704
16317
  const baseUrl = typeof rec.baseUrl === "string" ? rec.baseUrl.trim() : void 0;
15705
16318
  const basePath = typeof rec.basePath === "string" ? rec.basePath.trim() : void 0;
15706
16319
  const command = typeof rec.command === "string" ? rec.command.trim() : void 0;
@@ -15713,11 +16326,11 @@ function configFromRecord2(rec) {
15713
16326
  ...command ? { command } : {}
15714
16327
  };
15715
16328
  }
15716
- function profileSarvamPath(home = homedir10()) {
15717
- return join20(home, ".indusagi", "agent", "sarvam.json");
16329
+ function profileSarvamPath(home = homedir9()) {
16330
+ return join19(home, ".indusagi", "agent", "sarvam.json");
15718
16331
  }
15719
16332
  function projectSarvamPath(cwd) {
15720
- return join20(cwd, ".indusagi", "sarvam.json");
16333
+ return join19(cwd, ".indusagi", "sarvam.json");
15721
16334
  }
15722
16335
  function resolveSarvamConfig(opts = {}) {
15723
16336
  const env2 = opts.env ?? process.env;
@@ -15727,9 +16340,9 @@ function resolveSarvamConfig(opts = {}) {
15727
16340
  }
15728
16341
  const fromArgKey = opts.apiKey?.trim();
15729
16342
  const fromEnvKey = env2[ENV_KEY]?.trim();
15730
- const fromProject = configFromRecord2(readJsonFile2(projectSarvamPath(cwd)));
16343
+ const fromProject = configFromRecord2(readJsonFile2(projectSarvamPath(cwd)), false);
15731
16344
  const fromProfile = configFromRecord2(
15732
- readJsonFile2(profileSarvamPath(opts.home ?? homedir10()))
16345
+ readJsonFile2(profileSarvamPath(opts.home ?? homedir9()), true)
15733
16346
  );
15734
16347
  const fileBase = fromProject ?? fromProfile;
15735
16348
  const apiKey = fromArgKey || fromEnvKey || fileBase?.apiKey;
@@ -15854,8 +16467,6 @@ async function loadSarvamAgentTools(opts) {
15854
16467
  }
15855
16468
 
15856
16469
  // src/boot/auth-vault.ts
15857
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
15858
- import { dirname as dirname7 } from "node:path";
15859
16470
  import {
15860
16471
  ensureFreshOAuthCredentials,
15861
16472
  getOAuthProvider as getOAuthProvider3
@@ -15879,8 +16490,9 @@ function normaliseRecord(value) {
15879
16490
  return void 0;
15880
16491
  }
15881
16492
  function readAuthFile(filePath) {
16493
+ const text = readPrivateFileSync(filePath);
16494
+ if (text === null) return {};
15882
16495
  try {
15883
- const text = readFileSync15(filePath, "utf8");
15884
16496
  const parsed = JSON.parse(text);
15885
16497
  if (parsed === null || typeof parsed !== "object") return {};
15886
16498
  const file = {};
@@ -15899,9 +16511,8 @@ function readAuthFile(filePath) {
15899
16511
  }
15900
16512
  }
15901
16513
  function writeAuthFile(filePath, data) {
15902
- mkdirSync6(dirname7(filePath), { recursive: true });
15903
- writeFileSync9(filePath, `${JSON.stringify(data, null, 2)}
15904
- `, { mode: 384 });
16514
+ atomicWritePrivateFileSync(filePath, `${JSON.stringify(data, null, 2)}
16515
+ `);
15905
16516
  }
15906
16517
  function toCredentials(record) {
15907
16518
  const { kind: _kind, isDefault: _isDefault, ...credentials } = record;
@@ -15995,8 +16606,8 @@ function createAuthVault(authPath) {
15995
16606
  }
15996
16607
 
15997
16608
  // src/boot/runners/memdir.ts
15998
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "node:fs";
15999
- import { join as join21 } from "node:path";
16609
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
16610
+ import { join as join20 } from "node:path";
16000
16611
  var MEMORY_ENTRYPOINT = "MEMORY.md";
16001
16612
  var MAX_ENTRYPOINT_LINES = 200;
16002
16613
  var MAX_ENTRYPOINT_BYTES = 25e3;
@@ -16022,27 +16633,27 @@ function truncateEntrypointContent(raw) {
16022
16633
  }
16023
16634
  function memoryDirFor(workspace, cwd) {
16024
16635
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
16025
- return join21(workspace.profileDir, "memory", `--${slug}--`);
16636
+ return join20(workspace.profileDir, "memory", `--${slug}--`);
16026
16637
  }
16027
16638
  var DiskMemoryStore = class {
16028
16639
  memDir;
16029
16640
  file;
16030
16641
  constructor(memDir) {
16031
16642
  this.memDir = memDir;
16032
- this.file = join21(memDir, MEMORY_ENTRYPOINT);
16643
+ this.file = join20(memDir, MEMORY_ENTRYPOINT);
16033
16644
  }
16034
16645
  /** The current note, or `""` when no file has been written yet. */
16035
16646
  read() {
16036
16647
  try {
16037
- return existsSync8(this.file) ? readFileSync16(this.file, "utf8") : "";
16648
+ return existsSync7(this.file) ? readFileSync15(this.file, "utf8") : "";
16038
16649
  } catch {
16039
16650
  return "";
16040
16651
  }
16041
16652
  }
16042
16653
  /** Overwrite the note, creating the memory directory as needed (mode 0o600). */
16043
16654
  replace(content) {
16044
- mkdirSync7(this.memDir, { recursive: true });
16045
- writeFileSync10(this.file, content, { mode: 384 });
16655
+ mkdirSync5(this.memDir, { recursive: true });
16656
+ writeFileSync9(this.file, content, { mode: 384 });
16046
16657
  }
16047
16658
  /** Add a single line to the end of the note (composed of read + replace). */
16048
16659
  append(line) {
@@ -16055,9 +16666,9 @@ ${line}`;
16055
16666
  function loadMemoryDoc(memDir) {
16056
16667
  let raw;
16057
16668
  try {
16058
- const file = join21(memDir, MEMORY_ENTRYPOINT);
16059
- if (!existsSync8(file)) return void 0;
16060
- raw = readFileSync16(file, "utf8");
16669
+ const file = join20(memDir, MEMORY_ENTRYPOINT);
16670
+ if (!existsSync7(file)) return void 0;
16671
+ raw = readFileSync15(file, "utf8");
16061
16672
  } catch {
16062
16673
  return void 0;
16063
16674
  }
@@ -16068,11 +16679,104 @@ function loadMemoryDoc(memDir) {
16068
16679
  }
16069
16680
 
16070
16681
  // src/boot/runners/addon-wiring.ts
16682
+ import { createHash } from "node:crypto";
16683
+ import { lstatSync as lstatSync3, readFileSync as readFileSync16, readdirSync as readdirSync5, realpathSync as realpathSync3 } from "node:fs";
16684
+ import { join as join21, relative, resolve as resolve9 } from "node:path";
16685
+ var TRUST_FILE = "addon-trust.json";
16686
+ function hashAddonTree(addonsDir) {
16687
+ let root;
16688
+ try {
16689
+ const rootStat = lstatSync3(addonsDir);
16690
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
16691
+ root = realpathSync3(addonsDir);
16692
+ } catch (error) {
16693
+ if (error.code === "ENOENT") return null;
16694
+ throw error;
16695
+ }
16696
+ const files = [];
16697
+ const visit = (dir) => {
16698
+ for (const entry of readdirSync5(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
16699
+ const path5 = join21(dir, entry.name);
16700
+ const info2 = lstatSync3(path5);
16701
+ if (info2.isSymbolicLink()) throw new Error(`Refusing symlink in project addon tree: ${path5}`);
16702
+ if (info2.isDirectory()) visit(path5);
16703
+ else if (info2.isFile()) files.push(path5);
16704
+ else throw new Error(`Refusing special file in project addon tree: ${path5}`);
16705
+ }
16706
+ };
16707
+ visit(root);
16708
+ const hash = createHash("sha256");
16709
+ for (const file of files) {
16710
+ hash.update(relative(root, file));
16711
+ hash.update("\0");
16712
+ hash.update(readFileSync16(file));
16713
+ hash.update("\0");
16714
+ }
16715
+ return hash.digest("hex");
16716
+ }
16717
+ function readTrustStore(path5) {
16718
+ const raw = readPrivateFileSync(path5);
16719
+ if (raw === null) return { version: 1, projects: [] };
16720
+ try {
16721
+ const parsed = JSON.parse(raw);
16722
+ if (parsed.version !== 1 || !Array.isArray(parsed.projects)) return { version: 1, projects: [] };
16723
+ return {
16724
+ version: 1,
16725
+ projects: parsed.projects.filter((record) => typeof record?.root === "string" && typeof record?.digest === "string" && typeof record?.approvedAt === "string")
16726
+ };
16727
+ } catch {
16728
+ return { version: 1, projects: [] };
16729
+ }
16730
+ }
16731
+ function persistTrust(path5, root, digest) {
16732
+ const store = readTrustStore(path5);
16733
+ const projects = store.projects.filter((record) => record.root !== root);
16734
+ projects.push({ root, digest, approvedAt: (/* @__PURE__ */ new Date()).toISOString() });
16735
+ atomicWritePrivateFileSync(path5, `${JSON.stringify({ version: 1, projects }, null, 2)}
16736
+ `);
16737
+ }
16071
16738
  async function buildAddonHost(ctx) {
16072
16739
  const host = createAddonHost({ handles: {} });
16073
- host.onFault(() => {
16740
+ host.onFault((fault) => {
16741
+ process.stderr.write(`Addon ${fault.kind} fault: ${fault.message}
16742
+ `);
16074
16743
  });
16075
- return host.loadAll({ workspace: ctx.invocation.cwd ?? process.cwd() });
16744
+ const workspace = resolve9(ctx.invocation.cwd ?? process.cwd());
16745
+ const addonsDir = join21(workspace, ".indus", "addons");
16746
+ let canonicalRoot;
16747
+ let digest;
16748
+ try {
16749
+ canonicalRoot = realpathSync3(workspace);
16750
+ digest = hashAddonTree(addonsDir);
16751
+ } catch (error) {
16752
+ process.stderr.write(`Project addons blocked: ${error instanceof Error ? error.message : String(error)}
16753
+ `);
16754
+ return host.loadAll({ explicitPaths: [] });
16755
+ }
16756
+ if (digest === null) return host.loadAll({ explicitPaths: [] });
16757
+ const trustPath = join21(ctx.workspace.profileDir, TRUST_FILE);
16758
+ const approvedNow = ctx.invocation.flags["trust-project-addons"] === true;
16759
+ let trusted = false;
16760
+ try {
16761
+ const store = readTrustStore(trustPath);
16762
+ trusted = store.projects.some((record) => record.root === canonicalRoot && record.digest === digest);
16763
+ if (!trusted && approvedNow) {
16764
+ persistTrust(trustPath, canonicalRoot, digest);
16765
+ trusted = true;
16766
+ }
16767
+ } catch (error) {
16768
+ process.stderr.write(`Project addons blocked: ${error instanceof Error ? error.message : String(error)}
16769
+ `);
16770
+ return host.loadAll({ explicitPaths: [] });
16771
+ }
16772
+ if (!trusted) {
16773
+ process.stderr.write(
16774
+ `Project addons are disabled until explicitly trusted for this exact content. Re-run with --trust-project-addons to approve ${canonicalRoot}.
16775
+ `
16776
+ );
16777
+ return host.loadAll({ explicitPaths: [] });
16778
+ }
16779
+ return host.loadAll({ workspace: canonicalRoot });
16076
16780
  }
16077
16781
  function wrapToolsWithAddons(tools, bundle) {
16078
16782
  const chain = bundle.interceptors;
@@ -16113,12 +16817,107 @@ function concatAddonTools(deck, bundle) {
16113
16817
  return fresh.length === 0 ? deck : [...deck, ...fresh];
16114
16818
  }
16115
16819
 
16820
+ // src/boot/runners/benchmark-integrity.ts
16821
+ import { appendFileSync } from "node:fs";
16822
+ import path4 from "node:path";
16823
+ var GUARDED_TOOLS = /* @__PURE__ */ new Set(["read", "write", "edit", "grep", "find", "ls", "bash"]);
16824
+ var PATH_KEYS = /* @__PURE__ */ new Set(["path", "file", "file_path", "directory", "dir", "cwd", "search_path"]);
16825
+ var FORBIDDEN_ABSOLUTE = [
16826
+ "/tests",
16827
+ "/solution",
16828
+ "/solutions",
16829
+ "/reference",
16830
+ "/reference_solution",
16831
+ "/reference-solutions",
16832
+ "/reward.txt",
16833
+ "/rewards",
16834
+ "/grader",
16835
+ "/verifier"
16836
+ ];
16837
+ var FORBIDDEN_BASENAMES = /* @__PURE__ */ new Set([
16838
+ "solution.sh",
16839
+ "solve.sh",
16840
+ "reward.txt",
16841
+ "expected_answer.txt",
16842
+ "expected_output.txt"
16843
+ ]);
16844
+ function normalizeCandidate(value, cwd) {
16845
+ return path4.resolve(cwd, value);
16846
+ }
16847
+ function forbiddenPath(value, cwd) {
16848
+ const resolved = normalizeCandidate(value, cwd);
16849
+ const base = path4.basename(resolved).toLowerCase();
16850
+ if (FORBIDDEN_BASENAMES.has(base)) return true;
16851
+ return FORBIDDEN_ABSOLUTE.some((prefix) => resolved === prefix || resolved.startsWith(`${prefix}/`));
16852
+ }
16853
+ function pathValues(args) {
16854
+ if (args === null || typeof args !== "object" || Array.isArray(args)) return [];
16855
+ const values = [];
16856
+ for (const [key, value] of Object.entries(args)) {
16857
+ if (PATH_KEYS.has(key) && typeof value === "string") values.push(value);
16858
+ }
16859
+ return values;
16860
+ }
16861
+ function forbiddenShell(command, cwd) {
16862
+ const lower = command.toLowerCase();
16863
+ if ([...FORBIDDEN_BASENAMES].some((name) => lower.includes(name))) return true;
16864
+ if (FORBIDDEN_ABSOLUTE.some((prefix) => new RegExp(`(^|[\\s'"=])${prefix.replaceAll("/", "\\/")}(?:[/\\s'";|&]|$)`, "i").test(command))) return true;
16865
+ if (/(^|[\s'"=])\.\.\/(?:tests|solutions?|reference(?:[-_]solutions?)?|grader|verifier)(?:[/\s'";|&]|$)/i.test(command)) return true;
16866
+ if (/\b(?:find|ls|grep|rg)\s+(?:-[^\s]+\s+)*\/(?:\s|$)/i.test(command)) return true;
16867
+ const retrievesOnline = /\b(?:curl|wget|git\s+clone|gh\s+repo\s+clone)\b/i.test(command);
16868
+ const benchmarkMaterial = /terminal[-_ ]bench|reference[-_ ]solution|solution\.sh|solve\.sh|benchmark[-_ ]solution/i.test(command);
16869
+ if (retrievesOnline && benchmarkMaterial) return true;
16870
+ return false;
16871
+ }
16872
+ function denialReason(tool, args, cwd) {
16873
+ if (tool === "bash") {
16874
+ const command = typeof args?.command === "string" ? args.command : "";
16875
+ if (forbiddenShell(command, cwd)) return "Terminal-Bench integrity guard blocked benchmark-owned or solution material.";
16876
+ return void 0;
16877
+ }
16878
+ if (pathValues(args).some((candidate) => forbiddenPath(candidate, cwd))) {
16879
+ return "Terminal-Bench integrity guard blocked benchmark-owned or solution material.";
16880
+ }
16881
+ return void 0;
16882
+ }
16883
+ function recordDenial(tool, callId, reason, env2) {
16884
+ const auditPath = env2.INDUSAGI_BENCHMARK_AUDIT_LOG ?? "/logs/agent/integrity-denials.jsonl";
16885
+ try {
16886
+ appendFileSync(auditPath, `${JSON.stringify({ type: "integrity_denial", tool, call_id: callId, reason })}
16887
+ `, { encoding: "utf8" });
16888
+ } catch {
16889
+ }
16890
+ }
16891
+ function wrapOne2(tool, cwd, env2) {
16892
+ const execute = tool.execute.bind(tool);
16893
+ return {
16894
+ ...tool,
16895
+ async execute(callId, args, signal, onUpdate) {
16896
+ const reason = denialReason(tool.name, args, cwd);
16897
+ if (reason !== void 0) {
16898
+ recordDenial(tool.name, callId, reason, env2);
16899
+ return {
16900
+ content: [{ type: "text", text: reason }],
16901
+ details: void 0,
16902
+ isError: true
16903
+ };
16904
+ }
16905
+ return execute(callId, args, signal, onUpdate);
16906
+ }
16907
+ };
16908
+ }
16909
+ function wrapToolsForTerminalBench(tools, cwd, env2 = process.env) {
16910
+ if (env2.INDUSAGI_BENCHMARK_PROFILE !== "terminal-bench") return tools;
16911
+ return tools.map((tool) => GUARDED_TOOLS.has(tool.name) ? wrapOne2(tool, cwd, env2) : tool);
16912
+ }
16913
+
16116
16914
  // src/boot/runners/session.ts
16117
16915
  function authenticatedProviders(ctx) {
16118
16916
  try {
16119
- const path4 = ctx.workspace.authPath;
16120
- if (!existsSync9(path4)) return [];
16121
- const data = JSON.parse(readFileSync17(path4, "utf8"));
16917
+ const path5 = ctx.workspace.authPath;
16918
+ const raw = readPrivateFileSync(path5);
16919
+ if (raw === null) return [];
16920
+ const data = JSON.parse(raw);
16122
16921
  return Object.keys(data).filter((id) => {
16123
16922
  const accounts = data[id];
16124
16923
  return id.length > 0 && accounts !== null && typeof accounts === "object" && Object.keys(accounts).length > 0;
@@ -16196,9 +16995,9 @@ var PROVIDER_ENV = {
16196
16995
  };
16197
16996
  function primeProviderEnv(ctx) {
16198
16997
  try {
16199
- const path4 = ctx.workspace.authPath;
16200
- if (!existsSync9(path4)) return;
16201
- const data = JSON.parse(readFileSync17(path4, "utf8"));
16998
+ const path5 = ctx.workspace.authPath;
16999
+ if (!existsSync8(path5)) return;
17000
+ const data = JSON.parse(readFileSync17(path5, "utf8"));
16202
17001
  for (const [provider, accounts] of Object.entries(data)) {
16203
17002
  if (provider.startsWith("_") || accounts === null || typeof accounts !== "object") continue;
16204
17003
  const envVar = PROVIDER_ENV[provider];
@@ -16242,7 +17041,7 @@ function resolveThinkingLevel(raw, modelId) {
16242
17041
  }
16243
17042
  function resolveText(value) {
16244
17043
  try {
16245
- if (existsSync9(value)) return readFileSync17(value, "utf8");
17044
+ if (existsSync8(value)) return readFileSync17(value, "utf8");
16246
17045
  } catch {
16247
17046
  }
16248
17047
  return value;
@@ -16251,7 +17050,7 @@ var PROFILE_DIR2 = ".indusagi";
16251
17050
  function skillRoots2(cwd) {
16252
17051
  return [
16253
17052
  { dir: join22(cwd, PROFILE_DIR2, "skills"), origin: "project" },
16254
- { dir: join22(homedir11(), PROFILE_DIR2, "skills"), origin: "user" }
17053
+ { dir: join22(homedir10(), PROFILE_DIR2, "skills"), origin: "user" }
16255
17054
  ];
16256
17055
  }
16257
17056
  function gatherModelSkills(cwd) {
@@ -16282,8 +17081,9 @@ function selectTools(cwd, inv, runner, memoryStore, checkpoint, workflowRunner)
16282
17081
  return visible.filter((tool) => allow.has(canon2(tool.name)));
16283
17082
  }
16284
17083
  function composeSystem(tools, inv, cwd, skills = [], memoryDoc) {
17084
+ const approvedExternalPaths = Array.isArray(inv.flags["allow-context-import"]) ? inv.flags["allow-context-import"].filter((path5) => typeof path5 === "string").map((path5) => isAbsolute6(path5) ? path5 : resolve10(cwd, path5)) : [];
16285
17085
  const contextDocs = [
16286
- ...gatherContextDocs(cwd, homedir11()),
17086
+ ...gatherContextDocs(cwd, homedir10(), { approvedExternalPaths }),
16287
17087
  ...memoryDoc ? [memoryDoc] : []
16288
17088
  ];
16289
17089
  let base = inv.system !== void 0 ? resolveText(inv.system) : composeBriefing({ tools, contextDocs, skills });
@@ -16375,9 +17175,9 @@ async function loadMcpTools(inv, cwd) {
16375
17175
  console.error = () => {
16376
17176
  };
16377
17177
  try {
16378
- for (const path4 of paths) {
17178
+ for (const path5 of paths) {
16379
17179
  try {
16380
- const servers = loadMCPConfig2(path4);
17180
+ const servers = loadMCPConfig2(path5);
16381
17181
  if (servers.length === 0) continue;
16382
17182
  const pool = new MCPClientPool2({ servers });
16383
17183
  await pool.connectAll();
@@ -16409,7 +17209,8 @@ var condenseTranscript = async (messages, opts) => {
16409
17209
  const deps = model ? {
16410
17210
  model,
16411
17211
  ...opts?.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
16412
- ...opts?.signal !== void 0 ? { signal: opts.signal } : {}
17212
+ ...opts?.signal !== void 0 ? { signal: opts.signal } : {},
17213
+ ...opts?.instructions !== void 0 ? { instructions: opts.instructions } : {}
16413
17214
  } : {};
16414
17215
  const cleared = clearStaleToolResults(messages);
16415
17216
  if (opts?.force) {
@@ -16449,7 +17250,7 @@ function resolveDefaultMode(store) {
16449
17250
  const mode = tier.permissions?.defaultMode;
16450
17251
  if (mode !== void 0 && isPermissionMode(mode)) return mode;
16451
17252
  }
16452
- return "default";
17253
+ return "acceptEdits";
16453
17254
  }
16454
17255
  function readOnlyNamesFromDeck(tools) {
16455
17256
  const names = /* @__PURE__ */ new Set();
@@ -16536,7 +17337,10 @@ async function buildSessionConductor(ctx) {
16536
17337
  basePath: typeof inv.sarvamBasePath === "string" ? inv.sarvamBasePath : typeof inv.flags?.["sarvam-base-path"] === "string" ? inv.flags["sarvam-base-path"] : void 0
16537
17338
  })
16538
17339
  ];
16539
- const tools = wrapToolsWithAddons(concatAddonTools(deck, addons), addons);
17340
+ const tools = wrapToolsForTerminalBench(
17341
+ wrapToolsWithAddons(concatAddonTools(deck, addons), addons),
17342
+ cwd
17343
+ );
16540
17344
  const skills = gatherModelSkills(cwd);
16541
17345
  const system = composeSystem(tools, inv, cwd, skills, memoryDoc);
16542
17346
  const thinking = resolveThinkingLevel(inv.thinking, modelId);
@@ -16885,7 +17689,7 @@ function isProgramEntry() {
16885
17689
  const entryArg = processArgv[1];
16886
17690
  if (!entryArg) return false;
16887
17691
  try {
16888
- return realpathSync3(fileURLToPath2(import.meta.url)) === realpathSync3(entryArg);
17692
+ return realpathSync4(fileURLToPath2(import.meta.url)) === realpathSync4(entryArg);
16889
17693
  } catch {
16890
17694
  return false;
16891
17695
  }