@wrongstack/tui 0.281.1 → 0.282.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,4 +1,4 @@
1
- import { hasOpenTodos, writeErr, resolveProjectDir, wstackGlobalRoot, createHqPublisherFromEnv, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge, GlobalMailbox, resolveWstackPaths, loadGoal, InputHistoryStore, DefaultSecretScrubber, INPUT_HISTORY_DEFAULT_MAX, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, getDesignKitLoader, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, materializeTokens, applyTokenOverrides, setActiveKit, projectSlug, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, setBtwNote, buildChildEnv, DefaultPromptLoader, PromptUsageStore } from '@wrongstack/core';
1
+ import { hasOpenTodos, writeErr, resolveProjectDir, wstackGlobalRoot, createHqPublisherFromEnv, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge, GlobalMailbox, resolveWstackPaths, loadGoal, InputHistoryStore, DefaultSecretScrubber, INPUT_HISTORY_DEFAULT_MAX, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, getDesignKitLoader, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, materializeTokens, applyTokenOverrides, setActiveKit, projectSlug, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, setBtwNote, listBoards, getBoard, createBoard, addTask, buildChildEnv, DefaultPromptLoader, PromptUsageStore, removeTask, moveTask, updateTask, duplicateBoard, copyTaskToBoard, transferTaskToBoard } from '@wrongstack/core';
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';
@@ -17,6 +17,83 @@ import { readClipboardText, readClipboardImage } from '@wrongstack/runtime/clipb
17
17
 
18
18
  // src/run-tui.ts
19
19
 
20
+ // src/components/auth-panel-model.ts
21
+ var AUTH_PANEL_INITIAL = {
22
+ open: false,
23
+ view: "list",
24
+ busy: false,
25
+ providers: [],
26
+ presets: [],
27
+ catalog: [],
28
+ selected: 0,
29
+ filter: "",
30
+ flowTitle: "",
31
+ log: [],
32
+ flowDone: false
33
+ };
34
+ function filterAuthCatalog(catalog, filter) {
35
+ const q = filter.trim().toLowerCase();
36
+ if (!q) return catalog;
37
+ return catalog.filter(
38
+ (c) => c.id.toLowerCase().includes(q) || c.name.toLowerCase().includes(q) || c.family.toLowerCase().includes(q)
39
+ );
40
+ }
41
+ function authSelectedProvider(state) {
42
+ return state.providers.find((p) => p.id === state.providerId);
43
+ }
44
+ function authPanelRows(state) {
45
+ switch (state.view) {
46
+ case "list": {
47
+ const rows = state.providers.map((provider) => ({
48
+ kind: "provider",
49
+ provider
50
+ }));
51
+ rows.push(
52
+ { kind: "list-action", action: "catalog" },
53
+ { kind: "list-action", action: "local" },
54
+ { kind: "list-action", action: "custom" },
55
+ { kind: "list-action", action: "oauth" }
56
+ );
57
+ return rows;
58
+ }
59
+ case "provider": {
60
+ const provider = authSelectedProvider(state);
61
+ if (!provider) return [];
62
+ const rows = provider.keys.map((keyRow) => ({
63
+ kind: "key",
64
+ keyRow
65
+ }));
66
+ rows.push(
67
+ { kind: "provider-action", action: "add-key" },
68
+ { kind: "provider-action", action: "edit-family" },
69
+ { kind: "provider-action", action: "edit-base-url" },
70
+ { kind: "provider-action", action: "edit-models" },
71
+ { kind: "provider-action", action: "remove" }
72
+ );
73
+ return rows;
74
+ }
75
+ case "catalog":
76
+ return filterAuthCatalog(state.catalog, state.filter).map((entry) => ({
77
+ kind: "catalog-entry",
78
+ entry
79
+ }));
80
+ case "local":
81
+ return state.presets.map((preset) => ({ kind: "local-preset", preset }));
82
+ case "oauth":
83
+ return ["chatgpt", "claude", "copilot"].map((oauth) => ({
84
+ kind: "oauth-option",
85
+ oauth
86
+ }));
87
+ case "flow":
88
+ return [];
89
+ }
90
+ }
91
+ function authMoveSelected(state, delta) {
92
+ const count = authPanelRows(state).length;
93
+ if (count === 0) return 0;
94
+ return ((state.selected + delta) % count + count) % count;
95
+ }
96
+
20
97
  // src/theme.ts
21
98
  var pastel = Object.freeze({
22
99
  // Base 8
@@ -163,213 +240,82 @@ function PromptPicker({ entries, selected, category, total }) {
163
240
  })
164
241
  ] });
165
242
  }
