@earendil-works/pi-durable 0.99.0 → 0.99.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +435 -46
- package/dist/entries.d.ts +20 -2
- package/dist/entries.d.ts.map +1 -1
- package/dist/entries.js +14 -1
- package/dist/entries.js.map +1 -1
- package/dist/env/index.d.ts +13 -38
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js +2 -0
- package/dist/env/index.js.map +1 -1
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +51 -58
- package/dist/env/node.js.map +1 -1
- package/dist/harness/compaction.d.ts +56 -0
- package/dist/harness/compaction.d.ts.map +1 -0
- package/dist/harness/compaction.js +385 -0
- package/dist/harness/compaction.js.map +1 -0
- package/dist/harness/config.d.ts +10 -1
- package/dist/harness/config.d.ts.map +1 -1
- package/dist/harness/config.js +6 -0
- package/dist/harness/config.js.map +1 -1
- package/dist/harness/context.d.ts +8 -0
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +32 -21
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/events.d.ts +166 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +319 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/generation.d.ts +37 -6
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +350 -72
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +124 -3
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +57 -0
- package/dist/harness/inbox.d.ts.map +1 -0
- package/dist/harness/inbox.js +99 -0
- package/dist/harness/inbox.js.map +1 -0
- package/dist/harness/json.d.ts +10 -0
- package/dist/harness/json.d.ts.map +1 -0
- package/dist/harness/json.js +33 -0
- package/dist/harness/json.js.map +1 -0
- package/dist/harness/live.d.ts +66 -7
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +71 -7
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/output.d.ts +62 -0
- package/dist/harness/output.d.ts.map +1 -0
- package/dist/harness/output.js +261 -0
- package/dist/harness/output.js.map +1 -0
- package/dist/harness/prompt.d.ts +10 -5
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +66 -13
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/registry.d.ts.map +1 -1
- package/dist/harness/registry.js +11 -2
- package/dist/harness/registry.js.map +1 -1
- package/dist/harness/scheduler.d.ts +37 -12
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +632 -72
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +12 -7
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +72 -33
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/tool.d.ts +36 -0
- package/dist/harness/tool.d.ts.map +1 -0
- package/dist/harness/tool.js +412 -0
- package/dist/harness/tool.js.map +1 -0
- package/dist/harness/types.d.ts +185 -11
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +18 -0
- package/dist/harness/usage.d.ts.map +1 -0
- package/dist/harness/usage.js +57 -0
- package/dist/harness/usage.js.map +1 -0
- package/dist/harness/view.d.ts +43 -0
- package/dist/harness/view.d.ts.map +1 -0
- package/dist/harness/view.js +194 -0
- package/dist/harness/view.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/session/observation.d.ts +18 -11
- package/dist/session/observation.d.ts.map +1 -1
- package/dist/session/observation.js +24 -13
- package/dist/session/observation.js.map +1 -1
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +18 -4
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +10 -1
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +83 -27
- package/dist/session/transaction.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +22 -2
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +1 -1
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/migrations.js +1 -1
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +0 -1
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +34 -2
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tools/bash.d.ts +28 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +69 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit-diff.d.ts +89 -0
- package/dist/tools/edit-diff.d.ts.map +1 -0
- package/dist/tools/edit-diff.js +386 -0
- package/dist/tools/edit-diff.js.map +1 -0
- package/dist/tools/edit.d.ts +18 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +116 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/env.d.ts +5 -0
- package/dist/tools/env.d.ts.map +1 -0
- package/dist/tools/env.js +7 -0
- package/dist/tools/env.js.map +1 -0
- package/dist/tools/file-mutation-queue.d.ts +5 -0
- package/dist/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/tools/file-mutation-queue.js +46 -0
- package/dist/tools/file-mutation-queue.js.map +1 -0
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.d.ts.map +1 -0
- package/dist/tools/image.js +92 -0
- package/dist/tools/image.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/path-utils.d.ts +5 -0
- package/dist/tools/path-utils.d.ts.map +1 -0
- package/dist/tools/path-utils.js +26 -0
- package/dist/tools/path-utils.js.map +1 -0
- package/dist/tools/read.d.ts +17 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +100 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +30 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/{env/utils/truncate.d.ts → truncate.d.ts} +1 -17
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +146 -0
- package/dist/truncate.js.map +1 -0
- package/dist/types.d.ts +86 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/pico-v5-handoff.md +366 -86
- package/docs/pico-v5.md +1373 -283
- package/package.json +11 -3
- package/dist/env/utils/adaptive-publisher.d.ts +0 -24
- package/dist/env/utils/adaptive-publisher.d.ts.map +0 -1
- package/dist/env/utils/adaptive-publisher.js +0 -79
- package/dist/env/utils/adaptive-publisher.js.map +0 -1
- package/dist/env/utils/output-capture.d.ts +0 -31
- package/dist/env/utils/output-capture.d.ts.map +0 -1
- package/dist/env/utils/output-capture.js +0 -238
- package/dist/env/utils/output-capture.js.map +0 -1
- package/dist/env/utils/truncate.d.ts.map +0 -1
- package/dist/env/utils/truncate.js +0 -294
- package/dist/env/utils/truncate.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-durable",
|
|
3
|
-
"version": "0.99.
|
|
3
|
+
"version": "0.99.2",
|
|
4
4
|
"description": "Durable conversation, task, and document runtime for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"types": "./dist/env/node.d.ts",
|
|
22
22
|
"import": "./dist/env/node.js"
|
|
23
23
|
},
|
|
24
|
+
"./tools": {
|
|
25
|
+
"source": "./src/tools/index.ts",
|
|
26
|
+
"types": "./dist/tools/index.d.ts",
|
|
27
|
+
"import": "./dist/tools/index.js"
|
|
28
|
+
},
|
|
24
29
|
"./storage/memory": {
|
|
25
30
|
"source": "./src/storage/memory.ts",
|
|
26
31
|
"types": "./dist/storage/memory.d.ts",
|
|
@@ -65,6 +70,7 @@
|
|
|
65
70
|
"test": "vitest --run",
|
|
66
71
|
"bench:storage": "vitest bench --config vitest.benchmark.config.ts",
|
|
67
72
|
"bench:storage:memory": "node --conditions=source --experimental-strip-types test/storage-memory.ts",
|
|
73
|
+
"bench:tool-output": "node --conditions=source --experimental-strip-types --expose-gc test/tool-output-bench.ts",
|
|
68
74
|
"prepublishOnly": "npm run clean && npm run build"
|
|
69
75
|
},
|
|
70
76
|
"keywords": [
|
|
@@ -84,8 +90,10 @@
|
|
|
84
90
|
"node": ">=22.19.0"
|
|
85
91
|
},
|
|
86
92
|
"dependencies": {
|
|
87
|
-
"@earendil-works/chord": "^0.99.
|
|
88
|
-
"@earendil-works/pi-ai": "^0.99.
|
|
93
|
+
"@earendil-works/chord": "^0.99.2",
|
|
94
|
+
"@earendil-works/pi-ai": "^0.99.2",
|
|
95
|
+
"diff": "8.0.4",
|
|
96
|
+
"typebox": "1.3.27"
|
|
89
97
|
},
|
|
90
98
|
"devDependencies": {
|
|
91
99
|
"shx": "0.4.0",
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface AdaptivePublisherOptions<TValue, TUpdate> {
|
|
2
|
-
snapshot(): TValue;
|
|
3
|
-
update(previous: TValue | undefined, current: TValue): TUpdate | undefined;
|
|
4
|
-
measure(update: TUpdate): number;
|
|
5
|
-
publish(update: TUpdate): void;
|
|
6
|
-
onError(error: unknown): void;
|
|
7
|
-
minIntervalMs?: number;
|
|
8
|
-
targetBytesPerSecond?: number;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Publishes the latest state without queuing intermediate mutations.
|
|
12
|
-
*
|
|
13
|
-
* The first dirty state after idle is immediate. Each publication then buys a
|
|
14
|
-
* delay proportional to its encoded size, with a minimum interval that also
|
|
15
|
-
* bounds event count. A single trailing timer guarantees eventual publication.
|
|
16
|
-
*/
|
|
17
|
-
export declare class AdaptivePublisher<TValue, TUpdate> {
|
|
18
|
-
#private;
|
|
19
|
-
constructor(options: AdaptivePublisherOptions<TValue, TUpdate>);
|
|
20
|
-
markDirty(): void;
|
|
21
|
-
flush(force?: boolean): void;
|
|
22
|
-
dispose(): void;
|
|
23
|
-
}
|
|
24
|
-
//# sourceMappingURL=adaptive-publisher.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-publisher.d.ts","sourceRoot":"","sources":["../../../src/env/utils/adaptive-publisher.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB,CAAC,MAAM,EAAE,OAAO;IACxD,QAAQ,IAAI,MAAM,CAAC;IACnB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3E,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,qBAAa,iBAAiB,CAAC,MAAM,EAAE,OAAO;;IAU7C,YAAY,OAAO,EAAE,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,EAI7D;IAED,SAAS,IAAI,IAAI,CAShB;IAED,KAAK,CAAC,KAAK,UAAQ,GAAG,IAAI,CAuBzB;IAED,OAAO,IAAI,IAAI,CAId;CAaD"}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Publishes the latest state without queuing intermediate mutations.
|
|
3
|
-
*
|
|
4
|
-
* The first dirty state after idle is immediate. Each publication then buys a
|
|
5
|
-
* delay proportional to its encoded size, with a minimum interval that also
|
|
6
|
-
* bounds event count. A single trailing timer guarantees eventual publication.
|
|
7
|
-
*/
|
|
8
|
-
export class AdaptivePublisher {
|
|
9
|
-
#options;
|
|
10
|
-
#minIntervalMs;
|
|
11
|
-
#targetBytesPerSecond;
|
|
12
|
-
#published;
|
|
13
|
-
#dirty = false;
|
|
14
|
-
#nextEmitAt = 0;
|
|
15
|
-
#timer;
|
|
16
|
-
#disposed = false;
|
|
17
|
-
constructor(options) {
|
|
18
|
-
this.#options = options;
|
|
19
|
-
this.#minIntervalMs = options.minIntervalMs ?? 100;
|
|
20
|
-
this.#targetBytesPerSecond = options.targetBytesPerSecond ?? 100 * 1024;
|
|
21
|
-
}
|
|
22
|
-
markDirty() {
|
|
23
|
-
if (this.#disposed)
|
|
24
|
-
return;
|
|
25
|
-
this.#dirty = true;
|
|
26
|
-
const wait = this.#nextEmitAt - Date.now();
|
|
27
|
-
if (wait <= 0) {
|
|
28
|
-
this.flush();
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
this.#armTimer(wait);
|
|
32
|
-
}
|
|
33
|
-
flush(force = false) {
|
|
34
|
-
if (this.#disposed || !this.#dirty)
|
|
35
|
-
return;
|
|
36
|
-
const now = Date.now();
|
|
37
|
-
if (!force && now < this.#nextEmitAt) {
|
|
38
|
-
this.#armTimer(this.#nextEmitAt - now);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (this.#timer !== undefined)
|
|
42
|
-
clearTimeout(this.#timer);
|
|
43
|
-
this.#timer = undefined;
|
|
44
|
-
const current = this.#options.snapshot();
|
|
45
|
-
const update = this.#options.update(this.#published, current);
|
|
46
|
-
if (update === undefined) {
|
|
47
|
-
this.#published = current;
|
|
48
|
-
this.#dirty = false;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const encodedBytes = this.#options.measure(update);
|
|
52
|
-
this.#published = current;
|
|
53
|
-
this.#dirty = false;
|
|
54
|
-
this.#nextEmitAt = now + Math.max(this.#minIntervalMs, (encodedBytes * 1000) / this.#targetBytesPerSecond);
|
|
55
|
-
// Commit before delivery. A consumer may apply the update and then throw or
|
|
56
|
-
// reenter the producer; retaining the old baseline would duplicate that delta.
|
|
57
|
-
this.#options.publish(update);
|
|
58
|
-
}
|
|
59
|
-
dispose() {
|
|
60
|
-
if (this.#timer !== undefined)
|
|
61
|
-
clearTimeout(this.#timer);
|
|
62
|
-
this.#timer = undefined;
|
|
63
|
-
this.#disposed = true;
|
|
64
|
-
}
|
|
65
|
-
#armTimer(wait) {
|
|
66
|
-
if (this.#timer !== undefined)
|
|
67
|
-
return;
|
|
68
|
-
this.#timer = setTimeout(() => {
|
|
69
|
-
this.#timer = undefined;
|
|
70
|
-
try {
|
|
71
|
-
this.flush();
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
this.#options.onError(error);
|
|
75
|
-
}
|
|
76
|
-
}, wait);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
//# sourceMappingURL=adaptive-publisher.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-publisher.js","sourceRoot":"","sources":["../../../src/env/utils/adaptive-publisher.ts"],"names":[],"mappings":"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","sourcesContent":["export interface AdaptivePublisherOptions<TValue, TUpdate> {\n\tsnapshot(): TValue;\n\tupdate(previous: TValue | undefined, current: TValue): TUpdate | undefined;\n\tmeasure(update: TUpdate): number;\n\tpublish(update: TUpdate): void;\n\tonError(error: unknown): void;\n\tminIntervalMs?: number;\n\ttargetBytesPerSecond?: number;\n}\n\n/**\n * Publishes the latest state without queuing intermediate mutations.\n *\n * The first dirty state after idle is immediate. Each publication then buys a\n * delay proportional to its encoded size, with a minimum interval that also\n * bounds event count. A single trailing timer guarantees eventual publication.\n */\nexport class AdaptivePublisher<TValue, TUpdate> {\n\treadonly #options: AdaptivePublisherOptions<TValue, TUpdate>;\n\treadonly #minIntervalMs: number;\n\treadonly #targetBytesPerSecond: number;\n\t#published: TValue | undefined;\n\t#dirty = false;\n\t#nextEmitAt = 0;\n\t#timer: ReturnType<typeof setTimeout> | undefined;\n\t#disposed = false;\n\n\tconstructor(options: AdaptivePublisherOptions<TValue, TUpdate>) {\n\t\tthis.#options = options;\n\t\tthis.#minIntervalMs = options.minIntervalMs ?? 100;\n\t\tthis.#targetBytesPerSecond = options.targetBytesPerSecond ?? 100 * 1024;\n\t}\n\n\tmarkDirty(): void {\n\t\tif (this.#disposed) return;\n\t\tthis.#dirty = true;\n\t\tconst wait = this.#nextEmitAt - Date.now();\n\t\tif (wait <= 0) {\n\t\t\tthis.flush();\n\t\t\treturn;\n\t\t}\n\t\tthis.#armTimer(wait);\n\t}\n\n\tflush(force = false): void {\n\t\tif (this.#disposed || !this.#dirty) return;\n\t\tconst now = Date.now();\n\t\tif (!force && now < this.#nextEmitAt) {\n\t\t\tthis.#armTimer(this.#nextEmitAt - now);\n\t\t\treturn;\n\t\t}\n\t\tif (this.#timer !== undefined) clearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tconst current = this.#options.snapshot();\n\t\tconst update = this.#options.update(this.#published, current);\n\t\tif (update === undefined) {\n\t\t\tthis.#published = current;\n\t\t\tthis.#dirty = false;\n\t\t\treturn;\n\t\t}\n\t\tconst encodedBytes = this.#options.measure(update);\n\t\tthis.#published = current;\n\t\tthis.#dirty = false;\n\t\tthis.#nextEmitAt = now + Math.max(this.#minIntervalMs, (encodedBytes * 1000) / this.#targetBytesPerSecond);\n\t\t// Commit before delivery. A consumer may apply the update and then throw or\n\t\t// reenter the producer; retaining the old baseline would duplicate that delta.\n\t\tthis.#options.publish(update);\n\t}\n\n\tdispose(): void {\n\t\tif (this.#timer !== undefined) clearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#disposed = true;\n\t}\n\n\t#armTimer(wait: number): void {\n\t\tif (this.#timer !== undefined) return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\ttry {\n\t\t\t\tthis.flush();\n\t\t\t} catch (error) {\n\t\t\t\tthis.#options.onError(error);\n\t\t\t}\n\t\t}, wait);\n\t}\n}\n"]}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { Context } from "@earendil-works/chord";
|
|
2
|
-
import type { ShellOutputCaptureOptions, ShellOutputUpdate, ShellOutputView } from "../index.ts";
|
|
3
|
-
export declare const OUTPUT_MIN_EMIT_INTERVAL_MS = 100;
|
|
4
|
-
export declare const OUTPUT_TARGET_BYTES_PER_SECOND: number;
|
|
5
|
-
interface OutputCaptureHandlers {
|
|
6
|
-
onUpdate?: (update: ShellOutputUpdate, context: Context) => void;
|
|
7
|
-
onError(error: unknown): void;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Maintains and publishes one bounded shell-output view.
|
|
11
|
-
*
|
|
12
|
-
* Writes received while publication is rate-limited collapse into the latest
|
|
13
|
-
* view. Small changes remain responsive; complete window turnovers purchase a
|
|
14
|
-
* proportionally longer delay. The first update after idle and an explicit
|
|
15
|
-
* final flush are immediate.
|
|
16
|
-
*/
|
|
17
|
-
export declare class OutputCapture {
|
|
18
|
-
#private;
|
|
19
|
-
constructor(options: ShellOutputCaptureOptions | undefined, context: Context, handlers: OutputCaptureHandlers);
|
|
20
|
-
get truncated(): boolean;
|
|
21
|
-
push(chunk: string | Uint8Array): void;
|
|
22
|
-
finish(): void;
|
|
23
|
-
setSpillPath(path: string): void;
|
|
24
|
-
snapshot(): ShellOutputView;
|
|
25
|
-
flush(): void;
|
|
26
|
-
dispose(): void;
|
|
27
|
-
}
|
|
28
|
-
export declare function applyShellOutputUpdate(current: ShellOutputView | undefined, update: ShellOutputUpdate): ShellOutputView;
|
|
29
|
-
export declare function sanitizeShellOutput(text: string): string;
|
|
30
|
-
export {};
|
|
31
|
-
//# sourceMappingURL=output-capture.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"output-capture.d.ts","sourceRoot":"","sources":["../../../src/env/utils/output-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,yBAAyB,EAAuB,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAItH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,8BAA8B,QAAa,CAAC;AAMzD,UAAU,qBAAqB;IAC9B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,qBAAa,aAAa;;IAmBzB,YAAY,OAAO,EAAE,yBAAyB,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAqB5G;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAQrC;IAED,MAAM,IAAI,IAAI,CAGb;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK/B;IAED,QAAQ,IAAI,eAAe,CA6B1B;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,OAAO,IAAI,IAAI,CAGd;CAsCD;AAED,wBAAgB,sBAAsB,CACrC,OAAO,EAAE,eAAe,GAAG,SAAS,EACpC,MAAM,EAAE,iBAAiB,GACvB,eAAe,CAWjB;AA+CD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { AdaptivePublisher } from "./adaptive-publisher.js";
|
|
2
|
-
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateHead, truncateTail, utf8ByteLength } from "./truncate.js";
|
|
3
|
-
export const OUTPUT_MIN_EMIT_INTERVAL_MS = 100;
|
|
4
|
-
export const OUTPUT_TARGET_BYTES_PER_SECOND = 100 * 1024;
|
|
5
|
-
const INVALID_SHELL_OUTPUT = /[\x00-\x08\x0b-\x1f\ufff9-\ufffb]/g;
|
|
6
|
-
const textEncoder = new TextEncoder();
|
|
7
|
-
const textDecoder = new TextDecoder();
|
|
8
|
-
/**
|
|
9
|
-
* Maintains and publishes one bounded shell-output view.
|
|
10
|
-
*
|
|
11
|
-
* Writes received while publication is rate-limited collapse into the latest
|
|
12
|
-
* view. Small changes remain responsive; complete window turnovers purchase a
|
|
13
|
-
* proportionally longer delay. The first update after idle and an explicit
|
|
14
|
-
* final flush are immediate.
|
|
15
|
-
*/
|
|
16
|
-
export class OutputCapture {
|
|
17
|
-
#maxBytes;
|
|
18
|
-
#maxLines;
|
|
19
|
-
#retain;
|
|
20
|
-
#context;
|
|
21
|
-
#onUpdate;
|
|
22
|
-
#decoder = new TextDecoder();
|
|
23
|
-
#buffer = "";
|
|
24
|
-
#bufferBytes = 0;
|
|
25
|
-
#totalBytes = 0;
|
|
26
|
-
#newlines = 0;
|
|
27
|
-
#endsWithNewline = true;
|
|
28
|
-
#currentLineBytes = 0;
|
|
29
|
-
#lastTerminatedLineBytes = 0;
|
|
30
|
-
#spillPath;
|
|
31
|
-
#disposed = false;
|
|
32
|
-
#publisher;
|
|
33
|
-
constructor(options, context, handlers) {
|
|
34
|
-
this.#maxBytes = options?.limits.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
35
|
-
this.#maxLines = options?.limits.maxLines ?? DEFAULT_MAX_LINES;
|
|
36
|
-
this.#retain = options?.limits.retain ?? "tail";
|
|
37
|
-
this.#context = context;
|
|
38
|
-
this.#onUpdate = handlers.onUpdate;
|
|
39
|
-
if (!Number.isFinite(this.#maxBytes) || this.#maxBytes <= 0) {
|
|
40
|
-
throw new TypeError("Output maxBytes must be a positive finite number");
|
|
41
|
-
}
|
|
42
|
-
if (!Number.isInteger(this.#maxLines) || this.#maxLines <= 0) {
|
|
43
|
-
throw new TypeError("Output maxLines must be a positive integer");
|
|
44
|
-
}
|
|
45
|
-
this.#publisher = new AdaptivePublisher({
|
|
46
|
-
snapshot: () => this.snapshot(),
|
|
47
|
-
update: updateFrom,
|
|
48
|
-
measure: (update) => utf8ByteLength(JSON.stringify(update)),
|
|
49
|
-
publish: (update) => this.#onUpdate?.(update, this.#context),
|
|
50
|
-
onError: handlers.onError,
|
|
51
|
-
minIntervalMs: OUTPUT_MIN_EMIT_INTERVAL_MS,
|
|
52
|
-
targetBytesPerSecond: OUTPUT_TARGET_BYTES_PER_SECOND,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
get truncated() {
|
|
56
|
-
return this.#totalBytes > this.#maxBytes || this.#totalLines() > this.#maxLines;
|
|
57
|
-
}
|
|
58
|
-
push(chunk) {
|
|
59
|
-
if (this.#disposed)
|
|
60
|
-
return;
|
|
61
|
-
if (typeof chunk === "string") {
|
|
62
|
-
this.#appendText(this.#decoder.decode());
|
|
63
|
-
this.#appendText(chunk);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
this.#appendText(this.#decoder.decode(chunk, { stream: true }));
|
|
67
|
-
}
|
|
68
|
-
finish() {
|
|
69
|
-
if (this.#disposed)
|
|
70
|
-
return;
|
|
71
|
-
this.#appendText(this.#decoder.decode());
|
|
72
|
-
}
|
|
73
|
-
setSpillPath(path) {
|
|
74
|
-
if (this.#disposed || this.#spillPath === path)
|
|
75
|
-
return;
|
|
76
|
-
this.#spillPath = path;
|
|
77
|
-
this.#publisher.markDirty();
|
|
78
|
-
this.flush();
|
|
79
|
-
}
|
|
80
|
-
snapshot() {
|
|
81
|
-
const retained = this.#retain === "head"
|
|
82
|
-
? truncateHead(this.#buffer, { maxBytes: this.#maxBytes, maxLines: this.#maxLines })
|
|
83
|
-
: truncateTail(this.#buffer, { maxBytes: this.#maxBytes, maxLines: this.#maxLines });
|
|
84
|
-
const totalLines = this.#totalLines();
|
|
85
|
-
const truncated = this.truncated;
|
|
86
|
-
const { content, ...truncation } = retained;
|
|
87
|
-
// The working buffer preserves the retained edge, so its truncation decision reports the first limit reached.
|
|
88
|
-
const truncatedBy = !truncated
|
|
89
|
-
? null
|
|
90
|
-
: retained.truncated
|
|
91
|
-
? retained.truncatedBy
|
|
92
|
-
: totalLines > this.#maxLines
|
|
93
|
-
? "lines"
|
|
94
|
-
: "bytes";
|
|
95
|
-
const lastLineBytes = this.#endsWithNewline ? this.#lastTerminatedLineBytes : this.#currentLineBytes;
|
|
96
|
-
return {
|
|
97
|
-
text: sanitizeShellOutput(content),
|
|
98
|
-
truncation: {
|
|
99
|
-
...truncation,
|
|
100
|
-
truncated,
|
|
101
|
-
truncatedBy,
|
|
102
|
-
totalBytes: this.#totalBytes,
|
|
103
|
-
totalLines,
|
|
104
|
-
},
|
|
105
|
-
...(this.#spillPath === undefined ? {} : { spillPath: this.#spillPath }),
|
|
106
|
-
...(retained.lastLinePartial ? { lastLineBytes } : {}),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
flush() {
|
|
110
|
-
if (this.#disposed)
|
|
111
|
-
return;
|
|
112
|
-
this.#publisher.flush(true);
|
|
113
|
-
}
|
|
114
|
-
dispose() {
|
|
115
|
-
this.#publisher.dispose();
|
|
116
|
-
this.#disposed = true;
|
|
117
|
-
}
|
|
118
|
-
#appendText(text) {
|
|
119
|
-
if (text === "")
|
|
120
|
-
return;
|
|
121
|
-
const textBytes = utf8ByteLength(text);
|
|
122
|
-
this.#totalBytes += textBytes;
|
|
123
|
-
this.#newlines += countNewlines(text);
|
|
124
|
-
this.#endsWithNewline = text.endsWith("\n");
|
|
125
|
-
const lastNewline = text.lastIndexOf("\n");
|
|
126
|
-
if (lastNewline === -1) {
|
|
127
|
-
this.#currentLineBytes += textBytes;
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
const previousNewline = lastNewline === 0 ? -1 : text.lastIndexOf("\n", lastNewline - 1);
|
|
131
|
-
this.#lastTerminatedLineBytes =
|
|
132
|
-
previousNewline === -1
|
|
133
|
-
? this.#currentLineBytes + utf8ByteLength(text.slice(0, lastNewline))
|
|
134
|
-
: utf8ByteLength(text.slice(previousNewline + 1, lastNewline));
|
|
135
|
-
this.#currentLineBytes = utf8ByteLength(text.slice(lastNewline + 1));
|
|
136
|
-
}
|
|
137
|
-
this.#buffer += text;
|
|
138
|
-
this.#bufferBytes += textBytes;
|
|
139
|
-
// Trimming stops at a UTF-8 character boundary. The four-byte allowance keeps at least twice the byte limit,
|
|
140
|
-
// preserving the evidence for limits and partial lines even when an edge character is multi-byte.
|
|
141
|
-
const guard = this.#maxBytes * 2 + 4;
|
|
142
|
-
if (this.#bufferBytes > guard * 2) {
|
|
143
|
-
this.#buffer =
|
|
144
|
-
this.#retain === "tail"
|
|
145
|
-
? trimToLastUtf8Bytes(this.#buffer, guard)
|
|
146
|
-
: trimToFirstUtf8Bytes(this.#buffer, guard);
|
|
147
|
-
this.#bufferBytes = utf8ByteLength(this.#buffer);
|
|
148
|
-
}
|
|
149
|
-
this.#publisher.markDirty();
|
|
150
|
-
}
|
|
151
|
-
#totalLines() {
|
|
152
|
-
return this.#newlines + (this.#endsWithNewline || this.#totalBytes === 0 ? 0 : 1);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
export function applyShellOutputUpdate(current, update) {
|
|
156
|
-
switch (update.kind) {
|
|
157
|
-
case "replace":
|
|
158
|
-
return update.output;
|
|
159
|
-
case "append":
|
|
160
|
-
return { text: `${current?.text ?? ""}${update.text}`, ...update.metadata };
|
|
161
|
-
case "slide":
|
|
162
|
-
return { text: `${current?.text.slice(update.drop) ?? ""}${update.text}`, ...update.metadata };
|
|
163
|
-
case "metadata":
|
|
164
|
-
return { text: current?.text ?? "", ...update.metadata };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
function updateFrom(previous, current) {
|
|
168
|
-
if (previous === undefined)
|
|
169
|
-
return { kind: "replace", output: current };
|
|
170
|
-
const metadata = {
|
|
171
|
-
truncation: current.truncation,
|
|
172
|
-
...(current.spillPath === undefined ? {} : { spillPath: current.spillPath }),
|
|
173
|
-
...(current.lastLineBytes === undefined ? {} : { lastLineBytes: current.lastLineBytes }),
|
|
174
|
-
};
|
|
175
|
-
if (current.text === previous.text)
|
|
176
|
-
return { kind: "metadata", metadata };
|
|
177
|
-
if (current.text.length > previous.text.length && current.text.slice(0, previous.text.length) === previous.text) {
|
|
178
|
-
return { kind: "append", text: current.text.slice(previous.text.length), metadata };
|
|
179
|
-
}
|
|
180
|
-
const shared = suffixPrefixOverlap(previous.text, current.text, Math.min(previous.text.length, current.text.length, current.truncation.maxBytes * 2));
|
|
181
|
-
if (shared > 0) {
|
|
182
|
-
return {
|
|
183
|
-
kind: "slide",
|
|
184
|
-
drop: previous.text.length - shared,
|
|
185
|
-
text: current.text.slice(shared),
|
|
186
|
-
metadata,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
return { kind: "replace", output: current };
|
|
190
|
-
}
|
|
191
|
-
function suffixPrefixOverlap(before, after, scan) {
|
|
192
|
-
if (before.length === 0 || after.length === 0 || scan === 0)
|
|
193
|
-
return 0;
|
|
194
|
-
const tail = before.length > scan ? before.slice(before.length - scan) : before;
|
|
195
|
-
for (const probeLength of [Math.min(64, after.length), 1]) {
|
|
196
|
-
const probe = after.slice(0, probeLength);
|
|
197
|
-
let candidates = 0;
|
|
198
|
-
for (let index = tail.indexOf(probe); index !== -1; index = tail.indexOf(probe, index + 1)) {
|
|
199
|
-
if (++candidates > 8)
|
|
200
|
-
break;
|
|
201
|
-
const overlapLength = tail.length - index;
|
|
202
|
-
if (overlapLength <= after.length && tail.slice(index) === after.slice(0, overlapLength)) {
|
|
203
|
-
return overlapLength;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (probeLength === 1)
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
return 0;
|
|
210
|
-
}
|
|
211
|
-
export function sanitizeShellOutput(text) {
|
|
212
|
-
return text.replace(INVALID_SHELL_OUTPUT, "");
|
|
213
|
-
}
|
|
214
|
-
function countNewlines(text) {
|
|
215
|
-
let count = 0;
|
|
216
|
-
for (let index = text.indexOf("\n"); index !== -1; index = text.indexOf("\n", index + 1))
|
|
217
|
-
count++;
|
|
218
|
-
return count;
|
|
219
|
-
}
|
|
220
|
-
function trimToLastUtf8Bytes(text, maxBytes) {
|
|
221
|
-
const bytes = textEncoder.encode(text);
|
|
222
|
-
if (bytes.length <= maxBytes)
|
|
223
|
-
return text;
|
|
224
|
-
let start = bytes.length - maxBytes;
|
|
225
|
-
while (start < bytes.length && ((bytes[start] ?? 0) & 0xc0) === 0x80)
|
|
226
|
-
start++;
|
|
227
|
-
return textDecoder.decode(bytes.subarray(start));
|
|
228
|
-
}
|
|
229
|
-
function trimToFirstUtf8Bytes(text, maxBytes) {
|
|
230
|
-
const bytes = textEncoder.encode(text);
|
|
231
|
-
if (bytes.length <= maxBytes)
|
|
232
|
-
return text;
|
|
233
|
-
let end = maxBytes;
|
|
234
|
-
while (end > 0 && ((bytes[end] ?? 0) & 0xc0) === 0x80)
|
|
235
|
-
end--;
|
|
236
|
-
return textDecoder.decode(bytes.subarray(0, end));
|
|
237
|
-
}
|
|
238
|
-
//# sourceMappingURL=output-capture.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"output-capture.js","sourceRoot":"","sources":["../../../src/env/utils/output-capture.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport type { ShellOutputCaptureOptions, ShellOutputMetadata, ShellOutputUpdate, ShellOutputView } from \"../index.ts\";\nimport { AdaptivePublisher } from \"./adaptive-publisher.ts\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateHead, truncateTail, utf8ByteLength } from \"./truncate.ts\";\n\nexport const OUTPUT_MIN_EMIT_INTERVAL_MS = 100;\nexport const OUTPUT_TARGET_BYTES_PER_SECOND = 100 * 1024;\n\nconst INVALID_SHELL_OUTPUT = /[\\x00-\\x08\\x0b-\\x1f\\ufff9-\\ufffb]/g;\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\ninterface OutputCaptureHandlers {\n\tonUpdate?: (update: ShellOutputUpdate, context: Context) => void;\n\tonError(error: unknown): void;\n}\n\n/**\n * Maintains and publishes one bounded shell-output view.\n *\n * Writes received while publication is rate-limited collapse into the latest\n * view. Small changes remain responsive; complete window turnovers purchase a\n * proportionally longer delay. The first update after idle and an explicit\n * final flush are immediate.\n */\nexport class OutputCapture {\n\treadonly #maxBytes: number;\n\treadonly #maxLines: number;\n\treadonly #retain: \"head\" | \"tail\";\n\treadonly #context: Context;\n\treadonly #onUpdate: OutputCaptureHandlers[\"onUpdate\"];\n\n\treadonly #decoder = new TextDecoder();\n\t#buffer = \"\";\n\t#bufferBytes = 0;\n\t#totalBytes = 0;\n\t#newlines = 0;\n\t#endsWithNewline = true;\n\t#currentLineBytes = 0;\n\t#lastTerminatedLineBytes = 0;\n\t#spillPath: string | undefined;\n\t#disposed = false;\n\treadonly #publisher: AdaptivePublisher<ShellOutputView, ShellOutputUpdate>;\n\n\tconstructor(options: ShellOutputCaptureOptions | undefined, context: Context, handlers: OutputCaptureHandlers) {\n\t\tthis.#maxBytes = options?.limits.maxBytes ?? DEFAULT_MAX_BYTES;\n\t\tthis.#maxLines = options?.limits.maxLines ?? DEFAULT_MAX_LINES;\n\t\tthis.#retain = options?.limits.retain ?? \"tail\";\n\t\tthis.#context = context;\n\t\tthis.#onUpdate = handlers.onUpdate;\n\t\tif (!Number.isFinite(this.#maxBytes) || this.#maxBytes <= 0) {\n\t\t\tthrow new TypeError(\"Output maxBytes must be a positive finite number\");\n\t\t}\n\t\tif (!Number.isInteger(this.#maxLines) || this.#maxLines <= 0) {\n\t\t\tthrow new TypeError(\"Output maxLines must be a positive integer\");\n\t\t}\n\t\tthis.#publisher = new AdaptivePublisher({\n\t\t\tsnapshot: () => this.snapshot(),\n\t\t\tupdate: updateFrom,\n\t\t\tmeasure: (update) => utf8ByteLength(JSON.stringify(update)),\n\t\t\tpublish: (update) => this.#onUpdate?.(update, this.#context),\n\t\t\tonError: handlers.onError,\n\t\t\tminIntervalMs: OUTPUT_MIN_EMIT_INTERVAL_MS,\n\t\t\ttargetBytesPerSecond: OUTPUT_TARGET_BYTES_PER_SECOND,\n\t\t});\n\t}\n\n\tget truncated(): boolean {\n\t\treturn this.#totalBytes > this.#maxBytes || this.#totalLines() > this.#maxLines;\n\t}\n\n\tpush(chunk: string | Uint8Array): void {\n\t\tif (this.#disposed) return;\n\t\tif (typeof chunk === \"string\") {\n\t\t\tthis.#appendText(this.#decoder.decode());\n\t\t\tthis.#appendText(chunk);\n\t\t\treturn;\n\t\t}\n\t\tthis.#appendText(this.#decoder.decode(chunk, { stream: true }));\n\t}\n\n\tfinish(): void {\n\t\tif (this.#disposed) return;\n\t\tthis.#appendText(this.#decoder.decode());\n\t}\n\n\tsetSpillPath(path: string): void {\n\t\tif (this.#disposed || this.#spillPath === path) return;\n\t\tthis.#spillPath = path;\n\t\tthis.#publisher.markDirty();\n\t\tthis.flush();\n\t}\n\n\tsnapshot(): ShellOutputView {\n\t\tconst retained =\n\t\t\tthis.#retain === \"head\"\n\t\t\t\t? truncateHead(this.#buffer, { maxBytes: this.#maxBytes, maxLines: this.#maxLines })\n\t\t\t\t: truncateTail(this.#buffer, { maxBytes: this.#maxBytes, maxLines: this.#maxLines });\n\t\tconst totalLines = this.#totalLines();\n\t\tconst truncated = this.truncated;\n\t\tconst { content, ...truncation } = retained;\n\t\t// The working buffer preserves the retained edge, so its truncation decision reports the first limit reached.\n\t\tconst truncatedBy = !truncated\n\t\t\t? null\n\t\t\t: retained.truncated\n\t\t\t\t? retained.truncatedBy\n\t\t\t\t: totalLines > this.#maxLines\n\t\t\t\t\t? \"lines\"\n\t\t\t\t\t: \"bytes\";\n\t\tconst lastLineBytes = this.#endsWithNewline ? this.#lastTerminatedLineBytes : this.#currentLineBytes;\n\t\treturn {\n\t\t\ttext: sanitizeShellOutput(content),\n\t\t\ttruncation: {\n\t\t\t\t...truncation,\n\t\t\t\ttruncated,\n\t\t\t\ttruncatedBy,\n\t\t\t\ttotalBytes: this.#totalBytes,\n\t\t\t\ttotalLines,\n\t\t\t},\n\t\t\t...(this.#spillPath === undefined ? {} : { spillPath: this.#spillPath }),\n\t\t\t...(retained.lastLinePartial ? { lastLineBytes } : {}),\n\t\t};\n\t}\n\n\tflush(): void {\n\t\tif (this.#disposed) return;\n\t\tthis.#publisher.flush(true);\n\t}\n\n\tdispose(): void {\n\t\tthis.#publisher.dispose();\n\t\tthis.#disposed = true;\n\t}\n\n\t#appendText(text: string): void {\n\t\tif (text === \"\") return;\n\t\tconst textBytes = utf8ByteLength(text);\n\t\tthis.#totalBytes += textBytes;\n\t\tthis.#newlines += countNewlines(text);\n\t\tthis.#endsWithNewline = text.endsWith(\"\\n\");\n\t\tconst lastNewline = text.lastIndexOf(\"\\n\");\n\t\tif (lastNewline === -1) {\n\t\t\tthis.#currentLineBytes += textBytes;\n\t\t} else {\n\t\t\tconst previousNewline = lastNewline === 0 ? -1 : text.lastIndexOf(\"\\n\", lastNewline - 1);\n\t\t\tthis.#lastTerminatedLineBytes =\n\t\t\t\tpreviousNewline === -1\n\t\t\t\t\t? this.#currentLineBytes + utf8ByteLength(text.slice(0, lastNewline))\n\t\t\t\t\t: utf8ByteLength(text.slice(previousNewline + 1, lastNewline));\n\t\t\tthis.#currentLineBytes = utf8ByteLength(text.slice(lastNewline + 1));\n\t\t}\n\t\tthis.#buffer += text;\n\t\tthis.#bufferBytes += textBytes;\n\n\t\t// Trimming stops at a UTF-8 character boundary. The four-byte allowance keeps at least twice the byte limit,\n\t\t// preserving the evidence for limits and partial lines even when an edge character is multi-byte.\n\t\tconst guard = this.#maxBytes * 2 + 4;\n\t\tif (this.#bufferBytes > guard * 2) {\n\t\t\tthis.#buffer =\n\t\t\t\tthis.#retain === \"tail\"\n\t\t\t\t\t? trimToLastUtf8Bytes(this.#buffer, guard)\n\t\t\t\t\t: trimToFirstUtf8Bytes(this.#buffer, guard);\n\t\t\tthis.#bufferBytes = utf8ByteLength(this.#buffer);\n\t\t}\n\t\tthis.#publisher.markDirty();\n\t}\n\n\t#totalLines(): number {\n\t\treturn this.#newlines + (this.#endsWithNewline || this.#totalBytes === 0 ? 0 : 1);\n\t}\n}\n\nexport function applyShellOutputUpdate(\n\tcurrent: ShellOutputView | undefined,\n\tupdate: ShellOutputUpdate,\n): ShellOutputView {\n\tswitch (update.kind) {\n\t\tcase \"replace\":\n\t\t\treturn update.output;\n\t\tcase \"append\":\n\t\t\treturn { text: `${current?.text ?? \"\"}${update.text}`, ...update.metadata };\n\t\tcase \"slide\":\n\t\t\treturn { text: `${current?.text.slice(update.drop) ?? \"\"}${update.text}`, ...update.metadata };\n\t\tcase \"metadata\":\n\t\t\treturn { text: current?.text ?? \"\", ...update.metadata };\n\t}\n}\n\nfunction updateFrom(previous: ShellOutputView | undefined, current: ShellOutputView): ShellOutputUpdate {\n\tif (previous === undefined) return { kind: \"replace\", output: current };\n\tconst metadata: ShellOutputMetadata = {\n\t\ttruncation: current.truncation,\n\t\t...(current.spillPath === undefined ? {} : { spillPath: current.spillPath }),\n\t\t...(current.lastLineBytes === undefined ? {} : { lastLineBytes: current.lastLineBytes }),\n\t};\n\tif (current.text === previous.text) return { kind: \"metadata\", metadata };\n\tif (current.text.length > previous.text.length && current.text.slice(0, previous.text.length) === previous.text) {\n\t\treturn { kind: \"append\", text: current.text.slice(previous.text.length), metadata };\n\t}\n\tconst shared = suffixPrefixOverlap(\n\t\tprevious.text,\n\t\tcurrent.text,\n\t\tMath.min(previous.text.length, current.text.length, current.truncation.maxBytes * 2),\n\t);\n\tif (shared > 0) {\n\t\treturn {\n\t\t\tkind: \"slide\",\n\t\t\tdrop: previous.text.length - shared,\n\t\t\ttext: current.text.slice(shared),\n\t\t\tmetadata,\n\t\t};\n\t}\n\treturn { kind: \"replace\", output: current };\n}\n\nfunction suffixPrefixOverlap(before: string, after: string, scan: number): number {\n\tif (before.length === 0 || after.length === 0 || scan === 0) return 0;\n\tconst tail = before.length > scan ? before.slice(before.length - scan) : before;\n\tfor (const probeLength of [Math.min(64, after.length), 1]) {\n\t\tconst probe = after.slice(0, probeLength);\n\t\tlet candidates = 0;\n\t\tfor (let index = tail.indexOf(probe); index !== -1; index = tail.indexOf(probe, index + 1)) {\n\t\t\tif (++candidates > 8) break;\n\t\t\tconst overlapLength = tail.length - index;\n\t\t\tif (overlapLength <= after.length && tail.slice(index) === after.slice(0, overlapLength)) {\n\t\t\t\treturn overlapLength;\n\t\t\t}\n\t\t}\n\t\tif (probeLength === 1) break;\n\t}\n\treturn 0;\n}\n\nexport function sanitizeShellOutput(text: string): string {\n\treturn text.replace(INVALID_SHELL_OUTPUT, \"\");\n}\n\nfunction countNewlines(text: string): number {\n\tlet count = 0;\n\tfor (let index = text.indexOf(\"\\n\"); index !== -1; index = text.indexOf(\"\\n\", index + 1)) count++;\n\treturn count;\n}\n\nfunction trimToLastUtf8Bytes(text: string, maxBytes: number): string {\n\tconst bytes = textEncoder.encode(text);\n\tif (bytes.length <= maxBytes) return text;\n\tlet start = bytes.length - maxBytes;\n\twhile (start < bytes.length && ((bytes[start] ?? 0) & 0xc0) === 0x80) start++;\n\treturn textDecoder.decode(bytes.subarray(start));\n}\n\nfunction trimToFirstUtf8Bytes(text: string, maxBytes: number): string {\n\tconst bytes = textEncoder.encode(text);\n\tif (bytes.length <= maxBytes) return text;\n\tlet end = maxBytes;\n\twhile (end > 0 && ((bytes[end] ?? 0) & 0xc0) === 0x80) end--;\n\treturn textDecoder.decode(bytes.subarray(0, end));\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../../../src/env/utils/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAC3C,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAChC,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC;IACtC,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,eAAe,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,qBAAqB,EAAE,OAAO,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AASD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0BtD;AAgCD;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAgF/F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAuE/F;AA2CD;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAA6B,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAKzC"}
|