zidane 5.1.0 → 5.1.2

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.
@@ -14,7 +14,6 @@ import { anthropicOAuthProvider, openaiCodexOAuthProvider } from "@mariozechner/
14
14
  import { getModel, getModels } from "@mariozechner/pi-ai";
15
15
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
16
16
  import { jsx } from "react/jsx-runtime";
17
- import { jsx as jsx$1 } from "@opentui/react/jsx-runtime";
18
17
  //#region src/chat/agent-prompt.ts
19
18
  /**
20
19
  * Agent system-prompt fragments — composable doctrine for built-in profiles.
@@ -223,6 +222,21 @@ function joinPrompt(parts) {
223
222
  }
224
223
  //#endregion
225
224
  //#region src/chat/agents.ts
225
+ /**
226
+ * Resolve a profile's `accent` token to a concrete hex color via the
227
+ * caller's color palette. Renderer-agnostic — accepts the four-role
228
+ * subset of `ThemeColors` that's relevant to accent surfaces, so this
229
+ * helper composes against either `useColors()` (TUI) or a CSS-variable
230
+ * lookup (GUI). Omitted / unknown tokens fall back to `accent`.
231
+ */
232
+ function accentColor(accent, COLOR) {
233
+ switch (accent) {
234
+ case "brand": return COLOR.brand;
235
+ case "warn": return COLOR.warn;
236
+ case "model": return COLOR.model;
237
+ default: return COLOR.accent;
238
+ }
239
+ }
226
240
  /** Read-only tool slice shared by the Plan profile and any host-built read-only variant. */
