zidane 5.2.1 → 5.3.1

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.
Files changed (67) hide show
  1. package/README.md +7 -5
  2. package/dist/{agent-CGQajqtC.d.ts → agent-bKs7MRT2.d.ts} +429 -4
  3. package/dist/agent-bKs7MRT2.d.ts.map +1 -0
  4. package/dist/chat.d.ts +212 -58
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +2 -2
  7. package/dist/{errors-COmsomd5.js → errors-Byb0F8B9.js} +44 -2
  8. package/dist/errors-Byb0F8B9.js.map +1 -0
  9. package/dist/{index-DwbcFBr_.d.ts → index-BlMvPh9X.d.ts} +29 -3
  10. package/dist/index-BlMvPh9X.d.ts.map +1 -0
  11. package/dist/{index-BDP6mA3Y.d.ts → index-CTmNaIDb.d.ts} +2 -2
  12. package/dist/{index-BDP6mA3Y.d.ts.map → index-CTmNaIDb.d.ts.map} +1 -1
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +10 -10
  15. package/dist/{interpolate-BhmHKD6x.js → interpolate-ERgZUxgg.js} +2 -2
  16. package/dist/{interpolate-BhmHKD6x.js.map → interpolate-ERgZUxgg.js.map} +1 -1
  17. package/dist/{login-D7Tp-K5f.js → login-CNS9_8Ue.js} +3 -3
  18. package/dist/{login-D7Tp-K5f.js.map → login-CNS9_8Ue.js.map} +1 -1
  19. package/dist/{mcp-B1psg7jf.js → mcp-ZsSFo4Dp.js} +2 -2
  20. package/dist/{mcp-B1psg7jf.js.map → mcp-ZsSFo4Dp.js.map} +1 -1
  21. package/dist/mcp.d.ts +1 -1
  22. package/dist/mcp.js +1 -1
  23. package/dist/{messages-DsbMYNmt.js → messages-D0xT979U.js} +631 -68
  24. package/dist/messages-D0xT979U.js.map +1 -0
  25. package/dist/{presets-AgF0RFx1.js → presets-h5i3kpOP.js} +2 -2
  26. package/dist/{presets-AgF0RFx1.js.map → presets-h5i3kpOP.js.map} +1 -1
  27. package/dist/presets.d.ts +2 -2
  28. package/dist/presets.js +1 -1
  29. package/dist/{providers-v1Rn2rqG.js → providers-x3LZByR5.js} +38 -6
  30. package/dist/providers-x3LZByR5.js.map +1 -0
  31. package/dist/providers.d.ts +2 -2
  32. package/dist/providers.js +3 -3
  33. package/dist/session/sqlite.d.ts +1 -1
  34. package/dist/session/sqlite.js +1 -1
  35. package/dist/{session-DOJgRXvF.js → session-BHZwxmfr.js} +2 -2
  36. package/dist/{session-DOJgRXvF.js.map → session-BHZwxmfr.js.map} +1 -1
  37. package/dist/session.d.ts +1 -1
  38. package/dist/session.js +2 -2
  39. package/dist/skills.d.ts +2 -2
  40. package/dist/skills.js +1 -1
  41. package/dist/{tools-BRbbfdJh.js → tools-CWEDS2ZT.js} +251 -47
  42. package/dist/tools-CWEDS2ZT.js.map +1 -0
  43. package/dist/tools.d.ts +2 -2
  44. package/dist/tools.js +1 -1
  45. package/dist/{transcript-anchors-BBuIoU0x.d.ts → transcript-anchors-DOUqyvXR.d.ts} +28 -4
  46. package/dist/transcript-anchors-DOUqyvXR.d.ts.map +1 -0
  47. package/dist/tui.d.ts +29 -3
  48. package/dist/tui.d.ts.map +1 -1
  49. package/dist/tui.js +363 -28
  50. package/dist/tui.js.map +1 -1
  51. package/dist/{turn-operations-gJ0qtLPv.js → turn-operations-D9HvatsR.js} +396 -89
  52. package/dist/turn-operations-D9HvatsR.js.map +1 -0
  53. package/dist/types-IcokUOyC.js.map +1 -1
  54. package/dist/types.d.ts +2 -2
  55. package/dist/types.js +1 -1
  56. package/docs/ARCHITECTURE.md +3 -2
  57. package/docs/CHAT.md +55 -16
  58. package/docs/TUI.md +22 -2
  59. package/package.json +1 -1
  60. package/dist/agent-CGQajqtC.d.ts.map +0 -1
  61. package/dist/errors-COmsomd5.js.map +0 -1
  62. package/dist/index-DwbcFBr_.d.ts.map +0 -1
  63. package/dist/messages-DsbMYNmt.js.map +0 -1
  64. package/dist/providers-v1Rn2rqG.js.map +0 -1
  65. package/dist/tools-BRbbfdJh.js.map +0 -1
  66. package/dist/transcript-anchors-BBuIoU0x.d.ts.map +0 -1
  67. package/dist/turn-operations-gJ0qtLPv.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,11 +1,11 @@
