zelari-code 1.7.1 → 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.
Files changed (42) hide show
  1. package/dist/cli/app.js +48 -19
  2. package/dist/cli/app.js.map +1 -1
  3. package/dist/cli/browser/driver.js +58 -10
  4. package/dist/cli/browser/driver.js.map +1 -1
  5. package/dist/cli/browser/tools.js +4 -1
  6. package/dist/cli/browser/tools.js.map +1 -1
  7. package/dist/cli/budget/tokenBudget.js +92 -0
  8. package/dist/cli/budget/tokenBudget.js.map +1 -0
  9. package/dist/cli/components/LiveRegion.js +9 -1
  10. package/dist/cli/components/LiveRegion.js.map +1 -1
  11. package/dist/cli/components/PluginGate.js +24 -1
  12. package/dist/cli/components/PluginGate.js.map +1 -1
  13. package/dist/cli/components/Sidebar.js +44 -37
  14. package/dist/cli/components/Sidebar.js.map +1 -1
  15. package/dist/cli/components/StatusBar.js +18 -3
  16. package/dist/cli/components/StatusBar.js.map +1 -1
  17. package/dist/cli/components/brandArt.js +62 -0
  18. package/dist/cli/components/brandArt.js.map +1 -0
  19. package/dist/cli/councilDispatcher.js +3 -0
  20. package/dist/cli/councilDispatcher.js.map +1 -1
  21. package/dist/cli/hooks/conversationContext.js +136 -0
  22. package/dist/cli/hooks/conversationContext.js.map +1 -0
  23. package/dist/cli/hooks/useChatTurn.js +193 -74
  24. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  25. package/dist/cli/hooks/useSlashDispatch.js +55 -0
  26. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  27. package/dist/cli/hooks/useTerminalSize.js +17 -10
  28. package/dist/cli/hooks/useTerminalSize.js.map +1 -1
  29. package/dist/cli/main.bundled.js +1386 -493
  30. package/dist/cli/main.bundled.js.map +4 -4
  31. package/dist/cli/phase.js +52 -0
  32. package/dist/cli/phase.js.map +1 -0
  33. package/dist/cli/phaseState.js +12 -0
  34. package/dist/cli/phaseState.js.map +1 -0
  35. package/dist/cli/plugins/registry.js +90 -37
  36. package/dist/cli/plugins/registry.js.map +1 -1
  37. package/dist/cli/slashCommands.js +22 -0
  38. package/dist/cli/slashCommands.js.map +1 -1
  39. package/dist/cli/slashHandlers/provider.js.map +1 -1
  40. package/dist/cli/toolRegistry.js +3 -2
  41. package/dist/cli/toolRegistry.js.map +1 -1
  42. package/package.json +2 -2
