dsh-code 1.0.1 → 1.0.3
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 +21 -13
- package/README.md +21 -13
- package/lib/index.mjs +1902 -1092
- package/lib/types/app.d.ts +4 -13
- package/lib/types/attachments.d.ts +1 -1
- package/lib/types/editor-keys.d.ts +105 -0
- package/lib/types/git-workflow.d.ts +6 -2
- package/lib/types/keyboard.d.ts +31 -0
- package/lib/types/mentions.d.ts +2 -0
- package/lib/types/model-capabilities.d.ts +82 -0
- package/lib/types/provider-settings.d.ts +7 -0
- package/lib/types/render/animations.d.ts +27 -11
- package/lib/types/render/editor.d.ts +32 -7
- package/lib/types/render/lines.d.ts +26 -1
- package/lib/types/render/markdown.d.ts +1 -1
- package/lib/types/render/projection.d.ts +15 -1
- package/lib/types/render/text.d.ts +15 -9
- package/lib/types/render/width.d.ts +29 -0
- package/lib/types/session-directory.d.ts +27 -0
- package/lib/types/settings-file.d.ts +33 -0
- package/lib/types/store.d.ts +10 -0
- package/lib/types/subagents.d.ts +13 -3
- package/package.json +159 -159
- package/src/app.ts +920 -764
- package/src/attachments.ts +7 -0
- package/src/editor-keys.ts +371 -0
- package/src/git-workflow.ts +10 -6
- package/src/index.ts +1637 -1523
- package/src/internals.ts +26 -9
- package/src/keyboard.ts +131 -7
- package/src/mentions.ts +6 -1
- package/src/model-capabilities.ts +318 -0
- package/src/provider-settings.ts +16 -0
- package/src/render/animations.ts +64 -17
- package/src/render/editor.ts +125 -25
- package/src/render/lines.ts +403 -342
- package/src/render/markdown.ts +4 -7
- package/src/render/projection.ts +63 -40
- package/src/render/text.ts +152 -150
- package/src/render/width.ts +189 -0
- package/src/session-directory.ts +56 -0
- package/src/settings-file.ts +56 -0
- package/src/store.ts +26 -7
- package/src/subagents.ts +39 -6
package/lib/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ 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, open, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
7
|
+
import { mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
9
9
|
import z from "@deepseek-ai/schemastery";
|
|
10
10
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
@@ -14,12 +14,12 @@ 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";
|
|
18
|
+
import { formatSessionReferenceMention, parseSessionReferenceText } from "@deepseek-ai/dsh-session-reference";
|
|
17
19
|
import { execFile, spawn } from "node:child_process";
|
|
18
20
|
import { credentialKeyId, credentialKeyScope } from "@deepseek-ai/dsh-credentials";
|
|
19
21
|
import { AuthorizationDeclinedError } from "@deepseek-ai/dsh-authorization";
|
|
20
22
|
import { fileURLToPath } from "node:url";
|
|
21
|
-
import { DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES, DEFAULT_FILE_SEARCH_MAX_ENTRIES, DEFAULT_FILE_SEARCH_MAX_RESULTS, WorkspaceFileSearch } from "@deepseek-ai/dsh-file-reference-local";
|
|
22
|
-
import { formatSessionReferenceMention, parseSessionReferenceText } from "@deepseek-ai/dsh-session-reference";
|
|
23
23
|
import { UserQuestionError } from "@deepseek-ai/dsh-user-questions";
|
|
24
24
|
import { isUserInvocable } from "@deepseek-ai/dsh-skill";
|
|
25
25
|
//#region node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
|
|
@@ -21657,16 +21657,16 @@ var import_emoji_regex = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(
|
|
|
21657
21657
|
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;
|
|
21658
21658
|
};
|
|
21659
21659
|
})))(), 1);
|
|
21660
|
-
const segmenter$
|
|
21660
|
+
const segmenter$2 = new Intl.Segmenter();
|
|
21661
21661
|
const defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
|
|
21662
|
-
function stringWidth(string, options = {}) {
|
|
21662
|
+
function stringWidth$1(string, options = {}) {
|
|
21663
21663
|
if (typeof string !== "string" || string.length === 0) return 0;
|
|
21664
21664
|
const { ambiguousIsNarrow = true, countAnsiEscapeCodes = false } = options;
|
|
21665
21665
|
if (!countAnsiEscapeCodes) string = stripAnsi(string);
|
|
21666
21666
|
if (string.length === 0) return 0;
|
|
21667
21667
|
let width = 0;
|
|
21668
21668
|
const eastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
|
|
21669
|
-
for (const { segment: character } of segmenter$
|
|
21669
|
+
for (const { segment: character } of segmenter$2.segment(string)) {
|
|
21670
21670
|
const codePoint = character.codePointAt(0);
|
|
21671
21671
|
if (codePoint <= 31 || codePoint >= 127 && codePoint <= 159) continue;
|
|
21672
21672
|
if (codePoint >= 8203 && codePoint <= 8207 || codePoint === 65279) continue;
|
|
@@ -21686,7 +21686,7 @@ function stringWidth(string, options = {}) {
|
|
|
21686
21686
|
//#region node_modules/.pnpm/widest-line@5.0.0/node_modules/widest-line/index.js
|
|
21687
21687
|
function widestLine(string) {
|
|
21688
21688
|
let lineWidth = 0;
|
|
21689
|
-
for (const line of string.split("\n")) lineWidth = Math.max(lineWidth, stringWidth(line));
|
|
21689
|
+
for (const line of string.split("\n")) lineWidth = Math.max(lineWidth, stringWidth$1(line));
|
|
21690
21690
|
return lineWidth;
|
|
21691
21691
|
}
|
|
21692
21692
|
//#endregion
|
|
@@ -21885,14 +21885,14 @@ const ANSI_SGR_TERMINATOR = "m";
|
|
|
21885
21885
|
const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
|
|
21886
21886
|
const wrapAnsiCode = (code) => `${ESCAPES$3.values().next().value}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
|
|
21887
21887
|
const wrapAnsiHyperlink = (url) => `${ESCAPES$3.values().next().value}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
|
|
21888
|
-
const wordLengths = (string) => string.split(" ").map((character) => stringWidth(character));
|
|
21888
|
+
const wordLengths = (string) => string.split(" ").map((character) => stringWidth$1(character));
|
|
21889
21889
|
const wrapWord = (rows, word, columns) => {
|
|
21890
21890
|
const characters = [...word];
|
|
21891
21891
|
let isInsideEscape = false;
|
|
21892
21892
|
let isInsideLinkEscape = false;
|
|
21893
|
-
let visible = stringWidth(stripAnsi(rows.at(-1)));
|
|
21893
|
+
let visible = stringWidth$1(stripAnsi(rows.at(-1)));
|
|
21894
21894
|
for (const [index, character] of characters.entries()) {
|
|
21895
|
-
const characterLength = stringWidth(character);
|
|
21895
|
+
const characterLength = stringWidth$1(character);
|
|
21896
21896
|
if (visible + characterLength <= columns) rows[rows.length - 1] += character;
|
|
21897
21897
|
else {
|
|
21898
21898
|
rows.push(character);
|
|
@@ -21923,7 +21923,7 @@ const stringVisibleTrimSpacesRight = (string) => {
|
|
|
21923
21923
|
const words = string.split(" ");
|
|
21924
21924
|
let last = words.length;
|
|
21925
21925
|
while (last > 0) {
|
|
21926
|
-
if (stringWidth(words[last - 1]) > 0) break;
|
|
21926
|
+
if (stringWidth$1(words[last - 1]) > 0) break;
|
|
21927
21927
|
last--;
|
|
21928
21928
|
}
|
|
21929
21929
|
if (last === words.length) return string;
|
|
@@ -21938,7 +21938,7 @@ const exec = (string, columns, options = {}) => {
|
|
|
21938
21938
|
let rows = [""];
|
|
21939
21939
|
for (const [index, word] of string.split(" ").entries()) {
|
|
21940
21940
|
if (options.trim !== false) rows[rows.length - 1] = rows.at(-1).trimStart();
|
|
21941
|
-
let rowLength = stringWidth(rows.at(-1));
|
|
21941
|
+
let rowLength = stringWidth$1(rows.at(-1));
|
|
21942
21942
|
if (index !== 0) {
|
|
21943
21943
|
if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
|
|
21944
21944
|
rows.push("");
|
|
@@ -22086,7 +22086,7 @@ function cliTruncate(text, columns, options = {}) {
|
|
|
22086
22086
|
if (typeof columns !== "number") throw new TypeError(`Expected \`columns\` to be a number, got ${typeof columns}`);
|
|
22087
22087
|
if (columns < 1) return "";
|
|
22088
22088
|
if (columns === 1) return truncationCharacter;
|
|
22089
|
-
const length = stringWidth(text);
|
|
22089
|
+
const length = stringWidth$1(text);
|
|
22090
22090
|
if (length <= columns) return text;
|
|
22091
22091
|
if (position === "start") {
|
|
22092
22092
|
if (preferTruncationOnSpace) {
|
|
@@ -22094,7 +22094,7 @@ function cliTruncate(text, columns, options = {}) {
|
|
|
22094
22094
|
return truncationCharacter + sliceAnsi$1(text, nearestSpace, length).trim();
|
|
22095
22095
|
}
|
|
22096
22096
|
if (space === true) truncationCharacter += " ";
|
|
22097
|
-
return truncationCharacter + sliceAnsi$1(text, length - columns + stringWidth(truncationCharacter), length);
|
|
22097
|
+
return truncationCharacter + sliceAnsi$1(text, length - columns + stringWidth$1(truncationCharacter), length);
|
|
22098
22098
|
}
|
|
22099
22099
|
if (position === "middle") {
|
|
22100
22100
|
if (space === true) truncationCharacter = ` ${truncationCharacter} `;
|
|
@@ -22104,12 +22104,12 @@ function cliTruncate(text, columns, options = {}) {
|
|
|
22104
22104
|
const spaceNearSecondBreakPoint = getIndexOfNearestSpace(text, length - (columns - half) + 1, true);
|
|
22105
22105
|
return sliceAnsi$1(text, 0, spaceNearFirstBreakPoint) + truncationCharacter + sliceAnsi$1(text, spaceNearSecondBreakPoint, length).trim();
|
|
22106
22106
|
}
|
|
22107
|
-
return sliceAnsi$1(text, 0, half) + truncationCharacter + sliceAnsi$1(text, length - (columns - half) + stringWidth(truncationCharacter), length);
|
|
22107
|
+
return sliceAnsi$1(text, 0, half) + truncationCharacter + sliceAnsi$1(text, length - (columns - half) + stringWidth$1(truncationCharacter), length);
|
|
22108
22108
|
}
|
|
22109
22109
|
if (position === "end") {
|
|
22110
22110
|
if (preferTruncationOnSpace) return sliceAnsi$1(text, 0, getIndexOfNearestSpace(text, columns - 1)) + truncationCharacter;
|
|
22111
22111
|
if (space === true) truncationCharacter = ` ${truncationCharacter}`;
|
|
22112
|
-
return sliceAnsi$1(text, 0, columns - stringWidth(truncationCharacter)) + truncationCharacter;
|
|
22112
|
+
return sliceAnsi$1(text, 0, columns - stringWidth$1(truncationCharacter)) + truncationCharacter;
|
|
22113
22113
|
}
|
|
22114
22114
|
throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${position}`);
|
|
22115
22115
|
}
|
|
@@ -23064,7 +23064,7 @@ var Output = class {
|
|
|
23064
23064
|
if (clipHorizontally) {
|
|
23065
23065
|
lines = lines.map((line) => {
|
|
23066
23066
|
const from = x < clip.x1 ? clip.x1 - x : 0;
|
|
23067
|
-
const width = stringWidth(line);
|
|
23067
|
+
const width = stringWidth$1(line);
|
|
23068
23068
|
return sliceAnsi(line, from, x + width > clip.x2 ? clip.x2 - x : width);
|
|
23069
23069
|
});
|
|
23070
23070
|
if (x < clip.x1) x = clip.x1;
|
|
@@ -24355,6 +24355,180 @@ function followInspectorCursor(cursor, previousLength, nextLength) {
|
|
|
24355
24355
|
return Math.min(cursor, nextLast);
|
|
24356
24356
|
}
|
|
24357
24357
|
//#endregion
|
|
24358
|
+
//#region src/render/width.ts
|
|
24359
|
+
/**
|
|
24360
|
+
* Precise terminal-cell width measurement — the single authority every
|
|
24361
|
+
* budget, wrap, and truncation path shares. The previous heuristic
|
|
24362
|
+
* (`codePoint > 0x2e7f ? 2 : 1)) mis-sized Hangul Jamo (narrow), high
|
|
24363
|
+
* non-CJK code points (wide), and emoji: text-default glyphs like ✳ ⚠ ❤
|
|
24364
|
+
* counted 2 while terminals draw 1, and VS16 sequences counted 1 while
|
|
24365
|
+
* terminals draw 2 — the exact drift class the community dsh-TUI string
|
|
24366
|
+
* engine documents (a spinner glyph drifting one column per frame). This
|
|
24367
|
+
* module adapts that engine's rules without its Ink-fork renderer: an ASCII
|
|
24368
|
+
* fast path, grapheme-cluster iteration via Intl.Segmenter (code-point
|
|
24369
|
+
* fallback), a merged East-Asian-Wide/Fullwidth + Emoji_Presentation range
|
|
24370
|
+
* table, text-default emoji = 1, VS16 = 2, marks/selectors/ZWJ = 0.
|
|
24371
|
+
* @module @deepseek-ai/dsh-code/render/width
|
|
24372
|
+
*/
|
|
24373
|
+
/**
|
|
24374
|
+
* Code-point ranges rendered two cells wide: East Asian Wide/Fullwidth
|
|
24375
|
+
* blocks merged with the Emoji_Presentation set (default-wide emoji) and
|
|
24376
|
+
* regional indicators (flags). Sorted, inclusive, binary-searched.
|
|
24377
|
+
*/
|
|
24378
|
+
const WIDE_RANGES = [
|
|
24379
|
+
[4352, 4447],
|
|
24380
|
+
[8986, 8987],
|
|
24381
|
+
[9001, 9002],
|
|
24382
|
+
[9193, 9196],
|
|
24383
|
+
[9200, 9200],
|
|
24384
|
+
[9203, 9203],
|
|
24385
|
+
[9208, 9210],
|
|
24386
|
+
[9725, 9726],
|
|
24387
|
+
[9748, 9749],
|
|
24388
|
+
[9800, 9811],
|
|
24389
|
+
[9855, 9855],
|
|
24390
|
+
[9875, 9875],
|
|
24391
|
+
[9889, 9889],
|
|
24392
|
+
[9898, 9899],
|
|
24393
|
+
[9917, 9918],
|
|
24394
|
+
[9924, 9925],
|
|
24395
|
+
[9934, 9934],
|
|
24396
|
+
[9940, 9940],
|
|
24397
|
+
[9962, 9962],
|
|
24398
|
+
[9970, 9971],
|
|
24399
|
+
[9973, 9973],
|
|
24400
|
+
[9978, 9978],
|
|
24401
|
+
[9981, 9981],
|
|
24402
|
+
[9989, 9989],
|
|
24403
|
+
[9994, 9995],
|
|
24404
|
+
[10024, 10024],
|
|
24405
|
+
[10060, 10060],
|
|
24406
|
+
[10062, 10062],
|
|
24407
|
+
[10067, 10069],
|
|
24408
|
+
[10071, 10071],
|
|
24409
|
+
[10133, 10135],
|
|
24410
|
+
[10160, 10160],
|
|
24411
|
+
[10175, 10175],
|
|
24412
|
+
[11035, 11036],
|
|
24413
|
+
[11088, 11088],
|
|
24414
|
+
[11093, 11093],
|
|
24415
|
+
[11904, 12350],
|
|
24416
|
+
[12353, 13311],
|
|
24417
|
+
[13312, 19903],
|
|
24418
|
+
[19968, 42191],
|
|
24419
|
+
[43360, 43391],
|
|
24420
|
+
[44032, 55203],
|
|
24421
|
+
[63744, 64255],
|
|
24422
|
+
[65040, 65049],
|
|
24423
|
+
[65072, 65103],
|
|
24424
|
+
[65280, 65376],
|
|
24425
|
+
[65504, 65510],
|
|
24426
|
+
[94176, 94180],
|
|
24427
|
+
[94208, 101119],
|
|
24428
|
+
[110592, 111359],
|
|
24429
|
+
[126980, 126980],
|
|
24430
|
+
[127183, 127183],
|
|
24431
|
+
[127374, 127374],
|
|
24432
|
+
[127377, 127386],
|
|
24433
|
+
[127462, 127487],
|
|
24434
|
+
[127488, 127776],
|
|
24435
|
+
[127789, 127797],
|
|
24436
|
+
[127799, 127868],
|
|
24437
|
+
[127870, 127891],
|
|
24438
|
+
[127904, 127946],
|
|
24439
|
+
[127951, 127955],
|
|
24440
|
+
[127968, 127984],
|
|
24441
|
+
[127988, 127988],
|
|
24442
|
+
[127992, 128062],
|
|
24443
|
+
[128064, 128064],
|
|
24444
|
+
[128066, 128252],
|
|
24445
|
+
[128255, 128317],
|
|
24446
|
+
[128331, 128334],
|
|
24447
|
+
[128336, 128359],
|
|
24448
|
+
[128378, 128378],
|
|
24449
|
+
[128405, 128406],
|
|
24450
|
+
[128420, 128420],
|
|
24451
|
+
[128507, 128591],
|
|
24452
|
+
[128640, 128709],
|
|
24453
|
+
[128716, 128716],
|
|
24454
|
+
[128720, 128722],
|
|
24455
|
+
[128725, 128727],
|
|
24456
|
+
[128747, 128748],
|
|
24457
|
+
[128756, 128764],
|
|
24458
|
+
[128992, 129003],
|
|
24459
|
+
[129292, 129338],
|
|
24460
|
+
[129340, 129349],
|
|
24461
|
+
[129351, 129535],
|
|
24462
|
+
[129648, 129660],
|
|
24463
|
+
[129664, 129673],
|
|
24464
|
+
[129679, 129734],
|
|
24465
|
+
[129742, 129756],
|
|
24466
|
+
[129759, 129769],
|
|
24467
|
+
[129776, 129784],
|
|
24468
|
+
[131072, 196605],
|
|
24469
|
+
[196608, 262141]
|
|
24470
|
+
];
|
|
24471
|
+
/** Whether one code point falls inside a wide range (binary search). */
|
|
24472
|
+
function isWideCodePoint(code) {
|
|
24473
|
+
let low = 0;
|
|
24474
|
+
let high = WIDE_RANGES.length - 1;
|
|
24475
|
+
while (low <= high) {
|
|
24476
|
+
const mid = low + high >> 1;
|
|
24477
|
+
const [start, end] = WIDE_RANGES[mid];
|
|
24478
|
+
if (code < start) high = mid - 1;
|
|
24479
|
+
else if (code > end) low = mid + 1;
|
|
24480
|
+
else return true;
|
|
24481
|
+
}
|
|
24482
|
+
return false;
|
|
24483
|
+
}
|
|
24484
|
+
/** Zero-width code points: combining marks, enclosing marks, format controls. */
|
|
24485
|
+
const ZERO_WIDTH = /^[\p{Mn}\p{Me}\p{Cf}]$/u;
|
|
24486
|
+
/** Module-cached grapheme segmenter; undefined when the runtime lacks it. */
|
|
24487
|
+
let segmenter$1;
|
|
24488
|
+
try {
|
|
24489
|
+
segmenter$1 = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
24490
|
+
} catch {
|
|
24491
|
+
segmenter$1 = void 0;
|
|
24492
|
+
}
|
|
24493
|
+
/** Split text into grapheme clusters (code points when Segmenter is absent). */
|
|
24494
|
+
function splitGraphemes$1(text) {
|
|
24495
|
+
if (segmenter$1 === void 0) return Array.from(text);
|
|
24496
|
+
return Array.from(segmenter$1.segment(text), (part) => part.segment);
|
|
24497
|
+
}
|
|
24498
|
+
/** Terminal-cell width of one grapheme cluster. */
|
|
24499
|
+
function graphemeWidth(cluster) {
|
|
24500
|
+
if (cluster.includes("️", 0)) return 2;
|
|
24501
|
+
const first = cluster.codePointAt(0) ?? 0;
|
|
24502
|
+
if (ZERO_WIDTH.test(String.fromCodePoint(first))) return 0;
|
|
24503
|
+
return isWideCodePoint(first) ? 2 : 1;
|
|
24504
|
+
}
|
|
24505
|
+
/**
|
|
24506
|
+
* Terminal-cell width of a string: an ASCII fast path avoids the segmenter
|
|
24507
|
+
* for the overwhelmingly common case; everything else sums grapheme clusters.
|
|
24508
|
+
* Control characters occupy no cells (display sanitization makes them
|
|
24509
|
+
* visible escapes before they ever reach a budget).
|
|
24510
|
+
* @param text - display-safe or raw text to measure.
|
|
24511
|
+
* @returns the column count the terminal will draw.
|
|
24512
|
+
*/
|
|
24513
|
+
function stringWidth(text) {
|
|
24514
|
+
let asciiOnly = true;
|
|
24515
|
+
for (let index = 0; index < text.length; index += 1) if (text.charCodeAt(index) > 127) {
|
|
24516
|
+
asciiOnly = false;
|
|
24517
|
+
break;
|
|
24518
|
+
}
|
|
24519
|
+
if (asciiOnly) {
|
|
24520
|
+
let columns = 0;
|
|
24521
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
24522
|
+
const code = text.charCodeAt(index);
|
|
24523
|
+
if (code >= 32 && code !== 127) columns += 1;
|
|
24524
|
+
}
|
|
24525
|
+
return columns;
|
|
24526
|
+
}
|
|
24527
|
+
let columns = 0;
|
|
24528
|
+
for (const cluster of splitGraphemes$1(text)) columns += graphemeWidth(cluster);
|
|
24529
|
+
return columns;
|
|
24530
|
+
}
|
|
24531
|
+
//#endregion
|
|
24358
24532
|
//#region src/render/text.ts
|
|
24359
24533
|
/**
|
|
24360
24534
|
* Display-boundary sanitization for externally sourced text (model output,
|
|
@@ -24395,56 +24569,45 @@ function displayText(text) {
|
|
|
24395
24569
|
function singleLineText(text) {
|
|
24396
24570
|
return displayText(text).replace(/\r?\n/gu, " ↵ ").replace(/\t/gu, " ");
|
|
24397
24571
|
}
|
|
24398
|
-
/** Terminal-cell width matching the TUI's existing CJK-aware wrapping rule. */
|
|
24399
|
-
function cellWidth(text) {
|
|
24400
|
-
let columns = 0;
|
|
24401
|
-
for (const char of text) columns += (char.codePointAt(0) ?? 0) > 11903 ? 2 : 1;
|
|
24402
|
-
return columns;
|
|
24403
|
-
}
|
|
24404
24572
|
/**
|
|
24405
24573
|
* Truncate one display-safe row without ever exceeding its physical-column
|
|
24406
24574
|
* budget. The ellipsis is included inside the budget, matching Codex's popup
|
|
24407
|
-
* truncation contract; the
|
|
24408
|
-
*
|
|
24575
|
+
* truncation contract; the cut walks grapheme clusters so emoji and
|
|
24576
|
+
* combining sequences never split mid-cluster.
|
|
24409
24577
|
*/
|
|
24410
24578
|
function truncateColumns(text, columns) {
|
|
24411
24579
|
const limit = Math.max(0, Math.floor(columns));
|
|
24412
24580
|
if (limit === 0) return "";
|
|
24413
|
-
if (
|
|
24581
|
+
if (stringWidth(text) <= limit) return text;
|
|
24414
24582
|
const contentLimit = limit - 1;
|
|
24415
24583
|
let used = 0;
|
|
24416
24584
|
let result = "";
|
|
24417
|
-
for (const
|
|
24418
|
-
const width =
|
|
24585
|
+
for (const cluster of splitGraphemes$1(text)) {
|
|
24586
|
+
const width = graphemeWidth(cluster);
|
|
24419
24587
|
if (used + width > contentLimit) break;
|
|
24420
|
-
result +=
|
|
24588
|
+
result += cluster;
|
|
24421
24589
|
used += width;
|
|
24422
24590
|
}
|
|
24423
24591
|
return `${result}…`;
|
|
24424
24592
|
}
|
|
24425
|
-
/**
|
|
24426
|
-
|
|
24427
|
-
|
|
24428
|
-
|
|
24429
|
-
const first = text.charCodeAt(end - 2);
|
|
24430
|
-
if (first >= 55296 && first <= 56319) return {
|
|
24431
|
-
char: text.slice(end - 2, end),
|
|
24432
|
-
start: end - 2
|
|
24433
|
-
};
|
|
24434
|
-
}
|
|
24435
|
-
return {
|
|
24436
|
-
char: text.slice(end - 1, end),
|
|
24437
|
-
start: end - 1
|
|
24438
|
-
};
|
|
24439
|
-
}
|
|
24593
|
+
/** Punctuation that must never START a physical row (CJK kinsoku tail set). */
|
|
24594
|
+
const ROW_START_FORBIDDEN = ",。、;:!?)】」』〉》…‥";
|
|
24595
|
+
/** Punctuation that must never END a physical row (CJK kinsoku head set). */
|
|
24596
|
+
const ROW_END_FORBIDDEN = "(【「『〈《";
|
|
24440
24597
|
/**
|
|
24441
|
-
* Keep
|
|
24442
|
-
*
|
|
24443
|
-
*
|
|
24444
|
-
*
|
|
24445
|
-
*
|
|
24446
|
-
*
|
|
24447
|
-
*
|
|
24598
|
+
* Keep the newest display-safe text that fits a terminal rectangle, wrapping
|
|
24599
|
+
* FORWARD from the start of the text and slicing the tail rows.
|
|
24600
|
+
*
|
|
24601
|
+
* Forward wrapping is what keeps a streaming tail calm: rows already produced
|
|
24602
|
+
* never re-wrap as tokens append (a backward scan recomputes every wrap point
|
|
24603
|
+
* per chunk and the whole visible block jumps), and the wrap rules match the
|
|
24604
|
+
* settled text's renderer so the flush at turn end does not reflow the block
|
|
24605
|
+
* a second time. CJK kinsoku applies at both edges: closing punctuation
|
|
24606
|
+
* overhangs up to two cells onto the filled row instead of starting the next
|
|
24607
|
+
* one (within the caret column the caller reserves), and opening punctuation
|
|
24608
|
+
* moves down instead of dangling at a row end. Tabs expand to two spaces so
|
|
24609
|
+
* terminal tab stops cannot inflate the physical row count; clusters carry
|
|
24610
|
+
* emoji presentation and combining marks whole.
|
|
24448
24611
|
* @param text - raw externally sourced text.
|
|
24449
24612
|
* @param columns - available terminal columns.
|
|
24450
24613
|
* @param rows - available terminal rows.
|
|
@@ -24453,38 +24616,43 @@ function previousCharacter(text, end) {
|
|
|
24453
24616
|
function displayTail(text, columns, rows) {
|
|
24454
24617
|
const columnLimit = Math.max(1, Math.floor(columns));
|
|
24455
24618
|
const rowLimit = Math.max(1, Math.floor(rows));
|
|
24456
|
-
const
|
|
24457
|
-
let
|
|
24619
|
+
const wrapped = [];
|
|
24620
|
+
let current = "";
|
|
24458
24621
|
let used = 0;
|
|
24459
|
-
let
|
|
24460
|
-
|
|
24461
|
-
|
|
24462
|
-
|
|
24463
|
-
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24622
|
+
let lastCluster = "";
|
|
24623
|
+
const flush = () => {
|
|
24624
|
+
wrapped.push(current);
|
|
24625
|
+
current = "";
|
|
24626
|
+
used = 0;
|
|
24627
|
+
lastCluster = "";
|
|
24628
|
+
};
|
|
24629
|
+
for (const cluster of splitGraphemes$1(text)) {
|
|
24630
|
+
if (cluster === "\n") {
|
|
24631
|
+
flush();
|
|
24468
24632
|
continue;
|
|
24469
24633
|
}
|
|
24470
|
-
const safe =
|
|
24471
|
-
const width =
|
|
24634
|
+
const safe = cluster === " " ? " " : displayText(cluster);
|
|
24635
|
+
const width = stringWidth(safe);
|
|
24472
24636
|
if (used > 0 && used + width > columnLimit) {
|
|
24473
|
-
if (
|
|
24474
|
-
|
|
24475
|
-
|
|
24476
|
-
|
|
24477
|
-
|
|
24478
|
-
|
|
24479
|
-
|
|
24480
|
-
|
|
24481
|
-
|
|
24482
|
-
|
|
24483
|
-
|
|
24637
|
+
if (!(width <= 2 && ROW_START_FORBIDDEN.includes(cluster))) {
|
|
24638
|
+
if (lastCluster !== "" && ROW_END_FORBIDDEN.includes(lastCluster)) {
|
|
24639
|
+
const carried = lastCluster;
|
|
24640
|
+
current = current.slice(0, current.length - carried.length);
|
|
24641
|
+
flush();
|
|
24642
|
+
current = carried;
|
|
24643
|
+
used = stringWidth(carried);
|
|
24644
|
+
} else flush();
|
|
24645
|
+
}
|
|
24646
|
+
}
|
|
24647
|
+
current += safe;
|
|
24648
|
+
used += width;
|
|
24649
|
+
lastCluster = cluster;
|
|
24484
24650
|
}
|
|
24651
|
+
if (current !== "" || wrapped.length > 0 && text.endsWith("\n")) flush();
|
|
24652
|
+
const truncated = wrapped.length > rowLimit;
|
|
24485
24653
|
return {
|
|
24486
|
-
text:
|
|
24487
|
-
truncated
|
|
24654
|
+
text: (truncated ? wrapped.slice(-rowLimit) : wrapped).join("\n"),
|
|
24655
|
+
truncated
|
|
24488
24656
|
};
|
|
24489
24657
|
}
|
|
24490
24658
|
//#endregion
|
|
@@ -24947,535 +25115,14 @@ function reasoningOf(content) {
|
|
|
24947
25115
|
function estimateTokens(text) {
|
|
24948
25116
|
let wide = 0;
|
|
24949
25117
|
let narrow = 0;
|
|
24950
|
-
for (const
|
|
25118
|
+
for (const cluster of splitGraphemes$1(text)) if (graphemeWidth(cluster) > 1) wide += 1;
|
|
24951
25119
|
else narrow += 1;
|
|
24952
25120
|
return wide + Math.ceil(narrow / 4);
|
|
24953
25121
|
}
|
|
24954
|
-
/** A fresh, empty transcript view. */
|
|
24955
|
-
function createTranscriptView() {
|
|
24956
|
-
return {
|
|
24957
|
-
entries: [],
|
|
24958
|
-
streaming: "",
|
|
24959
|
-
streamingReasoning: "",
|
|
24960
|
-
todos: [],
|
|
24961
|
-
toolCallOrdinal: 0,
|
|
24962
|
-
busy: false,
|
|
24963
|
-
busySince: 0,
|
|
24964
|
-
model: "",
|
|
24965
|
-
plan: false,
|
|
24966
|
-
permission: "",
|
|
24967
|
-
title: "",
|
|
24968
|
-
sandbox: "",
|
|
24969
|
-
goal: void 0,
|
|
24970
|
-
pending: {
|
|
24971
|
-
"next-turn": [],
|
|
24972
|
-
"next-step": []
|
|
24973
|
-
},
|
|
24974
|
-
stats: {
|
|
24975
|
-
turns: 0,
|
|
24976
|
-
steps: 0,
|
|
24977
|
-
llmMs: 0,
|
|
24978
|
-
toolMs: 0,
|
|
24979
|
-
usage: {
|
|
24980
|
-
inputTokens: 0,
|
|
24981
|
-
outputTokens: 0,
|
|
24982
|
-
cacheReadTokens: 0
|
|
24983
|
-
},
|
|
24984
|
-
lastPromptTokens: 0,
|
|
24985
|
-
contextWindow: 0,
|
|
24986
|
-
contextSegments: {
|
|
24987
|
-
system: 0,
|
|
24988
|
-
prompt: 0,
|
|
24989
|
-
assistant: 0,
|
|
24990
|
-
thinking: 0,
|
|
24991
|
-
tools: 0
|
|
24992
|
-
},
|
|
24993
|
-
ttftMs: 0,
|
|
24994
|
-
ttftSteps: 0,
|
|
24995
|
-
decodeMs: 0,
|
|
24996
|
-
decodeTokens: 0,
|
|
24997
|
-
reasoningEffort: ""
|
|
24998
|
-
},
|
|
24999
|
-
anchors: {
|
|
25000
|
-
stepStart: /* @__PURE__ */ new Map(),
|
|
25001
|
-
toolStart: /* @__PURE__ */ new Map(),
|
|
25002
|
-
firstChunkAt: /* @__PURE__ */ new Map(),
|
|
25003
|
-
compactionTokens: /* @__PURE__ */ new Map(),
|
|
25004
|
-
lastPruneTokens: 0,
|
|
25005
|
-
turnFiles: /* @__PURE__ */ new Map(),
|
|
25006
|
-
turnSteps: /* @__PURE__ */ new Map(),
|
|
25007
|
-
turnTools: /* @__PURE__ */ new Map()
|
|
25008
|
-
}
|
|
25009
|
-
};
|
|
25010
|
-
}
|
|
25011
25122
|
/** Full prompt text of a queued message (identical to the durable user row it retires into). */
|
|
25012
25123
|
function pendingText(content) {
|
|
25013
25124
|
return textOf(content);
|
|
25014
25125
|
}
|
|
25015
|
-
/**
|
|
25016
|
-
* Fold one session event into an updated view (copy-on-write).
|
|
25017
|
-
* @param view - the view before the event.
|
|
25018
|
-
* @param event - one durable session event from `session/event` or the log.
|
|
25019
|
-
* @returns the view after the event; the input view is never mutated.
|
|
25020
|
-
*/
|
|
25021
|
-
function projectEvent(view, event) {
|
|
25022
|
-
switch (event.type) {
|
|
25023
|
-
case "user/message": {
|
|
25024
|
-
const message = event.data;
|
|
25025
|
-
let entries = view.entries;
|
|
25026
|
-
let pending = view.pending;
|
|
25027
|
-
for (const target of ["next-turn", "next-step"]) {
|
|
25028
|
-
const index = pending[target].indexOf(message.id);
|
|
25029
|
-
if (index < 0) continue;
|
|
25030
|
-
pending = {
|
|
25031
|
-
...pending,
|
|
25032
|
-
[target]: pending[target].filter((_, i) => i !== index)
|
|
25033
|
-
};
|
|
25034
|
-
entries = entries.filter((entry) => !(entry.kind === "pending" && entry.messageId === message.id));
|
|
25035
|
-
}
|
|
25036
|
-
const text = textOf(message.content);
|
|
25037
|
-
const images = imagesOf(message.content);
|
|
25038
|
-
if (message.source.kind === "user") return {
|
|
25039
|
-
...view,
|
|
25040
|
-
pending,
|
|
25041
|
-
entries: [...entries, {
|
|
25042
|
-
kind: "user",
|
|
25043
|
-
text,
|
|
25044
|
-
notice: false,
|
|
25045
|
-
...images.length === 0 ? {} : { images }
|
|
25046
|
-
}],
|
|
25047
|
-
stats: {
|
|
25048
|
-
...view.stats,
|
|
25049
|
-
contextSegments: {
|
|
25050
|
-
...view.stats.contextSegments,
|
|
25051
|
-
prompt: view.stats.contextSegments.prompt + estimateTokens(text)
|
|
25052
|
-
}
|
|
25053
|
-
}
|
|
25054
|
-
};
|
|
25055
|
-
const notice = message.source.kind === "plugin" && message.source.form === "notice" ? message.source.summary : message.source.kind;
|
|
25056
|
-
const summary = boundContextSummary(notice);
|
|
25057
|
-
return {
|
|
25058
|
-
...view,
|
|
25059
|
-
pending,
|
|
25060
|
-
entries: [...entries, {
|
|
25061
|
-
kind: "user",
|
|
25062
|
-
text: summary,
|
|
25063
|
-
notice: true
|
|
25064
|
-
}],
|
|
25065
|
-
stats: {
|
|
25066
|
-
...view.stats,
|
|
25067
|
-
contextSegments: {
|
|
25068
|
-
...view.stats.contextSegments,
|
|
25069
|
-
system: view.stats.contextSegments.system + estimateTokens(summary)
|
|
25070
|
-
}
|
|
25071
|
-
}
|
|
25072
|
-
};
|
|
25073
|
-
}
|
|
25074
|
-
case "agent/inbox/spliced": {
|
|
25075
|
-
const { target, start, removedCount = 0, inserted } = event.data;
|
|
25076
|
-
const ids = view.pending[target];
|
|
25077
|
-
const removed = ids.slice(start, start + removedCount);
|
|
25078
|
-
const nextIds = [
|
|
25079
|
-
...ids.slice(0, start),
|
|
25080
|
-
...ids.slice(start + removedCount),
|
|
25081
|
-
...inserted.map((message) => message.id)
|
|
25082
|
-
];
|
|
25083
|
-
let entries = view.entries;
|
|
25084
|
-
if (removed.length > 0) {
|
|
25085
|
-
const removedSet = new Set(removed);
|
|
25086
|
-
entries = entries.filter((entry) => !(entry.kind === "pending" && entry.target === target && removedSet.has(entry.messageId)));
|
|
25087
|
-
}
|
|
25088
|
-
for (const message of inserted) entries = [...entries, {
|
|
25089
|
-
kind: "pending",
|
|
25090
|
-
messageId: message.id,
|
|
25091
|
-
target,
|
|
25092
|
-
text: pendingText(message.content),
|
|
25093
|
-
...imagesOf(message.content).length === 0 ? {} : { images: imagesOf(message.content) }
|
|
25094
|
-
}];
|
|
25095
|
-
return {
|
|
25096
|
-
...view,
|
|
25097
|
-
entries,
|
|
25098
|
-
pending: {
|
|
25099
|
-
...view.pending,
|
|
25100
|
-
[target]: nextIds
|
|
25101
|
-
}
|
|
25102
|
-
};
|
|
25103
|
-
}
|
|
25104
|
-
case "assistant/chunk": {
|
|
25105
|
-
const chunk = event.data.chunk;
|
|
25106
|
-
const key = `${event.data.turn}:${event.data.step}`;
|
|
25107
|
-
const delta = chunk.type === "text-delta" || chunk.type === "reasoning-delta" ? chunk.text : "";
|
|
25108
|
-
let stats = view.stats;
|
|
25109
|
-
if (delta !== "" && !view.anchors.firstChunkAt.has(key)) {
|
|
25110
|
-
view.anchors.firstChunkAt.set(key, event.time);
|
|
25111
|
-
const started = view.anchors.stepStart.get(key);
|
|
25112
|
-
if (started !== void 0) stats = {
|
|
25113
|
-
...stats,
|
|
25114
|
-
ttftMs: stats.ttftMs + Math.max(0, event.time - started),
|
|
25115
|
-
ttftSteps: stats.ttftSteps + 1
|
|
25116
|
-
};
|
|
25117
|
-
}
|
|
25118
|
-
if (chunk.type === "text-delta") return {
|
|
25119
|
-
...view,
|
|
25120
|
-
streaming: appendStreamingTail(view.streaming, chunk.text),
|
|
25121
|
-
stats
|
|
25122
|
-
};
|
|
25123
|
-
if (chunk.type === "reasoning-delta") return {
|
|
25124
|
-
...view,
|
|
25125
|
-
streamingReasoning: appendStreamingTail(view.streamingReasoning, chunk.text),
|
|
25126
|
-
stats
|
|
25127
|
-
};
|
|
25128
|
-
return view;
|
|
25129
|
-
}
|
|
25130
|
-
case "assistant/message": {
|
|
25131
|
-
const key = `${event.data.turn}:${event.data.step}`;
|
|
25132
|
-
const started = view.anchors.stepStart.get(key);
|
|
25133
|
-
view.anchors.stepStart.delete(key);
|
|
25134
|
-
const firstChunk = view.anchors.firstChunkAt.get(key);
|
|
25135
|
-
view.anchors.firstChunkAt.delete(key);
|
|
25136
|
-
if (view.anchors.turnSteps.get(event.data.turn) === key) view.anchors.turnSteps.delete(event.data.turn);
|
|
25137
|
-
const usage = event.data.usage;
|
|
25138
|
-
const totals = view.stats.usage;
|
|
25139
|
-
const text = textOf(event.data.message.content);
|
|
25140
|
-
const reasoning = reasoningOf(event.data.message.content);
|
|
25141
|
-
return {
|
|
25142
|
-
...view,
|
|
25143
|
-
streaming: "",
|
|
25144
|
-
streamingReasoning: "",
|
|
25145
|
-
entries: [...view.entries, {
|
|
25146
|
-
kind: "assistant",
|
|
25147
|
-
text,
|
|
25148
|
-
reasoning,
|
|
25149
|
-
interrupted: event.data.interrupted === true ? true : void 0
|
|
25150
|
-
}],
|
|
25151
|
-
stats: {
|
|
25152
|
-
...view.stats,
|
|
25153
|
-
llmMs: view.stats.llmMs + (started === void 0 ? 0 : Math.max(0, event.time - started)),
|
|
25154
|
-
usage: usage === void 0 ? totals : {
|
|
25155
|
-
inputTokens: totals.inputTokens + usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0),
|
|
25156
|
-
outputTokens: totals.outputTokens + usage.outputTokens,
|
|
25157
|
-
cacheReadTokens: totals.cacheReadTokens + (usage.cacheReadTokens ?? 0)
|
|
25158
|
-
},
|
|
25159
|
-
lastPromptTokens: usage === void 0 ? view.stats.lastPromptTokens : usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0),
|
|
25160
|
-
decodeMs: view.stats.decodeMs + (firstChunk === void 0 ? 0 : Math.max(0, event.time - firstChunk)),
|
|
25161
|
-
decodeTokens: view.stats.decodeTokens + (firstChunk === void 0 || usage === void 0 ? 0 : usage.outputTokens),
|
|
25162
|
-
contextSegments: {
|
|
25163
|
-
...view.stats.contextSegments,
|
|
25164
|
-
thinking: view.stats.contextSegments.thinking + estimateTokens(reasoning),
|
|
25165
|
-
assistant: view.stats.contextSegments.assistant + estimateTokens(text)
|
|
25166
|
-
}
|
|
25167
|
-
}
|
|
25168
|
-
};
|
|
25169
|
-
}
|
|
25170
|
-
case "tool/call": {
|
|
25171
|
-
const data = event.data;
|
|
25172
|
-
view.anchors.toolStart.set(data.callId, event.time);
|
|
25173
|
-
const turnTools = view.anchors.turnTools.get(data.turn) ?? /* @__PURE__ */ new Set();
|
|
25174
|
-
turnTools.add(data.callId);
|
|
25175
|
-
view.anchors.turnTools.set(data.turn, turnTools);
|
|
25176
|
-
const ordinal = view.toolCallOrdinal + 1;
|
|
25177
|
-
return {
|
|
25178
|
-
...view,
|
|
25179
|
-
toolCallOrdinal: ordinal,
|
|
25180
|
-
entries: [...view.entries, {
|
|
25181
|
-
kind: "tool",
|
|
25182
|
-
callId: data.callId,
|
|
25183
|
-
ordinal,
|
|
25184
|
-
name: data.name,
|
|
25185
|
-
arguments: data.arguments,
|
|
25186
|
-
preview: toolArgumentsPreview(data.arguments, data.name),
|
|
25187
|
-
prompt: toolPromptPreview(data.name, data.arguments),
|
|
25188
|
-
state: "running",
|
|
25189
|
-
summary: "",
|
|
25190
|
-
detail: void 0
|
|
25191
|
-
}],
|
|
25192
|
-
stats: {
|
|
25193
|
-
...view.stats,
|
|
25194
|
-
contextSegments: {
|
|
25195
|
-
...view.stats.contextSegments,
|
|
25196
|
-
tools: view.stats.contextSegments.tools + (typeof data.arguments === "string" ? estimateTokens(data.arguments) : 0)
|
|
25197
|
-
}
|
|
25198
|
-
}
|
|
25199
|
-
};
|
|
25200
|
-
}
|
|
25201
|
-
case "tool/result": {
|
|
25202
|
-
const block = event.data.message.content[0];
|
|
25203
|
-
const started = view.anchors.toolStart.get(block.toolCallId);
|
|
25204
|
-
view.anchors.toolStart.delete(block.toolCallId);
|
|
25205
|
-
const turnTools = view.anchors.turnTools.get(event.data.turn);
|
|
25206
|
-
if (turnTools !== void 0) {
|
|
25207
|
-
turnTools.delete(block.toolCallId);
|
|
25208
|
-
if (turnTools.size === 0) view.anchors.turnTools.delete(event.data.turn);
|
|
25209
|
-
}
|
|
25210
|
-
const rawText = textOf(block.content);
|
|
25211
|
-
const summary = boundContextSummary(rawText);
|
|
25212
|
-
const detail = toolResultDetail(event.data.meta, rawText);
|
|
25213
|
-
if (detail?.kind === "diff") {
|
|
25214
|
-
const set = view.anchors.turnFiles.get(event.data.turn) ?? /* @__PURE__ */ new Set();
|
|
25215
|
-
for (const diff of detail.diffs) set.add(diff.path);
|
|
25216
|
-
view.anchors.turnFiles.set(event.data.turn, set);
|
|
25217
|
-
}
|
|
25218
|
-
const entries = view.entries.map((entry) => {
|
|
25219
|
-
if (entry.kind !== "tool" || entry.callId !== block.toolCallId) return entry;
|
|
25220
|
-
return {
|
|
25221
|
-
...entry,
|
|
25222
|
-
state: block.isError === true ? "error" : "done",
|
|
25223
|
-
summary,
|
|
25224
|
-
detail
|
|
25225
|
-
};
|
|
25226
|
-
});
|
|
25227
|
-
return {
|
|
25228
|
-
...view,
|
|
25229
|
-
entries,
|
|
25230
|
-
stats: {
|
|
25231
|
-
...view.stats,
|
|
25232
|
-
toolMs: view.stats.toolMs + (started === void 0 ? 0 : Math.max(0, event.time - started)),
|
|
25233
|
-
contextSegments: {
|
|
25234
|
-
...view.stats.contextSegments,
|
|
25235
|
-
tools: view.stats.contextSegments.tools + estimateTokens(rawText)
|
|
25236
|
-
}
|
|
25237
|
-
}
|
|
25238
|
-
};
|
|
25239
|
-
}
|
|
25240
|
-
case "todo/write": return {
|
|
25241
|
-
...view,
|
|
25242
|
-
todos: event.data.todos
|
|
25243
|
-
};
|
|
25244
|
-
case "turn/start": return {
|
|
25245
|
-
...view,
|
|
25246
|
-
busy: true,
|
|
25247
|
-
busySince: view.busy ? view.busySince : event.time,
|
|
25248
|
-
todos: [],
|
|
25249
|
-
stats: {
|
|
25250
|
-
...view.stats,
|
|
25251
|
-
turns: view.stats.turns + 1
|
|
25252
|
-
}
|
|
25253
|
-
};
|
|
25254
|
-
case "step/start": {
|
|
25255
|
-
const key = `${event.data.turn}:${event.data.step}`;
|
|
25256
|
-
const previous = view.anchors.turnSteps.get(event.data.turn);
|
|
25257
|
-
if (previous !== void 0 && previous !== key) {
|
|
25258
|
-
view.anchors.stepStart.delete(previous);
|
|
25259
|
-
view.anchors.firstChunkAt.delete(previous);
|
|
25260
|
-
}
|
|
25261
|
-
view.anchors.turnSteps.set(event.data.turn, key);
|
|
25262
|
-
view.anchors.stepStart.set(key, event.time);
|
|
25263
|
-
return {
|
|
25264
|
-
...view,
|
|
25265
|
-
streaming: "",
|
|
25266
|
-
streamingReasoning: "",
|
|
25267
|
-
stats: {
|
|
25268
|
-
...view.stats,
|
|
25269
|
-
steps: view.stats.steps + 1
|
|
25270
|
-
}
|
|
25271
|
-
};
|
|
25272
|
-
}
|
|
25273
|
-
case "turn/end": {
|
|
25274
|
-
const reason = event.data.reason;
|
|
25275
|
-
const appended = [];
|
|
25276
|
-
if (reason.kind === "error") {
|
|
25277
|
-
const recovery = reason.error.code === "MISSING_CREDENTIAL" ? " · open /model to add an API key" : "";
|
|
25278
|
-
appended.push({
|
|
25279
|
-
kind: "error",
|
|
25280
|
-
text: `${reason.error.code}: ${reason.error.message}${recovery}`
|
|
25281
|
-
});
|
|
25282
|
-
} else {
|
|
25283
|
-
const marker = reason.kind === "aborted" ? reason.reason.kind === "user" ? "turn cancelled by the user" : `turn cancelled (${reason.reason.kind})` : reason.kind === "max-tokens" ? "turn hit the output-token ceiling (max-tokens)" : reason.kind === "blocked" ? "turn ended blocked" : reason.kind === "interrupted" ? "turn was interrupted by a restart" : void 0;
|
|
25284
|
-
if (marker !== void 0) appended.push({
|
|
25285
|
-
kind: "turn-marker",
|
|
25286
|
-
text: marker
|
|
25287
|
-
});
|
|
25288
|
-
}
|
|
25289
|
-
const files = view.anchors.turnFiles.get(event.data.turn);
|
|
25290
|
-
view.anchors.turnFiles.delete(event.data.turn);
|
|
25291
|
-
if (files !== void 0 && files.size > 0) appended.push({
|
|
25292
|
-
kind: "files",
|
|
25293
|
-
paths: [...files].slice(0, 12)
|
|
25294
|
-
});
|
|
25295
|
-
const stepKey = view.anchors.turnSteps.get(event.data.turn);
|
|
25296
|
-
if (stepKey !== void 0) {
|
|
25297
|
-
view.anchors.stepStart.delete(stepKey);
|
|
25298
|
-
view.anchors.firstChunkAt.delete(stepKey);
|
|
25299
|
-
view.anchors.turnSteps.delete(event.data.turn);
|
|
25300
|
-
}
|
|
25301
|
-
const turnToolSet = view.anchors.turnTools.get(event.data.turn);
|
|
25302
|
-
if (turnToolSet !== void 0) {
|
|
25303
|
-
for (const callId of turnToolSet) view.anchors.toolStart.delete(callId);
|
|
25304
|
-
view.anchors.turnTools.delete(event.data.turn);
|
|
25305
|
-
}
|
|
25306
|
-
if (appended.length === 0) return {
|
|
25307
|
-
...view,
|
|
25308
|
-
busy: false,
|
|
25309
|
-
busySince: 0,
|
|
25310
|
-
streaming: "",
|
|
25311
|
-
streamingReasoning: ""
|
|
25312
|
-
};
|
|
25313
|
-
return {
|
|
25314
|
-
...view,
|
|
25315
|
-
busy: false,
|
|
25316
|
-
busySince: 0,
|
|
25317
|
-
streaming: "",
|
|
25318
|
-
streamingReasoning: "",
|
|
25319
|
-
entries: [...view.entries, ...appended]
|
|
25320
|
-
};
|
|
25321
|
-
}
|
|
25322
|
-
case "llm/retry": {
|
|
25323
|
-
const data = event.data;
|
|
25324
|
-
return {
|
|
25325
|
-
...view,
|
|
25326
|
-
streaming: "",
|
|
25327
|
-
streamingReasoning: "",
|
|
25328
|
-
entries: [...view.entries, {
|
|
25329
|
-
kind: "retry",
|
|
25330
|
-
retryId: data.retryId,
|
|
25331
|
-
attempt: data.retry,
|
|
25332
|
-
max: "maxRetries" in data ? data.maxRetries : data.retry,
|
|
25333
|
-
code: data.failure.code,
|
|
25334
|
-
delayMs: data.delayMs,
|
|
25335
|
-
state: "running"
|
|
25336
|
-
}]
|
|
25337
|
-
};
|
|
25338
|
-
}
|
|
25339
|
-
case "llm/retry-started": {
|
|
25340
|
-
const data = event.data;
|
|
25341
|
-
const entries = view.entries.map((entry) => {
|
|
25342
|
-
if (entry.kind !== "retry" || entry.retryId !== data.retryId) return entry;
|
|
25343
|
-
return {
|
|
25344
|
-
...entry,
|
|
25345
|
-
state: "done"
|
|
25346
|
-
};
|
|
25347
|
-
});
|
|
25348
|
-
return {
|
|
25349
|
-
...view,
|
|
25350
|
-
entries
|
|
25351
|
-
};
|
|
25352
|
-
}
|
|
25353
|
-
case "sandbox/mode": return {
|
|
25354
|
-
...view,
|
|
25355
|
-
sandbox: event.data.mode
|
|
25356
|
-
};
|
|
25357
|
-
case "goal/change": {
|
|
25358
|
-
const data = event.data;
|
|
25359
|
-
const clip = (text) => text.length > 60 ? `${text.slice(0, 59)}…` : text;
|
|
25360
|
-
if (data.operation === "clear") return {
|
|
25361
|
-
...view,
|
|
25362
|
-
goal: void 0,
|
|
25363
|
-
entries: [...view.entries, {
|
|
25364
|
-
kind: "turn-marker",
|
|
25365
|
-
text: "◎ goal cleared"
|
|
25366
|
-
}]
|
|
25367
|
-
};
|
|
25368
|
-
const goal = {
|
|
25369
|
-
objective: data.goal.objective,
|
|
25370
|
-
phase: data.goal.phase,
|
|
25371
|
-
rounds: data.roundsStarted,
|
|
25372
|
-
max: data.goal.maxGoalRounds,
|
|
25373
|
-
blocked: data.goal.blockedReason?.message ?? ""
|
|
25374
|
-
};
|
|
25375
|
-
const line = data.operation === "create" ? `◎ goal: ${clip(data.goal.objective)}` : data.operation === "complete" ? "◎ goal complete" : data.operation === "pause" ? "◎ goal paused" : data.operation === "resume" ? "◎ goal resumed" : data.operation === "block" ? `◎ goal blocked: ${clip(goal.blocked)}` : void 0;
|
|
25376
|
-
return {
|
|
25377
|
-
...view,
|
|
25378
|
-
goal,
|
|
25379
|
-
entries: line === void 0 ? view.entries : [...view.entries, {
|
|
25380
|
-
kind: "turn-marker",
|
|
25381
|
-
text: line
|
|
25382
|
-
}]
|
|
25383
|
-
};
|
|
25384
|
-
}
|
|
25385
|
-
case "session/title": return {
|
|
25386
|
-
...view,
|
|
25387
|
-
title: event.data.title
|
|
25388
|
-
};
|
|
25389
|
-
case "compaction/summary":
|
|
25390
|
-
if (view.anchors.compactionTokens.size >= MAX_COMPACTION_SUMMARY_RESIDUE) {
|
|
25391
|
-
const oldest = view.anchors.compactionTokens.keys().next().value;
|
|
25392
|
-
if (oldest !== void 0) view.anchors.compactionTokens.delete(oldest);
|
|
25393
|
-
}
|
|
25394
|
-
view.anchors.compactionTokens.set(event.data.compactionId, event.data.shadowedTokenCount);
|
|
25395
|
-
return view;
|
|
25396
|
-
case "compaction/prune": return {
|
|
25397
|
-
...view,
|
|
25398
|
-
anchors: {
|
|
25399
|
-
...view.anchors,
|
|
25400
|
-
lastPruneTokens: event.data.shadowedTokenCount
|
|
25401
|
-
}
|
|
25402
|
-
};
|
|
25403
|
-
case "compaction/end": {
|
|
25404
|
-
const ok = event.data.error === void 0;
|
|
25405
|
-
const tokens = view.anchors.compactionTokens.get(event.data.compactionId) ?? view.anchors.lastPruneTokens;
|
|
25406
|
-
view.anchors.compactionTokens.delete(event.data.compactionId);
|
|
25407
|
-
return {
|
|
25408
|
-
...view,
|
|
25409
|
-
entries: [...view.entries, {
|
|
25410
|
-
kind: "compaction",
|
|
25411
|
-
ok,
|
|
25412
|
-
tokens,
|
|
25413
|
-
error: event.data.error ?? ""
|
|
25414
|
-
}]
|
|
25415
|
-
};
|
|
25416
|
-
}
|
|
25417
|
-
case "request/context": return {
|
|
25418
|
-
...view,
|
|
25419
|
-
stats: {
|
|
25420
|
-
...view.stats,
|
|
25421
|
-
contextWindow: event.data.contextWindow ?? view.stats.contextWindow
|
|
25422
|
-
}
|
|
25423
|
-
};
|
|
25424
|
-
case "request/header": {
|
|
25425
|
-
const config = event.data.header.config;
|
|
25426
|
-
return {
|
|
25427
|
-
...view,
|
|
25428
|
-
model: `${config.provider}/${config.model}`,
|
|
25429
|
-
stats: {
|
|
25430
|
-
...view.stats,
|
|
25431
|
-
reasoningEffort: config.reasoningEffort === void 0 ? "" : String(config.reasoningEffort),
|
|
25432
|
-
contextSegments: {
|
|
25433
|
-
...view.stats.contextSegments,
|
|
25434
|
-
system: estimateTokens(event.data.header.system ?? "")
|
|
25435
|
-
}
|
|
25436
|
-
}
|
|
25437
|
-
};
|
|
25438
|
-
}
|
|
25439
|
-
case "plan/mode": return {
|
|
25440
|
-
...view,
|
|
25441
|
-
plan: event.data.active
|
|
25442
|
-
};
|
|
25443
|
-
case "permission/preset": return {
|
|
25444
|
-
...view,
|
|
25445
|
-
permission: event.data.preset
|
|
25446
|
-
};
|
|
25447
|
-
case "command/run": {
|
|
25448
|
-
const data = event.data;
|
|
25449
|
-
return {
|
|
25450
|
-
...view,
|
|
25451
|
-
entries: [...view.entries, {
|
|
25452
|
-
kind: "command",
|
|
25453
|
-
commandId: data.commandId,
|
|
25454
|
-
name: data.name,
|
|
25455
|
-
args: data.args ?? "",
|
|
25456
|
-
state: "running",
|
|
25457
|
-
summary: ""
|
|
25458
|
-
}]
|
|
25459
|
-
};
|
|
25460
|
-
}
|
|
25461
|
-
case "command/done": {
|
|
25462
|
-
const data = event.data;
|
|
25463
|
-
const entries = view.entries.map((entry) => {
|
|
25464
|
-
if (entry.kind !== "command" || entry.commandId !== data.commandId) return entry;
|
|
25465
|
-
return {
|
|
25466
|
-
...entry,
|
|
25467
|
-
state: data.kind === "success" ? "done" : "error",
|
|
25468
|
-
summary: boundContextSummary(data.text ?? "")
|
|
25469
|
-
};
|
|
25470
|
-
});
|
|
25471
|
-
return {
|
|
25472
|
-
...view,
|
|
25473
|
-
entries
|
|
25474
|
-
};
|
|
25475
|
-
}
|
|
25476
|
-
default: return view;
|
|
25477
|
-
}
|
|
25478
|
-
}
|
|
25479
25126
|
/** @internal A fresh replay accumulator whose state mirrors `createTranscriptView()`. */
|
|
25480
25127
|
function createReplayAccumulator() {
|
|
25481
25128
|
return {
|
|
@@ -25586,6 +25233,9 @@ function retireReplayEntry(acc, index) {
|
|
|
25586
25233
|
* to a sequential fold; only the `entries` container operations are mutable.
|
|
25587
25234
|
*
|
|
25588
25235
|
* @internal Test-instrumentation path; `projectEvents` is the public entry.
|
|
25236
|
+
* @returns whether the event changed the accumulated state — the live store
|
|
25237
|
+
* stays silent and keeps its snapshot identity for ignored events, exactly
|
|
25238
|
+
* like the copy-on-write reducer returning its input view unchanged.
|
|
25589
25239
|
*/
|
|
25590
25240
|
function replayProjectEvent(acc, event) {
|
|
25591
25241
|
switch (event.type) {
|
|
@@ -25620,7 +25270,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25620
25270
|
prompt: acc.stats.contextSegments.prompt + estimateTokens(text)
|
|
25621
25271
|
}
|
|
25622
25272
|
};
|
|
25623
|
-
return;
|
|
25273
|
+
return true;
|
|
25624
25274
|
}
|
|
25625
25275
|
const notice = message.source.kind === "plugin" && message.source.form === "notice" ? message.source.summary : message.source.kind;
|
|
25626
25276
|
const summary = boundContextSummary(notice);
|
|
@@ -25636,7 +25286,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25636
25286
|
system: acc.stats.contextSegments.system + estimateTokens(summary)
|
|
25637
25287
|
}
|
|
25638
25288
|
};
|
|
25639
|
-
return;
|
|
25289
|
+
return true;
|
|
25640
25290
|
}
|
|
25641
25291
|
case "agent/inbox/spliced": {
|
|
25642
25292
|
const { target, start, removedCount = 0, inserted } = event.data;
|
|
@@ -25666,7 +25316,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25666
25316
|
ids.push(message.id);
|
|
25667
25317
|
acc.ops += 1;
|
|
25668
25318
|
}
|
|
25669
|
-
return;
|
|
25319
|
+
return true;
|
|
25670
25320
|
}
|
|
25671
25321
|
case "assistant/chunk": {
|
|
25672
25322
|
const chunk = event.data.chunk;
|
|
@@ -25682,13 +25332,13 @@ function replayProjectEvent(acc, event) {
|
|
|
25682
25332
|
}
|
|
25683
25333
|
if (chunk.type === "text-delta") {
|
|
25684
25334
|
acc.streaming = appendStreamingTail(acc.streaming, chunk.text);
|
|
25685
|
-
return;
|
|
25335
|
+
return true;
|
|
25686
25336
|
}
|
|
25687
25337
|
if (chunk.type === "reasoning-delta") {
|
|
25688
25338
|
acc.streamingReasoning = appendStreamingTail(acc.streamingReasoning, chunk.text);
|
|
25689
|
-
return;
|
|
25339
|
+
return true;
|
|
25690
25340
|
}
|
|
25691
|
-
return;
|
|
25341
|
+
return false;
|
|
25692
25342
|
}
|
|
25693
25343
|
case "assistant/message": {
|
|
25694
25344
|
const key = `${event.data.turn}:${event.data.step}`;
|
|
@@ -25726,7 +25376,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25726
25376
|
assistant: acc.stats.contextSegments.assistant + estimateTokens(text)
|
|
25727
25377
|
}
|
|
25728
25378
|
};
|
|
25729
|
-
return;
|
|
25379
|
+
return true;
|
|
25730
25380
|
}
|
|
25731
25381
|
case "tool/call": {
|
|
25732
25382
|
const data = event.data;
|
|
@@ -25755,7 +25405,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25755
25405
|
tools: acc.stats.contextSegments.tools + (typeof data.arguments === "string" ? estimateTokens(data.arguments) : 0)
|
|
25756
25406
|
}
|
|
25757
25407
|
};
|
|
25758
|
-
return;
|
|
25408
|
+
return true;
|
|
25759
25409
|
}
|
|
25760
25410
|
case "tool/result": {
|
|
25761
25411
|
const block = event.data.message.content[0];
|
|
@@ -25789,11 +25439,11 @@ function replayProjectEvent(acc, event) {
|
|
|
25789
25439
|
tools: acc.stats.contextSegments.tools + estimateTokens(rawText)
|
|
25790
25440
|
}
|
|
25791
25441
|
};
|
|
25792
|
-
return;
|
|
25442
|
+
return true;
|
|
25793
25443
|
}
|
|
25794
25444
|
case "todo/write":
|
|
25795
25445
|
acc.todos = event.data.todos;
|
|
25796
|
-
return;
|
|
25446
|
+
return true;
|
|
25797
25447
|
case "turn/start": {
|
|
25798
25448
|
const wasBusy = acc.busy;
|
|
25799
25449
|
acc.busy = true;
|
|
@@ -25803,7 +25453,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25803
25453
|
...acc.stats,
|
|
25804
25454
|
turns: acc.stats.turns + 1
|
|
25805
25455
|
};
|
|
25806
|
-
return;
|
|
25456
|
+
return true;
|
|
25807
25457
|
}
|
|
25808
25458
|
case "step/start": {
|
|
25809
25459
|
const key = `${event.data.turn}:${event.data.step}`;
|
|
@@ -25820,7 +25470,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25820
25470
|
...acc.stats,
|
|
25821
25471
|
steps: acc.stats.steps + 1
|
|
25822
25472
|
};
|
|
25823
|
-
return;
|
|
25473
|
+
return true;
|
|
25824
25474
|
}
|
|
25825
25475
|
case "turn/end": {
|
|
25826
25476
|
const reason = event.data.reason;
|
|
@@ -25860,7 +25510,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25860
25510
|
acc.busy = false;
|
|
25861
25511
|
acc.busySince = 0;
|
|
25862
25512
|
for (const entry of appended) appendReplayEntry(acc, entry);
|
|
25863
|
-
return;
|
|
25513
|
+
return true;
|
|
25864
25514
|
}
|
|
25865
25515
|
case "llm/retry": {
|
|
25866
25516
|
const data = event.data;
|
|
@@ -25876,7 +25526,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25876
25526
|
state: "running"
|
|
25877
25527
|
});
|
|
25878
25528
|
indexList(acc.retryIndex, data.retryId).push(acc.entries.length - 1);
|
|
25879
|
-
return;
|
|
25529
|
+
return true;
|
|
25880
25530
|
}
|
|
25881
25531
|
case "llm/retry-started": {
|
|
25882
25532
|
const data = event.data;
|
|
@@ -25884,11 +25534,11 @@ function replayProjectEvent(acc, event) {
|
|
|
25884
25534
|
...entry,
|
|
25885
25535
|
state: "done"
|
|
25886
25536
|
}));
|
|
25887
|
-
return;
|
|
25537
|
+
return true;
|
|
25888
25538
|
}
|
|
25889
25539
|
case "sandbox/mode":
|
|
25890
25540
|
acc.sandbox = event.data.mode;
|
|
25891
|
-
return;
|
|
25541
|
+
return true;
|
|
25892
25542
|
case "goal/change": {
|
|
25893
25543
|
const data = event.data;
|
|
25894
25544
|
const clip = (text) => text.length > 60 ? `${text.slice(0, 59)}…` : text;
|
|
@@ -25898,7 +25548,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25898
25548
|
kind: "turn-marker",
|
|
25899
25549
|
text: "◎ goal cleared"
|
|
25900
25550
|
});
|
|
25901
|
-
return;
|
|
25551
|
+
return true;
|
|
25902
25552
|
}
|
|
25903
25553
|
const goal = {
|
|
25904
25554
|
objective: data.goal.objective,
|
|
@@ -25913,21 +25563,21 @@ function replayProjectEvent(acc, event) {
|
|
|
25913
25563
|
kind: "turn-marker",
|
|
25914
25564
|
text: line
|
|
25915
25565
|
});
|
|
25916
|
-
return;
|
|
25566
|
+
return true;
|
|
25917
25567
|
}
|
|
25918
25568
|
case "session/title":
|
|
25919
25569
|
acc.title = event.data.title;
|
|
25920
|
-
return;
|
|
25570
|
+
return true;
|
|
25921
25571
|
case "compaction/summary":
|
|
25922
25572
|
if (acc.compactionTokens.size >= MAX_COMPACTION_SUMMARY_RESIDUE) {
|
|
25923
25573
|
const oldest = acc.compactionTokens.keys().next().value;
|
|
25924
25574
|
if (oldest !== void 0) acc.compactionTokens.delete(oldest);
|
|
25925
25575
|
}
|
|
25926
25576
|
acc.compactionTokens.set(event.data.compactionId, event.data.shadowedTokenCount);
|
|
25927
|
-
return;
|
|
25577
|
+
return true;
|
|
25928
25578
|
case "compaction/prune":
|
|
25929
25579
|
acc.lastPruneTokens = event.data.shadowedTokenCount;
|
|
25930
|
-
return;
|
|
25580
|
+
return true;
|
|
25931
25581
|
case "compaction/end": {
|
|
25932
25582
|
const ok = event.data.error === void 0;
|
|
25933
25583
|
const tokens = acc.compactionTokens.get(event.data.compactionId) ?? acc.lastPruneTokens;
|
|
@@ -25938,14 +25588,14 @@ function replayProjectEvent(acc, event) {
|
|
|
25938
25588
|
tokens,
|
|
25939
25589
|
error: event.data.error ?? ""
|
|
25940
25590
|
});
|
|
25941
|
-
return;
|
|
25591
|
+
return true;
|
|
25942
25592
|
}
|
|
25943
25593
|
case "request/context":
|
|
25944
25594
|
acc.stats = {
|
|
25945
25595
|
...acc.stats,
|
|
25946
25596
|
contextWindow: event.data.contextWindow ?? acc.stats.contextWindow
|
|
25947
25597
|
};
|
|
25948
|
-
return;
|
|
25598
|
+
return true;
|
|
25949
25599
|
case "request/header": {
|
|
25950
25600
|
const config = event.data.header.config;
|
|
25951
25601
|
acc.model = `${config.provider}/${config.model}`;
|
|
@@ -25957,14 +25607,14 @@ function replayProjectEvent(acc, event) {
|
|
|
25957
25607
|
system: estimateTokens(event.data.header.system ?? "")
|
|
25958
25608
|
}
|
|
25959
25609
|
};
|
|
25960
|
-
return;
|
|
25610
|
+
return true;
|
|
25961
25611
|
}
|
|
25962
25612
|
case "plan/mode":
|
|
25963
25613
|
acc.plan = event.data.active;
|
|
25964
|
-
return;
|
|
25614
|
+
return true;
|
|
25965
25615
|
case "permission/preset":
|
|
25966
25616
|
acc.permission = event.data.preset;
|
|
25967
|
-
return;
|
|
25617
|
+
return true;
|
|
25968
25618
|
case "command/run": {
|
|
25969
25619
|
const data = event.data;
|
|
25970
25620
|
appendReplayEntry(acc, {
|
|
@@ -25976,7 +25626,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25976
25626
|
summary: ""
|
|
25977
25627
|
});
|
|
25978
25628
|
indexList(acc.commandIndex, data.commandId).push(acc.entries.length - 1);
|
|
25979
|
-
return;
|
|
25629
|
+
return true;
|
|
25980
25630
|
}
|
|
25981
25631
|
case "command/done": {
|
|
25982
25632
|
const data = event.data;
|
|
@@ -25986,20 +25636,27 @@ function replayProjectEvent(acc, event) {
|
|
|
25986
25636
|
summary: boundContextSummary(data.text ?? "")
|
|
25987
25637
|
});
|
|
25988
25638
|
updateReplayById(acc, acc.commandIndex, data.commandId, (entry) => entry.commandId === data.commandId, update);
|
|
25989
|
-
return;
|
|
25639
|
+
return true;
|
|
25990
25640
|
}
|
|
25991
|
-
default: return;
|
|
25641
|
+
default: return false;
|
|
25992
25642
|
}
|
|
25993
25643
|
}
|
|
25994
25644
|
/**
|
|
25995
|
-
* Materialize the accumulated fold as a
|
|
25996
|
-
*
|
|
25997
|
-
*
|
|
25645
|
+
* Materialize the accumulated fold as a fresh immutable snapshot for the
|
|
25646
|
+
* live store. Unlike {@link finishReplay} — the one-shot replay entry, which
|
|
25647
|
+
* hands the accumulator's own arrays through because the accumulator is
|
|
25648
|
+
* discarded — every array a renderer can hold is copied here, so later
|
|
25649
|
+
* folds never mutate a snapshot already handed out. Same fields, same
|
|
25650
|
+
* tombstone compaction.
|
|
25998
25651
|
*
|
|
25999
|
-
* @internal
|
|
25652
|
+
* @internal Live-store path; `projectEvents` is the public entry.
|
|
26000
25653
|
*/
|
|
26001
|
-
function
|
|
26002
|
-
|
|
25654
|
+
function snapshotReplayView(acc) {
|
|
25655
|
+
return materializeReplayView(acc, true);
|
|
25656
|
+
}
|
|
25657
|
+
/** Field-for-field materialization; `copy` selects snapshot array isolation. */
|
|
25658
|
+
function materializeReplayView(acc, copy) {
|
|
25659
|
+
const entries = acc.removedCount === 0 ? copy ? [...acc.entries] : acc.entries : acc.entries.filter((entry) => entry !== void 0);
|
|
26003
25660
|
if (acc.removedCount > 0) acc.ops += acc.entries.length;
|
|
26004
25661
|
return {
|
|
26005
25662
|
entries,
|
|
@@ -26033,22 +25690,6 @@ function finishReplay(acc) {
|
|
|
26033
25690
|
};
|
|
26034
25691
|
}
|
|
26035
25692
|
/**
|
|
26036
|
-
* Fold a replayed event history into one view.
|
|
26037
|
-
*
|
|
26038
|
-
* Folding is near-linear in the log size: the mutable replay accumulator
|
|
26039
|
-
* appends in place and resolves id-keyed updates through index maps, so a
|
|
26040
|
-
* long persisted session replays without the O(N²) copy-on-write rebuilds a
|
|
26041
|
-
* naive sequential fold would incur. The result is identical to folding
|
|
26042
|
-
* {@link projectEvent} per event in order.
|
|
26043
|
-
* @param events - events in `seq` order.
|
|
26044
|
-
* @returns the folded view.
|
|
26045
|
-
*/
|
|
26046
|
-
function projectEvents(events) {
|
|
26047
|
-
const acc = createReplayAccumulator();
|
|
26048
|
-
for (const event of events) replayProjectEvent(acc, event);
|
|
26049
|
-
return finishReplay(acc);
|
|
26050
|
-
}
|
|
26051
|
-
/**
|
|
26052
25693
|
* The append-only flush boundary for a transcript view: the count of entries
|
|
26053
25694
|
* no later event can remove. Entries at or beyond this index are mutable and
|
|
26054
25695
|
* must stay in the live tree.
|
|
@@ -26077,6 +25718,16 @@ function settledEntryCount(entries) {
|
|
|
26077
25718
|
}
|
|
26078
25719
|
//#endregion
|
|
26079
25720
|
//#region src/render/markdown.ts
|
|
25721
|
+
/**
|
|
25722
|
+
* Terminal markdown renderer for assistant replies: a pure GFM-subset
|
|
25723
|
+
* block/inline parser producing styled line segments the Ink renderer maps
|
|
25724
|
+
* to colored text. No ANSI here — the app owns color mapping, tests own the
|
|
25725
|
+
* structure. The subset mirrors what agent replies actually emit: headings,
|
|
25726
|
+
* emphasis, inline/fenced code, flat lists, blockquotes, links, rules, GFM
|
|
25727
|
+
* tables, and wrapped paragraphs. Unknown syntax degrades to plain text.
|
|
25728
|
+
*
|
|
25729
|
+
* @module @deepseek-ai/dsh-code/render/markdown
|
|
25730
|
+
*/
|
|
26080
25731
|
/** Plain segment helper. */
|
|
26081
25732
|
function seg(text, style = "plain") {
|
|
26082
25733
|
return {
|
|
@@ -26084,14 +25735,9 @@ function seg(text, style = "plain") {
|
|
|
26084
25735
|
style
|
|
26085
25736
|
};
|
|
26086
25737
|
}
|
|
26087
|
-
/** Visible width of a run in columns (
|
|
25738
|
+
/** Visible width of a run in columns (grapheme-cluster and emoji aware). */
|
|
26088
25739
|
function visibleColumns(text) {
|
|
26089
|
-
|
|
26090
|
-
for (const char of text) {
|
|
26091
|
-
const code = char.codePointAt(0) ?? 0;
|
|
26092
|
-
columns += code > 11903 ? 2 : 1;
|
|
26093
|
-
}
|
|
26094
|
-
return columns;
|
|
25740
|
+
return stringWidth(text);
|
|
26095
25741
|
}
|
|
26096
25742
|
/** Punctuation that should not become the first visible glyph of a row. */
|
|
26097
25743
|
function isClosingPunctuation(text) {
|
|
@@ -26633,11 +26279,7 @@ function renderMarkdown(text, width, options = {}) {
|
|
|
26633
26279
|
}
|
|
26634
26280
|
//#endregion
|
|
26635
26281
|
//#region src/render/animations.ts
|
|
26636
|
-
/**
|
|
26637
|
-
* The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
|
|
26638
|
-
* ring trail clockwise around the eight outer positions, one braille glyph
|
|
26639
|
-
* per step — 8 frames × 125ms = the web's 1s cycle.
|
|
26640
|
-
*/
|
|
26282
|
+
/** Original terminal StateDot chase frames. */
|
|
26641
26283
|
const BUSY_CHASE_FRAMES = [
|
|
26642
26284
|
"⣾",
|
|
26643
26285
|
"⣽",
|
|
@@ -26648,10 +26290,40 @@ const BUSY_CHASE_FRAMES = [
|
|
|
26648
26290
|
"⣯",
|
|
26649
26291
|
"⣷"
|
|
26650
26292
|
];
|
|
26651
|
-
/** Chase frame for a monotonic tick
|
|
26293
|
+
/** Chase frame for a monotonic tick. */
|
|
26652
26294
|
function busyChaseFrame(tick) {
|
|
26653
26295
|
return BUSY_CHASE_FRAMES[tick % BUSY_CHASE_FRAMES.length] ?? BUSY_CHASE_FRAMES[0];
|
|
26654
26296
|
}
|
|
26297
|
+
/** Codex shimmer timing and geometry. */
|
|
26298
|
+
const DEEP_DIVING_SHIMMER_DURATION_MS = 2e3;
|
|
26299
|
+
const DEEP_DIVING_SPARK_BREATH_DURATION_MS = 2e3;
|
|
26300
|
+
/**
|
|
26301
|
+
* Codex's 2-second shimmer sweep, expressed in terminal ticks. The sweep has
|
|
26302
|
+
* ten virtual columns of padding on either side and a five-column cosine
|
|
26303
|
+
* highlight band, so the text changes gently rather than cycling rapidly.
|
|
26304
|
+
*/
|
|
26305
|
+
function deepDivingShimmerIntensity(index, tick, graphemeCount) {
|
|
26306
|
+
if (graphemeCount <= 0) return 0;
|
|
26307
|
+
const period = graphemeCount + 20;
|
|
26308
|
+
const position = (tick * 33 % DEEP_DIVING_SHIMMER_DURATION_MS + DEEP_DIVING_SHIMMER_DURATION_MS) % DEEP_DIVING_SHIMMER_DURATION_MS / DEEP_DIVING_SHIMMER_DURATION_MS * period;
|
|
26309
|
+
const distance = Math.abs(index + 10 - position);
|
|
26310
|
+
if (distance > 5) return 0;
|
|
26311
|
+
const angle = Math.PI * distance / 5;
|
|
26312
|
+
return .5 * (1 + Math.cos(angle));
|
|
26313
|
+
}
|
|
26314
|
+
/** Blue RGB color for one grapheme in the Codex-style shimmer. */
|
|
26315
|
+
function deepDivingGradientColor(index, tick, graphemeCount, base, highlight) {
|
|
26316
|
+
return blendRgb(highlight, base, deepDivingShimmerIntensity(index, tick, graphemeCount));
|
|
26317
|
+
}
|
|
26318
|
+
/** Smooth breathing intensity for the always-visible Deep diving sparkle. */
|
|
26319
|
+
function deepDivingSparkIntensity(tick) {
|
|
26320
|
+
const phase = (tick * 33 % DEEP_DIVING_SPARK_BREATH_DURATION_MS + DEEP_DIVING_SPARK_BREATH_DURATION_MS) % DEEP_DIVING_SPARK_BREATH_DURATION_MS / DEEP_DIVING_SPARK_BREATH_DURATION_MS;
|
|
26321
|
+
return .2 + .8 * (.5 + .5 * Math.cos(Math.PI * 2 * phase));
|
|
26322
|
+
}
|
|
26323
|
+
/** Blue RGB color for the breathing Deep diving sparkle. */
|
|
26324
|
+
function deepDivingSparkColor(tick, base, highlight) {
|
|
26325
|
+
return blendRgb(highlight, base, deepDivingSparkIntensity(tick));
|
|
26326
|
+
}
|
|
26655
26327
|
/** Caret visibility: half the ticks on, half off (530ms blink). */
|
|
26656
26328
|
function caretVisible(tick) {
|
|
26657
26329
|
return tick % 2 === 0;
|
|
@@ -27041,6 +26713,87 @@ function effortAboveHigh(effort) {
|
|
|
27041
26713
|
return rank !== void 0 && rank > 3;
|
|
27042
26714
|
}
|
|
27043
26715
|
//#endregion
|
|
26716
|
+
//#region src/mentions.ts
|
|
26717
|
+
/** Menu cap on file rows; the service owns ranking and default rows. */
|
|
26718
|
+
const MAX_FILE_ROWS = 20;
|
|
26719
|
+
/** Whether a mention token is already navigating a filesystem path. */
|
|
26720
|
+
function isPathLikeMentionQuery(query) {
|
|
26721
|
+
return /[\\/]/u.test(query);
|
|
26722
|
+
}
|
|
26723
|
+
/**
|
|
26724
|
+
* Create the mention API for one agent's workspace. A missing
|
|
26725
|
+
* `fileReferences` service (with an agent present) or `sessionReferenceResolver`
|
|
26726
|
+
* degrades that half to empty rows; `prepare` passes text through untouched
|
|
26727
|
+
* without references. An undefined agent (a bare launch before any session
|
|
26728
|
+
* exists) runs the official WorkspaceFileSearch over the launch cwd — the
|
|
26729
|
+
* same class the mounted service uses per agent — so `@` file completion
|
|
26730
|
+
* works from the first keystroke; session references wait for the session.
|
|
26731
|
+
*
|
|
26732
|
+
* `candidates` never reaches for `this` — the runner hands it to the input
|
|
26733
|
+
* editor as a detached callback, and a `this`-bound method would throw on
|
|
26734
|
+
* every `@` key.
|
|
26735
|
+
* @param ctx - context carrying the optional `fileReferences` and
|
|
26736
|
+
* `sessionReferenceResolver` services.
|
|
26737
|
+
* @param agent - the session owner; excluded from its own session candidates.
|
|
26738
|
+
* @param cwd - launch working directory; bounds the pre-session search.
|
|
26739
|
+
*/
|
|
26740
|
+
function createMentions(ctx, agent, cwd) {
|
|
26741
|
+
const resolver = ctx.get("sessionReferenceResolver");
|
|
26742
|
+
const fileReferences = ctx.get("fileReferences");
|
|
26743
|
+
const sessionCapable = agent !== void 0 && resolver !== void 0;
|
|
26744
|
+
let preSessionSearch;
|
|
26745
|
+
const preSessionFiles = (query, signal) => {
|
|
26746
|
+
preSessionSearch ??= new WorkspaceFileSearch(cwd, {
|
|
26747
|
+
maxResults: DEFAULT_FILE_SEARCH_MAX_RESULTS,
|
|
26748
|
+
maxEntries: DEFAULT_FILE_SEARCH_MAX_ENTRIES,
|
|
26749
|
+
excludedDirectories: [...DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES]
|
|
26750
|
+
});
|
|
26751
|
+
return preSessionSearch.list(query, signal ?? new AbortController().signal);
|
|
26752
|
+
};
|
|
26753
|
+
return {
|
|
26754
|
+
async candidates(query, signal) {
|
|
26755
|
+
const needle = query.trim();
|
|
26756
|
+
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 !== "" && !isPathLikeMentionQuery(needle) && agent !== void 0 ? resolver.listCandidates(agent, needle, 10, signal).catch(() => []) : Promise.resolve([])]);
|
|
26757
|
+
const fileRows = files.slice(0, MAX_FILE_ROWS).map((candidate) => ({
|
|
26758
|
+
label: candidate.path,
|
|
26759
|
+
description: candidate.kind === "directory" ? "Folder" : "File",
|
|
26760
|
+
kind: candidate.kind,
|
|
26761
|
+
...candidate.kind === "file" ? { path: isAbsolute(candidate.path) ? candidate.path : resolve(cwd, candidate.path) } : {}
|
|
26762
|
+
}));
|
|
26763
|
+
const sessionRows = sessions.map((candidate) => ({
|
|
26764
|
+
label: formatSessionReferenceMention(candidate),
|
|
26765
|
+
description: `Session · ${candidate.cwd ?? "(no cwd)"}`,
|
|
26766
|
+
kind: "session"
|
|
26767
|
+
}));
|
|
26768
|
+
return [...fileRows, ...sessionRows];
|
|
26769
|
+
},
|
|
26770
|
+
parse(text) {
|
|
26771
|
+
return parseSessionReferenceText(text);
|
|
26772
|
+
},
|
|
26773
|
+
async prepare(parsed, signal) {
|
|
26774
|
+
if (parsed.references.length === 0 || resolver === void 0 || agent === void 0) return {
|
|
26775
|
+
text: parsed.text,
|
|
26776
|
+
references: parsed.references
|
|
26777
|
+
};
|
|
26778
|
+
const prepared = await resolver.prepare(agent, [{
|
|
26779
|
+
type: "text",
|
|
26780
|
+
text: parsed.text
|
|
26781
|
+
}], parsed.references, signal);
|
|
26782
|
+
return {
|
|
26783
|
+
text: prepared.content.filter((block) => block.type === "text").map((block) => block.text).join(""),
|
|
26784
|
+
references: parsed.references,
|
|
26785
|
+
additionalContext: prepared.additionalContext
|
|
26786
|
+
};
|
|
26787
|
+
},
|
|
26788
|
+
sessionMention(candidate) {
|
|
26789
|
+
return formatSessionReferenceMention({
|
|
26790
|
+
sessionId: candidate.sessionId,
|
|
26791
|
+
label: candidate.label
|
|
26792
|
+
});
|
|
26793
|
+
}
|
|
26794
|
+
};
|
|
26795
|
+
}
|
|
26796
|
+
//#endregion
|
|
27044
26797
|
//#region src/session-directory.ts
|
|
27045
26798
|
/** Lightweight session-directory projection for the /resume picker. */
|
|
27046
26799
|
/** Case-insensitive filesystems (Windows, macOS) compare paths by lowercased form. */
|
|
@@ -27188,6 +26941,59 @@ function collectDeletionSubtree(records, id) {
|
|
|
27188
26941
|
return [...doomed];
|
|
27189
26942
|
}
|
|
27190
26943
|
/**
|
|
26944
|
+
* Plan one session-subtree deletion with NO filesystem side effects: collect
|
|
26945
|
+
* the doomed lineage, refuse when the root or ANY member is live (a live
|
|
26946
|
+
* child would outlive its deleted parent) or missing from the listing, and
|
|
26947
|
+
* order the result children-first so the executor can never leave a deleted
|
|
26948
|
+
* parent behind surviving children. Artifact-location guards stay at the
|
|
26949
|
+
* call site; this is the pure preflight they complete.
|
|
26950
|
+
* @param records - the full directory listing.
|
|
26951
|
+
* @param id - the root session id to delete.
|
|
26952
|
+
* @returns the ordered plan, or a user-facing refusal reason.
|
|
26953
|
+
*/
|
|
26954
|
+
function planSessionDeletion(records, id) {
|
|
26955
|
+
const target = records.find((record) => record.header.id === id);
|
|
26956
|
+
if (target === void 0) return {
|
|
26957
|
+
ok: false,
|
|
26958
|
+
reason: `no persisted session matches "${id}"`
|
|
26959
|
+
};
|
|
26960
|
+
if (target.live) return {
|
|
26961
|
+
ok: false,
|
|
26962
|
+
reason: "cannot delete a live session — it is open in this or another process"
|
|
26963
|
+
};
|
|
26964
|
+
const doomed = collectDeletionSubtree(records, id);
|
|
26965
|
+
const byId = new Map(records.map((record) => [record.header.id, record]));
|
|
26966
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
26967
|
+
for (const record of records) parentOf.set(record.header.id, record.header.parentSession);
|
|
26968
|
+
const nodes = [];
|
|
26969
|
+
for (const candidate of doomed) {
|
|
26970
|
+
const record = byId.get(candidate);
|
|
26971
|
+
if (record === void 0) return {
|
|
26972
|
+
ok: false,
|
|
26973
|
+
reason: `no persisted session matches "${candidate}"`
|
|
26974
|
+
};
|
|
26975
|
+
if (record.live) return {
|
|
26976
|
+
ok: false,
|
|
26977
|
+
reason: `cannot delete: child session ${candidate.slice(-12)} is live — close it (and any process using it) first`
|
|
26978
|
+
};
|
|
26979
|
+
let depth = 0;
|
|
26980
|
+
let ancestor = parentOf.get(candidate);
|
|
26981
|
+
while (ancestor !== void 0 && depth < 64) {
|
|
26982
|
+
depth += 1;
|
|
26983
|
+
ancestor = parentOf.get(ancestor);
|
|
26984
|
+
}
|
|
26985
|
+
nodes.push({
|
|
26986
|
+
id: candidate,
|
|
26987
|
+
depth
|
|
26988
|
+
});
|
|
26989
|
+
}
|
|
26990
|
+
nodes.sort((left, right) => right.depth - left.depth);
|
|
26991
|
+
return {
|
|
26992
|
+
ok: true,
|
|
26993
|
+
nodes
|
|
26994
|
+
};
|
|
26995
|
+
}
|
|
26996
|
+
/**
|
|
27191
26997
|
* Codex-style relative time for session rows ("now", "5m ago", "3h ago",
|
|
27192
26998
|
* "2d ago"; older than a week falls back to the local date).
|
|
27193
26999
|
* @param timestamp - epoch milliseconds of the last activity.
|
|
@@ -27970,19 +27776,26 @@ function toolDetailLines(detail, columns) {
|
|
|
27970
27776
|
default: return detail;
|
|
27971
27777
|
}
|
|
27972
27778
|
}
|
|
27779
|
+
/** Default compact tool-card window used while the Ctrl+R fold is closed. */
|
|
27780
|
+
const DEFAULT_TOOL_ROWS = 3;
|
|
27781
|
+
/** Keep the invocation visible while making hidden tool output discoverable. */
|
|
27782
|
+
function compactToolLines(lines, columns) {
|
|
27783
|
+
if (lines.length <= DEFAULT_TOOL_ROWS) return lines;
|
|
27784
|
+
return [...lines.slice(0, 2), ...textLines(" … output hidden · Ctrl/Alt+R", columns, "dim").slice(0, 1)];
|
|
27785
|
+
}
|
|
27973
27786
|
/**
|
|
27974
27787
|
* Convert one durable transcript entry to its complete scrollable row model.
|
|
27975
27788
|
* The source entry stays intact; only the caller's visible slice is rendered.
|
|
27976
27789
|
* Wrapped continuations keep a hanging indent aligned under each row's
|
|
27977
27790
|
* content (Codex history-cell alignment) instead of resetting to column 0.
|
|
27978
27791
|
*/
|
|
27979
|
-
function transcriptEntryLines(entry, columns, showReasoning = true, reasoningToggleHint = true) {
|
|
27792
|
+
function transcriptEntryLines(entry, columns, showReasoning = true, reasoningToggleHint = true, showToolDetails = showReasoning) {
|
|
27980
27793
|
const width = Math.max(1, Math.floor(columns));
|
|
27981
27794
|
switch (entry.kind) {
|
|
27982
27795
|
case "user": return entry.notice ? hangingStyledLines([lineSegment(promptDisplayText(entry), "dim")], width, "⤷ ", "dim", " ", "dim") : hangingStyledLines([lineSegment(promptDisplayText(entry), "plain")], width, "❯ ", "brand", " ", "plain");
|
|
27983
27796
|
case "pending": return hangingStyledLines([lineSegment(promptDisplayText(entry), "plain")], width, "❯ ", "brand", " ", "plain");
|
|
27984
27797
|
case "assistant": {
|
|
27985
|
-
const reasoning = entry.reasoning === "" ? [] : showReasoning ? reasoningLines(entry.reasoning, width) : textLines(`✻ Thinking (${entry.reasoning.length} chars${reasoningToggleHint ? ", Ctrl+R to expand" : ""})`, width, "dim");
|
|
27798
|
+
const reasoning = entry.reasoning === "" ? [] : showReasoning ? reasoningLines(entry.reasoning, width) : textLines(`✻ Thinking (${entry.reasoning.length} chars${reasoningToggleHint ? ", Ctrl/Alt+R to expand" : ""})`, width, "dim");
|
|
27986
27799
|
const body = markdownLines(entry.text, Math.max(10, width - 2)).map((line) => ({ segments: [{
|
|
27987
27800
|
text: " ",
|
|
27988
27801
|
style: "plain"
|
|
@@ -27998,7 +27811,7 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
27998
27811
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
27999
27812
|
const markStyle = entry.state === "running" ? "brand" : entry.state === "error" ? "error" : "success";
|
|
28000
27813
|
const summaryStyle = entry.state === "error" ? "error" : "dim";
|
|
28001
|
-
|
|
27814
|
+
const lines = [
|
|
28002
27815
|
...hangingStyledLines([
|
|
28003
27816
|
lineSegment(`[${entry.ordinal}] `, "dim"),
|
|
28004
27817
|
lineSegment(entry.name, "brand"),
|
|
@@ -28008,6 +27821,7 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
28008
27821
|
...entry.summary === "" ? [] : hangingTextLines(entry.state === "error" ? `call ${entry.ordinal}: ${entry.summary}` : entry.summary, width, " ⎿ ", summaryStyle, " ", summaryStyle),
|
|
28009
27822
|
...entry.detail === void 0 ? [] : toolDetailLines(entry.detail, width)
|
|
28010
27823
|
];
|
|
27824
|
+
return showToolDetails ? lines : compactToolLines(lines, width);
|
|
28011
27825
|
}
|
|
28012
27826
|
case "command": {
|
|
28013
27827
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
@@ -28025,7 +27839,45 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
28025
27839
|
}
|
|
28026
27840
|
/** Settled-history variant carrying the Ctrl+R reasoning fold. */
|
|
28027
27841
|
function settledEntryLines(entry, columns, showReasoning) {
|
|
28028
|
-
return transcriptEntryLines(entry, columns, showReasoning, false);
|
|
27842
|
+
return transcriptEntryLines(entry, columns, showReasoning, false, showReasoning);
|
|
27843
|
+
}
|
|
27844
|
+
/**
|
|
27845
|
+
* Clamp the live-region allocation so the flexible dynamic rows never exceed
|
|
27846
|
+
* the post-chrome budget. By construction the caller derives these rows from
|
|
27847
|
+
* the same budget; this is the runtime tripwire for a future edit that breaks
|
|
27848
|
+
* that derivation. Reduction order: answer first (the freshest content is the
|
|
27849
|
+
* live tail), then reasoning, then settled live rows; nothing goes negative.
|
|
27850
|
+
* @param allocation - the intended row allocation.
|
|
27851
|
+
* @param dynamicRows - the post-chrome row budget.
|
|
27852
|
+
* @returns the clamped allocation and a warning string when clamping fired.
|
|
27853
|
+
*/
|
|
27854
|
+
function clampLiveAllocation(allocation, dynamicRows) {
|
|
27855
|
+
const live = Math.max(0, Math.floor(allocation.live));
|
|
27856
|
+
const reasoning = Math.max(0, Math.floor(allocation.reasoning));
|
|
27857
|
+
const answer = Math.max(0, Math.floor(allocation.answer));
|
|
27858
|
+
const budget = Math.max(0, Math.floor(dynamicRows));
|
|
27859
|
+
let excess = live + reasoning + answer - budget;
|
|
27860
|
+
if (excess <= 0) return { allocation: {
|
|
27861
|
+
live,
|
|
27862
|
+
reasoning,
|
|
27863
|
+
answer
|
|
27864
|
+
} };
|
|
27865
|
+
const take = (from) => {
|
|
27866
|
+
const cut = Math.min(from, excess);
|
|
27867
|
+
excess -= cut;
|
|
27868
|
+
return from - cut;
|
|
27869
|
+
};
|
|
27870
|
+
const clampedAnswer = take(answer);
|
|
27871
|
+
const clampedReasoning = excess > 0 ? take(reasoning) : reasoning;
|
|
27872
|
+
const clampedLive = excess > 0 ? take(live) : live;
|
|
27873
|
+
return {
|
|
27874
|
+
allocation: {
|
|
27875
|
+
live: clampedLive,
|
|
27876
|
+
reasoning: clampedReasoning,
|
|
27877
|
+
answer: clampedAnswer
|
|
27878
|
+
},
|
|
27879
|
+
warning: `live rows ${live} + ${reasoning} + ${answer} exceed the dynamic budget ${budget}; clamped to ${clampedLive}/${clampedReasoning}/${clampedAnswer}`
|
|
27880
|
+
};
|
|
28029
27881
|
}
|
|
28030
27882
|
//#endregion
|
|
28031
27883
|
//#region src/render/editor.ts
|
|
@@ -28039,9 +27891,8 @@ function settledEntryLines(entry, columns, showReasoning) {
|
|
|
28039
27891
|
* grapheme boundaries) so the React state stays two primitives
|
|
28040
27892
|
* (value, cursor) and every operation here stays pure and testable.
|
|
28041
27893
|
*
|
|
28042
|
-
* Word motion
|
|
28043
|
-
*
|
|
28044
|
-
* single word (two hanzi are one Alt+B step, not two).
|
|
27894
|
+
* Word motion follows Codex's piece semantics: whitespace separates runs,
|
|
27895
|
+
* punctuation runs stay atomic, and each Han grapheme is its own boundary.
|
|
28045
27896
|
*
|
|
28046
27897
|
* @module @deepseek-ai/dsh-code/render/editor
|
|
28047
27898
|
*/
|
|
@@ -28194,6 +28045,28 @@ function editorModel(value, columns) {
|
|
|
28194
28045
|
length: value.length
|
|
28195
28046
|
};
|
|
28196
28047
|
}
|
|
28048
|
+
/** Split one row around the authoritative caret; every other row stays whole. */
|
|
28049
|
+
function editorRowParts(row, rowIndex, caretRow, cursor, caretEnabled = true) {
|
|
28050
|
+
if (!caretEnabled || rowIndex !== caretRow) return {
|
|
28051
|
+
before: "",
|
|
28052
|
+
caret: "",
|
|
28053
|
+
after: row.text,
|
|
28054
|
+
hasCaret: false
|
|
28055
|
+
};
|
|
28056
|
+
const at = row.offsets.indexOf(cursor);
|
|
28057
|
+
if (at < 0) return {
|
|
28058
|
+
before: "",
|
|
28059
|
+
caret: "",
|
|
28060
|
+
after: row.text,
|
|
28061
|
+
hasCaret: false
|
|
28062
|
+
};
|
|
28063
|
+
return {
|
|
28064
|
+
before: at > 0 ? row.text.slice(0, row.cuts[at]) : "",
|
|
28065
|
+
caret: at < row.cuts.length - 1 ? row.text.slice(row.cuts[at], row.cuts[at + 1]) : " ",
|
|
28066
|
+
after: at < row.cuts.length - 1 ? row.text.slice(row.cuts[at + 1]) : "",
|
|
28067
|
+
hasCaret: true
|
|
28068
|
+
};
|
|
28069
|
+
}
|
|
28197
28070
|
/** Map a cursor offset to its caret site on the wrapped rows. */
|
|
28198
28071
|
function caretSite(model, offset) {
|
|
28199
28072
|
const target = Math.max(0, Math.min(model.length, offset));
|
|
@@ -28218,7 +28091,9 @@ function caretSite(model, offset) {
|
|
|
28218
28091
|
*/
|
|
28219
28092
|
function moveCursorVertically(model, offset, preferredColumn, delta) {
|
|
28220
28093
|
const target = caretSite(model, offset).row + delta;
|
|
28221
|
-
if (
|
|
28094
|
+
if (delta === 0) return offset;
|
|
28095
|
+
if (target < 0) return 0;
|
|
28096
|
+
if (target >= model.rows.length) return model.length;
|
|
28222
28097
|
const row = model.rows[target];
|
|
28223
28098
|
const wanted = Math.max(0, Math.min(preferredColumn, row.columns[row.columns.length - 1]));
|
|
28224
28099
|
let best = 0;
|
|
@@ -28238,24 +28113,28 @@ function lineBounds(value, offset) {
|
|
|
28238
28113
|
}
|
|
28239
28114
|
/** Codex WORD_SEPARATORS: punctuation runs are their own word pieces. */
|
|
28240
28115
|
const WORD_SEPARATORS = /* @__PURE__ */ new Set("`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?");
|
|
28116
|
+
const HAN_GRAPHEME = /^\p{Script=Han}(?:\p{Mark}|\uFE0F)*$/u;
|
|
28117
|
+
const UNICODE_PUNCTUATION = /^\p{P}+$/u;
|
|
28241
28118
|
function classifyGrapheme(text) {
|
|
28242
28119
|
if (/^\s$/u.test(text)) return "space";
|
|
28243
|
-
return WORD_SEPARATORS.has(text) ? "punct" : "word";
|
|
28120
|
+
return WORD_SEPARATORS.has(text) || UNICODE_PUNCTUATION.test(text) ? "punct" : "word";
|
|
28244
28121
|
}
|
|
28245
|
-
/** Maximal same-class runs
|
|
28122
|
+
/** Maximal same-class runs; Han graphemes deliberately stay one run each. */
|
|
28246
28123
|
function pieceRuns(value) {
|
|
28247
28124
|
const runs = [];
|
|
28248
28125
|
let current;
|
|
28249
28126
|
for (const span of splitGraphemes(value)) {
|
|
28250
28127
|
const klass = span.text === "\n" ? "space" : classifyGrapheme(span.text);
|
|
28251
|
-
|
|
28128
|
+
const atomic = klass === "word" && HAN_GRAPHEME.test(span.text);
|
|
28129
|
+
if (current !== void 0 && current.class === klass && !current.atomic && !atomic) {
|
|
28252
28130
|
current.end = span.end;
|
|
28253
28131
|
continue;
|
|
28254
28132
|
}
|
|
28255
28133
|
current = {
|
|
28256
28134
|
start: span.start,
|
|
28257
28135
|
end: span.end,
|
|
28258
|
-
class: klass
|
|
28136
|
+
class: klass,
|
|
28137
|
+
atomic
|
|
28259
28138
|
};
|
|
28260
28139
|
runs.push(current);
|
|
28261
28140
|
}
|
|
@@ -28266,10 +28145,9 @@ function pieceRuns(value) {
|
|
|
28266
28145
|
* START of the trailing non-space piece (extending over separator pieces).
|
|
28267
28146
|
*/
|
|
28268
28147
|
function moveWordLeft(value, offset) {
|
|
28269
|
-
const cursor =
|
|
28148
|
+
const cursor = clampCursor(value, offset);
|
|
28270
28149
|
const runs = pieceRuns(value);
|
|
28271
|
-
let index = runs.
|
|
28272
|
-
while (index >= 0 && runs[index].end > cursor) index -= 1;
|
|
28150
|
+
let index = runs.findLastIndex((run) => run.start < cursor);
|
|
28273
28151
|
if (index < 0) return 0;
|
|
28274
28152
|
if (runs[index].class === "space") {
|
|
28275
28153
|
index -= 1;
|
|
@@ -28284,10 +28162,9 @@ function moveWordLeft(value, offset) {
|
|
|
28284
28162
|
* the leading non-space piece (extending over separator pieces).
|
|
28285
28163
|
*/
|
|
28286
28164
|
function moveWordRight(value, offset) {
|
|
28287
|
-
const cursor =
|
|
28165
|
+
const cursor = clampCursor(value, offset);
|
|
28288
28166
|
const runs = pieceRuns(value);
|
|
28289
|
-
let index =
|
|
28290
|
-
while (index < runs.length && runs[index].start < cursor) index += 1;
|
|
28167
|
+
let index = runs.findIndex((run) => run.end > cursor);
|
|
28291
28168
|
if (index >= runs.length) return value.length;
|
|
28292
28169
|
if (runs[index].class === "space") {
|
|
28293
28170
|
index += 1;
|
|
@@ -28376,6 +28253,62 @@ function insertText(value, cursor, text) {
|
|
|
28376
28253
|
killed: void 0
|
|
28377
28254
|
};
|
|
28378
28255
|
}
|
|
28256
|
+
/** Ctrl+A: current logical line start, then the previous line start on repeat. */
|
|
28257
|
+
function moveToLineStart(value, cursor, crossOnRepeat) {
|
|
28258
|
+
const site = clampCursor(value, cursor);
|
|
28259
|
+
const bounds = lineBounds(value, site);
|
|
28260
|
+
if (!crossOnRepeat || site !== bounds.start || bounds.start === 0) return bounds.start;
|
|
28261
|
+
return lineBounds(value, bounds.start - 1).start;
|
|
28262
|
+
}
|
|
28263
|
+
/** Ctrl+E: current logical line end, then the next line end on repeat. */
|
|
28264
|
+
function moveToLineEnd(value, cursor, crossOnRepeat) {
|
|
28265
|
+
const site = clampCursor(value, cursor);
|
|
28266
|
+
const bounds = lineBounds(value, site);
|
|
28267
|
+
if (!crossOnRepeat || site !== bounds.end || bounds.end >= value.length) return bounds.end;
|
|
28268
|
+
return lineBounds(value, bounds.end + 1).end;
|
|
28269
|
+
}
|
|
28270
|
+
/**
|
|
28271
|
+
* Remap a captured range when all intervening edits are wholly before or
|
|
28272
|
+
* wholly after it. An edit overlapping either boundary invalidates the
|
|
28273
|
+
* anchor instead of guessing and inserting content at a surprising place.
|
|
28274
|
+
*/
|
|
28275
|
+
function remapStableRange(original, current, range) {
|
|
28276
|
+
const start = Math.max(0, Math.min(original.length, range.start));
|
|
28277
|
+
const end = Math.max(start, Math.min(original.length, range.end));
|
|
28278
|
+
if (original === current) return {
|
|
28279
|
+
start,
|
|
28280
|
+
end
|
|
28281
|
+
};
|
|
28282
|
+
let prefix = 0;
|
|
28283
|
+
const shared = Math.min(original.length, current.length);
|
|
28284
|
+
while (prefix < shared && original[prefix] === current[prefix]) prefix += 1;
|
|
28285
|
+
let suffix = 0;
|
|
28286
|
+
while (suffix < original.length - prefix && suffix < current.length - prefix && original[original.length - 1 - suffix] === current[current.length - 1 - suffix]) suffix += 1;
|
|
28287
|
+
const oldChangedEnd = original.length - suffix;
|
|
28288
|
+
if (prefix >= end) return {
|
|
28289
|
+
start,
|
|
28290
|
+
end
|
|
28291
|
+
};
|
|
28292
|
+
if (oldChangedEnd <= start) {
|
|
28293
|
+
const delta = current.length - original.length;
|
|
28294
|
+
return {
|
|
28295
|
+
start: start + delta,
|
|
28296
|
+
end: end + delta
|
|
28297
|
+
};
|
|
28298
|
+
}
|
|
28299
|
+
}
|
|
28300
|
+
/** Replace a current range while preserving a cursor moved after capture. */
|
|
28301
|
+
function replaceRangePreservingCursor(value, cursor, range, replacement) {
|
|
28302
|
+
const start = Math.max(0, Math.min(value.length, range.start));
|
|
28303
|
+
const end = Math.max(start, Math.min(value.length, range.end));
|
|
28304
|
+
const site = clampCursor(value, cursor);
|
|
28305
|
+
const nextCursor = site <= start ? site : site >= end ? site + replacement.length - (end - start) : start + replacement.length;
|
|
28306
|
+
return {
|
|
28307
|
+
value: value.slice(0, start) + replacement + value.slice(end),
|
|
28308
|
+
cursor: nextCursor,
|
|
28309
|
+
killed: void 0
|
|
28310
|
+
};
|
|
28311
|
+
}
|
|
28379
28312
|
/**
|
|
28380
28313
|
* Composer editor row budget: the editor itself never grows past this many
|
|
28381
28314
|
* physical rows; deeper drafts scroll internally to keep the caret visible.
|
|
@@ -28385,14 +28318,73 @@ function composerMaxRows(terminalRows) {
|
|
|
28385
28318
|
return Math.max(1, Math.min(6, Math.floor((Math.max(1, terminalRows) - 10) / 3)));
|
|
28386
28319
|
}
|
|
28387
28320
|
/**
|
|
28388
|
-
*
|
|
28389
|
-
*
|
|
28390
|
-
*
|
|
28321
|
+
* History navigation starts with Up on an empty draft, or after visual
|
|
28322
|
+
* movement has reached the directional text edge of an unchanged recalled
|
|
28323
|
+
* entry. Every other position remains under textarea movement.
|
|
28324
|
+
*/
|
|
28325
|
+
function shouldRecallNavigate(value, cursor, lastRecalled, direction) {
|
|
28326
|
+
if (value === "") return direction < 0;
|
|
28327
|
+
if (lastRecalled !== value) return false;
|
|
28328
|
+
return direction < 0 ? cursor === 0 : cursor === value.length;
|
|
28329
|
+
}
|
|
28330
|
+
//#endregion
|
|
28331
|
+
//#region src/keyboard.ts
|
|
28332
|
+
/**
|
|
28333
|
+
* Keyboard enhancement protocol (Codex `keyboard_modes` parity) and the
|
|
28334
|
+
* kitty CSI-u normalization layer.
|
|
28335
|
+
*
|
|
28336
|
+
* The TUI pushes the kitty keyboard protocol with DISAMBIGUATE_ESCAPE_CODES
|
|
28337
|
+
* and REPORT_ALTERNATE_KEYS (flags 1|4 = `\x1b[>5u`). Event types are
|
|
28338
|
+
* deliberately NOT requested: Ink 5's parser cannot decode the
|
|
28339
|
+
* `:event-type` suffix, and repeat/release reporting buys this surface
|
|
28340
|
+
* nothing.
|
|
28341
|
+
*
|
|
28342
|
+
* Ink 5 also cannot parse most CSI-u forms at all — they fall through its
|
|
28343
|
+
* regex as unnamed sequences and get INSERTED AS DRAFT TEXT. The composer's
|
|
28344
|
+
* stdin read patch therefore rewrites every CSI-u form it can decode back
|
|
28345
|
+
* to the legacy byte or canonical sequence the existing key handling
|
|
28346
|
+
* already understands, before Ink ever parses the chunk.
|
|
28347
|
+
* @module @deepseek-ai/dsh-code/keyboard
|
|
28348
|
+
*/
|
|
28349
|
+
/** Push keyboard enhancement (modifyOtherKeys off, kitty flags 1|4). */
|
|
28350
|
+
const KEYBOARD_ENHANCE_ENABLE = "\x1B[>4;0m\x1B[>5u";
|
|
28351
|
+
/** Pop the enhancement stack and reset modifyOtherKeys (exit path). */
|
|
28352
|
+
const KEYBOARD_ENHANCE_DISABLE = "\x1B[<u\x1B[>4;0m";
|
|
28353
|
+
const DSH_ENABLE_KEYBOARD_ENHANCEMENT = "DSH_ENABLE_KEYBOARD_ENHANCEMENT";
|
|
28354
|
+
function parseBooleanEnv(value) {
|
|
28355
|
+
if (value === void 0) return void 0;
|
|
28356
|
+
const normalized = value.trim().toLowerCase();
|
|
28357
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes") return true;
|
|
28358
|
+
if (normalized === "0" || normalized === "false" || normalized === "no") return false;
|
|
28359
|
+
}
|
|
28360
|
+
/** True when the process is running inside the VS Code integrated terminal. */
|
|
28361
|
+
function isVsCodeTerminalEnv(env = process.env) {
|
|
28362
|
+
return env.TERM_PROGRAM?.trim().toLowerCase() === "vscode" || env.VSCODE_INJECTION === "1";
|
|
28363
|
+
}
|
|
28364
|
+
/** Whether to push Kitty keyboard enhancement for the current terminal. */
|
|
28365
|
+
function shouldEnableKeyboardEnhancement(env = process.env) {
|
|
28366
|
+
if (parseBooleanEnv(env["DSH_DISABLE_KEYBOARD_ENHANCEMENT"]) === true) return false;
|
|
28367
|
+
const explicitEnable = parseBooleanEnv(env[DSH_ENABLE_KEYBOARD_ENHANCEMENT]);
|
|
28368
|
+
if (explicitEnable !== void 0) return explicitEnable;
|
|
28369
|
+
return !isVsCodeTerminalEnv(env);
|
|
28370
|
+
}
|
|
28371
|
+
/** Enable bracketed paste reporting. */
|
|
28372
|
+
const BRACKETED_PASTE_ENABLE = "\x1B[?2004h";
|
|
28373
|
+
/** Disable bracketed paste reporting. */
|
|
28374
|
+
const BRACKETED_PASTE_DISABLE = "\x1B[?2004l";
|
|
28375
|
+
/** Enable terminal focus-in/focus-out reporting (xterm focus protocol). */
|
|
28376
|
+
const TERMINAL_FOCUS_REPORT_ENABLE = "\x1B[?1004h";
|
|
28377
|
+
/** Disable terminal focus-in/focus-out reporting. */
|
|
28378
|
+
const TERMINAL_FOCUS_REPORT_DISABLE = "\x1B[?1004l";
|
|
28379
|
+
/**
|
|
28380
|
+
* Remove xterm focus reports from one input chunk and update the caller's
|
|
28381
|
+
* focus state. Focus reports are terminal protocol, not composer text.
|
|
28391
28382
|
*/
|
|
28392
|
-
function
|
|
28393
|
-
|
|
28394
|
-
|
|
28395
|
-
|
|
28383
|
+
function stripTerminalFocusEvents(chunk, onFocus) {
|
|
28384
|
+
return chunk.replace(/\x1b\[(I|O)/gu, (_whole, event) => {
|
|
28385
|
+
onFocus(event === "I");
|
|
28386
|
+
return "";
|
|
28387
|
+
});
|
|
28396
28388
|
}
|
|
28397
28389
|
/** Bracketed paste markers as Ink delivers them (it strips the leading ESC). */
|
|
28398
28390
|
const PASTE_START_MARKER = "[200~";
|
|
@@ -28464,6 +28456,98 @@ function normalizeKeyboardChunk(chunk) {
|
|
|
28464
28456
|
}) ?? whole;
|
|
28465
28457
|
});
|
|
28466
28458
|
}
|
|
28459
|
+
const HOME_SEQUENCES = [
|
|
28460
|
+
"\x1B[H",
|
|
28461
|
+
"\x1B[1~",
|
|
28462
|
+
"\x1B[7~",
|
|
28463
|
+
"\x1BOH"
|
|
28464
|
+
];
|
|
28465
|
+
const END_SEQUENCES = [
|
|
28466
|
+
"\x1B[F",
|
|
28467
|
+
"\x1B[4~",
|
|
28468
|
+
"\x1B[8~",
|
|
28469
|
+
"\x1BOF"
|
|
28470
|
+
];
|
|
28471
|
+
/** Parse one CSI functional-key sequence at `offset`. */
|
|
28472
|
+
function functionalToken(chunk, offset) {
|
|
28473
|
+
const tail = chunk.slice(offset);
|
|
28474
|
+
for (const sequence of HOME_SEQUENCES) if (tail.startsWith(sequence)) return {
|
|
28475
|
+
token: { kind: "home" },
|
|
28476
|
+
length: sequence.length
|
|
28477
|
+
};
|
|
28478
|
+
for (const sequence of END_SEQUENCES) if (tail.startsWith(sequence)) return {
|
|
28479
|
+
token: { kind: "end" },
|
|
28480
|
+
length: sequence.length
|
|
28481
|
+
};
|
|
28482
|
+
const modifiedHome = /^\x1b\[1;(\d+)H/u.exec(tail);
|
|
28483
|
+
if (modifiedHome !== null) return {
|
|
28484
|
+
token: { kind: "home" },
|
|
28485
|
+
length: modifiedHome[0].length
|
|
28486
|
+
};
|
|
28487
|
+
const modifiedEnd = /^\x1b\[1;(\d+)F/u.exec(tail);
|
|
28488
|
+
if (modifiedEnd !== null) return {
|
|
28489
|
+
token: { kind: "end" },
|
|
28490
|
+
length: modifiedEnd[0].length
|
|
28491
|
+
};
|
|
28492
|
+
const modifiedDelete = /^\x1b\[3(?:;(\d+))?~/u.exec(tail);
|
|
28493
|
+
if (modifiedDelete !== null) {
|
|
28494
|
+
const modifiers = Number.parseInt(modifiedDelete[1] ?? "1", 10) - 1;
|
|
28495
|
+
return {
|
|
28496
|
+
token: { kind: (modifiers & 2) !== 0 || (modifiers & 4) !== 0 ? "delete-word-forward" : "delete-forward" },
|
|
28497
|
+
length: modifiedDelete[0].length
|
|
28498
|
+
};
|
|
28499
|
+
}
|
|
28500
|
+
}
|
|
28501
|
+
/**
|
|
28502
|
+
* Tokenize a stdin chunk containing at least one editor-only key. Ink calls
|
|
28503
|
+
* `useInput` once for a pasted/batched chunk, so preserving each action here
|
|
28504
|
+
* prevents repeated Backspace/Home/End/Delete presses from collapsing into
|
|
28505
|
+
* one blurred key event. Unknown escape sequences return undefined and stay
|
|
28506
|
+
* under Ink's ownership.
|
|
28507
|
+
*/
|
|
28508
|
+
function tokenizeRawEditorChunk(chunk) {
|
|
28509
|
+
const tokens = [];
|
|
28510
|
+
let text = "";
|
|
28511
|
+
let special = false;
|
|
28512
|
+
const flushText = () => {
|
|
28513
|
+
if (text === "") return;
|
|
28514
|
+
tokens.push({
|
|
28515
|
+
kind: "text",
|
|
28516
|
+
text
|
|
28517
|
+
});
|
|
28518
|
+
text = "";
|
|
28519
|
+
};
|
|
28520
|
+
for (let offset = 0; offset < chunk.length;) {
|
|
28521
|
+
if (chunk.startsWith("\x1B", offset) || chunk.startsWith("\x1B\b", offset)) {
|
|
28522
|
+
flushText();
|
|
28523
|
+
tokens.push({ kind: "delete-word-backward" });
|
|
28524
|
+
special = true;
|
|
28525
|
+
offset += 2;
|
|
28526
|
+
continue;
|
|
28527
|
+
}
|
|
28528
|
+
const functional = functionalToken(chunk, offset);
|
|
28529
|
+
if (functional !== void 0) {
|
|
28530
|
+
flushText();
|
|
28531
|
+
tokens.push(functional.token);
|
|
28532
|
+
special = true;
|
|
28533
|
+
offset += functional.length;
|
|
28534
|
+
continue;
|
|
28535
|
+
}
|
|
28536
|
+
const char = chunk[offset];
|
|
28537
|
+
if (char === "" || char === "\b") {
|
|
28538
|
+
flushText();
|
|
28539
|
+
tokens.push({ kind: "delete-backward" });
|
|
28540
|
+
special = true;
|
|
28541
|
+
offset += 1;
|
|
28542
|
+
continue;
|
|
28543
|
+
}
|
|
28544
|
+
if (char === "\x1B") return void 0;
|
|
28545
|
+
text += char;
|
|
28546
|
+
offset += 1;
|
|
28547
|
+
}
|
|
28548
|
+
flushText();
|
|
28549
|
+
return special ? tokens : void 0;
|
|
28550
|
+
}
|
|
28467
28551
|
//#endregion
|
|
28468
28552
|
//#region src/kernel-panels.ts
|
|
28469
28553
|
/** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
|
|
@@ -29923,11 +30007,15 @@ async function inspectImagePaths(paths, attachments, cwd = process.cwd()) {
|
|
|
29923
30007
|
return inspected;
|
|
29924
30008
|
}
|
|
29925
30009
|
/** Read, validate, and persist an ordered image path list as model content blocks. */
|
|
29926
|
-
async function saveImagePaths(paths, attachments) {
|
|
30010
|
+
async function saveImagePaths(paths, attachments, signal) {
|
|
29927
30011
|
if (paths.length === 0) return [];
|
|
29928
30012
|
if (attachments === void 0) throw new Error("image attachments are unavailable in this profile");
|
|
30013
|
+
const checkCancelled = () => {
|
|
30014
|
+
if (signal?.aborted === true) throw new Error("image submission cancelled");
|
|
30015
|
+
};
|
|
29929
30016
|
const inputs = [];
|
|
29930
30017
|
for (const path of paths) {
|
|
30018
|
+
checkCancelled();
|
|
29931
30019
|
let data;
|
|
29932
30020
|
try {
|
|
29933
30021
|
data = await readFile(path);
|
|
@@ -29942,7 +30030,10 @@ async function saveImagePaths(paths, attachments) {
|
|
|
29942
30030
|
name: basename(path)
|
|
29943
30031
|
});
|
|
29944
30032
|
}
|
|
29945
|
-
|
|
30033
|
+
checkCancelled();
|
|
30034
|
+
const refs = await attachments.saveImages(inputs);
|
|
30035
|
+
checkCancelled();
|
|
30036
|
+
return refs.map((attachment) => ({
|
|
29946
30037
|
type: "image",
|
|
29947
30038
|
attachment
|
|
29948
30039
|
}));
|
|
@@ -30058,6 +30149,10 @@ const LOCAL_COMMANDS = [
|
|
|
30058
30149
|
label: "/subagent",
|
|
30059
30150
|
description: "choose the model delegated subagents run on"
|
|
30060
30151
|
},
|
|
30152
|
+
{
|
|
30153
|
+
label: "/vscode-keys",
|
|
30154
|
+
description: "pass ctrl+r through the vs code terminal"
|
|
30155
|
+
},
|
|
30061
30156
|
{
|
|
30062
30157
|
label: "/delete",
|
|
30063
30158
|
description: "delete a session and its subagent threads"
|
|
@@ -30098,14 +30193,15 @@ function padColumns(text, width) {
|
|
|
30098
30193
|
return clipped + " ".repeat(Math.max(0, width - visibleColumns(clipped)));
|
|
30099
30194
|
}
|
|
30100
30195
|
/** Interval-driven frame counter for one self-contained animated leaf. */
|
|
30101
|
-
function useFrames(intervalMs) {
|
|
30196
|
+
function useFrames(intervalMs, active = true) {
|
|
30102
30197
|
const [tick, setTick] = (0, import_react.useState)(0);
|
|
30103
30198
|
(0, import_react.useEffect)(() => {
|
|
30199
|
+
if (!active) return;
|
|
30104
30200
|
const id = setInterval(() => setTick((current) => current + 1), intervalMs);
|
|
30105
30201
|
return () => {
|
|
30106
30202
|
clearInterval(id);
|
|
30107
30203
|
};
|
|
30108
|
-
}, [intervalMs]);
|
|
30204
|
+
}, [active, intervalMs]);
|
|
30109
30205
|
return tick;
|
|
30110
30206
|
}
|
|
30111
30207
|
/**
|
|
@@ -30122,12 +30218,7 @@ function useStableInput(handler, active) {
|
|
|
30122
30218
|
}, []);
|
|
30123
30219
|
useInput(stableHandler, { isActive: active });
|
|
30124
30220
|
}
|
|
30125
|
-
/**
|
|
30126
|
-
* The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
|
|
30127
|
-
* ring trail clockwise around the eight outer positions (8 frames × 125ms =
|
|
30128
|
-
* the web's 1s cycle). Replaces the plain busy ellipsis as the composer's
|
|
30129
|
-
* prompt marker and leads the Deep-diving line.
|
|
30130
|
-
*/
|
|
30221
|
+
/** The original web StateDot chase used by the busy composer marker. */
|
|
30131
30222
|
function BusyChase() {
|
|
30132
30223
|
const tick = useFrames(125);
|
|
30133
30224
|
return (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright) }, busyChaseFrame(tick) + " ");
|
|
@@ -30137,21 +30228,55 @@ function Caret() {
|
|
|
30137
30228
|
const tick = useFrames(530);
|
|
30138
30229
|
return (0, import_react.createElement)(Text, null, caretVisible(tick) ? "▍" : " ");
|
|
30139
30230
|
}
|
|
30140
|
-
/**
|
|
30141
|
-
function
|
|
30142
|
-
const
|
|
30143
|
-
|
|
30231
|
+
/** One resettable input-caret phase shared by the entire composer. */
|
|
30232
|
+
function useCursorBlink(active) {
|
|
30233
|
+
const [epoch, setEpoch] = (0, import_react.useState)(0);
|
|
30234
|
+
const [visible, setVisible] = (0, import_react.useState)(true);
|
|
30235
|
+
(0, import_react.useEffect)(() => {
|
|
30236
|
+
setVisible(true);
|
|
30237
|
+
if (!active) return;
|
|
30238
|
+
const id = setInterval(() => setVisible((current) => !current), 530);
|
|
30239
|
+
return () => {
|
|
30240
|
+
clearInterval(id);
|
|
30241
|
+
};
|
|
30242
|
+
}, [active, epoch]);
|
|
30243
|
+
return {
|
|
30244
|
+
visible,
|
|
30245
|
+
reset: (0, import_react.useCallback)(() => {
|
|
30246
|
+
setVisible(true);
|
|
30247
|
+
setEpoch((current) => current + 1);
|
|
30248
|
+
}, [])
|
|
30249
|
+
};
|
|
30250
|
+
}
|
|
30251
|
+
/**
|
|
30252
|
+
* One bounded line painted with the deep-diving shimmer: a continuously
|
|
30253
|
+
* moving blue gradient across graphemes, the `✻` glyph in the breathing
|
|
30254
|
+
* spark color. Shared by the busy line and the collapsed thinking marker;
|
|
30255
|
+
* always exactly one row (truncate-end) so the live budget stays exact.
|
|
30256
|
+
*/
|
|
30257
|
+
function ShimmerLine({ text }) {
|
|
30258
|
+
const tick = useFrames(33);
|
|
30259
|
+
const palette = getPalette();
|
|
30260
|
+
const graphemes = splitGraphemes(text);
|
|
30261
|
+
return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, ...graphemes.map((grapheme, index) => {
|
|
30262
|
+
const sparkle = grapheme.text === "✻";
|
|
30263
|
+
return (0, import_react.createElement)(Text, {
|
|
30264
|
+
key: `${grapheme.start}-${grapheme.end}`,
|
|
30265
|
+
color: inkColor(sparkle ? deepDivingSparkColor(tick, palette.brandDeep, palette.brandBright) : deepDivingGradientColor(index, tick, graphemes.length, palette.brandDeep, palette.brandBright)),
|
|
30266
|
+
bold: sparkle || void 0
|
|
30267
|
+
}, grapheme.text);
|
|
30268
|
+
}));
|
|
30144
30269
|
}
|
|
30145
30270
|
/**
|
|
30146
|
-
* The busy line, web TurnStatus contract:
|
|
30147
|
-
* `Deep diving...` label, with the elapsed clock appended
|
|
30148
|
-
* has clearly been running (15s) — anchored to `turn/start`
|
|
30149
|
-
* mid-turn keeps the real time.
|
|
30271
|
+
* The busy line, web TurnStatus contract: a continuously moving blue gradient
|
|
30272
|
+
* paints the complete `Deep diving...` label, with the elapsed clock appended
|
|
30273
|
+
* only once the turn has clearly been running (15s) — anchored to `turn/start`
|
|
30274
|
+
* so a resumed mid-turn keeps the real time.
|
|
30150
30275
|
*/
|
|
30151
30276
|
function DeepDivingLine({ since }) {
|
|
30152
|
-
useFrames(1e3);
|
|
30153
30277
|
const elapsed = since === 0 ? 0 : Date.now() - since;
|
|
30154
|
-
|
|
30278
|
+
const text = elapsed >= 15e3 ? `✻ Deep diving... ${runClock(elapsed)}` : "✻ Deep diving...";
|
|
30279
|
+
return (0, import_react.createElement)(ShimmerLine, { text });
|
|
30155
30280
|
}
|
|
30156
30281
|
/**
|
|
30157
30282
|
* The streaming buffer rendered with a hard size cap: the live region must
|
|
@@ -30409,11 +30534,12 @@ function todoMark(status) {
|
|
|
30409
30534
|
}
|
|
30410
30535
|
/**
|
|
30411
30536
|
* One-row live subagent summary (the Codex agent status feed, compressed to
|
|
30412
|
-
* the transcript's budget): running count, total
|
|
30413
|
-
*
|
|
30414
|
-
* the
|
|
30537
|
+
* the transcript's budget): running count, the observed total (the row cap
|
|
30538
|
+
* is a display budget, not the fan-out size), and the most recently active
|
|
30539
|
+
* child's current activity. One line, never more — the full view is the
|
|
30540
|
+
* /agents panel.
|
|
30415
30541
|
*/
|
|
30416
|
-
function AgentsLine({ rows }) {
|
|
30542
|
+
function AgentsLine({ rows, total }) {
|
|
30417
30543
|
if (rows.length === 0) return void 0;
|
|
30418
30544
|
const running = rows.filter((row) => row.state !== "done").length;
|
|
30419
30545
|
const newest = [...rows].sort((left, right) => right.updatedAt - left.updatedAt)[0];
|
|
@@ -30422,7 +30548,7 @@ function AgentsLine({ rows }) {
|
|
|
30422
30548
|
color: inkColor(getPalette().brand),
|
|
30423
30549
|
bold: true,
|
|
30424
30550
|
wrap: "truncate-end"
|
|
30425
|
-
}, `agents ${running} live`, (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, ` · ${
|
|
30551
|
+
}, `agents ${running} live`, (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, ` · ${total} total · /agents`), (0, import_react.createElement)(Text, { color: inkColor(getPalette().text) }, ` · ${mark} ${newest.label} ${newest.activity}`)));
|
|
30426
30552
|
}
|
|
30427
30553
|
/** One-row todo summary: task count cannot grow the live Ink tree. */
|
|
30428
30554
|
function TodoPanel({ todos }) {
|
|
@@ -31641,7 +31767,7 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
31641
31767
|
key: "key-inspector",
|
|
31642
31768
|
dimColor: true,
|
|
31643
31769
|
wrap: "truncate-end"
|
|
31644
|
-
}, " ctrl+o history details · ctrl+r thinking · shift+tab permission preset"),
|
|
31770
|
+
}, " ctrl+o history details · ctrl/alt+r thinking · shift+tab permission preset"),
|
|
31645
31771
|
(0, import_react.createElement)(Text, {
|
|
31646
31772
|
key: "key-cancel",
|
|
31647
31773
|
dimColor: true,
|
|
@@ -31730,49 +31856,6 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
31730
31856
|
function verboseLine(text, columns) {
|
|
31731
31857
|
return truncateColumns(displayText(text).replace(/\n/gu, " ↵ ").replace(/\t/gu, " "), Math.max(1, columns));
|
|
31732
31858
|
}
|
|
31733
|
-
/** Identify one whole-chunk key sequence Ink drops or blurs. */
|
|
31734
|
-
function annotateRawKey(chunk) {
|
|
31735
|
-
switch (chunk) {
|
|
31736
|
-
case "": return "delete-backward";
|
|
31737
|
-
case "\x1B":
|
|
31738
|
-
case "\x1B\b": return "delete-word-backward";
|
|
31739
|
-
case "\x1B[3~":
|
|
31740
|
-
case "\x1B[3;2~": return "delete-forward";
|
|
31741
|
-
case "\x1B[3;3~":
|
|
31742
|
-
case "\x1B[3;5~": return "delete-word-forward";
|
|
31743
|
-
case "\x1B[H":
|
|
31744
|
-
case "\x1B[1~":
|
|
31745
|
-
case "\x1B[7~":
|
|
31746
|
-
case "\x1BOH": return "home";
|
|
31747
|
-
case "\x1B[F":
|
|
31748
|
-
case "\x1B[4~":
|
|
31749
|
-
case "\x1B[8~":
|
|
31750
|
-
case "\x1BOF": return "end";
|
|
31751
|
-
default: return;
|
|
31752
|
-
}
|
|
31753
|
-
}
|
|
31754
|
-
/**
|
|
31755
|
-
* One-row editor window keeping the logical cursor visible in long drafts.
|
|
31756
|
-
* The caret and its surroundings slice at grapheme boundaries: splitting a
|
|
31757
|
-
* star-plane surrogate pair would render an isolated half under the block
|
|
31758
|
-
* caret with a width the terminal never draws.
|
|
31759
|
-
*/
|
|
31760
|
-
function editorWindow(value, cursor, columns) {
|
|
31761
|
-
const width = Math.max(1, columns);
|
|
31762
|
-
const normalize = (text) => displayText(text).replace(/\n/gu, "↵").replace(/\t/gu, " ");
|
|
31763
|
-
const site = clampCursor(value, cursor);
|
|
31764
|
-
const caretSpan = splitGraphemes(value).find((span) => span.start === site);
|
|
31765
|
-
const caret = caretSpan === void 0 ? " " : normalize(caretSpan.text);
|
|
31766
|
-
const rest = value.slice(caretSpan === void 0 ? site : caretSpan.end);
|
|
31767
|
-
const remaining = Math.max(0, width - visibleColumns(caret));
|
|
31768
|
-
const afterBudget = Math.min(Math.floor(remaining / 3), visibleColumns(normalize(rest)));
|
|
31769
|
-
const beforeBudget = Math.max(0, remaining - afterBudget);
|
|
31770
|
-
return {
|
|
31771
|
-
before: beforeBudget === 0 ? "" : displayTail(normalize(value.slice(0, site)), beforeBudget, 1).text,
|
|
31772
|
-
caret,
|
|
31773
|
-
after: afterBudget === 0 ? "" : truncateColumns(normalize(rest), afterBudget)
|
|
31774
|
-
};
|
|
31775
|
-
}
|
|
31776
31859
|
/** The empty-composer placeholder text (shared by the static and wave paths). */
|
|
31777
31860
|
const COMPOSER_PLACEHOLDER = "type a message · / commands · @ mentions";
|
|
31778
31861
|
/** Adjacent cells with identical styling merge into one styled Text span. */
|
|
@@ -32021,9 +32104,11 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
32021
32104
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
32022
32105
|
* box passes every key through untouched.
|
|
32023
32106
|
*/
|
|
32024
|
-
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, inspectImages, prepareImages, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle, maxRows, onEditorRows }) {
|
|
32107
|
+
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, applyEditorKeys, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle, maxRows, onEditorRows }) {
|
|
32025
32108
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
32109
|
+
const editorColumns = Math.max(1, columns - 6);
|
|
32026
32110
|
const stdin = useStdin().stdin;
|
|
32111
|
+
const focusReporting = isVsCodeTerminalEnv();
|
|
32027
32112
|
const [value, setValue] = (0, import_react.useState)("");
|
|
32028
32113
|
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
32029
32114
|
const valueRef = (0, import_react.useRef)(value);
|
|
@@ -32034,22 +32119,37 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32034
32119
|
const draftImagesRef = (0, import_react.useRef)(draftImages);
|
|
32035
32120
|
draftImagesRef.current = draftImages;
|
|
32036
32121
|
const [preparingImages, setPreparingImages] = (0, import_react.useState)(false);
|
|
32122
|
+
const prepareAbortRef = (0, import_react.useRef)(void 0);
|
|
32123
|
+
const prepareEpochRef = (0, import_react.useRef)(0);
|
|
32124
|
+
const { visible: cursorVisible, reset: resetCursorBlink } = useCursorBlink(active && !frozen && !preparingImages);
|
|
32125
|
+
(0, import_react.useEffect)(() => () => {
|
|
32126
|
+
prepareEpochRef.current += 1;
|
|
32127
|
+
prepareAbortRef.current?.abort();
|
|
32128
|
+
}, []);
|
|
32037
32129
|
const killRef = (0, import_react.useRef)("");
|
|
32038
32130
|
const preferredColumnRef = (0, import_react.useRef)(null);
|
|
32039
32131
|
const editorScrollRef = (0, import_react.useRef)(0);
|
|
32040
32132
|
const pasteBracketRef = (0, import_react.useRef)(false);
|
|
32041
32133
|
/** Cancels the pending lost-paste safety timer (undefined when disarmed). */
|
|
32042
32134
|
const pasteBracketCancelRef = (0, import_react.useRef)(void 0);
|
|
32043
|
-
/**
|
|
32044
|
-
const
|
|
32135
|
+
/** Ordered editor tokens from the stdin chunk Ink is about to deliver. */
|
|
32136
|
+
const rawEditorTokens = (0, import_react.useRef)(void 0);
|
|
32137
|
+
/** VS Code focus state from xterm focus-report events; starts focused. */
|
|
32138
|
+
const terminalFocusedRef = (0, import_react.useRef)(true);
|
|
32045
32139
|
const recall = (0, import_react.useRef)(beginRecall([], ""));
|
|
32140
|
+
(0, import_react.useEffect)(() => {
|
|
32141
|
+
preferredColumnRef.current = null;
|
|
32142
|
+
}, [editorColumns]);
|
|
32046
32143
|
(0, import_react.useEffect)(() => {
|
|
32047
32144
|
if (historyFill === void 0) return;
|
|
32048
32145
|
const safe = sanitizeDraftText(historyFill.text);
|
|
32049
32146
|
draftImagesRef.current = [];
|
|
32050
32147
|
setDraftImages([]);
|
|
32148
|
+
valueRef.current = safe;
|
|
32149
|
+
cursorRef.current = safe.length;
|
|
32051
32150
|
setValue(safe);
|
|
32052
32151
|
setCursor(safe.length);
|
|
32152
|
+
resetCursorBlink();
|
|
32053
32153
|
preferredColumnRef.current = null;
|
|
32054
32154
|
setDismissedMenuValue(void 0);
|
|
32055
32155
|
recall.current = {
|
|
@@ -32062,7 +32162,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32062
32162
|
}, [
|
|
32063
32163
|
historyFill,
|
|
32064
32164
|
recallSpace,
|
|
32065
|
-
historyConsumed
|
|
32165
|
+
historyConsumed,
|
|
32166
|
+
resetCursorBlink
|
|
32066
32167
|
]);
|
|
32067
32168
|
(0, import_react.useEffect)(() => {
|
|
32068
32169
|
setDraftImages((current) => {
|
|
@@ -32078,13 +32179,16 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32078
32179
|
const chunk = originalRead(...args);
|
|
32079
32180
|
if (chunk === null) return chunk;
|
|
32080
32181
|
const normalized = normalizeKeyboardChunk(typeof chunk === "string" ? chunk : String(chunk));
|
|
32081
|
-
|
|
32082
|
-
|
|
32182
|
+
const input = focusReporting ? stripTerminalFocusEvents(normalized, (focused) => {
|
|
32183
|
+
terminalFocusedRef.current = focused;
|
|
32184
|
+
}) : normalized;
|
|
32185
|
+
rawEditorTokens.current = tokenizeRawEditorChunk(input);
|
|
32186
|
+
return input;
|
|
32083
32187
|
};
|
|
32084
32188
|
return () => {
|
|
32085
32189
|
stdin.read = originalRead;
|
|
32086
32190
|
};
|
|
32087
|
-
}, [stdin]);
|
|
32191
|
+
}, [focusReporting, stdin]);
|
|
32088
32192
|
if (recall.current.entries !== recallSpace) {
|
|
32089
32193
|
const index = recall.current.index === null || recall.current.index < recallSpace.length ? recall.current.index : null;
|
|
32090
32194
|
recall.current = {
|
|
@@ -32106,6 +32210,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32106
32210
|
};
|
|
32107
32211
|
const mentionActive = mentionToken !== void 0;
|
|
32108
32212
|
const [mentionRows, setMentionRows] = (0, import_react.useState)([]);
|
|
32213
|
+
const mentionRequestRef = (0, import_react.useRef)(0);
|
|
32109
32214
|
const sameImagePath = (left, right) => process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
32110
32215
|
const uniqueImageMarker = (name, source, reserved = []) => {
|
|
32111
32216
|
const safeName = singleLineText(sanitizeDraftText(name));
|
|
@@ -32131,6 +32236,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32131
32236
|
return true;
|
|
32132
32237
|
};
|
|
32133
32238
|
const insertDroppedImages = (paths) => {
|
|
32239
|
+
const originalValue = valueRef.current;
|
|
32240
|
+
const originalCursor = cursorRef.current;
|
|
32134
32241
|
notify(`checking ${paths.length} image${paths.length === 1 ? "" : "s"}…`);
|
|
32135
32242
|
inspectImages(paths).then((inspected) => {
|
|
32136
32243
|
const additions = [];
|
|
@@ -32148,14 +32255,24 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32148
32255
|
notify("those images are already attached", "warning");
|
|
32149
32256
|
return;
|
|
32150
32257
|
}
|
|
32151
|
-
const at = cursorRef.current;
|
|
32152
32258
|
const current = valueRef.current;
|
|
32259
|
+
const anchor = remapStableRange(originalValue, current, {
|
|
32260
|
+
start: originalCursor,
|
|
32261
|
+
end: originalCursor
|
|
32262
|
+
});
|
|
32263
|
+
if (anchor === void 0) {
|
|
32264
|
+
notify("draft changed at the image drop point; drop the images again", "warning");
|
|
32265
|
+
return;
|
|
32266
|
+
}
|
|
32267
|
+
const at = anchor.start;
|
|
32153
32268
|
const insertion = `${at > 0 && !/\s$/u.test(current.slice(0, at)) ? " " : ""}${markers.join(" ")}${current.slice(at) === "" ? "" : " "}`;
|
|
32154
|
-
const
|
|
32155
|
-
|
|
32156
|
-
|
|
32157
|
-
|
|
32158
|
-
|
|
32269
|
+
const edit = replaceRangePreservingCursor(current, cursorRef.current, anchor, insertion);
|
|
32270
|
+
const nextCursor = current === originalValue && cursorRef.current === originalCursor ? at + insertion.length : edit.cursor;
|
|
32271
|
+
valueRef.current = edit.value;
|
|
32272
|
+
cursorRef.current = nextCursor;
|
|
32273
|
+
setValue(edit.value);
|
|
32274
|
+
setCursor(nextCursor);
|
|
32275
|
+
resetCursorBlink();
|
|
32159
32276
|
const nextImages = [...draftImagesRef.current, ...additions];
|
|
32160
32277
|
draftImagesRef.current = nextImages;
|
|
32161
32278
|
setDraftImages(nextImages);
|
|
@@ -32165,14 +32282,21 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32165
32282
|
});
|
|
32166
32283
|
};
|
|
32167
32284
|
(0, import_react.useEffect)(() => {
|
|
32285
|
+
const requestId = mentionRequestRef.current + 1;
|
|
32286
|
+
mentionRequestRef.current = requestId;
|
|
32168
32287
|
if (!active || !mentionActive) {
|
|
32169
32288
|
setMentionRows([]);
|
|
32170
32289
|
return;
|
|
32171
32290
|
}
|
|
32172
32291
|
const controller = new AbortController();
|
|
32173
|
-
|
|
32174
|
-
|
|
32292
|
+
const query = mentionToken.query;
|
|
32293
|
+
const timer = setTimeout(() => {
|
|
32294
|
+
loadMentions(query, controller.signal).then((rows) => {
|
|
32295
|
+
if (!controller.signal.aborted && mentionRequestRef.current === requestId) setMentionRows(rows);
|
|
32296
|
+
}, () => {});
|
|
32297
|
+
}, 50);
|
|
32175
32298
|
return () => {
|
|
32299
|
+
clearTimeout(timer);
|
|
32176
32300
|
controller.abort();
|
|
32177
32301
|
};
|
|
32178
32302
|
}, [
|
|
@@ -32180,8 +32304,9 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32180
32304
|
mentionActive,
|
|
32181
32305
|
mentionToken?.query
|
|
32182
32306
|
]);
|
|
32183
|
-
const menuActive = (slashActive || mentionActive) && dismissedMenuValue !== value;
|
|
32184
|
-
const
|
|
32307
|
+
const menuActive = !preparingImages && (slashActive || mentionActive) && dismissedMenuValue !== value;
|
|
32308
|
+
const visibleMentionRows = mentionToken !== void 0 && isPathLikeMentionQuery(mentionToken.query) ? mentionRows.filter((row) => row.kind !== "session") : mentionRows;
|
|
32309
|
+
const menuRows = mentionActive ? visibleMentionRows.map((row) => ({
|
|
32185
32310
|
label: row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`,
|
|
32186
32311
|
description: row.description,
|
|
32187
32312
|
origin: "mention"
|
|
@@ -32189,34 +32314,45 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32189
32314
|
/** Accept the highlighted completion-menu candidate into the draft. */
|
|
32190
32315
|
const acceptMenuCandidate = () => {
|
|
32191
32316
|
if (mentionActive && mentionToken !== void 0) {
|
|
32192
|
-
|
|
32317
|
+
if (visibleMentionRows.length === 0) return;
|
|
32318
|
+
const row = visibleMentionRows[completionIndex % visibleMentionRows.length];
|
|
32193
32319
|
if (row !== void 0) {
|
|
32194
32320
|
if (row.kind === "file" && row.path !== void 0 && looksLikeImagePath(row.path)) {
|
|
32195
32321
|
const tokenText = value.slice(mentionToken.start, cursor);
|
|
32196
32322
|
const start = mentionToken.start;
|
|
32323
|
+
const originalValue = value;
|
|
32197
32324
|
notify(`checking image ${basename(row.path)}…`);
|
|
32198
32325
|
inspectImages([row.path]).then((inspected) => {
|
|
32199
32326
|
const inspection = inspected[0];
|
|
32200
32327
|
if (inspection === void 0) return;
|
|
32201
32328
|
const current = valueRef.current;
|
|
32202
|
-
|
|
32329
|
+
const anchor = remapStableRange(originalValue, current, {
|
|
32330
|
+
start,
|
|
32331
|
+
end: start + tokenText.length
|
|
32332
|
+
});
|
|
32333
|
+
if (anchor === void 0 || current.slice(anchor.start, anchor.end) !== tokenText) {
|
|
32334
|
+
notify("draft changed around the image mention; select it again", "warning");
|
|
32335
|
+
return;
|
|
32336
|
+
}
|
|
32203
32337
|
if (draftImagesRef.current.some((image) => sameImagePath(image.path, inspection.path))) {
|
|
32204
|
-
const
|
|
32205
|
-
valueRef.current =
|
|
32206
|
-
cursorRef.current =
|
|
32207
|
-
setValue(
|
|
32208
|
-
setCursor(
|
|
32209
|
-
|
|
32338
|
+
const edit = replaceRangePreservingCursor(current, cursorRef.current, anchor, "");
|
|
32339
|
+
valueRef.current = edit.value;
|
|
32340
|
+
cursorRef.current = edit.cursor;
|
|
32341
|
+
setValue(edit.value);
|
|
32342
|
+
setCursor(edit.cursor);
|
|
32343
|
+
resetCursorBlink();
|
|
32344
|
+
setDismissedMenuValue(edit.value);
|
|
32210
32345
|
notify(`${inspection.name} is already attached`, "warning");
|
|
32211
32346
|
return;
|
|
32212
32347
|
}
|
|
32213
32348
|
const marker = uniqueImageMarker(inspection.name, "mention");
|
|
32214
|
-
const
|
|
32215
|
-
valueRef.current =
|
|
32216
|
-
cursorRef.current =
|
|
32217
|
-
setValue(
|
|
32218
|
-
setCursor(
|
|
32219
|
-
|
|
32349
|
+
const edit = replaceRangePreservingCursor(current, cursorRef.current, anchor, marker);
|
|
32350
|
+
valueRef.current = edit.value;
|
|
32351
|
+
cursorRef.current = edit.cursor;
|
|
32352
|
+
setValue(edit.value);
|
|
32353
|
+
setCursor(edit.cursor);
|
|
32354
|
+
resetCursorBlink();
|
|
32355
|
+
setDismissedMenuValue(edit.value);
|
|
32220
32356
|
registerDraftImage(inspection, marker);
|
|
32221
32357
|
notify(`${inspection.name} ready for the next message`);
|
|
32222
32358
|
}, (reason) => {
|
|
@@ -32227,14 +32363,25 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32227
32363
|
return;
|
|
32228
32364
|
}
|
|
32229
32365
|
const insertion = row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`;
|
|
32230
|
-
|
|
32231
|
-
|
|
32366
|
+
const nextValue = value.slice(0, mentionToken.start) + insertion + value.slice(cursor);
|
|
32367
|
+
const nextCursor = mentionToken.start + insertion.length;
|
|
32368
|
+
valueRef.current = nextValue;
|
|
32369
|
+
cursorRef.current = nextCursor;
|
|
32370
|
+
setValue(nextValue);
|
|
32371
|
+
setCursor(nextCursor);
|
|
32372
|
+
resetCursorBlink();
|
|
32232
32373
|
}
|
|
32233
32374
|
} else {
|
|
32375
|
+
if (candidates.length === 0) return;
|
|
32234
32376
|
const candidate = candidates[completionIndex % candidates.length];
|
|
32235
32377
|
if (candidate !== void 0) {
|
|
32236
|
-
|
|
32237
|
-
|
|
32378
|
+
const nextValue = `${candidate.label} `;
|
|
32379
|
+
const nextCursor = candidate.label.length + 1;
|
|
32380
|
+
valueRef.current = nextValue;
|
|
32381
|
+
cursorRef.current = nextCursor;
|
|
32382
|
+
setValue(nextValue);
|
|
32383
|
+
setCursor(nextCursor);
|
|
32384
|
+
resetCursorBlink();
|
|
32238
32385
|
}
|
|
32239
32386
|
}
|
|
32240
32387
|
setCompletionIndex(0);
|
|
@@ -32243,21 +32390,101 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32243
32390
|
/** Apply one editor edit: draft, cursor, kill buffer, menu reset. */
|
|
32244
32391
|
const applyEdit = (edit) => {
|
|
32245
32392
|
if (edit.killed !== void 0 && edit.killed !== "") killRef.current = edit.killed;
|
|
32393
|
+
valueRef.current = edit.value;
|
|
32394
|
+
cursorRef.current = edit.cursor;
|
|
32246
32395
|
setValue(edit.value);
|
|
32247
32396
|
setCursor(edit.cursor);
|
|
32397
|
+
resetCursorBlink();
|
|
32248
32398
|
preferredColumnRef.current = null;
|
|
32249
32399
|
setCompletionIndex(0);
|
|
32250
32400
|
setDismissedMenuValue(void 0);
|
|
32251
32401
|
};
|
|
32252
32402
|
/** Move the cursor without editing; horizontal moves clear the column preference. */
|
|
32253
32403
|
const moveCursorTo = (next) => {
|
|
32254
|
-
|
|
32404
|
+
resetCursorBlink();
|
|
32405
|
+
if (next === cursorRef.current) return;
|
|
32406
|
+
cursorRef.current = next;
|
|
32255
32407
|
setCursor(next);
|
|
32256
32408
|
preferredColumnRef.current = null;
|
|
32257
32409
|
};
|
|
32258
|
-
|
|
32410
|
+
/** Apply an ordered raw-key batch against one current draft snapshot. */
|
|
32411
|
+
const applyRawEditorTokens = (tokens) => {
|
|
32412
|
+
let nextValue = valueRef.current;
|
|
32413
|
+
let nextCursor = cursorRef.current;
|
|
32414
|
+
for (const token of tokens) {
|
|
32415
|
+
if (token.kind === "text") {
|
|
32416
|
+
const edit = insertText(nextValue, nextCursor, token.text);
|
|
32417
|
+
nextValue = edit.value;
|
|
32418
|
+
nextCursor = edit.cursor;
|
|
32419
|
+
continue;
|
|
32420
|
+
}
|
|
32421
|
+
if (token.kind === "home") {
|
|
32422
|
+
nextCursor = moveToLineStart(nextValue, nextCursor, false);
|
|
32423
|
+
continue;
|
|
32424
|
+
}
|
|
32425
|
+
if (token.kind === "end") {
|
|
32426
|
+
nextCursor = moveToLineEnd(nextValue, nextCursor, false);
|
|
32427
|
+
continue;
|
|
32428
|
+
}
|
|
32429
|
+
const edit = token.kind === "delete-backward" ? deleteBackward(nextValue, nextCursor) : token.kind === "delete-word-backward" ? deleteWordBackward(nextValue, nextCursor) : token.kind === "delete-forward" ? deleteForward(nextValue, nextCursor) : deleteWordForward(nextValue, nextCursor);
|
|
32430
|
+
if (edit.killed !== void 0 && edit.killed !== "") killRef.current = edit.killed;
|
|
32431
|
+
nextValue = edit.value;
|
|
32432
|
+
nextCursor = edit.cursor;
|
|
32433
|
+
}
|
|
32434
|
+
valueRef.current = nextValue;
|
|
32435
|
+
cursorRef.current = nextCursor;
|
|
32436
|
+
setValue(nextValue);
|
|
32437
|
+
setCursor(nextCursor);
|
|
32438
|
+
resetCursorBlink();
|
|
32439
|
+
preferredColumnRef.current = null;
|
|
32440
|
+
setCompletionIndex(0);
|
|
32441
|
+
setDismissedMenuValue(void 0);
|
|
32442
|
+
};
|
|
32443
|
+
const cancelImageSubmission = () => {
|
|
32444
|
+
prepareEpochRef.current += 1;
|
|
32445
|
+
prepareAbortRef.current?.abort();
|
|
32446
|
+
prepareAbortRef.current = void 0;
|
|
32447
|
+
setPreparingImages(false);
|
|
32448
|
+
dismissNotice();
|
|
32449
|
+
notify("image submission cancelled", "warning");
|
|
32450
|
+
};
|
|
32451
|
+
/** Move through visual rows first, then cross history at the true edge. */
|
|
32452
|
+
const navigateVertical = (direction) => {
|
|
32453
|
+
const currentValue = valueRef.current;
|
|
32454
|
+
const currentCursor = cursorRef.current;
|
|
32455
|
+
const model = editorModel(currentValue, editorColumns);
|
|
32456
|
+
const preferred = preferredColumnRef.current ?? caretSite(model, currentCursor).column;
|
|
32457
|
+
const next = moveCursorVertically(model, currentCursor, preferred, direction);
|
|
32458
|
+
if (next !== currentCursor) {
|
|
32459
|
+
cursorRef.current = next;
|
|
32460
|
+
setCursor(next);
|
|
32461
|
+
resetCursorBlink();
|
|
32462
|
+
preferredColumnRef.current = preferred;
|
|
32463
|
+
return;
|
|
32464
|
+
}
|
|
32465
|
+
if (recall.current.entries.length > 0 && shouldRecallNavigate(currentValue, currentCursor, recall.current.lastRecalled, direction)) {
|
|
32466
|
+
const step = direction < 0 ? recallOlder(recall.current, currentValue) : recallNewer(recall.current);
|
|
32467
|
+
recall.current = step.state;
|
|
32468
|
+
if (step.entry !== void 0) {
|
|
32469
|
+
const safe = sanitizeDraftText(step.entry);
|
|
32470
|
+
valueRef.current = safe;
|
|
32471
|
+
cursorRef.current = safe.length;
|
|
32472
|
+
setValue(safe);
|
|
32473
|
+
setCursor(safe.length);
|
|
32474
|
+
preferredColumnRef.current = null;
|
|
32475
|
+
setDismissedMenuValue(void 0);
|
|
32476
|
+
}
|
|
32477
|
+
}
|
|
32478
|
+
resetCursorBlink();
|
|
32479
|
+
};
|
|
32480
|
+
useStableInput((input, key) => {
|
|
32259
32481
|
if (!active) return;
|
|
32260
|
-
|
|
32482
|
+
const liveValue = valueRef.current;
|
|
32483
|
+
const liveCursor = cursorRef.current;
|
|
32484
|
+
if (preparingImages) {
|
|
32485
|
+
if (key.escape || key.ctrl && input === "c") cancelImageSubmission();
|
|
32486
|
+
return;
|
|
32487
|
+
}
|
|
32261
32488
|
if (deleteConfirm !== void 0) {
|
|
32262
32489
|
if (input === "y" || input === "Y") confirmDelete();
|
|
32263
32490
|
else cancelDelete();
|
|
@@ -32272,7 +32499,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32272
32499
|
}
|
|
32273
32500
|
return;
|
|
32274
32501
|
}
|
|
32275
|
-
if (key.ctrl && input === "r") {
|
|
32502
|
+
if ((key.ctrl || key.meta) && input === "r") {
|
|
32503
|
+
if (focusReporting && !terminalFocusedRef.current) return;
|
|
32276
32504
|
toggleReasoning();
|
|
32277
32505
|
return;
|
|
32278
32506
|
}
|
|
@@ -32282,9 +32510,12 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32282
32510
|
}
|
|
32283
32511
|
if (key.ctrl && input === "c") {
|
|
32284
32512
|
if (busy) interrupt();
|
|
32285
|
-
else if (
|
|
32513
|
+
else if (liveValue !== "") {
|
|
32514
|
+
valueRef.current = "";
|
|
32515
|
+
cursorRef.current = 0;
|
|
32286
32516
|
setValue("");
|
|
32287
32517
|
setCursor(0);
|
|
32518
|
+
resetCursorBlink();
|
|
32288
32519
|
draftImagesRef.current = [];
|
|
32289
32520
|
setDraftImages([]);
|
|
32290
32521
|
setCompletionIndex(0);
|
|
@@ -32293,8 +32524,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32293
32524
|
return;
|
|
32294
32525
|
}
|
|
32295
32526
|
if (key.ctrl && input === "d") {
|
|
32296
|
-
if (
|
|
32297
|
-
applyEdit(deleteForward(
|
|
32527
|
+
if (liveValue !== "") {
|
|
32528
|
+
applyEdit(deleteForward(liveValue, liveCursor));
|
|
32298
32529
|
return;
|
|
32299
32530
|
}
|
|
32300
32531
|
if (busy) notify("cancel the running turn before exiting (Esc or Ctrl+C)", "warning");
|
|
@@ -32303,7 +32534,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32303
32534
|
}
|
|
32304
32535
|
if (key.escape) {
|
|
32305
32536
|
if (menuActive) {
|
|
32306
|
-
setDismissedMenuValue(
|
|
32537
|
+
setDismissedMenuValue(liveValue);
|
|
32307
32538
|
return;
|
|
32308
32539
|
}
|
|
32309
32540
|
if (hasNotice) {
|
|
@@ -32313,27 +32544,33 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32313
32544
|
if (busy) interrupt();
|
|
32314
32545
|
return;
|
|
32315
32546
|
}
|
|
32316
|
-
if (key.delete &&
|
|
32547
|
+
if (key.delete && liveValue === "" && queued.length > 0) {
|
|
32317
32548
|
cancelQueued(queued[queued.length - 1].messageId);
|
|
32318
32549
|
return;
|
|
32319
32550
|
}
|
|
32320
32551
|
if (key.return) {
|
|
32321
32552
|
if (pasteBracketRef.current) {
|
|
32322
|
-
applyEdit(insertText(
|
|
32553
|
+
applyEdit(insertText(liveValue, liveCursor, "\n"));
|
|
32323
32554
|
return;
|
|
32324
32555
|
}
|
|
32325
32556
|
if (menuActive) {
|
|
32326
|
-
if (!(!mentionActive && candidates.some((candidate) => candidate.label ===
|
|
32557
|
+
if (!(!mentionActive && candidates.some((candidate) => candidate.label === liveValue))) {
|
|
32327
32558
|
acceptMenuCandidate();
|
|
32328
32559
|
return;
|
|
32329
32560
|
}
|
|
32330
32561
|
}
|
|
32331
|
-
const text =
|
|
32562
|
+
const text = liveValue.trim();
|
|
32332
32563
|
if (draftImagesRef.current.length > 0) {
|
|
32564
|
+
const controller = new AbortController();
|
|
32565
|
+
const epoch = prepareEpochRef.current + 1;
|
|
32566
|
+
prepareEpochRef.current = epoch;
|
|
32567
|
+
prepareAbortRef.current = controller;
|
|
32333
32568
|
setPreparingImages(true);
|
|
32334
32569
|
notify(`processing ${draftImagesRef.current.length} image${draftImagesRef.current.length === 1 ? "" : "s"}…`);
|
|
32335
32570
|
const snapshot = draftImagesRef.current;
|
|
32336
|
-
prepareImages(snapshot.map((image) => image.path)).then((images) => {
|
|
32571
|
+
prepareImages(snapshot.map((image) => image.path), controller.signal).then((images) => {
|
|
32572
|
+
if (controller.signal.aborted || prepareEpochRef.current !== epoch) return;
|
|
32573
|
+
prepareAbortRef.current = void 0;
|
|
32337
32574
|
setPreparingImages(false);
|
|
32338
32575
|
valueRef.current = "";
|
|
32339
32576
|
cursorRef.current = 0;
|
|
@@ -32352,13 +32589,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32352
32589
|
if (busy) steer(text, images);
|
|
32353
32590
|
else dispatch(text, images);
|
|
32354
32591
|
}, (reason) => {
|
|
32592
|
+
if (controller.signal.aborted || prepareEpochRef.current !== epoch) return;
|
|
32593
|
+
prepareAbortRef.current = void 0;
|
|
32355
32594
|
setPreparingImages(false);
|
|
32356
32595
|
notify(`image submission failed: ${reason instanceof Error ? reason.message : String(reason)}`, "error");
|
|
32357
32596
|
});
|
|
32358
32597
|
return;
|
|
32359
32598
|
}
|
|
32599
|
+
valueRef.current = "";
|
|
32600
|
+
cursorRef.current = 0;
|
|
32360
32601
|
setValue("");
|
|
32361
32602
|
setCursor(0);
|
|
32603
|
+
resetCursorBlink();
|
|
32362
32604
|
setCompletionIndex(0);
|
|
32363
32605
|
setDismissedMenuValue(void 0);
|
|
32364
32606
|
if (text === "") return;
|
|
@@ -32469,6 +32711,10 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32469
32711
|
openTodos();
|
|
32470
32712
|
return;
|
|
32471
32713
|
}
|
|
32714
|
+
if (text === "/vscode-keys" || text.startsWith("/vscode-keys ")) {
|
|
32715
|
+
applyEditorKeys().then((summary) => notify(summary), (error) => notify(`vscode-keys failed: ${error instanceof Error ? error.message : String(error)}`, "error"));
|
|
32716
|
+
return;
|
|
32717
|
+
}
|
|
32472
32718
|
if (text === "/subagent") {
|
|
32473
32719
|
openSubagent();
|
|
32474
32720
|
return;
|
|
@@ -32485,6 +32731,12 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32485
32731
|
return;
|
|
32486
32732
|
}
|
|
32487
32733
|
if (input === "\n" || input === "\r") return;
|
|
32734
|
+
if (menuActive && (key.tab || input.startsWith(" "))) {
|
|
32735
|
+
const remainder = key.tab ? "" : input.slice(1);
|
|
32736
|
+
acceptMenuCandidate();
|
|
32737
|
+
if (remainder !== "") applyEdit(insertText(valueRef.current, cursorRef.current, remainder));
|
|
32738
|
+
return;
|
|
32739
|
+
}
|
|
32488
32740
|
if (menuActive && key.upArrow) {
|
|
32489
32741
|
setCompletionIndex((index) => (index + menuRows.length - 1) % menuRows.length);
|
|
32490
32742
|
return;
|
|
@@ -32493,119 +32745,74 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32493
32745
|
setCompletionIndex((index) => (index + 1) % menuRows.length);
|
|
32494
32746
|
return;
|
|
32495
32747
|
}
|
|
32496
|
-
const
|
|
32497
|
-
|
|
32498
|
-
|
|
32499
|
-
|
|
32500
|
-
else if (rawKey === "delete-backward") applyEdit(deleteBackward(value, cursor));
|
|
32501
|
-
else if (rawKey === "delete-word-backward") applyEdit(deleteWordBackward(value, cursor));
|
|
32502
|
-
else if (rawKey === "delete-forward") applyEdit(deleteForward(value, cursor));
|
|
32503
|
-
else applyEdit(deleteWordForward(value, cursor));
|
|
32748
|
+
const rawTokens = rawEditorTokens.current;
|
|
32749
|
+
rawEditorTokens.current = void 0;
|
|
32750
|
+
if (rawTokens !== void 0) {
|
|
32751
|
+
applyRawEditorTokens(rawTokens);
|
|
32504
32752
|
return;
|
|
32505
32753
|
}
|
|
32506
32754
|
if (key.upArrow || key.downArrow) {
|
|
32507
|
-
|
|
32508
|
-
const step = key.upArrow ? recallOlder(recall.current, value) : recallNewer(recall.current);
|
|
32509
|
-
recall.current = step.state;
|
|
32510
|
-
if (step.entry !== void 0) {
|
|
32511
|
-
const safe = sanitizeDraftText(step.entry);
|
|
32512
|
-
setValue(safe);
|
|
32513
|
-
setCursor(safe.length);
|
|
32514
|
-
preferredColumnRef.current = null;
|
|
32515
|
-
setDismissedMenuValue(void 0);
|
|
32516
|
-
}
|
|
32517
|
-
return;
|
|
32518
|
-
}
|
|
32519
|
-
const model = editorModel(value, Math.max(1, columns - 6));
|
|
32520
|
-
const preferred = preferredColumnRef.current ?? caretSite(model, cursor).column;
|
|
32521
|
-
const next = moveCursorVertically(model, cursor, preferred, key.upArrow ? -1 : 1);
|
|
32522
|
-
if (next !== cursor) {
|
|
32523
|
-
setCursor(next);
|
|
32524
|
-
preferredColumnRef.current = preferred;
|
|
32525
|
-
}
|
|
32755
|
+
navigateVertical(key.upArrow ? -1 : 1);
|
|
32526
32756
|
return;
|
|
32527
32757
|
}
|
|
32528
32758
|
if (key.ctrl && (input === "p" || input === "n")) {
|
|
32529
|
-
|
|
32530
|
-
if (recall.current.entries.length > 0 && shouldRecallNavigate(value, cursor, recall.current.lastRecalled)) {
|
|
32531
|
-
const step = up ? recallOlder(recall.current, value) : recallNewer(recall.current);
|
|
32532
|
-
recall.current = step.state;
|
|
32533
|
-
if (step.entry !== void 0) {
|
|
32534
|
-
const safe = sanitizeDraftText(step.entry);
|
|
32535
|
-
setValue(safe);
|
|
32536
|
-
setCursor(safe.length);
|
|
32537
|
-
preferredColumnRef.current = null;
|
|
32538
|
-
setDismissedMenuValue(void 0);
|
|
32539
|
-
}
|
|
32540
|
-
return;
|
|
32541
|
-
}
|
|
32542
|
-
const model = editorModel(value, Math.max(1, columns - 6));
|
|
32543
|
-
const preferred = preferredColumnRef.current ?? caretSite(model, cursor).column;
|
|
32544
|
-
const next = moveCursorVertically(model, cursor, preferred, up ? -1 : 1);
|
|
32545
|
-
if (next !== cursor) {
|
|
32546
|
-
setCursor(next);
|
|
32547
|
-
preferredColumnRef.current = preferred;
|
|
32548
|
-
}
|
|
32549
|
-
return;
|
|
32550
|
-
}
|
|
32551
|
-
if (key.tab && menuActive) {
|
|
32552
|
-
acceptMenuCandidate();
|
|
32759
|
+
navigateVertical(input === "p" ? -1 : 1);
|
|
32553
32760
|
return;
|
|
32554
32761
|
}
|
|
32555
32762
|
if (key.leftArrow) {
|
|
32556
|
-
moveCursorTo(key.meta || key.ctrl ? moveWordLeft(
|
|
32763
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordLeft(liveValue, liveCursor) : moveCursorBy(liveValue, liveCursor, -1));
|
|
32557
32764
|
return;
|
|
32558
32765
|
}
|
|
32559
32766
|
if (key.rightArrow) {
|
|
32560
|
-
moveCursorTo(key.meta || key.ctrl ? moveWordRight(
|
|
32767
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordRight(liveValue, liveCursor) : moveCursorBy(liveValue, liveCursor, 1));
|
|
32561
32768
|
return;
|
|
32562
32769
|
}
|
|
32563
32770
|
if (key.meta && input === "b") {
|
|
32564
|
-
moveCursorTo(moveWordLeft(
|
|
32771
|
+
moveCursorTo(moveWordLeft(liveValue, liveCursor));
|
|
32565
32772
|
return;
|
|
32566
32773
|
}
|
|
32567
32774
|
if (key.meta && input === "f") {
|
|
32568
|
-
moveCursorTo(moveWordRight(
|
|
32775
|
+
moveCursorTo(moveWordRight(liveValue, liveCursor));
|
|
32569
32776
|
return;
|
|
32570
32777
|
}
|
|
32571
32778
|
if (key.ctrl && input === "b") {
|
|
32572
|
-
moveCursorTo(moveCursorBy(
|
|
32779
|
+
moveCursorTo(moveCursorBy(liveValue, liveCursor, -1));
|
|
32573
32780
|
return;
|
|
32574
32781
|
}
|
|
32575
32782
|
if (key.ctrl && input === "f") {
|
|
32576
|
-
moveCursorTo(moveCursorBy(
|
|
32783
|
+
moveCursorTo(moveCursorBy(liveValue, liveCursor, 1));
|
|
32577
32784
|
return;
|
|
32578
32785
|
}
|
|
32579
32786
|
if (key.ctrl && input === "w") {
|
|
32580
|
-
applyEdit(deleteWordBackward(
|
|
32787
|
+
applyEdit(deleteWordBackward(liveValue, liveCursor));
|
|
32581
32788
|
return;
|
|
32582
32789
|
}
|
|
32583
32790
|
if (key.meta && input === "d") {
|
|
32584
|
-
applyEdit(deleteWordForward(
|
|
32791
|
+
applyEdit(deleteWordForward(liveValue, liveCursor));
|
|
32585
32792
|
return;
|
|
32586
32793
|
}
|
|
32587
32794
|
if (key.backspace || key.delete) {
|
|
32588
|
-
applyEdit(deleteBackward(
|
|
32795
|
+
applyEdit(deleteBackward(liveValue, liveCursor));
|
|
32589
32796
|
return;
|
|
32590
32797
|
}
|
|
32591
32798
|
if (key.ctrl && input === "a") {
|
|
32592
|
-
moveCursorTo(
|
|
32799
|
+
moveCursorTo(moveToLineStart(liveValue, liveCursor, true));
|
|
32593
32800
|
return;
|
|
32594
32801
|
}
|
|
32595
32802
|
if (key.ctrl && input === "e") {
|
|
32596
|
-
moveCursorTo(
|
|
32803
|
+
moveCursorTo(moveToLineEnd(liveValue, liveCursor, true));
|
|
32597
32804
|
return;
|
|
32598
32805
|
}
|
|
32599
32806
|
if (key.ctrl && input === "u") {
|
|
32600
|
-
applyEdit(killToLineStart(
|
|
32807
|
+
applyEdit(killToLineStart(liveValue, liveCursor));
|
|
32601
32808
|
return;
|
|
32602
32809
|
}
|
|
32603
32810
|
if (key.ctrl && input === "k") {
|
|
32604
|
-
applyEdit(killToLineEnd(
|
|
32811
|
+
applyEdit(killToLineEnd(liveValue, liveCursor));
|
|
32605
32812
|
return;
|
|
32606
32813
|
}
|
|
32607
32814
|
if (key.ctrl && input === "y") {
|
|
32608
|
-
if (killRef.current !== "") applyEdit(insertText(
|
|
32815
|
+
if (killRef.current !== "") applyEdit(insertText(liveValue, liveCursor, killRef.current));
|
|
32609
32816
|
return;
|
|
32610
32817
|
}
|
|
32611
32818
|
if (key.ctrl && input === "l") {
|
|
@@ -32638,9 +32845,9 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32638
32845
|
insertDroppedImages(droppedPaths);
|
|
32639
32846
|
return;
|
|
32640
32847
|
}
|
|
32641
|
-
applyEdit(insertText(
|
|
32848
|
+
applyEdit(insertText(valueRef.current, cursorRef.current, text));
|
|
32642
32849
|
}
|
|
32643
|
-
});
|
|
32850
|
+
}, active);
|
|
32644
32851
|
const [waveTick, setWaveTick] = (0, import_react.useState)(null);
|
|
32645
32852
|
const wavePrevious = (0, import_react.useRef)({
|
|
32646
32853
|
tier: null,
|
|
@@ -32658,7 +32865,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32658
32865
|
}
|
|
32659
32866
|
if (previous.tier !== waveTier || previous.style !== waveStyle) setWaveTick(0);
|
|
32660
32867
|
}, [waveTier, waveStyle]);
|
|
32661
|
-
const waveActive = waveTick !== null && waveTier !== null && waveStyle !== null && waveTick * 33 < deepseekWaveDuration(waveTier, waveStyle);
|
|
32868
|
+
const waveActive = !preparingImages && waveTick !== null && waveTier !== null && waveStyle !== null && waveTick * 33 < deepseekWaveDuration(waveTier, waveStyle);
|
|
32662
32869
|
(0, import_react.useEffect)(() => {
|
|
32663
32870
|
if (!waveActive) return;
|
|
32664
32871
|
const id = setInterval(() => {
|
|
@@ -32679,7 +32886,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32679
32886
|
const tierHues = waveTier === null ? null : deepseekWaveHues(waveTier);
|
|
32680
32887
|
const promptColor = tierHues === null ? inkColor(getPalette().brand) : inkColor(tierHues[0]);
|
|
32681
32888
|
const promptGlyph = waveTier === "flash" ? "›" : waveTier === "deepseek" ? "»" : "❯";
|
|
32682
|
-
const editorViewModel = editorModel(value,
|
|
32889
|
+
const editorViewModel = editorModel(value, editorColumns);
|
|
32683
32890
|
const clampedCursor = clampCursor(value, cursor);
|
|
32684
32891
|
const caret = caretSite(editorViewModel, clampedCursor);
|
|
32685
32892
|
const editorWindowRows = Math.min(editorViewModel.rows.length, Math.max(1, maxRows));
|
|
@@ -32730,123 +32937,120 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32730
32937
|
const editorRows = [];
|
|
32731
32938
|
for (let index = editorWindowStart; index < Math.min(editorViewModel.rows.length, editorWindowStart + editorWindowRows); index += 1) {
|
|
32732
32939
|
const row = editorViewModel.rows[index];
|
|
32733
|
-
const
|
|
32734
|
-
const
|
|
32735
|
-
const
|
|
32736
|
-
const
|
|
32737
|
-
const placeholder = index === 0 && value === "" && !busy;
|
|
32738
|
-
const tail = placeholder ? COMPOSER_PLACEHOLDER : after;
|
|
32739
|
-
const consumed = 2 + visibleColumns(before) + visibleColumns(caretChar) + visibleColumns(tail);
|
|
32940
|
+
const parts = editorRowParts(row, index, caret.row, clampedCursor, !preparingImages);
|
|
32941
|
+
const placeholder = index === 0 && value === "" && !busy && !preparingImages;
|
|
32942
|
+
const tail = placeholder ? COMPOSER_PLACEHOLDER : parts.after;
|
|
32943
|
+
const consumed = 2 + visibleColumns(parts.before) + visibleColumns(parts.caret) + visibleColumns(tail);
|
|
32740
32944
|
editorRows.push((0, import_react.createElement)(Text, {
|
|
32741
32945
|
key: index,
|
|
32742
32946
|
backgroundColor: bandBg,
|
|
32743
32947
|
wrap: "truncate-end"
|
|
32744
|
-
}, index === 0 ?
|
|
32948
|
+
}, index === 0 ? preparingImages ? (0, import_react.createElement)(Text, {
|
|
32949
|
+
color: inkColor(getPalette().warn),
|
|
32950
|
+
bold: true
|
|
32951
|
+
}, "… ") : busy ? (0, import_react.createElement)(BusyChase) : (0, import_react.createElement)(Text, {
|
|
32745
32952
|
color: promptColor,
|
|
32746
32953
|
bold: tierActive ? true : void 0
|
|
32747
|
-
}, `${promptGlyph} `) : " ", before, (0, import_react.createElement)(
|
|
32954
|
+
}, `${promptGlyph} `) : " ", parts.before, parts.hasCaret ? (0, import_react.createElement)(Text, {
|
|
32748
32955
|
key: "caret",
|
|
32749
|
-
|
|
32750
|
-
}), placeholder ? (0, import_react.createElement)(Text, { dimColor: true }, COMPOSER_PLACEHOLDER) : after, bandFill(consumed)));
|
|
32956
|
+
inverse: cursorVisible || void 0
|
|
32957
|
+
}, parts.caret) : null, placeholder ? (0, import_react.createElement)(Text, { dimColor: true }, COMPOSER_PLACEHOLDER) : parts.after, bandFill(consumed)));
|
|
32751
32958
|
}
|
|
32752
32959
|
const staticEditor = (0, import_react.createElement)(Box, { flexDirection: "column" }, ...editorRows);
|
|
32753
32960
|
const waveRow = () => {
|
|
32754
32961
|
const hues = deepseekWaveHues(waveTier);
|
|
32755
32962
|
const style = waveStyle;
|
|
32756
32963
|
const bandRgb = getPalette().composerBand;
|
|
32964
|
+
const visibleRows = editorViewModel.rows.slice(editorWindowStart, editorWindowStart + editorWindowRows);
|
|
32965
|
+
const totalBandRows = visibleRows.length + 2;
|
|
32757
32966
|
const waveBg = (row, column) => {
|
|
32758
|
-
const rgb = deepseekWaveColumnBg(waveTick, column, bandWidth, waveTier, style, hues, bandRgb, row,
|
|
32967
|
+
const rgb = deepseekWaveColumnBg(waveTick, column, bandWidth, waveTier, style, hues, bandRgb, row, totalBandRows);
|
|
32759
32968
|
return rgb === null ? bandBg : inkColor(rgb);
|
|
32760
32969
|
};
|
|
32761
32970
|
const blankBandRow = (row) => {
|
|
32762
32971
|
const blanks = [];
|
|
32763
|
-
|
|
32972
|
+
for (let column = 0; column < bandWidth; column += 1) blanks.push({
|
|
32764
32973
|
char: " ",
|
|
32765
|
-
|
|
32974
|
+
width: 1,
|
|
32975
|
+
backgroundColor: waveBg(row, column)
|
|
32766
32976
|
});
|
|
32767
|
-
return (0, import_react.createElement)(Text, { key: row }, ...waveRowSpans(blanks));
|
|
32977
|
+
return (0, import_react.createElement)(Text, { key: `blank-${row}` }, ...waveRowSpans(blanks));
|
|
32768
32978
|
};
|
|
32769
|
-
const
|
|
32770
|
-
|
|
32771
|
-
{
|
|
32772
|
-
|
|
32773
|
-
|
|
32774
|
-
|
|
32775
|
-
{
|
|
32776
|
-
char: " ",
|
|
32777
|
-
backgroundColor: waveBg(1, 1)
|
|
32778
|
-
},
|
|
32779
|
-
{
|
|
32780
|
-
char: promptGlyph,
|
|
32781
|
-
color: promptColor,
|
|
32782
|
-
bold: true,
|
|
32783
|
-
backgroundColor: waveBg(1, 2)
|
|
32784
|
-
},
|
|
32785
|
-
{
|
|
32786
|
-
char: " ",
|
|
32787
|
-
color: promptColor,
|
|
32788
|
-
backgroundColor: waveBg(1, 3)
|
|
32979
|
+
const cellIndexAtColumn = (cells, target) => {
|
|
32980
|
+
let column = 0;
|
|
32981
|
+
for (let index = 0; index < cells.length; index += 1) {
|
|
32982
|
+
if (column === target) return index;
|
|
32983
|
+
column += cells[index].width ?? visibleColumns(cells[index].char);
|
|
32984
|
+
if (column > target) return void 0;
|
|
32789
32985
|
}
|
|
32790
|
-
|
|
32791
|
-
|
|
32792
|
-
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
32796
|
-
|
|
32797
|
-
|
|
32798
|
-
|
|
32799
|
-
|
|
32800
|
-
|
|
32801
|
-
|
|
32802
|
-
|
|
32803
|
-
|
|
32804
|
-
|
|
32805
|
-
|
|
32806
|
-
|
|
32807
|
-
|
|
32808
|
-
|
|
32809
|
-
|
|
32810
|
-
|
|
32811
|
-
|
|
32986
|
+
};
|
|
32987
|
+
const editorWaveRows = visibleRows.map((row, visibleIndex) => {
|
|
32988
|
+
const sourceIndex = editorWindowStart + visibleIndex;
|
|
32989
|
+
const bandRow = visibleIndex + 1;
|
|
32990
|
+
const parts = editorRowParts(row, sourceIndex, caret.row, clampedCursor);
|
|
32991
|
+
const placeholder = sourceIndex === 0 && value === "" && !busy;
|
|
32992
|
+
const cells = [];
|
|
32993
|
+
let usedColumns = 0;
|
|
32994
|
+
const push = (char, extra = {}) => {
|
|
32995
|
+
const width = visibleColumns(char);
|
|
32996
|
+
cells.push({
|
|
32997
|
+
char,
|
|
32998
|
+
width,
|
|
32999
|
+
backgroundColor: waveBg(bandRow, usedColumns),
|
|
33000
|
+
...extra
|
|
33001
|
+
});
|
|
33002
|
+
usedColumns += width;
|
|
33003
|
+
};
|
|
33004
|
+
if (sourceIndex === 0) {
|
|
33005
|
+
push(promptGlyph, {
|
|
33006
|
+
color: promptColor,
|
|
33007
|
+
bold: true
|
|
33008
|
+
});
|
|
33009
|
+
push(" ", { color: promptColor });
|
|
33010
|
+
} else {
|
|
33011
|
+
push(" ");
|
|
33012
|
+
push(" ");
|
|
33013
|
+
}
|
|
33014
|
+
for (const span of splitGraphemes(parts.before)) push(span.text);
|
|
33015
|
+
if (parts.hasCaret) push(parts.caret, { inverse: cursorVisible });
|
|
33016
|
+
const tail = placeholder ? COMPOSER_PLACEHOLDER : parts.after;
|
|
33017
|
+
for (const span of splitGraphemes(tail)) push(span.text, placeholder ? { dim: true } : {});
|
|
33018
|
+
while (usedColumns < bandWidth) push(" ");
|
|
33019
|
+
const middleBandRow = Math.floor(totalBandRows / 2);
|
|
33020
|
+
if (bandRow === middleBandRow && deepseekWaveWordVisible(waveTick, waveTier, style)) {
|
|
33021
|
+
const word = waveTier === "unknown" ? "Into the Unknown" : "deepseek";
|
|
33022
|
+
const start = Math.max(2, Math.floor((bandWidth - word.length) / 2));
|
|
33023
|
+
const indices = Array.from({ length: word.length }, (_, at) => cellIndexAtColumn(cells, start + at));
|
|
33024
|
+
if (indices.every((index) => index !== void 0 && (cells[index].char === " " || cells[index].dim === true))) for (let at = 0; at < word.length; at += 1) {
|
|
33025
|
+
const cell = cells[indices[at]];
|
|
33026
|
+
cell.char = word[at];
|
|
33027
|
+
cell.width = 1;
|
|
33028
|
+
cell.color = inkColor(deepseekWaveWordHue(at, hues));
|
|
33029
|
+
cell.bold = true;
|
|
33030
|
+
cell.dim = false;
|
|
33031
|
+
}
|
|
33032
|
+
}
|
|
33033
|
+
if (bandRow === middleBandRow && (waveTier === "deepseek" || waveTier === "unknown") && style === "wave") {
|
|
33034
|
+
const spark = deepseekWaveSpark(waveTick);
|
|
33035
|
+
const lastIndex = cellIndexAtColumn(cells, bandWidth - 1);
|
|
33036
|
+
if (spark !== null && lastIndex !== void 0 && cells[lastIndex].char === " ") {
|
|
33037
|
+
cells[lastIndex].char = spark;
|
|
33038
|
+
cells[lastIndex].color = promptColor;
|
|
33039
|
+
cells[lastIndex].bold = true;
|
|
33040
|
+
cells[lastIndex].dim = false;
|
|
33041
|
+
}
|
|
33042
|
+
}
|
|
33043
|
+
return (0, import_react.createElement)(Text, {
|
|
33044
|
+
key: `editor-${sourceIndex}`,
|
|
33045
|
+
wrap: "truncate-end"
|
|
33046
|
+
}, ...waveRowSpans(cells));
|
|
32812
33047
|
});
|
|
32813
|
-
if (deepseekWaveWordVisible(waveTick, waveTier, style)) {
|
|
32814
|
-
const word = waveTier === "unknown" ? "Into the Unknown" : "deepseek";
|
|
32815
|
-
const start = Math.max(2, Math.floor((bandWidth - word.length) / 2));
|
|
32816
|
-
let clear = true;
|
|
32817
|
-
for (let at = 0; at < word.length; at += 1) {
|
|
32818
|
-
const cell = cells[start + at];
|
|
32819
|
-
if (cell === void 0 || cell.char !== " " && cell.dim !== true) {
|
|
32820
|
-
clear = false;
|
|
32821
|
-
break;
|
|
32822
|
-
}
|
|
32823
|
-
}
|
|
32824
|
-
if (clear) for (let at = 0; at < word.length; at += 1) {
|
|
32825
|
-
const cell = cells[start + at];
|
|
32826
|
-
cell.char = word[at];
|
|
32827
|
-
cell.color = inkColor(deepseekWaveWordHue(at, hues));
|
|
32828
|
-
cell.bold = true;
|
|
32829
|
-
cell.dim = false;
|
|
32830
|
-
}
|
|
32831
|
-
}
|
|
32832
|
-
if ((waveTier === "deepseek" || waveTier === "unknown") && style === "wave") {
|
|
32833
|
-
const spark = deepseekWaveSpark(waveTick);
|
|
32834
|
-
if (spark !== null) {
|
|
32835
|
-
const last = cells[cells.length - 1];
|
|
32836
|
-
if (last !== void 0 && last.char === " ") {
|
|
32837
|
-
last.char = spark;
|
|
32838
|
-
last.color = promptColor;
|
|
32839
|
-
last.bold = true;
|
|
32840
|
-
last.dim = false;
|
|
32841
|
-
}
|
|
32842
|
-
}
|
|
32843
|
-
}
|
|
32844
33048
|
return (0, import_react.createElement)(Box, {
|
|
32845
33049
|
flexDirection: "column",
|
|
32846
33050
|
width: bandWidth
|
|
32847
|
-
}, blankBandRow(0),
|
|
33051
|
+
}, blankBandRow(0), ...editorWaveRows, blankBandRow(totalBandRows - 1));
|
|
32848
33052
|
};
|
|
32849
|
-
return (0, import_react.createElement)(Box, { flexDirection: "column" }, menu, waveTick !== null && waveTier !== null && !busy ? waveRow() : band(staticEditor));
|
|
33053
|
+
return (0, import_react.createElement)(Box, { flexDirection: "column" }, menu, waveTick !== null && waveTier !== null && waveStyle !== null && !busy && !preparingImages ? waveRow() : band(staticEditor));
|
|
32850
33054
|
}
|
|
32851
33055
|
/** Build one settled row (row Box plus its roomy-prompt spacers and row count). */
|
|
32852
33056
|
function buildSettledRow(entry, index, showReasoning, columns) {
|
|
@@ -33143,6 +33347,7 @@ function App(props) {
|
|
|
33143
33347
|
}, [modelOpen, props.subscribeProviderAuthorizations]);
|
|
33144
33348
|
const busy = view.busy;
|
|
33145
33349
|
const [showReasoning, setShowReasoning] = (0, import_react.useState)(false);
|
|
33350
|
+
const budgetWarnRef = (0, import_react.useRef)(void 0);
|
|
33146
33351
|
const [verboseOpen, setVerboseOpen] = (0, import_react.useState)(false);
|
|
33147
33352
|
const [diffView, setDiffView] = (0, import_react.useState)(void 0);
|
|
33148
33353
|
const [helpOpen, setHelpOpen] = (0, import_react.useState)(false);
|
|
@@ -33311,6 +33516,18 @@ function App(props) {
|
|
|
33311
33516
|
const streamRows = Math.max(1, dynamicRows - visibleLiveLines.length);
|
|
33312
33517
|
const reasoningRows = view.streamingReasoning === "" ? 0 : view.streaming === "" ? streamRows : streamRows <= 1 ? 0 : showReasoning ? Math.max(1, Math.floor(streamRows / 3)) : 1;
|
|
33313
33518
|
const answerRows = view.streaming === "" ? 0 : Math.max(1, streamRows - reasoningRows);
|
|
33519
|
+
const liveAudit = clampLiveAllocation({
|
|
33520
|
+
live: visibleLiveLines.length,
|
|
33521
|
+
reasoning: reasoningRows,
|
|
33522
|
+
answer: answerRows
|
|
33523
|
+
}, dynamicRows);
|
|
33524
|
+
if (liveAudit.warning !== void 0 && budgetWarnRef.current !== liveAudit.warning) {
|
|
33525
|
+
budgetWarnRef.current = liveAudit.warning;
|
|
33526
|
+
console.warn(`[dsh-code] ${liveAudit.warning}`);
|
|
33527
|
+
}
|
|
33528
|
+
const auditedLiveLines = liveAudit.allocation.live === visibleLiveLines.length ? visibleLiveLines : visibleLiveLines.slice(-liveAudit.allocation.live);
|
|
33529
|
+
const auditedReasoningRows = liveAudit.allocation.reasoning;
|
|
33530
|
+
const auditedAnswerRows = liveAudit.allocation.answer;
|
|
33314
33531
|
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === void 0 && !approvalPending && !questionPending;
|
|
33315
33532
|
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || jobsOpen || statuslineOpen || themeOpen || historyOpen || agentsOpen || subagentOpen || todosOpen || verboseOpen && !approvalPending && !questionPending || diffView !== void 0 || approvalPending || questionPending;
|
|
33316
33533
|
const closeInspector = (0, import_react.useCallback)(() => {
|
|
@@ -33548,18 +33765,27 @@ function App(props) {
|
|
|
33548
33765
|
return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(MemoStaticTranscript, {
|
|
33549
33766
|
key: refreshEpoch,
|
|
33550
33767
|
items: settledRows
|
|
33551
|
-
}), transcriptVisible ? (0, import_react.createElement)(Box, { flexDirection: "column" },
|
|
33552
|
-
text:
|
|
33768
|
+
}), transcriptVisible ? (0, import_react.createElement)(Box, { flexDirection: "column" }, auditedLiveLines.length === 0 ? void 0 : (0, import_react.createElement)(StyledRows, { lines: auditedLiveLines }), view.streamingReasoning !== "" && auditedReasoningRows > 0 ? showReasoning ? (0, import_react.createElement)(StreamTail, {
|
|
33769
|
+
text: view.streamingReasoning,
|
|
33770
|
+
prefix: "✻ ",
|
|
33771
|
+
continuationPrefix: " ",
|
|
33772
|
+
dim: true,
|
|
33773
|
+
maxRows: auditedReasoningRows
|
|
33774
|
+
}) : view.streaming === "" ? (0, import_react.createElement)(ShimmerLine, { text: "✻ Thinking… (Ctrl/Alt+R to expand)" }) : (0, import_react.createElement)(StreamTail, {
|
|
33775
|
+
text: "Thinking… (Ctrl/Alt+R to expand)",
|
|
33553
33776
|
prefix: "✻ ",
|
|
33554
33777
|
continuationPrefix: " ",
|
|
33555
33778
|
dim: true,
|
|
33556
|
-
maxRows:
|
|
33557
|
-
}) : void 0, view.streaming !== "" &&
|
|
33779
|
+
maxRows: auditedReasoningRows
|
|
33780
|
+
}) : void 0, view.streaming !== "" && auditedAnswerRows > 0 ? (0, import_react.createElement)(StreamTail, {
|
|
33558
33781
|
text: view.streaming,
|
|
33559
33782
|
dim: false,
|
|
33560
|
-
maxRows:
|
|
33783
|
+
maxRows: auditedAnswerRows,
|
|
33561
33784
|
prefix: " "
|
|
33562
|
-
}, 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, {
|
|
33785
|
+
}, 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, {
|
|
33786
|
+
rows: agentRows,
|
|
33787
|
+
total: props.subagents.getTotalSeen()
|
|
33788
|
+
}) : void 0, todosOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(MemoTodoListPanel, {
|
|
33563
33789
|
todos: view.todos,
|
|
33564
33790
|
onClose: () => {
|
|
33565
33791
|
setTodosOpen(false);
|
|
@@ -33692,6 +33918,7 @@ function App(props) {
|
|
|
33692
33918
|
descriptors,
|
|
33693
33919
|
skills,
|
|
33694
33920
|
dispatch: props.dispatch,
|
|
33921
|
+
applyEditorKeys: props.applyEditorKeys,
|
|
33695
33922
|
steer: props.steer,
|
|
33696
33923
|
interrupt: props.interrupt,
|
|
33697
33924
|
quit: props.quit,
|
|
@@ -33786,7 +34013,7 @@ function App(props) {
|
|
|
33786
34013
|
refresh: refreshScreen,
|
|
33787
34014
|
toggleReasoning: () => {
|
|
33788
34015
|
setShowReasoning((current) => !current);
|
|
33789
|
-
|
|
34016
|
+
refreshScreen();
|
|
33790
34017
|
},
|
|
33791
34018
|
loadMentions: props.loadMentions,
|
|
33792
34019
|
inspectImages: props.inspectImages,
|
|
@@ -33992,7 +34219,9 @@ function isSlashLine(line) {
|
|
|
33992
34219
|
/** Substitutable runner effects; production values write to the real terminal. */
|
|
33993
34220
|
const internals = {
|
|
33994
34221
|
mount: (element) => {
|
|
33995
|
-
|
|
34222
|
+
const keyboardEnhanced = shouldEnableKeyboardEnhancement();
|
|
34223
|
+
const focusReporting = isVsCodeTerminalEnv();
|
|
34224
|
+
process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_ENABLE : "") + BRACKETED_PASTE_ENABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_ENABLE : ""));
|
|
33996
34225
|
const instance = render(element, { exitOnCtrlC: false });
|
|
33997
34226
|
return {
|
|
33998
34227
|
rerender(element) {
|
|
@@ -34000,7 +34229,7 @@ const internals = {
|
|
|
34000
34229
|
},
|
|
34001
34230
|
unmount() {
|
|
34002
34231
|
instance.unmount();
|
|
34003
|
-
process.stdout.write("
|
|
34232
|
+
process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_DISABLE : "") + BRACKETED_PASTE_DISABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_DISABLE : ""));
|
|
34004
34233
|
}
|
|
34005
34234
|
};
|
|
34006
34235
|
},
|
|
@@ -34153,6 +34382,202 @@ async function loadModelDirectory(ctx) {
|
|
|
34153
34382
|
};
|
|
34154
34383
|
}
|
|
34155
34384
|
//#endregion
|
|
34385
|
+
//#region src/model-capabilities.ts
|
|
34386
|
+
/** The settings namespace the llm-pi-ai plugin owns (identity via `settingsNamespace`). */
|
|
34387
|
+
const PI_AI_SETTINGS_NS = "llm-pi-ai";
|
|
34388
|
+
/** The effort level that means "send no reasoning parameter"; its wire value is always null. */
|
|
34389
|
+
const OFF_LEVEL = "off";
|
|
34390
|
+
/** Single-line a misbehaving error for the notice channel. */
|
|
34391
|
+
function singleLine$1(message) {
|
|
34392
|
+
return [...message].map((ch) => {
|
|
34393
|
+
const code = ch.charCodeAt(0);
|
|
34394
|
+
return code < 32 || code === 127 ? " " : ch;
|
|
34395
|
+
}).join("").split(" ").filter((part) => part !== "").join(" ");
|
|
34396
|
+
}
|
|
34397
|
+
/** Human text for a rejection value. */
|
|
34398
|
+
function messageOf$1(error) {
|
|
34399
|
+
return error instanceof Error ? error.message : String(error);
|
|
34400
|
+
}
|
|
34401
|
+
/** Read the value at a path through plain objects; undefined when any segment misses. */
|
|
34402
|
+
function getPath$1(value, path) {
|
|
34403
|
+
let current = value;
|
|
34404
|
+
for (const segment of path) {
|
|
34405
|
+
if (typeof current !== "object" || current === null) return void 0;
|
|
34406
|
+
current = current[segment];
|
|
34407
|
+
}
|
|
34408
|
+
return current;
|
|
34409
|
+
}
|
|
34410
|
+
/** Whether a raw settings value is a usable `reasoningEfforts` dict (non-empty, non-false). */
|
|
34411
|
+
function isDeclaredEfforts(value) {
|
|
34412
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
34413
|
+
}
|
|
34414
|
+
/**
|
|
34415
|
+
* The identity level map for an advertised effort list: `off` sends no
|
|
34416
|
+
* parameter, every other level keeps its own name — the wire spelling
|
|
34417
|
+
* OpenAI-compatible relays accept as-is.
|
|
34418
|
+
*/
|
|
34419
|
+
function identityEfforts(levels) {
|
|
34420
|
+
const dict = {};
|
|
34421
|
+
for (const level of levels) dict[level] = level === OFF_LEVEL ? null : level;
|
|
34422
|
+
return dict;
|
|
34423
|
+
}
|
|
34424
|
+
/**
|
|
34425
|
+
* Plan the reasoning declarations to materialize. A model entry inherits
|
|
34426
|
+
* when it declares nothing (`reasoningEfforts` absent — a dict or `false` is
|
|
34427
|
+
* an explicit choice and is never touched) and its live row advertises no
|
|
34428
|
+
* efforts; the donor is the first sibling settings declaration for the same
|
|
34429
|
+
* id, copied verbatim so dialect wire spellings survive, otherwise the first
|
|
34430
|
+
* other-route row advertising efforts for that id, mapped by identity.
|
|
34431
|
+
* Entries never lose fields and keep their key order; a provider appears in
|
|
34432
|
+
* the result only when at least one entry changes.
|
|
34433
|
+
* @param input - the live model rows and the raw pi-ai profiles from settings.
|
|
34434
|
+
* @returns one plan per provider with at least one inheritance.
|
|
34435
|
+
*/
|
|
34436
|
+
function planCapabilitySync(input) {
|
|
34437
|
+
const declared = /* @__PURE__ */ new Map();
|
|
34438
|
+
for (const [provider, profile] of input.profiles) for (const entry of profile.models) {
|
|
34439
|
+
const efforts = entry.reasoningEfforts;
|
|
34440
|
+
if (!isDeclaredEfforts(efforts)) continue;
|
|
34441
|
+
if (!declared.has(String(entry.id))) declared.set(String(entry.id), {
|
|
34442
|
+
dict: efforts,
|
|
34443
|
+
provider
|
|
34444
|
+
});
|
|
34445
|
+
}
|
|
34446
|
+
const advertised = /* @__PURE__ */ new Map();
|
|
34447
|
+
for (const row of input.rows) {
|
|
34448
|
+
const levels = row.reasoning?.efforts.map((effort) => effort.id) ?? [];
|
|
34449
|
+
if (levels.filter((level) => level !== OFF_LEVEL).length === 0) continue;
|
|
34450
|
+
if (!advertised.has(row.model)) advertised.set(row.model, {
|
|
34451
|
+
levels,
|
|
34452
|
+
provider: row.provider
|
|
34453
|
+
});
|
|
34454
|
+
}
|
|
34455
|
+
const plans = [];
|
|
34456
|
+
for (const [provider, profile] of input.profiles) {
|
|
34457
|
+
const inherited = [];
|
|
34458
|
+
const sources = [];
|
|
34459
|
+
const models = profile.models.map((entry) => {
|
|
34460
|
+
if (entry.reasoningEfforts !== void 0) return entry;
|
|
34461
|
+
const id = String(entry.id);
|
|
34462
|
+
const sibling = declared.get(id);
|
|
34463
|
+
if (sibling !== void 0 && sibling.provider !== provider) {
|
|
34464
|
+
inherited.push(id);
|
|
34465
|
+
if (!sources.includes(`${sibling.provider}/${id}`)) sources.push(`${sibling.provider}/${id}`);
|
|
34466
|
+
return {
|
|
34467
|
+
...entry,
|
|
34468
|
+
reasoningEfforts: sibling.dict
|
|
34469
|
+
};
|
|
34470
|
+
}
|
|
34471
|
+
const donor = advertised.get(id);
|
|
34472
|
+
if (donor !== void 0 && donor.provider !== provider) {
|
|
34473
|
+
inherited.push(id);
|
|
34474
|
+
if (!sources.includes(`${donor.provider}/${id}`)) sources.push(`${donor.provider}/${id}`);
|
|
34475
|
+
return {
|
|
34476
|
+
...entry,
|
|
34477
|
+
reasoningEfforts: identityEfforts(donor.levels)
|
|
34478
|
+
};
|
|
34479
|
+
}
|
|
34480
|
+
return entry;
|
|
34481
|
+
});
|
|
34482
|
+
if (inherited.length > 0) plans.push({
|
|
34483
|
+
provider,
|
|
34484
|
+
settingsNs: profile.settingsNs,
|
|
34485
|
+
settingsPath: profile.settingsPath,
|
|
34486
|
+
models,
|
|
34487
|
+
sourceFingerprint: JSON.stringify(profile.models),
|
|
34488
|
+
sourceRevision: profile.revision,
|
|
34489
|
+
inherited,
|
|
34490
|
+
sources
|
|
34491
|
+
});
|
|
34492
|
+
}
|
|
34493
|
+
return plans;
|
|
34494
|
+
}
|
|
34495
|
+
/** Provider -> the source snapshot and document revision its last write landed against. */
|
|
34496
|
+
const lastApplied = /* @__PURE__ */ new Map();
|
|
34497
|
+
/** Fresh revision of one namespace, or undefined when it does not resolve. */
|
|
34498
|
+
function revisionOf(settings, ns) {
|
|
34499
|
+
try {
|
|
34500
|
+
for (const descriptor of settings.describe({ redactSecrets: true })) if (descriptor.ns === ns) return descriptor.revision;
|
|
34501
|
+
} catch {
|
|
34502
|
+
return;
|
|
34503
|
+
}
|
|
34504
|
+
}
|
|
34505
|
+
/**
|
|
34506
|
+
* Materialize same-id reasoning declarations once per provider. Reads the
|
|
34507
|
+
* configurable directory, the redacted settings document, and the live model
|
|
34508
|
+
* rows; plans; then writes each provider's merged models array through
|
|
34509
|
+
* `settings.mutate` under a fresh revision (writes bump the section
|
|
34510
|
+
* revision, so per-plan revisions are re-read). Every failure converges to a
|
|
34511
|
+
* single-line notice — the caller's promise never rejects and the session
|
|
34512
|
+
* keeps running on the previous configuration.
|
|
34513
|
+
* @param ctx - context carrying the `llm` and `settings` services (optional).
|
|
34514
|
+
* @param notify - the app bridge's notice sink, when one is live.
|
|
34515
|
+
*/
|
|
34516
|
+
async function syncModelCapabilities(ctx, notify) {
|
|
34517
|
+
try {
|
|
34518
|
+
const llm = ctx.get("llm");
|
|
34519
|
+
if (llm?.listConfigurableProviders === void 0) return;
|
|
34520
|
+
const settings = ctx.get("settings");
|
|
34521
|
+
if (settings === void 0) return;
|
|
34522
|
+
let entries;
|
|
34523
|
+
try {
|
|
34524
|
+
entries = llm.listConfigurableProviders();
|
|
34525
|
+
} catch {
|
|
34526
|
+
return;
|
|
34527
|
+
}
|
|
34528
|
+
const routed = entries.filter((entry) => entry.settingsNs === PI_AI_SETTINGS_NS);
|
|
34529
|
+
if (routed.length === 0) return;
|
|
34530
|
+
let descriptors;
|
|
34531
|
+
try {
|
|
34532
|
+
descriptors = settings.describe({ redactSecrets: true });
|
|
34533
|
+
} catch {
|
|
34534
|
+
return;
|
|
34535
|
+
}
|
|
34536
|
+
const namespaces = new Map(descriptors.map((descriptor) => [descriptor.ns, descriptor]));
|
|
34537
|
+
const profiles = /* @__PURE__ */ new Map();
|
|
34538
|
+
for (const entry of routed) {
|
|
34539
|
+
const namespace = namespaces.get(entry.settingsNs);
|
|
34540
|
+
if (namespace === void 0) continue;
|
|
34541
|
+
const profile = entry.settingsPath.length === 0 ? namespace.value : getPath$1(namespace.value, entry.settingsPath);
|
|
34542
|
+
const models = Array.isArray(profile?.models) ? profile.models : void 0;
|
|
34543
|
+
if (models === void 0) continue;
|
|
34544
|
+
profiles.set(entry.provider, {
|
|
34545
|
+
settingsNs: entry.settingsNs,
|
|
34546
|
+
settingsPath: entry.settingsPath,
|
|
34547
|
+
revision: namespace.revision,
|
|
34548
|
+
models
|
|
34549
|
+
});
|
|
34550
|
+
}
|
|
34551
|
+
if (profiles.size === 0) return;
|
|
34552
|
+
const directory = await loadModelDirectory(ctx);
|
|
34553
|
+
for (const plan of planCapabilitySync({
|
|
34554
|
+
rows: directory.rows,
|
|
34555
|
+
profiles
|
|
34556
|
+
})) {
|
|
34557
|
+
const applied = lastApplied.get(plan.provider);
|
|
34558
|
+
if (applied !== void 0 && applied.fingerprint === plan.sourceFingerprint && applied.revision === plan.sourceRevision) continue;
|
|
34559
|
+
const revision = revisionOf(settings, plan.settingsNs);
|
|
34560
|
+
if (revision === void 0) continue;
|
|
34561
|
+
try {
|
|
34562
|
+
await settings.mutate(plan.settingsNs, [{
|
|
34563
|
+
op: "set",
|
|
34564
|
+
path: [...plan.settingsPath, "models"],
|
|
34565
|
+
value: plan.models
|
|
34566
|
+
}], revision);
|
|
34567
|
+
lastApplied.set(plan.provider, {
|
|
34568
|
+
fingerprint: plan.sourceFingerprint,
|
|
34569
|
+
revision: plan.sourceRevision
|
|
34570
|
+
});
|
|
34571
|
+
const shown = plan.sources.slice(0, 3).join(", ");
|
|
34572
|
+
const more = plan.sources.length > 3 ? `, +${plan.sources.length - 3}` : "";
|
|
34573
|
+
notify?.(`inherited reasoning levels for ${plan.inherited.length} model${plan.inherited.length === 1 ? "" : "s"} on ${plan.provider} (from ${shown}${more})`, "info");
|
|
34574
|
+
} catch (error) {
|
|
34575
|
+
notify?.(`capability inheritance failed on ${plan.provider}: ${singleLine$1(messageOf$1(error))}`, "warning");
|
|
34576
|
+
}
|
|
34577
|
+
}
|
|
34578
|
+
} catch {}
|
|
34579
|
+
}
|
|
34580
|
+
//#endregion
|
|
34156
34581
|
//#region src/provider-settings.ts
|
|
34157
34582
|
/** Human text for a rejection value (mirrors the web page's `messageOf`). */
|
|
34158
34583
|
function messageOf(error) {
|
|
@@ -34201,11 +34626,13 @@ function configurationOf(profile) {
|
|
|
34201
34626
|
if (typeof value !== "object" || value === null) return [];
|
|
34202
34627
|
const entry = value;
|
|
34203
34628
|
if (typeof entry.id !== "string" || entry.id.trim() === "") return [];
|
|
34629
|
+
const { id: _id, name: _name, contextWindow: _contextWindow, maxTokens: _maxTokens, ...extras } = entry;
|
|
34204
34630
|
return [{
|
|
34205
34631
|
id: entry.id,
|
|
34206
34632
|
...typeof entry.name === "string" && entry.name.trim() !== "" ? { name: entry.name } : {},
|
|
34207
34633
|
...typeof entry.contextWindow === "number" && Number.isFinite(entry.contextWindow) ? { contextWindow: entry.contextWindow } : {},
|
|
34208
|
-
...typeof entry.maxTokens === "number" && Number.isFinite(entry.maxTokens) ? { maxTokens: entry.maxTokens } : {}
|
|
34634
|
+
...typeof entry.maxTokens === "number" && Number.isFinite(entry.maxTokens) ? { maxTokens: entry.maxTokens } : {},
|
|
34635
|
+
...Object.keys(extras).length > 0 ? { extras } : {}
|
|
34209
34636
|
}];
|
|
34210
34637
|
});
|
|
34211
34638
|
return {
|
|
@@ -34421,6 +34848,7 @@ async function saveProviderConfiguration(ctx, target, configuration) {
|
|
|
34421
34848
|
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`);
|
|
34422
34849
|
return {
|
|
34423
34850
|
id,
|
|
34851
|
+
...model.extras,
|
|
34424
34852
|
...model.name === void 0 || model.name.trim() === "" ? {} : { name: model.name.trim() },
|
|
34425
34853
|
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
34426
34854
|
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
@@ -34504,83 +34932,6 @@ async function removeProviderSettings(ctx, target) {
|
|
|
34504
34932
|
}
|
|
34505
34933
|
}
|
|
34506
34934
|
//#endregion
|
|
34507
|
-
//#region src/mentions.ts
|
|
34508
|
-
/** Menu cap on file rows; the service owns ranking and default rows. */
|
|
34509
|
-
const MAX_FILE_ROWS = 20;
|
|
34510
|
-
/**
|
|
34511
|
-
* Create the mention API for one agent's workspace. A missing
|
|
34512
|
-
* `fileReferences` service (with an agent present) or `sessionReferenceResolver`
|
|
34513
|
-
* degrades that half to empty rows; `prepare` passes text through untouched
|
|
34514
|
-
* without references. An undefined agent (a bare launch before any session
|
|
34515
|
-
* exists) runs the official WorkspaceFileSearch over the launch cwd — the
|
|
34516
|
-
* same class the mounted service uses per agent — so `@` file completion
|
|
34517
|
-
* works from the first keystroke; session references wait for the session.
|
|
34518
|
-
*
|
|
34519
|
-
* `candidates` never reaches for `this` — the runner hands it to the input
|
|
34520
|
-
* editor as a detached callback, and a `this`-bound method would throw on
|
|
34521
|
-
* every `@` key.
|
|
34522
|
-
* @param ctx - context carrying the optional `fileReferences` and
|
|
34523
|
-
* `sessionReferenceResolver` services.
|
|
34524
|
-
* @param agent - the session owner; excluded from its own session candidates.
|
|
34525
|
-
* @param cwd - launch working directory; bounds the pre-session search.
|
|
34526
|
-
*/
|
|
34527
|
-
function createMentions(ctx, agent, cwd) {
|
|
34528
|
-
const resolver = ctx.get("sessionReferenceResolver");
|
|
34529
|
-
const fileReferences = ctx.get("fileReferences");
|
|
34530
|
-
const sessionCapable = agent !== void 0 && resolver !== void 0;
|
|
34531
|
-
let preSessionSearch;
|
|
34532
|
-
const preSessionFiles = (query, signal) => {
|
|
34533
|
-
preSessionSearch ??= new WorkspaceFileSearch(cwd, {
|
|
34534
|
-
maxResults: DEFAULT_FILE_SEARCH_MAX_RESULTS,
|
|
34535
|
-
maxEntries: DEFAULT_FILE_SEARCH_MAX_ENTRIES,
|
|
34536
|
-
excludedDirectories: [...DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES]
|
|
34537
|
-
});
|
|
34538
|
-
return preSessionSearch.list(query, signal ?? new AbortController().signal);
|
|
34539
|
-
};
|
|
34540
|
-
return {
|
|
34541
|
-
async candidates(query, signal) {
|
|
34542
|
-
const needle = query.trim();
|
|
34543
|
-
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([])]);
|
|
34544
|
-
const fileRows = files.slice(0, MAX_FILE_ROWS).map((candidate) => ({
|
|
34545
|
-
label: candidate.path,
|
|
34546
|
-
description: candidate.kind === "directory" ? "Folder" : "File",
|
|
34547
|
-
kind: candidate.kind,
|
|
34548
|
-
...candidate.kind === "file" ? { path: isAbsolute(candidate.path) ? candidate.path : resolve(cwd, candidate.path) } : {}
|
|
34549
|
-
}));
|
|
34550
|
-
const sessionRows = sessions.map((candidate) => ({
|
|
34551
|
-
label: formatSessionReferenceMention(candidate),
|
|
34552
|
-
description: `Session · ${candidate.cwd ?? "(no cwd)"}`,
|
|
34553
|
-
kind: "session"
|
|
34554
|
-
}));
|
|
34555
|
-
return [...fileRows, ...sessionRows];
|
|
34556
|
-
},
|
|
34557
|
-
parse(text) {
|
|
34558
|
-
return parseSessionReferenceText(text);
|
|
34559
|
-
},
|
|
34560
|
-
async prepare(parsed, signal) {
|
|
34561
|
-
if (parsed.references.length === 0 || resolver === void 0 || agent === void 0) return {
|
|
34562
|
-
text: parsed.text,
|
|
34563
|
-
references: parsed.references
|
|
34564
|
-
};
|
|
34565
|
-
const prepared = await resolver.prepare(agent, [{
|
|
34566
|
-
type: "text",
|
|
34567
|
-
text: parsed.text
|
|
34568
|
-
}], parsed.references, signal);
|
|
34569
|
-
return {
|
|
34570
|
-
text: prepared.content.filter((block) => block.type === "text").map((block) => block.text).join(""),
|
|
34571
|
-
references: parsed.references,
|
|
34572
|
-
additionalContext: prepared.additionalContext
|
|
34573
|
-
};
|
|
34574
|
-
},
|
|
34575
|
-
sessionMention(candidate) {
|
|
34576
|
-
return formatSessionReferenceMention({
|
|
34577
|
-
sessionId: candidate.sessionId,
|
|
34578
|
-
label: candidate.label
|
|
34579
|
-
});
|
|
34580
|
-
}
|
|
34581
|
-
};
|
|
34582
|
-
}
|
|
34583
|
-
//#endregion
|
|
34584
34935
|
//#region src/questions.ts
|
|
34585
34936
|
const ABORT_ERROR = new UserQuestionError("ask_user_question was interrupted before the user answered", "ASK_ABORTED");
|
|
34586
34937
|
/**
|
|
@@ -34681,7 +35032,10 @@ const NOTIFY_FRAME_MS$1 = 16;
|
|
|
34681
35032
|
* @returns the store the runner feeds and the renderer subscribes to.
|
|
34682
35033
|
*/
|
|
34683
35034
|
function createTranscriptStore(replay) {
|
|
34684
|
-
let
|
|
35035
|
+
let acc = createReplayAccumulator();
|
|
35036
|
+
for (const event of replay ?? []) replayProjectEvent(acc, event);
|
|
35037
|
+
let view = snapshotReplayView(acc);
|
|
35038
|
+
let dirty = false;
|
|
34685
35039
|
const listeners = /* @__PURE__ */ new Set();
|
|
34686
35040
|
let scheduled = false;
|
|
34687
35041
|
let lastNotifyAt = 0;
|
|
@@ -34698,7 +35052,13 @@ function createTranscriptStore(replay) {
|
|
|
34698
35052
|
else setTimeout(dispatch, wait);
|
|
34699
35053
|
};
|
|
34700
35054
|
return {
|
|
34701
|
-
getView: () =>
|
|
35055
|
+
getView: () => {
|
|
35056
|
+
if (dirty) {
|
|
35057
|
+
view = snapshotReplayView(acc);
|
|
35058
|
+
dirty = false;
|
|
35059
|
+
}
|
|
35060
|
+
return view;
|
|
35061
|
+
},
|
|
34702
35062
|
subscribe(listener) {
|
|
34703
35063
|
listeners.add(listener);
|
|
34704
35064
|
return () => {
|
|
@@ -34706,13 +35066,13 @@ function createTranscriptStore(replay) {
|
|
|
34706
35066
|
};
|
|
34707
35067
|
},
|
|
34708
35068
|
apply(event) {
|
|
34709
|
-
|
|
34710
|
-
|
|
34711
|
-
view = next;
|
|
35069
|
+
if (!replayProjectEvent(acc, event)) return;
|
|
35070
|
+
dirty = true;
|
|
34712
35071
|
notify();
|
|
34713
35072
|
},
|
|
34714
35073
|
reset() {
|
|
34715
|
-
|
|
35074
|
+
acc = createReplayAccumulator();
|
|
35075
|
+
dirty = true;
|
|
34716
35076
|
notify();
|
|
34717
35077
|
}
|
|
34718
35078
|
};
|
|
@@ -34830,6 +35190,7 @@ function foldSubagentRow(previous, sessionId, event) {
|
|
|
34830
35190
|
*/
|
|
34831
35191
|
function createSubagentFeed() {
|
|
34832
35192
|
let rows = Object.freeze([]);
|
|
35193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34833
35194
|
const listeners = /* @__PURE__ */ new Set();
|
|
34834
35195
|
let scheduled = false;
|
|
34835
35196
|
let lastNotifyAt = 0;
|
|
@@ -34850,12 +35211,30 @@ function createSubagentFeed() {
|
|
|
34850
35211
|
const index = rows.findIndex((row) => row.id === sessionId);
|
|
34851
35212
|
const previous = index === -1 ? void 0 : rows[index];
|
|
34852
35213
|
const next = foldSubagentRow(previous, sessionId, event);
|
|
34853
|
-
if (
|
|
34854
|
-
|
|
34855
|
-
|
|
35214
|
+
if (index !== -1) {
|
|
35215
|
+
if (next === previous) return;
|
|
35216
|
+
rows = Object.freeze(rows.map((row, at) => at === index ? next : row));
|
|
35217
|
+
notify();
|
|
35218
|
+
return;
|
|
35219
|
+
}
|
|
35220
|
+
const counted = !seen.has(sessionId);
|
|
35221
|
+
if (counted) seen.add(sessionId);
|
|
35222
|
+
if (rows.length >= 8) {
|
|
35223
|
+
const evict = rows.findIndex((row) => row.state === "done");
|
|
35224
|
+
if (evict === -1) {
|
|
35225
|
+
if (counted) notify();
|
|
35226
|
+
return;
|
|
35227
|
+
}
|
|
35228
|
+
rows = Object.freeze([
|
|
35229
|
+
...rows.slice(0, evict),
|
|
35230
|
+
next,
|
|
35231
|
+
...rows.slice(evict + 1)
|
|
35232
|
+
]);
|
|
35233
|
+
} else rows = Object.freeze([...rows, next]);
|
|
34856
35234
|
notify();
|
|
34857
35235
|
},
|
|
34858
35236
|
reset() {
|
|
35237
|
+
seen.clear();
|
|
34859
35238
|
if (rows.length === 0) return;
|
|
34860
35239
|
rows = Object.freeze([]);
|
|
34861
35240
|
notify();
|
|
@@ -34868,6 +35247,9 @@ function createSubagentFeed() {
|
|
|
34868
35247
|
},
|
|
34869
35248
|
getSnapshot() {
|
|
34870
35249
|
return rows;
|
|
35250
|
+
},
|
|
35251
|
+
getTotalSeen() {
|
|
35252
|
+
return seen.size;
|
|
34871
35253
|
}
|
|
34872
35254
|
};
|
|
34873
35255
|
}
|
|
@@ -35057,6 +35439,312 @@ function latestAssistantText(view) {
|
|
|
35057
35439
|
}
|
|
35058
35440
|
}
|
|
35059
35441
|
//#endregion
|
|
35442
|
+
//#region src/editor-keys.ts
|
|
35443
|
+
/**
|
|
35444
|
+
* VS Code-family terminal keybinding repair. VS Code hands Ctrl+R to the
|
|
35445
|
+
* workbench (Open Recent) even while an integrated terminal owns focus, so
|
|
35446
|
+
* the reasoning-fold key never reaches the TUI. Workspace-scoped keybindings
|
|
35447
|
+
* do not exist, so the fix is one user-level keybindings.json rule that
|
|
35448
|
+
* forwards the raw Ctrl byte via sendSequence under terminalFocus. This
|
|
35449
|
+
* module detects the hosting editor variant, resolves its user
|
|
35450
|
+
* keybindings.json, and merges the rule idempotently; pure merge/detect
|
|
35451
|
+
* helpers are separated from the fs orchestration so both stay testable.
|
|
35452
|
+
* @module @deepseek-ai/dsh-code/editor-keys
|
|
35453
|
+
*/
|
|
35454
|
+
/** Install directory names per family ('vscode' covers stable and Insiders). */
|
|
35455
|
+
const FAMILY_DIRS = {
|
|
35456
|
+
vscode: ["Code", "Code - Insiders"],
|
|
35457
|
+
cursor: ["Cursor"],
|
|
35458
|
+
vscodium: ["VSCodium"],
|
|
35459
|
+
windsurf: ["Windsurf"]
|
|
35460
|
+
};
|
|
35461
|
+
/**
|
|
35462
|
+
* Detect the editor hosting this integrated terminal.
|
|
35463
|
+
* @param env - process environment (TERM_PROGRAM decides; case/whitespace tolerant).
|
|
35464
|
+
* @returns the family, or undefined outside VS Code-family terminals.
|
|
35465
|
+
*/
|
|
35466
|
+
function detectEditorTerminalFamily(env = process.env) {
|
|
35467
|
+
const program = env.TERM_PROGRAM?.trim().toLowerCase();
|
|
35468
|
+
if (program === "vscode" || program === "cursor" || program === "vscodium" || program === "windsurf") return program;
|
|
35469
|
+
}
|
|
35470
|
+
/**
|
|
35471
|
+
* Whether the pty is hosted away from the editor UI (ssh/container/tunnel).
|
|
35472
|
+
* Keybindings live on the client machine, so a remote session must never
|
|
35473
|
+
* write them server-side.
|
|
35474
|
+
*/
|
|
35475
|
+
function isRemoteTerminalEnv(env = process.env) {
|
|
35476
|
+
return (env.VSCODE_IPC_HOOK_CLI ?? "").trim() !== "";
|
|
35477
|
+
}
|
|
35478
|
+
/**
|
|
35479
|
+
* Resolve the user keybindings.json candidates for one family, most likely
|
|
35480
|
+
* install first. Only paths that exist on disk are repaired.
|
|
35481
|
+
*/
|
|
35482
|
+
function editorKeybindingCandidates(family, context) {
|
|
35483
|
+
return FAMILY_DIRS[family].map((dir) => {
|
|
35484
|
+
if (context.platform === "win32") return join(context.appdata ?? join(context.homedir, "AppData", "Roaming"), dir, "User", "keybindings.json");
|
|
35485
|
+
if (context.platform === "darwin") return join(context.homedir, "Library", "Application Support", dir, "User", "keybindings.json");
|
|
35486
|
+
return join(context.homedir, ".config", dir, "User", "keybindings.json");
|
|
35487
|
+
});
|
|
35488
|
+
}
|
|
35489
|
+
/** The one workbench rule that hands Ctrl+R to the focused terminal. */
|
|
35490
|
+
const CTRL_R_PASSTHROUGH_RULE = {
|
|
35491
|
+
key: "ctrl+r",
|
|
35492
|
+
command: "workbench.action.terminal.sendSequence",
|
|
35493
|
+
args: { text: "" },
|
|
35494
|
+
when: "terminalFocus"
|
|
35495
|
+
};
|
|
35496
|
+
/** Serialized rule block (4-space indent, the editors' default style). */
|
|
35497
|
+
const RULE_BLOCK = [
|
|
35498
|
+
" {",
|
|
35499
|
+
" \"key\": \"ctrl+r\",",
|
|
35500
|
+
" \"command\": \"workbench.action.terminal.sendSequence\",",
|
|
35501
|
+
" \"args\": { \"text\": \"\\u0012\" },",
|
|
35502
|
+
" \"when\": \"terminalFocus\"",
|
|
35503
|
+
" }"
|
|
35504
|
+
].join("\n");
|
|
35505
|
+
/** Fresh-file template carrying the editors' standard header comment. */
|
|
35506
|
+
const KEYBINDINGS_TEMPLATE = "// Place your key bindings in this file to override the defaults\n[\n" + RULE_BLOCK + "\n]\n";
|
|
35507
|
+
/** Whether one parsed keybindings entry already forwards Ctrl+R to the terminal. */
|
|
35508
|
+
function isCtrlRPassthroughEntry(entry) {
|
|
35509
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
35510
|
+
const record = entry;
|
|
35511
|
+
if (record.command !== CTRL_R_PASSTHROUGH_RULE.command) return false;
|
|
35512
|
+
if (typeof record.key !== "string" || record.key.trim().toLowerCase() !== CTRL_R_PASSTHROUGH_RULE.key) return false;
|
|
35513
|
+
const args = record.args;
|
|
35514
|
+
if (typeof args !== "object" || args === null) return false;
|
|
35515
|
+
const text = args.text;
|
|
35516
|
+
if (typeof text !== "string" || !text.includes("")) return false;
|
|
35517
|
+
return typeof record.when === "string" && /\bterminalFocus\b/u.test(record.when);
|
|
35518
|
+
}
|
|
35519
|
+
/**
|
|
35520
|
+
* Remove // and block comments from one JSONC document. Double-quoted strings
|
|
35521
|
+
* survive untouched, so comment markers inside string values are preserved.
|
|
35522
|
+
*/
|
|
35523
|
+
function stripJsoncComments(text) {
|
|
35524
|
+
let out = "";
|
|
35525
|
+
let index = 0;
|
|
35526
|
+
let inString = false;
|
|
35527
|
+
while (index < text.length) {
|
|
35528
|
+
const char = text[index];
|
|
35529
|
+
if (inString) {
|
|
35530
|
+
out += char;
|
|
35531
|
+
if (char === "\\" && index + 1 < text.length) {
|
|
35532
|
+
out += text[index + 1];
|
|
35533
|
+
index += 2;
|
|
35534
|
+
continue;
|
|
35535
|
+
}
|
|
35536
|
+
if (char === "\"") inString = false;
|
|
35537
|
+
index += 1;
|
|
35538
|
+
continue;
|
|
35539
|
+
}
|
|
35540
|
+
if (char === "\"") {
|
|
35541
|
+
inString = true;
|
|
35542
|
+
out += char;
|
|
35543
|
+
index += 1;
|
|
35544
|
+
continue;
|
|
35545
|
+
}
|
|
35546
|
+
if (char === "/" && text[index + 1] === "/") {
|
|
35547
|
+
while (index < text.length && text[index] !== "\n") index += 1;
|
|
35548
|
+
continue;
|
|
35549
|
+
}
|
|
35550
|
+
if (char === "/" && text[index + 1] === "*") {
|
|
35551
|
+
index += 2;
|
|
35552
|
+
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) {
|
|
35553
|
+
if (text[index] === "\n") out += "\n";
|
|
35554
|
+
index += 1;
|
|
35555
|
+
}
|
|
35556
|
+
index += 2;
|
|
35557
|
+
continue;
|
|
35558
|
+
}
|
|
35559
|
+
out += char;
|
|
35560
|
+
index += 1;
|
|
35561
|
+
}
|
|
35562
|
+
return out;
|
|
35563
|
+
}
|
|
35564
|
+
/** Drop commas directly before a closing bracket (string-aware). */
|
|
35565
|
+
function removeTrailingCommas(text) {
|
|
35566
|
+
let out = "";
|
|
35567
|
+
let index = 0;
|
|
35568
|
+
let inString = false;
|
|
35569
|
+
while (index < text.length) {
|
|
35570
|
+
const char = text[index];
|
|
35571
|
+
if (inString) {
|
|
35572
|
+
out += char;
|
|
35573
|
+
if (char === "\\" && index + 1 < text.length) {
|
|
35574
|
+
out += text[index + 1];
|
|
35575
|
+
index += 2;
|
|
35576
|
+
continue;
|
|
35577
|
+
}
|
|
35578
|
+
if (char === "\"") inString = false;
|
|
35579
|
+
index += 1;
|
|
35580
|
+
continue;
|
|
35581
|
+
}
|
|
35582
|
+
if (char === "\"") {
|
|
35583
|
+
inString = true;
|
|
35584
|
+
out += char;
|
|
35585
|
+
index += 1;
|
|
35586
|
+
continue;
|
|
35587
|
+
}
|
|
35588
|
+
if (char === ",") {
|
|
35589
|
+
let peek = index + 1;
|
|
35590
|
+
while (peek < text.length && (text[peek] === " " || text[peek] === " " || text[peek] === "\n" || text[peek] === "\r")) peek += 1;
|
|
35591
|
+
const next = text[peek];
|
|
35592
|
+
if (next === "}" || next === "]") {
|
|
35593
|
+
index += 1;
|
|
35594
|
+
continue;
|
|
35595
|
+
}
|
|
35596
|
+
}
|
|
35597
|
+
out += char;
|
|
35598
|
+
index += 1;
|
|
35599
|
+
}
|
|
35600
|
+
return out;
|
|
35601
|
+
}
|
|
35602
|
+
/** Parse one JSONC document; trailing commas are tolerated. */
|
|
35603
|
+
function parseJsonc(text) {
|
|
35604
|
+
return JSON.parse(removeTrailingCommas(stripJsoncComments(text)));
|
|
35605
|
+
}
|
|
35606
|
+
/** Raw index of the top-level rule array's opening bracket, or -1 when absent. */
|
|
35607
|
+
function rawOpenBracketIndex(text) {
|
|
35608
|
+
let index = 0;
|
|
35609
|
+
let inString = false;
|
|
35610
|
+
while (index < text.length) {
|
|
35611
|
+
const char = text[index];
|
|
35612
|
+
if (inString) {
|
|
35613
|
+
if (char === "\\") {
|
|
35614
|
+
index += 2;
|
|
35615
|
+
continue;
|
|
35616
|
+
}
|
|
35617
|
+
if (char === "\"") inString = false;
|
|
35618
|
+
index += 1;
|
|
35619
|
+
continue;
|
|
35620
|
+
}
|
|
35621
|
+
if (char === "\"") {
|
|
35622
|
+
inString = true;
|
|
35623
|
+
index += 1;
|
|
35624
|
+
continue;
|
|
35625
|
+
}
|
|
35626
|
+
if (char === "/" && text[index + 1] === "/") {
|
|
35627
|
+
while (index < text.length && text[index] !== "\n") index += 1;
|
|
35628
|
+
continue;
|
|
35629
|
+
}
|
|
35630
|
+
if (char === "/" && text[index + 1] === "*") {
|
|
35631
|
+
index += 2;
|
|
35632
|
+
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) index += 1;
|
|
35633
|
+
index += 2;
|
|
35634
|
+
continue;
|
|
35635
|
+
}
|
|
35636
|
+
if (char === "[") return index;
|
|
35637
|
+
index += 1;
|
|
35638
|
+
}
|
|
35639
|
+
return -1;
|
|
35640
|
+
}
|
|
35641
|
+
/**
|
|
35642
|
+
* Merge the Ctrl+R passthrough into one keybindings.json document. The raw
|
|
35643
|
+
* text is preserved verbatim (comments included); the rule is inserted right
|
|
35644
|
+
* after the array opener so it cannot be shadowed by later conflicting user
|
|
35645
|
+
* rules. Missing files resolve to a fresh template.
|
|
35646
|
+
* @throws when the document does not carry a rule array.
|
|
35647
|
+
*/
|
|
35648
|
+
function mergeCtrlRPassthrough(raw) {
|
|
35649
|
+
if (raw === void 0) return {
|
|
35650
|
+
status: "created",
|
|
35651
|
+
text: KEYBINDINGS_TEMPLATE
|
|
35652
|
+
};
|
|
35653
|
+
const parsed = parseJsonc(raw);
|
|
35654
|
+
if (!Array.isArray(parsed)) throw new Error("keybindings.json does not contain a rule array");
|
|
35655
|
+
if (parsed.some(isCtrlRPassthroughEntry)) return { status: "present" };
|
|
35656
|
+
const open = rawOpenBracketIndex(raw);
|
|
35657
|
+
if (open === -1) throw new Error("keybindings.json does not contain a rule array");
|
|
35658
|
+
const insert = parsed.length > 0 ? "\n" + RULE_BLOCK + "," : "\n" + RULE_BLOCK;
|
|
35659
|
+
return {
|
|
35660
|
+
status: "updated",
|
|
35661
|
+
text: raw.slice(0, open + 1) + insert + raw.slice(open + 1)
|
|
35662
|
+
};
|
|
35663
|
+
}
|
|
35664
|
+
/** Parse one flag file snapshot; missing or corrupt content degrades to unshown. */
|
|
35665
|
+
function parseEditorKeysFlag(raw) {
|
|
35666
|
+
if (raw === void 0) return {};
|
|
35667
|
+
try {
|
|
35668
|
+
const parsed = JSON.parse(raw);
|
|
35669
|
+
if (typeof parsed !== "object" || parsed === null) return {};
|
|
35670
|
+
const shown = parsed.hintShownAt;
|
|
35671
|
+
return typeof shown === "string" ? { hintShownAt: shown } : {};
|
|
35672
|
+
} catch {
|
|
35673
|
+
return {};
|
|
35674
|
+
}
|
|
35675
|
+
}
|
|
35676
|
+
/** Persist the shown marker; best-effort, the hint is cosmetic and never a gate. */
|
|
35677
|
+
async function markEditorKeysHintShown(path) {
|
|
35678
|
+
await mkdir(dirname(path), { recursive: true });
|
|
35679
|
+
await writeFile(path, JSON.stringify({ hintShownAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n", "utf8");
|
|
35680
|
+
}
|
|
35681
|
+
/** Read one file if it exists; undefined otherwise (ENOENT and unreadable both). */
|
|
35682
|
+
async function readIfPresent(path) {
|
|
35683
|
+
try {
|
|
35684
|
+
return await readFile(path, "utf8");
|
|
35685
|
+
} catch {
|
|
35686
|
+
return;
|
|
35687
|
+
}
|
|
35688
|
+
}
|
|
35689
|
+
/**
|
|
35690
|
+
* Apply the Ctrl+R passthrough to every local keybindings.json of the hosting
|
|
35691
|
+
* editor and mark the startup hint shown. Existing files get a .dsh-bak
|
|
35692
|
+
* backup before the first write.
|
|
35693
|
+
* @returns a one-line user-facing summary.
|
|
35694
|
+
* @throws with an actionable message when the environment cannot be repaired.
|
|
35695
|
+
*/
|
|
35696
|
+
async function applyCtrlRPassthrough({ env, paths, flagPath }) {
|
|
35697
|
+
const family = detectEditorTerminalFamily(env);
|
|
35698
|
+
if (family === void 0) throw new Error("not a VS Code-family terminal (TERM_PROGRAM=" + (env.TERM_PROGRAM?.trim() || "unset") + "); add the ctrl+r rule to keybindings.json manually");
|
|
35699
|
+
if (isRemoteTerminalEnv(env)) throw new Error("remote terminal detected; apply the keybindings rule on the local machine instead");
|
|
35700
|
+
const candidates = editorKeybindingCandidates(family, paths);
|
|
35701
|
+
const targets = [];
|
|
35702
|
+
for (const candidate of candidates) if (await readIfPresent(candidate) !== void 0) targets.push(candidate);
|
|
35703
|
+
if (targets.length === 0) targets.push(candidates[0]);
|
|
35704
|
+
const updated = [];
|
|
35705
|
+
const present = [];
|
|
35706
|
+
for (const target of targets) {
|
|
35707
|
+
const raw = await readIfPresent(target);
|
|
35708
|
+
const merge = mergeCtrlRPassthrough(raw);
|
|
35709
|
+
if (merge.status === "present") {
|
|
35710
|
+
present.push(target);
|
|
35711
|
+
continue;
|
|
35712
|
+
}
|
|
35713
|
+
await mkdir(dirname(target), { recursive: true });
|
|
35714
|
+
if (raw !== void 0) await writeFile(target + ".dsh-bak", raw, "utf8");
|
|
35715
|
+
await writeFile(target, merge.text, "utf8");
|
|
35716
|
+
updated.push(target);
|
|
35717
|
+
}
|
|
35718
|
+
await markEditorKeysHintShown(flagPath).catch(() => {});
|
|
35719
|
+
const label = (target) => basename(dirname(dirname(target)));
|
|
35720
|
+
if (updated.length === 0) return "ctrl+r passthrough already configured in " + present.map(label).join(", ");
|
|
35721
|
+
return "ctrl+r passthrough written to " + updated.map(label).join(", ") + " — effective immediately";
|
|
35722
|
+
}
|
|
35723
|
+
/**
|
|
35724
|
+
* Resolve the one-shot startup hint for VS Code-family terminals. Fires at
|
|
35725
|
+
* most once per install (flag file), never when the passthrough rule is
|
|
35726
|
+
* already present, and never in remote ptys where the repair cannot run.
|
|
35727
|
+
* @returns the hint line, or undefined to stay silent.
|
|
35728
|
+
*/
|
|
35729
|
+
async function resolveEditorKeysStartupHint({ env, paths, flagPath }) {
|
|
35730
|
+
if (parseEditorKeysFlag(await readIfPresent(flagPath)).hintShownAt !== void 0) return void 0;
|
|
35731
|
+
const family = detectEditorTerminalFamily(env);
|
|
35732
|
+
if (family === void 0 || isRemoteTerminalEnv(env)) return void 0;
|
|
35733
|
+
for (const candidate of editorKeybindingCandidates(family, paths)) {
|
|
35734
|
+
const raw = await readIfPresent(candidate);
|
|
35735
|
+
if (raw === void 0) continue;
|
|
35736
|
+
try {
|
|
35737
|
+
const parsed = parseJsonc(raw);
|
|
35738
|
+
if (Array.isArray(parsed) && parsed.some(isCtrlRPassthroughEntry)) {
|
|
35739
|
+
await markEditorKeysHintShown(flagPath).catch(() => {});
|
|
35740
|
+
return;
|
|
35741
|
+
}
|
|
35742
|
+
} catch {}
|
|
35743
|
+
}
|
|
35744
|
+
await markEditorKeysHintShown(flagPath).catch(() => {});
|
|
35745
|
+
return "run /vscode-keys to pass ctrl+r through this editor (alt+r works meanwhile)";
|
|
35746
|
+
}
|
|
35747
|
+
//#endregion
|
|
35060
35748
|
//#region src/fork.ts
|
|
35061
35749
|
/** Select a completed turn and trailing between-turn metadata. */
|
|
35062
35750
|
function selectForkSeed(events, atSeq) {
|
|
@@ -35123,13 +35811,14 @@ function parseGitDiffSpec(argument) {
|
|
|
35123
35811
|
]
|
|
35124
35812
|
};
|
|
35125
35813
|
}
|
|
35126
|
-
function executeGit(cwd, args) {
|
|
35814
|
+
function executeGit(cwd, args, signal) {
|
|
35127
35815
|
return new Promise((resolve, reject) => {
|
|
35128
35816
|
execFile("git", [...args], {
|
|
35129
35817
|
cwd,
|
|
35130
35818
|
encoding: "utf8",
|
|
35131
35819
|
maxBuffer: 16777216,
|
|
35132
|
-
windowsHide: true
|
|
35820
|
+
windowsHide: true,
|
|
35821
|
+
signal
|
|
35133
35822
|
}, (error, stdout, stderr) => {
|
|
35134
35823
|
if (error !== null) {
|
|
35135
35824
|
reject(new Error(stderr.trim() || error.message));
|
|
@@ -35139,11 +35828,15 @@ function executeGit(cwd, args) {
|
|
|
35139
35828
|
});
|
|
35140
35829
|
});
|
|
35141
35830
|
}
|
|
35142
|
-
/**
|
|
35143
|
-
|
|
35831
|
+
/**
|
|
35832
|
+
* Load one complete textual diff without invoking external diff drivers.
|
|
35833
|
+
* @param signal - aborted by the caller on session switches/quit, killing the
|
|
35834
|
+
* git subprocess instead of letting a stale repository's diff land later.
|
|
35835
|
+
*/
|
|
35836
|
+
async function loadGitDiff(cwd, argument, signal) {
|
|
35144
35837
|
const spec = parseGitDiffSpec(argument);
|
|
35145
35838
|
try {
|
|
35146
|
-
const text = await executeGit(cwd, spec.args);
|
|
35839
|
+
const text = await executeGit(cwd, spec.args, signal);
|
|
35147
35840
|
return {
|
|
35148
35841
|
title: `git diff - ${spec.label}`,
|
|
35149
35842
|
files: parseGitDiffFiles(text)
|
|
@@ -35157,7 +35850,7 @@ async function loadGitDiff(cwd, argument) {
|
|
|
35157
35850
|
"--no-ext-diff",
|
|
35158
35851
|
"--unified=3",
|
|
35159
35852
|
"--"
|
|
35160
|
-
]))
|
|
35853
|
+
], signal))
|
|
35161
35854
|
};
|
|
35162
35855
|
}
|
|
35163
35856
|
}
|
|
@@ -35325,6 +36018,48 @@ function listPluginRows(ctx) {
|
|
|
35325
36018
|
return rows;
|
|
35326
36019
|
}
|
|
35327
36020
|
//#endregion
|
|
36021
|
+
//#region src/settings-file.ts
|
|
36022
|
+
/**
|
|
36023
|
+
* Serialized, crash-atomic persistence for the small user-level JSON files
|
|
36024
|
+
* under the DSH home (statusline.json, theme.json). Two guarantees the bare
|
|
36025
|
+
* floating `writeFile` path could not give:
|
|
36026
|
+
*
|
|
36027
|
+
* 1. Every save is appended to ONE chain, so rapid consecutive edits land
|
|
36028
|
+
* in submission order and the last snapshot is the one on disk (parallel
|
|
36029
|
+
* floating writes let an older snapshot finish last and win).
|
|
36030
|
+
* 2. Each write goes to a sibling temp file first and is renamed into
|
|
36031
|
+
* place, so a crash mid-write can never leave a half-written JSON
|
|
36032
|
+
* document behind.
|
|
36033
|
+
*
|
|
36034
|
+
* The chain itself never rejects: a failed write is reported to that
|
|
36035
|
+
* save's caller while later saves keep their turn.
|
|
36036
|
+
*
|
|
36037
|
+
* @module @deepseek-ai/dsh-code/settings-file
|
|
36038
|
+
*/
|
|
36039
|
+
/**
|
|
36040
|
+
* Create the shared settings-write chain. One instance per process keeps
|
|
36041
|
+
* every user-level JSON file mutually serialized.
|
|
36042
|
+
* @returns the persistence handle.
|
|
36043
|
+
*/
|
|
36044
|
+
function createUserSettingsPersistence() {
|
|
36045
|
+
let chain = Promise.resolve();
|
|
36046
|
+
return {
|
|
36047
|
+
save(path, text) {
|
|
36048
|
+
const write = chain.then(async () => {
|
|
36049
|
+
await mkdir(dirname(path), { recursive: true });
|
|
36050
|
+
const temp = `${path}.tmp`;
|
|
36051
|
+
await writeFile(temp, text, "utf8");
|
|
36052
|
+
await rename(temp, path);
|
|
36053
|
+
});
|
|
36054
|
+
chain = write.catch(() => {});
|
|
36055
|
+
return write;
|
|
36056
|
+
},
|
|
36057
|
+
flush() {
|
|
36058
|
+
return chain;
|
|
36059
|
+
}
|
|
36060
|
+
};
|
|
36061
|
+
}
|
|
36062
|
+
//#endregion
|
|
35328
36063
|
//#region src/index.ts
|
|
35329
36064
|
/**
|
|
35330
36065
|
* @deepseek-ai/dsh-code — the interactive terminal driver. The bundle patch
|
|
@@ -35648,6 +36383,20 @@ async function run(ctx, startup, io) {
|
|
|
35648
36383
|
});
|
|
35649
36384
|
const questions = mountQuestionProvider(ctx);
|
|
35650
36385
|
const bridge = { notify: () => {} };
|
|
36386
|
+
const capabilitySyncDebounceMs = 400;
|
|
36387
|
+
const runCapabilitySync = () => {
|
|
36388
|
+
syncModelCapabilities(ctx, bridge.notify);
|
|
36389
|
+
};
|
|
36390
|
+
let capabilitySyncTimer;
|
|
36391
|
+
const scheduleCapabilitySync = () => {
|
|
36392
|
+
if (capabilitySyncTimer !== void 0) clearTimeout(capabilitySyncTimer);
|
|
36393
|
+
capabilitySyncTimer = setTimeout(() => {
|
|
36394
|
+
capabilitySyncTimer = void 0;
|
|
36395
|
+
runCapabilitySync();
|
|
36396
|
+
}, capabilitySyncDebounceMs);
|
|
36397
|
+
};
|
|
36398
|
+
const offCapabilitySync = [ctx.on("settings/document-updated", scheduleCapabilitySync), ctx.on("llm/adapters-updated", scheduleCapabilitySync)];
|
|
36399
|
+
scheduleCapabilitySync();
|
|
35651
36400
|
const statuslinePath = join(homedir(), ".dsh", "dsh-code", "statusline.json");
|
|
35652
36401
|
let statuslineWarning;
|
|
35653
36402
|
let statuslineItems = [];
|
|
@@ -35657,12 +36406,23 @@ async function run(ctx, startup, io) {
|
|
|
35657
36406
|
statuslineItems = parseStatuslineItems(void 0);
|
|
35658
36407
|
if (error.code !== "ENOENT") statuslineWarning = error instanceof Error ? error.message : String(error);
|
|
35659
36408
|
}
|
|
36409
|
+
const settingsPersistence = createUserSettingsPersistence();
|
|
35660
36410
|
const saveStatusline = (items) => {
|
|
35661
36411
|
statuslineItems = [...items];
|
|
35662
|
-
|
|
36412
|
+
settingsPersistence.save(statuslinePath, JSON.stringify({ items }, null, 2) + "\n").catch((writeError) => {
|
|
35663
36413
|
bridge.notify("statusline save failed: " + (writeError instanceof Error ? writeError.message : String(writeError)), "error");
|
|
35664
36414
|
});
|
|
35665
36415
|
};
|
|
36416
|
+
const editorKeysEnv = {
|
|
36417
|
+
env: process.env,
|
|
36418
|
+
paths: {
|
|
36419
|
+
homedir: homedir(),
|
|
36420
|
+
appdata: process.env.APPDATA,
|
|
36421
|
+
platform: process.platform
|
|
36422
|
+
},
|
|
36423
|
+
flagPath: join(homedir(), ".dsh", "dsh-code", "editor-keys.json")
|
|
36424
|
+
};
|
|
36425
|
+
const applyEditorKeys = () => applyCtrlRPassthrough(editorKeysEnv);
|
|
35666
36426
|
const themePath = join(homedir(), ".dsh", "dsh-code", "theme.json");
|
|
35667
36427
|
let themeWarning;
|
|
35668
36428
|
if (startup.theme === void 0) try {
|
|
@@ -35673,7 +36433,7 @@ async function run(ctx, startup, io) {
|
|
|
35673
36433
|
else setTheme(startup.theme);
|
|
35674
36434
|
const saveTheme = (name) => {
|
|
35675
36435
|
setTheme(name);
|
|
35676
|
-
|
|
36436
|
+
settingsPersistence.save(themePath, JSON.stringify({ theme: name }, null, 2) + "\n").catch((writeError) => {
|
|
35677
36437
|
bridge.notify("theme save failed: " + (writeError instanceof Error ? writeError.message : String(writeError)), "error");
|
|
35678
36438
|
});
|
|
35679
36439
|
};
|
|
@@ -35712,6 +36472,8 @@ async function run(ctx, startup, io) {
|
|
|
35712
36472
|
quitAbort.abort();
|
|
35713
36473
|
epoch += 1;
|
|
35714
36474
|
off();
|
|
36475
|
+
for (const dispose of offCapabilitySync) dispose();
|
|
36476
|
+
if (capabilitySyncTimer !== void 0) clearTimeout(capabilitySyncTimer);
|
|
35715
36477
|
mountRef.current?.unmount();
|
|
35716
36478
|
const currentSession = session;
|
|
35717
36479
|
const currentActive = active;
|
|
@@ -35735,6 +36497,10 @@ async function run(ctx, startup, io) {
|
|
|
35735
36497
|
{
|
|
35736
36498
|
name: "history",
|
|
35737
36499
|
run: () => historyWriteChain
|
|
36500
|
+
},
|
|
36501
|
+
{
|
|
36502
|
+
name: "settings",
|
|
36503
|
+
run: () => settingsPersistence.flush()
|
|
35738
36504
|
}
|
|
35739
36505
|
], io.exit, report);
|
|
35740
36506
|
};
|
|
@@ -36054,10 +36820,21 @@ async function run(ctx, startup, io) {
|
|
|
36054
36820
|
* Delete one session subtree (/delete, codex semantics: subagent threads go
|
|
36055
36821
|
* with their root). The kernel persistence seam has NO deletion API by
|
|
36056
36822
|
* design — logs accumulate "until removed externally" — so this is the
|
|
36057
|
-
* controlled external removal
|
|
36058
|
-
*
|
|
36059
|
-
*
|
|
36060
|
-
*
|
|
36823
|
+
* controlled external removal, in three phases with a hard boundary
|
|
36824
|
+
* between planning and touching the filesystem:
|
|
36825
|
+
*
|
|
36826
|
+
* 1. `planSessionDeletion` collects the subtree and refuses when the root
|
|
36827
|
+
* or ANY member is live (a live child would outlive its deleted
|
|
36828
|
+
* parent), ordering the plan children-first.
|
|
36829
|
+
* 2. Every plan node must locate to a guarded artifact directory
|
|
36830
|
+
* (`encodeSegment(id)`/`session.jsonl` layout). Backends without a
|
|
36831
|
+
* locatable artifact (SQLite) refuse the WHOLE deletion here — no
|
|
36832
|
+
* file has been touched yet, so a backend or layout surprise can
|
|
36833
|
+
* never strand a half-deleted subtree.
|
|
36834
|
+
* 3. Artifacts are removed children-first: only an I/O error mid-delete
|
|
36835
|
+
* can stop it short (reported with removed/total counts), leaving the
|
|
36836
|
+
* shallowest lineage intact.
|
|
36837
|
+
*
|
|
36061
36838
|
* @param id - the root session id to delete.
|
|
36062
36839
|
* @returns the outcome line for the panel/notice.
|
|
36063
36840
|
*/
|
|
@@ -36065,19 +36842,22 @@ async function run(ctx, startup, io) {
|
|
|
36065
36842
|
if (sessionQuery === void 0) return "session query is unavailable in this profile";
|
|
36066
36843
|
if (session !== void 0 && session.id === id) return "cannot delete the session you are using — switch or /new first";
|
|
36067
36844
|
const records = await sessionQuery.listSessions();
|
|
36068
|
-
const
|
|
36069
|
-
if (
|
|
36070
|
-
if (target.live) return "cannot delete a live session — it is open in this or another process";
|
|
36071
|
-
const doomed = collectDeletionSubtree(records, id);
|
|
36845
|
+
const plan = planSessionDeletion(records, id);
|
|
36846
|
+
if (!plan.ok) return plan.reason;
|
|
36072
36847
|
const byId = new Map(records.map((record) => [record.header.id, record]));
|
|
36073
|
-
|
|
36074
|
-
for (const
|
|
36075
|
-
const record = byId.get(
|
|
36076
|
-
if (record === void 0
|
|
36848
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
36849
|
+
for (const node of plan.nodes) {
|
|
36850
|
+
const record = byId.get(node.id);
|
|
36851
|
+
if (record === void 0) return `no persisted session matches "${node.id}"`;
|
|
36077
36852
|
const location = persistence?.locate(record.header);
|
|
36078
|
-
if (location === void 0) return `session backend exposes no deletable artifact for ${
|
|
36079
|
-
const dir = sessionArtifactDirectory(location.path,
|
|
36853
|
+
if (location === void 0) return `session backend exposes no deletable artifact for ${node.id.slice(-12)} (deletion is unsupported on this backend)`;
|
|
36854
|
+
const dir = sessionArtifactDirectory(location.path, node.id);
|
|
36080
36855
|
if (dir === void 0) return `refusing to delete: unexpected artifact layout at ${location.path}`;
|
|
36856
|
+
dirs.set(node.id, dir);
|
|
36857
|
+
}
|
|
36858
|
+
let removed = 0;
|
|
36859
|
+
for (const node of plan.nodes) {
|
|
36860
|
+
const dir = dirs.get(node.id);
|
|
36081
36861
|
try {
|
|
36082
36862
|
for (const name of SESSION_ARTIFACT_NAMES) await rm(join(dir, name), { force: true });
|
|
36083
36863
|
await rm(dir, {
|
|
@@ -36086,7 +36866,7 @@ async function run(ctx, startup, io) {
|
|
|
36086
36866
|
}).catch(() => {});
|
|
36087
36867
|
removed += 1;
|
|
36088
36868
|
} catch (error) {
|
|
36089
|
-
return `delete failed for ${
|
|
36869
|
+
return `delete failed for ${node.id.slice(-12)} after ${removed} of ${plan.nodes.length}: ${error instanceof Error ? error.message : String(error)}`;
|
|
36090
36870
|
}
|
|
36091
36871
|
}
|
|
36092
36872
|
return `deleted ${removed} session${removed === 1 ? "" : "s"}`;
|
|
@@ -36111,13 +36891,20 @@ async function run(ctx, startup, io) {
|
|
|
36111
36891
|
pendingModeWork = choice.then(() => {}, () => {});
|
|
36112
36892
|
return choice;
|
|
36113
36893
|
}
|
|
36114
|
-
const
|
|
36115
|
-
|
|
36116
|
-
|
|
36117
|
-
|
|
36118
|
-
|
|
36119
|
-
|
|
36120
|
-
|
|
36894
|
+
const currentActive = active;
|
|
36895
|
+
const atEpoch = epoch;
|
|
36896
|
+
let selected;
|
|
36897
|
+
await compose(async () => {
|
|
36898
|
+
const preset = await selectPreset(presets, currentAgent, id);
|
|
36899
|
+
if (epoch !== atEpoch || agent !== currentAgent || active !== currentActive) throw new Error("session changed while switching mode — nothing applied; retry in the active session");
|
|
36900
|
+
if (active === void 0) throw new Error("active Agent has no session state");
|
|
36901
|
+
active.mode = preset.id;
|
|
36902
|
+
commands.setAgent(currentAgent);
|
|
36903
|
+
skills.setAgent(currentAgent);
|
|
36904
|
+
selected = preset.id;
|
|
36905
|
+
renderCurrent();
|
|
36906
|
+
});
|
|
36907
|
+
return selected;
|
|
36121
36908
|
};
|
|
36122
36909
|
const activate = (nextTarget) => {
|
|
36123
36910
|
if (quitting) return Promise.resolve();
|
|
@@ -36231,7 +37018,21 @@ async function run(ctx, startup, io) {
|
|
|
36231
37018
|
});
|
|
36232
37019
|
};
|
|
36233
37020
|
const reviewChanges = (argument) => {
|
|
36234
|
-
|
|
37021
|
+
const currentAgent = agent;
|
|
37022
|
+
if (currentAgent === void 0) {
|
|
37023
|
+
bridge.notify("no session yet - submit a message to start", "warning");
|
|
37024
|
+
return;
|
|
37025
|
+
}
|
|
37026
|
+
const atEpoch = epoch;
|
|
37027
|
+
const reviewCwd = session?.header.cwd ?? cwd;
|
|
37028
|
+
const controller = new AbortController();
|
|
37029
|
+
pendingControllers.add(controller);
|
|
37030
|
+
const finish = () => {
|
|
37031
|
+
pendingControllers.delete(controller);
|
|
37032
|
+
};
|
|
37033
|
+
loadGitDiff(reviewCwd, argument, controller.signal).then(({ title, files }) => {
|
|
37034
|
+
finish();
|
|
37035
|
+
if (controller.signal.aborted || epoch !== atEpoch || agent !== currentAgent) return;
|
|
36235
37036
|
try {
|
|
36236
37037
|
setPermissionAction("read-only");
|
|
36237
37038
|
} catch (error) {
|
|
@@ -36241,6 +37042,8 @@ async function run(ctx, startup, io) {
|
|
|
36241
37042
|
send(buildReviewPrompt(files.flatMap((file) => file.lines).join("\n"), title), "followup");
|
|
36242
37043
|
bridge.notify("review started under read-only permissions");
|
|
36243
37044
|
}, (error) => {
|
|
37045
|
+
finish();
|
|
37046
|
+
if (controller.signal.aborted || epoch !== atEpoch) return;
|
|
36244
37047
|
bridge.notify(`review failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
36245
37048
|
});
|
|
36246
37049
|
};
|
|
@@ -36331,7 +37134,7 @@ async function run(ctx, startup, io) {
|
|
|
36331
37134
|
copyTextValue: copyText,
|
|
36332
37135
|
loadMentions: (query, signal) => mentions.candidates(query, signal),
|
|
36333
37136
|
inspectImages: (paths) => inspectImagePaths(paths, ctx.get("attachments"), session?.header.cwd ?? cwd),
|
|
36334
|
-
prepareImages: (paths) => saveImagePaths(paths, ctx.get("attachments")),
|
|
37137
|
+
prepareImages: (paths, signal) => saveImagePaths(paths, ctx.get("attachments"), signal),
|
|
36335
37138
|
cyclePermission: cyclePermission$1,
|
|
36336
37139
|
setPermission: setPermissionAction,
|
|
36337
37140
|
selectModel,
|
|
@@ -36368,6 +37171,7 @@ async function run(ctx, startup, io) {
|
|
|
36368
37171
|
loadJobs: () => listJobs(ctx, active?.agent),
|
|
36369
37172
|
statusline: statuslineItems,
|
|
36370
37173
|
saveStatusline,
|
|
37174
|
+
applyEditorKeys,
|
|
36371
37175
|
saveTheme,
|
|
36372
37176
|
history: inputHistory,
|
|
36373
37177
|
recordHistory,
|
|
@@ -36400,6 +37204,12 @@ async function run(ctx, startup, io) {
|
|
|
36400
37204
|
if (themeWarning !== void 0) setTimeout(() => {
|
|
36401
37205
|
bridge.notify("theme config unreadable, using dark: " + themeWarning, "warning");
|
|
36402
37206
|
}, 50);
|
|
37207
|
+
resolveEditorKeysStartupHint(editorKeysEnv).then((hint) => {
|
|
37208
|
+
if (hint === void 0) return;
|
|
37209
|
+
setTimeout(() => {
|
|
37210
|
+
bridge.notify(hint);
|
|
37211
|
+
}, 50);
|
|
37212
|
+
}, () => {});
|
|
36403
37213
|
}
|
|
36404
37214
|
/**
|
|
36405
37215
|
* Mount the interactive terminal driver.
|