zelari-code 1.13.0 → 1.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -741,10 +741,11 @@ var init_providerConfig = __esm({
741
741
  DEFAULTS = {
742
742
  activeProviderId: "openai-compatible",
743
743
  modelByProvider: {
744
- "openai-compatible": "grok-4",
744
+ // grok-4.5: flagship; reasoning_effort defaults to "high" on the xAI API
745
+ "openai-compatible": "grok-4.5",
745
746
  "minimax": "MiniMax-M2.5",
746
747
  "glm": "glm-4.6",
747
- "grok": "grok-4",
748
+ "grok": "grok-4.5",
748
749
  "deepseek": "deepseek-v4-pro",
749
750
  "custom": ""
750
751
  },
@@ -18345,7 +18346,9 @@ Earlier members' outputs are shared context. Build on them; do not re-derive or
18345
18346
 
18346
18347
  - Be concise and structured. Prefer short markdown sections and bullets over walls of text.
18347
18348
  - Be proactive but not reckless: if a single missing fact would change the design, ask one focused question; otherwise make a documented assumption and proceed.
18348
- - Prefer action over description when the user wants code, fixes, or repo changes \u2014 use tools.
18349
+ - Prefer action over description when the user wants code, fixes, or repo changes \u2014 use tools (write_file/edit_file/bash), not prose-only plans.
18350
+ - When the user confirms a plan ("procedi", "s\xEC", "implementa"), the prior plan is work TO DO on disk. Reading alone is incomplete.
18351
+ - Never claim work is "already implemented" without verifying the real files (and writing if gaps remain).
18349
18352
  - Think step by step internally; surface conclusions and a brief rationale, not a full chain of thought.`
18350
18353
  };
18351
18354
  BEHAVIOR_COUNCIL = {
@@ -18416,11 +18419,12 @@ Earlier members' outputs are shared context. Build on them; do not re-derive or
18416
18419
  content: `# Coding Practices
18417
18420
 
18418
18421
  - **Read before edit**: open relevant files (and nearby callers/tests) before changing code.
18422
+ - **Then write**: if the task is to implement, follow reads with write_file/edit_file in the same turn. Do not end after exploration.
18419
18423
  - **Minimal diffs**: change only what the task requires; match existing style and patterns.
18420
18424
  - **Don't invent**: no fake APIs, deps, or config keys \u2014 discover from the tree or package manifests.
18421
18425
  - **Verify**: after non-trivial edits, run the project's tests/typecheck/build when available; fix failures you introduced.
18422
18426
  - **Use project scripts**: prefer package.json / Makefile / existing tooling over ad-hoc commands.
18423
- - **Finish**: summarize what changed and how to verify.`
18427
+ - **Finish**: list the paths you wrote/edited and how to verify. If you wrote nothing, say so honestly \u2014 do not invent a done report.`
18424
18428
  };
18425
18429
  CLARIFICATION_PROTOCOL_MODULE = {
18426
18430
  type: "custom",
@@ -19832,7 +19836,7 @@ ${cached2}`
19832
19836
  const key = hashToolCall(tt.name, tt.args);
19833
19837
  return !turnToolCalls.some((n) => hashToolCall(n.name, n.args) === key);
19834
19838
  });
19835
- if ((/---TOOLS---/.test(turnText) || /minimax|invoke\s+name=/i.test(turnText)) && textTools.length === 0) {
19839
+ if ((/---TOOLS---/.test(turnText) || /<\/?minimax:tool_call\b/i.test(turnText) || /invoke\s+name\s*=/i.test(turnText) || /\]\s*<\s*\]\s*minimax\s*\[/i.test(turnText)) && textTools.length === 0) {
19836
19840
  const parseErr = createBrainEvent("error", this.sessionId, {
19837
19841
  severity: "recoverable",
19838
19842
  message: "Found text-format tool block but parse failed; tool calls were not executed. Prefer native tool_call, or ---TOOLS--- with ONE valid JSON array.",
@@ -19899,6 +19903,9 @@ ${cached2}`
19899
19903
  finishRef.value = "tool_calls";
19900
19904
  }
19901
19905
  }
19906
+ if (turnToolResults.length > 0) {
19907
+ finishRef.value = "tool_calls";
19908
+ }
19902
19909
  if (finishRef.value === "tool_calls" && turnToolCalls.length === 0 && turnToolResults.length === 0) {
19903
19910
  const truncErr = createBrainEvent("error", this.sessionId, {
19904
19911
  severity: "recoverable",
@@ -20364,6 +20371,38 @@ function openaiCompatibleProvider(config2) {
20364
20371
  const decoder = new TextDecoder();
20365
20372
  let buffer = "";
20366
20373
  const toolCallAccumulator = /* @__PURE__ */ new Map();
20374
+ let emittedToolCall = false;
20375
+ let reasoningDetailsBuf = "";
20376
+ const tryParseArgs = (raw) => {
20377
+ const t = raw.trim();
20378
+ if (t.length === 0) return {};
20379
+ try {
20380
+ const parsed = JSON.parse(t);
20381
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
20382
+ return parsed;
20383
+ }
20384
+ return null;
20385
+ } catch {
20386
+ return null;
20387
+ }
20388
+ };
20389
+ const flushToolAccumulator = function* () {
20390
+ const entries = [...toolCallAccumulator.entries()].sort((a, b) => a[0] - b[0]);
20391
+ for (const [idx, existing] of entries) {
20392
+ if (!existing.name) continue;
20393
+ const args = tryParseArgs(existing.argsJson);
20394
+ if (args === null) continue;
20395
+ toolCallAccumulator.delete(idx);
20396
+ emittedToolCall = true;
20397
+ yield {
20398
+ kind: "tool_call",
20399
+ toolCallId: existing.id || `tc-${idx}`,
20400
+ toolName: existing.name,
20401
+ args
20402
+ };
20403
+ }
20404
+ toolCallAccumulator.clear();
20405
+ };
20367
20406
  try {
20368
20407
  while (true) {
20369
20408
  const { value, done } = await reader.read();
@@ -20376,7 +20415,11 @@ function openaiCompatibleProvider(config2) {
20376
20415
  if (!trimmed.startsWith("data:")) continue;
20377
20416
  const data = trimmed.slice(5).trim();
20378
20417
  if (data === "[DONE]") {
20379
- yield { kind: "finish", reason: "stop" };
20418
+ yield* flushToolAccumulator();
20419
+ yield {
20420
+ kind: "finish",
20421
+ reason: emittedToolCall ? "tool_calls" : "stop"
20422
+ };
20380
20423
  return;
20381
20424
  }
20382
20425
  try {
@@ -20405,6 +20448,22 @@ function openaiCompatibleProvider(config2) {
20405
20448
  if (typeof reasoning === "string" && reasoning.length > 0) {
20406
20449
  yield { kind: "thinking", delta: reasoning };
20407
20450
  }
20451
+ const details = delta?.reasoning_details;
20452
+ if (Array.isArray(details)) {
20453
+ for (const d of details) {
20454
+ if (!d || typeof d !== "object") continue;
20455
+ const t = d.text;
20456
+ if (typeof t !== "string" || t.length === 0) continue;
20457
+ if (t.startsWith(reasoningDetailsBuf)) {
20458
+ const piece = t.slice(reasoningDetailsBuf.length);
20459
+ reasoningDetailsBuf = t;
20460
+ if (piece.length > 0) yield { kind: "thinking", delta: piece };
20461
+ } else {
20462
+ reasoningDetailsBuf += t;
20463
+ yield { kind: "thinking", delta: t };
20464
+ }
20465
+ }
20466
+ }
20408
20467
  if (Array.isArray(delta?.tool_calls)) {
20409
20468
  for (const tc of delta.tool_calls) {
20410
20469
  const idx = tc.index ?? 0;
@@ -20418,27 +20477,34 @@ function openaiCompatibleProvider(config2) {
20418
20477
  if (tc.function?.arguments) existing.argsJson += tc.function.arguments;
20419
20478
  toolCallAccumulator.set(idx, existing);
20420
20479
  if (existing.argsJson.trim().endsWith("}")) {
20421
- try {
20422
- const parsedArgs = JSON.parse(existing.argsJson);
20480
+ const parsedArgs = tryParseArgs(existing.argsJson);
20481
+ if (parsedArgs !== null && existing.name) {
20423
20482
  toolCallAccumulator.delete(idx);
20483
+ emittedToolCall = true;
20424
20484
  yield {
20425
20485
  kind: "tool_call",
20426
20486
  toolCallId: existing.id || `tc-${idx}`,
20427
20487
  toolName: existing.name,
20428
20488
  args: parsedArgs
20429
20489
  };
20430
- } catch {
20431
20490
  }
20432
20491
  }
20433
20492
  }
20434
20493
  }
20435
20494
  if (choice?.finish_reason) {
20436
- yield { kind: "finish", reason: choice.finish_reason };
20495
+ yield* flushToolAccumulator();
20496
+ const reason = choice.finish_reason === "stop" && emittedToolCall ? "tool_calls" : choice.finish_reason;
20497
+ yield { kind: "finish", reason };
20437
20498
  }
20438
20499
  } catch {
20439
20500
  }
20440
20501
  }
20441
20502
  }
20503
+ yield* flushToolAccumulator();
20504
+ yield {
20505
+ kind: "finish",
20506
+ reason: emittedToolCall ? "tool_calls" : "stop"
20507
+ };
20442
20508
  } finally {
20443
20509
  reader.releaseLock();
20444
20510
  }
@@ -24757,7 +24823,12 @@ function parseThinking(text) {
24757
24823
  }
24758
24824
  function cleanAgentContent(text, opts = {}) {
24759
24825
  const stripQuestion = opts.stripQuestion !== false;
24760
- 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, "");
24826
+ const stripThink = opts.stripThink !== false;
24827
+ let out = text;
24828
+ if (stripThink) {
24829
+ out = out.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, "").replace(/<think(?:ing)?>[\s\S]*$/gi, "").replace(/<\/think(?:ing)?>/gi, "");
24830
+ }
24831
+ out = out.replace(/<minimax:tool_call>[\s\S]*?<\/minimax:tool_call>/g, "");
24761
24832
  if (stripQuestion) {
24762
24833
  out = out.replace(/---QUESTION---[\s\S]*?---END---/g, "");
24763
24834
  }
@@ -26988,14 +27059,18 @@ var conversationContext_exports = {};
26988
27059
  __export(conversationContext_exports, {
26989
27060
  _resetConversationContextForTests: () => _resetConversationContextForTests,
26990
27061
  appendMessages: () => appendMessages,
27062
+ buildAgentUserWithHistory: () => buildAgentUserWithHistory,
27063
+ buildContinueUserMessage: () => buildContinueUserMessage,
26991
27064
  buildCouncilTaskWithHistory: () => buildCouncilTaskWithHistory,
26992
27065
  clearHistory: () => clearHistory,
26993
27066
  compactInPlace: () => compactInPlace,
27067
+ expectsDiskImplementation: () => expectsDiskImplementation,
26994
27068
  formatHistoryForCouncil: () => formatHistoryForCouncil,
26995
27069
  formatHistoryMessages: () => formatHistoryMessages,
26996
27070
  getHistory: () => getHistory,
26997
27071
  getLastClarification: () => getLastClarification,
26998
27072
  hydrateHistory: () => hydrateHistory,
27073
+ isShortContinueReply: () => isShortContinueReply,
26999
27074
  maybeAnchorShortAnswer: () => maybeAnchorShortAnswer,
27000
27075
  serializeHistory: () => serializeHistory,
27001
27076
  setHistory: () => setHistory,
@@ -27078,22 +27153,67 @@ ${body.slice(body.length - maxTotalChars)}`;
27078
27153
  }
27079
27154
  return body;
27080
27155
  }
27081
- function buildCouncilTaskWithHistory(task, prior) {
27082
- const messages = prior ?? [];
27156
+ function isShortContinueReply(task) {
27083
27157
  const trimmed = task.trim();
27084
- let userPart = maybeAnchorShortAnswer(task) ?? task;
27085
- if (messages.length > 0 && (SHORT_CONTINUE.test(trimmed) || trimmed.length <= 40 && !trimmed.includes("\n"))) {
27086
- const lastAsst = [...messages].reverse().find((m) => m.role === "assistant" && m.content.trim());
27087
- if (lastAsst && SHORT_CONTINUE.test(trimmed)) {
27088
- userPart = `The user says "${trimmed}" \u2014 continue from the prior conversation. Do NOT restart from zero, do NOT re-ask for the overall goal, and do NOT ignore the prior plan/risks/decisions.
27158
+ if (!trimmed) return false;
27159
+ if (SHORT_CONTINUE.test(trimmed)) return true;
27160
+ if (trimmed.length <= 80 && !trimmed.includes("\n") && SHORT_CONTINUE_LOOSE.test(trimmed)) {
27161
+ return true;
27162
+ }
27163
+ return false;
27164
+ }
27165
+ function buildContinueUserMessage(task, prior, opts) {
27166
+ if (!isShortContinueReply(task) || prior.length === 0) return null;
27167
+ const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
27168
+ if (!lastAsst) return null;
27169
+ const max = opts?.maxPriorChars ?? 8e3;
27170
+ const trimmed = task.trim();
27171
+ return `The user says "${trimmed}" \u2014 this is a CONTINUATION of an existing multi-turn session (the Desktop phase may have switched plan\u2194build; mode may have changed). This is NOT a new conversation and you DO have prior context below.
27089
27172
 
27090
- ## Prior assistant output (authoritative context)
27091
- ${truncate(lastAsst.content, 4500)}
27173
+ ## CRITICAL \u2014 plan text \u2260 done on disk
27174
+ The prior assistant output is a PLAN / SPEC / PROPOSAL (or analysis). It is NOT proof that project files already contain those changes.
27175
+ The user CONFIRMED the plan and wants you to IMPLEMENT it ON DISK NOW.
27176
+ - You MUST use write_file and/or edit_file (and bash when needed) to apply every planned change.
27177
+ - Reading files alone is incomplete. Do not stop after read_file/list_files/grep.
27178
+ - Do NOT claim "already implemented" / "tutto fatto" unless you verified the changes exist on disk in THIS turn (read after your own successful writes).
27179
+ - Do NOT restart from zero or re-ask for the overall goal.
27180
+
27181
+ ## Prior assistant output (plan to implement \u2014 authoritative)
27182
+ ${truncate(lastAsst.content, max)}
27092
27183
 
27093
27184
  ## Instruction
27094
- Proceed with the next concrete steps implied by that context (implementation when the work phase is build; otherwise the next planned actions).`;
27185
+ Implement the plan on disk now with mutating tools, then briefly list the files you wrote/edited.`;
27186
+ }
27187
+ function expectsDiskImplementation(task, phase2, prior) {
27188
+ if ((phase2 ?? "build") === "plan") return false;
27189
+ const trimmed = task.trim();
27190
+ if (!trimmed) return false;
27191
+ if (isShortContinueReply(trimmed)) return true;
27192
+ if (/\b(implement|implementa|scrivi|scriviamo|applica|modifica|fix|write|edit|crea|aggiungi|aggiorna|apply|patch)\b/i.test(
27193
+ trimmed
27194
+ )) {
27195
+ return true;
27196
+ }
27197
+ if (prior && prior.length > 0 && isShortContinueReply(trimmed)) {
27198
+ const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
27199
+ if (lastAsst && /\b(se confermi|passo alla|scriv|implement|on disk|write_file|modifiche proposte|riepilogo delle modifiche)\b/i.test(
27200
+ lastAsst.content
27201
+ )) {
27202
+ return true;
27095
27203
  }
27096
27204
  }
27205
+ return false;
27206
+ }
27207
+ function buildCouncilTaskWithHistory(task, prior) {
27208
+ const messages = prior ?? [];
27209
+ const trimmed = task.trim();
27210
+ let userPart = maybeAnchorShortAnswer(task) ?? task;
27211
+ const continued = buildContinueUserMessage(trimmed, messages, {
27212
+ maxPriorChars: 4500
27213
+ });
27214
+ if (continued) {
27215
+ userPart = continued;
27216
+ }
27097
27217
  const block = formatHistoryMessages(messages, 6, 12e3);
27098
27218
  if (!block) return userPart;
27099
27219
  if (userPart.includes("Prior assistant output")) {
@@ -27104,6 +27224,12 @@ Proceed with the next concrete steps implied by that context (implementation whe
27104
27224
  ## Current user request
27105
27225
  ${userPart}`;
27106
27226
  }
27227
+ function buildAgentUserWithHistory(task, prior) {
27228
+ const messages = prior ?? [];
27229
+ const anchored = maybeAnchorShortAnswer(task);
27230
+ if (anchored) return anchored;
27231
+ return buildContinueUserMessage(task, messages, { maxPriorChars: 8e3 }) ?? task;
27232
+ }
27107
27233
  function truncate(s, max) {
27108
27234
  const t = s.replace(/\s+/g, " ").trim();
27109
27235
  if (t.length <= max) return t;
@@ -27113,14 +27239,15 @@ function _resetConversationContextForTests() {
27113
27239
  history = [];
27114
27240
  lastClarification = null;
27115
27241
  }
27116
- var history, lastClarification, SHORT_CONTINUE;
27242
+ var history, lastClarification, SHORT_CONTINUE, SHORT_CONTINUE_LOOSE;
27117
27243
  var init_conversationContext = __esm({
27118
27244
  "src/cli/hooks/conversationContext.ts"() {
27119
27245
  "use strict";
27120
27246
  init_historyCompaction();
27121
27247
  history = [];
27122
27248
  lastClarification = null;
27123
- SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed)$/i;
27249
+ SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed|conferma|confermo|applica|fai|scrivi|esegui|implementa|vai pure|fai pure|ok procedi|sì procedi|si procedi)$/i;
27250
+ SHORT_CONTINUE_LOOSE = /\b(procedi|continua|continue|conferma|confermo|applica|implementa|scriv[ia]|esegui|vai pure|fai pure|go ahead|proceed)\b/i;
27124
27251
  }
27125
27252
  });
27126
27253
 
@@ -31869,7 +31996,9 @@ import { Box as Box5, Text as Text6 } from "ink";
31869
31996
 
31870
31997
  // src/cli/modelPricing.ts
31871
31998
  var PRICES_PER_MILLION = {
31872
- // xAI Grok
31999
+ // xAI Grok (list prices; grok-4.5 default reasoning_effort is "high")
32000
+ "grok-4.5": { input: 2, output: 6, cachedInput: 0.5 },
32001
+ "grok-4.3": { input: 1.25, output: 2.5, cachedInput: 0.2 },
31873
32002
  "grok-4": { input: 3, output: 15 },
31874
32003
  "grok-4-fast": { input: 0.2, output: 0.5 },
31875
32004
  "grok-3": { input: 3, output: 15 },
@@ -35293,11 +35422,16 @@ function useChatTurn(params) {
35293
35422
  "- write_file / edit_file / bash / apply_diff are unavailable.",
35294
35423
  "- Produce a clear plan, ask clarifying questions (---QUESTION---), use workspace plan tools when relevant.",
35295
35424
  "- When the plan is ready, tell the user to run /build to implement."
35296
- ].join("\n") : workPhase === "build" && planSummary ? [
35425
+ ].join("\n") : workPhase === "build" ? [
35297
35426
  "",
35298
35427
  "# Work Phase: BUILD",
35299
- "Implement the approved plan. Prefer acting over describing. Update plan task statuses as you go."
35300
- ].join("\n") : "";
35428
+ "Implement on disk. Prefer acting over describing.",
35429
+ "- Prior plan/synthesis text is a SPEC to apply \u2014 not proof files already changed.",
35430
+ "- You MUST use write_file/edit_file for every file you change before claiming done.",
35431
+ "- After read_file: if the planned change is missing, WRITE it \u2014 do not stop at analysis.",
35432
+ "- Never claim already-implemented based only on reading a plan or skimming code.",
35433
+ planSummary ? "- An approved plan exists in the workspace \u2014 implement it and update task statuses as you go." : ""
35434
+ ].filter(Boolean).join("\n") : "";
35301
35435
  const shellContextBlock = [
35302
35436
  "# Platform & Shell",
35303
35437
  `platform: ${process.platform}`,
@@ -35538,7 +35672,8 @@ function useChatTurn(params) {
35538
35672
  (m) => m.role === "assistant" && m.content ? {
35539
35673
  ...m,
35540
35674
  content: cleanAgentContent(m.content, {
35541
- stripQuestion: false
35675
+ stripQuestion: false,
35676
+ stripThink: false
35542
35677
  })
35543
35678
  } : m
35544
35679
  )
@@ -37789,7 +37924,7 @@ async function handleLoginOAuthGrok(ctx) {
37789
37924
  });
37790
37925
  setActiveProviderId("grok");
37791
37926
  if (!getModelForProvider("grok")) {
37792
- setModelForProvider("grok", ctx.providerDefaults["grok"] ?? "grok-4");
37927
+ setModelForProvider("grok", ctx.providerDefaults["grok"] ?? "grok-4.5");
37793
37928
  }
37794
37929
  const expiresHint = resultOAuth.expiresAt ? `, expires ${new Date(resultOAuth.expiresAt).toISOString()}` : "";
37795
37930
  const refreshHint = resultOAuth.refreshToken ? ", refresh token saved" : "";
@@ -38611,10 +38746,10 @@ function useTerminalSize(options = {}) {
38611
38746
  }
38612
38747
 
38613
38748
  // src/cli/app.tsx
38614
- var MODEL = process.env.OPENAI_MODEL ?? "grok-4";
38749
+ var MODEL = process.env.OPENAI_MODEL ?? "grok-4.5";
38615
38750
  var providerDefaults = {
38616
- "openai-compatible": "grok-4",
38617
- "grok": "grok-4",
38751
+ "openai-compatible": "grok-4.5",
38752
+ "grok": "grok-4.5",
38618
38753
  "minimax": "MiniMax-chat-latest",
38619
38754
  "glm": "glm-4.5",
38620
38755
  "deepseek": "deepseek-v4-pro"
@@ -39471,7 +39606,21 @@ function parseHeadlessFlags(argv) {
39471
39606
  const parsedHist = JSON.parse(raw);
39472
39607
  if (Array.isArray(parsedHist)) {
39473
39608
  history2 = parsedHist.filter(
39474
- (m) => m && typeof m === "object" && typeof m.role === "string" && typeof m.content === "string"
39609
+ (m) => !!m && typeof m === "object" && typeof m.role === "string"
39610
+ ).map((m) => {
39611
+ const role = String(m.role);
39612
+ const raw2 = m.content;
39613
+ const content = typeof raw2 === "string" ? raw2 : raw2 == null ? "" : typeof raw2 === "object" ? JSON.stringify(raw2) : String(raw2);
39614
+ const msg = {
39615
+ role,
39616
+ content
39617
+ };
39618
+ if (typeof m.toolCallId === "string") {
39619
+ msg.toolCallId = m.toolCallId;
39620
+ }
39621
+ return msg;
39622
+ }).filter(
39623
+ (m) => m.role === "user" || m.role === "assistant" || m.role === "tool" || m.role === "system"
39475
39624
  );
39476
39625
  }
39477
39626
  } catch {
@@ -39535,6 +39684,7 @@ function emitEvent(event) {
39535
39684
  // src/cli/runHeadless.ts
39536
39685
  init_harness();
39537
39686
  init_conversationContext();
39687
+ init_council();
39538
39688
  init_toolRegistry();
39539
39689
  init_skills2();
39540
39690
  init_envNumber();
@@ -39708,7 +39858,19 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
39708
39858
  "The shell is NON-INTERACTIVE (stdin closed): pass non-interactive flags (--yes, --force, --template).",
39709
39859
  "",
39710
39860
  `# Work phase: ${opts.phase ?? "build"}`,
39711
- (opts.phase ?? "build") === "plan" ? "PLAN phase: explore and design only. Do not write project source files (write_file/edit_file/bash blocked). Plan artifacts under .zelari are allowed." : "BUILD phase: full tools; implement changes."
39861
+ (opts.phase ?? "build") === "plan" ? [
39862
+ "PLAN phase: explore and design only.",
39863
+ "Do not write project source files (write_file/edit_file/bash blocked).",
39864
+ "Plan artifacts under .zelari are allowed.",
39865
+ "When the plan is ready, tell the user to switch to BUILD to implement on disk."
39866
+ ].join(" ") : [
39867
+ "BUILD phase \u2014 IMPLEMENT ON DISK (mandatory when the user wants code/file changes).",
39868
+ "Prior chat may contain a plan or synthesis: that text is a SPEC to apply, NOT proof that files already changed.",
39869
+ "You MUST call write_file and/or edit_file for every file you change before saying you are done.",
39870
+ "After read_file: if the planned change is missing, WRITE it \u2014 do not stop at analysis.",
39871
+ 'Never claim "already implemented" / "tutto fatto" based only on reading a plan or skimming code.',
39872
+ "Only claim done after successful mutating tool calls in THIS turn (or after proving the exact planned diff already exists on disk via read_file of the real files)."
39873
+ ].join(" ")
39712
39874
  ].join("\n")
39713
39875
  };
39714
39876
  const { loadProjectInstructions: loadProjectInstructions2 } = await Promise.resolve().then(() => (init_projectInstructions(), projectInstructions_exports));
@@ -39753,95 +39915,202 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
39753
39915
  languageDirectiveContent
39754
39916
  ].join("\n");
39755
39917
  }
39756
- const historySeed = (opts.history ?? []).map(
39757
- (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
39758
- );
39759
- const effectiveTask = maybeAnchorShortAnswer(opts.task) ?? opts.task;
39760
- const historySeedLen = historySeed.length;
39761
- const harness = new AgentHarness({
39762
- model,
39763
- provider,
39764
- sessionId,
39765
- messages: [
39766
- { role: "system", content: systemPrompt },
39767
- ...historySeed,
39768
- { role: "user", content: effectiveTask }
39769
- ],
39770
- tools,
39771
- toolRegistry,
39772
- providerStream,
39773
- maxToolLoopIterations: (() => {
39774
- const n = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 30, min: 1 });
39775
- return n;
39776
- })()
39777
- });
39778
- let finalReason = "completed";
39779
- let exitCode = 0;
39780
- const textBuffer = [];
39781
- const scrub = createStreamScrubber();
39782
- try {
39783
- for await (const event of harness.run()) {
39784
- if (event.type === "message_start") {
39785
- scrub.reset();
39786
- }
39787
- if (event.type === "message_delta" && typeof event.delta === "string") {
39788
- const cleanDelta = scrub.push(event.delta);
39789
- if (opts.output === "json") {
39790
- if (cleanDelta.length > 0) {
39791
- emitEvent({ ...event, delta: cleanDelta });
39792
- }
39793
- } else if (opts.output === "plain") {
39794
- if (cleanDelta.length > 0) process.stdout.write(cleanDelta);
39795
- } else {
39796
- if (cleanDelta.length > 0) textBuffer.push(cleanDelta);
39918
+ const historySeed = (opts.history ?? []).filter((m) => m.role === "user" || m.role === "assistant").map(
39919
+ (m) => m.role === "assistant" && m.content ? {
39920
+ role: "assistant",
39921
+ content: cleanAgentContent(m.content, {
39922
+ stripQuestion: false,
39923
+ stripThink: false
39924
+ })
39925
+ } : { role: m.role, content: m.content ?? "" }
39926
+ ).filter((m) => (m.content ?? "").trim().length > 0);
39927
+ const effectiveTask = buildAgentUserWithHistory(opts.task, historySeed);
39928
+ const maxToolLoop = (() => {
39929
+ const n = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
39930
+ default: 30,
39931
+ min: 1
39932
+ });
39933
+ return n;
39934
+ })();
39935
+ async function runSinglePass(messages, passSessionId) {
39936
+ const harness = new AgentHarness({
39937
+ model,
39938
+ provider,
39939
+ sessionId: passSessionId,
39940
+ messages,
39941
+ tools,
39942
+ toolRegistry,
39943
+ providerStream,
39944
+ maxToolLoopIterations: maxToolLoop
39945
+ });
39946
+ let finalReason = "completed";
39947
+ let exitCode = 0;
39948
+ const textBuffer = [];
39949
+ let successfulWrites = 0;
39950
+ let emittedWrites = 0;
39951
+ const pendingToolNames = /* @__PURE__ */ new Map();
39952
+ const scrub = createStreamScrubber();
39953
+ try {
39954
+ for await (const event of harness.run()) {
39955
+ if (event.type === "message_start") {
39956
+ scrub.reset();
39797
39957
  }
39798
- } else {
39799
- if (opts.output === "json") {
39800
- emitEvent(event);
39958
+ if (event.type === "tool_execution_start") {
39959
+ const name = event.toolName ?? "";
39960
+ const id = event.toolCallId ?? "";
39961
+ if (id && name) pendingToolNames.set(id, name);
39962
+ if (name === "write_file" || name === "edit_file" || name === "apply_diff") {
39963
+ emittedWrites += 1;
39964
+ }
39965
+ }
39966
+ if (event.type === "tool_execution_end") {
39967
+ const id = event.toolCallId ?? "";
39968
+ const name = pendingToolNames.get(id) ?? "";
39969
+ pendingToolNames.delete(id);
39970
+ const isError = !!event.isError;
39971
+ const result = String(event.result ?? "");
39972
+ if ((name === "write_file" || name === "edit_file" || name === "apply_diff") && !isError) {
39973
+ const zeroEdit = name === "edit_file" && /occurrencesReplaced["']?\s*[:=]\s*0\b|0 occurrence|no changes/i.test(
39974
+ result
39975
+ );
39976
+ if (!zeroEdit) successfulWrites += 1;
39977
+ }
39801
39978
  }
39802
- if (event.type === "agent_end") {
39803
- const tail = scrub.flush();
39804
- if (tail.length > 0) {
39805
- if (opts.output === "plain") process.stdout.write(tail);
39806
- else textBuffer.push(tail);
39979
+ if (event.type === "message_delta" && typeof event.delta === "string") {
39980
+ const cleanDelta = scrub.push(event.delta);
39981
+ if (opts.output === "json") {
39982
+ if (cleanDelta.length > 0) {
39983
+ emitEvent({ ...event, delta: cleanDelta });
39984
+ }
39985
+ } else if (opts.output === "plain") {
39986
+ if (cleanDelta.length > 0) process.stdout.write(cleanDelta);
39987
+ } else {
39988
+ if (cleanDelta.length > 0) textBuffer.push(cleanDelta);
39807
39989
  }
39808
- finalReason = event.reason;
39809
- if (event.reason === "error") exitCode = 3;
39810
- } else if (event.type === "error") {
39811
- if (event.severity === "fatal") {
39812
- exitCode = 2;
39990
+ } else {
39991
+ if (opts.output === "json") {
39992
+ emitEvent(event);
39993
+ }
39994
+ if (event.type === "agent_end") {
39995
+ const tail = scrub.flush();
39996
+ if (tail.length > 0) {
39997
+ if (opts.output === "plain") process.stdout.write(tail);
39998
+ else textBuffer.push(tail);
39999
+ }
40000
+ finalReason = event.reason;
40001
+ if (event.reason === "error") exitCode = 3;
40002
+ } else if (event.type === "error") {
40003
+ if (event.severity === "fatal") {
40004
+ exitCode = 2;
40005
+ }
39813
40006
  }
39814
40007
  }
39815
40008
  }
40009
+ } catch (err) {
40010
+ process.stderr.write(
40011
+ `[zelari-code --headless] runtime error: ${err instanceof Error ? err.message : String(err)}
40012
+ `
40013
+ );
40014
+ return {
40015
+ finalReason: "error",
40016
+ exitCode: 2,
40017
+ textBuffer,
40018
+ successfulWrites,
40019
+ emittedWrites,
40020
+ messages: harness.getMessages()
40021
+ };
39816
40022
  }
39817
- } catch (err) {
39818
- process.stderr.write(
39819
- `[zelari-code --headless] runtime error: ${err instanceof Error ? err.message : String(err)}
40023
+ return {
40024
+ finalReason,
40025
+ exitCode,
40026
+ textBuffer,
40027
+ successfulWrites,
40028
+ emittedWrites,
40029
+ messages: harness.getMessages()
40030
+ };
40031
+ }
40032
+ const initialMessages = [
40033
+ { role: "system", content: systemPrompt },
40034
+ ...historySeed,
40035
+ { role: "user", content: effectiveTask }
40036
+ ];
40037
+ let pass = await runSinglePass(initialMessages, sessionId);
40038
+ const wantWrites = expectsDiskImplementation(
40039
+ opts.task,
40040
+ opts.phase,
40041
+ historySeed
40042
+ );
40043
+ if (wantWrites && pass.successfulWrites === 0 && pass.finalReason === "completed" && pass.exitCode === 0) {
40044
+ const retryPrompt = buildImplementationWriteRetryPrompt(opts.task);
40045
+ if (opts.output === "json") {
40046
+ emitEvent({
40047
+ type: "log",
40048
+ message: "[headless] BUILD: no successful write_file/edit_file \u2014 forcing implementation retry"
40049
+ });
40050
+ } else {
40051
+ process.stderr.write(
40052
+ `[zelari-code --headless] BUILD: no successful writes \u2014 forcing implementation retry
39820
40053
  `
39821
- );
39822
- return 2;
40054
+ );
40055
+ }
40056
+ const retryMessages = [
40057
+ ...pass.messages.filter((m) => m.role !== "system")
40058
+ ];
40059
+ const withSystem = [
40060
+ { role: "system", content: systemPrompt },
40061
+ ...retryMessages,
40062
+ { role: "user", content: retryPrompt }
40063
+ ];
40064
+ const retry = await runSinglePass(withSystem, `${sessionId}-write-retry`);
40065
+ pass = {
40066
+ ...retry,
40067
+ textBuffer: [...pass.textBuffer, ...retry.textBuffer],
40068
+ successfulWrites: pass.successfulWrites + retry.successfulWrites,
40069
+ emittedWrites: pass.emittedWrites + retry.emittedWrites
40070
+ };
39823
40071
  }
39824
- if (opts.output === "plain" && textBuffer.length > 0) {
39825
- process.stdout.write(textBuffer.join(""));
40072
+ if (opts.output === "plain" && pass.textBuffer.length > 0) {
40073
+ process.stdout.write(pass.textBuffer.join(""));
39826
40074
  }
39827
40075
  process.stdout.write("");
39828
- if (finalReason !== "error" && opts.output === "json") {
40076
+ if (pass.finalReason !== "error" && opts.output === "json") {
39829
40077
  try {
39830
- const all = harness.getMessages();
39831
- const seedLen = 1 + historySeedLen + 1;
39832
- if (all.length > seedLen) {
39833
- const tail = all.slice(seedLen).map(
39834
- (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
39835
- );
39836
- if (tail.length > 0) {
39837
- emitEvent({ type: "history_snapshot", messages: tail });
40078
+ const all = pass.messages;
40079
+ let lastAsst = "";
40080
+ for (let i = all.length - 1; i >= 0; i--) {
40081
+ const m = all[i];
40082
+ if (m?.role === "assistant" && (m.content ?? "").trim()) {
40083
+ lastAsst = cleanAgentContent(m.content, {
40084
+ stripQuestion: false,
40085
+ stripThink: false
40086
+ });
40087
+ break;
39838
40088
  }
39839
40089
  }
40090
+ if (!lastAsst.trim() && pass.textBuffer.length > 0) {
40091
+ lastAsst = pass.textBuffer.join("").trim();
40092
+ }
40093
+ if (wantWrites && pass.successfulWrites === 0) {
40094
+ lastAsst = (lastAsst ? `${lastAsst}
40095
+
40096
+ ` : "") + "[zelari] WARNING: BUILD turn ended with zero successful file writes. The planned changes may still need to be applied on disk.";
40097
+ emitEvent({
40098
+ type: "log",
40099
+ message: "[headless] BUILD warning: still zero successful writes after retry"
40100
+ });
40101
+ }
40102
+ const snapshot = [
40103
+ { role: "user", content: opts.task },
40104
+ ...lastAsst ? [{ role: "assistant", content: lastAsst }] : []
40105
+ ];
40106
+ if (snapshot.length > 0) {
40107
+ emitEvent({ type: "history_snapshot", messages: snapshot });
40108
+ }
39840
40109
  } catch {
39841
40110
  }
39842
40111
  }
39843
- if (finalReason === "error") return 3;
39844
- return exitCode;
40112
+ if (pass.finalReason === "error") return 3;
40113
+ return pass.exitCode;
39845
40114
  }
39846
40115
  async function buildCouncilToolRegistry(planMode, opts) {
39847
40116
  const { registry: toolRegistry } = createBuiltinToolRegistry({ planMode });
@@ -39870,7 +40139,13 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
39870
40139
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
39871
40140
  const feedbackStore = new FeedbackStore2();
39872
40141
  const historySeed = (opts.history ?? []).map(
39873
- (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
40142
+ (m) => m.role === "assistant" && m.content ? {
40143
+ ...m,
40144
+ content: cleanAgentContent(m.content, {
40145
+ stripQuestion: false,
40146
+ stripThink: false
40147
+ })
40148
+ } : m
39874
40149
  );
39875
40150
  const effectiveTask = buildCouncilTaskWithHistory(opts.task, historySeed);
39876
40151
  let exitCode = 0;
@@ -39969,7 +40244,13 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
39969
40244
  }
39970
40245
  };
39971
40246
  const historySeed = (opts.history ?? []).map(
39972
- (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
40247
+ (m) => m.role === "assistant" && m.content ? {
40248
+ ...m,
40249
+ content: cleanAgentContent(m.content, {
40250
+ stripQuestion: false,
40251
+ stripThink: false
40252
+ })
40253
+ } : m
39973
40254
  );
39974
40255
  const missionTask = buildCouncilTaskWithHistory(opts.task, historySeed);
39975
40256
  emit(`[zelari] mission brief
@@ -40061,7 +40342,8 @@ ${ragContext}` : slicePrompt;
40061
40342
  }
40062
40343
  if (synthesisText.trim()) {
40063
40344
  lastMissionAssistant = cleanAgentContent(synthesisText, {
40064
- stripQuestion: false
40345
+ stripQuestion: false,
40346
+ stripThink: false
40065
40347
  });
40066
40348
  }
40067
40349
  return {