@wrongstack/tui 0.282.0 → 0.282.1

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
@@ -1,11 +1,10 @@
1
- import { hasOpenTodos, writeErr, resolveProjectDir, wstackGlobalRoot, createHqPublisherFromEnv, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge, GlobalMailbox, resolveWstackPaths, loadGoal, InputHistoryStore, DefaultSecretScrubber, INPUT_HISTORY_DEFAULT_MAX, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, getDesignKitLoader, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, materializeTokens, applyTokenOverrides, setActiveKit, projectSlug, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, setBtwNote, listBoards, getBoard, createBoard, addTask, buildChildEnv, DefaultPromptLoader, PromptUsageStore, removeTask, moveTask, updateTask, duplicateBoard, copyTaskToBoard, transferTaskToBoard } from '@wrongstack/core';
1
+ import { hasOpenTodos, writeErr, resolveProjectDir, wstackGlobalRoot, createHqPublisherFromEnv, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge, GlobalMailbox, resolveWstackPaths, loadGoal, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, InputHistoryStore, DefaultSecretScrubber, INPUT_HISTORY_DEFAULT_MAX, DefaultPromptLoader, PromptUsageStore, getDesignKitLoader, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, materializeTokens, applyTokenOverrides, setActiveKit, projectSlug, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, setBtwNote, listBoards, getBoard, createBoard, addTask, buildChildEnv, removeTask, moveTask, updateTask, duplicateBoard, copyTaskToBoard, transferTaskToBoard } from '@wrongstack/core';
2
2
  export { buildGoalPreamble } from '@wrongstack/core';
3
3
  import { Box as Box$1, useStdout, Static, useInput, useStdin, measureElement, Text as Text$1, render, useApp } from 'ink';
4
4
  import { randomUUID } from 'crypto';
5
- import * as path6 from 'path';
5
+ import * as path9 from 'path';
6
6
  import React5, { forwardRef, useState, useEffect, useMemo, memo, useRef, useLayoutEffect, useCallback, useReducer, isValidElement, Fragment as Fragment$1 } from 'react';
7
7
  import * as fsp from 'fs/promises';
8
- import { createRequire } from 'module';
9
8
  import { expectDefined, toErrorMessage, resolveWstackPaths as resolveWstackPaths$1, sessionScopedPath } from '@wrongstack/core/utils';
10
9
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
11
10
  import { getIndexState, onIndexStateChange, getProcessRegistry } from '@wrongstack/tools';
@@ -14,6 +13,7 @@ import { getToolIcon, TOOL_ICON_CONFIG } from '@wrongstack/tools/tool-icons';
14
13
  import { spawn } from 'child_process';
15
14
  import * as v8 from 'v8';
16
15
  import { readClipboardText, readClipboardImage } from '@wrongstack/runtime/clipboard';
16
+ import { createRequire } from 'module';
17
17
 
18
18
  // src/run-tui.ts
19
19
 
@@ -4156,6 +4156,36 @@ function StatusBar({
4156
4156
  return () => clearInterval(t);
4157
4157
  }, [state]);
4158
4158
  const spinner = expectDefined$1(SPINNER_FRAMES[spinnerIdx]);
4159
+ const TODOS_CLEAR_DELAY_MS = 3e3;
4160
+ const [todosCleared, setTodosCleared] = useState(false);
4161
+ const todosClearTimerRef = useRef(null);
4162
+ useEffect(() => {
4163
+ const hasActive = todos && (todos.pending > 0 || todos.inProgress > 0);
4164
+ if (hasActive) {
4165
+ if (todosClearTimerRef.current) {
4166
+ clearTimeout(todosClearTimerRef.current);
4167
+ todosClearTimerRef.current = null;
4168
+ }
4169
+ setTodosCleared(false);
4170
+ return;
4171
+ }
4172
+ if (todos && todos.completed > 0) {
4173
+ if (!todosClearTimerRef.current) {
4174
+ todosClearTimerRef.current = setTimeout(() => {
4175
+ setTodosCleared(true);
4176
+ todosClearTimerRef.current = null;
4177
+ }, TODOS_CLEAR_DELAY_MS);
4178
+ }
4179
+ return () => {
4180
+ if (todosClearTimerRef.current) {
4181
+ clearTimeout(todosClearTimerRef.current);
4182
+ todosClearTimerRef.current = null;
4183
+ }
4184
+ };
4185
+ }
4186
+ setTodosCleared(true);
4187
+ return;
4188
+ }, [todos]);
4159
4189
  const animationStyle = thinkingAnimationStyle ?? "rainbow";
4160
4190
  const [cycleTick, setCycleTick] = useState(0);
4161
4191
  useEffect(() => {
@@ -4186,7 +4216,7 @@ function StatusBar({
4186
4216
  const hasNextStepsAutoSubmit = nextStepsAutoSubmitCountdown != null && nextStepsAutoSubmitCountdown > 0;
4187
4217
  const nextStepsColor = nextStepsAutoSubmitCountdown != null ? countdownColor(nextStepsAutoSubmitCountdown, 20, 10) : "green";
4188
4218
  const hasTaskActivity = tasks && (tasks.pending > 0 || tasks.inProgress > 0 || tasks.completed > 0 || tasks.blocked > 0 || tasks.failed > 0);
4189
- const hasThirdLine = todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) && showChip("todos") || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) && showChip("plan") || hasTaskActivity && showChip("tasks") || fleetHasActivity && showChip("fleet") || showBrain || showDebugStream || showEnhance || hasNextStepsAutoSubmit && showChip("next_steps");
4219
+ const hasThirdLine = todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0 && !todosCleared) && showChip("todos") || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) && showChip("plan") || hasTaskActivity && showChip("tasks") || fleetHasActivity && showChip("fleet") || showBrain || showDebugStream || showEnhance || hasNextStepsAutoSubmit && showChip("next_steps");
4190
4220
  const minimalWorkParts = [
4191
4221
  queueCount > 0 && showChip("queue") ? `q${queueCount}` : "",
4192
4222
  todos && showChip("todos") && todos.inProgress + todos.pending > 0 ? `todo ${todos.inProgress}/${todos.pending}` : "",
@@ -4529,7 +4559,7 @@ function StatusBar({
4529
4559
  ) }) : /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
4530
4560
  hasThirdLine ? /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, children: renderChipLine(
4531
4561
  [
4532
- todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) && showChip("todos") ? /* @__PURE__ */ jsxs(Text, { children: [
4562
+ todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0 && !todosCleared) && showChip("todos") ? /* @__PURE__ */ jsxs(Text, { children: [
4533
4563
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "todos " }),
4534
4564
  todos.inProgress > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
4535
4565
  "\u231B",
@@ -5008,7 +5038,7 @@ function FleetMonitor({
5008
5038
  nowTick,
5009
5039
  collabSession
5010
5040
  }) {
5011
- const TERMINAL_TTL_MS2 = 5 * 6e4;
5041
+ const TERMINAL_TTL_MS = 5 * 6e4;
5012
5042
  const all = Object.values(entries);
5013
5043
  const running = all.filter((e) => e.status === "running");
5014
5044
  const idle = all.filter((e) => e.status === "idle").length;
@@ -5016,7 +5046,7 @@ function FleetMonitor({
5016
5046
  const failed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
5017
5047
  const recent = all.filter((e) => {
5018
5048
  if (e.status === "running" || e.status === "idle") return true;
5019
- return nowTick - e.lastEventAt < TERMINAL_TTL_MS2;
5049
+ return nowTick - e.lastEventAt < TERMINAL_TTL_MS;
5020
5050
  });
5021
5051
  const concurrencyRatio = maxConcurrent > 0 ? running.length / maxConcurrent : 0;
5022
5052
  const ordered = [...recent].sort((a, b) => {
@@ -5214,15 +5244,12 @@ var STATUS2 = {
5214
5244
  };
5215
5245
  var IDLE_HIDE_MS = 6e4;
5216
5246
  var EMPTY_AGENTS_CLOSE_DELAY_MS = 7500;
5217
- function isTerminalAgentStatus(status) {
5218
- return status === "success" || status === "failed" || status === "timeout" || status === "stopped";
5219
- }
5220
5247
  function isLeaderEntry(entry) {
5221
5248
  return entry.id === "leader" || entry.name === "LEADER";
5222
5249
  }
5223
5250
  function selectLiveAgents(all, _now, _idleHideMs = IDLE_HIDE_MS) {
5224
5251
  const leader = all.find(isLeaderEntry);
5225
- const activeSubagents = all.filter((entry) => !isLeaderEntry(entry) && !isTerminalAgentStatus(entry.status));
5252
+ const activeSubagents = all.filter((entry) => !isLeaderEntry(entry) && entry.status === "running");
5226
5253
  const showLeader = leader !== void 0 && (leader.status !== "idle" || activeSubagents.length > 0);
5227
5254
  return all.filter((entry) => isLeaderEntry(entry) ? showLeader : activeSubagents.some((active) => active.id === entry.id));
5228
5255
  }
@@ -5551,9 +5578,6 @@ function AgentsMonitor({
5551
5578
  const running = live.filter((e) => e.status === "running").length;
5552
5579
  const totalDone = all.filter((e) => e.status === "success").length;
5553
5580
  const totalFailed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
5554
- const staleIdle = all.filter(
5555
- (e) => e.status === "idle" && nowTick - e.lastEventAt >= IDLE_HIDE_MS
5556
- ).length;
5557
5581
  const hotAgent = selectHotAgent(live);
5558
5582
  const pressure = live.length > 0 ? live.reduce((max, e) => Math.max(max, e.ctxPct ?? 0), 0) : 0;
5559
5583
  const toolCalls = live.reduce((sum, e) => sum + e.toolCalls, 0);
@@ -5645,12 +5669,7 @@ function AgentsMonitor({
5645
5669
  " \xB7 fleet $",
5646
5670
  totalCost.toFixed(4),
5647
5671
  ")"
5648
- ] }),
5649
- staleIdle > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5650
- "\xB7 ",
5651
- staleIdle,
5652
- " idle stale"
5653
- ] }) : null
5672
+ ] })
5654
5673
  ] }),
5655
5674
  live.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No live agents \u2014 spawn with /spawn or /fleet dispatch." }) : null,
5656
5675
  live.map((e) => e.id === selected?.id ? /* @__PURE__ */ jsx(AgentDetail, { entry: e, now: nowTick }, e.id) : /* @__PURE__ */ jsx(AgentRow, { entry: e, now: nowTick, selected: false }, e.id))
@@ -6664,8 +6683,8 @@ function detectLang(fenceInfo) {
6664
6683
  const tag = fenceInfo.trim().toLowerCase().split(/\s+/)[0] ?? "";
6665
6684
  return LANG_ALIASES[tag] ?? "plain";
6666
6685
  }
6667
- function langFromPath(path8) {
6668
- const m = /\.([A-Za-z0-9]+)$/.exec(path8.trim());
6686
+ function langFromPath(path10) {
6687
+ const m = /\.([A-Za-z0-9]+)$/.exec(path10.trim());
6669
6688
  const ext = m?.[1]?.toLowerCase() ?? "";
6670
6689
  return LANG_ALIASES[ext] ?? "plain";
6671
6690
  }
@@ -7459,19 +7478,19 @@ function visualEdit(toolName, text, ok) {
7459
7478
  const parsed = tryParseJson(text);
7460
7479
  if (parsed && typeof parsed === "object") {
7461
7480
  const obj = parsed;
7462
- const path8 = typeof obj["path"] === "string" ? obj["path"] : void 0;
7481
+ const path10 = typeof obj["path"] === "string" ? obj["path"] : void 0;
7463
7482
  const replacements = typeof obj["replacements"] === "number" ? obj["replacements"] : void 0;
7464
7483
  const bytes = typeof obj["bytes"] === "number" ? obj["bytes"] : void 0;
7465
7484
  const created = obj["created"] === true;
7466
7485
  const files = Array.isArray(obj["files"]) ? obj["files"] : void 0;
7467
7486
  const results = Array.isArray(obj["results"]) ? obj["results"] : void 0;
7468
- if (toolName === "edit" && path8 !== void 0) {
7487
+ if (toolName === "edit" && path10 !== void 0) {
7469
7488
  const repText = replacements !== void 0 ? `${replacements} replacement${replacements === 1 ? "" : "s"}` : void 0;
7470
- rows.push({ kind: "ok", text: "", marker: "edit ", path: path8 });
7489
+ rows.push({ kind: "ok", text: "", marker: "edit ", path: path10 });
7471
7490
  if (repText) rows.push({ kind: "meta", text: repText });
7472
- } else if (toolName === "write" && path8 !== void 0) {
7491
+ } else if (toolName === "write" && path10 !== void 0) {
7473
7492
  const sizeText = bytes !== void 0 ? `${bytes} bytes` : created ? "new file" : "updated";
7474
- rows.push({ kind: "ok", text: "", marker: "write ", path: path8 });
7493
+ rows.push({ kind: "ok", text: "", marker: "write ", path: path10 });
7475
7494
  rows.push({ kind: "meta", text: sizeText });
7476
7495
  } else if ((toolName === "diff" || toolName === "patch") && files && files.length > 0) {
7477
7496
  rows.push({
@@ -7494,8 +7513,8 @@ function visualEdit(toolName, text, ok) {
7494
7513
  marker: "replace ",
7495
7514
  text: `${results.length} replacement${results.length === 1 ? "" : "s"} across ${fileCount} file${fileCount === 1 ? "" : "s"}`
7496
7515
  });
7497
- } else if (path8 !== void 0) {
7498
- rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path8 });
7516
+ } else if (path10 !== void 0) {
7517
+ rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path10 });
7499
7518
  } else {
7500
7519
  return void 0;
7501
7520
  }
@@ -7589,12 +7608,12 @@ function parseMatchHit(hit) {
7589
7608
  }
7590
7609
  if (hit && typeof hit === "object") {
7591
7610
  const o = hit;
7592
- const path8 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
7611
+ const path10 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
7593
7612
  const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
7594
7613
  const title = stringOf(o["title"]);
7595
7614
  const snippet2 = stringOf(o["snippet"]);
7596
7615
  const text = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]) ?? [title, snippet2].filter(Boolean).join(" \u2014 ");
7597
- return { path: path8, line: line === void 0 ? void 0 : String(line), text };
7616
+ return { path: path10, line: line === void 0 ? void 0 : String(line), text };
7598
7617
  }
