@astralyn/pi 1.4.8 → 1.4.9
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/CHANGELOG.md +14 -0
- package/dist/bundle/chunks/{chunk-I2COK45I.js → chunk-3PKXUKTB.js} +10 -10
- package/dist/bundle/chunks/{chunk-FODCTZXW.js → chunk-BAGHIP5J.js} +2 -2
- package/dist/bundle/chunks/{chunk-PU2IWWXV.js → chunk-DLVLAQEG.js} +1 -1
- package/dist/bundle/chunks/{chunk-5VIOGR76.js → chunk-F66AY3WR.js} +1 -1
- package/dist/bundle/chunks/{chunk-MG5XDTXV.js → chunk-LPF7HQ3S.js} +1 -1
- package/dist/bundle/chunks/{chunk-RO2VH6UZ.js → chunk-R267VV5T.js} +1 -1
- package/dist/bundle/chunks/{chunk-RLNMABLM.js → chunk-RVKT7OUB.js} +1 -1
- package/dist/bundle/chunks/{cli-MEL7SGJD.js → cli-MBFCFGI2.js} +1 -1
- package/dist/bundle/chunks/{execute-4CWRNHPO.js → execute-P22VMM2H.js} +1 -1
- package/dist/bundle/chunks/{runtime-R3X67XMR.js → runtime-3RPOJS5T.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-BBLD7YLS.js → virtual-modules-VKMBQTR2.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/shell-paths/index.d.ts +8 -0
- package/dist/extensions/shell-paths/index.d.ts.map +1 -0
- package/dist/extensions/shell-paths/index.js +74 -0
- package/dist/extensions/shell-paths/index.js.map +1 -0
- package/dist/modes/interactive/chat-viewport.d.ts +5 -2
- package/dist/modes/interactive/chat-viewport.d.ts.map +1 -1
- package/dist/modes/interactive/chat-viewport.js +72 -12
- package/dist/modes/interactive/chat-viewport.js.map +1 -1
- package/dist/modes/interactive/components/sticky-user-message.d.ts +23 -0
- package/dist/modes/interactive/components/sticky-user-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/sticky-user-message.js +58 -0
- package/dist/modes/interactive/components/sticky-user-message.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts +9 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +51 -10
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/tool-view/chat.d.ts +3 -2
- package/dist/modes/interactive/tool-view/chat.d.ts.map +1 -1
- package/dist/modes/interactive/tool-view/chat.js +3 -3
- package/dist/modes/interactive/tool-view/chat.js.map +1 -1
- package/dist/modes/interactive/transcript-layout.d.ts +23 -0
- package/dist/modes/interactive/transcript-layout.d.ts.map +1 -0
- package/dist/modes/interactive/transcript-layout.js +29 -0
- package/dist/modes/interactive/transcript-layout.js.map +1 -0
- package/docs/bundled/README.md +1 -0
- package/docs/bundled/extensions/shell-paths.md +17 -0
- package/docs/docs.json +4 -0
- package/docs/usage.md +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/bundle/rpc-entry.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url);
|
|
3
|
-
import{configureHttpDispatcher,main}from"./chunks/chunk-
|
|
3
|
+
import{configureHttpDispatcher,main}from"./chunks/chunk-3PKXUKTB.js";import"./chunks/chunk-LPF7HQ3S.js";import"./chunks/chunk-DLVLAQEG.js";import"./chunks/chunk-BAGHIP5J.js";import"./chunks/chunk-6QGV3IWD.js";import{APP_NAME}from"./chunks/chunk-RVKT7OUB.js";import"./chunks/chunk-JEIV47WR.js";import"./chunks/chunk-D3ZCGC4T.js";import"./chunks/chunk-RZ2V53SP.js";import"./chunks/chunk-5FDOJRDS.js";import"./chunks/chunk-SA2AQYEI.js";import"./chunks/chunk-OV2VMHBX.js";import"./chunks/chunk-6JS2SOFO.js";import"./chunks/chunk-6VYRLG5F.js";import"./chunks/chunk-ANCZPKX7.js";import"./chunks/chunk-4L3WN2XY.js";process.title=`${APP_NAME}-rpc`;process.env.PI_CODING_AGENT="true";process.env.AI_AGENT="pi";process.emitWarning=(()=>{});configureHttpDispatcher();main(["--mode","rpc",...process.argv.slice(2)]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAkBnE,eAAO,MAAM,iBAAiB,EAAE,eAAe,EAiB9C,CAAC"}
|
package/dist/extensions/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import providerExtension from "./provider/index.js";
|
|
|
10
10
|
import questionExtension from "./question/index.js";
|
|
11
11
|
import runSummaryExtension from "./run-summary/index.js";
|
|
12
12
|
import searchExtension from "./search/index.js";
|
|
13
|
+
import shellPathsExtension from "./shell-paths/index.js";
|
|
13
14
|
import statuslineExtension from "./statusline/index.js";
|
|
14
15
|
import tasksExtension from "./tasks/index.js";
|
|
15
16
|
import toolPresentationExtension from "./tool-presentation/index.js";
|
|
@@ -30,5 +31,6 @@ export const builtInExtensions = [
|
|
|
30
31
|
{ name: "tool-presentation", factory: toolPresentationExtension, builtin: true },
|
|
31
32
|
{ name: "tasks", factory: tasksExtension, replaceable: true, builtin: true },
|
|
32
33
|
{ name: "search", factory: searchExtension, builtin: true },
|
|
34
|
+
{ name: "shell-paths", factory: shellPathsExtension, builtin: true },
|
|
33
35
|
];
|
|
34
36
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,yBAAyB,MAAM,8BAA8B,CAAC;AACrE,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,mBAAmB,MAAM,wBAAwB,CAAC;AACzD,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,yBAAyB,MAAM,8BAA8B,CAAC;AACrE,OAAO,mBAAmB,MAAM,wBAAwB,CAAC;AAEzD,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IACnD,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IAC5E,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IAClF,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACxE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACvF,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,yBAAyB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACnG,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE;IAC7D,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE;IACrD,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC/D,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC/D,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC/D,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACvF,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE;IACnE,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,yBAAyB,EAAE,OAAO,EAAE,IAAI,EAAE;IAChF,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IAC5E,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,yBAAyB,MAAM,8BAA8B,CAAC;AACrE,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,mBAAmB,MAAM,wBAAwB,CAAC;AACzD,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,mBAAmB,MAAM,wBAAwB,CAAC;AACzD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,yBAAyB,MAAM,8BAA8B,CAAC;AACrE,OAAO,mBAAmB,MAAM,wBAAwB,CAAC;AAEzD,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IACnD,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IAC5E,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IAClF,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACxE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACvF,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,yBAAyB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACnG,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE;IAC7D,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE;IACrD,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC/D,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC/D,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC/D,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IACvF,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE;IACnE,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,yBAAyB,EAAE,OAAO,EAAE,IAAI,EAAE;IAChF,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;IAC5E,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE;IAC3D,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE;CACpE,CAAC","sourcesContent":["import \"./keybindings.ts\";\nimport type { InlineExtension } from \"../core/extensions/types.ts\";\nimport agentExtension from \"./agent/index.ts\";\nimport btwExtension from \"./btw/index.ts\";\nimport cachedCompactionExtension from \"./cached-compaction/index.ts\";\nimport codemodeExtension from \"./codemode/index.ts\";\nimport deepwikiExtension from \"./deepwiki/index.ts\";\nimport llamaExtension from \"./llama/index.ts\";\nimport mcpExtension from \"./mcp/index.ts\";\nimport providerExtension from \"./provider/index.ts\";\nimport questionExtension from \"./question/index.ts\";\nimport runSummaryExtension from \"./run-summary/index.ts\";\nimport searchExtension from \"./search/index.ts\";\nimport shellPathsExtension from \"./shell-paths/index.ts\";\nimport statuslineExtension from \"./statusline/index.ts\";\nimport tasksExtension from \"./tasks/index.ts\";\nimport toolPresentationExtension from \"./tool-presentation/index.ts\";\nimport toolSearchExtension from \"./tool-search/index.ts\";\n\nexport const builtInExtensions: InlineExtension[] = [\n\t{ name: \"Agent\", factory: agentExtension, replaceable: true, builtin: true },\n\t{ name: \"codemode\", factory: codemodeExtension, replaceable: true, builtin: true },\n\t{ name: \"mcp\", factory: mcpExtension, replaceable: true, builtin: true },\n\t{ name: \"tool-search\", factory: toolSearchExtension, replaceable: true, builtin: true },\n\t{ name: \"cached-compaction\", factory: cachedCompactionExtension, replaceable: true, builtin: true },\n\t{ name: \"llama.cpp\", factory: llamaExtension, builtin: true },\n\t{ name: \"btw\", factory: btwExtension, builtin: true },\n\t{ name: \"deepwiki\", factory: deepwikiExtension, builtin: true },\n\t{ name: \"provider\", factory: providerExtension, builtin: true },\n\t{ name: \"question\", factory: questionExtension, builtin: true },\n\t{ name: \"run-summary\", factory: runSummaryExtension, replaceable: true, builtin: true },\n\t{ name: \"statusline\", factory: statuslineExtension, builtin: true },\n\t{ name: \"tool-presentation\", factory: toolPresentationExtension, builtin: true },\n\t{ name: \"tasks\", factory: tasksExtension, replaceable: true, builtin: true },\n\t{ name: \"search\", factory: searchExtension, builtin: true },\n\t{ name: \"shell-paths\", factory: shellPathsExtension, builtin: true },\n];\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "../../core/extensions/types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Bash on Windows reports paths under its own mount table (`/tmp`, `/usr`, ...), which native file
|
|
4
|
+
* tools would otherwise resolve against the current drive root. Rewrite them to the Windows
|
|
5
|
+
* directories bash means. Drive paths such as `/c/...` stay with core path normalization.
|
|
6
|
+
*/
|
|
7
|
+
export default function shellPathsExtension(pi: ExtensionAPI): void;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/shell-paths/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAsDnE;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAuBlE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { posix, win32 } from "node:path";
|
|
3
|
+
import { normalizePath, normalizeWindowsShellPath } from "../../utils/paths.js";
|
|
4
|
+
import { getShellConfig } from "../../utils/shell.js";
|
|
5
|
+
/** Built-in tools whose `path` argument names a file or directory. */
|
|
6
|
+
const PATH_TOOLS = new Set(["read", "write", "edit", "ls", "grep", "find"]);
|
|
7
|
+
/** `<Windows path> on <mount point> type <fs> (<flags>)`, as printed by Git Bash, MSYS2, and Cygwin `mount`. */
|
|
8
|
+
const MOUNT_LINE = /^(.+?) on (\/.*) type \S+ \([^)]*\)$/;
|
|
9
|
+
const MOUNT_TIMEOUT_MS = 5000;
|
|
10
|
+
function parseMounts(output) {
|
|
11
|
+
const mounts = [];
|
|
12
|
+
for (const line of output.split(/\r?\n/)) {
|
|
13
|
+
const match = MOUNT_LINE.exec(line.trim());
|
|
14
|
+
// Native file tools reach only Windows directories, which also skips virtual mounts.
|
|
15
|
+
if (!match || !/^[a-z]:(?:[\\/]|$)/i.test(match[1]))
|
|
16
|
+
continue;
|
|
17
|
+
const [, source, point] = match;
|
|
18
|
+
mounts.push({ point, target: win32.normalize(source.length === 2 ? `${source}\\` : source) });
|
|
19
|
+
}
|
|
20
|
+
// The longest mount point wins, so /tmp is matched before /.
|
|
21
|
+
return mounts.sort((a, b) => b.point.length - a.point.length);
|
|
22
|
+
}
|
|
23
|
+
function readMounts(shellPath) {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
try {
|
|
26
|
+
const shell = getShellConfig(shellPath ? normalizePath(shellPath) : undefined);
|
|
27
|
+
// Legacy WSL bash mounts live inside Linux; core already maps its /mnt/<drive> paths.
|
|
28
|
+
if (shell.commandTransport === "stdin")
|
|
29
|
+
return resolve([]);
|
|
30
|
+
execFile(shell.shell, [...shell.args, "mount"], { timeout: MOUNT_TIMEOUT_MS, windowsHide: true }, (error, stdout) => resolve(error ? [] : parseMounts(stdout)));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
resolve([]);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function toWindowsPath(path, mounts) {
|
|
38
|
+
const normalized = posix.normalize(path);
|
|
39
|
+
const mount = mounts.find(({ point }) => point === "/" || normalized === point || normalized.startsWith(`${point}/`));
|
|
40
|
+
return mount && win32.join(mount.target, posix.relative(mount.point, normalized));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Bash on Windows reports paths under its own mount table (`/tmp`, `/usr`, ...), which native file
|
|
44
|
+
* tools would otherwise resolve against the current drive root. Rewrite them to the Windows
|
|
45
|
+
* directories bash means. Drive paths such as `/c/...` stay with core path normalization.
|
|
46
|
+
*/
|
|
47
|
+
export default function shellPathsExtension(pi) {
|
|
48
|
+
if (process.platform !== "win32")
|
|
49
|
+
return;
|
|
50
|
+
const mountsByShellPath = new Map();
|
|
51
|
+
function getMounts() {
|
|
52
|
+
const shellPath = pi.getSettings().shellPath;
|
|
53
|
+
let mounts = mountsByShellPath.get(shellPath);
|
|
54
|
+
if (!mounts) {
|
|
55
|
+
mounts = readMounts(shellPath);
|
|
56
|
+
mountsByShellPath.set(shellPath, mounts);
|
|
57
|
+
}
|
|
58
|
+
return mounts;
|
|
59
|
+
}
|
|
60
|
+
pi.on("tool_call", async (event) => {
|
|
61
|
+
if (!PATH_TOOLS.has(event.toolName))
|
|
62
|
+
return;
|
|
63
|
+
const input = event.input;
|
|
64
|
+
const path = input.path;
|
|
65
|
+
if (typeof path !== "string" || !path.startsWith("/") || path.startsWith("//"))
|
|
66
|
+
return;
|
|
67
|
+
if (normalizeWindowsShellPath(path) !== path)
|
|
68
|
+
return;
|
|
69
|
+
const windowsPath = toWindowsPath(path, await getMounts());
|
|
70
|
+
if (windowsPath)
|
|
71
|
+
input.path = windowsPath;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/shell-paths/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { posix, win32 } from \"node:path\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { normalizePath, normalizeWindowsShellPath } from \"../../utils/paths.ts\";\nimport { getShellConfig } from \"../../utils/shell.ts\";\n\n/** Built-in tools whose `path` argument names a file or directory. */\nconst PATH_TOOLS = new Set([\"read\", \"write\", \"edit\", \"ls\", \"grep\", \"find\"]);\n/** `<Windows path> on <mount point> type <fs> (<flags>)`, as printed by Git Bash, MSYS2, and Cygwin `mount`. */\nconst MOUNT_LINE = /^(.+?) on (\\/.*) type \\S+ \\([^)]*\\)$/;\nconst MOUNT_TIMEOUT_MS = 5000;\n\ninterface Mount {\n\tpoint: string;\n\ttarget: string;\n}\n\nfunction parseMounts(output: string): Mount[] {\n\tconst mounts: Mount[] = [];\n\tfor (const line of output.split(/\\r?\\n/)) {\n\t\tconst match = MOUNT_LINE.exec(line.trim());\n\t\t// Native file tools reach only Windows directories, which also skips virtual mounts.\n\t\tif (!match || !/^[a-z]:(?:[\\\\/]|$)/i.test(match[1])) continue;\n\t\tconst [, source, point] = match;\n\t\tmounts.push({ point, target: win32.normalize(source.length === 2 ? `${source}\\\\` : source) });\n\t}\n\t// The longest mount point wins, so /tmp is matched before /.\n\treturn mounts.sort((a, b) => b.point.length - a.point.length);\n}\n\nfunction readMounts(shellPath: string | undefined): Promise<Mount[]> {\n\treturn new Promise((resolve) => {\n\t\ttry {\n\t\t\tconst shell = getShellConfig(shellPath ? normalizePath(shellPath) : undefined);\n\t\t\t// Legacy WSL bash mounts live inside Linux; core already maps its /mnt/<drive> paths.\n\t\t\tif (shell.commandTransport === \"stdin\") return resolve([]);\n\t\t\texecFile(\n\t\t\t\tshell.shell,\n\t\t\t\t[...shell.args, \"mount\"],\n\t\t\t\t{ timeout: MOUNT_TIMEOUT_MS, windowsHide: true },\n\t\t\t\t(error, stdout) => resolve(error ? [] : parseMounts(stdout)),\n\t\t\t);\n\t\t} catch {\n\t\t\tresolve([]);\n\t\t}\n\t});\n}\n\nfunction toWindowsPath(path: string, mounts: readonly Mount[]): string | undefined {\n\tconst normalized = posix.normalize(path);\n\tconst mount = mounts.find(\n\t\t({ point }) => point === \"/\" || normalized === point || normalized.startsWith(`${point}/`),\n\t);\n\treturn mount && win32.join(mount.target, posix.relative(mount.point, normalized));\n}\n\n/**\n * Bash on Windows reports paths under its own mount table (`/tmp`, `/usr`, ...), which native file\n * tools would otherwise resolve against the current drive root. Rewrite them to the Windows\n * directories bash means. Drive paths such as `/c/...` stay with core path normalization.\n */\nexport default function shellPathsExtension(pi: ExtensionAPI): void {\n\tif (process.platform !== \"win32\") return;\n\tconst mountsByShellPath = new Map<string | undefined, Promise<Mount[]>>();\n\n\tfunction getMounts(): Promise<Mount[]> {\n\t\tconst shellPath = pi.getSettings().shellPath;\n\t\tlet mounts = mountsByShellPath.get(shellPath);\n\t\tif (!mounts) {\n\t\t\tmounts = readMounts(shellPath);\n\t\t\tmountsByShellPath.set(shellPath, mounts);\n\t\t}\n\t\treturn mounts;\n\t}\n\n\tpi.on(\"tool_call\", async (event) => {\n\t\tif (!PATH_TOOLS.has(event.toolName)) return;\n\t\tconst input = event.input as { path?: unknown };\n\t\tconst path = input.path;\n\t\tif (typeof path !== \"string\" || !path.startsWith(\"/\") || path.startsWith(\"//\")) return;\n\t\tif (normalizeWindowsShellPath(path) !== path) return;\n\t\tconst windowsPath = toWindowsPath(path, await getMounts());\n\t\tif (windowsPath) input.path = windowsPath;\n\t});\n}\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Component, ScrollView, type ScrollViewScrollbar } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { TranscriptLayoutContainer } from "./transcript-layout.ts";
|
|
2
3
|
export interface ChatViewportOptions {
|
|
3
|
-
readonly document:
|
|
4
|
+
readonly document: TranscriptLayoutContainer;
|
|
5
|
+
readonly chat: TranscriptLayoutContainer;
|
|
4
6
|
readonly pendingMessages: Component;
|
|
5
7
|
readonly status: Component;
|
|
6
8
|
readonly editor: Component;
|
|
@@ -10,11 +12,12 @@ export interface ChatViewportOptions {
|
|
|
10
12
|
readonly scrollbar?: ScrollViewScrollbar;
|
|
11
13
|
readonly scrollbarTrackStyle?: (text: string) => string;
|
|
12
14
|
readonly scrollbarThumbStyle?: (text: string) => string;
|
|
15
|
+
readonly outputPad?: () => number;
|
|
13
16
|
}
|
|
14
17
|
export interface ChatViewport {
|
|
15
18
|
readonly root: Component;
|
|
16
19
|
readonly transcript: ScrollView;
|
|
17
20
|
}
|
|
18
|
-
/** Shared fullscreen transcript and fixed input-dock layout. */
|
|
21
|
+
/** Shared fullscreen transcript, sticky prompt, and fixed input-dock layout. */
|
|
19
22
|
export declare function createChatViewport(options: ChatViewportOptions): ChatViewport;
|
|
20
23
|
//# sourceMappingURL=chat-viewport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-viewport.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/chat-viewport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,UAAU,EAAE,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"chat-viewport.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/chat-viewport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,UAAU,EAAE,KAAK,mBAAmB,EAAkB,MAAM,wBAAwB,CAAC;AAO9G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IACzC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CAChC;AA+ED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAiB7E"}
|
|
@@ -1,14 +1,74 @@
|
|
|
1
|
-
import { ScrollView, VStack } from "@earendil-works/pi-tui";
|
|
2
|
-
|
|
1
|
+
import { ScrollView, Spacer, VStack } from "@earendil-works/pi-tui";
|
|
2
|
+
import { findStickyUserMessage, StickyUserMessageComponent, } from "./components/sticky-user-message.js";
|
|
3
|
+
import { UserMessageComponent } from "./components/user-message.js";
|
|
4
|
+
class TranscriptScrollView extends ScrollView {
|
|
5
|
+
updateLayout(contentHeight, viewportHeight, requestRender) {
|
|
6
|
+
super.updateLayout(contentHeight, viewportHeight, requestRender);
|
|
7
|
+
this.onLayout?.(requestRender);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
class ChatTranscriptView {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.padding = new Spacer(1);
|
|
13
|
+
this.paddingTop = 1;
|
|
14
|
+
this.document = options.document;
|
|
15
|
+
this.chat = options.chat;
|
|
16
|
+
this.sticky = new StickyUserMessageComponent(options.outputPad ?? (() => 1), (row) => this.scrollView.scrollTo(row));
|
|
17
|
+
const content = new VStack([this.padding, this.document]);
|
|
18
|
+
this.scrollView = new TranscriptScrollView(content, {
|
|
19
|
+
follow: "end",
|
|
20
|
+
primary: true,
|
|
21
|
+
overscroll: "chain",
|
|
22
|
+
scrollbar: options.scrollbar ?? "auto",
|
|
23
|
+
...(options.scrollbarTrackStyle === undefined ? {} : { scrollbarTrackStyle: options.scrollbarTrackStyle }),
|
|
24
|
+
...(options.scrollbarThumbStyle === undefined ? {} : { scrollbarThumbStyle: options.scrollbarThumbStyle }),
|
|
25
|
+
});
|
|
26
|
+
this.scrollView.onLayout = (requestRender) => {
|
|
27
|
+
if (this.updateStickyMessage())
|
|
28
|
+
requestRender();
|
|
29
|
+
};
|
|
30
|
+
this.root = new VStack([
|
|
31
|
+
{ component: this.sticky, shrink: 0, minSize: 0 },
|
|
32
|
+
{ component: this.scrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
updateStickyMessage() {
|
|
36
|
+
const judgment = this.scrollView.viewportHeight < 2
|
|
37
|
+
? undefined
|
|
38
|
+
: findStickyUserMessage(this.getUserMessageAnchors(), this.scrollView.scrollTop - 1);
|
|
39
|
+
const paddingTop = judgment ? 0 : 1;
|
|
40
|
+
let changed = this.sticky.setJudgment(judgment);
|
|
41
|
+
if (paddingTop !== this.paddingTop) {
|
|
42
|
+
this.paddingTop = paddingTop;
|
|
43
|
+
this.padding.setLines(paddingTop);
|
|
44
|
+
changed = true;
|
|
45
|
+
}
|
|
46
|
+
return changed;
|
|
47
|
+
}
|
|
48
|
+
getUserMessageAnchors() {
|
|
49
|
+
const documentLayout = this.document.getRenderedLayout();
|
|
50
|
+
const chatLayout = this.chat.getRenderedLayout();
|
|
51
|
+
const chatRow = documentLayout?.children.find((child) => child.component === this.chat)?.row;
|
|
52
|
+
if (chatRow === undefined || !chatLayout)
|
|
53
|
+
return [];
|
|
54
|
+
const anchors = [];
|
|
55
|
+
for (const child of chatLayout.children) {
|
|
56
|
+
if (!(child.component instanceof UserMessageComponent))
|
|
57
|
+
continue;
|
|
58
|
+
const summary = child.component.getNavigationSummary();
|
|
59
|
+
if (!summary)
|
|
60
|
+
continue;
|
|
61
|
+
anchors.push({
|
|
62
|
+
row: chatRow + child.row + child.component.getNavigationAnchorOffset(chatLayout.width),
|
|
63
|
+
summary,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return anchors;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Shared fullscreen transcript, sticky prompt, and fixed input-dock layout. */
|
|
3
70
|
export function createChatViewport(options) {
|
|
4
|
-
const
|
|
5
|
-
follow: "end",
|
|
6
|
-
primary: true,
|
|
7
|
-
overscroll: "chain",
|
|
8
|
-
scrollbar: options.scrollbar ?? "auto",
|
|
9
|
-
...(options.scrollbarTrackStyle === undefined ? {} : { scrollbarTrackStyle: options.scrollbarTrackStyle }),
|
|
10
|
-
...(options.scrollbarThumbStyle === undefined ? {} : { scrollbarThumbStyle: options.scrollbarThumbStyle }),
|
|
11
|
-
});
|
|
71
|
+
const transcriptView = new ChatTranscriptView(options);
|
|
12
72
|
const dock = new VStack([
|
|
13
73
|
{ component: options.pendingMessages, shrink: 1, minSize: 0 },
|
|
14
74
|
{ component: options.status, shrink: 1, minSize: 0 },
|
|
@@ -18,9 +78,9 @@ export function createChatViewport(options) {
|
|
|
18
78
|
{ component: options.footer, shrink: 1, minSize: 0 },
|
|
19
79
|
]);
|
|
20
80
|
return {
|
|
21
|
-
transcript,
|
|
81
|
+
transcript: transcriptView.scrollView,
|
|
22
82
|
root: new VStack([
|
|
23
|
-
{ component:
|
|
83
|
+
{ component: transcriptView.root, basis: 0, grow: 1, shrink: 1, minSize: 1 },
|
|
24
84
|
{ component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
|
|
25
85
|
]),
|
|
26
86
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-viewport.js","sourceRoot":"","sources":["../../../src/modes/interactive/chat-viewport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,UAAU,EAA4B,MAAM,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"chat-viewport.js","sourceRoot":"","sources":["../../../src/modes/interactive/chat-viewport.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, ScrollView, type ScrollViewScrollbar, Spacer, VStack } from \"@earendil-works/pi-tui\";\nimport {\n\tfindStickyUserMessage,\n\ttype StickyUserMessageAnchor,\n\tStickyUserMessageComponent,\n} from \"./components/sticky-user-message.ts\";\nimport { UserMessageComponent } from \"./components/user-message.ts\";\nimport type { TranscriptLayoutContainer } from \"./transcript-layout.ts\";\n\nexport interface ChatViewportOptions {\n\treadonly document: TranscriptLayoutContainer;\n\treadonly chat: TranscriptLayoutContainer;\n\treadonly pendingMessages: Component;\n\treadonly status: Component;\n\treadonly editor: Component;\n\treadonly footer: Component;\n\treadonly widgetsAbove?: Component;\n\treadonly widgetsBelow?: Component;\n\treadonly scrollbar?: ScrollViewScrollbar;\n\treadonly scrollbarTrackStyle?: (text: string) => string;\n\treadonly scrollbarThumbStyle?: (text: string) => string;\n\treadonly outputPad?: () => number;\n}\n\nexport interface ChatViewport {\n\treadonly root: Component;\n\treadonly transcript: ScrollView;\n}\n\nclass TranscriptScrollView extends ScrollView {\n\tonLayout: ((requestRender: () => void) => void) | undefined;\n\n\toverride updateLayout(contentHeight: number, viewportHeight: number, requestRender: () => void): void {\n\t\tsuper.updateLayout(contentHeight, viewportHeight, requestRender);\n\t\tthis.onLayout?.(requestRender);\n\t}\n}\n\nclass ChatTranscriptView {\n\treadonly root: VStack;\n\treadonly scrollView: TranscriptScrollView;\n\tprivate readonly document: TranscriptLayoutContainer;\n\tprivate readonly chat: TranscriptLayoutContainer;\n\tprivate readonly padding = new Spacer(1);\n\tprivate readonly sticky: StickyUserMessageComponent;\n\tprivate paddingTop = 1;\n\n\tconstructor(options: ChatViewportOptions) {\n\t\tthis.document = options.document;\n\t\tthis.chat = options.chat;\n\t\tthis.sticky = new StickyUserMessageComponent(options.outputPad ?? (() => 1), (row) =>\n\t\t\tthis.scrollView.scrollTo(row),\n\t\t);\n\t\tconst content = new VStack([this.padding, this.document]);\n\t\tthis.scrollView = new TranscriptScrollView(content, {\n\t\t\tfollow: \"end\",\n\t\t\tprimary: true,\n\t\t\toverscroll: \"chain\",\n\t\t\tscrollbar: options.scrollbar ?? \"auto\",\n\t\t\t...(options.scrollbarTrackStyle === undefined ? {} : { scrollbarTrackStyle: options.scrollbarTrackStyle }),\n\t\t\t...(options.scrollbarThumbStyle === undefined ? {} : { scrollbarThumbStyle: options.scrollbarThumbStyle }),\n\t\t});\n\t\tthis.scrollView.onLayout = (requestRender) => {\n\t\t\tif (this.updateStickyMessage()) requestRender();\n\t\t};\n\t\tthis.root = new VStack([\n\t\t\t{ component: this.sticky, shrink: 0, minSize: 0 },\n\t\t\t{ component: this.scrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },\n\t\t]);\n\t}\n\n\tprivate updateStickyMessage(): boolean {\n\t\tconst judgment =\n\t\t\tthis.scrollView.viewportHeight < 2\n\t\t\t\t? undefined\n\t\t\t\t: findStickyUserMessage(this.getUserMessageAnchors(), this.scrollView.scrollTop - 1);\n\t\tconst paddingTop = judgment ? 0 : 1;\n\t\tlet changed = this.sticky.setJudgment(judgment);\n\t\tif (paddingTop !== this.paddingTop) {\n\t\t\tthis.paddingTop = paddingTop;\n\t\t\tthis.padding.setLines(paddingTop);\n\t\t\tchanged = true;\n\t\t}\n\t\treturn changed;\n\t}\n\n\tprivate getUserMessageAnchors(): StickyUserMessageAnchor[] {\n\t\tconst documentLayout = this.document.getRenderedLayout();\n\t\tconst chatLayout = this.chat.getRenderedLayout();\n\t\tconst chatRow = documentLayout?.children.find((child) => child.component === this.chat)?.row;\n\t\tif (chatRow === undefined || !chatLayout) return [];\n\n\t\tconst anchors: StickyUserMessageAnchor[] = [];\n\t\tfor (const child of chatLayout.children) {\n\t\t\tif (!(child.component instanceof UserMessageComponent)) continue;\n\t\t\tconst summary = child.component.getNavigationSummary();\n\t\t\tif (!summary) continue;\n\t\t\tanchors.push({\n\t\t\t\trow: chatRow + child.row + child.component.getNavigationAnchorOffset(chatLayout.width),\n\t\t\t\tsummary,\n\t\t\t});\n\t\t}\n\t\treturn anchors;\n\t}\n}\n\n/** Shared fullscreen transcript, sticky prompt, and fixed input-dock layout. */\nexport function createChatViewport(options: ChatViewportOptions): ChatViewport {\n\tconst transcriptView = new ChatTranscriptView(options);\n\tconst dock = new VStack([\n\t\t{ component: options.pendingMessages, shrink: 1, minSize: 0 },\n\t\t{ component: options.status, shrink: 1, minSize: 0 },\n\t\t...(options.widgetsAbove === undefined ? [] : [{ component: options.widgetsAbove, shrink: 1, minSize: 0 }]),\n\t\t{ component: options.editor, shrink: 1, minSize: 3 },\n\t\t...(options.widgetsBelow === undefined ? [] : [{ component: options.widgetsBelow, shrink: 1, minSize: 0 }]),\n\t\t{ component: options.footer, shrink: 1, minSize: 0 },\n\t]);\n\treturn {\n\t\ttranscript: transcriptView.scrollView,\n\t\troot: new VStack([\n\t\t\t{ component: transcriptView.root, basis: 0, grow: 1, shrink: 1, minSize: 1 },\n\t\t\t{ component: dock, basis: \"auto\", grow: 0, shrink: 1, minSize: 1 },\n\t\t]),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
2
|
+
export interface StickyUserMessageAnchor {
|
|
3
|
+
/** Zero-based row of the first visible text line, excluding viewport padding. */
|
|
4
|
+
readonly row: number;
|
|
5
|
+
readonly summary: string;
|
|
6
|
+
}
|
|
7
|
+
export interface StickyUserMessageJudgment {
|
|
8
|
+
readonly summary: string;
|
|
9
|
+
readonly targetRow: number;
|
|
10
|
+
}
|
|
11
|
+
/** Select the nearest user message whose first line is above the viewport body. */
|
|
12
|
+
export declare function findStickyUserMessage(anchors: readonly StickyUserMessageAnchor[], bodyTop: number): StickyUserMessageJudgment | undefined;
|
|
13
|
+
export declare class StickyUserMessageComponent implements Component {
|
|
14
|
+
private judgment;
|
|
15
|
+
private readonly getOutputPad;
|
|
16
|
+
private readonly scrollTo;
|
|
17
|
+
constructor(getOutputPad: () => number, scrollTo: (row: number) => void);
|
|
18
|
+
setJudgment(judgment: StickyUserMessageJudgment | undefined): boolean;
|
|
19
|
+
invalidate(): void;
|
|
20
|
+
render(width: number): string[];
|
|
21
|
+
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=sticky-user-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sticky-user-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/sticky-user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EAEd,KAAK,aAAa,EAClB,KAAK,mBAAmB,EAGxB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,WAAW,uBAAuB;IACvC,iFAAiF;IACjF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,SAAS,uBAAuB,EAAE,EAC3C,OAAO,EAAE,MAAM,GACb,yBAAyB,GAAG,SAAS,CAavC;AAED,qBAAa,0BAA2B,YAAW,SAAS;IAC3D,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;gBAErC,YAAY,EAAE,MAAM,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI;IAKvE,WAAW,CAAC,QAAQ,EAAE,yBAAyB,GAAG,SAAS,GAAG,OAAO;IAQrE,UAAU,IAAI,IAAI;IAElB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAe/B,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,mBAAmB,GAAG,SAAS;CAelE"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { stripTerminalSequences, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
2
|
+
import { theme } from "../theme/theme.js";
|
|
3
|
+
/** Select the nearest user message whose first line is above the viewport body. */
|
|
4
|
+
export function findStickyUserMessage(anchors, bodyTop) {
|
|
5
|
+
let low = 0;
|
|
6
|
+
let high = anchors.length;
|
|
7
|
+
while (low < high) {
|
|
8
|
+
const middle = low + Math.floor((high - low) / 2);
|
|
9
|
+
if (anchors[middle].row < bodyTop)
|
|
10
|
+
low = middle + 1;
|
|
11
|
+
else
|
|
12
|
+
high = middle;
|
|
13
|
+
}
|
|
14
|
+
// Landing exactly on any message's first line shows the original, not a duplicate pill.
|
|
15
|
+
if (anchors[low]?.row === bodyTop || low === 0)
|
|
16
|
+
return undefined;
|
|
17
|
+
const anchor = anchors[low - 1];
|
|
18
|
+
return { summary: anchor.summary, targetRow: anchor.row + 1 };
|
|
19
|
+
}
|
|
20
|
+
export class StickyUserMessageComponent {
|
|
21
|
+
constructor(getOutputPad, scrollTo) {
|
|
22
|
+
this.getOutputPad = getOutputPad;
|
|
23
|
+
this.scrollTo = scrollTo;
|
|
24
|
+
}
|
|
25
|
+
setJudgment(judgment) {
|
|
26
|
+
if (this.judgment?.summary === judgment?.summary && this.judgment?.targetRow === judgment?.targetRow) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
this.judgment = judgment;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
invalidate() { }
|
|
33
|
+
render(width) {
|
|
34
|
+
if (!this.judgment || width <= 0)
|
|
35
|
+
return [];
|
|
36
|
+
const padding = Math.max(0, Math.floor(this.getOutputPad()));
|
|
37
|
+
// Truncate plain text before styling: the reset that truncation inserts would end the background mid-row.
|
|
38
|
+
const summaryWidth = Math.max(0, width - padding * 2 - visibleWidth("↑ "));
|
|
39
|
+
const summary = stripTerminalSequences(truncateToWidth(this.judgment.summary, summaryWidth, "…"));
|
|
40
|
+
const line = truncateToWidth(`${" ".repeat(padding)}${theme.fg("dim", "↑")} ${theme.fg("muted", summary)}`, width, "");
|
|
41
|
+
const fill = " ".repeat(Math.max(0, width - visibleWidth(line)));
|
|
42
|
+
return [theme.bg("userMessageBg", line + fill)];
|
|
43
|
+
}
|
|
44
|
+
handleMouse(event) {
|
|
45
|
+
if (event.type !== "click" ||
|
|
46
|
+
event.button !== "left" ||
|
|
47
|
+
event.shift ||
|
|
48
|
+
event.alt ||
|
|
49
|
+
event.ctrl ||
|
|
50
|
+
(event.clickCount ?? 1) !== 1 ||
|
|
51
|
+
!this.judgment) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
this.scrollTo(this.judgment.targetRow);
|
|
55
|
+
return { handled: true };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=sticky-user-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sticky-user-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/sticky-user-message.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Component,\n\tstripTerminalSequences,\n\ttype TuiMouseEvent,\n\ttype TuiMouseEventResult,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.ts\";\n\nexport interface StickyUserMessageAnchor {\n\t/** Zero-based row of the first visible text line, excluding viewport padding. */\n\treadonly row: number;\n\treadonly summary: string;\n}\n\nexport interface StickyUserMessageJudgment {\n\treadonly summary: string;\n\treadonly targetRow: number;\n}\n\n/** Select the nearest user message whose first line is above the viewport body. */\nexport function findStickyUserMessage(\n\tanchors: readonly StickyUserMessageAnchor[],\n\tbodyTop: number,\n): StickyUserMessageJudgment | undefined {\n\tlet low = 0;\n\tlet high = anchors.length;\n\twhile (low < high) {\n\t\tconst middle = low + Math.floor((high - low) / 2);\n\t\tif (anchors[middle]!.row < bodyTop) low = middle + 1;\n\t\telse high = middle;\n\t}\n\n\t// Landing exactly on any message's first line shows the original, not a duplicate pill.\n\tif (anchors[low]?.row === bodyTop || low === 0) return undefined;\n\tconst anchor = anchors[low - 1]!;\n\treturn { summary: anchor.summary, targetRow: anchor.row + 1 };\n}\n\nexport class StickyUserMessageComponent implements Component {\n\tprivate judgment: StickyUserMessageJudgment | undefined;\n\tprivate readonly getOutputPad: () => number;\n\tprivate readonly scrollTo: (row: number) => void;\n\n\tconstructor(getOutputPad: () => number, scrollTo: (row: number) => void) {\n\t\tthis.getOutputPad = getOutputPad;\n\t\tthis.scrollTo = scrollTo;\n\t}\n\n\tsetJudgment(judgment: StickyUserMessageJudgment | undefined): boolean {\n\t\tif (this.judgment?.summary === judgment?.summary && this.judgment?.targetRow === judgment?.targetRow) {\n\t\t\treturn false;\n\t\t}\n\t\tthis.judgment = judgment;\n\t\treturn true;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tif (!this.judgment || width <= 0) return [];\n\t\tconst padding = Math.max(0, Math.floor(this.getOutputPad()));\n\t\t// Truncate plain text before styling: the reset that truncation inserts would end the background mid-row.\n\t\tconst summaryWidth = Math.max(0, width - padding * 2 - visibleWidth(\"↑ \"));\n\t\tconst summary = stripTerminalSequences(truncateToWidth(this.judgment.summary, summaryWidth, \"…\"));\n\t\tconst line = truncateToWidth(\n\t\t\t`${\" \".repeat(padding)}${theme.fg(\"dim\", \"↑\")} ${theme.fg(\"muted\", summary)}`,\n\t\t\twidth,\n\t\t\t\"\",\n\t\t);\n\t\tconst fill = \" \".repeat(Math.max(0, width - visibleWidth(line)));\n\t\treturn [theme.bg(\"userMessageBg\", line + fill)];\n\t}\n\n\thandleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {\n\t\tif (\n\t\t\tevent.type !== \"click\" ||\n\t\t\tevent.button !== \"left\" ||\n\t\t\tevent.shift ||\n\t\t\tevent.alt ||\n\t\t\tevent.ctrl ||\n\t\t\t(event.clickCount ?? 1) !== 1 ||\n\t\t\t!this.judgment\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.scrollTo(this.judgment.targetRow);\n\t\treturn { handled: true };\n\t}\n}\n"]}
|
|
@@ -8,8 +8,17 @@ export declare class UserMessageComponent extends Container {
|
|
|
8
8
|
private markdownTheme;
|
|
9
9
|
private outputPad;
|
|
10
10
|
private markdownTransformers;
|
|
11
|
+
private readonly navigationAnchorOffsets;
|
|
12
|
+
private markdown;
|
|
13
|
+
private rendered;
|
|
11
14
|
constructor(text: string, markdownTheme?: MarkdownTheme, outputPad?: number, markdownTransformers?: readonly MarkdownTransformer[]);
|
|
15
|
+
getNavigationSummary(): string;
|
|
16
|
+
/** First visible text row within this component, after any leading blank lines. */
|
|
17
|
+
getNavigationAnchorOffset(width: number): number;
|
|
12
18
|
setOutputPad(padding: number): void;
|
|
19
|
+
invalidate(): void;
|
|
20
|
+
/** User messages take no mouse input; skip Container's per-event measurement, which renders at the wrong width. */
|
|
21
|
+
handleMouse(): undefined;
|
|
13
22
|
private rebuild;
|
|
14
23
|
render(width: number): string[];
|
|
15
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"user-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,KAAK,aAAa,EAA0B,MAAM,wBAAwB,CAAC;AACzG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAY7E;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;IAClD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,oBAAoB,CAAiC;IAC7D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA6B;IACrE,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,QAAQ,CAEH;gBAGZ,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,aAAkC,EACjD,SAAS,SAAI,EACb,oBAAoB,GAAE,SAAS,mBAAmB,EAAO;IAU1D,oBAAoB,IAAI,MAAM;IAI9B,mFAAmF;IACnF,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAShD,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK1B,UAAU,IAAI,IAAI;IAM3B,mHAAmH;IAC1G,WAAW,IAAI,SAAS;IAIjC,OAAO,CAAC,OAAO;IAyBN,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAoBxC"}
|
|
@@ -1,45 +1,86 @@
|
|
|
1
|
-
import { Container, Markdown } from "@earendil-works/pi-tui";
|
|
1
|
+
import { Container, Markdown, stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
3
|
import { createMarkdownTransform } from "./markdown-transform.js";
|
|
4
4
|
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
5
5
|
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
6
6
|
const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
7
|
+
/** Gutter glyph before the first row. Same width as the fullscreen sticky prompt's `↑`, so their text columns align. */
|
|
8
|
+
const PROMPT_MARKER = "❯";
|
|
9
|
+
const GUTTER_WIDTH = 2;
|
|
7
10
|
/**
|
|
8
11
|
* Component that renders a user message
|
|
9
12
|
*/
|
|
10
13
|
export class UserMessageComponent extends Container {
|
|
11
14
|
constructor(text, markdownTheme = getMarkdownTheme(), outputPad = 1, markdownTransformers = []) {
|
|
12
15
|
super();
|
|
16
|
+
this.navigationAnchorOffsets = new Map();
|
|
13
17
|
this.text = text;
|
|
14
18
|
this.markdownTheme = markdownTheme;
|
|
15
19
|
this.outputPad = outputPad;
|
|
16
20
|
this.markdownTransformers = markdownTransformers;
|
|
17
21
|
this.rebuild();
|
|
18
22
|
}
|
|
23
|
+
getNavigationSummary() {
|
|
24
|
+
return this.text.replaceAll(/\s+/g, " ").trim();
|
|
25
|
+
}
|
|
26
|
+
/** First visible text row within this component, after any leading blank lines. */
|
|
27
|
+
getNavigationAnchorOffset(width) {
|
|
28
|
+
const cached = this.navigationAnchorOffsets.get(width);
|
|
29
|
+
if (cached !== undefined)
|
|
30
|
+
return cached;
|
|
31
|
+
const offset = this.render(width).findIndex((line) => stripTerminalSequences(line).trim().length > 0);
|
|
32
|
+
const anchorOffset = offset < 0 ? 0 : offset;
|
|
33
|
+
this.navigationAnchorOffsets.set(width, anchorOffset);
|
|
34
|
+
return anchorOffset;
|
|
35
|
+
}
|
|
19
36
|
setOutputPad(padding) {
|
|
20
37
|
this.outputPad = padding;
|
|
21
38
|
this.rebuild();
|
|
22
39
|
}
|
|
40
|
+
invalidate() {
|
|
41
|
+
super.invalidate();
|
|
42
|
+
this.navigationAnchorOffsets.clear();
|
|
43
|
+
this.rendered = undefined;
|
|
44
|
+
}
|
|
45
|
+
/** User messages take no mouse input; skip Container's per-event measurement, which renders at the wrong width. */
|
|
46
|
+
handleMouse() {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
23
49
|
rebuild() {
|
|
50
|
+
this.navigationAnchorOffsets.clear();
|
|
51
|
+
this.rendered = undefined;
|
|
24
52
|
this.clear();
|
|
25
|
-
// The Markdown
|
|
26
|
-
//
|
|
27
|
-
|
|
53
|
+
// The Markdown colors its own background; render() adds the gutter and side padding. No vertical padding: the
|
|
54
|
+
// fullscreen sticky prompt replaces the first text row in place, so a padded block would leave a stray background
|
|
55
|
+
// row beside the pinned summary.
|
|
56
|
+
this.markdown = new Markdown(this.text, 0, 0, this.markdownTheme, {
|
|
28
57
|
color: (content) => theme.fg("userMessageText", content),
|
|
29
58
|
bgColor: (content) => theme.bg("userMessageBg", content),
|
|
30
59
|
}, {
|
|
31
60
|
preserveOrderedListMarkers: true,
|
|
32
61
|
preserveBackslashEscapes: true,
|
|
33
62
|
transform: createMarkdownTransform("user", false, this.markdownTransformers),
|
|
34
|
-
})
|
|
63
|
+
});
|
|
64
|
+
this.addChild(this.markdown);
|
|
35
65
|
}
|
|
36
66
|
render(width) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
const padding = Math.max(0, Math.floor(this.outputPad));
|
|
68
|
+
const source = this.markdown.render(Math.max(1, width - padding * 2 - GUTTER_WIDTH));
|
|
69
|
+
if (this.rendered?.width === width && this.rendered.source === source)
|
|
70
|
+
return this.rendered.lines;
|
|
71
|
+
if (source.length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
const fill = (text) => theme.bg("userMessageBg", text);
|
|
74
|
+
const indent = " ".repeat(padding);
|
|
75
|
+
const marker = fill(`${indent}${theme.fg("muted", PROMPT_MARKER)} `);
|
|
76
|
+
const hanging = fill(" ".repeat(padding + GUTTER_WIDTH));
|
|
77
|
+
const trailing = padding > 0 ? fill(indent) : "";
|
|
78
|
+
const lines = source.map((line, index) => `${index === 0 ? marker : hanging}${line}${trailing}`);
|
|
79
|
+
// Markers stay at line starts. Prefix the end markers first so a single-row message still begins with the prompt
|
|
80
|
+
// start marker that terminal and alt-screen prompt navigation look for.
|
|
42
81
|
lines[lines.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + lines[lines.length - 1];
|
|
82
|
+
lines[0] = OSC133_ZONE_START + lines[0];
|
|
83
|
+
this.rendered = { width, source, lines };
|
|
43
84
|
return lines;
|
|
44
85
|
}
|
|
45
86
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAsB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"user-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/user-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container, Markdown, type MarkdownTheme, stripTerminalSequences } from \"@earendil-works/pi-tui\";\nimport type { MarkdownTransformer } from \"../../../core/extensions/types.ts\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.ts\";\nimport { createMarkdownTransform } from \"./markdown-transform.ts\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\n/** Gutter glyph before the first row. Same width as the fullscreen sticky prompt's `↑`, so their text columns align. */\nconst PROMPT_MARKER = \"❯\";\nconst GUTTER_WIDTH = 2;\n\n/**\n * Component that renders a user message\n */\nexport class UserMessageComponent extends Container {\n\tprivate text: string;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate outputPad: number;\n\tprivate markdownTransformers: readonly MarkdownTransformer[];\n\tprivate readonly navigationAnchorOffsets = new Map<number, number>();\n\tprivate markdown!: Markdown;\n\tprivate rendered:\n\t\t| { readonly width: number; readonly source: readonly string[]; readonly lines: string[] }\n\t\t| undefined;\n\n\tconstructor(\n\t\ttext: string,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\toutputPad = 1,\n\t\tmarkdownTransformers: readonly MarkdownTransformer[] = [],\n\t) {\n\t\tsuper();\n\t\tthis.text = text;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.outputPad = outputPad;\n\t\tthis.markdownTransformers = markdownTransformers;\n\t\tthis.rebuild();\n\t}\n\n\tgetNavigationSummary(): string {\n\t\treturn this.text.replaceAll(/\\s+/g, \" \").trim();\n\t}\n\n\t/** First visible text row within this component, after any leading blank lines. */\n\tgetNavigationAnchorOffset(width: number): number {\n\t\tconst cached = this.navigationAnchorOffsets.get(width);\n\t\tif (cached !== undefined) return cached;\n\t\tconst offset = this.render(width).findIndex((line) => stripTerminalSequences(line).trim().length > 0);\n\t\tconst anchorOffset = offset < 0 ? 0 : offset;\n\t\tthis.navigationAnchorOffsets.set(width, anchorOffset);\n\t\treturn anchorOffset;\n\t}\n\n\tsetOutputPad(padding: number): void {\n\t\tthis.outputPad = padding;\n\t\tthis.rebuild();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.navigationAnchorOffsets.clear();\n\t\tthis.rendered = undefined;\n\t}\n\n\t/** User messages take no mouse input; skip Container's per-event measurement, which renders at the wrong width. */\n\toverride handleMouse(): undefined {\n\t\treturn undefined;\n\t}\n\n\tprivate rebuild(): void {\n\t\tthis.navigationAnchorOffsets.clear();\n\t\tthis.rendered = undefined;\n\t\tthis.clear();\n\t\t// The Markdown colors its own background; render() adds the gutter and side padding. No vertical padding: the\n\t\t// fullscreen sticky prompt replaces the first text row in place, so a padded block would leave a stray background\n\t\t// row beside the pinned summary.\n\t\tthis.markdown = new Markdown(\n\t\t\tthis.text,\n\t\t\t0,\n\t\t\t0,\n\t\t\tthis.markdownTheme,\n\t\t\t{\n\t\t\t\tcolor: (content: string) => theme.fg(\"userMessageText\", content),\n\t\t\t\tbgColor: (content: string) => theme.bg(\"userMessageBg\", content),\n\t\t\t},\n\t\t\t{\n\t\t\t\tpreserveOrderedListMarkers: true,\n\t\t\t\tpreserveBackslashEscapes: true,\n\t\t\t\ttransform: createMarkdownTransform(\"user\", false, this.markdownTransformers),\n\t\t\t},\n\t\t);\n\t\tthis.addChild(this.markdown);\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst padding = Math.max(0, Math.floor(this.outputPad));\n\t\tconst source = this.markdown.render(Math.max(1, width - padding * 2 - GUTTER_WIDTH));\n\t\tif (this.rendered?.width === width && this.rendered.source === source) return this.rendered.lines;\n\t\tif (source.length === 0) return [];\n\n\t\tconst fill = (text: string) => theme.bg(\"userMessageBg\", text);\n\t\tconst indent = \" \".repeat(padding);\n\t\tconst marker = fill(`${indent}${theme.fg(\"muted\", PROMPT_MARKER)} `);\n\t\tconst hanging = fill(\" \".repeat(padding + GUTTER_WIDTH));\n\t\tconst trailing = padding > 0 ? fill(indent) : \"\";\n\t\tconst lines = source.map((line, index) => `${index === 0 ? marker : hanging}${line}${trailing}`);\n\n\t\t// Markers stay at line starts. Prefix the end markers first so a single-row message still begins with the prompt\n\t\t// start marker that terminal and alt-screen prompt navigation look for.\n\t\tlines[lines.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + lines[lines.length - 1];\n\t\tlines[0] = OSC133_ZONE_START + lines[0];\n\t\tthis.rendered = { width, source, lines };\n\t\treturn lines;\n\t}\n}\n"]}
|