@wrongstack/tui 0.282.1 → 0.283.0

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,9 +1,9 @@
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';
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, detectContinueIntent, resolveContinuation, 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
5
  import * as path9 from 'path';
6
- import React5, { forwardRef, useState, useEffect, useMemo, memo, useRef, useLayoutEffect, useCallback, useReducer, isValidElement, Fragment as Fragment$1 } from 'react';
6
+ import React7, { 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
8
  import { expectDefined, toErrorMessage, resolveWstackPaths as resolveWstackPaths$1, sessionScopedPath } from '@wrongstack/core/utils';
9
9
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
@@ -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,
@@ -3344,6 +3524,10 @@ function reducer(state, action) {
3344
3524
  return { ...state, confirmQueue: [...state.confirmQueue, action.info] };
3345
3525
  case "confirmClose":
3346
3526
  return { ...state, confirmQueue: state.confirmQueue.slice(1) };
3527
+ case "shellCommandWarningOpen":
3528
+ return { ...state, shellCommandWarning: action.info };
3529
+ case "shellCommandWarningClose":
3530
+ return { ...state, shellCommandWarning: null };
3347
3531
  case "enhanceOpen":
3348
3532
  return { ...state, enhance: action.info };
3349
3533
  case "enhanceClose":
@@ -3352,6 +3536,10 @@ function reducer(state, action) {
3352
3536
  return { ...state, enhanceEnabled: action.enabled };
3353
3537
  case "enhanceBusy":
3354
3538
  return { ...state, enhanceBusy: action.on };
3539
+ case "continueConfirmOpen":
3540
+ return { ...state, continueConfirm: action.info };
3541
+ case "continueConfirmClose":
3542
+ return { ...state, continueConfirm: null };
3355
3543
  case "escConfirmOpen":
3356
3544
  return { ...state, escConfirm: { snapshot: action.snapshot } };
3357
3545
  case "escConfirmClose":
@@ -6166,22 +6354,62 @@ function BrainDecisionPrompt({
6166
6354
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press A/B/C or 1/2/3 to answer; Esc or D denies with the safe default." })
6167
6355
  ] });
6168
6356
  }
6357
+ function modeFamily(mode) {
6358
+ if (mode.tags?.includes("lite")) return "lite";
6359
+ if (mode.tags?.includes("deep")) return "deep";
6360
+ if (mode.tags?.includes("balanced") || mode.id === "default") return "balanced";
6361
+ return "custom";
6362
+ }
6169
6363
  function toModeOptions(modes, activeId) {
6170
6364
  return modes.map((m) => ({
6171
6365
  id: m.id,
6172
6366
  name: m.name,
6173
6367
  description: m.description,
6368
+ family: modeFamily(m),
6174
6369
  isActive: m.id === activeId
6175
6370
  }));
6176
6371
  }
6372
+ function familyLabel(family) {
6373
+ switch (family) {
6374
+ case "lite":
6375
+ return "lite";
6376
+ case "deep":
6377
+ return "deep";
6378
+ case "balanced":
6379
+ return "base";
6380
+ case "custom":
6381
+ return "custom";
6382
+ }
6383
+ }
6384
+ function familyColor(family) {
6385
+ switch (family) {
6386
+ case "lite":
6387
+ return "green";
6388
+ case "deep":
6389
+ return "magenta";
6390
+ case "balanced":
6391
+ return "blue";
6392
+ case "custom":
6393
+ return "yellow";
6394
+ }
6395
+ }
6177
6396
  function ModePicker({
6178
6397
  modes,
6179
6398
  selected,
6180
6399
  hint
6181
6400
  }) {
6401
+ const activeMode = modes.find((mode) => mode.isActive);
6402
+ const modeMeta = `${modes.length} mode${modes.length === 1 ? "" : "s"}`;
6182
6403
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
6183
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Mode Selection \u2501\u2501" }),
6184
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
6404
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
6405
+ "\u2501\u2501 Mode Selection \u2501\u2501 ",
6406
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: modeMeta })
6407
+ ] }),
6408
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
6409
+ "Current: ",
6410
+ activeMode ? activeMode.name : "none",
6411
+ " \xB7 \u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel"
6412
+ ] }),
6185
6413
  modes.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No modes available." }) : modes.map((opt, i) => /* @__PURE__ */ jsxs(
6186
6414
  Text,
6187
6415
  {
@@ -6189,9 +6417,17 @@ function ModePicker({
6189
6417
  ...i === selected ? { color: "cyan" } : {},
6190
6418
  children: [
6191
6419
  i === selected ? "\u203A " : " ",
6192
- /* @__PURE__ */ jsx(Text, { bold: true, children: opt.name.padEnd(20) }),
6193
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description }),
6194
- opt.isActive ? /* @__PURE__ */ jsx(Text, { color: "green", children: " [active]" }) : null
6420
+ /* @__PURE__ */ jsx(Text, { bold: true, children: opt.name.padEnd(18) }),
6421
+ /* @__PURE__ */ jsxs(Text, { color: familyColor(opt.family), children: [
6422
+ "[",
6423
+ familyLabel(opt.family),
6424
+ "]"
6425
+ ] }),
6426
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
6427
+ " ",
6428
+ opt.description
6429
+ ] }),
6430
+ opt.isActive ? /* @__PURE__ */ jsx(Text, { color: "green", children: " \u25CF active" }) : null
6195
6431
  ]
6196
6432
  },
6197
6433
  opt.id
@@ -8286,7 +8522,7 @@ function streamBoxRows(text, maxLines, contentWidth) {
8286
8522
  }
8287
8523
  return rows;
8288
8524
  }
8289
- var ToolStreamBox = React5.memo(function ToolStreamBox2({
8525
+ var ToolStreamBox = React7.memo(function ToolStreamBox2({
8290
8526
  name,
8291
8527
  text,
8292
8528
  startedAt,
@@ -8926,7 +9162,7 @@ function ConfirmPrompt({
8926
9162
  onEnableYolo,
8927
9163
  destructive
8928
9164
  }) {
8929
- React5.useEffect(() => {
9165
+ React7.useEffect(() => {
8930
9166
  writeOut("\x07");
8931
9167
  }, []);
8932
9168
  useInput((input2, _key) => {
@@ -8960,7 +9196,7 @@ function ConfirmPrompt({
8960
9196
  inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
8961
9197
  showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff, diffPath) }) : null,
8962
9198
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
8963
- /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { children: buttonLabels(suggestedPattern).map((l) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
9199
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { children: buttonLabels(suggestedPattern).map((l) => /* @__PURE__ */ jsxs(React7.Fragment, { children: [
8964
9200
  /* @__PURE__ */ jsx(Text, { bold: true, color: BUTTON_COLOR[l.decision], children: l.bracket }),
8965
9201
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: l.rest })
8966
9202
  ] }, l.decision)) }) }),
@@ -9098,6 +9334,72 @@ function DesignPicker({ kits, selected, stack }) {
9098
9334
  ] }) }, kit.id))
9099
9335
  ] });
9100
9336
  }
9337
+ function continueAutoProceedSeconds(grounded, delayMs) {
9338
+ return grounded ? Math.max(1, Math.ceil(delayMs / 1e3)) : null;
9339
+ }
9340
+ function ContinueConfirmPanel({
9341
+ label,
9342
+ instruction,
9343
+ source,
9344
+ grounded,
9345
+ delayMs,
9346
+ onDecision
9347
+ }) {
9348
+ const autoProceedSecs = continueAutoProceedSeconds(grounded, delayMs);
9349
+ const remainingRef = React7.useRef(autoProceedSecs ?? 0);
9350
+ const decideRef = React7.useRef(onDecision);
9351
+ decideRef.current = onDecision;
9352
+ React7.useEffect(() => {
9353
+ if (!grounded) return;
9354
+ const id = setInterval(() => {
9355
+ const r = remainingRef.current - 1;
9356
+ remainingRef.current = r;
9357
+ if (r <= 0) {
9358
+ clearInterval(id);
9359
+ decideRef.current("proceed");
9360
+ }
9361
+ }, 1e3);
9362
+ return () => clearInterval(id);
9363
+ }, [grounded]);
9364
+ useInput((input, key) => {
9365
+ if (key.return) {
9366
+ onDecision("proceed");
9367
+ } else if (key.escape) {
9368
+ onDecision("cancel");
9369
+ } else if (input?.toLowerCase() === "e") {
9370
+ onDecision("edit");
9371
+ }
9372
+ });
9373
+ const accent = grounded ? "cyan" : "yellow";
9374
+ const preview = instruction.length > 320 ? `${instruction.slice(0, 317)}\u2026` : instruction;
9375
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: accent, paddingX: 1, children: [
9376
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
9377
+ /* @__PURE__ */ jsx(Text, { bold: true, color: accent, children: grounded ? "\u25B6 Continue" : "\u26A0 Continue (ambiguous)" }),
9378
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ' \u2014 you typed a bare "continue"' })
9379
+ ] }),
9380
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
9381
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "resolves to: " }),
9382
+ /* @__PURE__ */ jsx(Text, { color: "white", children: label.replace(/^▶\s*Continue\s*→\s*/, "") })
9383
+ ] }),
9384
+ source === "open" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
9385
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: 'No pending todo or suggestion \u2014 "continue" has no anchor here.' }),
9386
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "If you proceed, I'll choose the next step myself from the conversation and project state. That's a guess \u2014 it can drift from what you meant." })
9387
+ ] }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: preview }) }),
9388
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
9389
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
9390
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[Enter]" }),
9391
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: autoProceedSecs != null ? ` proceed (auto ${autoProceedSecs}s) \xB7 ` : " proceed anyway \xB7 " }),
9392
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[e]" }),
9393
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
9394
+ " ",
9395
+ source === "open" ? "type what to continue" : "edit",
9396
+ " \xB7 "
9397
+ ] }),
9398
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
9399
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " cancel" })
9400
+ ] }) })
9401
+ ] });
9402
+ }
9101
9403
  function EnhancePanel({
9102
9404
  original,
9103
9405
  refined,
@@ -9107,12 +9409,12 @@ function EnhancePanel({
9107
9409
  onTick
9108
9410
  }) {
9109
9411
  const totalSecs = Math.max(1, Math.ceil(delayMs / 1e3));
9110
- const remainingRef = React5.useRef(totalSecs);
9111
- const decideRef = React5.useRef(onDecision);
9412
+ const remainingRef = React7.useRef(totalSecs);
9413
+ const decideRef = React7.useRef(onDecision);
9112
9414
  decideRef.current = onDecision;
9113
- const tickRef = React5.useRef(onTick);
9415
+ const tickRef = React7.useRef(onTick);
9114
9416
  tickRef.current = onTick;
9115
- React5.useEffect(() => {
9417
+ React7.useEffect(() => {
9116
9418
  const id = setInterval(() => {
9117
9419
  const r = remainingRef.current - 1;
9118
9420
  remainingRef.current = r;
@@ -9359,6 +9661,44 @@ function FilePicker({ query, matches, selected }) {
9359
9661
  function highlight(path10, _query) {
9360
9662
  return path10;
9361
9663
  }
9664
+ function truncateShellCommandWarningCommand(text, max = 80) {
9665
+ return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
9666
+ }
9667
+ function ShellCommandWarning({
9668
+ command,
9669
+ onDecision
9670
+ }) {
9671
+ React7.useEffect(() => {
9672
+ writeOut("\x07");
9673
+ }, []);
9674
+ useInput((input, _key) => {
9675
+ if (!input || input === "\r" || input === "\n") return;
9676
+ const ch = input.toLowerCase();
9677
+ if (ch === "y") onDecision("yes");
9678
+ if (ch === "n") onDecision("no");
9679
+ if (ch === "d") onDecision("dont-show-again");
9680
+ });
9681
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
9682
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
9683
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 SHELL COMMAND" }),
9684
+ /* @__PURE__ */ jsx(Text, { children: " " }),
9685
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: "white", children: [
9686
+ "!",
9687
+ truncateShellCommandWarningCommand(command)
9688
+ ] })
9689
+ ] }),
9690
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "This will execute in your shell via /dev. Only run commands you trust." }),
9691
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
9692
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
9693
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[y]" }),
9694
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "es " }),
9695
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[n]" }),
9696
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "o " }),
9697
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[d]" }),
9698
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "ont show again" })
9699
+ ] }) })
9700
+ ] });
9701
+ }
9362
9702
  var METER_WIDTH = 10;
