indusagi-coding-agent 0.2.3 → 0.2.4

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 (31) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/entry.js +1733 -572
  3. package/dist/guardrails.js +21 -6
  4. package/dist/index.js +1687 -552
  5. package/dist/types/boot/heap.d.ts +31 -0
  6. package/dist/types/boot/runners/delegate-runner.d.ts +21 -1
  7. package/dist/types/boot/runners/server-mode.d.ts +71 -0
  8. package/dist/types/boot/runners/session.d.ts +24 -1
  9. package/dist/types/boot/runners/session.test.d.ts +6 -1
  10. package/dist/types/boot/server-token.d.ts +97 -0
  11. package/dist/types/capability-deck/contract.d.ts +7 -6
  12. package/dist/types/conductor/conductor.d.ts +24 -0
  13. package/dist/types/conductor/contract.d.ts +71 -7
  14. package/dist/types/conductor/index.d.ts +3 -3
  15. package/dist/types/conductor/permissions.d.ts +74 -4
  16. package/dist/types/conductor/post-edit-diagnostics.test.d.ts +8 -3
  17. package/dist/types/conductor/quota-error.d.ts +35 -0
  18. package/dist/types/console/auth-status.d.ts +28 -0
  19. package/dist/types/console/components/TerminalConsole.d.ts +0 -5
  20. package/dist/types/console/contract.d.ts +13 -0
  21. package/dist/types/console/input/index.d.ts +1 -1
  22. package/dist/types/console/input/keymap.d.ts +1 -1
  23. package/dist/types/console/input/paste.d.ts +53 -1
  24. package/dist/types/console/overlays/approval-queue.d.ts +18 -1
  25. package/dist/types/console/overlays/index.d.ts +1 -1
  26. package/dist/types/launch/login.d.ts +68 -0
  27. package/dist/types/launch/oauth.test.d.ts +20 -0
  28. package/dist/types/window-budget/summarize/condense.d.ts +6 -0
  29. package/dist/types/workflow-engine/agent-runner.d.ts +20 -1
  30. package/dist/types/workspace/brand.d.ts +1 -1
  31. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -31,6 +31,70 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  mod
32
32
  ));
33
33
 
