@kernelonpanic/kitcode 1.3.3 → 1.4.0

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/README.md CHANGED
@@ -24,14 +24,14 @@ OpenAI-compatible providers, including OpenRouter and local model servers.
24
24
  - persistent and resumable sessions with native terminal scrollback;
25
25
  - searchable session management with rename, delete, and Markdown export;
26
26
  - input history with the `↑` and `↓` keys;
27
- - queued messages while the agent is working;
27
+ - queued messages while the agent is working, with status and settings commands running immediately;
28
28
  - image and UTF-8 text attachments;
29
- - manual and automatic context compaction;
29
+ - manual and automatic context compaction with a selectable context window;
30
30
  - token, estimated cost, exact context-window, and provider rate-limit indicators;
31
31
  - automatic file checkpoints with `/undo`;
32
32
  - detected project checks after file edits;
33
33
  - model and provider switching from the TUI;
34
- - live MCP add, enable, disable, and delete operations, plus skills and subagents;
34
+ - live MCP add, enable, disable, and delete operations, plus skills and subagents running concurrently;
35
35
  - a local `/checker` report for the runtime, provider, session, context, and MCP setup;
36
36
  - English and Russian interfaces with configurable accents.
37
37
 
@@ -111,6 +111,7 @@ Press `/` to open the command list.
111
111
  | `/key [provider]` | Change API key for a provider. |
112
112
  | `/refresh [provider]` | Reload a provider's model list, bypassing the 24h cache. |
113
113
  | `/effort [auto|low|medium|high|xhigh|max]` · `/thinking` | Select reasoning effort and configure reasoning output. |
114
+ | `/context [tokens]` | Show or set the model context window used by the meter and automatic compaction. |
114
115
  | `/memory show` · `/memory set <text>` · `/memory clear` | View, replace, or clear this project's persistent notes. |
115
116
  | `/resume` · `/clear` | Resume a session or start a new one. |
116
117
  | `/sessions` | Search sessions, then resume, rename, delete, or export one. |
@@ -151,7 +152,11 @@ Press `/` to open the command list.
151
152
  | `Ctrl+V` / `Cmd+V` | Attach an image from the system clipboard; `/attach clipboard` is the explicit fallback. |
152
153
  | `Esc` | Cancel the current request or close an overlay. |
153
154
 
154
- Messages entered while the agent is running are queued and processed in order.
155
+ Messages entered while the agent is running are queued and processed in order. Status and settings
156
+ commands — `/help`, `/usage`, `/context`, `/model`, `/effort`, `/thinking`, `/budget`, `/theme`,
157
+ `/lang`, `/subagents`, `/config`, `/checker`, `/exit` — run immediately instead. Commands that
158
+ change the running turn or the session (`/clear`, `/compact`, `/resume`, `/undo`, `/login`,
159
+ `/provider`, `/logout`, `/key`, `/refresh`, `/mcp`, `/sessions`) wait for the turn to finish.
155
160
 
156
161
  ## Modes
157
162
 
@@ -166,7 +171,26 @@ Completed output is committed to
166
171
  normal terminal scrollback, so it remains smooth to scroll while a response is streaming.
167
172
 
168
173
  When exact usage reaches 80% of a known context window, KitCode automatically summarizes older
169
- turns while keeping the latest two user turns. `/compact` runs the same operation manually.
174
+ turns while keeping the latest two user turns. `/compact` runs the same operation manually, and
175
+ `/context` sets the window when the provider does not report one.
176
+
177
+ ## Context window
178
+
179
+ `/context` shows the window currently in use and opens a picker with the common sizes — `256k`,
180
+ `512k`, `1M` — plus `auto`. The argument form accepts the same values and any explicit token count:
181
+
182
+ ```text
183
+ /context 256k
184
+ /context 1m
185
+ /context 400000
186
+ /context auto
187
+ ```
188
+
189
+ The value is stored in the config as `contextWindow` and survives restarts. A manual value outranks
190
+ whatever the provider catalogue reports, so a gateway that understates its window does not distort
191
+ the meter. It feeds both the status-bar capsule and the 80% threshold for automatic compaction.
192
+ `auto` returns to the provider's number; when the provider reports none, the meter shows `ctx ?` and
193
+ automatic compaction stays off, because it needs a known window to decide anything.
170
194
 
171
195
  Before the built-in `write` and `edit` tools change a file, KitCode creates a private checkpoint.
172
196
  `/undo` restores the latest checkpoint and leaves files with newer manual changes untouched.