9363
9703
  function isLeaderEntry2(entry) {
9364
9704
  return entry.id === "leader" || entry.name === "LEADER";
@@ -10738,7 +11078,7 @@ function brainRiskColor(risk) {
10738
11078
  return "red";
10739
11079
  }
10740
11080
  }
10741
- var Entry = React5.memo(function Entry2({
11081
+ var Entry = React7.memo(function Entry2({
10742
11082
  entry,
10743
11083
  termWidth,
10744
11084
  setSuggestions,
@@ -12599,7 +12939,135 @@ function PlanRow({ item }) {
12599
12939
  ] }) : null
12600
12940
  ] });
12601
12941
  }
12602
- var CHROME_ROWS2 = 17;
12942
+ var CHROME_ROWS2 = 14;
12943
+ var RISK_DESCS = {
12944
+ off: "Human decides everything",
12945
+ low: "Auto-decide low risk only",
12946
+ medium: "Auto-decide up to medium risk",
12947
+ high: "Auto-decide up to high risk",
12948
+ all: "Auto-decide everything"
12949
+ };
12950
+ var RISK_COLORS2 = {
12951
+ off: "gray",
12952
+ low: "green",
12953
+ medium: "yellow",
12954
+ high: "red",
12955
+ all: "magenta"
12956
+ };
12957
+ function BrainPanel({
12958
+ riskLevel,
12959
+ log,
12960
+ selected,
12961
+ hint
12962
+ }) {
12963
+ const { stdout } = useStdout();
12964
+ const termRows = stdout?.rows ?? 24;
12965
+ const maxVisible = Math.max(4, termRows - CHROME_ROWS2);
12966
+ const total = log.length;
12967
+ const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12968
+ const windowEnd = Math.min(windowStart + maxVisible, total);
12969
+ const above = windowStart;
12970
+ const below = total - windowEnd;
12971
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
12972
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "\u2501\u2501 Brain \u2501\u2501" }),
12973
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate log \xB7 \u2190/\u2192 change risk \xB7 Esc close" }),
12974
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
12975
+ /* @__PURE__ */ jsxs(Box, { children: [
12976
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Risk ceiling: " }),
12977
+ /* @__PURE__ */ jsx(Text, { color: RISK_COLORS2[riskLevel], bold: true, children: riskLevel.toUpperCase() }),
12978
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${RISK_DESCS[riskLevel]}` })
12979
+ ] }),
12980
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
12981
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "Recent decisions" }),
12982
+ total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " No decisions recorded yet this session." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12983
+ above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12984
+ log.slice(windowStart, windowEnd).map((entry, i) => {
12985
+ const index = windowStart + i;
12986
+ const focused = index === selected;
12987
+ return /* @__PURE__ */ jsxs(
12988
+ Text,
12989
+ {
12990
+ inverse: focused,
12991
+ ...focused ? { color: "magenta" } : {},
12992
+ wrap: "truncate-end",
12993
+ children: [
12994
+ focused ? "\u203A " : " ",
12995
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.age.padEnd(8) }),
12996
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: entry.kind.padEnd(12) }),
12997
+ /* @__PURE__ */ jsx(Text, { children: entry.question.length > 60 ? `${entry.question.slice(0, 57)}\u2026` : entry.question }),
12998
+ entry.outcome ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2192 ${entry.outcome.length > 20 ? `${entry.outcome.slice(0, 17)}\u2026` : entry.outcome}` }) : null
12999
+ ]
13000
+ },
13001
+ `${entry.kind}-${i}`
13002
+ );
13003
+ }),
13004
+ below > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${below} more` }) : null
13005
+ ] })
13006
+ ] })
13007
+ ] }),
13008
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13009
+ ] });
13010
+ }
13011
+ var CHROME_ROWS3 = 17;
13012
+ function statusBadge(status, enabled) {
13013
+ if (!enabled) return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB disabled" });
13014
+ switch (status) {
13015
+ case "connected":
13016
+ return /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF connected" });
13017
+ case "connecting":
13018
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25D0 connecting" });
13019
+ case "reconnecting":
13020
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25D1 reconnecting" });
13021
+ case "disconnected":
13022
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB disconnected" });
13023
+ case "failed":
13024
+ return /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 failed" });
13025
+ default:
13026
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: status });
13027
+ }
13028
+ }
13029
+ function McpPicker({
13030
+ items,
13031
+ selected,
13032
+ busy = false,
13033
+ hint
13034
+ }) {
13035
+ const { stdout } = useStdout();
13036
+ const rows = stdout?.rows ?? 24;
13037
+ const maxVisible = Math.max(4, rows - CHROME_ROWS3);
13038
+ const total = items.length;
13039
+ const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
13040
+ const windowEnd = Math.min(windowStart + maxVisible, total);
13041
+ const above = windowStart;
13042
+ const below = total - windowEnd;
13043
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
13044
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "MCP Servers" }),
13045
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 select \xB7 Enter/\u2190/\u2192 toggle enable \xB7 r restart \xB7 Esc close" }),
13046
+ /* @__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: [
13047
+ above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
13048
+ items.slice(windowStart, windowEnd).map((item, i) => {
13049
+ const index = windowStart + i;
13050
+ const focused = index === selected;
13051
+ const marker = focused ? "\u203A" : " ";
13052
+ return /* @__PURE__ */ jsxs(Text, { color: focused ? "cyan" : void 0, wrap: "truncate-end", children: [
13053
+ marker,
13054
+ " ",
13055
+ statusBadge(item.status, item.enabled),
13056
+ " ",
13057
+ /* @__PURE__ */ jsx(Text, { bold: true, children: item.name.padEnd(18) }),
13058
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13059
+ item.transport.padEnd(14),
13060
+ item.toolCount > 0 ? `${item.toolCount} tools` : "",
13061
+ item.lazy ? " lazy" : ""
13062
+ ] })
13063
+ ] }, item.name);
13064
+ }),
13065
+ below > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${below} more` }) : null
13066
+ ] }) }),
13067
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13068
+ ] });
13069
+ }
13070
+ var CHROME_ROWS4 = 17;
12603
13071
  function PluginPicker({
12604
13072
  items,
12605
13073
  selected,
@@ -12608,7 +13076,7 @@ function PluginPicker({
12608
13076
  }) {
12609
13077
  const { stdout } = useStdout();
12610
13078
  const rows = stdout?.rows ?? 24;
12611
- const maxVisible = Math.max(4, rows - CHROME_ROWS2);
13079
+ const maxVisible = Math.max(4, rows - CHROME_ROWS4);
12612
13080
  const total = items.length;
12613
13081
  const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12614
13082
  const windowEnd = Math.min(windowStart + maxVisible, total);
@@ -12654,43 +13122,369 @@ function PluginPicker({
12654
13122
  hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
12655
13123
  ] });
12656
13124
  }
12657
- function ProcessListMonitor() {
12658
- const [, setTick] = useState(0);
12659
- const [selectedIndex, setSelectedIndex] = useState(0);
12660
- useEffect(() => {
12661
- const id = setInterval(() => setTick((t) => t + 1), 1e3);
12662
- return () => clearInterval(id);
12663
- }, []);
12664
- const registry = getProcessRegistry();
12665
- const all = registry.list();
12666
- const stats = registry.stats();
12667
- const safeIndex = Math.min(selectedIndex, Math.max(0, all.length - 1));
12668
- if (safeIndex !== selectedIndex) {
12669
- setSelectedIndex(safeIndex);
13125
+ var CHROME_ROWS5 = 15;
13126
+ function toolActionLabel(enabled) {
13127
+ return enabled ? "Enter makes this tool passive" : "Enter re-enables this tool";
13128
+ }
13129
+ function statusBadge2(enabled) {
13130
+ return enabled ? /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF active" }) : /* @__PURE__ */ jsx(Text, { color: "red", children: "\u25CB passive" });
13131
+ }
13132
+ function rwBadge(mutating) {
13133
+ return mutating ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "mut" }) : /* @__PURE__ */ jsx(Text, { color: "cyan", children: "ro" });
13134
+ }
13135
+ function displayCategory(cat) {
13136
+ if (!cat || cat.trim() === "") return "Other";
13137
+ return cat.charAt(0).toUpperCase() + cat.slice(1);
13138
+ }
13139
+ var DEFAULT_COLUMN_WIDTHS = {
13140
+ name: 28,
13141
+ category: 16,
13142
+ owner: 31,
13143
+ permission: 8
13144
+ };
13145
+ var MIN_COLUMN_WIDTHS = {
13146
+ name: 22,
13147
+ category: 13,
13148
+ owner: 20,
13149
+ permission: 7
13150
+ };
13151
+ function shrinkColumn(widths, column, overflow) {
13152
+ const room = widths[column] - MIN_COLUMN_WIDTHS[column];
13153
+ const shrinkBy = Math.min(room, overflow);
13154
+ widths[column] -= shrinkBy;
13155
+ return overflow - shrinkBy;
13156
+ }
13157
+ function columnWidthsFor(columns) {
13158
+ const widths = { ...DEFAULT_COLUMN_WIDTHS };
13159
+ const rowChromeWidth = 30;
13160
+ const availableVariableWidth = Math.max(
13161
+ Object.values(MIN_COLUMN_WIDTHS).reduce((sum, width) => sum + width, 0),
13162
+ columns - rowChromeWidth
13163
+ );
13164
+ const desiredVariableWidth = Object.values(widths).reduce((sum, width) => sum + width, 0);
13165
+ let overflow = desiredVariableWidth - availableVariableWidth;
13166
+ for (const column of ["owner", "category", "name", "permission"]) {
13167
+ if (overflow <= 0) break;
13168
+ overflow = shrinkColumn(widths, column, overflow);
12670
13169
  }
12671
- const selected = all[safeIndex];
12672
- const now = Date.now();
12673
- const running = all.filter((p) => !p.killed).length;
12674
- const b = stats.breaker;
12675
- const breakerState = b.state === "closed" ? "\u{1F7E2} closed" : b.state === "half-open" ? "\u{1F7E1} half-open" : "\u{1F534} open";
12676
- const pageSize = Math.max(1, Math.floor(all.length / 2));
12677
- useInput((input, key) => {
12678
- if (key.upArrow) {
12679
- setSelectedIndex((prev) => Math.max(0, prev - 1));
12680
- } else if (key.downArrow) {
12681
- setSelectedIndex((prev) => Math.min(all.length - 1, prev + 1));
12682
- } else if (key.pageUp) {
12683
- setSelectedIndex((prev) => Math.max(0, prev - pageSize));
12684
- } else if (key.pageDown) {
12685
- setSelectedIndex((prev) => Math.min(all.length - 1, prev + pageSize));
12686
- } else if (key.home || key.ctrl && input === "a" || input === "g") {
12687
- setSelectedIndex(0);
12688
- } else if (key.end || key.ctrl && input === "e" || input === "G") {
12689
- setSelectedIndex(Math.max(0, all.length - 1));
12690
- } else if (key.return && selected) {
12691
- getProcessRegistry().kill(selected.pid);
12692
- } else if (key.delete && selected) {
12693
- getProcessRegistry().kill(selected.pid, { force: true });
13170
+ return widths;
13171
+ }
13172
+ function fitCell(value, width) {
13173
+ if (width <= 0) return "";
13174
+ if (value.length > width) {
13175
+ return `${value.slice(0, Math.max(0, width - 1))}\u2026`;
13176
+ }
13177
+ return value.padEnd(width);
13178
+ }
13179
+ function buildRows(items) {
13180
+ return items.map((item, index) => ({ item, index }));
13181
+ }
13182
+ function windowRows(rows, focus, max) {
13183
+ if (rows.length <= max) {
13184
+ return { rows, start: 0, end: rows.length };
13185
+ }
13186
+ let start = focus - Math.floor(max / 2);
13187
+ if (start < 0) start = 0;
13188
+ let end = start + max;
13189
+ if (end > rows.length) {
13190
+ end = rows.length;
13191
+ start = end - max;
13192
+ }
13193
+ return { rows: rows.slice(start, end), start, end };
13194
+ }
13195
+ function ToolsPicker({
13196
+ items,
13197
+ selected,
13198
+ busy = false,
13199
+ hint,
13200
+ filter
13201
+ }) {
13202
+ const { stdout } = useStdout();
13203
+ const termRows = stdout?.rows ?? 24;
13204
+ const termColumns = stdout?.columns ?? 100;
13205
+ const columnWidths = columnWidthsFor(termColumns);
13206
+ const maxVisible = Math.max(6, termRows - CHROME_ROWS5);
13207
+ const total = items.length;
13208
+ let visibleItems;
13209
+ let visibleSelected;
13210
+ const filterActive = Boolean(filter && filter.length > 0);
13211
+ if (filterActive) {
13212
+ const q = filter.toLowerCase();
13213
+ visibleItems = items.filter(
13214
+ (it) => it.name.toLowerCase().includes(q) || it.owner.toLowerCase().includes(q) || it.description.toLowerCase().includes(q)
13215
+ );
13216
+ const rawSelected = items[selected];
13217
+ visibleSelected = rawSelected ? visibleItems.indexOf(rawSelected) : 0;
13218
+ if (visibleSelected < 0) visibleSelected = 0;
13219
+ } else {
13220
+ visibleItems = items;
13221
+ visibleSelected = selected;
13222
+ }
13223
+ const rows = buildRows(visibleItems);
13224
+ const enabledCount = items.filter((item) => item.enabled).length;
13225
+ const passiveCount = total - enabledCount;
13226
+ const selectedItem = visibleItems[visibleSelected];
13227
+ let displayRows;
13228
+ let hiddenAbove = 0;
13229
+ let hiddenBelow = 0;
13230
+ if (filterActive) {
13231
+ displayRows = rows;
13232
+ } else {
13233
+ const win = windowRows(rows, visibleSelected, maxVisible);
13234
+ displayRows = win.rows;
13235
+ hiddenAbove = win.start;
13236
+ hiddenBelow = rows.length - win.end;
13237
+ }
13238
+ const hasFilter = filterActive;
13239
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [
13240
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "\u2501\u2501 Tools \u2501\u2501" }),
13241
+ /* @__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 ${enabledCount} active / ${passiveCount} passive` }),
13242
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Passive tools stay listed for this session so you can select them and re-enable them later." }),
13243
+ /* @__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: [
13244
+ 'No tools match "',
13245
+ filter,
13246
+ '".'
13247
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
13248
+ hiddenAbove > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${hiddenAbove} more` }) : null,
13249
+ displayRows.map(({ item, index }) => {
13250
+ const focused = index === visibleSelected;
13251
+ return /* @__PURE__ */ jsxs(
13252
+ Text,
13253
+ {
13254
+ inverse: focused,
13255
+ ...focused ? { color: "green" } : {},
13256
+ wrap: "truncate-end",
13257
+ children: [
13258
+ focused ? "\u203A " : " ",
13259
+ statusBadge2(item.enabled),
13260
+ " ",
13261
+ /* @__PURE__ */ jsx(Text, { bold: true, children: fitCell(item.name, columnWidths.name) }),
13262
+ " ",
13263
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13264
+ fitCell(displayCategory(item.category), columnWidths.category),
13265
+ " ",
13266
+ fitCell(`[${item.owner}]`, columnWidths.owner),
13267
+ " ",
13268
+ rwBadge(item.mutating),
13269
+ " ",
13270
+ " ",
13271
+ fitCell(item.permission, columnWidths.permission),
13272
+ " ",
13273
+ /* @__PURE__ */ jsxs(Text, { color: item.descMode === "simple" ? "yellow" : void 0, children: [
13274
+ "desc:",
13275
+ item.descMode
13276
+ ] })
13277
+ ] })
13278
+ ]
13279
+ },
13280
+ item.name
13281
+ );
13282
+ }),
13283
+ hiddenBelow > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${hiddenBelow} more` }) : null
13284
+ ] }) }),
13285
+ selectedItem ? /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
13286
+ /* @__PURE__ */ jsxs(Text, { children: [
13287
+ /* @__PURE__ */ jsx(Text, { color: selectedItem.enabled ? "green" : "red", children: toolActionLabel(selectedItem.enabled) }),
13288
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${selectedItem.mutating ? "Mutating" : "Read-only"} \xB7 permission: ${selectedItem.permission} \xB7 descriptions: ${selectedItem.descMode}` })
13289
+ ] }),
13290
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate-end", children: selectedItem.description || "No description provided." })
13291
+ ] }) : null,
13292
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13293
+ ] });
13294
+ }
13295
+ var CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
13296
+ var CHROME_ROWS6 = 7;
13297
+ function normalizeQuery(raw) {
13298
+ return raw.trim().toLowerCase();
13299
+ }
13300
+ function buildRows2(entries) {
13301
+ const rows = [];
13302
+ let lastCat = "";
13303
+ for (let i = 0; i < entries.length; i++) {
13304
+ const e = entries[i];
13305
+ const cat = e.category || "App";
13306
+ if (cat !== lastCat) {
13307
+ lastCat = cat;
13308
+ rows.push({ type: "header", category: cat });
13309
+ }
13310
+ rows.push({ type: "item", entry: e, index: i });
13311
+ }
13312
+ return rows;
13313
+ }
13314
+ function windowRows2(rows, focus, max) {
13315
+ if (rows.length <= max) {
13316
+ return { rows, start: 0, end: rows.length, contextHeader: null };
13317
+ }
13318
+ let start = focus - Math.floor(max / 2);
13319
+ if (start < 0) start = 0;
13320
+ let end = start + max;
13321
+ if (end > rows.length) {
13322
+ end = rows.length;
13323
+ start = end - max;
13324
+ }
13325
+ let contextHeader = null;
13326
+ if (start > 0) {
13327
+ const first = rows[start];
13328
+ if (first && first.type === "item") {
13329
+ for (let i = start - 1; i >= 0; i--) {
13330
+ const r = rows[i];
13331
+ if (r && r.type === "header") {
13332
+ contextHeader = r.category;
13333
+ break;
13334
+ }
13335
+ }
13336
+ }
13337
+ }
13338
+ return { rows: rows.slice(start, end), start, end, contextHeader };
13339
+ }
13340
+ function HelpPanel({
13341
+ entries,
13342
+ filter,
13343
+ selected,
13344
+ hint
13345
+ }) {
13346
+ const { stdout } = useStdout();
13347
+ const termRows = stdout?.rows ?? 24;
13348
+ const maxVisible = Math.max(6, termRows - CHROME_ROWS6);
13349
+ const query = normalizeQuery(filter);
13350
+ const filtered = query ? entries.filter(
13351
+ (e) => e.name.toLowerCase().includes(query) || e.description.toLowerCase().includes(query) || e.category.toLowerCase().includes(query) || (e.aliases ?? []).some((a) => a.toLowerCase().includes(query))
13352
+ ) : entries;
13353
+ filtered.sort((a, b) => {
13354
+ const ai = CATEGORY_ORDER.indexOf(a.category);
13355
+ const bi = CATEGORY_ORDER.indexOf(b.category);
13356
+ if (ai !== bi) return ai - bi;
13357
+ return a.name.localeCompare(b.name);
13358
+ });
13359
+ const rows = buildRows2(filtered);
13360
+ const focus = Math.min(selected, Math.max(0, filtered.length - 1));
13361
+ const selectedRowIdx = rows.findIndex((r) => r.type === "item" && r.index === focus);
13362
+ const win = windowRows2(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, maxVisible);
13363
+ const { rows: visible, start, end, contextHeader } = win;
13364
+ const hiddenAbove = start;
13365
+ const hiddenBelow = rows.length - end;
13366
+ const hasFilter = query.length > 0;
13367
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
13368
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u2501\u2501 Help / Command Palette \u2501\u2501" }),
13369
+ /* @__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" }),
13370
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13371
+ 'No commands match "',
13372
+ filter,
13373
+ '".'
13374
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
13375
+ hiddenAbove > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${hiddenAbove} more` }) : null,
13376
+ contextHeader && !hasFilter ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13377
+ " ",
13378
+ contextHeader
13379
+ ] }) : null,
13380
+ visible.map((row) => {
13381
+ if (row.type === "header") {
13382
+ return /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13383
+ " ",
13384
+ row.category
13385
+ ] }, `cat-${row.category}`);
13386
+ }
13387
+ const { entry, index } = row;
13388
+ const isSelected = index === focus;
13389
+ return /* @__PURE__ */ jsxs(
13390
+ Text,
13391
+ {
13392
+ inverse: isSelected,
13393
+ ...isSelected ? { color: "cyan" } : {},
13394
+ wrap: "truncate-end",
13395
+ children: [
13396
+ isSelected ? "\u203A " : " ",
13397
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
13398
+ "/",
13399
+ entry.name
13400
+ ] }),
13401
+ entry.aliases && entry.aliases.length > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13402
+ " (",
13403
+ entry.aliases.map((a) => `/${a}`).join(", "),
13404
+ ")"
13405
+ ] }) : null,
13406
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13407
+ " \u2014 ",
13408
+ entry.description
13409
+ ] }),
13410
+ entry.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13411
+ " ",
13412
+ entry.argsHint
13413
+ ] }) : null
13414
+ ]
13415
+ },
13416
+ entry.name
13417
+ );
13418
+ }),
13419
+ hiddenBelow > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${hiddenBelow} more` }) : null
13420
+ ] }) }),
13421
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13422
+ ] });
13423
+ }
13424
+ function ShadowPanel({
13425
+ shadow,
13426
+ hint
13427
+ }) {
13428
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
13429
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "\u2501\u2501 Shadow Agent \u2501\u2501" }),
13430
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "s start \xB7 t stop \xB7 i cycle interval \xB7 m cycle model \xB7 Esc close" }),
13431
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
13432
+ /* @__PURE__ */ jsxs(Box, { children: [
13433
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Status: " }),
13434
+ shadow.running ? /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF running" }) : /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u25CB stopped" }),
13435
+ shadow.activeId ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${shadow.activeId.slice(0, 12)}\u2026` }) : null
13436
+ ] }),
13437
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
13438
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Model: " }),
13439
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: shadow.model })
13440
+ ] }),
13441
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
13442
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Interval: " }),
13443
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: shadow.intervalMs >= 1e3 ? `${shadow.intervalMs / 1e3}s` : `${shadow.intervalMs}ms` }),
13444
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (min 5s)" })
13445
+ ] }),
13446
+ /* @__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" }) })
13447
+ ] }),
13448
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13449
+ ] });
13450
+ }
13451
+ function ProcessListMonitor() {
13452
+ const [, setTick] = useState(0);
13453
+ const [selectedIndex, setSelectedIndex] = useState(0);
13454
+ useEffect(() => {
13455
+ const id = setInterval(() => setTick((t) => t + 1), 1e3);
13456
+ return () => clearInterval(id);
13457
+ }, []);
13458
+ const registry = getProcessRegistry();
13459
+ const all = registry.list();
13460
+ const stats = registry.stats();
13461
+ const safeIndex = Math.min(selectedIndex, Math.max(0, all.length - 1));
13462
+ if (safeIndex !== selectedIndex) {
13463
+ setSelectedIndex(safeIndex);
13464
+ }
13465
+ const selected = all[safeIndex];
13466
+ const now = Date.now();
13467
+ const running = all.filter((p) => !p.killed).length;
13468
+ const b = stats.breaker;
13469
+ const breakerState = b.state === "closed" ? "\u{1F7E2} closed" : b.state === "half-open" ? "\u{1F7E1} half-open" : "\u{1F534} open";
13470
+ const pageSize = Math.max(1, Math.floor(all.length / 2));
13471
+ useInput((input, key) => {
13472
+ if (key.upArrow) {
13473
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
13474
+ } else if (key.downArrow) {
13475
+ setSelectedIndex((prev) => Math.min(all.length - 1, prev + 1));
13476
+ } else if (key.pageUp) {
13477
+ setSelectedIndex((prev) => Math.max(0, prev - pageSize));
13478
+ } else if (key.pageDown) {
13479
+ setSelectedIndex((prev) => Math.min(all.length - 1, prev + pageSize));
13480
+ } else if (key.home || key.ctrl && input === "a" || input === "g") {
13481
+ setSelectedIndex(0);
13482
+ } else if (key.end || key.ctrl && input === "e" || input === "G") {
13483
+ setSelectedIndex(Math.max(0, all.length - 1));
13484
+ } else if (key.return && selected) {
13485
+ getProcessRegistry().kill(selected.pid);
13486
+ } else if (key.delete && selected) {
13487
+ getProcessRegistry().kill(selected.pid, { force: true });
12694
13488
  } else if (input === "a" && !key.ctrl) {
12695
13489
  getProcessRegistry().killAll();
12696
13490
  } else if (input === "A") {
@@ -13325,6 +14119,7 @@ function SessionsPanel({
13325
14119
  var MAX_VISIBLE_ITEMS = 8;
13326
14120
  function SlashMenu({ query, matches, selected }) {
13327
14121
  const placeholder = query ? `/${query}` : "/";
14122
+ const resultMeta = matches.length > 0 ? `${selected + 1}/${matches.length}` : "no matches";
13328
14123
  const rows = [];
13329
14124
  let lastCategory = "";
13330
14125
  for (let i = 0; i < matches.length; i++) {
@@ -13336,15 +14131,21 @@ function SlashMenu({ query, matches, selected }) {
13336
14131
  rows.push({ type: "item", match: m, index: i });
13337
14132
  }
13338
14133
  const selectedRowIdx = rows.findIndex((r) => r.type === "item" && r.index === selected);
13339
- const visible = windowRows(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, MAX_VISIBLE_ITEMS);
14134
+ const visible = windowRows3(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, MAX_VISIBLE_ITEMS);
13340
14135
  const hiddenAbove = visible.start;
13341
14136
  const hiddenBelow = rows.length - visible.end;
13342
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
13343
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13344
- placeholder,
14137
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
14138
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
14139
+ "\u2501\u2501 Command palette \u2501\u2501 ",
14140
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder }),
13345
14141
  " ",
13346
- matches.length > 0 ? `(${selected + 1}/${matches.length})` : ""
14142
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
14143
+ "(",
14144
+ resultMeta,
14145
+ ")"
14146
+ ] })
13347
14147
  ] }),
14148
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Type to filter commands by name, alias, or description." }),
13348
14149
  hiddenAbove > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13349
14150
  " \u2191 ",
13350
14151
  hiddenAbove,
@@ -13388,7 +14189,7 @@ function SlashMenu({ query, matches, selected }) {
13388
14189
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500 \u2191\u2193 nav \xB7 Enter run \xB7 Tab fill \xB7 Esc close" })
13389
14190
  ] });
13390
14191
  }
13391
- function windowRows(rows, focus, max) {
14192
+ function windowRows3(rows, focus, max) {
13392
14193
  if (rows.length <= max) {
13393
14194
  return { rows, start: 0, end: rows.length, contextHeader: null };
13394
14195
  }
@@ -13782,6 +14583,79 @@ function runGit(cwd, args) {
13782
14583
  });
13783
14584
  }
13784
14585
 
14586
+ // src/rehydrate-history.ts
14587
+ function contentBlocksText(content) {
14588
+ if (typeof content === "string") return content;
14589
+ if (!Array.isArray(content)) return "";
14590
+ return content.filter(
14591
+ (block) => !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string"
14592
+ ).map((block) => block.text).join("");
14593
+ }
14594
+ function rehydrateHistory(messages, startId, toolCalls) {
14595
+ const entries = [];
14596
+ const toolCallsById = /* @__PURE__ */ new Map();
14597
+ if (toolCalls) {
14598
+ for (const tc of toolCalls) {
14599
+ if (!toolCallsById.has(tc.id)) toolCallsById.set(tc.id, tc);
14600
+ }
14601
+ }
14602
+ const consumed = /* @__PURE__ */ new Set();
14603
+ const fallback = [];
14604
+ let nextId = startId;
14605
+ const textOf = (msg) => contentBlocksText(msg.content);
14606
+ const toolEntryFor = (tc) => ({
14607
+ id: nextId++,
14608
+ kind: "tool",
14609
+ name: tc.name,
14610
+ durationMs: tc.durationMs,
14611
+ ok: tc.ok,
14612
+ outputBytes: tc.outputBytes,
14613
+ outputTokens: tc.outputTokens,
14614
+ outputLines: tc.outputLines
14615
+ });
14616
+ const pushAssistantText = (parts) => {
14617
+ const text = parts.join("").trim();
14618
+ if (text) entries.push({ id: nextId++, kind: "assistant", text });
14619
+ parts.length = 0;
14620
+ };
14621
+ for (const msg of messages) {
14622
+ if (msg.role === "system") continue;
14623
+ if (msg.role === "user") {
14624
+ const text = textOf(msg).trim();
14625
+ if (text) entries.push({ id: nextId++, kind: "user", text });
14626
+ continue;
14627
+ }
14628
+ if (msg.role === "assistant") {
14629
+ if (!Array.isArray(msg.content)) {
14630
+ const text = textOf(msg).trim();
14631
+ if (text) entries.push({ id: nextId++, kind: "assistant", text });
14632
+ continue;
14633
+ }
14634
+ const textParts = [];
14635
+ for (const block of msg.content) {
14636
+ if (block.type === "text") {
14637
+ textParts.push(block.text);
14638
+ continue;
14639
+ }
14640
+ if (block.type !== "tool_use") continue;
14641
+ pushAssistantText(textParts);
14642
+ const tc = toolCallsById.get(block.id);
14643
+ if (!tc) continue;
14644
+ entries.push(toolEntryFor(tc));
14645
+ consumed.add(block.id);
14646
+ }
14647
+ pushAssistantText(textParts);
14648
+ }
14649
+ }
14650
+ if (toolCalls) {
14651
+ for (const tc of toolCalls) {
14652
+ if (!consumed.has(tc.id)) fallback.push(toolEntryFor(tc));
14653
+ }
14654
+ }
14655
+ entries.push(...fallback);
14656
+ return entries;
14657
+ }
14658
+
13785
14659
  // src/app-initial-state.ts
