@kimbho/kimbho-cli 0.1.8 → 0.1.9

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.
package/dist/index.cjs CHANGED
@@ -3346,7 +3346,7 @@ var {
3346
3346
  // package.json
3347
3347
  var package_default = {
3348
3348
  name: "@kimbho/kimbho-cli",
3349
- version: "0.1.8",
3349
+ version: "0.1.9",
3350
3350
  description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
3351
3351
  type: "module",
3352
3352
  engines: {
@@ -7996,6 +7996,178 @@ var SessionSnapshotSchema = external_exports.object({
7996
7996
  events: external_exports.array(SessionEventSchema).default([])
7997
7997
  });
7998
7998
 
7999
+ // ../core/dist/jsonish.js
8000
+ function stripCodeFences(raw) {
8001
+ const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
8002
+ return (fenced?.[1] ?? raw).trim();
8003
+ }
8004
+ function normalizeSmartQuotes(value) {
8005
+ return value.replace(/[\u201c\u201d]/g, '"').replace(/[\u2018\u2019]/g, "'");
8006
+ }
8007
+ function maybeUnwrapQuotedJson(value) {
8008
+ const trimmed = value.trim();
8009
+ if (!trimmed.startsWith('"')) {
8010
+ return trimmed;
8011
+ }
8012
+ try {
8013
+ const parsed = JSON.parse(trimmed);
8014
+ return typeof parsed === "string" ? parsed.trim() : trimmed;
8015
+ } catch {
8016
+ return trimmed;
8017
+ }
8018
+ }
8019
+ function findBalancedObject(value) {
8020
+ for (let start = 0; start < value.length; start += 1) {
8021
+ if (value[start] !== "{") {
8022
+ continue;
8023
+ }
8024
+ let depth = 0;
8025
+ let inString = false;
8026
+ let escaping = false;
8027
+ for (let index = start; index < value.length; index += 1) {
8028
+ const character = value[index];
8029
+ if (escaping) {
8030
+ escaping = false;
8031
+ continue;
8032
+ }
8033
+ if (character === "\\") {
8034
+ escaping = true;
8035
+ continue;
8036
+ }
8037
+ if (character === '"') {
8038
+ inString = !inString;
8039
+ continue;
8040
+ }
8041
+ if (inString) {
8042
+ continue;
8043
+ }
8044
+ if (character === "{") {
8045
+ depth += 1;
8046
+ continue;
8047
+ }
8048
+ if (character === "}") {
8049
+ depth -= 1;
8050
+ if (depth === 0) {
8051
+ return value.slice(start, index + 1);
8052
+ }
8053
+ }
8054
+ }
8055
+ }
8056
+ return null;
8057
+ }
8058
+ function removeTrailingCommas(value) {
8059
+ let output = "";
8060
+ let inString = false;
8061
+ let escaping = false;
8062
+ for (let index = 0; index < value.length; index += 1) {
8063
+ const character = value[index];
8064
+ if (escaping) {
8065
+ output += character;
8066
+ escaping = false;
8067
+ continue;
8068
+ }
8069
+ if (character === "\\") {
8070
+ output += character;
8071
+ escaping = true;
8072
+ continue;
8073
+ }
8074
+ if (character === '"') {
8075
+ output += character;
8076
+ inString = !inString;
8077
+ continue;
8078
+ }
8079
+ if (!inString && character === ",") {
8080
+ let lookahead = index + 1;
8081
+ while (lookahead < value.length && /\s/.test(value[lookahead] ?? "")) {
8082
+ lookahead += 1;
8083
+ }
8084
+ if (value[lookahead] === "}" || value[lookahead] === "]") {
8085
+ continue;
8086
+ }
8087
+ }
8088
+ output += character;
8089
+ }
8090
+ return output;
8091
+ }
8092
+ function escapeControlCharsInStrings(value) {
8093
+ let output = "";
8094
+ let inString = false;
8095
+ let escaping = false;
8096
+ for (const character of value) {
8097
+ if (escaping) {
8098
+ output += character;
8099
+ escaping = false;
8100
+ continue;
8101
+ }
8102
+ if (character === "\\") {
8103
+ output += character;
8104
+ escaping = true;
8105
+ continue;
8106
+ }
8107
+ if (character === '"') {
8108
+ output += character;
8109
+ inString = !inString;
8110
+ continue;
8111
+ }
8112
+ if (inString && character === "\n") {
8113
+ output += "\\n";
8114
+ continue;
8115
+ }
8116
+ if (inString && character === "\r") {
8117
+ output += "\\r";
8118
+ continue;
8119
+ }
8120
+ if (inString && character === " ") {
8121
+ output += "\\t";
8122
+ continue;
8123
+ }
8124
+ output += character;
8125
+ }
8126
+ return output;
8127
+ }
8128
+ function parseCandidate(candidate) {
8129
+ return JSON.parse(candidate);
8130
+ }
8131
+ function extractJsonObjectish(raw, context) {
8132
+ const stripped = maybeUnwrapQuotedJson(normalizeSmartQuotes(stripCodeFences(raw)));
8133
+ const extracted = findBalancedObject(stripped) ?? stripped;
8134
+ const candidates = Array.from(new Set([
8135
+ stripped,
8136
+ extracted,
8137
+ removeTrailingCommas(extracted),
8138
+ escapeControlCharsInStrings(removeTrailingCommas(extracted))
8139
+ ].filter((candidate) => candidate.trim().length > 0)));
8140
+ let lastError;
8141
+ for (const candidate of candidates) {
8142
+ try {
8143
+ const parsed = parseCandidate(candidate);
8144
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8145
+ throw new Error(`Expected a JSON object in the ${context}.`);
8146
+ }
8147
+ return parsed;
8148
+ } catch (error) {
8149
+ lastError = error;
8150
+ }
8151
+ }
8152
+ if (lastError instanceof Error) {
8153
+ throw lastError;
8154
+ }
8155
+ throw new Error(`Expected a JSON object in the ${context}.`);
8156
+ }
8157
+ function summarizeStructuredOutputError(context, error) {
8158
+ const message = error instanceof Error ? error.message : String(error);
8159
+ if (/Expected a JSON object/i.test(message)) {
8160
+ return `${context} returned non-machine-readable output.`;
8161
+ }
8162
+ if (/Unexpected end of JSON input/i.test(message)) {
8163
+ return `${context} returned truncated JSON.`;
8164
+ }
8165
+ if (/JSON|property|position/i.test(message)) {
8166
+ return `${context} returned malformed JSON.`;
8167
+ }
8168
+ return `${context} returned invalid structured output.`;
8169
+ }
8170
+
7999
8171
  // ../core/dist/config/config.js
8000
8172
  var import_promises = require("node:fs/promises");
8001
8173
  var import_node_path = __toESM(require("node:path"), 1);
@@ -8415,6 +8587,10 @@ async function requestJson(url, init, timeoutMs = 3e4) {
8415
8587
  }
8416
8588
  return response.json();
8417
8589
  }
8590
+ function isBadRequestError(error) {
8591
+ const message = error instanceof Error ? error.message : String(error);
8592
+ return /Request failed with 400\b/i.test(message);
8593
+ }
8418
8594
  function trimTrailingSlash2(value) {
8419
8595
  return value.replace(/\/+$/, "");
8420
8596
  }
@@ -8796,20 +8972,39 @@ var OpenAICompatibleProvider = class {
8796
8972
  }
8797
8973
  const apiKey = resolveApiKey(this.definition);
8798
8974
  const model = requireModel(this.definition, input);
8799
- const response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
8800
- method: "POST",
8801
- headers: buildProviderHeaders(this.definition, apiKey),
8802
- body: JSON.stringify({
8803
- model,
8804
- messages: toChatMessages(input),
8805
- ...typeof input.temperature === "number" ? {
8806
- temperature: input.temperature
8807
- } : {},
8808
- ...typeof input.maxTokens === "number" ? {
8809
- max_tokens: input.maxTokens
8810
- } : {}
8811
- })
8812
- }, GENERATION_TIMEOUT_MS);
8975
+ const requestBody = {
8976
+ model,
8977
+ messages: toChatMessages(input),
8978
+ ...input.responseFormat === "json_object" ? {
8979
+ response_format: {
8980
+ type: "json_object"
8981
+ }
8982
+ } : {},
8983
+ ...typeof input.temperature === "number" ? {
8984
+ temperature: input.temperature
8985
+ } : {},
8986
+ ...typeof input.maxTokens === "number" ? {
8987
+ max_tokens: input.maxTokens
8988
+ } : {}
8989
+ };
8990
+ let response;
8991
+ try {
8992
+ response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
8993
+ method: "POST",
8994
+ headers: buildProviderHeaders(this.definition, apiKey),
8995
+ body: JSON.stringify(requestBody)
8996
+ }, GENERATION_TIMEOUT_MS);
8997
+ } catch (error) {
8998
+ if (!input.responseFormat || !isBadRequestError(error)) {
8999
+ throw error;
9000
+ }
9001
+ const { response_format: _ignored, ...fallbackBody } = requestBody;
9002
+ response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
9003
+ method: "POST",
9004
+ headers: buildProviderHeaders(this.definition, apiKey),
9005
+ body: JSON.stringify(fallbackBody)
9006
+ }, GENERATION_TIMEOUT_MS);
9007
+ }
8813
9008
  const choices = Array.isArray(response.choices) ? response.choices : [];