@@ -211,6 +235,19 @@ in the conversation. Secret-pattern redaction is best effort; do not put credent
211
235
  Context compaction separately asks the model to preserve requirements, evidence, verification
212
236
  commands and outcomes, assumptions, and remaining work. Generated summaries can still omit details.
213
237
 
238
+ ## Subagents
239
+
240
+ The `task` tool hands one self-contained sub-task to a subagent that works in its own fresh context
241
+ and reports back a single summary. Subagents share the filesystem and working directory but have no
242
+ memory of the conversation and cannot ask questions, so the prompt has to stand alone.
243
+
244
+ Consecutive `task` calls in one model response run concurrently, and there is no fixed cap on how
245
+ many: five or ten start together. Each subagent gets its own execution lane, so their tool calls and
246
+ model requests overlap instead of queueing. Two things stay deliberately serialized: approval prompts,
247
+ because the interface shows one at a time, and the per-message budget, which the whole turn shares.
248
+ The live box lists up to three running subagents and collapses the rest into a count; `/subagents`
249
+ prints every active task with its latest progress.
250
+
214
251
  ## Configuration
215
252
 
216
253
  KitCode uses these main paths:
@@ -242,9 +279,10 @@ Minimal manual provider configuration:
242
279
  }
243
280
  ```
244
281
 
245
- The main settings are `model`, `effort`, `thinking`, `maxTokens`, `budget`, and `diagnostics`,
246
- `theme`, `permissions`, `providers`, and `mcp`. `budget` controls local per-message safety limits for model
247
- requests, tokens, estimated cost (when model pricing is available), and subagents. These values are
282
+ The main settings are `model`, `effort`, `thinking`, `contextWindow`, `maxTokens`, `budget`,
283
+ `diagnostics`, `theme`, `permissions`, `providers`, and `mcp`. `budget` controls local per-message
284
+ safety limits for model requests, tokens, estimated cost (when model pricing is available), and
285
+ subagents. These values are
248
286
  not the provider's account balance or rate limits. `diagnostics.autoRun`
249
287
  enables checks after file edits; `diagnostics.commands` can replace automatic detection with up to
250
288
  eight explicit commands. KitCode checks npm for a newer version on every app start and prints the
package/README.ru.md CHANGED
@@ -24,14 +24,15 @@ OpenAI-совместимые провайдеры, включая OpenRouter и
24
24
  - продолжение и восстановление прошлых сессий с обычным scrollback терминала;
25
25
  - поиск по сессиям, переименование, удаление и экспорт в Markdown;
26
26
  - история введённых сообщений по клавишам `↑` и `↓`;
27
- - очередь сообщений, пока агент занят;
27
+ - очередь сообщений, пока агент занят, а статусные команды и настройки выполняются сразу;
28
28
  - вложения PNG, JPEG, GIF, WebP и текстовых UTF-8 файлов;
29
- - ручное и автоматическое сжатие старого контекста;
29
+ - ручное и автоматическое сжатие старого контекста с выбором размера окна;
30
30
  - индикатор токенов, стоимости, точного заполнения контекста и rate limits провайдера;
31
31
  - автоматические чекпоинты файлов и команда `/undo`;
32
32
  - автоматическое определение проверок проекта после правок;
33
33
  - переключение моделей и провайдеров из TUI;
34
- - добавление, включение, выключение и удаление MCP без перезапуска, skills и субагенты;
34
+ - добавление, включение, выключение и удаление MCP без перезапуска, skills и субагенты,
35
+ работающие параллельно;
35
36
  - команда `/checker` для проверки runtime, провайдера, сессии, контекста и MCP;
36
37
  - русский и английский интерфейс, настраиваемый цвет темы.
37
38
 
@@ -111,6 +112,7 @@ http://localhost:11434/v1
111
112
  | `/key [provider]` | Сменить ключ провайдера. |
112
113
  | `/refresh [provider]` | Перечитать список моделей провайдера в обход суточного кеша. |
113
114
  | `/effort [auto|low|medium|high|xhigh|max]` · `/thinking` | Выбрать effort и настроить reasoning. |
115
+ | `/context [tokens\|auto]` | Показать или задать окно контекста модели для индикатора и автосжатия. |
114
116
  | `/memory show` · `/memory set <текст>` · `/memory clear` | Просмотреть, заменить или очистить постоянные заметки проекта. |
115
117
  | `/resume` · `/clear` | Восстановить сессию или начать новую. |
116
118
  | `/sessions` | Найти сессию и открыть, переименовать, удалить или экспортировать её. |
@@ -151,6 +153,10 @@ http://localhost:11434/v1
151
153
  | `Esc` | Остановить текущий запрос или закрыть окно. |
152
154
 
153
155
  Сообщения можно вводить, пока агент работает: они попадут в очередь и выполнятся по порядку.
156
+ Статусные команды и настройки — `/help`, `/usage`, `/context`, `/model`, `/effort`, `/thinking`,
157
+ `/budget`, `/theme`, `/lang`, `/subagents`, `/config`, `/checker`, `/exit` — выполняются сразу.
158
+ Команды, которые меняют текущий ход или сессию (`/clear`, `/compact`, `/resume`, `/undo`, `/login`,
159
+ `/provider`, `/logout`, `/key`, `/refresh`, `/mcp`, `/sessions`), ждут завершения хода.
154
160
 
155
161
  ## Режимы
156
162
 
@@ -165,7 +171,39 @@ http://localhost:11434/v1
165
171
  scrollback терминала, поэтому историю можно плавно листать даже во время потокового ответа.
166
172
 
167
173
  При достижении 80% известного окна KitCode автоматически сворачивает старые сообщения в сводку,
168
- оставляя последние два пользовательских хода. Команда `/compact` запускает это вручную.
174
+ оставляя последние два пользовательских хода. Команда `/compact` запускает это вручную, а
175
+ `/context` задаёт окно, если провайдер его не сообщает.
176
+
177
+ ## Окно контекста
178
+
179
+ `/context` показывает текущее окно и открывает выбор из типичных размеров — `256k`, `512k`, `1M` —
180
+ плюс `auto`. Аргумент принимает те же значения и любое явное число токенов:
181
+
182
+ ```text
183
+ /context 256k
184
+ /context 1m
185
+ /context 400000
186
+ /context auto
187
+ ```
188
+
189
+ Значение хранится в конфиге как `contextWindow` и переживает перезапуски. Ручное значение важнее
190
+ того, что сообщает каталог провайдера, поэтому шлюз, занижающий окно, не искажает индикатор.
191
+ Оно питает и капсулу в статус-баре, и порог 80% для автосжатия. `auto` возвращает число провайдера;
192
+ если провайдер ничего не сообщает, индикатор показывает `ctx ?`, а автосжатие остаётся выключенным,
193
+ потому что без известного окна решать нечего.
194
+
195
+ ## Субагенты
196
+
197
+ Инструмент `task` передаёт одну самодостаточную подзадачу субагенту, который работает в своём
198
+ свежем контексте и возвращает один итог. Субагенты делят файловую систему и рабочую папку, но не
199
+ помнят переписку и не могут задавать вопросы, поэтому промт должен быть самостоятельным.
200
+
201
+ Подряд идущие вызовы `task` в одном ответе модели выполняются параллельно, и фиксированного
202
+ лимита нет: пять или десять стартуют одновременно. У каждого субагента свой execution lane, поэтому
203
+ их инструментальные вызовы и запросы к модели перекрываются, а не выстраиваются в очередь. Две вещи
204
+ остаются заведомо последовательными: подтверждения, потому что интерфейс показывает их по одному,
205
+ и бюджет на сообщение, общий для всего хода. Живой блок показывает до трёх работающих субагентов,
206
+ остальные сворачиваются в счётчик; `/subagents` печатает каждую активную задачу с её прогрессом.
169
207
 
170
208
  Перед изменением файла встроенными инструментами `write` и `edit` KitCode создаёт приватный
171
209
  чекпоинт. Команда `/undo` восстанавливает последний чекпоинт и не перезаписывает более свежие
@@ -241,9 +279,9 @@ OpenAI-совместимых моделей параметр не отправ
241
279
  }
242
280
  ```
