zidane 5.4.2 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/dist/{agent-DxBoKDba.d.ts → agent-CvImMxMQ.d.ts} +256 -5
- package/dist/agent-CvImMxMQ.d.ts.map +1 -0
- package/dist/chat.d.ts +137 -16
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +3 -2
- package/dist/contexts/docker.d.ts +1 -1
- package/dist/contexts-DhmMlT2W.js +472 -0
- package/dist/contexts-DhmMlT2W.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- 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-B0uc2C5x.d.ts} +9 -3
- package/dist/index-B0uc2C5x.d.ts.map +1 -0
- package/dist/{index-BiO_5Hm4.d.ts → index-CbS75MD3.d.ts} +2 -2
- package/dist/index-CbS75MD3.d.ts.map +1 -0
- package/dist/{index-B2VOOijU.d.ts → index-CtXksgqb.d.ts} +73 -4
- package/dist/index-CtXksgqb.d.ts.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +11 -11
- package/dist/{interpolate-ERgZUxgg.js → interpolate-BaaKaKzN.js} +156 -19
- package/dist/interpolate-BaaKaKzN.js.map +1 -0
- package/dist/{login-CJbeAadS.js → login-iTy-0wYz.js} +3 -3
- package/dist/{login-CJbeAadS.js.map → login-iTy-0wYz.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-h6UWhghO.js} +3 -2
- package/dist/presets-h6UWhghO.js.map +1 -0
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/{providers-x3LZByR5.js → providers-G0VBZK9j.js} +4 -4
- package/dist/{providers-x3LZByR5.js.map → providers-G0VBZK9j.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.d.ts.map +1 -1
- package/dist/session/sqlite.js +2 -1
- package/dist/session/sqlite.js.map +1 -1
- package/dist/{session-BHZwxmfr.js → session-CbkiJDlH.js} +3 -2
- package/dist/session-CbkiJDlH.js.map +1 -0
- 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-D_icxa-V.js} +813 -284
- package/dist/tools-D_icxa-V.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +2 -2
- package/dist/{transcript-anchors-DonKvoh4.d.ts → transcript-anchors-3FFw2xuk.d.ts} +98 -15
- package/dist/transcript-anchors-3FFw2xuk.d.ts.map +1 -0
- package/dist/tui.d.ts +29 -5
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +879 -70
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-TKvy0q29.js → turn-operations-CtgBlBHn.js} +412 -125
- package/dist/turn-operations-CtgBlBHn.js.map +1 -0
- package/dist/types-IcokUOyC.js.map +1 -1
- package/dist/types-KukEp-mi.d.ts +253 -0
- package/dist/types-KukEp-mi.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/types.js +1 -1
- package/docs/ARCHITECTURE.md +37 -3
- package/docs/CHAT.md +4 -2
- package/docs/RUN_IN_BACKGROUND.md +612 -0
- package/docs/SKILL.md +83 -14
- package/docs/TUI.md +40 -2
- package/package.json +4 -4
- package/dist/agent-DxBoKDba.d.ts.map +0 -1
- package/dist/contexts-BwiHIr2w.js +0 -129
- package/dist/contexts-BwiHIr2w.js.map +0 -1
- package/dist/index-B2VOOijU.d.ts.map +0 -1
- package/dist/index-BOtXdQkW.d.ts.map +0 -1
- package/dist/index-BiO_5Hm4.d.ts.map +0 -1
- package/dist/interpolate-ERgZUxgg.js.map +0 -1
- package/dist/presets-MCcvxiNT.js.map +0 -1
- package/dist/session-BHZwxmfr.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/types-Ce78ds4h.d.ts +0 -88
- package/dist/types-Ce78ds4h.d.ts.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
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 { A as resolvePersistDir, B as formatTaskStatus, H as previewLine, I as ageString, L as compactPath, O as cleanupPersistedSession, R as fmtTokens, U as shortId, V as formatTaskSummary, j as resolveTasksDir, p as createAgent, z as formatDuration } from "./tools-D_icxa-V.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-iTy-0wYz.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-CbkiJDlH.js";
|
|
7
7
|
import { createTuiStore } from "./session/sqlite.js";
|
|
8
|
-
import { $ as useMcpAuthDispatch, $n as
|
|
8
|
+
import { $ as useMcpAuthDispatch, $n as tryOpenBrowser, A as getSafelist, At as resolveChipColor, B as supportsOAuth, Bt as useDiscoveryOptional, Cn as mergeApprovalAndBodyOutcomes, Cr as piIdOf, Ct as SETTINGS_CHOICES, D as useSafeModeQueue, Dn as stripEditOutcomesAnnotation, Dt as useSettings, E as useSafeModeActions, En as rewriteMultiEditHeader, Et as clampFps, F as suggestSafelistEntry, Fn as matchesBinding, Fr as TODO_STATUS_GLYPHS, Gr as useActiveTodos, H as filterModelCatalog, Hn as uniqueSkillNamesFromReferences, Ht as useConfig, Jt as isEditErrorResult, K as discoverProjectMcps, Kt as deriveSessionTitle, L as splitPromptSegments, Lt as createDiscoverySlot, Nn as ensureKeybindingsFile, On as summarizeOutcomes, Q as McpAuthProvider, Qn as buildLinearRamp, Qt as listSessionMeta, R as formatPathForCwd, Rt as DiscoveryProvider, St as DEFAULT_SETTINGS, T as SafeModeProvider, Tn as resolveApprovalForPayload, Tt as SettingsProvider, U as indexOfEntry, Ut as resolveConfig, V as buildModelCatalog, Vn as createSkillsCompletionProvider, Vt as ConfigProvider, W as buildMcpServers, Wn as createFilesCompletionProvider, Wt as EDIT_TOOL_NAMES, Xn as useCompletion, Xt as isVisible, Y as createFileMcpCredentialStore, Yt as isTurnHighlighted, Zn as blendHsl, Zt as lastContextSizeFromTurns, _ as turnContextSize, _n as previewEditPayload, _r as getContextWindow, _t as truncateTrailing, a as computeTurnAnchors, at as InteractionsProvider, b as defaultSkillScanPaths, bt as listProjectFiles, c as formatToolCall, cn as turnSelectionOwnership, ct as createInteractionTools, d as useSelectStyle, dn as buildContextualDiff, dt as pendingInteractionsFromTurns, en as marginTopFor, et as useMcpAuthState, f as useSurfaces, fn as buildUnifiedDiff, g as finalizeStreamingMarkdownForOwner, gn as filetypeFromPath, gt as hintsLength, h as finalizeStreamingMarkdown, hn as extractEditPayload, ht as clipHintsToWidth, i as turnAsText, in as sumRunCosts, j as isOnSafelist, jt as resolveTheme, k as addToSafelist, kn as findGitRoot, kr as accentColor, l as ThemeProvider, ln as updateToolEventOutcomes, lr as setProviderCredential, m as useTheme, mt as useInteractionsQueue, n as deleteTurnSafely, ni as buildBuildSystem, nn as selectableTurnIds, nr as shouldAutoCompact, o as TOOL_DISPLAY, on as toolCallPreview, pt as useInteractionsActions, qt as eventsFromTurns, r as truncateTurnsAt, ri as buildPlanSystem, rn as stripSpawnTokensLine, rr as detectAuth, rt as splitMarkdownCodeBlocks, s as displayNameFor, sn as toolResultText, st as buildResumedToolResultsTurn, tr as bootTick, tt as getMcpAuthStatus, u as useColors, ut as makeRequestInteraction, v as useStreamBuffer, w as writeSessionExport, wn as parseEditOutcomesFromResult, wt as SETTINGS_TOGGLES, x as discoverProjectSkills, xn as buildEditOutcomesAnnotation, xt as useEnabledToggleSet, y as buildSkillsConfig, yn as summarizeEditPayload, yr as modelSupportsReasoning, yt as generateSessionTitle, z as runOAuthLogin, zt as useDiscovery } from "./turn-operations-CtgBlBHn.js";
|
|
9
|
+
import { homedir } from "node:os";
|
|
9
10
|
import { spawn } from "node:child_process";
|
|
10
|
-
import { Buffer } from "node:buffer";
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
|
-
import {
|
|
12
|
+
import { Buffer } from "node:buffer";
|
|
13
13
|
import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
14
14
|
import { BoxRenderable, CodeRenderable, RGBA, SyntaxStyle, TextRenderable, addDefaultParsers, createCliRenderer, decodePasteBytes, defaultTextareaKeyBindings, getTreeSitterClient, stripAnsiSequences } from "@opentui/core";
|
|
15
15
|
import { createRoot, useKeyboard, useRenderer, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
|
|
@@ -223,6 +223,182 @@ 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) {
|
|
254
|
+
const result = onCancel(row, "user-clicked-cancel");
|
|
255
|
+
if (result instanceof Promise) result.catch(() => {});
|
|
256
|
+
}
|
|
257
|
+
onClose();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (key.name === "a") {
|
|
261
|
+
onCancelAll();
|
|
262
|
+
onClose();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
const elapsedColWidth = 8;
|
|
266
|
+
const callIdColWidth = 14;
|
|
267
|
+
const childColWidth = 10;
|
|
268
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
269
|
+
title: "cancel tool call",
|
|
270
|
+
bottomTitle: empty ? "no calls in flight" : `${rows.length} in flight`,
|
|
271
|
+
maxWidth: Math.min(96, Math.max(64, termWidth - 8)),
|
|
272
|
+
minWidth: 56,
|
|
273
|
+
onClose,
|
|
274
|
+
children: [empty ? /* @__PURE__ */ jsxs("text", {
|
|
275
|
+
fg: COLOR.dim,
|
|
276
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
277
|
+
fg: COLOR.mute,
|
|
278
|
+
children: "no tool calls are currently in flight — "
|
|
279
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
280
|
+
fg: COLOR.dim,
|
|
281
|
+
children: "nothing to cancel."
|
|
282
|
+
})]
|
|
283
|
+
}) : /* @__PURE__ */ jsx("box", {
|
|
284
|
+
style: {
|
|
285
|
+
flexDirection: "column",
|
|
286
|
+
flexShrink: 0
|
|
287
|
+
},
|
|
288
|
+
children: rows.map((row, i) => /* @__PURE__ */ jsx(CancelToolRow, {
|
|
289
|
+
row,
|
|
290
|
+
isFocused: i === safeIndex,
|
|
291
|
+
highlightBg: SURFACE.selection,
|
|
292
|
+
elapsedColWidth,
|
|
293
|
+
callIdColWidth,
|
|
294
|
+
childColWidth
|
|
295
|
+
}, row.callId))
|
|
296
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
297
|
+
fg: COLOR.dim,
|
|
298
|
+
children: [
|
|
299
|
+
/* @__PURE__ */ jsx("span", {
|
|
300
|
+
fg: COLOR.warn,
|
|
301
|
+
children: "↑↓"
|
|
302
|
+
}),
|
|
303
|
+
" navigate · ",
|
|
304
|
+
/* @__PURE__ */ jsx("span", {
|
|
305
|
+
fg: COLOR.warn,
|
|
306
|
+
children: "↵"
|
|
307
|
+
}),
|
|
308
|
+
" cancel selected · ",
|
|
309
|
+
/* @__PURE__ */ jsx("span", {
|
|
310
|
+
fg: COLOR.warn,
|
|
311
|
+
children: "a"
|
|
312
|
+
}),
|
|
313
|
+
" cancel all · ",
|
|
314
|
+
/* @__PURE__ */ jsx("span", {
|
|
315
|
+
fg: COLOR.warn,
|
|
316
|
+
children: "esc"
|
|
317
|
+
}),
|
|
318
|
+
" close"
|
|
319
|
+
]
|
|
320
|
+
})]
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function CancelToolRow({ row, isFocused, highlightBg, elapsedColWidth, callIdColWidth, childColWidth }) {
|
|
324
|
+
const COLOR = useColors();
|
|
325
|
+
const elapsed = formatElapsed(Date.now() - row.startedAt).padStart(elapsedColWidth, " ");
|
|
326
|
+
const idLabel = truncate(row.callId, callIdColWidth).padEnd(callIdColWidth, " ");
|
|
327
|
+
const childLabel = (row.childId ? `· ${row.childId}` : "").padEnd(childColWidth, " ");
|
|
328
|
+
const kindGlyph = row.kind === "task" ? "⌁" : "·";
|
|
329
|
+
return /* @__PURE__ */ jsx("box", {
|
|
330
|
+
style: {
|
|
331
|
+
height: 1,
|
|
332
|
+
paddingLeft: 1,
|
|
333
|
+
paddingRight: 1,
|
|
334
|
+
flexShrink: 0,
|
|
335
|
+
backgroundColor: isFocused ? highlightBg : void 0
|
|
336
|
+
},
|
|
337
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
338
|
+
wrapMode: "none",
|
|
339
|
+
children: [
|
|
340
|
+
/* @__PURE__ */ jsx("span", {
|
|
341
|
+
fg: isFocused ? COLOR.brand : COLOR.mute,
|
|
342
|
+
children: isFocused ? "›" : " "
|
|
343
|
+
}),
|
|
344
|
+
/* @__PURE__ */ jsx("span", {
|
|
345
|
+
fg: COLOR.mute,
|
|
346
|
+
children: " "
|
|
347
|
+
}),
|
|
348
|
+
/* @__PURE__ */ jsx("span", {
|
|
349
|
+
fg: isFocused ? COLOR.warn : COLOR.mute,
|
|
350
|
+
children: kindGlyph
|
|
351
|
+
}),
|
|
352
|
+
/* @__PURE__ */ jsx("span", {
|
|
353
|
+
fg: COLOR.mute,
|
|
354
|
+
children: " "
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ jsx("span", {
|
|
357
|
+
fg: isFocused ? COLOR.brand : COLOR.dim,
|
|
358
|
+
children: row.tool
|
|
359
|
+
}),
|
|
360
|
+
/* @__PURE__ */ jsx("span", {
|
|
361
|
+
fg: COLOR.mute,
|
|
362
|
+
children: " "
|
|
363
|
+
}),
|
|
364
|
+
/* @__PURE__ */ jsx("span", {
|
|
365
|
+
fg: COLOR.mute,
|
|
366
|
+
children: idLabel
|
|
367
|
+
}),
|
|
368
|
+
/* @__PURE__ */ jsx("span", {
|
|
369
|
+
fg: COLOR.mute,
|
|
370
|
+
children: " "
|
|
371
|
+
}),
|
|
372
|
+
/* @__PURE__ */ jsx("span", {
|
|
373
|
+
fg: COLOR.mute,
|
|
374
|
+
children: childLabel
|
|
375
|
+
}),
|
|
376
|
+
/* @__PURE__ */ jsx("span", {
|
|
377
|
+
fg: COLOR.warn,
|
|
378
|
+
children: elapsed
|
|
379
|
+
})
|
|
380
|
+
]
|
|
381
|
+
})
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Format a sub-minute duration as `0.3s` / `7.4s`; minute-plus as `2m12s`.
|
|
386
|
+
* Tight + monospace-friendly so the column stays right-aligned.
|
|
387
|
+
*/
|
|
388
|
+
function formatElapsed(ms) {
|
|
389
|
+
if (ms < 0) return "0.0s";
|
|
390
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
391
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
392
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
393
|
+
const seconds = totalSeconds % 60;
|
|
394
|
+
return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
395
|
+
}
|
|
396
|
+
function truncate(s, max) {
|
|
397
|
+
if (s.length <= max) return s;
|
|
398
|
+
if (max <= 1) return s.slice(0, max);
|
|
399
|
+
return `${s.slice(0, max - 1)}…`;
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
226
402
|
//#region src/tui/clipboard.ts
|
|
227
403
|
/**
|
|
228
404
|
* Two-pronged clipboard write.
|
|
@@ -407,8 +583,15 @@ function CrushThrobber({ label, size = 15, from, to, labelColor }) {
|
|
|
407
583
|
* embedded Tree-sitter language tokens (`keyword`, `string`, `function`,
|
|
408
584
|
* …) — OpenTUI's `<markdown>` re-uses the same `SyntaxStyle` for the
|
|
409
585
|
* fenced-code renderable, so one table drives both surfaces.
|
|
586
|
+
*
|
|
587
|
+
* `overrides`, when set, replaces individual entries by token name —
|
|
588
|
+
* each override is merged INTO the resolved entry (shallow). Used by
|
|
589
|
+
* the "on-selection" variant to swap `markup.raw.bg` for the row's
|
|
590
|
+
* selection surface so inline code chips blend into the highlight
|
|
591
|
+
* rather than reading as "punched out" rectangles. Top-level keys
|
|
592
|
+
* absent from the base theme become brand-new entries.
|
|
410
593
|
*/
|
|
411
|
-
function buildMdStyle(theme) {
|
|
594
|
+
function buildMdStyle(theme, overrides) {
|
|
412
595
|
const styles = {};
|
|
413
596
|
for (const [token, style] of Object.entries(theme.syntax)) {
|
|
414
597
|
const out = {};
|
|
@@ -420,13 +603,26 @@ function buildMdStyle(theme) {
|
|
|
420
603
|
if (style.dim) out.dim = true;
|
|
421
604
|
styles[token] = out;
|
|
422
605
|
}
|
|
606
|
+
if (overrides) for (const [token, patch] of Object.entries(overrides)) styles[token] = {
|
|
607
|
+
...styles[token] ?? {},
|
|
608
|
+
...patch
|
|
609
|
+
};
|
|
423
610
|
return SyntaxStyle.fromStyles(styles);
|
|
424
611
|
}
|
|
425
612
|
const MdStyleContext = createContext(null);
|
|
426
613
|
function MdStyleProvider({ children }) {
|
|
427
614
|
const theme = useTheme();
|
|
428
|
-
const
|
|
429
|
-
|
|
615
|
+
const bundle = useMemo(() => {
|
|
616
|
+
const selectionBg = RGBA.fromHex(theme.surfaces.selection);
|
|
617
|
+
return {
|
|
618
|
+
regular: buildMdStyle(theme),
|
|
619
|
+
selected: buildMdStyle(theme, {
|
|
620
|
+
"markup.raw": { bg: selectionBg },
|
|
621
|
+
"markup.raw.block": { bg: selectionBg }
|
|
622
|
+
})
|
|
623
|
+
};
|
|
624
|
+
}, [theme]);
|
|
625
|
+
return createElement(MdStyleContext.Provider, { value: bundle }, children);
|
|
430
626
|
}
|
|
431
627
|
/**
|
|
432
628
|
* Active markdown / syntax-highlighting style. Returns a single shared
|
|
@@ -434,13 +630,18 @@ function MdStyleProvider({ children }) {
|
|
|
434
630
|
* mount, re-built on theme switch. A `Settings.theme` flip re-paints every
|
|
435
631
|
* `<markdown>` that reads this hook.
|
|
436
632
|
*
|
|
633
|
+
* Pass `{ selected: true }` to get the on-selection variant where inline
|
|
634
|
+
* code chips' background matches the row's selection surface (so the
|
|
635
|
+
* chips blend into the highlight rather than reading as punched-out
|
|
636
|
+
* rectangles).
|
|
637
|
+
*
|
|
437
638
|
* Throws if used outside `<MdStyleProvider>` so a missing wiring shows up
|
|
438
639
|
* loudly in development rather than silently rendering plain text.
|
|
439
640
|
*/
|
|
440
|
-
function useMdStyle() {
|
|
441
|
-
const
|
|
442
|
-
if (!
|
|
443
|
-
return
|
|
641
|
+
function useMdStyle(opts = {}) {
|
|
642
|
+
const bundle = useContext(MdStyleContext);
|
|
643
|
+
if (!bundle) throw new Error("useMdStyle must be used inside <MdStyleProvider>");
|
|
644
|
+
return opts.selected ? bundle.selected : bundle.regular;
|
|
444
645
|
}
|
|
445
646
|
const CHIP_TOKEN_PREFIX = "completion.reference";
|
|
446
647
|
/** Per-kind token name in the chip `SyntaxStyle` — e.g. `completion.reference.skills`. */
|
|
@@ -554,7 +755,7 @@ function useChipHighlights(textareaRef, references, chipStyle) {
|
|
|
554
755
|
* same-turn events read as one continuous highlighted block instead of a
|
|
555
756
|
* striped list.
|
|
556
757
|
*/
|
|
557
|
-
const EventLine = memo(({ event, previous, depthOffset = 0, selected = false, anchorId }) => {
|
|
758
|
+
const EventLine = memo(({ event, previous, depthOffset = 0, selected = false, anchorId, hideChildLabel = false }) => {
|
|
558
759
|
const SURFACE = useSurfaces();
|
|
559
760
|
const gap = marginTopFor(event, previous);
|
|
560
761
|
return /* @__PURE__ */ jsx("box", {
|
|
@@ -569,7 +770,9 @@ const EventLine = memo(({ event, previous, depthOffset = 0, selected = false, an
|
|
|
569
770
|
},
|
|
570
771
|
children: /* @__PURE__ */ jsx(EventLineImpl, {
|
|
571
772
|
event,
|
|
572
|
-
depthOffset
|
|
773
|
+
depthOffset,
|
|
774
|
+
hideChildLabel,
|
|
775
|
+
selected
|
|
573
776
|
})
|
|
574
777
|
});
|
|
575
778
|
});
|
|
@@ -1065,11 +1268,12 @@ function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds }) {
|
|
|
1065
1268
|
}, [events]);
|
|
1066
1269
|
const title = childIds.length === 0 ? " subagent " : childIds.length === 1 ? ` ${childIds[0]} ` : ` subagents · ${childIds.join(", ")} `;
|
|
1067
1270
|
const marginTop = previous ? 1 : 0;
|
|
1271
|
+
const hideChildLabel = childIds.length === 1;
|
|
1068
1272
|
return /* @__PURE__ */ jsx("box", {
|
|
1069
1273
|
title,
|
|
1070
1274
|
style: {
|
|
1071
1275
|
border: true,
|
|
1072
|
-
borderColor: COLOR.
|
|
1276
|
+
borderColor: COLOR.brand,
|
|
1073
1277
|
paddingLeft: 1,
|
|
1074
1278
|
paddingRight: 1,
|
|
1075
1279
|
paddingTop: 0,
|
|
@@ -1084,7 +1288,8 @@ function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds }) {
|
|
|
1084
1288
|
previous: events[i - 1],
|
|
1085
1289
|
depthOffset: 1,
|
|
1086
1290
|
selected: selectedTurnId !== null && evt.turnId === selectedTurnId,
|
|
1087
|
-
anchorId: anchorIds?.[i]
|
|
1291
|
+
anchorId: anchorIds?.[i],
|
|
1292
|
+
hideChildLabel
|
|
1088
1293
|
}, i))
|
|
1089
1294
|
});
|
|
1090
1295
|
}
|
|
@@ -1126,7 +1331,7 @@ function rowStyle(paddingLeft) {
|
|
|
1126
1331
|
alignSelf: "stretch"
|
|
1127
1332
|
};
|
|
1128
1333
|
}
|
|
1129
|
-
function EventLineImpl({ event, depthOffset = 0 }) {
|
|
1334
|
+
function EventLineImpl({ event, depthOffset = 0, hideChildLabel = false, selected = false }) {
|
|
1130
1335
|
const COLOR = useColors();
|
|
1131
1336
|
const { settings } = useSettings();
|
|
1132
1337
|
const safeText = event.text === "" ? " " : event.text;
|
|
@@ -1189,7 +1394,8 @@ function EventLineImpl({ event, depthOffset = 0 }) {
|
|
|
1189
1394
|
style: row,
|
|
1190
1395
|
children: /* @__PURE__ */ jsx(MarkdownBlock, {
|
|
1191
1396
|
text: event.text,
|
|
1192
|
-
dim: child
|
|
1397
|
+
dim: child,
|
|
1398
|
+
selected
|
|
1193
1399
|
})
|
|
1194
1400
|
});
|
|
1195
1401
|
case "spawn-start": return /* @__PURE__ */ jsx("box", {
|
|
@@ -1201,10 +1407,13 @@ function EventLineImpl({ event, depthOffset = 0 }) {
|
|
|
1201
1407
|
fg: COLOR.accent,
|
|
1202
1408
|
children: "🌱 "
|
|
1203
1409
|
}),
|
|
1204
|
-
/* @__PURE__ */ jsx("span", {
|
|
1205
|
-
fg: COLOR.
|
|
1206
|
-
children:
|
|
1207
|
-
}),
|
|
1410
|
+
!hideChildLabel && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1411
|
+
fg: COLOR.brand,
|
|
1412
|
+
children: event.childId ?? "child"
|
|
1413
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1414
|
+
fg: COLOR.mute,
|
|
1415
|
+
children: " · "
|
|
1416
|
+
})] }),
|
|
1208
1417
|
/* @__PURE__ */ jsx("span", {
|
|
1209
1418
|
fg: COLOR.dim,
|
|
1210
1419
|
children: safeText
|
|
@@ -1221,10 +1430,13 @@ function EventLineImpl({ event, depthOffset = 0 }) {
|
|
|
1221
1430
|
fg: COLOR.accent,
|
|
1222
1431
|
children: "🌳 "
|
|
1223
1432
|
}),
|
|
1224
|
-
/* @__PURE__ */ jsx("span", {
|
|
1225
|
-
fg: COLOR.
|
|
1226
|
-
children:
|
|
1227
|
-
}),
|
|
1433
|
+
!hideChildLabel && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1434
|
+
fg: COLOR.brand,
|
|
1435
|
+
children: event.childId ?? "child"
|
|
1436
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1437
|
+
fg: COLOR.mute,
|
|
1438
|
+
children: " · "
|
|
1439
|
+
})] }),
|
|
1228
1440
|
/* @__PURE__ */ jsx("span", {
|
|
1229
1441
|
fg: COLOR.mute,
|
|
1230
1442
|
children: safeText
|
|
@@ -1236,10 +1448,80 @@ function EventLineImpl({ event, depthOffset = 0 }) {
|
|
|
1236
1448
|
event,
|
|
1237
1449
|
indent: row.paddingLeft
|
|
1238
1450
|
});
|
|
1451
|
+
case "task-notification": return /* @__PURE__ */ jsx(TaskNotificationBlock, {
|
|
1452
|
+
event,
|
|
1453
|
+
indent: row.paddingLeft
|
|
1454
|
+
});
|
|
1239
1455
|
default: return /* @__PURE__ */ jsx("text", { children: safeText });
|
|
1240
1456
|
}
|
|
1241
1457
|
}
|
|
1242
1458
|
/**
|
|
1459
|
+
* One-line completion banner for a background task that exited or was
|
|
1460
|
+
* killed. Visual contract:
|
|
1461
|
+
*
|
|
1462
|
+
* - Glyph color reflects exit cleanliness:
|
|
1463
|
+
* `'exited'` exit code 0 → success (subtle accent — quiet).
|
|
1464
|
+
* `'exited'` non-zero → warn (model / user should notice).
|
|
1465
|
+
* `'killed'` → warn (we issued SIGTERM).
|
|
1466
|
+
* - Task id reads in the agent's brand accent so the banner stands
|
|
1467
|
+
* apart from surrounding markdown without screaming.
|
|
1468
|
+
* - Status label takes the same accent as the glyph — color-coupling
|
|
1469
|
+
* reinforces the "this is what happened" mental model at a glance.
|
|
1470
|
+
* - Output path is `compactPath()`-formatted (`~/.zidane/...` when
|
|
1471
|
+
* under `$HOME`) so the column doesn't blow past terminal width
|
|
1472
|
+
* just to show the user's home prefix they already know about.
|
|
1473
|
+
*
|
|
1474
|
+
* Vertical spacing comes from `marginTopFor` — banners get a `tool`-like
|
|
1475
|
+
* gap before (and the next non-task event provides the gap after), but
|
|
1476
|
+
* consecutive banners stack tightly so a burst of completions doesn't
|
|
1477
|
+
* scatter the transcript.
|
|
1478
|
+
*/
|
|
1479
|
+
function TaskNotificationBlock({ event, indent }) {
|
|
1480
|
+
const COLOR = useColors();
|
|
1481
|
+
const task = event.task;
|
|
1482
|
+
const statusAccent = (task ? task.status === "killed" || task.exitCode !== 0 : false) ? COLOR.warn : COLOR.brand;
|
|
1483
|
+
const statusLabel = task ? formatTaskStatus(task) : "done";
|
|
1484
|
+
const displayPath = task?.outputPath ? compactPath(task.outputPath) : "";
|
|
1485
|
+
return /* @__PURE__ */ jsx("box", {
|
|
1486
|
+
style: { paddingLeft: indent },
|
|
1487
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
1488
|
+
wrapMode: "none",
|
|
1489
|
+
children: [
|
|
1490
|
+
/* @__PURE__ */ jsx("span", {
|
|
1491
|
+
fg: statusAccent,
|
|
1492
|
+
children: "⌁ "
|
|
1493
|
+
}),
|
|
1494
|
+
/* @__PURE__ */ jsx("span", {
|
|
1495
|
+
fg: COLOR.brand,
|
|
1496
|
+
children: task?.taskId ?? "?"
|
|
1497
|
+
}),
|
|
1498
|
+
/* @__PURE__ */ jsx("span", {
|
|
1499
|
+
fg: COLOR.mute,
|
|
1500
|
+
children: " · "
|
|
1501
|
+
}),
|
|
1502
|
+
/* @__PURE__ */ jsx("span", {
|
|
1503
|
+
fg: statusAccent,
|
|
1504
|
+
children: statusLabel
|
|
1505
|
+
}),
|
|
1506
|
+
task && task.durationMs > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1507
|
+
fg: COLOR.mute,
|
|
1508
|
+
children: " · "
|
|
1509
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1510
|
+
fg: COLOR.dim,
|
|
1511
|
+
children: formatDuration(task.durationMs)
|
|
1512
|
+
})] }),
|
|
1513
|
+
displayPath && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1514
|
+
fg: COLOR.mute,
|
|
1515
|
+
children: " · "
|
|
1516
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1517
|
+
fg: COLOR.dim,
|
|
1518
|
+
children: displayPath
|
|
1519
|
+
})] })
|
|
1520
|
+
]
|
|
1521
|
+
})
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1243
1525
|
* Boundary card for `compact-summary` events. Two visual rows:
|
|
1244
1526
|
*
|
|
1245
1527
|
* 1. Meta line — emoji + replaced-turn count + model + token usage,
|
|
@@ -1657,10 +1939,10 @@ function parseBlockIndex(id) {
|
|
|
1657
1939
|
const parsed = Number.parseInt(match[1] ?? "", 10);
|
|
1658
1940
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
1659
1941
|
}
|
|
1660
|
-
const MarkdownBlock = memo(({ text, dim }) => {
|
|
1942
|
+
const MarkdownBlock = memo(({ text, dim, selected = false }) => {
|
|
1661
1943
|
const COLOR = useColors();
|
|
1662
1944
|
const SURFACE = useSurfaces();
|
|
1663
|
-
const mdStyle = useMdStyle();
|
|
1945
|
+
const mdStyle = useMdStyle({ selected });
|
|
1664
1946
|
const renderer = useRenderer();
|
|
1665
1947
|
const content = text.replace(/^\n+|\n+$/g, "");
|
|
1666
1948
|
const bag = useRef({
|
|
@@ -1682,7 +1964,7 @@ const MarkdownBlock = memo(({ text, dim }) => {
|
|
|
1682
1964
|
streaming: true,
|
|
1683
1965
|
internalBlockMode: "coalesced",
|
|
1684
1966
|
fg: dim ? COLOR.dim : void 0,
|
|
1685
|
-
bg: SURFACE.background,
|
|
1967
|
+
bg: selected ? SURFACE.selection : SURFACE.background,
|
|
1686
1968
|
renderNode
|
|
1687
1969
|
});
|
|
1688
1970
|
});
|
|
@@ -1721,11 +2003,15 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1721
2003
|
const COLOR = useColors();
|
|
1722
2004
|
const SURFACE = useSurfaces();
|
|
1723
2005
|
const mdStyle = useMdStyle();
|
|
2006
|
+
const { settings } = useSettings();
|
|
1724
2007
|
const filetype = useMemo(() => filetypeFromPath(payload.path), [payload.path]);
|
|
1725
2008
|
const replaceAllCount = payload.hunks.filter((h) => h.replaceAll).length;
|
|
1726
2009
|
const hunkBadge = payload.tool === "multi_edit" && payload.hunks.length > 1 ? `${payload.hunks.length} hunks` : null;
|
|
1727
|
-
const
|
|
1728
|
-
const
|
|
2010
|
+
const outcomeSummary = summarizeOutcomes(payload.outcomes);
|
|
2011
|
+
const hasMixedOutcomes = !!payload.outcomes && outcomeSummary.denied + outcomeSummary.skipped + outcomeSummary.failed > 0;
|
|
2012
|
+
const editSummary = useMemo(() => summarizeEditPayload(payload), [payload]);
|
|
2013
|
+
const perHunkMode = hasMixedOutcomes;
|
|
2014
|
+
const compact = settings.editDiffDisplay === "compact";
|
|
1729
2015
|
return /* @__PURE__ */ jsxs("box", {
|
|
1730
2016
|
style: {
|
|
1731
2017
|
flexDirection: "column",
|
|
@@ -1751,6 +2037,24 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1751
2037
|
fg: dim ? COLOR.dim : COLOR.warn,
|
|
1752
2038
|
children: payload.path
|
|
1753
2039
|
}),
|
|
2040
|
+
(editSummary.totalAdded > 0 || editSummary.totalRemoved > 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2041
|
+
/* @__PURE__ */ jsx("span", {
|
|
2042
|
+
fg: COLOR.mute,
|
|
2043
|
+
children: " · "
|
|
2044
|
+
}),
|
|
2045
|
+
editSummary.totalAdded > 0 && /* @__PURE__ */ jsx("span", {
|
|
2046
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2047
|
+
children: `+${editSummary.totalAdded}`
|
|
2048
|
+
}),
|
|
2049
|
+
editSummary.totalAdded > 0 && editSummary.totalRemoved > 0 && /* @__PURE__ */ jsx("span", {
|
|
2050
|
+
fg: COLOR.mute,
|
|
2051
|
+
children: " "
|
|
2052
|
+
}),
|
|
2053
|
+
editSummary.totalRemoved > 0 && /* @__PURE__ */ jsx("span", {
|
|
2054
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2055
|
+
children: `−${editSummary.totalRemoved}`
|
|
2056
|
+
})
|
|
2057
|
+
] }),
|
|
1754
2058
|
hunkBadge && /* @__PURE__ */ jsx("span", {
|
|
1755
2059
|
fg: COLOR.mute,
|
|
1756
2060
|
children: ` · ${hunkBadge}`
|
|
@@ -1765,33 +2069,36 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1765
2069
|
children: " · "
|
|
1766
2070
|
}),
|
|
1767
2071
|
/* @__PURE__ */ jsx("span", {
|
|
1768
|
-
fg:
|
|
1769
|
-
children: `${
|
|
2072
|
+
fg: outcomeSummary.applied > 0 ? COLOR.accent : COLOR.mute,
|
|
2073
|
+
children: `${outcomeSummary.applied} applied`
|
|
1770
2074
|
}),
|
|
1771
|
-
|
|
2075
|
+
outcomeSummary.denied > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1772
2076
|
fg: COLOR.mute,
|
|
1773
2077
|
children: " · "
|
|
1774
2078
|
}), /* @__PURE__ */ jsx("span", {
|
|
1775
2079
|
fg: COLOR.error,
|
|
1776
|
-
children: `${
|
|
2080
|
+
children: `${outcomeSummary.denied} denied`
|
|
1777
2081
|
})] }),
|
|
1778
|
-
|
|
2082
|
+
outcomeSummary.skipped > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1779
2083
|
fg: COLOR.mute,
|
|
1780
2084
|
children: " · "
|
|
1781
2085
|
}), /* @__PURE__ */ jsx("span", {
|
|
1782
2086
|
fg: COLOR.warn,
|
|
1783
|
-
children: `${
|
|
2087
|
+
children: `${outcomeSummary.skipped} skipped`
|
|
1784
2088
|
})] }),
|
|
1785
|
-
|
|
2089
|
+
outcomeSummary.failed > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1786
2090
|
fg: COLOR.mute,
|
|
1787
2091
|
children: " · "
|
|
1788
2092
|
}), /* @__PURE__ */ jsx("span", {
|
|
1789
2093
|
fg: COLOR.error,
|
|
1790
|
-
children: `${
|
|
2094
|
+
children: `${outcomeSummary.failed} failed`
|
|
1791
2095
|
})] })
|
|
1792
2096
|
] })
|
|
1793
2097
|
]
|
|
1794
|
-
}),
|
|
2098
|
+
}), compact ? /* @__PURE__ */ jsx(CompactDiffSummary, {
|
|
2099
|
+
summary: editSummary,
|
|
2100
|
+
dim
|
|
2101
|
+
}) : perHunkMode ? /* @__PURE__ */ jsx("box", {
|
|
1795
2102
|
style: {
|
|
1796
2103
|
flexDirection: "column",
|
|
1797
2104
|
flexShrink: 0
|
|
@@ -1809,10 +2116,10 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1809
2116
|
dim
|
|
1810
2117
|
}, i))
|
|
1811
2118
|
}) : /* @__PURE__ */ jsx("diff", {
|
|
1812
|
-
diff: buildUnifiedDiff(payload),
|
|
2119
|
+
diff: payload.priorContent !== void 0 ? buildContextualDiff(payload, payload.priorContent) : buildUnifiedDiff(payload),
|
|
1813
2120
|
view: "unified",
|
|
1814
2121
|
wrapMode: "word",
|
|
1815
|
-
showLineNumbers:
|
|
2122
|
+
showLineNumbers: true,
|
|
1816
2123
|
...filetype ? { filetype } : {},
|
|
1817
2124
|
syntaxStyle: mdStyle,
|
|
1818
2125
|
addedBg: SURFACE.diff.addBg,
|
|
@@ -1828,6 +2135,91 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1828
2135
|
});
|
|
1829
2136
|
}
|
|
1830
2137
|
/**
|
|
2138
|
+
* Compact-mode body — one line per hunk under the tool header. Format:
|
|
2139
|
+
*
|
|
2140
|
+
* ` L42 · +2 −1 · old → new`
|
|
2141
|
+
*
|
|
2142
|
+
* Where `L<n>` is the new-file line position (omitted when priorContent
|
|
2143
|
+
* is absent and the position is unknown), and the `old → new` preview
|
|
2144
|
+
* is the FIRST changed line on each side, ASCII-arrowed and truncated
|
|
2145
|
+
* by the renderer's own word-wrap. A pure addition shows `+ new` only;
|
|
2146
|
+
* a pure deletion shows `− old` only.
|
|
2147
|
+
*/
|
|
2148
|
+
function CompactDiffSummary({ summary, dim }) {
|
|
2149
|
+
const COLOR = useColors();
|
|
2150
|
+
const SURFACE = useSurfaces();
|
|
2151
|
+
if (summary.hunks.length === 0) return null;
|
|
2152
|
+
return /* @__PURE__ */ jsx("box", {
|
|
2153
|
+
style: {
|
|
2154
|
+
flexDirection: "column",
|
|
2155
|
+
flexShrink: 0
|
|
2156
|
+
},
|
|
2157
|
+
children: summary.hunks.map((h, i) => {
|
|
2158
|
+
const oldPreview = h.firstOld?.trim();
|
|
2159
|
+
const newPreview = h.firstNew?.trim();
|
|
2160
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
2161
|
+
fg: dim ? COLOR.dim : COLOR.mute,
|
|
2162
|
+
wrapMode: "word",
|
|
2163
|
+
children: [
|
|
2164
|
+
/* @__PURE__ */ jsx("span", {
|
|
2165
|
+
fg: COLOR.mute,
|
|
2166
|
+
children: " "
|
|
2167
|
+
}),
|
|
2168
|
+
h.line !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2169
|
+
fg: dim ? COLOR.dim : COLOR.mute,
|
|
2170
|
+
children: `L${h.line}`
|
|
2171
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2172
|
+
fg: COLOR.mute,
|
|
2173
|
+
children: " · "
|
|
2174
|
+
})] }),
|
|
2175
|
+
h.added > 0 && /* @__PURE__ */ jsx("span", {
|
|
2176
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2177
|
+
children: `+${h.added}`
|
|
2178
|
+
}),
|
|
2179
|
+
h.added > 0 && h.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2180
|
+
fg: COLOR.mute,
|
|
2181
|
+
children: " "
|
|
2182
|
+
}),
|
|
2183
|
+
h.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2184
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2185
|
+
children: `−${h.removed}`
|
|
2186
|
+
}),
|
|
2187
|
+
(oldPreview || newPreview) && /* @__PURE__ */ jsx("span", {
|
|
2188
|
+
fg: COLOR.mute,
|
|
2189
|
+
children: " · "
|
|
2190
|
+
}),
|
|
2191
|
+
oldPreview && newPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2192
|
+
/* @__PURE__ */ jsx("span", {
|
|
2193
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2194
|
+
children: oldPreview
|
|
2195
|
+
}),
|
|
2196
|
+
/* @__PURE__ */ jsx("span", {
|
|
2197
|
+
fg: COLOR.mute,
|
|
2198
|
+
children: " → "
|
|
2199
|
+
}),
|
|
2200
|
+
/* @__PURE__ */ jsx("span", {
|
|
2201
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2202
|
+
children: newPreview
|
|
2203
|
+
})
|
|
2204
|
+
] }) : oldPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2205
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2206
|
+
children: "− "
|
|
2207
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2208
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2209
|
+
children: oldPreview
|
|
2210
|
+
})] }) : newPreview ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2211
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2212
|
+
children: "+ "
|
|
2213
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2214
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2215
|
+
children: newPreview
|
|
2216
|
+
})] }) : null
|
|
2217
|
+
]
|
|
2218
|
+
}, i);
|
|
2219
|
+
})
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
1831
2223
|
* One hunk inside an `EditDiffBlock` rendered as its own mini-diff with
|
|
1832
2224
|
* a status badge above it. Used only in the per-hunk view (multi_edit
|
|
1833
2225
|
* with mixed outcomes) so denied / skipped / failed edits remain
|
|
@@ -1835,9 +2227,11 @@ function EditDiffBlock({ payload, dim }) {
|
|
|
1835
2227
|
*/
|
|
1836
2228
|
const BADGE_WIDTH = 7;
|
|
1837
2229
|
function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR, SURFACE, dim }) {
|
|
2230
|
+
const { settings } = useSettings();
|
|
1838
2231
|
const kind = outcome?.kind ?? "applied";
|
|
1839
2232
|
const reason = outcome?.reason;
|
|
1840
2233
|
const dimmed = dim || kind !== "applied";
|
|
2234
|
+
const compact = settings.editDiffDisplay === "compact";
|
|
1841
2235
|
const diffText = useMemo(() => buildUnifiedDiff({
|
|
1842
2236
|
tool,
|
|
1843
2237
|
path,
|
|
@@ -1847,6 +2241,15 @@ function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR,
|
|
|
1847
2241
|
tool,
|
|
1848
2242
|
path
|
|
1849
2243
|
]);
|
|
2244
|
+
const hunkStats = useMemo(() => summarizeEditPayload({
|
|
2245
|
+
tool,
|
|
2246
|
+
path,
|
|
2247
|
+
hunks: [hunk]
|
|
2248
|
+
}).hunks[0], [
|
|
2249
|
+
hunk,
|
|
2250
|
+
tool,
|
|
2251
|
+
path
|
|
2252
|
+
]);
|
|
1850
2253
|
const badge = kind === "applied" ? {
|
|
1851
2254
|
label: "applied",
|
|
1852
2255
|
fg: COLOR.accent
|
|
@@ -1881,6 +2284,24 @@ function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR,
|
|
|
1881
2284
|
fg: badge.fg,
|
|
1882
2285
|
children: badge.label.padEnd(BADGE_WIDTH)
|
|
1883
2286
|
}),
|
|
2287
|
+
hunkStats && (hunkStats.added > 0 || hunkStats.removed > 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2288
|
+
/* @__PURE__ */ jsx("span", {
|
|
2289
|
+
fg: COLOR.mute,
|
|
2290
|
+
children: " · "
|
|
2291
|
+
}),
|
|
2292
|
+
hunkStats.added > 0 && /* @__PURE__ */ jsx("span", {
|
|
2293
|
+
fg: dim ? COLOR.dim : SURFACE.diff.addFg,
|
|
2294
|
+
children: `+${hunkStats.added}`
|
|
2295
|
+
}),
|
|
2296
|
+
hunkStats.added > 0 && hunkStats.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2297
|
+
fg: COLOR.mute,
|
|
2298
|
+
children: " "
|
|
2299
|
+
}),
|
|
2300
|
+
hunkStats.removed > 0 && /* @__PURE__ */ jsx("span", {
|
|
2301
|
+
fg: dim ? COLOR.dim : SURFACE.diff.removeFg,
|
|
2302
|
+
children: `−${hunkStats.removed}`
|
|
2303
|
+
})
|
|
2304
|
+
] }),
|
|
1884
2305
|
reason && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1885
2306
|
fg: COLOR.mute,
|
|
1886
2307
|
children: ": "
|
|
@@ -1889,11 +2310,11 @@ function HunkBlock({ index, hunk, outcome, tool, path, filetype, mdStyle, COLOR,
|
|
|
1889
2310
|
children: reason
|
|
1890
2311
|
})] })
|
|
1891
2312
|
]
|
|
1892
|
-
}), /* @__PURE__ */ jsx("diff", {
|
|
2313
|
+
}), !compact && /* @__PURE__ */ jsx("diff", {
|
|
1893
2314
|
diff: diffText,
|
|
1894
2315
|
view: "unified",
|
|
1895
2316
|
wrapMode: "word",
|
|
1896
|
-
showLineNumbers:
|
|
2317
|
+
showLineNumbers: true,
|
|
1897
2318
|
...filetype ? { filetype } : {},
|
|
1898
2319
|
syntaxStyle: mdStyle,
|
|
1899
2320
|
addedBg: SURFACE.diff.addBg,
|
|
@@ -1913,7 +2334,7 @@ function ToolCallBlock({ event, display, dim }) {
|
|
|
1913
2334
|
const COLOR = useColors();
|
|
1914
2335
|
const mdStyle = useMdStyle();
|
|
1915
2336
|
const name = event.tool ?? "";
|
|
1916
|
-
const verb = displayNameFor(name);
|
|
2337
|
+
const verb = displayNameFor(name, event.input);
|
|
1917
2338
|
const pretty = useMemo(() => {
|
|
1918
2339
|
if (!event.input) return null;
|
|
1919
2340
|
try {
|
|
@@ -7612,6 +8033,31 @@ const PREVIEW_CHAR_MAX = 8e3;
|
|
|
7612
8033
|
* keeps a comfortable shape rather than stretching to the full height.
|
|
7613
8034
|
*/
|
|
7614
8035
|
const MAX_MODAL_HEIGHT = 28;
|
|
8036
|
+
const EDIT_TEXTAREA_BINDINGS = [
|
|
8037
|
+
...defaultTextareaKeyBindings.filter((b) => b.name !== "return" && !(b.name === "a" && b.ctrl && !b.shift && !b.meta)),
|
|
8038
|
+
{
|
|
8039
|
+
name: "a",
|
|
8040
|
+
ctrl: true,
|
|
8041
|
+
action: "select-all"
|
|
8042
|
+
},
|
|
8043
|
+
{
|
|
8044
|
+
name: "return",
|
|
8045
|
+
action: "submit"
|
|
8046
|
+
},
|
|
8047
|
+
{
|
|
8048
|
+
name: "return",
|
|
8049
|
+
shift: true,
|
|
8050
|
+
action: "newline"
|
|
8051
|
+
}
|
|
8052
|
+
];
|
|
8053
|
+
/**
|
|
8054
|
+
* Extract the editable text from a turn — joins all `text` blocks.
|
|
8055
|
+
* Non-text blocks (tool_call, tool_result, thinking, etc.) are structural
|
|
8056
|
+
* and not included in the editable surface.
|
|
8057
|
+
*/
|
|
8058
|
+
function editableText(turn) {
|
|
8059
|
+
return turn.content.filter((b) => b.type === "text").map((b) => b.text).join("\n\n");
|
|
8060
|
+
}
|
|
7615
8061
|
function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
7616
8062
|
const COLOR = useColors();
|
|
7617
8063
|
const modal = useModal();
|
|
@@ -7621,6 +8067,9 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7621
8067
|
const bottomTitle = `${index - 1} before · ${total - index} after`;
|
|
7622
8068
|
const [pending, setPending] = useState(null);
|
|
7623
8069
|
const [copyStatus, setCopyStatus] = useState("idle");
|
|
8070
|
+
const [editing, setEditing] = useState(false);
|
|
8071
|
+
const textareaRef = useRef(null);
|
|
8072
|
+
const hasEditableText = turn.content.some((b) => b.type === "text");
|
|
7624
8073
|
const commitFork = () => {
|
|
7625
8074
|
modal.close();
|
|
7626
8075
|
actions.onFork(turn.id);
|
|
@@ -7636,7 +8085,16 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7636
8085
|
}
|
|
7637
8086
|
setCopyStatus(writeToClipboard(fullText) ? "copied" : "failed");
|
|
7638
8087
|
};
|
|
8088
|
+
const commitEdit = () => {
|
|
8089
|
+
const newText = textareaRef.current?.plainText ?? "";
|
|
8090
|
+
modal.close();
|
|
8091
|
+
actions.onEdit(turn.id, newText);
|
|
8092
|
+
};
|
|
7639
8093
|
useKeyboard((key) => {
|
|
8094
|
+
if (editing) {
|
|
8095
|
+
if (key.name === "escape") setEditing(false);
|
|
8096
|
+
return;
|
|
8097
|
+
}
|
|
7640
8098
|
if (key.name === "escape" && pending) {
|
|
7641
8099
|
setPending(null);
|
|
7642
8100
|
return;
|
|
@@ -7658,15 +8116,22 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7658
8116
|
handleCopy();
|
|
7659
8117
|
return;
|
|
7660
8118
|
}
|
|
8119
|
+
if (matchesBinding(key, keybindings.turnEdit)) {
|
|
8120
|
+
if (!hasEditableText) return;
|
|
8121
|
+
setPending(null);
|
|
8122
|
+
setCopyStatus("idle");
|
|
8123
|
+
setEditing(true);
|
|
8124
|
+
return;
|
|
8125
|
+
}
|
|
7661
8126
|
if (pending) setPending(null);
|
|
7662
8127
|
});
|
|
7663
8128
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
7664
|
-
title: `turn ${index} / ${total} · ${turn.role}`,
|
|
7665
|
-
bottomTitle,
|
|
8129
|
+
title: editing ? `edit turn ${index} / ${total} · ${turn.role}` : `turn ${index} / ${total} · ${turn.role}`,
|
|
8130
|
+
bottomTitle: editing ? void 0 : bottomTitle,
|
|
7666
8131
|
maxHeight: MAX_MODAL_HEIGHT,
|
|
7667
|
-
disableEscape: pending !== null,
|
|
8132
|
+
disableEscape: editing || pending !== null,
|
|
7668
8133
|
children: [
|
|
7669
|
-
/* @__PURE__ */ jsxs("text", {
|
|
8134
|
+
!editing && /* @__PURE__ */ jsxs("text", {
|
|
7670
8135
|
fg: COLOR.dim,
|
|
7671
8136
|
children: [
|
|
7672
8137
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -7705,7 +8170,7 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7705
8170
|
] })
|
|
7706
8171
|
]
|
|
7707
8172
|
}),
|
|
7708
|
-
/* @__PURE__ */ jsxs("text", {
|
|
8173
|
+
!editing && /* @__PURE__ */ jsxs("text", {
|
|
7709
8174
|
fg: COLOR.dim,
|
|
7710
8175
|
children: [/* @__PURE__ */ jsx("span", {
|
|
7711
8176
|
fg: COLOR.mute,
|
|
@@ -7715,7 +8180,31 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7715
8180
|
children: summary
|
|
7716
8181
|
})]
|
|
7717
8182
|
}),
|
|
7718
|
-
/* @__PURE__ */ jsx("box", {
|
|
8183
|
+
editing ? /* @__PURE__ */ jsx("box", {
|
|
8184
|
+
title: " edit ",
|
|
8185
|
+
style: {
|
|
8186
|
+
border: true,
|
|
8187
|
+
borderColor: COLOR.borderActive,
|
|
8188
|
+
paddingLeft: 1,
|
|
8189
|
+
paddingRight: 1,
|
|
8190
|
+
flexDirection: "column",
|
|
8191
|
+
flexGrow: 1,
|
|
8192
|
+
flexShrink: 1,
|
|
8193
|
+
minHeight: 5
|
|
8194
|
+
},
|
|
8195
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
8196
|
+
ref: textareaRef,
|
|
8197
|
+
focused: true,
|
|
8198
|
+
keyBindings: EDIT_TEXTAREA_BINDINGS,
|
|
8199
|
+
initialValue: editableText(turn),
|
|
8200
|
+
placeholder: "enter text…",
|
|
8201
|
+
style: {
|
|
8202
|
+
flexGrow: 1,
|
|
8203
|
+
height: "100%"
|
|
8204
|
+
},
|
|
8205
|
+
onSubmit: commitEdit
|
|
8206
|
+
})
|
|
8207
|
+
}) : /* @__PURE__ */ jsx("box", {
|
|
7719
8208
|
title: " preview ",
|
|
7720
8209
|
style: {
|
|
7721
8210
|
border: true,
|
|
@@ -7740,13 +8229,34 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7740
8229
|
children: "— no text content —"
|
|
7741
8230
|
})
|
|
7742
8231
|
}),
|
|
7743
|
-
/* @__PURE__ */
|
|
8232
|
+
editing ? /* @__PURE__ */ jsxs("text", {
|
|
8233
|
+
fg: COLOR.dim,
|
|
8234
|
+
children: [
|
|
8235
|
+
/* @__PURE__ */ jsx("span", {
|
|
8236
|
+
fg: COLOR.warn,
|
|
8237
|
+
children: "↵"
|
|
8238
|
+
}),
|
|
8239
|
+
" save · ",
|
|
8240
|
+
/* @__PURE__ */ jsx("span", {
|
|
8241
|
+
fg: COLOR.warn,
|
|
8242
|
+
children: "shift+↵"
|
|
8243
|
+
}),
|
|
8244
|
+
" newline · ",
|
|
8245
|
+
/* @__PURE__ */ jsx("span", {
|
|
8246
|
+
fg: COLOR.warn,
|
|
8247
|
+
children: "esc"
|
|
8248
|
+
}),
|
|
8249
|
+
" cancel"
|
|
8250
|
+
]
|
|
8251
|
+
}) : /* @__PURE__ */ jsx(ActionRow, {
|
|
7744
8252
|
pending,
|
|
7745
8253
|
copyStatus,
|
|
7746
8254
|
canCopy: fullText.length > 0,
|
|
8255
|
+
canEdit: hasEditableText,
|
|
7747
8256
|
forkKey: keybindings.turnFork,
|
|
7748
8257
|
deleteKey: keybindings.turnDelete,
|
|
7749
|
-
copyKey: keybindings.turnCopy
|
|
8258
|
+
copyKey: keybindings.turnCopy,
|
|
8259
|
+
editKey: keybindings.turnEdit
|
|
7750
8260
|
})
|
|
7751
8261
|
]
|
|
7752
8262
|
});
|
|
@@ -7758,7 +8268,7 @@ function TurnDetailsModal({ turn, index, total, actions, keybindings }) {
|
|
|
7758
8268
|
* by accident. The copy result rides the same row when present — same
|
|
7759
8269
|
* geometry, no layout shift.
|
|
7760
8270
|
*/
|
|
7761
|
-
function ActionRow({ pending, copyStatus, canCopy, forkKey, deleteKey, copyKey }) {
|
|
8271
|
+
function ActionRow({ pending, copyStatus, canCopy, canEdit, forkKey, deleteKey, copyKey, editKey }) {
|
|
7762
8272
|
const COLOR = useColors();
|
|
7763
8273
|
if (pending === "fork") return /* @__PURE__ */ jsxs("text", {
|
|
7764
8274
|
fg: COLOR.dim,
|
|
@@ -7818,6 +8328,11 @@ function ActionRow({ pending, copyStatus, canCopy, forkKey, deleteKey, copyKey }
|
|
|
7818
8328
|
children: deleteKey
|
|
7819
8329
|
}),
|
|
7820
8330
|
" delete · ",
|
|
8331
|
+
/* @__PURE__ */ jsx("span", {
|
|
8332
|
+
fg: canEdit ? COLOR.warn : COLOR.mute,
|
|
8333
|
+
children: editKey
|
|
8334
|
+
}),
|
|
8335
|
+
canEdit ? " edit · " : " (no text) · ",
|
|
7821
8336
|
/* @__PURE__ */ jsx("span", {
|
|
7822
8337
|
fg: COLOR.warn,
|
|
7823
8338
|
children: "esc"
|
|
@@ -7858,6 +8373,11 @@ function ActionRow({ pending, copyStatus, canCopy, forkKey, deleteKey, copyKey }
|
|
|
7858
8373
|
children: copyKey
|
|
7859
8374
|
}),
|
|
7860
8375
|
canCopy ? " copy · " : " (nothing to copy) · ",
|
|
8376
|
+
/* @__PURE__ */ jsx("span", {
|
|
8377
|
+
fg: canEdit ? COLOR.warn : COLOR.mute,
|
|
8378
|
+
children: editKey
|
|
8379
|
+
}),
|
|
8380
|
+
canEdit ? " edit · " : " (no text) · ",
|
|
7861
8381
|
/* @__PURE__ */ jsx("span", {
|
|
7862
8382
|
fg: COLOR.warn,
|
|
7863
8383
|
children: "esc"
|
|
@@ -7919,6 +8439,42 @@ async function launchEditor(path) {
|
|
|
7919
8439
|
}).unref();
|
|
7920
8440
|
}
|
|
7921
8441
|
/**
|
|
8442
|
+
* Session-metadata key under which the TUI persists the user's "pinned"
|
|
8443
|
+
* active skills — the set the user toggled on via `/skill-name` and
|
|
8444
|
+
* expects to stay active across run boundaries (and TUI restarts).
|
|
8445
|
+
*
|
|
8446
|
+
* Separate from the agent's `skillActivationState` for two reasons:
|
|
8447
|
+
* 1. The framework's run-end pass deactivates everything; the pinned
|
|
8448
|
+
* set survives that pass so the next prompt can re-activate.
|
|
8449
|
+
* 2. The agent's session-resume rehydrator only sees `skills_use`
|
|
8450
|
+
* `tool_call` blocks in history. Slash-command activations bypass
|
|
8451
|
+
* that path; this metadata key is the system-of-record for them.
|
|
8452
|
+
*/
|
|
8453
|
+
const ACTIVE_SKILLS_META_KEY = "zidane.activeSkills";
|
|
8454
|
+
/**
|
|
8455
|
+
* Read the pinned-skills set out of session metadata. Tolerant by
|
|
8456
|
+
* design — older sessions, manually-edited metadata, or a future
|
|
8457
|
+
* type drift all degrade to "no pins", never throw. Returns a fresh
|
|
8458
|
+
* Set so callers can mutate-and-store without aliasing the input.
|
|
8459
|
+
*/
|
|
8460
|
+
function readPinnedSkills(raw) {
|
|
8461
|
+
if (!Array.isArray(raw)) return /* @__PURE__ */ new Set();
|
|
8462
|
+
return new Set(raw.filter((v) => typeof v === "string" && v.length > 0));
|
|
8463
|
+
}
|
|
8464
|
+
/**
|
|
8465
|
+
* Mirror the pinned-skills set into session metadata. Sorted for
|
|
8466
|
+
* stable on-disk ordering (diffs / debugging). `session.setMeta`
|
|
8467
|
+
* already routes through `session:meta` hooks, so observers see a
|
|
8468
|
+
* single normalized payload regardless of insertion order.
|
|
8469
|
+
*
|
|
8470
|
+
* Imported lazily via the session ref — calling sites already
|
|
8471
|
+
* captured `session` in scope, so we accept it as an argument
|
|
8472
|
+
* rather than re-fetching from a ref.
|
|
8473
|
+
*/
|
|
8474
|
+
function persistPinnedSkills(session, pins) {
|
|
8475
|
+
session.setMeta(ACTIVE_SKILLS_META_KEY, Array.from(pins).sort());
|
|
8476
|
+
}
|
|
8477
|
+
/**
|
|
7922
8478
|
* Filter a `multi_edit` (or single `edit` / `write_file`) input's hunk
|
|
7923
8479
|
* list down to the approved subset, in original order. Used by the
|
|
7924
8480
|
* approval gate to rebind `ctx.input.edits` after a partial decision —
|
|
@@ -8239,6 +8795,80 @@ function AppShell() {
|
|
|
8239
8795
|
const agentRef = useRef(null);
|
|
8240
8796
|
const sessionRef = useRef(null);
|
|
8241
8797
|
/**
|
|
8798
|
+
* Live registry of in-flight tool calls — populated by `tool:before`
|
|
8799
|
+
* (and `child:tool:before`), drained by `tool:after` / `tool:error` /
|
|
8800
|
+
* `tool:cancelled` (and their `child:*` siblings). Drives the
|
|
8801
|
+
* "cancel tool call" picker (see {@link CancelToolModal}).
|
|
8802
|
+
*
|
|
8803
|
+
* Mirrored in `inFlightToolsRef` for callbacks that read the latest
|
|
8804
|
+
* value synchronously without listing `inFlightTools` in their deps —
|
|
8805
|
+
* the picker open path needs both: a state snapshot for the modal's
|
|
8806
|
+
* rendered rows, and the live ref for any subsequent cancel actions
|
|
8807
|
+
* that fire after the snapshot was taken.
|
|
8808
|
+
*
|
|
8809
|
+
* Excludes `mcp:tool:before` / `mcp:tool:after` deliberately for
|
|
8810
|
+
* v1 — `agent.cancelTool(callId)` operates on the unified loop-side
|
|
8811
|
+
* callId registry, which MCP tools also live in (they're dispatched
|
|
8812
|
+
* through the same `executeSingleTool`), so the cancel works for
|
|
8813
|
+
* them too; we just don't surface them in the picker UI yet to
|
|
8814
|
+
* avoid drowning the list with high-cardinality MCP transient calls.
|
|
8815
|
+
*/
|
|
8816
|
+
const [inFlightTools, setInFlightTools] = useState([]);
|
|
8817
|
+
const inFlightToolsRef = useRef([]);
|
|
8818
|
+
inFlightToolsRef.current = inFlightTools;
|
|
8819
|
+
/**
|
|
8820
|
+
* Live registry of running background tasks — populated by
|
|
8821
|
+
* `background:start` (and `child:background:start`), drained by
|
|
8822
|
+
* `background:exit` (and the child sibling). Same shape as
|
|
8823
|
+
* {@link InFlightToolCall} so it merges cleanly into the cancel-tool
|
|
8824
|
+
* picker's snapshot; the `kind: 'task'` discriminator routes the
|
|
8825
|
+
* cancel callback to `agent.killBackgroundTask(taskId)` instead of
|
|
8826
|
+
* `agent.cancelTool(callId)`.
|
|
8827
|
+
*
|
|
8828
|
+
* Tracked separately from `inFlightTools` because the two have
|
|
8829
|
+
* different lifetimes: a tool call is in-flight only while
|
|
8830
|
+
* `tool:before` and `tool:after` straddle, but a background task
|
|
8831
|
+
* survives PAST its spawning tool call — the `shell` body returns
|
|
8832
|
+
* the handle and `tool:after` fires while the task keeps running.
|
|
8833
|
+
* Without this separate registry, `ctrl+k` would never see a task
|
|
8834
|
+
* because the tool entry has already drained.
|
|
8835
|
+
*/
|
|
8836
|
+
const [backgroundTasks, setBackgroundTasks] = useState([]);
|
|
8837
|
+
const backgroundTasksRef = useRef([]);
|
|
8838
|
+
backgroundTasksRef.current = backgroundTasks;
|
|
8839
|
+
/**
|
|
8840
|
+
* Names of currently-active skills, tracked via `skills:activate` /
|
|
8841
|
+
* `skills:deactivate` hooks. Drives the footer's "✦ N skill(s)"
|
|
8842
|
+
* chip — the user's only passive surface for noticing that a skill
|
|
8843
|
+
* (and its `allowed-tools` restrictions) is in effect. Cleared on
|
|
8844
|
+
* session teardown alongside the rest of the per-session live state.
|
|
8845
|
+
*
|
|
8846
|
+
* Stored as a Set rather than an array so dedup is structural (a
|
|
8847
|
+
* runaway `skills:activate` for the same name doesn't inflate the
|
|
8848
|
+
* count). React state is the snapshot we render against; a fresh
|
|
8849
|
+
* Set per update gives React identity-based change detection.
|
|
8850
|
+
*/
|
|
8851
|
+
const [activeSkillNames, setActiveSkillNames] = useState(() => /* @__PURE__ */ new Set());
|
|
8852
|
+
/**
|
|
8853
|
+
* Mirror of {@link activeSkillNames} for synchronous reads in
|
|
8854
|
+
* `onSubmitPrompt`. The submit path runs outside React's render cycle
|
|
8855
|
+
* and needs to pre-activate every user-pinned skill before
|
|
8856
|
+
* `agent.run()` — listing the state in `useCallback`'s deps would
|
|
8857
|
+
* re-bind the handler on every activation change, which would
|
|
8858
|
+
* invalidate the textarea's submit binding on every `/skill` trigger.
|
|
8859
|
+
* The ref keeps the binding stable and the read fresh.
|
|
8860
|
+
*/
|
|
8861
|
+
const activeSkillNamesRef = useRef(activeSkillNames);
|
|
8862
|
+
activeSkillNamesRef.current = activeSkillNames;
|
|
8863
|
+
const registerInFlightTool = useCallback((entry) => {
|
|
8864
|
+
setInFlightTools((prev) => {
|
|
8865
|
+
return [...prev.filter((e) => e.callId !== entry.callId), entry];
|
|
8866
|
+
});
|
|
8867
|
+
}, []);
|
|
8868
|
+
const unregisterInFlightTool = useCallback((callId) => {
|
|
8869
|
+
setInFlightTools((prev) => prev.filter((e) => e.callId !== callId));
|
|
8870
|
+
}, []);
|
|
8871
|
+
/**
|
|
8242
8872
|
* In-flight auto-compaction promise. Held in a ref so the next
|
|
8243
8873
|
* `onSubmitPrompt` invocation can `await` it before calling
|
|
8244
8874
|
* `agent.run()` — this is what prevents a user-submitted prompt from
|
|
@@ -8324,6 +8954,10 @@ function AppShell() {
|
|
|
8324
8954
|
persistThreshold: 0,
|
|
8325
8955
|
persistDir
|
|
8326
8956
|
} : { persistDir };
|
|
8957
|
+
const tasksDir = resolveTasksDir({
|
|
8958
|
+
userDir: dataDir,
|
|
8959
|
+
sessionId: session.id
|
|
8960
|
+
});
|
|
8327
8961
|
const agent = createAgent({
|
|
8328
8962
|
...profile.preset,
|
|
8329
8963
|
...builtInSystem ? { system: builtInSystem } : {},
|
|
@@ -8338,7 +8972,8 @@ function AppShell() {
|
|
|
8338
8972
|
},
|
|
8339
8973
|
behavior: {
|
|
8340
8974
|
...profile.preset.behavior ?? {},
|
|
8341
|
-
...persistBehavior
|
|
8975
|
+
...persistBehavior,
|
|
8976
|
+
tasksDir
|
|
8342
8977
|
},
|
|
8343
8978
|
provider: descriptor.factory(),
|
|
8344
8979
|
session,
|
|
@@ -8491,9 +9126,14 @@ function AppShell() {
|
|
|
8491
9126
|
agent.hooks.hook("stream:thinking", ({ delta, turnId }) => stream.queueStreamDelta("thinking", delta, { turnId }));
|
|
8492
9127
|
agent.hooks.hook("stream:text", ({ delta, turnId }) => stream.queueStreamDelta("markdown", delta, { turnId }));
|
|
8493
9128
|
agent.hooks.hook("tool:before", async ({ callId, name, input, turnId }) => {
|
|
9129
|
+
registerInFlightTool({
|
|
9130
|
+
callId,
|
|
9131
|
+
tool: name,
|
|
9132
|
+
startedAt: Date.now()
|
|
9133
|
+
});
|
|
8494
9134
|
if (pendingAnnotationsRef.current.has(callId)) return;
|
|
8495
9135
|
let priorContent;
|
|
8496
|
-
if (name
|
|
9136
|
+
if (EDIT_TOOL_NAMES.has(name) && agent.handle && typeof input.path === "string") try {
|
|
8497
9137
|
priorContent = await agent.execution.readFile(agent.handle, input.path);
|
|
8498
9138
|
} catch {}
|
|
8499
9139
|
const edit = extractEditPayload(name, input, priorContent);
|
|
@@ -8508,6 +9148,7 @@ function AppShell() {
|
|
|
8508
9148
|
});
|
|
8509
9149
|
});
|
|
8510
9150
|
agent.hooks.hook("tool:after", ({ callId, name, result, turnId }) => {
|
|
9151
|
+
unregisterInFlightTool(callId);
|
|
8511
9152
|
const raw = toolResultText(result);
|
|
8512
9153
|
const text = name === "spawn" ? stripSpawnTokensLine(raw) : raw;
|
|
8513
9154
|
stream.appendImmediate({
|
|
@@ -8518,6 +9159,69 @@ function AppShell() {
|
|
|
8518
9159
|
turnId
|
|
8519
9160
|
});
|
|
8520
9161
|
});
|
|
9162
|
+
agent.hooks.hook("tool:error", ({ callId }) => {
|
|
9163
|
+
unregisterInFlightTool(callId);
|
|
9164
|
+
});
|
|
9165
|
+
agent.hooks.hook("tool:cancelled", ({ callId }) => {
|
|
9166
|
+
unregisterInFlightTool(callId);
|
|
9167
|
+
});
|
|
9168
|
+
const registerBackgroundTask = (ctx) => {
|
|
9169
|
+
setBackgroundTasks((prev) => [...prev, {
|
|
9170
|
+
kind: "task",
|
|
9171
|
+
callId: ctx.taskId,
|
|
9172
|
+
tool: `shell (background): ${previewLine(ctx.command, 60)}`,
|
|
9173
|
+
startedAt: ctx.startedAt,
|
|
9174
|
+
...ctx.childId ? { childId: ctx.childId } : {}
|
|
9175
|
+
}]);
|
|
9176
|
+
};
|
|
9177
|
+
const dropBackgroundTaskAndEmitBanner = (ctx) => {
|
|
9178
|
+
setBackgroundTasks((prev) => prev.filter((t) => t.callId !== ctx.taskId));
|
|
9179
|
+
streamRef.current?.appendImmediate({
|
|
9180
|
+
kind: "task-notification",
|
|
9181
|
+
text: formatTaskSummary(ctx),
|
|
9182
|
+
task: {
|
|
9183
|
+
taskId: ctx.taskId,
|
|
9184
|
+
status: ctx.status,
|
|
9185
|
+
exitCode: ctx.exitCode,
|
|
9186
|
+
outputPath: ctx.outputPath,
|
|
9187
|
+
command: ctx.command,
|
|
9188
|
+
durationMs: ctx.durationMs
|
|
9189
|
+
},
|
|
9190
|
+
...ctx.childId ? { childId: ctx.childId } : {},
|
|
9191
|
+
...typeof ctx.depth === "number" ? { depth: ctx.depth } : {}
|
|
9192
|
+
});
|
|
9193
|
+
};
|
|
9194
|
+
agent.hooks.hook("background:start", (ctx) => registerBackgroundTask(ctx));
|
|
9195
|
+
agent.hooks.hook("background:exit", (ctx) => dropBackgroundTaskAndEmitBanner(ctx));
|
|
9196
|
+
agent.hooks.hook("background:reassign", (ctx) => {
|
|
9197
|
+
setBackgroundTasks((prev) => prev.map((entry) => entry.callId === ctx.taskId ? {
|
|
9198
|
+
kind: "task",
|
|
9199
|
+
callId: entry.callId,
|
|
9200
|
+
tool: entry.tool,
|
|
9201
|
+
startedAt: entry.startedAt
|
|
9202
|
+
} : entry));
|
|
9203
|
+
});
|
|
9204
|
+
agent.hooks.hook("child:background:start", (ctx) => registerBackgroundTask(ctx));
|
|
9205
|
+
agent.hooks.hook("child:background:exit", (ctx) => dropBackgroundTaskAndEmitBanner(ctx));
|
|
9206
|
+
agent.hooks.hook("skills:activate", ({ skill }) => {
|
|
9207
|
+
setActiveSkillNames((prev) => {
|
|
9208
|
+
if (prev.has(skill.name)) return prev;
|
|
9209
|
+
const next = new Set(prev);
|
|
9210
|
+
next.add(skill.name);
|
|
9211
|
+
persistPinnedSkills(session, next);
|
|
9212
|
+
return next;
|
|
9213
|
+
});
|
|
9214
|
+
});
|
|
9215
|
+
agent.hooks.hook("skills:deactivate", ({ skill, reason }) => {
|
|
9216
|
+
if (reason === "run-end") return;
|
|
9217
|
+
setActiveSkillNames((prev) => {
|
|
9218
|
+
if (!prev.has(skill.name)) return prev;
|
|
9219
|
+
const next = new Set(prev);
|
|
9220
|
+
next.delete(skill.name);
|
|
9221
|
+
persistPinnedSkills(session, next);
|
|
9222
|
+
return next;
|
|
9223
|
+
});
|
|
9224
|
+
});
|
|
8521
9225
|
agent.hooks.hook("mcp:tool:after", ({ callId, displayName, result, turnId }) => {
|
|
8522
9226
|
stream.appendImmediate({
|
|
8523
9227
|
kind: "tool-result",
|
|
@@ -8540,7 +9244,7 @@ function AppShell() {
|
|
|
8540
9244
|
stream.flushAndUpdate(finalizeStreamingMarkdown);
|
|
8541
9245
|
});
|
|
8542
9246
|
agent.hooks.hook("spawn:before", ({ id, task, depth }) => {
|
|
8543
|
-
const taskPreview = task
|
|
9247
|
+
const taskPreview = previewLine(task, 80);
|
|
8544
9248
|
stream.appendImmediate({
|
|
8545
9249
|
kind: "spawn-start",
|
|
8546
9250
|
text: taskPreview,
|
|
@@ -8580,6 +9284,12 @@ function AppShell() {
|
|
|
8580
9284
|
});
|
|
8581
9285
|
});
|
|
8582
9286
|
agent.hooks.hook("child:tool:before", ({ callId, name, input, childId, depth, turnId, priorContent }) => {
|
|
9287
|
+
registerInFlightTool({
|
|
9288
|
+
callId,
|
|
9289
|
+
tool: name,
|
|
9290
|
+
startedAt: Date.now(),
|
|
9291
|
+
childId
|
|
9292
|
+
});
|
|
8583
9293
|
if (pendingAnnotationsRef.current.has(callId)) return;
|
|
8584
9294
|
const edit = extractEditPayload(name, input, priorContent);
|
|
8585
9295
|
stream.appendImmediate({
|
|
@@ -8595,6 +9305,7 @@ function AppShell() {
|
|
|
8595
9305
|
});
|
|
8596
9306
|
});
|
|
8597
9307
|
agent.hooks.hook("child:tool:after", ({ callId, name, result, childId, depth, turnId }) => {
|
|
9308
|
+
unregisterInFlightTool(callId);
|
|
8598
9309
|
stream.appendImmediate({
|
|
8599
9310
|
kind: "tool-result",
|
|
8600
9311
|
text: toolResultText(result),
|
|
@@ -8605,6 +9316,12 @@ function AppShell() {
|
|
|
8605
9316
|
turnId
|
|
8606
9317
|
});
|
|
8607
9318
|
});
|
|
9319
|
+
agent.hooks.hook("child:tool:error", ({ callId }) => {
|
|
9320
|
+
unregisterInFlightTool(callId);
|
|
9321
|
+
});
|
|
9322
|
+
agent.hooks.hook("child:tool:cancelled", ({ callId }) => {
|
|
9323
|
+
unregisterInFlightTool(callId);
|
|
9324
|
+
});
|
|
8608
9325
|
agent.hooks.hook("child:stream:end", ({ childId }) => {
|
|
8609
9326
|
stream.flushAndUpdate((prev) => finalizeStreamingMarkdownForOwner(prev, childId));
|
|
8610
9327
|
});
|
|
@@ -8621,7 +9338,9 @@ function AppShell() {
|
|
|
8621
9338
|
config.prefix,
|
|
8622
9339
|
interactions,
|
|
8623
9340
|
dataDir,
|
|
8624
|
-
mcpCredentialStore
|
|
9341
|
+
mcpCredentialStore,
|
|
9342
|
+
registerInFlightTool,
|
|
9343
|
+
unregisterInFlightTool
|
|
8625
9344
|
]);
|
|
8626
9345
|
const refreshSessions = useCallback(async () => {
|
|
8627
9346
|
const list = await listSessionMeta(store, settings.showAllProjects ? void 0 : { projectRoot: projectDir });
|
|
@@ -8662,6 +9381,9 @@ function AppShell() {
|
|
|
8662
9381
|
runningRef.current = false;
|
|
8663
9382
|
sessionSafelistRef.current.clear();
|
|
8664
9383
|
pendingAnnotationsRef.current.clear();
|
|
9384
|
+
setInFlightTools([]);
|
|
9385
|
+
setBackgroundTasks([]);
|
|
9386
|
+
setActiveSkillNames(/* @__PURE__ */ new Set());
|
|
8665
9387
|
}, [
|
|
8666
9388
|
stream,
|
|
8667
9389
|
denyAll,
|
|
@@ -8739,6 +9461,7 @@ function AppShell() {
|
|
|
8739
9461
|
});
|
|
8740
9462
|
sessionRef.current = session;
|
|
8741
9463
|
agentRef.current = buildAgent(session, key);
|
|
9464
|
+
setActiveSkillNames(readPinnedSkills(session.metadata["zidane.activeSkills"]));
|
|
8742
9465
|
setEvents(eventsFromTurns(session.turns, session.runs));
|
|
8743
9466
|
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
8744
9467
|
setLastInputTokens(replayedTokens);
|
|
@@ -9094,7 +9817,8 @@ function AppShell() {
|
|
|
9094
9817
|
...refSpans.length > 0 ? { refs: refSpans } : {}
|
|
9095
9818
|
});
|
|
9096
9819
|
if (autoCompactInFlightRef.current) await autoCompactInFlightRef.current.catch(() => {});
|
|
9097
|
-
const
|
|
9820
|
+
const newRefs = uniqueSkillNamesFromReferences(references);
|
|
9821
|
+
const skillNames = Array.from(new Set([...activeSkillNamesRef.current, ...newRefs]));
|
|
9098
9822
|
for (const name of skillNames) try {
|
|
9099
9823
|
await agent.activateSkill(name);
|
|
9100
9824
|
} catch (err) {
|
|
@@ -9332,6 +10056,36 @@ function AppShell() {
|
|
|
9332
10056
|
return nextTurns.some((t) => t.id === prev) ? prev : null;
|
|
9333
10057
|
});
|
|
9334
10058
|
}, []);
|
|
10059
|
+
const onEditTurn = useCallback(async (turnId, newText) => {
|
|
10060
|
+
const session = sessionRef.current;
|
|
10061
|
+
if (!session) return;
|
|
10062
|
+
const turn = session.turns.find((t) => t.id === turnId);
|
|
10063
|
+
if (!turn) return;
|
|
10064
|
+
if (!turn.content.some((b) => b.type === "text")) return;
|
|
10065
|
+
const nonTextBlocks = turn.content.filter((b) => b.type !== "text");
|
|
10066
|
+
const firstTextIdx = turn.content.findIndex((b) => b.type === "text");
|
|
10067
|
+
const updatedContent = [...nonTextBlocks];
|
|
10068
|
+
if (newText.trim()) updatedContent.splice(firstTextIdx >= 0 ? Math.min(firstTextIdx, updatedContent.length) : 0, 0, {
|
|
10069
|
+
type: "text",
|
|
10070
|
+
text: newText
|
|
10071
|
+
});
|
|
10072
|
+
turn.content = updatedContent;
|
|
10073
|
+
session.setTurns([...session.turns]);
|
|
10074
|
+
try {
|
|
10075
|
+
await session.save();
|
|
10076
|
+
} catch (err) {
|
|
10077
|
+
debugLog("edit: save failed", err);
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
10080
|
+
setEvents(eventsFromTurns(session.turns, session.runs));
|
|
10081
|
+
const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs);
|
|
10082
|
+
setLastInputTokens(replayedTokens);
|
|
10083
|
+
lastInputTokensRef.current = replayedTokens;
|
|
10084
|
+
setCurrentSession((prev) => prev ? {
|
|
10085
|
+
...prev,
|
|
10086
|
+
updatedAt: Date.now()
|
|
10087
|
+
} : prev);
|
|
10088
|
+
}, []);
|
|
9335
10089
|
/**
|
|
9336
10090
|
* Identity of the session row the user has focused on the sessions
|
|
9337
10091
|
* screen — single source of truth. `SessionsScreen` is rendered fully
|
|
@@ -9357,6 +10111,10 @@ function AppShell() {
|
|
|
9357
10111
|
userDir: dataDir,
|
|
9358
10112
|
sessionId: id
|
|
9359
10113
|
}));
|
|
10114
|
+
cleanupPersistedSession(resolveTasksDir({
|
|
10115
|
+
userDir: dataDir,
|
|
10116
|
+
sessionId: id
|
|
10117
|
+
}));
|
|
9360
10118
|
const wasCurrent = id === currentSession?.id;
|
|
9361
10119
|
if (wasCurrent) {
|
|
9362
10120
|
await teardown();
|
|
@@ -9628,7 +10386,8 @@ function AppShell() {
|
|
|
9628
10386
|
total: turnIds.length,
|
|
9629
10387
|
actions: {
|
|
9630
10388
|
onFork: onForkTurn,
|
|
9631
|
-
onDelete: onDeleteTurn
|
|
10389
|
+
onDelete: onDeleteTurn,
|
|
10390
|
+
onEdit: onEditTurn
|
|
9632
10391
|
},
|
|
9633
10392
|
keybindings
|
|
9634
10393
|
}));
|
|
@@ -9638,6 +10397,7 @@ function AppShell() {
|
|
|
9638
10397
|
turnIds,
|
|
9639
10398
|
onForkTurn,
|
|
9640
10399
|
onDeleteTurn,
|
|
10400
|
+
onEditTurn,
|
|
9641
10401
|
keybindings
|
|
9642
10402
|
]);
|
|
9643
10403
|
useKeyboard((key) => {
|
|
@@ -9746,6 +10506,29 @@ function AppShell() {
|
|
|
9746
10506
|
onCycleAgent();
|
|
9747
10507
|
return;
|
|
9748
10508
|
}
|
|
10509
|
+
if (matchesBinding(key, keybindings.cancelToolCall) && screen === "chat" && !pendingApproval) {
|
|
10510
|
+
const tools = inFlightToolsRef.current.filter((entry) => entry.childId === void 0);
|
|
10511
|
+
const tasks = backgroundTasksRef.current.filter((entry) => entry.childId === void 0);
|
|
10512
|
+
const snapshot = [...tools, ...tasks];
|
|
10513
|
+
if (snapshot.length === 0) return;
|
|
10514
|
+
modal.open(/* @__PURE__ */ jsx(CancelToolModal, {
|
|
10515
|
+
inFlight: snapshot,
|
|
10516
|
+
onCancel: (entry, reason) => {
|
|
10517
|
+
const agent = agentRef.current;
|
|
10518
|
+
if (!agent) return false;
|
|
10519
|
+
if (entry.kind === "task") return agent.killBackgroundTask(entry.callId);
|
|
10520
|
+
return agent.cancelTool(entry.callId, reason);
|
|
10521
|
+
},
|
|
10522
|
+
onCancelAll: () => {
|
|
10523
|
+
const agent = agentRef.current;
|
|
10524
|
+
if (!agent) return;
|
|
10525
|
+
for (const entry of snapshot) if (entry.kind === "task") agent.killBackgroundTask(entry.callId);
|
|
10526
|
+
else agent.cancelTool(entry.callId, "user-cancelled-all");
|
|
10527
|
+
},
|
|
10528
|
+
onClose: () => modal.close()
|
|
10529
|
+
}));
|
|
10530
|
+
return;
|
|
10531
|
+
}
|
|
9749
10532
|
if (key.name !== "escape") return;
|
|
9750
10533
|
if (busy || pendingApproval) return onAbort();
|
|
9751
10534
|
if (popupOpenRef.current) return;
|
|
@@ -9777,7 +10560,10 @@ function AppShell() {
|
|
|
9777
10560
|
effortKeyColor: COLOR.warn,
|
|
9778
10561
|
agentLabel: pickedAgent.label,
|
|
9779
10562
|
agentColor: accentColor(pickedAgent.accent, COLOR),
|
|
9780
|
-
keybindings
|
|
10563
|
+
keybindings,
|
|
10564
|
+
inFlightToolCount: inFlightTools.reduce((n, entry) => entry.childId === void 0 ? n + 1 : n, 0) + backgroundTasks.reduce((n, entry) => entry.childId === void 0 ? n + 1 : n, 0),
|
|
10565
|
+
activeSkillCount: activeSkillNames.size,
|
|
10566
|
+
skillsChipColor: COLOR.brand
|
|
9781
10567
|
}), [
|
|
9782
10568
|
screen,
|
|
9783
10569
|
busy,
|
|
@@ -9790,7 +10576,10 @@ function AppShell() {
|
|
|
9790
10576
|
pickedAgent,
|
|
9791
10577
|
COLOR,
|
|
9792
10578
|
modelHasReasoning,
|
|
9793
|
-
keybindings
|
|
10579
|
+
keybindings,
|
|
10580
|
+
inFlightTools,
|
|
10581
|
+
backgroundTasks,
|
|
10582
|
+
activeSkillNames
|
|
9794
10583
|
]);
|
|
9795
10584
|
const queuedMessagePreviews = useMemo(() => messageQueue.map((m) => ({
|
|
9796
10585
|
text: m.prompt,
|
|
@@ -9909,7 +10698,7 @@ function effortForModel(descriptor, modelId, remembered) {
|
|
|
9909
10698
|
* secondary `/n` chord with the current effort label, surfacing the
|
|
9910
10699
|
* effort picker as a discoverable, in-place affordance.
|
|
9911
10700
|
*/
|
|
9912
|
-
function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInteractionResumed, currentSession, hasMultipleAgents, modelLabel, modelColor, effortLabel, effortColor, effortKeyColor, agentLabel, agentColor, keybindings }) {
|
|
10701
|
+
function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInteractionResumed, currentSession, hasMultipleAgents, modelLabel, modelColor, effortLabel, effortColor, effortKeyColor, agentLabel, agentColor, keybindings, inFlightToolCount, activeSkillCount, skillsChipColor }) {
|
|
9913
10702
|
if (pending) return [
|
|
9914
10703
|
{
|
|
9915
10704
|
key: "↑↓",
|
|
@@ -9952,10 +10741,18 @@ function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInte
|
|
|
9952
10741
|
label: "leave for later"
|
|
9953
10742
|
}
|
|
9954
10743
|
];
|
|
9955
|
-
if (busy)
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
10744
|
+
if (busy) {
|
|
10745
|
+
const baseBusyHints = [];
|
|
10746
|
+
if (inFlightToolCount > 0) baseBusyHints.push({
|
|
10747
|
+
key: keybindings.cancelToolCall,
|
|
10748
|
+
label: inFlightToolCount === 1 ? "cancel" : `cancel (${inFlightToolCount})`
|
|
10749
|
+
});
|
|
10750
|
+
baseBusyHints.push({
|
|
10751
|
+
key: "esc",
|
|
10752
|
+
label: "abort"
|
|
10753
|
+
});
|
|
10754
|
+
return baseBusyHints;
|
|
10755
|
+
}
|
|
9959
10756
|
if (screen === "auth") return [
|
|
9960
10757
|
{
|
|
9961
10758
|
key: "↑↓",
|
|
@@ -10003,6 +10800,16 @@ function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInte
|
|
|
10003
10800
|
labelColor: effortColor
|
|
10004
10801
|
} } : {}
|
|
10005
10802
|
} : null;
|
|
10803
|
+
const skillsChip = activeSkillCount > 0 ? {
|
|
10804
|
+
key: "✦",
|
|
10805
|
+
keyColor: skillsChipColor,
|
|
10806
|
+
label: activeSkillCount === 1 ? "1 skill" : `${activeSkillCount} skills`,
|
|
10807
|
+
labelColor: skillsChipColor
|
|
10808
|
+
} : null;
|
|
10809
|
+
const cancelTaskChip = inFlightToolCount > 0 ? {
|
|
10810
|
+
key: keybindings.cancelToolCall,
|
|
10811
|
+
label: inFlightToolCount === 1 ? "cancel task" : `cancel task (${inFlightToolCount})`
|
|
10812
|
+
} : null;
|
|
10006
10813
|
return [
|
|
10007
10814
|
...hasMultipleAgents ? [{
|
|
10008
10815
|
key: keybindings.cycleAgent,
|
|
@@ -10010,6 +10817,8 @@ function buildHints({ screen, busy, pending, pendingInteractionLive, pendingInte
|
|
|
10010
10817
|
labelColor: agentColor
|
|
10011
10818
|
}] : [],
|
|
10012
10819
|
...modelHint ? [modelHint] : [],
|
|
10820
|
+
...skillsChip ? [skillsChip] : [],
|
|
10821
|
+
...cancelTaskChip ? [cancelTaskChip] : [],
|
|
10013
10822
|
...currentSession ? [{
|
|
10014
10823
|
key: keybindings.openSessionDetails,
|
|
10015
10824
|
label: "session"
|