8814
9009
  const firstChoice = choices.length > 0 && typeof choices[0] === "object" && choices[0] !== null ? choices[0] : null;
8815
9010
  const message = firstChoice && typeof firstChoice.message === "object" && firstChoice.message !== null ? firstChoice.message : null;
@@ -8852,26 +9047,46 @@ var OllamaProvider = class {
8852
9047
  async generateText(input) {
8853
9048
  const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
8854
9049
  const model = requireModel(this.definition, input);
8855
- const response = await requestJson(`${baseUrl}/api/generate`, {
8856
- method: "POST",
8857
- headers: {
8858
- "content-type": "application/json",
8859
- ...this.definition.headers
8860
- },
8861
- body: JSON.stringify({
8862
- model,
8863
- prompt: input.userPrompt ?? toPromptText(input),
8864
- ...input.systemPrompt ? {
8865
- system: input.systemPrompt
8866
- } : {},
8867
- stream: false,
8868
- ...typeof input.temperature === "number" ? {
8869
- options: {
8870
- temperature: input.temperature
8871
- }
8872
- } : {}
8873
- })
8874
- }, GENERATION_TIMEOUT_MS);
9050
+ const requestBody = {
9051
+ model,
9052
+ prompt: input.userPrompt ?? toPromptText(input),
9053
+ ...input.systemPrompt ? {
9054
+ system: input.systemPrompt
9055
+ } : {},
9056
+ stream: false,
9057
+ ...input.responseFormat === "json_object" ? {
9058
+ format: "json"
9059
+ } : {},
9060
+ ...typeof input.temperature === "number" ? {
9061
+ options: {
9062
+ temperature: input.temperature
9063
+ }
9064
+ } : {}
9065
+ };
9066
+ let response;
9067
+ try {
9068
+ response = await requestJson(`${baseUrl}/api/generate`, {
9069
+ method: "POST",
9070
+ headers: {
9071
+ "content-type": "application/json",
9072
+ ...this.definition.headers
9073
+ },
9074
+ body: JSON.stringify(requestBody)
9075
+ }, GENERATION_TIMEOUT_MS);
9076
+ } catch (error) {
9077
+ if (!input.responseFormat || !isBadRequestError(error)) {
9078
+ throw error;
9079
+ }
9080
+ const { format: _ignored, ...fallbackBody } = requestBody;
9081
+ response = await requestJson(`${baseUrl}/api/generate`, {
9082
+ method: "POST",
9083
+ headers: {
9084
+ "content-type": "application/json",
9085
+ ...this.definition.headers
9086
+ },
9087
+ body: JSON.stringify(fallbackBody)
9088
+ }, GENERATION_TIMEOUT_MS);
9089
+ }
8875
9090
  const usage = extractUsage(response);
8876
9091
  return usage ? {
8877
9092
  text: typeof response.response === "string" ? response.response : "",
@@ -11884,16 +12099,6 @@ function truncateForModel(value) {
11884
12099
  return `${value.slice(0, MAX_TOOL_OUTPUT_CHARS)}
11885
12100
  ... [truncated]`;
11886
12101
  }
11887
- function extractJsonObject(raw) {
11888
- const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
11889
- const candidate = (fenced?.[1] ?? raw).trim();
11890
- const firstBrace = candidate.indexOf("{");
11891
- const lastBrace = candidate.lastIndexOf("}");
11892
- if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
11893
- throw new Error("Expected a JSON object in the model response.");
11894
- }
11895
- return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
11896
- }
11897
12102
  function renderToolResultForModel(result) {
11898
12103
  const sections = [
11899
12104
  `tool: ${result.toolId}`,
@@ -12061,6 +12266,9 @@ function summarizeSavedSessionContext(sessionId, session) {
12061
12266
  ...notes
12062
12267
  ];
12063
12268
  }
12269
+ function summarizeModelActionError(error) {
12270
+ return summarizeStructuredOutputError("Model action", error);
12271
+ }
12064
12272
  function buildInitialUserPrompt(task, request) {
12065
12273
  return [
12066
12274
  `Complete this task in the workspace.`,
@@ -12568,17 +12776,44 @@ var AutonomousTaskExecutor = class {
12568
12776
  let responseText = "";
12569
12777
  let parsedAction = null;
12570
12778
  for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt += 1) {
12571
- const response = await brain.client.generateText({
12572
- model: brain.model,
12573
- systemPrompt: buildSystemPrompt(agent, task, request, allowedTools, options.plan),
12574
- messages,
12575
- ...typeof brain.settings.temperature === "number" ? {
12576
- temperature: brain.settings.temperature
12577
- } : {},
12578
- ...typeof brain.settings.maxTokens === "number" ? {
12579
- maxTokens: brain.settings.maxTokens
12580
- } : {}
12779
+ await emitProgress({
12780
+ type: "task-note",
12781
+ sessionId,
12782
+ taskId: task.id,
12783
+ agentRole: task.agentRole,
12784
+ step,
12785
+ message: attempt === 0 ? "Thinking through the next safe action." : `Re-asking the model for a stricter machine-readable action (${attempt + 1}/${MAX_PARSE_RETRIES + 1}).`
12581
12786
  });
12787
+ let response;
12788
+ try {
12789
+ response = await brain.client.generateText({
12790
+ model: brain.model,
12791
+ systemPrompt: buildSystemPrompt(agent, task, request, allowedTools, options.plan),
12792
+ messages,
12793
+ responseFormat: "json_object",
12794
+ ...typeof brain.settings.temperature === "number" ? {
12795
+ temperature: brain.settings.temperature
12796
+ } : {},
12797
+ ...typeof brain.settings.maxTokens === "number" ? {
12798
+ maxTokens: brain.settings.maxTokens
12799
+ } : {}
12800
+ });
12801
+ } catch (error) {
12802
+ transcript.push({
12803
+ step,
12804
+ response: "",
12805
+ runtimeNote: `Model request failed: ${error instanceof Error ? error.message : String(error)}`
12806
+ });
12807
+ const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
12808
+ artifacts.add(transcriptPath2);
12809
+ return {
12810
+ status: "blocked",
12811
+ summary: `Model request failed before ${task.id} could choose a safe action: ${error instanceof Error ? error.message : String(error)}`,
12812
+ toolResults,
12813
+ artifacts: Array.from(artifacts),
12814
+ usage: usageTotals
12815
+ };
12816
+ }
12582
12817
  if (options.signal?.aborted) {
12583
12818
  const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
12584
12819
  artifacts.add(transcriptPath2);
@@ -12607,11 +12842,33 @@ var AutonomousTaskExecutor = class {
12607
12842
  } : {}
12608
12843
  });
12609
12844
  try {
12610
- parsedAction = actionSchema.parse(extractJsonObject(response.text));
12845
+ parsedAction = actionSchema.parse(extractJsonObjectish(response.text, "model response"));
12611
12846
  break;
12612
12847
  } catch (error) {
12848
+ const parseSummary = summarizeModelActionError(error);
12849
+ transcript.push({
12850
+ step,
12851
+ response: response.text,
12852
+ runtimeNote: `${parseSummary} ${error instanceof Error ? error.message : String(error)}`
12853
+ });
12613
12854
  if (attempt === MAX_PARSE_RETRIES) {
12614
- throw error;
12855
+ const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
12856
+ artifacts.add(transcriptPath2);
12857
+ await emitProgress({
12858
+ type: "task-note",
12859
+ sessionId,
12860
+ taskId: task.id,
12861
+ agentRole: task.agentRole,
12862
+ step,
12863
+ message: "Model stayed out of structured mode after multiple retries."
12864
+ });
12865
+ return {
12866
+ status: "blocked",
12867
+ summary: `${parseSummary} The task stopped before a safe tool action could be chosen.`,
12868
+ toolResults,
12869
+ artifacts: Array.from(artifacts),
12870
+ usage: usageTotals
12871
+ };
12615
12872
  }
12616
12873
  await emitProgress({
12617
12874
  type: "task-note",
@@ -12619,7 +12876,7 @@ var AutonomousTaskExecutor = class {
12619
12876
  taskId: task.id,
12620
12877
  agentRole: task.agentRole,
12621
12878
  step,
12622
- message: `Model response was invalid JSON; retrying parse (${attempt + 1}/${MAX_PARSE_RETRIES + 1}).`
12879
+ message: `Reformatting model output to match Kimbho's action schema (${attempt + 1}/${MAX_PARSE_RETRIES + 1}).`
12623
12880
  });
12624
12881
  messages.push({
12625
12882
  role: "assistant",
@@ -12628,9 +12885,11 @@ var AutonomousTaskExecutor = class {
12628
12885
  messages.push({
12629
12886
  role: "user",
12630
12887
  content: [
12631
- "Your previous response was invalid.",
12888
+ "Your previous response did not parse as a valid JSON object.",
12889
+ parseSummary,
12632
12890
  error instanceof Error ? error.message : String(error),
12633
- "Return exactly one valid JSON object matching the required action schema."
12891
+ "Return exactly one valid JSON object matching the required action schema.",
12892
+ "Do not include prose, markdown fences, comments, or trailing explanations."
12634
12893
  ].join("\n")
12635
12894
  });
12636
12895
  }
@@ -13147,16 +13406,6 @@ function createPlan(input) {
13147
13406
  var DEFAULT_PLANNER_TEMPERATURE = 0.1;
13148
13407
  var DEFAULT_PLANNER_MAX_TOKENS = 2400;
13149
13408
  var MAX_REPLAN_CONTEXT_CHARS = 8e3;
13150
- function extractJsonObject2(raw) {
13151
- const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
13152
- const candidate = (fenced?.[1] ?? raw).trim();
13153
- const firstBrace = candidate.indexOf("{");
13154
- const lastBrace = candidate.lastIndexOf("}");
13155
- if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
13156
- throw new Error("Expected a JSON object in the planner response.");
13157
- }
13158
- return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
13159
- }
13160
13409
  function validateTaskGraph(plan) {
13161
13410
  const taskIds = /* @__PURE__ */ new Set();
13162
13411
  for (const task of flattenPlanTasks(plan)) {
@@ -13300,7 +13549,7 @@ async function generatePlannerPlan(request, seedPlan, invocation, userPrompt, pr
13300
13549
  temperature: invocation.temperature ?? DEFAULT_PLANNER_TEMPERATURE,
13301
13550
  maxTokens: invocation.maxTokens ?? DEFAULT_PLANNER_MAX_TOKENS
13302
13551
  });
13303
- const parsed = extractJsonObject2(response.text);
13552
+ const parsed = extractJsonObjectish(response.text, "planner response");
13304
13553
  const candidatePlan = KimbhoPlanSchema.parse(parsed);
13305
13554
  const normalized = normalizeCandidatePlan(request, seedPlan, candidatePlan, true, preserveStatusesFrom);
13306
13555
  return {
@@ -13318,7 +13567,7 @@ async function generatePlannerPlan(request, seedPlan, invocation, userPrompt, pr
13318
13567
  return {
13319
13568
  plan: preserveStatusesFrom ? preserveTaskStatuses(preserveStatusesFrom, seedPlan) : seedPlan,
13320
13569
  source: "fallback",
13321
- warning: error instanceof Error ? error.message : String(error),
13570
+ warning: summarizeStructuredOutputError("Planner", error),
13322
13571
  ...invocation.modelLabel ? {
13323
13572
  modelLabel: invocation.modelLabel
13324
13573
  } : {}
@@ -13361,16 +13610,6 @@ function truncate2(value, maxChars = MAX_EXPANSION_CONTEXT_CHARS) {
13361
13610
  return `${value.slice(0, maxChars)}
13362
13611
  ... [truncated]`;
13363
13612
  }
13364
- function extractJsonObject3(raw) {
13365
- const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
13366
- const candidate = (fenced?.[1] ?? raw).trim();
13367
- const firstBrace = candidate.indexOf("{");
13368
- const lastBrace = candidate.lastIndexOf("}");
13369
- if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
13370
- throw new Error("Expected a JSON object in the task expansion response.");
13371
- }
13372
- return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
13373
- }
13374
13613
  function slugify(value) {
13375
13614
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
13376
13615
  }
@@ -13676,7 +13915,7 @@ async function generateExpandedTasks(request, plan, task, invocation, repoSummar
13676
13915
  temperature: invocation.temperature ?? DEFAULT_EXPANDER_TEMPERATURE,
13677
13916
  maxTokens: invocation.maxTokens ?? DEFAULT_EXPANDER_MAX_TOKENS
13678
13917
  });
13679
- const parsed = extractJsonObject3(response.text);
13918
+ const parsed = extractJsonObjectish(response.text, "task expansion response");
13680
13919
  const payload = TaskExpansionPayloadSchema.parse(parsed);
13681
13920
  const normalizedTasks = normalizeExpandedTasks(plan, task, payload, "model");
13682
13921
  const note = `Planner expanded ${task.id} into ${normalizedTasks.length} subtasks via ${invocation.modelLabel ?? "planner model"}: ${normalizedTasks.map((candidate) => candidate.id).join(", ")}.`;
@@ -13715,7 +13954,7 @@ async function generateExpandedTasks(request, plan, task, invocation, repoSummar
13715
13954
  expandedTaskId: task.id,
13716
13955
  createdTaskIds: fallbackTasks.map((candidate) => candidate.id),
13717
13956
  note,
13718
- warning: error instanceof Error ? error.message : String(error),
13957
+ warning: summarizeStructuredOutputError("Planner task expansion", error),
13719
13958
  ...invocation.modelLabel ? {
13720
13959
  modelLabel: invocation.modelLabel
13721
13960
  } : {}
@@ -15570,6 +15809,7 @@ var ExecutionOrchestrator = class {
15570
15809
  input.systemPrompt
15571
15810
  ].filter(Boolean).join("\n\n"),
15572
15811
  userPrompt: input.userPrompt,
15812
+ responseFormat: "json_object",
15573
15813
  ...typeof input.temperature === "number" ? {
15574
15814
  temperature: input.temperature
15575
15815
  } : {},
@@ -15587,7 +15827,7 @@ var ExecutionOrchestrator = class {
15587
15827
  if (result.warning) {
15588
15828
  return {
15589
15829
  plan,
15590
- note: `Planner replan fell back to the existing graph: ${result.warning}`
15830
+ note: `${result.warning} Keeping the current task graph.`
15591
15831
  };
15592
15832
  }
15593
15833
  return {
@@ -15653,6 +15893,7 @@ var ExecutionOrchestrator = class {
15653
15893
  input.systemPrompt
15654
15894
  ].filter(Boolean).join("\n\n"),
15655
15895
  userPrompt: input.userPrompt,
15896
+ responseFormat: "json_object",
15656
15897
  ...typeof input.temperature === "number" ? {
15657
15898
  temperature: input.temperature
15658
15899
  } : {},
@@ -16330,6 +16571,7 @@ async function generatePlanForRequest(request) {
16330
16571
  input.systemPrompt
16331
16572
  ].filter(Boolean).join("\n\n"),
16332
16573
  userPrompt: input.userPrompt,
16574
+ responseFormat: "json_object",
16333
16575
  ...typeof input.temperature === "number" ? {
16334
16576
  temperature: input.temperature
16335
16577
  } : {},
@@ -16359,7 +16601,7 @@ function renderPlanGenerationNotes(result) {
16359
16601
  lines.push(`planner tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`);
16360
16602
  }
16361
16603
  if (result.warning) {
16362
- lines.push(`planner note: ${result.warning}`);
16604
+ lines.push(`planner note: ${result.source === "fallback" ? `${result.warning} Using the safe default plan.` : result.warning}`);
16363
16605
  }
16364
16606
  return lines;
16365
16607
  }
@@ -17446,6 +17688,83 @@ function renderEventType(type) {
17446
17688
  function renderInlineMarkdown(value) {
17447
17689
  return value.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, url) => `${color(CYAN, label)} ${color(DIM, `(${url})`)}`).replace(/`([^`]+)`/g, (_match, code) => color(CYAN, code)).replace(/\*\*([^*]+)\*\*/g, (_match, boldText) => color(BOLD, boldText)).replace(/\*([^*]+)\*/g, (_match, italicText) => color(DIM, italicText));
17448
17690
  }
17691
+ function hashString(value) {
17692
+ let hash = 0;
17693
+ for (let index = 0; index < value.length; index += 1) {
17694
+ hash = (hash << 5) - hash + value.charCodeAt(index);
17695
+ hash |= 0;
17696
+ }
17697
+ return Math.abs(hash);
17698
+ }
17699
+ function pickStatusLabel(message) {
17700
+ const lower = message.toLowerCase();
17701
+ const choose = (labels) => labels[hashString(message) % labels.length];
17702
+ if (lower.includes("synthesizing") || lower.includes("architecture brief")) {
17703
+ return choose([
17704
+ "planning",
17705
+ "mapping",
17706
+ "sketching"
17707
+ ]);
17708
+ }
17709
+ if (lower.includes("repo context") || lower.includes("repo analysis") || lower.includes("deterministic repo context")) {
17710
+ return choose([
17711
+ "orienting",
17712
+ "surveying",
17713
+ "mapping"
17714
+ ]);
17715
+ }
17716
+ if (lower.includes("using ") && lower.includes(" via ")) {
17717
+ return choose([
17718
+ "warming up",
17719
+ "locking in",
17720
+ "switching gears"
17721
+ ]);
17722
+ }
17723
+ if (lower.includes("reformatting model output") || lower.includes("structured mode")) {
17724
+ return choose([
17725
+ "reformatting",
17726
+ "straightening",
17727
+ "coaxing"
17728
+ ]);
17729
+ }
17730
+ if (lower.includes("expanded") || lower.includes("revised the task graph") || lower.includes("keeping the current task graph")) {
17731
+ return choose([
17732
+ "replanning",
17733
+ "reshaping",
17734
+ "rerouting"
17735
+ ]);
17736
+ }
17737
+ if (lower.includes("approval")) {
17738
+ return choose([
17739
+ "waiting",
17740
+ "holding",
17741
+ "pausing"
17742
+ ]);
17743
+ }
17744
+ if (lower.includes("integration") || lower.includes("worktree")) {
17745
+ return choose([
17746
+ "reconciling",
17747
+ "merging",
17748
+ "untangling"
17749
+ ]);
17750
+ }
17751
+ return choose([
17752
+ "thinking",
17753
+ "musing",
17754
+ "assembling",
17755
+ "checking"
17756
+ ]);
17757
+ }
17758
+ function simplifyTaskNote(message) {
17759
+ const trimmed = message.trim();
17760
+ if (trimmed.startsWith("Using ") && trimmed.includes(" via ")) {
17761
+ return trimmed.replace(/^Using\s+/, "");
17762
+ }
17763
+ if (trimmed.startsWith("Reformatting model output")) {
17764
+ return trimmed;
17765
+ }
17766
+ return trimmed;
17767
+ }
17449
17768
  function renderTerminalMarkdown(markdown) {
17450
17769
  const lines = markdown.replace(/\r\n/g, "\n").split("\n");
17451
17770
  const output = [];
@@ -17658,7 +17977,7 @@ function renderLiveExecutionEvent(event) {
17658
17977
  ];
17659
17978
  case "task-note":
17660
17979
  return [
17661
- `${color(DIM, "note")} ${event.message}`
17980
+ `${color(DIM, `${pickStatusLabel(event.message)}...`)} ${simplifyTaskNote(event.message)}`
17662
17981
  ];
17663
17982
  case "approval-requested":
17664
17983
  return [