@kernelonpanic/kitcode 1.2.6 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +220 -65
- 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);
|
|
@@ -1422,6 +1430,10 @@ async function runTurn(cfg, history, hooks, signal) {
|
|
|
1422
1430
|
let pauses = 0;
|
|
1423
1431
|
let steps = 0;
|
|
1424
1432
|
for (; ; ) {
|
|
1433
|
+
if (signal.aborted) {
|
|
1434
|
+
hooks.onEvent({ type: "turn_end", stopReason: "aborted" });
|
|
1435
|
+
return sanitizeHistory(messages);
|
|
1436
|
+
}
|
|
1425
1437
|
if (++steps > MAX_TURN_STEPS) {
|
|
1426
1438
|
hooks.onEvent({
|
|
1427
1439
|
type: "notice",
|
|
@@ -1508,12 +1520,16 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
|
|
|
1508
1520
|
signal
|
|
1509
1521
|
};
|
|
1510
1522
|
let outcome;
|
|
1523
|
+
let text = "";
|
|
1524
|
+
let thinking = "";
|
|
1511
1525
|
for await (const event of streamWithRetry(cfg.provider, request, cfg.modelRef, signal)) {
|
|
1512
1526
|
switch (event.type) {
|
|
1513
1527
|
case "text_delta":
|
|
1528
|
+
text += event.text;
|
|
1514
1529
|
hooks.onEvent({ type: "text_delta", text: event.text });
|
|
1515
1530
|
break;
|
|
1516
1531
|
case "thinking_delta":
|
|
1532
|
+
thinking += event.text;
|
|
1517
1533
|
hooks.onEvent({ type: "thinking_delta", text: event.text });
|
|
1518
1534
|
break;
|
|
1519
1535
|
case "usage":
|
|
@@ -1532,7 +1548,12 @@ async function consumeStream(cfg, messages, hooks, signal, maxTokens) {
|
|
|
1532
1548
|
}
|
|
1533
1549
|
}
|
|
1534
1550
|
if (outcome) return outcome;
|
|
1535
|
-
if (signal.aborted)
|
|
1551
|
+
if (signal.aborted) {
|
|
1552
|
+
const content = [];
|
|
1553
|
+
if (thinking) content.push({ type: "thinking", text: thinking });
|
|
1554
|
+
if (text) content.push({ type: "text", text });
|
|
1555
|
+
return { content, stopReason: "aborted" };
|
|
1556
|
+
}
|
|
1536
1557
|
throw new Error(
|
|
1537
1558
|
`"${cfg.provider.id}" ended the stream without completing the turn \u2014 nothing was generated. The endpoint answered, but not with a usable ${cfg.provider.kind} response.`
|
|
1538
1559
|
);
|
|
@@ -1542,8 +1563,29 @@ async function* streamWithRetry(provider, request, modelRef, signal) {
|
|
|
1542
1563
|
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
1543
1564
|
if (signal.aborted) return;
|
|
1544
1565
|
try {
|
|
1545
|
-
|
|
1546
|
-
|
|
1566
|
+
const stream = provider.stream(request)[Symbol.asyncIterator]();
|
|
1567
|
+
let cancel;
|
|
1568
|
+
let cancelTimer;
|
|
1569
|
+
const cancelled = new Promise((resolve3) => {
|
|
1570
|
+
cancel = () => {
|
|
1571
|
+
cancelTimer ??= setTimeout(() => resolve3({ done: true, value: void 0 }), 100);
|
|
1572
|
+
};
|
|
1573
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
1574
|
+
if (signal.aborted) cancel();
|
|
1575
|
+
});
|
|
1576
|
+
try {
|
|
1577
|
+
for (; ; ) {
|
|
1578
|
+
const next = await Promise.race([stream.next(), cancelled]);
|
|
1579
|
+
if (next.done) break;
|
|
1580
|
+
if (!signal.aborted || next.value.type === "usage" || next.value.type === "done") {
|
|
1581
|
+
yield next.value;
|
|
1582
|
+
}
|
|
1583
|
+
if (next.value.type === "done") break;
|
|
1584
|
+
}
|
|
1585
|
+
} finally {
|
|
1586
|
+
signal.removeEventListener("abort", cancel);
|
|
1587
|
+
if (cancelTimer !== void 0) clearTimeout(cancelTimer);
|
|
1588
|
+
void stream.return?.().catch(() => void 0);
|
|
1547
1589
|
}
|
|
1548
1590
|
return;
|
|
1549
1591
|
} catch (error) {
|
|
@@ -1551,7 +1593,12 @@ async function* streamWithRetry(provider, request, modelRef, signal) {
|
|
|
1551
1593
|
if (attempt >= MAX_RETRIES2) break;
|
|
1552
1594
|
const waitMs = retryBackoffMs(error, attempt);
|
|
1553
1595
|
if (waitMs > 0) {
|
|
1554
|
-
|
|
1596
|
+
try {
|
|
1597
|
+
await sleep(waitMs, signal);
|
|
1598
|
+
} catch (error2) {
|
|
1599
|
+
if (signal.aborted) return;
|
|
1600
|
+
throw error2;
|
|
1601
|
+
}
|
|
1555
1602
|
continue;
|
|
1556
1603
|
}
|
|
1557
1604
|
break;
|
|
@@ -1762,7 +1809,7 @@ function createTurnBudget(limits, resolvePricing = pricingFor) {
|
|
|
1762
1809
|
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
1810
|
};
|
|
1764
1811
|
}
|
|
1765
|
-
if (current.costUsd !== null && current.costUsd >= limits.maxCostUsdPerTurn) {
|
|
1812
|
+
if (limits.maxCostUsdPerTurn > 0 && current.costUsd !== null && current.costUsd >= limits.maxCostUsdPerTurn) {
|
|
1766
1813
|
return {
|
|
1767
1814
|
allowed: false,
|
|
1768
1815
|
reason: `Turn stopped at the cost budget ($${limits.maxCostUsdPerTurn.toFixed(2)}). Send another message to continue.`
|
|
@@ -1781,7 +1828,7 @@ function createTurnBudget(limits, resolvePricing = pricingFor) {
|
|
|
1781
1828
|
};
|
|
1782
1829
|
}
|
|
1783
1830
|
const pricing = resolvePricing(request.modelRef);
|
|
1784
|
-
if (current.costUsd !== null) {
|
|
1831
|
+
if (limits.maxCostUsdPerTurn > 0 && current.costUsd !== null) {
|
|
1785
1832
|
const remainingUsd = limits.maxCostUsdPerTurn - current.costUsd;
|
|
1786
1833
|
const effectivePricing = pricing ?? UNKNOWN_MODEL_PRICING;
|
|
1787
1834
|
const inputUsd = estimatedInput * effectivePricing.input / 1e6;
|
|
@@ -3258,11 +3305,25 @@ function createSubagentRunner(makeConfig, tools) {
|
|
|
3258
3305
|
return {
|
|
3259
3306
|
async run(request) {
|
|
3260
3307
|
if (request.signal.aborted) return SUBAGENT_CANCELLED;
|
|
3308
|
+
let steps = 0;
|
|
3261
3309
|
const base = makeConfig(SUBAGENT_SYSTEM, tools);
|
|
3262
3310
|
const cfg = {
|
|
3263
3311
|
...base,
|
|
3312
|
+
provider: {
|
|
3313
|
+
id: base.provider.id,
|
|
3314
|
+
kind: base.provider.kind,
|
|
3315
|
+
listModels: () => base.provider.listModels(),
|
|
3316
|
+
knownModels: () => base.provider.knownModels(),
|
|
3317
|
+
stream: (chat) => base.provider.stream(steps === MAX_SUBAGENT_STEPS ? {
|
|
3318
|
+
...chat,
|
|
3319
|
+
tools: [],
|
|
3320
|
+
system: `${chat.system}
|
|
3321
|
+
|
|
3322
|
+
This is your last model call. Do not call tools. Return your findings now, clearly stating any unfinished work.`
|
|
3323
|
+
} : chat)
|
|
3324
|
+
},
|
|
3264
3325
|
tools: {
|
|
3265
|
-
get: (name) => name === TASK_TOOL_NAME ? void 0 : base.tools.get(name),
|
|
3326
|
+
get: (name) => name === TASK_TOOL_NAME || steps >= MAX_SUBAGENT_STEPS ? void 0 : base.tools.get(name),
|
|
3266
3327
|
schemas: () => base.tools.schemas().filter((schema) => schema.name !== TASK_TOOL_NAME)
|
|
3267
3328
|
},
|
|
3268
3329
|
system: `${SUBAGENT_SYSTEM}
|
|
@@ -3271,7 +3332,6 @@ Working directory: ${base.cwd}`
|
|
|
3271
3332
|
};
|
|
3272
3333
|
const limit = new AbortController();
|
|
3273
3334
|
const signal = AbortSignal.any([request.signal, limit.signal]);
|
|
3274
|
-
let steps = 0;
|
|
3275
3335
|
const hooks = {
|
|
3276
3336
|
onEvent(event) {
|
|
3277
3337
|
if (event.type === "turn_start" && ++steps > MAX_SUBAGENT_STEPS) limit.abort();
|
|
@@ -3295,6 +3355,7 @@ Working directory: ${base.cwd}`
|
|
|
3295
3355
|
];
|
|
3296
3356
|
const text = finalText(await runTurn(cfg, history, hooks, signal));
|
|
3297
3357
|
if (!limit.signal.aborted) return text;
|
|
3358
|
+
if (request.signal.aborted && text === SUBAGENT_NO_ANSWER) return SUBAGENT_CANCELLED;
|
|
3298
3359
|
return text === SUBAGENT_NO_ANSWER ? STEP_LIMIT_NOTE : `${text}
|
|
3299
3360
|
|
|
3300
3361
|
${STEP_LIMIT_NOTE}`;
|
|
@@ -3472,7 +3533,7 @@ function clip(value) {
|
|
|
3472
3533
|
// package.json
|
|
3473
3534
|
var package_default = {
|
|
3474
3535
|
name: "@kernelonpanic/kitcode",
|
|
3475
|
-
version: "1.2.
|
|
3536
|
+
version: "1.2.9",
|
|
3476
3537
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3477
3538
|
type: "module",
|
|
3478
3539
|
license: "MIT",
|
|
@@ -3540,7 +3601,7 @@ var package_default = {
|
|
|
3540
3601
|
|
|
3541
3602
|
// src/version.ts
|
|
3542
3603
|
var KITCODE_VERSION = package_default.version;
|
|
3543
|
-
var KITCODE_COMMIT = true ? "
|
|
3604
|
+
var KITCODE_COMMIT = true ? "cc499fc443e73408f49b928f204b77c48e9d2d05" : "development";
|
|
3544
3605
|
|
|
3545
3606
|
// src/mcp/client.ts
|
|
3546
3607
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -3928,7 +3989,7 @@ async function limitedResponseText(response, limit) {
|
|
|
3928
3989
|
import { chmod as chmod4, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
3929
3990
|
import path8 from "path";
|
|
3930
3991
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3931
|
-
var CACHE_VERSION =
|
|
3992
|
+
var CACHE_VERSION = 3;
|
|
3932
3993
|
async function loadModels(provider, refresh = false) {
|
|
3933
3994
|
const file = cacheFile(provider.id);
|
|
3934
3995
|
const cached = await readCache(file);
|
|
@@ -4155,13 +4216,15 @@ async function* streamTurn(client, providerId, apiKey2, req) {
|
|
|
4155
4216
|
final = await stream.finalMessage();
|
|
4156
4217
|
} catch (error) {
|
|
4157
4218
|
if (isUserAbort(error, req.signal)) {
|
|
4158
|
-
const
|
|
4159
|
-
if (
|
|
4219
|
+
const partial2 = stream.currentMessage;
|
|
4220
|
+
if (partial2) yield { type: "usage", usage: toUsage(partial2.usage) };
|
|
4160
4221
|
const limits3 = parseRateLimits(capture.headers());
|
|
4161
4222
|
if (limits3) yield { type: "rate_limits", limits: limits3 };
|
|
4162
4223
|
yield { type: "done", stopReason: "aborted", content: partialContent(thinking, text) };
|
|
4163
4224
|
return;
|
|
4164
4225
|
}
|
|
4226
|
+
const partial = stream.currentMessage;
|
|
4227
|
+
if (partial) yield { type: "usage", usage: toUsage(partial.usage) };
|
|
4165
4228
|
const limits2 = parseRateLimits(capture.headers());
|
|
4166
4229
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4167
4230
|
if (events === 0 && capture.succeeded() && !isConnectionFailure(error)) {
|
|
@@ -4195,7 +4258,7 @@ async function listModels(client, providerId, apiKey2) {
|
|
|
4195
4258
|
name: model.display_name,
|
|
4196
4259
|
contextWindow: model.max_input_tokens ?? void 0,
|
|
4197
4260
|
maxOutput: model.max_tokens ?? void 0,
|
|
4198
|
-
pricing: pricingFor(model.id)
|
|
4261
|
+
pricing: modelInfoFromRaw(model)?.pricing ?? pricingFor(model.id)
|
|
4199
4262
|
});
|
|
4200
4263
|
}
|
|
4201
4264
|
return models;
|
|
@@ -4355,6 +4418,7 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4355
4418
|
}
|
|
4356
4419
|
} catch (error) {
|
|
4357
4420
|
if (!isUserAbort(error, req.signal)) {
|
|
4421
|
+
if (sawUsage) yield { type: "usage", usage };
|
|
4358
4422
|
const limits2 = parseRateLimits(capture.headers());
|
|
4359
4423
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4360
4424
|
throw toProviderError(error, providerId, [apiKey2]);
|
|
@@ -4370,13 +4434,13 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4370
4434
|
return;
|
|
4371
4435
|
}
|
|
4372
4436
|
if (recognised === 0) throw await invalidStreamError(providerId, capture, void 0, [apiKey2]);
|
|
4437
|
+
if (sawUsage) yield { type: "usage", usage };
|
|
4373
4438
|
const content = toContentBlocks2(providerId, thinking, text, calls);
|
|
4374
4439
|
for (const block of content) {
|
|
4375
4440
|
if (block.type === "tool_use") {
|
|
4376
4441
|
yield { type: "tool_call", id: block.id, name: block.name, input: block.input };
|
|
4377
4442
|
}
|
|
4378
4443
|
}
|
|
4379
|
-
if (sawUsage) yield { type: "usage", usage };
|
|
4380
4444
|
const limits = parseRateLimits(capture.headers());
|
|
4381
4445
|
if (limits) yield { type: "rate_limits", limits };
|
|
4382
4446
|
yield { type: "done", stopReason: resolveStop(finishReason, calls.size > 0), content };
|
|
@@ -6505,6 +6569,7 @@ async function boot(options) {
|
|
|
6505
6569
|
async setMaxTokensPerTurn(tokens3) {
|
|
6506
6570
|
await persistConfig((draft) => {
|
|
6507
6571
|
draft.budget.maxTokensPerTurn = tokens3;
|
|
6572
|
+
draft.budget.maxCostUsdPerTurn = 0;
|
|
6508
6573
|
});
|
|
6509
6574
|
},
|
|
6510
6575
|
getLang: () => config.lang,
|
|
@@ -7037,8 +7102,48 @@ function validateKey(value) {
|
|
|
7037
7102
|
import { render } from "ink";
|
|
7038
7103
|
|
|
7039
7104
|
// src/ui/App.tsx
|
|
7040
|
-
import { Box as Box13, Text as Text12, useApp, useInput as useInput2,
|
|
7041
|
-
import { useCallback, useEffect as useEffect2, useMemo as
|
|
7105
|
+
import { Box as Box13, Text as Text12, useApp, useInput as useInput2, useStdout as useStdout2 } from "ink";
|
|
7106
|
+
import { useCallback, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef4, useState as useState5 } from "react";
|
|
7107
|
+
|
|
7108
|
+
// src/ui/terminal-size.ts
|
|
7109
|
+
import { useStdout } from "ink";
|
|
7110
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
7111
|
+
var stores = /* @__PURE__ */ new WeakMap();
|
|
7112
|
+
function createStore(stdout) {
|
|
7113
|
+
let size = { columns: stdout.columns || 80, rows: stdout.rows || 24 };
|
|
7114
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7115
|
+
const update = () => {
|
|
7116
|
+
const columns = stdout.columns || 80;
|
|
7117
|
+
const rows = stdout.rows || 24;
|
|
7118
|
+
if (columns === size.columns && rows === size.rows) return;
|
|
7119
|
+
size = { columns, rows };
|
|
7120
|
+
for (const listener of listeners) listener();
|
|
7121
|
+
};
|
|
7122
|
+
return {
|
|
7123
|
+
getSnapshot: () => size,
|
|
7124
|
+
subscribe(listener) {
|
|
7125
|
+
if (listeners.size === 0) stdout.on("resize", update);
|
|
7126
|
+
listeners.add(listener);
|
|
7127
|
+
update();
|
|
7128
|
+
return () => {
|
|
7129
|
+
listeners.delete(listener);
|
|
7130
|
+
if (listeners.size === 0) stdout.off("resize", update);
|
|
7131
|
+
};
|
|
7132
|
+
}
|
|
7133
|
+
};
|
|
7134
|
+
}
|
|
7135
|
+
function useTerminalSize() {
|
|
7136
|
+
const { stdout } = useStdout();
|
|
7137
|
+
const store = useMemo(() => {
|
|
7138
|
+
let current = stores.get(stdout);
|
|
7139
|
+
if (!current) {
|
|
7140
|
+
current = createStore(stdout);
|
|
7141
|
+
stores.set(stdout, current);
|
|
7142
|
+
}
|
|
7143
|
+
return current;
|
|
7144
|
+
}, [stdout]);
|
|
7145
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
7146
|
+
}
|
|
7042
7147
|
|
|
7043
7148
|
// src/mcp/add.ts
|
|
7044
7149
|
var SERVER_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
@@ -7665,7 +7770,7 @@ import { Box as Box3, Text as Text3 } from "ink";
|
|
|
7665
7770
|
import { useState } from "react";
|
|
7666
7771
|
|
|
7667
7772
|
// src/ui/components/Logo.tsx
|
|
7668
|
-
import { Box as Box2, Text as Text2
|
|
7773
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
7669
7774
|
|
|
7670
7775
|
// src/ui/workspace.ts
|
|
7671
7776
|
import { homedir } from "os";
|
|
@@ -7712,7 +7817,7 @@ var CAT = [" \u2571\\_\u2571\\ ", " ( o.o )", " > ^ < "];
|
|
|
7712
7817
|
function Logo({ subtitle, workspace }) {
|
|
7713
7818
|
const theme = useTheme();
|
|
7714
7819
|
const strings = useStrings();
|
|
7715
|
-
const { columns } =
|
|
7820
|
+
const { columns } = useTerminalSize();
|
|
7716
7821
|
const location = workspace ? formatWorkspacePath(workspace, Math.max(6, Math.min(72, columns - 15))) : "";
|
|
7717
7822
|
return /* @__PURE__ */ jsxs2(Box2, { width: "100%", marginBottom: 1, children: [
|
|
7718
7823
|
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", flexShrink: 0, children: CAT.map((line) => /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: line }, line)) }),
|
|
@@ -7970,15 +8075,16 @@ function previewText(value, limit = MAX_PREVIEW_CHARS) {
|
|
|
7970
8075
|
|
|
7971
8076
|
// src/ui/components/Picker.tsx
|
|
7972
8077
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
7973
|
-
import { useMemo, useState as useState3 } from "react";
|
|
8078
|
+
import { useMemo as useMemo2, useState as useState3 } from "react";
|
|
7974
8079
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
7975
8080
|
var WINDOW = 10;
|
|
7976
8081
|
function Picker({ title, items, onSelect, onCancel }) {
|
|
7977
8082
|
const theme = useTheme();
|
|
8083
|
+
const { columns } = useTerminalSize();
|
|
7978
8084
|
const strings = useStrings();
|
|
7979
8085
|
const [query, setQuery] = useState3("");
|
|
7980
8086
|
const [cursor, setCursor] = useState3(0);
|
|
7981
|
-
const matches =
|
|
8087
|
+
const matches = useMemo2(() => {
|
|
7982
8088
|
const needle = query.toLowerCase();
|
|
7983
8089
|
if (needle === "") return items;
|
|
7984
8090
|
return items.filter(
|
|
@@ -8005,7 +8111,7 @@ function Picker({ title, items, onSelect, onCancel }) {
|
|
|
8005
8111
|
});
|
|
8006
8112
|
const start = Math.max(0, Math.min(active2 - WINDOW + 2, matches.length - WINDOW));
|
|
8007
8113
|
const visible = matches.slice(start, start + WINDOW);
|
|
8008
|
-
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
8114
|
+
return /* @__PURE__ */ jsxs7(Box7, { width: columns, maxWidth: "100%", flexShrink: 0, flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
8009
8115
|
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accent, children: [
|
|
8010
8116
|
sanitizeTerminalText(title),
|
|
8011
8117
|
query !== "" && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
@@ -8015,7 +8121,7 @@ function Picker({ title, items, onSelect, onCancel }) {
|
|
|
8015
8121
|
] }),
|
|
8016
8122
|
matches.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: strings.noMatches }) : visible.map((item, index) => {
|
|
8017
8123
|
const selected = start + index === active2;
|
|
8018
|
-
return /* @__PURE__ */ jsxs7(Text7, { color: selected ? theme.accent : void 0, inverse: selected, children: [
|
|
8124
|
+
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate-end", color: selected ? theme.accent : void 0, inverse: selected, children: [
|
|
8019
8125
|
truncate2(sanitizeTerminalText(item.label), 70),
|
|
8020
8126
|
item.hint && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
8021
8127
|
" ",
|
|
@@ -8071,6 +8177,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8071
8177
|
attachments = []
|
|
8072
8178
|
}) {
|
|
8073
8179
|
const theme = useTheme();
|
|
8180
|
+
const { columns } = useTerminalSize();
|
|
8074
8181
|
const strings = useStrings();
|
|
8075
8182
|
const safeValue = sanitizeTerminalText(value);
|
|
8076
8183
|
const [selectionCursor, setSelectionCursor] = useState4(0);
|
|
@@ -8206,7 +8313,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8206
8313
|
});
|
|
8207
8314
|
const start = Math.max(0, Math.min(active2 - WINDOW2 + 2, suggestions.length - WINDOW2));
|
|
8208
8315
|
const visible = suggestions.slice(start, start + WINDOW2);
|
|
8209
|
-
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, flexShrink: 0, children: [
|
|
8316
|
+
return /* @__PURE__ */ jsxs8(Box8, { width: columns, maxWidth: "100%", flexDirection: "column", marginTop: 1, flexShrink: 0, children: [
|
|
8210
8317
|
/* @__PURE__ */ jsxs8(
|
|
8211
8318
|
Box8,
|
|
8212
8319
|
{
|
|
@@ -8217,7 +8324,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8217
8324
|
paddingX: 1,
|
|
8218
8325
|
children: [
|
|
8219
8326
|
/* @__PURE__ */ jsx8(Text8, { color: disabled ? "gray" : theme.accent, children: "\u203A " }),
|
|
8220
|
-
/* @__PURE__ */ jsx8(EditableText, { value: safeValue, cursor: inputCursor, placeholder: strings.placeholder }),
|
|
8327
|
+
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx8(EditableText, { value: safeValue, cursor: inputCursor, placeholder: strings.placeholder }) }),
|
|
8221
8328
|
pending && pending > 0 ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
8222
8329
|
" \xB7 ",
|
|
8223
8330
|
strings.queued(pending)
|
|
@@ -8510,9 +8617,12 @@ function liveTranscriptRows(rows) {
|
|
|
8510
8617
|
return Math.max(1, interactiveViewportRows(rows) - INTERACTIVE_CHROME_ROWS);
|
|
8511
8618
|
}
|
|
8512
8619
|
function TerminalViewport({ children, rows }) {
|
|
8620
|
+
const { columns } = useTerminalSize();
|
|
8513
8621
|
return /* @__PURE__ */ jsx10(
|
|
8514
8622
|
Box10,
|
|
8515
8623
|
{
|
|
8624
|
+
width: columns,
|
|
8625
|
+
maxWidth: "100%",
|
|
8516
8626
|
flexDirection: "column",
|
|
8517
8627
|
maxHeight: interactiveViewportRows(rows),
|
|
8518
8628
|
overflowY: "hidden",
|
|
@@ -8522,18 +8632,18 @@ function TerminalViewport({ children, rows }) {
|
|
|
8522
8632
|
}
|
|
8523
8633
|
|
|
8524
8634
|
// src/ui/components/Transcript.tsx
|
|
8525
|
-
import { Box as Box12, Static, Text as Text11
|
|
8526
|
-
import { memo as memo2, useMemo as
|
|
8635
|
+
import { Box as Box12, Static, Text as Text11 } from "ink";
|
|
8636
|
+
import { memo as memo2, useMemo as useMemo4, useRef as useRef3 } from "react";
|
|
8527
8637
|
import Spinner2 from "ink-spinner";
|
|
8528
8638
|
import stringWidth2 from "string-width";
|
|
8529
8639
|
|
|
8530
8640
|
// src/ui/markdown.tsx
|
|
8531
|
-
import { Box as Box11, Text as Text10
|
|
8532
|
-
import { Fragment as Fragment4, useMemo as
|
|
8641
|
+
import { Box as Box11, Text as Text10 } from "ink";
|
|
8642
|
+
import { Fragment as Fragment4, useMemo as useMemo3 } from "react";
|
|
8533
8643
|
import stringWidth from "string-width";
|
|
8534
8644
|
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
8535
8645
|
function Markdown({ children }) {
|
|
8536
|
-
const blocks =
|
|
8646
|
+
const blocks = useMemo3(() => extractBlocks(children), [children]);
|
|
8537
8647
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx11(BlockView, { block }, i)) });
|
|
8538
8648
|
}
|
|
8539
8649
|
function BlockView({ block }) {
|
|
@@ -8567,7 +8677,7 @@ function BlockView({ block }) {
|
|
|
8567
8677
|
case "code":
|
|
8568
8678
|
return /* @__PURE__ */ jsx11(CodeBlock, { code: block.text ?? "" });
|
|
8569
8679
|
case "hr":
|
|
8570
|
-
return /* @__PURE__ */ jsx11(Box11, {
|
|
8680
|
+
return /* @__PURE__ */ jsx11(Box11, { height: 1 });
|
|
8571
8681
|
}
|
|
8572
8682
|
}
|
|
8573
8683
|
function CodeBlock({ code }) {
|
|
@@ -8575,7 +8685,7 @@ function CodeBlock({ code }) {
|
|
|
8575
8685
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { color: "cyan", children: line }, i)) });
|
|
8576
8686
|
}
|
|
8577
8687
|
function TableView({ block }) {
|
|
8578
|
-
const { columns } =
|
|
8688
|
+
const { columns } = useTerminalSize();
|
|
8579
8689
|
const headers = block.headers ?? [];
|
|
8580
8690
|
const rows = block.rows ?? [];
|
|
8581
8691
|
if (headers.length === 0 && rows.length === 0) {
|
|
@@ -9148,26 +9258,41 @@ var BubbleView = memo2(function BubbleView2({
|
|
|
9148
9258
|
return /* @__PURE__ */ jsx12(AssistantView, { bubble, maxRows });
|
|
9149
9259
|
}
|
|
9150
9260
|
if (bubble.kind === "subagent") {
|
|
9151
|
-
return /* @__PURE__ */ jsx12(SubagentView, { bubble });
|
|
9261
|
+
return /* @__PURE__ */ jsx12(SubagentView, { bubble, maxRows });
|
|
9152
9262
|
}
|
|
9153
9263
|
return /* @__PURE__ */ jsx12(ToolView, { bubble });
|
|
9154
9264
|
});
|
|
9155
|
-
function
|
|
9156
|
-
const
|
|
9265
|
+
function splitThinkingPrefix(text, streaming) {
|
|
9266
|
+
const opening = /^\s*<think(?:ing)?>/i.exec(text);
|
|
9267
|
+
if (!opening) {
|
|
9268
|
+
const prefix = text.trimStart().toLowerCase();
|
|
9269
|
+
return { thinking: "", text: streaming && prefix !== "" && ["<think>", "<thinking>"].some((tag) => tag.startsWith(prefix)) ? "" : text };
|
|
9270
|
+
}
|
|
9271
|
+
const rest = text.slice(opening[0].length);
|
|
9272
|
+
const closing = /<\/think(?:ing)?>/i.exec(rest);
|
|
9273
|
+
return closing ? { thinking: sanitizeThinkingText(rest.slice(0, closing.index)), text: rest.slice(closing.index + closing[0].length) } : { thinking: sanitizeThinkingText(rest, streaming), text: "" };
|
|
9274
|
+
}
|
|
9275
|
+
function AssistantView({ bubble: source, maxRows }) {
|
|
9276
|
+
const tagged = splitThinkingPrefix(source.text, source.streaming);
|
|
9277
|
+
const bubble = { ...source, text: tagged.text, thinking: [source.thinking, tagged.thinking].filter(Boolean).join("\n") };
|
|
9278
|
+
const { columns } = useTerminalSize();
|
|
9157
9279
|
const frozenThinking = useRef3(void 0);
|
|
9158
9280
|
const answering = bubble.streaming && bubble.text !== "";
|
|
9159
9281
|
if (!answering) frozenThinking.current = void 0;
|
|
9160
9282
|
if (answering && frozenThinking.current === void 0) {
|
|
9161
9283
|
frozenThinking.current = bubble.thinking;
|
|
9162
9284
|
}
|
|
9163
|
-
const visibleThinking =
|
|
9285
|
+
const visibleThinking = sanitizeThinkingText(
|
|
9286
|
+
assistantThinkingForFrame(bubble, frozenThinking.current),
|
|
9287
|
+
bubble.streaming
|
|
9288
|
+
);
|
|
9164
9289
|
const liveBudget = bubble.streaming && maxRows !== void 0 ? Math.max(1, maxRows - 1) : void 0;
|
|
9165
9290
|
const thinkingBudget = liveBudget === void 0 ? void 0 : bubble.text !== "" ? Math.min(3, Math.max(0, liveBudget - 1)) : Math.max(0, liveBudget - 1);
|
|
9166
9291
|
const frameThinking = thinkingBudget === void 0 ? visibleThinking.trim() : clipTextToRows(visibleThinking.trim(), thinkingBudget, columns);
|
|
9167
9292
|
const usedThinkingRows = frameThinking === "" ? 0 : frameThinking.split("\n").length;
|
|
9168
9293
|
const frameText = liveBudget === void 0 ? bubble.text : clipTextToRows(bubble.text, Math.max(1, liveBudget - usedThinkingRows), columns);
|
|
9169
9294
|
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
9170
|
-
frameThinking !== "" && /* @__PURE__ */ jsx12(
|
|
9295
|
+
frameThinking !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { children: frameThinking }) }),
|
|
9171
9296
|
bubble.streaming ? /* @__PURE__ */ jsx12(Text11, { children: frameText }) : /* @__PURE__ */ jsx12(Markdown, { children: bubble.text }),
|
|
9172
9297
|
bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
9173
9298
|
/* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
|
|
@@ -9206,7 +9331,7 @@ function ToolView({ bubble }) {
|
|
|
9206
9331
|
const mark = bubble.state === "running" ? "\u25CC" : bubble.state === "ok" ? "\u25CF" : "\u2717";
|
|
9207
9332
|
const color = bubble.state === "error" ? theme.error : bubble.state === "ok" ? theme.ok : theme.warn;
|
|
9208
9333
|
const display = bubble.display;
|
|
9209
|
-
const diff =
|
|
9334
|
+
const diff = useMemo4(
|
|
9210
9335
|
() => display?.kind === "diff" ? diffLines(display.before, display.after) : null,
|
|
9211
9336
|
[display]
|
|
9212
9337
|
);
|
|
@@ -9244,20 +9369,20 @@ function previewLines(content) {
|
|
|
9244
9369
|
if (lines.length <= 6) return lines;
|
|
9245
9370
|
return [...lines.slice(0, 6), `\u2026 ${lines.length - 6} more lines`];
|
|
9246
9371
|
}
|
|
9247
|
-
function SubagentView({ bubble }) {
|
|
9372
|
+
function SubagentView({ bubble, maxRows }) {
|
|
9248
9373
|
const theme = useTheme();
|
|
9374
|
+
const { columns } = useTerminalSize();
|
|
9249
9375
|
const mark = bubble.state === "running" ? "\u25CC" : "\u25CF";
|
|
9250
9376
|
const color = bubble.state === "running" ? theme.warn : theme.ok;
|
|
9251
|
-
|
|
9252
|
-
|
|
9377
|
+
const latest = bubble.bubbles.at(-1);
|
|
9378
|
+
const progress = latest?.kind === "assistant" ? sanitizeThinkingText(latest.text || latest.thinking, latest.streaming) : latest?.kind === "tool" ? latest.summary : "";
|
|
9379
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, minWidth: 0, children: [
|
|
9380
|
+
/* @__PURE__ */ jsxs11(Text11, { color, bold: true, wrap: "truncate-end", children: [
|
|
9253
9381
|
mark,
|
|
9254
9382
|
" subagent: ",
|
|
9255
9383
|
bubble.description
|
|
9256
9384
|
] }),
|
|
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
|
-
] })
|
|
9385
|
+
/* @__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
9386
|
] });
|
|
9262
9387
|
}
|
|
9263
9388
|
|
|
@@ -9507,8 +9632,9 @@ function App({
|
|
|
9507
9632
|
initialHistory,
|
|
9508
9633
|
warnings = []
|
|
9509
9634
|
}) {
|
|
9510
|
-
const { exit } = useApp();
|
|
9511
|
-
const {
|
|
9635
|
+
const { exit, suspendTerminal } = useApp();
|
|
9636
|
+
const { stdout } = useStdout2();
|
|
9637
|
+
const { rows } = useTerminalSize();
|
|
9512
9638
|
const [transcript, setTranscript] = useState5(
|
|
9513
9639
|
() => warnings.reduce((state, text) => pushNotice(state, "warn", text), fromHistory(initialHistory))
|
|
9514
9640
|
);
|
|
@@ -9526,6 +9652,7 @@ function App({
|
|
|
9526
9652
|
const drainQueueRef = useRef4(() => void 0);
|
|
9527
9653
|
const [attachments, setAttachments] = useState5([]);
|
|
9528
9654
|
const attachmentsRef = useRef4([]);
|
|
9655
|
+
const attachmentGeneration = useRef4(0);
|
|
9529
9656
|
const automaticAttachmentTask = useRef4(null);
|
|
9530
9657
|
const clipboardPasteTask = useRef4(null);
|
|
9531
9658
|
const [overlay, setOverlay] = useState5({ kind: "none" });
|
|
@@ -9542,6 +9669,9 @@ function App({
|
|
|
9542
9669
|
const [context, setContext] = useState5(() => runtime.modelContext());
|
|
9543
9670
|
const transcriptEvents = useRef4([]);
|
|
9544
9671
|
const transcriptTimer = useRef4(null);
|
|
9672
|
+
const clearScreen = useCallback(() => {
|
|
9673
|
+
if (stdout.isTTY) stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
9674
|
+
}, [stdout]);
|
|
9545
9675
|
const replaceAttachments = useCallback((next) => {
|
|
9546
9676
|
attachmentsRef.current = next;
|
|
9547
9677
|
setAttachments(next);
|
|
@@ -9590,8 +9720,8 @@ function App({
|
|
|
9590
9720
|
const timer = setInterval(() => tick((n) => n + 1), 1e3);
|
|
9591
9721
|
return () => clearInterval(timer);
|
|
9592
9722
|
}, [turnStart]);
|
|
9593
|
-
const theme =
|
|
9594
|
-
const strings =
|
|
9723
|
+
const theme = useMemo5(() => makeTheme(accent), [accent]);
|
|
9724
|
+
const strings = useMemo5(() => stringsFor(lang), [lang]);
|
|
9595
9725
|
const notice = useCallback(
|
|
9596
9726
|
(level, text) => {
|
|
9597
9727
|
flushTranscriptEvents();
|
|
@@ -9709,12 +9839,20 @@ function App({
|
|
|
9709
9839
|
exit();
|
|
9710
9840
|
return;
|
|
9711
9841
|
case "clear":
|
|
9842
|
+
if (busyRef.current && abort.current) {
|
|
9843
|
+
abort.current.abort();
|
|
9844
|
+
}
|
|
9845
|
+
queueRef.current = [];
|
|
9846
|
+
setPendingCount(0);
|
|
9712
9847
|
await runtime.newSession();
|
|
9713
9848
|
history.current = [];
|
|
9714
9849
|
replaceAttachments([]);
|
|
9715
9850
|
setPromptHistory([]);
|
|
9716
|
-
|
|
9717
|
-
|
|
9851
|
+
await suspendTerminal(async () => {
|
|
9852
|
+
setTranscript(emptyTranscript());
|
|
9853
|
+
setTranscriptRevision((revision) => revision + 1);
|
|
9854
|
+
clearScreen();
|
|
9855
|
+
});
|
|
9718
9856
|
sessionStart.current = Date.now();
|
|
9719
9857
|
turns.current = 0;
|
|
9720
9858
|
void runtime.persist([]).catch(
|
|
@@ -10212,6 +10350,9 @@ ${strings.mcpAddUsage}`
|
|
|
10212
10350
|
}
|
|
10213
10351
|
case "attach": {
|
|
10214
10352
|
if (rawRest.toLowerCase() === "clear") {
|
|
10353
|
+
attachmentGeneration.current += 1;
|
|
10354
|
+
automaticAttachmentTask.current = null;
|
|
10355
|
+
clipboardPasteTask.current = null;
|
|
10215
10356
|
replaceAttachments([]);
|
|
10216
10357
|
notice("info", strings.attachmentsCleared);
|
|
10217
10358
|
return;
|
|
@@ -10223,15 +10364,18 @@ ${strings.mcpAddUsage}`
|
|
|
10223
10364
|
}
|
|
10224
10365
|
if (clipboardPasteTask.current) return;
|
|
10225
10366
|
const task = runtime.loadClipboardImage().then((block) => {
|
|
10367
|
+
if (clipboardPasteTask.current !== task) return;
|
|
10226
10368
|
if (appendAttachment(block)) {
|
|
10227
10369
|
notice(
|
|
10228
10370
|
"info",
|
|
10229
10371
|
strings.attachmentAdded(attachmentLabel(block) ?? "clipboard image")
|
|
10230
10372
|
);
|
|
10231
10373
|
}
|
|
10232
|
-
}).catch(
|
|
10233
|
-
|
|
10234
|
-
|
|
10374
|
+
}).catch((error) => {
|
|
10375
|
+
if (clipboardPasteTask.current === task) {
|
|
10376
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10377
|
+
}
|
|
10378
|
+
});
|
|
10235
10379
|
clipboardPasteTask.current = task;
|
|
10236
10380
|
try {
|
|
10237
10381
|
await task;
|
|
@@ -10540,6 +10684,7 @@ Rename the file if you want a different name.`);
|
|
|
10540
10684
|
return Promise.resolve(true);
|
|
10541
10685
|
}
|
|
10542
10686
|
const task = runtime.loadAutomaticAttachment(requestedPath).then((block) => {
|
|
10687
|
+
if (automaticAttachmentTask.current !== task) return true;
|
|
10543
10688
|
if (!block) return false;
|
|
10544
10689
|
if (!appendAttachment(block)) {
|
|
10545
10690
|
notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
|
|
@@ -10548,6 +10693,7 @@ Rename the file if you want a different name.`);
|
|
|
10548
10693
|
notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "attachment"));
|
|
10549
10694
|
return true;
|
|
10550
10695
|
}).catch(() => {
|
|
10696
|
+
if (automaticAttachmentTask.current !== task) return true;
|
|
10551
10697
|
return false;
|
|
10552
10698
|
});
|
|
10553
10699
|
automaticAttachmentTask.current = task;
|
|
@@ -10565,12 +10711,17 @@ Rename the file if you want a different name.`);
|
|
|
10565
10711
|
return;
|
|
10566
10712
|
}
|
|
10567
10713
|
const task = runtime.loadClipboardImage().then((block) => {
|
|
10714
|
+
if (clipboardPasteTask.current !== task) return;
|
|
10568
10715
|
if (!appendAttachment(block)) {
|
|
10569
10716
|
notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
|
|
10570
10717
|
return;
|
|
10571
10718
|
}
|
|
10572
10719
|
notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "clipboard image"));
|
|
10573
|
-
}).catch((error) =>
|
|
10720
|
+
}).catch((error) => {
|
|
10721
|
+
if (clipboardPasteTask.current === task) {
|
|
10722
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10723
|
+
}
|
|
10724
|
+
});
|
|
10574
10725
|
clipboardPasteTask.current = task;
|
|
10575
10726
|
void task.finally(() => {
|
|
10576
10727
|
if (clipboardPasteTask.current === task) clipboardPasteTask.current = null;
|
|
@@ -10578,6 +10729,7 @@ Rename the file if you want a different name.`);
|
|
|
10578
10729
|
}, [appendAttachment, notice, runtime, strings]);
|
|
10579
10730
|
const submit = useCallback(
|
|
10580
10731
|
async (raw) => {
|
|
10732
|
+
const generation = attachmentGeneration.current;
|
|
10581
10733
|
const pendingAttachments = [automaticAttachmentTask.current, clipboardPasteTask.current].filter(
|
|
10582
10734
|
(task) => task !== null
|
|
10583
10735
|
);
|
|
@@ -10585,6 +10737,7 @@ Rename the file if you want a different name.`);
|
|
|
10585
10737
|
if (detachedInput) {
|
|
10586
10738
|
setInput("");
|
|
10587
10739
|
await Promise.all(pendingAttachments);
|
|
10740
|
+
if (generation !== attachmentGeneration.current) return;
|
|
10588
10741
|
}
|
|
10589
10742
|
const text = raw.trim();
|
|
10590
10743
|
const submitSlash = () => {
|
|
@@ -10602,6 +10755,7 @@ Rename the file if you want a different name.`);
|
|
|
10602
10755
|
}
|
|
10603
10756
|
if (text && looksLikeAttachmentPath(text)) {
|
|
10604
10757
|
const attached = await tryQueueAutomaticAttachment(text);
|
|
10758
|
+
if (generation !== attachmentGeneration.current) return;
|
|
10605
10759
|
if (attached) {
|
|
10606
10760
|
if (!detachedInput) setInput("");
|
|
10607
10761
|
return;
|
|
@@ -10640,20 +10794,21 @@ Rename the file if you want a different name.`);
|
|
|
10640
10794
|
return;
|
|
10641
10795
|
}
|
|
10642
10796
|
if (!key.escape) return;
|
|
10643
|
-
if (
|
|
10797
|
+
if (busyRef.current && abort.current) {
|
|
10798
|
+
if (!abort.current.signal.aborted) {
|
|
10799
|
+
abort.current.abort();
|
|
10800
|
+
notice("warn", strings.cancelled);
|
|
10801
|
+
}
|
|
10802
|
+
return;
|
|
10803
|
+
}
|
|
10804
|
+
if (input !== "" || attachmentsRef.current.length > 0 || automaticAttachmentTask.current || clipboardPasteTask.current) {
|
|
10805
|
+
attachmentGeneration.current += 1;
|
|
10644
10806
|
setInput("");
|
|
10645
10807
|
replaceAttachments([]);
|
|
10646
10808
|
if (automaticAttachmentTask.current) automaticAttachmentTask.current = null;
|
|
10647
10809
|
if (clipboardPasteTask.current) clipboardPasteTask.current = null;
|
|
10648
10810
|
return;
|
|
10649
10811
|
}
|
|
10650
|
-
if (busy && abort.current) {
|
|
10651
|
-
abort.current.abort();
|
|
10652
|
-
queueRef.current = [];
|
|
10653
|
-
setPendingCount(0);
|
|
10654
|
-
notice("warn", strings.cancelled);
|
|
10655
|
-
return;
|
|
10656
|
-
}
|
|
10657
10812
|
if (overlay.kind === "permission") {
|
|
10658
10813
|
overlay.resolve("deny");
|
|
10659
10814
|
setOverlay({ kind: "none" });
|