dsh-code 1.0.2 → 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 +1156 -720
- package/lib/types/app.d.ts +2 -0
- package/lib/types/editor-keys.d.ts +105 -0
- package/lib/types/git-workflow.d.ts +6 -2
- package/lib/types/model-capabilities.d.ts +82 -0
- package/lib/types/provider-settings.d.ts +7 -0
- package/lib/types/render/lines.d.ts +25 -0
- 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 +1104 -1041
- package/src/editor-keys.ts +371 -0
- package/src/git-workflow.ts +10 -6
- package/src/index.ts +1637 -1523
- package/src/model-capabilities.ts +318 -0
- package/src/provider-settings.ts +16 -0
- package/src/render/lines.ts +403 -356
- 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";
|
|
@@ -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) {
|
|
@@ -27295,6 +26941,59 @@ function collectDeletionSubtree(records, id) {
|
|
|
27295
26941
|
return [...doomed];
|
|
27296
26942
|
}
|
|
27297
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
|
+
/**
|
|
27298
26997
|
* Codex-style relative time for session rows ("now", "5m ago", "3h ago",
|
|
27299
26998
|
* "2d ago"; older than a week falls back to the local date).
|
|
27300
26999
|
* @param timestamp - epoch milliseconds of the last activity.
|
|
@@ -28082,7 +27781,7 @@ const DEFAULT_TOOL_ROWS = 3;
|
|
|
28082
27781
|
/** Keep the invocation visible while making hidden tool output discoverable. */
|
|
28083
27782
|
function compactToolLines(lines, columns) {
|
|
28084
27783
|
if (lines.length <= DEFAULT_TOOL_ROWS) return lines;
|
|
28085
|
-
return [...lines.slice(0, 2), ...textLines(" … output hidden · Ctrl+R", columns, "dim").slice(0, 1)];
|
|
27784
|
+
return [...lines.slice(0, 2), ...textLines(" … output hidden · Ctrl/Alt+R", columns, "dim").slice(0, 1)];
|
|
28086
27785
|
}
|
|
28087
27786
|
/**
|
|
28088
27787
|
* Convert one durable transcript entry to its complete scrollable row model.
|
|
@@ -28096,7 +27795,7 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
28096
27795
|
case "user": return entry.notice ? hangingStyledLines([lineSegment(promptDisplayText(entry), "dim")], width, "⤷ ", "dim", " ", "dim") : hangingStyledLines([lineSegment(promptDisplayText(entry), "plain")], width, "❯ ", "brand", " ", "plain");
|
|
28097
27796
|
case "pending": return hangingStyledLines([lineSegment(promptDisplayText(entry), "plain")], width, "❯ ", "brand", " ", "plain");
|
|
28098
27797
|
case "assistant": {
|
|
28099
|
-
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");
|
|
28100
27799
|
const body = markdownLines(entry.text, Math.max(10, width - 2)).map((line) => ({ segments: [{
|
|
28101
27800
|
text: " ",
|
|
28102
27801
|
style: "plain"
|
|
@@ -28142,6 +27841,44 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
28142
27841
|
function settledEntryLines(entry, columns, showReasoning) {
|
|
28143
27842
|
return transcriptEntryLines(entry, columns, showReasoning, false, showReasoning);
|
|
28144
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
|
+
};
|
|
27881
|
+
}
|
|
28145
27882
|
//#endregion
|
|
28146
27883
|
//#region src/render/editor.ts
|
|
28147
27884
|
/**
|
|
@@ -30412,6 +30149,10 @@ const LOCAL_COMMANDS = [
|
|
|
30412
30149
|
label: "/subagent",
|
|
30413
30150
|
description: "choose the model delegated subagents run on"
|
|
30414
30151
|
},
|
|
30152
|
+
{
|
|
30153
|
+
label: "/vscode-keys",
|
|
30154
|
+
description: "pass ctrl+r through the vs code terminal"
|
|
30155
|
+
},
|
|
30415
30156
|
{
|
|
30416
30157
|
label: "/delete",
|
|
30417
30158
|
description: "delete a session and its subagent threads"
|
|
@@ -30508,15 +30249,13 @@ function useCursorBlink(active) {
|
|
|
30508
30249
|
};
|
|
30509
30250
|
}
|
|
30510
30251
|
/**
|
|
30511
|
-
*
|
|
30512
|
-
*
|
|
30513
|
-
*
|
|
30514
|
-
*
|
|
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.
|
|
30515
30256
|
*/
|
|
30516
|
-
function
|
|
30257
|
+
function ShimmerLine({ text }) {
|
|
30517
30258
|
const tick = useFrames(33);
|
|
30518
|
-
const elapsed = since === 0 ? 0 : Date.now() - since;
|
|
30519
|
-
const text = elapsed >= 15e3 ? `✻ Deep diving... ${runClock(elapsed)}` : "✻ Deep diving...";
|
|
30520
30259
|
const palette = getPalette();
|
|
30521
30260
|
const graphemes = splitGraphemes(text);
|
|
30522
30261
|
return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, ...graphemes.map((grapheme, index) => {
|
|
@@ -30529,6 +30268,17 @@ function DeepDivingLine({ since }) {
|
|
|
30529
30268
|
}));
|
|
30530
30269
|
}
|
|
30531
30270
|
/**
|
|
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.
|
|
30275
|
+
*/
|
|
30276
|
+
function DeepDivingLine({ since }) {
|
|
30277
|
+
const elapsed = since === 0 ? 0 : Date.now() - since;
|
|
30278
|
+
const text = elapsed >= 15e3 ? `✻ Deep diving... ${runClock(elapsed)}` : "✻ Deep diving...";
|
|
30279
|
+
return (0, import_react.createElement)(ShimmerLine, { text });
|
|
30280
|
+
}
|
|
30281
|
+
/**
|
|
30532
30282
|
* The streaming buffer rendered with a hard size cap: the live region must
|
|
30533
30283
|
* ALWAYS fit the terminal, or Ink's erase/rewrite of a dynamic tree taller
|
|
30534
30284
|
* than the screen freezes (cursor-up past the top, garbage, no scroll). The
|
|
@@ -30784,11 +30534,12 @@ function todoMark(status) {
|
|
|
30784
30534
|
}
|
|
30785
30535
|
/**
|
|
30786
30536
|
* One-row live subagent summary (the Codex agent status feed, compressed to
|
|
30787
|
-
* the transcript's budget): running count, total
|
|
30788
|
-
*
|
|
30789
|
-
* 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.
|
|
30790
30541
|
*/
|
|
30791
|
-
function AgentsLine({ rows }) {
|
|
30542
|
+
function AgentsLine({ rows, total }) {
|
|
30792
30543
|
if (rows.length === 0) return void 0;
|
|
30793
30544
|
const running = rows.filter((row) => row.state !== "done").length;
|
|
30794
30545
|
const newest = [...rows].sort((left, right) => right.updatedAt - left.updatedAt)[0];
|
|
@@ -30797,7 +30548,7 @@ function AgentsLine({ rows }) {
|
|
|
30797
30548
|
color: inkColor(getPalette().brand),
|
|
30798
30549
|
bold: true,
|
|
30799
30550
|
wrap: "truncate-end"
|
|
30800
|
-
}, `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}`)));
|
|
30801
30552
|
}
|
|
30802
30553
|
/** One-row todo summary: task count cannot grow the live Ink tree. */
|
|
30803
30554
|
function TodoPanel({ todos }) {
|
|
@@ -32016,7 +31767,7 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
32016
31767
|
key: "key-inspector",
|
|
32017
31768
|
dimColor: true,
|
|
32018
31769
|
wrap: "truncate-end"
|
|
32019
|
-
}, " ctrl+o history details · ctrl+r thinking · shift+tab permission preset"),
|
|
31770
|
+
}, " ctrl+o history details · ctrl/alt+r thinking · shift+tab permission preset"),
|
|
32020
31771
|
(0, import_react.createElement)(Text, {
|
|
32021
31772
|
key: "key-cancel",
|
|
32022
31773
|
dimColor: true,
|
|
@@ -32353,7 +32104,7 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
32353
32104
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
32354
32105
|
* box passes every key through untouched.
|
|
32355
32106
|
*/
|
|
32356
|
-
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 }) {
|
|
32357
32108
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
32358
32109
|
const editorColumns = Math.max(1, columns - 6);
|
|
32359
32110
|
const stdin = useStdin().stdin;
|
|
@@ -32748,7 +32499,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32748
32499
|
}
|
|
32749
32500
|
return;
|
|
32750
32501
|
}
|
|
32751
|
-
if (key.ctrl && input === "r") {
|
|
32502
|
+
if ((key.ctrl || key.meta) && input === "r") {
|
|
32752
32503
|
if (focusReporting && !terminalFocusedRef.current) return;
|
|
32753
32504
|
toggleReasoning();
|
|
32754
32505
|
return;
|
|
@@ -32960,6 +32711,10 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32960
32711
|
openTodos();
|
|
32961
32712
|
return;
|
|
32962
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
|
+
}
|
|
32963
32718
|
if (text === "/subagent") {
|
|
32964
32719
|
openSubagent();
|
|
32965
32720
|
return;
|
|
@@ -33592,6 +33347,7 @@ function App(props) {
|
|
|
33592
33347
|
}, [modelOpen, props.subscribeProviderAuthorizations]);
|
|
33593
33348
|
const busy = view.busy;
|
|
33594
33349
|
const [showReasoning, setShowReasoning] = (0, import_react.useState)(false);
|
|
33350
|
+
const budgetWarnRef = (0, import_react.useRef)(void 0);
|
|
33595
33351
|
const [verboseOpen, setVerboseOpen] = (0, import_react.useState)(false);
|
|
33596
33352
|
const [diffView, setDiffView] = (0, import_react.useState)(void 0);
|
|
33597
33353
|
const [helpOpen, setHelpOpen] = (0, import_react.useState)(false);
|
|
@@ -33760,6 +33516,18 @@ function App(props) {
|
|
|
33760
33516
|
const streamRows = Math.max(1, dynamicRows - visibleLiveLines.length);
|
|
33761
33517
|
const reasoningRows = view.streamingReasoning === "" ? 0 : view.streaming === "" ? streamRows : streamRows <= 1 ? 0 : showReasoning ? Math.max(1, Math.floor(streamRows / 3)) : 1;
|
|
33762
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;
|
|
33763
33531
|
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === void 0 && !approvalPending && !questionPending;
|
|
33764
33532
|
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || jobsOpen || statuslineOpen || themeOpen || historyOpen || agentsOpen || subagentOpen || todosOpen || verboseOpen && !approvalPending && !questionPending || diffView !== void 0 || approvalPending || questionPending;
|
|
33765
33533
|
const closeInspector = (0, import_react.useCallback)(() => {
|
|
@@ -33997,18 +33765,27 @@ function App(props) {
|
|
|
33997
33765
|
return (0, import_react.createElement)(Box, { flexDirection: "column" }, (0, import_react.createElement)(MemoStaticTranscript, {
|
|
33998
33766
|
key: refreshEpoch,
|
|
33999
33767
|
items: settledRows
|
|
34000
|
-
}), transcriptVisible ? (0, import_react.createElement)(Box, { flexDirection: "column" },
|
|
34001
|
-
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,
|
|
34002
33770
|
prefix: "✻ ",
|
|
34003
33771
|
continuationPrefix: " ",
|
|
34004
33772
|
dim: true,
|
|
34005
|
-
maxRows:
|
|
34006
|
-
}) :
|
|
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)",
|
|
33776
|
+
prefix: "✻ ",
|
|
33777
|
+
continuationPrefix: " ",
|
|
33778
|
+
dim: true,
|
|
33779
|
+
maxRows: auditedReasoningRows
|
|
33780
|
+
}) : void 0, view.streaming !== "" && auditedAnswerRows > 0 ? (0, import_react.createElement)(StreamTail, {
|
|
34007
33781
|
text: view.streaming,
|
|
34008
33782
|
dim: false,
|
|
34009
|
-
maxRows:
|
|
33783
|
+
maxRows: auditedAnswerRows,
|
|
34010
33784
|
prefix: " "
|
|
34011
|
-
}, 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, {
|
|
34012
33789
|
todos: view.todos,
|
|
34013
33790
|
onClose: () => {
|
|
34014
33791
|
setTodosOpen(false);
|
|
@@ -34141,6 +33918,7 @@ function App(props) {
|
|
|
34141
33918
|
descriptors,
|
|
34142
33919
|
skills,
|
|
34143
33920
|
dispatch: props.dispatch,
|
|
33921
|
+
applyEditorKeys: props.applyEditorKeys,
|
|
34144
33922
|
steer: props.steer,
|
|
34145
33923
|
interrupt: props.interrupt,
|
|
34146
33924
|
quit: props.quit,
|
|
@@ -34235,7 +34013,7 @@ function App(props) {
|
|
|
34235
34013
|
refresh: refreshScreen,
|
|
34236
34014
|
toggleReasoning: () => {
|
|
34237
34015
|
setShowReasoning((current) => !current);
|
|
34238
|
-
|
|
34016
|
+
refreshScreen();
|
|
34239
34017
|
},
|
|
34240
34018
|
loadMentions: props.loadMentions,
|
|
34241
34019
|
inspectImages: props.inspectImages,
|
|
@@ -34604,6 +34382,202 @@ async function loadModelDirectory(ctx) {
|
|
|
34604
34382
|
};
|
|
34605
34383
|
}
|
|
34606
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
|
|
34607
34581
|
//#region src/provider-settings.ts
|
|
34608
34582
|
/** Human text for a rejection value (mirrors the web page's `messageOf`). */
|
|
34609
34583
|
function messageOf(error) {
|
|
@@ -34652,11 +34626,13 @@ function configurationOf(profile) {
|
|
|
34652
34626
|
if (typeof value !== "object" || value === null) return [];
|
|
34653
34627
|
const entry = value;
|
|
34654
34628
|
if (typeof entry.id !== "string" || entry.id.trim() === "") return [];
|
|
34629
|
+
const { id: _id, name: _name, contextWindow: _contextWindow, maxTokens: _maxTokens, ...extras } = entry;
|
|
34655
34630
|
return [{
|
|
34656
34631
|
id: entry.id,
|
|
34657
34632
|
...typeof entry.name === "string" && entry.name.trim() !== "" ? { name: entry.name } : {},
|
|
34658
34633
|
...typeof entry.contextWindow === "number" && Number.isFinite(entry.contextWindow) ? { contextWindow: entry.contextWindow } : {},
|
|
34659
|
-
...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 } : {}
|
|
34660
34636
|
}];
|
|
34661
34637
|
});
|
|
34662
34638
|
return {
|
|
@@ -34872,6 +34848,7 @@ async function saveProviderConfiguration(ctx, target, configuration) {
|
|
|
34872
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`);
|
|
34873
34849
|
return {
|
|
34874
34850
|
id,
|
|
34851
|
+
...model.extras,
|
|
34875
34852
|
...model.name === void 0 || model.name.trim() === "" ? {} : { name: model.name.trim() },
|
|
34876
34853
|
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
34877
34854
|
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
@@ -35055,7 +35032,10 @@ const NOTIFY_FRAME_MS$1 = 16;
|
|
|
35055
35032
|
* @returns the store the runner feeds and the renderer subscribes to.
|
|
35056
35033
|
*/
|
|
35057
35034
|
function createTranscriptStore(replay) {
|
|
35058
|
-
let
|
|
35035
|
+
let acc = createReplayAccumulator();
|
|
35036
|
+
for (const event of replay ?? []) replayProjectEvent(acc, event);
|
|
35037
|
+
let view = snapshotReplayView(acc);
|
|
35038
|
+
let dirty = false;
|
|
35059
35039
|
const listeners = /* @__PURE__ */ new Set();
|
|
35060
35040
|
let scheduled = false;
|
|
35061
35041
|
let lastNotifyAt = 0;
|
|
@@ -35072,7 +35052,13 @@ function createTranscriptStore(replay) {
|
|
|
35072
35052
|
else setTimeout(dispatch, wait);
|
|
35073
35053
|
};
|
|
35074
35054
|
return {
|
|
35075
|
-
getView: () =>
|
|
35055
|
+
getView: () => {
|
|
35056
|
+
if (dirty) {
|
|
35057
|
+
view = snapshotReplayView(acc);
|
|
35058
|
+
dirty = false;
|
|
35059
|
+
}
|
|
35060
|
+
return view;
|
|
35061
|
+
},
|
|
35076
35062
|
subscribe(listener) {
|
|
35077
35063
|
listeners.add(listener);
|
|
35078
35064
|
return () => {
|
|
@@ -35080,13 +35066,13 @@ function createTranscriptStore(replay) {
|
|
|
35080
35066
|
};
|
|
35081
35067
|
},
|
|
35082
35068
|
apply(event) {
|
|
35083
|
-
|
|
35084
|
-
|
|
35085
|
-
view = next;
|
|
35069
|
+
if (!replayProjectEvent(acc, event)) return;
|
|
35070
|
+
dirty = true;
|
|
35086
35071
|
notify();
|
|
35087
35072
|
},
|
|
35088
35073
|
reset() {
|
|
35089
|
-
|
|
35074
|
+
acc = createReplayAccumulator();
|
|
35075
|
+
dirty = true;
|
|
35090
35076
|
notify();
|
|
35091
35077
|
}
|
|
35092
35078
|
};
|
|
@@ -35204,6 +35190,7 @@ function foldSubagentRow(previous, sessionId, event) {
|
|
|
35204
35190
|
*/
|
|
35205
35191
|
function createSubagentFeed() {
|
|
35206
35192
|
let rows = Object.freeze([]);
|
|
35193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35207
35194
|
const listeners = /* @__PURE__ */ new Set();
|
|
35208
35195
|
let scheduled = false;
|
|
35209
35196
|
let lastNotifyAt = 0;
|
|
@@ -35224,12 +35211,30 @@ function createSubagentFeed() {
|
|
|
35224
35211
|
const index = rows.findIndex((row) => row.id === sessionId);
|
|
35225
35212
|
const previous = index === -1 ? void 0 : rows[index];
|
|
35226
35213
|
const next = foldSubagentRow(previous, sessionId, event);
|
|
35227
|
-
if (
|
|
35228
|
-
|
|
35229
|
-
|
|
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]);
|
|
35230
35234
|
notify();
|
|
35231
35235
|
},
|
|
35232
35236
|
reset() {
|
|
35237
|
+
seen.clear();
|
|
35233
35238
|
if (rows.length === 0) return;
|
|
35234
35239
|
rows = Object.freeze([]);
|
|
35235
35240
|
notify();
|
|
@@ -35242,6 +35247,9 @@ function createSubagentFeed() {
|
|
|
35242
35247
|
},
|
|
35243
35248
|
getSnapshot() {
|
|
35244
35249
|
return rows;
|
|
35250
|
+
},
|
|
35251
|
+
getTotalSeen() {
|
|
35252
|
+
return seen.size;
|
|
35245
35253
|
}
|
|
35246
35254
|
};
|
|
35247
35255
|
}
|
|
@@ -35431,6 +35439,312 @@ function latestAssistantText(view) {
|
|
|
35431
35439
|
}
|
|
35432
35440
|
}
|
|
35433
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
|
|
35434
35748
|
//#region src/fork.ts
|
|
35435
35749
|
/** Select a completed turn and trailing between-turn metadata. */
|
|
35436
35750
|
function selectForkSeed(events, atSeq) {
|
|
@@ -35497,13 +35811,14 @@ function parseGitDiffSpec(argument) {
|
|
|
35497
35811
|
]
|
|
35498
35812
|
};
|
|
35499
35813
|
}
|
|
35500
|
-
function executeGit(cwd, args) {
|
|
35814
|
+
function executeGit(cwd, args, signal) {
|
|
35501
35815
|
return new Promise((resolve, reject) => {
|
|
35502
35816
|
execFile("git", [...args], {
|
|
35503
35817
|
cwd,
|
|
35504
35818
|
encoding: "utf8",
|
|
35505
35819
|
maxBuffer: 16777216,
|
|
35506
|
-
windowsHide: true
|
|
35820
|
+
windowsHide: true,
|
|
35821
|
+
signal
|
|
35507
35822
|
}, (error, stdout, stderr) => {
|
|
35508
35823
|
if (error !== null) {
|
|
35509
35824
|
reject(new Error(stderr.trim() || error.message));
|
|
@@ -35513,11 +35828,15 @@ function executeGit(cwd, args) {
|
|
|
35513
35828
|
});
|
|
35514
35829
|
});
|
|
35515
35830
|
}
|
|
35516
|
-
/**
|
|
35517
|
-
|
|
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) {
|
|
35518
35837
|
const spec = parseGitDiffSpec(argument);
|
|
35519
35838
|
try {
|
|
35520
|
-
const text = await executeGit(cwd, spec.args);
|
|
35839
|
+
const text = await executeGit(cwd, spec.args, signal);
|
|
35521
35840
|
return {
|
|
35522
35841
|
title: `git diff - ${spec.label}`,
|
|
35523
35842
|
files: parseGitDiffFiles(text)
|
|
@@ -35531,7 +35850,7 @@ async function loadGitDiff(cwd, argument) {
|
|
|
35531
35850
|
"--no-ext-diff",
|
|
35532
35851
|
"--unified=3",
|
|
35533
35852
|
"--"
|
|
35534
|
-
]))
|
|
35853
|
+
], signal))
|
|
35535
35854
|
};
|
|
35536
35855
|
}
|
|
35537
35856
|
}
|
|
@@ -35699,6 +36018,48 @@ function listPluginRows(ctx) {
|
|
|
35699
36018
|
return rows;
|
|
35700
36019
|
}
|
|
35701
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
|
|
35702
36063
|
//#region src/index.ts
|
|
35703
36064
|
/**
|
|
35704
36065
|
* @deepseek-ai/dsh-code — the interactive terminal driver. The bundle patch
|
|
@@ -36022,6 +36383,20 @@ async function run(ctx, startup, io) {
|
|
|
36022
36383
|
});
|
|
36023
36384
|
const questions = mountQuestionProvider(ctx);
|
|
36024
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();
|
|
36025
36400
|
const statuslinePath = join(homedir(), ".dsh", "dsh-code", "statusline.json");
|
|
36026
36401
|
let statuslineWarning;
|
|
36027
36402
|
let statuslineItems = [];
|
|
@@ -36031,12 +36406,23 @@ async function run(ctx, startup, io) {
|
|
|
36031
36406
|
statuslineItems = parseStatuslineItems(void 0);
|
|
36032
36407
|
if (error.code !== "ENOENT") statuslineWarning = error instanceof Error ? error.message : String(error);
|
|
36033
36408
|
}
|
|
36409
|
+
const settingsPersistence = createUserSettingsPersistence();
|
|
36034
36410
|
const saveStatusline = (items) => {
|
|
36035
36411
|
statuslineItems = [...items];
|
|
36036
|
-
|
|
36412
|
+
settingsPersistence.save(statuslinePath, JSON.stringify({ items }, null, 2) + "\n").catch((writeError) => {
|
|
36037
36413
|
bridge.notify("statusline save failed: " + (writeError instanceof Error ? writeError.message : String(writeError)), "error");
|
|
36038
36414
|
});
|
|
36039
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);
|
|
36040
36426
|
const themePath = join(homedir(), ".dsh", "dsh-code", "theme.json");
|
|
36041
36427
|
let themeWarning;
|
|
36042
36428
|
if (startup.theme === void 0) try {
|
|
@@ -36047,7 +36433,7 @@ async function run(ctx, startup, io) {
|
|
|
36047
36433
|
else setTheme(startup.theme);
|
|
36048
36434
|
const saveTheme = (name) => {
|
|
36049
36435
|
setTheme(name);
|
|
36050
|
-
|
|
36436
|
+
settingsPersistence.save(themePath, JSON.stringify({ theme: name }, null, 2) + "\n").catch((writeError) => {
|
|
36051
36437
|
bridge.notify("theme save failed: " + (writeError instanceof Error ? writeError.message : String(writeError)), "error");
|
|
36052
36438
|
});
|
|
36053
36439
|
};
|
|
@@ -36086,6 +36472,8 @@ async function run(ctx, startup, io) {
|
|
|
36086
36472
|
quitAbort.abort();
|
|
36087
36473
|
epoch += 1;
|
|
36088
36474
|
off();
|
|
36475
|
+
for (const dispose of offCapabilitySync) dispose();
|
|
36476
|
+
if (capabilitySyncTimer !== void 0) clearTimeout(capabilitySyncTimer);
|
|
36089
36477
|
mountRef.current?.unmount();
|
|
36090
36478
|
const currentSession = session;
|
|
36091
36479
|
const currentActive = active;
|
|
@@ -36109,6 +36497,10 @@ async function run(ctx, startup, io) {
|
|
|
36109
36497
|
{
|
|
36110
36498
|
name: "history",
|
|
36111
36499
|
run: () => historyWriteChain
|
|
36500
|
+
},
|
|
36501
|
+
{
|
|
36502
|
+
name: "settings",
|
|
36503
|
+
run: () => settingsPersistence.flush()
|
|
36112
36504
|
}
|
|
36113
36505
|
], io.exit, report);
|
|
36114
36506
|
};
|
|
@@ -36428,10 +36820,21 @@ async function run(ctx, startup, io) {
|
|
|
36428
36820
|
* Delete one session subtree (/delete, codex semantics: subagent threads go
|
|
36429
36821
|
* with their root). The kernel persistence seam has NO deletion API by
|
|
36430
36822
|
* design — logs accumulate "until removed externally" — so this is the
|
|
36431
|
-
* controlled external removal
|
|
36432
|
-
*
|
|
36433
|
-
*
|
|
36434
|
-
*
|
|
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
|
+
*
|
|
36435
36838
|
* @param id - the root session id to delete.
|
|
36436
36839
|
* @returns the outcome line for the panel/notice.
|
|
36437
36840
|
*/
|
|
@@ -36439,19 +36842,22 @@ async function run(ctx, startup, io) {
|
|
|
36439
36842
|
if (sessionQuery === void 0) return "session query is unavailable in this profile";
|
|
36440
36843
|
if (session !== void 0 && session.id === id) return "cannot delete the session you are using — switch or /new first";
|
|
36441
36844
|
const records = await sessionQuery.listSessions();
|
|
36442
|
-
const
|
|
36443
|
-
if (
|
|
36444
|
-
if (target.live) return "cannot delete a live session — it is open in this or another process";
|
|
36445
|
-
const doomed = collectDeletionSubtree(records, id);
|
|
36845
|
+
const plan = planSessionDeletion(records, id);
|
|
36846
|
+
if (!plan.ok) return plan.reason;
|
|
36446
36847
|
const byId = new Map(records.map((record) => [record.header.id, record]));
|
|
36447
|
-
|
|
36448
|
-
for (const
|
|
36449
|
-
const record = byId.get(
|
|
36450
|
-
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}"`;
|
|
36451
36852
|
const location = persistence?.locate(record.header);
|
|
36452
|
-
if (location === void 0) return `session backend exposes no deletable artifact for ${
|
|
36453
|
-
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);
|
|
36454
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);
|
|
36455
36861
|
try {
|
|
36456
36862
|
for (const name of SESSION_ARTIFACT_NAMES) await rm(join(dir, name), { force: true });
|
|
36457
36863
|
await rm(dir, {
|
|
@@ -36460,7 +36866,7 @@ async function run(ctx, startup, io) {
|
|
|
36460
36866
|
}).catch(() => {});
|
|
36461
36867
|
removed += 1;
|
|
36462
36868
|
} catch (error) {
|
|
36463
|
-
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)}`;
|
|
36464
36870
|
}
|
|
36465
36871
|
}
|
|
36466
36872
|
return `deleted ${removed} session${removed === 1 ? "" : "s"}`;
|
|
@@ -36485,13 +36891,20 @@ async function run(ctx, startup, io) {
|
|
|
36485
36891
|
pendingModeWork = choice.then(() => {}, () => {});
|
|
36486
36892
|
return choice;
|
|
36487
36893
|
}
|
|
36488
|
-
const
|
|
36489
|
-
|
|
36490
|
-
|
|
36491
|
-
|
|
36492
|
-
|
|
36493
|
-
|
|
36494
|
-
|
|
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;
|
|
36495
36908
|
};
|
|
36496
36909
|
const activate = (nextTarget) => {
|
|
36497
36910
|
if (quitting) return Promise.resolve();
|
|
@@ -36605,7 +37018,21 @@ async function run(ctx, startup, io) {
|
|
|
36605
37018
|
});
|
|
36606
37019
|
};
|
|
36607
37020
|
const reviewChanges = (argument) => {
|
|
36608
|
-
|
|
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;
|
|
36609
37036
|
try {
|
|
36610
37037
|
setPermissionAction("read-only");
|
|
36611
37038
|
} catch (error) {
|
|
@@ -36615,6 +37042,8 @@ async function run(ctx, startup, io) {
|
|
|
36615
37042
|
send(buildReviewPrompt(files.flatMap((file) => file.lines).join("\n"), title), "followup");
|
|
36616
37043
|
bridge.notify("review started under read-only permissions");
|
|
36617
37044
|
}, (error) => {
|
|
37045
|
+
finish();
|
|
37046
|
+
if (controller.signal.aborted || epoch !== atEpoch) return;
|
|
36618
37047
|
bridge.notify(`review failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
36619
37048
|
});
|
|
36620
37049
|
};
|
|
@@ -36742,6 +37171,7 @@ async function run(ctx, startup, io) {
|
|
|
36742
37171
|
loadJobs: () => listJobs(ctx, active?.agent),
|
|
36743
37172
|
statusline: statuslineItems,
|
|
36744
37173
|
saveStatusline,
|
|
37174
|
+
applyEditorKeys,
|
|
36745
37175
|
saveTheme,
|
|
36746
37176
|
history: inputHistory,
|
|
36747
37177
|
recordHistory,
|
|
@@ -36774,6 +37204,12 @@ async function run(ctx, startup, io) {
|
|
|
36774
37204
|
if (themeWarning !== void 0) setTimeout(() => {
|
|
36775
37205
|
bridge.notify("theme config unreadable, using dark: " + themeWarning, "warning");
|
|
36776
37206
|
}, 50);
|
|
37207
|
+
resolveEditorKeysStartupHint(editorKeysEnv).then((hint) => {
|
|
37208
|
+
if (hint === void 0) return;
|
|
37209
|
+
setTimeout(() => {
|
|
37210
|
+
bridge.notify(hint);
|
|
37211
|
+
}, 50);
|
|
37212
|
+
}, () => {});
|
|
36777
37213
|
}
|
|
36778
37214
|
/**
|
|
36779
37215
|
* Mount the interactive terminal driver.
|