@wrongstack/tui 0.275.0 → 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.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 path5 from 'path';
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 fs3 from 'fs/promises';
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.slice(0, 8) }) : null,
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(path7, _query) {
2338
- return path7;
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 path7 = typeof obj["path"] === "string" ? obj["path"] : void 0;
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" && path7 !== void 0) {
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: path7 });
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" && path7 !== void 0) {
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: path7 });
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 (path7 !== void 0) {
4593
- rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path7 });
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 path7 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
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: path7, line: line === void 0 ? void 0 : String(line), text };
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: path7,
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: path7 }),
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
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `${gutterPad} \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}${footerStats.length > 0 ? ` (${footerStats.join(" ")})` : ""}` }) : null
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 path7 = stringOf(record["path"]);
5570
- items.push(path7 ? { path: path7, diff } : { diff });
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 path7 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
5635
- return [{ path: path7, diff: diffText }];
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 path7 = extractPatchHeaderPath(blocks[0].diff);
5682
- if (path7) blocks[0] = { path: path7, diff: blocks[0].diff };
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 path7 = line.slice(4).trim();
5704
- const cleaned = path7.replace(/^b\//, "").split(" ")[0].trim();
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 path7 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
5744
+ const path8 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
5716
5745
  const lines = content === "" ? [] : content.replace(/\n$/, "").split("\n");
5717
- const header = [`+++ ${path7}`, `@@ -0,0 +1,${lines.length} @@`];
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 = path5.basename(entry.cwd);
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 path5.join(wstackGlobalRoot(), "logs", "heap.jsonl");
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 fs3.mkdir(path5.dirname(logPath), { recursive: true });
6492
+ await fsp.mkdir(path6.dirname(logPath), { recursive: true });
6462
6493
  dirReady = true;
6463
6494
  }
6464
- await fs3.appendFile(logPath, `${line}
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 path5.join(base, "backlog.plan.json");
7754
+ return path6.join(base, "backlog.plan.json");
7724
7755
  }
7725
7756
  if (sessionId) {
7726
- return path5.join(base, `${sessionId}.plan.json`);
7757
+ return sessionScopedPath(base, sessionId, ".plan.json");
7727
7758
  }
7728
- return path5.join(base, "backlog.plan.json");
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 fs3.readFile(filePath, "utf-8");
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.slice(0, 8)
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
- var ENTER_DOUBLE_TAP_MS = 50;
9963
- function debouncedEnter(host) {
9964
- const now = Date.now();
9965
- if (now - host.lastEnterAtRef.current < ENTER_DOUBLE_TAP_MS) return true;
9966
- host.lastEnterAtRef.current = now;
9967
- return false;
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 usePickerKeys(host) {
9970
- return useCallback(
9971
- (input, key, isEnter) => {
9972
- const { state, dispatch } = host;
9973
- if (state.modelPicker.open) {
9974
- if (key.escape) {
9975
- if (state.modelPicker.step === "model") {
9976
- dispatch({ type: "modelPickerBack" });
9977
- } else {
9978
- dispatch({ type: "modelPickerClose" });
9979
- }
9980
- return true;
9981
- }
9982
- if (key.mouse?.kind === "wheel") {
9983
- dispatch({ type: "modelPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
9984
- return true;
9985
- }
9986
- if (key.upArrow) {
9987
- dispatch({ type: "modelPickerMove", delta: -1 });
9988
- return true;
9989
- }
9990
- if (key.downArrow) {
9991
- dispatch({ type: "modelPickerMove", delta: 1 });
9992
- return true;
9993
- }
9994
- if (state.modelPicker.step === "model" && input && !key.return && !key.backspace) {
9995
- dispatch({ type: "modelPickerSearch", query: state.modelPicker.searchQuery + input });
9996
- return true;
9997
- }
9998
- if (state.modelPicker.step === "model" && key.backspace) {
9999
- const q = state.modelPicker.searchQuery;
10000
- if (q.length > 0) {
10001
- dispatch({ type: "modelPickerSearch", query: q.slice(0, -1) });
10002
- } else {
10003
- dispatch({ type: "modelPickerBack" });
10004
- }
10005
- return true;
10006
- }
10007
- if (isEnter) {
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
- if (state.autonomyPicker.open) {
10054
- if (key.escape) {
10055
- dispatch({ type: "autonomyPickerClose" });
10056
- return true;
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
- if (state.designPicker.open) {
10085
- if (key.escape) {
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 handleCollabDone(event, dispatch, stateRef) {
10842
- const collabSession = stateRef.current.collabSession;
10843
- if (!collabSession) return;
10844
- const payload = event.payload;
10845
- if (!payload?.report) return;
10846
- dispatch({
10847
- type: "collabSessionDone",
10848
- sessionId: payload.report.sessionId ?? collabSession.sessionId ?? "unknown",
10849
- verdict: payload.report.overallVerdict ?? "needs_revision"
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
- function useAutonomousCoordinator(subscribeCoordinatorEvents, dispatch) {
10853
- const dispatchRef = useRef(dispatch);
10854
- dispatchRef.current = dispatch;
10855
- const handler = useCallback((event) => {
10856
- dispatchRef.current({
10857
- type: "coordinatorEvent",
10858
- event
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
- if (!subscribeCoordinatorEvents) return;
10863
- const unsubscribe = subscribeCoordinatorEvents(handler);
10864
- return unsubscribe;
10865
- }, [subscribeCoordinatorEvents, handler]);
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) => addBrainEntry("answered", payload));
10988
- const offAskHuman = events.on("brain.decision_ask_human", (payload) => addBrainEntry("ask_human", payload));
10989
- const offDenied = events.on("brain.decision_denied", (payload) => addBrainEntry("denied", payload));
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 USAGE = "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";
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: renderList() };
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
- ${USAGE}` };
11812
+ ${USAGE2}` };
11394
11813
  }
11395
11814
  };
11396
11815
  }
11397
- function renderList() {
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
- deps.deleteAt(uniqueValid);
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
- return `Unknown subcommand "${sub}".
11571
- ${USAGE2}`;
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 renderList3(queue) {
11574
- if (queue.length === 0) return "Queue is empty.";
11575
- const lines = [`Queue (${queue.length}):`];
11576
- for (let i = 0; i < queue.length; i++) {
11577
- const item = queue[i];
11578
- if (!item) continue;
11579
- const preview = oneLine(item.displayText, 100);
11580
- lines.push(` ${i + 1}. ${preview}`);
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 = path5.join(
14178
- path5.dirname(req.resolve("@wrongstack/core/package.json")),
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 = path5.basename(projectRoot);
14275
- return base && base !== path5.sep ? base : void 0;
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 path5.relative(projectRoot, ctx.workingDir) || ".";
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 = path5.relative(projectRoot, newDir) || ".";
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 fs3.readFile(planPath, "utf8");
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 fs3.readFile(taskPath, "utf8");
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: async () => {
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 ? path5.basename(opts.projectRoot) : void 0
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: path5.basename(opts.projectRoot), appConfig: opts.appConfig });
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 [${path5.basename(opts.projectRoot)}]`,
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();