227
241
  const READ_ONLY_TOOLS = {
228
242
  readFile: readFile$1,
@@ -2176,30 +2190,200 @@ function toolResultText(output) {
2176
2190
  function stripSpawnTokensLine(text) {
2177
2191
  return text.replace(/(^\[sub-agent [^\n]+\n)Tokens:[^\n]*\n?/gm, "$1");
2178
2192
  }
2193
+ /** Tools whose `tool-result` event is suppressed when `showEditDiffs` is on. */
2194
+ const EDIT_TOOL_NAMES = new Set([
2195
+ "edit",
2196
+ "multi_edit",
2197
+ "write_file"
2198
+ ]);
2199
+ /**
2200
+ * Recognize a tool-result body as a failure. The three edit tools all
2201
+ * return short, deterministic error prefixes on the failure path:
2202
+ * - `edit` → "Edit error: …"
2203
+ * - `multi_edit` → "multi_edit error: …"
2204
+ * - `write_file` → succeeds on permission errors only via exception →
2205
+ * the loop wraps as "Tool failed: …" so we match that prefix too.
2206
+ *
2207
+ * Exported for unit-testability of the visibility matrix.
2208
+ */
2209
+ function isEditErrorResult(text) {
2210
+ return text.startsWith("Edit error:") || text.startsWith("multi_edit error:") || text.startsWith("Tool failed:");
2211
+ }
2212
+ /**
2213
+ * Per-event visibility — filters honor user toggles and the
2214
+ * `hideSubagentOutput` setting. When subagent output is hidden:
2215
+ * - Child-agent events are filtered down to the `spawn-start` /
2216
+ * `spawn-end` markers so the user still sees "🌱 working… 🌳 done".
2217
+ * - The parent's `tool-result` for `spawn` is hidden too. Its body
2218
+ * duplicates `spawn-end`'s stats line *and* the parent's next
2219
+ * markdown turn; showing it again produces an extra
2220
+ * `┃ [sub-agent child-1] Completed …` block users just want gone.
2221
+ *
2222
+ * Renderer-agnostic — returns plain `boolean` so TUI / GUI consumers
2223
+ * can filter events identically.
2224
+ */
2225
+ function isVisible(event, settings) {
2226
+ if (settings.hideSubagentOutput) {
2227
+ if ((event.depth ?? 0) > 0) return event.kind === "spawn-start" || event.kind === "spawn-end";
2228
+ if (event.kind === "tool-result" && event.tool === "spawn") return false;
2229
+ }
2230
+ if (settings.showEditDiffs && event.kind === "tool-result" && event.tool && EDIT_TOOL_NAMES.has(event.tool) && !isEditErrorResult(event.text)) return false;
2231
+ switch (event.kind) {
2232
+ case "thinking": return settings.showThinking;
2233
+ case "tool": return settings.toolCallDisplay !== "hidden";
2234
+ case "tool-result": return settings.showToolResults;
2235
+ default: return true;
2236
+ }
2237
+ }
2238
+ /**
2239
+ * Default top-margin per kind (in rows). Spacing intent:
2240
+ * - `info` / `markdown` / `tool` / `error` / `spawn-start` open a new
2241
+ * block, so they each get one row of breathing room above.
2242
+ * - `thinking` / `tool-result` / `spawn-end` continue the previous
2243
+ * block and stay flush.
2244
+ *
2245
+ * Context-aware overrides live in {@link marginTopFor}.
2246
+ */
2247
+ const MARGIN_TOP = {
2248
+ "separator": 0,
2249
+ "user-prompt": 1,
2250
+ "info": 1,
2251
+ "thinking": 0,
2252
+ "tool": 1,
2253
+ "tool-result": 0,
2254
+ "error": 1,
2255
+ "markdown": 1,
2256
+ "spawn-start": 1,
2257
+ "spawn-end": 0,
2258
+ "compact-summary": 1
2259
+ };
2260
+ const TOOL_KINDS = new Set(["tool", "tool-result"]);
2261
+ /**
2262
+ * Resolve the top margin (in rows) for an event given the one rendered
2263
+ * just before it. Context-aware rules:
2264
+ *
2265
+ * - A `tool` / `tool-result` event right after another
2266
+ * `tool` / `tool-result` collapses to a tight list — call→result
2267
+ * pairs and back-to-back calls read as one logical block.
2268
+ * - A parent-level event (`depth === 0`) right after a subagent event
2269
+ * (`depth > 0`) collapses too. The subagent's `🌳` end marker
2270
+ * already provides the separation; adding the event's default
2271
+ * `marginTop` on top would produce the visible "line jump" between
2272
+ * a subagent's outcome and the parent's follow-up.
2273
+ *
2274
+ * Renderer-agnostic — TUI uses it as Yoga `marginTop`; a CSS host can
2275
+ * use the same number as the row's top margin in `em` / `rem`.
2276
+ */
2277
+ function marginTopFor(event, previous) {
2278
+ if (TOOL_KINDS.has(event.kind) && previous && TOOL_KINDS.has(previous.kind)) return 0;
2279
+ const eventDepth = event.depth ?? 0;
2280
+ const previousDepth = previous?.depth ?? 0;
2281
+ if (eventDepth === 0 && previousDepth > 0) return 0;
2282
+ return MARGIN_TOP[event.kind] ?? 0;
2283
+ }
2284
+ /**
2285
+ * Build the `resultTurnId → owningAssistantTurnId` map used by the select-
2286
+ * turn mode to coalesce a tool-call's surrounding turns into ONE navigation
2287
+ * stop.
2288
+ *
2289
+ * Protocol shape: every `tool_call` block in an assistant turn is closed by
2290
+ * a matching `tool_result` block in the *next* user turn (the agent loop's
2291
+ * history validator depends on this). When the next user turn's only events
2292
+ * are `tool-result`s — i.e. it's pure plumbing for the prior assistant
2293
+ * turn — we map it back to that assistant turn here. The select-turn nav
2294
+ * index ({@link selectableTurnIds}) skips owned turns, and the renderer's
2295
+ * highlight gate ({@link isTurnHighlighted}) extends the selection accent
2296
+ * from the assistant turn to the events of any turn it owns. Net effect:
2297
+ *
2298
+ * - Navigation never lands the cursor on a result-only turn whose own
2299
+ * events may be hidden by `showToolResults: false` — the cursor
2300
+ * wouldn't be visible.
2301
+ * - Selecting an assistant turn highlights the call AND its result as
2302
+ * one unit, matching the user's mental model of "one message".
2303
+ *
2304
+ * Owner-lookup is conservative: result-only turns with no matching prior
2305
+ * assistant turn (orphaned — usually because the parent was deleted)
2306
+ * stay selectable so the user can act on them via the turn-details modal.
2307
+ *
2308
+ * Subagent (`childId` set) events are ignored — they live in a separate
2309
+ * conversation tree.
2310
+ */
2311
+ function turnSelectionOwnership(events) {
2312
+ const orderedTurnIds = [];
2313
+ const eventKindsByTurn = /* @__PURE__ */ new Map();
2314
+ for (const e of events) {
2315
+ if (!e.turnId) continue;
2316
+ if (e.childId) continue;
2317
+ if (!eventKindsByTurn.has(e.turnId)) {
2318
+ orderedTurnIds.push(e.turnId);
2319
+ eventKindsByTurn.set(e.turnId, []);
2320
+ }
2321
+ eventKindsByTurn.get(e.turnId).push(e.kind);
2322
+ }
2323
+ const ownership = /* @__PURE__ */ new Map();
2324
+ let lastToolEmitterTurnId = null;
2325
+ for (const tid of orderedTurnIds) {
2326
+ const kinds = eventKindsByTurn.get(tid);
2327
+ if (kinds.length > 0 && kinds.every((k) => k === "tool-result")) {
2328
+ if (lastToolEmitterTurnId) ownership.set(tid, lastToolEmitterTurnId);
2329
+ continue;
2330
+ }
2331
+ if (kinds.includes("tool")) lastToolEmitterTurnId = tid;
2332
+ }
2333
+ return ownership;
2334
+ }
2335
+ /**
2336
+ * Render-time check: should `event` paint with the selection accent?
2337
+ *
2338
+ * `true` when the event's own turn is selected, OR when the selected turn
2339
+ * `owns` the event's turn via {@link turnSelectionOwnership} (the call and
2340
+ * its tool-result rows highlight together). `false` when nothing is
2341
+ * selected or the relationship doesn't apply.
2342
+ *
2343
+ * Pure. Renderer-agnostic — the TUI's `<Transcript>` uses it; a GUI's
2344
+ * equivalent walks the same rule.
2345
+ */
2346
+ function isTurnHighlighted(event, selectedTurnId, ownership) {
2347
+ if (selectedTurnId === null || !event.turnId) return false;
2348
+ if (event.turnId === selectedTurnId) return true;
2349
+ return ownership.get(event.turnId) === selectedTurnId;
2350
+ }
2179
2351
  /**
2180
2352
  * Deduplicated, in-order list of **parent-conversation** turn ids that appear
2181
2353
  * in a rendered transcript — the navigation index for the TUI's select-turn
2182
- * mode. Subagent (`childId` set) events are deliberately skipped:
2183
- *
2184
- * - With `hideSubagentOutput: true` (default), child events are filtered
2185
- * out of the transcript by `isVisible`, so allowing the user to "select"
2186
- * them would highlight nothing and scroll to nowhere.
2187
- * - With `hideSubagentOutput: false`, child events are visible but they're
2188
- * nested execution detail; the user's mental model of a "message" is
2189
- * the conversational exchange, not each spawn turn.
2354
+ * mode. Three classes of turns are deliberately skipped:
2355
+ *
2356
+ * - **Subagent turns** (`childId` set). Nested execution detail; the
2357
+ * user's mental model of a "message" is the conversational exchange,
2358
+ * not each spawn turn. Also filtered out by `isVisible` under
2359
+ * `hideSubagentOutput: true` selecting them would highlight nothing.
2360
+ * - **Result-only turns** see {@link turnSelectionOwnership}. These get
2361
+ * coalesced into the assistant turn that emitted their tool_calls.
2362
+ * - **Settings-hidden turns** (when `settings` is supplied). A turn whose
2363
+ * every event fails {@link isVisible} would render no rows — landing
2364
+ * the cursor there hides it from the user entirely. The check is opt-
2365
+ * in so SDK callers without a Settings object keep the legacy
2366
+ * "everything visible" behavior.
2190
2367
  *
2191
2368
  * Synthetic events (separator, spawn-start, spawn-end) have no `turnId` and
2192
- * are skipped naturally. Pure function over the event stream — no settings
2193
- * dependency, no host concerns — so it composes the same in TUI / SDK
2194
- * consumers.
2195
- */
2196
- function selectableTurnIds(events) {
2369
+ * are skipped naturally.
2370
+ */
2371
+ function selectableTurnIds(events, settings) {
2372
+ const ownership = turnSelectionOwnership(events);
2373
+ const visibleCount = settings ? /* @__PURE__ */ new Map() : null;
2374
+ if (settings && visibleCount) for (const e of events) {
2375
+ if (!e.turnId || e.childId) continue;
2376
+ if (!isVisible(e, settings)) continue;
2377
+ visibleCount.set(e.turnId, (visibleCount.get(e.turnId) ?? 0) + 1);
2378
+ }
2197
2379
  const seen = /* @__PURE__ */ new Set();
2198
2380
  const ordered = [];
2199
2381
  for (const e of events) {
2200
2382
  if (!e.turnId) continue;
2201
2383
  if (e.childId) continue;
2202
2384
  if (seen.has(e.turnId)) continue;
2385
+ if (ownership.has(e.turnId)) continue;
2386
+ if (visibleCount && (visibleCount.get(e.turnId) ?? 0) === 0) continue;
2203
2387
  seen.add(e.turnId);
2204
2388
  ordered.push(e.turnId);
2205
2389
  }
@@ -2412,9 +2596,24 @@ function makeModelsResolver(registry) {
2412
2596
  };
2413
2597
  }
2414
2598
  function resolveResumeProvider(state, providers, storageDir) {
2415
- if (!state.lastProvider) return null;
2416
- if (!providers[state.lastProvider]) return null;
2417
- return detectAuth(storageDir, providers).find((p) => p.key === state.lastProvider && p.available) ?? null;
2599
+ if (!state.lastProvider) {
2600
+ if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/chat] resume: no \`lastProvider\` in state — auth screen will show. (Has \`${storageDir}/state.json\` been deleted or never written?)\n`);
2601
+ return null;
2602
+ }
2603
+ if (!providers[state.lastProvider]) {
2604
+ if (process.env.ZIDANE_DEBUG) {
2605
+ const known = Object.keys(providers).join(", ") || "<none>";
2606
+ process.stderr.write(`[zidane/chat] resume: \`state.lastProvider="${state.lastProvider}"\` is not in the registry. Known providers: ${known}.\n`);
2607
+ }
2608
+ return null;
2609
+ }
2610
+ const detected = detectAuth(storageDir, providers);
2611
+ const match = detected.find((p) => p.key === state.lastProvider && p.available);
2612
+ if (!match && process.env.ZIDANE_DEBUG) {
2613
+ const entry = detected.find((p) => p.key === state.lastProvider);
2614
+ process.stderr.write(`[zidane/chat] resume: \`${state.lastProvider}\` is in the registry but has no available auth method (apikey / env / oauth). Detection: ${entry ? JSON.stringify(entry.methods) : "<provider missing from detection>"}.\n`);
2615
+ }
2616
+ return match ?? null;
2418
2617
  }
2419
2618
  function pickInitial(auth, providers, state) {
2420
2619
  const descriptor = providers[auth.key];
@@ -3098,8 +3297,23 @@ const DEFAULT_SETTINGS = {
3098
3297
  persistToolResults: true,
3099
3298
  autoCompact: true,
3100
3299
  autoCompactThreshold: .8,
3101
- showEditDiffs: true
3300
+ showEditDiffs: true,
3301
+ targetFps: 60
3102
3302
  };
3303
+ /**
3304
+ * Hard-clamp a `targetFps` value to a safe range before handing it to
3305
+ * the renderer. Defends against an out-of-band edit to `state.json`
3306
+ * (`0`, negative, `NaN`, absurd values) silently pegging the loop or
3307
+ * tripping the frame-time math. The settings modal only exposes
3308
+ * `30 / 60 / 120` so the typical path never trips the clamp; this
3309
+ * guard exists for the "user hand-edited state.json" case.
3310
+ */
3311
+ const MIN_TARGET_FPS = 1;
3312
+ const MAX_TARGET_FPS = 240;
3313
+ function clampFps(value) {
3314
+ if (!Number.isFinite(value) || value <= 0) return DEFAULT_SETTINGS.targetFps;
3315
+ return Math.min(MAX_TARGET_FPS, Math.max(MIN_TARGET_FPS, Math.round(value)));
3316
+ }
3103
3317
  const SettingsContext = createContext(null);
3104
3318
  function SettingsProvider({ initial, onChange, children }) {
3105
3319
  const [settings, setSettings] = useState(initial);
@@ -3242,6 +3456,25 @@ const SETTINGS_CHOICES = [
3242
3456
  value: t.id,
3243
3457
  label: t.label
3244
3458
  }))
3459
+ },
3460
+ {
3461
+ key: "targetFps",
3462
+ label: "Renderer fps",
3463
+ description: "30 saves CPU · 60 recommended · 120 for ProMotion + modern terminals",
3464
+ options: [
3465
+ {
3466
+ value: 30,
3467
+ label: "30 fps"
3468
+ },
3469
+ {
3470
+ value: 60,
3471
+ label: "60 fps"
3472
+ },
3473
+ {
3474
+ value: 120,
3475
+ label: "120 fps"
3476
+ }
3477
+ ]
3245
3478
  }
3246
3479
  ];