243
281
 
244
- Основные параметры: `model`, `effort`, `thinking`, `maxTokens`, `budget` и `diagnostics`,
245
- `theme`, `permissions`, `providers` и `mcp`. Секция `budget` задаёт лимиты запросов к модели, токенов,
246
- примерной стоимости (если провайдер сообщает цены) и субагентов на одно сообщение.
282
+ Основные параметры: `model`, `effort`, `thinking`, `contextWindow`, `maxTokens`, `budget` и
283
+ `diagnostics`, `theme`, `permissions`, `providers` и `mcp`. Секция `budget` задаёт лимиты запросов к
284
+ модели, токенов, примерной стоимости (если провайдер сообщает цены) и субагентов на одно сообщение.
247
285
  `diagnostics.autoRun` включает проверки после правок, а `diagnostics.commands` позволяет заменить
248
286
  автоопределение списком до восьми команд. При каждом входе KitCode проверяет новую версию в npm и,
249
287
  если она вышла, показывает команду обновления. `/update` повторяет проверку вручную.
package/dist/index.js CHANGED
@@ -75,6 +75,9 @@ var configSchema = z.object({
75
75
  theme: themeSchema.default({ accent: "#a78bfa" }),
76
76
  effort: effortSchema.default("xhigh"),
77
77
  thinking: z.boolean().default(true),
78
+ // Manual override for the model's context window. Unset means the window
79
+ // comes from the provider catalogue or the endpoint's model list.
80
+ contextWindow: z.number().int().min(1e3).max(1e7).optional(),
78
81
  maxTokens: z.number().int().positive().max(2e5).default(64e3),
79
82
  budget: budgetSchema.default({
80
83
  maxTokensPerTurn: 1e7,
@@ -3704,12 +3707,12 @@ var SUBAGENT_SYSTEM = [
3704
3707
  ].join("\n");
3705
3708
  function createSubagentRunner(makeConfig, tools) {
3706
3709
  const permissionLane = createExecutionLane();
3707
- const executionLane = createExecutionLane();
3708
- const modelRequestLane = createExecutionLane();
3709
3710
  return {
3710
3711
  async run(request) {
3711
3712
  if (request.signal.aborted) return SUBAGENT_CANCELLED;
3712
3713
  let steps = 0;
3714
+ const executionLane = createExecutionLane();
3715
+ const modelRequestLane = createExecutionLane();
3713
3716
  const base = makeConfig(SUBAGENT_SYSTEM, tools);
3714
3717
  const cfg = {
3715
3718
  ...base,
@@ -3940,7 +3943,7 @@ function clip(value) {
3940
3943
  // package.json
3941
3944
  var package_default = {
3942
3945
  name: "@kernelonpanic/kitcode",
3943
- version: "1.3.3",
3946
+ version: "1.4.0",
3944
3947
  description: "Terminal coding agent with a config you never have to write by hand",
3945
3948
  type: "module",
3946
3949
  license: "MIT",
@@ -3973,6 +3976,7 @@ var package_default = {
3973
3976
  dependencies: {
3974
3977
  "@anthropic-ai/sdk": "^0.115.0",
3975
3978
  "@modelcontextprotocol/sdk": "^1.30.0",
3979
+ build: "^0.1.4",
3976
3980
  chalk: "^6.0.0",
3977
3981
  commander: "^15.0.0",
3978
3982
  "fast-glob": "^3.3.3",
@@ -4008,7 +4012,7 @@ var package_default = {
4008
4012
 
4009
4013
  // src/version.ts
4010
4014
  var KITCODE_VERSION = package_default.version;
4011
- var KITCODE_COMMIT = true ? "81f72aca6f23448aa2239b7bd9175da0f9e5862c" : "development";
4015
+ var KITCODE_COMMIT = true ? "f5ecc5fb8dac2872eeb1a988705c06552b250129" : "development";
4012
4016
 
4013
4017
  // src/mcp/client.ts
4014
4018
  var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
@@ -6619,6 +6623,7 @@ async function boot(options) {
6619
6623
  const notifyContext = () => {
6620
6624
  for (const listener of contextListeners) listener();
6621
6625
  };
6626
+ const resolveContextWindow = (ref, discovered) => config.contextWindow ?? discovered ?? knownContextWindow(ref);
6622
6627
  const knownContextWindow = (ref) => {
6623
6628
  const parsed = parseModelRef(ref);
6624
6629
  if (!parsed) return null;
@@ -6635,27 +6640,27 @@ async function boot(options) {
6635
6640
  modelRef = ref;
6636
6641
  if (latestRateLimits?.model !== ref) latestRateLimits = void 0;
6637
6642
  contextUsage = restoredContext?.model === ref ? restoredContext : void 0;
6638
- modelContextWindow = discoveredWindow ?? knownContextWindow(ref);
6643
+ modelContextWindow = resolveContextWindow(ref, discoveredWindow);
6639
6644
  notifyContext();
6640
6645
  };
6641
- modelContextWindow = knownContextWindow(modelRef);
6646
+ modelContextWindow = resolveContextWindow(modelRef);
6642
6647
  async function refreshModelContextWindow() {
6643
6648
  const targetRef = modelRef;
6644
6649
  const parsed = parseModelRef(targetRef);
6645
6650
  if (!parsed) {
6646
6651
  if (modelRef === targetRef) {
6647
- modelContextWindow = null;
6652
+ modelContextWindow = config.contextWindow ?? null;
6648
6653
  notifyContext();
6649
6654
  }
6650
6655
  return;
6651
6656
  }
6652
- let next = modelContextWindow ?? knownContextWindow(targetRef);
6657
+ let next = resolveContextWindow(targetRef);
6653
6658
  try {
6654
6659
  const provider = registry.get(parsed.provider);
6655
6660
  const models = await loadModels(provider);
6656
6661
  rememberModels(parsed.provider, models);
6657
6662
  const found = models.find((m) => m.id === parsed.model);
6658
- next = found?.contextWindow ?? next;
6663
+ next = resolveContextWindow(targetRef, found?.contextWindow);
6659
6664
  } catch {
6660
6665
  }
6661
6666
  if (modelRef === targetRef) {
@@ -6999,6 +7004,16 @@ async function boot(options) {
6999
7004
  draft.thinking = enabled;
7000
7005
  });
7001
7006
  },
7007
+ getContextWindowOverride: () => config.contextWindow,
7008
+ async setContextWindowOverride(tokens3) {
7009
+ await persistConfig((draft) => {
7010
+ if (tokens3 === void 0) delete draft.contextWindow;
7011
+ else draft.contextWindow = tokens3;
7012
+ });
7013
+ modelContextWindow = config.contextWindow ?? resolveContextWindow(modelRef);
7014
+ notifyContext();
7015
+ void refreshModelContextWindow();
7016
+ },
7002
7017
  getMaxTokensPerTurn: () => config.budget.maxTokensPerTurn,
7003
7018
  async setMaxTokensPerTurn(tokens3) {
7004
7019
  await persistConfig((draft) => {
@@ -7632,36 +7647,38 @@ function stdioServer(name, command, args) {
7632
7647
  }
7633
7648
 
7634
7649
  // src/ui/commands.ts
7650
+ var BUSY_SAFE = { runsWhileBusy: true };
7635
7651
  var COMMANDS = [
7636
- { name: "model" },
7652
+ { name: "model", ...BUSY_SAFE },
7637
7653
  { name: "login" },
7638
7654
  { name: "provider" },
7639
7655
  { name: "logout", args: "[provider]" },
7640
7656
  { name: "key", args: "[provider]" },
7641
7657
  { name: "refresh", args: "[provider]" },
7642
- { name: "effort" },
7643
- { name: "thinking" },
7644
- { name: "budget", args: "[tokens]" },
7645
- { name: "theme", args: "[color]" },
7646
- { name: "lang" },
7658
+ { name: "effort", ...BUSY_SAFE },
7659
+ { name: "thinking", ...BUSY_SAFE },
7660
+ { name: "context", args: "[tokens|auto]", ...BUSY_SAFE },
7661
+ { name: "budget", args: "[tokens]", ...BUSY_SAFE },
7662
+ { name: "theme", args: "[color]", ...BUSY_SAFE },
7663
+ { name: "lang", ...BUSY_SAFE },
7647
7664
  { name: "prompt", args: "[save <name>|delete <name>]" },
7648
7665
  { name: "skills", args: "[install <source>]" },
7649
7666
  { name: "bypass" },
7650
- { name: "usage" },
7667
+ { name: "usage", ...BUSY_SAFE },
7651
7668
  { name: "mcp", args: "[add|list|delete|enable|disable]" },
7652
7669
  { name: "attach", args: "<path|clipboard|clear>" },
7653
7670
  { name: "memory", args: "[show|set <text>|clear]" },
7654
7671
  { name: "compact" },
7655
7672
  { name: "update" },
7656
- { name: "checker" },
7673
+ { name: "checker", ...BUSY_SAFE },
7657
7674
  { name: "sessions", args: "[list|rename|delete [all]|export]" },
7658
- { name: "config" },
7675
+ { name: "config", ...BUSY_SAFE },
7659
7676
  { name: "resume" },
7660
7677
  { name: "undo" },
7661
7678
  { name: "clear" },
7662
- { name: "help" },
7663
- { name: "exit" },
7664
- { name: "subagents" }
7679
+ { name: "help", ...BUSY_SAFE },
7680
+ { name: "exit", ...BUSY_SAFE },
7681
+ { name: "subagents", ...BUSY_SAFE }
7665
7682
  ];
7666
7683
  var SUBCOMMANDS = [
7667
7684
  { name: "mcp add", args: "<name> <url|-- command>" },
@@ -7675,6 +7692,10 @@ var SUBCOMMANDS = [
7675
7692
  { name: "sessions delete all" },
7676
7693
  { name: "sessions export", args: "[id] [path]" }
7677
7694
  ];
7695
+ function commandRunsWhileBusy(name) {
7696
+ if (name === "quit") return true;
7697
+ return COMMANDS.some((command) => command.name === name && command.runsWhileBusy === true);
7698
+ }
7678
7699
  function matchCommands(line) {
7679
7700
  if (!line.startsWith("/")) return [];
7680
7701
  const token = line.slice(1);
@@ -7977,6 +7998,13 @@ ${url}`,
7977
7998
  undoResult: (restored, removed) => `Undo complete: ${restored} restored, ${removed} newly created removed.`,
7978
7999
  undoConflicts: (paths) => `Kept newer changes in: ${paths.join(", ")}`,
7979
8000
  undoFailed: (failures) => `Could not restore: ${failures.join("; ")}`,
8001
+ titleContext: "Context window",
8002
+ contextCurrent: (tokens3) => `Context window: ${tokens3.toLocaleString()} tokens`,
8003
+ contextSet: (tokens3) => `Context window set to ${tokens3.toLocaleString()} tokens. Auto-compact triggers at 80%.`,
8004
+ contextAuto: (tokens3) => tokens3 === null ? "Context window: unknown \u2014 using whatever the provider reports." : `Context window: auto (${tokens3.toLocaleString()} tokens from the provider).`,
8005
+ contextUnknown: "Context window: unknown. Set it with /context 256k, /context 512k or /context 1m.",
8006
+ contextInvalid: "Usage: /context 256k | 512k | 1m | <tokens> | auto",
8007
+ contextCurrentLabel: "current",
7980
8008
  titleAccent: "Accent colour",
7981
8009
  titleEffort: "Reasoning depth",
7982
8010
  titleModel: "Model",
@@ -7997,6 +8025,7 @@ ${url}`,
7997
8025
  refresh: "reload the model list for a provider",
7998
8026
  effort: "set reasoning depth",
7999
8027
  thinking: "toggle reasoning",
8028
+ context: "set or show the model context window",
8000
8029
  budget: "set token budget per turn",
8001
8030
  theme: "change the accent colour",
8002
8031
  lang: "change the interface language",
@@ -8169,6 +8198,13 @@ ${url}`,
8169
8198
  undoResult: (restored, removed) => `\u041E\u0442\u043A\u0430\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D: \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E ${restored}, \u0443\u0434\u0430\u043B\u0435\u043D\u043E \u043D\u043E\u0432\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432 ${removed}.`,
8170
8199
  undoConflicts: (paths) => `\u0411\u043E\u043B\u0435\u0435 \u0441\u0432\u0435\u0436\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B \u0432: ${paths.join(", ")}`,
8171
8200
  undoFailed: (failures) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C: ${failures.join("; ")}`,
8201
+ titleContext: "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430",
8202
+ contextCurrent: (tokens3) => `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: ${tokens3.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432`,
8203
+ contextSet: (tokens3) => `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: ${tokens3.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432. \u0410\u0432\u0442\u043E\u0441\u0436\u0430\u0442\u0438\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 80%.`,
8204
+ contextAuto: (tokens3) => tokens3 === null ? "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E \u2014 \u0431\u0435\u0440\u0451\u043C \u0442\u043E, \u0447\u0442\u043E \u0441\u043E\u043E\u0431\u0449\u0430\u0435\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440." : `\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430: \u0430\u0432\u0442\u043E (${tokens3.toLocaleString()} \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043E\u0442 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430).`,
8205
+ contextUnknown: "\u041E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E. \u0417\u0430\u0434\u0430\u0439 \u0435\u0433\u043E: /context 256k, /context 512k \u0438\u043B\u0438 /context 1m.",
8206
+ contextInvalid: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: /context 256k | 512k | 1m | <\u0442\u043E\u043A\u0435\u043D\u044B> | auto",
8207
+ contextCurrentLabel: "\u0442\u0435\u043A\u0443\u0449\u0435\u0435",
8172
8208
  titleAccent: "\u0426\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
8173
8209
  titleEffort: "\u0413\u043B\u0443\u0431\u0438\u043D\u0430 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
8174
8210
  titleModel: "\u041C\u043E\u0434\u0435\u043B\u044C",
@@ -8189,6 +8225,7 @@ ${url}`,
8189
8225
  refresh: "\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
8190
8226
  effort: "\u0433\u043B\u0443\u0431\u0438\u043D\u0430 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
8191
8227
  thinking: "\u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C/\u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u044F",
8228
+ context: "\u0437\u0430\u0434\u0430\u0442\u044C \u0438\u043B\u0438 \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043E\u043A\u043D\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u043C\u043E\u0434\u0435\u043B\u0438",
8192
8229
  budget: "\u043B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0437\u0430 \u0445\u043E\u0434 (0 = \u0431\u0435\u0437\u043B\u0438\u043C\u0438\u0442)",
8193
8230
  theme: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0446\u0432\u0435\u0442 \u0430\u043A\u0446\u0435\u043D\u0442\u0430",
8194
8231
  lang: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u044F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430",
@@ -8691,9 +8728,9 @@ var PromptInput = memo(function PromptInput2({
8691
8728
  onListFiles
8692
8729
  }) {
8693
8730
  const theme = useTheme();
8694
- const { columns, rows } = useTerminalSize();
8731
+ const { columns } = useTerminalSize();
8695
8732
  const panelWidth = Math.max(2, columns - 1);
8696
- const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, rows - 16));
8733
+ const inputRows = MAX_INPUT_ROWS;
8697
8734
  const strings = useStrings();
8698
8735
  const [catFrame, setCatFrame] = useState4(0);
8699
8736
  useEffect(() => {
@@ -9207,13 +9244,12 @@ function ContextMeter({
9207
9244
  // src/ui/components/TerminalViewport.tsx
9208
9245
  import { Box as Box10 } from "ink";
9209
9246
  import { jsx as jsx10 } from "react/jsx-runtime";
9210
- function interactiveViewportRows(rows) {
9211
- if (!Number.isFinite(rows)) return 22;
9212
- return Math.max(1, Math.floor(rows) - 2);
9213
- }
9214
9247
  var INTERACTIVE_CHROME_ROWS = 14;
9248
+ var MAX_LIVE_ROWS = 8;
9249
+ var MIN_LIVE_ROWS = 4;
9215
9250
  function liveTranscriptRows(rows) {
9216
- return Math.max(1, interactiveViewportRows(rows) - INTERACTIVE_CHROME_ROWS);
9251
+ if (!Number.isFinite(rows)) return MAX_LIVE_ROWS;
9252
+ return Math.max(MIN_LIVE_ROWS, Math.min(MAX_LIVE_ROWS, Math.floor(rows) - INTERACTIVE_CHROME_ROWS - 2));
9217
9253
  }
9218
9254
  function TerminalViewport({ children, rows }) {
9219
9255
  const { columns } = useTerminalSize();
@@ -9223,7 +9259,7 @@ function TerminalViewport({ children, rows }) {
9223
9259
  width: columns,
9224
9260
  maxWidth: "100%",
9225
9261
  flexDirection: "column",
9226
- maxHeight: interactiveViewportRows(rows),
9262
+ maxHeight: liveTranscriptRows(rows) + INTERACTIVE_CHROME_ROWS,
9227
9263
  overflowY: "hidden",
9228
9264
  children
9229
9265
  }
@@ -9242,9 +9278,17 @@ import { createContext as createContext3, Fragment as Fragment4, useContext as u
9242
9278
  import stringWidth from "string-width";
9243
9279
  import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
9244
9280
  var DimContext = createContext3(false);
9245
- function Text10({ dimColor, color, ...props }) {
9281
+ function Text10({ dimColor, color, bold, ...props }) {
9246
9282
  const dim = useContext3(DimContext);
9247
- return /* @__PURE__ */ jsx11(InkText, { ...props, color: dim ? void 0 : color, dimColor: dimColor ?? dim });
9283
+ return /* @__PURE__ */ jsx11(
9284
+ InkText,
9285
+ {
9286
+ ...props,
9287
+ color: dim ? void 0 : color,
9288
+ bold: dim ? void 0 : bold,
9289
+ dimColor: dimColor ?? dim
9290
+ }
9291
+ );
9248
9292
  }
9249
9293
  function Markdown({ children, dim = false }) {
9250
9294
  const blocks = useMemo3(() => extractBlocks(children), [children]);
@@ -10260,6 +10304,18 @@ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
10260
10304
  var EFFORTS = ["auto", "low", "medium", "high", "xhigh", "max"];
10261
10305
  var STREAM_FRAME_MS = 50;
10262
10306
  var MAX_ATTACHMENTS = 8;
10307
+ var CONTEXT_PRESETS = [256e3, 512e3, 1e6];
10308
+ function formatContextTokens(tokens3) {
10309
+ if (tokens3 >= 1e6) return `${tokens3 / 1e6}M`;
10310
+ return `${Math.round(tokens3 / 1e3)}k`;
10311
+ }
10312
+ function parseContextTokens(input) {
10313
+ const match = /^(\d+(?:\.\d+)?)\s*([km])?$/i.exec(input.trim());
10314
+ if (!match) return null;
10315
+ const scale = match[2]?.toLowerCase() === "m" ? 1e6 : match[2]?.toLowerCase() === "k" ? 1e3 : 1;
10316
+ const tokens3 = Math.round(Number(match[1]) * scale);
10317
+ return tokens3 >= 1e3 && tokens3 <= 1e7 ? tokens3 : null;
10318
+ }
10263
10319
  function App({
10264
10320
  runtime,
10265
10321
  initialHistory,
@@ -11180,6 +11236,57 @@ ${strings.mcpAddUsage}`
11180
11236
  }
11181
11237
  return;
11182
11238
  }
11239
+ case "context": {
11240
+ const override = runtime.getContextWindowOverride();
11241
+ const current = runtime.modelContext();
11242
+ const requested = rest[0]?.toLowerCase();
11243
+ if (requested !== void 0 && requested !== "") {
11244
+ if (requested === "auto") {
11245
+ await runtime.setContextWindowOverride(void 0);
11246
+ notice("info", strings.contextAuto(runtime.modelContext().window));
11247
+ forceRender((n) => n + 1);
11248
+ return;
11249
+ }
11250
+ const tokens3 = parseContextTokens(requested);
11251
+ if (tokens3 === null) {
11252
+ notice("warn", strings.contextInvalid);
11253
+ return;
11254
+ }
11255
+ await runtime.setContextWindowOverride(tokens3);
11256
+ notice("info", strings.contextSet(tokens3));
11257
+ forceRender((n) => n + 1);
11258
+ return;
11259
+ }
11260
+ const effective = override ?? current.window;
11261
+ notice("info", effective === null ? strings.contextUnknown : strings.contextCurrent(effective));
11262
+ const choice = await pick(
11263
+ strings.titleContext,
11264
+ CONTEXT_PRESETS.map((preset) => ({
11265
+ key: String(preset),
11266
+ label: formatContextTokens(preset),
11267
+ hint: current.window === preset ? strings.contextCurrentLabel : void 0
11268
+ })).concat([
11269
+ {
11270
+ key: "auto",
11271
+ label: "auto",
11272
+ hint: override === void 0 ? strings.contextCurrentLabel : void 0
11273
+ }
11274
+ ])
11275
+ );
11276
+ if (!choice) return;
11277
+ const chosen = CONTEXT_PRESETS.find((preset) => String(preset) === choice);
11278
+ if (choice === "auto") {
11279
+ await runtime.setContextWindowOverride(void 0);
11280
+ notice("info", strings.contextAuto(runtime.modelContext().window));
11281
+ } else if (chosen !== void 0) {
11282
+ await runtime.setContextWindowOverride(chosen);
11283
+ notice("info", strings.contextSet(chosen));
11284
+ } else {
11285
+ return;
11286
+ }
11287
+ forceRender((n) => n + 1);
11288
+ return;
11289
+ }
11183
11290
  case "model": {
11184
11291
  const items = await runtime.listModelItems();
11185
11292
  if (items.length === 0) {
@@ -11420,8 +11527,7 @@ Rename the file if you want a different name.`);
11420
11527
  const submitSlash = () => {
11421
11528
  if (!detachedInput) setInput("");
11422
11529
  const command = text.slice(1).trim().split(/\s+/)[0]?.toLowerCase();
11423
- const liveCommand = ["help", "usage", "subagents", "config", "exit", "quit"].includes(command ?? "");
11424
- if (busyRef.current && !liveCommand || slashPendingRef.current > 0) {
11530
+ if (busyRef.current && !commandRunsWhileBusy(command) || slashPendingRef.current > 0) {
11425
11531
  queueRef.current = [...queueRef.current, { kind: "command", line: text }];
11426
11532
  setPendingCount(queueRef.current.length);
11427
11533
  return;