@wrongstack/tui 0.282.1 → 0.282.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,
@@ -12599,7 +12779,135 @@ function PlanRow({ item }) {
12599
12779
  ] }) : null
12600
12780
  ] });
12601
12781
  }
12602
- var CHROME_ROWS2 = 17;
12782
+ var CHROME_ROWS2 = 14;
12783
+ var RISK_DESCS = {
12784
+ off: "Human decides everything",
12785
+ low: "Auto-decide low risk only",
12786
+ medium: "Auto-decide up to medium risk",
12787
+ high: "Auto-decide up to high risk",
12788
+ all: "Auto-decide everything"
12789
+ };
12790
+ var RISK_COLORS2 = {
12791
+ off: "gray",
12792
+ low: "green",
12793
+ medium: "yellow",
12794
+ high: "red",
12795
+ all: "magenta"
12796
+ };
12797
+ function BrainPanel({
12798
+ riskLevel,
12799
+ log,
12800
+ selected,
12801
+ hint
12802
+ }) {
12803
+ const { stdout } = useStdout();
12804
+ const termRows = stdout?.rows ?? 24;
12805
+ const maxVisible = Math.max(4, termRows - CHROME_ROWS2);
12806
+ const total = log.length;
12807
+ const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12808
+ const windowEnd = Math.min(windowStart + maxVisible, total);
12809
+ const above = windowStart;
12810
+ const below = total - windowEnd;
12811
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
12812
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "\u2501\u2501 Brain \u2501\u2501" }),
12813
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate log \xB7 \u2190/\u2192 change risk \xB7 Esc close" }),
12814
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
12815
+ /* @__PURE__ */ jsxs(Box, { children: [
12816
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Risk ceiling: " }),
12817
+ /* @__PURE__ */ jsx(Text, { color: RISK_COLORS2[riskLevel], bold: true, children: riskLevel.toUpperCase() }),
12818
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${RISK_DESCS[riskLevel]}` })
12819
+ ] }),
12820
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
12821
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "Recent decisions" }),
12822
+ total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " No decisions recorded yet this session." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12823
+ above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12824
+ log.slice(windowStart, windowEnd).map((entry, i) => {
12825
+ const index = windowStart + i;
12826
+ const focused = index === selected;
12827
+ return /* @__PURE__ */ jsxs(
12828
+ Text,
12829
+ {
12830
+ inverse: focused,
12831
+ ...focused ? { color: "magenta" } : {},
12832
+ wrap: "truncate-end",
12833
+ children: [
12834
+ focused ? "\u203A " : " ",
12835
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.age.padEnd(8) }),
12836
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: entry.kind.padEnd(12) }),
12837
+ /* @__PURE__ */ jsx(Text, { children: entry.question.length > 60 ? `${entry.question.slice(0, 57)}\u2026` : entry.question }),
12838
+ entry.outcome ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2192 ${entry.outcome.length > 20 ? `${entry.outcome.slice(0, 17)}\u2026` : entry.outcome}` }) : null
12839
+ ]
12840
+ },
12841
+ `${entry.kind}-${i}`
12842
+ );
12843
+ }),
12844
+ below > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${below} more` }) : null
12845
+ ] })
12846
+ ] })
12847
+ ] }),
12848
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
12849
+ ] });
12850
+ }
12851
+ var CHROME_ROWS3 = 17;
12852
+ function statusBadge(status, enabled) {
12853
+ if (!enabled) return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB disabled" });
12854
+ switch (status) {
12855
+ case "connected":
12856
+ return /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF connected" });
12857
+ case "connecting":
12858
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25D0 connecting" });
12859
+ case "reconnecting":
12860
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25D1 reconnecting" });
12861
+ case "disconnected":
12862
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB disconnected" });
12863
+ case "failed":
12864
+ return /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 failed" });
12865
+ default:
12866
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: status });
12867
+ }
12868
+ }
12869
+ function McpPicker({
12870
+ items,
12871
+ selected,
12872
+ busy = false,
12873
+ hint
12874
+ }) {
12875
+ const { stdout } = useStdout();
12876
+ const rows = stdout?.rows ?? 24;
12877
+ const maxVisible = Math.max(4, rows - CHROME_ROWS3);
12878
+ const total = items.length;
12879
+ const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12880
+ const windowEnd = Math.min(windowStart + maxVisible, total);
12881
+ const above = windowStart;
12882
+ const below = total - windowEnd;
12883
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
12884
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "MCP Servers" }),
12885
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 select \xB7 Enter/\u2190/\u2192 toggle enable \xB7 r restart \xB7 Esc close" }),
12886
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: busy ? "Loading servers\u2026" : "No MCP servers configured." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12887
+ above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12888
+ items.slice(windowStart, windowEnd).map((item, i) => {
12889
+ const index = windowStart + i;
12890
+ const focused = index === selected;
12891
+ const marker = focused ? "\u203A" : " ";
12892
+ return /* @__PURE__ */ jsxs(Text, { color: focused ? "cyan" : void 0, wrap: "truncate-end", children: [
12893
+ marker,
12894
+ " ",
12895
+ statusBadge(item.status, item.enabled),
12896
+ " ",
12897
+ /* @__PURE__ */ jsx(Text, { bold: true, children: item.name.padEnd(18) }),
12898
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12899
+ item.transport.padEnd(14),
12900
+ item.toolCount > 0 ? `${item.toolCount} tools` : "",
12901
+ item.lazy ? " lazy" : ""
12902
+ ] })
12903
+ ] }, item.name);
12904
+ }),
12905
+ below > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${below} more` }) : null
12906
+ ] }) }),
12907
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
12908
+ ] });
12909
+ }
12910
+ var CHROME_ROWS4 = 17;
12603
12911
  function PluginPicker({
12604
12912
  items,
12605
12913
  selected,
@@ -12608,7 +12916,7 @@ function PluginPicker({
12608
12916
  }) {
12609
12917
  const { stdout } = useStdout();
12610
12918
  const rows = stdout?.rows ?? 24;
12611
- const maxVisible = Math.max(4, rows - CHROME_ROWS2);
12919
+ const maxVisible = Math.max(4, rows - CHROME_ROWS4);
12612
12920
  const total = items.length;
12613
12921
  const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
12614
12922
  const windowEnd = Math.min(windowStart + maxVisible, total);
@@ -12654,6 +12962,334 @@ function PluginPicker({
12654
12962
  hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
12655
12963
  ] });
12656
12964
  }
12965
+ var CHROME_ROWS5 = 15;
12966
+ function formatCount(noun, count) {
12967
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
12968
+ }
12969
+ function toolActionLabel(enabled) {
12970
+ return enabled ? "Enter disables this tool" : "Enter re-enables this tool";
12971
+ }
12972
+ function statusBadge2(enabled) {
12973
+ return enabled ? /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF active" }) : /* @__PURE__ */ jsx(Text, { color: "red", children: "\u25CB disabled" });
12974
+ }
12975
+ function rwBadge(mutating) {
12976
+ return mutating ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "mut" }) : /* @__PURE__ */ jsx(Text, { color: "cyan", children: "ro" });
12977
+ }
12978
+ function displayCategory(cat) {
12979
+ if (!cat || cat.trim() === "") return "Other";
12980
+ return cat.charAt(0).toUpperCase() + cat.slice(1);
12981
+ }
12982
+ function buildRows(items) {
12983
+ const rows = [];
12984
+ let lastCat = "";
12985
+ for (let i = 0; i < items.length; i++) {
12986
+ const item = items[i];
12987
+ const cat = displayCategory(item.category);
12988
+ if (cat !== lastCat) {
12989
+ lastCat = cat;
12990
+ rows.push({ type: "header", category: cat });
12991
+ }
12992
+ rows.push({ type: "item", item, index: i });
12993
+ }
12994
+ return rows;
12995
+ }
12996
+ function windowRows(rows, focus, max) {
12997
+ if (rows.length <= max) {
12998
+ return { rows, start: 0, end: rows.length, contextHeader: null };
12999
+ }
13000
+ let start = focus - Math.floor(max / 2);
13001
+ if (start < 0) start = 0;
13002
+ let end = start + max;
13003
+ if (end > rows.length) {
13004
+ end = rows.length;
13005
+ start = end - max;
13006
+ }
13007
+ let contextHeader = null;
13008
+ if (start > 0) {
13009
+ const first = rows[start];
13010
+ if (first && first.type === "item") {
13011
+ for (let i = start - 1; i >= 0; i--) {
13012
+ const r = rows[i];
13013
+ if (r && r.type === "header") {
13014
+ contextHeader = r.category;
13015
+ break;
13016
+ }
13017
+ }
13018
+ }
13019
+ }
13020
+ return { rows: rows.slice(start, end), start, end, contextHeader };
13021
+ }
13022
+ function ToolsPicker({
13023
+ items,
13024
+ selected,
13025
+ busy = false,
13026
+ hint,
13027
+ filter
13028
+ }) {
13029
+ const { stdout } = useStdout();
13030
+ const termRows = stdout?.rows ?? 24;
13031
+ const maxVisible = Math.max(6, termRows - CHROME_ROWS5);
13032
+ const total = items.length;
13033
+ let visibleItems;
13034
+ let visibleSelected;
13035
+ const filterActive = Boolean(filter && filter.length > 0);
13036
+ if (filterActive) {
13037
+ const q = filter.toLowerCase();
13038
+ visibleItems = items.filter(
13039
+ (it) => it.name.toLowerCase().includes(q) || it.owner.toLowerCase().includes(q) || it.description.toLowerCase().includes(q)
13040
+ );
13041
+ const rawSelected = items[selected];
13042
+ visibleSelected = rawSelected ? visibleItems.indexOf(rawSelected) : 0;
13043
+ if (visibleSelected < 0) visibleSelected = 0;
13044
+ } else {
13045
+ visibleItems = items;
13046
+ visibleSelected = selected;
13047
+ }
13048
+ const rows = buildRows(visibleItems);
13049
+ const enabledCount = items.filter((item) => item.enabled).length;
13050
+ const disabledCount = total - enabledCount;
13051
+ const selectedItem = visibleItems[visibleSelected];
13052
+ let displayRows;
13053
+ let hiddenAbove = 0;
13054
+ let hiddenBelow = 0;
13055
+ let contextHeader = null;
13056
+ if (filterActive) {
13057
+ displayRows = rows;
13058
+ } else {
13059
+ const selectedRowIdx = rows.findIndex(
13060
+ (r) => r.type === "item" && r.index === visibleSelected
13061
+ );
13062
+ const win = windowRows(
13063
+ rows,
13064
+ selectedRowIdx < 0 ? 0 : selectedRowIdx,
13065
+ maxVisible
13066
+ );
13067
+ displayRows = win.rows;
13068
+ hiddenAbove = win.start;
13069
+ hiddenBelow = rows.length - win.end;
13070
+ contextHeader = win.contextHeader;
13071
+ }
13072
+ const hasFilter = filterActive;
13073
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [
13074
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "\u2501\u2501 Tools \u2501\u2501" }),
13075
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: hasFilter ? `Filter: ${filter} (${visibleItems.length} match${visibleItems.length === 1 ? "" : "es"}) \xB7 Backspace edit \xB7 Esc clear` : `\u2191/\u2193 select \xB7 type to filter \xB7 Enter toggles \xB7 Esc close \xB7 ${formatCount("active", enabledCount)} / ${formatCount("disabled", disabledCount)}` }),
13076
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Disabled tools stay listed here so you can select them and re-enable them later." }),
13077
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: busy ? "Loading tools\u2026" : "No tools registered." }) : visibleItems.length === 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13078
+ 'No tools match "',
13079
+ filter,
13080
+ '".'
13081
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
13082
+ hiddenAbove > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${hiddenAbove} more` }) : null,
13083
+ contextHeader && !hasFilter ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13084
+ " ",
13085
+ contextHeader
13086
+ ] }) : null,
13087
+ displayRows.map((row) => {
13088
+ if (row.type === "header") {
13089
+ return /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13090
+ " ",
13091
+ row.category
13092
+ ] }, `cat-${row.category}`);
13093
+ }
13094
+ const { item, index } = row;
13095
+ const focused = index === visibleSelected;
13096
+ return /* @__PURE__ */ jsxs(
13097
+ Text,
13098
+ {
13099
+ inverse: focused,
13100
+ ...focused ? { color: "green" } : {},
13101
+ wrap: "truncate-end",
13102
+ children: [
13103
+ focused ? "\u203A " : " ",
13104
+ statusBadge2(item.enabled),
13105
+ " ",
13106
+ /* @__PURE__ */ jsx(Text, { bold: true, children: item.name.padEnd(20) }),
13107
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13108
+ `[${item.owner}]`.padEnd(22),
13109
+ rwBadge(item.mutating),
13110
+ " ",
13111
+ " ",
13112
+ item.permission.padEnd(6),
13113
+ " ",
13114
+ " ",
13115
+ /* @__PURE__ */ jsxs(Text, { color: item.descMode === "simple" ? "yellow" : void 0, children: [
13116
+ "desc:",
13117
+ item.descMode
13118
+ ] })
13119
+ ] })
13120
+ ]
13121
+ },
13122
+ item.name
13123
+ );
13124
+ }),
13125
+ hiddenBelow > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${hiddenBelow} more` }) : null
13126
+ ] }) }),
13127
+ selectedItem ? /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
13128
+ /* @__PURE__ */ jsxs(Text, { children: [
13129
+ /* @__PURE__ */ jsx(Text, { color: selectedItem.enabled ? "green" : "red", children: toolActionLabel(selectedItem.enabled) }),
13130
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${selectedItem.mutating ? "Mutating" : "Read-only"} \xB7 permission: ${selectedItem.permission} \xB7 descriptions: ${selectedItem.descMode}` })
13131
+ ] }),
13132
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate-end", children: selectedItem.description || "No description provided." })
13133
+ ] }) : null,
13134
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13135
+ ] });
13136
+ }
13137
+ var CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
13138
+ var CHROME_ROWS6 = 7;
13139
+ function normalizeQuery(raw) {
13140
+ return raw.trim().toLowerCase();
13141
+ }
13142
+ function buildRows2(entries) {
13143
+ const rows = [];
13144
+ let lastCat = "";
13145
+ for (let i = 0; i < entries.length; i++) {
13146
+ const e = entries[i];
13147
+ const cat = e.category || "App";
13148
+ if (cat !== lastCat) {
13149
+ lastCat = cat;
13150
+ rows.push({ type: "header", category: cat });
13151
+ }
13152
+ rows.push({ type: "item", entry: e, index: i });
13153
+ }
13154
+ return rows;
13155
+ }
13156
+ function windowRows2(rows, focus, max) {
13157
+ if (rows.length <= max) {
13158
+ return { rows, start: 0, end: rows.length, contextHeader: null };
13159
+ }
13160
+ let start = focus - Math.floor(max / 2);
13161
+ if (start < 0) start = 0;
13162
+ let end = start + max;
13163
+ if (end > rows.length) {
13164
+ end = rows.length;
13165
+ start = end - max;
13166
+ }
13167
+ let contextHeader = null;
13168
+ if (start > 0) {
13169
+ const first = rows[start];
13170
+ if (first && first.type === "item") {
13171
+ for (let i = start - 1; i >= 0; i--) {
13172
+ const r = rows[i];
13173
+ if (r && r.type === "header") {
13174
+ contextHeader = r.category;
13175
+ break;
13176
+ }
13177
+ }
13178
+ }
13179
+ }
13180
+ return { rows: rows.slice(start, end), start, end, contextHeader };
13181
+ }
13182
+ function HelpPanel({
13183
+ entries,
13184
+ filter,
13185
+ selected,
13186
+ hint
13187
+ }) {
13188
+ const { stdout } = useStdout();
13189
+ const termRows = stdout?.rows ?? 24;
13190
+ const maxVisible = Math.max(6, termRows - CHROME_ROWS6);
13191
+ const query = normalizeQuery(filter);
13192
+ const filtered = query ? entries.filter(
13193
+ (e) => e.name.toLowerCase().includes(query) || e.description.toLowerCase().includes(query) || e.category.toLowerCase().includes(query) || (e.aliases ?? []).some((a) => a.toLowerCase().includes(query))
13194
+ ) : entries;
13195
+ filtered.sort((a, b) => {
13196
+ const ai = CATEGORY_ORDER.indexOf(a.category);
13197
+ const bi = CATEGORY_ORDER.indexOf(b.category);
13198
+ if (ai !== bi) return ai - bi;
13199
+ return a.name.localeCompare(b.name);
13200
+ });
13201
+ const rows = buildRows2(filtered);
13202
+ const focus = Math.min(selected, Math.max(0, filtered.length - 1));
13203
+ const selectedRowIdx = rows.findIndex((r) => r.type === "item" && r.index === focus);
13204
+ const win = windowRows2(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, maxVisible);
13205
+ const { rows: visible, start, end, contextHeader } = win;
13206
+ const hiddenAbove = start;
13207
+ const hiddenBelow = rows.length - end;
13208
+ const hasFilter = query.length > 0;
13209
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
13210
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u2501\u2501 Help / Command Palette \u2501\u2501" }),
13211
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: hasFilter ? `Filter: ${filter} (${filtered.length} match${filtered.length === 1 ? "" : "es"})` : "\u2191/\u2193 navigate \xB7 Enter run \xB7 Tab fill \xB7 type to filter \xB7 Esc close" }),
13212
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13213
+ 'No commands match "',
13214
+ filter,
13215
+ '".'
13216
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
13217
+ hiddenAbove > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${hiddenAbove} more` }) : null,
13218
+ contextHeader && !hasFilter ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13219
+ " ",
13220
+ contextHeader
13221
+ ] }) : null,
13222
+ visible.map((row) => {
13223
+ if (row.type === "header") {
13224
+ return /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
13225
+ " ",
13226
+ row.category
13227
+ ] }, `cat-${row.category}`);
13228
+ }
13229
+ const { entry, index } = row;
13230
+ const isSelected = index === focus;
13231
+ return /* @__PURE__ */ jsxs(
13232
+ Text,
13233
+ {
13234
+ inverse: isSelected,
13235
+ ...isSelected ? { color: "cyan" } : {},
13236
+ wrap: "truncate-end",
13237
+ children: [
13238
+ isSelected ? "\u203A " : " ",
13239
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
13240
+ "/",
13241
+ entry.name
13242
+ ] }),
13243
+ entry.aliases && entry.aliases.length > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13244
+ " (",
13245
+ entry.aliases.map((a) => `/${a}`).join(", "),
13246
+ ")"
13247
+ ] }) : null,
13248
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13249
+ " \u2014 ",
13250
+ entry.description
13251
+ ] }),
13252
+ entry.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13253
+ " ",
13254
+ entry.argsHint
13255
+ ] }) : null
13256
+ ]
13257
+ },
13258
+ entry.name
13259
+ );
13260
+ }),
13261
+ hiddenBelow > 0 && !hasFilter ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${hiddenBelow} more` }) : null
13262
+ ] }) }),
13263
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13264
+ ] });
13265
+ }
13266
+ function ShadowPanel({
13267
+ shadow,
13268
+ hint
13269
+ }) {
13270
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
13271
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "\u2501\u2501 Shadow Agent \u2501\u2501" }),
13272
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "s start \xB7 t stop \xB7 i cycle interval \xB7 m cycle model \xB7 Esc close" }),
13273
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
13274
+ /* @__PURE__ */ jsxs(Box, { children: [
13275
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Status: " }),
13276
+ shadow.running ? /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF running" }) : /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u25CB stopped" }),
13277
+ shadow.activeId ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${shadow.activeId.slice(0, 12)}\u2026` }) : null
13278
+ ] }),
13279
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
13280
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Model: " }),
13281
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: shadow.model })
13282
+ ] }),
13283
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
13284
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Interval: " }),
13285
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: shadow.intervalMs >= 1e3 ? `${shadow.intervalMs / 1e3}s` : `${shadow.intervalMs}ms` }),
13286
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (min 5s)" })
13287
+ ] }),
13288
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: shadow.running ? "Press t to stop the Shadow Agent" : "Press s to start the Shadow Agent" }) })
13289
+ ] }),
13290
+ hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) }) : null
13291
+ ] });
13292
+ }
12657
13293
  function ProcessListMonitor() {
12658
13294
  const [, setTick] = useState(0);
12659
13295
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -13336,7 +13972,7 @@ function SlashMenu({ query, matches, selected }) {
13336
13972
  rows.push({ type: "item", match: m, index: i });
13337
13973
  }
13338
13974
  const selectedRowIdx = rows.findIndex((r) => r.type === "item" && r.index === selected);
13339
- const visible = windowRows(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, MAX_VISIBLE_ITEMS);
13975
+ const visible = windowRows3(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, MAX_VISIBLE_ITEMS);
13340
13976
  const hiddenAbove = visible.start;
13341
13977
  const hiddenBelow = rows.length - visible.end;
13342
13978
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
@@ -13388,7 +14024,7 @@ function SlashMenu({ query, matches, selected }) {
13388
14024
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500 \u2191\u2193 nav \xB7 Enter run \xB7 Tab fill \xB7 Esc close" })
13389
14025
  ] });