166
- function isChipExpired(meta, now = Date.now()) {
167
- if (meta.expiresIn == null || meta.expiresIn === 0) return false;
168
- if (meta.shownAt == null || meta.shownAt === 0) return false;
169
- return now >= meta.shownAt + meta.expiresIn * 60 * 1e3;
170
- }
171
- var ITEM_DESCRIPTIONS = {
172
- version: "WrongStack version chip",
173
- state: "Agent run state / thinking spinner",
174
- model: "Current provider/model id",
175
- tokens: "Input/output token counters",
176
- cache: "Prompt cache hit ratio",
177
- queue: "Queued prompt count",
178
- processes: "Tracked shell/process count",
179
- hint: "Transient status hint text",
180
- index: "Codebase indexing status",
181
- breaker: "Process breaker countdown",
182
- todos: "Todo items (pending/in-progress/done)",
183
- plan: "Plan board items",
184
- tasks: "Task board items",
185
- fleet: "Fleet agent status",
186
- fleet_agents: "Per-agent live detail row",
187
- git: "Git branch name",
188
- elapsed: "Session elapsed time",
189
- context: "Context window usage %",
190
- cost: "Token cost estimate",
191
- working_dir: "Current working directory",
192
- project: "Project name",
193
- yolo: "YOLO permission mode",
194
- autonomy: "Autonomy mode",
195
- eternal_stage: "Autonomy stage",
196
- goal: "Active goal summary",
197
- mode: "Active agent mode label",
198
- auto_proceed: "Auto-proceed countdown",
199
- sessions: "Live session count",
200
- tools: "Registered tool count",
201
- token_saving: "Token-saving mode indicator",
202
- brain: "Brain arbiter decisions",
203
- mailbox: "Mailbox unread messages",
204
- enhance: "Prompt-enhance countdown",
205
- debug_stream: "Stream debug telemetry",
206
- next_steps: "Next-step auto-submit countdown"
207
- };
208
- var ITEM_LINE = {
209
- breaker: 1,
210
- cache: 1,
211
- context: 1,
212
- cost: 1,
213
- hint: 1,
214
- index: 1,
215
- model: 1,
216
- processes: 1,
217
- queue: 1,
218
- state: 1,
219
- tokens: 1,
220
- version: 1,
221
- auto_proceed: 2,
222
- autonomy: 2,
223
- elapsed: 2,
224
- eternal_stage: 2,
225
- git: 2,
226
- goal: 2,
227
- mode: 2,
228
- project: 2,
229
- sessions: 2,
230
- token_saving: 2,
231
- tools: 2,
232
- working_dir: 2,
233
- yolo: 2,
234
- brain: 3,
235
- debug_stream: 3,
236
- enhance: 3,
237
- fleet: 3,
238
- next_steps: 3,
239
- todos: 3,
240
- plan: 3,
241
- tasks: 3,
242
- fleet_agents: 4,
243
- mailbox: 4
244
- };
245
- var STATUSLINE_FIELD_COUNT = Object.keys(ITEM_LINE).length;
246
- var STATUSLINE_ITEMS = [
247
- // Line 1
248
- "breaker",
249
- "cache",
250
- "context",
251
- "cost",
252
- "hint",
253
- "index",
254
- "model",
255
- "processes",
256
- "queue",
257
- "state",
258
- "tokens",
259
- "version",
260
- // Line 2
261
- "auto_proceed",
262
- "autonomy",
263
- "elapsed",
264
- "eternal_stage",
265
- "git",
266
- "goal",
267
- "mode",
268
- "project",
269
- "sessions",
270
- "token_saving",
271
- "tools",
272
- "working_dir",
273
- "yolo",
274
- // Line 3
275
- "brain",
276
- "debug_stream",
277
- "enhance",
278
- "fleet",
279
- "next_steps",
280
- "plan",
281
- "tasks",
282
- "todos",
283
- // Line 4
284
- "fleet_agents",
285
- "mailbox"
286
- ];
287
- var STREAM_CHIP_KEYS = ["brain", "mailbox", "enhance", "debug_stream"];
288
- function groupByLine(items) {
289
- const map = /* @__PURE__ */ new Map();
290
- for (const item of items) {
291
- const line = ITEM_LINE[item];
292
- if (!map.has(line)) map.set(line, []);
293
- map.get(line).push(item);
243
+ var SEND_MODE_OPTIONS = [
244
+ {
245
+ mode: "queue",
246
+ key: "q",
247
+ label: "Queue",
248
+ description: "Run after the current turn finishes (default)",
249
+ color: "green"
250
+ },
251
+ {
252
+ mode: "btw",
253
+ key: "b",
254
+ label: "By the way",
255
+ description: "Fold in at the next step \u2014 no restart, no interrupt",
256
+ color: "cyan"
257
+ },
258
+ {
259
+ mode: "steer",
260
+ key: "s",
261
+ label: "Steer",
262
+ description: "Abort now, drop the queue, redirect to this",
263
+ color: "red"
294
264
  }
295
- return map;
265
+ ];
266
+ function nextSendModeIndex(selected, delta, len) {
267
+ if (len <= 0) return 0;
268
+ return ((selected + delta) % len + len) % len;
296
269
  }
297
- function StatuslinePicker({
298
- field,
299
- hiddenItems,
300
- visibleChips = [],
301
- hint
270
+ function sendModeFromKey(input, key, selected) {
271
+ if (key.escape) return "cancel";
272
+ if (key.return) return SEND_MODE_OPTIONS[selected]?.mode ?? "queue";
273
+ const ch = input.trim().toLowerCase();
274
+ const match = SEND_MODE_OPTIONS.find((o) => o.key === ch);
275
+ if (match) return match.mode;
276
+ return null;
277
+ }
278
+ function formatSendModeMessagePreview(text, maxChars = 120) {
279
+ const singleLine = text.replace(/\s+/g, " ").trim();
280
+ if (singleLine.length <= maxChars) return singleLine;
281
+ return `${singleLine.slice(0, Math.max(0, maxChars - 1))}\u2026`;
282
+ }
283
+ function SendModePicker({
284
+ selected,
285
+ messagePreview,
286
+ onMove,
287
+ onSelect
302
288
  }) {
303
- const hiddenSet = new Set(hiddenItems);
304
- const visibleChipsMap = new Map(visibleChips.map((c) => [c.key, c]));
305
- const totalFields = STATUSLINE_ITEMS.length;
306
- const byLine = groupByLine(STATUSLINE_ITEMS);
307
- const VISIBLE_FIELDS = 8;
308
- const windowStart = Math.max(0, Math.min(field - Math.floor(VISIBLE_FIELDS / 2), totalFields - VISIBLE_FIELDS));
309
- const windowEnd = Math.min(windowStart + VISIBLE_FIELDS, totalFields);
310
- const hasAbove = windowStart > 0;
311
- const hasBelow = windowEnd < totalFields;
312
- const rows = [];
313
- for (const [line, items] of [...byLine.entries()].sort((a, b) => a[0] - b[0])) {
314
- const windowedItems = items.filter((item) => {
315
- const idx = STATUSLINE_ITEMS.indexOf(item);
316
- return idx >= windowStart && idx < windowEnd;
317
- });
318
- if (windowedItems.length === 0) continue;
319
- rows.push({ section: `Line ${line}` });
320
- for (const item of windowedItems) {
321
- rows.push({ item, fieldIdx: STATUSLINE_ITEMS.indexOf(item) });
322
- }
323
- }
324
- const boolVal = (item) => {
325
- if (hiddenSet.has(item)) return "off";
326
- if (STREAM_CHIP_KEYS.includes(item)) {
327
- const meta = visibleChipsMap.get(item);
328
- if (!meta) return "auto";
329
- if (meta.expiresIn == null) return "on ";
330
- const remainingMs = meta.shownAt + meta.expiresIn * 6e4 - Date.now();
331
- if (remainingMs <= 0) return "auto";
332
- const remainingMin = Math.max(1, Math.ceil(remainingMs / 6e4));
333
- return `~${remainingMin}m`;
289
+ useInput((input, key) => {
290
+ if (key.upArrow) {
291
+ onMove(-1);
292
+ return;
334
293
  }
335
- return "on ";
336
- };
337
- const valColor = (item) => {
338
- if (hiddenSet.has(item)) return "red";
339
- if (STREAM_CHIP_KEYS.includes(item)) {
340
- const meta = visibleChipsMap.get(item);
341
- if (!meta) return "cyan";
342
- if (isChipExpired(meta)) return "cyan";
343
- return "yellow";
294
+ if (key.downArrow) {
295
+ onMove(1);
296
+ return;
344
297
  }
345
- return "green";
346
- };
347
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
348
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Statusline \u2501\u2501" }),
349
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 move \xB7 \u2190/\u2192 toggle \xB7 Esc to close" }),
350
- hasAbove ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${windowStart} item${windowStart === 1 ? "" : "s"} above` }) : null,
351
- rows.map((row) => {
352
- if (row.section) {
353
- return /* @__PURE__ */ jsxs(Text, { bold: true, color: "green", children: [
354
- "\u2500\u2500 ",
355
- row.section,
356
- " \u2500\u2500"
357
- ] }, `section-${row.section}`);
358
- }
359
- const item = row.item;
360
- const fieldIdx = row.fieldIdx;
361
- const selected = fieldIdx === field;
362
- return /* @__PURE__ */ jsxs(Text, { inverse: selected, ...selected ? { color: "yellow" } : {}, children: [
363
- selected ? "\u203A " : " ",
364
- /* @__PURE__ */ jsx(Text, { bold: true, children: item.padEnd(12) }),
365
- /* @__PURE__ */ jsx(Text, { color: valColor(item), children: boolVal(item).padEnd(4) }),
366
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ITEM_DESCRIPTIONS[item] }),
367
- selected ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2190/\u2192 toggle" }) : null
368
- ] }, `row-${item}`);
369
- }),
370
- hasBelow ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${totalFields - windowEnd} item${totalFields - windowEnd === 1 ? "" : "s"} below` }) : null,
371
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Changes apply instantly \xB7 persisted to ~/.wrongstack/statusline.json \xB7 auto chips show when data exists" }),
372
- hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
298
+ const decision = sendModeFromKey(input, key, selected);
299
+ if (decision) onSelect(decision);
300
+ });
301
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, marginY: 1, children: [
302
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Deliver this message how? \u2501\u2501" }),
303
+ messagePreview ? /* @__PURE__ */ jsxs(Text, { children: [
304
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Message: " }),
305
+ /* @__PURE__ */ jsx(Text, { color: "white", children: formatSendModeMessagePreview(messagePreview) })
306
+ ] }) : null,
307
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "q/b/s pick \xB7 \u2191/\u2193 move \xB7 Enter select \xB7 Esc \u2192 queue" }),
308
+ SEND_MODE_OPTIONS.map((opt, i) => /* @__PURE__ */ jsxs(Text, { inverse: i === selected, ...i === selected ? { color: opt.color } : {}, children: [
309
+ i === selected ? "\u203A " : " ",
310
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: opt.color, children: [
311
+ "[",
312
+ opt.key,
313
+ "]"
314
+ ] }),
315
+ " ",
316
+ /* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
317
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description })
318
+ ] }, opt.mode))
373
319
  ] });
374
320
  }
375
321
 
@@ -1494,159 +1440,213 @@ function SettingsPicker({
1494
1440
  hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
1495
1441
  ] });
1496
1442
  }
1497
-
1498
- // src/components/auth-panel-model.ts
1499
- var AUTH_PANEL_INITIAL = {
1500
- open: false,
1501
- view: "list",
1502
- busy: false,
1503
- providers: [],
1504
- presets: [],
1505
- catalog: [],
1506
- selected: 0,
1507
- filter: "",
1508
- flowTitle: "",
1509
- log: [],
1510
- flowDone: false
1511
- };
1512
- function filterAuthCatalog(catalog, filter) {
1513
- const q = filter.trim().toLowerCase();
1514
- if (!q) return catalog;
1515
- return catalog.filter(
1516
- (c) => c.id.toLowerCase().includes(q) || c.name.toLowerCase().includes(q) || c.family.toLowerCase().includes(q)
1517
- );
1443
+ function isChipExpired(meta, now = Date.now()) {
1444
+ if (meta.expiresIn == null || meta.expiresIn === 0) return false;
1445
+ if (meta.shownAt == null || meta.shownAt === 0) return false;
1446
+ return now >= meta.shownAt + meta.expiresIn * 60 * 1e3;
1518
1447
  }
1519
- function authSelectedProvider(state) {
1520
- return state.providers.find((p) => p.id === state.providerId);
1448
+ var ITEM_DESCRIPTIONS = {
1449
+ version: "WrongStack version chip",
1450
+ state: "Agent run state / thinking spinner",
1451
+ model: "Current provider/model id",
1452
+ tokens: "Input/output token counters",
1453
+ cache: "Prompt cache hit ratio",
1454
+ queue: "Queued prompt count",
1455
+ processes: "Tracked shell/process count",
1456
+ hint: "Transient status hint text",
1457
+ index: "Codebase indexing status",
1458
+ breaker: "Process breaker countdown",
1459
+ todos: "Todo items (pending/in-progress/done)",
1460
+ plan: "Plan board items",
1461
+ tasks: "Task board items",
1462
+ fleet: "Fleet agent status",
1463
+ fleet_agents: "Per-agent live detail row",
1464
+ git: "Git branch name",
1465
+ elapsed: "Session elapsed time",
1466
+ context: "Context window usage %",
1467
+ cost: "Token cost estimate",
1468
+ working_dir: "Current working directory",
1469
+ project: "Project name",
1470
+ yolo: "YOLO permission mode",
1471
+ autonomy: "Autonomy mode",
1472
+ eternal_stage: "Autonomy stage",
1473
+ goal: "Active goal summary",
1474
+ mode: "Active agent mode label",
1475
+ auto_proceed: "Auto-proceed countdown",
1476
+ sessions: "Live session count",
1477
+ tools: "Registered tool count",
1478
+ token_saving: "Token-saving mode indicator",
1479
+ brain: "Brain arbiter decisions",
1480
+ mailbox: "Mailbox unread messages",
1481
+ enhance: "Prompt-enhance countdown",
1482
+ debug_stream: "Stream debug telemetry",
1483
+ next_steps: "Next-step auto-submit countdown"
1484
+ };
1485
+ var ITEM_LINE = {
1486
+ breaker: 1,
1487
+ cache: 1,
1488
+ context: 1,
1489
+ cost: 1,
1490
+ hint: 1,
1491
+ index: 1,
1492
+ model: 1,
1493
+ processes: 1,
1494
+ queue: 1,
1495
+ state: 1,
1496
+ tokens: 1,
1497
+ version: 1,
1498
+ auto_proceed: 2,
1499
+ autonomy: 2,
1500
+ elapsed: 2,
1501
+ eternal_stage: 2,
1502
+ git: 2,
1503
+ goal: 2,
1504
+ mode: 2,
1505
+ project: 2,
1506
+ sessions: 2,
1507
+ token_saving: 2,
1508
+ tools: 2,
1509
+ working_dir: 2,
1510
+ yolo: 2,
1511
+ brain: 3,
1512
+ debug_stream: 3,
1513
+ enhance: 3,
1514
+ fleet: 3,
1515
+ next_steps: 3,
1516
+ todos: 3,
1517
+ plan: 3,
1518
+ tasks: 3,
1519
+ fleet_agents: 4,
1520
+ mailbox: 4
1521
+ };
1522
+ var STATUSLINE_FIELD_COUNT = Object.keys(ITEM_LINE).length;
1523
+ var STATUSLINE_ITEMS = [
1524
+ // Line 1
1525
+ "breaker",
1526
+ "cache",
1527
+ "context",
1528
+ "cost",
1529
+ "hint",
1530
+ "index",
1531
+ "model",
1532
+ "processes",
1533
+ "queue",
1534
+ "state",
1535
+ "tokens",
1536
+ "version",
1537
+ // Line 2
1538
+ "auto_proceed",
1539
+ "autonomy",
1540
+ "elapsed",
1541
+ "eternal_stage",
1542
+ "git",
1543
+ "goal",
1544
+ "mode",
1545
+ "project",
1546
+ "sessions",
1547
+ "token_saving",
1548
+ "tools",
1549
+ "working_dir",
1550
+ "yolo",
1551
+ // Line 3
1552
+ "brain",
1553
+ "debug_stream",
1554
+ "enhance",
1555
+ "fleet",
1556
+ "next_steps",
1557
+ "plan",
1558
+ "tasks",
1559
+ "todos",
1560
+ // Line 4
1561
+ "fleet_agents",
1562
+ "mailbox"
1563
+ ];
1564
+ var STREAM_CHIP_KEYS = ["brain", "mailbox", "enhance", "debug_stream"];
1565
+ function groupByLine(items) {
1566
+ const map = /* @__PURE__ */ new Map();
1567
+ for (const item of items) {
1568
+ const line = ITEM_LINE[item];
1569
+ if (!map.has(line)) map.set(line, []);
1570
+ map.get(line).push(item);
1571
+ }
1572
+ return map;
1521
1573
  }
1522
- function authPanelRows(state) {
1523
- switch (state.view) {
1524
- case "list": {
1525
- const rows = state.providers.map((provider) => ({
1526
- kind: "provider",
1527
- provider
1528
- }));
1529
- rows.push(
1530
- { kind: "list-action", action: "catalog" },
1531
- { kind: "list-action", action: "local" },
1532
- { kind: "list-action", action: "custom" },
1533
- { kind: "list-action", action: "oauth" }
1534
- );
1535
- return rows;
1536
- }
1537
- case "provider": {
1538
- const provider = authSelectedProvider(state);
1539
- if (!provider) return [];
1540
- const rows = provider.keys.map((keyRow) => ({
1541
- kind: "key",
1542
- keyRow
1543
- }));
1544
- rows.push(
1545
- { kind: "provider-action", action: "add-key" },
1546
- { kind: "provider-action", action: "edit-family" },
1547
- { kind: "provider-action", action: "edit-base-url" },
1548
- { kind: "provider-action", action: "edit-models" },
1549
- { kind: "provider-action", action: "remove" }
1550
- );
1551
- return rows;
1574
+ function StatuslinePicker({
1575
+ field,
1576
+ hiddenItems,
1577
+ visibleChips = [],
1578
+ hint
1579
+ }) {
1580
+ const hiddenSet = new Set(hiddenItems);
1581
+ const visibleChipsMap = new Map(visibleChips.map((c) => [c.key, c]));
1582
+ const totalFields = STATUSLINE_ITEMS.length;
1583
+ const byLine = groupByLine(STATUSLINE_ITEMS);
1584
+ const VISIBLE_FIELDS = 8;
1585
+ const windowStart = Math.max(0, Math.min(field - Math.floor(VISIBLE_FIELDS / 2), totalFields - VISIBLE_FIELDS));
1586
+ const windowEnd = Math.min(windowStart + VISIBLE_FIELDS, totalFields);
1587
+ const hasAbove = windowStart > 0;
1588
+ const hasBelow = windowEnd < totalFields;
1589
+ const rows = [];
1590
+ for (const [line, items] of [...byLine.entries()].sort((a, b) => a[0] - b[0])) {
1591
+ const windowedItems = items.filter((item) => {
1592
+ const idx = STATUSLINE_ITEMS.indexOf(item);
1593
+ return idx >= windowStart && idx < windowEnd;
1594
+ });
1595
+ if (windowedItems.length === 0) continue;
1596
+ rows.push({ section: `Line ${line}` });
1597
+ for (const item of windowedItems) {
1598
+ rows.push({ item, fieldIdx: STATUSLINE_ITEMS.indexOf(item) });
1552
1599
  }
1553
- case "catalog":
1554
- return filterAuthCatalog(state.catalog, state.filter).map((entry) => ({
1555
- kind: "catalog-entry",
1556
- entry
1557
- }));
1558
- case "local":
1559
- return state.presets.map((preset) => ({ kind: "local-preset", preset }));
1560
- case "oauth":
1561
- return ["chatgpt", "claude", "copilot"].map((oauth) => ({
1562
- kind: "oauth-option",
1563
- oauth
1564
- }));
1565
- case "flow":
1566
- return [];
1567
- }
1568
- }
1569
- function authMoveSelected(state, delta) {
1570
- const count = authPanelRows(state).length;
1571
- if (count === 0) return 0;
1572
- return ((state.selected + delta) % count + count) % count;
1573
- }
1574
- var SEND_MODE_OPTIONS = [
1575
- {
1576
- mode: "queue",
1577
- key: "q",
1578
- label: "Queue",
1579
- description: "Run after the current turn finishes (default)",
1580
- color: "green"
1581
- },
1582
- {
1583
- mode: "btw",
1584
- key: "b",
1585
- label: "By the way",
1586
- description: "Fold in at the next step \u2014 no restart, no interrupt",
1587
- color: "cyan"
1588
- },
1589
- {
1590
- mode: "steer",
1591
- key: "s",
1592
- label: "Steer",
1593
- description: "Abort now, drop the queue, redirect to this",
1594
- color: "red"
1595
1600
  }
1596
- ];
1597
- function nextSendModeIndex(selected, delta, len) {
1598
- if (len <= 0) return 0;
1599
- return ((selected + delta) % len + len) % len;
1600
- }
1601
- function sendModeFromKey(input, key, selected) {
1602
- if (key.escape) return "cancel";
1603
- if (key.return) return SEND_MODE_OPTIONS[selected]?.mode ?? "queue";
1604
- const ch = input.trim().toLowerCase();
1605
- const match = SEND_MODE_OPTIONS.find((o) => o.key === ch);
1606
- if (match) return match.mode;
1607
- return null;
1608
- }
1609
- function formatSendModeMessagePreview(text, maxChars = 120) {
1610
- const singleLine = text.replace(/\s+/g, " ").trim();
1611
- if (singleLine.length <= maxChars) return singleLine;
1612
- return `${singleLine.slice(0, Math.max(0, maxChars - 1))}\u2026`;
1613
- }
1614
- function SendModePicker({
1615
- selected,
1616
- messagePreview,
1617
- onMove,
1618
- onSelect
1619
- }) {
1620
- useInput((input, key) => {
1621
- if (key.upArrow) {
1622
- onMove(-1);
1623
- return;
1601
+ const boolVal = (item) => {
1602
+ if (hiddenSet.has(item)) return "off";
1603
+ if (STREAM_CHIP_KEYS.includes(item)) {
1604
+ const meta = visibleChipsMap.get(item);
1605
+ if (!meta) return "auto";
1606
+ if (meta.expiresIn == null) return "on ";
1607
+ const remainingMs = meta.shownAt + meta.expiresIn * 6e4 - Date.now();
1608
+ if (remainingMs <= 0) return "auto";
1609
+ const remainingMin = Math.max(1, Math.ceil(remainingMs / 6e4));
1610
+ return `~${remainingMin}m`;
1624
1611
  }
1625
- if (key.downArrow) {
1626
- onMove(1);
1627
- return;
1612
+ return "on ";
1613
+ };
1614
+ const valColor = (item) => {
1615
+ if (hiddenSet.has(item)) return "red";
1616
+ if (STREAM_CHIP_KEYS.includes(item)) {
1617
+ const meta = visibleChipsMap.get(item);
1618
+ if (!meta) return "cyan";
1619
+ if (isChipExpired(meta)) return "cyan";
1620
+ return "yellow";
1628
1621
  }
1629
- const decision = sendModeFromKey(input, key, selected);
1630
- if (decision) onSelect(decision);
1631
- });
1632
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, marginY: 1, children: [
1633
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Deliver this message how? \u2501\u2501" }),
1634
- messagePreview ? /* @__PURE__ */ jsxs(Text, { children: [
1635
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Message: " }),
1636
- /* @__PURE__ */ jsx(Text, { color: "white", children: formatSendModeMessagePreview(messagePreview) })
1637
- ] }) : null,
1638
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "q/b/s pick \xB7 \u2191/\u2193 move \xB7 Enter select \xB7 Esc \u2192 queue" }),
1639
- SEND_MODE_OPTIONS.map((opt, i) => /* @__PURE__ */ jsxs(Text, { inverse: i === selected, ...i === selected ? { color: opt.color } : {}, children: [
1640
- i === selected ? "\u203A " : " ",
1641
- /* @__PURE__ */ jsxs(Text, { bold: true, color: opt.color, children: [
1642
- "[",
1643
- opt.key,
1644
- "]"
1645
- ] }),
1646
- " ",
1647
- /* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
1648
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description })
1649
- ] }, opt.mode))
1622
+ return "green";
1623
+ };
1624
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1625
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Statusline \u2501\u2501" }),
1626
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 move \xB7 \u2190/\u2192 toggle \xB7 Esc to close" }),
1627
+ hasAbove ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${windowStart} item${windowStart === 1 ? "" : "s"} above` }) : null,
1628
+ rows.map((row) => {
1629
+ if (row.section) {
1630
+ return /* @__PURE__ */ jsxs(Text, { bold: true, color: "green", children: [
1631
+ "\u2500\u2500 ",
1632
+ row.section,
1633
+ " \u2500\u2500"
1634
+ ] }, `section-${row.section}`);
1635
+ }
1636
+ const item = row.item;
1637
+ const fieldIdx = row.fieldIdx;
1638
+ const selected = fieldIdx === field;
1639
+ return /* @__PURE__ */ jsxs(Text, { inverse: selected, ...selected ? { color: "yellow" } : {}, children: [
1640
+ selected ? "\u203A " : " ",
1641
+ /* @__PURE__ */ jsx(Text, { bold: true, children: item.padEnd(12) }),
1642
+ /* @__PURE__ */ jsx(Text, { color: valColor(item), children: boolVal(item).padEnd(4) }),
1643
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ITEM_DESCRIPTIONS[item] }),
1644
+ selected ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2190/\u2192 toggle" }) : null
1645
+ ] }, `row-${item}`);
1646
+ }),
1647
+ hasBelow ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${totalFields - windowEnd} item${totalFields - windowEnd === 1 ? "" : "s"} below` }) : null,
1648
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Changes apply instantly \xB7 persisted to ~/.wrongstack/statusline.json \xB7 auto chips show when data exists" }),
1649
+ hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
1650
1650
  ] });
1651
1651
  }
1652
1652
 
@@ -1661,6 +1661,7 @@ function closePanels(state) {
1661
1661
  processListOpen: false,
1662
1662
  auditPanelOpen: false,
1663
1663
  planPanelOpen: false,
1664
+ kanbanPanelOpen: false,
1664
1665
  goalPanelOpen: false,
1665
1666
  sessionsPanelOpen: false,
1666
1667
  settingsPicker: { ...state.settingsPicker, open: false },
@@ -2063,7 +2064,7 @@ function reducer(state, action) {
2063
2064
  switch (action.type) {
2064
2065
  case "addEntry": {
2065
2066
  const e = action.entry;
2066
- if ((e.kind === "user" || e.kind === "assistant" || e.kind === "info" || e.kind === "warn" || e.kind === "error" || e.kind === "turn-summary") && !e.text?.trim()) {
2067
+ if ((e.kind === "user" || e.kind === "assistant" || e.kind === "thinking" || e.kind === "info" || e.kind === "warn" || e.kind === "error" || e.kind === "turn-summary") && !e.text?.trim()) {
2067
2068
  return state;
2068
2069
  }
2069
2070
  const stored = e.kind === "tool" && e.input !== void 0 ? { ...e, input: pruneToolInput(e.input) } : e;
@@ -2412,6 +2413,31 @@ function reducer(state, action) {
2412
2413
  ...state,
2413
2414
  autonomyPicker: { ...state.autonomyPicker, hint: action.text }
2414
2415
  };
2416
+ case "modePickerOpen":
2417
+ return {
2418
+ ...state,
2419
+ ...closePanels(state),
2420
+ modePicker: { open: true, modes: action.modes, selected: 0, hint: void 0 }
2421
+ };
2422
+ case "modePickerClose":
2423
+ return {
2424
+ ...state,
2425
+ modePicker: { open: false, modes: [], selected: 0 }
2426
+ };
2427
+ case "modePickerMove": {
2428
+ const n = state.modePicker.modes.length;
2429
+ if (n === 0) return state;
2430
+ const next = (state.modePicker.selected + action.delta + n) % n;
2431
+ return {
2432
+ ...state,
2433
+ modePicker: { ...state.modePicker, selected: next }
2434
+ };
2435
+ }
2436
+ case "modePickerHint":
2437
+ return {
2438
+ ...state,
2439
+ modePicker: { ...state.modePicker, hint: action.text }
2440
+ };
2415
2441
  case "designPickerOpen":
2416
2442
  return {
2417
2443
  ...state,
@@ -3400,6 +3426,10 @@ function reducer(state, action) {
3400
3426
  const opening = !state.planPanelOpen;
3401
3427
  return opening ? { ...state, ...closePanels(state), planPanelOpen: true } : { ...state, planPanelOpen: false };
3402
3428
  }
3429
+ case "toggleKanbanPanel": {
3430
+ const opening = !state.kanbanPanelOpen;
3431
+ return opening ? { ...state, ...closePanels(state), kanbanPanelOpen: true } : { ...state, kanbanPanelOpen: false };
3432
+ }
3403
3433
  case "toggleGoalPanel": {
3404
3434
  const opening = !state.goalPanelOpen;
3405
3435
  return opening ? { ...state, ...closePanels(state), goalPanelOpen: true } : { ...state, goalPanelOpen: false };
@@ -4978,7 +5008,7 @@ function FleetMonitor({
4978
5008
  nowTick,
4979
5009
  collabSession
4980
5010
  }) {
4981
- const TERMINAL_TTL_MS = 5 * 6e4;
5011
+ const TERMINAL_TTL_MS2 = 5 * 6e4;
4982
5012
  const all = Object.values(entries);
4983
5013
  const running = all.filter((e) => e.status === "running");
4984
5014
  const idle = all.filter((e) => e.status === "idle").length;
@@ -4986,7 +5016,7 @@ function FleetMonitor({
4986
5016
  const failed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
4987
5017
  const recent = all.filter((e) => {
4988
5018
  if (e.status === "running" || e.status === "idle") return true;
4989
- return nowTick - e.lastEventAt < TERMINAL_TTL_MS;
5019
+ return nowTick - e.lastEventAt < TERMINAL_TTL_MS2;
4990
5020
  });
4991
5021
  const concurrencyRatio = maxConcurrent > 0 ? running.length / maxConcurrent : 0;
4992
5022
  const ordered = [...recent].sort((a, b) => {
@@ -5683,54 +5713,349 @@ function AuditPanel({ sideEffects, onClose: _onClose }) {
5683
5713
  }) })
5684
5714
  ] });
5685
5715
  }
5686
- var AUTONOMY_OPTIONS = [
5687
- {
5688
- mode: "off",
5689
- label: "OFF",
5690
- description: "Agent stops after each turn (normal interactive mode)",
5691
- color: "green"
5692
- },
5693
- {
5694
- mode: "suggest",
5695
- label: "SUGGEST",
5696
- description: "Shows next-step suggestions after each turn",
5697
- color: "cyan"
5698
- },
5699
- {
5700
- mode: "auto",
5701
- label: "AUTO",
5702
- description: "Self-driving \u2014 agent picks next step and continues",
5703
- color: "yellow"
5704
- },
5705
- {
5706
- mode: "eternal",
5707
- label: "ETERNAL",
5708
- description: "Goal-driven loop \u2014 requires /goal set first",
5709
- color: "red"
5710
- },
5711
- {
5712
- mode: "eternal-parallel",
5713
- label: "PARALLEL",
5714
- description: "Fan-out 4\u20138 subagents per tick \u2014 requires /goal",
5715
- color: "magenta"
5716
+
5717
+ // src/components/provider-colors.ts
5718
+ function colorForFamily(family) {
5719
+ return FAMILY_COLORS[family?.toLowerCase() ?? ""] ?? "#89dceb";
5720
+ }
5721
+ function dimColorForFamily(family) {
5722
+ return DIMMED_FAMILY_COLORS[family?.toLowerCase() ?? ""] ?? "#585b70";
5723
+ }
5724
+ var FAMILY_COLORS = {
5725
+ anthropic: "#f38ba8",
5726
+ "anthropic-oauth": "#f38ba8",
5727
+ "anthropic-claude": "#f38ba8",
5728
+ openai: "#a6e3a1",
5729
+ "openai-codex": "#a6e3a1",
5730
+ "openai-compatible": "#94e2d5",
5731
+ google: "#89b4fa",
5732
+ gemini: "#89b4fa",
5733
+ "google-gemini": "#89b4fa",
5734
+ "github-copilot": "#cba6f7",
5735
+ copilot: "#cba6f7",
5736
+ local: "#f9e2af",
5737
+ ollama: "#fab387",
5738
+ vllm: "#fab387",
5739
+ lmstudio: "#f9e2af",
5740
+ omniroute: "#eba0ac",
5741
+ mistral: "#b4befe",
5742
+ groq: "#89dceb",
5743
+ together: "#89dceb",
5744
+ fireworks: "#89dceb",
5745
+ perplexity: "#89dceb",
5746
+ deepseek: "#f5c2e7"
5747
+ };
5748
+ var DIMMED_FAMILY_COLORS = {
5749
+ anthropic: "#6c3f4a",
5750
+ "anthropic-oauth": "#6c3f4a",
5751
+ openai: "#3d5c3d",
5752
+ "openai-codex": "#3d5c3d",
5753
+ "openai-compatible": "#3a524e",
5754
+ google: "#3a4570",
5755
+ gemini: "#3a4570",
5756
+ "github-copilot": "#4a3f5c",
5757
+ copilot: "#4a3f5c",
5758
+ local: "#5c5430",
5759
+ ollama: "#5c4730",
5760
+ vllm: "#5c4730",
5761
+ mistral: "#3e3f5c",
5762
+ deepseek: "#5c3f50"
5763
+ };
5764
+ function badgeForKind(kind) {
5765
+ switch (kind) {
5766
+ case "oauth":
5767
+ return { label: "oauth", color: "#cba6f7" };
5768
+ case "session_token":
5769
+ return { label: "session", color: "#89dceb" };
5770
+ default:
5771
+ return void 0;
5716
5772
  }
5717
- ];
5718
- function AutonomyPicker({
5719
- options,
5720
- selected,
5721
- hint
5722
- }) {
5723
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
5724
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Autonomy Mode \u2501\u2501" }),
5725
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
5726
- options.map((opt, i) => /* @__PURE__ */ jsxs(
5727
- Text,
5728
- {
5729
- inverse: i === selected,
5730
- ...i === selected ? { color: opt.color } : {},
5731
- children: [
5732
- i === selected ? "\u203A " : " ",
5733
- /* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
5773
+ }
5774
+ var OAUTH_KIND_COLORS = {
5775
+ chatgpt: "#a6e3a1",
5776
+ claude: "#f38ba8",
5777
+ copilot: "#cba6f7"
5778
+ };
5779
+ var UI_COLORS = {
5780
+ focused: "#89b4fa",
5781
+ active: "#a6e3a1",
5782
+ inactive: "#585b70",
5783
+ warning: "#f9e2af",
5784
+ error: "#f38ba8",
5785
+ hint: "#f9e2af",
5786
+ border: "#89b4fa",
5787
+ title: "#89b4fa",
5788
+ selectedModel: "#89b4fa"};
5789
+ var CHROME_ROWS = 18;
5790
+ var LIST_ACTION_LABEL = {
5791
+ catalog: "\uFF0B Add provider (models.dev catalog)",
5792
+ local: "\uFF0B Add local server (OmniRoute / Ollama / vLLM / LM Studio)",
5793
+ custom: "\uFF0B Add custom provider",
5794
+ oauth: "\u26A1 Sign in with OAuth (ChatGPT / Claude / Copilot)"
5795
+ };
5796
+ var PROVIDER_ACTION_LABEL = {
5797
+ "add-key": "\uFF0B Add another key",
5798
+ "edit-family": "\u270E Edit family",
5799
+ "edit-base-url": "\u270E Edit base URL",
5800
+ "edit-models": "\u270E Edit visible model list",
5801
+ remove: "\u2715 Remove this provider"
5802
+ };
5803
+ var OAUTH_LABEL = {
5804
+ chatgpt: { title: "ChatGPT Plus/Pro", detail: "\u2192 openai-codex" },
5805
+ claude: { title: "Claude Pro/Max", detail: "\u2192 anthropic-oauth" },
5806
+ copilot: { title: "GitHub Copilot", detail: "\u2192 github-copilot" }
5807
+ };
5808
+ function keySummary(keyRow) {
5809
+ const method = keyRow.authMethod === "oauth" ? " oauth" : "";
5810
+ const created = keyRow.createdAt ? ` ${keyRow.createdAt.slice(0, 10)}` : "";
5811
+ return `${keyRow.masked}${method}${created}`;
5812
+ }
5813
+ function renderRow(row, focused, i) {
5814
+ const marker = focused ? "\u203A" : " ";
5815
+ const rowColor = focused ? UI_COLORS.focused : void 0;
5816
+ switch (row.kind) {
5817
+ case "provider": {
5818
+ const p = row.provider;
5819
+ const keys = p.keys.length === 0 ? "no keys" : p.keys.length === 1 ? `1 key ${p.keys[0]?.masked ?? ""}` : `${p.keys.length} keys`;
5820
+ const famColor = colorForFamily(p.family);
5821
+ return /* @__PURE__ */ jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [
5822
+ marker,
5823
+ " ",
5824
+ /* @__PURE__ */ jsx(Text, { bold: true, color: focused ? void 0 : famColor, children: p.id.padEnd(22) }),
5825
+ " ",
5826
+ /* @__PURE__ */ jsx(Text, { color: focused ? void 0 : dimColorForFamily(p.family), children: (p.family ?? "\u2014").padEnd(18) }),
5827
+ " ",
5828
+ /* @__PURE__ */ jsx(Text, { color: p.keys.length > 0 ? UI_COLORS.active : UI_COLORS.warning, children: keys })
5829
+ ] }, `p-${p.id}`);
5830
+ }
5831
+ case "list-action":
5832
+ return /* @__PURE__ */ jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [
5833
+ marker,
5834
+ " ",
5835
+ LIST_ACTION_LABEL[row.action]
5836
+ ] }, `a-${row.action}`);
5837
+ case "key": {
5838
+ const k = row.keyRow;
5839
+ const badge = badgeForKind(k.authMethod);
5840
+ return /* @__PURE__ */ jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [
5841
+ marker,
5842
+ " ",
5843
+ /* @__PURE__ */ jsx(Text, { color: k.active ? UI_COLORS.active : UI_COLORS.inactive, children: k.active ? "\u25CF" : "\u25CB" }),
5844
+ " ",
5845
+ k.label.padEnd(20),
5846
+ " ",
5847
+ badge ? /* @__PURE__ */ jsx(Text, { color: badge.color, children: badge.label.padEnd(8) }) : null,
5848
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: keySummary(k) })
5849
+ ] }, `k-${k.label}`);
5850
+ }
5851
+ case "provider-action":
5852
+ return /* @__PURE__ */ jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [
5853
+ marker,
5854
+ " ",
5855
+ PROVIDER_ACTION_LABEL[row.action]
5856
+ ] }, `pa-${row.action}`);
5857
+ case "catalog-entry": {
5858
+ const c = row.entry;
5859
+ const famColor = colorForFamily(c.family);
5860
+ return /* @__PURE__ */ jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [
5861
+ marker,
5862
+ " ",
5863
+ /* @__PURE__ */ jsx(Text, { color: c.saved ? UI_COLORS.active : UI_COLORS.inactive, children: c.saved ? "\u25C9" : "\u25CB" }),
5864
+ " ",
5865
+ /* @__PURE__ */ jsx(Text, { bold: true, color: focused ? void 0 : famColor, children: c.id.padEnd(24) }),
5866
+ " ",
5867
+ /* @__PURE__ */ jsx(Text, { color: focused ? void 0 : dimColorForFamily(c.family), children: c.family.padEnd(20) }),
5868
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: c.envVars.length > 0 ? c.envVars.join(", ") : "" })
5869
+ ] }, `c-${c.id}`);
5870
+ }
5871
+ case "local-preset": {
5872
+ const p = row.preset;
5873
+ return /* @__PURE__ */ jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [
5874
+ marker,
5875
+ " ",
5876
+ /* @__PURE__ */ jsx(Text, { bold: true, color: focused ? void 0 : "#fab387", children: p.label.padEnd(12) }),
5877
+ " ",
5878
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: p.defaultBaseUrl.padEnd(28) }),
5879
+ " ",
5880
+ /* @__PURE__ */ jsx(Text, { color: p.noAuth ? UI_COLORS.inactive : UI_COLORS.active, children: p.noAuth ? "no auth" : "optional key" })
5881
+ ] }, `l-${p.id}`);
5882
+ }
5883
+ case "oauth-option": {
5884
+ const o = OAUTH_LABEL[row.oauth] ?? { title: row.oauth, detail: "" };
5885
+ const kindColor = OAUTH_KIND_COLORS[row.oauth] ?? UI_COLORS.focused;
5886
+ return /* @__PURE__ */ jsxs(Text, { color: rowColor, wrap: "truncate-end", children: [
5887
+ marker,
5888
+ " ",
5889
+ /* @__PURE__ */ jsx(Text, { bold: true, color: focused ? void 0 : kindColor, children: o.title.padEnd(20) }),
5890
+ " ",
5891
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: o.detail })
5892
+ ] }, `o-${row.oauth}`);
5893
+ }
5894
+ default:
5895
+ return /* @__PURE__ */ jsx(Text, { children: " " }, `r-${i}`);
5896
+ }
5897
+ }
5898
+ function viewTitle(panel) {
5899
+ switch (panel.view) {
5900
+ case "list":
5901
+ return "API keys & sign-in";
5902
+ case "provider":
5903
+ return `Provider \u2014 ${panel.providerId ?? ""}`;
5904
+ case "catalog":
5905
+ return "Add provider \u2014 models.dev catalog";
5906
+ case "local":
5907
+ return "Add local server";
5908
+ case "oauth":
5909
+ return "Sign in with OAuth";
5910
+ case "flow":
5911
+ return panel.flowTitle || "Working\u2026";
5912
+ }
5913
+ }
5914
+ function viewLegend(panel) {
5915
+ if (panel.input) return "Enter submit \xB7 Esc cancel";
5916
+ if (panel.confirm) return "y/Enter confirm \xB7 n/Esc cancel";
5917
+ switch (panel.view) {
5918
+ case "list":
5919
+ return "\u2191/\u2193 select \xB7 Enter open \xB7 Esc close";
5920
+ case "provider":
5921
+ return "\u2191/\u2193 select \xB7 Enter activate/run \xB7 u update key \xB7 d delete key \xB7 Esc back";
5922
+ case "catalog":
5923
+ return "type to filter \xB7 \u2191/\u2193 select \xB7 Enter add key \xB7 Esc back";
5924
+ case "local":
5925
+ return "\u2191/\u2193 select \xB7 Enter configure & probe \xB7 Esc back";
5926
+ case "oauth":
5927
+ return "\u2191/\u2193 select \xB7 Enter sign in \xB7 Esc back";
5928
+ case "flow":
5929
+ return panel.flowDone ? "Enter/Esc back" : "Esc cancel";
5930
+ }
5931
+ }
5932
+ function AuthPanel({ panel }) {
5933
+ const { stdout } = useStdout();
5934
+ const rows = stdout?.rows ?? 24;
5935
+ const maxVisible = Math.max(4, rows - CHROME_ROWS);
5936
+ const allRows = authPanelRows(panel);
5937
+ const total = allRows.length;
5938
+ const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(panel.selected - Math.floor(maxVisible / 2), total - maxVisible));
5939
+ const windowEnd = Math.min(windowStart + maxVisible, total);
5940
+ const above = windowStart;
5941
+ const below = total - windowEnd;
5942
+ const provider = panel.view === "provider" ? authSelectedProvider(panel) : void 0;
5943
+ const logWindow = panel.log.slice(-Math.max(6, maxVisible));
5944
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, children: [
5945
+ /* @__PURE__ */ jsx(Text, { bold: true, color: UI_COLORS.title, children: viewTitle(panel) }),
5946
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: viewLegend(panel) }),
5947
+ panel.view === "provider" && provider ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
5948
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [
5949
+ "type: ",
5950
+ provider.type ?? provider.id,
5951
+ " \xB7 family: ",
5952
+ provider.family ?? "unset",
5953
+ " \xB7 baseUrl:",
5954
+ " ",
5955
+ provider.baseUrl ?? "unset"
5956
+ ] }),
5957
+ provider.models.length > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [
5958
+ "models: ",
5959
+ provider.models.join(", ")
5960
+ ] }) : null
5961
+ ] }) : null,
5962
+ panel.view === "oauth" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
5963
+ /* @__PURE__ */ jsx(Text, { color: UI_COLORS.warning, wrap: "truncate-end", children: "\u26A0 Subscription tokens used outside official clients may violate provider Terms \u2014" }),
5964
+ /* @__PURE__ */ jsx(Text, { color: UI_COLORS.warning, wrap: "truncate-end", children: "your account could be rate-limited or banned. An API key is the sanctioned path." })
5965
+ ] }) : null,
5966
+ panel.view === "catalog" ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
5967
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "filter: " }),
5968
+ panel.filter.length > 0 ? panel.filter : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(type to search)" }),
5969
+ /* @__PURE__ */ jsx(Text, { color: UI_COLORS.focused, children: "\u258F" })
5970
+ ] }) }) : null,
5971
+ panel.view === "flow" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
5972
+ logWindow.length === 0 && !panel.flowDone ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Starting\u2026" }) : null,
5973
+ logWindow.map((line) => /* @__PURE__ */ jsx(
5974
+ Text,
5975
+ {
5976
+ wrap: "truncate-end",
5977
+ color: line.startsWith("\u2717") ? UI_COLORS.error : line.startsWith("\u2713") ? UI_COLORS.active : void 0,
5978
+ dimColor: !line.startsWith("\u2717") && !line.startsWith("\u2713"),
5979
+ children: line
5980
+ },
5981
+ line
5982
+ )),
5983
+ panel.flowDone ? /* @__PURE__ */ jsxs(Text, { color: panel.flowOk ? UI_COLORS.active : UI_COLORS.error, children: [
5984
+ panel.flowOk ? "\u2713 Done." : "\u2717 Not completed.",
5985
+ " ",
5986
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter or Esc to go back." })
5987
+ ] }) : panel.input ? /* @__PURE__ */ jsxs(Box, { children: [
5988
+ /* @__PURE__ */ jsx(Text, { children: panel.input.label }),
5989
+ /* @__PURE__ */ jsx(Text, { color: UI_COLORS.hint, children: panel.input.masked ? "\u2022".repeat(panel.input.draft.length) : panel.input.draft }),
5990
+ /* @__PURE__ */ jsx(Text, { color: UI_COLORS.focused, children: "\u258F" })
5991
+ ] }) : null
5992
+ ] }) : null,
5993
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", minHeight: maxVisible, children: [
5994
+ above > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5995
+ "\u25B2",
5996
+ " ",
5997
+ above,
5998
+ " above"
5999
+ ] }) : null,
6000
+ allRows.slice(windowStart, windowEnd).map((row, ri) => /* @__PURE__ */ jsx(Box, { marginLeft: 0, children: renderRow(row, windowStart + ri === panel.selected, windowStart + ri) }, `row-${windowStart + ri}`)),
6001
+ Array.from({ length: maxVisible - (windowEnd - windowStart) }).map((_, i) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: " " }) }, `pad-${i}`)),
6002
+ below > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
6003
+ "\u25BC",
6004
+ " ",
6005
+ below,
6006
+ " below"
6007
+ ] }) : null
6008
+ ] })
6009
+ ] });
6010
+ }
6011
+ var AUTONOMY_OPTIONS = [
6012
+ {
6013
+ mode: "off",
6014
+ label: "OFF",
6015
+ description: "Agent stops after each turn (normal interactive mode)",
6016
+ color: "green"
6017
+ },
6018
+ {
6019
+ mode: "suggest",
6020
+ label: "SUGGEST",
6021
+ description: "Shows next-step suggestions after each turn",
6022
+ color: "cyan"
6023
+ },
6024
+ {
6025
+ mode: "auto",
6026
+ label: "AUTO",
6027
+ description: "Self-driving \u2014 agent picks next step and continues",
6028
+ color: "yellow"
6029
+ },
6030
+ {
6031
+ mode: "eternal",
6032
+ label: "ETERNAL",
6033
+ description: "Goal-driven loop \u2014 requires /goal set first",
6034
+ color: "red"
6035
+ },
6036
+ {
6037
+ mode: "eternal-parallel",
6038
+ label: "PARALLEL",
6039
+ description: "Fan-out 4\u20138 subagents per tick \u2014 requires /goal",
6040
+ color: "magenta"
6041
+ }
6042
+ ];
6043
+ function AutonomyPicker({
6044
+ options,
6045
+ selected,
6046
+ hint
6047
+ }) {
6048
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
6049
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Autonomy Mode \u2501\u2501" }),
6050
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
6051
+ options.map((opt, i) => /* @__PURE__ */ jsxs(
6052
+ Text,
6053
+ {
6054
+ inverse: i === selected,
6055
+ ...i === selected ? { color: opt.color } : {},
6056
+ children: [
6057
+ i === selected ? "\u203A " : " ",
6058
+ /* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
5734
6059
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description })
5735
6060
  ]
5736
6061
  },
@@ -5822,6 +6147,39 @@ function BrainDecisionPrompt({
5822
6147
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press A/B/C or 1/2/3 to answer; Esc or D denies with the safe default." })
5823
6148
  ] });
5824
6149
  }
6150
+ function toModeOptions(modes, activeId) {
6151
+ return modes.map((m) => ({
6152
+ id: m.id,
6153
+ name: m.name,
6154
+ description: m.description,
6155
+ isActive: m.id === activeId
6156
+ }));
6157
+ }
6158
+ function ModePicker({
6159
+ modes,
6160
+ selected,
6161
+ hint
6162
+ }) {
6163
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
6164
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Mode Selection \u2501\u2501" }),
6165
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
6166
+ modes.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No modes available." }) : modes.map((opt, i) => /* @__PURE__ */ jsxs(
6167
+ Text,
6168
+ {
6169
+ inverse: i === selected,
6170
+ ...i === selected ? { color: "cyan" } : {},
6171
+ children: [
6172
+ i === selected ? "\u203A " : " ",
6173
+ /* @__PURE__ */ jsx(Text, { bold: true, children: opt.name.padEnd(20) }),
6174
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description }),
6175
+ opt.isActive ? /* @__PURE__ */ jsx(Text, { color: "green", children: " [active]" }) : null
6176
+ ]
6177
+ },
6178
+ opt.id
6179
+ )),
6180
+ hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
6181
+ ] });
6182
+ }
5825
6183
  function CheckpointTimeline({
5826
6184
  checkpoints,
5827
6185
  selected,
@@ -7150,29 +7508,28 @@ function visualEdit(toolName, text, ok) {
7150
7508
  return rows;
7151
7509
  }
7152
7510
  function visualRead(text) {
7511
+ const header = parseHeaderLine(text);
7153
7512
  const lines = bodyLines(text);
7154
7513
  const numbered = lines.map((line) => line.match(/^\s*(\d+)→(.*)$/)).filter((match) => Boolean(match?.[1]));
7155
7514
  if (numbered.length === 0) {
7156
- const first = firstNonEmpty(lines.join("\n"));
7157
- return first ? [{ kind: "meta", text: first }] : void 0;
7515
+ const first2 = firstNonEmpty(lines.join("\n"));
7516
+ return first2 ? [{ kind: "meta", text: first2 }] : void 0;
7158
7517
  }
7159
- const rows = numbered.slice(0, 10).map((match) => ({
7160
- kind: "code",
7161
- lineNo: match[1],
7162
- text: match[2] ?? ""
7163
- }));
7164
- if (numbered.length > 10) {
7165
- const extra = numbered.slice(10, 15).map((match) => ({
7166
- kind: "code",
7167
- lineNo: match[1],
7168
- text: match[2] ?? ""
7169
- }));
7170
- rows.push(...extra);
7171
- if (numbered.length > 15) {
7172
- rows.push({ kind: "meta", text: `more ${numbered.length - 15} line(s)` });
7173
- }
7518
+ const first = Number.parseInt(numbered[0]?.[1] ?? "", 10);
7519
+ const last = Number.parseInt(numbered[numbered.length - 1]?.[1] ?? "", 10);
7520
+ const total = numberFromParsedField(header.fields, "total_lines");
7521
+ const parts = [];
7522
+ if (Number.isFinite(first) && Number.isFinite(last)) {
7523
+ parts.push(first === last ? `L${first}` : `L${first}\u2013${last}`);
7174
7524
  }
7175
- return rows;
7525
+ const contiguous = Number.isFinite(first) && Number.isFinite(last) ? numbered.length === last - first + 1 : true;
7526
+ parts.push(
7527
+ `${numbered.length} line${numbered.length === 1 ? "" : "s"}${contiguous ? "" : " (gaps)"}`
7528
+ );
7529
+ if (total !== void 0 && total !== numbered.length) parts.push(`${total} total`);
7530
+ if (header.fields["truncated"] === "true") parts.push("truncated");
7531
+ if (header.fields["cached"] === "true") parts.push("cached");
7532
+ return [{ kind: "meta", text: parts.join(" \xB7 ") }];
7176
7533
  }
7177
7534
  function visualSearch(toolName, text) {
7178
7535
  const json = tryParseJson(text);
@@ -7894,6 +8251,7 @@ function numberFromParsedField(fields, key) {
7894
8251
  }
7895
8252
  var MAX_STREAM_DISPLAY_CHARS = 480;
7896
8253
  var MAX_STREAM_LINES = 8;
8254
+ var WRITE_CREATE_STREAM_LINES = 3;
7897
8255
  function streamBoxRows(text, maxLines, contentWidth) {
7898
8256
  const trunc = (line) => line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
7899
8257
  const lines = text.split("\n");
@@ -7922,21 +8280,23 @@ var ToolStreamBox = React5.memo(function ToolStreamBox2({
7922
8280
  return () => clearInterval(t);
7923
8281
  }, []);
7924
8282
  const elapsedMs = Date.now() - startedAt;
8283
+ const streamLines = name === "write" ? WRITE_CREATE_STREAM_LINES : MAX_STREAM_LINES;
7925
8284
  const totalLines = text.split("\n").length;
7926
- const hidden = Math.max(0, totalLines - MAX_STREAM_LINES);
8285
+ const hidden = Math.max(0, totalLines - streamLines);
7927
8286
  const contentWidth = Math.max(20, Math.min(termWidth - 4, 100));
7928
- const rows = streamBoxRows(text, MAX_STREAM_LINES, contentWidth);
8287
+ const rows = streamBoxRows(text, streamLines, contentWidth);
8288
+ const isWritePreview = name === "write";
7929
8289
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
7930
8290
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
7931
8291
  /* @__PURE__ */ jsxs(Text, { color, children: [
7932
8292
  glyph2,
7933
8293
  " "
7934
8294
  ] }),
7935
- /* @__PURE__ */ jsx(Text, { bold: true, color, children: name }),
8295
+ /* @__PURE__ */ jsx(Text, { bold: true, color, children: isWritePreview ? "write \xB7 creating file" : name }),
7936
8296
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
7937
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
8297
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${streamLines})` }) : null
7938
8298
  ] }),
7939
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 2, children: rows.map((r, i) => /* @__PURE__ */ jsx(Text, { dimColor: true, italic: Boolean(r.italic), children: r.text || " " }, i)) })
8299
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 2, children: rows.map((r, i) => /* @__PURE__ */ jsx(Text, { color: isWritePreview ? "gray" : void 0, dimColor: !isWritePreview, italic: Boolean(r.italic), children: r.text || " " }, i)) })
7940
8300
  ] });
