@kernelonpanic/kitcode 1.4.2 → 1.4.5

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.js CHANGED
@@ -81,6 +81,14 @@ var configSchema = z.object({
81
81
  // Manual override for the model's context window. Unset means the window
82
82
  // comes from the provider catalogue or the endpoint's model list.
83
83
  contextWindow: z.number().int().min(1e3).max(1e7).optional(),
84
+ // Extra instructions appended to the built-in system prompt on every turn.
85
+ systemPrompt: z.string().max(2e4).optional(),
86
+ // When false, systemPrompt is kept in the config but not sent to the model.
87
+ systemPromptEnabled: z.boolean().default(true),
88
+ // Hard cap on model calls in one turn. Higher values let large refactors
89
+ // finish; 0 disables the cap entirely (use with care — a runaway loop will
90
+ // keep burning tokens until you press Esc).
91
+ maxTurnSteps: z.number().int().min(0).max(1e3).default(256),
84
92
  maxTokens: z.number().int().positive().max(2e5).default(64e3),
85
93
  budget: budgetSchema.default({
86
94
  maxTokensPerTurn: 1e7,
@@ -170,7 +178,10 @@ function describeProviderFailure(error, providerId, knownSecrets = []) {
170
178
  }
171
179
  function statusOf(error) {
172
180
  const value = prop(error, "status") ?? prop(error, "statusCode");
173
- return typeof value === "number" && value >= 100 ? value : void 0;
181
+ if (typeof value === "number" && value >= 100) return value;
182
+ const code = prop(error, "code") ?? prop(prop(error, "error"), "code");
183
+ const numeric = typeof code === "string" && /^\d{3}$/.test(code) ? Number(code) : code;
184
+ return typeof numeric === "number" && numeric >= 400 && numeric <= 599 ? numeric : void 0;
174
185
  }
175
186
  function isUserAbort(error, signal) {
176
187
  return signal?.aborted === true || abortKind(error) === "user";
@@ -1440,13 +1451,20 @@ function resolveEffort(effort, messages) {
1440
1451
  return text.length > 500 || HARD_TASK.test(text) ? "high" : "medium";
1441
1452
  }
1442
1453
  var rejected = /* @__PURE__ */ new Set();
1454
+ var capped = /* @__PURE__ */ new Map();
1455
+ var RANK = { low: 0, medium: 1, high: 2, xhigh: 3, max: 4 };
1443
1456
  function markEffortRejected(providerId, model) {
1444
1457
  rejected.add(`${providerId}/${model}`);
1445
1458
  }
1459
+ function markEffortCapped(providerId, model, level) {
1460
+ capped.set(`${providerId}/${model}`, level);
1461
+ }
1446
1462
  function effectiveEffort(kind, model, effort, messages, providerId) {
1447
1463
  if (providerId !== void 0 && rejected.has(`${providerId}/${model}`)) return void 0;
1448
1464
  const requested = resolveEffort(effort, messages);
1449
- return kind === "openai" ? openAiEffort(model, requested) : anthropicEffort(model, requested);
1465
+ const level = kind === "openai" ? openAiEffort(model, requested) : anthropicEffort(model, requested);
1466
+ const cap = providerId === void 0 ? void 0 : capped.get(`${providerId}/${model}`);
1467
+ return level && cap && RANK[level] > RANK[cap] ? cap : level;
1450
1468
  }
1451
1469
  function anthropicEffort(model, effort) {
1452
1470
  if (!effort) return void 0;
@@ -1878,7 +1896,7 @@ function validSessionId(id) {
1878
1896
  var MAX_PAUSE_RESUMES = 5;
1879
1897
  var MAX_RETRIES2 = 3;
1880
1898
  var INITIAL_BACKOFF_MS = 1e3;
1881
- var MAX_TURN_STEPS = 64;
1899
+ var DEFAULT_MAX_TURN_STEPS = 256;
1882
1900
  function createExecutionLane() {
1883
1901
  let tail2 = Promise.resolve();
1884
1902
  return {
@@ -1890,6 +1908,8 @@ function createExecutionLane() {
1890
1908
  };
1891
1909
  }
1892
1910
  async function runTurn(cfg, history, hooks, signal) {
1911
+ const maxTurnSteps = cfg.maxTurnSteps ?? DEFAULT_MAX_TURN_STEPS;
1912
+ const hasStepCap = maxTurnSteps > 0;
1893
1913
  const permissionLane = cfg.permissionLane ?? createExecutionLane();
1894
1914
  const originalHooks = hooks;
1895
1915
  hooks = {
@@ -1904,11 +1924,12 @@ async function runTurn(cfg, history, hooks, signal) {
1904
1924
  hooks.onEvent({ type: "turn_end", stopReason: "aborted" });
1905
1925
  return sanitizeHistory(messages);
1906
1926
  }
1907
- if (++steps > MAX_TURN_STEPS) {
1927
+ steps += 1;
1928
+ if (hasStepCap && steps > maxTurnSteps) {
1908
1929
  hooks.onEvent({
1909
1930
  type: "notice",
1910
1931
  level: "warn",
1911
- text: `Stopped after ${MAX_TURN_STEPS} model calls without finishing, to avoid burning tokens in a loop. Ask again with a narrower request.`
1932
+ text: `Stopped after ${maxTurnSteps} model calls without finishing, to avoid burning tokens in a loop. Ask again with a narrower request.`
1912
1933
  });
1913
1934
  hooks.onEvent({ type: "turn_end", stopReason: "max_tokens" });
1914
1935
  return messages;
@@ -2137,18 +2158,18 @@ function sleep(ms, signal) {
2137
2158
  signal.addEventListener("abort", onAbort, { once: true });
2138
2159
  });
2139
2160
  }
2161
+ var CHARS_PER_TOKEN = 4;
2140
2162
  function estimateRequestTokens(cfg, messages) {
2141
2163
  let characters3 = cfg.system.length;
2142
2164
  try {
2143
2165
  characters3 += JSON.stringify(
2144
2166
  messages,
2145
- (_key, value) => typeof value === "object" && value !== null && value.type === "image" ? `[image:${"#".repeat(2e3)}]` : value
2167
+ (_key, value) => typeof value === "object" && value !== null && value.type === "image" ? `[image:${"#".repeat(6e3)}]` : value
2146
2168
  ).length;
2147
2169
  characters3 += JSON.stringify(cfg.tools.schemas()).length;
2148
2170
  } catch {
2149
- return Math.max(1, characters3);
2150
2171
  }
2151
- return Math.max(1, characters3);
2172
+ return Math.max(1, Math.ceil(characters3 / CHARS_PER_TOKEN));
2152
2173
  }
2153
2174
  async function runToolCalls(cfg, content, hooks, signal) {
2154
2175
  const calls = content.filter((block) => block.type === "tool_use");
@@ -3789,6 +3810,7 @@ function withoutThinkingReplay(history) {
3789
3810
  });
3790
3811
  return changed ? out : history;
3791
3812
  }
3813
+ var CHARS_PER_TOKEN2 = 4;
3792
3814
  function estimateHistoryTokens(history) {
3793
3815
  let chars = 0;
3794
3816
  for (const message of history) {
@@ -3800,7 +3822,7 @@ function estimateHistoryTokens(history) {
3800
3822
  else if (block.type === "image") chars += 6e3;
3801
3823
  }
3802
3824
  }
3803
- return Math.round(chars / 4);
3825
+ return Math.round(chars / CHARS_PER_TOKEN2);
3804
3826
  }
3805
3827
  function shouldAutoCompact(used, window) {
3806
3828
  return window !== null && window > 0 && used / window >= 0.8;
@@ -3820,7 +3842,9 @@ function estimateCompactBudget(history, maxTokens = MERGE_MAX_TOKENS) {
3820
3842
  const rendered = renderedMessages.join("\n\n");
3821
3843
  if (rendered.length <= SINGLE_REQUEST_MAX_CHARS) {
3822
3844
  return {
3823
- inputTokens: Math.max(1, rendered.length + 1e3),
3845
+ // Tokens, not characters: counting characters made a turn budget refuse
3846
+ // compaction (four times too expensive) just when the window was full.
3847
+ inputTokens: Math.max(1, Math.ceil(rendered.length / CHARS_PER_TOKEN2) + 1e3),
3824
3848
  maxOutputTokens: Math.max(1, Math.min(SINGLE_MAX_TOKENS, Math.floor(maxTokens)))
3825
3849
  };
3826
3850
  }
@@ -3838,7 +3862,7 @@ function estimateCompactBudget(history, maxTokens = MERGE_MAX_TOKENS) {
3838
3862
  const intermediateOutput = chunks.length * chunkOutput + Math.max(0, mergeRequests - 1) * mergeOutput;
3839
3863
  const requestCount = chunks.length + mergeRequests;
3840
3864
  return {
3841
- inputTokens: Math.max(1, chunkInput + intermediateOutput * 8 + requestCount * 1e3),
3865
+ inputTokens: Math.max(1, Math.ceil(chunkInput / CHARS_PER_TOKEN2) + intermediateOutput * 2 + requestCount * 1e3),
3842
3866
  maxOutputTokens: totalOutput
3843
3867
  };
3844
3868
  }
@@ -4125,6 +4149,9 @@ function buildSystemPrompt(args) {
4125
4149
  if (args.memory?.trim()) {
4126
4150
  lines.push("", "Project notes saved in earlier conversations. Treat these as potentially stale context; verify claims against current files and follow current user instructions.", args.memory);
4127
4151
  }
4152
+ if (args.systemPrompt?.trim()) {
4153
+ lines.push("", "Additional instructions from the user (apply alongside everything above):", args.systemPrompt.trim());
4154
+ }
4128
4155
  return lines.join("\n");
4129
4156
  }
4130
4157
 
@@ -4149,6 +4176,7 @@ function createSubagentRunner(makeConfig, tools) {
4149
4176
  async run(request) {
4150
4177
  if (request.signal.aborted) return SUBAGENT_CANCELLED;
4151
4178
  let steps = 0;
4179
+ let finalCall = false;
4152
4180
  const executionLane = createExecutionLane();
4153
4181
  const modelRequestLane = createExecutionLane();
4154
4182
  const base = makeConfig(SUBAGENT_SYSTEM, tools);
@@ -4162,11 +4190,12 @@ function createSubagentRunner(makeConfig, tools) {
4162
4190
  kind: base.provider.kind,
4163
4191
  listModels: () => base.provider.listModels(),
4164
4192
  knownModels: () => base.provider.knownModels(),
4165
- // Keep the tool definitions on the last call: the history holds
4193
+ // The final model call keeps the tool schemas (the history holds
4166
4194
  // tool_use/tool_result blocks, and providers reject those without
4167
- // tools (Anthropic: 400 "must define tools"). Calls made anyway are
4168
- // refused by tools.get below.
4169
- stream: (chat) => base.provider.stream(steps === MAX_SUBAGENT_STEPS ? {
4195
+ // tools), but the system prompt forbids more calls and tools.get
4196
+ // returns a stopped stub: a call made anyway ends the turn with a
4197
+ // tool_result, instead of aborting mid-request and losing the text.
4198
+ stream: (chat) => base.provider.stream(finalCall ? {
4170
4199
  ...chat,
4171
4200
  system: `${chat.system}
4172
4201
 
@@ -4174,7 +4203,20 @@ This is your last model call. Do not call tools. Return your findings now, clear
4174
4203
  } : chat)
4175
4204
  },
4176
4205
  tools: {
4177
- get: (name) => name === TASK_TOOL_NAME || steps >= MAX_SUBAGENT_STEPS ? void 0 : base.tools.get(name),
4206
+ get: (name) => {
4207
+ if (name === TASK_TOOL_NAME) return void 0;
4208
+ const tool = base.tools.get(name);
4209
+ if (!tool) return void 0;
4210
+ if (!finalCall) return tool;
4211
+ return {
4212
+ ...tool,
4213
+ permission: () => "allow",
4214
+ execute: () => Promise.resolve({
4215
+ content: "Tool budget exhausted: this was your last model call. Write the final answer now from what you already gathered.",
4216
+ isError: true
4217
+ })
4218
+ };
4219
+ },
4178
4220
  schemas: () => base.tools.schemas().filter((schema) => schema.name !== TASK_TOOL_NAME)
4179
4221
  },
4180
4222
  system: `${SUBAGENT_SYSTEM}
@@ -4185,7 +4227,11 @@ Working directory: ${base.cwd}`
4185
4227
  const signal = AbortSignal.any([request.signal, limit.signal]);
4186
4228
  const hooks = {
4187
4229
  onEvent(event) {
4188
- if (event.type === "turn_start" && ++steps > MAX_SUBAGENT_STEPS) limit.abort();
4230
+ if (event.type === "turn_start") {
4231
+ ++steps;
4232
+ if (steps > MAX_SUBAGENT_STEPS + 1) limit.abort();
4233
+ else if (steps >= MAX_SUBAGENT_STEPS) finalCall = true;
4234
+ }
4189
4235
  if (event.type === "tool_start") request.onProgress(brief(event.summary, 80));
4190
4236
  if (event.type === "subagent_start" || event.type === "subagent_end" || event.type === "subagent_event") return;
4191
4237
  request.onEvent?.(event);
@@ -4400,7 +4446,7 @@ function clip(value) {
4400
4446
  // package.json
4401
4447
  var package_default = {
4402
4448
  name: "@kernelonpanic/kitcode",
4403
- version: "1.4.2",
4449
+ version: "1.4.5",
4404
4450
  description: "Terminal coding agent with a config you never have to write by hand",
4405
4451
  type: "module",
4406
4452
  license: "MIT",
@@ -4468,7 +4514,22 @@ var package_default = {
4468
4514
 
4469
4515
  // src/version.ts
4470
4516
  var KITCODE_VERSION = package_default.version;
4471
- var KITCODE_COMMIT = true ? "6b1ec0a742c44892447e71f477c068d8ba514c6a" : "development";
4517
+ var KITCODE_COMMIT = true ? "aac7d92efa29456e6757bdef12abafb489064dbe" : "development";
4518
+ function kitcodeUserAgent(sdkUserAgent2) {
4519
+ return `KitCode/${KITCODE_VERSION} ${sdkUserAgent2}`.trim();
4520
+ }
4521
+ function sdkUserAgent(client) {
4522
+ const read = client?.getUserAgent;
4523
+ try {
4524
+ return typeof read === "function" ? String(read.call(client)) : "";
4525
+ } catch {
4526
+ return "";
4527
+ }
4528
+ }
4529
+ function withKitcodeUserAgent(sdkUserAgent2, configured = {}) {
4530
+ const overridden = Object.keys(configured).some((name) => name.toLowerCase() === "user-agent");
4531
+ return overridden ? { ...configured } : { "User-Agent": kitcodeUserAgent(sdkUserAgent2), ...configured };
4532
+ }
4472
4533
 
4473
4534
  // src/mcp/client.ts
4474
4535
  var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
@@ -5047,13 +5108,14 @@ var KNOWN_MODELS = [
5047
5108
  knownModel("claude-fable-5", "Claude Fable 5", 1e6, 128e3)
5048
5109
  ];
5049
5110
  function createAnthropicProvider(args) {
5050
- const client = new Anthropic2({
5111
+ const options = {
5051
5112
  apiKey: args.apiKey,
5052
5113
  baseURL: args.baseUrl,
5053
- defaultHeaders: args.headers,
5054
5114
  maxRetries: MAX_RETRIES,
5055
5115
  timeout: args.timeoutMs ?? REQUEST_TIMEOUT_MS
5056
- });
5116
+ };
5117
+ const userAgent = sdkUserAgent(new Anthropic2(options));
5118
+ const client = new Anthropic2({ ...options, defaultHeaders: withKitcodeUserAgent(userAgent, args.headers) });
5057
5119
  return {
5058
5120
  id: args.id,
5059
5121
  kind: "anthropic",
@@ -5062,7 +5124,35 @@ function createAnthropicProvider(args) {
5062
5124
  knownModels: () => KNOWN_MODELS
5063
5125
  };
5064
5126
  }
5127
+ var thinkingRejected = /* @__PURE__ */ new Set();
5065
5128
  async function* streamTurn(client, providerId, apiKey2, req) {
5129
+ const key = `${providerId}/${req.model}`;
5130
+ let thinking = req.thinking === true && !thinkingRejected.has(key);
5131
+ for (; ; ) {
5132
+ let streamed = false;
5133
+ try {
5134
+ for await (const event of streamOnce(client, providerId, apiKey2, req, thinking)) {
5135
+ if (event.type !== "rate_limits" && event.type !== "usage") streamed = true;
5136
+ yield event;
5137
+ }
5138
+ return;
5139
+ } catch (error) {
5140
+ if (!streamed && thinking && refusesThinking(error)) {
5141
+ thinkingRejected.add(key);
5142
+ thinking = false;
5143
+ continue;
5144
+ }
5145
+ throw error;
5146
+ }
5147
+ }
5148
+ }
5149
+ function refusesThinking(error) {
5150
+ if (!(error instanceof ProviderError)) return false;
5151
+ if (error.status !== void 0 && error.status !== 400 && error.status !== 422) return false;
5152
+ const cause = error.cause instanceof Error ? error.cause.message : "";
5153
+ return /thinking|budget_tokens|adaptive/i.test(`${error.message} ${cause}`);
5154
+ }
5155
+ async function* streamOnce(client, providerId, apiKey2, req, thinkingOn) {
5066
5156
  const params = {
5067
5157
  model: req.model,
5068
5158
  max_tokens: req.maxTokens,
@@ -5070,7 +5160,7 @@ async function* streamTurn(client, providerId, apiKey2, req) {
5070
5160
  messages: toMessageParams(req.messages),
5071
5161
  tools: toToolParams(req.tools)
5072
5162
  };
5073
- if (req.thinking) {
5163
+ if (thinkingOn) {
5074
5164
  const thinking2 = thinkingParam(req.model, req.maxTokens);
5075
5165
  if (thinking2) params.thinking = thinking2;
5076
5166
  }
@@ -5232,7 +5322,7 @@ var THINKING_BUDGET = 16e3;
5232
5322
  var MIN_THINKING_BUDGET = 1024;
5233
5323
  function thinkingParam(model, maxTokens) {
5234
5324
  const id = (model.split("/").at(-1) ?? model).replace(/^(?:[a-z]{2}\.)?anthropic\./, "");
5235
- if (!BUDGET_THINKING.test(id)) return { type: "adaptive", display: "summarized" };
5325
+ if (id.startsWith("claude-") && !BUDGET_THINKING.test(id)) return { type: "adaptive", display: "summarized" };
5236
5326
  const budget = Math.min(THINKING_BUDGET, maxTokens - 1);
5237
5327
  return budget >= MIN_THINKING_BUDGET ? { type: "enabled", budget_tokens: budget } : void 0;
5238
5328
  }
@@ -5252,13 +5342,14 @@ var FINISH_REASONS = {
5252
5342
  content_filter: "refusal"
5253
5343
  };
5254
5344
  function createOpenAiProvider(args) {
5255
- const client = new OpenAI2({
5345
+ const options = {
5256
5346
  baseURL: args.baseUrl,
5257
5347
  apiKey: args.apiKey,
5258
- defaultHeaders: args.headers,
5259
5348
  maxRetries: MAX_RETRIES,
5260
5349
  timeout: args.timeoutMs ?? REQUEST_TIMEOUT_MS
5261
- });
5350
+ };
5351
+ const userAgent = sdkUserAgent(new OpenAI2(options));
5352
+ const client = new OpenAI2({ ...options, defaultHeaders: withKitcodeUserAgent(userAgent, args.headers) });
5262
5353
  return {
5263
5354
  id: args.id,
5264
5355
  kind: "openai",
@@ -5275,7 +5366,7 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
5275
5366
  for (; ; ) {
5276
5367
  let streamed = false;
5277
5368
  try {
5278
- for await (const event of streamOnce(client, providerId, apiKey2, req, effort, thinkingOff, tokenField)) {
5369
+ for await (const event of streamOnce2(client, providerId, apiKey2, req, effort, thinkingOff, tokenField)) {
5279
5370
  if (event.type !== "rate_limits" && event.type !== "usage") streamed = true;
5280
5371
  yield event;
5281
5372
  }
@@ -5288,8 +5379,14 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
5288
5379
  continue;
5289
5380
  }
5290
5381
  if (effort && rejectsField(error, /reasoning|effort/i)) {
5291
- markEffortRejected(providerId, req.model);
5292
- effort = void 0;
5382
+ const lower = effort === "xhigh" || effort === "max" ? "high" : effort === "high" ? "medium" : void 0;
5383
+ if (lower) {
5384
+ markEffortCapped(providerId, req.model, lower);
5385
+ effort = lower;
5386
+ } else {
5387
+ markEffortRejected(providerId, req.model);
5388
+ effort = void 0;
5389
+ }
5293
5390
  continue;
5294
5391
  }
5295
5392
  if (thinkingOff && rejectsField(error, /thinking/i)) {
@@ -5314,11 +5411,12 @@ function thinkingOffParams(model) {
5314
5411
  return void 0;
5315
5412
  }
5316
5413
  function rejectsField(error, field2) {
5317
- if (!(error instanceof ProviderError) || error.status !== 400 && error.status !== 422) return false;
5414
+ if (!(error instanceof ProviderError)) return false;
5415
+ if (error.status !== void 0 && error.status !== 400 && error.status !== 422) return false;
5318
5416
  const cause = error.cause instanceof Error ? error.cause.message : "";
5319
5417
  return field2.test(`${error.message} ${cause}`);
5320
5418
  }
5321
- async function* streamOnce(client, providerId, apiKey2, req, effort, thinkingOff, tokenField) {
5419
+ async function* streamOnce2(client, providerId, apiKey2, req, effort, thinkingOff, tokenField) {
5322
5420
  let text = "";
5323
5421
  let thinking = "";
5324
5422
  let finishReason = null;
@@ -7227,7 +7325,8 @@ async function boot(options) {
7227
7325
  cwd: options.cwd,
7228
7326
  toolNames: tools.list().map((tool) => tool.name),
7229
7327
  skills: skillCatalogue,
7230
- memory: projectMemory
7328
+ memory: projectMemory,
7329
+ systemPrompt: config.systemPromptEnabled ? config.systemPrompt : void 0
7231
7330
  });
7232
7331
  let modelRef = session.model || config.model || "";
7233
7332
  if (options.modelRef) modelRef = options.modelRef;
@@ -7297,6 +7396,7 @@ async function boot(options) {
7297
7396
  maxTokens: config.maxTokens,
7298
7397
  effort: config.effort,
7299
7398
  thinking: config.thinking,
7399
+ maxTurnSteps: config.maxTurnSteps,
7300
7400
  budget: activeBudget,
7301
7401
  checkpoint: activeCheckpoint,
7302
7402
  trimContext: (messages) => trimForWindow(resolved.provider.kind, messages)
@@ -7677,6 +7777,31 @@ async function boot(options) {
7677
7777
  draft.lang = lang;
7678
7778
  });
7679
7779
  },
7780
+ getSystemPrompt: () => config.systemPrompt ?? "",
7781
+ isSystemPromptEnabled: () => config.systemPromptEnabled && config.systemPrompt !== void 0 && config.systemPrompt.trim() !== "",
7782
+ async setSystemPrompt(text) {
7783
+ const trimmed = text.trim();
7784
+ await persistConfig((draft) => {
7785
+ if (trimmed === "") delete draft.systemPrompt;
7786
+ else draft.systemPrompt = trimmed;
7787
+ });
7788
+ },
7789
+ async setSystemPromptEnabled(enabled) {
7790
+ await persistConfig((draft) => {
7791
+ draft.systemPromptEnabled = enabled;
7792
+ });
7793
+ },
7794
+ async loadSystemPromptFile(requestedPath) {
7795
+ const { readFile: readFile15 } = await import("fs/promises");
7796
+ const resolved = path15.resolve(options.cwd, requestedPath);
7797
+ return readFile15(resolved, "utf8");
7798
+ },
7799
+ getMaxTurnSteps: () => config.maxTurnSteps,
7800
+ async setMaxTurnSteps(steps) {
7801
+ await persistConfig((draft) => {
7802
+ draft.maxTurnSteps = steps;
7803
+ });
7804
+ },
7680
7805
  getAccent: () => config.theme.accent,
7681
7806
  async setAccent(value) {
7682
7807
  await persistConfig((draft) => {
@@ -8041,7 +8166,7 @@ ${recovered.removed === 1 ? "The image was" : `${recovered.removed} images were`
8041
8166
  }
8042
8167
  function imageRejection(error, history) {
8043
8168
  const status = error?.status;
8044
- if (typeof status !== "number" || ![400, 413, 415, 422].includes(status)) return null;
8169
+ if (status !== void 0 && (typeof status !== "number" || ![400, 413, 415, 422].includes(status))) return null;
8045
8170
  const message = error instanceof Error ? error.message : String(error);
8046
8171
  if (status !== 413 && !/image|vision|multi-?modal/i.test(message)) return null;
8047
8172
  const result = withoutImages(history);
@@ -8257,7 +8382,7 @@ function validateKey(value) {
8257
8382
  import { render } from "ink";
8258
8383
 
8259
8384
  // src/ui/App.tsx
8260
- import { Box as Box14, Text as Text13, useApp, useInput as useInput3, useStdout as useStdout2 } from "ink";
8385
+ import { Box as Box14, Text as Text13, useApp, useInput as useInput4, useStdout as useStdout2 } from "ink";
8261
8386
  import { useCallback, useEffect as useEffect4, useMemo as useMemo6, useRef as useRef5, useState as useState8 } from "react";
8262
8387
 
8263
8388
  // src/ui/terminal-size.ts
@@ -8387,7 +8512,7 @@ function splitCommandLine(raw) {
8387
8512
  }
8388
8513
 
8389
8514
  // src/ui/speed.ts
8390
- var CHARS_PER_TOKEN = 4;
8515
+ var CHARS_PER_TOKEN3 = 4;
8391
8516
  var MIN_LIVE_MS = 1e3;
8392
8517
  function createSpeedMeter() {
8393
8518
  return { streamedChars: 0, firstTokenAt: null, last: null };
@@ -8414,7 +8539,7 @@ function readSpeed(meter, busy, now = Date.now()) {
8414
8539
  if (busy && meter.firstTokenAt !== null && meter.streamedChars > 0) {
8415
8540
  const ms = now - meter.firstTokenAt;
8416
8541
  if (ms >= MIN_LIVE_MS) {
8417
- return { tokensPerSecond: meter.streamedChars / CHARS_PER_TOKEN / (ms / 1e3), live: true };
8542
+ return { tokensPerSecond: meter.streamedChars / CHARS_PER_TOKEN3 / (ms / 1e3), live: true };
8418
8543
  }
8419
8544
  }
8420
8545
  return meter.last === null || !Number.isFinite(meter.last) ? null : { tokensPerSecond: meter.last, live: false };
@@ -8443,6 +8568,8 @@ var COMMANDS = [
8443
8568
  { name: "mcp", args: "[add|list|delete|enable|disable]" },
8444
8569
  { name: "attach", args: "<path|clipboard|clear>" },
8445
8570
  { name: "memory", args: "[show|set <text>|clear]" },
8571
+ { name: "systemp", args: "[set <text>|clear]", ...BUSY_SAFE },
8572
+ { name: "steps", args: "[count]", ...BUSY_SAFE },
8446
8573
  { name: "compact" },
8447
8574
  { name: "update" },
8448
8575
  { name: "checker", ...BUSY_SAFE },
@@ -8816,6 +8943,17 @@ ${url}`,
8816
8943
  memoryEmpty: "Project memory is empty.",
8817
8944
  memoryClearAsk: "Clear project memory?",
8818
8945
  memoryReplaceAsk: "Replace all project memory notes with this text?",
8946
+ systemPromptTitle: "System prompt addition",
8947
+ systemPromptUsage: "/systemp [show] \xB7 /systemp set <text> \xB7 /systemp clear",
8948
+ systemPromptEmpty: "No extra system prompt. Set one with /systemp set <text>.",
8949
+ systemPromptSet: "System prompt addition saved.",
8950
+ systemPromptCleared: "System prompt addition cleared.",
8951
+ systemPromptEnabled: "System prompt enabled.",
8952
+ systemPromptDisabled: "System prompt disabled (kept in config).",
8953
+ stepsCurrent: (n) => n === 0 ? "Model calls per turn: unlimited" : `Model calls per turn: ${n}`,
8954
+ stepsSet: (n) => `Model calls per turn set to ${n}`,
8955
+ stepsUnlimited: "Model calls per turn: unlimited (0). The turn budget still applies.",
8956
+ stepsInvalid: "Usage: /steps <0-1000> (0 = unlimited)",
8819
8957
  promptDeleted: (name) => `Deleted prompt "${name}"`,
8820
8958
  promptNotFound: (name) => `Prompt "${name}" not found.`,
8821
8959
  promptsEmpty: "No saved prompts. Use /prompt save <name>.",
@@ -8858,6 +8996,8 @@ ${url}`,
8858
8996
  theme: "change the accent colour",
8859
8997
  lang: "change the interface language",
8860
8998
  memory: "view, replace or clear project memory",
8999
+ systemp: "view, set, load from file, or toggle the extra system prompt",
9000
+ steps: "cap model calls per turn",
8861
9001
  prompt: "insert or save a prompt",
8862
9002
  "prompt save": "save the current or recent text as a prompt",
8863
9003
  "prompt delete": "delete a saved prompt",
@@ -9053,6 +9193,17 @@ ${url}`,
9053
9193
  memoryEmpty: "\u041F\u0430\u043C\u044F\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0443\u0441\u0442\u0430.",
9054
9194
  memoryClearAsk: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430?",
9055
9195
  memoryReplaceAsk: "\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u044C \u0432\u0441\u0435 \u0437\u0430\u043C\u0435\u0442\u043A\u0438 \u043F\u0430\u043C\u044F\u0442\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u044D\u0442\u0438\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C?",
9196
+ systemPromptTitle: "\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443",
9197
+ systemPromptUsage: "/systemp [show] \xB7 /systemp set <\u0442\u0435\u043A\u0441\u0442> \xB7 /systemp clear",
9198
+ systemPromptEmpty: "\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u043A \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443 \u043D\u0435\u0442. \u0417\u0430\u0434\u0430\u0439 \u0447\u0435\u0440\u0435\u0437 /systemp set <\u0442\u0435\u043A\u0441\u0442>.",
9199
+ systemPromptSet: "\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E.",
9200
+ systemPromptCleared: "\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443 \u0443\u0434\u0430\u043B\u0435\u043D\u043E.",
9201
+ systemPromptEnabled: "\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E.",
9202
+ systemPromptDisabled: "\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E (\u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E \u0432 \u043A\u043E\u043D\u0444\u0438\u0433\u0435).",
9203
+ stepsCurrent: (n) => n === 0 ? "\u0412\u044B\u0437\u043E\u0432\u043E\u0432 \u043C\u043E\u0434\u0435\u043B\u0438 \u0437\u0430 \u0445\u043E\u0434: \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430" : `\u0412\u044B\u0437\u043E\u0432\u043E\u0432 \u043C\u043E\u0434\u0435\u043B\u0438 \u0437\u0430 \u0445\u043E\u0434: ${n}`,
9204
+ stepsSet: (n) => `\u0412\u044B\u0437\u043E\u0432\u043E\u0432 \u043C\u043E\u0434\u0435\u043B\u0438 \u0437\u0430 \u0445\u043E\u0434 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E: ${n}`,
9205
+ stepsUnlimited: "\u0412\u044B\u0437\u043E\u0432\u043E\u0432 \u043C\u043E\u0434\u0435\u043B\u0438 \u0437\u0430 \u0445\u043E\u0434: \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430 (0). \u0411\u044E\u0434\u0436\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0432\u0441\u0451 \u0435\u0449\u0451 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442.",
9206
+ stepsInvalid: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: /steps <0-1000> (0 = \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430)",
9056
9207
  promptDeleted: (name) => `\u041F\u0440\u043E\u043C\u0442 \xAB${name}\xBB \u0443\u0434\u0430\u043B\u0451\u043D`,
9057
9208
  promptNotFound: (name) => `\u041F\u0440\u043E\u043C\u0442 \xAB${name}\xBB \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.`,
9058
9209
  promptsEmpty: "\u0421\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0445 \u043F\u0440\u043E\u043C\u0442\u043E\u0432 \u043D\u0435\u0442. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 /prompt save <\u0438\u043C\u044F>.",
@@ -9095,6 +9246,8 @@ ${url}`,
9095
9246
  theme: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
9096
9247
  lang: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u044F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430",
9097
9248
  memory: "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C, \u0437\u0430\u043C\u0435\u043D\u0438\u0442\u044C \u0438\u043B\u0438 \u043E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u0430\u043C\u044F\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430",
9249
+ systemp: "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C, \u0437\u0430\u0434\u0430\u0442\u044C, \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0438\u0437 \u0444\u0430\u0439\u043B\u0430 \u0438\u043B\u0438 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C/\u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0434\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u043C\u0443 \u043F\u0440\u043E\u043C\u043F\u0442\u0443",
9250
+ steps: "\u043B\u0438\u043C\u0438\u0442 \u0432\u044B\u0437\u043E\u0432\u043E\u0432 \u043C\u043E\u0434\u0435\u043B\u0438 \u0437\u0430 \u0445\u043E\u0434",
9098
9251
  prompt: "\u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0438\u043B\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u043C\u0442",
9099
9252
  skills: "\u0441\u043F\u0438\u0441\u043E\u043A \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0441\u043A\u0438\u043B\u043B\u043E\u0432",
9100
9253
  "prompt delete": "\u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0439 \u043F\u0440\u043E\u043C\u0442",
@@ -9273,18 +9426,30 @@ function LanguagePicker({ onPick }) {
9273
9426
  }
9274
9427
 
9275
9428
  // src/ui/components/Onboarding.tsx
9276
- import { Box as Box4, Text as Text4 } from "ink";
9429
+ import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
9277
9430
  import Spinner from "ink-spinner";
9278
9431
  import TextInput from "ink-text-input";
9279
9432
  import { useState as useState2 } from "react";
9280
9433
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
9281
- function Onboarding({ onSubmit }) {
9434
+ function Onboarding({
9435
+ onSubmit,
9436
+ onCancel
9437
+ }) {
9282
9438
  const theme = useTheme();
9283
9439
  const strings = useStrings();
9284
9440
  const [step, setStep] = useState2("url");
9285
9441
  const [url, setUrl] = useState2("");
9286
9442
  const [key, setKey] = useState2("");
9287
9443
  const [error, setError] = useState2("");
9444
+ useInput2((_input, inputKey) => {
9445
+ if (!inputKey.escape || step === "working") return;
9446
+ if (step === "key") {
9447
+ setError("");
9448
+ setStep("url");
9449
+ } else {
9450
+ onCancel?.();
9451
+ }
9452
+ });
9288
9453
  const submitKey = async (value) => {
9289
9454
  if (value.trim() === "") return;
9290
9455
  setStep("working");
@@ -10290,13 +10455,12 @@ function ContextMeter({
10290
10455
  }
10291
10456
 
10292
10457
  // src/ui/components/SubagentChat.tsx
10293
- import { Box as Box12, Text as Text12, useInput as useInput2 } from "ink";
10458
+ import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
10294
10459
  import { useState as useState7 } from "react";
10295
10460
 
10296
10461
  // src/ui/components/Transcript.tsx
10297
10462
  import { Box as Box11, Static, Text as Text11 } from "ink";
10298
10463
  import { memo as memo2, useEffect as useEffect3, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
10299
- import Spinner2 from "ink-spinner";
10300
10464
  import stringWidth4 from "string-width";
10301
10465
 
10302
10466
  // src/ui/markdown.tsx
@@ -11186,10 +11350,7 @@ function AssistantView({ bubble: source, maxRows }) {
11186
11350
  return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
11187
11351
  frameThinking !== "" && /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx11(Markdown, { dim: true, children: frameThinking }) }),
11188
11352
  bubble.streaming ? /* @__PURE__ */ jsx11(Text11, { children: frameText }) : /* @__PURE__ */ jsx11(Markdown, { children: bubble.text }),
11189
- bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
11190
- /* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
11191
- " thinking"
11192
- ] })
11353
+ bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "thinking\u2026" })
11193
11354
  ] });
11194
11355
  }
11195
11356
  function clipTextToRows(text, maxRows, columns) {
@@ -11350,7 +11511,7 @@ function SubagentChat({
11350
11511
  const strings = useStrings();
11351
11512
  const [offset, setOffset] = useState7(0);
11352
11513
  const total = bubble?.bubbles.length ?? 0;
11353
- useInput2((_input, key) => {
11514
+ useInput3((_input, key) => {
11354
11515
  if (key.escape) onClose();
11355
11516
  else if (key.upArrow) setOffset((value) => Math.min(Math.max(0, total - 1), value + 1));
11356
11517
  else if (key.downArrow) setOffset((value) => Math.max(0, value - 1));
@@ -11399,25 +11560,44 @@ function SubagentChat({
11399
11560
 
11400
11561
  // src/ui/components/TerminalViewport.tsx
11401
11562
  import { Box as Box13 } from "ink";
11402
- import { jsx as jsx13 } from "react/jsx-runtime";
11403
- var INTERACTIVE_CHROME_ROWS = 11;
11563
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
11564
+ var CHROME_ROWS = 16;
11404
11565
  var MAX_LIVE_ROWS = 11;
11405
11566
  var MIN_LIVE_ROWS = 4;
11406
- function liveTranscriptRows(rows) {
11567
+ var BOTTOM_SLACK_ROWS = 3;
11568
+ function liveTranscriptRows(rows, chromeRows) {
11407
11569
  if (!Number.isFinite(rows)) return MAX_LIVE_ROWS;
11408
- return Math.max(MIN_LIVE_ROWS, Math.min(MAX_LIVE_ROWS, Math.floor(rows) - INTERACTIVE_CHROME_ROWS - 2));
11570
+ const chrome = chromeRows ?? CHROME_ROWS;
11571
+ const budget = Math.floor(rows) - chrome - BOTTOM_SLACK_ROWS - 1;
11572
+ return Math.max(MIN_LIVE_ROWS, Math.min(MAX_LIVE_ROWS, budget));
11409
11573
  }
11410
11574
  function TerminalViewport({ children, rows }) {
11411
11575
  const { columns } = useTerminalSize();
11412
- return /* @__PURE__ */ jsx13(
11576
+ const liveRows = liveTranscriptRows(rows);
11577
+ const items = Array.isArray(children) ? children : [children];
11578
+ const [transcript, ...chromeChildren] = items;
11579
+ return /* @__PURE__ */ jsxs13(
11413
11580
  Box13,
11414
11581
  {
11415
- width: columns,
11582
+ width: columns - 1,
11416
11583
  maxWidth: "100%",
11417
11584
  flexDirection: "column",
11418
- maxHeight: liveTranscriptRows(rows) + INTERACTIVE_CHROME_ROWS,
11585
+ maxHeight: liveRows + CHROME_ROWS,
11419
11586
  overflowY: "hidden",
11420
- children
11587
+ children: [
11588
+ /* @__PURE__ */ jsx13(
11589
+ Box13,
11590
+ {
11591
+ flexDirection: "column",
11592
+ flexShrink: 1,
11593
+ minHeight: 0,
11594
+ maxHeight: liveRows,
11595
+ overflowY: "hidden",
11596
+ children: transcript
11597
+ }
11598
+ ),
11599
+ /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", flexShrink: 0, children: chromeChildren })
11600
+ ]
11421
11601
  }
11422
11602
  );
11423
11603
  }
@@ -11675,7 +11855,7 @@ function safeSummary(summarize, name, input) {
11675
11855
  }
11676
11856
 
11677
11857
  // src/ui/App.tsx
11678
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
11858
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
11679
11859
  var EFFORTS = ["auto", "low", "medium", "high", "xhigh", "max"];
11680
11860
  var STREAM_FRAME_MS = 50;
11681
11861
  var CUSTOM_ACCENT = "__custom__";
@@ -11957,6 +12137,7 @@ function App({
11957
12137
  });
11958
12138
  sessionStart.current = Date.now();
11959
12139
  turns.current = 0;
12140
+ speedMeter.current = createSpeedMeter();
11960
12141
  void runtime.persist([]).catch(
11961
12142
  (error) => notice("error", error instanceof Error ? error.message : String(error))
11962
12143
  );
@@ -12693,6 +12874,68 @@ ${strings.mcpAddUsage}`
12693
12874
  notice("info", runtime.readMemory() || strings.memoryEmpty);
12694
12875
  return;
12695
12876
  }
12877
+ case "systemp": {
12878
+ const sub = rest[0]?.toLowerCase();
12879
+ if (sub === "set") {
12880
+ const text = rawRest.replace(/^\S+\s*/, "").trim();
12881
+ if (!text) {
12882
+ const typed = await promptInput(strings.systemPromptTitle);
12883
+ if (!typed || typed.trim() === "") return;
12884
+ await runtime.setSystemPrompt(typed);
12885
+ notice("info", strings.systemPromptSet);
12886
+ return;
12887
+ }
12888
+ await runtime.setSystemPrompt(text);
12889
+ notice("info", strings.systemPromptSet);
12890
+ return;
12891
+ }
12892
+ if (sub === "load") {
12893
+ const filePath = rawRest.replace(/^\S+\s*/, "").trim();
12894
+ if (!filePath) {
12895
+ notice("warn", strings.systemPromptUsage);
12896
+ return;
12897
+ }
12898
+ try {
12899
+ const text = await runtime.loadSystemPromptFile(filePath);
12900
+ await runtime.setSystemPrompt(text);
12901
+ notice("info", strings.systemPromptSet);
12902
+ } catch (error) {
12903
+ notice("error", error instanceof Error ? error.message : String(error));
12904
+ }
12905
+ return;
12906
+ }
12907
+ if (sub === "clear") {
12908
+ if (!runtime.getSystemPrompt()) {
12909
+ notice("info", strings.systemPromptEmpty);
12910
+ return;
12911
+ }
12912
+ await runtime.setSystemPrompt("");
12913
+ notice("info", strings.systemPromptCleared);
12914
+ return;
12915
+ }
12916
+ if (sub === "on") {
12917
+ await runtime.setSystemPromptEnabled(true);
12918
+ notice("info", strings.systemPromptEnabled);
12919
+ return;
12920
+ }
12921
+ if (sub === "off") {
12922
+ await runtime.setSystemPromptEnabled(false);
12923
+ notice("info", strings.systemPromptDisabled);
12924
+ return;
12925
+ }
12926
+ if (sub !== void 0 && sub !== "" && sub !== "show") {
12927
+ notice("warn", strings.systemPromptUsage);
12928
+ return;
12929
+ }
12930
+ const current = runtime.getSystemPrompt();
12931
+ if (!current) {
12932
+ notice("info", strings.systemPromptEmpty);
12933
+ return;
12934
+ }
12935
+ const enabled = runtime.isSystemPromptEnabled();
12936
+ notice("info", (enabled ? "" : "[disabled] ") + current);
12937
+ return;
12938
+ }
12696
12939
  case "effort": {
12697
12940
  notice("info", runtime.effortDescription(history.current));
12698
12941
  const choice = EFFORTS.includes(rawRest) ? rawRest : await pick(
@@ -12705,6 +12948,21 @@ ${strings.mcpAddUsage}`
12705
12948
  forceRender((n) => n + 1);
12706
12949
  return;
12707
12950
  }
12951
+ case "steps": {
12952
+ const current = runtime.getMaxTurnSteps();
12953
+ if (!rest[0]) {
12954
+ notice("info", strings.stepsCurrent(current));
12955
+ return;
12956
+ }
12957
+ const parsed = Number(rest[0]);
12958
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 1e3) {
12959
+ notice("warn", strings.stepsInvalid);
12960
+ return;
12961
+ }
12962
+ await runtime.setMaxTurnSteps(parsed);
12963
+ notice("info", parsed === 0 ? strings.stepsUnlimited : strings.stepsSet(parsed));
12964
+ return;
12965
+ }
12708
12966
  case "budget": {
12709
12967
  const current = runtime.getMaxTokensPerTurn();
12710
12968
  if (rest[0]) {
@@ -13194,13 +13452,16 @@ ${strings.mcpAddUsage}`
13194
13452
  notice("info", `${summary2}
13195
13453
  ${strings.configAt(runtime.configPath())}`);
13196
13454
  return summary2;
13455
+ },
13456
+ onCancel: () => {
13457
+ if (!runtime.needsSetup()) setSetup(false);
13197
13458
  }
13198
13459
  }
13199
13460
  )
13200
13461
  );
13201
13462
  }
13202
13463
  return shell(
13203
- /* @__PURE__ */ jsxs13(TerminalViewport, { rows, children: [
13464
+ /* @__PURE__ */ jsxs14(TerminalViewport, { rows, children: [
13204
13465
  /* @__PURE__ */ jsx14(
13205
13466
  Transcript,
13206
13467
  {
@@ -13330,10 +13591,10 @@ function TextInputOverlay({
13330
13591
  const submit = useCallback(() => {
13331
13592
  onAnswer(value || null);
13332
13593
  }, [value, onAnswer]);
13333
- useInput3((_input, key) => {
13594
+ useInput4((_input, key) => {
13334
13595
  if (key.escape) onCancel();
13335
13596
  });
13336
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
13597
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
13337
13598
  /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: title }),
13338
13599
  /* @__PURE__ */ jsx14(
13339
13600
  TextInput2,
@@ -13453,10 +13714,15 @@ function clearTerminal() {
13453
13714
  if (process.stdout.isTTY) process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
13454
13715
  }
13455
13716
  var TUI_RENDER_OPTIONS = {
13456
- // Keep unchanged streamed lines in place instead of clearing and redrawing
13457
- // the whole live region on every frame.
13458
- incrementalRendering: true,
13459
- maxFps: 30
13717
+ // Incremental rendering is off: Ink's incremental diff races with its own
13718
+ // <Static> commits and leaves stale rows behind (the prompt frame appears
13719
+ // twice). Standard rendering clears the whole frame each time — a little
13720
+ // more work, but no stale lines.
13721
+ incrementalRendering: false,
13722
+ // Throttling defers writes; when a bubble closes into <Static> mid-stream,
13723
+ // the deferred frame and the static write race and leave stale rows behind.
13724
+ // Unthrottled renders cost a little CPU but keep every frame consistent.
13725
+ maxFps: 0
13460
13726
  };
13461
13727
  async function startTui(options) {
13462
13728
  const { runtime, history, warnings, shutdown } = await boot({