@wrongstack/tui 0.282.0 → 0.282.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
@@ -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
 
@@ -1667,6 +1667,11 @@ function closePanels(state) {
1667
1667
  settingsPicker: { ...state.settingsPicker, open: false },
1668
1668
  statuslinePicker: { ...state.statuslinePicker, open: false },
1669
1669
  pluginPicker: { ...state.pluginPicker, open: false },
1670
+ mcpPicker: { ...state.mcpPicker, open: false },
1671
+ toolsPicker: { ...state.toolsPicker, open: false },
1672
+ brainPanel: { ...state.brainPanel, open: false },
1673
+ helpPanel: { ...state.helpPanel, open: false },
1674
+ shadowPanel: { ...state.shadowPanel, open: false },
1670
1675
  authPanel: { ...state.authPanel, open: false, busy: false },
1671
1676
  projectPicker: { ...state.projectPicker, open: false },
1672
1677
  fKeyPicker: { ...state.fKeyPicker, open: false },
@@ -3168,6 +3173,181 @@ function reducer(state, action) {
3168
3173
  return { ...state, pluginPicker: { ...state.pluginPicker, busy: action.busy } };
3169
3174
  case "pluginPickerHint":
3170
3175
  return { ...state, pluginPicker: { ...state.pluginPicker, hint: action.text } };
3176
+ case "mcpPickerOpen": {
3177
+ const items = action.items ?? state.mcpPicker.items;
3178
+ return {
3179
+ ...state,
3180
+ ...closePanels(state),
3181
+ mcpPicker: {
3182
+ open: true,
3183
+ items,
3184
+ selected: Math.min(state.mcpPicker.selected, Math.max(0, items.length - 1)),
3185
+ busy: items.length === 0,
3186
+ hint: void 0
3187
+ }
3188
+ };
3189
+ }
3190
+ case "mcpPickerClose":
3191
+ return { ...state, mcpPicker: { ...state.mcpPicker, open: false, busy: false } };
3192
+ case "mcpPickerMove": {
3193
+ const count = state.mcpPicker.items.length;
3194
+ if (count === 0) return state;
3195
+ return {
3196
+ ...state,
3197
+ mcpPicker: {
3198
+ ...state.mcpPicker,
3199
+ selected: (state.mcpPicker.selected + action.delta + count) % count,
3200
+ hint: void 0
3201
+ }
3202
+ };
3203
+ }
3204
+ case "mcpPickerSetItems":
3205
+ return {
3206
+ ...state,
3207
+ mcpPicker: {
3208
+ ...state.mcpPicker,
3209
+ items: action.items,
3210
+ selected: Math.min(state.mcpPicker.selected, Math.max(0, action.items.length - 1)),
3211
+ busy: false
3212
+ }
3213
+ };
3214
+ case "mcpPickerBusy":
3215
+ return { ...state, mcpPicker: { ...state.mcpPicker, busy: action.busy } };
3216
+ case "mcpPickerHint":
3217
+ return { ...state, mcpPicker: { ...state.mcpPicker, hint: action.text } };
3218
+ case "toolsPickerOpen": {
3219
+ const items = action.items ?? state.toolsPicker.items;
3220
+ return {
3221
+ ...state,
3222
+ ...closePanels(state),
3223
+ toolsPicker: {
3224
+ open: true,
3225
+ items,
3226
+ selected: Math.min(state.toolsPicker.selected, Math.max(0, items.length - 1)),
3227
+ busy: items.length === 0,
3228
+ hint: void 0,
3229
+ filter: void 0
3230
+ }
3231
+ };
3232
+ }
3233
+ case "toolsPickerClose":
3234
+ return { ...state, toolsPicker: { ...state.toolsPicker, open: false, busy: false, filter: void 0 } };
3235
+ case "toolsPickerMove": {
3236
+ const count = state.toolsPicker.items.length;
3237
+ if (count === 0) return state;
3238
+ return {
3239
+ ...state,
3240
+ toolsPicker: {
3241
+ ...state.toolsPicker,
3242
+ selected: (state.toolsPicker.selected + action.delta + count) % count,
3243
+ hint: void 0
3244
+ }
3245
+ };
3246
+ }
3247
+ case "toolsPickerSetItems":
3248
+ return {
3249
+ ...state,
3250
+ toolsPicker: {
3251
+ ...state.toolsPicker,
3252
+ items: action.items,
3253
+ selected: Math.min(state.toolsPicker.selected, Math.max(0, action.items.length - 1)),
3254
+ busy: false
3255
+ }
3256
+ };
3257
+ case "toolsPickerToggle":
3258
+ return state;
3259
+ case "toolsPickerBusy":
3260
+ return { ...state, toolsPicker: { ...state.toolsPicker, busy: action.busy } };
3261
+ case "toolsPickerHint":
3262
+ return { ...state, toolsPicker: { ...state.toolsPicker, hint: action.text } };
3263
+ case "toolsPickerFilter":
3264
+ return { ...state, toolsPicker: { ...state.toolsPicker, filter: action.filter } };
3265
+ case "brainOpen": {
3266
+ return {
3267
+ ...state,
3268
+ ...closePanels(state),
3269
+ brainPanel: {
3270
+ open: true,
3271
+ riskLevel: action.riskLevel,
3272
+ log: action.log,
3273
+ selected: 0,
3274
+ hint: void 0
3275
+ }
3276
+ };
3277
+ }
3278
+ case "brainClose":
3279
+ return { ...state, brainPanel: { ...state.brainPanel, open: false } };
3280
+ case "brainMove": {
3281
+ const count = state.brainPanel.log.length;
3282
+ if (count === 0) return state;
3283
+ return {
3284
+ ...state,
3285
+ brainPanel: {
3286
+ ...state.brainPanel,
3287
+ selected: (state.brainPanel.selected + action.delta + count) % count,
3288
+ hint: void 0
3289
+ }
3290
+ };
3291
+ }
3292
+ case "brainRiskChange": {
3293
+ const levels = ["off", "low", "medium", "high", "all"];
3294
+ const cur = levels.indexOf(state.brainPanel.riskLevel);
3295
+ const next = (cur + action.delta + levels.length) % levels.length;
3296
+ return {
3297
+ ...state,
3298
+ brainPanel: {
3299
+ ...state.brainPanel,
3300
+ riskLevel: levels[next]
3301
+ }
3302
+ };
3303
+ }
3304
+ case "brainSetLog":
3305
+ return {
3306
+ ...state,
3307
+ brainPanel: {
3308
+ ...state.brainPanel,
3309
+ log: action.log,
3310
+ selected: Math.min(state.brainPanel.selected, Math.max(0, action.log.length - 1))
3311
+ }
3312
+ };
3313
+ case "brainHint":
3314
+ return { ...state, brainPanel: { ...state.brainPanel, hint: action.text } };
3315
+ case "helpOpen":
3316
+ return {
3317
+ ...state,
3318
+ ...closePanels(state),
3319
+ helpPanel: { open: true, entries: action.entries, selected: 0, filter: "", hint: void 0 }
3320
+ };
3321
+ case "helpClose":
3322
+ return { ...state, helpPanel: { ...state.helpPanel, open: false, filter: "" } };
3323
+ case "helpMove": {
3324
+ const count = state.helpPanel.entries.length;
3325
+ if (count === 0) return state;
3326
+ return {
3327
+ ...state,
3328
+ helpPanel: {
3329
+ ...state.helpPanel,
3330
+ selected: (state.helpPanel.selected + action.delta + count) % count,
3331
+ hint: void 0
3332
+ }
3333
+ };
3334
+ }
3335
+ case "helpFilter":
3336
+ return { ...state, helpPanel: { ...state.helpPanel, filter: action.filter, selected: 0 } };
3337
+ case "helpHint":
3338
+ return { ...state, helpPanel: { ...state.helpPanel, hint: action.text } };
3339
+ case "shadowOpen":
3340
+ return {
3341
+ ...state,
3342
+ ...closePanels(state),
3343
+ shadowPanel: { open: true, shadow: action.shadow, hint: void 0 }
3344
+ };
3345
+ case "shadowClose":
3346
+ return { ...state, shadowPanel: { ...state.shadowPanel, open: false } };
3347
+ case "shadowUpdate":
3348
+ return { ...state, shadowPanel: { ...state.shadowPanel, shadow: action.shadow } };
3349
+ case "shadowHint":
3350
+ return { ...state, shadowPanel: { ...state.shadowPanel, hint: action.text } };
3171
3351
  case "authOpen":
3172
3352
  return {
3173
3353
  ...state,
@@ -4156,6 +4336,36 @@ function StatusBar({
4156
4336
  return () => clearInterval(t);
4157
4337
  }, [state]);
4158
4338
  const spinner = expectDefined$1(SPINNER_FRAMES[spinnerIdx]);
4339
+ const TODOS_CLEAR_DELAY_MS = 3e3;
4340
+ const [todosCleared, setTodosCleared] = useState(false);
4341
+ const todosClearTimerRef = useRef(null);
4342
+ useEffect(() => {
4343
+ const hasActive = todos && (todos.pending > 0 || todos.inProgress > 0);
4344
+ if (hasActive) {
4345
+ if (todosClearTimerRef.current) {
4346
+ clearTimeout(todosClearTimerRef.current);
4347
+ todosClearTimerRef.current = null;
4348
+ }
4349
+ setTodosCleared(false);
4350
+ return;
4351
+ }
4352
+ if (todos && todos.completed > 0) {
4353
+ if (!todosClearTimerRef.current) {
4354
+ todosClearTimerRef.current = setTimeout(() => {
4355
+ setTodosCleared(true);
4356
+ todosClearTimerRef.current = null;
4357
+ }, TODOS_CLEAR_DELAY_MS);
4358
+ }
4359
+ return () => {
4360
+ if (todosClearTimerRef.current) {
4361
+ clearTimeout(todosClearTimerRef.current);
4362
+ todosClearTimerRef.current = null;
4363
+ }
4364
+ };
4365
+ }
4366
+ setTodosCleared(true);
4367
+ return;
4368
+ }, [todos]);
4159
4369
  const animationStyle = thinkingAnimationStyle ?? "rainbow";
4160
4370
  const [cycleTick, setCycleTick] = useState(0);
4161
4371
  useEffect(() => {
@@ -4186,7 +4396,7 @@ function StatusBar({
4186
4396
  const hasNextStepsAutoSubmit = nextStepsAutoSubmitCountdown != null && nextStepsAutoSubmitCountdown > 0;
4187
4397
  const nextStepsColor = nextStepsAutoSubmitCountdown != null ? countdownColor(nextStepsAutoSubmitCountdown, 20, 10) : "green";
4188
4398
  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");
4399
+ 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
4400
  const minimalWorkParts = [
4191
4401
  queueCount > 0 && showChip("queue") ? `q${queueCount}` : "",
4192
4402
  todos && showChip("todos") && todos.inProgress + todos.pending > 0 ? `todo ${todos.inProgress}/${todos.pending}` : "",
@@ -4529,7 +4739,7 @@ function StatusBar({
4529
4739
  ) }) : /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
4530
4740
  hasThirdLine ? /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, children: renderChipLine(
4531
4741
  [
4532
- todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) && showChip("todos") ? /* @__PURE__ */ jsxs(Text, { children: [
4742
+ todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0 && !todosCleared) && showChip("todos") ? /* @__PURE__ */ jsxs(Text, { children: [
4533
4743
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "todos " }),
4534
4744
  todos.inProgress > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
4535
4745
  "\u231B",
@@ -5008,7 +5218,7 @@ function FleetMonitor({
5008
5218
  nowTick,
5009
5219
  collabSession
5010
5220
  }) {
5011
- const TERMINAL_TTL_MS2 = 5 * 6e4;
5221
+ const TERMINAL_TTL_MS = 5 * 6e4;
5012
5222
  const all = Object.values(entries);
5013
5223
  const running = all.filter((e) => e.status === "running");
5014
5224
  const idle = all.filter((e) => e.status === "idle").length;
@@ -5016,7 +5226,7 @@ function FleetMonitor({
5016
5226
  const failed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
5017
5227
  const recent = all.filter((e) => {
5018
5228
  if (e.status === "running" || e.status === "idle") return true;
5019
- return nowTick - e.lastEventAt < TERMINAL_TTL_MS2;
5229
+ return nowTick - e.lastEventAt < TERMINAL_TTL_MS;
5020
5230
  });
5021
5231
  const concurrencyRatio = maxConcurrent > 0 ? running.length / maxConcurrent : 0;
5022
5232
  const ordered = [...recent].sort((a, b) => {
@@ -5214,15 +5424,12 @@ var STATUS2 = {
5214
5424
  };
5215
5425
  var IDLE_HIDE_MS = 6e4;
5216
5426
  var EMPTY_AGENTS_CLOSE_DELAY_MS = 7500;
5217
- function isTerminalAgentStatus(status) {
5218
- return status === "success" || status === "failed" || status === "timeout" || status === "stopped";
5219
- }
5220
5427
  function isLeaderEntry(entry) {
5221
5428
  return entry.id === "leader" || entry.name === "LEADER";
5222
5429
  }
5223
5430
  function selectLiveAgents(all, _now, _idleHideMs = IDLE_HIDE_MS) {
5224
5431
  const leader = all.find(isLeaderEntry);
5225
- const activeSubagents = all.filter((entry) => !isLeaderEntry(entry) && !isTerminalAgentStatus(entry.status));
5432
+ const activeSubagents = all.filter((entry) => !isLeaderEntry(entry) && entry.status === "running");
5226
5433
  const showLeader = leader !== void 0 && (leader.status !== "idle" || activeSubagents.length > 0);
5227
5434
  return all.filter((entry) => isLeaderEntry(entry) ? showLeader : activeSubagents.some((active) => active.id === entry.id));
5228
5435
  }
@@ -5551,9 +5758,6 @@ function AgentsMonitor({
5551
5758
  const running = live.filter((e) => e.status === "running").length;
5552
5759
  const totalDone = all.filter((e) => e.status === "success").length;
5553
5760
  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
5761
  const hotAgent = selectHotAgent(live);
5558
5762
  const pressure = live.length > 0 ? live.reduce((max, e) => Math.max(max, e.ctxPct ?? 0), 0) : 0;
5559
5763
  const toolCalls = live.reduce((sum, e) => sum + e.toolCalls, 0);
@@ -5645,12 +5849,7 @@ function AgentsMonitor({
5645
5849
  " \xB7 fleet $",
5646
5850
  totalCost.toFixed(4),
5647
5851
  ")"
5648
- ] }),
5649
- staleIdle > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5650
- "\xB7 ",
5651
- staleIdle,
5652
- " idle stale"
5653
- ] }) : null
5852
+ ] })
5654
5853
  ] }),
5655
5854
  live.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No live agents \u2014 spawn with /spawn or /fleet dispatch." }) : null,
5656
5855
  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 +6863,8 @@ function detectLang(fenceInfo) {
6664
6863
  const tag = fenceInfo.trim().toLowerCase().split(/\s+/)[0] ?? "";
6665
6864
  return LANG_ALIASES[tag] ?? "plain";
6666
6865
  }
6667
- function langFromPath(path8) {
6668
- const m = /\.([A-Za-z0-9]+)$/.exec(path8.trim());
6866
+ function langFromPath(path10) {
6867
+ const m = /\.([A-Za-z0-9]+)$/.exec(path10.trim());
6669
6868
  const ext = m?.[1]?.toLowerCase() ?? "";
6670
6869
  return LANG_ALIASES[ext] ?? "plain";
6671
6870
  }
@@ -7459,19 +7658,19 @@ function visualEdit(toolName, text, ok) {
7459
7658
  const parsed = tryParseJson(text);
7460
7659
  if (parsed && typeof parsed === "object") {
7461
7660
  const obj = parsed;
7462
- const path8 = typeof obj["path"] === "string" ? obj["path"] : void 0;
7661
+ const path10 = typeof obj["path"] === "string" ? obj["path"] : void 0;
7463
7662
  const replacements = typeof obj["replacements"] === "number" ? obj["replacements"] : void 0;
7464
7663
  const bytes = typeof obj["bytes"] === "number" ? obj["bytes"] : void 0;
7465
7664
  const created = obj["created"] === true;
7466
7665
  const files = Array.isArray(obj["files"]) ? obj["files"] : void 0;
7467
7666
  const results = Array.isArray(obj["results"]) ? obj["results"] : void 0;
7468
- if (toolName === "edit" && path8 !== void 0) {
7667
+ if (toolName === "edit" && path10 !== void 0) {
7469
7668
  const repText = replacements !== void 0 ? `${replacements} replacement${replacements === 1 ? "" : "s"}` : void 0;
7470
- rows.push({ kind: "ok", text: "", marker: "edit ", path: path8 });
7669
+ rows.push({ kind: "ok", text: "", marker: "edit ", path: path10 });
7471
7670
  if (repText) rows.push({ kind: "meta", text: repText });
7472
- } else if (toolName === "write" && path8 !== void 0) {
7671
+ } else if (toolName === "write" && path10 !== void 0) {
7473
7672
  const sizeText = bytes !== void 0 ? `${bytes} bytes` : created ? "new file" : "updated";
7474
- rows.push({ kind: "ok", text: "", marker: "write ", path: path8 });
7673
+ rows.push({ kind: "ok", text: "", marker: "write ", path: path10 });
7475
7674
  rows.push({ kind: "meta", text: sizeText });
7476
7675
  } else if ((toolName === "diff" || toolName === "patch") && files && files.length > 0) {
7477
7676
  rows.push({
@@ -7494,8 +7693,8 @@ function visualEdit(toolName, text, ok) {
7494
7693
  marker: "replace ",
7495
7694
  text: `${results.length} replacement${results.length === 1 ? "" : "s"} across ${fileCount} file${fileCount === 1 ? "" : "s"}`
7496
7695
  });
7497
- } else if (path8 !== void 0) {
7498
- rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path8 });
7696
+ } else if (path10 !== void 0) {
7697
+ rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path10 });
7499
7698
  } else {
7500
7699
  return void 0;
7501
7700
  }
@@ -7589,12 +7788,12 @@ function parseMatchHit(hit) {
7589
7788
  }
7590
7789
  if (hit && typeof hit === "object") {
7591
7790
  const o = hit;
7592
- const path8 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
7791
+ const path10 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
7593
7792
  const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
7594
7793
  const title = stringOf(o["title"]);
7595
7794
  const snippet2 = stringOf(o["snippet"]);
7596
7795
  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 };
7796
+ return { path: path10, line: line === void 0 ? void 0 : String(line), text };
7598
7797
  }
7599
7798
  return void 0;
7600
7799
  }
@@ -8402,13 +8601,13 @@ function formatMultiDiffSummary(summary, threshold = MULTI_DIFF_SUMMARY_THRESHOL
8402
8601
  return parts.join(" \xB7 ");
8403
8602
  }
8404
8603
  function DiffFileBlock({
8405
- path: path8,
8604
+ path: path10,
8406
8605
  preview,
8407
8606
  useColor = true,
8408
8607
  contentWidth
8409
8608
  }) {
8410
8609
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
8411
- /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: path8 }),
8610
+ /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: path10 }),
8412
8611
  /* @__PURE__ */ jsx(
8413
8612
  DiffBlock,
8414
8613
  {
@@ -8419,7 +8618,7 @@ function DiffFileBlock({
8419
8618
  hiddenAdded: preview.hiddenAdded,
8420
8619
  hiddenRemoved: preview.hiddenRemoved,
8421
8620
  useColor,
8422
- lang: langFromPath(path8),
8621
+ lang: langFromPath(path10),
8423
8622
  contentWidth
8424
8623
  }
8425
8624
  )
@@ -8509,7 +8708,8 @@ function DiffBlock({
8509
8708
  return row.text || " ";
8510
8709
  };
8511
8710
  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;
8711
+ const terminalWrapGuard = typeof contentWidth === "number" ? 1 : 0;
8712
+ const bodyBudget = typeof contentWidth === "number" ? Math.max(16, contentWidth - (2 + 3 + gutterWidth + 3 + terminalWrapGuard)) : DIFF_FALLBACK_WRAP_WIDTH;
8513
8713
  const hasWidth = typeof contentWidth === "number";
8514
8714
  const padBody = (seg) => {
8515
8715
  if (!hasWidth) return 0;
@@ -8677,8 +8877,8 @@ function splitReplaceDiffs(obj) {
8677
8877
  const record = result;
8678
8878
  const diff = stringOf(record["diff"]);
8679
8879
  if (!diff?.trim()) continue;
8680
- const path8 = stringOf(record["path"]);
8681
- items.push(path8 ? { path: path8, diff } : { diff });
8880
+ const path10 = stringOf(record["path"]);
8881
+ items.push(path10 ? { path: path10, diff } : { diff });
8682
8882
  }
8683
8883
  return items;
8684
8884
  }
@@ -8742,8 +8942,8 @@ function collectMultiFileDiffItems(toolName, output, _input) {
8742
8942
  if (!diffText?.trim()) return void 0;
8743
8943
  const blocks = splitGitStyleDiff(diffText);
8744
8944
  if (blocks.length === 0) {
8745
- const path8 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
8746
- return [{ path: path8, diff: diffText }];
8945
+ const path10 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
8946
+ return [{ path: path10, diff: diffText }];
8747
8947
  }
8748
8948
  return blocks.map((block, idx) => ({
8749
8949
  path: explicitFiles[idx] ?? block.path,
@@ -8789,17 +8989,17 @@ function splitGitStyleDiff(diff) {
8789
8989
  }
8790
8990
  flush();
8791
8991
  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 };
8992
+ const path10 = extractPatchHeaderPath(blocks[0].diff);
8993
+ if (path10) blocks[0] = { path: path10, diff: blocks[0].diff };
8794
8994
  }
8795
8995
  return blocks;
8796
8996
  }
8797
8997
  function parseDiffGitPath(line) {
8798
8998
  const rest = line.slice("diff --git ".length).trim();
8799
8999
  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);
9000
+ const sep3 = rest.lastIndexOf(" b/");
9001
+ if (sep3 > 0 && sep3 > rest.length - sep3 - 3) {
9002
+ return rest.slice(sep3 + 3);
8803
9003
  }
8804
9004
  const spaceIdx = rest.lastIndexOf(" ");
8805
9005
  if (spaceIdx > 0) {
@@ -8811,8 +9011,8 @@ function parseDiffGitPath(line) {
8811
9011
  function extractPatchHeaderPath(diffText) {
8812
9012
  for (const line of diffText.split("\n")) {
8813
9013
  if (line.startsWith("+++ ")) {
8814
- const path8 = line.slice(4).trim();
8815
- const cleaned = path8.replace(/^b\//, "").split(" ")[0].trim();
9014
+ const path10 = line.slice(4).trim();
9015
+ const cleaned = path10.replace(/^b\//, "").split(" ")[0].trim();
8816
9016
  return cleaned || void 0;
8817
9017
  }
8818
9018
  }
@@ -8838,9 +9038,9 @@ function newFileDiffFromWriteInput(output, input) {
8838
9038
  const obj = input;
8839
9039
  const content = stringOf(obj["content"]);
8840
9040
  if (content === void 0) return void 0;
8841
- const path8 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
9041
+ const path10 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
8842
9042
  const lines = content === "" ? [] : content.replace(/\n$/, "").split("\n");
8843
- const header = [`+++ ${path8}`, `@@ -0,0 +1,${lines.length} @@`];
9043
+ const header = [`+++ ${path10}`, `@@ -0,0 +1,${lines.length} @@`];
8844
9044
  return [...header, ...lines.map((line) => `+${line}`)].join("\n");
8845
9045
  }
8846
9046
  var BUTTON_COLOR = {
@@ -8878,7 +9078,7 @@ function renderDiffLine(line) {
8878
9078
  "\n"
8879
9079
  ] }, line);
8880
9080
  }
8881
- function renderDiff(diff, path8) {
9081
+ function renderDiff(diff, path10) {
8882
9082
  const preview = parseUnifiedDiff(diff, CONFIRM_DIFF_MAX_LINES);
8883
9083
  if (preview.rows.length > 0) {
8884
9084
  return /* @__PURE__ */ jsx(
@@ -8891,7 +9091,7 @@ function renderDiff(diff, path8) {
8891
9091
  hiddenAdded: preview.hiddenAdded,
8892
9092
  hiddenRemoved: preview.hiddenRemoved,
8893
9093
  useColor: theme.supportsBackground,
8894
- lang: langFromPath(path8 ?? "")
9094
+ lang: langFromPath(path10 ?? "")
8895
9095
  }
8896
9096
  );
8897
9097
  }
@@ -9336,10 +9536,9 @@ function FilePicker({ query, matches, selected }) {
9336
9536
  ] }, m))
9337
9537
  ] });
9338
9538
  }
9339
- function highlight(path8, _query) {
9340
- return path8;
9539
+ function highlight(path10, _query) {
9540
+ return path10;
9341
9541
  }
9342
- var TERMINAL_TTL_MS = 9e4;
9343
9542
  var METER_WIDTH = 10;
9344
9543
  function isLeaderEntry2(entry) {
9345
9544
  return entry.id === "leader" || entry.name === "LEADER";
@@ -9413,22 +9612,19 @@ function swarmColumnCount(width) {
9413
9612
  if (width < 54) return 1;
9414
9613
  return Math.max(1, Math.min(5, Math.floor(width / 38)));
9415
9614
  }
9416
- function selectSwarmEntries(entries, now, opts = {}) {
9417
- const terminalTtlMs = opts.terminalTtlMs ?? TERMINAL_TTL_MS;
9615
+ function selectSwarmEntries(entries, _now, opts = {}) {
9418
9616
  return Object.values(entries).filter((entry) => {
9419
9617
  if (isLeaderEntry2(entry) && !opts.includeLeader) return false;
9420
- if (entry.status === "running" || entry.status === "idle") return true;
9421
- return now - entry.lastEventAt <= terminalTtlMs;
9618
+ if (isLeaderEntry2(entry)) return true;
9619
+ return entry.status === "running";
9422
9620
  }).sort((a, b) => {
9423
9621
  const rank = (entry) => {
9424
9622
  if (entry.status === "running") return 0;
9425
- if (entry.status === "idle") return 1;
9426
- return 2;
9623
+ return 1;
9427
9624
  };
9428
9625
  const ar = rank(a);
9429
9626
  const br = rank(b);
9430
9627
  if (ar !== br) return ar - br;
9431
- if (ar === 2) return b.lastEventAt - a.lastEventAt;
9432
9628
  return a.startedAt - b.startedAt;
9433
9629
  });
9434
9630
  }
@@ -9549,16 +9745,8 @@ function FleetPanel({
9549
9745
  const hasSwarm = grid.rows.length > 0;
9550
9746
  if (!hasSwarm && todoRows.length === 0 && !hasCollab) return null;
9551
9747
  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
9748
  const summaryBits = [
9558
- runningCount > 0 ? `${runningCount} running` : "idle",
9559
- idleCount > 0 ? `${idleCount} idle` : "",
9560
- doneCount > 0 ? `${doneCount} done` : "",
9561
- failedCount > 0 ? `${failedCount} failed` : "",
9749
+ runningCount > 0 ? `${runningCount} running` : "",
9562
9750
  fmtCost2(totalCost)
9563
9751
  ].filter(Boolean);
9564
9752
  const collabLabel = hasCollab && collabSession.sessionId ? `collab ${collabSession.bugCount}b/${collabSession.planCount}p/${collabSession.evalCount}e` : "";
@@ -9962,8 +10150,8 @@ var SEP_RE = /^\s*\|[\s\-:|]+\|\s*$/;
9962
10150
  function detectTable(lines, start) {
9963
10151
  if (start + 1 >= lines.length) return start;
9964
10152
  if (!ROW_RE.test(lines[start] ?? "")) return start;
9965
- const sep2 = lines[start + 1] ?? "";
9966
- if (!SEP_RE.test(sep2) || !/-/.test(sep2)) return start;
10153
+ const sep3 = lines[start + 1] ?? "";
10154
+ if (!SEP_RE.test(sep3) || !/-/.test(sep3)) return start;
9967
10155
  let end = start + 2;
9968
10156
  while (end < lines.length && ROW_RE.test(lines[end] ?? "")) end++;
9969
10157
  return end;
@@ -9989,8 +10177,8 @@ function parseCells(line) {
9989
10177
  parts.push(buf);
9990
10178
  return parts.map((c) => c.trim());
9991
10179
  }
9992
- function parseAlign(sep2) {
9993
- const t = sep2.trim();
10180
+ function parseAlign(sep3) {
10181
+ const t = sep3.trim();
9994
10182
  const left = t.startsWith(":");
9995
10183
  const right = t.endsWith(":");
9996
10184
  if (left && right) return "center";
@@ -10556,7 +10744,7 @@ function Banner({
10556
10744
  entry
10557
10745
  }) {
10558
10746
  const cwdShort = shortenPath(entry.cwd, 48);
10559
- const projectLabel = path6.basename(entry.cwd);
10747
+ const projectLabel = path9.basename(entry.cwd);
10560
10748
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
10561
10749
  /* @__PURE__ */ jsxs(Text, { children: [
10562
10750
  /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
@@ -10594,18 +10782,12 @@ function Banner({
10594
10782
  }
10595
10783
 
10596
10784
  // 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
- ];
10785
+ var NEXT_STEPS_TAG_RE = /<nextsteps>\s*\n+/i;
10604
10786
  var ITEM_RE = /^(?:(\d+)[.)]\s*|[-*•]\s*)(.+?)(\s+auto="true")?$/;
10605
10787
  var MAX_STEPS = 6;
10606
10788
  function parseNextSteps(content, strict = false, requireHeading = true) {
10607
10789
  if (requireHeading) {
10608
- return parseWithHeading(content, strict);
10790
+ return parseWithHeading(content);
10609
10791
  }
10610
10792
  return parseRawNumbered(content);
10611
10793
  }
@@ -10640,9 +10822,8 @@ function parseRawNumbered(content) {
10640
10822
  autoTexts: steps.filter((s2) => s2.auto).map((s2) => s2.text)
10641
10823
  };
10642
10824
  }
10643
- function parseWithHeading(content, strict) {
10644
- const headingRe = strict ? STRICT_HEADING_RE : buildPermissiveHeadingRe();
10645
- const headingMatch = headingRe.exec(content);
10825
+ function parseWithHeading(content, _strict) {
10826
+ const headingMatch = NEXT_STEPS_TAG_RE.exec(content);
10646
10827
  if (!headingMatch) {
10647
10828
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
10648
10829
  }
@@ -10674,8 +10855,7 @@ function parseWithHeading(content, strict) {
10674
10855
  if (steps.length === 0) {
10675
10856
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
10676
10857
  }
10677
- const headingWasXmlTag = headingMatch[0].startsWith("<");
10678
- if (strict && headingWasXmlTag && !afterHeading.includes("</next_steps>")) {
10858
+ if (!afterHeading.includes("</nextsteps>")) {
10679
10859
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
10680
10860
  }
10681
10861
  const texts = steps.map((s2) => s2.text);
@@ -10685,14 +10865,10 @@ function parseWithHeading(content, strict) {
10685
10865
  const stripped = (content.slice(0, blockStart) + content.slice(blockStart + blockEnd)).replace(/\n{3,}/g, "\n\n").trim();
10686
10866
  return { steps, texts, stripped, autoTexts };
10687
10867
  }
10688
- function buildPermissiveHeadingRe() {
10689
- const variants = PERMISSIVE_HEADING_PATTERNS.map(({ re }) => `(?:${re.source})`).join("|");
10690
- return new RegExp(variants, "i");
10691
- }
10692
10868
  function findBlockEnd(afterHeading, stepCount) {
10693
- const closeIdx = afterHeading.indexOf("</next_steps>");
10869
+ const closeIdx = afterHeading.indexOf("</nextsteps>");
10694
10870
  if (closeIdx !== -1) {
10695
- let end = closeIdx + "</next_steps>".length;
10871
+ let end = closeIdx + "</nextsteps>".length;
10696
10872
  if (afterHeading[end] === "\n") end += 1;
10697
10873
  return end;
10698
10874
  }
@@ -10714,7 +10890,7 @@ function findBlockEnd(afterHeading, stepCount) {
10714
10890
  return consumed;
10715
10891
  }
10716
10892
  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();
10893
+ return text.replace(/<nextsteps\b[^>]*>[\s\S]*?<\/nextsteps>/gi, "").replace(/<nextsteps\b[^>]*\/?>/gi, "").replace(/\n{3,}/g, "\n\n").trim();
10718
10894
  }
10719
10895
  function brainStatusStyle(status) {
10720
10896
  switch (status) {
@@ -10908,15 +11084,15 @@ var Entry = React5.memo(function Entry2({
10908
11084
  const outObj = outJson && typeof outJson === "object" ? outJson : void 0;
10909
11085
  const created = name === "write" && (outObj?.["created"] === true || /^[^\n]*\bcreated=true\b/.test(entry.output ?? ""));
10910
11086
  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";
11087
+ const path10 = stringOf(inputObj?.["path"]) ?? stringOf(outObj?.["path"]);
11088
+ const target = multiDiffs2 ? multiDiffs2.length === 1 ? multiDiffs2[0].path : `${multiDiffs2.length} files` : path10 ?? "file";
10913
11089
  const agg = multiDiffs2 ? summarizeMultiFileDiffs(multiDiffs2) : void 0;
10914
11090
  return {
10915
11091
  verb,
10916
11092
  target,
10917
11093
  added: agg ? agg.added : diff2?.added ?? 0,
10918
11094
  removed: agg ? agg.removed : diff2?.removed ?? 0,
10919
- lang: langFromPath(multiDiffs2 ? "" : path8 ?? "")
11095
+ lang: langFromPath(multiDiffs2 ? "" : path10 ?? "")
10920
11096
  };
10921
11097
  })();
10922
11098
  return { argSummary: argSummary2, outLines: outLines2, visualLines: visualLines2, diff: diff2, multiDiffs: multiDiffs2, sizeChip: sizeChip2, mutation: mutation2 };
@@ -12470,12 +12646,12 @@ var STATUS_ICON = {
12470
12646
  function planFilePath(projectRoot, sessionId, scope) {
12471
12647
  const base = resolveWstackPaths$1({ projectRoot }).projectSessions;
12472
12648
  if (scope === "project") {
12473
- return path6.join(base, "backlog.plan.json");
12649
+ return path9.join(base, "backlog.plan.json");
12474
12650
  }
12475
12651
  if (sessionId) {
12476
12652
  return sessionScopedPath(base, sessionId, ".plan.json");
12477
12653
  }
12478
- return path6.join(base, "backlog.plan.json");
12654
+ return path9.join(base, "backlog.plan.json");
12479
12655
  }
12480
12656
  function PlanPanel({ projectRoot, sessionId, onClose: _onClose }) {
12481
12657
  const [items, setItems] = useState([]);
@@ -12603,7 +12779,135 @@ function PlanRow({ item }) {
12603
12779
  ] }) : null
12604
12780
  ] });
12605
12781
  }
12606
- var CHROME_ROWS2 = 17;
12782
+ var CHROME_ROWS2 = 14;
12783
+ var RISK_DESCS = {
12784
+ off: "Human decides everything",
12785
+ low: "Auto-decide low risk only",
12786
+ medium: "Auto-decide up to medium risk",
12787
+ high: "Auto-decide up to high risk",
12788
+ all: "Auto-decide everything"
12789
+ };
12790
+ var RISK_COLORS2 = {
12791
+ off: "gray",
12792
+ low: "green",
12793
+ medium: "yellow",
12794
+ high: "red",
12795
+ all: "magenta"
12796
+ };
12797
+ function BrainPanel({
12798
+ riskLevel,
12799
+ log,
12800
+ selected,
12801
+ hint
12802
+ }) {
12803
+ const { stdout } = useStdout();
12804
+ const termRows = stdout?.rows ?? 24;
12805
+ const maxVisible = Math.max(4, termRows - CHROME_ROWS2);
12806
+ const total = log.length;
12807
+ const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12808
+ const windowEnd = Math.min(windowStart + maxVisible, total);
12809
+ const above = windowStart;
12810
+ const below = total - windowEnd;
12811
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
12812
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "\u2501\u2501 Brain \u2501\u2501" }),
12813
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate log \xB7 \u2190/\u2192 change risk \xB7 Esc close" }),
12814
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
12815
+ /* @__PURE__ */ jsxs(Box, { children: [
12816
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Risk ceiling: " }),
12817
+ /* @__PURE__ */ jsx(Text, { color: RISK_COLORS2[riskLevel], bold: true, children: riskLevel.toUpperCase() }),
12818
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${RISK_DESCS[riskLevel]}` })
12819
+ ] }),
12820
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
12821
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "Recent decisions" }),
12822
+ total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " No decisions recorded yet this session." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12823
+ above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12824
+ log.slice(windowStart, windowEnd).map((entry, i) => {
12825
+ const index = windowStart + i;
12826
+ const focused = index === selected;
12827
+ return /* @__PURE__ */ jsxs(
12828
+ Text,
12829
+ {
12830
+ inverse: focused,
12831
+ ...focused ? { color: "magenta" } : {},
12832
+ wrap: "truncate-end",
12833
+ children: [
12834
+ focused ? "\u203A " : " ",
12835
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.age.padEnd(8) }),
12836
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: entry.kind.padEnd(12) }),
12837
+ /* @__PURE__ */ jsx(Text, { children: entry.question.length > 60 ? `${entry.question.slice(0, 57)}\u2026` : entry.question }),
12838
+ entry.outcome ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2192 ${entry.outcome.length > 20 ? `${entry.outcome.slice(0, 17)}\u2026` : entry.outcome}` }) : null
12839
+ ]
12840
+ },
12841
+ `${entry.kind}-${i}`
12842
+ );
12843
+ }),
12844
+ below > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${below} more` }) : null
12845
+ ] })
12846
+ ] })
12847
+ ] }),
12848
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
12849
+ ] });
12850
+ }
12851
+ var CHROME_ROWS3 = 17;
12852
+ function statusBadge(status, enabled) {
12853
+ if (!enabled) return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB disabled" });
12854
+ switch (status) {
12855
+ case "connected":
12856
+ return /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF connected" });
12857
+ case "connecting":
12858
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25D0 connecting" });
12859
+ case "reconnecting":
12860
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25D1 reconnecting" });
12861
+ case "disconnected":
12862
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB disconnected" });
12863
+ case "failed":
12864
+ return /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 failed" });
12865
+ default:
12866
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: status });
12867
+ }
12868
+ }
12869
+ function McpPicker({
12870
+ items,
12871
+ selected,
12872
+ busy = false,
12873
+ hint
12874
+ }) {
12875
+ const { stdout } = useStdout();
12876
+ const rows = stdout?.rows ?? 24;
12877
+ const maxVisible = Math.max(4, rows - CHROME_ROWS3);
12878
+ const total = items.length;
12879
+ const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12880
+ const windowEnd = Math.min(windowStart + maxVisible, total);
12881
+ const above = windowStart;
12882
+ const below = total - windowEnd;
12883
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
12884
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "MCP Servers" }),
12885
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 select \xB7 Enter/\u2190/\u2192 toggle enable \xB7 r restart \xB7 Esc close" }),
12886
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: busy ? "Loading servers\u2026" : "No MCP servers configured." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12887
+ above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12888
+ items.slice(windowStart, windowEnd).map((item, i) => {
12889
+ const index = windowStart + i;
12890
+ const focused = index === selected;
12891
+ const marker = focused ? "\u203A" : " ";
12892
+ return /* @__PURE__ */ jsxs(Text, { color: focused ? "cyan" : void 0, wrap: "truncate-end", children: [
12893
+ marker,
12894
+ " ",
12895
+ statusBadge(item.status, item.enabled),
12896
+ " ",
12897
+ /* @__PURE__ */ jsx(Text, { bold: true, children: item.name.padEnd(18) }),
12898
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12899
+ item.transport.padEnd(14),
12900
+ item.toolCount > 0 ? `${item.toolCount} tools` : "",
12901
+ item.lazy ? " lazy" : ""
12902
+ ] })
12903
+ ] }, item.name);
12904
+ }),
12905
+ below > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${below} more` }) : null
12906
+ ] }) }),
12907
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
12908
+ ] });
12909
+ }
12910
+ var CHROME_ROWS4 = 17;
12607
12911
  function PluginPicker({
12608
12912
  items,
12609
12913
  selected,
@@ -12612,15 +12916,20 @@ function PluginPicker({
12612
12916
  }) {
12613
12917
  const { stdout } = useStdout();
12614
12918
  const rows = stdout?.rows ?? 24;
12615
- const maxVisible = Math.max(4, rows - CHROME_ROWS2);
12919
+ const maxVisible = Math.max(4, rows - CHROME_ROWS4);
12616
12920
  const total = items.length;
12617
12921
  const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12618
12922
  const windowEnd = Math.min(windowStart + maxVisible, total);
12619
12923
  const above = windowStart;
12620
12924
  const below = total - windowEnd;
12925
+ const hasLockedRows = items.some((item) => item.lockable === false);
12621
12926
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
12622
12927
  /* @__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" }),
12928
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12929
+ "\u2191/\u2193 select \xB7 Enter/\u2190/\u2192 toggle",
12930
+ hasLockedRows ? " \xB7 \u{1F512} = locked" : "",
12931
+ " \xB7 Esc close"
12932
+ ] }),
12624
12933
  /* @__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
12934
  above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12626
12935
  items.slice(windowStart, windowEnd).map((item, i) => {
@@ -12653,60 +12962,388 @@ function PluginPicker({
12653
12962
  hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
12654
12963
  ] });
12655
12964
  }
12656
- function ProcessListMonitor() {
12657
- const [, setTick] = useState(0);
12658
- const [selectedIndex, setSelectedIndex] = useState(0);
12659
- useEffect(() => {
12660
- const id = setInterval(() => setTick((t) => t + 1), 1e3);
12661
- return () => clearInterval(id);
12662
- }, []);
12663
- const registry = getProcessRegistry();
12664
- const all = registry.list();
12665
- const stats = registry.stats();
12666
- const safeIndex = Math.min(selectedIndex, Math.max(0, all.length - 1));
12667
- if (safeIndex !== selectedIndex) {
12668
- setSelectedIndex(safeIndex);
12965
+ var CHROME_ROWS5 = 15;
12966
+ function formatCount(noun, count) {
12967
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
12968
+ }
12969
+ function toolActionLabel(enabled) {
12970
+ return enabled ? "Enter disables this tool" : "Enter re-enables this tool";
12971
+ }
12972
+ function statusBadge2(enabled) {
12973
+ return enabled ? /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF active" }) : /* @__PURE__ */ jsx(Text, { color: "red", children: "\u25CB disabled" });
12974
+ }
12975
+ function rwBadge(mutating) {
12976
+ return mutating ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "mut" }) : /* @__PURE__ */ jsx(Text, { color: "cyan", children: "ro" });
12977
+ }
12978
+ function displayCategory(cat) {
12979
+ if (!cat || cat.trim() === "") return "Other";
12980
+ return cat.charAt(0).toUpperCase() + cat.slice(1);
12981
+ }
12982
+ function buildRows(items) {
12983
+ const rows = [];
12984
+ let lastCat = "";
12985
+ for (let i = 0; i < items.length; i++) {
12986
+ const item = items[i];
12987
+ const cat = displayCategory(item.category);
12988
+ if (cat !== lastCat) {
12989
+ lastCat = cat;
12990
+ rows.push({ type: "header", category: cat });
12991
+ }
12992
+ rows.push({ type: "item", item, index: i });
12669
12993
  }
12670
- const selected = all[safeIndex];
12671
- const now = Date.now();
12672
- const running = all.filter((p) => !p.killed).length;
12673
- const b = stats.breaker;
12674
- const breakerState = b.state === "closed" ? "\u{1F7E2} closed" : b.state === "half-open" ? "\u{1F7E1} half-open" : "\u{1F534} open";
12675
- const pageSize = Math.max(1, Math.floor(all.length / 2));
12676
- useInput((input, key) => {
12677
- if (key.upArrow) {
12678
- setSelectedIndex((prev) => Math.max(0, prev - 1));
12679
- } else if (key.downArrow) {
12680
- setSelectedIndex((prev) => Math.min(all.length - 1, prev + 1));
12681
- } else if (key.pageUp) {
12682
- setSelectedIndex((prev) => Math.max(0, prev - pageSize));
12683
- } else if (key.pageDown) {
12684
- setSelectedIndex((prev) => Math.min(all.length - 1, prev + pageSize));
12685
- } else if (key.home || key.ctrl && input === "a" || input === "g") {
12686
- setSelectedIndex(0);
12687
- } else if (key.end || key.ctrl && input === "e" || input === "G") {
12688
- setSelectedIndex(Math.max(0, all.length - 1));
12689
- } else if (key.return && selected) {
12690
- getProcessRegistry().kill(selected.pid);
12691
- } else if (key.delete && selected) {
12692
- getProcessRegistry().kill(selected.pid, { force: true });
12693
- } else if (input === "a" && !key.ctrl) {
12694
- getProcessRegistry().killAll();
12695
- } else if (input === "A") {
12696
- getProcessRegistry().killAll({ force: true });
12697
- } else if (input === "r") {
12698
- getProcessRegistry().forceBreakerReset();
12994
+ return rows;
12995
+ }
12996
+ function windowRows(rows, focus, max) {
12997
+ if (rows.length <= max) {
12998
+ return { rows, start: 0, end: rows.length, contextHeader: null };
12999
+ }
13000
+ let start = focus - Math.floor(max / 2);
13001
+ if (start < 0) start = 0;
13002
+ let end = start + max;
13003
+ if (end > rows.length) {
13004
+ end = rows.length;
13005
+ start = end - max;
13006
+ }
13007
+ let contextHeader = null;
13008
+ if (start > 0) {
13009
+ const first = rows[start];
13010
+ if (first && first.type === "item") {
13011
+ for (let i = start - 1; i >= 0; i--) {
13012
+ const r = rows[i];
13013
+ if (r && r.type === "header") {
13014
+ contextHeader = r.category;
13015
+ break;
13016
+ }
13017
+ }
12699
13018
  }
12700
- });
12701
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, children: [
12702
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
12703
- /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "PROCESS LIST" }),
12704
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
12705
- /* @__PURE__ */ jsxs(Text, { color: running > 0 ? "yellow" : "green", children: [
12706
- "\u25CF",
12707
- running
12708
- ] }),
12709
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/" }),
13019
+ }
13020
+ return { rows: rows.slice(start, end), start, end, contextHeader };
13021
+ }
13022
+ function ToolsPicker({
13023
+ items,
13024
+ selected,
13025
+ busy = false,
13026
+ hint,
13027
+ filter
13028
+ }) {
13029
+ const { stdout } = useStdout();
13030
+ const termRows = stdout?.rows ?? 24;
13031
+ const maxVisible = Math.max(6, termRows - CHROME_ROWS5);
13032
+ const total = items.length;
13033
+ let visibleItems;
13034
+ let visibleSelected;
13035
+ const filterActive = Boolean(filter && filter.length > 0);
13036
+ if (filterActive) {
13037
+ const q = filter.toLowerCase();
13038
+ visibleItems = items.filter(
13039
+ (it) => it.name.toLowerCase().includes(q) || it.owner.toLowerCase().includes(q) || it.description.toLowerCase().includes(q)
13040
+ );
13041
+ const rawSelected = items[selected];
13042
+ visibleSelected = rawSelected ? visibleItems.indexOf(rawSelected) : 0;
13043
+ if (visibleSelected < 0) visibleSelected = 0;
13044
+ } else {
13045
+ visibleItems = items;
13046
+ visibleSelected = selected;
13047
+ }
13048
+ const rows = buildRows(visibleItems);
13049
+ const enabledCount = items.filter((item) => item.enabled).length;
13050
+ const disabledCount = total - enabledCount;
13051
+ const selectedItem = visibleItems[visibleSelected];
13052
+ let displayRows;
13053
+ let hiddenAbove = 0;
13054
+ let hiddenBelow = 0;
13055
+ let contextHeader = null;
13056
+ if (filterActive) {
13057
+ displayRows = rows;
13058
+ } else {
13059
+ const selectedRowIdx = rows.findIndex(
13060
+ (r) => r.type === "item" && r.index === visibleSelected
13061
+ );
13062
+ const win = windowRows(
13063
+ rows,
13064
+ selectedRowIdx < 0 ? 0 : selectedRowIdx,
13065
+ maxVisible
13066
+ );
13067
+ displayRows = win.rows;
13068
+ hiddenAbove = win.start;
13069
+ hiddenBelow = rows.length - win.end;
13070
+ contextHeader = win.contextHeader;
13071
+ }
13072
+ const hasFilter = filterActive;
13073
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [
13074
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "\u2501\u2501 Tools \u2501\u2501" }),
13075
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: hasFilter ? `Filter: ${filter} (${visibleItems.length} match${visibleItems.length === 1 ? "" : "es"}) \xB7 Backspace edit \xB7 Esc clear` : `\u2191/\u2193 select \xB7 type to filter \xB7 Enter toggles \xB7 Esc close \xB7 ${formatCount("active", enabledCount)} / ${formatCount("disabled", disabledCount)}` }),
13076
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Disabled tools stay listed here so you can select them and re-enable them later." }),
13077
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: busy ? "Loading tools\u2026" : "No tools registered." }) : visibleItems.length === 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13078
+ 'No tools match "',
13079
+ filter,
13080
+ '".'
13081
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
13082
+ hiddenAbove > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${hiddenAbove} more` }) : null,
13083
+ contextHeader && !hasFilter ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13084
+ " ",
13085
+ contextHeader
13086
+ ] }) : null,
13087
+ displayRows.map((row) => {
13088
+ if (row.type === "header") {
13089
+ return /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13090
+ " ",
13091
+ row.category
13092
+ ] }, `cat-${row.category}`);
13093
+ }
13094
+ const { item, index } = row;
13095
+ const focused = index === visibleSelected;
13096
+ return /* @__PURE__ */ jsxs(
13097
+ Text,
13098
+ {
13099
+ inverse: focused,
13100
+ ...focused ? { color: "green" } : {},
13101
+ wrap: "truncate-end",
13102
+ children: [
13103
+ focused ? "\u203A " : " ",
13104
+ statusBadge2(item.enabled),
13105
+ " ",
13106
+ /* @__PURE__ */ jsx(Text, { bold: true, children: item.name.padEnd(20) }),
13107
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13108
+ `[${item.owner}]`.padEnd(22),
13109
+ rwBadge(item.mutating),
13110
+ " ",
13111
+ " ",
13112
+ item.permission.padEnd(6),
13113
+ " ",
13114
+ " ",
13115
+ /* @__PURE__ */ jsxs(Text, { color: item.descMode === "simple" ? "yellow" : void 0, children: [
13116
+ "desc:",
13117
+ item.descMode
13118
+ ] })
13119
+ ] })
13120
+ ]
13121
+ },
13122
+ item.name
13123
+ );
13124
+ }),
13125
+ hiddenBelow > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${hiddenBelow} more` }) : null
13126
+ ] }) }),
13127
+ selectedItem ? /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
13128
+ /* @__PURE__ */ jsxs(Text, { children: [
13129
+ /* @__PURE__ */ jsx(Text, { color: selectedItem.enabled ? "green" : "red", children: toolActionLabel(selectedItem.enabled) }),
13130
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${selectedItem.mutating ? "Mutating" : "Read-only"} \xB7 permission: ${selectedItem.permission} \xB7 descriptions: ${selectedItem.descMode}` })
13131
+ ] }),
13132
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate-end", children: selectedItem.description || "No description provided." })
13133
+ ] }) : null,
13134
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13135
+ ] });
13136
+ }
13137
+ var CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
13138
+ var CHROME_ROWS6 = 7;
13139
+ function normalizeQuery(raw) {
13140
+ return raw.trim().toLowerCase();
13141
+ }
13142
+ function buildRows2(entries) {
13143
+ const rows = [];
13144
+ let lastCat = "";
13145
+ for (let i = 0; i < entries.length; i++) {
13146
+ const e = entries[i];
13147
+ const cat = e.category || "App";
13148
+ if (cat !== lastCat) {
13149
+ lastCat = cat;
13150
+ rows.push({ type: "header", category: cat });
13151
+ }
13152
+ rows.push({ type: "item", entry: e, index: i });
13153
+ }
13154
+ return rows;
13155
+ }
13156
+ function windowRows2(rows, focus, max) {
13157
+ if (rows.length <= max) {
13158
+ return { rows, start: 0, end: rows.length, contextHeader: null };
13159
+ }
13160
+ let start = focus - Math.floor(max / 2);
13161
+ if (start < 0) start = 0;
13162
+ let end = start + max;
13163
+ if (end > rows.length) {
13164
+ end = rows.length;
13165
+ start = end - max;
13166
+ }
13167
+ let contextHeader = null;
13168
+ if (start > 0) {
13169
+ const first = rows[start];
13170
+ if (first && first.type === "item") {
13171
+ for (let i = start - 1; i >= 0; i--) {
13172
+ const r = rows[i];
13173
+ if (r && r.type === "header") {
13174
+ contextHeader = r.category;
13175
+ break;
13176
+ }
13177
+ }
13178
+ }
13179
+ }
13180
+ return { rows: rows.slice(start, end), start, end, contextHeader };
13181
+ }
13182
+ function HelpPanel({
13183
+ entries,
13184
+ filter,
13185
+ selected,
13186
+ hint
13187
+ }) {
13188
+ const { stdout } = useStdout();
13189
+ const termRows = stdout?.rows ?? 24;
13190
+ const maxVisible = Math.max(6, termRows - CHROME_ROWS6);
13191
+ const query = normalizeQuery(filter);
13192
+ const filtered = query ? entries.filter(
13193
+ (e) => e.name.toLowerCase().includes(query) || e.description.toLowerCase().includes(query) || e.category.toLowerCase().includes(query) || (e.aliases ?? []).some((a) => a.toLowerCase().includes(query))
13194
+ ) : entries;
13195
+ filtered.sort((a, b) => {
13196
+ const ai = CATEGORY_ORDER.indexOf(a.category);
13197
+ const bi = CATEGORY_ORDER.indexOf(b.category);
13198
+ if (ai !== bi) return ai - bi;
13199
+ return a.name.localeCompare(b.name);
13200
+ });
13201
+ const rows = buildRows2(filtered);
13202
+ const focus = Math.min(selected, Math.max(0, filtered.length - 1));
13203
+ const selectedRowIdx = rows.findIndex((r) => r.type === "item" && r.index === focus);
13204
+ const win = windowRows2(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, maxVisible);
13205
+ const { rows: visible, start, end, contextHeader } = win;
13206
+ const hiddenAbove = start;
13207
+ const hiddenBelow = rows.length - end;
13208
+ const hasFilter = query.length > 0;
13209
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
13210
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u2501\u2501 Help / Command Palette \u2501\u2501" }),
13211
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: hasFilter ? `Filter: ${filter} (${filtered.length} match${filtered.length === 1 ? "" : "es"})` : "\u2191/\u2193 navigate \xB7 Enter run \xB7 Tab fill \xB7 type to filter \xB7 Esc close" }),
13212
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13213
+ 'No commands match "',
13214
+ filter,
13215
+ '".'
13216
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
13217
+ hiddenAbove > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${hiddenAbove} more` }) : null,
13218
+ contextHeader && !hasFilter ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13219
+ " ",
13220
+ contextHeader
13221
+ ] }) : null,
13222
+ visible.map((row) => {
13223
+ if (row.type === "header") {
13224
+ return /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13225
+ " ",
13226
+ row.category
13227
+ ] }, `cat-${row.category}`);
13228
+ }
13229
+ const { entry, index } = row;
13230
+ const isSelected = index === focus;
13231
+ return /* @__PURE__ */ jsxs(
13232
+ Text,
13233
+ {
13234
+ inverse: isSelected,
13235
+ ...isSelected ? { color: "cyan" } : {},
13236
+ wrap: "truncate-end",
13237
+ children: [
13238
+ isSelected ? "\u203A " : " ",
13239
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
13240
+ "/",
13241
+ entry.name
13242
+ ] }),
13243
+ entry.aliases && entry.aliases.length > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13244
+ " (",
13245
+ entry.aliases.map((a) => `/${a}`).join(", "),
13246
+ ")"
13247
+ ] }) : null,
13248
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13249
+ " \u2014 ",
13250
+ entry.description
13251
+ ] }),
13252
+ entry.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13253
+ " ",
13254
+ entry.argsHint
13255
+ ] }) : null
13256
+ ]
13257
+ },
13258
+ entry.name
13259
+ );
13260
+ }),
13261
+ hiddenBelow > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${hiddenBelow} more` }) : null
13262
+ ] }) }),
13263
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13264
+ ] });
13265
+ }
13266
+ function ShadowPanel({
13267
+ shadow,
13268
+ hint
13269
+ }) {
13270
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
13271
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "\u2501\u2501 Shadow Agent \u2501\u2501" }),
13272
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "s start \xB7 t stop \xB7 i cycle interval \xB7 m cycle model \xB7 Esc close" }),
13273
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
13274
+ /* @__PURE__ */ jsxs(Box, { children: [
13275
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Status: " }),
13276
+ shadow.running ? /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF running" }) : /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u25CB stopped" }),
13277
+ shadow.activeId ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${shadow.activeId.slice(0, 12)}\u2026` }) : null
13278
+ ] }),
13279
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
13280
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Model: " }),
13281
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: shadow.model })
13282
+ ] }),
13283
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
13284
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Interval: " }),
13285
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: shadow.intervalMs >= 1e3 ? `${shadow.intervalMs / 1e3}s` : `${shadow.intervalMs}ms` }),
13286
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (min 5s)" })
13287
+ ] }),
13288
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: shadow.running ? "Press t to stop the Shadow Agent" : "Press s to start the Shadow Agent" }) })
13289
+ ] }),
13290
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13291
+ ] });
13292
+ }
13293
+ function ProcessListMonitor() {
13294
+ const [, setTick] = useState(0);
13295
+ const [selectedIndex, setSelectedIndex] = useState(0);
13296
+ useEffect(() => {
13297
+ const id = setInterval(() => setTick((t) => t + 1), 1e3);
13298
+ return () => clearInterval(id);
13299
+ }, []);
13300
+ const registry = getProcessRegistry();
13301
+ const all = registry.list();
13302
+ const stats = registry.stats();
13303
+ const safeIndex = Math.min(selectedIndex, Math.max(0, all.length - 1));
13304
+ if (safeIndex !== selectedIndex) {
13305
+ setSelectedIndex(safeIndex);
13306
+ }
13307
+ const selected = all[safeIndex];
13308
+ const now = Date.now();
13309
+ const running = all.filter((p) => !p.killed).length;
13310
+ const b = stats.breaker;
13311
+ const breakerState = b.state === "closed" ? "\u{1F7E2} closed" : b.state === "half-open" ? "\u{1F7E1} half-open" : "\u{1F534} open";
13312
+ const pageSize = Math.max(1, Math.floor(all.length / 2));
13313
+ useInput((input, key) => {
13314
+ if (key.upArrow) {
13315
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
13316
+ } else if (key.downArrow) {
13317
+ setSelectedIndex((prev) => Math.min(all.length - 1, prev + 1));
13318
+ } else if (key.pageUp) {
13319
+ setSelectedIndex((prev) => Math.max(0, prev - pageSize));
13320
+ } else if (key.pageDown) {
13321
+ setSelectedIndex((prev) => Math.min(all.length - 1, prev + pageSize));
13322
+ } else if (key.home || key.ctrl && input === "a" || input === "g") {
13323
+ setSelectedIndex(0);
13324
+ } else if (key.end || key.ctrl && input === "e" || input === "G") {
13325
+ setSelectedIndex(Math.max(0, all.length - 1));
13326
+ } else if (key.return && selected) {
13327
+ getProcessRegistry().kill(selected.pid);
13328
+ } else if (key.delete && selected) {
13329
+ getProcessRegistry().kill(selected.pid, { force: true });
13330
+ } else if (input === "a" && !key.ctrl) {
13331
+ getProcessRegistry().killAll();
13332
+ } else if (input === "A") {
13333
+ getProcessRegistry().killAll({ force: true });
13334
+ } else if (input === "r") {
13335
+ getProcessRegistry().forceBreakerReset();
13336
+ }
13337
+ });
13338
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, children: [
13339
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
13340
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "PROCESS LIST" }),
13341
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
13342
+ /* @__PURE__ */ jsxs(Text, { color: running > 0 ? "yellow" : "green", children: [
13343
+ "\u25CF",
13344
+ running
13345
+ ] }),
13346
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/" }),
12710
13347
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12711
13348
  all.length,