7941
8301
  });
7942
8302
  function tailForDisplay(text, maxChars) {
@@ -7949,7 +8309,7 @@ function tailForDisplay(text, maxChars) {
7949
8309
  return `\u2026 ${text.slice(cut)}`;
7950
8310
  }
7951
8311
  var MAX_CODE_LINES = 80;
7952
- var DIFF_MAX_LINES = 12;
8312
+ var DIFF_MAX_LINES = Number.POSITIVE_INFINITY;
7953
8313
  var DIFF_LINE_SAFETY_CAP = 4e3;
7954
8314
  var DIFF_FALLBACK_WRAP_WIDTH = 100;
7955
8315
  function CodeBlock({
@@ -8073,9 +8433,17 @@ function formatDiffStats(added, removed) {
8073
8433
  const joined = parts.join(", ");
8074
8434
  return joined.charAt(0).toUpperCase() + joined.slice(1);
8075
8435
  }
8076
- function renderTokens(tokens) {
8077
- if (tokens.length === 0) return " ";
8078
- return tokens.map((t, j) => /* @__PURE__ */ jsx(
8436
+ function applyWashTokens(tokens, onWash) {
8437
+ if (!onWash) return tokens;
8438
+ return tokens.map((t) => {
8439
+ if (t.color !== "gray") return t;
8440
+ return { ...t, color: "grayBright", dim: false };
8441
+ });
8442
+ }
8443
+ function renderTokens(tokens, onWash = false) {
8444
+ const effective = applyWashTokens(tokens, onWash);
8445
+ if (effective.length === 0) return " ";
8446
+ return effective.map((t, j) => /* @__PURE__ */ jsx(
8079
8447
  Text,
8080
8448
  {
8081
8449
  dimColor: Boolean(t.dim),
@@ -8189,7 +8557,7 @@ function DiffBlock({
8189
8557
  /* @__PURE__ */ jsx(Text, { color: markerColor, bold: true, children: marker }),
8190
8558
  /* @__PURE__ */ jsx(Text, { children: " " })
8191
8559
  ] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: contPrefix }),
8192
- renderTokens(seg),
8560
+ renderTokens(seg, true),
8193
8561
  pad > 0 ? /* @__PURE__ */ jsx(Text, { children: " ".repeat(pad) }) : null
8194
8562
  ] }, si);
8195
8563
  }) }, key);
@@ -8456,7 +8824,14 @@ function extractUnifiedDiffText(text) {
8456
8824
  (line) => line.startsWith("--- ") || line.startsWith("diff --git") || line.startsWith("@@")
8457
8825
  );
8458
8826
  if (start === -1) return void 0;
8459
- return lines.slice(start).join("\n");
8827
+ let end = lines.length;
8828
+ for (let i = start + 1; i < lines.length; i++) {
8829
+ if (lines[i]?.length === 0) {
8830
+ end = i;
8831
+ break;
8832
+ }
8833
+ }
8834
+ return lines.slice(start, end).join("\n");
8460
8835
  }
8461
8836
  function newFileDiffFromWriteInput(output, input) {
8462
8837
  if (!input || typeof input !== "object") return void 0;
@@ -8596,13 +8971,13 @@ function fmtElapsed2(at, nowTick) {
8596
8971
  function GoalRow({
8597
8972
  goal
8598
8973
  }) {
8599
- const statusColor = {
8974
+ const statusColor2 = {
8600
8975
  active: "green",
8601
8976
  paused: "yellow",
8602
8977
  completed: "gray",
8603
8978
  failed: "red"
8604
8979
  };
8605
- const color = statusColor[goal.status] ?? "gray";
8980
+ const color = statusColor2[goal.status] ?? "gray";
8606
8981
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
8607
8982
  /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color, bold: true, children: [
8608
8983
  goal.status === "active" ? "\u25B6" : goal.status === "paused" ? "\u23F8" : goal.status === "completed" ? "\u2713" : "\u2717",
@@ -8964,9 +9339,188 @@ function FilePicker({ query, matches, selected }) {
8964
9339
  function highlight(path8, _query) {
8965
9340
  return path8;
8966
9341
  }
8967
- function FleetPanel({
8968
- entries,
9342
+ var TERMINAL_TTL_MS = 9e4;
9343
+ var METER_WIDTH = 10;
9344
+ function isLeaderEntry2(entry) {
9345
+ return entry.id === "leader" || entry.name === "LEADER";
9346
+ }
9347
+ function statusColor(status) {
9348
+ if (status === "running") return "green";
9349
+ if (status === "success") return "green";
9350
+ if (status === "failed") return "red";
9351
+ if (status === "timeout") return "yellow";
9352
+ if (status === "stopped") return "gray";
9353
+ return "gray";
9354
+ }
9355
+ function fmtShortDuration2(ms) {
9356
+ if (ms < 1e3) return `${Math.max(0, Math.round(ms))}ms`;
9357
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)}s`;
9358
+ const m = Math.floor(ms / 6e4);
9359
+ const s2 = Math.floor(ms % 6e4 / 1e3);
9360
+ return `${m}m${s2.toString().padStart(2, "0")}s`;
9361
+ }
9362
+ function fmtCost2(n) {
9363
+ if (n <= 0) return "";
9364
+ return `$${n.toFixed(4)}`;
9365
+ }
9366
+ function normalizeInlineText(text) {
9367
+ return text.replace(/\s+/g, " ").trim();
9368
+ }
9369
+ function truncToWidth(text, width) {
9370
+ if (width <= 0) return "";
9371
+ if (text.length <= width) return text;
9372
+ if (width === 1) return "\u2026";
9373
+ return `${text.slice(0, width - 1)}\u2026`;
9374
+ }
9375
+ function displayName(entry, roster) {
9376
+ const rosterName = roster?.[entry.id]?.name;
9377
+ const name = rosterName || entry.name || entry.id;
9378
+ return normalizeInlineText(name) || entry.id.slice(0, 8);
9379
+ }
9380
+ function activityText(entry, now) {
9381
+ if (entry.currentTool) {
9382
+ return `${entry.currentTool.name} ${fmtShortDuration2(now - entry.currentTool.startedAt)}`;
9383
+ }
9384
+ const streaming = normalizeInlineText(entry.streamingText);
9385
+ if (entry.status === "running" && streaming) return streaming;
9386
+ const lastMessage = entry.recentMessages.at(-1);
9387
+ if (lastMessage) return normalizeInlineText(lastMessage.text);
9388
+ const lastTool = entry.recentTools.at(-1);
9389
+ if (lastTool) {
9390
+ const ok = lastTool.ok === false ? "failed" : "done";
9391
+ return `${ok} ${lastTool.name}`;
9392
+ }
9393
+ if (entry.status === "running") return "thinking";
9394
+ if (entry.status === "idle") return "standing by";
9395
+ if (entry.status === "success") return "complete";
9396
+ if (entry.status === "timeout") return "timeout";
9397
+ if (entry.status === "stopped") return "stopped";
9398
+ return entry.failureReason ? `failed ${entry.failureReason}` : "failed";
9399
+ }
9400
+ function swarmMeter(entry, now, width = METER_WIDTH) {
9401
+ if (width <= 0) return "";
9402
+ if (entry.status === "success") return "\u2588".repeat(width);
9403
+ if (entry.status === "failed" || entry.status === "timeout" || entry.status === "stopped") {
9404
+ return "\u25A0".repeat(width);
9405
+ }
9406
+ if (entry.status === "idle") return "\u2591".repeat(width);
9407
+ const elapsedBucket = Math.floor(Math.max(0, now - entry.startedAt) / 1e3);
9408
+ const phase = (elapsedBucket + entry.iterations + entry.toolCalls) % width;
9409
+ const filled = Math.max(Math.min(width, 2), Math.min(width, phase + 1));
9410
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
9411
+ }
9412
+ function swarmColumnCount(width) {
9413
+ if (width < 54) return 1;
9414
+ return Math.max(1, Math.min(5, Math.floor(width / 38)));
9415
+ }
9416
+ function selectSwarmEntries(entries, now, opts = {}) {
9417
+ const terminalTtlMs = opts.terminalTtlMs ?? TERMINAL_TTL_MS;
9418
+ return Object.values(entries).filter((entry) => {
9419
+ if (isLeaderEntry2(entry) && !opts.includeLeader) return false;
9420
+ if (entry.status === "running" || entry.status === "idle") return true;
9421
+ return now - entry.lastEventAt <= terminalTtlMs;
9422
+ }).sort((a, b) => {
9423
+ const rank = (entry) => {
9424
+ if (entry.status === "running") return 0;
9425
+ if (entry.status === "idle") return 1;
9426
+ return 2;
9427
+ };
9428
+ const ar = rank(a);
9429
+ const br = rank(b);
9430
+ if (ar !== br) return ar - br;
9431
+ if (ar === 2) return b.lastEventAt - a.lastEventAt;
9432
+ return a.startedAt - b.startedAt;
9433
+ });
9434
+ }
9435
+ function buildSwarmGrid(entries, now, width, opts = {}) {
9436
+ const columns = swarmColumnCount(width);
9437
+ const gapWidth = Math.max(0, columns - 1) * 2;
9438
+ const tileWidth = Math.max(24, Math.floor((Math.max(24, width) - gapWidth) / columns));
9439
+ const maxRows = opts.maxRows ?? 3;
9440
+ const maxCells = Math.max(1, columns * maxRows);
9441
+ const selected = selectSwarmEntries(entries, now, { includeLeader: opts.includeLeader });
9442
+ const hasOverflow = selected.length > maxCells;
9443
+ const shown = hasOverflow ? selected.slice(0, maxCells - 1) : selected.slice(0, maxCells);
9444
+ const bodyWidth = Math.max(6, tileWidth - 3 - 1 - (METER_WIDTH + 2) - 1);
9445
+ const cells = shown.map((entry, i) => {
9446
+ const name = displayName(entry, opts.roster);
9447
+ const label = `${name} \xB7 ${activityText(entry, now)}`;
9448
+ return {
9449
+ id: entry.id,
9450
+ index: String(i + 1).padStart(3, "0"),
9451
+ meter: swarmMeter(entry, now),
9452
+ text: truncToWidth(label, bodyWidth),
9453
+ color: statusColor(entry.status),
9454
+ dim: entry.status === "idle"
9455
+ };
9456
+ });
9457
+ const overflow = selected.length - shown.length;
9458
+ if (overflow > 0) {
9459
+ cells.push({
9460
+ id: "__overflow",
9461
+ index: "+++",
9462
+ meter: "\u2591".repeat(METER_WIDTH),
9463
+ text: truncToWidth(`${overflow} more agents`, bodyWidth),
9464
+ color: "gray",
9465
+ dim: true
9466
+ });
9467
+ }
9468
+ const rows = [];
9469
+ for (let i = 0; i < cells.length; i += columns) rows.push(cells.slice(i, i + columns));
9470
+ return { columns, tileWidth, overflow, rows };
9471
+ }
9472
+ function buildTodoPreviewRows(todos, width, maxRows = 5) {
9473
+ if (!todos || todos.length === 0 || maxRows <= 0) return [];
9474
+ const ordered = [...todos].sort((a, b) => {
9475
+ const rank = (t) => t.status === "in_progress" ? 0 : t.status === "pending" ? 1 : 2;
9476
+ return rank(a) - rank(b);
9477
+ });
9478
+ const shown = ordered.slice(0, maxRows);
9479
+ const textWidth = Math.max(4, width - 5);
9480
+ const rows = shown.map((todo) => {
9481
+ const label = todo.status === "in_progress" && todo.activeForm ? todo.activeForm : todo.content;
9482
+ if (todo.status === "completed") {
9483
+ return {
9484
+ id: todo.id,
9485
+ marker: "\u2713",
9486
+ text: truncToWidth(label, textWidth),
9487
+ color: "green",
9488
+ dim: true
9489
+ };
9490
+ }
9491
+ if (todo.status === "in_progress") {
9492
+ return {
9493
+ id: todo.id,
9494
+ marker: "\u25CF",
9495
+ text: truncToWidth(label, textWidth),
9496
+ color: "blue"
9497
+ };
9498
+ }
9499
+ return {
9500
+ id: todo.id,
9501
+ marker: "\u25CB",
9502
+ text: truncToWidth(label, textWidth),
9503
+ color: "gray"
9504
+ };
9505
+ });
9506
+ const overflow = ordered.length - shown.length;
9507
+ if (overflow > 0) {
9508
+ rows.push({
9509
+ id: "__todo_overflow",
9510
+ marker: "+",
9511
+ text: truncToWidth(`${overflow} more`, textWidth),
9512
+ color: "gray",
9513
+ dim: true
9514
+ });
9515
+ }
9516
+ return rows;
9517
+ }
9518
+ function FleetPanel({
9519
+ entries,
8969
9520
  totalCost,
9521
+ roster,
9522
+ todos,
9523
+ nowTick,
8970
9524
  collabSession
8971
9525
  }) {
8972
9526
  const { stdout } = useStdout();
@@ -8980,52 +9534,54 @@ function FleetPanel({
8980
9534
  };
8981
9535
  }, [stdout]);
8982
9536
  const list = Object.values(entries);
8983
- if (list.length === 0 && !collabSession) return null;
8984
- const nameMaxLen = Math.max(6, Math.min(14, termWidth - 30));
8985
9537
  const leader = list.find((e) => e.id === "leader");
8986
- const subagents = list.filter((e) => e.id !== "leader");
8987
- const running = subagents.filter((e) => e.status === "running");
8988
- const runningCount = running.length;
9538
+ const subagents = list.filter((e) => !isLeaderEntry2(e));
8989
9539
  const hasCollab = !!collabSession;
8990
- const costLabel = totalCost > 0 ? ` \xB7 ${totalCost.toFixed(4)}` : "";
8991
- const collabLabel = hasCollab && collabSession.sessionId ? ` \xB7 collab(${collabSession.bugCount}b/${collabSession.planCount}p/${collabSession.evalCount}e)` : "";
8992
- const summaryLine = runningCount > 0 ? `${runningCount} running${costLabel}${collabLabel}` : `idle${costLabel}${collabLabel}`;
8993
- const shown = running.slice(0, 5);
8994
- const overflow = running.length > 5 ? running.length - 5 : 0;
8995
- const leaderTool = hasCollab ? "waiting for agents" : leader?.currentTool?.name ?? (leader?.status === "running" ? "running" : "\u2014");
9540
+ const now = nowTick ?? Date.now();
9541
+ const contentWidth = Math.max(40, termWidth - 2);
9542
+ const includeLeader = hasCollab || leader?.status === "running";
9543
+ const grid = buildSwarmGrid(entries, now, contentWidth, {
9544
+ includeLeader,
9545
+ maxRows: termWidth >= 120 ? 3 : 4,
9546
+ roster
9547
+ });
9548
+ const todoRows = buildTodoPreviewRows(todos, contentWidth, 5);
9549
+ const hasSwarm = grid.rows.length > 0;
9550
+ if (!hasSwarm && todoRows.length === 0 && !hasCollab) return null;
9551
+ const runningCount = subagents.filter((e) => e.status === "running").length;
9552
+ const idleCount = subagents.filter((e) => e.status === "idle").length;
9553
+ const doneCount = subagents.filter((e) => e.status === "success").length;
9554
+ const failedCount = subagents.filter(
9555
+ (e) => e.status === "failed" || e.status === "timeout"
9556
+ ).length;
9557
+ const summaryBits = [
9558
+ runningCount > 0 ? `${runningCount} running` : "idle",
9559
+ idleCount > 0 ? `${idleCount} idle` : "",
9560
+ doneCount > 0 ? `${doneCount} done` : "",
9561
+ failedCount > 0 ? `${failedCount} failed` : "",
9562
+ fmtCost2(totalCost)
9563
+ ].filter(Boolean);
9564
+ const collabLabel = hasCollab && collabSession.sessionId ? `collab ${collabSession.bugCount}b/${collabSession.planCount}p/${collabSession.evalCount}e` : "";
9565
+ if (collabLabel) summaryBits.push(collabLabel);
9566
+ const summary = summaryBits.join(" \xB7 ");
9567
+ const title = ` Agent Swarm${summary ? ` - ${summary}` : ""} `;
9568
+ const header = `\u2500${truncToWidth(title, Math.max(10, contentWidth - 1)).padEnd(contentWidth - 1, "\u2500")}`;
8996
9569
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
8997
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
8998
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u26A1 Fleet" }),
8999
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
9000
- /* @__PURE__ */ jsx(Text, { children: summaryLine })
9001
- ] }),
9002
- hasCollab && leader ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
9003
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u25CF" }),
9004
- /* @__PURE__ */ jsx(Text, { children: leader.name.slice(0, nameMaxLen) }),
9005
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2192" }),
9006
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: leaderTool })
9007
- ] }) : null,
9008
- shown.map((entry) => {
9009
- const name = entry.name && entry.name !== entry.id ? entry.name : entry.id.slice(0, nameMaxLen);
9010
- const tool = entry.currentTool?.name ?? "\u2014";
9011
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
9012
- /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF" }),
9013
- /* @__PURE__ */ jsx(Text, { children: name.slice(0, nameMaxLen) }),
9014
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2192" }),
9015
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: tool })
9016
- ] }, entry.id);
9017
- }),
9018
- overflow > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
9019
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
9020
- "+",
9021
- overflow,
9022
- ":"
9023
- ] }),
9024
- running.slice(5, 7).map((e) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
9025
- e.name?.split(" ")[0]?.slice(0, nameMaxLen - 2) ?? "agent",
9026
- ","
9027
- ] }, e.id)),
9028
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2026" })
9570
+ /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { color: "blue", children: header }) }),
9571
+ grid.rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, height: 1, children: row.map((cell) => /* @__PURE__ */ jsxs(Box, { width: grid.tileWidth, flexShrink: 0, children: [
9572
+ /* @__PURE__ */ jsx(Text, { color: "blue", children: cell.index }),
9573
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " [" }),
9574
+ /* @__PURE__ */ jsx(Text, { color: cell.color, children: cell.meter }),
9575
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "] " }),
9576
+ /* @__PURE__ */ jsx(Text, { color: cell.color, ...cell.dim ? { dimColor: true } : {}, wrap: "truncate", children: cell.text })
9577
+ ] }, cell.id)) }, `swarm-row-${rowIndex}`)),
9578
+ todoRows.length > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
9579
+ /* @__PURE__ */ jsx(Text, { color: "blue", children: "Todo" }),
9580
+ todoRows.map((row) => /* @__PURE__ */ jsxs(Box, { height: 1, children: [
9581
+ /* @__PURE__ */ jsx(Text, { color: row.color, children: row.marker }),
9582
+ /* @__PURE__ */ jsx(Text, { children: " " }),
9583
+ /* @__PURE__ */ jsx(Text, { color: row.color, ...row.dim ? { dimColor: true } : {}, wrap: "truncate", children: row.text })
9584
+ ] }, row.id))
9029
9585
  ] }) : null
9030
9586
  ] });
9031
9587
  }
@@ -9466,10 +10022,10 @@ function renderTable(tableLines, maxWidth) {
9466
10022
  const widths = computeWidths([header, ...dataRows], cols, maxWidth, sepWidths);
9467
10023
  const lines = [];
9468
10024
  lines.push(border("\u250C", "\u252C", "\u2510", widths));
9469
- lines.push(...renderRow(header, widths, aligns));
10025
+ lines.push(...renderRow2(header, widths, aligns));
9470
10026
  lines.push(border("\u251C", "\u253C", "\u2524", widths));
9471
10027
  for (const row of dataRows) {
9472
- lines.push(...renderRow(row, widths, aligns));
10028
+ lines.push(...renderRow2(row, widths, aligns));
9473
10029
  }
9474
10030
  lines.push(border("\u2514", "\u2534", "\u2518", widths));
9475
10031
  return lines.join("\n");
@@ -9640,7 +10196,7 @@ var ANSI_RESET_ALL = "\x1B[0m";
9640
10196
  function applyInlineAnsi(text) {
9641
10197
  return text.replace(/\*\*(.+?)\*\*/g, `${ANSI_BOLD}$1${ANSI_RESET_ALL}`).replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `${ANSI_DIM}$1${ANSI_RESET_ALL}`).replace(/`(.+?)`/g, `${ANSI_CYAN}$1${ANSI_RESET_ALL}`).replace(/~~(.+?)~~/g, `${ANSI_STRIKE}$1${ANSI_RESET_ALL}`);
9642
10198
  }
9643
- function renderRow(cells, widths, aligns) {
10199
+ function renderRow2(cells, widths, aligns) {
9644
10200
  const styled = cells.map((c) => applyInlineAnsi(c));
9645
10201
  const safe = styled.map((c) => breakLigatures(c));
9646
10202
  const wrapped = safe.map((c, i) => wrapCell(c, widths[i] ?? MIN_COL_WIDTH));
@@ -10233,6 +10789,27 @@ var Entry = React5.memo(function Entry2({
10233
10789
  ] })
10234
10790
  }
10235
10791
  );
10792
+ case "thinking": {
10793
+ const contentWidth = assistantContentWidth(termWidth);
10794
+ return /* @__PURE__ */ jsxs(
10795
+ Box,
10796
+ {
10797
+ flexDirection: "column",
10798
+ marginX: 0,
10799
+ marginY: 1,
10800
+ borderStyle: "single",
10801
+ borderTop: false,
10802
+ borderRight: false,
10803
+ borderBottom: false,
10804
+ borderColor: "magenta",
10805
+ paddingLeft: 1,
10806
+ children: [
10807
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "THINKING" }) }),
10808
+ /* @__PURE__ */ jsx(AssistantBody, { text: entry.text, termWidth, contentWidth })
10809
+ ]
10810
+ }
10811
+ );
10812
+ }
10236
10813
  case "assistant": {
10237
10814
  const contentWidth = assistantContentWidth(termWidth);
10238
10815
  const { steps, stripped } = nextSteps;
@@ -10732,21 +11309,21 @@ function inputIndexAtRowCol(prompt, value, width, row, col) {
10732
11309
  rows.push(cur);
10733
11310
  starts.push(curStart);
10734
11311
  }
10735
- const clamp = (n) => Math.max(0, Math.min(value.length, n));
11312
+ const clamp2 = (n) => Math.max(0, Math.min(value.length, n));
10736
11313
  if (row < 0) return 0;
10737
11314
  if (row >= rows.length) return value.length;
10738
11315
  const r = rows[row];
10739
11316
  const c = Math.max(0, col);
10740
11317
  if (c < r.length) {
10741
11318
  const b = r[c].buf;
10742
- return b < 0 ? 0 : clamp(b);
11319
+ return b < 0 ? 0 : clamp2(b);
10743
11320
  }
10744
11321
  for (let k = r.length - 1; k >= 0; k--) {
10745
11322
  const b = r[k].buf;
10746
- if (b >= 0) return clamp(b + 1);
11323
+ if (b >= 0) return clamp2(b + 1);
10747
11324
  }
10748
11325
  const start = starts[row];
10749
- return clamp(start !== void 0 && start >= 0 ? start : value.length);
11326
+ return clamp2(start !== void 0 && start >= 0 ? start : value.length);
10750
11327
  }
10751
11328
 
10752
11329
  // src/mouse.ts
@@ -10787,7 +11364,7 @@ function parseMouseEvents(data) {
10787
11364
  function isLeakedMouseInput(input) {
10788
11365
  return LEAKED_MOUSE_RE.test(input);
10789
11366
  }
10790
- function renderRow2(cells, rowKey, promptColor) {
11367
+ function renderRow3(cells, rowKey, promptColor) {
10791
11368
  const out = [];
10792
11369
  let run = "";
10793
11370
  let runStart = 0;
@@ -11002,11 +11579,456 @@ var Input = memo(function Input2({
11002
11579
  }
11003
11580
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
11004
11581
  rows.map(
11005
- (row, i) => row.length === 0 ? /* @__PURE__ */ jsx(Text, { children: " " }, i) : /* @__PURE__ */ jsx(Text, { children: renderRow2(row, `r${i}`, promptColor) }, i)
11582
+ (row, i) => row.length === 0 ? /* @__PURE__ */ jsx(Text, { children: " " }, i) : /* @__PURE__ */ jsx(Text, { children: renderRow3(row, `r${i}`, promptColor) }, i)
11006
11583
  ),
11007
11584
  hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
11008
11585
  ] });
11009
11586
  });
11587
+ function KanbanPanel({ projectRoot, onClose }) {
11588
+ const [boards, setBoards] = useState([]);
11589
+ const [selectedBoard, setSelectedBoard] = useState(0);
11590
+ const [selectedTask, setSelectedTask] = useState(0);
11591
+ const [board, setBoard] = useState(null);
11592
+ const [loading, setLoading] = useState(true);
11593
+ const [error, setError] = useState(null);
11594
+ const [notice, setNotice] = useState(null);
11595
+ const [prompt, setPrompt] = useState(null);
11596
+ const sortedColumns = useMemo(
11597
+ () => [...board?.columns ?? []].sort((a, b) => a.order - b.order),
11598
+ [board]
11599
+ );
11600
+ const visibleTasks = useMemo(() => {
11601
+ if (!board) return [];
11602
+ return sortedColumns.flatMap(
11603
+ (column) => board.tasks.filter((task) => task.columnId === column.id).sort((a, b) => a.order - b.order)
11604
+ );
11605
+ }, [board, sortedColumns]);
11606
+ const activeTask = visibleTasks[selectedTask] ?? null;
11607
+ const transferTarget = nextBoard(boards, board?.id);
11608
+ async function load(nextBoardIndex = selectedBoard, nextTaskIndex = selectedTask) {
11609
+ setLoading(true);
11610
+ setError(null);
11611
+ try {
11612
+ const summaries = await listBoards(projectRoot);
11613
+ setBoards(summaries);
11614
+ const clampedBoard = clamp(nextBoardIndex, 0, Math.max(0, summaries.length - 1));
11615
+ setSelectedBoard(clampedBoard);
11616
+ const active = summaries[clampedBoard];
11617
+ const loaded = active ? await getBoard(projectRoot, active.id) : null;
11618
+ setBoard(loaded);
11619
+ const taskCount = loaded?.tasks.length ?? 0;
11620
+ setSelectedTask(clamp(nextTaskIndex, 0, Math.max(0, taskCount - 1)));
11621
+ } catch (err) {
11622
+ setError(err instanceof Error ? err.message : String(err));
11623
+ } finally {
11624
+ setLoading(false);
11625
+ }
11626
+ }
11627
+ async function runMutation(fn, selection = {}) {
11628
+ setLoading(true);
11629
+ setError(null);
11630
+ try {
11631
+ const message = await fn();
11632
+ setNotice(message ?? null);
11633
+ await load(selection.boardIndex ?? selectedBoard, selection.taskIndex ?? selectedTask);
11634
+ } catch (err) {
11635
+ setError(err instanceof Error ? err.message : String(err));
11636
+ } finally {
11637
+ setLoading(false);
11638
+ }
11639
+ }
11640
+ async function commitPrompt() {
11641
+ if (!prompt) return;
11642
+ if (prompt.kind === "confirmDeleteTask") {
11643
+ setPrompt(null);
11644
+ await runMutation(async () => {
11645
+ if (!board) return null;
11646
+ await removeTask(projectRoot, board.id, prompt.task.id);
11647
+ return `Deleted task: ${prompt.task.title}`;
11648
+ });
11649
+ return;
11650
+ }
11651
+ const value = prompt.buffer.trim();
11652
+ if (!value) return;
11653
+ const kind = prompt.kind;
11654
+ setPrompt(null);
11655
+ await runMutation(
11656
+ async () => {
11657
+ if (kind === "createBoard") {
11658
+ const created = await createBoard(projectRoot, { title: value });
11659
+ return `Created board: ${created.title}`;
11660
+ }
11661
+ if (!board) return null;
11662
+ const added = await addTask(projectRoot, board.id, {
11663
+ title: value,
11664
+ columnId: sortedColumns[0]?.id ?? "backlog"
11665
+ });
11666
+ return added ? `Added task: ${added.task.title}` : "Task add failed";
11667
+ },
11668
+ kind === "createBoard" ? { boardIndex: 0, taskIndex: 0 } : { taskIndex: board?.tasks.length }
11669
+ );
11670
+ }
11671
+ useEffect(() => {
11672
+ void load(0, 0);
11673
+ }, [projectRoot]);
11674
+ useInput((input, key) => {
11675
+ if (prompt) {
11676
+ if (key.escape) {
11677
+ setPrompt(null);
11678
+ return;
11679
+ }
11680
+ if (prompt.kind === "confirmDeleteTask") {
11681
+ if (input.toLowerCase() === "y") void commitPrompt();
11682
+ else if (input.toLowerCase() === "n") setPrompt(null);
11683
+ return;
11684
+ }
11685
+ if (key.return || input === "\r" || input === "\n") {
11686
+ void commitPrompt();
11687
+ return;
11688
+ }
11689
+ if (key.backspace || key.delete) {
11690
+ setPrompt({ ...prompt, buffer: prompt.buffer.slice(0, -1) });
11691
+ return;
11692
+ }
11693
+ if (input && !key.ctrl && !key.meta && input >= " ") {
11694
+ setPrompt({ ...prompt, buffer: `${prompt.buffer}${input}` });
11695
+ }
11696
+ return;
11697
+ }
11698
+ if (key.escape || input === "q") {
11699
+ onClose();
11700
+ return;
11701
+ }
11702
+ if (input === "r" || input === "R") {
11703
+ void load(selectedBoard, selectedTask);
11704
+ return;
11705
+ }
11706
+ if (input === "c") {
11707
+ setPrompt({ kind: "createBoard", buffer: "" });
11708
+ return;
11709
+ }
11710
+ if (input === "a" && board) {
11711
+ setPrompt({ kind: "addTask", buffer: "" });
11712
+ return;
11713
+ }
11714
+ if ((input === "n" || key.downArrow) && boards.length > 0) {
11715
+ void load(clamp(selectedBoard + 1, 0, boards.length - 1), 0);
11716
+ return;
11717
+ }
11718
+ if ((input === "p" || key.upArrow) && boards.length > 0) {
11719
+ void load(clamp(selectedBoard - 1, 0, boards.length - 1), 0);
11720
+ return;
11721
+ }
11722
+ if (key.shift && key.tab && visibleTasks.length > 0) {
11723
+ setSelectedTask((idx) => (idx - 1 + visibleTasks.length) % visibleTasks.length);
11724
+ return;
11725
+ }
11726
+ if (key.tab && visibleTasks.length > 0) {
11727
+ setSelectedTask((idx) => (idx + 1) % visibleTasks.length);
11728
+ return;
11729
+ }
11730
+ if (key.rightArrow && board && activeTask) {
11731
+ const nextColumn = adjacentColumn(sortedColumns, activeTask.columnId, 1);
11732
+ if (nextColumn) {
11733
+ void runMutation(async () => {
11734
+ await moveTask(projectRoot, board.id, activeTask.id, nextColumn.id);
11735
+ return `Moved to ${nextColumn.title}`;
11736
+ });
11737
+ }
11738
+ return;
11739
+ }
11740
+ if (key.leftArrow && board && activeTask) {
11741
+ const prevColumn = adjacentColumn(sortedColumns, activeTask.columnId, -1);
11742
+ if (prevColumn) {
11743
+ void runMutation(async () => {
11744
+ await moveTask(projectRoot, board.id, activeTask.id, prevColumn.id);
11745
+ return `Moved to ${prevColumn.title}`;
11746
+ });
11747
+ }
11748
+ return;
11749
+ }
11750
+ if ((input === " " || input === "D") && board && activeTask) {
11751
+ void runMutation(async () => {
11752
+ await updateTask(projectRoot, board.id, activeTask.id, {
11753
+ status: "completed",
11754
+ columnId: doneColumnId(board) ?? activeTask.columnId
11755
+ });
11756
+ return `Completed task: ${activeTask.title}`;
11757
+ });
11758
+ return;
11759
+ }
11760
+ if (input === "b" && board && activeTask) {
11761
+ void runMutation(async () => {
11762
+ await updateTask(projectRoot, board.id, activeTask.id, { status: "blocked" });
11763
+ return `Blocked task: ${activeTask.title}`;
11764
+ });
11765
+ return;
11766
+ }
11767
+ if (input === "x" && activeTask) {
11768
+ setPrompt({ kind: "confirmDeleteTask", task: activeTask });
11769
+ return;
11770
+ }
11771
+ if (input === "d" && board) {
11772
+ void runMutation(
11773
+ async () => {
11774
+ const duplicated = await duplicateBoard(projectRoot, board.id, {
11775
+ title: `${board.title} Copy`,
11776
+ preserveAssignment: true
11777
+ });
11778
+ return duplicated ? `Duplicated board: ${duplicated.title}` : "Board duplicate failed";
11779
+ },
11780
+ { boardIndex: 0, taskIndex: selectedTask }
11781
+ );
11782
+ return;
11783
+ }
11784
+ if (input === "C" && board && activeTask && transferTarget) {
11785
+ void runMutation(async () => {
11786
+ await copyTaskToBoard(projectRoot, board.id, activeTask.id, transferTarget.id);
11787
+ return `Copied task to ${transferTarget.title}`;
11788
+ });
11789
+ return;
11790
+ }
11791
+ if (input === "T" && board && activeTask && transferTarget) {
11792
+ void runMutation(async () => {
11793
+ await transferTaskToBoard(projectRoot, board.id, activeTask.id, transferTarget.id, {
11794
+ preserveAssignment: true
11795
+ });
11796
+ return `Transferred task to ${transferTarget.title}`;
11797
+ });
11798
+ }
11799
+ });
11800
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
11801
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 1, children: [
11802
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "KANBAN" }),
11803
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
11804
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11805
+ boards.length,
11806
+ " board",
11807
+ boards.length === 1 ? "" : "s"
11808
+ ] }),
11809
+ board ? /* @__PURE__ */ jsxs(Fragment, { children: [
11810
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
11811
+ /* @__PURE__ */ jsx(Text, { children: board.title })
11812
+ ] }) : null,
11813
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "| n/p board | Tab task | left/right move | c/a/d create/add/dup | C/T copy/transfer | Esc close" })
11814
+ ] }),
11815
+ prompt ? /* @__PURE__ */ jsx(PromptLine, { prompt }) : null,
11816
+ notice && !prompt ? /* @__PURE__ */ jsx(Text, { color: "green", wrap: "truncate", children: notice }) : null,
11817
+ loading ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading kanban..." }) : error ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
11818
+ "Error: ",
11819
+ error
11820
+ ] }) : !board ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
11821
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No kanban boards yet." }),
11822
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11823
+ "Press ",
11824
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: "c" }),
11825
+ " to create one, or use",
11826
+ " ",
11827
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: "/kanban create <title>" }),
11828
+ "."
11829
+ ] })
11830
+ ] }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
11831
+ /* @__PURE__ */ jsx(BoardSummary, { board, target: transferTarget }),
11832
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
11833
+ /* @__PURE__ */ jsx(BoardList, { boards, selected: selectedBoard }),
11834
+ /* @__PURE__ */ jsx(BoardColumns, { board, activeTaskId: activeTask?.id }),
11835
+ /* @__PURE__ */ jsx(TaskDetail, { board, task: activeTask, target: transferTarget })
11836
+ ] })
11837
+ ] })
11838
+ ] });
11839
+ }
11840
+ function PromptLine({ prompt }) {
11841
+ if (prompt.kind === "confirmDeleteTask") {
11842
+ return /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
11843
+ 'Delete "',
11844
+ prompt.task.title,
11845
+ '"? y/n'
11846
+ ] });
11847
+ }
11848
+ const label = prompt.kind === "createBoard" ? "New board title" : "New task title";
11849
+ return /* @__PURE__ */ jsxs(Text, { children: [
11850
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
11851
+ label,
11852
+ ": "
11853
+ ] }),
11854
+ prompt.buffer,
11855
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "_" })
11856
+ ] });
11857
+ }
11858
+ function BoardList({
11859
+ boards,
11860
+ selected
11861
+ }) {
11862
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: 24, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
11863
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Boards" }),
11864
+ boards.slice(0, 12).map((item, index) => /* @__PURE__ */ jsxs(Text, { color: index === selected ? "cyan" : void 0, wrap: "truncate", children: [
11865
+ index === selected ? ">" : " ",
11866
+ " ",
11867
+ item.title,
11868
+ " (",
11869
+ item.taskCount,
11870
+ ")"
11871
+ ] }, item.id)),
11872
+ boards.length > 12 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11873
+ "... ",
11874
+ boards.length - 12,
11875
+ " more"
11876
+ ] }) : null
11877
+ ] });
11878
+ }
11879
+ function BoardColumns({
11880
+ board,
11881
+ activeTaskId
11882
+ }) {
11883
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 1, children: [...board.columns].sort((a, b) => a.order - b.order).slice(0, 5).map((column) => {
11884
+ const allTasks = board.tasks.filter((task) => task.columnId === column.id).sort((a, b) => a.order - b.order);
11885
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: 26, children: [
11886
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", wrap: "truncate", children: [
11887
+ column.title,
11888
+ " (",
11889
+ allTasks.length,
11890
+ ")"
11891
+ ] }),
11892
+ allTasks.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " empty" }) : allTasks.slice(0, 8).map((task) => /* @__PURE__ */ jsxs(
11893
+ Text,
11894
+ {
11895
+ color: task.id === activeTaskId ? "cyan" : void 0,
11896
+ wrap: "truncate",
11897
+ children: [
11898
+ task.id === activeTaskId ? ">" : " ",
11899
+ " ",
11900
+ statusIcon(task.status),
11901
+ " ",
11902
+ task.title,
11903
+ task.assignedAgent ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11904
+ " @",
11905
+ task.assignedAgent
11906
+ ] }) : null
11907
+ ]
11908
+ },
11909
+ task.id
11910
+ )),
11911
+ allTasks.length > 8 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11912
+ " ... ",
11913
+ allTasks.length - 8,
11914
+ " more"
11915
+ ] }) : null
11916
+ ] }, column.id);
11917
+ }) });
11918
+ }
11919
+ function TaskDetail({
11920
+ board,
11921
+ task,
11922
+ target
11923
+ }) {
11924
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: 34, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
11925
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Task" }),
11926
+ !task ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No task selected." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
11927
+ /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: task.title }),
11928
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11929
+ "ID ",
11930
+ task.id.slice(0, 8)
11931
+ ] }),
11932
+ /* @__PURE__ */ jsxs(Text, { children: [
11933
+ task.priority.toUpperCase(),
11934
+ " / ",
11935
+ task.status
11936
+ ] }),
11937
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11938
+ "Column ",
11939
+ columnTitle(board, task.columnId)
11940
+ ] }),
11941
+ task.description ? /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: task.description }) : null,
11942
+ task.assignment ? /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
11943
+ "Agent ",
11944
+ task.assignment.agentId ?? task.assignedAgent ?? task.assignment.role ?? "-",
11945
+ " /",
11946
+ " ",
11947
+ task.assignment.status
11948
+ ] }) : task.assignedAgent ? /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
11949
+ "Agent ",
11950
+ task.assignedAgent
11951
+ ] }) : null,
11952
+ task.dependsOn?.length ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11953
+ "Depends on ",
11954
+ task.dependsOn.length
11955
+ ] }) : null,
11956
+ target ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11957
+ "C/T target: ",
11958
+ target.title
11959
+ ] }) : null,
11960
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "space done | b block | x delete" })
11961
+ ] })
11962
+ ] });
11963
+ }
11964
+ function BoardSummary({
11965
+ board,
11966
+ target
11967
+ }) {
11968
+ const done = board.tasks.filter((task) => task.status === "completed").length;
11969
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 1, children: [
11970
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11971
+ "ID ",
11972
+ board.id.slice(0, 8)
11973
+ ] }),
11974
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
11975
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11976
+ board.columns.length,
11977
+ " columns"
11978
+ ] }),
11979
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
11980
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11981
+ board.tasks.length,
11982
+ " tasks"
11983
+ ] }),
11984
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
11985
+ /* @__PURE__ */ jsxs(Text, { color: done === board.tasks.length && done > 0 ? "green" : "yellow", children: [
11986
+ done,
11987
+ " done"
11988
+ ] }),
11989
+ target ? /* @__PURE__ */ jsxs(Fragment, { children: [
11990
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
11991
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11992
+ "next board: ",
11993
+ target.title
11994
+ ] })
11995
+ ] }) : null
11996
+ ] });
11997
+ }
11998
+ function nextBoard(boards, boardId) {
11999
+ if (!boardId || boards.length < 2) return void 0;
12000
+ const index = boards.findIndex((item) => item.id === boardId);
12001
+ if (index === -1) return boards.find((item) => item.id !== boardId);
12002
+ for (let offset = 1; offset < boards.length; offset++) {
12003
+ const candidate = boards[(index + offset) % boards.length];
12004
+ if (candidate && candidate.id !== boardId) return candidate;
12005
+ }
12006
+ return void 0;
12007
+ }
12008
+ function adjacentColumn(columns, columnId, delta) {
12009
+ const index = columns.findIndex((column) => column.id === columnId);
12010
+ if (index === -1) return void 0;
12011
+ return columns[index + delta];
12012
+ }
12013
+ function doneColumnId(board) {
12014
+ return board.columns.find(
12015
+ (column) => ["done", "completed", "finished"].includes(column.id.toLowerCase())
12016
+ )?.id;
12017
+ }
12018
+ function columnTitle(board, columnId) {
12019
+ return board.columns.find((column) => column.id === columnId)?.title ?? columnId;
12020
+ }
12021
+ function statusIcon(status) {
12022
+ if (status === "completed") return "x";
12023
+ if (status === "blocked") return "!";
12024
+ if (status === "in_progress") return ">";
12025
+ if (status === "failed") return "!";
12026
+ if (status === "review") return "?";
12027
+ return "-";
12028
+ }
12029
+ function clamp(value, min, max) {
12030
+ return Math.max(min, Math.min(max, value));
12031
+ }
11010
12032
  function hintsFor(ctx) {
11011
12033
  if (ctx.confirm) {
11012
12034
  return [
@@ -11174,33 +12196,46 @@ function ModelPicker({
11174
12196
  hint
11175
12197
  }) {
11176
12198
  if (step === "provider") {
11177
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
11178
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
12199
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, children: [
12200
+ /* @__PURE__ */ jsx(Text, { color: UI_COLORS.title, bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
11179
12201
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
11180
- providerOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no providers with keys \u2014 add one via `wstack auth`)" }) : providerOptions.map((p, i) => /* @__PURE__ */ jsxs(Text, { inverse: i === selected, ...i === selected ? { color: "cyan" } : {}, children: [
11181
- i === selected ? "\u203A " : " ",
11182
- /* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
11183
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11184
- " [",
11185
- p.family,
11186
- "]"
11187
- ] }),
11188
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11189
- " ",
11190
- p.models.length,
11191
- " model",
11192
- p.models.length === 1 ? "" : "s"
11193
- ] })
11194
- ] }, p.id)),
11195
- hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
11196
- ] });
12202
+ providerOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no providers with keys \u2014 add one via `wstack auth`)" }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", minHeight: providerOptions.length, children: providerOptions.map((p, i) => {
12203
+ const isSelected = i === selected;
12204
+ const famColor = colorForFamily(p.family);
12205
+ return /* @__PURE__ */ jsxs(
12206
+ Text,
12207
+ {
12208
+ inverse: isSelected,
12209
+ ...isSelected ? { color: UI_COLORS.focused } : {},
12210
+ children: [
12211
+ isSelected ? "\u203A " : " ",
12212
+ /* @__PURE__ */ jsx(Text, { bold: true, color: isSelected ? void 0 : famColor, children: p.id.padEnd(28) }),
12213
+ /* @__PURE__ */ jsxs(Text, { color: isSelected ? void 0 : famColor, dimColor: !isSelected, children: [
12214
+ " ",
12215
+ "[",
12216
+ p.family,
12217
+ "]"
12218
+ ] }),
12219
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12220
+ " ",
12221
+ p.models.length,
12222
+ " model",
12223
+ p.models.length === 1 ? "" : "s"
12224
+ ] })
12225
+ ]
12226
+ },
12227
+ p.id
12228
+ );
12229
+ }) }),
12230
+ hint ? /* @__PURE__ */ jsx(Text, { color: UI_COLORS.hint, children: hint }) : null
12231
+ ] });
11197
12232
  }
11198
12233
  const total = filteredOptions.length;
11199
12234
  const { start, end } = getVisibleWindow2(selected, total);
11200
12235
  const visibleItems = filteredOptions.slice(start, end);
11201
12236
  const searchHint = searchQuery ? ` | filter:"${searchQuery}" \u2192 ${total} match${total === 1 ? "" : "es"}` : total > MAX_VISIBLE2 ? ` (${total} models \u2014 type to filter)` : "";
11202
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
11203
- /* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
12237
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, children: [
12238
+ /* @__PURE__ */ jsxs(Text, { color: UI_COLORS.title, bold: true, children: [
11204
12239
  "\u2501\u2501 Switch model \u2014 Step 2/2: Pick model ",
11205
12240
  "(",
11206
12241
  pickedProviderId,
@@ -11209,7 +12244,7 @@ function ModelPicker({
11209
12244
  " \u2501\u2501"
11210
12245
  ] }),
11211
12246
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl+C exit \xB7 type to filter" }),
11212
- total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: searchQuery ? `(no models match "${searchQuery}")` : "(no models known for this provider)" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12247
+ total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: searchQuery ? `(no models match "${searchQuery}")` : "(no models known for this provider)" }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", minHeight: MAX_VISIBLE2 + 2, children: [
11213
12248
  start > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11214
12249
  "\u25B2 ",
11215
12250
  start,
@@ -11217,26 +12252,30 @@ function ModelPicker({
11217
12252
  ] }),
11218
12253
  visibleItems.map((id, vi) => {
11219
12254
  const absoluteIndex = start + vi;
12255
+ const isSelected = absoluteIndex === selected;
11220
12256
  return /* @__PURE__ */ jsxs(
11221
12257
  Text,
11222
12258
  {
11223
- inverse: absoluteIndex === selected,
11224
- ...absoluteIndex === selected ? { color: "cyan" } : {},
12259
+ inverse: isSelected,
12260
+ ...isSelected ? { color: UI_COLORS.selectedModel } : {},
11225
12261
  children: [
11226
- absoluteIndex === selected ? "\u203A " : " ",
12262
+ isSelected ? "\u203A " : " ",
11227
12263
  id
11228
12264
  ]
11229
12265
  },
11230
12266
  id
11231
12267
  );
11232
12268
  }),
12269
+ Array.from({ length: MAX_VISIBLE2 - visibleItems.length }).map(
12270
+ (_, i) => /* @__PURE__ */ jsx(Text, { children: " " }, `pad-${i}`)
12271
+ ),
11233
12272
  end < total && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
11234
12273
  "\u25BC ",
11235
12274
  total - end,
11236
12275
  " below"
11237
12276
  ] })
11238
12277
  ] }),
11239
- hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
12278
+ hint ? /* @__PURE__ */ jsx(Text, { color: UI_COLORS.hint, children: hint }) : null
11240
12279
  ] });
11241
12280
  }
11242
12281
  var fmtElapsed3 = (ms) => {
@@ -11564,7 +12603,7 @@ function PlanRow({ item }) {
11564
12603
  ] }) : null
11565
12604
  ] });
11566
12605
  }
11567
- var CHROME_ROWS = 17;
12606
+ var CHROME_ROWS2 = 17;
11568
12607
  function PluginPicker({
11569
12608
  items,
11570
12609
  selected,
@@ -11573,7 +12612,7 @@ function PluginPicker({
11573
12612
  }) {
11574
12613
  const { stdout } = useStdout();
11575
12614
  const rows = stdout?.rows ?? 24;
11576
- const maxVisible = Math.max(4, rows - CHROME_ROWS);
12615
+ const maxVisible = Math.max(4, rows - CHROME_ROWS2);
11577
12616
  const total = items.length;
11578
12617
  const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(selected - Math.floor(maxVisible / 2), total - maxVisible));
11579
12618
  const windowEnd = Math.min(windowStart + maxVisible, total);
@@ -12142,7 +13181,7 @@ function SddBoardOverlay({
12142
13181
  ] }) : null
12143
13182
  ] });
12144
13183
  }
12145
- function statusIcon(status) {
13184
+ function statusIcon2(status) {
12146
13185
  switch (status) {
12147
13186
  case "active":
12148
13187
  return "\u25CF";
@@ -12210,7 +13249,7 @@ function SessionsPanel({
12210
13249
  /* @__PURE__ */ jsxs(Box, { children: [
12211
13250
  /* @__PURE__ */ jsxs(Text, { inverse: idx === selected, color: s2.status === "active" ? "green" : s2.status === "idle" ? "cyan" : "yellow", children: [
12212
13251
  s2.sessionId === currentSessionId ? "\u25CF " : "",
12213
- statusIcon(s2.status),
13252
+ statusIcon2(s2.status),
12214
13253
  " "
12215
13254
  ] }),
12216
13255
  /* @__PURE__ */ jsx(Text, { bold: true, children: s2.projectName }),
@@ -12507,11 +13546,11 @@ function WorktreeMonitor({
12507
13546
  useInput((input, key) => {
12508
13547
  if (isWorktreeMonitorCloseKey(input, key)) onClose();
12509
13548
  });
12510
- const TERMINAL_TTL_MS = 5 * 6e4;
13549
+ const TERMINAL_TTL_MS2 = 5 * 6e4;
12511
13550
  const list = Object.values(worktrees);
12512
13551
  const recent = list.filter((w) => {
12513
13552
  if (["active", "committing", "merging", "needs-review"].includes(w.status)) return true;
12514
- return nowTick - (w.allocatedAt ?? nowTick) < TERMINAL_TTL_MS;
13553
+ return nowTick - (w.allocatedAt ?? nowTick) < TERMINAL_TTL_MS2;
12515
13554
  });
12516
13555
  const active = list.filter((w) => ["active", "committing", "merging"].includes(w.status)).length;
12517
13556
  const merged = list.filter((w) => w.status === "merged").length;
@@ -12573,350 +13612,121 @@ function WorktreeMonitor({
12573
13612
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 2, children: [
12574
13613
  /* @__PURE__ */ jsxs(Text, { color: "green", children: [
12575
13614
  "+",
12576
- w.insertions
12577
- ] }),
12578
- /* @__PURE__ */ jsxs(Text, { color: "red", children: [
12579
- "-",
12580
- w.deletions
12581
- ] }),
12582
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12583
- "\xB7 ",
12584
- w.files,
12585
- " files"
12586
- ] })
12587
- ] }),
12588
- w.conflictFiles && w.conflictFiles.length > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
12589
- "conflicts: ",
12590
- w.conflictFiles.join(", ")
12591
- ] }) }) : null
12592
- ] }, w.branch);
12593
- }),
12594
- /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
12595
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Esc / F4 to close \xB7 merge conflicts with /worktree merge <branch>" }),
12596
- staleTerminal > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${staleTerminal} terminal pruned` }) : null
12597
- ] })
12598
- ] });
12599
- }
12600
- var fmtElapsed6 = (ms) => {
12601
- const s2 = Math.floor(ms / 1e3);
12602
- const m = Math.floor(s2 / 60);
12603
- const h = Math.floor(m / 60);
12604
- if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
12605
- if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
12606
- return `${s2}s`;
12607
- };
12608
- var STATUS5 = {
12609
- allocating: { icon: "\u25CB", color: "gray" },
12610
- active: { icon: "\u25CF", color: "yellow" },
12611
- committing: { icon: "\u25D0", color: "cyan" },
12612
- merging: { icon: "\u21E1", color: "blue" },
12613
- merged: { icon: "\u2713", color: "green" },
12614
- "needs-review": { icon: "\u26A0", color: "magenta" },
12615
- failed: { icon: "\u2717", color: "red" }
12616
- };
12617
- function st(status) {
12618
- return STATUS5[status] ?? { icon: "?", color: "white" };
12619
- }
12620
- function WorktreePanel({
12621
- worktrees,
12622
- nowTick
12623
- }) {
12624
- const list = Object.values(worktrees);
12625
- if (list.length === 0) return null;
12626
- const active = list.filter(
12627
- (w) => w.status === "active" || w.status === "committing" || w.status === "merging"
12628
- ).length;
12629
- const merged = list.filter((w) => w.status === "merged").length;
12630
- const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
12631
- return /* @__PURE__ */ jsxs(
12632
- Box,
12633
- {
12634
- flexDirection: "column",
12635
- paddingX: 1,
12636
- borderStyle: "single",
12637
- borderTop: false,
12638
- borderBottom: false,
12639
- borderLeft: false,
12640
- borderRight: false,
12641
- children: [
12642
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
12643
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Worktrees" }),
12644
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
12645
- /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
12646
- "\u25B6",
12647
- active
12648
- ] }),
12649
- /* @__PURE__ */ jsxs(Text, { color: "green", children: [
12650
- "\u2713",
12651
- merged
12652
- ] }),
12653
- failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
12654
- "\u2717",
12655
- failed
12656
- ] }) : null,
12657
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12658
- "\xB7 ",
12659
- list.length,
12660
- " total \xB7 Ctrl+T for details"
12661
- ] })
12662
- ] }),
12663
- list.map((w) => {
12664
- const s2 = st(w.status);
12665
- const conflict = w.status === "needs-review";
12666
- const elapsed = w.allocatedAt ? fmtElapsed6(nowTick - w.allocatedAt) : "";
12667
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
12668
- /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.icon }),
12669
- /* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
12670
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: w.ownerLabel.slice(0, 12) }),
12671
- conflict ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: " CONFLICT" }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12672
- " ",
12673
- "+",
12674
- w.insertions,
12675
- "/-",
12676
- w.deletions,
12677
- " ",
12678
- w.files,
12679
- "f"
12680
- ] }),
12681
- elapsed && (w.status === "active" || w.status === "committing") ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
12682
- " ",
12683
- elapsed
12684
- ] }) : null
12685
- ] }, w.branch);
12686
- })
12687
- ]
12688
- }
12689
- );
12690
- }
12691
- var CHROME_ROWS2 = 18;
12692
- var LIST_ACTION_LABEL = {
12693
- catalog: "\uFF0B Add provider (models.dev catalog)",
12694
- local: "\uFF0B Add local server (OmniRoute / Ollama / vLLM / LM Studio)",
12695
- custom: "\uFF0B Add custom provider",
12696
- oauth: "\u26A1 Sign in with OAuth (ChatGPT / Claude / Copilot)"
12697
- };
12698
- var PROVIDER_ACTION_LABEL = {
12699
- "add-key": "\uFF0B Add another key",
12700
- "edit-family": "\u270E Edit family",
12701
- "edit-base-url": "\u270E Edit base URL",
12702
- "edit-models": "\u270E Edit visible model list",
12703
- remove: "\u2715 Remove this provider"
12704
- };
12705
- var OAUTH_LABEL = {
12706
- chatgpt: { title: "ChatGPT Plus/Pro", detail: "\u2192 openai-codex" },
12707
- claude: { title: "Claude Pro/Max", detail: "\u2192 anthropic-oauth" },
12708
- copilot: { title: "GitHub Copilot", detail: "\u2192 github-copilot" }
12709
- };
12710
- function keySummary(keyRow) {
12711
- const method = keyRow.authMethod === "oauth" ? " oauth" : "";
12712
- const created = keyRow.createdAt ? ` ${keyRow.createdAt.slice(0, 10)}` : "";
12713
- return `${keyRow.masked}${method}${created}`;
12714
- }
12715
- function renderRow3(row, focused, i) {
12716
- const marker = focused ? "\u203A" : " ";
12717
- const color = focused ? "cyan" : void 0;
12718
- switch (row.kind) {
12719
- case "provider": {
12720
- const p = row.provider;
12721
- const keys = p.keys.length === 0 ? "no keys" : p.keys.length === 1 ? `1 key ${p.keys[0]?.masked ?? ""}` : `${p.keys.length} keys`;
12722
- return /* @__PURE__ */ jsxs(Text, { color, wrap: "truncate-end", children: [
12723
- marker,
12724
- " ",
12725
- p.id.padEnd(22),
12726
- " ",
12727
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: (p.family ?? "\u2014").padEnd(18) }),
12728
- " ",
12729
- /* @__PURE__ */ jsx(Text, { color: p.keys.length > 0 ? "green" : "yellow", children: keys })
12730
- ] }, `p-${p.id}`);
12731
- }
12732
- case "list-action":
12733
- return /* @__PURE__ */ jsxs(Text, { color, wrap: "truncate-end", children: [
12734
- marker,
12735
- " ",
12736
- LIST_ACTION_LABEL[row.action]
12737
- ] }, `a-${row.action}`);
12738
- case "key": {
12739
- const k = row.keyRow;
12740
- return /* @__PURE__ */ jsxs(Text, { color, wrap: "truncate-end", children: [
12741
- marker,
12742
- " ",
12743
- /* @__PURE__ */ jsx(Text, { color: k.active ? "green" : "gray", children: k.active ? "\u25CF" : "\u25CB" }),
12744
- " ",
12745
- k.label.padEnd(20),
12746
- " ",
12747
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: keySummary(k) })
12748
- ] }, `k-${k.label}`);
12749
- }
12750
- case "provider-action":
12751
- return /* @__PURE__ */ jsxs(Text, { color, wrap: "truncate-end", children: [
12752
- marker,
12753
- " ",
12754
- PROVIDER_ACTION_LABEL[row.action]
12755
- ] }, `pa-${row.action}`);
12756
- case "catalog-entry": {
12757
- const c = row.entry;
12758
- return /* @__PURE__ */ jsxs(Text, { color, wrap: "truncate-end", children: [
12759
- marker,
12760
- " ",
12761
- /* @__PURE__ */ jsx(Text, { color: c.saved ? "green" : "gray", children: c.saved ? "\u25C9" : "\u25CB" }),
12762
- " ",
12763
- c.id.padEnd(24),
12764
- " ",
12765
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: c.family.padEnd(20) }),
12766
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: c.name !== c.id ? c.name : "" })
12767
- ] }, `c-${c.id}`);
12768
- }
12769
- case "local-preset": {
12770
- const p = row.preset;
12771
- return /* @__PURE__ */ jsxs(Text, { color, wrap: "truncate-end", children: [
12772
- marker,
12773
- " ",
12774
- p.label.padEnd(12),
12775
- " ",
12776
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: p.defaultBaseUrl.padEnd(28) }),
12777
- " ",
12778
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: p.noAuth ? "no auth" : "optional key" })
12779
- ] }, `l-${p.id}`);
12780
- }
12781
- case "oauth-option": {
12782
- const o = OAUTH_LABEL[row.oauth] ?? { title: row.oauth, detail: "" };
12783
- return /* @__PURE__ */ jsxs(Text, { color, wrap: "truncate-end", children: [
12784
- marker,
12785
- " ",
12786
- o.title.padEnd(20),
12787
- " ",
12788
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: o.detail })
12789
- ] }, `o-${row.oauth}`);
12790
- }
12791
- default:
12792
- return /* @__PURE__ */ jsx(Text, { children: " " }, `r-${i}`);
12793
- }
12794
- }
12795
- function viewTitle(panel) {
12796
- switch (panel.view) {
12797
- case "list":
12798
- return "API keys & sign-in";
12799
- case "provider":
12800
- return `Provider \u2014 ${panel.providerId ?? ""}`;
12801
- case "catalog":
12802
- return "Add provider \u2014 models.dev catalog";
12803
- case "local":
12804
- return "Add local server";
12805
- case "oauth":
12806
- return "Sign in with OAuth";
12807
- case "flow":
12808
- return panel.flowTitle || "Working\u2026";
12809
- }
12810
- }
12811
- function viewLegend(panel) {
12812
- if (panel.input) return "Enter submit \xB7 Esc cancel";
12813
- if (panel.confirm) return "y/Enter confirm \xB7 n/Esc cancel";
12814
- switch (panel.view) {
12815
- case "list":
12816
- return "\u2191/\u2193 select \xB7 Enter open \xB7 Esc close";
12817
- case "provider":
12818
- return "\u2191/\u2193 select \xB7 Enter activate/run \xB7 u update key \xB7 d delete key \xB7 Esc back";
12819
- case "catalog":
12820
- return "type to filter \xB7 \u2191/\u2193 select \xB7 Enter add key \xB7 Esc back";
12821
- case "local":
12822
- return "\u2191/\u2193 select \xB7 Enter configure & probe \xB7 Esc back";
12823
- case "oauth":
12824
- return "\u2191/\u2193 select \xB7 Enter sign in \xB7 Esc back";
12825
- case "flow":
12826
- return panel.flowDone ? "Enter/Esc back" : "Esc cancel";
12827
- }
12828
- }
12829
- function AuthPanel({ panel }) {
12830
- const { stdout } = useStdout();
12831
- const rows = stdout?.rows ?? 24;
12832
- const maxVisible = Math.max(4, rows - CHROME_ROWS2);
12833
- const allRows = authPanelRows(panel);
12834
- const total = allRows.length;
12835
- const windowStart = total <= maxVisible ? 0 : Math.max(0, Math.min(panel.selected - Math.floor(maxVisible / 2), total - maxVisible));
12836
- const windowEnd = Math.min(windowStart + maxVisible, total);
12837
- const above = windowStart;
12838
- const below = total - windowEnd;
12839
- const provider = panel.view === "provider" ? authSelectedProvider(panel) : void 0;
12840
- const logWindow = panel.log.slice(-Math.max(6, maxVisible));
12841
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
12842
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: viewTitle(panel) }),
12843
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: viewLegend(panel) }),
12844
- panel.view === "provider" && provider ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
12845
- /* @__PURE__ */ jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [
12846
- "type: ",
12847
- provider.type ?? provider.id,
12848
- " \xB7 family: ",
12849
- provider.family ?? "unset",
12850
- " \xB7 baseUrl:",
12851
- " ",
12852
- provider.baseUrl ?? "unset"
12853
- ] }),
12854
- provider.models.length > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [
12855
- "models: ",
12856
- provider.models.join(", ")
12857
- ] }) : null
12858
- ] }) : null,
12859
- panel.view === "oauth" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
12860
- /* @__PURE__ */ jsx(Text, { color: "yellow", wrap: "truncate-end", children: "\u26A0 Subscription tokens used outside official clients may violate provider Terms \u2014" }),
12861
- /* @__PURE__ */ jsx(Text, { color: "yellow", wrap: "truncate-end", children: "your account could be rate-limited or banned. An API key is the sanctioned path." })
12862
- ] }) : null,
12863
- panel.view === "catalog" ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
12864
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "filter: " }),
12865
- panel.filter.length > 0 ? panel.filter : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(type to search)" }),
12866
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258F" })
12867
- ] }) }) : null,
12868
- panel.view === "flow" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
12869
- logWindow.length === 0 && !panel.flowDone ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Starting\u2026" }) : null,
12870
- logWindow.map((line) => /* @__PURE__ */ jsx(
12871
- Text,
12872
- {
12873
- wrap: "truncate-end",
12874
- color: line.startsWith("\u2717") ? "red" : line.startsWith("\u2713") ? "green" : void 0,
12875
- dimColor: !line.startsWith("\u2717") && !line.startsWith("\u2713"),
12876
- children: line
12877
- },
12878
- line
12879
- )),
12880
- panel.flowDone ? /* @__PURE__ */ jsxs(Text, { color: panel.flowOk ? "green" : "red", children: [
12881
- panel.flowOk ? "\u2713 Done." : "\u2717 Not completed.",
12882
- " ",
12883
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter or Esc to go back." })
12884
- ] }) : panel.input ? null : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2026 working (Esc to cancel)" })
12885
- ] }) : /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: panel.busy ? "Loading\u2026" : panel.view === "catalog" ? "No catalog entries match." : "Nothing here yet." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
12886
- above > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${above} more` }) : null,
12887
- allRows.slice(windowStart, windowEnd).map((row, i) => renderRow3(row, windowStart + i === panel.selected, i)),
12888
- below > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${below} more` }) : null
12889
- ] }) }),
12890
- panel.input ? /* @__PURE__ */ jsxs(
12891
- Box,
12892
- {
12893
- flexDirection: "column",
12894
- marginTop: 1,
12895
- borderStyle: "round",
12896
- borderColor: "yellow",
12897
- paddingX: 1,
12898
- children: [
12899
- /* @__PURE__ */ jsxs(Text, { color: "yellow", wrap: "truncate-end", children: [
12900
- "? ",
12901
- panel.input.label
13615
+ w.insertions
12902
13616
  ] }),
12903
- /* @__PURE__ */ jsxs(Text, { children: [
12904
- panel.input.masked ? "\u2022".repeat(panel.input.draft.length) : panel.input.draft,
12905
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258F" }),
12906
- panel.input.masked ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (hidden \u2014 paste OK)" }) : null
13617
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
13618
+ "-",
13619
+ w.deletions
13620
+ ] }),
13621
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13622
+ "\xB7 ",
13623
+ w.files,
13624
+ " files"
12907
13625
  ] })