1
- import { S as cleanupPersistedSession, p as createAgent, w as resolvePersistDir } from "./tools-BRbbfdJh.js";
2
- import { o as errorMessage } from "./errors-COmsomd5.js";
3
- import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-B1psg7jf.js";
4
- import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-D7Tp-K5f.js";
1
+ import { D as resolvePersistDir, T as cleanupPersistedSession, p as createAgent } from "./tools-CWEDS2ZT.js";
2
+ import { s as errorMessage } from "./errors-Byb0F8B9.js";
3
+ import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-ZsSFo4Dp.js";
4
+ import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-CNS9_8Ue.js";
5
5
  import { n as formatTokenUsage } from "./stats-DgOvY7wd.js";
6
- import { n as loadSession, t as createSession } from "./session-DOJgRXvF.js";
6
+ import { n as loadSession, t as createSession } from "./session-BHZwxmfr.js";
7
7
  import { createTuiStore } from "./session/sqlite.js";
8
- import { $ as useMcpAuthDispatch, $n as bootTick, $t as isVisible, A as getSafelist, At as clampFps, B as supportsOAuth, Bn as uniqueSkillNamesFromReferences, Ct as shortId, D as useSafeModeQueue, Dn as findGitRoot, Dr as accentColor, Dt as SETTINGS_CHOICES, E as useSafeModeActions, En as summarizeOutcomes, Et as DEFAULT_SETTINGS, F as suggestSafelistEntry, Ft as resolveTheme, Gt as ConfigProvider, H as filterModelCatalog, Hn as createFilesCompletionProvider, Ht as DiscoveryProvider, Jn as useCompletion, K as discoverProjectMcps, Kt as useConfig, L as splitPromptSegments, Nn as matchesBinding, Ot as SETTINGS_TOGGLES, Pt as resolveChipColor, Q as McpAuthProvider, Qt as isTurnHighlighted, R as formatPathForCwd, Sn as buildEditOutcomesAnnotation, St as fmtTokens, T as SafeModeProvider, Tn as resolveApprovalForPayload, Tt as useEnabledToggleSet, U as indexOfEntry, Ut as useDiscovery, V as buildModelCatalog, Vt as createDiscoverySlot, W as buildMcpServers, Wt as useDiscoveryOptional, Xn as buildLinearRamp, Xr as buildPlanSystem, Xt as eventsFromTurns, Y as createFileMcpCredentialStore, Yn as blendHsl, Yr as buildBuildSystem, Yt as deriveSessionTitle, Zn as tryOpenBrowser, Zt as isEditErrorResult, _ as turnContextSize, _n as extractEditPayload, _r as modelSupportsReasoning, _t as truncateTrailing, a as computeTurnAnchors, an as selectableTurnIds, at as InteractionsProvider, b as defaultSkillScanPaths, bt as ageString, c as formatToolCall, ct as createInteractionTools, d as useSelectStyle, dn as turnSelectionOwnership, dt as pendingInteractionsFromTurns, en as lastContextSizeFromTurns, er as shouldAutoCompact, et as useMcpAuthState, f as useSurfaces, g as finalizeStreamingMarkdownForOwner, gt as hintsLength, h as finalizeStreamingMarkdown, hr as getContextWindow, ht as clipHintsToWidth, i as turnAsText, j as isOnSafelist, jn as ensureKeybindingsFile, jt as useSettings, k as addToSafelist, kt as SettingsProvider, l as ThemeProvider, ln as toolCallPreview, m as useTheme, mn as buildUnifiedDiff, mt as useInteractionsQueue, n as deleteTurnSafely, o as TOOL_DISPLAY, on as stripSpawnTokensLine, pt as useInteractionsActions, qt as resolveConfig, r as truncateTurnsAt, rn as marginTopFor, rt as splitMarkdownCodeBlocks, s as displayNameFor, sn as sumRunCosts, sr as setProviderCredential, st as buildResumedToolResultsTurn, tn as listSessionMeta, tr as detectAuth, tt as getMcpAuthStatus, u as useColors, un as toolResultText, ut as makeRequestInteraction, v as useStreamBuffer, vn as filetypeFromPath, w as writeSessionExport, wt as listProjectFiles, x as discoverProjectSkills, xr as piIdOf, xt as compactPath, y as buildSkillsConfig, yn as previewEditPayload, yt as generateSessionTitle, z as runOAuthLogin, zn as createSkillsCompletionProvider } from "./turn-operations-gJ0qtLPv.js";
8
+ import { $ as useMcpAuthDispatch, $n as bootTick, $t as isVisible, A as getSafelist, At as clampFps, B as supportsOAuth, Bn as uniqueSkillNamesFromReferences, Ct as shortId, D as useSafeModeQueue, Dn as findGitRoot, Dr as accentColor, Dt as SETTINGS_CHOICES, E as useSafeModeActions, En as summarizeOutcomes, Et as DEFAULT_SETTINGS, F as suggestSafelistEntry, Ft as resolveTheme, Gt as ConfigProvider, H as filterModelCatalog, Hn as createFilesCompletionProvider, Ht as DiscoveryProvider, Jn as useCompletion, K as discoverProjectMcps, Kt as useConfig, L as splitPromptSegments, Nn as matchesBinding, Nr as TODO_STATUS_GLYPHS, Ot as SETTINGS_TOGGLES, Pt as resolveChipColor, Q as McpAuthProvider, Qt as isTurnHighlighted, R as formatPathForCwd, Sn as buildEditOutcomesAnnotation, St as fmtTokens, T as SafeModeProvider, Tn as resolveApprovalForPayload, Tt as useEnabledToggleSet, U as indexOfEntry, Ur as useActiveTodos, Ut as useDiscovery, V as buildModelCatalog, Vt as createDiscoverySlot, W as buildMcpServers, Wt as useDiscoveryOptional, Xn as buildLinearRamp, Xt as eventsFromTurns, Y as createFileMcpCredentialStore, Yn as blendHsl, Yt as deriveSessionTitle, Zn as tryOpenBrowser, Zt as isEditErrorResult, _ as turnContextSize, _n as extractEditPayload, _r as modelSupportsReasoning, _t as truncateTrailing, a as computeTurnAnchors, an as selectableTurnIds, at as InteractionsProvider, b as defaultSkillScanPaths, bt as ageString, c as formatToolCall, ct as createInteractionTools, d as useSelectStyle, dn as turnSelectionOwnership, dt as pendingInteractionsFromTurns, ei as buildBuildSystem, en as lastContextSizeFromTurns, er as shouldAutoCompact, et as useMcpAuthState, f as useSurfaces, g as finalizeStreamingMarkdownForOwner, gt as hintsLength, h as finalizeStreamingMarkdown, hr as getContextWindow, ht as clipHintsToWidth, i as turnAsText, j as isOnSafelist, jn as ensureKeybindingsFile, jt as useSettings, k as addToSafelist, kt as SettingsProvider, l as ThemeProvider, ln as toolCallPreview, m as useTheme, mn as buildUnifiedDiff, mt as useInteractionsQueue, n as deleteTurnSafely, o as TOOL_DISPLAY, on as stripSpawnTokensLine, pt as useInteractionsActions, qt as resolveConfig, r as truncateTurnsAt, rn as marginTopFor, rt as splitMarkdownCodeBlocks, s as displayNameFor, sn as sumRunCosts, sr as setProviderCredential, st as buildResumedToolResultsTurn, ti as buildPlanSystem, tn as listSessionMeta, tr as detectAuth, tt as getMcpAuthStatus, u as useColors, un as toolResultText, ut as makeRequestInteraction, v as useStreamBuffer, vn as filetypeFromPath, w as writeSessionExport, wt as listProjectFiles, x as discoverProjectSkills, xr as piIdOf, xt as compactPath, y as buildSkillsConfig, yn as previewEditPayload, yt as generateSessionTitle, z as runOAuthLogin, zn as createSkillsCompletionProvider } from "./turn-operations-D9HvatsR.js";
9
9
  import { spawn } from "node:child_process";