3247
3480
  //#endregion
@@ -4173,9 +4406,9 @@ function McpAuthProvider({ children }) {
4173
4406
  const dispatch = useCallback((event) => {
4174
4407
  setState((prev) => reduceMcpAuth(prev, event));
4175
4408
  }, []);
4176
- return /* @__PURE__ */ jsx$1(StateContext.Provider, {
4409
+ return /* @__PURE__ */ jsx(StateContext.Provider, {
4177
4410
  value: state,
4178
- children: /* @__PURE__ */ jsx$1(DispatchContext.Provider, {
4411
+ children: /* @__PURE__ */ jsx(DispatchContext.Provider, {
4179
4412
  value: dispatch,
4180
4413
  children
4181
4414
  })
@@ -5256,16 +5489,18 @@ function buildSkillsConfig(opts) {
5256
5489
  //#endregion
5257
5490
  //#region src/chat/streaming.ts
5258
5491
  /**
5259
- * Streaming flush cadence. Set to roughly half the renderer's 30fps target
5260
- * (~16fps deltas) so OpenTUI's `MarkdownRenderable` has a full paint frame
5261
- * between content updates. The trailing markdown block is `destroyRecursively`
5262
- * + recreated on every content change while `streaming: true` (parser
5263
- * marks the last 2 blocks unstable); landing those mutations one-per-frame
5264
- * paints partial layout states, which renders as flicker. Pacing at ~60ms
5265
- * gives the renderer time to settle without sacrificing live feel — tokens
5266
- * still appear faster than the user can read.
5267
- */
5268
- const FLUSH_INTERVAL_MS = 60;
5492
+ * Streaming flush cadence. Calibrated to ~2 paint frames at the TUI's
5493
+ * 60fps renderer target (`src/tui/index.tsx` `DEFAULT_TARGET_FPS`),
5494
+ * giving OpenTUI's `MarkdownRenderable` one full frame to settle before
5495
+ * the next content delta lands. The trailing markdown block is
5496
+ * `destroyRecursively` + recreated on every content change while
5497
+ * `streaming: true` (parser marks the last 2 blocks unstable); landing
5498
+ * those mutations one-per-frame paints partial layout states, which reads
5499
+ * as flicker. ~32ms keeps deltas visible at ~30 updates/sec — well above
5500
+ * the rate at which any human eye can resolve token-by-token text —
5501
+ * without crowding the renderer's frame budget.
5502
+ */
5503
+ const FLUSH_INTERVAL_MS = 32;
5269
5504
  const PARENT_OWNER = "parent";
5270
5505
  function emptyBucket(owner, depth) {
5271
5506
  return {
@@ -5466,6 +5701,254 @@ function useSyntaxStyles() {
5466
5701
  return useContext(ThemeContext).syntax;
5467
5702
  }
5468
5703
  //#endregion
5704
+ //#region src/chat/tool-formatters.ts
5705
+ const TOOL_DISPLAY = {
5706
+ read_file: {
5707
+ displayName: "Read",
5708
+ format: (input) => {
5709
+ const path = stringField(input, "path");
5710
+ if (!path) return null;
5711
+ const meta = [];
5712
+ const offset = numberField(input, "offset");
5713
+ const limit = numberField(input, "limit");
5714
+ if (offset !== void 0 && limit !== void 0 && limit > 0) meta.push(`L${offset}–${offset + limit - 1}`);
5715
+ else if (offset !== void 0) meta.push(`from L${offset}`);
5716
+ else if (limit !== void 0 && limit > 0) meta.push(`${limit} lines`);
5717
+ return {
5718
+ target: path,
5719
+ meta
5720
+ };
5721
+ }
5722
+ },
5723
+ list_files: {
5724
+ displayName: "List",
5725
+ format: (input) => {
5726
+ return { target: stringField(input, "path") ?? "." };
5727
+ }
5728
+ },
5729
+ glob: {
5730
+ displayName: "Glob",
5731
+ format: (input) => {
5732
+ const pattern = stringField(input, "pattern");
5733
+ if (!pattern) return null;
5734
+ const meta = [];
5735
+ const limit = numberField(input, "limit");
5736
+ if (limit !== void 0) meta.push(`limit ${limit}`);
5737
+ return {
5738
+ target: pattern,
5739
+ meta
5740
+ };
5741
+ }
5742
+ },
5743
+ grep: {
5744
+ displayName: "Grep",
5745
+ format: (input) => {
5746
+ const pattern = stringField(input, "pattern");
5747
+ if (!pattern) return null;
5748
+ const target = `/${pattern}/`;
5749
+ const meta = [];
5750
+ const path = stringField(input, "path");
5751
+ if (path && path !== ".") meta.push(`in ${path}`);
5752
+ const glob = stringField(input, "glob");
5753
+ if (glob) meta.push(glob);
5754
+ const type = stringField(input, "type");
5755
+ if (type) meta.push(`type:${type}`);
5756
+ if (input["-i"] === true) meta.push("case-insensitive");
5757
+ const mode = stringField(input, "output_mode");
5758
+ if (mode && mode !== "files_with_matches") meta.push(mode);
5759
+ return {
5760
+ target,
5761
+ meta
5762
+ };
5763
+ }
5764
+ },
5765
+ shell: {
5766
+ displayName: "Shell",
5767
+ format: (input) => {
5768
+ const command = stringField(input, "command");
5769
+ if (!command) return null;
5770
+ return { target: truncate(command.trim(), 200) };
5771
+ }
5772
+ },
5773
+ edit: {
5774
+ displayName: "Edit",
5775
+ format: (input) => {
5776
+ const path = stringField(input, "path");
5777
+ if (!path) return null;
5778
+ return {
5779
+ target: path,
5780
+ meta: input.replace_all === true ? ["replace all"] : []
5781
+ };
5782
+ }
5783
+ },
5784
+ multi_edit: {
5785
+ displayName: "Multi-edit",
5786
+ format: (input) => {
5787
+ const path = stringField(input, "path");
5788
+ if (!path) return null;
5789
+ const edits = Array.isArray(input.edits) ? input.edits.length : 0;
5790
+ return {
5791
+ target: path,
5792
+ meta: edits > 0 ? [`${edits} hunk${edits === 1 ? "" : "s"}`] : []
5793
+ };
5794
+ }
5795
+ },
5796
+ write_file: {
5797
+ displayName: "Write",
5798
+ format: (input) => {
5799
+ const path = stringField(input, "path");
5800
+ if (!path) return null;
5801
+ const content = stringField(input, "content");
5802
+ const meta = [];
5803
+ if (content !== void 0) {
5804
+ const bytes = byteLengthUtf8(content);
5805
+ meta.push(`${formatBytes(bytes)}`);
5806
+ }
5807
+ return {
5808
+ target: path,
5809
+ meta
5810
+ };
5811
+ }
5812
+ },
5813
+ spawn: {
5814
+ displayName: "Spawn",
5815
+ format: (input) => {
5816
+ const task = stringField(input, "task");
5817
+ if (!task) return null;
5818
+ return { target: truncate(task.replace(/\s+/g, " ").trim(), 120) };
5819
+ }
5820
+ },
5821
+ tool_search: {
5822
+ displayName: "Search tools",
5823
+ format: (input) => {
5824
+ const query = stringField(input, "query");
5825
+ const names = Array.isArray(input.names) ? input.names.length : 0;
5826
+ if (query) return { target: `“${query}”` };
5827
+ if (names > 0) return { target: `${names} tool${names === 1 ? "" : "s"}` };
5828
+ return null;
5829
+ }
5830
+ },
5831
+ skills_use: {
5832
+ displayName: "Activate skill",
5833
+ format: (input) => {
5834
+ const name = stringField(input, "name");
5835
+ if (!name) return null;
5836
+ return { target: name };
5837
+ }
5838
+ },
5839
+ skills_read: {
5840
+ displayName: "Read skill",
5841
+ format: (input) => {
5842
+ const name = stringField(input, "name");
5843
+ const path = stringField(input, "path");
5844
+ if (!name) return null;
5845
+ return { target: path ? `${name}/${path}` : name };
5846
+ }
5847
+ },
5848
+ skills_run_script: {
5849
+ displayName: "Run script",
5850
+ format: (input) => {
5851
+ const name = stringField(input, "name");
5852
+ const script = stringField(input, "script");
5853
+ if (!name || !script) return null;
5854
+ const meta = [`skill ${name}`];
5855
+ const args = Array.isArray(input.args) ? input.args : null;
5856
+ if (args && args.length > 0) meta.push(truncate(args.map(String).join(" "), 80));
5857
+ return {
5858
+ target: script,
5859
+ meta
5860
+ };
5861
+ }
5862
+ },
5863
+ ask_user: {
5864
+ displayName: "Ask user",
5865
+ format: (input) => {
5866
+ const questions = Array.isArray(input.questions) ? input.questions.length : 0;
5867
+ if (questions === 0) return null;
5868
+ return { target: `${questions} question${questions === 1 ? "" : "s"}` };
5869
+ }
5870
+ },
5871
+ present_plan: {
5872
+ displayName: "Present plan",
5873
+ format: (input) => {
5874
+ const title = stringField(input, "title");
5875
+ if (!title) return null;
5876
+ return { target: title };
5877
+ }
5878
+ }
5879
+ };
5880
+ /**
5881
+ * Resolve the display verb for a tool. Native tools use their curated
5882
+ * entry from {@link TOOL_DISPLAY}; everything else gets a sentence-case
5883
+ * version of the raw name (`my_host_tool` → `My host tool`) so an MCP /
5884
+ * host tool still reads cleanly in the transcript without shouting
5885
+ * Title Case at every word.
5886
+ *
5887
+ * MCP convention: every tool surfaced by `mcp/connectMcpServers` is
5888
+ * namespaced as `mcp_<server>_<tool>` (see `src/mcp/index.ts`). The
5889
+ * `mcp_` prefix is plumbing — strip it before casing so the label
5890
+ * reads as `Github create issue` instead of `Mcp github create issue`.
5891
+ * The server name leads, which doubles as a free visual grouping
5892
+ * affordance ("everything starting with `Github` came from the github
5893
+ * MCP server").
5894
+ */
5895
+ function displayNameFor(name) {
5896
+ const entry = TOOL_DISPLAY[name];
5897
+ if (entry) return entry.displayName;
5898
+ return sentenceCase(name.startsWith("mcp_") ? name.slice(4) : name);
5899
+ }
5900
+ /**
5901
+ * Run a tool's curated formatter and return the result, or `null` when
5902
+ * no formatter is registered / the input shape doesn't match. Renderer
5903
+ * decides what to do with `null` — typically: show `↳ <displayName>`
5904
+ * with no target / meta tail.
5905
+ */
5906
+ function formatToolCall(name, input) {
5907
+ const entry = TOOL_DISPLAY[name];
5908
+ if (!entry) return null;
5909
+ try {
5910
+ return entry.format(input);
5911
+ } catch {
5912
+ return null;
5913
+ }
5914
+ }
5915
+ function stringField(input, key) {
5916
+ const v = input[key];
5917
+ return typeof v === "string" && v.length > 0 ? v : void 0;
5918
+ }
5919
+ function numberField(input, key) {
5920
+ const v = input[key];
5921
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
5922
+ }
5923
+ /** `snake_case` / `kebab-case` / lowercase → `Sentence case`. */
5924
+ function sentenceCase(s) {
5925
+ const words = s.split(/[-_\s]+/).filter(Boolean).map((w) => w.toLowerCase());
5926
+ if (words.length === 0) return "";
5927
+ words[0] = (words[0][0]?.toUpperCase() ?? "") + words[0].slice(1);
5928
+ return words.join(" ");
5929
+ }
5930
+ function truncate(s, max) {
5931
+ return s.length <= max ? s : `${s.slice(0, max - 1)}…`;
5932
+ }
5933
+ function byteLengthUtf8(s) {
5934
+ let bytes = 0;
5935
+ for (let i = 0; i < s.length; i++) {
5936
+ const code = s.charCodeAt(i);
5937
+ if (code < 128) bytes += 1;
5938
+ else if (code < 2048) bytes += 2;
5939
+ else if (code >= 55296 && code <= 56319) {
5940
+ bytes += 4;
5941
+ i++;
5942
+ } else bytes += 3;
5943
+ }
5944
+ return bytes;
5945
+ }
5946
+ function formatBytes(bytes) {
5947
+ if (bytes < 1024) return `${bytes} B`;
5948
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
5949
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
5950
+ }
5951
+ //#endregion
5469
5952
  //#region src/chat/turn-operations.ts
5470
5953
  /**
5471
5954
  * Fork — keep every turn up to and including `turnId`, then strip any
@@ -5581,6 +6064,6 @@ function countNeighbors(turnIds, turnId) {
5581
6064
  };
5582
6065
  }
5583
6066
  //#endregion
5584
- export { InteractionsProvider as $, DOING_TASKS_DOCTRINE as $n, findGitRoot$1 as $t, readProjects as A, writeCredentials as An, CATPPUCCIN_MOCHA as At, defaultMcpsConfigPaths as B, modelsForDescriptor as Bn, saveState as Bt, useSafeModeQueue as C, detectAuth as Cn, DEFAULT_THEME as Ct, isOnSafelist as D, readProviderCredential as Dn, CATPPUCCIN_FRAPPE as Dt, getSafelist as E, readCredentials as En, VAPORWAVE_THEME as Et, supportsOAuth as F, credKeyOf as Fn, deriveSessionTitle as Ft, mcpCredentialsPath as G, BUILTIN_AGENTS as Gn, toolResultText as Gt, parseMcpsFile as H, openrouterDescriptor as Hn, stripSpawnTokensLine as Ht, buildModelCatalog as I, effectiveContextWindow as In, eventsFromTurns as It, useMcpAuthDispatch as J, PLAN_AGENT as Jn, computeLineDiff as Jt, patchMcpCredential as K, DEFAULT_AGENT_ID as Kn, buildUnifiedDiff as Kt, filterModelCatalog as L, getContextWindow as Ln, lastContextSizeFromTurns as Lt, writeProjects as M, OUTPUT_RESERVE_TOKENS as Mn, useConfig as Mt, splitPromptSegments as N, anthropicDescriptor as Nn, resolveConfig as Nt, matchesSafelistEntry as O, removeProviderCredential as On, CATPPUCCIN_LATTE as Ot, runOAuthLogin as P, cerebrasDescriptor as Pn, createStateStore as Pt, ASK_USER_TOOL as Q, COMMUNICATION_DOCTRINE as Qn, tokenize as Qt, indexOfEntry as R, getModelInfo as Rn, listSessionMeta as Rt, useSafeModeActions as S, shouldAutoCompact as Sn, BUILTIN_THEMES as St, addToSafelist as T, credentialsPath as Tn, resolveTheme as Tt, projectUserPaths as U, piIdOf as Un, titleFromTurns as Ut, discoverProjectMcps as V, openaiDescriptor as Vn, selectableTurnIds as Vt, createFileMcpCredentialStore as W, BUILD_AGENT as Wn, toolCallPreview as Wt, getMcpAuthStatus as X, singleAgentRegistry as Xn, filetypeFromPath as Xt, useMcpAuthState as Y, resolveAgentId as Yn, extractEditPayload as Yt, reduceMcpAuth as Z, ACTIONS_WITH_CARE_DOCTRINE as Zn, splitLines as Zt, discoverProjectSkills as _, collectReferences as _n, DEFAULT_SETTINGS as _t, ThemeProvider as a, matchesBinding as an, buildBuildSystem as ar, pendingInteractionsFromTurns as at, writeSessionExport as b, useCompletion as bn, SettingsProvider as bt, useSurfaces as c, readKeybindings as cn, useInteractionsQueue as ct, finalizeStreamingMarkdown as d, createSkillsCompletionProvider as dn, ageString as dt, DEFAULT_KEYBINDINGS as en, IDENTITY_PREFIX as er, PRESENT_PLAN_TOOL as et, finalizeStreamingMarkdownForOwner as f, uniqueSkillNamesFromReferences as fn, compactPath as ft, defaultSkillScanPaths as g, applyInsert as gn, useEnabledToggleSet as gt, buildSkillsConfig as h, uniqueFilesFromReferences as hn, listProjectFiles as ht, turnAsText as i, keybindingsPath as in, TOKEN_DISCIPLINE_DOCTRINE as ir, makeRequestInteraction as it, suggestSafelistEntry as j, BUILTIN_PROVIDERS as jn, ConfigProvider as jt, projectsFilePath as k, setProviderCredential as kn, CATPPUCCIN_MACCHIATO as kt, useSyntaxStyles as l, stripJsonComments as ln, cleanTitle as lt, useStreamBuffer as m, createFilesCompletionProvider as mn, shortId as mt, deleteTurnSafely as n, KEYBINDING_DEF_BY_ACTION as nn, PLAN_MODE_DOCTRINE as nr, createInteractionTools as nt, useColors as o, mergeKeybindings as on, buildPlanSystem as or, serializeInteractionResponse as ot, turnContextSize as p, FILES_TRIGGER as pn, fmtTokens as pt, McpAuthProvider as q, DEFAULT_PERSIST_EXCLUDE_TOOLS as qn, computeInlineDiff as qt, truncateTurnsAt as r, ensureKeybindingsFile as rn, SUBAGENT_GUIDANCE as rr, isInteractionTool as rt, useSelectStyle as s, parseBindingSpec as sn, envSection as sr, useInteractionsActions as st, countNeighbors as t, KEYBINDING_DEFS as tn, INTERACTION_GUIDANCE as tr, buildResumedToolResultsTurn as tt, useTheme as u, SKILLS_TRIGGER as un, generateSessionTitle as ut, renderSession as v, findActiveTrigger as vn, SETTINGS_CHOICES as vt, IMPLICITLY_SAFE_TOOLS as w, applyApiKeyEnv as wn, resolveChipColor as wt, SafeModeProvider as x, tryOpenBrowser as xn, useSettings as xt, resolveSessionExportTarget as y, mergeReferences as yn, SETTINGS_TOGGLES as yt, buildMcpServers as z, modelSupportsReasoning as zn, loadState as zt };
6067
+ export { getMcpAuthStatus as $, BUILD_AGENT as $n, toolResultText as $t, isOnSafelist as A, tryOpenBrowser as An, VAPORWAVE_THEME as At, filterModelCatalog as B, BUILTIN_PROVIDERS as Bn, eventsFromTurns as Bt, writeSessionExport as C, createFilesCompletionProvider as Cn, SettingsProvider as Ct, IMPLICITLY_SAFE_TOOLS as D, findActiveTrigger as Dn, DEFAULT_THEME as Dt, useSafeModeQueue as E, collectReferences as En, BUILTIN_THEMES as Et, writeProjects as F, readCredentials as Fn, ConfigProvider as Ft, parseMcpsFile as G, effectiveContextWindow as Gn, listSessionMeta as Gt, buildMcpServers as H, anthropicDescriptor as Hn, isTurnHighlighted as Ht, splitPromptSegments as I, readProviderCredential as In, useConfig as It, mcpCredentialsPath as J, modelSupportsReasoning as Jn, saveState as Jt, projectUserPaths as K, getContextWindow as Kn, loadState as Kt, runOAuthLogin as L, removeProviderCredential as Ln, resolveConfig as Lt, projectsFilePath as M, detectAuth as Mn, CATPPUCCIN_LATTE as Mt, readProjects as N, applyApiKeyEnv as Nn, CATPPUCCIN_MACCHIATO as Nt, addToSafelist as O, mergeReferences as On, resolveChipColor as Ot, suggestSafelistEntry as P, credentialsPath as Pn, CATPPUCCIN_MOCHA as Pt, useMcpAuthState as Q, piIdOf as Qn, toolCallPreview as Qt, supportsOAuth as R, setProviderCredential as Rn, createStateStore as Rt, resolveSessionExportTarget as S, FILES_TRIGGER as Sn, SETTINGS_TOGGLES as St, useSafeModeActions as T, applyInsert as Tn, useSettings as Tt, defaultMcpsConfigPaths as U, cerebrasDescriptor as Un, isVisible as Ut, indexOfEntry as V, OUTPUT_RESERVE_TOKENS as Vn, isEditErrorResult as Vt, discoverProjectMcps as W, credKeyOf as Wn, lastContextSizeFromTurns as Wt, McpAuthProvider as X, openaiDescriptor as Xn, stripSpawnTokensLine as Xt, patchMcpCredential as Y, modelsForDescriptor as Yn, selectableTurnIds as Yt, useMcpAuthDispatch as Z, openrouterDescriptor as Zn, titleFromTurns as Zt, useStreamBuffer as _, readKeybindings as _n, envSection as _r, shortId as _t, TOOL_DISPLAY as a, filetypeFromPath as an, resolveAgentId as ar, createInteractionTools as at, discoverProjectSkills as b, createSkillsCompletionProvider as bn, DEFAULT_SETTINGS as bt, ThemeProvider as c, findGitRoot$1 as cn, COMMUNICATION_DOCTRINE as cr, pendingInteractionsFromTurns as ct, useSurfaces as d, KEYBINDING_DEF_BY_ACTION as dn, INTERACTION_GUIDANCE as dr, useInteractionsQueue as dt, turnSelectionOwnership as en, BUILTIN_AGENTS as er, reduceMcpAuth as et, useSyntaxStyles as f, ensureKeybindingsFile as fn, PLAN_MODE_DOCTRINE as fr, cleanTitle as ft, turnContextSize as g, parseBindingSpec as gn, buildPlanSystem as gr, fmtTokens as gt, finalizeStreamingMarkdownForOwner as h, mergeKeybindings as hn, buildBuildSystem as hr, compactPath as ht, turnAsText as i, extractEditPayload as in, accentColor as ir, buildResumedToolResultsTurn as it, matchesSafelistEntry as j, shouldAutoCompact as jn, CATPPUCCIN_FRAPPE as jt, getSafelist as k, useCompletion as kn, resolveTheme as kt, useColors as l, DEFAULT_KEYBINDINGS as ln, DOING_TASKS_DOCTRINE as lr, serializeInteractionResponse as lt, finalizeStreamingMarkdown as m, matchesBinding as mn, TOKEN_DISCIPLINE_DOCTRINE as mr, ageString as mt, deleteTurnSafely as n, computeInlineDiff as nn, DEFAULT_PERSIST_EXCLUDE_TOOLS as nr, InteractionsProvider as nt, displayNameFor as o, splitLines as on, singleAgentRegistry as or, isInteractionTool as ot, useTheme as p, keybindingsPath as pn, SUBAGENT_GUIDANCE as pr, generateSessionTitle as pt, createFileMcpCredentialStore as q, getModelInfo as qn, marginTopFor as qt, truncateTurnsAt as r, computeLineDiff as rn, PLAN_AGENT as rr, PRESENT_PLAN_TOOL as rt, formatToolCall as s, tokenize as sn, ACTIONS_WITH_CARE_DOCTRINE as sr, makeRequestInteraction as st, countNeighbors as t, buildUnifiedDiff as tn, DEFAULT_AGENT_ID as tr, ASK_USER_TOOL as tt, useSelectStyle as u, KEYBINDING_DEFS as un, IDENTITY_PREFIX as ur, useInteractionsActions as ut, buildSkillsConfig as v, stripJsonComments as vn, listProjectFiles as vt, SafeModeProvider as w, uniqueFilesFromReferences as wn, clampFps as wt, renderSession as x, uniqueSkillNamesFromReferences as xn, SETTINGS_CHOICES as xt, defaultSkillScanPaths as y, SKILLS_TRIGGER as yn, useEnabledToggleSet as yt, buildModelCatalog as z, writeCredentials as zn, deriveSessionTitle as zt };
5585
6068
 
5586
- //# sourceMappingURL=turn-operations-BzOIM6Of.js.map
6069
+ //# sourceMappingURL=turn-operations-2cgnXS2d.js.map