12908
- ]
12909
- }
12910
- ) : null,
12911
- panel.confirm ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "yellow", wrap: "truncate-end", children: [
12912
- "? ",
12913
- panel.confirm.question,
12914
- " ",
12915
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[y/N]" })
12916
- ] }) }) : null,
12917
- panel.hint ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate-end", children: panel.hint }) }) : null
13626
+ ] }),
13627
+ w.conflictFiles && w.conflictFiles.length > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
13628
+ "conflicts: ",
13629
+ w.conflictFiles.join(", ")
13630
+ ] }) }) : null
13631
+ ] }, w.branch);
13632
+ }),
13633
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
13634
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Esc / F4 to close \xB7 merge conflicts with /worktree merge <branch>" }),
13635
+ staleTerminal > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${staleTerminal} terminal pruned` }) : null
13636
+ ] })
12918
13637
  ] });
12919
13638
  }
13639
+ var fmtElapsed6 = (ms) => {
13640
+ const s2 = Math.floor(ms / 1e3);
13641
+ const m = Math.floor(s2 / 60);
13642
+ const h = Math.floor(m / 60);
13643
+ if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
13644
+ if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
13645
+ return `${s2}s`;
13646
+ };
13647
+ var STATUS5 = {
13648
+ allocating: { icon: "\u25CB", color: "gray" },
13649
+ active: { icon: "\u25CF", color: "yellow" },
13650
+ committing: { icon: "\u25D0", color: "cyan" },
13651
+ merging: { icon: "\u21E1", color: "blue" },
13652
+ merged: { icon: "\u2713", color: "green" },
13653
+ "needs-review": { icon: "\u26A0", color: "magenta" },
13654
+ failed: { icon: "\u2717", color: "red" }
13655
+ };
13656
+ function st(status) {
13657
+ return STATUS5[status] ?? { icon: "?", color: "white" };
13658
+ }
13659
+ function WorktreePanel({
13660
+ worktrees,
13661
+ nowTick
13662
+ }) {
13663
+ const list = Object.values(worktrees);
13664
+ if (list.length === 0) return null;
13665
+ const active = list.filter(
13666
+ (w) => w.status === "active" || w.status === "committing" || w.status === "merging"
13667
+ ).length;
13668
+ const merged = list.filter((w) => w.status === "merged").length;
13669
+ const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
13670
+ return /* @__PURE__ */ jsxs(
13671
+ Box,
13672
+ {
13673
+ flexDirection: "column",
13674
+ paddingX: 1,
13675
+ borderStyle: "single",
13676
+ borderTop: false,
13677
+ borderBottom: false,
13678
+ borderLeft: false,
13679
+ borderRight: false,
13680
+ children: [
13681
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
13682
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Worktrees" }),
13683
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
13684
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
13685
+ "\u25B6",
13686
+ active
13687
+ ] }),
13688
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
13689
+ "\u2713",
13690
+ merged
13691
+ ] }),
13692
+ failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
13693
+ "\u2717",
13694
+ failed
13695
+ ] }) : null,
13696
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13697
+ "\xB7 ",
13698
+ list.length,
13699
+ " total \xB7 Ctrl+T for details"
13700
+ ] })
13701
+ ] }),
13702
+ list.map((w) => {
13703
+ const s2 = st(w.status);
13704
+ const conflict = w.status === "needs-review";
13705
+ const elapsed = w.allocatedAt ? fmtElapsed6(nowTick - w.allocatedAt) : "";
13706
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
13707
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.icon }),
13708
+ /* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
13709
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: w.ownerLabel.slice(0, 12) }),
13710
+ conflict ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: " CONFLICT" }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13711
+ " ",
13712
+ "+",
13713
+ w.insertions,
13714
+ "/-",
13715
+ w.deletions,
13716
+ " ",
13717
+ w.files,
13718
+ "f"
13719
+ ] }),
13720
+ elapsed && (w.status === "active" || w.status === "committing") ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
13721
+ " ",
13722
+ elapsed
13723
+ ] }) : null
13724
+ ] }, w.branch);
13725
+ })
13726
+ ]
13727
+ }
13728
+ );
13729
+ }
12920
13730
  async function readGitInfo(cwd) {
12921
13731
  const [branchRes, numstatRes, statusRes] = await Promise.all([
12922
13732
  runGit(cwd, ["branch", "--show-current"]),
@@ -14192,6 +15002,33 @@ function usePickerKeys(host) {
14192
15002
  }
14193
15003
  return true;
14194
15004
  }
15005
+ if (state.modePicker.open) {
15006
+ if (key.escape) {
15007
+ dispatch({ type: "modePickerClose" });
15008
+ return true;
15009
+ }
15010
+ if (key.mouse?.kind === "wheel") {
15011
+ dispatch({ type: "modePickerMove", delta: key.mouse.wheel > 0 ? -1 : 1 });
15012
+ return true;
15013
+ }
15014
+ if (key.upArrow) {
15015
+ dispatch({ type: "modePickerMove", delta: -1 });
15016
+ return true;
15017
+ }
15018
+ if (key.downArrow) {
15019
+ dispatch({ type: "modePickerMove", delta: 1 });
15020
+ return true;
15021
+ }
15022
+ if (isEnter) {
15023
+ if (debouncedEnter(host)) return true;
15024
+ const opt = state.modePicker.modes[state.modePicker.selected];
15025
+ if (!opt) return true;
15026
+ dispatch({ type: "modePickerClose" });
15027
+ host.submit?.(`/mode ${opt.id}`);
15028
+ return true;
15029
+ }
15030
+ return true;
15031
+ }
14195
15032
  if (state.autonomyPicker.open) {
14196
15033
  if (key.escape) {
14197
15034
  dispatch({ type: "autonomyPickerClose" });
@@ -15378,6 +16215,58 @@ ${breakerLine}`;
15378
16215
  return [...lines, "", breakerLine].join("\n");
