@kernelonpanic/kitcode 1.2.6 → 1.2.8
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 +168 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ var themeSchema = z.object({
|
|
|
62
62
|
var langSchema = z.enum(["en", "ru"]);
|
|
63
63
|
var budgetSchema = z.object({
|
|
64
64
|
maxTokensPerTurn: z.number().int().min(0).max(1e7).default(1e7),
|
|
65
|
-
maxCostUsdPerTurn: z.number().
|
|
65
|
+
maxCostUsdPerTurn: z.number().nonnegative().max(1e3).default(0),
|
|
66
66
|
maxSubagentsPerTurn: z.number().int().min(0).max(16).default(3)
|
|
67
67
|
});
|
|
68
68
|
var diagnosticsSchema = z.object({
|
|
@@ -79,7 +79,7 @@ var configSchema = z.object({
|
|
|
79
79
|
maxTokens: z.number().int().positive().max(2e5).default(64e3),
|
|
80
80
|
budget: budgetSchema.default({
|
|
81
81
|
maxTokensPerTurn: 1e7,
|
|
82
|
-
maxCostUsdPerTurn:
|
|
82
|
+
maxCostUsdPerTurn: 0,
|
|
83
83
|
maxSubagentsPerTurn: 3
|
|
84
84
|
}),
|
|
85
85
|
diagnostics: diagnosticsSchema.default({ autoRun: true, commands: [] }),
|
|
@@ -999,6 +999,14 @@ function sanitizeTerminalText(value) {
|
|
|
999
999
|
}
|
|
1000
1000
|
return output;
|
|
1001
1001
|
}
|
|
1002
|
+
function sanitizeThinkingText(text, streaming = false) {
|
|
1003
|
+
const clean = sanitizeTerminalText(text).replace(/<\/?think(?:ing)?>/gi, "");
|
|
1004
|
+
if (!streaming) return clean;
|
|
1005
|
+
const start = clean.lastIndexOf("<");
|
|
1006
|
+
if (start === -1) return clean;
|
|
1007
|
+
const suffix = clean.slice(start).toLowerCase();
|
|
1008
|
+
return ["<think>", "</think>", "<thinking>", "</thinking>"].some((tag) => tag.startsWith(suffix)) ? clean.slice(0, start) : clean;
|
|
1009
|
+
}
|
|
1002
1010
|
function skipCsi(text, from) {
|
|
1003
1011
|
for (let index = from; index < text.length; index += 1) {
|
|
1004
1012
|
const code = text.charCodeAt(index);
|
|
@@ -1551,7 +1559,12 @@ async function* streamWithRetry(provider, request, modelRef, signal) {
|
|
|
1551
1559
|
if (attempt >= MAX_RETRIES2) break;
|
|
1552
1560
|
const waitMs = retryBackoffMs(error, attempt);
|
|
1553
1561
|
if (waitMs > 0) {
|
|
1554
|
-
|
|
1562
|
+
try {
|
|
1563
|
+
await sleep(waitMs, signal);
|
|
1564
|
+
} catch (error2) {
|
|
1565
|
+
if (signal.aborted) return;
|
|
1566
|
+
throw error2;
|
|
1567
|
+
}
|
|
1555
1568
|
continue;
|
|
1556
1569
|
}
|
|
1557
1570
|
break;
|
|
@@ -1762,7 +1775,7 @@ function createTurnBudget(limits, resolvePricing = pricingFor) {
|
|
|
1762
1775
|
reason: `Turn stopped at the token budget (${limits.maxTokensPerTurn.toLocaleString()} tokens). This is a safety limit to prevent runaway costs. To disable it, run: /budget 0`
|
|
1763
1776
|
};
|
|
1764
1777
|
}
|
|
1765
|
-
if (current.costUsd !== null && current.costUsd >= limits.maxCostUsdPerTurn) {
|
|
1778
|
+
if (limits.maxCostUsdPerTurn > 0 && current.costUsd !== null && current.costUsd >= limits.maxCostUsdPerTurn) {
|
|
1766
1779
|
return {
|
|
1767
1780
|
allowed: false,
|
|
1768
1781
|
reason: `Turn stopped at the cost budget ($${limits.maxCostUsdPerTurn.toFixed(2)}). Send another message to continue.`
|
|
@@ -1781,7 +1794,7 @@ function createTurnBudget(limits, resolvePricing = pricingFor) {
|
|
|
1781
1794
|
};
|
|
1782
1795
|
}
|
|
1783
1796
|
const pricing = resolvePricing(request.modelRef);
|
|
1784
|
-
if (current.costUsd !== null) {
|
|
1797
|
+
if (limits.maxCostUsdPerTurn > 0 && current.costUsd !== null) {
|
|
1785
1798
|
const remainingUsd = limits.maxCostUsdPerTurn - current.costUsd;
|
|
1786
1799
|
const effectivePricing = pricing ?? UNKNOWN_MODEL_PRICING;
|
|
1787
1800
|
const inputUsd = estimatedInput * effectivePricing.input / 1e6;
|
|
@@ -3258,11 +3271,25 @@ function createSubagentRunner(makeConfig, tools) {
|
|
|
3258
3271
|
return {
|
|
3259
3272
|
async run(request) {
|
|
3260
3273
|
if (request.signal.aborted) return SUBAGENT_CANCELLED;
|
|
3274
|
+
let steps = 0;
|
|
3261
3275
|
const base = makeConfig(SUBAGENT_SYSTEM, tools);
|
|
3262
3276
|
const cfg = {
|
|
3263
3277
|
...base,
|
|
3278
|
+
provider: {
|
|
3279
|
+
id: base.provider.id,
|
|
3280
|
+
kind: base.provider.kind,
|
|
3281
|
+
listModels: () => base.provider.listModels(),
|
|
3282
|
+
knownModels: () => base.provider.knownModels(),
|
|
3283
|
+
stream: (chat) => base.provider.stream(steps === MAX_SUBAGENT_STEPS ? {
|
|
3284
|
+
...chat,
|
|
3285
|
+
tools: [],
|
|
3286
|
+
system: `${chat.system}
|
|
3287
|
+
|
|
3288
|
+
This is your last model call. Do not call tools. Return your findings now, clearly stating any unfinished work.`
|
|
3289
|
+
} : chat)
|
|
3290
|
+
},
|
|
3264
3291
|
tools: {
|
|
3265
|
-
get: (name) => name === TASK_TOOL_NAME ? void 0 : base.tools.get(name),
|
|
3292
|
+
get: (name) => name === TASK_TOOL_NAME || steps >= MAX_SUBAGENT_STEPS ? void 0 : base.tools.get(name),
|
|
3266
3293
|
schemas: () => base.tools.schemas().filter((schema) => schema.name !== TASK_TOOL_NAME)
|
|
3267
3294
|
},
|
|
3268
3295
|
system: `${SUBAGENT_SYSTEM}
|
|
@@ -3271,7 +3298,6 @@ Working directory: ${base.cwd}`
|
|
|
3271
3298
|
};
|
|
3272
3299
|
const limit = new AbortController();
|
|
3273
3300
|
const signal = AbortSignal.any([request.signal, limit.signal]);
|
|
3274
|
-
let steps = 0;
|
|
3275
3301
|
const hooks = {
|
|
3276
3302
|
onEvent(event) {
|
|
3277
3303
|
if (event.type === "turn_start" && ++steps > MAX_SUBAGENT_STEPS) limit.abort();
|
|
@@ -3295,6 +3321,7 @@ Working directory: ${base.cwd}`
|
|
|
3295
3321
|
];
|
|
3296
3322
|
const text = finalText(await runTurn(cfg, history, hooks, signal));
|
|
3297
3323
|
if (!limit.signal.aborted) return text;
|
|
3324
|
+
if (request.signal.aborted && text === SUBAGENT_NO_ANSWER) return SUBAGENT_CANCELLED;
|
|
3298
3325
|
return text === SUBAGENT_NO_ANSWER ? STEP_LIMIT_NOTE : `${text}
|
|
3299
3326
|
|
|
3300
3327
|
${STEP_LIMIT_NOTE}`;
|
|
@@ -3472,7 +3499,7 @@ function clip(value) {
|
|
|
3472
3499
|
// package.json
|
|
3473
3500
|
var package_default = {
|
|
3474
3501
|
name: "@kernelonpanic/kitcode",
|
|
3475
|
-
version: "1.2.
|
|
3502
|
+
version: "1.2.8",
|
|
3476
3503
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3477
3504
|
type: "module",
|
|
3478
3505
|
license: "MIT",
|
|
@@ -3540,7 +3567,7 @@ var package_default = {
|
|
|
3540
3567
|
|
|
3541
3568
|
// src/version.ts
|
|
3542
3569
|
var KITCODE_VERSION = package_default.version;
|
|
3543
|
-
var KITCODE_COMMIT = true ? "
|
|
3570
|
+
var KITCODE_COMMIT = true ? "422e80dd7cd81a1c20f186b1e3ab36d6e0779b89" : "development";
|
|
3544
3571
|
|
|
3545
3572
|
// src/mcp/client.ts
|
|
3546
3573
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -3928,7 +3955,7 @@ async function limitedResponseText(response, limit) {
|
|
|
3928
3955
|
import { chmod as chmod4, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
3929
3956
|
import path8 from "path";
|
|
3930
3957
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3931
|
-
var CACHE_VERSION =
|
|
3958
|
+
var CACHE_VERSION = 3;
|
|
3932
3959
|
async function loadModels(provider, refresh = false) {
|
|
3933
3960
|
const file = cacheFile(provider.id);
|
|
3934
3961
|
const cached = await readCache(file);
|
|
@@ -4155,13 +4182,15 @@ async function* streamTurn(client, providerId, apiKey2, req) {
|
|
|
4155
4182
|
final = await stream.finalMessage();
|
|
4156
4183
|
} catch (error) {
|
|
4157
4184
|
if (isUserAbort(error, req.signal)) {
|
|
4158
|
-
const
|
|
4159
|
-
if (
|
|
4185
|
+
const partial2 = stream.currentMessage;
|
|
4186
|
+
if (partial2) yield { type: "usage", usage: toUsage(partial2.usage) };
|
|
4160
4187
|
const limits3 = parseRateLimits(capture.headers());
|
|
4161
4188
|
if (limits3) yield { type: "rate_limits", limits: limits3 };
|
|
4162
4189
|
yield { type: "done", stopReason: "aborted", content: partialContent(thinking, text) };
|
|
4163
4190
|
return;
|
|
4164
4191
|
}
|
|
4192
|
+
const partial = stream.currentMessage;
|
|
4193
|
+
if (partial) yield { type: "usage", usage: toUsage(partial.usage) };
|
|
4165
4194
|
const limits2 = parseRateLimits(capture.headers());
|
|
4166
4195
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4167
4196
|
if (events === 0 && capture.succeeded() && !isConnectionFailure(error)) {
|
|
@@ -4195,7 +4224,7 @@ async function listModels(client, providerId, apiKey2) {
|
|
|
4195
4224
|
name: model.display_name,
|
|
4196
4225
|
contextWindow: model.max_input_tokens ?? void 0,
|
|
4197
4226
|
maxOutput: model.max_tokens ?? void 0,
|
|
4198
|
-
pricing: pricingFor(model.id)
|
|
4227
|
+
pricing: modelInfoFromRaw(model)?.pricing ?? pricingFor(model.id)
|
|
4199
4228
|
});
|
|
4200
4229
|
}
|
|
4201
4230
|
return models;
|
|
@@ -4355,6 +4384,7 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4355
4384
|
}
|
|
4356
4385
|
} catch (error) {
|
|
4357
4386
|
if (!isUserAbort(error, req.signal)) {
|
|
4387
|
+
if (sawUsage) yield { type: "usage", usage };
|
|
4358
4388
|
const limits2 = parseRateLimits(capture.headers());
|
|
4359
4389
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4360
4390
|
throw toProviderError(error, providerId, [apiKey2]);
|
|
@@ -4370,13 +4400,13 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4370
4400
|
return;
|
|
4371
4401
|
}
|
|
4372
4402
|
if (recognised === 0) throw await invalidStreamError(providerId, capture, void 0, [apiKey2]);
|
|
4403
|
+
if (sawUsage) yield { type: "usage", usage };
|
|
4373
4404
|
const content = toContentBlocks2(providerId, thinking, text, calls);
|
|
4374
4405
|
for (const block of content) {
|
|
4375
4406
|
if (block.type === "tool_use") {
|
|
4376
4407
|
yield { type: "tool_call", id: block.id, name: block.name, input: block.input };
|
|
4377
4408
|
}
|
|
4378
4409
|
}
|
|
4379
|
-
if (sawUsage) yield { type: "usage", usage };
|
|
4380
4410
|
const limits = parseRateLimits(capture.headers());
|
|
4381
4411
|
if (limits) yield { type: "rate_limits", limits };
|
|
4382
4412
|
yield { type: "done", stopReason: resolveStop(finishReason, calls.size > 0), content };
|
|
@@ -6505,6 +6535,7 @@ async function boot(options) {
|
|
|
6505
6535
|
async setMaxTokensPerTurn(tokens3) {
|
|
6506
6536
|
await persistConfig((draft) => {
|
|
6507
6537
|
draft.budget.maxTokensPerTurn = tokens3;
|
|
6538
|
+
draft.budget.maxCostUsdPerTurn = 0;
|
|
6508
6539
|
});
|
|
6509
6540
|
},
|
|
6510
6541
|
getLang: () => config.lang,
|
|
@@ -7036,9 +7067,49 @@ function validateKey(value) {
|
|
|
7036
7067
|
// src/app/tui.tsx
|
|
7037
7068
|
import { render } from "ink";
|
|
7038
7069
|
|
|
7070
|
+
// src/ui/terminal-size.ts
|
|
7071
|
+
import { useStdout } from "ink";
|
|
7072
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
7073
|
+
var stores = /* @__PURE__ */ new WeakMap();
|
|
7074
|
+
function createStore(stdout) {
|
|
7075
|
+
let size = { columns: stdout.columns || 80, rows: stdout.rows || 24 };
|
|
7076
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7077
|
+
const update = () => {
|
|
7078
|
+
const columns = stdout.columns || 80;
|
|
7079
|
+
const rows = stdout.rows || 24;
|
|
7080
|
+
if (columns === size.columns && rows === size.rows) return;
|
|
7081
|
+
size = { columns, rows };
|
|
7082
|
+
for (const listener of listeners) listener();
|
|
7083
|
+
};
|
|
7084
|
+
return {
|
|
7085
|
+
getSnapshot: () => size,
|
|
7086
|
+
subscribe(listener) {
|
|
7087
|
+
if (listeners.size === 0) stdout.on("resize", update);
|
|
7088
|
+
listeners.add(listener);
|
|
7089
|
+
update();
|
|
7090
|
+
return () => {
|
|
7091
|
+
listeners.delete(listener);
|
|
7092
|
+
if (listeners.size === 0) stdout.off("resize", update);
|
|
7093
|
+
};
|
|
7094
|
+
}
|
|
7095
|
+
};
|
|
7096
|
+
}
|
|
7097
|
+
function useTerminalSize() {
|
|
7098
|
+
const { stdout } = useStdout();
|
|
7099
|
+
const store = useMemo(() => {
|
|
7100
|
+
let current = stores.get(stdout);
|
|
7101
|
+
if (!current) {
|
|
7102
|
+
current = createStore(stdout);
|
|
7103
|
+
stores.set(stdout, current);
|
|
7104
|
+
}
|
|
7105
|
+
return current;
|
|
7106
|
+
}, [stdout]);
|
|
7107
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
7108
|
+
}
|
|
7109
|
+
|
|
7039
7110
|
// src/ui/App.tsx
|
|
7040
|
-
import { Box as Box13, Text as Text12, useApp, useInput as useInput2
|
|
7041
|
-
import { useCallback, useEffect as useEffect2, useMemo as
|
|
7111
|
+
import { Box as Box13, Text as Text12, useApp, useInput as useInput2 } from "ink";
|
|
7112
|
+
import { useCallback, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef4, useState as useState5 } from "react";
|
|
7042
7113
|
|
|
7043
7114
|
// src/mcp/add.ts
|
|
7044
7115
|
var SERVER_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
@@ -7665,7 +7736,7 @@ import { Box as Box3, Text as Text3 } from "ink";
|
|
|
7665
7736
|
import { useState } from "react";
|
|
7666
7737
|
|
|
7667
7738
|
// src/ui/components/Logo.tsx
|
|
7668
|
-
import { Box as Box2, Text as Text2
|
|
7739
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
7669
7740
|
|
|
7670
7741
|
// src/ui/workspace.ts
|
|
7671
7742
|
import { homedir } from "os";
|
|
@@ -7712,7 +7783,7 @@ var CAT = [" \u2571\\_\u2571\\ ", " ( o.o )", " > ^ < "];
|
|
|
7712
7783
|
function Logo({ subtitle, workspace }) {
|
|
7713
7784
|
const theme = useTheme();
|
|
7714
7785
|
const strings = useStrings();
|
|
7715
|
-
const { columns } =
|
|
7786
|
+
const { columns } = useTerminalSize();
|
|
7716
7787
|
const location = workspace ? formatWorkspacePath(workspace, Math.max(6, Math.min(72, columns - 15))) : "";
|
|
7717
7788
|
return /* @__PURE__ */ jsxs2(Box2, { width: "100%", marginBottom: 1, children: [
|
|
7718
7789
|
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", flexShrink: 0, children: CAT.map((line) => /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: line }, line)) }),
|
|
@@ -7970,15 +8041,16 @@ function previewText(value, limit = MAX_PREVIEW_CHARS) {
|
|
|
7970
8041
|
|
|
7971
8042
|
// src/ui/components/Picker.tsx
|
|
7972
8043
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
7973
|
-
import { useMemo, useState as useState3 } from "react";
|
|
8044
|
+
import { useMemo as useMemo2, useState as useState3 } from "react";
|
|
7974
8045
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
7975
8046
|
var WINDOW = 10;
|
|
7976
8047
|
function Picker({ title, items, onSelect, onCancel }) {
|
|
7977
8048
|
const theme = useTheme();
|
|
8049
|
+
const { columns } = useTerminalSize();
|
|
7978
8050
|
const strings = useStrings();
|
|
7979
8051
|
const [query, setQuery] = useState3("");
|
|
7980
8052
|
const [cursor, setCursor] = useState3(0);
|
|
7981
|
-
const matches =
|
|
8053
|
+
const matches = useMemo2(() => {
|
|
7982
8054
|
const needle = query.toLowerCase();
|
|
7983
8055
|
if (needle === "") return items;
|
|
7984
8056
|
return items.filter(
|
|
@@ -8005,7 +8077,7 @@ function Picker({ title, items, onSelect, onCancel }) {
|
|
|
8005
8077
|
});
|
|
8006
8078
|
const start = Math.max(0, Math.min(active2 - WINDOW + 2, matches.length - WINDOW));
|
|
8007
8079
|
const visible = matches.slice(start, start + WINDOW);
|
|
8008
|
-
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
8080
|
+
return /* @__PURE__ */ jsxs7(Box7, { width: columns, maxWidth: "100%", flexShrink: 0, flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
8009
8081
|
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accent, children: [
|
|
8010
8082
|
sanitizeTerminalText(title),
|
|
8011
8083
|
query !== "" && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
@@ -8015,7 +8087,7 @@ function Picker({ title, items, onSelect, onCancel }) {
|
|
|
8015
8087
|
] }),
|
|
8016
8088
|
matches.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: strings.noMatches }) : visible.map((item, index) => {
|
|
8017
8089
|
const selected = start + index === active2;
|
|
8018
|
-
return /* @__PURE__ */ jsxs7(Text7, { color: selected ? theme.accent : void 0, inverse: selected, children: [
|
|
8090
|
+
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate-end", color: selected ? theme.accent : void 0, inverse: selected, children: [
|
|
8019
8091
|
truncate2(sanitizeTerminalText(item.label), 70),
|
|
8020
8092
|
item.hint && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
8021
8093
|
" ",
|
|
@@ -8071,6 +8143,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8071
8143
|
attachments = []
|
|
8072
8144
|
}) {
|
|
8073
8145
|
const theme = useTheme();
|
|
8146
|
+
const { columns } = useTerminalSize();
|
|
8074
8147
|
const strings = useStrings();
|
|
8075
8148
|
const safeValue = sanitizeTerminalText(value);
|
|
8076
8149
|
const [selectionCursor, setSelectionCursor] = useState4(0);
|
|
@@ -8206,7 +8279,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8206
8279
|
});
|
|
8207
8280
|
const start = Math.max(0, Math.min(active2 - WINDOW2 + 2, suggestions.length - WINDOW2));
|
|
8208
8281
|
const visible = suggestions.slice(start, start + WINDOW2);
|
|
8209
|
-
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, flexShrink: 0, children: [
|
|
8282
|
+
return /* @__PURE__ */ jsxs8(Box8, { width: columns, maxWidth: "100%", flexDirection: "column", marginTop: 1, flexShrink: 0, children: [
|
|
8210
8283
|
/* @__PURE__ */ jsxs8(
|
|
8211
8284
|
Box8,
|
|
8212
8285
|
{
|
|
@@ -8217,7 +8290,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8217
8290
|
paddingX: 1,
|
|
8218
8291
|
children: [
|
|
8219
8292
|
/* @__PURE__ */ jsx8(Text8, { color: disabled ? "gray" : theme.accent, children: "\u203A " }),
|
|
8220
|
-
/* @__PURE__ */ jsx8(EditableText, { value: safeValue, cursor: inputCursor, placeholder: strings.placeholder }),
|
|
8293
|
+
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx8(EditableText, { value: safeValue, cursor: inputCursor, placeholder: strings.placeholder }) }),
|
|
8221
8294
|
pending && pending > 0 ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
8222
8295
|
" \xB7 ",
|
|
8223
8296
|
strings.queued(pending)
|
|
@@ -8510,9 +8583,12 @@ function liveTranscriptRows(rows) {
|
|
|
8510
8583
|
return Math.max(1, interactiveViewportRows(rows) - INTERACTIVE_CHROME_ROWS);
|
|
8511
8584
|
}
|
|
8512
8585
|
function TerminalViewport({ children, rows }) {
|
|
8586
|
+
const { columns } = useTerminalSize();
|
|
8513
8587
|
return /* @__PURE__ */ jsx10(
|
|
8514
8588
|
Box10,
|
|
8515
8589
|
{
|
|
8590
|
+
width: columns,
|
|
8591
|
+
maxWidth: "100%",
|
|
8516
8592
|
flexDirection: "column",
|
|
8517
8593
|
maxHeight: interactiveViewportRows(rows),
|
|
8518
8594
|
overflowY: "hidden",
|
|
@@ -8522,18 +8598,18 @@ function TerminalViewport({ children, rows }) {
|
|
|
8522
8598
|
}
|
|
8523
8599
|
|
|
8524
8600
|
// src/ui/components/Transcript.tsx
|
|
8525
|
-
import { Box as Box12, Static, Text as Text11
|
|
8526
|
-
import { memo as memo2, useMemo as
|
|
8601
|
+
import { Box as Box12, Static, Text as Text11 } from "ink";
|
|
8602
|
+
import { memo as memo2, useMemo as useMemo4, useRef as useRef3 } from "react";
|
|
8527
8603
|
import Spinner2 from "ink-spinner";
|
|
8528
8604
|
import stringWidth2 from "string-width";
|
|
8529
8605
|
|
|
8530
8606
|
// src/ui/markdown.tsx
|
|
8531
|
-
import { Box as Box11, Text as Text10
|
|
8532
|
-
import { Fragment as Fragment4, useMemo as
|
|
8607
|
+
import { Box as Box11, Text as Text10 } from "ink";
|
|
8608
|
+
import { Fragment as Fragment4, useMemo as useMemo3 } from "react";
|
|
8533
8609
|
import stringWidth from "string-width";
|
|
8534
8610
|
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
8535
8611
|
function Markdown({ children }) {
|
|
8536
|
-
const blocks =
|
|
8612
|
+
const blocks = useMemo3(() => extractBlocks(children), [children]);
|
|
8537
8613
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx11(BlockView, { block }, i)) });
|
|
8538
8614
|
}
|
|
8539
8615
|
function BlockView({ block }) {
|
|
@@ -8567,7 +8643,7 @@ function BlockView({ block }) {
|
|
|
8567
8643
|
case "code":
|
|
8568
8644
|
return /* @__PURE__ */ jsx11(CodeBlock, { code: block.text ?? "" });
|
|
8569
8645
|
case "hr":
|
|
8570
|
-
return /* @__PURE__ */ jsx11(Box11, {
|
|
8646
|
+
return /* @__PURE__ */ jsx11(Box11, { height: 1 });
|
|
8571
8647
|
}
|
|
8572
8648
|
}
|
|
8573
8649
|
function CodeBlock({ code }) {
|
|
@@ -8575,7 +8651,7 @@ function CodeBlock({ code }) {
|
|
|
8575
8651
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { color: "cyan", children: line }, i)) });
|
|
8576
8652
|
}
|
|
8577
8653
|
function TableView({ block }) {
|
|
8578
|
-
const { columns } =
|
|
8654
|
+
const { columns } = useTerminalSize();
|
|
8579
8655
|
const headers = block.headers ?? [];
|
|
8580
8656
|
const rows = block.rows ?? [];
|
|
8581
8657
|
if (headers.length === 0 && rows.length === 0) {
|
|
@@ -9148,26 +9224,41 @@ var BubbleView = memo2(function BubbleView2({
|
|
|
9148
9224
|
return /* @__PURE__ */ jsx12(AssistantView, { bubble, maxRows });
|
|
9149
9225
|
}
|
|
9150
9226
|
if (bubble.kind === "subagent") {
|
|
9151
|
-
return /* @__PURE__ */ jsx12(SubagentView, { bubble });
|
|
9227
|
+
return /* @__PURE__ */ jsx12(SubagentView, { bubble, maxRows });
|
|
9152
9228
|
}
|
|
9153
9229
|
return /* @__PURE__ */ jsx12(ToolView, { bubble });
|
|
9154
9230
|
});
|
|
9155
|
-
function
|
|
9156
|
-
const
|
|
9231
|
+
function splitThinkingPrefix(text, streaming) {
|
|
9232
|
+
const opening = /^\s*<think(?:ing)?>/i.exec(text);
|
|
9233
|
+
if (!opening) {
|
|
9234
|
+
const prefix = text.trimStart().toLowerCase();
|
|
9235
|
+
return { thinking: "", text: streaming && prefix !== "" && ["<think>", "<thinking>"].some((tag) => tag.startsWith(prefix)) ? "" : text };
|
|
9236
|
+
}
|
|
9237
|
+
const rest = text.slice(opening[0].length);
|
|
9238
|
+
const closing = /<\/think(?:ing)?>/i.exec(rest);
|
|
9239
|
+
return closing ? { thinking: sanitizeThinkingText(rest.slice(0, closing.index)), text: rest.slice(closing.index + closing[0].length) } : { thinking: sanitizeThinkingText(rest, streaming), text: "" };
|
|
9240
|
+
}
|
|
9241
|
+
function AssistantView({ bubble: source, maxRows }) {
|
|
9242
|
+
const tagged = splitThinkingPrefix(source.text, source.streaming);
|
|
9243
|
+
const bubble = { ...source, text: tagged.text, thinking: [source.thinking, tagged.thinking].filter(Boolean).join("\n") };
|
|
9244
|
+
const { columns } = useTerminalSize();
|
|
9157
9245
|
const frozenThinking = useRef3(void 0);
|
|
9158
9246
|
const answering = bubble.streaming && bubble.text !== "";
|
|
9159
9247
|
if (!answering) frozenThinking.current = void 0;
|
|
9160
9248
|
if (answering && frozenThinking.current === void 0) {
|
|
9161
9249
|
frozenThinking.current = bubble.thinking;
|
|
9162
9250
|
}
|
|
9163
|
-
const visibleThinking =
|
|
9251
|
+
const visibleThinking = sanitizeThinkingText(
|
|
9252
|
+
assistantThinkingForFrame(bubble, frozenThinking.current),
|
|
9253
|
+
bubble.streaming
|
|
9254
|
+
);
|
|
9164
9255
|
const liveBudget = bubble.streaming && maxRows !== void 0 ? Math.max(1, maxRows - 1) : void 0;
|
|
9165
9256
|
const thinkingBudget = liveBudget === void 0 ? void 0 : bubble.text !== "" ? Math.min(3, Math.max(0, liveBudget - 1)) : Math.max(0, liveBudget - 1);
|
|
9166
9257
|
const frameThinking = thinkingBudget === void 0 ? visibleThinking.trim() : clipTextToRows(visibleThinking.trim(), thinkingBudget, columns);
|
|
9167
9258
|
const usedThinkingRows = frameThinking === "" ? 0 : frameThinking.split("\n").length;
|
|
9168
9259
|
const frameText = liveBudget === void 0 ? bubble.text : clipTextToRows(bubble.text, Math.max(1, liveBudget - usedThinkingRows), columns);
|
|
9169
9260
|
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
9170
|
-
frameThinking !== "" && /* @__PURE__ */ jsx12(
|
|
9261
|
+
frameThinking !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { children: frameThinking }) }),
|
|
9171
9262
|
bubble.streaming ? /* @__PURE__ */ jsx12(Text11, { children: frameText }) : /* @__PURE__ */ jsx12(Markdown, { children: bubble.text }),
|
|
9172
9263
|
bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
9173
9264
|
/* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
|
|
@@ -9206,7 +9297,7 @@ function ToolView({ bubble }) {
|
|
|
9206
9297
|
const mark = bubble.state === "running" ? "\u25CC" : bubble.state === "ok" ? "\u25CF" : "\u2717";
|
|
9207
9298
|
const color = bubble.state === "error" ? theme.error : bubble.state === "ok" ? theme.ok : theme.warn;
|
|
9208
9299
|
const display = bubble.display;
|
|
9209
|
-
const diff =
|
|
9300
|
+
const diff = useMemo4(
|
|
9210
9301
|
() => display?.kind === "diff" ? diffLines(display.before, display.after) : null,
|
|
9211
9302
|
[display]
|
|
9212
9303
|
);
|
|
@@ -9244,20 +9335,20 @@ function previewLines(content) {
|
|
|
9244
9335
|
if (lines.length <= 6) return lines;
|
|
9245
9336
|
return [...lines.slice(0, 6), `\u2026 ${lines.length - 6} more lines`];
|
|
9246
9337
|
}
|
|
9247
|
-
function SubagentView({ bubble }) {
|
|
9338
|
+
function SubagentView({ bubble, maxRows }) {
|
|
9248
9339
|
const theme = useTheme();
|
|
9340
|
+
const { columns } = useTerminalSize();
|
|
9249
9341
|
const mark = bubble.state === "running" ? "\u25CC" : "\u25CF";
|
|
9250
9342
|
const color = bubble.state === "running" ? theme.warn : theme.ok;
|
|
9251
|
-
|
|
9252
|
-
|
|
9343
|
+
const latest = bubble.bubbles.at(-1);
|
|
9344
|
+
const progress = latest?.kind === "assistant" ? sanitizeThinkingText(latest.text || latest.thinking, latest.streaming) : latest?.kind === "tool" ? latest.summary : "";
|
|
9345
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
|
|
9346
|
+
/* @__PURE__ */ jsxs11(Text11, { color, bold: true, wrap: "truncate-end", children: [
|
|
9253
9347
|
mark,
|
|
9254
9348
|
" subagent: ",
|
|
9255
9349
|
bubble.description
|
|
9256
9350
|
] }),
|
|
9257
|
-
/* @__PURE__ */
|
|
9258
|
-
bubble.bubbles.map((inner, index) => /* @__PURE__ */ jsx12(BubbleView, { bubble: inner }, index)),
|
|
9259
|
-
bubble.state === "done" && bubble.result && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "\u2500\u2500 result \u2500\u2500" }) })
|
|
9260
|
-
] })
|
|
9351
|
+
/* @__PURE__ */ jsx12(Box12, { marginLeft: 2, flexDirection: "column", minWidth: 0, children: bubble.state === "running" ? /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: Math.max(1, (maxRows ?? 6) - 2), overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { children: clipTextToRows(progress, Math.max(1, (maxRows ?? 6) - 2), Math.max(1, columns - 2)) }) }) : bubble.result ? /* @__PURE__ */ jsx12(Markdown, { children: sanitizeThinkingText(bubble.result) }) : null })
|
|
9261
9352
|
] });
|
|
9262
9353
|
}
|
|
9263
9354
|
|
|
@@ -9508,7 +9599,7 @@ function App({
|
|
|
9508
9599
|
warnings = []
|
|
9509
9600
|
}) {
|
|
9510
9601
|
const { exit } = useApp();
|
|
9511
|
-
const { rows } =
|
|
9602
|
+
const { rows } = useTerminalSize();
|
|
9512
9603
|
const [transcript, setTranscript] = useState5(
|
|
9513
9604
|
() => warnings.reduce((state, text) => pushNotice(state, "warn", text), fromHistory(initialHistory))
|
|
9514
9605
|
);
|
|
@@ -9526,6 +9617,7 @@ function App({
|
|
|
9526
9617
|
const drainQueueRef = useRef4(() => void 0);
|
|
9527
9618
|
const [attachments, setAttachments] = useState5([]);
|
|
9528
9619
|
const attachmentsRef = useRef4([]);
|
|
9620
|
+
const attachmentGeneration = useRef4(0);
|
|
9529
9621
|
const automaticAttachmentTask = useRef4(null);
|
|
9530
9622
|
const clipboardPasteTask = useRef4(null);
|
|
9531
9623
|
const [overlay, setOverlay] = useState5({ kind: "none" });
|
|
@@ -9590,8 +9682,8 @@ function App({
|
|
|
9590
9682
|
const timer = setInterval(() => tick((n) => n + 1), 1e3);
|
|
9591
9683
|
return () => clearInterval(timer);
|
|
9592
9684
|
}, [turnStart]);
|
|
9593
|
-
const theme =
|
|
9594
|
-
const strings =
|
|
9685
|
+
const theme = useMemo5(() => makeTheme(accent), [accent]);
|
|
9686
|
+
const strings = useMemo5(() => stringsFor(lang), [lang]);
|
|
9595
9687
|
const notice = useCallback(
|
|
9596
9688
|
(level, text) => {
|
|
9597
9689
|
flushTranscriptEvents();
|
|
@@ -10212,6 +10304,9 @@ ${strings.mcpAddUsage}`
|
|
|
10212
10304
|
}
|
|
10213
10305
|
case "attach": {
|
|
10214
10306
|
if (rawRest.toLowerCase() === "clear") {
|
|
10307
|
+
attachmentGeneration.current += 1;
|
|
10308
|
+
automaticAttachmentTask.current = null;
|
|
10309
|
+
clipboardPasteTask.current = null;
|
|
10215
10310
|
replaceAttachments([]);
|
|
10216
10311
|
notice("info", strings.attachmentsCleared);
|
|
10217
10312
|
return;
|
|
@@ -10223,15 +10318,18 @@ ${strings.mcpAddUsage}`
|
|
|
10223
10318
|
}
|
|
10224
10319
|
if (clipboardPasteTask.current) return;
|
|
10225
10320
|
const task = runtime.loadClipboardImage().then((block) => {
|
|
10321
|
+
if (clipboardPasteTask.current !== task) return;
|
|
10226
10322
|
if (appendAttachment(block)) {
|
|
10227
10323
|
notice(
|
|
10228
10324
|
"info",
|
|
10229
10325
|
strings.attachmentAdded(attachmentLabel(block) ?? "clipboard image")
|
|
10230
10326
|
);
|
|
10231
10327
|
}
|
|
10232
|
-
}).catch(
|
|
10233
|
-
|
|
10234
|
-
|
|
10328
|
+
}).catch((error) => {
|
|
10329
|
+
if (clipboardPasteTask.current === task) {
|
|
10330
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10331
|
+
}
|
|
10332
|
+
});
|
|
10235
10333
|
clipboardPasteTask.current = task;
|
|
10236
10334
|
try {
|
|
10237
10335
|
await task;
|
|
@@ -10540,6 +10638,7 @@ Rename the file if you want a different name.`);
|
|
|
10540
10638
|
return Promise.resolve(true);
|
|
10541
10639
|
}
|
|
10542
10640
|
const task = runtime.loadAutomaticAttachment(requestedPath).then((block) => {
|
|
10641
|
+
if (automaticAttachmentTask.current !== task) return true;
|
|
10543
10642
|
if (!block) return false;
|
|
10544
10643
|
if (!appendAttachment(block)) {
|
|
10545
10644
|
notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
|
|
@@ -10548,6 +10647,7 @@ Rename the file if you want a different name.`);
|
|
|
10548
10647
|
notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "attachment"));
|
|
10549
10648
|
return true;
|
|
10550
10649
|
}).catch(() => {
|
|
10650
|
+
if (automaticAttachmentTask.current !== task) return true;
|
|
10551
10651
|
return false;
|
|
10552
10652
|
});
|
|
10553
10653
|
automaticAttachmentTask.current = task;
|
|
@@ -10565,12 +10665,17 @@ Rename the file if you want a different name.`);
|
|
|
10565
10665
|
return;
|
|
10566
10666
|
}
|
|
10567
10667
|
const task = runtime.loadClipboardImage().then((block) => {
|
|
10668
|
+
if (clipboardPasteTask.current !== task) return;
|
|
10568
10669
|
if (!appendAttachment(block)) {
|
|
10569
10670
|
notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
|
|
10570
10671
|
return;
|
|
10571
10672
|
}
|
|
10572
10673
|
notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "clipboard image"));
|
|
10573
|
-
}).catch((error) =>
|
|
10674
|
+
}).catch((error) => {
|
|
10675
|
+
if (clipboardPasteTask.current === task) {
|
|
10676
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10677
|
+
}
|
|
10678
|
+
});
|
|
10574
10679
|
clipboardPasteTask.current = task;
|
|
10575
10680
|
void task.finally(() => {
|
|
10576
10681
|
if (clipboardPasteTask.current === task) clipboardPasteTask.current = null;
|
|
@@ -10578,6 +10683,7 @@ Rename the file if you want a different name.`);
|
|
|
10578
10683
|
}, [appendAttachment, notice, runtime, strings]);
|
|
10579
10684
|
const submit = useCallback(
|
|
10580
10685
|
async (raw) => {
|
|
10686
|
+
const generation = attachmentGeneration.current;
|
|
10581
10687
|
const pendingAttachments = [automaticAttachmentTask.current, clipboardPasteTask.current].filter(
|
|
10582
10688
|
(task) => task !== null
|
|
10583
10689
|
);
|
|
@@ -10585,6 +10691,7 @@ Rename the file if you want a different name.`);
|
|
|
10585
10691
|
if (detachedInput) {
|
|
10586
10692
|
setInput("");
|
|
10587
10693
|
await Promise.all(pendingAttachments);
|
|
10694
|
+
if (generation !== attachmentGeneration.current) return;
|
|
10588
10695
|
}
|
|
10589
10696
|
const text = raw.trim();
|
|
10590
10697
|
const submitSlash = () => {
|
|
@@ -10602,6 +10709,7 @@ Rename the file if you want a different name.`);
|
|
|
10602
10709
|
}
|
|
10603
10710
|
if (text && looksLikeAttachmentPath(text)) {
|
|
10604
10711
|
const attached = await tryQueueAutomaticAttachment(text);
|
|
10712
|
+
if (generation !== attachmentGeneration.current) return;
|
|
10605
10713
|
if (attached) {
|
|
10606
10714
|
if (!detachedInput) setInput("");
|
|
10607
10715
|
return;
|
|
@@ -10640,20 +10748,21 @@ Rename the file if you want a different name.`);
|
|
|
10640
10748
|
return;
|
|
10641
10749
|
}
|
|
10642
10750
|
if (!key.escape) return;
|
|
10643
|
-
if (
|
|
10751
|
+
if (busyRef.current && abort.current) {
|
|
10752
|
+
if (!abort.current.signal.aborted) {
|
|
10753
|
+
abort.current.abort();
|
|
10754
|
+
notice("warn", strings.cancelled);
|
|
10755
|
+
}
|
|
10756
|
+
return;
|
|
10757
|
+
}
|
|
10758
|
+
if (input !== "" || attachmentsRef.current.length > 0 || automaticAttachmentTask.current || clipboardPasteTask.current) {
|
|
10759
|
+
attachmentGeneration.current += 1;
|
|
10644
10760
|
setInput("");
|
|
10645
10761
|
replaceAttachments([]);
|
|
10646
10762
|
if (automaticAttachmentTask.current) automaticAttachmentTask.current = null;
|
|
10647
10763
|
if (clipboardPasteTask.current) clipboardPasteTask.current = null;
|
|
10648
10764
|
return;
|
|
10649
10765
|
}
|
|
10650
|
-
if (busy && abort.current) {
|
|
10651
|
-
abort.current.abort();
|
|
10652
|
-
queueRef.current = [];
|
|
10653
|
-
setPendingCount(0);
|
|
10654
|
-
notice("warn", strings.cancelled);
|
|
10655
|
-
return;
|
|
10656
|
-
}
|
|
10657
10766
|
if (overlay.kind === "permission") {
|
|
10658
10767
|
overlay.resolve("deny");
|
|
10659
10768
|
setOverlay({ kind: "none" });
|