@kernelonpanic/kitcode 1.2.5 → 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 +208 -74
- 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;
|
|
@@ -2467,7 +2480,7 @@ async function isFile2(file) {
|
|
|
2467
2480
|
|
|
2468
2481
|
// src/core/attachments.ts
|
|
2469
2482
|
import { execFile } from "child_process";
|
|
2470
|
-
import { lstat as lstat2, readFile as readFile6, stat as stat5 } from "fs/promises";
|
|
2483
|
+
import { lstat as lstat2, readdir as readdir3, readFile as readFile6, stat as stat5 } from "fs/promises";
|
|
2471
2484
|
import os2 from "os";
|
|
2472
2485
|
import path7 from "path";
|
|
2473
2486
|
import { fileURLToPath } from "url";
|
|
@@ -2506,15 +2519,17 @@ async function loadAttachment(cwd, requestedPath) {
|
|
|
2506
2519
|
if (error.code === "ENOENT") throw new Error(`Attachment not found: ${resolved}`);
|
|
2507
2520
|
throw error;
|
|
2508
2521
|
});
|
|
2522
|
+
if (info.isDirectory()) {
|
|
2523
|
+
const attachment = await loadFirstImageFromDirectory(resolved);
|
|
2524
|
+
if (attachment) return attachment;
|
|
2525
|
+
throw new Error(`No supported images found in directory: ${path7.basename(resolved)}`);
|
|
2526
|
+
}
|
|
2509
2527
|
if (!info.isFile()) throw new Error(`Attachment is not a regular file: ${resolved}`);
|
|
2510
2528
|
return loadResolvedAttachment(resolved, info.size);
|
|
2511
2529
|
}
|
|
2512
2530
|
async function loadAutomaticAttachment(cwd, requestedPath) {
|
|
2513
2531
|
if (!looksLikeAttachmentPath(requestedPath)) return null;
|
|
2514
|
-
const
|
|
2515
|
-
const safe = resolveInside(cwd, requested);
|
|
2516
|
-
if (!safe.ok || safe.relative === "") return null;
|
|
2517
|
-
const resolved = safe.path;
|
|
2532
|
+
const resolved = resolveAttachmentPath(cwd, requestedPath);
|
|
2518
2533
|
if (isSensitiveAutomaticPath(resolved)) {
|
|
2519
2534
|
throw new Error(
|
|
2520
2535
|
`For safety, sensitive-looking files must be attached explicitly with /attach: ${path7.basename(resolved)}`
|
|
@@ -2526,17 +2541,23 @@ async function loadAutomaticAttachment(cwd, requestedPath) {
|
|
|
2526
2541
|
if (error.code === "ENOENT" || error.code === "ENOTDIR") return null;
|
|
2527
2542
|
throw error;
|
|
2528
2543
|
});
|
|
2544
|
+
if (info?.isDirectory()) return loadFirstImageFromDirectory(resolved);
|
|
2529
2545
|
if (!info?.isFile()) return null;
|
|
2530
2546
|
return loadResolvedAttachment(resolved, info.size);
|
|
2531
2547
|
}
|
|
2548
|
+
var MAX_PATH_CHARS = 2048;
|
|
2532
2549
|
function looksLikeAttachmentPath(value) {
|
|
2533
2550
|
const trimmed = value.trim();
|
|
2534
2551
|
if (!trimmed || /[\r\n\0]/.test(trimmed)) return false;
|
|
2552
|
+
if (trimmed.length > MAX_PATH_CHARS) return false;
|
|
2535
2553
|
const candidate = normalizeInputPath(trimmed);
|
|
2536
2554
|
if (/^file:\/\//i.test(candidate)) return true;
|
|
2537
2555
|
if (path7.isAbsolute(candidate)) return true;
|
|
2538
2556
|
if (/^(?:~|\.{1,2})[\\/]/.test(candidate)) return true;
|
|
2539
|
-
if (candidate.includes("/") || candidate.includes("\\"))
|
|
2557
|
+
if (candidate.includes("/") || candidate.includes("\\")) {
|
|
2558
|
+
if (trimmed.length > 200) return false;
|
|
2559
|
+
return true;
|
|
2560
|
+
}
|
|
2540
2561
|
const basename3 = path7.basename(candidate).toLowerCase();
|
|
2541
2562
|
return path7.extname(basename3) !== "" || AUTO_PATH_NAMES.has(basename3);
|
|
2542
2563
|
}
|
|
@@ -2600,6 +2621,18 @@ async function loadResolvedAttachment(resolved, size) {
|
|
|
2600
2621
|
block: { type: "file", mediaType: textMime(resolved), text, name }
|
|
2601
2622
|
};
|
|
2602
2623
|
}
|
|
2624
|
+
async function loadFirstImageFromDirectory(dirPath) {
|
|
2625
|
+
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
2626
|
+
const entries = await readdir3(dirPath, { withFileTypes: true }).catch(() => null);
|
|
2627
|
+
if (!entries) return null;
|
|
2628
|
+
const sorted = entries.filter((entry) => entry.isFile() && IMAGE_EXTENSIONS.has(path7.extname(entry.name).toLowerCase())).sort((a, b) => a.name.localeCompare(b.name));
|
|
2629
|
+
if (sorted.length === 0) return null;
|
|
2630
|
+
const first2 = sorted[0];
|
|
2631
|
+
const filePath = path7.join(dirPath, first2.name);
|
|
2632
|
+
const info = await stat5(filePath).catch(() => null);
|
|
2633
|
+
if (!info?.isFile()) return null;
|
|
2634
|
+
return loadResolvedAttachment(filePath, info.size);
|
|
2635
|
+
}
|
|
2603
2636
|
function resolveAttachmentPath(cwd, requestedPath) {
|
|
2604
2637
|
const cleaned = normalizeInputPath(requestedPath.trim());
|
|
2605
2638
|
if (!cleaned) throw new Error("Give a file path: /attach <path>");
|
|
@@ -3238,11 +3271,25 @@ function createSubagentRunner(makeConfig, tools) {
|
|
|
3238
3271
|
return {
|
|
3239
3272
|
async run(request) {
|
|
3240
3273
|
if (request.signal.aborted) return SUBAGENT_CANCELLED;
|
|
3274
|
+
let steps = 0;
|
|
3241
3275
|
const base = makeConfig(SUBAGENT_SYSTEM, tools);
|
|
3242
3276
|
const cfg = {
|
|
3243
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
|
+
},
|
|
3244
3291
|
tools: {
|
|
3245
|
-
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),
|
|
3246
3293
|
schemas: () => base.tools.schemas().filter((schema) => schema.name !== TASK_TOOL_NAME)
|
|
3247
3294
|
},
|
|
3248
3295
|
system: `${SUBAGENT_SYSTEM}
|
|
@@ -3251,7 +3298,6 @@ Working directory: ${base.cwd}`
|
|
|
3251
3298
|
};
|
|
3252
3299
|
const limit = new AbortController();
|
|
3253
3300
|
const signal = AbortSignal.any([request.signal, limit.signal]);
|
|
3254
|
-
let steps = 0;
|
|
3255
3301
|
const hooks = {
|
|
3256
3302
|
onEvent(event) {
|
|
3257
3303
|
if (event.type === "turn_start" && ++steps > MAX_SUBAGENT_STEPS) limit.abort();
|
|
@@ -3275,6 +3321,7 @@ Working directory: ${base.cwd}`
|
|
|
3275
3321
|
];
|
|
3276
3322
|
const text = finalText(await runTurn(cfg, history, hooks, signal));
|
|
3277
3323
|
if (!limit.signal.aborted) return text;
|
|
3324
|
+
if (request.signal.aborted && text === SUBAGENT_NO_ANSWER) return SUBAGENT_CANCELLED;
|
|
3278
3325
|
return text === SUBAGENT_NO_ANSWER ? STEP_LIMIT_NOTE : `${text}
|
|
3279
3326
|
|
|
3280
3327
|
${STEP_LIMIT_NOTE}`;
|
|
@@ -3452,7 +3499,7 @@ function clip(value) {
|
|
|
3452
3499
|
// package.json
|
|
3453
3500
|
var package_default = {
|
|
3454
3501
|
name: "@kernelonpanic/kitcode",
|
|
3455
|
-
version: "1.2.
|
|
3502
|
+
version: "1.2.8",
|
|
3456
3503
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3457
3504
|
type: "module",
|
|
3458
3505
|
license: "MIT",
|
|
@@ -3520,7 +3567,7 @@ var package_default = {
|
|
|
3520
3567
|
|
|
3521
3568
|
// src/version.ts
|
|
3522
3569
|
var KITCODE_VERSION = package_default.version;
|
|
3523
|
-
var KITCODE_COMMIT = true ? "
|
|
3570
|
+
var KITCODE_COMMIT = true ? "422e80dd7cd81a1c20f186b1e3ab36d6e0779b89" : "development";
|
|
3524
3571
|
|
|
3525
3572
|
// src/mcp/client.ts
|
|
3526
3573
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -3908,7 +3955,7 @@ async function limitedResponseText(response, limit) {
|
|
|
3908
3955
|
import { chmod as chmod4, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
3909
3956
|
import path8 from "path";
|
|
3910
3957
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3911
|
-
var CACHE_VERSION =
|
|
3958
|
+
var CACHE_VERSION = 3;
|
|
3912
3959
|
async function loadModels(provider, refresh = false) {
|
|
3913
3960
|
const file = cacheFile(provider.id);
|
|
3914
3961
|
const cached = await readCache(file);
|
|
@@ -4135,13 +4182,15 @@ async function* streamTurn(client, providerId, apiKey2, req) {
|
|
|
4135
4182
|
final = await stream.finalMessage();
|
|
4136
4183
|
} catch (error) {
|
|
4137
4184
|
if (isUserAbort(error, req.signal)) {
|
|
4138
|
-
const
|
|
4139
|
-
if (
|
|
4185
|
+
const partial2 = stream.currentMessage;
|
|
4186
|
+
if (partial2) yield { type: "usage", usage: toUsage(partial2.usage) };
|
|
4140
4187
|
const limits3 = parseRateLimits(capture.headers());
|
|
4141
4188
|
if (limits3) yield { type: "rate_limits", limits: limits3 };
|
|
4142
4189
|
yield { type: "done", stopReason: "aborted", content: partialContent(thinking, text) };
|
|
4143
4190
|
return;
|
|
4144
4191
|
}
|
|
4192
|
+
const partial = stream.currentMessage;
|
|
4193
|
+
if (partial) yield { type: "usage", usage: toUsage(partial.usage) };
|
|
4145
4194
|
const limits2 = parseRateLimits(capture.headers());
|
|
4146
4195
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4147
4196
|
if (events === 0 && capture.succeeded() && !isConnectionFailure(error)) {
|
|
@@ -4175,7 +4224,7 @@ async function listModels(client, providerId, apiKey2) {
|
|
|
4175
4224
|
name: model.display_name,
|
|
4176
4225
|
contextWindow: model.max_input_tokens ?? void 0,
|
|
4177
4226
|
maxOutput: model.max_tokens ?? void 0,
|
|
4178
|
-
pricing: pricingFor(model.id)
|
|
4227
|
+
pricing: modelInfoFromRaw(model)?.pricing ?? pricingFor(model.id)
|
|
4179
4228
|
});
|
|
4180
4229
|
}
|
|
4181
4230
|
return models;
|
|
@@ -4335,6 +4384,7 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4335
4384
|
}
|
|
4336
4385
|
} catch (error) {
|
|
4337
4386
|
if (!isUserAbort(error, req.signal)) {
|
|
4387
|
+
if (sawUsage) yield { type: "usage", usage };
|
|
4338
4388
|
const limits2 = parseRateLimits(capture.headers());
|
|
4339
4389
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4340
4390
|
throw toProviderError(error, providerId, [apiKey2]);
|
|
@@ -4350,13 +4400,13 @@ async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
|
4350
4400
|
return;
|
|
4351
4401
|
}
|
|
4352
4402
|
if (recognised === 0) throw await invalidStreamError(providerId, capture, void 0, [apiKey2]);
|
|
4403
|
+
if (sawUsage) yield { type: "usage", usage };
|
|
4353
4404
|
const content = toContentBlocks2(providerId, thinking, text, calls);
|
|
4354
4405
|
for (const block of content) {
|
|
4355
4406
|
if (block.type === "tool_use") {
|
|
4356
4407
|
yield { type: "tool_call", id: block.id, name: block.name, input: block.input };
|
|
4357
4408
|
}
|
|
4358
4409
|
}
|
|
4359
|
-
if (sawUsage) yield { type: "usage", usage };
|
|
4360
4410
|
const limits = parseRateLimits(capture.headers());
|
|
4361
4411
|
if (limits) yield { type: "rate_limits", limits };
|
|
4362
4412
|
yield { type: "done", stopReason: resolveStop(finishReason, calls.size > 0), content };
|
|
@@ -5313,7 +5363,7 @@ function createToolRegistry(tools) {
|
|
|
5313
5363
|
}
|
|
5314
5364
|
|
|
5315
5365
|
// src/prompts/library.ts
|
|
5316
|
-
import { chmod as chmod6, readFile as readFile12, readdir as
|
|
5366
|
+
import { chmod as chmod6, readFile as readFile12, readdir as readdir4, unlink as unlink3, writeFile as writeFile6 } from "fs/promises";
|
|
5317
5367
|
import path10 from "path";
|
|
5318
5368
|
async function savePrompt(input) {
|
|
5319
5369
|
const slug = slugify(input.name);
|
|
@@ -5346,7 +5396,7 @@ async function getPrompt(slug) {
|
|
|
5346
5396
|
async function listPrompts() {
|
|
5347
5397
|
let names;
|
|
5348
5398
|
try {
|
|
5349
|
-
names = await
|
|
5399
|
+
names = await readdir4(promptsDir);
|
|
5350
5400
|
} catch (error) {
|
|
5351
5401
|
if (!isMissing(error)) throw error;
|
|
5352
5402
|
return [];
|
|
@@ -5418,7 +5468,7 @@ function parse(slug, text) {
|
|
|
5418
5468
|
}
|
|
5419
5469
|
|
|
5420
5470
|
// src/skills/library.ts
|
|
5421
|
-
import { lstat as lstat4, open as open2, readdir as
|
|
5471
|
+
import { lstat as lstat4, open as open2, readdir as readdir5 } from "fs/promises";
|
|
5422
5472
|
import path11 from "path";
|
|
5423
5473
|
var SKILL_FILE = "SKILL.md";
|
|
5424
5474
|
var FRONTMATTER_BYTES = 8192;
|
|
@@ -5430,7 +5480,7 @@ async function discoverSkills(dirs) {
|
|
|
5430
5480
|
for (const root of dirs) {
|
|
5431
5481
|
const rootInfo = await lstat4(root).catch(() => null);
|
|
5432
5482
|
if (!rootInfo?.isDirectory() || rootInfo.isSymbolicLink()) continue;
|
|
5433
|
-
const entries = await
|
|
5483
|
+
const entries = await readdir5(root, { withFileTypes: true }).catch(() => []);
|
|
5434
5484
|
const found = await Promise.all(
|
|
5435
5485
|
entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).slice(0, MAX_SKILLS_PER_ROOT).map((entry) => readMeta(root, rootInfo, path11.join(root, entry.name)))
|
|
5436
5486
|
);
|
|
@@ -6485,6 +6535,7 @@ async function boot(options) {
|
|
|
6485
6535
|
async setMaxTokensPerTurn(tokens3) {
|
|
6486
6536
|
await persistConfig((draft) => {
|
|
6487
6537
|
draft.budget.maxTokensPerTurn = tokens3;
|
|
6538
|
+
draft.budget.maxCostUsdPerTurn = 0;
|
|
6488
6539
|
});
|
|
6489
6540
|
},
|
|
6490
6541
|
getLang: () => config.lang,
|
|
@@ -7016,9 +7067,49 @@ function validateKey(value) {
|
|
|
7016
7067
|
// src/app/tui.tsx
|
|
7017
7068
|
import { render } from "ink";
|
|
7018
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
|
+
|
|
7019
7110
|
// src/ui/App.tsx
|
|
7020
|
-
import { Box as Box13, Text as Text12, useApp, useInput as useInput2
|
|
7021
|
-
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";
|
|
7022
7113
|
|
|
7023
7114
|
// src/mcp/add.ts
|
|
7024
7115
|
var SERVER_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
@@ -7645,7 +7736,7 @@ import { Box as Box3, Text as Text3 } from "ink";
|
|
|
7645
7736
|
import { useState } from "react";
|
|
7646
7737
|
|
|
7647
7738
|
// src/ui/components/Logo.tsx
|
|
7648
|
-
import { Box as Box2, Text as Text2
|
|
7739
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
7649
7740
|
|
|
7650
7741
|
// src/ui/workspace.ts
|
|
7651
7742
|
import { homedir } from "os";
|
|
@@ -7692,7 +7783,7 @@ var CAT = [" \u2571\\_\u2571\\ ", " ( o.o )", " > ^ < "];
|
|
|
7692
7783
|
function Logo({ subtitle, workspace }) {
|
|
7693
7784
|
const theme = useTheme();
|
|
7694
7785
|
const strings = useStrings();
|
|
7695
|
-
const { columns } =
|
|
7786
|
+
const { columns } = useTerminalSize();
|
|
7696
7787
|
const location = workspace ? formatWorkspacePath(workspace, Math.max(6, Math.min(72, columns - 15))) : "";
|
|
7697
7788
|
return /* @__PURE__ */ jsxs2(Box2, { width: "100%", marginBottom: 1, children: [
|
|
7698
7789
|
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", flexShrink: 0, children: CAT.map((line) => /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: line }, line)) }),
|
|
@@ -7950,15 +8041,16 @@ function previewText(value, limit = MAX_PREVIEW_CHARS) {
|
|
|
7950
8041
|
|
|
7951
8042
|
// src/ui/components/Picker.tsx
|
|
7952
8043
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
7953
|
-
import { useMemo, useState as useState3 } from "react";
|
|
8044
|
+
import { useMemo as useMemo2, useState as useState3 } from "react";
|
|
7954
8045
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
7955
8046
|
var WINDOW = 10;
|
|
7956
8047
|
function Picker({ title, items, onSelect, onCancel }) {
|
|
7957
8048
|
const theme = useTheme();
|
|
8049
|
+
const { columns } = useTerminalSize();
|
|
7958
8050
|
const strings = useStrings();
|
|
7959
8051
|
const [query, setQuery] = useState3("");
|
|
7960
8052
|
const [cursor, setCursor] = useState3(0);
|
|
7961
|
-
const matches =
|
|
8053
|
+
const matches = useMemo2(() => {
|
|
7962
8054
|
const needle = query.toLowerCase();
|
|
7963
8055
|
if (needle === "") return items;
|
|
7964
8056
|
return items.filter(
|
|
@@ -7985,7 +8077,7 @@ function Picker({ title, items, onSelect, onCancel }) {
|
|
|
7985
8077
|
});
|
|
7986
8078
|
const start = Math.max(0, Math.min(active2 - WINDOW + 2, matches.length - WINDOW));
|
|
7987
8079
|
const visible = matches.slice(start, start + WINDOW);
|
|
7988
|
-
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: [
|
|
7989
8081
|
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accent, children: [
|
|
7990
8082
|
sanitizeTerminalText(title),
|
|
7991
8083
|
query !== "" && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
@@ -7995,7 +8087,7 @@ function Picker({ title, items, onSelect, onCancel }) {
|
|
|
7995
8087
|
] }),
|
|
7996
8088
|
matches.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: strings.noMatches }) : visible.map((item, index) => {
|
|
7997
8089
|
const selected = start + index === active2;
|
|
7998
|
-
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: [
|
|
7999
8091
|
truncate2(sanitizeTerminalText(item.label), 70),
|
|
8000
8092
|
item.hint && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
8001
8093
|
" ",
|
|
@@ -8051,6 +8143,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8051
8143
|
attachments = []
|
|
8052
8144
|
}) {
|
|
8053
8145
|
const theme = useTheme();
|
|
8146
|
+
const { columns } = useTerminalSize();
|
|
8054
8147
|
const strings = useStrings();
|
|
8055
8148
|
const safeValue = sanitizeTerminalText(value);
|
|
8056
8149
|
const [selectionCursor, setSelectionCursor] = useState4(0);
|
|
@@ -8186,7 +8279,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8186
8279
|
});
|
|
8187
8280
|
const start = Math.max(0, Math.min(active2 - WINDOW2 + 2, suggestions.length - WINDOW2));
|
|
8188
8281
|
const visible = suggestions.slice(start, start + WINDOW2);
|
|
8189
|
-
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: [
|
|
8190
8283
|
/* @__PURE__ */ jsxs8(
|
|
8191
8284
|
Box8,
|
|
8192
8285
|
{
|
|
@@ -8197,7 +8290,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
8197
8290
|
paddingX: 1,
|
|
8198
8291
|
children: [
|
|
8199
8292
|
/* @__PURE__ */ jsx8(Text8, { color: disabled ? "gray" : theme.accent, children: "\u203A " }),
|
|
8200
|
-
/* @__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 }) }),
|
|
8201
8294
|
pending && pending > 0 ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
8202
8295
|
" \xB7 ",
|
|
8203
8296
|
strings.queued(pending)
|
|
@@ -8490,9 +8583,12 @@ function liveTranscriptRows(rows) {
|
|
|
8490
8583
|
return Math.max(1, interactiveViewportRows(rows) - INTERACTIVE_CHROME_ROWS);
|
|
8491
8584
|
}
|
|
8492
8585
|
function TerminalViewport({ children, rows }) {
|
|
8586
|
+
const { columns } = useTerminalSize();
|
|
8493
8587
|
return /* @__PURE__ */ jsx10(
|
|
8494
8588
|
Box10,
|
|
8495
8589
|
{
|
|
8590
|
+
width: columns,
|
|
8591
|
+
maxWidth: "100%",
|
|
8496
8592
|
flexDirection: "column",
|
|
8497
8593
|
maxHeight: interactiveViewportRows(rows),
|
|
8498
8594
|
overflowY: "hidden",
|
|
@@ -8502,18 +8598,18 @@ function TerminalViewport({ children, rows }) {
|
|
|
8502
8598
|
}
|
|
8503
8599
|
|
|
8504
8600
|
// src/ui/components/Transcript.tsx
|
|
8505
|
-
import { Box as Box12, Static, Text as Text11
|
|
8506
|
-
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";
|
|
8507
8603
|
import Spinner2 from "ink-spinner";
|
|
8508
8604
|
import stringWidth2 from "string-width";
|
|
8509
8605
|
|
|
8510
8606
|
// src/ui/markdown.tsx
|
|
8511
|
-
import { Box as Box11, Text as Text10
|
|
8512
|
-
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";
|
|
8513
8609
|
import stringWidth from "string-width";
|
|
8514
8610
|
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
8515
8611
|
function Markdown({ children }) {
|
|
8516
|
-
const blocks =
|
|
8612
|
+
const blocks = useMemo3(() => extractBlocks(children), [children]);
|
|
8517
8613
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx11(BlockView, { block }, i)) });
|
|
8518
8614
|
}
|
|
8519
8615
|
function BlockView({ block }) {
|
|
@@ -8547,7 +8643,7 @@ function BlockView({ block }) {
|
|
|
8547
8643
|
case "code":
|
|
8548
8644
|
return /* @__PURE__ */ jsx11(CodeBlock, { code: block.text ?? "" });
|
|
8549
8645
|
case "hr":
|
|
8550
|
-
return /* @__PURE__ */ jsx11(Box11, {
|
|
8646
|
+
return /* @__PURE__ */ jsx11(Box11, { height: 1 });
|
|
8551
8647
|
}
|
|
8552
8648
|
}
|
|
8553
8649
|
function CodeBlock({ code }) {
|
|
@@ -8555,7 +8651,7 @@ function CodeBlock({ code }) {
|
|
|
8555
8651
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { color: "cyan", children: line }, i)) });
|
|
8556
8652
|
}
|
|
8557
8653
|
function TableView({ block }) {
|
|
8558
|
-
const { columns } =
|
|
8654
|
+
const { columns } = useTerminalSize();
|
|
8559
8655
|
const headers = block.headers ?? [];
|
|
8560
8656
|
const rows = block.rows ?? [];
|
|
8561
8657
|
if (headers.length === 0 && rows.length === 0) {
|
|
@@ -9128,26 +9224,41 @@ var BubbleView = memo2(function BubbleView2({
|
|
|
9128
9224
|
return /* @__PURE__ */ jsx12(AssistantView, { bubble, maxRows });
|
|
9129
9225
|
}
|
|
9130
9226
|
if (bubble.kind === "subagent") {
|
|
9131
|
-
return /* @__PURE__ */ jsx12(SubagentView, { bubble });
|
|
9227
|
+
return /* @__PURE__ */ jsx12(SubagentView, { bubble, maxRows });
|
|
9132
9228
|
}
|
|
9133
9229
|
return /* @__PURE__ */ jsx12(ToolView, { bubble });
|
|
9134
9230
|
});
|
|
9135
|
-
function
|
|
9136
|
-
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();
|
|
9137
9245
|
const frozenThinking = useRef3(void 0);
|
|
9138
9246
|
const answering = bubble.streaming && bubble.text !== "";
|
|
9139
9247
|
if (!answering) frozenThinking.current = void 0;
|
|
9140
9248
|
if (answering && frozenThinking.current === void 0) {
|
|
9141
9249
|
frozenThinking.current = bubble.thinking;
|
|
9142
9250
|
}
|
|
9143
|
-
const visibleThinking =
|
|
9251
|
+
const visibleThinking = sanitizeThinkingText(
|
|
9252
|
+
assistantThinkingForFrame(bubble, frozenThinking.current),
|
|
9253
|
+
bubble.streaming
|
|
9254
|
+
);
|
|
9144
9255
|
const liveBudget = bubble.streaming && maxRows !== void 0 ? Math.max(1, maxRows - 1) : void 0;
|
|
9145
9256
|
const thinkingBudget = liveBudget === void 0 ? void 0 : bubble.text !== "" ? Math.min(3, Math.max(0, liveBudget - 1)) : Math.max(0, liveBudget - 1);
|
|
9146
9257
|
const frameThinking = thinkingBudget === void 0 ? visibleThinking.trim() : clipTextToRows(visibleThinking.trim(), thinkingBudget, columns);
|
|
9147
9258
|
const usedThinkingRows = frameThinking === "" ? 0 : frameThinking.split("\n").length;
|
|
9148
9259
|
const frameText = liveBudget === void 0 ? bubble.text : clipTextToRows(bubble.text, Math.max(1, liveBudget - usedThinkingRows), columns);
|
|
9149
9260
|
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
9150
|
-
frameThinking !== "" && /* @__PURE__ */ jsx12(
|
|
9261
|
+
frameThinking !== "" && /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", maxHeight: thinkingBudget, overflowY: "hidden", children: /* @__PURE__ */ jsx12(Markdown, { children: frameThinking }) }),
|
|
9151
9262
|
bubble.streaming ? /* @__PURE__ */ jsx12(Text11, { children: frameText }) : /* @__PURE__ */ jsx12(Markdown, { children: bubble.text }),
|
|
9152
9263
|
bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
9153
9264
|
/* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
|
|
@@ -9186,7 +9297,7 @@ function ToolView({ bubble }) {
|
|
|
9186
9297
|
const mark = bubble.state === "running" ? "\u25CC" : bubble.state === "ok" ? "\u25CF" : "\u2717";
|
|
9187
9298
|
const color = bubble.state === "error" ? theme.error : bubble.state === "ok" ? theme.ok : theme.warn;
|
|
9188
9299
|
const display = bubble.display;
|
|
9189
|
-
const diff =
|
|
9300
|
+
const diff = useMemo4(
|
|
9190
9301
|
() => display?.kind === "diff" ? diffLines(display.before, display.after) : null,
|
|
9191
9302
|
[display]
|
|
9192
9303
|
);
|
|
@@ -9224,20 +9335,20 @@ function previewLines(content) {
|
|
|
9224
9335
|
if (lines.length <= 6) return lines;
|
|
9225
9336
|
return [...lines.slice(0, 6), `\u2026 ${lines.length - 6} more lines`];
|
|
9226
9337
|
}
|
|
9227
|
-
function SubagentView({ bubble }) {
|
|
9338
|
+
function SubagentView({ bubble, maxRows }) {
|
|
9228
9339
|
const theme = useTheme();
|
|
9340
|
+
const { columns } = useTerminalSize();
|
|
9229
9341
|
const mark = bubble.state === "running" ? "\u25CC" : "\u25CF";
|
|
9230
9342
|
const color = bubble.state === "running" ? theme.warn : theme.ok;
|
|
9231
|
-
|
|
9232
|
-
|
|
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: [
|
|
9233
9347
|
mark,
|
|
9234
9348
|
" subagent: ",
|
|
9235
9349
|
bubble.description
|
|
9236
9350
|
] }),
|
|
9237
|
-
/* @__PURE__ */
|
|
9238
|
-
bubble.bubbles.map((inner, index) => /* @__PURE__ */ jsx12(BubbleView, { bubble: inner }, index)),
|
|
9239
|
-
bubble.state === "done" && bubble.result && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "\u2500\u2500 result \u2500\u2500" }) })
|
|
9240
|
-
] })
|
|
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 })
|
|
9241
9352
|
] });
|
|
9242
9353
|
}
|
|
9243
9354
|
|
|
@@ -9488,7 +9599,7 @@ function App({
|
|
|
9488
9599
|
warnings = []
|
|
9489
9600
|
}) {
|
|
9490
9601
|
const { exit } = useApp();
|
|
9491
|
-
const { rows } =
|
|
9602
|
+
const { rows } = useTerminalSize();
|
|
9492
9603
|
const [transcript, setTranscript] = useState5(
|
|
9493
9604
|
() => warnings.reduce((state, text) => pushNotice(state, "warn", text), fromHistory(initialHistory))
|
|
9494
9605
|
);
|
|
@@ -9506,6 +9617,7 @@ function App({
|
|
|
9506
9617
|
const drainQueueRef = useRef4(() => void 0);
|
|
9507
9618
|
const [attachments, setAttachments] = useState5([]);
|
|
9508
9619
|
const attachmentsRef = useRef4([]);
|
|
9620
|
+
const attachmentGeneration = useRef4(0);
|
|
9509
9621
|
const automaticAttachmentTask = useRef4(null);
|
|
9510
9622
|
const clipboardPasteTask = useRef4(null);
|
|
9511
9623
|
const [overlay, setOverlay] = useState5({ kind: "none" });
|
|
@@ -9570,8 +9682,8 @@ function App({
|
|
|
9570
9682
|
const timer = setInterval(() => tick((n) => n + 1), 1e3);
|
|
9571
9683
|
return () => clearInterval(timer);
|
|
9572
9684
|
}, [turnStart]);
|
|
9573
|
-
const theme =
|
|
9574
|
-
const strings =
|
|
9685
|
+
const theme = useMemo5(() => makeTheme(accent), [accent]);
|
|
9686
|
+
const strings = useMemo5(() => stringsFor(lang), [lang]);
|
|
9575
9687
|
const notice = useCallback(
|
|
9576
9688
|
(level, text) => {
|
|
9577
9689
|
flushTranscriptEvents();
|
|
@@ -10192,6 +10304,9 @@ ${strings.mcpAddUsage}`
|
|
|
10192
10304
|
}
|
|
10193
10305
|
case "attach": {
|
|
10194
10306
|
if (rawRest.toLowerCase() === "clear") {
|
|
10307
|
+
attachmentGeneration.current += 1;
|
|
10308
|
+
automaticAttachmentTask.current = null;
|
|
10309
|
+
clipboardPasteTask.current = null;
|
|
10195
10310
|
replaceAttachments([]);
|
|
10196
10311
|
notice("info", strings.attachmentsCleared);
|
|
10197
10312
|
return;
|
|
@@ -10203,15 +10318,18 @@ ${strings.mcpAddUsage}`
|
|
|
10203
10318
|
}
|
|
10204
10319
|
if (clipboardPasteTask.current) return;
|
|
10205
10320
|
const task = runtime.loadClipboardImage().then((block) => {
|
|
10321
|
+
if (clipboardPasteTask.current !== task) return;
|
|
10206
10322
|
if (appendAttachment(block)) {
|
|
10207
10323
|
notice(
|
|
10208
10324
|
"info",
|
|
10209
10325
|
strings.attachmentAdded(attachmentLabel(block) ?? "clipboard image")
|
|
10210
10326
|
);
|
|
10211
10327
|
}
|
|
10212
|
-
}).catch(
|
|
10213
|
-
|
|
10214
|
-
|
|
10328
|
+
}).catch((error) => {
|
|
10329
|
+
if (clipboardPasteTask.current === task) {
|
|
10330
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10331
|
+
}
|
|
10332
|
+
});
|
|
10215
10333
|
clipboardPasteTask.current = task;
|
|
10216
10334
|
try {
|
|
10217
10335
|
await task;
|
|
@@ -10520,6 +10638,7 @@ Rename the file if you want a different name.`);
|
|
|
10520
10638
|
return Promise.resolve(true);
|
|
10521
10639
|
}
|
|
10522
10640
|
const task = runtime.loadAutomaticAttachment(requestedPath).then((block) => {
|
|
10641
|
+
if (automaticAttachmentTask.current !== task) return true;
|
|
10523
10642
|
if (!block) return false;
|
|
10524
10643
|
if (!appendAttachment(block)) {
|
|
10525
10644
|
notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
|
|
@@ -10527,9 +10646,9 @@ Rename the file if you want a different name.`);
|
|
|
10527
10646
|
}
|
|
10528
10647
|
notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "attachment"));
|
|
10529
10648
|
return true;
|
|
10530
|
-
}).catch((
|
|
10531
|
-
|
|
10532
|
-
return
|
|
10649
|
+
}).catch(() => {
|
|
10650
|
+
if (automaticAttachmentTask.current !== task) return true;
|
|
10651
|
+
return false;
|
|
10533
10652
|
});
|
|
10534
10653
|
automaticAttachmentTask.current = task;
|
|
10535
10654
|
void task.finally(() => {
|
|
@@ -10546,12 +10665,17 @@ Rename the file if you want a different name.`);
|
|
|
10546
10665
|
return;
|
|
10547
10666
|
}
|
|
10548
10667
|
const task = runtime.loadClipboardImage().then((block) => {
|
|
10668
|
+
if (clipboardPasteTask.current !== task) return;
|
|
10549
10669
|
if (!appendAttachment(block)) {
|
|
10550
10670
|
notice("warn", `At most ${MAX_ATTACHMENTS} attachments can be queued for one message.`);
|
|
10551
10671
|
return;
|
|
10552
10672
|
}
|
|
10553
10673
|
notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "clipboard image"));
|
|
10554
|
-
}).catch((error) =>
|
|
10674
|
+
}).catch((error) => {
|
|
10675
|
+
if (clipboardPasteTask.current === task) {
|
|
10676
|
+
notice("error", error instanceof Error ? error.message : String(error));
|
|
10677
|
+
}
|
|
10678
|
+
});
|
|
10555
10679
|
clipboardPasteTask.current = task;
|
|
10556
10680
|
void task.finally(() => {
|
|
10557
10681
|
if (clipboardPasteTask.current === task) clipboardPasteTask.current = null;
|
|
@@ -10559,6 +10683,7 @@ Rename the file if you want a different name.`);
|
|
|
10559
10683
|
}, [appendAttachment, notice, runtime, strings]);
|
|
10560
10684
|
const submit = useCallback(
|
|
10561
10685
|
async (raw) => {
|
|
10686
|
+
const generation = attachmentGeneration.current;
|
|
10562
10687
|
const pendingAttachments = [automaticAttachmentTask.current, clipboardPasteTask.current].filter(
|
|
10563
10688
|
(task) => task !== null
|
|
10564
10689
|
);
|
|
@@ -10566,6 +10691,7 @@ Rename the file if you want a different name.`);
|
|
|
10566
10691
|
if (detachedInput) {
|
|
10567
10692
|
setInput("");
|
|
10568
10693
|
await Promise.all(pendingAttachments);
|
|
10694
|
+
if (generation !== attachmentGeneration.current) return;
|
|
10569
10695
|
}
|
|
10570
10696
|
const text = raw.trim();
|
|
10571
10697
|
const submitSlash = () => {
|
|
@@ -10581,9 +10707,13 @@ Rename the file if you want a different name.`);
|
|
|
10581
10707
|
submitSlash();
|
|
10582
10708
|
return;
|
|
10583
10709
|
}
|
|
10584
|
-
if (text && looksLikeAttachmentPath(text)
|
|
10585
|
-
|
|
10586
|
-
return;
|
|
10710
|
+
if (text && looksLikeAttachmentPath(text)) {
|
|
10711
|
+
const attached = await tryQueueAutomaticAttachment(text);
|
|
10712
|
+
if (generation !== attachmentGeneration.current) return;
|
|
10713
|
+
if (attached) {
|
|
10714
|
+
if (!detachedInput) setInput("");
|
|
10715
|
+
return;
|
|
10716
|
+
}
|
|
10587
10717
|
}
|
|
10588
10718
|
if (text.startsWith("/")) {
|
|
10589
10719
|
submitSlash();
|
|
@@ -10618,15 +10748,19 @@ Rename the file if you want a different name.`);
|
|
|
10618
10748
|
return;
|
|
10619
10749
|
}
|
|
10620
10750
|
if (!key.escape) return;
|
|
10621
|
-
if (
|
|
10622
|
-
|
|
10751
|
+
if (busyRef.current && abort.current) {
|
|
10752
|
+
if (!abort.current.signal.aborted) {
|
|
10753
|
+
abort.current.abort();
|
|
10754
|
+
notice("warn", strings.cancelled);
|
|
10755
|
+
}
|
|
10623
10756
|
return;
|
|
10624
10757
|
}
|
|
10625
|
-
if (
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10758
|
+
if (input !== "" || attachmentsRef.current.length > 0 || automaticAttachmentTask.current || clipboardPasteTask.current) {
|
|
10759
|
+
attachmentGeneration.current += 1;
|
|
10760
|
+
setInput("");
|
|
10761
|
+
replaceAttachments([]);
|
|
10762
|
+
if (automaticAttachmentTask.current) automaticAttachmentTask.current = null;
|
|
10763
|
+
if (clipboardPasteTask.current) clipboardPasteTask.current = null;
|
|
10630
10764
|
return;
|
|
10631
10765
|
}
|
|
10632
10766
|
if (overlay.kind === "permission") {
|