15379
16216
  }
15380
16217
 
16218
+ // src/on-panel-open.ts
16219
+ function createPanelOpenDispatcher(deps) {
16220
+ const { dispatch, openProjectPicker, openStatuslinePicker, openAuthPanel } = deps;
16221
+ return (action) => {
16222
+ switch (action) {
16223
+ case "authOpen":
16224
+ return openAuthPanel ? openAuthPanel("list") : false;
16225
+ case "authOauthOpen":
16226
+ return openAuthPanel ? openAuthPanel("oauth") : false;
16227
+ case "pluginPickerOpen":
16228
+ dispatch({ type: "pluginPickerOpen" });
16229
+ return true;
16230
+ case "projectPickerOpen":
16231
+ void openProjectPicker();
16232
+ return true;
16233
+ case "statuslineOpen":
16234
+ openStatuslinePicker();
16235
+ return true;
16236
+ case "modePickerOpen":
16237
+ if (deps.openModePicker) {
16238
+ void deps.openModePicker();
16239
+ return true;
16240
+ }
16241
+ return false;
16242
+ case "toggleMonitor":
16243
+ dispatch({ type: "toggleMonitor" });
16244
+ return true;
16245
+ case "toggleAuditPanel":
16246
+ dispatch({ type: "toggleAuditPanel" });
16247
+ return true;
16248
+ // All other toggle* panels share the same handler in the reducer —
16249
+ // the action type string IS the dispatch type. Dispatching them
16250
+ // verbatim works because the reducer's switch covers every F-key
16251
+ // panel action.
16252
+ case "toggleAgentsMonitor":
16253
+ case "toggleWorktreeMonitor":
16254
+ case "togglePlanPanel":
16255
+ case "toggleKanbanPanel":
16256
+ case "toggleTodosMonitor":
16257
+ case "toggleQueuePanel":
16258
+ case "toggleProcessList":
16259
+ case "toggleGoalPanel":
16260
+ case "toggleSessionsPanel":
16261
+ case "toggleCoordinatorMonitor":
16262
+ dispatch({ type: action });
16263
+ return true;
16264
+ default:
16265
+ return false;
16266
+ }
16267
+ };
16268
+ }
16269
+
15381
16270
  // src/paste-accumulator.ts
