indusagi-coding-agent 0.2.12 → 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 (81) hide show
  1. package/.env.review.example +25 -0
  2. package/README.md +30 -0
  3. package/REVIEW_AGENT.md +137 -0
  4. package/dist/entry.js +1861 -979
  5. package/dist/guardrails.js +212 -36
  6. package/dist/index.js +2829 -1057
  7. package/dist/review-agent.js +4461 -0
  8. package/dist/review-server.js +4633 -0
  9. package/package.json +24 -7
  10. package/src/boot/auth-vault.ts +4 -5
  11. package/src/boot/contract.ts +1 -1
  12. package/src/boot/runners/addon-wiring.ts +111 -3
  13. package/src/boot/runners/benchmark-integrity.ts +107 -0
  14. package/src/boot/runners/repl-runner.ts +2 -1
  15. package/src/boot/runners/session.ts +26 -12
  16. package/src/boot/server-token.ts +20 -11
  17. package/src/boot/stages.ts +3 -6
  18. package/src/briefing/context-docs.ts +144 -12
  19. package/src/channels/oneshot.ts +6 -0
  20. package/src/conductor/catalog/catalog.ts +3 -1
  21. package/src/conductor/conductor.ts +77 -17
  22. package/src/conductor/contract.ts +9 -7
  23. package/src/conductor/index.ts +1 -1
  24. package/src/conductor/transcript-store/store.ts +4 -8
  25. package/src/console/auth-status.ts +9 -4
  26. package/src/console/components/AgentsView.ts +2 -2
  27. package/src/console/components/BackgroundAgents.ts +15 -2
  28. package/src/console/components/Banner.ts +20 -9
  29. package/src/console/components/Composer.ts +57 -82
  30. package/src/console/components/ScrollViewport.ts +46 -0
  31. package/src/console/components/TerminalConsole.ts +165 -162
  32. package/src/console/components/WorkingIndicator.ts +6 -5
  33. package/src/console/components/banner-sweep.ts +5 -1
  34. package/src/console/components/welcome.ts +3 -3
  35. package/src/console/contract.ts +10 -5
  36. package/src/console/index.ts +7 -7
  37. package/src/console/input/complete.ts +2 -2
  38. package/src/console/input/dir-reader.ts +3 -1
  39. package/src/console/input/graphemes.ts +22 -0
  40. package/src/console/input/keymap.ts +6 -4
  41. package/src/console/mount.ts +45 -3
  42. package/src/console/overlays/auth.ts +28 -8
  43. package/src/console/overlays/boards.ts +13 -40
  44. package/src/console/overlays/host.ts +10 -0
  45. package/src/console/overlays/pickers.ts +35 -30
  46. package/src/console/reducer.ts +12 -5
  47. package/src/console/slash/builtins.ts +4 -3
  48. package/src/console/slash/commands/integrations.ts +13 -14
  49. package/src/console/slash/commands/transcript.ts +3 -8
  50. package/src/console/slash/commands/workbench.ts +4 -4
  51. package/src/console/test-support.ts +48 -0
  52. package/src/index.ts +1 -0
  53. package/src/integrations/sarvam/config.ts +15 -13
  54. package/src/kit/external-editor.ts +17 -1
  55. package/src/kit/run-shell.ts +31 -0
  56. package/src/launch/catalog.ts +4 -2
  57. package/src/launch/contract.ts +1 -0
  58. package/src/launch/invocation/flags.ts +13 -1
  59. package/src/launch/invocation/read.ts +4 -3
  60. package/src/launch/login.ts +23 -10
  61. package/src/launch/oauth.ts +3 -0
  62. package/src/models.ts +166 -0
  63. package/src/review-agent/config.ts +75 -0
  64. package/src/review-agent/contract.ts +59 -0
  65. package/src/review-agent/github.ts +242 -0
  66. package/src/review-agent/index.ts +8 -0
  67. package/src/review-agent/prompt.ts +132 -0
  68. package/src/review-agent/reviewer.ts +118 -0
  69. package/src/review-agent/server.ts +221 -0
  70. package/src/review-agent/service.ts +165 -0
  71. package/src/review-agent/supabase.ts +209 -0
  72. package/src/review-agent/webhook.ts +76 -0
  73. package/src/security/private-state.ts +122 -0
  74. package/src/window-budget/summarize/condense.ts +4 -1
  75. package/src/workspace/brand.ts +1 -1
  76. package/src/workspace/locator.ts +2 -2
  77. package/supabase/migrations/20260827031803_indusagi_review_agent.sql +109 -0
  78. package/supabase/migrations/20260827035747_atomic_webhook_enqueue.sql +75 -0
  79. package/supabase/migrations/20260827040104_check_run_reconciliation.sql +8 -0
  80. package/supabase/migrations/20260827041049_owned_job_leases.sql +80 -0
  81. package/supabase/migrations/20260827042500_static_token_webhooks.sql +75 -0
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";
@@ -31,7 +31,8 @@ var THINKING_EFFORTS = [
31
31
  "low",
32
32
  "medium",
33
33
  "high",
34
- "xhigh"
34
+ "xhigh",
35
+ "max"
35
36
  ];
