dsh-code 0.9.1 → 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 +29 -13
- package/README.md +264 -248
- package/bin/deepseek.mjs +100 -6
- package/cordis.patch.yml +29 -1
- package/lib/index.mjs +2223 -687
- 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 +10 -39
- 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 -141
- package/src/app.ts +4455 -3917
- 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 +25 -55
- 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
|
"·",
|
|
@@ -26626,6 +26765,8 @@ const DEEPSEEK_WAVE_BANDS = {
|
|
|
26626
26765
|
]]
|
|
26627
26766
|
}
|
|
26628
26767
|
};
|
|
26768
|
+
/** Extra display time applied to every Codex ignition style. */
|
|
26769
|
+
const DEEPSEEK_WAVE_DURATION_EXTENSION_MS = 200;
|
|
26629
26770
|
/** Original Codex duration used as the animation's sampling timeline. */
|
|
26630
26771
|
function deepseekWaveBaseDuration(tier, style) {
|
|
26631
26772
|
const pro = tier === "deepseek" || tier === "unknown";
|
|
@@ -26643,7 +26784,7 @@ function deepseekWaveBaseDuration(tier, style) {
|
|
|
26643
26784
|
* @returns the duration in milliseconds.
|
|
26644
26785
|
*/
|
|
26645
26786
|
function deepseekWaveDuration(tier, style = "wave") {
|
|
26646
|
-
return deepseekWaveBaseDuration(tier, style) +
|
|
26787
|
+
return deepseekWaveBaseDuration(tier, style) + DEEPSEEK_WAVE_DURATION_EXTENSION_MS;
|
|
26647
26788
|
}
|
|
26648
26789
|
/** Map the extended display timeline back onto the original Codex samples. */
|
|
26649
26790
|
function deepseekWaveSampleElapsedMs(tick, tier, style) {
|
|
@@ -26661,22 +26802,6 @@ function deepseekWaveStyleRandom(previous) {
|
|
|
26661
26802
|
return candidates[Math.floor(Math.random() * candidates.length)] ?? "wave";
|
|
26662
26803
|
}
|
|
26663
26804
|
/**
|
|
26664
|
-
* Blank-cell background the wave tint blends toward — fixed approximations
|
|
26665
|
-
* of the terminal's default background, mirroring Codex's
|
|
26666
|
-
* `user_message_bg_rgb` (which derives a near-black / near-white bubble tint
|
|
26667
|
-
* from the terminal background). The Ink layer picks the active theme's one.
|
|
26668
|
-
*/
|
|
26669
|
-
const WAVE_BASE_DARK = [
|
|
26670
|
-
16,
|
|
26671
|
-
18,
|
|
26672
|
-
24
|
|
26673
|
-
];
|
|
26674
|
-
const WAVE_BASE_LIGHT = [
|
|
26675
|
-
242,
|
|
26676
|
-
244,
|
|
26677
|
-
248
|
|
26678
|
-
];
|
|
26679
|
-
/**
|
|
26680
26805
|
* Tier for a `provider/model` label: a model id containing `flash` runs the
|
|
26681
26806
|
* single-band flash tier; everything else (pro/reasoner/chat) runs the
|
|
26682
26807
|
* dual-band deepseek tier. Mirrors Codex's Max→Ultra mapping.
|
|
@@ -26770,14 +26895,22 @@ function blendRgb(fg, bg, alpha) {
|
|
|
26770
26895
|
];
|
|
26771
26896
|
}
|
|
26772
26897
|
/**
|
|
26773
|
-
*
|
|
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
|
|
26774
26905
|
* `paint_bands` + `Canvas::tint` for all three styles. Bands overlap with a
|
|
26775
26906
|
* max for Wave/Pulse and a SUM for Aurora (Codex differs by style), the
|
|
26776
26907
|
* weighted hues mix per column (Wave/Pulse always end on hue 0), the tint
|
|
26777
26908
|
* blends the mixed hue toward the blank-cell base at the style's alpha cap,
|
|
26778
26909
|
* and Aurora applies its own fade envelope. Returns `null` when the column
|
|
26779
26910
|
* should stay transparent, so the row returns to no `backgroundColor` on
|
|
26780
|
-
* 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.
|
|
26781
26914
|
* @param tick - wave frame (0, 1, … at DEEPSEEK_WAVE_TICK_MS).
|
|
26782
26915
|
* @param column - column index in the content row (0..width-1).
|
|
26783
26916
|
* @param width - content-row width in columns.
|
|
@@ -26785,11 +26918,13 @@ function blendRgb(fg, bg, alpha) {
|
|
|
26785
26918
|
* @param style - the ignition style.
|
|
26786
26919
|
* @param hues - the tier's three hues.
|
|
26787
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).
|
|
26788
26923
|
* @returns the blended RGB background, or null for transparent.
|
|
26789
26924
|
*/
|
|
26790
|
-
function deepseekWaveColumnBg(tick, column, width, tier, style, hues, base) {
|
|
26925
|
+
function deepseekWaveColumnBg(tick, column, width, tier, style, hues, base, row = 0, rows = 1) {
|
|
26791
26926
|
const total = deepseekWaveBaseDuration(tier, style) / 1e3;
|
|
26792
|
-
const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1e3;
|
|
26927
|
+
const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1e3 - (row - (rows - 1) / 2) * total * DEEPSEEK_WAVE_ROW_PHASE;
|
|
26793
26928
|
const fade = style === "aurora" ? envelope(elapsed, total, .25, .4) : 1;
|
|
26794
26929
|
const weights = [
|
|
26795
26930
|
0,
|
|
@@ -26828,27 +26963,11 @@ function deepseekWaveColumnBg(tick, column, width, tier, style, hues, base) {
|
|
|
26828
26963
|
*/
|
|
26829
26964
|
function deepseekWaveSpark(tick) {
|
|
26830
26965
|
const elapsed = deepseekWaveSampleElapsedMs(tick, "deepseek", "wave");
|
|
26831
|
-
if (elapsed <
|
|
26832
|
-
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);
|
|
26833
26968
|
return SPARK_GLYPHS[frame] ?? null;
|
|
26834
26969
|
}
|
|
26835
26970
|
/**
|
|
26836
|
-
* The composer BORDER color at a tick: the frame breathes with the wave —
|
|
26837
|
-
* the palette's static dim blends toward the tier accent as the crest is
|
|
26838
|
-
* alive and back, so the frame glows up while the wave sweeps and settles to
|
|
26839
|
-
* dim on both ends (first==last frame==dim, no hard jump).
|
|
26840
|
-
* @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
|
|
26841
|
-
* @param tier - the wave tier.
|
|
26842
|
-
* @param hues - the tier's three hues; the border blends toward hues[0].
|
|
26843
|
-
* @param dim - the palette's static dim RGB (the resting border color).
|
|
26844
|
-
* @returns the blended border RGB.
|
|
26845
|
-
*/
|
|
26846
|
-
function deepseekWaveBorderColor(tick, tier, style, hues, dim) {
|
|
26847
|
-
const total = deepseekWaveBaseDuration(tier, style) / 1e3;
|
|
26848
|
-
const glow = envelope(deepseekWaveSampleElapsedMs(tick, tier, style) / 1e3, total, total * .25, total * .4);
|
|
26849
|
-
return blendRgb(hues[0], dim, .25 + glow * .6);
|
|
26850
|
-
}
|
|
26851
|
-
/**
|
|
26852
26971
|
* Whether the `deepseek` wordmark rides the wave at this tick: it fades in
|
|
26853
26972
|
* shortly after the first crest launches and out before the wave settles,
|
|
26854
26973
|
* so the brand name surfaces through the sweep's middle. The Ink layer
|
|
@@ -26921,9 +27040,9 @@ function effortAboveHigh(effort) {
|
|
|
26921
27040
|
/** Lightweight session-directory projection for the /resume picker. */
|
|
26922
27041
|
/** Case-insensitive filesystems (Windows, macOS) compare paths by lowercased form. */
|
|
26923
27042
|
const CASE_INSENSITIVE_FS = process.platform === "win32" || process.platform === "darwin";
|
|
26924
|
-
/** True
|
|
27043
|
+
/** True only for delegated subagents; ordinary forks also carry lineage. */
|
|
26925
27044
|
function isSubagentSession(header) {
|
|
26926
|
-
return header.origin === "subagent"
|
|
27045
|
+
return header.origin === "subagent";
|
|
26927
27046
|
}
|
|
26928
27047
|
function comparablePath(value) {
|
|
26929
27048
|
const resolved = resolve(value);
|
|
@@ -27143,10 +27262,18 @@ function cacheHitPercent(usage) {
|
|
|
27143
27262
|
const STATUS_ITEM_SEPARATOR = " · ";
|
|
27144
27263
|
/** The Codex-style mode cycle hint appended to the permission badge. */
|
|
27145
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;
|
|
27146
27267
|
/** Free-tail floor in columns: wide enough for the bare percent readout, so
|
|
27147
27268
|
* the warning stays visible even at 100%+ occupancy. */
|
|
27148
27269
|
const CONTEXT_MIN_FREE = 5;
|
|
27149
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
|
+
/**
|
|
27150
27277
|
* Render context occupancy as ONE stepless bar: a solid DeepSeek-blue fill
|
|
27151
27278
|
* run, a dim dotted free track, and the usage readout riding the track's
|
|
27152
27279
|
* right edge (`12.3K/1.0M 25%`, shrinking to the bare percent as the track
|
|
@@ -27164,7 +27291,7 @@ function contextBar(usedTokens, contextWindow, width) {
|
|
|
27164
27291
|
if (width <= 0 || contextWindow <= 0) return [];
|
|
27165
27292
|
const used = Math.max(0, usedTokens);
|
|
27166
27293
|
const percent = Math.round(used / contextWindow * 100);
|
|
27167
|
-
const readoutTone = percent >=
|
|
27294
|
+
const readoutTone = percent >= CONTEXT_WARN_PERCENT ? "warn" : "value";
|
|
27168
27295
|
const freeShare = Math.round(Math.max(0, contextWindow - used) / contextWindow * width);
|
|
27169
27296
|
const freeColumns = Math.min(width, Math.max(freeShare, CONTEXT_MIN_FREE));
|
|
27170
27297
|
const usedColumns = Math.max(0, width - freeColumns);
|
|
@@ -27356,7 +27483,7 @@ function joinWidth(parts, separator) {
|
|
|
27356
27483
|
return width + separator * (parts.length - 1);
|
|
27357
27484
|
}
|
|
27358
27485
|
/** Build every candidate group/span with its drop rank and item id. */
|
|
27359
|
-
function buildCandidates(facts, stats, busy, enabled) {
|
|
27486
|
+
function buildCandidates(facts, stats, busy, enabled, contextWidth) {
|
|
27360
27487
|
const identity = [{
|
|
27361
27488
|
text: busy ? "● " : "○ ",
|
|
27362
27489
|
tone: busy ? "live" : "meta"
|
|
@@ -27478,7 +27605,7 @@ function buildCandidates(facts, stats, busy, enabled) {
|
|
|
27478
27605
|
group: { spans: [{
|
|
27479
27606
|
text: "context ",
|
|
27480
27607
|
tone: "label"
|
|
27481
|
-
}, ...contextBar(stats.lastPromptTokens, stats.contextWindow,
|
|
27608
|
+
}, ...contextBar(stats.lastPromptTokens, stats.contextWindow, contextWidth)] },
|
|
27482
27609
|
rank: RANK_CONTEXT,
|
|
27483
27610
|
id: "context"
|
|
27484
27611
|
});
|
|
@@ -27567,7 +27694,8 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
|
|
|
27567
27694
|
const items = options.items ?? DEFAULT_STATUSLINE_ITEMS;
|
|
27568
27695
|
const enabled = new Set(items);
|
|
27569
27696
|
const budget = Math.max(1, Math.floor(columns) - WIDTH_SAFETY);
|
|
27570
|
-
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);
|
|
27571
27699
|
const groupSeparator = visibleColumns(" | ");
|
|
27572
27700
|
const itemSeparator = visibleColumns(STATUS_ITEM_SEPARATOR);
|
|
27573
27701
|
const position = new Map(items.map((id, index) => [id, index]));
|
|
@@ -27578,12 +27706,51 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
|
|
|
27578
27706
|
let hint = badge >= 0 && !busy;
|
|
27579
27707
|
const leftKept = [...orderedLeft];
|
|
27580
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
|
+
};
|
|
27581
27722
|
const width = () => {
|
|
27582
27723
|
const leftWidth = joinWidth(leftKept.map((entry) => spansWidth(entry.group.spans)), groupSeparator);
|
|
27583
27724
|
const rightWidth = joinWidth(rightKept.map((entry) => visibleColumns(entry.span.text)), itemSeparator) + (hint ? visibleColumns(STATUS_CYCLE_HINT) : 0);
|
|
27584
27725
|
return rightWidth > 0 ? leftWidth + LEFT_RIGHT_GAP + rightWidth : leftWidth;
|
|
27585
27726
|
};
|
|
27586
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
|
+
}
|
|
27587
27754
|
if (hint) {
|
|
27588
27755
|
hint = false;
|
|
27589
27756
|
continue;
|
|
@@ -27654,6 +27821,7 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
|
|
|
27654
27821
|
}
|
|
27655
27822
|
//#endregion
|
|
27656
27823
|
//#region src/render/lines.ts
|
|
27824
|
+
/** Width-safe styled physical rows for bounded terminal panels. */
|
|
27657
27825
|
/** Construct one segment without leaking mutable objects into cached rows. */
|
|
27658
27826
|
function lineSegment(text, style = "plain") {
|
|
27659
27827
|
return {
|
|
@@ -27711,6 +27879,59 @@ function styledLines(segments, columns) {
|
|
|
27711
27879
|
function textLines(text, columns, style = "plain") {
|
|
27712
27880
|
return styledLines([lineSegment(text, style)], columns);
|
|
27713
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
|
+
}
|
|
27714
27935
|
/** Markdown rows re-hardened so a single long word cannot escape the budget. */
|
|
27715
27936
|
function markdownLines(text, columns) {
|
|
27716
27937
|
const width = Math.max(1, Math.floor(columns));
|
|
@@ -27728,68 +27949,517 @@ function reasoningLines(text, columns) {
|
|
|
27728
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] }));
|
|
27729
27950
|
}
|
|
27730
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
|
+
*/
|
|
27731
27958
|
function toolDetailLines(detail, columns) {
|
|
27732
27959
|
switch (detail.kind) {
|
|
27733
|
-
case "diff": return detail.diffs.flatMap((diff) => [...
|
|
27734
|
-
|
|
27735
|
-
|
|
27736
|
-
|
|
27737
|
-
|
|
27738
|
-
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"))];
|
|
27739
|
-
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")];
|
|
27740
|
-
case "web-fetch": return textLines(` ${detail.url} · HTTP ${detail.statusCode}`, columns, "dim");
|
|
27741
|
-
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")];
|
|
27742
27965
|
default: return detail;
|
|
27743
27966
|
}
|
|
27744
27967
|
}
|
|
27745
27968
|
/**
|
|
27746
27969
|
* Convert one durable transcript entry to its complete scrollable row model.
|
|
27747
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.
|
|
27748
27973
|
*/
|
|
27749
|
-
function transcriptEntryLines(entry, columns) {
|
|
27974
|
+
function transcriptEntryLines(entry, columns, showReasoning = true, reasoningToggleHint = true) {
|
|
27750
27975
|
const width = Math.max(1, Math.floor(columns));
|
|
27751
27976
|
switch (entry.kind) {
|
|
27752
|
-
case "user": return
|
|
27753
|
-
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");
|
|
27754
27979
|
case "assistant": {
|
|
27755
|
-
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");
|
|
27756
27981
|
const body = markdownLines(entry.text, Math.max(10, width - 2)).map((line) => ({ segments: [{
|
|
27757
27982
|
text: " ",
|
|
27758
27983
|
style: "plain"
|
|
27759
27984
|
}, ...line.segments] }));
|
|
27760
|
-
|
|
27985
|
+
const interrupted = entry.interrupted === true ? textLines(" ⏹ interrupted", width, "dim") : [];
|
|
27986
|
+
return [
|
|
27987
|
+
...reasoning,
|
|
27988
|
+
...body,
|
|
27989
|
+
...interrupted
|
|
27990
|
+
];
|
|
27761
27991
|
}
|
|
27762
27992
|
case "tool": {
|
|
27763
27993
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
27764
27994
|
const markStyle = entry.state === "running" ? "brand" : entry.state === "error" ? "error" : "success";
|
|
27995
|
+
const summaryStyle = entry.state === "error" ? "error" : "dim";
|
|
27765
27996
|
return [
|
|
27766
|
-
...
|
|
27767
|
-
lineSegment(
|
|
27997
|
+
...hangingStyledLines([
|
|
27998
|
+
lineSegment(`[${entry.ordinal}] `, "dim"),
|
|
27768
27999
|
lineSegment(entry.name, "brand"),
|
|
27769
28000
|
lineSegment(entry.preview === "" ? "" : ` ${entry.preview}`, "dim")
|
|
27770
|
-
], width),
|
|
27771
|
-
...entry.prompt === "" ? [] :
|
|
27772
|
-
...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),
|
|
27773
28004
|
...entry.detail === void 0 ? [] : toolDetailLines(entry.detail, width)
|
|
27774
28005
|
];
|
|
27775
28006
|
}
|
|
27776
28007
|
case "command": {
|
|
27777
28008
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
27778
28009
|
const markStyle = entry.state === "running" ? "brand" : entry.state === "error" ? "error" : "success";
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
lineSegment(`/${entry.name}`, "brand"),
|
|
27782
|
-
lineSegment(entry.args === "" ? "" : ` ${entry.args}`, "dim")
|
|
27783
|
-
], 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)];
|
|
27784
28012
|
}
|
|
27785
28013
|
case "turn-marker": return textLines(` ⏹ ${entry.text}`, width, "dim");
|
|
27786
28014
|
case "compaction": return textLines(entry.ok ? ` ⧉ compacted ~${formatTokens(entry.tokens)} tokens` : ` ⧉ compaction failed: ${entry.error}`, width, "dim");
|
|
27787
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");
|
|
27788
|
-
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", " "))];
|
|
27789
28017
|
case "error": return textLines(entry.text, width, "error");
|
|
27790
28018
|
default: return entry;
|
|
27791
28019
|
}
|
|
27792
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
|
+
}
|
|
27793
28463
|
//#endregion
|
|
27794
28464
|
//#region src/kernel-panels.ts
|
|
27795
28465
|
/** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
|
|
@@ -27848,9 +28518,15 @@ function ListFrame(props) {
|
|
|
27848
28518
|
wrap: "truncate-end"
|
|
27849
28519
|
}, truncateColumns(singleLineText(props.footer), viewport.contentColumns)));
|
|
27850
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
|
+
*/
|
|
27851
28526
|
function editQuery(query, input, key) {
|
|
27852
|
-
if (key.backspace || key.delete) return query
|
|
27853
|
-
|
|
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;
|
|
27854
28530
|
}
|
|
27855
28531
|
function ModePanel({ current, load, select, close }) {
|
|
27856
28532
|
const [rows, setRows] = (0, import_react.useState)([]);
|
|
@@ -27975,6 +28651,57 @@ function PluginPanel({ load, close, initialQuery = "" }) {
|
|
|
27975
28651
|
footer: "↑↓ inspect · enter details · r refresh · esc close"
|
|
27976
28652
|
});
|
|
27977
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
|
+
}
|
|
27978
28705
|
function ResumePanel({ currentCwd, load, readTranscript, select, requestDelete, deleteConfirmId, reloadToken = 0, deleteMode = false, close }) {
|
|
27979
28706
|
const [options, setOptions] = (0, import_react.useState)({
|
|
27980
28707
|
sessions: "roots",
|
|
@@ -28122,11 +28849,42 @@ function ResumePanel({ currentCwd, load, readTranscript, select, requestDelete,
|
|
|
28122
28849
|
footer: "tab/←→ filters · ↑↓/pg navigate · ctrl+f search · e details · t transcript · d delete · enter resume"
|
|
28123
28850
|
});
|
|
28124
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
|
+
}
|
|
28125
28883
|
function DocumentPanel({ title, text, error, close }) {
|
|
28126
28884
|
const stdout = useStdout().stdout;
|
|
28127
28885
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
28128
28886
|
const [scroll, setScroll] = (0, import_react.useState)(0);
|
|
28129
|
-
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]);
|
|
28130
28888
|
useInput((input, key) => {
|
|
28131
28889
|
if (key.escape || input === "q" || input === "t") return close();
|
|
28132
28890
|
if (key.upArrow) return setScroll((value) => Math.max(0, value - 1));
|
|
@@ -28137,7 +28895,7 @@ function DocumentPanel({ title, text, error, close }) {
|
|
|
28137
28895
|
if (input === "G") return setScroll(Math.max(0, lines.length - viewport.bodyRows));
|
|
28138
28896
|
});
|
|
28139
28897
|
if (viewport.compact) return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns("transcript · esc close", viewport.contentColumns));
|
|
28140
|
-
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);
|
|
28141
28899
|
return (0, import_react.createElement)(Box, {
|
|
28142
28900
|
width: viewport.outerColumns,
|
|
28143
28901
|
borderStyle: "round",
|
|
@@ -28147,10 +28905,7 @@ function DocumentPanel({ title, text, error, close }) {
|
|
|
28147
28905
|
}, (0, import_react.createElement)(Text, {
|
|
28148
28906
|
color: inkColor(getPalette().brandBright),
|
|
28149
28907
|
wrap: "truncate-end"
|
|
28150
|
-
}, truncateColumns(singleLineText(title), viewport.contentColumns)),
|
|
28151
|
-
key: `${scroll}-${index}`,
|
|
28152
|
-
wrap: "truncate-end"
|
|
28153
|
-
}, 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, {
|
|
28154
28909
|
dimColor: true,
|
|
28155
28910
|
wrap: "truncate-end"
|
|
28156
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)));
|
|
@@ -28177,13 +28932,16 @@ function HistoryPanel({ entries, fill, close }) {
|
|
|
28177
28932
|
if (input === "g") return setCursor(0);
|
|
28178
28933
|
if (input === "G") return setCursor(matches.length - 1);
|
|
28179
28934
|
if (key.backspace) {
|
|
28180
|
-
setQuery((current) => current
|
|
28935
|
+
setQuery((current) => deleteLastGrapheme(current));
|
|
28181
28936
|
setCursor(0);
|
|
28182
28937
|
return;
|
|
28183
28938
|
}
|
|
28184
28939
|
if (input !== "" && !key.ctrl && !key.meta && !key.shift) {
|
|
28185
|
-
|
|
28186
|
-
|
|
28940
|
+
const text = stripPasteMarkers(input);
|
|
28941
|
+
if (text !== "") {
|
|
28942
|
+
setQuery((current) => (current + text).slice(0, 120));
|
|
28943
|
+
setCursor(0);
|
|
28944
|
+
}
|
|
28187
28945
|
}
|
|
28188
28946
|
});
|
|
28189
28947
|
const stdout = useStdout().stdout;
|
|
@@ -28614,6 +29372,15 @@ function recallEntries(persistent, local) {
|
|
|
28614
29372
|
const localSet = new Set(local);
|
|
28615
29373
|
return [...persistent.filter((entry) => !localSet.has(entry)), ...local].reverse();
|
|
28616
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
|
+
}
|
|
28617
29384
|
/**
|
|
28618
29385
|
* Move one entry older (Up, toward index +1 in the newest-first space). The
|
|
28619
29386
|
* first Up saves the current draft so Down past the newest can restore it
|
|
@@ -28697,8 +29464,32 @@ function recallNewer(state) {
|
|
|
28697
29464
|
*/
|
|
28698
29465
|
/** Match Codex's settled-resize window before rebuilding terminal scrollback. */
|
|
28699
29466
|
const RESIZE_REFLOW_DELAY_MS = 75;
|
|
28700
|
-
/**
|
|
28701
|
-
|
|
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";
|
|
28702
29493
|
/** Pad text with spaces to a visible-column target (menu name column). */
|
|
28703
29494
|
function padColumns(text, width) {
|
|
28704
29495
|
const clipped = truncateColumns(singleLineText(text), width);
|
|
@@ -28729,11 +29520,6 @@ function useStableInput(handler, active) {
|
|
|
28729
29520
|
}, []);
|
|
28730
29521
|
useInput(stableHandler, { isActive: active });
|
|
28731
29522
|
}
|
|
28732
|
-
/** Single-cell stepped pulse: the web's 125ms flat-hold brightness steps over 1s. */
|
|
28733
|
-
function Pulse() {
|
|
28734
|
-
const tick = useFrames(125);
|
|
28735
|
-
return (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright) }, pulseFrame(tick));
|
|
28736
|
-
}
|
|
28737
29523
|
/**
|
|
28738
29524
|
* The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
|
|
28739
29525
|
* ring trail clockwise around the eight outer positions (8 frames × 125ms =
|
|
@@ -28754,13 +29540,6 @@ function CursorBlock({ char }) {
|
|
|
28754
29540
|
const tick = useFrames(530);
|
|
28755
29541
|
return (0, import_react.createElement)(Text, { inverse: caretVisible(tick) || void 0 }, char);
|
|
28756
29542
|
}
|
|
28757
|
-
/** Web TurnStatus elapsed format: `45s` under a minute, `2m03s` beyond. */
|
|
28758
|
-
function runClock(ms) {
|
|
28759
|
-
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
28760
|
-
const minutes = Math.floor(total / 60);
|
|
28761
|
-
const seconds = total % 60;
|
|
28762
|
-
return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
|
|
28763
|
-
}
|
|
28764
29543
|
/**
|
|
28765
29544
|
* The busy line, web TurnStatus contract: the StateDot chase leads the plain
|
|
28766
29545
|
* `Deep diving...` label, with the elapsed clock appended only once the turn
|
|
@@ -28784,7 +29563,7 @@ function StreamTail({ text, dim, maxRows, prefix = "", continuationPrefix = pref
|
|
|
28784
29563
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
28785
29564
|
const safeRows = Math.max(1, maxRows);
|
|
28786
29565
|
const prefixColumns = Math.max(visibleColumns(prefix), visibleColumns(continuationPrefix));
|
|
28787
|
-
const contentColumns = Math.max(10, columns -
|
|
29566
|
+
const contentColumns = Math.max(10, columns - 1 - prefixColumns);
|
|
28788
29567
|
const initial = displayTail(text, contentColumns, safeRows);
|
|
28789
29568
|
const tail = initial.truncated && safeRows > 1 ? displayTail(text, contentColumns, safeRows - 1) : initial;
|
|
28790
29569
|
const rows = tail.text.split("\n");
|
|
@@ -28906,138 +29685,80 @@ function StyledRows({ lines }) {
|
|
|
28906
29685
|
...lineStyleProps(segment.style)
|
|
28907
29686
|
}, segment.text)))));
|
|
28908
29687
|
}
|
|
28909
|
-
/**
|
|
28910
|
-
function
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
|
|
28914
|
-
|
|
28915
|
-
const
|
|
28916
|
-
const lines = (0, import_react.useMemo)(() =>
|
|
28917
|
-
|
|
28918
|
-
|
|
28919
|
-
|
|
28920
|
-
|
|
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));
|
|
28921
29716
|
return (0, import_react.createElement)(Box, {
|
|
28922
29717
|
flexDirection: "column",
|
|
28923
|
-
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
|
|
28934
|
-
}
|
|
28935
|
-
/**
|
|
28936
|
-
* One expanded tool-card body for the verbose transcript (Ctrl+O): the
|
|
28937
|
-
* presentation contract's structured cards — inline diffs, read windows,
|
|
28938
|
-
* web sources — rendered as plain terminal rows, degradation-safe against
|
|
28939
|
-
* replayed metadata.
|
|
28940
|
-
*/
|
|
28941
|
-
function ToolDetailBody({ detail }) {
|
|
28942
|
-
switch (detail.kind) {
|
|
28943
|
-
case "diff": return (0, import_react.createElement)(Box, { flexDirection: "column" }, ...detail.diffs.map((diff, index) => (0, import_react.createElement)(Box, {
|
|
28944
|
-
key: index,
|
|
28945
|
-
flexDirection: "column"
|
|
28946
|
-
}, (0, import_react.createElement)(Text, {
|
|
28947
|
-
dimColor: true,
|
|
28948
|
-
wrap: "truncate-end"
|
|
28949
|
-
}, ` ── ${displayText(diff.path)}${diff.truncated ? " (diff truncated)" : ""}`), ...diff.lines.map((line, at) => (0, import_react.createElement)(Text, {
|
|
28950
|
-
key: at,
|
|
28951
|
-
color: line.mark === "+" ? inkColor(getPalette().success) : line.mark === "-" ? inkColor(getPalette().error) : inkColor(getPalette().dim),
|
|
28952
|
-
wrap: "truncate-end"
|
|
28953
|
-
}, ` ${line.mark}${displayText(line.text)}`)))));
|
|
28954
|
-
case "read": return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(Text, {
|
|
28955
|
-
dimColor: true,
|
|
28956
|
-
wrap: "truncate-end"
|
|
28957
|
-
}, ` ── ${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, {
|
|
28958
|
-
key: at,
|
|
28959
|
-
dimColor: true,
|
|
28960
|
-
wrap: "truncate-end"
|
|
28961
|
-
}, ` ${String(line.number).padStart(5, " ")} | ${displayText(line.text)}`)));
|
|
28962
|
-
case "web-search": return (0, import_react.createElement)(Box, { flexDirection: "column" }, ...detail.sources.map((source, at) => (0, import_react.createElement)(Text, {
|
|
28963
|
-
key: at,
|
|
28964
|
-
wrap: "truncate-end"
|
|
28965
|
-
}, 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)" : ""}`)));
|
|
28966
|
-
case "web-fetch": return (0, import_react.createElement)(Text, {
|
|
28967
|
-
dimColor: true,
|
|
28968
|
-
wrap: "truncate-end"
|
|
28969
|
-
}, dim(` ${displayText(detail.url)} · HTTP ${detail.statusCode}`));
|
|
28970
|
-
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, {
|
|
28971
|
-
key: at,
|
|
28972
|
-
dimColor: true,
|
|
28973
|
-
wrap: "truncate-end"
|
|
28974
|
-
}, ` ${line}`)), (0, import_react.createElement)(Text, { dimColor: true }, detail.truncated ? " … (output truncated)" : " (end of output)"));
|
|
28975
|
-
default: return assertNever(detail, "tool detail kind");
|
|
28976
|
-
}
|
|
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)));
|
|
28977
29729
|
}
|
|
28978
|
-
/**
|
|
28979
|
-
function
|
|
28980
|
-
|
|
28981
|
-
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));
|
|
28982
|
-
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, {
|
|
28983
|
-
text: entry.text,
|
|
28984
|
-
indent: 2
|
|
28985
|
-
}));
|
|
28986
|
-
case "tool": {
|
|
28987
|
-
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) }, "⏺");
|
|
28988
|
-
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, {
|
|
28989
|
-
color: entry.state === "error" ? inkColor(getPalette().error) : inkColor(getPalette().dim),
|
|
28990
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28991
|
-
}, ` ⎿ ${displayText(entry.summary)}`), verbose && entry.detail !== void 0 ? (0, import_react.createElement)(ToolDetailBody, { detail: entry.detail }) : void 0);
|
|
28992
|
-
}
|
|
28993
|
-
case "command": {
|
|
28994
|
-
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) }, "⏺");
|
|
28995
|
-
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, {
|
|
28996
|
-
color: inkColor(getPalette().dim),
|
|
28997
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
28998
|
-
}, ` ⎿ ${displayText(entry.summary)}`));
|
|
28999
|
-
}
|
|
29000
|
-
case "turn-marker": return (0, import_react.createElement)(Text, {
|
|
29001
|
-
dimColor: true,
|
|
29002
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
29003
|
-
}, ` ⏹ ${displayText(entry.text)}`);
|
|
29004
|
-
case "compaction": return (0, import_react.createElement)(Text, {
|
|
29005
|
-
dimColor: true,
|
|
29006
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
29007
|
-
}, entry.ok ? ` ⧉ compacted ~${formatTokens(entry.tokens)} tokens` : ` ⧉ compaction failed: ${displayText(entry.error)}`);
|
|
29008
|
-
case "retry": return (0, import_react.createElement)(Text, {
|
|
29009
|
-
color: entry.state === "running" ? inkColor(getPalette().warn) : inkColor(getPalette().dim),
|
|
29010
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
29011
|
-
}, ` ↻ retry ${entry.attempt}/${entry.max} · ${displayText(entry.code)} · ${Math.round(entry.delayMs / 100) / 10}s`);
|
|
29012
|
-
case "files": {
|
|
29013
|
-
const shown = entry.paths.slice(0, 3).map((path) => displayText(path)).join(" · ");
|
|
29014
|
-
const more = entry.paths.length > 3 ? ` (+${entry.paths.length - 3} more)` : "";
|
|
29015
|
-
return (0, import_react.createElement)(Text, {
|
|
29016
|
-
dimColor: true,
|
|
29017
|
-
wrap: verbose ? "truncate-end" : void 0
|
|
29018
|
-
}, ` ⎄ ${shown}${more}`);
|
|
29019
|
-
}
|
|
29020
|
-
case "pending": return (0, import_react.createElement)(Text, { wrap: verbose ? "truncate-end" : void 0 }, brand("❯ "), displayText(entry.text));
|
|
29021
|
-
case "error": return (0, import_react.createElement)(Text, { wrap: verbose ? "truncate-end" : void 0 }, error(displayText(entry.text)));
|
|
29022
|
-
default: return assertNever(entry, "transcript entry kind");
|
|
29023
|
-
}
|
|
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;
|
|
29024
29733
|
}
|
|
29025
29734
|
/**
|
|
29026
|
-
* The whale header with a compact
|
|
29027
|
-
*
|
|
29028
|
-
*
|
|
29029
|
-
*
|
|
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.
|
|
29030
29741
|
*/
|
|
29031
29742
|
function Header({ resumed }) {
|
|
29032
29743
|
const stdout = useStdout().stdout;
|
|
29033
29744
|
const rows = stdout?.rows ?? 40;
|
|
29034
29745
|
const columns = stdout?.columns ?? 80;
|
|
29746
|
+
const kernelLine = (() => {
|
|
29747
|
+
const version = dshKernelVersion();
|
|
29748
|
+
return version === void 0 ? void 0 : `dsh-v${version}`;
|
|
29749
|
+
})();
|
|
29035
29750
|
const title = `DeepSeek Harness · v${DSH_CODE_VERSION}`;
|
|
29036
29751
|
const slogan = "Into the Unknown 探索未至之境";
|
|
29037
29752
|
const hint = resumed ? "resumed · /help · Esc interrupt" : "/help · Esc interrupt · Ctrl+C quit";
|
|
29038
|
-
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);
|
|
29039
29760
|
const compact = `${title} · ${hint}`;
|
|
29040
|
-
if (rows < 20 || columns < 26 + copyColumns +
|
|
29761
|
+
if (rows < 20 || columns < 26 + copyColumns + 10) return (0, import_react.createElement)(Box, {
|
|
29041
29762
|
width: Math.max(1, columns - 1),
|
|
29042
29763
|
borderStyle: "round",
|
|
29043
29764
|
borderColor: inkColor(getPalette().brand),
|
|
@@ -29052,7 +29773,7 @@ function Header({ resumed }) {
|
|
|
29052
29773
|
gap: 2,
|
|
29053
29774
|
borderStyle: "round",
|
|
29054
29775
|
borderColor: inkColor(getPalette().brand),
|
|
29055
|
-
paddingX:
|
|
29776
|
+
paddingX: 2,
|
|
29056
29777
|
alignSelf: "flex-start"
|
|
29057
29778
|
}, (0, import_react.createElement)(Box, {
|
|
29058
29779
|
flexDirection: "column",
|
|
@@ -29065,7 +29786,10 @@ function Header({ resumed }) {
|
|
|
29065
29786
|
flexDirection: "column",
|
|
29066
29787
|
width: copyColumns,
|
|
29067
29788
|
justifyContent: "center"
|
|
29068
|
-
}, (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, {
|
|
29069
29793
|
color: inkColor(getPalette().brandBright),
|
|
29070
29794
|
bold: true,
|
|
29071
29795
|
wrap: "truncate-end"
|
|
@@ -29109,8 +29833,69 @@ function TodoPanel({ todos }) {
|
|
|
29109
29833
|
color: inkColor(getPalette().brand),
|
|
29110
29834
|
bold: true,
|
|
29111
29835
|
wrap: "truncate-end"
|
|
29112
|
-
}, `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))));
|
|
29113
29897
|
}
|
|
29898
|
+
const MemoTodoListPanel = (0, import_react.memo)(TodoListPanel);
|
|
29114
29899
|
/**
|
|
29115
29900
|
* Ink props for one status tone: the Codex status-line accent mapping over
|
|
29116
29901
|
* the DeepSeek palette, all blue by design — the status bar speaks only in
|
|
@@ -29193,7 +29978,7 @@ function statusToneProps(tone) {
|
|
|
29193
29978
|
* truncation degrades groups, it never wraps a row.
|
|
29194
29979
|
*
|
|
29195
29980
|
* The DeepSeek easter egg: when the model label *switches* to an official
|
|
29196
|
-
* DeepSeek route, the composer's INPUT ROW (
|
|
29981
|
+
* DeepSeek route, the composer's INPUT ROW (the band's middle) plays Codex's
|
|
29197
29982
|
* effort-ignition "Wave" — a blue crest sweeping the content row column by
|
|
29198
29983
|
* column, with the `· ✦ ✧` sparkles on the deepseek tier — and the prompt
|
|
29199
29984
|
* marker keeps the tier accent afterwards. The border stays a constant
|
|
@@ -29219,10 +30004,28 @@ function deepseekWaveHues(tier) {
|
|
|
29219
30004
|
];
|
|
29220
30005
|
}
|
|
29221
30006
|
function StatusLine({ facts, stats, busy, columns, items }) {
|
|
29222
|
-
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), {
|
|
29223
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,
|
|
30025
|
+
busy,
|
|
30026
|
+
columns,
|
|
29224
30027
|
items
|
|
29225
|
-
|
|
30028
|
+
]);
|
|
29226
30029
|
const renderRow = (row, key, indent = 0) => {
|
|
29227
30030
|
const leftParts = [];
|
|
29228
30031
|
row.left.forEach((group, groupIndex) => {
|
|
@@ -29591,10 +30394,13 @@ function QuestionBar({ store, snapshot, locked }) {
|
|
|
29591
30394
|
returnToOptions();
|
|
29592
30395
|
return;
|
|
29593
30396
|
}
|
|
29594
|
-
setCustom((current) => current
|
|
30397
|
+
setCustom((current) => deleteLastGrapheme(current));
|
|
29595
30398
|
return;
|
|
29596
30399
|
}
|
|
29597
|
-
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
|
+
}
|
|
29598
30404
|
return;
|
|
29599
30405
|
}
|
|
29600
30406
|
if (key.upArrow) {
|
|
@@ -29761,7 +30567,7 @@ function providerStateLabel(row) {
|
|
|
29761
30567
|
return `${route} · ${row.configured ? "provider auth" : "not configured"}`;
|
|
29762
30568
|
}
|
|
29763
30569
|
/** The provider-management stage reached from /model with `a`. */
|
|
29764
|
-
function ProviderPanel({ directory, error, onCredential, onUnset, onRemove, onRetry, onBack }) {
|
|
30570
|
+
function ProviderPanel({ directory, error, onCredential, onConfigure, onUnset, onRemove, onRetry, onBack }) {
|
|
29765
30571
|
const stdout = useStdout().stdout;
|
|
29766
30572
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
29767
30573
|
const rows = directory?.rows ?? [];
|
|
@@ -29807,6 +30613,11 @@ function ProviderPanel({ directory, error, onCredential, onUnset, onRemove, onRe
|
|
|
29807
30613
|
}
|
|
29808
30614
|
const target = rows[cursor];
|
|
29809
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
|
+
}
|
|
29810
30621
|
if (input === "d") {
|
|
29811
30622
|
const facts = target.credential;
|
|
29812
30623
|
if (facts?.kind !== "facts" || !facts.configured) setActionError("this provider has no configured API key to remove");
|
|
@@ -29878,7 +30689,143 @@ function ProviderPanel({ directory, error, onCredential, onUnset, onRemove, onRe
|
|
|
29878
30689
|
}), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(Text, {
|
|
29879
30690
|
color: inkColor(getPalette().dim),
|
|
29880
30691
|
wrap: "truncate-end"
|
|
29881
|
-
}, 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)));
|
|
29882
30829
|
}
|
|
29883
30830
|
/** Write-only masked API-key editor; the secret lives only in this mounted component. */
|
|
29884
30831
|
function ProviderCredentialPanel({ target, save, done, back }) {
|
|
@@ -29921,7 +30868,7 @@ function ProviderCredentialPanel({ target, save, done, back }) {
|
|
|
29921
30868
|
return;
|
|
29922
30869
|
}
|
|
29923
30870
|
if (key.ctrl || key.meta || input.length === 0) return;
|
|
29924
|
-
const next = draft + input;
|
|
30871
|
+
const next = draft + stripPasteMarkers(input);
|
|
29925
30872
|
if (next.length > 4096) {
|
|
29926
30873
|
setError("API key input is too long");
|
|
29927
30874
|
return;
|
|
@@ -30060,7 +31007,7 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
30060
31007
|
key: "key-mentions",
|
|
30061
31008
|
dimColor: true,
|
|
30062
31009
|
wrap: "truncate-end"
|
|
30063
|
-
}, "
|
|
31010
|
+
}, " @ mentions workspace files and sessions"),
|
|
30064
31011
|
(0, import_react.createElement)(Text, {
|
|
30065
31012
|
key: "key-inspector",
|
|
30066
31013
|
dimColor: true,
|
|
@@ -30098,17 +31045,23 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
30098
31045
|
(0, import_react.createElement)(Box, { key: "local-mode" }, row("/mode", "inspect or select the agent preset (/mode [preset])")),
|
|
30099
31046
|
(0, import_react.createElement)(Box, { key: "local-permission" }, row("/permission", "inspect or select the permission preset (/permission [preset])")),
|
|
30100
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])")),
|
|
30101
31049
|
(0, import_react.createElement)(Box, { key: "local-resume" }, row("/resume", "browse or switch root sessions (/resume [id|prefix])")),
|
|
30102
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")),
|
|
30103
31052
|
(0, import_react.createElement)(Box, { key: "local-statusline" }, row("/statusline", "customize the status line items")),
|
|
30104
31053
|
(0, import_react.createElement)(Box, { key: "local-theme" }, row("/theme", "switch the color theme")),
|
|
30105
31054
|
(0, import_react.createElement)(Box, { key: "local-history" }, row("/history", "search and recall past prompts")),
|
|
30106
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")),
|
|
30107
31057
|
(0, import_react.createElement)(Box, { key: "local-subagent" }, row("/subagent", "choose the model delegated subagents run on")),
|
|
30108
31058
|
(0, import_react.createElement)(Box, { key: "local-delete" }, row("/delete", "delete a session and its subagent threads")),
|
|
30109
31059
|
(0, import_react.createElement)(Box, { key: "local-clear" }, row("/clear", "clear the screen")),
|
|
30110
31060
|
(0, import_react.createElement)(Box, { key: "local-export" }, row("/export", "export the transcript to markdown (/export [path])")),
|
|
30111
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")),
|
|
30112
31065
|
(0, import_react.createElement)(Box, { key: "local-quit" }, row("/quit", "exit")),
|
|
30113
31066
|
...descriptors.map((descriptor) => (0, import_react.createElement)(Text, {
|
|
30114
31067
|
key: `command-${descriptor.name}`,
|
|
@@ -30171,19 +31124,47 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
30171
31124
|
function verboseLine(text, columns) {
|
|
30172
31125
|
return truncateColumns(displayText(text).replace(/\n/gu, " ↵ ").replace(/\t/gu, " "), Math.max(1, columns));
|
|
30173
31126
|
}
|
|
30174
|
-
/**
|
|
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
|
+
*/
|
|
30175
31154
|
function editorWindow(value, cursor, columns) {
|
|
30176
31155
|
const width = Math.max(1, columns);
|
|
30177
31156
|
const normalize = (text) => displayText(text).replace(/\n/gu, "↵").replace(/\t/gu, " ");
|
|
30178
|
-
const
|
|
30179
|
-
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);
|
|
30180
31161
|
const remaining = Math.max(0, width - visibleColumns(caret));
|
|
30181
|
-
const afterBudget = Math.min(Math.floor(remaining / 3), visibleColumns(normalize(
|
|
31162
|
+
const afterBudget = Math.min(Math.floor(remaining / 3), visibleColumns(normalize(rest)));
|
|
30182
31163
|
const beforeBudget = Math.max(0, remaining - afterBudget);
|
|
30183
31164
|
return {
|
|
30184
|
-
before: beforeBudget === 0 ? "" : displayTail(normalize(value.slice(0,
|
|
31165
|
+
before: beforeBudget === 0 ? "" : displayTail(normalize(value.slice(0, site)), beforeBudget, 1).text,
|
|
30185
31166
|
caret,
|
|
30186
|
-
after: afterBudget === 0 ? "" : truncateColumns(normalize(
|
|
31167
|
+
after: afterBudget === 0 ? "" : truncateColumns(normalize(rest), afterBudget)
|
|
30187
31168
|
};
|
|
30188
31169
|
}
|
|
30189
31170
|
/** The empty-composer placeholder text (shared by the static and wave paths). */
|
|
@@ -30382,6 +31363,11 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30382
31363
|
description: "start a fresh session",
|
|
30383
31364
|
origin: "command"
|
|
30384
31365
|
},
|
|
31366
|
+
{
|
|
31367
|
+
label: "/fork",
|
|
31368
|
+
description: "fork at a completed turn",
|
|
31369
|
+
origin: "command"
|
|
31370
|
+
},
|
|
30385
31371
|
{
|
|
30386
31372
|
label: "/resume",
|
|
30387
31373
|
description: "browse or switch sessions",
|
|
@@ -30392,6 +31378,11 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30392
31378
|
description: "inspect the plugin composition",
|
|
30393
31379
|
origin: "command"
|
|
30394
31380
|
},
|
|
31381
|
+
{
|
|
31382
|
+
label: "/jobs",
|
|
31383
|
+
description: "inspect background jobs",
|
|
31384
|
+
origin: "command"
|
|
31385
|
+
},
|
|
30395
31386
|
{
|
|
30396
31387
|
label: "/statusline",
|
|
30397
31388
|
description: "customize the status line",
|
|
@@ -30412,6 +31403,11 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30412
31403
|
description: "inspect subagent sessions of this conversation",
|
|
30413
31404
|
origin: "command"
|
|
30414
31405
|
},
|
|
31406
|
+
{
|
|
31407
|
+
label: "/todos",
|
|
31408
|
+
description: "inspect the full todo list",
|
|
31409
|
+
origin: "command"
|
|
31410
|
+
},
|
|
30415
31411
|
{
|
|
30416
31412
|
label: "/subagent",
|
|
30417
31413
|
description: "choose the model delegated subagents run on",
|
|
@@ -30437,6 +31433,21 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30437
31433
|
description: "rename this session",
|
|
30438
31434
|
origin: "command"
|
|
30439
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
|
+
},
|
|
30440
31451
|
{
|
|
30441
31452
|
label: "/quit",
|
|
30442
31453
|
description: "exit",
|
|
@@ -30472,7 +31483,7 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
30472
31483
|
}
|
|
30473
31484
|
/**
|
|
30474
31485
|
* The completion menu, rendered inside the composer's subtree directly above
|
|
30475
|
-
* the
|
|
31486
|
+
* the composer band — attached the way Claude-Code anchors its dropdown. Opening
|
|
30476
31487
|
* it grows the stack downward: the composer stays the last element on screen
|
|
30477
31488
|
* and everything above (the flushed static transcript, the status line) never
|
|
30478
31489
|
* moves. Props-only (no lifted state): the menu is a pure view of the input
|
|
@@ -30485,7 +31496,7 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
30485
31496
|
const terminalRows = stdout?.rows ?? 30;
|
|
30486
31497
|
if (!active) return void 0;
|
|
30487
31498
|
const contentColumns = Math.max(1, columns - 4);
|
|
30488
|
-
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);
|
|
30489
31500
|
const descBudget = Math.max(0, contentColumns - nameWidth - 2);
|
|
30490
31501
|
const showFooter = terminalRows >= 12;
|
|
30491
31502
|
const verticalPadding = terminalRows >= 14 ? 1 : 0;
|
|
@@ -30523,26 +31534,32 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
30523
31534
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
30524
31535
|
* box passes every key through untouched.
|
|
30525
31536
|
*/
|
|
30526
|
-
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 }) {
|
|
30527
31538
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
31539
|
+
const stdin = useStdin().stdin;
|
|
30528
31540
|
const [value, setValue] = (0, import_react.useState)("");
|
|
30529
31541
|
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
30530
|
-
const
|
|
30531
|
-
|
|
30532
|
-
|
|
30533
|
-
|
|
30534
|
-
|
|
30535
|
-
|
|
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([], ""));
|
|
30536
31551
|
(0, import_react.useEffect)(() => {
|
|
30537
31552
|
if (historyFill === void 0) return;
|
|
30538
|
-
|
|
30539
|
-
|
|
31553
|
+
const safe = sanitizeDraftText(historyFill.text);
|
|
31554
|
+
setValue(safe);
|
|
31555
|
+
setCursor(safe.length);
|
|
31556
|
+
preferredColumnRef.current = null;
|
|
30540
31557
|
setDismissedMenuValue(void 0);
|
|
30541
31558
|
recall.current = {
|
|
30542
31559
|
entries: recallSpace,
|
|
30543
31560
|
index: historyFill.index,
|
|
30544
|
-
savedDraft:
|
|
30545
|
-
lastRecalled:
|
|
31561
|
+
savedDraft: safe,
|
|
31562
|
+
lastRecalled: safe
|
|
30546
31563
|
};
|
|
30547
31564
|
historyConsumed();
|
|
30548
31565
|
}, [
|
|
@@ -30550,6 +31567,20 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30550
31567
|
recallSpace,
|
|
30551
31568
|
historyConsumed
|
|
30552
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]);
|
|
30553
31584
|
if (recall.current.entries !== recallSpace) {
|
|
30554
31585
|
const index = recall.current.index === null || recall.current.index < recallSpace.length ? recall.current.index : null;
|
|
30555
31586
|
recall.current = {
|
|
@@ -30571,27 +31602,6 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30571
31602
|
};
|
|
30572
31603
|
const mentionActive = mentionToken !== void 0;
|
|
30573
31604
|
const [mentionRows, setMentionRows] = (0, import_react.useState)([]);
|
|
30574
|
-
const bareTokenMatch = /([^\s]+)$/u.exec(lastLine);
|
|
30575
|
-
const bareToken = bareTokenMatch === null ? "" : bareTokenMatch[1] ?? "";
|
|
30576
|
-
const pathActive = !mentionActive && !bareToken.startsWith("/") && (bareToken.includes("/") || bareToken === "." || bareToken === "..");
|
|
30577
|
-
const pathTokenStart = beforeCursor.length - bareToken.length;
|
|
30578
|
-
const [pathRows, setPathRows] = (0, import_react.useState)([]);
|
|
30579
|
-
(0, import_react.useEffect)(() => {
|
|
30580
|
-
if (!active || !pathActive) {
|
|
30581
|
-
setPathRows([]);
|
|
30582
|
-
return;
|
|
30583
|
-
}
|
|
30584
|
-
const controller = new AbortController();
|
|
30585
|
-
setPathRows([]);
|
|
30586
|
-
loadMentions(bareToken, controller.signal).then((rows) => setPathRows(rows.filter((row) => row.kind !== "session")), () => {});
|
|
30587
|
-
return () => {
|
|
30588
|
-
controller.abort();
|
|
30589
|
-
};
|
|
30590
|
-
}, [
|
|
30591
|
-
active,
|
|
30592
|
-
pathActive,
|
|
30593
|
-
bareToken
|
|
30594
|
-
]);
|
|
30595
31605
|
(0, import_react.useEffect)(() => {
|
|
30596
31606
|
if (!active || !mentionActive) {
|
|
30597
31607
|
setMentionRows([]);
|
|
@@ -30608,15 +31618,11 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30608
31618
|
mentionActive,
|
|
30609
31619
|
mentionToken?.query
|
|
30610
31620
|
]);
|
|
30611
|
-
const menuActive = (slashActive || mentionActive
|
|
31621
|
+
const menuActive = (slashActive || mentionActive) && dismissedMenuValue !== value;
|
|
30612
31622
|
const menuRows = mentionActive ? mentionRows.map((row) => ({
|
|
30613
31623
|
label: row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`,
|
|
30614
31624
|
description: row.description,
|
|
30615
31625
|
origin: "mention"
|
|
30616
|
-
})) : pathActive ? pathRows.map((row) => ({
|
|
30617
|
-
label: row.label,
|
|
30618
|
-
description: row.description,
|
|
30619
|
-
origin: "path"
|
|
30620
31626
|
})) : candidates;
|
|
30621
31627
|
/** Accept the highlighted completion-menu candidate into the draft. */
|
|
30622
31628
|
const acceptMenuCandidate = () => {
|
|
@@ -30627,13 +31633,6 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30627
31633
|
setValue(value.slice(0, mentionToken.start) + insertion + value.slice(cursor));
|
|
30628
31634
|
setCursor(mentionToken.start + insertion.length);
|
|
30629
31635
|
}
|
|
30630
|
-
} else if (pathActive) {
|
|
30631
|
-
const row = pathRows[completionIndex % Math.max(1, pathRows.length)];
|
|
30632
|
-
if (row !== void 0) {
|
|
30633
|
-
const insertion = row.kind === "directory" ? `${row.label}/` : row.label;
|
|
30634
|
-
setValue(value.slice(0, pathTokenStart) + insertion + value.slice(cursor));
|
|
30635
|
-
setCursor(pathTokenStart + insertion.length);
|
|
30636
|
-
}
|
|
30637
31636
|
} else {
|
|
30638
31637
|
const candidate = candidates[completionIndex % candidates.length];
|
|
30639
31638
|
if (candidate !== void 0) {
|
|
@@ -30644,6 +31643,21 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30644
31643
|
setCompletionIndex(0);
|
|
30645
31644
|
setDismissedMenuValue(void 0);
|
|
30646
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
|
+
};
|
|
30647
31661
|
useInput((input, key) => {
|
|
30648
31662
|
if (!active) return;
|
|
30649
31663
|
if (deleteConfirm !== void 0) {
|
|
@@ -30679,6 +31693,10 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30679
31693
|
return;
|
|
30680
31694
|
}
|
|
30681
31695
|
if (key.ctrl && input === "d") {
|
|
31696
|
+
if (value !== "") {
|
|
31697
|
+
applyEdit(deleteForward(value, cursor));
|
|
31698
|
+
return;
|
|
31699
|
+
}
|
|
30682
31700
|
if (busy) notify("cancel the running turn before exiting (Esc or Ctrl+C)", "warning");
|
|
30683
31701
|
else quit();
|
|
30684
31702
|
return;
|
|
@@ -30699,15 +31717,20 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30699
31717
|
cancelQueued(queued[queued.length - 1].messageId);
|
|
30700
31718
|
return;
|
|
30701
31719
|
}
|
|
30702
|
-
|
|
30703
|
-
|
|
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") {
|
|
30704
31727
|
setValue(value.slice(0, cursor) + "\n" + value.slice(cursor));
|
|
30705
31728
|
setCursor(cursor + 1);
|
|
30706
31729
|
setDismissedMenuValue(void 0);
|
|
30707
31730
|
return;
|
|
30708
31731
|
}
|
|
30709
31732
|
if (menuActive) {
|
|
30710
|
-
if (!(!mentionActive &&
|
|
31733
|
+
if (!(!mentionActive && candidates.some((candidate) => candidate.label === value))) {
|
|
30711
31734
|
acceptMenuCandidate();
|
|
30712
31735
|
return;
|
|
30713
31736
|
}
|
|
@@ -30723,12 +31746,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30723
31746
|
recordLocal(text);
|
|
30724
31747
|
recordHistory(text);
|
|
30725
31748
|
}
|
|
30726
|
-
recall.current =
|
|
30727
|
-
entries: recallSpace,
|
|
30728
|
-
index: null,
|
|
30729
|
-
savedDraft: "",
|
|
30730
|
-
lastRecalled: null
|
|
30731
|
-
};
|
|
31749
|
+
recall.current = beginRecall(recallSpace, "");
|
|
30732
31750
|
if (text === "/quit") {
|
|
30733
31751
|
quit();
|
|
30734
31752
|
return;
|
|
@@ -30752,6 +31770,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30752
31770
|
notify(outcome, outcome.startsWith("rename failed:") ? "error" : outcome.startsWith("usage:") || outcome.includes("unavailable") ? "warning" : "info");
|
|
30753
31771
|
return;
|
|
30754
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
|
+
}
|
|
30755
31785
|
if (text === "/model" || text.startsWith("/model ")) {
|
|
30756
31786
|
openModel();
|
|
30757
31787
|
return;
|
|
@@ -30786,10 +31816,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30786
31816
|
createSession(text.slice(4).trim() || void 0);
|
|
30787
31817
|
return;
|
|
30788
31818
|
}
|
|
31819
|
+
if (text === "/fork" || text.startsWith("/fork ")) {
|
|
31820
|
+
forkSession(text.slice(5));
|
|
31821
|
+
return;
|
|
31822
|
+
}
|
|
30789
31823
|
if (text === "/plugin" || text.startsWith("/plugin ")) {
|
|
30790
31824
|
openPlugin(text.slice(7).trim());
|
|
30791
31825
|
return;
|
|
30792
31826
|
}
|
|
31827
|
+
if (text === "/jobs" || text.startsWith("/jobs ")) {
|
|
31828
|
+
openJobs();
|
|
31829
|
+
return;
|
|
31830
|
+
}
|
|
30793
31831
|
if (text === "/statusline") {
|
|
30794
31832
|
openStatusline();
|
|
30795
31833
|
return;
|
|
@@ -30806,6 +31844,10 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30806
31844
|
openAgents();
|
|
30807
31845
|
return;
|
|
30808
31846
|
}
|
|
31847
|
+
if (text === "/todos") {
|
|
31848
|
+
openTodos();
|
|
31849
|
+
return;
|
|
31850
|
+
}
|
|
30809
31851
|
if (text === "/subagent") {
|
|
30810
31852
|
openSubagent();
|
|
30811
31853
|
return;
|
|
@@ -30829,25 +31871,58 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30829
31871
|
setCompletionIndex((index) => (index + 1) % menuRows.length);
|
|
30830
31872
|
return;
|
|
30831
31873
|
}
|
|
30832
|
-
|
|
30833
|
-
|
|
30834
|
-
|
|
30835
|
-
|
|
30836
|
-
if (
|
|
30837
|
-
|
|
30838
|
-
|
|
30839
|
-
|
|
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;
|
|
30840
31903
|
}
|
|
30841
31904
|
return;
|
|
30842
31905
|
}
|
|
30843
|
-
if (key.
|
|
30844
|
-
|
|
30845
|
-
|
|
30846
|
-
|
|
30847
|
-
|
|
30848
|
-
|
|
30849
|
-
|
|
30850
|
-
|
|
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;
|
|
30851
31926
|
}
|
|
30852
31927
|
return;
|
|
30853
31928
|
}
|
|
@@ -30855,51 +31930,88 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30855
31930
|
acceptMenuCandidate();
|
|
30856
31931
|
return;
|
|
30857
31932
|
}
|
|
30858
|
-
if (key.backspace || key.delete) {
|
|
30859
|
-
if (cursor > 0) {
|
|
30860
|
-
setValue(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
30861
|
-
setCursor(cursor - 1);
|
|
30862
|
-
setCompletionIndex(0);
|
|
30863
|
-
setDismissedMenuValue(void 0);
|
|
30864
|
-
}
|
|
30865
|
-
return;
|
|
30866
|
-
}
|
|
30867
31933
|
if (key.leftArrow) {
|
|
30868
|
-
|
|
31934
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordLeft(value, cursor) : moveCursorBy(value, cursor, -1));
|
|
30869
31935
|
return;
|
|
30870
31936
|
}
|
|
30871
31937
|
if (key.rightArrow) {
|
|
30872
|
-
|
|
31938
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordRight(value, cursor) : moveCursorBy(value, cursor, 1));
|
|
30873
31939
|
return;
|
|
30874
31940
|
}
|
|
30875
|
-
if (key.
|
|
30876
|
-
|
|
30877
|
-
setCursor(0);
|
|
30878
|
-
setDismissedMenuValue(void 0);
|
|
31941
|
+
if (key.meta && input === "b") {
|
|
31942
|
+
moveCursorTo(moveWordLeft(value, cursor));
|
|
30879
31943
|
return;
|
|
30880
31944
|
}
|
|
30881
|
-
if (key.
|
|
30882
|
-
|
|
30883
|
-
setDismissedMenuValue(void 0);
|
|
31945
|
+
if (key.meta && input === "f") {
|
|
31946
|
+
moveCursorTo(moveWordRight(value, cursor));
|
|
30884
31947
|
return;
|
|
30885
31948
|
}
|
|
30886
|
-
if (key.ctrl && input === "
|
|
30887
|
-
|
|
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));
|
|
30888
31967
|
return;
|
|
30889
31968
|
}
|
|
30890
31969
|
if (key.ctrl && input === "a") {
|
|
30891
|
-
|
|
31970
|
+
moveCursorTo(lineBounds(value, cursor).start);
|
|
30892
31971
|
return;
|
|
30893
31972
|
}
|
|
30894
31973
|
if (key.ctrl && input === "e") {
|
|
30895
|
-
|
|
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();
|
|
30896
31991
|
return;
|
|
30897
31992
|
}
|
|
30898
31993
|
if (input !== "" && !key.ctrl && !key.meta) {
|
|
30899
|
-
|
|
30900
|
-
|
|
30901
|
-
|
|
30902
|
-
|
|
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));
|
|
30903
32015
|
}
|
|
30904
32016
|
});
|
|
30905
32017
|
const [waveTick, setWaveTick] = (0, import_react.useState)(null);
|
|
@@ -30940,94 +32052,140 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30940
32052
|
const tierHues = waveTier === null ? null : deepseekWaveHues(waveTier);
|
|
30941
32053
|
const promptColor = tierHues === null ? inkColor(getPalette().brand) : inkColor(tierHues[0]);
|
|
30942
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)));
|
|
30943
32074
|
if (frozen) {
|
|
30944
|
-
if (deleteConfirm !== void 0)
|
|
30945
|
-
|
|
30946
|
-
|
|
30947
|
-
|
|
30948
|
-
|
|
30949
|
-
|
|
30950
|
-
|
|
30951
|
-
|
|
30952
|
-
|
|
30953
|
-
|
|
30954
|
-
|
|
30955
|
-
|
|
30956
|
-
|
|
30957
|
-
|
|
30958
|
-
|
|
30959
|
-
|
|
30960
|
-
|
|
30961
|
-
|
|
30962
|
-
}, (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, {
|
|
30963
32093
|
color: promptColor,
|
|
30964
32094
|
bold: tierActive ? true : void 0
|
|
30965
|
-
}, busy ? "… " : `${promptGlyph} `),
|
|
32095
|
+
}, busy ? "… " : `${promptGlyph} `), frozenLine, bandFill(2 + visibleColumns(frozenLine))));
|
|
30966
32096
|
}
|
|
30967
|
-
const frame = (row, borderRgb) => (0, import_react.createElement)(Box, {
|
|
30968
|
-
width: Math.max(1, columns - 1),
|
|
30969
|
-
borderStyle: "round",
|
|
30970
|
-
borderColor: inkColor(borderRgb ?? getPalette().dim),
|
|
30971
|
-
paddingX: 1
|
|
30972
|
-
}, row);
|
|
30973
32097
|
const menu = (0, import_react.createElement)(CompletionMenu, {
|
|
30974
32098
|
active: menuActive,
|
|
30975
32099
|
mention: mentionActive,
|
|
30976
32100
|
index: completionIndex,
|
|
30977
32101
|
rows: menuRows
|
|
30978
32102
|
});
|
|
30979
|
-
const
|
|
30980
|
-
|
|
30981
|
-
|
|
30982
|
-
|
|
30983
|
-
|
|
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);
|
|
30984
32126
|
const waveRow = () => {
|
|
30985
|
-
const contentWidth = Math.max(1, columns - 5);
|
|
30986
|
-
const waveEditor = editorWindow(value, cursor, Math.max(1, contentWidth - 2));
|
|
30987
32127
|
const hues = deepseekWaveHues(waveTier);
|
|
30988
32128
|
const style = waveStyle;
|
|
30989
|
-
const
|
|
30990
|
-
const waveBg = (column) => {
|
|
30991
|
-
const rgb = deepseekWaveColumnBg(waveTick, column,
|
|
30992
|
-
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);
|
|
30993
32133
|
};
|
|
30994
|
-
const
|
|
30995
|
-
|
|
30996
|
-
|
|
30997
|
-
|
|
30998
|
-
|
|
30999
|
-
|
|
31000
|
-
|
|
31001
|
-
|
|
31002
|
-
|
|
31003
|
-
|
|
31004
|
-
|
|
31005
|
-
|
|
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
|
+
];
|
|
31006
32164
|
for (const char of waveEditor.before) cells.push({
|
|
31007
32165
|
char,
|
|
31008
|
-
backgroundColor: waveBg(cells.length)
|
|
32166
|
+
backgroundColor: waveBg(1, cells.length)
|
|
31009
32167
|
});
|
|
31010
32168
|
cells.push({
|
|
31011
32169
|
char: waveEditor.caret,
|
|
31012
32170
|
inverse: true,
|
|
31013
|
-
backgroundColor: waveBg(cells.length)
|
|
32171
|
+
backgroundColor: waveBg(1, cells.length)
|
|
31014
32172
|
});
|
|
31015
32173
|
if (value === "" && !busy) for (let at = 0; at < 40; at += 1) cells.push({
|
|
31016
32174
|
char: COMPOSER_PLACEHOLDER[at],
|
|
31017
32175
|
dim: true,
|
|
31018
|
-
backgroundColor: waveBg(cells.length)
|
|
32176
|
+
backgroundColor: waveBg(1, cells.length)
|
|
31019
32177
|
});
|
|
31020
32178
|
else for (const char of waveEditor.after) cells.push({
|
|
31021
32179
|
char,
|
|
31022
|
-
backgroundColor: waveBg(cells.length)
|
|
32180
|
+
backgroundColor: waveBg(1, cells.length)
|
|
31023
32181
|
});
|
|
31024
|
-
while (cells.length <
|
|
32182
|
+
while (cells.length < bandWidth) cells.push({
|
|
31025
32183
|
char: " ",
|
|
31026
|
-
backgroundColor: waveBg(cells.length)
|
|
32184
|
+
backgroundColor: waveBg(1, cells.length)
|
|
31027
32185
|
});
|
|
31028
32186
|
if (deepseekWaveWordVisible(waveTick, waveTier, style)) {
|
|
31029
32187
|
const word = waveTier === "unknown" ? "Into the Unknown" : "deepseek";
|
|
31030
|
-
const start = Math.max(2, Math.floor((
|
|
32188
|
+
const start = Math.max(2, Math.floor((bandWidth - word.length) / 2));
|
|
31031
32189
|
let clear = true;
|
|
31032
32190
|
for (let at = 0; at < word.length; at += 1) {
|
|
31033
32191
|
const cell = cells[start + at];
|
|
@@ -31056,24 +32214,19 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31056
32214
|
}
|
|
31057
32215
|
}
|
|
31058
32216
|
}
|
|
31059
|
-
|
|
31060
|
-
|
|
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));
|
|
31061
32221
|
};
|
|
31062
|
-
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));
|
|
31063
32223
|
}
|
|
31064
|
-
/** Build one settled row (row Box plus its roomy-prompt spacers). */
|
|
31065
|
-
function buildSettledRow(entry, index, showReasoning) {
|
|
31066
|
-
const row = (0, import_react.createElement)(EntryLine, {
|
|
31067
|
-
entry,
|
|
31068
|
-
showReasoning,
|
|
31069
|
-
verbose: false
|
|
31070
|
-
});
|
|
32224
|
+
/** Build one settled row (row Box plus its roomy-prompt spacers and row count). */
|
|
32225
|
+
function buildSettledRow(entry, index, showReasoning, columns) {
|
|
31071
32226
|
const roomyPrompt = entry.kind === "user" && !entry.notice;
|
|
32227
|
+
const lines = settledEntryLines(entry, Math.max(10, columns - 2), showReasoning);
|
|
31072
32228
|
return {
|
|
31073
|
-
box: (0, import_react.createElement)(Box, {
|
|
31074
|
-
key: index,
|
|
31075
|
-
paddingX: 1
|
|
31076
|
-
}, row),
|
|
32229
|
+
box: (0, import_react.createElement)(Box, { key: index }, (0, import_react.createElement)(StyledRows, { lines })),
|
|
31077
32230
|
before: roomyPrompt ? (0, import_react.createElement)(Box, {
|
|
31078
32231
|
key: `prompt-before-${index}`,
|
|
31079
32232
|
paddingX: 1
|
|
@@ -31082,10 +32235,17 @@ function buildSettledRow(entry, index, showReasoning) {
|
|
|
31082
32235
|
key: `prompt-after-${index}`,
|
|
31083
32236
|
paddingX: 1
|
|
31084
32237
|
}, (0, import_react.createElement)(Text, null, " ")) : void 0,
|
|
31085
|
-
|
|
31086
|
-
showReasoning
|
|
32238
|
+
rows: lines.length + (roomyPrompt ? 2 : 0)
|
|
31087
32239
|
};
|
|
31088
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
|
+
}
|
|
31089
32249
|
/**
|
|
31090
32250
|
* The settled `<Static>` row set as a PURE incremental state machine (App
|
|
31091
32251
|
* drives it from the memo; tests drive it directly and read `built`).
|
|
@@ -31099,77 +32259,82 @@ function buildSettledRow(entry, index, showReasoning) {
|
|
|
31099
32259
|
* rebuild of rows, Map, or MarkdownBody parses). `records` is mutated in place
|
|
31100
32260
|
* on the append/toggle paths to stay O(delta).
|
|
31101
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
|
+
*
|
|
31102
32277
|
* Full rebuilds run only on the rare, deliberate paths: no cache yet, a
|
|
31103
|
-
* source-backed replay (`epoch` bump: resize / Ctrl+L / Ctrl+R
|
|
31104
|
-
* `<Static>` and must re-flush the CURRENT rows
|
|
31105
|
-
*
|
|
31106
|
-
*
|
|
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.
|
|
31107
32283
|
*/
|
|
31108
|
-
function computeSettledRows(previous, entries, settled, showReasoning, resumed, epoch) {
|
|
32284
|
+
function computeSettledRows(previous, entries, settled, showReasoning, resumed, epoch, columns = 80, rowCap = SETTLED_ROW_CAP) {
|
|
31109
32285
|
if (previous === void 0 || previous.epoch !== epoch || previous.resumed !== resumed || settled < previous.entries.length) {
|
|
31110
32286
|
const records = /* @__PURE__ */ new Map();
|
|
31111
|
-
const
|
|
31112
|
-
|
|
31113
|
-
|
|
31114
|
-
|
|
31115
|
-
for (
|
|
32287
|
+
const window = [];
|
|
32288
|
+
let windowRows = 0;
|
|
32289
|
+
let droppedEntries = 0;
|
|
32290
|
+
let index = settled - 1;
|
|
32291
|
+
for (; index >= 0; index--) {
|
|
31116
32292
|
const entry = entries[index];
|
|
31117
|
-
|
|
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
|
+
}
|
|
31118
32299
|
records.set(entry, record);
|
|
31119
|
-
|
|
31120
|
-
|
|
31121
|
-
|
|
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);
|
|
31122
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];
|
|
31123
32314
|
return {
|
|
31124
32315
|
cache: {
|
|
31125
|
-
entries: entries.slice(
|
|
32316
|
+
entries: entries.slice(droppedEntries, settled),
|
|
31126
32317
|
records,
|
|
31127
|
-
header
|
|
32318
|
+
header,
|
|
31128
32319
|
resumed,
|
|
31129
32320
|
showReasoning,
|
|
31130
32321
|
epoch,
|
|
31131
|
-
|
|
32322
|
+
columns,
|
|
32323
|
+
flat,
|
|
32324
|
+
droppedEntries,
|
|
32325
|
+
totalRows: windowRows,
|
|
32326
|
+
needsTrim: false
|
|
31132
32327
|
},
|
|
31133
|
-
built:
|
|
31134
|
-
};
|
|
31135
|
-
}
|
|
31136
|
-
if (previous.showReasoning !== showReasoning) {
|
|
31137
|
-
const records = previous.records;
|
|
31138
|
-
const flat = [previous.header];
|
|
31139
|
-
let built = 0;
|
|
31140
|
-
for (let index = 0; index < previous.entries.length; index++) {
|
|
31141
|
-
const entry = previous.entries[index];
|
|
31142
|
-
const record = records.get(entry);
|
|
31143
|
-
const current = record.reasonSensitive ? {
|
|
31144
|
-
...record,
|
|
31145
|
-
box: (0, import_react.createElement)(Box, {
|
|
31146
|
-
key: index,
|
|
31147
|
-
paddingX: 1
|
|
31148
|
-
}, (0, import_react.createElement)(EntryLine, {
|
|
31149
|
-
entry,
|
|
31150
|
-
showReasoning,
|
|
31151
|
-
verbose: false
|
|
31152
|
-
})),
|
|
31153
|
-
showReasoning
|
|
31154
|
-
} : record;
|
|
31155
|
-
if (current !== record) {
|
|
31156
|
-
records.set(entry, current);
|
|
31157
|
-
built += 1;
|
|
31158
|
-
}
|
|
31159
|
-
if (current.before !== void 0) flat.push(current.before);
|
|
31160
|
-
flat.push(current.box);
|
|
31161
|
-
if (current.after !== void 0) flat.push(current.after);
|
|
31162
|
-
}
|
|
31163
|
-
return {
|
|
31164
|
-
cache: {
|
|
31165
|
-
...previous,
|
|
31166
|
-
records,
|
|
31167
|
-
showReasoning,
|
|
31168
|
-
flat
|
|
31169
|
-
},
|
|
31170
|
-
built
|
|
32328
|
+
built: records.size
|
|
31171
32329
|
};
|
|
31172
32330
|
}
|
|
32331
|
+
if (previous.showReasoning !== showReasoning) return {
|
|
32332
|
+
cache: {
|
|
32333
|
+
...previous,
|
|
32334
|
+
showReasoning
|
|
32335
|
+
},
|
|
32336
|
+
built: 0
|
|
32337
|
+
};
|
|
31173
32338
|
if (settled === previous.entries.length) return {
|
|
31174
32339
|
cache: previous,
|
|
31175
32340
|
built: 0
|
|
@@ -31177,15 +32342,19 @@ function computeSettledRows(previous, entries, settled, showReasoning, resumed,
|
|
|
31177
32342
|
const records = previous.records;
|
|
31178
32343
|
const suffix = [];
|
|
31179
32344
|
const added = [];
|
|
31180
|
-
|
|
32345
|
+
let deltaRows = 0;
|
|
32346
|
+
for (let index = previous.entries.length + previous.droppedEntries; index < settled; index++) {
|
|
31181
32347
|
const entry = entries[index];
|
|
31182
|
-
const record = buildSettledRow(entry, index, showReasoning);
|
|
32348
|
+
const record = buildSettledRow(entry, index, showReasoning, previous.columns);
|
|
31183
32349
|
records.set(entry, record);
|
|
31184
32350
|
suffix.push(entry);
|
|
32351
|
+
deltaRows += record.rows;
|
|
31185
32352
|
if (record.before !== void 0) added.push(record.before);
|
|
31186
32353
|
added.push(record.box);
|
|
31187
32354
|
if (record.after !== void 0) added.push(record.after);
|
|
31188
32355
|
}
|
|
32356
|
+
const totalRows = previous.totalRows + deltaRows;
|
|
32357
|
+
const needsTrim = rowCap > 0 && totalRows > rowCap + Math.floor(rowCap / 4);
|
|
31189
32358
|
return {
|
|
31190
32359
|
cache: {
|
|
31191
32360
|
entries: previous.entries.concat(suffix),
|
|
@@ -31194,9 +32363,13 @@ function computeSettledRows(previous, entries, settled, showReasoning, resumed,
|
|
|
31194
32363
|
resumed: previous.resumed,
|
|
31195
32364
|
showReasoning,
|
|
31196
32365
|
epoch: previous.epoch,
|
|
31197
|
-
|
|
32366
|
+
columns: previous.columns,
|
|
32367
|
+
flat: previous.flat.concat(added),
|
|
32368
|
+
droppedEntries: previous.droppedEntries,
|
|
32369
|
+
totalRows,
|
|
32370
|
+
needsTrim
|
|
31198
32371
|
},
|
|
31199
|
-
built:
|
|
32372
|
+
built: suffix.length
|
|
31200
32373
|
};
|
|
31201
32374
|
}
|
|
31202
32375
|
/** The whole terminal app; state arrives via the store, output via Ink. */
|
|
@@ -31224,7 +32397,7 @@ function App(props) {
|
|
|
31224
32397
|
* follows the applied model label (what the status bar actually shows),
|
|
31225
32398
|
* never the initial paint, and the tier is derived from the label and
|
|
31226
32399
|
* cached at the switch. The 33ms tick itself lives inside Input, so the
|
|
31227
|
-
* sweep re-renders only the composer
|
|
32400
|
+
* sweep re-renders only the composer band, not the whole tree, at 30fps;
|
|
31228
32401
|
* App owns the rarely-changing tier/style and Input starts the sweep
|
|
31229
32402
|
* whenever that pair changes. */
|
|
31230
32403
|
const [waveTier, setWaveTier] = (0, import_react.useState)(null);
|
|
@@ -31315,18 +32488,21 @@ function App(props) {
|
|
|
31315
32488
|
const busy = view.busy;
|
|
31316
32489
|
const [showReasoning, setShowReasoning] = (0, import_react.useState)(false);
|
|
31317
32490
|
const [verboseOpen, setVerboseOpen] = (0, import_react.useState)(false);
|
|
32491
|
+
const [diffView, setDiffView] = (0, import_react.useState)(void 0);
|
|
31318
32492
|
const [helpOpen, setHelpOpen] = (0, import_react.useState)(false);
|
|
31319
32493
|
const [modeOpen, setModeOpen] = (0, import_react.useState)(false);
|
|
31320
32494
|
const [permissionOpen, setPermissionOpen] = (0, import_react.useState)(false);
|
|
31321
32495
|
const [resumeOpen, setResumeOpen] = (0, import_react.useState)(false);
|
|
31322
32496
|
const [pluginOpen, setPluginOpen] = (0, import_react.useState)(false);
|
|
31323
32497
|
const [pluginQuery, setPluginQuery] = (0, import_react.useState)("");
|
|
32498
|
+
const [jobsOpen, setJobsOpen] = (0, import_react.useState)(false);
|
|
31324
32499
|
const [statuslineOpen, setStatuslineOpen] = (0, import_react.useState)(false);
|
|
31325
32500
|
const [statuslineItems, setStatuslineItems] = (0, import_react.useState)(() => parseStatuslineItems(props.statusline));
|
|
31326
32501
|
const [themeOpen, setThemeOpen] = (0, import_react.useState)(false);
|
|
31327
32502
|
const [historyOpen, setHistoryOpen] = (0, import_react.useState)(false);
|
|
31328
32503
|
const [agentsOpen, setAgentsOpen] = (0, import_react.useState)(false);
|
|
31329
32504
|
const [subagentOpen, setSubagentOpen] = (0, import_react.useState)(false);
|
|
32505
|
+
const [todosOpen, setTodosOpen] = (0, import_react.useState)(false);
|
|
31330
32506
|
/** /delete state: delete-mode hint plus an optional pre-armed row id. */
|
|
31331
32507
|
const [resumeDelete, setResumeDelete] = (0, import_react.useState)({ mode: false });
|
|
31332
32508
|
/** The row id awaiting y/n in the COMPOSER (codex delete confirm): the
|
|
@@ -31391,7 +32567,7 @@ function App(props) {
|
|
|
31391
32567
|
const agentRows = (0, import_react.useSyncExternalStore)(props.subagents.subscribe, props.subagents.getSnapshot);
|
|
31392
32568
|
const approvalPending = approvalSnapshot.pending !== void 0;
|
|
31393
32569
|
const questionPending = questionSnapshot.pending !== void 0;
|
|
31394
|
-
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;
|
|
31395
32571
|
(0, import_react.useEffect)(() => {
|
|
31396
32572
|
if (!approvalPending && !questionPending) return;
|
|
31397
32573
|
setModelOpen(false);
|
|
@@ -31408,12 +32584,20 @@ function App(props) {
|
|
|
31408
32584
|
setHistoryOpen(false);
|
|
31409
32585
|
setAgentsOpen(false);
|
|
31410
32586
|
setSubagentOpen(false);
|
|
32587
|
+
setTodosOpen(false);
|
|
31411
32588
|
setDeleteConfirmId(void 0);
|
|
31412
32589
|
setVerboseOpen(false);
|
|
32590
|
+
setDiffView(void 0);
|
|
31413
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);
|
|
31414
32598
|
const settledRowsCache = (0, import_react.useRef)(void 0);
|
|
31415
32599
|
const settledRows = (0, import_react.useMemo)(() => {
|
|
31416
|
-
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);
|
|
31417
32601
|
settledRowsCache.current = result.cache;
|
|
31418
32602
|
return result.cache.flat;
|
|
31419
32603
|
}, [
|
|
@@ -31421,14 +32605,10 @@ function App(props) {
|
|
|
31421
32605
|
settled,
|
|
31422
32606
|
showReasoning,
|
|
31423
32607
|
props.resumed,
|
|
31424
|
-
refreshEpoch
|
|
32608
|
+
refreshEpoch,
|
|
32609
|
+
terminalSize.columns
|
|
31425
32610
|
]);
|
|
31426
|
-
const
|
|
31427
|
-
const [terminalSize, setTerminalSize] = (0, import_react.useState)(() => ({
|
|
31428
|
-
columns: appStdout?.columns ?? 80,
|
|
31429
|
-
rows: appStdout?.rows ?? 30
|
|
31430
|
-
}));
|
|
31431
|
-
const terminalSizeRef = (0, import_react.useRef)(terminalSize);
|
|
32611
|
+
const synchronizedReplayPending = (0, import_react.useRef)(false);
|
|
31432
32612
|
(0, import_react.useEffect)(() => {
|
|
31433
32613
|
if (appStdout === void 0) return;
|
|
31434
32614
|
let replayTimer;
|
|
@@ -31442,7 +32622,8 @@ function App(props) {
|
|
|
31442
32622
|
setTerminalSize(next);
|
|
31443
32623
|
if (replayTimer !== void 0) clearTimeout(replayTimer);
|
|
31444
32624
|
replayTimer = setTimeout(() => {
|
|
31445
|
-
|
|
32625
|
+
synchronizedReplayPending.current = true;
|
|
32626
|
+
appStdout.write("\x1B[?2026h\x1B[r\x1B[0m\x1B[H\x1B[2J\x1B[3J\x1B[H");
|
|
31446
32627
|
setRefreshEpoch((epoch) => epoch + 1);
|
|
31447
32628
|
}, RESIZE_REFLOW_DELAY_MS);
|
|
31448
32629
|
};
|
|
@@ -31455,28 +32636,51 @@ function App(props) {
|
|
|
31455
32636
|
const terminalRows = terminalSize.rows;
|
|
31456
32637
|
const terminalColumns = terminalSize.columns;
|
|
31457
32638
|
const composerGutterRows = layoutGutterRows(terminalRows);
|
|
31458
|
-
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));
|
|
31459
32645
|
const streamingActive = view.streaming !== "" || view.streamingReasoning !== "";
|
|
31460
32646
|
const deepDivingVisible = busy && !streamingActive;
|
|
31461
|
-
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)), [
|
|
31462
32648
|
view.entries,
|
|
31463
32649
|
settled,
|
|
31464
|
-
terminalColumns
|
|
32650
|
+
terminalColumns,
|
|
32651
|
+
showReasoning
|
|
31465
32652
|
]);
|
|
31466
|
-
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));
|
|
31467
32654
|
const visibleLiveLines = liveBudget === 0 ? [] : allLiveLines.slice(-liveBudget);
|
|
31468
32655
|
const streamRows = Math.max(1, dynamicRows - visibleLiveLines.length);
|
|
31469
32656
|
const reasoningRows = view.streamingReasoning === "" ? 0 : view.streaming === "" ? streamRows : streamRows <= 1 ? 0 : showReasoning ? Math.max(1, Math.floor(streamRows / 3)) : 1;
|
|
31470
32657
|
const answerRows = view.streaming === "" ? 0 : Math.max(1, streamRows - reasoningRows);
|
|
31471
|
-
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !verboseOpen && !approvalPending && !questionPending;
|
|
31472
|
-
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;
|
|
31473
32660
|
const closeInspector = (0, import_react.useCallback)(() => {
|
|
31474
32661
|
setVerboseOpen(false);
|
|
31475
32662
|
}, []);
|
|
31476
32663
|
const refreshScreen = () => {
|
|
31477
|
-
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
|
+
}
|
|
31478
32668
|
setRefreshEpoch((epoch) => epoch + 1);
|
|
31479
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
|
+
]);
|
|
31480
32684
|
/** Apply one /model pick: record the selection, close the panel, report via notice. */
|
|
31481
32685
|
const applyModel = (row, effortId) => {
|
|
31482
32686
|
try {
|
|
@@ -31503,7 +32707,20 @@ function App(props) {
|
|
|
31503
32707
|
};
|
|
31504
32708
|
let modelSurface;
|
|
31505
32709
|
if (modelOpen && !approvalPending && !questionPending) {
|
|
31506
|
-
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, {
|
|
31507
32724
|
target: providerAction.target,
|
|
31508
32725
|
save: props.saveModelProviderCredential,
|
|
31509
32726
|
done: () => {
|
|
@@ -31554,6 +32771,16 @@ function App(props) {
|
|
|
31554
32771
|
target
|
|
31555
32772
|
});
|
|
31556
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
|
+
},
|
|
31557
32784
|
onUnset: (target) => {
|
|
31558
32785
|
if (props.unsetModelProviderCredential === void 0) {
|
|
31559
32786
|
notify("API key removal is unavailable in this profile", "warning");
|
|
@@ -31604,11 +32831,8 @@ function App(props) {
|
|
|
31604
32831
|
return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(MemoStaticTranscript, {
|
|
31605
32832
|
key: refreshEpoch,
|
|
31606
32833
|
items: settledRows
|
|
31607
|
-
}), transcriptVisible ? (0, import_react.createElement)(Box, {
|
|
31608
|
-
|
|
31609
|
-
paddingX: 2
|
|
31610
|
-
}, visibleLiveLines.length === 0 ? void 0 : (0, import_react.createElement)(StyledRows, { lines: visibleLiveLines }), view.streamingReasoning !== "" && reasoningRows > 0 ? (0, import_react.createElement)(StreamTail, {
|
|
31611
|
-
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)",
|
|
31612
32836
|
prefix: "✻ ",
|
|
31613
32837
|
continuationPrefix: " ",
|
|
31614
32838
|
dim: true,
|
|
@@ -31618,7 +32842,12 @@ function App(props) {
|
|
|
31618
32842
|
dim: false,
|
|
31619
32843
|
maxRows: answerRows,
|
|
31620
32844
|
prefix: " "
|
|
31621
|
-
}, 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, {
|
|
31622
32851
|
store: props.questions,
|
|
31623
32852
|
snapshot: questionSnapshot,
|
|
31624
32853
|
locked: false
|
|
@@ -31634,6 +32863,9 @@ function App(props) {
|
|
|
31634
32863
|
onClose: () => {
|
|
31635
32864
|
setHelpOpen(false);
|
|
31636
32865
|
}
|
|
32866
|
+
}) : void 0, diffView !== void 0 && !approvalPending && !questionPending ? (0, import_react.createElement)(DiffPanel, {
|
|
32867
|
+
view: diffView,
|
|
32868
|
+
onClose: () => setDiffView(void 0)
|
|
31637
32869
|
}) : void 0, verboseOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(MemoVerbosePanel, {
|
|
31638
32870
|
entries: view.entries,
|
|
31639
32871
|
onClose: closeInspector
|
|
@@ -31677,6 +32909,9 @@ function App(props) {
|
|
|
31677
32909
|
load: props.loadPlugins,
|
|
31678
32910
|
initialQuery: pluginQuery,
|
|
31679
32911
|
close: () => setPluginOpen(false)
|
|
32912
|
+
}) : void 0, jobsOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(JobsPanel, {
|
|
32913
|
+
load: props.loadJobs,
|
|
32914
|
+
close: () => setJobsOpen(false)
|
|
31680
32915
|
}) : void 0, statuslineOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(StatuslinePanel, {
|
|
31681
32916
|
enabled: statuslineItems,
|
|
31682
32917
|
change: (items) => {
|
|
@@ -31790,11 +33025,13 @@ function App(props) {
|
|
|
31790
33025
|
setPluginQuery(query);
|
|
31791
33026
|
setPluginOpen(true);
|
|
31792
33027
|
},
|
|
33028
|
+
openJobs: () => setJobsOpen(true),
|
|
31793
33029
|
openStatusline: () => setStatuslineOpen(true),
|
|
31794
33030
|
openTheme: () => setThemeOpen(true),
|
|
31795
33031
|
openHistory: () => setHistoryOpen(true),
|
|
31796
33032
|
openAgents: () => setAgentsOpen(true),
|
|
31797
33033
|
openSubagent: () => setSubagentOpen(true),
|
|
33034
|
+
openTodos: () => setTodosOpen(true),
|
|
31798
33035
|
openDelete: (id) => {
|
|
31799
33036
|
const armed = id === void 0 || id === "" ? void 0 : id;
|
|
31800
33037
|
setResumeDelete({
|
|
@@ -31804,10 +33041,17 @@ function App(props) {
|
|
|
31804
33041
|
setDeleteConfirmId(armed);
|
|
31805
33042
|
setResumeOpen(true);
|
|
31806
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,
|
|
31807
33050
|
deleteConfirm: deleteConfirmId,
|
|
31808
33051
|
confirmDelete,
|
|
31809
33052
|
cancelDelete,
|
|
31810
33053
|
createSession: props.createSession,
|
|
33054
|
+
forkSession: props.forkSession,
|
|
31811
33055
|
cancelSessionSwitch: props.cancelSessionSwitch,
|
|
31812
33056
|
notify,
|
|
31813
33057
|
hasNotice: notice !== void 0,
|
|
@@ -31823,12 +33067,13 @@ function App(props) {
|
|
|
31823
33067
|
refresh: refreshScreen,
|
|
31824
33068
|
toggleReasoning: () => {
|
|
31825
33069
|
setShowReasoning((current) => !current);
|
|
31826
|
-
refreshScreen();
|
|
33070
|
+
if (!busy && !streamingActive) refreshScreen();
|
|
31827
33071
|
},
|
|
31828
33072
|
loadMentions: props.loadMentions,
|
|
31829
33073
|
cyclePermission: props.cyclePermission,
|
|
31830
33074
|
exportTranscript: props.exportTranscript,
|
|
31831
33075
|
renameTitle: props.renameTitle,
|
|
33076
|
+
copyLastResponse: props.copyLastResponse,
|
|
31832
33077
|
recallSpace,
|
|
31833
33078
|
recordLocal,
|
|
31834
33079
|
recordHistory: props.recordHistory,
|
|
@@ -31837,7 +33082,9 @@ function App(props) {
|
|
|
31837
33082
|
historyFill,
|
|
31838
33083
|
historyConsumed,
|
|
31839
33084
|
waveTier,
|
|
31840
|
-
waveStyle
|
|
33085
|
+
waveStyle,
|
|
33086
|
+
maxRows: composerEditorCap,
|
|
33087
|
+
onEditorRows: handleEditorRows
|
|
31841
33088
|
}), (0, import_react.createElement)(StatusLine, {
|
|
31842
33089
|
facts: {
|
|
31843
33090
|
model: modelLabel,
|
|
@@ -32024,13 +33271,15 @@ function isSlashLine(line) {
|
|
|
32024
33271
|
/** Substitutable runner effects; production values write to the real terminal. */
|
|
32025
33272
|
const internals = {
|
|
32026
33273
|
mount: (element) => {
|
|
32027
|
-
|
|
33274
|
+
process.stdout.write("\x1B[>4;0m\x1B[>5u\x1B[?2004h");
|
|
33275
|
+
const instance = render(element, { exitOnCtrlC: false });
|
|
32028
33276
|
return {
|
|
32029
33277
|
rerender(element) {
|
|
32030
33278
|
instance.rerender(element);
|
|
32031
33279
|
},
|
|
32032
33280
|
unmount() {
|
|
32033
33281
|
instance.unmount();
|
|
33282
|
+
process.stdout.write("\x1B[<u\x1B[>4;0m\x1B[?2004l");
|
|
32034
33283
|
}
|
|
32035
33284
|
};
|
|
32036
33285
|
},
|
|
@@ -32221,6 +33470,26 @@ function profileRefOf(profile) {
|
|
|
32221
33470
|
const ref = profile.apiKeyEnv;
|
|
32222
33471
|
return typeof ref === "string" && ref.length > 0 ? ref : void 0;
|
|
32223
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
|
+
}
|
|
32224
33493
|
/**
|
|
32225
33494
|
* The conventional credential reference for a provider route: `<ROUTE>_API_KEY`
|
|
32226
33495
|
* with the route uppercased and every non-alphanumeric run collapsed to one
|
|
@@ -32323,6 +33592,7 @@ async function loadProviderSettings(ctx) {
|
|
|
32323
33592
|
settingsRevision: namespace?.revision ?? 0,
|
|
32324
33593
|
configured,
|
|
32325
33594
|
removable,
|
|
33595
|
+
configuration: configurationOf(profile),
|
|
32326
33596
|
...credentialRef === void 0 ? {} : { credentialRef },
|
|
32327
33597
|
suggestedRef: deriveCredentialRef(base.provider),
|
|
32328
33598
|
...base.declared === void 0 ? {} : { declared: base.declared }
|
|
@@ -32408,6 +33678,50 @@ async function saveProviderCredential(ctx, target, rawKey) {
|
|
|
32408
33678
|
throw new ProviderSettingsError(credentialWriteMessage(error, checked.value));
|
|
32409
33679
|
}
|
|
32410
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
|
+
}
|
|
32411
33725
|
/**
|
|
32412
33726
|
* Remove the currently named credential without touching the provider
|
|
32413
33727
|
* profile. Only the resolved profile's own reference is unset; a dormant or
|
|
@@ -32468,133 +33782,43 @@ async function removeProviderSettings(ctx, target) {
|
|
|
32468
33782
|
}
|
|
32469
33783
|
//#endregion
|
|
32470
33784
|
//#region src/mentions.ts
|
|
32471
|
-
/**
|
|
32472
|
-
|
|
32473
|
-
* async scan of the session cwd, session candidates from the opt-in
|
|
32474
|
-
* `sessionReferenceResolver` service, and submission preparation through its
|
|
32475
|
-
* `prepare()` API. Picked session mentions land as canonical
|
|
32476
|
-
* `@[label](dsh-session:…)` tokens; on submit the text is parsed back into
|
|
32477
|
-
* readable `@label` text plus structured references, snapshots are injected
|
|
32478
|
-
* via `agent.inject()` before the readable message wakes the driver
|
|
32479
|
-
* (`followup` idle, `steer` running) — exactly the upstream README's wiring.
|
|
32480
|
-
*
|
|
32481
|
-
* Harness exposes no workspace-file mention service (only session references
|
|
32482
|
-
* plus a post-hoc produced-file linker), so the file index is the lightweight
|
|
32483
|
-
* bounded scan below, kept deliberately smaller than Codex's streaming
|
|
32484
|
-
* gitignore-aware walker.
|
|
32485
|
-
*
|
|
32486
|
-
* @module @deepseek-ai/dsh-code/mentions
|
|
32487
|
-
*/
|
|
32488
|
-
/** Directories never entered and files never listed during the scan. */
|
|
32489
|
-
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
32490
|
-
".git",
|
|
32491
|
-
"node_modules",
|
|
32492
|
-
"lib",
|
|
32493
|
-
"dist",
|
|
32494
|
-
"out",
|
|
32495
|
-
".omc",
|
|
32496
|
-
"coverage"
|
|
32497
|
-
]);
|
|
32498
|
-
const MAX_FILES = 4e3;
|
|
32499
|
-
const MAX_DEPTH = 12;
|
|
32500
|
-
/** Empty-query default rows: proves the index exists without typing (Codex's
|
|
32501
|
-
* popups show something on a bare sigil too). */
|
|
32502
|
-
const EMPTY_QUERY_ROWS = 20;
|
|
32503
|
-
/**
|
|
32504
|
-
* Bounded async BFS scan of a workspace; unreadable entries are skipped.
|
|
32505
|
-
* Both files and directories are indexed (directories insert with a trailing
|
|
32506
|
-
* slash), mirroring Codex's `MatchType::{File,Directory}` index. Dotfiles and
|
|
32507
|
-
* the {@link SKIP_DIRS} list are excluded, which is a coarser filter than
|
|
32508
|
-
* Codex's gitignore-aware walker but stays dependency-free and bounded.
|
|
32509
|
-
*/
|
|
32510
|
-
async function scanWorkspaceFiles(root, signal) {
|
|
32511
|
-
const found = [];
|
|
32512
|
-
const pending = [{
|
|
32513
|
-
absolute: root,
|
|
32514
|
-
relative: "",
|
|
32515
|
-
depth: 0
|
|
32516
|
-
}];
|
|
32517
|
-
const aborted = () => signal?.aborted === true;
|
|
32518
|
-
while (pending.length > 0 && found.length < MAX_FILES && !aborted()) {
|
|
32519
|
-
const current = pending.shift();
|
|
32520
|
-
if (current === void 0) break;
|
|
32521
|
-
let entries;
|
|
32522
|
-
try {
|
|
32523
|
-
entries = await readdir(current.absolute, { withFileTypes: true });
|
|
32524
|
-
} catch {
|
|
32525
|
-
continue;
|
|
32526
|
-
}
|
|
32527
|
-
for (const entry of entries) {
|
|
32528
|
-
if (found.length >= MAX_FILES || aborted()) return found;
|
|
32529
|
-
if (entry.name.startsWith(".")) continue;
|
|
32530
|
-
const relative = current.relative === "" ? entry.name : `${current.relative}/${entry.name}`;
|
|
32531
|
-
if (entry.isDirectory()) {
|
|
32532
|
-
if (SKIP_DIRS.has(entry.name) || current.depth + 1 > MAX_DEPTH) continue;
|
|
32533
|
-
found.push({
|
|
32534
|
-
path: relative,
|
|
32535
|
-
kind: "directory"
|
|
32536
|
-
});
|
|
32537
|
-
pending.push({
|
|
32538
|
-
absolute: join(current.absolute, entry.name),
|
|
32539
|
-
relative,
|
|
32540
|
-
depth: current.depth + 1
|
|
32541
|
-
});
|
|
32542
|
-
} else if (entry.isFile()) found.push({
|
|
32543
|
-
path: relative,
|
|
32544
|
-
kind: "file"
|
|
32545
|
-
});
|
|
32546
|
-
}
|
|
32547
|
-
}
|
|
32548
|
-
return found.sort((left, right) => left.path < right.path ? -1 : 1);
|
|
32549
|
-
}
|
|
32550
|
-
/** True when every query character appears in order in the haystack. */
|
|
32551
|
-
function isSubsequence(query, haystack) {
|
|
32552
|
-
let at = 0;
|
|
32553
|
-
for (const char of haystack) {
|
|
32554
|
-
if (char === query[at]) at += 1;
|
|
32555
|
-
if (at >= query.length) return true;
|
|
32556
|
-
}
|
|
32557
|
-
return at >= query.length;
|
|
32558
|
-
}
|
|
32559
|
-
/** Rank one file path against the typed query (community-TUI scoring shape). */
|
|
32560
|
-
function scoreFile(path, query) {
|
|
32561
|
-
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
32562
|
-
if (query === "") return 0;
|
|
32563
|
-
if (name === query) return 1e3;
|
|
32564
|
-
if (name.startsWith(query)) return 900;
|
|
32565
|
-
if (name.includes(query)) return 700;
|
|
32566
|
-
if (path.includes(query)) return 500;
|
|
32567
|
-
if (isSubsequence(query, name)) return 300;
|
|
32568
|
-
return 0;
|
|
32569
|
-
}
|
|
33785
|
+
/** Menu cap on file rows; the service owns ranking and default rows. */
|
|
33786
|
+
const MAX_FILE_ROWS = 20;
|
|
32570
33787
|
/**
|
|
32571
33788
|
* Create the mention API for one agent's workspace. A missing
|
|
32572
|
-
*
|
|
32573
|
-
*
|
|
32574
|
-
* (a bare launch before any session
|
|
32575
|
-
*
|
|
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.
|
|
32576
33795
|
*
|
|
32577
|
-
* `
|
|
32578
|
-
*
|
|
32579
|
-
*
|
|
32580
|
-
* @param ctx - context carrying the optional `
|
|
32581
|
-
*
|
|
32582
|
-
* @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.
|
|
32583
33803
|
*/
|
|
32584
33804
|
function createMentions(ctx, agent, cwd) {
|
|
32585
33805
|
const resolver = ctx.get("sessionReferenceResolver");
|
|
33806
|
+
const fileReferences = ctx.get("fileReferences");
|
|
32586
33807
|
const sessionCapable = agent !== void 0 && resolver !== void 0;
|
|
32587
|
-
let
|
|
32588
|
-
const
|
|
32589
|
-
|
|
32590
|
-
|
|
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);
|
|
32591
33816
|
};
|
|
32592
33817
|
return {
|
|
32593
|
-
files,
|
|
32594
33818
|
async candidates(query, signal) {
|
|
32595
33819
|
const needle = query.trim();
|
|
32596
|
-
const [
|
|
32597
|
-
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) => ({
|
|
32598
33822
|
label: candidate.path,
|
|
32599
33823
|
description: candidate.kind === "directory" ? "Folder" : "File",
|
|
32600
33824
|
kind: candidate.kind
|
|
@@ -32720,6 +33944,8 @@ function mountQuestionProvider(ctx) {
|
|
|
32720
33944
|
}
|
|
32721
33945
|
//#endregion
|
|
32722
33946
|
//#region src/store.ts
|
|
33947
|
+
/** Render frame budget: the notification cadence's upper bound. */
|
|
33948
|
+
const NOTIFY_FRAME_MS$1 = 16;
|
|
32723
33949
|
/**
|
|
32724
33950
|
* Create one transcript store, optionally seeded with replayed history. The
|
|
32725
33951
|
* seed folds synchronously BEFORE the first render, so a resumed session
|
|
@@ -32734,13 +33960,18 @@ function createTranscriptStore(replay) {
|
|
|
32734
33960
|
let view = replay === void 0 ? createTranscriptView() : projectEvents(replay);
|
|
32735
33961
|
const listeners = /* @__PURE__ */ new Set();
|
|
32736
33962
|
let scheduled = false;
|
|
33963
|
+
let lastNotifyAt = 0;
|
|
32737
33964
|
const notify = () => {
|
|
32738
33965
|
if (scheduled) return;
|
|
32739
33966
|
scheduled = true;
|
|
32740
|
-
|
|
33967
|
+
const wait = NOTIFY_FRAME_MS$1 - (Date.now() - lastNotifyAt);
|
|
33968
|
+
const dispatch = () => {
|
|
32741
33969
|
scheduled = false;
|
|
33970
|
+
lastNotifyAt = Date.now();
|
|
32742
33971
|
for (const listener of listeners) listener();
|
|
32743
|
-
}
|
|
33972
|
+
};
|
|
33973
|
+
if (wait <= 0) setImmediate(dispatch);
|
|
33974
|
+
else setTimeout(dispatch, wait);
|
|
32744
33975
|
};
|
|
32745
33976
|
return {
|
|
32746
33977
|
getView: () => view,
|
|
@@ -32762,6 +33993,8 @@ function createTranscriptStore(replay) {
|
|
|
32762
33993
|
}
|
|
32763
33994
|
};
|
|
32764
33995
|
}
|
|
33996
|
+
/** Render frame budget: the notification cadence's upper bound. */
|
|
33997
|
+
const NOTIFY_FRAME_MS = 16;
|
|
32765
33998
|
/** Bounded last-activity text (plain characters, display-sliced later). */
|
|
32766
33999
|
const MAX_ACTIVITY_CHARS = 80;
|
|
32767
34000
|
/** Single-line bounded preview of an assembled message's text content. */
|
|
@@ -32875,13 +34108,18 @@ function createSubagentFeed() {
|
|
|
32875
34108
|
let rows = Object.freeze([]);
|
|
32876
34109
|
const listeners = /* @__PURE__ */ new Set();
|
|
32877
34110
|
let scheduled = false;
|
|
34111
|
+
let lastNotifyAt = 0;
|
|
32878
34112
|
const notify = () => {
|
|
32879
34113
|
if (scheduled) return;
|
|
32880
34114
|
scheduled = true;
|
|
32881
|
-
|
|
34115
|
+
const wait = NOTIFY_FRAME_MS - (Date.now() - lastNotifyAt);
|
|
34116
|
+
const dispatch = () => {
|
|
32882
34117
|
scheduled = false;
|
|
34118
|
+
lastNotifyAt = Date.now();
|
|
32883
34119
|
for (const listener of listeners) listener();
|
|
32884
|
-
}
|
|
34120
|
+
};
|
|
34121
|
+
if (wait <= 0) setImmediate(dispatch);
|
|
34122
|
+
else setTimeout(dispatch, wait);
|
|
32885
34123
|
};
|
|
32886
34124
|
return {
|
|
32887
34125
|
apply(sessionId, event) {
|
|
@@ -32999,7 +34237,7 @@ function buildExportMarkdown(view, sessionId) {
|
|
|
32999
34237
|
for (const entry of view.entries) switch (entry.kind) {
|
|
33000
34238
|
case "user":
|
|
33001
34239
|
if (entry.notice) out.push(`> ⤷ context: ${entry.text}`, "");
|
|
33002
|
-
else out.push("## user", "", entry.text, "");
|
|
34240
|
+
else out.push("## user", "", entry.text, ...imageLabels(entry.images) === "" ? [] : [imageLabels(entry.images)], "");
|
|
33003
34241
|
break;
|
|
33004
34242
|
case "assistant":
|
|
33005
34243
|
if (entry.reasoning !== "") out.push("<details><summary>thinking</summary>", "", entry.reasoning, "", "</details>", "");
|
|
@@ -33032,7 +34270,7 @@ function buildExportMarkdown(view, sessionId) {
|
|
|
33032
34270
|
out.push(`> files changed: ${entry.paths.join(", ")}`, "");
|
|
33033
34271
|
break;
|
|
33034
34272
|
case "pending":
|
|
33035
|
-
out.push("## user", "", entry.text, "");
|
|
34273
|
+
out.push("## user", "", entry.text, ...imageLabels(entry.images) === "" ? [] : [imageLabels(entry.images)], "");
|
|
33036
34274
|
break;
|
|
33037
34275
|
default: assertNever(entry, "transcript entry kind");
|
|
33038
34276
|
}
|
|
@@ -33046,6 +34284,212 @@ function buildExportMarkdown(view, sessionId) {
|
|
|
33046
34284
|
return out.join("\n");
|
|
33047
34285
|
}
|
|
33048
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
|
|
33049
34493
|
//#region src/session-switch.ts
|
|
33050
34494
|
var SessionSwitchQueue = class {
|
|
33051
34495
|
execute;
|
|
@@ -33219,7 +34663,9 @@ const Config = z.object({ startup: z.object({
|
|
|
33219
34663
|
kind: z.string().required(),
|
|
33220
34664
|
sessionId: z.string(),
|
|
33221
34665
|
mode: z.string(),
|
|
33222
|
-
theme: z.string()
|
|
34666
|
+
theme: z.string(),
|
|
34667
|
+
prompt: z.string(),
|
|
34668
|
+
images: z.array(z.string())
|
|
33223
34669
|
}) });
|
|
33224
34670
|
/** Report an unexpected direct-driver failure and request a failing exit. */
|
|
33225
34671
|
function fail(io, error) {
|
|
@@ -33227,6 +34673,33 @@ function fail(io, error) {
|
|
|
33227
34673
|
io.exit(1);
|
|
33228
34674
|
}
|
|
33229
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
|
+
/**
|
|
33230
34703
|
* Resolve the working directory's git branch for the status line.
|
|
33231
34704
|
* @param cwd - the session's working directory.
|
|
33232
34705
|
* @returns the branch name, or '' outside a repository or on a detached HEAD.
|
|
@@ -33391,21 +34864,23 @@ async function run(ctx, startup, io) {
|
|
|
33391
34864
|
sessionId: SessionId(next.sessionId),
|
|
33392
34865
|
meta: {
|
|
33393
34866
|
cwd: nextCwd,
|
|
33394
|
-
agentPreset: mode
|
|
34867
|
+
agentPreset: mode,
|
|
34868
|
+
...next.parentSession === void 0 ? {} : { parentSession: next.parentSession },
|
|
34869
|
+
...next.seedLength === void 0 ? {} : { seedLength: next.seedLength }
|
|
33395
34870
|
},
|
|
34871
|
+
...next.seed === void 0 ? {} : { seed: next.seed },
|
|
33396
34872
|
agentOptions: seedOptions,
|
|
33397
34873
|
signal: quitAbort.signal,
|
|
33398
34874
|
setup
|
|
33399
34875
|
});
|
|
33400
34876
|
const session = handle.agent.session;
|
|
33401
34877
|
if (!next.resume && permissionPresets !== void 0) applyPendingPermission(permissionPresets, session, pendingPermission);
|
|
33402
|
-
const sessionCwd = session.header.cwd ?? nextCwd;
|
|
33403
34878
|
return {
|
|
33404
34879
|
handle,
|
|
33405
34880
|
agent: handle.agent,
|
|
33406
34881
|
session,
|
|
33407
34882
|
store: createTranscriptStore(session.events),
|
|
33408
|
-
mentions: createMentions(ctx, handle.agent,
|
|
34883
|
+
mentions: createMentions(ctx, handle.agent, session.header.cwd ?? nextCwd),
|
|
33409
34884
|
mode: mode ?? "standard",
|
|
33410
34885
|
selection: selectionState,
|
|
33411
34886
|
resumed: next.resume
|
|
@@ -33471,7 +34946,7 @@ async function run(ctx, startup, io) {
|
|
|
33471
34946
|
store.apply(event);
|
|
33472
34947
|
return;
|
|
33473
34948
|
}
|
|
33474
|
-
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);
|
|
33475
34950
|
});
|
|
33476
34951
|
const commands = watchCommands(ctx);
|
|
33477
34952
|
if (agent !== void 0) commands.setAgent(agent);
|
|
@@ -33597,7 +35072,7 @@ async function run(ctx, startup, io) {
|
|
|
33597
35072
|
const finish = () => {
|
|
33598
35073
|
pendingControllers.delete(controller);
|
|
33599
35074
|
};
|
|
33600
|
-
Promise.resolve().then(() => registry.execute(currentAgent, line, controller.signal)).then((execution) => {
|
|
35075
|
+
Promise.resolve().then(() => registry.execute(currentAgent, line, [], controller.signal)).then((execution) => {
|
|
33601
35076
|
finish();
|
|
33602
35077
|
if (epoch !== atEpoch || agent !== currentAgent) return;
|
|
33603
35078
|
if (execution === void 0) try {
|
|
@@ -33618,10 +35093,10 @@ async function run(ctx, startup, io) {
|
|
|
33618
35093
|
});
|
|
33619
35094
|
};
|
|
33620
35095
|
/** Deliver one trimmed line to the live session, expanding mentions first. */
|
|
33621
|
-
const deliverLine = (line, mode) => {
|
|
35096
|
+
const deliverLine = (line, mode, images = []) => {
|
|
33622
35097
|
const currentAgent = agent;
|
|
33623
35098
|
const currentMentions = mentions;
|
|
33624
|
-
if (isSlashLine(line) && mode === "followup") {
|
|
35099
|
+
if (images.length === 0 && isSlashLine(line) && mode === "followup") {
|
|
33625
35100
|
runSlash(line);
|
|
33626
35101
|
return;
|
|
33627
35102
|
}
|
|
@@ -33637,11 +35112,12 @@ async function run(ctx, startup, io) {
|
|
|
33637
35112
|
if (epoch !== atEpoch || agent !== currentAgent) return;
|
|
33638
35113
|
try {
|
|
33639
35114
|
if (context !== void 0) currentAgent.inject(context);
|
|
35115
|
+
const content = [...readable === "" ? [] : [{
|
|
35116
|
+
type: "text",
|
|
35117
|
+
text: readable
|
|
35118
|
+
}], ...images];
|
|
33640
35119
|
const message = createUserMessage({
|
|
33641
|
-
content
|
|
33642
|
-
type: "text",
|
|
33643
|
-
text: readable
|
|
33644
|
-
}],
|
|
35120
|
+
content,
|
|
33645
35121
|
source: { kind: "user" }
|
|
33646
35122
|
});
|
|
33647
35123
|
if (mode === "steer") currentAgent.steer(message);
|
|
@@ -33675,7 +35151,7 @@ async function run(ctx, startup, io) {
|
|
|
33675
35151
|
await pendingModeWork;
|
|
33676
35152
|
if (session !== void 0) {
|
|
33677
35153
|
const queued = pendingInputs.splice(0);
|
|
33678
|
-
for (const item of queued) deliverLine(item.text, item.mode);
|
|
35154
|
+
for (const item of queued) deliverLine(item.text, item.mode, item.images);
|
|
33679
35155
|
return;
|
|
33680
35156
|
}
|
|
33681
35157
|
const next = await prepare({
|
|
@@ -33702,7 +35178,7 @@ async function run(ctx, startup, io) {
|
|
|
33702
35178
|
abortPendingControllers();
|
|
33703
35179
|
epoch += 1;
|
|
33704
35180
|
const queued = pendingInputs.splice(0);
|
|
33705
|
-
for (const item of queued) deliverLine(item.text, item.mode);
|
|
35181
|
+
for (const item of queued) deliverLine(item.text, item.mode, item.images);
|
|
33706
35182
|
} finally {
|
|
33707
35183
|
creating = false;
|
|
33708
35184
|
}
|
|
@@ -33712,14 +35188,14 @@ async function run(ctx, startup, io) {
|
|
|
33712
35188
|
});
|
|
33713
35189
|
};
|
|
33714
35190
|
/** Deliver one readable line to the agent, expanding session mentions first. */
|
|
33715
|
-
const send = (text, mode) => {
|
|
35191
|
+
const send = (text, mode, images = []) => {
|
|
33716
35192
|
const line = text.trim();
|
|
33717
|
-
if (line === "") return;
|
|
33718
|
-
if (line.startsWith("/mode ")) {
|
|
35193
|
+
if (line === "" && images.length === 0) return;
|
|
35194
|
+
if (images.length === 0 && line.startsWith("/mode ")) {
|
|
33719
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"));
|
|
33720
35196
|
return;
|
|
33721
35197
|
}
|
|
33722
|
-
if (line.startsWith("/permission ")) {
|
|
35198
|
+
if (images.length === 0 && line.startsWith("/permission ")) {
|
|
33723
35199
|
try {
|
|
33724
35200
|
const selected = setPermissionAction(line.slice(12).trim());
|
|
33725
35201
|
bridge.notify(`permission → ${selected}`);
|
|
@@ -33731,12 +35207,13 @@ async function run(ctx, startup, io) {
|
|
|
33731
35207
|
if (session === void 0) {
|
|
33732
35208
|
pendingInputs.push({
|
|
33733
35209
|
text: line,
|
|
33734
|
-
mode
|
|
35210
|
+
mode,
|
|
35211
|
+
images
|
|
33735
35212
|
});
|
|
33736
35213
|
ensureSession();
|
|
33737
35214
|
return;
|
|
33738
35215
|
}
|
|
33739
|
-
deliverLine(line, mode);
|
|
35216
|
+
deliverLine(line, mode, images);
|
|
33740
35217
|
};
|
|
33741
35218
|
/** Dispatch one submitted line: slash commands to the registry, other text to the agent. */
|
|
33742
35219
|
const dispatch = (text) => {
|
|
@@ -34068,6 +35545,47 @@ async function run(ctx, startup, io) {
|
|
|
34068
35545
|
label: id.slice(-12)
|
|
34069
35546
|
});
|
|
34070
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
|
+
};
|
|
34071
35589
|
const switchSession = (row) => {
|
|
34072
35590
|
if (!row.resumable) {
|
|
34073
35591
|
bridge.notify("subagent conversations are read-only", "warning");
|
|
@@ -34116,6 +35634,7 @@ async function run(ctx, startup, io) {
|
|
|
34116
35634
|
loadModelProviders: () => loadProviderSettings(ctx),
|
|
34117
35635
|
subscribeModelProviders: (listener) => subscribeProviderSettings(ctx, listener),
|
|
34118
35636
|
saveModelProviderCredential: (target, key) => saveProviderCredential(ctx, target, key),
|
|
35637
|
+
saveModelProviderConfiguration: (target, configuration) => saveProviderConfiguration(ctx, target, configuration),
|
|
34119
35638
|
unsetModelProviderCredential: (target) => unsetProviderCredential(ctx, target),
|
|
34120
35639
|
removeModelProvider: (target) => removeProviderSettings(ctx, target),
|
|
34121
35640
|
loadMentions: (query, signal) => mentions.candidates(query, signal),
|
|
@@ -34128,10 +35647,14 @@ async function run(ctx, startup, io) {
|
|
|
34128
35647
|
deleteSession,
|
|
34129
35648
|
exportTranscript,
|
|
34130
35649
|
renameTitle,
|
|
35650
|
+
copyLastResponse,
|
|
35651
|
+
loadGitDiff: (argument) => loadGitDiff(session?.header.cwd ?? cwd, argument),
|
|
35652
|
+
reviewChanges,
|
|
34131
35653
|
loadPresets: () => presets.list(),
|
|
34132
35654
|
switchMode: switchModeAction,
|
|
34133
35655
|
loadPermissions: () => permissionPresets === void 0 ? Promise.reject(/* @__PURE__ */ new Error("permission presets are not mounted in this composition")) : Promise.resolve(listPermissionRows(permissionPresets)),
|
|
34134
35656
|
createSession,
|
|
35657
|
+
forkSession,
|
|
34135
35658
|
loadSessions,
|
|
34136
35659
|
loadSessionTranscript,
|
|
34137
35660
|
loadSubagents: () => {
|
|
@@ -34143,11 +35666,12 @@ async function run(ctx, startup, io) {
|
|
|
34143
35666
|
sort: "newest",
|
|
34144
35667
|
currentCwd: current.header.cwd ?? cwd,
|
|
34145
35668
|
query: ""
|
|
34146
|
-
}).then((rows) => rows.filter((row) => row.parent === current.id));
|
|
35669
|
+
}).then((rows) => rows.filter((row) => row.parent === current.id && row.subagent));
|
|
34147
35670
|
},
|
|
34148
35671
|
switchSession,
|
|
34149
35672
|
cancelSessionSwitch,
|
|
34150
35673
|
loadPlugins: () => listPluginRows(ctx),
|
|
35674
|
+
loadJobs: () => listJobs(ctx, active?.agent),
|
|
34151
35675
|
statusline: statuslineItems,
|
|
34152
35676
|
saveStatusline,
|
|
34153
35677
|
saveTheme,
|
|
@@ -34163,6 +35687,13 @@ async function run(ctx, startup, io) {
|
|
|
34163
35687
|
mountRef.current?.rerender(appElement());
|
|
34164
35688
|
};
|
|
34165
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
|
+
}
|
|
34166
35697
|
if (statuslineWarning !== void 0) setTimeout(() => {
|
|
34167
35698
|
bridge.notify("statusline config unreadable, using defaults: " + statuslineWarning, "warning");
|
|
34168
35699
|
}, 50);
|
|
@@ -34177,22 +35708,27 @@ async function run(ctx, startup, io) {
|
|
|
34177
35708
|
*/
|
|
34178
35709
|
function apply(ctx, config) {
|
|
34179
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
|
+
};
|
|
34180
35716
|
const startup = config.startup.kind === "resume" && config.startup.sessionId !== void 0 ? {
|
|
34181
35717
|
kind: "resume",
|
|
34182
35718
|
sessionId: config.startup.sessionId,
|
|
34183
|
-
...
|
|
35719
|
+
...input
|
|
34184
35720
|
} : config.startup.kind === "latest" ? {
|
|
34185
35721
|
kind: "latest",
|
|
34186
|
-
...
|
|
35722
|
+
...input
|
|
34187
35723
|
} : config.startup.kind === "named" && config.startup.sessionId !== void 0 ? {
|
|
34188
35724
|
kind: "named",
|
|
34189
35725
|
sessionId: config.startup.sessionId,
|
|
34190
35726
|
...config.startup.mode === void 0 ? {} : { mode: config.startup.mode },
|
|
34191
|
-
...
|
|
35727
|
+
...input
|
|
34192
35728
|
} : {
|
|
34193
35729
|
kind: "fresh",
|
|
34194
35730
|
...config.startup.mode === void 0 ? {} : { mode: config.startup.mode },
|
|
34195
|
-
...
|
|
35731
|
+
...input
|
|
34196
35732
|
};
|
|
34197
35733
|
const exit = ctx.get("appExit");
|
|
34198
35734
|
if (exit === void 0) throw new Error("tui-runner: the launcher must provide ctx.appExit before the tree mounts");
|