7599
7618
  return void 0;
7600
7619
  }
@@ -8402,13 +8421,13 @@ function formatMultiDiffSummary(summary, threshold = MULTI_DIFF_SUMMARY_THRESHOL
8402
8421
  return parts.join(" \xB7 ");
8403
8422
  }
8404
8423
  function DiffFileBlock({
8405
- path: path8,
8424
+ path: path10,
8406
8425
  preview,
8407
8426
  useColor = true,
8408
8427
  contentWidth
8409
8428
  }) {
8410
8429
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
8411
- /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: path8 }),
8430
+ /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: path10 }),
8412
8431
  /* @__PURE__ */ jsx(
8413
8432
  DiffBlock,
8414
8433
  {
@@ -8419,7 +8438,7 @@ function DiffFileBlock({
8419
8438
  hiddenAdded: preview.hiddenAdded,
8420
8439
  hiddenRemoved: preview.hiddenRemoved,
8421
8440
  useColor,
8422
- lang: langFromPath(path8),
8441
+ lang: langFromPath(path10),
8423
8442
  contentWidth
8424
8443
  }
8425
8444
  )
@@ -8509,7 +8528,8 @@ function DiffBlock({
8509
8528
  return row.text || " ";
8510
8529
  };
8511
8530
  const stats = showStats ? formatDiffStats(added, removed) : null;
8512
- const bodyBudget = typeof contentWidth === "number" ? Math.max(16, contentWidth - (2 + 3 + gutterWidth + 3)) : DIFF_FALLBACK_WRAP_WIDTH;
8531
+ const terminalWrapGuard = typeof contentWidth === "number" ? 1 : 0;
8532
+ const bodyBudget = typeof contentWidth === "number" ? Math.max(16, contentWidth - (2 + 3 + gutterWidth + 3 + terminalWrapGuard)) : DIFF_FALLBACK_WRAP_WIDTH;
8513
8533
  const hasWidth = typeof contentWidth === "number";
8514
8534
  const padBody = (seg) => {
8515
8535
  if (!hasWidth) return 0;
@@ -8677,8 +8697,8 @@ function splitReplaceDiffs(obj) {
8677
8697
  const record = result;
8678
8698
  const diff = stringOf(record["diff"]);
8679
8699
  if (!diff?.trim()) continue;
8680
- const path8 = stringOf(record["path"]);
8681
- items.push(path8 ? { path: path8, diff } : { diff });
8700
+ const path10 = stringOf(record["path"]);
8701
+ items.push(path10 ? { path: path10, diff } : { diff });
8682
8702
  }
8683
8703
  return items;
8684
8704
  }
@@ -8742,8 +8762,8 @@ function collectMultiFileDiffItems(toolName, output, _input) {
8742
8762
  if (!diffText?.trim()) return void 0;
8743
8763
  const blocks = splitGitStyleDiff(diffText);
8744
8764
  if (blocks.length === 0) {
8745
- const path8 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
8746
- return [{ path: path8, diff: diffText }];
8765
+ const path10 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
8766
+ return [{ path: path10, diff: diffText }];
8747
8767
  }
8748
8768
  return blocks.map((block, idx) => ({
8749
8769
  path: explicitFiles[idx] ?? block.path,
@@ -8789,17 +8809,17 @@ function splitGitStyleDiff(diff) {
8789
8809
  }
8790
8810
  flush();
8791
8811
  if (blocks.length === 1 && !blocks[0].path) {
8792
- const path8 = extractPatchHeaderPath(blocks[0].diff);
8793
- if (path8) blocks[0] = { path: path8, diff: blocks[0].diff };
8812
+ const path10 = extractPatchHeaderPath(blocks[0].diff);
8813
+ if (path10) blocks[0] = { path: path10, diff: blocks[0].diff };
8794
8814
  }
8795
8815
  return blocks;
8796
8816
  }
8797
8817
  function parseDiffGitPath(line) {
8798
8818
  const rest = line.slice("diff --git ".length).trim();
8799
8819
  if (!rest) return void 0;
8800
- const sep2 = rest.lastIndexOf(" b/");
8801
- if (sep2 > 0 && sep2 > rest.length - sep2 - 3) {
8802
- return rest.slice(sep2 + 3);
8820
+ const sep3 = rest.lastIndexOf(" b/");
8821
+ if (sep3 > 0 && sep3 > rest.length - sep3 - 3) {
8822
+ return rest.slice(sep3 + 3);
8803
8823
  }
8804
8824
  const spaceIdx = rest.lastIndexOf(" ");
8805
8825
  if (spaceIdx > 0) {
@@ -8811,8 +8831,8 @@ function parseDiffGitPath(line) {
8811
8831
  function extractPatchHeaderPath(diffText) {
8812
8832
  for (const line of diffText.split("\n")) {
8813
8833
  if (line.startsWith("+++ ")) {
8814
- const path8 = line.slice(4).trim();
8815
- const cleaned = path8.replace(/^b\//, "").split(" ")[0].trim();
8834
+ const path10 = line.slice(4).trim();
8835
+ const cleaned = path10.replace(/^b\//, "").split(" ")[0].trim();
8816
8836
  return cleaned || void 0;
8817
8837
  }
8818
8838
  }
@@ -8838,9 +8858,9 @@ function newFileDiffFromWriteInput(output, input) {
8838
8858
  const obj = input;
8839
8859
  const content = stringOf(obj["content"]);
8840
8860
  if (content === void 0) return void 0;
8841
- const path8 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
8861
+ const path10 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
8842
8862
  const lines = content === "" ? [] : content.replace(/\n$/, "").split("\n");
8843
- const header = [`+++ ${path8}`, `@@ -0,0 +1,${lines.length} @@`];
8863
+ const header = [`+++ ${path10}`, `@@ -0,0 +1,${lines.length} @@`];
8844
8864
  return [...header, ...lines.map((line) => `+${line}`)].join("\n");
8845
8865
  }
8846
8866
  var BUTTON_COLOR = {
@@ -8878,7 +8898,7 @@ function renderDiffLine(line) {
8878
8898
  "\n"
8879
8899
  ] }, line);
8880
8900
  }
8881
- function renderDiff(diff, path8) {
8901
+ function renderDiff(diff, path10) {
8882
8902
  const preview = parseUnifiedDiff(diff, CONFIRM_DIFF_MAX_LINES);
8883
8903
  if (preview.rows.length > 0) {
8884
8904
  return /* @__PURE__ */ jsx(
@@ -8891,7 +8911,7 @@ function renderDiff(diff, path8) {
8891
8911
  hiddenAdded: preview.hiddenAdded,
8892
8912
  hiddenRemoved: preview.hiddenRemoved,
8893
8913
  useColor: theme.supportsBackground,
8894
- lang: langFromPath(path8 ?? "")
8914
+ lang: langFromPath(path10 ?? "")
8895
8915
  }
8896
8916
  );
8897
8917
  }
@@ -9336,10 +9356,9 @@ function FilePicker({ query, matches, selected }) {
9336
9356
  ] }, m))
9337
9357
  ] });
9338
9358
  }
9339
- function highlight(path8, _query) {
9340
- return path8;
9359
+ function highlight(path10, _query) {
9360
+ return path10;
9341
9361
  }
9342
- var TERMINAL_TTL_MS = 9e4;
9343
9362
  var METER_WIDTH = 10;
9344
9363
  function isLeaderEntry2(entry) {
9345
9364
  return entry.id === "leader" || entry.name === "LEADER";
@@ -9413,22 +9432,19 @@ function swarmColumnCount(width) {
9413
9432
  if (width < 54) return 1;
9414
9433
  return Math.max(1, Math.min(5, Math.floor(width / 38)));
9415
9434
  }
9416
- function selectSwarmEntries(entries, now, opts = {}) {
9417
- const terminalTtlMs = opts.terminalTtlMs ?? TERMINAL_TTL_MS;
9435
+ function selectSwarmEntries(entries, _now, opts = {}) {
9418
9436
  return Object.values(entries).filter((entry) => {
9419
9437
  if (isLeaderEntry2(entry) && !opts.includeLeader) return false;
9420
- if (entry.status === "running" || entry.status === "idle") return true;
9421
- return now - entry.lastEventAt <= terminalTtlMs;
9438
+ if (isLeaderEntry2(entry)) return true;
9439
+ return entry.status === "running";
9422
9440
  }).sort((a, b) => {
9423
9441
  const rank = (entry) => {
9424
9442
  if (entry.status === "running") return 0;
9425
- if (entry.status === "idle") return 1;
9426
- return 2;
9443
+ return 1;
9427
9444
  };
9428
9445
  const ar = rank(a);
9429
9446
  const br = rank(b);
9430
9447
  if (ar !== br) return ar - br;
9431
- if (ar === 2) return b.lastEventAt - a.lastEventAt;
9432
9448
  return a.startedAt - b.startedAt;
9433
9449
  });
9434
9450
  }
@@ -9549,16 +9565,8 @@ function FleetPanel({
9549
9565
  const hasSwarm = grid.rows.length > 0;
9550
9566
  if (!hasSwarm && todoRows.length === 0 && !hasCollab) return null;
9551
9567
  const runningCount = subagents.filter((e) => e.status === "running").length;
9552
- const idleCount = subagents.filter((e) => e.status === "idle").length;
9553
- const doneCount = subagents.filter((e) => e.status === "success").length;
9554
- const failedCount = subagents.filter(
9555
- (e) => e.status === "failed" || e.status === "timeout"
9556
- ).length;
9557
9568
  const summaryBits = [
9558
- runningCount > 0 ? `${runningCount} running` : "idle",
9559
- idleCount > 0 ? `${idleCount} idle` : "",
9560
- doneCount > 0 ? `${doneCount} done` : "",
9561
- failedCount > 0 ? `${failedCount} failed` : "",
9569
+ runningCount > 0 ? `${runningCount} running` : "",
9562
9570
  fmtCost2(totalCost)
9563
9571
  ].filter(Boolean);
9564
9572
  const collabLabel = hasCollab && collabSession.sessionId ? `collab ${collabSession.bugCount}b/${collabSession.planCount}p/${collabSession.evalCount}e` : "";
@@ -9962,8 +9970,8 @@ var SEP_RE = /^\s*\|[\s\-:|]+\|\s*$/;
9962
9970
  function detectTable(lines, start) {
9963
9971
  if (start + 1 >= lines.length) return start;
9964
9972
  if (!ROW_RE.test(lines[start] ?? "")) return start;
9965
- const sep2 = lines[start + 1] ?? "";
9966
- if (!SEP_RE.test(sep2) || !/-/.test(sep2)) return start;
9973
+ const sep3 = lines[start + 1] ?? "";
9974
+ if (!SEP_RE.test(sep3) || !/-/.test(sep3)) return start;
9967
9975
  let end = start + 2;
9968
9976
  while (end < lines.length && ROW_RE.test(lines[end] ?? "")) end++;
9969
9977
  return end;
@@ -9989,8 +9997,8 @@ function parseCells(line) {
9989
9997
  parts.push(buf);
9990
9998
  return parts.map((c) => c.trim());
9991
9999
  }
9992
- function parseAlign(sep2) {
9993
- const t = sep2.trim();
10000
+ function parseAlign(sep3) {
10001
+ const t = sep3.trim();
9994
10002
  const left = t.startsWith(":");
9995
10003
  const right = t.endsWith(":");
9996
10004
  if (left && right) return "center";
@@ -10556,7 +10564,7 @@ function Banner({
10556
10564
  entry
10557
10565
  }) {
10558
10566
  const cwdShort = shortenPath(entry.cwd, 48);
10559
- const projectLabel = path6.basename(entry.cwd);
10567
+ const projectLabel = path9.basename(entry.cwd);
10560
10568
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
10561
10569
  /* @__PURE__ */ jsxs(Text, { children: [
10562
10570
  /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
@@ -10594,18 +10602,12 @@ function Banner({
10594
10602
  }
10595
10603
 
10596
10604
  // src/components/suggestions.ts
10597
- var STRICT_HEADING_RE = /(?:💡\s*Next steps?|<next_steps>)\s*\n+/i;
10598
- var PERMISSIVE_HEADING_PATTERNS = [
10599
- { re: /💡\s*Next steps?\s*\n+/i, label: "emoji" },
10600
- { re: /##?\s*Next steps?\s*\n+/i, label: "markdown" },
10601
- { re: /\n{1,2}Next steps?\s*\n+/i, label: "plain" },
10602
- { re: /<next_steps>\s*\n+/i, label: "xml-tag" }
10603
- ];
10605
+ var NEXT_STEPS_TAG_RE = /<nextsteps>\s*\n+/i;
10604
10606
  var ITEM_RE = /^(?:(\d+)[.)]\s*|[-*•]\s*)(.+?)(\s+auto="true")?$/;
10605
10607
  var MAX_STEPS = 6;
10606
10608
  function parseNextSteps(content, strict = false, requireHeading = true) {
10607
10609
  if (requireHeading) {
10608
- return parseWithHeading(content, strict);
10610
+ return parseWithHeading(content);
10609
10611
  }
10610
10612
  return parseRawNumbered(content);
10611
10613
  }
@@ -10640,9 +10642,8 @@ function parseRawNumbered(content) {
10640
10642
  autoTexts: steps.filter((s2) => s2.auto).map((s2) => s2.text)
10641
10643
  };
10642
10644
  }
10643
- function parseWithHeading(content, strict) {
10644
- const headingRe = strict ? STRICT_HEADING_RE : buildPermissiveHeadingRe();
10645
- const headingMatch = headingRe.exec(content);
10645
+ function parseWithHeading(content, _strict) {
10646
+ const headingMatch = NEXT_STEPS_TAG_RE.exec(content);
10646
10647
  if (!headingMatch) {
10647
10648
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
10648
10649
  }
@@ -10674,8 +10675,7 @@ function parseWithHeading(content, strict) {
10674
10675
  if (steps.length === 0) {
10675
10676
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
10676
10677
  }
10677
- const headingWasXmlTag = headingMatch[0].startsWith("<");
10678
- if (strict && headingWasXmlTag && !afterHeading.includes("</next_steps>")) {
10678
+ if (!afterHeading.includes("</nextsteps>")) {
10679
10679
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
10680
10680
  }
10681
10681
  const texts = steps.map((s2) => s2.text);
@@ -10685,14 +10685,10 @@ function parseWithHeading(content, strict) {
10685
10685
  const stripped = (content.slice(0, blockStart) + content.slice(blockStart + blockEnd)).replace(/\n{3,}/g, "\n\n").trim();
10686
10686
  return { steps, texts, stripped, autoTexts };
10687
10687
  }
10688
- function buildPermissiveHeadingRe() {
10689
- const variants = PERMISSIVE_HEADING_PATTERNS.map(({ re }) => `(?:${re.source})`).join("|");
10690
- return new RegExp(variants, "i");
10691
- }
10692
10688
  function findBlockEnd(afterHeading, stepCount) {
10693
- const closeIdx = afterHeading.indexOf("</next_steps>");
10689
+ const closeIdx = afterHeading.indexOf("</nextsteps>");
10694
10690
  if (closeIdx !== -1) {
10695
- let end = closeIdx + "</next_steps>".length;
10691
+ let end = closeIdx + "</nextsteps>".length;
10696
10692
  if (afterHeading[end] === "\n") end += 1;
10697
10693
  return end;
10698
10694
  }
@@ -10714,7 +10710,7 @@ function findBlockEnd(afterHeading, stepCount) {
10714
10710
  return consumed;
10715
10711
  }
10716
10712
  function stripNextStepsBlock(text) {
10717
- return text.replace(/<next_steps\b[^>]*>[\s\S]*?<\/next_steps>/gi, "").replace(/<next_steps\b[^>]*\/?>/gi, "").replace(/\n{3,}/g, "\n\n").trim();
10713
+ return text.replace(/<nextsteps\b[^>]*>[\s\S]*?<\/nextsteps>/gi, "").replace(/<nextsteps\b[^>]*\/?>/gi, "").replace(/\n{3,}/g, "\n\n").trim();
10718
10714
  }
10719
10715
  function brainStatusStyle(status) {
10720
10716
  switch (status) {
@@ -10908,15 +10904,15 @@ var Entry = React5.memo(function Entry2({
10908
10904
  const outObj = outJson && typeof outJson === "object" ? outJson : void 0;
10909
10905
  const created = name === "write" && (outObj?.["created"] === true || /^[^\n]*\bcreated=true\b/.test(entry.output ?? ""));
10910
10906
  const verb = created ? "Write" : "Update";
10911
- const path8 = stringOf(inputObj?.["path"]) ?? stringOf(outObj?.["path"]);
10912
- const target = multiDiffs2 ? multiDiffs2.length === 1 ? multiDiffs2[0].path : `${multiDiffs2.length} files` : path8 ?? "file";
10907
+ const path10 = stringOf(inputObj?.["path"]) ?? stringOf(outObj?.["path"]);
10908
+ const target = multiDiffs2 ? multiDiffs2.length === 1 ? multiDiffs2[0].path : `${multiDiffs2.length} files` : path10 ?? "file";
10913
10909
  const agg = multiDiffs2 ? summarizeMultiFileDiffs(multiDiffs2) : void 0;
10914
10910
  return {
10915
10911
  verb,
10916
10912
  target,
10917
10913
  added: agg ? agg.added : diff2?.added ?? 0,
10918
10914
  removed: agg ? agg.removed : diff2?.removed ?? 0,
10919
- lang: langFromPath(multiDiffs2 ? "" : path8 ?? "")
10915
+ lang: langFromPath(multiDiffs2 ? "" : path10 ?? "")
10920
10916
  };
10921
10917
  })();
10922
10918
  return { argSummary: argSummary2, outLines: outLines2, visualLines: visualLines2, diff: diff2, multiDiffs: multiDiffs2, sizeChip: sizeChip2, mutation: mutation2 };
@@ -12470,12 +12466,12 @@ var STATUS_ICON = {
12470
12466
  function planFilePath(projectRoot, sessionId, scope) {
12471
12467
  const base = resolveWstackPaths$1({ projectRoot }).projectSessions;
12472
12468
  if (scope === "project") {
12473
- return path6.join(base, "backlog.plan.json");
12469
+ return path9.join(base, "backlog.plan.json");
12474
12470
  }
12475
12471
  if (sessionId) {
12476
12472
  return sessionScopedPath(base, sessionId, ".plan.json");
12477
12473
  }
12478
- return path6.join(base, "backlog.plan.json");
12474
+ return path9.join(base, "backlog.plan.json");
12479
12475
  }
12480
12476
  function PlanPanel({ projectRoot, sessionId, onClose: _onClose }) {
12481
12477
  const [items, setItems] = useState([]);
@@ -12618,9 +12614,14 @@ function PluginPicker({
12618
12614
  const windowEnd = Math.min(windowStart + maxVisible, total);
12619
12615
  const above = windowStart;
12620
12616
  const below = total - windowEnd;
12617
+ const hasLockedRows = items.some((item) => item.lockable === false);
12621
12618
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
12622
12619
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Plugin menu" }),
12623
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 select \xB7 Enter/\u2190/\u2192 toggle \xB7 \u{1F512} = locked \xB7 Esc close" }),
12620
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12621
+ "\u2191/\u2193 select \xB7 Enter/\u2190/\u2192 toggle",
12622
+ hasLockedRows ? " \xB7 \u{1F512} = locked" : "",
12623
+ " \xB7 Esc close"
12624
+ ] }),
12624
12625
  /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: busy ? "Loading plugins\u2026" : "No plugins available." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12625
12626
  above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12626
12627
  items.slice(windowStart, windowEnd).map((item, i) => {
@@ -13546,11 +13547,11 @@ function WorktreeMonitor({
13546
13547
  useInput((input, key) => {
13547
13548
  if (isWorktreeMonitorCloseKey(input, key)) onClose();
13548
13549
  });
13549
- const TERMINAL_TTL_MS2 = 5 * 6e4;
13550
+ const TERMINAL_TTL_MS = 5 * 6e4;
13550
13551
  const list = Object.values(worktrees);
13551
13552
  const recent = list.filter((w) => {
13552
13553
  if (["active", "committing", "merging", "needs-review"].includes(w.status)) return true;
13553
- return nowTick - (w.allocatedAt ?? nowTick) < TERMINAL_TTL_MS2;
13554
+ return nowTick - (w.allocatedAt ?? nowTick) < TERMINAL_TTL_MS;
13554
13555
  });
13555
13556
  const active = list.filter((w) => ["active", "committing", "merging"].includes(w.status)).length;
13556
13557
  const merged = list.filter((w) => w.status === "merged").length;
@@ -13780,9 +13781,217 @@ function runGit(cwd, args) {
13780
13781
  }
13781
13782
  });
13782
13783
  }
13784
+
13785
+ // src/app-initial-state.ts
13786
+ function buildRestoredEntries(messages, restoredToolCalls) {
13787
+ if (!messages || messages.length === 0) return [];
13788
+ const visible = messages.filter((m) => m.role !== "system");
13789
+ if (visible.length === 0) return [];
13790
+ return rehydrateHistory(visible, 1, restoredToolCalls);
13791
+ }
13792
+ function createInitialState(options) {
13793
+ const {
13794
+ banner,
13795
+ appVersion,
13796
+ provider,
13797
+ model,
13798
+ cwd,
13799
+ family,
13800
+ keyTail,
13801
+ restoredEntries,
13802
+ enhanceEnabled,
13803
+ initialAgentsMonitorOpen
13804
+ } = options;
13805
+ const initialNextId = 1 + restoredEntries.length;
13806
+ return {
13807
+ entries: [
13808
+ ...banner ? [
13809
+ {
13810
+ id: 0,
13811
+ kind: "banner",
13812
+ version: appVersion ?? "dev",
13813
+ provider: provider ?? "agent",
13814
+ model,
13815
+ cwd,
13816
+ family,
13817
+ keyTail
13818
+ }
13819
+ ] : [],
13820
+ ...restoredEntries
13821
+ ],
13822
+ historyGen: 0,
13823
+ buffer: "",
13824
+ cursor: 0,
13825
+ streamingText: "",
13826
+ toolStream: null,
13827
+ status: "idle",
13828
+ interrupts: 0,
13829
+ steeringPending: false,
13830
+ steerSnapshot: null,
13831
+ hint: "",
13832
+ brain: { state: "idle" },
13833
+ brainPrompt: null,
13834
+ nextId: initialNextId,
13835
+ picker: { open: false, query: "", matches: [], selected: 0 },
13836
+ slashPicker: { open: false, query: "", matches: [], selected: 0 },
13837
+ runningTools: /* @__PURE__ */ new Map(),
13838
+ queue: [],
13839
+ nextQueueId: 1,
13840
+ inputHistory: [],
13841
+ historyIndex: 0,
13842
+ historyDraft: "",
13843
+ modelPicker: {
13844
+ open: false,
13845
+ step: "provider",
13846
+ providerOptions: [],
13847
+ modelOptions: [],
13848
+ filteredOptions: [],
13849
+ selected: 0,
13850
+ searchQuery: ""
13851
+ },
13852
+ autonomyPicker: { open: false, options: [], selected: 0 },
13853
+ modePicker: { open: false, modes: [], selected: 0 },
13854
+ designPicker: { open: false, kits: [], selected: 0, stack: "web" },
13855
+ promptPicker: {
13856
+ open: false,
13857
+ all: [],
13858
+ categories: [],
13859
+ recentSlugs: [],
13860
+ catIndex: 0,
13861
+ selected: 0
13862
+ },
13863
+ resumePicker: {
13864
+ open: false,
13865
+ sessions: [],
13866
+ selected: 0,
13867
+ busy: false,
13868
+ hint: void 0,
13869
+ error: void 0
13870
+ },
13871
+ settingsPicker: {
13872
+ open: false,
13873
+ field: 0,
13874
+ lastSettingsField: 0,
13875
+ filter: "",
13876
+ mode: "off",
13877
+ delayMs: 0,
13878
+ titleAnimation: true,
13879
+ yolo: false,
13880
+ streamFleet: true,
13881
+ chime: false,
13882
+ confirmExit: true,
13883
+ nextPrediction: false,
13884
+ featureMcp: true,
13885
+ featurePlugins: true,
13886
+ featureMemory: true,
13887
+ featureSkills: true,
13888
+ featureModelsRegistry: true,
13889
+ tokenSavingTier: "off",
13890
+ allowOutsideProjectRoot: true,
13891
+ contextAutoCompact: true,
13892
+ contextStrategy: "hybrid",
13893
+ contextMode: "balanced",
13894
+ maxConcurrent: 10,
13895
+ logLevel: "info",
13896
+ auditLevel: "standard",
13897
+ indexOnStart: true,
13898
+ multiDiffSummaryThreshold: 5,
13899
+ maxIterations: 500,
13900
+ autoProceedMaxIterations: 50,
13901
+ enhanceDelayMs: 6e4,
13902
+ enhanceEnabled: true,
13903
+ enhanceLanguage: "original",
13904
+ debugStream: false,
13905
+ statuslineMode: "detailed",
13906
+ reasoningMode: "auto",
13907
+ reasoningEffort: "high",
13908
+ reasoningPreserve: false,
13909
+ thinkingWord: "thinking",
13910
+ thinkingWordEditing: false,
13911
+ thinkingWordDraft: "",
13912
+ cacheTtl: "default",
13913
+ configScope: "global",
13914
+ animationStyle: "rainbow"
13915
+ },
13916
+ statuslinePicker: {
13917
+ open: false,
13918
+ field: 0,
13919
+ hiddenItems: [],
13920
+ visibleChips: [],
13921
+ hint: void 0
13922
+ },
13923
+ pluginPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
13924
+ authPanel: AUTH_PANEL_INITIAL,
13925
+ projectPicker: {
13926
+ open: false,
13927
+ allItems: [],
13928
+ items: [],
13929
+ selected: 0,
13930
+ filter: "",
13931
+ hint: void 0
13932
+ },
13933
+ fKeyPicker: { open: false, selected: 0 },
13934
+ confirmQueue: [],
13935
+ enhance: null,
13936
+ enhanceEnabled,
13937
+ enhanceBusy: false,
13938
+ escConfirm: null,
13939
+ sendModePicker: null,
13940
+ contextChipVersion: 0,
13941
+ fleet: {},
13942
+ leader: {
13943
+ iterations: 0,
13944
+ toolCalls: 0,
13945
+ recentTools: [],
13946
+ currentTool: void 0,
13947
+ startedAt: Date.now(),
13948
+ lastEventAt: Date.now(),
13949
+ iterating: false
13950
+ },
13951
+ fleetCost: 0,
13952
+ fleetTokens: { input: 0, output: 0 },
13953
+ fleetConcurrency: 4,
13954
+ streamFleet: true,
13955
+ monitorOpen: false,
13956
+ agentsMonitorOpen: initialAgentsMonitorOpen ?? false,
13957
+ helpOpen: false,
13958
+ todosMonitorOpen: false,
13959
+ queuePanelOpen: false,
13960
+ processListOpen: false,
13961
+ auditPanelOpen: false,
13962
+ planPanelOpen: false,
13963
+ kanbanPanelOpen: false,
13964
+ goalPanelOpen: false,
13965
+ sessionsPanelOpen: false,
13966
+ sessionsPanel: { sessions: [], busy: false, selected: -1 },
13967
+ sessionResumeConfirm: null,
13968
+ collabSession: null,
13969
+ checkpoints: [],
13970
+ rewindOverlay: null,
13971
+ eternalStage: null,
13972
+ goalSummary: null,
13973
+ autoPhase: null,
13974
+ sddBoard: null,
13975
+ worktrees: {},
13976
+ worktreeMonitorOpen: false,
13977
+ coordinator: {
13978
+ goals: [],
13979
+ timeline: [],
13980
+ knowledgeCount: 0,
13981
+ monitorOpen: false,
13982
+ healthy: false
13983
+ },
13984
+ scrollOffset: 0,
13985
+ totalLines: 0,
13986
+ viewportRows: 0,
13987
+ pendingNewLines: 0,
13988
+ debugStreamStats: null,
13989
+ countdown: null
13990
+ };
13991
+ }
13783
13992
  var MB = 1024 * 1024;
13784
13993
  function defaultHeapLogPath() {
13785
- return path6.join(wstackGlobalRoot(), "logs", "heap.jsonl");
13994
+ return path9.join(wstackGlobalRoot(), "logs", "heap.jsonl");
13786
13995
  }
13787
13996
  function takeHeapSample() {
13788
13997
  const m = process.memoryUsage();
@@ -13812,7 +14021,7 @@ function startHeapWatchdog(opts = {}) {
13812
14021
  const append = (line) => {
13813
14022
  writeChain = writeChain.then(async () => {
13814
14023
  if (!dirReady) {
13815
- await fsp.mkdir(path6.dirname(logPath), { recursive: true });
14024
+ await fsp.mkdir(path9.dirname(logPath), { recursive: true });
13816
14025
  dirReady = true;
13817
14026
  }
13818
14027
  await fsp.appendFile(logPath, `${line}
@@ -14603,7 +14812,7 @@ async function loadIndex(root) {
14603
14812
  async function walk(root, rel, depth, out) {
14604
14813
  if (out.length >= MAX_FILES_INDEXED) return;
14605
14814
  if (depth > MAX_DEPTH) return;
14606
- const dir = rel ? path6.join(root, rel) : root;
14815
+ const dir = rel ? path9.join(root, rel) : root;
14607
14816
  let entries;
14608
14817
  try {
14609
14818
  entries = await fsp.readdir(dir, { withFileTypes: true });
@@ -14702,7 +14911,7 @@ function useFileSearch(options) {
14702
14911
  dispatch({ type: "pickerClose" });
14703
14912
  return;
14704
14913
  }
14705
- const absPath = path6.isAbsolute(picked) ? picked : path6.join(projectRoot, picked);
14914
+ const absPath = path9.isAbsolute(picked) ? picked : path9.join(projectRoot, picked);
14706
14915
  try {
14707
14916
  const data = await fsp.readFile(absPath, "utf8");
14708
14917
  const token = await builder.registerFile({
@@ -15452,50 +15661,164 @@ function usePickerKeys(host) {
15452
15661
  [host]
15453
15662
  );
15454
15663
  }
15455
-
15456
- // src/queue-slash.ts
15457
- 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";
15458
- function createQueueSlashCommand(deps) {
15459
- return {
15460
- name: "queue",
15461
- description: "List, clear, or delete pending messages typed while the agent was running.",
15462
- async run(args) {
15463
- const out = handleQueueCommand(args, deps);
15464
- return { message: out };
15465
- }
15466
- };
15467
- }
15468
- function handleQueueCommand(args, deps) {
15469
- const trimmed = args.trim();
15470
- const [sub, ...rest] = trimmed.split(/\s+/);
15471
- const subcommand = sub?.toLowerCase() ?? "";
15472
- if (subcommand === "" || subcommand === "list") {
15473
- return renderList(deps.getQueue());
15474
- }
15475
- if (subcommand === "picker") {
15476
- if (!deps.getPickerEnabled || !deps.setPickerEnabled) {
15477
- return "Send-mode picker toggle is unavailable in this session.";
15478
- }
15479
- const arg = rest[0]?.toLowerCase() ?? "";
15480
- if (arg === "" || arg === "status") {
15481
- return `Mid-run send-mode picker is ${deps.getPickerEnabled() ? "on" : "off"}.`;
15482
- }
15483
- if (arg === "on" || arg === "off") {
15484
- const next = arg === "on";
15485
- deps.setPickerEnabled(next);
15486
- 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.";
15487
- }
15488
- return "Usage: /queue picker on|off";
15489
- }
15490
- if (subcommand === "clear") {
15491
- const before = deps.getQueue().length;
15492
- if (before === 0) return "Queue is already empty.";
15493
- deps.clear();
15494
- return `Cleared ${before} queued message${before === 1 ? "" : "s"}.`;
15495
- }
15496
- if (subcommand === "delete" || subcommand === "del" || subcommand === "rm") {
15497
- if (rest.length === 0) return "Usage: /queue delete <position> [<position>\u2026]";
15498
- const queue = deps.getQueue();
15664
+ function useInputHistoryPersistence({
15665
+ projectRoot,
15666
+ inputHistory,
15667
+ dispatch
15668
+ }) {
15669
+ const inputHistoryStore = useMemo(() => {
15670
+ if (!projectRoot) return null;
15671
+ const file = resolveWstackPaths({ projectRoot }).projectInputHistory;
15672
+ return new InputHistoryStore(file, new DefaultSecretScrubber(), INPUT_HISTORY_DEFAULT_MAX);
15673
+ }, [projectRoot]);
15674
+ const inputHistoryLoadedRef = useRef(false);
15675
+ const inputHistorySaveTimerRef = useRef(void 0);
15676
+ const inputHistoryLastSavedRef = useRef("");
15677
+ useEffect(() => {
15678
+ if (!inputHistoryStore) return;
15679
+ let cancelled = false;
15680
+ inputHistoryStore.load().then((entries) => {
15681
+ if (cancelled) return;
15682
+ inputHistoryLoadedRef.current = true;
15683
+ inputHistoryLastSavedRef.current = JSON.stringify(entries);
15684
+ if (entries.length === 0) return;
15685
+ dispatch({ type: "setInputHistory", entries });
15686
+ }).catch(() => {
15687
+ if (!cancelled) inputHistoryLoadedRef.current = true;
15688
+ });
15689
+ return () => {
15690
+ cancelled = true;
15691
+ };
15692
+ }, [inputHistoryStore, dispatch]);
15693
+ useEffect(() => {
15694
+ if (!inputHistoryStore || !inputHistoryLoadedRef.current) return;
15695
+ const snapshot = JSON.stringify(inputHistory);
15696
+ if (snapshot === inputHistoryLastSavedRef.current) return;
15697
+ if (inputHistorySaveTimerRef.current) clearTimeout(inputHistorySaveTimerRef.current);
15698
+ inputHistorySaveTimerRef.current = setTimeout(() => {
15699
+ inputHistoryLastSavedRef.current = snapshot;
15700
+ inputHistoryStore.save(inputHistory).catch(() => {
15701
+ });
15702
+ }, 200);
15703
+ return () => {
15704
+ if (inputHistorySaveTimerRef.current) clearTimeout(inputHistorySaveTimerRef.current);
15705
+ };
15706
+ }, [inputHistory, inputHistoryStore]);
15707
+ }
15708
+ function buildModePickerOptions(result) {
15709
+ return toModeOptions(result.modes, result.activeId);
15710
+ }
15711
+ function useModePicker({
15712
+ dispatch,
15713
+ getModes
15714
+ }) {
15715
+ const openModePicker = useCallback(async () => {
15716
+ if (!getModes) return;
15717
+ const result = await getModes();
15718
+ const options = buildModePickerOptions(result);
15719
+ dispatch({ type: "modePickerOpen", modes: options });
15720
+ }, [dispatch, getModes]);
15721
+ return { openModePicker };
15722
+ }
15723
+ function buildPromptPickerPayload(all, recentSlugs) {
15724
+ const entries = all.map((e) => ({
15725
+ slug: e.slug,
15726
+ title: e.title,
15727
+ description: e.description,
15728
+ category: e.category,
15729
+ source: e.source,
15730
+ content: e.content,
15731
+ favorite: e.favorite
15732
+ }));
15733
+ const hasFavorites = entries.some((e) => e.favorite);
15734
+ const categories = [
15735
+ "all",
15736
+ ...recentSlugs.length > 0 ? ["\u{1F558} recent"] : [],
15737
+ ...hasFavorites ? ["\u2605 favorites"] : [],
15738
+ ...Array.from(new Set(entries.map((e) => e.category))).sort()
15739
+ ];
15740
+ return { all: entries, categories, recentSlugs };
15741
+ }
15742
+ function usePromptPicker({
15743
+ projectRoot,
15744
+ dispatch
15745
+ }) {
15746
+ const promptLoaderRef = useRef(null);
15747
+ const promptUsageRef = useRef(null);
15748
+ const getPromptLoader = useCallback(() => {
15749
+ if (!promptLoaderRef.current && projectRoot) {
15750
+ let bundledDir;
15751
+ try {
15752
+ const req = createRequire(import.meta.url);
15753
+ bundledDir = path9.join(
15754
+ path9.dirname(req.resolve("@wrongstack/core/package.json")),
15755
+ "data",
15756
+ "prompts"
15757
+ );
15758
+ } catch {
15759
+ bundledDir = void 0;
15760
+ }
15761
+ const paths = resolveWstackPaths({ projectRoot });
15762
+ promptLoaderRef.current = new DefaultPromptLoader({ paths, bundledDir });
15763
+ promptUsageRef.current = new PromptUsageStore(paths.promptUsage);
15764
+ }
15765
+ return promptLoaderRef.current;
15766
+ }, [projectRoot]);
15767
+ const openPromptPicker = useCallback(async () => {
15768
+ const loader = getPromptLoader();
15769
+ if (!loader) return;
15770
+ loader.invalidateCache();
15771
+ const all = await loader.list();
15772
+ const recentSlugs = (await promptUsageRef.current?.recent(50).catch(() => []))?.map((r) => r.slug) ?? [];
15773
+ const payload = buildPromptPickerPayload(all, recentSlugs);
15774
+ dispatch({ type: "promptPickerOpen", ...payload });
15775
+ }, [dispatch, getPromptLoader]);
15776
+ return { openPromptPicker };
15777
+ }
15778
+
15779
+ // src/queue-slash.ts
15780
+ 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";
15781
+ function createQueueSlashCommand(deps) {
15782
+ return {
15783
+ name: "queue",
15784
+ description: "List, clear, or delete pending messages typed while the agent was running.",
15785
+ async run(args) {
15786
+ const out = handleQueueCommand(args, deps);
15787
+ return { message: out };
15788
+ }
15789
+ };
15790
+ }
15791
+ function handleQueueCommand(args, deps) {
15792
+ const trimmed = args.trim();
15793
+ const [sub, ...rest] = trimmed.split(/\s+/);
15794
+ const subcommand = sub?.toLowerCase() ?? "";
15795
+ if (subcommand === "" || subcommand === "list") {
15796
+ return renderList(deps.getQueue());
15797
+ }
15798
+ if (subcommand === "picker") {
15799
+ if (!deps.getPickerEnabled || !deps.setPickerEnabled) {
15800
+ return "Send-mode picker toggle is unavailable in this session.";
15801
+ }
15802
+ const arg = rest[0]?.toLowerCase() ?? "";
15803
+ if (arg === "" || arg === "status") {
15804
+ return `Mid-run send-mode picker is ${deps.getPickerEnabled() ? "on" : "off"}.`;
15805
+ }
15806
+ if (arg === "on" || arg === "off") {
15807
+ const next = arg === "on";
15808
+ deps.setPickerEnabled(next);
15809
+ 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.";
15810
+ }
15811
+ return "Usage: /queue picker on|off";
15812
+ }
15813
+ if (subcommand === "clear") {
15814
+ const before = deps.getQueue().length;
15815
+ if (before === 0) return "Queue is already empty.";
15816
+ deps.clear();
15817
+ return `Cleared ${before} queued message${before === 1 ? "" : "s"}.`;
15818
+ }
15819
+ if (subcommand === "delete" || subcommand === "del" || subcommand === "rm") {
15820
+ if (rest.length === 0) return "Usage: /queue delete <position> [<position>\u2026]";
15821
+ const queue = deps.getQueue();
15499
15822
  if (queue.length === 0) return "Queue is empty \u2014 nothing to delete.";
15500
15823
  const parsed = [];
15501
15824
  const invalid = [];
@@ -15615,6 +15938,90 @@ function useQueueManager({
15615
15938
  };
15616
15939
  }, [slashRegistry, stateRef, dispatch, getSettings, saveSettings, midRunSendPickerRef]);
15617
15940
  }
15941
+ function statuslineHiddenDiffers(hookHidden, pickerHidden) {
15942
+ const currentHidden = new Set(hookHidden);
15943
+ const pickerHiddenSet = new Set(pickerHidden);
15944
+ return currentHidden.size !== pickerHiddenSet.size || pickerHidden.some((item) => !currentHidden.has(item)) || hookHidden.some((item) => !pickerHiddenSet.has(item));
15945
+ }
15946
+ function useStatuslineHiddenSync({
15947
+ pickerOpen,
15948
+ pickerHidden,
15949
+ hiddenItems,
15950
+ setHiddenItems
15951
+ }) {
15952
+ useEffect(() => {
15953
+ if (!pickerOpen) return;
15954
+ if (statuslineHiddenDiffers(hiddenItems, pickerHidden)) {
15955
+ setHiddenItems([...pickerHidden]);
15956
+ }
15957
+ }, [pickerHidden, pickerOpen, setHiddenItems, hiddenItems]);
15958
+ }
15959
+ function computeStreamChipActions(input) {
15960
+ const actions = [];
15961
+ const visible = new Set(input.visibleChipKeys);
15962
+ if (input.brainPromptNow && !input.brainPromptPrev) {
15963
+ actions.push({ type: "statuslineChipShow", key: "brain", expiresIn: 5 });
15964
+ } else if (!input.brainPromptNow && input.brainPromptPrev && visible.has("brain")) {
15965
+ actions.push({ type: "statuslineChipExpire", key: "brain" });
15966
+ }
15967
+ if (input.enhanceNow && !input.enhancePrev) {
15968
+ actions.push({ type: "statuslineChipShow", key: "enhance", expiresIn: 5 });
15969
+ } else if (!input.enhanceNow && input.enhancePrev && visible.has("enhance")) {
15970
+ actions.push({ type: "statuslineChipExpire", key: "enhance" });
15971
+ }
15972
+ return actions;
15973
+ }
15974
+ function computeExpiredChipKeys(chips, now = Date.now()) {
15975
+ return chips.filter((chip) => isChipExpired(chip, now)).map((chip) => chip.key);
15976
+ }
15977
+ function useStreamChipExpiration({
15978
+ brainPrompt,
15979
+ enhance,
15980
+ visibleChips,
15981
+ dispatch
15982
+ }) {
15983
+ const prevBrainPromptRef = useRef(brainPrompt);
15984
+ const prevEnhanceRef = useRef(enhance);
15985
+ useEffect(() => {
15986
+ const actions = computeStreamChipActions({
15987
+ brainPromptNow: !!brainPrompt,
15988
+ brainPromptPrev: !!prevBrainPromptRef.current,
15989
+ enhanceNow: !!enhance,
15990
+ enhancePrev: !!prevEnhanceRef.current,
15991
+ visibleChipKeys: visibleChips.map((chip) => chip.key)
15992
+ });
15993
+ for (const action of actions) dispatch(action);
15994
+ prevBrainPromptRef.current = brainPrompt;
15995
+ prevEnhanceRef.current = enhance;
15996
+ }, [brainPrompt, enhance, visibleChips, dispatch]);
15997
+ useEffect(() => {
15998
+ const interval = setInterval(() => {
15999
+ const expired = computeExpiredChipKeys(visibleChips, Date.now());
16000
+ for (const key of expired) {
16001
+ dispatch({ type: "statuslineChipExpire", key });
16002
+ }
16003
+ }, 3e4);
16004
+ return () => clearInterval(interval);
16005
+ }, [visibleChips, dispatch]);
16006
+ }
16007
+ function formatWorkingDirChip(workingDir, projectRoot) {
16008
+ if (!workingDir || workingDir === projectRoot) return void 0;
16009
+ const rel = path9.relative(projectRoot, workingDir) || ".";
16010
+ if (rel === ".") return void 0;
16011
+ return rel.replaceAll(path9.sep, "/");
16012
+ }
16013
+ function useWorkingDirChip(ctx, projectRoot) {
16014
+ const [workingDirChip, setWorkingDirChip] = useState(
16015
+ () => formatWorkingDirChip(ctx.workingDir, projectRoot)
16016
+ );
16017
+ useEffect(() => {
16018
+ setWorkingDirChip(formatWorkingDirChip(ctx.workingDir, projectRoot));
16019
+ return ctx.onWorkingDirChanged((newDir) => {
16020
+ setWorkingDirChip(formatWorkingDirChip(newDir, projectRoot));
16021
+ });
16022
+ }, [ctx, projectRoot]);
16023
+ return workingDirChip;
16024
+ }
15618
16025
  function useStatuslineState(opts) {
15619
16026
  const [liveModel, setLiveModel] = useState(opts.model);
15620
16027
  const [liveProvider, setLiveProvider] = useState(opts.provider ?? "agent");
@@ -16267,6 +16674,22 @@ function createPanelOpenDispatcher(deps) {
16267
16674
  };
16268
16675
  }
16269
16676
 
16677
+ // src/submit-history.ts
16678
+ function shouldPushSubmittedHistory(raw) {
16679
+ const trimmed = raw.trim();
16680
+ if (!trimmed) return { push: false, route: "empty", trimmed };
16681
+ if (trimmed === "/image" || trimmed === "/paste-image") {
16682
+ return { push: true, route: "paste-image", trimmed };
16683
+ }
16684
+ if (trimmed === "/prompt" || trimmed === "/prompts-browse") {
16685
+ return { push: true, route: "prompt-picker", trimmed };
16686
+ }
16687
+ if (trimmed.startsWith("/")) {
16688
+ return { push: true, route: "slash", trimmed };
16689
+ }
16690
+ return { push: true, route: "normal", trimmed };
16691
+ }
16692
+
16270
16693
  // src/paste-accumulator.ts
16271
16694
  var BEGIN = "[200~";
16272
16695
  var END = "[201~";
@@ -16295,7 +16718,7 @@ function feedPaste(accum, input) {
16295
16718
  if (accum === null && !input.includes(BEGIN)) {
16296
16719
  if (input.startsWith("[") && !input.startsWith(BEGIN) && !input.startsWith(END)) {
16297
16720
  if (PARTIAL_ANSI_RE.test(input)) {
16298
- return { accum: "", complete: null };
16721
+ return { accum: null, complete: null };
16299
16722
  }
16300
16723
  return null;
16301
16724
  }
@@ -16774,11 +17197,17 @@ function App({
16774
17197
  useEffect(() => {
16775
17198
  setStatuslineHiddenItems(hiddenItems);
16776
17199
  saveStatuslineHiddenItems(hiddenItems).catch?.((err) => {
16777
- console.error("[statusline] failed to persist hidden items:", err);
17200
+ console.error(
17201
+ JSON.stringify({
17202
+ level: "error",
17203
+ event: "statusline.persist_hidden_failed",
17204
+ message: err instanceof Error ? err.message : String(err),
17205
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17206
+ })
17207
+ );
16778
17208
  });
16779
17209
  }, [setStatuslineHiddenItems, saveStatuslineHiddenItems, hiddenItems]);
16780
17210
  const projectRoot = agent.ctx.projectRoot;
16781
- const promptLoaderRef = useRef(null);
16782
17211
  const promptUsageRef = useRef(null);
16783
17212
  const refreshGoalSummary = useCallback(() => {
16784
17213
  if (!projectRoot) return;
@@ -16810,331 +17239,41 @@ function App({
16810
17239
  useEffect(() => {
16811
17240
  refreshGoalSummary();
16812
17241
  }, [refreshGoalSummary]);
16813
- const restoredEntries = (() => {
16814
- const msgs = agent.ctx.messages;
16815
- if (!msgs || msgs.length === 0) return [];
16816
- const visible = msgs.filter((m) => m.role !== "system");
16817
- if (visible.length === 0) return [];
16818
- return rehydrateHistory(
16819
- visible,
16820
- /* startId */
16821
- 1,
16822
- restoredToolCalls
16823
- );
16824
- })();
16825
- const initialNextId = 1 + restoredEntries.length;
16826
- const [state, dispatch] = useReducer(reducer, {
16827
- entries: [
16828
- ...banner ? [
16829
- {
16830
- id: 0,
16831
- kind: "banner",
16832
- version: appVersion ?? "dev",
16833
- provider: provider ?? "agent",
16834
- model,
16835
- cwd: agent.ctx.cwd,
16836
- family,
16837
- keyTail
16838
- }
16839
- ] : [],
16840
- ...restoredEntries
16841
- ],
16842
- historyGen: 0,
16843
- buffer: "",
16844
- cursor: 0,
16845
- streamingText: "",
16846
- toolStream: null,
16847
- status: "idle",
16848
- interrupts: 0,
16849
- steeringPending: false,
16850
- steerSnapshot: null,
16851
- hint: "",
16852
- brain: { state: "idle" },
16853
- brainPrompt: null,
16854
- nextId: initialNextId,
16855
- picker: { open: false, query: "", matches: [], selected: 0 },
16856
- slashPicker: { open: false, query: "", matches: [], selected: 0 },
16857
- runningTools: /* @__PURE__ */ new Map(),
16858
- queue: [],
16859
- nextQueueId: 1,
16860
- inputHistory: [],
16861
- historyIndex: 0,
16862
- historyDraft: "",
16863
- modelPicker: {
16864
- open: false,
16865
- step: "provider",
16866
- providerOptions: [],
16867
- modelOptions: [],
16868
- filteredOptions: [],
16869
- selected: 0,
16870
- searchQuery: ""
16871
- },
16872
- autonomyPicker: { open: false, options: [], selected: 0 },
16873
- modePicker: { open: false, modes: [], selected: 0 },
16874
- designPicker: { open: false, kits: [], selected: 0, stack: "web" },
16875
- promptPicker: {
16876
- open: false,
16877
- all: [],
16878
- categories: [],
16879
- recentSlugs: [],
16880
- catIndex: 0,
16881
- selected: 0
16882
- },
16883
- resumePicker: {
16884
- open: false,
16885
- sessions: [],
16886
- selected: 0,
16887
- busy: false,
16888
- hint: void 0,
16889
- error: void 0
16890
- },
16891
- settingsPicker: {
16892
- open: false,
16893
- field: 0,
16894
- lastSettingsField: 0,
16895
- filter: "",
16896
- mode: "off",
16897
- delayMs: 0,
16898
- titleAnimation: true,
16899
- yolo: false,
16900
- streamFleet: true,
16901
- chime: false,
16902
- confirmExit: true,
16903
- nextPrediction: false,
16904
- featureMcp: true,
16905
- featurePlugins: true,
16906
- featureMemory: true,
16907
- featureSkills: true,
16908
- featureModelsRegistry: true,
16909
- tokenSavingTier: "off",
16910
- allowOutsideProjectRoot: true,
16911
- contextAutoCompact: true,
16912
- contextStrategy: "hybrid",
16913
- contextMode: "balanced",
16914
- maxConcurrent: 10,
16915
- logLevel: "info",
16916
- auditLevel: "standard",
16917
- indexOnStart: true,
16918
- multiDiffSummaryThreshold: 5,
16919
- maxIterations: 500,
16920
- autoProceedMaxIterations: 50,
16921
- enhanceDelayMs: 6e4,
16922
- enhanceEnabled: true,
16923
- enhanceLanguage: "original",
16924
- debugStream: false,
16925
- statuslineMode: "detailed",
16926
- reasoningMode: "auto",
16927
- reasoningEffort: "high",
16928
- reasoningPreserve: false,
16929
- thinkingWord: "thinking",
16930
- thinkingWordEditing: false,
16931
- thinkingWordDraft: "",
16932
- cacheTtl: "default",
16933
- configScope: "global",
16934
- animationStyle: "rainbow"
16935
- },
16936
- statuslinePicker: { open: false, field: 0, hiddenItems: [], visibleChips: [], hint: void 0 },
16937
- pluginPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
16938
- authPanel: AUTH_PANEL_INITIAL,
16939
- projectPicker: {
16940
- open: false,
16941
- allItems: [],
16942
- items: [],
16943
- selected: 0,
16944
- filter: "",
16945
- hint: void 0
16946
- },
16947
- fKeyPicker: { open: false, selected: 0 },
16948
- confirmQueue: [],
16949
- enhance: null,
16950
- enhanceEnabled,
16951
- enhanceBusy: false,
16952
- escConfirm: null,
16953
- sendModePicker: null,
16954
- contextChipVersion: 0,
16955
- fleet: {},
16956
- leader: {
16957
- iterations: 0,
16958
- toolCalls: 0,
16959
- recentTools: [],
16960
- currentTool: void 0,
16961
- startedAt: Date.now(),
16962
- lastEventAt: Date.now(),
16963
- iterating: false
16964
- },
16965
- fleetCost: 0,
16966
- fleetTokens: { input: 0, output: 0 },
16967
- fleetConcurrency: 4,
16968
- streamFleet: true,
16969
- monitorOpen: false,
16970
- agentsMonitorOpen: initialAgentsMonitorOpen ?? false,
16971
- helpOpen: false,
16972
- todosMonitorOpen: false,
16973
- queuePanelOpen: false,
16974
- processListOpen: false,
16975
- auditPanelOpen: false,
16976
- planPanelOpen: false,
16977
- kanbanPanelOpen: false,
16978
- goalPanelOpen: false,
16979
- sessionsPanelOpen: false,
16980
- sessionsPanel: { sessions: [], busy: false, selected: -1 },
16981
- sessionResumeConfirm: null,
16982
- collabSession: null,
16983
- checkpoints: [],
16984
- rewindOverlay: null,
16985
- eternalStage: null,
16986
- goalSummary: null,
16987
- autoPhase: null,
16988
- sddBoard: null,
16989
- worktrees: {},
16990
- worktreeMonitorOpen: false,
16991
- coordinator: {
16992
- goals: [],
16993
- timeline: [],
16994
- knowledgeCount: 0,
16995
- monitorOpen: false,
16996
- healthy: false
16997
- },
16998
- scrollOffset: 0,
16999
- totalLines: 0,
17000
- viewportRows: 0,
17001
- pendingNewLines: 0,
17002
- debugStreamStats: null,
17003
- countdown: null
17242
+ const restoredEntries = buildRestoredEntries(agent.ctx.messages, restoredToolCalls);
17243
+ const [state, dispatch] = useReducer(
17244
+ reducer,
17245
+ createInitialState({
17246
+ banner,
17247
+ appVersion,
17248
+ provider,
17249
+ model,
17250
+ cwd: agent.ctx.cwd,
17251
+ family,
17252
+ keyTail,
17253
+ restoredEntries,
17254
+ enhanceEnabled,
17255
+ initialAgentsMonitorOpen
17256
+ })
17257
+ );
17258
+ useInputHistoryPersistence({
17259
+ projectRoot,
17260
+ inputHistory: state.inputHistory,
17261
+ dispatch
17262
+ });
17263
+ const { openPromptPicker } = usePromptPicker({ projectRoot, dispatch });
17264
+ const { openModePicker } = useModePicker({ dispatch, getModes });
17265
+ useStatuslineHiddenSync({
17266
+ pickerOpen: state.statuslinePicker.open,
17267
+ pickerHidden: state.statuslinePicker.hiddenItems,
17268
+ hiddenItems,
17269
+ setHiddenItems: (items) => setHiddenItems(items)
17270
+ });
17271
+ useStreamChipExpiration({
17272
+ brainPrompt: state.brainPrompt,
17273
+ enhance: state.enhance,
17274
+ visibleChips: state.statuslinePicker.visibleChips,
17275
+ dispatch
17004
17276
  });
17005
- const inputHistoryStore = useMemo(() => {
17006
- if (!projectRoot) return null;
17007
- const file = resolveWstackPaths({ projectRoot }).projectInputHistory;
17008
- return new InputHistoryStore(file, new DefaultSecretScrubber(), INPUT_HISTORY_DEFAULT_MAX);
17009
- }, [projectRoot]);
17010
- const inputHistoryLoadedRef = useRef(false);
17011
- const inputHistorySaveTimerRef = useRef(void 0);
17012
- const inputHistoryLastSavedRef = useRef("");
17013
- useEffect(() => {
17014
- if (!inputHistoryStore) return;
17015
- let cancelled = false;
17016
- inputHistoryStore.load().then((entries) => {
17017
- if (cancelled) return;
17018
- inputHistoryLoadedRef.current = true;
17019
- inputHistoryLastSavedRef.current = JSON.stringify(entries);
17020
- if (entries.length === 0) return;
17021
- dispatch({ type: "setInputHistory", entries });
17022
- }).catch(() => {
17023
- if (!cancelled) inputHistoryLoadedRef.current = true;
17024
- });
17025
- return () => {
17026
- cancelled = true;
17027
- };
17028
- }, [inputHistoryStore]);
17029
- useEffect(() => {
17030
- if (!inputHistoryStore || !inputHistoryLoadedRef.current) return;
17031
- const snapshot = JSON.stringify(state.inputHistory);
17032
- if (snapshot === inputHistoryLastSavedRef.current) return;
17033
- if (inputHistorySaveTimerRef.current) clearTimeout(inputHistorySaveTimerRef.current);
17034
- inputHistorySaveTimerRef.current = setTimeout(() => {
17035
- inputHistoryLastSavedRef.current = snapshot;
17036
- inputHistoryStore.save(state.inputHistory).catch(() => {
17037
- });
17038
- }, 200);
17039
- return () => {
17040
- if (inputHistorySaveTimerRef.current) clearTimeout(inputHistorySaveTimerRef.current);
17041
- };
17042
- }, [state.inputHistory, inputHistoryStore]);
17043
- const getPromptLoader = () => {
17044
- if (!promptLoaderRef.current && projectRoot) {
17045
- let bundledDir;
17046
- try {
17047
- const req = createRequire(import.meta.url);
17048
- bundledDir = path6.join(
17049
- path6.dirname(req.resolve("@wrongstack/core/package.json")),
17050
- "data",
17051
- "prompts"
17052
- );
17053
- } catch {
17054
- bundledDir = void 0;
17055
- }
17056
- const paths = resolveWstackPaths({ projectRoot });
17057
- promptLoaderRef.current = new DefaultPromptLoader({ paths, bundledDir });
17058
- promptUsageRef.current = new PromptUsageStore(paths.promptUsage);
17059
- }
17060
- return promptLoaderRef.current;
17061
- };
17062
- const openPromptPicker = async () => {
17063
- const loader = getPromptLoader();
17064
- if (!loader) return;
17065
- loader.invalidateCache();
17066
- const all = await loader.list();
17067
- const entries = all.map((e) => ({
17068
- slug: e.slug,
17069
- title: e.title,
17070
- description: e.description,
17071
- category: e.category,
17072
- source: e.source,
17073
- content: e.content,
17074
- favorite: e.favorite
17075
- }));
17076
- const recentSlugs = (await promptUsageRef.current?.recent(50).catch(() => []))?.map((r) => r.slug) ?? [];
17077
- const hasFavorites = entries.some((e) => e.favorite);
17078
- const cats = [
17079
- "all",
17080
- ...recentSlugs.length > 0 ? ["\u{1F558} recent"] : [],
17081
- ...hasFavorites ? ["\u2605 favorites"] : [],
17082
- ...Array.from(new Set(entries.map((e) => e.category))).sort()
17083
- ];
17084
- dispatch({ type: "promptPickerOpen", all: entries, categories: cats, recentSlugs });
17085
- };
17086
- const openModePicker = useCallback(async () => {
17087
- if (!getModes) return;
17088
- const result = await getModes();
17089
- const options = toModeOptions(result.modes, result.activeId);
17090
- dispatch({ type: "modePickerOpen", modes: options });
17091
- }, [getModes]);
17092
- useEffect(() => {
17093
- if (state.statuslinePicker.open) {
17094
- const pickerHidden = state.statuslinePicker.hiddenItems;
17095
- const currentHidden = new Set(hiddenItems);
17096
- const pickerHiddenSet = new Set(pickerHidden);
17097
- const differs = currentHidden.size !== pickerHiddenSet.size || pickerHidden.some((item) => !currentHidden.has(item)) || hiddenItems.some((item) => !pickerHiddenSet.has(item));
17098
- if (differs) {
17099
- setHiddenItems([...pickerHidden]);
17100
- }
17101
- }
17102
- }, [
17103
- state.statuslinePicker.hiddenItems,
17104
- state.statuslinePicker.open,
17105
- setHiddenItems,
17106
- hiddenItems
17107
- ]);
17108
- const prevBrainPromptRef = useRef(state.brainPrompt);
17109
- const prevEnhanceRef = useRef(state.enhance);
17110
- useEffect(() => {
17111
- if (state.brainPrompt && !prevBrainPromptRef.current) {
17112
- dispatch({ type: "statuslineChipShow", key: "brain", expiresIn: 5 });
17113
- } else if (!state.brainPrompt && prevBrainPromptRef.current) {
17114
- if (state.statuslinePicker.visibleChips.some((c) => c.key === "brain")) {
17115
- dispatch({ type: "statuslineChipExpire", key: "brain" });
17116
- }
17117
- }
17118
- prevBrainPromptRef.current = state.brainPrompt;
17119
- if (state.enhance && !prevEnhanceRef.current) {
17120
- dispatch({ type: "statuslineChipShow", key: "enhance", expiresIn: 5 });
17121
- } else if (!state.enhance && prevEnhanceRef.current) {
17122
- if (state.statuslinePicker.visibleChips.some((c) => c.key === "enhance")) {
17123
- dispatch({ type: "statuslineChipExpire", key: "enhance" });
17124
- }
17125
- }
17126
- prevEnhanceRef.current = state.enhance;
17127
- }, [state.brainPrompt, state.enhance, state.statuslinePicker.visibleChips, dispatch]);
17128
- useEffect(() => {
17129
- const interval = setInterval(() => {
17130
- const now = Date.now();
17131
- const expired = state.statuslinePicker.visibleChips.filter((c) => isChipExpired(c, now));
17132
- for (const chip of expired) {
17133
- dispatch({ type: "statuslineChipExpire", key: chip.key });
17134
- }
17135
- }, 3e4);
17136
- return () => clearInterval(interval);
17137
- }, [state.statuslinePicker.visibleChips, dispatch]);
17138
17277
  useAutonomousCoordinator(subscribeCoordinatorEvents, dispatch);
17139
17278
  const builderRef = useRef(null);
17140
17279
  if (builderRef.current === null) {
@@ -17146,23 +17285,10 @@ function App({
17146
17285
  const lastEnterAtRef = useRef(0);
17147
17286
  const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
17148
17287
  const projectName = React5.useMemo(() => {
17149
- const base = path6.basename(projectRoot);
17150
- return base && base !== path6.sep ? base : void 0;
17288
+ const base = path9.basename(projectRoot);
17289
+ return base && base !== path9.sep ? base : void 0;
17151
17290
  }, [projectRoot]);
17152
- const [workingDirChip, setWorkingDirChip] = React5.useState(() => {
17153
- const ctx = agent.ctx;
17154
- if (ctx.workingDir && ctx.workingDir !== projectRoot) {
17155
- return path6.relative(projectRoot, ctx.workingDir) || ".";
17156
- }
17157
- return void 0;
17158
- });
17159
- React5.useEffect(() => {
17160
- const ctx = agent.ctx;
17161
- return ctx.onWorkingDirChanged((newDir) => {
17162
- const rel = path6.relative(projectRoot, newDir) || ".";
17163
- setWorkingDirChip(rel === "." ? void 0 : rel);
17164
- });
17165
- }, [agent.ctx, projectRoot]);
17291
+ const workingDirChip = useWorkingDirChip(agent.ctx, projectRoot);
17166
17292
  const liveSettings = getSettings?.();
17167
17293
  const liveStatuslineMode = liveSettings?.statuslineMode ?? "detailed";
17168
17294
  const liveAnimationStyle = liveSettings?.animationStyle ?? "rainbow";
@@ -17564,15 +17690,16 @@ function App({
17564
17690
  const fleetCounts = useMemo(() => {
17565
17691
  const entries = Object.values(state.fleet);
17566
17692
  if (entries.length === 0) return void 0;
17567
- let running = 0;
17568
- let idle = 0;
17569
- let completed = 0;
17570
- for (const e of entries) {
17571
- if (e.status === "running") running += 1;
17572
- else if (e.status === "idle") idle += 1;
17573
- else completed += 1;
17574
- }
17575
- return { running, idle, pending: 0, completed };
17693
+ const running = entries.filter((e) => e.status === "running").length;
17694
+ if (running === 0) return void 0;
17695
+ return { running, idle: 0, pending: 0, completed: 0 };
17696
+ }, [state.fleet]);
17697
+ const visibleSubagentCount = fleetCounts?.running ?? 0;
17698
+ const hasVisibleFleetPanel = useMemo(() => {
17699
+ for (const e of Object.values(state.fleet)) {
17700
+ if (e.status === "running") return true;
17701
+ }
17702
+ return false;
17576
17703
  }, [state.fleet]);
17577
17704
  const entriesWithLeader = useMemo(() => {
17578
17705
  const leaderEntry = {
@@ -20407,7 +20534,10 @@ Available chords:
20407
20534
  autoSubmitCapWarnedRef.current = false;
20408
20535
  dispatch({ type: "scrollToBottom" });
20409
20536
  const pushSubmittedHistory = () => {
20410
- if (trimmed) dispatch({ type: "historyPush", text: trimmed });
20537
+ const decision = shouldPushSubmittedHistory(trimmed);
20538
+ if (decision.push) {
20539
+ dispatch({ type: "historyPush", text: decision.trimmed });
20540
+ }
20411
20541
  };
20412
20542
  if (trimmed === "/image" || trimmed === "/paste-image") {
20413
20543
  pushSubmittedHistory();
@@ -21157,7 +21287,7 @@ ${content}
21157
21287
  brain: state.brain,
21158
21288
  projectName,
21159
21289
  workingDir: workingDirChip,
21160
- subagentCount: Object.keys(state.fleet).length,
21290
+ subagentCount: visibleSubagentCount,
21161
21291
  processCount: getProcessRegistry().activeCount,
21162
21292
  hiddenItems,
21163
21293
  mode: liveStatuslineMode,
@@ -21255,7 +21385,7 @@ ${content}
21255
21385
  onCoordinatorStop: onCoordinatorStop ?? void 0,
21256
21386
  coordinatorRunning
21257
21387
  }
21258
- ) : director || Object.keys(state.fleet).length > 0 || state.collabSession ? /* @__PURE__ */ jsx(
21388
+ ) : director || hasVisibleFleetPanel || state.collabSession ? /* @__PURE__ */ jsx(
21259
21389
  FleetPanel,
21260
21390
  {
21261
21391
  entries: entriesWithLeader,
@@ -21442,7 +21572,7 @@ async function runTui(opts) {
21442
21572
  stdout,
21443
21573
  events: opts.events,
21444
21574
  model: opts.model,
21445
- appName: opts.projectRoot ? path6.basename(opts.projectRoot) : void 0
21575
+ appName: opts.projectRoot ? path9.basename(opts.projectRoot) : void 0
21446
21576
  });
21447
21577
  };
21448
21578
  const stopTitle = () => {
@@ -21539,7 +21669,7 @@ async function runTui(opts) {
21539
21669
  const hqPublisher = createHqPublisherFromEnv({
21540
21670
  clientKind: "tui",
21541
21671
  projectRoot: opts.projectRoot,
21542
- projectName: path6.basename(opts.projectRoot),
21672
+ projectName: path9.basename(opts.projectRoot),
21543
21673
  appConfig: opts.appConfig
21544
21674
  });
21545
21675
  hqPublisher?.connect();
@@ -21579,7 +21709,7 @@ async function runTui(opts) {
21579
21709
  await mailbox.registerClient({
21580
21710
  clientId,
21581
21711
  sessionId: opts.getSessionId?.() ?? opts.projectRoot,
21582
- name: `TUI [${path6.basename(opts.projectRoot)}]`,
21712
+ name: `TUI [${path9.basename(opts.projectRoot)}]`,
21583
21713
  source: "tui",
21584
21714
  pid: process.pid
21585
21715
  });