@earendil-works/pi-durable 0.99.1 → 1.0.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 +7 -0
- package/README.md +557 -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 +18 -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 +2 -0
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +53 -58
- package/dist/env/node.js.map +1 -1
- package/dist/harness/agent.d.ts +31 -0
- package/dist/harness/agent.d.ts.map +1 -0
- package/dist/harness/agent.js +209 -0
- package/dist/harness/agent.js.map +1 -0
- package/dist/harness/compaction.d.ts +56 -0
- package/dist/harness/compaction.d.ts.map +1 -0
- package/dist/harness/compaction.js +382 -0
- package/dist/harness/compaction.js.map +1 -0
- 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/define.d.ts +18 -0
- package/dist/harness/define.d.ts.map +1 -0
- package/dist/harness/define.js +25 -0
- package/dist/harness/define.js.map +1 -0
- 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 +36 -7
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +356 -82
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +141 -106
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +59 -0
- package/dist/harness/inbox.d.ts.map +1 -0
- package/dist/harness/inbox.js +97 -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 +9 -6
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +57 -14
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/registry.d.ts +2 -3
- package/dist/harness/registry.d.ts.map +1 -1
- package/dist/harness/registry.js +69 -277
- package/dist/harness/registry.js.map +1 -1
- package/dist/harness/scheduler.d.ts +42 -12
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +649 -75
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +14 -9
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +76 -34
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +49 -0
- package/dist/harness/task-graph.d.ts.map +1 -0
- package/dist/harness/task-graph.js +163 -0
- package/dist/harness/task-graph.js.map +1 -0
- package/dist/harness/tool.d.ts +36 -0
- package/dist/harness/tool.d.ts.map +1 -0
- package/dist/harness/tool.js +415 -0
- package/dist/harness/tool.js.map +1 -0
- package/dist/harness/types.d.ts +330 -125
- 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 +189 -0
- package/dist/harness/view.js.map +1 -0
- package/dist/index.d.ts +13 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- 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 +23 -5
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +12 -3
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +90 -31
- 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/database.d.ts +26 -19
- package/dist/storage/sqlite/database.d.ts.map +1 -1
- package/dist/storage/sqlite/database.js.map +1 -1
- package/dist/storage/sqlite/index.d.ts +1 -1
- package/dist/storage/sqlite/index.d.ts.map +1 -1
- package/dist/storage/sqlite/index.js.map +1 -1
- package/dist/storage/sqlite/migrations.js +7 -7
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/node.d.ts +23 -7
- package/dist/storage/sqlite/node.d.ts.map +1 -1
- package/dist/storage/sqlite/node.js +129 -47
- package/dist/storage/sqlite/node.js.map +1 -1
- package/dist/storage/sqlite/storage.d.ts +12 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +121 -129
- package/dist/storage/sqlite/storage.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 +70 -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 +117 -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 +9 -0
- package/dist/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/tools/file-mutation-queue.js +52 -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 +23 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +15 -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 +101 -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 +31 -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 +97 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +32 -14
- 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/dist/harness/config.d.ts +0 -20
- package/dist/harness/config.d.ts.map +0 -1
- package/dist/harness/config.js +0 -18
- package/dist/harness/config.js.map +0 -1
- package/docs/chord-delta-findings.md +0 -358
- package/docs/pico-v5-chord-usage.md +0 -373
- package/docs/pico-v5-handoff.md +0 -581
- package/docs/pico-v5.md +0 -3084
|
@@ -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"}
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared truncation utilities for tool outputs.
|
|
3
|
-
*
|
|
4
|
-
* Truncation is based on two independent limits - whichever is hit first wins:
|
|
5
|
-
* - Line limit (default: 2000 lines)
|
|
6
|
-
* - Byte limit (default: 50KB)
|
|
7
|
-
*
|
|
8
|
-
* Never returns partial lines (except bash tail truncation edge case).
|
|
9
|
-
*/
|
|
10
|
-
export const DEFAULT_MAX_LINES = 2000;
|
|
11
|
-
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
|
-
export const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line
|
|
13
|
-
const runtimeBuffer = globalThis.Buffer;
|
|
14
|
-
const nonAsciiPattern = /[^\x00-\x7f]/;
|
|
15
|
-
export function utf8ByteLength(content) {
|
|
16
|
-
if (runtimeBuffer)
|
|
17
|
-
return runtimeBuffer.byteLength(content, "utf8");
|
|
18
|
-
const firstNonAscii = content.search(nonAsciiPattern);
|
|
19
|
-
if (firstNonAscii === -1)
|
|
20
|
-
return content.length;
|
|
21
|
-
let bytes = firstNonAscii;
|
|
22
|
-
for (let i = firstNonAscii; i < content.length; i++) {
|
|
23
|
-
const code = content.charCodeAt(i);
|
|
24
|
-
if (code <= 0x7f) {
|
|
25
|
-
bytes += 1;
|
|
26
|
-
}
|
|
27
|
-
else if (code <= 0x7ff) {
|
|
28
|
-
bytes += 2;
|
|
29
|
-
}
|
|
30
|
-
else if (code >= 0xd800 && code <= 0xdbff && i + 1 < content.length) {
|
|
31
|
-
const next = content.charCodeAt(i + 1);
|
|
32
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
33
|
-
bytes += 4;
|
|
34
|
-
i++;
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
bytes += 3;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
bytes += 3;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return bytes;
|
|
45
|
-
}
|
|
46
|
-
function splitLinesForCounting(content) {
|
|
47
|
-
if (content.length === 0)
|
|
48
|
-
return [];
|
|
49
|
-
const lines = content.split("\n");
|
|
50
|
-
if (content.endsWith("\n"))
|
|
51
|
-
lines.pop();
|
|
52
|
-
return lines;
|
|
53
|
-
}
|
|
54
|
-
function replaceUnpairedSurrogates(content) {
|
|
55
|
-
let output = "";
|
|
56
|
-
for (let i = 0; i < content.length; i++) {
|
|
57
|
-
const code = content.charCodeAt(i);
|
|
58
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
59
|
-
if (i + 1 < content.length) {
|
|
60
|
-
const next = content.charCodeAt(i + 1);
|
|
61
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
62
|
-
output += content[i] + content[i + 1];
|
|
63
|
-
i++;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
output += "�";
|
|
68
|
-
}
|
|
69
|
-
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
70
|
-
output += "�";
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
output += content[i];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return output;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Format bytes as human-readable size.
|
|
80
|
-
*/
|
|
81
|
-
export function formatSize(bytes) {
|
|
82
|
-
if (bytes < 1024) {
|
|
83
|
-
return `${bytes}B`;
|
|
84
|
-
}
|
|
85
|
-
else if (bytes < 1024 * 1024) {
|
|
86
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Truncate content from the head (keep first N lines/bytes).
|
|
94
|
-
* Suitable for file reads where you want to see the beginning.
|
|
95
|
-
*
|
|
96
|
-
* Never returns partial lines. If first line exceeds byte limit,
|
|
97
|
-
* returns empty content with firstLineExceedsLimit=true.
|
|
98
|
-
*/
|
|
99
|
-
export function truncateHead(content, options = {}) {
|
|
100
|
-
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
101
|
-
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
102
|
-
const totalBytes = utf8ByteLength(content);
|
|
103
|
-
const lines = splitLinesForCounting(content);
|
|
104
|
-
const totalLines = lines.length;
|
|
105
|
-
// Check if no truncation needed
|
|
106
|
-
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
107
|
-
return {
|
|
108
|
-
content,
|
|
109
|
-
truncated: false,
|
|
110
|
-
truncatedBy: null,
|
|
111
|
-
totalLines,
|
|
112
|
-
totalBytes,
|
|
113
|
-
outputLines: totalLines,
|
|
114
|
-
outputBytes: totalBytes,
|
|
115
|
-
lastLinePartial: false,
|
|
116
|
-
firstLineExceedsLimit: false,
|
|
117
|
-
maxLines,
|
|
118
|
-
maxBytes,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
// Check if first line alone exceeds byte limit
|
|
122
|
-
const firstLineBytes = utf8ByteLength(lines[0]);
|
|
123
|
-
if (firstLineBytes > maxBytes) {
|
|
124
|
-
return {
|
|
125
|
-
content: "",
|
|
126
|
-
truncated: true,
|
|
127
|
-
truncatedBy: "bytes",
|
|
128
|
-
totalLines,
|
|
129
|
-
totalBytes,
|
|
130
|
-
outputLines: 0,
|
|
131
|
-
outputBytes: 0,
|
|
132
|
-
lastLinePartial: false,
|
|
133
|
-
firstLineExceedsLimit: true,
|
|
134
|
-
maxLines,
|
|
135
|
-
maxBytes,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
// Collect complete lines that fit
|
|
139
|
-
const outputLinesArr = [];
|
|
140
|
-
let outputBytesCount = 0;
|
|
141
|
-
let truncatedBy = "lines";
|
|
142
|
-
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
143
|
-
const line = lines[i];
|
|
144
|
-
const lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline
|
|
145
|
-
if (outputBytesCount + lineBytes > maxBytes) {
|
|
146
|
-
truncatedBy = "bytes";
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
outputLinesArr.push(line);
|
|
150
|
-
outputBytesCount += lineBytes;
|
|
151
|
-
}
|
|
152
|
-
// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.
|
|
153
|
-
if (truncatedBy !== "bytes")
|
|
154
|
-
truncatedBy = outputLinesArr.length < totalLines ? "lines" : "bytes";
|
|
155
|
-
const outputContent = outputLinesArr.join("\n");
|
|
156
|
-
const finalOutputBytes = utf8ByteLength(outputContent);
|
|
157
|
-
return {
|
|
158
|
-
content: outputContent,
|
|
159
|
-
truncated: true,
|
|
160
|
-
truncatedBy,
|
|
161
|
-
totalLines,
|
|
162
|
-
totalBytes,
|
|
163
|
-
outputLines: outputLinesArr.length,
|
|
164
|
-
outputBytes: finalOutputBytes,
|
|
165
|
-
lastLinePartial: false,
|
|
166
|
-
firstLineExceedsLimit: false,
|
|
167
|
-
maxLines,
|
|
168
|
-
maxBytes,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Truncate content from the tail (keep last N lines/bytes).
|
|
173
|
-
* Suitable for bash output where you want to see the end (errors, final results).
|
|
174
|
-
*
|
|
175
|
-
* May return partial first line if the last line of original content exceeds byte limit.
|
|
176
|
-
*/
|
|
177
|
-
export function truncateTail(content, options = {}) {
|
|
178
|
-
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
179
|
-
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
180
|
-
const totalBytes = utf8ByteLength(content);
|
|
181
|
-
const lines = splitLinesForCounting(content);
|
|
182
|
-
const totalLines = lines.length;
|
|
183
|
-
// Check if no truncation needed
|
|
184
|
-
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
185
|
-
return {
|
|
186
|
-
content,
|
|
187
|
-
truncated: false,
|
|
188
|
-
truncatedBy: null,
|
|
189
|
-
totalLines,
|
|
190
|
-
totalBytes,
|
|
191
|
-
outputLines: totalLines,
|
|
192
|
-
outputBytes: totalBytes,
|
|
193
|
-
lastLinePartial: false,
|
|
194
|
-
firstLineExceedsLimit: false,
|
|
195
|
-
maxLines,
|
|
196
|
-
maxBytes,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
// Work backwards from the end
|
|
200
|
-
const outputLinesArr = [];
|
|
201
|
-
let outputBytesCount = 0;
|
|
202
|
-
let truncatedBy = "lines";
|
|
203
|
-
let lastLinePartial = false;
|
|
204
|
-
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
205
|
-
const line = lines[i];
|
|
206
|
-
const lineBytes = utf8ByteLength(line) + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
|
|
207
|
-
if (outputBytesCount + lineBytes > maxBytes) {
|
|
208
|
-
truncatedBy = "bytes";
|
|
209
|
-
// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
|
|
210
|
-
// take the end of the line (partial)
|
|
211
|
-
if (outputLinesArr.length === 0) {
|
|
212
|
-
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
213
|
-
outputLinesArr.unshift(truncatedLine);
|
|
214
|
-
outputBytesCount = utf8ByteLength(truncatedLine);
|
|
215
|
-
lastLinePartial = true;
|
|
216
|
-
}
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
outputLinesArr.unshift(line);
|
|
220
|
-
outputBytesCount += lineBytes;
|
|
221
|
-
}
|
|
222
|
-
// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.
|
|
223
|
-
if (truncatedBy !== "bytes")
|
|
224
|
-
truncatedBy = outputLinesArr.length < totalLines ? "lines" : "bytes";
|
|
225
|
-
const outputContent = outputLinesArr.join("\n");
|
|
226
|
-
const finalOutputBytes = utf8ByteLength(outputContent);
|
|
227
|
-
return {
|
|
228
|
-
content: outputContent,
|
|
229
|
-
truncated: true,
|
|
230
|
-
truncatedBy,
|
|
231
|
-
totalLines,
|
|
232
|
-
totalBytes,
|
|
233
|
-
outputLines: outputLinesArr.length,
|
|
234
|
-
outputBytes: finalOutputBytes,
|
|
235
|
-
lastLinePartial,
|
|
236
|
-
firstLineExceedsLimit: false,
|
|
237
|
-
maxLines,
|
|
238
|
-
maxBytes,
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Truncate a string to fit within a byte limit (from the end).
|
|
243
|
-
* Handles multi-byte UTF-8 characters correctly.
|
|
244
|
-
*/
|
|
245
|
-
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
246
|
-
if (maxBytes <= 0)
|
|
247
|
-
return "";
|
|
248
|
-
let outputBytes = 0;
|
|
249
|
-
let start = str.length;
|
|
250
|
-
let needsReplacement = false;
|
|
251
|
-
for (let i = str.length; i > 0;) {
|
|
252
|
-
let characterStart = i - 1;
|
|
253
|
-
const code = str.charCodeAt(characterStart);
|
|
254
|
-
let characterBytes;
|
|
255
|
-
let unpairedSurrogate = false;
|
|
256
|
-
if (code >= 0xdc00 && code <= 0xdfff && characterStart > 0) {
|
|
257
|
-
const previous = str.charCodeAt(characterStart - 1);
|
|
258
|
-
if (previous >= 0xd800 && previous <= 0xdbff) {
|
|
259
|
-
characterStart--;
|
|
260
|
-
characterBytes = 4;
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
characterBytes = 3;
|
|
264
|
-
unpairedSurrogate = true;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
else if (code >= 0xd800 && code <= 0xdfff) {
|
|
268
|
-
characterBytes = 3;
|
|
269
|
-
unpairedSurrogate = true;
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
characterBytes = code <= 0x7f ? 1 : code <= 0x7ff ? 2 : 3;
|
|
273
|
-
}
|
|
274
|
-
if (outputBytes + characterBytes > maxBytes)
|
|
275
|
-
break;
|
|
276
|
-
outputBytes += characterBytes;
|
|
277
|
-
start = characterStart;
|
|
278
|
-
needsReplacement ||= unpairedSurrogate;
|
|
279
|
-
i = characterStart;
|
|
280
|
-
}
|
|
281
|
-
const output = str.slice(start);
|
|
282
|
-
return needsReplacement ? replaceUnpairedSurrogates(output) : output;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Truncate a single line to max characters, adding [truncated] suffix.
|
|
286
|
-
* Used for grep match lines.
|
|
287
|
-
*/
|
|
288
|
-
export function truncateLine(line, maxChars = GREP_MAX_LINE_LENGTH) {
|
|
289
|
-
if (line.length <= maxChars) {
|
|
290
|
-
return { text: line, wasTruncated: false };
|
|
291
|
-
}
|
|
292
|
-
return { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };
|
|
293
|
-
}
|
|
294
|
-
//# sourceMappingURL=truncate.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../../src/env/utils/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO;AACnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC,CAAC,gCAAgC;AAsCzE,MAAM,aAAa,GAAI,UAAyC,CAAC,MAAM,CAAC;AACxE,MAAM,eAAe,GAAG,cAAc,CAAC;AAEvC,MAAM,UAAU,cAAc,CAAC,OAAe;IAC7C,IAAI,aAAa;QAAE,OAAO,aAAa,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAEpE,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACtD,IAAI,aAAa,KAAK,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC;IAEhD,IAAI,KAAK,GAAG,aAAa,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,aAAa,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YAClB,KAAK,IAAI,CAAC,CAAC;QACZ,CAAC;aAAM,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,KAAK,IAAI,CAAC,CAAC;QACZ,CAAC;aAAM,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YACvE,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACvC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;gBACtC,KAAK,IAAI,CAAC,CAAC;gBACX,CAAC,EAAE,CAAC;YACL,CAAC;iBAAM,CAAC;gBACP,KAAK,IAAI,CAAC,CAAC;YACZ,CAAC;QACF,CAAC;aAAM,CAAC;YACP,KAAK,IAAI,CAAC,CAAC;QACZ,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,qBAAqB,CAAC,OAAe;IAC7C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IACxC,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,yBAAyB,CAAC,OAAe;IACjD,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACvC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;oBACtC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBACtC,CAAC,EAAE,CAAC;oBACJ,SAAS;gBACV,CAAC;YACF,CAAC;YACD,MAAM,IAAI,GAAG,CAAC;QACf,CAAC;aAAM,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,CAAC;QACf,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QAClB,OAAO,GAAG,KAAK,GAAG,CAAC;IACpB,CAAC;SAAM,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAChC,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACzC,CAAC;SAAM,CAAC;QACP,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAClD,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,OAAO,GAAsB,EAAE;IAC5E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IAEvD,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IAEhC,gCAAgC;IAChC,IAAI,UAAU,IAAI,QAAQ,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;QACtD,OAAO;YACN,OAAO;YACP,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,IAAI;YACjB,UAAU;YACV,UAAU;YACV,WAAW,EAAE,UAAU;YACvB,WAAW,EAAE,UAAU;YACvB,eAAe,EAAE,KAAK;YACtB,qBAAqB,EAAE,KAAK;YAC5B,QAAQ;YACR,QAAQ;SACR,CAAC;IACH,CAAC;IAED,+CAA+C;IAC/C,MAAM,cAAc,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,IAAI,cAAc,GAAG,QAAQ,EAAE,CAAC;QAC/B,OAAO;YACN,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,OAAO;YACpB,UAAU;YACV,UAAU;YACV,WAAW,EAAE,CAAC;YACd,WAAW,EAAE,CAAC;YACd,eAAe,EAAE,KAAK;YACtB,qBAAqB,EAAE,IAAI;YAC3B,QAAQ;YACR,QAAQ;SACR,CAAC;IACH,CAAC;IAED,kCAAkC;IAClC,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,WAAW,GAAsB,OAAO,CAAC;IAE7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;QAE3E,IAAI,gBAAgB,GAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;YAC7C,WAAW,GAAG,OAAO,CAAC;YACtB,MAAM;QACP,CAAC;QAED,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,gBAAgB,IAAI,SAAS,CAAC;IAC/B,CAAC;IAED,0HAA0H;IAC1H,IAAI,WAAW,KAAK,OAAO;QAAE,WAAW,GAAG,cAAc,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAElG,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,gBAAgB,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IAEvD,OAAO;QACN,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,IAAI;QACf,WAAW;QACX,UAAU;QACV,UAAU;QACV,WAAW,EAAE,cAAc,CAAC,MAAM;QAClC,WAAW,EAAE,gBAAgB;QAC7B,eAAe,EAAE,KAAK;QACtB,qBAAqB,EAAE,KAAK;QAC5B,QAAQ;QACR,QAAQ;KACR,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,OAAO,GAAsB,EAAE;IAC5E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IAEvD,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IAEhC,gCAAgC;IAChC,IAAI,UAAU,IAAI,QAAQ,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;QACtD,OAAO;YACN,OAAO;YACP,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,IAAI;YACjB,UAAU;YACV,UAAU;YACV,WAAW,EAAE,UAAU;YACvB,WAAW,EAAE,UAAU;YACvB,eAAe,EAAE,KAAK;YACtB,qBAAqB,EAAE,KAAK;YAC5B,QAAQ;YACR,QAAQ;SACR,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,WAAW,GAAsB,OAAO,CAAC;IAC7C,IAAI,eAAe,GAAG,KAAK,CAAC;IAE5B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QAChF,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;QAE/F,IAAI,gBAAgB,GAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;YAC7C,WAAW,GAAG,OAAO,CAAC;YACtB,+EAA+E;YAC/E,qCAAqC;YACrC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,aAAa,GAAG,4BAA4B,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBACnE,cAAc,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;gBACtC,gBAAgB,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;gBACjD,eAAe,GAAG,IAAI,CAAC;YACxB,CAAC;YACD,MAAM;QACP,CAAC;QAED,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,gBAAgB,IAAI,SAAS,CAAC;IAC/B,CAAC;IAED,0HAA0H;IAC1H,IAAI,WAAW,KAAK,OAAO;QAAE,WAAW,GAAG,cAAc,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAElG,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,gBAAgB,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IAEvD,OAAO;QACN,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,IAAI;QACf,WAAW;QACX,UAAU;QACV,UAAU;QACV,WAAW,EAAE,cAAc,CAAC,MAAM;QAClC,WAAW,EAAE,gBAAgB;QAC7B,eAAe;QACf,qBAAqB,EAAE,KAAK;QAC5B,QAAQ;QACR,QAAQ;KACR,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,4BAA4B,CAAC,GAAW,EAAE,QAAgB;IAClE,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAE7B,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC;IACvB,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAI,CAAC;QAClC,IAAI,cAAc,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QAC5C,IAAI,cAAsB,CAAC;QAC3B,IAAI,iBAAiB,GAAG,KAAK,CAAC;QAC9B,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;YAC5D,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;YACpD,IAAI,QAAQ,IAAI,MAAM,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;gBAC9C,cAAc,EAAE,CAAC;gBACjB,cAAc,GAAG,CAAC,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACP,cAAc,GAAG,CAAC,CAAC;gBACnB,iBAAiB,GAAG,IAAI,CAAC;YAC1B,CAAC;QACF,CAAC;aAAM,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YAC7C,cAAc,GAAG,CAAC,CAAC;YACnB,iBAAiB,GAAG,IAAI,CAAC;QAC1B,CAAC;aAAM,CAAC;YACP,cAAc,GAAG,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,WAAW,GAAG,cAAc,GAAG,QAAQ;YAAE,MAAM;QACnD,WAAW,IAAI,cAAc,CAAC;QAC9B,KAAK,GAAG,cAAc,CAAC;QACvB,gBAAgB,KAAK,iBAAiB,CAAC;QACvC,CAAC,GAAG,cAAc,CAAC;IACpB,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,gBAAgB,CAAC,CAAC,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC3B,IAAY,EACZ,QAAQ,GAAW,oBAAoB;IAEvC,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,iBAAiB,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AAClF,CAAC","sourcesContent":["/**\n * Shared truncation utilities for tool outputs.\n *\n * Truncation is based on two independent limits - whichever is hit first wins:\n * - Line limit (default: 2000 lines)\n * - Byte limit (default: 50KB)\n *\n * Never returns partial lines (except bash tail truncation edge case).\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\nexport const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line\n\nexport interface TruncationResult {\n\t/** The truncated content */\n\tcontent: string;\n\t/** Whether truncation occurred */\n\ttruncated: boolean;\n\t/** Which limit was hit: \"lines\", \"bytes\", or null if not truncated */\n\ttruncatedBy: \"lines\" | \"bytes\" | null;\n\t/** Total number of lines in the original content */\n\ttotalLines: number;\n\t/** Total number of bytes in the original content */\n\ttotalBytes: number;\n\t/** Number of complete lines in the truncated output */\n\toutputLines: number;\n\t/** Number of bytes in the truncated output */\n\toutputBytes: number;\n\t/** Whether the last line was partially truncated (only for tail truncation edge case) */\n\tlastLinePartial: boolean;\n\t/** Whether the first line exceeded the byte limit (for head truncation) */\n\tfirstLineExceedsLimit: boolean;\n\t/** The max lines limit that was applied */\n\tmaxLines: number;\n\t/** The max bytes limit that was applied */\n\tmaxBytes: number;\n}\n\nexport interface TruncationOptions {\n\t/** Maximum number of lines (default: 2000) */\n\tmaxLines?: number;\n\t/** Maximum number of bytes (default: 50KB) */\n\tmaxBytes?: number;\n}\n\ninterface RuntimeBuffer {\n\tbyteLength(content: string, encoding: \"utf8\"): number;\n}\n\nconst runtimeBuffer = (globalThis as { Buffer?: RuntimeBuffer }).Buffer;\nconst nonAsciiPattern = /[^\\x00-\\x7f]/;\n\nexport function utf8ByteLength(content: string): number {\n\tif (runtimeBuffer) return runtimeBuffer.byteLength(content, \"utf8\");\n\n\tconst firstNonAscii = content.search(nonAsciiPattern);\n\tif (firstNonAscii === -1) return content.length;\n\n\tlet bytes = firstNonAscii;\n\tfor (let i = firstNonAscii; i < content.length; i++) {\n\t\tconst code = content.charCodeAt(i);\n\t\tif (code <= 0x7f) {\n\t\t\tbytes += 1;\n\t\t} else if (code <= 0x7ff) {\n\t\t\tbytes += 2;\n\t\t} else if (code >= 0xd800 && code <= 0xdbff && i + 1 < content.length) {\n\t\t\tconst next = content.charCodeAt(i + 1);\n\t\t\tif (next >= 0xdc00 && next <= 0xdfff) {\n\t\t\t\tbytes += 4;\n\t\t\t\ti++;\n\t\t\t} else {\n\t\t\t\tbytes += 3;\n\t\t\t}\n\t\t} else {\n\t\t\tbytes += 3;\n\t\t}\n\t}\n\treturn bytes;\n}\n\nfunction splitLinesForCounting(content: string): string[] {\n\tif (content.length === 0) return [];\n\tconst lines = content.split(\"\\n\");\n\tif (content.endsWith(\"\\n\")) lines.pop();\n\treturn lines;\n}\n\nfunction replaceUnpairedSurrogates(content: string): string {\n\tlet output = \"\";\n\tfor (let i = 0; i < content.length; i++) {\n\t\tconst code = content.charCodeAt(i);\n\t\tif (code >= 0xd800 && code <= 0xdbff) {\n\t\t\tif (i + 1 < content.length) {\n\t\t\t\tconst next = content.charCodeAt(i + 1);\n\t\t\t\tif (next >= 0xdc00 && next <= 0xdfff) {\n\t\t\t\t\toutput += content[i] + content[i + 1];\n\t\t\t\t\ti++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\toutput += \"�\";\n\t\t} else if (code >= 0xdc00 && code <= 0xdfff) {\n\t\t\toutput += \"�\";\n\t\t} else {\n\t\t\toutput += content[i];\n\t\t}\n\t}\n\treturn output;\n}\n\n/**\n * Format bytes as human-readable size.\n */\nexport function formatSize(bytes: number): string {\n\tif (bytes < 1024) {\n\t\treturn `${bytes}B`;\n\t} else if (bytes < 1024 * 1024) {\n\t\treturn `${(bytes / 1024).toFixed(1)}KB`;\n\t} else {\n\t\treturn `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n\t}\n}\n\n/**\n * Truncate content from the head (keep first N lines/bytes).\n * Suitable for file reads where you want to see the beginning.\n *\n * Never returns partial lines. If first line exceeds byte limit,\n * returns empty content with firstLineExceedsLimit=true.\n */\nexport function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = utf8ByteLength(content);\n\tconst lines = splitLinesForCounting(content);\n\tconst totalLines = lines.length;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\n\t\t\ttruncated: false,\n\t\t\ttruncatedBy: null,\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: totalLines,\n\t\t\toutputBytes: totalBytes,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: false,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Check if first line alone exceeds byte limit\n\tconst firstLineBytes = utf8ByteLength(lines[0]);\n\tif (firstLineBytes > maxBytes) {\n\t\treturn {\n\t\t\tcontent: \"\",\n\t\t\ttruncated: true,\n\t\t\ttruncatedBy: \"bytes\",\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: 0,\n\t\t\toutputBytes: 0,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: true,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Collect complete lines that fit\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\n\tfor (let i = 0; i < lines.length && i < maxLines; i++) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.push(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.\n\tif (truncatedBy !== \"bytes\") truncatedBy = outputLinesArr.length < totalLines ? \"lines\" : \"bytes\";\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = utf8ByteLength(outputContent);\n\n\treturn {\n\t\tcontent: outputContent,\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\ttotalLines,\n\t\ttotalBytes,\n\t\toutputLines: outputLinesArr.length,\n\t\toutputBytes: finalOutputBytes,\n\t\tlastLinePartial: false,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines,\n\t\tmaxBytes,\n\t};\n}\n\n/**\n * Truncate content from the tail (keep last N lines/bytes).\n * Suitable for bash output where you want to see the end (errors, final results).\n *\n * May return partial first line if the last line of original content exceeds byte limit.\n */\nexport function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {\n\tconst maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n\tconst totalBytes = utf8ByteLength(content);\n\tconst lines = splitLinesForCounting(content);\n\tconst totalLines = lines.length;\n\n\t// Check if no truncation needed\n\tif (totalLines <= maxLines && totalBytes <= maxBytes) {\n\t\treturn {\n\t\t\tcontent,\n\t\t\ttruncated: false,\n\t\t\ttruncatedBy: null,\n\t\t\ttotalLines,\n\t\t\ttotalBytes,\n\t\t\toutputLines: totalLines,\n\t\t\toutputBytes: totalBytes,\n\t\t\tlastLinePartial: false,\n\t\t\tfirstLineExceedsLimit: false,\n\t\t\tmaxLines,\n\t\t\tmaxBytes,\n\t\t};\n\t}\n\n\t// Work backwards from the end\n\tconst outputLinesArr: string[] = [];\n\tlet outputBytesCount = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\tlet lastLinePartial = false;\n\n\tfor (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {\n\t\tconst line = lines[i];\n\t\tconst lineBytes = utf8ByteLength(line) + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline\n\n\t\tif (outputBytesCount + lineBytes > maxBytes) {\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\t// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,\n\t\t\t// take the end of the line (partial)\n\t\t\tif (outputLinesArr.length === 0) {\n\t\t\t\tconst truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);\n\t\t\t\toutputLinesArr.unshift(truncatedLine);\n\t\t\t\toutputBytesCount = utf8ByteLength(truncatedLine);\n\t\t\t\tlastLinePartial = true;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toutputLinesArr.unshift(line);\n\t\toutputBytesCount += lineBytes;\n\t}\n\n\t// Without a byte break, only omitted lines prove the line limit was reached; otherwise a trailing newline exceeded bytes.\n\tif (truncatedBy !== \"bytes\") truncatedBy = outputLinesArr.length < totalLines ? \"lines\" : \"bytes\";\n\n\tconst outputContent = outputLinesArr.join(\"\\n\");\n\tconst finalOutputBytes = utf8ByteLength(outputContent);\n\n\treturn {\n\t\tcontent: outputContent,\n\t\ttruncated: true,\n\t\ttruncatedBy,\n\t\ttotalLines,\n\t\ttotalBytes,\n\t\toutputLines: outputLinesArr.length,\n\t\toutputBytes: finalOutputBytes,\n\t\tlastLinePartial,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines,\n\t\tmaxBytes,\n\t};\n}\n\n/**\n * Truncate a string to fit within a byte limit (from the end).\n * Handles multi-byte UTF-8 characters correctly.\n */\nfunction truncateStringToBytesFromEnd(str: string, maxBytes: number): string {\n\tif (maxBytes <= 0) return \"\";\n\n\tlet outputBytes = 0;\n\tlet start = str.length;\n\tlet needsReplacement = false;\n\tfor (let i = str.length; i > 0; ) {\n\t\tlet characterStart = i - 1;\n\t\tconst code = str.charCodeAt(characterStart);\n\t\tlet characterBytes: number;\n\t\tlet unpairedSurrogate = false;\n\t\tif (code >= 0xdc00 && code <= 0xdfff && characterStart > 0) {\n\t\t\tconst previous = str.charCodeAt(characterStart - 1);\n\t\t\tif (previous >= 0xd800 && previous <= 0xdbff) {\n\t\t\t\tcharacterStart--;\n\t\t\t\tcharacterBytes = 4;\n\t\t\t} else {\n\t\t\t\tcharacterBytes = 3;\n\t\t\t\tunpairedSurrogate = true;\n\t\t\t}\n\t\t} else if (code >= 0xd800 && code <= 0xdfff) {\n\t\t\tcharacterBytes = 3;\n\t\t\tunpairedSurrogate = true;\n\t\t} else {\n\t\t\tcharacterBytes = code <= 0x7f ? 1 : code <= 0x7ff ? 2 : 3;\n\t\t}\n\t\tif (outputBytes + characterBytes > maxBytes) break;\n\t\toutputBytes += characterBytes;\n\t\tstart = characterStart;\n\t\tneedsReplacement ||= unpairedSurrogate;\n\t\ti = characterStart;\n\t}\n\n\tconst output = str.slice(start);\n\treturn needsReplacement ? replaceUnpairedSurrogates(output) : output;\n}\n\n/**\n * Truncate a single line to max characters, adding [truncated] suffix.\n * Used for grep match lines.\n */\nexport function truncateLine(\n\tline: string,\n\tmaxChars: number = GREP_MAX_LINE_LENGTH,\n): { text: string; wasTruncated: boolean } {\n\tif (line.length <= maxChars) {\n\t\treturn { text: line, wasTruncated: false };\n\t}\n\treturn { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };\n}\n"]}
|