12712
13349
  " tracked"
@@ -13335,7 +13972,7 @@ function SlashMenu({ query, matches, selected }) {
13335
13972
  rows.push({ type: "item", match: m, index: i });
13336
13973
  }
13337
13974
  const selectedRowIdx = rows.findIndex((r) => r.type === "item" && r.index === selected);
13338
- const visible = windowRows(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, MAX_VISIBLE_ITEMS);
13975
+ const visible = windowRows3(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, MAX_VISIBLE_ITEMS);
13339
13976
  const hiddenAbove = visible.start;
13340
13977
  const hiddenBelow = rows.length - visible.end;
13341
13978
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
@@ -13387,7 +14024,7 @@ function SlashMenu({ query, matches, selected }) {
13387
14024
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500 \u2191\u2193 nav \xB7 Enter run \xB7 Tab fill \xB7 Esc close" })
13388
14025
  ] });
13389
14026
  }
13390
- function windowRows(rows, focus, max) {
14027
+ function windowRows3(rows, focus, max) {
13391
14028
  if (rows.length <= max) {
13392
14029
  return { rows, start: 0, end: rows.length, contextHeader: null };
13393
14030
  }
@@ -13546,11 +14183,11 @@ function WorktreeMonitor({
13546
14183
  useInput((input, key) => {
13547
14184
  if (isWorktreeMonitorCloseKey(input, key)) onClose();
13548
14185
  });
13549
- const TERMINAL_TTL_MS2 = 5 * 6e4;
14186
+ const TERMINAL_TTL_MS = 5 * 6e4;
13550
14187
  const list = Object.values(worktrees);
13551
14188
  const recent = list.filter((w) => {
13552
14189
  if (["active", "committing", "merging", "needs-review"].includes(w.status)) return true;
13553
- return nowTick - (w.allocatedAt ?? nowTick) < TERMINAL_TTL_MS2;
14190
+ return nowTick - (w.allocatedAt ?? nowTick) < TERMINAL_TTL_MS;
13554
14191
  });
13555
14192
  const active = list.filter((w) => ["active", "committing", "merging"].includes(w.status)).length;
13556
14193
  const merged = list.filter((w) => w.status === "merged").length;
@@ -13780,80 +14417,293 @@ function runGit(cwd, args) {
13780
14417
  }
13781
14418
  });
13782
14419
  }
13783
- var MB = 1024 * 1024;
13784
- function defaultHeapLogPath() {
13785
- return path6.join(wstackGlobalRoot(), "logs", "heap.jsonl");
13786
- }
13787
- function takeHeapSample() {
13788
- const m = process.memoryUsage();
13789
- const limit = v8.getHeapStatistics().heap_size_limit || 0;
14420
+
14421
+ // src/app-initial-state.ts
14422
+ function buildRestoredEntries(messages, restoredToolCalls) {
14423
+ if (!messages || messages.length === 0) return [];
14424
+ const visible = messages.filter((m) => m.role !== "system");
14425
+ if (visible.length === 0) return [];
14426
+ return rehydrateHistory(visible, 1, restoredToolCalls);
14427
+ }
14428
+ function createInitialState(options) {
14429
+ const {
14430
+ banner,
14431
+ appVersion,
14432
+ provider,
14433
+ model,
14434
+ cwd,
14435
+ family,
14436
+ keyTail,
14437
+ restoredEntries,
14438
+ enhanceEnabled,
14439
+ initialAgentsMonitorOpen
14440
+ } = options;
14441
+ const initialNextId = 1 + restoredEntries.length;
13790
14442
  return {
13791
- ts: (/* @__PURE__ */ new Date()).toISOString(),
13792
- rss: m.rss,
13793
- heapUsed: m.heapUsed,
13794
- heapTotal: m.heapTotal,
13795
- external: m.external,
13796
- heapLimit: limit,
13797
- load: limit > 0 ? m.heapUsed / limit : 0
13798
- };
13799
- }
13800
- function startHeapWatchdog(opts = {}) {
13801
- const sampleEveryMs = opts.sampleEveryMs ?? 6e4;
13802
- const logEveryMs = opts.logEveryMs ?? 3e5;
13803
- const logPath = opts.logPath ?? defaultHeapLogPath();
13804
- const warnAt = opts.warnAt ?? 0.6;
13805
- const criticalAt = opts.criticalAt ?? 0.85;
13806
- const REARM_MARGIN = 0.05;
13807
- let warnArmed = true;
13808
- let criticalArmed = true;
13809
- let lastLogAt = 0;
13810
- let writeChain = Promise.resolve();
13811
- let dirReady = false;
13812
- const append = (line) => {
13813
- writeChain = writeChain.then(async () => {
13814
- if (!dirReady) {
13815
- await fsp.mkdir(path6.dirname(logPath), { recursive: true });
13816
- dirReady = true;
13817
- }
13818
- await fsp.appendFile(logPath, `${line}
13819
- `, "utf8");
13820
- }).catch(() => void 0);
13821
- };
13822
- const tick = () => {
13823
- let userTimings = 0;
13824
- try {
13825
- userTimings = performance.getEntriesByType("mark").length + performance.getEntriesByType("measure").length;
13826
- if (userTimings > 0) {
13827
- performance.clearMarks();
13828
- performance.clearMeasures();
13829
- }
13830
- } catch {
13831
- }
13832
- const s2 = takeHeapSample();
13833
- if (s2.load >= criticalAt && criticalArmed) {
13834
- criticalArmed = false;
13835
- warnArmed = false;
13836
- opts.onWarn?.(
13837
- "critical",
13838
- `Heap critical: ${Math.round(s2.heapUsed / MB)} MB of ~${Math.round(s2.heapLimit / MB)} MB V8 limit (${Math.round(s2.load * 100)}%). An out-of-memory crash is likely soon \u2014 finish/checkpoint work and restart the session. Diagnostics: ${logPath}`,
13839
- s2
13840
- );
13841
- } else if (s2.load >= warnAt && warnArmed) {
13842
- warnArmed = false;
13843
- opts.onWarn?.(
13844
- "warn",
13845
- `Heap high: ${Math.round(s2.heapUsed / MB)} MB of ~${Math.round(s2.heapLimit / MB)} MB V8 limit (${Math.round(s2.load * 100)}%). Memory diagnostics are being recorded to ${logPath}`,
13846
- s2
13847
- );
13848
- }
13849
- if (!warnArmed && s2.load < warnAt - REARM_MARGIN) warnArmed = true;
13850
- if (!criticalArmed && s2.load < criticalAt - REARM_MARGIN) criticalArmed = true;
13851
- const due = Date.now() - lastLogAt >= logEveryMs;
13852
- const crossed = s2.load >= warnAt;
13853
- if (due || crossed) {
13854
- lastLogAt = Date.now();
13855
- let extras = {};
13856
- try {
14443
+ entries: [
14444
+ ...banner ? [
14445
+ {
14446
+ id: 0,
14447
+ kind: "banner",
14448
+ version: appVersion ?? "dev",
14449
+ provider: provider ?? "agent",
14450
+ model,
14451
+ cwd,
14452
+ family,
14453
+ keyTail
14454
+ }
14455
+ ] : [],
14456
+ ...restoredEntries
14457
+ ],
14458
+ historyGen: 0,
14459
+ buffer: "",
14460
+ cursor: 0,
14461
+ streamingText: "",
14462
+ toolStream: null,
14463
+ status: "idle",
14464
+ interrupts: 0,
14465
+ steeringPending: false,
14466
+ steerSnapshot: null,
14467
+ hint: "",
14468
+ brain: { state: "idle" },
14469
+ brainPrompt: null,
14470
+ nextId: initialNextId,
14471
+ picker: { open: false, query: "", matches: [], selected: 0 },
14472
+ slashPicker: { open: false, query: "", matches: [], selected: 0 },
14473
+ runningTools: /* @__PURE__ */ new Map(),
14474
+ queue: [],
14475
+ nextQueueId: 1,
14476
+ inputHistory: [],
14477
+ historyIndex: 0,
14478
+ historyDraft: "",
14479
+ modelPicker: {
14480
+ open: false,
14481
+ step: "provider",
14482
+ providerOptions: [],
14483
+ modelOptions: [],
14484
+ filteredOptions: [],
14485
+ selected: 0,
14486
+ searchQuery: ""
14487
+ },
14488
+ autonomyPicker: { open: false, options: [], selected: 0 },
14489
+ modePicker: { open: false, modes: [], selected: 0 },
14490
+ designPicker: { open: false, kits: [], selected: 0, stack: "web" },
14491
+ promptPicker: {
14492
+ open: false,
14493
+ all: [],
14494
+ categories: [],
14495
+ recentSlugs: [],
14496
+ catIndex: 0,
14497
+ selected: 0
14498
+ },
14499
+ resumePicker: {
14500
+ open: false,
14501
+ sessions: [],
14502
+ selected: 0,
14503
+ busy: false,
14504
+ hint: void 0,
14505
+ error: void 0
14506
+ },
14507
+ settingsPicker: {
14508
+ open: false,
14509
+ field: 0,
14510
+ lastSettingsField: 0,
14511
+ filter: "",
14512
+ mode: "off",
14513
+ delayMs: 0,
14514
+ titleAnimation: true,
14515
+ yolo: false,
14516
+ streamFleet: true,
14517
+ chime: false,
14518
+ confirmExit: true,
14519
+ nextPrediction: false,
14520
+ featureMcp: true,
14521
+ featurePlugins: true,
14522
+ featureMemory: true,
14523
+ featureSkills: true,
14524
+ featureModelsRegistry: true,
14525
+ tokenSavingTier: "off",
14526
+ allowOutsideProjectRoot: true,
14527
+ contextAutoCompact: true,
14528
+ contextStrategy: "hybrid",
14529
+ contextMode: "balanced",
14530
+ maxConcurrent: 10,
14531
+ logLevel: "info",
14532
+ auditLevel: "standard",
14533
+ indexOnStart: true,
14534
+ multiDiffSummaryThreshold: 5,
14535
+ maxIterations: 500,
14536
+ autoProceedMaxIterations: 50,
14537
+ enhanceDelayMs: 6e4,
14538
+ enhanceEnabled: true,
14539
+ enhanceLanguage: "original",
14540
+ debugStream: false,
14541
+ statuslineMode: "detailed",
14542
+ reasoningMode: "auto",
14543
+ reasoningEffort: "high",
14544
+ reasoningPreserve: false,
14545
+ thinkingWord: "thinking",
14546
+ thinkingWordEditing: false,
14547
+ thinkingWordDraft: "",
14548
+ cacheTtl: "default",
14549
+ configScope: "global",
14550
+ animationStyle: "rainbow"
14551
+ },
14552
+ statuslinePicker: {
14553
+ open: false,
14554
+ field: 0,
14555
+ hiddenItems: [],
14556
+ visibleChips: [],
14557
+ hint: void 0
14558
+ },
14559
+ pluginPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
14560
+ mcpPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
14561
+ toolsPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0, filter: void 0 },
14562
+ brainPanel: { open: false, riskLevel: "medium", log: [], selected: 0, hint: void 0 },
14563
+ helpPanel: { open: false, entries: [], selected: 0, filter: "", hint: void 0 },
14564
+ shadowPanel: { open: false, shadow: { activeId: null, running: false, model: "", intervalMs: 3e4 }, hint: void 0 },
14565
+ authPanel: AUTH_PANEL_INITIAL,
14566
+ projectPicker: {
14567
+ open: false,
14568
+ allItems: [],
14569
+ items: [],
14570
+ selected: 0,
14571
+ filter: "",
14572
+ hint: void 0
14573
+ },
14574
+ fKeyPicker: { open: false, selected: 0 },
14575
+ confirmQueue: [],
14576
+ enhance: null,
14577
+ enhanceEnabled,
14578
+ enhanceBusy: false,
14579
+ escConfirm: null,
14580
+ sendModePicker: null,
14581
+ contextChipVersion: 0,
14582
+ fleet: {},
14583
+ leader: {
14584
+ iterations: 0,
14585
+ toolCalls: 0,
14586
+ recentTools: [],
14587
+ currentTool: void 0,
14588
+ startedAt: Date.now(),
14589
+ lastEventAt: Date.now(),
14590
+ iterating: false
14591
+ },
14592
+ fleetCost: 0,
14593
+ fleetTokens: { input: 0, output: 0 },
14594
+ fleetConcurrency: 4,
14595
+ streamFleet: true,
14596
+ monitorOpen: false,
14597
+ agentsMonitorOpen: initialAgentsMonitorOpen ?? false,
14598
+ helpOpen: false,
14599
+ todosMonitorOpen: false,
14600
+ queuePanelOpen: false,
14601
+ processListOpen: false,
14602
+ auditPanelOpen: false,
14603
+ planPanelOpen: false,
14604
+ kanbanPanelOpen: false,
14605
+ goalPanelOpen: false,
14606
+ sessionsPanelOpen: false,
14607
+ sessionsPanel: { sessions: [], busy: false, selected: -1 },
14608
+ sessionResumeConfirm: null,
14609
+ collabSession: null,
14610
+ checkpoints: [],
14611
+ rewindOverlay: null,
14612
+ eternalStage: null,
14613
+ goalSummary: null,
14614
+ autoPhase: null,
14615
+ sddBoard: null,
14616
+ worktrees: {},
14617
+ worktreeMonitorOpen: false,
14618
+ coordinator: {
14619
+ goals: [],
14620
+ timeline: [],
14621
+ knowledgeCount: 0,
14622
+ monitorOpen: false,
14623
+ healthy: false
14624
+ },
14625
+ scrollOffset: 0,
14626
+ totalLines: 0,
14627
+ viewportRows: 0,
14628
+ pendingNewLines: 0,
14629
+ debugStreamStats: null,
14630
+ countdown: null
14631
+ };
14632
+ }
14633
+ var MB = 1024 * 1024;
14634
+ function defaultHeapLogPath() {
14635
+ return path9.join(wstackGlobalRoot(), "logs", "heap.jsonl");
14636
+ }
14637
+ function takeHeapSample() {
14638
+ const m = process.memoryUsage();
14639
+ const limit = v8.getHeapStatistics().heap_size_limit || 0;
14640
+ return {
14641
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14642
+ rss: m.rss,
14643
+ heapUsed: m.heapUsed,
14644
+ heapTotal: m.heapTotal,
14645
+ external: m.external,
14646
+ heapLimit: limit,
14647
+ load: limit > 0 ? m.heapUsed / limit : 0
14648
+ };
14649
+ }
14650
+ function startHeapWatchdog(opts = {}) {
14651
+ const sampleEveryMs = opts.sampleEveryMs ?? 6e4;
14652
+ const logEveryMs = opts.logEveryMs ?? 3e5;
14653
+ const logPath = opts.logPath ?? defaultHeapLogPath();
14654
+ const warnAt = opts.warnAt ?? 0.6;
14655
+ const criticalAt = opts.criticalAt ?? 0.85;
14656
+ const REARM_MARGIN = 0.05;
14657
+ let warnArmed = true;
14658
+ let criticalArmed = true;
14659
+ let lastLogAt = 0;
14660
+ let writeChain = Promise.resolve();
14661
+ let dirReady = false;
14662
+ const append = (line) => {
14663
+ writeChain = writeChain.then(async () => {
14664
+ if (!dirReady) {
14665
+ await fsp.mkdir(path9.dirname(logPath), { recursive: true });
14666
+ dirReady = true;
14667
+ }
14668
+ await fsp.appendFile(logPath, `${line}
14669
+ `, "utf8");
14670
+ }).catch(() => void 0);
14671
+ };
14672
+ const tick = () => {
14673
+ let userTimings = 0;
14674
+ try {
14675
+ userTimings = performance.getEntriesByType("mark").length + performance.getEntriesByType("measure").length;
14676
+ if (userTimings > 0) {
14677
+ performance.clearMarks();
14678
+ performance.clearMeasures();
14679
+ }
14680
+ } catch {
14681
+ }
14682
+ const s2 = takeHeapSample();
14683
+ if (s2.load >= criticalAt && criticalArmed) {
14684
+ criticalArmed = false;
14685
+ warnArmed = false;
14686
+ opts.onWarn?.(
14687
+ "critical",
14688
+ `Heap critical: ${Math.round(s2.heapUsed / MB)} MB of ~${Math.round(s2.heapLimit / MB)} MB V8 limit (${Math.round(s2.load * 100)}%). An out-of-memory crash is likely soon \u2014 finish/checkpoint work and restart the session. Diagnostics: ${logPath}`,
14689
+ s2
14690
+ );
14691
+ } else if (s2.load >= warnAt && warnArmed) {
14692
+ warnArmed = false;
14693
+ opts.onWarn?.(
14694
+ "warn",
14695
+ `Heap high: ${Math.round(s2.heapUsed / MB)} MB of ~${Math.round(s2.heapLimit / MB)} MB V8 limit (${Math.round(s2.load * 100)}%). Memory diagnostics are being recorded to ${logPath}`,
14696
+ s2
14697
+ );
14698
+ }
14699
+ if (!warnArmed && s2.load < warnAt - REARM_MARGIN) warnArmed = true;
14700
+ if (!criticalArmed && s2.load < criticalAt - REARM_MARGIN) criticalArmed = true;
14701
+ const due = Date.now() - lastLogAt >= logEveryMs;
14702
+ const crossed = s2.load >= warnAt;
14703
+ if (due || crossed) {
14704
+ lastLogAt = Date.now();
14705
+ let extras = {};
14706
+ try {
13857
14707
  extras = opts.collectStats?.() ?? {};
13858
14708
  } catch {
13859
14709
  }
@@ -14603,7 +15453,7 @@ async function loadIndex(root) {
14603
15453
  async function walk(root, rel, depth, out) {
14604
15454
  if (out.length >= MAX_FILES_INDEXED) return;
14605
15455
  if (depth > MAX_DEPTH) return;
14606
- const dir = rel ? path6.join(root, rel) : root;
15456
+ const dir = rel ? path9.join(root, rel) : root;
14607
15457
  let entries;
14608
15458
  try {
14609
15459
  entries = await fsp.readdir(dir, { withFileTypes: true });
@@ -14702,7 +15552,7 @@ function useFileSearch(options) {
14702
15552
  dispatch({ type: "pickerClose" });
14703
15553
  return;
14704
15554
  }
14705
- const absPath = path6.isAbsolute(picked) ? picked : path6.join(projectRoot, picked);
15555
+ const absPath = path9.isAbsolute(picked) ? picked : path9.join(projectRoot, picked);
14706
15556
  try {
14707
15557
  const data = await fsp.readFile(absPath, "utf8");
14708
15558
  const token = await builder.registerFile({
@@ -15265,13 +16115,152 @@ function usePickerKeys(host) {
15265
16115
  }
15266
16116
  return true;
15267
16117
  }
15268
- if (state.statuslinePicker.open) {
16118
+ if (state.mcpPicker.open) {
15269
16119
  if (key.escape) {
15270
- dispatch({ type: "statuslineClose" });
16120
+ dispatch({ type: "mcpPickerClose" });
15271
16121
  return true;
15272
16122
  }
15273
16123
  if (key.mouse?.kind === "wheel") {
15274
- dispatch({ type: "statuslineFieldMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16124
+ dispatch({ type: "mcpPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16125
+ return true;
16126
+ }
16127
+ if (key.upArrow) {
16128
+ dispatch({ type: "mcpPickerMove", delta: -1 });
16129
+ return true;
16130
+ }
16131
+ if (key.downArrow) {
16132
+ dispatch({ type: "mcpPickerMove", delta: 1 });
16133
+ return true;
16134
+ }
16135
+ if (input === "r" || input === "R") {
16136
+ void host.onMcpPickerRestart?.();
16137
+ return true;
16138
+ }
16139
+ if (key.leftArrow || key.rightArrow || isEnter) {
16140
+ if (debouncedEnter(host)) return true;
16141
+ void host.onMcpPickerToggle?.();
16142
+ return true;
16143
+ }
16144
+ return true;
16145
+ }
16146
+ if (state.toolsPicker.open) {
16147
+ if (key.escape) {
16148
+ if (state.toolsPicker.filter) {
16149
+ dispatch({ type: "toolsPickerFilter", filter: "" });
16150
+ } else {
16151
+ dispatch({ type: "toolsPickerClose" });
16152
+ }
16153
+ return true;
16154
+ }
16155
+ if (key.mouse?.kind === "wheel") {
16156
+ dispatch({ type: "toolsPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16157
+ return true;
16158
+ }
16159
+ if (key.upArrow) {
16160
+ dispatch({ type: "toolsPickerMove", delta: -1 });
16161
+ return true;
16162
+ }
16163
+ if (key.downArrow) {
16164
+ dispatch({ type: "toolsPickerMove", delta: 1 });
16165
+ return true;
16166
+ }
16167
+ if (key.leftArrow || key.rightArrow || isEnter) {
16168
+ if (debouncedEnter(host)) return true;
16169
+ void host.onToolsPickerToggle?.();
16170
+ return true;
16171
+ }
16172
+ if (input && input.length === 1 && input.charCodeAt(0) >= 32 && input.charCodeAt(0) < 127) {
16173
+ dispatch({ type: "toolsPickerFilter", filter: (state.toolsPicker.filter ?? "") + input });
16174
+ return true;
16175
+ }
16176
+ if (key.backspace) {
16177
+ const cur = state.toolsPicker.filter ?? "";
16178
+ dispatch({ type: "toolsPickerFilter", filter: cur.length > 0 ? cur.slice(0, -1) : "" });
16179
+ return true;
16180
+ }
16181
+ return true;
16182
+ }
16183
+ if (state.helpPanel.open) {
16184
+ if (key.escape) {
16185
+ dispatch({ type: "helpClose" });
16186
+ return true;
16187
+ }
16188
+ if (key.mouse?.kind === "wheel") {
16189
+ dispatch({ type: "helpMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16190
+ return true;
16191
+ }
16192
+ if (key.upArrow) {
16193
+ dispatch({ type: "helpMove", delta: -1 });
16194
+ return true;
16195
+ }
16196
+ if (key.downArrow) {
16197
+ dispatch({ type: "helpMove", delta: 1 });
16198
+ return true;
16199
+ }
16200
+ if (key.backspace && state.helpPanel.filter) {
16201
+ dispatch({ type: "helpFilter", filter: state.helpPanel.filter.slice(0, -1) });
16202
+ return true;
16203
+ }
16204
+ if (input && input.length === 1 && input.charCodeAt(0) >= 32 && input.charCodeAt(0) < 127) {
16205
+ dispatch({ type: "helpFilter", filter: state.helpPanel.filter + input });
16206
+ return true;
16207
+ }
16208
+ if (isEnter) {
16209
+ if (debouncedEnter(host)) return true;
16210
+ host.onHelpPanelEnter?.();
16211
+ return true;
16212
+ }
16213
+ return true;
16214
+ }
16215
+ if (state.brainPanel.open) {
16216
+ if (key.escape) {
16217
+ dispatch({ type: "brainClose" });
16218
+ return true;
16219
+ }
16220
+ if (key.mouse?.kind === "wheel") {
16221
+ dispatch({ type: "brainMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16222
+ return true;
16223
+ }
16224
+ if (key.upArrow) {
16225
+ dispatch({ type: "brainMove", delta: -1 });
16226
+ return true;
16227
+ }
16228
+ if (key.downArrow) {
16229
+ dispatch({ type: "brainMove", delta: 1 });
16230
+ return true;
16231
+ }
16232
+ if (key.leftArrow) {
16233
+ host.onBrainRiskChange?.(-1);
16234
+ return true;
16235
+ }
16236
+ if (key.rightArrow) {
16237
+ host.onBrainRiskChange?.(1);
16238
+ return true;
16239
+ }
16240
+ return true;
16241
+ }
16242
+ if (state.shadowPanel.open) {
16243
+ if (key.escape) {
16244
+ dispatch({ type: "shadowClose" });
16245
+ return true;
16246
+ }
16247
+ if (input === "s" || input === "S") {
16248
+ void host.onShadowStart?.();
16249
+ return true;
16250
+ }
16251
+ if (input === "t" || input === "T") {
16252
+ void host.onShadowStop?.();
16253
+ return true;
16254
+ }
16255
+ return true;
16256
+ }
16257
+ if (state.statuslinePicker.open) {
16258
+ if (key.escape) {
16259
+ dispatch({ type: "statuslineClose" });
16260
+ return true;
16261
+ }
16262
+ if (key.mouse?.kind === "wheel") {
16263
+ dispatch({ type: "statuslineFieldMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
15275
16264
  return true;
15276
16265
  }
15277
16266
  if (key.upArrow) {
@@ -15452,6 +16441,120 @@ function usePickerKeys(host) {
15452
16441
  [host]
15453
16442
  );
15454
16443
  }
16444
+ function useInputHistoryPersistence({
16445
+ projectRoot,
16446
+ inputHistory,
16447
+ dispatch
16448
+ }) {
16449
+ const inputHistoryStore = useMemo(() => {
16450
+ if (!projectRoot) return null;
16451
+ const file = resolveWstackPaths({ projectRoot }).projectInputHistory;
16452
+ return new InputHistoryStore(file, new DefaultSecretScrubber(), INPUT_HISTORY_DEFAULT_MAX);
16453
+ }, [projectRoot]);
16454
+ const inputHistoryLoadedRef = useRef(false);
16455
+ const inputHistorySaveTimerRef = useRef(void 0);
16456
+ const inputHistoryLastSavedRef = useRef("");
16457
+ useEffect(() => {
16458
+ if (!inputHistoryStore) return;
16459
+ let cancelled = false;
16460
+ inputHistoryStore.load().then((entries) => {
16461
+ if (cancelled) return;
16462
+ inputHistoryLoadedRef.current = true;
16463
+ inputHistoryLastSavedRef.current = JSON.stringify(entries);
16464
+ if (entries.length === 0) return;
16465
+ dispatch({ type: "setInputHistory", entries });
16466
+ }).catch(() => {
16467
+ if (!cancelled) inputHistoryLoadedRef.current = true;
16468
+ });
16469
+ return () => {
16470
+ cancelled = true;
16471
+ };
16472
+ }, [inputHistoryStore, dispatch]);
16473
+ useEffect(() => {
16474
+ if (!inputHistoryStore || !inputHistoryLoadedRef.current) return;
16475
+ const snapshot = JSON.stringify(inputHistory);
16476
+ if (snapshot === inputHistoryLastSavedRef.current) return;
16477
+ if (inputHistorySaveTimerRef.current) clearTimeout(inputHistorySaveTimerRef.current);
16478
+ inputHistorySaveTimerRef.current = setTimeout(() => {
16479
+ inputHistoryLastSavedRef.current = snapshot;
16480
+ inputHistoryStore.save(inputHistory).catch(() => {
16481
+ });
16482
+ }, 200);
16483
+ return () => {
16484
+ if (inputHistorySaveTimerRef.current) clearTimeout(inputHistorySaveTimerRef.current);
16485
+ };
16486
+ }, [inputHistory, inputHistoryStore]);
16487
+ }
16488
+ function buildModePickerOptions(result) {
16489
+ return toModeOptions(result.modes, result.activeId);
16490
+ }
16491
+ function useModePicker({
16492
+ dispatch,
16493
+ getModes
16494
+ }) {
16495
+ const openModePicker = useCallback(async () => {
16496
+ if (!getModes) return;
16497
+ const result = await getModes();
16498
+ const options = buildModePickerOptions(result);
16499
+ dispatch({ type: "modePickerOpen", modes: options });
16500
+ }, [dispatch, getModes]);
16501
+ return { openModePicker };
16502
+ }
16503
+ function buildPromptPickerPayload(all, recentSlugs) {
16504
+ const entries = all.map((e) => ({
16505
+ slug: e.slug,
16506
+ title: e.title,
16507
+ description: e.description,
16508
+ category: e.category,
16509
+ source: e.source,
16510
+ content: e.content,
16511
+ favorite: e.favorite
16512
+ }));
16513
+ const hasFavorites = entries.some((e) => e.favorite);
16514
+ const categories = [
16515
+ "all",
16516
+ ...recentSlugs.length > 0 ? ["\u{1F558} recent"] : [],
16517
+ ...hasFavorites ? ["\u2605 favorites"] : [],
16518
+ ...Array.from(new Set(entries.map((e) => e.category))).sort()
16519
+ ];
16520
+ return { all: entries, categories, recentSlugs };
16521
+ }
16522
+ function usePromptPicker({
16523
+ projectRoot,
16524
+ dispatch
16525
+ }) {
16526
+ const promptLoaderRef = useRef(null);
16527
+ const promptUsageRef = useRef(null);
16528
+ const getPromptLoader = useCallback(() => {
16529
+ if (!promptLoaderRef.current && projectRoot) {
16530
+ let bundledDir;
16531
+ try {
16532
+ const req = createRequire(import.meta.url);
16533
+ bundledDir = path9.join(
16534
+ path9.dirname(req.resolve("@wrongstack/core/package.json")),
16535
+ "data",
16536
+ "prompts"
16537
+ );
16538
+ } catch {
16539
+ bundledDir = void 0;
16540
+ }
16541
+ const paths = resolveWstackPaths({ projectRoot });
16542
+ promptLoaderRef.current = new DefaultPromptLoader({ paths, bundledDir });
16543
+ promptUsageRef.current = new PromptUsageStore(paths.promptUsage);
16544
+ }
16545
+ return promptLoaderRef.current;
16546
+ }, [projectRoot]);
16547
+ const openPromptPicker = useCallback(async () => {
16548
+ const loader = getPromptLoader();
16549
+ if (!loader) return;
16550
+ loader.invalidateCache();
16551
+ const all = await loader.list();
16552
+ const recentSlugs = (await promptUsageRef.current?.recent(50).catch(() => []))?.map((r) => r.slug) ?? [];
16553
+ const payload = buildPromptPickerPayload(all, recentSlugs);
16554
+ dispatch({ type: "promptPickerOpen", ...payload });
16555
+ }, [dispatch, getPromptLoader]);
16556
+ return { openPromptPicker };
16557
+ }
15455
16558
 
15456
16559
  // src/queue-slash.ts
15457
16560
  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";
@@ -15615,6 +16718,90 @@ function useQueueManager({
15615
16718
  };
15616
16719
  }, [slashRegistry, stateRef, dispatch, getSettings, saveSettings, midRunSendPickerRef]);
15617
16720
  }
16721
+ function statuslineHiddenDiffers(hookHidden, pickerHidden) {
16722
+ const currentHidden = new Set(hookHidden);
16723
+ const pickerHiddenSet = new Set(pickerHidden);
16724
+ return currentHidden.size !== pickerHiddenSet.size || pickerHidden.some((item) => !currentHidden.has(item)) || hookHidden.some((item) => !pickerHiddenSet.has(item));
16725
+ }
16726
+ function useStatuslineHiddenSync({
16727
+ pickerOpen,
16728
+ pickerHidden,
16729
+ hiddenItems,
16730
+ setHiddenItems
16731
+ }) {
16732
+ useEffect(() => {
16733
+ if (!pickerOpen) return;
16734
+ if (statuslineHiddenDiffers(hiddenItems, pickerHidden)) {
16735
+ setHiddenItems([...pickerHidden]);
16736
+ }
16737
+ }, [pickerHidden, pickerOpen, setHiddenItems, hiddenItems]);
16738
+ }
16739
+ function computeStreamChipActions(input) {
16740
+ const actions = [];
16741
+ const visible = new Set(input.visibleChipKeys);
16742
+ if (input.brainPromptNow && !input.brainPromptPrev) {
16743
+ actions.push({ type: "statuslineChipShow", key: "brain", expiresIn: 5 });
16744
+ } else if (!input.brainPromptNow && input.brainPromptPrev && visible.has("brain")) {
16745
+ actions.push({ type: "statuslineChipExpire", key: "brain" });
16746
+ }
16747
+ if (input.enhanceNow && !input.enhancePrev) {
16748
+ actions.push({ type: "statuslineChipShow", key: "enhance", expiresIn: 5 });
16749
+ } else if (!input.enhanceNow && input.enhancePrev && visible.has("enhance")) {
16750
+ actions.push({ type: "statuslineChipExpire", key: "enhance" });
16751
+ }
16752
+ return actions;
16753
+ }
16754
+ function computeExpiredChipKeys(chips, now = Date.now()) {
16755
+ return chips.filter((chip) => isChipExpired(chip, now)).map((chip) => chip.key);
16756
+ }
16757
+ function useStreamChipExpiration({
16758
+ brainPrompt,
16759
+ enhance,
16760
+ visibleChips,
16761
+ dispatch
16762
+ }) {
16763
+ const prevBrainPromptRef = useRef(brainPrompt);
16764
+ const prevEnhanceRef = useRef(enhance);
16765
+ useEffect(() => {
16766
+ const actions = computeStreamChipActions({
16767
+ brainPromptNow: !!brainPrompt,
16768
+ brainPromptPrev: !!prevBrainPromptRef.current,
16769
+ enhanceNow: !!enhance,
16770
+ enhancePrev: !!prevEnhanceRef.current,
16771
+ visibleChipKeys: visibleChips.map((chip) => chip.key)
16772
+ });
16773
+ for (const action of actions) dispatch(action);
16774
+ prevBrainPromptRef.current = brainPrompt;
16775
+ prevEnhanceRef.current = enhance;
16776
+ }, [brainPrompt, enhance, visibleChips, dispatch]);
16777
+ useEffect(() => {
16778
+ const interval = setInterval(() => {
16779
+ const expired = computeExpiredChipKeys(visibleChips, Date.now());
16780
+ for (const key of expired) {
16781
+ dispatch({ type: "statuslineChipExpire", key });
16782
+ }
16783
+ }, 3e4);
16784
+ return () => clearInterval(interval);
16785
+ }, [visibleChips, dispatch]);
16786
+ }
16787
+ function formatWorkingDirChip(workingDir, projectRoot) {
16788
+ if (!workingDir || workingDir === projectRoot) return void 0;
16789
+ const rel = path9.relative(projectRoot, workingDir) || ".";
16790
+ if (rel === ".") return void 0;
16791
+ return rel.replaceAll(path9.sep, "/");
16792
+ }
16793
+ function useWorkingDirChip(ctx, projectRoot) {
16794
+ const [workingDirChip, setWorkingDirChip] = useState(
16795
+ () => formatWorkingDirChip(ctx.workingDir, projectRoot)
16796
+ );
16797
+ useEffect(() => {
16798
+ setWorkingDirChip(formatWorkingDirChip(ctx.workingDir, projectRoot));
16799
+ return ctx.onWorkingDirChanged((newDir) => {
16800
+ setWorkingDirChip(formatWorkingDirChip(newDir, projectRoot));
16801
+ });
16802
+ }, [ctx, projectRoot]);
16803
+ return workingDirChip;
16804
+ }
15618
16805
  function useStatuslineState(opts) {
15619
16806
  const [liveModel, setLiveModel] = useState(opts.model);
15620
16807
  const [liveProvider, setLiveProvider] = useState(opts.provider ?? "agent");
@@ -16227,6 +17414,12 @@ function createPanelOpenDispatcher(deps) {
16227
17414
  case "pluginPickerOpen":
16228
17415
  dispatch({ type: "pluginPickerOpen" });
16229
17416
  return true;
17417
+ case "mcpPickerOpen":
17418
+ dispatch({ type: "mcpPickerOpen" });
17419
+ return true;
17420
+ case "toolsPickerOpen":
17421
+ dispatch({ type: "toolsPickerOpen" });
17422
+ return true;
16230
17423
  case "projectPickerOpen":
16231
17424
  void openProjectPicker();
16232
17425
  return true;
@@ -16239,6 +17432,24 @@ function createPanelOpenDispatcher(deps) {
16239
17432
  return true;
16240
17433
  }
16241
17434
  return false;
17435
+ case "brainOpen":
17436
+ if (deps.openBrainPanel) {
17437
+ void deps.openBrainPanel();
17438
+ return true;
17439
+ }
17440
+ return false;
17441
+ case "shadowOpen":
17442
+ if (deps.openShadowPanel) {
17443
+ void deps.openShadowPanel();
17444
+ return true;
17445
+ }
17446
+ return false;
17447
+ case "helpOpen":
17448
+ if (deps.openHelpPanel) {
17449
+ void deps.openHelpPanel();
17450
+ return true;
17451
+ }
17452
+ return false;
16242
17453
  case "toggleMonitor":
16243
17454
  dispatch({ type: "toggleMonitor" });
16244
17455
  return true;
@@ -16267,6 +17478,22 @@ function createPanelOpenDispatcher(deps) {
16267
17478
  };
16268
17479
  }
16269
17480
 
17481
+ // src/submit-history.ts
17482
+ function shouldPushSubmittedHistory(raw) {
17483
+ const trimmed = raw.trim();
17484
+ if (!trimmed) return { push: false, route: "empty", trimmed };
17485
+ if (trimmed === "/image" || trimmed === "/paste-image") {
17486
+ return { push: true, route: "paste-image", trimmed };
17487
+ }
17488
+ if (trimmed === "/prompt" || trimmed === "/prompts-browse") {
17489
+ return { push: true, route: "prompt-picker", trimmed };
17490
+ }
17491
+ if (trimmed.startsWith("/")) {
17492
+ return { push: true, route: "slash", trimmed };
17493
+ }
17494
+ return { push: true, route: "normal", trimmed };
17495
+ }
17496
+
16270
17497
  // src/paste-accumulator.ts
16271
17498
  var BEGIN = "[200~";
16272
17499
  var END = "[201~";
@@ -16295,7 +17522,7 @@ function feedPaste(accum, input) {
16295
17522
  if (accum === null && !input.includes(BEGIN)) {
16296
17523
  if (input.startsWith("[") && !input.startsWith(BEGIN) && !input.startsWith(END)) {
16297
17524
  if (PARTIAL_ANSI_RE.test(input)) {
16298
- return { accum: "", complete: null };
17525
+ return { accum: null, complete: null };
16299
17526
  }
16300
17527
  return null;
16301
17528
  }
@@ -16338,7 +17565,7 @@ function renderList3() {
16338
17565
  }
16339
17566
 
16340
17567
  // src/slash-command-search.ts
16341
- var CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
17568
+ var CATEGORY_ORDER2 = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
16342
17569
  function buildSlashCommandMatches(entries, rawQuery) {
16343
17570
  const query = normalizeSlashQuery(rawQuery);
16344
17571
  const matches = [];
@@ -16349,7 +17576,7 @@ function buildSlashCommandMatches(entries, rawQuery) {
16349
17576
  }
16350
17577
  return matches.sort((a, b) => {
16351
17578
  if (a.rank !== b.rank) return a.rank - b.rank;
16352
- const catDiff = CATEGORY_ORDER.indexOf(a.category) - CATEGORY_ORDER.indexOf(b.category);
17579
+ const catDiff = CATEGORY_ORDER2.indexOf(a.category) - CATEGORY_ORDER2.indexOf(b.category);
16353
17580
  if (catDiff !== 0) return catDiff;
16354
17581
  return a.name.localeCompare(b.name);
16355
17582
  }).map(({ rank: _rank, ...match }) => match);
@@ -16546,24 +17773,38 @@ function rehydrateHistory(messages, startId, toolCalls) {
16546
17773
  outputTokens: tc.outputTokens,
16547
17774
  outputLines: tc.outputLines
16548
17775
  });
17776
+ const pushAssistantText = (parts) => {
17777
+ const text = parts.join("").trim();
17778
+ if (text) entries.push({ id: nextId++, kind: "assistant", text });
17779
+ parts.length = 0;
17780
+ };
16549
17781
  for (const msg of messages) {
16550
17782
  if (msg.role === "system") continue;
16551
- const text = textOf(msg).trim();
16552
17783
  if (msg.role === "user") {
17784
+ const text = textOf(msg).trim();
16553
17785
  if (text) entries.push({ id: nextId++, kind: "user", text });
16554
17786
  continue;
16555
17787
  }
16556
17788
  if (msg.role === "assistant") {
16557
- if (text) entries.push({ id: nextId++, kind: "assistant", text });
16558
- if (Array.isArray(msg.content)) {
16559
- for (const block of msg.content) {
16560
- if (block.type !== "tool_use") continue;
16561
- const tc = toolCallsById.get(block.id);
16562
- if (!tc) continue;
16563
- entries.push(toolEntryFor(tc));
16564
- consumed.add(block.id);
17789
+ if (!Array.isArray(msg.content)) {
17790
+ const text = textOf(msg).trim();
17791
+ if (text) entries.push({ id: nextId++, kind: "assistant", text });
17792
+ continue;
17793
+ }
17794
+ const textParts = [];
17795
+ for (const block of msg.content) {
17796
+ if (block.type === "text") {
17797
+ textParts.push(block.text);
17798
+ continue;
16565
17799
  }
17800
+ if (block.type !== "tool_use") continue;
17801
+ pushAssistantText(textParts);
17802
+ const tc = toolCallsById.get(block.id);
17803
+ if (!tc) continue;
17804
+ entries.push(toolEntryFor(tc));
17805
+ consumed.add(block.id);
16566
17806
  }
17807
+ pushAssistantText(textParts);
16567
17808
  }
16568
17809
  }
16569
17810
  if (toolCalls) {
@@ -16663,6 +17904,16 @@ function App({
16663
17904
  saveSettings,
16664
17905
  getPluginItems,
16665
17906
  onPluginToggle,
17907
+ getMcpServers,
17908
+ onMcpToggle,
17909
+ onMcpRestart,
17910
+ getToolsItems,
17911
+ onToolToggle,
17912
+ getBrainData,
17913
+ onBrainRiskLevel,
17914
+ getShadowData,
17915
+ onShadowStart,
17916
+ onShadowStop,
16666
17917
  authHost,
16667
17918
  predictNext,
16668
17919
  onSuggestionsParsed,
@@ -16683,458 +17934,239 @@ function App({
16683
17934
  interruptController,
16684
17935
  statuslineHiddenItems,
16685
17936
  setStatuslineHiddenItems,
16686
- saveStatuslineHiddenItems,
16687
- agentsMonitorController,
16688
- initialGoal,
16689
- initialAsk,
16690
- sessionsDir,
16691
- modeLabel,
16692
- getModeLabel,
16693
- getModes,
16694
- registerDebugStreamCallback,
16695
- restoreDebugStreamCallback,
16696
- restoredToolCalls,
16697
- getProjectPickerItems,
16698
- onProjectSelect,
16699
- requestExit,
16700
- getLiveSessions,
16701
- onSwitchToSession,
16702
- initialAgentsMonitorOpen,
16703
- onPanelOpen,
16704
- subscribeCoordinatorEvents,
16705
- onCoordinatorStart,
16706
- onCoordinatorStop,
16707
- // Reserved for the coordinator monitor panel: terminal-driven task discovery/claim.
16708
- onCoordinatorTasks: _onCoordinatorTasks,
16709
- onCoordinatorClaim: _onCoordinatorClaim,
16710
- coordinatorRunning = false,
16711
- clientId
16712
- }) {
16713
- const { exit } = useApp();
16714
- const { stdout } = useStdout();
16715
- const {
16716
- liveModel,
16717
- setLiveModel,
16718
- liveProvider,
16719
- setLiveProvider,
16720
- activeMaxContext,
16721
- setActiveMaxContext,
16722
- yoloLive,
16723
- setYoloLive,
16724
- autonomyLive,
16725
- setAutonomyLive,
16726
- liveModeLabel,
16727
- setLiveModeLabel,
16728
- hiddenItems,
16729
- setHiddenItems,
16730
- sessionCount,
16731
- setSessionCount
16732
- } = useStatuslineState({
16733
- model,
16734
- provider,
16735
- effectiveMaxContext,
16736
- yolo,
16737
- getAutonomy,
16738
- modeLabel,
16739
- statuslineHiddenItems
16740
- });
16741
- const hiddenItemsRef = useRef(hiddenItems);
16742
- hiddenItemsRef.current = hiddenItems;
16743
- const prevBranchRef = useRef(null);
16744
- const [indexState, setIndexState] = useState(() => getIndexState());
16745
- useEffect(() => {
16746
- setIndexState(getIndexState());
16747
- return onIndexStateChange((next) => setIndexState(next));
16748
- }, []);
16749
- const [breakerCountdown, setBreakerCountdown] = useState(
16750
- () => getProcessRegistry().getBreakerCountdown()
16751
- );
16752
- useEffect(() => {
16753
- const s2 = getSettings?.();
16754
- if (s2) {
16755
- getProcessRegistry().setBreakerConfig({
16756
- enabled: s2.breakerEnabled ?? false,
16757
- autoKillResetMs: s2.breakerAutoKillResetMs ?? 6e4
16758
- });
16759
- }
16760
- return getProcessRegistry().onBreakerCountdownChange((snap) => setBreakerCountdown(snap));
16761
- }, [getSettings]);
16762
- const breakerArmed = breakerCountdown !== null;
16763
- useEffect(() => {
16764
- if (!breakerArmed) return;
16765
- const t = setInterval(
16766
- () => setBreakerCountdown(getProcessRegistry().getBreakerCountdown()),
16767
- 1e3
16768
- );
16769
- return () => clearInterval(t);
16770
- }, [breakerArmed]);
16771
- useEffect(() => {
16772
- setHiddenItems([...statuslineHiddenItems]);
16773
- }, [statuslineHiddenItems]);
16774
- useEffect(() => {
16775
- setStatuslineHiddenItems(hiddenItems);
16776
- saveStatuslineHiddenItems(hiddenItems).catch?.((err) => {
16777
- console.error("[statusline] failed to persist hidden items:", err);
16778
- });
16779
- }, [setStatuslineHiddenItems, saveStatuslineHiddenItems, hiddenItems]);
16780
- const projectRoot = agent.ctx.projectRoot;
16781
- const promptLoaderRef = useRef(null);
16782
- const promptUsageRef = useRef(null);
16783
- const refreshGoalSummary = useCallback(() => {
16784
- if (!projectRoot) return;
16785
- const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
16786
- loadGoal(goalPath).then((goal) => {
16787
- if (!goal) {
16788
- dispatch({ type: "goalSummary", summary: null });
16789
- return;
16790
- }
16791
- const lastEntry = goal.journal?.[goal.journal.length - 1];
16792
- dispatch({
16793
- type: "goalSummary",
16794
- summary: {
16795
- goal: goal.goal,
16796
- refinedGoal: goal.refinedGoal,
16797
- goalState: goal.goalState ?? "active",
16798
- iterations: goal.iterations,
16799
- progress: goal.progress,
16800
- progressNote: goal.progressNote,
16801
- progressTrend: goal.progressTrend,
16802
- deliverables: goal.deliverables,
16803
- lastTask: lastEntry?.task,
16804
- lastStatus: lastEntry?.status
16805
- }
16806
- });
16807
- }).catch(() => {
16808
- });
16809
- }, [projectRoot]);
16810
- useEffect(() => {
16811
- refreshGoalSummary();
16812
- }, [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
17937
+ saveStatuslineHiddenItems,
17938
+ agentsMonitorController,
17939
+ initialGoal,
17940
+ initialAsk,
17941
+ sessionsDir,
17942
+ modeLabel,
17943
+ getModeLabel,
17944
+ getModes,
17945
+ registerDebugStreamCallback,
17946
+ restoreDebugStreamCallback,
17947
+ restoredToolCalls,
17948
+ getProjectPickerItems,
17949
+ onProjectSelect,
17950
+ requestExit,
17951
+ getLiveSessions,
17952
+ onSwitchToSession,
17953
+ initialAgentsMonitorOpen,
17954
+ onPanelOpen,
17955
+ subscribeCoordinatorEvents,
17956
+ onCoordinatorStart,
17957
+ onCoordinatorStop,
17958
+ // Reserved for the coordinator monitor panel: terminal-driven task discovery/claim.
17959
+ onCoordinatorTasks: _onCoordinatorTasks,
17960
+ onCoordinatorClaim: _onCoordinatorClaim,
17961
+ coordinatorRunning = false,
17962
+ clientId
17963
+ }) {
17964
+ const { exit } = useApp();
17965
+ const { stdout } = useStdout();
17966
+ const {
17967
+ liveModel,
17968
+ setLiveModel,
17969
+ liveProvider,
17970
+ setLiveProvider,
17971
+ activeMaxContext,
17972
+ setActiveMaxContext,
17973
+ yoloLive,
17974
+ setYoloLive,
17975
+ autonomyLive,
17976
+ setAutonomyLive,
17977
+ liveModeLabel,
17978
+ setLiveModeLabel,
17979
+ hiddenItems,
17980
+ setHiddenItems,
17981
+ sessionCount,
17982
+ setSessionCount
17983
+ } = useStatuslineState({
17984
+ model,
17985
+ provider,
17986
+ effectiveMaxContext,
17987
+ yolo,
17988
+ getAutonomy,
17989
+ modeLabel,
17990
+ statuslineHiddenItems
17004
17991
  });
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("");
17992
+ const hiddenItemsRef = useRef(hiddenItems);
17993
+ hiddenItemsRef.current = hiddenItems;
17994
+ const prevBranchRef = useRef(null);
17995
+ const [indexState, setIndexState] = useState(() => getIndexState());
17013
17996
  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]);
17997
+ setIndexState(getIndexState());
17998
+ return onIndexStateChange((next) => setIndexState(next));
17999
+ }, []);
18000
+ const [breakerCountdown, setBreakerCountdown] = useState(
18001
+ () => getProcessRegistry().getBreakerCountdown()
18002
+ );
17029
18003
  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(() => {
18004
+ const s2 = getSettings?.();
18005
+ if (s2) {
18006
+ getProcessRegistry().setBreakerConfig({
18007
+ enabled: s2.breakerEnabled ?? false,
18008
+ autoKillResetMs: s2.breakerAutoKillResetMs ?? 6e4
17037
18009
  });
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
18010
  }
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]);
18011
+ return getProcessRegistry().onBreakerCountdownChange((snap) => setBreakerCountdown(snap));
18012
+ }, [getSettings]);
18013
+ const breakerArmed = breakerCountdown !== null;
17092
18014
  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);
18015
+ if (!breakerArmed) return;
18016
+ const t = setInterval(
18017
+ () => setBreakerCountdown(getProcessRegistry().getBreakerCountdown()),
18018
+ 1e3
18019
+ );
18020
+ return () => clearInterval(t);
18021
+ }, [breakerArmed]);
17110
18022
  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" });
18023
+ setHiddenItems([...statuslineHiddenItems]);
18024
+ }, [statuslineHiddenItems]);
18025
+ useEffect(() => {
18026
+ setStatuslineHiddenItems(hiddenItems);
18027
+ saveStatuslineHiddenItems(hiddenItems).catch?.((err) => {
18028
+ console.error(
18029
+ JSON.stringify({
18030
+ level: "error",
18031
+ event: "statusline.persist_hidden_failed",
18032
+ message: err instanceof Error ? err.message : String(err),
18033
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
18034
+ })
18035
+ );
18036
+ });
18037
+ }, [setStatuslineHiddenItems, saveStatuslineHiddenItems, hiddenItems]);
18038
+ const projectRoot = agent.ctx.projectRoot;
18039
+ const promptUsageRef = useRef(null);
18040
+ const refreshGoalSummary = useCallback(() => {
18041
+ if (!projectRoot) return;
18042
+ const goalPath = resolveWstackPaths({ projectRoot }).projectGoal;
18043
+ loadGoal(goalPath).then((goal) => {
18044
+ if (!goal) {
18045
+ dispatch({ type: "goalSummary", summary: null });
18046
+ return;
17124
18047
  }
17125
- }
17126
- prevEnhanceRef.current = state.enhance;
17127
- }, [state.brainPrompt, state.enhance, state.statuslinePicker.visibleChips, dispatch]);
18048
+ const lastEntry = goal.journal?.[goal.journal.length - 1];
18049
+ dispatch({
18050
+ type: "goalSummary",
18051
+ summary: {
18052
+ goal: goal.goal,
18053
+ refinedGoal: goal.refinedGoal,
18054
+ goalState: goal.goalState ?? "active",
18055
+ iterations: goal.iterations,
18056
+ progress: goal.progress,
18057
+ progressNote: goal.progressNote,
18058
+ progressTrend: goal.progressTrend,
18059
+ deliverables: goal.deliverables,
18060
+ lastTask: lastEntry?.task,
18061
+ lastStatus: lastEntry?.status
18062
+ }
18063
+ });
18064
+ }).catch(() => {
18065
+ });
18066
+ }, [projectRoot]);
17128
18067
  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 });
18068
+ refreshGoalSummary();
18069
+ }, [refreshGoalSummary]);
18070
+ const restoredEntries = buildRestoredEntries(agent.ctx.messages, restoredToolCalls);
18071
+ const [state, dispatch] = useReducer(
18072
+ reducer,
18073
+ createInitialState({
18074
+ banner,
18075
+ appVersion,
18076
+ provider,
18077
+ model,
18078
+ cwd: agent.ctx.cwd,
18079
+ family,
18080
+ keyTail,
18081
+ restoredEntries,
18082
+ enhanceEnabled,
18083
+ initialAgentsMonitorOpen
18084
+ })
18085
+ );
18086
+ useInputHistoryPersistence({
18087
+ projectRoot,
18088
+ inputHistory: state.inputHistory,
18089
+ dispatch
18090
+ });
18091
+ const { openPromptPicker } = usePromptPicker({ projectRoot, dispatch });
18092
+ const { openModePicker } = useModePicker({ dispatch, getModes });
18093
+ const openBrainPanel = React5.useCallback(() => {
18094
+ if (!getBrainData) return;
18095
+ const data = getBrainData();
18096
+ dispatch({ type: "brainOpen", riskLevel: data.riskLevel, log: data.log });
18097
+ }, [getBrainData]);
18098
+ const changeBrainRisk = React5.useCallback(
18099
+ (delta) => {
18100
+ dispatch({ type: "brainRiskChange", delta });
18101
+ },
18102
+ []
18103
+ );
18104
+ const prevRiskRef = React5.useRef(state.brainPanel.riskLevel);
18105
+ React5.useEffect(() => {
18106
+ if (!state.brainPanel.open) return;
18107
+ const current = state.brainPanel.riskLevel;
18108
+ if (current !== prevRiskRef.current && onBrainRiskLevel) {
18109
+ prevRiskRef.current = current;
18110
+ const err = onBrainRiskLevel(current);
18111
+ if (err) dispatch({ type: "brainHint", text: err });
18112
+ else dispatch({ type: "brainHint", text: `Risk ceiling \u2192 ${current.toUpperCase()}` });
18113
+ }
18114
+ }, [state.brainPanel.riskLevel, state.brainPanel.open, onBrainRiskLevel]);
18115
+ const openShadowPanel = React5.useCallback(() => {
18116
+ if (!getShadowData) return;
18117
+ const data = getShadowData();
18118
+ dispatch({ type: "shadowOpen", shadow: data });
18119
+ }, [getShadowData]);
18120
+ const handleShadowStart = React5.useCallback(async () => {
18121
+ if (!onShadowStart) return;
18122
+ dispatch({ type: "shadowHint", text: "Starting Shadow Agent\u2026" });
18123
+ try {
18124
+ const err = await onShadowStart();
18125
+ if (err) dispatch({ type: "shadowHint", text: err });
18126
+ else {
18127
+ if (getShadowData) dispatch({ type: "shadowUpdate", shadow: getShadowData() });
18128
+ dispatch({ type: "shadowHint", text: "Shadow Agent started." });
17134
18129
  }
17135
- }, 3e4);
17136
- return () => clearInterval(interval);
17137
- }, [state.statuslinePicker.visibleChips, dispatch]);
18130
+ } catch (e) {
18131
+ dispatch({ type: "shadowHint", text: `Failed to start: ${toErrorMessage(e)}` });
18132
+ }
18133
+ }, [onShadowStart, getShadowData]);
18134
+ const handleShadowStop = React5.useCallback(async () => {
18135
+ if (!onShadowStop) return;
18136
+ dispatch({ type: "shadowHint", text: "Stopping Shadow Agent\u2026" });
18137
+ try {
18138
+ const err = await onShadowStop();
18139
+ if (err) dispatch({ type: "shadowHint", text: err });
18140
+ else {
18141
+ if (getShadowData) dispatch({ type: "shadowUpdate", shadow: getShadowData() });
18142
+ dispatch({ type: "shadowHint", text: "Shadow Agent stopped." });
18143
+ }
18144
+ } catch (e) {
18145
+ dispatch({ type: "shadowHint", text: `Failed to stop: ${toErrorMessage(e)}` });
18146
+ }
18147
+ }, [onShadowStop, getShadowData]);
18148
+ const openHelpPanel = React5.useCallback(() => {
18149
+ const entries = Array.from(slashRegistry.listWithOwner()).map(({ cmd, owner }) => ({
18150
+ name: owner === "core" ? cmd.name : `${owner}:${cmd.name}`,
18151
+ description: cmd.description,
18152
+ category: cmd.category ?? "App",
18153
+ aliases: cmd.aliases,
18154
+ argsHint: cmd.argsHint
18155
+ }));
18156
+ dispatch({ type: "helpOpen", entries });
18157
+ }, []);
18158
+ useStatuslineHiddenSync({
18159
+ pickerOpen: state.statuslinePicker.open,
18160
+ pickerHidden: state.statuslinePicker.hiddenItems,
18161
+ hiddenItems,
18162
+ setHiddenItems: (items) => setHiddenItems(items)
18163
+ });
18164
+ useStreamChipExpiration({
18165
+ brainPrompt: state.brainPrompt,
18166
+ enhance: state.enhance,
18167
+ visibleChips: state.statuslinePicker.visibleChips,
18168
+ dispatch
18169
+ });
17138
18170
  useAutonomousCoordinator(subscribeCoordinatorEvents, dispatch);
17139
18171
  const builderRef = useRef(null);
17140
18172
  if (builderRef.current === null) {
@@ -17146,23 +18178,10 @@ function App({
17146
18178
  const lastEnterAtRef = useRef(0);
17147
18179
  const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
17148
18180
  const projectName = React5.useMemo(() => {
17149
- const base = path6.basename(projectRoot);
17150
- return base && base !== path6.sep ? base : void 0;
18181
+ const base = path9.basename(projectRoot);
18182
+ return base && base !== path9.sep ? base : void 0;
17151
18183
  }, [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]);
18184
+ const workingDirChip = useWorkingDirChip(agent.ctx, projectRoot);
17166
18185
  const liveSettings = getSettings?.();
17167
18186
  const liveStatuslineMode = liveSettings?.statuslineMode ?? "detailed";
17168
18187
  const liveAnimationStyle = liveSettings?.animationStyle ?? "rainbow";
@@ -17192,6 +18211,7 @@ function App({
17192
18211
  const streamSegmentsRef = useRef([]);
17193
18212
  const pendingDeltaRef = useRef("");
17194
18213
  const flushTimerRef = useRef(null);
18214
+ const assistantCommittedThisRunRef = useRef(false);
17195
18215
  const stateRef = useRef(state);
17196
18216
  stateRef.current = state;
17197
18217
  const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
@@ -17220,7 +18240,7 @@ function App({
17220
18240
  [statuslineHiddenForPicker]
17221
18241
  );
17222
18242
  const [mouseMode, setMouseMode] = useState(mouse);
17223
- const pickerOverlayOpen = state.modelPicker.open || state.autonomyPicker.open || state.modePicker.open || state.designPicker.open || state.settingsPicker.open || state.projectPicker.open || state.slashPicker.open || state.statuslinePicker.open || state.fKeyPicker.open || state.authPanel.open || state.picker.open;
18243
+ const pickerOverlayOpen = state.modelPicker.open || state.autonomyPicker.open || state.modePicker.open || state.designPicker.open || state.settingsPicker.open || state.projectPicker.open || state.slashPicker.open || state.statuslinePicker.open || state.pluginPicker.open || state.mcpPicker.open || state.toolsPicker.open || state.brainPanel.open || state.helpPanel.open || state.shadowPanel.open || state.fKeyPicker.open || state.authPanel.open || state.picker.open;
17224
18244
  const mouseTrackingOn = mouseMode || pickerOverlayOpen;
17225
18245
  const mouseWrittenRef = useRef(false);
17226
18246
  useEffect(() => {
@@ -17564,15 +18584,16 @@ function App({
17564
18584
  const fleetCounts = useMemo(() => {
17565
18585
  const entries = Object.values(state.fleet);
17566
18586
  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 };
18587
+ const running = entries.filter((e) => e.status === "running").length;
18588
+ if (running === 0) return void 0;
18589
+ return { running, idle: 0, pending: 0, completed: 0 };
18590
+ }, [state.fleet]);
18591
+ const visibleSubagentCount = fleetCounts?.running ?? 0;
18592
+ const hasVisibleFleetPanel = useMemo(() => {
18593
+ for (const e of Object.values(state.fleet)) {
18594
+ if (e.status === "running") return true;
18595
+ }
18596
+ return false;
17576
18597
  }, [state.fleet]);
17577
18598
  const entriesWithLeader = useMemo(() => {
17578
18599
  const leaderEntry = {
@@ -18034,7 +19055,10 @@ function App({
18034
19055
  openProjectPicker,
18035
19056
  openStatuslinePicker,
18036
19057
  openAuthPanel: authPanelController.openAuthPanel,
18037
- openModePicker
19058
+ openModePicker,
19059
+ openBrainPanel,
19060
+ openShadowPanel,
19061
+ openHelpPanel
18038
19062
  });
18039
19063
  onPanelOpen.current = dispatcher;
18040
19064
  return () => {
@@ -18046,7 +19070,10 @@ function App({
18046
19070
  openProjectPicker,
18047
19071
  openStatuslinePicker,
18048
19072
  authPanelController.openAuthPanel,
18049
- openModePicker
19073
+ openModePicker,
19074
+ openBrainPanel,
19075
+ openShadowPanel,
19076
+ openHelpPanel
18050
19077
  ]);
18051
19078
  useEffect(() => {
18052
19079
  if (!state.sessionsPanelOpen || !getLiveSessions) return void 0;
@@ -18132,6 +19159,73 @@ function App({
18132
19159
  });
18133
19160
  }
18134
19161
  }, [onPluginToggle]);
19162
+ const refreshMcpPicker = React5.useCallback(() => {
19163
+ if (!getMcpServers) return;
19164
+ dispatch({ type: "mcpPickerSetItems", items: getMcpServers() });
19165
+ }, [getMcpServers]);
19166
+ useEffect(() => {
19167
+ if (!state.mcpPicker.open) return;
19168
+ refreshMcpPicker();
19169
+ }, [state.mcpPicker.open, refreshMcpPicker]);
19170
+ const toggleSelectedMcpServer = React5.useCallback(async () => {
19171
+ if (!onMcpToggle) return;
19172
+ const selected = stateRef.current.mcpPicker.items[stateRef.current.mcpPicker.selected];
19173
+ if (!selected) return;
19174
+ dispatch({ type: "mcpPickerBusy", busy: true });
19175
+ try {
19176
+ const result = await onMcpToggle(selected.name);
19177
+ dispatch({ type: "mcpPickerSetItems", items: result.items });
19178
+ dispatch({ type: "mcpPickerHint", text: result.error ?? result.message });
19179
+ } catch (err) {
19180
+ dispatch({ type: "mcpPickerBusy", busy: false });
19181
+ dispatch({
19182
+ type: "mcpPickerHint",
19183
+ text: `Failed to toggle ${selected.name}: ${toErrorMessage(err)}`
19184
+ });
19185
+ }
19186
+ }, [onMcpToggle]);
19187
+ const restartSelectedMcpServer = React5.useCallback(async () => {
19188
+ if (!onMcpRestart) return;
19189
+ const selected = stateRef.current.mcpPicker.items[stateRef.current.mcpPicker.selected];
19190
+ if (!selected) return;
19191
+ dispatch({ type: "mcpPickerBusy", busy: true });
19192
+ try {
19193
+ const result = await onMcpRestart(selected.name);
19194
+ dispatch({ type: "mcpPickerSetItems", items: result.items });
19195
+ dispatch({ type: "mcpPickerHint", text: result.error ?? result.message });
19196
+ } catch (err) {
19197
+ dispatch({ type: "mcpPickerBusy", busy: false });
19198
+ dispatch({
19199
+ type: "mcpPickerHint",
19200
+ text: `Failed to restart ${selected.name}: ${toErrorMessage(err)}`
19201
+ });
19202
+ }
19203
+ }, [onMcpRestart]);
19204
+ const refreshToolsPicker = React5.useCallback(() => {
19205
+ if (!getToolsItems) return;
19206
+ dispatch({ type: "toolsPickerSetItems", items: getToolsItems() });
19207
+ }, [getToolsItems]);
19208
+ useEffect(() => {
19209
+ if (!state.toolsPicker.open) return;
19210
+ refreshToolsPicker();
19211
+ }, [state.toolsPicker.open, refreshToolsPicker]);
19212
+ const toggleSelectedTool = React5.useCallback(async () => {
19213
+ if (!onToolToggle) return;
19214
+ const selected = stateRef.current.toolsPicker.items[stateRef.current.toolsPicker.selected];
19215
+ if (!selected) return;
19216
+ dispatch({ type: "toolsPickerBusy", busy: true });
19217
+ try {
19218
+ const result = await onToolToggle(selected.name);
19219
+ dispatch({ type: "toolsPickerSetItems", items: result.items });
19220
+ dispatch({ type: "toolsPickerHint", text: result.error ?? result.message });
19221
+ } catch (err) {
19222
+ dispatch({ type: "toolsPickerBusy", busy: false });
19223
+ dispatch({
19224
+ type: "toolsPickerHint",
19225
+ text: `Failed to toggle ${selected.name}: ${toErrorMessage(err)}`
19226
+ });
19227
+ }
19228
+ }, [onToolToggle]);
18135
19229
  const autoSubmitStreakRef = useRef(0);
18136
19230
  const autoSubmitCapWarnedRef = useRef(false);
18137
19231
  const [nextStepsRecheck, setNextStepsRecheck] = useState(0);
@@ -18808,12 +19902,15 @@ Available chords:
18808
19902
  for (const seg of segments) {
18809
19903
  if (seg.text.trim()) {
18810
19904
  dispatch({ type: "addEntry", entry: { kind: seg.kind, text: seg.text } });
19905
+ if (seg.kind === "assistant") assistantCommittedThisRunRef.current = true;
18811
19906
  }
18812
19907
  }
18813
19908
  if (segments.length === 0 && text.trim()) {
18814
19909
  dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
19910
+ assistantCommittedThisRunRef.current = true;
18815
19911
  } else if (segments.length === 0 && fallbackText.trim()) {
18816
19912
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: fallbackText } });
19913
+ assistantCommittedThisRunRef.current = true;
18817
19914
  }
18818
19915
  });
18819
19916
  const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
@@ -19223,6 +20320,18 @@ Available chords:
19223
20320
  }
19224
20321
  },
19225
20322
  onPluginPickerToggle: toggleSelectedPlugin,
20323
+ onMcpPickerToggle: toggleSelectedMcpServer,
20324
+ onMcpPickerRestart: restartSelectedMcpServer,
20325
+ onToolsPickerToggle: toggleSelectedTool,
20326
+ onHelpPanelEnter: () => {
20327
+ const entry = state.helpPanel.entries[state.helpPanel.selected];
20328
+ if (!entry) return;
20329
+ dispatch({ type: "helpClose" });
20330
+ submitRef.current(`/${entry.name}`);
20331
+ },
20332
+ onBrainRiskChange: changeBrainRisk,
20333
+ onShadowStart: handleShadowStart,
20334
+ onShadowStop: handleShadowStop,
19226
20335
  onAuthEnter: authPanelController.onAuthEnter,
19227
20336
  onAuthBack: authPanelController.onAuthBack,
19228
20337
  onAuthShortcut: authPanelController.onAuthShortcut,
@@ -20193,9 +21302,7 @@ Available chords:
20193
21302
  }
20194
21303
  });
20195
21304
  }
20196
- const assistantEntriesBeforeRun = stateRef.current.entries.filter(
20197
- (entry) => entry.kind === "assistant"
20198
- ).length;
21305
+ assistantCommittedThisRunRef.current = false;
20199
21306
  const result = await agent.run(routed.blocks, { signal: ctrl.signal });
20200
21307
  const lingering = streamingTextRef.current;
20201
21308
  const lingeringSegments = streamSegmentsRef.current;
@@ -20215,7 +21322,13 @@ Available chords:
20215
21322
  }
20216
21323
  } else if (lingering.trim()) {
20217
21324
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: lingering } });
20218
- } else if (result.status === "done" && result.finalText?.trim() && stateRef.current.entries.filter((entry) => entry.kind === "assistant").length === assistantEntriesBeforeRun) {
21325
+ } else if (result.status === "done" && result.finalText?.trim() && // Only recover finalText when no assistant entry was committed by the
21326
+ // per-iteration `provider.response` flush during this run. Checked via
21327
+ // a synchronous ref, NOT `stateRef` — the flush's `addEntry` dispatch
21328
+ // isn't reflected in `stateRef` yet when we resume here, so a
21329
+ // state-based count would read stale and re-commit the final reply,
21330
+ // rendering it twice.
21331
+ !assistantCommittedThisRunRef.current) {
20219
21332
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: result.finalText } });
20220
21333
  }
20221
21334
  if (result.status === "aborted") {
@@ -20407,7 +21520,10 @@ Available chords:
20407
21520
  autoSubmitCapWarnedRef.current = false;
20408
21521
  dispatch({ type: "scrollToBottom" });
20409
21522
  const pushSubmittedHistory = () => {
20410
- if (trimmed) dispatch({ type: "historyPush", text: trimmed });
21523
+ const decision = shouldPushSubmittedHistory(trimmed);
21524
+ if (decision.push) {
21525
+ dispatch({ type: "historyPush", text: decision.trimmed });
21526
+ }
20411
21527
  };
20412
21528
  if (trimmed === "/image" || trimmed === "/paste-image") {
20413
21529
  pushSubmittedHistory();
@@ -20944,6 +22060,50 @@ ${content}
20944
22060
  hint: state.pluginPicker.hint
20945
22061
  }
20946
22062
  ) : null,
22063
+ state.mcpPicker.open ? /* @__PURE__ */ jsx(
22064
+ McpPicker,
22065
+ {
22066
+ items: state.mcpPicker.items,
22067
+ selected: state.mcpPicker.selected,
22068
+ busy: state.mcpPicker.busy,
22069
+ hint: state.mcpPicker.hint
22070
+ }
22071
+ ) : null,
22072
+ state.toolsPicker.open ? /* @__PURE__ */ jsx(
22073
+ ToolsPicker,
22074
+ {
22075
+ items: state.toolsPicker.items,
22076
+ selected: state.toolsPicker.selected,
22077
+ busy: state.toolsPicker.busy,
22078
+ hint: state.toolsPicker.hint,
22079
+ filter: state.toolsPicker.filter
22080
+ }
22081
+ ) : null,
22082
+ state.brainPanel.open ? /* @__PURE__ */ jsx(
22083
+ BrainPanel,
22084
+ {
22085
+ riskLevel: state.brainPanel.riskLevel,
22086
+ log: state.brainPanel.log,
22087
+ selected: state.brainPanel.selected,
22088
+ hint: state.brainPanel.hint
22089
+ }
22090
+ ) : null,
22091
+ state.helpPanel.open ? /* @__PURE__ */ jsx(
22092
+ HelpPanel,
22093
+ {
22094
+ entries: state.helpPanel.entries,
22095
+ filter: state.helpPanel.filter,
22096
+ selected: state.helpPanel.selected,
22097
+ hint: state.helpPanel.hint
22098
+ }
22099
+ ) : null,
22100
+ state.shadowPanel.open ? /* @__PURE__ */ jsx(
22101
+ ShadowPanel,
22102
+ {
22103
+ shadow: state.shadowPanel.shadow,
22104
+ hint: state.shadowPanel.hint
22105
+ }
22106
+ ) : null,
20947
22107
  state.authPanel.open ? /* @__PURE__ */ jsx(AuthPanel, { panel: state.authPanel }) : null,
20948
22108
  state.projectPicker.open ? /* @__PURE__ */ jsx(
20949
22109
  ProjectPicker,
@@ -21157,7 +22317,7 @@ ${content}
21157
22317
  brain: state.brain,
21158
22318
  projectName,
21159
22319
  workingDir: workingDirChip,
21160
- subagentCount: Object.keys(state.fleet).length,
22320
+ subagentCount: visibleSubagentCount,
21161
22321
  processCount: getProcessRegistry().activeCount,
21162
22322
  hiddenItems,
21163
22323
  mode: liveStatuslineMode,
@@ -21255,7 +22415,7 @@ ${content}
21255
22415
  onCoordinatorStop: onCoordinatorStop ?? void 0,
21256
22416
  coordinatorRunning
21257
22417
  }
21258
- ) : director || Object.keys(state.fleet).length > 0 || state.collabSession ? /* @__PURE__ */ jsx(
22418
+ ) : director || hasVisibleFleetPanel || state.collabSession ? /* @__PURE__ */ jsx(
21259
22419
  FleetPanel,
21260
22420
  {
21261
22421
  entries: entriesWithLeader,
@@ -21442,7 +22602,7 @@ async function runTui(opts) {
21442
22602
  stdout,
21443
22603
  events: opts.events,
21444
22604
  model: opts.model,
21445
- appName: opts.projectRoot ? path6.basename(opts.projectRoot) : void 0
22605
+ appName: opts.projectRoot ? path9.basename(opts.projectRoot) : void 0
21446
22606
  });
21447
22607
  };
21448
22608
  const stopTitle = () => {
@@ -21539,7 +22699,7 @@ async function runTui(opts) {
21539
22699
  const hqPublisher = createHqPublisherFromEnv({
21540
22700
  clientKind: "tui",
21541
22701
  projectRoot: opts.projectRoot,
21542
- projectName: path6.basename(opts.projectRoot),
22702
+ projectName: path9.basename(opts.projectRoot),
21543
22703
  appConfig: opts.appConfig
21544
22704
  });
21545
22705
  hqPublisher?.connect();
@@ -21579,7 +22739,7 @@ async function runTui(opts) {
21579
22739
  await mailbox.registerClient({
21580
22740
  clientId,
21581
22741
  sessionId: opts.getSessionId?.() ?? opts.projectRoot,
21582
- name: `TUI [${path6.basename(opts.projectRoot)}]`,
22742
+ name: `TUI [${path9.basename(opts.projectRoot)}]`,
21583
22743
  source: "tui",
21584
22744
  pid: process.pid
21585
22745
  });
@@ -21718,6 +22878,16 @@ async function runTui(opts) {
21718
22878
  saveSettings: opts.saveSettings,
21719
22879
  getPluginItems: opts.getPluginItems,
21720
22880
  onPluginToggle: opts.onPluginToggle,
22881
+ getMcpServers: opts.getMcpServers,
22882
+ onMcpToggle: opts.onMcpToggle,
22883
+ onMcpRestart: opts.onMcpRestart,
22884
+ getToolsItems: opts.getToolsItems,
22885
+ onToolToggle: opts.onToolToggle,
22886
+ getBrainData: opts.getBrainData,
22887
+ onBrainRiskLevel: opts.onBrainRiskLevel,
22888
+ getShadowData: opts.getShadowData,
22889
+ onShadowStart: opts.onShadowStart,
22890
+ onShadowStop: opts.onShadowStop,
21721
22891
  authHost: opts.authHost,
21722
22892
  predictNext: opts.predictNext,
21723
22893
  onSuggestionsParsed: opts.onSuggestionsParsed,