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.
- package/dist/chat.d.ts +3 -3
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/index.js +1 -1
- package/dist/{login-BiuHyuEh.js → login-B7b7NNJQ.js} +2 -1
- package/dist/login-B7b7NNJQ.js.map +1 -0
- package/dist/{theme-CcGLMJrn.d.ts → tool-formatters-D7cN3T_W.d.ts} +203 -14
- package/dist/tool-formatters-D7cN3T_W.d.ts.map +1 -0
- package/dist/tui.d.ts +9 -33
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +49 -385
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-BzOIM6Of.js → turn-operations-2cgnXS2d.js} +515 -32
- package/dist/turn-operations-2cgnXS2d.js.map +1 -0
- package/package.json +1 -1
- package/dist/login-BiuHyuEh.js.map +0 -1
- package/dist/theme-CcGLMJrn.d.ts.map +0 -1
- package/dist/turn-operations-BzOIM6Of.js.map +0 -1
|
@@ -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.
|
|
2183
|
-
*
|
|
2184
|
-
* -
|
|
2185
|
-
*
|
|
2186
|
-
*
|
|
2187
|
-
*
|
|
2188
|
-
*
|
|
2189
|
-
* the
|
|
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.
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
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)
|
|
2416
|
-
|
|
2417
|
-
|
|
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
|
|
4409
|
+
return /* @__PURE__ */ jsx(StateContext.Provider, {
|
|
4177
4410
|
value: state,
|
|
4178
|
-
children: /* @__PURE__ */ jsx
|
|
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.
|
|
5260
|
-
*
|
|
5261
|
-
*
|
|
5262
|
-
*
|
|
5263
|
-
*
|
|
5264
|
-
*
|
|
5265
|
-
*
|
|
5266
|
-
*
|
|
5267
|
-
|
|
5268
|
-
|
|
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 {
|
|
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-
|
|
6069
|
+
//# sourceMappingURL=turn-operations-2cgnXS2d.js.map
|