zidane 5.4.2 → 5.4.3
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/README.md +15 -0
- package/dist/{agent-DxBoKDba.d.ts → agent-Yu8uhpy-.d.ts} +74 -3
- package/dist/{agent-DxBoKDba.d.ts.map → agent-Yu8uhpy-.d.ts.map} +1 -1
- package/dist/chat.d.ts +49 -6
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/{errors-Byb0F8B9.js → errors-CDwtPIMX.js} +4 -2
- package/dist/{errors-Byb0F8B9.js.map → errors-CDwtPIMX.js.map} +1 -1
- package/dist/{index-BOtXdQkW.d.ts → index-DklfxeYy.d.ts} +8 -2
- package/dist/index-DklfxeYy.d.ts.map +1 -0
- package/dist/{index-B2VOOijU.d.ts → index-j9tY28ah.d.ts} +16 -3
- package/dist/index-j9tY28ah.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -10
- package/dist/{interpolate-ERgZUxgg.js → interpolate-CmtjEyRJ.js} +155 -18
- package/dist/interpolate-CmtjEyRJ.js.map +1 -0
- package/dist/{login-CJbeAadS.js → login-DxyAERe1.js} +3 -3
- package/dist/{login-CJbeAadS.js.map → login-DxyAERe1.js.map} +1 -1
- package/dist/{mcp-DhmmJfxK.js → mcp-CNUbvbsy.js} +2 -2
- package/dist/{mcp-DhmmJfxK.js.map → mcp-CNUbvbsy.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-D0xT979U.js → messages-fTR19Ga6.js} +2 -2
- package/dist/{messages-D0xT979U.js.map → messages-fTR19Ga6.js.map} +1 -1
- package/dist/{presets-MCcvxiNT.js → presets-D9IbaI40.js} +2 -2
- package/dist/{presets-MCcvxiNT.js.map → presets-D9IbaI40.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/{providers-x3LZByR5.js → providers-CEzRFYtS.js} +3 -3
- package/dist/{providers-x3LZByR5.js.map → providers-CEzRFYtS.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +2 -2
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session/sqlite.js +1 -1
- package/dist/{session-BHZwxmfr.js → session-kwsNnOmt.js} +2 -2
- package/dist/{session-BHZwxmfr.js.map → session-kwsNnOmt.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -1
- package/dist/{tools-BNfyY14s.js → tools-BK2vG9UX.js} +149 -32
- package/dist/tools-BK2vG9UX.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-DonKvoh4.d.ts → transcript-anchors-DnaBcJej.d.ts} +52 -8
- package/dist/transcript-anchors-DnaBcJej.d.ts.map +1 -0
- package/dist/tui.d.ts +4 -2
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +651 -42
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-TKvy0q29.js → turn-operations-OzKEOXul.js} +240 -52
- package/dist/turn-operations-OzKEOXul.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/docs/ARCHITECTURE.md +16 -3
- package/docs/CHAT.md +1 -1
- package/docs/SKILL.md +24 -14
- package/docs/TUI.md +24 -0
- package/package.json +3 -3
- package/dist/index-B2VOOijU.d.ts.map +0 -1
- package/dist/index-BOtXdQkW.d.ts.map +0 -1
- package/dist/interpolate-ERgZUxgg.js.map +0 -1
- package/dist/tools-BNfyY14s.js.map +0 -1
- package/dist/transcript-anchors-DonKvoh4.d.ts.map +0 -1
- package/dist/turn-operations-TKvy0q29.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { s as errorMessage } from "./errors-
|
|
3
|
-
import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-
|
|
4
|
-
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-
|
|
1
|
+
import { E as cleanupPersistedSession, O as resolvePersistDir, p as createAgent } from "./tools-BK2vG9UX.js";
|
|
2
|
+
import { s as errorMessage } from "./errors-CDwtPIMX.js";
|
|
3
|
+
import { s as McpOAuthProvider, t as connectMcpServers } from "./mcp-CNUbvbsy.js";
|
|
4
|
+
import { C as summaryToTurn, a as selectFilesFromSession, r as buildPostCompactAttachments, s as compactConversation, t as loginMcpServer } from "./login-DxyAERe1.js";
|
|
5
5
|
import { n as formatTokenUsage } from "./stats-DgOvY7wd.js";
|
|
6
|
-
import { n as loadSession, t as createSession } from "./session-
|
|
6
|
+
import { n as loadSession, t as createSession } from "./session-kwsNnOmt.js";
|
|
7
7
|
import { createTuiStore } from "./session/sqlite.js";
|
|
8
|
-
import { $ as useMcpAuthDispatch, $
|
|
8
|
+
import { $ as useMcpAuthDispatch, $t as isTurnHighlighted, A as getSafelist, An as rewriteMultiEditHeader, At as clampFps, B as supportsOAuth, Cn as summarizeEditPayload, Cr as modelSupportsReasoning, Ct as shortId, D as useSafeModeQueue, Dn as mergeApprovalAndBodyOutcomes, Dr as piIdOf, Dt as SETTINGS_CHOICES, E as useSafeModeActions, Et as DEFAULT_SETTINGS, F as suggestSafelistEntry, Ft as resolveTheme, Gn as createSkillsCompletionProvider, Gt as ConfigProvider, H as filterModelCatalog, Ht as DiscoveryProvider, Jn as createFilesCompletionProvider, Jt as EDIT_TOOL_NAMES, K as discoverProjectMcps, Kn as uniqueSkillNamesFromReferences, Kt as useConfig, L as splitPromptSegments, Ln as ensureKeybindingsFile, Mn as summarizeOutcomes, Nn as findGitRoot, Nr as accentColor, On as parseEditOutcomesFromResult, Ot as SETTINGS_TOGGLES, Pt as resolveChipColor, Q as McpAuthProvider, Qt as isEditErrorResult, R as formatPathForCwd, St as fmtTokens, T as SafeModeProvider, Tn as buildEditOutcomesAnnotation, Tt as useEnabledToggleSet, U as indexOfEntry, Ut as useDiscovery, V as buildModelCatalog, Vt as createDiscoverySlot, W as buildMcpServers, Wt as useDiscoveryOptional, Xt as deriveSessionTitle, Y as createFileMcpCredentialStore, Yr as useActiveTodos, Zt as eventsFromTurns, _ as turnContextSize, _t as truncateTrailing, a as computeTurnAnchors, ar as bootTick, at as InteractionsProvider, b as defaultSkillScanPaths, bn as filetypeFromPath, bt as ageString, c as formatToolCall, cn as sumRunCosts, ct as createInteractionTools, d as useSelectStyle, dn as toolResultText, dt as pendingInteractionsFromTurns, en as isVisible, er as useCompletion, et as useMcpAuthState, f as useSurfaces, fn as turnSelectionOwnership, g as finalizeStreamingMarkdownForOwner, gn as buildUnifiedDiff, gt as hintsLength, h as finalizeStreamingMarkdown, hn as buildContextualDiff, ht as clipHintsToWidth, i as turnAsText, in as marginTopFor, j as isOnSafelist, jn as stripEditOutcomesAnnotation, jt as useSettings, k as addToSafelist, kn as resolveApprovalForPayload, kt as SettingsProvider, l as ThemeProvider, m as useTheme, mt as useInteractionsQueue, n as deleteTurnSafely, nn as listSessionMeta, nr as buildLinearRamp, o as TOOL_DISPLAY, oi as buildBuildSystem, on as selectableTurnIds, or as shouldAutoCompact, pn as updateToolEventOutcomes, pr as setProviderCredential, pt as useInteractionsActions, qt as resolveConfig, r as truncateTurnsAt, rr as tryOpenBrowser, rt as splitMarkdownCodeBlocks, s as displayNameFor, si as buildPlanSystem, sn as stripSpawnTokensLine, sr as detectAuth, st as buildResumedToolResultsTurn, tn as lastContextSizeFromTurns, tr as blendHsl, tt as getMcpAuthStatus, u as useColors, un as toolCallPreview, ut as makeRequestInteraction, v as useStreamBuffer, w as writeSessionExport, wt as listProjectFiles, x as discoverProjectSkills, xn as previewEditPayload, xr as getContextWindow, xt as compactPath, y as buildSkillsConfig, yn as extractEditPayload, yt as generateSessionTitle, z as runOAuthLogin, zn as matchesBinding, zr as TODO_STATUS_GLYPHS } from "./turn-operations-OzKEOXul.js";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import { Buffer } from "node:buffer";
|
|
11
11
|
import * as fs from "node:fs";
|
|
@@ -223,6 +223,170 @@ function EmptyState$1() {
|
|
|
223
223
|
});
|
|
224
224
|
}
|
|
225
225
|
//#endregion
|
|
226
|
+
//#region src/tui/cancel-tool-modal.tsx
|
|
227
|
+
/** @jsxImportSource @opentui/react */
|
|
228
|
+
function CancelToolModal({ inFlight, onCancel, onCancelAll, onClose }) {
|
|
229
|
+
const COLOR = useColors();
|
|
230
|
+
const SURFACE = useSurfaces();
|
|
231
|
+
const { width: termWidth } = useTerminalDimensions();
|
|
232
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
233
|
+
const rows = inFlight;
|
|
234
|
+
const empty = rows.length === 0;
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (!empty) return;
|
|
237
|
+
const t = setTimeout(onClose, 500);
|
|
238
|
+
return () => clearTimeout(t);
|
|
239
|
+
}, [empty, onClose]);
|
|
240
|
+
const safeIndex = empty ? 0 : Math.min(selectedIdx, rows.length - 1);
|
|
241
|
+
useKeyboard((key) => {
|
|
242
|
+
if (empty) return;
|
|
243
|
+
if (key.name === "up") {
|
|
244
|
+
setSelectedIdx((i) => ((i - 1) % rows.length + rows.length) % rows.length);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (key.name === "down") {
|
|
248
|
+
setSelectedIdx((i) => (i + 1) % rows.length);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (key.name === "return") {
|
|
252
|
+
const row = rows[safeIndex];
|
|
253
|
+
if (row) onCancel(row.callId, "user-clicked-cancel");
|
|
254
|
+
onClose();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (key.name === "a") {
|
|
258
|
+
onCancelAll();
|
|
259
|
+
onClose();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const elapsedColWidth = 8;
|
|
263
|
+
const callIdColWidth = 14;
|
|
264
|
+
const childColWidth = 10;
|
|
265
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
266
|
+
title: "cancel tool call",
|
|
267
|
+
bottomTitle: empty ? "no calls in flight" : `${rows.length} in flight`,
|
|
268
|
+
maxWidth: Math.min(96, Math.max(64, termWidth - 8)),
|
|
269
|
+
minWidth: 56,
|
|
270
|
+
onClose,
|
|
271
|
+
children: [empty ? /* @__PURE__ */ jsxs("text", {
|
|
272
|
+
fg: COLOR.dim,
|
|
273
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
274
|
+
fg: COLOR.mute,
|
|
275
|
+
children: "no tool calls are currently in flight — "
|
|
276
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
277
|
+
fg: COLOR.dim,
|
|
278
|
+
children: "nothing to cancel."
|
|
279
|
+
})]
|
|
280
|
+
}) : /* @__PURE__ */ jsx("box", {
|
|
281
|
+
style: {
|
|
282
|
+
flexDirection: "column",
|
|
283
|
+
flexShrink: 0
|
|
284
|
+
},
|
|
285
|
+
children: rows.map((row, i) => /* @__PURE__ */ jsx(CancelToolRow, {
|
|
286
|
+
row,
|
|
287
|
+
isFocused: i === safeIndex,
|
|
288
|
+
highlightBg: SURFACE.selection,
|
|
289
|
+
elapsedColWidth,
|
|
290
|
+
callIdColWidth,
|
|
291
|
+
childColWidth
|
|
292
|
+
}, row.callId))
|
|
293
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
294
|
+
fg: COLOR.dim,
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ jsx("span", {
|
|
297
|
+
fg: COLOR.warn,
|
|
298
|
+
children: "↑↓"
|
|
299
|
+
}),
|
|
300
|
+
" navigate · ",
|
|
301
|
+
/* @__PURE__ */ jsx("span", {
|
|
302
|
+
fg: COLOR.warn,
|
|
303
|
+
children: "↵"
|
|
304
|
+
}),
|
|
305
|
+
" cancel selected · ",
|
|
306
|
+
/* @__PURE__ */ jsx("span", {
|
|
307
|
+
fg: COLOR.warn,
|
|
308
|
+
children: "a"
|
|
309
|
+
}),
|
|
310
|
+
" cancel all · ",
|
|
311
|
+
/* @__PURE__ */ jsx("span", {
|
|
312
|
+
fg: COLOR.warn,
|
|
313
|
+
children: "esc"
|
|
314
|
+
}),
|
|
315
|
+
" close"
|
|
316
|
+
]
|
|
317
|
+
})]
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function CancelToolRow({ row, isFocused, highlightBg, elapsedColWidth, callIdColWidth, childColWidth }) {
|
|
321
|
+
const COLOR = useColors();
|
|
322
|
+
const elapsed = formatElapsed(Date.now() - row.startedAt).padStart(elapsedColWidth, " ");
|
|
323
|
+
const idLabel = truncate(row.callId, callIdColWidth).padEnd(callIdColWidth, " ");
|
|
324
|
+
const childLabel = (row.childId ? `· ${row.childId}` : "").padEnd(childColWidth, " ");
|
|
325
|
+
return /* @__PURE__ */ jsx("box", {
|
|
326
|
+
style: {
|
|
327
|
+
height: 1,
|
|
328
|
+
paddingLeft: 1,
|
|
329
|
+
paddingRight: 1,
|
|
330
|
+
flexShrink: 0,
|
|
331
|
+
backgroundColor: isFocused ? highlightBg : void 0
|
|
332
|
+
},
|
|
333
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
334
|
+
wrapMode: "none",
|
|
335
|
+
children: [
|
|
336
|
+
/* @__PURE__ */ jsx("span", {
|
|
337
|
+
fg: isFocused ? COLOR.brand : COLOR.mute,
|
|
338
|
+
children: isFocused ? "›" : " "
|
|
339
|
+
}),
|
|
340
|
+
/* @__PURE__ */ jsx("span", {
|
|
341
|
+
fg: COLOR.mute,
|
|
342
|
+
children: " "
|
|
343
|
+
}),
|
|
344
|
+
/* @__PURE__ */ jsx("span", {
|
|
345
|
+
fg: isFocused ? COLOR.brand : COLOR.dim,
|
|
346
|
+
children: row.tool
|
|
347
|
+
}),
|
|
348
|
+
/* @__PURE__ */ jsx("span", {
|
|
349
|
+
fg: COLOR.mute,
|
|
350
|
+
children: " "
|
|
351
|
+
}),
|
|
352
|
+
/* @__PURE__ */ jsx("span", {
|
|
353
|
+
fg: COLOR.mute,
|
|
354
|
+
children: idLabel
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ jsx("span", {
|
|
357
|
+
fg: COLOR.mute,
|
|
358
|
+
children: " "
|
|
359
|
+
}),
|
|
360
|
+
/* @__PURE__ */ jsx("span", {
|
|
361
|
+
fg: COLOR.mute,
|
|
362
|
+
children: childLabel
|
|
363
|
+
}),
|
|
364
|
+
/* @__PURE__ */ jsx("span", {
|
|
365
|
+
fg: COLOR.warn,
|
|
366
|
+
children: elapsed
|
|
367
|
+
})
|
|
368
|
+
]
|
|
369
|
+
})
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Format a sub-minute duration as `0.3s` / `7.4s`; minute-plus as `2m12s`.
|
|
374
|
+
* Tight + monospace-friendly so the column stays right-aligned.
|
|
375
|
+
*/
|
|
376
|
+
function formatElapsed(ms) {
|
|
377
|
+
if (ms < 0) return "0.0s";
|
|
378
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
379
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
380
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
381
|
+
const seconds = totalSeconds % 60;
|
|
382
|
+
return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
383
|
+
}
|
|
384
|
+
function truncate(s, max) {
|
|
385
|
+
if (s.length <= max) return s;
|
|
386
|
+
if (max <= 1) return s.slice(0, max);
|
|
387
|
+
return `${s.slice(0, max - 1)}…`;
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
226
390
|
//#region src/tui/clipboard.ts
|
|
227
391
|
/**
|
|
228
392
|
* Two-pronged clipboard write.
|
|
@@ -1721,11 +1885,15 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1721
1885
|
const COLOR = useColors();
|
|
1722
1886
|
const SURFACE = useSurfaces();
|
|
1723
1887
|
const mdStyle = useMdStyle();
|
|
1888
|
+
const { settings } = useSettings();
|
|
1724
1889
|
const filetype = useMemo(() => filetypeFromPath(payload.path), [payload.path]);
|
|
1725
1890
|
const replaceAllCount = payload.hunks.filter((h) => h.replaceAll).length;
|
|
1726
1891
|
const hunkBadge = payload.tool === "multi_edit" && payload.hunks.length > 1 ? `${payload.hunks.length} hunks` : null;
|
|
1727
|
-
const
|
|
1728
|
-
const
|
|
1892
|
+
const outcomeSummary = summarizeOutcomes(payload.outcomes);
|
|
1893
|
+
const hasMixedOutcomes = !!payload.outcomes && outcomeSummary.denied + outcomeSummary.skipped + outcomeSummary.failed > 0;
|
|
1894
|
+
const editSummary = useMemo(() => summarizeEditPayload(payload), [payload]);
|
|
1895
|
+
const perHunkMode = hasMixedOutcomes;
|
|
1896
|
+
const compact = settings.editDiffDisplay === "compact";
|
|
1729
1897
|
return /* @__PURE__ */ jsxs("box", {
|
|
1730
1898
|
style: {
|
|
1731
1899
|
flexDirection: "column",
|
|
@@ -1751,6 +1919,24 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1751
1919
|
fg: dim ? COLOR.dim : COLOR.warn,
|
|
1752
1920
|
children: payload.path
|
|
1753
1921
|
}),
|
|
1922
|
+
(editSummary.totalAdded > 0 || editSummary.totalRemoved > 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1923
|
+
/* @__PURE__ */ jsx("span", {
|
|
1924
|
+
fg: COLOR.mute,
|
|
1925
|
+
children: " · "
|
|
1926
|
+
}),
|
|
1927
|
+
editSummary.totalAdded > 0 && /* @__PURE__ */ jsx("span", {
|
|
1928
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
1929
|
+
children: `+${editSummary.totalAdded}`
|
|
1930
|
+
}),
|
|
1931
|
+
editSummary.totalAdded > 0 && editSummary.totalRemoved > 0 && /* @__PURE__ */ jsx("span", {
|
|
1932
|
+
fg: COLOR.mute,
|
|
1933
|
+
children: " "
|
|
1934
|
+
}),
|
|
1935
|
+
editSummary.totalRemoved > 0 && /* @__PURE__ */ jsx("span", {
|
|
1936
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
1937
|
+
children: `−${editSummary.totalRemoved}`
|
|
1938
|
+
})
|
|
1939
|
+
] }),
|
|
1754
1940
|
hunkBadge && /* @__PURE__ */ jsx("span", {
|
|
1755
1941
|
fg: COLOR.mute,
|
|
1756
1942
|
children: ` · ${hunkBadge}`
|
|
@@ -1765,33 +1951,36 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1765
1951
|
children: " · "
|
|
1766
1952
|
}),
|
|
1767
1953
|
/* @__PURE__ */ jsx("span", {
|
|
1768
|
-
fg:
|
|
1769
|
-
children: `${
|
|
1954
|
+
fg: outcomeSummary.applied > 0 ? COLOR.accent : COLOR.mute,
|
|
1955
|
+
children: `${outcomeSummary.applied} applied`
|
|
1770
1956
|
}),
|
|
1771
|
-
|
|
1957
|
+
outcomeSummary.denied > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1772
1958
|
fg: COLOR.mute,
|
|
1773
1959
|
children: " · "
|
|
1774
1960
|
}), /* @__PURE__ */ jsx("span", {
|
|
1775
1961
|
fg: COLOR.error,
|
|
1776
|
-
children: `${
|
|
1962
|
+
children: `${outcomeSummary.denied} denied`
|
|
1777
1963
|
})] }),
|
|
1778
|
-
|
|
1964
|
+
outcomeSummary.skipped > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1779
1965
|
fg: COLOR.mute,
|
|
1780
1966
|
children: " · "
|
|
1781
1967
|
}), /* @__PURE__ */ jsx("span", {
|
|
1782
1968
|
fg: COLOR.warn,
|
|
1783
|
-
children: `${
|
|
1969
|
+
children: `${outcomeSummary.skipped} skipped`
|
|
1784
1970
|
})] }),
|
|
1785
|
-
|
|
1971
|
+
outcomeSummary.failed > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1786
1972
|
fg: COLOR.mute,
|
|
1787
1973
|
children: " · "
|
|
1788
1974
|
}), /* @__PURE__ */ jsx("span", {
|
|
1789
1975
|
fg: COLOR.error,
|
|
1790
|
-
children: `${
|
|
1976
|
+
children: `${outcomeSummary.failed} failed`
|
|
1791
1977
|
})] })
|
|
1792
1978
|
] })
|
|
1793
1979
|
]
|
|
1794
|
-
}),
|
|
1980
|
+
}), compact ? /* @__PURE__ */ jsx(CompactDiffSummary, {
|
|
1981
|
+
summary: editSummary,
|
|
1982
|
+
dim
|
|
1983
|
+
}) : perHunkMode ? /* @__PURE__ */ jsx("box", {
|
|
1795
1984
|
style: {
|
|
1796
1985
|
flexDirection: "column",
|
|
1797
1986
|
flexShrink: 0
|
|
@@ -1809,10 +1998,10 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1809
1998
|
dim
|
|
1810
1999
|
}, i))
|
|
1811
2000
|
}) : /* @__PURE__ */ jsx("diff", {
|
|
1812
|
-
diff: buildUnifiedDiff(payload),
|
|
2001
|
+
diff: payload.priorContent !== void 0 ? buildContextualDiff(payload, payload.priorContent) : buildUnifiedDiff(payload),
|
|
1813
2002
|
view: "unified",
|
|
1814
2003
|
wrapMode: "word",
|
|
1815
|
-
showLineNumbers:
|
|
2004
|
+
showLineNumbers: true,
|
|
1816
2005
|
...filetype ? { filetype } : {},
|
|
1817
2006
|
syntaxStyle: mdStyle,
|
|
1818
2007
|
addedBg: SURFACE.diff.addBg,
|
|
@@ -1828,6 +2017,91 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1828
2017
|
});
|
|
1829
2018
|
}
|
|
1830
2019
|
/**
|
|
2020
|
+
* Compact-mode body — one line per hunk under the tool header. Format:
|
|
2021
|
+
*
|
|
2022
|
+
* ` L42 · +2 −1 · old → new`
|
|
2023
|
+
*
|
|
2024
|
+
* Where `L<n>` is the new-file line position (omitted when priorContent
|
|
2025
|
+
* is absent and the position is unknown), and the `old → new` preview
|
|
2026
|
+
* is the FIRST changed line on each side, ASCII-arrowed and truncated
|
|
2027
|
+
* by the renderer's own word-wrap. A pure addition shows `+ new` only;
|
|
2028
|
+
* a pure deletion shows `− old` only.
|
|
2029
|
+
*/
|
|
2030
|
+
function CompactDiffSummary({ summary, dim }) {
|
|
2031
|
+
const COLOR = useColors();
|
|
2032
|
+
const SURFACE = useSurfaces();
|
|
2033
|
+
if (summary.hunks.length === 0) return null;
|
|
2034
|
+
return /* @__PURE__ */ jsx("box", {
|
|
2035
|
+
style: {
|
|
2036
|
+
flexDirection: "column",
|
|
2037
|
+
flexShrink: 0
|
|
2038
|
+
},
|
|
2039
|
+
children: summary.hunks.map((h, i) => {
|
|
2040
|
+
const oldPreview = h.firstOld?.trim();
|
|
2041
|
+
const newPreview = h.firstNew?.trim();
|
|
2042
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
2043
|
+
fg: dim ? COLOR.dim : COLOR.mute,
|
|
2044
|
+
wrapMode: "word",
|
|
2045
|
+
children: [
|
|
2046
|
+
/* @__PURE__ */ jsx("span", {
|
|
2047
|
+
fg: COLOR.mute,
|
|
2048
|
+
children: " "
|
|
2049
|
+
}),
|
|
2050
|
+
h.line !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2051
|
+
fg: dim ? COLOR.dim : COLOR.mute,
|
|
2052
|
+
children: `L${h.line}`
|
|
2053
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2054
|
+
fg: COLOR.mute,
|
|
2055
|
+
children: " · "
|
|
2056
|
+
})] }),
|
|
2057
|
+
h.added > 0 && /* @__PURE__ */ jsx("span", {
|
|
2058
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2059
|
+
children: `+${h.added}`
|
|
2060
|
+
}),
|
|
2061
|
+
h.added > 0 && h.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2062
|
+
fg: COLOR.mute,
|
|
2063
|
+
children: " "
|
|
2064
|
+
}),
|
|
2065
|
+
h.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2066
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2067
|
+
children: `−${h.removed}`
|
|
2068
|
+
}),
|
|
2069
|
+
(oldPreview || newPreview) && /* @__PURE__ */ jsx("span", {
|
|
2070
|
+
fg: COLOR.mute,
|
|
2071
|
+
children: " · "
|
|
2072
|
+
}),
|
|
2073
|
+
oldPreview && newPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2074
|
+
/* @__PURE__ */ jsx("span", {
|
|
2075
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2076
|
+
children: oldPreview
|
|
2077
|
+
}),
|
|
2078
|
+
/* @__PURE__ */ jsx("span", {
|
|
2079
|
+
fg: COLOR.mute,
|
|
2080
|
+
children: " → "
|
|
2081
|
+
}),
|
|
2082
|
+
/* @__PURE__ */ jsx("span", {
|
|
2083
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2084
|
+
children: newPreview
|
|
2085
|
+
})
|
|
2086
|
+
] }) : oldPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2087
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2088
|
+
children: "− "
|
|
2089
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2090
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2091
|
+
children: oldPreview
|
|
2092
|
+
})] }) : newPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2093
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2094
|
+
children: "+ "
|
|
2095
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2096
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2097
|
+
children: newPreview
|
|
2098
|
+
})] }) : null
|
|
2099
|
+
]
|
|
2100
|
+
}, i);
|
|
2101
|
+
})
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
1831
2105
|
* One hunk inside an `EditDiffBlock` rendered as its own mini-diff with
|
|
1832
2106
|
* a status badge above it. Used only in the per-hunk view (multi_edit
|
|
1833
2107
|
* with mixed outcomes) so denied / skipped / failed edits remain
|
|
@@ -1835,9 +2109,11 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1835
2109
|
*/
|
|
1836
2110
|
const BADGE_WIDTH = 7;
|
|
1837
2111
|
function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR, SURFACE, dim }) {
|
|
2112
|
+
const { settings } = useSettings();
|
|
1838
2113
|
const kind = outcome?.kind ?? "applied";
|
|
1839
2114
|
const reason = outcome?.reason;
|
|
1840
2115
|
const dimmed = dim || kind !== "applied";
|
|
2116
|
+
const compact = settings.editDiffDisplay === "compact";
|
|
1841
2117
|
const diffText = useMemo(() => buildUnifiedDiff({
|
|
1842
2118
|
tool,
|
|
1843
2119
|
path,
|
|
@@ -1847,6 +2123,15 @@ function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR,
|
|
|
1847
2123
|
tool,
|
|
1848
2124
|
path
|
|
1849
2125
|
]);
|
|
2126
|
+
const hunkStats = useMemo(() => summarizeEditPayload({
|
|
2127
|
+
tool,
|
|
2128
|
+
path,
|
|
2129
|
+
hunks: [hunk]
|
|
2130
|
+
}).hunks[0], [
|
|
2131
|
+
hunk,
|
|
2132
|
+
tool,
|
|
2133
|
+
path
|
|
2134
|
+
]);
|
|
1850
2135
|
const badge = kind === "applied" ? {
|
|
1851
2136
|
label: "applied",
|
|
1852
2137
|
fg: COLOR.accent
|
|
@@ -1881,6 +2166,24 @@ function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR,
|
|
|
1881
2166
|
fg: badge.fg,
|
|
1882
2167
|
children: badge.label.padEnd(BADGE_WIDTH)
|
|
1883
2168
|
}),
|
|
2169
|
+
hunkStats && (hunkStats.added > 0 || hunkStats.removed > 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2170
|
+
/* @__PURE__ */ jsx("span", {
|
|
2171
|
+
fg: COLOR.mute,
|
|
2172
|
+
children: " · "
|
|
2173
|
+
}),
|
|
2174
|
+
hunkStats.added > 0 && /* @__PURE__ */ jsx("span", {
|
|
2175
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2176
|
+
children: `+${hunkStats.added}`
|
|
2177
|
+
}),
|
|
2178
|
+
hunkStats.added > 0 && hunkStats.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2179
|
+
fg: COLOR.mute,
|
|
2180
|
+
children: " "
|
|
2181
|
+
}),
|
|
2182
|
+
hunkStats.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2183
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2184
|
+
children: `−${hunkStats.removed}`
|
|
2185
|
+
})
|
|
2186
|
+
] }),
|
|
1884
2187
|
reason && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1885
2188
|
fg: COLOR.mute,
|
|
1886
2189
|
children: ": "
|
|
@@ -1889,11 +2192,11 @@ function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR,
|
|
|
1889
2192
|
children: reason
|
|
1890
2193
|
})] })
|
|
1891
2194
|
]
|
|
1892
|
-
}), /* @__PURE__ */ jsx("diff", {
|
|
2195
|
+
}), !compact && /* @__PURE__ */ jsx("diff", {
|
|
1893
2196
|
diff: diffText,
|
|
1894
2197
|
view: "unified",
|
|
1895
2198
|
wrapMode: "word",
|
|
1896
|
-
showLineNumbers:
|
|
2199
|
+
showLineNumbers: true,
|
|
1897
2200
|
...filetype ? { filetype } : {},
|
|
1898
2201
|
syntaxStyle: mdStyle,
|
|
1899
2202
|
addedBg: SURFACE.diff.addBg,
|
|
@@ -1913,7 +2216,7 @@ function ToolCallBlock({ event, display, dim }) {
|
|
|
1913
2216
|
const COLOR = useColors();
|
|
1914
2217
|
const mdStyle = useMdStyle();
|
|
1915
2218
|
const name = event.tool ?? "";
|
|
1916
|
-
const verb = displayNameFor(name);
|
|
2219
|
+
const verb = displayNameFor(name, event.input);
|
|
1917
2220
|
const pretty = useMemo(() => {
|
|
1918
2221
|
if (!event.input) return null;
|
|
1919
2222
|
try {
|
|
@@ -7612,6 +7915,31 @@ const PREVIEW_CHAR_MAX = 8e3;
|
|
|
7612
7915
|
* keeps a comfortable shape rather than stretching to the full height.
|
|
7613
7916
|
*/
|
|
7614
7917
|
const MAX_MODAL_HEIGHT = 28;
|
|
7918
|
+
const EDIT_TEXTAREA_BINDINGS = [
|
|
7919
|
+
...defaultTextareaKeyBindings.filter((b) => b.name !== "return" && !(b.name === "a" && b.ctrl && !b.shift && !b.meta)),
|
|
7920
|
+
{
|
|
7921
|
+
name: "a",
|
|
7922
|
+
ctrl: true,
|
|
7923
|
+
action: "select-all"
|
|
7924
|
+
},
|
|
7925
|
+
{
|
|
7926
|
+
name: "return",
|
|
7927
|
+
action: "submit"
|
|
7928
|
+
},
|
|
7929
|
+
{
|
|
7930
|
+
name: "return",
|
|
7931
|
+
shift: true,
|
|
7932
|
+
action: "newline"
|
|
7933
|
+
}
|
|
7934
|
+
];
|
|
7935
|
+
/**
|
|
7936
|
+
* Extract the editable text from a turn — joins all `text` blocks.
|
|
7937
|
+
* Non-text blocks (tool_call, tool_result, thinking, etc.) are structural
|
|
7938
|
+
* and not included in the editable surface.
|
|
7939
|
+
*/
|
|
7940
|
+
function editableText(turn) {
|
|
7941
|
+
return turn.content.filter((b) => b.type === "text").map((b) => b.text).join("\n\n");
|
|
7942
|
+
}
|
|
7615
7943
|
function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
7616
7944
|
const COLOR = useColors();
|
|
7617
7945
|
const modal = useModal();
|
|
@@ -7621,6 +7949,9 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7621
7949
|
const bottomTitle = `${index - 1} before · ${total - index} after`;
|
|
7622
7950
|
const [pending, setPending] = useState(null);
|
|
7623
7951
|
const [copyStatus, setCopyStatus] = useState("idle");
|
|
7952
|
+
const [editing, setEditing] = useState(false);
|
|
7953
|
+
const textareaRef = useRef(null);
|
|
7954
|
+
const hasEditableText = turn.content.some((b) => b.type === "text");
|
|
7624
7955
|
const commitFork = () => {
|
|
7625
7956
|
modal.close();
|
|
7626
7957
|
actions.onFork(turn.id);
|
|
@@ -7636,7 +7967,16 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7636
7967
|
}
|
|
7637
7968
|
setCopyStatus(writeToClipboard(fullText) ? "copied" : "failed");
|
|
7638
7969
|
};
|
|
7970
|
+
const commitEdit = () => {
|
|
7971
|
+
const newText = textareaRef.current?.plainText ?? "";
|
|
7972
|
+
modal.close();
|
|
7973
|
+
actions.onEdit(turn.id, newText);
|
|
7974
|
+
};
|
|
7639
7975
|
useKeyboard((key) => {
|
|
7976
|
+
if (editing) {
|
|
7977
|
+
if (key.name === "escape") setEditing(false);
|
|
7978
|
+
return;
|
|
7979
|
+
}
|
|
7640
7980
|
if (key.name === "escape" && pending) {
|
|
7641
7981
|
setPending(null);
|
|
7642
7982
|
return;
|
|
@@ -7658,15 +7998,22 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7658
7998
|
handleCopy();
|
|
7659
7999
|
return;
|
|
7660
8000
|
}
|
|
8001
|
+
if (matchesBinding(key, keybindings.turnEdit)) {
|
|
8002
|
+
if (!hasEditableText) return;
|
|
8003
|
+
setPending(null);
|
|
8004
|
+
setCopyStatus("idle");
|
|
8005
|
+
setEditing(true);
|
|
8006
|
+
return;
|
|
8007
|
+
}
|
|
7661
8008
|
if (pending) setPending(null);
|
|
7662
8009
|
});
|
|
7663
8010
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
7664
|
-
title: `turn ${index} / ${total} · ${turn.role}`,
|
|
7665
|
-
bottomTitle,
|
|
8011
|
+
title: editing ? `edit turn ${index} / ${total} · ${turn.role}` : `turn ${index} / ${total} · ${turn.role}`,
|
|
8012
|
+
bottomTitle: editing ? void 0 : bottomTitle,
|
|
7666
8013
|
maxHeight: MAX_MODAL_HEIGHT,
|
|
7667
|
-
disableEscape: pending !== null,
|
|
8014
|
+
disableEscape: editing || pending !== null,
|
|
7668
8015
|
children: [
|
|
7669
|
-
/* @__PURE__ */ jsxs("text", {
|
|
8016
|
+
!editing && /* @__PURE__ */ jsxs("text", {
|
|
7670
8017
|
fg: COLOR.dim,
|
|
7671
8018
|
children: [
|
|
7672
8019
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -7705,7 +8052,7 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7705
8052
|
] })
|
|
7706
8053
|
]
|
|
7707
8054
|
}),
|
|
7708
|
-
/* @__PURE__ */ jsxs("text", {
|
|
8055
|
+
!editing && /* @__PURE__ */ jsxs("text", {
|
|
7709
8056
|
fg: COLOR.dim,
|
|
7710
8057
|
children: [/* @__PURE__ */ jsx("span", {
|
|
7711
8058
|
fg: COLOR.mute,
|
|
@@ -7715,7 +8062,31 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7715
8062
|
children: summary
|
|
7716
8063
|
})]
|
|
7717
8064
|
}),
|
|
7718
|
-
/* @__PURE__ */ jsx("box", {
|
|
8065
|
+
editing ? /* @__PURE__ */ jsx("box", {
|
|
8066
|
+
title: " edit ",
|
|
8067
|
+
style: {
|
|
8068
|
+
border: true,
|
|
8069
|
+
borderColor: COLOR.borderActive,
|
|
8070
|
+
paddingLeft: 1,
|
|
8071
|
+
paddingRight: 1,
|
|
8072
|
+
flexDirection: "column",
|
|
8073
|
+
flexGrow: 1,
|
|
8074
|
+
flexShrink: 1,
|
|
8075
|
+
minHeight: 5
|
|
8076
|
+
},
|
|
8077
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
8078
|
+
ref: textareaRef,
|
|
8079
|
+
focused: true,
|
|
8080
|
+
keyBindings: EDIT_TEXTAREA_BINDINGS,
|
|
8081
|
+
initialValue: editableText(turn),
|
|
8082
|
+
placeholder: "enter text…",
|
|
8083
|
+
style: {
|
|
8084
|
+
flexGrow: 1,
|
|
8085
|
+
height: "100%"
|
|
8086
|
+
},
|
|
8087
|
+
onSubmit: commitEdit
|
|
8088
|
+
})
|
|
8089
|
+
}) : /* @__PURE__ */ jsx("box", {
|
|
7719
8090
|
title: " preview ",
|
|
7720
8091
|
style: {
|
|
7721
8092
|
border: true,
|
|
@@ -7740,13 +8111,34 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7740
8111
|
children: "— no text content —"
|
|
7741
8112
|
})
|
|
7742
8113
|
}),
|
|
7743
|
-
/* @__PURE__ */
|
|
8114
|
+
editing ? /* @__PURE__ */ jsxs("text", {
|
|
8115
|
+
fg: COLOR.dim,
|
|
8116
|
+
children: [
|
|
8117
|
+
/* @__PURE__ */ jsx("span", {
|
|
8118
|
+
fg: COLOR.warn,
|
|
8119
|
+
children: "↵"
|
|
8120
|
+
}),
|
|
8121
|
+
" save · ",
|
|
8122
|
+
/* @__PURE__ */ jsx("span", {
|
|
8123
|
+
fg: COLOR.warn,
|
|
8124
|
+
children: "shift+↵"
|
|
8125
|
+
}),
|
|
8126
|
+
" newline · ",
|
|
8127
|
+
/* @__PURE__ */ jsx("span", {
|
|
8128
|
+
fg: COLOR.warn,
|
|
8129
|
+
children: "esc"
|
|
8130
|
+
}),
|
|
8131
|
+
" cancel"
|
|
8132
|
+
]
|
|
8133
|
+
}) : /* @__PURE__ */ jsx(ActionRow, {
|
|
7744
8134
|
pending,
|
|
7745
8135
|
copyStatus,
|
|
7746
8136
|
canCopy: fullText.length > 0,
|
|
8137
|
+
canEdit: hasEditableText,
|
|
7747
8138
|
forkKey: keybindings.turnFork,
|
|
7748
8139
|
deleteKey: keybindings.turnDelete,
|
|
7749
|
-
copyKey: keybindings.turnCopy
|
|
8140
|
+
copyKey: keybindings.turnCopy,
|
|
8141
|
+
editKey: keybindings.turnEdit
|
|
7750
8142
|
})
|
|
7751
8143
|
]
|
|
7752
8144
|
});
|
|
@@ -7758,7 +8150,7 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7758
8150
|
* by accident. The copy result rides the same row when present — same
|
|
7759
8151
|
* geometry, no layout shift.
|
|
7760
8152
|
*/
|
|
7761
|
-
function ActionRow({ pending, copyStatus, canCopy, forkKey, deleteKey, copyKey }) {
|
|
8153
|
+
function ActionRow({ pending, copyStatus, canCopy, canEdit, forkKey, deleteKey, copyKey, editKey }) {
|
|
7762
8154
|
const COLOR = useColors();
|
|
7763
8155
|
if (pending === "fork") return /* @__PURE__ */ jsxs("text", {
|
|
7764
8156
|
fg: COLOR.dim,
|
|
@@ -7818,6 +8210,11 @@ function ActionRow({ pending, copyStatus, canCopy, forkKey, deleteKey, copyKey }
|
|
|
7818
8210
|
children: deleteKey
|
|
7819
8211
|
}),
|
|
7820
8212
|
" delete · ",
|
|
8213
|
+
/* @__PURE__ */ jsx("span", {
|
|
8214
|
+
fg: canEdit ? COLOR.warn : COLOR.mute,
|
|
8215
|
+
children: editKey
|
|
8216
|
+
}),
|
|
8217
|
+
canEdit ? " edit · " : " (no text) · ",
|
|
7821
8218
|
/* @__PURE__ */ jsx("span", {
|
|
7822
8219
|
fg: COLOR.warn,
|
|
7823
8220
|
children: "esc"
|
|
@@ -7858,6 +8255,11 @@ function ActionRow({ pending, copyStatus, canCopy, forkKey, deleteKey, copyKey }
|
|
|
7858
8255
|
children: copyKey
|
|
7859
8256
|
}),
|
|
7860
8257
|
canCopy ? " copy · " : " (nothing to copy) · ",
|
|
8258
|
+
/* @__PURE__ */ jsx("span", {
|
|
8259
|
+
fg: canEdit ? COLOR.warn : COLOR.mute,
|
|
8260
|
+
children: editKey
|
|
8261
|
+
}),
|
|
8262
|
+
canEdit ? " edit · " : " (no text) · ",
|
|
7861
8263
|
/* @__PURE__ */ jsx("span", {
|
|
7862
8264
|
fg: COLOR.warn,
|
|
7863
8265
|
children: "esc"
|
|
@@ -7919,6 +8321,42 @@ async function launchEditor(path) {
|
|
|
7919
8321
|
}).unref();
|
|
7920
8322
|
}
|
|
7921
8323
|
/**
|
|
8324
|
+
* Session-metadata key under which the TUI persists the user's "pinned"
|
|
8325
|
+
* active skills — the set the user toggled on via `/skill-name` and
|
|
8326
|
+
* expects to stay active across run boundaries (and TUI restarts).
|
|
8327
|
+
*
|
|
8328
|
+
* Separate from the agent's `skillActivationState` for two reasons:
|
|
8329
|
+
* 1. The framework's run-end pass deactivates everything; the pinned
|
|
8330
|
+
* set survives that pass so the next prompt can re-activate.
|
|
8331
|
+
* 2. The agent's session-resume rehydrator only sees `skills_use`
|
|
8332
|
+
* `tool_call` blocks in history. Slash-command activations bypass
|
|
8333
|
+
* that path; this metadata key is the system-of-record for them.
|
|
8334
|
+
*/
|
|
8335
|
+
const ACTIVE_SKILLS_META_KEY = "zidane.activeSkills";
|
|
8336
|
+
/**
|
|
8337
|
+
* Read the pinned-skills set out of session metadata. Tolerant by
|
|
8338
|
+
* design — older sessions, manually-edited metadata, or a future
|
|
8339
|
+
* type drift all degrade to "no pins", never throw. Returns a fresh
|
|
8340
|
+
* Set so callers can mutate-and-store without aliasing the input.
|
|
8341
|
+
*/
|
|
8342
|
+
function readPinnedSkills(raw) {
|
|
8343
|
+
if (!Array.isArray(raw)) return /* @__PURE__ */ new Set();
|
|
8344
|
+
return new Set(raw.filter((v) => typeof v === "string" && v.length > 0));
|
|
8345
|
+
}
|
|
8346
|
+
/**
|
|
8347
|
+
* Mirror the pinned-skills set into session metadata. Sorted for
|
|
8348
|
+
* stable on-disk ordering (diffs / debugging). `session.setMeta`
|
|
8349
|
+
* already routes through `session:meta` hooks, so observers see a
|
|
8350
|
+
* single normalized payload regardless of insertion order.
|
|
8351
|
+
*
|
|
8352
|
+
* Imported lazily via the session ref — calling sites already
|
|
8353
|
+
* captured `session` in scope, so we accept it as an argument
|
|
8354
|
+
* rather than re-fetching from a ref.
|
|
8355
|
+
*/
|
|
8356
|
+
function persistPinnedSkills(session, pins) {
|
|
8357
|
+
session.setMeta(ACTIVE_SKILLS_META_KEY, Array.from(pins).sort());
|
|
8358
|
+
}
|
|
8359
|
+
/**
|
|
7922
8360
|
* Filter a `multi_edit` (or single `edit` / `write_file`) input's hunk
|
|
7923
8361
|
* list down to the approved subset, in original order. Used by the
|
|
7924
8362
|
* approval gate to rebind `ctx.input.edits` after a partial decision —
|
|
@@ -8239,6 +8677,60 @@ function AppShell() {
|
|
|
8239
8677
|
const agentRef = useRef(null);
|
|
8240
8678
|
const sessionRef = useRef(null);
|
|
8241
8679
|
/**
|
|
8680
|
+
* Live registry of in-flight tool calls — populated by `tool:before`
|
|
8681
|
+
* (and `child:tool:before`), drained by `tool:after` / `tool:error` /
|
|
8682
|
+
* `tool:cancelled` (and their `child:*` siblings). Drives the
|
|
8683
|
+
* "cancel tool call" picker (see {@link CancelToolModal}).
|
|
8684
|
+
*
|
|
8685
|
+
* Mirrored in `inFlightToolsRef` for callbacks that read the latest
|
|
8686
|
+
* value synchronously without listing `inFlightTools` in their deps —
|
|
8687
|
+
* the picker open path needs both: a state snapshot for the modal's
|
|
8688
|
+
* rendered rows, and the live ref for any subsequent cancel actions
|
|
8689
|
+
* that fire after the snapshot was taken.
|
|
8690
|
+
*
|
|
8691
|
+
* Excludes `mcp:tool:before` / `mcp:tool:after` deliberately for
|
|
8692
|
+
* v1 — `agent.cancelTool(callId)` operates on the unified loop-side
|
|
8693
|
+
* callId registry, which MCP tools also live in (they're dispatched
|
|
8694
|
+
* through the same `executeSingleTool`), so the cancel works for
|
|
8695
|
+
* them too; we just don't surface them in the picker UI yet to
|
|
8696
|
+
* avoid drowning the list with high-cardinality MCP transient calls.
|
|
8697
|
+
*/
|
|
8698
|
+
const [inFlightTools, setInFlightTools] = useState([]);
|
|
8699
|
+
const inFlightToolsRef = useRef([]);
|
|
8700
|
+
inFlightToolsRef.current = inFlightTools;
|
|
8701
|
+
/**
|
|
8702
|
+
* Names of currently-active skills, tracked via `skills:activate` /
|
|
8703
|
+
* `skills:deactivate` hooks. Drives the footer's "✦ N skill(s)"
|
|
8704
|
+
* chip — the user's only passive surface for noticing that a skill
|
|
8705
|
+
* (and its `allowed-tools` restrictions) is in effect. Cleared on
|
|
8706
|
+
* session teardown alongside the rest of the per-session live state.
|
|
8707
|
+
*
|
|
8708
|
+
* Stored as a Set rather than an array so dedup is structural (a
|
|
8709
|
+
* runaway `skills:activate` for the same name doesn't inflate the
|
|
8710
|
+
* count). React state is the snapshot we render against; a fresh
|
|
8711
|
+
* Set per update gives React identity-based change detection.
|
|
8712
|
+
*/
|
|
8713
|
+
const [activeSkillNames, setActiveSkillNames] = useState(() => /* @__PURE__ */ new Set());
|
|
8714
|
+
/**
|
|
8715
|
+
* Mirror of {@link activeSkillNames} for synchronous reads in
|
|
8716
|
+
* `onSubmitPrompt`. The submit path runs outside React's render cycle
|
|
8717
|
+
* and needs to pre-activate every user-pinned skill before
|
|
8718
|
+
* `agent.run()` — listing the state in `useCallback`'s deps would
|
|
8719
|
+
* re-bind the handler on every activation change, which would
|
|
8720
|
+
* invalidate the textarea's submit binding on every `/skill` trigger.
|
|
8721
|
+
* The ref keeps the binding stable and the read fresh.
|
|
8722
|
+
*/
|
|
8723
|
+
const activeSkillNamesRef = useRef(activeSkillNames);
|
|
8724
|
+
activeSkillNamesRef.current = activeSkillNames;
|
|
8725
|
+
const registerInFlightTool = useCallback((entry) => {
|
|
8726
|
+
setInFlightTools((prev) => {
|
|
8727
|
+
return [...prev.filter((e) => e.callId !== entry.callId), entry];
|
|
8728
|
+
});
|
|
8729
|
+
}, []);
|
|
8730
|
+
const unregisterInFlightTool = useCallback((callId) => {
|
|
8731
|
+
setInFlightTools((prev) => prev.filter((e) => e.callId !== callId));
|
|
8732
|
+
}, []);
|
|
8733
|
+
/**
|
|
8242
8734
|
* In-flight auto-compaction promise. Held in a ref so the next
|
|
8243
8735
|
* `onSubmitPrompt` invocation can `await` it before calling
|
|
8244
8736
|
* `agent.run()` — this is what prevents a user-submitted prompt from
|
|
@@ -8491,9 +8983,14 @@ function AppShell() {
|
|
|
8491
8983
|
agent.hooks.hook("stream:thinking", ({ delta, turnId }) => stream.queueStreamDelta("thinking", delta, { turnId }));
|
|
8492
8984
|
agent.hooks.hook("stream:text", ({ delta, turnId }) => stream.queueStreamDelta("markdown", delta, { turnId }));
|
|
8493
8985
|
agent.hooks.hook("tool:before", async ({ callId, name, input, turnId }) => {
|
|
8986
|
+
registerInFlightTool({
|
|
8987
|
+
callId,
|
|
8988
|
+
tool: name,
|
|
8989
|
+
startedAt: Date.now()
|
|
8990
|
+
});
|
|
8494
8991
|
if (pendingAnnotationsRef.current.has(callId)) return;
|
|
8495
8992
|
let priorContent;
|
|
8496
|
-
if (name
|
|
8993
|
+
if (EDIT_TOOL_NAMES.has(name) && agent.handle && typeof input.path === "string") try {
|
|
8497
8994
|
priorContent = await agent.execution.readFile(agent.handle, input.path);
|
|
8498
8995
|
} catch {}
|
|
8499
8996
|
const edit = extractEditPayload(name, input, priorContent);
|
|
@@ -8508,6 +9005,7 @@ function AppShell() {
|
|
|
8508
9005
|
});
|
|
8509
9006
|
});
|
|
8510
9007
|
agent.hooks.hook("tool:after", ({ callId, name, result, turnId }) => {
|
|
9008
|
+
unregisterInFlightTool(callId);
|
|
8511
9009
|
const raw = toolResultText(result);
|
|
8512
9010
|
const text = name === "spawn" ? stripSpawnTokensLine(raw) : raw;
|
|
8513
9011
|
stream.appendImmediate({
|
|
@@ -8518,6 +9016,31 @@ function AppShell() {
|
|
|
8518
9016
|
turnId
|
|
8519
9017
|
});
|
|
8520
9018
|
});
|
|
9019
|
+
agent.hooks.hook("tool:error", ({ callId }) => {
|
|
9020
|
+
unregisterInFlightTool(callId);
|
|
9021
|
+
});
|
|
9022
|
+
agent.hooks.hook("tool:cancelled", ({ callId }) => {
|
|
9023
|
+
unregisterInFlightTool(callId);
|
|
9024
|
+
});
|
|
9025
|
+
agent.hooks.hook("skills:activate", ({ skill }) => {
|
|
9026
|
+
setActiveSkillNames((prev) => {
|
|
9027
|
+
if (prev.has(skill.name)) return prev;
|
|
9028
|
+
const next = new Set(prev);
|
|
9029
|
+
next.add(skill.name);
|
|
9030
|
+
persistPinnedSkills(session, next);
|
|
9031
|
+
return next;
|
|
9032
|
+
});
|
|
9033
|
+
});
|
|
9034
|
+
agent.hooks.hook("skills:deactivate", ({ skill, reason }) => {
|
|
9035
|
+
if (reason === "run-end") return;
|
|
9036
|
+
setActiveSkillNames((prev) => {
|
|
9037
|
+
if (!prev.has(skill.name)) return prev;
|
|
9038
|
+
const next = new Set(prev);
|
|
9039
|
+
next.delete(skill.name);
|
|
9040
|
+
persistPinnedSkills(session, next);
|
|
9041
|
+
return next;
|
|
9042
|
+
});
|
|
9043
|
+
});
|
|
8521
9044
|
agent.hooks.hook("mcp:tool:after", ({ callId, displayName, result, turnId }) => {
|
|
8522
9045
|
stream.appendImmediate({
|
|
8523
9046
|
kind: "tool-result",
|
|
@@ -8580,6 +9103,12 @@ function AppShell() {
|
|
|
8580
9103
|
});
|
|
8581
9104
|
});
|
|
8582
9105
|
agent.hooks.hook("child:tool:before", ({ callId, name, input, childId, depth, turnId, priorContent }) => {
|
|
9106
|
+
registerInFlightTool({
|
|
9107
|
+
callId,
|
|
9108
|
+
tool: name,
|
|
9109
|
+
startedAt: Date.now(),
|
|
9110
|
+
childId
|
|
9111
|
+
});
|
|
8583
9112
|
if (pendingAnnotationsRef.current.has(callId)) return;
|
|
8584
9113
|
const edit = extractEditPayload(name, input, priorContent);
|
|
8585
9114
|
stream.appendImmediate({
|
|
@@ -8595,6 +9124,7 @@ function AppShell() {
|
|
|
8595
9124
|
});
|
|
8596
9125
|
});
|
|
8597
9126
|
agent.hooks.hook("child:tool:after", ({ callId, name, result, childId, depth, turnId }) => {
|
|
9127
|
+
unregisterInFlightTool(callId);
|
|
8598
9128
|
stream.appendImmediate({
|
|
8599
9129
|
kind: "tool-result",
|
|
8600
9130
|
text: toolResultText(result),
|
|
@@ -8605,6 +9135,12 @@ function AppShell() {
|
|
|
8605
9135
|
turnId
|
|
8606
9136
|
});
|
|
8607
9137
|
});
|
|
9138
|
+
agent.hooks.hook("child:tool:error", ({ callId }) => {
|
|
9139
|
+
unregisterInFlightTool(callId);
|
|
9140
|
+
});
|
|
9141
|
+
agent.hooks.hook("child:tool:cancelled", ({ callId }) => {
|
|
9142
|
+
unregisterInFlightTool(callId);
|
|
9143
|
+
});
|
|
8608
9144
|
agent.hooks.hook("child:stream:end", ({ childId }) => {
|
|
8609
9145
|
stream.flushAndUpdate((prev) => finalizeStreamingMarkdownForOwner(prev, childId));
|
|
8610
9146
|
});
|
|
@@ -8621,7 +9157,9 @@ function AppShell() {
|
|
|
8621
9157
|
config.prefix,
|
|
8622
9158
|
interactions,
|
|
8623
9159
|
dataDir,
|
|
8624
|
-
mcpCredentialStore
|
|
9160
|
+
mcpCredentialStore,
|
|
9161
|
+
registerInFlightTool,
|
|
9162
|
+
unregisterInFlightTool
|
|
8625
9163
|
]);
|
|
8626
9164
|
const refreshSessions = useCallback(async () => {
|
|
8627
9165
|
const list = await listSessionMeta(store, settings.showAllProjects ? void 0 : { projectRoot: projectDir });
|
|
@@ -8662,6 +9200,8 @@ function AppShell() {
|
|
|
8662
9200
|
runningRef.current = false;
|
|
8663
9201
|
sessionSafelistRef.current.clear();
|
|
8664
9202
|
pendingAnnotationsRef.current.clear();
|
|
9203
|
+
setInFlightTools([]);
|
|
9204
|
+
setActiveSkillNames(/* @__PURE__ */ new Set());
|
|
8665
9205
|
}, [
|
|
8666
9206
|
stream,
|
|
8667
9207
|
denyAll,
|
|
@@ -8739,6 +9279,7 @@ function AppShell() {
|
|
|
8739
9279
|
});
|
|
8740
9280
|
sessionRef.current = session;
|
|
8741
9281
|
agentRef.current = buildAgent(session, key);
|
|
9282
|
+
setActiveSkillNames(readPinnedSkills(session.metadata["zidane.activeSkills"]));
|
|
8742
9283
|
setEvents(eventsFromTurns(session.turns, session.runs));
|
|
8743
9284
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
8744
9285
|
setLastInputTokens(replayedTokens);
|
|
@@ -9094,7 +9635,8 @@ function AppShell() {
|
|
|
9094
9635
|
...refSpans.length > 0 ? { refs: refSpans } : {}
|
|
9095
9636
|
});
|
|
9096
9637
|
if (autoCompactInFlightRef.current) await autoCompactInFlightRef.current.catch(() => {});
|
|
9097
|
-
const
|
|
9638
|
+
const newRefs = uniqueSkillNamesFromReferences(references);
|
|
9639
|
+
const skillNames = Array.from(new Set([...activeSkillNamesRef.current, ...newRefs]));
|
|
9098
9640
|
for (const name of skillNames) try {
|
|
9099
9641
|
await agent.activateSkill(name);
|
|
9100
9642
|
} catch (err) {
|
|
@@ -9332,6 +9874,36 @@ function AppShell() {
|
|
|
9332
9874
|
return nextTurns.some((t) => t.id === prev) ? prev : null;
|
|
9333
9875
|
});
|
|
9334
9876
|
}, []);
|
|
9877
|
+
const onEditTurn = useCallback(async (turnId, newText) => {
|
|
9878
|
+
const session = sessionRef.current;
|
|
9879
|
+
if (!session) return;
|
|
9880
|
+
const turn = session.turns.find((t) => t.id === turnId);
|
|
9881
|
+
if (!turn) return;
|
|
9882
|
+
if (!turn.content.some((b) => b.type === "text")) return;
|
|
9883
|
+
const nonTextBlocks = turn.content.filter((b) => b.type !== "text");
|
|
9884
|
+
const firstTextIdx = turn.content.findIndex((b) => b.type === "text");
|
|
9885
|
+
const updatedContent = [...nonTextBlocks];
|
|
9886
|
+
if (newText.trim()) updatedContent.splice(firstTextIdx >= 0 ? Math.min(firstTextIdx, updatedContent.length) : 0, 0, {
|
|
9887
|
+
type: "text",
|
|
9888
|
+
text: newText
|
|
9889
|
+
});
|
|
9890
|
+
turn.content = updatedContent;
|
|
9891
|
+
session.setTurns([...session.turns]);
|
|
9892
|
+
try {
|
|
9893
|
+
await session.save();
|
|
9894
|
+
} catch (err) {
|
|
9895
|
+
debugLog("edit: save failed", err);
|
|
9896
|
+
return;
|
|
9897
|
+
}
|
|
9898
|
+
setEvents(eventsFromTurns(session.turns, session.runs));
|
|
9899
|
+
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
9900
|
+
setLastInputTokens(replayedTokens);
|
|
9901
|
+
lastInputTokensRef.current = replayedTokens;
|
|
9902
|
+
setCurrentSession((prev) => prev ? {
|
|
9903
|
+
...prev,
|
|
9904
|
+
updatedAt: Date.now()
|
|
9905
|
+
} : prev);
|
|
9906
|
+
}, []);
|
|
9335
9907
|
/**
|
|
9336
9908
|
* Identity of the session row the user has focused on the sessions
|
|
9337
9909
|
* screen — single source of truth. `SessionsScreen` is rendered fully
|
|
@@ -9628,7 +10200,8 @@ function AppShell() {
|
|
|
9628
10200
|
total: turnIds.length,
|
|
9629
10201
|
actions: {
|
|
9630
10202
|
onFork: onForkTurn,
|
|
9631
|
-
onDelete: onDeleteTurn
|
|
10203
|
+
onDelete: onDeleteTurn,
|
|
10204
|
+
onEdit: onEditTurn
|
|
9632
10205
|
},
|
|
9633
10206
|
keybindings
|
|
9634
10207
|
}));
|
|
@@ -9638,6 +10211,7 @@ function AppShell() {
|
|
|
9638
10211
|
turnIds,
|
|
9639
10212
|
onForkTurn,
|
|
9640
10213
|
onDeleteTurn,
|
|
10214
|
+
onEditTurn,
|
|
9641
10215
|
keybindings
|
|
9642
10216
|
]);
|
|
9643
10217
|
useKeyboard((key) => {
|
|
@@ -9746,6 +10320,21 @@ function AppShell() {
|
|
|
9746
10320
|
onCycleAgent();
|
|
9747
10321
|
return;
|
|
9748
10322
|
}
|
|
10323
|
+
if (matchesBinding(key, keybindings.cancelToolCall) && screen === "chat" && busy && !pendingApproval) {
|
|
10324
|
+
const snapshot = inFlightToolsRef.current;
|
|
10325
|
+
if (snapshot.length === 0) return;
|
|
10326
|
+
modal.open(/* @__PURE__ */ jsx(CancelToolModal, {
|
|
10327
|
+
inFlight: snapshot,
|
|
10328
|
+
onCancel: (callId, reason) => agentRef.current?.cancelTool(callId, reason) ?? false,
|
|
10329
|
+
onCancelAll: () => {
|
|
10330
|
+
const agent = agentRef.current;
|
|
10331
|
+
if (!agent) return;
|
|
10332
|
+
for (const entry of inFlightToolsRef.current) agent.cancelTool(entry.callId, "user-cancelled-all");
|
|
10333
|
+
},
|
|
10334
|
+
onClose: () => modal.close()
|
|
10335
|
+
}));
|
|
10336
|
+
return;
|
|
10337
|
+
}
|
|
9749
10338
|
if (key.name !== "escape") return;
|
|
9750
10339
|
if (busy || pendingApproval) return onAbort();
|
|
9751
10340
|
if (popupOpenRef.current) return;
|
|
@@ -9777,7 +10366,10 @@ function AppShell() {
|
|
|
9777
10366
|
effortKeyColor: COLOR.warn,
|
|
9778
10367
|
agentLabel: pickedAgent.label,
|
|
9779
10368
|
agentColor: accentColor(pickedAgent.accent, COLOR),
|
|
9780
|
-
keybindings
|
|
10369
|
+
keybindings,
|
|
10370
|
+
inFlightToolCount: inFlightTools.length,
|
|
10371
|
+
activeSkillCount: activeSkillNames.size,
|
|
10372
|
+
skillsChipColor: COLOR.brand
|
|
9781
10373
|
}), [
|
|
9782
10374
|
screen,
|
|
9783
10375
|
busy,
|
|
@@ -9790,7 +10382,9 @@ function AppShell() {
|
|
|
9790
10382
|
pickedAgent,
|
|
9791
10383
|
COLOR,
|
|
9792
10384
|
modelHasReasoning,
|
|
9793
|
-
keybindings
|
|
10385
|
+
keybindings,
|
|
10386
|
+
inFlightTools,
|
|
10387
|
+
activeSkillNames
|
|
9794
10388
|
]);
|
|
9795
10389
|
const queuedMessagePreviews = useMemo(() => messageQueue.map((m) => ({
|
|
9796
10390
|
text: m.prompt,
|
|
@@ -9909,7 +10503,7 @@ function effortForModel(descriptor, modelId, remembered) {
|
|
|
9909
10503
|
* secondary `/n` chord with the current effort label, surfacing the
|
|
9910
10504
|
* effort picker as a discoverable, in-place affordance.
|
|
9911
10505
|
*/
|
|
9912
|
-
function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInteractionResumed, currentSession, hasMultipleAgents, modelLabel, modelColor, effortLabel, effortColor, effortKeyColor, agentLabel, agentColor, keybindings }) {
|
|
10506
|
+
function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInteractionResumed, currentSession, hasMultipleAgents, modelLabel, modelColor, effortLabel, effortColor, effortKeyColor, agentLabel, agentColor, keybindings, inFlightToolCount, activeSkillCount, skillsChipColor }) {
|
|
9913
10507
|
if (pending) return [
|
|
9914
10508
|
{
|
|
9915
10509
|
key: "↑↓",
|
|
@@ -9952,10 +10546,18 @@ function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInte
|
|
|
9952
10546
|
label: "leave for later"
|
|
9953
10547
|
}
|
|
9954
10548
|
];
|
|
9955
|
-
if (busy)
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
10549
|
+
if (busy) {
|
|
10550
|
+
const baseBusyHints = [];
|
|
10551
|
+
if (inFlightToolCount > 0) baseBusyHints.push({
|
|
10552
|
+
key: keybindings.cancelToolCall,
|
|
10553
|
+
label: inFlightToolCount === 1 ? "cancel tool" : `cancel tool (${inFlightToolCount})`
|
|
10554
|
+
});
|
|
10555
|
+
baseBusyHints.push({
|
|
10556
|
+
key: "esc",
|
|
10557
|
+
label: "abort"
|
|
10558
|
+
});
|
|
10559
|
+
return baseBusyHints;
|
|
10560
|
+
}
|
|
9959
10561
|
if (screen === "auth") return [
|
|
9960
10562
|
{
|
|
9961
10563
|
key: "↑↓",
|
|
@@ -10003,6 +10605,12 @@ function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInte
|
|
|
10003
10605
|
labelColor: effortColor
|
|
10004
10606
|
} } : {}
|
|
10005
10607
|
} : null;
|
|
10608
|
+
const skillsChip = activeSkillCount > 0 ? {
|
|
10609
|
+
key: "✦",
|
|
10610
|
+
keyColor: skillsChipColor,
|
|
10611
|
+
label: activeSkillCount === 1 ? "1 skill" : `${activeSkillCount} skills`,
|
|
10612
|
+
labelColor: skillsChipColor
|
|
10613
|
+
} : null;
|
|
10006
10614
|
return [
|
|
10007
10615
|
...hasMultipleAgents ? [{
|
|
10008
10616
|
key: keybindings.cycleAgent,
|
|
@@ -10010,6 +10618,7 @@ function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInte
|
|
|
10010
10618
|
labelColor: agentColor
|
|
10011
10619
|
}] : [],
|
|
10012
10620
|
...modelHint ? [modelHint] : [],
|
|
10621
|
+
...skillsChip ? [skillsChip] : [],
|
|
10013
10622
|
...currentSession ? [{
|
|
10014
10623
|
key: keybindings.openSessionDetails,
|
|
10015
10624
|
label: "session"
|