@linxiraos/pi-utils 1.1.7 → 1.1.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 +2 -8
- package/README.md +18 -18
- package/package.json +57 -63
- package/src/acp/connection.ts +6 -10
- package/src/ar/entries.ts +1 -1
- package/src/ar/sevenzip.ts +17 -2
- package/src/async.ts +25 -4
- package/src/browsers.ts +11 -5
- package/src/dirs.ts +15 -10
- package/src/dom/core.ts +4 -4
- package/src/env.ts +12 -0
- package/src/fetch-retry.ts +36 -1
- package/src/format.ts +1 -1
- package/src/frontmatter.ts +1 -0
- package/src/incoming-json.ts +856 -0
- package/src/index.ts +2 -0
- package/src/json-lexer.ts +413 -0
- package/src/json-parse.ts +93 -350
- package/src/marked/core.ts +8 -1
- package/src/materialize-string.ts +10 -0
- package/src/path.ts +14 -0
- package/src/postmortem.ts +33 -6
- package/src/prompt.ts +1 -0
- package/src/ring.ts +1 -0
- package/src/temp.ts +16 -4
- package/src/template.ts +2 -2
- package/src/tls-fetch.ts +2 -16
- package/src/turndown/html.ts +1 -1
- package/src/vterm/query-responder.ts +76 -0
- package/src/vterm.ts +1 -0
- package/THIRD-PARTY-NOTICES.txt +0 -22909
- package/dist/types/abortable.d.ts +0 -32
- package/dist/types/acp/connection.d.ts +0 -118
- package/dist/types/acp/protocol.d.ts +0 -526
- package/dist/types/acp/schema.d.ts +0 -41
- package/dist/types/acp/stream.d.ts +0 -8
- package/dist/types/acp/transport.d.ts +0 -88
- package/dist/types/acp.d.ts +0 -6
- package/dist/types/ar/arj.d.ts +0 -5
- package/dist/types/ar/asar.d.ts +0 -7
- package/dist/types/ar/bytes.d.ts +0 -20
- package/dist/types/ar/cab.d.ts +0 -5
- package/dist/types/ar/checksums.d.ts +0 -10
- package/dist/types/ar/codecs/bzip2.d.ts +0 -4
- package/dist/types/ar/codecs/gzip.d.ts +0 -6
- package/dist/types/ar/codecs/lzma.d.ts +0 -6
- package/dist/types/ar/codecs/lzw.d.ts +0 -4
- package/dist/types/ar/codecs/lzx.d.ts +0 -7
- package/dist/types/ar/codecs/xz.d.ts +0 -4
- package/dist/types/ar/codecs/zstd.d.ts +0 -6
- package/dist/types/ar/cpio.d.ts +0 -7
- package/dist/types/ar/deb.d.ts +0 -5
- package/dist/types/ar/entries.d.ts +0 -22
- package/dist/types/ar/error.d.ts +0 -8
- package/dist/types/ar/index.d.ts +0 -11
- package/dist/types/ar/iso.d.ts +0 -5
- package/dist/types/ar/limits.d.ts +0 -33
- package/dist/types/ar/lzh.d.ts +0 -7
- package/dist/types/ar/open.d.ts +0 -45
- package/dist/types/ar/paths.d.ts +0 -18
- package/dist/types/ar/rar/rar4-decoder.d.ts +0 -6
- package/dist/types/ar/rar/rar5-decoder.d.ts +0 -6
- package/dist/types/ar/rar.d.ts +0 -5
- package/dist/types/ar/reader.d.ts +0 -28
- package/dist/types/ar/registry.d.ts +0 -17
- package/dist/types/ar/rpm.d.ts +0 -5
- package/dist/types/ar/sevenzip/decode.d.ts +0 -33
- package/dist/types/ar/sevenzip.d.ts +0 -5
- package/dist/types/ar/source.d.ts +0 -54
- package/dist/types/ar/tar.d.ts +0 -9
- package/dist/types/ar/types.d.ts +0 -99
- package/dist/types/ar/unix-ar.d.ts +0 -7
- package/dist/types/ar/write.d.ts +0 -10
- package/dist/types/ar/zip.d.ts +0 -10
- package/dist/types/async.d.ts +0 -22
- package/dist/types/binary.d.ts +0 -23
- package/dist/types/browsers.d.ts +0 -68
- package/dist/types/chalk.d.ts +0 -125
- package/dist/types/cli.d.ts +0 -134
- package/dist/types/color.d.ts +0 -136
- package/dist/types/dates.d.ts +0 -7
- package/dist/types/dirs.d.ts +0 -325
- package/dist/types/docx/converter.d.ts +0 -46
- package/dist/types/docx/xml.d.ts +0 -26
- package/dist/types/docx.d.ts +0 -11
- package/dist/types/dom/core.d.ts +0 -431
- package/dist/types/dom/parser.d.ts +0 -7
- package/dist/types/dom/selector.d.ts +0 -5
- package/dist/types/dom.d.ts +0 -5
- package/dist/types/env.d.ts +0 -124
- package/dist/types/fetch-retry.d.ts +0 -96
- package/dist/types/file-lock.d.ts +0 -23
- package/dist/types/format.d.ts +0 -37
- package/dist/types/frontmatter.d.ts +0 -46
- package/dist/types/fs-error.d.ts +0 -31
- package/dist/types/glob.d.ts +0 -28
- package/dist/types/headers.d.ts +0 -34
- package/dist/types/index.d.ts +0 -41
- package/dist/types/json-parse.d.ts +0 -83
- package/dist/types/json.d.ts +0 -20
- package/dist/types/logger/rotating-file.d.ts +0 -18
- package/dist/types/logger.d.ts +0 -96
- package/dist/types/loop-phase.d.ts +0 -10
- package/dist/types/lru.d.ts +0 -46
- package/dist/types/marked/core.d.ts +0 -445
- package/dist/types/marked.d.ts +0 -2
- package/dist/types/math-delimiters.d.ts +0 -45
- package/dist/types/mermaid-ascii.d.ts +0 -11
- package/dist/types/mime.d.ts +0 -29
- package/dist/types/module-timer.d.ts +0 -1
- package/dist/types/path-tree.d.ts +0 -76
- package/dist/types/path.d.ts +0 -2
- package/dist/types/peek-file.d.ts +0 -29
- package/dist/types/postmortem.d.ts +0 -174
- package/dist/types/process-name.d.ts +0 -7
- package/dist/types/procmgr.d.ts +0 -74
- package/dist/types/prompt.d.ts +0 -18
- package/dist/types/ptree.d.ts +0 -124
- package/dist/types/readability/readability.d.ts +0 -9
- package/dist/types/readability/readerable.d.ts +0 -10
- package/dist/types/readability/types.d.ts +0 -70
- package/dist/types/readability.d.ts +0 -4
- package/dist/types/ring.d.ts +0 -93
- package/dist/types/runtime-install.d.ts +0 -85
- package/dist/types/sanitize-text.d.ts +0 -29
- package/dist/types/snowflake.d.ts +0 -25
- package/dist/types/sqlite.d.ts +0 -26
- package/dist/types/stderr-guard.d.ts +0 -22
- package/dist/types/stream.d.ts +0 -75
- package/dist/types/tab-spacing.d.ts +0 -24
- package/dist/types/temp.d.ts +0 -17
- package/dist/types/template.d.ts +0 -62
- package/dist/types/timing-buffer.d.ts +0 -22
- package/dist/types/tls-fetch.d.ts +0 -37
- package/dist/types/turndown/gfm.d.ts +0 -11
- package/dist/types/turndown/html.d.ts +0 -5
- package/dist/types/turndown/service.d.ts +0 -21
- package/dist/types/turndown/types.d.ts +0 -70
- package/dist/types/turndown.d.ts +0 -4
- package/dist/types/type-guards.d.ts +0 -5
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +0 -41
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +0 -89
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +0 -12
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +0 -66
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +0 -48
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +0 -43
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +0 -56
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +0 -65
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +0 -27
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +0 -34
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +0 -26
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +0 -31
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +0 -59
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +0 -30
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +0 -55
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +0 -206
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +0 -51
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +0 -2
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +0 -102
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +0 -76
- package/dist/types/vendor/mermaid-ascii/index.d.ts +0 -1
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +0 -9
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +0 -130
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +0 -23
- package/dist/types/vendor/mermaid-ascii/types.d.ts +0 -114
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +0 -25
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +0 -145
- package/dist/types/version.d.ts +0 -18
- package/dist/types/vterm/buffer.d.ts +0 -99
- package/dist/types/vterm/terminal.d.ts +0 -44
- package/dist/types/vterm.d.ts +0 -8
- package/dist/types/which.d.ts +0 -37
- package/dist/types/worker-host.d.ts +0 -51
- package/dist/types/xml.d.ts +0 -31
package/src/json-parse.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
BACKSLASH,
|
|
3
|
+
COLON,
|
|
4
|
+
COMMA,
|
|
5
|
+
isHexDigit,
|
|
6
|
+
isNumberStart,
|
|
7
|
+
isWhitespace,
|
|
8
|
+
JsonLexer,
|
|
9
|
+
LBRACE,
|
|
10
|
+
LBRACKET,
|
|
11
|
+
QUOTE,
|
|
12
|
+
RBRACE,
|
|
13
|
+
RBRACKET,
|
|
14
|
+
SQUOTE,
|
|
15
|
+
VALID_ESCAPE_CHAR,
|
|
16
|
+
} from "./json-lexer";
|
|
5
17
|
|
|
6
|
-
|
|
7
|
-
const VALID_ESCAPE_CHAR = new Uint8Array(128);
|
|
8
|
-
for (const ch of '"\\/bfnrtu') VALID_ESCAPE_CHAR[ch.charCodeAt(0)] = 1;
|
|
18
|
+
const U = 0x75;
|
|
9
19
|
|
|
10
20
|
const CONTROL_ESCAPES: readonly string[] = (() => {
|
|
11
21
|
const e: string[] = [];
|
|
@@ -20,47 +30,6 @@ const CONTROL_ESCAPES: readonly string[] = (() => {
|
|
|
20
30
|
return e;
|
|
21
31
|
})();
|
|
22
32
|
|
|
23
|
-
const HEX4_RE = /^[0-9a-fA-F]{4}$/;
|
|
24
|
-
|
|
25
|
-
function isHexDigit(cp: number): boolean {
|
|
26
|
-
return (cp >= 0x30 && cp <= 0x39) || ((cp | 0x20) >= 0x61 && (cp | 0x20) <= 0x66);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isWhitespace(cp: number): boolean {
|
|
30
|
-
return cp === 0x20 || cp === 0x09 || cp === 0x0a || cp === 0x0d;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isIdentChar(cp: number): boolean {
|
|
34
|
-
return (
|
|
35
|
-
(cp >= 0x30 && cp <= 0x39) ||
|
|
36
|
-
((cp | 0x20) >= 0x61 && (cp | 0x20) <= 0x7a) ||
|
|
37
|
-
cp === 0x5f /* _ */ ||
|
|
38
|
-
cp === 0x24 /* $ */
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Bareword literals: standard JSON plus Python `True`/`False`/`None`. */
|
|
43
|
-
const KEYWORDS: readonly (readonly [string, unknown])[] = [
|
|
44
|
-
["true", true],
|
|
45
|
-
["false", false],
|
|
46
|
-
["null", null],
|
|
47
|
-
["True", true],
|
|
48
|
-
["False", false],
|
|
49
|
-
["None", null],
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* JS-only atoms never recovered as bareword strings — a tool must not execute
|
|
54
|
-
* with a non-finite or undefined argument masquerading as a string.
|
|
55
|
-
*/
|
|
56
|
-
const NON_RECOVERABLE_BAREWORDS: Record<string, true> = {
|
|
57
|
-
NaN: true,
|
|
58
|
-
Infinity: true,
|
|
59
|
-
"-Infinity": true,
|
|
60
|
-
"+Infinity": true,
|
|
61
|
-
undefined: true,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
33
|
/**
|
|
65
34
|
* Sentinel returned by partial-mode value parsing when an atomic value
|
|
66
35
|
* (number / keyword) is incomplete at the streaming edge, so the enclosing
|
|
@@ -160,139 +129,109 @@ export function repairJson(json: string): string {
|
|
|
160
129
|
}
|
|
161
130
|
|
|
162
131
|
/**
|
|
163
|
-
* Recursive-descent
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* - single-quoted strings and unquoted object keys (JSON5);
|
|
168
|
-
* - trailing / stray commas, and `//` + block comments;
|
|
169
|
-
* - Python literals `True` / `False` / `None` and JS `NaN` / `Infinity`;
|
|
170
|
-
* - raw control characters and invalid `\x` escapes inside strings (kept literally);
|
|
171
|
-
* - unescaped quotes inside strings — a quote only closes a string when followed
|
|
172
|
-
* by a value terminator, recovering apostrophes such as `'it's'`;
|
|
173
|
-
* - unquoted string values in object/array value position (strict mode only) —
|
|
174
|
-
* an unrecognized bareword such as `{"paths": packages/foo/*}` is recovered as
|
|
175
|
-
* a string up to the next `,` / `}` / `]` / newline.
|
|
132
|
+
* Recursive-descent tree builder over the tolerant {@link JsonLexer} grammar
|
|
133
|
+
* (single quotes, unquoted keys, comments, Python literals, stray commas,
|
|
134
|
+
* literal invalid escapes, apostrophe recovery, bareword values).
|
|
176
135
|
*
|
|
177
|
-
* In `partial` mode an unterminated string/object/array
|
|
178
|
-
* end-of-input) is auto-closed with whatever was parsed
|
|
179
|
-
*
|
|
180
|
-
*
|
|
136
|
+
* In `partial` mode (lexer mode `streaming`) an unterminated string/object/array
|
|
137
|
+
* (or a value cut off at end-of-input) is auto-closed with whatever was parsed
|
|
138
|
+
* so far, and an incomplete trailing atom rolls the enclosing container back
|
|
139
|
+
* to its last valid prefix. In strict mode, end-of-input mid-value and trailing
|
|
140
|
+
* garbage both throw, so a final parse never silently accepts a half-formed
|
|
141
|
+
* tool call.
|
|
181
142
|
*/
|
|
182
143
|
class RelaxedJson {
|
|
183
|
-
readonly #
|
|
184
|
-
readonly #n: number;
|
|
144
|
+
readonly #lex: JsonLexer;
|
|
185
145
|
readonly #partial: boolean;
|
|
186
|
-
#i = 0;
|
|
187
146
|
|
|
188
147
|
constructor(source: string, partial: boolean) {
|
|
189
|
-
this.#
|
|
190
|
-
this.#n = source.length;
|
|
148
|
+
this.#lex = new JsonLexer(source, partial ? "streaming" : "strict");
|
|
191
149
|
this.#partial = partial;
|
|
192
150
|
}
|
|
193
151
|
|
|
194
152
|
parse(): unknown {
|
|
195
|
-
this.#
|
|
196
|
-
|
|
153
|
+
const lex = this.#lex;
|
|
154
|
+
lex.ws();
|
|
155
|
+
if (lex.atEnd) {
|
|
197
156
|
if (this.#partial) return undefined;
|
|
198
157
|
throw new SyntaxError("Unexpected end of JSON input");
|
|
199
158
|
}
|
|
200
159
|
const value = this.#value(false);
|
|
201
160
|
if (value === INCOMPLETE) return undefined;
|
|
202
|
-
|
|
203
|
-
if (!this.#partial &&
|
|
204
|
-
throw new SyntaxError(`Unexpected trailing characters at position ${
|
|
161
|
+
lex.ws();
|
|
162
|
+
if (!this.#partial && !lex.atEnd) {
|
|
163
|
+
throw new SyntaxError(`Unexpected trailing characters at position ${lex.pos}`);
|
|
205
164
|
}
|
|
206
165
|
return value;
|
|
207
166
|
}
|
|
208
167
|
|
|
209
|
-
#ws(): void {
|
|
210
|
-
const s = this.#s;
|
|
211
|
-
for (;;) {
|
|
212
|
-
while (this.#i < this.#n && isWhitespace(s.charCodeAt(this.#i))) this.#i++;
|
|
213
|
-
if (this.#i + 1 < this.#n && s.charCodeAt(this.#i) === 0x2f /* / */) {
|
|
214
|
-
const next = s.charCodeAt(this.#i + 1);
|
|
215
|
-
if (next === 0x2f /* / line comment */) {
|
|
216
|
-
this.#i += 2;
|
|
217
|
-
while (this.#i < this.#n && s.charCodeAt(this.#i) !== 0x0a) this.#i++;
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (next === 0x2a /* * block comment */) {
|
|
221
|
-
this.#i += 2;
|
|
222
|
-
while (
|
|
223
|
-
this.#i + 1 < this.#n &&
|
|
224
|
-
!(s.charCodeAt(this.#i) === 0x2a && s.charCodeAt(this.#i + 1) === 0x2f)
|
|
225
|
-
) {
|
|
226
|
-
this.#i++;
|
|
227
|
-
}
|
|
228
|
-
this.#i = Math.min(this.#i + 2, this.#n);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
168
|
#value(allowBareword: boolean): unknown {
|
|
237
|
-
const
|
|
238
|
-
const c =
|
|
239
|
-
if (c ===
|
|
240
|
-
if (c ===
|
|
241
|
-
if (c ===
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
169
|
+
const lex = this.#lex;
|
|
170
|
+
const c = lex.peek();
|
|
171
|
+
if (c === LBRACE) return this.#object();
|
|
172
|
+
if (c === LBRACKET) return this.#array();
|
|
173
|
+
if (c === QUOTE || c === SQUOTE) return lex.string(c).value;
|
|
174
|
+
// JS-only NaN / Infinity are deliberately not accepted: a tool must not
|
|
175
|
+
// execute with a non-finite numeric arg; they fail the lexer's finite
|
|
176
|
+
// guard (strict throw / partial rollback) like other bad tokens.
|
|
177
|
+
if (isNumberStart(c)) return lex.number() ?? INCOMPLETE;
|
|
178
|
+
const keyword = lex.keyword();
|
|
179
|
+
if (keyword !== undefined) return keyword;
|
|
180
|
+
if (this.#partial) {
|
|
181
|
+
// Incomplete / unrecognized atomic token at the streaming edge — signal the
|
|
182
|
+
// caller to roll back to the last valid prefix instead of committing junk.
|
|
183
|
+
lex.pos = lex.src.length;
|
|
184
|
+
return INCOMPLETE;
|
|
248
185
|
}
|
|
249
|
-
|
|
186
|
+
if (allowBareword) return lex.bareword();
|
|
187
|
+
throw new SyntaxError(`Unexpected token at position ${lex.pos}`);
|
|
250
188
|
}
|
|
251
189
|
|
|
252
190
|
#object(): Record<string, unknown> {
|
|
253
|
-
|
|
191
|
+
const lex = this.#lex;
|
|
192
|
+
lex.pos++; // consume {
|
|
254
193
|
const out: Record<string, unknown> = {};
|
|
255
194
|
for (;;) {
|
|
256
|
-
|
|
257
|
-
if (
|
|
195
|
+
lex.ws();
|
|
196
|
+
if (lex.atEnd) {
|
|
258
197
|
if (this.#partial) return out;
|
|
259
198
|
throw new SyntaxError("Unterminated object");
|
|
260
199
|
}
|
|
261
|
-
const c =
|
|
262
|
-
if (c ===
|
|
263
|
-
|
|
200
|
+
const c = lex.peek();
|
|
201
|
+
if (c === RBRACE) {
|
|
202
|
+
lex.pos++;
|
|
264
203
|
return out;
|
|
265
204
|
}
|
|
266
|
-
if (c ===
|
|
205
|
+
if (c === COMMA) {
|
|
267
206
|
// Tolerate leading / doubled / trailing commas.
|
|
268
|
-
|
|
207
|
+
lex.pos++;
|
|
269
208
|
continue;
|
|
270
209
|
}
|
|
271
210
|
const key = this.#key();
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
|
|
211
|
+
lex.ws();
|
|
212
|
+
if (lex.peek() === COLON) {
|
|
213
|
+
lex.pos++;
|
|
275
214
|
} else if (this.#partial) {
|
|
276
215
|
return out;
|
|
277
216
|
} else {
|
|
278
217
|
throw new SyntaxError("Expected ':' in object");
|
|
279
218
|
}
|
|
280
|
-
|
|
281
|
-
if (
|
|
219
|
+
lex.ws();
|
|
220
|
+
if (lex.atEnd) {
|
|
282
221
|
if (this.#partial) return out;
|
|
283
222
|
throw new SyntaxError("Expected value after ':'");
|
|
284
223
|
}
|
|
285
224
|
const value = this.#value(true);
|
|
286
225
|
if (value === INCOMPLETE) return out;
|
|
287
226
|
out[key] = value;
|
|
288
|
-
|
|
289
|
-
const d =
|
|
290
|
-
if (d ===
|
|
291
|
-
|
|
227
|
+
lex.ws();
|
|
228
|
+
const d = lex.peek();
|
|
229
|
+
if (d === COMMA) {
|
|
230
|
+
lex.pos++;
|
|
292
231
|
continue;
|
|
293
232
|
}
|
|
294
|
-
if (d ===
|
|
295
|
-
|
|
233
|
+
if (d === RBRACE) {
|
|
234
|
+
lex.pos++;
|
|
296
235
|
return out;
|
|
297
236
|
}
|
|
298
237
|
if (this.#partial) return out;
|
|
@@ -301,34 +240,35 @@ class RelaxedJson {
|
|
|
301
240
|
}
|
|
302
241
|
|
|
303
242
|
#array(): unknown[] {
|
|
304
|
-
|
|
243
|
+
const lex = this.#lex;
|
|
244
|
+
lex.pos++; // consume [
|
|
305
245
|
const out: unknown[] = [];
|
|
306
246
|
for (;;) {
|
|
307
|
-
|
|
308
|
-
if (
|
|
247
|
+
lex.ws();
|
|
248
|
+
if (lex.atEnd) {
|
|
309
249
|
if (this.#partial) return out;
|
|
310
250
|
throw new SyntaxError("Unterminated array");
|
|
311
251
|
}
|
|
312
|
-
const c =
|
|
313
|
-
if (c ===
|
|
314
|
-
|
|
252
|
+
const c = lex.peek();
|
|
253
|
+
if (c === RBRACKET) {
|
|
254
|
+
lex.pos++;
|
|
315
255
|
return out;
|
|
316
256
|
}
|
|
317
|
-
if (c ===
|
|
318
|
-
|
|
257
|
+
if (c === COMMA) {
|
|
258
|
+
lex.pos++;
|
|
319
259
|
continue;
|
|
320
260
|
}
|
|
321
261
|
const value = this.#value(true);
|
|
322
262
|
if (value === INCOMPLETE) return out;
|
|
323
263
|
out.push(value);
|
|
324
|
-
|
|
325
|
-
const d =
|
|
326
|
-
if (d ===
|
|
327
|
-
|
|
264
|
+
lex.ws();
|
|
265
|
+
const d = lex.peek();
|
|
266
|
+
if (d === COMMA) {
|
|
267
|
+
lex.pos++;
|
|
328
268
|
continue;
|
|
329
269
|
}
|
|
330
|
-
if (d ===
|
|
331
|
-
|
|
270
|
+
if (d === RBRACKET) {
|
|
271
|
+
lex.pos++;
|
|
332
272
|
return out;
|
|
333
273
|
}
|
|
334
274
|
if (this.#partial) return out;
|
|
@@ -337,209 +277,12 @@ class RelaxedJson {
|
|
|
337
277
|
}
|
|
338
278
|
|
|
339
279
|
#key(): string {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (ch === ":" || ch === "," || ch === "}" || isWhitespace(this.#s.charCodeAt(this.#i))) break;
|
|
347
|
-
this.#i++;
|
|
348
|
-
}
|
|
349
|
-
if (this.#i === start) {
|
|
350
|
-
if (this.#partial) return "";
|
|
351
|
-
throw new SyntaxError("Expected object key");
|
|
352
|
-
}
|
|
353
|
-
return this.#s.slice(start, this.#i);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
#string(quote: number): string {
|
|
357
|
-
const s = this.#s;
|
|
358
|
-
const n = this.#n;
|
|
359
|
-
let i = this.#i + 1; // skip opening quote
|
|
360
|
-
let out = "";
|
|
361
|
-
let runStart = i;
|
|
362
|
-
while (i < n) {
|
|
363
|
-
const cc = s.charCodeAt(i);
|
|
364
|
-
if (cc !== BACKSLASH && cc !== quote) {
|
|
365
|
-
i++;
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
if (cc === quote) {
|
|
369
|
-
// Apostrophe / inner-quote recovery (a quote that isn't followed by a
|
|
370
|
-
// value terminator is literal) is safe for single quotes and in partial
|
|
371
|
-
// mode. For double quotes in strict mode, close on the first unescaped
|
|
372
|
-
// quote like standard JSON so malformed structure fails loudly instead
|
|
373
|
-
// of silently swallowing commas/colons into one string.
|
|
374
|
-
const lenient = quote === SQUOTE || this.#partial;
|
|
375
|
-
if (!lenient || this.#closesString(i + 1)) {
|
|
376
|
-
out += s.slice(runStart, i);
|
|
377
|
-
this.#i = i + 1;
|
|
378
|
-
return out;
|
|
379
|
-
}
|
|
380
|
-
// Unescaped inner quote (e.g. apostrophe in `'it's'`) — keep it literal.
|
|
381
|
-
i++;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
// Backslash escape.
|
|
385
|
-
out += s.slice(runStart, i);
|
|
386
|
-
i++;
|
|
387
|
-
if (i >= n) {
|
|
388
|
-
out += "\\";
|
|
389
|
-
runStart = i;
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
const esc = s.charCodeAt(i);
|
|
393
|
-
switch (esc) {
|
|
394
|
-
case QUOTE:
|
|
395
|
-
out += '"';
|
|
396
|
-
break;
|
|
397
|
-
case SQUOTE:
|
|
398
|
-
out += "'";
|
|
399
|
-
break;
|
|
400
|
-
case BACKSLASH:
|
|
401
|
-
out += "\\";
|
|
402
|
-
break;
|
|
403
|
-
case 0x2f:
|
|
404
|
-
out += "/";
|
|
405
|
-
break;
|
|
406
|
-
case 0x62:
|
|
407
|
-
out += "\b";
|
|
408
|
-
break;
|
|
409
|
-
case 0x66:
|
|
410
|
-
out += "\f";
|
|
411
|
-
break;
|
|
412
|
-
case 0x6e:
|
|
413
|
-
out += "\n";
|
|
414
|
-
break;
|
|
415
|
-
case 0x72:
|
|
416
|
-
out += "\r";
|
|
417
|
-
break;
|
|
418
|
-
case 0x74:
|
|
419
|
-
out += "\t";
|
|
420
|
-
break;
|
|
421
|
-
case U: {
|
|
422
|
-
const hex = s.slice(i + 1, i + 5);
|
|
423
|
-
if (HEX4_RE.test(hex)) {
|
|
424
|
-
out += String.fromCharCode(parseInt(hex, 16));
|
|
425
|
-
i += 4;
|
|
426
|
-
} else {
|
|
427
|
-
out += "\\u"; // invalid \u — keep literal
|
|
428
|
-
}
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
default:
|
|
432
|
-
out += `\\${s[i]}`; // invalid escape — keep backslash literal
|
|
433
|
-
}
|
|
434
|
-
i++;
|
|
435
|
-
runStart = i;
|
|
436
|
-
}
|
|
437
|
-
out += s.slice(runStart, i);
|
|
438
|
-
if (this.#partial) {
|
|
439
|
-
this.#i = i;
|
|
440
|
-
return out;
|
|
441
|
-
}
|
|
442
|
-
throw new SyntaxError("Unterminated string");
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/** A quote closes a string only when the next non-space char ends a value. */
|
|
446
|
-
#closesString(from: number): boolean {
|
|
447
|
-
const s = this.#s;
|
|
448
|
-
let k = from;
|
|
449
|
-
while (k < this.#n && isWhitespace(s.charCodeAt(k))) k++;
|
|
450
|
-
if (k >= this.#n) return true;
|
|
451
|
-
const c = s[k];
|
|
452
|
-
return c === "," || c === "}" || c === "]" || c === ":";
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
#number(): unknown {
|
|
456
|
-
const s = this.#s;
|
|
457
|
-
const start = this.#i;
|
|
458
|
-
while (this.#i < this.#n) {
|
|
459
|
-
const ch = s[this.#i];
|
|
460
|
-
if (
|
|
461
|
-
(ch >= "0" && ch <= "9") ||
|
|
462
|
-
ch === "-" ||
|
|
463
|
-
ch === "+" ||
|
|
464
|
-
ch === "." ||
|
|
465
|
-
ch === "e" ||
|
|
466
|
-
ch === "E" ||
|
|
467
|
-
ch === "x" ||
|
|
468
|
-
ch === "X" ||
|
|
469
|
-
(ch >= "a" && ch <= "f") ||
|
|
470
|
-
(ch >= "A" && ch <= "F")
|
|
471
|
-
) {
|
|
472
|
-
this.#i++;
|
|
473
|
-
} else {
|
|
474
|
-
break;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
const token = s.slice(start, this.#i);
|
|
478
|
-
const num = Number(token);
|
|
479
|
-
if (Number.isNaN(num)) {
|
|
480
|
-
if (this.#partial) return INCOMPLETE;
|
|
481
|
-
throw new SyntaxError(`Invalid number: ${token}`);
|
|
482
|
-
}
|
|
483
|
-
return num;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
#keyword(allowBareword: boolean): unknown {
|
|
487
|
-
const s = this.#s;
|
|
488
|
-
const i = this.#i;
|
|
489
|
-
for (const [word, value] of KEYWORDS) {
|
|
490
|
-
// Require a non-identifier boundary so `Truex` / `nullish` are not misread
|
|
491
|
-
// as the keyword followed by junk.
|
|
492
|
-
if (s.startsWith(word, i) && !isIdentChar(s.charCodeAt(i + word.length))) {
|
|
493
|
-
this.#i += word.length;
|
|
494
|
-
return value;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (this.#partial) {
|
|
498
|
-
// Incomplete / unrecognized atomic token at the streaming edge — signal the
|
|
499
|
-
// caller to roll back to the last valid prefix instead of committing junk.
|
|
500
|
-
this.#i = this.#n;
|
|
501
|
-
return INCOMPLETE;
|
|
502
|
-
}
|
|
503
|
-
if (allowBareword) return this.#bareword();
|
|
504
|
-
throw new SyntaxError(`Unexpected token at position ${this.#i}`);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Strict-mode recovery of an unquoted string value, e.g.
|
|
509
|
-
* `{"paths": packages/foo/*}`: consume until `,` / `}` / `]` / newline and
|
|
510
|
-
* trim trailing whitespace. Recovery still throws — so a final parse never
|
|
511
|
-
* accepts a half-formed or non-finite argument — when the token:
|
|
512
|
-
* - hits end-of-input before a delimiter (truncated value);
|
|
513
|
-
* - contains a `"`, `{`, `[`, or a key-like `:` — this parser accepts
|
|
514
|
-
* unquoted keys, so a missed comma (`{"a": foo "b": 1}`, `{a: foo b: 1}`)
|
|
515
|
-
* would otherwise silently swallow the following field. A colon followed
|
|
516
|
-
* by `/` or `\` stays literal so URL and Windows-path values recover;
|
|
517
|
-
* - is a non-finite atom ({@link NON_RECOVERABLE_BAREWORDS}).
|
|
518
|
-
*/
|
|
519
|
-
#bareword(): string {
|
|
520
|
-
const s = this.#s;
|
|
521
|
-
const start = this.#i;
|
|
522
|
-
let i = start;
|
|
523
|
-
while (i < this.#n) {
|
|
524
|
-
const cc = s.charCodeAt(i);
|
|
525
|
-
if (cc === 0x2c /* , */ || cc === 0x7d /* } */ || cc === 0x5d /* ] */ || cc === 0x0a || cc === 0x0d) break;
|
|
526
|
-
if (
|
|
527
|
-
cc === QUOTE ||
|
|
528
|
-
cc === 0x7b /* { */ ||
|
|
529
|
-
cc === 0x5b /* [ */ ||
|
|
530
|
-
(cc === 0x3a /* : */ && s.charCodeAt(i + 1) !== 0x2f /* / */ && s.charCodeAt(i + 1) !== 0x5c) /* \ */
|
|
531
|
-
) {
|
|
532
|
-
throw new SyntaxError(`Unexpected token at position ${start}`);
|
|
533
|
-
}
|
|
534
|
-
i++;
|
|
535
|
-
}
|
|
536
|
-
if (i >= this.#n) throw new SyntaxError(`Unexpected token at position ${start}`);
|
|
537
|
-
let end = i;
|
|
538
|
-
while (end > start && isWhitespace(s.charCodeAt(end - 1))) end--;
|
|
539
|
-
const word = s.slice(start, end);
|
|
540
|
-
if (NON_RECOVERABLE_BAREWORDS[word]) throw new SyntaxError(`Unexpected token at position ${start}`);
|
|
541
|
-
this.#i = i;
|
|
542
|
-
return word;
|
|
280
|
+
const lex = this.#lex;
|
|
281
|
+
const c = lex.peek();
|
|
282
|
+
if (c === QUOTE || c === SQUOTE) return lex.string(c).value;
|
|
283
|
+
const key = lex.unquotedKey();
|
|
284
|
+
if (key.length === 0 && !this.#partial) throw new SyntaxError("Expected object key");
|
|
285
|
+
return key;
|
|
543
286
|
}
|
|
544
287
|
}
|
|
545
288
|
|
package/src/marked/core.ts
CHANGED
|
@@ -343,8 +343,15 @@ const DEFAULTS: MarkedOptions = {
|
|
|
343
343
|
};
|
|
344
344
|
const PUNCTUATION = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/;
|
|
345
345
|
|
|
346
|
-
function tokenList(links: Links =
|
|
346
|
+
function tokenList(links: Links = Object.create(null)): TokensList {
|
|
347
347
|
const list = [] as unknown as TokensList;
|
|
348
|
+
// The reference-definition map is keyed by user-controlled labels. A plain
|
|
349
|
+
// `{}` inherits `Object.prototype`, so a reference-style link whose label is
|
|
350
|
+
// an inherited member (`[x][constructor]`, `[x][__proto__]`) resolves to a
|
|
351
|
+
// truthy non-definition and yields a link token with `href: undefined`
|
|
352
|
+
// (issue #10283). A null-prototype map makes such lookups miss, so the link
|
|
353
|
+
// correctly falls back to literal text, and label writes cannot pollute the
|
|
354
|
+
// prototype.
|
|
348
355
|
list.links = links;
|
|
349
356
|
return list;
|
|
350
357
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy text into independent UTF-16 backing storage before a bounded substring outlives its source.
|
|
3
|
+
*
|
|
4
|
+
* The UTF-16 round trip preserves every JavaScript code unit, including lone
|
|
5
|
+
* surrogates that a UTF-8 round trip would replace.
|
|
6
|
+
*/
|
|
7
|
+
export function materializeString(text: string): string {
|
|
8
|
+
if (text.length === 0) return "";
|
|
9
|
+
return Buffer.from(text, "utf16le").toString("utf16le");
|
|
10
|
+
}
|
package/src/path.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
|
|
3
|
+
const WINDOWS_DRIVE_PATH = /^([A-Za-z]):[\\/](.*)$/;
|
|
4
|
+
|
|
5
|
+
/** Maps an absolute Windows drive path to the drive's default WSL mount. */
|
|
6
|
+
export function windowsPathToWslMount(filePath: string): string | undefined {
|
|
7
|
+
const normalized = path.win32.normalize(filePath.trim());
|
|
8
|
+
const match = WINDOWS_DRIVE_PATH.exec(normalized);
|
|
9
|
+
if (!match) return undefined;
|
|
10
|
+
const [, drive, rest] = match;
|
|
11
|
+
const segments = rest.split("\\").filter(Boolean);
|
|
12
|
+
return path.posix.join("/mnt", drive!.toLowerCase(), ...segments);
|
|
13
|
+
}
|
|
14
|
+
|
|
1
15
|
const WINDOWS_DRIVE_EXTENDED_PREFIX = /^\\\\[?]\\([A-Za-z]:[\\/].*)$/;
|
|
2
16
|
const WINDOWS_UNC_EXTENDED_PREFIX = /^\\\\[?]\\UNC[\\/]([^\\/]+)[\\/](.+)$/i;
|
|
3
17
|
const WINDOWS_DRIVE_EXTENDED_FORWARD_PREFIX = /^\/\/[?]\/([A-Za-z]:\/.*)$/;
|
package/src/postmortem.ts
CHANGED
|
@@ -222,7 +222,7 @@ export function isInternalSocketClosedError(err: unknown): boolean {
|
|
|
222
222
|
const internal = frames.every(frame => {
|
|
223
223
|
const trimmed = frame.trim();
|
|
224
224
|
if (trimmed === "" || trimmed === "at unknown" || trimmed === "at native") return true;
|
|
225
|
-
if (!/\(node:[^)]*\)$/.test(trimmed) &&
|
|
225
|
+
if (!/\(node:[^)]*\)$/.test(trimmed) && !trimmed.startsWith("at node:")) return false;
|
|
226
226
|
hasNetFrame ||= trimmed.includes("node:net:");
|
|
227
227
|
return true;
|
|
228
228
|
});
|
|
@@ -395,23 +395,40 @@ function formatFatalError(label: string, err: Error): string {
|
|
|
395
395
|
return `\n[${label}] ${name}: ${message}${formattedStack}\n`;
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
async function exitAfterFatal(
|
|
398
|
+
async function exitAfterFatal(output: string, logMessage: string, err: Error, reason: Reason): Promise<never> {
|
|
399
399
|
const forcedExit = setTimeout(() => exitProcess(1), CLEANUP_DEADLINE_MS);
|
|
400
400
|
try {
|
|
401
|
+
// Cleanup callbacks are invoked synchronously before runCleanup returns its
|
|
402
|
+
// completion promise. TUI owners therefore hand the cursor back before the
|
|
403
|
+
// fatal report is written, while slower resource cleanup continues afterward.
|
|
404
|
+
const cleanup = runCleanup(reason);
|
|
401
405
|
restoreTerminalStderr();
|
|
402
406
|
// A revoked terminal can make stream writes raise another fatal error. Use
|
|
403
407
|
// the descriptor directly so failure stays synchronous and contained.
|
|
404
408
|
try {
|
|
405
|
-
fs.writeSync(2,
|
|
409
|
+
fs.writeSync(2, output);
|
|
406
410
|
} catch {}
|
|
407
411
|
logger.error(logMessage, { err });
|
|
408
|
-
await
|
|
412
|
+
await cleanup;
|
|
409
413
|
} finally {
|
|
410
414
|
clearTimeout(forcedExit);
|
|
411
415
|
exitProcess(1);
|
|
412
416
|
}
|
|
413
417
|
}
|
|
414
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Reports a caught top-level failure after terminal owners restore their display, then exits.
|
|
421
|
+
*/
|
|
422
|
+
export async function fatal(error: unknown): Promise<never> {
|
|
423
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
424
|
+
const output = `${Bun.inspect(error, { colors: process.stderr.isTTY === true })}\n${formatFatalRecoveryHints()}`;
|
|
425
|
+
if (!isMainThread) {
|
|
426
|
+
process.stderr.write(output);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
return exitAfterFatal(output, "Fatal error", err, Reason.UNHANDLED_REJECTION);
|
|
430
|
+
}
|
|
431
|
+
|
|
415
432
|
if (isMainThread) {
|
|
416
433
|
process
|
|
417
434
|
.on("SIGINT", async () => {
|
|
@@ -461,7 +478,12 @@ if (isMainThread) {
|
|
|
461
478
|
});
|
|
462
479
|
return;
|
|
463
480
|
}
|
|
464
|
-
await exitAfterFatal(
|
|
481
|
+
await exitAfterFatal(
|
|
482
|
+
`${formatFatalError("Uncaught Exception", err)}${formatFatalRecoveryHints()}`,
|
|
483
|
+
"Uncaught exception",
|
|
484
|
+
err,
|
|
485
|
+
Reason.UNCAUGHT_EXCEPTION,
|
|
486
|
+
);
|
|
465
487
|
})
|
|
466
488
|
.on("unhandledRejection", async reason => {
|
|
467
489
|
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
@@ -497,7 +519,12 @@ if (isMainThread) {
|
|
|
497
519
|
});
|
|
498
520
|
}
|
|
499
521
|
}
|
|
500
|
-
await exitAfterFatal(
|
|
522
|
+
await exitAfterFatal(
|
|
523
|
+
`${formatFatalError("Unhandled Rejection", err)}${formatFatalRecoveryHints()}`,
|
|
524
|
+
"Unhandled rejection",
|
|
525
|
+
err,
|
|
526
|
+
Reason.UNHANDLED_REJECTION,
|
|
527
|
+
);
|
|
501
528
|
})
|
|
502
529
|
.on("exit", async () => {
|
|
503
530
|
void runCleanup(Reason.EXIT); // fire and forget (exit imminent)
|
package/src/prompt.ts
CHANGED
|
@@ -196,6 +196,7 @@ export function format(content: string, options: PromptFormatOptions = {}): stri
|
|
|
196
196
|
} = options;
|
|
197
197
|
const isPreRender = renderPhase === "pre-render";
|
|
198
198
|
const lines = content.split("\n");
|
|
199
|
+
// [suppressed] length preallocation
|
|
199
200
|
const result: string[] = new Array(lines.length);
|
|
200
201
|
let n = 0; // logical length of `result` (pops are n--)
|
|
201
202
|
let inCodeBlock = false;
|
package/src/ring.ts
CHANGED
|
@@ -15,6 +15,7 @@ export class RingBuffer<T> {
|
|
|
15
15
|
* @param capacity - The maximum number of elements the buffer can hold. Must be positive.
|
|
16
16
|
*/
|
|
17
17
|
constructor(public readonly capacity: number) {
|
|
18
|
+
// [suppressed] length preallocation
|
|
18
19
|
this.#buf = new Array(capacity);
|
|
19
20
|
}
|
|
20
21
|
|