dsh-code 0.9.0 → 1.0.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.en.md +31 -8
- package/README.md +264 -241
- package/bin/deepseek.mjs +100 -6
- package/cordis.patch.yml +29 -1
- package/lib/index.mjs +2317 -708
- package/lib/startup.mjs +21 -11
- package/lib/{theme-BEi4i_aN.mjs → theme-DCT8Y2xf.mjs} +13 -9
- package/lib/types/app.d.ts +66 -14
- package/lib/types/attachments.d.ts +7 -0
- package/lib/types/editor.d.ts +6 -0
- package/lib/types/fork.d.ts +8 -0
- package/lib/types/git-workflow.d.ts +23 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/kernel-panels.d.ts +39 -0
- package/lib/types/keyboard.d.ts +43 -0
- package/lib/types/mentions.d.ts +28 -38
- package/lib/types/presets.d.ts +1 -3
- package/lib/types/provider-settings.d.ts +16 -0
- package/lib/types/render/animations.d.ts +24 -41
- package/lib/types/render/editor.d.ts +137 -0
- package/lib/types/render/export.d.ts +1 -1
- package/lib/types/render/lines.d.ts +6 -2
- package/lib/types/render/markdown.d.ts +3 -1
- package/lib/types/render/projection.d.ts +29 -3
- package/lib/types/render/status.d.ts +5 -12
- package/lib/types/session-directory.d.ts +1 -3
- package/lib/types/startup.d.ts +14 -11
- package/lib/types/store.d.ts +11 -9
- package/lib/types/subagents.d.ts +3 -3
- package/lib/types/theme.d.ts +14 -1
- package/lib/types/version.d.ts +15 -2
- package/package.json +153 -117
- package/src/app.ts +4455 -3904
- package/src/attachments.ts +44 -0
- package/src/editor.ts +51 -0
- package/src/fork.ts +31 -0
- package/src/git-workflow.ts +87 -0
- package/src/index.ts +1510 -1374
- package/src/internals.ts +14 -1
- package/src/kernel-panels.ts +914 -798
- package/src/keyboard.ts +125 -0
- package/src/mentions.ts +72 -117
- package/src/presets.ts +1 -4
- package/src/provider-settings.ts +94 -0
- package/src/render/animations.ts +74 -60
- package/src/render/editor.ts +398 -0
- package/src/render/export.ts +79 -79
- package/src/render/lines.ts +342 -236
- package/src/render/markdown.ts +99 -26
- package/src/render/projection.ts +102 -19
- package/src/render/status.ts +713 -650
- package/src/render/text.ts +150 -150
- package/src/render/tool-detail.ts +3 -1
- package/src/session-directory.ts +3 -3
- package/src/startup.ts +136 -119
- package/src/store.ts +23 -11
- package/src/subagents.ts +13 -5
- package/src/theme.ts +214 -206
- package/src/version.ts +58 -1
package/lib/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { n as __require, r as __toESM, t as __commonJSMin } from "./rolldown-runtime-CMFfr-1z.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as inkColor, c as chalk, i as getTheme, n as dim, o as parseThemeName, r as getPalette, s as setTheme } from "./theme-DCT8Y2xf.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import { readFileSync, realpathSync } from "node:fs";
|
|
6
6
|
import os, { homedir } from "node:os";
|
|
7
|
-
import { mkdir,
|
|
7
|
+
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { basename, dirname, join, resolve } from "node:path";
|
|
9
9
|
import z from "@deepseek-ai/schemastery";
|
|
10
10
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
@@ -14,9 +14,11 @@ import { PassThrough, Stream } from "node:stream";
|
|
|
14
14
|
import process$1, { cwd, env } from "node:process";
|
|
15
15
|
import { EventEmitter } from "node:events";
|
|
16
16
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
17
|
+
import { DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES, DEFAULT_FILE_SEARCH_MAX_ENTRIES, DEFAULT_FILE_SEARCH_MAX_RESULTS, WorkspaceFileSearch } from "@deepseek-ai/dsh-file-reference-local";
|
|
17
18
|
import { formatSessionReferenceMention, parseSessionReferenceText } from "@deepseek-ai/dsh-session-reference";
|
|
18
19
|
import { UserQuestionError } from "@deepseek-ai/dsh-user-questions";
|
|
19
20
|
import { isUserInvocable } from "@deepseek-ai/dsh-skill";
|
|
21
|
+
import { execFile, spawn } from "node:child_process";
|
|
20
22
|
//#region node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
|
|
21
23
|
/**
|
|
22
24
|
* @license React
|
|
@@ -21652,7 +21654,7 @@ var import_emoji_regex = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(
|
|
|
21652
21654
|
return /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E-\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED8\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])))?))?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3C-\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE8A\uDE8E-\uDEC2\uDEC6\uDEC8\uDECD-\uDEDC\uDEDF-\uDEEA\uDEEF]|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;
|
|
21653
21655
|
};
|
|
21654
21656
|
})))(), 1);
|
|
21655
|
-
const segmenter = new Intl.Segmenter();
|
|
21657
|
+
const segmenter$1 = new Intl.Segmenter();
|
|
21656
21658
|
const defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
|
|
21657
21659
|
function stringWidth(string, options = {}) {
|
|
21658
21660
|
if (typeof string !== "string" || string.length === 0) return 0;
|
|
@@ -21661,7 +21663,7 @@ function stringWidth(string, options = {}) {
|
|
|
21661
21663
|
if (string.length === 0) return 0;
|
|
21662
21664
|
let width = 0;
|
|
21663
21665
|
const eastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
|
|
21664
|
-
for (const { segment: character } of segmenter.segment(string)) {
|
|
21666
|
+
for (const { segment: character } of segmenter$1.segment(string)) {
|
|
21665
21667
|
const codePoint = character.codePointAt(0);
|
|
21666
21668
|
if (codePoint <= 31 || codePoint >= 127 && codePoint <= 159) continue;
|
|
21667
21669
|
if (codePoint >= 8203 && codePoint <= 8207 || codePoint === 65279) continue;
|
|
@@ -24580,6 +24582,43 @@ function readPackageVersion(manifest = new URL("../package.json", import.meta.ur
|
|
|
24580
24582
|
}
|
|
24581
24583
|
/** Version of the installed dsh-code package. */
|
|
24582
24584
|
const DSH_CODE_VERSION = readPackageVersion();
|
|
24585
|
+
/** The harness host package: the dsh CLI whose process runs the TUI plugin. */
|
|
24586
|
+
const DSH_HOST_PACKAGE_NAME = "@deepseek-ai/dsh";
|
|
24587
|
+
/** Parent levels above the host entry allowed to hold its package manifest. */
|
|
24588
|
+
const DSH_HOST_WALK_LIMIT = 4;
|
|
24589
|
+
/**
|
|
24590
|
+
* Resolve the running dsh CLI host's version from its entry file
|
|
24591
|
+
* (`process.argv[1]`, e.g. `.../@deepseek-ai/dsh/lib/bin.js`). Only a manifest
|
|
24592
|
+
* literally named `@deepseek-ai/dsh` counts, so an unrelated entry (vitest, a
|
|
24593
|
+
* plain node script) resolves to undefined instead of faking a kernel version.
|
|
24594
|
+
*/
|
|
24595
|
+
function resolveDshHostVersion(entry = process.argv[1]) {
|
|
24596
|
+
if (entry === void 0 || entry === "") return void 0;
|
|
24597
|
+
let directory = dirname(resolve(entry));
|
|
24598
|
+
for (let depth = 0; depth < DSH_HOST_WALK_LIMIT; depth += 1) {
|
|
24599
|
+
try {
|
|
24600
|
+
const parsed = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
24601
|
+
if (parsed.name === DSH_HOST_PACKAGE_NAME && typeof parsed.version === "string" && parsed.version.length > 0) return parsed.version;
|
|
24602
|
+
} catch {}
|
|
24603
|
+
const parent = dirname(directory);
|
|
24604
|
+
if (parent === directory) return void 0;
|
|
24605
|
+
directory = parent;
|
|
24606
|
+
}
|
|
24607
|
+
}
|
|
24608
|
+
let cachedDshKernelVersion;
|
|
24609
|
+
let dshKernelVersionResolved = false;
|
|
24610
|
+
/**
|
|
24611
|
+
* The dsh kernel version the TUI runs on, memoized after the first probe: the
|
|
24612
|
+
* host process never changes within a run, and the header reads this on every
|
|
24613
|
+
* Static replay.
|
|
24614
|
+
*/
|
|
24615
|
+
function dshKernelVersion() {
|
|
24616
|
+
if (!dshKernelVersionResolved) {
|
|
24617
|
+
cachedDshKernelVersion = resolveDshHostVersion();
|
|
24618
|
+
dshKernelVersionResolved = true;
|
|
24619
|
+
}
|
|
24620
|
+
return cachedDshKernelVersion;
|
|
24621
|
+
}
|
|
24583
24622
|
//#endregion
|
|
24584
24623
|
//#region src/render/tool-preview.ts
|
|
24585
24624
|
/**
|
|
@@ -24682,7 +24721,7 @@ const MAX_DIFFS = 8;
|
|
|
24682
24721
|
const MAX_DIFF_TEXT_CHARS = 24e3;
|
|
24683
24722
|
/** Truncate one line to the visible-column budget with an ellipsis marker. */
|
|
24684
24723
|
function clipLine(text) {
|
|
24685
|
-
return text
|
|
24724
|
+
return truncateColumns(displayText(text), MAX_LINE_COLUMNS);
|
|
24686
24725
|
}
|
|
24687
24726
|
/** Split text into lines, dropping the trailing empty element of a final newline. */
|
|
24688
24727
|
function toLines(text) {
|
|
@@ -24871,6 +24910,23 @@ function appendStreamingTail(current, delta) {
|
|
|
24871
24910
|
function textOf(content) {
|
|
24872
24911
|
return content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
24873
24912
|
}
|
|
24913
|
+
/** Durable image references in their model-visible order. */
|
|
24914
|
+
function imagesOf(content) {
|
|
24915
|
+
return content.filter((block) => block.type === "image").map((block) => block.attachment);
|
|
24916
|
+
}
|
|
24917
|
+
/** Human-readable bounded image labels for transcript, inspector, and export surfaces. */
|
|
24918
|
+
function imageLabels(images) {
|
|
24919
|
+
if (images === void 0 || images.length === 0) return "";
|
|
24920
|
+
return images.map((image, index) => {
|
|
24921
|
+
const rawName = image.name?.trim() || `image ${index + 1}`;
|
|
24922
|
+
return `[image: ${rawName.length <= 80 ? rawName : `${rawName.slice(0, 79)}…`} · ${image.width}×${image.height} · ${image.bytes} B]`;
|
|
24923
|
+
}).join("\n");
|
|
24924
|
+
}
|
|
24925
|
+
/** Prompt text with its durable image labels, without exposing local paths or bytes. */
|
|
24926
|
+
function promptDisplayText(entry) {
|
|
24927
|
+
const labels = imageLabels(entry.images);
|
|
24928
|
+
return entry.text === "" ? labels : labels === "" ? entry.text : `${entry.text}\n${labels}`;
|
|
24929
|
+
}
|
|
24874
24930
|
/** Join the reasoning blocks of a content list; non-reasoning blocks contribute nothing. */
|
|
24875
24931
|
function reasoningOf(content) {
|
|
24876
24932
|
return content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
|
|
@@ -24897,6 +24953,7 @@ function createTranscriptView() {
|
|
|
24897
24953
|
streaming: "",
|
|
24898
24954
|
streamingReasoning: "",
|
|
24899
24955
|
todos: [],
|
|
24956
|
+
toolCallOrdinal: 0,
|
|
24900
24957
|
busy: false,
|
|
24901
24958
|
busySince: 0,
|
|
24902
24959
|
model: "",
|
|
@@ -24972,13 +25029,15 @@ function projectEvent(view, event) {
|
|
|
24972
25029
|
entries = entries.filter((entry) => !(entry.kind === "pending" && entry.messageId === message.id));
|
|
24973
25030
|
}
|
|
24974
25031
|
const text = textOf(message.content);
|
|
25032
|
+
const images = imagesOf(message.content);
|
|
24975
25033
|
if (message.source.kind === "user") return {
|
|
24976
25034
|
...view,
|
|
24977
25035
|
pending,
|
|
24978
25036
|
entries: [...entries, {
|
|
24979
25037
|
kind: "user",
|
|
24980
25038
|
text,
|
|
24981
|
-
notice: false
|
|
25039
|
+
notice: false,
|
|
25040
|
+
...images.length === 0 ? {} : { images }
|
|
24982
25041
|
}],
|
|
24983
25042
|
stats: {
|
|
24984
25043
|
...view.stats,
|
|
@@ -25025,7 +25084,8 @@ function projectEvent(view, event) {
|
|
|
25025
25084
|
kind: "pending",
|
|
25026
25085
|
messageId: message.id,
|
|
25027
25086
|
target,
|
|
25028
|
-
text: pendingText(message.content)
|
|
25087
|
+
text: pendingText(message.content),
|
|
25088
|
+
...imagesOf(message.content).length === 0 ? {} : { images: imagesOf(message.content) }
|
|
25029
25089
|
}];
|
|
25030
25090
|
return {
|
|
25031
25091
|
...view,
|
|
@@ -25080,7 +25140,8 @@ function projectEvent(view, event) {
|
|
|
25080
25140
|
entries: [...view.entries, {
|
|
25081
25141
|
kind: "assistant",
|
|
25082
25142
|
text,
|
|
25083
|
-
reasoning
|
|
25143
|
+
reasoning,
|
|
25144
|
+
interrupted: event.data.interrupted === true ? true : void 0
|
|
25084
25145
|
}],
|
|
25085
25146
|
stats: {
|
|
25086
25147
|
...view.stats,
|
|
@@ -25107,11 +25168,14 @@ function projectEvent(view, event) {
|
|
|
25107
25168
|
const turnTools = view.anchors.turnTools.get(data.turn) ?? /* @__PURE__ */ new Set();
|
|
25108
25169
|
turnTools.add(data.callId);
|
|
25109
25170
|
view.anchors.turnTools.set(data.turn, turnTools);
|
|
25171
|
+
const ordinal = view.toolCallOrdinal + 1;
|
|
25110
25172
|
return {
|
|
25111
25173
|
...view,
|
|
25174
|
+
toolCallOrdinal: ordinal,
|
|
25112
25175
|
entries: [...view.entries, {
|
|
25113
25176
|
kind: "tool",
|
|
25114
25177
|
callId: data.callId,
|
|
25178
|
+
ordinal,
|
|
25115
25179
|
name: data.name,
|
|
25116
25180
|
arguments: data.arguments,
|
|
25117
25181
|
preview: toolArgumentsPreview(data.arguments, data.name),
|
|
@@ -25193,6 +25257,8 @@ function projectEvent(view, event) {
|
|
|
25193
25257
|
view.anchors.stepStart.set(key, event.time);
|
|
25194
25258
|
return {
|
|
25195
25259
|
...view,
|
|
25260
|
+
streaming: "",
|
|
25261
|
+
streamingReasoning: "",
|
|
25196
25262
|
stats: {
|
|
25197
25263
|
...view.stats,
|
|
25198
25264
|
steps: view.stats.steps + 1
|
|
@@ -25235,12 +25301,16 @@ function projectEvent(view, event) {
|
|
|
25235
25301
|
if (appended.length === 0) return {
|
|
25236
25302
|
...view,
|
|
25237
25303
|
busy: false,
|
|
25238
|
-
busySince: 0
|
|
25304
|
+
busySince: 0,
|
|
25305
|
+
streaming: "",
|
|
25306
|
+
streamingReasoning: ""
|
|
25239
25307
|
};
|
|
25240
25308
|
return {
|
|
25241
25309
|
...view,
|
|
25242
25310
|
busy: false,
|
|
25243
25311
|
busySince: 0,
|
|
25312
|
+
streaming: "",
|
|
25313
|
+
streamingReasoning: "",
|
|
25244
25314
|
entries: [...view.entries, ...appended]
|
|
25245
25315
|
};
|
|
25246
25316
|
}
|
|
@@ -25248,6 +25318,8 @@ function projectEvent(view, event) {
|
|
|
25248
25318
|
const data = event.data;
|
|
25249
25319
|
return {
|
|
25250
25320
|
...view,
|
|
25321
|
+
streaming: "",
|
|
25322
|
+
streamingReasoning: "",
|
|
25251
25323
|
entries: [...view.entries, {
|
|
25252
25324
|
kind: "retry",
|
|
25253
25325
|
retryId: data.retryId,
|
|
@@ -25413,6 +25485,7 @@ function createReplayAccumulator() {
|
|
|
25413
25485
|
streaming: "",
|
|
25414
25486
|
streamingReasoning: "",
|
|
25415
25487
|
todos: [],
|
|
25488
|
+
toolCallOrdinal: 0,
|
|
25416
25489
|
busy: false,
|
|
25417
25490
|
busySince: 0,
|
|
25418
25491
|
model: "",
|
|
@@ -25527,11 +25600,13 @@ function replayProjectEvent(acc, event) {
|
|
|
25527
25600
|
}
|
|
25528
25601
|
}
|
|
25529
25602
|
const text = textOf(message.content);
|
|
25603
|
+
const images = imagesOf(message.content);
|
|
25530
25604
|
if (message.source.kind === "user") {
|
|
25531
25605
|
appendReplayEntry(acc, {
|
|
25532
25606
|
kind: "user",
|
|
25533
25607
|
text,
|
|
25534
|
-
notice: false
|
|
25608
|
+
notice: false,
|
|
25609
|
+
...images.length === 0 ? {} : { images }
|
|
25535
25610
|
});
|
|
25536
25611
|
acc.stats = {
|
|
25537
25612
|
...acc.stats,
|
|
@@ -25574,11 +25649,13 @@ function replayProjectEvent(acc, event) {
|
|
|
25574
25649
|
}
|
|
25575
25650
|
}
|
|
25576
25651
|
for (const message of inserted) {
|
|
25652
|
+
const images = imagesOf(message.content);
|
|
25577
25653
|
appendReplayEntry(acc, {
|
|
25578
25654
|
kind: "pending",
|
|
25579
25655
|
messageId: message.id,
|
|
25580
25656
|
target,
|
|
25581
|
-
text: pendingText(message.content)
|
|
25657
|
+
text: pendingText(message.content),
|
|
25658
|
+
...images.length === 0 ? {} : { images }
|
|
25582
25659
|
});
|
|
25583
25660
|
indexList(acc.pendingIndex, message.id).push(acc.entries.length - 1);
|
|
25584
25661
|
ids.push(message.id);
|
|
@@ -25624,7 +25701,8 @@ function replayProjectEvent(acc, event) {
|
|
|
25624
25701
|
appendReplayEntry(acc, {
|
|
25625
25702
|
kind: "assistant",
|
|
25626
25703
|
text,
|
|
25627
|
-
reasoning
|
|
25704
|
+
reasoning,
|
|
25705
|
+
interrupted: event.data.interrupted === true ? true : void 0
|
|
25628
25706
|
});
|
|
25629
25707
|
acc.stats = {
|
|
25630
25708
|
...acc.stats,
|
|
@@ -25651,9 +25729,11 @@ function replayProjectEvent(acc, event) {
|
|
|
25651
25729
|
const turnTools = acc.turnTools.get(data.turn) ?? /* @__PURE__ */ new Set();
|
|
25652
25730
|
turnTools.add(data.callId);
|
|
25653
25731
|
acc.turnTools.set(data.turn, turnTools);
|
|
25732
|
+
acc.toolCallOrdinal += 1;
|
|
25654
25733
|
appendReplayEntry(acc, {
|
|
25655
25734
|
kind: "tool",
|
|
25656
25735
|
callId: data.callId,
|
|
25736
|
+
ordinal: acc.toolCallOrdinal,
|
|
25657
25737
|
name: data.name,
|
|
25658
25738
|
arguments: data.arguments,
|
|
25659
25739
|
preview: toolArgumentsPreview(data.arguments, data.name),
|
|
@@ -25729,6 +25809,8 @@ function replayProjectEvent(acc, event) {
|
|
|
25729
25809
|
}
|
|
25730
25810
|
acc.turnSteps.set(event.data.turn, key);
|
|
25731
25811
|
acc.stepStart.set(key, event.time);
|
|
25812
|
+
acc.streaming = "";
|
|
25813
|
+
acc.streamingReasoning = "";
|
|
25732
25814
|
acc.stats = {
|
|
25733
25815
|
...acc.stats,
|
|
25734
25816
|
steps: acc.stats.steps + 1
|
|
@@ -25738,6 +25820,8 @@ function replayProjectEvent(acc, event) {
|
|
|
25738
25820
|
case "turn/end": {
|
|
25739
25821
|
const reason = event.data.reason;
|
|
25740
25822
|
const appended = [];
|
|
25823
|
+
acc.streamingReasoning = "";
|
|
25824
|
+
acc.streaming = "";
|
|
25741
25825
|
if (reason.kind === "error") {
|
|
25742
25826
|
const recovery = reason.error.code === "MISSING_CREDENTIAL" ? " · open /model to add an API key" : "";
|
|
25743
25827
|
appended.push({
|
|
@@ -25775,6 +25859,8 @@ function replayProjectEvent(acc, event) {
|
|
|
25775
25859
|
}
|
|
25776
25860
|
case "llm/retry": {
|
|
25777
25861
|
const data = event.data;
|
|
25862
|
+
acc.streaming = "";
|
|
25863
|
+
acc.streamingReasoning = "";
|
|
25778
25864
|
appendReplayEntry(acc, {
|
|
25779
25865
|
kind: "retry",
|
|
25780
25866
|
retryId: data.retryId,
|
|
@@ -25915,6 +26001,7 @@ function finishReplay(acc) {
|
|
|
25915
26001
|
streaming: acc.streaming,
|
|
25916
26002
|
streamingReasoning: acc.streamingReasoning,
|
|
25917
26003
|
todos: acc.todos,
|
|
26004
|
+
toolCallOrdinal: acc.toolCallOrdinal,
|
|
25918
26005
|
busy: acc.busy,
|
|
25919
26006
|
busySince: acc.busySince,
|
|
25920
26007
|
model: acc.model,
|
|
@@ -26001,34 +26088,94 @@ function visibleColumns(text) {
|
|
|
26001
26088
|
}
|
|
26002
26089
|
return columns;
|
|
26003
26090
|
}
|
|
26004
|
-
/**
|
|
26091
|
+
/** Punctuation that should not become the first visible glyph of a row. */
|
|
26092
|
+
function isClosingPunctuation(text) {
|
|
26093
|
+
return /^[,.;:!?%、。,.!?:;%)》」』】〕〉》”’]/u.test(text);
|
|
26094
|
+
}
|
|
26095
|
+
/**
|
|
26096
|
+
* Split prose into soft-wrap units: ASCII words stay intact, while wide CJK
|
|
26097
|
+
* glyphs become individual break opportunities. This keeps ordinary prose
|
|
26098
|
+
* readable without allowing a Chinese paragraph to become one overlong word.
|
|
26099
|
+
*/
|
|
26100
|
+
function wrapUnits(segments) {
|
|
26101
|
+
const units = [];
|
|
26102
|
+
for (const segment of segments) {
|
|
26103
|
+
let word = "";
|
|
26104
|
+
const flushWord = () => {
|
|
26105
|
+
if (word !== "") units.push({
|
|
26106
|
+
text: word,
|
|
26107
|
+
style: segment.style
|
|
26108
|
+
});
|
|
26109
|
+
word = "";
|
|
26110
|
+
};
|
|
26111
|
+
for (const char of segment.text) if (char === " ") {
|
|
26112
|
+
flushWord();
|
|
26113
|
+
units.push({
|
|
26114
|
+
text: char,
|
|
26115
|
+
style: segment.style
|
|
26116
|
+
});
|
|
26117
|
+
} else if (visibleColumns(char) > 1) {
|
|
26118
|
+
flushWord();
|
|
26119
|
+
units.push({
|
|
26120
|
+
text: char,
|
|
26121
|
+
style: segment.style
|
|
26122
|
+
});
|
|
26123
|
+
} else word += char;
|
|
26124
|
+
flushWord();
|
|
26125
|
+
}
|
|
26126
|
+
return units;
|
|
26127
|
+
}
|
|
26128
|
+
/** Walk styled units, keeping logical rows within the physical column budget. */
|
|
26005
26129
|
function wrapSegments(segments, width) {
|
|
26130
|
+
const limit = Math.max(1, Math.floor(width));
|
|
26006
26131
|
const lines = [];
|
|
26007
26132
|
let current = [];
|
|
26008
26133
|
let used = 0;
|
|
26009
|
-
|
|
26010
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
26013
|
-
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
|
|
26017
|
-
|
|
26018
|
-
|
|
26019
|
-
|
|
26020
|
-
|
|
26021
|
-
|
|
26134
|
+
const flush = () => {
|
|
26135
|
+
while (current.at(-1)?.text === " ") used -= visibleColumns(current.pop().text);
|
|
26136
|
+
if (current.length > 0) lines.push(merge(current));
|
|
26137
|
+
current = [];
|
|
26138
|
+
used = 0;
|
|
26139
|
+
};
|
|
26140
|
+
const appendAtom = (unit) => {
|
|
26141
|
+
const columns = visibleColumns(unit.text);
|
|
26142
|
+
if (used > 0 && used + columns > limit) flush();
|
|
26143
|
+
current.push(unit);
|
|
26144
|
+
used += columns;
|
|
26145
|
+
};
|
|
26146
|
+
const append = (unit) => {
|
|
26147
|
+
const columns = visibleColumns(unit.text);
|
|
26148
|
+
if (used === 0 && columns > limit) {
|
|
26149
|
+
for (const char of unit.text) appendAtom({
|
|
26150
|
+
text: char,
|
|
26151
|
+
style: unit.style
|
|
26022
26152
|
});
|
|
26153
|
+
return;
|
|
26154
|
+
}
|
|
26155
|
+
if (used + columns <= limit || current.length === 0) {
|
|
26156
|
+
current.push(unit);
|
|
26023
26157
|
used += columns;
|
|
26158
|
+
return;
|
|
26024
26159
|
}
|
|
26025
|
-
|
|
26026
|
-
|
|
26027
|
-
|
|
26028
|
-
|
|
26029
|
-
|
|
26030
|
-
|
|
26031
|
-
|
|
26160
|
+
const previous = current.at(-1);
|
|
26161
|
+
if (isClosingPunctuation(unit.text) && previous !== void 0 && visibleColumns(previous.text) > 1) {
|
|
26162
|
+
current.pop();
|
|
26163
|
+
used -= visibleColumns(previous.text);
|
|
26164
|
+
flush();
|
|
26165
|
+
appendAtom(previous);
|
|
26166
|
+
appendAtom(unit);
|
|
26167
|
+
return;
|
|
26168
|
+
}
|
|
26169
|
+
flush();
|
|
26170
|
+
if (columns > limit) for (const char of unit.text) appendAtom({
|
|
26171
|
+
text: char,
|
|
26172
|
+
style: unit.style
|
|
26173
|
+
});
|
|
26174
|
+
else appendAtom(unit);
|
|
26175
|
+
};
|
|
26176
|
+
for (const unit of wrapUnits(segments)) append(unit);
|
|
26177
|
+
flush();
|
|
26178
|
+
return lines;
|
|
26032
26179
|
}
|
|
26033
26180
|
/** Join adjacent same-style runs so the app renders fewer elements. */
|
|
26034
26181
|
function merge(segments) {
|
|
@@ -26395,10 +26542,14 @@ function renderTableRecords(table, width) {
|
|
|
26395
26542
|
return lines;
|
|
26396
26543
|
}
|
|
26397
26544
|
/** Render markdown text into styled lines of at most `width` columns. */
|
|
26398
|
-
function renderMarkdown(text, width) {
|
|
26545
|
+
function renderMarkdown(text, width, options = {}) {
|
|
26399
26546
|
const lines = [];
|
|
26400
26547
|
let separatorPending = false;
|
|
26401
26548
|
const push = (segments) => {
|
|
26549
|
+
if (options.physicalWrap === false) {
|
|
26550
|
+
lines.push({ segments: merge(segments) });
|
|
26551
|
+
return;
|
|
26552
|
+
}
|
|
26402
26553
|
for (const wrapped of wrapSegments(segments, Math.max(10, width))) lines.push({ segments: merge(wrapped) });
|
|
26403
26554
|
};
|
|
26404
26555
|
const startBlock = () => {
|
|
@@ -26450,12 +26601,12 @@ function renderMarkdown(text, width) {
|
|
|
26450
26601
|
}
|
|
26451
26602
|
const ordered = ORDERED.exec(line);
|
|
26452
26603
|
if (ordered !== null) {
|
|
26453
|
-
push([seg(` ${ordered[1] ?? ""}. `, "accent"), ...parseInline(ordered[2] ?? "").map((run) => seg(run.text, run.style))]);
|
|
26604
|
+
push([seg(options.physicalWrap === false ? `${ordered[1] ?? ""}. ` : ` ${ordered[1] ?? ""}. `, "accent"), ...parseInline(ordered[2] ?? "").map((run) => seg(run.text, run.style))]);
|
|
26454
26605
|
continue;
|
|
26455
26606
|
}
|
|
26456
26607
|
const unordered = UNORDERED.exec(line);
|
|
26457
26608
|
if (unordered !== null) {
|
|
26458
|
-
push([seg(" • ", "accent"), ...parseInline(unordered[1] ?? "").map((run) => seg(run.text, run.style))]);
|
|
26609
|
+
push([seg(options.physicalWrap === false ? "• " : " • ", "accent"), ...parseInline(unordered[1] ?? "").map((run) => seg(run.text, run.style))]);
|
|
26459
26610
|
continue;
|
|
26460
26611
|
}
|
|
26461
26612
|
const paragraph = [line];
|
|
@@ -26477,21 +26628,6 @@ function renderMarkdown(text, width) {
|
|
|
26477
26628
|
}
|
|
26478
26629
|
//#endregion
|
|
26479
26630
|
//#region src/render/animations.ts
|
|
26480
|
-
/** Single-cell stepped pulse: flat holds mirroring the web's 125ms keyframes. */
|
|
26481
|
-
const PULSE_FRAMES = [
|
|
26482
|
-
"█",
|
|
26483
|
-
"█",
|
|
26484
|
-
"▆",
|
|
26485
|
-
"▃",
|
|
26486
|
-
"▁",
|
|
26487
|
-
"▃",
|
|
26488
|
-
"▆",
|
|
26489
|
-
"█"
|
|
26490
|
-
];
|
|
26491
|
-
/** Pulse frame for a monotonic tick. */
|
|
26492
|
-
function pulseFrame(tick) {
|
|
26493
|
-
return PULSE_FRAMES[tick % PULSE_FRAMES.length] ?? PULSE_FRAMES[0];
|
|
26494
|
-
}
|
|
26495
26631
|
/**
|
|
26496
26632
|
* The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
|
|
26497
26633
|
* ring trail clockwise around the eight outer positions, one braille glyph
|
|
@@ -26515,7 +26651,7 @@ function busyChaseFrame(tick) {
|
|
|
26515
26651
|
function caretVisible(tick) {
|
|
26516
26652
|
return tick % 2 === 0;
|
|
26517
26653
|
}
|
|
26518
|
-
/** All styles in canonical order, for random selection
|
|
26654
|
+
/** All styles in canonical order, for random selection. */
|
|
26519
26655
|
const DEEPSEEK_WAVE_STYLES = [
|
|
26520
26656
|
"wave",
|
|
26521
26657
|
"aurora",
|
|
@@ -26523,6 +26659,9 @@ const DEEPSEEK_WAVE_STYLES = [
|
|
|
26523
26659
|
];
|
|
26524
26660
|
/** Pulse ring half-width in columns — Codex PULSE_HALF_WIDTH (4.5). */
|
|
26525
26661
|
const PULSE_HALF_WIDTH = 4.5;
|
|
26662
|
+
/** Sparkle start and frame cadence — Codex SPARK_START / SPARK_FRAME. */
|
|
26663
|
+
const SPARK_START_MS = 900;
|
|
26664
|
+
const SPARK_FRAME_MS = 100;
|
|
26526
26665
|
/** Sparkle glyphs in frame order — Codex SPARK_GLYPHS (`· ✦ ✧`). */
|
|
26527
26666
|
const SPARK_GLYPHS = [
|
|
26528
26667
|
"·",
|
|
@@ -26544,6 +26683,15 @@ const DEEPSEEK_WAVE_BANDS = {
|
|
|
26544
26683
|
.35,
|
|
26545
26684
|
.55,
|
|
26546
26685
|
1
|
|
26686
|
+
]],
|
|
26687
|
+
unknown: [[
|
|
26688
|
+
.1,
|
|
26689
|
+
.7,
|
|
26690
|
+
1
|
|
26691
|
+
], [
|
|
26692
|
+
.35,
|
|
26693
|
+
.55,
|
|
26694
|
+
1
|
|
26547
26695
|
]]
|
|
26548
26696
|
},
|
|
26549
26697
|
aurora: {
|
|
@@ -26572,6 +26720,23 @@ const DEEPSEEK_WAVE_BANDS = {
|
|
|
26572
26720
|
.35,
|
|
26573
26721
|
2
|
|
26574
26722
|
]
|
|
26723
|
+
],
|
|
26724
|
+
unknown: [
|
|
26725
|
+
[
|
|
26726
|
+
.35,
|
|
26727
|
+
.15,
|
|
26728
|
+
0
|
|
26729
|
+
],
|
|
26730
|
+
[
|
|
26731
|
+
-.5,
|
|
26732
|
+
.6,
|
|
26733
|
+
1
|
|
26734
|
+
],
|
|
26735
|
+
[
|
|
26736
|
+
.75,
|
|
26737
|
+
.35,
|
|
26738
|
+
2
|
|
26739
|
+
]
|
|
26575
26740
|
]
|
|
26576
26741
|
},
|
|
26577
26742
|
pulse: {
|
|
@@ -26588,15 +26753,27 @@ const DEEPSEEK_WAVE_BANDS = {
|
|
|
26588
26753
|
.45,
|
|
26589
26754
|
.55,
|
|
26590
26755
|
1.1
|
|
26756
|
+
]],
|
|
26757
|
+
unknown: [[
|
|
26758
|
+
.1,
|
|
26759
|
+
.55,
|
|
26760
|
+
.8
|
|
26761
|
+
], [
|
|
26762
|
+
.45,
|
|
26763
|
+
.55,
|
|
26764
|
+
1.1
|
|
26591
26765
|
]]
|
|
26592
26766
|
}
|
|
26593
26767
|
};
|
|
26768
|
+
/** Extra display time applied to every Codex ignition style. */
|
|
26769
|
+
const DEEPSEEK_WAVE_DURATION_EXTENSION_MS = 200;
|
|
26594
26770
|
/** Original Codex duration used as the animation's sampling timeline. */
|
|
26595
26771
|
function deepseekWaveBaseDuration(tier, style) {
|
|
26772
|
+
const pro = tier === "deepseek" || tier === "unknown";
|
|
26596
26773
|
switch (style) {
|
|
26597
|
-
case "aurora": return
|
|
26598
|
-
case "pulse": return
|
|
26599
|
-
case "wave": return
|
|
26774
|
+
case "aurora": return pro ? 1600 : 1300;
|
|
26775
|
+
case "pulse": return pro ? 1250 : 900;
|
|
26776
|
+
case "wave": return pro ? 1300 : 1e3;
|
|
26600
26777
|
}
|
|
26601
26778
|
}
|
|
26602
26779
|
/**
|
|
@@ -26607,7 +26784,7 @@ function deepseekWaveBaseDuration(tier, style) {
|
|
|
26607
26784
|
* @returns the duration in milliseconds.
|
|
26608
26785
|
*/
|
|
26609
26786
|
function deepseekWaveDuration(tier, style = "wave") {
|
|
26610
|
-
return deepseekWaveBaseDuration(tier, style) +
|
|
26787
|
+
return deepseekWaveBaseDuration(tier, style) + DEEPSEEK_WAVE_DURATION_EXTENSION_MS;
|
|
26611
26788
|
}
|
|
26612
26789
|
/** Map the extended display timeline back onto the original Codex samples. */
|
|
26613
26790
|
function deepseekWaveSampleElapsedMs(tick, tier, style) {
|
|
@@ -26625,22 +26802,6 @@ function deepseekWaveStyleRandom(previous) {
|
|
|
26625
26802
|
return candidates[Math.floor(Math.random() * candidates.length)] ?? "wave";
|
|
26626
26803
|
}
|
|
26627
26804
|
/**
|
|
26628
|
-
* Blank-cell background the wave tint blends toward — fixed approximations
|
|
26629
|
-
* of the terminal's default background, mirroring Codex's
|
|
26630
|
-
* `user_message_bg_rgb` (which derives a near-black / near-white bubble tint
|
|
26631
|
-
* from the terminal background). The Ink layer picks the active theme's one.
|
|
26632
|
-
*/
|
|
26633
|
-
const WAVE_BASE_DARK = [
|
|
26634
|
-
16,
|
|
26635
|
-
18,
|
|
26636
|
-
24
|
|
26637
|
-
];
|
|
26638
|
-
const WAVE_BASE_LIGHT = [
|
|
26639
|
-
242,
|
|
26640
|
-
244,
|
|
26641
|
-
248
|
|
26642
|
-
];
|
|
26643
|
-
/**
|
|
26644
26805
|
* Tier for a `provider/model` label: a model id containing `flash` runs the
|
|
26645
26806
|
* single-band flash tier; everything else (pro/reasoner/chat) runs the
|
|
26646
26807
|
* dual-band deepseek tier. Mirrors Codex's Max→Ultra mapping.
|
|
@@ -26734,14 +26895,22 @@ function blendRgb(fg, bg, alpha) {
|
|
|
26734
26895
|
];
|
|
26735
26896
|
}
|
|
26736
26897
|
/**
|
|
26737
|
-
*
|
|
26898
|
+
* Per-row phase share of the duration: the crest reaches the top row first
|
|
26899
|
+
* and the bottom row last, sweeping down the band. 0.12 keeps the bottom
|
|
26900
|
+
* row's lag inside the 200ms duration extension.
|
|
26901
|
+
*/
|
|
26902
|
+
const DEEPSEEK_WAVE_ROW_PHASE = .12;
|
|
26903
|
+
/**
|
|
26904
|
+
* The background color for one composer-band column at a tick — Codex
|
|
26738
26905
|
* `paint_bands` + `Canvas::tint` for all three styles. Bands overlap with a
|
|
26739
26906
|
* max for Wave/Pulse and a SUM for Aurora (Codex differs by style), the
|
|
26740
26907
|
* weighted hues mix per column (Wave/Pulse always end on hue 0), the tint
|
|
26741
26908
|
* blends the mixed hue toward the blank-cell base at the style's alpha cap,
|
|
26742
26909
|
* and Aurora applies its own fade envelope. Returns `null` when the column
|
|
26743
26910
|
* should stay transparent, so the row returns to no `backgroundColor` on
|
|
26744
|
-
* both ends.
|
|
26911
|
+
* both ends. With `rows > 1` each row samples the same timeline shifted by a
|
|
26912
|
+
* per-row phase offset, so the crest cascades down the band instead of
|
|
26913
|
+
* painting every row identically.
|
|
26745
26914
|
* @param tick - wave frame (0, 1, … at DEEPSEEK_WAVE_TICK_MS).
|
|
26746
26915
|
* @param column - column index in the content row (0..width-1).
|
|
26747
26916
|
* @param width - content-row width in columns.
|
|
@@ -26749,11 +26918,13 @@ function blendRgb(fg, bg, alpha) {
|
|
|
26749
26918
|
* @param style - the ignition style.
|
|
26750
26919
|
* @param hues - the tier's three hues.
|
|
26751
26920
|
* @param base - the blank-cell base color the tint blends toward.
|
|
26921
|
+
* @param row - row index in the band (0..rows-1; default 0 = old single-row).
|
|
26922
|
+
* @param rows - band height in rows (default 1).
|
|
26752
26923
|
* @returns the blended RGB background, or null for transparent.
|
|
26753
26924
|
*/
|
|
26754
|
-
function deepseekWaveColumnBg(tick, column, width, tier, style, hues, base) {
|
|
26925
|
+
function deepseekWaveColumnBg(tick, column, width, tier, style, hues, base, row = 0, rows = 1) {
|
|
26755
26926
|
const total = deepseekWaveBaseDuration(tier, style) / 1e3;
|
|
26756
|
-
const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1e3;
|
|
26927
|
+
const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1e3 - (row - (rows - 1) / 2) * total * DEEPSEEK_WAVE_ROW_PHASE;
|
|
26757
26928
|
const fade = style === "aurora" ? envelope(elapsed, total, .25, .4) : 1;
|
|
26758
26929
|
const weights = [
|
|
26759
26930
|
0,
|
|
@@ -26792,27 +26963,11 @@ function deepseekWaveColumnBg(tick, column, width, tier, style, hues, base) {
|
|
|
26792
26963
|
*/
|
|
26793
26964
|
function deepseekWaveSpark(tick) {
|
|
26794
26965
|
const elapsed = deepseekWaveSampleElapsedMs(tick, "deepseek", "wave");
|
|
26795
|
-
if (elapsed <
|
|
26796
|
-
const frame = Math.floor((elapsed -
|
|
26966
|
+
if (elapsed < SPARK_START_MS) return null;
|
|
26967
|
+
const frame = Math.floor((elapsed - SPARK_START_MS) / SPARK_FRAME_MS);
|
|
26797
26968
|
return SPARK_GLYPHS[frame] ?? null;
|
|
26798
26969
|
}
|
|
26799
26970
|
/**
|
|
26800
|
-
* The composer BORDER color at a tick: the frame breathes with the wave —
|
|
26801
|
-
* the palette's static dim blends toward the tier accent as the crest is
|
|
26802
|
-
* alive and back, so the frame glows up while the wave sweeps and settles to
|
|
26803
|
-
* dim on both ends (first==last frame==dim, no hard jump).
|
|
26804
|
-
* @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
|
|
26805
|
-
* @param tier - the wave tier.
|
|
26806
|
-
* @param hues - the tier's three hues; the border blends toward hues[0].
|
|
26807
|
-
* @param dim - the palette's static dim RGB (the resting border color).
|
|
26808
|
-
* @returns the blended border RGB.
|
|
26809
|
-
*/
|
|
26810
|
-
function deepseekWaveBorderColor(tick, tier, style, hues, dim) {
|
|
26811
|
-
const total = deepseekWaveBaseDuration(tier, style) / 1e3;
|
|
26812
|
-
const glow = envelope(deepseekWaveSampleElapsedMs(tick, tier, style) / 1e3, total, total * .25, total * .4);
|
|
26813
|
-
return blendRgb(hues[0], dim, .25 + glow * .6);
|
|
26814
|
-
}
|
|
26815
|
-
/**
|
|
26816
26971
|
* Whether the `deepseek` wordmark rides the wave at this tick: it fades in
|
|
26817
26972
|
* shortly after the first crest launches and out before the wave settles,
|
|
26818
26973
|
* so the brand name surfaces through the sweep's middle. The Ink layer
|
|
@@ -26848,14 +27003,46 @@ function isOfficialDeepSeekLabel(label) {
|
|
|
26848
27003
|
const model = slash < 0 ? "" : label.slice(slash + 1);
|
|
26849
27004
|
return provider.toLowerCase().includes("deepseek") || model.toLowerCase().includes("deepseek");
|
|
26850
27005
|
}
|
|
27006
|
+
/**
|
|
27007
|
+
* Known reasoning-effort ranks in ascending order. Effort ids are opaque
|
|
27008
|
+
* adapter-owned strings, so the rank table covers the conventional names
|
|
27009
|
+
* (off → low → medium → high → xhigh → max/ultra); an unrecognized id
|
|
27010
|
+
* ranks as unknown (0), which never triggers the high-effort wave.
|
|
27011
|
+
*/
|
|
27012
|
+
const EFFORT_RANK = {
|
|
27013
|
+
off: 0,
|
|
27014
|
+
none: 0,
|
|
27015
|
+
low: 1,
|
|
27016
|
+
medium: 2,
|
|
27017
|
+
med: 2,
|
|
27018
|
+
high: 3,
|
|
27019
|
+
xhigh: 4,
|
|
27020
|
+
"x-high": 4,
|
|
27021
|
+
"very-high": 4,
|
|
27022
|
+
max: 5,
|
|
27023
|
+
maximum: 5,
|
|
27024
|
+
ultra: 5
|
|
27025
|
+
};
|
|
27026
|
+
/**
|
|
27027
|
+
* True when an effective reasoning effort is STRICTLY above `high` — the
|
|
27028
|
+
* trigger gate for the "Into the Unknown" wave on non-DeepSeek routes.
|
|
27029
|
+
* Absent efforts and unrecognized ids never qualify.
|
|
27030
|
+
* @param effort - the effective reasoning-effort id ('' or undefined when none).
|
|
27031
|
+
* @returns whether the effort ranks above high.
|
|
27032
|
+
*/
|
|
27033
|
+
function effortAboveHigh(effort) {
|
|
27034
|
+
if (effort === void 0 || effort === "") return false;
|
|
27035
|
+
const rank = EFFORT_RANK[effort.trim().toLowerCase()];
|
|
27036
|
+
return rank !== void 0 && rank > 3;
|
|
27037
|
+
}
|
|
26851
27038
|
//#endregion
|
|
26852
27039
|
//#region src/session-directory.ts
|
|
26853
27040
|
/** Lightweight session-directory projection for the /resume picker. */
|
|
26854
27041
|
/** Case-insensitive filesystems (Windows, macOS) compare paths by lowercased form. */
|
|
26855
27042
|
const CASE_INSENSITIVE_FS = process.platform === "win32" || process.platform === "darwin";
|
|
26856
|
-
/** True
|
|
27043
|
+
/** True only for delegated subagents; ordinary forks also carry lineage. */
|
|
26857
27044
|
function isSubagentSession(header) {
|
|
26858
|
-
return header.origin === "subagent"
|
|
27045
|
+
return header.origin === "subagent";
|
|
26859
27046
|
}
|
|
26860
27047
|
function comparablePath(value) {
|
|
26861
27048
|
const resolved = resolve(value);
|
|
@@ -27075,10 +27262,18 @@ function cacheHitPercent(usage) {
|
|
|
27075
27262
|
const STATUS_ITEM_SEPARATOR = " · ";
|
|
27076
27263
|
/** The Codex-style mode cycle hint appended to the permission badge. */
|
|
27077
27264
|
const STATUS_CYCLE_HINT = " (shift+tab to cycle)";
|
|
27265
|
+
/** Occupancy at which the usage readout flips from brand blue to amber. */
|
|
27266
|
+
const CONTEXT_WARN_PERCENT = 90;
|
|
27078
27267
|
/** Free-tail floor in columns: wide enough for the bare percent readout, so
|
|
27079
27268
|
* the warning stays visible even at 100%+ occupancy. */
|
|
27080
27269
|
const CONTEXT_MIN_FREE = 5;
|
|
27081
27270
|
/**
|
|
27271
|
+
* Narrowest bar width the drop ladder tries before giving up on the context
|
|
27272
|
+
* group: the bar shrinks inside its own budget first (a few columns still
|
|
27273
|
+
* show the bare percent readout) and only then drops as a whole.
|
|
27274
|
+
*/
|
|
27275
|
+
const CONTEXT_MIN_WIDTH = 5;
|
|
27276
|
+
/**
|
|
27082
27277
|
* Render context occupancy as ONE stepless bar: a solid DeepSeek-blue fill
|
|
27083
27278
|
* run, a dim dotted free track, and the usage readout riding the track's
|
|
27084
27279
|
* right edge (`12.3K/1.0M 25%`, shrinking to the bare percent as the track
|
|
@@ -27096,7 +27291,7 @@ function contextBar(usedTokens, contextWindow, width) {
|
|
|
27096
27291
|
if (width <= 0 || contextWindow <= 0) return [];
|
|
27097
27292
|
const used = Math.max(0, usedTokens);
|
|
27098
27293
|
const percent = Math.round(used / contextWindow * 100);
|
|
27099
|
-
const readoutTone = percent >=
|
|
27294
|
+
const readoutTone = percent >= CONTEXT_WARN_PERCENT ? "warn" : "value";
|
|
27100
27295
|
const freeShare = Math.round(Math.max(0, contextWindow - used) / contextWindow * width);
|
|
27101
27296
|
const freeColumns = Math.min(width, Math.max(freeShare, CONTEXT_MIN_FREE));
|
|
27102
27297
|
const usedColumns = Math.max(0, width - freeColumns);
|
|
@@ -27288,7 +27483,7 @@ function joinWidth(parts, separator) {
|
|
|
27288
27483
|
return width + separator * (parts.length - 1);
|
|
27289
27484
|
}
|
|
27290
27485
|
/** Build every candidate group/span with its drop rank and item id. */
|
|
27291
|
-
function buildCandidates(facts, stats, busy, enabled) {
|
|
27486
|
+
function buildCandidates(facts, stats, busy, enabled, contextWidth) {
|
|
27292
27487
|
const identity = [{
|
|
27293
27488
|
text: busy ? "● " : "○ ",
|
|
27294
27489
|
tone: busy ? "live" : "meta"
|
|
@@ -27410,7 +27605,7 @@ function buildCandidates(facts, stats, busy, enabled) {
|
|
|
27410
27605
|
group: { spans: [{
|
|
27411
27606
|
text: "context ",
|
|
27412
27607
|
tone: "label"
|
|
27413
|
-
}, ...contextBar(stats.lastPromptTokens, stats.contextWindow,
|
|
27608
|
+
}, ...contextBar(stats.lastPromptTokens, stats.contextWindow, contextWidth)] },
|
|
27414
27609
|
rank: RANK_CONTEXT,
|
|
27415
27610
|
id: "context"
|
|
27416
27611
|
});
|
|
@@ -27499,7 +27694,8 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
|
|
|
27499
27694
|
const items = options.items ?? DEFAULT_STATUSLINE_ITEMS;
|
|
27500
27695
|
const enabled = new Set(items);
|
|
27501
27696
|
const budget = Math.max(1, Math.floor(columns) - WIDTH_SAFETY);
|
|
27502
|
-
const
|
|
27697
|
+
const maxContextWidth = Math.max(CONTEXT_MIN_WIDTH, Math.min(budget, Math.floor(options.contextWidth ?? 24)));
|
|
27698
|
+
const { left, right, badge, row2 } = buildCandidates(facts, stats, busy, enabled, maxContextWidth);
|
|
27503
27699
|
const groupSeparator = visibleColumns(" | ");
|
|
27504
27700
|
const itemSeparator = visibleColumns(STATUS_ITEM_SEPARATOR);
|
|
27505
27701
|
const position = new Map(items.map((id, index) => [id, index]));
|
|
@@ -27510,12 +27706,51 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
|
|
|
27510
27706
|
let hint = badge >= 0 && !busy;
|
|
27511
27707
|
const leftKept = [...orderedLeft];
|
|
27512
27708
|
const rightKept = [...orderedRight];
|
|
27709
|
+
let contextWidth = maxContextWidth;
|
|
27710
|
+
const rebuildContext = () => {
|
|
27711
|
+
const index = leftKept.findIndex((entry) => entry.id === "context");
|
|
27712
|
+
if (index < 0) return;
|
|
27713
|
+
leftKept[index] = {
|
|
27714
|
+
group: { spans: [{
|
|
27715
|
+
text: "context ",
|
|
27716
|
+
tone: "label"
|
|
27717
|
+
}, ...contextBar(stats.lastPromptTokens, stats.contextWindow, contextWidth)] },
|
|
27718
|
+
rank: RANK_CONTEXT,
|
|
27719
|
+
id: "context"
|
|
27720
|
+
};
|
|
27721
|
+
};
|
|
27513
27722
|
const width = () => {
|
|
27514
27723
|
const leftWidth = joinWidth(leftKept.map((entry) => spansWidth(entry.group.spans)), groupSeparator);
|
|
27515
27724
|
const rightWidth = joinWidth(rightKept.map((entry) => visibleColumns(entry.span.text)), itemSeparator) + (hint ? visibleColumns(STATUS_CYCLE_HINT) : 0);
|
|
27516
27725
|
return rightWidth > 0 ? leftWidth + LEFT_RIGHT_GAP + rightWidth : leftWidth;
|
|
27517
27726
|
};
|
|
27518
27727
|
while (width() > budget) {
|
|
27728
|
+
if (leftKept.some((entry) => entry.id === "context") && contextWidth > CONTEXT_MIN_WIDTH) {
|
|
27729
|
+
const overflow = width() - budget;
|
|
27730
|
+
contextWidth = Math.max(CONTEXT_MIN_WIDTH, contextWidth - overflow);
|
|
27731
|
+
rebuildContext();
|
|
27732
|
+
continue;
|
|
27733
|
+
}
|
|
27734
|
+
const contextIndex = leftKept.findIndex((entry) => entry.id === "context");
|
|
27735
|
+
if (contextIndex >= 0) {
|
|
27736
|
+
leftKept.splice(contextIndex, 1);
|
|
27737
|
+
continue;
|
|
27738
|
+
}
|
|
27739
|
+
if (hint && rightKept.length > 0 && leftKept.length > 0) {
|
|
27740
|
+
const identity = leftKept[0];
|
|
27741
|
+
const identityText = identity.group.spans.map((span) => span.text).join("");
|
|
27742
|
+
const identityBudget = budget - joinWidth(rightKept.map((entry) => visibleColumns(entry.span.text)), itemSeparator) - LEFT_RIGHT_GAP - visibleColumns(STATUS_CYCLE_HINT);
|
|
27743
|
+
if (identityBudget > 0 && visibleColumns(identityText) > identityBudget) {
|
|
27744
|
+
leftKept[0] = {
|
|
27745
|
+
...identity,
|
|
27746
|
+
group: { spans: [{
|
|
27747
|
+
text: truncateColumns(identityText, identityBudget),
|
|
27748
|
+
tone: "model"
|
|
27749
|
+
}] }
|
|
27750
|
+
};
|
|
27751
|
+
continue;
|
|
27752
|
+
}
|
|
27753
|
+
}
|
|
27519
27754
|
if (hint) {
|
|
27520
27755
|
hint = false;
|
|
27521
27756
|
continue;
|
|
@@ -27586,6 +27821,7 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
|
|
|
27586
27821
|
}
|
|
27587
27822
|
//#endregion
|
|
27588
27823
|
//#region src/render/lines.ts
|
|
27824
|
+
/** Width-safe styled physical rows for bounded terminal panels. */
|
|
27589
27825
|
/** Construct one segment without leaking mutable objects into cached rows. */
|
|
27590
27826
|
function lineSegment(text, style = "plain") {
|
|
27591
27827
|
return {
|
|
@@ -27643,6 +27879,59 @@ function styledLines(segments, columns) {
|
|
|
27643
27879
|
function textLines(text, columns, style = "plain") {
|
|
27644
27880
|
return styledLines([lineSegment(text, style)], columns);
|
|
27645
27881
|
}
|
|
27882
|
+
/** Prefix every wrapped physical row without exceeding the column budget. */
|
|
27883
|
+
function prefixedStyledLines(segments, columns, prefix, prefixStyle = "plain") {
|
|
27884
|
+
const width = Math.max(1, Math.floor(columns));
|
|
27885
|
+
const prefixWidth = Math.min(width, visibleColumns(prefix));
|
|
27886
|
+
return styledLines(segments, Math.max(1, width - prefixWidth)).map((line) => ({ segments: [lineSegment(prefix, prefixStyle), ...line.segments] }));
|
|
27887
|
+
}
|
|
27888
|
+
/** Text convenience for a tool row whose continuation must keep its gutter. */
|
|
27889
|
+
function prefixedTextLines(text, columns, prefix, style = "plain") {
|
|
27890
|
+
return prefixedStyledLines([lineSegment(text, style)], columns, prefix, style);
|
|
27891
|
+
}
|
|
27892
|
+
/**
|
|
27893
|
+
* Wrap styled segments with a hanging indent: the first physical row carries
|
|
27894
|
+
* `firstPrefix` (often a marker plus gutter) and every wrapped continuation
|
|
27895
|
+
* carries the narrower `contPrefix`, so long tool summaries and prompts
|
|
27896
|
+
* align under their card instead of falling back to column zero. The first
|
|
27897
|
+
* row may hold one prefix-width more than the continuations.
|
|
27898
|
+
*/
|
|
27899
|
+
function hangingStyledLines(segments, columns, firstPrefix, firstStyle, contPrefix, contStyle = firstStyle) {
|
|
27900
|
+
const width = Math.max(2, Math.floor(columns));
|
|
27901
|
+
const firstPrefixText = truncateColumns(firstPrefix, Math.max(1, width - 1));
|
|
27902
|
+
const contPrefixText = truncateColumns(contPrefix, Math.max(1, width - 1));
|
|
27903
|
+
const firstBudget = Math.max(1, width - visibleColumns(firstPrefixText));
|
|
27904
|
+
const contBudget = Math.max(1, width - visibleColumns(contPrefixText));
|
|
27905
|
+
const lines = [];
|
|
27906
|
+
let current = [];
|
|
27907
|
+
let used = 0;
|
|
27908
|
+
let budget = firstBudget;
|
|
27909
|
+
const flush = () => {
|
|
27910
|
+
lines.push({ segments: current });
|
|
27911
|
+
current = [];
|
|
27912
|
+
used = 0;
|
|
27913
|
+
budget = contBudget;
|
|
27914
|
+
};
|
|
27915
|
+
for (const segment of segments) {
|
|
27916
|
+
const safe = displayText(segment.text).replaceAll(" ", " ").replaceAll("\r", "");
|
|
27917
|
+
for (const char of safe) {
|
|
27918
|
+
if (char === "\n") {
|
|
27919
|
+
flush();
|
|
27920
|
+
continue;
|
|
27921
|
+
}
|
|
27922
|
+
const cells = visibleColumns(char);
|
|
27923
|
+
if (used > 0 && used + cells > budget) flush();
|
|
27924
|
+
appendSegment(current, char, segment.style);
|
|
27925
|
+
used += cells;
|
|
27926
|
+
}
|
|
27927
|
+
}
|
|
27928
|
+
if (current.length > 0 || lines.length === 0) flush();
|
|
27929
|
+
return lines.map((line, index) => ({ segments: [lineSegment(index === 0 ? firstPrefixText : contPrefixText, index === 0 ? firstStyle : contStyle), ...line.segments] }));
|
|
27930
|
+
}
|
|
27931
|
+
/** Plain-text convenience over {@link hangingStyledLines}. */
|
|
27932
|
+
function hangingTextLines(text, columns, firstPrefix, firstStyle = "plain", contPrefix = " ", contStyle = firstStyle) {
|
|
27933
|
+
return hangingStyledLines([lineSegment(text, firstStyle)], columns, firstPrefix, firstStyle, contPrefix, contStyle);
|
|
27934
|
+
}
|
|
27646
27935
|
/** Markdown rows re-hardened so a single long word cannot escape the budget. */
|
|
27647
27936
|
function markdownLines(text, columns) {
|
|
27648
27937
|
const width = Math.max(1, Math.floor(columns));
|
|
@@ -27660,68 +27949,517 @@ function reasoningLines(text, columns) {
|
|
|
27660
27949
|
return displayText(text).replaceAll(" ", " ").replaceAll("\r", "").split("\n").flatMap((line) => styledLines([lineSegment(line, "dimItalic")], contentWidth)).map((line, index) => ({ segments: [lineSegment(index === 0 ? "✻ " : " ", "dimItalic"), ...line.segments] }));
|
|
27661
27950
|
}
|
|
27662
27951
|
/** Expanded structured tool detail as scrollable, width-safe rows. */
|
|
27952
|
+
/**
|
|
27953
|
+
* Detail rows share the tool card's four-column hanging gutter: the summary
|
|
27954
|
+
* (⎿) and delegation prompt (└) continuations already sit at four columns, so
|
|
27955
|
+
* diff/read/web/raw rows align under them instead of floating two columns
|
|
27956
|
+
* shallower.
|
|
27957
|
+
*/
|
|
27663
27958
|
function toolDetailLines(detail, columns) {
|
|
27664
27959
|
switch (detail.kind) {
|
|
27665
|
-
case "diff": return detail.diffs.flatMap((diff) => [...
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
27669
|
-
|
|
27670
|
-
case "read": return [...textLines(` ── ${detail.path} · lines ${detail.offset}-${detail.lines.length > 0 ? detail.lines[detail.lines.length - 1].number : detail.offset - 1} of ${detail.totalLines}${detail.truncated ? " (window truncated)" : ""}`, columns, "dim"), ...detail.lines.flatMap((line) => textLines(` ${String(line.number).padStart(5, " ")} | ${line.text}`, columns, "dim"))];
|
|
27671
|
-
case "web-search": return [...detail.sources.flatMap((source) => [...styledLines([lineSegment(` ? ${source.title ?? source.url}`, "brand"), lineSegment(` - ${source.url}`, "dim")], columns), ...source.snippet === "" ? [] : textLines(` ${source.snippet}`, columns, "dim")]), ...textLines(` ${detail.sources.length} sources${detail.truncated ? " (capped)" : ""}`, columns, "dim")];
|
|
27672
|
-
case "web-fetch": return textLines(` ${detail.url} · HTTP ${detail.statusCode}`, columns, "dim");
|
|
27673
|
-
case "raw": return [...textLines(` ${detail.text}`, columns, "dim"), ...textLines(detail.truncated ? " … (output truncated)" : " (end of output)", columns, "dim")];
|
|
27960
|
+
case "diff": return detail.diffs.flatMap((diff) => [...prefixedTextLines(`${diff.path}${diff.truncated ? " (diff truncated)" : ""}`, columns, " ── ", "dim"), ...diff.lines.flatMap((line) => prefixedTextLines(`${line.mark}${line.text}`, columns, " ", line.mark === "+" ? "success" : line.mark === "-" ? "error" : "dim"))]);
|
|
27961
|
+
case "read": return [...prefixedTextLines(`${detail.path} · lines ${detail.offset}-${detail.lines.length > 0 ? detail.lines[detail.lines.length - 1].number : detail.offset - 1} of ${detail.totalLines}${detail.truncated ? " (window truncated)" : ""}`, columns, " ── ", "dim"), ...detail.lines.flatMap((line) => prefixedTextLines(`${String(line.number).padStart(5, " ")} | ${line.text}`, columns, " ", "dim"))];
|
|
27962
|
+
case "web-search": return [...detail.sources.flatMap((source) => [...prefixedStyledLines([lineSegment(source.title ?? source.url, "brand"), lineSegment(` - ${source.url}`, "dim")], columns, " ? "), ...source.snippet === "" ? [] : prefixedTextLines(source.snippet, columns, " ", "dim")]), ...prefixedTextLines(`${detail.sources.length} sources${detail.truncated ? " (capped)" : ""}`, columns, " ", "dim")];
|
|
27963
|
+
case "web-fetch": return prefixedTextLines(`${detail.url} · HTTP ${detail.statusCode}`, columns, " ", "dim");
|
|
27964
|
+
case "raw": return [...prefixedTextLines(detail.text, columns, " ", "dim"), ...prefixedTextLines(detail.truncated ? "… (output truncated)" : "(end of output)", columns, " ", "dim")];
|
|
27674
27965
|
default: return detail;
|
|
27675
27966
|
}
|
|
27676
27967
|
}
|
|
27677
27968
|
/**
|
|
27678
27969
|
* Convert one durable transcript entry to its complete scrollable row model.
|
|
27679
27970
|
* The source entry stays intact; only the caller's visible slice is rendered.
|
|
27971
|
+
* Wrapped continuations keep a hanging indent aligned under each row's
|
|
27972
|
+
* content (Codex history-cell alignment) instead of resetting to column 0.
|
|
27680
27973
|
*/
|
|
27681
|
-
function transcriptEntryLines(entry, columns) {
|
|
27974
|
+
function transcriptEntryLines(entry, columns, showReasoning = true, reasoningToggleHint = true) {
|
|
27682
27975
|
const width = Math.max(1, Math.floor(columns));
|
|
27683
27976
|
switch (entry.kind) {
|
|
27684
|
-
case "user": return
|
|
27685
|
-
case "pending": return
|
|
27977
|
+
case "user": return entry.notice ? hangingStyledLines([lineSegment(promptDisplayText(entry), "dim")], width, "⤷ ", "dim", " ", "dim") : hangingStyledLines([lineSegment(promptDisplayText(entry), "plain")], width, "❯ ", "brand", " ", "plain");
|
|
27978
|
+
case "pending": return hangingStyledLines([lineSegment(promptDisplayText(entry), "plain")], width, "❯ ", "brand", " ", "plain");
|
|
27686
27979
|
case "assistant": {
|
|
27687
|
-
const reasoning = entry.reasoning === "" ? [] : reasoningLines(entry.reasoning, width);
|
|
27980
|
+
const reasoning = entry.reasoning === "" ? [] : showReasoning ? reasoningLines(entry.reasoning, width) : textLines(`✻ Thinking (${entry.reasoning.length} chars${reasoningToggleHint ? ", Ctrl+R to expand" : ""})`, width, "dim");
|
|
27688
27981
|
const body = markdownLines(entry.text, Math.max(10, width - 2)).map((line) => ({ segments: [{
|
|
27689
27982
|
text: " ",
|
|
27690
27983
|
style: "plain"
|
|
27691
27984
|
}, ...line.segments] }));
|
|
27692
|
-
|
|
27985
|
+
const interrupted = entry.interrupted === true ? textLines(" ⏹ interrupted", width, "dim") : [];
|
|
27986
|
+
return [
|
|
27987
|
+
...reasoning,
|
|
27988
|
+
...body,
|
|
27989
|
+
...interrupted
|
|
27990
|
+
];
|
|
27693
27991
|
}
|
|
27694
27992
|
case "tool": {
|
|
27695
27993
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
27696
27994
|
const markStyle = entry.state === "running" ? "brand" : entry.state === "error" ? "error" : "success";
|
|
27995
|
+
const summaryStyle = entry.state === "error" ? "error" : "dim";
|
|
27697
27996
|
return [
|
|
27698
|
-
...
|
|
27699
|
-
lineSegment(
|
|
27997
|
+
...hangingStyledLines([
|
|
27998
|
+
lineSegment(`[${entry.ordinal}] `, "dim"),
|
|
27700
27999
|
lineSegment(entry.name, "brand"),
|
|
27701
28000
|
lineSegment(entry.preview === "" ? "" : ` ${entry.preview}`, "dim")
|
|
27702
|
-
], width),
|
|
27703
|
-
...entry.prompt === "" ? [] :
|
|
27704
|
-
...entry.summary === "" ? [] :
|
|
28001
|
+
], width, `${mark} `, markStyle, " ", "plain"),
|
|
28002
|
+
...entry.prompt === "" ? [] : hangingTextLines(entry.prompt, width, " └ ", "dim", " "),
|
|
28003
|
+
...entry.summary === "" ? [] : hangingTextLines(entry.state === "error" ? `call ${entry.ordinal}: ${entry.summary}` : entry.summary, width, " ⎿ ", summaryStyle, " ", summaryStyle),
|
|
27705
28004
|
...entry.detail === void 0 ? [] : toolDetailLines(entry.detail, width)
|
|
27706
28005
|
];
|
|
27707
28006
|
}
|
|
27708
28007
|
case "command": {
|
|
27709
28008
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
27710
28009
|
const markStyle = entry.state === "running" ? "brand" : entry.state === "error" ? "error" : "success";
|
|
27711
|
-
|
|
27712
|
-
|
|
27713
|
-
lineSegment(`/${entry.name}`, "brand"),
|
|
27714
|
-
lineSegment(entry.args === "" ? "" : ` ${entry.args}`, "dim")
|
|
27715
|
-
], width), ...entry.summary === "" ? [] : textLines(` ⎿ ${entry.summary}`, width, entry.state === "error" ? "error" : "dim")];
|
|
28010
|
+
const summaryStyle = entry.state === "error" ? "error" : "dim";
|
|
28011
|
+
return [...hangingStyledLines([lineSegment(`/${entry.name}`, "brand"), lineSegment(entry.args === "" ? "" : ` ${entry.args}`, "dim")], width, `${mark} `, markStyle, " ", "plain"), ...entry.summary === "" ? [] : hangingTextLines(entry.summary, width, " ⎿ ", summaryStyle, " ", summaryStyle)];
|
|
27716
28012
|
}
|
|
27717
28013
|
case "turn-marker": return textLines(` ⏹ ${entry.text}`, width, "dim");
|
|
27718
28014
|
case "compaction": return textLines(entry.ok ? ` ⧉ compacted ~${formatTokens(entry.tokens)} tokens` : ` ⧉ compaction failed: ${entry.error}`, width, "dim");
|
|
27719
28015
|
case "retry": return textLines(` ↻ retry ${entry.attempt}/${entry.max} · ${entry.code} · ${Math.round(entry.delayMs / 100) / 10}s`, width, entry.state === "running" ? "warn" : "dim");
|
|
27720
|
-
case "files": return entry.paths.length === 0 ? textLines(" ⎄ no changed files", width, "dim") : [...textLines(` ⎄ ${entry.paths.length} changed file${entry.paths.length === 1 ? "" : "s"}`, width, "dim"), ...entry.paths.flatMap((path) =>
|
|
28016
|
+
case "files": return entry.paths.length === 0 ? textLines(" ⎄ no changed files", width, "dim") : [...textLines(` ⎄ ${entry.paths.length} changed file${entry.paths.length === 1 ? "" : "s"}`, width, "dim"), ...entry.paths.flatMap((path) => hangingTextLines(path, width, " ", "dim", " "))];
|
|
27721
28017
|
case "error": return textLines(entry.text, width, "error");
|
|
27722
28018
|
default: return entry;
|
|
27723
28019
|
}
|
|
27724
28020
|
}
|
|
28021
|
+
/** Settled-history variant carrying the Ctrl+R reasoning fold. */
|
|
28022
|
+
function settledEntryLines(entry, columns, showReasoning) {
|
|
28023
|
+
return transcriptEntryLines(entry, columns, showReasoning, false);
|
|
28024
|
+
}
|
|
28025
|
+
//#endregion
|
|
28026
|
+
//#region src/render/editor.ts
|
|
28027
|
+
/**
|
|
28028
|
+
* Pure composer editor model with Codex textarea semantics: a grapheme
|
|
28029
|
+
* cursor over a column-safe multiline layout, word/piece motion, single-entry
|
|
28030
|
+
* kill + yank, and the shell-recall boundary gate that keeps Up/Down usable
|
|
28031
|
+
* inside a multiline draft.
|
|
28032
|
+
*
|
|
28033
|
+
* The model is intentionally string-offset based (UTF-16 indices clamped to
|
|
28034
|
+
* grapheme boundaries) so the React state stays two primitives
|
|
28035
|
+
* (value, cursor) and every operation here stays pure and testable.
|
|
28036
|
+
*
|
|
28037
|
+
* Word motion deviates from Codex's UAX#29 segmentation in one deliberate
|
|
28038
|
+
* way: a run of same-class characters is ONE piece, so a CJK run moves as a
|
|
28039
|
+
* single word (two hanzi are one Alt+B step, not two).
|
|
28040
|
+
*
|
|
28041
|
+
* @module @deepseek-ai/dsh-code/render/editor
|
|
28042
|
+
*/
|
|
28043
|
+
const segmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("en", { granularity: "grapheme" }) : void 0;
|
|
28044
|
+
/**
|
|
28045
|
+
* Split text into grapheme clusters. Falls back to code points when
|
|
28046
|
+
* Intl.Segmenter is unavailable; the fallback still keeps surrogate pairs
|
|
28047
|
+
* (emoji) atomic so the cursor can never split one.
|
|
28048
|
+
*/
|
|
28049
|
+
function splitGraphemes(text) {
|
|
28050
|
+
if (text === "") return [];
|
|
28051
|
+
const spans = [];
|
|
28052
|
+
if (segmenter !== void 0) {
|
|
28053
|
+
for (const piece of segmenter.segment(text)) spans.push({
|
|
28054
|
+
text: piece.segment,
|
|
28055
|
+
start: piece.index,
|
|
28056
|
+
end: piece.index + piece.segment.length,
|
|
28057
|
+
width: visibleColumns(piece.segment)
|
|
28058
|
+
});
|
|
28059
|
+
return spans;
|
|
28060
|
+
}
|
|
28061
|
+
let start = 0;
|
|
28062
|
+
for (const char of text) {
|
|
28063
|
+
spans.push({
|
|
28064
|
+
text: char,
|
|
28065
|
+
start,
|
|
28066
|
+
end: start + char.length,
|
|
28067
|
+
width: visibleColumns(char)
|
|
28068
|
+
});
|
|
28069
|
+
start += char.length;
|
|
28070
|
+
}
|
|
28071
|
+
return spans;
|
|
28072
|
+
}
|
|
28073
|
+
/** Round one UTF-16 offset down to the grapheme boundary at or before it. */
|
|
28074
|
+
function floorBoundary(spans, offset) {
|
|
28075
|
+
for (let index = spans.length - 1; index >= 0; index -= 1) {
|
|
28076
|
+
const span = spans[index];
|
|
28077
|
+
if (span.end <= offset) return span.end;
|
|
28078
|
+
if (span.start < offset) return span.start;
|
|
28079
|
+
}
|
|
28080
|
+
return 0;
|
|
28081
|
+
}
|
|
28082
|
+
/** Clamp a cursor offset to the nearest grapheme boundary (surrogates, ZWJ, marks stay whole). */
|
|
28083
|
+
function clampCursor(value, offset) {
|
|
28084
|
+
if (value === "") return 0;
|
|
28085
|
+
const target = Math.max(0, Math.min(value.length, Math.floor(offset)));
|
|
28086
|
+
if (target === 0 || target === value.length) return target;
|
|
28087
|
+
const spans = splitGraphemes(value);
|
|
28088
|
+
const down = floorBoundary(spans, target);
|
|
28089
|
+
if (down === target) return target;
|
|
28090
|
+
const up = spans.find((span) => span.start >= target)?.start ?? value.length;
|
|
28091
|
+
return up - target < target - down ? up : down;
|
|
28092
|
+
}
|
|
28093
|
+
/**
|
|
28094
|
+
* Delete the final grapheme cluster (append-only drafts without a cursor).
|
|
28095
|
+
* Surrogate pairs and multi-codepoint emoji stay whole instead of leaving a
|
|
28096
|
+
* lone trailing code unit behind.
|
|
28097
|
+
*/
|
|
28098
|
+
function deleteLastGrapheme(text) {
|
|
28099
|
+
if (text === "") return "";
|
|
28100
|
+
const spans = splitGraphemes(text);
|
|
28101
|
+
return text.slice(0, spans[spans.length - 1].start);
|
|
28102
|
+
}
|
|
28103
|
+
/**
|
|
28104
|
+
* Step the cursor by whole graphemes (negative steps left). The cursor is
|
|
28105
|
+
* assumed to sit on a boundary; any drift is clamped first.
|
|
28106
|
+
*/
|
|
28107
|
+
function moveCursorBy(value, offset, delta) {
|
|
28108
|
+
if (delta === 0 || value === "") return clampCursor(value, offset);
|
|
28109
|
+
const spans = splitGraphemes(value);
|
|
28110
|
+
const boundaries = [0];
|
|
28111
|
+
for (const span of spans) boundaries.push(span.end);
|
|
28112
|
+
const current = boundaries.indexOf(clampCursor(value, offset));
|
|
28113
|
+
if (current === -1) return clampCursor(value, offset);
|
|
28114
|
+
return boundaries[Math.max(0, Math.min(boundaries.length - 1, current + delta))];
|
|
28115
|
+
}
|
|
28116
|
+
/**
|
|
28117
|
+
* Normalize text entering the draft: CRLF/CR become LF, tabs become two
|
|
28118
|
+
* spaces (terminal tab stops are contextual and cannot join a deterministic
|
|
28119
|
+
* row budget), and every other C0 control byte plus DEL is REMOVED — the
|
|
28120
|
+
* draft is data, so a stray ESC (Windows Terminal file drops) disappears
|
|
28121
|
+
* instead of rendering as literal backslash-x-1-b text. Newlines survive.
|
|
28122
|
+
*/
|
|
28123
|
+
function sanitizeDraftText(text) {
|
|
28124
|
+
return text.replaceAll("\r\n", "\n").replaceAll("\r", "\n").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, "").replaceAll(" ", " ");
|
|
28125
|
+
}
|
|
28126
|
+
/**
|
|
28127
|
+
* Hard-wrap the draft into column-safe physical rows. Wide graphemes never
|
|
28128
|
+
* split across rows (a grapheme that does not fit flushes the row first) and
|
|
28129
|
+
* explicit newlines end their row without occupying a cell.
|
|
28130
|
+
*/
|
|
28131
|
+
function editorModel(value, columns) {
|
|
28132
|
+
const width = Math.max(1, Math.floor(columns));
|
|
28133
|
+
const spans = splitGraphemes(value);
|
|
28134
|
+
const rows = [];
|
|
28135
|
+
let text = "";
|
|
28136
|
+
let start = 0;
|
|
28137
|
+
let used = 0;
|
|
28138
|
+
const offsets = [0];
|
|
28139
|
+
const rowColumns = [0];
|
|
28140
|
+
const rowCuts = [0];
|
|
28141
|
+
const flush = (end) => {
|
|
28142
|
+
rows.push({
|
|
28143
|
+
text,
|
|
28144
|
+
start,
|
|
28145
|
+
end,
|
|
28146
|
+
offsets: [...offsets],
|
|
28147
|
+
columns: [...rowColumns],
|
|
28148
|
+
cuts: [...rowCuts]
|
|
28149
|
+
});
|
|
28150
|
+
text = "";
|
|
28151
|
+
used = 0;
|
|
28152
|
+
offsets.length = 0;
|
|
28153
|
+
rowColumns.length = 0;
|
|
28154
|
+
rowCuts.length = 0;
|
|
28155
|
+
};
|
|
28156
|
+
for (const span of spans) {
|
|
28157
|
+
if (span.text === "\n") {
|
|
28158
|
+
flush(span.start);
|
|
28159
|
+
start = span.end;
|
|
28160
|
+
offsets.push(span.end);
|
|
28161
|
+
rowColumns.push(0);
|
|
28162
|
+
rowCuts.push(0);
|
|
28163
|
+
continue;
|
|
28164
|
+
}
|
|
28165
|
+
if (used > 0 && used + span.width > width) {
|
|
28166
|
+
flush(span.start);
|
|
28167
|
+
start = span.start;
|
|
28168
|
+
offsets.push(span.start);
|
|
28169
|
+
rowColumns.push(0);
|
|
28170
|
+
rowCuts.push(0);
|
|
28171
|
+
}
|
|
28172
|
+
text += span.text;
|
|
28173
|
+
used += span.width;
|
|
28174
|
+
offsets.push(span.end);
|
|
28175
|
+
rowColumns.push(used);
|
|
28176
|
+
rowCuts.push(text.length);
|
|
28177
|
+
}
|
|
28178
|
+
if (text !== "" || rows.length === 0) flush(value.length);
|
|
28179
|
+
else if (offsets.length > 0) rows.push({
|
|
28180
|
+
text: "",
|
|
28181
|
+
start,
|
|
28182
|
+
end: value.length,
|
|
28183
|
+
offsets: [...offsets],
|
|
28184
|
+
columns: [...rowColumns],
|
|
28185
|
+
cuts: [...rowCuts]
|
|
28186
|
+
});
|
|
28187
|
+
return {
|
|
28188
|
+
rows,
|
|
28189
|
+
length: value.length
|
|
28190
|
+
};
|
|
28191
|
+
}
|
|
28192
|
+
/** Map a cursor offset to its caret site on the wrapped rows. */
|
|
28193
|
+
function caretSite(model, offset) {
|
|
28194
|
+
const target = Math.max(0, Math.min(model.length, offset));
|
|
28195
|
+
for (let index = model.rows.length - 1; index >= 0; index -= 1) {
|
|
28196
|
+
const row = model.rows[index];
|
|
28197
|
+
const at = row.offsets.indexOf(target);
|
|
28198
|
+
if (at >= 0) return {
|
|
28199
|
+
row: index,
|
|
28200
|
+
column: row.columns[at]
|
|
28201
|
+
};
|
|
28202
|
+
}
|
|
28203
|
+
const last = model.rows[model.rows.length - 1];
|
|
28204
|
+
return {
|
|
28205
|
+
row: model.rows.length - 1,
|
|
28206
|
+
column: last === void 0 ? 0 : last.columns[last.columns.length - 1]
|
|
28207
|
+
};
|
|
28208
|
+
}
|
|
28209
|
+
/**
|
|
28210
|
+
* Move the caret across physical rows keeping a preferred display column
|
|
28211
|
+
* (Codex `preferred_col`): horizontal moves reset the preference, vertical
|
|
28212
|
+
* moves reuse it, clamped to each row's width.
|
|
28213
|
+
*/
|
|
28214
|
+
function moveCursorVertically(model, offset, preferredColumn, delta) {
|
|
28215
|
+
const target = caretSite(model, offset).row + delta;
|
|
28216
|
+
if (target < 0 || target >= model.rows.length || delta === 0) return offset;
|
|
28217
|
+
const row = model.rows[target];
|
|
28218
|
+
const wanted = Math.max(0, Math.min(preferredColumn, row.columns[row.columns.length - 1]));
|
|
28219
|
+
let best = 0;
|
|
28220
|
+
for (let index = 1; index < row.columns.length; index += 1) if (row.columns[index] <= wanted) best = index;
|
|
28221
|
+
else break;
|
|
28222
|
+
return row.offsets[best];
|
|
28223
|
+
}
|
|
28224
|
+
/** The start/end offsets of the logical line containing the cursor. */
|
|
28225
|
+
function lineBounds(value, offset) {
|
|
28226
|
+
const target = Math.max(0, Math.min(value.length, offset));
|
|
28227
|
+
const start = value.lastIndexOf("\n", Math.max(0, target - 1)) + 1;
|
|
28228
|
+
const end = value.indexOf("\n", target);
|
|
28229
|
+
return {
|
|
28230
|
+
start,
|
|
28231
|
+
end: end === -1 ? value.length : end
|
|
28232
|
+
};
|
|
28233
|
+
}
|
|
28234
|
+
/** Codex WORD_SEPARATORS: punctuation runs are their own word pieces. */
|
|
28235
|
+
const WORD_SEPARATORS = /* @__PURE__ */ new Set("`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?");
|
|
28236
|
+
function classifyGrapheme(text) {
|
|
28237
|
+
if (/^\s$/u.test(text)) return "space";
|
|
28238
|
+
return WORD_SEPARATORS.has(text) ? "punct" : "word";
|
|
28239
|
+
}
|
|
28240
|
+
/** Maximal same-class runs of graphemes as [start, end) spans. */
|
|
28241
|
+
function pieceRuns(value) {
|
|
28242
|
+
const runs = [];
|
|
28243
|
+
let current;
|
|
28244
|
+
for (const span of splitGraphemes(value)) {
|
|
28245
|
+
const klass = span.text === "\n" ? "space" : classifyGrapheme(span.text);
|
|
28246
|
+
if (current !== void 0 && current.class === klass) {
|
|
28247
|
+
current.end = span.end;
|
|
28248
|
+
continue;
|
|
28249
|
+
}
|
|
28250
|
+
current = {
|
|
28251
|
+
start: span.start,
|
|
28252
|
+
end: span.end,
|
|
28253
|
+
class: klass
|
|
28254
|
+
};
|
|
28255
|
+
runs.push(current);
|
|
28256
|
+
}
|
|
28257
|
+
return runs;
|
|
28258
|
+
}
|
|
28259
|
+
/**
|
|
28260
|
+
* Codex `beginning_of_previous_word`: skip whitespace left, then land on the
|
|
28261
|
+
* START of the trailing non-space piece (extending over separator pieces).
|
|
28262
|
+
*/
|
|
28263
|
+
function moveWordLeft(value, offset) {
|
|
28264
|
+
const cursor = Math.max(0, Math.min(value.length, offset));
|
|
28265
|
+
const runs = pieceRuns(value);
|
|
28266
|
+
let index = runs.length - 1;
|
|
28267
|
+
while (index >= 0 && runs[index].end > cursor) index -= 1;
|
|
28268
|
+
if (index < 0) return 0;
|
|
28269
|
+
if (runs[index].class === "space") {
|
|
28270
|
+
index -= 1;
|
|
28271
|
+
if (index < 0) return 0;
|
|
28272
|
+
}
|
|
28273
|
+
let target = index;
|
|
28274
|
+
while (target > 0 && runs[target].class === "punct" && runs[target - 1].class === "punct") target -= 1;
|
|
28275
|
+
return runs[target].start;
|
|
28276
|
+
}
|
|
28277
|
+
/**
|
|
28278
|
+
* Codex `end_of_next_word`: skip whitespace right, then land on the END of
|
|
28279
|
+
* the leading non-space piece (extending over separator pieces).
|
|
28280
|
+
*/
|
|
28281
|
+
function moveWordRight(value, offset) {
|
|
28282
|
+
const cursor = Math.max(0, Math.min(value.length, offset));
|
|
28283
|
+
const runs = pieceRuns(value);
|
|
28284
|
+
let index = 0;
|
|
28285
|
+
while (index < runs.length && runs[index].start < cursor) index += 1;
|
|
28286
|
+
if (index >= runs.length) return value.length;
|
|
28287
|
+
if (runs[index].class === "space") {
|
|
28288
|
+
index += 1;
|
|
28289
|
+
if (index >= runs.length) return value.length;
|
|
28290
|
+
}
|
|
28291
|
+
let target = index;
|
|
28292
|
+
while (target < runs.length - 1 && runs[target].class === "punct" && runs[target + 1].class === "punct") target += 1;
|
|
28293
|
+
return runs[target].end;
|
|
28294
|
+
}
|
|
28295
|
+
function replaceRange(value, cursor, start, end, killed) {
|
|
28296
|
+
const from = Math.min(start, end);
|
|
28297
|
+
const to = Math.max(start, end);
|
|
28298
|
+
if (from >= to) return {
|
|
28299
|
+
value,
|
|
28300
|
+
cursor,
|
|
28301
|
+
killed: void 0
|
|
28302
|
+
};
|
|
28303
|
+
const text = value.slice(from, to);
|
|
28304
|
+
return {
|
|
28305
|
+
value: value.slice(0, from) + value.slice(to),
|
|
28306
|
+
cursor: Math.max(0, cursor - Math.max(0, Math.min(cursor, to) - from)),
|
|
28307
|
+
killed: killed ? text : void 0
|
|
28308
|
+
};
|
|
28309
|
+
}
|
|
28310
|
+
/** Delete the grapheme cluster before the cursor. */
|
|
28311
|
+
function deleteBackward(value, cursor) {
|
|
28312
|
+
if (cursor === 0) return {
|
|
28313
|
+
value,
|
|
28314
|
+
cursor,
|
|
28315
|
+
killed: void 0
|
|
28316
|
+
};
|
|
28317
|
+
const spans = splitGraphemes(value.slice(0, cursor));
|
|
28318
|
+
return replaceRange(value, cursor, spans.length === 0 ? 0 : spans[spans.length - 1].start, cursor, false);
|
|
28319
|
+
}
|
|
28320
|
+
/** Delete the grapheme cluster at the cursor. */
|
|
28321
|
+
function deleteForward(value, cursor) {
|
|
28322
|
+
if (cursor >= value.length) return {
|
|
28323
|
+
value,
|
|
28324
|
+
cursor,
|
|
28325
|
+
killed: void 0
|
|
28326
|
+
};
|
|
28327
|
+
const span = splitGraphemes(value).find((candidate) => candidate.start >= cursor);
|
|
28328
|
+
return replaceRange(value, cursor, cursor, span === void 0 ? value.length : span.end, false);
|
|
28329
|
+
}
|
|
28330
|
+
/** Delete back to the start of the previous word (fills the kill buffer). */
|
|
28331
|
+
function deleteWordBackward(value, cursor) {
|
|
28332
|
+
return replaceRange(value, cursor, moveWordLeft(value, cursor), cursor, true);
|
|
28333
|
+
}
|
|
28334
|
+
/** Delete forward to the end of the next word (fills the kill buffer). */
|
|
28335
|
+
function deleteWordForward(value, cursor) {
|
|
28336
|
+
return replaceRange(value, cursor, cursor, moveWordRight(value, cursor), true);
|
|
28337
|
+
}
|
|
28338
|
+
/** Ctrl+U: kill from the line start to the cursor; at BOL, kill the newline. */
|
|
28339
|
+
function killToLineStart(value, cursor) {
|
|
28340
|
+
const bounds = lineBounds(value, cursor);
|
|
28341
|
+
if (cursor > bounds.start) return replaceRange(value, cursor, bounds.start, cursor, true);
|
|
28342
|
+
if (bounds.start > 0) return replaceRange(value, cursor, bounds.start - 1, bounds.start, true);
|
|
28343
|
+
return {
|
|
28344
|
+
value,
|
|
28345
|
+
cursor,
|
|
28346
|
+
killed: void 0
|
|
28347
|
+
};
|
|
28348
|
+
}
|
|
28349
|
+
/** Ctrl+K: kill from the cursor to the line end; at EOL, kill the newline. */
|
|
28350
|
+
function killToLineEnd(value, cursor) {
|
|
28351
|
+
const bounds = lineBounds(value, cursor);
|
|
28352
|
+
if (cursor < bounds.end) return replaceRange(value, cursor, cursor, bounds.end, true);
|
|
28353
|
+
if (bounds.end < value.length) return replaceRange(value, cursor, bounds.end, bounds.end + 1, true);
|
|
28354
|
+
return {
|
|
28355
|
+
value,
|
|
28356
|
+
cursor,
|
|
28357
|
+
killed: void 0
|
|
28358
|
+
};
|
|
28359
|
+
}
|
|
28360
|
+
/** Insert sanitized text at the cursor. */
|
|
28361
|
+
function insertText(value, cursor, text) {
|
|
28362
|
+
const safe = sanitizeDraftText(text);
|
|
28363
|
+
if (safe === "") return {
|
|
28364
|
+
value,
|
|
28365
|
+
cursor,
|
|
28366
|
+
killed: void 0
|
|
28367
|
+
};
|
|
28368
|
+
return {
|
|
28369
|
+
value: value.slice(0, cursor) + safe + value.slice(cursor),
|
|
28370
|
+
cursor: cursor + safe.length,
|
|
28371
|
+
killed: void 0
|
|
28372
|
+
};
|
|
28373
|
+
}
|
|
28374
|
+
/**
|
|
28375
|
+
* Composer editor row budget: the editor itself never grows past this many
|
|
28376
|
+
* physical rows; deeper drafts scroll internally to keep the caret visible.
|
|
28377
|
+
* Short terminals collapse toward one row so the live transcript keeps room.
|
|
28378
|
+
*/
|
|
28379
|
+
function composerMaxRows(terminalRows) {
|
|
28380
|
+
return Math.max(1, Math.min(6, Math.floor((Math.max(1, terminalRows) - 10) / 3)));
|
|
28381
|
+
}
|
|
28382
|
+
/**
|
|
28383
|
+
* Codex `should_handle_navigation`: Up/Down walk history only from an empty
|
|
28384
|
+
* draft, or from a boundary of a draft that still exactly matches the last
|
|
28385
|
+
* recalled entry. Any interior cursor position keeps vertical caret movement.
|
|
28386
|
+
*/
|
|
28387
|
+
function shouldRecallNavigate(value, cursor, lastRecalled) {
|
|
28388
|
+
if (value === "") return true;
|
|
28389
|
+
if (cursor !== 0 && cursor !== value.length) return false;
|
|
28390
|
+
return lastRecalled === value;
|
|
28391
|
+
}
|
|
28392
|
+
/** Bracketed paste markers as Ink delivers them (it strips the leading ESC). */
|
|
28393
|
+
const PASTE_START_MARKER = "[200~";
|
|
28394
|
+
const PASTE_END_MARKER = "[201~";
|
|
28395
|
+
/**
|
|
28396
|
+
* Remove bracketed paste markers from one input chunk. Panel drafts accept raw
|
|
28397
|
+
* `input` text, where an unhandled paste would otherwise persist the literal
|
|
28398
|
+
* "[200~"/"[201~" markers Ink leaves after stripping the ESC byte.
|
|
28399
|
+
*/
|
|
28400
|
+
function stripPasteMarkers(text) {
|
|
28401
|
+
return text.replaceAll(PASTE_START_MARKER, "").replaceAll(PASTE_END_MARKER, "");
|
|
28402
|
+
}
|
|
28403
|
+
/** Match one CSI-u sequence (code, optional ;modifiers, then :event or ;alternate). */
|
|
28404
|
+
const CSI_U_SOURCE = "\x1B\\[(\\d+)(?:;(\\d+))?(?:[:;](\\d+))?u";
|
|
28405
|
+
/** Legacy equivalent for one decoded CSI-u key, or undefined to pass through. */
|
|
28406
|
+
function legacyForKey(key) {
|
|
28407
|
+
const bits = Math.max(0, key.modifiers - 1);
|
|
28408
|
+
const shift = (bits & 1) !== 0;
|
|
28409
|
+
const alt = (bits & 2) !== 0;
|
|
28410
|
+
const ctrl = (bits & 4) !== 0;
|
|
28411
|
+
if (key.code === 13) {
|
|
28412
|
+
if (shift) return "\x1B[13;2u";
|
|
28413
|
+
if (ctrl) return "\n";
|
|
28414
|
+
if (alt) return "\x1B\r";
|
|
28415
|
+
return "\r";
|
|
28416
|
+
}
|
|
28417
|
+
if (key.code === 27) return "\x1B";
|
|
28418
|
+
if (key.code === 9) return shift ? "\x1B[Z" : " ";
|
|
28419
|
+
if (key.code === 127) return alt || ctrl ? "\x1B" : "";
|
|
28420
|
+
if (key.code >= 1 && key.code <= 6) {
|
|
28421
|
+
const mask = (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
|
|
28422
|
+
const mods = mask === 0 ? "" : `;${mask + 1}`;
|
|
28423
|
+
if (key.code === 1) return mods === "" ? "\x1B[H" : `\x1b[1${mods}H`;
|
|
28424
|
+
if (key.code === 4) return mods === "" ? "\x1B[F" : `\x1b[1${mods}F`;
|
|
28425
|
+
return `\x1b[${key.code}${mods}~`;
|
|
28426
|
+
}
|
|
28427
|
+
if (key.code >= 97 && key.code <= 122) {
|
|
28428
|
+
const letter = String.fromCodePoint(key.code);
|
|
28429
|
+
if (ctrl) return String.fromCodePoint(key.code - 96);
|
|
28430
|
+
if (alt) return "\x1B" + letter;
|
|
28431
|
+
if (shift) return String.fromCodePoint(key.alternate ?? key.code - 32);
|
|
28432
|
+
return letter;
|
|
28433
|
+
}
|
|
28434
|
+
if (key.code >= 65 && key.code <= 90) {
|
|
28435
|
+
if (ctrl) return String.fromCodePoint(key.code + 32 - 96);
|
|
28436
|
+
if (alt) return "\x1B" + String.fromCodePoint(key.code + 32);
|
|
28437
|
+
return String.fromCodePoint(key.code);
|
|
28438
|
+
}
|
|
28439
|
+
if (key.code >= 32 && key.code <= 126 && key.alternate !== void 0) {
|
|
28440
|
+
const base = key.alternate >= 97 && key.alternate <= 122 ? key.alternate : key.code;
|
|
28441
|
+
if (ctrl && base - 96 >= 1 && base - 96 <= 26) return String.fromCodePoint(base - 96);
|
|
28442
|
+
if (alt) return "\x1B" + String.fromCodePoint(key.alternate);
|
|
28443
|
+
return String.fromCodePoint(key.alternate);
|
|
28444
|
+
}
|
|
28445
|
+
}
|
|
28446
|
+
/**
|
|
28447
|
+
* Rewrite every decodable kitty CSI-u sequence in one stdin chunk to the
|
|
28448
|
+
* legacy form the input layer already handles. Undecodable or non-key
|
|
28449
|
+
* sequences pass through untouched, so terminals without the protocol are
|
|
28450
|
+
* unaffected.
|
|
28451
|
+
*/
|
|
28452
|
+
function normalizeKeyboardChunk(chunk) {
|
|
28453
|
+
if (!chunk.includes("\x1B[") || !chunk.includes("u")) return chunk;
|
|
28454
|
+
const pattern = new RegExp(CSI_U_SOURCE, "g");
|
|
28455
|
+
return chunk.replace(pattern, (whole, code, mods, third) => {
|
|
28456
|
+
return legacyForKey({
|
|
28457
|
+
code: Number.parseInt(code, 10),
|
|
28458
|
+
modifiers: mods === void 0 || mods === "" ? 1 : Math.max(1, Number.parseInt(mods, 10)),
|
|
28459
|
+
alternate: third !== void 0 && third !== "" ? Number.parseInt(third, 10) : void 0
|
|
28460
|
+
}) ?? whole;
|
|
28461
|
+
});
|
|
28462
|
+
}
|
|
27725
28463
|
//#endregion
|
|
27726
28464
|
//#region src/kernel-panels.ts
|
|
27727
28465
|
/** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
|
|
@@ -27780,9 +28518,15 @@ function ListFrame(props) {
|
|
|
27780
28518
|
wrap: "truncate-end"
|
|
27781
28519
|
}, truncateColumns(singleLineText(props.footer), viewport.contentColumns)));
|
|
27782
28520
|
}
|
|
28521
|
+
/**
|
|
28522
|
+
* Apply one keystroke to a panel search query. IME commits arrive as one
|
|
28523
|
+
* multi-character chunk, so the whole printable run is appended; paste
|
|
28524
|
+
* markers are stripped and control-laden chunks are ignored.
|
|
28525
|
+
*/
|
|
27783
28526
|
function editQuery(query, input, key) {
|
|
27784
|
-
if (key.backspace || key.delete) return query
|
|
27785
|
-
|
|
28527
|
+
if (key.backspace || key.delete) return deleteLastGrapheme(query);
|
|
28528
|
+
const text = stripPasteMarkers(input);
|
|
28529
|
+
if (text !== "" && !/[\u0000-\u001f\u007f]/u.test(text)) return query + text;
|
|
27786
28530
|
}
|
|
27787
28531
|
function ModePanel({ current, load, select, close }) {
|
|
27788
28532
|
const [rows, setRows] = (0, import_react.useState)([]);
|
|
@@ -27907,6 +28651,57 @@ function PluginPanel({ load, close, initialQuery = "" }) {
|
|
|
27907
28651
|
footer: "↑↓ inspect · enter details · r refresh · esc close"
|
|
27908
28652
|
});
|
|
27909
28653
|
}
|
|
28654
|
+
/** Web TurnStatus elapsed format: `45s` under a minute, `2m03s` beyond. */
|
|
28655
|
+
function runClock(ms) {
|
|
28656
|
+
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
28657
|
+
const minutes = Math.floor(total / 60);
|
|
28658
|
+
const seconds = total % 60;
|
|
28659
|
+
return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
|
|
28660
|
+
}
|
|
28661
|
+
/** Status glyph per job lifecycle state. */
|
|
28662
|
+
const JOB_MARK = {
|
|
28663
|
+
running: "●",
|
|
28664
|
+
stopping: "⏸",
|
|
28665
|
+
completed: "✓",
|
|
28666
|
+
killed: "⊘",
|
|
28667
|
+
failed: "✗"
|
|
28668
|
+
};
|
|
28669
|
+
/**
|
|
28670
|
+
* The read-only background-job panel: caller-owned and unowned jobs from the
|
|
28671
|
+
* host `jobs` registry in registration order, with a local second-hand while
|
|
28672
|
+
* the panel is open (elapsed clocks advance and the snapshot re-reads; the
|
|
28673
|
+
* interval dies with the panel). Cancel stays upstream-only; an absent
|
|
28674
|
+
* registry renders as the plain empty state (a harmless missing service).
|
|
28675
|
+
*/
|
|
28676
|
+
function JobsPanel({ load, close }) {
|
|
28677
|
+
const [, setRefresh] = (0, import_react.useState)(0);
|
|
28678
|
+
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
28679
|
+
const [, setTick] = (0, import_react.useState)(0);
|
|
28680
|
+
(0, import_react.useEffect)(() => {
|
|
28681
|
+
const id = setInterval(() => setTick((value) => value + 1), 1e3);
|
|
28682
|
+
return () => clearInterval(id);
|
|
28683
|
+
}, []);
|
|
28684
|
+
const rows = load();
|
|
28685
|
+
useInput((input, key) => {
|
|
28686
|
+
if (key.escape || input === "q") return close();
|
|
28687
|
+
if (input === "r") return setRefresh((value) => value + 1);
|
|
28688
|
+
if (key.upArrow) return setCursor((value) => rows.length === 0 ? 0 : (value + rows.length - 1) % rows.length);
|
|
28689
|
+
if (key.downArrow) return setCursor((value) => rows.length === 0 ? 0 : (value + 1) % rows.length);
|
|
28690
|
+
});
|
|
28691
|
+
(0, import_react.useEffect)(() => setCursor((value) => Math.min(value, Math.max(0, rows.length - 1))), [rows.length]);
|
|
28692
|
+
return (0, import_react.createElement)(ListFrame, {
|
|
28693
|
+
title: `/jobs · background tasks · ${rows.length}`,
|
|
28694
|
+
rows: rows.map((row) => ({
|
|
28695
|
+
key: row.id,
|
|
28696
|
+
text: `${JOB_MARK[row.status]} ${row.id} · ${singleLineText(row.label)} · ${runClock((row.finishedAt ?? Date.now()) - row.startedAt)}${row.detail === void 0 ? "" : ` · ${singleLineText(row.detail)}`}`
|
|
28697
|
+
})),
|
|
28698
|
+
cursor,
|
|
28699
|
+
loading: false,
|
|
28700
|
+
query: "",
|
|
28701
|
+
searching: false,
|
|
28702
|
+
footer: "↑↓ inspect · r refresh · esc close"
|
|
28703
|
+
});
|
|
28704
|
+
}
|
|
27910
28705
|
function ResumePanel({ currentCwd, load, readTranscript, select, requestDelete, deleteConfirmId, reloadToken = 0, deleteMode = false, close }) {
|
|
27911
28706
|
const [options, setOptions] = (0, import_react.useState)({
|
|
27912
28707
|
sessions: "roots",
|
|
@@ -28054,11 +28849,42 @@ function ResumePanel({ currentCwd, load, readTranscript, select, requestDelete,
|
|
|
28054
28849
|
footer: "tab/←→ filters · ↑↓/pg navigate · ctrl+f search · e details · t transcript · d delete · enter resume"
|
|
28055
28850
|
});
|
|
28056
28851
|
}
|
|
28852
|
+
function documentStyleProps(style) {
|
|
28853
|
+
switch (style) {
|
|
28854
|
+
case "accent": return { color: inkColor(getPalette().brandBright) };
|
|
28855
|
+
case "accentBold": return {
|
|
28856
|
+
color: inkColor(getPalette().brandBright),
|
|
28857
|
+
bold: true
|
|
28858
|
+
};
|
|
28859
|
+
case "code": return { color: inkColor(getPalette().code) };
|
|
28860
|
+
case "dim": return { color: inkColor(getPalette().dim) };
|
|
28861
|
+
case "bold": return { bold: true };
|
|
28862
|
+
case "italic": return { italic: true };
|
|
28863
|
+
case "boldItalic": return {
|
|
28864
|
+
bold: true,
|
|
28865
|
+
italic: true
|
|
28866
|
+
};
|
|
28867
|
+
case "strike": return {
|
|
28868
|
+
color: inkColor(getPalette().dim),
|
|
28869
|
+
strikethrough: true
|
|
28870
|
+
};
|
|
28871
|
+
default: return {};
|
|
28872
|
+
}
|
|
28873
|
+
}
|
|
28874
|
+
function DocumentRows({ lines }) {
|
|
28875
|
+
return (0, import_react.createElement)(Box, { flexDirection: "column" }, ...lines.map((line, index) => (0, import_react.createElement)(Text, {
|
|
28876
|
+
key: index,
|
|
28877
|
+
wrap: "truncate-end"
|
|
28878
|
+
}, line.segments.length === 0 ? " " : line.segments.map((segment, at) => (0, import_react.createElement)(Text, {
|
|
28879
|
+
key: at,
|
|
28880
|
+
...documentStyleProps(segment.style)
|
|
28881
|
+
}, segment.text)))));
|
|
28882
|
+
}
|
|
28057
28883
|
function DocumentPanel({ title, text, error, close }) {
|
|
28058
28884
|
const stdout = useStdout().stdout;
|
|
28059
28885
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
28060
28886
|
const [scroll, setScroll] = (0, import_react.useState)(0);
|
|
28061
|
-
const lines = (0, import_react.useMemo)(() => text === void 0 ? [] :
|
|
28887
|
+
const lines = (0, import_react.useMemo)(() => text === void 0 ? [] : markdownLines(text, viewport.contentColumns), [text, viewport.contentColumns]);
|
|
28062
28888
|
useInput((input, key) => {
|
|
28063
28889
|
if (key.escape || input === "q" || input === "t") return close();
|
|
28064
28890
|
if (key.upArrow) return setScroll((value) => Math.max(0, value - 1));
|
|
@@ -28069,7 +28895,7 @@ function DocumentPanel({ title, text, error, close }) {
|
|
|
28069
28895
|
if (input === "G") return setScroll(Math.max(0, lines.length - viewport.bodyRows));
|
|
28070
28896
|
});
|
|
28071
28897
|
if (viewport.compact) return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns("transcript · esc close", viewport.contentColumns));
|
|
28072
|
-
const body = error !== void 0 ?
|
|
28898
|
+
const body = error !== void 0 ? textLines(`error: ${singleLineText(error)}`, viewport.contentColumns, "error") : text === void 0 ? textLines("loading transcript…", viewport.contentColumns, "dim") : lines.slice(scroll, scroll + viewport.bodyRows);
|
|
28073
28899
|
return (0, import_react.createElement)(Box, {
|
|
28074
28900
|
width: viewport.outerColumns,
|
|
28075
28901
|
borderStyle: "round",
|
|
@@ -28079,10 +28905,7 @@ function DocumentPanel({ title, text, error, close }) {
|
|
|
28079
28905
|
}, (0, import_react.createElement)(Text, {
|
|
28080
28906
|
color: inkColor(getPalette().brandBright),
|
|
28081
28907
|
wrap: "truncate-end"
|
|
28082
|
-
}, truncateColumns(singleLineText(title), viewport.contentColumns)),
|
|
28083
|
-
key: `${scroll}-${index}`,
|
|
28084
|
-
wrap: "truncate-end"
|
|
28085
|
-
}, truncateColumns(line, viewport.contentColumns))), (0, import_react.createElement)(Text, {
|
|
28908
|
+
}, truncateColumns(singleLineText(title), viewport.contentColumns)), (0, import_react.createElement)(DocumentRows, { lines: body }), (0, import_react.createElement)(Text, {
|
|
28086
28909
|
dimColor: true,
|
|
28087
28910
|
wrap: "truncate-end"
|
|
28088
28911
|
}, truncateColumns(`lines ${lines.length === 0 ? 0 : scroll + 1}-${Math.min(lines.length, scroll + viewport.bodyRows)}/${lines.length} · ↑↓/pg/g/G · t/esc close`, viewport.contentColumns)));
|
|
@@ -28109,13 +28932,16 @@ function HistoryPanel({ entries, fill, close }) {
|
|
|
28109
28932
|
if (input === "g") return setCursor(0);
|
|
28110
28933
|
if (input === "G") return setCursor(matches.length - 1);
|
|
28111
28934
|
if (key.backspace) {
|
|
28112
|
-
setQuery((current) => current
|
|
28935
|
+
setQuery((current) => deleteLastGrapheme(current));
|
|
28113
28936
|
setCursor(0);
|
|
28114
28937
|
return;
|
|
28115
28938
|
}
|
|
28116
28939
|
if (input !== "" && !key.ctrl && !key.meta && !key.shift) {
|
|
28117
|
-
|
|
28118
|
-
|
|
28940
|
+
const text = stripPasteMarkers(input);
|
|
28941
|
+
if (text !== "") {
|
|
28942
|
+
setQuery((current) => (current + text).slice(0, 120));
|
|
28943
|
+
setCursor(0);
|
|
28944
|
+
}
|
|
28119
28945
|
}
|
|
28120
28946
|
});
|
|
28121
28947
|
const stdout = useStdout().stdout;
|
|
@@ -28546,6 +29372,15 @@ function recallEntries(persistent, local) {
|
|
|
28546
29372
|
const localSet = new Set(local);
|
|
28547
29373
|
return [...persistent.filter((entry) => !localSet.has(entry)), ...local].reverse();
|
|
28548
29374
|
}
|
|
29375
|
+
/** Fresh navigation state over one recall space. */
|
|
29376
|
+
function beginRecall(entries, draft) {
|
|
29377
|
+
return {
|
|
29378
|
+
entries,
|
|
29379
|
+
index: null,
|
|
29380
|
+
savedDraft: draft,
|
|
29381
|
+
lastRecalled: null
|
|
29382
|
+
};
|
|
29383
|
+
}
|
|
28549
29384
|
/**
|
|
28550
29385
|
* Move one entry older (Up, toward index +1 in the newest-first space). The
|
|
28551
29386
|
* first Up saves the current draft so Down past the newest can restore it
|
|
@@ -28629,8 +29464,32 @@ function recallNewer(state) {
|
|
|
28629
29464
|
*/
|
|
28630
29465
|
/** Match Codex's settled-resize window before rebuilding terminal scrollback. */
|
|
28631
29466
|
const RESIZE_REFLOW_DELAY_MS = 75;
|
|
28632
|
-
/**
|
|
28633
|
-
|
|
29467
|
+
/**
|
|
29468
|
+
* Cap on rendered settled history, in physical rows (header and hint
|
|
29469
|
+
* included). 3,000 rows sits inside Codex's 1k–10k reflow budget range:
|
|
29470
|
+
* replays stay under ~200ms while roughly a hundred messages stay visible
|
|
29471
|
+
* before the oldest drop out. `DSH_SETTLED_ROWS` overrides it; 0 disables
|
|
29472
|
+
* the cap entirely (the historical unbounded behavior).
|
|
29473
|
+
*/
|
|
29474
|
+
const SETTLED_ROW_CAP = readSettledRowCap();
|
|
29475
|
+
/** Hysteresis: the cap may overflow by 25% before one trimming replay fires. */
|
|
29476
|
+
/** Header rows plus the trim hint, reserved out of the row cap. */
|
|
29477
|
+
const SETTLED_ROW_RESERVE = 12;
|
|
29478
|
+
/** Read the configurable settled-history cap once per process. */
|
|
29479
|
+
function readSettledRowCap() {
|
|
29480
|
+
const raw = process.env.DSH_SETTLED_ROWS;
|
|
29481
|
+
if (raw === void 0) return 3e3;
|
|
29482
|
+
const parsed = Number.parseInt(raw, 10);
|
|
29483
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 3e3;
|
|
29484
|
+
}
|
|
29485
|
+
/**
|
|
29486
|
+
* Safety net for a bracketed paste whose end marker never arrives (terminal
|
|
29487
|
+
* defect or crash mid-paste): past this window the open-paste flag resets so
|
|
29488
|
+
* Enter submits again instead of inserting newlines forever.
|
|
29489
|
+
*/
|
|
29490
|
+
const PASTE_BRACKET_TIMEOUT_MS = 1e3;
|
|
29491
|
+
/** Release the held frame after Ink has replayed the source-backed Static rows. */
|
|
29492
|
+
const SYNCHRONIZED_UPDATE_END = "\x1B[?2026l";
|
|
28634
29493
|
/** Pad text with spaces to a visible-column target (menu name column). */
|
|
28635
29494
|
function padColumns(text, width) {
|
|
28636
29495
|
const clipped = truncateColumns(singleLineText(text), width);
|
|
@@ -28661,11 +29520,6 @@ function useStableInput(handler, active) {
|
|
|
28661
29520
|
}, []);
|
|
28662
29521
|
useInput(stableHandler, { isActive: active });
|
|
28663
29522
|
}
|
|
28664
|
-
/** Single-cell stepped pulse: the web's 125ms flat-hold brightness steps over 1s. */
|
|
28665
|
-
function Pulse() {
|
|
28666
|
-
const tick = useFrames(125);
|
|
28667
|
-
return (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright) }, pulseFrame(tick));
|
|
28668
|
-
}
|
|
28669
29523
|
/**
|
|
28670
29524
|
* The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
|
|
28671
29525
|
* ring trail clockwise around the eight outer positions (8 frames × 125ms =
|
|
@@ -28686,13 +29540,6 @@ function CursorBlock({ char }) {
|
|
|
28686
29540
|
const tick = useFrames(530);
|
|
28687
29541
|
return (0, import_react.createElement)(Text, { inverse: caretVisible(tick) || void 0 }, char);
|
|
28688
29542
|
}
|
|
28689
|
-
/** Web TurnStatus elapsed format: `45s` under a minute, `2m03s` beyond. */
|
|
28690
|
-
function runClock(ms) {
|
|
28691
|
-
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
28692
|
-
const minutes = Math.floor(total / 60);
|
|
28693
|
-
const seconds = total % 60;
|
|
28694
|
-
return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
|
|
28695
|
-
}
|
|
28696
29543
|
/**
|
|
28697
29544
|
* The busy line, web TurnStatus contract: the StateDot chase leads the plain
|
|
28698
29545
|
* `Deep diving...` label, with the elapsed clock appended only once the turn
|
|
@@ -28716,7 +29563,7 @@ function StreamTail({ text, dim, maxRows, prefix = "", continuationPrefix = pref
|
|
|
28716
29563
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
28717
29564
|
const safeRows = Math.max(1, maxRows);
|
|
28718
29565
|
const prefixColumns = Math.max(visibleColumns(prefix), visibleColumns(continuationPrefix));
|
|
28719
|
-
const contentColumns = Math.max(10, columns -
|
|
29566
|
+
const contentColumns = Math.max(10, columns - 1 - prefixColumns);
|
|
28720
29567
|
const initial = displayTail(text, contentColumns, safeRows);
|
|
28721
29568
|
const tail = initial.truncated && safeRows > 1 ? displayTail(text, contentColumns, safeRows - 1) : initial;
|
|
28722
29569
|
const rows = tail.text.split("\n");
|
|
@@ -28838,138 +29685,80 @@ function StyledRows({ lines }) {
|
|
|
28838
29685
|
...lineStyleProps(segment.style)
|
|
28839
29686
|
}, segment.text)))));
|
|
28840
29687
|
}
|
|
28841
|
-
/**
|
|
28842
|
-
function
|
|
28843
|
-
|
|
28844
|
-
|
|
28845
|
-
|
|
28846
|
-
|
|
28847
|
-
const
|
|
28848
|
-
const lines = (0, import_react.useMemo)(() =>
|
|
28849
|
-
|
|
28850
|
-
|
|
28851
|
-
|
|
28852
|
-
|
|
29688
|
+
/** File-oriented, color-coded unified diff viewport. */
|
|
29689
|
+
function DiffPanel({ view, onClose }) {
|
|
29690
|
+
const stdout = useStdout().stdout;
|
|
29691
|
+
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
29692
|
+
const [fileIndex, setFileIndex] = (0, import_react.useState)(0);
|
|
29693
|
+
const [scroll, setScroll] = (0, import_react.useState)(0);
|
|
29694
|
+
const file = view.files[fileIndex];
|
|
29695
|
+
const lines = (0, import_react.useMemo)(() => {
|
|
29696
|
+
if (file === void 0) return textLines(" (no changes)", viewport.contentColumns, "dim");
|
|
29697
|
+
return file.lines.flatMap((line) => styledLines([lineSegment(line, line.startsWith("+") && !line.startsWith("+++") ? "success" : line.startsWith("-") && !line.startsWith("---") ? "error" : line.startsWith("@@") || line.startsWith("diff --git") || line.startsWith("index ") ? "brand" : "dim")], viewport.contentColumns));
|
|
29698
|
+
}, [file, viewport.contentColumns]);
|
|
29699
|
+
const visibleScroll = clampScroll(scroll, lines.length, viewport.bodyRows);
|
|
29700
|
+
useInput((input, key) => {
|
|
29701
|
+
if (key.escape || input === "q") onClose();
|
|
29702
|
+
else if (key.leftArrow && view.files.length > 0) {
|
|
29703
|
+
setFileIndex((current) => (current + view.files.length - 1) % view.files.length);
|
|
29704
|
+
setScroll(0);
|
|
29705
|
+
} else if (key.rightArrow && view.files.length > 0) {
|
|
29706
|
+
setFileIndex((current) => (current + 1) % view.files.length);
|
|
29707
|
+
setScroll(0);
|
|
29708
|
+
} else if (input === "g") setScroll(0);
|
|
29709
|
+
else if (input === "G") setScroll(Math.max(0, lines.length - viewport.bodyRows));
|
|
29710
|
+
else if (key.upArrow) setScroll((current) => moveScroll(current, -1, lines.length, viewport.bodyRows));
|
|
29711
|
+
else if (key.downArrow) setScroll((current) => moveScroll(current, 1, lines.length, viewport.bodyRows));
|
|
29712
|
+
else if (key.pageUp) setScroll((current) => moveScroll(current, -viewport.bodyRows, lines.length, viewport.bodyRows));
|
|
29713
|
+
else if (key.pageDown) setScroll((current) => moveScroll(current, viewport.bodyRows, lines.length, viewport.bodyRows));
|
|
29714
|
+
});
|
|
29715
|
+
if (viewport.compact) return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns(`${view.title} · ${view.files.length} files · esc/q close`, viewport.contentColumns));
|
|
28853
29716
|
return (0, import_react.createElement)(Box, {
|
|
28854
29717
|
flexDirection: "column",
|
|
28855
|
-
|
|
28856
|
-
|
|
28857
|
-
|
|
28858
|
-
|
|
28859
|
-
|
|
28860
|
-
|
|
28861
|
-
|
|
28862
|
-
|
|
28863
|
-
|
|
28864
|
-
|
|
28865
|
-
|
|
28866
|
-
}
|
|
28867
|
-
/**
|
|
28868
|
-
* One expanded tool-card body for the verbose transcript (Ctrl+O): the
|
|
28869
|
-
* presentation contract's structured cards — inline diffs, read windows,
|
|
28870
|
-
* web sources — rendered as plain terminal rows, degradation-safe against
|
|
28871
|
-
* replayed metadata.
|
|
28872
|
-
*/
|
|
28873
|
-
function ToolDetailBody({ detail }) {
|
|
28874
|
-
switch (detail.kind) {
|
|
28875
|
-
case "diff": return (0, import_react.createElement)(Box, { flexDirection: "column" }, ...detail.diffs.map((diff, index) => (0, import_react.createElement)(Box, {
|
|
28876
|
-
key: index,
|
|
28877
|
-
flexDirection: "column"
|
|
28878
|
-
}, (0, import_react.createElement)(Text, {
|
|
28879
|
-
dimColor: true,
|
|
28880
|
-
wrap: "truncate-end"
|
|
28881
|
-
}, ` ── ${displayText(diff.path)}${diff.truncated ? " (diff truncated)" : ""}`), ...diff.lines.map((line, at) => (0, import_react.createElement)(Text, {
|
|
28882
|
-
key: at,
|
|
28883
|
-
color: line.mark === "+" ? inkColor(getPalette().success) : line.mark === "-" ? inkColor(getPalette().error) : inkColor(getPalette().dim),
|
|
28884
|
-
wrap: "truncate-end"
|
|
28885
|
-
}, ` ${line.mark}${displayText(line.text)}`)))));
|
|
28886
|
-
case "read": return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(Text, {
|
|
28887
|
-
dimColor: true,
|
|
28888
|
-
wrap: "truncate-end"
|
|
28889
|
-
}, ` ── ${displayText(detail.path)} · lines ${detail.offset}-${detail.lines.length > 0 ? detail.lines[detail.lines.length - 1].number : detail.offset - 1} of ${detail.totalLines}${detail.truncated ? " (window truncated)" : ""}`), ...detail.lines.map((line, at) => (0, import_react.createElement)(Text, {
|
|
28890
|
-
key: at,
|
|
28891
|
-
dimColor: true,
|
|
28892
|
-
wrap: "truncate-end"
|
|
28893
|
-
}, ` ${String(line.number).padStart(5, " ")} | ${displayText(line.text)}`)));
|
|
28894
|
-
case "web-search": return (0, import_react.createElement)(Box, { flexDirection: "column" }, ...detail.sources.map((source, at) => (0, import_react.createElement)(Text, {
|
|
28895
|
-
key: at,
|
|
28896
|
-
wrap: "truncate-end"
|
|
28897
|
-
}, brand(` ? ${displayText(source.title === void 0 ? source.url : source.title)}`), (0, import_react.createElement)(Text, { dimColor: true }, dim(` - ${displayText(source.url)}`)))), (0, import_react.createElement)(Text, { dimColor: true }, dim(` ${detail.sources.length} sources${detail.truncated ? " (capped)" : ""}`)));
|
|
28898
|
-
case "web-fetch": return (0, import_react.createElement)(Text, {
|
|
28899
|
-
dimColor: true,
|
|
28900
|
-
wrap: "truncate-end"
|
|
28901
|
-
}, dim(` ${displayText(detail.url)} · HTTP ${detail.statusCode}`));
|
|
28902
|
-
case "raw": return (0, import_react.createElement)(Box, { flexDirection: "column" }, ...displayText(detail.text).split("\n").slice(0, 40).map((line, at) => (0, import_react.createElement)(Text, {
|
|
28903
|
-
key: at,
|
|
28904
|
-
dimColor: true,
|
|
28905
|
-
wrap: "truncate-end"
|
|
28906
|
-
}, ` ${line}`)), (0, import_react.createElement)(Text, { dimColor: true }, detail.truncated ? " … (output truncated)" : " (end of output)"));
|
|
28907
|
-
default: return assertNever(detail, "tool detail kind");
|
|
28908
|
-
}
|
|
29718
|
+
borderStyle: "round",
|
|
29719
|
+
borderColor: inkColor(getPalette().dim),
|
|
29720
|
+
paddingX: 1
|
|
29721
|
+
}, (0, import_react.createElement)(Text, {
|
|
29722
|
+
color: inkColor(getPalette().brand),
|
|
29723
|
+
bold: true,
|
|
29724
|
+
wrap: "truncate-end"
|
|
29725
|
+
}, truncateColumns(`${view.title} · ${view.files.length === 0 ? "no files" : `${fileIndex + 1}/${view.files.length} ${file?.path ?? ""}`} · rows ${lines.length === 0 ? 0 : visibleScroll + 1}-${Math.min(lines.length, visibleScroll + viewport.bodyRows)}/${lines.length}`, viewport.contentColumns)), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(StyledRows, { lines: lines.slice(visibleScroll, visibleScroll + viewport.bodyRows) }), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(Text, {
|
|
29726
|
+
color: inkColor(getPalette().dim),
|
|
29727
|
+
wrap: "truncate-end"
|
|
29728
|
+
}, truncateColumns("↑/↓ scroll · g/G ends · esc/q close", viewport.contentColumns)));
|
|
28909
29729
|
}
|
|
28910
|
-
/**
|
|
28911
|
-
function
|
|
28912
|
-
|
|
28913
|
-
case "user": return entry.notice ? (0, import_react.createElement)(Text, { dimColor: true }, `⤷ ${displayText(entry.text)}`) : (0, import_react.createElement)(Text, null, brand("❯ "), displayText(entry.text));
|
|
28914
|
-
case "assistant": return (0, import_react.createElement)(Box, { flexDirection: "column" }, entry.reasoning === "" ? void 0 : showReasoning ? (0, import_react.createElement)(ReasoningBody, { text: entry.reasoning }) : (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, `✻ Thinking (${entry.reasoning.length} chars, Ctrl+R to expand)`), (0, import_react.createElement)(MarkdownBody, {
|
|
28915
|
-
text: entry.text,
|
|
28916
|
-
indent: 2
|
|
28917
|
-
}));
|
|
28918
|
-
case "tool": {
|
|
28919
|
-
const mark = entry.state === "running" ? (0, import_react.createElement)(Pulse) : entry.state === "error" ? (0, import_react.createElement)(Text, { color: inkColor(getPalette().error) }, "⨯") : (0, import_react.createElement)(Text, { color: inkColor(getPalette().success) }, "⏺");
|
|
28920
|
-
return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(Text, { wrap: verbose ? "truncate-end" : void 0 }, mark, " ", brand(displayText(entry.name)), entry.preview === "" ? "" : ` ${dim(displayText(entry.preview))}`), entry.summary === "" ? void 0 : (0, import_react.createElement)(Text, {
|
|
28921
|
-
color: entry.state === "error" ? inkColor(getPalette().error) : inkColor(getPalette().dim),
|
|
28922
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28923
|
-
}, ` ⎿ ${displayText(entry.summary)}`), verbose && entry.detail !== void 0 ? (0, import_react.createElement)(ToolDetailBody, { detail: entry.detail }) : void 0);
|
|
28924
|
-
}
|
|
28925
|
-
case "command": {
|
|
28926
|
-
const mark = entry.state === "running" ? (0, import_react.createElement)(Pulse) : entry.state === "error" ? (0, import_react.createElement)(Text, { color: inkColor(getPalette().error) }, "⨯") : (0, import_react.createElement)(Text, { color: inkColor(getPalette().success) }, "⏺");
|
|
28927
|
-
return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(Text, { wrap: verbose ? "truncate-end" : void 0 }, mark, " ", brand(displayText(`/${entry.name}`)), entry.args === "" ? "" : ` ${dim(displayText(entry.args))}`), entry.summary === "" ? void 0 : (0, import_react.createElement)(Text, {
|
|
28928
|
-
color: inkColor(getPalette().dim),
|
|
28929
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28930
|
-
}, ` ⎿ ${displayText(entry.summary)}`));
|
|
28931
|
-
}
|
|
28932
|
-
case "turn-marker": return (0, import_react.createElement)(Text, {
|
|
28933
|
-
dimColor: true,
|
|
28934
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28935
|
-
}, ` ⏹ ${displayText(entry.text)}`);
|
|
28936
|
-
case "compaction": return (0, import_react.createElement)(Text, {
|
|
28937
|
-
dimColor: true,
|
|
28938
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28939
|
-
}, entry.ok ? ` ⧉ compacted ~${formatTokens(entry.tokens)} tokens` : ` ⧉ compaction failed: ${displayText(entry.error)}`);
|
|
28940
|
-
case "retry": return (0, import_react.createElement)(Text, {
|
|
28941
|
-
color: entry.state === "running" ? inkColor(getPalette().warn) : inkColor(getPalette().dim),
|
|
28942
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28943
|
-
}, ` ↻ retry ${entry.attempt}/${entry.max} · ${displayText(entry.code)} · ${Math.round(entry.delayMs / 100) / 10}s`);
|
|
28944
|
-
case "files": {
|
|
28945
|
-
const shown = entry.paths.slice(0, 3).map((path) => displayText(path)).join(" · ");
|
|
28946
|
-
const more = entry.paths.length > 3 ? ` (+${entry.paths.length - 3} more)` : "";
|
|
28947
|
-
return (0, import_react.createElement)(Text, {
|
|
28948
|
-
dimColor: true,
|
|
28949
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28950
|
-
}, ` ⎄ ${shown}${more}`);
|
|
28951
|
-
}
|
|
28952
|
-
case "pending": return (0, import_react.createElement)(Text, { wrap: verbose ? "truncate-end" : void 0 }, brand("❯ "), displayText(entry.text));
|
|
28953
|
-
case "error": return (0, import_react.createElement)(Text, { wrap: verbose ? "truncate-end" : void 0 }, error(displayText(entry.text)));
|
|
28954
|
-
default: return assertNever(entry, "transcript entry kind");
|
|
28955
|
-
}
|
|
29730
|
+
/** Codex-style panel rhythm that still participates in the row budget. */
|
|
29731
|
+
function PanelGap({ visible }) {
|
|
29732
|
+
return visible ? (0, import_react.createElement)(Text, null, " ") : void 0;
|
|
28956
29733
|
}
|
|
28957
29734
|
/**
|
|
28958
|
-
* The whale header with a compact
|
|
28959
|
-
*
|
|
28960
|
-
*
|
|
28961
|
-
*
|
|
29735
|
+
* The whale header with a compact copy lockup. The dsh kernel version (when
|
|
29736
|
+
* the host manifest resolves), the title, the bilingual slogan, and the key
|
|
29737
|
+
* hint stay centered inside the existing eight content rows, preserving the
|
|
29738
|
+
* Static header's ten physical rows; without a resolvable host the lockup
|
|
29739
|
+
* keeps its historical three lines. Short or narrow terminals keep a one-line
|
|
29740
|
+
* form without the kernel line.
|
|
28962
29741
|
*/
|
|
28963
29742
|
function Header({ resumed }) {
|
|
28964
29743
|
const stdout = useStdout().stdout;
|
|
28965
29744
|
const rows = stdout?.rows ?? 40;
|
|
28966
29745
|
const columns = stdout?.columns ?? 80;
|
|
29746
|
+
const kernelLine = (() => {
|
|
29747
|
+
const version = dshKernelVersion();
|
|
29748
|
+
return version === void 0 ? void 0 : `dsh-v${version}`;
|
|
29749
|
+
})();
|
|
28967
29750
|
const title = `DeepSeek Harness · v${DSH_CODE_VERSION}`;
|
|
28968
29751
|
const slogan = "Into the Unknown 探索未至之境";
|
|
28969
29752
|
const hint = resumed ? "resumed · /help · Esc interrupt" : "/help · Esc interrupt · Ctrl+C quit";
|
|
28970
|
-
const
|
|
29753
|
+
const copyWidths = [
|
|
29754
|
+
visibleColumns(title),
|
|
29755
|
+
visibleColumns(slogan),
|
|
29756
|
+
visibleColumns(hint)
|
|
29757
|
+
];
|
|
29758
|
+
if (kernelLine !== void 0) copyWidths.push(visibleColumns(kernelLine));
|
|
29759
|
+
const copyColumns = Math.max(...copyWidths);
|
|
28971
29760
|
const compact = `${title} · ${hint}`;
|
|
28972
|
-
if (rows < 20 || columns < 26 + copyColumns +
|
|
29761
|
+
if (rows < 20 || columns < 26 + copyColumns + 10) return (0, import_react.createElement)(Box, {
|
|
28973
29762
|
width: Math.max(1, columns - 1),
|
|
28974
29763
|
borderStyle: "round",
|
|
28975
29764
|
borderColor: inkColor(getPalette().brand),
|
|
@@ -28984,7 +29773,7 @@ function Header({ resumed }) {
|
|
|
28984
29773
|
gap: 2,
|
|
28985
29774
|
borderStyle: "round",
|
|
28986
29775
|
borderColor: inkColor(getPalette().brand),
|
|
28987
|
-
paddingX:
|
|
29776
|
+
paddingX: 2,
|
|
28988
29777
|
alignSelf: "flex-start"
|
|
28989
29778
|
}, (0, import_react.createElement)(Box, {
|
|
28990
29779
|
flexDirection: "column",
|
|
@@ -28997,7 +29786,10 @@ function Header({ resumed }) {
|
|
|
28997
29786
|
flexDirection: "column",
|
|
28998
29787
|
width: copyColumns,
|
|
28999
29788
|
justifyContent: "center"
|
|
29000
|
-
}, (0, import_react.createElement)(Text, {
|
|
29789
|
+
}, ...kernelLine === void 0 ? [] : [(0, import_react.createElement)(Text, {
|
|
29790
|
+
color: inkColor(getPalette().dim),
|
|
29791
|
+
wrap: "truncate-end"
|
|
29792
|
+
}, kernelLine)], (0, import_react.createElement)(Text, {
|
|
29001
29793
|
color: inkColor(getPalette().brandBright),
|
|
29002
29794
|
bold: true,
|
|
29003
29795
|
wrap: "truncate-end"
|
|
@@ -29041,8 +29833,69 @@ function TodoPanel({ todos }) {
|
|
|
29041
29833
|
color: inkColor(getPalette().brand),
|
|
29042
29834
|
bold: true,
|
|
29043
29835
|
wrap: "truncate-end"
|
|
29044
|
-
}, `todos ${completed}/${todos.length}`, (0, import_react.createElement)(Text, { dimColor: true }, ` · ${inProgress} active · ${pending} pending`), current === void 0 ? "" : (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright) }, ` · ${todoMark(current.status)} ${displayText(current.content)}`)));
|
|
29836
|
+
}, `todos ${completed}/${todos.length}`, (0, import_react.createElement)(Text, { dimColor: true }, ` · ${inProgress} active · ${pending} pending`), current === void 0 ? "" : (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright) }, ` · ${todoMark(current.status)} ${displayText(current.content)}`), (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, " · /todos")));
|
|
29837
|
+
}
|
|
29838
|
+
/**
|
|
29839
|
+
* The /todos subpage: the full todo list in one bounded, scrollable panel.
|
|
29840
|
+
* The live tree's TodoPanel stays a one-row summary; this exclusive view
|
|
29841
|
+
* shows EVERY item with its three-state mark inside the shared panel
|
|
29842
|
+
* viewport (same contract as /help and Ctrl+O: border/title/body/footer all
|
|
29843
|
+
* ride one height budget, the composer and status stay put below).
|
|
29844
|
+
*/
|
|
29845
|
+
function TodoListPanel({ todos, onClose }) {
|
|
29846
|
+
const stdout = useStdout().stdout;
|
|
29847
|
+
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
29848
|
+
const [scroll, setScroll] = (0, import_react.useState)(0);
|
|
29849
|
+
const bodyColumns = Math.max(4, viewport.contentColumns - 4);
|
|
29850
|
+
const completed = todos.filter((todo) => todo.status === "completed").length;
|
|
29851
|
+
const inProgress = todos.filter((todo) => todo.status === "in_progress").length;
|
|
29852
|
+
const pending = todos.length - completed - inProgress;
|
|
29853
|
+
const rows = todos.length === 0 ? [(0, import_react.createElement)(Text, {
|
|
29854
|
+
key: "empty",
|
|
29855
|
+
dimColor: true,
|
|
29856
|
+
wrap: "truncate-end"
|
|
29857
|
+
}, " no todos yet")] : todos.map((todo) => (0, import_react.createElement)(Text, {
|
|
29858
|
+
key: todo.content,
|
|
29859
|
+
dimColor: true,
|
|
29860
|
+
wrap: "truncate-end"
|
|
29861
|
+
}, ` ${todoMark(todo.status)} ${truncateColumns(displayText(todo.content), bodyColumns)}`));
|
|
29862
|
+
const visibleScroll = clampScroll(scroll, rows.length, viewport.bodyRows);
|
|
29863
|
+
const scrollBy = (delta) => {
|
|
29864
|
+
setScroll((current) => moveScroll(current, delta, rows.length, viewport.bodyRows));
|
|
29865
|
+
};
|
|
29866
|
+
(0, import_react.useEffect)(() => {
|
|
29867
|
+
if (visibleScroll !== scroll) setScroll(visibleScroll);
|
|
29868
|
+
}, [visibleScroll, scroll]);
|
|
29869
|
+
useInput((input, key) => {
|
|
29870
|
+
if (key.escape || input === "q") {
|
|
29871
|
+
onClose();
|
|
29872
|
+
return;
|
|
29873
|
+
}
|
|
29874
|
+
if (key.upArrow) scrollBy(-1);
|
|
29875
|
+
else if (key.downArrow) scrollBy(1);
|
|
29876
|
+
else if (key.pageUp) scrollBy(-Math.max(1, viewport.bodyRows - 1));
|
|
29877
|
+
else if (key.pageDown) scrollBy(Math.max(1, viewport.bodyRows - 1));
|
|
29878
|
+
else if (input === "g") setScroll(0);
|
|
29879
|
+
else if (input === "G") setScroll(Math.max(0, rows.length - viewport.bodyRows));
|
|
29880
|
+
});
|
|
29881
|
+
if (viewport.maxHeight === 0) return (0, import_react.createElement)(Box, { display: "none" });
|
|
29882
|
+
if (viewport.compact) return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns("todos · esc/q close", viewport.contentColumns));
|
|
29883
|
+
return (0, import_react.createElement)(Box, {
|
|
29884
|
+
flexDirection: "column",
|
|
29885
|
+
width: viewport.outerColumns,
|
|
29886
|
+
paddingX: 1,
|
|
29887
|
+
borderStyle: "round",
|
|
29888
|
+
borderColor: inkColor(getPalette().brand)
|
|
29889
|
+
}, (0, import_react.createElement)(Text, {
|
|
29890
|
+
color: inkColor(getPalette().brand),
|
|
29891
|
+
bold: true,
|
|
29892
|
+
wrap: "truncate-end"
|
|
29893
|
+
}, truncateColumns(`todos · ${completed}/${todos.length} done · ${inProgress} active · ${pending} pending · rows ${rows.length === 0 ? 0 : visibleScroll + 1}-${Math.min(rows.length, visibleScroll + viewport.bodyRows)}/${rows.length}`, viewport.contentColumns)), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), ...rows.slice(visibleScroll, visibleScroll + viewport.bodyRows), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(Text, {
|
|
29894
|
+
dimColor: true,
|
|
29895
|
+
wrap: "truncate-end"
|
|
29896
|
+
}, dim(truncateColumns("↑↓ scroll · pgup/pgdn page · g/G ends · esc/q close", viewport.contentColumns))));
|
|
29045
29897
|
}
|
|
29898
|
+
const MemoTodoListPanel = (0, import_react.memo)(TodoListPanel);
|
|
29046
29899
|
/**
|
|
29047
29900
|
* Ink props for one status tone: the Codex status-line accent mapping over
|
|
29048
29901
|
* the DeepSeek palette, all blue by design — the status bar speaks only in
|
|
@@ -29125,7 +29978,7 @@ function statusToneProps(tone) {
|
|
|
29125
29978
|
* truncation degrades groups, it never wraps a row.
|
|
29126
29979
|
*
|
|
29127
29980
|
* The DeepSeek easter egg: when the model label *switches* to an official
|
|
29128
|
-
* DeepSeek route, the composer's INPUT ROW (
|
|
29981
|
+
* DeepSeek route, the composer's INPUT ROW (the band's middle) plays Codex's
|
|
29129
29982
|
* effort-ignition "Wave" — a blue crest sweeping the content row column by
|
|
29130
29983
|
* column, with the `· ✦ ✧` sparkles on the deepseek tier — and the prompt
|
|
29131
29984
|
* marker keeps the tier accent afterwards. The border stays a constant
|
|
@@ -29134,9 +29987,10 @@ function statusToneProps(tone) {
|
|
|
29134
29987
|
*/
|
|
29135
29988
|
/** Theme anchors for the one-shot composer wave, read from the active palette
|
|
29136
29989
|
* so the wave stays coordinated in both themes. The flash tier runs the
|
|
29137
|
-
* brand blues; the deepseek
|
|
29138
|
-
* richer mix
|
|
29139
|
-
*
|
|
29990
|
+
* brand blues; the deepseek AND unknown tiers swap in the code sky-blue for
|
|
29991
|
+
* a brighter, richer mix (the unknown tier reuses the pro palette). Codex's
|
|
29992
|
+
* Wave bands carry no hue index (only hues[0] tints the row), so the accent
|
|
29993
|
+
* the prompt keeps is always hues[0]. */
|
|
29140
29994
|
function deepseekWaveHues(tier) {
|
|
29141
29995
|
const palette = getPalette();
|
|
29142
29996
|
return tier === "flash" ? [
|
|
@@ -29150,10 +30004,28 @@ function deepseekWaveHues(tier) {
|
|
|
29150
30004
|
];
|
|
29151
30005
|
}
|
|
29152
30006
|
function StatusLine({ facts, stats, busy, columns, items }) {
|
|
29153
|
-
const layout = layoutStatusBar(facts, stats, Math.max(8, columns - 2), {
|
|
30007
|
+
const layout = (0, import_react.useMemo)(() => layoutStatusBar(facts, stats, Math.max(8, columns - 2), {
|
|
30008
|
+
busy,
|
|
30009
|
+
items,
|
|
30010
|
+
contextWidth: Math.max(5, columns - 6)
|
|
30011
|
+
}), [
|
|
30012
|
+
facts.model,
|
|
30013
|
+
facts.mode,
|
|
30014
|
+
facts.cwd,
|
|
30015
|
+
facts.branch,
|
|
30016
|
+
facts.sessionId,
|
|
30017
|
+
facts.title,
|
|
30018
|
+
facts.sandbox,
|
|
30019
|
+
facts.plan,
|
|
30020
|
+
facts.permission,
|
|
30021
|
+
facts.goal?.phase,
|
|
30022
|
+
facts.goal?.rounds,
|
|
30023
|
+
facts.goal?.max,
|
|
30024
|
+
stats,
|
|
29154
30025
|
busy,
|
|
30026
|
+
columns,
|
|
29155
30027
|
items
|
|
29156
|
-
|
|
30028
|
+
]);
|
|
29157
30029
|
const renderRow = (row, key, indent = 0) => {
|
|
29158
30030
|
const leftParts = [];
|
|
29159
30031
|
row.left.forEach((group, groupIndex) => {
|
|
@@ -29522,10 +30394,13 @@ function QuestionBar({ store, snapshot, locked }) {
|
|
|
29522
30394
|
returnToOptions();
|
|
29523
30395
|
return;
|
|
29524
30396
|
}
|
|
29525
|
-
setCustom((current) => current
|
|
30397
|
+
setCustom((current) => deleteLastGrapheme(current));
|
|
29526
30398
|
return;
|
|
29527
30399
|
}
|
|
29528
|
-
if (input !== "" && !key.ctrl && !key.meta)
|
|
30400
|
+
if (input !== "" && !key.ctrl && !key.meta) {
|
|
30401
|
+
const text = stripPasteMarkers(input);
|
|
30402
|
+
if (text !== "") setCustom((current) => current + text);
|
|
30403
|
+
}
|
|
29529
30404
|
return;
|
|
29530
30405
|
}
|
|
29531
30406
|
if (key.upArrow) {
|
|
@@ -29692,7 +30567,7 @@ function providerStateLabel(row) {
|
|
|
29692
30567
|
return `${route} · ${row.configured ? "provider auth" : "not configured"}`;
|
|
29693
30568
|
}
|
|
29694
30569
|
/** The provider-management stage reached from /model with `a`. */
|
|
29695
|
-
function ProviderPanel({ directory, error, onCredential, onUnset, onRemove, onRetry, onBack }) {
|
|
30570
|
+
function ProviderPanel({ directory, error, onCredential, onConfigure, onUnset, onRemove, onRetry, onBack }) {
|
|
29696
30571
|
const stdout = useStdout().stdout;
|
|
29697
30572
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
29698
30573
|
const rows = directory?.rows ?? [];
|
|
@@ -29738,6 +30613,11 @@ function ProviderPanel({ directory, error, onCredential, onUnset, onRemove, onRe
|
|
|
29738
30613
|
}
|
|
29739
30614
|
const target = rows[cursor];
|
|
29740
30615
|
if (target === void 0) return;
|
|
30616
|
+
if (key.tab) {
|
|
30617
|
+
if (target.settingsNs.length === 0) setActionError("this provider is not managed by Harness settings");
|
|
30618
|
+
else onConfigure(target);
|
|
30619
|
+
return;
|
|
30620
|
+
}
|
|
29741
30621
|
if (input === "d") {
|
|
29742
30622
|
const facts = target.credential;
|
|
29743
30623
|
if (facts?.kind !== "facts" || !facts.configured) setActionError("this provider has no configured API key to remove");
|
|
@@ -29809,7 +30689,143 @@ function ProviderPanel({ directory, error, onCredential, onUnset, onRemove, onRe
|
|
|
29809
30689
|
}), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(Text, {
|
|
29810
30690
|
color: inkColor(getPalette().dim),
|
|
29811
30691
|
wrap: "truncate-end"
|
|
29812
|
-
}, truncateColumns("↑↓ move · enter add/update key · d remove key · x remove custom provider · r retry · esc back", viewport.contentColumns)));
|
|
30692
|
+
}, truncateColumns("↑↓ move · tab configure · enter add/update key · d remove key · x remove custom provider · r retry · esc back", viewport.contentColumns)));
|
|
30693
|
+
}
|
|
30694
|
+
/** Provider configuration editor: only explicit models are written to settings. */
|
|
30695
|
+
function ProviderConfigurationPanel({ target, catalog, save, done, back }) {
|
|
30696
|
+
const stdout = useStdout().stdout;
|
|
30697
|
+
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
30698
|
+
const [baseURL, setBaseURL] = (0, import_react.useState)(target.configuration.baseURL ?? "");
|
|
30699
|
+
const [models, setModels] = (0, import_react.useState)(target.configuration.models);
|
|
30700
|
+
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
30701
|
+
const [focus, setFocus] = (0, import_react.useState)("url");
|
|
30702
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
30703
|
+
const [error, setError] = (0, import_react.useState)(void 0);
|
|
30704
|
+
const choices = (0, import_react.useMemo)(() => {
|
|
30705
|
+
const known = catalog.filter((row) => row.provider === target.provider);
|
|
30706
|
+
const ids = new Set(known.map((row) => row.model));
|
|
30707
|
+
return [...known.map((row) => ({
|
|
30708
|
+
id: row.model,
|
|
30709
|
+
name: row.modelName
|
|
30710
|
+
})), ...models.filter((model) => !ids.has(model.id)).map((model) => ({
|
|
30711
|
+
id: model.id,
|
|
30712
|
+
name: model.name ?? model.id
|
|
30713
|
+
}))];
|
|
30714
|
+
}, [
|
|
30715
|
+
catalog,
|
|
30716
|
+
models,
|
|
30717
|
+
target.provider
|
|
30718
|
+
]);
|
|
30719
|
+
const selected = choices[cursor];
|
|
30720
|
+
const selectedModel = selected === void 0 ? void 0 : models.find((model) => model.id === selected.id);
|
|
30721
|
+
const updateSelected = (change) => {
|
|
30722
|
+
if (selected === void 0) return;
|
|
30723
|
+
setModels((current) => current.some((model) => model.id === selected.id) ? current.map((model) => model.id === selected.id ? {
|
|
30724
|
+
...model,
|
|
30725
|
+
...change
|
|
30726
|
+
} : model) : [...current, {
|
|
30727
|
+
id: selected.id,
|
|
30728
|
+
name: selected.name,
|
|
30729
|
+
...change
|
|
30730
|
+
}]);
|
|
30731
|
+
};
|
|
30732
|
+
const submit = () => {
|
|
30733
|
+
if (busy) return;
|
|
30734
|
+
setBusy(true);
|
|
30735
|
+
setError(void 0);
|
|
30736
|
+
Promise.resolve().then(() => save(target, {
|
|
30737
|
+
...baseURL.trim() === "" ? {} : { baseURL },
|
|
30738
|
+
models
|
|
30739
|
+
})).then(done, (reason) => {
|
|
30740
|
+
setBusy(false);
|
|
30741
|
+
setError(singleLineText(reason instanceof Error ? reason.message : String(reason)));
|
|
30742
|
+
});
|
|
30743
|
+
};
|
|
30744
|
+
useStableInput((input, key) => {
|
|
30745
|
+
if (busy) return;
|
|
30746
|
+
if (key.escape || input === "q") {
|
|
30747
|
+
back();
|
|
30748
|
+
return;
|
|
30749
|
+
}
|
|
30750
|
+
if (key.tab) {
|
|
30751
|
+
setFocus((current) => current === "url" ? "models" : current === "models" ? "context" : current === "context" ? "output" : "url");
|
|
30752
|
+
return;
|
|
30753
|
+
}
|
|
30754
|
+
if (key.return) {
|
|
30755
|
+
submit();
|
|
30756
|
+
return;
|
|
30757
|
+
}
|
|
30758
|
+
if (focus === "url") {
|
|
30759
|
+
if (key.backspace || key.delete) setBaseURL((current) => deleteLastGrapheme(current));
|
|
30760
|
+
else if (!key.ctrl && !key.meta && input !== "") setBaseURL((current) => current + stripPasteMarkers(input));
|
|
30761
|
+
return;
|
|
30762
|
+
}
|
|
30763
|
+
if (key.upArrow && choices.length > 0) {
|
|
30764
|
+
setCursor((current) => Math.max(0, current - 1));
|
|
30765
|
+
return;
|
|
30766
|
+
}
|
|
30767
|
+
if (key.downArrow && choices.length > 0) {
|
|
30768
|
+
setCursor((current) => Math.min(choices.length - 1, current + 1));
|
|
30769
|
+
return;
|
|
30770
|
+
}
|
|
30771
|
+
if (focus === "models" && input === " ") {
|
|
30772
|
+
if (selectedModel === void 0) updateSelected({});
|
|
30773
|
+
else setModels((current) => current.filter((model) => model.id !== selectedModel.id));
|
|
30774
|
+
return;
|
|
30775
|
+
}
|
|
30776
|
+
if ((focus === "context" || focus === "output") && selectedModel !== void 0) {
|
|
30777
|
+
const field = focus === "context" ? "contextWindow" : "maxTokens";
|
|
30778
|
+
const current = String(selectedModel[field] ?? "");
|
|
30779
|
+
if (key.backspace || key.delete) {
|
|
30780
|
+
const next = current.slice(0, -1);
|
|
30781
|
+
updateSelected({ [field]: next === "" ? void 0 : Number(next) });
|
|
30782
|
+
} else {
|
|
30783
|
+
const digits = stripPasteMarkers(input);
|
|
30784
|
+
if (/^[0-9]+$/u.test(digits)) {
|
|
30785
|
+
const next = `${current}${digits}`;
|
|
30786
|
+
updateSelected({ [field]: Number(next) });
|
|
30787
|
+
}
|
|
30788
|
+
}
|
|
30789
|
+
}
|
|
30790
|
+
}, true);
|
|
30791
|
+
if (viewport.maxHeight === 0) return (0, import_react.createElement)(Box, { display: "none" });
|
|
30792
|
+
const stateRows = error === void 0 ? [] : [(0, import_react.createElement)(Text, {
|
|
30793
|
+
key: "error",
|
|
30794
|
+
color: inkColor(getPalette().error),
|
|
30795
|
+
wrap: "truncate-end"
|
|
30796
|
+
}, truncateColumns(` ${error}`, viewport.contentColumns))];
|
|
30797
|
+
const rowBudget = Math.max(0, viewport.bodyRows - stateRows.length - 1);
|
|
30798
|
+
const first = selectionWindow(cursor, choices.length, rowBudget);
|
|
30799
|
+
const visible = choices.slice(first, first + rowBudget);
|
|
30800
|
+
return (0, import_react.createElement)(Box, {
|
|
30801
|
+
flexDirection: "column",
|
|
30802
|
+
width: viewport.outerColumns,
|
|
30803
|
+
paddingX: 1,
|
|
30804
|
+
borderStyle: "round",
|
|
30805
|
+
borderColor: inkColor(getPalette().brand)
|
|
30806
|
+
}, (0, import_react.createElement)(Text, {
|
|
30807
|
+
color: inkColor(getPalette().brand),
|
|
30808
|
+
bold: true,
|
|
30809
|
+
wrap: "truncate-end"
|
|
30810
|
+
}, truncateColumns(`/model - ${target.displayName} configuration`, viewport.contentColumns)), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(Text, {
|
|
30811
|
+
color: focus === "url" ? inkColor(getPalette().brandBright) : inkColor(getPalette().dim),
|
|
30812
|
+
wrap: "truncate-end"
|
|
30813
|
+
}, truncateColumns(` ${focus === "url" ? ">" : " "} endpoint: ${baseURL === "" ? "(adapter default)" : baseURL}`, viewport.contentColumns)), ...stateRows, ...visible.map((choice, index) => {
|
|
30814
|
+
const absolute = first + index;
|
|
30815
|
+
const model = models.find((item) => item.id === choice.id);
|
|
30816
|
+
const selectedMark = model === void 0 ? "[ ]" : "[x]";
|
|
30817
|
+
const context = model?.contextWindow === void 0 ? "-" : String(model.contextWindow);
|
|
30818
|
+
const output = model?.maxTokens === void 0 ? "-" : String(model.maxTokens);
|
|
30819
|
+
const active = absolute === cursor && focus !== "url";
|
|
30820
|
+
return (0, import_react.createElement)(Text, {
|
|
30821
|
+
key: choice.id,
|
|
30822
|
+
color: active ? inkColor(getPalette().brandBright) : model === void 0 ? inkColor(getPalette().dim) : inkColor(getPalette().success),
|
|
30823
|
+
wrap: "truncate-end"
|
|
30824
|
+
}, truncateColumns(`${active ? ">" : " "} ${selectedMark} ${choice.name} in:${context} out:${output}`, viewport.contentColumns));
|
|
30825
|
+
}), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(Text, {
|
|
30826
|
+
color: inkColor(getPalette().dim),
|
|
30827
|
+
wrap: "truncate-end"
|
|
30828
|
+
}, truncateColumns("tab endpoint/models/input/output - space select - arrows model - digits set window - enter save - esc back", viewport.contentColumns)));
|
|
29813
30829
|
}
|
|
29814
30830
|
/** Write-only masked API-key editor; the secret lives only in this mounted component. */
|
|
29815
30831
|
function ProviderCredentialPanel({ target, save, done, back }) {
|
|
@@ -29852,7 +30868,7 @@ function ProviderCredentialPanel({ target, save, done, back }) {
|
|
|
29852
30868
|
return;
|
|
29853
30869
|
}
|
|
29854
30870
|
if (key.ctrl || key.meta || input.length === 0) return;
|
|
29855
|
-
const next = draft + input;
|
|
30871
|
+
const next = draft + stripPasteMarkers(input);
|
|
29856
30872
|
if (next.length > 4096) {
|
|
29857
30873
|
setError("API key input is too long");
|
|
29858
30874
|
return;
|
|
@@ -29991,7 +31007,7 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
29991
31007
|
key: "key-mentions",
|
|
29992
31008
|
dimColor: true,
|
|
29993
31009
|
wrap: "truncate-end"
|
|
29994
|
-
}, "
|
|
31010
|
+
}, " @ mentions workspace files and sessions"),
|
|
29995
31011
|
(0, import_react.createElement)(Text, {
|
|
29996
31012
|
key: "key-inspector",
|
|
29997
31013
|
dimColor: true,
|
|
@@ -30029,17 +31045,23 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
30029
31045
|
(0, import_react.createElement)(Box, { key: "local-mode" }, row("/mode", "inspect or select the agent preset (/mode [preset])")),
|
|
30030
31046
|
(0, import_react.createElement)(Box, { key: "local-permission" }, row("/permission", "inspect or select the permission preset (/permission [preset])")),
|
|
30031
31047
|
(0, import_react.createElement)(Box, { key: "local-new" }, row("/new", "create and switch to a fresh session (/new [preset])")),
|
|
31048
|
+
(0, import_react.createElement)(Box, { key: "local-fork" }, row("/fork", "fork at the latest completed turn (/fork [event-seq])")),
|
|
30032
31049
|
(0, import_react.createElement)(Box, { key: "local-resume" }, row("/resume", "browse or switch root sessions (/resume [id|prefix])")),
|
|
30033
31050
|
(0, import_react.createElement)(Box, { key: "local-plugin" }, row("/plugin", "inspect the live plugin composition")),
|
|
31051
|
+
(0, import_react.createElement)(Box, { key: "local-jobs" }, row("/jobs", "inspect background jobs")),
|
|
30034
31052
|
(0, import_react.createElement)(Box, { key: "local-statusline" }, row("/statusline", "customize the status line items")),
|
|
30035
31053
|
(0, import_react.createElement)(Box, { key: "local-theme" }, row("/theme", "switch the color theme")),
|
|
30036
31054
|
(0, import_react.createElement)(Box, { key: "local-history" }, row("/history", "search and recall past prompts")),
|
|
30037
31055
|
(0, import_react.createElement)(Box, { key: "local-agents" }, row("/agents", "inspect subagent sessions of this conversation")),
|
|
31056
|
+
(0, import_react.createElement)(Box, { key: "local-todos" }, row("/todos", "inspect the full todo list")),
|
|
30038
31057
|
(0, import_react.createElement)(Box, { key: "local-subagent" }, row("/subagent", "choose the model delegated subagents run on")),
|
|
30039
31058
|
(0, import_react.createElement)(Box, { key: "local-delete" }, row("/delete", "delete a session and its subagent threads")),
|
|
30040
31059
|
(0, import_react.createElement)(Box, { key: "local-clear" }, row("/clear", "clear the screen")),
|
|
30041
31060
|
(0, import_react.createElement)(Box, { key: "local-export" }, row("/export", "export the transcript to markdown (/export [path])")),
|
|
30042
31061
|
(0, import_react.createElement)(Box, { key: "local-title" }, row("/title", "rename this session (/title <text>)")),
|
|
31062
|
+
(0, import_react.createElement)(Box, { key: "local-copy" }, row("/copy", "copy the latest assistant response")),
|
|
31063
|
+
(0, import_react.createElement)(Box, { key: "local-diff" }, row("/diff", "inspect Git changes (/diff [--staged|ref])")),
|
|
31064
|
+
(0, import_react.createElement)(Box, { key: "local-review" }, row("/review", "review Git changes under read-only permissions")),
|
|
30043
31065
|
(0, import_react.createElement)(Box, { key: "local-quit" }, row("/quit", "exit")),
|
|
30044
31066
|
...descriptors.map((descriptor) => (0, import_react.createElement)(Text, {
|
|
30045
31067
|
key: `command-${descriptor.name}`,
|
|
@@ -30102,19 +31124,47 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
30102
31124
|
function verboseLine(text, columns) {
|
|
30103
31125
|
return truncateColumns(displayText(text).replace(/\n/gu, " ↵ ").replace(/\t/gu, " "), Math.max(1, columns));
|
|
30104
31126
|
}
|
|
30105
|
-
/**
|
|
31127
|
+
/** Identify one whole-chunk key sequence Ink drops or blurs. */
|
|
31128
|
+
function annotateRawKey(chunk) {
|
|
31129
|
+
switch (chunk) {
|
|
31130
|
+
case "": return "delete-backward";
|
|
31131
|
+
case "\x1B":
|
|
31132
|
+
case "\x1B\b": return "delete-word-backward";
|
|
31133
|
+
case "\x1B[3~":
|
|
31134
|
+
case "\x1B[3;2~": return "delete-forward";
|
|
31135
|
+
case "\x1B[3;3~":
|
|
31136
|
+
case "\x1B[3;5~": return "delete-word-forward";
|
|
31137
|
+
case "\x1B[H":
|
|
31138
|
+
case "\x1B[1~":
|
|
31139
|
+
case "\x1B[7~":
|
|
31140
|
+
case "\x1BOH": return "home";
|
|
31141
|
+
case "\x1B[F":
|
|
31142
|
+
case "\x1B[4~":
|
|
31143
|
+
case "\x1B[8~":
|
|
31144
|
+
case "\x1BOF": return "end";
|
|
31145
|
+
default: return;
|
|
31146
|
+
}
|
|
31147
|
+
}
|
|
31148
|
+
/**
|
|
31149
|
+
* One-row editor window keeping the logical cursor visible in long drafts.
|
|
31150
|
+
* The caret and its surroundings slice at grapheme boundaries: splitting a
|
|
31151
|
+
* star-plane surrogate pair would render an isolated half under the block
|
|
31152
|
+
* caret with a width the terminal never draws.
|
|
31153
|
+
*/
|
|
30106
31154
|
function editorWindow(value, cursor, columns) {
|
|
30107
31155
|
const width = Math.max(1, columns);
|
|
30108
31156
|
const normalize = (text) => displayText(text).replace(/\n/gu, "↵").replace(/\t/gu, " ");
|
|
30109
|
-
const
|
|
30110
|
-
const
|
|
31157
|
+
const site = clampCursor(value, cursor);
|
|
31158
|
+
const caretSpan = splitGraphemes(value).find((span) => span.start === site);
|
|
31159
|
+
const caret = caretSpan === void 0 ? " " : normalize(caretSpan.text);
|
|
31160
|
+
const rest = value.slice(caretSpan === void 0 ? site : caretSpan.end);
|
|
30111
31161
|
const remaining = Math.max(0, width - visibleColumns(caret));
|
|
30112
|
-
const afterBudget = Math.min(Math.floor(remaining / 3), visibleColumns(normalize(
|
|
31162
|
+
const afterBudget = Math.min(Math.floor(remaining / 3), visibleColumns(normalize(rest)));
|
|
30113
31163
|
const beforeBudget = Math.max(0, remaining - afterBudget);
|
|
30114
31164
|
return {
|
|
30115
|
-
before: beforeBudget === 0 ? "" : displayTail(normalize(value.slice(0,
|
|
31165
|
+
before: beforeBudget === 0 ? "" : displayTail(normalize(value.slice(0, site)), beforeBudget, 1).text,
|
|
30116
31166
|
caret,
|
|
30117
|
-
after: afterBudget === 0 ? "" : truncateColumns(normalize(
|
|
31167
|
+
after: afterBudget === 0 ? "" : truncateColumns(normalize(rest), afterBudget)
|
|
30118
31168
|
};
|
|
30119
31169
|
}
|
|
30120
31170
|
/** The empty-composer placeholder text (shared by the static and wave paths). */
|
|
@@ -30313,6 +31363,11 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30313
31363
|
description: "start a fresh session",
|
|
30314
31364
|
origin: "command"
|
|
30315
31365
|
},
|
|
31366
|
+
{
|
|
31367
|
+
label: "/fork",
|
|
31368
|
+
description: "fork at a completed turn",
|
|
31369
|
+
origin: "command"
|
|
31370
|
+
},
|
|
30316
31371
|
{
|
|
30317
31372
|
label: "/resume",
|
|
30318
31373
|
description: "browse or switch sessions",
|
|
@@ -30323,6 +31378,11 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30323
31378
|
description: "inspect the plugin composition",
|
|
30324
31379
|
origin: "command"
|
|
30325
31380
|
},
|
|
31381
|
+
{
|
|
31382
|
+
label: "/jobs",
|
|
31383
|
+
description: "inspect background jobs",
|
|
31384
|
+
origin: "command"
|
|
31385
|
+
},
|
|
30326
31386
|
{
|
|
30327
31387
|
label: "/statusline",
|
|
30328
31388
|
description: "customize the status line",
|
|
@@ -30343,6 +31403,11 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30343
31403
|
description: "inspect subagent sessions of this conversation",
|
|
30344
31404
|
origin: "command"
|
|
30345
31405
|
},
|
|
31406
|
+
{
|
|
31407
|
+
label: "/todos",
|
|
31408
|
+
description: "inspect the full todo list",
|
|
31409
|
+
origin: "command"
|
|
31410
|
+
},
|
|
30346
31411
|
{
|
|
30347
31412
|
label: "/subagent",
|
|
30348
31413
|
description: "choose the model delegated subagents run on",
|
|
@@ -30368,6 +31433,21 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30368
31433
|
description: "rename this session",
|
|
30369
31434
|
origin: "command"
|
|
30370
31435
|
},
|
|
31436
|
+
{
|
|
31437
|
+
label: "/copy",
|
|
31438
|
+
description: "copy the latest assistant response",
|
|
31439
|
+
origin: "command"
|
|
31440
|
+
},
|
|
31441
|
+
{
|
|
31442
|
+
label: "/diff",
|
|
31443
|
+
description: "inspect Git changes",
|
|
31444
|
+
origin: "command"
|
|
31445
|
+
},
|
|
31446
|
+
{
|
|
31447
|
+
label: "/review",
|
|
31448
|
+
description: "review changes read-only",
|
|
31449
|
+
origin: "command"
|
|
31450
|
+
},
|
|
30371
31451
|
{
|
|
30372
31452
|
label: "/quit",
|
|
30373
31453
|
description: "exit",
|
|
@@ -30403,7 +31483,7 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30403
31483
|
}
|
|
30404
31484
|
/**
|
|
30405
31485
|
* The completion menu, rendered inside the composer's subtree directly above
|
|
30406
|
-
* the
|
|
31486
|
+
* the composer band — attached the way Claude-Code anchors its dropdown. Opening
|
|
30407
31487
|
* it grows the stack downward: the composer stays the last element on screen
|
|
30408
31488
|
* and everything above (the flushed static transcript, the status line) never
|
|
30409
31489
|
* moves. Props-only (no lifted state): the menu is a pure view of the input
|
|
@@ -30416,7 +31496,7 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
30416
31496
|
const terminalRows = stdout?.rows ?? 30;
|
|
30417
31497
|
if (!active) return void 0;
|
|
30418
31498
|
const contentColumns = Math.max(1, columns - 4);
|
|
30419
|
-
const nameWidth = Math.min(18, Math.max(1, contentColumns - 2), Math.max(0, ...rows.map((row) => visibleColumns(row.label))) + 2);
|
|
31499
|
+
const nameWidth = mention ? Math.max(1, contentColumns - 2) : Math.min(18, Math.max(1, contentColumns - 2), Math.max(0, ...rows.map((row) => visibleColumns(row.label))) + 2);
|
|
30420
31500
|
const descBudget = Math.max(0, contentColumns - nameWidth - 2);
|
|
30421
31501
|
const showFooter = terminalRows >= 12;
|
|
30422
31502
|
const verticalPadding = terminalRows >= 14 ? 1 : 0;
|
|
@@ -30454,26 +31534,32 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
30454
31534
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
30455
31535
|
* box passes every key through untouched.
|
|
30456
31536
|
*/
|
|
30457
|
-
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openStatusline, openTheme, openHistory, openAgents, openSubagent, openDelete, deleteConfirm, confirmDelete, cancelDelete, createSession, cancelSessionSwitch, notify, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, cyclePermission, exportTranscript, renameTitle, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle }) {
|
|
31537
|
+
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle, maxRows, onEditorRows }) {
|
|
30458
31538
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
31539
|
+
const stdin = useStdin().stdin;
|
|
30459
31540
|
const [value, setValue] = (0, import_react.useState)("");
|
|
30460
31541
|
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
30461
|
-
const
|
|
30462
|
-
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
31542
|
+
const killRef = (0, import_react.useRef)("");
|
|
31543
|
+
const preferredColumnRef = (0, import_react.useRef)(null);
|
|
31544
|
+
const editorScrollRef = (0, import_react.useRef)(0);
|
|
31545
|
+
const pasteBracketRef = (0, import_react.useRef)(false);
|
|
31546
|
+
/** Cancels the pending lost-paste safety timer (undefined when disarmed). */
|
|
31547
|
+
const pasteBracketCancelRef = (0, import_react.useRef)(void 0);
|
|
31548
|
+
/** Annotation of the stdin chunk Ink is about to deliver to useInput. */
|
|
31549
|
+
const rawAnnotation = (0, import_react.useRef)(void 0);
|
|
31550
|
+
const recall = (0, import_react.useRef)(beginRecall([], ""));
|
|
30467
31551
|
(0, import_react.useEffect)(() => {
|
|
30468
31552
|
if (historyFill === void 0) return;
|
|
30469
|
-
|
|
30470
|
-
|
|
31553
|
+
const safe = sanitizeDraftText(historyFill.text);
|
|
31554
|
+
setValue(safe);
|
|
31555
|
+
setCursor(safe.length);
|
|
31556
|
+
preferredColumnRef.current = null;
|
|
30471
31557
|
setDismissedMenuValue(void 0);
|
|
30472
31558
|
recall.current = {
|
|
30473
31559
|
entries: recallSpace,
|
|
30474
31560
|
index: historyFill.index,
|
|
30475
|
-
savedDraft:
|
|
30476
|
-
lastRecalled:
|
|
31561
|
+
savedDraft: safe,
|
|
31562
|
+
lastRecalled: safe
|
|
30477
31563
|
};
|
|
30478
31564
|
historyConsumed();
|
|
30479
31565
|
}, [
|
|
@@ -30481,6 +31567,20 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30481
31567
|
recallSpace,
|
|
30482
31568
|
historyConsumed
|
|
30483
31569
|
]);
|
|
31570
|
+
(0, import_react.useEffect)(() => {
|
|
31571
|
+
if (stdin === void 0) return;
|
|
31572
|
+
const originalRead = stdin.read.bind(stdin);
|
|
31573
|
+
stdin.read = function patchedRead(...args) {
|
|
31574
|
+
const chunk = originalRead(...args);
|
|
31575
|
+
if (chunk === null) return chunk;
|
|
31576
|
+
const normalized = normalizeKeyboardChunk(typeof chunk === "string" ? chunk : String(chunk));
|
|
31577
|
+
rawAnnotation.current = annotateRawKey(normalized);
|
|
31578
|
+
return normalized;
|
|
31579
|
+
};
|
|
31580
|
+
return () => {
|
|
31581
|
+
stdin.read = originalRead;
|
|
31582
|
+
};
|
|
31583
|
+
}, [stdin]);
|
|
30484
31584
|
if (recall.current.entries !== recallSpace) {
|
|
30485
31585
|
const index = recall.current.index === null || recall.current.index < recallSpace.length ? recall.current.index : null;
|
|
30486
31586
|
recall.current = {
|
|
@@ -30502,27 +31602,6 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30502
31602
|
};
|
|
30503
31603
|
const mentionActive = mentionToken !== void 0;
|
|
30504
31604
|
const [mentionRows, setMentionRows] = (0, import_react.useState)([]);
|
|
30505
|
-
const bareTokenMatch = /([^\s]+)$/u.exec(lastLine);
|
|
30506
|
-
const bareToken = bareTokenMatch === null ? "" : bareTokenMatch[1] ?? "";
|
|
30507
|
-
const pathActive = !mentionActive && !bareToken.startsWith("/") && (bareToken.includes("/") || bareToken === "." || bareToken === "..");
|
|
30508
|
-
const pathTokenStart = beforeCursor.length - bareToken.length;
|
|
30509
|
-
const [pathRows, setPathRows] = (0, import_react.useState)([]);
|
|
30510
|
-
(0, import_react.useEffect)(() => {
|
|
30511
|
-
if (!active || !pathActive) {
|
|
30512
|
-
setPathRows([]);
|
|
30513
|
-
return;
|
|
30514
|
-
}
|
|
30515
|
-
const controller = new AbortController();
|
|
30516
|
-
setPathRows([]);
|
|
30517
|
-
loadMentions(bareToken, controller.signal).then((rows) => setPathRows(rows.filter((row) => row.kind !== "session")), () => {});
|
|
30518
|
-
return () => {
|
|
30519
|
-
controller.abort();
|
|
30520
|
-
};
|
|
30521
|
-
}, [
|
|
30522
|
-
active,
|
|
30523
|
-
pathActive,
|
|
30524
|
-
bareToken
|
|
30525
|
-
]);
|
|
30526
31605
|
(0, import_react.useEffect)(() => {
|
|
30527
31606
|
if (!active || !mentionActive) {
|
|
30528
31607
|
setMentionRows([]);
|
|
@@ -30539,15 +31618,11 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30539
31618
|
mentionActive,
|
|
30540
31619
|
mentionToken?.query
|
|
30541
31620
|
]);
|
|
30542
|
-
const menuActive = (slashActive || mentionActive
|
|
31621
|
+
const menuActive = (slashActive || mentionActive) && dismissedMenuValue !== value;
|
|
30543
31622
|
const menuRows = mentionActive ? mentionRows.map((row) => ({
|
|
30544
31623
|
label: row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`,
|
|
30545
31624
|
description: row.description,
|
|
30546
31625
|
origin: "mention"
|
|
30547
|
-
})) : pathActive ? pathRows.map((row) => ({
|
|
30548
|
-
label: row.label,
|
|
30549
|
-
description: row.description,
|
|
30550
|
-
origin: "path"
|
|
30551
31626
|
})) : candidates;
|
|
30552
31627
|
/** Accept the highlighted completion-menu candidate into the draft. */
|
|
30553
31628
|
const acceptMenuCandidate = () => {
|
|
@@ -30558,13 +31633,6 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30558
31633
|
setValue(value.slice(0, mentionToken.start) + insertion + value.slice(cursor));
|
|
30559
31634
|
setCursor(mentionToken.start + insertion.length);
|
|
30560
31635
|
}
|
|
30561
|
-
} else if (pathActive) {
|
|
30562
|
-
const row = pathRows[completionIndex % Math.max(1, pathRows.length)];
|
|
30563
|
-
if (row !== void 0) {
|
|
30564
|
-
const insertion = row.kind === "directory" ? `${row.label}/` : row.label;
|
|
30565
|
-
setValue(value.slice(0, pathTokenStart) + insertion + value.slice(cursor));
|
|
30566
|
-
setCursor(pathTokenStart + insertion.length);
|
|
30567
|
-
}
|
|
30568
31636
|
} else {
|
|
30569
31637
|
const candidate = candidates[completionIndex % candidates.length];
|
|
30570
31638
|
if (candidate !== void 0) {
|
|
@@ -30575,6 +31643,21 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30575
31643
|
setCompletionIndex(0);
|
|
30576
31644
|
setDismissedMenuValue(void 0);
|
|
30577
31645
|
};
|
|
31646
|
+
/** Apply one editor edit: draft, cursor, kill buffer, menu reset. */
|
|
31647
|
+
const applyEdit = (edit) => {
|
|
31648
|
+
if (edit.killed !== void 0 && edit.killed !== "") killRef.current = edit.killed;
|
|
31649
|
+
setValue(edit.value);
|
|
31650
|
+
setCursor(edit.cursor);
|
|
31651
|
+
preferredColumnRef.current = null;
|
|
31652
|
+
setCompletionIndex(0);
|
|
31653
|
+
setDismissedMenuValue(void 0);
|
|
31654
|
+
};
|
|
31655
|
+
/** Move the cursor without editing; horizontal moves clear the column preference. */
|
|
31656
|
+
const moveCursorTo = (next) => {
|
|
31657
|
+
if (next === cursor) return;
|
|
31658
|
+
setCursor(next);
|
|
31659
|
+
preferredColumnRef.current = null;
|
|
31660
|
+
};
|
|
30578
31661
|
useInput((input, key) => {
|
|
30579
31662
|
if (!active) return;
|
|
30580
31663
|
if (deleteConfirm !== void 0) {
|
|
@@ -30610,6 +31693,10 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30610
31693
|
return;
|
|
30611
31694
|
}
|
|
30612
31695
|
if (key.ctrl && input === "d") {
|
|
31696
|
+
if (value !== "") {
|
|
31697
|
+
applyEdit(deleteForward(value, cursor));
|
|
31698
|
+
return;
|
|
31699
|
+
}
|
|
30613
31700
|
if (busy) notify("cancel the running turn before exiting (Esc or Ctrl+C)", "warning");
|
|
30614
31701
|
else quit();
|
|
30615
31702
|
return;
|
|
@@ -30630,15 +31717,20 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30630
31717
|
cancelQueued(queued[queued.length - 1].messageId);
|
|
30631
31718
|
return;
|
|
30632
31719
|
}
|
|
30633
|
-
|
|
30634
|
-
|
|
31720
|
+
const shiftEnterSequence = input === "[13;2u" || input === "[27;2;13~";
|
|
31721
|
+
if (shiftEnterSequence || key.return) {
|
|
31722
|
+
if (pasteBracketRef.current) {
|
|
31723
|
+
applyEdit(insertText(value, cursor, "\n"));
|
|
31724
|
+
return;
|
|
31725
|
+
}
|
|
31726
|
+
if (shiftEnterSequence || key.shift || key.meta || key.ctrl && input === "j") {
|
|
30635
31727
|
setValue(value.slice(0, cursor) + "\n" + value.slice(cursor));
|
|
30636
31728
|
setCursor(cursor + 1);
|
|
30637
31729
|
setDismissedMenuValue(void 0);
|
|
30638
31730
|
return;
|
|
30639
31731
|
}
|
|
30640
31732
|
if (menuActive) {
|
|
30641
|
-
if (!(!mentionActive &&
|
|
31733
|
+
if (!(!mentionActive && candidates.some((candidate) => candidate.label === value))) {
|
|
30642
31734
|
acceptMenuCandidate();
|
|
30643
31735
|
return;
|
|
30644
31736
|
}
|
|
@@ -30654,12 +31746,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30654
31746
|
recordLocal(text);
|
|
30655
31747
|
recordHistory(text);
|
|
30656
31748
|
}
|
|
30657
|
-
recall.current =
|
|
30658
|
-
entries: recallSpace,
|
|
30659
|
-
index: null,
|
|
30660
|
-
savedDraft: "",
|
|
30661
|
-
lastRecalled: null
|
|
30662
|
-
};
|
|
31749
|
+
recall.current = beginRecall(recallSpace, "");
|
|
30663
31750
|
if (text === "/quit") {
|
|
30664
31751
|
quit();
|
|
30665
31752
|
return;
|
|
@@ -30683,6 +31770,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30683
31770
|
notify(outcome, outcome.startsWith("rename failed:") ? "error" : outcome.startsWith("usage:") || outcome.includes("unavailable") ? "warning" : "info");
|
|
30684
31771
|
return;
|
|
30685
31772
|
}
|
|
31773
|
+
if (text === "/copy") {
|
|
31774
|
+
copyLastResponse().then((outcome) => notify(outcome), (error) => notify(`copy failed: ${error instanceof Error ? error.message : String(error)}`, "error"));
|
|
31775
|
+
return;
|
|
31776
|
+
}
|
|
31777
|
+
if (text === "/diff" || text.startsWith("/diff ")) {
|
|
31778
|
+
openDiff(text.slice(5));
|
|
31779
|
+
return;
|
|
31780
|
+
}
|
|
31781
|
+
if (text === "/review" || text.startsWith("/review ")) {
|
|
31782
|
+
reviewChanges(text.slice(7));
|
|
31783
|
+
return;
|
|
31784
|
+
}
|
|
30686
31785
|
if (text === "/model" || text.startsWith("/model ")) {
|
|
30687
31786
|
openModel();
|
|
30688
31787
|
return;
|
|
@@ -30717,10 +31816,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30717
31816
|
createSession(text.slice(4).trim() || void 0);
|
|
30718
31817
|
return;
|
|
30719
31818
|
}
|
|
31819
|
+
if (text === "/fork" || text.startsWith("/fork ")) {
|
|
31820
|
+
forkSession(text.slice(5));
|
|
31821
|
+
return;
|
|
31822
|
+
}
|
|
30720
31823
|
if (text === "/plugin" || text.startsWith("/plugin ")) {
|
|
30721
31824
|
openPlugin(text.slice(7).trim());
|
|
30722
31825
|
return;
|
|
30723
31826
|
}
|
|
31827
|
+
if (text === "/jobs" || text.startsWith("/jobs ")) {
|
|
31828
|
+
openJobs();
|
|
31829
|
+
return;
|
|
31830
|
+
}
|
|
30724
31831
|
if (text === "/statusline") {
|
|
30725
31832
|
openStatusline();
|
|
30726
31833
|
return;
|
|
@@ -30737,6 +31844,10 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30737
31844
|
openAgents();
|
|
30738
31845
|
return;
|
|
30739
31846
|
}
|
|
31847
|
+
if (text === "/todos") {
|
|
31848
|
+
openTodos();
|
|
31849
|
+
return;
|
|
31850
|
+
}
|
|
30740
31851
|
if (text === "/subagent") {
|
|
30741
31852
|
openSubagent();
|
|
30742
31853
|
return;
|
|
@@ -30760,25 +31871,58 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30760
31871
|
setCompletionIndex((index) => (index + 1) % menuRows.length);
|
|
30761
31872
|
return;
|
|
30762
31873
|
}
|
|
30763
|
-
|
|
30764
|
-
|
|
30765
|
-
|
|
30766
|
-
|
|
30767
|
-
if (
|
|
30768
|
-
|
|
30769
|
-
|
|
30770
|
-
|
|
31874
|
+
const rawKey = rawAnnotation.current;
|
|
31875
|
+
if (rawKey !== void 0) {
|
|
31876
|
+
if (rawKey === "home") moveCursorTo(lineBounds(value, cursor).start);
|
|
31877
|
+
else if (rawKey === "end") moveCursorTo(lineBounds(value, cursor).end);
|
|
31878
|
+
else if (rawKey === "delete-backward") applyEdit(deleteBackward(value, cursor));
|
|
31879
|
+
else if (rawKey === "delete-word-backward") applyEdit(deleteWordBackward(value, cursor));
|
|
31880
|
+
else if (rawKey === "delete-forward") applyEdit(deleteForward(value, cursor));
|
|
31881
|
+
else applyEdit(deleteWordForward(value, cursor));
|
|
31882
|
+
return;
|
|
31883
|
+
}
|
|
31884
|
+
if (key.upArrow || key.downArrow) {
|
|
31885
|
+
if (recall.current.entries.length > 0 && shouldRecallNavigate(value, cursor, recall.current.lastRecalled)) {
|
|
31886
|
+
const step = key.upArrow ? recallOlder(recall.current, value) : recallNewer(recall.current);
|
|
31887
|
+
recall.current = step.state;
|
|
31888
|
+
if (step.entry !== void 0) {
|
|
31889
|
+
const safe = sanitizeDraftText(step.entry);
|
|
31890
|
+
setValue(safe);
|
|
31891
|
+
setCursor(safe.length);
|
|
31892
|
+
preferredColumnRef.current = null;
|
|
31893
|
+
setDismissedMenuValue(void 0);
|
|
31894
|
+
}
|
|
31895
|
+
return;
|
|
31896
|
+
}
|
|
31897
|
+
const model = editorModel(value, Math.max(1, columns - 6));
|
|
31898
|
+
const preferred = preferredColumnRef.current ?? caretSite(model, cursor).column;
|
|
31899
|
+
const next = moveCursorVertically(model, cursor, preferred, key.upArrow ? -1 : 1);
|
|
31900
|
+
if (next !== cursor) {
|
|
31901
|
+
setCursor(next);
|
|
31902
|
+
preferredColumnRef.current = preferred;
|
|
30771
31903
|
}
|
|
30772
31904
|
return;
|
|
30773
31905
|
}
|
|
30774
|
-
if (key.
|
|
30775
|
-
|
|
30776
|
-
|
|
30777
|
-
|
|
30778
|
-
|
|
30779
|
-
|
|
30780
|
-
|
|
30781
|
-
|
|
31906
|
+
if (key.ctrl && (input === "p" || input === "n")) {
|
|
31907
|
+
const up = input === "p";
|
|
31908
|
+
if (recall.current.entries.length > 0 && shouldRecallNavigate(value, cursor, recall.current.lastRecalled)) {
|
|
31909
|
+
const step = up ? recallOlder(recall.current, value) : recallNewer(recall.current);
|
|
31910
|
+
recall.current = step.state;
|
|
31911
|
+
if (step.entry !== void 0) {
|
|
31912
|
+
const safe = sanitizeDraftText(step.entry);
|
|
31913
|
+
setValue(safe);
|
|
31914
|
+
setCursor(safe.length);
|
|
31915
|
+
preferredColumnRef.current = null;
|
|
31916
|
+
setDismissedMenuValue(void 0);
|
|
31917
|
+
}
|
|
31918
|
+
return;
|
|
31919
|
+
}
|
|
31920
|
+
const model = editorModel(value, Math.max(1, columns - 6));
|
|
31921
|
+
const preferred = preferredColumnRef.current ?? caretSite(model, cursor).column;
|
|
31922
|
+
const next = moveCursorVertically(model, cursor, preferred, up ? -1 : 1);
|
|
31923
|
+
if (next !== cursor) {
|
|
31924
|
+
setCursor(next);
|
|
31925
|
+
preferredColumnRef.current = preferred;
|
|
30782
31926
|
}
|
|
30783
31927
|
return;
|
|
30784
31928
|
}
|
|
@@ -30786,51 +31930,88 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30786
31930
|
acceptMenuCandidate();
|
|
30787
31931
|
return;
|
|
30788
31932
|
}
|
|
30789
|
-
if (key.backspace || key.delete) {
|
|
30790
|
-
if (cursor > 0) {
|
|
30791
|
-
setValue(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
30792
|
-
setCursor(cursor - 1);
|
|
30793
|
-
setCompletionIndex(0);
|
|
30794
|
-
setDismissedMenuValue(void 0);
|
|
30795
|
-
}
|
|
30796
|
-
return;
|
|
30797
|
-
}
|
|
30798
31933
|
if (key.leftArrow) {
|
|
30799
|
-
|
|
31934
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordLeft(value, cursor) : moveCursorBy(value, cursor, -1));
|
|
30800
31935
|
return;
|
|
30801
31936
|
}
|
|
30802
31937
|
if (key.rightArrow) {
|
|
30803
|
-
|
|
31938
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordRight(value, cursor) : moveCursorBy(value, cursor, 1));
|
|
30804
31939
|
return;
|
|
30805
31940
|
}
|
|
30806
|
-
if (key.
|
|
30807
|
-
|
|
30808
|
-
setCursor(0);
|
|
30809
|
-
setDismissedMenuValue(void 0);
|
|
31941
|
+
if (key.meta && input === "b") {
|
|
31942
|
+
moveCursorTo(moveWordLeft(value, cursor));
|
|
30810
31943
|
return;
|
|
30811
31944
|
}
|
|
30812
|
-
if (key.
|
|
30813
|
-
|
|
30814
|
-
setDismissedMenuValue(void 0);
|
|
31945
|
+
if (key.meta && input === "f") {
|
|
31946
|
+
moveCursorTo(moveWordRight(value, cursor));
|
|
30815
31947
|
return;
|
|
30816
31948
|
}
|
|
30817
|
-
if (key.ctrl && input === "
|
|
30818
|
-
|
|
31949
|
+
if (key.ctrl && input === "b") {
|
|
31950
|
+
moveCursorTo(moveCursorBy(value, cursor, -1));
|
|
31951
|
+
return;
|
|
31952
|
+
}
|
|
31953
|
+
if (key.ctrl && input === "f") {
|
|
31954
|
+
moveCursorTo(moveCursorBy(value, cursor, 1));
|
|
31955
|
+
return;
|
|
31956
|
+
}
|
|
31957
|
+
if (key.ctrl && input === "w") {
|
|
31958
|
+
applyEdit(deleteWordBackward(value, cursor));
|
|
31959
|
+
return;
|
|
31960
|
+
}
|
|
31961
|
+
if (key.meta && input === "d") {
|
|
31962
|
+
applyEdit(deleteWordForward(value, cursor));
|
|
31963
|
+
return;
|
|
31964
|
+
}
|
|
31965
|
+
if (key.backspace || key.delete) {
|
|
31966
|
+
applyEdit(deleteBackward(value, cursor));
|
|
30819
31967
|
return;
|
|
30820
31968
|
}
|
|
30821
31969
|
if (key.ctrl && input === "a") {
|
|
30822
|
-
|
|
31970
|
+
moveCursorTo(lineBounds(value, cursor).start);
|
|
30823
31971
|
return;
|
|
30824
31972
|
}
|
|
30825
31973
|
if (key.ctrl && input === "e") {
|
|
30826
|
-
|
|
31974
|
+
moveCursorTo(lineBounds(value, cursor).end);
|
|
31975
|
+
return;
|
|
31976
|
+
}
|
|
31977
|
+
if (key.ctrl && input === "u") {
|
|
31978
|
+
applyEdit(killToLineStart(value, cursor));
|
|
31979
|
+
return;
|
|
31980
|
+
}
|
|
31981
|
+
if (key.ctrl && input === "k") {
|
|
31982
|
+
applyEdit(killToLineEnd(value, cursor));
|
|
31983
|
+
return;
|
|
31984
|
+
}
|
|
31985
|
+
if (key.ctrl && input === "y") {
|
|
31986
|
+
if (killRef.current !== "") applyEdit(insertText(value, cursor, killRef.current));
|
|
31987
|
+
return;
|
|
31988
|
+
}
|
|
31989
|
+
if (key.ctrl && input === "l") {
|
|
31990
|
+
refresh();
|
|
30827
31991
|
return;
|
|
30828
31992
|
}
|
|
30829
31993
|
if (input !== "" && !key.ctrl && !key.meta) {
|
|
30830
|
-
|
|
30831
|
-
|
|
30832
|
-
|
|
30833
|
-
|
|
31994
|
+
let text = input;
|
|
31995
|
+
if (text.includes("[200~")) {
|
|
31996
|
+
pasteBracketRef.current = true;
|
|
31997
|
+
pasteBracketCancelRef.current?.();
|
|
31998
|
+
const timer = setTimeout(() => {
|
|
31999
|
+
pasteBracketRef.current = false;
|
|
32000
|
+
pasteBracketCancelRef.current = void 0;
|
|
32001
|
+
}, PASTE_BRACKET_TIMEOUT_MS);
|
|
32002
|
+
pasteBracketCancelRef.current = () => {
|
|
32003
|
+
clearTimeout(timer);
|
|
32004
|
+
pasteBracketCancelRef.current = void 0;
|
|
32005
|
+
};
|
|
32006
|
+
text = text.replaceAll(PASTE_START_MARKER, "");
|
|
32007
|
+
}
|
|
32008
|
+
if (text.includes("[201~")) {
|
|
32009
|
+
pasteBracketRef.current = false;
|
|
32010
|
+
pasteBracketCancelRef.current?.();
|
|
32011
|
+
text = text.replaceAll(PASTE_END_MARKER, "");
|
|
32012
|
+
}
|
|
32013
|
+
if (text === "") return;
|
|
32014
|
+
applyEdit(insertText(value, cursor, text));
|
|
30834
32015
|
}
|
|
30835
32016
|
});
|
|
30836
32017
|
const [waveTick, setWaveTick] = (0, import_react.useState)(null);
|
|
@@ -30871,103 +32052,149 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30871
32052
|
const tierHues = waveTier === null ? null : deepseekWaveHues(waveTier);
|
|
30872
32053
|
const promptColor = tierHues === null ? inkColor(getPalette().brand) : inkColor(tierHues[0]);
|
|
30873
32054
|
const promptGlyph = waveTier === "flash" ? "›" : waveTier === "deepseek" ? "»" : "❯";
|
|
32055
|
+
const editorViewModel = editorModel(value, Math.max(1, columns - 6));
|
|
32056
|
+
const clampedCursor = clampCursor(value, cursor);
|
|
32057
|
+
const caret = caretSite(editorViewModel, clampedCursor);
|
|
32058
|
+
const editorWindowRows = Math.min(editorViewModel.rows.length, Math.max(1, maxRows));
|
|
32059
|
+
const maxEditorScroll = Math.max(0, editorViewModel.rows.length - editorWindowRows);
|
|
32060
|
+
const currentEditorScroll = Math.min(Math.max(0, editorScrollRef.current), maxEditorScroll);
|
|
32061
|
+
const editorWindowStart = caret.row < currentEditorScroll ? caret.row : caret.row >= currentEditorScroll + editorWindowRows ? caret.row - editorWindowRows + 1 : currentEditorScroll;
|
|
32062
|
+
editorScrollRef.current = editorWindowStart;
|
|
32063
|
+
const editorRowCount = frozen ? 1 : editorWindowRows;
|
|
32064
|
+
(0, import_react.useEffect)(() => {
|
|
32065
|
+
onEditorRows(editorRowCount);
|
|
32066
|
+
}, [editorRowCount, onEditorRows]);
|
|
32067
|
+
const bandWidth = Math.max(1, columns - 1);
|
|
32068
|
+
const bandBg = inkColor(getPalette().composerBand);
|
|
32069
|
+
const bandFill = (consumed) => " ".repeat(Math.max(0, bandWidth - consumed));
|
|
32070
|
+
const band = (content) => (0, import_react.createElement)(Box, {
|
|
32071
|
+
flexDirection: "column",
|
|
32072
|
+
width: bandWidth
|
|
32073
|
+
}, (0, import_react.createElement)(Text, { backgroundColor: bandBg }, " ".repeat(bandWidth)), content, (0, import_react.createElement)(Text, { backgroundColor: bandBg }, " ".repeat(bandWidth)));
|
|
30874
32074
|
if (frozen) {
|
|
30875
|
-
if (deleteConfirm !== void 0)
|
|
30876
|
-
|
|
30877
|
-
|
|
30878
|
-
|
|
30879
|
-
|
|
30880
|
-
|
|
30881
|
-
|
|
30882
|
-
|
|
30883
|
-
|
|
30884
|
-
|
|
30885
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30890
|
-
|
|
30891
|
-
|
|
30892
|
-
|
|
30893
|
-
}, (0, import_react.createElement)(Text, { wrap: "truncate-end" }, (0, import_react.createElement)(Text, {
|
|
32075
|
+
if (deleteConfirm !== void 0) {
|
|
32076
|
+
const warning = "y delete · any other key cancels";
|
|
32077
|
+
return band((0, import_react.createElement)(Text, {
|
|
32078
|
+
backgroundColor: bandBg,
|
|
32079
|
+
wrap: "truncate-end"
|
|
32080
|
+
}, (0, import_react.createElement)(Text, {
|
|
32081
|
+
color: inkColor(getPalette().warn),
|
|
32082
|
+
bold: true
|
|
32083
|
+
}, "❯ "), (0, import_react.createElement)(Text, {
|
|
32084
|
+
color: inkColor(getPalette().warn),
|
|
32085
|
+
bold: true
|
|
32086
|
+
}, warning), bandFill(2 + visibleColumns(warning))));
|
|
32087
|
+
}
|
|
32088
|
+
const frozenLine = value === "" ? "type a message" : verboseLine(value, Math.max(1, columns - 6));
|
|
32089
|
+
return band((0, import_react.createElement)(Text, {
|
|
32090
|
+
backgroundColor: bandBg,
|
|
32091
|
+
wrap: "truncate-end"
|
|
32092
|
+
}, (0, import_react.createElement)(Text, {
|
|
30894
32093
|
color: promptColor,
|
|
30895
32094
|
bold: tierActive ? true : void 0
|
|
30896
|
-
}, busy ? "… " : `${promptGlyph} `),
|
|
32095
|
+
}, busy ? "… " : `${promptGlyph} `), frozenLine, bandFill(2 + visibleColumns(frozenLine))));
|
|
30897
32096
|
}
|
|
30898
|
-
const frame = (row, borderRgb) => (0, import_react.createElement)(Box, {
|
|
30899
|
-
width: Math.max(1, columns - 1),
|
|
30900
|
-
borderStyle: "round",
|
|
30901
|
-
borderColor: inkColor(borderRgb ?? getPalette().dim),
|
|
30902
|
-
paddingX: 1
|
|
30903
|
-
}, row);
|
|
30904
32097
|
const menu = (0, import_react.createElement)(CompletionMenu, {
|
|
30905
32098
|
active: menuActive,
|
|
30906
32099
|
mention: mentionActive,
|
|
30907
32100
|
index: completionIndex,
|
|
30908
32101
|
rows: menuRows
|
|
30909
32102
|
});
|
|
30910
|
-
const
|
|
30911
|
-
|
|
30912
|
-
|
|
30913
|
-
|
|
30914
|
-
|
|
32103
|
+
const editorRows = [];
|
|
32104
|
+
for (let index = editorWindowStart; index < Math.min(editorViewModel.rows.length, editorWindowStart + editorWindowRows); index += 1) {
|
|
32105
|
+
const row = editorViewModel.rows[index];
|
|
32106
|
+
const caretAt = index === caret.row ? row.offsets.indexOf(clampedCursor) : -1;
|
|
32107
|
+
const before = caretAt > 0 ? row.text.slice(0, row.cuts[caretAt]) : "";
|
|
32108
|
+
const caretChar = caretAt >= 0 && caretAt < row.cuts.length - 1 ? row.text.slice(row.cuts[caretAt], row.cuts[caretAt + 1]) : " ";
|
|
32109
|
+
const after = caretAt < 0 ? row.text : caretAt < row.cuts.length - 1 ? row.text.slice(row.cuts[caretAt + 1]) : "";
|
|
32110
|
+
const placeholder = index === 0 && value === "" && !busy;
|
|
32111
|
+
const tail = placeholder ? COMPOSER_PLACEHOLDER : after;
|
|
32112
|
+
const consumed = 2 + visibleColumns(before) + visibleColumns(caretChar) + visibleColumns(tail);
|
|
32113
|
+
editorRows.push((0, import_react.createElement)(Text, {
|
|
32114
|
+
key: index,
|
|
32115
|
+
backgroundColor: bandBg,
|
|
32116
|
+
wrap: "truncate-end"
|
|
32117
|
+
}, index === 0 ? busy ? (0, import_react.createElement)(BusyChase) : (0, import_react.createElement)(Text, {
|
|
32118
|
+
color: promptColor,
|
|
32119
|
+
bold: tierActive ? true : void 0
|
|
32120
|
+
}, `${promptGlyph} `) : " ", before, (0, import_react.createElement)(CursorBlock, {
|
|
32121
|
+
key: "caret",
|
|
32122
|
+
char: caretChar
|
|
32123
|
+
}), placeholder ? (0, import_react.createElement)(Text, { dimColor: true }, COMPOSER_PLACEHOLDER) : after, bandFill(consumed)));
|
|
32124
|
+
}
|
|
32125
|
+
const staticEditor = (0, import_react.createElement)(Box, { flexDirection: "column" }, ...editorRows);
|
|
30915
32126
|
const waveRow = () => {
|
|
30916
|
-
const contentWidth = Math.max(1, columns - 5);
|
|
30917
|
-
const waveEditor = editorWindow(value, cursor, Math.max(1, contentWidth - 2));
|
|
30918
32127
|
const hues = deepseekWaveHues(waveTier);
|
|
30919
32128
|
const style = waveStyle;
|
|
30920
|
-
const
|
|
30921
|
-
const waveBg = (column) => {
|
|
30922
|
-
const rgb = deepseekWaveColumnBg(waveTick, column,
|
|
30923
|
-
return rgb === null ?
|
|
32129
|
+
const bandRgb = getPalette().composerBand;
|
|
32130
|
+
const waveBg = (row, column) => {
|
|
32131
|
+
const rgb = deepseekWaveColumnBg(waveTick, column, bandWidth, waveTier, style, hues, bandRgb, row, 3);
|
|
32132
|
+
return rgb === null ? bandBg : inkColor(rgb);
|
|
30924
32133
|
};
|
|
30925
|
-
const
|
|
30926
|
-
|
|
30927
|
-
|
|
30928
|
-
|
|
30929
|
-
|
|
30930
|
-
|
|
30931
|
-
|
|
30932
|
-
|
|
30933
|
-
|
|
30934
|
-
|
|
30935
|
-
|
|
30936
|
-
|
|
32134
|
+
const blankBandRow = (row) => {
|
|
32135
|
+
const blanks = [];
|
|
32136
|
+
while (blanks.length < bandWidth) blanks.push({
|
|
32137
|
+
char: " ",
|
|
32138
|
+
backgroundColor: waveBg(row, blanks.length)
|
|
32139
|
+
});
|
|
32140
|
+
return (0, import_react.createElement)(Text, { key: row }, ...waveRowSpans(blanks));
|
|
32141
|
+
};
|
|
32142
|
+
const waveEditor = editorWindow(value, cursor, Math.max(1, columns - 7));
|
|
32143
|
+
const cells = [
|
|
32144
|
+
{
|
|
32145
|
+
char: " ",
|
|
32146
|
+
backgroundColor: waveBg(1, 0)
|
|
32147
|
+
},
|
|
32148
|
+
{
|
|
32149
|
+
char: " ",
|
|
32150
|
+
backgroundColor: waveBg(1, 1)
|
|
32151
|
+
},
|
|
32152
|
+
{
|
|
32153
|
+
char: promptGlyph,
|
|
32154
|
+
color: promptColor,
|
|
32155
|
+
bold: true,
|
|
32156
|
+
backgroundColor: waveBg(1, 2)
|
|
32157
|
+
},
|
|
32158
|
+
{
|
|
32159
|
+
char: " ",
|
|
32160
|
+
color: promptColor,
|
|
32161
|
+
backgroundColor: waveBg(1, 3)
|
|
32162
|
+
}
|
|
32163
|
+
];
|
|
30937
32164
|
for (const char of waveEditor.before) cells.push({
|
|
30938
32165
|
char,
|
|
30939
|
-
backgroundColor: waveBg(cells.length)
|
|
32166
|
+
backgroundColor: waveBg(1, cells.length)
|
|
30940
32167
|
});
|
|
30941
32168
|
cells.push({
|
|
30942
32169
|
char: waveEditor.caret,
|
|
30943
32170
|
inverse: true,
|
|
30944
|
-
backgroundColor: waveBg(cells.length)
|
|
32171
|
+
backgroundColor: waveBg(1, cells.length)
|
|
30945
32172
|
});
|
|
30946
32173
|
if (value === "" && !busy) for (let at = 0; at < 40; at += 1) cells.push({
|
|
30947
32174
|
char: COMPOSER_PLACEHOLDER[at],
|
|
30948
32175
|
dim: true,
|
|
30949
|
-
backgroundColor: waveBg(cells.length)
|
|
32176
|
+
backgroundColor: waveBg(1, cells.length)
|
|
30950
32177
|
});
|
|
30951
32178
|
else for (const char of waveEditor.after) cells.push({
|
|
30952
32179
|
char,
|
|
30953
|
-
backgroundColor: waveBg(cells.length)
|
|
32180
|
+
backgroundColor: waveBg(1, cells.length)
|
|
30954
32181
|
});
|
|
30955
|
-
while (cells.length <
|
|
32182
|
+
while (cells.length < bandWidth) cells.push({
|
|
30956
32183
|
char: " ",
|
|
30957
|
-
backgroundColor: waveBg(cells.length)
|
|
32184
|
+
backgroundColor: waveBg(1, cells.length)
|
|
30958
32185
|
});
|
|
30959
32186
|
if (deepseekWaveWordVisible(waveTick, waveTier, style)) {
|
|
30960
|
-
const word = "deepseek";
|
|
30961
|
-
const start = Math.max(2, Math.floor((
|
|
32187
|
+
const word = waveTier === "unknown" ? "Into the Unknown" : "deepseek";
|
|
32188
|
+
const start = Math.max(2, Math.floor((bandWidth - word.length) / 2));
|
|
30962
32189
|
let clear = true;
|
|
30963
|
-
for (let at = 0; at <
|
|
32190
|
+
for (let at = 0; at < word.length; at += 1) {
|
|
30964
32191
|
const cell = cells[start + at];
|
|
30965
32192
|
if (cell === void 0 || cell.char !== " " && cell.dim !== true) {
|
|
30966
32193
|
clear = false;
|
|
30967
32194
|
break;
|
|
30968
32195
|
}
|
|
30969
32196
|
}
|
|
30970
|
-
if (clear) for (let at = 0; at <
|
|
32197
|
+
if (clear) for (let at = 0; at < word.length; at += 1) {
|
|
30971
32198
|
const cell = cells[start + at];
|
|
30972
32199
|
cell.char = word[at];
|
|
30973
32200
|
cell.color = inkColor(deepseekWaveWordHue(at, hues));
|
|
@@ -30975,7 +32202,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30975
32202
|
cell.dim = false;
|
|
30976
32203
|
}
|
|
30977
32204
|
}
|
|
30978
|
-
if (waveTier === "deepseek" && style === "wave") {
|
|
32205
|
+
if ((waveTier === "deepseek" || waveTier === "unknown") && style === "wave") {
|
|
30979
32206
|
const spark = deepseekWaveSpark(waveTick);
|
|
30980
32207
|
if (spark !== null) {
|
|
30981
32208
|
const last = cells[cells.length - 1];
|
|
@@ -30987,24 +32214,19 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30987
32214
|
}
|
|
30988
32215
|
}
|
|
30989
32216
|
}
|
|
30990
|
-
|
|
30991
|
-
|
|
32217
|
+
return (0, import_react.createElement)(Box, {
|
|
32218
|
+
flexDirection: "column",
|
|
32219
|
+
width: bandWidth
|
|
32220
|
+
}, blankBandRow(0), (0, import_react.createElement)(Text, { wrap: "truncate-end" }, ...waveRowSpans(cells)), blankBandRow(2));
|
|
30992
32221
|
};
|
|
30993
|
-
return (0, import_react.createElement)(Box, { flexDirection: "column" }, menu, waveTick !== null && waveTier !== null && !busy ? waveRow() :
|
|
32222
|
+
return (0, import_react.createElement)(Box, { flexDirection: "column" }, menu, waveTick !== null && waveTier !== null && !busy ? waveRow() : band(staticEditor));
|
|
30994
32223
|
}
|
|
30995
|
-
/** Build one settled row (row Box plus its roomy-prompt spacers). */
|
|
30996
|
-
function buildSettledRow(entry, index, showReasoning) {
|
|
30997
|
-
const row = (0, import_react.createElement)(EntryLine, {
|
|
30998
|
-
entry,
|
|
30999
|
-
showReasoning,
|
|
31000
|
-
verbose: false
|
|
31001
|
-
});
|
|
32224
|
+
/** Build one settled row (row Box plus its roomy-prompt spacers and row count). */
|
|
32225
|
+
function buildSettledRow(entry, index, showReasoning, columns) {
|
|
31002
32226
|
const roomyPrompt = entry.kind === "user" && !entry.notice;
|
|
32227
|
+
const lines = settledEntryLines(entry, Math.max(10, columns - 2), showReasoning);
|
|
31003
32228
|
return {
|
|
31004
|
-
box: (0, import_react.createElement)(Box, {
|
|
31005
|
-
key: index,
|
|
31006
|
-
paddingX: 1
|
|
31007
|
-
}, row),
|
|
32229
|
+
box: (0, import_react.createElement)(Box, { key: index }, (0, import_react.createElement)(StyledRows, { lines })),
|
|
31008
32230
|
before: roomyPrompt ? (0, import_react.createElement)(Box, {
|
|
31009
32231
|
key: `prompt-before-${index}`,
|
|
31010
32232
|
paddingX: 1
|
|
@@ -31013,10 +32235,17 @@ function buildSettledRow(entry, index, showReasoning) {
|
|
|
31013
32235
|
key: `prompt-after-${index}`,
|
|
31014
32236
|
paddingX: 1
|
|
31015
32237
|
}, (0, import_react.createElement)(Text, null, " ")) : void 0,
|
|
31016
|
-
|
|
31017
|
-
showReasoning
|
|
32238
|
+
rows: lines.length + (roomyPrompt ? 2 : 0)
|
|
31018
32239
|
};
|
|
31019
32240
|
}
|
|
32241
|
+
/** The dim hint row placed under the header once the window has dropped entries. */
|
|
32242
|
+
function settledTrimHint(droppedEntries, columns) {
|
|
32243
|
+
return (0, import_react.createElement)(Text, {
|
|
32244
|
+
key: "history-cap-hint",
|
|
32245
|
+
color: inkColor(getPalette().dim),
|
|
32246
|
+
wrap: "truncate-end"
|
|
32247
|
+
}, truncateColumns(`… +${droppedEntries} earlier messages hidden · ctrl+o browse · /export full transcript`, Math.max(10, columns - 2)));
|
|
32248
|
+
}
|
|
31020
32249
|
/**
|
|
31021
32250
|
* The settled `<Static>` row set as a PURE incremental state machine (App
|
|
31022
32251
|
* drives it from the memo; tests drive it directly and read `built`).
|
|
@@ -31030,77 +32259,82 @@ function buildSettledRow(entry, index, showReasoning) {
|
|
|
31030
32259
|
* rebuild of rows, Map, or MarkdownBody parses). `records` is mutated in place
|
|
31031
32260
|
* on the append/toggle paths to stay O(delta).
|
|
31032
32261
|
*
|
|
32262
|
+
* RENDERED-HISTORY CAP: the window holds at most `rowCap` physical rows of
|
|
32263
|
+
* settled transcript (header and hint reserved on top). The cap exists only
|
|
32264
|
+
* here — the event log, the store projection, /export, Ctrl+O, and /resume
|
|
32265
|
+
* keep the full history. Ink 5's <Static> is a consumption counter
|
|
32266
|
+
* (items.slice(index) keyed on length): deleting head items mid-stream while
|
|
32267
|
+
* appending tail items can permanently swallow new rows, so the append branch
|
|
32268
|
+
* NEVER drops the head — it only accounts rows and flags `needsTrim` once the
|
|
32269
|
+
* window overflows cap + margin. The flag fires one source-backed replay
|
|
32270
|
+
* (epoch bump = the existing clear + <Static> remount), whose rebuild branch
|
|
32271
|
+
* walks the settled entries BACKWARD from the newest, keeps whole entries
|
|
32272
|
+
* until the cap, and counts everything older as `droppedEntries` (those
|
|
32273
|
+
* entries never even reach settledEntryLines). Hysteresis bounds replays to
|
|
32274
|
+
* at most one per 25% growth; resize / Ctrl+L / idle Ctrl+R replays re-window
|
|
32275
|
+
* for free on the same path.
|
|
32276
|
+
*
|
|
31033
32277
|
* Full rebuilds run only on the rare, deliberate paths: no cache yet, a
|
|
31034
|
-
* source-backed replay (`epoch` bump: resize / Ctrl+L / Ctrl+R
|
|
31035
|
-
* `<Static>` and must re-flush the CURRENT rows
|
|
31036
|
-
*
|
|
31037
|
-
*
|
|
32278
|
+
* source-backed replay (`epoch` bump: resize / Ctrl+L / an idle Ctrl+R fold
|
|
32279
|
+
* toggle / a cap trim remounts `<Static>` and must re-flush the CURRENT rows
|
|
32280
|
+
* at the CURRENT fold state), a `resumed` change, or a shrink (`store.reset`).
|
|
32281
|
+
* While a turn is busy or streaming, Ctrl+R only flips the live region; rows
|
|
32282
|
+
* already emitted to native scrollback change exclusively through rebuilds.
|
|
31038
32283
|
*/
|
|
31039
|
-
function computeSettledRows(previous, entries, settled, showReasoning, resumed, epoch) {
|
|
32284
|
+
function computeSettledRows(previous, entries, settled, showReasoning, resumed, epoch, columns = 80, rowCap = SETTLED_ROW_CAP) {
|
|
31040
32285
|
if (previous === void 0 || previous.epoch !== epoch || previous.resumed !== resumed || settled < previous.entries.length) {
|
|
31041
32286
|
const records = /* @__PURE__ */ new Map();
|
|
31042
|
-
const
|
|
31043
|
-
|
|
31044
|
-
|
|
31045
|
-
|
|
31046
|
-
for (
|
|
32287
|
+
const window = [];
|
|
32288
|
+
let windowRows = 0;
|
|
32289
|
+
let droppedEntries = 0;
|
|
32290
|
+
let index = settled - 1;
|
|
32291
|
+
for (; index >= 0; index--) {
|
|
31047
32292
|
const entry = entries[index];
|
|
31048
|
-
|
|
32293
|
+
if (entry === void 0) break;
|
|
32294
|
+
const record = buildSettledRow(entry, index, showReasoning, columns);
|
|
32295
|
+
if (rowCap > 0 && windowRows + record.rows > rowCap - SETTLED_ROW_RESERVE) {
|
|
32296
|
+
droppedEntries = index + 1;
|
|
32297
|
+
break;
|
|
32298
|
+
}
|
|
31049
32299
|
records.set(entry, record);
|
|
31050
|
-
|
|
31051
|
-
|
|
31052
|
-
|
|
32300
|
+
windowRows += record.rows;
|
|
32301
|
+
if (record.after !== void 0) window.unshift(record.after);
|
|
32302
|
+
window.unshift(record.box);
|
|
32303
|
+
if (record.before !== void 0) window.unshift(record.before);
|
|
31053
32304
|
}
|
|
32305
|
+
const header = (0, import_react.createElement)(Header, {
|
|
32306
|
+
key: "header",
|
|
32307
|
+
resumed
|
|
32308
|
+
});
|
|
32309
|
+
const flat = droppedEntries > 0 ? [
|
|
32310
|
+
header,
|
|
32311
|
+
settledTrimHint(droppedEntries, columns),
|
|
32312
|
+
...window
|
|
32313
|
+
] : [header, ...window];
|
|
31054
32314
|
return {
|
|
31055
32315
|
cache: {
|
|
31056
|
-
entries: entries.slice(
|
|
32316
|
+
entries: entries.slice(droppedEntries, settled),
|
|
31057
32317
|
records,
|
|
31058
|
-
header
|
|
32318
|
+
header,
|
|
31059
32319
|
resumed,
|
|
31060
32320
|
showReasoning,
|
|
31061
32321
|
epoch,
|
|
31062
|
-
|
|
31063
|
-
|
|
31064
|
-
|
|
31065
|
-
|
|
31066
|
-
|
|
31067
|
-
if (previous.showReasoning !== showReasoning) {
|
|
31068
|
-
const records = previous.records;
|
|
31069
|
-
const flat = [previous.header];
|
|
31070
|
-
let built = 0;
|
|
31071
|
-
for (let index = 0; index < previous.entries.length; index++) {
|
|
31072
|
-
const entry = previous.entries[index];
|
|
31073
|
-
const record = records.get(entry);
|
|
31074
|
-
const current = record.reasonSensitive ? {
|
|
31075
|
-
...record,
|
|
31076
|
-
box: (0, import_react.createElement)(Box, {
|
|
31077
|
-
key: index,
|
|
31078
|
-
paddingX: 1
|
|
31079
|
-
}, (0, import_react.createElement)(EntryLine, {
|
|
31080
|
-
entry,
|
|
31081
|
-
showReasoning,
|
|
31082
|
-
verbose: false
|
|
31083
|
-
})),
|
|
31084
|
-
showReasoning
|
|
31085
|
-
} : record;
|
|
31086
|
-
if (current !== record) {
|
|
31087
|
-
records.set(entry, current);
|
|
31088
|
-
built += 1;
|
|
31089
|
-
}
|
|
31090
|
-
if (current.before !== void 0) flat.push(current.before);
|
|
31091
|
-
flat.push(current.box);
|
|
31092
|
-
if (current.after !== void 0) flat.push(current.after);
|
|
31093
|
-
}
|
|
31094
|
-
return {
|
|
31095
|
-
cache: {
|
|
31096
|
-
...previous,
|
|
31097
|
-
records,
|
|
31098
|
-
showReasoning,
|
|
31099
|
-
flat
|
|
32322
|
+
columns,
|
|
32323
|
+
flat,
|
|
32324
|
+
droppedEntries,
|
|
32325
|
+
totalRows: windowRows,
|
|
32326
|
+
needsTrim: false
|
|
31100
32327
|
},
|
|
31101
|
-
built
|
|
32328
|
+
built: records.size
|
|
31102
32329
|
};
|
|
31103
32330
|
}
|
|
32331
|
+
if (previous.showReasoning !== showReasoning) return {
|
|
32332
|
+
cache: {
|
|
32333
|
+
...previous,
|
|
32334
|
+
showReasoning
|
|
32335
|
+
},
|
|
32336
|
+
built: 0
|
|
32337
|
+
};
|
|
31104
32338
|
if (settled === previous.entries.length) return {
|
|
31105
32339
|
cache: previous,
|
|
31106
32340
|
built: 0
|
|
@@ -31108,15 +32342,19 @@ function computeSettledRows(previous, entries, settled, showReasoning, resumed,
|
|
|
31108
32342
|
const records = previous.records;
|
|
31109
32343
|
const suffix = [];
|
|
31110
32344
|
const added = [];
|
|
31111
|
-
|
|
32345
|
+
let deltaRows = 0;
|
|
32346
|
+
for (let index = previous.entries.length + previous.droppedEntries; index < settled; index++) {
|
|
31112
32347
|
const entry = entries[index];
|
|
31113
|
-
const record = buildSettledRow(entry, index, showReasoning);
|
|
32348
|
+
const record = buildSettledRow(entry, index, showReasoning, previous.columns);
|
|
31114
32349
|
records.set(entry, record);
|
|
31115
32350
|
suffix.push(entry);
|
|
32351
|
+
deltaRows += record.rows;
|
|
31116
32352
|
if (record.before !== void 0) added.push(record.before);
|
|
31117
32353
|
added.push(record.box);
|
|
31118
32354
|
if (record.after !== void 0) added.push(record.after);
|
|
31119
32355
|
}
|
|
32356
|
+
const totalRows = previous.totalRows + deltaRows;
|
|
32357
|
+
const needsTrim = rowCap > 0 && totalRows > rowCap + Math.floor(rowCap / 4);
|
|
31120
32358
|
return {
|
|
31121
32359
|
cache: {
|
|
31122
32360
|
entries: previous.entries.concat(suffix),
|
|
@@ -31125,9 +32363,13 @@ function computeSettledRows(previous, entries, settled, showReasoning, resumed,
|
|
|
31125
32363
|
resumed: previous.resumed,
|
|
31126
32364
|
showReasoning,
|
|
31127
32365
|
epoch: previous.epoch,
|
|
31128
|
-
|
|
32366
|
+
columns: previous.columns,
|
|
32367
|
+
flat: previous.flat.concat(added),
|
|
32368
|
+
droppedEntries: previous.droppedEntries,
|
|
32369
|
+
totalRows,
|
|
32370
|
+
needsTrim
|
|
31129
32371
|
},
|
|
31130
|
-
built:
|
|
32372
|
+
built: suffix.length
|
|
31131
32373
|
};
|
|
31132
32374
|
}
|
|
31133
32375
|
/** The whole terminal app; state arrives via the store, output via Ink. */
|
|
@@ -31146,16 +32388,18 @@ function App(props) {
|
|
|
31146
32388
|
const [effortFor, setEffortFor] = (0, import_react.useState)(void 0);
|
|
31147
32389
|
/** Effective reasoning effort, shown in the /model picker and switch notice. */
|
|
31148
32390
|
const [effortLabel, setEffortLabel] = (0, import_react.useState)(props.effort);
|
|
31149
|
-
/** DeepSeek easter egg: switching INTO an official DeepSeek route
|
|
31150
|
-
*
|
|
31151
|
-
*
|
|
31152
|
-
*
|
|
31153
|
-
*
|
|
31154
|
-
*
|
|
31155
|
-
*
|
|
31156
|
-
*
|
|
31157
|
-
*
|
|
31158
|
-
*
|
|
32391
|
+
/** DeepSeek easter egg: switching INTO an official DeepSeek route — or
|
|
32392
|
+
* onto a NON-DeepSeek model running a reasoning effort strictly above
|
|
32393
|
+
* high — plays one of Codex's three ignition styles (Wave / Aurora /
|
|
32394
|
+
* Pulse, picked at random without repeating) across the composer's
|
|
32395
|
+
* padded band (33ms tick, per-style durations), then the band returns
|
|
32396
|
+
* to static while the prompt marker keeps the tier accent. The trigger
|
|
32397
|
+
* follows the applied model label (what the status bar actually shows),
|
|
32398
|
+
* never the initial paint, and the tier is derived from the label and
|
|
32399
|
+
* cached at the switch. The 33ms tick itself lives inside Input, so the
|
|
32400
|
+
* sweep re-renders only the composer band, not the whole tree, at 30fps;
|
|
32401
|
+
* App owns the rarely-changing tier/style and Input starts the sweep
|
|
32402
|
+
* whenever that pair changes. */
|
|
31159
32403
|
const [waveTier, setWaveTier] = (0, import_react.useState)(null);
|
|
31160
32404
|
const [waveStyle, setWaveStyle] = (0, import_react.useState)(null);
|
|
31161
32405
|
const previousModel = (0, import_react.useRef)(void 0);
|
|
@@ -31167,13 +32411,15 @@ function App(props) {
|
|
|
31167
32411
|
const effortChanged = previousEffort.current !== effortLabel;
|
|
31168
32412
|
previousEffort.current = effortLabel;
|
|
31169
32413
|
const modelChanged = previous !== void 0 && previous !== modelLabel;
|
|
31170
|
-
|
|
32414
|
+
const official = isOfficialDeepSeekLabel(modelLabel);
|
|
32415
|
+
const unknownTrigger = !official && effortAboveHigh(effortLabel);
|
|
32416
|
+
if (!official && !unknownTrigger) {
|
|
31171
32417
|
setWaveTier(null);
|
|
31172
32418
|
setWaveStyle(null);
|
|
31173
32419
|
return;
|
|
31174
32420
|
}
|
|
31175
32421
|
if (modelChanged || effortChanged) {
|
|
31176
|
-
setWaveTier(deepseekWaveTier(modelLabel));
|
|
32422
|
+
setWaveTier(official ? deepseekWaveTier(modelLabel) : "unknown");
|
|
31177
32423
|
const nextStyle = deepseekWaveStyleRandom(previousStyle.current);
|
|
31178
32424
|
previousStyle.current = nextStyle;
|
|
31179
32425
|
setWaveStyle(nextStyle);
|
|
@@ -31242,18 +32488,21 @@ function App(props) {
|
|
|
31242
32488
|
const busy = view.busy;
|
|
31243
32489
|
const [showReasoning, setShowReasoning] = (0, import_react.useState)(false);
|
|
31244
32490
|
const [verboseOpen, setVerboseOpen] = (0, import_react.useState)(false);
|
|
32491
|
+
const [diffView, setDiffView] = (0, import_react.useState)(void 0);
|
|
31245
32492
|
const [helpOpen, setHelpOpen] = (0, import_react.useState)(false);
|
|
31246
32493
|
const [modeOpen, setModeOpen] = (0, import_react.useState)(false);
|
|
31247
32494
|
const [permissionOpen, setPermissionOpen] = (0, import_react.useState)(false);
|
|
31248
32495
|
const [resumeOpen, setResumeOpen] = (0, import_react.useState)(false);
|
|
31249
32496
|
const [pluginOpen, setPluginOpen] = (0, import_react.useState)(false);
|
|
31250
32497
|
const [pluginQuery, setPluginQuery] = (0, import_react.useState)("");
|
|
32498
|
+
const [jobsOpen, setJobsOpen] = (0, import_react.useState)(false);
|
|
31251
32499
|
const [statuslineOpen, setStatuslineOpen] = (0, import_react.useState)(false);
|
|
31252
32500
|
const [statuslineItems, setStatuslineItems] = (0, import_react.useState)(() => parseStatuslineItems(props.statusline));
|
|
31253
32501
|
const [themeOpen, setThemeOpen] = (0, import_react.useState)(false);
|
|
31254
32502
|
const [historyOpen, setHistoryOpen] = (0, import_react.useState)(false);
|
|
31255
32503
|
const [agentsOpen, setAgentsOpen] = (0, import_react.useState)(false);
|
|
31256
32504
|
const [subagentOpen, setSubagentOpen] = (0, import_react.useState)(false);
|
|
32505
|
+
const [todosOpen, setTodosOpen] = (0, import_react.useState)(false);
|
|
31257
32506
|
/** /delete state: delete-mode hint plus an optional pre-armed row id. */
|
|
31258
32507
|
const [resumeDelete, setResumeDelete] = (0, import_react.useState)({ mode: false });
|
|
31259
32508
|
/** The row id awaiting y/n in the COMPOSER (codex delete confirm): the
|
|
@@ -31318,7 +32567,7 @@ function App(props) {
|
|
|
31318
32567
|
const agentRows = (0, import_react.useSyncExternalStore)(props.subagents.subscribe, props.subagents.getSnapshot);
|
|
31319
32568
|
const approvalPending = approvalSnapshot.pending !== void 0;
|
|
31320
32569
|
const questionPending = questionSnapshot.pending !== void 0;
|
|
31321
|
-
const inputActive = deleteConfirmId !== void 0 ? !approvalPending && !questionPending : !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !verboseOpen && !approvalPending && !questionPending;
|
|
32570
|
+
const inputActive = deleteConfirmId !== void 0 ? !approvalPending && !questionPending : !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === void 0 && !approvalPending && !questionPending;
|
|
31322
32571
|
(0, import_react.useEffect)(() => {
|
|
31323
32572
|
if (!approvalPending && !questionPending) return;
|
|
31324
32573
|
setModelOpen(false);
|
|
@@ -31335,12 +32584,20 @@ function App(props) {
|
|
|
31335
32584
|
setHistoryOpen(false);
|
|
31336
32585
|
setAgentsOpen(false);
|
|
31337
32586
|
setSubagentOpen(false);
|
|
32587
|
+
setTodosOpen(false);
|
|
31338
32588
|
setDeleteConfirmId(void 0);
|
|
31339
32589
|
setVerboseOpen(false);
|
|
32590
|
+
setDiffView(void 0);
|
|
31340
32591
|
}, [approvalPending, questionPending]);
|
|
32592
|
+
const appStdout = useStdout().stdout;
|
|
32593
|
+
const [terminalSize, setTerminalSize] = (0, import_react.useState)(() => ({
|
|
32594
|
+
columns: appStdout?.columns ?? 80,
|
|
32595
|
+
rows: appStdout?.rows ?? 30
|
|
32596
|
+
}));
|
|
32597
|
+
const terminalSizeRef = (0, import_react.useRef)(terminalSize);
|
|
31341
32598
|
const settledRowsCache = (0, import_react.useRef)(void 0);
|
|
31342
32599
|
const settledRows = (0, import_react.useMemo)(() => {
|
|
31343
|
-
const result = computeSettledRows(settledRowsCache.current, view.entries, settled, showReasoning, props.resumed, refreshEpoch);
|
|
32600
|
+
const result = computeSettledRows(settledRowsCache.current, view.entries, settled, showReasoning, props.resumed, refreshEpoch, terminalSize.columns);
|
|
31344
32601
|
settledRowsCache.current = result.cache;
|
|
31345
32602
|
return result.cache.flat;
|
|
31346
32603
|
}, [
|
|
@@ -31348,14 +32605,10 @@ function App(props) {
|
|
|
31348
32605
|
settled,
|
|
31349
32606
|
showReasoning,
|
|
31350
32607
|
props.resumed,
|
|
31351
|
-
refreshEpoch
|
|
32608
|
+
refreshEpoch,
|
|
32609
|
+
terminalSize.columns
|
|
31352
32610
|
]);
|
|
31353
|
-
const
|
|
31354
|
-
const [terminalSize, setTerminalSize] = (0, import_react.useState)(() => ({
|
|
31355
|
-
columns: appStdout?.columns ?? 80,
|
|
31356
|
-
rows: appStdout?.rows ?? 30
|
|
31357
|
-
}));
|
|
31358
|
-
const terminalSizeRef = (0, import_react.useRef)(terminalSize);
|
|
32611
|
+
const synchronizedReplayPending = (0, import_react.useRef)(false);
|
|
31359
32612
|
(0, import_react.useEffect)(() => {
|
|
31360
32613
|
if (appStdout === void 0) return;
|
|
31361
32614
|
let replayTimer;
|
|
@@ -31369,7 +32622,8 @@ function App(props) {
|
|
|
31369
32622
|
setTerminalSize(next);
|
|
31370
32623
|
if (replayTimer !== void 0) clearTimeout(replayTimer);
|
|
31371
32624
|
replayTimer = setTimeout(() => {
|
|
31372
|
-
|
|
32625
|
+
synchronizedReplayPending.current = true;
|
|
32626
|
+
appStdout.write("\x1B[?2026h\x1B[r\x1B[0m\x1B[H\x1B[2J\x1B[3J\x1B[H");
|
|
31373
32627
|
setRefreshEpoch((epoch) => epoch + 1);
|
|
31374
32628
|
}, RESIZE_REFLOW_DELAY_MS);
|
|
31375
32629
|
};
|
|
@@ -31382,28 +32636,51 @@ function App(props) {
|
|
|
31382
32636
|
const terminalRows = terminalSize.rows;
|
|
31383
32637
|
const terminalColumns = terminalSize.columns;
|
|
31384
32638
|
const composerGutterRows = layoutGutterRows(terminalRows);
|
|
31385
|
-
const
|
|
32639
|
+
const [composerRows, setComposerRows] = (0, import_react.useState)(1);
|
|
32640
|
+
const handleEditorRows = (0, import_react.useCallback)((rows) => {
|
|
32641
|
+
setComposerRows((current) => current === rows ? current : rows);
|
|
32642
|
+
}, []);
|
|
32643
|
+
const composerEditorCap = composerMaxRows(terminalRows);
|
|
32644
|
+
const dynamicRows = Math.max(1, terminalRows - 13 - composerGutterRows - (composerRows - 1));
|
|
31386
32645
|
const streamingActive = view.streaming !== "" || view.streamingReasoning !== "";
|
|
31387
32646
|
const deepDivingVisible = busy && !streamingActive;
|
|
31388
|
-
const allLiveLines = (0, import_react.useMemo)(() => view.entries.slice(settled).flatMap((entry) => transcriptEntryLines(entry, Math.max(
|
|
32647
|
+
const allLiveLines = (0, import_react.useMemo)(() => view.entries.slice(settled).flatMap((entry) => transcriptEntryLines(entry, Math.max(10, terminalColumns - 2), showReasoning)), [
|
|
31389
32648
|
view.entries,
|
|
31390
32649
|
settled,
|
|
31391
|
-
terminalColumns
|
|
32650
|
+
terminalColumns,
|
|
32651
|
+
showReasoning
|
|
31392
32652
|
]);
|
|
31393
|
-
const liveBudget = streamingActive ? Math.max(1, Math.floor(dynamicRows / 3)) : Math.max(0, dynamicRows - (deepDivingVisible ? 1 : 0));
|
|
32653
|
+
const liveBudget = busy || streamingActive ? Math.max(1, Math.floor(dynamicRows / 3)) : Math.max(0, dynamicRows - (deepDivingVisible ? 1 : 0));
|
|
31394
32654
|
const visibleLiveLines = liveBudget === 0 ? [] : allLiveLines.slice(-liveBudget);
|
|
31395
32655
|
const streamRows = Math.max(1, dynamicRows - visibleLiveLines.length);
|
|
31396
32656
|
const reasoningRows = view.streamingReasoning === "" ? 0 : view.streaming === "" ? streamRows : streamRows <= 1 ? 0 : showReasoning ? Math.max(1, Math.floor(streamRows / 3)) : 1;
|
|
31397
32657
|
const answerRows = view.streaming === "" ? 0 : Math.max(1, streamRows - reasoningRows);
|
|
31398
|
-
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !verboseOpen && !approvalPending && !questionPending;
|
|
31399
|
-
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || statuslineOpen || themeOpen || historyOpen || agentsOpen || subagentOpen || verboseOpen && !approvalPending && !questionPending || approvalPending || questionPending;
|
|
32658
|
+
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === void 0 && !approvalPending && !questionPending;
|
|
32659
|
+
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || jobsOpen || statuslineOpen || themeOpen || historyOpen || agentsOpen || subagentOpen || todosOpen || verboseOpen && !approvalPending && !questionPending || diffView !== void 0 || approvalPending || questionPending;
|
|
31400
32660
|
const closeInspector = (0, import_react.useCallback)(() => {
|
|
31401
32661
|
setVerboseOpen(false);
|
|
31402
32662
|
}, []);
|
|
31403
32663
|
const refreshScreen = () => {
|
|
31404
|
-
if (appStdout !== void 0)
|
|
32664
|
+
if (appStdout !== void 0) {
|
|
32665
|
+
synchronizedReplayPending.current = true;
|
|
32666
|
+
appStdout.write("\x1B[?2026h\x1B[r\x1B[0m\x1B[H\x1B[2J\x1B[3J\x1B[H");
|
|
32667
|
+
}
|
|
31405
32668
|
setRefreshEpoch((epoch) => epoch + 1);
|
|
31406
32669
|
};
|
|
32670
|
+
(0, import_react.useEffect)(() => {
|
|
32671
|
+
if (!synchronizedReplayPending.current || appStdout === void 0) return;
|
|
32672
|
+
synchronizedReplayPending.current = false;
|
|
32673
|
+
appStdout.write(SYNCHRONIZED_UPDATE_END);
|
|
32674
|
+
}, [appStdout, refreshEpoch]);
|
|
32675
|
+
const settledNeedsTrim = settledRowsCache.current?.needsTrim === true;
|
|
32676
|
+
(0, import_react.useEffect)(() => {
|
|
32677
|
+
if (!settledNeedsTrim || busy || streamingActive) return;
|
|
32678
|
+
refreshScreen();
|
|
32679
|
+
}, [
|
|
32680
|
+
settledNeedsTrim,
|
|
32681
|
+
busy,
|
|
32682
|
+
streamingActive
|
|
32683
|
+
]);
|
|
31407
32684
|
/** Apply one /model pick: record the selection, close the panel, report via notice. */
|
|
31408
32685
|
const applyModel = (row, effortId) => {
|
|
31409
32686
|
try {
|
|
@@ -31430,7 +32707,20 @@ function App(props) {
|
|
|
31430
32707
|
};
|
|
31431
32708
|
let modelSurface;
|
|
31432
32709
|
if (modelOpen && !approvalPending && !questionPending) {
|
|
31433
|
-
if (providerAction?.kind === "
|
|
32710
|
+
if (providerAction?.kind === "configure" && props.saveModelProviderConfiguration !== void 0) modelSurface = (0, import_react.createElement)(ProviderConfigurationPanel, {
|
|
32711
|
+
target: providerAction.target,
|
|
32712
|
+
catalog: directory?.rows ?? [],
|
|
32713
|
+
save: props.saveModelProviderConfiguration,
|
|
32714
|
+
done: () => {
|
|
32715
|
+
const target = providerAction.target;
|
|
32716
|
+
setProviderAction(void 0);
|
|
32717
|
+
setProviderOpen(true);
|
|
32718
|
+
reloadModelSurfaces();
|
|
32719
|
+
notify(`provider configuration saved: ${target.displayName}`);
|
|
32720
|
+
},
|
|
32721
|
+
back: () => setProviderAction(void 0)
|
|
32722
|
+
});
|
|
32723
|
+
else if (providerAction?.kind === "credential" && props.saveModelProviderCredential !== void 0) modelSurface = (0, import_react.createElement)(ProviderCredentialPanel, {
|
|
31434
32724
|
target: providerAction.target,
|
|
31435
32725
|
save: props.saveModelProviderCredential,
|
|
31436
32726
|
done: () => {
|
|
@@ -31481,6 +32771,16 @@ function App(props) {
|
|
|
31481
32771
|
target
|
|
31482
32772
|
});
|
|
31483
32773
|
},
|
|
32774
|
+
onConfigure: (target) => {
|
|
32775
|
+
if (props.saveModelProviderConfiguration === void 0) {
|
|
32776
|
+
notify("provider configuration is unavailable in this profile", "warning");
|
|
32777
|
+
return;
|
|
32778
|
+
}
|
|
32779
|
+
setProviderAction({
|
|
32780
|
+
kind: "configure",
|
|
32781
|
+
target
|
|
32782
|
+
});
|
|
32783
|
+
},
|
|
31484
32784
|
onUnset: (target) => {
|
|
31485
32785
|
if (props.unsetModelProviderCredential === void 0) {
|
|
31486
32786
|
notify("API key removal is unavailable in this profile", "warning");
|
|
@@ -31531,11 +32831,8 @@ function App(props) {
|
|
|
31531
32831
|
return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(MemoStaticTranscript, {
|
|
31532
32832
|
key: refreshEpoch,
|
|
31533
32833
|
items: settledRows
|
|
31534
|
-
}), transcriptVisible ? (0, import_react.createElement)(Box, {
|
|
31535
|
-
|
|
31536
|
-
paddingX: 2
|
|
31537
|
-
}, visibleLiveLines.length === 0 ? void 0 : (0, import_react.createElement)(StyledRows, { lines: visibleLiveLines }), view.streamingReasoning !== "" && reasoningRows > 0 ? (0, import_react.createElement)(StreamTail, {
|
|
31538
|
-
text: showReasoning ? view.streamingReasoning : "Thinking…",
|
|
32834
|
+
}), transcriptVisible ? (0, import_react.createElement)(Box, { flexDirection: "column" }, visibleLiveLines.length === 0 ? void 0 : (0, import_react.createElement)(StyledRows, { lines: visibleLiveLines }), view.streamingReasoning !== "" && reasoningRows > 0 ? (0, import_react.createElement)(StreamTail, {
|
|
32835
|
+
text: showReasoning ? view.streamingReasoning : "Thinking… (Ctrl+R to expand)",
|
|
31539
32836
|
prefix: "✻ ",
|
|
31540
32837
|
continuationPrefix: " ",
|
|
31541
32838
|
dim: true,
|
|
@@ -31545,7 +32842,12 @@ function App(props) {
|
|
|
31545
32842
|
dim: false,
|
|
31546
32843
|
maxRows: answerRows,
|
|
31547
32844
|
prefix: " "
|
|
31548
|
-
}, busy ? (0, import_react.createElement)(Caret) : void 0) : void 0, deepDivingVisible ? (0, import_react.createElement)(DeepDivingLine, { since: view.busySince }) : void 0) : void 0, transcriptVisible ? (0, import_react.createElement)(TodoPanel, { todos: view.todos }) : void 0, transcriptVisible ? (0, import_react.createElement)(AgentsLine, { rows: agentRows }) : void 0, (0, import_react.createElement)(
|
|
32845
|
+
}, busy ? (0, import_react.createElement)(Caret) : void 0) : void 0, deepDivingVisible ? (0, import_react.createElement)(DeepDivingLine, { since: view.busySince }) : void 0) : void 0, transcriptVisible ? (0, import_react.createElement)(TodoPanel, { todos: view.todos }) : void 0, transcriptVisible ? (0, import_react.createElement)(AgentsLine, { rows: agentRows }) : void 0, todosOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(MemoTodoListPanel, {
|
|
32846
|
+
todos: view.todos,
|
|
32847
|
+
onClose: () => {
|
|
32848
|
+
setTodosOpen(false);
|
|
32849
|
+
}
|
|
32850
|
+
}) : void 0, (0, import_react.createElement)(QuestionBar, {
|
|
31549
32851
|
store: props.questions,
|
|
31550
32852
|
snapshot: questionSnapshot,
|
|
31551
32853
|
locked: false
|
|
@@ -31561,6 +32863,9 @@ function App(props) {
|
|
|
31561
32863
|
onClose: () => {
|
|
31562
32864
|
setHelpOpen(false);
|
|
31563
32865
|
}
|
|
32866
|
+
}) : void 0, diffView !== void 0 && !approvalPending && !questionPending ? (0, import_react.createElement)(DiffPanel, {
|
|
32867
|
+
view: diffView,
|
|
32868
|
+
onClose: () => setDiffView(void 0)
|
|
31564
32869
|
}) : void 0, verboseOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(MemoVerbosePanel, {
|
|
31565
32870
|
entries: view.entries,
|
|
31566
32871
|
onClose: closeInspector
|
|
@@ -31604,6 +32909,9 @@ function App(props) {
|
|
|
31604
32909
|
load: props.loadPlugins,
|
|
31605
32910
|
initialQuery: pluginQuery,
|
|
31606
32911
|
close: () => setPluginOpen(false)
|
|
32912
|
+
}) : void 0, jobsOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(JobsPanel, {
|
|
32913
|
+
load: props.loadJobs,
|
|
32914
|
+
close: () => setJobsOpen(false)
|
|
31607
32915
|
}) : void 0, statuslineOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(StatuslinePanel, {
|
|
31608
32916
|
enabled: statuslineItems,
|
|
31609
32917
|
change: (items) => {
|
|
@@ -31717,11 +33025,13 @@ function App(props) {
|
|
|
31717
33025
|
setPluginQuery(query);
|
|
31718
33026
|
setPluginOpen(true);
|
|
31719
33027
|
},
|
|
33028
|
+
openJobs: () => setJobsOpen(true),
|
|
31720
33029
|
openStatusline: () => setStatuslineOpen(true),
|
|
31721
33030
|
openTheme: () => setThemeOpen(true),
|
|
31722
33031
|
openHistory: () => setHistoryOpen(true),
|
|
31723
33032
|
openAgents: () => setAgentsOpen(true),
|
|
31724
33033
|
openSubagent: () => setSubagentOpen(true),
|
|
33034
|
+
openTodos: () => setTodosOpen(true),
|
|
31725
33035
|
openDelete: (id) => {
|
|
31726
33036
|
const armed = id === void 0 || id === "" ? void 0 : id;
|
|
31727
33037
|
setResumeDelete({
|
|
@@ -31731,10 +33041,17 @@ function App(props) {
|
|
|
31731
33041
|
setDeleteConfirmId(armed);
|
|
31732
33042
|
setResumeOpen(true);
|
|
31733
33043
|
},
|
|
33044
|
+
openDiff: (argument) => {
|
|
33045
|
+
props.loadGitDiff(argument).then(setDiffView, (error) => {
|
|
33046
|
+
notify(`diff failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
33047
|
+
});
|
|
33048
|
+
},
|
|
33049
|
+
reviewChanges: props.reviewChanges,
|
|
31734
33050
|
deleteConfirm: deleteConfirmId,
|
|
31735
33051
|
confirmDelete,
|
|
31736
33052
|
cancelDelete,
|
|
31737
33053
|
createSession: props.createSession,
|
|
33054
|
+
forkSession: props.forkSession,
|
|
31738
33055
|
cancelSessionSwitch: props.cancelSessionSwitch,
|
|
31739
33056
|
notify,
|
|
31740
33057
|
hasNotice: notice !== void 0,
|
|
@@ -31750,12 +33067,13 @@ function App(props) {
|
|
|
31750
33067
|
refresh: refreshScreen,
|
|
31751
33068
|
toggleReasoning: () => {
|
|
31752
33069
|
setShowReasoning((current) => !current);
|
|
31753
|
-
refreshScreen();
|
|
33070
|
+
if (!busy && !streamingActive) refreshScreen();
|
|
31754
33071
|
},
|
|
31755
33072
|
loadMentions: props.loadMentions,
|
|
31756
33073
|
cyclePermission: props.cyclePermission,
|
|
31757
33074
|
exportTranscript: props.exportTranscript,
|
|
31758
33075
|
renameTitle: props.renameTitle,
|
|
33076
|
+
copyLastResponse: props.copyLastResponse,
|
|
31759
33077
|
recallSpace,
|
|
31760
33078
|
recordLocal,
|
|
31761
33079
|
recordHistory: props.recordHistory,
|
|
@@ -31764,7 +33082,9 @@ function App(props) {
|
|
|
31764
33082
|
historyFill,
|
|
31765
33083
|
historyConsumed,
|
|
31766
33084
|
waveTier,
|
|
31767
|
-
waveStyle
|
|
33085
|
+
waveStyle,
|
|
33086
|
+
maxRows: composerEditorCap,
|
|
33087
|
+
onEditorRows: handleEditorRows
|
|
31768
33088
|
}), (0, import_react.createElement)(StatusLine, {
|
|
31769
33089
|
facts: {
|
|
31770
33090
|
model: modelLabel,
|
|
@@ -31951,13 +33271,15 @@ function isSlashLine(line) {
|
|
|
31951
33271
|
/** Substitutable runner effects; production values write to the real terminal. */
|
|
31952
33272
|
const internals = {
|
|
31953
33273
|
mount: (element) => {
|
|
31954
|
-
|
|
33274
|
+
process.stdout.write("\x1B[>4;0m\x1B[>5u\x1B[?2004h");
|
|
33275
|
+
const instance = render(element, { exitOnCtrlC: false });
|
|
31955
33276
|
return {
|
|
31956
33277
|
rerender(element) {
|
|
31957
33278
|
instance.rerender(element);
|
|
31958
33279
|
},
|
|
31959
33280
|
unmount() {
|
|
31960
33281
|
instance.unmount();
|
|
33282
|
+
process.stdout.write("\x1B[<u\x1B[>4;0m\x1B[?2004l");
|
|
31961
33283
|
}
|
|
31962
33284
|
};
|
|
31963
33285
|
},
|
|
@@ -32148,6 +33470,26 @@ function profileRefOf(profile) {
|
|
|
32148
33470
|
const ref = profile.apiKeyEnv;
|
|
32149
33471
|
return typeof ref === "string" && ref.length > 0 ? ref : void 0;
|
|
32150
33472
|
}
|
|
33473
|
+
/** Extract only fields the terminal can round-trip without touching provider-specific extras. */
|
|
33474
|
+
function configurationOf(profile) {
|
|
33475
|
+
if (typeof profile !== "object" || profile === null) return { models: [] };
|
|
33476
|
+
const record = profile;
|
|
33477
|
+
const models = (Array.isArray(record.models) ? record.models : []).flatMap((value) => {
|
|
33478
|
+
if (typeof value !== "object" || value === null) return [];
|
|
33479
|
+
const entry = value;
|
|
33480
|
+
if (typeof entry.id !== "string" || entry.id.trim() === "") return [];
|
|
33481
|
+
return [{
|
|
33482
|
+
id: entry.id,
|
|
33483
|
+
...typeof entry.name === "string" && entry.name.trim() !== "" ? { name: entry.name } : {},
|
|
33484
|
+
...typeof entry.contextWindow === "number" && Number.isFinite(entry.contextWindow) ? { contextWindow: entry.contextWindow } : {},
|
|
33485
|
+
...typeof entry.maxTokens === "number" && Number.isFinite(entry.maxTokens) ? { maxTokens: entry.maxTokens } : {}
|
|
33486
|
+
}];
|
|
33487
|
+
});
|
|
33488
|
+
return {
|
|
33489
|
+
...typeof record.baseURL === "string" && record.baseURL.trim() !== "" ? { baseURL: record.baseURL } : {},
|
|
33490
|
+
models
|
|
33491
|
+
};
|
|
33492
|
+
}
|
|
32151
33493
|
/**
|
|
32152
33494
|
* The conventional credential reference for a provider route: `<ROUTE>_API_KEY`
|
|
32153
33495
|
* with the route uppercased and every non-alphanumeric run collapsed to one
|
|
@@ -32250,6 +33592,7 @@ async function loadProviderSettings(ctx) {
|
|
|
32250
33592
|
settingsRevision: namespace?.revision ?? 0,
|
|
32251
33593
|
configured,
|
|
32252
33594
|
removable,
|
|
33595
|
+
configuration: configurationOf(profile),
|
|
32253
33596
|
...credentialRef === void 0 ? {} : { credentialRef },
|
|
32254
33597
|
suggestedRef: deriveCredentialRef(base.provider),
|
|
32255
33598
|
...base.declared === void 0 ? {} : { declared: base.declared }
|
|
@@ -32335,6 +33678,50 @@ async function saveProviderCredential(ctx, target, rawKey) {
|
|
|
32335
33678
|
throw new ProviderSettingsError(credentialWriteMessage(error, checked.value));
|
|
32336
33679
|
}
|
|
32337
33680
|
}
|
|
33681
|
+
/** Save the endpoint and an explicit model allow-list without rebuilding the profile. */
|
|
33682
|
+
async function saveProviderConfiguration(ctx, target, configuration) {
|
|
33683
|
+
if (target.settingsNs.length === 0) throw new ProviderSettingsError(`provider "${target.provider}" has no managed settings namespace; configure it in settings.yaml`);
|
|
33684
|
+
const settings = ctx.get("settings");
|
|
33685
|
+
if (settings === void 0 || settings.writable !== true) throw new ProviderSettingsError("settings are read-only; provider configuration cannot be changed here");
|
|
33686
|
+
const baseURL = configuration.baseURL?.trim();
|
|
33687
|
+
if (baseURL !== void 0 && baseURL !== "") try {
|
|
33688
|
+
const parsed = new URL(baseURL);
|
|
33689
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("unsupported protocol");
|
|
33690
|
+
} catch {
|
|
33691
|
+
throw new ProviderSettingsError("base URL must be an absolute http or https URL");
|
|
33692
|
+
}
|
|
33693
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33694
|
+
const models = configuration.models.map((model) => {
|
|
33695
|
+
const id = model.id.trim();
|
|
33696
|
+
if (id === "" || seen.has(id)) throw new ProviderSettingsError("each selected model must have a unique non-empty id");
|
|
33697
|
+
seen.add(id);
|
|
33698
|
+
for (const [label, value] of [["context window", model.contextWindow], ["output window", model.maxTokens]]) if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new ProviderSettingsError(`${label} must be a positive integer`);
|
|
33699
|
+
return {
|
|
33700
|
+
id,
|
|
33701
|
+
...model.name === void 0 || model.name.trim() === "" ? {} : { name: model.name.trim() },
|
|
33702
|
+
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
33703
|
+
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
33704
|
+
};
|
|
33705
|
+
});
|
|
33706
|
+
const root = target.settingsPath;
|
|
33707
|
+
const ops = [baseURL === void 0 || baseURL === "" ? {
|
|
33708
|
+
op: "unset",
|
|
33709
|
+
path: [...root, "baseURL"]
|
|
33710
|
+
} : {
|
|
33711
|
+
op: "set",
|
|
33712
|
+
path: [...root, "baseURL"],
|
|
33713
|
+
value: baseURL
|
|
33714
|
+
}, {
|
|
33715
|
+
op: "set",
|
|
33716
|
+
path: [...root, "models"],
|
|
33717
|
+
value: models
|
|
33718
|
+
}];
|
|
33719
|
+
try {
|
|
33720
|
+
await settings.mutate(target.settingsNs, ops, target.settingsRevision);
|
|
33721
|
+
} catch (error) {
|
|
33722
|
+
throw new ProviderSettingsError(singleLine(messageOf(error)));
|
|
33723
|
+
}
|
|
33724
|
+
}
|
|
32338
33725
|
/**
|
|
32339
33726
|
* Remove the currently named credential without touching the provider
|
|
32340
33727
|
* profile. Only the resolved profile's own reference is unset; a dormant or
|
|
@@ -32395,133 +33782,43 @@ async function removeProviderSettings(ctx, target) {
|
|
|
32395
33782
|
}
|
|
32396
33783
|
//#endregion
|
|
32397
33784
|
//#region src/mentions.ts
|
|
32398
|
-
/**
|
|
32399
|
-
|
|
32400
|
-
* async scan of the session cwd, session candidates from the opt-in
|
|
32401
|
-
* `sessionReferenceResolver` service, and submission preparation through its
|
|
32402
|
-
* `prepare()` API. Picked session mentions land as canonical
|
|
32403
|
-
* `@[label](dsh-session:…)` tokens; on submit the text is parsed back into
|
|
32404
|
-
* readable `@label` text plus structured references, snapshots are injected
|
|
32405
|
-
* via `agent.inject()` before the readable message wakes the driver
|
|
32406
|
-
* (`followup` idle, `steer` running) — exactly the upstream README's wiring.
|
|
32407
|
-
*
|
|
32408
|
-
* Harness exposes no workspace-file mention service (only session references
|
|
32409
|
-
* plus a post-hoc produced-file linker), so the file index is the lightweight
|
|
32410
|
-
* bounded scan below, kept deliberately smaller than Codex's streaming
|
|
32411
|
-
* gitignore-aware walker.
|
|
32412
|
-
*
|
|
32413
|
-
* @module @deepseek-ai/dsh-code/mentions
|
|
32414
|
-
*/
|
|
32415
|
-
/** Directories never entered and files never listed during the scan. */
|
|
32416
|
-
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
32417
|
-
".git",
|
|
32418
|
-
"node_modules",
|
|
32419
|
-
"lib",
|
|
32420
|
-
"dist",
|
|
32421
|
-
"out",
|
|
32422
|
-
".omc",
|
|
32423
|
-
"coverage"
|
|
32424
|
-
]);
|
|
32425
|
-
const MAX_FILES = 4e3;
|
|
32426
|
-
const MAX_DEPTH = 12;
|
|
32427
|
-
/** Empty-query default rows: proves the index exists without typing (Codex's
|
|
32428
|
-
* popups show something on a bare sigil too). */
|
|
32429
|
-
const EMPTY_QUERY_ROWS = 20;
|
|
32430
|
-
/**
|
|
32431
|
-
* Bounded async BFS scan of a workspace; unreadable entries are skipped.
|
|
32432
|
-
* Both files and directories are indexed (directories insert with a trailing
|
|
32433
|
-
* slash), mirroring Codex's `MatchType::{File,Directory}` index. Dotfiles and
|
|
32434
|
-
* the {@link SKIP_DIRS} list are excluded, which is a coarser filter than
|
|
32435
|
-
* Codex's gitignore-aware walker but stays dependency-free and bounded.
|
|
32436
|
-
*/
|
|
32437
|
-
async function scanWorkspaceFiles(root, signal) {
|
|
32438
|
-
const found = [];
|
|
32439
|
-
const pending = [{
|
|
32440
|
-
absolute: root,
|
|
32441
|
-
relative: "",
|
|
32442
|
-
depth: 0
|
|
32443
|
-
}];
|
|
32444
|
-
const aborted = () => signal?.aborted === true;
|
|
32445
|
-
while (pending.length > 0 && found.length < MAX_FILES && !aborted()) {
|
|
32446
|
-
const current = pending.shift();
|
|
32447
|
-
if (current === void 0) break;
|
|
32448
|
-
let entries;
|
|
32449
|
-
try {
|
|
32450
|
-
entries = await readdir(current.absolute, { withFileTypes: true });
|
|
32451
|
-
} catch {
|
|
32452
|
-
continue;
|
|
32453
|
-
}
|
|
32454
|
-
for (const entry of entries) {
|
|
32455
|
-
if (found.length >= MAX_FILES || aborted()) return found;
|
|
32456
|
-
if (entry.name.startsWith(".")) continue;
|
|
32457
|
-
const relative = current.relative === "" ? entry.name : `${current.relative}/${entry.name}`;
|
|
32458
|
-
if (entry.isDirectory()) {
|
|
32459
|
-
if (SKIP_DIRS.has(entry.name) || current.depth + 1 > MAX_DEPTH) continue;
|
|
32460
|
-
found.push({
|
|
32461
|
-
path: relative,
|
|
32462
|
-
kind: "directory"
|
|
32463
|
-
});
|
|
32464
|
-
pending.push({
|
|
32465
|
-
absolute: join(current.absolute, entry.name),
|
|
32466
|
-
relative,
|
|
32467
|
-
depth: current.depth + 1
|
|
32468
|
-
});
|
|
32469
|
-
} else if (entry.isFile()) found.push({
|
|
32470
|
-
path: relative,
|
|
32471
|
-
kind: "file"
|
|
32472
|
-
});
|
|
32473
|
-
}
|
|
32474
|
-
}
|
|
32475
|
-
return found.sort((left, right) => left.path < right.path ? -1 : 1);
|
|
32476
|
-
}
|
|
32477
|
-
/** True when every query character appears in order in the haystack. */
|
|
32478
|
-
function isSubsequence(query, haystack) {
|
|
32479
|
-
let at = 0;
|
|
32480
|
-
for (const char of haystack) {
|
|
32481
|
-
if (char === query[at]) at += 1;
|
|
32482
|
-
if (at >= query.length) return true;
|
|
32483
|
-
}
|
|
32484
|
-
return at >= query.length;
|
|
32485
|
-
}
|
|
32486
|
-
/** Rank one file path against the typed query (community-TUI scoring shape). */
|
|
32487
|
-
function scoreFile(path, query) {
|
|
32488
|
-
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
32489
|
-
if (query === "") return 0;
|
|
32490
|
-
if (name === query) return 1e3;
|
|
32491
|
-
if (name.startsWith(query)) return 900;
|
|
32492
|
-
if (name.includes(query)) return 700;
|
|
32493
|
-
if (path.includes(query)) return 500;
|
|
32494
|
-
if (isSubsequence(query, name)) return 300;
|
|
32495
|
-
return 0;
|
|
32496
|
-
}
|
|
33785
|
+
/** Menu cap on file rows; the service owns ranking and default rows. */
|
|
33786
|
+
const MAX_FILE_ROWS = 20;
|
|
32497
33787
|
/**
|
|
32498
33788
|
* Create the mention API for one agent's workspace. A missing
|
|
32499
|
-
*
|
|
32500
|
-
*
|
|
32501
|
-
* (a bare launch before any session
|
|
32502
|
-
*
|
|
33789
|
+
* `fileReferences` service (with an agent present) or `sessionReferenceResolver`
|
|
33790
|
+
* degrades that half to empty rows; `prepare` passes text through untouched
|
|
33791
|
+
* without references. An undefined agent (a bare launch before any session
|
|
33792
|
+
* exists) runs the official WorkspaceFileSearch over the launch cwd — the
|
|
33793
|
+
* same class the mounted service uses per agent — so `@` file completion
|
|
33794
|
+
* works from the first keystroke; session references wait for the session.
|
|
32503
33795
|
*
|
|
32504
|
-
* `
|
|
32505
|
-
*
|
|
32506
|
-
*
|
|
32507
|
-
* @param ctx - context carrying the optional `
|
|
32508
|
-
*
|
|
32509
|
-
* @param
|
|
33796
|
+
* `candidates` never reaches for `this` — the runner hands it to the input
|
|
33797
|
+
* editor as a detached callback, and a `this`-bound method would throw on
|
|
33798
|
+
* every `@` key.
|
|
33799
|
+
* @param ctx - context carrying the optional `fileReferences` and
|
|
33800
|
+
* `sessionReferenceResolver` services.
|
|
33801
|
+
* @param agent - the session owner; excluded from its own session candidates.
|
|
33802
|
+
* @param cwd - launch working directory; bounds the pre-session search.
|
|
32510
33803
|
*/
|
|
32511
33804
|
function createMentions(ctx, agent, cwd) {
|
|
32512
33805
|
const resolver = ctx.get("sessionReferenceResolver");
|
|
33806
|
+
const fileReferences = ctx.get("fileReferences");
|
|
32513
33807
|
const sessionCapable = agent !== void 0 && resolver !== void 0;
|
|
32514
|
-
let
|
|
32515
|
-
const
|
|
32516
|
-
|
|
32517
|
-
|
|
33808
|
+
let preSessionSearch;
|
|
33809
|
+
const preSessionFiles = (query, signal) => {
|
|
33810
|
+
preSessionSearch ??= new WorkspaceFileSearch(cwd, {
|
|
33811
|
+
maxResults: DEFAULT_FILE_SEARCH_MAX_RESULTS,
|
|
33812
|
+
maxEntries: DEFAULT_FILE_SEARCH_MAX_ENTRIES,
|
|
33813
|
+
excludedDirectories: [...DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES]
|
|
33814
|
+
});
|
|
33815
|
+
return preSessionSearch.list(query, signal ?? new AbortController().signal);
|
|
32518
33816
|
};
|
|
32519
33817
|
return {
|
|
32520
|
-
files,
|
|
32521
33818
|
async candidates(query, signal) {
|
|
32522
33819
|
const needle = query.trim();
|
|
32523
|
-
const [
|
|
32524
|
-
const fileRows =
|
|
33820
|
+
const [files, sessions] = await Promise.all([agent !== void 0 && fileReferences !== void 0 ? fileReferences.list(agent, needle, signal ?? new AbortController().signal).catch(() => []) : agent === void 0 ? preSessionFiles(needle, signal).catch(() => []) : Promise.resolve([]), sessionCapable && needle !== "" && agent !== void 0 ? resolver.listCandidates(agent, needle, 10, signal).catch(() => []) : Promise.resolve([])]);
|
|
33821
|
+
const fileRows = files.slice(0, MAX_FILE_ROWS).map((candidate) => ({
|
|
32525
33822
|
label: candidate.path,
|
|
32526
33823
|
description: candidate.kind === "directory" ? "Folder" : "File",
|
|
32527
33824
|
kind: candidate.kind
|
|
@@ -32647,6 +33944,8 @@ function mountQuestionProvider(ctx) {
|
|
|
32647
33944
|
}
|
|
32648
33945
|
//#endregion
|
|
32649
33946
|
//#region src/store.ts
|
|
33947
|
+
/** Render frame budget: the notification cadence's upper bound. */
|
|
33948
|
+
const NOTIFY_FRAME_MS$1 = 16;
|
|
32650
33949
|
/**
|
|
32651
33950
|
* Create one transcript store, optionally seeded with replayed history. The
|
|
32652
33951
|
* seed folds synchronously BEFORE the first render, so a resumed session
|
|
@@ -32661,13 +33960,18 @@ function createTranscriptStore(replay) {
|
|
|
32661
33960
|
let view = replay === void 0 ? createTranscriptView() : projectEvents(replay);
|
|
32662
33961
|
const listeners = /* @__PURE__ */ new Set();
|
|
32663
33962
|
let scheduled = false;
|
|
33963
|
+
let lastNotifyAt = 0;
|
|
32664
33964
|
const notify = () => {
|
|
32665
33965
|
if (scheduled) return;
|
|
32666
33966
|
scheduled = true;
|
|
32667
|
-
|
|
33967
|
+
const wait = NOTIFY_FRAME_MS$1 - (Date.now() - lastNotifyAt);
|
|
33968
|
+
const dispatch = () => {
|
|
32668
33969
|
scheduled = false;
|
|
33970
|
+
lastNotifyAt = Date.now();
|
|
32669
33971
|
for (const listener of listeners) listener();
|
|
32670
|
-
}
|
|
33972
|
+
};
|
|
33973
|
+
if (wait <= 0) setImmediate(dispatch);
|
|
33974
|
+
else setTimeout(dispatch, wait);
|
|
32671
33975
|
};
|
|
32672
33976
|
return {
|
|
32673
33977
|
getView: () => view,
|
|
@@ -32689,6 +33993,8 @@ function createTranscriptStore(replay) {
|
|
|
32689
33993
|
}
|
|
32690
33994
|
};
|
|
32691
33995
|
}
|
|
33996
|
+
/** Render frame budget: the notification cadence's upper bound. */
|
|
33997
|
+
const NOTIFY_FRAME_MS = 16;
|
|
32692
33998
|
/** Bounded last-activity text (plain characters, display-sliced later). */
|
|
32693
33999
|
const MAX_ACTIVITY_CHARS = 80;
|
|
32694
34000
|
/** Single-line bounded preview of an assembled message's text content. */
|
|
@@ -32802,13 +34108,18 @@ function createSubagentFeed() {
|
|
|
32802
34108
|
let rows = Object.freeze([]);
|
|
32803
34109
|
const listeners = /* @__PURE__ */ new Set();
|
|
32804
34110
|
let scheduled = false;
|
|
34111
|
+
let lastNotifyAt = 0;
|
|
32805
34112
|
const notify = () => {
|
|
32806
34113
|
if (scheduled) return;
|
|
32807
34114
|
scheduled = true;
|
|
32808
|
-
|
|
34115
|
+
const wait = NOTIFY_FRAME_MS - (Date.now() - lastNotifyAt);
|
|
34116
|
+
const dispatch = () => {
|
|
32809
34117
|
scheduled = false;
|
|
34118
|
+
lastNotifyAt = Date.now();
|
|
32810
34119
|
for (const listener of listeners) listener();
|
|
32811
|
-
}
|
|
34120
|
+
};
|
|
34121
|
+
if (wait <= 0) setImmediate(dispatch);
|
|
34122
|
+
else setTimeout(dispatch, wait);
|
|
32812
34123
|
};
|
|
32813
34124
|
return {
|
|
32814
34125
|
apply(sessionId, event) {
|
|
@@ -32926,7 +34237,7 @@ function buildExportMarkdown(view, sessionId) {
|
|
|
32926
34237
|
for (const entry of view.entries) switch (entry.kind) {
|
|
32927
34238
|
case "user":
|
|
32928
34239
|
if (entry.notice) out.push(`> ⤷ context: ${entry.text}`, "");
|
|
32929
|
-
else out.push("## user", "", entry.text, "");
|
|
34240
|
+
else out.push("## user", "", entry.text, ...imageLabels(entry.images) === "" ? [] : [imageLabels(entry.images)], "");
|
|
32930
34241
|
break;
|
|
32931
34242
|
case "assistant":
|
|
32932
34243
|
if (entry.reasoning !== "") out.push("<details><summary>thinking</summary>", "", entry.reasoning, "", "</details>", "");
|
|
@@ -32959,7 +34270,7 @@ function buildExportMarkdown(view, sessionId) {
|
|
|
32959
34270
|
out.push(`> files changed: ${entry.paths.join(", ")}`, "");
|
|
32960
34271
|
break;
|
|
32961
34272
|
case "pending":
|
|
32962
|
-
out.push("## user", "", entry.text, "");
|
|
34273
|
+
out.push("## user", "", entry.text, ...imageLabels(entry.images) === "" ? [] : [imageLabels(entry.images)], "");
|
|
32963
34274
|
break;
|
|
32964
34275
|
default: assertNever(entry, "transcript entry kind");
|
|
32965
34276
|
}
|
|
@@ -32973,6 +34284,212 @@ function buildExportMarkdown(view, sessionId) {
|
|
|
32973
34284
|
return out.join("\n");
|
|
32974
34285
|
}
|
|
32975
34286
|
//#endregion
|
|
34287
|
+
//#region src/attachments.ts
|
|
34288
|
+
/** Terminal image-file adapter over the Harness durable attachment service. */
|
|
34289
|
+
/** Detect the supported encoded raster formats from bytes, never from a path suffix. */
|
|
34290
|
+
function detectImageMediaType(data) {
|
|
34291
|
+
if (data.length >= 8 && data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71 && data[4] === 13 && data[5] === 10 && data[6] === 26 && data[7] === 10) return "image/png";
|
|
34292
|
+
if (data.length >= 3 && data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
34293
|
+
if (data.length >= 6) {
|
|
34294
|
+
const signature = String.fromCharCode(...data.subarray(0, 6));
|
|
34295
|
+
if (signature === "GIF87a" || signature === "GIF89a") return "image/gif";
|
|
34296
|
+
}
|
|
34297
|
+
if (data.length >= 12 && String.fromCharCode(...data.subarray(0, 4)) === "RIFF" && String.fromCharCode(...data.subarray(8, 12)) === "WEBP") return "image/webp";
|
|
34298
|
+
}
|
|
34299
|
+
/** Read, validate, and persist an ordered image path list as model content blocks. */
|
|
34300
|
+
async function saveImagePaths(paths, attachments) {
|
|
34301
|
+
if (paths.length === 0) return [];
|
|
34302
|
+
if (attachments === void 0) throw new Error("image attachments are unavailable in this profile");
|
|
34303
|
+
const inputs = [];
|
|
34304
|
+
for (const path of paths) {
|
|
34305
|
+
let data;
|
|
34306
|
+
try {
|
|
34307
|
+
data = await readFile(path);
|
|
34308
|
+
} catch (error) {
|
|
34309
|
+
throw new Error(`cannot read image "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
34310
|
+
}
|
|
34311
|
+
const mediaType = detectImageMediaType(data);
|
|
34312
|
+
if (mediaType === void 0) throw new Error(`unsupported image file "${path}" (expected PNG, JPEG, WebP, or GIF)`);
|
|
34313
|
+
inputs.push({
|
|
34314
|
+
data,
|
|
34315
|
+
mediaType,
|
|
34316
|
+
name: basename(path)
|
|
34317
|
+
});
|
|
34318
|
+
}
|
|
34319
|
+
return (await attachments.saveImages(inputs)).map((attachment) => ({
|
|
34320
|
+
type: "image",
|
|
34321
|
+
attachment
|
|
34322
|
+
}));
|
|
34323
|
+
}
|
|
34324
|
+
//#endregion
|
|
34325
|
+
//#region src/editor.ts
|
|
34326
|
+
/** Host editor and clipboard adapters used by the terminal surface. */
|
|
34327
|
+
function waitForProcess(command, args, input) {
|
|
34328
|
+
return new Promise((resolve, reject) => {
|
|
34329
|
+
const child = spawn(command, [...args], {
|
|
34330
|
+
stdio: input === void 0 ? "inherit" : [
|
|
34331
|
+
"pipe",
|
|
34332
|
+
"ignore",
|
|
34333
|
+
"pipe"
|
|
34334
|
+
],
|
|
34335
|
+
windowsHide: true
|
|
34336
|
+
});
|
|
34337
|
+
let stderr = "";
|
|
34338
|
+
child.stderr?.on("data", (chunk) => {
|
|
34339
|
+
stderr += String(chunk);
|
|
34340
|
+
});
|
|
34341
|
+
child.once("error", reject);
|
|
34342
|
+
child.once("exit", (code) => {
|
|
34343
|
+
if (code === 0) resolve();
|
|
34344
|
+
else reject(/* @__PURE__ */ new Error(`${command} exited with code ${String(code)}${stderr.trim() === "" ? "" : `: ${stderr.trim()}`}`));
|
|
34345
|
+
});
|
|
34346
|
+
if (input !== void 0) child.stdin?.end(input);
|
|
34347
|
+
});
|
|
34348
|
+
}
|
|
34349
|
+
/** Copy UTF-8 text through the platform clipboard command. */
|
|
34350
|
+
async function copyText(text) {
|
|
34351
|
+
if (process.platform === "win32") {
|
|
34352
|
+
await waitForProcess("powershell.exe", [
|
|
34353
|
+
"-NoProfile",
|
|
34354
|
+
"-NonInteractive",
|
|
34355
|
+
"-Command",
|
|
34356
|
+
"[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false); Set-Clipboard -Value ([Console]::In.ReadToEnd())"
|
|
34357
|
+
], text);
|
|
34358
|
+
return;
|
|
34359
|
+
}
|
|
34360
|
+
if (process.platform === "darwin") {
|
|
34361
|
+
await waitForProcess("pbcopy", [], text);
|
|
34362
|
+
return;
|
|
34363
|
+
}
|
|
34364
|
+
await waitForProcess("xclip", ["-selection", "clipboard"], text);
|
|
34365
|
+
}
|
|
34366
|
+
/** Latest complete assistant text, excluding streaming and reasoning. */
|
|
34367
|
+
function latestAssistantText(view) {
|
|
34368
|
+
for (let index = view.entries.length - 1; index >= 0; index -= 1) {
|
|
34369
|
+
const entry = view.entries[index];
|
|
34370
|
+
if (entry?.kind === "assistant" && entry.text !== "") return entry.text;
|
|
34371
|
+
}
|
|
34372
|
+
}
|
|
34373
|
+
//#endregion
|
|
34374
|
+
//#region src/fork.ts
|
|
34375
|
+
/** Select a completed turn and trailing between-turn metadata. */
|
|
34376
|
+
function selectForkSeed(events, atSeq) {
|
|
34377
|
+
if (atSeq !== void 0 && (!Number.isSafeInteger(atSeq) || atSeq < 0)) throw new Error("fork event sequence must be a non-negative integer");
|
|
34378
|
+
const lastSeq = events.at(-1)?.seq ?? -1;
|
|
34379
|
+
const boundary = (atSeq === void 0 ? void 0 : events.find((event) => event.type === "turn/end" && event.seq >= atSeq)) ?? (atSeq === void 0 || atSeq > lastSeq ? events.findLast((event) => event.type === "turn/end") : void 0);
|
|
34380
|
+
if (boundary === void 0) throw new Error(atSeq !== void 0 && atSeq <= lastSeq ? `the turn containing event ${atSeq} has not completed` : "this session has no completed turn to fork from");
|
|
34381
|
+
let cut = events.indexOf(boundary) + 1;
|
|
34382
|
+
while (cut < events.length && events[cut]?.type !== "turn/start") cut += 1;
|
|
34383
|
+
return {
|
|
34384
|
+
boundarySeq: boundary.seq,
|
|
34385
|
+
events: events.slice(0, cut)
|
|
34386
|
+
};
|
|
34387
|
+
}
|
|
34388
|
+
//#endregion
|
|
34389
|
+
//#region src/git-workflow.ts
|
|
34390
|
+
/** Read-only Git inspection used by /diff and /review. */
|
|
34391
|
+
/** Split Git's stable `diff --git` framing without interpreting patch content. */
|
|
34392
|
+
function parseGitDiffFiles(text) {
|
|
34393
|
+
if (text === "") return [];
|
|
34394
|
+
return text.split(/(?=^diff --git )/mu).filter((chunk) => chunk !== "").map((chunk, index) => {
|
|
34395
|
+
const lines = chunk.replace(/\n$/u, "").split("\n");
|
|
34396
|
+
const plus = lines.find((line) => line.startsWith("+++ b/"));
|
|
34397
|
+
const minus = lines.find((line) => line.startsWith("--- a/"));
|
|
34398
|
+
const header = /^diff --git a\/(.+) b\/(.+)$/u.exec(lines[0] ?? "");
|
|
34399
|
+
return {
|
|
34400
|
+
path: plus?.slice(6) || minus?.slice(6) || header?.[2] || header?.[1] || `file ${index + 1}`,
|
|
34401
|
+
lines
|
|
34402
|
+
};
|
|
34403
|
+
});
|
|
34404
|
+
}
|
|
34405
|
+
/** Parse the intentionally small, option-safe /diff argument vocabulary. */
|
|
34406
|
+
function parseGitDiffSpec(argument) {
|
|
34407
|
+
const value = argument.trim();
|
|
34408
|
+
if (value === "") return {
|
|
34409
|
+
label: "working tree vs HEAD",
|
|
34410
|
+
args: [
|
|
34411
|
+
"diff",
|
|
34412
|
+
"--no-ext-diff",
|
|
34413
|
+
"--unified=3",
|
|
34414
|
+
"HEAD",
|
|
34415
|
+
"--"
|
|
34416
|
+
]
|
|
34417
|
+
};
|
|
34418
|
+
if (value === "--staged" || value === "--cached") return {
|
|
34419
|
+
label: "staged changes",
|
|
34420
|
+
args: [
|
|
34421
|
+
"diff",
|
|
34422
|
+
"--no-ext-diff",
|
|
34423
|
+
"--unified=3",
|
|
34424
|
+
"--cached",
|
|
34425
|
+
"--"
|
|
34426
|
+
]
|
|
34427
|
+
};
|
|
34428
|
+
if (value.startsWith("-") || /\s/u.test(value)) throw new Error("usage: /diff [--staged|git-ref]");
|
|
34429
|
+
return {
|
|
34430
|
+
label: `changes since ${value}`,
|
|
34431
|
+
args: [
|
|
34432
|
+
"diff",
|
|
34433
|
+
"--no-ext-diff",
|
|
34434
|
+
"--unified=3",
|
|
34435
|
+
value,
|
|
34436
|
+
"--"
|
|
34437
|
+
]
|
|
34438
|
+
};
|
|
34439
|
+
}
|
|
34440
|
+
function executeGit(cwd, args) {
|
|
34441
|
+
return new Promise((resolve, reject) => {
|
|
34442
|
+
execFile("git", [...args], {
|
|
34443
|
+
cwd,
|
|
34444
|
+
encoding: "utf8",
|
|
34445
|
+
maxBuffer: 16777216,
|
|
34446
|
+
windowsHide: true
|
|
34447
|
+
}, (error, stdout, stderr) => {
|
|
34448
|
+
if (error !== null) {
|
|
34449
|
+
reject(new Error(stderr.trim() || error.message));
|
|
34450
|
+
return;
|
|
34451
|
+
}
|
|
34452
|
+
resolve(stdout.replace(/\r\n/gu, "\n"));
|
|
34453
|
+
});
|
|
34454
|
+
});
|
|
34455
|
+
}
|
|
34456
|
+
/** Load one complete textual diff without invoking external diff drivers. */
|
|
34457
|
+
async function loadGitDiff(cwd, argument) {
|
|
34458
|
+
const spec = parseGitDiffSpec(argument);
|
|
34459
|
+
try {
|
|
34460
|
+
const text = await executeGit(cwd, spec.args);
|
|
34461
|
+
return {
|
|
34462
|
+
title: `git diff - ${spec.label}`,
|
|
34463
|
+
files: parseGitDiffFiles(text)
|
|
34464
|
+
};
|
|
34465
|
+
} catch (error) {
|
|
34466
|
+
if (argument.trim() !== "") throw error;
|
|
34467
|
+
return {
|
|
34468
|
+
title: "git diff - working tree",
|
|
34469
|
+
files: parseGitDiffFiles(await executeGit(cwd, [
|
|
34470
|
+
"diff",
|
|
34471
|
+
"--no-ext-diff",
|
|
34472
|
+
"--unified=3",
|
|
34473
|
+
"--"
|
|
34474
|
+
]))
|
|
34475
|
+
};
|
|
34476
|
+
}
|
|
34477
|
+
}
|
|
34478
|
+
/** Review prompt capped before it reaches a provider context window. */
|
|
34479
|
+
function buildReviewPrompt(diff, label, maxChars = 2e5) {
|
|
34480
|
+
const truncated = diff.length > maxChars;
|
|
34481
|
+
const body = truncated ? diff.slice(0, maxChars) : diff;
|
|
34482
|
+
return [
|
|
34483
|
+
"Review the following Git changes. Do not modify files or run write operations.",
|
|
34484
|
+
"Lead with concrete bugs, regressions, security risks, and missing tests, ordered by severity.",
|
|
34485
|
+
`Scope: ${label}${truncated ? " (diff truncated by CLI)" : ""}`,
|
|
34486
|
+
"",
|
|
34487
|
+
"```diff",
|
|
34488
|
+
body,
|
|
34489
|
+
"```"
|
|
34490
|
+
].join("\n");
|
|
34491
|
+
}
|
|
34492
|
+
//#endregion
|
|
32976
34493
|
//#region src/session-switch.ts
|
|
32977
34494
|
var SessionSwitchQueue = class {
|
|
32978
34495
|
execute;
|
|
@@ -33146,7 +34663,9 @@ const Config = z.object({ startup: z.object({
|
|
|
33146
34663
|
kind: z.string().required(),
|
|
33147
34664
|
sessionId: z.string(),
|
|
33148
34665
|
mode: z.string(),
|
|
33149
|
-
theme: z.string()
|
|
34666
|
+
theme: z.string(),
|
|
34667
|
+
prompt: z.string(),
|
|
34668
|
+
images: z.array(z.string())
|
|
33150
34669
|
}) });
|
|
33151
34670
|
/** Report an unexpected direct-driver failure and request a failing exit. */
|
|
33152
34671
|
function fail(io, error) {
|
|
@@ -33154,6 +34673,33 @@ function fail(io, error) {
|
|
|
33154
34673
|
io.exit(1);
|
|
33155
34674
|
}
|
|
33156
34675
|
/**
|
|
34676
|
+
* Snapshot caller-visible background jobs for the /jobs panel. Jobs the agent
|
|
34677
|
+
* started through run_in_background are fenced by their owner, so the CURRENT
|
|
34678
|
+
* agent is the caller. A missing registry is a harmless absence (the base
|
|
34679
|
+
* composition may not mount one) and collapses to the empty panel state —
|
|
34680
|
+
* the documented degradation for harmless probes, not an error.
|
|
34681
|
+
* @param ctx - context carrying the optional `jobs` registry.
|
|
34682
|
+
* @param caller - the active agent (undefined sees only unowned jobs).
|
|
34683
|
+
* @returns job rows in registration order; never throws.
|
|
34684
|
+
*/
|
|
34685
|
+
function listJobs(ctx, caller) {
|
|
34686
|
+
const jobs = ctx.get("jobs");
|
|
34687
|
+
if (jobs === void 0) return [];
|
|
34688
|
+
try {
|
|
34689
|
+
return jobs.list(caller).map((job) => ({
|
|
34690
|
+
id: job.id,
|
|
34691
|
+
kind: job.kind,
|
|
34692
|
+
label: job.label,
|
|
34693
|
+
status: job.status,
|
|
34694
|
+
detail: job.detail,
|
|
34695
|
+
startedAt: job.startedAt,
|
|
34696
|
+
finishedAt: job.finishedAt
|
|
34697
|
+
}));
|
|
34698
|
+
} catch {
|
|
34699
|
+
return [];
|
|
34700
|
+
}
|
|
34701
|
+
}
|
|
34702
|
+
/**
|
|
33157
34703
|
* Resolve the working directory's git branch for the status line.
|
|
33158
34704
|
* @param cwd - the session's working directory.
|
|
33159
34705
|
* @returns the branch name, or '' outside a repository or on a detached HEAD.
|
|
@@ -33318,21 +34864,23 @@ async function run(ctx, startup, io) {
|
|
|
33318
34864
|
sessionId: SessionId(next.sessionId),
|
|
33319
34865
|
meta: {
|
|
33320
34866
|
cwd: nextCwd,
|
|
33321
|
-
agentPreset: mode
|
|
34867
|
+
agentPreset: mode,
|
|
34868
|
+
...next.parentSession === void 0 ? {} : { parentSession: next.parentSession },
|
|
34869
|
+
...next.seedLength === void 0 ? {} : { seedLength: next.seedLength }
|
|
33322
34870
|
},
|
|
34871
|
+
...next.seed === void 0 ? {} : { seed: next.seed },
|
|
33323
34872
|
agentOptions: seedOptions,
|
|
33324
34873
|
signal: quitAbort.signal,
|
|
33325
34874
|
setup
|
|
33326
34875
|
});
|
|
33327
34876
|
const session = handle.agent.session;
|
|
33328
34877
|
if (!next.resume && permissionPresets !== void 0) applyPendingPermission(permissionPresets, session, pendingPermission);
|
|
33329
|
-
const sessionCwd = session.header.cwd ?? nextCwd;
|
|
33330
34878
|
return {
|
|
33331
34879
|
handle,
|
|
33332
34880
|
agent: handle.agent,
|
|
33333
34881
|
session,
|
|
33334
34882
|
store: createTranscriptStore(session.events),
|
|
33335
|
-
mentions: createMentions(ctx, handle.agent,
|
|
34883
|
+
mentions: createMentions(ctx, handle.agent, session.header.cwd ?? nextCwd),
|
|
33336
34884
|
mode: mode ?? "standard",
|
|
33337
34885
|
selection: selectionState,
|
|
33338
34886
|
resumed: next.resume
|
|
@@ -33398,7 +34946,7 @@ async function run(ctx, startup, io) {
|
|
|
33398
34946
|
store.apply(event);
|
|
33399
34947
|
return;
|
|
33400
34948
|
}
|
|
33401
|
-
if (subject.header.parentSession === session.id) subagents.apply(subject.id, event);
|
|
34949
|
+
if (subject.header.parentSession === session.id && subject.header.origin === "subagent") subagents.apply(subject.id, event);
|
|
33402
34950
|
});
|
|
33403
34951
|
const commands = watchCommands(ctx);
|
|
33404
34952
|
if (agent !== void 0) commands.setAgent(agent);
|
|
@@ -33524,7 +35072,7 @@ async function run(ctx, startup, io) {
|
|
|
33524
35072
|
const finish = () => {
|
|
33525
35073
|
pendingControllers.delete(controller);
|
|
33526
35074
|
};
|
|
33527
|
-
Promise.resolve().then(() => registry.execute(currentAgent, line, controller.signal)).then((execution) => {
|
|
35075
|
+
Promise.resolve().then(() => registry.execute(currentAgent, line, [], controller.signal)).then((execution) => {
|
|
33528
35076
|
finish();
|
|
33529
35077
|
if (epoch !== atEpoch || agent !== currentAgent) return;
|
|
33530
35078
|
if (execution === void 0) try {
|
|
@@ -33545,10 +35093,10 @@ async function run(ctx, startup, io) {
|
|
|
33545
35093
|
});
|
|
33546
35094
|
};
|
|
33547
35095
|
/** Deliver one trimmed line to the live session, expanding mentions first. */
|
|
33548
|
-
const deliverLine = (line, mode) => {
|
|
35096
|
+
const deliverLine = (line, mode, images = []) => {
|
|
33549
35097
|
const currentAgent = agent;
|
|
33550
35098
|
const currentMentions = mentions;
|
|
33551
|
-
if (isSlashLine(line) && mode === "followup") {
|
|
35099
|
+
if (images.length === 0 && isSlashLine(line) && mode === "followup") {
|
|
33552
35100
|
runSlash(line);
|
|
33553
35101
|
return;
|
|
33554
35102
|
}
|
|
@@ -33564,11 +35112,12 @@ async function run(ctx, startup, io) {
|
|
|
33564
35112
|
if (epoch !== atEpoch || agent !== currentAgent) return;
|
|
33565
35113
|
try {
|
|
33566
35114
|
if (context !== void 0) currentAgent.inject(context);
|
|
35115
|
+
const content = [...readable === "" ? [] : [{
|
|
35116
|
+
type: "text",
|
|
35117
|
+
text: readable
|
|
35118
|
+
}], ...images];
|
|
33567
35119
|
const message = createUserMessage({
|
|
33568
|
-
content
|
|
33569
|
-
type: "text",
|
|
33570
|
-
text: readable
|
|
33571
|
-
}],
|
|
35120
|
+
content,
|
|
33572
35121
|
source: { kind: "user" }
|
|
33573
35122
|
});
|
|
33574
35123
|
if (mode === "steer") currentAgent.steer(message);
|
|
@@ -33602,7 +35151,7 @@ async function run(ctx, startup, io) {
|
|
|
33602
35151
|
await pendingModeWork;
|
|
33603
35152
|
if (session !== void 0) {
|
|
33604
35153
|
const queued = pendingInputs.splice(0);
|
|
33605
|
-
for (const item of queued) deliverLine(item.text, item.mode);
|
|
35154
|
+
for (const item of queued) deliverLine(item.text, item.mode, item.images);
|
|
33606
35155
|
return;
|
|
33607
35156
|
}
|
|
33608
35157
|
const next = await prepare({
|
|
@@ -33629,7 +35178,7 @@ async function run(ctx, startup, io) {
|
|
|
33629
35178
|
abortPendingControllers();
|
|
33630
35179
|
epoch += 1;
|
|
33631
35180
|
const queued = pendingInputs.splice(0);
|
|
33632
|
-
for (const item of queued) deliverLine(item.text, item.mode);
|
|
35181
|
+
for (const item of queued) deliverLine(item.text, item.mode, item.images);
|
|
33633
35182
|
} finally {
|
|
33634
35183
|
creating = false;
|
|
33635
35184
|
}
|
|
@@ -33639,14 +35188,14 @@ async function run(ctx, startup, io) {
|
|
|
33639
35188
|
});
|
|
33640
35189
|
};
|
|
33641
35190
|
/** Deliver one readable line to the agent, expanding session mentions first. */
|
|
33642
|
-
const send = (text, mode) => {
|
|
35191
|
+
const send = (text, mode, images = []) => {
|
|
33643
35192
|
const line = text.trim();
|
|
33644
|
-
if (line === "") return;
|
|
33645
|
-
if (line.startsWith("/mode ")) {
|
|
35193
|
+
if (line === "" && images.length === 0) return;
|
|
35194
|
+
if (images.length === 0 && line.startsWith("/mode ")) {
|
|
33646
35195
|
switchModeAction(line.slice(6).trim()).then((selected) => bridge.notify(`mode → ${selected}`), (error) => bridge.notify(`mode switch failed: ${error instanceof Error ? error.message : String(error)}`, "error"));
|
|
33647
35196
|
return;
|
|
33648
35197
|
}
|
|
33649
|
-
if (line.startsWith("/permission ")) {
|
|
35198
|
+
if (images.length === 0 && line.startsWith("/permission ")) {
|
|
33650
35199
|
try {
|
|
33651
35200
|
const selected = setPermissionAction(line.slice(12).trim());
|
|
33652
35201
|
bridge.notify(`permission → ${selected}`);
|
|
@@ -33658,12 +35207,13 @@ async function run(ctx, startup, io) {
|
|
|
33658
35207
|
if (session === void 0) {
|
|
33659
35208
|
pendingInputs.push({
|
|
33660
35209
|
text: line,
|
|
33661
|
-
mode
|
|
35210
|
+
mode,
|
|
35211
|
+
images
|
|
33662
35212
|
});
|
|
33663
35213
|
ensureSession();
|
|
33664
35214
|
return;
|
|
33665
35215
|
}
|
|
33666
|
-
deliverLine(line, mode);
|
|
35216
|
+
deliverLine(line, mode, images);
|
|
33667
35217
|
};
|
|
33668
35218
|
/** Dispatch one submitted line: slash commands to the registry, other text to the agent. */
|
|
33669
35219
|
const dispatch = (text) => {
|
|
@@ -33995,6 +35545,47 @@ async function run(ctx, startup, io) {
|
|
|
33995
35545
|
label: id.slice(-12)
|
|
33996
35546
|
});
|
|
33997
35547
|
};
|
|
35548
|
+
const reviewChanges = (argument) => {
|
|
35549
|
+
loadGitDiff(session?.header.cwd ?? cwd, argument).then(({ title, files }) => {
|
|
35550
|
+
try {
|
|
35551
|
+
setPermissionAction("read-only");
|
|
35552
|
+
} catch (error) {
|
|
35553
|
+
bridge.notify(`review unavailable: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
35554
|
+
return;
|
|
35555
|
+
}
|
|
35556
|
+
send(buildReviewPrompt(files.flatMap((file) => file.lines).join("\n"), title), "followup");
|
|
35557
|
+
bridge.notify("review started under read-only permissions");
|
|
35558
|
+
}, (error) => {
|
|
35559
|
+
bridge.notify(`review failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
35560
|
+
});
|
|
35561
|
+
};
|
|
35562
|
+
const forkSession = (argument) => {
|
|
35563
|
+
if (session === void 0 || active === void 0) {
|
|
35564
|
+
bridge.notify("no session yet - submit a message to start", "warning");
|
|
35565
|
+
return;
|
|
35566
|
+
}
|
|
35567
|
+
try {
|
|
35568
|
+
const text = argument.trim();
|
|
35569
|
+
const atSeq = text === "" ? void 0 : Number(text);
|
|
35570
|
+
if (text !== "" && (!Number.isSafeInteger(atSeq) || (atSeq ?? -1) < 0)) throw new Error("usage: /fork [event-seq]");
|
|
35571
|
+
const seed = selectForkSeed(session.events, atSeq);
|
|
35572
|
+
const id = `session-${randomUUID()}`;
|
|
35573
|
+
requestSwitch({
|
|
35574
|
+
target: {
|
|
35575
|
+
sessionId: id,
|
|
35576
|
+
resume: false,
|
|
35577
|
+
mode: active.mode,
|
|
35578
|
+
cwd: session.header.cwd ?? cwd,
|
|
35579
|
+
seed: seed.events,
|
|
35580
|
+
parentSession: session.id,
|
|
35581
|
+
seedLength: seed.events.length
|
|
35582
|
+
},
|
|
35583
|
+
label: id.slice(-12)
|
|
35584
|
+
});
|
|
35585
|
+
} catch (error) {
|
|
35586
|
+
bridge.notify(`fork failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
35587
|
+
}
|
|
35588
|
+
};
|
|
33998
35589
|
const switchSession = (row) => {
|
|
33999
35590
|
if (!row.resumable) {
|
|
34000
35591
|
bridge.notify("subagent conversations are read-only", "warning");
|
|
@@ -34043,6 +35634,7 @@ async function run(ctx, startup, io) {
|
|
|
34043
35634
|
loadModelProviders: () => loadProviderSettings(ctx),
|
|
34044
35635
|
subscribeModelProviders: (listener) => subscribeProviderSettings(ctx, listener),
|
|
34045
35636
|
saveModelProviderCredential: (target, key) => saveProviderCredential(ctx, target, key),
|
|
35637
|
+
saveModelProviderConfiguration: (target, configuration) => saveProviderConfiguration(ctx, target, configuration),
|
|
34046
35638
|
unsetModelProviderCredential: (target) => unsetProviderCredential(ctx, target),
|
|
34047
35639
|
removeModelProvider: (target) => removeProviderSettings(ctx, target),
|
|
34048
35640
|
loadMentions: (query, signal) => mentions.candidates(query, signal),
|
|
@@ -34055,10 +35647,14 @@ async function run(ctx, startup, io) {
|
|
|
34055
35647
|
deleteSession,
|
|
34056
35648
|
exportTranscript,
|
|
34057
35649
|
renameTitle,
|
|
35650
|
+
copyLastResponse,
|
|
35651
|
+
loadGitDiff: (argument) => loadGitDiff(session?.header.cwd ?? cwd, argument),
|
|
35652
|
+
reviewChanges,
|
|
34058
35653
|
loadPresets: () => presets.list(),
|
|
34059
35654
|
switchMode: switchModeAction,
|
|
34060
35655
|
loadPermissions: () => permissionPresets === void 0 ? Promise.reject(/* @__PURE__ */ new Error("permission presets are not mounted in this composition")) : Promise.resolve(listPermissionRows(permissionPresets)),
|
|
34061
35656
|
createSession,
|
|
35657
|
+
forkSession,
|
|
34062
35658
|
loadSessions,
|
|
34063
35659
|
loadSessionTranscript,
|
|
34064
35660
|
loadSubagents: () => {
|
|
@@ -34070,11 +35666,12 @@ async function run(ctx, startup, io) {
|
|
|
34070
35666
|
sort: "newest",
|
|
34071
35667
|
currentCwd: current.header.cwd ?? cwd,
|
|
34072
35668
|
query: ""
|
|
34073
|
-
}).then((rows) => rows.filter((row) => row.parent === current.id));
|
|
35669
|
+
}).then((rows) => rows.filter((row) => row.parent === current.id && row.subagent));
|
|
34074
35670
|
},
|
|
34075
35671
|
switchSession,
|
|
34076
35672
|
cancelSessionSwitch,
|
|
34077
35673
|
loadPlugins: () => listPluginRows(ctx),
|
|
35674
|
+
loadJobs: () => listJobs(ctx, active?.agent),
|
|
34078
35675
|
statusline: statuslineItems,
|
|
34079
35676
|
saveStatusline,
|
|
34080
35677
|
saveTheme,
|
|
@@ -34090,6 +35687,13 @@ async function run(ctx, startup, io) {
|
|
|
34090
35687
|
mountRef.current?.rerender(appElement());
|
|
34091
35688
|
};
|
|
34092
35689
|
mountRef.current = io.mount(appElement());
|
|
35690
|
+
if (startup.prompt !== void 0 || (startup.images?.length ?? 0) > 0) saveImagePaths(startup.images ?? [], ctx.get("attachments")).then((images) => send(startup.prompt ?? "", "followup", images), (error) => bridge.notify(`initial prompt failed: ${error instanceof Error ? error.message : String(error)}`, "error"));
|
|
35691
|
+
async function copyLastResponse() {
|
|
35692
|
+
const text = latestAssistantText(store.getView());
|
|
35693
|
+
if (text === void 0) return "nothing to copy yet";
|
|
35694
|
+
await copyText(text);
|
|
35695
|
+
return "copied latest response";
|
|
35696
|
+
}
|
|
34093
35697
|
if (statuslineWarning !== void 0) setTimeout(() => {
|
|
34094
35698
|
bridge.notify("statusline config unreadable, using defaults: " + statuslineWarning, "warning");
|
|
34095
35699
|
}, 50);
|
|
@@ -34104,22 +35708,27 @@ async function run(ctx, startup, io) {
|
|
|
34104
35708
|
*/
|
|
34105
35709
|
function apply(ctx, config) {
|
|
34106
35710
|
const theme = config.startup.theme === void 0 ? void 0 : parseThemeName(config.startup.theme);
|
|
35711
|
+
const input = {
|
|
35712
|
+
...theme === void 0 ? {} : { theme },
|
|
35713
|
+
...config.startup.prompt === void 0 ? {} : { prompt: config.startup.prompt },
|
|
35714
|
+
...config.startup.images === void 0 ? {} : { images: config.startup.images }
|
|
35715
|
+
};
|
|
34107
35716
|
const startup = config.startup.kind === "resume" && config.startup.sessionId !== void 0 ? {
|
|
34108
35717
|
kind: "resume",
|
|
34109
35718
|
sessionId: config.startup.sessionId,
|
|
34110
|
-
...
|
|
35719
|
+
...input
|
|
34111
35720
|
} : config.startup.kind === "latest" ? {
|
|
34112
35721
|
kind: "latest",
|
|
34113
|
-
...
|
|
35722
|
+
...input
|
|
34114
35723
|
} : config.startup.kind === "named" && config.startup.sessionId !== void 0 ? {
|
|
34115
35724
|
kind: "named",
|
|
34116
35725
|
sessionId: config.startup.sessionId,
|
|
34117
35726
|
...config.startup.mode === void 0 ? {} : { mode: config.startup.mode },
|
|
34118
|
-
...
|
|
35727
|
+
...input
|
|
34119
35728
|
} : {
|
|
34120
35729
|
kind: "fresh",
|
|
34121
35730
|
...config.startup.mode === void 0 ? {} : { mode: config.startup.mode },
|
|
34122
|
-
...
|
|
35731
|
+
...input
|
|
34123
35732
|
};
|
|
34124
35733
|
const exit = ctx.get("appExit");
|
|
34125
35734
|
if (exit === void 0) throw new Error("tui-runner: the launcher must provide ctx.appExit before the tree mounts");
|