zelari-code 1.8.0 → 1.8.2

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",
@@ -18946,16 +18973,53 @@ function parseTextToolCalls(text) {
18946
18973
  const m = /---TOOLS---\s*([\s\S]*?)---END---/.exec(text);
18947
18974
  if (!m || !m[1])
18948
18975
  return [];
18976
+ let body = m[1].trim();
18977
+ body = body.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
18978
+ const candidates = [body];
18979
+ if (/\]\s*\[/.test(body)) {
18980
+ candidates.push(body.replace(/\]\s*\[/g, ","));
18981
+ }
18982
+ if (body.includes('\\"')) {
18983
+ candidates.push(body.replace(/\\"/g, '"'));
18984
+ if (/\]\s*\[/.test(body)) {
18985
+ candidates.push(body.replace(/\\"/g, '"').replace(/\]\s*\[/g, ","));
18986
+ }
18987
+ }
18988
+ for (const cand of candidates) {
18989
+ const items = tryParseToolArray(cand);
18990
+ if (items.length > 0)
18991
+ return items;
18992
+ }
18993
+ const arrays = extractJsonArrays(body);
18994
+ if (arrays.length > 1) {
18995
+ const merged = [];
18996
+ for (const a of arrays) {
18997
+ merged.push(...tryParseToolArray(a));
18998
+ }
18999
+ if (merged.length > 0)
19000
+ return merged;
19001
+ }
19002
+ return extractToolObjects(body);
19003
+ }
19004
+ function tryParseToolArray(raw) {
18949
19005
  let parsed;
18950
19006
  try {
18951
- parsed = JSON.parse(m[1].trim());
19007
+ parsed = JSON.parse(raw.trim());
18952
19008
  } catch {
18953
19009
  return [];
18954
19010
  }
18955
- if (!Array.isArray(parsed))
18956
- return [];
19011
+ if (!Array.isArray(parsed)) {
19012
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19013
+ parsed = [parsed];
19014
+ } else {
19015
+ return [];
19016
+ }
19017
+ }
19018
+ return normalizeToolItems(parsed);
19019
+ }
19020
+ function normalizeToolItems(items) {
18957
19021
  const out = [];
18958
- for (const item of parsed) {
19022
+ for (const item of items) {
18959
19023
  if (item && typeof item === "object" && typeof item.name === "string") {
18960
19024
  const rawArgs = item.args;
18961
19025
  out.push({
@@ -18966,6 +19030,70 @@ function parseTextToolCalls(text) {
18966
19030
  }
18967
19031
  return out;
18968
19032
  }
19033
+ function extractJsonArrays(text) {
19034
+ const out = [];
19035
+ let i = 0;
19036
+ while (i < text.length) {
19037
+ if (text[i] !== "[") {
19038
+ i++;
19039
+ continue;
19040
+ }
19041
+ let depth = 0;
19042
+ let inStr = false;
19043
+ let esc2 = false;
19044
+ let end = -1;
19045
+ for (let j = i; j < text.length; j++) {
19046
+ const c = text[j];
19047
+ if (inStr) {
19048
+ if (esc2)
19049
+ esc2 = false;
19050
+ else if (c === "\\")
19051
+ esc2 = true;
19052
+ else if (c === '"')
19053
+ inStr = false;
19054
+ continue;
19055
+ }
19056
+ if (c === '"') {
19057
+ inStr = true;
19058
+ continue;
19059
+ }
19060
+ if (c === "[")
19061
+ depth++;
19062
+ else if (c === "]") {
19063
+ depth--;
19064
+ if (depth === 0) {
19065
+ end = j;
19066
+ break;
19067
+ }
19068
+ }
19069
+ }
19070
+ if (end > i) {
19071
+ out.push(text.slice(i, end + 1));
19072
+ i = end + 1;
19073
+ } else {
19074
+ i++;
19075
+ }
19076
+ }
19077
+ return out;
19078
+ }
19079
+ function extractToolObjects(text) {
19080
+ const out = [];
19081
+ const re = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})\s*\}/g;
19082
+ let match;
19083
+ while ((match = re.exec(text)) !== null) {
19084
+ const name = match[1];
19085
+ let args = {};
19086
+ try {
19087
+ const parsed = JSON.parse(match[2]);
19088
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19089
+ args = parsed;
19090
+ }
19091
+ } catch {
19092
+ }
19093
+ out.push({ name, args });
19094
+ }
19095
+ return out;
19096
+ }
18969
19097
  var AgentHarness;
18970
19098
  var init_AgentHarness = __esm({
18971
19099
  "packages/core/dist/core/AgentHarness.js"() {
@@ -23934,11 +24062,19 @@ function parseClarificationRequest(text) {
23934
24062
  }
23935
24063
  }
23936
24064
  function parseThinking(text) {
23937
- const match = text.match(/<think>([\s\S]*?)<\/think>/);
23938
- return match ? match[1].trim() : "";
24065
+ const complete = text.match(/<think(?:ing)?>([\s\S]*?)<\/think(?:ing)?>/i);
24066
+ if (complete)
24067
+ return complete[1].trim();
24068
+ const open = text.match(/<think(?:ing)?>([\s\S]*)$/i);
24069
+ return open ? open[1].trim() : "";
23939
24070
  }
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();
24071
+ function cleanAgentContent(text, opts = {}) {
24072
+ const stripQuestion = opts.stripQuestion !== false;
24073
+ 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, "");
24074
+ if (stripQuestion) {
24075
+ out = out.replace(/---QUESTION---[\s\S]*?---END---/g, "");
24076
+ }
24077
+ return out.replace(/\n{3,}/g, "\n\n").trim();
23942
24078
  }
23943
24079
  function restrictImplementationWrites(toolNames, opts) {
23944
24080
  if (opts.runMode !== "implementation" || opts.isImplementer)
@@ -30520,7 +30656,7 @@ import React14 from "react";
30520
30656
  import { render } from "ink";
30521
30657
 
30522
30658
  // src/cli/app.tsx
30523
- import React9, { useState as useState8, useMemo, useCallback as useCallback5, useEffect as useEffect7 } from "react";
30659
+ import React9, { useState as useState8, useMemo, useCallback as useCallback5, useEffect as useEffect7, useRef as useRef6 } from "react";
30524
30660
  import { Box as Box8, Static, useInput as useInput2, useStdin as useStdin2 } from "ink";
30525
30661
 
30526
30662
  // src/cli/components/InputBar.tsx
@@ -30918,9 +31054,12 @@ function WorkingIndicator({
30918
31054
 
30919
31055
  // src/cli/components/LiveRegion.tsx
30920
31056
  var LIVE_STREAM_TAIL_LINES = 10;
31057
+ var MAX_LIVE_TOOLS = 4;
30921
31058
  function LiveRegion({ live, busy, elapsedMs = null }) {
30922
31059
  const { streaming, runningTools } = live;
30923
31060
  if (!streaming && runningTools.length === 0 && !busy) return null;
31061
+ const visibleTools = runningTools.slice(0, MAX_LIVE_TOOLS);
31062
+ const hiddenTools = runningTools.length - visibleTools.length;
30924
31063
  return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", paddingX: 1 }, streaming && streaming.role === "assistant" && /* @__PURE__ */ React5.createElement(
30925
31064
  StreamingTail,
30926
31065
  {
@@ -30930,7 +31069,7 @@ function LiveRegion({ live, busy, elapsedMs = null }) {
30930
31069
  memberName: streaming.memberName,
30931
31070
  memberId: streaming.memberId
30932
31071
  }
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 }));
31072
+ ), 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
31073
  }
30935
31074
  function StreamingTail(m) {
30936
31075
  const lines = m.content.split("\n");
@@ -31093,11 +31232,25 @@ import React8 from "react";
31093
31232
  import { Box as Box7, Text as Text8 } from "ink";
31094
31233
  var SIDEBAR_WIDTH = 28;
31095
31234
  var SIDEBAR_MIN_COLUMNS = 96;
31096
- var EMBLEM_MIN_ROWS = 26;
31097
- var MAX_FILES_SHORT = 6;
31098
- var MAX_FILES_TALL = 10;
31235
+ var SIDEBAR_HIDE_COLUMNS = 88;
31236
+ var SIDEBAR_MIN_ROWS = 16;
31237
+ var SIDEBAR_HIDE_ROWS = 14;
31238
+ var MAX_FILES_SHORT = 4;
31239
+ var MAX_FILES_TALL = 8;
31240
+ var SIDEBAR_CHROME_LINES = 4;
31099
31241
  function shouldShowSidebar(columns, rows) {
31100
- return columns >= SIDEBAR_MIN_COLUMNS && rows >= 16;
31242
+ return columns >= SIDEBAR_MIN_COLUMNS && rows >= SIDEBAR_MIN_ROWS;
31243
+ }
31244
+ function sidebarVisibility(columns, rows, currentlyVisible) {
31245
+ if (currentlyVisible) {
31246
+ return columns >= SIDEBAR_HIDE_COLUMNS && rows >= SIDEBAR_HIDE_ROWS;
31247
+ }
31248
+ return shouldShowSidebar(columns, rows);
31249
+ }
31250
+ function maxSidebarFiles(rows) {
31251
+ const budget = Math.max(2, rows - 14 - SIDEBAR_CHROME_LINES);
31252
+ const cap = rows >= 28 ? MAX_FILES_TALL : MAX_FILES_SHORT;
31253
+ return Math.min(cap, budget);
31101
31254
  }
31102
31255
  function truncatePath(p3, max) {
31103
31256
  if (p3.length <= max) return p3;
@@ -31105,7 +31258,7 @@ function truncatePath(p3, max) {
31105
31258
  }
31106
31259
  function Sidebar({ version: version2, changes, rows }) {
31107
31260
  void version2;
31108
- const maxFiles = rows >= EMBLEM_MIN_ROWS + 8 ? MAX_FILES_TALL : MAX_FILES_SHORT;
31261
+ const maxFiles = maxSidebarFiles(rows);
31109
31262
  const visible = changes.files.slice(0, maxFiles);
31110
31263
  const hidden = changes.files.length - visible.length;
31111
31264
  const innerWidth = SIDEBAR_WIDTH - 4;
@@ -31117,7 +31270,9 @@ function Sidebar({ version: version2, changes, rows }) {
31117
31270
  borderStyle: "single",
31118
31271
  borderColor: "gray",
31119
31272
  paddingX: 1,
31120
- flexShrink: 0
31273
+ flexShrink: 0,
31274
+ height: Math.min(rows - 8, SIDEBAR_CHROME_LINES + maxFiles + 2),
31275
+ overflow: "hidden"
31121
31276
  },
31122
31277
  /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, changes.branch ? truncatePath(changes.branch, 18) : "git")),
31123
31278
  /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "\u2500".repeat(innerWidth)),
@@ -31129,6 +31284,53 @@ function FileRow({ file: file2, pathWidth }) {
31129
31284
  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
31285
  }
31131
31286
 
31287
+ // src/cli/components/brandArt.ts
31288
+ var BRAND_LOGO_ASCII = [
31289
+ " -#%=",
31290
+ " +%%%%#.",
31291
+ " :#%%%%%%%-",
31292
+ ":%%@@@@@@@@=",
31293
+ "-%@@@@@@@@@@@+",
31294
+ ":%@@@@@@%@@@@@@=",
31295
+ "-%%@@@@@.+%@@%@+",
31296
+ " -@@@@@@:%++%@=",
31297
+ "=*%@@@@@@:@@%=*@#+.",
31298
+ "%%%%%@@@@:+++-*%@@*",
31299
+ ".%%%%%@@@@@+%+=:%@@@@:",
31300
+ "*@@@@@@@@@@@@@%@@@@@@*"
31301
+ ].join("\n");
31302
+ var BRAND_LOGO_COMPACT = [
31303
+ " -#%=",
31304
+ " +%%%%#.",
31305
+ ":%%@@@@=",
31306
+ "*@@@@@@*"
31307
+ ].join("\n");
31308
+ function formatBannerWithLogoRight(opts) {
31309
+ const logoSrc = opts.compact ? BRAND_LOGO_COMPACT : BRAND_LOGO_ASCII;
31310
+ const logoLines = logoSrc.split("\n");
31311
+ const logoWidth = Math.max(...logoLines.map((l) => l.length));
31312
+ const cols = Math.max(logoWidth + 20, Math.min(opts.columns, 120));
31313
+ const left = [...opts.leftLines];
31314
+ const wordmark = `ZELARI CODE v${opts.version}`;
31315
+ const rightExtra = [wordmark];
31316
+ const rightBlock = [...logoLines, ...rightExtra];
31317
+ const rows = Math.max(left.length, rightBlock.length);
31318
+ const out = [];
31319
+ for (let i = 0; i < rows; i++) {
31320
+ const L = left[i] ?? "";
31321
+ const R = rightBlock[i] ?? "";
31322
+ if (!R) {
31323
+ out.push(L);
31324
+ continue;
31325
+ }
31326
+ const maxLeft = Math.max(0, cols - R.length - 2);
31327
+ const leftClipped = L.length > maxLeft ? L.slice(0, Math.max(0, maxLeft - 1)) + "\u2026" : L;
31328
+ const pad = Math.max(2, cols - leftClipped.length - R.length);
31329
+ out.push(leftClipped + " ".repeat(pad) + R);
31330
+ }
31331
+ return out.join("\n");
31332
+ }
31333
+
31132
31334
  // src/cli/modelDiscovery.ts
31133
31335
  import { promises as fs3, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
31134
31336
  import { homedir } from "node:os";
@@ -34478,15 +34680,16 @@ function useChatTurn(params) {
34478
34680
  if (event.type === "message_delta") {
34479
34681
  assistantContent += event.delta;
34480
34682
  streamContent += event.delta;
34683
+ const displayContent = cleanAgentContent(streamContent);
34481
34684
  if (useLiveModel) {
34482
- setStreaming(commitStreaming, streamContent, Date.now(), {
34685
+ setStreaming(commitStreaming, displayContent, Date.now(), {
34483
34686
  ...event.memberId ? { memberId: event.memberId } : {},
34484
34687
  ...event.memberName ? { memberName: event.memberName } : {}
34485
34688
  });
34486
34689
  } else {
34487
34690
  appendOrExtendStreamingAssistant(
34488
34691
  commitStreaming,
34489
- streamContent,
34692
+ displayContent,
34490
34693
  Date.now(),
34491
34694
  {
34492
34695
  ...event.memberId ? { memberId: event.memberId } : {},
@@ -34552,32 +34755,41 @@ function useChatTurn(params) {
34552
34755
  const all = h.getMessages();
34553
34756
  const seedLen = 1 + historySeedLen + 1;
34554
34757
  if (all.length > seedLen) {
34555
- appendMessages(all.slice(seedLen));
34758
+ appendMessages(
34759
+ all.slice(seedLen).map(
34760
+ (m) => m.role === "assistant" && m.content ? {
34761
+ ...m,
34762
+ content: cleanAgentContent(m.content, {
34763
+ stripQuestion: false
34764
+ })
34765
+ } : m
34766
+ )
34767
+ );
34556
34768
  }
34557
34769
  }
34558
34770
  } catch {
34559
34771
  }
34560
34772
  if (turnSucceeded && assistantContent) {
34561
34773
  try {
34774
+ const needsScrub = assistantContent.includes("<think") || assistantContent.includes("<thinking") || assistantContent.includes("---QUESTION---");
34775
+ if (needsScrub) {
34776
+ setMessages(
34777
+ (prev2) => prev2.map((m) => {
34778
+ if (m.role !== "assistant") return m;
34779
+ if (!m.content.includes("<think") && !m.content.includes("<thinking") && !m.content.includes("---QUESTION---")) {
34780
+ return m;
34781
+ }
34782
+ const cleaned = cleanAgentContent(m.content);
34783
+ return cleaned === m.content ? m : { ...m, content: cleaned };
34784
+ })
34785
+ );
34786
+ }
34562
34787
  const clar = parseClarificationRequest(assistantContent);
34563
34788
  if (clar && clar.choices && clar.choices.length >= 2) {
34564
34789
  setLastClarification({
34565
34790
  question: clar.question,
34566
34791
  choices: clar.choices
34567
34792
  });
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
34793
  if (setPicker) {
34582
34794
  setPicker({
34583
34795
  kind: "clarification",
@@ -34866,17 +35078,23 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34866
35078
  streamMemberId = memberId;
34867
35079
  }
34868
35080
  streamContent += event.delta;
34869
- setStreaming(commitStreaming, streamContent, event.ts, {
34870
- ...event.memberId ? { memberId: event.memberId } : {},
34871
- ...event.memberName ? { memberName: event.memberName } : {}
34872
- });
35081
+ setStreaming(
35082
+ commitStreaming,
35083
+ cleanAgentContent(streamContent),
35084
+ event.ts,
35085
+ {
35086
+ ...event.memberId ? { memberId: event.memberId } : {},
35087
+ ...event.memberName ? { memberName: event.memberName } : {}
35088
+ }
35089
+ );
34873
35090
  } else {
34874
35091
  commitStreaming((prev2) => {
34875
35092
  const last = prev2[prev2.length - 1];
34876
35093
  if (last && last.role === "assistant" && last.id.startsWith("streaming-") && (last.memberId ?? null) === (event.memberId ?? null)) {
35094
+ const nextContent = cleanAgentContent(last.content + event.delta);
34877
35095
  return [
34878
35096
  ...prev2.slice(0, -1),
34879
- { ...last, content: last.content + event.delta }
35097
+ { ...last, content: nextContent }
34880
35098
  ];
34881
35099
  }
34882
35100
  return [
@@ -34884,7 +35102,7 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34884
35102
  {
34885
35103
  id: `streaming-${crypto.randomUUID()}`,
34886
35104
  role: "assistant",
34887
- content: event.delta,
35105
+ content: cleanAgentContent(event.delta),
34888
35106
  ts: event.ts,
34889
35107
  ...event.memberId ? { memberId: event.memberId } : {},
34890
35108
  ...event.memberName ? { memberName: event.memberName } : {}
@@ -37569,8 +37787,9 @@ function useBatchedMessages(state2, setState, cadenceMs = 16) {
37569
37787
  // src/cli/hooks/useTerminalSize.ts
37570
37788
  import { useEffect as useEffect6, useState as useState7 } from "react";
37571
37789
  import { useStdout as useStdout2 } from "ink";
37790
+ var DEFAULT_COALESCE_MS = 120;
37572
37791
  function useTerminalSize(options = {}) {
37573
- const { defaults = { columns: 80, rows: 24 }, coalesceMs = 16 } = options;
37792
+ const { defaults = { columns: 80, rows: 24 }, coalesceMs = DEFAULT_COALESCE_MS } = options;
37574
37793
  const { stdout } = useStdout2();
37575
37794
  const [size, setSize] = useState7({
37576
37795
  columns: stdout?.columns ?? defaults.columns,
@@ -37578,25 +37797,25 @@ function useTerminalSize(options = {}) {
37578
37797
  });
37579
37798
  useEffect6(() => {
37580
37799
  if (!stdout) return;
37581
- setSize({
37800
+ const read = () => ({
37582
37801
  columns: stdout.columns ?? defaults.columns,
37583
37802
  rows: stdout.rows ?? defaults.rows
37584
37803
  });
37804
+ const commit = (next) => {
37805
+ setSize(
37806
+ (prev2) => prev2.columns === next.columns && prev2.rows === next.rows ? prev2 : next
37807
+ );
37808
+ };
37809
+ commit(read());
37585
37810
  let rafId = null;
37586
37811
  const handleResize = () => {
37587
37812
  if (coalesceMs <= 0) {
37588
- setSize({
37589
- columns: stdout.columns ?? defaults.columns,
37590
- rows: stdout.rows ?? defaults.rows
37591
- });
37813
+ commit(read());
37592
37814
  return;
37593
37815
  }
37594
37816
  if (rafId !== null) clearTimeout(rafId);
37595
37817
  rafId = setTimeout(() => {
37596
- setSize({
37597
- columns: stdout.columns ?? defaults.columns,
37598
- rows: stdout.rows ?? defaults.rows
37599
- });
37818
+ commit(read());
37600
37819
  rafId = null;
37601
37820
  }, coalesceMs);
37602
37821
  };
@@ -37723,60 +37942,84 @@ function App() {
37723
37942
  }
37724
37943
  setPicker(null);
37725
37944
  }, [picker]);
37945
+ const bannerColsRef = useRef6(null);
37946
+ if (bannerColsRef.current === null) {
37947
+ bannerColsRef.current = size.columns > 0 ? size.columns : 80;
37948
+ }
37726
37949
  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);
37950
+ const cols = bannerColsRef.current ?? 80;
37951
+ const content = formatBannerWithLogoRight({
37952
+ leftLines: [
37953
+ `zelari-code \xB7 ${activeProviderSpec.id}/${activeModel}`,
37954
+ `cwd: ${cwd}`,
37955
+ `/help \xB7 /plan \xB7 /build \xB7 /view-plan \xB7 shift+tab mode`
37956
+ ],
37957
+ version: VERSION,
37958
+ columns: cols,
37959
+ compact: cols < 72
37960
+ });
37730
37961
  return {
37731
37962
  id: "banner-once",
37732
37963
  role: "system",
37733
37964
  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)`
37965
+ content
37737
37966
  };
37738
37967
  }, [activeProviderSpec.id, activeModel, cwd]);
37968
+ const [sidebarOpen, setSidebarOpen] = useState8(false);
37969
+ useEffect7(() => {
37970
+ setSidebarOpen((prev2) => sidebarVisibility(size.columns, size.rows, prev2));
37971
+ }, [size.columns, size.rows]);
37739
37972
  const staticKey = `${session.sessionId || "pre-bootstrap"}-${clearEpoch}`;
37740
37973
  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,
37744
- {
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,
37974
+ const dynamicMaxRows = Math.max(8, Math.min(18, size.rows - 2));
37975
+ return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { key: staticKey, items: staticItems }, (item) => renderMessage(item)), /* @__PURE__ */ React9.createElement(
37976
+ Box8,
37753
37977
  {
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 })));
37978
+ flexDirection: "row",
37979
+ width: size.columns > 0 ? size.columns : void 0,
37980
+ height: dynamicMaxRows,
37981
+ overflow: "hidden"
37982
+ },
37983
+ /* @__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(
37984
+ SelectList,
37985
+ {
37986
+ title: picker.title,
37987
+ items: picker.items,
37988
+ onSelect: onPickerSelect,
37989
+ onCancel: onPickerCancel,
37990
+ maxVisible: Math.max(3, Math.min(8, size.rows - 12))
37991
+ }
37992
+ ) : /* @__PURE__ */ React9.createElement(
37993
+ InputBar,
37994
+ {
37995
+ value: input,
37996
+ onChange: setInput,
37997
+ onSubmit: handleSubmit,
37998
+ disabled: busy
37999
+ }
38000
+ ), /* @__PURE__ */ React9.createElement(
38001
+ StatusBar,
38002
+ {
38003
+ model: activeModel,
38004
+ provider: activeProviderSpec.id,
38005
+ sessionId: session.sessionId ? session.sessionId.slice(0, 8) : "...",
38006
+ sessionActive: session.sessionActive,
38007
+ queueCount: chatTurn.queueCount,
38008
+ busy,
38009
+ mode,
38010
+ phase: phase2,
38011
+ cwd,
38012
+ elapsedMs: timer.elapsedMs,
38013
+ lastMs: timer.lastMs,
38014
+ costUsd: sessionStats.totalCostUsd,
38015
+ cachedTokens: sessionStats.cachedTokens,
38016
+ contextUsed: sessionStats.totalTokens,
38017
+ contextLimit: Number(process.env.ZELARI_CONTEXT_LIMIT) || 2e5,
38018
+ brandVersion: VERSION
38019
+ }
38020
+ )),
38021
+ sidebarOpen && /* @__PURE__ */ React9.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })
38022
+ ));
37780
38023
  }
37781
38024
 
37782
38025
  // src/cli/components/SplashScreen.tsx