13786
14660
  function buildRestoredEntries(messages, restoredToolCalls) {
13787
14661
  if (!messages || messages.length === 0) return [];
@@ -13921,6 +14795,11 @@ function createInitialState(options) {
13921
14795
  hint: void 0
13922
14796
  },
13923
14797
  pluginPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
14798
+ mcpPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
14799
+ toolsPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0, filter: void 0 },
14800
+ brainPanel: { open: false, riskLevel: "medium", log: [], selected: 0, hint: void 0 },
14801
+ helpPanel: { open: false, entries: [], selected: 0, filter: "", hint: void 0 },
14802
+ shadowPanel: { open: false, shadow: { activeId: null, running: false, model: "", intervalMs: 3e4 }, hint: void 0 },
13924
14803
  authPanel: AUTH_PANEL_INITIAL,
13925
14804
  projectPicker: {
13926
14805
  open: false,
@@ -13932,9 +14811,11 @@ function createInitialState(options) {
13932
14811
  },
13933
14812
  fKeyPicker: { open: false, selected: 0 },
13934
14813
  confirmQueue: [],
14814
+ shellCommandWarning: null,
13935
14815
  enhance: null,
13936
14816
  enhanceEnabled,
13937
14817
  enhanceBusy: false,
14818
+ continueConfirm: null,
13938
14819
  escConfirm: null,
13939
14820
  sendModePicker: null,
13940
14821
  contextChipVersion: 0,
@@ -15029,6 +15910,7 @@ function usePasteHandling({
15029
15910
  };
15030
15911
  }
15031
15912
  var ENTER_DOUBLE_TAP_MS = 50;
15913
+ var formatTokens = (tokens) => tokens.toLocaleString("en-US");
15032
15914
  function debouncedEnter(host) {
15033
15915
  const now = Date.now();
15034
15916
  if (now - host.lastEnterAtRef.current < ENTER_DOUBLE_TAP_MS) return true;
@@ -15187,12 +16069,16 @@ function usePickerKeys(host) {
15187
16069
  dispatch({ type: "modelPickerHint", text: err });
15188
16070
  return;
15189
16071
  }
16072
+ const previousMaxContext = host.activeMaxContext;
16073
+ const nextMaxContext = host.getAgentCtxMaxContext();
15190
16074
  host.setLiveProvider?.(providerId);
15191
16075
  host.setLiveModel?.(modelId);
15192
- host.setActiveMaxContext?.(host.agentCtxMaxContext);
16076
+ host.setActiveMaxContext?.(nextMaxContext);
16077
+ const contextWarning = previousMaxContext && nextMaxContext > 0 && nextMaxContext < previousMaxContext ? `
16078
+ \u26A0 smaller context window: ${formatTokens(previousMaxContext)} \u2192 ${formatTokens(nextMaxContext)} tokens${host.currentContextTokens > 0 ? `; current request \u2248 ${formatTokens(host.currentContextTokens)} tokens (${Math.round(host.currentContextTokens / nextMaxContext * 100)}% of new window)` : ""}` : "";
15193
16079
  dispatch({
15194
16080
  type: "addEntry",
15195
- entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
16081
+ entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.${contextWarning}` }
15196
16082
  });
15197
16083
  dispatch({ type: "modelPickerClose" });
15198
16084
  };
@@ -15474,6 +16360,145 @@ function usePickerKeys(host) {
15474
16360
  }
15475
16361
  return true;
15476
16362
  }
16363
+ if (state.mcpPicker.open) {
16364
+ if (key.escape) {
16365
+ dispatch({ type: "mcpPickerClose" });
16366
+ return true;
16367
+ }
16368
+ if (key.mouse?.kind === "wheel") {
16369
+ dispatch({ type: "mcpPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16370
+ return true;
16371
+ }
16372
+ if (key.upArrow) {
16373
+ dispatch({ type: "mcpPickerMove", delta: -1 });
16374
+ return true;
16375
+ }
16376
+ if (key.downArrow) {
16377
+ dispatch({ type: "mcpPickerMove", delta: 1 });
16378
+ return true;
16379
+ }
16380
+ if (input === "r" || input === "R") {
16381
+ void host.onMcpPickerRestart?.();
16382
+ return true;
16383
+ }
16384
+ if (key.leftArrow || key.rightArrow || isEnter) {
16385
+ if (debouncedEnter(host)) return true;
16386
+ void host.onMcpPickerToggle?.();
16387
+ return true;
16388
+ }
16389
+ return true;
16390
+ }
16391
+ if (state.toolsPicker.open) {
16392
+ if (key.escape) {
16393
+ if (state.toolsPicker.filter) {
16394
+ dispatch({ type: "toolsPickerFilter", filter: "" });
16395
+ } else {
16396
+ dispatch({ type: "toolsPickerClose" });
16397
+ }
16398
+ return true;
16399
+ }
16400
+ if (key.mouse?.kind === "wheel") {
16401
+ dispatch({ type: "toolsPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16402
+ return true;
16403
+ }
16404
+ if (key.upArrow) {
16405
+ dispatch({ type: "toolsPickerMove", delta: -1 });
16406
+ return true;
16407
+ }
16408
+ if (key.downArrow) {
16409
+ dispatch({ type: "toolsPickerMove", delta: 1 });
16410
+ return true;
16411
+ }
16412
+ if (key.leftArrow || key.rightArrow || isEnter) {
16413
+ if (debouncedEnter(host)) return true;
16414
+ void host.onToolsPickerToggle?.();
16415
+ return true;
16416
+ }
16417
+ if (input && input.length === 1 && input.charCodeAt(0) >= 32 && input.charCodeAt(0) < 127) {
16418
+ dispatch({ type: "toolsPickerFilter", filter: (state.toolsPicker.filter ?? "") + input });
16419
+ return true;
16420
+ }
16421
+ if (key.backspace) {
16422
+ const cur = state.toolsPicker.filter ?? "";
16423
+ dispatch({ type: "toolsPickerFilter", filter: cur.length > 0 ? cur.slice(0, -1) : "" });
16424
+ return true;
16425
+ }
16426
+ return true;
16427
+ }
16428
+ if (state.helpPanel.open) {
16429
+ if (key.escape) {
16430
+ dispatch({ type: "helpClose" });
16431
+ return true;
16432
+ }
16433
+ if (key.mouse?.kind === "wheel") {
16434
+ dispatch({ type: "helpMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16435
+ return true;
16436
+ }
16437
+ if (key.upArrow) {
16438
+ dispatch({ type: "helpMove", delta: -1 });
16439
+ return true;
16440
+ }
16441
+ if (key.downArrow) {
16442
+ dispatch({ type: "helpMove", delta: 1 });
16443
+ return true;
16444
+ }
16445
+ if (key.backspace && state.helpPanel.filter) {
16446
+ dispatch({ type: "helpFilter", filter: state.helpPanel.filter.slice(0, -1) });
16447
+ return true;
16448
+ }
16449
+ if (input && input.length === 1 && input.charCodeAt(0) >= 32 && input.charCodeAt(0) < 127) {
16450
+ dispatch({ type: "helpFilter", filter: state.helpPanel.filter + input });
16451
+ return true;
16452
+ }
16453
+ if (isEnter) {
16454
+ if (debouncedEnter(host)) return true;
16455
+ host.onHelpPanelEnter?.();
16456
+ return true;
16457
+ }
16458
+ return true;
16459
+ }
16460
+ if (state.brainPanel.open) {
16461
+ if (key.escape) {
16462
+ dispatch({ type: "brainClose" });
16463
+ return true;
16464
+ }
16465
+ if (key.mouse?.kind === "wheel") {
16466
+ dispatch({ type: "brainMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16467
+ return true;
16468
+ }
16469
+ if (key.upArrow) {
16470
+ dispatch({ type: "brainMove", delta: -1 });
16471
+ return true;
16472
+ }
16473
+ if (key.downArrow) {
16474
+ dispatch({ type: "brainMove", delta: 1 });
16475
+ return true;
16476
+ }
16477
+ if (key.leftArrow) {
16478
+ host.onBrainRiskChange?.(-1);
16479
+ return true;
16480
+ }
16481
+ if (key.rightArrow) {
16482
+ host.onBrainRiskChange?.(1);
16483
+ return true;
16484
+ }
16485
+ return true;
16486
+ }
16487
+ if (state.shadowPanel.open) {
16488
+ if (key.escape) {
16489
+ dispatch({ type: "shadowClose" });
16490
+ return true;
16491
+ }
16492
+ if (input === "s" || input === "S") {
16493
+ void host.onShadowStart?.();
16494
+ return true;
16495
+ }
16496
+ if (input === "t" || input === "T") {
16497
+ void host.onShadowStop?.();
16498
+ return true;
16499
+ }
16500
+ return true;
16501
+ }
15477
16502
  if (state.statuslinePicker.open) {
15478
16503
  if (key.escape) {
15479
16504
  dispatch({ type: "statuslineClose" });
@@ -16634,6 +17659,12 @@ function createPanelOpenDispatcher(deps) {
16634
17659
  case "pluginPickerOpen":
16635
17660
  dispatch({ type: "pluginPickerOpen" });
16636
17661
  return true;
17662
+ case "mcpPickerOpen":
17663
+ dispatch({ type: "mcpPickerOpen" });
17664
+ return true;
17665
+ case "toolsPickerOpen":
17666
+ dispatch({ type: "toolsPickerOpen" });
17667
+ return true;
16637
17668
  case "projectPickerOpen":
16638
17669
  void openProjectPicker();
16639
17670
  return true;
@@ -16646,6 +17677,24 @@ function createPanelOpenDispatcher(deps) {
16646
17677
  return true;
16647
17678
  }
16648
17679
  return false;
17680
+ case "brainOpen":
17681
+ if (deps.openBrainPanel) {
17682
+ void deps.openBrainPanel();
17683
+ return true;
17684
+ }
17685
+ return false;
17686
+ case "shadowOpen":
17687
+ if (deps.openShadowPanel) {
17688
+ void deps.openShadowPanel();
17689
+ return true;
17690
+ }
17691
+ return false;
17692
+ case "helpOpen":
17693
+ if (deps.openHelpPanel) {
17694
+ void deps.openHelpPanel();
17695
+ return true;
17696
+ }
17697
+ return false;
16649
17698
  case "toggleMonitor":
16650
17699
  dispatch({ type: "toggleMonitor" });
16651
17700
  return true;
@@ -16761,7 +17810,7 @@ function renderList3() {
16761
17810
  }
16762
17811
 
16763
17812
  // src/slash-command-search.ts
16764
- var CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
17813
+ var CATEGORY_ORDER2 = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
16765
17814
  function buildSlashCommandMatches(entries, rawQuery) {
16766
17815
  const query = normalizeSlashQuery(rawQuery);
16767
17816
  const matches = [];
@@ -16772,7 +17821,7 @@ function buildSlashCommandMatches(entries, rawQuery) {
16772
17821
  }
16773
17822
  return matches.sort((a, b) => {
16774
17823
  if (a.rank !== b.rank) return a.rank - b.rank;
16775
- const catDiff = CATEGORY_ORDER.indexOf(a.category) - CATEGORY_ORDER.indexOf(b.category);
17824
+ const catDiff = CATEGORY_ORDER2.indexOf(a.category) - CATEGORY_ORDER2.indexOf(b.category);
16776
17825
  if (catDiff !== 0) return catDiff;
16777
17826
  return a.name.localeCompare(b.name);
16778
17827
  }).map(({ rank: _rank, ...match }) => match);
@@ -16940,64 +17989,8 @@ function selectedSlashCommandLine(picker) {
16940
17989
  const picked = picker.matches[picker.selected];
16941
17990
  return picked ? `/${picked.name}` : null;
16942
17991
  }
16943
- function contentBlocksText(content) {
16944
- if (typeof content === "string") return content;
16945
- if (!Array.isArray(content)) return "";
16946
- return content.filter(
16947
- (block) => !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string"
16948
- ).map((block) => block.text).join("");
16949
- }
16950
- function rehydrateHistory(messages, startId, toolCalls) {
16951
- const entries = [];
16952
- const toolCallsById = /* @__PURE__ */ new Map();
16953
- if (toolCalls) {
16954
- for (const tc of toolCalls) {
16955
- if (!toolCallsById.has(tc.id)) toolCallsById.set(tc.id, tc);
16956
- }
16957
- }
16958
- const consumed = /* @__PURE__ */ new Set();
16959
- const fallback = [];
16960
- let nextId = startId;
16961
- const textOf = (msg) => contentBlocksText(msg.content);
16962
- const toolEntryFor = (tc) => ({
16963
- id: nextId++,
16964
- kind: "tool",
16965
- name: tc.name,
16966
- durationMs: tc.durationMs,
16967
- ok: tc.ok,
16968
- outputBytes: tc.outputBytes,
16969
- outputTokens: tc.outputTokens,
16970
- outputLines: tc.outputLines
16971
- });
16972
- for (const msg of messages) {
16973
- if (msg.role === "system") continue;
16974
- const text = textOf(msg).trim();
16975
- if (msg.role === "user") {
16976
- if (text) entries.push({ id: nextId++, kind: "user", text });
16977
- continue;
16978
- }
16979
- if (msg.role === "assistant") {
16980
- if (text) entries.push({ id: nextId++, kind: "assistant", text });
16981
- if (Array.isArray(msg.content)) {
16982
- for (const block of msg.content) {
16983
- if (block.type !== "tool_use") continue;
16984
- const tc = toolCallsById.get(block.id);
16985
- if (!tc) continue;
16986
- entries.push(toolEntryFor(tc));
16987
- consumed.add(block.id);
16988
- }
16989
- }
16990
- }
16991
- }
16992
- if (toolCalls) {
16993
- for (const tc of toolCalls) {
16994
- if (!consumed.has(tc.id)) fallback.push(toolEntryFor(tc));
16995
- }
16996
- }
16997
- entries.push(...fallback);
16998
- return entries;
16999
- }
17000
17992
  var PASTE_THRESHOLD_CHARS = 200;
17993
+ var CONTINUE_CONFIRM_DELAY_MS = 4e3;
17001
17994
  var SB_PADX2 = 2;
17002
17995
  function sddLifecycleEntry(op, r) {
17003
17996
  if (typeof r === "number") {
@@ -17086,6 +18079,16 @@ function App({
17086
18079
  saveSettings,
17087
18080
  getPluginItems,
17088
18081
  onPluginToggle,
18082
+ getMcpServers,
18083
+ onMcpToggle,
18084
+ onMcpRestart,
18085
+ getToolsItems,
18086
+ onToolToggle,
18087
+ getBrainData,
18088
+ onBrainRiskLevel,
18089
+ getShadowData,
18090
+ onShadowStart,
18091
+ onShadowStop,
17089
18092
  authHost,
17090
18093
  predictNext,
17091
18094
  onSuggestionsParsed,
@@ -17163,6 +18166,10 @@ function App({
17163
18166
  });
17164
18167
  const hiddenItemsRef = useRef(hiddenItems);
17165
18168
  hiddenItemsRef.current = hiddenItems;
18169
+ const [liveToolCount, setLiveToolCount] = useState(toolCount);
18170
+ useEffect(() => {
18171
+ setLiveToolCount(toolCount);
18172
+ }, [toolCount]);
17166
18173
  const prevBranchRef = useRef(null);
17167
18174
  const [indexState, setIndexState] = useState(() => getIndexState());
17168
18175
  useEffect(() => {
@@ -17262,6 +18269,71 @@ function App({
17262
18269
  });
17263
18270
  const { openPromptPicker } = usePromptPicker({ projectRoot, dispatch });
17264
18271
  const { openModePicker } = useModePicker({ dispatch, getModes });
18272
+ const openBrainPanel = React7.useCallback(() => {
18273
+ if (!getBrainData) return;
18274
+ const data = getBrainData();
18275
+ dispatch({ type: "brainOpen", riskLevel: data.riskLevel, log: data.log });
18276
+ }, [getBrainData]);
18277
+ const changeBrainRisk = React7.useCallback(
18278
+ (delta) => {
18279
+ dispatch({ type: "brainRiskChange", delta });
18280
+ },
18281
+ []
18282
+ );
18283
+ const prevRiskRef = React7.useRef(state.brainPanel.riskLevel);
18284
+ React7.useEffect(() => {
18285
+ if (!state.brainPanel.open) return;
18286
+ const current = state.brainPanel.riskLevel;
18287
+ if (current !== prevRiskRef.current && onBrainRiskLevel) {
18288
+ prevRiskRef.current = current;
18289
+ const err = onBrainRiskLevel(current);
18290
+ if (err) dispatch({ type: "brainHint", text: err });
18291
+ else dispatch({ type: "brainHint", text: `Risk ceiling \u2192 ${current.toUpperCase()}` });
18292
+ }
18293
+ }, [state.brainPanel.riskLevel, state.brainPanel.open, onBrainRiskLevel]);
18294
+ const openShadowPanel = React7.useCallback(() => {
18295
+ if (!getShadowData) return;
18296
+ const data = getShadowData();
18297
+ dispatch({ type: "shadowOpen", shadow: data });
18298
+ }, [getShadowData]);
18299
+ const handleShadowStart = React7.useCallback(async () => {
18300
+ if (!onShadowStart) return;
18301
+ dispatch({ type: "shadowHint", text: "Starting Shadow Agent\u2026" });
18302
+ try {
18303
+ const err = await onShadowStart();
18304
+ if (err) dispatch({ type: "shadowHint", text: err });
18305
+ else {
18306
+ if (getShadowData) dispatch({ type: "shadowUpdate", shadow: getShadowData() });
18307
+ dispatch({ type: "shadowHint", text: "Shadow Agent started." });
18308
+ }
18309
+ } catch (e) {
18310
+ dispatch({ type: "shadowHint", text: `Failed to start: ${toErrorMessage(e)}` });
18311
+ }
18312
+ }, [onShadowStart, getShadowData]);
18313
+ const handleShadowStop = React7.useCallback(async () => {
18314
+ if (!onShadowStop) return;
18315
+ dispatch({ type: "shadowHint", text: "Stopping Shadow Agent\u2026" });
18316
+ try {
18317
+ const err = await onShadowStop();
18318
+ if (err) dispatch({ type: "shadowHint", text: err });
18319
+ else {
18320
+ if (getShadowData) dispatch({ type: "shadowUpdate", shadow: getShadowData() });
18321
+ dispatch({ type: "shadowHint", text: "Shadow Agent stopped." });
18322
+ }
18323
+ } catch (e) {
18324
+ dispatch({ type: "shadowHint", text: `Failed to stop: ${toErrorMessage(e)}` });
18325
+ }
18326
+ }, [onShadowStop, getShadowData]);
18327
+ const openHelpPanel = React7.useCallback(() => {
18328
+ const entries = Array.from(slashRegistry.listWithOwner()).map(({ cmd, owner }) => ({
18329
+ name: owner === "core" ? cmd.name : `${owner}:${cmd.name}`,
18330
+ description: cmd.description,
18331
+ category: cmd.category ?? "App",
18332
+ aliases: cmd.aliases,
18333
+ argsHint: cmd.argsHint
18334
+ }));
18335
+ dispatch({ type: "helpOpen", entries });
18336
+ }, []);
17265
18337
  useStatuslineHiddenSync({
17266
18338
  pickerOpen: state.statuslinePicker.open,
17267
18339
  pickerHidden: state.statuslinePicker.hiddenItems,
@@ -17284,7 +18356,7 @@ function App({
17284
18356
  const inputGateRef = useRef(false);
17285
18357
  const lastEnterAtRef = useRef(0);
17286
18358
  const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
17287
- const projectName = React5.useMemo(() => {
18359
+ const projectName = React7.useMemo(() => {
17288
18360
  const base = path9.basename(projectRoot);
17289
18361
  return base && base !== path9.sep ? base : void 0;
17290
18362
  }, [projectRoot]);
@@ -17318,6 +18390,7 @@ function App({
17318
18390
  const streamSegmentsRef = useRef([]);
17319
18391
  const pendingDeltaRef = useRef("");
17320
18392
  const flushTimerRef = useRef(null);
18393
+ const assistantCommittedThisRunRef = useRef(false);
17321
18394
  const stateRef = useRef(state);
17322
18395
  stateRef.current = state;
17323
18396
  const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
@@ -17346,7 +18419,7 @@ function App({
17346
18419
  [statuslineHiddenForPicker]
17347
18420
  );
17348
18421
  const [mouseMode, setMouseMode] = useState(mouse);
17349
- 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;
18422
+ 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;
17350
18423
  const mouseTrackingOn = mouseMode || pickerOverlayOpen;
17351
18424
  const mouseWrittenRef = useRef(false);
17352
18425
  useEffect(() => {
@@ -17388,7 +18461,7 @@ function App({
17388
18461
  }
17389
18462
  });
17390
18463
  const handleKeyRef = useRef(null);
17391
- const handleRewindTo = React5.useCallback(
18464
+ const handleRewindTo = React7.useCallback(
17392
18465
  async (checkpointIndex) => {
17393
18466
  const sessionId = agent.ctx.session.id;
17394
18467
  if (!sessionId) return;
@@ -17410,7 +18483,7 @@ function App({
17410
18483
  dispatch({ type: "clearInput" });
17411
18484
  };
17412
18485
  const startedAtRef = useRef(Date.now());
17413
- const [nowTick, setNowTick] = React5.useState(Date.now());
18486
+ const [nowTick, setNowTick] = React7.useState(Date.now());
17414
18487
  useEffect(() => {
17415
18488
  const t = setInterval(() => setNowTick(Date.now()), 1e4);
17416
18489
  return () => clearInterval(t);
@@ -17496,7 +18569,7 @@ function App({
17496
18569
  agent.ctx.provider,
17497
18570
  agent.ctx.todos
17498
18571
  ]);
17499
- const [gitInfo, setGitInfo] = React5.useState(null);
18572
+ const [gitInfo, setGitInfo] = React7.useState(null);
17500
18573
  useEffect(() => {
17501
18574
  let cancelled = false;
17502
18575
  const refresh = () => {
@@ -17824,8 +18897,8 @@ function App({
17824
18897
  } catch {
17825
18898
  }
17826
18899
  }, []);
17827
- React5.useLayoutEffect(() => {
17828
- const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.designPicker.open || state.resumePicker.open || state.settingsPicker.open || state.enhanceBusy || state.enhance != null || state.coordinator.monitorOpen || state.escConfirm != null || state.sendModePicker != null || state.confirmQueue.length > 0;
18900
+ React7.useLayoutEffect(() => {
18901
+ const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.designPicker.open || state.resumePicker.open || state.settingsPicker.open || state.enhanceBusy || state.enhance != null || state.continueConfirm != null || state.coordinator.monitorOpen || state.escConfirm != null || state.sendModePicker != null || state.confirmQueue.length > 0;
17829
18902
  const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
17830
18903
  const newEntryCommitted = state.entries.length > prevEntriesCount.current;
17831
18904
  const curToolStreamLen = state.toolStream?.text.length ?? 0;
@@ -17845,6 +18918,7 @@ function App({
17845
18918
  state.settingsPicker.open,
17846
18919
  state.enhanceBusy,
17847
18920
  state.enhance,
18921
+ state.continueConfirm,
17848
18922
  state.coordinator.monitorOpen,
17849
18923
  state.escConfirm,
17850
18924
  state.sendModePicker,
@@ -17977,8 +19051,9 @@ function App({
17977
19051
  process.stdout.off("resize", handleResize);
17978
19052
  };
17979
19053
  }, [eraseLiveRegion]);
17980
- React5.useLayoutEffect(() => {
17981
- if (state.enhanceBusy || state.enhance != null) eraseLiveRegion();
19054
+ React7.useLayoutEffect(() => {
19055
+ if (state.enhanceBusy || state.enhance != null || state.continueConfirm != null)
19056
+ eraseLiveRegion();
17982
19057
  });
17983
19058
  useEffect(() => {
17984
19059
  const trimmed = state.buffer.trimStart();
@@ -18128,20 +19203,20 @@ function App({
18128
19203
  slashRegistry.unregister("agents");
18129
19204
  };
18130
19205
  }, [slashRegistry]);
18131
- const openModelPicker = React5.useCallback(async () => {
19206
+ const openModelPicker = React7.useCallback(async () => {
18132
19207
  if (!getPickableProviders) return;
18133
19208
  const providers = await getPickableProviders();
18134
19209
  dispatch({ type: "modelPickerOpen", providers });
18135
19210
  }, [getPickableProviders]);
18136
- const openProjectPicker = React5.useCallback(async () => {
19211
+ const openProjectPicker = React7.useCallback(async () => {
18137
19212
  if (!getProjectPickerItems) return;
18138
19213
  const items = await getProjectPickerItems();
18139
19214
  dispatch({ type: "projectPickerOpen", items });
18140
19215
  }, [getProjectPickerItems]);
18141
- const openFKeyPicker = React5.useCallback(() => {
19216
+ const openFKeyPicker = React7.useCallback(() => {
18142
19217
  dispatch({ type: "fKeyPickerOpen" });
18143
19218
  }, [dispatch]);
18144
- const loadLiveSessions = React5.useCallback(async () => {
19219
+ const loadLiveSessions = React7.useCallback(async () => {
18145
19220
  if (!getLiveSessions) {
18146
19221
  dispatch({ type: "sessionsPanelSet", sessions: [] });
18147
19222
  return;
@@ -18161,7 +19236,10 @@ function App({
18161
19236
  openProjectPicker,
18162
19237
  openStatuslinePicker,
18163
19238
  openAuthPanel: authPanelController.openAuthPanel,
18164
- openModePicker
19239
+ openModePicker,
19240
+ openBrainPanel,
19241
+ openShadowPanel,
19242
+ openHelpPanel
18165
19243
  });
18166
19244
  onPanelOpen.current = dispatcher;
18167
19245
  return () => {
@@ -18173,7 +19251,10 @@ function App({
18173
19251
  openProjectPicker,
18174
19252
  openStatuslinePicker,
18175
19253
  authPanelController.openAuthPanel,
18176
- openModePicker
19254
+ openModePicker,
19255
+ openBrainPanel,
19256
+ openShadowPanel,
19257
+ openHelpPanel
18177
19258
  ]);
18178
19259
  useEffect(() => {
18179
19260
  if (!state.sessionsPanelOpen || !getLiveSessions) return void 0;
@@ -18182,7 +19263,7 @@ function App({
18182
19263
  }, 5e3);
18183
19264
  return () => clearInterval(t);
18184
19265
  }, [state.sessionsPanelOpen, getLiveSessions, loadLiveSessions]);
18185
- const openSettings = React5.useCallback(() => {
19266
+ const openSettings = React7.useCallback(() => {
18186
19267
  if (!getSettings) return;
18187
19268
  const s2 = getSettings();
18188
19269
  dispatch({
@@ -18227,7 +19308,7 @@ function App({
18227
19308
  animationStyle: s2.animationStyle ?? "rainbow"
18228
19309
  });
18229
19310
  }, [getSettings]);
18230
- const refreshPluginPicker = React5.useCallback(() => {
19311
+ const refreshPluginPicker = React7.useCallback(() => {
18231
19312
  if (!getPluginItems) return;
18232
19313
  dispatch({ type: "pluginPickerSetItems", items: getPluginItems() });
18233
19314
  }, [getPluginItems]);
@@ -18235,7 +19316,7 @@ function App({
18235
19316
  if (!state.pluginPicker.open) return;
18236
19317
  refreshPluginPicker();
18237
19318
  }, [state.pluginPicker.open, refreshPluginPicker]);
18238
- const toggleSelectedPlugin = React5.useCallback(async () => {
19319
+ const toggleSelectedPlugin = React7.useCallback(async () => {
18239
19320
  if (!onPluginToggle) return;
18240
19321
  const selected = stateRef.current.pluginPicker.items[stateRef.current.pluginPicker.selected];
18241
19322
  if (!selected) return;
@@ -18259,6 +19340,76 @@ function App({
18259
19340
  });
18260
19341
  }
18261
19342
  }, [onPluginToggle]);
19343
+ const refreshMcpPicker = React7.useCallback(() => {
19344
+ if (!getMcpServers) return;
19345
+ dispatch({ type: "mcpPickerSetItems", items: getMcpServers() });
19346
+ }, [getMcpServers]);
19347
+ useEffect(() => {
19348
+ if (!state.mcpPicker.open) return;
19349
+ refreshMcpPicker();
19350
+ }, [state.mcpPicker.open, refreshMcpPicker]);
19351
+ const toggleSelectedMcpServer = React7.useCallback(async () => {
19352
+ if (!onMcpToggle) return;
19353
+ const selected = stateRef.current.mcpPicker.items[stateRef.current.mcpPicker.selected];
19354
+ if (!selected) return;
19355
+ dispatch({ type: "mcpPickerBusy", busy: true });
19356
+ try {
19357
+ const result = await onMcpToggle(selected.name);
19358
+ dispatch({ type: "mcpPickerSetItems", items: result.items });
19359
+ dispatch({ type: "mcpPickerHint", text: result.error ?? result.message });
19360
+ } catch (err) {
19361
+ dispatch({ type: "mcpPickerBusy", busy: false });
19362
+ dispatch({
19363
+ type: "mcpPickerHint",
19364
+ text: `Failed to toggle ${selected.name}: ${toErrorMessage(err)}`
19365
+ });
19366
+ }
19367
+ }, [onMcpToggle]);
19368
+ const restartSelectedMcpServer = React7.useCallback(async () => {
19369
+ if (!onMcpRestart) return;
19370
+ const selected = stateRef.current.mcpPicker.items[stateRef.current.mcpPicker.selected];
19371
+ if (!selected) return;
19372
+ dispatch({ type: "mcpPickerBusy", busy: true });
19373
+ try {
19374
+ const result = await onMcpRestart(selected.name);
19375
+ dispatch({ type: "mcpPickerSetItems", items: result.items });
19376
+ dispatch({ type: "mcpPickerHint", text: result.error ?? result.message });
19377
+ } catch (err) {
19378
+ dispatch({ type: "mcpPickerBusy", busy: false });
19379
+ dispatch({
19380
+ type: "mcpPickerHint",
19381
+ text: `Failed to restart ${selected.name}: ${toErrorMessage(err)}`
19382
+ });
19383
+ }
19384
+ }, [onMcpRestart]);
19385
+ const refreshToolsPicker = React7.useCallback(() => {
19386
+ if (!getToolsItems) return;
19387
+ const items = getToolsItems();
19388
+ dispatch({ type: "toolsPickerSetItems", items });
19389
+ setLiveToolCount(items.filter((item) => item.enabled).length);
19390
+ }, [getToolsItems]);
19391
+ useEffect(() => {
19392
+ if (!state.toolsPicker.open) return;
19393
+ refreshToolsPicker();
19394
+ }, [state.toolsPicker.open, refreshToolsPicker]);
19395
+ const toggleSelectedTool = React7.useCallback(async () => {
19396
+ if (!onToolToggle) return;
19397
+ const selected = stateRef.current.toolsPicker.items[stateRef.current.toolsPicker.selected];
19398
+ if (!selected) return;
19399
+ dispatch({ type: "toolsPickerBusy", busy: true });
19400
+ try {
19401
+ const result = await onToolToggle(selected.name);
19402
+ dispatch({ type: "toolsPickerSetItems", items: result.items });
19403
+ setLiveToolCount(result.items.filter((item) => item.enabled).length);
19404
+ dispatch({ type: "toolsPickerHint", text: result.error ?? result.message });
19405
+ } catch (err) {
19406
+ dispatch({ type: "toolsPickerBusy", busy: false });
19407
+ dispatch({
19408
+ type: "toolsPickerHint",
19409
+ text: `Failed to toggle ${selected.name}: ${toErrorMessage(err)}`
19410
+ });
19411
+ }
19412
+ }, [onToolToggle]);
18262
19413
  const autoSubmitStreakRef = useRef(0);
18263
19414
  const autoSubmitCapWarnedRef = useRef(false);
18264
19415
  const [nextStepsRecheck, setNextStepsRecheck] = useState(0);
@@ -18274,7 +19425,7 @@ function App({
18274
19425
  nextStepsAutoSubmitSuggestionRef.current = null;
18275
19426
  return;
18276
19427
  }
18277
- if (state.enhance != null || state.enhanceBusy) {
19428
+ if (state.enhance != null || state.enhanceBusy || state.continueConfirm != null) {
18278
19429
  return;
18279
19430
  }
18280
19431
  if (nextStepsAutoSubmitTimerRef.current != null) {
@@ -18355,6 +19506,7 @@ function App({
18355
19506
  autonomyLive,
18356
19507
  state.enhance,
18357
19508
  state.enhanceBusy,
19509
+ state.continueConfirm,
18358
19510
  nextStepsRecheck,
18359
19511
  getSettings,
18360
19512
  getSuggestions,
@@ -18912,11 +20064,14 @@ Available chords:
18912
20064
  });
18913
20065
  });
18914
20066
  const offFallback = events.on("provider.fallback", (e) => {
20067
+ const fallbackEvent = e;
20068
+ const contextWarning = fallbackEvent.contextWindowWarning ? `
20069
+ \u26A0 smaller context window: ${fallbackEvent.contextWindowWarning.fromMaxContext.toLocaleString("en-US")} \u2192 ${fallbackEvent.contextWindowWarning.toMaxContext.toLocaleString("en-US")} tokens${fallbackEvent.contextWindowWarning.currentTokens ? `; current request \u2248 ${fallbackEvent.contextWindowWarning.currentTokens.toLocaleString("en-US")} tokens (${Math.round(fallbackEvent.contextWindowWarning.currentTokens / fallbackEvent.contextWindowWarning.toMaxContext * 100)}% of new window)` : ""}` : "";
18915
20070
  dispatch({
18916
20071
  type: "addEntry",
18917
20072
  entry: {
18918
20073
  kind: "warn",
18919
- text: `\u21BB rate-limited (${e.status}) \u2014 switched to ${e.to.providerId}/${e.to.model}`
20074
+ text: `\u21BB rate-limited (${e.status}) \u2014 switched to ${e.to.providerId}/${e.to.model}${contextWarning}`
18920
20075
  }
18921
20076
  });
18922
20077
  });
@@ -18935,12 +20090,15 @@ Available chords:
18935
20090
  for (const seg of segments) {
18936
20091
  if (seg.text.trim()) {
18937
20092
  dispatch({ type: "addEntry", entry: { kind: seg.kind, text: seg.text } });
20093
+ if (seg.kind === "assistant") assistantCommittedThisRunRef.current = true;
18938
20094
  }
18939
20095
  }
18940
20096
  if (segments.length === 0 && text.trim()) {
18941
20097
  dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
20098
+ assistantCommittedThisRunRef.current = true;
18942
20099
  } else if (segments.length === 0 && fallbackText.trim()) {
18943
20100
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: fallbackText } });
20101
+ assistantCommittedThisRunRef.current = true;
18944
20102
  }
18945
20103
  });
18946
20104
  const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
@@ -19183,7 +20341,9 @@ Available chords:
19183
20341
  setLiveProvider,
19184
20342
  setLiveModel,
19185
20343
  setActiveMaxContext,
19186
- agentCtxMaxContext: agent.ctx.provider.capabilities.maxContext,
20344
+ getAgentCtxMaxContext: () => agent.ctx.provider.capabilities.maxContext,
20345
+ activeMaxContext,
20346
+ currentContextTokens,
19187
20347
  switchAutonomy,
19188
20348
  submit: (text) => submitRef.current(text),
19189
20349
  onPromptPickerEnter: () => {
@@ -19350,6 +20510,18 @@ Available chords:
19350
20510
  }
19351
20511
  },
19352
20512
  onPluginPickerToggle: toggleSelectedPlugin,
20513
+ onMcpPickerToggle: toggleSelectedMcpServer,
20514
+ onMcpPickerRestart: restartSelectedMcpServer,
20515
+ onToolsPickerToggle: toggleSelectedTool,
20516
+ onHelpPanelEnter: () => {
20517
+ const entry = state.helpPanel.entries[state.helpPanel.selected];
20518
+ if (!entry) return;
20519
+ dispatch({ type: "helpClose" });
20520
+ submitRef.current(`/${entry.name}`);
20521
+ },
20522
+ onBrainRiskChange: changeBrainRisk,
20523
+ onShadowStart: handleShadowStart,
20524
+ onShadowStop: handleShadowStop,
19353
20525
  onAuthEnter: authPanelController.onAuthEnter,
19354
20526
  onAuthBack: authPanelController.onAuthBack,
19355
20527
  onAuthShortcut: authPanelController.onAuthShortcut,
@@ -19505,11 +20677,13 @@ Available chords:
19505
20677
  const handleKey = async (input, key) => {
19506
20678
  if (state.status === "aborting" && !state.steeringPending && state.interrupts === 0) return;
19507
20679
  if (state.confirmQueue.length > 0) return;
20680
+ if (state.shellCommandWarning) return;
19508
20681
  if (state.enhanceBusy) {
19509
20682
  if (key.escape) enhanceAbortRef.current?.abort();
19510
20683
  return;
19511
20684
  }
19512
20685
  if (state.enhance) return;
20686
+ if (state.continueConfirm) return;
19513
20687
  if (state.escConfirm) return;
19514
20688
  if (state.sendModePicker) return;
19515
20689
  if (state.helpOpen) {
@@ -20320,9 +21494,7 @@ Available chords:
20320
21494
  }
20321
21495
  });
20322
21496
  }
20323
- const assistantEntriesBeforeRun = stateRef.current.entries.filter(
20324
- (entry) => entry.kind === "assistant"
20325
- ).length;
21497
+ assistantCommittedThisRunRef.current = false;
20326
21498
  const result = await agent.run(routed.blocks, { signal: ctrl.signal });
20327
21499
  const lingering = streamingTextRef.current;
20328
21500
  const lingeringSegments = streamSegmentsRef.current;
@@ -20342,7 +21514,13 @@ Available chords:
20342
21514
  }
20343
21515
  } else if (lingering.trim()) {
20344
21516
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: lingering } });
20345
- } else if (result.status === "done" && result.finalText?.trim() && stateRef.current.entries.filter((entry) => entry.kind === "assistant").length === assistantEntriesBeforeRun) {
21517
+ } else if (result.status === "done" && result.finalText?.trim() && // Only recover finalText when no assistant entry was committed by the
21518
+ // per-iteration `provider.response` flush during this run. Checked via
21519
+ // a synchronous ref, NOT `stateRef` — the flush's `addEntry` dispatch
21520
+ // isn't reflected in `stateRef` yet when we resume here, so a
21521
+ // state-based count would read stale and re-commit the final reply,
21522
+ // rendering it twice.
21523
+ !assistantCommittedThisRunRef.current) {
20346
21524
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: result.finalText } });
20347
21525
  }
20348
21526
  if (result.status === "aborted") {
@@ -20545,6 +21723,33 @@ Available chords:
20545
21723
  await pasteClipboardImage();
20546
21724
  return;
20547
21725
  }
21726
+ if (trimmed.startsWith("!")) {
21727
+ const command = trimmed.slice(1).trim();
21728
+ if (!command) {
21729
+ dispatch({
21730
+ type: "addEntry",
21731
+ entry: { kind: "info", text: "Usage: !<shell command>" }
21732
+ });
21733
+ return;
21734
+ }
21735
+ const settings = getSettings?.();
21736
+ if (settings?.shellBangWarningDontShowAgain !== true) {
21737
+ const decision = await new Promise((resolve) => {
21738
+ dispatch({ type: "shellCommandWarningOpen", info: { command, resolve } });
21739
+ });
21740
+ dispatch({ type: "shellCommandWarningClose" });
21741
+ if (decision === "no") return;
21742
+ if (decision === "dont-show-again") {
21743
+ if (settings && saveSettings) {
21744
+ const err = await saveSettings({ ...settings, shellBangWarningDontShowAgain: true });
21745
+ if (err) {
21746
+ dispatch({ type: "addEntry", entry: { kind: "warn", text: err } });
21747
+ }
21748
+ }
21749
+ }
21750
+ }
21751
+ return submit(`/dev ${command}`);
21752
+ }
20548
21753
  if (trimmed === "/prompt" || trimmed === "/prompts-browse") {
20549
21754
  pushSubmittedHistory();
20550
21755
  clearDraft();
@@ -20627,6 +21832,9 @@ ${content}
20627
21832
  const currentMode = getModeLabel();
20628
21833
  if (currentMode !== liveModeLabel) setLiveModeLabel(currentMode);
20629
21834
  }
21835
+ if (getToolsItems) {
21836
+ setLiveToolCount(getToolsItems().filter((item) => item.enabled).length);
21837
+ }
20630
21838
  if (res?.exit) {
20631
21839
  exit();
20632
21840
  onExit(0);
@@ -20664,6 +21872,10 @@ ${content}
20664
21872
  const builder = builderRef.current;
20665
21873
  if (!builder) return;
20666
21874
  const steering = state.steeringPending;
21875
+ const continueResolved = !steering && state.status === "idle" && detectContinueIntent(trimmed) ? resolveContinuation({
21876
+ todos: agent.ctx.todos,
21877
+ suggestions: getSuggestions?.() ?? []
21878
+ }) : null;
20667
21879
  let effectiveText = trimmed;
20668
21880
  const chips = [];
20669
21881
  let cleanText = trimmed;
@@ -20679,7 +21891,7 @@ ${content}
20679
21891
  chips.length = 0;
20680
21892
  }
20681
21893
  }
20682
- if (enhanceEnabledRef.current && state.status === "idle" && !steering && shouldEnhance(cleanText)) {
21894
+ if (enhanceEnabledRef.current && state.status === "idle" && !steering && !continueResolved && shouldEnhance(cleanText)) {
20683
21895
  dispatch({ type: "enhanceBusy", on: true });
20684
21896
  const ac = new AbortController();
20685
21897
  enhanceAbortRef.current = ac;
@@ -20742,6 +21954,31 @@ ${content}
20742
21954
  }
20743
21955
  }
20744
21956
  }
21957
+ if (continueResolved) {
21958
+ const decision = await new Promise((resolve) => {
21959
+ dispatch({
21960
+ type: "continueConfirmOpen",
21961
+ info: {
21962
+ label: continueResolved.label,
21963
+ instruction: continueResolved.text,
21964
+ source: continueResolved.source,
21965
+ grounded: continueResolved.source !== "open",
21966
+ resolve
21967
+ }
21968
+ });
21969
+ });
21970
+ dispatch({ type: "continueConfirmClose" });
21971
+ if (decision === "cancel") {
21972
+ clearDraft();
21973
+ return;
21974
+ }
21975
+ if (decision === "edit") {
21976
+ const seed = continueResolved.source === "open" ? "" : continueResolved.text;
21977
+ setDraft(seed, seed.length);
21978
+ return;
21979
+ }
21980
+ effectiveText = continueResolved.text;
21981
+ }
20745
21982
  const sddContext = await getSDDContext?.();
20746
21983
  if (sddContext && trimmed) {
20747
21984
  builder.appendText(`[SDD SESSION ACTIVE]
@@ -20756,7 +21993,7 @@ User message:
20756
21993
  builder.appendText(toAppend);
20757
21994
  }
20758
21995
  if (steering) dispatch({ type: "steerConsume" });
20759
- const displayText = steering ? `\u21AF ${effectiveText}` : effectiveText;
21996
+ const displayText = continueResolved ? continueResolved.label : steering ? `\u21AF ${effectiveText}` : effectiveText;
20760
21997
  const pasteParts = [];
20761
21998
  for (const m of trimmed.matchAll(new RegExp(INLINE_TOKEN_SRC, "g"))) {
20762
21999
  const token = m[0];
@@ -20887,7 +22124,7 @@ ${content}
20887
22124
  stdout?.columns ?? 80
20888
22125
  );
20889
22126
  const inputHeight = Math.max(1, inputCellRows.length);
20890
- const hideInput = enhanceActive || state.helpOpen || state.processListOpen || state.authPanel.open;
22127
+ const hideInput = enhanceActive || state.continueConfirm != null || state.helpOpen || state.processListOpen || state.authPanel.open;
20891
22128
  const lowerFunctionPanelOpen = state.monitorOpen || state.agentsMonitorOpen || (state.autoPhase?.monitorOpen ?? false) || state.worktreeMonitorOpen || state.planPanelOpen || state.kanbanPanelOpen || state.todosMonitorOpen || state.queuePanelOpen || state.processListOpen || state.goalPanelOpen;
20892
22129
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 0, children: [
20893
22130
  mouseMode ? /* @__PURE__ */ jsx(
@@ -21074,6 +22311,50 @@ ${content}
21074
22311
  hint: state.pluginPicker.hint
21075
22312
  }
21076
22313
  ) : null,
22314
+ state.mcpPicker.open ? /* @__PURE__ */ jsx(
22315
+ McpPicker,
22316
+ {
22317
+ items: state.mcpPicker.items,
22318
+ selected: state.mcpPicker.selected,
22319
+ busy: state.mcpPicker.busy,
22320
+ hint: state.mcpPicker.hint
22321
+ }
22322
+ ) : null,
22323
+ state.toolsPicker.open ? /* @__PURE__ */ jsx(
22324
+ ToolsPicker,
22325
+ {
22326
+ items: state.toolsPicker.items,
22327
+ selected: state.toolsPicker.selected,
22328
+ busy: state.toolsPicker.busy,
22329
+ hint: state.toolsPicker.hint,
22330
+ filter: state.toolsPicker.filter
22331
+ }
22332
+ ) : null,
22333
+ state.brainPanel.open ? /* @__PURE__ */ jsx(
22334
+ BrainPanel,
22335
+ {
22336
+ riskLevel: state.brainPanel.riskLevel,
22337
+ log: state.brainPanel.log,
22338
+ selected: state.brainPanel.selected,
22339
+ hint: state.brainPanel.hint
22340
+ }
22341
+ ) : null,
22342
+ state.helpPanel.open ? /* @__PURE__ */ jsx(
22343
+ HelpPanel,
22344
+ {
22345
+ entries: state.helpPanel.entries,
22346
+ filter: state.helpPanel.filter,
22347
+ selected: state.helpPanel.selected,
22348
+ hint: state.helpPanel.hint
22349
+ }
22350
+ ) : null,
22351
+ state.shadowPanel.open ? /* @__PURE__ */ jsx(
22352
+ ShadowPanel,
22353
+ {
22354
+ shadow: state.shadowPanel.shadow,
22355
+ hint: state.shadowPanel.hint
22356
+ }
22357
+ ) : null,
21077
22358
  state.authPanel.open ? /* @__PURE__ */ jsx(AuthPanel, { panel: state.authPanel }) : null,
21078
22359
  state.projectPicker.open ? /* @__PURE__ */ jsx(
21079
22360
  ProjectPicker,
@@ -21136,6 +22417,17 @@ ${content}
21136
22417
  }
21137
22418
  }
21138
22419
  ) }) : null,
