zelari-code 1.8.0 → 1.8.3

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.
@@ -16880,6 +16880,25 @@ var init_walk = __esm({
16880
16880
  // packages/core/dist/core/tools/builtin/search.js
16881
16881
  import { promises as fs6 } from "node:fs";
16882
16882
  import path7 from "node:path";
16883
+ function coerceStringList(value, fallback) {
16884
+ if (value === void 0 || value === null)
16885
+ return fallback;
16886
+ if (Array.isArray(value)) {
16887
+ const cleaned = value.filter((x) => typeof x === "string" && x.trim().length > 0);
16888
+ return cleaned.length > 0 ? cleaned : fallback;
16889
+ }
16890
+ if (typeof value === "string") {
16891
+ const s = value.trim();
16892
+ if (!s)
16893
+ return fallback;
16894
+ if (s.includes(",") && !s.includes("{")) {
16895
+ const parts = s.split(",").map((x) => x.trim()).filter(Boolean);
16896
+ return parts.length > 0 ? parts : fallback;
16897
+ }
16898
+ return [s];
16899
+ }
16900
+ return fallback;
16901
+ }
16883
16902
  async function searchFile(absPath, relPath, regex, contextLines, remainingSlots) {
16884
16903
  let buf;
16885
16904
  try {
@@ -16923,13 +16942,14 @@ async function isDirectory(p3) {
16923
16942
  return false;
16924
16943
  }
16925
16944
  }
16926
- var GrepContentArgsSchema, grepContentTool;
16945
+ var stringOrStringArray, GrepContentArgsSchema, grepContentTool;
16927
16946
  var init_search = __esm({
16928
16947
  "packages/core/dist/core/tools/builtin/search.js"() {
16929
16948
  "use strict";
16930
16949
  init_zod();
16931
16950
  init_toolTypes();
16932
16951
  init_walk();
16952
+ stringOrStringArray = external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]);
16933
16953
  GrepContentArgsSchema = external_exports.object({
16934
16954
  /** File OR directory to search (relative to cwd or absolute). */
16935
16955
  path: external_exports.string().min(1),
@@ -16940,21 +16960,22 @@ var init_search = __esm({
16940
16960
  /** Max matches returned (total matches still counted). */
16941
16961
  maxMatches: external_exports.number().int().positive().max(1e3).default(50),
16942
16962
  /**
16943
- * Glob patterns to INCLUDE when path is a directory (e.g. ['*.ts', '*.tsx']).
16963
+ * Glob pattern(s) to INCLUDE when path is a directory.
16964
+ * Accepts a string OR string[] (models often emit a bare string).
16944
16965
  * Default ['*'] = all files. Ignored when path is a file.
16945
16966
  */
16946
- include: external_exports.array(external_exports.string()).default(["*"]),
16967
+ include: stringOrStringArray.optional().default(["*"]),
16947
16968
  /**
16948
- * Glob patterns to EXCLUDE when path is a directory (e.g. ['*.test.ts']).
16949
- * Defaults to common noise dirs. Ignored when path is a file.
16969
+ * Glob pattern(s) to EXCLUDE when path is a directory.
16970
+ * Accepts a string OR string[]. Defaults to common noise dirs.
16950
16971
  */
16951
- exclude: external_exports.array(external_exports.string()).default(DEFAULT_EXCLUDES),
16972
+ exclude: stringOrStringArray.optional().default(DEFAULT_EXCLUDES),
16952
16973
  /** Max recursion depth when path is a directory (default 8). */
16953
16974
  maxDepth: external_exports.number().int().positive().max(15).default(8)
16954
16975
  });
16955
16976
  grepContentTool = {
16956
16977
  name: "grep_content",
16957
- description: "Regex search for content in a file OR recursively in a directory. When path is a directory, include/exclude globs filter which files are searched (default: all files, excluding node_modules/dist/.git/etc.). Returns matches with line numbers and surrounding context. Single-file mode preserves v0.3.x behavior.",
16978
+ description: 'Regex search for content in a file OR recursively in a directory. When path is a directory, include/exclude globs filter which files are searched (default: all files, excluding node_modules/dist/.git/etc.). include/exclude accept a single glob string (e.g. "*.ts") OR an array of globs. Returns matches with line numbers and surrounding context.',
16958
16979
  permissions: ["read"],
16959
16980
  timeoutMs: 3e4,
16960
16981
  inputSchema: GrepContentArgsSchema,
@@ -16962,6 +16983,8 @@ var init_search = __esm({
16962
16983
  try {
16963
16984
  const absRoot = path7.isAbsolute(args.path) ? args.path : path7.join(ctx.cwd, args.path);
16964
16985
  const regex = new RegExp(args.pattern, "gm");
16986
+ const include = coerceStringList(args.include, ["*"]);
16987
+ const exclude = coerceStringList(args.exclude, DEFAULT_EXCLUDES);
16965
16988
  if (!await isDirectory(absRoot)) {
16966
16989
  const single = await searchFile(absRoot, args.path, regex, args.contextLines, args.maxMatches);
16967
16990
  return typedOk({
@@ -16973,8 +16996,8 @@ var init_search = __esm({
16973
16996
  });
16974
16997
  }
16975
16998
  const allEntries = [];
16976
- await walk(absRoot, "", 0, args.maxDepth, args.exclude, allEntries, ctx.signal);
16977
- const matchedFiles = filterByInclude(allEntries, args.include);
16999
+ await walk(absRoot, "", 0, args.maxDepth, exclude, allEntries, ctx.signal);
17000
+ const matchedFiles = filterByInclude(allEntries, include);
16978
17001
  const allMatches = [];
16979
17002
  let totalMatches = 0;
16980
17003
  let truncated = false;
@@ -18271,20 +18294,24 @@ The council shares a context window across turns. Follow these rules:
18271
18294
  type: "tool-usage-guidelines",
18272
18295
  title: "Tool Usage",
18273
18296
  priority: 60,
18274
- content: `# Tool Usage Guidelines
18275
-
18276
- - Use tools to create or modify durable state (tasks, ideas, documents, mind maps, milestones).
18277
- - Tool calls go in a dedicated block at the end of your response using the exact format documented below.
18278
- - Only use tools listed in the AVAILABLE TOOLS section. Never invent tool names.
18279
- - Pass arguments as JSON. Required parameters must be present.
18280
- - One tool call per entry. Multiple entries are allowed in a single block.
18281
-
18282
- Format:
18283
- \`\`\`
18284
- ---TOOLS---
18285
- [{"name":"<toolName>","args":{...}}]
18286
- ---END---
18287
- \`\`\``
18297
+ content: [
18298
+ "# Tool Usage Guidelines",
18299
+ "",
18300
+ "- Use tools to create or modify durable state (tasks, ideas, documents, mind maps, milestones).",
18301
+ "- Tool calls go in a dedicated block at the end of your response using the exact format documented below.",
18302
+ "- Only use tools listed in the AVAILABLE TOOLS section. Never invent tool names.",
18303
+ "- Pass arguments as JSON. Required parameters must be present.",
18304
+ "- One tool call per entry. Multiple entries are allowed in a single block.",
18305
+ "",
18306
+ "Format (ONE JSON array only - never multiple arrays stacked):",
18307
+ "```",
18308
+ "---TOOLS---",
18309
+ '[{"name":"<toolName>","args":{"key":"value"}},{"name":"<toolName2>","args":{"key":"value"}}]',
18310
+ "---END---",
18311
+ "```",
18312
+ "Rules: valid JSON; escape newlines inside strings as \\n; do NOT stack separate",
18313
+ "JSON arrays (one tool per array) - put every call in the SAME outer array."
18314
+ ].join("\n")
18288
18315
  },
18289
18316
  {
18290
18317
  type: "custom",
@@ -18944,18 +18971,131 @@ function normalizeTextToolArgs(name, args) {
18944
18971
  }
18945
18972
  function parseTextToolCalls(text) {
18946
18973
  const m = /---TOOLS---\s*([\s\S]*?)---END---/.exec(text);
18947
- if (!m || !m[1])
18948
- return [];
18974
+ if (m?.[1]) {
18975
+ let body = m[1].trim();
18976
+ body = body.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
18977
+ const candidates = [body];
18978
+ if (/\]\s*\[/.test(body)) {
18979
+ candidates.push(body.replace(/\]\s*\[/g, ","));
18980
+ }
18981
+ if (body.includes('\\"')) {
18982
+ candidates.push(body.replace(/\\"/g, '"'));
18983
+ if (/\]\s*\[/.test(body)) {
18984
+ candidates.push(body.replace(/\\"/g, '"').replace(/\]\s*\[/g, ","));
18985
+ }
18986
+ }
18987
+ for (const cand of candidates) {
18988
+ const items = tryParseToolArray(cand);
18989
+ if (items.length > 0)
18990
+ return items;
18991
+ }
18992
+ const arrays = extractJsonArrays(body);
18993
+ if (arrays.length > 1) {
18994
+ const merged = [];
18995
+ for (const a of arrays) {
18996
+ merged.push(...tryParseToolArray(a));
18997
+ }
18998
+ if (merged.length > 0)
18999
+ return merged;
19000
+ }
19001
+ const objs = extractToolObjects(body);
19002
+ if (objs.length > 0)
19003
+ return objs;
19004
+ }
19005
+ const mini = parseMinimaxStyleToolCalls(text);
19006
+ if (mini.length > 0)
19007
+ return mini;
19008
+ return [];
19009
+ }
19010
+ function parseMinimaxStyleToolCalls(text) {
19011
+ const out = [];
19012
+ const normalized = text.replace(/\]\s*<\s*\]\s*minimax\s*\[\s*>\s*\[</gi, "<minimax:").replace(/\]\s*<\s*\]\s*minimax\s*\[\s*>/gi, "<minimax:").replace(/minimax\s*\[\s*>\s*\[</gi, "minimax:").replace(/\]\s*</g, "<");
19013
+ const blockRe = /<(?:minimax:)?tool_call[^>]*>([\s\S]*?)<\/(?:minimax:)?tool_call>|<invoke\s+name=["']([^"']+)["'][^>]*>([\s\S]*?)(?:<\/invoke>|(?=<invoke\s+name=)|$)/gi;
19014
+ let match;
19015
+ while ((match = blockRe.exec(normalized)) !== null) {
19016
+ if (match[2]) {
19017
+ out.push({ name: match[2], args: parseLooseArgs(match[3] ?? "") });
19018
+ } else if (match[1]) {
19019
+ const inner = match[1];
19020
+ const inv = /invoke\s+name=["']([^"']+)["']/i.exec(inner);
19021
+ if (inv) {
19022
+ out.push({ name: inv[1], args: parseLooseArgs(inner) });
19023
+ } else {
19024
+ const jsonish = tryParseToolArray(inner.trim());
19025
+ if (jsonish.length)
19026
+ out.push(...jsonish);
19027
+ else {
19028
+ const objs = extractToolObjects(inner);
19029
+ if (objs.length)
19030
+ out.push(...objs);
19031
+ }
19032
+ }
19033
+ }
19034
+ }
19035
+ if (out.length === 0) {
19036
+ const loose = /invoke\s+name=["']([^"']+)["']([\s\S]*?)(?=invoke\s+name=|$)/gi;
19037
+ let lm;
19038
+ while ((lm = loose.exec(normalized)) !== null) {
19039
+ const name = lm[1];
19040
+ const args = parseLooseArgs(lm[2] ?? "");
19041
+ if (name)
19042
+ out.push({ name, args });
19043
+ }
19044
+ }
19045
+ return out;
19046
+ }
19047
+ function parseLooseArgs(body) {
19048
+ const args = {};
19049
+ const paramTag = /(?:parameter\s+name=|<\s*)["']?([a-zA-Z_][\w]*)["']?\s*(?:>|=\s*["']?)([^<\]\n]+)/gi;
19050
+ let m;
19051
+ while ((m = paramTag.exec(body)) !== null) {
19052
+ const key = m[1];
19053
+ if (key === "invoke" || key === "minimax" || key === "tool_call")
19054
+ continue;
19055
+ args[key] = m[2].replace(/^["']|["']$/g, "").trim();
19056
+ }
19057
+ const lineRe = /(?:^|[\s\[>])([a-zA-Z_][\w]*)\s*[>:=]\s*([^\n<\]]+)/g;
19058
+ while ((m = lineRe.exec(body)) !== null) {
19059
+ const key = m[1];
19060
+ if (args[key] !== void 0)
19061
+ continue;
19062
+ if (["invoke", "name", "minimax", "tool_call", "parameter"].includes(key))
19063
+ continue;
19064
+ args[key] = m[2].replace(/^["']|["']$/g, "").trim();
19065
+ }
19066
+ if (Object.keys(args).length === 0) {
19067
+ const brace = body.indexOf("{");
19068
+ if (brace >= 0) {
19069
+ try {
19070
+ const parsed = JSON.parse(body.slice(brace));
19071
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19072
+ return parsed;
19073
+ }
19074
+ } catch {
19075
+ }
19076
+ }
19077
+ }
19078
+ return args;
19079
+ }
19080
+ function tryParseToolArray(raw) {
18949
19081
  let parsed;
18950
19082
  try {
18951
- parsed = JSON.parse(m[1].trim());
19083
+ parsed = JSON.parse(raw.trim());
18952
19084
  } catch {
18953
19085
  return [];
18954
19086
  }
18955
- if (!Array.isArray(parsed))
18956
- return [];
19087
+ if (!Array.isArray(parsed)) {
19088
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19089
+ parsed = [parsed];
19090
+ } else {
19091
+ return [];
19092
+ }
19093
+ }
19094
+ return normalizeToolItems(parsed);
19095
+ }
19096
+ function normalizeToolItems(items) {
18957
19097
  const out = [];
18958
- for (const item of parsed) {
19098
+ for (const item of items) {
18959
19099
  if (item && typeof item === "object" && typeof item.name === "string") {
18960
19100
  const rawArgs = item.args;
18961
19101
  out.push({
@@ -18966,6 +19106,70 @@ function parseTextToolCalls(text) {
18966
19106
  }
18967
19107
  return out;
18968
19108
  }
19109
+ function extractJsonArrays(text) {
19110
+ const out = [];
19111
+ let i = 0;
19112
+ while (i < text.length) {
19113
+ if (text[i] !== "[") {
19114
+ i++;
19115
+ continue;
19116
+ }
19117
+ let depth = 0;
19118
+ let inStr = false;
19119
+ let esc2 = false;
19120
+ let end = -1;
19121
+ for (let j = i; j < text.length; j++) {
19122
+ const c = text[j];
19123
+ if (inStr) {
19124
+ if (esc2)
19125
+ esc2 = false;
19126
+ else if (c === "\\")
19127
+ esc2 = true;
19128
+ else if (c === '"')
19129
+ inStr = false;
19130
+ continue;
19131
+ }
19132
+ if (c === '"') {
19133
+ inStr = true;
19134
+ continue;
19135
+ }
19136
+ if (c === "[")
19137
+ depth++;
19138
+ else if (c === "]") {
19139
+ depth--;
19140
+ if (depth === 0) {
19141
+ end = j;
19142
+ break;
19143
+ }
19144
+ }
19145
+ }
19146
+ if (end > i) {
19147
+ out.push(text.slice(i, end + 1));
19148
+ i = end + 1;
19149
+ } else {
19150
+ i++;
19151
+ }
19152
+ }
19153
+ return out;
19154
+ }
19155
+ function extractToolObjects(text) {
19156
+ const out = [];
19157
+ const re = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})\s*\}/g;
19158
+ let match;
19159
+ while ((match = re.exec(text)) !== null) {
19160
+ const name = match[1];
19161
+ let args = {};
19162
+ try {
19163
+ const parsed = JSON.parse(match[2]);
19164
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19165
+ args = parsed;
19166
+ }
19167
+ } catch {
19168
+ }
19169
+ out.push({ name, args });
19170
+ }
19171
+ return out;
19172
+ }
18969
19173
  var AgentHarness;
18970
19174
  var init_AgentHarness = __esm({
18971
19175
  "packages/core/dist/core/AgentHarness.js"() {
@@ -18977,6 +19181,7 @@ var init_AgentHarness = __esm({
18977
19181
  sessionId;
18978
19182
  maxQueuedIterations;
18979
19183
  maxToolLoopIterations;
19184
+ maxToolLoopHardCap;
18980
19185
  cancelled = false;
18981
19186
  activeController = null;
18982
19187
  queue = [];
@@ -19002,6 +19207,9 @@ var init_AgentHarness = __esm({
19002
19207
  this.sessionId = config2.sessionId ?? crypto.randomUUID();
19003
19208
  this.maxQueuedIterations = config2.maxQueuedIterations ?? 3;
19004
19209
  this.maxToolLoopIterations = config2.maxToolLoopIterations ?? 30;
19210
+ const soft = this.maxToolLoopIterations;
19211
+ const hardCfg = config2.maxToolLoopHardCap;
19212
+ this.maxToolLoopHardCap = typeof hardCfg === "number" && hardCfg > 0 ? Math.max(soft, hardCfg) : Math.max(soft * 3, soft + 60);
19005
19213
  }
19006
19214
  /**
19007
19215
  * Snapshot of the live transcript (`this.config.messages`) as mutated
@@ -19278,7 +19486,11 @@ ${shared2.content}`,
19278
19486
  this.emit(initialMsgEnd);
19279
19487
  yield initialMsgEnd;
19280
19488
  let toolLoopTurns = 0;
19281
- while (!this.cancelled && !hadError && toolLoopTurns < this.maxToolLoopIterations && initialFinishRef.value === "tool_calls") {
19489
+ let softCap = this.maxToolLoopIterations;
19490
+ const hardCap = this.maxToolLoopHardCap;
19491
+ let extensions = 0;
19492
+ const maxExtensions = 8;
19493
+ while (!this.cancelled && !hadError && toolLoopTurns < softCap && initialFinishRef.value === "tool_calls") {
19282
19494
  toolLoopTurns++;
19283
19495
  const turnMessageId = crypto.randomUUID();
19284
19496
  const msgStart = createBrainEvent("message_start", this.sessionId, { messageId: turnMessageId, role: "assistant", ...this.memberFields() });
@@ -19309,9 +19521,25 @@ ${shared2.content}`,
19309
19521
  initialFinishRef.value = turnFinishRef.value;
19310
19522
  if (hadError || this.cancelled)
19311
19523
  break;
19524
+ if (initialFinishRef.value === "tool_calls" && toolLoopTurns >= softCap && toolLoopTurns < hardCap && extensions < maxExtensions) {
19525
+ extensions++;
19526
+ const chunk = this.maxToolLoopIterations;
19527
+ softCap = Math.min(hardCap, softCap + chunk);
19528
+ this.config.messages.push({
19529
+ role: "user",
19530
+ content: `[system] Soft tool budget reached (${toolLoopTurns}/${hardCap} hard). Work may be incomplete \u2014 CONTINUE with tools as needed to finish remaining steps. Prefer concrete progress over summarizing. Do not apologize for budgets.`
19531
+ });
19532
+ const note = createBrainEvent("error", this.sessionId, {
19533
+ severity: "recoverable",
19534
+ message: `Tool budget extended (${toolLoopTurns}\u2192${softCap}, hard ${hardCap}) to allow completion.`,
19535
+ code: "tool_budget_extended"
19536
+ });
19537
+ this.emit(note);
19538
+ yield note;
19539
+ }
19312
19540
  }
19313
- const hitIterationCap = toolLoopTurns >= this.maxToolLoopIterations;
19314
- if (!this.cancelled && !hadError && initialFinishRef.value === "tool_calls" && hitIterationCap) {
19541
+ const hitHardCap = toolLoopTurns >= hardCap || toolLoopTurns >= softCap && softCap >= hardCap;
19542
+ if (!this.cancelled && !hadError && initialFinishRef.value === "tool_calls" && hitHardCap) {
19315
19543
  yield* this.runFinalAnswerTurn();
19316
19544
  }
19317
19545
  let turns = 0;
@@ -19475,13 +19703,14 @@ ${cached2}`
19475
19703
  pendingNativeTools.length = 0;
19476
19704
  }
19477
19705
  const textTools = parseTextToolCalls(turnText);
19478
- const nativeWriteRan = turnToolCalls.some((t) => t.name === "write_file" || t.name === "edit_file");
19479
- const textWriteTools = textTools.filter((t) => t.name === "write_file" || t.name === "edit_file");
19480
- const toolsToRun = nativeWriteRan ? [] : turnToolCalls.length === 0 ? textTools : textWriteTools;
19481
- if (/---TOOLS---/.test(turnText) && textTools.length === 0) {
19706
+ const toolsToRun = textTools.filter((tt) => {
19707
+ const key = hashToolCall(tt.name, tt.args);
19708
+ return !turnToolCalls.some((n) => hashToolCall(n.name, n.args) === key);
19709
+ });
19710
+ if ((/---TOOLS---/.test(turnText) || /minimax|invoke\s+name=/i.test(turnText)) && textTools.length === 0) {
19482
19711
  const parseErr = createBrainEvent("error", this.sessionId, {
19483
19712
  severity: "recoverable",
19484
- message: "Found ---TOOLS--- block but JSON parse failed; tool calls were not executed. Emit valid JSON (escape newlines as \\n inside strings).",
19713
+ message: "Found text-format tool block but parse failed; tool calls were not executed. Prefer native tool_call, or ---TOOLS--- with ONE valid JSON array.",
19485
19714
  code: "text_tools_parse_failed"
19486
19715
  });
19487
19716
  this.emit(parseErr);
@@ -19613,7 +19842,7 @@ ${cached2}`
19613
19842
  yield msgStart;
19614
19843
  this.config.messages.push({
19615
19844
  role: "user",
19616
- content: "[system] Tool iteration budget exhausted. Stop calling tools and answer the original request now using the information you have already gathered. Do not apologize for the tools."
19845
+ content: "[system] Hard tool-iteration ceiling reached. Stop calling tools and give a clear status: what is DONE, what remains, and the exact next steps. Use what you already gathered. Do not apologize for the tools."
19617
19846
  });
19618
19847
  let totalLength = 0;
19619
19848
  const finishRef = { value: "stop" };
@@ -19812,6 +20041,7 @@ __export(harness_exports, {
19812
20041
  SessionJsonlWriter: () => SessionJsonlWriter,
19813
20042
  hashToolCall: () => hashToolCall,
19814
20043
  normalizeTextToolArgs: () => normalizeTextToolArgs,
20044
+ parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
19815
20045
  parseTextToolCalls: () => parseTextToolCalls,
19816
20046
  readSession: () => readSession,
19817
20047
  wrapLegacyStream: () => wrapLegacyStream
@@ -23934,11 +24164,19 @@ function parseClarificationRequest(text) {
23934
24164
  }
23935
24165
  }
23936
24166
  function parseThinking(text) {
23937
- const match = text.match(/<think>([\s\S]*?)<\/think>/);
23938
- return match ? match[1].trim() : "";
24167
+ const complete = text.match(/<think(?:ing)?>([\s\S]*?)<\/think(?:ing)?>/i);
24168
+ if (complete)
24169
+ return complete[1].trim();
24170
+ const open = text.match(/<think(?:ing)?>([\s\S]*)$/i);
24171
+ return open ? open[1].trim() : "";
23939
24172
  }
23940
- function cleanAgentContent(text) {
23941
- return text.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/<minimax:tool_call>[\s\S]*?<\/minimax:tool_call>/g, "").replace(/---QUESTION---[\s\S]*?---END---/g, "").trim();
24173
+ function cleanAgentContent(text, opts = {}) {
24174
+ const stripQuestion = opts.stripQuestion !== false;
24175
+ let out = text.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, "").replace(/<think(?:ing)?>[\s\S]*$/gi, "").replace(/<\/think(?:ing)?>/gi, "").replace(/<minimax:tool_call>[\s\S]*?<\/minimax:tool_call>/g, "");
24176
+ if (stripQuestion) {
24177
+ out = out.replace(/---QUESTION---[\s\S]*?---END---/g, "");
24178
+ }
24179
+ return out.replace(/\n{3,}/g, "\n\n").trim();
23942
24180
  }
23943
24181
  function restrictImplementationWrites(toolNames, opts) {
23944
24182
  if (opts.runMode !== "implementation" || opts.isImplementer)
@@ -30520,7 +30758,7 @@ import React14 from "react";
30520
30758
  import { render } from "ink";
30521
30759
 
30522
30760
  // src/cli/app.tsx
30523
- import React9, { useState as useState8, useMemo, useCallback as useCallback5, useEffect as useEffect7 } from "react";
30761
+ import React9, { useState as useState8, useMemo, useCallback as useCallback5, useEffect as useEffect7, useRef as useRef6 } from "react";
30524
30762
  import { Box as Box8, Static, useInput as useInput2, useStdin as useStdin2 } from "ink";
30525
30763
 
30526
30764
  // src/cli/components/InputBar.tsx
@@ -30918,9 +31156,12 @@ function WorkingIndicator({
30918
31156
 
30919
31157
  // src/cli/components/LiveRegion.tsx
30920
31158
  var LIVE_STREAM_TAIL_LINES = 10;
31159
+ var MAX_LIVE_TOOLS = 4;
30921
31160
  function LiveRegion({ live, busy, elapsedMs = null }) {
30922
31161
  const { streaming, runningTools } = live;
30923
31162
  if (!streaming && runningTools.length === 0 && !busy) return null;
31163
+ const visibleTools = runningTools.slice(0, MAX_LIVE_TOOLS);
31164
+ const hiddenTools = runningTools.length - visibleTools.length;
30924
31165
  return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", paddingX: 1 }, streaming && streaming.role === "assistant" && /* @__PURE__ */ React5.createElement(
30925
31166
  StreamingTail,
30926
31167
  {
@@ -30930,7 +31171,7 @@ function LiveRegion({ live, busy, elapsedMs = null }) {
30930
31171
  memberName: streaming.memberName,
30931
31172
  memberId: streaming.memberId
30932
31173
  }
30933
- ), runningTools.map((t) => /* @__PURE__ */ React5.createElement(Box4, { key: t.id, flexDirection: "column" }, renderMessage(t, true))), busy && runningTools.length === 0 && !streaming && /* @__PURE__ */ React5.createElement(WorkingIndicator, { elapsedMs }));
31174
+ ), visibleTools.map((t) => /* @__PURE__ */ React5.createElement(Box4, { key: t.id, flexDirection: "column" }, renderMessage(t, true))), hiddenTools > 0 ? /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \u2026 +", hiddenTools, " more tools") : null, busy && runningTools.length === 0 && !streaming && /* @__PURE__ */ React5.createElement(WorkingIndicator, { elapsedMs }));
30934
31175
  }
30935
31176
  function StreamingTail(m) {
30936
31177
  const lines = m.content.split("\n");
@@ -31093,11 +31334,25 @@ import React8 from "react";
31093
31334
  import { Box as Box7, Text as Text8 } from "ink";
31094
31335
  var SIDEBAR_WIDTH = 28;
31095
31336
  var SIDEBAR_MIN_COLUMNS = 96;
31096
- var EMBLEM_MIN_ROWS = 26;
31097
- var MAX_FILES_SHORT = 6;
31098
- var MAX_FILES_TALL = 10;
31337
+ var SIDEBAR_HIDE_COLUMNS = 88;
31338
+ var SIDEBAR_MIN_ROWS = 16;
31339
+ var SIDEBAR_HIDE_ROWS = 14;
31340
+ var MAX_FILES_SHORT = 4;
31341
+ var MAX_FILES_TALL = 8;
31342
+ var SIDEBAR_CHROME_LINES = 4;
31099
31343
  function shouldShowSidebar(columns, rows) {
31100
- return columns >= SIDEBAR_MIN_COLUMNS && rows >= 16;
31344
+ return columns >= SIDEBAR_MIN_COLUMNS && rows >= SIDEBAR_MIN_ROWS;
31345
+ }
31346
+ function sidebarVisibility(columns, rows, currentlyVisible) {
31347
+ if (currentlyVisible) {
31348
+ return columns >= SIDEBAR_HIDE_COLUMNS && rows >= SIDEBAR_HIDE_ROWS;
31349
+ }
31350
+ return shouldShowSidebar(columns, rows);
31351
+ }
31352
+ function maxSidebarFiles(rows) {
31353
+ const budget = Math.max(2, rows - 14 - SIDEBAR_CHROME_LINES);
31354
+ const cap = rows >= 28 ? MAX_FILES_TALL : MAX_FILES_SHORT;
31355
+ return Math.min(cap, budget);
31101
31356
  }
31102
31357
  function truncatePath(p3, max) {
31103
31358
  if (p3.length <= max) return p3;
@@ -31105,7 +31360,7 @@ function truncatePath(p3, max) {
31105
31360
  }
31106
31361
  function Sidebar({ version: version2, changes, rows }) {
31107
31362
  void version2;
31108
- const maxFiles = rows >= EMBLEM_MIN_ROWS + 8 ? MAX_FILES_TALL : MAX_FILES_SHORT;
31363
+ const maxFiles = maxSidebarFiles(rows);
31109
31364
  const visible = changes.files.slice(0, maxFiles);
31110
31365
  const hidden = changes.files.length - visible.length;
31111
31366
  const innerWidth = SIDEBAR_WIDTH - 4;
@@ -31117,7 +31372,9 @@ function Sidebar({ version: version2, changes, rows }) {
31117
31372
  borderStyle: "single",
31118
31373
  borderColor: "gray",
31119
31374
  paddingX: 1,
31120
- flexShrink: 0
31375
+ flexShrink: 0,
31376
+ height: Math.min(rows - 8, SIDEBAR_CHROME_LINES + maxFiles + 2),
31377
+ overflow: "hidden"
31121
31378
  },
31122
31379
  /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, changes.branch ? truncatePath(changes.branch, 18) : "git")),
31123
31380
  /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "\u2500".repeat(innerWidth)),
@@ -31129,6 +31386,53 @@ function FileRow({ file: file2, pathWidth }) {
31129
31386
  return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text8, { wrap: "truncate" }, /* @__PURE__ */ React8.createElement(Text8, { color: file2.untracked ? "yellow" : "white" }, name), file2.untracked ? /* @__PURE__ */ React8.createElement(Text8, { color: "yellow" }, " new") : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, { color: "green" }, " +", file2.added ?? "\xB7"), /* @__PURE__ */ React8.createElement(Text8, { color: "red" }, " -", file2.removed ?? "\xB7"))));
31130
31387
  }
31131
31388
 
31389
+ // src/cli/components/brandArt.ts
31390
+ var BRAND_LOGO_ASCII = [
31391
+ " -#%=",
31392
+ " +%%%%#.",
31393
+ " :#%%%%%%%-",
31394
+ ":%%@@@@@@@@=",
31395
+ "-%@@@@@@@@@@@+",
31396
+ ":%@@@@@@%@@@@@@=",
31397
+ "-%%@@@@@.+%@@%@+",
31398
+ " -@@@@@@:%++%@=",
31399
+ "=*%@@@@@@:@@%=*@#+.",
31400
+ "%%%%%@@@@:+++-*%@@*",
31401
+ ".%%%%%@@@@@+%+=:%@@@@:",
31402
+ "*@@@@@@@@@@@@@%@@@@@@*"
31403
+ ].join("\n");
31404
+ var BRAND_LOGO_COMPACT = [
31405
+ " -#%=",
31406
+ " +%%%%#.",
31407
+ ":%%@@@@=",
31408
+ "*@@@@@@*"
31409
+ ].join("\n");
31410
+ function formatBannerWithLogoRight(opts) {
31411
+ const logoSrc = opts.compact ? BRAND_LOGO_COMPACT : BRAND_LOGO_ASCII;
31412
+ const logoLines = logoSrc.split("\n");
31413
+ const logoWidth = Math.max(...logoLines.map((l) => l.length));
31414
+ const cols = Math.max(logoWidth + 20, Math.min(opts.columns, 120));
31415
+ const left = [...opts.leftLines];
31416
+ const wordmark = `ZELARI CODE v${opts.version}`;
31417
+ const rightExtra = [wordmark];
31418
+ const rightBlock = [...logoLines, ...rightExtra];
31419
+ const rows = Math.max(left.length, rightBlock.length);
31420
+ const out = [];
31421
+ for (let i = 0; i < rows; i++) {
31422
+ const L = left[i] ?? "";
31423
+ const R = rightBlock[i] ?? "";
31424
+ if (!R) {
31425
+ out.push(L);
31426
+ continue;
31427
+ }
31428
+ const maxLeft = Math.max(0, cols - R.length - 2);
31429
+ const leftClipped = L.length > maxLeft ? L.slice(0, Math.max(0, maxLeft - 1)) + "\u2026" : L;
31430
+ const pad = Math.max(2, cols - leftClipped.length - R.length);
31431
+ out.push(leftClipped + " ".repeat(pad) + R);
31432
+ }
31433
+ return out.join("\n");
31434
+ }
31435
+
31132
31436
  // src/cli/modelDiscovery.ts
31133
31437
  import { promises as fs3, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
31134
31438
  import { homedir } from "node:os";
@@ -34113,10 +34417,12 @@ function computeSessionStatsDelta(realUsage, userText, assistantContent, model,
34113
34417
  const completionTokens = realUsage ? realUsage.completionTokens : Math.ceil(assistantContent.length / 4);
34114
34418
  const cachedPromptTokens = realUsage?.cachedPromptTokens ?? 0;
34115
34419
  const turnCost = calculateCost(model, promptTokens, completionTokens, cachedPromptTokens);
34420
+ const contextTokens = realUsage ? realUsage.totalTokens || promptTokens + completionTokens : promptTokens + completionTokens;
34116
34421
  return {
34117
34422
  totalTokens: prev2.totalTokens + promptTokens + completionTokens,
34118
34423
  totalCostUsd: prev2.totalCostUsd + turnCost,
34119
- cachedTokens: (prev2.cachedTokens ?? 0) + cachedPromptTokens
34424
+ cachedTokens: (prev2.cachedTokens ?? 0) + cachedPromptTokens,
34425
+ contextTokens
34120
34426
  };
34121
34427
  }
34122
34428
 
@@ -34402,6 +34708,11 @@ function useChatTurn(params) {
34402
34708
  min: 1
34403
34709
  });
34404
34710
  const maxToolLoopIterations = budget.maxToolLoopIterations;
34711
+ const maxToolLoopHardCap = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_HARD, {
34712
+ default: 0,
34713
+ // 0 → harness default (soft×3, min soft+60)
34714
+ min: 0
34715
+ });
34405
34716
  const harness2 = new AgentHarness({
34406
34717
  model: envConfig.model,
34407
34718
  provider: "openai-compatible",
@@ -34422,7 +34733,8 @@ function useChatTurn(params) {
34422
34733
  providerStream,
34423
34734
  cwd,
34424
34735
  maxToolCallsPerTurn,
34425
- maxToolLoopIterations
34736
+ maxToolLoopIterations,
34737
+ ...maxToolLoopHardCap > 0 ? { maxToolLoopHardCap } : {}
34426
34738
  });
34427
34739
  harnessRef.current = harness2;
34428
34740
  setQueueCount(harness2.queueLength);
@@ -34478,15 +34790,16 @@ function useChatTurn(params) {
34478
34790
  if (event.type === "message_delta") {
34479
34791
  assistantContent += event.delta;
34480
34792
  streamContent += event.delta;
34793
+ const displayContent = cleanAgentContent(streamContent);
34481
34794
  if (useLiveModel) {
34482
- setStreaming(commitStreaming, streamContent, Date.now(), {
34795
+ setStreaming(commitStreaming, displayContent, Date.now(), {
34483
34796
  ...event.memberId ? { memberId: event.memberId } : {},
34484
34797
  ...event.memberName ? { memberName: event.memberName } : {}
34485
34798
  });
34486
34799
  } else {
34487
34800
  appendOrExtendStreamingAssistant(
34488
34801
  commitStreaming,
34489
- streamContent,
34802
+ displayContent,
34490
34803
  Date.now(),
34491
34804
  {
34492
34805
  ...event.memberId ? { memberId: event.memberId } : {},
@@ -34496,7 +34809,15 @@ function useChatTurn(params) {
34496
34809
  }
34497
34810
  } else if (event.type === "error") {
34498
34811
  flushStreaming();
34499
- appendSystem(setMessages, `[error] ${event.message}`, Date.now());
34812
+ if (event.code === "tool_budget_extended") {
34813
+ appendSystem(
34814
+ setMessages,
34815
+ `[budget] ${event.message}`,
34816
+ Date.now()
34817
+ );
34818
+ } else {
34819
+ appendSystem(setMessages, `[error] ${event.message}`, Date.now());
34820
+ }
34500
34821
  } else if (event.type === "tool_execution_start") {
34501
34822
  toolNameById.set(event.toolCallId, event.toolName);
34502
34823
  flushStreaming();
@@ -34552,32 +34873,41 @@ function useChatTurn(params) {
34552
34873
  const all = h.getMessages();
34553
34874
  const seedLen = 1 + historySeedLen + 1;
34554
34875
  if (all.length > seedLen) {
34555
- appendMessages(all.slice(seedLen));
34876
+ appendMessages(
34877
+ all.slice(seedLen).map(
34878
+ (m) => m.role === "assistant" && m.content ? {
34879
+ ...m,
34880
+ content: cleanAgentContent(m.content, {
34881
+ stripQuestion: false
34882
+ })
34883
+ } : m
34884
+ )
34885
+ );
34556
34886
  }
34557
34887
  }
34558
34888
  } catch {
34559
34889
  }
34560
34890
  if (turnSucceeded && assistantContent) {
34561
34891
  try {
34892
+ const needsScrub = assistantContent.includes("<think") || assistantContent.includes("<thinking") || assistantContent.includes("---QUESTION---");
34893
+ if (needsScrub) {
34894
+ setMessages(
34895
+ (prev2) => prev2.map((m) => {
34896
+ if (m.role !== "assistant") return m;
34897
+ if (!m.content.includes("<think") && !m.content.includes("<thinking") && !m.content.includes("---QUESTION---")) {
34898
+ return m;
34899
+ }
34900
+ const cleaned = cleanAgentContent(m.content);
34901
+ return cleaned === m.content ? m : { ...m, content: cleaned };
34902
+ })
34903
+ );
34904
+ }
34562
34905
  const clar = parseClarificationRequest(assistantContent);
34563
34906
  if (clar && clar.choices && clar.choices.length >= 2) {
34564
34907
  setLastClarification({
34565
34908
  question: clar.question,
34566
34909
  choices: clar.choices
34567
34910
  });
34568
- const cleaned = cleanAgentContent(assistantContent);
34569
- if (cleaned !== assistantContent) {
34570
- setMessages((prev2) => {
34571
- const next = [...prev2];
34572
- for (let i = next.length - 1; i >= 0; i--) {
34573
- if (next[i].role === "assistant") {
34574
- next[i] = { ...next[i], content: cleaned };
34575
- break;
34576
- }
34577
- }
34578
- return next;
34579
- });
34580
- }
34581
34911
  if (setPicker) {
34582
34912
  setPicker({
34583
34913
  kind: "clarification",
@@ -34866,17 +35196,23 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34866
35196
  streamMemberId = memberId;
34867
35197
  }
34868
35198
  streamContent += event.delta;
34869
- setStreaming(commitStreaming, streamContent, event.ts, {
34870
- ...event.memberId ? { memberId: event.memberId } : {},
34871
- ...event.memberName ? { memberName: event.memberName } : {}
34872
- });
35199
+ setStreaming(
35200
+ commitStreaming,
35201
+ cleanAgentContent(streamContent),
35202
+ event.ts,
35203
+ {
35204
+ ...event.memberId ? { memberId: event.memberId } : {},
35205
+ ...event.memberName ? { memberName: event.memberName } : {}
35206
+ }
35207
+ );
34873
35208
  } else {
34874
35209
  commitStreaming((prev2) => {
34875
35210
  const last = prev2[prev2.length - 1];
34876
35211
  if (last && last.role === "assistant" && last.id.startsWith("streaming-") && (last.memberId ?? null) === (event.memberId ?? null)) {
35212
+ const nextContent = cleanAgentContent(last.content + event.delta);
34877
35213
  return [
34878
35214
  ...prev2.slice(0, -1),
34879
- { ...last, content: last.content + event.delta }
35215
+ { ...last, content: nextContent }
34880
35216
  ];
34881
35217
  }
34882
35218
  return [
@@ -34884,7 +35220,7 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34884
35220
  {
34885
35221
  id: `streaming-${crypto.randomUUID()}`,
34886
35222
  role: "assistant",
34887
- content: event.delta,
35223
+ content: cleanAgentContent(event.delta),
34888
35224
  ts: event.ts,
34889
35225
  ...event.memberId ? { memberId: event.memberId } : {},
34890
35226
  ...event.memberName ? { memberName: event.memberName } : {}
@@ -37569,8 +37905,9 @@ function useBatchedMessages(state2, setState, cadenceMs = 16) {
37569
37905
  // src/cli/hooks/useTerminalSize.ts
37570
37906
  import { useEffect as useEffect6, useState as useState7 } from "react";
37571
37907
  import { useStdout as useStdout2 } from "ink";
37908
+ var DEFAULT_COALESCE_MS = 120;
37572
37909
  function useTerminalSize(options = {}) {
37573
- const { defaults = { columns: 80, rows: 24 }, coalesceMs = 16 } = options;
37910
+ const { defaults = { columns: 80, rows: 24 }, coalesceMs = DEFAULT_COALESCE_MS } = options;
37574
37911
  const { stdout } = useStdout2();
37575
37912
  const [size, setSize] = useState7({
37576
37913
  columns: stdout?.columns ?? defaults.columns,
@@ -37578,25 +37915,25 @@ function useTerminalSize(options = {}) {
37578
37915
  });
37579
37916
  useEffect6(() => {
37580
37917
  if (!stdout) return;
37581
- setSize({
37918
+ const read = () => ({
37582
37919
  columns: stdout.columns ?? defaults.columns,
37583
37920
  rows: stdout.rows ?? defaults.rows
37584
37921
  });
37922
+ const commit = (next) => {
37923
+ setSize(
37924
+ (prev2) => prev2.columns === next.columns && prev2.rows === next.rows ? prev2 : next
37925
+ );
37926
+ };
37927
+ commit(read());
37585
37928
  let rafId = null;
37586
37929
  const handleResize = () => {
37587
37930
  if (coalesceMs <= 0) {
37588
- setSize({
37589
- columns: stdout.columns ?? defaults.columns,
37590
- rows: stdout.rows ?? defaults.rows
37591
- });
37931
+ commit(read());
37592
37932
  return;
37593
37933
  }
37594
37934
  if (rafId !== null) clearTimeout(rafId);
37595
37935
  rafId = setTimeout(() => {
37596
- setSize({
37597
- columns: stdout.columns ?? defaults.columns,
37598
- rows: stdout.rows ?? defaults.rows
37599
- });
37936
+ commit(read());
37600
37937
  rafId = null;
37601
37938
  }, coalesceMs);
37602
37939
  };
@@ -37622,7 +37959,12 @@ function App() {
37622
37959
  const [input, setInput] = useState8("");
37623
37960
  const [busy, setBusy] = useState8(false);
37624
37961
  const [providerConfig, setProviderConfig] = useState8(() => getProviderConfig());
37625
- const [sessionStats, setSessionStats] = useState8({ totalTokens: 0, totalCostUsd: 0, cachedTokens: 0 });
37962
+ const [sessionStats, setSessionStats] = useState8({
37963
+ totalTokens: 0,
37964
+ totalCostUsd: 0,
37965
+ cachedTokens: 0,
37966
+ contextTokens: 0
37967
+ });
37626
37968
  const [clearEpoch, setClearEpoch] = useState8(0);
37627
37969
  const [mode, setMode] = useState8("agent");
37628
37970
  const [phase2, setPhaseUi] = useState8("build");
@@ -37723,60 +38065,84 @@ function App() {
37723
38065
  }
37724
38066
  setPicker(null);
37725
38067
  }, [picker]);
38068
+ const bannerColsRef = useRef6(null);
38069
+ if (bannerColsRef.current === null) {
38070
+ bannerColsRef.current = size.columns > 0 ? size.columns : 80;
38071
+ }
37726
38072
  const banner = useMemo(() => {
37727
- const left = `zelari-code \xB7 ${activeProviderSpec.id}/${activeModel}`;
37728
- const right = `ZELARI CODE v${VERSION}`;
37729
- const pad = Math.max(2, 72 - left.length - right.length);
38073
+ const cols = bannerColsRef.current ?? 80;
38074
+ const content = formatBannerWithLogoRight({
38075
+ leftLines: [
38076
+ `zelari-code \xB7 ${activeProviderSpec.id}/${activeModel}`,
38077
+ `cwd: ${cwd}`,
38078
+ `/help \xB7 /plan \xB7 /build \xB7 /view-plan \xB7 shift+tab mode`
38079
+ ],
38080
+ version: VERSION,
38081
+ columns: cols,
38082
+ compact: cols < 72
38083
+ });
37730
38084
  return {
37731
38085
  id: "banner-once",
37732
38086
  role: "system",
37733
38087
  ts: 0,
37734
- content: `${left}${" ".repeat(pad)}${right}
37735
- cwd: ${cwd}
37736
- /help \xB7 /plan \xB7 /build \xB7 /view-plan \xB7 shift+tab mode (agent/council/zelari)`
38088
+ content
37737
38089
  };
37738
38090
  }, [activeProviderSpec.id, activeModel, cwd]);
38091
+ const [sidebarOpen, setSidebarOpen] = useState8(false);
38092
+ useEffect7(() => {
38093
+ setSidebarOpen((prev2) => sidebarVisibility(size.columns, size.rows, prev2));
38094
+ }, [size.columns, size.rows]);
37739
38095
  const staticKey = `${session.sessionId || "pre-bootstrap"}-${clearEpoch}`;
37740
38096
  const staticItems = session.sessionId ? [banner, ...session.messages] : [];
37741
- const showSidebar = shouldShowSidebar(size.columns, size.rows);
37742
- return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { key: staticKey, items: staticItems }, (item) => renderMessage(item)), /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "row" }, /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", flexGrow: 1, paddingX: 1 }, /* @__PURE__ */ React9.createElement(LiveRegion, { live: session.live, busy, elapsedMs: timer.elapsedMs }), picker ? /* @__PURE__ */ React9.createElement(
37743
- SelectList,
38097
+ const dynamicMaxRows = Math.max(8, Math.min(18, size.rows - 2));
38098
+ return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { key: staticKey, items: staticItems }, (item) => renderMessage(item)), /* @__PURE__ */ React9.createElement(
38099
+ Box8,
37744
38100
  {
37745
- title: picker.title,
37746
- items: picker.items,
37747
- onSelect: onPickerSelect,
37748
- onCancel: onPickerCancel,
37749
- maxVisible: Math.max(4, Math.min(10, size.rows - 10))
37750
- }
37751
- ) : /* @__PURE__ */ React9.createElement(
37752
- InputBar,
37753
- {
37754
- value: input,
37755
- onChange: setInput,
37756
- onSubmit: handleSubmit,
37757
- disabled: busy
37758
- }
37759
- ), /* @__PURE__ */ React9.createElement(
37760
- StatusBar,
37761
- {
37762
- model: activeModel,
37763
- provider: activeProviderSpec.id,
37764
- sessionId: session.sessionId ? session.sessionId.slice(0, 8) : "...",
37765
- sessionActive: session.sessionActive,
37766
- queueCount: chatTurn.queueCount,
37767
- busy,
37768
- mode,
37769
- phase: phase2,
37770
- cwd,
37771
- elapsedMs: timer.elapsedMs,
37772
- lastMs: timer.lastMs,
37773
- costUsd: sessionStats.totalCostUsd,
37774
- cachedTokens: sessionStats.cachedTokens,
37775
- contextUsed: sessionStats.totalTokens,
37776
- contextLimit: Number(process.env.ZELARI_CONTEXT_LIMIT) || 2e5,
37777
- brandVersion: VERSION
37778
- }
37779
- )), showSidebar && /* @__PURE__ */ React9.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })));
38101
+ flexDirection: "row",
38102
+ width: size.columns > 0 ? size.columns : void 0,
38103
+ height: dynamicMaxRows,
38104
+ overflow: "hidden"
38105
+ },
38106
+ /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", flexGrow: 1, paddingX: 1, overflow: "hidden" }, /* @__PURE__ */ React9.createElement(LiveRegion, { live: session.live, busy, elapsedMs: timer.elapsedMs }), picker ? /* @__PURE__ */ React9.createElement(
38107
+ SelectList,
38108
+ {
38109
+ title: picker.title,
38110
+ items: picker.items,
38111
+ onSelect: onPickerSelect,
38112
+ onCancel: onPickerCancel,
38113
+ maxVisible: Math.max(3, Math.min(8, size.rows - 12))
38114
+ }
38115
+ ) : /* @__PURE__ */ React9.createElement(
38116
+ InputBar,
38117
+ {
38118
+ value: input,
38119
+ onChange: setInput,
38120
+ onSubmit: handleSubmit,
38121
+ disabled: busy
38122
+ }
38123
+ ), /* @__PURE__ */ React9.createElement(
38124
+ StatusBar,
38125
+ {
38126
+ model: activeModel,
38127
+ provider: activeProviderSpec.id,
38128
+ sessionId: session.sessionId ? session.sessionId.slice(0, 8) : "...",
38129
+ sessionActive: session.sessionActive,
38130
+ queueCount: chatTurn.queueCount,
38131
+ busy,
38132
+ mode,
38133
+ phase: phase2,
38134
+ cwd,
38135
+ elapsedMs: timer.elapsedMs,
38136
+ lastMs: timer.lastMs,
38137
+ costUsd: sessionStats.totalCostUsd,
38138
+ cachedTokens: sessionStats.cachedTokens,
38139
+ contextUsed: sessionStats.contextTokens || 0,
38140
+ contextLimit: Number(process.env.ZELARI_CONTEXT_LIMIT) || 2e5,
38141
+ brandVersion: VERSION
38142
+ }
38143
+ )),
38144
+ sidebarOpen && /* @__PURE__ */ React9.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })
38145
+ ));
37780
38146
  }
37781
38147
 
37782
38148
  // src/cli/components/SplashScreen.tsx