zelari-code 2.37.1 → 2.37.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.
@@ -28993,6 +28993,9 @@ var init_AgentHarness = __esm({
28993
28993
  maxToolLoopIterations;
28994
28994
  maxToolLoopHardCap;
28995
28995
  cancelled = false;
28996
+ /** Host-supplied cancel cause (`turn_timeout` vs user Stop). */
28997
+ cancelReason;
28998
+ cancelEventEmitted = false;
28996
28999
  activeController = null;
28997
29000
  queue = [];
28998
29001
  /**
@@ -29347,13 +29350,28 @@ ${shared.content}`,
29347
29350
  * After cancel() returns, the harness should be discarded by the
29348
29351
  * caller (the run() generator finishes after the current turn ends).
29349
29352
  * For mid-stream interrupt + new-prompt injection, see Task C.3.2.
29353
+ *
29354
+ * `reason` is optional host context: `turn_timeout` is the Desktop sidecar
29355
+ * idle watchdog (not a user Stop). The error event message must not claim
29356
+ * the user cancelled when they did not.
29350
29357
  */
29351
- cancel() {
29358
+ cancel(reason) {
29352
29359
  if (this.cancelled)
29353
29360
  return;
29354
29361
  this.cancelled = true;
29362
+ if (reason)
29363
+ this.cancelReason = reason;
29355
29364
  this.activeController?.abort();
29356
29365
  }
29366
+ buildCancelEvent() {
29367
+ this.cancelEventEmitted = true;
29368
+ const watchdog = this.cancelReason === "turn_timeout";
29369
+ return createBrainEvent("error", this.sessionId, {
29370
+ severity: "cancelled",
29371
+ message: watchdog ? "Turn cancelled: Desktop idle watchdog saw no events (silent model thinking or a tentacle with no tools). This was not a user Stop." : "Run cancelled by user.",
29372
+ code: watchdog ? "turn_timeout" : "cancelled"
29373
+ });
29374
+ }
29357
29375
  /** Current size of the queued user-prompt buffer. */
29358
29376
  get queueLength() {
29359
29377
  return this.queue.length;
@@ -29398,6 +29416,7 @@ ${shared.content}`,
29398
29416
  async *run() {
29399
29417
  const startTime = Date.now();
29400
29418
  this.activeController = new AbortController();
29419
+ this.cancelEventEmitted = false;
29401
29420
  this.toolCallCache = /* @__PURE__ */ new Map();
29402
29421
  this.toolCallCounts = /* @__PURE__ */ new Map();
29403
29422
  this.textToolReentries = 0;
@@ -29643,6 +29662,11 @@ ${shared.content}`,
29643
29662
  } catch {
29644
29663
  }
29645
29664
  }
29665
+ if (this.cancelled && !this.cancelEventEmitted) {
29666
+ const cancelEvent = this.buildCancelEvent();
29667
+ this.emit(cancelEvent);
29668
+ yield cancelEvent;
29669
+ }
29646
29670
  const agentEnd = createBrainEvent("agent_end", this.sessionId, {
29647
29671
  reason: hadError ? "error" : this.cancelled ? "cancelled" : "completed",
29648
29672
  durationMs: Date.now() - startTime,
@@ -29754,11 +29778,7 @@ ${shared.content}`,
29754
29778
  let lastLoopCheckLen = 0;
29755
29779
  for await (const delta of stream) {
29756
29780
  if (this.cancelled) {
29757
- const cancelEvent = createBrainEvent("error", this.sessionId, {
29758
- severity: "cancelled",
29759
- message: "Run cancelled by user.",
29760
- code: "cancelled"
29761
- });
29781
+ const cancelEvent = this.buildCancelEvent();
29762
29782
  this.emit(cancelEvent);
29763
29783
  yield cancelEvent;
29764
29784
  break;
@@ -38416,7 +38436,7 @@ var CORE_VERSION;
38416
38436
  var init_version = __esm({
38417
38437
  "packages/core/dist/version.js"() {
38418
38438
  "use strict";
38419
- CORE_VERSION = "2.37.1";
38439
+ CORE_VERSION = "2.37.3";
38420
38440
  }
38421
38441
  });
38422
38442
 
@@ -43086,6 +43106,43 @@ var init_metrics2 = __esm({
43086
43106
  }
43087
43107
  });
43088
43108
 
43109
+ // src/cli/tools/tentacleHeartbeat.ts
43110
+ function resolveTentacleHeartbeatMs(env = process.env) {
43111
+ const raw = env.ZELARI_TENTACLE_HEARTBEAT_MS?.trim();
43112
+ if (raw === "0" || raw === "off") return 0;
43113
+ if (!raw) return TENTACLE_HEARTBEAT_DEFAULT_MS;
43114
+ const n = Number.parseInt(raw, 10);
43115
+ return Number.isFinite(n) && n >= 0 ? n : TENTACLE_HEARTBEAT_DEFAULT_MS;
43116
+ }
43117
+ function formatTentacleElapsed(ms) {
43118
+ const totalSec = Math.max(0, Math.floor(ms / 1e3));
43119
+ const m = Math.floor(totalSec / 60);
43120
+ const s = totalSec % 60;
43121
+ return m > 0 ? `${m}m ${s}s` : `${s}s`;
43122
+ }
43123
+ function tentacleHeartbeatCaption(elapsedMs) {
43124
+ return `reasoning \xB7 ${formatTentacleElapsed(elapsedMs)}`;
43125
+ }
43126
+ function startTentacleHeartbeat(onBeat, opts) {
43127
+ const intervalMs = opts?.intervalMs ?? resolveTentacleHeartbeatMs();
43128
+ if (intervalMs <= 0) return () => {
43129
+ };
43130
+ const now = opts?.now ?? Date.now;
43131
+ const started = now();
43132
+ const timer = setInterval(() => {
43133
+ onBeat(tentacleHeartbeatCaption(now() - started));
43134
+ }, intervalMs);
43135
+ timer.unref?.();
43136
+ return () => clearInterval(timer);
43137
+ }
43138
+ var TENTACLE_HEARTBEAT_DEFAULT_MS;
43139
+ var init_tentacleHeartbeat = __esm({
43140
+ "src/cli/tools/tentacleHeartbeat.ts"() {
43141
+ "use strict";
43142
+ TENTACLE_HEARTBEAT_DEFAULT_MS = 15e3;
43143
+ }
43144
+ });
43145
+
43089
43146
  // src/cli/kraken/tentacle.ts
43090
43147
  var tentacle_exports = {};
43091
43148
  __export(tentacle_exports, {
@@ -46875,36 +46932,54 @@ ${taskUserContent}`,
46875
46932
  emitActivity({ type: "agent_tool", agentId: liveId, toolCallId: ev.toolCallId, tool: startedTools.get(ev.toolCallId) ?? "unknown", status: ev.isError ? "failed" : "completed", durationMs: ev.durationMs, ts: Date.now() });
46876
46933
  }
46877
46934
  };
46878
- let { result, error: error51, aborted: aborted2, usage, toolTrace } = await runSubAgent(harness, {
46879
- ...opts.signal ? { signal: opts.signal } : {},
46880
- onEvent: onHarnessEvent
46935
+ const stopHeartbeat = startTentacleHeartbeat((caption) => {
46936
+ emitActivity({
46937
+ type: "agent_status",
46938
+ agentId: liveId,
46939
+ status: "running",
46940
+ message: caption,
46941
+ ts: Date.now()
46942
+ });
46881
46943
  });
46882
- if (!aborted2 && !result && sub.fallback && sub.fallback.model !== sub.model) {
46883
- const { isUnknownModelError: isUnknownModelError2 } = await Promise.resolve().then(() => (init_krakenModel(), krakenModel_exports));
46884
- if (isUnknownModelError2(error51)) {
46885
- emitPhase(`model ${sub.model} unavailable \u2014 retrying with ${sub.fallback.model}`);
46886
- const retryConfig = {
46887
- ...config2,
46888
- model: sub.fallback.model,
46889
- provider: sub.fallback.provider,
46890
- providerStream: sub.fallback.providerStream
46891
- };
46892
- try {
46893
- harness = deps.harnessFactory ? deps.harnessFactory(retryConfig) : new (await Promise.resolve().then(() => (init_harness(), harness_exports))).AgentHarness(retryConfig);
46894
- const retry = await runSubAgent(harness, {
46895
- ...opts.signal ? { signal: opts.signal } : {},
46896
- onEvent: onHarnessEvent
46897
- });
46898
- result = retry.result;
46899
- error51 = retry.error;
46900
- aborted2 = retry.aborted;
46901
- usage = retry.usage;
46902
- toolTrace = retry.toolTrace;
46903
- sub = { ...sub, model: sub.fallback.model, provider: sub.fallback.provider };
46904
- } catch (err) {
46905
- error51 = err instanceof Error ? err.message : String(err);
46944
+ let result;
46945
+ let error51;
46946
+ let aborted2;
46947
+ let usage;
46948
+ let toolTrace;
46949
+ try {
46950
+ ({ result, error: error51, aborted: aborted2, usage, toolTrace } = await runSubAgent(harness, {
46951
+ ...opts.signal ? { signal: opts.signal } : {},
46952
+ onEvent: onHarnessEvent
46953
+ }));
46954
+ if (!aborted2 && !result && sub.fallback && sub.fallback.model !== sub.model) {
46955
+ const { isUnknownModelError: isUnknownModelError2 } = await Promise.resolve().then(() => (init_krakenModel(), krakenModel_exports));
46956
+ if (isUnknownModelError2(error51)) {
46957
+ emitPhase(`model ${sub.model} unavailable \u2014 retrying with ${sub.fallback.model}`);
46958
+ const retryConfig = {
46959
+ ...config2,
46960
+ model: sub.fallback.model,
46961
+ provider: sub.fallback.provider,
46962
+ providerStream: sub.fallback.providerStream
46963
+ };
46964
+ try {
46965
+ harness = deps.harnessFactory ? deps.harnessFactory(retryConfig) : new (await Promise.resolve().then(() => (init_harness(), harness_exports))).AgentHarness(retryConfig);
46966
+ const retry = await runSubAgent(harness, {
46967
+ ...opts.signal ? { signal: opts.signal } : {},
46968
+ onEvent: onHarnessEvent
46969
+ });
46970
+ result = retry.result;
46971
+ error51 = retry.error;
46972
+ aborted2 = retry.aborted;
46973
+ usage = retry.usage;
46974
+ toolTrace = retry.toolTrace;
46975
+ sub = { ...sub, model: sub.fallback.model, provider: sub.fallback.provider };
46976
+ } catch (err) {
46977
+ error51 = err instanceof Error ? err.message : String(err);
46978
+ }
46906
46979
  }
46907
46980
  }
46981
+ } finally {
46982
+ stopHeartbeat();
46908
46983
  }
46909
46984
  const durationMs = Date.now() - started;
46910
46985
  if (aborted2) {
@@ -46914,7 +46989,7 @@ ${taskUserContent}`,
46914
46989
  agent,
46915
46990
  thoroughness,
46916
46991
  description: args.description,
46917
- detail: "cancelled: node timeout",
46992
+ detail: "cancelled by parent",
46918
46993
  model: sub.model,
46919
46994
  worktree: worktree?.path ?? null,
46920
46995
  durationMs,
@@ -46926,7 +47001,7 @@ ${taskUserContent}`,
46926
47001
  detail: "cancelled",
46927
47002
  durationMs
46928
47003
  });
46929
- return { ok: false, agent, error: "task: sub-agent cancelled (node timeout)", cancelled: true };
47004
+ return { ok: false, agent, error: "task: sub-agent cancelled by parent", cancelled: true };
46930
47005
  }
46931
47006
  if (!result) {
46932
47007
  if (worktree) await cleanupKrakenWorktree(worktree);
@@ -47204,6 +47279,7 @@ var init_taskTool = __esm({
47204
47279
  init_verifyReport();
47205
47280
  init_metrics2();
47206
47281
  init_dist();
47282
+ init_tentacleHeartbeat();
47207
47283
  TASK_TOOL_TIMEOUT_MS = 27e5;
47208
47284
  EXPLORE_PROMPT = [
47209
47285
  "You are a focused EXPLORE tentacle of Kraken (parent super-agent).",
@@ -48283,6 +48359,7 @@ function frozenProfile(input) {
48283
48359
  Object.freeze(input.buildRecovery);
48284
48360
  Object.freeze(input.sampling);
48285
48361
  Object.freeze(input.compaction);
48362
+ if (input.stream) Object.freeze(input.stream);
48286
48363
  return Object.freeze(input);
48287
48364
  }
48288
48365
  function resolveHarnessProfile(model, providerId) {
@@ -48355,6 +48432,18 @@ var init_capabilities = __esm({
48355
48432
  buildRecovery: { forceToolChoice: true, maxForcedTurns: 1 },
48356
48433
  sampling: { temperature: 0.7 },
48357
48434
  compaction: { ...SHARED_COMPACTION },
48435
+ // Copied from Grok Build (`~/.grok/config.toml` + user-guide):
48436
+ // inference_idle_timeout_secs = 600
48437
+ // max_retries = 8
48438
+ // xAI SDK timeout = 3600s on reasoning models
48439
+ // grok-4.6 xhigh reasons with hidden tokens + SSE keep-alives and no
48440
+ // content for minutes; a 5-min useful-token idle kills BUILD.
48441
+ stream: {
48442
+ idleMs: 6e5,
48443
+ firstTokenIdleMs: 6e5,
48444
+ maxMs: 36e5,
48445
+ maxRetries: 8
48446
+ },
48358
48447
  profile: "grok"
48359
48448
  });
48360
48449
  MINIMAX_M3_CAPS = frozenProfile({
@@ -50979,18 +51068,40 @@ var openai_compatible_exports = {};
50979
51068
  __export(openai_compatible_exports, {
50980
51069
  PROVIDER_CONNECT_TIMEOUT_MS: () => PROVIDER_CONNECT_TIMEOUT_MS,
50981
51070
  PROVIDER_ENDPOINTS: () => PROVIDER_ENDPOINTS,
51071
+ PROVIDER_FIRST_TOKEN_IDLE_MS: () => PROVIDER_FIRST_TOKEN_IDLE_MS,
50982
51072
  PROVIDER_STREAM_IDLE_MS: () => PROVIDER_STREAM_IDLE_MS,
50983
51073
  PROVIDER_STREAM_MAX_MS: () => PROVIDER_STREAM_MAX_MS,
50984
51074
  dataUriFromImage: () => dataUriFromImage,
51075
+ glmModelLooksVision: () => glmModelLooksVision,
51076
+ isGlmCodingEndpoint: () => isGlmCodingEndpoint,
51077
+ isTextOnlyContentRejection: () => isTextOnlyContentRejection,
50985
51078
  modelSupportsVision: () => modelSupportsVision,
50986
51079
  openaiCompatibleProvider: () => openaiCompatibleProvider,
50987
51080
  parseCachedPromptTokens: () => parseCachedPromptTokens,
50988
51081
  providerConfigFor: () => providerConfigFor,
50989
51082
  providerFromEnv: () => providerFromEnv,
50990
51083
  readChunkWithTimeout: () => readChunkWithTimeout,
51084
+ resetTextOnlyVisionMemory: () => resetTextOnlyVisionMemory,
50991
51085
  resolveActiveProvider: () => resolveActiveProvider2,
50992
- resolveBaseUrl: () => resolveBaseUrl
50993
- });
51086
+ resolveBaseUrl: () => resolveBaseUrl,
51087
+ resolveStreamTimeouts: () => resolveStreamTimeouts
51088
+ });
51089
+ function resolveStreamTimeouts(capabilities) {
51090
+ const envIdle = process.env.ZELARI_PROVIDER_STREAM_IDLE_MS ?? process.env.ZELARI_PROVIDER_TIMEOUT_MS;
51091
+ const envFirst = process.env.ZELARI_PROVIDER_FIRST_TOKEN_IDLE_MS;
51092
+ const envMax = process.env.ZELARI_PROVIDER_STREAM_MAX_MS;
51093
+ const envRetries = process.env.ZELARI_PROVIDER_MAX_RETRIES;
51094
+ const idleMs = envIdle ? PROVIDER_STREAM_IDLE_MS : capabilities.stream?.idleMs ?? PROVIDER_STREAM_IDLE_MS;
51095
+ const firstRaw = envFirst ? PROVIDER_FIRST_TOKEN_IDLE_MS : capabilities.stream?.firstTokenIdleMs ?? PROVIDER_FIRST_TOKEN_IDLE_MS;
51096
+ const maxMs = envMax ? PROVIDER_STREAM_MAX_MS : capabilities.stream?.maxMs ?? PROVIDER_STREAM_MAX_MS;
51097
+ const maxRetries = envRetries ? MAX_RETRIES : capabilities.stream?.maxRetries ?? MAX_RETRIES;
51098
+ return {
51099
+ idleMs,
51100
+ firstTokenIdleMs: Math.max(firstRaw, idleMs),
51101
+ maxMs,
51102
+ maxRetries
51103
+ };
51104
+ }
50994
51105
  function abortableSleep(ms, signal) {
50995
51106
  return new Promise((resolve9) => {
50996
51107
  if (signal?.aborted) return resolve9();
@@ -51009,6 +51120,12 @@ function isTimeoutAbortMessage(msg) {
51009
51120
  const m = msg.toLowerCase();
51010
51121
  return m.includes("aborted due to timeout") || m.includes("timeout") || m.includes("the operation was aborted");
51011
51122
  }
51123
+ function formatStreamIdleError(elapsedMs, budgetMs, kind2) {
51124
+ const elapsedS = Math.max(1, Math.round(elapsedMs / 1e3));
51125
+ const budgetS = Math.max(1, Math.round(budgetMs / 1e3));
51126
+ const why = kind2 === "keep-alive" ? "no content tokens \u2014 keep-alive frames don't count" : "no tokens";
51127
+ return `Provider stream idle for ${elapsedS}s of ${budgetS}s (${why}). The model/gateway stalled \u2014 try again or switch model. Override with ZELARI_PROVIDER_STREAM_IDLE_MS or ZELARI_PROVIDER_FIRST_TOKEN_IDLE_MS.`;
51128
+ }
51012
51129
  async function readChunkWithTimeout(reader, opts) {
51013
51130
  if (opts.signal?.aborted) {
51014
51131
  throw new Error("aborted");
@@ -51022,9 +51139,7 @@ async function readChunkWithTimeout(reader, opts) {
51022
51139
  }
51023
51140
  const idleElapsed = now - opts.lastUsefulAt();
51024
51141
  if (idleElapsed >= opts.idleMs) {
51025
- throw new Error(
51026
- `Provider stream idle for ${Math.round(idleElapsed / 1e3)}s (no content tokens \u2014 keep-alive frames don't count). The model/gateway stalled \u2014 try again or switch model. Override with ZELARI_PROVIDER_STREAM_IDLE_MS.`
51027
- );
51142
+ throw new Error(formatStreamIdleError(idleElapsed, opts.idleMs, "keep-alive"));
51028
51143
  }
51029
51144
  const waitMs = Math.min(opts.idleMs - idleElapsed, remaining);
51030
51145
  let idleTimer;
@@ -51034,11 +51149,8 @@ async function readChunkWithTimeout(reader, opts) {
51034
51149
  reader.read(),
51035
51150
  new Promise((_, reject) => {
51036
51151
  idleTimer = setTimeout(() => {
51037
- reject(
51038
- new Error(
51039
- `Provider stream idle for ${Math.round(waitMs / 1e3)}s (no tokens). The model/gateway stalled \u2014 try again or switch model. Override with ZELARI_PROVIDER_STREAM_IDLE_MS.`
51040
- )
51041
- );
51152
+ const elapsed = Date.now() - opts.lastUsefulAt();
51153
+ reject(new Error(formatStreamIdleError(elapsed, opts.idleMs, "silence")));
51042
51154
  }, waitMs);
51043
51155
  if (opts.signal) {
51044
51156
  onAbort = () => reject(new Error("aborted"));
@@ -51060,9 +51172,52 @@ function backoffDelay(attempt, retryAfterHeader) {
51060
51172
  }
51061
51173
  return Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CAP_MS);
51062
51174
  }
51063
- function modelSupportsVision(_model) {
51064
- const force = process.env.ZELARI_VISION;
51065
- return !(force === "0" || force === "false" || force === "off");
51175
+ function visionMemoryKey(model, probe) {
51176
+ return `${probe?.providerId ?? ""}::${model}::${probe?.baseUrl ?? ""}`;
51177
+ }
51178
+ function resetTextOnlyVisionMemory() {
51179
+ textOnlyVisionMemory.clear();
51180
+ }
51181
+ function isGlmCodingEndpoint(baseUrl) {
51182
+ if (!baseUrl) return false;
51183
+ return /\/coding(\/|$)/i.test(baseUrl);
51184
+ }
51185
+ function glmModelLooksVision(model) {
51186
+ const n = model.trim().toLowerCase();
51187
+ if (!n) return false;
51188
+ if (/(?:^|[-_/.])(?:vl|vision)(?:[-_/.]|$)/.test(n)) return true;
51189
+ return /^glm[-_.]?[\w.]*v(?:-|$)/.test(n);
51190
+ }
51191
+ function glmModelIsTextOnly(model) {
51192
+ const n = model.trim().toLowerCase();
51193
+ if (!n.startsWith("glm")) return false;
51194
+ return !glmModelLooksVision(n);
51195
+ }
51196
+ function isTextOnlyContentRejection(status, body) {
51197
+ if (status !== 400) return false;
51198
+ if (!/messages\.content\.type/i.test(body)) return false;
51199
+ return /allowed values/i.test(body) || /\[\s*['"]text['"]\s*\]/.test(body) || /取值范围/.test(body) || /is invalid/i.test(body);
51200
+ }
51201
+ function messagesHaveImageUrl(messages) {
51202
+ for (const m of messages) {
51203
+ const content = m.content;
51204
+ if (!Array.isArray(content)) continue;
51205
+ for (const part of content) {
51206
+ if (part && typeof part === "object" && part.type === "image_url") {
51207
+ return true;
51208
+ }
51209
+ }
51210
+ }
51211
+ return false;
51212
+ }
51213
+ function modelSupportsVision(model, probe) {
51214
+ const force = (process.env.ZELARI_VISION ?? "").trim().toLowerCase();
51215
+ if (force === "0" || force === "false" || force === "off") return false;
51216
+ if (force === "1" || force === "true" || force === "on") return true;
51217
+ if (textOnlyVisionMemory.has(visionMemoryKey(model, probe))) return false;
51218
+ if (isGlmCodingEndpoint(probe?.baseUrl)) return false;
51219
+ if (glmModelIsTextOnly(model)) return false;
51220
+ return true;
51066
51221
  }
51067
51222
  function dataUriFromImage(img) {
51068
51223
  return `data:${img.mime};base64,${img.dataBase64}`;
@@ -51182,29 +51337,38 @@ function positiveEnvInt(name) {
51182
51337
  function openaiCompatibleProvider(config2) {
51183
51338
  return async function* (params) {
51184
51339
  const capabilities = capabilitiesFor(params.model, config2.providerId);
51185
- const vision = modelSupportsVision(params.model);
51340
+ const streamTimeouts = resolveStreamTimeouts(capabilities);
51341
+ const visionProbe = {
51342
+ providerId: config2.providerId,
51343
+ baseUrl: config2.baseUrl
51344
+ };
51345
+ let vision = modelSupportsVision(params.model, visionProbe);
51186
51346
  const msgsIn = params.messages;
51187
- let toolRunImages = [];
51188
- const messages = msgsIn.flatMap((m, i) => {
51189
- const cacheable = !(m.role === "user" && m.images && m.images.length > 0);
51190
- if (cacheable) {
51191
- const cached2 = messageMappingCache.get(m);
51192
- if (cached2) return [cached2];
51193
- }
51194
- const mapped = mapAgentMessage(m, vision);
51195
- if (cacheable) messageMappingCache.set(m, mapped);
51196
- if (m.role === "tool") {
51197
- if (m.images && m.images.length > 0) toolRunImages.push(...m.images);
51198
- const next = msgsIn[i + 1];
51199
- const runEnds = !next || next.role !== "tool";
51200
- if (runEnds && vision && toolRunImages.length > 0) {
51201
- const followUp = imagesFollowUpMessage(toolRunImages);
51202
- toolRunImages = [];
51203
- return [mapped, followUp];
51204
- }
51205
- }
51206
- return [mapped];
51207
- });
51347
+ const wireMessages = (visionFlag) => {
51348
+ let toolRunImages = [];
51349
+ return msgsIn.flatMap((m, i) => {
51350
+ const cacheable = visionFlag && !(m.role === "user" && m.images && m.images.length > 0);
51351
+ if (cacheable) {
51352
+ const cached2 = messageMappingCache.get(m);
51353
+ if (cached2) return [cached2];
51354
+ }
51355
+ const mapped = mapAgentMessage(m, visionFlag);
51356
+ if (cacheable) messageMappingCache.set(m, mapped);
51357
+ if (m.role === "tool") {
51358
+ if (m.images && m.images.length > 0) toolRunImages.push(...m.images);
51359
+ const next = msgsIn[i + 1];
51360
+ const runEnds = !next || next.role !== "tool";
51361
+ if (runEnds && visionFlag && toolRunImages.length > 0) {
51362
+ const followUp = imagesFollowUpMessage(toolRunImages);
51363
+ toolRunImages = [];
51364
+ return [mapped, followUp];
51365
+ }
51366
+ if (runEnds) toolRunImages = [];
51367
+ }
51368
+ return [mapped];
51369
+ });
51370
+ };
51371
+ let messages = wireMessages(vision);
51208
51372
  const generation = params.generation;
51209
51373
  const body = {
51210
51374
  // Use `params.model` (per-call override from AgentHarness, e.g. for
@@ -51254,6 +51418,7 @@ function openaiCompatibleProvider(config2) {
51254
51418
  const recoveryAttempt = generation?.recoveryAttempt ?? 1;
51255
51419
  const forceRecoveryTool = generation?.toolChoice === "required" && capabilities.buildRecovery.forceToolChoice && recoveryAttempt <= capabilities.buildRecovery.maxForcedTurns;
51256
51420
  body.tool_choice = forceRecoveryTool ? "required" : "auto";
51421
+ if (config2.providerId === "glm") body.tool_stream = true;
51257
51422
  }
51258
51423
  const headers3 = {
51259
51424
  "Content-Type": "application/json",
@@ -51268,7 +51433,7 @@ function openaiCompatibleProvider(config2) {
51268
51433
  let response;
51269
51434
  let lastErrText = "";
51270
51435
  let lastStatus = 0;
51271
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
51436
+ for (let attempt = 0; attempt <= streamTimeouts.maxRetries; attempt += 1) {
51272
51437
  if (params.signal?.aborted) {
51273
51438
  yield { kind: "error", message: "aborted" };
51274
51439
  return;
@@ -51309,7 +51474,7 @@ function openaiCompatibleProvider(config2) {
51309
51474
  yield { kind: "error", message: "aborted" };
51310
51475
  return;
51311
51476
  }
51312
- const maxAttempts = isTimeoutAbortMessage(lastErrText) ? Math.min(1, MAX_RETRIES) : MAX_RETRIES;
51477
+ const maxAttempts = isTimeoutAbortMessage(lastErrText) ? Math.min(1, streamTimeouts.maxRetries) : streamTimeouts.maxRetries;
51313
51478
  if (attempt < maxAttempts) {
51314
51479
  await abortableSleep(backoffDelay(attempt, null), params.signal);
51315
51480
  continue;
@@ -51320,7 +51485,14 @@ function openaiCompatibleProvider(config2) {
51320
51485
  if (response.ok && response.body) break;
51321
51486
  lastStatus = response.status;
51322
51487
  lastErrText = await response.text().catch(() => "");
51323
- if (!RETRYABLE_STATUSES.has(response.status) || attempt >= MAX_RETRIES) break;
51488
+ if (vision && isTextOnlyContentRejection(lastStatus, lastErrText) && messagesHaveImageUrl(messages)) {
51489
+ textOnlyVisionMemory.add(visionMemoryKey(params.model, visionProbe));
51490
+ vision = false;
51491
+ messages = wireMessages(false);
51492
+ body.messages = messages;
51493
+ continue;
51494
+ }
51495
+ if (!RETRYABLE_STATUSES.has(response.status) || attempt >= streamTimeouts.maxRetries) break;
51324
51496
  const retryAfter = response.headers.get("retry-after");
51325
51497
  await abortableSleep(backoffDelay(attempt, retryAfter), params.signal);
51326
51498
  }
@@ -51366,17 +51538,19 @@ function openaiCompatibleProvider(config2) {
51366
51538
  }
51367
51539
  toolCallAccumulator.clear();
51368
51540
  };
51369
- const streamDeadline = Date.now() + PROVIDER_STREAM_MAX_MS;
51541
+ const streamDeadline = Date.now() + streamTimeouts.maxMs;
51370
51542
  let lastUsefulAt = Date.now();
51543
+ let emittedUseful = false;
51371
51544
  const markUseful = () => {
51372
51545
  lastUsefulAt = Date.now();
51546
+ emittedUseful = true;
51373
51547
  };
51374
51548
  try {
51375
51549
  while (true) {
51376
51550
  let chunk;
51377
51551
  try {
51378
51552
  chunk = await readChunkWithTimeout(reader, {
51379
- idleMs: PROVIDER_STREAM_IDLE_MS,
51553
+ idleMs: emittedUseful ? streamTimeouts.idleMs : streamTimeouts.firstTokenIdleMs,
51380
51554
  deadlineMs: streamDeadline,
51381
51555
  signal: params.signal,
51382
51556
  lastUsefulAt: () => lastUsefulAt
@@ -51431,17 +51605,33 @@ function openaiCompatibleProvider(config2) {
51431
51605
  }
51432
51606
  };
51433
51607
  }
51434
- if (typeof delta?.content === "string" && delta.content.length > 0) {
51608
+ const content = delta?.content;
51609
+ if (typeof content === "string" && content.length > 0) {
51435
51610
  markUseful();
51436
- yield { kind: "text", delta: delta.content };
51611
+ yield { kind: "text", delta: content };
51612
+ } else if (Array.isArray(content)) {
51613
+ for (const part of content) {
51614
+ if (!part || typeof part !== "object") continue;
51615
+ const p3 = part;
51616
+ const text = typeof p3.text === "string" ? p3.text : "";
51617
+ if (!text) continue;
51618
+ markUseful();
51619
+ const partType = typeof p3.type === "string" ? p3.type : "text";
51620
+ yield {
51621
+ kind: partType === "reasoning" || partType === "thinking" ? "thinking" : "text",
51622
+ delta: text
51623
+ };
51624
+ }
51437
51625
  }
51438
- const reasoning = delta?.reasoning_content ?? delta?.reasoning;
51439
- if (typeof reasoning === "string" && reasoning.length > 0) {
51626
+ const reasoningRaw = delta?.reasoning_content ?? delta?.reasoning ?? delta?.thinking;
51627
+ const reasoning = typeof reasoningRaw === "string" ? reasoningRaw : reasoningRaw && typeof reasoningRaw === "object" && typeof reasoningRaw.text === "string" ? reasoningRaw.text : reasoningRaw && typeof reasoningRaw === "object" && typeof reasoningRaw.content === "string" ? reasoningRaw.content : "";
51628
+ if (reasoning.length > 0) {
51440
51629
  markUseful();
51441
51630
  yield { kind: "thinking", delta: reasoning };
51442
51631
  }
51443
51632
  const details = delta?.reasoning_details;
51444
- if (Array.isArray(details)) {
51633
+ if (Array.isArray(details) && details.length > 0) {
51634
+ markUseful();
51445
51635
  for (const d of details) {
51446
51636
  if (!d || typeof d !== "object") continue;
51447
51637
  const t = d.text;
@@ -51461,6 +51651,7 @@ function openaiCompatibleProvider(config2) {
51461
51651
  }
51462
51652
  }
51463
51653
  if (Array.isArray(delta?.tool_calls)) {
51654
+ markUseful();
51464
51655
  for (const tc of delta.tool_calls) {
51465
51656
  const idx = tc.index ?? 0;
51466
51657
  const existing = toolCallAccumulator.get(idx) ?? {
@@ -51527,7 +51718,7 @@ async function providerConfigFor(providerId) {
51527
51718
  ...extraFromStored(providerId)
51528
51719
  };
51529
51720
  }
51530
- var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_CONNECT_TIMEOUT_MS, PROVIDER_STREAM_IDLE_MS, PROVIDER_STREAM_MAX_MS, PROVIDER_ENDPOINTS, messageMappingCache;
51721
+ var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_CONNECT_TIMEOUT_MS, PROVIDER_STREAM_IDLE_MS, PROVIDER_FIRST_TOKEN_IDLE_MS, PROVIDER_STREAM_MAX_MS, textOnlyVisionMemory, PROVIDER_ENDPOINTS, messageMappingCache;
51531
51722
  var init_openai_compatible = __esm({
51532
51723
  "src/cli/provider/openai-compatible.ts"() {
51533
51724
  "use strict";
@@ -51553,11 +51744,17 @@ var init_openai_compatible = __esm({
51553
51744
  const n = raw ? Number.parseInt(raw, 10) : 3e5;
51554
51745
  return Number.isFinite(n) && n >= 15e3 ? n : 3e5;
51555
51746
  })();
51747
+ PROVIDER_FIRST_TOKEN_IDLE_MS = (() => {
51748
+ const raw = process.env.ZELARI_PROVIDER_FIRST_TOKEN_IDLE_MS;
51749
+ const n = raw ? Number.parseInt(raw, 10) : 6e5;
51750
+ return Number.isFinite(n) && n >= 15e3 ? n : 6e5;
51751
+ })();
51556
51752
  PROVIDER_STREAM_MAX_MS = (() => {
51557
51753
  const raw = process.env.ZELARI_PROVIDER_STREAM_MAX_MS;
51558
51754
  const n = raw ? Number.parseInt(raw, 10) : 18e5;
51559
51755
  return Number.isFinite(n) && n >= 6e4 ? n : 18e5;
51560
51756
  })();
51757
+ textOnlyVisionMemory = /* @__PURE__ */ new Set();
51561
51758
  PROVIDER_ENDPOINTS = {
51562
51759
  "openai-compatible": "https://api.x.ai/v1",
51563
51760
  "minimax": "https://api.minimax.io/v1",
@@ -67393,7 +67590,7 @@ var init_controlBridge = __esm({
67393
67590
  function attachHeadlessLiveCancel(opts) {
67394
67591
  const abort = new AbortController();
67395
67592
  const controlQueue = new RuntimeControlQueue();
67396
- const cancel = () => {
67593
+ const cancel = (_reason) => {
67397
67594
  if (!abort.signal.aborted) abort.abort();
67398
67595
  return true;
67399
67596
  };
@@ -67951,10 +68148,10 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
67951
68148
  })() : void 0;
67952
68149
  const unregisterLiveTurnControl = process.env.ZELARI_SERVE_HARNESS === "1" ? registerLiveTurnControl({
67953
68150
  queue: controlQueue,
67954
- cancel: () => {
68151
+ cancel: (reason) => {
67955
68152
  const cancelHook = harnessHolder.cancel;
67956
68153
  if (!cancelHook) return false;
67957
- cancelHook();
68154
+ cancelHook(reason);
67958
68155
  return true;
67959
68156
  }
67960
68157
  }) : void 0;
@@ -68240,7 +68437,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
68240
68437
  memoryContextChars: 2e3
68241
68438
  } : {}
68242
68439
  });
68243
- harnessHolder.cancel = () => harness.cancel();
68440
+ harnessHolder.cancel = (reason) => harness.cancel(reason);
68244
68441
  const readBuildProgress = () => {
68245
68442
  const getter = harness.getBuildProgress;
68246
68443
  return typeof getter === "function" ? getter.call(harness) : { mutationsAttempted: 0, mutationsSucceeded: 0 };
@@ -68427,11 +68624,11 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
68427
68624
  process.stdout.write(pass.textBuffer.join(""));
68428
68625
  }
68429
68626
  process.stdout.write("");
68430
- if (pass.finalReason !== "error" && opts.output === "json" && wantWrites && pass.successfulWrites === 0) {
68627
+ if (pass.finalReason !== "error" && pass.finalReason !== "cancelled" && opts.output === "json" && wantWrites && pass.successfulWrites === 0) {
68431
68628
  emitEvent({ type: "log", message: "[headless] BUILD failed: zero successful mutations after liveness recovery" });
68432
68629
  }
68433
68630
  try {
68434
- const closeStatus = pass.finalReason === "error" ? "error" : strictExit !== 0 ? "stopped" : "completed";
68631
+ const closeStatus = pass.finalReason === "error" ? "error" : pass.finalReason === "cancelled" ? "cancelled" : strictExit !== 0 ? "stopped" : "completed";
68435
68632
  await spine.close(closeStatus);
68436
68633
  } catch {
68437
68634
  }
@@ -72765,7 +72962,8 @@ function startHarnessServer(options = {}) {
72765
72962
  };
72766
72963
  }
72767
72964
  if (isCancel) {
72768
- const delivered = live.cancel();
72965
+ const reason = typeof params.reason === "string" && params.reason.trim().length > 0 ? params.reason.trim() : void 0;
72966
+ const delivered = live.cancel(reason);
72769
72967
  if (delivered) {
72770
72968
  write(JSON.stringify(controlAppliedEvent(controlId, "cancel", "cancel")));
72771
72969
  }