22420
+ state.shellCommandWarning ? (() => {
22421
+ const info = state.shellCommandWarning;
22422
+ let resolved = false;
22423
+ const onDecision = (decision) => {
22424
+ if (resolved) return;
22425
+ resolved = true;
22426
+ info.resolve(decision);
22427
+ dispatch({ type: "shellCommandWarningClose" });
22428
+ };
22429
+ return /* @__PURE__ */ jsx(ShellCommandWarning, { command: info.command, onDecision });
22430
+ })() : null,
21139
22431
  state.confirmQueue.length > 0 && (() => {
21140
22432
  const head = expectDefined$1(state.confirmQueue[0]);
21141
22433
  let resolved = false;
@@ -21257,6 +22549,26 @@ ${content}
21257
22549
  }
21258
22550
  );
21259
22551
  })() : null,
22552
+ state.continueConfirm ? (() => {
22553
+ const info = state.continueConfirm;
22554
+ let resolved = false;
22555
+ const onDecision = (decision) => {
22556
+ if (resolved) return;
22557
+ resolved = true;
22558
+ info.resolve(decision);
22559
+ };
22560
+ return /* @__PURE__ */ jsx(
22561
+ ContinueConfirmPanel,
22562
+ {
22563
+ label: info.label,
22564
+ instruction: info.instruction,
22565
+ source: info.source,
22566
+ grounded: info.grounded,
22567
+ delayMs: CONTINUE_CONFIRM_DELAY_MS,
22568
+ onDecision
22569
+ }
22570
+ );
22571
+ })() : null,
21260
22572
  /* @__PURE__ */ jsx(Box, { ref: statusBarWrapRef, flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx(
21261
22573
  StatusBar,
21262
22574
  {
@@ -21307,7 +22619,7 @@ ${content}
21307
22619
  sessionCount,
21308
22620
  mailbox: mailboxStatus,
21309
22621
  tokenSavingMode: liveSettings?.featureTokenSaving ?? tokenSavingMode,
21310
- toolCount,
22622
+ toolCount: liveToolCount,
21311
22623
  sideEffectCount: agent.ctx.sideEffects?.length ?? 0
21312
22624
  }
21313
22625
  ) }),