15382
16271
  var BEGIN = "[200~";
15383
16272
  var END = "[201~";
@@ -15471,7 +16360,7 @@ function normalizeSlashQuery(raw) {
15471
16360
  function rankSlashCommand(entry, query) {
15472
16361
  const { cmd, owner } = entry;
15473
16362
  if (query === "" && cmd.hidden) return null;
15474
- const name = displayName(entry);
16363
+ const name = displayName2(entry);
15475
16364
  const lowerName = name.toLowerCase();
15476
16365
  const aliases = aliasCandidates(entry);
15477
16366
  const category = cmd.category ?? "App";
@@ -15504,7 +16393,7 @@ function rankSlashCommand(entry, query) {
15504
16393
  rank
15505
16394
  };
15506
16395
  }
15507
- function displayName({ cmd, owner, fullName }) {
16396
+ function displayName2({ cmd, owner, fullName }) {
15508
16397
  if (owner === "core") return cmd.name;
15509
16398
  return fullName.includes(":") ? fullName : cmd.name;
15510
16399
  }
@@ -15557,51 +16446,6 @@ function buildSteeringPreamble(snapshot, newDirection) {
15557
16446
  return lines.join("\n");
15558
16447
  }
15559
16448
 
15560
- // src/on-panel-open.ts
15561
- function createPanelOpenDispatcher(deps) {
15562
- const { dispatch, openProjectPicker, openStatuslinePicker, openAuthPanel } = deps;
15563
- return (action) => {
15564
- switch (action) {
15565
- case "authOpen":
15566
- return openAuthPanel ? openAuthPanel("list") : false;
15567
- case "authOauthOpen":
15568
- return openAuthPanel ? openAuthPanel("oauth") : false;
15569
- case "pluginPickerOpen":
15570
- dispatch({ type: "pluginPickerOpen" });
15571
- return true;
15572
- case "projectPickerOpen":
15573
- void openProjectPicker();
15574
- return true;
15575
- case "statuslineOpen":
15576
- openStatuslinePicker();
15577
- return true;
15578
- case "toggleMonitor":
15579
- dispatch({ type: "toggleMonitor" });
15580
- return true;
15581
- case "toggleAuditPanel":
15582
- dispatch({ type: "toggleAuditPanel" });
15583
- return true;
15584
- // All other toggle* panels share the same handler in the reducer —
15585
- // the action type string IS the dispatch type. Dispatching them
15586
- // verbatim works because the reducer's switch covers every F-key
15587
- // panel action.
15588
- case "toggleAgentsMonitor":
15589
- case "toggleWorktreeMonitor":
15590
- case "togglePlanPanel":
15591
- case "toggleTodosMonitor":
15592
- case "toggleQueuePanel":
15593
- case "toggleProcessList":
15594
- case "toggleGoalPanel":
15595
- case "toggleSessionsPanel":
15596
- case "toggleCoordinatorMonitor":
15597
- dispatch({ type: action });
15598
- return true;
15599
- default:
15600
- return false;
15601
- }
15602
- };
15603
- }
15604
-
15605
16449
  // src/input-editing.ts
15606
16450
  var WORD_SEPARATORS = new Set(
15607
16451
  // dashes
@@ -15673,6 +16517,13 @@ function selectedSlashCommandLine(picker) {
15673
16517
  const picked = picker.matches[picker.selected];
15674
16518
  return picked ? `/${picked.name}` : null;
15675
16519
  }
16520
+ function contentBlocksText(content) {
16521
+ if (typeof content === "string") return content;
16522
+ if (!Array.isArray(content)) return "";
16523
+ return content.filter(
16524
+ (block) => !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string"
16525
+ ).map((block) => block.text).join("");
16526
+ }
15676
16527
  function rehydrateHistory(messages, startId, toolCalls) {
15677
16528
  const entries = [];
15678
16529
  const toolCallsById = /* @__PURE__ */ new Map();
@@ -15684,10 +16535,7 @@ function rehydrateHistory(messages, startId, toolCalls) {
15684
16535
  const consumed = /* @__PURE__ */ new Set();
15685
16536
  const fallback = [];
15686
16537
  let nextId = startId;
15687
- const textOf = (msg) => {
15688
- if (typeof msg.content === "string") return msg.content;
15689
- return msg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
15690
- };
16538
+ const textOf = (msg) => contentBlocksText(msg.content);
15691
16539
  const toolEntryFor = (tc) => ({
15692
16540
  id: nextId++,
15693
16541
  kind: "tool",
@@ -15701,13 +16549,12 @@ function rehydrateHistory(messages, startId, toolCalls) {
15701
16549
  for (const msg of messages) {
15702
16550
  if (msg.role === "system") continue;
15703
16551
  const text = textOf(msg).trim();
15704
- if (!text) continue;
15705
16552
  if (msg.role === "user") {
15706
- entries.push({ id: nextId++, kind: "user", text });
16553
+ if (text) entries.push({ id: nextId++, kind: "user", text });
15707
16554
  continue;
15708
16555
  }
15709
16556
  if (msg.role === "assistant") {
15710
- entries.push({ id: nextId++, kind: "assistant", text });
16557
+ if (text) entries.push({ id: nextId++, kind: "assistant", text });
15711
16558
  if (Array.isArray(msg.content)) {
15712
16559
  for (const block of msg.content) {
15713
16560
  if (block.type !== "tool_use") continue;
@@ -15843,6 +16690,7 @@ function App({
15843
16690
  sessionsDir,
15844
16691
  modeLabel,
15845
16692
  getModeLabel,
16693
+ getModes,
15846
16694
  registerDebugStreamCallback,
15847
16695
  restoreDebugStreamCallback,
15848
16696
  restoredToolCalls,
@@ -16022,6 +16870,7 @@ function App({
16022
16870
  searchQuery: ""
16023
16871
  },
16024
16872
  autonomyPicker: { open: false, options: [], selected: 0 },
16873
+ modePicker: { open: false, modes: [], selected: 0 },
16025
16874
  designPicker: { open: false, kits: [], selected: 0, stack: "web" },
16026
16875
  promptPicker: {
16027
16876
  open: false,
@@ -16125,6 +16974,7 @@ function App({
16125
16974
  processListOpen: false,
16126
16975
  auditPanelOpen: false,
16127
16976
  planPanelOpen: false,
16977
+ kanbanPanelOpen: false,
16128
16978
  goalPanelOpen: false,
16129
16979
  sessionsPanelOpen: false,
16130
16980
  sessionsPanel: { sessions: [], busy: false, selected: -1 },
@@ -16233,6 +17083,12 @@ function App({
16233
17083
  ];
16234
17084
  dispatch({ type: "promptPickerOpen", all: entries, categories: cats, recentSlugs });
16235
17085
  };
17086
+ const openModePicker = useCallback(async () => {
17087
+ if (!getModes) return;
17088
+ const result = await getModes();
17089
+ const options = toModeOptions(result.modes, result.activeId);
17090
+ dispatch({ type: "modePickerOpen", modes: options });
17091
+ }, [getModes]);
16236
17092
  useEffect(() => {
16237
17093
  if (state.statuslinePicker.open) {
16238
17094
  const pickerHidden = state.statuslinePicker.hiddenItems;
@@ -16333,6 +17189,7 @@ function App({
16333
17189
  titleController.setEnabled(liveTitleAnimation !== false);
16334
17190
  }, [titleController, liveTitleAnimation]);
16335
17191
  const streamingTextRef = useRef("");
17192
+ const streamSegmentsRef = useRef([]);
16336
17193
  const pendingDeltaRef = useRef("");
16337
17194
  const flushTimerRef = useRef(null);
16338
17195
  const stateRef = useRef(state);
@@ -16363,7 +17220,7 @@ function App({
16363
17220
  [statuslineHiddenForPicker]
16364
17221
  );
16365
17222
  const [mouseMode, setMouseMode] = useState(mouse);
16366
- const pickerOverlayOpen = state.modelPicker.open || state.autonomyPicker.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;
17223
+ const pickerOverlayOpen = state.modelPicker.open || state.autonomyPicker.open || state.modePicker.open || state.designPicker.open || state.settingsPicker.open || state.projectPicker.open || state.slashPicker.open || state.statuslinePicker.open || state.fKeyPicker.open || state.authPanel.open || state.picker.open;
16367
17224
  const mouseTrackingOn = mouseMode || pickerOverlayOpen;
16368
17225
  const mouseWrittenRef = useRef(false);
16369
17226
  useEffect(() => {
@@ -16886,6 +17743,8 @@ function App({
16886
17743
  todos: stateRef.current.todosMonitorOpen,
16887
17744
  queue: stateRef.current.queuePanelOpen,
16888
17745
  processList: stateRef.current.processListOpen,
17746
+ planPanel: stateRef.current.planPanelOpen,
17747
+ kanbanPanel: stateRef.current.kanbanPanelOpen,
16889
17748
  goalPanel: stateRef.current.goalPanelOpen,
16890
17749
  sessionsPanel: stateRef.current.sessionsPanelOpen,
16891
17750
  coordinator: stateRef.current.coordinator.monitorOpen
@@ -16908,6 +17767,7 @@ function App({
16908
17767
  if (stateRef.current.queuePanelOpen) dispatch({ type: "toggleQueuePanel" });
16909
17768
  if (stateRef.current.processListOpen) dispatch({ type: "toggleProcessList" });
16910
17769
  if (stateRef.current.planPanelOpen) dispatch({ type: "togglePlanPanel" });
17770
+ if (stateRef.current.kanbanPanelOpen) dispatch({ type: "toggleKanbanPanel" });
16911
17771
  if (stateRef.current.goalPanelOpen) dispatch({ type: "toggleGoalPanel" });
16912
17772
  if (stateRef.current.sessionsPanelOpen) dispatch({ type: "toggleSessionsPanel" });
16913
17773
  eraseLiveRegion();
@@ -16971,6 +17831,8 @@ function App({
16971
17831
  if (prev.todos) dispatch({ type: "toggleTodosMonitor" });
16972
17832
  if (prev.queue) dispatch({ type: "toggleQueuePanel" });
16973
17833
  if (prev.processList) dispatch({ type: "toggleProcessList" });
17834
+ if (prev.planPanel) dispatch({ type: "togglePlanPanel" });
17835
+ if (prev.kanbanPanel) dispatch({ type: "toggleKanbanPanel" });
16974
17836
  if (prev.goalPanel) dispatch({ type: "toggleGoalPanel" });
16975
17837
  if (prev.sessionsPanel) dispatch({ type: "toggleSessionsPanel" });
16976
17838
  if (prev.coordinator) dispatch({ type: "toggleCoordinatorMonitor" });
@@ -17171,7 +18033,8 @@ function App({
17171
18033
  dispatch,
17172
18034
  openProjectPicker,
17173
18035
  openStatuslinePicker,
17174
- openAuthPanel: authPanelController.openAuthPanel
18036
+ openAuthPanel: authPanelController.openAuthPanel,
18037
+ openModePicker
17175
18038
  });
17176
18039
  onPanelOpen.current = dispatcher;
17177
18040
  return () => {
@@ -17182,7 +18045,8 @@ function App({
17182
18045
  dispatch,
17183
18046
  openProjectPicker,
17184
18047
  openStatuslinePicker,
17185
- authPanelController.openAuthPanel
18048
+ authPanelController.openAuthPanel,
18049
+ openModePicker
17186
18050
  ]);
17187
18051
  useEffect(() => {
17188
18052
  if (!state.sessionsPanelOpen || !getLiveSessions) return void 0;
@@ -17829,6 +18693,14 @@ Available chords:
17829
18693
  }, [slashRegistry, listSessions]);
17830
18694
  useEffect(() => {
17831
18695
  const FLUSH_MS2 = 100;
18696
+ const appendStreamSegment = (kind, text) => {
18697
+ const last = streamSegmentsRef.current.at(-1);
18698
+ if (last?.kind === kind) {
18699
+ last.text += text;
18700
+ } else {
18701
+ streamSegmentsRef.current.push({ kind, text });
18702
+ }
18703
+ };
17832
18704
  const flush = () => {
17833
18705
  if (pendingDeltaRef.current) {
17834
18706
  dispatch({ type: "streamDelta", delta: pendingDeltaRef.current });
@@ -17840,6 +18712,13 @@ Available chords:
17840
18712
  const text = e.text.replace(/\x1b?\[200~|\x1b?\[201~/g, "");
17841
18713
  streamingTextRef.current += text;
17842
18714
  pendingDeltaRef.current += text;
18715
+ appendStreamSegment("assistant", text);
18716
+ if (!flushTimerRef.current) flushTimerRef.current = setTimeout(flush, FLUSH_MS2);
18717
+ });
18718
+ const offThinking = events.on("provider.thinking_delta", (e) => {
18719
+ const text = e.text.replace(/\x1b?\[200~|\x1b?\[201~/g, "");
18720
+ appendStreamSegment("thinking", text);
18721
+ pendingDeltaRef.current += text;
17843
18722
  if (!flushTimerRef.current) flushTimerRef.current = setTimeout(flush, FLUSH_MS2);
17844
18723
  });
17845
18724
  const offToolStart = events.on("tool.started", (e) => {
@@ -17914,17 +18793,27 @@ Available chords:
17914
18793
  }
17915
18794
  });
17916
18795
  });
17917
- const offProvResp = events.on("provider.response", () => {
18796
+ const offProvResp = events.on("provider.response", (e) => {
17918
18797
  const text = streamingTextRef.current;
18798
+ const segments = streamSegmentsRef.current;
18799
+ const fallbackText = contentBlocksText(e.content);
17919
18800
  streamingTextRef.current = "";
18801
+ streamSegmentsRef.current = [];
17920
18802
  pendingDeltaRef.current = "";
17921
18803
  if (flushTimerRef.current) {
17922
18804
  clearTimeout(flushTimerRef.current);
17923
18805
  flushTimerRef.current = null;
17924
18806
  }
17925
18807
  dispatch({ type: "streamReset" });
17926
- if (text.trim()) {
18808
+ for (const seg of segments) {
18809
+ if (seg.text.trim()) {
18810
+ dispatch({ type: "addEntry", entry: { kind: seg.kind, text: seg.text } });
18811
+ }
18812
+ }
18813
+ if (segments.length === 0 && text.trim()) {
17927
18814
  dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
18815
+ } else if (segments.length === 0 && fallbackText.trim()) {
18816
+ dispatch({ type: "addEntry", entry: { kind: "assistant", text: fallbackText } });
17928
18817
  }
17929
18818
  });
17930
18819
  const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
@@ -17981,6 +18870,7 @@ Available chords:
17981
18870
  });
17982
18871
  return () => {
17983
18872
  offDelta();
18873
+ offThinking();
17984
18874
  offToolStart();
17985
18875
  offIterStart();
17986
18876
  offIterEnd();
@@ -19308,18 +20198,26 @@ Available chords:
19308
20198
  ).length;
19309
20199
  const result = await agent.run(routed.blocks, { signal: ctrl.signal });
19310
20200
  const lingering = streamingTextRef.current;
19311
- if (lingering.trim()) {
19312
- dispatch({ type: "addEntry", entry: { kind: "assistant", text: lingering } });
19313
- } else if (result.status === "done" && result.finalText?.trim() && stateRef.current.entries.filter((entry) => entry.kind === "assistant").length === assistantEntriesBeforeRun) {
19314
- dispatch({ type: "addEntry", entry: { kind: "assistant", text: result.finalText } });
19315
- }
20201
+ const lingeringSegments = streamSegmentsRef.current;
19316
20202
  streamingTextRef.current = "";
20203
+ streamSegmentsRef.current = [];
19317
20204
  pendingDeltaRef.current = "";
19318
20205
  if (flushTimerRef.current) {
19319
20206
  clearTimeout(flushTimerRef.current);
19320
20207
  flushTimerRef.current = null;
19321
20208
  }
19322
20209
  dispatch({ type: "streamReset" });
20210
+ if (lingeringSegments.length > 0) {
20211
+ for (const seg of lingeringSegments) {
20212
+ if (seg.text.trim()) {
20213
+ dispatch({ type: "addEntry", entry: { kind: seg.kind, text: seg.text } });
20214
+ }
20215
+ }
20216
+ } else if (lingering.trim()) {
20217
+ dispatch({ type: "addEntry", entry: { kind: "assistant", text: lingering } });
20218
+ } else if (result.status === "done" && result.finalText?.trim() && stateRef.current.entries.filter((entry) => entry.kind === "assistant").length === assistantEntriesBeforeRun) {
20219
+ dispatch({ type: "addEntry", entry: { kind: "assistant", text: result.finalText } });
20220
+ }
19323
20221
  if (result.status === "aborted") {
19324
20222
  const reason = result.abortReason ? `Aborted (${result.abortReason}).` : "Aborted.";
19325
20223
  dispatch({ type: "addEntry", entry: { kind: "warn", text: reason } });
@@ -19860,7 +20758,7 @@ ${content}
19860
20758
  );
19861
20759
  const inputHeight = Math.max(1, inputCellRows.length);
19862
20760
  const hideInput = enhanceActive || state.helpOpen || state.processListOpen || state.authPanel.open;
19863
- const lowerFunctionPanelOpen = state.monitorOpen || state.agentsMonitorOpen || (state.autoPhase?.monitorOpen ?? false) || state.worktreeMonitorOpen || state.planPanelOpen || state.todosMonitorOpen || state.queuePanelOpen || state.processListOpen || state.goalPanelOpen;
20761
+ const lowerFunctionPanelOpen = state.monitorOpen || state.agentsMonitorOpen || (state.autoPhase?.monitorOpen ?? false) || state.worktreeMonitorOpen || state.planPanelOpen || state.kanbanPanelOpen || state.todosMonitorOpen || state.queuePanelOpen || state.processListOpen || state.goalPanelOpen;
19864
20762
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 0, children: [
19865
20763
  mouseMode ? /* @__PURE__ */ jsx(
19866
20764
  ScrollableHistory,
@@ -19941,6 +20839,14 @@ ${content}
19941
20839
  hint: state.autonomyPicker.hint
19942
20840
  }
19943
20841
  ) : null,
20842
+ state.modePicker.open ? /* @__PURE__ */ jsx(
20843
+ ModePicker,
20844
+ {
20845
+ modes: state.modePicker.modes,
20846
+ selected: state.modePicker.selected,
20847
+ hint: state.modePicker.hint
20848
+ }
20849
+ ) : null,
19944
20850
  state.designPicker.open ? /* @__PURE__ */ jsx(
19945
20851
  DesignPicker,
19946
20852
  {
@@ -20335,6 +21241,12 @@ ${content}
20335
21241
  sessionId: agent.ctx.session?.id ?? null,
20336
21242
  onClose: () => dispatch({ type: "togglePlanPanel" })
20337
21243
  }
21244
+ ) : state.kanbanPanelOpen ? /* @__PURE__ */ jsx(
21245
+ KanbanPanel,
21246
+ {
21247
+ projectRoot: agent.ctx.projectRoot,
21248
+ onClose: () => dispatch({ type: "toggleKanbanPanel" })
21249
+ }
20338
21250
  ) : state.queuePanelOpen ? /* @__PURE__ */ jsx(QueuePanel, { items: state.queue }) : state.processListOpen ? /* @__PURE__ */ jsx(ProcessListMonitor, {}) : state.goalPanelOpen ? /* @__PURE__ */ jsx(
20339
21251
  GoalPanel,
20340
21252
  {
@@ -20343,12 +21255,14 @@ ${content}
20343
21255
  onCoordinatorStop: onCoordinatorStop ?? void 0,
20344
21256
  coordinatorRunning
20345
21257
  }
20346
- ) : director ? /* @__PURE__ */ jsx(
21258
+ ) : director || Object.keys(state.fleet).length > 0 || state.collabSession ? /* @__PURE__ */ jsx(
20347
21259
  FleetPanel,
20348
21260
  {
20349
21261
  entries: entriesWithLeader,
20350
21262
  totalCost: state.fleetCost,
20351
21263
  roster: fleetRoster,
21264
+ todos: agent.ctx.todos,
21265
+ nowTick,
20352
21266
  collabSession: state.collabSession
20353
21267
  }
20354
21268
  ) : null,
@@ -20819,6 +21733,8 @@ async function runTui(opts) {
20819
21733
  tokenSavingMode: opts.tokenSavingMode,
20820
21734
  toolCount: opts.toolCount,
20821
21735
  getModeLabel: opts.getModeLabel,
21736
+ getModes: opts.getModes,
21737
+ switchMode: opts.switchMode,
20822
21738
  registerDebugStreamCallback: opts.registerDebugStreamCallback,
20823
21739
  restoreDebugStreamCallback: opts.restoreDebugStreamCallback,
20824
21740
  restoredMessages: opts.restoredMessages,