@@ -1535,10 +1535,10 @@ function mergeDefs(...defs) {
1535
1535
  function cloneDef(schema) {
1536
1536
  return mergeDefs(schema._zod.def);
1537
1537
  }
1538
- function getElementAtPath(obj, path34) {
1539
- if (!path34)
1538
+ function getElementAtPath(obj, path36) {
1539
+ if (!path36)
1540
1540
  return obj;
1541
- return path34.reduce((acc, key) => acc?.[key], obj);
1541
+ return path36.reduce((acc, key) => acc?.[key], obj);
1542
1542
  }
1543
1543
  function promiseAllObject(promisesObj) {
1544
1544
  const keys = Object.keys(promisesObj);
@@ -1866,11 +1866,11 @@ function explicitlyAborted(x, startIndex = 0) {
1866
1866
  }
1867
1867
  return false;
1868
1868
  }
1869
- function prefixIssues(path34, issues) {
1869
+ function prefixIssues(path36, issues) {
1870
1870
  return issues.map((iss) => {
1871
1871
  var _a3;
1872
1872
  (_a3 = iss).path ?? (_a3.path = []);
1873
- iss.path.unshift(path34);
1873
+ iss.path.unshift(path36);
1874
1874
  return iss;
1875
1875
  });
1876
1876
  }
@@ -2088,16 +2088,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2088
2088
  }
2089
2089
  function formatError(error51, mapper = (issue2) => issue2.message) {
2090
2090
  const fieldErrors = { _errors: [] };
2091
- const processError = (error52, path34 = []) => {
2091
+ const processError = (error52, path36 = []) => {
2092
2092
  for (const issue2 of error52.issues) {
2093
2093
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2094
- issue2.errors.map((issues) => processError({ issues }, [...path34, ...issue2.path]));
2094
+ issue2.errors.map((issues) => processError({ issues }, [...path36, ...issue2.path]));
2095
2095
  } else if (issue2.code === "invalid_key") {
2096
- processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2096
+ processError({ issues: issue2.issues }, [...path36, ...issue2.path]);
2097
2097
  } else if (issue2.code === "invalid_element") {
2098
- processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2098
+ processError({ issues: issue2.issues }, [...path36, ...issue2.path]);
2099
2099
  } else {
2100
- const fullpath = [...path34, ...issue2.path];
2100
+ const fullpath = [...path36, ...issue2.path];
2101
2101
  if (fullpath.length === 0) {
2102
2102
  fieldErrors._errors.push(mapper(issue2));
2103
2103
  } else {
@@ -2124,17 +2124,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2124
2124
  }
2125
2125
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2126
2126
  const result = { errors: [] };
2127
- const processError = (error52, path34 = []) => {
2127
+ const processError = (error52, path36 = []) => {
2128
2128
  var _a3, _b;
2129
2129
  for (const issue2 of error52.issues) {
2130
2130
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2131
- issue2.errors.map((issues) => processError({ issues }, [...path34, ...issue2.path]));
2131
+ issue2.errors.map((issues) => processError({ issues }, [...path36, ...issue2.path]));
2132
2132
  } else if (issue2.code === "invalid_key") {
2133
- processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2133
+ processError({ issues: issue2.issues }, [...path36, ...issue2.path]);
2134
2134
  } else if (issue2.code === "invalid_element") {
2135
- processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2135
+ processError({ issues: issue2.issues }, [...path36, ...issue2.path]);
2136
2136
  } else {
2137
- const fullpath = [...path34, ...issue2.path];
2137
+ const fullpath = [...path36, ...issue2.path];
2138
2138
  if (fullpath.length === 0) {
2139
2139
  result.errors.push(mapper(issue2));
2140
2140
  continue;
@@ -2166,8 +2166,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2166
2166
  }
2167
2167
  function toDotPath(_path) {
2168
2168
  const segs = [];
2169
- const path34 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
- for (const seg of path34) {
2169
+ const path36 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
+ for (const seg of path36) {
2171
2171
  if (typeof seg === "number")
2172
2172
  segs.push(`[${seg}]`);
2173
2173
  else if (typeof seg === "symbol")
@@ -15670,13 +15670,13 @@ function resolveRef(ref, ctx) {
15670
15670
  if (!ref.startsWith("#")) {
15671
15671
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15672
15672
  }
15673
- const path34 = ref.slice(1).split("/").filter(Boolean);
15674
- if (path34.length === 0) {
15673
+ const path36 = ref.slice(1).split("/").filter(Boolean);
15674
+ if (path36.length === 0) {
15675
15675
  return ctx.rootSchema;
15676
15676
  }
15677
15677
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15678
- if (path34[0] === defsKey) {
15679
- const key = path34[1];
15678
+ if (path36[0] === defsKey) {
15679
+ const key = path36[1];
15680
15680
  if (!key || !ctx.defs[key]) {
15681
15681
  throw new Error(`Reference not found: ${ref}`);
15682
15682
  }
@@ -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;
@@ -17753,11 +17776,11 @@ var init_tools = __esm({
17753
17776
  if (!ctx.addDocument)
17754
17777
  return "Knowledge vault tool not available.";
17755
17778
  const title = args["title"] || "New Document";
17756
- const path34 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17779
+ const path36 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17757
17780
  const content = args["content"] || "";
17758
17781
  const tags = args["tags"] || [];
17759
17782
  ctx.addDocument({
17760
- path: path34,
17783
+ path: path36,
17761
17784
  title,
17762
17785
  content,
17763
17786
  format: "markdown",
@@ -17766,7 +17789,7 @@ var init_tools = __esm({
17766
17789
  workspaceId: ctx.workspaceId
17767
17790
  });
17768
17791
  ctx.addActivity("vault", "created document", title);
17769
- return `Document "${title}" created at "${path34}".`;
17792
+ return `Document "${title}" created at "${path36}".`;
17770
17793
  }
17771
17794
  }
17772
17795
  ];
@@ -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"() {
@@ -19037,6 +19165,145 @@ var init_AgentHarness = __esm({
19037
19165
  ...this.config.memberName ? { memberName: this.config.memberName } : {}
19038
19166
  };
19039
19167
  }
19168
+ /**
19169
+ * v1.8.0: true when a tool may run concurrently with other parallel-safe
19170
+ * tools. Write/execute tools stay serial (preserve file/order safety).
19171
+ * `task` is parallel-safe (read-only sub-agents). Opt out: ZELARI_PARALLEL_TOOLS=0.
19172
+ */
19173
+ isParallelSafeTool(toolName) {
19174
+ if (process.env.ZELARI_PARALLEL_TOOLS === "0")
19175
+ return false;
19176
+ if (toolName === "task")
19177
+ return true;
19178
+ const def = this.config.toolRegistry?.get(toolName);
19179
+ if (!def) {
19180
+ if (toolName.startsWith("mcp_")) {
19181
+ const lower = toolName.toLowerCase();
19182
+ if (lower.includes("write") || lower.includes("edit") || lower.includes("delete")) {
19183
+ return false;
19184
+ }
19185
+ return true;
19186
+ }
19187
+ return false;
19188
+ }
19189
+ const perms = def.permissions ?? [];
19190
+ if (perms.includes("write") || perms.includes("execute"))
19191
+ return false;
19192
+ return true;
19193
+ }
19194
+ /**
19195
+ * Execute buffered native tool calls: consecutive parallel-safe tools run
19196
+ * via Promise.all (chunked by ZELARI_MAX_PARALLEL_TOOLS, default 6); write/
19197
+ * execute tools run one-at-a-time in order.
19198
+ */
19199
+ async executePendingTools(pending, maxToolCalls) {
19200
+ const out = new Array(pending.length);
19201
+ const maxParallel = Math.max(1, Number.parseInt(process.env.ZELARI_MAX_PARALLEL_TOOLS ?? "6", 10) || 6);
19202
+ const inflight = /* @__PURE__ */ new Map();
19203
+ const invokeOne = async (p3) => {
19204
+ if (p3.cached !== void 0) {
19205
+ return {
19206
+ content: p3.cached,
19207
+ isError: p3.skipped || p3.cached.startsWith("[skipped]"),
19208
+ durationMs: 0
19209
+ };
19210
+ }
19211
+ if (p3.skipped || !this.config.toolRegistry) {
19212
+ return {
19213
+ content: `[skipped] maxToolCallsPerTurn reached (limit=${maxToolCalls})`,
19214
+ isError: true,
19215
+ durationMs: 0
19216
+ };
19217
+ }
19218
+ const callKey = hashToolCall(p3.toolName, p3.args);
19219
+ const fromRunCache = this.toolCallCache.get(callKey);
19220
+ if (fromRunCache !== void 0) {
19221
+ return {
19222
+ content: `[duplicate call \u2014 result repeated; do not call this tool again with the same arguments]
19223
+ ${fromRunCache}`,
19224
+ isError: false,
19225
+ durationMs: 0
19226
+ };
19227
+ }
19228
+ const existing = inflight.get(callKey);
19229
+ if (existing) {
19230
+ const shared2 = await existing;
19231
+ return {
19232
+ content: `[duplicate call \u2014 result repeated; do not call this tool again with the same arguments]
19233
+ ${shared2.content}`,
19234
+ isError: false,
19235
+ durationMs: 0
19236
+ };
19237
+ }
19238
+ const startMs = Date.now();
19239
+ const prom = (async () => {
19240
+ const result = await this.config.toolRegistry.invoke(p3.toolName, p3.args, {
19241
+ cwd: this.config.cwd,
19242
+ sessionId: this.sessionId,
19243
+ signal: this.activeController?.signal
19244
+ });
19245
+ let resultStr = "";
19246
+ if (result.ok) {
19247
+ if (typeof result.value === "string")
19248
+ resultStr = result.value;
19249
+ else if (typeof result.value === "object" && result.value !== null) {
19250
+ resultStr = JSON.stringify(result.value, null, 2);
19251
+ } else
19252
+ resultStr = String(result.value);
19253
+ } else {
19254
+ resultStr = result.error;
19255
+ }
19256
+ return {
19257
+ content: resultStr,
19258
+ isError: !result.ok,
19259
+ durationMs: Date.now() - startMs
19260
+ };
19261
+ })();
19262
+ inflight.set(callKey, prom);
19263
+ const r = await prom;
19264
+ if (!r.isError)
19265
+ this.toolCallCache.set(callKey, r.content);
19266
+ return { ...r, cacheKey: callKey };
19267
+ };
19268
+ const toOut = (p3, r) => {
19269
+ const endEvent = createBrainEvent("tool_execution_end", this.sessionId, {
19270
+ toolCallId: p3.toolCallId,
19271
+ result: r.content,
19272
+ isError: r.isError,
19273
+ durationMs: r.durationMs
19274
+ });
19275
+ return {
19276
+ toolCallId: p3.toolCallId,
19277
+ content: r.content,
19278
+ isError: r.isError,
19279
+ endEvent
19280
+ // Cache already written in invokeOne; no need to re-set.
19281
+ };
19282
+ };
19283
+ let i = 0;
19284
+ while (i < pending.length) {
19285
+ const p3 = pending[i];
19286
+ if (p3.skipped || p3.cached !== void 0 || !this.isParallelSafeTool(p3.toolName)) {
19287
+ out[i] = toOut(p3, await invokeOne(p3));
19288
+ i += 1;
19289
+ continue;
19290
+ }
19291
+ let j = i;
19292
+ while (j < pending.length && !pending[j].skipped && pending[j].cached === void 0 && this.isParallelSafeTool(pending[j].toolName)) {
19293
+ j += 1;
19294
+ }
19295
+ for (let off = i; off < j; off += maxParallel) {
19296
+ const end = Math.min(off + maxParallel, j);
19297
+ const slice = pending.slice(off, end);
19298
+ const results = await Promise.all(slice.map((item) => invokeOne(item)));
19299
+ for (let k = 0; k < results.length; k++) {
19300
+ out[off + k] = toOut(slice[k], results[k]);
19301
+ }
19302
+ }
19303
+ i = j;
19304
+ }
19305
+ return out;
19306
+ }
19040
19307
  /**
19041
19308
  * Cancel the in-flight run. Events drain until end of stream.
19042
19309
  *
@@ -19254,6 +19521,7 @@ var init_AgentHarness = __esm({
19254
19521
  let turnText = "";
19255
19522
  const turnToolCalls = [];
19256
19523
  const turnToolResults = [];
19524
+ const pendingNativeTools = [];
19257
19525
  for await (const delta of stream) {
19258
19526
  if (this.cancelled) {
19259
19527
  const cancelEvent = createBrainEvent("error", this.sessionId, {
@@ -19288,73 +19556,52 @@ var init_AgentHarness = __esm({
19288
19556
  this.emit(toolStartEvent);
19289
19557
  yield toolStartEvent;
19290
19558
  const skipped = typeof maxToolCalls === "number" && toolCallsThisTurn > maxToolCalls;
19291
- if (this.config.toolRegistry && !skipped) {
19559
+ if (!this.config.toolRegistry) {
19560
+ } else if (skipped) {
19561
+ pendingNativeTools.push({
19562
+ toolCallId: delta.toolCallId,
19563
+ toolName: delta.toolName,
19564
+ args: delta.args,
19565
+ skipped: true
19566
+ });
19567
+ } else {
19292
19568
  const callKey = hashToolCall(delta.toolName, delta.args);
19293
19569
  const cached2 = this.toolCallCache.get(callKey);
19294
19570
  if (cached2 !== void 0) {
19295
- const dupResult = `[duplicate call \u2014 result repeated; do not call this tool again with the same arguments]
19296
- ${cached2}`;
19297
- const endEvent = createBrainEvent("tool_execution_end", this.sessionId, {
19298
- toolCallId: delta.toolCallId,
19299
- result: dupResult,
19300
- isError: false,
19301
- durationMs: 0
19302
- });
19303
- this.emit(endEvent);
19304
- yield endEvent;
19305
- turnToolResults.push({
19571
+ pendingNativeTools.push({
19306
19572
  toolCallId: delta.toolCallId,
19307
- content: dupResult
19573
+ toolName: delta.toolName,
19574
+ args: delta.args,
19575
+ skipped: false,
19576
+ cached: `[duplicate call \u2014 result repeated; do not call this tool again with the same arguments]
19577
+ ${cached2}`
19308
19578
  });
19309
19579
  } else {
19310
- const startMs = Date.now();
19311
- const result = await this.config.toolRegistry.invoke(delta.toolName, delta.args, {
19312
- cwd: this.config.cwd,
19313
- sessionId: this.sessionId,
19314
- signal: this.activeController?.signal
19315
- });
19316
- let resultStr = "";
19317
- if (result.ok) {
19318
- if (typeof result.value === "string") {
19319
- resultStr = result.value;
19320
- } else if (typeof result.value === "object" && result.value !== null) {
19321
- resultStr = JSON.stringify(result.value, null, 2);
19322
- } else {
19323
- resultStr = String(result.value);
19324
- }
19325
- } else {
19326
- resultStr = result.error;
19327
- }
19328
- const endEvent = createBrainEvent("tool_execution_end", this.sessionId, {
19329
- toolCallId: delta.toolCallId,
19330
- result: resultStr,
19331
- isError: !result.ok,
19332
- durationMs: Date.now() - startMs
19333
- });
19334
- this.emit(endEvent);
19335
- yield endEvent;
19336
- turnToolResults.push({
19580
+ pendingNativeTools.push({
19337
19581
  toolCallId: delta.toolCallId,
19338
- content: resultStr
19582
+ toolName: delta.toolName,
19583
+ args: delta.args,
19584
+ skipped: false
19339
19585
  });
19340
- this.toolCallCache.set(callKey, resultStr);
19341
19586
  }
19342
- } else if (skipped) {
19343
- const endEvent = createBrainEvent("tool_execution_end", this.sessionId, {
19344
- toolCallId: delta.toolCallId,
19345
- result: `[skipped] maxToolCallsPerTurn reached (limit=${maxToolCalls})`,
19346
- isError: true,
19347
- durationMs: 0
19348
- });
19349
- this.emit(endEvent);
19350
- yield endEvent;
19351
- turnToolResults.push({
19352
- toolCallId: delta.toolCallId,
19353
- content: `[skipped] maxToolCallsPerTurn reached (limit=${maxToolCalls})`
19354
- });
19355
19587
  }
19356
19588
  } else if (delta.kind === "finish") {
19357
19589
  finishRef.value = delta.reason;
19590
+ if (pendingNativeTools.length > 0) {
19591
+ const executed = await this.executePendingTools(pendingNativeTools, maxToolCalls);
19592
+ for (const item of executed) {
19593
+ this.emit(item.endEvent);
19594
+ yield item.endEvent;
19595
+ turnToolResults.push({
19596
+ toolCallId: item.toolCallId,
19597
+ content: item.content
19598
+ });
19599
+ if (item.cacheKey && item.content && !item.isError) {
19600
+ this.toolCallCache.set(item.cacheKey, item.content);
19601
+ }
19602
+ }
19603
+ pendingNativeTools.length = 0;
19604
+ }
19358
19605
  const textTools = parseTextToolCalls(turnText);
19359
19606
  const nativeWriteRan = turnToolCalls.some((t) => t.name === "write_file" || t.name === "edit_file");
19360
19607
  const textWriteTools = textTools.filter((t) => t.name === "write_file" || t.name === "edit_file");
@@ -21705,12 +21952,44 @@ var init_tools4 = __esm({
21705
21952
  });
21706
21953
 
21707
21954
  // src/cli/browser/driver.ts
21708
- async function runBrowserCheck(options, loader = defaultPlaywrightLoader) {
21955
+ import { createRequire } from "node:module";
21956
+ import path21 from "node:path";
21957
+ import { pathToFileURL } from "node:url";
21958
+ function asPlaywright(mod) {
21959
+ if (!mod || typeof mod !== "object") return null;
21960
+ const m = mod;
21961
+ if (m.chromium && typeof m.chromium.launch === "function") return m;
21962
+ const d = m.default;
21963
+ if (d && d.chromium && typeof d.chromium.launch === "function") return d;
21964
+ return null;
21965
+ }
21966
+ async function loadPlaywright(cwd) {
21967
+ const base = cwd && cwd.length > 0 ? path21.resolve(cwd) : void 0;
21968
+ if (base) {
21969
+ try {
21970
+ const req = createRequire(path21.join(base, "package.json"));
21971
+ const resolved = req.resolve("playwright");
21972
+ const mod = await import(pathToFileURL(resolved).href);
21973
+ const pw = asPlaywright(mod);
21974
+ if (pw) return pw;
21975
+ } catch {
21976
+ }
21977
+ }
21978
+ try {
21979
+ const pkg = "playwright";
21980
+ const mod = await import(pkg);
21981
+ return asPlaywright(mod);
21982
+ } catch {
21983
+ return null;
21984
+ }
21985
+ }
21986
+ async function runBrowserCheck(options, loader) {
21709
21987
  const consoleErrors = [];
21710
21988
  const pageErrors = [];
21711
21989
  const failedRequests = [];
21712
21990
  const base = { ok: false, consoleErrors, pageErrors, failedRequests };
21713
- const pw = await loader();
21991
+ const resolve = loader ?? (() => loadPlaywright(options.cwd ?? process.cwd()));
21992
+ const pw = await resolve();
21714
21993
  if (!pw) {
21715
21994
  return {
21716
21995
  ...base,
@@ -21786,25 +22065,14 @@ async function runBrowserCheck(options, loader = defaultPlaywrightLoader) {
21786
22065
  }
21787
22066
  }
21788
22067
  }
21789
- var defaultPlaywrightLoader;
21790
22068
  var init_driver = __esm({
21791
22069
  "src/cli/browser/driver.ts"() {
21792
22070
  "use strict";
21793
- defaultPlaywrightLoader = async () => {
21794
- try {
21795
- const pkg = "playwright";
21796
- const mod = await import(pkg);
21797
- if (mod && mod.chromium && typeof mod.chromium.launch === "function") return mod;
21798
- return null;
21799
- } catch {
21800
- return null;
21801
- }
21802
- };
21803
22071
  }
21804
22072
  });
21805
22073
 
21806
22074
  // src/cli/browser/tools.ts
21807
- import path21 from "node:path";
22075
+ import path22 from "node:path";
21808
22076
  import os7 from "node:os";
21809
22077
  function createBrowserTool(deps = {}) {
21810
22078
  return {
@@ -21819,13 +22087,14 @@ function createBrowserTool(deps = {}) {
21819
22087
  waitForSelector: external_exports.string().optional().describe("Assert this CSS selector is present after actions."),
21820
22088
  screenshot: external_exports.boolean().optional().describe("Save a screenshot (default true).")
21821
22089
  }),
21822
- execute: async (args) => {
22090
+ execute: async (args, ctx) => {
21823
22091
  const a = args;
21824
22092
  const dir = deps.screenshotDir ?? os7.tmpdir();
21825
- const screenshotPath = a.screenshot === false ? void 0 : path21.join(dir, `zelari-browser-${Date.now()}.png`);
22093
+ const screenshotPath = a.screenshot === false ? void 0 : path22.join(dir, `zelari-browser-${Date.now()}.png`);
21826
22094
  const result = await runBrowserCheck(
21827
22095
  {
21828
22096
  url: a.url,
22097
+ cwd: ctx.cwd,
21829
22098
  ...a.actions ? { actions: a.actions } : {},
21830
22099
  ...a.waitForSelector ? { waitForSelector: a.waitForSelector } : {},
21831
22100
  ...screenshotPath ? { screenshotPath } : {}
@@ -21890,7 +22159,7 @@ function createBuiltinToolRegistry(options = {}) {
21890
22159
  const safeFetchUrl = wrapWithAudit(fetchUrlTool, audit, sessionId);
21891
22160
  const safeWebSearch = wrapWithAudit(webSearchTool, audit, sessionId);
21892
22161
  const registry3 = new ToolRegistry();
21893
- const readOnly = options.readOnly === true;
22162
+ const readOnly = options.readOnly === true || options.planMode === true;
21894
22163
  registry3.register(safeReadFile);
21895
22164
  registry3.register(safeGrepContent);
21896
22165
  registry3.register(safeListFiles);
@@ -23047,11 +23316,11 @@ var init_synthesisAudit = __esm({
23047
23316
  import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
23048
23317
  import { join as join2 } from "node:path";
23049
23318
  function loadNfrSpec(zelariRoot) {
23050
- const path34 = join2(zelariRoot, "nfr-spec.json");
23051
- if (!existsSync10(path34))
23319
+ const path36 = join2(zelariRoot, "nfr-spec.json");
23320
+ if (!existsSync10(path36))
23052
23321
  return null;
23053
23322
  try {
23054
- const raw = JSON.parse(readFileSync8(path34, "utf8"));
23323
+ const raw = JSON.parse(readFileSync8(path36, "utf8"));
23055
23324
  if (raw.version !== 1 || !Array.isArray(raw.targets))
23056
23325
  return null;
23057
23326
  return raw;
@@ -23793,11 +24062,19 @@ function parseClarificationRequest(text) {
23793
24062
  }
23794
24063
  }
23795
24064
  function parseThinking(text) {
23796
- const match = text.match(/<think>([\s\S]*?)<\/think>/);
23797
- 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() : "";
23798
24070
  }
23799
- function cleanAgentContent(text) {
23800
- 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();
23801
24078
  }
23802
24079
  function restrictImplementationWrites(toolNames, opts) {
23803
24080
  if (opts.runMode !== "implementation" || opts.isImplementer)
@@ -24035,6 +24312,19 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24035
24312
  role: "system",
24036
24313
  content: `(Agent ${agent.name} asked: ${clarification.question})`
24037
24314
  });
24315
+ if (callbacks.onClarification && clarification.choices && clarification.choices.length >= 2) {
24316
+ try {
24317
+ const answer = await callbacks.onClarification(clarification);
24318
+ if (answer && answer.trim()) {
24319
+ agentOutputs.push({
24320
+ name: "User",
24321
+ role: "user",
24322
+ content: `Answer to clarifying question "${clarification.question}": ${answer.trim()}`
24323
+ });
24324
+ }
24325
+ } catch {
24326
+ }
24327
+ }
24038
24328
  }
24039
24329
  }
24040
24330
  if (oracle && !completedIds.has(oracle.id)) {
@@ -25263,9 +25553,9 @@ var init_types2 = __esm({
25263
25553
  import { readFileSync as readFileSync13 } from "node:fs";
25264
25554
  import { join as join8 } from "node:path";
25265
25555
  function readLessonsDeduped(zelariRoot) {
25266
- const path34 = join8(zelariRoot, LESSONS_FILE);
25556
+ const path36 = join8(zelariRoot, LESSONS_FILE);
25267
25557
  try {
25268
- const raw = readFileSync13(path34, "utf8");
25558
+ const raw = readFileSync13(path36, "utf8");
25269
25559
  const byId = /* @__PURE__ */ new Map();
25270
25560
  for (const line of raw.split(/\r?\n/)) {
25271
25561
  if (!line.trim())
@@ -25366,8 +25656,8 @@ function keywordsFrom(check2, signature) {
25366
25656
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
25367
25657
  }
25368
25658
  function writeLesson(zelariRoot, lesson) {
25369
- const path34 = join9(zelariRoot, LESSONS_FILE);
25370
- appendFileSync2(path34, `${JSON.stringify(lesson)}
25659
+ const path36 = join9(zelariRoot, LESSONS_FILE);
25660
+ appendFileSync2(path36, `${JSON.stringify(lesson)}
25371
25661
  `, "utf8");
25372
25662
  }
25373
25663
  function findSimilar(lessons, signature) {
@@ -25901,6 +26191,273 @@ var init_council = __esm({
25901
26191
  }
25902
26192
  });
25903
26193
 
26194
+ // src/cli/utils/envNumber.ts
26195
+ function envNumber(raw, opts) {
26196
+ const { default: def, min, max } = opts;
26197
+ if (raw === void 0 || raw === null) return def;
26198
+ const trimmed = raw.trim();
26199
+ if (trimmed.length === 0) return def;
26200
+ if (trimmed.toLowerCase() === "undefined" || trimmed.toLowerCase() === "null") return def;
26201
+ const parsed = Number.parseInt(trimmed, 10);
26202
+ if (!Number.isFinite(parsed)) return def;
26203
+ const absStr = `${Math.abs(parsed)}`;
26204
+ const body = trimmed.replace(/^[-+]/, "").replace(/^0+(?=\d)/, "");
26205
+ if (body !== absStr) return def;
26206
+ const firstChar = trimmed[0];
26207
+ const expectedSign = parsed < 0 ? "-" : /[0-9+]/.test(firstChar ?? "");
26208
+ const actualSign = parsed < 0 ? firstChar === "-" : firstChar !== "-";
26209
+ if (!expectedSign || !actualSign) return def;
26210
+ let clamped = parsed;
26211
+ if (min !== void 0 && clamped < min) clamped = min;
26212
+ if (max !== void 0 && clamped > max) clamped = max;
26213
+ return clamped;
26214
+ }
26215
+ var init_envNumber = __esm({
26216
+ "src/cli/utils/envNumber.ts"() {
26217
+ "use strict";
26218
+ }
26219
+ });
26220
+
26221
+ // src/cli/hooks/historyCompaction.ts
26222
+ function resolveMaxMessages(opts) {
26223
+ const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
26224
+ const turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
26225
+ if (turns <= 0) return 0;
26226
+ return turns * 4;
26227
+ }
26228
+ function findValidCutIndex(messages, naiveCut) {
26229
+ let cut = naiveCut;
26230
+ while (cut < messages.length) {
26231
+ const kept = messages.slice(cut);
26232
+ const declared = /* @__PURE__ */ new Set();
26233
+ for (const m of kept) {
26234
+ if (m.role === "assistant" && m.toolCalls) {
26235
+ for (const tc of m.toolCalls) declared.add(tc.id);
26236
+ }
26237
+ }
26238
+ let moved = false;
26239
+ for (let k = 0; k < kept.length; k++) {
26240
+ const m = kept[k];
26241
+ if (m.role === "tool" && m.toolCallId && !declared.has(m.toolCallId)) {
26242
+ for (let j = cut - 1; j >= 0; j--) {
26243
+ const prev2 = messages[j];
26244
+ if (prev2.role === "assistant" && prev2.toolCalls && prev2.toolCalls.some((tc) => tc.id === m.toolCallId)) {
26245
+ cut = j;
26246
+ moved = true;
26247
+ break;
26248
+ }
26249
+ }
26250
+ break;
26251
+ }
26252
+ }
26253
+ if (!moved) break;
26254
+ }
26255
+ return cut;
26256
+ }
26257
+ function compactHistory(messages, opts) {
26258
+ const maxMessages = resolveMaxMessages(opts);
26259
+ if (maxMessages === 0) return [];
26260
+ if (messages.length <= maxMessages * 2) return messages;
26261
+ const naiveCut = messages.length - maxMessages;
26262
+ const cut = findValidCutIndex(messages, naiveCut);
26263
+ const dropped = cut;
26264
+ if (dropped === 0) return messages;
26265
+ const kept = messages.slice(cut);
26266
+ const summary = {
26267
+ role: "system",
26268
+ content: `${COMPACT_MARKER} ${dropped} earlier message(s) dropped.`
26269
+ };
26270
+ return [summary, ...kept];
26271
+ }
26272
+ var COMPACT_MARKER;
26273
+ var init_historyCompaction = __esm({
26274
+ "src/cli/hooks/historyCompaction.ts"() {
26275
+ "use strict";
26276
+ init_envNumber();
26277
+ COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
26278
+ }
26279
+ });
26280
+
26281
+ // src/cli/hooks/conversationContext.ts
26282
+ var conversationContext_exports = {};
26283
+ __export(conversationContext_exports, {
26284
+ _resetConversationContextForTests: () => _resetConversationContextForTests,
26285
+ appendMessages: () => appendMessages,
26286
+ clearHistory: () => clearHistory,
26287
+ compactInPlace: () => compactInPlace,
26288
+ formatHistoryForCouncil: () => formatHistoryForCouncil,
26289
+ getHistory: () => getHistory,
26290
+ getLastClarification: () => getLastClarification,
26291
+ hydrateHistory: () => hydrateHistory,
26292
+ maybeAnchorShortAnswer: () => maybeAnchorShortAnswer,
26293
+ serializeHistory: () => serializeHistory,
26294
+ setHistory: () => setHistory,
26295
+ setLastClarification: () => setLastClarification
26296
+ });
26297
+ function getHistory() {
26298
+ return history;
26299
+ }
26300
+ function setHistory(messages) {
26301
+ history = [...messages];
26302
+ }
26303
+ function compactInPlace() {
26304
+ history = compactHistory(history);
26305
+ }
26306
+ function appendMessages(msgs) {
26307
+ if (msgs.length === 0) return;
26308
+ history = history.concat(msgs);
26309
+ }
26310
+ function clearHistory() {
26311
+ history = [];
26312
+ lastClarification = null;
26313
+ }
26314
+ function serializeHistory() {
26315
+ return [...history];
26316
+ }
26317
+ function hydrateHistory(messages) {
26318
+ history = [...messages];
26319
+ }
26320
+ function getLastClarification() {
26321
+ return lastClarification;
26322
+ }
26323
+ function setLastClarification(c) {
26324
+ lastClarification = c ? { question: c.question, choices: c.choices, at: Date.now() } : null;
26325
+ }
26326
+ function maybeAnchorShortAnswer(userText) {
26327
+ const clar = lastClarification;
26328
+ if (!clar) return null;
26329
+ const trimmed = userText.trim();
26330
+ if (!trimmed) return null;
26331
+ if (trimmed.length > 80 || trimmed.includes("\n")) return null;
26332
+ const lower = trimmed.toLowerCase();
26333
+ const choices = clar.choices;
26334
+ const matched = choices.find((c) => c.toLowerCase() === lower) ?? choices.find((c) => c.toLowerCase().startsWith(lower)) ?? choices.find((c) => lower.startsWith(c.toLowerCase().slice(0, Math.min(4, c.length))));
26335
+ let choiceLabel = matched ?? null;
26336
+ if (!choiceLabel && /^\d{1,2}$/.test(trimmed)) {
26337
+ const idx = Number.parseInt(trimmed, 10) - 1;
26338
+ if (idx >= 0 && idx < choices.length) choiceLabel = choices[idx] ?? null;
26339
+ }
26340
+ if (!choiceLabel && trimmed.length > 24) return null;
26341
+ const picked = choiceLabel ?? trimmed;
26342
+ return `The user is answering your previous clarifying question.
26343
+ Question: ${clar.question}
26344
+ Choices were: ${choices.join(" | ")}
26345
+ User's answer: ${picked}
26346
+ Proceed using this answer; do not re-ask the same question unless the answer is still ambiguous.`;
26347
+ }
26348
+ function formatHistoryForCouncil(maxTurns = 4) {
26349
+ if (history.length === 0) return "";
26350
+ const lines = [];
26351
+ let turns = 0;
26352
+ const chunk = [];
26353
+ for (let i = history.length - 1; i >= 0 && turns < maxTurns; i--) {
26354
+ const m = history[i];
26355
+ if (m.role === "user") {
26356
+ chunk.push(`User: ${truncate(m.content, 400)}`);
26357
+ turns += 1;
26358
+ } else if (m.role === "assistant" && m.content.trim()) {
26359
+ chunk.push(`Assistant: ${truncate(m.content, 600)}`);
26360
+ }
26361
+ }
26362
+ if (chunk.length === 0) return "";
26363
+ lines.push("## Prior conversation (rolling context)");
26364
+ lines.push(...chunk.reverse());
26365
+ return lines.join("\n");
26366
+ }
26367
+ function truncate(s, max) {
26368
+ const t = s.replace(/\s+/g, " ").trim();
26369
+ if (t.length <= max) return t;
26370
+ return `${t.slice(0, max - 1)}\u2026`;
26371
+ }
26372
+ function _resetConversationContextForTests() {
26373
+ history = [];
26374
+ lastClarification = null;
26375
+ }
26376
+ var history, lastClarification;
26377
+ var init_conversationContext = __esm({
26378
+ "src/cli/hooks/conversationContext.ts"() {
26379
+ "use strict";
26380
+ init_historyCompaction();
26381
+ history = [];
26382
+ lastClarification = null;
26383
+ }
26384
+ });
26385
+
26386
+ // src/cli/phaseState.ts
26387
+ var phaseState_exports = {};
26388
+ __export(phaseState_exports, {
26389
+ _resetPhaseForTests: () => _resetPhaseForTests,
26390
+ getPhase: () => getPhase,
26391
+ setPhase: () => setPhase
26392
+ });
26393
+ function getPhase() {
26394
+ return phase;
26395
+ }
26396
+ function setPhase(next) {
26397
+ phase = next;
26398
+ }
26399
+ function _resetPhaseForTests() {
26400
+ phase = "build";
26401
+ }
26402
+ var phase;
26403
+ var init_phaseState = __esm({
26404
+ "src/cli/phaseState.ts"() {
26405
+ "use strict";
26406
+ phase = "build";
26407
+ }
26408
+ });
26409
+
26410
+ // src/cli/phase.ts
26411
+ var phase_exports = {};
26412
+ __export(phase_exports, {
26413
+ PHASES: () => PHASES,
26414
+ PLAN_ALLOWED_WRITE_TOOLS: () => PLAN_ALLOWED_WRITE_TOOLS,
26415
+ PLAN_BLOCKED_TOOLS: () => PLAN_BLOCKED_TOOLS,
26416
+ describePhase: () => describePhase,
26417
+ nextPhase: () => nextPhase,
26418
+ parsePhase: () => parsePhase
26419
+ });
26420
+ function parsePhase(input) {
26421
+ const v = input.trim().toLowerCase();
26422
+ return PHASES.includes(v) ? v : null;
26423
+ }
26424
+ function nextPhase(current) {
26425
+ return current === "plan" ? "build" : "plan";
26426
+ }
26427
+ function describePhase(phase2) {
26428
+ switch (phase2) {
26429
+ case "plan":
26430
+ return "plan \u2014 explore & design only (no project writes; plan files allowed)";
26431
+ default:
26432
+ return "build \u2014 implement with full tools";
26433
+ }
26434
+ }
26435
+ var PHASES, PLAN_ALLOWED_WRITE_TOOLS, PLAN_BLOCKED_TOOLS;
26436
+ var init_phase = __esm({
26437
+ "src/cli/phase.ts"() {
26438
+ "use strict";
26439
+ PHASES = ["plan", "build"];
26440
+ PLAN_ALLOWED_WRITE_TOOLS = /* @__PURE__ */ new Set([
26441
+ // Workspace plan/docs — intentional plan-mode outputs
26442
+ "createPlan",
26443
+ "createTask",
26444
+ "updateTask",
26445
+ "createMilestone",
26446
+ "createDocument",
26447
+ "createDecision",
26448
+ "linkDocuments"
26449
+ // Soft writes that only touch .zelari / plan paths are still gated in
26450
+ // toolRegistry by path when needed; write_file/edit_file stay DENIED.
26451
+ ]);
26452
+ PLAN_BLOCKED_TOOLS = /* @__PURE__ */ new Set([
26453
+ "write_file",
26454
+ "edit_file",
26455
+ "apply_diff",
26456
+ "bash"
26457
+ ]);
26458
+ }
26459
+ });
26460
+
25904
26461
  // src/cli/workspace/paths.ts
25905
26462
  import {
25906
26463
  mkdirSync as mkdirSync6,
@@ -26082,12 +26639,12 @@ function buildPlanSummary(projectRoot = process.cwd(), options) {
26082
26639
  scopedOpen = [...inScope, ...neutral];
26083
26640
  }
26084
26641
  }
26085
- for (const phase of [...phases].sort(
26642
+ for (const phase2 of [...phases].sort(
26086
26643
  (a, b) => (a.order ?? 0) - (b.order ?? 0)
26087
26644
  )) {
26088
- const phaseTasks = scopedOpen.filter((t) => t.phaseId === phase.id);
26089
- parts.push("", `## ${phase.order ?? "?"}. ${phase.name ?? phase.id}`);
26090
- if (phase.description) parts.push(phase.description);
26645
+ const phaseTasks = scopedOpen.filter((t) => t.phaseId === phase2.id);
26646
+ parts.push("", `## ${phase2.order ?? "?"}. ${phase2.name ?? phase2.id}`);
26647
+ if (phase2.description) parts.push(phase2.description);
26091
26648
  for (const t of phaseTasks.slice(0, PLAN_SUMMARY_MAX_TASKS)) {
26092
26649
  parts.push(formatTaskLine(t));
26093
26650
  }
@@ -26508,28 +27065,28 @@ var init_storage = __esm({
26508
27065
  VALID_SCALARS = /^(true|false|null|~)$/i;
26509
27066
  Storage = class {
26510
27067
  /** Read a Markdown file with frontmatter. Throws if not found. */
26511
- read(path34) {
26512
- if (!existsSync15(path34)) {
26513
- throw new Error(`File not found: ${path34}`);
27068
+ read(path36) {
27069
+ if (!existsSync15(path36)) {
27070
+ throw new Error(`File not found: ${path36}`);
26514
27071
  }
26515
- const md = readFileSync15(path34, "utf8");
27072
+ const md = readFileSync15(path36, "utf8");
26516
27073
  return parseFrontmatter(md);
26517
27074
  }
26518
27075
  /** Read a Markdown file; returns null if not found. */
26519
- readIfExists(path34) {
26520
- if (!existsSync15(path34)) return null;
26521
- return this.read(path34);
27076
+ readIfExists(path36) {
27077
+ if (!existsSync15(path36)) return null;
27078
+ return this.read(path36);
26522
27079
  }
26523
27080
  /**
26524
27081
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
26525
27082
  * The meta object is serialized as YAML frontmatter; body as Markdown.
26526
27083
  */
26527
- write(path34, meta3, body) {
26528
- mkdirSync7(dirname2(path34), { recursive: true });
26529
- const tmp = path34 + ".tmp-" + process.pid;
27084
+ write(path36, meta3, body) {
27085
+ mkdirSync7(dirname2(path36), { recursive: true });
27086
+ const tmp = path36 + ".tmp-" + process.pid;
26530
27087
  const md = serializeFrontmatter(meta3, body);
26531
27088
  writeFileSync11(tmp, md, "utf8");
26532
- renameSync2(tmp, path34);
27089
+ renameSync2(tmp, path36);
26533
27090
  }
26534
27091
  /** List all .md files in a directory (non-recursive). */
26535
27092
  listMarkdown(dir) {
@@ -26607,8 +27164,8 @@ function readPlan(ctx) {
26607
27164
  } catch {
26608
27165
  }
26609
27166
  }
26610
- const path34 = workspaceFile(ctx.rootDir, "plan");
26611
- const doc = ctx.storage.readIfExists(path34);
27167
+ const path36 = workspaceFile(ctx.rootDir, "plan");
27168
+ const doc = ctx.storage.readIfExists(path36);
26612
27169
  if (!doc) return { phases: [], tasks: [], milestones: [] };
26613
27170
  const meta3 = doc.meta;
26614
27171
  return {
@@ -26642,11 +27199,11 @@ function renderPlanBody(summary) {
26642
27199
  if (summary.phases.length === 0) {
26643
27200
  lines.push(`_(none yet)_`);
26644
27201
  } else {
26645
- for (const phase of [...summary.phases].sort(
27202
+ for (const phase2 of [...summary.phases].sort(
26646
27203
  (a, b) => (a.order ?? 0) - (b.order ?? 0)
26647
27204
  )) {
26648
27205
  lines.push(
26649
- `### ${phase.order ?? "?"}. ${phase.id} \`${phase.color ?? ""}\``
27206
+ `### ${phase2.order ?? "?"}. ${phase2.id} \`${phase2.color ?? ""}\``
26650
27207
  );
26651
27208
  lines.push("");
26652
27209
  }
@@ -26773,7 +27330,7 @@ function addMilestoneRecord(ctx, summary, input) {
26773
27330
  dueDate: input.dueDate,
26774
27331
  targetVersion: version2
26775
27332
  });
26776
- const path34 = join14(ctx.rootDir, "milestones", `${id}.md`);
27333
+ const path36 = join14(ctx.rootDir, "milestones", `${id}.md`);
26777
27334
  const meta3 = {
26778
27335
  kind: "milestone",
26779
27336
  id,
@@ -26790,7 +27347,7 @@ function addMilestoneRecord(ctx, summary, input) {
26790
27347
  `Target version: ${version2}`,
26791
27348
  ""
26792
27349
  ].join("\n");
26793
- ctx.storage.write(path34, meta3, body);
27350
+ ctx.storage.write(path36, meta3, body);
26794
27351
  return { id, created: true };
26795
27352
  }
26796
27353
  function readPlanSummary(ctx) {
@@ -26994,7 +27551,7 @@ function addIdeaStub(ctx) {
26994
27551
  const tags = args["tags"] ?? [];
26995
27552
  const category = args["category"] ?? "General";
26996
27553
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
26997
- const path34 = workspaceArtifact(ctx.rootDir, "decisions", id);
27554
+ const path36 = workspaceArtifact(ctx.rootDir, "decisions", id);
26998
27555
  const meta3 = {
26999
27556
  kind: "adr",
27000
27557
  status: "proposed",
@@ -27020,7 +27577,7 @@ function addIdeaStub(ctx) {
27020
27577
  ...consequences.map((c) => `- ${c}`),
27021
27578
  ""
27022
27579
  ].join("\n");
27023
- ctx.storage.write(path34, meta3, body);
27580
+ ctx.storage.write(path36, meta3, body);
27024
27581
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
27025
27582
  });
27026
27583
  }
@@ -27102,14 +27659,14 @@ function createDocumentStub(ctx) {
27102
27659
  ctx.storage.write(risksPath, riskMeta, content);
27103
27660
  return `Document "${title}" created at risks.md (workspace root).`;
27104
27661
  }
27105
- const path34 = workspaceArtifact(ctx.rootDir, "docs", slug);
27662
+ const path36 = workspaceArtifact(ctx.rootDir, "docs", slug);
27106
27663
  const meta3 = {
27107
27664
  kind: "doc",
27108
27665
  id: slug,
27109
27666
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
27110
27667
  tags
27111
27668
  };
27112
- ctx.storage.write(path34, meta3, content);
27669
+ ctx.storage.write(path36, meta3, content);
27113
27670
  return `Document "${title}" created at docs/${slug}.md.`;
27114
27671
  });
27115
27672
  }
@@ -27325,18 +27882,18 @@ __export(updater_exports, {
27325
27882
  performUpdate: () => performUpdate,
27326
27883
  resolveBundledNpmCli: () => resolveBundledNpmCli
27327
27884
  });
27328
- import { createRequire } from "node:module";
27885
+ import { createRequire as createRequire2 } from "node:module";
27329
27886
  import { spawn as spawn4 } from "node:child_process";
27330
27887
  import { existsSync as existsSync17 } from "node:fs";
27331
- import path22 from "node:path";
27888
+ import path23 from "node:path";
27332
27889
  import { fileURLToPath } from "node:url";
27333
27890
  function resolveBundledNpmCli(execPath = process.execPath) {
27334
- const dir = path22.dirname(execPath);
27891
+ const dir = path23.dirname(execPath);
27335
27892
  const candidates = [
27336
27893
  // Windows: C:\...\node.exe → C:\...\node_modules\npm\bin\npm-cli.js
27337
- path22.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
27894
+ path23.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
27338
27895
  // POSIX: <prefix>/bin/node → <prefix>/lib/node_modules/npm/bin/npm-cli.js
27339
- path22.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
27896
+ path23.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
27340
27897
  ];
27341
27898
  for (const candidate of candidates) {
27342
27899
  try {
@@ -27353,7 +27910,7 @@ function looksLikeBrokenShim(exitCode, output) {
27353
27910
  }
27354
27911
  function getCurrentVersion() {
27355
27912
  try {
27356
- const pkgPath = path22.resolve(__dirname2, "..", "..", "package.json");
27913
+ const pkgPath = path23.resolve(__dirname2, "..", "..", "package.json");
27357
27914
  const pkg = require2(pkgPath);
27358
27915
  return pkg.version;
27359
27916
  } catch {
@@ -27464,8 +28021,8 @@ var init_updater = __esm({
27464
28021
  "src/cli/updater.ts"() {
27465
28022
  "use strict";
27466
28023
  init_cmdline();
27467
- require2 = createRequire(import.meta.url);
27468
- __dirname2 = path22.dirname(fileURLToPath(import.meta.url));
28024
+ require2 = createRequire2(import.meta.url);
28025
+ __dirname2 = path23.dirname(fileURLToPath(import.meta.url));
27469
28026
  REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
27470
28027
  }
27471
28028
  });
@@ -27870,6 +28427,9 @@ async function* dispatchCouncil(userMessage, options) {
27870
28427
  if (options.onCouncilStatus) {
27871
28428
  callbacks.onCouncilStatus = options.onCouncilStatus;
27872
28429
  }
28430
+ if (options.onClarification) {
28431
+ callbacks.onClarification = options.onClarification;
28432
+ }
27873
28433
  yield* runCouncilPure(userMessage, config2, callbacks);
27874
28434
  }
27875
28435
  var CouncilDispatchError;
@@ -27903,10 +28463,10 @@ import { createHash as createHash3 } from "node:crypto";
27903
28463
  import { join as join17 } from "node:path";
27904
28464
  import { readFile as readFile2 } from "node:fs/promises";
27905
28465
  async function readPackageJson2(projectRoot) {
27906
- const path34 = join17(projectRoot, "package.json");
27907
- if (!existsSync20(path34)) return null;
28466
+ const path36 = join17(projectRoot, "package.json");
28467
+ if (!existsSync20(path36)) return null;
27908
28468
  try {
27909
- return JSON.parse(await readFile2(path34, "utf8"));
28469
+ return JSON.parse(await readFile2(path36, "utf8"));
27910
28470
  } catch {
27911
28471
  return null;
27912
28472
  }
@@ -27988,9 +28548,9 @@ async function genBuild(ctx) {
27988
28548
  ].join("\n");
27989
28549
  }
27990
28550
  async function genOpenQuestions(ctx) {
27991
- const path34 = join17(ctx.rootDir, "risks.md");
27992
- if (!existsSync20(path34)) return "_No open questions._";
27993
- const content = readFileSync19(path34, "utf8");
28551
+ const path36 = join17(ctx.rootDir, "risks.md");
28552
+ if (!existsSync20(path36)) return "_No open questions._";
28553
+ const content = readFileSync19(path36, "utf8");
27994
28554
  const lines = content.split("\n");
27995
28555
  const questions = [];
27996
28556
  let currentTitle = "";
@@ -28141,9 +28701,9 @@ var init_agentsMd = __esm({
28141
28701
  });
28142
28702
 
28143
28703
  // src/cli/workspace/completeDesign.ts
28144
- function curatedTasksForPhase(phase) {
28145
- const label = (phase.name ?? phase.id).trim();
28146
- const scope = phase.description?.trim() ? ` Scope: ${phase.description.trim()}` : "";
28704
+ function curatedTasksForPhase(phase2) {
28705
+ const label = (phase2.name ?? phase2.id).trim();
28706
+ const scope = phase2.description?.trim() ? ` Scope: ${phase2.description.trim()}` : "";
28147
28707
  return [
28148
28708
  {
28149
28709
  title: `Specify ${label} deliverables`,
@@ -28190,15 +28750,15 @@ async function runBuiltinCompleteDesign(ctx) {
28190
28750
  const createMilestone = stubs.find((s) => s.name === "createMilestone");
28191
28751
  const stubCtx = ctx;
28192
28752
  let tasksAdded = 0;
28193
- for (const phase of summary.phases) {
28194
- const existingTasks = summary.tasks.filter((t) => t.phaseId === phase.id);
28753
+ for (const phase2 of summary.phases) {
28754
+ const existingTasks = summary.tasks.filter((t) => t.phaseId === phase2.id);
28195
28755
  if (existingTasks.length >= MIN_TASKS_PER_PHASE) continue;
28196
28756
  const existingTitles = new Set(existingTasks.map((t) => t.name ?? ""));
28197
- const templates = curatedTasksForPhase(phase).filter((t) => !existingTitles.has(t.title)).slice(0, MIN_TASKS_PER_PHASE - existingTasks.length);
28757
+ const templates = curatedTasksForPhase(phase2).filter((t) => !existingTitles.has(t.title)).slice(0, MIN_TASKS_PER_PHASE - existingTasks.length);
28198
28758
  for (const t of templates) {
28199
28759
  await createTask.execute(
28200
28760
  {
28201
- phaseId: phase.id,
28761
+ phaseId: phase2.id,
28202
28762
  title: t.title,
28203
28763
  description: t.description,
28204
28764
  fileRefs: t.fileRefs,
@@ -28557,8 +29117,8 @@ async function runPostCouncilHook(ctx, options) {
28557
29117
  sources: scope.sources
28558
29118
  } : void 0
28559
29119
  });
28560
- const path34 = writeCouncilCompletion(ctx.rootDir, completion);
28561
- completionHook = { ran: true, path: path34, completion };
29120
+ const path36 = writeCouncilCompletion(ctx.rootDir, completion);
29121
+ completionHook = { ran: true, path: path36, completion };
28562
29122
  } catch (err) {
28563
29123
  completionHook = {
28564
29124
  ran: true,
@@ -28627,7 +29187,7 @@ import {
28627
29187
  writeFileSync as writeFileSync14,
28628
29188
  mkdirSync as mkdirSync9
28629
29189
  } from "node:fs";
28630
- import path23 from "node:path";
29190
+ import path24 from "node:path";
28631
29191
  import os8 from "node:os";
28632
29192
  var FeedbackStore;
28633
29193
  var init_councilFeedback = __esm({
@@ -28638,7 +29198,7 @@ var init_councilFeedback = __esm({
28638
29198
  now;
28639
29199
  entries = [];
28640
29200
  constructor(options = {}) {
28641
- this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path23.join(os8.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
29201
+ this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path24.join(os8.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
28642
29202
  this.now = options.now ?? Date.now;
28643
29203
  this.load();
28644
29204
  }
@@ -28744,7 +29304,7 @@ var init_councilFeedback = __esm({
28744
29304
  }
28745
29305
  }
28746
29306
  save() {
28747
- mkdirSync9(path23.dirname(this.file), { recursive: true });
29307
+ mkdirSync9(path24.dirname(this.file), { recursive: true });
28748
29308
  writeFileSync14(
28749
29309
  this.file,
28750
29310
  JSON.stringify({ entries: this.entries }, null, 2),
@@ -28779,7 +29339,7 @@ __export(fileBackend_exports, {
28779
29339
  });
28780
29340
  import { randomUUID as randomUUID2 } from "node:crypto";
28781
29341
  import { promises as fs14 } from "node:fs";
28782
- import * as path24 from "node:path";
29342
+ import * as path25 from "node:path";
28783
29343
  function tokenize(text) {
28784
29344
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
28785
29345
  }
@@ -28817,8 +29377,8 @@ var init_fileBackend = __esm({
28817
29377
  logPath = "";
28818
29378
  memoryDir = "";
28819
29379
  async init(projectRoot) {
28820
- this.memoryDir = path24.join(projectRoot, ".zelari", "memory");
28821
- this.logPath = path24.join(this.memoryDir, "log.jsonl");
29380
+ this.memoryDir = path25.join(projectRoot, ".zelari", "memory");
29381
+ this.logPath = path25.join(this.memoryDir, "log.jsonl");
28822
29382
  await fs14.mkdir(this.memoryDir, { recursive: true });
28823
29383
  }
28824
29384
  async add(content, metadata = {}, graph) {
@@ -28893,7 +29453,7 @@ import { execFile as execFile2 } from "node:child_process";
28893
29453
  import { promisify } from "node:util";
28894
29454
  import { mkdtempSync, rmSync } from "node:fs";
28895
29455
  import { tmpdir } from "node:os";
28896
- import path25 from "node:path";
29456
+ import path26 from "node:path";
28897
29457
  import { randomUUID as randomUUID3 } from "node:crypto";
28898
29458
  async function git(cwd, args, env) {
28899
29459
  const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
@@ -28913,8 +29473,8 @@ async function isGitRepo(cwd) {
28913
29473
  return await gitSafe(cwd, ["rev-parse", "--is-inside-work-tree"]) === "true";
28914
29474
  }
28915
29475
  async function withTempIndex(fn) {
28916
- const dir = mkdtempSync(path25.join(tmpdir(), "zelari-ckpt-"));
28917
- const indexFile = path25.join(dir, "index");
29476
+ const dir = mkdtempSync(path26.join(tmpdir(), "zelari-ckpt-"));
29477
+ const indexFile = path26.join(dir, "index");
28918
29478
  try {
28919
29479
  return await fn(indexFile);
28920
29480
  } finally {
@@ -29005,7 +29565,7 @@ async function restoreCheckpoint(cwd, id) {
29005
29565
  const deleted = [];
29006
29566
  for (const rel2 of added) {
29007
29567
  try {
29008
- rmSync(path25.join(cwd, rel2), { force: true });
29568
+ rmSync(path26.join(cwd, rel2), { force: true });
29009
29569
  deleted.push(rel2);
29010
29570
  } catch {
29011
29571
  }
@@ -29035,7 +29595,7 @@ __export(zelariMission_exports, {
29035
29595
  });
29036
29596
  import { randomUUID as randomUUID4 } from "node:crypto";
29037
29597
  import { promises as fs15 } from "node:fs";
29038
- import * as path26 from "node:path";
29598
+ import * as path27 from "node:path";
29039
29599
  function resolveMaxIterations(env = process.env) {
29040
29600
  const raw = env.ZELARI_MISSION_MAX_ITER;
29041
29601
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -29051,10 +29611,10 @@ function isMissionAutoStart(env = process.env) {
29051
29611
  return env.ZELARI_MISSION_AUTO === "1";
29052
29612
  }
29053
29613
  async function writeMissionState(projectRoot, state2) {
29054
- const dir = path26.join(projectRoot, ".zelari");
29614
+ const dir = path27.join(projectRoot, ".zelari");
29055
29615
  await fs15.mkdir(dir, { recursive: true });
29056
29616
  await fs15.writeFile(
29057
- path26.join(dir, "mission-state.json"),
29617
+ path27.join(dir, "mission-state.json"),
29058
29618
  JSON.stringify(state2, null, 2) + "\n",
29059
29619
  "utf8"
29060
29620
  );
@@ -29505,10 +30065,10 @@ var init_prereqChecks = __esm({
29505
30065
 
29506
30066
  // src/cli/plugins/prefs.ts
29507
30067
  import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10 } from "node:fs";
29508
- import path28 from "node:path";
30068
+ import path29 from "node:path";
29509
30069
  import os9 from "node:os";
29510
30070
  function getPluginPrefsPath() {
29511
- return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path28.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
30071
+ return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path29.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
29512
30072
  }
29513
30073
  function getPluginPrefs() {
29514
30074
  const file2 = getPluginPrefsPath();
@@ -29529,7 +30089,7 @@ function getPluginPrefs() {
29529
30089
  }
29530
30090
  function writePluginPrefs(prefs) {
29531
30091
  const file2 = getPluginPrefsPath();
29532
- mkdirSync10(path28.dirname(file2), { recursive: true });
30092
+ mkdirSync10(path29.dirname(file2), { recursive: true });
29533
30093
  writeFileSync15(file2, JSON.stringify(prefs, null, 2), {
29534
30094
  encoding: "utf-8",
29535
30095
  mode: 384
@@ -29562,9 +30122,11 @@ var registry_exports = {};
29562
30122
  __export(registry_exports, {
29563
30123
  PLUGINS: () => PLUGINS,
29564
30124
  detectMissingPlugins: () => detectMissingPlugins,
29565
- findPlugin: () => findPlugin
30125
+ findPlugin: () => findPlugin,
30126
+ isBinaryOnPath: () => isBinaryOnPath
29566
30127
  });
29567
- import { spawnSync as spawnSync3 } from "node:child_process";
30128
+ import { existsSync as existsSync27 } from "node:fs";
30129
+ import path30 from "node:path";
29568
30130
  function detectLocalBin(bin) {
29569
30131
  return (cwd) => {
29570
30132
  try {
@@ -29575,27 +30137,48 @@ function detectLocalBin(bin) {
29575
30137
  }
29576
30138
  };
29577
30139
  }
29578
- function detectGlobalBin(bin) {
29579
- return () => {
30140
+ function isBinaryOnPath(bin, opts = {}) {
30141
+ if (!bin || bin.includes("/") || bin.includes("\\") || bin.includes("..")) {
30142
+ return false;
30143
+ }
30144
+ const platform = opts.platform ?? process.platform;
30145
+ const exists = opts.exists ?? existsSync27;
30146
+ const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
30147
+ const pathMod = platform === "win32" ? path30.win32 : path30.posix;
30148
+ const sep = platform === "win32" ? ";" : ":";
30149
+ const dirs = pathEnv.split(sep).filter((d) => d.length > 0);
30150
+ const candidates = [bin];
30151
+ if (platform === "win32") {
30152
+ const pathExt = opts.pathExt ?? process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM";
30153
+ for (const ext of pathExt.split(";")) {
30154
+ if (!ext) continue;
30155
+ candidates.push(bin + ext);
30156
+ const lower = ext.toLowerCase();
30157
+ if (lower !== ext) candidates.push(bin + lower);
30158
+ }
30159
+ }
30160
+ for (const dir of dirs) {
30161
+ for (const name of candidates) {
30162
+ try {
30163
+ if (exists(pathMod.join(dir, name))) return true;
30164
+ } catch {
30165
+ }
30166
+ }
30167
+ }
30168
+ return false;
30169
+ }
30170
+ function detectPathBin(bin) {
30171
+ return (cwd) => {
29580
30172
  try {
29581
- const args = ["--version"];
29582
- const res = process.platform === "win32" ? spawnSync3(buildCmdLine(bin, args), {
29583
- stdio: ["ignore", "pipe", "ignore"],
29584
- shell: true,
29585
- timeout: 4e3
29586
- }) : spawnSync3(bin, args, {
29587
- stdio: ["ignore", "pipe", "ignore"],
29588
- timeout: 4e3
29589
- });
29590
- return Promise.resolve(res.status === 0 || res.stdout != null && res.stdout.toString().trim().length > 0 && res.error === void 0);
30173
+ if (resolveBin(bin, cwd) !== bin) return Promise.resolve(true);
29591
30174
  } catch {
29592
- return Promise.resolve(false);
29593
30175
  }
30176
+ return Promise.resolve(isBinaryOnPath(bin));
29594
30177
  };
29595
30178
  }
29596
- async function detectPlaywright() {
30179
+ async function detectPlaywright(cwd) {
29597
30180
  try {
29598
- const mod = await defaultPlaywrightLoader();
30181
+ const mod = await loadPlaywright(cwd);
29599
30182
  return mod !== null;
29600
30183
  } catch {
29601
30184
  return false;
@@ -29633,7 +30216,6 @@ var PLUGINS;
29633
30216
  var init_registry2 = __esm({
29634
30217
  "src/cli/plugins/registry.ts"() {
29635
30218
  "use strict";
29636
- init_cmdline();
29637
30219
  init_engine();
29638
30220
  init_driver();
29639
30221
  init_engine();
@@ -29663,7 +30245,7 @@ var init_registry2 = __esm({
29663
30245
  label: "Playwright (browser_check tool)",
29664
30246
  npmPackage: "playwright",
29665
30247
  installScope: "dev",
29666
- detect: () => detectPlaywright(),
30248
+ detect: detectPlaywright,
29667
30249
  postInstallHint: "Then fetch the browser binary: `npx playwright install chromium`",
29668
30250
  featureGate: "ZELARI_BROWSER",
29669
30251
  description: "Powers the browser_check tool (URL probing, click/fill/wait, screenshots)."
@@ -29673,7 +30255,7 @@ var init_registry2 = __esm({
29673
30255
  label: "typescript-language-server (LSP for TS/JS)",
29674
30256
  npmPackage: "typescript-language-server",
29675
30257
  installScope: "global",
29676
- detect: detectGlobalBin(binForLspLanguage("typescript")),
30258
+ detect: detectPathBin(binForLspLanguage("typescript")),
29677
30259
  featureGate: "ZELARI_LSP",
29678
30260
  description: "Powers go_to_definition / find_references / hover_type / rename_symbol for TS/JS."
29679
30261
  },
@@ -29682,9 +30264,24 @@ var init_registry2 = __esm({
29682
30264
  label: "pyright (LSP for Python)",
29683
30265
  npmPackage: "pyright",
29684
30266
  installScope: "global",
29685
- detect: detectGlobalBin(binForLspLanguage("python")),
30267
+ // Detect the langserver binary runtime spawns (pyright-langserver), not
30268
+ // the `pyright` CLI — and never via --version (langserver rejects it).
30269
+ detect: detectPathBin(binForLspLanguage("python")),
29686
30270
  featureGate: "ZELARI_LSP",
29687
30271
  description: "Powers go_to_definition / find_references / hover_type / rename_symbol for Python."
30272
+ },
30273
+ {
30274
+ // fff — high-performance codebase search MCP (fffind / ffgrep).
30275
+ // Installed as a global CLI; wire it in ~/.zelari-code/mcp.json (see
30276
+ // postInstallHint). Kill-switch: ZELARI_FFF=0.
30277
+ id: "fff",
30278
+ label: "fff (fast codebase search MCP)",
30279
+ npmPackage: "fff-mcp",
30280
+ installScope: "global",
30281
+ detect: detectPathBin("fff-mcp"),
30282
+ postInstallHint: 'Add to ~/.zelari-code/mcp.json: {"mcpServers":{"fff":{"command":"fff-mcp","args":[]}}} then restart. Prefer mcp_fff_* tools for search.',
30283
+ featureGate: "ZELARI_FFF",
30284
+ description: "Accelerates codebase search via fff MCP (fffind, ffgrep, fff-multi-grep) \u2014 faster and more token-efficient than plain grep."
29688
30285
  }
29689
30286
  ];
29690
30287
  }
@@ -29696,26 +30293,26 @@ __export(doctor_exports, {
29696
30293
  runDoctor: () => runDoctor
29697
30294
  });
29698
30295
  import { execSync as execSync2 } from "node:child_process";
29699
- import { existsSync as existsSync31, readFileSync as readFileSync26, readlinkSync, statSync as statSync6 } from "node:fs";
29700
- import { createRequire as createRequire2 } from "node:module";
30296
+ import { existsSync as existsSync32, readFileSync as readFileSync26, readlinkSync, statSync as statSync6 } from "node:fs";
30297
+ import { createRequire as createRequire3 } from "node:module";
29701
30298
  import { fileURLToPath as fileURLToPath2 } from "node:url";
29702
- import path33 from "node:path";
30299
+ import path35 from "node:path";
29703
30300
  function findPackageRoot(start) {
29704
30301
  let dir = start;
29705
30302
  for (let i = 0; i < 6; i += 1) {
29706
- const candidate = path33.join(dir, "package.json");
29707
- if (existsSync31(candidate)) {
30303
+ const candidate = path35.join(dir, "package.json");
30304
+ if (existsSync32(candidate)) {
29708
30305
  try {
29709
30306
  const pkg = JSON.parse(readFileSync26(candidate, "utf8"));
29710
30307
  if (pkg.name === "zelari-code") return dir;
29711
30308
  } catch {
29712
30309
  }
29713
30310
  }
29714
- const parent = path33.dirname(dir);
30311
+ const parent = path35.dirname(dir);
29715
30312
  if (parent === dir) break;
29716
30313
  dir = parent;
29717
30314
  }
29718
- return path33.resolve(__dirname3, "..", "..", "..");
30315
+ return path35.resolve(__dirname3, "..", "..", "..");
29719
30316
  }
29720
30317
  function tryExec(cmd) {
29721
30318
  try {
@@ -29729,7 +30326,7 @@ function tryExec(cmd) {
29729
30326
  }
29730
30327
  function readPackageJson3() {
29731
30328
  try {
29732
- const pkgPath = path33.join(packageRoot, "package.json");
30329
+ const pkgPath = path35.join(packageRoot, "package.json");
29733
30330
  return JSON.parse(readFileSync26(pkgPath, "utf8"));
29734
30331
  } catch {
29735
30332
  return null;
@@ -29745,8 +30342,8 @@ function checkShim(pkgName) {
29745
30342
  }
29746
30343
  const isWin = process.platform === "win32";
29747
30344
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
29748
- const shimPath = path33.join(prefix, shimName);
29749
- if (!existsSync31(shimPath)) {
30345
+ const shimPath = path35.join(prefix, shimName);
30346
+ if (!existsSync32(shimPath)) {
29750
30347
  return FAIL(
29751
30348
  `shim not found at ${shimPath}
29752
30349
  fix: npm install -g ${pkgName}@latest --force`
@@ -29773,8 +30370,8 @@ function checkShim(pkgName) {
29773
30370
  fix: npm install -g ${pkgName}@latest --force`
29774
30371
  );
29775
30372
  }
29776
- const resolved = path33.resolve(path33.dirname(shimPath), target);
29777
- const expected = path33.join(
30373
+ const resolved = path35.resolve(path35.dirname(shimPath), target);
30374
+ const expected = path35.join(
29778
30375
  prefix,
29779
30376
  "node_modules",
29780
30377
  pkgName,
@@ -29813,8 +30410,8 @@ function checkNode(pkg) {
29813
30410
  return OK(`node ${raw}`);
29814
30411
  }
29815
30412
  function checkBundle() {
29816
- const bundle = path33.join(packageRoot, "dist", "cli", "main.bundled.js");
29817
- if (!existsSync31(bundle)) {
30413
+ const bundle = path35.join(packageRoot, "dist", "cli", "main.bundled.js");
30414
+ if (!existsSync32(bundle)) {
29818
30415
  return FAIL(
29819
30416
  `dist/cli/main.bundled.js missing at ${bundle}
29820
30417
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -29834,7 +30431,7 @@ function checkRuntimeDeps() {
29834
30431
  const missing = [];
29835
30432
  for (const dep of required2) {
29836
30433
  try {
29837
- const localReq = createRequire2(path33.join(packageRoot, "package.json"));
30434
+ const localReq = createRequire3(path35.join(packageRoot, "package.json"));
29838
30435
  localReq.resolve(dep);
29839
30436
  } catch {
29840
30437
  missing.push(dep);
@@ -29954,8 +30551,8 @@ var init_doctor = __esm({
29954
30551
  "src/cli/utils/doctor.ts"() {
29955
30552
  "use strict";
29956
30553
  init_prereqChecks();
29957
- require3 = createRequire2(import.meta.url);
29958
- __dirname3 = path33.dirname(fileURLToPath2(import.meta.url));
30554
+ require3 = createRequire3(import.meta.url);
30555
+ __dirname3 = path35.dirname(fileURLToPath2(import.meta.url));
29959
30556
  packageRoot = findPackageRoot(__dirname3);
29960
30557
  OK = (message) => ({
29961
30558
  ok: true,
@@ -29980,11 +30577,11 @@ var fixPath_exports = {};
29980
30577
  __export(fixPath_exports, {
29981
30578
  repairWindowsUserPath: () => repairWindowsUserPath
29982
30579
  });
29983
- import { spawnSync as spawnSync4 } from "node:child_process";
30580
+ import { spawnSync as spawnSync3 } from "node:child_process";
29984
30581
  function getGlobalPrefix2() {
29985
30582
  return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim() || (() => {
29986
30583
  try {
29987
- return spawnSync4("npm", ["prefix", "-g"], {
30584
+ return spawnSync3("npm", ["prefix", "-g"], {
29988
30585
  encoding: "utf8",
29989
30586
  stdio: ["ignore", "pipe", "ignore"]
29990
30587
  }).stdout?.trim() ?? "";
@@ -29995,7 +30592,7 @@ function getGlobalPrefix2() {
29995
30592
  }
29996
30593
  function powershell(script) {
29997
30594
  try {
29998
- const res = spawnSync4(
30595
+ const res = spawnSync3(
29999
30596
  "powershell.exe",
30000
30597
  ["-NoProfile", "-NonInteractive", "-Command", script],
30001
30598
  { encoding: "utf8" }
@@ -30059,7 +30656,7 @@ import React14 from "react";
30059
30656
  import { render } from "ink";
30060
30657
 
30061
30658
  // src/cli/app.tsx
30062
- 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";
30063
30660
  import { Box as Box8, Static, useInput as useInput2, useStdin as useStdin2 } from "ink";
30064
30661
 
30065
30662
  // src/cli/components/InputBar.tsx
@@ -30457,9 +31054,12 @@ function WorkingIndicator({
30457
31054
 
30458
31055
  // src/cli/components/LiveRegion.tsx
30459
31056
  var LIVE_STREAM_TAIL_LINES = 10;
31057
+ var MAX_LIVE_TOOLS = 4;
30460
31058
  function LiveRegion({ live, busy, elapsedMs = null }) {
30461
31059
  const { streaming, runningTools } = live;
30462
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;
30463
31063
  return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", paddingX: 1 }, streaming && streaming.role === "assistant" && /* @__PURE__ */ React5.createElement(
30464
31064
  StreamingTail,
30465
31065
  {
@@ -30469,7 +31069,7 @@ function LiveRegion({ live, busy, elapsedMs = null }) {
30469
31069
  memberName: streaming.memberName,
30470
31070
  memberId: streaming.memberId
30471
31071
  }
30472
- ), 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 }));
30473
31073
  }
30474
31074
  function StreamingTail(m) {
30475
31075
  const lines = m.content.split("\n");
@@ -30564,20 +31164,25 @@ function StatusBar({
30564
31164
  queueCount = 0,
30565
31165
  busy = false,
30566
31166
  mode = "agent",
31167
+ phase: phase2 = "build",
30567
31168
  cwd,
30568
31169
  elapsedMs = null,
30569
31170
  lastMs = null,
30570
31171
  costUsd = 0,
30571
- cachedTokens = 0
31172
+ cachedTokens = 0,
31173
+ contextUsed = 0,
31174
+ contextLimit = 0,
31175
+ brandVersion
30572
31176
  }) {
30573
- return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1, width: "100%", justifyContent: "space-between", gap: 2 }, /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 2 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, /* @__PURE__ */ React6.createElement(Text6, { color: sessionActive ? "green" : "gray" }, sessionActive ? "\u25CF" : "\u25CB"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " "), /* @__PURE__ */ React6.createElement(
31177
+ const ctxLabel = contextLimit > 0 ? `${formatTokens(contextUsed)}/${formatTokens(contextLimit)}` : contextUsed > 0 ? formatTokens(contextUsed) : null;
31178
+ return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1, width: "100%", justifyContent: "space-between", gap: 2 }, /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 2 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, /* @__PURE__ */ React6.createElement(Text6, { color: sessionActive ? "green" : "gray" }, sessionActive ? "\u25CF" : "\u25CB"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: phase2 === "plan" ? "yellow" : "green" }, phase2 === "plan" ? "\u25C7 plan" : "\u25C6 build"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(
30574
31179
  Text6,
30575
31180
  {
30576
31181
  bold: true,
30577
31182
  color: mode === "council" ? "magenta" : mode === "zelari" ? "green" : "cyan"
30578
31183
  },
30579
- mode === "council" ? "\u26EC council" : mode === "zelari" ? "\u26A1 zelari" : "\u23F5 agent"
30580
- ), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (shift+tab)"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, model), cwd ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, cwd)) : null)), /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 1 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, busy && elapsedMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Spinner, { color: "yellow" }), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " ", formatDuration(elapsedMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : lastMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "last ", formatDuration(lastMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, queueCount > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "queue ", queueCount), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, costUsd > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, formatCost(costUsd)), cachedTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", formatTokens(cachedTokens), " cached)") : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId))));
31184
+ mode === "council" ? "council" : mode === "zelari" ? "zelari" : "agent"
31185
+ ), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, model), cwd ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, cwd)) : null)), /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 1 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, busy && elapsedMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Spinner, { color: "yellow" }), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " ", formatDuration(elapsedMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : lastMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "last ", formatDuration(lastMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, queueCount > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "queue ", queueCount), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, ctxLabel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, ctxLabel), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, costUsd > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, formatCost(costUsd)), cachedTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", formatTokens(cachedTokens), " cached)") : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, brandVersion ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "white" }, "ZELARI"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " v", brandVersion), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId))));
30581
31186
  }
30582
31187
 
30583
31188
  // src/cli/components/SelectList.tsx
@@ -30625,30 +31230,35 @@ function SelectList({
30625
31230
  // src/cli/components/Sidebar.tsx
30626
31231
  import React8 from "react";
30627
31232
  import { Box as Box7, Text as Text8 } from "ink";
30628
- var EMBLEM_BRAILLE = `\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28F4\u28FF\u28F7\u28C6\u2840
30629
- \u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28F6\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6\u2840
30630
- \u2800\u2800\u2800\u2800\u2800\u2800\u28F0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6
30631
- \u2800\u2800\u2800\u2800\u28A0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28F7\u2844
30632
- \u2800\u2800\u2800\u2800\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2819\u283B\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2806
30633
- \u2800\u2800\u2800\u2800\u28C8\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2800\u28F7\u28EC\u285B\u28BF\u28FF\u28DF\u28C1
30634
- \u2800\u2880\u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2800\u287F\u28BF\u287F\u2883\u28C8\u28FB\u28FF\u28FF\u28F6
30635
- \u2800\u28FC\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E0\u28F4\u28C6\u283B\u280E\u28BF\u28FF\u28FF\u28FF\u28FF\u28E7
30636
- \u28F0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2847`;
30637
31233
  var SIDEBAR_WIDTH = 28;
30638
31234
  var SIDEBAR_MIN_COLUMNS = 96;
30639
- var EMBLEM_MIN_ROWS = 26;
30640
- var MAX_FILES_SHORT = 6;
30641
- 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;
30642
31241
  function shouldShowSidebar(columns, rows) {
30643
- 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);
30644
31254
  }
30645
31255
  function truncatePath(p3, max) {
30646
31256
  if (p3.length <= max) return p3;
30647
31257
  return `\u2026${p3.slice(-(max - 1))}`;
30648
31258
  }
30649
31259
  function Sidebar({ version: version2, changes, rows }) {
30650
- const showEmblem = rows >= EMBLEM_MIN_ROWS;
30651
- const maxFiles = rows >= EMBLEM_MIN_ROWS + 8 ? MAX_FILES_TALL : MAX_FILES_SHORT;
31260
+ void version2;
31261
+ const maxFiles = maxSidebarFiles(rows);
30652
31262
  const visible = changes.files.slice(0, maxFiles);
30653
31263
  const hidden = changes.files.length - visible.length;
30654
31264
  const innerWidth = SIDEBAR_WIDTH - 4;
@@ -30660,11 +31270,11 @@ function Sidebar({ version: version2, changes, rows }) {
30660
31270
  borderStyle: "single",
30661
31271
  borderColor: "gray",
30662
31272
  paddingX: 1,
30663
- flexShrink: 0
31273
+ flexShrink: 0,
31274
+ height: Math.min(rows - 8, SIDEBAR_CHROME_LINES + maxFiles + 2),
31275
+ overflow: "hidden"
30664
31276
  },
30665
- showEmblem && /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, EMBLEM_BRAILLE)),
30666
- /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "white" }, "ZELARI CODE")),
30667
- /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "v", version2, changes.branch ? ` \xB7 ${truncatePath(changes.branch, 12)}` : "")),
31277
+ /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, changes.branch ? truncatePath(changes.branch, 18) : "git")),
30668
31278
  /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "\u2500".repeat(innerWidth)),
30669
31279
  !changes.isRepo ? /* @__PURE__ */ React8.createElement(Text8, { dimColor: true, italic: true }, "not a git repo") : changes.files.length === 0 ? /* @__PURE__ */ React8.createElement(Text8, { dimColor: true, italic: true }, "no changes") : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "changes (", changes.files.length, ")"), visible.map((f) => /* @__PURE__ */ React8.createElement(FileRow, { key: f.path, file: f, pathWidth: innerWidth - 10 })), hidden > 0 && /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " +", hidden, " more\u2026"))
30670
31280
  );
@@ -30674,6 +31284,53 @@ function FileRow({ file: file2, pathWidth }) {
30674
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"))));
30675
31285
  }
30676
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
+
30677
31334
  // src/cli/modelDiscovery.ts
30678
31335
  import { promises as fs3, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
30679
31336
  import { homedir } from "node:os";
@@ -33649,80 +34306,8 @@ function finalizeStreamingAssistant(setMessages) {
33649
34306
  });
33650
34307
  }
33651
34308
 
33652
- // src/cli/utils/envNumber.ts
33653
- function envNumber(raw, opts) {
33654
- const { default: def, min, max } = opts;
33655
- if (raw === void 0 || raw === null) return def;
33656
- const trimmed = raw.trim();
33657
- if (trimmed.length === 0) return def;
33658
- if (trimmed.toLowerCase() === "undefined" || trimmed.toLowerCase() === "null") return def;
33659
- const parsed = Number.parseInt(trimmed, 10);
33660
- if (!Number.isFinite(parsed)) return def;
33661
- const absStr = `${Math.abs(parsed)}`;
33662
- const body = trimmed.replace(/^[-+]/, "").replace(/^0+(?=\d)/, "");
33663
- if (body !== absStr) return def;
33664
- const firstChar = trimmed[0];
33665
- const expectedSign = parsed < 0 ? "-" : /[0-9+]/.test(firstChar ?? "");
33666
- const actualSign = parsed < 0 ? firstChar === "-" : firstChar !== "-";
33667
- if (!expectedSign || !actualSign) return def;
33668
- let clamped = parsed;
33669
- if (min !== void 0 && clamped < min) clamped = min;
33670
- if (max !== void 0 && clamped > max) clamped = max;
33671
- return clamped;
33672
- }
33673
-
33674
- // src/cli/hooks/historyCompaction.ts
33675
- var COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
33676
- function resolveMaxMessages(opts) {
33677
- const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
33678
- const turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
33679
- if (turns <= 0) return 0;
33680
- return turns * 4;
33681
- }
33682
- function findValidCutIndex(messages, naiveCut) {
33683
- let cut = naiveCut;
33684
- while (cut < messages.length) {
33685
- const kept = messages.slice(cut);
33686
- const declared = /* @__PURE__ */ new Set();
33687
- for (const m of kept) {
33688
- if (m.role === "assistant" && m.toolCalls) {
33689
- for (const tc of m.toolCalls) declared.add(tc.id);
33690
- }
33691
- }
33692
- let moved = false;
33693
- for (let k = 0; k < kept.length; k++) {
33694
- const m = kept[k];
33695
- if (m.role === "tool" && m.toolCallId && !declared.has(m.toolCallId)) {
33696
- for (let j = cut - 1; j >= 0; j--) {
33697
- const prev2 = messages[j];
33698
- if (prev2.role === "assistant" && prev2.toolCalls && prev2.toolCalls.some((tc) => tc.id === m.toolCallId)) {
33699
- cut = j;
33700
- moved = true;
33701
- break;
33702
- }
33703
- }
33704
- break;
33705
- }
33706
- }
33707
- if (!moved) break;
33708
- }
33709
- return cut;
33710
- }
33711
- function compactHistory(messages, opts) {
33712
- const maxMessages = resolveMaxMessages(opts);
33713
- if (maxMessages === 0) return [];
33714
- if (messages.length <= maxMessages * 2) return messages;
33715
- const naiveCut = messages.length - maxMessages;
33716
- const cut = findValidCutIndex(messages, naiveCut);
33717
- const dropped = cut;
33718
- if (dropped === 0) return messages;
33719
- const kept = messages.slice(cut);
33720
- const summary = {
33721
- role: "system",
33722
- content: `${COMPACT_MARKER} ${dropped} earlier message(s) dropped.`
33723
- };
33724
- return [summary, ...kept];
33725
- }
34309
+ // src/cli/hooks/useChatTurn.ts
34310
+ init_conversationContext();
33726
34311
 
33727
34312
  // src/cli/hooks/chatStats.ts
33728
34313
  function computeSessionStatsDelta(realUsage, userText, assistantContent, model, prev2) {
@@ -33737,6 +34322,83 @@ function computeSessionStatsDelta(realUsage, userText, assistantContent, model,
33737
34322
  };
33738
34323
  }
33739
34324
 
34325
+ // src/cli/hooks/useChatTurn.ts
34326
+ init_envNumber();
34327
+ init_phaseState();
34328
+ init_phase();
34329
+
34330
+ // src/cli/budget/tokenBudget.ts
34331
+ init_envNumber();
34332
+ init_historyCompaction();
34333
+ function estimateTokens(text) {
34334
+ if (!text) return 0;
34335
+ return Math.max(1, Math.ceil(text.length / 4));
34336
+ }
34337
+ function estimateHistoryTokens(messages) {
34338
+ let n = 0;
34339
+ for (const m of messages) {
34340
+ n += estimateTokens(m.content);
34341
+ if (m.toolCalls) {
34342
+ for (const tc of m.toolCalls) {
34343
+ n += estimateTokens(tc.name) + estimateTokens(JSON.stringify(tc.args ?? {}));
34344
+ }
34345
+ }
34346
+ }
34347
+ return n;
34348
+ }
34349
+ function resolveContextLimit() {
34350
+ return envNumber(process.env.ZELARI_CONTEXT_LIMIT, {
34351
+ default: 2e5,
34352
+ min: 4e3,
34353
+ max: 2e6
34354
+ });
34355
+ }
34356
+ function applyBudgetPolicy(history2, phase2, opts) {
34357
+ const contextLimit = resolveContextLimit();
34358
+ const warnings = [];
34359
+ let historyTurns = phase2 === "plan" ? envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 8, min: 0 }) : envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
34360
+ let maxToolLoopIterations = phase2 === "plan" ? envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 40, min: 1 }) : envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 90, min: 1 });
34361
+ let hist = history2;
34362
+ let estimated = estimateHistoryTokens(hist);
34363
+ const sessionExtra = opts?.sessionTokens ?? 0;
34364
+ let occupancy = Math.min(1, (estimated + sessionExtra) / contextLimit);
34365
+ if (occupancy >= 0.7 && occupancy < 0.85) {
34366
+ warnings.push(
34367
+ `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated + sessionExtra}/${contextLimit} tok est.) \u2014 consider /compact or shorter replies.`
34368
+ );
34369
+ }
34370
+ if (occupancy >= 0.85) {
34371
+ const forcedTurns = Math.max(1, Math.floor(historyTurns / 2));
34372
+ historyTurns = forcedTurns;
34373
+ hist = compactHistory(hist, { maxMessages: forcedTurns * 4 });
34374
+ estimated = estimateHistoryTokens(hist);
34375
+ occupancy = Math.min(1, (estimated + sessionExtra) / contextLimit);
34376
+ warnings.push(
34377
+ `[budget] auto-compact at 85% \u2014 kept ~${forcedTurns} turns (${estimated} tok history est.).`
34378
+ );
34379
+ maxToolLoopIterations = Math.min(maxToolLoopIterations, phase2 === "plan" ? 24 : 40);
34380
+ }
34381
+ if (occupancy >= 0.95) {
34382
+ hist = compactHistory(hist, { maxMessages: 8 });
34383
+ estimated = estimateHistoryTokens(hist);
34384
+ occupancy = Math.min(1, (estimated + sessionExtra) / contextLimit);
34385
+ historyTurns = 2;
34386
+ maxToolLoopIterations = Math.min(maxToolLoopIterations, 16);
34387
+ warnings.push(
34388
+ `[budget] HARD context pressure (\u226595%) \u2014 history cut to last ~2 turns. Prefer /clear or a new session if quality drops.`
34389
+ );
34390
+ }
34391
+ return {
34392
+ history: hist,
34393
+ warnings,
34394
+ maxToolLoopIterations,
34395
+ historyTurns,
34396
+ estimatedHistoryTokens: estimated,
34397
+ contextLimit,
34398
+ occupancy
34399
+ };
34400
+ }
34401
+
33740
34402
  // src/cli/hooks/useChatTurn.ts
33741
34403
  function useChatTurn(params) {
33742
34404
  const {
@@ -33754,8 +34416,10 @@ function useChatTurn(params) {
33754
34416
  } = params;
33755
34417
  const harnessRef = useRef4(null);
33756
34418
  const [queueCount, setQueueCount] = useState6(0);
33757
- const historyRef = useRef4([]);
33758
34419
  const useLiveModel = !!(setLive && liveRef);
34420
+ const clearConversationHistory = useCallback2(() => {
34421
+ clearHistory();
34422
+ }, []);
33759
34423
  const dispatchPrompt = useCallback2(
33760
34424
  async (userText, opts) => {
33761
34425
  let envConfig;
@@ -33763,8 +34427,15 @@ function useChatTurn(params) {
33763
34427
  let historySeedLen = 0;
33764
34428
  let turnSucceeded = false;
33765
34429
  try {
33766
- historyRef.current = compactHistory(historyRef.current);
33767
- historySeedLen = historyRef.current.length;
34430
+ compactInPlace();
34431
+ const budget = applyBudgetPolicy(getHistory(), getPhase());
34432
+ setHistory(budget.history);
34433
+ for (const w of budget.warnings) {
34434
+ appendSystem(setMessages, w, Date.now());
34435
+ }
34436
+ historySeedLen = getHistory().length;
34437
+ const anchored = maybeAnchorShortAnswer(userText);
34438
+ const effectiveUserText = anchored ?? userText;
33768
34439
  envConfig = await providerFromEnv();
33769
34440
  if (!envConfig) {
33770
34441
  const active = resolveActiveProvider();
@@ -33776,7 +34447,10 @@ function useChatTurn(params) {
33776
34447
  return;
33777
34448
  }
33778
34449
  setBusy(true);
33779
- const { registry: toolRegistry } = createBuiltinToolRegistry();
34450
+ const workPhase = getPhase();
34451
+ const { registry: toolRegistry } = createBuiltinToolRegistry({
34452
+ planMode: workPhase === "plan"
34453
+ });
33780
34454
  const baseProviderStream = openaiCompatibleProvider(envConfig);
33781
34455
  const failoverResolution = await resolveFailoverStream({
33782
34456
  failoverEnabled: process.env.ANATHEMA_FAILOVER !== "0",
@@ -33856,12 +34530,26 @@ function useChatTurn(params) {
33856
34530
  const isWindows = process.platform === "win32";
33857
34531
  const shellGuidance = resolvedShell.isBash ? `The bash tool runs commands via Git Bash / MSYS2 (${resolvedShell.shell}). Write POSIX commands: ls, grep, $VAR, &&, /c/Users/... all work.` : isWindows ? `The bash tool runs commands via cmd.exe (Git Bash not found). Write Windows-native commands: use dir (not ls), %VAR% (not $VAR), avoid POSIX-only syntax.` : `The bash tool runs commands via /bin/sh.`;
33858
34532
  const nonInteractiveGuidance = "The shell is NON-INTERACTIVE (stdin closed): commands that prompt for input fail immediately. Always pass non-interactive flags (--yes, -y, --template, --force). If a scaffolder still insists on prompting (e.g. `npm create vite` in a non-empty directory), do NOT retry it \u2014 scaffold into a fresh empty subdirectory and move the files, or write package.json/configs/sources yourself with write_file, then run `npm install`.";
34533
+ const planPhaseBlock = workPhase === "plan" ? [
34534
+ "",
34535
+ "# Work Phase: PLAN",
34536
+ "You are in PLAN mode. Explore and design only.",
34537
+ "- Do NOT implement production code or run destructive shell commands.",
34538
+ "- write_file / edit_file / bash / apply_diff are unavailable.",
34539
+ "- Produce a clear plan, ask clarifying questions (---QUESTION---), use workspace plan tools when relevant.",
34540
+ "- When the plan is ready, tell the user to run /build to implement."
34541
+ ].join("\n") : workPhase === "build" && planSummary ? [
34542
+ "",
34543
+ "# Work Phase: BUILD",
34544
+ "Implement the approved plan. Prefer acting over describing. Update plan task statuses as you go."
34545
+ ].join("\n") : "";
33859
34546
  const shellContextBlock = [
33860
34547
  "# Platform & Shell",
33861
34548
  `platform: ${process.platform}`,
33862
34549
  `shell: ${resolvedShell.via}`,
33863
34550
  shellGuidance,
33864
34551
  nonInteractiveGuidance,
34552
+ planPhaseBlock,
33865
34553
  "",
33866
34554
  "# Working Directory",
33867
34555
  `You are running in: ${cwd}`,
@@ -33915,20 +34603,17 @@ function useChatTurn(params) {
33915
34603
  default: 25,
33916
34604
  min: 1
33917
34605
  });
33918
- const maxToolLoopIterations = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
33919
- default: 90,
33920
- min: 1
33921
- });
34606
+ const maxToolLoopIterations = budget.maxToolLoopIterations;
33922
34607
  const harness2 = new AgentHarness({
33923
34608
  model: envConfig.model,
33924
34609
  provider: "openai-compatible",
33925
34610
  messages: [
33926
34611
  { role: "system", content: systemPrompt },
33927
- // v1.6.0: seed prior turns so the model sees its own last
33928
- // question when the user answers briefly. historyRef.current
33929
- // is compacted above (possibly empty if ZELARI_HISTORY_TURNS=0).
33930
- ...historyRef.current,
33931
- { role: "user", content: userText }
34612
+ // v1.8.0: shared rolling history (agent/council/zelari) so short
34613
+ // answers bind to prior ---QUESTION--- blocks. Possibly empty
34614
+ // when ZELARI_HISTORY_TURNS=0.
34615
+ ...getHistory(),
34616
+ { role: "user", content: effectiveUserText }
33932
34617
  ],
33933
34618
  tools: toolRegistry.toOpenAITools().map((t) => ({
33934
34619
  name: t.function.name,
@@ -33995,15 +34680,16 @@ function useChatTurn(params) {
33995
34680
  if (event.type === "message_delta") {
33996
34681
  assistantContent += event.delta;
33997
34682
  streamContent += event.delta;
34683
+ const displayContent = cleanAgentContent(streamContent);
33998
34684
  if (useLiveModel) {
33999
- setStreaming(commitStreaming, streamContent, Date.now(), {
34685
+ setStreaming(commitStreaming, displayContent, Date.now(), {
34000
34686
  ...event.memberId ? { memberId: event.memberId } : {},
34001
34687
  ...event.memberName ? { memberName: event.memberName } : {}
34002
34688
  });
34003
34689
  } else {
34004
34690
  appendOrExtendStreamingAssistant(
34005
34691
  commitStreaming,
34006
- streamContent,
34692
+ displayContent,
34007
34693
  Date.now(),
34008
34694
  {
34009
34695
  ...event.memberId ? { memberId: event.memberId } : {},
@@ -34069,38 +34755,53 @@ function useChatTurn(params) {
34069
34755
  const all = h.getMessages();
34070
34756
  const seedLen = 1 + historySeedLen + 1;
34071
34757
  if (all.length > seedLen) {
34072
- historyRef.current = historyRef.current.concat(
34073
- 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
+ )
34074
34767
  );
34075
34768
  }
34076
34769
  }
34077
34770
  } catch {
34078
34771
  }
34079
- if (turnSucceeded && setPicker && assistantContent) {
34772
+ if (turnSucceeded && assistantContent) {
34080
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
+ }
34081
34787
  const clar = parseClarificationRequest(assistantContent);
34082
34788
  if (clar && clar.choices && clar.choices.length >= 2) {
34083
- const cleaned = cleanAgentContent(assistantContent);
34084
- if (cleaned !== assistantContent) {
34085
- setMessages((prev2) => {
34086
- const next = [...prev2];
34087
- for (let i = next.length - 1; i >= 0; i--) {
34088
- if (next[i].role === "assistant") {
34089
- next[i] = { ...next[i], content: cleaned };
34090
- break;
34091
- }
34789
+ setLastClarification({
34790
+ question: clar.question,
34791
+ choices: clar.choices
34792
+ });
34793
+ if (setPicker) {
34794
+ setPicker({
34795
+ kind: "clarification",
34796
+ title: clar.question,
34797
+ items: clar.choices.map((c) => ({ value: c, label: c })),
34798
+ onAnswer: (value) => {
34799
+ void dispatchPrompt(value);
34092
34800
  }
34093
- return next;
34094
34801
  });
34095
34802
  }
34096
- setPicker({
34097
- kind: "clarification",
34098
- title: clar.question,
34099
- items: clar.choices.map((c) => ({ value: c, label: c })),
34100
- onAnswer: (value) => {
34101
- void dispatchPrompt(value);
34102
- }
34103
- });
34803
+ } else {
34804
+ setLastClarification(null);
34104
34805
  }
34105
34806
  } catch {
34106
34807
  }
@@ -34152,7 +34853,8 @@ function useChatTurn(params) {
34152
34853
  setBusy,
34153
34854
  setQueueCount,
34154
34855
  setLive,
34155
- liveRef
34856
+ liveRef,
34857
+ setPicker
34156
34858
  });
34157
34859
  },
34158
34860
  [
@@ -34164,7 +34866,8 @@ function useChatTurn(params) {
34164
34866
  setBusy,
34165
34867
  setQueueCount,
34166
34868
  setLive,
34167
- liveRef
34869
+ liveRef,
34870
+ setPicker
34168
34871
  ]
34169
34872
  );
34170
34873
  const pendingZelariRef = useRef4(null);
@@ -34204,7 +34907,8 @@ function useChatTurn(params) {
34204
34907
  dispatchZelariPrompt,
34205
34908
  harnessRef,
34206
34909
  queueCount,
34207
- setQueueCount
34910
+ setQueueCount,
34911
+ clearConversationHistory
34208
34912
  };
34209
34913
  }
34210
34914
  async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
@@ -34216,7 +34920,8 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34216
34920
  flushStreaming,
34217
34921
  setBusy,
34218
34922
  setLive,
34219
- liveRef
34923
+ liveRef,
34924
+ setPicker
34220
34925
  } = deps;
34221
34926
  const useLiveModel = !!(setLive && liveRef);
34222
34927
  const envConfig = await providerFromEnv();
@@ -34230,6 +34935,19 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34230
34935
  return { completionOk: false, ran: false };
34231
34936
  }
34232
34937
  setBusy(true);
34938
+ compactInPlace();
34939
+ const councilBudget = applyBudgetPolicy(getHistory(), getPhase());
34940
+ setHistory(councilBudget.history);
34941
+ for (const w of councilBudget.warnings) {
34942
+ appendSystem(setMessages, w, Date.now());
34943
+ }
34944
+ const anchored = maybeAnchorShortAnswer(text);
34945
+ const effectiveText = anchored ?? text;
34946
+ appendSystem(
34947
+ setMessages,
34948
+ `[phase] ${describePhase(getPhase())}`,
34949
+ Date.now()
34950
+ );
34233
34951
  const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
34234
34952
  const { createWorkspaceContext: createWorkspaceContext2, createWorkspaceStubs: createWorkspaceStubs2 } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
34235
34953
  const { createWorkspaceToolRegistry: createWorkspaceToolRegistry2 } = await Promise.resolve().then(() => (init_toolRegistry2(), toolRegistry_exports2));
@@ -34238,13 +34956,18 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34238
34956
  const { buildWorkspaceSummary: buildWorkspaceSummary2, buildPlanSummary: buildPlanSummary2 } = await Promise.resolve().then(() => (init_workspaceSummary(), workspaceSummary_exports));
34239
34957
  const { buildLessonsSummary: buildLessonsSummary2 } = await Promise.resolve().then(() => (init_buildLessonsSummary(), buildLessonsSummary_exports));
34240
34958
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
34241
- const { registry: councilToolRegistry } = createBuiltinToolRegistry();
34959
+ const workPhase = getPhase();
34960
+ const { registry: councilToolRegistry } = createBuiltinToolRegistry({
34961
+ planMode: workPhase === "plan"
34962
+ });
34242
34963
  const workspaceCtx = createWorkspaceContext2();
34243
34964
  const workspaceReg = createWorkspaceToolRegistry2(workspaceCtx);
34244
34965
  for (const name of workspaceReg.list()) {
34245
34966
  const td = workspaceReg.get(name);
34246
- if (td) councilToolRegistry.register(td);
34967
+ if (!td) continue;
34968
+ councilToolRegistry.register(td);
34247
34969
  }
34970
+ const phaseRunMode = overrides.runMode ?? (workPhase === "plan" ? "design-phase" : "implementation");
34248
34971
  try {
34249
34972
  const { registerMcpTools: registerMcpTools2 } = await Promise.resolve().then(() => (init_mcpManager(), mcpManager_exports));
34250
34973
  const mcp = await registerMcpTools2(councilToolRegistry);
@@ -34273,7 +34996,7 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34273
34996
  let sliceDegraded = false;
34274
34997
  const PROVIDER_ERROR_ABORT_THRESHOLD = 2;
34275
34998
  try {
34276
- for await (const event of dispatchCouncil2(text, {
34999
+ for await (const event of dispatchCouncil2(effectiveText, {
34277
35000
  apiKey: envConfig.apiKey,
34278
35001
  model: envConfig.model,
34279
35002
  provider: "openai-compatible",
@@ -34287,20 +35010,49 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34287
35010
  // on and projected their identity onto the task.
34288
35011
  // v0.7.3: append the existing plan (if any) so a follow-up /council
34289
35012
  // continues it instead of re-planning from scratch.
35013
+ // v1.8.0: rolling conversation context so short answers bind across
35014
+ // council turns (same history store as the single-agent path).
34290
35015
  workspaceContext: [
34291
35016
  buildWorkspaceSummary2(process.cwd()),
34292
- buildPlanSummary2(process.cwd(), { userMessage: text }),
34293
- buildLessonsSummary2(process.cwd(), text)
35017
+ buildPlanSummary2(process.cwd(), { userMessage: effectiveText }),
35018
+ buildLessonsSummary2(process.cwd(), effectiveText),
35019
+ formatHistoryForCouncil(4)
34294
35020
  ].filter(Boolean).join("\n\n"),
34295
35021
  maxToolCallsPerTurn: councilMaxToolCalls,
34296
35022
  // v1.0: Zelari-mode per-slice overrides (memory RAG, forced run mode,
34297
35023
  // raised chairman budget). No-ops for a normal /council run.
34298
35024
  ...overrides.ragContext ? { ragContext: overrides.ragContext } : {},
34299
- ...overrides.runMode ? { runMode: overrides.runMode } : {},
35025
+ runMode: phaseRunMode,
34300
35026
  ...overrides.maxToolCallsChairman ? { maxToolCallsChairman: overrides.maxToolCallsChairman } : {},
34301
35027
  onCouncilStatus: (message) => {
34302
35028
  appendSystem(setMessages, message, Date.now());
34303
- }
35029
+ },
35030
+ // v1.8.0: pause council when a member asks a structured question.
35031
+ onClarification: setPicker ? (req) => new Promise((resolve) => {
35032
+ const choices = req.choices ?? [];
35033
+ if (choices.length < 2) {
35034
+ resolve(null);
35035
+ return;
35036
+ }
35037
+ setLastClarification({
35038
+ question: req.question,
35039
+ choices
35040
+ });
35041
+ let settled = false;
35042
+ const finish = (value) => {
35043
+ if (settled) return;
35044
+ settled = true;
35045
+ setPicker(null);
35046
+ resolve(value);
35047
+ };
35048
+ setPicker({
35049
+ kind: "clarification",
35050
+ title: req.question,
35051
+ items: choices.map((c) => ({ value: c, label: c })),
35052
+ onAnswer: (value) => finish(value),
35053
+ onCancel: () => finish(null)
35054
+ });
35055
+ }) : void 0
34304
35056
  })) {
34305
35057
  if (councilAborted) {
34306
35058
  if (writerRef.current) await writerRef.current.append(event);
@@ -34326,17 +35078,23 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34326
35078
  streamMemberId = memberId;
34327
35079
  }
34328
35080
  streamContent += event.delta;
34329
- setStreaming(commitStreaming, streamContent, event.ts, {
34330
- ...event.memberId ? { memberId: event.memberId } : {},
34331
- ...event.memberName ? { memberName: event.memberName } : {}
34332
- });
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
+ );
34333
35090
  } else {
34334
35091
  commitStreaming((prev2) => {
34335
35092
  const last = prev2[prev2.length - 1];
34336
35093
  if (last && last.role === "assistant" && last.id.startsWith("streaming-") && (last.memberId ?? null) === (event.memberId ?? null)) {
35094
+ const nextContent = cleanAgentContent(last.content + event.delta);
34337
35095
  return [
34338
35096
  ...prev2.slice(0, -1),
34339
- { ...last, content: last.content + event.delta }
35097
+ { ...last, content: nextContent }
34340
35098
  ];
34341
35099
  }
34342
35100
  return [
@@ -34344,7 +35102,7 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34344
35102
  {
34345
35103
  id: `streaming-${crypto.randomUUID()}`,
34346
35104
  role: "assistant",
34347
- content: event.delta,
35105
+ content: cleanAgentContent(event.delta),
34348
35106
  ts: event.ts,
34349
35107
  ...event.memberId ? { memberId: event.memberId } : {},
34350
35108
  ...event.memberName ? { memberName: event.memberName } : {}
@@ -34451,6 +35209,18 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34451
35209
  flushStreaming();
34452
35210
  if (useLiveModel) finalizeStreaming(setMessages, setLive);
34453
35211
  else finalizeStreamingAssistant(setMessages);
35212
+ if (membersCompleted > 0 || chairmanProducedOutput) {
35213
+ try {
35214
+ appendMessages([
35215
+ { role: "user", content: effectiveText },
35216
+ {
35217
+ role: "assistant",
35218
+ content: chairmanSynthesisText.trim() || "[council completed without chairman synthesis text]"
35219
+ }
35220
+ ]);
35221
+ } catch {
35222
+ }
35223
+ }
34454
35224
  const hookShouldRun = membersCompleted > 0 || chairmanProducedOutput;
34455
35225
  sliceRan = hookShouldRun;
34456
35226
  if (hookShouldRun) {
@@ -34473,7 +35243,7 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
34473
35243
  }
34474
35244
  const hook = await runPostCouncilHook2(workspaceCtx, {
34475
35245
  runMode: councilRunMode,
34476
- userMessage: text,
35246
+ userMessage: effectiveText,
34477
35247
  synthesisText: chairmanSynthesisText || void 0,
34478
35248
  degradedRun: degraded.degraded,
34479
35249
  degradedReasons: degraded.reasons
@@ -35123,6 +35893,27 @@ ${formatSkillList(availableSkills)}`
35123
35893
  }
35124
35894
  return { handled: true, kind: "mode_set" };
35125
35895
  }
35896
+ case "plan": {
35897
+ const goal = args.join(" ").trim();
35898
+ return {
35899
+ handled: true,
35900
+ kind: "phase_set",
35901
+ phaseTarget: "plan",
35902
+ ...goal ? { phaseGoal: goal } : {}
35903
+ };
35904
+ }
35905
+ case "build": {
35906
+ const goal = args.join(" ").trim();
35907
+ return {
35908
+ handled: true,
35909
+ kind: "phase_set",
35910
+ phaseTarget: "build",
35911
+ ...goal ? { phaseGoal: goal } : {}
35912
+ };
35913
+ }
35914
+ case "view-plan": {
35915
+ return { handled: true, kind: "view_plan" };
35916
+ }
35126
35917
  case "index": {
35127
35918
  return args[0] === "status" ? { handled: true, kind: "index_status" } : { handled: true, kind: "index_build" };
35128
35919
  }
@@ -35210,7 +36001,7 @@ ${formatSkillList(availableSkills)}`
35210
36001
  // src/cli/gitOps.ts
35211
36002
  import { execFile as execFile3 } from "node:child_process";
35212
36003
  import { promisify as promisify2 } from "node:util";
35213
- import path27 from "node:path";
36004
+ import path28 from "node:path";
35214
36005
  var execFileAsync2 = promisify2(execFile3);
35215
36006
  async function git2(cwd, args) {
35216
36007
  try {
@@ -35256,7 +36047,7 @@ async function undoWorkingChanges(opts = {}) {
35256
36047
  };
35257
36048
  }
35258
36049
  function defaultProjectRoot() {
35259
- return path27.resolve(__dirname, "..", "..", "..");
36050
+ return path28.resolve(__dirname, "..", "..", "..");
35260
36051
  }
35261
36052
 
35262
36053
  // src/cli/slashHandlers/git.ts
@@ -35664,15 +36455,15 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
35664
36455
 
35665
36456
  // src/cli/slashHandlers/promoteMember.ts
35666
36457
  import { promises as fs16 } from "node:fs";
35667
- import path29 from "node:path";
36458
+ import path31 from "node:path";
35668
36459
  import os10 from "node:os";
35669
36460
  async function handlePromoteMember(ctx, memberId) {
35670
36461
  try {
35671
36462
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
35672
36463
  const { skill, markdown } = promoteMember2(memberId);
35673
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path29.join(os10.homedir(), ".tmp", "zelari-code", "skills");
36464
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path31.join(os10.homedir(), ".tmp", "zelari-code", "skills");
35674
36465
  await fs16.mkdir(skillDir, { recursive: true });
35675
- const filePath = path29.join(skillDir, `${skill.id}.md`);
36466
+ const filePath = path31.join(skillDir, `${skill.id}.md`);
35676
36467
  await fs16.writeFile(filePath, markdown, "utf8");
35677
36468
  appendSystem(
35678
36469
  ctx.setMessages,
@@ -35689,29 +36480,29 @@ async function handlePromoteMember(ctx, memberId) {
35689
36480
  }
35690
36481
 
35691
36482
  // src/cli/branchManager.ts
35692
- import { promises as fs17, existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync16, mkdirSync as mkdirSync11, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
35693
- import path30 from "node:path";
36483
+ import { promises as fs17, existsSync as existsSync28, readFileSync as readFileSync24, writeFileSync as writeFileSync16, mkdirSync as mkdirSync11, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
36484
+ import path32 from "node:path";
35694
36485
  import os11 from "node:os";
35695
36486
  var META_FILENAME = "meta.json";
35696
36487
  var SESSIONS_SUBDIR = "sessions";
35697
36488
  function getBranchesBaseDir() {
35698
- return process.env.ANATHEMA_BRANCHES_DIR ?? path30.join(os11.homedir(), ".tmp", "zelari-code", "branches");
36489
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path32.join(os11.homedir(), ".tmp", "zelari-code", "branches");
35699
36490
  }
35700
36491
  function getSessionsBaseDir() {
35701
- return process.env.ANATHEMA_SESSIONS_DIR ?? path30.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
36492
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path32.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
35702
36493
  }
35703
36494
  function branchPathFor(name, baseDir) {
35704
- return path30.join(baseDir, name);
36495
+ return path32.join(baseDir, name);
35705
36496
  }
35706
36497
  function metaPathFor(name, baseDir) {
35707
- return path30.join(baseDir, name, META_FILENAME);
36498
+ return path32.join(baseDir, name, META_FILENAME);
35708
36499
  }
35709
36500
  function sessionsPathFor(name, baseDir) {
35710
- return path30.join(baseDir, name, SESSIONS_SUBDIR);
36501
+ return path32.join(baseDir, name, SESSIONS_SUBDIR);
35711
36502
  }
35712
36503
  function readBranchMeta(name, baseDir) {
35713
36504
  const metaPath = metaPathFor(name, baseDir);
35714
- if (!existsSync27(metaPath)) {
36505
+ if (!existsSync28(metaPath)) {
35715
36506
  throw new BranchNotFoundError(`Branch "${name}" not found`);
35716
36507
  }
35717
36508
  try {
@@ -35732,7 +36523,7 @@ function readBranchMeta(name, baseDir) {
35732
36523
  }
35733
36524
  function writeBranchMeta(name, baseDir, meta3) {
35734
36525
  const metaPath = metaPathFor(name, baseDir);
35735
- mkdirSync11(path30.dirname(metaPath), { recursive: true });
36526
+ mkdirSync11(path32.dirname(metaPath), { recursive: true });
35736
36527
  writeFileSync16(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
35737
36528
  }
35738
36529
  async function countSessions(name, baseDir) {
@@ -35771,7 +36562,7 @@ var SessionNotFoundError = class extends Error {
35771
36562
  };
35772
36563
  function branchExists(name, baseDir = getBranchesBaseDir()) {
35773
36564
  const bp = branchPathFor(name, baseDir);
35774
- return existsSync27(bp) && existsSync27(metaPathFor(name, baseDir));
36565
+ return existsSync28(bp) && existsSync28(metaPathFor(name, baseDir));
35775
36566
  }
35776
36567
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
35777
36568
  if (!name || name.trim().length === 0) {
@@ -35783,14 +36574,14 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
35783
36574
  if (branchExists(name, baseDir)) {
35784
36575
  throw new BranchAlreadyExistsError(name);
35785
36576
  }
35786
- const sourcePath = path30.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
35787
- if (!existsSync27(sourcePath)) {
36577
+ const sourcePath = path32.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
36578
+ if (!existsSync28(sourcePath)) {
35788
36579
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
35789
36580
  }
35790
36581
  const branchPath = branchPathFor(name, baseDir);
35791
36582
  const branchSessionsPath = sessionsPathFor(name, baseDir);
35792
36583
  mkdirSync11(branchSessionsPath, { recursive: true });
35793
- const destPath = path30.join(branchSessionsPath, `${fromSessionId}.jsonl`);
36584
+ const destPath = path32.join(branchSessionsPath, `${fromSessionId}.jsonl`);
35794
36585
  await fs17.copyFile(sourcePath, destPath);
35795
36586
  const meta3 = {
35796
36587
  name,
@@ -35817,7 +36608,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
35817
36608
  const results = [];
35818
36609
  for (const entry of entries) {
35819
36610
  const metaPath = metaPathFor(entry, baseDir);
35820
- if (!existsSync27(metaPath)) continue;
36611
+ if (!existsSync28(metaPath)) continue;
35821
36612
  try {
35822
36613
  const meta3 = readBranchMeta(entry, baseDir);
35823
36614
  const sessionCount = await countSessions(entry, baseDir);
@@ -35891,14 +36682,14 @@ async function handleBranchCheckout(ctx, branchName) {
35891
36682
 
35892
36683
  // src/cli/slashHandlers/workspace.ts
35893
36684
  import { promises as fs18 } from "node:fs";
35894
- import path31 from "node:path";
36685
+ import path33 from "node:path";
35895
36686
  async function handleWorkspaceShow(ctx, what) {
35896
36687
  try {
35897
- const zelari = path31.join(process.cwd(), ".zelari");
36688
+ const zelari = path33.join(process.cwd(), ".zelari");
35898
36689
  let content;
35899
36690
  switch (what) {
35900
36691
  case "plan": {
35901
- const planPath = path31.join(zelari, "plan.md");
36692
+ const planPath = path33.join(zelari, "plan.md");
35902
36693
  try {
35903
36694
  content = await fs18.readFile(planPath, "utf-8");
35904
36695
  } catch {
@@ -35907,7 +36698,7 @@ async function handleWorkspaceShow(ctx, what) {
35907
36698
  break;
35908
36699
  }
35909
36700
  case "decisions": {
35910
- const decisionsDir = path31.join(zelari, "decisions");
36701
+ const decisionsDir = path33.join(zelari, "decisions");
35911
36702
  try {
35912
36703
  const files = (await fs18.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
35913
36704
  if (files.length === 0) {
@@ -35917,7 +36708,7 @@ async function handleWorkspaceShow(ctx, what) {
35917
36708
  `];
35918
36709
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
35919
36710
  for (const f of files) {
35920
- const raw = await fs18.readFile(path31.join(decisionsDir, f), "utf-8");
36711
+ const raw = await fs18.readFile(path33.join(decisionsDir, f), "utf-8");
35921
36712
  const { meta: meta3, body } = parseFrontmatter2(raw);
35922
36713
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
35923
36714
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -35930,7 +36721,7 @@ async function handleWorkspaceShow(ctx, what) {
35930
36721
  break;
35931
36722
  }
35932
36723
  case "risks": {
35933
- const risksPath = path31.join(zelari, "risks.md");
36724
+ const risksPath = path33.join(zelari, "risks.md");
35934
36725
  try {
35935
36726
  content = await fs18.readFile(risksPath, "utf-8");
35936
36727
  } catch {
@@ -35939,7 +36730,7 @@ async function handleWorkspaceShow(ctx, what) {
35939
36730
  break;
35940
36731
  }
35941
36732
  case "agents": {
35942
- const agentsPath = path31.join(process.cwd(), "AGENTS.MD");
36733
+ const agentsPath = path33.join(process.cwd(), "AGENTS.MD");
35943
36734
  try {
35944
36735
  content = await fs18.readFile(agentsPath, "utf-8");
35945
36736
  } catch {
@@ -35948,7 +36739,7 @@ async function handleWorkspaceShow(ctx, what) {
35948
36739
  break;
35949
36740
  }
35950
36741
  case "docs": {
35951
- const docsDir = path31.join(zelari, "docs");
36742
+ const docsDir = path33.join(zelari, "docs");
35952
36743
  try {
35953
36744
  const files = (await fs18.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
35954
36745
  content = files.length ? `# Docs (${files.length})
@@ -35990,7 +36781,7 @@ async function handleWorkspaceReset(ctx, force) {
35990
36781
  return;
35991
36782
  }
35992
36783
  try {
35993
- const target = path31.join(process.cwd(), ".zelari");
36784
+ const target = path33.join(process.cwd(), ".zelari");
35994
36785
  await fs18.rm(target, { recursive: true, force: true });
35995
36786
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
35996
36787
  } catch (err) {
@@ -36349,11 +37140,11 @@ function handleModelsRefresh(ctx) {
36349
37140
  }
36350
37141
 
36351
37142
  // src/cli/slashHandlers/skills.ts
36352
- import path32 from "node:path";
37143
+ import path34 from "node:path";
36353
37144
  import os12 from "node:os";
36354
37145
 
36355
37146
  // src/cli/skillHistory.ts
36356
- import { promises as fs19, existsSync as existsSync28, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync12 } from "node:fs";
37147
+ import { promises as fs19, existsSync as existsSync29, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync12 } from "node:fs";
36357
37148
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
36358
37149
  async function readSkillHistory(file2) {
36359
37150
  let raw = "";
@@ -36451,7 +37242,7 @@ async function applySteerInterrupt(options) {
36451
37242
 
36452
37243
  // src/cli/slashHandlers/skills.ts
36453
37244
  async function handleSkillStats(ctx, skillId) {
36454
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path32.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
37245
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path34.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
36455
37246
  try {
36456
37247
  const records = await readSkillHistory(historyFile);
36457
37248
  const stats = getSkillStats(records, skillId);
@@ -36467,7 +37258,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
36467
37258
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
36468
37259
  return;
36469
37260
  }
36470
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path32.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
37261
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path34.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
36471
37262
  try {
36472
37263
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
36473
37264
  appendSystem(ctx.setMessages, formatted);
@@ -36593,6 +37384,11 @@ function useSlashDispatch(params) {
36593
37384
  setSessionId(id);
36594
37385
  setMessages([]);
36595
37386
  setSessionActive(false);
37387
+ try {
37388
+ const { clearHistory: clearHistory2 } = await Promise.resolve().then(() => (init_conversationContext(), conversationContext_exports));
37389
+ clearHistory2();
37390
+ } catch {
37391
+ }
36596
37392
  onNewSession?.(id);
36597
37393
  }
36598
37394
  setInput("");
@@ -36825,6 +37621,40 @@ function useSlashDispatch(params) {
36825
37621
  setInput("");
36826
37622
  return;
36827
37623
  }
37624
+ if (result.kind === "phase_set" && result.phaseTarget) {
37625
+ const { setPhase: setPhase2 } = await Promise.resolve().then(() => (init_phaseState(), phaseState_exports));
37626
+ const { describePhase: describePhase2 } = await Promise.resolve().then(() => (init_phase(), phase_exports));
37627
+ setPhase2(result.phaseTarget);
37628
+ params.onPhaseChange?.(result.phaseTarget);
37629
+ appendSystem(setMessages, `[phase] ${describePhase2(result.phaseTarget)}`);
37630
+ setInput("");
37631
+ if (result.phaseGoal) {
37632
+ appendUser(setMessages, result.phaseGoal);
37633
+ setSessionActive(true);
37634
+ if (mode === "zelari") {
37635
+ await dispatchZelariPrompt(result.phaseGoal);
37636
+ } else if (mode === "council") {
37637
+ await dispatchCouncilPrompt(result.phaseGoal);
37638
+ } else {
37639
+ await dispatchPrompt(result.phaseGoal);
37640
+ }
37641
+ }
37642
+ return;
37643
+ }
37644
+ if (result.kind === "view_plan") {
37645
+ try {
37646
+ const { buildPlanSummary: buildPlanSummary2 } = await Promise.resolve().then(() => (init_workspaceSummary(), workspaceSummary_exports));
37647
+ const summary = buildPlanSummary2(process.cwd(), { userMessage: "" });
37648
+ appendSystem(
37649
+ setMessages,
37650
+ summary?.trim() ? summary : "[plan] No plan found. Run /plan <goal> or /workspace show plan after a design council."
37651
+ );
37652
+ } catch {
37653
+ appendSystem(setMessages, "[plan] Unable to read plan (workspace summary failed).");
37654
+ }
37655
+ setInput("");
37656
+ return;
37657
+ }
36828
37658
  if (result.kind === "promote_member" && result.promoteMemberId) {
36829
37659
  await handlePromoteMember(baseCtx, result.promoteMemberId);
36830
37660
  setInput("");
@@ -36856,6 +37686,11 @@ function useSlashDispatch(params) {
36856
37686
  }
36857
37687
  if (result.kind === "clear") {
36858
37688
  handleClearChat(setMessages, setSessionActive);
37689
+ try {
37690
+ const { clearHistory: clearHistory2 } = await Promise.resolve().then(() => (init_conversationContext(), conversationContext_exports));
37691
+ clearHistory2();
37692
+ } catch {
37693
+ }
36859
37694
  params.onClear?.();
36860
37695
  setInput("");
36861
37696
  return;
@@ -36952,8 +37787,9 @@ function useBatchedMessages(state2, setState, cadenceMs = 16) {
36952
37787
  // src/cli/hooks/useTerminalSize.ts
36953
37788
  import { useEffect as useEffect6, useState as useState7 } from "react";
36954
37789
  import { useStdout as useStdout2 } from "ink";
37790
+ var DEFAULT_COALESCE_MS = 120;
36955
37791
  function useTerminalSize(options = {}) {
36956
- const { defaults = { columns: 80, rows: 24 }, coalesceMs = 16 } = options;
37792
+ const { defaults = { columns: 80, rows: 24 }, coalesceMs = DEFAULT_COALESCE_MS } = options;
36957
37793
  const { stdout } = useStdout2();
36958
37794
  const [size, setSize] = useState7({
36959
37795
  columns: stdout?.columns ?? defaults.columns,
@@ -36961,25 +37797,25 @@ function useTerminalSize(options = {}) {
36961
37797
  });
36962
37798
  useEffect6(() => {
36963
37799
  if (!stdout) return;
36964
- setSize({
37800
+ const read = () => ({
36965
37801
  columns: stdout.columns ?? defaults.columns,
36966
37802
  rows: stdout.rows ?? defaults.rows
36967
37803
  });
37804
+ const commit = (next) => {
37805
+ setSize(
37806
+ (prev2) => prev2.columns === next.columns && prev2.rows === next.rows ? prev2 : next
37807
+ );
37808
+ };
37809
+ commit(read());
36968
37810
  let rafId = null;
36969
37811
  const handleResize = () => {
36970
37812
  if (coalesceMs <= 0) {
36971
- setSize({
36972
- columns: stdout.columns ?? defaults.columns,
36973
- rows: stdout.rows ?? defaults.rows
36974
- });
37813
+ commit(read());
36975
37814
  return;
36976
37815
  }
36977
37816
  if (rafId !== null) clearTimeout(rafId);
36978
37817
  rafId = setTimeout(() => {
36979
- setSize({
36980
- columns: stdout.columns ?? defaults.columns,
36981
- rows: stdout.rows ?? defaults.rows
36982
- });
37818
+ commit(read());
36983
37819
  rafId = null;
36984
37820
  }, coalesceMs);
36985
37821
  };
@@ -37008,6 +37844,7 @@ function App() {
37008
37844
  const [sessionStats, setSessionStats] = useState8({ totalTokens: 0, totalCostUsd: 0, cachedTokens: 0 });
37009
37845
  const [clearEpoch, setClearEpoch] = useState8(0);
37010
37846
  const [mode, setMode] = useState8("agent");
37847
+ const [phase2, setPhaseUi] = useState8("build");
37011
37848
  const [picker, setPicker] = useState8(null);
37012
37849
  const activeProviderSpec = getActiveProvider();
37013
37850
  const activeModel = providerConfig.modelByProvider[activeProviderSpec.id];
@@ -37086,7 +37923,8 @@ function App() {
37086
37923
  openPicker: setPicker,
37087
37924
  onNewSession,
37088
37925
  onExit,
37089
- onClear
37926
+ onClear,
37927
+ onPhaseChange: setPhaseUi
37090
37928
  });
37091
37929
  const onPickerSelect = useCallback5((value) => {
37092
37930
  if (!picker) return;
@@ -37098,54 +37936,90 @@ function App() {
37098
37936
  const cmd = `${picker.commandPrefix} ${value}`;
37099
37937
  void handleSubmit(cmd);
37100
37938
  }, [picker, handleSubmit]);
37101
- const onPickerCancel = useCallback5(() => setPicker(null), []);
37939
+ const onPickerCancel = useCallback5(() => {
37940
+ if (picker?.kind === "clarification") {
37941
+ picker.onCancel?.();
37942
+ }
37943
+ setPicker(null);
37944
+ }, [picker]);
37945
+ const bannerColsRef = useRef6(null);
37946
+ if (bannerColsRef.current === null) {
37947
+ bannerColsRef.current = size.columns > 0 ? size.columns : 80;
37948
+ }
37102
37949
  const banner = useMemo(() => {
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
+ });
37103
37961
  return {
37104
37962
  id: "banner-once",
37105
37963
  role: "system",
37106
37964
  ts: 0,
37107
- content: `zelari-code v${VERSION} \u2014 ${activeProviderSpec.id}/${activeModel}
37108
- cwd: ${cwd}
37109
- /help for commands \xB7 /skill <name> \xB7 shift+tab toggles agent/council`
37965
+ content
37110
37966
  };
37111
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]);
37112
37972
  const staticKey = `${session.sessionId || "pre-bootstrap"}-${clearEpoch}`;
37113
37973
  const staticItems = session.sessionId ? [banner, ...session.messages] : [];
37114
- const showSidebar = shouldShowSidebar(size.columns, size.rows);
37115
- 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(
37116
- SelectList,
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,
37117
37977
  {
37118
- title: picker.title,
37119
- items: picker.items,
37120
- onSelect: onPickerSelect,
37121
- onCancel: onPickerCancel,
37122
- maxVisible: Math.max(4, Math.min(10, size.rows - 10))
37123
- }
37124
- ) : /* @__PURE__ */ React9.createElement(
37125
- InputBar,
37126
- {
37127
- value: input,
37128
- onChange: setInput,
37129
- onSubmit: handleSubmit,
37130
- disabled: busy
37131
- }
37132
- ), /* @__PURE__ */ React9.createElement(
37133
- StatusBar,
37134
- {
37135
- model: activeModel,
37136
- provider: activeProviderSpec.id,
37137
- sessionId: session.sessionId ? session.sessionId.slice(0, 8) : "...",
37138
- sessionActive: session.sessionActive,
37139
- queueCount: chatTurn.queueCount,
37140
- busy,
37141
- mode,
37142
- cwd,
37143
- elapsedMs: timer.elapsedMs,
37144
- lastMs: timer.lastMs,
37145
- costUsd: sessionStats.totalCostUsd,
37146
- cachedTokens: sessionStats.cachedTokens
37147
- }
37148
- )), 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
+ ));
37149
38023
  }
37150
38024
 
37151
38025
  // src/cli/components/SplashScreen.tsx
@@ -37329,28 +38203,28 @@ var CHOICE_LATER = "__later__";
37329
38203
  var CHOICE_NEVER = "__never__";
37330
38204
  function PluginGate({ cwd, children }) {
37331
38205
  const { isRawModeSupported } = useStdin4();
37332
- const [phase, setPhase] = useState10("detecting");
38206
+ const [phase2, setPhase2] = useState10("detecting");
37333
38207
  const [queue, setQueue] = useState10([]);
37334
38208
  const [current, setCurrent] = useState10(null);
37335
38209
  const [result, setResult] = useState10(null);
37336
38210
  const skipGate = process.env.ZELARI_NO_PLUGIN_PROMPT === "1" || isRawModeSupported !== true;
37337
38211
  useEffect9(() => {
37338
38212
  if (skipGate) {
37339
- setPhase("done");
38213
+ setPhase2("done");
37340
38214
  return;
37341
38215
  }
37342
38216
  let cancelled = false;
37343
38217
  void detectMissingPlugins(cwd).then((missing) => {
37344
38218
  if (cancelled) return;
37345
38219
  if (missing.length === 0) {
37346
- setPhase("done");
38220
+ setPhase2("done");
37347
38221
  return;
37348
38222
  }
37349
38223
  setQueue(missing);
37350
38224
  setCurrent(missing[0] ?? null);
37351
- setPhase(missing[0] ? "prompting" : "done");
38225
+ setPhase2(missing[0] ? "prompting" : "done");
37352
38226
  }).catch(() => {
37353
- if (!cancelled) setPhase("done");
38227
+ if (!cancelled) setPhase2("done");
37354
38228
  });
37355
38229
  return () => {
37356
38230
  cancelled = true;
@@ -37360,12 +38234,12 @@ function PluginGate({ cwd, children }) {
37360
38234
  setQueue((q) => {
37361
38235
  const rest = q.slice(1);
37362
38236
  if (rest.length === 0) {
37363
- setPhase("done");
38237
+ setPhase2("done");
37364
38238
  setCurrent(null);
37365
38239
  return rest;
37366
38240
  }
37367
38241
  setCurrent(rest[0] ?? null);
37368
- setPhase(rest[0] ? "prompting" : "done");
38242
+ setPhase2(rest[0] ? "prompting" : "done");
37369
38243
  setResult(null);
37370
38244
  return rest;
37371
38245
  });
@@ -37379,31 +38253,49 @@ function PluginGate({ cwd, children }) {
37379
38253
  markDontAskAgain(current.id);
37380
38254
  advance();
37381
38255
  } else if (value === CHOICE_INSTALL) {
37382
- setPhase("installing");
37383
- void installPlugin(current, cwd).then((r) => {
38256
+ setPhase2("installing");
38257
+ void installPlugin(current, cwd).then(async (r) => {
38258
+ if (r.ok) {
38259
+ let present = false;
38260
+ try {
38261
+ present = await current.detect(cwd);
38262
+ } catch {
38263
+ present = false;
38264
+ }
38265
+ if (!present) {
38266
+ setResult({
38267
+ ok: false,
38268
+ output: r.output,
38269
+ exitCode: r.exitCode,
38270
+ error: `npm reported success but ${current.label} is still not detectable from this workspace. Try \`/plugins install ${current.id}\` or see the post-install hint.`
38271
+ });
38272
+ setPhase2("result");
38273
+ return;
38274
+ }
38275
+ }
37384
38276
  setResult(r);
37385
- setPhase("result");
38277
+ setPhase2("result");
37386
38278
  }).catch(() => {
37387
38279
  setResult({ ok: false, output: "", exitCode: null, error: "unexpected error" });
37388
- setPhase("result");
38280
+ setPhase2("result");
37389
38281
  });
37390
38282
  }
37391
38283
  },
37392
38284
  [current, cwd, advance]
37393
38285
  );
37394
- if (phase === "done") {
38286
+ if (phase2 === "done") {
37395
38287
  return /* @__PURE__ */ React11.createElement(React11.Fragment, null, children);
37396
38288
  }
37397
- if (phase === "detecting") {
38289
+ if (phase2 === "detecting") {
37398
38290
  return /* @__PURE__ */ React11.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "Checking for optional tool plugins\u2026"));
37399
38291
  }
37400
38292
  if (!current) {
37401
38293
  return /* @__PURE__ */ React11.createElement(React11.Fragment, null, children);
37402
38294
  }
37403
- if (phase === "installing") {
38295
+ if (phase2 === "installing") {
37404
38296
  return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, "\u23F3 Installing ", current.label, "\u2026"), /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "npm install ", current.installScope === "global" ? "-g" : "-D", " ", current.npmPackage));
37405
38297
  }
37406
- if (phase === "result") {
38298
+ if (phase2 === "result") {
37407
38299
  const ok = result?.ok === true;
37408
38300
  return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, { color: ok ? "green" : "red" }, ok ? "\u2713" : "\u2717", " ", ok ? "Installed" : "Install failed", ": ", current.label), !ok && result?.error ? /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, result.error) : null, ok && current.postInstallHint ? /* @__PURE__ */ React11.createElement(Text11, { color: "yellow" }, " \u2192 ", current.postInstallHint) : null, /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "Press any key to continue\u2026"), /* @__PURE__ */ React11.createElement(ContinueKey, { onContinue: advance }));
37409
38301
  }
@@ -37441,9 +38333,9 @@ function ContinueKey({ onContinue }) {
37441
38333
  init_providerConfig();
37442
38334
 
37443
38335
  // src/cli/wizard/firstRun.ts
37444
- import { existsSync as existsSync29 } from "node:fs";
38336
+ import { existsSync as existsSync30 } from "node:fs";
37445
38337
  function shouldRunWizard(input) {
37446
- const exists = input.exists ?? existsSync29;
38338
+ const exists = input.exists ?? existsSync30;
37447
38339
  if (input.hasResetConfigFlag) {
37448
38340
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
37449
38341
  }
@@ -37801,6 +38693,7 @@ function emitEvent(event) {
37801
38693
  init_harness();
37802
38694
  init_toolRegistry();
37803
38695
  init_skills2();
38696
+ init_envNumber();
37804
38697
  async function runHeadless(opts) {
37805
38698
  const { provider, model } = resolveHeadlessProvider(opts);
37806
38699
  const key = await resolveHeadlessKey(provider);
@@ -37989,7 +38882,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
37989
38882
 
37990
38883
  // src/cli/skillsMd.ts
37991
38884
  init_skills2();
37992
- import { existsSync as existsSync30, readdirSync as readdirSync4, readFileSync as readFileSync25 } from "node:fs";
38885
+ import { existsSync as existsSync31, readdirSync as readdirSync4, readFileSync as readFileSync25 } from "node:fs";
37993
38886
  import { join as join23 } from "node:path";
37994
38887
  import { homedir as homedir6 } from "node:os";
37995
38888
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
@@ -38067,7 +38960,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
38067
38960
  const summary = { loaded: [], skipped: [] };
38068
38961
  const seen = new Set(options.existingIds ?? []);
38069
38962
  for (const dir of skillMdSearchDirs(projectRoot)) {
38070
- if (!existsSync30(dir)) continue;
38963
+ if (!existsSync31(dir)) continue;
38071
38964
  let entries;
38072
38965
  try {
38073
38966
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -38076,7 +38969,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
38076
38969
  }
38077
38970
  for (const entry of entries) {
38078
38971
  const skillPath = join23(dir, entry, "SKILL.md");
38079
- if (!existsSync30(skillPath)) continue;
38972
+ if (!existsSync31(skillPath)) continue;
38080
38973
  try {
38081
38974
  const parsed = parseSkillMd(readFileSync25(skillPath, "utf8"), skillPath);
38082
38975
  if (!parsed) {