@@ -21793,7 +23105,7 @@ async function runTui(opts) {
21793
23105
  };
21794
23106
  try {
21795
23107
  instance = render(
21796
- React5.createElement(App, {
23108
+ React7.createElement(App, {
21797
23109
  agent: opts.agent,
21798
23110
  slashRegistry: opts.slashRegistry,
21799
23111
  attachments: opts.attachments,
@@ -21848,6 +23160,16 @@ async function runTui(opts) {
21848
23160
  saveSettings: opts.saveSettings,
21849
23161
  getPluginItems: opts.getPluginItems,
21850
23162
  onPluginToggle: opts.onPluginToggle,
23163
+ getMcpServers: opts.getMcpServers,
23164
+ onMcpToggle: opts.onMcpToggle,
23165
+ onMcpRestart: opts.onMcpRestart,
23166
+ getToolsItems: opts.getToolsItems,
23167
+ onToolToggle: opts.onToolToggle,
23168
+ getBrainData: opts.getBrainData,
23169
+ onBrainRiskLevel: opts.onBrainRiskLevel,
23170
+ getShadowData: opts.getShadowData,
23171
+ onShadowStart: opts.onShadowStart,
23172
+ onShadowStop: opts.onShadowStop,
21851
23173
  authHost: opts.authHost,
21852
23174
  predictNext: opts.predictNext,
21853
23175
  onSuggestionsParsed: opts.onSuggestionsParsed,