36
37
  function isThinkingEffort(value) {
37
38
  return THINKING_EFFORTS.includes(value);
@@ -76,26 +77,153 @@ import {
76
77
 
77
78
  // src/launch/login.ts
78
79
  import { createInterface } from "node:readline/promises";
80
+ import { setTimeout as delay } from "node:timers/promises";
79
81
  import chalk from "chalk";
80
82
 
81
83
  // src/boot/server-token.ts
82
- import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
84
+ import { unlink } from "node:fs/promises";
83
85
  import os from "node:os";
84
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
85
208
  var INDUS_SERVER_URL = process.env.INDUS_SERVER_URL?.replace(/\/+$/, "") ?? "https://indus-server.vercel.app";
86
209
  var CONFIG_DIR = path.join(os.homedir(), ".indusagi", "agent");
87
210
  var TOKEN_FILE = path.join(CONFIG_DIR, "server-token.json");
88
- 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 = {}) {
89
216
  try {
90
- const data = await readFile(TOKEN_FILE, "utf8");
217
+ const data = readPrivateFileSync(resolveTokenPaths(paths).tokenFile);
218
+ if (data === null) return null;
91
219
  return JSON.parse(data);
92
220
  } catch {
93
221
  return null;
94
222
  }
95
223
  }
96
- async function storeServerToken(token) {
224
+ async function storeServerToken(token, paths = {}) {
97
225
  try {
98
- await mkdir(CONFIG_DIR, { recursive: true });
226
+ const { tokenFile } = resolveTokenPaths(paths);
99
227
  const tokenData = {
100
228
  access_token: token.access_token,
101
229
  refresh_token: token.refresh_token,
@@ -104,20 +232,17 @@ async function storeServerToken(token) {
104
232
  expires_at: token.expires_in !== void 0 ? new Date(Date.now() + token.expires_in * 1e3).toISOString() : null,
105
233
  created_at: (/* @__PURE__ */ new Date()).toISOString()
106
234
  };
107
- await writeFile(TOKEN_FILE, `${JSON.stringify(tokenData, null, 2)}
108
- `, {
109
- encoding: "utf8",
110
- mode: 384
111
- });
235
+ atomicWritePrivateFileSync(tokenFile, `${JSON.stringify(tokenData, null, 2)}
236
+ `);
112
237
  return true;
113
238
  } catch (error) {
114
239
  console.error("Failed to store server token:", error.message);
115
240
  return false;
116
241
  }
117
242
  }
118
- async function clearServerToken() {
243
+ async function clearServerToken(paths = {}) {
119
244
  try {
120
- await unlink(TOKEN_FILE);
245
+ await unlink(resolveTokenPaths(paths).tokenFile);
121
246
  return true;
122
247
  } catch {
123
248
  return false;
@@ -154,10 +279,10 @@ function err(line) {
154
279
  process.stderr.write(`${line}
155
280
  `);
156
281
  }
157
- function sleep(ms) {
158
- return new Promise((resolve9) => setTimeout(resolve9, ms));
282
+ function sleep(ms, signal) {
283
+ return delay(ms, void 0, { signal });
159
284
  }
160
- async function postJson(url, body) {
285
+ async function postJson(url, body, signal) {
161
286
  const res = await fetch(url, {
162
287
  method: "POST",
163
288
  headers: {
@@ -165,7 +290,8 @@ async function postJson(url, body) {
165
290
  accept: "application/json",
166
291
  "user-agent": USER_AGENT
167
292
  },
168
- body: JSON.stringify(body)
293
+ body: JSON.stringify(body),
294
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
169
295
  });
170
296
  const data = await res.json().catch(() => ({}));
171
297
  return { status: res.status, data };
@@ -201,11 +327,11 @@ async function confirm(prompt, defaultValue) {
201
327
  rl.close();
202
328
  }
203
329
  }
204
- async function requestDeviceCode(serverUrl, clientId) {
330
+ async function requestDeviceCode(serverUrl, clientId, signal) {
205
331
  const { status, data } = await postJson(`${serverUrl}/api/auth/device/code`, {
206
332
  client_id: clientId,
207
333
  scope: DEVICE_SCOPE
208
- });
334
+ }, signal);
209
335
  if (status >= 400 || typeof data.device_code !== "string") {
210
336
  throw new LoginError(
211
337
  `Authorization request failed: ${data.error_description ?? data.error ?? data.message ?? `HTTP ${status}`}`
@@ -213,18 +339,18 @@ async function requestDeviceCode(serverUrl, clientId) {
213
339
  }
214
340
  return data;
215
341
  }
216
- async function pollForToken(serverUrl, clientId, device) {
342
+ async function pollForToken(serverUrl, clientId, device, signal) {
217
343
  let intervalMs = Math.max(1, device.interval ?? 5) * 1e3;
218
344
  const deadline = Date.now() + (device.expires_in ?? 0) * 1e3;
219
345
  while (Date.now() < deadline) {
220
- await sleep(intervalMs);
346
+ await sleep(intervalMs, signal);
221
347
  const { data } = await postJson(`${serverUrl}/api/auth/device/token`, {
222
348
  grant_type: DEVICE_GRANT_TYPE,
223
349
  device_code: device.device_code,
224
350
  client_id: clientId
225
- });
351
+ }, signal);
226
352
  if (typeof data.access_token === "string") {
227
- return data;
353
+ return { ...data, access_token: data.access_token };
228
354
  }
229
355
  const code = data.error;
230
356
  switch (code) {
@@ -247,14 +373,17 @@ async function pollForToken(serverUrl, clientId, device) {
247
373
  throw new LoginError("Timed out waiting for authorization. Run `indus login` again.");
248
374
  }
249
375
  async function runServerTierLogin(callbacks, serverUrl = INDUS_SERVER_URL, clientId = INDUS_CLIENT_ID) {
250
- const device = await requestDeviceCode(serverUrl, clientId);
376
+ callbacks.signal?.throwIfAborted();
377
+ const device = await requestDeviceCode(serverUrl, clientId, callbacks.signal);
378
+ callbacks.signal?.throwIfAborted();
251
379
  callbacks.onAuth({
252
380
  url: device.verification_uri_complete ?? device.verification_uri ?? "",
253
381
  instructions: `Enter code ${device.user_code} to authorize this device.`
254
382
  });
255
383
  callbacks.onProgress?.("Waiting for authorization...");
256
- const token = await pollForToken(serverUrl, clientId, device);
257
- 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.");
258
387
  callbacks.onProgress?.("Server sign-in complete.");
259
388
  }
260
389
  async function doLogin(args) {
@@ -465,12 +594,12 @@ function openLoginUrl(url) {
465
594
  const tryAt = (index) => {
466
595
  if (index >= attempts.length) return Promise.resolve(false);
467
596
  const { command, args } = attempts[index];
468
- return new Promise((resolve9) => {
597
+ return new Promise((resolve11) => {
469
598
  let settled = false;
470
599
  const finish = (ok) => {
471
600
  if (settled) return;
472
601
  settled = true;
473
- resolve9(ok);
602
+ resolve11(ok);
474
603
  };
475
604
  try {
476
605
  const child = spawn(command, [...args], {
@@ -503,8 +632,11 @@ async function startOAuthLogin(providerId, callbacks, vault, account = "default"
503
632
  const hint = envVar !== void 0 ? `set ${envVar} and restart` : "configure its OAuth client id";
504
633
  throw new Error(`Browser sign-in for "${providerId}" is not configured: ${hint}.`);
505
634
  }
635
+ callbacks.signal?.throwIfAborted();
506
636
  const credentials = await provider.login(callbacks);
637
+ callbacks.signal?.throwIfAborted();
507
638
  const existing = await vault.listAccounts(providerId);
639
+ callbacks.signal?.throwIfAborted();
508
640
  const madeDefault = existing.length === 0;
509
641
  await vault.putOAuth(providerId, account, credentials, madeDefault);
510
642
  return { provider: providerId, account, madeDefault };
@@ -1157,7 +1289,7 @@ var FLAG_GROUPS = [
1157
1289
  { id: "tools", title: "Tools" },
1158
1290
  { id: "meta", title: "Meta" }
1159
1291
  ];
1160
- var THINKING_CHOICES = ["off", "minimal", "low", "medium", "high", "xhigh"];
1292
+ var THINKING_CHOICES = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
1161
1293
  var FLAG_SPECS = [
1162
1294
  /* ---- Output ---------------------------------------------------------- */
1163
1295
  {
@@ -1246,6 +1378,18 @@ var FLAG_SPECS = [
1246
1378
  group: "context",
1247
1379
  describe: "Continue the most recent session in this directory."
1248
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
+ },
1249
1393
  /* ---- Tools ----------------------------------------------------------- */
1250
1394
  {
1251
1395
  name: "--tools",
@@ -1330,6 +1474,150 @@ var FLAG_SPECS = [
1330
1474
  }
1331
1475
  ];
1332
1476
 
1477
+ // src/models.ts
1478
+ import { ModelRegistry } from "indusagi/ai";
1479
+ var GPT_6_ASTRA_BASE = {
1480
+ id: "gpt-6-astra",
1481
+ name: "GPT-6 Astra",
1482
+ api: "openai-responses",
1483
+ reasoning: true,
1484
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
1485
+ input: ["text", "image"],
1486
+ cost: {
1487
+ input: 10,
1488
+ cacheRead: 1,
1489
+ cacheWrite: 12.5,
1490
+ output: 50
1491
+ },
1492
+ contextWindow: 105e4,
1493
+ maxTokens: 128e3,
1494
+ longContextPricing: {
1495
+ threshold: 272e3,
1496
+ multipliers: {
1497
+ input: 2,
1498
+ cacheRead: 2,
1499
+ cacheWrite: 2,
1500
+ output: 1.5
1501
+ }
1502
+ }
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
+ };
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
+ },
1572
+ {
1573
+ ...GPT_6_ASTRA_BASE,
1574
+ provider: "openai",
1575
+ baseUrl: "https://api.openai.com/v1"
1576
+ },
1577
+ {
1578
+ ...GPT_6_ASTRA_BASE,
1579
+ api: "openai-codex-responses",
1580
+ provider: "openai-codex",
1581
+ baseUrl: "https://chatgpt.com/backend-api"
1582
+ },
1583
+ {
1584
+ ...GPT_6_ASTRA_BASE,
1585
+ provider: "codex-cli",
1586
+ baseUrl: "bridge:codex-cli"
1587
+ },
1588
+ {
1589
+ ...GPT_6_ASTRA_BASE,
1590
+ id: "openai/gpt-6-astra",
1591
+ provider: "indusagi-cli",
1592
+ baseUrl: "bridge:indusagi-cli"
1593
+ },
1594
+ {
1595
+ ...GPT_6_ASTRA_BASE,
1596
+ id: "openai-codex/gpt-6-astra",
1597
+ api: "openai-codex-responses",
1598
+ provider: "indusagi-cli",
1599
+ baseUrl: "bridge:indusagi-cli"
1600
+ }
1601
+ ];
1602
+ function applyCodingAgentModelOverlay(registry) {
1603
+ for (const model of CODING_AGENT_MODELS) registry.registerCustomModel(model);
1604
+ return registry;
1605
+ }
1606
+ function createCodingAgentModelRegistry() {
1607
+ return applyCodingAgentModelOverlay(new ModelRegistry());
1608
+ }
1609
+ function isReasoningEffortSupported(modelId, 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;
1619
+ }
1620
+
1333
1621
  // src/launch/invocation/read.ts
1334
1622
  function isFlagToken(token) {
1335
1623
  return token.length > 1 && token.startsWith("-");
@@ -1445,9 +1733,9 @@ function deriveMode(flags) {
1445
1733
  if (readBool(flags, "json")) return "rpc";
1446
1734
  return "text";
1447
1735
  }
1448
- function resolveThinking(flags) {
1736
+ function resolveThinking(flags, modelId) {
1449
1737
  const raw = readString(flags, "thinking");
1450
- return raw !== void 0 && isThinkingEffort(raw) ? raw : void 0;
1738
+ return raw !== void 0 && isThinkingEffort(raw) && isReasoningEffortSupported(modelId, raw) ? raw : void 0;
1451
1739
  }
1452
1740
  function resolveTools(flags) {
1453
1741
  const raw = readList(flags, "tools");
@@ -1496,7 +1784,7 @@ function readInvocation(argv2) {
1496
1784
  const cwd = readString(flags, "cwd");
1497
1785
  const system = readString(flags, "system");
1498
1786
  const appendSystem = readString(flags, "append-system");
1499
- const thinking = resolveThinking(flags);
1787
+ const thinking = resolveThinking(flags, model);
1500
1788
  const tools = resolveTools(flags);
1501
1789
  return {
1502
1790
  mode,
@@ -1580,7 +1868,7 @@ var MAX_TEXT_BYTES = 10 * 1024 * 1024;
1580
1868
  var MAX_IMAGE_BYTES = 20 * 1024 * 1024;
1581
1869
 
1582
1870
  // src/launch/catalog.ts
1583
- import { ModelRegistry } from "indusagi/ai";
1871
+ import { ModelRegistry as ModelRegistry2 } from "indusagi/ai";
1584
1872
  function defaultCatalogIo() {
1585
1873
  return {
1586
1874
  print(line) {
@@ -1589,10 +1877,11 @@ function defaultCatalogIo() {
1589
1877
  }
1590
1878
  };
1591
1879
  }
1592
- function registrySource(registry = new ModelRegistry()) {
1880
+ function registrySource(registry = new ModelRegistry2()) {
1881
+ const overlaid = applyCodingAgentModelOverlay(registry);
1593
1882
  return {
1594
- providers: () => registry.getProviders(),
1595
- models: (provider) => registry.getModels(provider)
1883
+ providers: () => overlaid.getProviders(),
1884
+ models: (provider) => overlaid.getModels(provider)
1596
1885
  };
1597
1886
  }
1598
1887
  var HEADERS = [
@@ -1699,13 +1988,13 @@ function defaultResumeDeps() {
1699
1988
  },
1700
1989
  mountPicker(sessions, totalCount) {
1701
1990
  const byPath = new Map(sessions.map((s) => [s.path, s]));
1702
- return new Promise((resolve9, reject) => {
1991
+ return new Promise((resolve11, reject) => {
1703
1992
  let settled = false;
1704
1993
  const settle = (value) => {
1705
1994
  if (settled) return;
1706
1995
  settled = true;
1707
1996
  instance.unmount();
1708
- resolve9(value);
1997
+ resolve11(value);
1709
1998
  };
1710
1999
  let instance;
1711
2000
  try {
@@ -1724,7 +2013,7 @@ function defaultResumeDeps() {
1724
2013
  void instance.waitUntilExit().then(() => {
1725
2014
  if (!settled) {
1726
2015
  settled = true;
1727
- resolve9(null);
2016
+ resolve11(null);
1728
2017
  }
1729
2018
  });
1730
2019
  });
@@ -1829,7 +2118,7 @@ import { join as join2 } from "node:path";
1829
2118
 
1830
2119
  // src/boot/upgrade/upgrades.ts
1831
2120
  import { promises as fs, constants as fsConstants } from "node:fs";
1832
- import { join, dirname, basename } from "node:path";
2121
+ import { join, dirname as dirname2, basename } from "node:path";
1833
2122
  var SECURE_FILE_MODE = 384;
1834
2123
  var RETIRED_SUFFIX = ".retired";
1835
2124
  async function pathExists(target) {
@@ -1862,7 +2151,7 @@ async function readJsonOrUndefined(target) {
1862
2151
  }
1863
2152
  }
1864
2153
  async function writeJsonWithMode(target, value, mode) {
1865
- await fs.mkdir(dirname(target), { recursive: true });
2154
+ await fs.mkdir(dirname2(target), { recursive: true });
1866
2155
  await fs.writeFile(target, `${JSON.stringify(value, null, 2)}
1867
2156
  `, { mode });
1868
2157
  await fs.chmod(target, mode);
@@ -1870,7 +2159,7 @@ async function writeJsonWithMode(target, value, mode) {
1870
2159
  async function relocateWithoutClobber(from, into) {
1871
2160
  if (!await pathExists(from)) return false;
1872
2161
  if (await pathExists(into)) return false;
1873
- await fs.mkdir(dirname(into), { recursive: true });
2162
+ await fs.mkdir(dirname2(into), { recursive: true });
1874
2163
  await fs.rename(from, into);
1875
2164
  return true;
1876
2165
  }
@@ -2027,7 +2316,6 @@ async function applyUpgrades(ws) {
2027
2316
  }
2028
2317
 
2029
2318
  // src/workspace/locator.ts
2030
- import { mkdirSync } from "node:fs";
2031
2319
  import { homedir } from "node:os";
2032
2320
  import { isAbsolute, join as join3, resolve } from "node:path";
2033
2321
  var LAYOUT = {
@@ -2091,7 +2379,7 @@ function createWorkspace(overrides = {}) {
2091
2379
  }
2092
2380
  function ensureDirs(workspace) {
2093
2381
  for (const key of DIRECTORY_KEYS) {
2094
- mkdirSync(workspace[key], { recursive: true });
2382
+ ensurePrivateDirectorySync(workspace[key]);
2095
2383
  }
2096
2384
  return workspace;
2097
2385
  }
@@ -2139,11 +2427,7 @@ async function loadDefaultSettings() {
2139
2427
  }
2140
2428
  }
2141
2429
  async function loadModelRegistry() {
2142
- const mod = await import("indusagi/ai");
2143
- if (typeof mod.ModelRegistry === "function") {
2144
- return new mod.ModelRegistry();
2145
- }
2146
- return mod.modelRegistry;
2430
+ return createCodingAgentModelRegistry();
2147
2431
  }
2148
2432
  var selectRunnerStage = {
2149
2433
  name: "select-runner",
@@ -2192,6 +2476,30 @@ var EMPTY_CONSOLE_STATE = {
2192
2476
  tick: 0
2193
2477
  };
2194
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
+
2195
2503
  // src/console/reducer.ts
2196
2504
  function clamp(offset, max) {
2197
2505
  if (offset < 0) return 0;
@@ -2215,13 +2523,14 @@ function insertBuffer(state, text) {
2215
2523
  function erasePrev(state) {
2216
2524
  const at = clamp(state.caret, state.buffer.length);
2217
2525
  if (at === 0) return state;
2218
- const next = state.buffer.slice(0, at - 1) + state.buffer.slice(at);
2219
- 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);
2220
2529
  }
2221
2530
  function eraseNext(state) {
2222
2531
  const at = clamp(state.caret, state.buffer.length);
2223
2532
  if (at >= state.buffer.length) return state;
2224
- 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));
2225
2534
  return withBuffer(state, next, at);
2226
2535
  }
2227
2536
  function pushHistory(state, text) {
@@ -2264,6 +2573,10 @@ function patchRow(state, id, text) {
2264
2573
  }
2265
2574
  function consoleReducer(state, event) {
2266
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 };
2267
2580
  // Composer buffer & caret
2268
2581
  case "buffer:set":
2269
2582
  return setBuffer(state, event.text);
@@ -2282,9 +2595,9 @@ function consoleReducer(state, event) {
2282
2595
  case "buffer:eraseNext":
2283
2596
  return eraseNext(state);
2284
2597
  case "caret:move":
2285
- return { ...state, caret: clamp(state.caret + event.delta, state.buffer.length) };
2598
+ return { ...state, caret: moveGrapheme(state.buffer, state.caret, event.delta) };
2286
2599
  case "caret:jump":
2287
- return { ...state, caret: clamp(event.offset, state.buffer.length) };
2600
+ return { ...state, caret: snapGrapheme(state.buffer, event.offset) };
2288
2601
  // History ring
2289
2602
  case "history:push":
2290
2603
  return pushHistory(state, event.text);
@@ -2514,7 +2827,7 @@ function resolveTheme(scheme) {
2514
2827
  var THEME_SCHEMES = Object.keys(THEMES);
2515
2828
 
2516
2829
  // src/console/slash/builtins.ts
2517
- import { homedir as homedir3 } from "node:os";
2830
+ import { homedir as homedir2 } from "node:os";
2518
2831
  import { join as join12 } from "node:path";
2519
2832
 
2520
2833
  // src/briefing/contract.ts
@@ -2699,7 +3012,7 @@ function composeBriefing(input) {
2699
3012
  }
2700
3013
 
2701
3014
  // src/briefing/macros.ts
2702
- import { readFileSync, readdirSync, statSync } from "node:fs";
3015
+ import { readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
2703
3016
  import { basename as basename2, join as join4 } from "node:path";
2704
3017
  var decimal = (ch) => ch.charCodeAt(0) >= 48 && ch.charCodeAt(0) <= 57;
2705
3018
  var bareword = (ch) => {
@@ -2911,13 +3224,13 @@ function loadMacros(dir, opts = {}) {
2911
3224
  const macros = [];
2912
3225
  for (const entry of entries.sort()) {
2913
3226
  if (entry.startsWith(".") || !entry.toLowerCase().endsWith(".md")) continue;
2914
- const path4 = join4(dir, entry);
3227
+ const path5 = join4(dir, entry);
2915
3228
  try {
2916
- if (!statSync(path4).isFile()) continue;
3229
+ if (!statSync(path5).isFile()) continue;
2917
3230
  } catch {
2918
3231
  continue;
2919
3232
  }
2920
- const macro = readMacroFile(path4, origin, label);
3233
+ const macro = readMacroFile(path5, origin, label);
2921
3234
  if (!seen.has(macro.name)) {
2922
3235
  seen.add(macro.name);
2923
3236
  macros.push(macro);
@@ -2925,17 +3238,17 @@ function loadMacros(dir, opts = {}) {
2925
3238
  }
2926
3239
  return macros;
2927
3240
  }
2928
- function readMacroFile(path4, origin, label) {
3241
+ function readMacroFile(path5, origin, label) {
2929
3242
  let text;
2930
3243
  try {
2931
- text = readFileSync(path4, "utf8");
3244
+ text = readFileSync2(path5, "utf8");
2932
3245
  } catch (cause) {
2933
- 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);
2934
3247
  }
2935
3248
  const { frontmatter, body } = splitFrontmatter(text);
2936
- const name = basename2(path4).replace(/\.md$/i, "");
3249
+ const name = basename2(path5).replace(/\.md$/i, "");
2937
3250
  const declared = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
2938
- 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 };
2939
3252
  }
2940
3253
  function deriveDescription(body, label) {
2941
3254
  const first = body.split("\n").find((line) => line.trim())?.trim() ?? "";
@@ -2961,8 +3274,8 @@ function unquoteScalar(value) {
2961
3274
  }
2962
3275
 
2963
3276
  // src/briefing/skills.ts
2964
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, realpathSync, statSync as statSync2 } from "node:fs";
2965
- 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";
2966
3279
  var SKILL_MANIFEST = "SKILL.md";
2967
3280
  var PRUNED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
2968
3281
  var KNOWN_FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["name", "description", "license", "compatibility", "metadata", "allowed-tools", "disable-model-invocation"]);
@@ -2997,7 +3310,7 @@ function* walkLevel(dir, atRoot, seen) {
2997
3310
  seen.add(real);
2998
3311
  yield* walkLevel(full, false, seen);
2999
3312
  }
3000
- } 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)) };
3001
3314
  }
3002
3315
  }
3003
3316
  function checkName(name, dirName, fromDir) {
@@ -3036,7 +3349,7 @@ function describeCause(cause) {
3036
3349
  function parseCandidate(candidate, origin) {
3037
3350
  let text;
3038
3351
  try {
3039
- text = readFileSync2(candidate.path, "utf8");
3352
+ text = readFileSync3(candidate.path, "utf8");
3040
3353
  } catch (cause) {
3041
3354
  return { card: null, diagnostic: diag("invalid", candidate.path, `could not read the skill file (${describeCause(cause)})`) };
3042
3355
  }
@@ -3087,49 +3400,52 @@ function modelInvocableCards(cards) {
3087
3400
  }
3088
3401
 
3089
3402
  // src/briefing/context-docs.ts
3090
- import { existsSync, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
3091
- 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";
3092
3405
  var DEFAULT_MAX_IMPORT_DEPTH = 5;
3093
3406
  var DEFAULT_MAX_BYTES_PER_DOC = 4e4;
3094
3407
  var CANDIDATE_FILES = ["AGENTS.md", "CLAUDE.md", "INDUSAGI.md", "CLAUDE.local.md", "AGENTS.local.md", join6(".indusagi", "CLAUDE.md"), join6(".claude", "CLAUDE.md")];
3095
- 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"]);
3096
3409
  var IMPORT_REGEX = /(?:^|\s)@((?:[^\s\\]|\\ )+)/g;
3097
- function shortenPath(path4, cwd, home) {
3098
- if (cwd && path4.startsWith(cwd)) {
3099
- 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(/^[/\\]+/, "");
3100
3424
  return rest ? `./${rest}` : ".";
3101
3425
  }
3102
- return home && path4.startsWith(home) ? `~${path4.slice(home.length)}` : path4;
3103
- }
3104
- function dirChain(cwd) {
3105
- const chain = [];
3106
- let current = cwd;
3107
- for (let i = 0; i < 256; i++) {
3108
- chain.push(current);
3109
- const parent = dirname3(current);
3110
- if (parent === current || current === parse(current).root) break;
3111
- current = parent;
3112
- }
3113
- return chain.reverse();
3114
- }
3115
- function safeRealpath(path4) {
3116
- try {
3117
- return realpathSync2(path4);
3118
- } catch {
3119
- return path4;
3120
- }
3426
+ return home && (path5 === home || path5.startsWith(`${home}${sep}`)) ? `~${path5.slice(home.length)}` : path5;
3121
3427
  }
3122
- function isTextFile(path4) {
3123
- const extension = extname(path4).toLowerCase();
3428
+ function isTextFile(path5) {
3429
+ const extension = extname(path5).toLowerCase();
3124
3430
  return !extension || TEXT_FILE_EXTENSIONS.has(extension);
3125
3431
  }
3126
- function readFileBody(path4) {
3127
- 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;
3128
3438
  try {
3129
- 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();
3130
3444
  return body || null;
3131
3445
  } catch {
3132
3446
  return null;
3447
+ } finally {
3448
+ if (fd !== void 0) closeSync2(fd);
3133
3449
  }
3134
3450
  }
3135
3451
  function extractImports(body, baseDir, home) {
@@ -3148,23 +3464,46 @@ function extractImports(body, baseDir, home) {
3148
3464
  }
3149
3465
  return out2;
3150
3466
  }
3151
- function loadDocTree(path4, cwd, home, seen, depth, maxDepth, maxBytes, out2) {
3152
- const real = safeRealpath(path4);
3153
- 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;
3154
3484
  seen.add(real);
3155
- const body = readFileBody(path4);
3485
+ const body = readFileBody(real);
3156
3486
  if (body === null) return;
3157
- 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 });
3158
3488
  if (depth >= maxDepth) return;
3159
- 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
+ }
3160
3492
  }
3161
- function gatherContextDocs(cwd, home, opts) {
3493
+ function gatherContextDocs(cwd, home, opts = {}) {
3162
3494
  const out2 = [];
3163
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 };
3164
3503
  const seen = /* @__PURE__ */ new Set();
3165
- const roots = dirChain(cwd);
3166
- if (home) roots.push(home);
3167
- 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
+ }
3168
3507
  } catch {
3169
3508
  return out2;
3170
3509
  }
@@ -3277,9 +3616,9 @@ function buildDynamicCommands(sources = {}) {
3277
3616
 
3278
3617
  // src/console/slash/commands/integrations.ts
3279
3618
  import { spawnSync as spawnSync3 } from "node:child_process";
3280
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
3281
- import { homedir as homedir2 } from "node:os";
3282
- 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";
3283
3622
  import { createMCPAgentToolFactory, loadMCPConfig, MCPClientPool } from "indusagi/mcp";
3284
3623
  import { createComposioGateway } from "indusagi/connectors-saas";
3285
3624
 
@@ -4399,14 +4738,14 @@ var DaemonTable = class {
4399
4738
  if (handle.state !== "running") return;
4400
4739
  const child = handle.child;
4401
4740
  child.kill("SIGTERM");
4402
- await new Promise((resolve9) => {
4741
+ await new Promise((resolve11) => {
4403
4742
  const timer = setTimeout(() => {
4404
4743
  if (handle.state === "running") child.kill("SIGKILL");
4405
- resolve9();
4744
+ resolve11();
4406
4745
  }, graceMs);
4407
4746
  child.once("exit", () => {
4408
4747
  clearTimeout(timer);
4409
- resolve9();
4748
+ resolve11();
4410
4749
  });
4411
4750
  });
4412
4751
  }
@@ -5150,7 +5489,7 @@ function createLimiter(limit) {
5150
5489
  queue.shift()?.();
5151
5490
  };
5152
5491
  return async (fn) => {
5153
- if (active >= limit) await new Promise((resolve9) => queue.push(resolve9));
5492
+ if (active >= limit) await new Promise((resolve11) => queue.push(resolve11));
5154
5493
  active++;
5155
5494
  try {
5156
5495
  return await fn();
@@ -5216,9 +5555,59 @@ import { Agent } from "indusagi/agent";
5216
5555
  import { createZeroUsage as createZeroUsage2 } from "indusagi/ai";
5217
5556
  import * as path3 from "node:path";
5218
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
+
5219
5608
  // src/conductor/catalog/catalog.ts
5220
5609
  import { z } from "zod";
5221
- import { getModels, getProviders } from "indusagi/ai";
5610
+ import { getModels, getProviders, modelRegistry } from "indusagi/ai";
5222
5611
  var rawModelGate = z.object({
5223
5612
  id: z.string().min(1),
5224
5613
  name: z.string().min(1),
@@ -5228,6 +5617,7 @@ var rawModelGate = z.object({
5228
5617
  contextWindow: z.number().optional()
5229
5618
  });
5230
5619
  var EXCLUDED_PROVIDERS = /* @__PURE__ */ new Set(["mock"]);
5620
+ applyCodingAgentModelOverlay(modelRegistry);
5231
5621
  var frameworkSource = {
5232
5622
  providers: () => getProviders().filter((provider) => !EXCLUDED_PROVIDERS.has(String(provider))),
5233
5623
  models: (provider) => getModels(provider)
@@ -5642,30 +6032,21 @@ function memoryBackend() {
5642
6032
  }
5643
6033
  function fsBackend(dir) {
5644
6034
  const fileOf = async (sessionId) => {
5645
- const path4 = await import("node:path");
5646
- return path4.join(dir, `${sessionId}.ndjson`);
6035
+ const path5 = await import("node:path");
6036
+ return path5.join(dir, `${sessionId}.ndjson`);
5647
6037
  };
5648
6038
  return {
5649
6039
  locate: (sessionId) => `${dir}/${sessionId}.ndjson`,
5650
6040
  read: async (sessionId) => {
5651
- const fs3 = await import("node:fs/promises");
5652
- try {
5653
- return await fs3.readFile(await fileOf(sessionId), "utf8");
5654
- } catch {
5655
- return null;
5656
- }
6041
+ return readPrivateFileSync(await fileOf(sessionId));
5657
6042
  },
5658
6043
  append: async (sessionId, line) => {
5659
- const fs3 = await import("node:fs/promises");
5660
- await fs3.mkdir(dir, { recursive: true });
5661
- await fs3.appendFile(await fileOf(sessionId), `${line}
5662
- `, "utf8");
6044
+ appendPrivateFileSync(await fileOf(sessionId), `${line}
6045
+ `);
5663
6046
  },
5664
6047
  rewrite: async (sessionId, text) => {
5665
- const fs3 = await import("node:fs/promises");
5666
- await fs3.mkdir(dir, { recursive: true });
5667
- await fs3.writeFile(await fileOf(sessionId), text.length === 0 ? "" : `${text}
5668
- `, "utf8");
6048
+ atomicWritePrivateFileSync(await fileOf(sessionId), text.length === 0 ? "" : `${text}
6049
+ `);
5669
6050
  }
5670
6051
  };
5671
6052
  }
@@ -6303,7 +6684,10 @@ async function summarize(messages, deps = {}) {
6303
6684
  const digest2 = localFallbackDigest(messages, deps.priorDigest);
6304
6685
  return { message: toSummaryMessage(digest2, scope), coveredCount };
6305
6686
  }
6306
- 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}` : "");
6307
6691
  const context = {
6308
6692
  systemPrompt: CONDENSER_BRIEF,
6309
6693
  messages: [{ role: "user", content: prompt, timestamp: Date.now() }]
@@ -6427,8 +6811,8 @@ function collectVisibleReadPaths(kept, readToolName) {
6427
6811
  if (!isRecord4(block2) || block2.type !== "toolCall") continue;
6428
6812
  if (block2.name !== readToolName) continue;
6429
6813
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
6430
- const path4 = args && typeof args.path === "string" ? args.path : "";
6431
- 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);
6432
6816
  }
6433
6817
  }
6434
6818
  return paths;
@@ -6444,8 +6828,8 @@ function collectDroppedReads(dropped, readToolName) {
6444
6828
  if (block2.name !== readToolName) continue;
6445
6829
  const id = typeof block2.id === "string" ? block2.id : "";
6446
6830
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
6447
- const path4 = args && typeof args.path === "string" ? args.path : "";
6448
- 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 });
6449
6833
  }
6450
6834
  }
6451
6835
  if (calls.size === 0) return [];
@@ -6496,7 +6880,7 @@ ${read.body}`,
6496
6880
 
6497
6881
  // src/conductor/diagnostics.ts
6498
6882
  import * as path2 from "node:path";
6499
- import { existsSync as existsSync2 } from "node:fs";
6883
+ import { existsSync } from "node:fs";
6500
6884
  var MAX_PER_FILE = 10;
6501
6885
  var MAX_TOTAL = 30;
6502
6886
  var DELIVERED_KEYS_CAP = 500;
@@ -6553,18 +6937,18 @@ async function loadSpawn() {
6553
6937
  return void 0;
6554
6938
  }
6555
6939
  }
6556
- function captureOutput(spawn3, bin, args, cwd, signal) {
6557
- return new Promise((resolve9) => {
6940
+ function captureOutput(spawn4, bin, args, cwd, signal) {
6941
+ return new Promise((resolve11) => {
6558
6942
  let settled = false;
6559
6943
  const finish = (value) => {
6560
6944
  if (!settled) {
6561
6945
  settled = true;
6562
- resolve9(value);
6946
+ resolve11(value);
6563
6947
  }
6564
6948
  };
6565
6949
  let child;
6566
6950
  try {
6567
- child = spawn3(bin, args, { cwd, shell: false });
6951
+ child = spawn4(bin, args, { cwd, shell: false });
6568
6952
  } catch {
6569
6953
  finish();
6570
6954
  return;
@@ -6593,9 +6977,9 @@ function captureOutput(spawn3, bin, args, cwd, signal) {
6593
6977
  });
6594
6978
  }
6595
6979
  async function runTscDiagnostics(cwd, signal) {
6596
- const spawn3 = await loadSpawn();
6597
- if (!spawn3 || signal?.aborted) return [];
6598
- 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);
6599
6983
  return output === void 0 ? [] : parseTscOutput(output, cwd);
6600
6984
  }
6601
6985
  function parseTscOutput(output, cwd) {
@@ -6615,9 +6999,9 @@ function parseTscOutput(output, cwd) {
6615
6999
  }
6616
7000
  async function runEslintDiagnostics(files, cwd, signal) {
6617
7001
  if (files.length === 0) return [];
6618
- const spawn3 = await loadSpawn();
6619
- if (!spawn3 || signal?.aborted) return [];
6620
- 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);
6621
7005
  return output === void 0 ? [] : parseEslintOutput(output, cwd);
6622
7006
  }
6623
7007
  function parseEslintOutput(output, cwd) {
@@ -6724,14 +7108,14 @@ function isTypeScriptOrJs(filePath) {
6724
7108
  }
6725
7109
  function hasTsConfig(cwd) {
6726
7110
  try {
6727
- return existsSync2(path2.join(cwd, "tsconfig.json"));
7111
+ return existsSync(path2.join(cwd, "tsconfig.json"));
6728
7112
  } catch {
6729
7113
  return false;
6730
7114
  }
6731
7115
  }
6732
7116
  function hasEslintConfig(cwd) {
6733
7117
  try {
6734
- 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)));
6735
7119
  } catch {
6736
7120
  return false;
6737
7121
  }
@@ -6758,7 +7142,7 @@ function reduceState(prev, action) {
6758
7142
  }
6759
7143
  }
6760
7144
  var DEFAULT_RETRY = { maxAttempts: 2, baseDelayMs: 250 };
6761
- var THINKING_LADDER = ["off", "low", "medium", "high"];
7145
+ var DEFAULT_THINKING_LADDER = ["off", "low", "medium", "high"];
6762
7146
  var PERMISSION_CYCLE = ["default", "acceptEdits", "plan", "bypass"];
6763
7147
  function newSessionId() {
6764
7148
  return `s_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
@@ -6780,6 +7164,10 @@ function isOverloadFault(error) {
6780
7164
  const text = errorText(error).toLowerCase();
6781
7165
  return text.includes("529") || text.includes("overload");
6782
7166
  }
7167
+ function thinkingLadderForModel(model) {
7168
+ const efforts = model?.reasoningEfforts;
7169
+ return efforts?.length ? efforts : DEFAULT_THINKING_LADDER;
7170
+ }
6783
7171
  function withGatewayBaseUrl(model, gatewayBaseUrls) {
6784
7172
  const url = gatewayBaseUrls?.[model.provider];
6785
7173
  return url ? { ...model, baseUrl: url } : model;
@@ -6878,6 +7266,8 @@ var SessionConductorImpl = class {
6878
7266
  #matcher;
6879
7267
  #state;
6880
7268
  #busy = false;
7269
+ #activeWork;
7270
+ #shellAbort;
6881
7271
  #draining = false;
6882
7272
  #queue = [];
6883
7273
  #boundModel;
@@ -6937,6 +7327,7 @@ var SessionConductorImpl = class {
6937
7327
  abort() {
6938
7328
  this.clearQueue();
6939
7329
  this.#condenseAbort?.abort();
7330
+ this.#shellAbort?.abort();
6940
7331
  this.agent.abort();
6941
7332
  const fault = conductorFault("aborted", "turn aborted by caller");
6942
7333
  this.#dispatch({ type: "fault", fault });
@@ -6949,13 +7340,22 @@ var SessionConductorImpl = class {
6949
7340
  return this.#state;
6950
7341
  }
6951
7342
  this.#busy = true;
6952
- try {
6953
- await this.#runTurn(input);
6954
- await this.#drainQueue();
6955
- return this.#state;
6956
- } finally {
6957
- this.#busy = false;
6958
- }
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;
6959
7359
  }
6960
7360
  async #drainQueue() {
6961
7361
  if (this.#draining) return;
@@ -6993,7 +7393,7 @@ var SessionConductorImpl = class {
6993
7393
  }
6994
7394
  if (attempt < (this.deps.retry ?? DEFAULT_RETRY).maxAttempts && isTransient(error)) {
6995
7395
  this.#hub.emit({ kind: "fault", fault: conductorFault("model", `transient model fault; retry ${attempt + 1}`, error), transient: true });
6996
- 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);
6997
7397
  continue;
6998
7398
  }
6999
7399
  if (isOverloadFault(error) && this.options.fallbackModelId) this.selectModel(this.options.fallbackModelId);
@@ -7044,8 +7444,16 @@ var SessionConductorImpl = class {
7044
7444
  }
7045
7445
  this.#applyModel(card);
7046
7446
  }
7047
- cycleModel(id) {
7048
- 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);
7049
7457
  }
7050
7458
  #applyModel(card) {
7051
7459
  const model = withGatewayBaseUrl(card.model, this.#gatewayBaseUrls);
@@ -7060,28 +7468,36 @@ var SessionConductorImpl = class {
7060
7468
  registerTools(tools) {
7061
7469
  this.agent.setTools([...this.#deckTools, ...tools]);
7062
7470
  }
7063
- async condense() {
7471
+ async condense(instructions) {
7064
7472
  if (this.#busy) return "busy";
7065
7473
  this.#busy = true;
7066
7474
  this.#condenseAbort = new AbortController();
7067
- try {
7068
- const result = await this.#runCondense(true, this.#condenseAbort.signal);
7069
- await this.#drainQueue();
7070
- return result;
7071
- } finally {
7072
- this.#busy = false;
7073
- this.#condenseAbort = void 0;
7074
- }
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;
7075
7487
  }
7076
7488
  cancelCondense() {
7077
7489
  this.#condenseAbort?.abort();
7078
7490
  }
7079
- async #runCondense(force, signal) {
7491
+ async #runCondense(force, signal, instructions) {
7080
7492
  const before = [...this.agent.state.messages];
7081
7493
  this.#dispatch({ type: "phase", phase: "condensing" });
7082
7494
  try {
7083
- const after = await (this.deps.condense ?? noopCondense)(before, { force, model: this.#boundModel, contextTokens: this.#state.contextTokens, signal });
7084
- 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
+ }
7085
7501
  if (after.length >= before.length) {
7086
7502
  this.#dispatch({ type: "settled" });
7087
7503
  return "nothing";
@@ -7108,10 +7524,12 @@ var SessionConductorImpl = class {
7108
7524
  }
7109
7525
  async #move(id, message) {
7110
7526
  try {
7527
+ await this.#stopWork();
7111
7528
  await this.store.branchAt(id);
7112
7529
  this.agent.replaceMessages(this.store.messages());
7113
7530
  this.#dispatch({ type: "head", head: this.store.head });
7114
7531
  this.#dispatch({ type: "settled" });
7532
+ this.#hub.emit({ kind: "reset" });
7115
7533
  this.#hub.emit({ kind: "idle" });
7116
7534
  } catch (error) {
7117
7535
  const fault = conductorFault("persistence", message, error);
@@ -7126,18 +7544,33 @@ var SessionConductorImpl = class {
7126
7544
  return { canRestore: this.options.checkpoint?.hasSnapshot(id) ?? false };
7127
7545
  }
7128
7546
  async executeBash(command, opts = {}) {
7129
- const { spawn: spawn3 } = await import("node:child_process");
7130
- const outcome = await new Promise((resolve9) => {
7131
- const child = spawn3(command, { cwd: this.#workspace, shell: true });
7132
- let output = "";
7133
- child.stdout?.on("data", (chunk) => output += String(chunk));
7134
- child.stderr?.on("data", (chunk) => output += String(chunk));
7135
- child.on("error", (error) => resolve9({ output: output + errorText(error), exitCode: 1 }));
7136
- child.on("close", (code) => resolve9({ output, exitCode: code ?? 1 }));
7137
- });
7138
- if (!opts.excludeFromContext) await this.store.append({ role: "user", content: `$ ${command}
7139
- ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
7140
- 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;
7141
7574
  }
7142
7575
  stats() {
7143
7576
  const messages = this.agent.state.messages;
@@ -7160,11 +7593,13 @@ ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
7160
7593
  return this.#thinking;
7161
7594
  }
7162
7595
  setThinkingLevel(level) {
7596
+ if (!isReasoningEffortSupported(this.#boundModel?.id, level)) return;
7163
7597
  this.#thinking = level;
7164
7598
  this.agent.setThinkingLevel?.(level);
7165
7599
  }
7166
7600
  cycleThinkingLevel() {
7167
- const next = THINKING_LADDER[(THINKING_LADDER.indexOf(this.#thinking) + 1) % THINKING_LADDER.length];
7601
+ const ladder = thinkingLadderForModel(this.#boundModel);
7602
+ const next = ladder[(ladder.indexOf(this.#thinking) + 1) % ladder.length];
7168
7603
  this.setThinkingLevel(next);
7169
7604
  return next;
7170
7605
  }
@@ -7205,6 +7640,7 @@ ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
7205
7640
  this.#sessionName = name;
7206
7641
  }
7207
7642
  async resume(id) {
7643
+ await this.#stopWork();
7208
7644
  if (!await this.store.load(id)) {
7209
7645
  const fault = conductorFault("persistence", `no session "${id}" to resume`);
7210
7646
  this.#dispatch({ type: "fault", fault });
@@ -7213,19 +7649,23 @@ ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
7213
7649
  }
7214
7650
  this.agent.replaceMessages(this.store.messages());
7215
7651
  this.#state = { ...this.#state, head: this.store.head, usage: this.store.usage ?? createZeroUsage2(), phase: "idle" };
7652
+ this.#hub.emit({ kind: "reset" });
7216
7653
  this.#hub.emit({ kind: "idle" });
7217
7654
  }
7218
7655
  async newSession() {
7219
- this.agent.abort();
7656
+ await this.#stopWork();
7220
7657
  this.agent.replaceMessages([]);
7221
7658
  this.store.startNewSession(newSessionId());
7222
7659
  this.#queue.length = 0;
7223
7660
  this.#sessionName = void 0;
7224
7661
  this.#state = { phase: "idle", head: this.store.head, usage: createZeroUsage2(), contextTokens: 0, modelId: this.#state.modelId };
7662
+ this.#hub.emit({ kind: "reset" });
7225
7663
  this.#hub.emit({ kind: "idle" });
7226
7664
  }
7227
7665
  #dispatch(action) {
7666
+ const phase = this.#state.phase;
7228
7667
  this.#state = reduceState(this.#state, action);
7668
+ if (phase !== this.#state.phase) this.#hub.emit({ kind: "phase", phase: this.#state.phase });
7229
7669
  }
7230
7670
  };
7231
7671
 
@@ -7993,7 +8433,7 @@ var SETTING_KEYS = Object.freeze(
7993
8433
 
7994
8434
  // src/kit/clipboard-image.ts
7995
8435
  import { spawnSync } from "node:child_process";
7996
- import { readFileSync as readFileSync4, writeFileSync } from "node:fs";
8436
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
7997
8437
  import { tmpdir } from "node:os";
7998
8438
  import { join as join8 } from "node:path";
7999
8439
  var READ_TIMEOUT_MS = 3e3;
@@ -8038,10 +8478,10 @@ function captureViaOsascript() {
8038
8478
  { timeout: READ_TIMEOUT_MS, encoding: "utf8" }
8039
8479
  );
8040
8480
  if (result.error || result.status !== 0) return null;
8041
- const path4 = String(result.stdout ?? "").trim();
8042
- if (path4.length === 0) return null;
8481
+ const path5 = String(result.stdout ?? "").trim();
8482
+ if (path5.length === 0) return null;
8043
8483
  try {
8044
- const bytes = readFileSync4(path4);
8484
+ const bytes = readFileSync5(path5);
8045
8485
  return bytes.length > 0 ? bytes : null;
8046
8486
  } catch {
8047
8487
  return null;
@@ -8072,10 +8512,10 @@ function readClipboardImage(options = {}) {
8072
8512
  if (bytes === null) return null;
8073
8513
  const dir = options.dir ?? tmpdir();
8074
8514
  const name = `indus-clipboard-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.png`;
8075
- const path4 = join8(dir, name);
8515
+ const path5 = join8(dir, name);
8076
8516
  try {
8077
- writeFileSync(path4, bytes);
8078
- return path4;
8517
+ writeFileSync2(path5, bytes);
8518
+ return path5;
8079
8519
  } catch {
8080
8520
  return null;
8081
8521
  }
@@ -8083,7 +8523,7 @@ function readClipboardImage(options = {}) {
8083
8523
 
8084
8524
  // src/kit/external-editor.ts
8085
8525
  import { spawnSync as spawnSync2 } from "node:child_process";
8086
- import { readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
8526
+ import { readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
8087
8527
  import { tmpdir as tmpdir2 } from "node:os";
8088
8528
  import { join as join9 } from "node:path";
8089
8529
  var FALLBACK_EDITOR = "vi";
@@ -8096,18 +8536,28 @@ function openInExternalEditor(buffer, options = {}) {
8096
8536
  const env2 = options.env ?? process.env;
8097
8537
  const dir = options.dir ?? tmpdir2();
8098
8538
  const name = `indus-editor-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.md`;
8099
- const path4 = join9(dir, name);
8539
+ const path5 = join9(dir, name);
8100
8540
  const [editor, ...editorArgs] = resolveEditor(env2);
8101
8541
  try {
8102
- writeFileSync2(path4, buffer, "utf8");
8103
- 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
+ }
8104
8554
  if (result.error || result.status !== 0) return null;
8105
- return readFileSync5(path4, "utf8").replace(/\n$/, "");
8555
+ return readFileSync6(path5, "utf8").replace(/\n$/, "");
8106
8556
  } catch {
8107
8557
  return null;
8108
8558
  } finally {
8109
8559
  try {
8110
- unlinkSync(path4);
8560
+ unlinkSync(path5);
8111
8561
  } catch {
8112
8562
  }
8113
8563
  }
@@ -8757,6 +9207,7 @@ function lastAssistantText(messages) {
8757
9207
  return "";
8758
9208
  }
8759
9209
  function toPublishRole(role) {
9210
+ if (role === "toolResult") return "tool";
8760
9211
  return PUBLISH_ROLES.has(role) ? role : "note";
8761
9212
  }
8762
9213
  function toPublishEntries(messages) {
@@ -8787,7 +9238,7 @@ function exportTranscriptHtml(ctx, target) {
8787
9238
  const html = publishTranscript(toPublishEntries(messages), {
8788
9239
  title: "Session Transcript"
8789
9240
  });
8790
- writeFileSync3(outPath, html, "utf-8");
9241
+ writeFileSync4(outPath, html, "utf-8");
8791
9242
  return { ok: true, path: outPath };
8792
9243
  } catch (cause) {
8793
9244
  const message = cause instanceof Error ? cause.message : String(cause);
@@ -9178,13 +9629,11 @@ function runLogin2(ctx) {
9178
9629
  }
9179
9630
  return HANDLED;
9180
9631
  }
9181
- function hasLocalProviderKey(provider) {
9182
- if (provider.length === 0) return false;
9632
+ async function hasLocalProviderKey(provider, ctx) {
9633
+ if (!provider) return false;
9183
9634
  try {
9184
- const file = join10(homedir2(), ".indusagi", "agent", "auth.json");
9185
- const vault = JSON.parse(readFileSync6(file, "utf8"));
9186
- const entry = vault[provider];
9187
- 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;
9188
9637
  } catch {
9189
9638
  return false;
9190
9639
  }
@@ -9200,7 +9649,7 @@ function runStatus(ctx) {
9200
9649
  signedIn = false;
9201
9650
  }
9202
9651
  const eligible = isGatewayEligibleProvider(provider);
9203
- const localKey = hasLocalProviderKey(provider);
9652
+ const localKey = await hasLocalProviderKey(provider, ctx);
9204
9653
  const viaServer = signedIn && eligible && !localKey;
9205
9654
  if (viaServer) {
9206
9655
  ctx.setStatus(info(`\u25CF server tier \u2014 ${modelId} routing via ${INDUS_SERVER_URL} (signed in)`));
@@ -9381,12 +9830,7 @@ var compactCommand = {
9381
9830
  ctx.setStatus({ kind: "busy", text: busyText });
9382
9831
  void (async () => {
9383
9832
  try {
9384
- if (instructions.length > 0) {
9385
- await ctx.conductor.executeBash(
9386
- `printf '%s' ${JSON.stringify(`Compaction guidance: ${instructions}`)}`
9387
- );
9388
- }
9389
- switch (await ctx.conductor.condense()) {
9833
+ switch (await ctx.conductor.condense(instructions || void 0)) {
9390
9834
  case "condensed":
9391
9835
  ctx.setStatus(
9392
9836
  info(
@@ -9561,7 +10005,7 @@ var transcriptCommands = [
9561
10005
  ];
9562
10006
 
9563
10007
  // src/console/slash/commands/workbench.ts
9564
- import { mkdtempSync, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
10008
+ import { mkdtempSync, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
9565
10009
  import { tmpdir as tmpdir3 } from "node:os";
9566
10010
  import { join as join11 } from "node:path";
9567
10011
  function block(slug, title, markdown) {
@@ -9597,7 +10041,7 @@ function hotkeysBlock() {
9597
10041
  function readChangelog(cwd) {
9598
10042
  for (const name of CHANGELOG_NAMES) {
9599
10043
  try {
9600
- const text = readFileSync7(join11(cwd, name), "utf8");
10044
+ const text = readFileSync8(join11(cwd, name), "utf8");
9601
10045
  if (text.trim().length > 0) return text;
9602
10046
  } catch {
9603
10047
  }
@@ -9635,9 +10079,9 @@ function runDebug(ctx) {
9635
10079
  const jsonl = records.map((r) => JSON.stringify(r)).join("\n") + "\n";
9636
10080
  try {
9637
10081
  const dir = mkdtempSync(join11(tmpdir3(), "indus-debug-"));
9638
- const path4 = join11(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
9639
- writeFileSync4(path4, jsonl, "utf8");
9640
- 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}`));
9641
10085
  } catch (cause) {
9642
10086
  const detail = cause instanceof Error ? cause.message : String(cause);
9643
10087
  ctx.setStatus(warn(`Could not write the diagnostics log (${detail}).`));
@@ -9689,7 +10133,7 @@ var HOTKEYS = [
9689
10133
  ["Tab", "Accept the highlighted completion"],
9690
10134
  ["Ctrl+C", "Interrupt the running turn"],
9691
10135
  ["Ctrl+T", "Toggle reasoning / thinking rows"],
9692
- ["/thinking", "Cycle thinking effort level (off \u2192 low \u2192 medium)"],
10136
+ ["/thinking", "Cycle the selected model's supported reasoning efforts"],
9693
10137
  ["Ctrl+V", "Attach an image from the clipboard"],
9694
10138
  ["Ctrl+G", "Edit the composer in your external editor"],
9695
10139
  ["Ctrl+O", "Expand or collapse full tool output"],
@@ -9761,7 +10205,7 @@ var workbenchCommands = [
9761
10205
  {
9762
10206
  name: "whats-new",
9763
10207
  summary: "Show what changed in this build",
9764
- aliases: ["changelog"],
10208
+ aliases: ["changelog", "release-notes"],
9765
10209
  run: runChangelog
9766
10210
  },
9767
10211
  {
@@ -9771,7 +10215,7 @@ var workbenchCommands = [
9771
10215
  },
9772
10216
  {
9773
10217
  name: "thinking",
9774
- summary: "Cycle thinking effort level (off \u2192 low \u2192 medium \u2192 off)",
10218
+ summary: "Cycle reasoning effort using the selected model's supported levels",
9775
10219
  run: (ctx) => {
9776
10220
  ctx.conductor.cycleThinkingLevel();
9777
10221
  return HANDLED;
@@ -9787,7 +10231,7 @@ var workbenchCommands = [
9787
10231
  // src/console/slash/builtins.ts
9788
10232
  var PROJECT_DIR = ".indusagi";
9789
10233
  function dynamicRoots() {
9790
- const home = homedir3();
10234
+ const home = homedir2();
9791
10235
  const cwd = process.cwd();
9792
10236
  return {
9793
10237
  skills: [
@@ -9925,7 +10369,7 @@ function readKey(input, key) {
9925
10369
  case "n":
9926
10370
  return { verb: "flow:cycleModel" };
9927
10371
  case "p":
9928
- return { verb: "flow:cycleModel" };
10372
+ return { verb: "flow:cycleModel", direction: -1 };
9929
10373
  case "t":
9930
10374
  return { verb: "view:toggleReasoning" };
9931
10375
  case "v":
@@ -9950,15 +10394,15 @@ function readKey(input, key) {
9950
10394
  }
9951
10395
  if (key.escape) return { verb: "flow:dismiss" };
9952
10396
  if (key.tab) return key.shift ? { verb: "mode:cyclePermission" } : { verb: "flow:accept" };
9953
- 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") {
9954
10399
  return { verb: "edit:erasePrev" };
9955
10400
  }
9956
10401
  if (key.leftArrow) return { verb: "nav:left" };
9957
10402
  if (key.rightArrow) return { verb: "nav:right" };
9958
10403
  if (key.upArrow) return { verb: "nav:up" };
9959
10404
  if (key.downArrow) return { verb: "nav:down" };
9960
- if (key.pageUp) return { verb: "nav:home" };
9961
- if (key.pageDown) return { verb: "nav:end" };
10405
+ if (key.pageUp || key.pageDown) return NO_INTENT;
9962
10406
  return isPrintable(input) ? typed(input) : NO_INTENT;
9963
10407
  }
9964
10408
  var NO_CHORD = { armed: null };
@@ -10078,12 +10522,12 @@ function splitPathStem(stem) {
10078
10522
  const body = stem.slice(sigil.length);
10079
10523
  const slash = body.lastIndexOf("/");
10080
10524
  if (slash < 0) return { dir: ".", leaf: body, sigil };
10081
- 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 };
10082
10526
  }
10083
10527
  function completePath(stem, readDir) {
10084
10528
  const { dir, leaf, sigil } = splitPathStem(stem);
10085
10529
  const lowerLeaf = leaf.toLowerCase();
10086
- const prefix = dir === "." ? "" : `${dir}/`;
10530
+ const prefix = dir === "." ? "" : dir === "/" ? "/" : `${dir}/`;
10087
10531
  const matches = readDir(dir).filter((e) => e.name.toLowerCase().startsWith(lowerLeaf)).sort((a, b) => {
10088
10532
  if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
10089
10533
  return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
@@ -10119,6 +10563,7 @@ function applySuggestion(buffer, span, suggestion) {
10119
10563
  // src/console/input/dir-reader.ts
10120
10564
  import { readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
10121
10565
  import { resolve as resolve6 } from "node:path";
10566
+ import { homedir as homedir3 } from "node:os";
10122
10567
  function entryIsDir(base, name, isDirent, isSymlink) {
10123
10568
  if (isDirent) return true;
10124
10569
  if (!isSymlink) return false;
@@ -10130,7 +10575,8 @@ function entryIsDir(base, name, isDirent, isSymlink) {
10130
10575
  }
10131
10576
  function createDirReader(cwd = process.cwd()) {
10132
10577
  return (dirPath) => {
10133
- const target = resolve6(cwd, dirPath);
10578
+ const expanded = dirPath === "~" ? homedir3() : dirPath.startsWith("~/") ? resolve6(homedir3(), dirPath.slice(2)) : dirPath;
10579
+ const target = resolve6(cwd, expanded);
10134
10580
  try {
10135
10581
  const dirents = readdirSync3(target, { withFileTypes: true });
10136
10582
  return dirents.map((d) => ({
@@ -10145,11 +10591,12 @@ function createDirReader(cwd = process.cwd()) {
10145
10591
 
10146
10592
  // src/console/components/TerminalConsole.ts
10147
10593
  import { execFileSync } from "node:child_process";
10148
- import { homedir as homedir7 } from "node:os";
10149
- import React3, { useEffect as useEffect7, useMemo as useMemo2, useState as useState8 } from "indusagi/react-host";
10150
- 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";
10151
10598
  import { MessageList, StreamingMarkdown, TaskPanel } from "indusagi/react-ink";
10152
- 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";
10153
10600
 
10154
10601
  // src/workspace/runtime-detect.ts
10155
10602
  var BUN_VIRTUAL_FS_MARKERS = ["$bunfs", "~BUN", "%7EBUN"];
@@ -10164,13 +10611,13 @@ function isPackagedBinary() {
10164
10611
  }
10165
10612
 
10166
10613
  // src/settings/manager.ts
10167
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
10168
- 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";
10169
10616
  function loadTier(filePath) {
10170
- if (!existsSync3(filePath)) return {};
10617
+ if (!existsSync2(filePath)) return {};
10171
10618
  let raw;
10172
10619
  try {
10173
- raw = readFileSync8(filePath, "utf-8");
10620
+ raw = readFileSync9(filePath, "utf-8");
10174
10621
  } catch {
10175
10622
  return {};
10176
10623
  }
@@ -10186,9 +10633,9 @@ function loadTier(filePath) {
10186
10633
  return { ...parsed };
10187
10634
  }
10188
10635
  function writeTier(filePath, tier) {
10189
- const dir = dirname4(filePath);
10190
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
10191
- 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)}
10192
10639
  `, "utf-8");
10193
10640
  }
10194
10641
  var PreferenceStore = class _PreferenceStore {
@@ -10261,14 +10708,14 @@ var IMAGE_SNIFF_BYTES = PNG_SIGNATURE.length;
10261
10708
 
10262
10709
  // src/sessions/library.ts
10263
10710
  import {
10264
- mkdir as mkdir2,
10265
- readFile as readFile2,
10711
+ mkdir,
10712
+ readFile,
10266
10713
  readdir,
10267
10714
  rename,
10268
10715
  rm,
10269
10716
  stat
10270
10717
  } from "node:fs/promises";
10271
- import { basename as basename4, join as join14 } from "node:path";
10718
+ import { basename as basename5, join as join14 } from "node:path";
10272
10719
  var SESSION_FILE_EXT = ".ndjson";
10273
10720
  var PREVIEW_LIMIT = 72;
10274
10721
  var SessionLibrary = class {
@@ -10300,14 +10747,14 @@ var SessionLibrary = class {
10300
10747
  if (!await this.#missing(toPath)) {
10301
10748
  throw new Error(`SessionLibrary.rename: session "${toId}" already exists`);
10302
10749
  }
10303
- await mkdir2(this.#dir, { recursive: true });
10750
+ await mkdir(this.#dir, { recursive: true });
10304
10751
  await rename(fromPath, toPath);
10305
10752
  return toPath;
10306
10753
  }
10307
10754
  async remove(id) {
10308
- const path4 = this.pathOf(id);
10309
- if (await this.#missing(path4)) return false;
10310
- 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 });
10311
10758
  return true;
10312
10759
  }
10313
10760
  pathOf(id) {
@@ -10362,41 +10809,41 @@ var SessionLibrary = class {
10362
10809
  } catch {
10363
10810
  return [];
10364
10811
  }
10365
- 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));
10366
10813
  }
10367
10814
  async #shallowRow(id) {
10368
- const path4 = this.pathOf(id);
10369
- const meta = await this.#fileMeta(path4);
10370
- return { id, path: path4, ...meta };
10815
+ const path5 = this.pathOf(id);
10816
+ const meta = await this.#fileMeta(path5);
10817
+ return { id, path: path5, ...meta };
10371
10818
  }
10372
10819
  async #deepRow(id) {
10373
- const path4 = this.pathOf(id);
10374
- const meta = await this.#fileMeta(path4);
10820
+ const path5 = this.pathOf(id);
10821
+ const meta = await this.#fileMeta(path5);
10375
10822
  const store = await this.open(id);
10376
- if (store === null) return { id, path: path4, ...meta };
10823
+ if (store === null) return { id, path: path5, ...meta };
10377
10824
  const branch = store.pathTo();
10378
10825
  const conversational = branch.filter(isConversational);
10379
10826
  const opener = conversational.find((entry) => entry.role === "user");
10380
10827
  const preview2 = opener ? previewOf(messageText(opener.content)) : void 0;
10381
10828
  return {
10382
10829
  id,
10383
- path: path4,
10830
+ path: path5,
10384
10831
  ...meta,
10385
10832
  messageCount: conversational.length,
10386
10833
  ...preview2 ? { preview: preview2, name: preview2 } : {}
10387
10834
  };
10388
10835
  }
10389
- async #fileMeta(path4) {
10836
+ async #fileMeta(path5) {
10390
10837
  try {
10391
- const info2 = await stat(path4);
10838
+ const info2 = await stat(path5);
10392
10839
  return { size: info2.size, lastModified: info2.mtimeMs };
10393
10840
  } catch {
10394
10841
  return {};
10395
10842
  }
10396
10843
  }
10397
- async #missing(path4) {
10844
+ async #missing(path5) {
10398
10845
  try {
10399
- await readFile2(path4);
10846
+ await readFile(path5);
10400
10847
  return false;
10401
10848
  } catch {
10402
10849
  return true;
@@ -10555,9 +11002,9 @@ function resolveSpecifierPath(specifier) {
10555
11002
  function buildAliasMap() {
10556
11003
  const map = {};
10557
11004
  for (const specifier of BUNDLED_NAMESPACES) {
10558
- const path4 = resolveSpecifierPath(specifier);
10559
- if (path4 !== void 0) {
10560
- map[specifier] = path4;
11005
+ const path5 = resolveSpecifierPath(specifier);
11006
+ if (path5 !== void 0) {
11007
+ map[specifier] = path5;
10561
11008
  }
10562
11009
  }
10563
11010
  return map;
@@ -10605,8 +11052,8 @@ function createJitiLoader(options = {}) {
10605
11052
  const base = options.base ?? process.cwd();
10606
11053
  const home = options.home ?? homedir4();
10607
11054
  return {
10608
- async load(path4) {
10609
- const absolute = resolvePath(path4, base, home);
11055
+ async load(path5) {
11056
+ const absolute = resolvePath(path5, base, home);
10610
11057
  const specifier = pathToFileURL(absolute).href;
10611
11058
  const imported = await jiti.import(specifier);
10612
11059
  const manifest = toManifest(imported);
@@ -10621,8 +11068,8 @@ function createJitiLoader(options = {}) {
10621
11068
  }
10622
11069
 
10623
11070
  // src/addons/manifest.ts
10624
- import { readFileSync as readFileSync9, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
10625
- 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";
10626
11073
  var ENTRY_EXTENSIONS = [
10627
11074
  ".ts",
10628
11075
  ".tsx",
@@ -10639,9 +11086,9 @@ function isHidden(name) {
10639
11086
  function hasEntryExtension(name) {
10640
11087
  return ENTRY_EXTENSIONS.includes(extname3(name).toLowerCase());
10641
11088
  }
10642
- function entryKind(path4) {
11089
+ function entryKind(path5) {
10643
11090
  try {
10644
- const stat2 = statSync4(path4);
11091
+ const stat2 = statSync4(path5);
10645
11092
  if (stat2.isDirectory()) return "directory";
10646
11093
  if (stat2.isFile()) return "file";
10647
11094
  return void 0;
@@ -10662,7 +11109,7 @@ function readManifestEntry(packageDir) {
10662
11109
  return void 0;
10663
11110
  }
10664
11111
  try {
10665
- const parsed = JSON.parse(readFileSync9(manifestPath, "utf8"));
11112
+ const parsed = JSON.parse(readFileSync10(manifestPath, "utf8"));
10666
11113
  if (typeof parsed !== "object" || parsed === null) {
10667
11114
  return void 0;
10668
11115
  }
@@ -10694,13 +11141,13 @@ function resolveTopLevelEntry(dir, name) {
10694
11141
  if (isHidden(name)) {
10695
11142
  return void 0;
10696
11143
  }
10697
- const path4 = join15(dir, name);
10698
- const kind = entryKind(path4);
11144
+ const path5 = join15(dir, name);
11145
+ const kind = entryKind(path5);
10699
11146
  if (kind === "file") {
10700
- return hasEntryExtension(name) ? path4 : void 0;
11147
+ return hasEntryExtension(name) ? path5 : void 0;
10701
11148
  }
10702
11149
  if (kind === "directory") {
10703
- return resolvePackageEntry(path4);
11150
+ return resolvePackageEntry(path5);
10704
11151
  }
10705
11152
  return void 0;
10706
11153
  }
@@ -10715,9 +11162,9 @@ function discoverAddons(dir) {
10715
11162
  return entries.sort();
10716
11163
  }
10717
11164
  function deriveAddonId(entryPath) {
10718
- const file = basename5(entryPath);
11165
+ const file = basename6(entryPath);
10719
11166
  const stem = file.slice(0, file.length - extname3(file).length);
10720
- const label = INDEX_BASENAMES.includes(stem) ? basename5(join15(entryPath, "..")) : stem;
11167
+ const label = INDEX_BASENAMES.includes(stem) ? basename6(join15(entryPath, "..")) : stem;
10721
11168
  return addonId(label.length > 0 ? label : entryPath);
10722
11169
  }
10723
11170
  function addonsDirFor(workspace, dir = ADDONS_DIR) {
@@ -10734,12 +11181,12 @@ function discoverSources(discovery) {
10734
11181
  }
10735
11182
  const seen = /* @__PURE__ */ new Set();
10736
11183
  const sources = [];
10737
- for (const path4 of paths) {
10738
- if (seen.has(path4)) {
11184
+ for (const path5 of paths) {
11185
+ if (seen.has(path5)) {
10739
11186
  continue;
10740
11187
  }
10741
- seen.add(path4);
10742
- sources.push({ id: deriveAddonId(path4), path: path4 });
11188
+ seen.add(path5);
11189
+ sources.push({ id: deriveAddonId(path5), path: path5 });
10743
11190
  }
10744
11191
  return sources;
10745
11192
  }
@@ -11308,6 +11755,7 @@ function ndjsonStrategy() {
11308
11755
  },
11309
11756
  onSignal(signal, ctx) {
11310
11757
  const s = signal;
11758
+ if (s.kind === "tool_update") return;
11311
11759
  ctx.out.write(ctx.framer.encodeLine({ type: "signal", name: s.kind, body: s }));
11312
11760
  },
11313
11761
  finish(state, ctx) {
@@ -11414,16 +11862,16 @@ function createDialogBridge(deps) {
11414
11862
  ask(kind, payload, fallback) {
11415
11863
  const id = nextAskId(seq);
11416
11864
  const frame = { type: "ask", id, kind, payload };
11417
- return new Promise((resolve9) => {
11865
+ return new Promise((resolve11) => {
11418
11866
  const timer = setTimeout(() => {
11419
11867
  pending.delete(id);
11420
- resolve9(fallback);
11868
+ resolve11(fallback);
11421
11869
  }, deps.timings.dialogMs);
11422
11870
  if (typeof timer.unref === "function") {
11423
11871
  timer.unref();
11424
11872
  }
11425
11873
  pending.set(id, {
11426
- settle: (value) => resolve9(value),
11874
+ settle: (value) => resolve11(value),
11427
11875
  timer
11428
11876
  });
11429
11877
  emit(frame);
@@ -12016,6 +12464,9 @@ var DEFAULT_REDACTOR = createRedactor();
12016
12464
  // src/insight/replay.ts
12017
12465
  var TEXT_DECODER3 = new TextDecoder();
12018
12466
 
12467
+ // src/review-agent/reviewer.ts
12468
+ import { Type as Type8 } from "@sinclair/typebox";
12469
+
12019
12470
  // src/console/overlays/approval-queue.ts
12020
12471
  var EMPTY_APPROVAL_QUEUE = { entries: [] };
12021
12472
  function approvalQueueReducer(state, event) {
@@ -12049,6 +12500,7 @@ import { jsx as jsx2 } from "indusagi/react-host/jsx-runtime";
12049
12500
 
12050
12501
  // src/console/auth-status.ts
12051
12502
  import { useEffect, useState } from "indusagi/react-host";
12503
+ import { getEnvApiKey as getEnvApiKey3 } from "indusagi/ai";
12052
12504
  function useAuthenticatedProviders(services, refreshKey) {
12053
12505
  const [authed, setAuthed] = useState(null);
12054
12506
  useEffect(() => {
@@ -12057,10 +12509,14 @@ function useAuthenticatedProviders(services, refreshKey) {
12057
12509
  void (async () => {
12058
12510
  const found = /* @__PURE__ */ new Set();
12059
12511
  try {
12060
- 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) {
12061
12517
  try {
12062
- if ((await services.vault.listAccounts(provider.id)).length > 0) {
12063
- found.add(provider.id);
12518
+ if (getEnvApiKey3(provider) || (await services.vault.listAccounts(provider)).length > 0) {
12519
+ found.add(provider);
12064
12520
  }
12065
12521
  } catch {
12066
12522
  }
@@ -12078,15 +12534,12 @@ function useAuthenticatedProviders(services, refreshKey) {
12078
12534
  return () => {
12079
12535
  live3 = false;
12080
12536
  };
12081
- }, [refreshKey]);
12537
+ }, [services, refreshKey]);
12082
12538
  return authed;
12083
12539
  }
12084
12540
 
12085
12541
  // src/console/overlays/boards.ts
12086
12542
  import chalk2 from "chalk";
12087
- import { readFileSync as readFileSync10 } from "node:fs";
12088
- import { homedir as homedir5 } from "node:os";
12089
- import { join as join16 } from "node:path";
12090
12543
  import { useEffect as useEffect2, useMemo, useState as useState2 } from "indusagi/react-host";
12091
12544
  import { Box, Text, useInput } from "indusagi/react-host/ink";
12092
12545
  import {
@@ -12094,28 +12547,6 @@ import {
12094
12547
  wrapSelectionIndex
12095
12548
  } from "indusagi/react-ink";
12096
12549
  import { Fragment, jsx, jsxs } from "indusagi/react-host/jsx-runtime";
12097
- function readServerTierInfo() {
12098
- let signedIn = false;
12099
- try {
12100
- const file = join16(homedir5(), ".indusagi", "agent", "server-token.json");
12101
- const token = JSON.parse(readFileSync10(file, "utf8"));
12102
- signedIn = typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1e3;
12103
- } catch {
12104
- signedIn = false;
12105
- }
12106
- const localKeys = /* @__PURE__ */ new Set();
12107
- try {
12108
- const file = join16(homedir5(), ".indusagi", "agent", "auth.json");
12109
- const vault = JSON.parse(readFileSync10(file, "utf8"));
12110
- for (const [provider, entry] of Object.entries(vault)) {
12111
- if (entry !== null && typeof entry === "object" && Object.keys(entry).length > 0) {
12112
- localKeys.add(provider);
12113
- }
12114
- }
12115
- } catch {
12116
- }
12117
- return { signedIn, localKeys };
12118
- }
12119
12550
  function truncate(text, width) {
12120
12551
  if (text.length <= width) return text;
12121
12552
  if (width <= 1) return text.slice(0, width);
@@ -12212,6 +12643,7 @@ function nextValue(values, current, direction) {
12212
12643
  return values[(at + direction + values.length) % values.length];
12213
12644
  }
12214
12645
  function SettingsBoard({ theme, items, onClose }) {
12646
+ const [error, setError] = useState2();
12215
12647
  const [query, setQuery] = useState2("");
12216
12648
  const [selectedIndex, setSelectedIndex] = useState2(0);
12217
12649
  const filtered = useMemo(
@@ -12228,7 +12660,15 @@ function SettingsBoard({ theme, items, onClose }) {
12228
12660
  const item = filtered[selectedIndex];
12229
12661
  if (!item) return;
12230
12662
  const value = nextValue(item.values, item.value, direction);
12231
- 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
+ }
12232
12672
  };
12233
12673
  useInput((input, key) => {
12234
12674
  if (key.escape) return onClose();
@@ -12243,7 +12683,7 @@ function SettingsBoard({ theme, items, onClose }) {
12243
12683
  const { start, budget, below } = windowAround(filtered.length, selectedIndex);
12244
12684
  const visible = filtered.slice(start, start + budget);
12245
12685
  const selected = filtered[selectedIndex];
12246
- const detail = selected ? `${selected.description} \xB7 ${selected.values.join(" / ")}` : void 0;
12686
+ const detail = error ?? (selected ? `${selected.description} \xB7 ${selected.values.join(" / ")}` : void 0);
12247
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, {
12248
12688
  flexDirection: "column",
12249
12689
  children: visible.map((item, index) => {
@@ -12283,12 +12723,7 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
12283
12723
  const [query, setQuery] = useState2(search ?? "");
12284
12724
  const [selectedIndex, setSelectedIndex] = useState2(0);
12285
12725
  useEffect2(() => setQuery(search ?? ""), [search]);
12286
- const serverTier = useMemo(readServerTierInfo, []);
12287
- const isServerTier = (provider) => serverTier.signedIn && isGatewayEligibleProvider(provider) && !serverTier.localKeys.has(provider);
12288
- const available = useMemo(
12289
- () => models.filter((model) => serverTier.localKeys.has(model.provider) || isServerTier(model.provider)),
12290
- [models, serverTier]
12291
- );
12726
+ const available = models;
12292
12727
  const filtered = useMemo(() => available.filter((model) => modelMatches(model, query)), [available, query]);
12293
12728
  useEffect2(() => setSelectedIndex(0), [query]);
12294
12729
  useEffect2(() => {
@@ -12324,10 +12759,10 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
12324
12759
  jsx(Box, { width: 2, children: jsx(Text, { children: isSelected ? theme.color("accent", "\u203A") : " " }) }),
12325
12760
  jsx(Text, { bold: isSelected, children: theme.color("text", model.id) }),
12326
12761
  model.name ? jsx(Text, { children: theme.dim(` ${model.name}`) }) : null,
12327
- isServerTier(model.provider) ? jsx(Text, { children: theme.color("accent", " \u2AE1 server") }) : null
12762
+ jsx(Text, { children: theme.dim(` ${model.provider}`) })
12328
12763
  ]
12329
12764
  },
12330
- model.id
12765
+ `${model.provider}/${model.id}`
12331
12766
  );
12332
12767
  })
12333
12768
  });
@@ -12344,6 +12779,19 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
12344
12779
 
12345
12780
  // src/console/overlays/pickers.ts
12346
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
+ }
12347
12795
  function refToModel(ref) {
12348
12796
  return {
12349
12797
  id: ref.id,
@@ -12366,7 +12814,7 @@ function listModels(services) {
12366
12814
  }
12367
12815
  }
12368
12816
  function ModelPicker(props) {
12369
- const { services, theme, closeModal } = props;
12817
+ const { services, theme, closeModal, dispatch: dispatch2 } = props;
12370
12818
  const authed = useAuthenticatedProviders(services);
12371
12819
  if (!services) return null;
12372
12820
  const all = listModels(services);
@@ -12377,7 +12825,8 @@ function ModelPicker(props) {
12377
12825
  onSelect: (model) => {
12378
12826
  try {
12379
12827
  services.conductor.selectModel(model.id);
12380
- } catch {
12828
+ } catch (error) {
12829
+ reportError(dispatch2, error);
12381
12830
  }
12382
12831
  closeModal();
12383
12832
  },
@@ -12398,8 +12847,7 @@ function ScopedModelPicker(props) {
12398
12847
  useEffect3(() => {
12399
12848
  if (!services || payload.intent !== "reset") return;
12400
12849
  try {
12401
- services.settings.set("enabledModels", []);
12402
- services.settings.save();
12850
+ saveSetting(services.settings, "enabledModels", []);
12403
12851
  dispatch2({
12404
12852
  type: "status:set",
12405
12853
  status: { kind: "info", text: "Per-scope model overrides cleared." }
@@ -12429,9 +12877,9 @@ function ScopedModelPicker(props) {
12429
12877
  try {
12430
12878
  const all = models.map((model) => model.id);
12431
12879
  const next = ids.length === all.length ? [] : ids;
12432
- services.settings.set("enabledModels", next);
12433
- services.settings.save();
12434
- } catch {
12880
+ saveSetting(services.settings, "enabledModels", next);
12881
+ } catch (error) {
12882
+ reportError(dispatch2, error);
12435
12883
  }
12436
12884
  closeModal();
12437
12885
  },
@@ -12458,11 +12906,11 @@ function ThemePicker(props) {
12458
12906
  themes: [...THEME_SCHEMES],
12459
12907
  onSelect: (name) => {
12460
12908
  if (isThemeScheme(name)) {
12461
- dispatch2({ type: "scheme:set", scheme: name });
12462
12909
  try {
12463
- services.settings.set("colourScheme", name);
12464
- services.settings.save();
12465
- } catch {
12910
+ saveSetting(services.settings, "colourScheme", name);
12911
+ dispatch2({ type: "scheme:set", scheme: name });
12912
+ } catch (error) {
12913
+ reportError(dispatch2, error);
12466
12914
  }
12467
12915
  }
12468
12916
  closeModal();
@@ -12484,9 +12932,8 @@ function SettingsPicker(props) {
12484
12932
  value: onOff(settings.get(key)),
12485
12933
  values: [...TOGGLE_VALUES],
12486
12934
  onChange: (value) => {
12487
- settings.set(key, value === "on");
12488
- settings.save();
12489
- live3?.();
12935
+ saveSetting(settings, key, value === "on");
12936
+ live3?.(value === "on");
12490
12937
  bump();
12491
12938
  }
12492
12939
  });
@@ -12498,8 +12945,7 @@ function SettingsPicker(props) {
12498
12945
  values: [...DELIVERY_MODES],
12499
12946
  onChange: (value) => {
12500
12947
  if (isDeliveryMode(value)) {
12501
- settings.set(key, value);
12502
- settings.save();
12948
+ saveSetting(settings, key, value);
12503
12949
  }
12504
12950
  bump();
12505
12951
  }
@@ -12513,16 +12959,15 @@ function SettingsPicker(props) {
12513
12959
  value: settings.get("colourScheme"),
12514
12960
  values: [...THEME_SCHEMES],
12515
12961
  onChange: (value) => {
12516
- settings.set("colourScheme", value);
12517
- settings.save();
12962
+ saveSetting(settings, "colourScheme", value);
12518
12963
  if (isThemeScheme(value)) dispatch2({ type: "scheme:set", scheme: value });
12519
12964
  bump();
12520
12965
  }
12521
12966
  });
12522
- 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 })));
12523
12968
  items.push(toggleRow("image-auto-resize", "imageAutoResize", "Auto-resize images", "Shrink oversized images to fit before handing them to a provider."));
12524
12969
  items.push(toggleRow("block-images", "blockImages", "Block images", "Withhold image content from providers entirely."));
12525
- 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 })));
12526
12971
  items.push(toggleRow("skill-commands", "enableSkillCommands", "Skill commands", "Surface discovered skills as their own slash commands."));
12527
12972
  items.push(deliveryRow("steering-mode", "steeringMode", "Steering mode", "Whether queued steering corrections release one at a time or all at once."));
12528
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."));
@@ -12531,7 +12976,6 @@ function SettingsPicker(props) {
12531
12976
  items.push(toggleRow("quiet-startup", "quietStartup", "Quiet startup", "Suppress the banner and tips shown on a normal interactive launch."));
12532
12977
  items.push(toggleRow("logo-sweep", "logoSweep", "Logo sweep", "Tint the startup wordmark and emblem along a static colour gradient."));
12533
12978
  items.push(toggleRow("reduced-motion", "reducedMotion", "Reduced motion", "Suppress motion-flavoured flourishes such as the logo colour sweep."));
12534
- items.push(toggleRow("hardware-cursor", "showHardwareCursor", "Hardware cursor", "Reveal the terminal's own cursor instead of the software-drawn caret."));
12535
12979
  items.push({
12536
12980
  id: "editor-padding",
12537
12981
  label: "Editor padding",
@@ -12541,8 +12985,7 @@ function SettingsPicker(props) {
12541
12985
  onChange: (value) => {
12542
12986
  const parsed = Number.parseInt(value, 10);
12543
12987
  if (Number.isFinite(parsed)) {
12544
- settings.set("editorPaddingX", parsed);
12545
- settings.save();
12988
+ saveSetting(settings, "editorPaddingX", parsed);
12546
12989
  }
12547
12990
  bump();
12548
12991
  }
@@ -12796,7 +13239,7 @@ function SignInDialog(props) {
12796
13239
  var SERVER_TIER_ACCOUNT_ID = "server-session";
12797
13240
  var SERVER_TIER_PROVIDER_ID2 = "indus-server";
12798
13241
  function SignOutDialog(props) {
12799
- const { services, closeModal } = props;
13242
+ const { services, closeModal, dispatch: dispatch2 } = props;
12800
13243
  const [accounts, setAccounts] = useState5([]);
12801
13244
  useEffect5(() => {
12802
13245
  let live3 = true;
@@ -12848,7 +13291,10 @@ function SignOutDialog(props) {
12848
13291
  } else {
12849
13292
  await services.vault.remove(account.provider, account.accountId);
12850
13293
  }
12851
- } 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." } });
12852
13298
  }
12853
13299
  closeModal();
12854
13300
  }
@@ -12886,15 +13332,24 @@ function OAuthFlowDialog(props) {
12886
13332
  })();
12887
13333
  const [state, setState] = useState5(() => seedOAuthState(providerId, providerName, mode));
12888
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
+ }, []);
12889
13341
  const merge = (next) => setState((prev) => ({ ...prev, ...next }));
12890
13342
  useEffect5(() => {
12891
13343
  if (mode !== "oauth") return;
12892
13344
  let live3 = true;
13345
+ const controller = new AbortController();
12893
13346
  const safeMerge = (next) => {
12894
13347
  if (live3) merge(next);
12895
13348
  };
12896
13349
  const callbacks = {
13350
+ signal: controller.signal,
12897
13351
  onAuth: (info2) => {
13352
+ if (!live3) return;
12898
13353
  safeMerge({ authInfo: info2, progress: "Opening your browser to sign in\u2026" });
12899
13354
  void services.openLoginUrl(info2.url).then((opened) => {
12900
13355
  safeMerge({
@@ -12902,12 +13357,28 @@ function OAuthFlowDialog(props) {
12902
13357
  });
12903
13358
  });
12904
13359
  },
12905
- onPrompt: (prompt) => new Promise((resolve9) => {
12906
- 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
+ };
12907
13370
  safeMerge({ prompt, inputLabel: prompt.message, inputValue: "" });
12908
13371
  }),
12909
- onManualCodeInput: () => new Promise((resolve9) => {
12910
- 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
+ };
12911
13382
  safeMerge({
12912
13383
  prompt: { message: "Paste the authorization code or complete redirect URL:" },
12913
13384
  inputLabel: "Authorization code or redirect URL",
@@ -12921,7 +13392,9 @@ function OAuthFlowDialog(props) {
12921
13392
  void (async () => {
12922
13393
  try {
12923
13394
  await services.startOAuthLogin(providerId, callbacks, services.vault);
13395
+ if (!live3) return;
12924
13396
  await services.refreshGatewayRouting();
13397
+ if (!live3) return;
12925
13398
  selectProviderModel(services, providerId);
12926
13399
  if (live3) {
12927
13400
  dispatch2({
@@ -12936,15 +13409,23 @@ function OAuthFlowDialog(props) {
12936
13409
  })();
12937
13410
  return () => {
12938
13411
  live3 = false;
13412
+ controller.abort();
13413
+ cancelPrompt.current?.();
13414
+ cancelPrompt.current = null;
13415
+ pending.current = null;
12939
13416
  };
12940
13417
  }, []);
12941
13418
  const saveApiKey = () => {
12942
13419
  const key = state.inputValue.trim();
12943
- if (key.length === 0) return;
13420
+ if (key.length === 0 || saving.current) return;
13421
+ saving.current = true;
12944
13422
  const account = state.accountId ?? "default";
12945
13423
  void (async () => {
12946
13424
  try {
12947
13425
  await services.vault.putApiKey(providerId, account, key, true);
13426
+ if (!mounted.current) return;
13427
+ await services.refreshGatewayRouting();
13428
+ if (!mounted.current) return;
12948
13429
  selectProviderModel(services, providerId);
12949
13430
  dispatch2({
12950
13431
  type: "status:set",
@@ -12967,12 +13448,12 @@ function OAuthFlowDialog(props) {
12967
13448
  saveApiKey();
12968
13449
  return;
12969
13450
  }
12970
- const resolve9 = pending.current;
12971
- if (resolve9 === null) return;
13451
+ const resolve11 = pending.current;
13452
+ if (resolve11 === null) return;
12972
13453
  pending.current = null;
12973
13454
  const value = state.inputValue;
12974
13455
  merge({ inputValue: "", prompt: void 0 });
12975
- resolve9(value);
13456
+ resolve11(value);
12976
13457
  },
12977
13458
  onClose: closeModal
12978
13459
  });
@@ -13127,12 +13608,12 @@ function summarizeInput(input) {
13127
13608
  }
13128
13609
  function ApprovalDialog(props) {
13129
13610
  const { payload, closeModal } = props;
13130
- const { request, resolve: resolve9 } = payload;
13611
+ const { request, resolve: resolve11 } = payload;
13131
13612
  let settled = false;
13132
13613
  const settle = (choice) => {
13133
13614
  if (settled) return;
13134
13615
  settled = true;
13135
- resolve9(choice);
13616
+ resolve11(choice);
13136
13617
  closeModal();
13137
13618
  };
13138
13619
  const args = summarizeInput(request.input);
@@ -13169,8 +13650,18 @@ function ApprovalOverlays(props) {
13169
13650
  }
13170
13651
 
13171
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
+ }
13172
13660
  function OverlayHost(props) {
13173
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
+ }
13174
13665
  const forwarded = {
13175
13666
  modal: props.modal,
13176
13667
  services: props.services,
@@ -13189,31 +13680,31 @@ function OverlayHost(props) {
13189
13680
  }
13190
13681
 
13191
13682
  // src/console/startup.ts
13192
- import { existsSync as existsSync4, readFileSync as readFileSync11 } from "node:fs";
13193
- 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";
13194
13685
  var PROFILE_DIR = ".indusagi";
13195
13686
  var CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md"];
13196
13687
  function skillRoots(cwd, home) {
13197
13688
  return [
13198
- { dir: join17(cwd, PROFILE_DIR, "skills"), origin: "project" },
13199
- { 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" }
13200
13691
  ];
13201
13692
  }
13202
13693
  function macroRoots(cwd, home) {
13203
13694
  return [
13204
- { dir: join17(cwd, PROFILE_DIR, "commands"), origin: "project", label: "project" },
13205
- { 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" }
13206
13697
  ];
13207
13698
  }
13208
- function shortenPath2(path4, cwd, home) {
13209
- if (cwd.length > 0 && path4.startsWith(cwd)) {
13210
- 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(/^[/\\]+/, "");
13211
13702
  return rest.length > 0 ? `./${rest}` : ".";
13212
13703
  }
13213
- if (home.length > 0 && path4.startsWith(home)) {
13214
- return `~${path4.slice(home.length)}`;
13704
+ if (home.length > 0 && path5.startsWith(home)) {
13705
+ return `~${path5.slice(home.length)}`;
13215
13706
  }
13216
- return path4;
13707
+ return path5;
13217
13708
  }
13218
13709
  var MAX_SECTION_LINES = 6;
13219
13710
  function capLines(lines) {
@@ -13232,10 +13723,10 @@ function gatherContext(cwd, home) {
13232
13723
  for (const root of [cwd, home]) {
13233
13724
  if (root.length === 0) continue;
13234
13725
  for (const name of CONTEXT_FILES) {
13235
- const path4 = join17(root, name);
13236
- if (seen.has(path4)) continue;
13237
- seen.add(path4);
13238
- 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));
13239
13730
  }
13240
13731
  }
13241
13732
  return out2;
@@ -13299,12 +13790,12 @@ function mascotRows(face) {
13299
13790
  ];
13300
13791
  }
13301
13792
  var TIPS = [
13302
- "Run /init to create a CLAUDE.md file with instructions for IndusCode",
13793
+ "Run /help to explore commands and configured skill helpers",
13303
13794
  'Ask in plain English \u2014 try "fix the failing tests" or "explain src/agent.ts"',
13304
- "Shift+Tab cycles permission modes: auto \u2192 plan \u2192 accept-edits",
13795
+ "Shift+Tab cycles: default \u2192 accept-edits \u2192 plan \u2192 bypass",
13305
13796
  "Type @ to attach a file, or paste an image straight into the prompt",
13306
13797
  "Run /status to check your model, account, and connectivity",
13307
- "Use /skills to discover specialized helpers for the task at hand"
13798
+ "Use /help to discover specialized commands for the task at hand"
13308
13799
  ];
13309
13800
  var WHATS_NEW = [
13310
13801
  "Ctrl+X runs your newest queued message NOW \u2014 the current run stops cleanly",
@@ -13324,148 +13815,21 @@ function pickWelcomeVariant(seed) {
13324
13815
  var PINNED_WELCOME_SEED = 12;
13325
13816
 
13326
13817
  // src/console/components/AgentsView.ts
13327
- import { useState as useState6 } from "indusagi/react-host";
13328
- 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";
13329
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";
13330
13827
  import { jsx as jsx7, jsxs as jsxs4 } from "indusagi/react-host/jsx-runtime";
13331
- function formatTokens2(tokens) {
13332
- return tokens >= 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : String(tokens);
13333
- }
13334
- function clampLine(text, width) {
13335
- const flat = text.replace(/\s+/g, " ").trim();
13336
- if (flat.length <= width) return flat;
13337
- if (width <= 1) return flat.slice(0, width);
13338
- return `${flat.slice(0, width - 1)}\u2026`;
13339
- }
13340
- function runStatusLine(run, now, tokens) {
13341
- let head;
13342
- if (run.status === "error") head = "failed";
13343
- else if (run.status === "done") head = "done";
13344
- else if (run.startedAt > 0 && now >= run.startedAt) {
13345
- head = `${Math.max(0, Math.round((now - run.startedAt) / 1e3))}s`;
13346
- } else head = "working";
13347
- const tok = tokens !== void 0 && tokens > 0 ? ` \xB7 \u2193 ${formatTokens2(tokens)} tokens` : "";
13348
- return `${head}${tok}`;
13349
- }
13350
- var NAME_WIDTH = 12;
13351
- function ActivityRow({ theme, line }) {
13352
- if (line.tone === "tool") {
13353
- return jsxs4(Box4, {
13354
- children: [
13355
- jsx7(Text4, { children: theme.color("success", "\u25CF ") }),
13356
- jsx7(Text4, { children: theme.color("text", clampLine(line.text, 96)) })
13357
- ]
13358
- });
13359
- }
13360
- if (line.tone === "result") {
13361
- return jsx7(Box4, { marginLeft: 2, children: jsx7(Text4, { children: theme.dim(`\u2514 ${clampLine(line.text, 92)}`) }) });
13362
- }
13363
- return jsxs4(Box4, {
13364
- children: [
13365
- jsx7(Text4, { children: theme.color("accent", "\u25CF ") }),
13366
- jsx7(Text4, { children: theme.color("text", clampLine(line.text, 96)) })
13367
- ]
13368
- });
13369
- }
13370
- function AgentsView({ theme, runs, activity, tokens, now, onExit }) {
13371
- const clock = now ?? Date.now();
13372
- const [selectedIndex, setSelectedIndex] = useState6(runs.length > 0 ? 1 : 0);
13373
- const rowCount = runs.length + 1;
13374
- const index = Math.min(selectedIndex, rowCount - 1);
13375
- const viewed = index > 0 ? runs[index - 1] : void 0;
13376
- useInput3((input, key) => {
13377
- if (key.escape || input === "x") return onExit();
13378
- if (key.upArrow) return setSelectedIndex((i) => wrapSelectionIndex2(Math.min(i, rowCount - 1), -1, rowCount));
13379
- if (key.downArrow) return setSelectedIndex((i) => wrapSelectionIndex2(Math.min(i, rowCount - 1), 1, rowCount));
13380
- if (key.return && index === 0) onExit();
13381
- });
13382
- const lines = viewed ? activity[viewed.id] ?? [] : [];
13383
- return jsxs4(Box4, {
13384
- flexDirection: "column",
13385
- marginTop: 1,
13386
- children: [
13387
- viewed ? jsxs4(Box4, {
13388
- flexDirection: "column",
13389
- children: [
13390
- jsxs4(Box4, {
13391
- marginBottom: 1,
13392
- children: [
13393
- jsx7(Text4, { children: theme.color("accent", "\u2736 ") }),
13394
- jsx7(Text4, { bold: true, children: theme.color("accent", viewed.name) }),
13395
- jsx7(Text4, { children: theme.dim(` ${runStatusLine(viewed, clock, tokens[viewed.id])}`) })
13396
- ]
13397
- }),
13398
- lines.length === 0 ? jsx7(Text4, { children: theme.muted(" Waiting for the first step\u2026") }) : lines.map((line, i) => jsx7(ActivityRow, { theme, line }, i)),
13399
- jsx7(Box4, {
13400
- marginTop: 1,
13401
- children: jsx7(Text4, { wrap: "truncate", children: theme.muted(`\u2514 ${clampLine(viewed.objective, 100)}`) })
13402
- })
13403
- ]
13404
- }) : jsxs4(Box4, {
13405
- children: [
13406
- jsx7(Text4, { children: theme.muted("Select a sub-agent to view its live activity, or press Enter on ") }),
13407
- jsx7(Text4, { bold: true, children: theme.color("text", "main") }),
13408
- jsx7(Text4, { children: theme.muted(" to return.") })
13409
- ]
13410
- }),
13411
- jsxs4(Box4, {
13412
- flexDirection: "column",
13413
- marginTop: 1,
13414
- borderStyle: "single",
13415
- borderColor: theme.colors["borderMuted"],
13416
- borderBottom: false,
13417
- borderLeft: false,
13418
- borderRight: false,
13419
- paddingTop: 0,
13420
- children: [
13421
- jsxs4(Box4, {
13422
- children: [
13423
- jsx7(Text4, { children: index === 0 ? theme.color("accent", "\u203A ") : " " }),
13424
- jsx7(Text4, { children: index === 0 ? theme.color("text", "\u25CF ") : theme.dim("\u25CB ") }),
13425
- jsx7(Text4, { bold: index === 0, children: theme.color("text", "main") })
13426
- ]
13427
- }),
13428
- runs.map((run, i) => {
13429
- const selected = index === i + 1;
13430
- const dot = run.status === "running" ? theme.color("accent", "\u25D0") : run.status === "error" ? theme.color("error", "\u25CF") : theme.color("success", "\u25CF");
13431
- return jsxs4(
13432
- Box4,
13433
- {
13434
- children: [
13435
- jsx7(Text4, { children: selected ? theme.color("accent", "\u203A ") : " " }),
13436
- jsx7(Box4, { width: 2, flexShrink: 0, children: jsx7(Text4, { children: dot }) }),
13437
- jsx7(Box4, {
13438
- width: NAME_WIDTH,
13439
- flexShrink: 0,
13440
- children: jsx7(Text4, { bold: selected, children: theme.color("accent", clampLine(run.name, NAME_WIDTH - 1)) })
13441
- }),
13442
- jsx7(Box4, {
13443
- flexGrow: 1,
13444
- flexShrink: 1,
13445
- children: jsx7(Text4, { wrap: "truncate", children: theme.muted(clampLine(run.objective, 56)) })
13446
- }),
13447
- jsx7(Text4, { children: theme.dim(` ${runStatusLine(run, clock, tokens[run.id])}`) })
13448
- ]
13449
- },
13450
- run.id
13451
- );
13452
- })
13453
- ]
13454
- }),
13455
- jsx7(Box4, { marginTop: 1, children: jsx7(Text4, { children: theme.dim("\u2191/\u2193 select \xB7 Enter to view \xB7 Enter on main / Esc to return") }) })
13456
- ]
13457
- });
13458
- }
13459
-
13460
- // src/console/components/BackgroundAgents.ts
13461
- import { Box as Box5, Text as Text5 } from "indusagi/react-host/ink";
13462
- import { splitAssistantMessage } from "indusagi/react-ink";
13463
- import { jsx as jsx8, jsxs as jsxs5 } from "indusagi/react-host/jsx-runtime";
13464
- function readString2(args, key) {
13465
- const value = args[key];
13466
- if (typeof value !== "string") return void 0;
13467
- const trimmed = value.trim();
13468
- 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;
13469
13833
  }
13470
13834
  function deriveAgentRuns(messages) {
13471
13835
  const runs = [];
@@ -13496,7 +13860,7 @@ function deriveAgentRuns(messages) {
13496
13860
  return runs.map((run) => ({ ...run, status: status.get(run.id) ?? run.status }));
13497
13861
  }
13498
13862
  var MAX_VISIBLE_RUNS = 8;
13499
- var NAME_WIDTH2 = 12;
13863
+ var NAME_WIDTH = 12;
13500
13864
  function clamp2(text, width) {
13501
13865
  if (text.length <= width) return text;
13502
13866
  if (width <= 1) return text.slice(0, width);
@@ -13506,7 +13870,7 @@ var OBJECTIVE_WIDTH = 64;
13506
13870
  function oneLine(text) {
13507
13871
  return text.replace(/\s+/g, " ").trim();
13508
13872
  }
13509
- function formatTokens3(tokens) {
13873
+ function formatTokens2(tokens) {
13510
13874
  if (tokens >= 1e3) return `${(tokens / 1e3).toFixed(1)}k`;
13511
13875
  return String(tokens);
13512
13876
  }
@@ -13518,65 +13882,206 @@ function statusText(run, now, tokens) {
13518
13882
  head = `${Math.max(0, Math.round((now - run.startedAt) / 1e3))}s`;
13519
13883
  } else head = "running";
13520
13884
  if (tokens !== void 0 && tokens > 0) {
13521
- return `${head} \xB7 \u2193 ${formatTokens3(tokens)} tokens`;
13885
+ return `${head} \xB7 \u2193 ${formatTokens2(tokens)} tokens`;
13522
13886
  }
13523
13887
  return head;
13524
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
+ }
13525
13898
  function BackgroundAgents({ theme, runs, tokens, now }) {
13526
- const clock = now ?? Date.now();
13899
+ const clock = useAgentClock(runs.some((run) => run.status === "running"), now);
13527
13900
  const runningCount = runs.reduce((n, run) => run.status === "running" ? n + 1 : n, 0);
13528
13901
  if (runningCount === 0) return null;
13529
- const visible = runs.slice(-MAX_VISIBLE_RUNS);
13530
- 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, {
13531
13906
  flexDirection: "column",
13532
13907
  marginTop: 1,
13533
13908
  children: [
13534
- jsxs5(Box5, {
13909
+ jsxs4(Box5, {
13535
13910
  children: [
13536
- jsx8(Text5, { children: theme.color("text", "\u25CF ") }),
13537
- 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") })
13538
13913
  ]
13539
13914
  }),
13540
13915
  visible.map((run) => {
13541
13916
  const dot = run.status === "running" ? theme.color("accent", "\u25D0") : run.status === "error" ? theme.color("error", "\u25CF") : theme.color("success", "\u25CF");
13542
- return jsxs5(
13917
+ return jsxs4(
13543
13918
  Box5,
13544
13919
  {
13545
13920
  children: [
13546
- jsx8(Box5, { width: 2, flexShrink: 0, children: jsx8(Text5, { children: dot }) }),
13547
- jsx8(Box5, {
13548
- width: NAME_WIDTH2,
13921
+ jsx7(Box5, { width: 2, flexShrink: 0, children: jsx7(Text5, { children: dot }) }),
13922
+ jsx7(Box5, {
13923
+ width: NAME_WIDTH,
13549
13924
  flexShrink: 0,
13550
- 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)) })
13551
13926
  }),
13552
- jsx8(Box5, {
13927
+ jsx7(Box5, {
13553
13928
  flexGrow: 1,
13554
13929
  flexShrink: 1,
13555
- 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)) })
13556
13931
  }),
13557
- jsx8(Text5, { children: theme.dim(` ${statusText(run, clock, tokens?.[run.id])}`) })
13932
+ jsx7(Text5, { children: theme.dim(` ${statusText(run, clock, tokens?.[run.id])}`) })
13558
13933
  ]
13559
13934
  },
13560
13935
  run.id
13561
13936
  );
13562
13937
  }),
13563
- jsxs5(Box5, {
13938
+ jsxs4(Box5, {
13564
13939
  marginTop: 1,
13565
13940
  children: [
13566
- jsx8(Text5, { children: theme.color("accent", "\u2736 ") }),
13567
- 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`) })
13568
13943
  ]
13569
13944
  })
13570
13945
  ]
13571
13946
  });
13572
13947
  }
13573
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
+
13574
14079
  // src/console/components/Banner.ts
13575
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs6 } from "indusagi/react-host/jsx-runtime";
13576
- 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";
13577
14082
  import { ChangelogBlock } from "indusagi/react-ink";
13578
- import { homedir as homedir6 } from "node:os";
13579
- import { basename as basename6 } from "node:path";
14083
+ import { homedir as homedir5 } from "node:os";
14084
+ import { basename as basename7 } from "node:path";
13580
14085
 
13581
14086
  // src/console/components/banner-sweep.ts
13582
14087
  var WELCOME_FALLBACK = "Welcome back!";
@@ -13587,6 +14092,70 @@ function welcomeLine(name) {
13587
14092
  if (trimmed.length === 0 || trimmed.length > MAX_NAME) return WELCOME_FALLBACK;
13588
14093
  return `Welcome back, ${trimmed}!`;
13589
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
+ }
13590
14159
 
13591
14160
  // src/console/components/Banner.ts
13592
14161
  var BRAND2 = "IndusCode";
@@ -13599,12 +14168,16 @@ var WORDMARK_ROWS = [
13599
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",
13600
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"
13601
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
+ }
13602
14175
  function StartupNotices({ theme, notices }) {
13603
14176
  if (notices.length === 0) return null;
13604
- return jsx9(Box6, {
14177
+ return jsx10(Box8, {
13605
14178
  flexDirection: "column",
13606
14179
  marginBottom: 1,
13607
- 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))
13608
14181
  });
13609
14182
  }
13610
14183
  function paintNotice(theme, notice) {
@@ -13620,15 +14193,15 @@ function paintNotice(theme, notice) {
13620
14193
  }
13621
14194
  function StartupChangelogBlock({ theme, changelog }) {
13622
14195
  if (changelog.mode === "full" && changelog.markdown !== void 0) {
13623
- return jsx9(ChangelogBlock, { markdown: changelog.markdown, theme, title: "What is new" });
14196
+ return jsx10(ChangelogBlock, { markdown: changelog.markdown, theme, title: "What is new" });
13624
14197
  }
13625
14198
  if (changelog.mode === "condensed") {
13626
- return jsxs6(Box6, {
14199
+ return jsxs6(Box8, {
13627
14200
  marginTop: 1,
13628
14201
  children: [
13629
- jsx9(Text6, { children: theme.muted(`Updated to v${changelog.version}. Use `) }),
13630
- jsx9(Text6, { children: theme.color("accent", "/changelog") }),
13631
- 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.") })
13632
14205
  ]
13633
14206
  });
13634
14207
  }
@@ -13638,46 +14211,46 @@ function WelcomeLine({ theme, name, tone = "accent" }) {
13638
14211
  const paint = (text) => tone === "muted" ? theme.muted(text) : theme.color("accent", text);
13639
14212
  const line = welcomeLine(name);
13640
14213
  if (name !== void 0 && line !== "Welcome back!") {
13641
- return jsxs6(Box6, {
14214
+ return jsxs6(Box8, {
13642
14215
  children: [
13643
- jsx9(Text6, { children: paint("Welcome back, ") }),
13644
- jsx9(Text6, { bold: true, children: name.trim() }),
13645
- jsx9(Text6, { children: paint("!") })
14216
+ jsx10(Text7, { children: paint("Welcome back, ") }),
14217
+ jsx10(Text7, { bold: true, children: name.trim() }),
14218
+ jsx10(Text7, { children: paint("!") })
13646
14219
  ]
13647
14220
  });
13648
14221
  }
13649
- return jsx9(Box6, { children: jsx9(Text6, { children: paint(line) }) });
14222
+ return jsx10(Box8, { children: jsx10(Text7, { children: paint(line) }) });
13650
14223
  }
13651
14224
  function ShellLine({ theme, workspace, branch }) {
13652
- const dir = basename6(workspace);
13653
- return jsxs6(Box6, {
14225
+ const dir = basename7(workspace);
14226
+ return jsxs6(Box8, {
13654
14227
  children: [
13655
- jsx9(Text6, { children: theme.color("customMessage", "\u279C") }),
13656
- jsx9(Text6, { children: " " }),
13657
- 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) }),
13658
14231
  branch !== void 0 && branch !== null && branch.length > 0 ? jsxs6(Fragment3, {
13659
14232
  children: [
13660
- jsx9(Text6, { children: " " }),
13661
- jsx9(Text6, { children: theme.color("accent", "git:(") }),
13662
- jsx9(Text6, { children: theme.color("customMessage", branch) }),
13663
- 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", ")") })
13664
14237
  ]
13665
14238
  }) : null,
13666
- jsx9(Text6, { children: theme.dim(" indus") })
14239
+ jsx10(Text7, { children: theme.dim(" indus") })
13667
14240
  ]
13668
14241
  });
13669
14242
  }
13670
- function Wordmark({ theme }) {
13671
- return jsx9(Box6, {
14243
+ function Wordmark({ theme, sweep }) {
14244
+ return jsx10(Box8, {
13672
14245
  flexDirection: "column",
13673
- 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))
13674
14247
  });
13675
14248
  }
13676
14249
  function SessionFact({ theme, label, value }) {
13677
- return jsxs6(Box6, {
14250
+ return jsxs6(Box8, {
13678
14251
  children: [
13679
- jsx9(Text6, { children: theme.dim(`${label.padEnd(7)} `) }),
13680
- jsx9(Text6, { children: theme.muted(value) })
14252
+ jsx10(Text7, { children: theme.dim(`${label.padEnd(7)} `) }),
14253
+ jsx10(Text7, { children: theme.muted(value) })
13681
14254
  ]
13682
14255
  });
13683
14256
  }
@@ -13693,17 +14266,17 @@ function paintMascot(theme, tone, text) {
13693
14266
  return theme.dim(text);
13694
14267
  }
13695
14268
  }
13696
- function Mascot({ theme, face }) {
13697
- return jsx9(Box6, {
14269
+ function Mascot({ theme, face, sweep }) {
14270
+ return jsx10(Box8, {
13698
14271
  flexDirection: "column",
13699
14272
  alignItems: "center",
13700
14273
  marginY: 1,
13701
14274
  children: mascotRows(face).map(
13702
- (row, rowIndex) => jsx9(
13703
- Box6,
14275
+ (row, rowIndex) => jsx10(
14276
+ Box8,
13704
14277
  {
13705
14278
  children: row.map(
13706
- (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)
13707
14280
  )
13708
14281
  },
13709
14282
  rowIndex
@@ -13712,13 +14285,14 @@ function Mascot({ theme, face }) {
13712
14285
  });
13713
14286
  }
13714
14287
  function shortenWorkspace(workspace) {
13715
- const home = homedir6();
14288
+ const home = homedir5();
13716
14289
  if (home.length > 0 && workspace.startsWith(home)) {
13717
14290
  return `~${workspace.slice(home.length)}`;
13718
14291
  }
13719
14292
  return workspace;
13720
14293
  }
13721
14294
  function Banner(props) {
14295
+ const { columns } = useTerminalSize();
13722
14296
  const {
13723
14297
  theme,
13724
14298
  modelId,
@@ -13732,71 +14306,75 @@ function Banner(props) {
13732
14306
  variantSeed,
13733
14307
  whatsNew,
13734
14308
  notices,
13735
- changelog
14309
+ changelog,
14310
+ sweep,
14311
+ startup
13736
14312
  } = props;
13737
14313
  const noticeList = notices ?? [];
13738
- if (quiet || compact) {
13739
- return jsxs6(Box6, {
14314
+ if (quiet || compact || columns < 76) {
14315
+ return jsxs6(Box8, {
13740
14316
  flexDirection: "column",
13741
14317
  marginBottom: 1,
13742
14318
  children: [
13743
- jsx9(StartupNotices, { theme, notices: noticeList }),
13744
- jsxs6(Box6, {
14319
+ jsx10(StartupNotices, { theme, notices: noticeList }),
14320
+ jsx10(StartupSummary, { theme, startup }),
14321
+ jsxs6(Box8, {
13745
14322
  children: [
13746
14323
  compact ? jsxs6(Fragment3, {
13747
14324
  children: [
13748
- jsx9(Text6, { children: theme.color("accent", COMPACT_EMBLEM.slice(0, 2)) }),
13749
- jsx9(Text6, { children: theme.color("customMessage", COMPACT_EMBLEM.slice(2)) }),
13750
- 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: " " })
13751
14328
  ]
13752
14329
  }) : null,
13753
- jsx9(Text6, { bold: true, children: theme.color("accent", BRAND2) }),
13754
- jsx9(Text6, { children: " " }),
13755
- jsx9(Text6, { children: theme.muted(`v${version}`) }),
13756
- jsx9(Text6, { children: " " }),
13757
- 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) })
13758
14335
  ]
13759
14336
  }),
13760
- jsx9(WelcomeLine, { theme, name, tone: "muted" }),
13761
- changelog ? jsx9(StartupChangelogBlock, { theme, changelog }) : null
14337
+ jsx10(WelcomeLine, { theme, name, tone: "muted" }),
14338
+ changelog ? jsx10(StartupChangelogBlock, { theme, changelog }) : null
13762
14339
  ]
13763
14340
  });
13764
14341
  }
13765
14342
  if (started) {
13766
- return jsxs6(Box6, {
14343
+ return jsxs6(Box8, {
13767
14344
  flexDirection: "column",
13768
14345
  marginBottom: 1,
13769
14346
  children: [
13770
- jsx9(StartupNotices, { theme, notices: noticeList }),
13771
- jsx9(ShellLine, { theme, workspace, branch }),
13772
- jsx9(Box6, { marginTop: 1, children: jsx9(Wordmark, { theme }) }),
13773
- 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, {
13774
14352
  marginTop: 1,
13775
14353
  flexDirection: "column",
13776
14354
  children: [
13777
- jsxs6(Box6, {
14355
+ jsxs6(Box8, {
13778
14356
  children: [
13779
- jsx9(Text6, { bold: true, children: theme.color("accent", "indus console") }),
13780
- jsx9(Text6, { children: " " }),
13781
- 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}`) })
13782
14360
  ]
13783
14361
  }),
13784
- jsx9(WelcomeLine, { theme, name, tone: "accent" })
14362
+ jsx10(WelcomeLine, { theme, name, tone: "accent" })
13785
14363
  ]
13786
14364
  }),
13787
- jsxs6(Box6, {
14365
+ jsxs6(Box8, {
13788
14366
  flexDirection: "column",
13789
14367
  borderStyle: "round",
13790
14368
  borderColor: theme.colors["accent"],
13791
14369
  paddingX: 1,
13792
14370
  marginTop: 1,
13793
14371
  children: [
13794
- jsx9(Text6, { bold: true, children: theme.color("accent", "Session") }),
13795
- jsx9(SessionFact, { theme, label: "model", value: modelId }),
13796
- 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) })
13797
14375
  ]
13798
14376
  }),
13799
- changelog ? jsx9(StartupChangelogBlock, { theme, changelog }) : null
14377
+ changelog ? jsx10(StartupChangelogBlock, { theme, changelog }) : null
13800
14378
  ]
13801
14379
  });
13802
14380
  }
@@ -13804,43 +14382,44 @@ function Banner(props) {
13804
14382
  const greeting = welcomeLine(name);
13805
14383
  const news = whatsNew && whatsNew.length > 0 ? whatsNew : WHATS_NEW;
13806
14384
  const metaLine = modelId;
13807
- return jsxs6(Box6, {
14385
+ return jsxs6(Box8, {
13808
14386
  flexDirection: "column",
13809
14387
  marginBottom: 1,
13810
14388
  children: [
13811
- jsx9(StartupNotices, { theme, notices: noticeList }),
13812
- jsxs6(Box6, {
14389
+ jsx10(StartupNotices, { theme, notices: noticeList }),
14390
+ jsx10(StartupSummary, { theme, startup }),
14391
+ jsxs6(Box8, {
13813
14392
  flexDirection: "column",
13814
14393
  borderStyle: "round",
13815
14394
  borderColor: theme.colors["accent"],
13816
14395
  paddingX: 2,
13817
14396
  paddingY: 1,
13818
14397
  children: [
13819
- jsxs6(Box6, {
14398
+ jsxs6(Box8, {
13820
14399
  children: [
13821
- jsx9(Text6, { children: theme.color("customMessage", "\u25CF ") }),
13822
- jsx9(Text6, { bold: true, children: theme.color("accent", `${BRAND2} `) }),
13823
- 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}`) })
13824
14403
  ]
13825
14404
  }),
13826
- jsxs6(Box6, {
14405
+ jsxs6(Box8, {
13827
14406
  marginTop: 1,
13828
14407
  flexDirection: "row",
13829
14408
  children: [
13830
- jsxs6(Box6, {
14409
+ jsxs6(Box8, {
13831
14410
  flexDirection: "column",
13832
14411
  alignItems: "center",
13833
14412
  width: 32,
13834
14413
  flexShrink: 0,
13835
14414
  marginRight: 2,
13836
14415
  children: [
13837
- jsx9(Text6, { bold: true, children: theme.color("text", greeting) }),
13838
- jsx9(Mascot, { theme, face: variant.mascot }),
13839
- jsx9(Text6, { children: theme.dim(metaLine) }),
13840
- 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)) })
13841
14420
  ]
13842
14421
  }),
13843
- jsxs6(Box6, {
14422
+ jsxs6(Box8, {
13844
14423
  flexDirection: "column",
13845
14424
  flexGrow: 1,
13846
14425
  borderStyle: "single",
@@ -13850,108 +14429,96 @@ function Banner(props) {
13850
14429
  borderBottom: false,
13851
14430
  paddingLeft: 2,
13852
14431
  children: [
13853
- jsx9(Text6, { bold: true, children: theme.color("accent", "Tips for getting started") }),
13854
- jsx9(Text6, { children: theme.color("text", variant.tip) }),
13855
- 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") }) }),
13856
14435
  news.map(
13857
- (line, index) => jsxs6(Box6, {
14436
+ (line, index) => jsxs6(Box8, {
13858
14437
  flexDirection: "row",
13859
14438
  children: [
13860
- jsx9(Box6, { width: 2, flexShrink: 0, children: jsx9(Text6, { children: theme.color("customMessage", "\u25B8") }) }),
13861
- 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) }) })
13862
14441
  ]
13863
14442
  }, index)
13864
14443
  ),
13865
- 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") }) })
13866
14445
  ]
13867
14446
  })
13868
14447
  ]
13869
14448
  })
13870
14449
  ]
13871
14450
  }),
13872
- changelog ? jsx9(StartupChangelogBlock, { theme, changelog }) : null
14451
+ changelog ? jsx10(StartupChangelogBlock, { theme, changelog }) : null
13873
14452
  ]
13874
14453
  });
13875
14454
  }
13876
14455
 
13877
14456
  // src/console/components/Composer.ts
13878
- import { Box as Box7, Text as Text7 } from "indusagi/react-host/ink";
13879
- import { jsx as jsx10, jsxs as jsxs7 } from "indusagi/react-host/jsx-runtime";
13880
- var PROMPT_GLYPH = "\u203A ";
13881
- var SUGGESTION_WINDOW = 8;
13882
- var NAME_COLUMN_CAP = 24;
13883
- var NAME_COLUMN_FLOOR = 8;
13884
- function clampName(label, width) {
13885
- if (label.length <= width) return label;
13886
- if (width <= 1) return label.slice(0, width);
13887
- return `${label.slice(0, width - 1)}\u2026`;
13888
- }
13889
- function bufferWithCaret(buffer, caret) {
13890
- const before = buffer.slice(0, caret);
13891
- const under = caret < buffer.length ? buffer[caret] : " ";
13892
- const after = caret < buffer.length ? buffer.slice(caret + 1) : "";
13893
- return jsxs7(Text7, {
13894
- children: [before, jsx10(Text7, { inverse: true, children: under }), after]
13895
- });
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;
13896
14483
  }
13897
- function Composer({ theme, buffer, caret, busy, suggestions, highlight }) {
13898
- const shown = suggestions.slice(0, SUGGESTION_WINDOW);
13899
- const borderColor = theme.colors.borderMuted ?? theme.colors.accent ?? "gray";
13900
- const nameWidth = shown.length > 0 ? Math.max(NAME_COLUMN_FLOOR, Math.min(NAME_COLUMN_CAP, Math.max(...shown.map((s) => s.label.length)))) : 0;
13901
- return jsxs7(Box7, {
13902
- flexDirection: "column",
13903
- children: [
13904
- jsxs7(Box7, {
13905
- borderStyle: "round",
13906
- borderColor,
13907
- paddingX: 1,
13908
- children: [
13909
- jsxs7(Box7, {
13910
- flexGrow: 1,
13911
- children: [
13912
- jsx10(Text7, { children: theme.color("customMessage", PROMPT_GLYPH) }),
13913
- bufferWithCaret(buffer, caret)
13914
- ]
13915
- }),
13916
- jsx10(Text7, { children: theme.dim("\u21B5 send") })
13917
- ]
13918
- }),
13919
- shown.length > 0 ? jsx10(Box7, {
13920
- flexDirection: "column",
13921
- marginTop: 1,
13922
- children: shown.map((suggestion, index) => {
13923
- const active = index === highlight;
13924
- const name = clampName(suggestion.label, nameWidth);
13925
- return jsxs7(
13926
- Box7,
13927
- {
13928
- children: [
13929
- jsx10(Box7, {
13930
- width: 2,
13931
- flexShrink: 0,
13932
- children: jsx10(Text7, { children: active ? theme.color("accent", "\u25B8") : " " })
13933
- }),
13934
- jsx10(Box7, {
13935
- width: nameWidth,
13936
- flexShrink: 0,
13937
- children: jsx10(Text7, { bold: active, children: theme.color("customMessage", name) })
13938
- }),
13939
- jsx10(Text7, { children: " " }),
13940
- jsx10(Text7, { children: theme.muted(suggestion.detail) })
13941
- ]
13942
- },
13943
- suggestion.value
13944
- );
13945
- })
13946
- }) : null
13947
- ]
13948
- });
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
+ ] });
13949
14516
  }
13950
14517
 
13951
14518
  // src/console/components/StatusBar.ts
13952
- 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";
13953
14520
  import { Footer, StatusLine } from "indusagi/react-ink";
13954
- 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";
13955
14522
  function permissionBadge(mode) {
13956
14523
  switch (mode) {
13957
14524
  case "acceptEdits":
@@ -13968,26 +14535,26 @@ function permissionBadge(mode) {
13968
14535
  }
13969
14536
  function StatusBar({ theme, snapshot, branch, providerCount, status, permissionMode }) {
13970
14537
  const badge = permissionMode !== void 0 ? permissionBadge(permissionMode) : null;
13971
- return jsxs8(Box8, {
14538
+ return jsxs8(Box10, {
13972
14539
  flexDirection: "column",
13973
14540
  children: [
13974
- jsx11(StatusLine, { theme, snapshot, status, showBusyText: false }),
13975
- badge !== null ? jsx11(Box8, {
13976
- children: jsxs8(Text8, {
14541
+ jsx12(StatusLine, { theme, snapshot, status, showBusyText: false }),
14542
+ badge !== null ? jsx12(Box10, {
14543
+ children: jsxs8(Text9, {
13977
14544
  color: badge.color,
13978
14545
  children: ["\u25CF permission: ", badge.label]
13979
14546
  })
13980
14547
  }) : null,
13981
- jsx11(Footer, { theme, branch, snapshot, availableProviderCount: providerCount })
14548
+ jsx12(Footer, { theme, branch, snapshot, availableProviderCount: providerCount })
13982
14549
  ]
13983
14550
  });
13984
14551
  }
13985
14552
 
13986
14553
  // src/console/components/WorkingIndicator.ts
13987
- import React2, { useEffect as useEffect6, useState as useState7 } from "indusagi/react-host";
13988
- import { Box as Box9, Text as Text9 } from "indusagi/react-host/ink";
13989
- import { jsx as jsx12, jsxs as jsxs9 } from "indusagi/react-host/jsx-runtime";
13990
- 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;
13991
14558
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
13992
14559
  var FRAME_MS = 120;
13993
14560
  var WORD_MS = 4e3;
@@ -14037,11 +14604,11 @@ function phaseLabel(phase) {
14037
14604
  return null;
14038
14605
  }
14039
14606
  }
14040
- function WorkingIndicator({ theme, busy, phase }) {
14041
- const [frame, setFrame] = useState7(0);
14607
+ function WorkingIndicator({ theme, busy, phase, reducedMotion = false }) {
14608
+ const [frame, setFrame] = useState9(0);
14042
14609
  const startRef = useRef2(null);
14043
14610
  const wordOffsetRef = useRef2(0);
14044
- useEffect6(() => {
14611
+ useEffect8(() => {
14045
14612
  if (!busy) {
14046
14613
  startRef.current = null;
14047
14614
  return;
@@ -14050,28 +14617,27 @@ function WorkingIndicator({ theme, busy, phase }) {
14050
14617
  startRef.current = Date.now();
14051
14618
  wordOffsetRef.current = Math.floor(Math.random() * WORKING_WORDS.length);
14052
14619
  }
14053
- 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);
14054
14621
  return () => clearInterval(id);
14055
- }, [busy]);
14622
+ }, [busy, reducedMotion]);
14056
14623
  if (!busy) return null;
14057
14624
  const elapsedMs = startRef.current !== null ? Date.now() - startRef.current : 0;
14058
14625
  const elapsedSec = Math.floor(elapsedMs / 1e3);
14059
- const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
14060
- const label = phaseLabel(phase) ?? pickWord(wordOffsetRef.current + Math.floor(elapsedMs / WORD_MS));
14061
- 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, {
14062
14629
  marginTop: 1,
14063
14630
  children: [
14064
- jsx12(Text9, { children: theme.color("accent", `${spinner} ${label}\u2026 `) }),
14065
- 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)`) })
14066
14633
  ]
14067
14634
  });
14068
14635
  }
14069
14636
 
14070
14637
  // src/console/components/TerminalConsole.ts
14071
- var { useReducer, useRef: useRef3, useCallback } = React3;
14638
+ var { useReducer, useRef: useRef3, useCallback } = React4;
14072
14639
  var CHORD_WINDOW_MS = 600;
14073
14640
  var CTRL_C_EXIT_WINDOW_MS = 500;
14074
- var FALLBACK_TERMINAL_ROWS = 40;
14075
14641
  var CHROME_ROWS = 14;
14076
14642
  var MODAL_CHROME_ROWS = 24;
14077
14643
  var MIN_VISIBLE_ROWS = 8;
@@ -14212,8 +14778,39 @@ function permissionModeStatusText(mode) {
14212
14778
  }
14213
14779
  function TerminalConsole(props) {
14214
14780
  const { conductor, theme, slash, verbose, onExit, initialInput, services } = props;
14215
- const adapter = theme.adapter;
14216
- 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;
14217
14814
  const chord = useRef3(NO_CHORD);
14218
14815
  const chordTimer = useRef3(null);
14219
14816
  const paste = useRef3(EMPTY_PASTE_VAULT);
@@ -14224,34 +14821,39 @@ function TerminalConsole(props) {
14224
14821
  const liveReason = useRef3(null);
14225
14822
  const rowSeed = useRef3(0);
14226
14823
  const ctrlCAt = useRef3(0);
14227
- const [highlight, setHighlight] = useState8(0);
14228
- const [toolExecutions, setToolExecutions] = useState8({});
14229
- const [taskTokens, setTaskTokens] = useState8({});
14230
- const [subagentActivity, setSubagentActivity] = useState8({});
14231
- const [expandTools, setExpandTools] = useState8(false);
14232
- const [agentsOpen, setAgentsOpen] = useState8(false);
14233
- const [terminalRows, setTerminalRows] = useState8(() => process.stdout.rows ?? FALLBACK_TERMINAL_ROWS);
14234
- 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);
14235
14837
  const pulse = useCallback(() => forceTick((n) => n + 1), []);
14236
14838
  const dirReader = useMemo2(() => createDirReader(process.cwd()), []);
14237
14839
  const [approvalQueue, dispatchApproval] = useReducer(approvalQueueReducer, EMPTY_APPROVAL_QUEUE);
14238
14840
  const approvalSeed = useRef3(0);
14239
14841
  const approvalResolvers = useRef3(/* @__PURE__ */ new Map());
14240
14842
  const settleApproval = useCallback((id, choice) => {
14241
- const resolve9 = approvalResolvers.current.get(id);
14242
- if (resolve9 === void 0) return;
14843
+ const resolve11 = approvalResolvers.current.get(id);
14844
+ if (resolve11 === void 0) return;
14243
14845
  approvalResolvers.current.delete(id);
14244
14846
  dispatchApproval({ type: "settle", id });
14245
- resolve9(choice);
14847
+ resolve11(choice);
14246
14848
  }, []);
14247
14849
  const denyAllApprovals = useCallback(() => {
14248
14850
  const pending = [...approvalResolvers.current.entries()];
14249
14851
  approvalResolvers.current.clear();
14250
14852
  dispatchApproval({ type: "clear" });
14251
- for (const [, resolve9] of pending) resolve9("deny");
14853
+ for (const [, resolve11] of pending) resolve11("deny");
14252
14854
  }, []);
14253
14855
  const requestApproval = useCallback(
14254
- (toolName, input, opts) => new Promise((resolve9) => {
14856
+ (toolName, input, opts) => new Promise((resolve11) => {
14255
14857
  approvalSeed.current += 1;
14256
14858
  const id = `ap_${approvalSeed.current}`;
14257
14859
  const request = {
@@ -14259,21 +14861,26 @@ function TerminalConsole(props) {
14259
14861
  input,
14260
14862
  suggestions: allowAlwaysRuleStrings(toolName, input)
14261
14863
  };
14262
- approvalResolvers.current.set(id, resolve9);
14263
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);
14264
14871
  if (signal !== void 0) {
14265
14872
  if (signal.aborted) {
14266
14873
  approvalResolvers.current.delete(id);
14267
- resolve9("deny");
14874
+ resolve11("deny");
14268
14875
  return;
14269
14876
  }
14270
- signal.addEventListener("abort", () => settleApproval(id, "deny"), { once: true });
14877
+ signal.addEventListener("abort", onAbort, { once: true });
14271
14878
  }
14272
- dispatchApproval({ type: "enqueue", entry: { id, request, resolve: resolve9 } });
14879
+ dispatchApproval({ type: "enqueue", entry: { id, request, resolve: finish } });
14273
14880
  }),
14274
14881
  [settleApproval]
14275
14882
  );
14276
- useEffect7(() => {
14883
+ useEffect9(() => {
14277
14884
  conductor.setApprovalResolver(requestApproval);
14278
14885
  return () => {
14279
14886
  conductor.setApprovalResolver(void 0);
@@ -14283,7 +14890,7 @@ function TerminalConsole(props) {
14283
14890
  const head = activeApproval(approvalQueue);
14284
14891
  const headId = head?.id;
14285
14892
  const approvalModalOpen = useRef3(false);
14286
- useEffect7(() => {
14893
+ useEffect9(() => {
14287
14894
  if (head === void 0) {
14288
14895
  if (approvalModalOpen.current) {
14289
14896
  approvalModalOpen.current = false;
@@ -14314,11 +14921,53 @@ function TerminalConsole(props) {
14314
14921
  const answerText = useRef3("");
14315
14922
  const reasonText = useRef3("");
14316
14923
  const visibleFrom = useRef3(0);
14317
- const [compactNote, setCompactNote] = useState8(null);
14318
- 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(() => {
14319
14955
  const off = conductor.subscribe((signal) => {
14320
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
+ }
14321
14965
  case "prompt": {
14966
+ liveAnswer.current = null;
14967
+ liveReason.current = null;
14968
+ answerText.current = "";
14969
+ reasonText.current = "";
14970
+ dispatch2({ type: "busy:set", busy: true });
14322
14971
  break;
14323
14972
  }
14324
14973
  case "text": {
@@ -14326,10 +14975,8 @@ function TerminalConsole(props) {
14326
14975
  const id = freshRowId(rowSeed.current++);
14327
14976
  liveAnswer.current = id;
14328
14977
  answerText.current = signal.delta;
14329
- dispatch2({ type: "rows:append", row: { id, kind: "answer", text: answerText.current } });
14330
14978
  } else {
14331
14979
  answerText.current += signal.delta;
14332
- dispatch2({ type: "rows:patch", id: liveAnswer.current, text: answerText.current });
14333
14980
  }
14334
14981
  break;
14335
14982
  }
@@ -14338,19 +14985,14 @@ function TerminalConsole(props) {
14338
14985
  const id = freshRowId(rowSeed.current++);
14339
14986
  liveReason.current = id;
14340
14987
  reasonText.current = signal.delta;
14341
- dispatch2({ type: "rows:append", row: { id, kind: "reason", text: reasonText.current } });
14342
14988
  } else {
14343
14989
  reasonText.current += signal.delta;
14344
- dispatch2({ type: "rows:patch", id: liveReason.current, text: reasonText.current });
14345
14990
  }
14346
14991
  break;
14347
14992
  }
14348
14993
  case "tool_start": {
14349
14994
  liveAnswer.current = null;
14350
14995
  liveReason.current = null;
14351
- const id = `tool-${signal.id}`;
14352
- const row = { id, kind: "toolRun", text: signal.name, runId: signal.id };
14353
- dispatch2({ type: "rows:append", row });
14354
14996
  setToolExecutions((current) => ({
14355
14997
  ...current,
14356
14998
  [signal.id]: {
@@ -14395,11 +15037,6 @@ function TerminalConsole(props) {
14395
15037
  break;
14396
15038
  }
14397
15039
  case "tool_end": {
14398
- dispatch2({
14399
- type: "rows:patch",
14400
- id: `tool-${signal.id}`,
14401
- text: signal.ok ? "done" : "failed"
14402
- });
14403
15040
  setToolExecutions((current) => {
14404
15041
  const prior = current[signal.id];
14405
15042
  if (prior === void 0) return current;
@@ -14424,7 +15061,7 @@ function TerminalConsole(props) {
14424
15061
  if (signal.manual) {
14425
15062
  const carried = [];
14426
15063
  for (const message of conductor.messages()) {
14427
- const content = message.content;
15064
+ const content = "content" in message ? message.content : void 0;
14428
15065
  if (typeof content === "string" && content.startsWith(RESTORED_FILE_PREFIX)) {
14429
15066
  const firstLine = content.slice(RESTORED_FILE_PREFIX.length).split("\n", 1)[0];
14430
15067
  if (firstLine) carried.push(firstLine.trim());
@@ -14436,7 +15073,7 @@ function TerminalConsole(props) {
14436
15073
  liveReason.current = null;
14437
15074
  answerText.current = "";
14438
15075
  reasonText.current = "";
14439
- dispatch2({ type: "rows:set", rows: [] });
15076
+ dispatch2({ type: "blocks:clear" });
14440
15077
  }
14441
15078
  dispatch2({
14442
15079
  type: "status:set",
@@ -14463,6 +15100,8 @@ function TerminalConsole(props) {
14463
15100
  break;
14464
15101
  }
14465
15102
  case "idle": {
15103
+ liveAnswer.current = null;
15104
+ liveReason.current = null;
14466
15105
  dispatch2({ type: "busy:set", busy: false });
14467
15106
  break;
14468
15107
  }
@@ -14472,19 +15111,17 @@ function TerminalConsole(props) {
14472
15111
  pulse();
14473
15112
  });
14474
15113
  return off;
14475
- }, [conductor, pulse]);
15114
+ }, [conductor, pulse, resetView]);
14476
15115
  const completion = useMemo2(
14477
15116
  () => completeAt(state.buffer, state.caret, slash, dirReader),
14478
15117
  [state.buffer, state.caret, slash, dirReader]
14479
15118
  );
14480
15119
  const suggestions = completion.suggestions;
14481
- useEffect7(() => {
15120
+ useEffect9(() => {
14482
15121
  if (highlight >= suggestions.length) setHighlight(0);
14483
15122
  }, [suggestions.length, highlight]);
14484
15123
  const runTurn = useCallback(
14485
15124
  async (text) => {
14486
- const id = freshRowId(rowSeed.current++);
14487
- dispatch2({ type: "rows:append", row: { id, kind: "prompt", text } });
14488
15125
  dispatch2({ type: "busy:set", busy: true });
14489
15126
  if (conductor.snapshot().phase === "condensing") {
14490
15127
  dispatch2({
@@ -14494,8 +15131,6 @@ function TerminalConsole(props) {
14494
15131
  }
14495
15132
  setCompactNote(null);
14496
15133
  activePaste.current = null;
14497
- answerText.current = "";
14498
- reasonText.current = "";
14499
15134
  try {
14500
15135
  await conductor.submit(text);
14501
15136
  } catch (err2) {
@@ -14514,15 +15149,29 @@ function TerminalConsole(props) {
14514
15149
  const text = hasMarkers(raw, paste.current) ? expandMarkers(raw, paste.current) : raw;
14515
15150
  const trimmed = text.trim();
14516
15151
  dispatch2({ type: "buffer:set", text: "" });
14517
- paste.current = EMPTY_PASTE_VAULT;
15152
+ paste.current = { bodies: /* @__PURE__ */ new Map(), minted: paste.current.minted };
15153
+ activePaste.current = null;
14518
15154
  if (trimmed.length === 0) return;
14519
- dispatch2({ type: "history:push", text: raw });
15155
+ dispatch2({ type: "history:push", text });
14520
15156
  if (trimmed.startsWith("!")) {
14521
15157
  const excludeFromContext = trimmed.startsWith("!!");
14522
15158
  const command = trimmed.slice(excludeFromContext ? 2 : 1).trim();
14523
15159
  if (command.length === 0) return;
14524
15160
  dispatch2({ type: "status:set", status: { kind: "busy", text: `Running: ${command}` } });
14525
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
+ } });
14526
15175
  dispatch2({
14527
15176
  type: "status:set",
14528
15177
  status: {
@@ -14540,9 +15189,10 @@ function TerminalConsole(props) {
14540
15189
  }
14541
15190
  const resolution = resolveSlash(trimmed, slash);
14542
15191
  if (resolution.kind === "match") {
14543
- const outcome = resolution.command.run({
15192
+ void Promise.resolve().then(() => resolution.command.run({
14544
15193
  args: resolution.args,
14545
15194
  conductor,
15195
+ services,
14546
15196
  dispatch: dispatch2,
14547
15197
  openModal: (kind, payload) => dispatch2({ type: "modal:open", kind, payload }),
14548
15198
  closeModal: () => dispatch2({ type: "modal:close" }),
@@ -14550,10 +15200,9 @@ function TerminalConsole(props) {
14550
15200
  setStatus: (status) => dispatch2({ type: "status:set", status }),
14551
15201
  setBuffer: (value) => dispatch2({ type: "buffer:set", text: value }),
14552
15202
  appendBlock: (block2) => dispatch2({ type: "block:append", block: block2 })
14553
- });
14554
- void Promise.resolve(outcome).then((settled) => {
15203
+ })).then((settled) => {
14555
15204
  if (settled.kind === "prompt") void runTurn(settled.text);
14556
- });
15205
+ }).catch((error) => dispatch2({ type: "status:set", status: { kind: "error", text: error instanceof Error ? error.message : String(error) } }));
14557
15206
  return;
14558
15207
  }
14559
15208
  if (resolution.kind === "miss") {
@@ -14565,7 +15214,7 @@ function TerminalConsole(props) {
14565
15214
  }
14566
15215
  void runTurn(text);
14567
15216
  },
14568
- [slash, conductor, onExit, runTurn]
15217
+ [slash, conductor, onExit, runTurn, services]
14569
15218
  );
14570
15219
  const flushBurst = useCallback(() => {
14571
15220
  if (burstTimer.current !== null) {
@@ -14613,22 +15262,15 @@ function TerminalConsole(props) {
14613
15262
  chordTimer.current = null;
14614
15263
  }
14615
15264
  }, []);
14616
- useEffect7(
15265
+ useEffect9(
14617
15266
  () => () => {
14618
15267
  if (chordTimer.current !== null) clearTimeout(chordTimer.current);
14619
15268
  if (burstTimer.current !== null) clearTimeout(burstTimer.current);
14620
15269
  },
14621
15270
  []
14622
15271
  );
14623
- useEffect7(() => {
14624
- const onResize = () => setTerminalRows(process.stdout.rows ?? FALLBACK_TERMINAL_ROWS);
14625
- process.stdout.on("resize", onResize);
14626
- return () => {
14627
- process.stdout.off("resize", onResize);
14628
- };
14629
- }, []);
14630
15272
  const titleName = conductor.sessionName();
14631
- useEffect7(() => {
15273
+ useEffect9(() => {
14632
15274
  writeTerminalTitle(titleName && titleName.length > 0 ? titleName : "indus console");
14633
15275
  }, [titleName]);
14634
15276
  const requestExit = useCallback(() => {
@@ -14643,11 +15285,12 @@ function TerminalConsole(props) {
14643
15285
  setHighlight(0);
14644
15286
  return true;
14645
15287
  }, [suggestions, highlight, state.buffer, completion.span]);
14646
- useInput4(
15288
+ useInput6(
14647
15289
  (rawInput, key) => {
14648
15290
  const input = rawInput.replace(BRACKETED_PASTE, "");
14649
15291
  if (input.length === 0 && input !== rawInput) return;
14650
- 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 });
14651
15294
  if (intent.verb === "text:type") {
14652
15295
  burst.current += intent.text ?? "";
14653
15296
  armChord(NO_CHORD);
@@ -14784,7 +15427,8 @@ function TerminalConsole(props) {
14784
15427
  return;
14785
15428
  }
14786
15429
  case "flow:cycleModel":
14787
- conductor.cycleModel?.("");
15430
+ conductor.cycleModel?.("", intent.direction ?? 1);
15431
+ pulse();
14788
15432
  return;
14789
15433
  case "model:cycleThinking": {
14790
15434
  const level = conductor.cycleThinkingLevel();
@@ -14827,10 +15471,10 @@ function TerminalConsole(props) {
14827
15471
  return;
14828
15472
  }
14829
15473
  case "input:pasteImage": {
14830
- const path4 = readClipboardImage();
14831
- if (path4 !== null) {
14832
- const sep = state.buffer.length > 0 && !/\s$/.test(state.buffer) ? " " : "";
14833
- 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}` });
14834
15478
  } else {
14835
15479
  dispatch2({
14836
15480
  type: "status:set",
@@ -14840,7 +15484,7 @@ function TerminalConsole(props) {
14840
15484
  return;
14841
15485
  }
14842
15486
  case "input:externalEditor": {
14843
- const edited = openInExternalEditor(state.buffer);
15487
+ const edited = openInExternalEditor(expandMarkers(state.buffer, paste.current), { terminal: { stdin, stdout } });
14844
15488
  if (edited !== null) {
14845
15489
  dispatch2({ type: "buffer:set", text: edited });
14846
15490
  }
@@ -14854,9 +15498,30 @@ function TerminalConsole(props) {
14854
15498
  case "view:expandTools":
14855
15499
  setExpandTools((current) => !current);
14856
15500
  return;
14857
- case "flow:suspend":
14858
- 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
+ }
14859
15523
  return;
15524
+ }
14860
15525
  case "none":
14861
15526
  default:
14862
15527
  return;
@@ -14864,14 +15529,23 @@ function TerminalConsole(props) {
14864
15529
  },
14865
15530
  { isActive: state.modal.kind === "none" && !agentsOpen }
14866
15531
  );
14867
- 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(
14868
15542
  (_rawInput, key) => {
14869
15543
  if (key.tab && key.shift) cyclePermission();
14870
15544
  },
14871
15545
  { isActive: state.modal.kind === "approval" }
14872
15546
  );
14873
15547
  const seeded = useRef3(false);
14874
- useEffect7(() => {
15548
+ useEffect9(() => {
14875
15549
  if (seeded.current) return;
14876
15550
  seeded.current = true;
14877
15551
  const opening = conductor.permissionMode();
@@ -14905,7 +15579,7 @@ function TerminalConsole(props) {
14905
15579
  const taskPanelRows = showTaskPanel ? 2 + Math.min(pendingMessages.length, 8) : 0;
14906
15580
  const workingIndicatorRows = state.busy ? 2 : 0;
14907
15581
  const maxItems = transcriptHeight(
14908
- terminalRows,
15582
+ renderRows,
14909
15583
  modalOpen,
14910
15584
  agentPanelRows(agentRuns) + streamingAnswerRows + taskPanelRows + workingIndicatorRows
14911
15585
  );
@@ -14914,7 +15588,7 @@ function TerminalConsole(props) {
14914
15588
  const startupMap = useMemo2(
14915
15589
  () => gatherStartup({
14916
15590
  cwd: process.cwd(),
14917
- home: homedir7(),
15591
+ home: homedir6(),
14918
15592
  version: VERSION,
14919
15593
  lastSeenVersion
14920
15594
  }),
@@ -14923,8 +15597,8 @@ function TerminalConsole(props) {
14923
15597
  const quietStartup = (services?.settings.get("quietStartup") ?? false) && !verbose;
14924
15598
  const sweep = (services?.settings.get("logoSweep") ?? false) && !(services?.settings.get("reducedMotion") ?? false) && Boolean(process.stdout.isTTY);
14925
15599
  const welcomeSeed = PINNED_WELCOME_SEED;
14926
- const [welcomeName, setWelcomeName] = useState8(void 0);
14927
- useEffect7(() => {
15600
+ const [welcomeName, setWelcomeName] = useState10(void 0);
15601
+ useEffect9(() => {
14928
15602
  if (!services) return;
14929
15603
  let live3 = true;
14930
15604
  const provider = services.settings.get("defaultProvider");
@@ -14936,119 +15610,127 @@ function TerminalConsole(props) {
14936
15610
  live3 = false;
14937
15611
  };
14938
15612
  }, [services]);
14939
- 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);
14940
15615
  const boundModelId = conductor.snapshot().modelId;
14941
15616
  const boundProvider = boundModelId.includes("/") ? boundModelId.split("/")[0] : boundModelId;
14942
15617
  const modelAuthed = authedProviders === null || authedProviders.has(boundProvider);
14943
15618
  const bannerModelId = modelAuthed ? boundModelId : "no authenticated model \u2014 run /login";
14944
15619
  const statusBarSnapshot = modelAuthed || !snapshot.model ? snapshot : { ...snapshot, model: { ...snapshot.model, provider: "no model", id: "run /login" } };
14945
- return jsxs10(Box10, {
15620
+ return jsx14(Box12, {
14946
15621
  flexDirection: "column",
14947
- children: [
14948
- agentsOpen ? jsx13(AgentsView, {
14949
- theme: adapter,
14950
- runs: agentRuns,
14951
- activity: subagentActivity,
14952
- tokens: taskTokens,
14953
- onExit: () => setAgentsOpen(false)
14954
- }) : jsxs10(Fragment4, {
14955
- children: [
14956
- jsx13(Banner, {
14957
- theme: adapter,
14958
- modelId: bannerModelId,
14959
- workspace: process.cwd(),
14960
- version: VERSION,
14961
- verbose,
14962
- quiet: quietStartup,
14963
- compact: false,
14964
- started: liveMessages.length > 0,
14965
- branch,
14966
- name: welcomeName,
14967
- sweep,
14968
- startup: startupMap,
14969
- variantSeed: welcomeSeed
14970
- }),
14971
- compactNote !== null ? jsxs10(Box10, {
14972
- flexDirection: "column",
14973
- borderStyle: "round",
14974
- borderColor: adapter.colors["borderMuted"],
14975
- paddingX: 1,
14976
- marginBottom: 1,
14977
- children: [
14978
- jsx13(Text10, {
14979
- children: adapter.color("accent", "\u25C6 Context condensed \u2014 fresh session, history summarized")
14980
- }),
14981
- compactNote.length > 0 ? jsxs10(Fragment4, {
14982
- children: [
14983
- jsx13(Text10, { children: adapter.muted("Files carried into the new context:") }),
14984
- compactNote.map(
14985
- (file, index) => jsx13(Text10, { children: adapter.muted(` \xB7 ${file}`) }, index)
14986
- )
14987
- ]
14988
- }) : jsx13(Text10, {
14989
- children: adapter.muted("No files re-attached; the summary digest carries the history.")
14990
- })
14991
- ]
14992
- }) : null,
14993
- jsx13(MessageList, {
14994
- messages: liveMessages,
14995
- theme: adapter,
14996
- showThinking: state.showReasoning,
14997
- showImages: state.showImages,
14998
- expandToolOutputs: expandTools,
14999
- maxItems,
15000
- displayBlocks: [...state.blocks]
15001
- }),
15002
- liveAnswer.current !== null && answerText.current ? jsx13(Box10, {
15003
- flexDirection: "column",
15004
- marginBottom: 1,
15005
- children: jsx13(StreamingMarkdown, { theme: adapter, children: answerText.current })
15006
- }) : null,
15007
- showTaskPanel ? jsx13(TaskPanel, {
15008
- theme: adapter,
15009
- snapshot,
15010
- toolExecutions: SHOW_TOOL_ACTIVITY ? toolExecutions : {},
15011
- expandToolOutputs: expandTools,
15012
- pendingMessages
15013
- }) : null,
15014
- jsx13(WorkingIndicator, {
15015
- theme: adapter,
15016
- busy: state.busy || conductor.snapshot().phase === "condensing",
15017
- phase: conductor.snapshot().phase
15018
- }),
15019
- jsx13(Composer, {
15020
- theme: adapter,
15021
- buffer: state.buffer,
15022
- caret: state.caret,
15023
- busy: state.busy,
15024
- suggestions,
15025
- highlight
15026
- }),
15027
- jsx13(StatusBar, {
15028
- theme: adapter,
15029
- snapshot: statusBarSnapshot,
15030
- branch,
15031
- providerCount,
15032
- status: state.status,
15033
- permissionMode: conductor.permissionMode()
15034
- }),
15035
- jsx13(BackgroundAgents, { theme: adapter, runs: agentRuns, tokens: taskTokens })
15036
- ]
15037
- }),
15038
- jsx13(OverlayHost, {
15039
- modal: state.modal,
15040
- 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, {
15041
15639
  theme: adapter,
15042
- dispatch: dispatch2,
15043
- closeModal: () => dispatch2({ type: "modal:close" })
15044
- })
15045
- ]
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
+ ] })
15046
15706
  });
15047
15707
  }
15048
15708
 
15049
15709
  // src/console/mount.ts
15050
15710
  import { createElement as createElement2 } from "indusagi/react-host";
15051
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
+ }
15052
15734
  async function mountConsole(conductor, opts = {}) {
15053
15735
  const theme = resolveTheme(opts.scheme ?? null);
15054
15736
  const slash = opts.slash ?? DEFAULT_SLASH_REGISTRY;
@@ -15066,15 +15748,23 @@ async function mountConsole(conductor, opts = {}) {
15066
15748
  instance.unmount();
15067
15749
  }
15068
15750
  };
15069
- const instance = render2(createElement2(TerminalConsole, props));
15070
- await instance.waitUntilExit();
15071
- 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
+ }
15072
15762
  }
15073
15763
 
15074
15764
  // src/boot/runners/session.ts
15075
- import { existsSync as existsSync9, readFileSync as readFileSync17 } from "node:fs";
15076
- import { join as join22 } from "node:path";
15077
- 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";
15078
15768
  import { createMCPAgentToolFactory as createMCPAgentToolFactory4, loadMCPConfig as loadMCPConfig2, MCPClientPool as MCPClientPool2 } from "indusagi/mcp";
15079
15769
 
15080
15770
  // src/boot/runners/delegate-runner.ts
@@ -15131,7 +15821,7 @@ async function acquireTaskSlot() {
15131
15821
  if (activeTasks < MAX_PARALLEL_TASKS) {
15132
15822
  activeTasks++;
15133
15823
  } else {
15134
- await new Promise((resolve9) => taskQueue.push(resolve9));
15824
+ await new Promise((resolve11) => taskQueue.push(resolve11));
15135
15825
  }
15136
15826
  let released = false;
15137
15827
  return () => {
@@ -15236,7 +15926,7 @@ ${request.context}` : request.objective;
15236
15926
  }
15237
15927
 
15238
15928
  // src/boot/runners/checkpoint.ts
15239
- 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";
15240
15930
  import { dirname as dirname22, normalize as normalize2 } from "node:path";
15241
15931
  var CHECKPOINT_HANDLE_KEY = "checkpoint";
15242
15932
  var ROOT_NODE_ID = "__root__";
@@ -15304,7 +15994,7 @@ var CheckpointStore = class {
15304
15994
  snapshotFor(nodeId) {
15305
15995
  const paths = this.byNode.get(nodeId);
15306
15996
  if (paths === void 0) return [];
15307
- return [...paths].map(([path4, previous]) => ({ path: path4, previous }));
15997
+ return [...paths].map(([path5, previous]) => ({ path: path5, previous }));
15308
15998
  }
15309
15999
  /** Whether a node has ANY recorded file snapshot (the picker's restore gate). */
15310
16000
  hasSnapshot(nodeId) {
@@ -15330,15 +16020,15 @@ var CheckpointStore = class {
15330
16020
  restore(nodeId) {
15331
16021
  const snapshots = this.snapshotFor(nodeId);
15332
16022
  const restored = [];
15333
- for (const { path: path4, previous } of snapshots) {
16023
+ for (const { path: path5, previous } of snapshots) {
15334
16024
  try {
15335
16025
  if (previous === null) {
15336
- rmSync(path4, { force: true });
16026
+ rmSync2(path5, { force: true });
15337
16027
  } else {
15338
- mkdirSync3(dirname22(path4), { recursive: true });
15339
- writeFileSync6(path4, previous, "utf8");
16028
+ mkdirSync3(dirname22(path5), { recursive: true });
16029
+ writeFileSync7(path5, previous, "utf8");
15340
16030
  }
15341
- restored.push(path4);
16031
+ restored.push(path5);
15342
16032
  } catch {
15343
16033
  }
15344
16034
  }
@@ -15385,15 +16075,15 @@ function createReadStateStore() {
15385
16075
  }
15386
16076
 
15387
16077
  // src/integrations/zoho/config.ts
15388
- import { existsSync as existsSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync4 } from "node:fs";
15389
- import { homedir as homedir8 } from "node:os";
15390
- 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";
15391
16081
  var ENV_URL = "ZOHO_MCP_URL";
15392
16082
  var ENV_DISABLE = "ZOHO_DISABLED";
15393
- function readJsonFile(path4) {
16083
+ function readJsonFile(path5) {
15394
16084
  try {
15395
- if (!existsSync5(path4)) return void 0;
15396
- const raw = readFileSync12(path4, "utf8");
16085
+ if (!existsSync4(path5)) return void 0;
16086
+ const raw = readFileSync12(path5, "utf8");
15397
16087
  const parsed = JSON.parse(raw);
15398
16088
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
15399
16089
  return void 0;
@@ -15412,11 +16102,11 @@ function configFromRecord(rec) {
15412
16102
  const auth = rec.auth === "none" || rec.auth === "oauth" || rec.auth === "auto" ? rec.auth : "auto";
15413
16103
  return { url, id, enabled, auth };
15414
16104
  }
15415
- function profileZohoPath(home = homedir8()) {
15416
- return join18(home, ".indusagi", "agent", "zoho.json");
16105
+ function profileZohoPath(home = homedir7()) {
16106
+ return join17(home, ".indusagi", "agent", "zoho.json");
15417
16107
  }
15418
16108
  function projectZohoPath(cwd) {
15419
- return join18(cwd, ".indusagi", "zoho.json");
16109
+ return join17(cwd, ".indusagi", "zoho.json");
15420
16110
  }
15421
16111
  function resolveZohoConfig(opts = {}) {
15422
16112
  const env2 = opts.env ?? process.env;
@@ -15428,7 +16118,7 @@ function resolveZohoConfig(opts = {}) {
15428
16118
  const fromEnv = env2[ENV_URL]?.trim();
15429
16119
  const fromProject = configFromRecord(readJsonFile(projectZohoPath(cwd)));
15430
16120
  const fromProfile = configFromRecord(
15431
- readJsonFile(profileZohoPath(opts.home ?? homedir8()))
16121
+ readJsonFile(profileZohoPath(opts.home ?? homedir7()))
15432
16122
  );
15433
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;
15434
16124
  if (!base) return void 0;
@@ -15440,9 +16130,9 @@ function resolveZohoConfig(opts = {}) {
15440
16130
  }
15441
16131
 
15442
16132
  // src/integrations/zoho/attach.ts
15443
- import { existsSync as existsSync6, readFileSync as readFileSync13 } from "node:fs";
15444
- import { homedir as homedir9 } from "node:os";
15445
- 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";
15446
16136
  import { connectZohoMcp } from "indusagi/zoho";
15447
16137
  import { createMCPAgentToolFactory as createMCPAgentToolFactory2 } from "indusagi/mcp";
15448
16138
  var live = null;
@@ -15451,15 +16141,15 @@ var cancelConnect = false;
15451
16141
  var lastAuthUrl;
15452
16142
  function zohoTokenPath(id = "zoho") {
15453
16143
  const xdg = process.env.XDG_CONFIG_HOME;
15454
- const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir9();
15455
- const dir = xdg && xdg.length > 0 ? join19(xdg, "indusagi", "credentials", "mcp") : join19(home, ".config", "indusagi", "credentials", "mcp");
15456
- 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`);
15457
16147
  }
15458
16148
  function hasUsableZohoTokens(id = "zoho") {
15459
- const path4 = zohoTokenPath(id);
16149
+ const path5 = zohoTokenPath(id);
15460
16150
  try {
15461
- if (!existsSync6(path4)) return false;
15462
- const raw = readFileSync13(path4, "utf8");
16151
+ if (!existsSync5(path5)) return false;
16152
+ const raw = readFileSync13(path5, "utf8");
15463
16153
  const parsed = JSON.parse(raw);
15464
16154
  return typeof parsed?.tokens?.access_token === "string" && parsed.tokens.access_token.length > 0;
15465
16155
  } catch {
@@ -15598,17 +16288,18 @@ async function loadZohoAgentTools(opts) {
15598
16288
  }
15599
16289
 
15600
16290
  // src/integrations/sarvam/config.ts
15601
- import { existsSync as existsSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "node:fs";
15602
- import { homedir as homedir10 } from "node:os";
15603
- 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";
15604
16294
  var ENV_KEY = "SARVAM_API_KEY";
15605
16295
  var ENV_DISABLE2 = "SARVAM_DISABLED";
15606
16296
  var ENV_BASE_URL = "SARVAM_API_BASE_URL";
15607
16297
  var ENV_BASE_PATH = "SARVAM_MCP_BASE_PATH";
15608
- function readJsonFile2(path4) {
16298
+ function readJsonFile2(path5, sensitive = false) {
15609
16299
  try {
15610
- if (!existsSync7(path4)) return void 0;
15611
- 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;
15612
16303
  const parsed = JSON.parse(raw);
15613
16304
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
15614
16305
  return void 0;
@@ -15618,11 +16309,11 @@ function readJsonFile2(path4) {
15618
16309
  return void 0;
15619
16310
  }
15620
16311
  }
15621
- function configFromRecord2(rec) {
16312
+ function configFromRecord2(rec, includeApiKey = true) {
15622
16313
  if (!rec) return void 0;
15623
16314
  const enabled = rec.enabled === false ? false : true;
15624
16315
  const id = typeof rec.id === "string" && rec.id.trim() ? rec.id.trim() : "sarvam";
15625
- const apiKey = typeof rec.apiKey === "string" ? rec.apiKey.trim() : void 0;
16316
+ const apiKey = includeApiKey && typeof rec.apiKey === "string" ? rec.apiKey.trim() : void 0;
15626
16317
  const baseUrl = typeof rec.baseUrl === "string" ? rec.baseUrl.trim() : void 0;
15627
16318
  const basePath = typeof rec.basePath === "string" ? rec.basePath.trim() : void 0;
15628
16319
  const command = typeof rec.command === "string" ? rec.command.trim() : void 0;
@@ -15635,11 +16326,11 @@ function configFromRecord2(rec) {
15635
16326
  ...command ? { command } : {}
15636
16327
  };
15637
16328
  }
15638
- function profileSarvamPath(home = homedir10()) {
15639
- return join20(home, ".indusagi", "agent", "sarvam.json");
16329
+ function profileSarvamPath(home = homedir9()) {
16330
+ return join19(home, ".indusagi", "agent", "sarvam.json");
15640
16331
  }
15641
16332
  function projectSarvamPath(cwd) {
15642
- return join20(cwd, ".indusagi", "sarvam.json");
16333
+ return join19(cwd, ".indusagi", "sarvam.json");
15643
16334
  }
15644
16335
  function resolveSarvamConfig(opts = {}) {
15645
16336
  const env2 = opts.env ?? process.env;
@@ -15649,9 +16340,9 @@ function resolveSarvamConfig(opts = {}) {
15649
16340
  }
15650
16341
  const fromArgKey = opts.apiKey?.trim();
15651
16342
  const fromEnvKey = env2[ENV_KEY]?.trim();
15652
- const fromProject = configFromRecord2(readJsonFile2(projectSarvamPath(cwd)));
16343
+ const fromProject = configFromRecord2(readJsonFile2(projectSarvamPath(cwd)), false);
15653
16344
  const fromProfile = configFromRecord2(
15654
- readJsonFile2(profileSarvamPath(opts.home ?? homedir10()))
16345
+ readJsonFile2(profileSarvamPath(opts.home ?? homedir9()), true)
15655
16346
  );
15656
16347
  const fileBase = fromProject ?? fromProfile;
15657
16348
  const apiKey = fromArgKey || fromEnvKey || fileBase?.apiKey;
@@ -15776,8 +16467,6 @@ async function loadSarvamAgentTools(opts) {
15776
16467
  }
15777
16468
 
15778
16469
  // src/boot/auth-vault.ts
15779
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
15780
- import { dirname as dirname7 } from "node:path";
15781
16470
  import {
15782
16471
  ensureFreshOAuthCredentials,
15783
16472
  getOAuthProvider as getOAuthProvider3
@@ -15801,8 +16490,9 @@ function normaliseRecord(value) {
15801
16490
  return void 0;
15802
16491
  }
15803
16492
  function readAuthFile(filePath) {
16493
+ const text = readPrivateFileSync(filePath);
16494
+ if (text === null) return {};
15804
16495
  try {
15805
- const text = readFileSync15(filePath, "utf8");
15806
16496
  const parsed = JSON.parse(text);
15807
16497
  if (parsed === null || typeof parsed !== "object") return {};
15808
16498
  const file = {};
@@ -15821,9 +16511,8 @@ function readAuthFile(filePath) {
15821
16511
  }
15822
16512
  }
15823
16513
  function writeAuthFile(filePath, data) {
15824
- mkdirSync6(dirname7(filePath), { recursive: true });
15825
- writeFileSync9(filePath, `${JSON.stringify(data, null, 2)}
15826
- `, { mode: 384 });
16514
+ atomicWritePrivateFileSync(filePath, `${JSON.stringify(data, null, 2)}
16515
+ `);
15827
16516
  }
15828
16517
  function toCredentials(record) {
15829
16518
  const { kind: _kind, isDefault: _isDefault, ...credentials } = record;
@@ -15917,8 +16606,8 @@ function createAuthVault(authPath) {
15917
16606
  }
15918
16607
 
15919
16608
  // src/boot/runners/memdir.ts
15920
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "node:fs";
15921
- 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";
15922
16611
  var MEMORY_ENTRYPOINT = "MEMORY.md";
15923
16612
  var MAX_ENTRYPOINT_LINES = 200;
15924
16613
  var MAX_ENTRYPOINT_BYTES = 25e3;
@@ -15944,27 +16633,27 @@ function truncateEntrypointContent(raw) {
15944
16633
  }
15945
16634
  function memoryDirFor(workspace, cwd) {
15946
16635
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
15947
- return join21(workspace.profileDir, "memory", `--${slug}--`);
16636
+ return join20(workspace.profileDir, "memory", `--${slug}--`);
15948
16637
  }
15949
16638
  var DiskMemoryStore = class {
15950
16639
  memDir;
15951
16640
  file;
15952
16641
  constructor(memDir) {
15953
16642
  this.memDir = memDir;
15954
- this.file = join21(memDir, MEMORY_ENTRYPOINT);
16643
+ this.file = join20(memDir, MEMORY_ENTRYPOINT);
15955
16644
  }
15956
16645
  /** The current note, or `""` when no file has been written yet. */
15957
16646
  read() {
15958
16647
  try {
15959
- return existsSync8(this.file) ? readFileSync16(this.file, "utf8") : "";
16648
+ return existsSync7(this.file) ? readFileSync15(this.file, "utf8") : "";
15960
16649
  } catch {
15961
16650
  return "";
15962
16651
  }
15963
16652
  }
15964
16653
  /** Overwrite the note, creating the memory directory as needed (mode 0o600). */
15965
16654
  replace(content) {
15966
- mkdirSync7(this.memDir, { recursive: true });
15967
- writeFileSync10(this.file, content, { mode: 384 });
16655
+ mkdirSync5(this.memDir, { recursive: true });
16656
+ writeFileSync9(this.file, content, { mode: 384 });
15968
16657
  }
15969
16658
  /** Add a single line to the end of the note (composed of read + replace). */
15970
16659
  append(line) {
@@ -15977,9 +16666,9 @@ ${line}`;
15977
16666
  function loadMemoryDoc(memDir) {
15978
16667
  let raw;
15979
16668
  try {
15980
- const file = join21(memDir, MEMORY_ENTRYPOINT);
15981
- if (!existsSync8(file)) return void 0;
15982
- raw = readFileSync16(file, "utf8");
16669
+ const file = join20(memDir, MEMORY_ENTRYPOINT);
16670
+ if (!existsSync7(file)) return void 0;
16671
+ raw = readFileSync15(file, "utf8");
15983
16672
  } catch {
15984
16673
  return void 0;
15985
16674
  }
@@ -15990,11 +16679,104 @@ function loadMemoryDoc(memDir) {
15990
16679
  }
15991
16680
 
15992
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
+ }
15993
16738
  async function buildAddonHost(ctx) {
15994
16739
  const host = createAddonHost({ handles: {} });
15995
- host.onFault(() => {
16740
+ host.onFault((fault) => {
16741
+ process.stderr.write(`Addon ${fault.kind} fault: ${fault.message}
16742
+ `);
15996
16743
  });
15997
- 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 });
15998
16780
  }
15999
16781
  function wrapToolsWithAddons(tools, bundle) {
16000
16782
  const chain = bundle.interceptors;
@@ -16035,12 +16817,107 @@ function concatAddonTools(deck, bundle) {
16035
16817
  return fresh.length === 0 ? deck : [...deck, ...fresh];
16036
16818
  }
16037
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
+
16038
16914
  // src/boot/runners/session.ts
16039
16915
  function authenticatedProviders(ctx) {
16040
16916
  try {
16041
- const path4 = ctx.workspace.authPath;
16042
- if (!existsSync9(path4)) return [];
16043
- 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);
16044
16921
  return Object.keys(data).filter((id) => {
16045
16922
  const accounts = data[id];
16046
16923
  return id.length > 0 && accounts !== null && typeof accounts === "object" && Object.keys(accounts).length > 0;
@@ -16118,9 +16995,9 @@ var PROVIDER_ENV = {
16118
16995
  };
16119
16996
  function primeProviderEnv(ctx) {
16120
16997
  try {
16121
- const path4 = ctx.workspace.authPath;
16122
- if (!existsSync9(path4)) return;
16123
- 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"));
16124
17001
  for (const [provider, accounts] of Object.entries(data)) {
16125
17002
  if (provider.startsWith("_") || accounts === null || typeof accounts !== "object") continue;
16126
17003
  const envVar = PROVIDER_ENV[provider];
@@ -16158,13 +17035,13 @@ function buildKeyResolver(ctx, vault, requestedAccount) {
16158
17035
  }
16159
17036
  };
16160
17037
  }
16161
- var THINKING_LEVELS = /* @__PURE__ */ new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
16162
- function resolveThinkingLevel(raw) {
16163
- return raw !== void 0 && THINKING_LEVELS.has(raw) ? raw : void 0;
17038
+ var THINKING_LEVELS = /* @__PURE__ */ new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
17039
+ function resolveThinkingLevel(raw, modelId) {
17040
+ return raw !== void 0 && THINKING_LEVELS.has(raw) && isReasoningEffortSupported(modelId, raw) ? raw : void 0;
16164
17041
  }
16165
17042
  function resolveText(value) {
16166
17043
  try {
16167
- if (existsSync9(value)) return readFileSync17(value, "utf8");
17044
+ if (existsSync8(value)) return readFileSync17(value, "utf8");
16168
17045
  } catch {
16169
17046
  }
16170
17047
  return value;
@@ -16173,7 +17050,7 @@ var PROFILE_DIR2 = ".indusagi";
16173
17050
  function skillRoots2(cwd) {
16174
17051
  return [
16175
17052
  { dir: join22(cwd, PROFILE_DIR2, "skills"), origin: "project" },
16176
- { dir: join22(homedir11(), PROFILE_DIR2, "skills"), origin: "user" }
17053
+ { dir: join22(homedir10(), PROFILE_DIR2, "skills"), origin: "user" }
16177
17054
  ];
16178
17055
  }
16179
17056
  function gatherModelSkills(cwd) {
@@ -16204,8 +17081,9 @@ function selectTools(cwd, inv, runner, memoryStore, checkpoint, workflowRunner)
16204
17081
  return visible.filter((tool) => allow.has(canon2(tool.name)));
16205
17082
  }
16206
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)) : [];
16207
17085
  const contextDocs = [
16208
- ...gatherContextDocs(cwd, homedir11()),
17086
+ ...gatherContextDocs(cwd, homedir10(), { approvedExternalPaths }),
16209
17087
  ...memoryDoc ? [memoryDoc] : []
16210
17088
  ];
16211
17089
  let base = inv.system !== void 0 ? resolveText(inv.system) : composeBriefing({ tools, contextDocs, skills });
@@ -16297,9 +17175,9 @@ async function loadMcpTools(inv, cwd) {
16297
17175
  console.error = () => {
16298
17176
  };
16299
17177
  try {
16300
- for (const path4 of paths) {
17178
+ for (const path5 of paths) {
16301
17179
  try {
16302
- const servers = loadMCPConfig2(path4);
17180
+ const servers = loadMCPConfig2(path5);
16303
17181
  if (servers.length === 0) continue;
16304
17182
  const pool = new MCPClientPool2({ servers });
16305
17183
  await pool.connectAll();
@@ -16331,7 +17209,8 @@ var condenseTranscript = async (messages, opts) => {
16331
17209
  const deps = model ? {
16332
17210
  model,
16333
17211
  ...opts?.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
16334
- ...opts?.signal !== void 0 ? { signal: opts.signal } : {}
17212
+ ...opts?.signal !== void 0 ? { signal: opts.signal } : {},
17213
+ ...opts?.instructions !== void 0 ? { instructions: opts.instructions } : {}
16335
17214
  } : {};
16336
17215
  const cleared = clearStaleToolResults(messages);
16337
17216
  if (opts?.force) {
@@ -16371,7 +17250,7 @@ function resolveDefaultMode(store) {
16371
17250
  const mode = tier.permissions?.defaultMode;
16372
17251
  if (mode !== void 0 && isPermissionMode(mode)) return mode;
16373
17252
  }
16374
- return "default";
17253
+ return "acceptEdits";
16375
17254
  }
16376
17255
  function readOnlyNamesFromDeck(tools) {
16377
17256
  const names = /* @__PURE__ */ new Set();
@@ -16458,10 +17337,13 @@ async function buildSessionConductor(ctx) {
16458
17337
  basePath: typeof inv.sarvamBasePath === "string" ? inv.sarvamBasePath : typeof inv.flags?.["sarvam-base-path"] === "string" ? inv.flags["sarvam-base-path"] : void 0
16459
17338
  })
16460
17339
  ];
16461
- const tools = wrapToolsWithAddons(concatAddonTools(deck, addons), addons);
17340
+ const tools = wrapToolsForTerminalBench(
17341
+ wrapToolsWithAddons(concatAddonTools(deck, addons), addons),
17342
+ cwd
17343
+ );
16462
17344
  const skills = gatherModelSkills(cwd);
16463
17345
  const system = composeSystem(tools, inv, cwd, skills, memoryDoc);
16464
- const thinking = resolveThinkingLevel(inv.thinking);
17346
+ const thinking = resolveThinkingLevel(inv.thinking, modelId);
16465
17347
  const fallbackModelId = inv.fallbackModelId;
16466
17348
  const planReachable = inv.mode === "repl";
16467
17349
  const canUseTool = buildPermissionGate(permissionRules, permissionMode, tools, planReachable);
@@ -16807,7 +17689,7 @@ function isProgramEntry() {
16807
17689
  const entryArg = processArgv[1];
16808
17690
  if (!entryArg) return false;
16809
17691
  try {
16810
- return realpathSync3(fileURLToPath2(import.meta.url)) === realpathSync3(entryArg);
17692
+ return realpathSync4(fileURLToPath2(import.meta.url)) === realpathSync4(entryArg);
16811
17693
  } catch {
16812
17694
  return false;
16813
17695
  }