@fitchmultz/pi-subagents 0.50.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -2
- package/README.md +36 -5
- package/dist/agent/child-runtime.js +80 -0
- package/dist/agent/contracts.js +15 -24
- package/dist/agent/host.js +3 -16
- package/dist/agent/index.js +212 -192
- package/dist/agent/journal-tail.js +115 -156
- package/dist/agent/launch.js +20 -8
- package/dist/agent/legacy.js +65 -23
- package/dist/agent/notifications.js +138 -47
- package/dist/agent/observation-state.js +246 -0
- package/dist/agent/observation-view.js +258 -0
- package/dist/agent/observation.js +350 -0
- package/dist/agent/observe-card.js +243 -0
- package/dist/agent/observe-chrome.js +85 -0
- package/dist/agent/observe-client.js +285 -0
- package/dist/agent/observe-layout.js +65 -0
- package/dist/agent/observe-reading.js +84 -0
- package/dist/agent/observe-screen.js +400 -96
- package/dist/agent/observe-transcript.js +382 -0
- package/dist/agent/observe.js +250 -43
- package/dist/agent/parking.js +253 -0
- package/dist/agent/process.js +228 -28
- package/dist/agent/receipts.js +92 -20
- package/dist/agent/runner.js +229 -126
- package/dist/agent/shell.js +2 -1
- package/dist/agent/storage.js +47 -12
- package/dist/agent/tool-schemas.js +20 -0
- package/dist/agent/typebox.js +10 -5
- package/dist/agent/usage.js +0 -115
- package/dist/agent/view.js +46 -16
- package/dist/agent/wake.js +99 -0
- package/dist/build-info.js +1 -1
- package/dist/intercom-client.d.ts +26 -0
- package/dist/intercom-client.js +45 -0
- package/dist/pi-intercom/agent-dir.d.ts +1 -0
- package/dist/pi-intercom/bridge-session.js +2 -1
- package/dist/pi-intercom/broker/broker.js +62 -43
- package/dist/pi-intercom/broker/client-last.d.ts +24 -0
- package/dist/pi-intercom/broker/client-last.js +42 -5
- package/dist/pi-intercom/broker/client-park.d.ts +9 -0
- package/dist/pi-intercom/broker/client-park.js +21 -0
- package/dist/pi-intercom/broker/client.d.ts +59 -0
- package/dist/pi-intercom/broker/client.js +55 -29
- package/dist/pi-intercom/broker/connection.d.ts +29 -0
- package/dist/pi-intercom/broker/framing.d.ts +17 -0
- package/dist/pi-intercom/broker/framing.js +10 -7
- package/dist/pi-intercom/broker/guard.js +23 -0
- package/dist/pi-intercom/broker/last.d.ts +30 -0
- package/dist/pi-intercom/broker/last.js +87 -40
- package/dist/pi-intercom/broker/parked.js +107 -0
- package/dist/pi-intercom/broker/paths.d.ts +13 -0
- package/dist/pi-intercom/broker/paths.js +35 -12
- package/dist/pi-intercom/broker/pid.js +5 -1
- package/dist/pi-intercom/broker/server-connection.js +20 -1
- package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
- package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
- package/dist/pi-intercom/broker/targets.d.ts +9 -0
- package/dist/pi-intercom/connection.js +71 -20
- package/dist/pi-intercom/identity.js +6 -5
- package/dist/pi-intercom/inbound-delivery.js +16 -60
- package/dist/pi-intercom/inbound-journal.js +83 -6
- package/dist/pi-intercom/inbound-record.js +18 -1
- package/dist/pi-intercom/inbound.js +22 -37
- package/dist/pi-intercom/index.js +41 -0
- package/dist/pi-intercom/last-message.js +4 -16
- package/dist/pi-intercom/lifecycle.js +88 -3
- package/dist/pi-intercom/outbound.js +5 -15
- package/dist/pi-intercom/reply-tracker.js +7 -6
- package/dist/pi-intercom/reply-wait.js +2 -2
- package/dist/pi-intercom/session-targets.d.ts +24 -0
- package/dist/pi-intercom/tool-registration.js +4 -1
- package/dist/pi-intercom/tool-results.js +5 -7
- package/dist/pi-intercom/tool-schemas.js +12 -26
- package/dist/pi-intercom/types.d.ts +212 -0
- package/dist/pi-intercom/types.js +54 -3
- package/dist/pi-intercom/validation.d.ts +12 -0
- package/dist/pi-intercom/validation.js +3 -0
- package/dist/shared/bounded-json.js +151 -0
- package/dist/shared/compact-cards.js +66 -0
- package/dist/shared/journal-frames.js +3 -6
- package/dist/shared/json-projection.js +267 -129
- package/dist/shared/native-journal.js +123 -56
- package/dist/shared/parking.js +12 -0
- package/dist/shared/post-exit-stdio-guard.js +120 -7
- package/dist/shared/tui-action.js +29 -0
- package/dist/shared/unknown.d.ts +9 -0
- package/docs/code-quality.md +5 -3
- package/docs/development.md +42 -3
- package/docs/intercom-bridge.md +9 -4
- package/docs/intercom.md +228 -7
- package/docs/performance.md +118 -14
- package/docs/reference.md +168 -20
- package/docs/retired-history-maintenance.md +252 -0
- package/package.json +28 -15
- package/scripts/archive-retired-history.mjs +464 -0
- package/scripts/build.mjs +48 -28
- package/scripts/measure-size.mjs +1 -1
- package/scripts/native-package-smoke.mjs +153 -12
- package/scripts/package-smoke.mjs +80 -11
- package/scripts/prepare.mjs +2 -2
- package/scripts/real-pi-live.mjs +377 -0
- package/scripts/real-pi-processes.mjs +6 -1
- package/scripts/real-pi-smoke.mjs +6 -282
- package/scripts/run-tests.mjs +4 -2
- package/scripts/typescript.mjs +35 -0
- package/skills/pi-intercom/SKILL.md +2 -1
- package/skills/pi-subagents/SKILL.md +23 -5
- package/src/agent/child-runtime.ts +105 -0
- package/src/agent/contracts.ts +18 -34
- package/src/agent/host.ts +39 -20
- package/src/agent/index.ts +234 -207
- package/src/agent/journal-tail.ts +141 -153
- package/src/agent/launch.ts +33 -9
- package/src/agent/legacy.ts +85 -31
- package/src/agent/notifications.ts +192 -67
- package/src/agent/observation-state.ts +277 -0
- package/src/agent/observation-view.ts +267 -0
- package/src/agent/observation.ts +409 -0
- package/src/agent/observe-card.ts +304 -0
- package/src/agent/observe-chrome.ts +170 -0
- package/src/agent/observe-client.ts +318 -0
- package/src/agent/observe-layout.ts +79 -0
- package/src/agent/observe-reading.ts +97 -0
- package/src/agent/observe-screen.ts +443 -129
- package/src/agent/observe-transcript.ts +434 -0
- package/src/agent/observe.ts +304 -71
- package/src/agent/parking.ts +341 -0
- package/src/agent/process.ts +280 -37
- package/src/agent/receipts.ts +114 -24
- package/src/agent/runner.ts +227 -149
- package/src/agent/shell.ts +2 -1
- package/src/agent/storage.ts +44 -11
- package/src/agent/tool-schemas.ts +33 -0
- package/src/agent/typebox.ts +12 -7
- package/src/agent/usage.ts +0 -130
- package/src/agent/view.ts +70 -20
- package/src/agent/wake.ts +107 -0
- package/src/intercom-client.ts +108 -0
- package/src/pi-intercom/bridge-session.ts +2 -2
- package/src/pi-intercom/broker/broker.ts +64 -53
- package/src/pi-intercom/broker/client-last.ts +65 -12
- package/src/pi-intercom/broker/client-park.ts +23 -0
- package/src/pi-intercom/broker/client.ts +63 -45
- package/src/pi-intercom/broker/framing.ts +11 -8
- package/src/pi-intercom/broker/guard.ts +33 -0
- package/src/pi-intercom/broker/last.ts +131 -43
- package/src/pi-intercom/broker/parked.ts +130 -0
- package/src/pi-intercom/broker/paths.ts +39 -12
- package/src/pi-intercom/broker/pid.ts +6 -1
- package/src/pi-intercom/broker/server-connection.ts +27 -2
- package/src/pi-intercom/connection.ts +103 -39
- package/src/pi-intercom/identity.ts +8 -5
- package/src/pi-intercom/inbound-delivery.ts +20 -66
- package/src/pi-intercom/inbound-journal.ts +103 -7
- package/src/pi-intercom/inbound-record.ts +21 -4
- package/src/pi-intercom/inbound.ts +32 -49
- package/src/pi-intercom/index.ts +44 -0
- package/src/pi-intercom/last-message.ts +11 -20
- package/src/pi-intercom/lifecycle.ts +117 -14
- package/src/pi-intercom/outbound.ts +7 -16
- package/src/pi-intercom/reply-tracker.ts +8 -6
- package/src/pi-intercom/reply-wait.ts +3 -2
- package/src/pi-intercom/runtime-types.ts +0 -1
- package/src/pi-intercom/tool-registration.ts +4 -1
- package/src/pi-intercom/tool-results.ts +10 -9
- package/src/pi-intercom/tool-schemas.ts +12 -44
- package/src/pi-intercom/types.ts +115 -4
- package/src/pi-intercom/validation.ts +4 -0
- package/src/shared/bounded-json.ts +162 -0
- package/src/shared/compact-cards.ts +118 -0
- package/src/shared/journal-frames.ts +3 -19
- package/src/shared/json-projection.ts +267 -147
- package/src/shared/native-journal.ts +157 -75
- package/src/shared/parking.ts +22 -0
- package/src/shared/post-exit-stdio-guard.ts +165 -7
- package/src/shared/tui-action.ts +39 -0
- package/tsconfig.client.json +8 -0
- package/tsconfig.json +1 -0
- package/src/shared/stream-json.d.ts +0 -10
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { jsonParser, type Token } from "stream-json/core/parser.js";
|
|
2
|
-
import { none } from "stream-chain/defs.js";
|
|
3
1
|
import { isRecord, type UnknownRecord } from "./unknown.ts";
|
|
4
2
|
|
|
5
3
|
export type Projection = (
|
|
@@ -12,86 +10,126 @@ export type StringChunk = (
|
|
|
12
10
|
text: string,
|
|
13
11
|
root?: UnknownRecord,
|
|
14
12
|
) => void;
|
|
15
|
-
export interface ProjectionLimits {
|
|
16
|
-
readonly depth: number;
|
|
17
|
-
readonly nodes: number;
|
|
18
|
-
readonly arrayLength: number;
|
|
19
|
-
readonly keyLength: number;
|
|
20
|
-
}
|
|
21
13
|
interface Container {
|
|
22
|
-
path: (string | number)[];
|
|
23
|
-
value?: Record<string, unknown> | unknown[];
|
|
14
|
+
readonly path: readonly (string | number)[];
|
|
15
|
+
readonly value?: Record<string, unknown> | unknown[];
|
|
16
|
+
readonly array: boolean;
|
|
24
17
|
key: string;
|
|
25
18
|
index: number;
|
|
26
19
|
}
|
|
27
20
|
|
|
21
|
+
// Grammar positions between tokens, then the two scalar states that can span chunks.
|
|
22
|
+
const VALUE = 0;
|
|
23
|
+
const FIRST_VALUE = 1;
|
|
24
|
+
const KEY = 2;
|
|
25
|
+
const FIRST_KEY = 3;
|
|
26
|
+
const COLON = 4;
|
|
27
|
+
const AFTER = 5;
|
|
28
|
+
const DONE = 6;
|
|
29
|
+
const STRING = 7;
|
|
30
|
+
const NUMBER = 8;
|
|
31
|
+
// Unescaped units exclude quote, backslash and C0 controls. Native regexp code skips a bounded
|
|
32
|
+
// run of them and valid escapes, so JavaScript handles only the unit that stopped it.
|
|
33
|
+
const BODY = /[ !#-[\]-\uffff]*(?:\\(?:["\\/bfnrt]|u[\da-fA-F]{4})[ !#-[\]-\uffff]*){0,1024}/y;
|
|
34
|
+
const LITERALS = new Map<number, readonly [string, boolean | null]>([
|
|
35
|
+
[102, ["false", false]],
|
|
36
|
+
[110, ["null", null]],
|
|
37
|
+
[116, ["true", true]],
|
|
38
|
+
]);
|
|
39
|
+
// Number states: start, sign, zero, integer, point, fraction, e, exponent sign, exponent.
|
|
40
|
+
// Columns: "0", "1"-"9", ".", "e"/"E", "+"/"-". A missing transition ends the number;
|
|
41
|
+
// only "-" reaches the start state's sign column because values dispatch on their first unit.
|
|
42
|
+
const NUMBER_NEXT = [
|
|
43
|
+
[2, 3, -1, -1, 1],
|
|
44
|
+
[2, 3],
|
|
45
|
+
[-1, -1, 4, 6],
|
|
46
|
+
[3, 3, 4, 6],
|
|
47
|
+
[5, 5],
|
|
48
|
+
[5, 5, -1, 6],
|
|
49
|
+
[8, 8, -1, -1, 7],
|
|
50
|
+
[8, 8],
|
|
51
|
+
[8, 8],
|
|
52
|
+
];
|
|
53
|
+
const NUMBER_END = new Set([2, 3, 5, 8]);
|
|
54
|
+
const NUMBER_COLUMN = new Int8Array(128).fill(-1);
|
|
55
|
+
for (const [column, units] of ["0", "123456789", ".", "eE", "+-"].entries()) {
|
|
56
|
+
for (const unit of units) {
|
|
57
|
+
NUMBER_COLUMN[unit.charCodeAt(0)] = column;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Classify where BODY stopped short of a closing quote. True means the chunk ends at or inside an
|
|
62
|
+
* escape, so the rest carries; false means a bounded run ended at a valid escape. Controls throw,
|
|
63
|
+
* and so does a complete escape BODY made no progress at, because BODY consumes any valid one.
|
|
64
|
+
*/
|
|
65
|
+
function chunkEnds(text: string, end: number, progressed: boolean): boolean {
|
|
66
|
+
if (end === text.length) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (text.charCodeAt(end) !== 92) {
|
|
70
|
+
throw new SyntaxError("Bad control character in JSON string");
|
|
71
|
+
}
|
|
72
|
+
if (end + (text.charCodeAt(end + 1) === 117 ? 6 : 2) > text.length) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (!progressed) {
|
|
76
|
+
throw new SyntaxError("Bad escaped character in JSON string");
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function unexpected(): SyntaxError {
|
|
81
|
+
return new SyntaxError("Unexpected character in JSON");
|
|
82
|
+
}
|
|
28
83
|
function retained(keep: boolean | number): boolean {
|
|
29
84
|
return keep === true || (typeof keep === "number" && keep !== 0 && !Number.isNaN(keep));
|
|
30
85
|
}
|
|
31
86
|
|
|
32
|
-
/** Validate all
|
|
87
|
+
/** Validate all JSON text, assembling selected values only. Skipped strings are never decoded. */
|
|
33
88
|
export class JsonProjection {
|
|
34
|
-
private readonly parse = jsonParser({ packValues: false });
|
|
35
89
|
private readonly stack: Container[] = [];
|
|
36
|
-
private
|
|
90
|
+
private state = VALUE;
|
|
91
|
+
private carry = "";
|
|
92
|
+
private path: readonly (string | number)[] = [];
|
|
37
93
|
private key = false;
|
|
38
|
-
private
|
|
39
|
-
private
|
|
94
|
+
private textCap = 0;
|
|
95
|
+
private text = "";
|
|
96
|
+
private numberState = 0;
|
|
40
97
|
private readonly select: Projection;
|
|
41
98
|
private readonly stringChunk?: StringChunk;
|
|
42
99
|
private readonly keys: KeyLimit;
|
|
43
|
-
private readonly limits?: ProjectionLimits;
|
|
44
100
|
value: unknown;
|
|
45
|
-
constructor(
|
|
46
|
-
select: Projection,
|
|
47
|
-
stringChunk?: StringChunk,
|
|
48
|
-
keys: KeyLimit = () => 4096,
|
|
49
|
-
limits?: ProjectionLimits,
|
|
50
|
-
) {
|
|
101
|
+
constructor(select: Projection, stringChunk?: StringChunk, keys: KeyLimit = () => 4096) {
|
|
51
102
|
this.select = select;
|
|
52
103
|
this.stringChunk = stringChunk;
|
|
53
104
|
this.keys = keys;
|
|
54
|
-
this.limits = limits;
|
|
55
105
|
}
|
|
56
106
|
private root(): UnknownRecord | undefined {
|
|
57
107
|
return isRecord(this.value) ? this.value : undefined;
|
|
58
108
|
}
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
if (!parent) {
|
|
62
|
-
throw new SyntaxError("JSON token requires a containing object");
|
|
63
|
-
}
|
|
64
|
-
return parent;
|
|
65
|
-
}
|
|
66
|
-
private selected(path: readonly (string | number)[]): boolean | number {
|
|
67
|
-
const parent = this.stack.at(-1);
|
|
68
|
-
const keep = parent && parent.value === undefined ? false : this.select(path, this.root());
|
|
69
|
-
if (retained(keep) && this.limits && ++this.nodes > this.limits.nodes) {
|
|
70
|
-
throw new RangeError("JSON projection exceeds its assembled-node budget.");
|
|
71
|
-
}
|
|
72
|
-
return keep;
|
|
109
|
+
private selected(): boolean | number {
|
|
110
|
+
return this.select(this.path, this.root());
|
|
73
111
|
}
|
|
74
|
-
|
|
112
|
+
/** Paths inside skipped containers are only built for string observers, never selected. */
|
|
113
|
+
private start(): boolean | number {
|
|
75
114
|
const parent = this.stack.at(-1);
|
|
76
|
-
if (
|
|
77
|
-
this.limits &&
|
|
78
|
-
(this.stack.length > this.limits.depth || (parent && parent.index >= this.limits.arrayLength))
|
|
79
|
-
) {
|
|
80
|
-
throw new RangeError("JSON projection exceeds its depth or array-position budget.");
|
|
81
|
-
}
|
|
82
115
|
if (!parent) {
|
|
83
|
-
|
|
116
|
+
this.path = [];
|
|
117
|
+
return this.selected();
|
|
118
|
+
}
|
|
119
|
+
const step = parent.array ? parent.index++ : parent.key;
|
|
120
|
+
if (parent.value !== undefined) {
|
|
121
|
+
this.path = [...parent.path, step];
|
|
122
|
+
return this.selected();
|
|
84
123
|
}
|
|
85
|
-
|
|
124
|
+
this.path = this.stringChunk ? [...parent.path, step] : parent.path;
|
|
125
|
+
return false;
|
|
86
126
|
}
|
|
87
|
-
private put(
|
|
127
|
+
private put(value: unknown): void {
|
|
88
128
|
const parent = this.stack.at(-1);
|
|
129
|
+
const key = this.path.at(-1);
|
|
89
130
|
if (!parent) {
|
|
90
131
|
this.value = value;
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
const key = path.at(-1);
|
|
94
|
-
if (parent.value !== undefined && key !== undefined) {
|
|
132
|
+
} else if (parent.value !== undefined && key !== undefined) {
|
|
95
133
|
Object.defineProperty(parent.value, key, {
|
|
96
134
|
value,
|
|
97
135
|
enumerable: true,
|
|
@@ -100,125 +138,207 @@ export class JsonProjection {
|
|
|
100
138
|
});
|
|
101
139
|
}
|
|
102
140
|
}
|
|
103
|
-
private
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
private ended(): void {
|
|
142
|
+
this.state = this.stack.length === 0 ? DONE : AFTER;
|
|
143
|
+
}
|
|
144
|
+
private close(index: number): number {
|
|
145
|
+
this.stack.pop();
|
|
146
|
+
this.ended();
|
|
147
|
+
return index + 1;
|
|
148
|
+
}
|
|
149
|
+
private scalar(state: number, key: boolean, keep: boolean | number): void {
|
|
150
|
+
const cap = keep === true ? Infinity : Number(keep);
|
|
151
|
+
this.state = state;
|
|
152
|
+
this.key = key;
|
|
153
|
+
this.textCap = Number.isNaN(cap) ? 0 : cap;
|
|
154
|
+
this.text = "";
|
|
155
|
+
this.numberState = 0;
|
|
156
|
+
}
|
|
157
|
+
private append(text: string): void {
|
|
158
|
+
if (this.text.length < this.textCap) {
|
|
159
|
+
this.text += text.slice(0, this.textCap - this.text.length);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
private container(array: boolean): void {
|
|
106
163
|
let value: Container["value"];
|
|
107
|
-
if (retained(
|
|
164
|
+
if (retained(this.start())) {
|
|
108
165
|
value = array ? [] : {};
|
|
109
|
-
this.put(
|
|
166
|
+
this.put(value);
|
|
110
167
|
}
|
|
111
|
-
this.stack.push({ path, value, key: "", index:
|
|
168
|
+
this.stack.push({ path: this.path, value, array, key: "", index: 0 });
|
|
169
|
+
this.state = array ? FIRST_VALUE : FIRST_KEY;
|
|
112
170
|
}
|
|
113
|
-
private
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
171
|
+
private literal(text: string, index: number, code: number): number {
|
|
172
|
+
const literal = LITERALS.get(code);
|
|
173
|
+
if (!literal) {
|
|
174
|
+
throw unexpected();
|
|
175
|
+
}
|
|
176
|
+
const [word, value] = literal;
|
|
177
|
+
if (text.startsWith(word, index)) {
|
|
178
|
+
if (retained(this.start())) {
|
|
179
|
+
this.put(value);
|
|
180
|
+
}
|
|
181
|
+
this.ended();
|
|
182
|
+
return index + word.length;
|
|
183
|
+
}
|
|
184
|
+
if (text.length - index < word.length && word.startsWith(text.slice(index))) {
|
|
185
|
+
this.carry = text.slice(index);
|
|
186
|
+
return text.length;
|
|
187
|
+
}
|
|
188
|
+
throw unexpected();
|
|
119
189
|
}
|
|
120
|
-
private
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
190
|
+
private startValue(text: string, index: number, code: number): number {
|
|
191
|
+
if (code === 34) {
|
|
192
|
+
this.scalar(STRING, false, this.start());
|
|
193
|
+
return index + 1;
|
|
194
|
+
}
|
|
195
|
+
if (code === 45 || (code >= 48 && code <= 57)) {
|
|
196
|
+
this.scalar(NUMBER, false, this.start());
|
|
197
|
+
return index;
|
|
128
198
|
}
|
|
129
|
-
if (
|
|
130
|
-
|
|
199
|
+
if (code === 123 || code === 91) {
|
|
200
|
+
this.container(code === 91);
|
|
201
|
+
return index + 1;
|
|
131
202
|
}
|
|
203
|
+
return this.literal(text, index, code);
|
|
132
204
|
}
|
|
133
|
-
private
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
return;
|
|
205
|
+
private member(index: number, code: number): number {
|
|
206
|
+
const parent = this.stack.at(-1);
|
|
207
|
+
if (code === 125 && this.state === FIRST_KEY) {
|
|
208
|
+
return this.close(index);
|
|
137
209
|
}
|
|
138
|
-
if (
|
|
139
|
-
|
|
210
|
+
if (code !== 34 || !parent) {
|
|
211
|
+
throw unexpected();
|
|
140
212
|
}
|
|
141
|
-
|
|
142
|
-
|
|
213
|
+
this.scalar(STRING, true, parent.value === undefined ? 0 : this.keys(parent.path, this.root()));
|
|
214
|
+
return index + 1;
|
|
215
|
+
}
|
|
216
|
+
private after(index: number, code: number): number {
|
|
217
|
+
const parent = this.stack.at(-1);
|
|
218
|
+
if (parent && code === 44) {
|
|
219
|
+
this.state = parent.array ? VALUE : KEY;
|
|
220
|
+
return index + 1;
|
|
143
221
|
}
|
|
222
|
+
if (parent && code === (parent.array ? 93 : 125)) {
|
|
223
|
+
return this.close(index);
|
|
224
|
+
}
|
|
225
|
+
throw unexpected();
|
|
144
226
|
}
|
|
145
|
-
private
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
227
|
+
private structure(text: string, index: number, code: number): number {
|
|
228
|
+
switch (this.state) {
|
|
229
|
+
case VALUE:
|
|
230
|
+
return this.startValue(text, index, code);
|
|
231
|
+
case FIRST_VALUE:
|
|
232
|
+
return code === 93 ? this.close(index) : this.startValue(text, index, code);
|
|
233
|
+
case KEY:
|
|
234
|
+
case FIRST_KEY:
|
|
235
|
+
return this.member(index, code);
|
|
236
|
+
case COLON:
|
|
237
|
+
if (code !== 58) {
|
|
238
|
+
throw unexpected();
|
|
239
|
+
}
|
|
240
|
+
this.state = VALUE;
|
|
241
|
+
return index + 1;
|
|
242
|
+
case AFTER:
|
|
243
|
+
return this.after(index, code);
|
|
244
|
+
default:
|
|
245
|
+
throw unexpected();
|
|
149
246
|
}
|
|
150
|
-
this.scalar = undefined;
|
|
151
247
|
}
|
|
152
|
-
private
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
248
|
+
private observed(decoded: string): void {
|
|
249
|
+
if (decoded === "") {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (this.stringChunk && !this.key) {
|
|
253
|
+
this.stringChunk(this.path, decoded, this.root());
|
|
254
|
+
}
|
|
255
|
+
this.append(decoded);
|
|
256
|
+
}
|
|
257
|
+
private closeString(): void {
|
|
258
|
+
const parent = this.stack.at(-1);
|
|
259
|
+
if (this.key && parent) {
|
|
260
|
+
parent.key = this.text;
|
|
261
|
+
this.state = COLON;
|
|
262
|
+
} else {
|
|
263
|
+
if (this.textCap !== 0) {
|
|
264
|
+
this.put(this.text);
|
|
160
265
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.appendScalar(token.value, token.name === "stringChunk");
|
|
186
|
-
}
|
|
187
|
-
break;
|
|
188
|
-
case "endString":
|
|
189
|
-
case "endNumber":
|
|
190
|
-
this.finishScalar();
|
|
191
|
-
break;
|
|
192
|
-
case "trueValue":
|
|
193
|
-
case "falseValue":
|
|
194
|
-
case "nullValue": {
|
|
195
|
-
const path = this.nextPath();
|
|
196
|
-
const keep = this.selected(path);
|
|
197
|
-
if (retained(keep)) {
|
|
198
|
-
this.put(path, token.value);
|
|
199
|
-
}
|
|
200
|
-
break;
|
|
266
|
+
this.ended();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** Observed values and unfinished selections need text; others are only validated. */
|
|
270
|
+
private decoding(): boolean {
|
|
271
|
+
return (this.stringChunk !== undefined && !this.key) || this.text.length < this.textCap;
|
|
272
|
+
}
|
|
273
|
+
private string(text: string, start: number): number {
|
|
274
|
+
for (let index = start; ;) {
|
|
275
|
+
BODY.lastIndex = index;
|
|
276
|
+
BODY.test(text);
|
|
277
|
+
const end = BODY.lastIndex;
|
|
278
|
+
if (end > index && this.decoding()) {
|
|
279
|
+
const raw = text.slice(index, end);
|
|
280
|
+
// A matched run is a complete valid string body no longer than this chunk.
|
|
281
|
+
this.observed(raw.includes("\\") ? String(JSON.parse(`"${raw}"`)) : raw);
|
|
282
|
+
}
|
|
283
|
+
if (text.charCodeAt(end) === 34) {
|
|
284
|
+
this.closeString();
|
|
285
|
+
return end + 1;
|
|
286
|
+
}
|
|
287
|
+
if (chunkEnds(text, end, end > index)) {
|
|
288
|
+
this.carry = text.slice(end);
|
|
289
|
+
return text.length;
|
|
201
290
|
}
|
|
202
|
-
|
|
203
|
-
case "stringValue":
|
|
204
|
-
case "numberValue":
|
|
205
|
-
case "whitespace":
|
|
206
|
-
break; // Packing is disabled; whitespace carries no projected value.
|
|
291
|
+
index = end;
|
|
207
292
|
}
|
|
208
293
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
294
|
+
private number(text: string, start: number, final: boolean): number {
|
|
295
|
+
let state = this.numberState;
|
|
296
|
+
let index = start;
|
|
297
|
+
for (; index < text.length; index++) {
|
|
298
|
+
const next = NUMBER_NEXT[state]?.[NUMBER_COLUMN[text.charCodeAt(index)] ?? -1] ?? -1;
|
|
299
|
+
if (next < 0) {
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
state = next;
|
|
303
|
+
}
|
|
304
|
+
this.numberState = state;
|
|
305
|
+
this.append(text.slice(start, index));
|
|
306
|
+
if (index === text.length && !final) {
|
|
307
|
+
return index;
|
|
308
|
+
}
|
|
309
|
+
if (!NUMBER_END.has(state)) {
|
|
310
|
+
throw new SyntaxError("Incomplete number in JSON");
|
|
212
311
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
312
|
+
if (this.textCap !== 0) {
|
|
313
|
+
this.put(Number(this.text));
|
|
314
|
+
}
|
|
315
|
+
this.ended();
|
|
316
|
+
return index;
|
|
317
|
+
}
|
|
318
|
+
write(chunk: string): void {
|
|
319
|
+
const text = this.carry + chunk;
|
|
320
|
+
this.carry = "";
|
|
321
|
+
let index = 0;
|
|
322
|
+
while (index < text.length) {
|
|
323
|
+
const code = text.charCodeAt(index);
|
|
324
|
+
if (this.state === STRING) {
|
|
325
|
+
index = this.string(text, index);
|
|
326
|
+
} else if (this.state === NUMBER) {
|
|
327
|
+
index = this.number(text, index, false);
|
|
328
|
+
} else if (code === 32 || code === 10 || code === 13 || code === 9) {
|
|
329
|
+
index++;
|
|
330
|
+
} else {
|
|
331
|
+
index = this.structure(text, index, code);
|
|
217
332
|
}
|
|
218
333
|
}
|
|
219
334
|
}
|
|
220
335
|
finish(): unknown {
|
|
221
|
-
this.
|
|
336
|
+
if (this.state === NUMBER) {
|
|
337
|
+
this.number("", 0, true);
|
|
338
|
+
}
|
|
339
|
+
if (this.state !== DONE || this.carry !== "") {
|
|
340
|
+
throw new SyntaxError("Unexpected end of JSON input");
|
|
341
|
+
}
|
|
222
342
|
return this.value;
|
|
223
343
|
}
|
|
224
344
|
}
|