34
+ // src/boot/server-token.ts
35
+ import { mkdir, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
36
+ import os from "node:os";
37
+ import path from "node:path";
38
+ async function getServerToken() {
39
+ try {
40
+ const data = await readFile2(TOKEN_FILE, "utf8");
41
+ return JSON.parse(data);
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ async function storeServerToken(token) {
47
+ try {
48
+ await mkdir(CONFIG_DIR, { recursive: true });
49
+ const tokenData = {
50
+ access_token: token.access_token,
51
+ refresh_token: token.refresh_token,
52
+ token_type: token.token_type ?? "Bearer",
53
+ scope: token.scope,
54
+ expires_at: token.expires_in !== void 0 ? new Date(Date.now() + token.expires_in * 1e3).toISOString() : null,
55
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
56
+ };
57
+ await writeFile(TOKEN_FILE, `${JSON.stringify(tokenData, null, 2)}
58
+ `, {
59
+ encoding: "utf8",
60
+ mode: 384
61
+ });
62
+ return true;
63
+ } catch (error) {
64
+ console.error("Failed to store server token:", error.message);
65
+ return false;
66
+ }
67
+ }
68
+ async function clearServerToken() {
69
+ try {
70
+ await unlink(TOKEN_FILE);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ function isServerTokenExpired(token) {
77
+ if (!token || !token.expires_at) {
78
+ return true;
79
+ }
80
+ const expiresAt = new Date(token.expires_at);
81
+ const now = /* @__PURE__ */ new Date();
82
+ return expiresAt.getTime() - now.getTime() < 5 * 60 * 1e3;
83
+ }
84
+ async function hasValidServerToken() {
85
+ const token = await getServerToken();
86
+ return token !== null && !isServerTokenExpired(token);
87
+ }
88
+ var INDUS_SERVER_URL, CONFIG_DIR, TOKEN_FILE;
89
+ var init_server_token = __esm({
90
+ "src/boot/server-token.ts"() {
91
+ "use strict";
92
+ INDUS_SERVER_URL = process.env.INDUS_SERVER_URL?.replace(/\/+$/, "") ?? "https://indus-server.vercel.app";
93
+ CONFIG_DIR = path.join(os.homedir(), ".indusagi", "agent");
94
+ TOKEN_FILE = path.join(CONFIG_DIR, "server-token.json");
95
+ }
96
+ });
97
+
34
98
  // src/briefing/contract.ts
35
99
  function briefingFault(kind, message, cause) {
36
100
  return cause === void 0 ? { kind, message } : { kind, message, cause };
@@ -96,29 +160,29 @@ function bullets(lines) {
96
160
  return lines.map((l) => `- ${l}`).join("\n");
97
161
  }
98
162
  function escapeXml(value) {
99
- let out = "";
163
+ let out2 = "";
100
164
  for (const ch of value) {
101
165
  switch (ch) {
102
166
  case "&":
103
- out += "&amp;";
167
+ out2 += "&amp;";
104
168
  break;
105
169
  case "<":
106
- out += "&lt;";
170
+ out2 += "&lt;";
107
171
  break;
108
172
  case ">":
109
- out += "&gt;";
173
+ out2 += "&gt;";
110
174
  break;
111
175
  case '"':
112
- out += "&quot;";
176
+ out2 += "&quot;";
113
177
  break;
114
178
  case "'":
115
- out += "&#39;";
179
+ out2 += "&#39;";
116
180
  break;
117
181
  default:
118
- out += ch;
182
+ out2 += ch;
119
183
  }
120
184
  }
121
- return out;
185
+ return out2;
122
186
  }
123
187
  function hasTool(ctx, id) {
124
188
  return (ctx.tools ?? []).some((t) => t.name === id);
@@ -553,7 +617,7 @@ function buildMacroScope(raw) {
553
617
  return { args, all: args.join(" "), raw };
554
618
  }
555
619
  function splitArguments(raw) {
556
- const out = [];
620
+ const out2 = [];
557
621
  let current = "";
558
622
  let inWord = false;
559
623
  let quote = null;
@@ -575,7 +639,7 @@ function splitArguments(raw) {
575
639
  }
576
640
  if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v") {
577
641
  if (inWord) {
578
- out.push(current);
642
+ out2.push(current);
579
643
  current = "";
580
644
  inWord = false;
581
645
  }
@@ -584,33 +648,33 @@ function splitArguments(raw) {
584
648
  current += ch;
585
649
  inWord = true;
586
650
  }
587
- if (inWord) out.push(current);
588
- return out;
651
+ if (inWord) out2.push(current);
652
+ return out2;
589
653
  }
590
654
  function resolveTokens(tokens, scope) {
591
- let out = "";
655
+ let out2 = "";
592
656
  for (const token of tokens) {
593
657
  switch (token.kind) {
594
658
  case "literal":
595
- out += token.text;
659
+ out2 += token.text;
596
660
  break;
597
661
  case "positional": {
598
662
  const value = scope.args[token.index - 1];
599
- if (value !== void 0) out += value;
663
+ if (value !== void 0) out2 += value;
600
664
  break;
601
665
  }
602
666
  case "all":
603
- out += scope.all;
667
+ out2 += scope.all;
604
668
  break;
605
669
  case "slice": {
606
670
  const from = Math.max(0, token.start - 1);
607
671
  const slice = token.length === void 0 ? scope.args.slice(from) : scope.args.slice(from, from + Math.max(0, token.length));
608
- out += slice.join(" ");
672
+ out2 += slice.join(" ");
609
673
  break;
610
674
  }
611
675
  }
612
676
  }
613
- return out;
677
+ return out2;
614
678
  }
615
679
  function applyMacros(body, raw) {
616
680
  return resolveTokens(scanMacroBody(body), buildMacroScope(raw));
@@ -660,18 +724,18 @@ function loadMacros(dir, opts = {}) {
660
724
  }
661
725
  return macros;
662
726
  }
663
- function readMacroFile(path3, origin, label2) {
727
+ function readMacroFile(path4, origin, label2) {
664
728
  let text;
665
729
  try {
666
- text = readFileSync(path3, "utf8");
730
+ text = readFileSync(path4, "utf8");
667
731
  } catch (cause) {
668
- throw briefingFault("macro_invalid", `could not read macro file at ${path3}`, cause);
732
+ throw briefingFault("macro_invalid", `could not read macro file at ${path4}`, cause);
669
733
  }
670
734
  const { frontmatter, body } = splitFrontmatter(text);
671
- const name = basename2(path3).replace(/\.md$/i, "");
735
+ const name = basename2(path4).replace(/\.md$/i, "");
672
736
  const declared = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
673
737
  const description = declared.length > 0 ? `${declared} (${label2})` : deriveDescription(body, label2);
674
- return { name, description, body, origin, source: path3 };
738
+ return { name, description, body, origin, source: path4 };
675
739
  }
676
740
  function deriveDescription(body, label2) {
677
741
  const firstLine = body.split("\n").find((l) => l.trim().length > 0)?.trim() ?? "";
@@ -810,41 +874,41 @@ function checkFrontmatterKeys(frontmatter) {
810
874
  return null;
811
875
  }
812
876
  function projectFrontmatter(raw) {
813
- const out = {};
814
- if (typeof raw.name === "string") out.name = raw.name.trim();
815
- if (typeof raw.description === "string") out.description = raw.description.trim();
816
- if (typeof raw.license === "string") out.license = raw.license.trim();
817
- if (typeof raw.compatibility === "string") out.compatibility = raw.compatibility.trim();
877
+ const out2 = {};
878
+ if (typeof raw.name === "string") out2.name = raw.name.trim();
879
+ if (typeof raw.description === "string") out2.description = raw.description.trim();
880
+ if (typeof raw.license === "string") out2.license = raw.license.trim();
881
+ if (typeof raw.compatibility === "string") out2.compatibility = raw.compatibility.trim();
818
882
  if (typeof raw.metadata === "object" && raw.metadata !== null) {
819
- out.metadata = raw.metadata;
883
+ out2.metadata = raw.metadata;
820
884
  }
821
885
  const tools = raw["allowed-tools"];
822
886
  if (typeof tools === "string") {
823
887
  const list = tools.split(/[\s,]+/).map((t) => t.trim()).filter((t) => t.length > 0);
824
- if (list.length > 0) out.allowedTools = list;
888
+ if (list.length > 0) out2.allowedTools = list;
825
889
  } else if (Array.isArray(tools)) {
826
- out.allowedTools = tools.map((t) => String(t));
890
+ out2.allowedTools = tools.map((t) => String(t));
827
891
  }
828
892
  const flag = raw["disable-model-invocation"];
829
- if (flag === true || flag === "true") out.disableModelInvocation = true;
830
- return out;
893
+ if (flag === true || flag === "true") out2.disableModelInvocation = true;
894
+ return out2;
831
895
  }
832
896
  function diag(kind, location, detail) {
833
897
  return { kind, location, detail };
834
898
  }
835
899
  function parseCandidate(candidate, origin) {
836
- const { path: path3, dirName } = candidate;
900
+ const { path: path4, dirName } = candidate;
837
901
  let text;
838
902
  try {
839
- text = readFileSync2(path3, "utf8");
903
+ text = readFileSync2(path4, "utf8");
840
904
  } catch (cause) {
841
905
  const detail = `could not read the skill file (${describeCause(cause)})`;
842
- return { card: null, diagnostic: diag("invalid", path3, detail) };
906
+ return { card: null, diagnostic: diag("invalid", path4, detail) };
843
907
  }
844
908
  const { frontmatter, body } = splitFrontmatter(text);
845
909
  const keyProblem = checkFrontmatterKeys(frontmatter);
846
910
  if (keyProblem !== null) {
847
- return { card: null, diagnostic: diag("invalid", path3, keyProblem) };
911
+ return { card: null, diagnostic: diag("invalid", path4, keyProblem) };
848
912
  }
849
913
  const fm = projectFrontmatter(frontmatter);
850
914
  const declaredName = fm.name ?? "";
@@ -852,22 +916,22 @@ function parseCandidate(candidate, origin) {
852
916
  const name = fromDir ? dirName : declaredName;
853
917
  const nameProblem = checkName(name, dirName, fromDir);
854
918
  if (nameProblem !== null) {
855
- return { card: null, diagnostic: diag("invalid", path3, nameProblem) };
919
+ return { card: null, diagnostic: diag("invalid", path4, nameProblem) };
856
920
  }
857
921
  const description = fm.description ?? "";
858
922
  const descProblem = checkDescription(description);
859
923
  if (descProblem !== null) {
860
- return { card: null, diagnostic: diag("invalid", path3, descProblem) };
924
+ return { card: null, diagnostic: diag("invalid", path4, descProblem) };
861
925
  }
862
926
  const card = {
863
927
  name,
864
928
  description,
865
929
  body,
866
- location: path3,
930
+ location: path4,
867
931
  origin,
868
932
  frontmatter: fm
869
933
  };
870
- return { card, diagnostic: diag("loaded", path3, `loaded skill "${name}"`) };
934
+ return { card, diagnostic: diag("loaded", path4, `loaded skill "${name}"`) };
871
935
  }
872
936
  function describeCause(cause) {
873
937
  if (cause instanceof Error) return cause.message;
@@ -940,15 +1004,15 @@ var init_skills = __esm({
940
1004
  // src/briefing/context-docs.ts
941
1005
  import { existsSync, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
942
1006
  import { dirname as dirname4, extname as extname2, isAbsolute as isAbsolute2, join as join6, parse, resolve as resolve2 } from "node:path";
943
- function shortenPath(path3, cwd, home) {
944
- if (cwd.length > 0 && path3.startsWith(cwd)) {
945
- const rest = path3.slice(cwd.length).replace(/^[/\\]+/, "");
1007
+ function shortenPath(path4, cwd, home) {
1008
+ if (cwd.length > 0 && path4.startsWith(cwd)) {
1009
+ const rest = path4.slice(cwd.length).replace(/^[/\\]+/, "");
946
1010
  return rest.length > 0 ? `./${rest}` : ".";
947
1011
  }
948
- if (home.length > 0 && path3.startsWith(home)) {
949
- return `~${path3.slice(home.length)}`;
1012
+ if (home.length > 0 && path4.startsWith(home)) {
1013
+ return `~${path4.slice(home.length)}`;
950
1014
  }
951
- return path3;
1015
+ return path4;
952
1016
  }
953
1017
  function dirChain(cwd) {
954
1018
  const chain = [];
@@ -961,15 +1025,15 @@ function dirChain(cwd) {
961
1025
  }
962
1026
  return chain.reverse();
963
1027
  }
964
- function safeRealpath(path3) {
1028
+ function safeRealpath(path4) {
965
1029
  try {
966
- return realpathSync2(path3);
1030
+ return realpathSync2(path4);
967
1031
  } catch {
968
- return path3;
1032
+ return path4;
969
1033
  }
970
1034
  }
971
- function isTextFile(path3) {
972
- const ext = extname2(path3).toLowerCase();
1035
+ function isTextFile(path4) {
1036
+ const ext = extname2(path4).toLowerCase();
973
1037
  return ext.length === 0 || TEXT_FILE_EXTENSIONS.has(ext);
974
1038
  }
975
1039
  function readFileBody(absPath) {
@@ -984,7 +1048,7 @@ function readFileBody(absPath) {
984
1048
  return trimmed.length > 0 ? trimmed : null;
985
1049
  }
986
1050
  function extractImports(body, baseDir, home) {
987
- const out = [];
1051
+ const out2 = [];
988
1052
  IMPORT_REGEX.lastIndex = 0;
989
1053
  let match;
990
1054
  while ((match = IMPORT_REGEX.exec(body)) !== null) {
@@ -1003,26 +1067,26 @@ function extractImports(body, baseDir, home) {
1003
1067
  abs = resolve2(baseDir, spec);
1004
1068
  }
1005
1069
  if (!isTextFile(abs)) continue;
1006
- out.push(abs);
1070
+ out2.push(abs);
1007
1071
  }
1008
- return out;
1072
+ return out2;
1009
1073
  }
1010
- function loadDocTree(absPath, cwd, home, seen, depth, maxDepth, maxBytes, out) {
1074
+ function loadDocTree(absPath, cwd, home, seen, depth, maxDepth, maxBytes, out2) {
1011
1075
  const real = safeRealpath(absPath);
1012
1076
  if (seen.has(real)) return;
1013
1077
  seen.add(real);
1014
1078
  const body = readFileBody(absPath);
1015
1079
  if (body === null) return;
1016
1080
  const clipped = body.length > maxBytes ? body.slice(0, maxBytes) : body;
1017
- out.push({ path: shortenPath(absPath, cwd, home), body: clipped });
1081
+ out2.push({ path: shortenPath(absPath, cwd, home), body: clipped });
1018
1082
  if (depth >= maxDepth) return;
1019
1083
  const baseDir = dirname4(absPath);
1020
1084
  for (const child of extractImports(body, baseDir, home)) {
1021
- loadDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out);
1085
+ loadDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out2);
1022
1086
  }
1023
1087
  }
1024
1088
  function gatherContextDocs(cwd, home, opts) {
1025
- const out = [];
1089
+ const out2 = [];
1026
1090
  try {
1027
1091
  const maxDepth = opts?.maxImportDepth ?? DEFAULT_MAX_IMPORT_DEPTH;
1028
1092
  const maxBytes = opts?.maxBytesPerDoc ?? DEFAULT_MAX_BYTES_PER_DOC;
@@ -1033,13 +1097,13 @@ function gatherContextDocs(cwd, home, opts) {
1033
1097
  if (dir.length === 0) continue;
1034
1098
  for (const name of CANDIDATE_FILES) {
1035
1099
  const candidate = join6(dir, name);
1036
- loadDocTree(candidate, cwd, home, seen, 0, maxDepth, maxBytes, out);
1100
+ loadDocTree(candidate, cwd, home, seen, 0, maxDepth, maxBytes, out2);
1037
1101
  }
1038
1102
  }
1039
1103
  } catch {
1040
- return out;
1104
+ return out2;
1041
1105
  }
1042
- return out;
1106
+ return out2;
1043
1107
  }
1044
1108
  var DEFAULT_MAX_IMPORT_DEPTH, DEFAULT_MAX_BYTES_PER_DOC, CANDIDATE_FILES, TEXT_FILE_EXTENSIONS, IMPORT_REGEX;
1045
1109
  var init_context_docs = __esm({
@@ -1229,13 +1293,15 @@ var init_transcript = __esm({
1229
1293
  }
1230
1294
  };
1231
1295
  compactCommand = {
1232
- name: "summarize-context",
1296
+ // `compact` is the canonical name — it is what users type (and what the
1297
+ // suggestion list shows); the longer forms stay reachable as aliases.
1298
+ name: "compact",
1233
1299
  summary: "Condense the conversation context",
1234
- aliases: ["condense", "compact"],
1300
+ aliases: ["condense", "summarize-context"],
1235
1301
  takesArgs: true,
1236
1302
  run(ctx) {
1237
1303
  const instructions = ctx.args.trim();
1238
- const busyText = instructions.length > 0 ? `Condensing with guidance: ${instructions}` : "Condensing conversation context...";
1304
+ const busyText = instructions.length > 0 ? `Compacting with guidance: ${instructions} \u2014 Esc to cancel` : "Compacting conversation context... Press Esc to cancel.";
1239
1305
  ctx.setStatus({ kind: "busy", text: busyText });
1240
1306
  void (async () => {
1241
1307
  try {
@@ -1244,14 +1310,29 @@ var init_transcript = __esm({
1244
1310
  `printf '%s' ${JSON.stringify(`Compaction guidance: ${instructions}`)}`
1245
1311
  );
1246
1312
  }
1247
- await ctx.conductor.condense();
1248
- ctx.setStatus(
1249
- info(
1250
- instructions.length > 0 ? "Context condensed (guidance recorded for the agent)." : "Context condensed."
1251
- )
1252
- );
1313
+ switch (await ctx.conductor.condense()) {
1314
+ case "condensed":
1315
+ ctx.setStatus(
1316
+ info(
1317
+ instructions.length > 0 ? "Context condensed (guidance recorded for the agent)." : "Context condensed."
1318
+ )
1319
+ );
1320
+ break;
1321
+ case "nothing":
1322
+ ctx.setStatus(warn("Nothing to compact yet."));
1323
+ break;
1324
+ case "cancelled":
1325
+ ctx.setStatus(warn("Compaction cancelled."));
1326
+ break;
1327
+ case "busy":
1328
+ ctx.setStatus(warn("A turn is running \u2014 press Esc to stop it first, then /compact."));
1329
+ break;
1330
+ case "failed":
1331
+ ctx.setStatus(warn("Compaction failed; the transcript is unchanged."));
1332
+ break;
1333
+ }
1253
1334
  } catch {
1254
- ctx.setStatus(warn("Condense could not complete; the transcript is unchanged."));
1335
+ ctx.setStatus(warn("Compaction failed; the transcript is unchanged."));
1255
1336
  }
1256
1337
  })();
1257
1338
  return HANDLED;
@@ -1291,7 +1372,8 @@ var init_transcript = __esm({
1291
1372
  costCommand = {
1292
1373
  name: "cost",
1293
1374
  summary: "Show session cost and token usage",
1294
- aliases: ["usage"],
1375
+ // `usage` is intentionally NOT an alias here: `/usage` is the server-tier
1376
+ // quota command (see integrations.ts). `/cost` shows this session's local stats.
1295
1377
  run(ctx) {
1296
1378
  const stats = ctx.conductor.stats();
1297
1379
  const t = stats.tokens;
@@ -1482,9 +1564,9 @@ function runDebug(ctx) {
1482
1564
  const jsonl = records.map((r) => JSON.stringify(r)).join("\n") + "\n";
1483
1565
  try {
1484
1566
  const dir = mkdtempSync(join7(tmpdir(), "indus-debug-"));
1485
- const path3 = join7(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
1486
- writeFileSync(path3, jsonl, "utf8");
1487
- ctx.setStatus(info(`Diagnostics written to ${path3}`));
1567
+ const path4 = join7(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
1568
+ writeFileSync(path4, jsonl, "utf8");
1569
+ ctx.setStatus(info(`Diagnostics written to ${path4}`));
1488
1570
  } catch (cause) {
1489
1571
  const detail = cause instanceof Error ? cause.message : String(cause);
1490
1572
  ctx.setStatus(warn(`Could not write the diagnostics log (${detail}).`));
@@ -1553,6 +1635,7 @@ var init_workbench = __esm({
1553
1635
  ["Ctrl+J", "Insert a newline"],
1554
1636
  ["Ctrl+Z", "Suspend the process"],
1555
1637
  ["Alt+Up", "Pull the newest queued input back into the composer"],
1638
+ ["Ctrl+X", "Run the newest queued message NOW (stops the current run)"],
1556
1639
  ["! <cmd>", "Run a shell command (kept in context)"],
1557
1640
  ["!! <cmd>", "Run a shell command (excluded from context)"]
1558
1641
  ];
@@ -1637,6 +1720,60 @@ var init_workbench = __esm({
1637
1720
  }
1638
1721
  });
1639
1722
 
1723
+ // src/boot/runners/server-mode.ts
1724
+ function isGatewayEligibleProvider(provider) {
1725
+ return GATEWAY_ELIGIBLE_PROVIDERS.has(provider);
1726
+ }
1727
+ function serverTierProviders() {
1728
+ return [...GATEWAY_ELIGIBLE_PROVIDERS];
1729
+ }
1730
+ async function hasUsableLocalKey(provider, vault) {
1731
+ try {
1732
+ const accounts = [];
1733
+ const def = await vault.defaultAccount(provider);
1734
+ if (def !== void 0 && def.length > 0) accounts.push(def);
1735
+ for (const account of await vault.listAccounts(provider)) {
1736
+ if (!accounts.includes(account)) accounts.push(account);
1737
+ }
1738
+ for (const account of accounts) {
1739
+ const key = await vault.readUsableKey(provider, account);
1740
+ if (typeof key === "string" && key.length > 0) return true;
1741
+ }
1742
+ } catch {
1743
+ }
1744
+ return false;
1745
+ }
1746
+ async function resolveServerMode(_ctx, provider, vault) {
1747
+ if (!isGatewayEligibleProvider(provider)) return { serverMode: false };
1748
+ if (await hasUsableLocalKey(provider, vault)) return { serverMode: false };
1749
+ if (!await hasValidServerToken()) return { serverMode: false };
1750
+ const token = await getServerToken();
1751
+ const access = token?.access_token;
1752
+ if (typeof access !== "string" || access.length === 0) return { serverMode: false };
1753
+ return { serverMode: true, token: access };
1754
+ }
1755
+ async function resolveServerGatewayUrls(ctx, vault) {
1756
+ const map = {};
1757
+ for (const provider of GATEWAY_ELIGIBLE_PROVIDERS) {
1758
+ const result = await resolveServerMode(ctx, provider, vault);
1759
+ if (result.serverMode) {
1760
+ map[provider] = `${INDUS_SERVER_URL}/gateway/${provider}`;
1761
+ }
1762
+ }
1763
+ return map;
1764
+ }
1765
+ var GATEWAY_ELIGIBLE_PROVIDERS;
1766
+ var init_server_mode = __esm({
1767
+ "src/boot/runners/server-mode.ts"() {
1768
+ "use strict";
1769
+ init_server_token();
1770
+ GATEWAY_ELIGIBLE_PROVIDERS = /* @__PURE__ */ new Set([
1771
+ "minimax",
1772
+ "sarvam"
1773
+ ]);
1774
+ }
1775
+ });
1776
+
1640
1777
  // src/capability-deck/contract.ts
1641
1778
  function capabilityId(raw) {
1642
1779
  return raw;
@@ -9272,14 +9409,14 @@ Poll it with action:"poll", id:"${h.id}".`
9272
9409
  case "poll": {
9273
9410
  const h = params.id ? table.get(params.id) : void 0;
9274
9411
  if (!h) return errResult("poll", `No background process with id "${params.id ?? ""}".`);
9275
- const out = drainBuffer(h.stdout, params.sinceLast ?? false);
9276
- const err = drainBuffer(h.stderr, params.sinceLast ?? false);
9412
+ const out2 = drainBuffer(h.stdout, params.sinceLast ?? false);
9413
+ const err2 = drainBuffer(h.stderr, params.sinceLast ?? false);
9277
9414
  const parts = [`Process ${h.id} \u2014 ${statusLine(h)}`];
9278
- if (out.length) parts.push(`--- stdout ---
9279
- ${out.join("\n")}`);
9280
- if (err.length) parts.push(`--- stderr ---
9281
- ${err.join("\n")}`);
9282
- if (out.length === 0 && err.length === 0) parts.push("(no new output)");
9415
+ if (out2.length) parts.push(`--- stdout ---
9416
+ ${out2.join("\n")}`);
9417
+ if (err2.length) parts.push(`--- stderr ---
9418
+ ${err2.join("\n")}`);
9419
+ if (out2.length === 0 && err2.length === 0) parts.push("(no new output)");
9283
9420
  return {
9284
9421
  content: [{ type: "text", text: parts.join("\n") }],
9285
9422
  details: { action: "poll", ok: true, id: h.id, state: h.state, exitCode: h.exitCode }
@@ -9359,8 +9496,8 @@ var init_bg_process_card = __esm({
9359
9496
  handle.signal = sig;
9360
9497
  handle.state = sig ? "signalled" : "exited";
9361
9498
  });
9362
- child.on("error", (err) => {
9363
- pushLines(handle.stderr, `spawn error: ${String(err)}`);
9499
+ child.on("error", (err2) => {
9500
+ pushLines(handle.stderr, `spawn error: ${String(err2)}`);
9364
9501
  handle.state = "exited";
9365
9502
  });
9366
9503
  this.handles.set(id, handle);
@@ -9700,6 +9837,10 @@ function createStructuredOutputTool({
9700
9837
  name,
9701
9838
  label: "Structured Output",
9702
9839
  description: "Return the final machine-readable result for this subagent task.",
9840
+ // Pure in-memory capture — no filesystem/shell/network mutation — so the
9841
+ // subagent permission gate auto-allows it in every mode (a schema-bound
9842
+ // workflow must be able to land its result even under `default`/`plan`).
9843
+ readOnly: true,
9703
9844
  parameters: schema,
9704
9845
  async execute(_toolCallId, params) {
9705
9846
  capture.value = params;
@@ -9807,13 +9948,13 @@ function assembleTree(header, blocks, logLines, maxLines) {
9807
9948
  if (1 + keptLen + summaryCost + logLines.length <= maxLines) break;
9808
9949
  folded.add(i);
9809
9950
  }
9810
- const out = [header];
9811
- if (folded.size) out.push(` \u2026 ${folded.size} earlier phases done`);
9951
+ const out2 = [header];
9952
+ if (folded.size) out2.push(` \u2026 ${folded.size} earlier phases done`);
9812
9953
  blocks.forEach((b, i) => {
9813
- if (!folded.has(i)) out.push(...baseRow(b));
9954
+ if (!folded.has(i)) out2.push(...baseRow(b));
9814
9955
  });
9815
- out.push(...logLines);
9816
- return out;
9956
+ out2.push(...logLines);
9957
+ return out2;
9817
9958
  }
9818
9959
  function compactPhaseLine(marker, phase, done, agents, now) {
9819
9960
  const prefix = ` ${marker} ${phase} ${done}/${agents.length}`;
@@ -9897,7 +10038,7 @@ var init_display = __esm({
9897
10038
 
9898
10039
  // src/workflow-engine/engine.ts
9899
10040
  import vm2 from "node:vm";
9900
- import os from "node:os";
10041
+ import os2 from "node:os";
9901
10042
  async function runWorkflow(script, options) {
9902
10043
  const started = Date.now();
9903
10044
  const { meta, body } = parseWorkflowScript(script);
@@ -10067,7 +10208,7 @@ ${body}
10067
10208
  };
10068
10209
  }
10069
10210
  function defaultConcurrency() {
10070
- const count = os.cpus()?.length;
10211
+ const count = os2.cpus()?.length;
10071
10212
  return typeof count === "number" && count > 0 ? count : 8;
10072
10213
  }
10073
10214
  function createLimiter(limit) {
@@ -10745,8 +10886,8 @@ function memoryBackend() {
10745
10886
  }
10746
10887
  function fsBackend(dir) {
10747
10888
  const fileOf = async (sessionId) => {
10748
- const path3 = await import("node:path");
10749
- return path3.join(dir, `${sessionId}.ndjson`);
10889
+ const path4 = await import("node:path");
10890
+ return path4.join(dir, `${sessionId}.ndjson`);
10750
10891
  };
10751
10892
  return {
10752
10893
  locate: (sessionId) => `${dir}/${sessionId}.ndjson`,
@@ -11159,6 +11300,73 @@ var init_skill_parse = __esm({
11159
11300
  }
11160
11301
  });
11161
11302
 
11303
+ // src/conductor/quota-error.ts
11304
+ function collectErrorSignals(error) {
11305
+ const statuses = [];
11306
+ const parts = [];
11307
+ const seen = /* @__PURE__ */ new Set();
11308
+ const visit = (value, depth) => {
11309
+ if (value === null || value === void 0 || depth > 6) return;
11310
+ if (typeof value === "string") {
11311
+ parts.push(value);
11312
+ return;
11313
+ }
11314
+ if (typeof value === "number") {
11315
+ statuses.push(value);
11316
+ return;
11317
+ }
11318
+ if (typeof value !== "object") return;
11319
+ if (seen.has(value)) return;
11320
+ seen.add(value);
11321
+ const obj = value;
11322
+ for (const key of ["status", "statusCode", "code"]) {
11323
+ const raw = obj[key];
11324
+ if (typeof raw === "number") statuses.push(raw);
11325
+ else if (typeof raw === "string" && /^\d+$/.test(raw)) statuses.push(Number(raw));
11326
+ }
11327
+ if (typeof obj.message === "string") parts.push(obj.message);
11328
+ visit(obj.cause, depth + 1);
11329
+ visit(obj.error, depth + 1);
11330
+ visit(obj.response, depth + 1);
11331
+ visit(obj.body, depth + 1);
11332
+ visit(obj.data, depth + 1);
11333
+ };
11334
+ visit(error, 0);
11335
+ try {
11336
+ parts.push(JSON.stringify(error));
11337
+ } catch {
11338
+ }
11339
+ if (error instanceof Error && typeof error.stack === "string") parts.push(error.stack);
11340
+ return { statuses, text: parts.join("  ").toLowerCase() };
11341
+ }
11342
+ function isQuotaFault(error) {
11343
+ const { statuses, text } = collectErrorSignals(error);
11344
+ if (!text.includes(QUOTA_MARKER)) return false;
11345
+ return statuses.includes(429) || text.includes(QUOTA_MARKER);
11346
+ }
11347
+ function parseUsedLimit(text) {
11348
+ const used = text.match(/"used"\s*:\s*(\d+)/);
11349
+ const limit = text.match(/"limit"\s*:\s*(\d+)/);
11350
+ if (used && limit) {
11351
+ return { used: Number(used[1]), limit: Number(limit[1]) };
11352
+ }
11353
+ return void 0;
11354
+ }
11355
+ function quotaErrorMessage(error) {
11356
+ if (!isQuotaFault(error)) return void 0;
11357
+ const { text } = collectErrorSignals(error);
11358
+ const numbers = parseUsedLimit(text);
11359
+ const usage = numbers ? ` (${numbers.used}/${numbers.limit} tokens this month)` : "";
11360
+ return `You've hit your indus usage limit${usage}. Add your own API key with \`indus signin <provider>\`, or upgrade your plan.`;
11361
+ }
11362
+ var QUOTA_MARKER;
11363
+ var init_quota_error = __esm({
11364
+ "src/conductor/quota-error.ts"() {
11365
+ "use strict";
11366
+ QUOTA_MARKER = "quota_exceeded";
11367
+ }
11368
+ });
11369
+
11162
11370
  // src/capability-deck/cards/plan-file.ts
11163
11371
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync5 } from "node:fs";
11164
11372
  import { join as join8 } from "node:path";
@@ -11174,14 +11382,14 @@ function writePlan(sessionDir, plan) {
11174
11382
  const dir = join8(sessionDir, PLANS_DIRNAME);
11175
11383
  mkdirSync2(dir, { recursive: true });
11176
11384
  const stamp = Date.now().toString(36);
11177
- const path3 = join8(dir, `${stamp}-${planSlug(plan)}.md`);
11178
- writeFileSync2(path3, plan.endsWith("\n") ? plan : `${plan}
11385
+ const path4 = join8(dir, `${stamp}-${planSlug(plan)}.md`);
11386
+ writeFileSync2(path4, plan.endsWith("\n") ? plan : `${plan}
11179
11387
  `, { encoding: "utf8", mode: 384 });
11180
- return path3;
11388
+ return path4;
11181
11389
  }
11182
- function readPlan(path3) {
11390
+ function readPlan(path4) {
11183
11391
  try {
11184
- return readFileSync5(path3, "utf8");
11392
+ return readFileSync5(path4, "utf8");
11185
11393
  } catch {
11186
11394
  return void 0;
11187
11395
  }
@@ -11432,10 +11640,10 @@ function flattenMessage(message) {
11432
11640
  return `${TURN_MARK} ${tag} (${message.toolName}): ${text}`;
11433
11641
  }
11434
11642
  case "bashExecution": {
11435
- const out = tidy(message.output);
11643
+ const out2 = tidy(message.output);
11436
11644
  const code = message.exitCode ?? "n/a";
11437
11645
  return `${TURN_MARK} shell$ ${tidy(message.command)} [exit ${code}]
11438
- ${out}`;
11646
+ ${out2}`;
11439
11647
  }
11440
11648
  case "custom": {
11441
11649
  const text = tidy(readContent(message.content));
@@ -11559,7 +11767,8 @@ async function summarize(messages, deps = {}) {
11559
11767
  const options = {
11560
11768
  reasoning: "high",
11561
11769
  ...deps.maxTokens !== void 0 ? { maxTokens: deps.maxTokens } : {},
11562
- ...deps.signal ? { signal: deps.signal } : {}
11770
+ ...deps.signal ? { signal: deps.signal } : {},
11771
+ ...deps.apiKey !== void 0 ? { apiKey: deps.apiKey } : {}
11563
11772
  };
11564
11773
  const complete = deps.complete ?? completeSimple;
11565
11774
  const reply = await complete(deps.model, context, options);
@@ -11721,8 +11930,8 @@ function collectVisibleReadPaths(kept, readToolName) {
11721
11930
  if (!isRecord4(block2) || block2.type !== "toolCall") continue;
11722
11931
  if (block2.name !== readToolName) continue;
11723
11932
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
11724
- const path3 = args && typeof args.path === "string" ? args.path : "";
11725
- if (path3.length > 0) paths.add(path3);
11933
+ const path4 = args && typeof args.path === "string" ? args.path : "";
11934
+ if (path4.length > 0) paths.add(path4);
11726
11935
  }
11727
11936
  }
11728
11937
  return paths;
@@ -11738,8 +11947,8 @@ function collectDroppedReads(dropped, readToolName) {
11738
11947
  if (block2.name !== readToolName) continue;
11739
11948
  const id = typeof block2.id === "string" ? block2.id : "";
11740
11949
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
11741
- const path3 = args && typeof args.path === "string" ? args.path : "";
11742
- if (id.length > 0 && path3.length > 0) calls.set(id, { path: path3, order: i });
11950
+ const path4 = args && typeof args.path === "string" ? args.path : "";
11951
+ if (id.length > 0 && path4.length > 0) calls.set(id, { path: path4, order: i });
11743
11952
  }
11744
11953
  }
11745
11954
  if (calls.size === 0) return [];
@@ -11834,7 +12043,7 @@ var init_window_budget = __esm({
11834
12043
  });
11835
12044
 
11836
12045
  // src/conductor/diagnostics.ts
11837
- import * as path from "node:path";
12046
+ import * as path2 from "node:path";
11838
12047
  import { existsSync as existsSync2 } from "node:fs";
11839
12048
  function createDiagnosticKey(d) {
11840
12049
  const { start, end } = d.range;
@@ -11951,7 +12160,7 @@ async function runTscDiagnostics(cwd, signal) {
11951
12160
  const spawn3 = await loadSpawn();
11952
12161
  if (spawn3 === void 0) return [];
11953
12162
  if (signal?.aborted) return [];
11954
- const bin = path.join(cwd, "node_modules", ".bin", "tsc");
12163
+ const bin = path2.join(cwd, "node_modules", ".bin", "tsc");
11955
12164
  const output = await captureOutput(
11956
12165
  spawn3,
11957
12166
  bin,
@@ -11969,7 +12178,7 @@ function parseTscOutput(output, cwd) {
11969
12178
  const m = re.exec(raw);
11970
12179
  if (m === null) continue;
11971
12180
  const [, file, lineStr, colStr, sevWord, code, message] = m;
11972
- const uri = path.isAbsolute(file) ? file : path.resolve(cwd, file);
12181
+ const uri = path2.isAbsolute(file) ? file : path2.resolve(cwd, file);
11973
12182
  const line = Math.max(0, Number(lineStr) - 1);
11974
12183
  const character = Math.max(0, Number(colStr) - 1);
11975
12184
  const diagnostic = {
@@ -11990,7 +12199,7 @@ async function runEslintDiagnostics(files, cwd, signal) {
11990
12199
  const spawn3 = await loadSpawn();
11991
12200
  if (spawn3 === void 0) return [];
11992
12201
  if (signal?.aborted) return [];
11993
- const bin = path.join(cwd, "node_modules", ".bin", "eslint");
12202
+ const bin = path2.join(cwd, "node_modules", ".bin", "eslint");
11994
12203
  const output = await captureOutput(
11995
12204
  spawn3,
11996
12205
  bin,
@@ -12011,10 +12220,10 @@ function parseEslintOutput(output, cwd) {
12011
12220
  } catch {
12012
12221
  return [];
12013
12222
  }
12014
- const out = [];
12223
+ const out2 = [];
12015
12224
  for (const file of parsed) {
12016
12225
  if (!Array.isArray(file.messages) || file.messages.length === 0) continue;
12017
- const uri = path.isAbsolute(file.filePath) ? file.filePath : path.resolve(cwd, file.filePath);
12226
+ const uri = path2.isAbsolute(file.filePath) ? file.filePath : path2.resolve(cwd, file.filePath);
12018
12227
  const diagnostics = [];
12019
12228
  for (const msg of file.messages) {
12020
12229
  const line = Math.max(0, (msg.line ?? 1) - 1);
@@ -12032,9 +12241,9 @@ function parseEslintOutput(output, cwd) {
12032
12241
  ...msg.ruleId ? { code: msg.ruleId } : {}
12033
12242
  });
12034
12243
  }
12035
- if (diagnostics.length > 0) out.push({ uri, diagnostics });
12244
+ if (diagnostics.length > 0) out2.push({ uri, diagnostics });
12036
12245
  }
12037
- return out;
12246
+ return out2;
12038
12247
  }
12039
12248
  function captureOutput(spawn3, bin, args, cwd, signal) {
12040
12249
  return new Promise((resolve8) => {
@@ -12081,11 +12290,11 @@ function captureOutput(spawn3, bin, args, cwd, signal) {
12081
12290
  });
12082
12291
  }
12083
12292
  function isTypeScriptOrJs(filePath) {
12084
- return TS_JS_EXTENSIONS.has(path.extname(filePath).toLowerCase());
12293
+ return TS_JS_EXTENSIONS.has(path2.extname(filePath).toLowerCase());
12085
12294
  }
12086
12295
  function hasTsConfig(cwd) {
12087
12296
  try {
12088
- return existsSync2(path.join(cwd, "tsconfig.json"));
12297
+ return existsSync2(path2.join(cwd, "tsconfig.json"));
12089
12298
  } catch {
12090
12299
  return false;
12091
12300
  }
@@ -12103,7 +12312,7 @@ function hasEslintConfig(cwd) {
12103
12312
  ".eslintrc.yml",
12104
12313
  ".eslintrc.yaml"
12105
12314
  ];
12106
- return candidates.some((name) => existsSync2(path.join(cwd, name)));
12315
+ return candidates.some((name) => existsSync2(path2.join(cwd, name)));
12107
12316
  } catch {
12108
12317
  return false;
12109
12318
  }
@@ -12193,7 +12402,7 @@ var init_diagnostics = __esm({
12193
12402
  if (!this.#enabled) return [];
12194
12403
  if (editedPaths.length === 0) return [];
12195
12404
  const all = await this.#runAll(signal);
12196
- const editedSet = new Set(editedPaths.map((p) => path.resolve(this.#cwd, p)));
12405
+ const editedSet = new Set(editedPaths.map((p) => path2.resolve(this.#cwd, p)));
12197
12406
  if (this.#baseline === void 0) {
12198
12407
  this.#baseline = /* @__PURE__ */ new Map();
12199
12408
  for (const file of all) {
@@ -12218,7 +12427,7 @@ var init_diagnostics = __esm({
12218
12427
  * re-surface (e.g. the agent edited it again). The baseline is left intact.
12219
12428
  */
12220
12429
  clearDeliveredForFile(uri) {
12221
- this.#delivered.delete(path.resolve(this.#cwd, uri));
12430
+ this.#delivered.delete(path2.resolve(this.#cwd, uri));
12222
12431
  }
12223
12432
  /** Drop all cross-turn state and the baseline (re-seeds on the next run). */
12224
12433
  reset() {
@@ -12261,7 +12470,7 @@ var init_diagnostics = __esm({
12261
12470
  // src/conductor/conductor.ts
12262
12471
  import { Agent } from "indusagi/agent";
12263
12472
  import { createZeroUsage as createZeroUsage2 } from "indusagi/ai";
12264
- import * as path2 from "node:path";
12473
+ import * as path3 from "node:path";
12265
12474
  function reduceState(prev, action) {
12266
12475
  switch (action.type) {
12267
12476
  case "phase":
@@ -12320,7 +12529,7 @@ function createSessionConductor(options, deps = {}) {
12320
12529
  const approvalHolder = { resolver: void 0 };
12321
12530
  const approvalDelegate = async (toolName, input, opts) => {
12322
12531
  const installed = approvalHolder.resolver;
12323
- if (installed === void 0) return "deny";
12532
+ if (installed === void 0) return "unavailable";
12324
12533
  return installed(toolName, input, opts);
12325
12534
  };
12326
12535
  const gate = options.canUseTool?.(
@@ -12341,7 +12550,11 @@ function buildLiveAgent(options, deps, gate) {
12341
12550
  if (card === void 0) {
12342
12551
  throw new Error(`createSessionConductor: no model matches "${options.modelId}"`);
12343
12552
  }
12344
- return new LazyAgent(card.model, options, gate);
12553
+ return new LazyAgent(withGatewayBaseUrl(card.model, options.gatewayBaseUrls), options, gate);
12554
+ }
12555
+ function withGatewayBaseUrl(model, gatewayBaseUrls) {
12556
+ const url = gatewayBaseUrls?.[model.provider];
12557
+ return url ? { ...model, baseUrl: url } : model;
12345
12558
  }
12346
12559
  function makeAgent(model, options, gate) {
12347
12560
  const agent = new Agent({
@@ -12431,7 +12644,7 @@ ${outcome.output}`.trimEnd();
12431
12644
  timestamp: Date.now()
12432
12645
  };
12433
12646
  }
12434
- var noopCondense, DEFAULT_RETRY, MAX_CONSECUTIVE_CONDENSE_FAILURES, THINKING_LADDER, DEFAULT_THINKING, PERMISSION_CYCLE, SessionConductorImpl, LazyAgent;
12647
+ var noopCondense, DEFAULT_RETRY, MAX_CONSECUTIVE_CONDENSE_FAILURES, MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT, THINKING_LADDER, DEFAULT_THINKING, PERMISSION_CYCLE, SessionConductorImpl, LazyAgent;
12435
12648
  var init_conductor = __esm({
12436
12649
  "src/conductor/conductor.ts"() {
12437
12650
  "use strict";
@@ -12440,12 +12653,14 @@ var init_conductor = __esm({
12440
12653
  init_signal_hub();
12441
12654
  init_transcript_store();
12442
12655
  init_skill_parse();
12656
+ init_quota_error();
12443
12657
  init_plan_file();
12444
12658
  init_window_budget();
12445
12659
  init_diagnostics();
12446
12660
  noopCondense = (messages) => messages;
12447
12661
  DEFAULT_RETRY = { maxAttempts: 2, baseDelayMs: 250 };
12448
12662
  MAX_CONSECUTIVE_CONDENSE_FAILURES = 3;
12663
+ MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT = 3;
12449
12664
  THINKING_LADDER = ["off", "low", "medium", "high"];
12450
12665
  DEFAULT_THINKING = "off";
12451
12666
  PERMISSION_CYCLE = ["default", "acceptEdits", "plan", "bypass"];
@@ -12477,6 +12692,29 @@ var init_conductor = __esm({
12477
12692
  * to 0 on the next successful condense.
12478
12693
  */
12479
12694
  #condenseFailures = 0;
12695
+ /**
12696
+ * The cancel handle for an in-flight MANUAL condense (`/compact`), or
12697
+ * `undefined` when none is running. {@link cancelCondense} (and {@link abort},
12698
+ * so a user's Esc stops a compaction the same way it stops a turn) abort it;
12699
+ * {@link #runCondense} then discards the digest and reports `"cancelled"`.
12700
+ */
12701
+ #condenseAbort;
12702
+ /**
12703
+ * The session's per-call credential resolver (the same one the framework
12704
+ * Agent uses). The condense seam resolves the bound model's key through it so
12705
+ * the summarizer's model call authenticates exactly like a turn — including
12706
+ * server-tier sessions, whose ONLY credential is the vended session token
12707
+ * (no env var exists for the summarizer to fall back to).
12708
+ */
12709
+ #getApiKey;
12710
+ /**
12711
+ * Whether the next in-flight-turn abort is an EXPECTED steer (Ctrl+X ran
12712
+ * {@link steerNow}): the unwinding turn consumes the rejection quietly —
12713
+ * no terminal fault, no error toast, no busy-spinner drop — because the
12714
+ * steered turn takes over immediately. Reset at every turn start so a stale
12715
+ * flag can never swallow a genuine later fault.
12716
+ */
12717
+ #steerPending = false;
12480
12718
  /**
12481
12719
  * Canonical id of the model to fall back to when the bound model is overloaded
12482
12720
  * (HTTP 529), or `undefined` when no `--fallback-model` was configured. When
@@ -12484,6 +12722,20 @@ var init_conductor = __esm({
12484
12722
  * and retries instead of surfacing a terminal fault.
12485
12723
  */
12486
12724
  #fallbackModelId;
12725
+ /**
12726
+ * Base URL of the indus model gateway to route this session through ("server
12727
+ * mode"), or `undefined` for the normal direct-to-provider path. When set,
12728
+ * every model bind clones the framework model with its `baseUrl` swapped to
12729
+ * this URL (see {@link withGatewayBaseUrl}) so requests flow through the
12730
+ * quota-enforcing server while the session token (vended by `getApiKey`)
12731
+ * authenticates them. Absent leaves the local-key path byte-for-byte unchanged.
12732
+ *
12733
+ * Not `readonly`: {@link updateGatewayBaseUrls} replaces this map when a
12734
+ * mid-session sign-in changes which providers are gateway-eligible, so a
12735
+ * later rebind (see `#bindModel`/`#applyModelCard`) picks up the fresh
12736
+ * routing without waiting for a full conductor rebuild.
12737
+ */
12738
+ #gatewayBaseUrls;
12487
12739
  /**
12488
12740
  * Whether the one-shot fallback swap has already fired for the CURRENT turn.
12489
12741
  * Reset to `false` at the top of every {@link #runTurn} (not per attempt) so a
@@ -12535,6 +12787,13 @@ var init_conductor = __esm({
12535
12787
  * {@link #runPostEditDiagnostics} at the turn-end seam.
12536
12788
  */
12537
12789
  #editedThisTurn = /* @__PURE__ */ new Set();
12790
+ /**
12791
+ * How many diagnostics follow-ups have been auto-injected since the last REAL
12792
+ * user prompt. Reset by {@link submit}; capped by
12793
+ * {@link MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT} so a model that cannot satisfy a
12794
+ * checker can never be re-prompted forever without the user asking again.
12795
+ */
12796
+ #diagnosticFollowUps = 0;
12538
12797
  /**
12539
12798
  * The mode captured the moment plan mode was entered, restored when plan mode is
12540
12799
  * left. `undefined` whenever the session is not in (or paused from) plan mode.
@@ -12574,6 +12833,7 @@ var init_conductor = __esm({
12574
12833
  this.#hub = deps.hub ?? new SignalHub();
12575
12834
  this.#matcher = deps.matcher;
12576
12835
  this.#condense = deps.condense ?? noopCondense;
12836
+ this.#getApiKey = options.getApiKey;
12577
12837
  this.#retry = deps.retry ?? DEFAULT_RETRY;
12578
12838
  this.#autoCompact = options.autoCompact ?? true;
12579
12839
  this.#sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -12581,6 +12841,7 @@ var init_conductor = __esm({
12581
12841
  this.#modeHolder = modeHolder ?? { mode: options.permissionMode ?? "default" };
12582
12842
  this.#approvalHolder = approvalHolder ?? { resolver: void 0 };
12583
12843
  this.#fallbackModelId = options.fallbackModelId;
12844
+ this.#gatewayBaseUrls = options.gatewayBaseUrls;
12584
12845
  this.#workspace = options.workspace ?? process.cwd();
12585
12846
  this.#plansDir = options.plansDir;
12586
12847
  this.#checkpoint = options.checkpoint;
@@ -12597,7 +12858,10 @@ var init_conductor = __esm({
12597
12858
  // no fault initially
12598
12859
  };
12599
12860
  const initial = this.#matcher?.resolveCard(options.modelId);
12600
- this.#boundModel = initial?.model ?? agent.state.model;
12861
+ this.#boundModel = withGatewayBaseUrl(
12862
+ initial?.model ?? agent.state.model,
12863
+ this.#gatewayBaseUrls
12864
+ );
12601
12865
  }
12602
12866
  /* ---- public surface ---- */
12603
12867
  subscribe(handler) {
@@ -12620,12 +12884,18 @@ var init_conductor = __esm({
12620
12884
  return [...this.#matcher.resolveAll("")];
12621
12885
  }
12622
12886
  abort() {
12887
+ if (this.#queue.length > 0) {
12888
+ this.#queue.length = 0;
12889
+ this.#hub.emit({ kind: "queue", count: 0 });
12890
+ }
12891
+ this.#condenseAbort?.abort();
12623
12892
  this.#agent.abort();
12624
12893
  this.#dispatch({ type: "fault", fault: conductorFault("aborted", "turn aborted by caller") });
12625
12894
  this.#hub.emit({ kind: "fault", fault: conductorFault("aborted", "turn aborted by caller") });
12626
12895
  this.#hub.emit({ kind: "idle" });
12627
12896
  }
12628
12897
  async submit(input) {
12898
+ this.#diagnosticFollowUps = 0;
12629
12899
  if (this.#busy) {
12630
12900
  this.enqueue(input);
12631
12901
  return this.#state;
@@ -12660,6 +12930,22 @@ var init_conductor = __esm({
12660
12930
  this.#hub.emit({ kind: "queue", count: this.#queue.length });
12661
12931
  return popped.text;
12662
12932
  }
12933
+ steerNow() {
12934
+ const promoted = this.#queue.pop();
12935
+ if (promoted === void 0) return false;
12936
+ this.#queue.unshift({ mode: "steer", text: promoted.text });
12937
+ this.#hub.emit({ kind: "queue", count: this.#queue.length });
12938
+ if (this.#busy) {
12939
+ this.#steerPending = true;
12940
+ this.#condenseAbort?.abort();
12941
+ this.#agent.abort();
12942
+ return true;
12943
+ }
12944
+ const next = this.#takeNext();
12945
+ this.#hub.emit({ kind: "queue", count: this.#queue.length });
12946
+ void this.submit(next.text);
12947
+ return true;
12948
+ }
12663
12949
  /**
12664
12950
  * Drain the pending-input queue in order, running each entry as its own turn.
12665
12951
  * `steer`-mode entries are taken ahead of plain follow-ups so an interrupt is
@@ -12697,6 +12983,7 @@ var init_conductor = __esm({
12697
12983
  const prompt = skill ? skill.body : input;
12698
12984
  this.#inFlightTools.length = 0;
12699
12985
  this.#fallbackTried = false;
12986
+ this.#steerPending = false;
12700
12987
  this.#checkpoint?.setActiveNodeId(this.#store.head.leaf);
12701
12988
  this.#dispatch({ type: "phase", phase: "streaming" });
12702
12989
  if (this.#diagnostics !== void 0 && !this.#diagnostics.hasBaseline) {
@@ -12708,7 +12995,7 @@ var init_conductor = __esm({
12708
12995
  const baseline = this.#agent.state.messages.length;
12709
12996
  const clean = await this.#runWithRetry(prompt);
12710
12997
  if (!clean) {
12711
- this.#hub.emit({ kind: "idle" });
12998
+ if (this.#queue.length === 0) this.#hub.emit({ kind: "idle" });
12712
12999
  return this.#state;
12713
13000
  }
12714
13001
  await this.#persistTail(baseline);
@@ -12722,7 +13009,7 @@ var init_conductor = __esm({
12722
13009
  this.#dispatch({ type: "head", head: this.#store.head });
12723
13010
  }
12724
13011
  await this.#runPlanHandshakes();
12725
- this.#hub.emit({ kind: "idle" });
13012
+ if (this.#queue.length === 0) this.#hub.emit({ kind: "idle" });
12726
13013
  return this.#state;
12727
13014
  }
12728
13015
  async newSession() {
@@ -12732,6 +13019,7 @@ var init_conductor = __esm({
12732
13019
  this.#queue.length = 0;
12733
13020
  this.#inFlightTools.length = 0;
12734
13021
  this.#condenseFailures = 0;
13022
+ this.#diagnosticFollowUps = 0;
12735
13023
  this.#agent.replaceMessages([]);
12736
13024
  this.#store.startNewSession(newSessionId());
12737
13025
  this.#sessionName = void 0;
@@ -12772,6 +13060,19 @@ var init_conductor = __esm({
12772
13060
  selectModel(id) {
12773
13061
  this.#bindModel(id);
12774
13062
  }
13063
+ /**
13064
+ * Replace the server-tier gateway routing map and immediately re-bind the
13065
+ * currently selected model against it, so a mid-session sign-in (`/login` ->
13066
+ * "Indus Server") takes effect on the very next turn instead of requiring a
13067
+ * session restart. Re-resolves the current model's catalog card via
13068
+ * {@link #bindModel} rather than mutating `#boundModel` directly, so the
13069
+ * rebind goes through the exact same path as `selectModel`/`cycleModel`
13070
+ * (tool deck, thinking level, and state dispatch all stay consistent).
13071
+ */
13072
+ updateGatewayBaseUrls(map) {
13073
+ this.#gatewayBaseUrls = map;
13074
+ this.#bindModel(this.#state.modelId);
13075
+ }
12775
13076
  /**
12776
13077
  * Resolve a model by canonical id, bind it on the agent for subsequent turns,
12777
13078
  * track the full model object, and record the new id in state. A miss surfaces
@@ -12795,8 +13096,9 @@ var init_conductor = __esm({
12795
13096
  * so it must resolve its own card and call straight into here.
12796
13097
  */
12797
13098
  #applyModelCard(card) {
12798
- this.#agent.setModel(card.model);
12799
- this.#boundModel = card.model;
13099
+ const model = withGatewayBaseUrl(card.model, this.#gatewayBaseUrls);
13100
+ this.#agent.setModel(model);
13101
+ this.#boundModel = model;
12800
13102
  this.#agent.setThinkingLevel?.(this.#thinking);
12801
13103
  this.#dispatch({ type: "model", modelId: card.id });
12802
13104
  }
@@ -12825,7 +13127,10 @@ var init_conductor = __esm({
12825
13127
  fault: conductorFault(
12826
13128
  "model",
12827
13129
  `Switched to ${card.id} due to high demand for ${previous}`
12828
- )
13130
+ ),
13131
+ // The turn keeps running on the fallback model — this is an in-turn
13132
+ // notice, not the turn's end, so a busy/in-flight indicator must survive it.
13133
+ transient: true
12829
13134
  });
12830
13135
  return true;
12831
13136
  }
@@ -12852,11 +13157,32 @@ var init_conductor = __esm({
12852
13157
  return true;
12853
13158
  } catch (error) {
12854
13159
  unsubscribe();
13160
+ if (this.#steerPending) {
13161
+ this.#steerPending = false;
13162
+ return false;
13163
+ }
13164
+ if (isQuotaFault(error)) {
13165
+ const fault2 = conductorFault(
13166
+ "model",
13167
+ quotaErrorMessage(error) ?? "You've hit your indus usage limit.",
13168
+ error
13169
+ );
13170
+ this.#dispatch({ type: "fault", fault: fault2 });
13171
+ this.#hub.emit({ kind: "fault", fault: fault2 });
13172
+ return false;
13173
+ }
12855
13174
  if (attempt < this.#retry.maxAttempts && isTransient(error)) {
12856
13175
  attempt++;
12857
13176
  this.#hub.emit({
12858
13177
  kind: "fault",
12859
- fault: conductorFault("model", `transient model fault; retry ${attempt}`, error)
13178
+ fault: conductorFault("model", `transient model fault; retry ${attempt}`, error),
13179
+ // The retry loop is about to try again on the SAME turn — this is an
13180
+ // in-turn notice, not the turn's end. Without this flag the console
13181
+ // clears its busy/in-flight indicator here and never sets it back
13182
+ // (busy only flips true again at the START of the next turn), so the
13183
+ // agent looks "stuck" for the rest of a turn that is actually still
13184
+ // retrying/streaming underneath.
13185
+ transient: true
12860
13186
  });
12861
13187
  await this.#sleep(this.#retry.baseDelayMs * 2 ** (attempt - 1));
12862
13188
  continue;
@@ -12892,6 +13218,7 @@ var init_conductor = __esm({
12892
13218
  this.#capturePlanSignals(event.result);
12893
13219
  }
12894
13220
  for (const signal of translateAgentEvent(event)) {
13221
+ if (signal.kind === "fault" && this.#steerPending) continue;
12895
13222
  if (signal.kind === "turn_end") {
12896
13223
  this.#dispatch({
12897
13224
  type: "usage",
@@ -12918,7 +13245,7 @@ var init_conductor = __esm({
12918
13245
  const raw = args?.path;
12919
13246
  if (typeof raw !== "string" || raw.length === 0) return;
12920
13247
  if (!isTypeScriptOrJs(raw)) return;
12921
- this.#editedThisTurn.add(path2.resolve(this.#workspace, raw));
13248
+ this.#editedThisTurn.add(path3.resolve(this.#workspace, raw));
12922
13249
  }
12923
13250
  /* ---- plan mode ---- */
12924
13251
  /**
@@ -13064,8 +13391,13 @@ var init_conductor = __esm({
13064
13391
  * Invariants:
13065
13392
  * - **never faults the turn** — every failure (a slow/absent checker, a
13066
13393
  * parse error) is swallowed and the set is always cleared in `finally`;
13067
- * - **re-entrancy safe** — the injected follow-up turn edits nothing, so the
13068
- * set is empty and this short-circuits, avoiding an infinite loop;
13394
+ * - **can never loop forever** — a diagnostic key is delivered AT MOST ONCE
13395
+ * per session (the engine's delivered ledger is never cleared here; a
13396
+ * "fix" turn that fails to eliminate the same error is NOT re-prompted
13397
+ * with it), and even genuinely new errors stop re-prompting after
13398
+ * {@link MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT} consecutive follow-ups —
13399
+ * the agent must not keep running itself after its final report just
13400
+ * because a checker stays red;
13069
13401
  * - inert when diagnostics are disabled (`#diagnostics === undefined`).
13070
13402
  */
13071
13403
  async #runPostEditDiagnostics() {
@@ -13074,11 +13406,15 @@ var init_conductor = __esm({
13074
13406
  this.#editedThisTurn.clear();
13075
13407
  return;
13076
13408
  }
13409
+ if (this.#diagnosticFollowUps >= MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT) {
13410
+ this.#editedThisTurn.clear();
13411
+ return;
13412
+ }
13077
13413
  const edited = [...this.#editedThisTurn];
13078
13414
  try {
13079
- for (const uri of edited) engine.clearDeliveredForFile(uri);
13080
13415
  const files = await engine.newDiagnostics(edited);
13081
13416
  if (files.length > 0) {
13417
+ this.#diagnosticFollowUps++;
13082
13418
  this.enqueue(
13083
13419
  "Diagnostics for files you just changed (fix these before continuing):\n\n" + formatDiagnosticsSummary(files),
13084
13420
  "followUp"
@@ -13115,12 +13451,29 @@ var init_conductor = __esm({
13115
13451
  }
13116
13452
  /**
13117
13453
  * Manually drive the same condense path the auto-compactor uses, regardless of
13118
- * the auto-compaction flag or the soft threshold. Emits `{ kind: "compacted" }`
13119
- * and rebinds the agent's context when the hook shrinks the branch. Safe to
13120
- * invoke while idle.
13454
+ * the auto-compaction flag or the soft threshold, and report the outcome.
13455
+ *
13456
+ * Runs under the busy flag so a prompt submitted mid-compaction QUEUES (and
13457
+ * drains once the condense settles) instead of racing the rebind; is
13458
+ * cancellable via {@link cancelCondense} (the result is discarded); and
13459
+ * refuses (`"busy"`) while a real turn is in flight rather than condensing a
13460
+ * branch that is still growing.
13121
13461
  */
13122
13462
  async condense() {
13123
- await this.#runCondense(true);
13463
+ if (this.#busy) return "busy";
13464
+ this.#busy = true;
13465
+ this.#condenseAbort = new AbortController();
13466
+ try {
13467
+ const outcome = await this.#runCondense(true, this.#condenseAbort.signal);
13468
+ await this.#drainQueue();
13469
+ return outcome;
13470
+ } finally {
13471
+ this.#condenseAbort = void 0;
13472
+ this.#busy = false;
13473
+ }
13474
+ }
13475
+ cancelCondense() {
13476
+ this.#condenseAbort?.abort();
13124
13477
  }
13125
13478
  /* ---- branch navigation ---- */
13126
13479
  async fork(entryId) {
@@ -13321,27 +13674,51 @@ var init_conductor = __esm({
13321
13674
  * to a local stub). A throw surfaces an `overflow` fault and bumps the
13322
13675
  * consecutive-failure counter (the circuit breaker); a clean run resets it.
13323
13676
  */
13324
- async #runCondense(force) {
13677
+ async #runCondense(force, signal) {
13325
13678
  const branch = this.#store.pathTo();
13679
+ const prior = this.#state.phase;
13326
13680
  this.#dispatch({ type: "phase", phase: "condensing" });
13327
- this.#hub.emit({ kind: "compacted" });
13328
13681
  try {
13329
13682
  const before = branch.map((entry) => entry.content);
13683
+ let apiKey;
13684
+ if (this.#boundModel !== void 0 && this.#getApiKey !== void 0) {
13685
+ try {
13686
+ apiKey = await this.#getApiKey(String(this.#boundModel.provider));
13687
+ } catch {
13688
+ }
13689
+ }
13330
13690
  const after = await this.#condense(before, {
13331
13691
  force,
13332
13692
  ...this.#boundModel !== void 0 ? { model: this.#boundModel } : {},
13333
- contextTokens: this.#state.contextTokens
13693
+ contextTokens: this.#state.contextTokens,
13694
+ ...signal !== void 0 ? { signal } : {},
13695
+ ...apiKey !== void 0 ? { apiKey } : {}
13334
13696
  });
13335
- if (after.length < before.length) {
13336
- this.#agent.replaceMessages(after);
13337
- await this.#persistCondenseBoundary(branch, after);
13697
+ if (signal?.aborted) {
13698
+ this.#dispatch({ type: "phase", phase: prior });
13699
+ return "cancelled";
13338
13700
  }
13701
+ if (after.length >= before.length) {
13702
+ this.#condenseFailures = 0;
13703
+ this.#dispatch({ type: "phase", phase: prior });
13704
+ return "nothing";
13705
+ }
13706
+ this.#agent.replaceMessages([...after]);
13707
+ await this.#persistCondenseBoundary(branch, after);
13339
13708
  this.#condenseFailures = 0;
13709
+ if (force) {
13710
+ this.#dispatch({ type: "usage", usage: createZeroUsage2(), contextTokens: 0 });
13711
+ await this.#persistUsage();
13712
+ }
13713
+ this.#hub.emit({ kind: "compacted", manual: force });
13714
+ this.#dispatch({ type: "phase", phase: prior });
13715
+ return "condensed";
13340
13716
  } catch (error) {
13341
13717
  this.#condenseFailures++;
13342
13718
  const fault = conductorFault("overflow", "auto-condense failed", error);
13343
13719
  this.#dispatch({ type: "fault", fault });
13344
13720
  this.#hub.emit({ kind: "fault", fault });
13721
+ return "failed";
13345
13722
  }
13346
13723
  }
13347
13724
  /**
@@ -13690,11 +14067,11 @@ function consumeWrapperOptions(rest, wrapper) {
13690
14067
  }
13691
14068
  function parseBashCommand(command) {
13692
14069
  if (typeof command !== "string" || command.trim().length === 0) return [];
13693
- const out = [];
13694
- parseInto(command, out);
13695
- return out;
14070
+ const out2 = [];
14071
+ parseInto(command, out2);
14072
+ return out2;
13696
14073
  }
13697
- function parseInto(command, out) {
14074
+ function parseInto(command, out2) {
13698
14075
  for (const slice of splitTopLevel(command)) {
13699
14076
  const { outer, inner } = extractSubshells(slice);
13700
14077
  if (outer.length > 0) {
@@ -13703,7 +14080,7 @@ function parseInto(command, out) {
13703
14080
  const { rest, elevated } = stripWrappers(argv);
13704
14081
  const name = rest[0];
13705
14082
  if (name !== void 0 && name.length > 0) {
13706
- out.push({
14083
+ out2.push({
13707
14084
  name: name.toLowerCase(),
13708
14085
  args: rest.slice(1),
13709
14086
  elevated,
@@ -13713,7 +14090,7 @@ function parseInto(command, out) {
13713
14090
  }
13714
14091
  }
13715
14092
  for (const body of inner) {
13716
- if (body.trim().length > 0) parseInto(body, out);
14093
+ if (body.trim().length > 0) parseInto(body, out2);
13717
14094
  }
13718
14095
  }
13719
14096
  }
@@ -14017,8 +14394,18 @@ function resolveRuleDecision(toolName, input, rules, mode, readOnlyExtra) {
14017
14394
  }
14018
14395
  return "ask";
14019
14396
  }
14397
+ function allowAlwaysRuleStrings(toolName, input) {
14398
+ if (isBashTool(toolName)) {
14399
+ const bag = input;
14400
+ const command = typeof input === "string" ? input : bag !== null && typeof bag === "object" && typeof bag.command === "string" ? bag.command : "";
14401
+ if (command.length > 0) {
14402
+ return bashSubcommandSubjects(command).map((subject) => `${toolName}(${subject})`);
14403
+ }
14404
+ }
14405
+ return [toolName];
14406
+ }
14020
14407
  function noResolverMessage(toolName) {
14021
- return `Tool "${toolName}" requires approval, but this session is non-interactive (no approval prompt is available). Denying. Add an allow rule under permissions.allow in settings, or run with --permission-mode acceptEdits/bypass, to permit it.`;
14408
+ return `Tool "${toolName}" requires approval, but no interactive approval prompt is available here (a non-interactive run, or a sub-agent). Denying. Add an allow rule under permissions.allow in settings, or run with --permission-mode acceptEdits/bypass, to permit it.`;
14022
14409
  }
14023
14410
  function declinedMessage(toolName) {
14024
14411
  return `Tool "${toolName}" was denied by the user.`;
@@ -14060,15 +14447,34 @@ function createPermissionGate(config) {
14060
14447
  } catch {
14061
14448
  choice = "deny";
14062
14449
  }
14063
- if (choice === "deny") {
14064
- return { behavior: "deny", message: declinedMessage(toolName) };
14450
+ if (choice === "allow-once" || choice === "allow-always") {
14451
+ if (choice === "allow-always" && config.appendAllowRule) {
14452
+ for (const raw of allowAlwaysRuleStrings(toolName, input)) {
14453
+ config.appendAllowRule(makeRule("allow", raw));
14454
+ }
14455
+ }
14456
+ return { behavior: "allow" };
14065
14457
  }
14066
- if (choice === "allow-always" && config.appendAllowRule) {
14067
- config.appendAllowRule(makeRule("allow", toolName));
14458
+ if (choice === "unavailable") {
14459
+ return { behavior: "deny", message: noResolverMessage(toolName) };
14068
14460
  }
14069
- return { behavior: "allow" };
14461
+ return { behavior: "deny", message: declinedMessage(toolName) };
14070
14462
  };
14071
14463
  }
14464
+ function collectReadOnlyToolNames(tools) {
14465
+ const names = /* @__PURE__ */ new Set();
14466
+ for (const tool of tools) {
14467
+ if (tool.readOnly === true) names.add(tool.name);
14468
+ }
14469
+ return names;
14470
+ }
14471
+ function createSubagentPermissionGate(policy, tools) {
14472
+ return createPermissionGate({
14473
+ rules: policy.rules,
14474
+ mode: policy.mode,
14475
+ readOnlyToolNames: collectReadOnlyToolNames(tools)
14476
+ });
14477
+ }
14072
14478
  var READ_ONLY_TOOL_NAMES, EDIT_TOOL_NAMES;
14073
14479
  var init_permissions = __esm({
14074
14480
  "src/conductor/permissions.ts"() {
@@ -14102,14 +14508,17 @@ __export(conductor_exports, {
14102
14508
  SignalHub: () => SignalHub,
14103
14509
  TRANSCRIPT_SCHEMA: () => TRANSCRIPT_SCHEMA,
14104
14510
  TranscriptStore: () => TranscriptStore,
14511
+ allowAlwaysRuleStrings: () => allowAlwaysRuleStrings,
14105
14512
  areDiagnosticsEqual: () => areDiagnosticsEqual,
14106
14513
  bashSubcommandSubjects: () => bashSubcommandSubjects,
14107
14514
  canonicalId: () => canonicalId,
14108
14515
  catastrophicReason: () => catastrophicReason,
14516
+ collectReadOnlyToolNames: () => collectReadOnlyToolNames,
14109
14517
  conductorFault: () => conductorFault,
14110
14518
  createDiagnosticKey: () => createDiagnosticKey,
14111
14519
  createPermissionGate: () => createPermissionGate,
14112
14520
  createSessionConductor: () => createSessionConductor,
14521
+ createSubagentPermissionGate: () => createSubagentPermissionGate,
14113
14522
  deduplicateAndCap: () => deduplicateAndCap,
14114
14523
  evaluateCatastrophic: () => evaluateCatastrophic,
14115
14524
  formatDiagnosticsSummary: () => formatDiagnosticsSummary,
@@ -14136,7 +14545,8 @@ __export(conductor_exports, {
14136
14545
  runTscDiagnostics: () => runTscDiagnostics,
14137
14546
  toCardRef: () => toCardRef,
14138
14547
  toolMatchesRule: () => toolMatchesRule,
14139
- translateAgentEvent: () => translateAgentEvent
14548
+ translateAgentEvent: () => translateAgentEvent,
14549
+ withGatewayBaseUrl: () => withGatewayBaseUrl
14140
14550
  });
14141
14551
  var init_conductor2 = __esm({
14142
14552
  "src/conductor/index.ts"() {
@@ -14214,13 +14624,22 @@ function createWorkflowAgentRunner(opts) {
14214
14624
  if (model === void 0) {
14215
14625
  throw new Error(`no model resolved for workflow subagent: ${options.model ?? opts.modelId}`);
14216
14626
  }
14627
+ const gatewayBaseUrls = await opts.getGatewayBaseUrls?.();
14628
+ const tools = buildTools();
14217
14629
  agent = new Agent2({
14218
14630
  initialState: {
14219
- model,
14631
+ model: withGatewayBaseUrl(model, gatewayBaseUrls),
14220
14632
  systemPrompt: system,
14221
- tools: buildTools()
14633
+ tools
14222
14634
  },
14223
- ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {}
14635
+ ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {},
14636
+ // Gate the subagent's inner tool loop with the parent session's live
14637
+ // policy (rules + current mode). Resolver-less: `ask` denies — a
14638
+ // subagent cannot prompt — while deny rules, plan mode, and the
14639
+ // catastrophic-bash blocklist are enforced per inner tool call. The
14640
+ // `structured_output` capture tool is flagged readOnly, so a
14641
+ // schema-bound workflow still lands its result in every mode.
14642
+ ...opts.permissionPolicy !== void 0 ? { canUseTool: createSubagentPermissionGate(opts.permissionPolicy, tools) } : {}
14224
14643
  });
14225
14644
  }
14226
14645
  const activity = { tokensIn: 0, tokensOut: 0, toolCount: 0 };
@@ -14874,12 +15293,12 @@ function withdrawServer(ledger, server, at = (/* @__PURE__ */ new Date()).toISOS
14874
15293
  return { log, snapshot: reduceLedger(log), nextSeq: seq };
14875
15294
  }
14876
15295
  function liveCapabilitiesForServer(ledger, server) {
14877
- const out = [];
15296
+ const out2 = [];
14878
15297
  for (const key of liveKeysForServer(ledger, server)) {
14879
15298
  const cap = ledger.snapshot.live.get(key);
14880
- if (cap !== void 0) out.push(cap);
15299
+ if (cap !== void 0) out2.push(cap);
14881
15300
  }
14882
- return out;
15301
+ return out2;
14883
15302
  }
14884
15303
  function liveCapabilities(ledger) {
14885
15304
  return [...ledger.snapshot.live.values()];
@@ -14898,11 +15317,11 @@ function liveKeysForServer(ledger, server) {
14898
15317
  lastServer.set(entry.key, entry.server);
14899
15318
  }
14900
15319
  }
14901
- const out = [];
15320
+ const out2 = [];
14902
15321
  for (const key of ledger.snapshot.live.keys()) {
14903
- if (lastServer.get(key) === server) out.push(key);
15322
+ if (lastServer.get(key) === server) out2.push(key);
14904
15323
  }
14905
- return out;
15324
+ return out2;
14906
15325
  }
14907
15326
  var init_ledger = __esm({
14908
15327
  "src/capability-deck/bridge-ledger/ledger.ts"() {
@@ -15321,11 +15740,11 @@ function escapeChar(ch) {
15321
15740
  }
15322
15741
  }
15323
15742
  function escapeHtml(text) {
15324
- let out = "";
15743
+ let out2 = "";
15325
15744
  for (const ch of text) {
15326
- out += escapeChar(ch) ?? ch;
15745
+ out2 += escapeChar(ch) ?? ch;
15327
15746
  }
15328
- return out;
15747
+ return out2;
15329
15748
  }
15330
15749
  function classesFor(state) {
15331
15750
  const classes = [];
@@ -15371,15 +15790,15 @@ function paintSgr(input) {
15371
15790
  if (input.length === 0) return "";
15372
15791
  const tokens = tokenizeSgr(input);
15373
15792
  let state = { ...SGR_INITIAL_STATE };
15374
- let out = "";
15793
+ let out2 = "";
15375
15794
  for (const token of tokens) {
15376
15795
  if (token.kind === "sgr") {
15377
15796
  state = foldSgr(state, token.params);
15378
15797
  } else {
15379
- out += renderRun(token.text, state);
15798
+ out2 += renderRun(token.text, state);
15380
15799
  }
15381
15800
  }
15382
- return out;
15801
+ return out2;
15383
15802
  }
15384
15803
  var BASE_PALETTE, BRIGHT_PALETTE, PALETTE_16, CODE_TABLE, SGR_CODE_TABLE, ESC, CLASS_PREFIX, FLAG_CLASSES;
15385
15804
  var init_sgr = __esm({
@@ -15593,13 +16012,13 @@ function escapeRegExp(text) {
15593
16012
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15594
16013
  }
15595
16014
  function fill(template, values) {
15596
- let out = template;
16015
+ let out2 = template;
15597
16016
  for (const token of Object.values(SHELL_SLOTS)) {
15598
16017
  const value = values[token] ?? "";
15599
16018
  const marker = new RegExp(`\\{\\{${escapeRegExp(token)}\\}\\}`, "g");
15600
- out = out.replace(marker, () => value);
16019
+ out2 = out2.replace(marker, () => value);
15601
16020
  }
15602
- return out;
16021
+ return out2;
15603
16022
  }
15604
16023
  var PAGE_STYLES, CLIENT_SCRIPT, PAGE_SHELL;
15605
16024
  var init_template = __esm({
@@ -15877,8 +16296,8 @@ function configuredMarked() {
15877
16296
  return marked;
15878
16297
  }
15879
16298
  function renderMarkdown(md, source) {
15880
- const out = md.parse(source, { async: false });
15881
- return typeof out === "string" ? out : "";
16299
+ const out2 = md.parse(source, { async: false });
16300
+ return typeof out2 === "string" ? out2 : "";
15882
16301
  }
15883
16302
  function renderImage(part) {
15884
16303
  const src = `data:${escapeHtml2(part.mimeType)};base64,${part.data}`;
@@ -16056,8 +16475,9 @@ var init_transcript_export = __esm({
16056
16475
 
16057
16476
  // src/console/slash/commands/integrations.ts
16058
16477
  import { spawnSync } from "node:child_process";
16059
- import { writeFileSync as writeFileSync3 } from "node:fs";
16060
- import { resolve as resolve5 } from "node:path";
16478
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
16479
+ import { homedir as homedir2 } from "node:os";
16480
+ import { join as join10, resolve as resolve5 } from "node:path";
16061
16481
  import {
16062
16482
  createMCPAgentToolFactory,
16063
16483
  loadMCPConfig,
@@ -16116,13 +16536,13 @@ function exportTranscriptHtml(ctx, target) {
16116
16536
  if (messages.length === 0) {
16117
16537
  return { ok: false, reason: "Nothing to export yet \u2014 the transcript is empty." };
16118
16538
  }
16119
- const path3 = target.length > 0 ? resolve5(process.cwd(), target) : defaultExportPath();
16539
+ const path4 = target.length > 0 ? resolve5(process.cwd(), target) : defaultExportPath();
16120
16540
  try {
16121
16541
  const html = publishTranscript(toPublishEntries(messages), {
16122
16542
  title: "Session Transcript"
16123
16543
  });
16124
- writeFileSync3(path3, html, "utf-8");
16125
- return { ok: true, path: path3 };
16544
+ writeFileSync3(path4, html, "utf-8");
16545
+ return { ok: true, path: path4 };
16126
16546
  } catch (cause) {
16127
16547
  const message = cause instanceof Error ? cause.message : String(cause);
16128
16548
  return { ok: false, reason: `Could not write the HTML export: ${message}` };
@@ -16345,7 +16765,7 @@ function runShare(ctx) {
16345
16765
  });
16346
16766
  return HANDLED;
16347
16767
  }
16348
- function runLogin(ctx) {
16768
+ function runLogin2(ctx) {
16349
16769
  const provider = ctx.args.trim().toLowerCase();
16350
16770
  if (provider.length > 0) {
16351
16771
  ctx.openModal("signIn", { providerId: provider });
@@ -16354,10 +16774,82 @@ function runLogin(ctx) {
16354
16774
  }
16355
16775
  return HANDLED;
16356
16776
  }
16777
+ function hasLocalProviderKey(provider) {
16778
+ if (provider.length === 0) return false;
16779
+ try {
16780
+ const file = join10(homedir2(), ".indusagi", "agent", "auth.json");
16781
+ const vault = JSON.parse(readFileSync6(file, "utf8"));
16782
+ const entry = vault[provider];
16783
+ return entry !== null && typeof entry === "object" && Object.keys(entry).length > 0;
16784
+ } catch {
16785
+ return false;
16786
+ }
16787
+ }
16788
+ function runStatus(ctx) {
16789
+ const modelId = ctx.conductor.snapshot().modelId;
16790
+ const provider = modelId.includes("/") ? modelId.split("/")[0] : modelId;
16791
+ void (async () => {
16792
+ let signedIn = false;
16793
+ try {
16794
+ signedIn = await hasValidServerToken();
16795
+ } catch {
16796
+ signedIn = false;
16797
+ }
16798
+ const eligible = isGatewayEligibleProvider(provider);
16799
+ const localKey = hasLocalProviderKey(provider);
16800
+ const viaServer = signedIn && eligible && !localKey;
16801
+ if (viaServer) {
16802
+ ctx.setStatus(
16803
+ info(`\u25CF server tier \u2014 ${modelId} routing via ${INDUS_SERVER_URL} (signed in)`)
16804
+ );
16805
+ } else if (localKey) {
16806
+ ctx.setStatus(
16807
+ info(`\u25CB local key \u2014 ${modelId}${signedIn ? " (signed in to server)" : ""}`)
16808
+ );
16809
+ } else if (!signedIn) {
16810
+ ctx.setStatus(
16811
+ warn(`signed out \u2014 ${modelId} has no key. Run /login to use the server tier.`)
16812
+ );
16813
+ } else {
16814
+ ctx.setStatus(
16815
+ info(`signed in \u2014 ${modelId} (${provider} is not gateway-eligible; needs a local key)`)
16816
+ );
16817
+ }
16818
+ })();
16819
+ return HANDLED;
16820
+ }
16821
+ function runUsage(ctx) {
16822
+ void (async () => {
16823
+ const token = await getServerToken();
16824
+ if (!token?.access_token) {
16825
+ ctx.setStatus(warn("Not signed in to the server \u2014 run /login to use the server tier."));
16826
+ return;
16827
+ }
16828
+ try {
16829
+ const res = await fetch(`${INDUS_SERVER_URL}/api/usage`, {
16830
+ headers: { Authorization: `Bearer ${token.access_token}` }
16831
+ });
16832
+ if (!res.ok) {
16833
+ ctx.setStatus(warn(`Couldn't read usage (HTTP ${res.status}).`));
16834
+ return;
16835
+ }
16836
+ const u = await res.json();
16837
+ const pct = u.limit > 0 ? Math.round(u.used / u.limit * 100) : 0;
16838
+ const line = `Usage this month: ${u.used.toLocaleString()} / ${u.limit.toLocaleString()} tokens (${pct}%) \xB7 ${u.remaining.toLocaleString()} left \xB7 ${u.requests} reqs \xB7 $${(u.costUsd ?? 0).toFixed(4)}` + (u.allowed ? "" : " \xB7 LIMIT REACHED \u2014 requests are blocked");
16839
+ ctx.setStatus(u.allowed ? info(line) : warn(line));
16840
+ } catch (cause) {
16841
+ const message = cause instanceof Error ? cause.message : String(cause);
16842
+ ctx.setStatus(warn(`Usage check failed: ${message}`));
16843
+ }
16844
+ })();
16845
+ return HANDLED;
16846
+ }
16357
16847
  var CLIPBOARD_WRITERS, PUBLISH_ROLES, mcpPool, mcpServers, memoryEnabled, MEMORY_SUBS, COMPOSIO_ENV_KEY, composioGateway, composioResolved, COMPOSIO_NO_KEY, COMPOSIO_SUBS, runMemory, runComposio, integrationCommands;
16358
16848
  var init_integrations = __esm({
16359
16849
  "src/console/slash/commands/integrations.ts"() {
16360
16850
  "use strict";
16851
+ init_server_token();
16852
+ init_server_mode();
16361
16853
  init_capability_deck();
16362
16854
  init_transcript_export();
16363
16855
  init_shared();
@@ -16549,17 +17041,31 @@ var init_integrations = __esm({
16549
17041
  summary: "Sign in to a model provider.",
16550
17042
  takesArgs: true,
16551
17043
  run(ctx) {
16552
- return runLogin(ctx);
17044
+ return runLogin2(ctx);
16553
17045
  }
16554
17046
  },
16555
17047
  {
16556
17048
  name: "logout",
16557
- summary: "Sign out of a model provider.",
17049
+ summary: "Sign out: pick a provider account or the Indus Server session to clear.",
16558
17050
  run(ctx) {
16559
17051
  ctx.openModal("signOut");
16560
17052
  return HANDLED;
16561
17053
  }
16562
17054
  },
17055
+ {
17056
+ name: "status",
17057
+ summary: "Show the server session and whether this model uses the server.",
17058
+ run(ctx) {
17059
+ return runStatus(ctx);
17060
+ }
17061
+ },
17062
+ {
17063
+ name: "usage",
17064
+ summary: "Show your server usage this month (tokens used / limit / cost).",
17065
+ run(ctx) {
17066
+ return runUsage(ctx);
17067
+ }
17068
+ },
16563
17069
  {
16564
17070
  name: "mcp",
16565
17071
  summary: "Manage MCP servers and their tools.",
@@ -16684,19 +17190,19 @@ __export(builtins_exports, {
16684
17190
  FAMILY: () => FAMILY,
16685
17191
  SLASH_COMMANDS: () => SLASH_COMMANDS
16686
17192
  });
16687
- import { homedir as homedir2 } from "node:os";
16688
- import { join as join10 } from "node:path";
17193
+ import { homedir as homedir3 } from "node:os";
17194
+ import { join as join11 } from "node:path";
16689
17195
  function dynamicRoots() {
16690
- const home = homedir2();
17196
+ const home = homedir3();
16691
17197
  const cwd = process.cwd();
16692
17198
  return {
16693
17199
  skills: [
16694
- { dir: join10(cwd, PROJECT_DIR, "skills"), origin: "project" },
16695
- { dir: join10(home, PROJECT_DIR, "skills"), origin: "user" }
17200
+ { dir: join11(cwd, PROJECT_DIR, "skills"), origin: "project" },
17201
+ { dir: join11(home, PROJECT_DIR, "skills"), origin: "user" }
16696
17202
  ],
16697
17203
  macros: [
16698
- { dir: join10(cwd, PROJECT_DIR, "commands"), origin: "project", label: "project" },
16699
- { dir: join10(home, PROJECT_DIR, "commands"), origin: "user", label: "user" }
17204
+ { dir: join11(cwd, PROJECT_DIR, "commands"), origin: "project", label: "project" },
17205
+ { dir: join11(home, PROJECT_DIR, "commands"), origin: "user", label: "user" }
16700
17206
  ]
16701
17207
  };
16702
17208
  }
@@ -16813,7 +17319,7 @@ var BRAND = Object.freeze({
16813
17319
  envShareViewer: "INDUSAGI_SHARE_VIEWER_URL",
16814
17320
  shareViewerUrl: "https://buildwithindusagi.ai/session/"
16815
17321
  });
16816
- var VERSION = "0.2.3";
17322
+ var VERSION = "0.2.4";
16817
17323
 
16818
17324
  // src/workspace/locator.ts
16819
17325
  import { homedir } from "node:os";
@@ -17328,6 +17834,12 @@ var FLAG_SPECS = [
17328
17834
  group: "tools",
17329
17835
  describe: "Attach an external MCP server endpoint (comma-separated or repeated)."
17330
17836
  },
17837
+ {
17838
+ name: "--permission-mode",
17839
+ kind: "string",
17840
+ group: "tools",
17841
+ describe: "Open in the given permission mode: default, acceptEdits, plan, or bypass. On headless runs (--print/--json) `default` denies tools that would prompt."
17842
+ },
17331
17843
  /* ---- Meta ------------------------------------------------------------ */
17332
17844
  {
17333
17845
  name: "--help",
@@ -17676,8 +18188,8 @@ var AttachmentError = class extends Error {
17676
18188
  if (detail.cause !== void 0) this.cause = detail.cause;
17677
18189
  }
17678
18190
  };
17679
- function extensionOf(path3) {
17680
- return extname(path3).toLowerCase();
18191
+ function extensionOf(path4) {
18192
+ return extname(path4).toLowerCase();
17681
18193
  }
17682
18194
  function isMissing(error) {
17683
18195
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
@@ -17888,7 +18400,7 @@ function doConfig(store, io) {
17888
18400
  }
17889
18401
 
17890
18402
  // src/launch/credentials.ts
17891
- import { createInterface } from "node:readline/promises";
18403
+ import { createInterface as createInterface2 } from "node:readline/promises";
17892
18404
  import { getEnvApiKey, getOAuthProvider as getOAuthProvider2 } from "indusagi/ai";
17893
18405
 
17894
18406
  // src/launch/oauth.ts
@@ -17901,6 +18413,256 @@ import {
17901
18413
  openaiCodexOAuthProvider,
17902
18414
  registerOAuthProvider
17903
18415
  } from "indusagi/ai";
18416
+
18417
+ // src/launch/login.ts
18418
+ init_server_token();
18419
+ import { createInterface } from "node:readline/promises";
18420
+ import chalk from "chalk";
18421
+ var INDUS_CLIENT_ID = process.env.INDUS_CLIENT_ID ?? "indus-cli";
18422
+ var USER_AGENT = "indus CLI";
18423
+ var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
18424
+ var DEVICE_SCOPE = "openid profile email";
18425
+ var EXIT_OK = 0;
18426
+ var EXIT_FAILURE = 1;
18427
+ var SERVER_TIER_PROVIDER_ID = "indus-server";
18428
+ var LoginError = class extends Error {
18429
+ };
18430
+ function out(line = "") {
18431
+ process.stdout.write(`${line}
18432
+ `);
18433
+ }
18434
+ function err(line) {
18435
+ process.stderr.write(`${line}
18436
+ `);
18437
+ }
18438
+ function sleep(ms) {
18439
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
18440
+ }
18441
+ async function postJson(url, body) {
18442
+ const res = await fetch(url, {
18443
+ method: "POST",
18444
+ headers: {
18445
+ "content-type": "application/json",
18446
+ accept: "application/json",
18447
+ "user-agent": USER_AGENT
18448
+ },
18449
+ body: JSON.stringify(body)
18450
+ });
18451
+ const data = await res.json().catch(() => ({}));
18452
+ return { status: res.status, data };
18453
+ }
18454
+ function parseLoginArgs(tail) {
18455
+ let serverUrl = INDUS_SERVER_URL;
18456
+ let clientId = INDUS_CLIENT_ID;
18457
+ for (let i = 0; i < tail.length; i++) {
18458
+ const token = tail[i];
18459
+ const eq = token.indexOf("=");
18460
+ const name = eq >= 0 ? token.slice(0, eq) : token;
18461
+ const inline = eq >= 0 ? token.slice(eq + 1) : void 0;
18462
+ const take = () => inline !== void 0 ? inline : tail[++i];
18463
+ if (name === "--server-url") {
18464
+ const value = take();
18465
+ if (value) serverUrl = value;
18466
+ } else if (name === "--client-id") {
18467
+ const value = take();
18468
+ if (value) clientId = value;
18469
+ }
18470
+ }
18471
+ return { serverUrl: serverUrl.replace(/\/+$/, ""), clientId };
18472
+ }
18473
+ async function confirm(prompt, defaultValue) {
18474
+ if (!process.stdin.isTTY) return defaultValue;
18475
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
18476
+ try {
18477
+ const hint = defaultValue ? "Y/n" : "y/N";
18478
+ const answer = (await rl.question(`${prompt} [${hint}] `)).trim().toLowerCase();
18479
+ if (answer === "") return defaultValue;
18480
+ return answer === "y" || answer === "yes";
18481
+ } finally {
18482
+ rl.close();
18483
+ }
18484
+ }
18485
+ async function requestDeviceCode(serverUrl, clientId) {
18486
+ const { status, data } = await postJson(`${serverUrl}/api/auth/device/code`, {
18487
+ client_id: clientId,
18488
+ scope: DEVICE_SCOPE
18489
+ });
18490
+ if (status >= 400 || typeof data.device_code !== "string") {
18491
+ const e = data;
18492
+ throw new LoginError(
18493
+ `Authorization request failed: ${e.error_description ?? e.error ?? e.message ?? `HTTP ${status}`}`
18494
+ );
18495
+ }
18496
+ return data;
18497
+ }
18498
+ async function pollForToken(serverUrl, clientId, device) {
18499
+ let intervalMs = Math.max(1, device.interval ?? 5) * 1e3;
18500
+ const deadline = Date.now() + device.expires_in * 1e3;
18501
+ while (Date.now() < deadline) {
18502
+ await sleep(intervalMs);
18503
+ const { data } = await postJson(`${serverUrl}/api/auth/device/token`, {
18504
+ grant_type: DEVICE_GRANT_TYPE,
18505
+ device_code: device.device_code,
18506
+ client_id: clientId
18507
+ });
18508
+ if (typeof data.access_token === "string") {
18509
+ return data;
18510
+ }
18511
+ const code = data.error;
18512
+ switch (code) {
18513
+ case "authorization_pending":
18514
+ case void 0:
18515
+ break;
18516
+ case "slow_down":
18517
+ intervalMs += 5e3;
18518
+ break;
18519
+ case "access_denied":
18520
+ throw new LoginError("Authorization was denied.");
18521
+ case "expired_token":
18522
+ throw new LoginError("Device code expired. Run `indus login` again.");
18523
+ default:
18524
+ throw new LoginError(
18525
+ `Authorization error: ${data.error_description ?? code}`
18526
+ );
18527
+ }
18528
+ }
18529
+ throw new LoginError("Timed out waiting for authorization. Run `indus login` again.");
18530
+ }
18531
+ async function runServerTierLogin(callbacks, serverUrl = INDUS_SERVER_URL, clientId = INDUS_CLIENT_ID) {
18532
+ const device = await requestDeviceCode(serverUrl, clientId);
18533
+ callbacks.onAuth({
18534
+ url: device.verification_uri_complete ?? device.verification_uri,
18535
+ instructions: `Enter code ${device.user_code} to authorize this device.`
18536
+ });
18537
+ callbacks.onProgress?.("Waiting for authorization...");
18538
+ const token = await pollForToken(serverUrl, clientId, device);
18539
+ await storeServerToken(token);
18540
+ callbacks.onProgress?.("Server sign-in complete.");
18541
+ }
18542
+ async function doLogin(args) {
18543
+ const { serverUrl, clientId } = args;
18544
+ out();
18545
+ out(chalk.bold.cyan(" indus login"));
18546
+ out(chalk.dim(` ${serverUrl}`));
18547
+ out();
18548
+ const existing = await getServerToken();
18549
+ if (existing && !isServerTokenExpired(existing)) {
18550
+ const again = await confirm(
18551
+ chalk.yellow("Already authenticated. Log in again?"),
18552
+ false
18553
+ );
18554
+ if (!again) {
18555
+ out(chalk.dim(" Login cancelled \u2014 existing session preserved."));
18556
+ return EXIT_OK;
18557
+ }
18558
+ }
18559
+ try {
18560
+ out(chalk.dim(" Requesting device authorization..."));
18561
+ const device = await requestDeviceCode(serverUrl, clientId);
18562
+ const url = device.verification_uri_complete ?? device.verification_uri;
18563
+ out();
18564
+ out(` ${chalk.bold("Open this URL to authorize:")}`);
18565
+ out(` ${chalk.green.underline(url)}`);
18566
+ out();
18567
+ out(` ${chalk.bold("Verification code:")} ${chalk.bold.cyan(device.user_code)}`);
18568
+ out(
18569
+ chalk.dim(
18570
+ ` Expires in ${Math.max(1, Math.floor(device.expires_in / 60))} min \xB7 polling every ${Math.max(
18571
+ 1,
18572
+ device.interval ?? 5
18573
+ )}s`
18574
+ )
18575
+ );
18576
+ out();
18577
+ out(chalk.dim(" Waiting for authorization..."));
18578
+ const token = await pollForToken(serverUrl, clientId, device);
18579
+ const saved = await storeServerToken(token);
18580
+ out();
18581
+ out(chalk.green.bold(" Authentication successful."));
18582
+ if (saved) {
18583
+ out(chalk.dim(` Token stored at ${TOKEN_FILE}`));
18584
+ } else {
18585
+ out(
18586
+ chalk.yellow(
18587
+ " Warning: could not write the token to disk \u2014 you may need to log in again."
18588
+ )
18589
+ );
18590
+ }
18591
+ out();
18592
+ return EXIT_OK;
18593
+ } catch (error) {
18594
+ const message = error instanceof LoginError ? error.message : error instanceof Error ? error.message : String(error);
18595
+ err("");
18596
+ err(chalk.red(` Login failed: ${message}`));
18597
+ err("");
18598
+ return EXIT_FAILURE;
18599
+ }
18600
+ }
18601
+ async function doLogout() {
18602
+ const cleared = await clearServerToken();
18603
+ if (cleared) {
18604
+ out(chalk.green(" Logged out \u2014 server session token removed."));
18605
+ } else {
18606
+ out(chalk.dim(" No stored session token to remove."));
18607
+ }
18608
+ return EXIT_OK;
18609
+ }
18610
+ async function doWhoami(args) {
18611
+ const token = await getServerToken();
18612
+ if (!token || isServerTokenExpired(token)) {
18613
+ err(chalk.yellow(" Not logged in. Run `indus login` first."));
18614
+ return EXIT_FAILURE;
18615
+ }
18616
+ try {
18617
+ const res = await fetch(`${args.serverUrl}/api/user/me`, {
18618
+ method: "GET",
18619
+ headers: {
18620
+ authorization: `Bearer ${token.access_token}`,
18621
+ accept: "application/json",
18622
+ "user-agent": USER_AGENT
18623
+ }
18624
+ });
18625
+ if (res.status === 401) {
18626
+ err(chalk.yellow(" Session expired or invalid. Run `indus login` again."));
18627
+ return EXIT_FAILURE;
18628
+ }
18629
+ if (!res.ok) {
18630
+ err(chalk.red(` Could not fetch account: HTTP ${res.status}`));
18631
+ return EXIT_FAILURE;
18632
+ }
18633
+ const user = await res.json();
18634
+ out();
18635
+ out(chalk.bold(" Signed in as:"));
18636
+ out(` ${chalk.bold(user.name || user.email)} ${chalk.dim(`<${user.email}>`)}`);
18637
+ out(` ${chalk.dim("plan:")} ${user.plan}`);
18638
+ out(
18639
+ ` ${chalk.dim("monthly token limit:")} ${user.monthlyTokenLimit.toLocaleString()}`
18640
+ );
18641
+ out();
18642
+ return EXIT_OK;
18643
+ } catch (error) {
18644
+ const message = error instanceof Error ? error.message : String(error);
18645
+ err(chalk.red(` Could not reach the server: ${message}`));
18646
+ return EXIT_FAILURE;
18647
+ }
18648
+ }
18649
+ async function runLogin(ctx) {
18650
+ const verb = ctx.argv[0];
18651
+ if (verb !== "login" && verb !== "logout" && verb !== "whoami") {
18652
+ return void 0;
18653
+ }
18654
+ const args = parseLoginArgs(ctx.argv.slice(1));
18655
+ switch (verb) {
18656
+ case "login":
18657
+ return doLogin(args);
18658
+ case "logout":
18659
+ return doLogout();
18660
+ case "whoami":
18661
+ return doWhoami(args);
18662
+ }
18663
+ }
18664
+
18665
+ // src/launch/oauth.ts
17904
18666
  var BUILT_IN_OAUTH = [
17905
18667
  { provider: anthropicOAuthProvider, label: "Anthropic (Claude sign-in)" },
17906
18668
  { provider: openaiCodexOAuthProvider, label: "OpenAI (ChatGPT sign-in)" },
@@ -17942,6 +18704,11 @@ function oauthClientIdEnvVar(providerId) {
17942
18704
  return OAUTH_CLIENT_ID_WIRING[providerId]?.envVar;
17943
18705
  }
17944
18706
  function listLoginProviders() {
18707
+ const serverTier = {
18708
+ id: SERVER_TIER_PROVIDER_ID,
18709
+ label: "Indus Server (sign in for server-tier model access)",
18710
+ authKind: "oauth"
18711
+ };
17945
18712
  const oauth = getOAuthProviders().map((provider) => ({
17946
18713
  id: provider.id,
17947
18714
  label: OAUTH_LABELS[provider.id] ?? provider.name,
@@ -17952,7 +18719,7 @@ function listLoginProviders() {
17952
18719
  label: entry.label,
17953
18720
  authKind: "apiKey"
17954
18721
  }));
17955
- return [...oauth, ...apiKey];
18722
+ return [serverTier, ...oauth, ...apiKey];
17956
18723
  }
17957
18724
  function isWebUrl(url) {
17958
18725
  try {
@@ -18008,6 +18775,10 @@ function openLoginUrl(url) {
18008
18775
  return tryAt(0);
18009
18776
  }
18010
18777
  async function startOAuthLogin(providerId, callbacks, vault, account = "default") {
18778
+ if (providerId === SERVER_TIER_PROVIDER_ID) {
18779
+ await runServerTierLogin(callbacks);
18780
+ return { provider: providerId, account, madeDefault: true };
18781
+ }
18011
18782
  const provider = getOAuthProvider(providerId);
18012
18783
  if (provider === void 0) {
18013
18784
  throw new Error(`No browser sign-in is available for "${providerId}".`);
@@ -18031,7 +18802,7 @@ function defaultCredentialIo() {
18031
18802
  process.stdout.write(line.endsWith("\n") ? line : line + "\n");
18032
18803
  },
18033
18804
  async ask(prompt) {
18034
- const rl = createInterface({
18805
+ const rl = createInterface2({
18035
18806
  input: process.stdin,
18036
18807
  output: process.stdout
18037
18808
  });
@@ -18042,14 +18813,14 @@ function defaultCredentialIo() {
18042
18813
  }
18043
18814
  },
18044
18815
  async askSecret(prompt) {
18045
- const rl = createInterface({
18816
+ const rl = createInterface2({
18046
18817
  input: process.stdin,
18047
18818
  output: process.stdout
18048
18819
  });
18049
- const out = process.stdout;
18820
+ const out2 = process.stdout;
18050
18821
  let muted = false;
18051
- const original = out.write.bind(out);
18052
- out.write = ((chunk, ...args) => {
18822
+ const original = out2.write.bind(out2);
18823
+ out2.write = ((chunk, ...args) => {
18053
18824
  if (muted && typeof chunk === "string") return true;
18054
18825
  return original(chunk, ...args);
18055
18826
  });
@@ -18060,8 +18831,8 @@ function defaultCredentialIo() {
18060
18831
  return value.trim();
18061
18832
  } finally {
18062
18833
  muted = false;
18063
- out.write = original;
18064
- out.write("\n");
18834
+ out2.write = original;
18835
+ out2.write("\n");
18065
18836
  rl.close();
18066
18837
  }
18067
18838
  }
@@ -18225,8 +18996,8 @@ function validateAccountName(name) {
18225
18996
  }
18226
18997
  var DEFAULT_ACCOUNT = "default";
18227
18998
  function asVerb(token) {
18228
- if (token === "signin" || token === "login") return "signin";
18229
- if (token === "signout" || token === "logout") return "signout";
18999
+ if (token === "signin") return "signin";
19000
+ if (token === "signout") return "signout";
18230
19001
  return void 0;
18231
19002
  }
18232
19003
  async function runCredentialCommand(argv, opts) {
@@ -18637,7 +19408,7 @@ function printModelCatalog(io = defaultCatalogIo(), filter = {}, source = regist
18637
19408
  }
18638
19409
 
18639
19410
  // src/launch/pickers.ts
18640
- import { createInterface as createInterface2 } from "node:readline/promises";
19411
+ import { createInterface as createInterface3 } from "node:readline/promises";
18641
19412
  import { createElement } from "indusagi/react-host";
18642
19413
  import { render } from "indusagi/react-host/ink";
18643
19414
  import { StartupSessionPicker } from "indusagi/react-ink";
@@ -18725,7 +19496,7 @@ function defaultSettingsBrowseIo() {
18725
19496
  process.stdout.write(line.endsWith("\n") ? line : line + "\n");
18726
19497
  },
18727
19498
  async pause(prompt) {
18728
- const rl = createInterface2({
19499
+ const rl = createInterface3({
18729
19500
  input: process.stdin,
18730
19501
  output: process.stdout
18731
19502
  });
@@ -18739,7 +19510,7 @@ function defaultSettingsBrowseIo() {
18739
19510
  }
18740
19511
  function renderSection(title, paths) {
18741
19512
  if (paths.length === 0) return [`${title}: (none)`];
18742
- return [`${title}:`, ...paths.map((path3) => ` - ${path3}`)];
19513
+ return [`${title}:`, ...paths.map((path4) => ` - ${path4}`)];
18743
19514
  }
18744
19515
  function renderSettingsListing(opts) {
18745
19516
  const lines = [];
@@ -19058,6 +19829,14 @@ function consoleReducer(state, event) {
19058
19829
  return setBuffer(state, event.text);
19059
19830
  case "buffer:insert":
19060
19831
  return insertBuffer(state, event.text);
19832
+ case "buffer:replaceSpan": {
19833
+ const at = state.buffer.indexOf(event.find);
19834
+ if (at < 0) return state;
19835
+ const buffer = state.buffer.slice(0, at) + event.replaceWith + state.buffer.slice(at + event.find.length);
19836
+ const spanEnd = at + event.find.length;
19837
+ const caret = state.caret >= spanEnd ? state.caret + (event.replaceWith.length - event.find.length) : state.caret > at ? at + event.replaceWith.length : state.caret;
19838
+ return { ...state, buffer, caret: clamp(caret, buffer.length) };
19839
+ }
19061
19840
  case "buffer:erasePrev":
19062
19841
  return erasePrev(state);
19063
19842
  case "buffer:eraseNext":
@@ -19384,7 +20163,8 @@ var DEFAULT_SLASH_REGISTRY = buildRegistry(SLASH_COMMANDS);
19384
20163
  // src/console/input/keymap.ts
19385
20164
  var NO_INTENT = { verb: "none" };
19386
20165
  function typed(text) {
19387
- return text.length === 0 ? NO_INTENT : { verb: "text:type", text };
20166
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
20167
+ return normalized.length === 0 ? NO_INTENT : { verb: "text:type", text: normalized };
19388
20168
  }
19389
20169
  function isPrintable(input) {
19390
20170
  if (input.length === 0) return false;
@@ -19419,6 +20199,8 @@ function readKey(input, key) {
19419
20199
  return { verb: "input:externalEditor" };
19420
20200
  case "r":
19421
20201
  return { verb: "overlay:open", overlay: "sessions" };
20202
+ case "x":
20203
+ return { verb: "queue:steer" };
19422
20204
  case "l":
19423
20205
  return { verb: "overlay:open", overlay: "models" };
19424
20206
  case "o":
@@ -19461,8 +20243,10 @@ function advanceChord(latch, intent) {
19461
20243
  }
19462
20244
 
19463
20245
  // src/console/input/paste.ts
19464
- var PASTE_THRESHOLDS = { lines: 8, chars: 800 };
20246
+ var PASTE_THRESHOLDS = { lines: 10, chars: 1001 };
19465
20247
  var PASTE_BURST_GAP_MS = 24;
20248
+ var PASTE_CONTINUATION_MS = 250;
20249
+ var PASTE_MARKER_LINE_CAP = 1e3;
19466
20250
  function pasteMarker(ordinal, lineCount) {
19467
20251
  return `[Pasted text #${ordinal}${lineCount > 0 ? ` +${lineCount} lines` : ""}]`;
19468
20252
  }
@@ -19490,6 +20274,19 @@ function coalescePaste(body, vault = EMPTY_PASTE_VAULT) {
19490
20274
  bodies.set(ordinal, body);
19491
20275
  return { marker, vault: { bodies, minted: ordinal } };
19492
20276
  }
20277
+ function appendToPaste(ordinal, extra, vault) {
20278
+ const body = (vault.bodies.get(ordinal) ?? "") + extra;
20279
+ const lineCount = body.length === 0 ? 0 : lineBreaks(body) + 1;
20280
+ const marker = pasteMarker(ordinal, lineCount);
20281
+ const bodies = new Map(vault.bodies);
20282
+ bodies.set(ordinal, body);
20283
+ return { marker, vault: { bodies, minted: vault.minted } };
20284
+ }
20285
+ function pasteLines(vault, ordinal) {
20286
+ const body = vault.bodies.get(ordinal);
20287
+ if (body === void 0 || body.length === 0) return 0;
20288
+ return lineBreaks(body) + 1;
20289
+ }
19493
20290
  function expandMarkers(buffer, vault) {
19494
20291
  if (vault.bodies.size === 0) return buffer;
19495
20292
  return buffer.replace(markerScanner(), (_match, ordinalStr) => {
@@ -19611,10 +20408,11 @@ function createDirReader(cwd = process.cwd()) {
19611
20408
 
19612
20409
  // src/console/components/TerminalConsole.tsx
19613
20410
  init_workflow_engine();
20411
+ init_window_budget();
19614
20412
  import { execFileSync } from "node:child_process";
19615
- import { homedir as homedir4 } from "node:os";
19616
- import React3, { useEffect as useEffect6, useMemo as useMemo2, useState as useState7 } from "indusagi/react-host";
19617
- import { Box as Box10, useInput as useInput3 } from "indusagi/react-host/ink";
20413
+ import { homedir as homedir6 } from "node:os";
20414
+ import React3, { useEffect as useEffect7, useMemo as useMemo2, useState as useState8 } from "indusagi/react-host";
20415
+ import { Box as Box10, Text as Text10, useInput as useInput3 } from "indusagi/react-host/ink";
19618
20416
  import {
19619
20417
  MessageList,
19620
20418
  StreamingMarkdown,
@@ -19622,7 +20420,7 @@ import {
19622
20420
  } from "indusagi/react-ink";
19623
20421
 
19624
20422
  // src/console/components/Banner.tsx
19625
- import { homedir as homedir3 } from "node:os";
20423
+ import { homedir as homedir4 } from "node:os";
19626
20424
  import { basename as basename4 } from "node:path";
19627
20425
  import { Box, Text } from "indusagi/react-host/ink";
19628
20426
  import { ChangelogBlock } from "indusagi/react-ink";
@@ -19665,9 +20463,9 @@ var TIPS = [
19665
20463
  "Use /skills to discover specialized helpers for the task at hand"
19666
20464
  ];
19667
20465
  var WHATS_NEW = [
19668
- "Added /rewind support for resuming a conversation from before /clear was run",
19669
- "Fixed scroll jumping to the bottom while you read earlier output mid-stream",
19670
- "Stopped background agents stay stopped \u2014 no more resurrecting from the tasks panel"
20466
+ "Ctrl+X runs your newest queued message NOW \u2014 the current run stops cleanly",
20467
+ "/compact shows live progress, resets to a fresh screen, and lists carried files",
20468
+ '"Allow always" is remembered for the session; sub-agents respect your permission mode'
19671
20469
  ];
19672
20470
  function wrapIndex(seed, len) {
19673
20471
  if (len <= 0) return 0;
@@ -19799,7 +20597,7 @@ function Mascot({
19799
20597
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", alignItems: "center", marginY: 1, children: mascotRows(face).map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.map((seg, segIndex) => /* @__PURE__ */ jsx(Text, { children: paintMascot(theme, seg.tone, seg.text) }, segIndex)) }, rowIndex)) });
19800
20598
  }
19801
20599
  function shortenWorkspace(workspace) {
19802
- const home = homedir3();
20600
+ const home = homedir4();
19803
20601
  if (home.length > 0 && workspace.startsWith(home)) {
19804
20602
  return `~${workspace.slice(home.length)}`;
19805
20603
  }
@@ -19939,6 +20737,43 @@ function Banner({
19939
20737
  ] });
19940
20738
  }
19941
20739
 
20740
+ // src/console/auth-status.ts
20741
+ init_server_token();
20742
+ init_server_mode();
20743
+ import { useEffect, useState } from "indusagi/react-host";
20744
+ function useAuthenticatedProviders(services, refreshKey) {
20745
+ const [authed, setAuthed] = useState(null);
20746
+ useEffect(() => {
20747
+ if (!services) return;
20748
+ let live = true;
20749
+ void (async () => {
20750
+ const found = /* @__PURE__ */ new Set();
20751
+ try {
20752
+ for (const provider of services.listLoginProviders()) {
20753
+ try {
20754
+ if ((await services.vault.listAccounts(provider.id)).length > 0) {
20755
+ found.add(provider.id);
20756
+ }
20757
+ } catch {
20758
+ }
20759
+ }
20760
+ } catch {
20761
+ }
20762
+ try {
20763
+ if (await hasValidServerToken()) {
20764
+ for (const provider of serverTierProviders()) found.add(provider);
20765
+ }
20766
+ } catch {
20767
+ }
20768
+ if (live) setAuthed(found);
20769
+ })();
20770
+ return () => {
20771
+ live = false;
20772
+ };
20773
+ }, [refreshKey]);
20774
+ return authed;
20775
+ }
20776
+
19942
20777
  // src/console/components/BackgroundAgents.tsx
19943
20778
  import { Box as Box2, Text as Text2 } from "indusagi/react-host/ink";
19944
20779
  import { splitAssistantMessage } from "indusagi/react-ink";
@@ -20033,7 +20868,7 @@ function BackgroundAgents({ theme, runs, tokens, now }) {
20033
20868
  }
20034
20869
 
20035
20870
  // src/console/components/AgentsView.tsx
20036
- import { useState } from "indusagi/react-host";
20871
+ import { useState as useState2 } from "indusagi/react-host";
20037
20872
  import { Box as Box3, Text as Text3, useInput } from "indusagi/react-host/ink";
20038
20873
  import { wrapSelectionIndex } from "indusagi/react-ink";
20039
20874
  import { jsx as jsx3, jsxs as jsxs3 } from "indusagi/react-host/jsx-runtime";
@@ -20077,7 +20912,7 @@ function ActivityRow({
20077
20912
  }
20078
20913
  function AgentsView({ theme, runs, activity, tokens, now, onExit }) {
20079
20914
  const clock = now ?? Date.now();
20080
- const [selectedIndex, setSelectedIndex] = useState(runs.length > 0 ? 1 : 0);
20915
+ const [selectedIndex, setSelectedIndex] = useState2(runs.length > 0 ? 1 : 0);
20081
20916
  const rowCount = runs.length + 1;
20082
20917
  const index = Math.min(selectedIndex, rowCount - 1);
20083
20918
  const viewed = index > 0 ? runs[index - 1] : void 0;
@@ -20248,7 +21083,7 @@ function StatusBar({
20248
21083
  }
20249
21084
 
20250
21085
  // src/console/components/WorkingIndicator.tsx
20251
- import React, { useEffect, useState as useState2 } from "indusagi/react-host";
21086
+ import React, { useEffect as useEffect2, useState as useState3 } from "indusagi/react-host";
20252
21087
  import { Box as Box6, Text as Text6 } from "indusagi/react-host/ink";
20253
21088
  import { jsx as jsx6, jsxs as jsxs6 } from "indusagi/react-host/jsx-runtime";
20254
21089
  var { useRef } = React;
@@ -20306,10 +21141,10 @@ function WorkingIndicator({
20306
21141
  busy,
20307
21142
  phase
20308
21143
  }) {
20309
- const [frame, setFrame] = useState2(0);
21144
+ const [frame, setFrame] = useState3(0);
20310
21145
  const startRef = useRef(null);
20311
21146
  const wordOffsetRef = useRef(0);
20312
- useEffect(() => {
21147
+ useEffect2(() => {
20313
21148
  if (!busy) {
20314
21149
  startRef.current = null;
20315
21150
  return;
@@ -20344,7 +21179,7 @@ var APPROVAL_CHOICES = [
20344
21179
  {
20345
21180
  id: "allow-always",
20346
21181
  label: "Allow always",
20347
- description: "run it and remember the tool for this session"
21182
+ description: "run it and remember this action for the session"
20348
21183
  },
20349
21184
  { id: "deny", label: "Deny", description: "block this call" }
20350
21185
  ];
@@ -20446,7 +21281,8 @@ function ApprovalOverlays(props) {
20446
21281
  }
20447
21282
 
20448
21283
  // src/console/overlays/auth.tsx
20449
- import React2, { useEffect as useEffect2, useState as useState3 } from "indusagi/react-host";
21284
+ init_server_token();
21285
+ import React2, { useEffect as useEffect3, useState as useState4 } from "indusagi/react-host";
20450
21286
  import { Box as Box8, Text as Text8 } from "indusagi/react-host/ink";
20451
21287
  import {
20452
21288
  DialogFrame,
@@ -20469,7 +21305,7 @@ function SignInDialog(props) {
20469
21305
  }
20470
21306
  })();
20471
21307
  const direct = requestedProvider !== void 0 ? providers.find((p) => p.id === requestedProvider) : void 0;
20472
- useEffect2(() => {
21308
+ useEffect3(() => {
20473
21309
  if (direct === void 0) return;
20474
21310
  dispatch2({
20475
21311
  type: "modal:open",
@@ -20500,13 +21336,25 @@ function SignInDialog(props) {
20500
21336
  }
20501
21337
  );
20502
21338
  }
21339
+ var SERVER_TIER_ACCOUNT_ID = "server-session";
20503
21340
  function SignOutDialog(props) {
20504
21341
  const { services, closeModal } = props;
20505
- const [accounts, setAccounts] = useState3([]);
20506
- useEffect2(() => {
21342
+ const [accounts, setAccounts] = useState4([]);
21343
+ useEffect3(() => {
20507
21344
  let live = true;
20508
21345
  void (async () => {
20509
21346
  const rows = [];
21347
+ try {
21348
+ const token = await getServerToken();
21349
+ if (token) {
21350
+ rows.push({
21351
+ provider: SERVER_TIER_PROVIDER_ID,
21352
+ accountId: SERVER_TIER_ACCOUNT_ID,
21353
+ label: "Indus Server \xB7 signed in"
21354
+ });
21355
+ }
21356
+ } catch {
21357
+ }
20510
21358
  try {
20511
21359
  for (const provider of services.listLoginProviders()) {
20512
21360
  let names = [];
@@ -20539,7 +21387,11 @@ function SignOutDialog(props) {
20539
21387
  onClose: closeModal,
20540
21388
  onSelect: async (account) => {
20541
21389
  try {
20542
- await services.vault.remove(account.provider, account.accountId);
21390
+ if (account.provider === SERVER_TIER_PROVIDER_ID) {
21391
+ await clearServerToken();
21392
+ } else {
21393
+ await services.vault.remove(account.provider, account.accountId);
21394
+ }
20543
21395
  } catch {
20544
21396
  }
20545
21397
  closeModal();
@@ -20577,12 +21429,12 @@ function OAuthFlowDialog(props) {
20577
21429
  return providerId;
20578
21430
  }
20579
21431
  })();
20580
- const [state, setState] = useState3(
21432
+ const [state, setState] = useState4(
20581
21433
  () => seedOAuthState(providerId, providerName, mode)
20582
21434
  );
20583
21435
  const pending = useRef2(null);
20584
21436
  const merge = (next) => setState((prev) => ({ ...prev, ...next }));
20585
- useEffect2(() => {
21437
+ useEffect3(() => {
20586
21438
  if (mode !== "oauth") return;
20587
21439
  let live = true;
20588
21440
  const safeMerge = (next) => {
@@ -20620,6 +21472,7 @@ function OAuthFlowDialog(props) {
20620
21472
  void (async () => {
20621
21473
  try {
20622
21474
  await services.startOAuthLogin(providerId, callbacks, services.vault);
21475
+ await services.refreshGatewayRouting();
20623
21476
  selectProviderModel(services, providerId);
20624
21477
  if (live) {
20625
21478
  dispatch2({
@@ -20763,21 +21616,47 @@ function readEntryMode(payload) {
20763
21616
  }
20764
21617
 
20765
21618
  // src/console/overlays/pickers.tsx
20766
- import { useEffect as useEffect4, useState as useState5 } from "indusagi/react-host";
21619
+ import { useEffect as useEffect5, useState as useState6 } from "indusagi/react-host";
20767
21620
  import {
20768
21621
  ScopedModelsDialog,
20769
21622
  ThemeDialog as ThemeDialog2
20770
21623
  } from "indusagi/react-ink";
20771
21624
 
20772
21625
  // src/console/overlays/boards.tsx
20773
- import chalk from "chalk";
20774
- import { useEffect as useEffect3, useMemo, useState as useState4 } from "indusagi/react-host";
21626
+ init_server_mode();
21627
+ import chalk2 from "chalk";
21628
+ import { readFileSync as readFileSync7 } from "node:fs";
21629
+ import { homedir as homedir5 } from "node:os";
21630
+ import { join as join12 } from "node:path";
21631
+ import { useEffect as useEffect4, useMemo, useState as useState5 } from "indusagi/react-host";
20775
21632
  import { Box as Box9, Text as Text9, useInput as useInput2 } from "indusagi/react-host/ink";
20776
21633
  import {
20777
21634
  matchesSearchQuery,
20778
21635
  wrapSelectionIndex as wrapSelectionIndex2
20779
21636
  } from "indusagi/react-ink";
20780
21637
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "indusagi/react-host/jsx-runtime";
21638
+ function readServerTierInfo() {
21639
+ let signedIn = false;
21640
+ try {
21641
+ const file = join12(homedir5(), ".indusagi", "agent", "server-token.json");
21642
+ const token = JSON.parse(readFileSync7(file, "utf8"));
21643
+ signedIn = typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1e3;
21644
+ } catch {
21645
+ signedIn = false;
21646
+ }
21647
+ const localKeys = /* @__PURE__ */ new Set();
21648
+ try {
21649
+ const file = join12(homedir5(), ".indusagi", "agent", "auth.json");
21650
+ const vault = JSON.parse(readFileSync7(file, "utf8"));
21651
+ for (const [provider, entry] of Object.entries(vault)) {
21652
+ if (entry !== null && typeof entry === "object" && Object.keys(entry).length > 0) {
21653
+ localKeys.add(provider);
21654
+ }
21655
+ }
21656
+ } catch {
21657
+ }
21658
+ return { signedIn, localKeys };
21659
+ }
20781
21660
  function truncate(text, width) {
20782
21661
  if (text.length <= width) return text;
20783
21662
  if (width <= 1) return text.slice(0, width);
@@ -20786,7 +21665,7 @@ function truncate(text, width) {
20786
21665
  function tabPill(theme, text) {
20787
21666
  const accent = theme.colors["accent"] ?? "#f7a23f";
20788
21667
  try {
20789
- return chalk.bgHex(accent).hex("#1a120a").bold(` ${text} `);
21668
+ return chalk2.bgHex(accent).hex("#1a120a").bold(` ${text} `);
20790
21669
  } catch {
20791
21670
  return theme.color("accent", text);
20792
21671
  }
@@ -20895,16 +21774,16 @@ function nextValue(values, current, direction) {
20895
21774
  return values[(at + direction + values.length) % values.length];
20896
21775
  }
20897
21776
  function SettingsBoard({ theme, items, onClose }) {
20898
- const [query, setQuery] = useState4("");
20899
- const [selectedIndex, setSelectedIndex] = useState4(0);
21777
+ const [query, setQuery] = useState5("");
21778
+ const [selectedIndex, setSelectedIndex] = useState5(0);
20900
21779
  const filtered = useMemo(
20901
21780
  () => items.filter(
20902
21781
  (item) => matchesSearchQuery(`${item.label} ${item.description} ${item.value} ${item.values.join(" ")}`, query)
20903
21782
  ),
20904
21783
  [items, query]
20905
21784
  );
20906
- useEffect3(() => setSelectedIndex(0), [query]);
20907
- useEffect3(() => {
21785
+ useEffect4(() => setSelectedIndex(0), [query]);
21786
+ useEffect4(() => {
20908
21787
  if (selectedIndex >= filtered.length) setSelectedIndex(0);
20909
21788
  }, [filtered.length, selectedIndex]);
20910
21789
  const cycle = (direction) => {
@@ -20954,12 +21833,20 @@ function modelMatches(model, query) {
20954
21833
  return matchesSearchQuery(`${model.provider} ${model.id} ${model.name ?? ""}`, query);
20955
21834
  }
20956
21835
  function ModelBoard({ theme, models, onClose, onSelect, search }) {
20957
- const [query, setQuery] = useState4(search ?? "");
20958
- const [selectedIndex, setSelectedIndex] = useState4(0);
20959
- useEffect3(() => setQuery(search ?? ""), [search]);
20960
- const filtered = useMemo(() => models.filter((model) => modelMatches(model, query)), [models, query]);
20961
- useEffect3(() => setSelectedIndex(0), [query]);
20962
- useEffect3(() => {
21836
+ const [query, setQuery] = useState5(search ?? "");
21837
+ const [selectedIndex, setSelectedIndex] = useState5(0);
21838
+ useEffect4(() => setQuery(search ?? ""), [search]);
21839
+ const serverTier = useMemo(readServerTierInfo, []);
21840
+ const isServerTier = (provider) => serverTier.signedIn && isGatewayEligibleProvider(provider) && !serverTier.localKeys.has(provider);
21841
+ const available = useMemo(
21842
+ () => models.filter(
21843
+ (model) => serverTier.localKeys.has(model.provider) || isServerTier(model.provider)
21844
+ ),
21845
+ [models, serverTier]
21846
+ );
21847
+ const filtered = useMemo(() => available.filter((model) => modelMatches(model, query)), [available, query]);
21848
+ useEffect4(() => setSelectedIndex(0), [query]);
21849
+ useEffect4(() => {
20963
21850
  if (selectedIndex >= filtered.length) setSelectedIndex(0);
20964
21851
  }, [filtered.length, selectedIndex]);
20965
21852
  useInput2((input, key) => {
@@ -20977,13 +21864,16 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
20977
21864
  });
20978
21865
  const { start, budget, below } = windowAround(filtered.length, selectedIndex);
20979
21866
  const visible = filtered.slice(start, start + budget);
20980
- const list = filtered.length === 0 ? /* @__PURE__ */ jsx9(Text9, { children: theme.muted("No models matched the search.") }) : /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: visible.map((model, index) => {
21867
+ const list = filtered.length === 0 ? /* @__PURE__ */ jsx9(Text9, { children: theme.muted(
21868
+ available.length === 0 ? "No models available \u2014 sign in with a provider key or run /login to use the server tier." : "No models matched the search."
21869
+ ) }) : /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: visible.map((model, index) => {
20981
21870
  const isSelected = start + index === selectedIndex;
20982
21871
  return /* @__PURE__ */ jsxs8(Box9, { children: [
20983
21872
  /* @__PURE__ */ jsx9(Box9, { width: 2, children: /* @__PURE__ */ jsx9(Text9, { children: isSelected ? theme.color("accent", "\u203A") : " " }) }),
20984
- /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: theme.color("text", `${model.provider}/${model.id}`) }),
20985
- model.name ? /* @__PURE__ */ jsx9(Text9, { children: theme.dim(` ${model.name}`) }) : null
20986
- ] }, `${model.provider}/${model.id}`);
21873
+ /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: theme.color("text", model.id) }),
21874
+ model.name ? /* @__PURE__ */ jsx9(Text9, { children: theme.dim(` ${model.name}`) }) : null,
21875
+ isServerTier(model.provider) ? /* @__PURE__ */ jsx9(Text9, { children: theme.color("accent", " \u2B21 server") }) : null
21876
+ ] }, model.id);
20987
21877
  }) });
20988
21878
  return /* @__PURE__ */ jsx9(
20989
21879
  BoardShell,
@@ -21070,13 +21960,13 @@ var SETTING_KEYS = Object.freeze(
21070
21960
  );
21071
21961
 
21072
21962
  // src/settings/manager.ts
21073
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21074
- import { dirname as dirname5, join as join11 } from "node:path";
21963
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
21964
+ import { dirname as dirname5, join as join13 } from "node:path";
21075
21965
  function loadTier(filePath) {
21076
21966
  if (!existsSync3(filePath)) return {};
21077
21967
  let raw;
21078
21968
  try {
21079
- raw = readFileSync6(filePath, "utf-8");
21969
+ raw = readFileSync8(filePath, "utf-8");
21080
21970
  } catch {
21081
21971
  return {};
21082
21972
  }
@@ -21127,7 +22017,7 @@ var PreferenceStore = class _PreferenceStore {
21127
22017
  static fromWorkspace(workspace, cwd = process.cwd()) {
21128
22018
  return new _PreferenceStore({
21129
22019
  global: workspace.settingsPath,
21130
- project: join11(cwd, BRAND.profileDirName, "settings.json")
22020
+ project: join13(cwd, BRAND.profileDirName, "settings.json")
21131
22021
  });
21132
22022
  }
21133
22023
  /** The two resolved file locations this store reads and writes. */
@@ -21196,11 +22086,11 @@ var PreferenceStore = class _PreferenceStore {
21196
22086
  }
21197
22087
  };
21198
22088
  function stripUndefined(tier) {
21199
- const out = {};
22089
+ const out2 = {};
21200
22090
  for (const [key, value] of Object.entries(tier)) {
21201
- if (value !== void 0) out[key] = value;
22091
+ if (value !== void 0) out2[key] = value;
21202
22092
  }
21203
- return out;
22093
+ return out2;
21204
22094
  }
21205
22095
 
21206
22096
  // src/console/overlays/pickers.tsx
@@ -21243,38 +22133,12 @@ function listModels(services) {
21243
22133
  return [];
21244
22134
  }
21245
22135
  }
21246
- function useAuthenticatedProviders(services) {
21247
- const [authed, setAuthed] = useState5(null);
21248
- useEffect4(() => {
21249
- if (!services) return;
21250
- let live = true;
21251
- void (async () => {
21252
- const found = /* @__PURE__ */ new Set();
21253
- try {
21254
- for (const provider of services.listLoginProviders()) {
21255
- try {
21256
- if ((await services.vault.listAccounts(provider.id)).length > 0) {
21257
- found.add(provider.id);
21258
- }
21259
- } catch {
21260
- }
21261
- }
21262
- } catch {
21263
- }
21264
- if (live) setAuthed(found);
21265
- })();
21266
- return () => {
21267
- live = false;
21268
- };
21269
- }, []);
21270
- return authed;
21271
- }
21272
22136
  function ModelPicker(props) {
21273
22137
  const { services, theme, closeModal } = props;
21274
22138
  const authed = useAuthenticatedProviders(services);
21275
22139
  if (!services) return null;
21276
22140
  const all = listModels(services);
21277
- const models = authed && authed.size > 0 ? all.filter((model) => authed.has(model.provider)) : all;
22141
+ const models = authed === null ? all : all.filter((model) => authed.has(model.provider));
21278
22142
  return /* @__PURE__ */ jsx10(
21279
22143
  ModelBoard,
21280
22144
  {
@@ -21294,15 +22158,15 @@ function ModelPicker(props) {
21294
22158
  function readScopedPayload(payload) {
21295
22159
  if (payload === null || typeof payload !== "object") return {};
21296
22160
  const record = payload;
21297
- const out = {};
21298
- if (record.focus === "summary") out.focus = "summary";
21299
- if (record.intent === "reset") out.intent = "reset";
21300
- return out;
22161
+ const out2 = {};
22162
+ if (record.focus === "summary") out2.focus = "summary";
22163
+ if (record.intent === "reset") out2.intent = "reset";
22164
+ return out2;
21301
22165
  }
21302
22166
  function ScopedModelPicker(props) {
21303
22167
  const { services, dispatch: dispatch2, closeModal, modal } = props;
21304
22168
  const payload = readScopedPayload(modal.payload);
21305
- useEffect4(() => {
22169
+ useEffect5(() => {
21306
22170
  if (!services || payload.intent !== "reset") return;
21307
22171
  try {
21308
22172
  services.settings.set("enabledModels", []);
@@ -21358,7 +22222,7 @@ function currentScheme(services) {
21358
22222
  }
21359
22223
  function ThemePicker(props) {
21360
22224
  const { services, dispatch: dispatch2, closeModal } = props;
21361
- const [originalScheme] = useState5(
22225
+ const [originalScheme] = useState6(
21362
22226
  () => services ? currentScheme(services) : null
21363
22227
  );
21364
22228
  if (!services) return null;
@@ -21389,7 +22253,7 @@ function SettingsPicker(props) {
21389
22253
  const { services, theme, dispatch: dispatch2, closeModal } = props;
21390
22254
  if (!services) return null;
21391
22255
  const settings = services.settings;
21392
- const [, setVersion] = useState5(0);
22256
+ const [, setVersion] = useState6(0);
21393
22257
  const bump = () => setVersion((n) => n + 1);
21394
22258
  const onOff = (flag) => flag ? "on" : "off";
21395
22259
  const toggleRow = (id, key, label2, description, live) => ({
@@ -21571,7 +22435,7 @@ function PickerOverlays(props) {
21571
22435
  }
21572
22436
 
21573
22437
  // src/console/overlays/sessions.tsx
21574
- import { useEffect as useEffect5, useState as useState6 } from "indusagi/react-host";
22438
+ import { useEffect as useEffect6, useState as useState7 } from "indusagi/react-host";
21575
22439
  import {
21576
22440
  SessionDialog,
21577
22441
  TreeDialog,
@@ -21593,8 +22457,8 @@ function toSessionInfo(row) {
21593
22457
  }
21594
22458
  function SessionPicker(props) {
21595
22459
  const { services, closeModal } = props;
21596
- const [sessions, setSessions] = useState6([]);
21597
- useEffect5(() => {
22460
+ const [sessions, setSessions] = useState7([]);
22461
+ useEffect6(() => {
21598
22462
  let live = true;
21599
22463
  void (async () => {
21600
22464
  try {
@@ -21654,8 +22518,8 @@ function SessionPicker(props) {
21654
22518
  }
21655
22519
  function BranchTree(props) {
21656
22520
  const { services, closeModal } = props;
21657
- const [items, setItems] = useState6([]);
21658
- useEffect5(() => {
22521
+ const [items, setItems] = useState7([]);
22522
+ useEffect6(() => {
21659
22523
  let live = true;
21660
22524
  void (async () => {
21661
22525
  try {
@@ -21706,8 +22570,8 @@ function BranchTree(props) {
21706
22570
  }
21707
22571
  function PriorTurns(props) {
21708
22572
  const { services, closeModal } = props;
21709
- const [items, setItems] = useState6([]);
21710
- useEffect5(() => {
22573
+ const [items, setItems] = useState7([]);
22574
+ useEffect6(() => {
21711
22575
  let live = true;
21712
22576
  void (async () => {
21713
22577
  try {
@@ -21805,34 +22669,40 @@ function approvalQueueReducer(state, event) {
21805
22669
  function activeApproval(state) {
21806
22670
  return state.entries[0];
21807
22671
  }
22672
+ function modeReleasesApprovals(mode) {
22673
+ return mode === "bypass" || mode === "bypassPermissions";
22674
+ }
22675
+
22676
+ // src/console/components/TerminalConsole.tsx
22677
+ init_conductor2();
21808
22678
 
21809
22679
  // src/console/startup.ts
21810
22680
  init_briefing();
21811
- import { existsSync as existsSync4, readFileSync as readFileSync7 } from "node:fs";
21812
- import { join as join12 } from "node:path";
22681
+ import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
22682
+ import { join as join14 } from "node:path";
21813
22683
  var PROFILE_DIR = ".indusagi";
21814
22684
  var CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md"];
21815
22685
  function skillRoots(cwd, home) {
21816
22686
  return [
21817
- { dir: join12(cwd, PROFILE_DIR, "skills"), origin: "project" },
21818
- { dir: join12(home, PROFILE_DIR, "skills"), origin: "user" }
22687
+ { dir: join14(cwd, PROFILE_DIR, "skills"), origin: "project" },
22688
+ { dir: join14(home, PROFILE_DIR, "skills"), origin: "user" }
21819
22689
  ];
21820
22690
  }
21821
22691
  function macroRoots(cwd, home) {
21822
22692
  return [
21823
- { dir: join12(cwd, PROFILE_DIR, "commands"), origin: "project", label: "project" },
21824
- { dir: join12(home, PROFILE_DIR, "commands"), origin: "user", label: "user" }
22693
+ { dir: join14(cwd, PROFILE_DIR, "commands"), origin: "project", label: "project" },
22694
+ { dir: join14(home, PROFILE_DIR, "commands"), origin: "user", label: "user" }
21825
22695
  ];
21826
22696
  }
21827
- function shortenPath2(path3, cwd, home) {
21828
- if (cwd.length > 0 && path3.startsWith(cwd)) {
21829
- const rest = path3.slice(cwd.length).replace(/^[/\\]+/, "");
22697
+ function shortenPath2(path4, cwd, home) {
22698
+ if (cwd.length > 0 && path4.startsWith(cwd)) {
22699
+ const rest = path4.slice(cwd.length).replace(/^[/\\]+/, "");
21830
22700
  return rest.length > 0 ? `./${rest}` : ".";
21831
22701
  }
21832
- if (home.length > 0 && path3.startsWith(home)) {
21833
- return `~${path3.slice(home.length)}`;
22702
+ if (home.length > 0 && path4.startsWith(home)) {
22703
+ return `~${path4.slice(home.length)}`;
21834
22704
  }
21835
- return path3;
22705
+ return path4;
21836
22706
  }
21837
22707
  var MAX_SECTION_LINES = 6;
21838
22708
  function capLines(lines) {
@@ -21847,17 +22717,17 @@ function section(title, lines) {
21847
22717
  }
21848
22718
  function gatherContext(cwd, home) {
21849
22719
  const seen = /* @__PURE__ */ new Set();
21850
- const out = [];
22720
+ const out2 = [];
21851
22721
  for (const root of [cwd, home]) {
21852
22722
  if (root.length === 0) continue;
21853
22723
  for (const name of CONTEXT_FILES) {
21854
- const path3 = join12(root, name);
21855
- if (seen.has(path3)) continue;
21856
- seen.add(path3);
21857
- if (existsSync4(path3)) out.push(shortenPath2(path3, cwd, home));
22724
+ const path4 = join14(root, name);
22725
+ if (seen.has(path4)) continue;
22726
+ seen.add(path4);
22727
+ if (existsSync4(path4)) out2.push(shortenPath2(path4, cwd, home));
21858
22728
  }
21859
22729
  }
21860
- return out;
22730
+ return out2;
21861
22731
  }
21862
22732
  function gatherSkills(cwd, home) {
21863
22733
  let cards = [];
@@ -21933,7 +22803,7 @@ function gatherChangelog(inputs) {
21933
22803
  if (lastSeenVersion !== void 0 && lastSeenVersion === version) return none2;
21934
22804
  let raw;
21935
22805
  try {
21936
- raw = readFileSync7(changelogPath, "utf-8");
22806
+ raw = readFileSync9(changelogPath, "utf-8");
21937
22807
  } catch {
21938
22808
  return none2;
21939
22809
  }
@@ -21969,11 +22839,11 @@ __export(kit_exports, {
21969
22839
  });
21970
22840
 
21971
22841
  // src/kit/tool-fetch.ts
21972
- import { join as join13 } from "node:path";
22842
+ import { join as join15 } from "node:path";
21973
22843
  var KIT_USER_AGENT = "indusagi-kit-provisioner/1.0";
21974
22844
  function resolveManagedBinaryPath(binDir, bin, platform = process.platform) {
21975
22845
  const fileName = platform === "win32" ? `${bin}.exe` : bin;
21976
- return join13(binDir, fileName);
22846
+ return join15(binDir, fileName);
21977
22847
  }
21978
22848
  function pickReleaseAsset(descriptor, release) {
21979
22849
  for (const asset of release.assets) {
@@ -22055,9 +22925,9 @@ function buildShellCommand(args) {
22055
22925
 
22056
22926
  // src/kit/clipboard-image.ts
22057
22927
  import { spawnSync as spawnSync2 } from "node:child_process";
22058
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
22928
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
22059
22929
  import { tmpdir as tmpdir2 } from "node:os";
22060
- import { join as join14 } from "node:path";
22930
+ import { join as join16 } from "node:path";
22061
22931
  var READ_TIMEOUT_MS = 3e3;
22062
22932
  var MAX_BUFFER_BYTES = 50 * 1024 * 1024;
22063
22933
  function captureStdout(command, args) {
@@ -22066,9 +22936,9 @@ function captureStdout(command, args) {
22066
22936
  maxBuffer: MAX_BUFFER_BYTES
22067
22937
  });
22068
22938
  if (result.error || result.status !== 0) return null;
22069
- const out = result.stdout;
22070
- if (!Buffer.isBuffer(out) || out.length === 0) return null;
22071
- return out;
22939
+ const out2 = result.stdout;
22940
+ if (!Buffer.isBuffer(out2) || out2.length === 0) return null;
22941
+ return out2;
22072
22942
  }
22073
22943
  function captureViaOsascript() {
22074
22944
  const result = spawnSync2(
@@ -22100,10 +22970,10 @@ function captureViaOsascript() {
22100
22970
  { timeout: READ_TIMEOUT_MS, encoding: "utf8" }
22101
22971
  );
22102
22972
  if (result.error || result.status !== 0) return null;
22103
- const path3 = String(result.stdout ?? "").trim();
22104
- if (path3.length === 0) return null;
22973
+ const path4 = String(result.stdout ?? "").trim();
22974
+ if (path4.length === 0) return null;
22105
22975
  try {
22106
- const bytes = readFileSync8(path3);
22976
+ const bytes = readFileSync10(path4);
22107
22977
  return bytes.length > 0 ? bytes : null;
22108
22978
  } catch {
22109
22979
  return null;
@@ -22130,10 +23000,10 @@ function readClipboardImage(options = {}) {
22130
23000
  if (bytes === null) return null;
22131
23001
  const dir = options.dir ?? tmpdir2();
22132
23002
  const name = `indus-clipboard-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.png`;
22133
- const path3 = join14(dir, name);
23003
+ const path4 = join16(dir, name);
22134
23004
  try {
22135
- writeFileSync5(path3, bytes);
22136
- return path3;
23005
+ writeFileSync5(path4, bytes);
23006
+ return path4;
22137
23007
  } catch {
22138
23008
  return null;
22139
23009
  }
@@ -22141,9 +23011,9 @@ function readClipboardImage(options = {}) {
22141
23011
 
22142
23012
  // src/kit/external-editor.ts
22143
23013
  import { spawnSync as spawnSync3 } from "node:child_process";
22144
- import { readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
23014
+ import { readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
22145
23015
  import { tmpdir as tmpdir3 } from "node:os";
22146
- import { join as join15 } from "node:path";
23016
+ import { join as join17 } from "node:path";
22147
23017
  var FALLBACK_EDITOR = "vi";
22148
23018
  function resolveEditor(env) {
22149
23019
  const configured = env.VISUAL || env.EDITOR || FALLBACK_EDITOR;
@@ -22154,18 +23024,18 @@ function openInExternalEditor(buffer, options = {}) {
22154
23024
  const env = options.env ?? process.env;
22155
23025
  const dir = options.dir ?? tmpdir3();
22156
23026
  const name = `indus-editor-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.md`;
22157
- const path3 = join15(dir, name);
23027
+ const path4 = join17(dir, name);
22158
23028
  const [editor, ...editorArgs] = resolveEditor(env);
22159
23029
  try {
22160
- writeFileSync6(path3, buffer, "utf8");
22161
- const result = spawnSync3(editor, [...editorArgs, path3], { stdio: "inherit" });
23030
+ writeFileSync6(path4, buffer, "utf8");
23031
+ const result = spawnSync3(editor, [...editorArgs, path4], { stdio: "inherit" });
22162
23032
  if (result.error || result.status !== 0) return null;
22163
- return readFileSync9(path3, "utf8").replace(/\n$/, "");
23033
+ return readFileSync11(path4, "utf8").replace(/\n$/, "");
22164
23034
  } catch {
22165
23035
  return null;
22166
23036
  } finally {
22167
23037
  try {
22168
- unlinkSync(path3);
23038
+ unlinkSync(path4);
22169
23039
  } catch {
22170
23040
  }
22171
23041
  }
@@ -22181,16 +23051,16 @@ var CHROME_ROWS = 14;
22181
23051
  var MODAL_CHROME_ROWS = 24;
22182
23052
  var MIN_VISIBLE_ROWS = 8;
22183
23053
  var FALLBACK_CONTEXT_WINDOW = 2e5;
22184
- var PASTE_BURST_MS = 12;
23054
+ var PASTE_BURST_MS = PASTE_BURST_GAP_MS;
22185
23055
  var BRACKETED_PASTE = /\x1b?\[20[01]~/g;
22186
23056
  function readBranch() {
22187
23057
  try {
22188
- const out = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
23058
+ const out2 = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
22189
23059
  cwd: process.cwd(),
22190
23060
  encoding: "utf8",
22191
23061
  stdio: ["ignore", "pipe", "ignore"]
22192
23062
  }).trim();
22193
- return out.length > 0 ? out : null;
23063
+ return out2.length > 0 ? out2 : null;
22194
23064
  } catch {
22195
23065
  return null;
22196
23066
  }
@@ -22298,6 +23168,19 @@ function renderToolUpdateText(details) {
22298
23168
  const text = details.text;
22299
23169
  return typeof text === "string" ? text : void 0;
22300
23170
  }
23171
+ function permissionModeStatusText(mode) {
23172
+ switch (mode) {
23173
+ case "plan":
23174
+ return "Permission mode: PLAN \u2014 read-only research; mutating tools are blocked. Shift+Tab to cycle.";
23175
+ case "bypass":
23176
+ case "bypassPermissions":
23177
+ return "Permission mode: BYPASS \u2014 every tool auto-allowed (unsafe). Shift+Tab to cycle.";
23178
+ case "acceptEdits":
23179
+ return "Permission mode: ACCEPT-EDITS \u2014 edits auto-approved. Shift+Tab to cycle.";
23180
+ default:
23181
+ return "Permission mode: DEFAULT \u2014 tools prompt as usual. Shift+Tab to cycle.";
23182
+ }
23183
+ }
22301
23184
  function TerminalConsole(props) {
22302
23185
  const { conductor, theme, slash, verbose, onExit, initialInput, services } = props;
22303
23186
  const adapter = theme.adapter;
@@ -22308,22 +23191,23 @@ function TerminalConsole(props) {
22308
23191
  const chord = useRef3(NO_CHORD);
22309
23192
  const chordTimer = useRef3(null);
22310
23193
  const paste = useRef3(EMPTY_PASTE_VAULT);
23194
+ const activePaste = useRef3(null);
22311
23195
  const burst = useRef3("");
22312
23196
  const burstTimer = useRef3(null);
22313
23197
  const liveAnswer = useRef3(null);
22314
23198
  const liveReason = useRef3(null);
22315
23199
  const rowSeed = useRef3(0);
22316
23200
  const ctrlCAt = useRef3(0);
22317
- const [highlight, setHighlight] = useState7(0);
22318
- const [toolExecutions, setToolExecutions] = useState7({});
22319
- const [taskTokens, setTaskTokens] = useState7({});
22320
- const [subagentActivity, setSubagentActivity] = useState7({});
22321
- const [expandTools, setExpandTools] = useState7(false);
22322
- const [agentsOpen, setAgentsOpen] = useState7(false);
22323
- const [terminalRows, setTerminalRows] = useState7(
23201
+ const [highlight, setHighlight] = useState8(0);
23202
+ const [toolExecutions, setToolExecutions] = useState8({});
23203
+ const [taskTokens, setTaskTokens] = useState8({});
23204
+ const [subagentActivity, setSubagentActivity] = useState8({});
23205
+ const [expandTools, setExpandTools] = useState8(false);
23206
+ const [agentsOpen, setAgentsOpen] = useState8(false);
23207
+ const [terminalRows, setTerminalRows] = useState8(
22324
23208
  () => process.stdout.rows ?? FALLBACK_TERMINAL_ROWS
22325
23209
  );
22326
- const [, forceTick] = useState7(0);
23210
+ const [, forceTick] = useState8(0);
22327
23211
  const pulse = useCallback(() => forceTick((n) => n + 1), []);
22328
23212
  const dirReader = useMemo2(() => createDirReader(process.cwd()), []);
22329
23213
  const [approvalQueue, dispatchApproval] = useReducer(
@@ -22349,7 +23233,11 @@ function TerminalConsole(props) {
22349
23233
  (toolName, input, opts) => new Promise((resolve8) => {
22350
23234
  approvalSeed.current += 1;
22351
23235
  const id = `ap_${approvalSeed.current}`;
22352
- const request = { toolName, input };
23236
+ const request = {
23237
+ toolName,
23238
+ input,
23239
+ suggestions: allowAlwaysRuleStrings(toolName, input)
23240
+ };
22353
23241
  approvalResolvers.current.set(id, resolve8);
22354
23242
  const signal = opts.signal;
22355
23243
  if (signal !== void 0) {
@@ -22368,7 +23256,7 @@ function TerminalConsole(props) {
22368
23256
  }),
22369
23257
  [settleApproval]
22370
23258
  );
22371
- useEffect6(() => {
23259
+ useEffect7(() => {
22372
23260
  conductor.setApprovalResolver(requestApproval);
22373
23261
  return () => {
22374
23262
  conductor.setApprovalResolver(void 0);
@@ -22378,7 +23266,7 @@ function TerminalConsole(props) {
22378
23266
  const head = activeApproval(approvalQueue);
22379
23267
  const headId = head?.id;
22380
23268
  const approvalModalOpen = useRef3(false);
22381
- useEffect6(() => {
23269
+ useEffect7(() => {
22382
23270
  if (head === void 0) {
22383
23271
  if (approvalModalOpen.current) {
22384
23272
  approvalModalOpen.current = false;
@@ -22396,7 +23284,17 @@ function TerminalConsole(props) {
22396
23284
  }
22397
23285
  });
22398
23286
  }, [headId, settleApproval]);
22399
- useEffect6(() => {
23287
+ const cyclePermission = useCallback(() => {
23288
+ const now = conductor.cyclePermissionMode();
23289
+ let text = permissionModeStatusText(now);
23290
+ if (modeReleasesApprovals(now) && approvalResolvers.current.size > 0) {
23291
+ const parked = [...approvalResolvers.current.keys()];
23292
+ for (const id of parked) settleApproval(id, "allow-once");
23293
+ text += ` Released ${parked.length} pending approval${parked.length === 1 ? "" : "s"}.`;
23294
+ }
23295
+ dispatch2({ type: "status:set", status: { kind: "info", text } });
23296
+ }, [conductor, settleApproval]);
23297
+ useEffect7(() => {
22400
23298
  const off = conductor.subscribe((signal) => {
22401
23299
  switch (signal.kind) {
22402
23300
  case "prompt": {
@@ -22502,7 +23400,30 @@ function TerminalConsole(props) {
22502
23400
  break;
22503
23401
  }
22504
23402
  case "compacted": {
22505
- dispatch2({ type: "status:set", status: { kind: "info", text: "Conversation condensed." } });
23403
+ if (signal.manual) {
23404
+ const carried = [];
23405
+ for (const message of conductor.messages()) {
23406
+ const content = message.content;
23407
+ if (typeof content === "string" && content.startsWith(RESTORED_FILE_PREFIX)) {
23408
+ const firstLine = content.slice(RESTORED_FILE_PREFIX.length).split("\n", 1)[0];
23409
+ if (firstLine) carried.push(firstLine.trim());
23410
+ }
23411
+ }
23412
+ setCompactNote(carried);
23413
+ visibleFrom.current = conductor.messages().length;
23414
+ liveAnswer.current = null;
23415
+ liveReason.current = null;
23416
+ answerText.current = "";
23417
+ reasonText.current = "";
23418
+ dispatch2({ type: "rows:set", rows: [] });
23419
+ }
23420
+ dispatch2({
23421
+ type: "status:set",
23422
+ status: {
23423
+ kind: "info",
23424
+ text: signal.manual ? "Conversation condensed." : "Auto-compacted older history to stay within the context window (automatic \u2014 you didn't run /compact)."
23425
+ }
23426
+ });
22506
23427
  break;
22507
23428
  }
22508
23429
  case "persisted": {
@@ -22510,6 +23431,10 @@ function TerminalConsole(props) {
22510
23431
  break;
22511
23432
  }
22512
23433
  case "fault": {
23434
+ if (signal.transient) {
23435
+ dispatch2({ type: "status:set", status: { kind: "warning", text: signal.fault.message } });
23436
+ break;
23437
+ }
22513
23438
  liveAnswer.current = null;
22514
23439
  liveReason.current = null;
22515
23440
  dispatch2({ type: "busy:set", busy: false });
@@ -22529,12 +23454,14 @@ function TerminalConsole(props) {
22529
23454
  }, [conductor]);
22530
23455
  const answerText = useRef3("");
22531
23456
  const reasonText = useRef3("");
23457
+ const visibleFrom = useRef3(0);
23458
+ const [compactNote, setCompactNote] = useState8(null);
22532
23459
  const completion = useMemo2(
22533
23460
  () => completeAt(state.buffer, state.caret, slash, dirReader),
22534
23461
  [state.buffer, state.caret, slash, dirReader]
22535
23462
  );
22536
23463
  const suggestions = completion.suggestions;
22537
- useEffect6(() => {
23464
+ useEffect7(() => {
22538
23465
  if (highlight >= suggestions.length) setHighlight(0);
22539
23466
  }, [suggestions.length, highlight]);
22540
23467
  const submit = useCallback(
@@ -22558,10 +23485,10 @@ function TerminalConsole(props) {
22558
23485
  text: `Shell exited ${outcome.exitCode}.`
22559
23486
  }
22560
23487
  });
22561
- }).catch((err) => {
23488
+ }).catch((err2) => {
22562
23489
  dispatch2({
22563
23490
  type: "status:set",
22564
- status: { kind: "error", text: err instanceof Error ? err.message : String(err) }
23491
+ status: { kind: "error", text: err2 instanceof Error ? err2.message : String(err2) }
22565
23492
  });
22566
23493
  });
22567
23494
  return;
@@ -22599,15 +23526,23 @@ function TerminalConsole(props) {
22599
23526
  const id = freshRowId(rowSeed.current++);
22600
23527
  dispatch2({ type: "rows:append", row: { id, kind: "prompt", text } });
22601
23528
  dispatch2({ type: "busy:set", busy: true });
23529
+ if (conductor.snapshot().phase === "condensing") {
23530
+ dispatch2({
23531
+ type: "status:set",
23532
+ status: { kind: "info", text: "Queued message for after compaction." }
23533
+ });
23534
+ }
23535
+ setCompactNote(null);
23536
+ activePaste.current = null;
22602
23537
  answerText.current = "";
22603
23538
  reasonText.current = "";
22604
23539
  try {
22605
23540
  await conductor.submit(text);
22606
- } catch (err) {
23541
+ } catch (err2) {
22607
23542
  dispatch2({ type: "busy:set", busy: false });
22608
23543
  dispatch2({
22609
23544
  type: "status:set",
22610
- status: { kind: "error", text: err instanceof Error ? err.message : String(err) }
23545
+ status: { kind: "error", text: err2 instanceof Error ? err2.message : String(err2) }
22611
23546
  });
22612
23547
  }
22613
23548
  pulse();
@@ -22623,12 +23558,29 @@ function TerminalConsole(props) {
22623
23558
  const text = burst.current;
22624
23559
  if (text.length === 0) return;
22625
23560
  burst.current = "";
22626
- if (text.length > 1 && classifyPaste(text) === "coalesce") {
23561
+ const now = Date.now();
23562
+ const active = activePaste.current;
23563
+ if (active !== null && now <= active.until && text.length > 1 && pasteLines(paste.current, active.ordinal) < PASTE_MARKER_LINE_CAP) {
23564
+ const captured = appendToPaste(active.ordinal, text, paste.current);
23565
+ paste.current = captured.vault;
23566
+ dispatch2({ type: "buffer:replaceSpan", find: active.marker, replaceWith: captured.marker });
23567
+ activePaste.current = {
23568
+ ordinal: active.ordinal,
23569
+ marker: captured.marker,
23570
+ until: now + PASTE_CONTINUATION_MS
23571
+ };
23572
+ } else if (text.length > 1 && (classifyPaste(text) === "coalesce" || active !== null && now <= active.until)) {
22627
23573
  const captured = coalescePaste(text, paste.current);
22628
23574
  paste.current = captured.vault;
22629
23575
  dispatch2({ type: "buffer:insert", text: captured.marker });
23576
+ activePaste.current = {
23577
+ ordinal: captured.vault.minted,
23578
+ marker: captured.marker,
23579
+ until: now + PASTE_CONTINUATION_MS
23580
+ };
22630
23581
  } else {
22631
23582
  dispatch2({ type: "buffer:insert", text });
23583
+ activePaste.current = null;
22632
23584
  }
22633
23585
  setHighlight(0);
22634
23586
  }, []);
@@ -22644,14 +23596,14 @@ function TerminalConsole(props) {
22644
23596
  chordTimer.current = null;
22645
23597
  }
22646
23598
  }, []);
22647
- useEffect6(
23599
+ useEffect7(
22648
23600
  () => () => {
22649
23601
  if (chordTimer.current !== null) clearTimeout(chordTimer.current);
22650
23602
  if (burstTimer.current !== null) clearTimeout(burstTimer.current);
22651
23603
  },
22652
23604
  []
22653
23605
  );
22654
- useEffect6(() => {
23606
+ useEffect7(() => {
22655
23607
  const onResize = () => setTerminalRows(process.stdout.rows ?? FALLBACK_TERMINAL_ROWS);
22656
23608
  process.stdout.on("resize", onResize);
22657
23609
  return () => {
@@ -22659,7 +23611,7 @@ function TerminalConsole(props) {
22659
23611
  };
22660
23612
  }, []);
22661
23613
  const titleName = conductor.sessionName();
22662
- useEffect6(() => {
23614
+ useEffect7(() => {
22663
23615
  writeTerminalTitle(titleName && titleName.length > 0 ? titleName : "indus console");
22664
23616
  }, [titleName]);
22665
23617
  const requestExit = useCallback(() => {
@@ -22785,7 +23737,7 @@ function TerminalConsole(props) {
22785
23737
  }
22786
23738
  } else if (state.modal.kind !== "none") {
22787
23739
  dispatch2({ type: "modal:close" });
22788
- } else if (state.busy) {
23740
+ } else if (state.busy || conductor.snapshot().phase === "condensing") {
22789
23741
  denyAllApprovals();
22790
23742
  conductor.abort();
22791
23743
  } else if (suggestions.length > 0) {
@@ -22821,12 +23773,9 @@ function TerminalConsole(props) {
22821
23773
  dispatch2({ type: "status:set", status: { kind: "info", text: `Reasoning effort: ${level}.` } });
22822
23774
  return;
22823
23775
  }
22824
- case "mode:cyclePermission": {
22825
- const now = conductor.cyclePermissionMode();
22826
- const text = now === "plan" ? "Permission mode: PLAN \u2014 read-only research; mutating tools are blocked. Shift+Tab to cycle." : now === "bypass" || now === "bypassPermissions" ? "Permission mode: BYPASS \u2014 every tool auto-allowed (unsafe). Shift+Tab to cycle." : now === "acceptEdits" ? "Permission mode: ACCEPT-EDITS \u2014 edits auto-approved. Shift+Tab to cycle." : "Permission mode: DEFAULT \u2014 tools prompt as usual. Shift+Tab to cycle.";
22827
- dispatch2({ type: "status:set", status: { kind: "info", text } });
23776
+ case "mode:cyclePermission":
23777
+ cyclePermission();
22828
23778
  return;
22829
- }
22830
23779
  case "view:toggleReasoning":
22831
23780
  dispatch2({ type: "toggle:reasoning" });
22832
23781
  return;
@@ -22839,11 +23788,25 @@ function TerminalConsole(props) {
22839
23788
  }
22840
23789
  return;
22841
23790
  }
23791
+ case "queue:steer": {
23792
+ if (conductor.steerNow()) {
23793
+ dispatch2({
23794
+ type: "status:set",
23795
+ status: { kind: "info", text: "Running your queued message now \u2014 current run stopped." }
23796
+ });
23797
+ } else {
23798
+ dispatch2({
23799
+ type: "status:set",
23800
+ status: { kind: "warning", text: "No queued message to run \u2014 type one first, then Ctrl+X." }
23801
+ });
23802
+ }
23803
+ return;
23804
+ }
22842
23805
  case "input:pasteImage": {
22843
- const path3 = readClipboardImage();
22844
- if (path3 !== null) {
23806
+ const path4 = readClipboardImage();
23807
+ if (path4 !== null) {
22845
23808
  const sep = state.buffer.length > 0 && !/\s$/.test(state.buffer) ? " " : "";
22846
- dispatch2({ type: "buffer:insert", text: `${sep}${path3}` });
23809
+ dispatch2({ type: "buffer:insert", text: `${sep}${path4}` });
22847
23810
  } else {
22848
23811
  dispatch2({ type: "status:set", status: { kind: "warning", text: "No image on the clipboard." } });
22849
23812
  }
@@ -22872,20 +23835,30 @@ function TerminalConsole(props) {
22872
23835
  return;
22873
23836
  }
22874
23837
  }, { isActive: state.modal.kind === "none" && !agentsOpen });
23838
+ useInput3((_rawInput, key) => {
23839
+ if (key.tab && key.shift) cyclePermission();
23840
+ }, { isActive: state.modal.kind === "approval" });
22875
23841
  const seeded = useRef3(false);
22876
- useEffect6(() => {
23842
+ useEffect7(() => {
22877
23843
  if (seeded.current) return;
22878
23844
  seeded.current = true;
23845
+ const opening = conductor.permissionMode();
23846
+ if (opening !== "default") {
23847
+ dispatch2({
23848
+ type: "status:set",
23849
+ status: { kind: "warning", text: permissionModeStatusText(opening) }
23850
+ });
23851
+ }
22879
23852
  if (initialInput && initialInput.trim().length > 0) {
22880
23853
  void runTurn(initialInput);
22881
23854
  }
22882
23855
  }, []);
22883
- const liveMessages = [...conductor.messages()];
23856
+ const allMessages = [...conductor.messages()];
23857
+ if (allMessages.length < visibleFrom.current) visibleFrom.current = 0;
23858
+ const liveMessages = allMessages.slice(visibleFrom.current);
22884
23859
  const agentRuns = deriveAgentRuns(liveMessages);
22885
23860
  const snapshot = projectSnapshot(conductor, liveMessages, services);
22886
23861
  const branch = useMemo2(() => readBranch(), []);
22887
- const modalOpen = state.modal.kind !== "none";
22888
- const maxItems = transcriptHeight(terminalRows, modalOpen, agentPanelRows(agentRuns));
22889
23862
  const pendingMessages = conductor.pendingInputs().map((entry, index) => ({
22890
23863
  id: `pending-${index}-${entry.mode}`,
22891
23864
  mode: entry.mode,
@@ -22895,12 +23868,21 @@ function TerminalConsole(props) {
22895
23868
  const SHOW_TOOL_ACTIVITY = false;
22896
23869
  const runningTools = Object.values(toolExecutions).some((t) => t.status === "running");
22897
23870
  const showTaskPanel = SHOW_TOOL_ACTIVITY && runningTools || pendingMessages.length > 0;
23871
+ const modalOpen = state.modal.kind !== "none";
23872
+ const streamingAnswerRows = liveAnswer.current !== null && answerText.current ? Math.min(6, answerText.current.split("\n").length + 1) : 0;
23873
+ const taskPanelRows = showTaskPanel ? 2 + Math.min(pendingMessages.length, 8) : 0;
23874
+ const workingIndicatorRows = state.busy ? 2 : 0;
23875
+ const maxItems = transcriptHeight(
23876
+ terminalRows,
23877
+ modalOpen,
23878
+ agentPanelRows(agentRuns) + streamingAnswerRows + taskPanelRows + workingIndicatorRows
23879
+ );
22898
23880
  const providerCount = countProviders(conductor);
22899
23881
  const lastSeenVersion = services?.settings.get("lastSeenVersion") ?? "";
22900
23882
  const startupMap = useMemo2(
22901
23883
  () => gatherStartup({
22902
23884
  cwd: process.cwd(),
22903
- home: homedir4(),
23885
+ home: homedir6(),
22904
23886
  version: VERSION,
22905
23887
  lastSeenVersion
22906
23888
  }),
@@ -22909,8 +23891,8 @@ function TerminalConsole(props) {
22909
23891
  const quietStartup = (services?.settings.get("quietStartup") ?? false) && !verbose;
22910
23892
  const sweep = (services?.settings.get("logoSweep") ?? false) && !(services?.settings.get("reducedMotion") ?? false) && Boolean(process.stdout.isTTY);
22911
23893
  const welcomeSeed = PINNED_WELCOME_SEED;
22912
- const [welcomeName, setWelcomeName] = useState7(void 0);
22913
- useEffect6(() => {
23894
+ const [welcomeName, setWelcomeName] = useState8(void 0);
23895
+ useEffect7(() => {
22914
23896
  if (!services) return;
22915
23897
  let live = true;
22916
23898
  const provider = services.settings.get("defaultProvider");
@@ -22922,6 +23904,12 @@ function TerminalConsole(props) {
22922
23904
  live = false;
22923
23905
  };
22924
23906
  }, [services]);
23907
+ const authedProviders = useAuthenticatedProviders(services, state.status);
23908
+ const boundModelId = conductor.snapshot().modelId;
23909
+ const boundProvider = boundModelId.includes("/") ? boundModelId.split("/")[0] : boundModelId;
23910
+ const modelAuthed = authedProviders === null || authedProviders.has(boundProvider);
23911
+ const bannerModelId = modelAuthed ? boundModelId : "no authenticated model \u2014 run /login";
23912
+ const statusBarSnapshot = modelAuthed || !snapshot.model ? snapshot : { ...snapshot, model: { ...snapshot.model, provider: "no model", id: "run /login" } };
22925
23913
  return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
22926
23914
  agentsOpen ? (
22927
23915
  // The drill-in agents view replaces the transcript while open; it owns
@@ -22941,7 +23929,7 @@ function TerminalConsole(props) {
22941
23929
  Banner,
22942
23930
  {
22943
23931
  theme: adapter,
22944
- modelId: conductor.snapshot().modelId,
23932
+ modelId: bannerModelId,
22945
23933
  workspace: process.cwd(),
22946
23934
  version: VERSION,
22947
23935
  verbose,
@@ -22955,6 +23943,23 @@ function TerminalConsole(props) {
22955
23943
  variantSeed: welcomeSeed
22956
23944
  }
22957
23945
  ),
23946
+ compactNote !== null ? /* @__PURE__ */ jsxs10(
23947
+ Box10,
23948
+ {
23949
+ flexDirection: "column",
23950
+ borderStyle: "round",
23951
+ borderColor: adapter.colors["borderMuted"],
23952
+ paddingX: 1,
23953
+ marginBottom: 1,
23954
+ children: [
23955
+ /* @__PURE__ */ jsx13(Text10, { children: adapter.color("accent", "\u25C6 Context condensed \u2014 fresh session, history summarized") }),
23956
+ compactNote.length > 0 ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
23957
+ /* @__PURE__ */ jsx13(Text10, { children: adapter.muted("Files carried into the new context:") }),
23958
+ compactNote.map((file, index) => /* @__PURE__ */ jsx13(Text10, { children: adapter.muted(` \xB7 ${file}`) }, index))
23959
+ ] }) : /* @__PURE__ */ jsx13(Text10, { children: adapter.muted("No files re-attached; the summary digest carries the history.") })
23960
+ ]
23961
+ }
23962
+ ) : null,
22958
23963
  /* @__PURE__ */ jsx13(
22959
23964
  MessageList,
22960
23965
  {
@@ -22978,7 +23983,14 @@ function TerminalConsole(props) {
22978
23983
  pendingMessages
22979
23984
  }
22980
23985
  ) : null,
22981
- /* @__PURE__ */ jsx13(WorkingIndicator, { theme: adapter, busy: state.busy, phase: conductor.snapshot().phase }),
23986
+ /* @__PURE__ */ jsx13(
23987
+ WorkingIndicator,
23988
+ {
23989
+ theme: adapter,
23990
+ busy: state.busy || conductor.snapshot().phase === "condensing",
23991
+ phase: conductor.snapshot().phase
23992
+ }
23993
+ ),
22982
23994
  /* @__PURE__ */ jsx13(
22983
23995
  Composer,
22984
23996
  {
@@ -22994,7 +24006,7 @@ function TerminalConsole(props) {
22994
24006
  StatusBar,
22995
24007
  {
22996
24008
  theme: adapter,
22997
- snapshot,
24009
+ snapshot: statusBarSnapshot,
22998
24010
  branch,
22999
24011
  providerCount,
23000
24012
  status: state.status,
@@ -23049,8 +24061,8 @@ __export(sessions_exports, {
23049
24061
 
23050
24062
  // src/sessions/library.ts
23051
24063
  init_conductor2();
23052
- import { mkdir, readFile as readFile2, readdir, rename, rm, stat as stat2 } from "node:fs/promises";
23053
- import { basename as basename5, join as join16 } from "node:path";
24064
+ import { mkdir as mkdir2, readFile as readFile3, readdir, rename, rm, stat as stat2 } from "node:fs/promises";
24065
+ import { basename as basename5, join as join18 } from "node:path";
23054
24066
  var SESSION_FILE_EXT = ".ndjson";
23055
24067
  var PREVIEW_LIMIT = 72;
23056
24068
  var SessionLibrary = class {
@@ -23110,7 +24122,7 @@ var SessionLibrary = class {
23110
24122
  if (!await this.#missing(toPath)) {
23111
24123
  throw new Error(`SessionLibrary.rename: session "${toId}" already exists`);
23112
24124
  }
23113
- await mkdir(this.#dir, { recursive: true });
24125
+ await mkdir2(this.#dir, { recursive: true });
23114
24126
  await rename(fromPath, toPath);
23115
24127
  return toPath;
23116
24128
  }
@@ -23119,14 +24131,14 @@ var SessionLibrary = class {
23119
24131
  * there was nothing to delete — never throws on an already-absent session.
23120
24132
  */
23121
24133
  async remove(id) {
23122
- const path3 = this.pathOf(id);
23123
- if (await this.#missing(path3)) return false;
23124
- await rm(path3, { force: true });
24134
+ const path4 = this.pathOf(id);
24135
+ if (await this.#missing(path4)) return false;
24136
+ await rm(path4, { force: true });
23125
24137
  return true;
23126
24138
  }
23127
24139
  /** Absolute path a session id persists to. */
23128
24140
  pathOf(id) {
23129
- return join16(this.#dir, `${id}${SESSION_FILE_EXT}`);
24141
+ return join18(this.#dir, `${id}${SESSION_FILE_EXT}`);
23130
24142
  }
23131
24143
  /**
23132
24144
  * Flatten a loaded transcript into an ordered {@link BranchNode} list for a
@@ -23150,9 +24162,9 @@ var SessionLibrary = class {
23150
24162
  for (const node of nodes) {
23151
24163
  if (node.parent !== null) hasChildren.add(node.parent);
23152
24164
  }
23153
- const out = [];
24165
+ const out2 = [];
23154
24166
  const walk = (entry, depth) => {
23155
- out.push({
24167
+ out2.push({
23156
24168
  id: entry.id,
23157
24169
  parent: entry.parent,
23158
24170
  label: labelFor(entry, depth),
@@ -23167,7 +24179,7 @@ var SessionLibrary = class {
23167
24179
  for (const root of childrenOf.get(null) ?? []) {
23168
24180
  walk(root, 0);
23169
24181
  }
23170
- return out;
24182
+ return out2;
23171
24183
  }
23172
24184
  /**
23173
24185
  * Extract the user prompts along a loaded transcript's active branch as fork
@@ -23178,14 +24190,14 @@ var SessionLibrary = class {
23178
24190
  * text, projecting each to a {@link PriorTurn} a forking picker can offer.
23179
24191
  */
23180
24192
  priorTurns(store) {
23181
- const out = [];
24193
+ const out2 = [];
23182
24194
  for (const entry of store.pathTo()) {
23183
24195
  if (entry.role !== "user") continue;
23184
24196
  const text = messageText(entry.content).trim();
23185
24197
  if (text.length === 0) continue;
23186
- out.push({ entryId: entry.id, text, preview: previewOf(text) });
24198
+ out2.push({ entryId: entry.id, text, preview: previewOf(text) });
23187
24199
  }
23188
- return out;
24200
+ return out2;
23189
24201
  }
23190
24202
  /* ---- internals ---- */
23191
24203
  /** Bare session ids present in the directory (no extension), or `[]`. */
@@ -23200,41 +24212,41 @@ var SessionLibrary = class {
23200
24212
  }
23201
24213
  /** Catalog row from file metadata only — no parse. */
23202
24214
  async #shallowRow(id) {
23203
- const path3 = this.pathOf(id);
23204
- const meta = await this.#fileMeta(path3);
23205
- return { id, path: path3, ...meta };
24215
+ const path4 = this.pathOf(id);
24216
+ const meta = await this.#fileMeta(path4);
24217
+ return { id, path: path4, ...meta };
23206
24218
  }
23207
24219
  /** Catalog row enriched by opening the transcript and reducing its text. */
23208
24220
  async #deepRow(id) {
23209
- const path3 = this.pathOf(id);
23210
- const meta = await this.#fileMeta(path3);
24221
+ const path4 = this.pathOf(id);
24222
+ const meta = await this.#fileMeta(path4);
23211
24223
  const store = await this.open(id);
23212
- if (store === null) return { id, path: path3, ...meta };
24224
+ if (store === null) return { id, path: path4, ...meta };
23213
24225
  const branch = store.pathTo();
23214
24226
  const conversational = branch.filter(isConversational);
23215
24227
  const opener = conversational.find((entry) => entry.role === "user");
23216
24228
  const preview2 = opener ? previewOf(messageText(opener.content)) : void 0;
23217
24229
  return {
23218
24230
  id,
23219
- path: path3,
24231
+ path: path4,
23220
24232
  ...meta,
23221
24233
  messageCount: conversational.length,
23222
24234
  ...preview2 ? { preview: preview2, name: preview2 } : {}
23223
24235
  };
23224
24236
  }
23225
24237
  /** Best-effort `{ size, lastModified }`; empty object when the file is gone. */
23226
- async #fileMeta(path3) {
24238
+ async #fileMeta(path4) {
23227
24239
  try {
23228
- const info2 = await stat2(path3);
24240
+ const info2 = await stat2(path4);
23229
24241
  return { size: info2.size, lastModified: info2.mtimeMs };
23230
24242
  } catch {
23231
24243
  return {};
23232
24244
  }
23233
24245
  }
23234
24246
  /** True when a path does not resolve to a readable file. */
23235
- async #missing(path3) {
24247
+ async #missing(path4) {
23236
24248
  try {
23237
- await readFile2(path3);
24249
+ await readFile3(path4);
23238
24250
  return false;
23239
24251
  } catch {
23240
24252
  return true;
@@ -23289,7 +24301,7 @@ function previewOf(text, limit = PREVIEW_LIMIT) {
23289
24301
  }
23290
24302
 
23291
24303
  // src/boot/auth-vault.ts
23292
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
24304
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
23293
24305
  import { dirname as dirname6 } from "node:path";
23294
24306
  import {
23295
24307
  ensureFreshOAuthCredentials,
@@ -23313,9 +24325,9 @@ function normaliseRecord(value) {
23313
24325
  }
23314
24326
  return void 0;
23315
24327
  }
23316
- function readAuthFile(path3) {
24328
+ function readAuthFile(path4) {
23317
24329
  try {
23318
- const text = readFileSync10(path3, "utf8");
24330
+ const text = readFileSync12(path4, "utf8");
23319
24331
  const parsed = JSON.parse(text);
23320
24332
  if (parsed === null || typeof parsed !== "object") return {};
23321
24333
  const file = {};
@@ -23333,9 +24345,9 @@ function readAuthFile(path3) {
23333
24345
  return {};
23334
24346
  }
23335
24347
  }
23336
- function writeAuthFile(path3, data) {
23337
- mkdirSync4(dirname6(path3), { recursive: true });
23338
- writeFileSync7(path3, `${JSON.stringify(data, null, 2)}
24348
+ function writeAuthFile(path4, data) {
24349
+ mkdirSync4(dirname6(path4), { recursive: true });
24350
+ writeFileSync7(path4, `${JSON.stringify(data, null, 2)}
23339
24351
  `, { mode: 384 });
23340
24352
  }
23341
24353
  function toCredentials(record) {
@@ -23429,11 +24441,14 @@ function createAuthVault(authPath) {
23429
24441
  };
23430
24442
  }
23431
24443
 
24444
+ // src/boot/runners/repl-runner.ts
24445
+ init_server_mode();
24446
+
23432
24447
  // src/boot/runners/session.ts
23433
24448
  init_conductor2();
23434
- import { existsSync as existsSync6, readFileSync as readFileSync13 } from "node:fs";
23435
- import { homedir as homedir6 } from "node:os";
23436
- import { join as join19 } from "node:path";
24449
+ import { existsSync as existsSync6, readFileSync as readFileSync15 } from "node:fs";
24450
+ import { homedir as homedir8 } from "node:os";
24451
+ import { join as join21 } from "node:path";
23437
24452
  init_capability_deck();
23438
24453
  init_task_card();
23439
24454
  init_workflow_card();
@@ -23464,7 +24479,7 @@ function argPreview(args) {
23464
24479
  return ` (${oneLine2(String(text), 56)})`;
23465
24480
  }
23466
24481
  function buildActivity(messages) {
23467
- const out = [];
24482
+ const out2 = [];
23468
24483
  for (const message of messages) {
23469
24484
  if (message.role !== "assistant") continue;
23470
24485
  const content = message.content;
@@ -23472,14 +24487,50 @@ function buildActivity(messages) {
23472
24487
  for (const part of content) {
23473
24488
  const p = part;
23474
24489
  if (p.type === "text" && typeof p.text === "string" && p.text.trim() || p.type === "thinking" && typeof p.thinking === "string" && p.thinking.trim()) {
23475
- out.push({ tone: "reason", text: oneLine2(p.text ?? p.thinking) });
24490
+ out2.push({ tone: "reason", text: oneLine2(p.text ?? p.thinking) });
23476
24491
  } else if (p.type === "toolCall" && typeof p.name === "string") {
23477
24492
  const name = p.name.charAt(0).toUpperCase() + p.name.slice(1);
23478
- out.push({ tone: "tool", text: `${name}${argPreview(p.arguments)}` });
24493
+ out2.push({ tone: "tool", text: `${name}${argPreview(p.arguments)}` });
23479
24494
  }
23480
24495
  }
23481
24496
  }
23482
- return out.slice(-14);
24497
+ return out2.slice(-14);
24498
+ }
24499
+ var MAX_PARALLEL_TASKS = (() => {
24500
+ const n = parseInt(process.env.INDUS_MAX_PARALLEL_TASKS ?? "", 10);
24501
+ return Number.isFinite(n) && n >= 1 ? n : 2;
24502
+ })();
24503
+ var MAX_REPORT_CHARS = (() => {
24504
+ const n = parseInt(process.env.INDUS_MAX_DELEGATE_REPORT_CHARS ?? "", 10);
24505
+ return Number.isFinite(n) && n >= 1e3 ? n : 48e3;
24506
+ })();
24507
+ var activeTasks = 0;
24508
+ var taskQueue = [];
24509
+ async function acquireTaskSlot() {
24510
+ if (activeTasks < MAX_PARALLEL_TASKS) {
24511
+ activeTasks++;
24512
+ } else {
24513
+ await new Promise((resolve8) => taskQueue.push(resolve8));
24514
+ }
24515
+ let released = false;
24516
+ return () => {
24517
+ if (released) return;
24518
+ released = true;
24519
+ const next = taskQueue.shift();
24520
+ if (next) next();
24521
+ else activeTasks--;
24522
+ };
24523
+ }
24524
+ function clampReport(report) {
24525
+ if (report.length <= MAX_REPORT_CHARS) return report;
24526
+ const head = Math.floor(MAX_REPORT_CHARS * 0.7);
24527
+ const tail = MAX_REPORT_CHARS - head;
24528
+ const omitted = report.length - MAX_REPORT_CHARS;
24529
+ return `${report.slice(0, head)}
24530
+
24531
+ \u2026[delegate report truncated \u2014 ${omitted} characters omitted to protect parent context]\u2026
24532
+
24533
+ ${report.slice(report.length - tail)}`;
23483
24534
  }
23484
24535
  function finalAssistantText2(messages) {
23485
24536
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -23507,48 +24558,65 @@ function createDelegateRunner(opts) {
23507
24558
 
23508
24559
  Context:
23509
24560
  ${request.context}` : request.objective;
23510
- const agent = opts.spawn?.(request.objective, request.context) ?? new Agent3({
23511
- initialState: {
23512
- // `model` is defined here: the early-return above guaranteed it when
23513
- // no `spawn` seam was given.
23514
- model,
23515
- systemPrompt: opts.system,
23516
- // The sub-agent's deck — the `'authoring'` profile excludes the
23517
- // `task` card, so the sub-agent cannot recurse into delegation.
23518
- tools: opts.tools?.() ?? provisionDeck("authoring", { cwd: opts.cwd }).tools()
23519
- },
23520
- ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {}
23521
- });
23522
- const onAbort = () => agent.abort();
23523
- signal?.addEventListener("abort", onAbort, { once: true });
23524
- let lastSignature = "";
23525
- const reportProgress = () => {
23526
- if (!onProgress) return;
23527
- const tokens = sumTokens(agent.state.messages);
23528
- const activity = buildActivity(agent.state.messages);
23529
- const signature2 = `${tokens}:${activity.length}`;
23530
- if (signature2 !== lastSignature) {
23531
- lastSignature = signature2;
23532
- onProgress({ tokens, activity });
23533
- }
23534
- };
23535
- const offProgress = onProgress ? agent.subscribe?.(reportProgress) : void 0;
24561
+ const gatewayBaseUrls = await opts.getGatewayBaseUrls?.();
24562
+ const releaseSlot = await acquireTaskSlot();
23536
24563
  try {
23537
- await agent.prompt(objective);
23538
- reportProgress();
23539
- } catch (cause) {
23540
- const message = cause instanceof Error ? cause.message : String(cause);
23541
- return { ok: false, report: message || "delegation failed" };
24564
+ if (signal?.aborted) {
24565
+ return { ok: false, report: "delegation aborted" };
24566
+ }
24567
+ let agent = opts.spawn?.(request.objective, request.context);
24568
+ if (agent === void 0) {
24569
+ const tools = opts.tools?.() ?? provisionDeck("authoring", { cwd: opts.cwd }).tools();
24570
+ agent = new Agent3({
24571
+ initialState: {
24572
+ // `model` is defined here: the early-return above guaranteed it when
24573
+ // no `spawn` seam was given. In server mode, route it through the
24574
+ // gateway (freshly resolved above) rather than the raw provider url.
24575
+ model: withGatewayBaseUrl(model, gatewayBaseUrls),
24576
+ systemPrompt: opts.system,
24577
+ tools
24578
+ },
24579
+ ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {},
24580
+ // Gate the sub-agent's inner tool loop with the parent session's live
24581
+ // policy (rules + current mode). Resolver-less: `ask` denies — a
24582
+ // sub-agent cannot prompt the user — while deny rules, plan mode, and
24583
+ // the catastrophic-bash blocklist are enforced per inner tool call.
24584
+ ...opts.permissionPolicy !== void 0 ? { canUseTool: createSubagentPermissionGate(opts.permissionPolicy, tools) } : {}
24585
+ });
24586
+ }
24587
+ const onAbort = () => agent.abort();
24588
+ signal?.addEventListener("abort", onAbort, { once: true });
24589
+ let lastSignature = "";
24590
+ const reportProgress = () => {
24591
+ if (!onProgress) return;
24592
+ const tokens = sumTokens(agent.state.messages);
24593
+ const activity = buildActivity(agent.state.messages);
24594
+ const signature2 = `${tokens}:${activity.length}`;
24595
+ if (signature2 !== lastSignature) {
24596
+ lastSignature = signature2;
24597
+ onProgress({ tokens, activity });
24598
+ }
24599
+ };
24600
+ const offProgress = onProgress ? agent.subscribe?.(reportProgress) : void 0;
24601
+ try {
24602
+ await agent.prompt(objective);
24603
+ reportProgress();
24604
+ } catch (cause) {
24605
+ const message = cause instanceof Error ? cause.message : String(cause);
24606
+ return { ok: false, report: clampReport(message || "delegation failed") };
24607
+ } finally {
24608
+ offProgress?.();
24609
+ signal?.removeEventListener("abort", onAbort);
24610
+ }
24611
+ const error = agent.state.error;
24612
+ const report = finalAssistantText2(agent.state.messages);
24613
+ return {
24614
+ ok: error === void 0 && report.length > 0,
24615
+ report: clampReport(error ?? (report || "(no output)"))
24616
+ };
23542
24617
  } finally {
23543
- offProgress?.();
23544
- signal?.removeEventListener("abort", onAbort);
24618
+ releaseSlot();
23545
24619
  }
23546
- const error = agent.state.error;
23547
- const report = finalAssistantText2(agent.state.messages);
23548
- return {
23549
- ok: error === void 0 && report.length > 0,
23550
- report: error ?? (report || "(no output)")
23551
- };
23552
24620
  }
23553
24621
  };
23554
24622
  }
@@ -23659,7 +24727,7 @@ var CheckpointStore = class {
23659
24727
  snapshotFor(nodeId) {
23660
24728
  const paths = this.byNode.get(nodeId);
23661
24729
  if (paths === void 0) return [];
23662
- return [...paths].map(([path3, previous]) => ({ path: path3, previous }));
24730
+ return [...paths].map(([path4, previous]) => ({ path: path4, previous }));
23663
24731
  }
23664
24732
  /** Whether a node has ANY recorded file snapshot (the picker's restore gate). */
23665
24733
  hasSnapshot(nodeId) {
@@ -23684,15 +24752,15 @@ var CheckpointStore = class {
23684
24752
  restore(nodeId) {
23685
24753
  const snapshots = this.snapshotFor(nodeId);
23686
24754
  const restored = [];
23687
- for (const { path: path3, previous } of snapshots) {
24755
+ for (const { path: path4, previous } of snapshots) {
23688
24756
  try {
23689
24757
  if (previous === null) {
23690
- rmSync(path3, { force: true });
24758
+ rmSync(path4, { force: true });
23691
24759
  } else {
23692
- mkdirSync5(dirname7(path3), { recursive: true });
23693
- writeFileSync8(path3, previous, "utf8");
24760
+ mkdirSync5(dirname7(path4), { recursive: true });
24761
+ writeFileSync8(path4, previous, "utf8");
23694
24762
  }
23695
- restored.push(path3);
24763
+ restored.push(path4);
23696
24764
  } catch {
23697
24765
  }
23698
24766
  }
@@ -23755,7 +24823,7 @@ function addonFault(kind, message, detail) {
23755
24823
 
23756
24824
  // src/addons/sandbox.ts
23757
24825
  import { createJiti } from "jiti";
23758
- import { homedir as homedir5 } from "node:os";
24826
+ import { homedir as homedir7 } from "node:os";
23759
24827
  import { isAbsolute as isAbsolute4, resolve as resolvePathSegments } from "node:path";
23760
24828
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
23761
24829
  var typeboxNamespace = __toESM(require_cjs(), 1);
@@ -23795,17 +24863,17 @@ var INVISIBLE_CODE_POINTS = [
23795
24863
  ];
23796
24864
  var INVISIBLE_SET = new Set(INVISIBLE_CODE_POINTS);
23797
24865
  function scrubInvisible(raw) {
23798
- let out = "";
24866
+ let out2 = "";
23799
24867
  for (const char of raw) {
23800
24868
  const code = char.codePointAt(0);
23801
24869
  if (code !== void 0 && INVISIBLE_SET.has(code)) {
23802
24870
  continue;
23803
24871
  }
23804
- out += char;
24872
+ out2 += char;
23805
24873
  }
23806
- return out.trim();
24874
+ return out2.trim();
23807
24875
  }
23808
- function expandPath(raw, home = homedir5()) {
24876
+ function expandPath(raw, home = homedir7()) {
23809
24877
  const cleaned = scrubInvisible(raw);
23810
24878
  if (cleaned === "~") {
23811
24879
  return home;
@@ -23815,7 +24883,7 @@ function expandPath(raw, home = homedir5()) {
23815
24883
  }
23816
24884
  return cleaned;
23817
24885
  }
23818
- function resolvePath(raw, base = process.cwd(), home = homedir5()) {
24886
+ function resolvePath(raw, base = process.cwd(), home = homedir7()) {
23819
24887
  const expanded = expandPath(raw, home);
23820
24888
  return isAbsolute4(expanded) ? resolvePathSegments(expanded) : resolvePathSegments(base, expanded);
23821
24889
  }
@@ -23837,9 +24905,9 @@ function resolveSpecifierPath(specifier) {
23837
24905
  function buildAliasMap() {
23838
24906
  const map = {};
23839
24907
  for (const specifier of BUNDLED_NAMESPACES) {
23840
- const path3 = resolveSpecifierPath(specifier);
23841
- if (path3 !== void 0) {
23842
- map[specifier] = path3;
24908
+ const path4 = resolveSpecifierPath(specifier);
24909
+ if (path4 !== void 0) {
24910
+ map[specifier] = path4;
23843
24911
  }
23844
24912
  }
23845
24913
  return map;
@@ -23885,10 +24953,10 @@ function isObject(value) {
23885
24953
  function createJitiLoader(options = {}) {
23886
24954
  const jiti = createJiti(import.meta.url, jitiOptionsForRuntime());
23887
24955
  const base = options.base ?? process.cwd();
23888
- const home = options.home ?? homedir5();
24956
+ const home = options.home ?? homedir7();
23889
24957
  return {
23890
- async load(path3) {
23891
- const absolute = resolvePath(path3, base, home);
24958
+ async load(path4) {
24959
+ const absolute = resolvePath(path4, base, home);
23892
24960
  const specifier = pathToFileURL(absolute).href;
23893
24961
  const imported = await jiti.import(specifier);
23894
24962
  const manifest = toManifest(imported);
@@ -23903,8 +24971,8 @@ function createJitiLoader(options = {}) {
23903
24971
  }
23904
24972
 
23905
24973
  // src/addons/manifest.ts
23906
- import { readFileSync as readFileSync11, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
23907
- import { basename as basename6, extname as extname4, isAbsolute as isAbsolute5, join as join17, resolve as resolve7 } from "node:path";
24974
+ import { readFileSync as readFileSync13, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
24975
+ import { basename as basename6, extname as extname4, isAbsolute as isAbsolute5, join as join19, resolve as resolve7 } from "node:path";
23908
24976
  var ENTRY_EXTENSIONS = [
23909
24977
  ".ts",
23910
24978
  ".tsx",
@@ -23921,9 +24989,9 @@ function isHidden(name) {
23921
24989
  function hasEntryExtension(name) {
23922
24990
  return ENTRY_EXTENSIONS.includes(extname4(name).toLowerCase());
23923
24991
  }
23924
- function entryKind(path3) {
24992
+ function entryKind(path4) {
23925
24993
  try {
23926
- const stat3 = statSync4(path3);
24994
+ const stat3 = statSync4(path4);
23927
24995
  if (stat3.isDirectory()) return "directory";
23928
24996
  if (stat3.isFile()) return "file";
23929
24997
  return void 0;
@@ -23939,12 +25007,12 @@ function listNames(dir) {
23939
25007
  }
23940
25008
  }
23941
25009
  function readManifestEntry(packageDir) {
23942
- const manifestPath = join17(packageDir, "package.json");
25010
+ const manifestPath = join19(packageDir, "package.json");
23943
25011
  if (entryKind(manifestPath) !== "file") {
23944
25012
  return void 0;
23945
25013
  }
23946
25014
  try {
23947
- const parsed = JSON.parse(readFileSync11(manifestPath, "utf8"));
25015
+ const parsed = JSON.parse(readFileSync13(manifestPath, "utf8"));
23948
25016
  if (typeof parsed !== "object" || parsed === null) {
23949
25017
  return void 0;
23950
25018
  }
@@ -23964,7 +25032,7 @@ function resolvePackageEntry(packageDir) {
23964
25032
  }
23965
25033
  for (const base of INDEX_BASENAMES) {
23966
25034
  for (const ext of ENTRY_EXTENSIONS) {
23967
- const candidate = join17(packageDir, `${base}${ext}`);
25035
+ const candidate = join19(packageDir, `${base}${ext}`);
23968
25036
  if (entryKind(candidate) === "file") {
23969
25037
  return candidate;
23970
25038
  }
@@ -23976,13 +25044,13 @@ function resolveTopLevelEntry(dir, name) {
23976
25044
  if (isHidden(name)) {
23977
25045
  return void 0;
23978
25046
  }
23979
- const path3 = join17(dir, name);
23980
- const kind = entryKind(path3);
25047
+ const path4 = join19(dir, name);
25048
+ const kind = entryKind(path4);
23981
25049
  if (kind === "file") {
23982
- return hasEntryExtension(name) ? path3 : void 0;
25050
+ return hasEntryExtension(name) ? path4 : void 0;
23983
25051
  }
23984
25052
  if (kind === "directory") {
23985
- return resolvePackageEntry(path3);
25053
+ return resolvePackageEntry(path4);
23986
25054
  }
23987
25055
  return void 0;
23988
25056
  }
@@ -23999,7 +25067,7 @@ function discoverAddons(dir) {
23999
25067
  function deriveAddonId(entryPath) {
24000
25068
  const file = basename6(entryPath);
24001
25069
  const stem = file.slice(0, file.length - extname4(file).length);
24002
- const label2 = INDEX_BASENAMES.includes(stem) ? basename6(join17(entryPath, "..")) : stem;
25070
+ const label2 = INDEX_BASENAMES.includes(stem) ? basename6(join19(entryPath, "..")) : stem;
24003
25071
  return addonId(label2.length > 0 ? label2 : entryPath);
24004
25072
  }
24005
25073
  function addonsDirFor(workspace, dir = ADDONS_DIR) {
@@ -24016,12 +25084,12 @@ function discoverSources(discovery) {
24016
25084
  }
24017
25085
  const seen = /* @__PURE__ */ new Set();
24018
25086
  const sources = [];
24019
- for (const path3 of paths) {
24020
- if (seen.has(path3)) {
25087
+ for (const path4 of paths) {
25088
+ if (seen.has(path4)) {
24021
25089
  continue;
24022
25090
  }
24023
- seen.add(path3);
24024
- sources.push({ id: deriveAddonId(path3), path: path3 });
25091
+ seen.add(path4);
25092
+ sources.push({ id: deriveAddonId(path4), path: path4 });
24025
25093
  }
24026
25094
  return sources;
24027
25095
  }
@@ -24764,8 +25832,8 @@ function concatAddonTools(deck, bundle) {
24764
25832
 
24765
25833
  // src/boot/runners/memdir.ts
24766
25834
  init_capability_deck();
24767
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
24768
- import { join as join18 } from "node:path";
25835
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
25836
+ import { join as join20 } from "node:path";
24769
25837
  var MEMORY_ENTRYPOINT = "MEMORY.md";
24770
25838
  var MAX_ENTRYPOINT_LINES = 200;
24771
25839
  var MAX_ENTRYPOINT_BYTES = 25e3;
@@ -24791,19 +25859,19 @@ function truncateEntrypointContent(raw) {
24791
25859
  }
24792
25860
  function memoryDirFor(workspace, cwd) {
24793
25861
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
24794
- return join18(workspace.profileDir, "memory", `--${slug}--`);
25862
+ return join20(workspace.profileDir, "memory", `--${slug}--`);
24795
25863
  }
24796
25864
  var DiskMemoryStore = class {
24797
25865
  constructor(memDir) {
24798
25866
  this.memDir = memDir;
24799
- this.file = join18(memDir, MEMORY_ENTRYPOINT);
25867
+ this.file = join20(memDir, MEMORY_ENTRYPOINT);
24800
25868
  }
24801
25869
  memDir;
24802
25870
  file;
24803
25871
  /** The current note, or `""` when no file has been written yet. */
24804
25872
  read() {
24805
25873
  try {
24806
- return existsSync5(this.file) ? readFileSync12(this.file, "utf8") : "";
25874
+ return existsSync5(this.file) ? readFileSync14(this.file, "utf8") : "";
24807
25875
  } catch {
24808
25876
  return "";
24809
25877
  }
@@ -24824,9 +25892,9 @@ ${line}`;
24824
25892
  function loadMemoryDoc(memDir) {
24825
25893
  let raw;
24826
25894
  try {
24827
- const file = join18(memDir, MEMORY_ENTRYPOINT);
25895
+ const file = join20(memDir, MEMORY_ENTRYPOINT);
24828
25896
  if (!existsSync5(file)) return void 0;
24829
- raw = readFileSync12(file, "utf8");
25897
+ raw = readFileSync14(file, "utf8");
24830
25898
  } catch {
24831
25899
  return void 0;
24832
25900
  }
@@ -24838,11 +25906,13 @@ function loadMemoryDoc(memDir) {
24838
25906
  // src/boot/runners/session.ts
24839
25907
  init_briefing();
24840
25908
  init_window_budget();
25909
+ init_server_token();
25910
+ init_server_mode();
24841
25911
  function authenticatedProviders(ctx) {
24842
25912
  try {
24843
- const path3 = ctx.workspace.authPath;
24844
- if (!existsSync6(path3)) return [];
24845
- const data = JSON.parse(readFileSync13(path3, "utf8"));
25913
+ const path4 = ctx.workspace.authPath;
25914
+ if (!existsSync6(path4)) return [];
25915
+ const data = JSON.parse(readFileSync15(path4, "utf8"));
24846
25916
  return Object.keys(data).filter((id) => {
24847
25917
  const accounts = data[id];
24848
25918
  return id.length > 0 && accounts !== null && typeof accounts === "object" && Object.keys(accounts).length > 0;
@@ -24859,8 +25929,18 @@ var PREFERRED_DEFAULT = {
24859
25929
  "claude-sonnet-4-0",
24860
25930
  "claude-opus-4-5"
24861
25931
  ],
24862
- openai: ["gpt-5.1", "gpt-4.1", "gpt-4o"]
25932
+ openai: ["gpt-5.1", "gpt-4.1", "gpt-4o"],
25933
+ minimax: ["MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.5"],
25934
+ sarvam: ["sarvam-105b", "sarvam-30b"]
24863
25935
  };
25936
+ function hasServerSessionSync() {
25937
+ try {
25938
+ const token = JSON.parse(readFileSync15(TOKEN_FILE, "utf8"));
25939
+ return typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1e3;
25940
+ } catch {
25941
+ return false;
25942
+ }
25943
+ }
24864
25944
  function providerDefaultModel(catalog, provider) {
24865
25945
  const cards = catalog.byProvider(provider);
24866
25946
  if (cards.length === 0) return void 0;
@@ -24880,6 +25960,12 @@ function resolveModelId(ctx) {
24880
25960
  const id = providerDefaultModel(catalog, provider);
24881
25961
  if (id !== void 0) return id;
24882
25962
  }
25963
+ if (hasServerSessionSync()) {
25964
+ for (const provider of serverTierProviders()) {
25965
+ const id = providerDefaultModel(catalog, provider);
25966
+ if (id !== void 0) return id;
25967
+ }
25968
+ }
24883
25969
  const card = new ModelMatcher(catalog).resolve({});
24884
25970
  return card?.id ?? "";
24885
25971
  }
@@ -24904,9 +25990,9 @@ var PROVIDER_ENV = {
24904
25990
  };
24905
25991
  function primeProviderEnv(ctx) {
24906
25992
  try {
24907
- const path3 = ctx.workspace.authPath;
24908
- if (!existsSync6(path3)) return;
24909
- const data = JSON.parse(readFileSync13(path3, "utf8"));
25993
+ const path4 = ctx.workspace.authPath;
25994
+ if (!existsSync6(path4)) return;
25995
+ const data = JSON.parse(readFileSync15(path4, "utf8"));
24910
25996
  for (const [provider, accounts] of Object.entries(data)) {
24911
25997
  if (provider.startsWith("_") || accounts === null || typeof accounts !== "object") continue;
24912
25998
  const envVar = PROVIDER_ENV[provider];
@@ -24921,7 +26007,7 @@ function primeProviderEnv(ctx) {
24921
26007
  } catch {
24922
26008
  }
24923
26009
  }
24924
- function buildKeyResolver(vault, requestedAccount) {
26010
+ function buildKeyResolver(ctx, vault, requestedAccount) {
24925
26011
  return async (provider) => {
24926
26012
  try {
24927
26013
  const candidates = [
@@ -24934,6 +26020,8 @@ function buildKeyResolver(vault, requestedAccount) {
24934
26020
  const key = await vault.readUsableKey(provider, account);
24935
26021
  if (key) return key;
24936
26022
  }
26023
+ const server = await resolveServerMode(ctx, provider, vault);
26024
+ if (server.serverMode && server.token) return server.token;
24937
26025
  return void 0;
24938
26026
  } catch {
24939
26027
  return void 0;
@@ -24946,7 +26034,7 @@ function resolveThinkingLevel(raw) {
24946
26034
  }
24947
26035
  function resolveText(value) {
24948
26036
  try {
24949
- if (existsSync6(value)) return readFileSync13(value, "utf8");
26037
+ if (existsSync6(value)) return readFileSync15(value, "utf8");
24950
26038
  } catch {
24951
26039
  }
24952
26040
  return value;
@@ -24954,8 +26042,8 @@ function resolveText(value) {
24954
26042
  var PROFILE_DIR2 = ".indusagi";
24955
26043
  function skillRoots2(cwd) {
24956
26044
  return [
24957
- { dir: join19(cwd, PROFILE_DIR2, "skills"), origin: "project" },
24958
- { dir: join19(homedir6(), PROFILE_DIR2, "skills"), origin: "user" }
26045
+ { dir: join21(cwd, PROFILE_DIR2, "skills"), origin: "project" },
26046
+ { dir: join21(homedir8(), PROFILE_DIR2, "skills"), origin: "user" }
24959
26047
  ];
24960
26048
  }
24961
26049
  function gatherModelSkills(cwd) {
@@ -24987,7 +26075,7 @@ function selectTools(cwd, inv, runner, memoryStore, checkpoint, workflowRunner)
24987
26075
  }
24988
26076
  function composeSystem(tools, inv, cwd, skills = [], memoryDoc) {
24989
26077
  const contextDocs = [
24990
- ...gatherContextDocs(cwd, homedir6()),
26078
+ ...gatherContextDocs(cwd, homedir8()),
24991
26079
  ...memoryDoc ? [memoryDoc] : []
24992
26080
  ];
24993
26081
  const base = inv.system !== void 0 ? resolveText(inv.system) : composeBriefing({ tools, contextDocs, skills });
@@ -25005,9 +26093,9 @@ async function loadMcpTools(inv, cwd) {
25005
26093
  console.error = () => {
25006
26094
  };
25007
26095
  try {
25008
- for (const path3 of paths) {
26096
+ for (const path4 of paths) {
25009
26097
  try {
25010
- const servers = loadMCPConfig2(path3);
26098
+ const servers = loadMCPConfig2(path4);
25011
26099
  if (servers.length === 0) continue;
25012
26100
  const pool = new MCPClientPool2({ servers });
25013
26101
  await pool.connectAll();
@@ -25026,7 +26114,7 @@ async function loadMcpTools(inv, cwd) {
25026
26114
  }
25027
26115
  function sessionScopeDir(sessionsRoot, cwd) {
25028
26116
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
25029
- return join19(sessionsRoot, `--${slug}--`);
26117
+ return join21(sessionsRoot, `--${slug}--`);
25030
26118
  }
25031
26119
  function lastUserTurnStart(messages) {
25032
26120
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -25036,7 +26124,11 @@ function lastUserTurnStart(messages) {
25036
26124
  }
25037
26125
  var condenseTranscript = async (messages, opts) => {
25038
26126
  const model = opts?.model;
25039
- const deps = model ? { model } : {};
26127
+ const deps = model ? {
26128
+ model,
26129
+ ...opts?.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
26130
+ ...opts?.signal !== void 0 ? { signal: opts.signal } : {}
26131
+ } : {};
25040
26132
  const cleared = clearStaleToolResults(messages);
25041
26133
  if (opts?.force) {
25042
26134
  const cut = lastUserTurnStart(cleared);
@@ -25084,10 +26176,8 @@ function readOnlyNamesFromDeck(tools) {
25084
26176
  }
25085
26177
  return names;
25086
26178
  }
25087
- function buildPermissionGate(store, tools, planReachable = false) {
25088
- const rules = gatherPermissionRules(store);
25089
- const mode = resolveDefaultMode(store);
25090
- if (rules.length === 0 && (mode === "default" || mode === "bypass") && !planReachable) {
26179
+ function buildPermissionGate(rules, mode, tools, planReachable = false) {
26180
+ if (rules.length === 0 && mode === "bypass" && !planReachable) {
25091
26181
  return void 0;
25092
26182
  }
25093
26183
  const readOnlyToolNames = readOnlyNamesFromDeck(tools);
@@ -25097,9 +26187,17 @@ function buildPermissionGate(store, tools, planReachable = false) {
25097
26187
  // The conductor's stable approval delegate: denies until the interactive
25098
26188
  // console installs a resolver via setApprovalResolver, then routes `ask`.
25099
26189
  ...requestApproval !== void 0 ? { requestApproval } : {},
26190
+ // Session-scoped memory for `allow-always`: push into the SAME live array
26191
+ // the gate (and the sub-agent gates) re-scan per call. Never persisted.
26192
+ appendAllowRule: (rule) => rules.push(rule),
25100
26193
  readOnlyToolNames
25101
26194
  });
25102
26195
  }
26196
+ function resolveOpeningMode(inv, store) {
26197
+ const raw = inv.flags["permission-mode"];
26198
+ if (typeof raw === "string" && isPermissionMode(raw)) return raw;
26199
+ return resolveDefaultMode(store);
26200
+ }
25103
26201
  async function buildSessionConductor(ctx) {
25104
26202
  primeProviderEnv(ctx);
25105
26203
  const inv = ctx.invocation;
@@ -25110,12 +26208,35 @@ async function buildSessionConductor(ctx) {
25110
26208
  const memDir = memoryDirFor(ctx.workspace, cwd);
25111
26209
  const memoryStore = new DiskMemoryStore(memDir);
25112
26210
  const memoryDoc = loadMemoryDoc(memDir);
25113
- const getApiKey = buildKeyResolver(createAuthVault(ctx.workspace.authPath), inv.account);
26211
+ const vault = createAuthVault(ctx.workspace.authPath);
26212
+ const getApiKey = buildKeyResolver(ctx, vault, inv.account);
26213
+ const gatewayBaseUrls = await resolveServerGatewayUrls(ctx, vault);
26214
+ const settings = PreferenceStore.fromWorkspace(ctx.workspace, cwd);
26215
+ const permissionRules = gatherPermissionRules(settings);
26216
+ const permissionMode = resolveOpeningMode(inv, settings);
26217
+ let liveConductor;
26218
+ const subagentPolicy = {
26219
+ rules: permissionRules,
26220
+ mode: () => liveConductor?.permissionMode() ?? permissionMode
26221
+ };
25114
26222
  const surveyBriefing = composeBriefing({
25115
26223
  tools: provisionDeck("authoring", { cwd }).tools()
25116
26224
  });
25117
- const runner = createDelegateRunner({ modelId, getApiKey, system: surveyBriefing, cwd });
25118
- const workflowRunner = createWorkflowAgentRunner({ modelId, getApiKey, cwd });
26225
+ const runner = createDelegateRunner({
26226
+ modelId,
26227
+ getApiKey,
26228
+ system: surveyBriefing,
26229
+ cwd,
26230
+ getGatewayBaseUrls: () => resolveServerGatewayUrls(ctx, vault),
26231
+ permissionPolicy: subagentPolicy
26232
+ });
26233
+ const workflowRunner = createWorkflowAgentRunner({
26234
+ modelId,
26235
+ getApiKey,
26236
+ cwd,
26237
+ getGatewayBaseUrls: () => resolveServerGatewayUrls(ctx, vault),
26238
+ permissionPolicy: subagentPolicy
26239
+ });
25119
26240
  const addons = await buildAddonHost(ctx);
25120
26241
  const checkpoint = createCheckpointStore();
25121
26242
  const deck = [
@@ -25127,11 +26248,9 @@ async function buildSessionConductor(ctx) {
25127
26248
  const system = composeSystem(tools, inv, cwd, skills, memoryDoc);
25128
26249
  const thinking = resolveThinkingLevel(inv.thinking);
25129
26250
  const fallbackModelId = inv.fallbackModelId;
25130
- const settings = PreferenceStore.fromWorkspace(ctx.workspace, cwd);
25131
- const permissionMode = resolveDefaultMode(settings);
25132
26251
  const planReachable = inv.mode === "repl";
25133
- const canUseTool = buildPermissionGate(settings, tools, planReachable);
25134
- return createSessionConductor(
26252
+ const canUseTool = buildPermissionGate(permissionRules, permissionMode, tools, planReachable);
26253
+ const conductor = createSessionConductor(
25135
26254
  {
25136
26255
  modelId,
25137
26256
  tools,
@@ -25149,12 +26268,19 @@ async function buildSessionConductor(ctx) {
25149
26268
  ...canUseTool !== void 0 ? { canUseTool } : {},
25150
26269
  ...thinking !== void 0 ? { thinking } : {},
25151
26270
  ...workspace ? { workspace } : {},
25152
- ...fallbackModelId !== void 0 ? { fallbackModelId } : {}
26271
+ ...fallbackModelId !== void 0 ? { fallbackModelId } : {},
26272
+ // When server mode is active for the bound model's provider, hand the
26273
+ // conductor the gateway base url. A4 (`conductor.ts`) clones `card.model`
26274
+ // with this `baseUrl` so the existing model card routes through the indus
26275
+ // gateway; absent (the local-key path) the conductor binds the card as-is.
26276
+ ...Object.keys(gatewayBaseUrls).length > 0 ? { gatewayBaseUrls } : {}
25153
26277
  },
25154
26278
  // The live condense hook: `/compact` and auto-compaction fold older turns
25155
26279
  // into a digest. (Tests that build conductors directly keep the no-op default.)
25156
26280
  { condense: condenseTranscript }
25157
26281
  );
26282
+ liveConductor = conductor;
26283
+ return conductor;
25158
26284
  }
25159
26285
  function oneshotPrompts(ctx) {
25160
26286
  const prompts = [];
@@ -25166,9 +26292,10 @@ function oneshotPrompts(ctx) {
25166
26292
  }
25167
26293
 
25168
26294
  // src/boot/runners/repl-runner.ts
25169
- var EXIT_OK = 0;
26295
+ var EXIT_OK2 = 0;
25170
26296
  function buildOverlayServices(ctx, conductor) {
25171
26297
  const cwd = ctx.invocation.cwd ?? process.cwd();
26298
+ const vault = createAuthVault(ctx.workspace.authPath);
25172
26299
  return {
25173
26300
  conductor,
25174
26301
  settings: PreferenceStore.fromWorkspace(ctx.workspace, cwd),
@@ -25178,7 +26305,11 @@ function buildOverlayServices(ctx, conductor) {
25178
26305
  listLoginProviders,
25179
26306
  startOAuthLogin,
25180
26307
  openLoginUrl,
25181
- vault: createAuthVault(ctx.workspace.authPath)
26308
+ vault,
26309
+ async refreshGatewayRouting() {
26310
+ const map = await resolveServerGatewayUrls(ctx, vault);
26311
+ conductor.updateGatewayBaseUrls(map);
26312
+ }
25182
26313
  };
25183
26314
  }
25184
26315
  function toResumeRef(row) {
@@ -25255,7 +26386,7 @@ var replRunner = {
25255
26386
  conductor,
25256
26387
  initialInput === void 0 ? { services } : { initialInput, services }
25257
26388
  );
25258
- return EXIT_OK;
26389
+ return EXIT_OK2;
25259
26390
  }
25260
26391
  };
25261
26392
 
@@ -25401,10 +26532,10 @@ var inertDialog = Object.freeze({
25401
26532
  tell(_kind, _payload) {
25402
26533
  }
25403
26534
  });
25404
- var EXIT_OK2 = 0;
26535
+ var EXIT_OK3 = 0;
25405
26536
  var EXIT_FAULT = 1;
25406
26537
  function exitCodeFor(state) {
25407
- return state.phase === "faulted" ? EXIT_FAULT : EXIT_OK2;
26538
+ return state.phase === "faulted" ? EXIT_FAULT : EXIT_OK3;
25408
26539
  }
25409
26540
  function textStrategy() {
25410
26541
  let answer = "";
@@ -25813,7 +26944,7 @@ var oneshotRunner = {
25813
26944
  };
25814
26945
 
25815
26946
  // src/boot/runners/link-runner.ts
25816
- var EXIT_OK3 = 0;
26947
+ var EXIT_OK4 = 0;
25817
26948
  var STDOUT2 = {
25818
26949
  write(chunk, cb) {
25819
26950
  return process.stdout.write(chunk, cb);
@@ -25831,7 +26962,7 @@ var linkRunner = {
25831
26962
  out: STDOUT2
25832
26963
  });
25833
26964
  await server.done;
25834
- return EXIT_OK3;
26965
+ return EXIT_OK4;
25835
26966
  }
25836
26967
  };
25837
26968
 
@@ -25847,8 +26978,8 @@ function selectRunner(inv) {
25847
26978
  }
25848
26979
 
25849
26980
  // src/boot/boot.ts
25850
- var EXIT_OK4 = 0;
25851
- var EXIT_FAILURE = 1;
26981
+ var EXIT_OK5 = 0;
26982
+ var EXIT_FAILURE2 = 1;
25852
26983
  function seedContext(argv) {
25853
26984
  const workspace = createWorkspace();
25854
26985
  const invocation = tokenizeInvocation(argv);
@@ -25890,7 +27021,7 @@ async function runCredential(ctx) {
25890
27021
  `);
25891
27022
  return EXIT_CREDENTIAL_FAULT;
25892
27023
  }
25893
- return EXIT_OK4;
27024
+ return EXIT_OK5;
25894
27025
  }
25895
27026
  async function runPackage(ctx) {
25896
27027
  const result = await runPackageCommand(ctx.argv, {
@@ -25899,7 +27030,7 @@ async function runPackage(ctx) {
25899
27030
  if (!result.handled) {
25900
27031
  return void 0;
25901
27032
  }
25902
- return result.code ?? EXIT_OK4;
27033
+ return result.code ?? EXIT_OK5;
25903
27034
  }
25904
27035
  async function boot(argv) {
25905
27036
  const seeded = seedContext(argv);
@@ -25907,22 +27038,26 @@ async function boot(argv) {
25907
27038
  if (credential !== void 0) {
25908
27039
  return credential;
25909
27040
  }
27041
+ const login = await runLogin(seeded);
27042
+ if (login !== void 0) {
27043
+ return login;
27044
+ }
25910
27045
  const pkg = await runPackage(seeded);
25911
27046
  if (pkg !== void 0) {
25912
27047
  return pkg;
25913
27048
  }
25914
27049
  if (wantsHelp(seeded.invocation)) {
25915
27050
  renderHelp();
25916
- return EXIT_OK4;
27051
+ return EXIT_OK5;
25917
27052
  }
25918
27053
  if (wantsVersion(seeded.invocation)) {
25919
27054
  renderVersion();
25920
- return EXIT_OK4;
27055
+ return EXIT_OK5;
25921
27056
  }
25922
27057
  if (seeded.invocation.listModels) {
25923
27058
  const filter = seeded.invocation.listModelsFilter;
25924
27059
  printModelCatalog(void 0, filter !== void 0 ? { search: filter } : {});
25925
- return EXIT_OK4;
27060
+ return EXIT_OK5;
25926
27061
  }
25927
27062
  const ctx = await runStages(seeded);
25928
27063
  try {
@@ -25932,7 +27067,7 @@ async function boot(argv) {
25932
27067
  const message = error instanceof Error ? error.message : String(error);
25933
27068
  process.stderr.write(`${BRAND.name}: ${message}
25934
27069
  `);
25935
- return EXIT_FAILURE;
27070
+ return EXIT_FAILURE2;
25936
27071
  } finally {
25937
27072
  await drainClosables(ctx);
25938
27073
  }
@@ -27135,14 +28270,14 @@ function keyMatches(rule, key) {
27135
28270
  return matcher.test(key);
27136
28271
  }
27137
28272
  function applyValueRules(rules, text) {
27138
- let out = text;
28273
+ let out2 = text;
27139
28274
  for (const rule of rules) {
27140
28275
  const pattern = rule.value;
27141
28276
  if (pattern === void 0) continue;
27142
28277
  if (pattern.global || pattern.sticky) pattern.lastIndex = 0;
27143
- out = out.replace(pattern, REDACTED_TOKEN);
28278
+ out2 = out2.replace(pattern, REDACTED_TOKEN);
27144
28279
  }
27145
- return out;
28280
+ return out2;
27146
28281
  }
27147
28282
  function createRedactor(rules = DEFAULT_REDACTION_RULES, options = {}) {
27148
28283
  const frozenRules = Object.freeze([...rules]);
@@ -27174,12 +28309,12 @@ function createRedactor(rules = DEFAULT_REDACTION_RULES, options = {}) {
27174
28309
  return Object.freeze(value.map((item) => scrubNode(item, void 0)));
27175
28310
  }
27176
28311
  if (value !== null && typeof value === "object") {
27177
- const out = {};
28312
+ const out2 = {};
27178
28313
  for (const [k, v] of Object.entries(value)) {
27179
28314
  if (isOmitted(k)) continue;
27180
- out[k] = scrubNode(v, k);
28315
+ out2[k] = scrubNode(v, k);
27181
28316
  }
27182
- return Object.freeze(out);
28317
+ return Object.freeze(out2);
27183
28318
  }
27184
28319
  return value;
27185
28320
  }
@@ -27187,12 +28322,12 @@ function createRedactor(rules = DEFAULT_REDACTION_RULES, options = {}) {
27187
28322
  return scrubNode(value, void 0);
27188
28323
  }
27189
28324
  function scrubAttributes(attributes) {
27190
- const out = {};
28325
+ const out2 = {};
27191
28326
  for (const [k, v] of Object.entries(attributes)) {
27192
28327
  if (isOmitted(k)) continue;
27193
- out[k] = scrubNode(v, k);
28328
+ out2[k] = scrubNode(v, k);
27194
28329
  }
27195
- return Object.freeze(out);
28330
+ return Object.freeze(out2);
27196
28331
  }
27197
28332
  function scrubFault(fault) {
27198
28333
  const scrubbed = {
@@ -27297,7 +28432,7 @@ function createConsoleSink(options = {}) {
27297
28432
  }
27298
28433
 
27299
28434
  // src/insight/sinks/file.ts
27300
- import { open, mkdir as mkdir2 } from "node:fs/promises";
28435
+ import { open, mkdir as mkdir3 } from "node:fs/promises";
27301
28436
  import { dirname as dirname8 } from "node:path";
27302
28437
  function createFileSink(options) {
27303
28438
  const id = options.id ?? "file";
@@ -27310,7 +28445,7 @@ function createFileSink(options) {
27310
28445
  for await (const signal of stream) {
27311
28446
  if (handle === null) {
27312
28447
  if (options.mkdirp ?? true) {
27313
- await mkdir2(dirname8(options.path), { recursive: true });
28448
+ await mkdir3(dirname8(options.path), { recursive: true });
27314
28449
  }
27315
28450
  handle = await open(options.path, mode);
27316
28451
  }