10
10
  import { Buffer } from "node:buffer";
11
11
  import * as fs from "node:fs";
@@ -80,7 +80,7 @@ function useModalAwareFocus(preferred = true) {
80
80
  *
81
81
  * Uses `useTerminalDimensions()` so it reflows on `SIGWINCH` without remount.
82
82
  */
83
- function Modal({ title, bottomTitle, onClose, disableEscape = false, children, maxWidth = 92, minWidth = 44, maxHeight, horizontalMargin = 4, verticalMargin = 2 }) {
83
+ function Modal({ title, bottomTitle, rightTitle, onClose, disableEscape = false, children, maxWidth = 92, minWidth = 44, maxHeight, horizontalMargin = 4, verticalMargin = 2 }) {
84
84
  const ctx = useContext(ModalContext);
85
85
  const dismiss = onClose ?? ctx?.close;
86
86
  const COLOR = useColors();
@@ -91,7 +91,7 @@ function Modal({ title, bottomTitle, onClose, disableEscape = false, children, m
91
91
  const { width: termWidth, height: termHeight } = useTerminalDimensions();
92
92
  const width = Math.max(minWidth, Math.min(maxWidth, termWidth - horizontalMargin * 2));
93
93
  const height = maxHeight === void 0 ? void 0 : Math.min(maxHeight, Math.max(0, termHeight - verticalMargin * 2));
94
- return /* @__PURE__ */ jsx("box", {
94
+ const panel = /* @__PURE__ */ jsx("box", {
95
95
  title: title ? ` ${title} ` : void 0,
96
96
  bottomTitle: bottomTitle ? ` ${bottomTitle} ` : void 0,
97
97
  bottomTitleAlignment: "right",
@@ -110,6 +110,21 @@ function Modal({ title, bottomTitle, onClose, disableEscape = false, children, m
110
110
  },
111
111
  children
112
112
  });
113
+ if (rightTitle == null) return panel;
114
+ return /* @__PURE__ */ jsxs("box", {
115
+ style: {
116
+ width,
117
+ flexDirection: "column"
118
+ },
119
+ children: [panel, /* @__PURE__ */ jsx("box", {
120
+ style: {
121
+ position: "absolute",
122
+ top: 0,
123
+ right: 1
124
+ },
125
+ children: rightTitle
126
+ })]
127
+ });
113
128
  }
114
129
  //#endregion
115
130
  //#region src/tui/agent-picker.tsx
@@ -923,7 +938,7 @@ function StatusSpinner({ color }) {
923
938
  function Transcript({ events, settings, selectedTurnId = null, busy = false }) {
924
939
  const COLOR = useColors();
925
940
  const items = useMemo(() => partitionTranscript(events, settings), [events, settings]);
926
- const showThrobber = busy;
941
+ const showThrobber = busy && settings.showThrobber;
927
942
  const throbberLabel = events.length > 0 && events[events.length - 1].kind === "thinking" ? "Thinking" : void 0;
928
943
  const ownership = useMemo(() => turnSelectionOwnership(events), [events]);
929
944
  const scrollboxRef = useRef(null);
@@ -1145,13 +1160,16 @@ function EventLineImpl({ event, depthOffset = 0 }) {
1145
1160
  dim: child
1146
1161
  })
1147
1162
  });
1148
- return /* @__PURE__ */ jsx("box", {
1163
+ return /* @__PURE__ */ jsxs("box", {
1149
1164
  style: row,
1150
- children: /* @__PURE__ */ jsx(ToolCallBlock, {
1165
+ children: [/* @__PURE__ */ jsx(ToolCallBlock, {
1151
1166
  event,
1152
1167
  display: settings.toolCallDisplay === "full" ? "full" : "formatted",
1153
1168
  dim: child
1154
- })
1169
+ }), event.tool === "todowrite" && /* @__PURE__ */ jsx(TodoInProgressList, {
1170
+ input: event.input,
1171
+ dim: child
1172
+ })]
1155
1173
  });
1156
1174
  case "tool-result": return /* @__PURE__ */ jsx(ToolResultBlock, {
1157
1175
  text: event.text,
@@ -1644,6 +1662,7 @@ const MarkdownBlock = memo(({ text, dim }) => {
1644
1662
  const SURFACE = useSurfaces();
1645
1663
  const mdStyle = useMdStyle();
1646
1664
  const renderer = useRenderer();
1665
+ const content = text.replace(/^\n+|\n+$/g, "");
1647
1666
  const bag = useRef({
1648
1667
  ctx: renderer,
1649
1668
  colors: COLOR,
@@ -1658,7 +1677,7 @@ const MarkdownBlock = memo(({ text, dim }) => {
1658
1677
  }, [COLOR, SURFACE]);
1659
1678
  const renderNode = useMemo(() => makeMarkdownRenderNode(bag), []);
1660
1679
  return /* @__PURE__ */ jsx("markdown", {
1661
- content: text,
1680
+ content,
1662
1681
  syntaxStyle: mdStyle,
1663
1682
  streaming: true,
1664
1683
  internalBlockMode: "coalesced",
@@ -1966,6 +1985,43 @@ function ToolCallBlock({ event, display, dim }) {
1966
1985
  ]
1967
1986
  });
1968
1987
  }
1988
+ function TodoInProgressList({ input, dim }) {
1989
+ const COLOR = useColors();
1990
+ const items = useMemo(() => {
1991
+ const raw = input?.todos;
1992
+ if (!Array.isArray(raw)) return [];
1993
+ return raw.flatMap((t) => {
1994
+ if (t == null || typeof t !== "object") return [];
1995
+ const rec = t;
1996
+ if (rec.status !== "in_progress") return [];
1997
+ const id = typeof rec.id === "string" ? rec.id : "";
1998
+ const content = typeof rec.content === "string" ? rec.content : "";
1999
+ if (!id || !content) return [];
2000
+ return [{
2001
+ id,
2002
+ content
2003
+ }];
2004
+ });
2005
+ }, [input]);
2006
+ if (items.length === 0) return null;
2007
+ const glyph = TODO_STATUS_GLYPHS.in_progress;
2008
+ return /* @__PURE__ */ jsx("box", {
2009
+ style: {
2010
+ flexDirection: "column",
2011
+ marginLeft: 2
2012
+ },
2013
+ children: items.map((item) => /* @__PURE__ */ jsxs("text", {
2014
+ wrapMode: "none",
2015
+ children: [/* @__PURE__ */ jsx("span", {
2016
+ fg: COLOR.mute,
2017
+ children: `${glyph} `
2018
+ }), /* @__PURE__ */ jsx("span", {
2019
+ fg: dim ? COLOR.dim : COLOR.warn,
2020
+ children: item.content
2021
+ })]
2022
+ }, item.id))
2023
+ });
2024
+ }
1969
2025
  //#endregion
1970
2026
  //#region src/tui/discovery-shell.tsx
1971
2027
  /**
@@ -4160,6 +4216,72 @@ function OptionList({ items, initialCursor, onPick }) {
4160
4216
  });
4161
4217
  }
4162
4218
  //#endregion
4219
+ //#region src/tui/todo-indicator.tsx
4220
+ /**
4221
+ * Layout budget when truncating the content. The bar never wraps —
4222
+ * single-line by contract — so we subtract every column consumed by
4223
+ * surrounding chrome before measuring how much room is left for the
4224
+ * todo's `content` text.
4225
+ *
4226
+ * - 2 cols ⇒ `<ChatScreen>`'s `border: true` (1 cell each side).
4227
+ * - 2 cols ⇒ this indicator's own `paddingLeft: 1 + paddingRight: 1`
4228
+ * (mirrors the queue block's outer padding so the bar
4229
+ * and queue read as aligned siblings).
4230
+ * - 4 cols ⇒ the indicator's own visible chrome: glyph "◐" (1) +
4231
+ * two spaces (2) + 1 col of trailing breathing room.
4232
+ *
4233
+ * That's 8 cols total of non-content overhead. Below `MIN_VISIBLE_TAIL`
4234
+ * the bar bails — a one-or-two-char tail isn't worth painting.
4235
+ */
4236
+ const SCREEN_BORDER_COLS = 2;
4237
+ const INDICATOR_PADDING_COLS = 2;
4238
+ const CHROME_COLS = 4;
4239
+ const MIN_VISIBLE_TAIL = 8;
4240
+ function truncateForWidth(text, max) {
4241
+ if (max <= 0) return "";
4242
+ if (text.length <= max) return text;
4243
+ if (max <= 1) return "…";
4244
+ return `${text.slice(0, max - 1)}…`;
4245
+ }
4246
+ function TodoIndicator({ session }) {
4247
+ const { settings } = useSettings();
4248
+ const COLOR = useColors();
4249
+ const { width: termWidth } = useTerminalDimensions();
4250
+ const state = useActiveTodos(session);
4251
+ if (!settings.showTodoIndicator) return null;
4252
+ const item = state.inProgress;
4253
+ if (!item) return null;
4254
+ const usable = Math.max(0, termWidth - SCREEN_BORDER_COLS - INDICATOR_PADDING_COLS - CHROME_COLS);
4255
+ if (usable < MIN_VISIBLE_TAIL) return null;
4256
+ const display = truncateForWidth(item.content.replace(/\s+/g, " ").trim(), usable);
4257
+ return /* @__PURE__ */ jsx("box", {
4258
+ style: {
4259
+ marginTop: 1,
4260
+ flexShrink: 0,
4261
+ flexDirection: "row",
4262
+ paddingLeft: 1,
4263
+ paddingRight: 1
4264
+ },
4265
+ children: /* @__PURE__ */ jsxs("text", {
4266
+ wrapMode: "none",
4267
+ children: [
4268
+ /* @__PURE__ */ jsx("span", {
4269
+ fg: COLOR.warn,
4270
+ children: TODO_STATUS_GLYPHS.in_progress
4271
+ }),
4272
+ /* @__PURE__ */ jsx("span", {
4273
+ fg: COLOR.mute,
4274
+ children: " "
4275
+ }),
4276
+ /* @__PURE__ */ jsx("span", {
4277
+ fg: COLOR.dim,
4278
+ children: display
4279
+ })
4280
+ ]
4281
+ })
4282
+ });
4283
+ }
4284
+ //#endregion
4163
4285
  //#region src/tui/screens.tsx
4164
4286
  /**
4165
4287
  * Build a key-binding set for the prompt textarea / API-key input. Strips the
@@ -4978,7 +5100,7 @@ const CWD_DISPLAY = compactPath(process.cwd());
4978
5100
  * default-value reference at the prop destructure.
4979
5101
  */
4980
5102
  const EMPTY_QUEUED_MESSAGES = [];
4981
- function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, onPopupOpenChange, selectedTurnId, promptTriggerHints }) {
5103
+ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_QUEUED_MESSAGES, queueSelectionIndex = null, queueShortcuts, onEnterQueueFromEmptyPrompt, settings, onSubmit, session, pending, onApproval, pendingInteraction, onInteraction, completionProviders, onPopupOpenChange, selectedTurnId, promptTriggerHints, liveSession = null }) {
4982
5104
  const COLOR = useColors();
4983
5105
  const titleText = session?.title ?? "untitled";
4984
5106
  const showSessionShortcut = !!session && !busy && !pending && !pendingInteraction;
@@ -5077,20 +5199,24 @@ function ChatScreen({ events, busy, compacting = false, queuedMessages = EMPTY_Q
5077
5199
  }) : pendingInteraction ? /* @__PURE__ */ jsx(InteractionBlock, {
5078
5200
  request: pendingInteraction,
5079
5201
  onResolve: onInteraction
5080
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [queuedMessages.length > 0 && !completionPopupOpen && /* @__PURE__ */ jsx(QueuedMessagesBlock, {
5081
- messages: queuedMessages,
5082
- selectionIndex: queueSelectionIndex,
5083
- shortcuts: queueShortcuts
5084
- }), /* @__PURE__ */ jsx(PromptBlock, {
5085
- userPrompts,
5086
- onSubmit,
5087
- completionProviders,
5088
- onPopupOpenChange: handlePopupOpenChange,
5089
- selectMode: queueSelectionIndex != null ? "queue" : selectedTurnId != null ? "turn" : null,
5090
- triggerHints: promptTriggerHints,
5091
- busy,
5092
- onEnterQueueFromEmpty: onEnterQueueFromEmptyPrompt
5093
- })] }),
5202
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5203
+ queuedMessages.length > 0 && !completionPopupOpen && /* @__PURE__ */ jsx(QueuedMessagesBlock, {
5204
+ messages: queuedMessages,
5205
+ selectionIndex: queueSelectionIndex,
5206
+ shortcuts: queueShortcuts
5207
+ }),
5208
+ /* @__PURE__ */ jsx(TodoIndicator, { session: liveSession }),
5209
+ /* @__PURE__ */ jsx(PromptBlock, {
5210
+ userPrompts,
5211
+ onSubmit,
5212
+ completionProviders,
5213
+ onPopupOpenChange: handlePopupOpenChange,
5214
+ selectMode: queueSelectionIndex != null ? "queue" : selectedTurnId != null ? "turn" : null,
5215
+ triggerHints: promptTriggerHints,
5216
+ busy,
5217
+ onEnterQueueFromEmpty: onEnterQueueFromEmptyPrompt
5218
+ })
5219
+ ] }),
5094
5220
  /* @__PURE__ */ jsx(TitleOverlay, {
5095
5221
  title: titleText,
5096
5222
  meta: metaSegments
@@ -7276,6 +7402,207 @@ function displayPath$1(path, home) {
7276
7402
  return path;
7277
7403
  }
7278
7404
  //#endregion
7405
+ //#region src/tui/todos-modal.tsx
7406
+ /** Floor on the modal height — keeps the header + at least a few rows visible on tiny terminals. */
7407
+ const MIN_MODAL_HEIGHT = 12;
7408
+ /** Ceiling on the modal height — prevents a giant list from blanket-filling a tall window. */
7409
+ const MAX_MODAL_HEIGHT$1 = 36;
7410
+ /**
7411
+ * Per-status palette for a row — the glyph and the content text pick
7412
+ * different tones so a quick scan reads the status from the glyph color
7413
+ * while the row content stays in a calm reading tone:
7414
+ *
7415
+ * pending glyph `mute` · text `dim` — queued / not yet started
7416
+ * in_progress glyph `warn` · text `brand` — currently working (loudest)
7417
+ * completed glyph `accent` · text `dim` — done (success glyph, calm text)
7418
+ * cancelled glyph `error` · text `mute` — explicitly dropped
7419
+ *
7420
+ * All tokens come from the active theme — a runtime theme switch
7421
+ * repaints without touching this component.
7422
+ */
7423
+ function statusColors(status, COLOR) {
7424
+ switch (status) {
7425
+ case "in_progress": return {
7426
+ glyph: COLOR.warn,
7427
+ text: COLOR.brand
7428
+ };
7429
+ case "completed": return {
7430
+ glyph: COLOR.accent,
7431
+ text: COLOR.dim
7432
+ };
7433
+ case "cancelled": return {
7434
+ glyph: COLOR.error,
7435
+ text: COLOR.mute
7436
+ };
7437
+ default: return {
7438
+ glyph: COLOR.mute,
7439
+ text: COLOR.dim
7440
+ };
7441
+ }
7442
+ }
7443
+ function TodoRow({ item, rowId }) {
7444
+ const COLOR = useColors();
7445
+ const colors = statusColors(item.status, COLOR);
7446
+ return /* @__PURE__ */ jsx("box", {
7447
+ id: rowId,
7448
+ style: {
7449
+ flexShrink: 0,
7450
+ flexDirection: "row"
7451
+ },
7452
+ children: /* @__PURE__ */ jsxs("text", {
7453
+ wrapMode: "word",
7454
+ children: [
7455
+ /* @__PURE__ */ jsx("span", {
7456
+ fg: colors.glyph,
7457
+ children: TODO_STATUS_GLYPHS[item.status]
7458
+ }),
7459
+ /* @__PURE__ */ jsx("span", {
7460
+ fg: COLOR.mute,
7461
+ children: " "
7462
+ }),
7463
+ /* @__PURE__ */ jsx("span", {
7464
+ fg: colors.text,
7465
+ children: item.content
7466
+ })
7467
+ ]
7468
+ })
7469
+ });
7470
+ }
7471
+ function TodosModal({ session, agent }) {
7472
+ const COLOR = useColors();
7473
+ const { height: termHeight } = useTerminalDimensions();
7474
+ const [, setTick] = useState(0);
7475
+ useEffect(() => {
7476
+ if (!agent) return;
7477
+ return agent.hooks.hook("tool:after", (ctx) => {
7478
+ if (ctx.name === "todowrite") setTick((t) => t + 1);
7479
+ });
7480
+ }, [agent]);
7481
+ const state = useActiveTodos(session);
7482
+ const scrollRef = useRef(null);
7483
+ const inProgressId = state.inProgress?.id ?? null;
7484
+ useEffect(() => {
7485
+ if (!inProgressId) return;
7486
+ const sb = scrollRef.current;
7487
+ if (!sb) return;
7488
+ const handle = requestAnimationFrame(() => {
7489
+ sb.scrollChildIntoView(`todo-row-${inProgressId}`);
7490
+ });
7491
+ return () => cancelAnimationFrame(handle);
7492
+ }, [inProgressId]);
7493
+ const idealHeight = Math.floor((termHeight - 4) * .66);
7494
+ const maxHeight = Math.max(MIN_MODAL_HEIGHT, Math.min(MAX_MODAL_HEIGHT$1, idealHeight));
7495
+ const display = state.todos.length > 0 ? state.todos : state.archive;
7496
+ const total = display.length;
7497
+ return /* @__PURE__ */ jsx(Modal, {
7498
+ title: "todos",
7499
+ bottomTitle: total > 0 ? `${total} item${total === 1 ? "" : "s"} · esc close` : "esc close",
7500
+ rightTitle: display.length > 0 ? /* @__PURE__ */ jsx(CountsBadge, { items: display }) : null,
7501
+ maxWidth: 100,
7502
+ maxHeight,
7503
+ children: total === 0 ? /* @__PURE__ */ jsxs("text", { children: [
7504
+ /* @__PURE__ */ jsx("span", {
7505
+ fg: COLOR.mute,
7506
+ children: "(no todos yet — the agent hasn't called "
7507
+ }),
7508
+ /* @__PURE__ */ jsx("span", {
7509
+ fg: COLOR.warn,
7510
+ children: "todowrite"
7511
+ }),
7512
+ /* @__PURE__ */ jsx("span", {
7513
+ fg: COLOR.mute,
7514
+ children: ")"
7515
+ })
7516
+ ] }) : /* @__PURE__ */ jsx("box", {
7517
+ style: {
7518
+ flexDirection: "column",
7519
+ flexGrow: 1,
7520
+ flexShrink: 1,
7521
+ overflow: "hidden"
7522
+ },
7523
+ children: /* @__PURE__ */ jsx("scrollbox", {
7524
+ ref: scrollRef,
7525
+ focusable: false,
7526
+ stickyScroll: false,
7527
+ style: {
7528
+ flexGrow: 1,
7529
+ flexShrink: 1
7530
+ },
7531
+ children: display.map((item) => /* @__PURE__ */ jsx(TodoRow, {
7532
+ item,
7533
+ rowId: `todo-row-${item.id}`
7534
+ }, item.id))
7535
+ })
7536
+ })
7537
+ });
7538
+ }
7539
+ /**
7540
+ * Right-aligned top-border badge — "{in-progress} in progress ·
7541
+ * {completed} completed". Empty buckets are dropped so a homogeneous
7542
+ * list reads cleanly. Status order is fixed (in-progress first, then
7543
+ * completed) regardless of which buckets are populated — the badge's
7544
+ * shape stays predictable so the eye lands on the live count instantly.
7545
+ */
7546
+ function CountsBadge({ items }) {
7547
+ const COLOR = useColors();
7548
+ const counts = {
7549
+ pending: 0,
7550
+ in_progress: 0,
7551
+ completed: 0,
7552
+ cancelled: 0
7553
+ };
7554
+ for (const item of items) counts[item.status] += 1;
7555
+ const parts = [];
7556
+ if (counts.in_progress) parts.push({
7557
+ count: counts.in_progress,
7558
+ label: "in progress",
7559
+ color: COLOR.warn
7560
+ });
7561
+ if (counts.completed) parts.push({
7562
+ count: counts.completed,
7563
+ label: "completed",
7564
+ color: COLOR.accent
7565
+ });
7566
+ if (counts.pending) parts.push({
7567
+ count: counts.pending,
7568
+ label: "pending",
7569
+ color: COLOR.dim
7570
+ });
7571
+ if (counts.cancelled) parts.push({
7572
+ count: counts.cancelled,
7573
+ label: "cancelled",
7574
+ color: COLOR.error
7575
+ });
7576
+ if (parts.length === 0) return null;
7577
+ return /* @__PURE__ */ jsxs("text", {
7578
+ wrapMode: "none",
7579
+ children: [
7580
+ /* @__PURE__ */ jsx("span", {
7581
+ fg: COLOR.mute,
7582
+ children: " "
7583
+ }),
7584
+ parts.map((p, i) => /* @__PURE__ */ jsxs("span", { children: [
7585
+ i > 0 && /* @__PURE__ */ jsx("span", {
7586
+ fg: COLOR.mute,
7587
+ children: " · "
7588
+ }),
7589
+ /* @__PURE__ */ jsx("span", {
7590
+ fg: p.color,
7591
+ children: String(p.count)
7592
+ }),
7593
+ /* @__PURE__ */ jsx("span", {
7594
+ fg: COLOR.mute,
7595
+ children: ` ${p.label}`
7596
+ })
7597
+ ] }, p.label)),
7598
+ /* @__PURE__ */ jsx("span", {
7599
+ fg: COLOR.mute,
7600
+ children: " "
7601
+ })
7602
+ ]
7603
+ });
7604
+ }
7605
+ //#endregion
7279
7606
  //#region src/tui/turn-details-modal.tsx
7280
7607
  /** Max chars surfaced in the scrollable preview pane. Long enough that almost everything fits without truncation. */
7281
7608
  const PREVIEW_CHAR_MAX = 8e3;
@@ -9375,6 +9702,13 @@ function AppShell() {
9375
9702
  }));
9376
9703
  return;
9377
9704
  }
9705
+ if (matchesBinding(key, keybindings.openTodos) && screen === "chat" && currentSession) {
9706
+ modal.open(/* @__PURE__ */ jsx(TodosModal, {
9707
+ session: sessionRef.current,
9708
+ agent: agentRef.current
9709
+ }));
9710
+ return;
9711
+ }
9378
9712
  if (matchesBinding(key, keybindings.enterSelectTurnMode) && screen === "chat" && !busy && !pendingApproval && !pendingInteraction) {
9379
9713
  enterSelectMode();
9380
9714
  return;
@@ -9508,6 +9842,7 @@ function AppShell() {
9508
9842
  settings,
9509
9843
  onSubmit: onSubmitPrompt,
9510
9844
  session: currentSession,
9845
+ liveSession: sessionRef.current,
9511
9846
  pending: pendingApproval,
9512
9847
  onApproval: resolveHead,
9513
9848
  pendingInteraction,