13390
14026
  }
13391
- function windowRows(rows, focus, max) {
14027
+ function windowRows3(rows, focus, max) {
13392
14028
  if (rows.length <= max) {
13393
14029
  return { rows, start: 0, end: rows.length, contextHeader: null };
13394
14030
  }
@@ -13921,6 +14557,11 @@ function createInitialState(options) {
13921
14557
  hint: void 0
13922
14558
  },
13923
14559
  pluginPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
14560
+ mcpPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0 },
14561
+ toolsPicker: { open: false, items: [], selected: 0, busy: false, hint: void 0, filter: void 0 },
14562
+ brainPanel: { open: false, riskLevel: "medium", log: [], selected: 0, hint: void 0 },
14563
+ helpPanel: { open: false, entries: [], selected: 0, filter: "", hint: void 0 },
14564
+ shadowPanel: { open: false, shadow: { activeId: null, running: false, model: "", intervalMs: 3e4 }, hint: void 0 },
13924
14565
  authPanel: AUTH_PANEL_INITIAL,
13925
14566
  projectPicker: {
13926
14567
  open: false,
@@ -15474,6 +16115,145 @@ function usePickerKeys(host) {
15474
16115
  }
15475
16116
  return true;
15476
16117
  }
16118
+ if (state.mcpPicker.open) {
16119
+ if (key.escape) {
16120
+ dispatch({ type: "mcpPickerClose" });
16121
+ return true;
16122
+ }
16123
+ if (key.mouse?.kind === "wheel") {
16124
+ dispatch({ type: "mcpPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16125
+ return true;
16126
+ }
16127
+ if (key.upArrow) {
16128
+ dispatch({ type: "mcpPickerMove", delta: -1 });
16129
+ return true;
16130
+ }
16131
+ if (key.downArrow) {
16132
+ dispatch({ type: "mcpPickerMove", delta: 1 });
16133
+ return true;
16134
+ }
16135
+ if (input === "r" || input === "R") {
16136
+ void host.onMcpPickerRestart?.();
16137
+ return true;
16138
+ }
16139
+ if (key.leftArrow || key.rightArrow || isEnter) {
16140
+ if (debouncedEnter(host)) return true;
16141
+ void host.onMcpPickerToggle?.();
16142
+ return true;
16143
+ }
16144
+ return true;
16145
+ }
16146
+ if (state.toolsPicker.open) {
16147
+ if (key.escape) {
16148
+ if (state.toolsPicker.filter) {
16149
+ dispatch({ type: "toolsPickerFilter", filter: "" });
16150
+ } else {
16151
+ dispatch({ type: "toolsPickerClose" });
16152
+ }
16153
+ return true;
16154
+ }
16155
+ if (key.mouse?.kind === "wheel") {
16156
+ dispatch({ type: "toolsPickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16157
+ return true;
16158
+ }
16159
+ if (key.upArrow) {
16160
+ dispatch({ type: "toolsPickerMove", delta: -1 });
16161
+ return true;
16162
+ }
16163
+ if (key.downArrow) {
16164
+ dispatch({ type: "toolsPickerMove", delta: 1 });
16165
+ return true;
16166
+ }
16167
+ if (key.leftArrow || key.rightArrow || isEnter) {
16168
+ if (debouncedEnter(host)) return true;
16169
+ void host.onToolsPickerToggle?.();
16170
+ return true;
16171
+ }
16172
+ if (input && input.length === 1 && input.charCodeAt(0) >= 32 && input.charCodeAt(0) < 127) {
16173
+ dispatch({ type: "toolsPickerFilter", filter: (state.toolsPicker.filter ?? "") + input });
16174
+ return true;
16175
+ }
16176
+ if (key.backspace) {
16177
+ const cur = state.toolsPicker.filter ?? "";
16178
+ dispatch({ type: "toolsPickerFilter", filter: cur.length > 0 ? cur.slice(0, -1) : "" });
16179
+ return true;
16180
+ }
16181
+ return true;
16182
+ }
16183
+ if (state.helpPanel.open) {
16184
+ if (key.escape) {
16185
+ dispatch({ type: "helpClose" });
16186
+ return true;
16187
+ }
16188
+ if (key.mouse?.kind === "wheel") {
16189
+ dispatch({ type: "helpMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16190
+ return true;
16191
+ }
16192
+ if (key.upArrow) {
16193
+ dispatch({ type: "helpMove", delta: -1 });
16194
+ return true;
16195
+ }
16196
+ if (key.downArrow) {
16197
+ dispatch({ type: "helpMove", delta: 1 });
16198
+ return true;
16199
+ }
16200
+ if (key.backspace && state.helpPanel.filter) {
16201
+ dispatch({ type: "helpFilter", filter: state.helpPanel.filter.slice(0, -1) });
16202
+ return true;
16203
+ }
16204
+ if (input && input.length === 1 && input.charCodeAt(0) >= 32 && input.charCodeAt(0) < 127) {
16205
+ dispatch({ type: "helpFilter", filter: state.helpPanel.filter + input });
16206
+ return true;
16207
+ }
16208
+ if (isEnter) {
16209
+ if (debouncedEnter(host)) return true;
16210
+ host.onHelpPanelEnter?.();
16211
+ return true;
16212
+ }
16213
+ return true;
16214
+ }
16215
+ if (state.brainPanel.open) {
16216
+ if (key.escape) {
16217
+ dispatch({ type: "brainClose" });
16218
+ return true;
16219
+ }
16220
+ if (key.mouse?.kind === "wheel") {
16221
+ dispatch({ type: "brainMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
16222
+ return true;
16223
+ }
16224
+ if (key.upArrow) {
16225
+ dispatch({ type: "brainMove", delta: -1 });
16226
+ return true;
16227
+ }
16228
+ if (key.downArrow) {
16229
+ dispatch({ type: "brainMove", delta: 1 });
16230
+ return true;
16231
+ }
16232
+ if (key.leftArrow) {
16233
+ host.onBrainRiskChange?.(-1);
16234
+ return true;
16235
+ }
16236
+ if (key.rightArrow) {
16237
+ host.onBrainRiskChange?.(1);
16238
+ return true;
16239
+ }
16240
+ return true;
16241
+ }
16242
+ if (state.shadowPanel.open) {
16243
+ if (key.escape) {
16244
+ dispatch({ type: "shadowClose" });
16245
+ return true;
16246
+ }
16247
+ if (input === "s" || input === "S") {
16248
+ void host.onShadowStart?.();
16249
+ return true;
16250
+ }
16251
+ if (input === "t" || input === "T") {
16252
+ void host.onShadowStop?.();
16253
+ return true;
16254
+ }
16255
+ return true;
16256
+ }
15477
16257
  if (state.statuslinePicker.open) {
15478
16258
  if (key.escape) {
15479
16259
  dispatch({ type: "statuslineClose" });
@@ -16634,6 +17414,12 @@ function createPanelOpenDispatcher(deps) {
16634
17414
  case "pluginPickerOpen":
16635
17415
  dispatch({ type: "pluginPickerOpen" });
16636
17416
  return true;
17417
+ case "mcpPickerOpen":
17418
+ dispatch({ type: "mcpPickerOpen" });
17419
+ return true;
17420
+ case "toolsPickerOpen":
17421
+ dispatch({ type: "toolsPickerOpen" });
17422
+ return true;
16637
17423
  case "projectPickerOpen":
16638
17424
  void openProjectPicker();
16639
17425
  return true;
@@ -16646,6 +17432,24 @@ function createPanelOpenDispatcher(deps) {
16646
17432
  return true;
16647
17433
  }
16648
17434
  return false;
17435
+ case "brainOpen":
17436
+ if (deps.openBrainPanel) {
17437
+ void deps.openBrainPanel();
17438
+ return true;
17439
+ }
17440
+ return false;
17441
+ case "shadowOpen":
17442
+ if (deps.openShadowPanel) {
17443
+ void deps.openShadowPanel();
17444
+ return true;
17445
+ }
17446
+ return false;
17447
+ case "helpOpen":
17448
+ if (deps.openHelpPanel) {
17449
+ void deps.openHelpPanel();
17450
+ return true;
17451
+ }
17452
+ return false;
16649
17453
  case "toggleMonitor":
16650
17454
  dispatch({ type: "toggleMonitor" });
16651
17455
  return true;
@@ -16761,7 +17565,7 @@ function renderList3() {
16761
17565
  }
16762
17566
 
16763
17567
  // src/slash-command-search.ts
16764
- var CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
17568
+ var CATEGORY_ORDER2 = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
16765
17569
  function buildSlashCommandMatches(entries, rawQuery) {
16766
17570
  const query = normalizeSlashQuery(rawQuery);
16767
17571
  const matches = [];
@@ -16772,7 +17576,7 @@ function buildSlashCommandMatches(entries, rawQuery) {
16772
17576
  }
16773
17577
  return matches.sort((a, b) => {
16774
17578
  if (a.rank !== b.rank) return a.rank - b.rank;
16775
- const catDiff = CATEGORY_ORDER.indexOf(a.category) - CATEGORY_ORDER.indexOf(b.category);
17579
+ const catDiff = CATEGORY_ORDER2.indexOf(a.category) - CATEGORY_ORDER2.indexOf(b.category);
16776
17580
  if (catDiff !== 0) return catDiff;
16777
17581
  return a.name.localeCompare(b.name);
16778
17582
  }).map(({ rank: _rank, ...match }) => match);
@@ -16969,24 +17773,38 @@ function rehydrateHistory(messages, startId, toolCalls) {
16969
17773
  outputTokens: tc.outputTokens,
16970
17774
  outputLines: tc.outputLines
16971
17775
  });
17776
+ const pushAssistantText = (parts) => {
17777
+ const text = parts.join("").trim();
17778
+ if (text) entries.push({ id: nextId++, kind: "assistant", text });
17779
+ parts.length = 0;
17780
+ };
16972
17781
  for (const msg of messages) {
16973
17782
  if (msg.role === "system") continue;
16974
- const text = textOf(msg).trim();
16975
17783
  if (msg.role === "user") {
17784
+ const text = textOf(msg).trim();
16976
17785
  if (text) entries.push({ id: nextId++, kind: "user", text });
16977
17786
  continue;
16978
17787
  }
16979
17788
  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);
17789
+ if (!Array.isArray(msg.content)) {
17790
+ const text = textOf(msg).trim();
17791
+ if (text) entries.push({ id: nextId++, kind: "assistant", text });
17792
+ continue;
17793
+ }
17794
+ const textParts = [];
17795
+ for (const block of msg.content) {
17796
+ if (block.type === "text") {
17797
+ textParts.push(block.text);
17798
+ continue;
16988
17799
  }
17800
+ if (block.type !== "tool_use") continue;
17801
+ pushAssistantText(textParts);
17802
+ const tc = toolCallsById.get(block.id);
17803
+ if (!tc) continue;
17804
+ entries.push(toolEntryFor(tc));
17805
+ consumed.add(block.id);
16989
17806
  }
17807
+ pushAssistantText(textParts);
16990
17808
  }
16991
17809
  }
16992
17810
  if (toolCalls) {
@@ -17086,6 +17904,16 @@ function App({
17086
17904
  saveSettings,
17087
17905
  getPluginItems,
17088
17906
  onPluginToggle,
17907
+ getMcpServers,
17908
+ onMcpToggle,
17909
+ onMcpRestart,
17910
+ getToolsItems,
17911
+ onToolToggle,
17912
+ getBrainData,
17913
+ onBrainRiskLevel,
17914
+ getShadowData,
17915
+ onShadowStart,
17916
+ onShadowStop,
17089
17917
  authHost,
17090
17918
  predictNext,
17091
17919
  onSuggestionsParsed,
@@ -17262,6 +18090,71 @@ function App({
17262
18090
  });
17263
18091
  const { openPromptPicker } = usePromptPicker({ projectRoot, dispatch });
17264
18092
  const { openModePicker } = useModePicker({ dispatch, getModes });
18093
+ const openBrainPanel = React5.useCallback(() => {
18094
+ if (!getBrainData) return;
18095
+ const data = getBrainData();
18096
+ dispatch({ type: "brainOpen", riskLevel: data.riskLevel, log: data.log });
18097
+ }, [getBrainData]);
18098
+ const changeBrainRisk = React5.useCallback(
18099
+ (delta) => {
18100
+ dispatch({ type: "brainRiskChange", delta });
18101
+ },
18102
+ []
18103
+ );
18104
+ const prevRiskRef = React5.useRef(state.brainPanel.riskLevel);
18105
+ React5.useEffect(() => {
18106
+ if (!state.brainPanel.open) return;
18107
+ const current = state.brainPanel.riskLevel;
18108
+ if (current !== prevRiskRef.current && onBrainRiskLevel) {
18109
+ prevRiskRef.current = current;
18110
+ const err = onBrainRiskLevel(current);
18111
+ if (err) dispatch({ type: "brainHint", text: err });
18112
+ else dispatch({ type: "brainHint", text: `Risk ceiling \u2192 ${current.toUpperCase()}` });
18113
+ }
18114
+ }, [state.brainPanel.riskLevel, state.brainPanel.open, onBrainRiskLevel]);
18115
+ const openShadowPanel = React5.useCallback(() => {
18116
+ if (!getShadowData) return;
18117
+ const data = getShadowData();
18118
+ dispatch({ type: "shadowOpen", shadow: data });
18119
+ }, [getShadowData]);
18120
+ const handleShadowStart = React5.useCallback(async () => {
18121
+ if (!onShadowStart) return;
18122
+ dispatch({ type: "shadowHint", text: "Starting Shadow Agent\u2026" });
18123
+ try {
18124
+ const err = await onShadowStart();
18125
+ if (err) dispatch({ type: "shadowHint", text: err });
18126
+ else {
18127
+ if (getShadowData) dispatch({ type: "shadowUpdate", shadow: getShadowData() });
18128
+ dispatch({ type: "shadowHint", text: "Shadow Agent started." });
18129
+ }
18130
+ } catch (e) {
18131
+ dispatch({ type: "shadowHint", text: `Failed to start: ${toErrorMessage(e)}` });
18132
+ }
18133
+ }, [onShadowStart, getShadowData]);
18134
+ const handleShadowStop = React5.useCallback(async () => {
18135
+ if (!onShadowStop) return;
18136
+ dispatch({ type: "shadowHint", text: "Stopping Shadow Agent\u2026" });
18137
+ try {
18138
+ const err = await onShadowStop();
18139
+ if (err) dispatch({ type: "shadowHint", text: err });
18140
+ else {
18141
+ if (getShadowData) dispatch({ type: "shadowUpdate", shadow: getShadowData() });
18142
+ dispatch({ type: "shadowHint", text: "Shadow Agent stopped." });
18143
+ }
18144
+ } catch (e) {
18145
+ dispatch({ type: "shadowHint", text: `Failed to stop: ${toErrorMessage(e)}` });
18146
+ }
18147
+ }, [onShadowStop, getShadowData]);
18148
+ const openHelpPanel = React5.useCallback(() => {
18149
+ const entries = Array.from(slashRegistry.listWithOwner()).map(({ cmd, owner }) => ({
18150
+ name: owner === "core" ? cmd.name : `${owner}:${cmd.name}`,
18151
+ description: cmd.description,
18152
+ category: cmd.category ?? "App",
18153
+ aliases: cmd.aliases,
18154
+ argsHint: cmd.argsHint
18155
+ }));
18156
+ dispatch({ type: "helpOpen", entries });
18157
+ }, []);
17265
18158
  useStatuslineHiddenSync({
17266
18159
  pickerOpen: state.statuslinePicker.open,
17267
18160
  pickerHidden: state.statuslinePicker.hiddenItems,
@@ -17318,6 +18211,7 @@ function App({
17318
18211
  const streamSegmentsRef = useRef([]);
17319
18212
  const pendingDeltaRef = useRef("");
17320
18213
  const flushTimerRef = useRef(null);
18214
+ const assistantCommittedThisRunRef = useRef(false);
17321
18215
  const stateRef = useRef(state);
17322
18216
  stateRef.current = state;
17323
18217
  const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
@@ -17346,7 +18240,7 @@ function App({
17346
18240
  [statuslineHiddenForPicker]
17347
18241
  );
17348
18242
  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;
18243
+ const pickerOverlayOpen = state.modelPicker.open || state.autonomyPicker.open || state.modePicker.open || state.designPicker.open || state.settingsPicker.open || state.projectPicker.open || state.slashPicker.open || state.statuslinePicker.open || state.pluginPicker.open || state.mcpPicker.open || state.toolsPicker.open || state.brainPanel.open || state.helpPanel.open || state.shadowPanel.open || state.fKeyPicker.open || state.authPanel.open || state.picker.open;
17350
18244
  const mouseTrackingOn = mouseMode || pickerOverlayOpen;
17351
18245
  const mouseWrittenRef = useRef(false);
17352
18246
  useEffect(() => {
@@ -18161,7 +19055,10 @@ function App({
18161
19055
  openProjectPicker,
18162
19056
  openStatuslinePicker,
18163
19057
  openAuthPanel: authPanelController.openAuthPanel,
18164
- openModePicker
19058
+ openModePicker,
19059
+ openBrainPanel,
19060
+ openShadowPanel,
19061
+ openHelpPanel
18165
19062
  });
18166
19063
  onPanelOpen.current = dispatcher;
18167
19064
  return () => {
@@ -18173,7 +19070,10 @@ function App({
18173
19070
  openProjectPicker,
18174
19071
  openStatuslinePicker,
18175
19072
  authPanelController.openAuthPanel,
18176
- openModePicker
19073
+ openModePicker,
19074
+ openBrainPanel,
19075
+ openShadowPanel,
19076
+ openHelpPanel
18177
19077
  ]);
18178
19078
  useEffect(() => {
18179
19079
  if (!state.sessionsPanelOpen || !getLiveSessions) return void 0;
@@ -18259,6 +19159,73 @@ function App({
18259
19159
  });
18260
19160
  }
18261
19161
  }, [onPluginToggle]);
19162
+ const refreshMcpPicker = React5.useCallback(() => {
19163
+ if (!getMcpServers) return;
19164
+ dispatch({ type: "mcpPickerSetItems", items: getMcpServers() });
19165
+ }, [getMcpServers]);
19166
+ useEffect(() => {
19167
+ if (!state.mcpPicker.open) return;
19168
+ refreshMcpPicker();
19169
+ }, [state.mcpPicker.open, refreshMcpPicker]);
19170
+ const toggleSelectedMcpServer = React5.useCallback(async () => {
19171
+ if (!onMcpToggle) return;
19172
+ const selected = stateRef.current.mcpPicker.items[stateRef.current.mcpPicker.selected];
19173
+ if (!selected) return;
19174
+ dispatch({ type: "mcpPickerBusy", busy: true });
19175
+ try {
19176
+ const result = await onMcpToggle(selected.name);
19177
+ dispatch({ type: "mcpPickerSetItems", items: result.items });
19178
+ dispatch({ type: "mcpPickerHint", text: result.error ?? result.message });
19179
+ } catch (err) {
19180
+ dispatch({ type: "mcpPickerBusy", busy: false });
19181
+ dispatch({
19182
+ type: "mcpPickerHint",
19183
+ text: `Failed to toggle ${selected.name}: ${toErrorMessage(err)}`
19184
+ });
19185
+ }
19186
+ }, [onMcpToggle]);
19187
+ const restartSelectedMcpServer = React5.useCallback(async () => {
19188
+ if (!onMcpRestart) return;
19189
+ const selected = stateRef.current.mcpPicker.items[stateRef.current.mcpPicker.selected];
19190
+ if (!selected) return;
19191
+ dispatch({ type: "mcpPickerBusy", busy: true });
19192
+ try {
19193
+ const result = await onMcpRestart(selected.name);
19194
+ dispatch({ type: "mcpPickerSetItems", items: result.items });
19195
+ dispatch({ type: "mcpPickerHint", text: result.error ?? result.message });
19196
+ } catch (err) {
19197
+ dispatch({ type: "mcpPickerBusy", busy: false });
19198
+ dispatch({
19199
+ type: "mcpPickerHint",
19200
+ text: `Failed to restart ${selected.name}: ${toErrorMessage(err)}`
19201
+ });
19202
+ }
19203
+ }, [onMcpRestart]);
19204
+ const refreshToolsPicker = React5.useCallback(() => {
19205
+ if (!getToolsItems) return;
19206
+ dispatch({ type: "toolsPickerSetItems", items: getToolsItems() });
19207
+ }, [getToolsItems]);
19208
+ useEffect(() => {
19209
+ if (!state.toolsPicker.open) return;
19210
+ refreshToolsPicker();
19211
+ }, [state.toolsPicker.open, refreshToolsPicker]);
19212
+ const toggleSelectedTool = React5.useCallback(async () => {
19213
+ if (!onToolToggle) return;
19214
+ const selected = stateRef.current.toolsPicker.items[stateRef.current.toolsPicker.selected];
19215
+ if (!selected) return;
19216
+ dispatch({ type: "toolsPickerBusy", busy: true });
19217
+ try {
19218
+ const result = await onToolToggle(selected.name);
19219
+ dispatch({ type: "toolsPickerSetItems", items: result.items });
19220
+ dispatch({ type: "toolsPickerHint", text: result.error ?? result.message });
19221
+ } catch (err) {
19222
+ dispatch({ type: "toolsPickerBusy", busy: false });
19223
+ dispatch({
19224
+ type: "toolsPickerHint",
19225
+ text: `Failed to toggle ${selected.name}: ${toErrorMessage(err)}`
19226
+ });
19227
+ }
19228
+ }, [onToolToggle]);
18262
19229
  const autoSubmitStreakRef = useRef(0);
18263
19230
  const autoSubmitCapWarnedRef = useRef(false);
18264
19231
  const [nextStepsRecheck, setNextStepsRecheck] = useState(0);
@@ -18935,12 +19902,15 @@ Available chords:
18935
19902
  for (const seg of segments) {
18936
19903
  if (seg.text.trim()) {
18937
19904
  dispatch({ type: "addEntry", entry: { kind: seg.kind, text: seg.text } });
19905
+ if (seg.kind === "assistant") assistantCommittedThisRunRef.current = true;
18938
19906
  }
18939
19907
  }
18940
19908
  if (segments.length === 0 && text.trim()) {
18941
19909
  dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
19910
+ assistantCommittedThisRunRef.current = true;
18942
19911
  } else if (segments.length === 0 && fallbackText.trim()) {
18943
19912
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: fallbackText } });
19913
+ assistantCommittedThisRunRef.current = true;
18944
19914
  }
18945
19915
  });
18946
19916
  const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
@@ -19350,6 +20320,18 @@ Available chords:
19350
20320
  }
19351
20321
  },
19352
20322
  onPluginPickerToggle: toggleSelectedPlugin,
20323
+ onMcpPickerToggle: toggleSelectedMcpServer,
20324
+ onMcpPickerRestart: restartSelectedMcpServer,
20325
+ onToolsPickerToggle: toggleSelectedTool,
20326
+ onHelpPanelEnter: () => {
20327
+ const entry = state.helpPanel.entries[state.helpPanel.selected];
20328
+ if (!entry) return;
20329
+ dispatch({ type: "helpClose" });
20330
+ submitRef.current(`/${entry.name}`);
20331
+ },
20332
+ onBrainRiskChange: changeBrainRisk,
20333
+ onShadowStart: handleShadowStart,
20334
+ onShadowStop: handleShadowStop,
19353
20335
  onAuthEnter: authPanelController.onAuthEnter,
19354
20336
  onAuthBack: authPanelController.onAuthBack,
19355
20337
  onAuthShortcut: authPanelController.onAuthShortcut,
@@ -20320,9 +21302,7 @@ Available chords:
20320
21302
  }
20321
21303
  });
20322
21304
  }
20323
- const assistantEntriesBeforeRun = stateRef.current.entries.filter(
20324
- (entry) => entry.kind === "assistant"
20325
- ).length;
21305
+ assistantCommittedThisRunRef.current = false;
20326
21306
  const result = await agent.run(routed.blocks, { signal: ctrl.signal });
20327
21307
  const lingering = streamingTextRef.current;
20328
21308
  const lingeringSegments = streamSegmentsRef.current;
@@ -20342,7 +21322,13 @@ Available chords:
20342
21322
  }
20343
21323
  } else if (lingering.trim()) {
20344
21324
  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) {
21325
+ } else if (result.status === "done" && result.finalText?.trim() && // Only recover finalText when no assistant entry was committed by the
21326
+ // per-iteration `provider.response` flush during this run. Checked via
21327
+ // a synchronous ref, NOT `stateRef` — the flush's `addEntry` dispatch
21328
+ // isn't reflected in `stateRef` yet when we resume here, so a
21329
+ // state-based count would read stale and re-commit the final reply,
21330
+ // rendering it twice.
21331
+ !assistantCommittedThisRunRef.current) {
20346
21332
  dispatch({ type: "addEntry", entry: { kind: "assistant", text: result.finalText } });
20347
21333
  }
20348
21334
  if (result.status === "aborted") {
@@ -21074,6 +22060,50 @@ ${content}
21074
22060
  hint: state.pluginPicker.hint
21075
22061
  }
21076
22062
  ) : null,
22063
+ state.mcpPicker.open ? /* @__PURE__ */ jsx(
22064
+ McpPicker,
22065
+ {
22066
+ items: state.mcpPicker.items,
22067
+ selected: state.mcpPicker.selected,
22068
+ busy: state.mcpPicker.busy,
22069
+ hint: state.mcpPicker.hint
22070
+ }
22071
+ ) : null,
22072
+ state.toolsPicker.open ? /* @__PURE__ */ jsx(
22073
+ ToolsPicker,
22074
+ {
22075
+ items: state.toolsPicker.items,
22076
+ selected: state.toolsPicker.selected,
22077
+ busy: state.toolsPicker.busy,
22078
+ hint: state.toolsPicker.hint,
22079
+ filter: state.toolsPicker.filter
22080
+ }
22081
+ ) : null,
22082
+ state.brainPanel.open ? /* @__PURE__ */ jsx(
22083
+ BrainPanel,
22084
+ {
22085
+ riskLevel: state.brainPanel.riskLevel,
22086
+ log: state.brainPanel.log,
22087
+ selected: state.brainPanel.selected,
22088
+ hint: state.brainPanel.hint
22089
+ }
22090
+ ) : null,
22091
+ state.helpPanel.open ? /* @__PURE__ */ jsx(
22092
+ HelpPanel,
22093
+ {
22094
+ entries: state.helpPanel.entries,
22095
+ filter: state.helpPanel.filter,
22096
+ selected: state.helpPanel.selected,
22097
+ hint: state.helpPanel.hint
22098
+ }
22099
+ ) : null,
22100
+ state.shadowPanel.open ? /* @__PURE__ */ jsx(
22101
+ ShadowPanel,
22102
+ {
22103
+ shadow: state.shadowPanel.shadow,
22104
+ hint: state.shadowPanel.hint
22105
+ }
22106
+ ) : null,
21077
22107
  state.authPanel.open ? /* @__PURE__ */ jsx(AuthPanel, { panel: state.authPanel }) : null,
21078
22108
  state.projectPicker.open ? /* @__PURE__ */ jsx(
21079
22109
  ProjectPicker,
@@ -21848,6 +22878,16 @@ async function runTui(opts) {
21848
22878
  saveSettings: opts.saveSettings,
21849
22879
  getPluginItems: opts.getPluginItems,
21850
22880
  onPluginToggle: opts.onPluginToggle,
22881
+ getMcpServers: opts.getMcpServers,
22882
+ onMcpToggle: opts.onMcpToggle,
22883
+ onMcpRestart: opts.onMcpRestart,
22884
+ getToolsItems: opts.getToolsItems,
22885
+ onToolToggle: opts.onToolToggle,
22886
+ getBrainData: opts.getBrainData,
22887
+ onBrainRiskLevel: opts.onBrainRiskLevel,
22888
+ getShadowData: opts.getShadowData,
22889
+ onShadowStart: opts.onShadowStart,
22890
+ onShadowStop: opts.onShadowStop,
21851
22891
  authHost: opts.authHost,
21852
22892
  predictNext: opts.predictNext,
21853
22893
  onSuggestionsParsed: opts.onSuggestionsParsed,