@north-light/crouter 0.3.223 → 0.3.224
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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Regression: the preview mirror is bounded. A `crtr integration run` in a
|
|
2
|
+
// canvas bash call once mirrored a 3 MB CRM dump into ToolResult.details via
|
|
3
|
+
// this channel — bypassing pi's 50 KB stdout truncation entirely — and five
|
|
4
|
+
// such calls made a 16 MB transcript that Northlight could no longer open.
|
|
5
|
+
// The writer is the one choke point, so the writer is what this pins.
|
|
6
|
+
import { strict as assert } from 'node:assert';
|
|
7
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { after, test } from 'node:test';
|
|
11
|
+
import { beginPreview, publishPreviewResult, PREVIEW_RESULT_MAX_BYTES } from '../io.js';
|
|
12
|
+
import { PREVIEW_RESULT_PATH_ENV } from '../preview-result-path.js';
|
|
13
|
+
const dir = mkdtempSync(join(tmpdir(), 'crtr-preview-cap-'));
|
|
14
|
+
const priorEnv = process.env[PREVIEW_RESULT_PATH_ENV];
|
|
15
|
+
after(() => {
|
|
16
|
+
rmSync(dir, { recursive: true, force: true });
|
|
17
|
+
if (priorEnv === undefined)
|
|
18
|
+
delete process.env[PREVIEW_RESULT_PATH_ENV];
|
|
19
|
+
else
|
|
20
|
+
process.env[PREVIEW_RESULT_PATH_ENV] = priorEnv;
|
|
21
|
+
});
|
|
22
|
+
test('a record over PREVIEW_RESULT_MAX_BYTES is dropped, not mirrored', () => {
|
|
23
|
+
const resultPath = join(dir, 'oversized.json');
|
|
24
|
+
process.env[PREVIEW_RESULT_PATH_ENV] = resultPath;
|
|
25
|
+
beginPreview('integration run');
|
|
26
|
+
publishPreviewResult({ ok: true, output: 'x'.repeat(PREVIEW_RESULT_MAX_BYTES) });
|
|
27
|
+
assert.equal(existsSync(resultPath), false);
|
|
28
|
+
});
|
|
29
|
+
test('a bounded record still mirrors verbatim', () => {
|
|
30
|
+
const resultPath = join(dir, 'bounded.json');
|
|
31
|
+
process.env[PREVIEW_RESULT_PATH_ENV] = resultPath;
|
|
32
|
+
beginPreview('integration run');
|
|
33
|
+
publishPreviewResult({ ok: true, output: 'small' });
|
|
34
|
+
const record = JSON.parse(readFileSync(resultPath, 'utf8'));
|
|
35
|
+
assert.equal(record.path, 'integration run');
|
|
36
|
+
assert.equal(record.result.output, 'small');
|
|
37
|
+
});
|
package/dist/core/io.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ export interface CrtrPreviewRecord {
|
|
|
10
10
|
jsonl?: Record<string, unknown>[];
|
|
11
11
|
error?: ErrorPayload;
|
|
12
12
|
}
|
|
13
|
+
/** A preview is bounded UI metadata, never a bulk-data channel. One
|
|
14
|
+
* `crtr integration run` once mirrored a 3 MB CRM dump into every copy of the
|
|
15
|
+
* transcript that contains its tool result — the session file, every replay
|
|
16
|
+
* frame, and the host product's stored conversation — because pi's 50 KB
|
|
17
|
+
* stdout truncation never sees this channel. The mirror write is the one
|
|
18
|
+
* choke point every record passes, so the bound lives here: an oversized
|
|
19
|
+
* record is dropped, and the viewer falls back to rendering the agent-facing
|
|
20
|
+
* stdout text, which pi bounds itself. */
|
|
21
|
+
export declare const PREVIEW_RESULT_MAX_BYTES: number;
|
|
13
22
|
/** Begin collecting the current leaf's structured response, if its caller
|
|
14
23
|
* supplied the private per-tool-call result path. */
|
|
15
24
|
export declare function beginPreview(path: string): void;
|
package/dist/core/io.js
CHANGED
|
@@ -14,6 +14,15 @@ import { ApiError } from '../api/index.js';
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
let jsonOutput = false;
|
|
16
16
|
let preview;
|
|
17
|
+
/** A preview is bounded UI metadata, never a bulk-data channel. One
|
|
18
|
+
* `crtr integration run` once mirrored a 3 MB CRM dump into every copy of the
|
|
19
|
+
* transcript that contains its tool result — the session file, every replay
|
|
20
|
+
* frame, and the host product's stored conversation — because pi's 50 KB
|
|
21
|
+
* stdout truncation never sees this channel. The mirror write is the one
|
|
22
|
+
* choke point every record passes, so the bound lives here: an oversized
|
|
23
|
+
* record is dropped, and the viewer falls back to rendering the agent-facing
|
|
24
|
+
* stdout text, which pi bounds itself. */
|
|
25
|
+
export const PREVIEW_RESULT_MAX_BYTES = 256 * 1024;
|
|
17
26
|
/** Begin collecting the current leaf's structured response, if its caller
|
|
18
27
|
* supplied the private per-tool-call result path. */
|
|
19
28
|
export function beginPreview(path) {
|
|
@@ -24,9 +33,12 @@ function publishPreview(record) {
|
|
|
24
33
|
const resultPath = process.env[PREVIEW_RESULT_PATH_ENV];
|
|
25
34
|
if (resultPath === undefined || resultPath === '')
|
|
26
35
|
return;
|
|
36
|
+
const line = `${JSON.stringify({ path: preview?.path ?? '', ...record })}\n`;
|
|
37
|
+
if (Buffer.byteLength(line, 'utf8') > PREVIEW_RESULT_MAX_BYTES)
|
|
38
|
+
return;
|
|
27
39
|
const tempPath = `${resultPath}.${process.pid}.tmp`;
|
|
28
40
|
try {
|
|
29
|
-
writeFileSync(tempPath,
|
|
41
|
+
writeFileSync(tempPath, line, 'utf8');
|
|
30
42
|
renameSync(tempPath, resultPath);
|
|
31
43
|
}
|
|
32
44
|
catch {
|
package/package.json
CHANGED
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.224",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/crouter",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.224",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|