@wrongstack/webui 0.73.1 → 0.82.6

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
@@ -408,6 +408,10 @@ var WrongStackWebSocketClient = class {
408
408
  clearTodos() {
409
409
  this.send({ type: "todos.clear" });
410
410
  }
411
+ removeTodo(idOrIndex) {
412
+ const payload = typeof idOrIndex === "number" ? { index: idOrIndex } : { id: idOrIndex };
413
+ this.send({ type: "todos.remove", payload });
414
+ }
411
415
  listSessions(limit = 50) {
412
416
  this.send({ type: "sessions.list", payload: { limit } });
413
417
  }
@@ -469,19 +473,25 @@ function getWSClient(url) {
469
473
  // src/stores/chat-store.ts
470
474
  import { create } from "zustand";
471
475
  import { persist } from "zustand/middleware";
476
+ function expectDefined(value) {
477
+ if (value === null || value === void 0) {
478
+ throw new Error("Expected value to be defined");
479
+ }
480
+ return value;
481
+ }
472
482
  function dedupeRepeatedBlocks(text) {
473
483
  if (!text) return text;
474
484
  const paraSplit = text.split(/\n{2,}/);
475
485
  const paras = [];
476
486
  for (const p of paraSplit) {
477
- if (paras.length > 0 && paras[paras.length - 1].trim() === p.trim()) continue;
487
+ if (paras.length > 0 && paras[paras.length - 1]?.trim() === p.trim()) continue;
478
488
  paras.push(p);
479
489
  }
480
490
  const cleaned = paras.map((p) => {
481
491
  const lines = p.split("\n");
482
492
  const out = [];
483
493
  for (const line of lines) {
484
- if (out.length > 0 && line.trim().length > 0 && out[out.length - 1].trim() === line.trim()) {
494
+ if (out.length > 0 && line.trim().length > 0 && out[out.length - 1]?.trim() === line.trim()) {
485
495
  continue;
486
496
  }
487
497
  out.push(line);
@@ -591,7 +601,7 @@ var useChatStore = create()(
591
601
  if (queue.length === 0) return null;
592
602
  const [next, ...rest] = queue;
593
603
  set({ queue: rest });
594
- return next;
604
+ return expectDefined(next);
595
605
  },
596
606
  removeQueued: (idx) => set((state) => ({ queue: state.queue.filter((_, i) => i !== idx) })),
597
607
  clearQueue: () => set({ queue: [] }),
@@ -1048,6 +1058,12 @@ function notifyIfHidden(title, body, tag) {
1048
1058
  }
1049
1059
 
1050
1060
  // src/hooks/ws-handlers.ts
1061
+ function expectDefined2(value) {
1062
+ if (value === null || value === void 0) {
1063
+ throw new Error("Expected value to be defined");
1064
+ }
1065
+ return value;
1066
+ }
1051
1067
  function handleSessionStart(msg) {
1052
1068
  const payload = msg.payload;
1053
1069
  const prev = useSessionStore.getState().session?.id;
@@ -1097,8 +1113,8 @@ function handleSessionStart(msg) {
1097
1113
  const all = useChatStore.getState().messages;
1098
1114
  let last;
1099
1115
  for (let i = all.length - 1; i >= 0; i--) {
1100
- if (all[i].toolUseId === String(b.tool_use_id ?? "")) {
1101
- last = all[i];
1116
+ if (all[i]?.toolUseId === String(b.tool_use_id ?? "")) {
1117
+ last = expectDefined2(all[i]);
1102
1118
  break;
1103
1119
  }
1104
1120
  }
@@ -1143,7 +1159,7 @@ function handleKeyOperationResult(msg) {
1143
1159
  function handleContextCompacted(msg) {
1144
1160
  const payload = msg.payload;
1145
1161
  let summary = payload.reductions.length ? payload.reductions.map((r) => `${r.phase}: ${r.saved}`).join(", ") : "no-op";
1146
- if (payload.repaired) summary += `; repaired ${payload.repaired.removedToolUses.length} tool_use, ${payload.repaired.removedToolResults.length} tool_result, ${payload.repaired.removedMessages} empty messages`;
1162
+ if (payload.repaired) summary += `; repaired ${payload.repaired.removedToolUses?.length ?? 0} tool_use, ${payload.repaired.removedToolResults?.length ?? 0} tool_result, ${payload.repaired.removedMessages} empty messages`;
1147
1163
  useChatStore.getState().addMessage({ role: "assistant", content: `\u{1F5DC}\uFE0F Context compacted: ${payload.before} \u2192 ${payload.after} tokens (saved ~${payload.saved}). ${summary}` });
1148
1164
  useSessionStore.setState({ lastInputTokens: payload.after });
1149
1165
  }
@@ -1257,14 +1273,14 @@ function handleRunResult(msg) {
1257
1273
  let lastAssistantIdx = -1;
1258
1274
  let toolCount = 0;
1259
1275
  for (let i = all.length - 1; i >= 0; i--) {
1260
- const m = all[i];
1276
+ const m = expectDefined2(all[i]);
1261
1277
  if (m.role === "assistant" && lastAssistantIdx === -1 && m.content) lastAssistantIdx = i;
1262
1278
  if (m.role === "tool" && m.timestamp >= runStart.at) toolCount += 1;
1263
1279
  if (m.role === "user" && m.timestamp <= runStart.at) break;
1264
1280
  }
1265
1281
  if (lastAssistantIdx !== -1) {
1266
1282
  const sessionCost = useSessionStore.getState().cost;
1267
- useChatStore.getState().updateMessage(all[lastAssistantIdx].id, { runSummary: { iterations: payload.iterations, tools: toolCount, durationMs: Date.now() - runStart.at, costDelta: Math.max(0, sessionCost - runStart.cost) } });
1283
+ useChatStore.getState().updateMessage(all[lastAssistantIdx]?.id, { runSummary: { iterations: payload.iterations, tools: toolCount, durationMs: Date.now() - runStart.at, costDelta: Math.max(0, sessionCost - runStart.cost) } });
1268
1284
  }
1269
1285
  }
1270
1286
  useChatStore.getState().setRunStart(null);
@@ -1605,7 +1621,7 @@ function useWebSocket() {
1605
1621
  }
1606
1622
 
1607
1623
  // src/App.tsx
1608
- import { useEffect as useEffect21 } from "react";
1624
+ import { useEffect as useEffect22 } from "react";
1609
1625
 
1610
1626
  // src/components/ChatView/index.tsx
1611
1627
  import {
@@ -2056,7 +2072,7 @@ function renderGroupedList(filtered, index, dispatch, setIndex) {
2056
2072
  const groups = {};
2057
2073
  filtered.forEach((it, i) => {
2058
2074
  if (!groups[it.category]) groups[it.category] = [];
2059
- groups[it.category].push({ item: it, globalIdx: i });
2075
+ groups[it.category]?.push({ item: it, globalIdx: i });
2060
2076
  });
2061
2077
  return /* @__PURE__ */ jsx2("div", { className: "p-1", children: Object.entries(groups).map(([cat, rows]) => /* @__PURE__ */ jsxs2("div", { children: [
2062
2078
  /* @__PURE__ */ jsx2("div", { className: "px-3 pt-2 pb-1 text-[10px] uppercase tracking-wider text-muted-foreground", children: cat }),
@@ -2092,6 +2108,12 @@ function renderGroupedList(filtered, index, dispatch, setIndex) {
2092
2108
  import { FileText, Folder } from "lucide-react";
2093
2109
  import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
2094
2110
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2111
+ function expectDefined3(value) {
2112
+ if (value === null || value === void 0) {
2113
+ throw new Error("Expected value to be defined");
2114
+ }
2115
+ return value;
2116
+ }
2095
2117
  function FilePicker({ query, onPick, onClose }) {
2096
2118
  const ws = useWebSocket();
2097
2119
  const wsUrl = useConfigStore((s) => s.wsUrl);
@@ -2130,7 +2152,7 @@ function FilePicker({ query, onPick, onClose }) {
2130
2152
  } else if (e.key === "Enter" || e.key === "Tab") {
2131
2153
  if (files.length === 0) return;
2132
2154
  e.preventDefault();
2133
- onPick(files[index]);
2155
+ onPick(expectDefined3(files[index]));
2134
2156
  } else if (e.key === "Escape") {
2135
2157
  e.preventDefault();
2136
2158
  onClose();
@@ -2206,25 +2228,64 @@ var Button = React.forwardRef(
2206
2228
  Button.displayName = "Button";
2207
2229
 
2208
2230
  // src/components/ChatInput/slash-commands.ts
2231
+ function expectDefined4(value) {
2232
+ if (value === null || value === void 0) {
2233
+ throw new Error("Expected value to be defined");
2234
+ }
2235
+ return value;
2236
+ }
2209
2237
  var SLASH_COMMANDS = [
2210
- { name: "/help", category: "App", description: "Show every slash command and what it does" },
2211
- { name: "/export", category: "Session", description: "Download the current chat as markdown" },
2212
- { name: "/todos", category: "Inspect", description: "List current todos (try `/todos clear` to reset)" },
2213
- { name: "/clear", category: "Session", description: "Wipe current context (keeps session id, disk record stays)" },
2238
+ // Run
2239
+ { name: "/abort", category: "Run", aliases: ["/stop"], description: "Abort the current run" },
2240
+ // Session
2214
2241
  { name: "/new", category: "Session", description: "Start a brand-new session (fresh on disk and in memory)" },
2242
+ { name: "/clear", category: "Session", description: "Wipe current context (keeps session id, disk record stays)" },
2215
2243
  { name: "/compact", category: "Session", description: "Shrink context \u2014 elide ancient tool output" },
2216
2244
  { name: "/repair", category: "Session", description: "Repair orphan tool_use/tool_result blocks in context" },
2245
+ { name: "/save", category: "Session", description: "Force-flush the session (auto-saved already)" },
2246
+ { name: "/load", category: "Session", aliases: ["/resume"], description: "Resume a previous session from disk" },
2247
+ { name: "/prune", category: "Session", description: "Delete old sessions (default older than 30 days)" },
2248
+ { name: "/export", category: "Session", description: "Download the current chat as markdown" },
2249
+ // Inspect
2217
2250
  { name: "/debug", category: "Inspect", aliases: ["/context"], description: "Per-section context size breakdown" },
2218
2251
  { name: "/tools", category: "Inspect", description: "List every registered tool the model can call" },
2219
2252
  { name: "/memory", category: "Inspect", description: "Show all remembered notes (project + user scope)" },
2220
2253
  { name: "/skill", category: "Inspect", aliases: ["/skills"], description: "List active skills" },
2221
2254
  { name: "/diag", category: "Inspect", description: "Runtime diagnostics (provider, tools, features, mode, usage)" },
2222
2255
  { name: "/stats", category: "Inspect", description: "Session stats: tokens, cache hit ratio, cost, elapsed" },
2223
- { name: "/save", category: "Session", description: "Force-flush the session (auto-saved already)" },
2224
- { name: "/abort", category: "Run", aliases: ["/stop"], description: "Abort the current run" },
2225
- { name: "/settings", category: "App", aliases: ["/model"], description: "Open settings (provider/model/keys)" }
2256
+ { name: "/todos", category: "Inspect", description: "List current todos (try `/todos clear` to reset)" },
2257
+ { name: "/codebase-reindex", category: "Inspect", aliases: ["/reindex"], description: "Rebuild the codebase symbol index" },
2258
+ // Agent
2259
+ { name: "/spawn", category: "Agent", description: "Spawn an isolated subagent to handle a task" },
2260
+ { name: "/agents", category: "Agent", description: "Show status of spawned subagents" },
2261
+ { name: "/fleet", category: "Agent", description: "Inspect and control the agent fleet" },
2262
+ { name: "/director", category: "Agent", description: "Promote to director mode at runtime" },
2263
+ { name: "/autonomy", category: "Agent", description: "Toggle or query autonomy mode (self-driving agent)" },
2264
+ { name: "/goal", category: "Agent", description: "Set, inspect, or clear the autonomous mission" },
2265
+ { name: "/autophase", category: "Agent", description: "Autonomous phase-based workflow with subagents" },
2266
+ { name: "/fix", category: "Agent", description: "Diagnose and fix a reported error or bug" },
2267
+ { name: "/sdd", category: "Agent", description: "AI-driven Specification-Driven Development workflow" },
2268
+ { name: "/btw", category: "Agent", description: "Drop a mid-run note without interrupting the agent" },
2269
+ { name: "/collab", category: "Agent", description: "Live collaboration helpers (status / invite / history)" },
2270
+ // Config
2271
+ { name: "/settings", category: "Config", aliases: ["/model"], description: "Open settings (provider/model/keys)" },
2272
+ { name: "/setmodel", category: "Config", description: "Quick-switch the active provider/model" },
2273
+ { name: "/models", category: "Config", description: "List available providers and models" },
2274
+ { name: "/mode", category: "Config", description: "Switch the active mode (persona/skill set)" },
2275
+ { name: "/yolo", category: "Config", description: "Toggle or query YOLO (auto-approve) mode" },
2276
+ { name: "/next", category: "Config", description: "Toggle next-task prediction after each turn" },
2277
+ { name: "/enhance", category: "Config", description: "Toggle prompt refinement before sending" },
2278
+ { name: "/mcp", category: "Config", aliases: ["/mcp-servers"], description: "Manage MCP servers" },
2279
+ { name: "/plugin", category: "Config", aliases: ["/plugins"], description: "Manage plugins" },
2280
+ { name: "/statusline", category: "Config", aliases: ["/sl"], description: "Customize status bar chips" },
2281
+ { name: "/telegram-setup", category: "Config", aliases: ["/tg-setup"], description: "Configure Telegram bot token and chat" },
2282
+ { name: "/init", category: "Config", description: "Create or update .wrongstack/AGENTS.md project context" },
2283
+ { name: "/worktree", category: "Config", aliases: ["/wt"], description: "Inspect/manage git worktrees for AutoPhase" },
2284
+ // App
2285
+ { name: "/help", category: "App", description: "Show every slash command and what it does" },
2286
+ { name: "/exit", category: "App", description: "Exit the current session" }
2226
2287
  ];
2227
- var SLASH_CATEGORY_ORDER = ["Run", "Session", "Inspect", "App"];
2288
+ var SLASH_CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
2228
2289
  function matchSlash(query) {
2229
2290
  const q = query.toLowerCase();
2230
2291
  if (q === "/" || q === "") return SLASH_COMMANDS;
@@ -2235,7 +2296,7 @@ function matchSlash(query) {
2235
2296
  function detectAtMention(value, cursor) {
2236
2297
  let i = cursor - 1;
2237
2298
  while (i >= 0) {
2238
- const c = value[i];
2299
+ const c = expectDefined4(value[i]);
2239
2300
  if (c === "@") {
2240
2301
  const prev = i > 0 ? value[i - 1] : "";
2241
2302
  if (i === 0 || /\s/.test(prev ?? "")) {
@@ -2251,6 +2312,12 @@ function detectAtMention(value, cursor) {
2251
2312
 
2252
2313
  // src/components/ChatInput.tsx
2253
2314
  import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2315
+ function expectDefined5(value) {
2316
+ if (value === null || value === void 0) {
2317
+ throw new Error("Expected value to be defined");
2318
+ }
2319
+ return value;
2320
+ }
2254
2321
  function ChatInput() {
2255
2322
  const { isLoading, setLoading, addMessage, clearMessages } = useChatStore();
2256
2323
  const queue = useChatStore((s) => s.queue);
@@ -2444,7 +2511,7 @@ function ChatInput() {
2444
2511
  setLoading(false);
2445
2512
  const all = useChatStore.getState().messages;
2446
2513
  for (let i = all.length - 1; i >= 0; i--) {
2447
- const m = all[i];
2514
+ const m = expectDefined5(all[i]);
2448
2515
  if (m.role === "user" && m.content) {
2449
2516
  setInput(m.content);
2450
2517
  requestAnimationFrame(() => {
@@ -2656,7 +2723,7 @@ function ChatInput() {
2656
2723
  const next = before + insertion + after;
2657
2724
  setInput(next);
2658
2725
  const lastTokenStart = before.length + lead.length + tokens.slice(0, -1).join(" ").length + (tokens.length > 1 ? 1 : 0);
2659
- const lastBasename = files[files.length - 1].name;
2726
+ const lastBasename = files[files.length - 1]?.name;
2660
2727
  requestAnimationFrame(() => {
2661
2728
  if (ta) {
2662
2729
  const cur = before.length + insertion.length - trail.length;
@@ -2708,14 +2775,14 @@ function ChatInput() {
2708
2775
  const byCategory = {};
2709
2776
  slashSuggestions.forEach((cmd, idx) => {
2710
2777
  if (!byCategory[cmd.category]) byCategory[cmd.category] = [];
2711
- byCategory[cmd.category].push({ cmd, idx });
2778
+ byCategory[cmd.category]?.push({ cmd, idx });
2712
2779
  });
2713
2780
  const orderedCategories = SLASH_CATEGORY_ORDER.filter((c) => byCategory[c]?.length);
2714
2781
  return /* @__PURE__ */ jsxs4("div", { className: "absolute bottom-full left-0 right-0 mb-2 rounded-lg border bg-popover shadow-md p-1 text-sm max-h-72 overflow-auto", children: [
2715
2782
  /* @__PURE__ */ jsx5("div", { className: "px-3 py-1 text-[10px] uppercase tracking-wider text-muted-foreground border-b mb-1", children: "\u2191/\u2193 select \xB7 Tab complete \xB7 Enter dispatch \xB7 Esc dismiss" }),
2716
2783
  orderedCategories.map((cat) => /* @__PURE__ */ jsxs4("div", { className: "mb-1", children: [
2717
2784
  /* @__PURE__ */ jsx5("div", { className: "px-3 pt-1 pb-0.5 text-[10px] uppercase tracking-wider text-muted-foreground/70 font-semibold", children: cat }),
2718
- byCategory[cat].map(({ cmd, idx }) => /* @__PURE__ */ jsxs4(
2785
+ byCategory[cat]?.map(({ cmd, idx }) => /* @__PURE__ */ jsxs4(
2719
2786
  "button",
2720
2787
  {
2721
2788
  type: "button",
@@ -2946,6 +3013,12 @@ function ConnectionChip({ wsStatus, wsConnected }) {
2946
3013
  import { Check, ChevronDown, Gauge } from "lucide-react";
2947
3014
  import { useEffect as useEffect7, useRef as useRef5, useState as useState5 } from "react";
2948
3015
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3016
+ function expectDefined6(value) {
3017
+ if (value === null || value === void 0) {
3018
+ throw new Error("Expected value to be defined");
3019
+ }
3020
+ return value;
3021
+ }
2949
3022
  var FALLBACK_MODES = [
2950
3023
  {
2951
3024
  id: "balanced",
@@ -2981,7 +3054,7 @@ function ContextModePicker() {
2981
3054
  };
2982
3055
  }, [open]);
2983
3056
  const items = contextModes.length > 0 ? contextModes : FALLBACK_MODES;
2984
- const active = items.find((m) => m.id === contextMode) ?? items[0];
3057
+ const active = items.find((m) => m.id === contextMode) ?? expectDefined6(items[0]);
2985
3058
  return /* @__PURE__ */ jsxs6("div", { ref: rootRef, className: "relative shrink-0", children: [
2986
3059
  /* @__PURE__ */ jsxs6(
2987
3060
  "button",
@@ -3028,7 +3101,7 @@ function ContextModePicker() {
3028
3101
  /* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
3029
3102
  /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
3030
3103
  /* @__PURE__ */ jsx7("span", { className: "text-xs font-mono", children: m.id }),
3031
- m.thresholds && /* @__PURE__ */ jsxs6("span", { className: "text-[10px] text-muted-foreground tabular-nums", children: [
3104
+ m.thresholds?.warn !== void 0 && /* @__PURE__ */ jsxs6("span", { className: "text-[10px] text-muted-foreground tabular-nums", children: [
3032
3105
  Math.round(m.thresholds.warn * 100),
3033
3106
  "/",
3034
3107
  Math.round(m.thresholds.soft * 100),
@@ -3318,6 +3391,12 @@ import remarkGfm from "remark-gfm";
3318
3391
  // src/components/DiffView.tsx
3319
3392
  import { memo, useMemo as useMemo2 } from "react";
3320
3393
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3394
+ function expectDefined7(value) {
3395
+ if (value === null || value === void 0) {
3396
+ throw new Error("Expected value to be defined");
3397
+ }
3398
+ return value;
3399
+ }
3321
3400
  var DiffView = memo(function DiffView2({ oldText, newText, caption }) {
3322
3401
  const rows = useMemo2(() => computeDiff(oldText, newText), [oldText, newText]);
3323
3402
  if (rows === null) {
@@ -3387,8 +3466,8 @@ function computeDiff(oldText, newText) {
3387
3466
  const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
3388
3467
  for (let i2 = n - 1; i2 >= 0; i2--) {
3389
3468
  for (let j2 = m - 1; j2 >= 0; j2--) {
3390
- if (a[i2] === b[j2]) dp[i2][j2] = dp[i2 + 1][j2 + 1] + 1;
3391
- else dp[i2][j2] = Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
3469
+ if (a[i2] === b[j2]) expectDefined7(dp[i2])[j2] = expectDefined7(dp[i2 + 1]?.[j2 + 1]) + 1;
3470
+ else expectDefined7(dp[i2])[j2] = Math.max(expectDefined7(dp[i2 + 1]?.[j2]), expectDefined7(dp[i2]?.[j2 + 1]));
3392
3471
  }
3393
3472
  }
3394
3473
  const rows = [];
@@ -3396,19 +3475,19 @@ function computeDiff(oldText, newText) {
3396
3475
  let j = 0;
3397
3476
  while (i < n && j < m) {
3398
3477
  if (a[i] === b[j]) {
3399
- rows.push({ kind: "ctx", text: a[i] });
3478
+ rows.push({ kind: "ctx", text: expectDefined7(a[i]) });
3400
3479
  i++;
3401
3480
  j++;
3402
- } else if (dp[i + 1][j] >= dp[i][j + 1]) {
3403
- rows.push({ kind: "del", text: a[i] });
3481
+ } else if (expectDefined7(dp[i + 1]?.[j]) >= expectDefined7(dp[i]?.[j + 1])) {
3482
+ rows.push({ kind: "del", text: expectDefined7(a[i]) });
3404
3483
  i++;
3405
3484
  } else {
3406
- rows.push({ kind: "add", text: b[j] });
3485
+ rows.push({ kind: "add", text: expectDefined7(b[j]) });
3407
3486
  j++;
3408
3487
  }
3409
3488
  }
3410
- while (i < n) rows.push({ kind: "del", text: a[i++] });
3411
- while (j < m) rows.push({ kind: "add", text: b[j++] });
3489
+ while (i < n) rows.push({ kind: "del", text: expectDefined7(a[i++]) });
3490
+ while (j < m) rows.push({ kind: "add", text: expectDefined7(b[j++]) });
3412
3491
  return rows;
3413
3492
  }
3414
3493
  function diffFromToolInput(toolName, input) {
@@ -3775,10 +3854,16 @@ function CopyButton({
3775
3854
  // src/components/MessageBubble/ErrorBody.tsx
3776
3855
  import { useState as useState9 } from "react";
3777
3856
  import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3857
+ function expectDefined8(value) {
3858
+ if (value === null || value === void 0) {
3859
+ throw new Error("Expected value to be defined");
3860
+ }
3861
+ return value;
3862
+ }
3778
3863
  function detectStackBoundary(text) {
3779
3864
  const lines = text.split("\n");
3780
3865
  for (let i = 0; i < lines.length; i++) {
3781
- const ln = lines[i];
3866
+ const ln = expectDefined8(lines[i]);
3782
3867
  if (/^\s*at\s+\S+.*\(.*:\d+:\d+\)\s*$/.test(ln)) return i;
3783
3868
  if (/^\s*at\s+\S+\.\S+\(\S+\.java:\d+\)\s*$/.test(ln)) return i;
3784
3869
  if (/^\s+File "[^"]+", line \d+/.test(ln)) return i;
@@ -3881,6 +3966,12 @@ function ToolInputView({ input }) {
3881
3966
 
3882
3967
  // src/components/MessageBubble/index.tsx
3883
3968
  import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
3969
+ function expectDefined9(value) {
3970
+ if (value === null || value === void 0) {
3971
+ throw new Error("Expected value to be defined");
3972
+ }
3973
+ return value;
3974
+ }
3884
3975
  var MessageBubble = memo3(function MessageBubble2({
3885
3976
  message,
3886
3977
  isFirst = false,
@@ -3909,7 +4000,7 @@ var MessageBubble = memo3(function MessageBubble2({
3909
4000
  if (message.role !== "assistant" || isLoading) return false;
3910
4001
  const all = useChatStore.getState().messages;
3911
4002
  for (let i = all.length - 1; i >= 0; i--) {
3912
- const m = all[i];
4003
+ const m = expectDefined9(all[i]);
3913
4004
  if (m.role === "assistant") return m.id === message.id;
3914
4005
  }
3915
4006
  return false;
@@ -3920,13 +4011,13 @@ var MessageBubble = memo3(function MessageBubble2({
3920
4011
  if (idx === -1) return;
3921
4012
  let userIdx = -1;
3922
4013
  for (let i = idx - 1; i >= 0; i--) {
3923
- if (all[i].role === "user") {
4014
+ if (all[i]?.role === "user") {
3924
4015
  userIdx = i;
3925
4016
  break;
3926
4017
  }
3927
4018
  }
3928
4019
  if (userIdx === -1) return;
3929
- const userMsg = all[userIdx];
4020
+ const userMsg = expectDefined9(all[userIdx]);
3930
4021
  truncateAfter(userMsg.id);
3931
4022
  addMessage({ role: "user", content: userMsg.content });
3932
4023
  setLoading(true);
@@ -4003,7 +4094,14 @@ var MessageBubble = memo3(function MessageBubble2({
4003
4094
  const lines = message.toolResult ? message.toolResult.split("\n").length : 0;
4004
4095
  return /* @__PURE__ */ jsxs14("div", { className: "space-y-1", children: [
4005
4096
  inputSummary && !expanded && /* @__PURE__ */ jsx15("div", { className: "text-xs text-muted-foreground font-mono truncate", children: inputSummary }),
4006
- message.toolResult === void 0 && message.progressLines && message.progressLines.length > 0 && /* @__PURE__ */ jsx15("div", { className: "mt-1 rounded-md border border-amber-500/20 bg-amber-500/5 p-1.5 text-[11px] font-mono leading-snug max-h-32 overflow-auto", children: message.progressLines.slice(-6).map((line, i) => /* @__PURE__ */ jsx15("div", { className: "truncate text-muted-foreground", children: line }, i)) }),
4097
+ message.toolResult === void 0 && message.progressLines && message.progressLines.length > 0 && /* @__PURE__ */ jsx15("div", { className: "mt-1 rounded-md border border-amber-500/20 bg-amber-500/5 p-1.5 text-[11px] font-mono leading-snug max-h-32 overflow-auto", children: (() => {
4098
+ const seen = /* @__PURE__ */ new Map();
4099
+ return message.progressLines.slice(-6).map((line) => {
4100
+ const occurrence = seen.get(line) ?? 0;
4101
+ seen.set(line, occurrence + 1);
4102
+ return /* @__PURE__ */ jsx15("div", { className: "truncate text-muted-foreground", children: line }, `${line}-${occurrence}`);
4103
+ });
4104
+ })() }),
4007
4105
  expanded && message.toolInput !== void 0 && (() => {
4008
4106
  const diffArgs = diffFromToolInput(message.toolName, message.toolInput);
4009
4107
  if (diffArgs) return /* @__PURE__ */ jsx15(DiffView, { oldText: diffArgs.oldText, newText: diffArgs.newText, caption: diffArgs.caption });
@@ -4447,7 +4545,6 @@ function ThemeToggle({ className }) {
4447
4545
  "inline-flex items-center gap-0.5 rounded-lg border border-border bg-card/70 p-0.5 shadow-sm",
4448
4546
  className
4449
4547
  ),
4450
- role: "radiogroup",
4451
4548
  "aria-label": "Theme",
4452
4549
  children: OPTIONS.map(({ value, icon: Icon2, label }) => {
4453
4550
  const active = theme === value;
@@ -4455,8 +4552,7 @@ function ThemeToggle({ className }) {
4455
4552
  "button",
4456
4553
  {
4457
4554
  type: "button",
4458
- role: "radio",
4459
- "aria-checked": active,
4555
+ "aria-pressed": active,
4460
4556
  title: `${label} theme`,
4461
4557
  "aria-label": `${label} theme`,
4462
4558
  onClick: () => setTheme(value),
@@ -4477,6 +4573,12 @@ function ThemeToggle({ className }) {
4477
4573
  import { CheckCircle2 as CheckCircle23, ChevronDown as ChevronDown5, ChevronRight as ChevronRight3, Loader2 as Loader22, Terminal as Terminal2, XCircle as XCircle3 } from "lucide-react";
4478
4574
  import { memo as memo4, useState as useState15 } from "react";
4479
4575
  import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
4576
+ function expectDefined10(value) {
4577
+ if (value === null || value === void 0) {
4578
+ throw new Error("Expected value to be defined");
4579
+ }
4580
+ return value;
4581
+ }
4480
4582
  function formatDuration(ms) {
4481
4583
  if (ms < 1e3) return `${ms}ms`;
4482
4584
  if (ms < 6e4) return `${(ms / 1e3).toFixed(ms < 1e4 ? 2 : 1)}s`;
@@ -4491,7 +4593,7 @@ var ToolGroup = memo4(function ToolGroup2({
4491
4593
  }) {
4492
4594
  const [open, setOpen] = useState15(defaultOpen);
4493
4595
  if (tools.length === 1) {
4494
- return /* @__PURE__ */ jsx20(MessageBubble, { message: tools[0], isFirst: true, isContinuation });
4596
+ return /* @__PURE__ */ jsx20(MessageBubble, { message: expectDefined10(tools[0]), isFirst: true, isContinuation });
4495
4597
  }
4496
4598
  const running = tools.filter((t) => t.toolResult === void 0).length;
4497
4599
  const errored = tools.filter((t) => t.isError).length;
@@ -4864,6 +4966,12 @@ function ThinkingBubble() {
4864
4966
 
4865
4967
  // src/components/ChatView/index.tsx
4866
4968
  import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
4969
+ function expectDefined11(value) {
4970
+ if (value === null || value === void 0) {
4971
+ throw new Error("Expected value to be defined");
4972
+ }
4973
+ return value;
4974
+ }
4867
4975
  function ChatView() {
4868
4976
  const { messages, isLoading } = useChatStore();
4869
4977
  const setPaletteOpen = useUIStore((s) => s.setPaletteOpen);
@@ -5168,7 +5276,7 @@ function ChatView() {
5168
5276
  (() => {
5169
5277
  const groups = [];
5170
5278
  for (let i = 0; i < messages.length; i++) {
5171
- const m = messages[i];
5279
+ const m = expectDefined11(messages[i]);
5172
5280
  if (m.role === "tool") {
5173
5281
  const last = groups[groups.length - 1];
5174
5282
  if (last && last.kind === "tools") {
@@ -5219,12 +5327,12 @@ function ChatView() {
5219
5327
  };
5220
5328
  const turnTs = (t) => {
5221
5329
  if (t.kind === "user") return t.message.timestamp;
5222
- const first = t.items[0];
5223
- return first.kind === "msg" ? first.message.timestamp : first.tools[0].timestamp;
5330
+ const first = expectDefined11(t.items[0]);
5331
+ return first.kind === "msg" ? first.message.timestamp : first.tools[0]?.timestamp;
5224
5332
  };
5225
5333
  const out = [];
5226
5334
  for (let idx = 0; idx < turns.length; idx++) {
5227
- const t = turns[idx];
5335
+ const t = expectDefined11(turns[idx]);
5228
5336
  const ts = turnTs(t);
5229
5337
  const day = dayKey(ts);
5230
5338
  if (day !== prevDay) {
@@ -6561,7 +6669,7 @@ function ProviderSection({
6561
6669
  const catalogByFamily = filteredCatalog.reduce(
6562
6670
  (acc, p) => {
6563
6671
  if (!acc[p.family]) acc[p.family] = [];
6564
- acc[p.family].push(p);
6672
+ acc[p.family]?.push(p);
6565
6673
  return acc;
6566
6674
  },
6567
6675
  {}
@@ -7812,8 +7920,104 @@ function Sidebar() {
7812
7920
  ] });
7813
7921
  }
7814
7922
 
7923
+ // src/components/TodosPanel.tsx
7924
+ import { CheckCircle2 as CheckCircle27, Circle as Circle2, Clock as Clock3, X as X8 } from "lucide-react";
7925
+ import { useCallback as useCallback6, useEffect as useEffect21, useRef as useRef12, useState as useState26 } from "react";
7926
+ import { jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
7927
+ function TodosPanel() {
7928
+ const [todos, setTodos] = useState26([]);
7929
+ const ws = getWSClient();
7930
+ const offRef = useRef12(null);
7931
+ useEffect21(() => {
7932
+ ws.send({ type: "todos.get" });
7933
+ offRef.current = ws.on("todos.updated", (msg) => {
7934
+ const payload = msg?.payload;
7935
+ if (payload?.todos) setTodos(payload.todos);
7936
+ });
7937
+ return () => {
7938
+ offRef.current?.();
7939
+ };
7940
+ }, [ws]);
7941
+ const handleRemove = useCallback6(
7942
+ (id) => {
7943
+ ws.removeTodo(id);
7944
+ },
7945
+ [ws]
7946
+ );
7947
+ const pending = todos.filter((t) => t.status === "pending");
7948
+ const inProgress = todos.filter((t) => t.status === "in_progress");
7949
+ const completed = todos.filter((t) => t.status === "completed");
7950
+ return /* @__PURE__ */ jsxs38("div", { className: "flex flex-col h-full bg-background", children: [
7951
+ /* @__PURE__ */ jsxs38("div", { className: "px-4 py-3 border-b border-border flex items-center justify-between shrink-0", children: [
7952
+ /* @__PURE__ */ jsxs38("div", { className: "flex items-center gap-2", children: [
7953
+ /* @__PURE__ */ jsx43("h2", { className: "text-sm font-semibold text-foreground", children: "TODOS" }),
7954
+ /* @__PURE__ */ jsxs38("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
7955
+ completed.length,
7956
+ "/",
7957
+ todos.length
7958
+ ] })
7959
+ ] }),
7960
+ /* @__PURE__ */ jsxs38("div", { className: "flex items-center gap-2 text-xs", children: [
7961
+ inProgress.length > 0 && /* @__PURE__ */ jsxs38("span", { className: "flex items-center gap-1 text-yellow-600 dark:text-yellow-400", children: [
7962
+ /* @__PURE__ */ jsx43(Clock3, { className: "w-3 h-3" }),
7963
+ inProgress.length
7964
+ ] }),
7965
+ pending.length > 0 && /* @__PURE__ */ jsxs38("span", { className: "flex items-center gap-1 text-muted-foreground", children: [
7966
+ /* @__PURE__ */ jsx43(Circle2, { className: "w-3 h-3" }),
7967
+ pending.length
7968
+ ] }),
7969
+ completed.length > 0 && /* @__PURE__ */ jsxs38("span", { className: "flex items-center gap-1 text-emerald-600 dark:text-emerald-400", children: [
7970
+ /* @__PURE__ */ jsx43(CheckCircle27, { className: "w-3 h-3" }),
7971
+ completed.length
7972
+ ] })
7973
+ ] })
7974
+ ] }),
7975
+ /* @__PURE__ */ jsx43("div", { className: "flex-1 overflow-y-auto", children: todos.length === 0 ? /* @__PURE__ */ jsx43("p", { className: "px-4 py-8 text-xs text-muted-foreground text-center", children: "No todos yet. The agent will create them as it plans work." }) : /* @__PURE__ */ jsx43("div", { className: "py-1", children: todos.map((t) => {
7976
+ const label = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
7977
+ const isInProgress = t.status === "in_progress";
7978
+ const isCompleted = t.status === "completed";
7979
+ return /* @__PURE__ */ jsxs38(
7980
+ "div",
7981
+ {
7982
+ className: `px-4 py-2 flex items-start gap-2.5 text-sm border-l-2 group ${isInProgress ? "border-l-yellow-500 bg-yellow-50/40 dark:bg-yellow-950/20" : isCompleted ? "border-l-emerald-500 bg-emerald-50/30 dark:bg-emerald-950/15" : "border-l-transparent"}`,
7983
+ children: [
7984
+ /* @__PURE__ */ jsx43("span", { className: "mt-0.5 shrink-0", children: isCompleted ? /* @__PURE__ */ jsx43(CheckCircle27, { className: "w-3.5 h-3.5 text-emerald-500" }) : isInProgress ? /* @__PURE__ */ jsx43(Clock3, { className: "w-3.5 h-3.5 text-yellow-500 animate-spin" }) : /* @__PURE__ */ jsx43(Circle2, { className: "w-3.5 h-3.5 text-muted-foreground/50" }) }),
7985
+ /* @__PURE__ */ jsx43(
7986
+ "span",
7987
+ {
7988
+ className: `leading-snug flex-1 ${isInProgress ? "text-yellow-800 dark:text-yellow-200 font-medium" : isCompleted ? "text-muted-foreground line-through" : "text-foreground"}`,
7989
+ children: label
7990
+ }
7991
+ ),
7992
+ /* @__PURE__ */ jsx43(
7993
+ "button",
7994
+ {
7995
+ type: "button",
7996
+ onClick: (e) => {
7997
+ e.stopPropagation();
7998
+ handleRemove(t.id);
7999
+ },
8000
+ className: "shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-60 hover:opacity-100 hover:bg-muted transition-all",
8001
+ title: "Remove",
8002
+ children: /* @__PURE__ */ jsx43(X8, { className: "w-3 h-3 text-muted-foreground" })
8003
+ }
8004
+ )
8005
+ ]
8006
+ },
8007
+ t.id
8008
+ );
8009
+ }) }) })
8010
+ ] });
8011
+ }
8012
+
7815
8013
  // src/App.tsx
7816
- import { Fragment as Fragment11, jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
8014
+ import { Fragment as Fragment11, jsx as jsx44, jsxs as jsxs39 } from "react/jsx-runtime";
8015
+ function expectDefined12(value) {
8016
+ if (value === null || value === void 0) {
8017
+ throw new Error("Expected value to be defined");
8018
+ }
8019
+ return value;
8020
+ }
7817
8021
  function AppInner() {
7818
8022
  const { theme } = useTheme();
7819
8023
  const { currentView, sidebarOpen, toggleSidebar, setSearchOpen, setSidebarOpen } = useUIStore();
@@ -7823,7 +8027,7 @@ function AppInner() {
7823
8027
  const sessionTitle = useSessionStore((s) => s.session?.title);
7824
8028
  const sessionId = useSessionStore((s) => s.session?.id);
7825
8029
  const nickname = useUIStore((s) => sessionId ? s.sessionNicknames[sessionId] : void 0);
7826
- useEffect21(() => {
8030
+ useEffect22(() => {
7827
8031
  if (typeof window === "undefined") return;
7828
8032
  const mq = window.matchMedia("(max-width: 768px)");
7829
8033
  const apply = () => {
@@ -7836,7 +8040,7 @@ function AppInner() {
7836
8040
  return () => mq.removeEventListener("change", apply);
7837
8041
  }, [setSidebarOpen]);
7838
8042
  useWebSocketBootstrap();
7839
- useEffect21(() => {
8043
+ useEffect22(() => {
7840
8044
  const parts = [];
7841
8045
  if (isLoading) {
7842
8046
  const it = iteration ? ` iter ${iteration.index}${iteration.max ? `/${iteration.max}` : ""}` : "";
@@ -7853,7 +8057,7 @@ function AppInner() {
7853
8057
  document.title = "WrongStack";
7854
8058
  };
7855
8059
  }, [isLoading, iteration, projectName, sessionTitle, nickname]);
7856
- useEffect21(() => {
8060
+ useEffect22(() => {
7857
8061
  const onKey = (e) => {
7858
8062
  const t = e.target;
7859
8063
  const tag = t?.tagName?.toLowerCase();
@@ -7922,12 +8126,12 @@ function AppInner() {
7922
8126
  }
7923
8127
  if (e.key === "g" && !e.shiftKey) {
7924
8128
  e.preventDefault();
7925
- focusBubble(bubbles[0]);
8129
+ focusBubble(expectDefined12(bubbles[0]));
7926
8130
  return;
7927
8131
  }
7928
8132
  if (e.key === "G" || e.key === "g" && e.shiftKey) {
7929
8133
  e.preventDefault();
7930
- focusBubble(bubbles[bubbles.length - 1]);
8134
+ focusBubble(expectDefined12(bubbles[bubbles.length - 1]));
7931
8135
  return;
7932
8136
  }
7933
8137
  if (e.key === "Escape" && current) {
@@ -7949,33 +8153,40 @@ function AppInner() {
7949
8153
  window.addEventListener("keydown", onKey);
7950
8154
  return () => window.removeEventListener("keydown", onKey);
7951
8155
  }, [toggleSidebar, setSearchOpen]);
7952
- return /* @__PURE__ */ jsxs38("div", { className: cn("flex h-screen", theme), children: [
7953
- sidebarOpen && /* @__PURE__ */ jsx43(Sidebar, {}),
7954
- /* @__PURE__ */ jsxs38("main", { className: "flex-1 flex flex-col overflow-hidden", children: [
7955
- /* @__PURE__ */ jsx43(ConnectionBanner, {}),
7956
- currentView === "chat" && /* @__PURE__ */ jsxs38(Fragment11, { children: [
7957
- sessionId && /* @__PURE__ */ jsxs38("div", { className: "px-4 pt-2 space-y-2", children: [
7958
- /* @__PURE__ */ jsx43(CollabPanel, { sessionId }),
7959
- /* @__PURE__ */ jsx43(FleetPanel, {})
8156
+ return /* @__PURE__ */ jsxs39("div", { className: cn("flex h-screen", theme), children: [
8157
+ sidebarOpen && /* @__PURE__ */ jsx44(Sidebar, {}),
8158
+ /* @__PURE__ */ jsxs39("main", { className: "flex-1 flex flex-col overflow-hidden", children: [
8159
+ /* @__PURE__ */ jsx44(ConnectionBanner, {}),
8160
+ currentView === "chat" && /* @__PURE__ */ jsxs39(Fragment11, { children: [
8161
+ sessionId && /* @__PURE__ */ jsxs39("div", { className: "px-4 pt-2 space-y-2", children: [
8162
+ /* @__PURE__ */ jsx44(CollabPanel, { sessionId }),
8163
+ /* @__PURE__ */ jsx44(FleetPanel, {}),
8164
+ /* @__PURE__ */ jsx44(TodosPanel, {})
7960
8165
  ] }),
7961
- /* @__PURE__ */ jsx43(ChatView, {})
8166
+ /* @__PURE__ */ jsx44(ChatView, {})
7962
8167
  ] }),
7963
- currentView === "settings" && /* @__PURE__ */ jsx43(SettingsPanel, {})
8168
+ currentView === "settings" && /* @__PURE__ */ jsx44(SettingsPanel, {})
7964
8169
  ] }),
7965
- /* @__PURE__ */ jsx43(ConfirmDialog, {}),
7966
- /* @__PURE__ */ jsx43(CommandPalette, {}),
7967
- /* @__PURE__ */ jsx43(ShortcutsOverlay, {}),
7968
- /* @__PURE__ */ jsx43(QuickModelSwitcher, {}),
7969
- /* @__PURE__ */ jsx43(Toaster, {})
8170
+ /* @__PURE__ */ jsx44(ConfirmDialog, {}),
8171
+ /* @__PURE__ */ jsx44(CommandPalette, {}),
8172
+ /* @__PURE__ */ jsx44(ShortcutsOverlay, {}),
8173
+ /* @__PURE__ */ jsx44(QuickModelSwitcher, {}),
8174
+ /* @__PURE__ */ jsx44(Toaster, {})
7970
8175
  ] });
7971
8176
  }
7972
8177
  function App() {
7973
- return /* @__PURE__ */ jsx43(ErrorBoundary, { children: /* @__PURE__ */ jsx43(ThemeProvider, { defaultTheme: "system", children: /* @__PURE__ */ jsx43(AppInner, {}) }) });
8178
+ return /* @__PURE__ */ jsx44(ErrorBoundary, { children: /* @__PURE__ */ jsx44(ThemeProvider, { defaultTheme: "system", children: /* @__PURE__ */ jsx44(AppInner, {}) }) });
7974
8179
  }
7975
8180
 
7976
8181
  // src/main.tsx
7977
- import { jsx as jsx44 } from "react/jsx-runtime";
7978
- ReactDOM.createRoot(document.getElementById("root")).render(
7979
- /* @__PURE__ */ jsx44(React6.StrictMode, { children: /* @__PURE__ */ jsx44(App, {}) })
8182
+ import { jsx as jsx45 } from "react/jsx-runtime";
8183
+ function expectDefined13(value) {
8184
+ if (value === null || value === void 0) {
8185
+ throw new Error("Expected value to be defined");
8186
+ }
8187
+ return value;
8188
+ }
8189
+ ReactDOM.createRoot(expectDefined13(document.getElementById("root"))).render(
8190
+ /* @__PURE__ */ jsx45(React6.StrictMode, { children: /* @__PURE__ */ jsx45(App, {}) })
7980
8191
  );
7981
8192
  //# sourceMappingURL=index.js.map