@wrongstack/tui 0.275.1 → 0.276.2
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.d.ts +2 -0
- package/dist/index.js +811 -646
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2,18 +2,18 @@ import { writeErr, resolveProjectDir, wstackGlobalRoot, createHqPublisherFromEnv
|
|
|
2
2
|
export { buildGoalPreamble } from '@wrongstack/core';
|
|
3
3
|
import { Box as Box$1, useStdout, Static, measureElement, useInput, useStdin, Text as Text$1, render, useApp } from 'ink';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
|
-
import * as
|
|
5
|
+
import * as path6 from 'path';
|
|
6
6
|
import React5, { forwardRef, useState, useEffect, useMemo, memo, useRef, useLayoutEffect, useCallback, useReducer, isValidElement, Fragment as Fragment$1 } from 'react';
|
|
7
|
-
import * as
|
|
7
|
+
import * as fsp from 'fs/promises';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
|
-
import { expectDefined, toErrorMessage, resolveWstackPaths as resolveWstackPaths$1 } from '@wrongstack/core/utils';
|
|
9
|
+
import { expectDefined, toErrorMessage, resolveWstackPaths as resolveWstackPaths$1, sessionScopedPath } from '@wrongstack/core/utils';
|
|
10
10
|
import { routeImagesForModel } from '@wrongstack/runtime/vision';
|
|
11
11
|
import { getIndexState, onIndexStateChange, getProcessRegistry } from '@wrongstack/tools';
|
|
12
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
|
-
import { readClipboardImage, readClipboardText } from '@wrongstack/runtime/clipboard';
|
|
14
13
|
import { getToolIcon, TOOL_ICON_CONFIG } from '@wrongstack/tools/tool-icons';
|
|
15
14
|
import * as v8 from 'v8';
|
|
16
15
|
import { spawn } from 'child_process';
|
|
16
|
+
import { readClipboardText, readClipboardImage } from '@wrongstack/runtime/clipboard';
|
|
17
17
|
|
|
18
18
|
// src/run-tui.ts
|
|
19
19
|
|
|
@@ -97,7 +97,7 @@ function snapshotTokenCounter(tokenCounter) {
|
|
|
97
97
|
cacheStats: tokenCounter.cacheStats()
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
function useTokenCounterRefresh(tokenCounter, events) {
|
|
100
|
+
function useTokenCounterRefresh(tokenCounter, events, sessionId) {
|
|
101
101
|
const [data, setData] = useState(
|
|
102
102
|
() => tokenCounter ? snapshotTokenCounter(tokenCounter) : void 0
|
|
103
103
|
);
|
|
@@ -109,11 +109,12 @@ function useTokenCounterRefresh(tokenCounter, events) {
|
|
|
109
109
|
const snapshot = () => snapshotTokenCounter(tokenCounter);
|
|
110
110
|
setData(snapshot());
|
|
111
111
|
if (!events) return;
|
|
112
|
-
const off = events.on("token.accounted", () => {
|
|
112
|
+
const off = events.on("token.accounted", (event) => {
|
|
113
|
+
if (sessionId && event.sessionId && event.sessionId !== sessionId) return;
|
|
113
114
|
setData(snapshot());
|
|
114
115
|
});
|
|
115
116
|
return off;
|
|
116
|
-
}, [tokenCounter, events]);
|
|
117
|
+
}, [tokenCounter, events, sessionId]);
|
|
117
118
|
return data;
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -315,6 +316,7 @@ function StatusBar({
|
|
|
315
316
|
hiddenItems,
|
|
316
317
|
mode = "detailed",
|
|
317
318
|
events,
|
|
319
|
+
sessionId,
|
|
318
320
|
eternalStage,
|
|
319
321
|
goalSummary,
|
|
320
322
|
indexState,
|
|
@@ -346,7 +348,7 @@ function StatusBar({
|
|
|
346
348
|
const isComfortable = termWidth >= COMFORTABLE_THRESHOLD;
|
|
347
349
|
const hiddenSet = useMemo(() => new Set(hiddenItems), [hiddenItems]);
|
|
348
350
|
const showChip = (item) => !hiddenSet.has(item);
|
|
349
|
-
const tokenData = useTokenCounterRefresh(tokenCounter, events);
|
|
351
|
+
const tokenData = useTokenCounterRefresh(tokenCounter, events, sessionId);
|
|
350
352
|
const usage = tokenData?.usage;
|
|
351
353
|
const displayTokens = tokenDisplayTotals(usage, tokenData?.currentRequest);
|
|
352
354
|
const showTokenDisplay = hasTokenDisplay(displayTokens);
|
|
@@ -1096,6 +1098,10 @@ function fmtDebugBytes(n) {
|
|
|
1096
1098
|
function pad2(n) {
|
|
1097
1099
|
return n < 10 ? `0${n}` : String(n);
|
|
1098
1100
|
}
|
|
1101
|
+
function shortSessionId(sessionId) {
|
|
1102
|
+
const leaf = sessionId.split("/").pop() ?? sessionId;
|
|
1103
|
+
return leaf.length > 12 ? `${leaf.slice(0, 12)}\u2026` : leaf;
|
|
1104
|
+
}
|
|
1099
1105
|
var STATUS = {
|
|
1100
1106
|
idle: { icon: "\u25CB", color: "gray" },
|
|
1101
1107
|
running: { icon: "\u25B6", color: "yellow" },
|
|
@@ -1223,7 +1229,7 @@ function FleetMonitor({
|
|
|
1223
1229
|
collabSession ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
1224
1230
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1225
1231
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "\u26A1 COLLAB SESSION" }),
|
|
1226
|
-
collabSession.sessionId ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: collabSession.sessionId
|
|
1232
|
+
collabSession.sessionId ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: shortSessionId(collabSession.sessionId) }) : null,
|
|
1227
1233
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1228
1234
|
/* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
1229
1235
|
"\u{1F41B}",
|
|
@@ -2334,8 +2340,8 @@ function FilePicker({ query, matches, selected }) {
|
|
|
2334
2340
|
] }, m))
|
|
2335
2341
|
] });
|
|
2336
2342
|
}
|
|
2337
|
-
function highlight(
|
|
2338
|
-
return
|
|
2343
|
+
function highlight(path8, _query) {
|
|
2344
|
+
return path8;
|
|
2339
2345
|
}
|
|
2340
2346
|
function FleetPanel({
|
|
2341
2347
|
entries,
|
|
@@ -4554,19 +4560,19 @@ function visualEdit(toolName, text, ok) {
|
|
|
4554
4560
|
const parsed = tryParseJson(text);
|
|
4555
4561
|
if (parsed && typeof parsed === "object") {
|
|
4556
4562
|
const obj = parsed;
|
|
4557
|
-
const
|
|
4563
|
+
const path8 = typeof obj["path"] === "string" ? obj["path"] : void 0;
|
|
4558
4564
|
const replacements = typeof obj["replacements"] === "number" ? obj["replacements"] : void 0;
|
|
4559
4565
|
const bytes = typeof obj["bytes"] === "number" ? obj["bytes"] : void 0;
|
|
4560
4566
|
const created = obj["created"] === true;
|
|
4561
4567
|
const files = Array.isArray(obj["files"]) ? obj["files"] : void 0;
|
|
4562
4568
|
const results = Array.isArray(obj["results"]) ? obj["results"] : void 0;
|
|
4563
|
-
if (toolName === "edit" &&
|
|
4569
|
+
if (toolName === "edit" && path8 !== void 0) {
|
|
4564
4570
|
const repText = replacements !== void 0 ? `${replacements} replacement${replacements === 1 ? "" : "s"}` : void 0;
|
|
4565
|
-
rows.push({ kind: "ok", text: "", marker: "edit ", path:
|
|
4571
|
+
rows.push({ kind: "ok", text: "", marker: "edit ", path: path8 });
|
|
4566
4572
|
if (repText) rows.push({ kind: "meta", text: repText });
|
|
4567
|
-
} else if (toolName === "write" &&
|
|
4573
|
+
} else if (toolName === "write" && path8 !== void 0) {
|
|
4568
4574
|
const sizeText = bytes !== void 0 ? `${bytes} bytes` : created ? "new file" : "updated";
|
|
4569
|
-
rows.push({ kind: "ok", text: "", marker: "write ", path:
|
|
4575
|
+
rows.push({ kind: "ok", text: "", marker: "write ", path: path8 });
|
|
4570
4576
|
rows.push({ kind: "meta", text: sizeText });
|
|
4571
4577
|
} else if ((toolName === "diff" || toolName === "patch") && files && files.length > 0) {
|
|
4572
4578
|
rows.push({
|
|
@@ -4589,8 +4595,8 @@ function visualEdit(toolName, text, ok) {
|
|
|
4589
4595
|
marker: "replace ",
|
|
4590
4596
|
text: `${results.length} replacement${results.length === 1 ? "" : "s"} across ${fileCount} file${fileCount === 1 ? "" : "s"}`
|
|
4591
4597
|
});
|
|
4592
|
-
} else if (
|
|
4593
|
-
rows.push({ kind: "ok", text: "", marker: `${toolName} `, path:
|
|
4598
|
+
} else if (path8 !== void 0) {
|
|
4599
|
+
rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path8 });
|
|
4594
4600
|
} else {
|
|
4595
4601
|
return void 0;
|
|
4596
4602
|
}
|
|
@@ -4674,12 +4680,12 @@ function parseMatchHit(hit) {
|
|
|
4674
4680
|
}
|
|
4675
4681
|
if (hit && typeof hit === "object") {
|
|
4676
4682
|
const o = hit;
|
|
4677
|
-
const
|
|
4683
|
+
const path8 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
|
|
4678
4684
|
const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
|
|
4679
4685
|
const title = stringOf(o["title"]);
|
|
4680
4686
|
const snippet2 = stringOf(o["snippet"]);
|
|
4681
4687
|
const text = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]) ?? [title, snippet2].filter(Boolean).join(" \u2014 ");
|
|
4682
|
-
return { path:
|
|
4688
|
+
return { path: path8, line: line === void 0 ? void 0 : String(line), text };
|
|
4683
4689
|
}
|
|
4684
4690
|
return void 0;
|
|
4685
4691
|
}
|
|
@@ -5381,17 +5387,19 @@ function formatMultiDiffSummary(summary, threshold = MULTI_DIFF_SUMMARY_THRESHOL
|
|
|
5381
5387
|
return parts.join(" \xB7 ");
|
|
5382
5388
|
}
|
|
5383
5389
|
function DiffFileBlock({
|
|
5384
|
-
path:
|
|
5390
|
+
path: path8,
|
|
5385
5391
|
preview,
|
|
5386
5392
|
useColor = true
|
|
5387
5393
|
}) {
|
|
5388
5394
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
5389
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children:
|
|
5395
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: path8 }),
|
|
5390
5396
|
/* @__PURE__ */ jsx(
|
|
5391
5397
|
DiffBlock,
|
|
5392
5398
|
{
|
|
5393
5399
|
rows: preview.rows,
|
|
5394
5400
|
hidden: preview.hidden,
|
|
5401
|
+
added: preview.added,
|
|
5402
|
+
removed: preview.removed,
|
|
5395
5403
|
hiddenAdded: preview.hiddenAdded,
|
|
5396
5404
|
hiddenRemoved: preview.hiddenRemoved,
|
|
5397
5405
|
useColor
|
|
@@ -5402,6 +5410,8 @@ function DiffFileBlock({
|
|
|
5402
5410
|
function DiffBlock({
|
|
5403
5411
|
rows,
|
|
5404
5412
|
hidden,
|
|
5413
|
+
added = 0,
|
|
5414
|
+
removed = 0,
|
|
5405
5415
|
hiddenAdded = 0,
|
|
5406
5416
|
hiddenRemoved = 0,
|
|
5407
5417
|
useColor = true
|
|
@@ -5417,10 +5427,6 @@ function DiffBlock({
|
|
|
5417
5427
|
}
|
|
5418
5428
|
const blank = " ".repeat(gutterWidth);
|
|
5419
5429
|
const gutterPad = `${blank} ${blank}`;
|
|
5420
|
-
const footerStats = [
|
|
5421
|
-
hiddenAdded > 0 ? `+${hiddenAdded}` : "",
|
|
5422
|
-
hiddenRemoved > 0 ? `-${hiddenRemoved}` : ""
|
|
5423
|
-
].filter(Boolean);
|
|
5424
5430
|
const markerFor = (kind) => {
|
|
5425
5431
|
if (kind === "add") return "+";
|
|
5426
5432
|
if (kind === "del") return "-";
|
|
@@ -5467,7 +5473,30 @@ function DiffBlock({
|
|
|
5467
5473
|
/* @__PURE__ */ jsx(Text, { children: text })
|
|
5468
5474
|
] }, key);
|
|
5469
5475
|
}),
|
|
5470
|
-
|
|
5476
|
+
summaryFooter({
|
|
5477
|
+
added,
|
|
5478
|
+
removed,
|
|
5479
|
+
hidden,
|
|
5480
|
+
hiddenAdded,
|
|
5481
|
+
hiddenRemoved,
|
|
5482
|
+
gutterPad
|
|
5483
|
+
})
|
|
5484
|
+
] });
|
|
5485
|
+
}
|
|
5486
|
+
function summaryFooter(opts) {
|
|
5487
|
+
const { added, removed, hidden, hiddenAdded, hiddenRemoved, gutterPad } = opts;
|
|
5488
|
+
if (added <= 0 && removed <= 0 && hidden <= 0) return null;
|
|
5489
|
+
const hiddenStats = [];
|
|
5490
|
+
if (hiddenAdded > 0) hiddenStats.push(`+${hiddenAdded}`);
|
|
5491
|
+
if (hiddenRemoved > 0) hiddenStats.push(`-${hiddenRemoved}`);
|
|
5492
|
+
const truncNote = hidden > 0 ? ` \xB7 \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}${hiddenStats.length > 0 ? ` (${hiddenStats.join(" ")} hidden)` : ""}` : "";
|
|
5493
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
5494
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${gutterPad} ` }),
|
|
5495
|
+
/* @__PURE__ */ jsx(Text, { color: theme.success, bold: true, children: `+${added}` }),
|
|
5496
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` added ` }),
|
|
5497
|
+
/* @__PURE__ */ jsx(Text, { color: theme.error, bold: true, children: `-${removed}` }),
|
|
5498
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` deleted` }),
|
|
5499
|
+
truncNote ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: truncNote }) : null
|
|
5471
5500
|
] });
|
|
5472
5501
|
}
|
|
5473
5502
|
function parseUnifiedDiff(diff, maxLines) {
|
|
@@ -5566,8 +5595,8 @@ function splitReplaceDiffs(obj) {
|
|
|
5566
5595
|
const record = result;
|
|
5567
5596
|
const diff = stringOf(record["diff"]);
|
|
5568
5597
|
if (!diff?.trim()) continue;
|
|
5569
|
-
const
|
|
5570
|
-
items.push(
|
|
5598
|
+
const path8 = stringOf(record["path"]);
|
|
5599
|
+
items.push(path8 ? { path: path8, diff } : { diff });
|
|
5571
5600
|
}
|
|
5572
5601
|
return items;
|
|
5573
5602
|
}
|
|
@@ -5631,8 +5660,8 @@ function collectMultiFileDiffItems(toolName, output, _input) {
|
|
|
5631
5660
|
if (!diffText?.trim()) return void 0;
|
|
5632
5661
|
const blocks = splitGitStyleDiff(diffText);
|
|
5633
5662
|
if (blocks.length === 0) {
|
|
5634
|
-
const
|
|
5635
|
-
return [{ path:
|
|
5663
|
+
const path8 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
|
|
5664
|
+
return [{ path: path8, diff: diffText }];
|
|
5636
5665
|
}
|
|
5637
5666
|
return blocks.map((block, idx) => ({
|
|
5638
5667
|
path: explicitFiles[idx] ?? block.path,
|
|
@@ -5678,8 +5707,8 @@ function splitGitStyleDiff(diff) {
|
|
|
5678
5707
|
}
|
|
5679
5708
|
flush();
|
|
5680
5709
|
if (blocks.length === 1 && !blocks[0].path) {
|
|
5681
|
-
const
|
|
5682
|
-
if (
|
|
5710
|
+
const path8 = extractPatchHeaderPath(blocks[0].diff);
|
|
5711
|
+
if (path8) blocks[0] = { path: path8, diff: blocks[0].diff };
|
|
5683
5712
|
}
|
|
5684
5713
|
return blocks;
|
|
5685
5714
|
}
|
|
@@ -5700,8 +5729,8 @@ function parseDiffGitPath(line) {
|
|
|
5700
5729
|
function extractPatchHeaderPath(diffText) {
|
|
5701
5730
|
for (const line of diffText.split("\n")) {
|
|
5702
5731
|
if (line.startsWith("+++ ")) {
|
|
5703
|
-
const
|
|
5704
|
-
const cleaned =
|
|
5732
|
+
const path8 = line.slice(4).trim();
|
|
5733
|
+
const cleaned = path8.replace(/^b\//, "").split(" ")[0].trim();
|
|
5705
5734
|
return cleaned || void 0;
|
|
5706
5735
|
}
|
|
5707
5736
|
}
|
|
@@ -5712,9 +5741,9 @@ function newFileDiffFromWriteInput(output, input) {
|
|
|
5712
5741
|
const obj = input;
|
|
5713
5742
|
const content = stringOf(obj["content"]);
|
|
5714
5743
|
if (content === void 0) return void 0;
|
|
5715
|
-
const
|
|
5744
|
+
const path8 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
|
|
5716
5745
|
const lines = content === "" ? [] : content.replace(/\n$/, "").split("\n");
|
|
5717
|
-
const header = [`+++ ${
|
|
5746
|
+
const header = [`+++ ${path8}`, `@@ -0,0 +1,${lines.length} @@`];
|
|
5718
5747
|
return [...header, ...lines.map((line) => `+${line}`)].join("\n");
|
|
5719
5748
|
}
|
|
5720
5749
|
var MESSAGE_PANEL_CHROME_WIDTH = 2;
|
|
@@ -5814,7 +5843,7 @@ function Banner({
|
|
|
5814
5843
|
entry
|
|
5815
5844
|
}) {
|
|
5816
5845
|
const cwdShort = shortenPath(entry.cwd, 48);
|
|
5817
|
-
const projectLabel =
|
|
5846
|
+
const projectLabel = path6.basename(entry.cwd);
|
|
5818
5847
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
|
|
5819
5848
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
5820
5849
|
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
|
|
@@ -6183,6 +6212,8 @@ var Entry = React5.memo(function Entry2({
|
|
|
6183
6212
|
{
|
|
6184
6213
|
rows: diff.rows,
|
|
6185
6214
|
hidden: diff.hidden,
|
|
6215
|
+
added: diff.added,
|
|
6216
|
+
removed: diff.removed,
|
|
6186
6217
|
hiddenAdded: diff.hiddenAdded,
|
|
6187
6218
|
hiddenRemoved: diff.hiddenRemoved,
|
|
6188
6219
|
useColor: true
|
|
@@ -6428,7 +6459,7 @@ var ScrollableHistory = memo(function ScrollableHistory2({
|
|
|
6428
6459
|
});
|
|
6429
6460
|
var MB = 1024 * 1024;
|
|
6430
6461
|
function defaultHeapLogPath() {
|
|
6431
|
-
return
|
|
6462
|
+
return path6.join(wstackGlobalRoot(), "logs", "heap.jsonl");
|
|
6432
6463
|
}
|
|
6433
6464
|
function takeHeapSample() {
|
|
6434
6465
|
const m = process.memoryUsage();
|
|
@@ -6458,10 +6489,10 @@ function startHeapWatchdog(opts = {}) {
|
|
|
6458
6489
|
const append = (line) => {
|
|
6459
6490
|
writeChain = writeChain.then(async () => {
|
|
6460
6491
|
if (!dirReady) {
|
|
6461
|
-
await
|
|
6492
|
+
await fsp.mkdir(path6.dirname(logPath), { recursive: true });
|
|
6462
6493
|
dirReady = true;
|
|
6463
6494
|
}
|
|
6464
|
-
await
|
|
6495
|
+
await fsp.appendFile(logPath, `${line}
|
|
6465
6496
|
`, "utf8");
|
|
6466
6497
|
}).catch(() => void 0);
|
|
6467
6498
|
};
|
|
@@ -7720,12 +7751,12 @@ var STATUS_ICON = {
|
|
|
7720
7751
|
function planFilePath(projectRoot, sessionId, scope) {
|
|
7721
7752
|
const base = resolveWstackPaths$1({ projectRoot }).projectSessions;
|
|
7722
7753
|
if (scope === "project") {
|
|
7723
|
-
return
|
|
7754
|
+
return path6.join(base, "backlog.plan.json");
|
|
7724
7755
|
}
|
|
7725
7756
|
if (sessionId) {
|
|
7726
|
-
return
|
|
7757
|
+
return sessionScopedPath(base, sessionId, ".plan.json");
|
|
7727
7758
|
}
|
|
7728
|
-
return
|
|
7759
|
+
return path6.join(base, "backlog.plan.json");
|
|
7729
7760
|
}
|
|
7730
7761
|
function PlanPanel({ projectRoot, sessionId, onClose: _onClose }) {
|
|
7731
7762
|
const [items, setItems] = useState([]);
|
|
@@ -7740,7 +7771,7 @@ function PlanPanel({ projectRoot, sessionId, onClose: _onClose }) {
|
|
|
7740
7771
|
const filePath = planFilePath(projectRoot, sessionId, scope_);
|
|
7741
7772
|
let content;
|
|
7742
7773
|
try {
|
|
7743
|
-
content = await
|
|
7774
|
+
content = await fsp.readFile(filePath, "utf-8");
|
|
7744
7775
|
} catch {
|
|
7745
7776
|
setItems([]);
|
|
7746
7777
|
setTitle2(void 0);
|
|
@@ -8114,6 +8145,10 @@ function fmtDuration2(startedAt) {
|
|
|
8114
8145
|
if (h < 24) return `${h}h ${min % 60}m`;
|
|
8115
8146
|
return `${Math.floor(h / 24)}d ${h % 24}h`;
|
|
8116
8147
|
}
|
|
8148
|
+
function shortSessionId2(sessionId) {
|
|
8149
|
+
const leaf = sessionId.split("/").pop() ?? sessionId;
|
|
8150
|
+
return leaf.length > 18 ? leaf.slice(0, 18) : leaf;
|
|
8151
|
+
}
|
|
8117
8152
|
function SessionsPanel({
|
|
8118
8153
|
sessions,
|
|
8119
8154
|
busy,
|
|
@@ -8150,7 +8185,7 @@ function SessionsPanel({
|
|
|
8150
8185
|
] }),
|
|
8151
8186
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
8152
8187
|
" \xB7 ",
|
|
8153
|
-
s2.sessionId
|
|
8188
|
+
shortSessionId2(s2.sessionId)
|
|
8154
8189
|
] }),
|
|
8155
8190
|
s2.gitBranch ? /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
|
|
8156
8191
|
" \u2387 ",
|
|
@@ -9828,84 +9863,6 @@ function WorktreePanel({
|
|
|
9828
9863
|
}
|
|
9829
9864
|
);
|
|
9830
9865
|
}
|
|
9831
|
-
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
9832
|
-
"node_modules",
|
|
9833
|
-
".git",
|
|
9834
|
-
"dist",
|
|
9835
|
-
"build",
|
|
9836
|
-
".next",
|
|
9837
|
-
".turbo",
|
|
9838
|
-
".cache",
|
|
9839
|
-
"coverage",
|
|
9840
|
-
".idea",
|
|
9841
|
-
".vscode"
|
|
9842
|
-
]);
|
|
9843
|
-
var MAX_FILES_INDEXED = 5e3;
|
|
9844
|
-
var MAX_DEPTH = 8;
|
|
9845
|
-
var cache = null;
|
|
9846
|
-
var CACHE_TTL_MS = 3e4;
|
|
9847
|
-
async function loadIndex(root) {
|
|
9848
|
-
const now = Date.now();
|
|
9849
|
-
if (cache && cache.root === root && now - cache.loadedAt < CACHE_TTL_MS) {
|
|
9850
|
-
return cache.files;
|
|
9851
|
-
}
|
|
9852
|
-
const files = [];
|
|
9853
|
-
await walk(root, "", 0, files);
|
|
9854
|
-
files.sort();
|
|
9855
|
-
cache = { root, files, loadedAt: now };
|
|
9856
|
-
return files;
|
|
9857
|
-
}
|
|
9858
|
-
async function walk(root, rel, depth, out) {
|
|
9859
|
-
if (out.length >= MAX_FILES_INDEXED) return;
|
|
9860
|
-
if (depth > MAX_DEPTH) return;
|
|
9861
|
-
const dir = rel ? path5.join(root, rel) : root;
|
|
9862
|
-
let entries;
|
|
9863
|
-
try {
|
|
9864
|
-
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
9865
|
-
} catch {
|
|
9866
|
-
return;
|
|
9867
|
-
}
|
|
9868
|
-
for (const e of entries) {
|
|
9869
|
-
if (out.length >= MAX_FILES_INDEXED) return;
|
|
9870
|
-
if (e.name.startsWith(".") && e.name !== ".env.example") continue;
|
|
9871
|
-
if (IGNORED_DIRS.has(e.name)) continue;
|
|
9872
|
-
const next = rel ? `${rel}/${e.name}` : e.name;
|
|
9873
|
-
if (e.isDirectory()) {
|
|
9874
|
-
await walk(root, next, depth + 1, out);
|
|
9875
|
-
} else if (e.isFile()) {
|
|
9876
|
-
out.push(next);
|
|
9877
|
-
}
|
|
9878
|
-
}
|
|
9879
|
-
}
|
|
9880
|
-
function score(s2, query) {
|
|
9881
|
-
if (!query) return s2.length;
|
|
9882
|
-
const ql = query.toLowerCase();
|
|
9883
|
-
const sl = s2.toLowerCase();
|
|
9884
|
-
let si = 0;
|
|
9885
|
-
let firstHit = -1;
|
|
9886
|
-
let lastHit = -1;
|
|
9887
|
-
for (let qi = 0; qi < ql.length; qi++) {
|
|
9888
|
-
const c = ql.charCodeAt(qi);
|
|
9889
|
-
while (si < sl.length && sl.charCodeAt(si) !== c) si++;
|
|
9890
|
-
if (si >= sl.length) return null;
|
|
9891
|
-
if (firstHit < 0) firstHit = si;
|
|
9892
|
-
lastHit = si;
|
|
9893
|
-
si++;
|
|
9894
|
-
}
|
|
9895
|
-
const span = lastHit - firstHit;
|
|
9896
|
-
return span * 100 + firstHit * 2 + s2.length;
|
|
9897
|
-
}
|
|
9898
|
-
async function searchFiles(root, query, limit = 8) {
|
|
9899
|
-
const all = await loadIndex(root);
|
|
9900
|
-
if (!query) return all.slice(0, limit);
|
|
9901
|
-
const scored = [];
|
|
9902
|
-
for (const f of all) {
|
|
9903
|
-
const sc = score(f, query);
|
|
9904
|
-
if (sc !== null) scored.push({ path: f, score: sc });
|
|
9905
|
-
}
|
|
9906
|
-
scored.sort((a, b) => a.score - b.score);
|
|
9907
|
-
return scored.slice(0, limit).map((x) => x.path);
|
|
9908
|
-
}
|
|
9909
9866
|
async function readGitInfo(cwd) {
|
|
9910
9867
|
const [branchRes, numstatRes, statusRes] = await Promise.all([
|
|
9911
9868
|
runGit(cwd, ["branch", "--show-current"]),
|
|
@@ -9959,130 +9916,383 @@ function runGit(cwd, args) {
|
|
|
9959
9916
|
}
|
|
9960
9917
|
});
|
|
9961
9918
|
}
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9919
|
+
|
|
9920
|
+
// src/queue-slash.ts
|
|
9921
|
+
var USAGE = "Usage:\n /queue \u2014 list pending messages\n /queue list \u2014 same as /queue\n /queue clear \u2014 drop all pending messages\n /queue delete N M\u2026 \u2014 drop messages at 1-based positions\n /queue picker on|off \u2014 toggle the mid-run send-mode picker (queue/btw/steer)\n /queue picker \u2014 show whether the picker is on";
|
|
9922
|
+
function createQueueSlashCommand(deps) {
|
|
9923
|
+
return {
|
|
9924
|
+
name: "queue",
|
|
9925
|
+
description: "List, clear, or delete pending messages typed while the agent was running.",
|
|
9926
|
+
async run(args) {
|
|
9927
|
+
const out = handleQueueCommand(args, deps);
|
|
9928
|
+
return { message: out };
|
|
9929
|
+
}
|
|
9930
|
+
};
|
|
9968
9931
|
}
|
|
9969
|
-
function
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
if (debouncedEnter(host)) return true;
|
|
10009
|
-
host.inputGateRef.current = true;
|
|
10010
|
-
try {
|
|
10011
|
-
if (state.modelPicker.step === "provider") {
|
|
10012
|
-
const opt = state.modelPicker.providerOptions[state.modelPicker.selected];
|
|
10013
|
-
if (!opt) return true;
|
|
10014
|
-
dispatch({
|
|
10015
|
-
type: "modelPickerPickProvider",
|
|
10016
|
-
providerId: opt.id,
|
|
10017
|
-
models: opt.models
|
|
10018
|
-
});
|
|
10019
|
-
return true;
|
|
10020
|
-
}
|
|
10021
|
-
const providerId = state.modelPicker.pickedProviderId;
|
|
10022
|
-
const modelId = state.modelPicker.filteredOptions[state.modelPicker.selected];
|
|
10023
|
-
if (!providerId || !modelId) return true;
|
|
10024
|
-
const complete = (err) => {
|
|
10025
|
-
if (err) {
|
|
10026
|
-
dispatch({ type: "modelPickerHint", text: err });
|
|
10027
|
-
return;
|
|
10028
|
-
}
|
|
10029
|
-
host.setLiveProvider?.(providerId);
|
|
10030
|
-
host.setLiveModel?.(modelId);
|
|
10031
|
-
host.setActiveMaxContext?.(host.agentCtxMaxContext);
|
|
10032
|
-
dispatch({
|
|
10033
|
-
type: "addEntry",
|
|
10034
|
-
entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
|
|
10035
|
-
});
|
|
10036
|
-
dispatch({ type: "modelPickerClose" });
|
|
10037
|
-
};
|
|
10038
|
-
const result = host.switchProviderAndModel?.(providerId, modelId);
|
|
10039
|
-
if (result && typeof result.then === "function") {
|
|
10040
|
-
void result.then(complete).catch((err) => {
|
|
10041
|
-
complete(err instanceof Error ? err.message : String(err));
|
|
10042
|
-
});
|
|
10043
|
-
return true;
|
|
10044
|
-
}
|
|
10045
|
-
complete(result);
|
|
10046
|
-
return true;
|
|
10047
|
-
} finally {
|
|
10048
|
-
host.inputGateRef.current = false;
|
|
10049
|
-
}
|
|
10050
|
-
}
|
|
10051
|
-
return true;
|
|
9932
|
+
function handleQueueCommand(args, deps) {
|
|
9933
|
+
const trimmed = args.trim();
|
|
9934
|
+
const [sub, ...rest] = trimmed.split(/\s+/);
|
|
9935
|
+
const subcommand = sub?.toLowerCase() ?? "";
|
|
9936
|
+
if (subcommand === "" || subcommand === "list") {
|
|
9937
|
+
return renderList(deps.getQueue());
|
|
9938
|
+
}
|
|
9939
|
+
if (subcommand === "picker") {
|
|
9940
|
+
if (!deps.getPickerEnabled || !deps.setPickerEnabled) {
|
|
9941
|
+
return "Send-mode picker toggle is unavailable in this session.";
|
|
9942
|
+
}
|
|
9943
|
+
const arg = rest[0]?.toLowerCase() ?? "";
|
|
9944
|
+
if (arg === "" || arg === "status") {
|
|
9945
|
+
return `Mid-run send-mode picker is ${deps.getPickerEnabled() ? "on" : "off"}.`;
|
|
9946
|
+
}
|
|
9947
|
+
if (arg === "on" || arg === "off") {
|
|
9948
|
+
const next = arg === "on";
|
|
9949
|
+
deps.setPickerEnabled(next);
|
|
9950
|
+
return next ? "Mid-run send-mode picker: on \u2014 plain messages typed while busy ask queue/btw/steer." : "Mid-run send-mode picker: off \u2014 plain messages typed while busy are queued silently.";
|
|
9951
|
+
}
|
|
9952
|
+
return "Usage: /queue picker on|off";
|
|
9953
|
+
}
|
|
9954
|
+
if (subcommand === "clear") {
|
|
9955
|
+
const before = deps.getQueue().length;
|
|
9956
|
+
if (before === 0) return "Queue is already empty.";
|
|
9957
|
+
deps.clear();
|
|
9958
|
+
return `Cleared ${before} queued message${before === 1 ? "" : "s"}.`;
|
|
9959
|
+
}
|
|
9960
|
+
if (subcommand === "delete" || subcommand === "del" || subcommand === "rm") {
|
|
9961
|
+
if (rest.length === 0) return "Usage: /queue delete <position> [<position>\u2026]";
|
|
9962
|
+
const queue = deps.getQueue();
|
|
9963
|
+
if (queue.length === 0) return "Queue is empty \u2014 nothing to delete.";
|
|
9964
|
+
const parsed = [];
|
|
9965
|
+
const invalid = [];
|
|
9966
|
+
const outOfRange = [];
|
|
9967
|
+
for (const tok of rest) {
|
|
9968
|
+
if (!/^\d+$/.test(tok)) {
|
|
9969
|
+
invalid.push(tok);
|
|
9970
|
+
continue;
|
|
10052
9971
|
}
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
}
|
|
10058
|
-
if (key.mouse?.kind === "wheel") {
|
|
10059
|
-
dispatch({ type: "autonomyPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
|
|
10060
|
-
return true;
|
|
10061
|
-
}
|
|
10062
|
-
if (key.upArrow) {
|
|
10063
|
-
dispatch({ type: "autonomyPickerMove", delta: -1 });
|
|
10064
|
-
return true;
|
|
10065
|
-
}
|
|
10066
|
-
if (key.downArrow) {
|
|
10067
|
-
dispatch({ type: "autonomyPickerMove", delta: 1 });
|
|
10068
|
-
return true;
|
|
10069
|
-
}
|
|
10070
|
-
if (isEnter) {
|
|
10071
|
-
if (debouncedEnter(host)) return true;
|
|
10072
|
-
const opt = state.autonomyPicker.options[state.autonomyPicker.selected];
|
|
10073
|
-
if (!opt) return true;
|
|
10074
|
-
const err = host.switchAutonomy?.(opt.mode);
|
|
10075
|
-
if (err) {
|
|
10076
|
-
dispatch({ type: "autonomyPickerHint", text: err });
|
|
10077
|
-
return true;
|
|
10078
|
-
}
|
|
10079
|
-
dispatch({ type: "autonomyPickerClose" });
|
|
10080
|
-
return true;
|
|
10081
|
-
}
|
|
10082
|
-
return true;
|
|
9972
|
+
const n = Number.parseInt(tok, 10);
|
|
9973
|
+
if (n < 1 || n > queue.length) {
|
|
9974
|
+
outOfRange.push(n);
|
|
9975
|
+
continue;
|
|
10083
9976
|
}
|
|
10084
|
-
|
|
10085
|
-
|
|
9977
|
+
parsed.push(n);
|
|
9978
|
+
}
|
|
9979
|
+
const uniqueValid = [...new Set(parsed)];
|
|
9980
|
+
if (uniqueValid.length === 0) {
|
|
9981
|
+
const parts2 = ["No valid positions to delete."];
|
|
9982
|
+
if (invalid.length > 0) parts2.push(`Invalid: ${invalid.join(", ")}.`);
|
|
9983
|
+
if (outOfRange.length > 0)
|
|
9984
|
+
parts2.push(`Out of range (queue has ${queue.length}): ${outOfRange.join(", ")}.`);
|
|
9985
|
+
return parts2.join(" ");
|
|
9986
|
+
}
|
|
9987
|
+
deps.deleteAt(uniqueValid);
|
|
9988
|
+
const parts = [
|
|
9989
|
+
`Deleted ${uniqueValid.length} of ${queue.length} (positions ${uniqueValid.sort((a, b) => a - b).join(", ")}).`
|
|
9990
|
+
];
|
|
9991
|
+
if (invalid.length > 0) parts.push(`Ignored invalid: ${invalid.join(", ")}.`);
|
|
9992
|
+
if (outOfRange.length > 0) parts.push(`Ignored out of range: ${outOfRange.join(", ")}.`);
|
|
9993
|
+
return parts.join(" ");
|
|
9994
|
+
}
|
|
9995
|
+
return `Unknown subcommand "${sub}".
|
|
9996
|
+
${USAGE}`;
|
|
9997
|
+
}
|
|
9998
|
+
function renderList(queue) {
|
|
9999
|
+
if (queue.length === 0) return "Queue is empty.";
|
|
10000
|
+
const lines = [`Queue (${queue.length}):`];
|
|
10001
|
+
for (let i = 0; i < queue.length; i++) {
|
|
10002
|
+
const item = queue[i];
|
|
10003
|
+
if (!item) continue;
|
|
10004
|
+
const preview = oneLine(item.displayText, 100);
|
|
10005
|
+
lines.push(` ${i + 1}. ${preview}`);
|
|
10006
|
+
}
|
|
10007
|
+
return lines.join("\n");
|
|
10008
|
+
}
|
|
10009
|
+
function oneLine(s2, max) {
|
|
10010
|
+
const collapsed = s2.replace(/\s+/g, " ").trim();
|
|
10011
|
+
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
10012
|
+
}
|
|
10013
|
+
|
|
10014
|
+
// src/hooks/use-queue-manager.ts
|
|
10015
|
+
function useQueueManager({
|
|
10016
|
+
queueStore,
|
|
10017
|
+
onQueueChange,
|
|
10018
|
+
slashRegistry,
|
|
10019
|
+
stateRef,
|
|
10020
|
+
dispatch,
|
|
10021
|
+
getSettings,
|
|
10022
|
+
saveSettings,
|
|
10023
|
+
midRunSendPickerRef
|
|
10024
|
+
}) {
|
|
10025
|
+
useEffect(() => {
|
|
10026
|
+
if (!queueStore) return;
|
|
10027
|
+
let cancelled = false;
|
|
10028
|
+
queueStore.read().then((items) => {
|
|
10029
|
+
if (cancelled || items.length === 0) return;
|
|
10030
|
+
for (const item of items) {
|
|
10031
|
+
dispatch({
|
|
10032
|
+
type: "enqueue",
|
|
10033
|
+
item: { displayText: item.displayText, blocks: item.blocks }
|
|
10034
|
+
});
|
|
10035
|
+
}
|
|
10036
|
+
dispatch({
|
|
10037
|
+
type: "addEntry",
|
|
10038
|
+
entry: {
|
|
10039
|
+
kind: "info",
|
|
10040
|
+
text: `Restored ${items.length} queued message${items.length === 1 ? "" : "s"} from a previous run.`
|
|
10041
|
+
}
|
|
10042
|
+
});
|
|
10043
|
+
}).catch(() => void 0);
|
|
10044
|
+
return () => {
|
|
10045
|
+
cancelled = true;
|
|
10046
|
+
};
|
|
10047
|
+
}, [queueStore]);
|
|
10048
|
+
useEffect(() => {
|
|
10049
|
+
if (!queueStore) return;
|
|
10050
|
+
const raw = stateRef.current.queue.map(
|
|
10051
|
+
({ displayText, blocks }) => ({
|
|
10052
|
+
displayText,
|
|
10053
|
+
blocks
|
|
10054
|
+
})
|
|
10055
|
+
);
|
|
10056
|
+
queueStore.write(raw).catch(() => void 0);
|
|
10057
|
+
}, [stateRef.current.queue, queueStore, stateRef]);
|
|
10058
|
+
useEffect(() => {
|
|
10059
|
+
onQueueChange?.(stateRef.current.queue.map((q) => q.displayText));
|
|
10060
|
+
}, [stateRef.current.queue, onQueueChange, stateRef]);
|
|
10061
|
+
useEffect(() => {
|
|
10062
|
+
const cmd = createQueueSlashCommand({
|
|
10063
|
+
getQueue: () => stateRef.current.queue,
|
|
10064
|
+
clear: () => dispatch({ type: "queueClear" }),
|
|
10065
|
+
deleteAt: (positions) => dispatch({ type: "queueDelete", positions }),
|
|
10066
|
+
getPickerEnabled: () => midRunSendPickerRef.current,
|
|
10067
|
+
setPickerEnabled: (enabled) => {
|
|
10068
|
+
midRunSendPickerRef.current = enabled;
|
|
10069
|
+
const cur = getSettings?.();
|
|
10070
|
+
if (cur && saveSettings) {
|
|
10071
|
+
Promise.resolve(saveSettings({ ...cur, midRunSendPicker: enabled })).catch(() => {
|
|
10072
|
+
});
|
|
10073
|
+
}
|
|
10074
|
+
}
|
|
10075
|
+
});
|
|
10076
|
+
slashRegistry.register(cmd);
|
|
10077
|
+
return () => {
|
|
10078
|
+
slashRegistry.unregister("queue");
|
|
10079
|
+
};
|
|
10080
|
+
}, [slashRegistry, stateRef, dispatch, getSettings, saveSettings, midRunSendPickerRef]);
|
|
10081
|
+
}
|
|
10082
|
+
|
|
10083
|
+
// src/hooks/use-paste-handling.ts
|
|
10084
|
+
function usePasteHandling({
|
|
10085
|
+
builderRef,
|
|
10086
|
+
dispatch,
|
|
10087
|
+
draftRef,
|
|
10088
|
+
setDraft,
|
|
10089
|
+
tokenPreviewsRef
|
|
10090
|
+
}) {
|
|
10091
|
+
const pasteAccumRef = useRef(null);
|
|
10092
|
+
const pasteFlushTimerRef = useRef(null);
|
|
10093
|
+
const commitPaste = async (full) => {
|
|
10094
|
+
const builder = builderRef.current;
|
|
10095
|
+
if (!builder || !full) return;
|
|
10096
|
+
const { buffer, cursor } = draftRef.current;
|
|
10097
|
+
const isSlashCmd = buffer.trimStart().startsWith("/");
|
|
10098
|
+
const mustCollapse = builder.wouldCollapse(full);
|
|
10099
|
+
const multiLine = full.includes("\n");
|
|
10100
|
+
if (isSlashCmd && !mustCollapse) {
|
|
10101
|
+
const next2 = buffer.slice(0, cursor) + full + buffer.slice(cursor);
|
|
10102
|
+
setDraft(next2, cursor + full.length);
|
|
10103
|
+
return;
|
|
10104
|
+
}
|
|
10105
|
+
if (mustCollapse || multiLine) {
|
|
10106
|
+
const token = await builder.registerPaste(full);
|
|
10107
|
+
tokenPreviewsRef.current.set(token, full);
|
|
10108
|
+
const next2 = buffer.slice(0, cursor) + token + buffer.slice(cursor);
|
|
10109
|
+
setDraft(next2, cursor + token.length);
|
|
10110
|
+
return;
|
|
10111
|
+
}
|
|
10112
|
+
const next = buffer.slice(0, cursor) + full + buffer.slice(cursor);
|
|
10113
|
+
setDraft(next, cursor + full.length);
|
|
10114
|
+
};
|
|
10115
|
+
const pasteClipboardImage = async () => {
|
|
10116
|
+
const builder = builderRef.current;
|
|
10117
|
+
if (!builder) return;
|
|
10118
|
+
try {
|
|
10119
|
+
const img = await readClipboardImage();
|
|
10120
|
+
if (!img) {
|
|
10121
|
+
dispatch({
|
|
10122
|
+
type: "addEntry",
|
|
10123
|
+
entry: { kind: "info", text: "No image on the clipboard." }
|
|
10124
|
+
});
|
|
10125
|
+
return;
|
|
10126
|
+
}
|
|
10127
|
+
const token = await builder.registerImage(img.base64, img.mediaType);
|
|
10128
|
+
const kb = (img.bytes / 1024).toFixed(0);
|
|
10129
|
+
tokenPreviewsRef.current.set(token, `image, ${kb} KB`);
|
|
10130
|
+
const { buffer, cursor } = draftRef.current;
|
|
10131
|
+
const next = buffer.slice(0, cursor) + token + buffer.slice(cursor);
|
|
10132
|
+
setDraft(next, cursor + token.length);
|
|
10133
|
+
} catch (err) {
|
|
10134
|
+
dispatch({
|
|
10135
|
+
type: "addEntry",
|
|
10136
|
+
entry: {
|
|
10137
|
+
kind: "error",
|
|
10138
|
+
text: `Clipboard image error: ${toErrorMessage(err)}`
|
|
10139
|
+
}
|
|
10140
|
+
});
|
|
10141
|
+
}
|
|
10142
|
+
};
|
|
10143
|
+
const pasteClipboardText = async () => {
|
|
10144
|
+
try {
|
|
10145
|
+
const text = await readClipboardText();
|
|
10146
|
+
if (!text) {
|
|
10147
|
+
dispatch({
|
|
10148
|
+
type: "addEntry",
|
|
10149
|
+
entry: { kind: "info", text: "No text on the clipboard." }
|
|
10150
|
+
});
|
|
10151
|
+
return;
|
|
10152
|
+
}
|
|
10153
|
+
await commitPaste(text);
|
|
10154
|
+
} catch (err) {
|
|
10155
|
+
dispatch({
|
|
10156
|
+
type: "addEntry",
|
|
10157
|
+
entry: {
|
|
10158
|
+
kind: "error",
|
|
10159
|
+
text: `Clipboard error: ${toErrorMessage(err)}`
|
|
10160
|
+
}
|
|
10161
|
+
});
|
|
10162
|
+
}
|
|
10163
|
+
};
|
|
10164
|
+
return {
|
|
10165
|
+
pasteAccumRef,
|
|
10166
|
+
pasteFlushTimerRef,
|
|
10167
|
+
commitPaste,
|
|
10168
|
+
pasteClipboardImage,
|
|
10169
|
+
pasteClipboardText
|
|
10170
|
+
};
|
|
10171
|
+
}
|
|
10172
|
+
var ENTER_DOUBLE_TAP_MS = 50;
|
|
10173
|
+
function debouncedEnter(host) {
|
|
10174
|
+
const now = Date.now();
|
|
10175
|
+
if (now - host.lastEnterAtRef.current < ENTER_DOUBLE_TAP_MS) return true;
|
|
10176
|
+
host.lastEnterAtRef.current = now;
|
|
10177
|
+
return false;
|
|
10178
|
+
}
|
|
10179
|
+
function usePickerKeys(host) {
|
|
10180
|
+
return useCallback(
|
|
10181
|
+
(input, key, isEnter) => {
|
|
10182
|
+
const { state, dispatch } = host;
|
|
10183
|
+
if (state.modelPicker.open) {
|
|
10184
|
+
if (key.escape) {
|
|
10185
|
+
if (state.modelPicker.step === "model") {
|
|
10186
|
+
dispatch({ type: "modelPickerBack" });
|
|
10187
|
+
} else {
|
|
10188
|
+
dispatch({ type: "modelPickerClose" });
|
|
10189
|
+
}
|
|
10190
|
+
return true;
|
|
10191
|
+
}
|
|
10192
|
+
if (key.mouse?.kind === "wheel") {
|
|
10193
|
+
dispatch({ type: "modelPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
|
|
10194
|
+
return true;
|
|
10195
|
+
}
|
|
10196
|
+
if (key.upArrow) {
|
|
10197
|
+
dispatch({ type: "modelPickerMove", delta: -1 });
|
|
10198
|
+
return true;
|
|
10199
|
+
}
|
|
10200
|
+
if (key.downArrow) {
|
|
10201
|
+
dispatch({ type: "modelPickerMove", delta: 1 });
|
|
10202
|
+
return true;
|
|
10203
|
+
}
|
|
10204
|
+
if (state.modelPicker.step === "model" && input && !key.return && !key.backspace) {
|
|
10205
|
+
dispatch({ type: "modelPickerSearch", query: state.modelPicker.searchQuery + input });
|
|
10206
|
+
return true;
|
|
10207
|
+
}
|
|
10208
|
+
if (state.modelPicker.step === "model" && key.backspace) {
|
|
10209
|
+
const q = state.modelPicker.searchQuery;
|
|
10210
|
+
if (q.length > 0) {
|
|
10211
|
+
dispatch({ type: "modelPickerSearch", query: q.slice(0, -1) });
|
|
10212
|
+
} else {
|
|
10213
|
+
dispatch({ type: "modelPickerBack" });
|
|
10214
|
+
}
|
|
10215
|
+
return true;
|
|
10216
|
+
}
|
|
10217
|
+
if (isEnter) {
|
|
10218
|
+
if (debouncedEnter(host)) return true;
|
|
10219
|
+
host.inputGateRef.current = true;
|
|
10220
|
+
try {
|
|
10221
|
+
if (state.modelPicker.step === "provider") {
|
|
10222
|
+
const opt = state.modelPicker.providerOptions[state.modelPicker.selected];
|
|
10223
|
+
if (!opt) return true;
|
|
10224
|
+
dispatch({
|
|
10225
|
+
type: "modelPickerPickProvider",
|
|
10226
|
+
providerId: opt.id,
|
|
10227
|
+
models: opt.models
|
|
10228
|
+
});
|
|
10229
|
+
return true;
|
|
10230
|
+
}
|
|
10231
|
+
const providerId = state.modelPicker.pickedProviderId;
|
|
10232
|
+
const modelId = state.modelPicker.filteredOptions[state.modelPicker.selected];
|
|
10233
|
+
if (!providerId || !modelId) return true;
|
|
10234
|
+
const complete = (err) => {
|
|
10235
|
+
if (err) {
|
|
10236
|
+
dispatch({ type: "modelPickerHint", text: err });
|
|
10237
|
+
return;
|
|
10238
|
+
}
|
|
10239
|
+
host.setLiveProvider?.(providerId);
|
|
10240
|
+
host.setLiveModel?.(modelId);
|
|
10241
|
+
host.setActiveMaxContext?.(host.agentCtxMaxContext);
|
|
10242
|
+
dispatch({
|
|
10243
|
+
type: "addEntry",
|
|
10244
|
+
entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
|
|
10245
|
+
});
|
|
10246
|
+
dispatch({ type: "modelPickerClose" });
|
|
10247
|
+
};
|
|
10248
|
+
const result = host.switchProviderAndModel?.(providerId, modelId);
|
|
10249
|
+
if (result && typeof result.then === "function") {
|
|
10250
|
+
void result.then(complete).catch((err) => {
|
|
10251
|
+
complete(err instanceof Error ? err.message : String(err));
|
|
10252
|
+
});
|
|
10253
|
+
return true;
|
|
10254
|
+
}
|
|
10255
|
+
complete(result);
|
|
10256
|
+
return true;
|
|
10257
|
+
} finally {
|
|
10258
|
+
host.inputGateRef.current = false;
|
|
10259
|
+
}
|
|
10260
|
+
}
|
|
10261
|
+
return true;
|
|
10262
|
+
}
|
|
10263
|
+
if (state.autonomyPicker.open) {
|
|
10264
|
+
if (key.escape) {
|
|
10265
|
+
dispatch({ type: "autonomyPickerClose" });
|
|
10266
|
+
return true;
|
|
10267
|
+
}
|
|
10268
|
+
if (key.mouse?.kind === "wheel") {
|
|
10269
|
+
dispatch({ type: "autonomyPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
|
|
10270
|
+
return true;
|
|
10271
|
+
}
|
|
10272
|
+
if (key.upArrow) {
|
|
10273
|
+
dispatch({ type: "autonomyPickerMove", delta: -1 });
|
|
10274
|
+
return true;
|
|
10275
|
+
}
|
|
10276
|
+
if (key.downArrow) {
|
|
10277
|
+
dispatch({ type: "autonomyPickerMove", delta: 1 });
|
|
10278
|
+
return true;
|
|
10279
|
+
}
|
|
10280
|
+
if (isEnter) {
|
|
10281
|
+
if (debouncedEnter(host)) return true;
|
|
10282
|
+
const opt = state.autonomyPicker.options[state.autonomyPicker.selected];
|
|
10283
|
+
if (!opt) return true;
|
|
10284
|
+
const err = host.switchAutonomy?.(opt.mode);
|
|
10285
|
+
if (err) {
|
|
10286
|
+
dispatch({ type: "autonomyPickerHint", text: err });
|
|
10287
|
+
return true;
|
|
10288
|
+
}
|
|
10289
|
+
dispatch({ type: "autonomyPickerClose" });
|
|
10290
|
+
return true;
|
|
10291
|
+
}
|
|
10292
|
+
return true;
|
|
10293
|
+
}
|
|
10294
|
+
if (state.designPicker.open) {
|
|
10295
|
+
if (key.escape) {
|
|
10086
10296
|
dispatch({ type: "designPickerClose" });
|
|
10087
10297
|
return true;
|
|
10088
10298
|
}
|
|
@@ -10826,43 +11036,199 @@ function handleCollabPlan(event, dispatch, stateRef) {
|
|
|
10826
11036
|
phaseCount: payload.plan.phases?.length ?? 0
|
|
10827
11037
|
});
|
|
10828
11038
|
}
|
|
10829
|
-
function handleCollabEvaluation(event, dispatch, stateRef) {
|
|
10830
|
-
if (!stateRef.current.collabSession) return;
|
|
10831
|
-
const payload = event.payload;
|
|
10832
|
-
if (!payload?.evaluation) return;
|
|
10833
|
-
dispatch({
|
|
10834
|
-
type: "collabEvalComplete",
|
|
10835
|
-
sessionId: collabSessionId(event.subagentId),
|
|
10836
|
-
evalId: payload.evaluation.id ?? "unknown",
|
|
10837
|
-
verdict: payload.evaluation.verdict ?? "unknown",
|
|
10838
|
-
score: payload.evaluation.score ?? 0
|
|
10839
|
-
});
|
|
11039
|
+
function handleCollabEvaluation(event, dispatch, stateRef) {
|
|
11040
|
+
if (!stateRef.current.collabSession) return;
|
|
11041
|
+
const payload = event.payload;
|
|
11042
|
+
if (!payload?.evaluation) return;
|
|
11043
|
+
dispatch({
|
|
11044
|
+
type: "collabEvalComplete",
|
|
11045
|
+
sessionId: collabSessionId(event.subagentId),
|
|
11046
|
+
evalId: payload.evaluation.id ?? "unknown",
|
|
11047
|
+
verdict: payload.evaluation.verdict ?? "unknown",
|
|
11048
|
+
score: payload.evaluation.score ?? 0
|
|
11049
|
+
});
|
|
11050
|
+
}
|
|
11051
|
+
function handleCollabDone(event, dispatch, stateRef) {
|
|
11052
|
+
const collabSession = stateRef.current.collabSession;
|
|
11053
|
+
if (!collabSession) return;
|
|
11054
|
+
const payload = event.payload;
|
|
11055
|
+
if (!payload?.report) return;
|
|
11056
|
+
dispatch({
|
|
11057
|
+
type: "collabSessionDone",
|
|
11058
|
+
sessionId: payload.report.sessionId ?? collabSession.sessionId ?? "unknown",
|
|
11059
|
+
verdict: payload.report.overallVerdict ?? "needs_revision"
|
|
11060
|
+
});
|
|
11061
|
+
}
|
|
11062
|
+
function useAutonomousCoordinator(subscribeCoordinatorEvents, dispatch) {
|
|
11063
|
+
const dispatchRef = useRef(dispatch);
|
|
11064
|
+
dispatchRef.current = dispatch;
|
|
11065
|
+
const handler = useCallback((event) => {
|
|
11066
|
+
dispatchRef.current({
|
|
11067
|
+
type: "coordinatorEvent",
|
|
11068
|
+
event
|
|
11069
|
+
});
|
|
11070
|
+
}, []);
|
|
11071
|
+
useEffect(() => {
|
|
11072
|
+
if (!subscribeCoordinatorEvents) return;
|
|
11073
|
+
const unsubscribe = subscribeCoordinatorEvents(handler);
|
|
11074
|
+
return unsubscribe;
|
|
11075
|
+
}, [subscribeCoordinatorEvents, handler]);
|
|
11076
|
+
}
|
|
11077
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
11078
|
+
"node_modules",
|
|
11079
|
+
".git",
|
|
11080
|
+
"dist",
|
|
11081
|
+
"build",
|
|
11082
|
+
".next",
|
|
11083
|
+
".turbo",
|
|
11084
|
+
".cache",
|
|
11085
|
+
"coverage",
|
|
11086
|
+
".idea",
|
|
11087
|
+
".vscode"
|
|
11088
|
+
]);
|
|
11089
|
+
var MAX_FILES_INDEXED = 5e3;
|
|
11090
|
+
var MAX_DEPTH = 8;
|
|
11091
|
+
var cache = null;
|
|
11092
|
+
var CACHE_TTL_MS = 3e4;
|
|
11093
|
+
async function loadIndex(root) {
|
|
11094
|
+
const now = Date.now();
|
|
11095
|
+
if (cache && cache.root === root && now - cache.loadedAt < CACHE_TTL_MS) {
|
|
11096
|
+
return cache.files;
|
|
11097
|
+
}
|
|
11098
|
+
const files = [];
|
|
11099
|
+
await walk(root, "", 0, files);
|
|
11100
|
+
files.sort();
|
|
11101
|
+
cache = { root, files, loadedAt: now };
|
|
11102
|
+
return files;
|
|
11103
|
+
}
|
|
11104
|
+
async function walk(root, rel, depth, out) {
|
|
11105
|
+
if (out.length >= MAX_FILES_INDEXED) return;
|
|
11106
|
+
if (depth > MAX_DEPTH) return;
|
|
11107
|
+
const dir = rel ? path6.join(root, rel) : root;
|
|
11108
|
+
let entries;
|
|
11109
|
+
try {
|
|
11110
|
+
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
11111
|
+
} catch {
|
|
11112
|
+
return;
|
|
11113
|
+
}
|
|
11114
|
+
for (const e of entries) {
|
|
11115
|
+
if (out.length >= MAX_FILES_INDEXED) return;
|
|
11116
|
+
if (e.name.startsWith(".") && e.name !== ".env.example") continue;
|
|
11117
|
+
if (IGNORED_DIRS.has(e.name)) continue;
|
|
11118
|
+
const next = rel ? `${rel}/${e.name}` : e.name;
|
|
11119
|
+
if (e.isDirectory()) {
|
|
11120
|
+
await walk(root, next, depth + 1, out);
|
|
11121
|
+
} else if (e.isFile()) {
|
|
11122
|
+
out.push(next);
|
|
11123
|
+
}
|
|
11124
|
+
}
|
|
11125
|
+
}
|
|
11126
|
+
function score(s2, query) {
|
|
11127
|
+
if (!query) return s2.length;
|
|
11128
|
+
const ql = query.toLowerCase();
|
|
11129
|
+
const sl = s2.toLowerCase();
|
|
11130
|
+
let si = 0;
|
|
11131
|
+
let firstHit = -1;
|
|
11132
|
+
let lastHit = -1;
|
|
11133
|
+
for (let qi = 0; qi < ql.length; qi++) {
|
|
11134
|
+
const c = ql.charCodeAt(qi);
|
|
11135
|
+
while (si < sl.length && sl.charCodeAt(si) !== c) si++;
|
|
11136
|
+
if (si >= sl.length) return null;
|
|
11137
|
+
if (firstHit < 0) firstHit = si;
|
|
11138
|
+
lastHit = si;
|
|
11139
|
+
si++;
|
|
11140
|
+
}
|
|
11141
|
+
const span = lastHit - firstHit;
|
|
11142
|
+
return span * 100 + firstHit * 2 + s2.length;
|
|
10840
11143
|
}
|
|
10841
|
-
function
|
|
10842
|
-
const
|
|
10843
|
-
if (!
|
|
10844
|
-
const
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
11144
|
+
async function searchFiles(root, query, limit = 8) {
|
|
11145
|
+
const all = await loadIndex(root);
|
|
11146
|
+
if (!query) return all.slice(0, limit);
|
|
11147
|
+
const scored = [];
|
|
11148
|
+
for (const f of all) {
|
|
11149
|
+
const sc = score(f, query);
|
|
11150
|
+
if (sc !== null) scored.push({ path: f, score: sc });
|
|
11151
|
+
}
|
|
11152
|
+
scored.sort((a, b) => a.score - b.score);
|
|
11153
|
+
return scored.slice(0, limit).map((x) => x.path);
|
|
10851
11154
|
}
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
11155
|
+
|
|
11156
|
+
// src/hooks/use-file-search.ts
|
|
11157
|
+
function detectAtToken(buffer, cursor) {
|
|
11158
|
+
let i = cursor - 1;
|
|
11159
|
+
while (i >= 0) {
|
|
11160
|
+
const ch = buffer.charCodeAt(i);
|
|
11161
|
+
if (ch === 64) {
|
|
11162
|
+
if (i === 0 || /\s/.test(buffer[i - 1] ?? "")) {
|
|
11163
|
+
return { start: i, end: cursor, query: buffer.slice(i + 1, cursor) };
|
|
11164
|
+
}
|
|
11165
|
+
return null;
|
|
11166
|
+
}
|
|
11167
|
+
if (ch === 32 || ch === 9 || ch === 10) return null;
|
|
11168
|
+
i--;
|
|
11169
|
+
}
|
|
11170
|
+
return null;
|
|
11171
|
+
}
|
|
11172
|
+
function useFileSearch(options) {
|
|
11173
|
+
const { state, dispatch, projectRoot, builderRef, draftRef, setDraft, tokenPreviewsRef } = options;
|
|
10861
11174
|
useEffect(() => {
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
11175
|
+
const detected = detectAtToken(state.buffer, state.cursor);
|
|
11176
|
+
if (!detected) {
|
|
11177
|
+
if (state.picker.open) dispatch({ type: "pickerClose" });
|
|
11178
|
+
return;
|
|
11179
|
+
}
|
|
11180
|
+
if (!state.picker.open || state.picker.query !== detected.query) {
|
|
11181
|
+
dispatch({ type: "pickerOpen", query: detected.query });
|
|
11182
|
+
}
|
|
11183
|
+
let cancelled = false;
|
|
11184
|
+
searchFiles(projectRoot, detected.query, 8).then((matches) => {
|
|
11185
|
+
if (!cancelled) {
|
|
11186
|
+
dispatch({ type: "pickerSetMatches", query: detected.query, matches });
|
|
11187
|
+
}
|
|
11188
|
+
}).catch(() => void 0);
|
|
11189
|
+
return () => {
|
|
11190
|
+
cancelled = true;
|
|
11191
|
+
};
|
|
11192
|
+
}, [state.buffer, state.cursor, projectRoot]);
|
|
11193
|
+
const acceptPickerSelection = async () => {
|
|
11194
|
+
const { open, matches, selected } = state.picker;
|
|
11195
|
+
if (!open || matches.length === 0) return;
|
|
11196
|
+
const picked = matches[selected];
|
|
11197
|
+
if (!picked) return;
|
|
11198
|
+
const builder = builderRef.current;
|
|
11199
|
+
if (!builder) return;
|
|
11200
|
+
const draft = draftRef.current;
|
|
11201
|
+
const tok = detectAtToken(draft.buffer, draft.cursor);
|
|
11202
|
+
if (!tok) {
|
|
11203
|
+
dispatch({ type: "pickerClose" });
|
|
11204
|
+
return;
|
|
11205
|
+
}
|
|
11206
|
+
const absPath = path6.isAbsolute(picked) ? picked : path6.join(projectRoot, picked);
|
|
11207
|
+
try {
|
|
11208
|
+
const data = await fsp.readFile(absPath, "utf8");
|
|
11209
|
+
const token = await builder.registerFile({
|
|
11210
|
+
kind: "file",
|
|
11211
|
+
data,
|
|
11212
|
+
meta: { filename: picked, label: picked }
|
|
11213
|
+
});
|
|
11214
|
+
tokenPreviewsRef.current.set(token, data);
|
|
11215
|
+
const before = draft.buffer.slice(0, tok.start);
|
|
11216
|
+
const after = draft.buffer.slice(tok.end);
|
|
11217
|
+
const next = `${before}${token}${after}`;
|
|
11218
|
+
setDraft(next, tok.start + token.length);
|
|
11219
|
+
dispatch({ type: "pickerClose" });
|
|
11220
|
+
} catch (err) {
|
|
11221
|
+
dispatch({
|
|
11222
|
+
type: "addEntry",
|
|
11223
|
+
entry: {
|
|
11224
|
+
kind: "error",
|
|
11225
|
+
text: `Attach failed: ${toErrorMessage(err)}`
|
|
11226
|
+
}
|
|
11227
|
+
});
|
|
11228
|
+
dispatch({ type: "pickerClose" });
|
|
11229
|
+
}
|
|
11230
|
+
};
|
|
11231
|
+
return { onPickerEnter: acceptPickerSelection };
|
|
10866
11232
|
}
|
|
10867
11233
|
function useStatuslineState(opts) {
|
|
10868
11234
|
const [liveModel, setLiveModel] = useState(opts.model);
|
|
@@ -10959,8 +11325,12 @@ function useTuiControllers({
|
|
|
10959
11325
|
};
|
|
10960
11326
|
}, [onPanelOpen, dispatch]);
|
|
10961
11327
|
}
|
|
10962
|
-
function useBrainEvents(events, dispatch) {
|
|
11328
|
+
function useBrainEvents(events, dispatch, getSessionId) {
|
|
10963
11329
|
useEffect(() => {
|
|
11330
|
+
const isCurrentSession = (sessionId) => {
|
|
11331
|
+
const current = getSessionId?.();
|
|
11332
|
+
return !sessionId || !current || sessionId === current;
|
|
11333
|
+
};
|
|
10964
11334
|
const requestSummary = (request) => `${request.source}: ${request.question}`.slice(0, 80);
|
|
10965
11335
|
const addBrainEntry = (status, payload) => {
|
|
10966
11336
|
const p = payload;
|
|
@@ -10981,13 +11351,21 @@ function useBrainEvents(events, dispatch) {
|
|
|
10981
11351
|
}
|
|
10982
11352
|
dispatch({ type: "addEntry", entry: { kind: "brain", status, source: p.request.source, risk: p.request.risk, question: p.request.question, decision, rationale: p.decision.rationale } });
|
|
10983
11353
|
};
|
|
10984
|
-
const offRequested = events.on("brain.decision_requested", ({ request }) => {
|
|
11354
|
+
const offRequested = events.on("brain.decision_requested", ({ sessionId, request }) => {
|
|
11355
|
+
if (!isCurrentSession(sessionId)) return;
|
|
10985
11356
|
dispatch({ type: "brainStatus", state: "deciding", source: request.source, risk: request.risk, summary: requestSummary(request) });
|
|
10986
11357
|
});
|
|
10987
|
-
const offAnswered = events.on("brain.decision_answered", (payload) =>
|
|
10988
|
-
|
|
10989
|
-
|
|
11358
|
+
const offAnswered = events.on("brain.decision_answered", (payload) => {
|
|
11359
|
+
if (isCurrentSession(payload.sessionId)) addBrainEntry("answered", payload);
|
|
11360
|
+
});
|
|
11361
|
+
const offAskHuman = events.on("brain.decision_ask_human", (payload) => {
|
|
11362
|
+
if (isCurrentSession(payload.sessionId)) addBrainEntry("ask_human", payload);
|
|
11363
|
+
});
|
|
11364
|
+
const offDenied = events.on("brain.decision_denied", (payload) => {
|
|
11365
|
+
if (isCurrentSession(payload.sessionId)) addBrainEntry("denied", payload);
|
|
11366
|
+
});
|
|
10990
11367
|
const offIntervention = events.on("brain.intervention", (payload) => {
|
|
11368
|
+
if (!isCurrentSession(payload.sessionId)) return;
|
|
10991
11369
|
const decision = payload.intervened ? `steered the agent (${payload.kind.replace(/_/g, " ")})` : "observed \u2014 no action needed";
|
|
10992
11370
|
const rationale = payload.decision.type === "answer" ? payload.decision.rationale : void 0;
|
|
10993
11371
|
dispatch({
|
|
@@ -11010,7 +11388,7 @@ function useBrainEvents(events, dispatch) {
|
|
|
11010
11388
|
offDenied();
|
|
11011
11389
|
offIntervention();
|
|
11012
11390
|
};
|
|
11013
|
-
}, [events, dispatch]);
|
|
11391
|
+
}, [events, dispatch, getSessionId]);
|
|
11014
11392
|
}
|
|
11015
11393
|
var STREAM_COLORS2 = ["cyan", "magenta", "yellow", "green", "blue"];
|
|
11016
11394
|
function labelFor2(labelsRef, id, name) {
|
|
@@ -11025,7 +11403,7 @@ function labelFor2(labelsRef, id, name) {
|
|
|
11025
11403
|
m.set(id, v);
|
|
11026
11404
|
return v;
|
|
11027
11405
|
}
|
|
11028
|
-
function useSubagentEvents(events, dispatch, setActiveMaxContext) {
|
|
11406
|
+
function useSubagentEvents(events, dispatch, setActiveMaxContext, getSessionId) {
|
|
11029
11407
|
const labelsRef = useRef(/* @__PURE__ */ new Map());
|
|
11030
11408
|
const lbl = useCallback(
|
|
11031
11409
|
(id, name) => labelFor2(labelsRef, id, name),
|
|
@@ -11033,7 +11411,12 @@ function useSubagentEvents(events, dispatch, setActiveMaxContext) {
|
|
|
11033
11411
|
// labelsRef is a stable ref
|
|
11034
11412
|
);
|
|
11035
11413
|
useEffect(() => {
|
|
11414
|
+
const isCurrentSession = (sessionId) => {
|
|
11415
|
+
const current = getSessionId?.();
|
|
11416
|
+
return !sessionId || !current || sessionId === current;
|
|
11417
|
+
};
|
|
11036
11418
|
const offSpawned = events.on("subagent.spawned", (e) => {
|
|
11419
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11037
11420
|
const l = lbl(e.subagentId, e.name);
|
|
11038
11421
|
dispatch({ type: "fleetSpawn", id: e.subagentId, name: e.name, provider: e.provider, model: e.model, transcriptPath: e.transcriptPath });
|
|
11039
11422
|
const where = e.provider && e.model ? `${e.provider}/${e.model}` : "spawned";
|
|
@@ -11041,12 +11424,14 @@ function useSubagentEvents(events, dispatch, setActiveMaxContext) {
|
|
|
11041
11424
|
dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u25B6", text: `${where}${desc}` } });
|
|
11042
11425
|
});
|
|
11043
11426
|
const offStarted = events.on("subagent.task_started", (e) => {
|
|
11427
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11044
11428
|
const l = lbl(e.subagentId);
|
|
11045
11429
|
dispatch({ type: "fleetStart", id: e.subagentId, taskId: e.taskId });
|
|
11046
11430
|
const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
|
|
11047
11431
|
dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u25CF", text: `task started${desc}` } });
|
|
11048
11432
|
});
|
|
11049
11433
|
const offCompleted = events.on("subagent.task_completed", (e) => {
|
|
11434
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11050
11435
|
const l = lbl(e.subagentId);
|
|
11051
11436
|
const errKind = e.error?.kind;
|
|
11052
11437
|
dispatch({ type: "fleetDone", id: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, failureReason: errKind });
|
|
@@ -11058,17 +11443,20 @@ function useSubagentEvents(events, dispatch, setActiveMaxContext) {
|
|
|
11058
11443
|
dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon, text: `${e.status} (${e.iterations} iter \xB7 ${e.toolCalls} tools \xB7 ${secs}s)${errChip}${errMsgTail}` } });
|
|
11059
11444
|
});
|
|
11060
11445
|
const offBudgetWarning = events.on("subagent.budget_warning", (e) => {
|
|
11446
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11061
11447
|
const l = lbl(e.subagentId);
|
|
11062
11448
|
dispatch({ type: "fleetBudgetWarning", id: e.subagentId, kind: e.kind, used: e.used, limit: e.limit });
|
|
11063
11449
|
const timeoutSuffix = e.kind === "timeout" ? " (subagent continues running)" : " \u2014 extending";
|
|
11064
11450
|
dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u26A1", text: `hitting ${e.kind} limit (${e.used}/${e.limit})${timeoutSuffix}` } });
|
|
11065
11451
|
});
|
|
11066
11452
|
const offBudgetExtended = events.on("subagent.budget_extended", (e) => {
|
|
11453
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11067
11454
|
const l = lbl(e.subagentId);
|
|
11068
11455
|
dispatch({ type: "fleetBudgetExtended", id: e.subagentId, totalExtensions: e.totalExtensions });
|
|
11069
11456
|
dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u26A1", text: `extended ${e.kind} \u2192 ${e.newLimit} (\xD7${e.totalExtensions})` } });
|
|
11070
11457
|
});
|
|
11071
11458
|
const offIterationSummary = events.on("subagent.iteration_summary", (e) => {
|
|
11459
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11072
11460
|
const l = lbl(e.subagentId);
|
|
11073
11461
|
const costStr = e.costUsd > 0 ? ` \xB7 ${e.costUsd.toFixed(4)}` : "";
|
|
11074
11462
|
const toolStr = e.currentTool ? ` \xB7 doing ${e.currentTool}` : "";
|
|
@@ -11076,22 +11464,39 @@ function useSubagentEvents(events, dispatch, setActiveMaxContext) {
|
|
|
11076
11464
|
dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u{1F4AC}", text: `L${e.iteration} \xB7 ${e.toolCalls} tools${costStr}${toolStr}${partial}` } });
|
|
11077
11465
|
});
|
|
11078
11466
|
const offCtxPct = events.on("subagent.ctx_pct", (e) => {
|
|
11467
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11079
11468
|
dispatch({ type: "fleetCtxPct", id: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext });
|
|
11080
11469
|
});
|
|
11081
11470
|
const offConcurrencyChanged = events.on("concurrency.changed", (e) => {
|
|
11082
|
-
const { n } = e;
|
|
11471
|
+
const { n, sessionId } = e;
|
|
11472
|
+
if (!isCurrentSession(sessionId)) return;
|
|
11083
11473
|
if (typeof n === "number" && n > 0) {
|
|
11084
11474
|
dispatch({ type: "fleetConcurrency", n });
|
|
11085
11475
|
}
|
|
11086
11476
|
});
|
|
11087
11477
|
const offLeaderCtxPct = events.on("ctx.pct", (e) => {
|
|
11478
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11088
11479
|
setActiveMaxContext(e.maxContext);
|
|
11089
11480
|
dispatch({ type: "leaderCtxPct", load: e.load, tokens: e.tokens, maxContext: e.maxContext });
|
|
11090
11481
|
});
|
|
11091
11482
|
const offLeaderMaxContext = events.on("ctx.max_context", (e) => {
|
|
11483
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11092
11484
|
if (e.maxContext > 0) setActiveMaxContext(e.maxContext);
|
|
11093
11485
|
});
|
|
11486
|
+
const offCompactionFired = events.on("compaction.fired", (e) => {
|
|
11487
|
+
const saved = e.report.before - e.report.after;
|
|
11488
|
+
let label;
|
|
11489
|
+
if (saved > 0) {
|
|
11490
|
+
label = `\u26A1 compact: ${e.report.before} \u2192 ${e.report.after} tokens (\u2212${saved}) [${e.level}]`;
|
|
11491
|
+
} else if (saved < 0) {
|
|
11492
|
+
label = `\u26A0\uFE0F compact: context GREW by ${-saved} tokens [${e.level}]`;
|
|
11493
|
+
} else {
|
|
11494
|
+
label = "\u26A1 compact: no reduction needed";
|
|
11495
|
+
}
|
|
11496
|
+
dispatch({ type: "addEntry", entry: { kind: "warn", text: label } });
|
|
11497
|
+
});
|
|
11094
11498
|
const offTool = events.on("subagent.tool_executed", (e) => {
|
|
11499
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11095
11500
|
dispatch({ type: "fleetTool", id: e.subagentId, name: e.name, ok: e.ok, durationMs: e.durationMs, outputBytes: e.outputBytes });
|
|
11096
11501
|
dispatch({ type: "fleetToolEnd", id: e.subagentId });
|
|
11097
11502
|
});
|
|
@@ -11106,9 +11511,10 @@ function useSubagentEvents(events, dispatch, setActiveMaxContext) {
|
|
|
11106
11511
|
offConcurrencyChanged();
|
|
11107
11512
|
offLeaderCtxPct();
|
|
11108
11513
|
offLeaderMaxContext();
|
|
11514
|
+
offCompactionFired();
|
|
11109
11515
|
offTool();
|
|
11110
11516
|
};
|
|
11111
|
-
}, [events, dispatch, setActiveMaxContext, lbl]);
|
|
11517
|
+
}, [events, dispatch, setActiveMaxContext, getSessionId, lbl]);
|
|
11112
11518
|
}
|
|
11113
11519
|
|
|
11114
11520
|
// src/hooks/use-tui-event-bridge.ts
|
|
@@ -11117,17 +11523,23 @@ function useTuiEventBridge({
|
|
|
11117
11523
|
dispatch,
|
|
11118
11524
|
stateRef,
|
|
11119
11525
|
setActiveMaxContext,
|
|
11526
|
+
getSessionId,
|
|
11120
11527
|
subscribeAutoPhase,
|
|
11121
11528
|
onClearHistory
|
|
11122
11529
|
}) {
|
|
11123
|
-
useSubagentEvents(events, dispatch, setActiveMaxContext);
|
|
11124
|
-
useSessionEvents(events, dispatch, onClearHistory);
|
|
11125
|
-
useBrainEvents(events, dispatch);
|
|
11126
|
-
useAutoPhaseEvents(subscribeAutoPhase, dispatch, stateRef);
|
|
11530
|
+
useSubagentEvents(events, dispatch, setActiveMaxContext, getSessionId);
|
|
11531
|
+
useSessionEvents(events, dispatch, onClearHistory, getSessionId);
|
|
11532
|
+
useBrainEvents(events, dispatch, getSessionId);
|
|
11533
|
+
useAutoPhaseEvents(subscribeAutoPhase, dispatch, stateRef, getSessionId);
|
|
11127
11534
|
}
|
|
11128
|
-
function useSessionEvents(events, dispatch, onClearHistory) {
|
|
11535
|
+
function useSessionEvents(events, dispatch, onClearHistory, getSessionId) {
|
|
11129
11536
|
useEffect(() => {
|
|
11537
|
+
const isCurrentSession = (sessionId) => {
|
|
11538
|
+
const current = getSessionId?.();
|
|
11539
|
+
return !sessionId || !current || sessionId === current;
|
|
11540
|
+
};
|
|
11130
11541
|
const offCheckpoint = events.on("checkpoint.written", (e) => {
|
|
11542
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11131
11543
|
dispatch({
|
|
11132
11544
|
type: "checkpointReceived",
|
|
11133
11545
|
cp: {
|
|
@@ -11138,7 +11550,8 @@ function useSessionEvents(events, dispatch, onClearHistory) {
|
|
|
11138
11550
|
}
|
|
11139
11551
|
});
|
|
11140
11552
|
});
|
|
11141
|
-
const offRewound = events.on("session.rewound", () => {
|
|
11553
|
+
const offRewound = events.on("session.rewound", (e) => {
|
|
11554
|
+
if (!isCurrentSession(e.sessionId)) return;
|
|
11142
11555
|
dispatch({ type: "sessionRewound", toPromptIndex: 0 });
|
|
11143
11556
|
dispatch({ type: "clearHistory" });
|
|
11144
11557
|
dispatch({ type: "resetContextChip" });
|
|
@@ -11148,12 +11561,18 @@ function useSessionEvents(events, dispatch, onClearHistory) {
|
|
|
11148
11561
|
offCheckpoint();
|
|
11149
11562
|
offRewound();
|
|
11150
11563
|
};
|
|
11151
|
-
}, [events, dispatch, onClearHistory]);
|
|
11564
|
+
}, [events, dispatch, onClearHistory, getSessionId]);
|
|
11152
11565
|
}
|
|
11153
|
-
function useAutoPhaseEvents(subscribeAutoPhase, dispatch, stateRef) {
|
|
11566
|
+
function useAutoPhaseEvents(subscribeAutoPhase, dispatch, stateRef, getSessionId) {
|
|
11154
11567
|
useEffect(() => {
|
|
11155
11568
|
if (!subscribeAutoPhase) return;
|
|
11569
|
+
const isCurrentSession = (sessionId) => {
|
|
11570
|
+
const current = getSessionId?.();
|
|
11571
|
+
return !sessionId || !current || sessionId === current;
|
|
11572
|
+
};
|
|
11156
11573
|
const handler = (event, payload) => {
|
|
11574
|
+
const sessionId = payload && typeof payload === "object" && "sessionId" in payload ? payload.sessionId : void 0;
|
|
11575
|
+
if (!isCurrentSession(sessionId)) return;
|
|
11157
11576
|
switch (event) {
|
|
11158
11577
|
case "phase.started": {
|
|
11159
11578
|
const p = payload;
|
|
@@ -11349,9 +11768,9 @@ function useAutoPhaseEvents(subscribeAutoPhase, dispatch, stateRef) {
|
|
|
11349
11768
|
}
|
|
11350
11769
|
};
|
|
11351
11770
|
return subscribeAutoPhase(handler);
|
|
11352
|
-
}, [subscribeAutoPhase, dispatch, stateRef]);
|
|
11771
|
+
}, [subscribeAutoPhase, dispatch, stateRef, getSessionId]);
|
|
11353
11772
|
}
|
|
11354
|
-
var
|
|
11773
|
+
var USAGE2 = "Usage:\n /kill \u2014 list active processes + breaker state\n /kill list \u2014 same as /kill\n /kill all \u2014 kill all tracked processes (SIGTERM \u2192 SIGKILL)\n /kill force \u2014 kill all with SIGKILL immediately\n /kill reset \u2014 reset the circuit breaker to closed\n /kill <pid> \u2014 kill a specific process by PID";
|
|
11355
11774
|
function createKillSlashCommand() {
|
|
11356
11775
|
return {
|
|
11357
11776
|
name: "kill",
|
|
@@ -11361,7 +11780,7 @@ function createKillSlashCommand() {
|
|
|
11361
11780
|
const parts = trimmed.split(/\s+/);
|
|
11362
11781
|
const sub = parts[0]?.toLowerCase() ?? "";
|
|
11363
11782
|
if (sub === "" || sub === "list") {
|
|
11364
|
-
return { message:
|
|
11783
|
+
return { message: renderList2() };
|
|
11365
11784
|
}
|
|
11366
11785
|
if (sub === "all") {
|
|
11367
11786
|
const pids = getProcessRegistry().killAll();
|
|
@@ -11390,11 +11809,11 @@ function createKillSlashCommand() {
|
|
|
11390
11809
|
return { message: `Process ${pid} not found in registry.` };
|
|
11391
11810
|
}
|
|
11392
11811
|
return { message: `Unknown subcommand "${sub}".
|
|
11393
|
-
${
|
|
11812
|
+
${USAGE2}` };
|
|
11394
11813
|
}
|
|
11395
11814
|
};
|
|
11396
11815
|
}
|
|
11397
|
-
function
|
|
11816
|
+
function renderList2() {
|
|
11398
11817
|
const registry = getProcessRegistry();
|
|
11399
11818
|
const stats = registry.stats();
|
|
11400
11819
|
const all = registry.list();
|
|
@@ -11445,146 +11864,52 @@ var ANSI_RE = new RegExp(
|
|
|
11445
11864
|
"\\x1b"
|
|
11446
11865
|
].join("|"),
|
|
11447
11866
|
"g"
|
|
11448
|
-
);
|
|
11449
|
-
function feedPaste(accum, input) {
|
|
11450
|
-
if (accum === null && !input.includes(BEGIN)) {
|
|
11451
|
-
if (input.startsWith("[") && !input.startsWith(BEGIN) && !input.startsWith(END)) {
|
|
11452
|
-
if (PARTIAL_ANSI_RE.test(input)) {
|
|
11453
|
-
return { accum: "", complete: null };
|
|
11454
|
-
}
|
|
11455
|
-
return null;
|
|
11456
|
-
}
|
|
11457
|
-
return null;
|
|
11458
|
-
}
|
|
11459
|
-
const piece = input.replace(BEGIN_RE, "").replace(END_RE, "").replace(ANSI_RE, "");
|
|
11460
|
-
const next = (accum ?? "") + piece;
|
|
11461
|
-
if (input.includes(END)) return { accum: null, complete: next };
|
|
11462
|
-
return { accum: next, complete: null };
|
|
11463
|
-
}
|
|
11464
|
-
function createPsSlashCommand() {
|
|
11465
|
-
return {
|
|
11466
|
-
name: "ps",
|
|
11467
|
-
description: "List all active bash/exec processes tracked by the process registry.",
|
|
11468
|
-
async run(_args) {
|
|
11469
|
-
return { message: renderList2() };
|
|
11470
|
-
}
|
|
11471
|
-
};
|
|
11472
|
-
}
|
|
11473
|
-
function renderList2() {
|
|
11474
|
-
const registry = getProcessRegistry();
|
|
11475
|
-
const stats = registry.stats();
|
|
11476
|
-
const all = registry.list();
|
|
11477
|
-
if (all.length === 0) return "No active processes.";
|
|
11478
|
-
const breaker = stats.breaker;
|
|
11479
|
-
const stateLabel = breaker.state === "closed" ? "\u{1F7E2} closed" : breaker.state === "half-open" ? "\u{1F7E1} half-open" : `\u{1F534} open`;
|
|
11480
|
-
const now = Date.now();
|
|
11481
|
-
const lines = [
|
|
11482
|
-
`Active processes (${all.length}) \u2014 breaker ${stateLabel}`,
|
|
11483
|
-
` failure=${breaker.consecutiveFailures}/5 slow=${breaker.slowCallsInWindow}/3 rate=${breaker.callsInWindow}/30`,
|
|
11484
|
-
""
|
|
11485
|
-
];
|
|
11486
|
-
for (const p of all) {
|
|
11487
|
-
const age = ((now - p.startedAt) / 1e3).toFixed(1);
|
|
11488
|
-
const killedTag = p.killed ? " [killed]" : "";
|
|
11489
|
-
const cmd = p.command.length > 80 ? p.command.slice(0, 77) + "\u2026" : p.command;
|
|
11490
|
-
lines.push(` ${p.pid} ${p.name} ${age}s ${cmd}${killedTag}`);
|
|
11491
|
-
}
|
|
11492
|
-
return lines.join("\n");
|
|
11493
|
-
}
|
|
11494
|
-
|
|
11495
|
-
// src/queue-slash.ts
|
|
11496
|
-
var USAGE2 = "Usage:\n /queue \u2014 list pending messages\n /queue list \u2014 same as /queue\n /queue clear \u2014 drop all pending messages\n /queue delete N M\u2026 \u2014 drop messages at 1-based positions\n /queue picker on|off \u2014 toggle the mid-run send-mode picker (queue/btw/steer)\n /queue picker \u2014 show whether the picker is on";
|
|
11497
|
-
function createQueueSlashCommand(deps) {
|
|
11498
|
-
return {
|
|
11499
|
-
name: "queue",
|
|
11500
|
-
description: "List, clear, or delete pending messages typed while the agent was running.",
|
|
11501
|
-
async run(args) {
|
|
11502
|
-
const out = handleQueueCommand(args, deps);
|
|
11503
|
-
return { message: out };
|
|
11504
|
-
}
|
|
11505
|
-
};
|
|
11506
|
-
}
|
|
11507
|
-
function handleQueueCommand(args, deps) {
|
|
11508
|
-
const trimmed = args.trim();
|
|
11509
|
-
const [sub, ...rest] = trimmed.split(/\s+/);
|
|
11510
|
-
const subcommand = sub?.toLowerCase() ?? "";
|
|
11511
|
-
if (subcommand === "" || subcommand === "list") {
|
|
11512
|
-
return renderList3(deps.getQueue());
|
|
11513
|
-
}
|
|
11514
|
-
if (subcommand === "picker") {
|
|
11515
|
-
if (!deps.getPickerEnabled || !deps.setPickerEnabled) {
|
|
11516
|
-
return "Send-mode picker toggle is unavailable in this session.";
|
|
11517
|
-
}
|
|
11518
|
-
const arg = rest[0]?.toLowerCase() ?? "";
|
|
11519
|
-
if (arg === "" || arg === "status") {
|
|
11520
|
-
return `Mid-run send-mode picker is ${deps.getPickerEnabled() ? "on" : "off"}.`;
|
|
11521
|
-
}
|
|
11522
|
-
if (arg === "on" || arg === "off") {
|
|
11523
|
-
const next = arg === "on";
|
|
11524
|
-
deps.setPickerEnabled(next);
|
|
11525
|
-
return next ? "Mid-run send-mode picker: on \u2014 plain messages typed while busy ask queue/btw/steer." : "Mid-run send-mode picker: off \u2014 plain messages typed while busy are queued silently.";
|
|
11526
|
-
}
|
|
11527
|
-
return "Usage: /queue picker on|off";
|
|
11528
|
-
}
|
|
11529
|
-
if (subcommand === "clear") {
|
|
11530
|
-
const before = deps.getQueue().length;
|
|
11531
|
-
if (before === 0) return "Queue is already empty.";
|
|
11532
|
-
deps.clear();
|
|
11533
|
-
return `Cleared ${before} queued message${before === 1 ? "" : "s"}.`;
|
|
11534
|
-
}
|
|
11535
|
-
if (subcommand === "delete" || subcommand === "del" || subcommand === "rm") {
|
|
11536
|
-
if (rest.length === 0) return "Usage: /queue delete <position> [<position>\u2026]";
|
|
11537
|
-
const queue = deps.getQueue();
|
|
11538
|
-
if (queue.length === 0) return "Queue is empty \u2014 nothing to delete.";
|
|
11539
|
-
const parsed = [];
|
|
11540
|
-
const invalid = [];
|
|
11541
|
-
const outOfRange = [];
|
|
11542
|
-
for (const tok of rest) {
|
|
11543
|
-
if (!/^\d+$/.test(tok)) {
|
|
11544
|
-
invalid.push(tok);
|
|
11545
|
-
continue;
|
|
11546
|
-
}
|
|
11547
|
-
const n = Number.parseInt(tok, 10);
|
|
11548
|
-
if (n < 1 || n > queue.length) {
|
|
11549
|
-
outOfRange.push(n);
|
|
11550
|
-
continue;
|
|
11551
|
-
}
|
|
11552
|
-
parsed.push(n);
|
|
11553
|
-
}
|
|
11554
|
-
const uniqueValid = [...new Set(parsed)];
|
|
11555
|
-
if (uniqueValid.length === 0) {
|
|
11556
|
-
const parts2 = ["No valid positions to delete."];
|
|
11557
|
-
if (invalid.length > 0) parts2.push(`Invalid: ${invalid.join(", ")}.`);
|
|
11558
|
-
if (outOfRange.length > 0)
|
|
11559
|
-
parts2.push(`Out of range (queue has ${queue.length}): ${outOfRange.join(", ")}.`);
|
|
11560
|
-
return parts2.join(" ");
|
|
11867
|
+
);
|
|
11868
|
+
function feedPaste(accum, input) {
|
|
11869
|
+
if (accum === null && !input.includes(BEGIN)) {
|
|
11870
|
+
if (input.startsWith("[") && !input.startsWith(BEGIN) && !input.startsWith(END)) {
|
|
11871
|
+
if (PARTIAL_ANSI_RE.test(input)) {
|
|
11872
|
+
return { accum: "", complete: null };
|
|
11873
|
+
}
|
|
11874
|
+
return null;
|
|
11561
11875
|
}
|
|
11562
|
-
|
|
11563
|
-
const parts = [
|
|
11564
|
-
`Deleted ${uniqueValid.length} of ${queue.length} (positions ${uniqueValid.sort((a, b) => a - b).join(", ")}).`
|
|
11565
|
-
];
|
|
11566
|
-
if (invalid.length > 0) parts.push(`Ignored invalid: ${invalid.join(", ")}.`);
|
|
11567
|
-
if (outOfRange.length > 0) parts.push(`Ignored out of range: ${outOfRange.join(", ")}.`);
|
|
11568
|
-
return parts.join(" ");
|
|
11876
|
+
return null;
|
|
11569
11877
|
}
|
|
11570
|
-
|
|
11571
|
-
|
|
11878
|
+
const piece = input.replace(BEGIN_RE, "").replace(END_RE, "").replace(ANSI_RE, "");
|
|
11879
|
+
const next = (accum ?? "") + piece;
|
|
11880
|
+
if (input.includes(END)) return { accum: null, complete: next };
|
|
11881
|
+
return { accum: next, complete: null };
|
|
11572
11882
|
}
|
|
11573
|
-
function
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11883
|
+
function createPsSlashCommand() {
|
|
11884
|
+
return {
|
|
11885
|
+
name: "ps",
|
|
11886
|
+
description: "List all active bash/exec processes tracked by the process registry.",
|
|
11887
|
+
async run(_args) {
|
|
11888
|
+
return { message: renderList3() };
|
|
11889
|
+
}
|
|
11890
|
+
};
|
|
11891
|
+
}
|
|
11892
|
+
function renderList3() {
|
|
11893
|
+
const registry = getProcessRegistry();
|
|
11894
|
+
const stats = registry.stats();
|
|
11895
|
+
const all = registry.list();
|
|
11896
|
+
if (all.length === 0) return "No active processes.";
|
|
11897
|
+
const breaker = stats.breaker;
|
|
11898
|
+
const stateLabel = breaker.state === "closed" ? "\u{1F7E2} closed" : breaker.state === "half-open" ? "\u{1F7E1} half-open" : `\u{1F534} open`;
|
|
11899
|
+
const now = Date.now();
|
|
11900
|
+
const lines = [
|
|
11901
|
+
`Active processes (${all.length}) \u2014 breaker ${stateLabel}`,
|
|
11902
|
+
` failure=${breaker.consecutiveFailures}/5 slow=${breaker.slowCallsInWindow}/3 rate=${breaker.callsInWindow}/30`,
|
|
11903
|
+
""
|
|
11904
|
+
];
|
|
11905
|
+
for (const p of all) {
|
|
11906
|
+
const age = ((now - p.startedAt) / 1e3).toFixed(1);
|
|
11907
|
+
const killedTag = p.killed ? " [killed]" : "";
|
|
11908
|
+
const cmd = p.command.length > 80 ? p.command.slice(0, 77) + "\u2026" : p.command;
|
|
11909
|
+
lines.push(` ${p.pid} ${p.name} ${age}s ${cmd}${killedTag}`);
|
|
11581
11910
|
}
|
|
11582
11911
|
return lines.join("\n");
|
|
11583
11912
|
}
|
|
11584
|
-
function oneLine(s2, max) {
|
|
11585
|
-
const collapsed = s2.replace(/\s+/g, " ").trim();
|
|
11586
|
-
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
11587
|
-
}
|
|
11588
11913
|
|
|
11589
11914
|
// src/slash-command-search.ts
|
|
11590
11915
|
var CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
|
|
@@ -14174,8 +14499,8 @@ function App({
|
|
|
14174
14499
|
let bundledDir;
|
|
14175
14500
|
try {
|
|
14176
14501
|
const req = createRequire(import.meta.url);
|
|
14177
|
-
bundledDir =
|
|
14178
|
-
|
|
14502
|
+
bundledDir = path6.join(
|
|
14503
|
+
path6.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
14179
14504
|
"data",
|
|
14180
14505
|
"prompts"
|
|
14181
14506
|
);
|
|
@@ -14263,28 +14588,26 @@ function App({
|
|
|
14263
14588
|
if (builderRef.current === null) {
|
|
14264
14589
|
builderRef.current = new InputBuilder({ store: attachments });
|
|
14265
14590
|
}
|
|
14266
|
-
const pasteAccumRef = useRef(null);
|
|
14267
|
-
const pasteFlushTimerRef = useRef(null);
|
|
14268
14591
|
const activeCtrlRef = useRef(null);
|
|
14269
14592
|
const exitRequestedRef = useRef(false);
|
|
14270
14593
|
const inputGateRef = useRef(false);
|
|
14271
14594
|
const lastEnterAtRef = useRef(0);
|
|
14272
14595
|
const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
|
|
14273
14596
|
const projectName = React5.useMemo(() => {
|
|
14274
|
-
const base =
|
|
14275
|
-
return base && base !==
|
|
14597
|
+
const base = path6.basename(projectRoot);
|
|
14598
|
+
return base && base !== path6.sep ? base : void 0;
|
|
14276
14599
|
}, [projectRoot]);
|
|
14277
14600
|
const [workingDirChip, setWorkingDirChip] = React5.useState(() => {
|
|
14278
14601
|
const ctx = agent.ctx;
|
|
14279
14602
|
if (ctx.workingDir && ctx.workingDir !== projectRoot) {
|
|
14280
|
-
return
|
|
14603
|
+
return path6.relative(projectRoot, ctx.workingDir) || ".";
|
|
14281
14604
|
}
|
|
14282
14605
|
return void 0;
|
|
14283
14606
|
});
|
|
14284
14607
|
React5.useEffect(() => {
|
|
14285
14608
|
const ctx = agent.ctx;
|
|
14286
14609
|
return ctx.onWorkingDirChanged((newDir) => {
|
|
14287
|
-
const rel =
|
|
14610
|
+
const rel = path6.relative(projectRoot, newDir) || ".";
|
|
14288
14611
|
setWorkingDirChip(rel === "." ? void 0 : rel);
|
|
14289
14612
|
});
|
|
14290
14613
|
}, [agent.ctx, projectRoot]);
|
|
@@ -14711,7 +15034,7 @@ function App({
|
|
|
14711
15034
|
let cancelled = false;
|
|
14712
15035
|
const poll = async () => {
|
|
14713
15036
|
try {
|
|
14714
|
-
const data = await
|
|
15037
|
+
const data = await fsp.readFile(planPath, "utf8");
|
|
14715
15038
|
const parsed = JSON.parse(data);
|
|
14716
15039
|
if (cancelled) return;
|
|
14717
15040
|
if (!Array.isArray(parsed.items)) {
|
|
@@ -14745,7 +15068,7 @@ function App({
|
|
|
14745
15068
|
let cancelled = false;
|
|
14746
15069
|
const poll = async () => {
|
|
14747
15070
|
try {
|
|
14748
|
-
const data = await
|
|
15071
|
+
const data = await fsp.readFile(taskPath, "utf8");
|
|
14749
15072
|
const parsed = JSON.parse(data);
|
|
14750
15073
|
if (cancelled) return;
|
|
14751
15074
|
if (!Array.isArray(parsed.tasks)) {
|
|
@@ -14944,25 +15267,6 @@ function App({
|
|
|
14944
15267
|
React5.useLayoutEffect(() => {
|
|
14945
15268
|
if (state.enhanceBusy || state.enhance != null) eraseLiveRegion();
|
|
14946
15269
|
});
|
|
14947
|
-
useEffect(() => {
|
|
14948
|
-
const detected = detectAtToken(state.buffer, state.cursor);
|
|
14949
|
-
if (!detected) {
|
|
14950
|
-
if (state.picker.open) dispatch({ type: "pickerClose" });
|
|
14951
|
-
return;
|
|
14952
|
-
}
|
|
14953
|
-
if (!state.picker.open || state.picker.query !== detected.query) {
|
|
14954
|
-
dispatch({ type: "pickerOpen", query: detected.query });
|
|
14955
|
-
}
|
|
14956
|
-
let cancelled = false;
|
|
14957
|
-
searchFiles(projectRoot, detected.query, 8).then((matches) => {
|
|
14958
|
-
if (!cancelled) {
|
|
14959
|
-
dispatch({ type: "pickerSetMatches", query: detected.query, matches });
|
|
14960
|
-
}
|
|
14961
|
-
}).catch(() => void 0);
|
|
14962
|
-
return () => {
|
|
14963
|
-
cancelled = true;
|
|
14964
|
-
};
|
|
14965
|
-
}, [state.buffer, state.cursor, projectRoot]);
|
|
14966
15270
|
useEffect(() => {
|
|
14967
15271
|
const trimmed = state.buffer.trimStart();
|
|
14968
15272
|
if (!trimmed.startsWith("/")) {
|
|
@@ -14981,93 +15285,6 @@ function App({
|
|
|
14981
15285
|
dispatch({ type: "slashPickerOpen", query, matches });
|
|
14982
15286
|
}
|
|
14983
15287
|
}, [state.buffer, slashRegistry]);
|
|
14984
|
-
const pasteClipboardImage = async () => {
|
|
14985
|
-
const builder = builderRef.current;
|
|
14986
|
-
if (!builder) return;
|
|
14987
|
-
try {
|
|
14988
|
-
const img = await readClipboardImage();
|
|
14989
|
-
if (!img) {
|
|
14990
|
-
dispatch({
|
|
14991
|
-
type: "addEntry",
|
|
14992
|
-
entry: { kind: "info", text: "No image on the clipboard." }
|
|
14993
|
-
});
|
|
14994
|
-
return;
|
|
14995
|
-
}
|
|
14996
|
-
const token = await builder.registerImage(img.base64, img.mediaType);
|
|
14997
|
-
const kb = (img.bytes / 1024).toFixed(0);
|
|
14998
|
-
tokenPreviewsRef.current.set(token, `image, ${kb} KB`);
|
|
14999
|
-
const { buffer, cursor } = draftRef.current;
|
|
15000
|
-
const next = buffer.slice(0, cursor) + token + buffer.slice(cursor);
|
|
15001
|
-
setDraft(next, cursor + token.length);
|
|
15002
|
-
} catch (err) {
|
|
15003
|
-
dispatch({
|
|
15004
|
-
type: "addEntry",
|
|
15005
|
-
entry: {
|
|
15006
|
-
kind: "error",
|
|
15007
|
-
text: `Clipboard image error: ${toErrorMessage(err)}`
|
|
15008
|
-
}
|
|
15009
|
-
});
|
|
15010
|
-
}
|
|
15011
|
-
};
|
|
15012
|
-
const pasteClipboardText = async () => {
|
|
15013
|
-
try {
|
|
15014
|
-
const text = await readClipboardText();
|
|
15015
|
-
if (!text) {
|
|
15016
|
-
dispatch({
|
|
15017
|
-
type: "addEntry",
|
|
15018
|
-
entry: { kind: "info", text: "No text on the clipboard." }
|
|
15019
|
-
});
|
|
15020
|
-
return;
|
|
15021
|
-
}
|
|
15022
|
-
await commitPaste(text);
|
|
15023
|
-
} catch (err) {
|
|
15024
|
-
dispatch({
|
|
15025
|
-
type: "addEntry",
|
|
15026
|
-
entry: {
|
|
15027
|
-
kind: "error",
|
|
15028
|
-
text: `Clipboard error: ${toErrorMessage(err)}`
|
|
15029
|
-
}
|
|
15030
|
-
});
|
|
15031
|
-
}
|
|
15032
|
-
};
|
|
15033
|
-
const acceptPickerSelection = async () => {
|
|
15034
|
-
const { open, matches, selected } = state.picker;
|
|
15035
|
-
if (!open || matches.length === 0) return;
|
|
15036
|
-
const picked = matches[selected];
|
|
15037
|
-
if (!picked) return;
|
|
15038
|
-
const builder = builderRef.current;
|
|
15039
|
-
if (!builder) return;
|
|
15040
|
-
const draft = draftRef.current;
|
|
15041
|
-
const tok = detectAtToken(draft.buffer, draft.cursor);
|
|
15042
|
-
if (!tok) {
|
|
15043
|
-
dispatch({ type: "pickerClose" });
|
|
15044
|
-
return;
|
|
15045
|
-
}
|
|
15046
|
-
const absPath = path5.isAbsolute(picked) ? picked : path5.join(projectRoot, picked);
|
|
15047
|
-
try {
|
|
15048
|
-
const data = await fs3.readFile(absPath, "utf8");
|
|
15049
|
-
const token = await builder.registerFile({
|
|
15050
|
-
kind: "file",
|
|
15051
|
-
data,
|
|
15052
|
-
meta: { filename: picked, label: picked }
|
|
15053
|
-
});
|
|
15054
|
-
tokenPreviewsRef.current.set(token, data);
|
|
15055
|
-
const before = draft.buffer.slice(0, tok.start);
|
|
15056
|
-
const after = draft.buffer.slice(tok.end);
|
|
15057
|
-
const next = `${before}${token}${after}`;
|
|
15058
|
-
setDraft(next, tok.start + token.length);
|
|
15059
|
-
dispatch({ type: "pickerClose" });
|
|
15060
|
-
} catch (err) {
|
|
15061
|
-
dispatch({
|
|
15062
|
-
type: "addEntry",
|
|
15063
|
-
entry: {
|
|
15064
|
-
kind: "error",
|
|
15065
|
-
text: `Attach failed: ${toErrorMessage(err)}`
|
|
15066
|
-
}
|
|
15067
|
-
});
|
|
15068
|
-
dispatch({ type: "pickerClose" });
|
|
15069
|
-
}
|
|
15070
|
-
};
|
|
15071
15288
|
const acceptSlashPickerSelection = () => {
|
|
15072
15289
|
const { open, matches, selected } = state.slashPicker;
|
|
15073
15290
|
if (!open || matches.length === 0) return;
|
|
@@ -15077,56 +15294,6 @@ function App({
|
|
|
15077
15294
|
setDraft(cmd, cmd.length);
|
|
15078
15295
|
dispatch({ type: "slashPickerClose" });
|
|
15079
15296
|
};
|
|
15080
|
-
useEffect(() => {
|
|
15081
|
-
if (!queueStore) return;
|
|
15082
|
-
let cancelled = false;
|
|
15083
|
-
queueStore.read().then((items) => {
|
|
15084
|
-
if (cancelled || items.length === 0) return;
|
|
15085
|
-
for (const item of items) {
|
|
15086
|
-
dispatch({
|
|
15087
|
-
type: "enqueue",
|
|
15088
|
-
item: { displayText: item.displayText, blocks: item.blocks }
|
|
15089
|
-
});
|
|
15090
|
-
}
|
|
15091
|
-
dispatch({
|
|
15092
|
-
type: "addEntry",
|
|
15093
|
-
entry: {
|
|
15094
|
-
kind: "info",
|
|
15095
|
-
text: `Restored ${items.length} queued message${items.length === 1 ? "" : "s"} from a previous run.`
|
|
15096
|
-
}
|
|
15097
|
-
});
|
|
15098
|
-
}).catch(() => void 0);
|
|
15099
|
-
return () => {
|
|
15100
|
-
cancelled = true;
|
|
15101
|
-
};
|
|
15102
|
-
}, [queueStore]);
|
|
15103
|
-
useEffect(() => {
|
|
15104
|
-
if (!queueStore) return;
|
|
15105
|
-
queueStore.write(state.queue.map(({ displayText, blocks }) => ({ displayText, blocks }))).catch(() => void 0);
|
|
15106
|
-
}, [state.queue, queueStore]);
|
|
15107
|
-
useEffect(() => {
|
|
15108
|
-
onQueueChange?.(state.queue.map((q) => q.displayText));
|
|
15109
|
-
}, [state.queue, onQueueChange]);
|
|
15110
|
-
useEffect(() => {
|
|
15111
|
-
const cmd = createQueueSlashCommand({
|
|
15112
|
-
getQueue: () => stateRef.current.queue,
|
|
15113
|
-
clear: () => dispatch({ type: "queueClear" }),
|
|
15114
|
-
deleteAt: (positions) => dispatch({ type: "queueDelete", positions }),
|
|
15115
|
-
getPickerEnabled: () => midRunSendPickerRef.current,
|
|
15116
|
-
setPickerEnabled: (enabled) => {
|
|
15117
|
-
midRunSendPickerRef.current = enabled;
|
|
15118
|
-
const cur = getSettings?.();
|
|
15119
|
-
if (cur && saveSettings) {
|
|
15120
|
-
Promise.resolve(saveSettings({ ...cur, midRunSendPicker: enabled })).catch(() => {
|
|
15121
|
-
});
|
|
15122
|
-
}
|
|
15123
|
-
}
|
|
15124
|
-
});
|
|
15125
|
-
slashRegistry.register(cmd);
|
|
15126
|
-
return () => {
|
|
15127
|
-
slashRegistry.unregister("queue");
|
|
15128
|
-
};
|
|
15129
|
-
}, [slashRegistry]);
|
|
15130
15297
|
useEffect(() => {
|
|
15131
15298
|
slashRegistry.register(createKillSlashCommand());
|
|
15132
15299
|
slashRegistry.register(createPsSlashCommand());
|
|
@@ -16046,6 +16213,7 @@ Available chords:
|
|
|
16046
16213
|
events.emit("client.status", {
|
|
16047
16214
|
clientType: "tui",
|
|
16048
16215
|
clientId,
|
|
16216
|
+
sessionId: agent.ctx.session.id,
|
|
16049
16217
|
projectHash: agent.ctx.projectRoot ? projectSlug(agent.ctx.projectRoot) : "unknown",
|
|
16050
16218
|
agentCount: 1,
|
|
16051
16219
|
// TUI is a single leader agent
|
|
@@ -16110,16 +16278,41 @@ Available chords:
|
|
|
16110
16278
|
}, [state.enhanceEnabled]);
|
|
16111
16279
|
const midRunSendPickerRef = useRef(midRunSendPicker);
|
|
16112
16280
|
const enhanceAbortRef = useRef(null);
|
|
16281
|
+
const {
|
|
16282
|
+
pasteAccumRef,
|
|
16283
|
+
pasteFlushTimerRef,
|
|
16284
|
+
commitPaste,
|
|
16285
|
+
pasteClipboardImage,
|
|
16286
|
+
pasteClipboardText
|
|
16287
|
+
} = usePasteHandling({
|
|
16288
|
+
builderRef,
|
|
16289
|
+
dispatch,
|
|
16290
|
+
draftRef,
|
|
16291
|
+
setDraft,
|
|
16292
|
+
tokenPreviewsRef
|
|
16293
|
+
});
|
|
16294
|
+
useQueueManager({
|
|
16295
|
+
queueStore,
|
|
16296
|
+
onQueueChange,
|
|
16297
|
+
slashRegistry,
|
|
16298
|
+
stateRef,
|
|
16299
|
+
dispatch,
|
|
16300
|
+
getSettings,
|
|
16301
|
+
saveSettings,
|
|
16302
|
+
midRunSendPickerRef
|
|
16303
|
+
});
|
|
16113
16304
|
const [enhanceCountdown, setEnhanceCountdown] = useState(null);
|
|
16114
16305
|
const [nextStepsAutoSubmitCountdown, setNextStepsAutoSubmitCountdown] = useState(null);
|
|
16115
16306
|
const [nextStepsAutoSubmitLabel, setNextStepsAutoSubmitLabel] = useState(null);
|
|
16116
16307
|
const nextStepsAutoSubmitSuggestionRef = useRef(null);
|
|
16117
16308
|
const nextStepsAutoSubmitTimerRef = useRef(void 0);
|
|
16309
|
+
const getActiveSessionId = useCallback(() => agent.ctx.session.id, [agent]);
|
|
16118
16310
|
useTuiEventBridge({
|
|
16119
16311
|
events,
|
|
16120
16312
|
dispatch,
|
|
16121
16313
|
stateRef,
|
|
16122
16314
|
setActiveMaxContext,
|
|
16315
|
+
getSessionId: getActiveSessionId,
|
|
16123
16316
|
subscribeAutoPhase,
|
|
16124
16317
|
onClearHistory
|
|
16125
16318
|
});
|
|
@@ -16150,6 +16343,15 @@ Available chords:
|
|
|
16150
16343
|
stateRef,
|
|
16151
16344
|
streamFleet: state.streamFleet
|
|
16152
16345
|
});
|
|
16346
|
+
const { onPickerEnter } = useFileSearch({
|
|
16347
|
+
state,
|
|
16348
|
+
dispatch,
|
|
16349
|
+
projectRoot,
|
|
16350
|
+
builderRef,
|
|
16351
|
+
draftRef,
|
|
16352
|
+
setDraft,
|
|
16353
|
+
tokenPreviewsRef
|
|
16354
|
+
});
|
|
16153
16355
|
const submitRef = useRef(() => {
|
|
16154
16356
|
});
|
|
16155
16357
|
const tryPickerKey = usePickerKeys({
|
|
@@ -16330,9 +16532,7 @@ Available chords:
|
|
|
16330
16532
|
const action = actionForFKeyPanel(entry, statuslineHiddenForPicker());
|
|
16331
16533
|
if (action) dispatch(action);
|
|
16332
16534
|
},
|
|
16333
|
-
onPickerEnter
|
|
16334
|
-
await acceptPickerSelection();
|
|
16335
|
-
}
|
|
16535
|
+
onPickerEnter
|
|
16336
16536
|
});
|
|
16337
16537
|
useEffect(() => {
|
|
16338
16538
|
const onSigint = () => {
|
|
@@ -16456,28 +16656,6 @@ Available chords:
|
|
|
16456
16656
|
process.off("SIGINT", onSigint);
|
|
16457
16657
|
};
|
|
16458
16658
|
}, [director, getEternalEngine, getParallelEngine, getSddRun, switchAutonomy, onExit, exit]);
|
|
16459
|
-
const commitPaste = async (full) => {
|
|
16460
|
-
const builder = builderRef.current;
|
|
16461
|
-
if (!builder || !full) return;
|
|
16462
|
-
const { buffer, cursor } = draftRef.current;
|
|
16463
|
-
const isSlashCmd = buffer.trimStart().startsWith("/");
|
|
16464
|
-
const mustCollapse = builder.wouldCollapse(full);
|
|
16465
|
-
const multiLine = full.includes("\n");
|
|
16466
|
-
if (isSlashCmd && !mustCollapse) {
|
|
16467
|
-
const next2 = buffer.slice(0, cursor) + full + buffer.slice(cursor);
|
|
16468
|
-
setDraft(next2, cursor + full.length);
|
|
16469
|
-
return;
|
|
16470
|
-
}
|
|
16471
|
-
if (mustCollapse || multiLine) {
|
|
16472
|
-
const token = await builder.registerPaste(full);
|
|
16473
|
-
tokenPreviewsRef.current.set(token, full);
|
|
16474
|
-
const next2 = buffer.slice(0, cursor) + token + buffer.slice(cursor);
|
|
16475
|
-
setDraft(next2, cursor + token.length);
|
|
16476
|
-
return;
|
|
16477
|
-
}
|
|
16478
|
-
const next = buffer.slice(0, cursor) + full + buffer.slice(cursor);
|
|
16479
|
-
setDraft(next, cursor + full.length);
|
|
16480
|
-
};
|
|
16481
16659
|
const handleKey = async (input, key) => {
|
|
16482
16660
|
if (state.status === "aborting" && !state.steeringPending && state.interrupts === 0) return;
|
|
16483
16661
|
if (state.confirmQueue.length > 0) return;
|
|
@@ -16545,6 +16723,7 @@ Available chords:
|
|
|
16545
16723
|
}
|
|
16546
16724
|
return;
|
|
16547
16725
|
}
|
|
16726
|
+
if (pasteAccumRef.current !== null && isEnter) return;
|
|
16548
16727
|
if (tryPickerKey(input, key, isEnter)) return;
|
|
16549
16728
|
if (key.escape && state.status !== "idle" && state.confirmQueue.length === 0) {
|
|
16550
16729
|
const runningTools = Array.from(state.runningTools.values()).map((t) => t.name);
|
|
@@ -18239,6 +18418,7 @@ ${content}
|
|
|
18239
18418
|
mode: liveStatuslineMode,
|
|
18240
18419
|
visibleChips: state.statuslinePicker.visibleChips,
|
|
18241
18420
|
events,
|
|
18421
|
+
sessionId: agent.ctx.session.id,
|
|
18242
18422
|
eternalStage: state.eternalStage,
|
|
18243
18423
|
goalSummary: state.goalSummary,
|
|
18244
18424
|
indexState,
|
|
@@ -18377,21 +18557,6 @@ function renderRunningTools(running) {
|
|
|
18377
18557
|
const more = running.size > 1 ? ` (+${running.size - 1})` : "";
|
|
18378
18558
|
return `running: ${oldest.name} ${elapsedSec}s${more}`;
|
|
18379
18559
|
}
|
|
18380
|
-
function detectAtToken(buffer, cursor) {
|
|
18381
|
-
let i = cursor - 1;
|
|
18382
|
-
while (i >= 0) {
|
|
18383
|
-
const ch = buffer.charCodeAt(i);
|
|
18384
|
-
if (ch === 64) {
|
|
18385
|
-
if (i === 0 || /\s/.test(buffer[i - 1] ?? "")) {
|
|
18386
|
-
return { start: i, end: cursor, query: buffer.slice(i + 1, cursor) };
|
|
18387
|
-
}
|
|
18388
|
-
return null;
|
|
18389
|
-
}
|
|
18390
|
-
if (ch === 32 || ch === 9 || ch === 10) return null;
|
|
18391
|
-
i--;
|
|
18392
|
-
}
|
|
18393
|
-
return null;
|
|
18394
|
-
}
|
|
18395
18560
|
function fmtTok3(n) {
|
|
18396
18561
|
if (n < 1e3) return String(n);
|
|
18397
18562
|
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
@@ -18524,7 +18689,7 @@ async function runTui(opts) {
|
|
|
18524
18689
|
stdout,
|
|
18525
18690
|
events: opts.events,
|
|
18526
18691
|
model: opts.model,
|
|
18527
|
-
appName: opts.projectRoot ?
|
|
18692
|
+
appName: opts.projectRoot ? path6.basename(opts.projectRoot) : void 0
|
|
18528
18693
|
});
|
|
18529
18694
|
};
|
|
18530
18695
|
const stopTitle = () => {
|
|
@@ -18617,19 +18782,19 @@ async function runTui(opts) {
|
|
|
18617
18782
|
if (!opts.projectRoot) return null;
|
|
18618
18783
|
try {
|
|
18619
18784
|
const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot());
|
|
18620
|
-
const hqPublisher = createHqPublisherFromEnv({ clientKind: "tui", projectRoot: opts.projectRoot, projectName:
|
|
18785
|
+
const hqPublisher = createHqPublisherFromEnv({ clientKind: "tui", projectRoot: opts.projectRoot, projectName: path6.basename(opts.projectRoot), appConfig: opts.appConfig });
|
|
18621
18786
|
hqPublisher?.connect();
|
|
18622
18787
|
const mailbox = new GlobalMailbox(projectDir, opts.events, hqPublisher);
|
|
18623
18788
|
const clientId = `tui@${randomUUID().slice(0, 8)}`;
|
|
18624
18789
|
await mailbox.registerClient({
|
|
18625
18790
|
clientId,
|
|
18626
|
-
sessionId: opts.projectRoot,
|
|
18627
|
-
name: `TUI [${
|
|
18791
|
+
sessionId: opts.getSessionId?.() ?? opts.projectRoot,
|
|
18792
|
+
name: `TUI [${path6.basename(opts.projectRoot)}]`,
|
|
18628
18793
|
source: "tui",
|
|
18629
18794
|
pid: process.pid
|
|
18630
18795
|
});
|
|
18631
18796
|
clientHeartbeatTimer = setInterval(() => {
|
|
18632
|
-
mailbox.clientHeartbeat({ clientId }).catch(() => {
|
|
18797
|
+
mailbox.clientHeartbeat({ clientId, sessionId: opts.getSessionId?.() ?? opts.projectRoot }).catch(() => {
|
|
18633
18798
|
});
|
|
18634
18799
|
}, CLIENT_HEARTBEAT_MS);
|
|
18635
18800
|
clientHeartbeatTimer.unref();
|