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/entry.js CHANGED
@@ -32,6 +32,70 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  mod
33
33
  ));
34
34
 
35
+ // src/boot/server-token.ts
36
+ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
37
+ import os from "node:os";
38
+ import path from "node:path";
39
+ async function getServerToken() {
40
+ try {
41
+ const data = await readFile(TOKEN_FILE, "utf8");
42
+ return JSON.parse(data);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ async function storeServerToken(token) {
48
+ try {
49
+ await mkdir(CONFIG_DIR, { recursive: true });
50
+ const tokenData = {
51
+ access_token: token.access_token,
52
+ refresh_token: token.refresh_token,
53
+ token_type: token.token_type ?? "Bearer",
54
+ scope: token.scope,
55
+ expires_at: token.expires_in !== void 0 ? new Date(Date.now() + token.expires_in * 1e3).toISOString() : null,
56
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
57
+ };
58
+ await writeFile(TOKEN_FILE, `${JSON.stringify(tokenData, null, 2)}
59
+ `, {
60
+ encoding: "utf8",
61
+ mode: 384
62
+ });
63
+ return true;
64
+ } catch (error) {
65
+ console.error("Failed to store server token:", error.message);
66
+ return false;
67
+ }
68
+ }
69
+ async function clearServerToken() {
70
+ try {
71
+ await unlink(TOKEN_FILE);
72
+ return true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ function isServerTokenExpired(token) {
78
+ if (!token || !token.expires_at) {
79
+ return true;
80
+ }
81
+ const expiresAt = new Date(token.expires_at);
82
+ const now = /* @__PURE__ */ new Date();
83
+ return expiresAt.getTime() - now.getTime() < 5 * 60 * 1e3;
84
+ }
85
+ async function hasValidServerToken() {
86
+ const token = await getServerToken();
87
+ return token !== null && !isServerTokenExpired(token);
88
+ }
89
+ var INDUS_SERVER_URL, CONFIG_DIR, TOKEN_FILE;
90
+ var init_server_token = __esm({
91
+ "src/boot/server-token.ts"() {
92
+ "use strict";
93
+ INDUS_SERVER_URL = process.env.INDUS_SERVER_URL?.replace(/\/+$/, "") ?? "https://indus-server.vercel.app";
94
+ CONFIG_DIR = path.join(os.homedir(), ".indusagi", "agent");
95
+ TOKEN_FILE = path.join(CONFIG_DIR, "server-token.json");
96
+ }
97
+ });
98
+
35
99
  // src/briefing/contract.ts
36
100
  function briefingFault(kind, message, cause) {
37
101
  return cause === void 0 ? { kind, message } : { kind, message, cause };
@@ -97,29 +161,29 @@ function bullets(lines) {
97
161
  return lines.map((l) => `- ${l}`).join("\n");
98
162
  }
99
163
  function escapeXml(value) {
100
- let out = "";
164
+ let out2 = "";
101
165
  for (const ch of value) {
102
166
  switch (ch) {
103
167
  case "&":
104
- out += "&amp;";
168
+ out2 += "&amp;";
105
169
  break;
106
170
  case "<":
107
- out += "&lt;";
171
+ out2 += "&lt;";
108
172
  break;
109
173
  case ">":
110
- out += "&gt;";
174
+ out2 += "&gt;";
111
175
  break;
112
176
  case '"':
113
- out += "&quot;";
177
+ out2 += "&quot;";
114
178
  break;
115
179
  case "'":
116
- out += "&#39;";
180
+ out2 += "&#39;";
117
181
  break;
118
182
  default:
119
- out += ch;
183
+ out2 += ch;
120
184
  }
121
185
  }
122
- return out;
186
+ return out2;
123
187
  }
124
188
  function hasTool(ctx, id) {
125
189
  return (ctx.tools ?? []).some((t) => t.name === id);
@@ -554,7 +618,7 @@ function buildMacroScope(raw) {
554
618
  return { args, all: args.join(" "), raw };
555
619
  }
556
620
  function splitArguments(raw) {
557
- const out = [];
621
+ const out2 = [];
558
622
  let current = "";
559
623
  let inWord = false;
560
624
  let quote = null;
@@ -576,7 +640,7 @@ function splitArguments(raw) {
576
640
  }
577
641
  if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v") {
578
642
  if (inWord) {
579
- out.push(current);
643
+ out2.push(current);
580
644
  current = "";
581
645
  inWord = false;
582
646
  }
@@ -585,33 +649,33 @@ function splitArguments(raw) {
585
649
  current += ch;
586
650
  inWord = true;
587
651
  }
588
- if (inWord) out.push(current);
589
- return out;
652
+ if (inWord) out2.push(current);
653
+ return out2;
590
654
  }
591
655
  function resolveTokens(tokens, scope) {
592
- let out = "";
656
+ let out2 = "";
593
657
  for (const token of tokens) {
594
658
  switch (token.kind) {
595
659
  case "literal":
596
- out += token.text;
660
+ out2 += token.text;
597
661
  break;
598
662
  case "positional": {
599
663
  const value = scope.args[token.index - 1];
600
- if (value !== void 0) out += value;
664
+ if (value !== void 0) out2 += value;
601
665
  break;
602
666
  }
603
667
  case "all":
604
- out += scope.all;
668
+ out2 += scope.all;
605
669
  break;
606
670
  case "slice": {
607
671
  const from = Math.max(0, token.start - 1);
608
672
  const slice = token.length === void 0 ? scope.args.slice(from) : scope.args.slice(from, from + Math.max(0, token.length));
609
- out += slice.join(" ");
673
+ out2 += slice.join(" ");
610
674
  break;
611
675
  }
612
676
  }
613
677
  }
614
- return out;
678
+ return out2;
615
679
  }
616
680
  function applyMacros(body, raw) {
617
681
  return resolveTokens(scanMacroBody(body), buildMacroScope(raw));
@@ -644,18 +708,18 @@ function loadMacros(dir, opts = {}) {
644
708
  }
645
709
  return macros;
646
710
  }
647
- function readMacroFile(path3, origin, label) {
711
+ function readMacroFile(path4, origin, label) {
648
712
  let text;
649
713
  try {
650
- text = readFileSync(path3, "utf8");
714
+ text = readFileSync(path4, "utf8");
651
715
  } catch (cause) {
652
- throw briefingFault("macro_invalid", `could not read macro file at ${path3}`, cause);
716
+ throw briefingFault("macro_invalid", `could not read macro file at ${path4}`, cause);
653
717
  }
654
718
  const { frontmatter, body } = splitFrontmatter(text);
655
- const name = basename2(path3).replace(/\.md$/i, "");
719
+ const name = basename2(path4).replace(/\.md$/i, "");
656
720
  const declared = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
657
721
  const description = declared.length > 0 ? `${declared} (${label})` : deriveDescription(body, label);
658
- return { name, description, body, origin, source: path3 };
722
+ return { name, description, body, origin, source: path4 };
659
723
  }
660
724
  function deriveDescription(body, label) {
661
725
  const firstLine = body.split("\n").find((l) => l.trim().length > 0)?.trim() ?? "";
@@ -794,41 +858,41 @@ function checkFrontmatterKeys(frontmatter) {
794
858
  return null;
795
859
  }
796
860
  function projectFrontmatter(raw) {
797
- const out = {};
798
- if (typeof raw.name === "string") out.name = raw.name.trim();
799
- if (typeof raw.description === "string") out.description = raw.description.trim();
800
- if (typeof raw.license === "string") out.license = raw.license.trim();
801
- if (typeof raw.compatibility === "string") out.compatibility = raw.compatibility.trim();
861
+ const out2 = {};
862
+ if (typeof raw.name === "string") out2.name = raw.name.trim();
863
+ if (typeof raw.description === "string") out2.description = raw.description.trim();
864
+ if (typeof raw.license === "string") out2.license = raw.license.trim();
865
+ if (typeof raw.compatibility === "string") out2.compatibility = raw.compatibility.trim();
802
866
  if (typeof raw.metadata === "object" && raw.metadata !== null) {
803
- out.metadata = raw.metadata;
867
+ out2.metadata = raw.metadata;
804
868
  }
805
869
  const tools = raw["allowed-tools"];
806
870
  if (typeof tools === "string") {
807
871
  const list = tools.split(/[\s,]+/).map((t) => t.trim()).filter((t) => t.length > 0);
808
- if (list.length > 0) out.allowedTools = list;
872
+ if (list.length > 0) out2.allowedTools = list;
809
873
  } else if (Array.isArray(tools)) {
810
- out.allowedTools = tools.map((t) => String(t));
874
+ out2.allowedTools = tools.map((t) => String(t));
811
875
  }
812
876
  const flag = raw["disable-model-invocation"];
813
- if (flag === true || flag === "true") out.disableModelInvocation = true;
814
- return out;
877
+ if (flag === true || flag === "true") out2.disableModelInvocation = true;
878
+ return out2;
815
879
  }
816
880
  function diag(kind, location, detail) {
817
881
  return { kind, location, detail };
818
882
  }
819
883
  function parseCandidate(candidate, origin) {
820
- const { path: path3, dirName } = candidate;
884
+ const { path: path4, dirName } = candidate;
821
885
  let text;
822
886
  try {
823
- text = readFileSync2(path3, "utf8");
887
+ text = readFileSync2(path4, "utf8");
824
888
  } catch (cause) {
825
889
  const detail = `could not read the skill file (${describeCause(cause)})`;
826
- return { card: null, diagnostic: diag("invalid", path3, detail) };
890
+ return { card: null, diagnostic: diag("invalid", path4, detail) };
827
891
  }
828
892
  const { frontmatter, body } = splitFrontmatter(text);
829
893
  const keyProblem = checkFrontmatterKeys(frontmatter);
830
894
  if (keyProblem !== null) {
831
- return { card: null, diagnostic: diag("invalid", path3, keyProblem) };
895
+ return { card: null, diagnostic: diag("invalid", path4, keyProblem) };
832
896
  }
833
897
  const fm = projectFrontmatter(frontmatter);
834
898
  const declaredName = fm.name ?? "";
@@ -836,22 +900,22 @@ function parseCandidate(candidate, origin) {
836
900
  const name = fromDir ? dirName : declaredName;
837
901
  const nameProblem = checkName(name, dirName, fromDir);
838
902
  if (nameProblem !== null) {
839
- return { card: null, diagnostic: diag("invalid", path3, nameProblem) };
903
+ return { card: null, diagnostic: diag("invalid", path4, nameProblem) };
840
904
  }
841
905
  const description = fm.description ?? "";
842
906
  const descProblem = checkDescription(description);
843
907
  if (descProblem !== null) {
844
- return { card: null, diagnostic: diag("invalid", path3, descProblem) };
908
+ return { card: null, diagnostic: diag("invalid", path4, descProblem) };
845
909
  }
846
910
  const card = {
847
911
  name,
848
912
  description,
849
913
  body,
850
- location: path3,
914
+ location: path4,
851
915
  origin,
852
916
  frontmatter: fm
853
917
  };
854
- return { card, diagnostic: diag("loaded", path3, `loaded skill "${name}"`) };
918
+ return { card, diagnostic: diag("loaded", path4, `loaded skill "${name}"`) };
855
919
  }
856
920
  function describeCause(cause) {
857
921
  if (cause instanceof Error) return cause.message;
@@ -924,15 +988,15 @@ var init_skills = __esm({
924
988
  // src/briefing/context-docs.ts
925
989
  import { existsSync, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
926
990
  import { dirname as dirname3, extname, isAbsolute as isAbsolute2, join as join6, parse, resolve as resolve2 } from "node:path";
927
- function shortenPath(path3, cwd, home) {
928
- if (cwd.length > 0 && path3.startsWith(cwd)) {
929
- const rest = path3.slice(cwd.length).replace(/^[/\\]+/, "");
991
+ function shortenPath(path4, cwd, home) {
992
+ if (cwd.length > 0 && path4.startsWith(cwd)) {
993
+ const rest = path4.slice(cwd.length).replace(/^[/\\]+/, "");
930
994
  return rest.length > 0 ? `./${rest}` : ".";
931
995
  }
932
- if (home.length > 0 && path3.startsWith(home)) {
933
- return `~${path3.slice(home.length)}`;
996
+ if (home.length > 0 && path4.startsWith(home)) {
997
+ return `~${path4.slice(home.length)}`;
934
998
  }
935
- return path3;
999
+ return path4;
936
1000
  }
937
1001
  function dirChain(cwd) {
938
1002
  const chain = [];
@@ -945,15 +1009,15 @@ function dirChain(cwd) {
945
1009
  }
946
1010
  return chain.reverse();
947
1011
  }
948
- function safeRealpath(path3) {
1012
+ function safeRealpath(path4) {
949
1013
  try {
950
- return realpathSync2(path3);
1014
+ return realpathSync2(path4);
951
1015
  } catch {
952
- return path3;
1016
+ return path4;
953
1017
  }
954
1018
  }
955
- function isTextFile(path3) {
956
- const ext = extname(path3).toLowerCase();
1019
+ function isTextFile(path4) {
1020
+ const ext = extname(path4).toLowerCase();
957
1021
  return ext.length === 0 || TEXT_FILE_EXTENSIONS.has(ext);
958
1022
  }
959
1023
  function readFileBody(absPath) {
@@ -968,7 +1032,7 @@ function readFileBody(absPath) {
968
1032
  return trimmed.length > 0 ? trimmed : null;
969
1033
  }
970
1034
  function extractImports(body, baseDir, home) {
971
- const out = [];
1035
+ const out2 = [];
972
1036
  IMPORT_REGEX.lastIndex = 0;
973
1037
  let match;
974
1038
  while ((match = IMPORT_REGEX.exec(body)) !== null) {
@@ -987,26 +1051,26 @@ function extractImports(body, baseDir, home) {
987
1051
  abs = resolve2(baseDir, spec);
988
1052
  }
989
1053
  if (!isTextFile(abs)) continue;
990
- out.push(abs);
1054
+ out2.push(abs);
991
1055
  }
992
- return out;
1056
+ return out2;
993
1057
  }
994
- function loadDocTree(absPath, cwd, home, seen, depth, maxDepth, maxBytes, out) {
1058
+ function loadDocTree(absPath, cwd, home, seen, depth, maxDepth, maxBytes, out2) {
995
1059
  const real = safeRealpath(absPath);
996
1060
  if (seen.has(real)) return;
997
1061
  seen.add(real);
998
1062
  const body = readFileBody(absPath);
999
1063
  if (body === null) return;
1000
1064
  const clipped = body.length > maxBytes ? body.slice(0, maxBytes) : body;
1001
- out.push({ path: shortenPath(absPath, cwd, home), body: clipped });
1065
+ out2.push({ path: shortenPath(absPath, cwd, home), body: clipped });
1002
1066
  if (depth >= maxDepth) return;
1003
1067
  const baseDir = dirname3(absPath);
1004
1068
  for (const child of extractImports(body, baseDir, home)) {
1005
- loadDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out);
1069
+ loadDocTree(child, cwd, home, seen, depth + 1, maxDepth, maxBytes, out2);
1006
1070
  }
1007
1071
  }
1008
1072
  function gatherContextDocs(cwd, home, opts) {
1009
- const out = [];
1073
+ const out2 = [];
1010
1074
  try {
1011
1075
  const maxDepth = opts?.maxImportDepth ?? DEFAULT_MAX_IMPORT_DEPTH;
1012
1076
  const maxBytes = opts?.maxBytesPerDoc ?? DEFAULT_MAX_BYTES_PER_DOC;
@@ -1017,13 +1081,13 @@ function gatherContextDocs(cwd, home, opts) {
1017
1081
  if (dir.length === 0) continue;
1018
1082
  for (const name of CANDIDATE_FILES) {
1019
1083
  const candidate = join6(dir, name);
1020
- loadDocTree(candidate, cwd, home, seen, 0, maxDepth, maxBytes, out);
1084
+ loadDocTree(candidate, cwd, home, seen, 0, maxDepth, maxBytes, out2);
1021
1085
  }
1022
1086
  }
1023
1087
  } catch {
1024
- return out;
1088
+ return out2;
1025
1089
  }
1026
- return out;
1090
+ return out2;
1027
1091
  }
1028
1092
  var DEFAULT_MAX_IMPORT_DEPTH, DEFAULT_MAX_BYTES_PER_DOC, CANDIDATE_FILES, TEXT_FILE_EXTENSIONS, IMPORT_REGEX;
1029
1093
  var init_context_docs = __esm({
@@ -1190,13 +1254,15 @@ var init_transcript = __esm({
1190
1254
  }
1191
1255
  };
1192
1256
  compactCommand = {
1193
- name: "summarize-context",
1257
+ // `compact` is the canonical name — it is what users type (and what the
1258
+ // suggestion list shows); the longer forms stay reachable as aliases.
1259
+ name: "compact",
1194
1260
  summary: "Condense the conversation context",
1195
- aliases: ["condense", "compact"],
1261
+ aliases: ["condense", "summarize-context"],
1196
1262
  takesArgs: true,
1197
1263
  run(ctx) {
1198
1264
  const instructions = ctx.args.trim();
1199
- const busyText = instructions.length > 0 ? `Condensing with guidance: ${instructions}` : "Condensing conversation context...";
1265
+ const busyText = instructions.length > 0 ? `Compacting with guidance: ${instructions} \u2014 Esc to cancel` : "Compacting conversation context... Press Esc to cancel.";
1200
1266
  ctx.setStatus({ kind: "busy", text: busyText });
1201
1267
  void (async () => {
1202
1268
  try {
@@ -1205,14 +1271,29 @@ var init_transcript = __esm({
1205
1271
  `printf '%s' ${JSON.stringify(`Compaction guidance: ${instructions}`)}`
1206
1272
  );
1207
1273
  }
1208
- await ctx.conductor.condense();
1209
- ctx.setStatus(
1210
- info(
1211
- instructions.length > 0 ? "Context condensed (guidance recorded for the agent)." : "Context condensed."
1212
- )
1213
- );
1274
+ switch (await ctx.conductor.condense()) {
1275
+ case "condensed":
1276
+ ctx.setStatus(
1277
+ info(
1278
+ instructions.length > 0 ? "Context condensed (guidance recorded for the agent)." : "Context condensed."
1279
+ )
1280
+ );
1281
+ break;
1282
+ case "nothing":
1283
+ ctx.setStatus(warn("Nothing to compact yet."));
1284
+ break;
1285
+ case "cancelled":
1286
+ ctx.setStatus(warn("Compaction cancelled."));
1287
+ break;
1288
+ case "busy":
1289
+ ctx.setStatus(warn("A turn is running \u2014 press Esc to stop it first, then /compact."));
1290
+ break;
1291
+ case "failed":
1292
+ ctx.setStatus(warn("Compaction failed; the transcript is unchanged."));
1293
+ break;
1294
+ }
1214
1295
  } catch {
1215
- ctx.setStatus(warn("Condense could not complete; the transcript is unchanged."));
1296
+ ctx.setStatus(warn("Compaction failed; the transcript is unchanged."));
1216
1297
  }
1217
1298
  })();
1218
1299
  return HANDLED;
@@ -1252,7 +1333,8 @@ var init_transcript = __esm({
1252
1333
  costCommand = {
1253
1334
  name: "cost",
1254
1335
  summary: "Show session cost and token usage",
1255
- aliases: ["usage"],
1336
+ // `usage` is intentionally NOT an alias here: `/usage` is the server-tier
1337
+ // quota command (see integrations.ts). `/cost` shows this session's local stats.
1256
1338
  run(ctx) {
1257
1339
  const stats = ctx.conductor.stats();
1258
1340
  const t = stats.tokens;
@@ -1443,9 +1525,9 @@ function runDebug(ctx) {
1443
1525
  const jsonl = records.map((r) => JSON.stringify(r)).join("\n") + "\n";
1444
1526
  try {
1445
1527
  const dir = mkdtempSync(join7(tmpdir(), "indus-debug-"));
1446
- const path3 = join7(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
1447
- writeFileSync(path3, jsonl, "utf8");
1448
- ctx.setStatus(info(`Diagnostics written to ${path3}`));
1528
+ const path4 = join7(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
1529
+ writeFileSync(path4, jsonl, "utf8");
1530
+ ctx.setStatus(info(`Diagnostics written to ${path4}`));
1449
1531
  } catch (cause) {
1450
1532
  const detail = cause instanceof Error ? cause.message : String(cause);
1451
1533
  ctx.setStatus(warn(`Could not write the diagnostics log (${detail}).`));
@@ -1514,6 +1596,7 @@ var init_workbench = __esm({
1514
1596
  ["Ctrl+J", "Insert a newline"],
1515
1597
  ["Ctrl+Z", "Suspend the process"],
1516
1598
  ["Alt+Up", "Pull the newest queued input back into the composer"],
1599
+ ["Ctrl+X", "Run the newest queued message NOW (stops the current run)"],
1517
1600
  ["! <cmd>", "Run a shell command (kept in context)"],
1518
1601
  ["!! <cmd>", "Run a shell command (excluded from context)"]
1519
1602
  ];
@@ -1598,6 +1681,60 @@ var init_workbench = __esm({
1598
1681
  }
1599
1682
  });
1600
1683
 
1684
+ // src/boot/runners/server-mode.ts
1685
+ function isGatewayEligibleProvider(provider) {
1686
+ return GATEWAY_ELIGIBLE_PROVIDERS.has(provider);
1687
+ }
1688
+ function serverTierProviders() {
1689
+ return [...GATEWAY_ELIGIBLE_PROVIDERS];
1690
+ }
1691
+ async function hasUsableLocalKey(provider, vault) {
1692
+ try {
1693
+ const accounts = [];
1694
+ const def = await vault.defaultAccount(provider);
1695
+ if (def !== void 0 && def.length > 0) accounts.push(def);
1696
+ for (const account of await vault.listAccounts(provider)) {
1697
+ if (!accounts.includes(account)) accounts.push(account);
1698
+ }
1699
+ for (const account of accounts) {
1700
+ const key = await vault.readUsableKey(provider, account);
1701
+ if (typeof key === "string" && key.length > 0) return true;
1702
+ }
1703
+ } catch {
1704
+ }
1705
+ return false;
1706
+ }
1707
+ async function resolveServerMode(_ctx, provider, vault) {
1708
+ if (!isGatewayEligibleProvider(provider)) return { serverMode: false };
1709
+ if (await hasUsableLocalKey(provider, vault)) return { serverMode: false };
1710
+ if (!await hasValidServerToken()) return { serverMode: false };
1711
+ const token = await getServerToken();
1712
+ const access = token?.access_token;
1713
+ if (typeof access !== "string" || access.length === 0) return { serverMode: false };
1714
+ return { serverMode: true, token: access };
1715
+ }
1716
+ async function resolveServerGatewayUrls(ctx, vault) {
1717
+ const map = {};
1718
+ for (const provider of GATEWAY_ELIGIBLE_PROVIDERS) {
1719
+ const result = await resolveServerMode(ctx, provider, vault);
1720
+ if (result.serverMode) {
1721
+ map[provider] = `${INDUS_SERVER_URL}/gateway/${provider}`;
1722
+ }
1723
+ }
1724
+ return map;
1725
+ }
1726
+ var GATEWAY_ELIGIBLE_PROVIDERS;
1727
+ var init_server_mode = __esm({
1728
+ "src/boot/runners/server-mode.ts"() {
1729
+ "use strict";
1730
+ init_server_token();
1731
+ GATEWAY_ELIGIBLE_PROVIDERS = /* @__PURE__ */ new Set([
1732
+ "minimax",
1733
+ "sarvam"
1734
+ ]);
1735
+ }
1736
+ });
1737
+
1601
1738
  // src/capability-deck/contract.ts
1602
1739
  function capabilityId(raw) {
1603
1740
  return raw;
@@ -9173,14 +9310,14 @@ Poll it with action:"poll", id:"${h.id}".`
9173
9310
  case "poll": {
9174
9311
  const h = params.id ? table.get(params.id) : void 0;
9175
9312
  if (!h) return errResult("poll", `No background process with id "${params.id ?? ""}".`);
9176
- const out = drainBuffer(h.stdout, params.sinceLast ?? false);
9177
- const err = drainBuffer(h.stderr, params.sinceLast ?? false);
9313
+ const out2 = drainBuffer(h.stdout, params.sinceLast ?? false);
9314
+ const err2 = drainBuffer(h.stderr, params.sinceLast ?? false);
9178
9315
  const parts = [`Process ${h.id} \u2014 ${statusLine(h)}`];
9179
- if (out.length) parts.push(`--- stdout ---
9180
- ${out.join("\n")}`);
9181
- if (err.length) parts.push(`--- stderr ---
9182
- ${err.join("\n")}`);
9183
- if (out.length === 0 && err.length === 0) parts.push("(no new output)");
9316
+ if (out2.length) parts.push(`--- stdout ---
9317
+ ${out2.join("\n")}`);
9318
+ if (err2.length) parts.push(`--- stderr ---
9319
+ ${err2.join("\n")}`);
9320
+ if (out2.length === 0 && err2.length === 0) parts.push("(no new output)");
9184
9321
  return {
9185
9322
  content: [{ type: "text", text: parts.join("\n") }],
9186
9323
  details: { action: "poll", ok: true, id: h.id, state: h.state, exitCode: h.exitCode }
@@ -9260,8 +9397,8 @@ var init_bg_process_card = __esm({
9260
9397
  handle.signal = sig;
9261
9398
  handle.state = sig ? "signalled" : "exited";
9262
9399
  });
9263
- child.on("error", (err) => {
9264
- pushLines(handle.stderr, `spawn error: ${String(err)}`);
9400
+ child.on("error", (err2) => {
9401
+ pushLines(handle.stderr, `spawn error: ${String(err2)}`);
9265
9402
  handle.state = "exited";
9266
9403
  });
9267
9404
  this.handles.set(id, handle);
@@ -9601,6 +9738,10 @@ function createStructuredOutputTool({
9601
9738
  name,
9602
9739
  label: "Structured Output",
9603
9740
  description: "Return the final machine-readable result for this subagent task.",
9741
+ // Pure in-memory capture — no filesystem/shell/network mutation — so the
9742
+ // subagent permission gate auto-allows it in every mode (a schema-bound
9743
+ // workflow must be able to land its result even under `default`/`plan`).
9744
+ readOnly: true,
9604
9745
  parameters: schema,
9605
9746
  async execute(_toolCallId, params) {
9606
9747
  capture.value = params;
@@ -9708,13 +9849,13 @@ function assembleTree(header, blocks, logLines, maxLines) {
9708
9849
  if (1 + keptLen + summaryCost + logLines.length <= maxLines) break;
9709
9850
  folded.add(i);
9710
9851
  }
9711
- const out = [header];
9712
- if (folded.size) out.push(` \u2026 ${folded.size} earlier phases done`);
9852
+ const out2 = [header];
9853
+ if (folded.size) out2.push(` \u2026 ${folded.size} earlier phases done`);
9713
9854
  blocks.forEach((b, i) => {
9714
- if (!folded.has(i)) out.push(...baseRow(b));
9855
+ if (!folded.has(i)) out2.push(...baseRow(b));
9715
9856
  });
9716
- out.push(...logLines);
9717
- return out;
9857
+ out2.push(...logLines);
9858
+ return out2;
9718
9859
  }
9719
9860
  function compactPhaseLine(marker, phase, done, agents, now) {
9720
9861
  const prefix = ` ${marker} ${phase} ${done}/${agents.length}`;
@@ -9798,7 +9939,7 @@ var init_display = __esm({
9798
9939
 
9799
9940
  // src/workflow-engine/engine.ts
9800
9941
  import vm2 from "node:vm";
9801
- import os from "node:os";
9942
+ import os2 from "node:os";
9802
9943
  async function runWorkflow(script, options) {
9803
9944
  const started = Date.now();
9804
9945
  const { meta, body } = parseWorkflowScript(script);
@@ -9968,7 +10109,7 @@ ${body}
9968
10109
  };
9969
10110
  }
9970
10111
  function defaultConcurrency() {
9971
- const count = os.cpus()?.length;
10112
+ const count = os2.cpus()?.length;
9972
10113
  return typeof count === "number" && count > 0 ? count : 8;
9973
10114
  }
9974
10115
  function createLimiter(limit) {
@@ -10646,8 +10787,8 @@ function memoryBackend() {
10646
10787
  }
10647
10788
  function fsBackend(dir) {
10648
10789
  const fileOf = async (sessionId) => {
10649
- const path3 = await import("node:path");
10650
- return path3.join(dir, `${sessionId}.ndjson`);
10790
+ const path4 = await import("node:path");
10791
+ return path4.join(dir, `${sessionId}.ndjson`);
10651
10792
  };
10652
10793
  return {
10653
10794
  locate: (sessionId) => `${dir}/${sessionId}.ndjson`,
@@ -11060,6 +11201,73 @@ var init_skill_parse = __esm({
11060
11201
  }
11061
11202
  });
11062
11203
 
11204
+ // src/conductor/quota-error.ts
11205
+ function collectErrorSignals(error) {
11206
+ const statuses = [];
11207
+ const parts = [];
11208
+ const seen = /* @__PURE__ */ new Set();
11209
+ const visit = (value, depth) => {
11210
+ if (value === null || value === void 0 || depth > 6) return;
11211
+ if (typeof value === "string") {
11212
+ parts.push(value);
11213
+ return;
11214
+ }
11215
+ if (typeof value === "number") {
11216
+ statuses.push(value);
11217
+ return;
11218
+ }
11219
+ if (typeof value !== "object") return;
11220
+ if (seen.has(value)) return;
11221
+ seen.add(value);
11222
+ const obj = value;
11223
+ for (const key of ["status", "statusCode", "code"]) {
11224
+ const raw = obj[key];
11225
+ if (typeof raw === "number") statuses.push(raw);
11226
+ else if (typeof raw === "string" && /^\d+$/.test(raw)) statuses.push(Number(raw));
11227
+ }
11228
+ if (typeof obj.message === "string") parts.push(obj.message);
11229
+ visit(obj.cause, depth + 1);
11230
+ visit(obj.error, depth + 1);
11231
+ visit(obj.response, depth + 1);
11232
+ visit(obj.body, depth + 1);
11233
+ visit(obj.data, depth + 1);
11234
+ };
11235
+ visit(error, 0);
11236
+ try {
11237
+ parts.push(JSON.stringify(error));
11238
+ } catch {
11239
+ }
11240
+ if (error instanceof Error && typeof error.stack === "string") parts.push(error.stack);
11241
+ return { statuses, text: parts.join("  ").toLowerCase() };
11242
+ }
11243
+ function isQuotaFault(error) {
11244
+ const { statuses, text } = collectErrorSignals(error);
11245
+ if (!text.includes(QUOTA_MARKER)) return false;
11246
+ return statuses.includes(429) || text.includes(QUOTA_MARKER);
11247
+ }
11248
+ function parseUsedLimit(text) {
11249
+ const used = text.match(/"used"\s*:\s*(\d+)/);
11250
+ const limit = text.match(/"limit"\s*:\s*(\d+)/);
11251
+ if (used && limit) {
11252
+ return { used: Number(used[1]), limit: Number(limit[1]) };
11253
+ }
11254
+ return void 0;
11255
+ }
11256
+ function quotaErrorMessage(error) {
11257
+ if (!isQuotaFault(error)) return void 0;
11258
+ const { text } = collectErrorSignals(error);
11259
+ const numbers = parseUsedLimit(text);
11260
+ const usage = numbers ? ` (${numbers.used}/${numbers.limit} tokens this month)` : "";
11261
+ return `You've hit your indus usage limit${usage}. Add your own API key with \`indus signin <provider>\`, or upgrade your plan.`;
11262
+ }
11263
+ var QUOTA_MARKER;
11264
+ var init_quota_error = __esm({
11265
+ "src/conductor/quota-error.ts"() {
11266
+ "use strict";
11267
+ QUOTA_MARKER = "quota_exceeded";
11268
+ }
11269
+ });
11270
+
11063
11271
  // src/capability-deck/cards/plan-file.ts
11064
11272
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync5 } from "node:fs";
11065
11273
  import { join as join8 } from "node:path";
@@ -11072,10 +11280,10 @@ function writePlan(sessionDir, plan) {
11072
11280
  const dir = join8(sessionDir, PLANS_DIRNAME);
11073
11281
  mkdirSync2(dir, { recursive: true });
11074
11282
  const stamp = Date.now().toString(36);
11075
- const path3 = join8(dir, `${stamp}-${planSlug(plan)}.md`);
11076
- writeFileSync2(path3, plan.endsWith("\n") ? plan : `${plan}
11283
+ const path4 = join8(dir, `${stamp}-${planSlug(plan)}.md`);
11284
+ writeFileSync2(path4, plan.endsWith("\n") ? plan : `${plan}
11077
11285
  `, { encoding: "utf8", mode: 384 });
11078
- return path3;
11286
+ return path4;
11079
11287
  }
11080
11288
  var PLANS_DIRNAME, MAX_SLUG_LEN;
11081
11289
  var init_plan_file = __esm({
@@ -11323,10 +11531,10 @@ function flattenMessage(message) {
11323
11531
  return `${TURN_MARK} ${tag} (${message.toolName}): ${text}`;
11324
11532
  }
11325
11533
  case "bashExecution": {
11326
- const out = tidy(message.output);
11534
+ const out2 = tidy(message.output);
11327
11535
  const code = message.exitCode ?? "n/a";
11328
11536
  return `${TURN_MARK} shell$ ${tidy(message.command)} [exit ${code}]
11329
- ${out}`;
11537
+ ${out2}`;
11330
11538
  }
11331
11539
  case "custom": {
11332
11540
  const text = tidy(readContent(message.content));
@@ -11450,7 +11658,8 @@ async function summarize(messages, deps = {}) {
11450
11658
  const options = {
11451
11659
  reasoning: "high",
11452
11660
  ...deps.maxTokens !== void 0 ? { maxTokens: deps.maxTokens } : {},
11453
- ...deps.signal ? { signal: deps.signal } : {}
11661
+ ...deps.signal ? { signal: deps.signal } : {},
11662
+ ...deps.apiKey !== void 0 ? { apiKey: deps.apiKey } : {}
11454
11663
  };
11455
11664
  const complete = deps.complete ?? completeSimple;
11456
11665
  const reply = await complete(deps.model, context, options);
@@ -11593,8 +11802,8 @@ function collectVisibleReadPaths(kept, readToolName) {
11593
11802
  if (!isRecord4(block2) || block2.type !== "toolCall") continue;
11594
11803
  if (block2.name !== readToolName) continue;
11595
11804
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
11596
- const path3 = args && typeof args.path === "string" ? args.path : "";
11597
- if (path3.length > 0) paths.add(path3);
11805
+ const path4 = args && typeof args.path === "string" ? args.path : "";
11806
+ if (path4.length > 0) paths.add(path4);
11598
11807
  }
11599
11808
  }
11600
11809
  return paths;
@@ -11610,8 +11819,8 @@ function collectDroppedReads(dropped, readToolName) {
11610
11819
  if (block2.name !== readToolName) continue;
11611
11820
  const id = typeof block2.id === "string" ? block2.id : "";
11612
11821
  const args = isRecord4(block2.arguments) ? block2.arguments : void 0;
11613
- const path3 = args && typeof args.path === "string" ? args.path : "";
11614
- if (id.length > 0 && path3.length > 0) calls.set(id, { path: path3, order: i });
11822
+ const path4 = args && typeof args.path === "string" ? args.path : "";
11823
+ if (id.length > 0 && path4.length > 0) calls.set(id, { path: path4, order: i });
11615
11824
  }
11616
11825
  }
11617
11826
  if (calls.size === 0) return [];
@@ -11684,7 +11893,7 @@ var init_window_budget = __esm({
11684
11893
  });
11685
11894
 
11686
11895
  // src/conductor/diagnostics.ts
11687
- import * as path from "node:path";
11896
+ import * as path2 from "node:path";
11688
11897
  import { existsSync as existsSync2 } from "node:fs";
11689
11898
  function createDiagnosticKey(d) {
11690
11899
  const { start, end } = d.range;
@@ -11798,7 +12007,7 @@ async function runTscDiagnostics(cwd, signal) {
11798
12007
  const spawn3 = await loadSpawn();
11799
12008
  if (spawn3 === void 0) return [];
11800
12009
  if (signal?.aborted) return [];
11801
- const bin = path.join(cwd, "node_modules", ".bin", "tsc");
12010
+ const bin = path2.join(cwd, "node_modules", ".bin", "tsc");
11802
12011
  const output = await captureOutput(
11803
12012
  spawn3,
11804
12013
  bin,
@@ -11816,7 +12025,7 @@ function parseTscOutput(output, cwd) {
11816
12025
  const m = re.exec(raw);
11817
12026
  if (m === null) continue;
11818
12027
  const [, file, lineStr, colStr, sevWord, code, message] = m;
11819
- const uri = path.isAbsolute(file) ? file : path.resolve(cwd, file);
12028
+ const uri = path2.isAbsolute(file) ? file : path2.resolve(cwd, file);
11820
12029
  const line = Math.max(0, Number(lineStr) - 1);
11821
12030
  const character = Math.max(0, Number(colStr) - 1);
11822
12031
  const diagnostic = {
@@ -11837,7 +12046,7 @@ async function runEslintDiagnostics(files, cwd, signal) {
11837
12046
  const spawn3 = await loadSpawn();
11838
12047
  if (spawn3 === void 0) return [];
11839
12048
  if (signal?.aborted) return [];
11840
- const bin = path.join(cwd, "node_modules", ".bin", "eslint");
12049
+ const bin = path2.join(cwd, "node_modules", ".bin", "eslint");
11841
12050
  const output = await captureOutput(
11842
12051
  spawn3,
11843
12052
  bin,
@@ -11858,10 +12067,10 @@ function parseEslintOutput(output, cwd) {
11858
12067
  } catch {
11859
12068
  return [];
11860
12069
  }
11861
- const out = [];
12070
+ const out2 = [];
11862
12071
  for (const file of parsed) {
11863
12072
  if (!Array.isArray(file.messages) || file.messages.length === 0) continue;
11864
- const uri = path.isAbsolute(file.filePath) ? file.filePath : path.resolve(cwd, file.filePath);
12073
+ const uri = path2.isAbsolute(file.filePath) ? file.filePath : path2.resolve(cwd, file.filePath);
11865
12074
  const diagnostics = [];
11866
12075
  for (const msg of file.messages) {
11867
12076
  const line = Math.max(0, (msg.line ?? 1) - 1);
@@ -11879,9 +12088,9 @@ function parseEslintOutput(output, cwd) {
11879
12088
  ...msg.ruleId ? { code: msg.ruleId } : {}
11880
12089
  });
11881
12090
  }
11882
- if (diagnostics.length > 0) out.push({ uri, diagnostics });
12091
+ if (diagnostics.length > 0) out2.push({ uri, diagnostics });
11883
12092
  }
11884
- return out;
12093
+ return out2;
11885
12094
  }
11886
12095
  function captureOutput(spawn3, bin, args, cwd, signal) {
11887
12096
  return new Promise((resolve8) => {
@@ -11928,11 +12137,11 @@ function captureOutput(spawn3, bin, args, cwd, signal) {
11928
12137
  });
11929
12138
  }
11930
12139
  function isTypeScriptOrJs(filePath) {
11931
- return TS_JS_EXTENSIONS.has(path.extname(filePath).toLowerCase());
12140
+ return TS_JS_EXTENSIONS.has(path2.extname(filePath).toLowerCase());
11932
12141
  }
11933
12142
  function hasTsConfig(cwd) {
11934
12143
  try {
11935
- return existsSync2(path.join(cwd, "tsconfig.json"));
12144
+ return existsSync2(path2.join(cwd, "tsconfig.json"));
11936
12145
  } catch {
11937
12146
  return false;
11938
12147
  }
@@ -11950,7 +12159,7 @@ function hasEslintConfig(cwd) {
11950
12159
  ".eslintrc.yml",
11951
12160
  ".eslintrc.yaml"
11952
12161
  ];
11953
- return candidates.some((name) => existsSync2(path.join(cwd, name)));
12162
+ return candidates.some((name) => existsSync2(path2.join(cwd, name)));
11954
12163
  } catch {
11955
12164
  return false;
11956
12165
  }
@@ -12040,7 +12249,7 @@ var init_diagnostics = __esm({
12040
12249
  if (!this.#enabled) return [];
12041
12250
  if (editedPaths.length === 0) return [];
12042
12251
  const all = await this.#runAll(signal);
12043
- const editedSet = new Set(editedPaths.map((p) => path.resolve(this.#cwd, p)));
12252
+ const editedSet = new Set(editedPaths.map((p) => path2.resolve(this.#cwd, p)));
12044
12253
  if (this.#baseline === void 0) {
12045
12254
  this.#baseline = /* @__PURE__ */ new Map();
12046
12255
  for (const file of all) {
@@ -12065,7 +12274,7 @@ var init_diagnostics = __esm({
12065
12274
  * re-surface (e.g. the agent edited it again). The baseline is left intact.
12066
12275
  */
12067
12276
  clearDeliveredForFile(uri) {
12068
- this.#delivered.delete(path.resolve(this.#cwd, uri));
12277
+ this.#delivered.delete(path2.resolve(this.#cwd, uri));
12069
12278
  }
12070
12279
  /** Drop all cross-turn state and the baseline (re-seeds on the next run). */
12071
12280
  reset() {
@@ -12108,7 +12317,7 @@ var init_diagnostics = __esm({
12108
12317
  // src/conductor/conductor.ts
12109
12318
  import { Agent } from "indusagi/agent";
12110
12319
  import { createZeroUsage as createZeroUsage2 } from "indusagi/ai";
12111
- import * as path2 from "node:path";
12320
+ import * as path3 from "node:path";
12112
12321
  function reduceState(prev, action) {
12113
12322
  switch (action.type) {
12114
12323
  case "phase":
@@ -12167,7 +12376,7 @@ function createSessionConductor(options, deps = {}) {
12167
12376
  const approvalHolder = { resolver: void 0 };
12168
12377
  const approvalDelegate = async (toolName, input, opts) => {
12169
12378
  const installed = approvalHolder.resolver;
12170
- if (installed === void 0) return "deny";
12379
+ if (installed === void 0) return "unavailable";
12171
12380
  return installed(toolName, input, opts);
12172
12381
  };
12173
12382
  const gate = options.canUseTool?.(
@@ -12188,7 +12397,11 @@ function buildLiveAgent(options, deps, gate) {
12188
12397
  if (card === void 0) {
12189
12398
  throw new Error(`createSessionConductor: no model matches "${options.modelId}"`);
12190
12399
  }
12191
- return new LazyAgent(card.model, options, gate);
12400
+ return new LazyAgent(withGatewayBaseUrl(card.model, options.gatewayBaseUrls), options, gate);
12401
+ }
12402
+ function withGatewayBaseUrl(model, gatewayBaseUrls) {
12403
+ const url = gatewayBaseUrls?.[model.provider];
12404
+ return url ? { ...model, baseUrl: url } : model;
12192
12405
  }
12193
12406
  function makeAgent(model, options, gate) {
12194
12407
  const agent = new Agent({
@@ -12278,7 +12491,7 @@ ${outcome.output}`.trimEnd();
12278
12491
  timestamp: Date.now()
12279
12492
  };
12280
12493
  }
12281
- var noopCondense, DEFAULT_RETRY, MAX_CONSECUTIVE_CONDENSE_FAILURES, THINKING_LADDER, DEFAULT_THINKING, PERMISSION_CYCLE, SessionConductorImpl, LazyAgent;
12494
+ var noopCondense, DEFAULT_RETRY, MAX_CONSECUTIVE_CONDENSE_FAILURES, MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT, THINKING_LADDER, DEFAULT_THINKING, PERMISSION_CYCLE, SessionConductorImpl, LazyAgent;
12282
12495
  var init_conductor = __esm({
12283
12496
  "src/conductor/conductor.ts"() {
12284
12497
  "use strict";
@@ -12287,12 +12500,14 @@ var init_conductor = __esm({
12287
12500
  init_signal_hub();
12288
12501
  init_transcript_store();
12289
12502
  init_skill_parse();
12503
+ init_quota_error();
12290
12504
  init_plan_file();
12291
12505
  init_window_budget();
12292
12506
  init_diagnostics();
12293
12507
  noopCondense = (messages) => messages;
12294
12508
  DEFAULT_RETRY = { maxAttempts: 2, baseDelayMs: 250 };
12295
12509
  MAX_CONSECUTIVE_CONDENSE_FAILURES = 3;
12510
+ MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT = 3;
12296
12511
  THINKING_LADDER = ["off", "low", "medium", "high"];
12297
12512
  DEFAULT_THINKING = "off";
12298
12513
  PERMISSION_CYCLE = ["default", "acceptEdits", "plan", "bypass"];
@@ -12324,6 +12539,29 @@ var init_conductor = __esm({
12324
12539
  * to 0 on the next successful condense.
12325
12540
  */
12326
12541
  #condenseFailures = 0;
12542
+ /**
12543
+ * The cancel handle for an in-flight MANUAL condense (`/compact`), or
12544
+ * `undefined` when none is running. {@link cancelCondense} (and {@link abort},
12545
+ * so a user's Esc stops a compaction the same way it stops a turn) abort it;
12546
+ * {@link #runCondense} then discards the digest and reports `"cancelled"`.
12547
+ */
12548
+ #condenseAbort;
12549
+ /**
12550
+ * The session's per-call credential resolver (the same one the framework
12551
+ * Agent uses). The condense seam resolves the bound model's key through it so
12552
+ * the summarizer's model call authenticates exactly like a turn — including
12553
+ * server-tier sessions, whose ONLY credential is the vended session token
12554
+ * (no env var exists for the summarizer to fall back to).
12555
+ */
12556
+ #getApiKey;
12557
+ /**
12558
+ * Whether the next in-flight-turn abort is an EXPECTED steer (Ctrl+X ran
12559
+ * {@link steerNow}): the unwinding turn consumes the rejection quietly —
12560
+ * no terminal fault, no error toast, no busy-spinner drop — because the
12561
+ * steered turn takes over immediately. Reset at every turn start so a stale
12562
+ * flag can never swallow a genuine later fault.
12563
+ */
12564
+ #steerPending = false;
12327
12565
  /**
12328
12566
  * Canonical id of the model to fall back to when the bound model is overloaded
12329
12567
  * (HTTP 529), or `undefined` when no `--fallback-model` was configured. When
@@ -12331,6 +12569,20 @@ var init_conductor = __esm({
12331
12569
  * and retries instead of surfacing a terminal fault.
12332
12570
  */
12333
12571
  #fallbackModelId;
12572
+ /**
12573
+ * Base URL of the indus model gateway to route this session through ("server
12574
+ * mode"), or `undefined` for the normal direct-to-provider path. When set,
12575
+ * every model bind clones the framework model with its `baseUrl` swapped to
12576
+ * this URL (see {@link withGatewayBaseUrl}) so requests flow through the
12577
+ * quota-enforcing server while the session token (vended by `getApiKey`)
12578
+ * authenticates them. Absent leaves the local-key path byte-for-byte unchanged.
12579
+ *
12580
+ * Not `readonly`: {@link updateGatewayBaseUrls} replaces this map when a
12581
+ * mid-session sign-in changes which providers are gateway-eligible, so a
12582
+ * later rebind (see `#bindModel`/`#applyModelCard`) picks up the fresh
12583
+ * routing without waiting for a full conductor rebuild.
12584
+ */
12585
+ #gatewayBaseUrls;
12334
12586
  /**
12335
12587
  * Whether the one-shot fallback swap has already fired for the CURRENT turn.
12336
12588
  * Reset to `false` at the top of every {@link #runTurn} (not per attempt) so a
@@ -12382,6 +12634,13 @@ var init_conductor = __esm({
12382
12634
  * {@link #runPostEditDiagnostics} at the turn-end seam.
12383
12635
  */
12384
12636
  #editedThisTurn = /* @__PURE__ */ new Set();
12637
+ /**
12638
+ * How many diagnostics follow-ups have been auto-injected since the last REAL
12639
+ * user prompt. Reset by {@link submit}; capped by
12640
+ * {@link MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT} so a model that cannot satisfy a
12641
+ * checker can never be re-prompted forever without the user asking again.
12642
+ */
12643
+ #diagnosticFollowUps = 0;
12385
12644
  /**
12386
12645
  * The mode captured the moment plan mode was entered, restored when plan mode is
12387
12646
  * left. `undefined` whenever the session is not in (or paused from) plan mode.
@@ -12421,6 +12680,7 @@ var init_conductor = __esm({
12421
12680
  this.#hub = deps.hub ?? new SignalHub();
12422
12681
  this.#matcher = deps.matcher;
12423
12682
  this.#condense = deps.condense ?? noopCondense;
12683
+ this.#getApiKey = options.getApiKey;
12424
12684
  this.#retry = deps.retry ?? DEFAULT_RETRY;
12425
12685
  this.#autoCompact = options.autoCompact ?? true;
12426
12686
  this.#sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -12428,6 +12688,7 @@ var init_conductor = __esm({
12428
12688
  this.#modeHolder = modeHolder ?? { mode: options.permissionMode ?? "default" };
12429
12689
  this.#approvalHolder = approvalHolder ?? { resolver: void 0 };
12430
12690
  this.#fallbackModelId = options.fallbackModelId;
12691
+ this.#gatewayBaseUrls = options.gatewayBaseUrls;
12431
12692
  this.#workspace = options.workspace ?? process.cwd();
12432
12693
  this.#plansDir = options.plansDir;
12433
12694
  this.#checkpoint = options.checkpoint;
@@ -12444,7 +12705,10 @@ var init_conductor = __esm({
12444
12705
  // no fault initially
12445
12706
  };
12446
12707
  const initial = this.#matcher?.resolveCard(options.modelId);
12447
- this.#boundModel = initial?.model ?? agent.state.model;
12708
+ this.#boundModel = withGatewayBaseUrl(
12709
+ initial?.model ?? agent.state.model,
12710
+ this.#gatewayBaseUrls
12711
+ );
12448
12712
  }
12449
12713
  /* ---- public surface ---- */
12450
12714
  subscribe(handler) {
@@ -12467,12 +12731,18 @@ var init_conductor = __esm({
12467
12731
  return [...this.#matcher.resolveAll("")];
12468
12732
  }
12469
12733
  abort() {
12734
+ if (this.#queue.length > 0) {
12735
+ this.#queue.length = 0;
12736
+ this.#hub.emit({ kind: "queue", count: 0 });
12737
+ }
12738
+ this.#condenseAbort?.abort();
12470
12739
  this.#agent.abort();
12471
12740
  this.#dispatch({ type: "fault", fault: conductorFault("aborted", "turn aborted by caller") });
12472
12741
  this.#hub.emit({ kind: "fault", fault: conductorFault("aborted", "turn aborted by caller") });
12473
12742
  this.#hub.emit({ kind: "idle" });
12474
12743
  }
12475
12744
  async submit(input) {
12745
+ this.#diagnosticFollowUps = 0;
12476
12746
  if (this.#busy) {
12477
12747
  this.enqueue(input);
12478
12748
  return this.#state;
@@ -12507,6 +12777,22 @@ var init_conductor = __esm({
12507
12777
  this.#hub.emit({ kind: "queue", count: this.#queue.length });
12508
12778
  return popped.text;
12509
12779
  }
12780
+ steerNow() {
12781
+ const promoted = this.#queue.pop();
12782
+ if (promoted === void 0) return false;
12783
+ this.#queue.unshift({ mode: "steer", text: promoted.text });
12784
+ this.#hub.emit({ kind: "queue", count: this.#queue.length });
12785
+ if (this.#busy) {
12786
+ this.#steerPending = true;
12787
+ this.#condenseAbort?.abort();
12788
+ this.#agent.abort();
12789
+ return true;
12790
+ }
12791
+ const next = this.#takeNext();
12792
+ this.#hub.emit({ kind: "queue", count: this.#queue.length });
12793
+ void this.submit(next.text);
12794
+ return true;
12795
+ }
12510
12796
  /**
12511
12797
  * Drain the pending-input queue in order, running each entry as its own turn.
12512
12798
  * `steer`-mode entries are taken ahead of plain follow-ups so an interrupt is
@@ -12544,6 +12830,7 @@ var init_conductor = __esm({
12544
12830
  const prompt = skill ? skill.body : input;
12545
12831
  this.#inFlightTools.length = 0;
12546
12832
  this.#fallbackTried = false;
12833
+ this.#steerPending = false;
12547
12834
  this.#checkpoint?.setActiveNodeId(this.#store.head.leaf);
12548
12835
  this.#dispatch({ type: "phase", phase: "streaming" });
12549
12836
  if (this.#diagnostics !== void 0 && !this.#diagnostics.hasBaseline) {
@@ -12555,7 +12842,7 @@ var init_conductor = __esm({
12555
12842
  const baseline = this.#agent.state.messages.length;
12556
12843
  const clean = await this.#runWithRetry(prompt);
12557
12844
  if (!clean) {
12558
- this.#hub.emit({ kind: "idle" });
12845
+ if (this.#queue.length === 0) this.#hub.emit({ kind: "idle" });
12559
12846
  return this.#state;
12560
12847
  }
12561
12848
  await this.#persistTail(baseline);
@@ -12569,7 +12856,7 @@ var init_conductor = __esm({
12569
12856
  this.#dispatch({ type: "head", head: this.#store.head });
12570
12857
  }
12571
12858
  await this.#runPlanHandshakes();
12572
- this.#hub.emit({ kind: "idle" });
12859
+ if (this.#queue.length === 0) this.#hub.emit({ kind: "idle" });
12573
12860
  return this.#state;
12574
12861
  }
12575
12862
  async newSession() {
@@ -12579,6 +12866,7 @@ var init_conductor = __esm({
12579
12866
  this.#queue.length = 0;
12580
12867
  this.#inFlightTools.length = 0;
12581
12868
  this.#condenseFailures = 0;
12869
+ this.#diagnosticFollowUps = 0;
12582
12870
  this.#agent.replaceMessages([]);
12583
12871
  this.#store.startNewSession(newSessionId());
12584
12872
  this.#sessionName = void 0;
@@ -12619,6 +12907,19 @@ var init_conductor = __esm({
12619
12907
  selectModel(id) {
12620
12908
  this.#bindModel(id);
12621
12909
  }
12910
+ /**
12911
+ * Replace the server-tier gateway routing map and immediately re-bind the
12912
+ * currently selected model against it, so a mid-session sign-in (`/login` ->
12913
+ * "Indus Server") takes effect on the very next turn instead of requiring a
12914
+ * session restart. Re-resolves the current model's catalog card via
12915
+ * {@link #bindModel} rather than mutating `#boundModel` directly, so the
12916
+ * rebind goes through the exact same path as `selectModel`/`cycleModel`
12917
+ * (tool deck, thinking level, and state dispatch all stay consistent).
12918
+ */
12919
+ updateGatewayBaseUrls(map) {
12920
+ this.#gatewayBaseUrls = map;
12921
+ this.#bindModel(this.#state.modelId);
12922
+ }
12622
12923
  /**
12623
12924
  * Resolve a model by canonical id, bind it on the agent for subsequent turns,
12624
12925
  * track the full model object, and record the new id in state. A miss surfaces
@@ -12642,8 +12943,9 @@ var init_conductor = __esm({
12642
12943
  * so it must resolve its own card and call straight into here.
12643
12944
  */
12644
12945
  #applyModelCard(card) {
12645
- this.#agent.setModel(card.model);
12646
- this.#boundModel = card.model;
12946
+ const model = withGatewayBaseUrl(card.model, this.#gatewayBaseUrls);
12947
+ this.#agent.setModel(model);
12948
+ this.#boundModel = model;
12647
12949
  this.#agent.setThinkingLevel?.(this.#thinking);
12648
12950
  this.#dispatch({ type: "model", modelId: card.id });
12649
12951
  }
@@ -12672,7 +12974,10 @@ var init_conductor = __esm({
12672
12974
  fault: conductorFault(
12673
12975
  "model",
12674
12976
  `Switched to ${card.id} due to high demand for ${previous}`
12675
- )
12977
+ ),
12978
+ // The turn keeps running on the fallback model — this is an in-turn
12979
+ // notice, not the turn's end, so a busy/in-flight indicator must survive it.
12980
+ transient: true
12676
12981
  });
12677
12982
  return true;
12678
12983
  }
@@ -12699,11 +13004,32 @@ var init_conductor = __esm({
12699
13004
  return true;
12700
13005
  } catch (error) {
12701
13006
  unsubscribe();
13007
+ if (this.#steerPending) {
13008
+ this.#steerPending = false;
13009
+ return false;
13010
+ }
13011
+ if (isQuotaFault(error)) {
13012
+ const fault2 = conductorFault(
13013
+ "model",
13014
+ quotaErrorMessage(error) ?? "You've hit your indus usage limit.",
13015
+ error
13016
+ );
13017
+ this.#dispatch({ type: "fault", fault: fault2 });
13018
+ this.#hub.emit({ kind: "fault", fault: fault2 });
13019
+ return false;
13020
+ }
12702
13021
  if (attempt < this.#retry.maxAttempts && isTransient(error)) {
12703
13022
  attempt++;
12704
13023
  this.#hub.emit({
12705
13024
  kind: "fault",
12706
- fault: conductorFault("model", `transient model fault; retry ${attempt}`, error)
13025
+ fault: conductorFault("model", `transient model fault; retry ${attempt}`, error),
13026
+ // The retry loop is about to try again on the SAME turn — this is an
13027
+ // in-turn notice, not the turn's end. Without this flag the console
13028
+ // clears its busy/in-flight indicator here and never sets it back
13029
+ // (busy only flips true again at the START of the next turn), so the
13030
+ // agent looks "stuck" for the rest of a turn that is actually still
13031
+ // retrying/streaming underneath.
13032
+ transient: true
12707
13033
  });
12708
13034
  await this.#sleep(this.#retry.baseDelayMs * 2 ** (attempt - 1));
12709
13035
  continue;
@@ -12739,6 +13065,7 @@ var init_conductor = __esm({
12739
13065
  this.#capturePlanSignals(event.result);
12740
13066
  }
12741
13067
  for (const signal of translateAgentEvent(event)) {
13068
+ if (signal.kind === "fault" && this.#steerPending) continue;
12742
13069
  if (signal.kind === "turn_end") {
12743
13070
  this.#dispatch({
12744
13071
  type: "usage",
@@ -12765,7 +13092,7 @@ var init_conductor = __esm({
12765
13092
  const raw = args?.path;
12766
13093
  if (typeof raw !== "string" || raw.length === 0) return;
12767
13094
  if (!isTypeScriptOrJs(raw)) return;
12768
- this.#editedThisTurn.add(path2.resolve(this.#workspace, raw));
13095
+ this.#editedThisTurn.add(path3.resolve(this.#workspace, raw));
12769
13096
  }
12770
13097
  /* ---- plan mode ---- */
12771
13098
  /**
@@ -12911,8 +13238,13 @@ var init_conductor = __esm({
12911
13238
  * Invariants:
12912
13239
  * - **never faults the turn** — every failure (a slow/absent checker, a
12913
13240
  * parse error) is swallowed and the set is always cleared in `finally`;
12914
- * - **re-entrancy safe** — the injected follow-up turn edits nothing, so the
12915
- * set is empty and this short-circuits, avoiding an infinite loop;
13241
+ * - **can never loop forever** — a diagnostic key is delivered AT MOST ONCE
13242
+ * per session (the engine's delivered ledger is never cleared here; a
13243
+ * "fix" turn that fails to eliminate the same error is NOT re-prompted
13244
+ * with it), and even genuinely new errors stop re-prompting after
13245
+ * {@link MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT} consecutive follow-ups —
13246
+ * the agent must not keep running itself after its final report just
13247
+ * because a checker stays red;
12916
13248
  * - inert when diagnostics are disabled (`#diagnostics === undefined`).
12917
13249
  */
12918
13250
  async #runPostEditDiagnostics() {
@@ -12921,11 +13253,15 @@ var init_conductor = __esm({
12921
13253
  this.#editedThisTurn.clear();
12922
13254
  return;
12923
13255
  }
13256
+ if (this.#diagnosticFollowUps >= MAX_DIAGNOSTIC_FOLLOWUPS_PER_PROMPT) {
13257
+ this.#editedThisTurn.clear();
13258
+ return;
13259
+ }
12924
13260
  const edited = [...this.#editedThisTurn];
12925
13261
  try {
12926
- for (const uri of edited) engine.clearDeliveredForFile(uri);
12927
13262
  const files = await engine.newDiagnostics(edited);
12928
13263
  if (files.length > 0) {
13264
+ this.#diagnosticFollowUps++;
12929
13265
  this.enqueue(
12930
13266
  "Diagnostics for files you just changed (fix these before continuing):\n\n" + formatDiagnosticsSummary(files),
12931
13267
  "followUp"
@@ -12962,12 +13298,29 @@ var init_conductor = __esm({
12962
13298
  }
12963
13299
  /**
12964
13300
  * Manually drive the same condense path the auto-compactor uses, regardless of
12965
- * the auto-compaction flag or the soft threshold. Emits `{ kind: "compacted" }`
12966
- * and rebinds the agent's context when the hook shrinks the branch. Safe to
12967
- * invoke while idle.
13301
+ * the auto-compaction flag or the soft threshold, and report the outcome.
13302
+ *
13303
+ * Runs under the busy flag so a prompt submitted mid-compaction QUEUES (and
13304
+ * drains once the condense settles) instead of racing the rebind; is
13305
+ * cancellable via {@link cancelCondense} (the result is discarded); and
13306
+ * refuses (`"busy"`) while a real turn is in flight rather than condensing a
13307
+ * branch that is still growing.
12968
13308
  */
12969
13309
  async condense() {
12970
- await this.#runCondense(true);
13310
+ if (this.#busy) return "busy";
13311
+ this.#busy = true;
13312
+ this.#condenseAbort = new AbortController();
13313
+ try {
13314
+ const outcome = await this.#runCondense(true, this.#condenseAbort.signal);
13315
+ await this.#drainQueue();
13316
+ return outcome;
13317
+ } finally {
13318
+ this.#condenseAbort = void 0;
13319
+ this.#busy = false;
13320
+ }
13321
+ }
13322
+ cancelCondense() {
13323
+ this.#condenseAbort?.abort();
12971
13324
  }
12972
13325
  /* ---- branch navigation ---- */
12973
13326
  async fork(entryId) {
@@ -13168,27 +13521,51 @@ var init_conductor = __esm({
13168
13521
  * to a local stub). A throw surfaces an `overflow` fault and bumps the
13169
13522
  * consecutive-failure counter (the circuit breaker); a clean run resets it.
13170
13523
  */
13171
- async #runCondense(force) {
13524
+ async #runCondense(force, signal) {
13172
13525
  const branch = this.#store.pathTo();
13526
+ const prior = this.#state.phase;
13173
13527
  this.#dispatch({ type: "phase", phase: "condensing" });
13174
- this.#hub.emit({ kind: "compacted" });
13175
13528
  try {
13176
13529
  const before = branch.map((entry) => entry.content);
13530
+ let apiKey;
13531
+ if (this.#boundModel !== void 0 && this.#getApiKey !== void 0) {
13532
+ try {
13533
+ apiKey = await this.#getApiKey(String(this.#boundModel.provider));
13534
+ } catch {
13535
+ }
13536
+ }
13177
13537
  const after = await this.#condense(before, {
13178
13538
  force,
13179
13539
  ...this.#boundModel !== void 0 ? { model: this.#boundModel } : {},
13180
- contextTokens: this.#state.contextTokens
13540
+ contextTokens: this.#state.contextTokens,
13541
+ ...signal !== void 0 ? { signal } : {},
13542
+ ...apiKey !== void 0 ? { apiKey } : {}
13181
13543
  });
13182
- if (after.length < before.length) {
13183
- this.#agent.replaceMessages(after);
13184
- await this.#persistCondenseBoundary(branch, after);
13544
+ if (signal?.aborted) {
13545
+ this.#dispatch({ type: "phase", phase: prior });
13546
+ return "cancelled";
13185
13547
  }
13548
+ if (after.length >= before.length) {
13549
+ this.#condenseFailures = 0;
13550
+ this.#dispatch({ type: "phase", phase: prior });
13551
+ return "nothing";
13552
+ }
13553
+ this.#agent.replaceMessages([...after]);
13554
+ await this.#persistCondenseBoundary(branch, after);
13186
13555
  this.#condenseFailures = 0;
13556
+ if (force) {
13557
+ this.#dispatch({ type: "usage", usage: createZeroUsage2(), contextTokens: 0 });
13558
+ await this.#persistUsage();
13559
+ }
13560
+ this.#hub.emit({ kind: "compacted", manual: force });
13561
+ this.#dispatch({ type: "phase", phase: prior });
13562
+ return "condensed";
13187
13563
  } catch (error) {
13188
13564
  this.#condenseFailures++;
13189
13565
  const fault = conductorFault("overflow", "auto-condense failed", error);
13190
13566
  this.#dispatch({ type: "fault", fault });
13191
13567
  this.#hub.emit({ kind: "fault", fault });
13568
+ return "failed";
13192
13569
  }
13193
13570
  }
13194
13571
  /**
@@ -13486,8 +13863,8 @@ function tokenizeArgv(slice) {
13486
13863
  flush();
13487
13864
  return tokens;
13488
13865
  }
13489
- function stripWrappers(argv) {
13490
- const rest = [...argv];
13866
+ function stripWrappers(argv2) {
13867
+ const rest = [...argv2];
13491
13868
  let elevated = false;
13492
13869
  for (; ; ) {
13493
13870
  const head = rest[0];
@@ -13537,20 +13914,20 @@ function consumeWrapperOptions(rest, wrapper) {
13537
13914
  }
13538
13915
  function parseBashCommand(command) {
13539
13916
  if (typeof command !== "string" || command.trim().length === 0) return [];
13540
- const out = [];
13541
- parseInto(command, out);
13542
- return out;
13917
+ const out2 = [];
13918
+ parseInto(command, out2);
13919
+ return out2;
13543
13920
  }
13544
- function parseInto(command, out) {
13921
+ function parseInto(command, out2) {
13545
13922
  for (const slice of splitTopLevel(command)) {
13546
13923
  const { outer, inner } = extractSubshells(slice);
13547
13924
  if (outer.length > 0) {
13548
- const argv = tokenizeArgv(outer);
13549
- if (argv.length > 0) {
13550
- const { rest, elevated } = stripWrappers(argv);
13925
+ const argv2 = tokenizeArgv(outer);
13926
+ if (argv2.length > 0) {
13927
+ const { rest, elevated } = stripWrappers(argv2);
13551
13928
  const name = rest[0];
13552
13929
  if (name !== void 0 && name.length > 0) {
13553
- out.push({
13930
+ out2.push({
13554
13931
  name: name.toLowerCase(),
13555
13932
  args: rest.slice(1),
13556
13933
  elevated,
@@ -13560,7 +13937,7 @@ function parseInto(command, out) {
13560
13937
  }
13561
13938
  }
13562
13939
  for (const body of inner) {
13563
- if (body.trim().length > 0) parseInto(body, out);
13940
+ if (body.trim().length > 0) parseInto(body, out2);
13564
13941
  }
13565
13942
  }
13566
13943
  }
@@ -13861,8 +14238,18 @@ function resolveRuleDecision(toolName, input, rules, mode, readOnlyExtra) {
13861
14238
  }
13862
14239
  return "ask";
13863
14240
  }
14241
+ function allowAlwaysRuleStrings(toolName, input) {
14242
+ if (isBashTool(toolName)) {
14243
+ const bag = input;
14244
+ const command = typeof input === "string" ? input : bag !== null && typeof bag === "object" && typeof bag.command === "string" ? bag.command : "";
14245
+ if (command.length > 0) {
14246
+ return bashSubcommandSubjects(command).map((subject) => `${toolName}(${subject})`);
14247
+ }
14248
+ }
14249
+ return [toolName];
14250
+ }
13864
14251
  function noResolverMessage(toolName) {
13865
- 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.`;
14252
+ 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.`;
13866
14253
  }
13867
14254
  function declinedMessage(toolName) {
13868
14255
  return `Tool "${toolName}" was denied by the user.`;
@@ -13904,15 +14291,34 @@ function createPermissionGate(config) {
13904
14291
  } catch {
13905
14292
  choice = "deny";
13906
14293
  }
13907
- if (choice === "deny") {
13908
- return { behavior: "deny", message: declinedMessage(toolName) };
14294
+ if (choice === "allow-once" || choice === "allow-always") {
14295
+ if (choice === "allow-always" && config.appendAllowRule) {
14296
+ for (const raw of allowAlwaysRuleStrings(toolName, input)) {
14297
+ config.appendAllowRule(makeRule("allow", raw));
14298
+ }
14299
+ }
14300
+ return { behavior: "allow" };
13909
14301
  }
13910
- if (choice === "allow-always" && config.appendAllowRule) {
13911
- config.appendAllowRule(makeRule("allow", toolName));
14302
+ if (choice === "unavailable") {
14303
+ return { behavior: "deny", message: noResolverMessage(toolName) };
13912
14304
  }
13913
- return { behavior: "allow" };
14305
+ return { behavior: "deny", message: declinedMessage(toolName) };
13914
14306
  };
13915
14307
  }
14308
+ function collectReadOnlyToolNames(tools) {
14309
+ const names = /* @__PURE__ */ new Set();
14310
+ for (const tool of tools) {
14311
+ if (tool.readOnly === true) names.add(tool.name);
14312
+ }
14313
+ return names;
14314
+ }
14315
+ function createSubagentPermissionGate(policy, tools) {
14316
+ return createPermissionGate({
14317
+ rules: policy.rules,
14318
+ mode: policy.mode,
14319
+ readOnlyToolNames: collectReadOnlyToolNames(tools)
14320
+ });
14321
+ }
13916
14322
  var READ_ONLY_TOOL_NAMES, EDIT_TOOL_NAMES;
13917
14323
  var init_permissions = __esm({
13918
14324
  "src/conductor/permissions.ts"() {
@@ -14009,13 +14415,22 @@ function createWorkflowAgentRunner(opts) {
14009
14415
  if (model === void 0) {
14010
14416
  throw new Error(`no model resolved for workflow subagent: ${options.model ?? opts.modelId}`);
14011
14417
  }
14418
+ const gatewayBaseUrls = await opts.getGatewayBaseUrls?.();
14419
+ const tools = buildTools();
14012
14420
  agent = new Agent2({
14013
14421
  initialState: {
14014
- model,
14422
+ model: withGatewayBaseUrl(model, gatewayBaseUrls),
14015
14423
  systemPrompt: system,
14016
- tools: buildTools()
14424
+ tools
14017
14425
  },
14018
- ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {}
14426
+ ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {},
14427
+ // Gate the subagent's inner tool loop with the parent session's live
14428
+ // policy (rules + current mode). Resolver-less: `ask` denies — a
14429
+ // subagent cannot prompt — while deny rules, plan mode, and the
14430
+ // catastrophic-bash blocklist are enforced per inner tool call. The
14431
+ // `structured_output` capture tool is flagged readOnly, so a
14432
+ // schema-bound workflow still lands its result in every mode.
14433
+ ...opts.permissionPolicy !== void 0 ? { canUseTool: createSubagentPermissionGate(opts.permissionPolicy, tools) } : {}
14019
14434
  });
14020
14435
  }
14021
14436
  const activity = { tokensIn: 0, tokensOut: 0, toolCount: 0 };
@@ -14858,11 +15273,11 @@ function escapeChar(ch) {
14858
15273
  }
14859
15274
  }
14860
15275
  function escapeHtml(text) {
14861
- let out = "";
15276
+ let out2 = "";
14862
15277
  for (const ch of text) {
14863
- out += escapeChar(ch) ?? ch;
15278
+ out2 += escapeChar(ch) ?? ch;
14864
15279
  }
14865
- return out;
15280
+ return out2;
14866
15281
  }
14867
15282
  function classesFor(state) {
14868
15283
  const classes = [];
@@ -14908,15 +15323,15 @@ function paintSgr(input) {
14908
15323
  if (input.length === 0) return "";
14909
15324
  const tokens = tokenizeSgr(input);
14910
15325
  let state = { ...SGR_INITIAL_STATE };
14911
- let out = "";
15326
+ let out2 = "";
14912
15327
  for (const token of tokens) {
14913
15328
  if (token.kind === "sgr") {
14914
15329
  state = foldSgr(state, token.params);
14915
15330
  } else {
14916
- out += renderRun(token.text, state);
15331
+ out2 += renderRun(token.text, state);
14917
15332
  }
14918
15333
  }
14919
- return out;
15334
+ return out2;
14920
15335
  }
14921
15336
  var BASE_PALETTE, BRIGHT_PALETTE, PALETTE_16, CODE_TABLE, ESC, CLASS_PREFIX, FLAG_CLASSES;
14922
15337
  var init_sgr = __esm({
@@ -15129,13 +15544,13 @@ function escapeRegExp(text) {
15129
15544
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15130
15545
  }
15131
15546
  function fill(template, values) {
15132
- let out = template;
15547
+ let out2 = template;
15133
15548
  for (const token of Object.values(SHELL_SLOTS)) {
15134
15549
  const value = values[token] ?? "";
15135
15550
  const marker = new RegExp(`\\{\\{${escapeRegExp(token)}\\}\\}`, "g");
15136
- out = out.replace(marker, () => value);
15551
+ out2 = out2.replace(marker, () => value);
15137
15552
  }
15138
- return out;
15553
+ return out2;
15139
15554
  }
15140
15555
  var PAGE_STYLES, CLIENT_SCRIPT, PAGE_SHELL;
15141
15556
  var init_template = __esm({
@@ -15413,8 +15828,8 @@ function configuredMarked() {
15413
15828
  return marked;
15414
15829
  }
15415
15830
  function renderMarkdown(md, source) {
15416
- const out = md.parse(source, { async: false });
15417
- return typeof out === "string" ? out : "";
15831
+ const out2 = md.parse(source, { async: false });
15832
+ return typeof out2 === "string" ? out2 : "";
15418
15833
  }
15419
15834
  function renderImage(part) {
15420
15835
  const src = `data:${escapeHtml2(part.mimeType)};base64,${part.data}`;
@@ -15571,8 +15986,9 @@ var init_transcript_export = __esm({
15571
15986
 
15572
15987
  // src/console/slash/commands/integrations.ts
15573
15988
  import { spawnSync } from "node:child_process";
15574
- import { writeFileSync as writeFileSync3 } from "node:fs";
15575
- import { resolve as resolve5 } from "node:path";
15989
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
15990
+ import { homedir as homedir2 } from "node:os";
15991
+ import { join as join10, resolve as resolve5 } from "node:path";
15576
15992
  import {
15577
15993
  createMCPAgentToolFactory,
15578
15994
  loadMCPConfig,
@@ -15631,13 +16047,13 @@ function exportTranscriptHtml(ctx, target) {
15631
16047
  if (messages.length === 0) {
15632
16048
  return { ok: false, reason: "Nothing to export yet \u2014 the transcript is empty." };
15633
16049
  }
15634
- const path3 = target.length > 0 ? resolve5(process.cwd(), target) : defaultExportPath();
16050
+ const path4 = target.length > 0 ? resolve5(process.cwd(), target) : defaultExportPath();
15635
16051
  try {
15636
16052
  const html = publishTranscript(toPublishEntries(messages), {
15637
16053
  title: "Session Transcript"
15638
16054
  });
15639
- writeFileSync3(path3, html, "utf-8");
15640
- return { ok: true, path: path3 };
16055
+ writeFileSync3(path4, html, "utf-8");
16056
+ return { ok: true, path: path4 };
15641
16057
  } catch (cause) {
15642
16058
  const message = cause instanceof Error ? cause.message : String(cause);
15643
16059
  return { ok: false, reason: `Could not write the HTML export: ${message}` };
@@ -15860,7 +16276,7 @@ function runShare(ctx) {
15860
16276
  });
15861
16277
  return HANDLED;
15862
16278
  }
15863
- function runLogin(ctx) {
16279
+ function runLogin2(ctx) {
15864
16280
  const provider = ctx.args.trim().toLowerCase();
15865
16281
  if (provider.length > 0) {
15866
16282
  ctx.openModal("signIn", { providerId: provider });
@@ -15869,10 +16285,82 @@ function runLogin(ctx) {
15869
16285
  }
15870
16286
  return HANDLED;
15871
16287
  }
16288
+ function hasLocalProviderKey(provider) {
16289
+ if (provider.length === 0) return false;
16290
+ try {
16291
+ const file = join10(homedir2(), ".indusagi", "agent", "auth.json");
16292
+ const vault = JSON.parse(readFileSync6(file, "utf8"));
16293
+ const entry = vault[provider];
16294
+ return entry !== null && typeof entry === "object" && Object.keys(entry).length > 0;
16295
+ } catch {
16296
+ return false;
16297
+ }
16298
+ }
16299
+ function runStatus(ctx) {
16300
+ const modelId = ctx.conductor.snapshot().modelId;
16301
+ const provider = modelId.includes("/") ? modelId.split("/")[0] : modelId;
16302
+ void (async () => {
16303
+ let signedIn = false;
16304
+ try {
16305
+ signedIn = await hasValidServerToken();
16306
+ } catch {
16307
+ signedIn = false;
16308
+ }
16309
+ const eligible = isGatewayEligibleProvider(provider);
16310
+ const localKey = hasLocalProviderKey(provider);
16311
+ const viaServer = signedIn && eligible && !localKey;
16312
+ if (viaServer) {
16313
+ ctx.setStatus(
16314
+ info(`\u25CF server tier \u2014 ${modelId} routing via ${INDUS_SERVER_URL} (signed in)`)
16315
+ );
16316
+ } else if (localKey) {
16317
+ ctx.setStatus(
16318
+ info(`\u25CB local key \u2014 ${modelId}${signedIn ? " (signed in to server)" : ""}`)
16319
+ );
16320
+ } else if (!signedIn) {
16321
+ ctx.setStatus(
16322
+ warn(`signed out \u2014 ${modelId} has no key. Run /login to use the server tier.`)
16323
+ );
16324
+ } else {
16325
+ ctx.setStatus(
16326
+ info(`signed in \u2014 ${modelId} (${provider} is not gateway-eligible; needs a local key)`)
16327
+ );
16328
+ }
16329
+ })();
16330
+ return HANDLED;
16331
+ }
16332
+ function runUsage(ctx) {
16333
+ void (async () => {
16334
+ const token = await getServerToken();
16335
+ if (!token?.access_token) {
16336
+ ctx.setStatus(warn("Not signed in to the server \u2014 run /login to use the server tier."));
16337
+ return;
16338
+ }
16339
+ try {
16340
+ const res = await fetch(`${INDUS_SERVER_URL}/api/usage`, {
16341
+ headers: { Authorization: `Bearer ${token.access_token}` }
16342
+ });
16343
+ if (!res.ok) {
16344
+ ctx.setStatus(warn(`Couldn't read usage (HTTP ${res.status}).`));
16345
+ return;
16346
+ }
16347
+ const u = await res.json();
16348
+ const pct = u.limit > 0 ? Math.round(u.used / u.limit * 100) : 0;
16349
+ 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");
16350
+ ctx.setStatus(u.allowed ? info(line) : warn(line));
16351
+ } catch (cause) {
16352
+ const message = cause instanceof Error ? cause.message : String(cause);
16353
+ ctx.setStatus(warn(`Usage check failed: ${message}`));
16354
+ }
16355
+ })();
16356
+ return HANDLED;
16357
+ }
15872
16358
  var CLIPBOARD_WRITERS, PUBLISH_ROLES, mcpPool, mcpServers, memoryEnabled, MEMORY_SUBS, COMPOSIO_ENV_KEY, composioGateway, composioResolved, COMPOSIO_NO_KEY, COMPOSIO_SUBS, runMemory, runComposio, integrationCommands;
15873
16359
  var init_integrations = __esm({
15874
16360
  "src/console/slash/commands/integrations.ts"() {
15875
16361
  "use strict";
16362
+ init_server_token();
16363
+ init_server_mode();
15876
16364
  init_capability_deck();
15877
16365
  init_transcript_export();
15878
16366
  init_shared();
@@ -16064,17 +16552,31 @@ var init_integrations = __esm({
16064
16552
  summary: "Sign in to a model provider.",
16065
16553
  takesArgs: true,
16066
16554
  run(ctx) {
16067
- return runLogin(ctx);
16555
+ return runLogin2(ctx);
16068
16556
  }
16069
16557
  },
16070
16558
  {
16071
16559
  name: "logout",
16072
- summary: "Sign out of a model provider.",
16560
+ summary: "Sign out: pick a provider account or the Indus Server session to clear.",
16073
16561
  run(ctx) {
16074
16562
  ctx.openModal("signOut");
16075
16563
  return HANDLED;
16076
16564
  }
16077
16565
  },
16566
+ {
16567
+ name: "status",
16568
+ summary: "Show the server session and whether this model uses the server.",
16569
+ run(ctx) {
16570
+ return runStatus(ctx);
16571
+ }
16572
+ },
16573
+ {
16574
+ name: "usage",
16575
+ summary: "Show your server usage this month (tokens used / limit / cost).",
16576
+ run(ctx) {
16577
+ return runUsage(ctx);
16578
+ }
16579
+ },
16078
16580
  {
16079
16581
  name: "mcp",
16080
16582
  summary: "Manage MCP servers and their tools.",
@@ -16199,19 +16701,19 @@ __export(builtins_exports, {
16199
16701
  FAMILY: () => FAMILY,
16200
16702
  SLASH_COMMANDS: () => SLASH_COMMANDS
16201
16703
  });
16202
- import { homedir as homedir2 } from "node:os";
16203
- import { join as join10 } from "node:path";
16704
+ import { homedir as homedir3 } from "node:os";
16705
+ import { join as join11 } from "node:path";
16204
16706
  function dynamicRoots() {
16205
- const home = homedir2();
16707
+ const home = homedir3();
16206
16708
  const cwd = process.cwd();
16207
16709
  return {
16208
16710
  skills: [
16209
- { dir: join10(cwd, PROJECT_DIR, "skills"), origin: "project" },
16210
- { dir: join10(home, PROJECT_DIR, "skills"), origin: "user" }
16711
+ { dir: join11(cwd, PROJECT_DIR, "skills"), origin: "project" },
16712
+ { dir: join11(home, PROJECT_DIR, "skills"), origin: "user" }
16211
16713
  ],
16212
16714
  macros: [
16213
- { dir: join10(cwd, PROJECT_DIR, "commands"), origin: "project", label: "project" },
16214
- { dir: join10(home, PROJECT_DIR, "commands"), origin: "user", label: "user" }
16715
+ { dir: join11(cwd, PROJECT_DIR, "commands"), origin: "project", label: "project" },
16716
+ { dir: join11(home, PROJECT_DIR, "commands"), origin: "user", label: "user" }
16215
16717
  ]
16216
16718
  };
16217
16719
  }
@@ -16274,7 +16776,7 @@ var init_builtins = __esm({
16274
16776
  // src/entry.ts
16275
16777
  import { fileURLToPath as fileURLToPath2 } from "node:url";
16276
16778
  import { realpathSync as realpathSync3 } from "node:fs";
16277
- import { argv as processArgv, env } from "node:process";
16779
+ import { argv as processArgv, env as env2 } from "node:process";
16278
16780
 
16279
16781
  // src/workspace/brand.ts
16280
16782
  var BRAND = Object.freeze({
@@ -16289,7 +16791,7 @@ var BRAND = Object.freeze({
16289
16791
  envShareViewer: "INDUSAGI_SHARE_VIEWER_URL",
16290
16792
  shareViewerUrl: "https://buildwithindusagi.ai/session/"
16291
16793
  });
16292
- var VERSION = "0.2.3";
16794
+ var VERSION = "0.2.4";
16293
16795
 
16294
16796
  // src/workspace/locator.ts
16295
16797
  import { homedir } from "node:os";
@@ -16321,8 +16823,8 @@ var DIRECTORY_KEYS = [
16321
16823
  "themesDir",
16322
16824
  "exportTemplateDir"
16323
16825
  ];
16324
- function resolveProfileRoot(brand, home, cwd, env2) {
16325
- const override = env2[brand.envProfileDir]?.trim();
16826
+ function resolveProfileRoot(brand, home, cwd, env3) {
16827
+ const override = env3[brand.envProfileDir]?.trim();
16326
16828
  if (override) {
16327
16829
  return expandHome(override, home, cwd);
16328
16830
  }
@@ -16344,8 +16846,8 @@ function createWorkspace(overrides = {}) {
16344
16846
  const brand = overrides.brand ?? BRAND;
16345
16847
  const home = overrides.home ?? homedir();
16346
16848
  const cwd = overrides.cwd ?? process.cwd();
16347
- const env2 = overrides.env ?? process.env;
16348
- const profileRoot = resolveProfileRoot(brand, home, cwd, env2);
16849
+ const env3 = overrides.env ?? process.env;
16850
+ const profileRoot = resolveProfileRoot(brand, home, cwd, env3);
16349
16851
  const entries = Object.keys(LAYOUT).map((key) => {
16350
16852
  const segments = LAYOUT[key];
16351
16853
  const absolute = segments.length === 0 ? profileRoot : join(profileRoot, ...segments);
@@ -16730,6 +17232,12 @@ var FLAG_SPECS = [
16730
17232
  group: "tools",
16731
17233
  describe: "Attach an external MCP server endpoint (comma-separated or repeated)."
16732
17234
  },
17235
+ {
17236
+ name: "--permission-mode",
17237
+ kind: "string",
17238
+ group: "tools",
17239
+ describe: "Open in the given permission mode: default, acceptEdits, plan, or bypass. On headless runs (--print/--json) `default` denies tools that would prompt."
17240
+ },
16733
17241
  /* ---- Meta ------------------------------------------------------------ */
16734
17242
  {
16735
17243
  name: "--help",
@@ -16812,7 +17320,7 @@ function accumulateList(state, key, items) {
16812
17320
  const base = Array.isArray(prior) ? prior : [];
16813
17321
  state.flags[key] = [...base, ...items];
16814
17322
  }
16815
- function applyFlag(spec, inline, argv, i, state) {
17323
+ function applyFlag(spec, inline, argv2, i, state) {
16816
17324
  const key = flagKey(spec.name);
16817
17325
  if (spec.kind === "boolean") {
16818
17326
  state.flags[key] = true;
@@ -16821,7 +17329,7 @@ function applyFlag(spec, inline, argv, i, state) {
16821
17329
  let value = inline;
16822
17330
  let consumed = i;
16823
17331
  if (value === void 0) {
16824
- const next = argv[i + 1];
17332
+ const next = argv2[i + 1];
16825
17333
  if (next !== void 0 && !isFlagToken(next)) {
16826
17334
  value = next;
16827
17335
  consumed = i + 1;
@@ -16871,11 +17379,11 @@ function resolveTools(flags) {
16871
17379
  if (raw === void 0) return void 0;
16872
17380
  return raw.filter((name) => isToolName(name));
16873
17381
  }
16874
- function readInvocation(argv) {
17382
+ function readInvocation(argv2) {
16875
17383
  const state = { flags: {}, positionals: [], fileRefs: [] };
16876
17384
  let optionsTerminated = false;
16877
- for (let i = 0; i < argv.length; i += 1) {
16878
- const token = argv[i];
17385
+ for (let i = 0; i < argv2.length; i += 1) {
17386
+ const token = argv2[i];
16879
17387
  if (optionsTerminated) {
16880
17388
  pushPositional(state, token);
16881
17389
  continue;
@@ -16895,7 +17403,7 @@ function readInvocation(argv) {
16895
17403
  const { name, inline } = splitInline(token);
16896
17404
  const spec = TOKEN_INDEX.get(name);
16897
17405
  if (spec) {
16898
- i = applyFlag(spec, inline, argv, i, state);
17406
+ i = applyFlag(spec, inline, argv2, i, state);
16899
17407
  continue;
16900
17408
  }
16901
17409
  if (isShortCluster(token) && applyShortCluster(token, state)) {
@@ -17044,11 +17552,11 @@ function writeSources(store, sources) {
17044
17552
  function readSource(rest) {
17045
17553
  return rest.find((token) => !token.startsWith("-"));
17046
17554
  }
17047
- async function runPackageCommand(argv, opts) {
17048
- const command = asPackageCommand(argv[0]);
17555
+ async function runPackageCommand(argv2, opts) {
17556
+ const command = asPackageCommand(argv2[0]);
17049
17557
  if (command === void 0) return { handled: false };
17050
17558
  const io = opts.io ?? defaultPackageIo();
17051
- const rest = argv.slice(1);
17559
+ const rest = argv2.slice(1);
17052
17560
  switch (command) {
17053
17561
  case "install":
17054
17562
  return doInstall(rest, opts.store, io);
@@ -17128,7 +17636,7 @@ function doConfig(store, io) {
17128
17636
  }
17129
17637
 
17130
17638
  // src/launch/credentials.ts
17131
- import { createInterface } from "node:readline/promises";
17639
+ import { createInterface as createInterface2 } from "node:readline/promises";
17132
17640
  import { getEnvApiKey, getOAuthProvider as getOAuthProvider2 } from "indusagi/ai";
17133
17641
 
17134
17642
  // src/launch/oauth.ts
@@ -17141,6 +17649,256 @@ import {
17141
17649
  openaiCodexOAuthProvider,
17142
17650
  registerOAuthProvider
17143
17651
  } from "indusagi/ai";
17652
+
17653
+ // src/launch/login.ts
17654
+ init_server_token();
17655
+ import { createInterface } from "node:readline/promises";
17656
+ import chalk from "chalk";
17657
+ var INDUS_CLIENT_ID = process.env.INDUS_CLIENT_ID ?? "indus-cli";
17658
+ var USER_AGENT = "indus CLI";
17659
+ var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
17660
+ var DEVICE_SCOPE = "openid profile email";
17661
+ var EXIT_OK = 0;
17662
+ var EXIT_FAILURE = 1;
17663
+ var SERVER_TIER_PROVIDER_ID = "indus-server";
17664
+ var LoginError = class extends Error {
17665
+ };
17666
+ function out(line = "") {
17667
+ process.stdout.write(`${line}
17668
+ `);
17669
+ }
17670
+ function err(line) {
17671
+ process.stderr.write(`${line}
17672
+ `);
17673
+ }
17674
+ function sleep(ms) {
17675
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
17676
+ }
17677
+ async function postJson(url, body) {
17678
+ const res = await fetch(url, {
17679
+ method: "POST",
17680
+ headers: {
17681
+ "content-type": "application/json",
17682
+ accept: "application/json",
17683
+ "user-agent": USER_AGENT
17684
+ },
17685
+ body: JSON.stringify(body)
17686
+ });
17687
+ const data = await res.json().catch(() => ({}));
17688
+ return { status: res.status, data };
17689
+ }
17690
+ function parseLoginArgs(tail) {
17691
+ let serverUrl = INDUS_SERVER_URL;
17692
+ let clientId = INDUS_CLIENT_ID;
17693
+ for (let i = 0; i < tail.length; i++) {
17694
+ const token = tail[i];
17695
+ const eq = token.indexOf("=");
17696
+ const name = eq >= 0 ? token.slice(0, eq) : token;
17697
+ const inline = eq >= 0 ? token.slice(eq + 1) : void 0;
17698
+ const take = () => inline !== void 0 ? inline : tail[++i];
17699
+ if (name === "--server-url") {
17700
+ const value = take();
17701
+ if (value) serverUrl = value;
17702
+ } else if (name === "--client-id") {
17703
+ const value = take();
17704
+ if (value) clientId = value;
17705
+ }
17706
+ }
17707
+ return { serverUrl: serverUrl.replace(/\/+$/, ""), clientId };
17708
+ }
17709
+ async function confirm(prompt, defaultValue) {
17710
+ if (!process.stdin.isTTY) return defaultValue;
17711
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
17712
+ try {
17713
+ const hint = defaultValue ? "Y/n" : "y/N";
17714
+ const answer = (await rl.question(`${prompt} [${hint}] `)).trim().toLowerCase();
17715
+ if (answer === "") return defaultValue;
17716
+ return answer === "y" || answer === "yes";
17717
+ } finally {
17718
+ rl.close();
17719
+ }
17720
+ }
17721
+ async function requestDeviceCode(serverUrl, clientId) {
17722
+ const { status, data } = await postJson(`${serverUrl}/api/auth/device/code`, {
17723
+ client_id: clientId,
17724
+ scope: DEVICE_SCOPE
17725
+ });
17726
+ if (status >= 400 || typeof data.device_code !== "string") {
17727
+ const e = data;
17728
+ throw new LoginError(
17729
+ `Authorization request failed: ${e.error_description ?? e.error ?? e.message ?? `HTTP ${status}`}`
17730
+ );
17731
+ }
17732
+ return data;
17733
+ }
17734
+ async function pollForToken(serverUrl, clientId, device) {
17735
+ let intervalMs = Math.max(1, device.interval ?? 5) * 1e3;
17736
+ const deadline = Date.now() + device.expires_in * 1e3;
17737
+ while (Date.now() < deadline) {
17738
+ await sleep(intervalMs);
17739
+ const { data } = await postJson(`${serverUrl}/api/auth/device/token`, {
17740
+ grant_type: DEVICE_GRANT_TYPE,
17741
+ device_code: device.device_code,
17742
+ client_id: clientId
17743
+ });
17744
+ if (typeof data.access_token === "string") {
17745
+ return data;
17746
+ }
17747
+ const code = data.error;
17748
+ switch (code) {
17749
+ case "authorization_pending":
17750
+ case void 0:
17751
+ break;
17752
+ case "slow_down":
17753
+ intervalMs += 5e3;
17754
+ break;
17755
+ case "access_denied":
17756
+ throw new LoginError("Authorization was denied.");
17757
+ case "expired_token":
17758
+ throw new LoginError("Device code expired. Run `indus login` again.");
17759
+ default:
17760
+ throw new LoginError(
17761
+ `Authorization error: ${data.error_description ?? code}`
17762
+ );
17763
+ }
17764
+ }
17765
+ throw new LoginError("Timed out waiting for authorization. Run `indus login` again.");
17766
+ }
17767
+ async function runServerTierLogin(callbacks, serverUrl = INDUS_SERVER_URL, clientId = INDUS_CLIENT_ID) {
17768
+ const device = await requestDeviceCode(serverUrl, clientId);
17769
+ callbacks.onAuth({
17770
+ url: device.verification_uri_complete ?? device.verification_uri,
17771
+ instructions: `Enter code ${device.user_code} to authorize this device.`
17772
+ });
17773
+ callbacks.onProgress?.("Waiting for authorization...");
17774
+ const token = await pollForToken(serverUrl, clientId, device);
17775
+ await storeServerToken(token);
17776
+ callbacks.onProgress?.("Server sign-in complete.");
17777
+ }
17778
+ async function doLogin(args) {
17779
+ const { serverUrl, clientId } = args;
17780
+ out();
17781
+ out(chalk.bold.cyan(" indus login"));
17782
+ out(chalk.dim(` ${serverUrl}`));
17783
+ out();
17784
+ const existing = await getServerToken();
17785
+ if (existing && !isServerTokenExpired(existing)) {
17786
+ const again = await confirm(
17787
+ chalk.yellow("Already authenticated. Log in again?"),
17788
+ false
17789
+ );
17790
+ if (!again) {
17791
+ out(chalk.dim(" Login cancelled \u2014 existing session preserved."));
17792
+ return EXIT_OK;
17793
+ }
17794
+ }
17795
+ try {
17796
+ out(chalk.dim(" Requesting device authorization..."));
17797
+ const device = await requestDeviceCode(serverUrl, clientId);
17798
+ const url = device.verification_uri_complete ?? device.verification_uri;
17799
+ out();
17800
+ out(` ${chalk.bold("Open this URL to authorize:")}`);
17801
+ out(` ${chalk.green.underline(url)}`);
17802
+ out();
17803
+ out(` ${chalk.bold("Verification code:")} ${chalk.bold.cyan(device.user_code)}`);
17804
+ out(
17805
+ chalk.dim(
17806
+ ` Expires in ${Math.max(1, Math.floor(device.expires_in / 60))} min \xB7 polling every ${Math.max(
17807
+ 1,
17808
+ device.interval ?? 5
17809
+ )}s`
17810
+ )
17811
+ );
17812
+ out();
17813
+ out(chalk.dim(" Waiting for authorization..."));
17814
+ const token = await pollForToken(serverUrl, clientId, device);
17815
+ const saved = await storeServerToken(token);
17816
+ out();
17817
+ out(chalk.green.bold(" Authentication successful."));
17818
+ if (saved) {
17819
+ out(chalk.dim(` Token stored at ${TOKEN_FILE}`));
17820
+ } else {
17821
+ out(
17822
+ chalk.yellow(
17823
+ " Warning: could not write the token to disk \u2014 you may need to log in again."
17824
+ )
17825
+ );
17826
+ }
17827
+ out();
17828
+ return EXIT_OK;
17829
+ } catch (error) {
17830
+ const message = error instanceof LoginError ? error.message : error instanceof Error ? error.message : String(error);
17831
+ err("");
17832
+ err(chalk.red(` Login failed: ${message}`));
17833
+ err("");
17834
+ return EXIT_FAILURE;
17835
+ }
17836
+ }
17837
+ async function doLogout() {
17838
+ const cleared = await clearServerToken();
17839
+ if (cleared) {
17840
+ out(chalk.green(" Logged out \u2014 server session token removed."));
17841
+ } else {
17842
+ out(chalk.dim(" No stored session token to remove."));
17843
+ }
17844
+ return EXIT_OK;
17845
+ }
17846
+ async function doWhoami(args) {
17847
+ const token = await getServerToken();
17848
+ if (!token || isServerTokenExpired(token)) {
17849
+ err(chalk.yellow(" Not logged in. Run `indus login` first."));
17850
+ return EXIT_FAILURE;
17851
+ }
17852
+ try {
17853
+ const res = await fetch(`${args.serverUrl}/api/user/me`, {
17854
+ method: "GET",
17855
+ headers: {
17856
+ authorization: `Bearer ${token.access_token}`,
17857
+ accept: "application/json",
17858
+ "user-agent": USER_AGENT
17859
+ }
17860
+ });
17861
+ if (res.status === 401) {
17862
+ err(chalk.yellow(" Session expired or invalid. Run `indus login` again."));
17863
+ return EXIT_FAILURE;
17864
+ }
17865
+ if (!res.ok) {
17866
+ err(chalk.red(` Could not fetch account: HTTP ${res.status}`));
17867
+ return EXIT_FAILURE;
17868
+ }
17869
+ const user = await res.json();
17870
+ out();
17871
+ out(chalk.bold(" Signed in as:"));
17872
+ out(` ${chalk.bold(user.name || user.email)} ${chalk.dim(`<${user.email}>`)}`);
17873
+ out(` ${chalk.dim("plan:")} ${user.plan}`);
17874
+ out(
17875
+ ` ${chalk.dim("monthly token limit:")} ${user.monthlyTokenLimit.toLocaleString()}`
17876
+ );
17877
+ out();
17878
+ return EXIT_OK;
17879
+ } catch (error) {
17880
+ const message = error instanceof Error ? error.message : String(error);
17881
+ err(chalk.red(` Could not reach the server: ${message}`));
17882
+ return EXIT_FAILURE;
17883
+ }
17884
+ }
17885
+ async function runLogin(ctx) {
17886
+ const verb = ctx.argv[0];
17887
+ if (verb !== "login" && verb !== "logout" && verb !== "whoami") {
17888
+ return void 0;
17889
+ }
17890
+ const args = parseLoginArgs(ctx.argv.slice(1));
17891
+ switch (verb) {
17892
+ case "login":
17893
+ return doLogin(args);
17894
+ case "logout":
17895
+ return doLogout();
17896
+ case "whoami":
17897
+ return doWhoami(args);
17898
+ }
17899
+ }
17900
+
17901
+ // src/launch/oauth.ts
17144
17902
  var BUILT_IN_OAUTH = [
17145
17903
  { provider: anthropicOAuthProvider, label: "Anthropic (Claude sign-in)" },
17146
17904
  { provider: openaiCodexOAuthProvider, label: "OpenAI (ChatGPT sign-in)" },
@@ -17182,6 +17940,11 @@ function oauthClientIdEnvVar(providerId) {
17182
17940
  return OAUTH_CLIENT_ID_WIRING[providerId]?.envVar;
17183
17941
  }
17184
17942
  function listLoginProviders() {
17943
+ const serverTier = {
17944
+ id: SERVER_TIER_PROVIDER_ID,
17945
+ label: "Indus Server (sign in for server-tier model access)",
17946
+ authKind: "oauth"
17947
+ };
17185
17948
  const oauth = getOAuthProviders().map((provider) => ({
17186
17949
  id: provider.id,
17187
17950
  label: OAUTH_LABELS[provider.id] ?? provider.name,
@@ -17192,7 +17955,7 @@ function listLoginProviders() {
17192
17955
  label: entry.label,
17193
17956
  authKind: "apiKey"
17194
17957
  }));
17195
- return [...oauth, ...apiKey];
17958
+ return [serverTier, ...oauth, ...apiKey];
17196
17959
  }
17197
17960
  function isWebUrl(url) {
17198
17961
  try {
@@ -17248,6 +18011,10 @@ function openLoginUrl(url) {
17248
18011
  return tryAt(0);
17249
18012
  }
17250
18013
  async function startOAuthLogin(providerId, callbacks, vault, account = "default") {
18014
+ if (providerId === SERVER_TIER_PROVIDER_ID) {
18015
+ await runServerTierLogin(callbacks);
18016
+ return { provider: providerId, account, madeDefault: true };
18017
+ }
17251
18018
  const provider = getOAuthProvider(providerId);
17252
18019
  if (provider === void 0) {
17253
18020
  throw new Error(`No browser sign-in is available for "${providerId}".`);
@@ -17271,7 +18038,7 @@ function defaultCredentialIo() {
17271
18038
  process.stdout.write(line.endsWith("\n") ? line : line + "\n");
17272
18039
  },
17273
18040
  async ask(prompt) {
17274
- const rl = createInterface({
18041
+ const rl = createInterface2({
17275
18042
  input: process.stdin,
17276
18043
  output: process.stdout
17277
18044
  });
@@ -17282,14 +18049,14 @@ function defaultCredentialIo() {
17282
18049
  }
17283
18050
  },
17284
18051
  async askSecret(prompt) {
17285
- const rl = createInterface({
18052
+ const rl = createInterface2({
17286
18053
  input: process.stdin,
17287
18054
  output: process.stdout
17288
18055
  });
17289
- const out = process.stdout;
18056
+ const out2 = process.stdout;
17290
18057
  let muted = false;
17291
- const original = out.write.bind(out);
17292
- out.write = ((chunk, ...args) => {
18058
+ const original = out2.write.bind(out2);
18059
+ out2.write = ((chunk, ...args) => {
17293
18060
  if (muted && typeof chunk === "string") return true;
17294
18061
  return original(chunk, ...args);
17295
18062
  });
@@ -17300,8 +18067,8 @@ function defaultCredentialIo() {
17300
18067
  return value.trim();
17301
18068
  } finally {
17302
18069
  muted = false;
17303
- out.write = original;
17304
- out.write("\n");
18070
+ out2.write = original;
18071
+ out2.write("\n");
17305
18072
  rl.close();
17306
18073
  }
17307
18074
  }
@@ -17465,15 +18232,15 @@ function validateAccountName(name) {
17465
18232
  }
17466
18233
  var DEFAULT_ACCOUNT = "default";
17467
18234
  function asVerb(token) {
17468
- if (token === "signin" || token === "login") return "signin";
17469
- if (token === "signout" || token === "logout") return "signout";
18235
+ if (token === "signin") return "signin";
18236
+ if (token === "signout") return "signout";
17470
18237
  return void 0;
17471
18238
  }
17472
- async function runCredentialCommand(argv, opts) {
17473
- const verb = asVerb(argv[0]);
18239
+ async function runCredentialCommand(argv2, opts) {
18240
+ const verb = asVerb(argv2[0]);
17474
18241
  if (verb === void 0) return { handled: false };
17475
18242
  const io = opts.io ?? defaultCredentialIo();
17476
- const rest = argv.slice(1);
18243
+ const rest = argv2.slice(1);
17477
18244
  const { provider: namedProvider, account, method, list } = readVerbFlags(rest);
17478
18245
  if (verb === "signin" && list) {
17479
18246
  try {
@@ -17877,7 +18644,7 @@ function printModelCatalog(io = defaultCatalogIo(), filter = {}, source = regist
17877
18644
  }
17878
18645
 
17879
18646
  // src/launch/pickers.ts
17880
- import { createInterface as createInterface2 } from "node:readline/promises";
18647
+ import { createInterface as createInterface3 } from "node:readline/promises";
17881
18648
  import { createElement } from "indusagi/react-host";
17882
18649
  import { render } from "indusagi/react-host/ink";
17883
18650
  import { StartupSessionPicker } from "indusagi/react-ink";
@@ -17997,8 +18764,8 @@ function projectInvocation(launch) {
17997
18764
  rest: launch.positionals
17998
18765
  };
17999
18766
  }
18000
- function tokenizeInvocation(argv) {
18001
- return projectInvocation(readInvocation(argv));
18767
+ function tokenizeInvocation(argv2) {
18768
+ return projectInvocation(readInvocation(argv2));
18002
18769
  }
18003
18770
  function wantsHelp(inv) {
18004
18771
  return inv.flags.help === true;
@@ -18180,6 +18947,14 @@ function consoleReducer(state, event) {
18180
18947
  return setBuffer(state, event.text);
18181
18948
  case "buffer:insert":
18182
18949
  return insertBuffer(state, event.text);
18950
+ case "buffer:replaceSpan": {
18951
+ const at = state.buffer.indexOf(event.find);
18952
+ if (at < 0) return state;
18953
+ const buffer = state.buffer.slice(0, at) + event.replaceWith + state.buffer.slice(at + event.find.length);
18954
+ const spanEnd = at + event.find.length;
18955
+ const caret = state.caret >= spanEnd ? state.caret + (event.replaceWith.length - event.find.length) : state.caret > at ? at + event.replaceWith.length : state.caret;
18956
+ return { ...state, buffer, caret: clamp(caret, buffer.length) };
18957
+ }
18183
18958
  case "buffer:erasePrev":
18184
18959
  return erasePrev(state);
18185
18960
  case "buffer:eraseNext":
@@ -18483,7 +19258,8 @@ var DEFAULT_SLASH_REGISTRY = buildRegistry(SLASH_COMMANDS);
18483
19258
  // src/console/input/keymap.ts
18484
19259
  var NO_INTENT = { verb: "none" };
18485
19260
  function typed(text) {
18486
- return text.length === 0 ? NO_INTENT : { verb: "text:type", text };
19261
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
19262
+ return normalized.length === 0 ? NO_INTENT : { verb: "text:type", text: normalized };
18487
19263
  }
18488
19264
  function isPrintable(input) {
18489
19265
  if (input.length === 0) return false;
@@ -18518,6 +19294,8 @@ function readKey(input, key) {
18518
19294
  return { verb: "input:externalEditor" };
18519
19295
  case "r":
18520
19296
  return { verb: "overlay:open", overlay: "sessions" };
19297
+ case "x":
19298
+ return { verb: "queue:steer" };
18521
19299
  case "l":
18522
19300
  return { verb: "overlay:open", overlay: "models" };
18523
19301
  case "o":
@@ -18560,7 +19338,10 @@ function advanceChord(latch, intent) {
18560
19338
  }
18561
19339
 
18562
19340
  // src/console/input/paste.ts
18563
- var PASTE_THRESHOLDS = { lines: 8, chars: 800 };
19341
+ var PASTE_THRESHOLDS = { lines: 10, chars: 1001 };
19342
+ var PASTE_BURST_GAP_MS = 24;
19343
+ var PASTE_CONTINUATION_MS = 250;
19344
+ var PASTE_MARKER_LINE_CAP = 1e3;
18564
19345
  function pasteMarker(ordinal, lineCount) {
18565
19346
  return `[Pasted text #${ordinal}${lineCount > 0 ? ` +${lineCount} lines` : ""}]`;
18566
19347
  }
@@ -18588,6 +19369,19 @@ function coalescePaste(body, vault = EMPTY_PASTE_VAULT) {
18588
19369
  bodies.set(ordinal, body);
18589
19370
  return { marker, vault: { bodies, minted: ordinal } };
18590
19371
  }
19372
+ function appendToPaste(ordinal, extra, vault) {
19373
+ const body = (vault.bodies.get(ordinal) ?? "") + extra;
19374
+ const lineCount = body.length === 0 ? 0 : lineBreaks(body) + 1;
19375
+ const marker = pasteMarker(ordinal, lineCount);
19376
+ const bodies = new Map(vault.bodies);
19377
+ bodies.set(ordinal, body);
19378
+ return { marker, vault: { bodies, minted: vault.minted } };
19379
+ }
19380
+ function pasteLines(vault, ordinal) {
19381
+ const body = vault.bodies.get(ordinal);
19382
+ if (body === void 0 || body.length === 0) return 0;
19383
+ return lineBreaks(body) + 1;
19384
+ }
18591
19385
  function expandMarkers(buffer, vault) {
18592
19386
  if (vault.bodies.size === 0) return buffer;
18593
19387
  return buffer.replace(markerScanner(), (_match, ordinalStr) => {
@@ -18709,10 +19503,11 @@ function createDirReader(cwd = process.cwd()) {
18709
19503
 
18710
19504
  // src/console/components/TerminalConsole.tsx
18711
19505
  init_workflow_engine();
19506
+ init_window_budget();
18712
19507
  import { execFileSync } from "node:child_process";
18713
- import { homedir as homedir5 } from "node:os";
18714
- import React3, { useEffect as useEffect6, useMemo as useMemo2, useState as useState7 } from "indusagi/react-host";
18715
- import { Box as Box10, useInput as useInput3 } from "indusagi/react-host/ink";
19508
+ import { homedir as homedir7 } from "node:os";
19509
+ import React3, { useEffect as useEffect7, useMemo as useMemo2, useState as useState8 } from "indusagi/react-host";
19510
+ import { Box as Box10, Text as Text10, useInput as useInput3 } from "indusagi/react-host/ink";
18716
19511
  import {
18717
19512
  MessageList,
18718
19513
  StreamingMarkdown,
@@ -18720,7 +19515,7 @@ import {
18720
19515
  } from "indusagi/react-ink";
18721
19516
 
18722
19517
  // src/console/components/Banner.tsx
18723
- import { homedir as homedir3 } from "node:os";
19518
+ import { homedir as homedir4 } from "node:os";
18724
19519
  import { basename as basename4 } from "node:path";
18725
19520
  import { Box, Text } from "indusagi/react-host/ink";
18726
19521
  import { ChangelogBlock } from "indusagi/react-ink";
@@ -18763,9 +19558,9 @@ var TIPS = [
18763
19558
  "Use /skills to discover specialized helpers for the task at hand"
18764
19559
  ];
18765
19560
  var WHATS_NEW = [
18766
- "Added /rewind support for resuming a conversation from before /clear was run",
18767
- "Fixed scroll jumping to the bottom while you read earlier output mid-stream",
18768
- "Stopped background agents stay stopped \u2014 no more resurrecting from the tasks panel"
19561
+ "Ctrl+X runs your newest queued message NOW \u2014 the current run stops cleanly",
19562
+ "/compact shows live progress, resets to a fresh screen, and lists carried files",
19563
+ '"Allow always" is remembered for the session; sub-agents respect your permission mode'
18769
19564
  ];
18770
19565
  function wrapIndex(seed, len) {
18771
19566
  if (len <= 0) return 0;
@@ -18894,7 +19689,7 @@ function Mascot({
18894
19689
  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)) });
18895
19690
  }
18896
19691
  function shortenWorkspace(workspace) {
18897
- const home = homedir3();
19692
+ const home = homedir4();
18898
19693
  if (home.length > 0 && workspace.startsWith(home)) {
18899
19694
  return `~${workspace.slice(home.length)}`;
18900
19695
  }
@@ -19034,6 +19829,43 @@ function Banner({
19034
19829
  ] });
19035
19830
  }
19036
19831
 
19832
+ // src/console/auth-status.ts
19833
+ init_server_token();
19834
+ init_server_mode();
19835
+ import { useEffect, useState } from "indusagi/react-host";
19836
+ function useAuthenticatedProviders(services, refreshKey) {
19837
+ const [authed, setAuthed] = useState(null);
19838
+ useEffect(() => {
19839
+ if (!services) return;
19840
+ let live = true;
19841
+ void (async () => {
19842
+ const found = /* @__PURE__ */ new Set();
19843
+ try {
19844
+ for (const provider of services.listLoginProviders()) {
19845
+ try {
19846
+ if ((await services.vault.listAccounts(provider.id)).length > 0) {
19847
+ found.add(provider.id);
19848
+ }
19849
+ } catch {
19850
+ }
19851
+ }
19852
+ } catch {
19853
+ }
19854
+ try {
19855
+ if (await hasValidServerToken()) {
19856
+ for (const provider of serverTierProviders()) found.add(provider);
19857
+ }
19858
+ } catch {
19859
+ }
19860
+ if (live) setAuthed(found);
19861
+ })();
19862
+ return () => {
19863
+ live = false;
19864
+ };
19865
+ }, [refreshKey]);
19866
+ return authed;
19867
+ }
19868
+
19037
19869
  // src/console/components/BackgroundAgents.tsx
19038
19870
  import { Box as Box2, Text as Text2 } from "indusagi/react-host/ink";
19039
19871
  import { splitAssistantMessage } from "indusagi/react-ink";
@@ -19128,7 +19960,7 @@ function BackgroundAgents({ theme, runs, tokens, now }) {
19128
19960
  }
19129
19961
 
19130
19962
  // src/console/components/AgentsView.tsx
19131
- import { useState } from "indusagi/react-host";
19963
+ import { useState as useState2 } from "indusagi/react-host";
19132
19964
  import { Box as Box3, Text as Text3, useInput } from "indusagi/react-host/ink";
19133
19965
  import { wrapSelectionIndex } from "indusagi/react-ink";
19134
19966
  import { jsx as jsx3, jsxs as jsxs3 } from "indusagi/react-host/jsx-runtime";
@@ -19172,7 +20004,7 @@ function ActivityRow({
19172
20004
  }
19173
20005
  function AgentsView({ theme, runs, activity, tokens, now, onExit }) {
19174
20006
  const clock = now ?? Date.now();
19175
- const [selectedIndex, setSelectedIndex] = useState(runs.length > 0 ? 1 : 0);
20007
+ const [selectedIndex, setSelectedIndex] = useState2(runs.length > 0 ? 1 : 0);
19176
20008
  const rowCount = runs.length + 1;
19177
20009
  const index = Math.min(selectedIndex, rowCount - 1);
19178
20010
  const viewed = index > 0 ? runs[index - 1] : void 0;
@@ -19343,7 +20175,7 @@ function StatusBar({
19343
20175
  }
19344
20176
 
19345
20177
  // src/console/components/WorkingIndicator.tsx
19346
- import React, { useEffect, useState as useState2 } from "indusagi/react-host";
20178
+ import React, { useEffect as useEffect2, useState as useState3 } from "indusagi/react-host";
19347
20179
  import { Box as Box6, Text as Text6 } from "indusagi/react-host/ink";
19348
20180
  import { jsx as jsx6, jsxs as jsxs6 } from "indusagi/react-host/jsx-runtime";
19349
20181
  var { useRef } = React;
@@ -19401,10 +20233,10 @@ function WorkingIndicator({
19401
20233
  busy,
19402
20234
  phase
19403
20235
  }) {
19404
- const [frame, setFrame] = useState2(0);
20236
+ const [frame, setFrame] = useState3(0);
19405
20237
  const startRef = useRef(null);
19406
20238
  const wordOffsetRef = useRef(0);
19407
- useEffect(() => {
20239
+ useEffect2(() => {
19408
20240
  if (!busy) {
19409
20241
  startRef.current = null;
19410
20242
  return;
@@ -19439,7 +20271,7 @@ var APPROVAL_CHOICES = [
19439
20271
  {
19440
20272
  id: "allow-always",
19441
20273
  label: "Allow always",
19442
- description: "run it and remember the tool for this session"
20274
+ description: "run it and remember this action for the session"
19443
20275
  },
19444
20276
  { id: "deny", label: "Deny", description: "block this call" }
19445
20277
  ];
@@ -19541,7 +20373,8 @@ function ApprovalOverlays(props) {
19541
20373
  }
19542
20374
 
19543
20375
  // src/console/overlays/auth.tsx
19544
- import React2, { useEffect as useEffect2, useState as useState3 } from "indusagi/react-host";
20376
+ init_server_token();
20377
+ import React2, { useEffect as useEffect3, useState as useState4 } from "indusagi/react-host";
19545
20378
  import { Box as Box8, Text as Text8 } from "indusagi/react-host/ink";
19546
20379
  import {
19547
20380
  DialogFrame,
@@ -19564,7 +20397,7 @@ function SignInDialog(props) {
19564
20397
  }
19565
20398
  })();
19566
20399
  const direct = requestedProvider !== void 0 ? providers.find((p) => p.id === requestedProvider) : void 0;
19567
- useEffect2(() => {
20400
+ useEffect3(() => {
19568
20401
  if (direct === void 0) return;
19569
20402
  dispatch2({
19570
20403
  type: "modal:open",
@@ -19595,13 +20428,25 @@ function SignInDialog(props) {
19595
20428
  }
19596
20429
  );
19597
20430
  }
20431
+ var SERVER_TIER_ACCOUNT_ID = "server-session";
19598
20432
  function SignOutDialog(props) {
19599
20433
  const { services, closeModal } = props;
19600
- const [accounts, setAccounts] = useState3([]);
19601
- useEffect2(() => {
20434
+ const [accounts, setAccounts] = useState4([]);
20435
+ useEffect3(() => {
19602
20436
  let live = true;
19603
20437
  void (async () => {
19604
20438
  const rows = [];
20439
+ try {
20440
+ const token = await getServerToken();
20441
+ if (token) {
20442
+ rows.push({
20443
+ provider: SERVER_TIER_PROVIDER_ID,
20444
+ accountId: SERVER_TIER_ACCOUNT_ID,
20445
+ label: "Indus Server \xB7 signed in"
20446
+ });
20447
+ }
20448
+ } catch {
20449
+ }
19605
20450
  try {
19606
20451
  for (const provider of services.listLoginProviders()) {
19607
20452
  let names = [];
@@ -19634,7 +20479,11 @@ function SignOutDialog(props) {
19634
20479
  onClose: closeModal,
19635
20480
  onSelect: async (account) => {
19636
20481
  try {
19637
- await services.vault.remove(account.provider, account.accountId);
20482
+ if (account.provider === SERVER_TIER_PROVIDER_ID) {
20483
+ await clearServerToken();
20484
+ } else {
20485
+ await services.vault.remove(account.provider, account.accountId);
20486
+ }
19638
20487
  } catch {
19639
20488
  }
19640
20489
  closeModal();
@@ -19672,12 +20521,12 @@ function OAuthFlowDialog(props) {
19672
20521
  return providerId;
19673
20522
  }
19674
20523
  })();
19675
- const [state, setState] = useState3(
20524
+ const [state, setState] = useState4(
19676
20525
  () => seedOAuthState(providerId, providerName, mode)
19677
20526
  );
19678
20527
  const pending = useRef2(null);
19679
20528
  const merge = (next) => setState((prev) => ({ ...prev, ...next }));
19680
- useEffect2(() => {
20529
+ useEffect3(() => {
19681
20530
  if (mode !== "oauth") return;
19682
20531
  let live = true;
19683
20532
  const safeMerge = (next) => {
@@ -19715,6 +20564,7 @@ function OAuthFlowDialog(props) {
19715
20564
  void (async () => {
19716
20565
  try {
19717
20566
  await services.startOAuthLogin(providerId, callbacks, services.vault);
20567
+ await services.refreshGatewayRouting();
19718
20568
  selectProviderModel(services, providerId);
19719
20569
  if (live) {
19720
20570
  dispatch2({
@@ -19858,21 +20708,47 @@ function readEntryMode(payload) {
19858
20708
  }
19859
20709
 
19860
20710
  // src/console/overlays/pickers.tsx
19861
- import { useEffect as useEffect4, useState as useState5 } from "indusagi/react-host";
20711
+ import { useEffect as useEffect5, useState as useState6 } from "indusagi/react-host";
19862
20712
  import {
19863
20713
  ScopedModelsDialog,
19864
20714
  ThemeDialog as ThemeDialog2
19865
20715
  } from "indusagi/react-ink";
19866
20716
 
19867
20717
  // src/console/overlays/boards.tsx
19868
- import chalk from "chalk";
19869
- import { useEffect as useEffect3, useMemo, useState as useState4 } from "indusagi/react-host";
20718
+ init_server_mode();
20719
+ import chalk2 from "chalk";
20720
+ import { readFileSync as readFileSync7 } from "node:fs";
20721
+ import { homedir as homedir5 } from "node:os";
20722
+ import { join as join12 } from "node:path";
20723
+ import { useEffect as useEffect4, useMemo, useState as useState5 } from "indusagi/react-host";
19870
20724
  import { Box as Box9, Text as Text9, useInput as useInput2 } from "indusagi/react-host/ink";
19871
20725
  import {
19872
20726
  matchesSearchQuery,
19873
20727
  wrapSelectionIndex as wrapSelectionIndex2
19874
20728
  } from "indusagi/react-ink";
19875
20729
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "indusagi/react-host/jsx-runtime";
20730
+ function readServerTierInfo() {
20731
+ let signedIn = false;
20732
+ try {
20733
+ const file = join12(homedir5(), ".indusagi", "agent", "server-token.json");
20734
+ const token = JSON.parse(readFileSync7(file, "utf8"));
20735
+ signedIn = typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1e3;
20736
+ } catch {
20737
+ signedIn = false;
20738
+ }
20739
+ const localKeys = /* @__PURE__ */ new Set();
20740
+ try {
20741
+ const file = join12(homedir5(), ".indusagi", "agent", "auth.json");
20742
+ const vault = JSON.parse(readFileSync7(file, "utf8"));
20743
+ for (const [provider, entry] of Object.entries(vault)) {
20744
+ if (entry !== null && typeof entry === "object" && Object.keys(entry).length > 0) {
20745
+ localKeys.add(provider);
20746
+ }
20747
+ }
20748
+ } catch {
20749
+ }
20750
+ return { signedIn, localKeys };
20751
+ }
19876
20752
  function truncate(text, width) {
19877
20753
  if (text.length <= width) return text;
19878
20754
  if (width <= 1) return text.slice(0, width);
@@ -19881,7 +20757,7 @@ function truncate(text, width) {
19881
20757
  function tabPill(theme, text) {
19882
20758
  const accent = theme.colors["accent"] ?? "#f7a23f";
19883
20759
  try {
19884
- return chalk.bgHex(accent).hex("#1a120a").bold(` ${text} `);
20760
+ return chalk2.bgHex(accent).hex("#1a120a").bold(` ${text} `);
19885
20761
  } catch {
19886
20762
  return theme.color("accent", text);
19887
20763
  }
@@ -19990,16 +20866,16 @@ function nextValue(values, current, direction) {
19990
20866
  return values[(at + direction + values.length) % values.length];
19991
20867
  }
19992
20868
  function SettingsBoard({ theme, items, onClose }) {
19993
- const [query, setQuery] = useState4("");
19994
- const [selectedIndex, setSelectedIndex] = useState4(0);
20869
+ const [query, setQuery] = useState5("");
20870
+ const [selectedIndex, setSelectedIndex] = useState5(0);
19995
20871
  const filtered = useMemo(
19996
20872
  () => items.filter(
19997
20873
  (item) => matchesSearchQuery(`${item.label} ${item.description} ${item.value} ${item.values.join(" ")}`, query)
19998
20874
  ),
19999
20875
  [items, query]
20000
20876
  );
20001
- useEffect3(() => setSelectedIndex(0), [query]);
20002
- useEffect3(() => {
20877
+ useEffect4(() => setSelectedIndex(0), [query]);
20878
+ useEffect4(() => {
20003
20879
  if (selectedIndex >= filtered.length) setSelectedIndex(0);
20004
20880
  }, [filtered.length, selectedIndex]);
20005
20881
  const cycle = (direction) => {
@@ -20049,12 +20925,20 @@ function modelMatches(model, query) {
20049
20925
  return matchesSearchQuery(`${model.provider} ${model.id} ${model.name ?? ""}`, query);
20050
20926
  }
20051
20927
  function ModelBoard({ theme, models, onClose, onSelect, search }) {
20052
- const [query, setQuery] = useState4(search ?? "");
20053
- const [selectedIndex, setSelectedIndex] = useState4(0);
20054
- useEffect3(() => setQuery(search ?? ""), [search]);
20055
- const filtered = useMemo(() => models.filter((model) => modelMatches(model, query)), [models, query]);
20056
- useEffect3(() => setSelectedIndex(0), [query]);
20057
- useEffect3(() => {
20928
+ const [query, setQuery] = useState5(search ?? "");
20929
+ const [selectedIndex, setSelectedIndex] = useState5(0);
20930
+ useEffect4(() => setQuery(search ?? ""), [search]);
20931
+ const serverTier = useMemo(readServerTierInfo, []);
20932
+ const isServerTier = (provider) => serverTier.signedIn && isGatewayEligibleProvider(provider) && !serverTier.localKeys.has(provider);
20933
+ const available = useMemo(
20934
+ () => models.filter(
20935
+ (model) => serverTier.localKeys.has(model.provider) || isServerTier(model.provider)
20936
+ ),
20937
+ [models, serverTier]
20938
+ );
20939
+ const filtered = useMemo(() => available.filter((model) => modelMatches(model, query)), [available, query]);
20940
+ useEffect4(() => setSelectedIndex(0), [query]);
20941
+ useEffect4(() => {
20058
20942
  if (selectedIndex >= filtered.length) setSelectedIndex(0);
20059
20943
  }, [filtered.length, selectedIndex]);
20060
20944
  useInput2((input, key) => {
@@ -20072,13 +20956,16 @@ function ModelBoard({ theme, models, onClose, onSelect, search }) {
20072
20956
  });
20073
20957
  const { start, budget, below } = windowAround(filtered.length, selectedIndex);
20074
20958
  const visible = filtered.slice(start, start + budget);
20075
- 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) => {
20959
+ const list = filtered.length === 0 ? /* @__PURE__ */ jsx9(Text9, { children: theme.muted(
20960
+ 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."
20961
+ ) }) : /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: visible.map((model, index) => {
20076
20962
  const isSelected = start + index === selectedIndex;
20077
20963
  return /* @__PURE__ */ jsxs8(Box9, { children: [
20078
20964
  /* @__PURE__ */ jsx9(Box9, { width: 2, children: /* @__PURE__ */ jsx9(Text9, { children: isSelected ? theme.color("accent", "\u203A") : " " }) }),
20079
- /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: theme.color("text", `${model.provider}/${model.id}`) }),
20080
- model.name ? /* @__PURE__ */ jsx9(Text9, { children: theme.dim(` ${model.name}`) }) : null
20081
- ] }, `${model.provider}/${model.id}`);
20965
+ /* @__PURE__ */ jsx9(Text9, { bold: isSelected, children: theme.color("text", model.id) }),
20966
+ model.name ? /* @__PURE__ */ jsx9(Text9, { children: theme.dim(` ${model.name}`) }) : null,
20967
+ isServerTier(model.provider) ? /* @__PURE__ */ jsx9(Text9, { children: theme.color("accent", " \u2B21 server") }) : null
20968
+ ] }, model.id);
20082
20969
  }) });
20083
20970
  return /* @__PURE__ */ jsx9(
20084
20971
  BoardShell,
@@ -20137,13 +21024,13 @@ var SETTING_KEYS = Object.freeze(
20137
21024
  );
20138
21025
 
20139
21026
  // src/settings/manager.ts
20140
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
20141
- import { dirname as dirname4, join as join11 } from "node:path";
21027
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
21028
+ import { dirname as dirname4, join as join13 } from "node:path";
20142
21029
  function loadTier(filePath) {
20143
21030
  if (!existsSync3(filePath)) return {};
20144
21031
  let raw;
20145
21032
  try {
20146
- raw = readFileSync6(filePath, "utf-8");
21033
+ raw = readFileSync8(filePath, "utf-8");
20147
21034
  } catch {
20148
21035
  return {};
20149
21036
  }
@@ -20194,7 +21081,7 @@ var PreferenceStore = class _PreferenceStore {
20194
21081
  static fromWorkspace(workspace, cwd = process.cwd()) {
20195
21082
  return new _PreferenceStore({
20196
21083
  global: workspace.settingsPath,
20197
- project: join11(cwd, BRAND.profileDirName, "settings.json")
21084
+ project: join13(cwd, BRAND.profileDirName, "settings.json")
20198
21085
  });
20199
21086
  }
20200
21087
  /** The two resolved file locations this store reads and writes. */
@@ -20263,11 +21150,11 @@ var PreferenceStore = class _PreferenceStore {
20263
21150
  }
20264
21151
  };
20265
21152
  function stripUndefined(tier) {
20266
- const out = {};
21153
+ const out2 = {};
20267
21154
  for (const [key, value] of Object.entries(tier)) {
20268
- if (value !== void 0) out[key] = value;
21155
+ if (value !== void 0) out2[key] = value;
20269
21156
  }
20270
- return out;
21157
+ return out2;
20271
21158
  }
20272
21159
 
20273
21160
  // src/console/overlays/pickers.tsx
@@ -20310,38 +21197,12 @@ function listModels(services) {
20310
21197
  return [];
20311
21198
  }
20312
21199
  }
20313
- function useAuthenticatedProviders(services) {
20314
- const [authed, setAuthed] = useState5(null);
20315
- useEffect4(() => {
20316
- if (!services) return;
20317
- let live = true;
20318
- void (async () => {
20319
- const found = /* @__PURE__ */ new Set();
20320
- try {
20321
- for (const provider of services.listLoginProviders()) {
20322
- try {
20323
- if ((await services.vault.listAccounts(provider.id)).length > 0) {
20324
- found.add(provider.id);
20325
- }
20326
- } catch {
20327
- }
20328
- }
20329
- } catch {
20330
- }
20331
- if (live) setAuthed(found);
20332
- })();
20333
- return () => {
20334
- live = false;
20335
- };
20336
- }, []);
20337
- return authed;
20338
- }
20339
21200
  function ModelPicker(props) {
20340
21201
  const { services, theme, closeModal } = props;
20341
21202
  const authed = useAuthenticatedProviders(services);
20342
21203
  if (!services) return null;
20343
21204
  const all = listModels(services);
20344
- const models = authed && authed.size > 0 ? all.filter((model) => authed.has(model.provider)) : all;
21205
+ const models = authed === null ? all : all.filter((model) => authed.has(model.provider));
20345
21206
  return /* @__PURE__ */ jsx10(
20346
21207
  ModelBoard,
20347
21208
  {
@@ -20361,15 +21222,15 @@ function ModelPicker(props) {
20361
21222
  function readScopedPayload(payload) {
20362
21223
  if (payload === null || typeof payload !== "object") return {};
20363
21224
  const record = payload;
20364
- const out = {};
20365
- if (record.focus === "summary") out.focus = "summary";
20366
- if (record.intent === "reset") out.intent = "reset";
20367
- return out;
21225
+ const out2 = {};
21226
+ if (record.focus === "summary") out2.focus = "summary";
21227
+ if (record.intent === "reset") out2.intent = "reset";
21228
+ return out2;
20368
21229
  }
20369
21230
  function ScopedModelPicker(props) {
20370
21231
  const { services, dispatch: dispatch2, closeModal, modal } = props;
20371
21232
  const payload = readScopedPayload(modal.payload);
20372
- useEffect4(() => {
21233
+ useEffect5(() => {
20373
21234
  if (!services || payload.intent !== "reset") return;
20374
21235
  try {
20375
21236
  services.settings.set("enabledModels", []);
@@ -20425,7 +21286,7 @@ function currentScheme(services) {
20425
21286
  }
20426
21287
  function ThemePicker(props) {
20427
21288
  const { services, dispatch: dispatch2, closeModal } = props;
20428
- const [originalScheme] = useState5(
21289
+ const [originalScheme] = useState6(
20429
21290
  () => services ? currentScheme(services) : null
20430
21291
  );
20431
21292
  if (!services) return null;
@@ -20456,7 +21317,7 @@ function SettingsPicker(props) {
20456
21317
  const { services, theme, dispatch: dispatch2, closeModal } = props;
20457
21318
  if (!services) return null;
20458
21319
  const settings = services.settings;
20459
- const [, setVersion] = useState5(0);
21320
+ const [, setVersion] = useState6(0);
20460
21321
  const bump = () => setVersion((n) => n + 1);
20461
21322
  const onOff = (flag) => flag ? "on" : "off";
20462
21323
  const toggleRow = (id, key, label, description, live) => ({
@@ -20638,7 +21499,7 @@ function PickerOverlays(props) {
20638
21499
  }
20639
21500
 
20640
21501
  // src/console/overlays/sessions.tsx
20641
- import { useEffect as useEffect5, useState as useState6 } from "indusagi/react-host";
21502
+ import { useEffect as useEffect6, useState as useState7 } from "indusagi/react-host";
20642
21503
  import {
20643
21504
  SessionDialog,
20644
21505
  TreeDialog,
@@ -20660,8 +21521,8 @@ function toSessionInfo(row) {
20660
21521
  }
20661
21522
  function SessionPicker(props) {
20662
21523
  const { services, closeModal } = props;
20663
- const [sessions, setSessions] = useState6([]);
20664
- useEffect5(() => {
21524
+ const [sessions, setSessions] = useState7([]);
21525
+ useEffect6(() => {
20665
21526
  let live = true;
20666
21527
  void (async () => {
20667
21528
  try {
@@ -20721,8 +21582,8 @@ function SessionPicker(props) {
20721
21582
  }
20722
21583
  function BranchTree(props) {
20723
21584
  const { services, closeModal } = props;
20724
- const [items, setItems] = useState6([]);
20725
- useEffect5(() => {
21585
+ const [items, setItems] = useState7([]);
21586
+ useEffect6(() => {
20726
21587
  let live = true;
20727
21588
  void (async () => {
20728
21589
  try {
@@ -20773,8 +21634,8 @@ function BranchTree(props) {
20773
21634
  }
20774
21635
  function PriorTurns(props) {
20775
21636
  const { services, closeModal } = props;
20776
- const [items, setItems] = useState6([]);
20777
- useEffect5(() => {
21637
+ const [items, setItems] = useState7([]);
21638
+ useEffect6(() => {
20778
21639
  let live = true;
20779
21640
  void (async () => {
20780
21641
  try {
@@ -20872,34 +21733,40 @@ function approvalQueueReducer(state, event) {
20872
21733
  function activeApproval(state) {
20873
21734
  return state.entries[0];
20874
21735
  }
21736
+ function modeReleasesApprovals(mode) {
21737
+ return mode === "bypass" || mode === "bypassPermissions";
21738
+ }
21739
+
21740
+ // src/console/components/TerminalConsole.tsx
21741
+ init_conductor2();
20875
21742
 
20876
21743
  // src/console/startup.ts
20877
21744
  init_briefing();
20878
- import { existsSync as existsSync4, readFileSync as readFileSync7 } from "node:fs";
20879
- import { join as join12 } from "node:path";
21745
+ import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
21746
+ import { join as join14 } from "node:path";
20880
21747
  var PROFILE_DIR = ".indusagi";
20881
21748
  var CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md"];
20882
21749
  function skillRoots(cwd, home) {
20883
21750
  return [
20884
- { dir: join12(cwd, PROFILE_DIR, "skills"), origin: "project" },
20885
- { dir: join12(home, PROFILE_DIR, "skills"), origin: "user" }
21751
+ { dir: join14(cwd, PROFILE_DIR, "skills"), origin: "project" },
21752
+ { dir: join14(home, PROFILE_DIR, "skills"), origin: "user" }
20886
21753
  ];
20887
21754
  }
20888
21755
  function macroRoots(cwd, home) {
20889
21756
  return [
20890
- { dir: join12(cwd, PROFILE_DIR, "commands"), origin: "project", label: "project" },
20891
- { dir: join12(home, PROFILE_DIR, "commands"), origin: "user", label: "user" }
21757
+ { dir: join14(cwd, PROFILE_DIR, "commands"), origin: "project", label: "project" },
21758
+ { dir: join14(home, PROFILE_DIR, "commands"), origin: "user", label: "user" }
20892
21759
  ];
20893
21760
  }
20894
- function shortenPath2(path3, cwd, home) {
20895
- if (cwd.length > 0 && path3.startsWith(cwd)) {
20896
- const rest = path3.slice(cwd.length).replace(/^[/\\]+/, "");
21761
+ function shortenPath2(path4, cwd, home) {
21762
+ if (cwd.length > 0 && path4.startsWith(cwd)) {
21763
+ const rest = path4.slice(cwd.length).replace(/^[/\\]+/, "");
20897
21764
  return rest.length > 0 ? `./${rest}` : ".";
20898
21765
  }
20899
- if (home.length > 0 && path3.startsWith(home)) {
20900
- return `~${path3.slice(home.length)}`;
21766
+ if (home.length > 0 && path4.startsWith(home)) {
21767
+ return `~${path4.slice(home.length)}`;
20901
21768
  }
20902
- return path3;
21769
+ return path4;
20903
21770
  }
20904
21771
  var MAX_SECTION_LINES = 6;
20905
21772
  function capLines(lines) {
@@ -20914,17 +21781,17 @@ function section(title, lines) {
20914
21781
  }
20915
21782
  function gatherContext(cwd, home) {
20916
21783
  const seen = /* @__PURE__ */ new Set();
20917
- const out = [];
21784
+ const out2 = [];
20918
21785
  for (const root of [cwd, home]) {
20919
21786
  if (root.length === 0) continue;
20920
21787
  for (const name of CONTEXT_FILES) {
20921
- const path3 = join12(root, name);
20922
- if (seen.has(path3)) continue;
20923
- seen.add(path3);
20924
- if (existsSync4(path3)) out.push(shortenPath2(path3, cwd, home));
21788
+ const path4 = join14(root, name);
21789
+ if (seen.has(path4)) continue;
21790
+ seen.add(path4);
21791
+ if (existsSync4(path4)) out2.push(shortenPath2(path4, cwd, home));
20925
21792
  }
20926
21793
  }
20927
- return out;
21794
+ return out2;
20928
21795
  }
20929
21796
  function gatherSkills(cwd, home) {
20930
21797
  let cards = [];
@@ -21342,7 +22209,7 @@ function addonFault(kind, message, detail) {
21342
22209
 
21343
22210
  // src/addons/sandbox.ts
21344
22211
  import { createJiti } from "jiti";
21345
- import { homedir as homedir4 } from "node:os";
22212
+ import { homedir as homedir6 } from "node:os";
21346
22213
  import { isAbsolute as isAbsolute4, resolve as resolvePathSegments } from "node:path";
21347
22214
  import { fileURLToPath, pathToFileURL } from "node:url";
21348
22215
  var typeboxNamespace = __toESM(require_cjs(), 1);
@@ -21382,17 +22249,17 @@ var INVISIBLE_CODE_POINTS = [
21382
22249
  ];
21383
22250
  var INVISIBLE_SET = new Set(INVISIBLE_CODE_POINTS);
21384
22251
  function scrubInvisible(raw) {
21385
- let out = "";
22252
+ let out2 = "";
21386
22253
  for (const char of raw) {
21387
22254
  const code = char.codePointAt(0);
21388
22255
  if (code !== void 0 && INVISIBLE_SET.has(code)) {
21389
22256
  continue;
21390
22257
  }
21391
- out += char;
22258
+ out2 += char;
21392
22259
  }
21393
- return out.trim();
22260
+ return out2.trim();
21394
22261
  }
21395
- function expandPath(raw, home = homedir4()) {
22262
+ function expandPath(raw, home = homedir6()) {
21396
22263
  const cleaned = scrubInvisible(raw);
21397
22264
  if (cleaned === "~") {
21398
22265
  return home;
@@ -21402,7 +22269,7 @@ function expandPath(raw, home = homedir4()) {
21402
22269
  }
21403
22270
  return cleaned;
21404
22271
  }
21405
- function resolvePath(raw, base = process.cwd(), home = homedir4()) {
22272
+ function resolvePath(raw, base = process.cwd(), home = homedir6()) {
21406
22273
  const expanded = expandPath(raw, home);
21407
22274
  return isAbsolute4(expanded) ? resolvePathSegments(expanded) : resolvePathSegments(base, expanded);
21408
22275
  }
@@ -21424,9 +22291,9 @@ function resolveSpecifierPath(specifier) {
21424
22291
  function buildAliasMap() {
21425
22292
  const map = {};
21426
22293
  for (const specifier of BUNDLED_NAMESPACES) {
21427
- const path3 = resolveSpecifierPath(specifier);
21428
- if (path3 !== void 0) {
21429
- map[specifier] = path3;
22294
+ const path4 = resolveSpecifierPath(specifier);
22295
+ if (path4 !== void 0) {
22296
+ map[specifier] = path4;
21430
22297
  }
21431
22298
  }
21432
22299
  return map;
@@ -21472,10 +22339,10 @@ function isObject(value) {
21472
22339
  function createJitiLoader(options = {}) {
21473
22340
  const jiti = createJiti(import.meta.url, jitiOptionsForRuntime());
21474
22341
  const base = options.base ?? process.cwd();
21475
- const home = options.home ?? homedir4();
22342
+ const home = options.home ?? homedir6();
21476
22343
  return {
21477
- async load(path3) {
21478
- const absolute = resolvePath(path3, base, home);
22344
+ async load(path4) {
22345
+ const absolute = resolvePath(path4, base, home);
21479
22346
  const specifier = pathToFileURL(absolute).href;
21480
22347
  const imported = await jiti.import(specifier);
21481
22348
  const manifest = toManifest(imported);
@@ -21490,8 +22357,8 @@ function createJitiLoader(options = {}) {
21490
22357
  }
21491
22358
 
21492
22359
  // src/addons/manifest.ts
21493
- import { readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
21494
- import { basename as basename5, extname as extname3, isAbsolute as isAbsolute5, join as join13, resolve as resolve7 } from "node:path";
22360
+ import { readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
22361
+ import { basename as basename5, extname as extname3, isAbsolute as isAbsolute5, join as join15, resolve as resolve7 } from "node:path";
21495
22362
  var ENTRY_EXTENSIONS = [
21496
22363
  ".ts",
21497
22364
  ".tsx",
@@ -21508,9 +22375,9 @@ function isHidden(name) {
21508
22375
  function hasEntryExtension(name) {
21509
22376
  return ENTRY_EXTENSIONS.includes(extname3(name).toLowerCase());
21510
22377
  }
21511
- function entryKind(path3) {
22378
+ function entryKind(path4) {
21512
22379
  try {
21513
- const stat2 = statSync4(path3);
22380
+ const stat2 = statSync4(path4);
21514
22381
  if (stat2.isDirectory()) return "directory";
21515
22382
  if (stat2.isFile()) return "file";
21516
22383
  return void 0;
@@ -21526,12 +22393,12 @@ function listNames(dir) {
21526
22393
  }
21527
22394
  }
21528
22395
  function readManifestEntry(packageDir) {
21529
- const manifestPath = join13(packageDir, "package.json");
22396
+ const manifestPath = join15(packageDir, "package.json");
21530
22397
  if (entryKind(manifestPath) !== "file") {
21531
22398
  return void 0;
21532
22399
  }
21533
22400
  try {
21534
- const parsed = JSON.parse(readFileSync8(manifestPath, "utf8"));
22401
+ const parsed = JSON.parse(readFileSync10(manifestPath, "utf8"));
21535
22402
  if (typeof parsed !== "object" || parsed === null) {
21536
22403
  return void 0;
21537
22404
  }
@@ -21551,7 +22418,7 @@ function resolvePackageEntry(packageDir) {
21551
22418
  }
21552
22419
  for (const base of INDEX_BASENAMES) {
21553
22420
  for (const ext of ENTRY_EXTENSIONS) {
21554
- const candidate = join13(packageDir, `${base}${ext}`);
22421
+ const candidate = join15(packageDir, `${base}${ext}`);
21555
22422
  if (entryKind(candidate) === "file") {
21556
22423
  return candidate;
21557
22424
  }
@@ -21563,13 +22430,13 @@ function resolveTopLevelEntry(dir, name) {
21563
22430
  if (isHidden(name)) {
21564
22431
  return void 0;
21565
22432
  }
21566
- const path3 = join13(dir, name);
21567
- const kind = entryKind(path3);
22433
+ const path4 = join15(dir, name);
22434
+ const kind = entryKind(path4);
21568
22435
  if (kind === "file") {
21569
- return hasEntryExtension(name) ? path3 : void 0;
22436
+ return hasEntryExtension(name) ? path4 : void 0;
21570
22437
  }
21571
22438
  if (kind === "directory") {
21572
- return resolvePackageEntry(path3);
22439
+ return resolvePackageEntry(path4);
21573
22440
  }
21574
22441
  return void 0;
21575
22442
  }
@@ -21586,7 +22453,7 @@ function discoverAddons(dir) {
21586
22453
  function deriveAddonId(entryPath) {
21587
22454
  const file = basename5(entryPath);
21588
22455
  const stem = file.slice(0, file.length - extname3(file).length);
21589
- const label = INDEX_BASENAMES.includes(stem) ? basename5(join13(entryPath, "..")) : stem;
22456
+ const label = INDEX_BASENAMES.includes(stem) ? basename5(join15(entryPath, "..")) : stem;
21590
22457
  return addonId(label.length > 0 ? label : entryPath);
21591
22458
  }
21592
22459
  function addonsDirFor(workspace, dir = ADDONS_DIR) {
@@ -21603,12 +22470,12 @@ function discoverSources(discovery) {
21603
22470
  }
21604
22471
  const seen = /* @__PURE__ */ new Set();
21605
22472
  const sources = [];
21606
- for (const path3 of paths) {
21607
- if (seen.has(path3)) {
22473
+ for (const path4 of paths) {
22474
+ if (seen.has(path4)) {
21608
22475
  continue;
21609
22476
  }
21610
- seen.add(path3);
21611
- sources.push({ id: deriveAddonId(path3), path: path3 });
22477
+ seen.add(path4);
22478
+ sources.push({ id: deriveAddonId(path4), path: path4 });
21612
22479
  }
21613
22480
  return sources;
21614
22481
  }
@@ -22392,10 +23259,10 @@ var inertDialog = Object.freeze({
22392
23259
  tell(_kind, _payload) {
22393
23260
  }
22394
23261
  });
22395
- var EXIT_OK = 0;
23262
+ var EXIT_OK2 = 0;
22396
23263
  var EXIT_FAULT = 1;
22397
23264
  function exitCodeFor(state) {
22398
- return state.phase === "faulted" ? EXIT_FAULT : EXIT_OK;
23265
+ return state.phase === "faulted" ? EXIT_FAULT : EXIT_OK2;
22399
23266
  }
22400
23267
  function textStrategy() {
22401
23268
  let answer = "";
@@ -22740,14 +23607,14 @@ function keyMatches(rule, key) {
22740
23607
  return matcher.test(key);
22741
23608
  }
22742
23609
  function applyValueRules(rules, text) {
22743
- let out = text;
23610
+ let out2 = text;
22744
23611
  for (const rule of rules) {
22745
23612
  const pattern = rule.value;
22746
23613
  if (pattern === void 0) continue;
22747
23614
  if (pattern.global || pattern.sticky) pattern.lastIndex = 0;
22748
- out = out.replace(pattern, REDACTED_TOKEN);
23615
+ out2 = out2.replace(pattern, REDACTED_TOKEN);
22749
23616
  }
22750
- return out;
23617
+ return out2;
22751
23618
  }
22752
23619
  function createRedactor(rules = DEFAULT_REDACTION_RULES, options = {}) {
22753
23620
  const frozenRules = Object.freeze([...rules]);
@@ -22779,12 +23646,12 @@ function createRedactor(rules = DEFAULT_REDACTION_RULES, options = {}) {
22779
23646
  return Object.freeze(value.map((item) => scrubNode(item, void 0)));
22780
23647
  }
22781
23648
  if (value !== null && typeof value === "object") {
22782
- const out = {};
23649
+ const out2 = {};
22783
23650
  for (const [k, v] of Object.entries(value)) {
22784
23651
  if (isOmitted(k)) continue;
22785
- out[k] = scrubNode(v, k);
23652
+ out2[k] = scrubNode(v, k);
22786
23653
  }
22787
- return Object.freeze(out);
23654
+ return Object.freeze(out2);
22788
23655
  }
22789
23656
  return value;
22790
23657
  }
@@ -22792,12 +23659,12 @@ function createRedactor(rules = DEFAULT_REDACTION_RULES, options = {}) {
22792
23659
  return scrubNode(value, void 0);
22793
23660
  }
22794
23661
  function scrubAttributes(attributes) {
22795
- const out = {};
23662
+ const out2 = {};
22796
23663
  for (const [k, v] of Object.entries(attributes)) {
22797
23664
  if (isOmitted(k)) continue;
22798
- out[k] = scrubNode(v, k);
23665
+ out2[k] = scrubNode(v, k);
22799
23666
  }
22800
- return Object.freeze(out);
23667
+ return Object.freeze(out2);
22801
23668
  }
22802
23669
  function scrubFault(fault) {
22803
23670
  const scrubbed = {
@@ -22837,9 +23704,9 @@ var IMAGE_SNIFF_BYTES = PNG_SIGNATURE.length;
22837
23704
 
22838
23705
  // src/kit/clipboard-image.ts
22839
23706
  import { spawnSync as spawnSync2 } from "node:child_process";
22840
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
23707
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
22841
23708
  import { tmpdir as tmpdir2 } from "node:os";
22842
- import { join as join14 } from "node:path";
23709
+ import { join as join16 } from "node:path";
22843
23710
  var READ_TIMEOUT_MS = 3e3;
22844
23711
  var MAX_BUFFER_BYTES = 50 * 1024 * 1024;
22845
23712
  function captureStdout(command, args) {
@@ -22848,9 +23715,9 @@ function captureStdout(command, args) {
22848
23715
  maxBuffer: MAX_BUFFER_BYTES
22849
23716
  });
22850
23717
  if (result.error || result.status !== 0) return null;
22851
- const out = result.stdout;
22852
- if (!Buffer.isBuffer(out) || out.length === 0) return null;
22853
- return out;
23718
+ const out2 = result.stdout;
23719
+ if (!Buffer.isBuffer(out2) || out2.length === 0) return null;
23720
+ return out2;
22854
23721
  }
22855
23722
  function captureViaOsascript() {
22856
23723
  const result = spawnSync2(
@@ -22882,21 +23749,21 @@ function captureViaOsascript() {
22882
23749
  { timeout: READ_TIMEOUT_MS, encoding: "utf8" }
22883
23750
  );
22884
23751
  if (result.error || result.status !== 0) return null;
22885
- const path3 = String(result.stdout ?? "").trim();
22886
- if (path3.length === 0) return null;
23752
+ const path4 = String(result.stdout ?? "").trim();
23753
+ if (path4.length === 0) return null;
22887
23754
  try {
22888
- const bytes = readFileSync9(path3);
23755
+ const bytes = readFileSync11(path4);
22889
23756
  return bytes.length > 0 ? bytes : null;
22890
23757
  } catch {
22891
23758
  return null;
22892
23759
  }
22893
23760
  }
22894
- function readClipboardBytes(platform, env2) {
23761
+ function readClipboardBytes(platform, env3) {
22895
23762
  if (platform === "darwin") {
22896
23763
  return captureStdout("pngpaste", ["-"]) ?? captureViaOsascript();
22897
23764
  }
22898
23765
  if (platform === "linux") {
22899
- const wayland = Boolean(env2.WAYLAND_DISPLAY) || env2.XDG_SESSION_TYPE === "wayland";
23766
+ const wayland = Boolean(env3.WAYLAND_DISPLAY) || env3.XDG_SESSION_TYPE === "wayland";
22900
23767
  if (wayland) {
22901
23768
  const fromWayland = captureStdout("wl-paste", ["--type", "image/png", "--no-newline"]);
22902
23769
  if (fromWayland) return fromWayland;
@@ -22907,15 +23774,15 @@ function readClipboardBytes(platform, env2) {
22907
23774
  }
22908
23775
  function readClipboardImage(options = {}) {
22909
23776
  const platform = options.platform ?? process.platform;
22910
- const env2 = options.env ?? process.env;
22911
- const bytes = readClipboardBytes(platform, env2);
23777
+ const env3 = options.env ?? process.env;
23778
+ const bytes = readClipboardBytes(platform, env3);
22912
23779
  if (bytes === null) return null;
22913
23780
  const dir = options.dir ?? tmpdir2();
22914
23781
  const name = `indus-clipboard-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.png`;
22915
- const path3 = join14(dir, name);
23782
+ const path4 = join16(dir, name);
22916
23783
  try {
22917
- writeFileSync5(path3, bytes);
22918
- return path3;
23784
+ writeFileSync5(path4, bytes);
23785
+ return path4;
22919
23786
  } catch {
22920
23787
  return null;
22921
23788
  }
@@ -22923,31 +23790,31 @@ function readClipboardImage(options = {}) {
22923
23790
 
22924
23791
  // src/kit/external-editor.ts
22925
23792
  import { spawnSync as spawnSync3 } from "node:child_process";
22926
- import { readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
23793
+ import { readFileSync as readFileSync12, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
22927
23794
  import { tmpdir as tmpdir3 } from "node:os";
22928
- import { join as join15 } from "node:path";
23795
+ import { join as join17 } from "node:path";
22929
23796
  var FALLBACK_EDITOR = "vi";
22930
- function resolveEditor(env2) {
22931
- const configured = env2.VISUAL || env2.EDITOR || FALLBACK_EDITOR;
23797
+ function resolveEditor(env3) {
23798
+ const configured = env3.VISUAL || env3.EDITOR || FALLBACK_EDITOR;
22932
23799
  const parts = configured.split(/\s+/).filter((part) => part.length > 0);
22933
23800
  return parts.length > 0 ? parts : [FALLBACK_EDITOR];
22934
23801
  }
22935
23802
  function openInExternalEditor(buffer, options = {}) {
22936
- const env2 = options.env ?? process.env;
23803
+ const env3 = options.env ?? process.env;
22937
23804
  const dir = options.dir ?? tmpdir3();
22938
23805
  const name = `indus-editor-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.md`;
22939
- const path3 = join15(dir, name);
22940
- const [editor, ...editorArgs] = resolveEditor(env2);
23806
+ const path4 = join17(dir, name);
23807
+ const [editor, ...editorArgs] = resolveEditor(env3);
22941
23808
  try {
22942
- writeFileSync6(path3, buffer, "utf8");
22943
- const result = spawnSync3(editor, [...editorArgs, path3], { stdio: "inherit" });
23809
+ writeFileSync6(path4, buffer, "utf8");
23810
+ const result = spawnSync3(editor, [...editorArgs, path4], { stdio: "inherit" });
22944
23811
  if (result.error || result.status !== 0) return null;
22945
- return readFileSync10(path3, "utf8").replace(/\n$/, "");
23812
+ return readFileSync12(path4, "utf8").replace(/\n$/, "");
22946
23813
  } catch {
22947
23814
  return null;
22948
23815
  } finally {
22949
23816
  try {
22950
- unlinkSync(path3);
23817
+ unlinkSync(path4);
22951
23818
  } catch {
22952
23819
  }
22953
23820
  }
@@ -22955,8 +23822,8 @@ function openInExternalEditor(buffer, options = {}) {
22955
23822
 
22956
23823
  // src/sessions/library.ts
22957
23824
  init_conductor2();
22958
- import { mkdir, readFile, readdir, rename, rm, stat } from "node:fs/promises";
22959
- import { basename as basename6, join as join16 } from "node:path";
23825
+ import { mkdir as mkdir2, readFile as readFile2, readdir, rename, rm, stat } from "node:fs/promises";
23826
+ import { basename as basename6, join as join18 } from "node:path";
22960
23827
  var SESSION_FILE_EXT = ".ndjson";
22961
23828
  var PREVIEW_LIMIT = 72;
22962
23829
  var SessionLibrary = class {
@@ -23016,7 +23883,7 @@ var SessionLibrary = class {
23016
23883
  if (!await this.#missing(toPath)) {
23017
23884
  throw new Error(`SessionLibrary.rename: session "${toId}" already exists`);
23018
23885
  }
23019
- await mkdir(this.#dir, { recursive: true });
23886
+ await mkdir2(this.#dir, { recursive: true });
23020
23887
  await rename(fromPath, toPath);
23021
23888
  return toPath;
23022
23889
  }
@@ -23025,14 +23892,14 @@ var SessionLibrary = class {
23025
23892
  * there was nothing to delete — never throws on an already-absent session.
23026
23893
  */
23027
23894
  async remove(id) {
23028
- const path3 = this.pathOf(id);
23029
- if (await this.#missing(path3)) return false;
23030
- await rm(path3, { force: true });
23895
+ const path4 = this.pathOf(id);
23896
+ if (await this.#missing(path4)) return false;
23897
+ await rm(path4, { force: true });
23031
23898
  return true;
23032
23899
  }
23033
23900
  /** Absolute path a session id persists to. */
23034
23901
  pathOf(id) {
23035
- return join16(this.#dir, `${id}${SESSION_FILE_EXT}`);
23902
+ return join18(this.#dir, `${id}${SESSION_FILE_EXT}`);
23036
23903
  }
23037
23904
  /**
23038
23905
  * Flatten a loaded transcript into an ordered {@link BranchNode} list for a
@@ -23056,9 +23923,9 @@ var SessionLibrary = class {
23056
23923
  for (const node of nodes) {
23057
23924
  if (node.parent !== null) hasChildren.add(node.parent);
23058
23925
  }
23059
- const out = [];
23926
+ const out2 = [];
23060
23927
  const walk = (entry, depth) => {
23061
- out.push({
23928
+ out2.push({
23062
23929
  id: entry.id,
23063
23930
  parent: entry.parent,
23064
23931
  label: labelFor(entry, depth),
@@ -23073,7 +23940,7 @@ var SessionLibrary = class {
23073
23940
  for (const root of childrenOf.get(null) ?? []) {
23074
23941
  walk(root, 0);
23075
23942
  }
23076
- return out;
23943
+ return out2;
23077
23944
  }
23078
23945
  /**
23079
23946
  * Extract the user prompts along a loaded transcript's active branch as fork
@@ -23084,14 +23951,14 @@ var SessionLibrary = class {
23084
23951
  * text, projecting each to a {@link PriorTurn} a forking picker can offer.
23085
23952
  */
23086
23953
  priorTurns(store) {
23087
- const out = [];
23954
+ const out2 = [];
23088
23955
  for (const entry of store.pathTo()) {
23089
23956
  if (entry.role !== "user") continue;
23090
23957
  const text = messageText(entry.content).trim();
23091
23958
  if (text.length === 0) continue;
23092
- out.push({ entryId: entry.id, text, preview: previewOf(text) });
23959
+ out2.push({ entryId: entry.id, text, preview: previewOf(text) });
23093
23960
  }
23094
- return out;
23961
+ return out2;
23095
23962
  }
23096
23963
  /* ---- internals ---- */
23097
23964
  /** Bare session ids present in the directory (no extension), or `[]`. */
@@ -23106,41 +23973,41 @@ var SessionLibrary = class {
23106
23973
  }
23107
23974
  /** Catalog row from file metadata only — no parse. */
23108
23975
  async #shallowRow(id) {
23109
- const path3 = this.pathOf(id);
23110
- const meta = await this.#fileMeta(path3);
23111
- return { id, path: path3, ...meta };
23976
+ const path4 = this.pathOf(id);
23977
+ const meta = await this.#fileMeta(path4);
23978
+ return { id, path: path4, ...meta };
23112
23979
  }
23113
23980
  /** Catalog row enriched by opening the transcript and reducing its text. */
23114
23981
  async #deepRow(id) {
23115
- const path3 = this.pathOf(id);
23116
- const meta = await this.#fileMeta(path3);
23982
+ const path4 = this.pathOf(id);
23983
+ const meta = await this.#fileMeta(path4);
23117
23984
  const store = await this.open(id);
23118
- if (store === null) return { id, path: path3, ...meta };
23985
+ if (store === null) return { id, path: path4, ...meta };
23119
23986
  const branch = store.pathTo();
23120
23987
  const conversational = branch.filter(isConversational);
23121
23988
  const opener = conversational.find((entry) => entry.role === "user");
23122
23989
  const preview2 = opener ? previewOf(messageText(opener.content)) : void 0;
23123
23990
  return {
23124
23991
  id,
23125
- path: path3,
23992
+ path: path4,
23126
23993
  ...meta,
23127
23994
  messageCount: conversational.length,
23128
23995
  ...preview2 ? { preview: preview2, name: preview2 } : {}
23129
23996
  };
23130
23997
  }
23131
23998
  /** Best-effort `{ size, lastModified }`; empty object when the file is gone. */
23132
- async #fileMeta(path3) {
23999
+ async #fileMeta(path4) {
23133
24000
  try {
23134
- const info2 = await stat(path3);
24001
+ const info2 = await stat(path4);
23135
24002
  return { size: info2.size, lastModified: info2.mtimeMs };
23136
24003
  } catch {
23137
24004
  return {};
23138
24005
  }
23139
24006
  }
23140
24007
  /** True when a path does not resolve to a readable file. */
23141
- async #missing(path3) {
24008
+ async #missing(path4) {
23142
24009
  try {
23143
- await readFile(path3);
24010
+ await readFile2(path4);
23144
24011
  return false;
23145
24012
  } catch {
23146
24013
  return true;
@@ -23204,16 +24071,16 @@ var CHROME_ROWS = 14;
23204
24071
  var MODAL_CHROME_ROWS = 24;
23205
24072
  var MIN_VISIBLE_ROWS = 8;
23206
24073
  var FALLBACK_CONTEXT_WINDOW = 2e5;
23207
- var PASTE_BURST_MS = 12;
24074
+ var PASTE_BURST_MS = PASTE_BURST_GAP_MS;
23208
24075
  var BRACKETED_PASTE = /\x1b?\[20[01]~/g;
23209
24076
  function readBranch() {
23210
24077
  try {
23211
- const out = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
24078
+ const out2 = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
23212
24079
  cwd: process.cwd(),
23213
24080
  encoding: "utf8",
23214
24081
  stdio: ["ignore", "pipe", "ignore"]
23215
24082
  }).trim();
23216
- return out.length > 0 ? out : null;
24083
+ return out2.length > 0 ? out2 : null;
23217
24084
  } catch {
23218
24085
  return null;
23219
24086
  }
@@ -23321,6 +24188,19 @@ function renderToolUpdateText(details) {
23321
24188
  const text = details.text;
23322
24189
  return typeof text === "string" ? text : void 0;
23323
24190
  }
24191
+ function permissionModeStatusText(mode) {
24192
+ switch (mode) {
24193
+ case "plan":
24194
+ return "Permission mode: PLAN \u2014 read-only research; mutating tools are blocked. Shift+Tab to cycle.";
24195
+ case "bypass":
24196
+ case "bypassPermissions":
24197
+ return "Permission mode: BYPASS \u2014 every tool auto-allowed (unsafe). Shift+Tab to cycle.";
24198
+ case "acceptEdits":
24199
+ return "Permission mode: ACCEPT-EDITS \u2014 edits auto-approved. Shift+Tab to cycle.";
24200
+ default:
24201
+ return "Permission mode: DEFAULT \u2014 tools prompt as usual. Shift+Tab to cycle.";
24202
+ }
24203
+ }
23324
24204
  function TerminalConsole(props) {
23325
24205
  const { conductor, theme, slash, verbose, onExit, initialInput, services } = props;
23326
24206
  const adapter = theme.adapter;
@@ -23331,22 +24211,23 @@ function TerminalConsole(props) {
23331
24211
  const chord = useRef3(NO_CHORD);
23332
24212
  const chordTimer = useRef3(null);
23333
24213
  const paste = useRef3(EMPTY_PASTE_VAULT);
24214
+ const activePaste = useRef3(null);
23334
24215
  const burst = useRef3("");
23335
24216
  const burstTimer = useRef3(null);
23336
24217
  const liveAnswer = useRef3(null);
23337
24218
  const liveReason = useRef3(null);
23338
24219
  const rowSeed = useRef3(0);
23339
24220
  const ctrlCAt = useRef3(0);
23340
- const [highlight, setHighlight] = useState7(0);
23341
- const [toolExecutions, setToolExecutions] = useState7({});
23342
- const [taskTokens, setTaskTokens] = useState7({});
23343
- const [subagentActivity, setSubagentActivity] = useState7({});
23344
- const [expandTools, setExpandTools] = useState7(false);
23345
- const [agentsOpen, setAgentsOpen] = useState7(false);
23346
- const [terminalRows, setTerminalRows] = useState7(
24221
+ const [highlight, setHighlight] = useState8(0);
24222
+ const [toolExecutions, setToolExecutions] = useState8({});
24223
+ const [taskTokens, setTaskTokens] = useState8({});
24224
+ const [subagentActivity, setSubagentActivity] = useState8({});
24225
+ const [expandTools, setExpandTools] = useState8(false);
24226
+ const [agentsOpen, setAgentsOpen] = useState8(false);
24227
+ const [terminalRows, setTerminalRows] = useState8(
23347
24228
  () => process.stdout.rows ?? FALLBACK_TERMINAL_ROWS
23348
24229
  );
23349
- const [, forceTick] = useState7(0);
24230
+ const [, forceTick] = useState8(0);
23350
24231
  const pulse = useCallback(() => forceTick((n) => n + 1), []);
23351
24232
  const dirReader = useMemo2(() => createDirReader(process.cwd()), []);
23352
24233
  const [approvalQueue, dispatchApproval] = useReducer(
@@ -23372,7 +24253,11 @@ function TerminalConsole(props) {
23372
24253
  (toolName, input, opts) => new Promise((resolve8) => {
23373
24254
  approvalSeed.current += 1;
23374
24255
  const id = `ap_${approvalSeed.current}`;
23375
- const request = { toolName, input };
24256
+ const request = {
24257
+ toolName,
24258
+ input,
24259
+ suggestions: allowAlwaysRuleStrings(toolName, input)
24260
+ };
23376
24261
  approvalResolvers.current.set(id, resolve8);
23377
24262
  const signal = opts.signal;
23378
24263
  if (signal !== void 0) {
@@ -23391,7 +24276,7 @@ function TerminalConsole(props) {
23391
24276
  }),
23392
24277
  [settleApproval]
23393
24278
  );
23394
- useEffect6(() => {
24279
+ useEffect7(() => {
23395
24280
  conductor.setApprovalResolver(requestApproval);
23396
24281
  return () => {
23397
24282
  conductor.setApprovalResolver(void 0);
@@ -23401,7 +24286,7 @@ function TerminalConsole(props) {
23401
24286
  const head = activeApproval(approvalQueue);
23402
24287
  const headId = head?.id;
23403
24288
  const approvalModalOpen = useRef3(false);
23404
- useEffect6(() => {
24289
+ useEffect7(() => {
23405
24290
  if (head === void 0) {
23406
24291
  if (approvalModalOpen.current) {
23407
24292
  approvalModalOpen.current = false;
@@ -23419,7 +24304,17 @@ function TerminalConsole(props) {
23419
24304
  }
23420
24305
  });
23421
24306
  }, [headId, settleApproval]);
23422
- useEffect6(() => {
24307
+ const cyclePermission = useCallback(() => {
24308
+ const now = conductor.cyclePermissionMode();
24309
+ let text = permissionModeStatusText(now);
24310
+ if (modeReleasesApprovals(now) && approvalResolvers.current.size > 0) {
24311
+ const parked = [...approvalResolvers.current.keys()];
24312
+ for (const id of parked) settleApproval(id, "allow-once");
24313
+ text += ` Released ${parked.length} pending approval${parked.length === 1 ? "" : "s"}.`;
24314
+ }
24315
+ dispatch2({ type: "status:set", status: { kind: "info", text } });
24316
+ }, [conductor, settleApproval]);
24317
+ useEffect7(() => {
23423
24318
  const off = conductor.subscribe((signal) => {
23424
24319
  switch (signal.kind) {
23425
24320
  case "prompt": {
@@ -23525,7 +24420,30 @@ function TerminalConsole(props) {
23525
24420
  break;
23526
24421
  }
23527
24422
  case "compacted": {
23528
- dispatch2({ type: "status:set", status: { kind: "info", text: "Conversation condensed." } });
24423
+ if (signal.manual) {
24424
+ const carried = [];
24425
+ for (const message of conductor.messages()) {
24426
+ const content = message.content;
24427
+ if (typeof content === "string" && content.startsWith(RESTORED_FILE_PREFIX)) {
24428
+ const firstLine = content.slice(RESTORED_FILE_PREFIX.length).split("\n", 1)[0];
24429
+ if (firstLine) carried.push(firstLine.trim());
24430
+ }
24431
+ }
24432
+ setCompactNote(carried);
24433
+ visibleFrom.current = conductor.messages().length;
24434
+ liveAnswer.current = null;
24435
+ liveReason.current = null;
24436
+ answerText.current = "";
24437
+ reasonText.current = "";
24438
+ dispatch2({ type: "rows:set", rows: [] });
24439
+ }
24440
+ dispatch2({
24441
+ type: "status:set",
24442
+ status: {
24443
+ kind: "info",
24444
+ text: signal.manual ? "Conversation condensed." : "Auto-compacted older history to stay within the context window (automatic \u2014 you didn't run /compact)."
24445
+ }
24446
+ });
23529
24447
  break;
23530
24448
  }
23531
24449
  case "persisted": {
@@ -23533,6 +24451,10 @@ function TerminalConsole(props) {
23533
24451
  break;
23534
24452
  }
23535
24453
  case "fault": {
24454
+ if (signal.transient) {
24455
+ dispatch2({ type: "status:set", status: { kind: "warning", text: signal.fault.message } });
24456
+ break;
24457
+ }
23536
24458
  liveAnswer.current = null;
23537
24459
  liveReason.current = null;
23538
24460
  dispatch2({ type: "busy:set", busy: false });
@@ -23552,12 +24474,14 @@ function TerminalConsole(props) {
23552
24474
  }, [conductor]);
23553
24475
  const answerText = useRef3("");
23554
24476
  const reasonText = useRef3("");
24477
+ const visibleFrom = useRef3(0);
24478
+ const [compactNote, setCompactNote] = useState8(null);
23555
24479
  const completion = useMemo2(
23556
24480
  () => completeAt(state.buffer, state.caret, slash, dirReader),
23557
24481
  [state.buffer, state.caret, slash, dirReader]
23558
24482
  );
23559
24483
  const suggestions = completion.suggestions;
23560
- useEffect6(() => {
24484
+ useEffect7(() => {
23561
24485
  if (highlight >= suggestions.length) setHighlight(0);
23562
24486
  }, [suggestions.length, highlight]);
23563
24487
  const submit = useCallback(
@@ -23581,10 +24505,10 @@ function TerminalConsole(props) {
23581
24505
  text: `Shell exited ${outcome.exitCode}.`
23582
24506
  }
23583
24507
  });
23584
- }).catch((err) => {
24508
+ }).catch((err2) => {
23585
24509
  dispatch2({
23586
24510
  type: "status:set",
23587
- status: { kind: "error", text: err instanceof Error ? err.message : String(err) }
24511
+ status: { kind: "error", text: err2 instanceof Error ? err2.message : String(err2) }
23588
24512
  });
23589
24513
  });
23590
24514
  return;
@@ -23622,15 +24546,23 @@ function TerminalConsole(props) {
23622
24546
  const id = freshRowId(rowSeed.current++);
23623
24547
  dispatch2({ type: "rows:append", row: { id, kind: "prompt", text } });
23624
24548
  dispatch2({ type: "busy:set", busy: true });
24549
+ if (conductor.snapshot().phase === "condensing") {
24550
+ dispatch2({
24551
+ type: "status:set",
24552
+ status: { kind: "info", text: "Queued message for after compaction." }
24553
+ });
24554
+ }
24555
+ setCompactNote(null);
24556
+ activePaste.current = null;
23625
24557
  answerText.current = "";
23626
24558
  reasonText.current = "";
23627
24559
  try {
23628
24560
  await conductor.submit(text);
23629
- } catch (err) {
24561
+ } catch (err2) {
23630
24562
  dispatch2({ type: "busy:set", busy: false });
23631
24563
  dispatch2({
23632
24564
  type: "status:set",
23633
- status: { kind: "error", text: err instanceof Error ? err.message : String(err) }
24565
+ status: { kind: "error", text: err2 instanceof Error ? err2.message : String(err2) }
23634
24566
  });
23635
24567
  }
23636
24568
  pulse();
@@ -23646,12 +24578,29 @@ function TerminalConsole(props) {
23646
24578
  const text = burst.current;
23647
24579
  if (text.length === 0) return;
23648
24580
  burst.current = "";
23649
- if (text.length > 1 && classifyPaste(text) === "coalesce") {
24581
+ const now = Date.now();
24582
+ const active = activePaste.current;
24583
+ if (active !== null && now <= active.until && text.length > 1 && pasteLines(paste.current, active.ordinal) < PASTE_MARKER_LINE_CAP) {
24584
+ const captured = appendToPaste(active.ordinal, text, paste.current);
24585
+ paste.current = captured.vault;
24586
+ dispatch2({ type: "buffer:replaceSpan", find: active.marker, replaceWith: captured.marker });
24587
+ activePaste.current = {
24588
+ ordinal: active.ordinal,
24589
+ marker: captured.marker,
24590
+ until: now + PASTE_CONTINUATION_MS
24591
+ };
24592
+ } else if (text.length > 1 && (classifyPaste(text) === "coalesce" || active !== null && now <= active.until)) {
23650
24593
  const captured = coalescePaste(text, paste.current);
23651
24594
  paste.current = captured.vault;
23652
24595
  dispatch2({ type: "buffer:insert", text: captured.marker });
24596
+ activePaste.current = {
24597
+ ordinal: captured.vault.minted,
24598
+ marker: captured.marker,
24599
+ until: now + PASTE_CONTINUATION_MS
24600
+ };
23653
24601
  } else {
23654
24602
  dispatch2({ type: "buffer:insert", text });
24603
+ activePaste.current = null;
23655
24604
  }
23656
24605
  setHighlight(0);
23657
24606
  }, []);
@@ -23667,14 +24616,14 @@ function TerminalConsole(props) {
23667
24616
  chordTimer.current = null;
23668
24617
  }
23669
24618
  }, []);
23670
- useEffect6(
24619
+ useEffect7(
23671
24620
  () => () => {
23672
24621
  if (chordTimer.current !== null) clearTimeout(chordTimer.current);
23673
24622
  if (burstTimer.current !== null) clearTimeout(burstTimer.current);
23674
24623
  },
23675
24624
  []
23676
24625
  );
23677
- useEffect6(() => {
24626
+ useEffect7(() => {
23678
24627
  const onResize = () => setTerminalRows(process.stdout.rows ?? FALLBACK_TERMINAL_ROWS);
23679
24628
  process.stdout.on("resize", onResize);
23680
24629
  return () => {
@@ -23682,7 +24631,7 @@ function TerminalConsole(props) {
23682
24631
  };
23683
24632
  }, []);
23684
24633
  const titleName = conductor.sessionName();
23685
- useEffect6(() => {
24634
+ useEffect7(() => {
23686
24635
  writeTerminalTitle(titleName && titleName.length > 0 ? titleName : "indus console");
23687
24636
  }, [titleName]);
23688
24637
  const requestExit = useCallback(() => {
@@ -23808,7 +24757,7 @@ function TerminalConsole(props) {
23808
24757
  }
23809
24758
  } else if (state.modal.kind !== "none") {
23810
24759
  dispatch2({ type: "modal:close" });
23811
- } else if (state.busy) {
24760
+ } else if (state.busy || conductor.snapshot().phase === "condensing") {
23812
24761
  denyAllApprovals();
23813
24762
  conductor.abort();
23814
24763
  } else if (suggestions.length > 0) {
@@ -23844,12 +24793,9 @@ function TerminalConsole(props) {
23844
24793
  dispatch2({ type: "status:set", status: { kind: "info", text: `Reasoning effort: ${level}.` } });
23845
24794
  return;
23846
24795
  }
23847
- case "mode:cyclePermission": {
23848
- const now = conductor.cyclePermissionMode();
23849
- 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.";
23850
- dispatch2({ type: "status:set", status: { kind: "info", text } });
24796
+ case "mode:cyclePermission":
24797
+ cyclePermission();
23851
24798
  return;
23852
- }
23853
24799
  case "view:toggleReasoning":
23854
24800
  dispatch2({ type: "toggle:reasoning" });
23855
24801
  return;
@@ -23862,11 +24808,25 @@ function TerminalConsole(props) {
23862
24808
  }
23863
24809
  return;
23864
24810
  }
24811
+ case "queue:steer": {
24812
+ if (conductor.steerNow()) {
24813
+ dispatch2({
24814
+ type: "status:set",
24815
+ status: { kind: "info", text: "Running your queued message now \u2014 current run stopped." }
24816
+ });
24817
+ } else {
24818
+ dispatch2({
24819
+ type: "status:set",
24820
+ status: { kind: "warning", text: "No queued message to run \u2014 type one first, then Ctrl+X." }
24821
+ });
24822
+ }
24823
+ return;
24824
+ }
23865
24825
  case "input:pasteImage": {
23866
- const path3 = readClipboardImage();
23867
- if (path3 !== null) {
24826
+ const path4 = readClipboardImage();
24827
+ if (path4 !== null) {
23868
24828
  const sep = state.buffer.length > 0 && !/\s$/.test(state.buffer) ? " " : "";
23869
- dispatch2({ type: "buffer:insert", text: `${sep}${path3}` });
24829
+ dispatch2({ type: "buffer:insert", text: `${sep}${path4}` });
23870
24830
  } else {
23871
24831
  dispatch2({ type: "status:set", status: { kind: "warning", text: "No image on the clipboard." } });
23872
24832
  }
@@ -23895,20 +24855,30 @@ function TerminalConsole(props) {
23895
24855
  return;
23896
24856
  }
23897
24857
  }, { isActive: state.modal.kind === "none" && !agentsOpen });
24858
+ useInput3((_rawInput, key) => {
24859
+ if (key.tab && key.shift) cyclePermission();
24860
+ }, { isActive: state.modal.kind === "approval" });
23898
24861
  const seeded = useRef3(false);
23899
- useEffect6(() => {
24862
+ useEffect7(() => {
23900
24863
  if (seeded.current) return;
23901
24864
  seeded.current = true;
24865
+ const opening = conductor.permissionMode();
24866
+ if (opening !== "default") {
24867
+ dispatch2({
24868
+ type: "status:set",
24869
+ status: { kind: "warning", text: permissionModeStatusText(opening) }
24870
+ });
24871
+ }
23902
24872
  if (initialInput && initialInput.trim().length > 0) {
23903
24873
  void runTurn(initialInput);
23904
24874
  }
23905
24875
  }, []);
23906
- const liveMessages = [...conductor.messages()];
24876
+ const allMessages = [...conductor.messages()];
24877
+ if (allMessages.length < visibleFrom.current) visibleFrom.current = 0;
24878
+ const liveMessages = allMessages.slice(visibleFrom.current);
23907
24879
  const agentRuns = deriveAgentRuns(liveMessages);
23908
24880
  const snapshot = projectSnapshot2(conductor, liveMessages, services);
23909
24881
  const branch = useMemo2(() => readBranch(), []);
23910
- const modalOpen = state.modal.kind !== "none";
23911
- const maxItems = transcriptHeight(terminalRows, modalOpen, agentPanelRows(agentRuns));
23912
24882
  const pendingMessages = conductor.pendingInputs().map((entry, index) => ({
23913
24883
  id: `pending-${index}-${entry.mode}`,
23914
24884
  mode: entry.mode,
@@ -23918,12 +24888,21 @@ function TerminalConsole(props) {
23918
24888
  const SHOW_TOOL_ACTIVITY = false;
23919
24889
  const runningTools = Object.values(toolExecutions).some((t) => t.status === "running");
23920
24890
  const showTaskPanel = SHOW_TOOL_ACTIVITY && runningTools || pendingMessages.length > 0;
24891
+ const modalOpen = state.modal.kind !== "none";
24892
+ const streamingAnswerRows = liveAnswer.current !== null && answerText.current ? Math.min(6, answerText.current.split("\n").length + 1) : 0;
24893
+ const taskPanelRows = showTaskPanel ? 2 + Math.min(pendingMessages.length, 8) : 0;
24894
+ const workingIndicatorRows = state.busy ? 2 : 0;
24895
+ const maxItems = transcriptHeight(
24896
+ terminalRows,
24897
+ modalOpen,
24898
+ agentPanelRows(agentRuns) + streamingAnswerRows + taskPanelRows + workingIndicatorRows
24899
+ );
23921
24900
  const providerCount = countProviders(conductor);
23922
24901
  const lastSeenVersion = services?.settings.get("lastSeenVersion") ?? "";
23923
24902
  const startupMap = useMemo2(
23924
24903
  () => gatherStartup({
23925
24904
  cwd: process.cwd(),
23926
- home: homedir5(),
24905
+ home: homedir7(),
23927
24906
  version: VERSION,
23928
24907
  lastSeenVersion
23929
24908
  }),
@@ -23932,8 +24911,8 @@ function TerminalConsole(props) {
23932
24911
  const quietStartup = (services?.settings.get("quietStartup") ?? false) && !verbose;
23933
24912
  const sweep = (services?.settings.get("logoSweep") ?? false) && !(services?.settings.get("reducedMotion") ?? false) && Boolean(process.stdout.isTTY);
23934
24913
  const welcomeSeed = PINNED_WELCOME_SEED;
23935
- const [welcomeName, setWelcomeName] = useState7(void 0);
23936
- useEffect6(() => {
24914
+ const [welcomeName, setWelcomeName] = useState8(void 0);
24915
+ useEffect7(() => {
23937
24916
  if (!services) return;
23938
24917
  let live = true;
23939
24918
  const provider = services.settings.get("defaultProvider");
@@ -23945,6 +24924,12 @@ function TerminalConsole(props) {
23945
24924
  live = false;
23946
24925
  };
23947
24926
  }, [services]);
24927
+ const authedProviders = useAuthenticatedProviders(services, state.status);
24928
+ const boundModelId = conductor.snapshot().modelId;
24929
+ const boundProvider = boundModelId.includes("/") ? boundModelId.split("/")[0] : boundModelId;
24930
+ const modelAuthed = authedProviders === null || authedProviders.has(boundProvider);
24931
+ const bannerModelId = modelAuthed ? boundModelId : "no authenticated model \u2014 run /login";
24932
+ const statusBarSnapshot = modelAuthed || !snapshot.model ? snapshot : { ...snapshot, model: { ...snapshot.model, provider: "no model", id: "run /login" } };
23948
24933
  return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
23949
24934
  agentsOpen ? (
23950
24935
  // The drill-in agents view replaces the transcript while open; it owns
@@ -23964,7 +24949,7 @@ function TerminalConsole(props) {
23964
24949
  Banner,
23965
24950
  {
23966
24951
  theme: adapter,
23967
- modelId: conductor.snapshot().modelId,
24952
+ modelId: bannerModelId,
23968
24953
  workspace: process.cwd(),
23969
24954
  version: VERSION,
23970
24955
  verbose,
@@ -23978,6 +24963,23 @@ function TerminalConsole(props) {
23978
24963
  variantSeed: welcomeSeed
23979
24964
  }
23980
24965
  ),
24966
+ compactNote !== null ? /* @__PURE__ */ jsxs10(
24967
+ Box10,
24968
+ {
24969
+ flexDirection: "column",
24970
+ borderStyle: "round",
24971
+ borderColor: adapter.colors["borderMuted"],
24972
+ paddingX: 1,
24973
+ marginBottom: 1,
24974
+ children: [
24975
+ /* @__PURE__ */ jsx13(Text10, { children: adapter.color("accent", "\u25C6 Context condensed \u2014 fresh session, history summarized") }),
24976
+ compactNote.length > 0 ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
24977
+ /* @__PURE__ */ jsx13(Text10, { children: adapter.muted("Files carried into the new context:") }),
24978
+ compactNote.map((file, index) => /* @__PURE__ */ jsx13(Text10, { children: adapter.muted(` \xB7 ${file}`) }, index))
24979
+ ] }) : /* @__PURE__ */ jsx13(Text10, { children: adapter.muted("No files re-attached; the summary digest carries the history.") })
24980
+ ]
24981
+ }
24982
+ ) : null,
23981
24983
  /* @__PURE__ */ jsx13(
23982
24984
  MessageList,
23983
24985
  {
@@ -24001,7 +25003,14 @@ function TerminalConsole(props) {
24001
25003
  pendingMessages
24002
25004
  }
24003
25005
  ) : null,
24004
- /* @__PURE__ */ jsx13(WorkingIndicator, { theme: adapter, busy: state.busy, phase: conductor.snapshot().phase }),
25006
+ /* @__PURE__ */ jsx13(
25007
+ WorkingIndicator,
25008
+ {
25009
+ theme: adapter,
25010
+ busy: state.busy || conductor.snapshot().phase === "condensing",
25011
+ phase: conductor.snapshot().phase
25012
+ }
25013
+ ),
24005
25014
  /* @__PURE__ */ jsx13(
24006
25015
  Composer,
24007
25016
  {
@@ -24017,7 +25026,7 @@ function TerminalConsole(props) {
24017
25026
  StatusBar,
24018
25027
  {
24019
25028
  theme: adapter,
24020
- snapshot,
25029
+ snapshot: statusBarSnapshot,
24021
25030
  branch,
24022
25031
  providerCount,
24023
25032
  status: state.status,
@@ -24065,7 +25074,7 @@ async function mountConsole(conductor, opts = {}) {
24065
25074
  }
24066
25075
 
24067
25076
  // src/boot/auth-vault.ts
24068
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
25077
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
24069
25078
  import { dirname as dirname5 } from "node:path";
24070
25079
  import {
24071
25080
  ensureFreshOAuthCredentials,
@@ -24089,9 +25098,9 @@ function normaliseRecord(value) {
24089
25098
  }
24090
25099
  return void 0;
24091
25100
  }
24092
- function readAuthFile(path3) {
25101
+ function readAuthFile(path4) {
24093
25102
  try {
24094
- const text = readFileSync11(path3, "utf8");
25103
+ const text = readFileSync13(path4, "utf8");
24095
25104
  const parsed = JSON.parse(text);
24096
25105
  if (parsed === null || typeof parsed !== "object") return {};
24097
25106
  const file = {};
@@ -24109,9 +25118,9 @@ function readAuthFile(path3) {
24109
25118
  return {};
24110
25119
  }
24111
25120
  }
24112
- function writeAuthFile(path3, data) {
24113
- mkdirSync4(dirname5(path3), { recursive: true });
24114
- writeFileSync7(path3, `${JSON.stringify(data, null, 2)}
25121
+ function writeAuthFile(path4, data) {
25122
+ mkdirSync4(dirname5(path4), { recursive: true });
25123
+ writeFileSync7(path4, `${JSON.stringify(data, null, 2)}
24115
25124
  `, { mode: 384 });
24116
25125
  }
24117
25126
  function toCredentials(record) {
@@ -24205,11 +25214,14 @@ function createAuthVault(authPath) {
24205
25214
  };
24206
25215
  }
24207
25216
 
25217
+ // src/boot/runners/repl-runner.ts
25218
+ init_server_mode();
25219
+
24208
25220
  // src/boot/runners/session.ts
24209
25221
  init_conductor2();
24210
- import { existsSync as existsSync6, readFileSync as readFileSync13 } from "node:fs";
24211
- import { homedir as homedir6 } from "node:os";
24212
- import { join as join18 } from "node:path";
25222
+ import { existsSync as existsSync6, readFileSync as readFileSync15 } from "node:fs";
25223
+ import { homedir as homedir8 } from "node:os";
25224
+ import { join as join20 } from "node:path";
24213
25225
  init_capability_deck();
24214
25226
  init_task_card();
24215
25227
  init_workflow_card();
@@ -24240,7 +25252,7 @@ function argPreview(args) {
24240
25252
  return ` (${oneLine2(String(text), 56)})`;
24241
25253
  }
24242
25254
  function buildActivity(messages) {
24243
- const out = [];
25255
+ const out2 = [];
24244
25256
  for (const message of messages) {
24245
25257
  if (message.role !== "assistant") continue;
24246
25258
  const content = message.content;
@@ -24248,14 +25260,50 @@ function buildActivity(messages) {
24248
25260
  for (const part of content) {
24249
25261
  const p = part;
24250
25262
  if (p.type === "text" && typeof p.text === "string" && p.text.trim() || p.type === "thinking" && typeof p.thinking === "string" && p.thinking.trim()) {
24251
- out.push({ tone: "reason", text: oneLine2(p.text ?? p.thinking) });
25263
+ out2.push({ tone: "reason", text: oneLine2(p.text ?? p.thinking) });
24252
25264
  } else if (p.type === "toolCall" && typeof p.name === "string") {
24253
25265
  const name = p.name.charAt(0).toUpperCase() + p.name.slice(1);
24254
- out.push({ tone: "tool", text: `${name}${argPreview(p.arguments)}` });
25266
+ out2.push({ tone: "tool", text: `${name}${argPreview(p.arguments)}` });
24255
25267
  }
24256
25268
  }
24257
25269
  }
24258
- return out.slice(-14);
25270
+ return out2.slice(-14);
25271
+ }
25272
+ var MAX_PARALLEL_TASKS = (() => {
25273
+ const n = parseInt(process.env.INDUS_MAX_PARALLEL_TASKS ?? "", 10);
25274
+ return Number.isFinite(n) && n >= 1 ? n : 2;
25275
+ })();
25276
+ var MAX_REPORT_CHARS = (() => {
25277
+ const n = parseInt(process.env.INDUS_MAX_DELEGATE_REPORT_CHARS ?? "", 10);
25278
+ return Number.isFinite(n) && n >= 1e3 ? n : 48e3;
25279
+ })();
25280
+ var activeTasks = 0;
25281
+ var taskQueue = [];
25282
+ async function acquireTaskSlot() {
25283
+ if (activeTasks < MAX_PARALLEL_TASKS) {
25284
+ activeTasks++;
25285
+ } else {
25286
+ await new Promise((resolve8) => taskQueue.push(resolve8));
25287
+ }
25288
+ let released = false;
25289
+ return () => {
25290
+ if (released) return;
25291
+ released = true;
25292
+ const next = taskQueue.shift();
25293
+ if (next) next();
25294
+ else activeTasks--;
25295
+ };
25296
+ }
25297
+ function clampReport(report) {
25298
+ if (report.length <= MAX_REPORT_CHARS) return report;
25299
+ const head = Math.floor(MAX_REPORT_CHARS * 0.7);
25300
+ const tail = MAX_REPORT_CHARS - head;
25301
+ const omitted = report.length - MAX_REPORT_CHARS;
25302
+ return `${report.slice(0, head)}
25303
+
25304
+ \u2026[delegate report truncated \u2014 ${omitted} characters omitted to protect parent context]\u2026
25305
+
25306
+ ${report.slice(report.length - tail)}`;
24259
25307
  }
24260
25308
  function finalAssistantText2(messages) {
24261
25309
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -24283,48 +25331,65 @@ function createDelegateRunner(opts) {
24283
25331
 
24284
25332
  Context:
24285
25333
  ${request.context}` : request.objective;
24286
- const agent = opts.spawn?.(request.objective, request.context) ?? new Agent3({
24287
- initialState: {
24288
- // `model` is defined here: the early-return above guaranteed it when
24289
- // no `spawn` seam was given.
24290
- model,
24291
- systemPrompt: opts.system,
24292
- // The sub-agent's deck — the `'authoring'` profile excludes the
24293
- // `task` card, so the sub-agent cannot recurse into delegation.
24294
- tools: opts.tools?.() ?? provisionDeck("authoring", { cwd: opts.cwd }).tools()
24295
- },
24296
- ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {}
24297
- });
24298
- const onAbort = () => agent.abort();
24299
- signal?.addEventListener("abort", onAbort, { once: true });
24300
- let lastSignature = "";
24301
- const reportProgress = () => {
24302
- if (!onProgress) return;
24303
- const tokens = sumTokens(agent.state.messages);
24304
- const activity = buildActivity(agent.state.messages);
24305
- const signature2 = `${tokens}:${activity.length}`;
24306
- if (signature2 !== lastSignature) {
24307
- lastSignature = signature2;
24308
- onProgress({ tokens, activity });
24309
- }
24310
- };
24311
- const offProgress = onProgress ? agent.subscribe?.(reportProgress) : void 0;
25334
+ const gatewayBaseUrls = await opts.getGatewayBaseUrls?.();
25335
+ const releaseSlot = await acquireTaskSlot();
24312
25336
  try {
24313
- await agent.prompt(objective);
24314
- reportProgress();
24315
- } catch (cause) {
24316
- const message = cause instanceof Error ? cause.message : String(cause);
24317
- return { ok: false, report: message || "delegation failed" };
25337
+ if (signal?.aborted) {
25338
+ return { ok: false, report: "delegation aborted" };
25339
+ }
25340
+ let agent = opts.spawn?.(request.objective, request.context);
25341
+ if (agent === void 0) {
25342
+ const tools = opts.tools?.() ?? provisionDeck("authoring", { cwd: opts.cwd }).tools();
25343
+ agent = new Agent3({
25344
+ initialState: {
25345
+ // `model` is defined here: the early-return above guaranteed it when
25346
+ // no `spawn` seam was given. In server mode, route it through the
25347
+ // gateway (freshly resolved above) rather than the raw provider url.
25348
+ model: withGatewayBaseUrl(model, gatewayBaseUrls),
25349
+ systemPrompt: opts.system,
25350
+ tools
25351
+ },
25352
+ ...opts.getApiKey ? { getApiKey: opts.getApiKey } : {},
25353
+ // Gate the sub-agent's inner tool loop with the parent session's live
25354
+ // policy (rules + current mode). Resolver-less: `ask` denies — a
25355
+ // sub-agent cannot prompt the user — while deny rules, plan mode, and
25356
+ // the catastrophic-bash blocklist are enforced per inner tool call.
25357
+ ...opts.permissionPolicy !== void 0 ? { canUseTool: createSubagentPermissionGate(opts.permissionPolicy, tools) } : {}
25358
+ });
25359
+ }
25360
+ const onAbort = () => agent.abort();
25361
+ signal?.addEventListener("abort", onAbort, { once: true });
25362
+ let lastSignature = "";
25363
+ const reportProgress = () => {
25364
+ if (!onProgress) return;
25365
+ const tokens = sumTokens(agent.state.messages);
25366
+ const activity = buildActivity(agent.state.messages);
25367
+ const signature2 = `${tokens}:${activity.length}`;
25368
+ if (signature2 !== lastSignature) {
25369
+ lastSignature = signature2;
25370
+ onProgress({ tokens, activity });
25371
+ }
25372
+ };
25373
+ const offProgress = onProgress ? agent.subscribe?.(reportProgress) : void 0;
25374
+ try {
25375
+ await agent.prompt(objective);
25376
+ reportProgress();
25377
+ } catch (cause) {
25378
+ const message = cause instanceof Error ? cause.message : String(cause);
25379
+ return { ok: false, report: clampReport(message || "delegation failed") };
25380
+ } finally {
25381
+ offProgress?.();
25382
+ signal?.removeEventListener("abort", onAbort);
25383
+ }
25384
+ const error = agent.state.error;
25385
+ const report = finalAssistantText2(agent.state.messages);
25386
+ return {
25387
+ ok: error === void 0 && report.length > 0,
25388
+ report: clampReport(error ?? (report || "(no output)"))
25389
+ };
24318
25390
  } finally {
24319
- offProgress?.();
24320
- signal?.removeEventListener("abort", onAbort);
25391
+ releaseSlot();
24321
25392
  }
24322
- const error = agent.state.error;
24323
- const report = finalAssistantText2(agent.state.messages);
24324
- return {
24325
- ok: error === void 0 && report.length > 0,
24326
- report: error ?? (report || "(no output)")
24327
- };
24328
25393
  }
24329
25394
  };
24330
25395
  }
@@ -24435,7 +25500,7 @@ var CheckpointStore = class {
24435
25500
  snapshotFor(nodeId) {
24436
25501
  const paths = this.byNode.get(nodeId);
24437
25502
  if (paths === void 0) return [];
24438
- return [...paths].map(([path3, previous]) => ({ path: path3, previous }));
25503
+ return [...paths].map(([path4, previous]) => ({ path: path4, previous }));
24439
25504
  }
24440
25505
  /** Whether a node has ANY recorded file snapshot (the picker's restore gate). */
24441
25506
  hasSnapshot(nodeId) {
@@ -24460,15 +25525,15 @@ var CheckpointStore = class {
24460
25525
  restore(nodeId) {
24461
25526
  const snapshots = this.snapshotFor(nodeId);
24462
25527
  const restored = [];
24463
- for (const { path: path3, previous } of snapshots) {
25528
+ for (const { path: path4, previous } of snapshots) {
24464
25529
  try {
24465
25530
  if (previous === null) {
24466
- rmSync(path3, { force: true });
25531
+ rmSync(path4, { force: true });
24467
25532
  } else {
24468
- mkdirSync5(dirname6(path3), { recursive: true });
24469
- writeFileSync8(path3, previous, "utf8");
25533
+ mkdirSync5(dirname6(path4), { recursive: true });
25534
+ writeFileSync8(path4, previous, "utf8");
24470
25535
  }
24471
- restored.push(path3);
25536
+ restored.push(path4);
24472
25537
  } catch {
24473
25538
  }
24474
25539
  }
@@ -24527,8 +25592,8 @@ function concatAddonTools(deck, bundle) {
24527
25592
 
24528
25593
  // src/boot/runners/memdir.ts
24529
25594
  init_capability_deck();
24530
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
24531
- import { join as join17 } from "node:path";
25595
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
25596
+ import { join as join19 } from "node:path";
24532
25597
  var MEMORY_ENTRYPOINT = "MEMORY.md";
24533
25598
  var MAX_ENTRYPOINT_LINES = 200;
24534
25599
  var MAX_ENTRYPOINT_BYTES = 25e3;
@@ -24554,19 +25619,19 @@ function truncateEntrypointContent(raw) {
24554
25619
  }
24555
25620
  function memoryDirFor(workspace, cwd) {
24556
25621
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
24557
- return join17(workspace.profileDir, "memory", `--${slug}--`);
25622
+ return join19(workspace.profileDir, "memory", `--${slug}--`);
24558
25623
  }
24559
25624
  var DiskMemoryStore = class {
24560
25625
  constructor(memDir) {
24561
25626
  this.memDir = memDir;
24562
- this.file = join17(memDir, MEMORY_ENTRYPOINT);
25627
+ this.file = join19(memDir, MEMORY_ENTRYPOINT);
24563
25628
  }
24564
25629
  memDir;
24565
25630
  file;
24566
25631
  /** The current note, or `""` when no file has been written yet. */
24567
25632
  read() {
24568
25633
  try {
24569
- return existsSync5(this.file) ? readFileSync12(this.file, "utf8") : "";
25634
+ return existsSync5(this.file) ? readFileSync14(this.file, "utf8") : "";
24570
25635
  } catch {
24571
25636
  return "";
24572
25637
  }
@@ -24587,9 +25652,9 @@ ${line}`;
24587
25652
  function loadMemoryDoc(memDir) {
24588
25653
  let raw;
24589
25654
  try {
24590
- const file = join17(memDir, MEMORY_ENTRYPOINT);
25655
+ const file = join19(memDir, MEMORY_ENTRYPOINT);
24591
25656
  if (!existsSync5(file)) return void 0;
24592
- raw = readFileSync12(file, "utf8");
25657
+ raw = readFileSync14(file, "utf8");
24593
25658
  } catch {
24594
25659
  return void 0;
24595
25660
  }
@@ -24601,11 +25666,13 @@ function loadMemoryDoc(memDir) {
24601
25666
  // src/boot/runners/session.ts
24602
25667
  init_briefing();
24603
25668
  init_window_budget();
25669
+ init_server_token();
25670
+ init_server_mode();
24604
25671
  function authenticatedProviders(ctx) {
24605
25672
  try {
24606
- const path3 = ctx.workspace.authPath;
24607
- if (!existsSync6(path3)) return [];
24608
- const data = JSON.parse(readFileSync13(path3, "utf8"));
25673
+ const path4 = ctx.workspace.authPath;
25674
+ if (!existsSync6(path4)) return [];
25675
+ const data = JSON.parse(readFileSync15(path4, "utf8"));
24609
25676
  return Object.keys(data).filter((id) => {
24610
25677
  const accounts = data[id];
24611
25678
  return id.length > 0 && accounts !== null && typeof accounts === "object" && Object.keys(accounts).length > 0;
@@ -24622,8 +25689,18 @@ var PREFERRED_DEFAULT = {
24622
25689
  "claude-sonnet-4-0",
24623
25690
  "claude-opus-4-5"
24624
25691
  ],
24625
- openai: ["gpt-5.1", "gpt-4.1", "gpt-4o"]
25692
+ openai: ["gpt-5.1", "gpt-4.1", "gpt-4o"],
25693
+ minimax: ["MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.5"],
25694
+ sarvam: ["sarvam-105b", "sarvam-30b"]
24626
25695
  };
25696
+ function hasServerSessionSync() {
25697
+ try {
25698
+ const token = JSON.parse(readFileSync15(TOKEN_FILE, "utf8"));
25699
+ return typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1e3;
25700
+ } catch {
25701
+ return false;
25702
+ }
25703
+ }
24627
25704
  function providerDefaultModel(catalog, provider) {
24628
25705
  const cards = catalog.byProvider(provider);
24629
25706
  if (cards.length === 0) return void 0;
@@ -24643,6 +25720,12 @@ function resolveModelId(ctx) {
24643
25720
  const id = providerDefaultModel(catalog, provider);
24644
25721
  if (id !== void 0) return id;
24645
25722
  }
25723
+ if (hasServerSessionSync()) {
25724
+ for (const provider of serverTierProviders()) {
25725
+ const id = providerDefaultModel(catalog, provider);
25726
+ if (id !== void 0) return id;
25727
+ }
25728
+ }
24646
25729
  const card = new ModelMatcher(catalog).resolve({});
24647
25730
  return card?.id ?? "";
24648
25731
  }
@@ -24667,9 +25750,9 @@ var PROVIDER_ENV = {
24667
25750
  };
24668
25751
  function primeProviderEnv(ctx) {
24669
25752
  try {
24670
- const path3 = ctx.workspace.authPath;
24671
- if (!existsSync6(path3)) return;
24672
- const data = JSON.parse(readFileSync13(path3, "utf8"));
25753
+ const path4 = ctx.workspace.authPath;
25754
+ if (!existsSync6(path4)) return;
25755
+ const data = JSON.parse(readFileSync15(path4, "utf8"));
24673
25756
  for (const [provider, accounts] of Object.entries(data)) {
24674
25757
  if (provider.startsWith("_") || accounts === null || typeof accounts !== "object") continue;
24675
25758
  const envVar = PROVIDER_ENV[provider];
@@ -24684,7 +25767,7 @@ function primeProviderEnv(ctx) {
24684
25767
  } catch {
24685
25768
  }
24686
25769
  }
24687
- function buildKeyResolver(vault, requestedAccount) {
25770
+ function buildKeyResolver(ctx, vault, requestedAccount) {
24688
25771
  return async (provider) => {
24689
25772
  try {
24690
25773
  const candidates = [
@@ -24697,6 +25780,8 @@ function buildKeyResolver(vault, requestedAccount) {
24697
25780
  const key = await vault.readUsableKey(provider, account);
24698
25781
  if (key) return key;
24699
25782
  }
25783
+ const server = await resolveServerMode(ctx, provider, vault);
25784
+ if (server.serverMode && server.token) return server.token;
24700
25785
  return void 0;
24701
25786
  } catch {
24702
25787
  return void 0;
@@ -24709,7 +25794,7 @@ function resolveThinkingLevel(raw) {
24709
25794
  }
24710
25795
  function resolveText(value) {
24711
25796
  try {
24712
- if (existsSync6(value)) return readFileSync13(value, "utf8");
25797
+ if (existsSync6(value)) return readFileSync15(value, "utf8");
24713
25798
  } catch {
24714
25799
  }
24715
25800
  return value;
@@ -24717,8 +25802,8 @@ function resolveText(value) {
24717
25802
  var PROFILE_DIR2 = ".indusagi";
24718
25803
  function skillRoots2(cwd) {
24719
25804
  return [
24720
- { dir: join18(cwd, PROFILE_DIR2, "skills"), origin: "project" },
24721
- { dir: join18(homedir6(), PROFILE_DIR2, "skills"), origin: "user" }
25805
+ { dir: join20(cwd, PROFILE_DIR2, "skills"), origin: "project" },
25806
+ { dir: join20(homedir8(), PROFILE_DIR2, "skills"), origin: "user" }
24722
25807
  ];
24723
25808
  }
24724
25809
  function gatherModelSkills(cwd) {
@@ -24750,7 +25835,7 @@ function selectTools(cwd, inv, runner, memoryStore, checkpoint, workflowRunner)
24750
25835
  }
24751
25836
  function composeSystem(tools, inv, cwd, skills = [], memoryDoc) {
24752
25837
  const contextDocs = [
24753
- ...gatherContextDocs(cwd, homedir6()),
25838
+ ...gatherContextDocs(cwd, homedir8()),
24754
25839
  ...memoryDoc ? [memoryDoc] : []
24755
25840
  ];
24756
25841
  const base = inv.system !== void 0 ? resolveText(inv.system) : composeBriefing({ tools, contextDocs, skills });
@@ -24768,9 +25853,9 @@ async function loadMcpTools(inv, cwd) {
24768
25853
  console.error = () => {
24769
25854
  };
24770
25855
  try {
24771
- for (const path3 of paths) {
25856
+ for (const path4 of paths) {
24772
25857
  try {
24773
- const servers = loadMCPConfig2(path3);
25858
+ const servers = loadMCPConfig2(path4);
24774
25859
  if (servers.length === 0) continue;
24775
25860
  const pool = new MCPClientPool2({ servers });
24776
25861
  await pool.connectAll();
@@ -24789,7 +25874,7 @@ async function loadMcpTools(inv, cwd) {
24789
25874
  }
24790
25875
  function sessionScopeDir(sessionsRoot, cwd) {
24791
25876
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
24792
- return join18(sessionsRoot, `--${slug}--`);
25877
+ return join20(sessionsRoot, `--${slug}--`);
24793
25878
  }
24794
25879
  function lastUserTurnStart(messages) {
24795
25880
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -24799,7 +25884,11 @@ function lastUserTurnStart(messages) {
24799
25884
  }
24800
25885
  var condenseTranscript = async (messages, opts) => {
24801
25886
  const model = opts?.model;
24802
- const deps = model ? { model } : {};
25887
+ const deps = model ? {
25888
+ model,
25889
+ ...opts?.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
25890
+ ...opts?.signal !== void 0 ? { signal: opts.signal } : {}
25891
+ } : {};
24803
25892
  const cleared = clearStaleToolResults(messages);
24804
25893
  if (opts?.force) {
24805
25894
  const cut = lastUserTurnStart(cleared);
@@ -24847,10 +25936,8 @@ function readOnlyNamesFromDeck(tools) {
24847
25936
  }
24848
25937
  return names;
24849
25938
  }
24850
- function buildPermissionGate(store, tools, planReachable = false) {
24851
- const rules = gatherPermissionRules(store);
24852
- const mode = resolveDefaultMode(store);
24853
- if (rules.length === 0 && (mode === "default" || mode === "bypass") && !planReachable) {
25939
+ function buildPermissionGate(rules, mode, tools, planReachable = false) {
25940
+ if (rules.length === 0 && mode === "bypass" && !planReachable) {
24854
25941
  return void 0;
24855
25942
  }
24856
25943
  const readOnlyToolNames = readOnlyNamesFromDeck(tools);
@@ -24860,9 +25947,17 @@ function buildPermissionGate(store, tools, planReachable = false) {
24860
25947
  // The conductor's stable approval delegate: denies until the interactive
24861
25948
  // console installs a resolver via setApprovalResolver, then routes `ask`.
24862
25949
  ...requestApproval !== void 0 ? { requestApproval } : {},
25950
+ // Session-scoped memory for `allow-always`: push into the SAME live array
25951
+ // the gate (and the sub-agent gates) re-scan per call. Never persisted.
25952
+ appendAllowRule: (rule) => rules.push(rule),
24863
25953
  readOnlyToolNames
24864
25954
  });
24865
25955
  }
25956
+ function resolveOpeningMode(inv, store) {
25957
+ const raw = inv.flags["permission-mode"];
25958
+ if (typeof raw === "string" && isPermissionMode(raw)) return raw;
25959
+ return resolveDefaultMode(store);
25960
+ }
24866
25961
  async function buildSessionConductor(ctx) {
24867
25962
  primeProviderEnv(ctx);
24868
25963
  const inv = ctx.invocation;
@@ -24873,12 +25968,35 @@ async function buildSessionConductor(ctx) {
24873
25968
  const memDir = memoryDirFor(ctx.workspace, cwd);
24874
25969
  const memoryStore = new DiskMemoryStore(memDir);
24875
25970
  const memoryDoc = loadMemoryDoc(memDir);
24876
- const getApiKey = buildKeyResolver(createAuthVault(ctx.workspace.authPath), inv.account);
25971
+ const vault = createAuthVault(ctx.workspace.authPath);
25972
+ const getApiKey = buildKeyResolver(ctx, vault, inv.account);
25973
+ const gatewayBaseUrls = await resolveServerGatewayUrls(ctx, vault);
25974
+ const settings = PreferenceStore.fromWorkspace(ctx.workspace, cwd);
25975
+ const permissionRules = gatherPermissionRules(settings);
25976
+ const permissionMode = resolveOpeningMode(inv, settings);
25977
+ let liveConductor;
25978
+ const subagentPolicy = {
25979
+ rules: permissionRules,
25980
+ mode: () => liveConductor?.permissionMode() ?? permissionMode
25981
+ };
24877
25982
  const surveyBriefing = composeBriefing({
24878
25983
  tools: provisionDeck("authoring", { cwd }).tools()
24879
25984
  });
24880
- const runner = createDelegateRunner({ modelId, getApiKey, system: surveyBriefing, cwd });
24881
- const workflowRunner = createWorkflowAgentRunner({ modelId, getApiKey, cwd });
25985
+ const runner = createDelegateRunner({
25986
+ modelId,
25987
+ getApiKey,
25988
+ system: surveyBriefing,
25989
+ cwd,
25990
+ getGatewayBaseUrls: () => resolveServerGatewayUrls(ctx, vault),
25991
+ permissionPolicy: subagentPolicy
25992
+ });
25993
+ const workflowRunner = createWorkflowAgentRunner({
25994
+ modelId,
25995
+ getApiKey,
25996
+ cwd,
25997
+ getGatewayBaseUrls: () => resolveServerGatewayUrls(ctx, vault),
25998
+ permissionPolicy: subagentPolicy
25999
+ });
24882
26000
  const addons = await buildAddonHost(ctx);
24883
26001
  const checkpoint = createCheckpointStore();
24884
26002
  const deck = [
@@ -24890,11 +26008,9 @@ async function buildSessionConductor(ctx) {
24890
26008
  const system = composeSystem(tools, inv, cwd, skills, memoryDoc);
24891
26009
  const thinking = resolveThinkingLevel(inv.thinking);
24892
26010
  const fallbackModelId = inv.fallbackModelId;
24893
- const settings = PreferenceStore.fromWorkspace(ctx.workspace, cwd);
24894
- const permissionMode = resolveDefaultMode(settings);
24895
26011
  const planReachable = inv.mode === "repl";
24896
- const canUseTool = buildPermissionGate(settings, tools, planReachable);
24897
- return createSessionConductor(
26012
+ const canUseTool = buildPermissionGate(permissionRules, permissionMode, tools, planReachable);
26013
+ const conductor = createSessionConductor(
24898
26014
  {
24899
26015
  modelId,
24900
26016
  tools,
@@ -24912,12 +26028,19 @@ async function buildSessionConductor(ctx) {
24912
26028
  ...canUseTool !== void 0 ? { canUseTool } : {},
24913
26029
  ...thinking !== void 0 ? { thinking } : {},
24914
26030
  ...workspace ? { workspace } : {},
24915
- ...fallbackModelId !== void 0 ? { fallbackModelId } : {}
26031
+ ...fallbackModelId !== void 0 ? { fallbackModelId } : {},
26032
+ // When server mode is active for the bound model's provider, hand the
26033
+ // conductor the gateway base url. A4 (`conductor.ts`) clones `card.model`
26034
+ // with this `baseUrl` so the existing model card routes through the indus
26035
+ // gateway; absent (the local-key path) the conductor binds the card as-is.
26036
+ ...Object.keys(gatewayBaseUrls).length > 0 ? { gatewayBaseUrls } : {}
24916
26037
  },
24917
26038
  // The live condense hook: `/compact` and auto-compaction fold older turns
24918
26039
  // into a digest. (Tests that build conductors directly keep the no-op default.)
24919
26040
  { condense: condenseTranscript }
24920
26041
  );
26042
+ liveConductor = conductor;
26043
+ return conductor;
24921
26044
  }
24922
26045
  function oneshotPrompts(ctx) {
24923
26046
  const prompts = [];
@@ -24929,9 +26052,10 @@ function oneshotPrompts(ctx) {
24929
26052
  }
24930
26053
 
24931
26054
  // src/boot/runners/repl-runner.ts
24932
- var EXIT_OK2 = 0;
26055
+ var EXIT_OK3 = 0;
24933
26056
  function buildOverlayServices(ctx, conductor) {
24934
26057
  const cwd = ctx.invocation.cwd ?? process.cwd();
26058
+ const vault = createAuthVault(ctx.workspace.authPath);
24935
26059
  return {
24936
26060
  conductor,
24937
26061
  settings: PreferenceStore.fromWorkspace(ctx.workspace, cwd),
@@ -24941,7 +26065,11 @@ function buildOverlayServices(ctx, conductor) {
24941
26065
  listLoginProviders,
24942
26066
  startOAuthLogin,
24943
26067
  openLoginUrl,
24944
- vault: createAuthVault(ctx.workspace.authPath)
26068
+ vault,
26069
+ async refreshGatewayRouting() {
26070
+ const map = await resolveServerGatewayUrls(ctx, vault);
26071
+ conductor.updateGatewayBaseUrls(map);
26072
+ }
24945
26073
  };
24946
26074
  }
24947
26075
  function toResumeRef(row) {
@@ -25018,7 +26146,7 @@ var replRunner = {
25018
26146
  conductor,
25019
26147
  initialInput === void 0 ? { services } : { initialInput, services }
25020
26148
  );
25021
- return EXIT_OK2;
26149
+ return EXIT_OK3;
25022
26150
  }
25023
26151
  };
25024
26152
 
@@ -25059,7 +26187,7 @@ var oneshotRunner = {
25059
26187
  };
25060
26188
 
25061
26189
  // src/boot/runners/link-runner.ts
25062
- var EXIT_OK3 = 0;
26190
+ var EXIT_OK4 = 0;
25063
26191
  var STDOUT2 = {
25064
26192
  write(chunk, cb) {
25065
26193
  return process.stdout.write(chunk, cb);
@@ -25077,7 +26205,7 @@ var linkRunner = {
25077
26205
  out: STDOUT2
25078
26206
  });
25079
26207
  await server.done;
25080
- return EXIT_OK3;
26208
+ return EXIT_OK4;
25081
26209
  }
25082
26210
  };
25083
26211
 
@@ -25093,13 +26221,13 @@ function selectRunner(inv) {
25093
26221
  }
25094
26222
 
25095
26223
  // src/boot/boot.ts
25096
- var EXIT_OK4 = 0;
25097
- var EXIT_FAILURE = 1;
25098
- function seedContext(argv) {
26224
+ var EXIT_OK5 = 0;
26225
+ var EXIT_FAILURE2 = 1;
26226
+ function seedContext(argv2) {
25099
26227
  const workspace = createWorkspace();
25100
- const invocation = tokenizeInvocation(argv);
26228
+ const invocation = tokenizeInvocation(argv2);
25101
26229
  return {
25102
- argv,
26230
+ argv: argv2,
25103
26231
  workspace,
25104
26232
  brand: BRAND,
25105
26233
  invocation,
@@ -25136,7 +26264,7 @@ async function runCredential(ctx) {
25136
26264
  `);
25137
26265
  return EXIT_CREDENTIAL_FAULT;
25138
26266
  }
25139
- return EXIT_OK4;
26267
+ return EXIT_OK5;
25140
26268
  }
25141
26269
  async function runPackage(ctx) {
25142
26270
  const result = await runPackageCommand(ctx.argv, {
@@ -25145,30 +26273,34 @@ async function runPackage(ctx) {
25145
26273
  if (!result.handled) {
25146
26274
  return void 0;
25147
26275
  }
25148
- return result.code ?? EXIT_OK4;
26276
+ return result.code ?? EXIT_OK5;
25149
26277
  }
25150
- async function boot(argv) {
25151
- const seeded = seedContext(argv);
26278
+ async function boot(argv2) {
26279
+ const seeded = seedContext(argv2);
25152
26280
  const credential = await runCredential(seeded);
25153
26281
  if (credential !== void 0) {
25154
26282
  return credential;
25155
26283
  }
26284
+ const login = await runLogin(seeded);
26285
+ if (login !== void 0) {
26286
+ return login;
26287
+ }
25156
26288
  const pkg = await runPackage(seeded);
25157
26289
  if (pkg !== void 0) {
25158
26290
  return pkg;
25159
26291
  }
25160
26292
  if (wantsHelp(seeded.invocation)) {
25161
26293
  renderHelp();
25162
- return EXIT_OK4;
26294
+ return EXIT_OK5;
25163
26295
  }
25164
26296
  if (wantsVersion(seeded.invocation)) {
25165
26297
  renderVersion();
25166
- return EXIT_OK4;
26298
+ return EXIT_OK5;
25167
26299
  }
25168
26300
  if (seeded.invocation.listModels) {
25169
26301
  const filter = seeded.invocation.listModelsFilter;
25170
26302
  printModelCatalog(void 0, filter !== void 0 ? { search: filter } : {});
25171
- return EXIT_OK4;
26303
+ return EXIT_OK5;
25172
26304
  }
25173
26305
  const ctx = await runStages(seeded);
25174
26306
  try {
@@ -25178,19 +26310,47 @@ async function boot(argv) {
25178
26310
  const message = error instanceof Error ? error.message : String(error);
25179
26311
  process.stderr.write(`${BRAND.name}: ${message}
25180
26312
  `);
25181
- return EXIT_FAILURE;
26313
+ return EXIT_FAILURE2;
25182
26314
  } finally {
25183
26315
  await drainClosables(ctx);
25184
26316
  }
25185
26317
  }
25186
26318
 
26319
+ // src/boot/heap.ts
26320
+ import { spawnSync as spawnSync4 } from "node:child_process";
26321
+ import { argv, env, execArgv, execPath, exit } from "node:process";
26322
+ function targetHeapMb() {
26323
+ const raw = parseInt(env.INDUS_MAX_HEAP_MB ?? "", 10);
26324
+ if (Number.isFinite(raw)) return Math.max(0, raw);
26325
+ return 4096;
26326
+ }
26327
+ function alreadyRaised() {
26328
+ return env.INDUS_HEAP_BOOSTED === "1" || (env.NODE_OPTIONS ?? "").includes("--max-old-space-size") || execArgv.some(
26329
+ (a) => a.startsWith("--max-old-space-size") || a.startsWith("--max_old_space_size")
26330
+ );
26331
+ }
26332
+ function ensureHeapHeadroom() {
26333
+ const mb = targetHeapMb();
26334
+ if (mb === 0 || alreadyRaised()) return;
26335
+ const entry = argv[1];
26336
+ if (!entry) return;
26337
+ const result = spawnSync4(
26338
+ execPath,
26339
+ [`--max-old-space-size=${mb}`, ...execArgv, entry, ...argv.slice(2)],
26340
+ { stdio: "inherit", env: { ...env, INDUS_HEAP_BOOSTED: "1" } }
26341
+ );
26342
+ if (result.error) return;
26343
+ if (typeof result.status === "number") exit(result.status);
26344
+ exit(result.signal ? 1 : 0);
26345
+ }
26346
+
25187
26347
  // src/entry.ts
25188
26348
  var MCP_NOISE_MARKER = "[MCP]";
25189
26349
  function installMcpNoiseFilter() {
25190
26350
  const self = installMcpNoiseFilter;
25191
26351
  if (self.installed) return;
25192
26352
  self.installed = true;
25193
- if (env[BRAND.envDebug]) return;
26353
+ if (env2[BRAND.envDebug]) return;
25194
26354
  const carriesMcpNoise = (args) => typeof args[0] === "string" && args[0].includes(MCP_NOISE_MARKER);
25195
26355
  const wrap = (original) => (...args) => {
25196
26356
  if (carriesMcpNoise(args)) return;
@@ -25211,6 +26371,7 @@ function isProgramEntry() {
25211
26371
  process.title = BRAND.binNames[0];
25212
26372
  installMcpNoiseFilter();
25213
26373
  if (isProgramEntry()) {
26374
+ ensureHeapHeadroom();
25214
26375
  void boot(processArgv.slice(2)).then((code) => {
25215
26376
  process.exitCode = code;
25216
26377
  process.stdout.write("", () => process.exit(code));