@inflexa-ai/harness 0.23.0 → 0.25.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/README.md +1 -2
- package/dist/agents/conversation-agent.d.ts +0 -9
- package/dist/agents/sandbox/__fixtures__/deps.js +6 -0
- package/dist/agents/sandbox/data-profiler.js +5 -4
- package/dist/agents/sandbox/shared.js +11 -0
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/data-profile-orientation.js +30 -1
- package/dist/app/data-profile-policy.d.ts +33 -7
- package/dist/app/data-profile-policy.js +31 -9
- package/dist/contracts/chat-parts.d.ts +29 -33
- package/dist/contracts/content-url.d.ts +7 -41
- package/dist/contracts/content-url.js +7 -48
- package/dist/contracts/data-profile.d.ts +166 -0
- package/dist/contracts/index.d.ts +3 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/message.d.ts +1 -1
- package/dist/contracts/message.js +1 -1
- package/dist/contracts/part-registry.js +2 -3
- package/dist/contracts/schemas/chat-parts.d.ts +16 -53
- package/dist/contracts/schemas/chat-parts.js +15 -25
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/execution/input-signature.d.ts +33 -0
- package/dist/execution/input-signature.js +0 -0
- package/dist/execution/run-authorizer.d.ts +1 -1
- package/dist/execution/run-authorizer.js +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -2
- package/dist/input-scan/coverage.d.ts +22 -0
- package/dist/input-scan/coverage.js +85 -0
- package/dist/input-scan/enrich.d.ts +41 -0
- package/dist/input-scan/enrich.js +227 -0
- package/dist/input-scan/formats.d.ts +41 -0
- package/dist/input-scan/formats.js +279 -0
- package/dist/input-scan/index.d.ts +12 -0
- package/dist/input-scan/index.js +11 -0
- package/dist/input-scan/scan.d.ts +50 -0
- package/dist/input-scan/scan.js +182 -0
- package/dist/input-scan/shapes.d.ts +46 -0
- package/dist/input-scan/shapes.js +0 -0
- package/dist/input-scan/types.d.ts +172 -0
- package/dist/input-scan/types.js +12 -0
- package/dist/lib/zod-issues.d.ts +2 -3
- package/dist/lib/zod-issues.js +2 -3
- package/dist/loop/__fixtures__/scripted-provider.js +3 -1
- package/dist/loop/run-agent.d.ts +5 -0
- package/dist/loop/run-agent.js +10 -1
- package/dist/loop/run-to-terminal.d.ts +1 -1
- package/dist/loop/run-to-terminal.js +1 -1
- package/dist/memory/card-builders.d.ts +0 -27
- package/dist/memory/card-builders.js +0 -75
- package/dist/memory/conversation-display-backfill.d.ts +2 -4
- package/dist/memory/conversation-display-backfill.js +3 -9
- package/dist/memory/conversation-display-storage.d.ts +3 -4
- package/dist/memory/conversation-display-storage.js +3 -4
- package/dist/memory/reconstruct-cards.d.ts +5 -7
- package/dist/memory/reconstruct-cards.js +6 -20
- package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
- package/dist/prompts/sandbox/data-profiler.js +159 -275
- package/dist/provenance/exec-frame.js +9 -3
- package/dist/runtime/boot.js +2 -12
- package/dist/sandbox/client.d.ts +7 -0
- package/dist/sandbox/create-sandbox.js +1 -0
- package/dist/sandbox/docker-client.d.ts +1 -0
- package/dist/sandbox/docker-client.js +20 -15
- package/dist/sandbox/k8s-client.d.ts +6 -5
- package/dist/sandbox/k8s-client.js +55 -39
- package/dist/sandbox/reaper.d.ts +24 -13
- package/dist/sandbox/reaper.js +47 -14
- package/dist/sandbox/types.d.ts +6 -5
- package/dist/schemas/data-profile-schemas.d.ts +62 -0
- package/dist/schemas/data-profile-schemas.js +68 -2
- package/dist/state/data-profile.d.ts +7 -88
- package/dist/state/index.d.ts +1 -1
- package/dist/tasks/data-profile-activity.d.ts +8 -0
- package/dist/tasks/data-profile-activity.js +1 -0
- package/dist/tasks/data-profile-index.d.ts +45 -0
- package/dist/tasks/data-profile-index.js +134 -0
- package/dist/tasks/data-profile.d.ts +8 -23
- package/dist/tasks/data-profile.js +150 -117
- package/dist/tools/report-session/preview-report.d.ts +2 -1
- package/dist/tools/report-session/preview-report.js +9 -1
- package/dist/tools/research/generate-plan.js +1 -1
- package/dist/tools/research/inspect-data-profile.d.ts +30 -4
- package/dist/tools/research/inspect-data-profile.js +66 -10
- package/dist/tools/start-report-session.js +2 -2
- package/dist/tools/workspace/scan-inputs.d.ts +38 -0
- package/dist/tools/workspace/scan-inputs.js +61 -0
- package/dist/tools/workspace/workspace-search.d.ts +1 -1
- package/dist/tools/workspace/workspace-search.js +12 -9
- package/dist/workspace/filesystem.d.ts +27 -0
- package/dist/workspace/filesystem.js +27 -0
- package/dist/workspace/paths.d.ts +0 -21
- package/dist/workspace/paths.js +0 -37
- package/package.json +1 -3
- package/dist/agents/report-builder.d.ts +0 -34
- package/dist/agents/report-builder.js +0 -34
- package/dist/execution/report-render.d.ts +0 -39
- package/dist/execution/report-render.js +0 -98
- package/dist/execution/report-runner.d.ts +0 -111
- package/dist/execution/report-runner.js +0 -230
- package/dist/prompts/report-builder.d.ts +0 -1
- package/dist/prompts/report-builder.js +0 -174
- package/dist/tools/iterate-report.d.ts +0 -227
- package/dist/tools/iterate-report.js +0 -883
- package/dist/tools/lib/preview-lock.d.ts +0 -20
- package/dist/tools/lib/preview-lock.js +0 -35
- package/dist/tools/lib/report-preflight.d.ts +0 -39
- package/dist/tools/lib/report-preflight.js +0 -283
- package/dist/tools/report/build-report.d.ts +0 -13
- package/dist/tools/report/build-report.js +0 -27
- package/dist/tools/report/index.d.ts +0 -15
- package/dist/tools/report/index.js +0 -15
- package/dist/tools/report/mint-preview-url.d.ts +0 -26
- package/dist/tools/report/mint-preview-url.js +0 -47
- package/dist/tools/report/preview-publisher.d.ts +0 -53
- package/dist/tools/report/preview-publisher.js +0 -45
- package/dist/tools/report/preview-snapshot.d.ts +0 -34
- package/dist/tools/report/preview-snapshot.js +0 -98
- package/dist/tools/report/submit-report.d.ts +0 -26
- package/dist/tools/report/submit-report.js +0 -148
- package/dist/tools/report/version-fs.d.ts +0 -15
- package/dist/tools/report/version-fs.js +0 -165
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-previewId serialization for report iterations.
|
|
3
|
-
*
|
|
4
|
-
* Two iterations on the same preview must not race on its shared
|
|
5
|
-
* `assets/` directory or version-dir creation. This lock ensures
|
|
6
|
-
* sequential execution per previewId; iterations on different previewIds
|
|
7
|
-
* remain fully parallel.
|
|
8
|
-
*
|
|
9
|
-
* SCOPE — in-process only. This Map is process-local: it serializes
|
|
10
|
-
* iterations within one replica but gives NO cross-pod guarantee, and it
|
|
11
|
-
* does not survive a DBOS replay. That is acceptable today because
|
|
12
|
-
* `runReportIteration` runs in the in-process chat path (`passthroughStep`),
|
|
13
|
-
* not as a durable workflow. When `iterateReport` is ported to a DBOS
|
|
14
|
-
* workflow, replace this with a durable mechanism (a DBOS queue keyed by
|
|
15
|
-
* previewId, or a Postgres advisory lock) — an in-process lock would then be
|
|
16
|
-
* both a replay and a cross-replica correctness bug.
|
|
17
|
-
*/
|
|
18
|
-
/** @internal — exposed for tests to assert no leak after settlement. */
|
|
19
|
-
export declare const __previewLocksForTest: Map<string, Promise<unknown>>;
|
|
20
|
-
export declare function withPreviewLock<T>(previewId: string, fn: () => Promise<T>): Promise<T>;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-previewId serialization for report iterations.
|
|
3
|
-
*
|
|
4
|
-
* Two iterations on the same preview must not race on its shared
|
|
5
|
-
* `assets/` directory or version-dir creation. This lock ensures
|
|
6
|
-
* sequential execution per previewId; iterations on different previewIds
|
|
7
|
-
* remain fully parallel.
|
|
8
|
-
*
|
|
9
|
-
* SCOPE — in-process only. This Map is process-local: it serializes
|
|
10
|
-
* iterations within one replica but gives NO cross-pod guarantee, and it
|
|
11
|
-
* does not survive a DBOS replay. That is acceptable today because
|
|
12
|
-
* `runReportIteration` runs in the in-process chat path (`passthroughStep`),
|
|
13
|
-
* not as a durable workflow. When `iterateReport` is ported to a DBOS
|
|
14
|
-
* workflow, replace this with a durable mechanism (a DBOS queue keyed by
|
|
15
|
-
* previewId, or a Postgres advisory lock) — an in-process lock would then be
|
|
16
|
-
* both a replay and a cross-replica correctness bug.
|
|
17
|
-
*/
|
|
18
|
-
const previewLocks = new Map();
|
|
19
|
-
/** @internal — exposed for tests to assert no leak after settlement. */
|
|
20
|
-
export const __previewLocksForTest = previewLocks;
|
|
21
|
-
export async function withPreviewLock(previewId, fn) {
|
|
22
|
-
const prior = previewLocks.get(previewId) ?? Promise.resolve();
|
|
23
|
-
const next = prior.then(fn, fn);
|
|
24
|
-
// The stored promise must not surface as an unhandled rejection — callers
|
|
25
|
-
// only see/handle `next`. Swallow on the stored chain after running the
|
|
26
|
-
// cleanup hook.
|
|
27
|
-
const stored = next
|
|
28
|
-
.finally(() => {
|
|
29
|
-
if (previewLocks.get(previewId) === stored)
|
|
30
|
-
previewLocks.delete(previewId);
|
|
31
|
-
})
|
|
32
|
-
.catch(() => undefined);
|
|
33
|
-
previewLocks.set(previewId, stored);
|
|
34
|
-
return next;
|
|
35
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Report pre-flight — deterministic asset staging for iterate-report.
|
|
3
|
-
*
|
|
4
|
-
* Runs before the report-builder agent starts. Resolves each declared
|
|
5
|
-
* source under the analysis root, enforces the 50MB cap, copies bytes
|
|
6
|
-
* into the preview's shared assets/ dir, and enriches the brief with
|
|
7
|
-
* inferred kind, size, CSV columns, head rows, and row count.
|
|
8
|
-
*
|
|
9
|
-
* The builder receives a complete brief and never has to discover or
|
|
10
|
-
* stage anything itself.
|
|
11
|
-
*/
|
|
12
|
-
export type AssetKind = "csv" | "tsv" | "image" | "json" | "other";
|
|
13
|
-
export interface StagedAsset {
|
|
14
|
-
name: string;
|
|
15
|
-
path: string;
|
|
16
|
-
kind: AssetKind;
|
|
17
|
-
sizeBytes: number;
|
|
18
|
-
rowCount?: number;
|
|
19
|
-
columns?: string[];
|
|
20
|
-
headRows?: string[][];
|
|
21
|
-
}
|
|
22
|
-
export interface PreflightSource {
|
|
23
|
-
path: string;
|
|
24
|
-
as?: string;
|
|
25
|
-
}
|
|
26
|
-
export type PreflightResult = {
|
|
27
|
-
ok: true;
|
|
28
|
-
staged: StagedAsset[];
|
|
29
|
-
} | {
|
|
30
|
-
ok: false;
|
|
31
|
-
reason: string;
|
|
32
|
-
};
|
|
33
|
-
export interface PreflightOptions {
|
|
34
|
-
sources: PreflightSource[];
|
|
35
|
-
analysisRoot: string;
|
|
36
|
-
assetsDirAbs: string;
|
|
37
|
-
}
|
|
38
|
-
export declare function stageReportAssets(opts: PreflightOptions): Promise<PreflightResult>;
|
|
39
|
-
export declare function formatBytes(bytes: number): string;
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Report pre-flight — deterministic asset staging for iterate-report.
|
|
3
|
-
*
|
|
4
|
-
* Runs before the report-builder agent starts. Resolves each declared
|
|
5
|
-
* source under the analysis root, enforces the 50MB cap, copies bytes
|
|
6
|
-
* into the preview's shared assets/ dir, and enriches the brief with
|
|
7
|
-
* inferred kind, size, CSV columns, head rows, and row count.
|
|
8
|
-
*
|
|
9
|
-
* The builder receives a complete brief and never has to discover or
|
|
10
|
-
* stage anything itself.
|
|
11
|
-
*/
|
|
12
|
-
import { copyFile, mkdir, open, realpath, stat } from "node:fs/promises";
|
|
13
|
-
import { basename, extname, resolve, sep } from "node:path";
|
|
14
|
-
const MAX_ASSET_BYTES = 50 * 1024 * 1024;
|
|
15
|
-
const MAX_ASSET_LABEL = "50MB";
|
|
16
|
-
const HEAD_BUF_BYTES = 64 * 1024;
|
|
17
|
-
const HEAD_ROW_COUNT = 5;
|
|
18
|
-
const COUNT_CHUNK_BYTES = 64 * 1024;
|
|
19
|
-
// JSON enrichment parses the whole file to detect array-of-objects shape.
|
|
20
|
-
// Cap below the 50MB asset limit so we don't synchronously parse huge files
|
|
21
|
-
// just for metadata — large JSON gets staged but lands in the brief without
|
|
22
|
-
// columns/rowCount, the same way a non-tabular JSON would.
|
|
23
|
-
const JSON_ENRICH_MAX_BYTES = 5 * 1024 * 1024;
|
|
24
|
-
export async function stageReportAssets(opts) {
|
|
25
|
-
await mkdir(opts.assetsDirAbs, { recursive: true });
|
|
26
|
-
// Canonicalize analysisRoot so containment checks work when the path
|
|
27
|
-
// traverses symlinks (e.g. macOS /var → /private/var).
|
|
28
|
-
const analysisRoot = await realpath(opts.analysisRoot);
|
|
29
|
-
// Dedupe by destination name. Identical (path, name) pairs collapse
|
|
30
|
-
// silently; conflicting names from different paths are an error.
|
|
31
|
-
const byName = new Map();
|
|
32
|
-
for (const src of opts.sources) {
|
|
33
|
-
const name = (src.as ?? basename(src.path)).trim();
|
|
34
|
-
if (!name) {
|
|
35
|
-
return { ok: false, reason: `source has no destination name: ${src.path}` };
|
|
36
|
-
}
|
|
37
|
-
const existing = byName.get(name);
|
|
38
|
-
if (existing && existing.path !== src.path) {
|
|
39
|
-
return {
|
|
40
|
-
ok: false,
|
|
41
|
-
reason: `duplicate asset name '${name}' from sources '${existing.path}' and '${src.path}' — use \`as\` to disambiguate`,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
byName.set(name, src);
|
|
45
|
-
}
|
|
46
|
-
const staged = [];
|
|
47
|
-
for (const [name, src] of byName) {
|
|
48
|
-
const resolved = await resolveSource(src.path, analysisRoot);
|
|
49
|
-
if (!resolved.ok)
|
|
50
|
-
return resolved;
|
|
51
|
-
let st;
|
|
52
|
-
try {
|
|
53
|
-
st = await stat(resolved.path);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return { ok: false, reason: `source not found: ${src.path}` };
|
|
57
|
-
}
|
|
58
|
-
if (!st.isFile()) {
|
|
59
|
-
return { ok: false, reason: `source is not a regular file: ${src.path}` };
|
|
60
|
-
}
|
|
61
|
-
if (st.size > MAX_ASSET_BYTES) {
|
|
62
|
-
return {
|
|
63
|
-
ok: false,
|
|
64
|
-
reason: `source ${src.path} is ${formatBytes(st.size)} — exceeds ${MAX_ASSET_LABEL} cap`,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const dst = resolveDestination(name, opts.assetsDirAbs);
|
|
68
|
-
if (!dst.ok)
|
|
69
|
-
return dst;
|
|
70
|
-
await copyFile(resolved.path, dst.path);
|
|
71
|
-
const kind = inferKind(name);
|
|
72
|
-
const asset = {
|
|
73
|
-
name,
|
|
74
|
-
path: src.path,
|
|
75
|
-
kind,
|
|
76
|
-
sizeBytes: st.size,
|
|
77
|
-
};
|
|
78
|
-
if (kind === "csv" || kind === "tsv") {
|
|
79
|
-
const enriched = await enrichTabular(dst.path, kind === "tsv" ? "\t" : ",");
|
|
80
|
-
if (enriched.columns)
|
|
81
|
-
asset.columns = enriched.columns;
|
|
82
|
-
if (enriched.headRows)
|
|
83
|
-
asset.headRows = enriched.headRows;
|
|
84
|
-
if (enriched.rowCount !== undefined)
|
|
85
|
-
asset.rowCount = enriched.rowCount;
|
|
86
|
-
}
|
|
87
|
-
else if (kind === "json" && st.size <= JSON_ENRICH_MAX_BYTES) {
|
|
88
|
-
const enriched = await enrichJson(dst.path);
|
|
89
|
-
if (enriched.columns)
|
|
90
|
-
asset.columns = enriched.columns;
|
|
91
|
-
if (enriched.headRows)
|
|
92
|
-
asset.headRows = enriched.headRows;
|
|
93
|
-
if (enriched.rowCount !== undefined)
|
|
94
|
-
asset.rowCount = enriched.rowCount;
|
|
95
|
-
}
|
|
96
|
-
staged.push(asset);
|
|
97
|
-
}
|
|
98
|
-
return { ok: true, staged };
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Resolves a source path under analysisRoot and canonicalizes through
|
|
102
|
-
* symlinks. Sandbox steps have RW access to their own output dir on the
|
|
103
|
-
* shared session storage, so a malicious or compromised step could create
|
|
104
|
-
* a symlink pointing outside the analysis tree (e.g. to /etc/passwd).
|
|
105
|
-
* `path.resolve` is purely lexical — only `realpath` catches symlink
|
|
106
|
-
* escapes — so we re-validate containment after canonicalization.
|
|
107
|
-
*/
|
|
108
|
-
async function resolveSource(path, analysisRoot) {
|
|
109
|
-
if (path.includes("\0")) {
|
|
110
|
-
return { ok: false, reason: `source contains null byte: ${path}` };
|
|
111
|
-
}
|
|
112
|
-
if (path.startsWith("/")) {
|
|
113
|
-
return { ok: false, reason: `source must be relative to analysis root: ${path}` };
|
|
114
|
-
}
|
|
115
|
-
const lexical = resolve(analysisRoot, path);
|
|
116
|
-
if (lexical !== analysisRoot && !lexical.startsWith(analysisRoot + sep)) {
|
|
117
|
-
return { ok: false, reason: `source escapes analysis root: ${path}` };
|
|
118
|
-
}
|
|
119
|
-
let canonical;
|
|
120
|
-
try {
|
|
121
|
-
canonical = await realpath(lexical);
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
if (err.code === "ENOENT") {
|
|
125
|
-
return { ok: false, reason: `source not found: ${path}` };
|
|
126
|
-
}
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
if (canonical !== analysisRoot && !canonical.startsWith(analysisRoot + sep)) {
|
|
130
|
-
return { ok: false, reason: `source escapes analysis root via symlink: ${path}` };
|
|
131
|
-
}
|
|
132
|
-
return { ok: true, path: canonical };
|
|
133
|
-
}
|
|
134
|
-
function resolveDestination(name, assetsDirAbs) {
|
|
135
|
-
if (name.includes("\0")) {
|
|
136
|
-
return { ok: false, reason: `destination contains null byte: ${name}` };
|
|
137
|
-
}
|
|
138
|
-
// Reject any path-y name — assets/ is intentionally flat. `as: "subdir/x"`
|
|
139
|
-
// would otherwise pass the starts-with check but fail at copyFile time
|
|
140
|
-
// because the parent dir doesn't exist, bubbling an unhandled exception
|
|
141
|
-
// out of stageReportAssets's {ok} contract.
|
|
142
|
-
if (name !== basename(name)) {
|
|
143
|
-
return { ok: false, reason: `destination must be a flat filename (no path separators): ${name}` };
|
|
144
|
-
}
|
|
145
|
-
const resolved = resolve(assetsDirAbs, name);
|
|
146
|
-
if (!resolved.startsWith(assetsDirAbs + sep)) {
|
|
147
|
-
return { ok: false, reason: `destination escapes assets dir: ${name}` };
|
|
148
|
-
}
|
|
149
|
-
return { ok: true, path: resolved };
|
|
150
|
-
}
|
|
151
|
-
function inferKind(name) {
|
|
152
|
-
const ext = extname(name).toLowerCase();
|
|
153
|
-
if (ext === ".csv")
|
|
154
|
-
return "csv";
|
|
155
|
-
if (ext === ".tsv")
|
|
156
|
-
return "tsv";
|
|
157
|
-
if (ext === ".png" || ext === ".jpg" || ext === ".jpeg" || ext === ".svg" || ext === ".gif" || ext === ".webp") {
|
|
158
|
-
return "image";
|
|
159
|
-
}
|
|
160
|
-
if (ext === ".json")
|
|
161
|
-
return "json";
|
|
162
|
-
return "other";
|
|
163
|
-
}
|
|
164
|
-
async function enrichTabular(path, delim) {
|
|
165
|
-
const fd = await open(path, "r");
|
|
166
|
-
try {
|
|
167
|
-
const buf = Buffer.alloc(HEAD_BUF_BYTES);
|
|
168
|
-
const { bytesRead } = await fd.read(buf, 0, HEAD_BUF_BYTES, 0);
|
|
169
|
-
const head = buf.subarray(0, bytesRead).toString("utf8");
|
|
170
|
-
const lines = head.split(/\r?\n/);
|
|
171
|
-
if (lines.length === 0 || lines[0].length === 0)
|
|
172
|
-
return {};
|
|
173
|
-
const columns = parseLine(lines[0], delim);
|
|
174
|
-
const headRows = [];
|
|
175
|
-
for (let i = 1; i < lines.length && headRows.length < HEAD_ROW_COUNT; i++) {
|
|
176
|
-
if (lines[i].length === 0)
|
|
177
|
-
continue;
|
|
178
|
-
headRows.push(parseLine(lines[i], delim));
|
|
179
|
-
}
|
|
180
|
-
const rowCount = await countRows(fd);
|
|
181
|
-
const result = {
|
|
182
|
-
columns,
|
|
183
|
-
};
|
|
184
|
-
if (headRows.length > 0)
|
|
185
|
-
result.headRows = headRows;
|
|
186
|
-
if (rowCount !== undefined)
|
|
187
|
-
result.rowCount = rowCount;
|
|
188
|
-
return result;
|
|
189
|
-
}
|
|
190
|
-
finally {
|
|
191
|
-
await fd.close();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
function parseLine(line, delim) {
|
|
195
|
-
// Naive split — sufficient for analysis output where fields rarely contain
|
|
196
|
-
// delimiters or embedded newlines. The builder reads the actual file via the
|
|
197
|
-
// assets/ symlink for rendering; the brief just surfaces column names so the
|
|
198
|
-
// conv agent can compose section content correctly.
|
|
199
|
-
return line.split(delim).map((s) => s.replace(/^"|"$/g, ""));
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Detect array-of-objects JSON shape so the brief surfaces columns + a few
|
|
203
|
-
* head rows the same way it does for CSV/TSV. Anything other than a top-level
|
|
204
|
-
* array of objects (e.g. ECharts options, scalar config, nested aggregates)
|
|
205
|
-
* is staged with no enrichment — the builder can still load it client-side
|
|
206
|
-
* and use it directly, but the conv agent picks chart encoding without
|
|
207
|
-
* column hints. Parses the full file; caller caps file size upstream.
|
|
208
|
-
*/
|
|
209
|
-
async function enrichJson(path) {
|
|
210
|
-
let parsed;
|
|
211
|
-
try {
|
|
212
|
-
const { readFile } = await import("node:fs/promises");
|
|
213
|
-
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
return {};
|
|
217
|
-
}
|
|
218
|
-
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
219
|
-
return {};
|
|
220
|
-
// Sample the first non-null object for keys. Mixed-shape arrays still get
|
|
221
|
-
// first-element keys — good enough for the brief; renderer reads the file
|
|
222
|
-
// directly anyway.
|
|
223
|
-
const first = parsed.find((r) => r && typeof r === "object" && !Array.isArray(r));
|
|
224
|
-
if (!first)
|
|
225
|
-
return { rowCount: parsed.length };
|
|
226
|
-
const columns = Object.keys(first);
|
|
227
|
-
const headRows = [];
|
|
228
|
-
for (let i = 0; i < parsed.length && headRows.length < HEAD_ROW_COUNT; i++) {
|
|
229
|
-
const r = parsed[i];
|
|
230
|
-
if (!r || typeof r !== "object" || Array.isArray(r))
|
|
231
|
-
continue;
|
|
232
|
-
const obj = r;
|
|
233
|
-
headRows.push(columns.map((c) => stringifyCell(obj[c])));
|
|
234
|
-
}
|
|
235
|
-
const result = {
|
|
236
|
-
columns,
|
|
237
|
-
rowCount: parsed.length,
|
|
238
|
-
};
|
|
239
|
-
if (headRows.length > 0)
|
|
240
|
-
result.headRows = headRows;
|
|
241
|
-
return result;
|
|
242
|
-
}
|
|
243
|
-
function stringifyCell(v) {
|
|
244
|
-
if (v === null || v === undefined)
|
|
245
|
-
return "";
|
|
246
|
-
if (typeof v === "string")
|
|
247
|
-
return v;
|
|
248
|
-
if (typeof v === "number" || typeof v === "boolean")
|
|
249
|
-
return String(v);
|
|
250
|
-
return JSON.stringify(v);
|
|
251
|
-
}
|
|
252
|
-
async function countRows(fd) {
|
|
253
|
-
const buf = Buffer.alloc(COUNT_CHUNK_BYTES);
|
|
254
|
-
let newlines = 0;
|
|
255
|
-
let pos = 0;
|
|
256
|
-
let lastByte = -1;
|
|
257
|
-
while (true) {
|
|
258
|
-
const { bytesRead } = await fd.read(buf, 0, buf.length, pos);
|
|
259
|
-
if (bytesRead === 0)
|
|
260
|
-
break;
|
|
261
|
-
for (let i = 0; i < bytesRead; i++) {
|
|
262
|
-
if (buf[i] === 0x0a)
|
|
263
|
-
newlines++;
|
|
264
|
-
}
|
|
265
|
-
lastByte = buf[bytesRead - 1];
|
|
266
|
-
pos += bytesRead;
|
|
267
|
-
}
|
|
268
|
-
if (pos === 0)
|
|
269
|
-
return 0;
|
|
270
|
-
// If the file doesn't end with a newline, the last row wasn't counted.
|
|
271
|
-
const total = lastByte === 0x0a ? newlines : newlines + 1;
|
|
272
|
-
// Subtract one for the header.
|
|
273
|
-
return Math.max(0, total - 1);
|
|
274
|
-
}
|
|
275
|
-
export function formatBytes(bytes) {
|
|
276
|
-
if (bytes < 1024)
|
|
277
|
-
return `${bytes}B`;
|
|
278
|
-
if (bytes < 1024 * 1024)
|
|
279
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
280
|
-
if (bytes < 1024 * 1024 * 1024)
|
|
281
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
282
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
|
|
283
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* build_report tool — renders `report.html.j2` → `index.html` via the
|
|
3
|
-
* in-process Nunjucks renderer. Closes over the runner's preview-dir state.
|
|
4
|
-
*
|
|
5
|
-
* Returns structured success or a typed error so the agent can fix and retry.
|
|
6
|
-
* Version dir and templates dir are closure-state.
|
|
7
|
-
*/
|
|
8
|
-
import { type Tool } from "../define-tool.js";
|
|
9
|
-
export interface BuildReportToolState {
|
|
10
|
-
readonly versionDir: string;
|
|
11
|
-
readonly templatesDir: string;
|
|
12
|
-
}
|
|
13
|
-
export declare function createBuildReportTool(state: BuildReportToolState): Tool;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* build_report tool — renders `report.html.j2` → `index.html` via the
|
|
3
|
-
* in-process Nunjucks renderer. Closes over the runner's preview-dir state.
|
|
4
|
-
*
|
|
5
|
-
* Returns structured success or a typed error so the agent can fix and retry.
|
|
6
|
-
* Version dir and templates dir are closure-state.
|
|
7
|
-
*/
|
|
8
|
-
import { ok } from "neverthrow";
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
import { defineTool } from "../define-tool.js";
|
|
11
|
-
import { renderReport } from "../../execution/report-render.js";
|
|
12
|
-
export function createBuildReportTool(state) {
|
|
13
|
-
return defineTool({
|
|
14
|
-
id: "build_report",
|
|
15
|
-
description: "Render the report by compiling report.html.j2 into index.html. " +
|
|
16
|
-
"Always use this — never invoke a build script directly. Returns " +
|
|
17
|
-
"structured errors with line numbers when the template is broken.",
|
|
18
|
-
inputSchema: z.object({}),
|
|
19
|
-
describeCall: "none",
|
|
20
|
-
execute: async () => {
|
|
21
|
-
return ok(await renderReport({
|
|
22
|
-
versionDir: state.versionDir,
|
|
23
|
-
templatesDir: state.templatesDir,
|
|
24
|
-
}));
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Report-tool barrel. These tools are NOT registered on the conversation
|
|
3
|
-
* agent — they are constructed inside `harness/execution/report-runner.ts`
|
|
4
|
-
* so each tool factory shares the runner's closure-captured outcome state
|
|
5
|
-
* and preview-dir paths. The conversation agent reaches the report-builder
|
|
6
|
-
* through its `submit_report` tool, which orchestrates the whole iteration.
|
|
7
|
-
* (That conversation-agent tool shares the id `submit_report` with this
|
|
8
|
-
* barrel's builder-side terminal tool — different rosters, never mixed.)
|
|
9
|
-
*/
|
|
10
|
-
export { createBuildReportTool, type BuildReportToolState } from "./build-report.js";
|
|
11
|
-
export { createSubmitReportTool, type ReportOutcome, type SubmitReportToolState } from "./submit-report.js";
|
|
12
|
-
export { createMintPreviewUrlTool, type MintPreviewUrlToolState, type PreviewUrlCell } from "./mint-preview-url.js";
|
|
13
|
-
export { createPreviewSnapshotTool, type PreviewSnapshotToolState } from "./preview-snapshot.js";
|
|
14
|
-
export { createVersionFsTools, type VersionFsToolsState } from "./version-fs.js";
|
|
15
|
-
export { type PreviewPublisher, type PreviewMintResult, UnavailablePreviewPublisher } from "./preview-publisher.js";
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Report-tool barrel. These tools are NOT registered on the conversation
|
|
3
|
-
* agent — they are constructed inside `harness/execution/report-runner.ts`
|
|
4
|
-
* so each tool factory shares the runner's closure-captured outcome state
|
|
5
|
-
* and preview-dir paths. The conversation agent reaches the report-builder
|
|
6
|
-
* through its `submit_report` tool, which orchestrates the whole iteration.
|
|
7
|
-
* (That conversation-agent tool shares the id `submit_report` with this
|
|
8
|
-
* barrel's builder-side terminal tool — different rosters, never mixed.)
|
|
9
|
-
*/
|
|
10
|
-
export { createBuildReportTool } from "./build-report.js";
|
|
11
|
-
export { createSubmitReportTool } from "./submit-report.js";
|
|
12
|
-
export { createMintPreviewUrlTool } from "./mint-preview-url.js";
|
|
13
|
-
export { createPreviewSnapshotTool } from "./preview-snapshot.js";
|
|
14
|
-
export { createVersionFsTools } from "./version-fs.js";
|
|
15
|
-
export { UnavailablePreviewPublisher } from "./preview-publisher.js";
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mint_preview_url tool — get a content-server URL for the report preview.
|
|
3
|
-
*
|
|
4
|
-
* Calls the preview-access seam and returns the full navigatable URL
|
|
5
|
-
* for the requested version.
|
|
6
|
-
*
|
|
7
|
-
* Most callers don't need this — `preview_snapshot` mints lazily on first
|
|
8
|
-
* use. Reach for this tool when:
|
|
9
|
-
* - the previously-minted URL has expired
|
|
10
|
-
* - inspecting an older version (pass `version`)
|
|
11
|
-
*/
|
|
12
|
-
import { type Tool } from "../define-tool.js";
|
|
13
|
-
import { type PreviewPublisher } from "./preview-publisher.js";
|
|
14
|
-
/** Shared, mutable URL cell so mint and preview-snapshot share a cached URL. */
|
|
15
|
-
export interface PreviewUrlCell {
|
|
16
|
-
url: string | undefined;
|
|
17
|
-
expiresAt: string | undefined;
|
|
18
|
-
}
|
|
19
|
-
export interface MintPreviewUrlToolState {
|
|
20
|
-
readonly resourceId: string;
|
|
21
|
-
readonly previewId: string;
|
|
22
|
-
readonly currentVersion: number;
|
|
23
|
-
readonly previews: PreviewPublisher;
|
|
24
|
-
readonly urlCell: PreviewUrlCell;
|
|
25
|
-
}
|
|
26
|
-
export declare function createMintPreviewUrlTool(state: MintPreviewUrlToolState): Tool;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mint_preview_url tool — get a content-server URL for the report preview.
|
|
3
|
-
*
|
|
4
|
-
* Calls the preview-access seam and returns the full navigatable URL
|
|
5
|
-
* for the requested version.
|
|
6
|
-
*
|
|
7
|
-
* Most callers don't need this — `preview_snapshot` mints lazily on first
|
|
8
|
-
* use. Reach for this tool when:
|
|
9
|
-
* - the previously-minted URL has expired
|
|
10
|
-
* - inspecting an older version (pass `version`)
|
|
11
|
-
*/
|
|
12
|
-
import { ok } from "neverthrow";
|
|
13
|
-
import { z } from "zod";
|
|
14
|
-
import { defineTool } from "../define-tool.js";
|
|
15
|
-
import { describeMintFailure } from "./preview-publisher.js";
|
|
16
|
-
export function createMintPreviewUrlTool(state) {
|
|
17
|
-
return defineTool({
|
|
18
|
-
id: "mint_preview_url",
|
|
19
|
-
description: "Mint a fresh preview URL for the report. Returns a time-limited " +
|
|
20
|
-
"content-server URL with an embedded auth token, alongside the " +
|
|
21
|
-
"`expiresAt` it is good until. Use this only when you need a URL for " +
|
|
22
|
-
"direct browser navigation — preview_snapshot handles the common case " +
|
|
23
|
-
"automatically.",
|
|
24
|
-
inputSchema: z.object({
|
|
25
|
-
version: z.number().optional().describe("Version to mint for. Defaults to the iteration's current version."),
|
|
26
|
-
}),
|
|
27
|
-
describeCall: "none",
|
|
28
|
-
execute: async (input) => {
|
|
29
|
-
const version = input.version ?? state.currentVersion;
|
|
30
|
-
const result = await state.previews.mintPreviewAccess(state.resourceId, state.previewId);
|
|
31
|
-
if (!result.ok) {
|
|
32
|
-
return ok({
|
|
33
|
-
ok: false,
|
|
34
|
-
error: describeMintFailure(result),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
const url = `${result.data.baseUrl.replace(/\/?$/, "/")}v${version}/index.html?t=${result.data.token}`;
|
|
38
|
-
state.urlCell.url = url;
|
|
39
|
-
state.urlCell.expiresAt = result.data.expiresAt;
|
|
40
|
-
return ok({
|
|
41
|
-
ok: true,
|
|
42
|
-
url,
|
|
43
|
-
expiresAt: result.data.expiresAt,
|
|
44
|
-
});
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PreviewPublisher — the seam between the harness report path and the hosted
|
|
3
|
-
* preview surface. The report build itself (FS + Nunjucks + LLM via
|
|
4
|
-
* `ChatProvider`) is pure in-process; the only thing that reaches outside
|
|
5
|
-
* the process is minting a content-server URL for the headless-Chrome
|
|
6
|
-
* preview. That mint is hidden behind this one method.
|
|
7
|
-
*
|
|
8
|
-
* The managed implementation (HostedPreviewPublisher) owns the access
|
|
9
|
-
* grant mint + managed client. The local default (UnavailablePreviewPublisher)
|
|
10
|
-
* returns not-ok, so `preview_snapshot` short-circuits before touching
|
|
11
|
-
* Chrome and the report still builds (submit_report is the only gate).
|
|
12
|
-
*/
|
|
13
|
-
export type PreviewMintResult = {
|
|
14
|
-
ok: true;
|
|
15
|
-
data: {
|
|
16
|
-
baseUrl: string;
|
|
17
|
-
token: string;
|
|
18
|
-
expiresAt: string;
|
|
19
|
-
};
|
|
20
|
-
} | {
|
|
21
|
-
ok: false;
|
|
22
|
-
status?: number;
|
|
23
|
-
error: {
|
|
24
|
-
message?: string;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
/** The not-ok arm of the seam's result — what a failed mint is allowed to carry. */
|
|
28
|
-
export type PreviewMintFailure = Extract<PreviewMintResult, {
|
|
29
|
-
ok: false;
|
|
30
|
-
}>;
|
|
31
|
-
/**
|
|
32
|
-
* Renders a failed mint as the line an agent reads back from a preview tool.
|
|
33
|
-
*
|
|
34
|
-
* The failure shape is sparse by design: a realization with no HTTP transport
|
|
35
|
-
* behind it supplies neither a status nor, necessarily, a message. Naming a
|
|
36
|
-
* field the seam left unset would put the literal `status=undefined` in front
|
|
37
|
-
* of the model, so the message carries only what actually arrived. This lives
|
|
38
|
-
* beside the type because its whole job is to honour that type's optionality —
|
|
39
|
-
* every tool holding the seam owes the same message, and one of them owning
|
|
40
|
-
* the wording would make the others depend on a peer for a seam-wide concern.
|
|
41
|
-
*/
|
|
42
|
-
export declare function describeMintFailure(failure: PreviewMintFailure): string;
|
|
43
|
-
export interface PreviewPublisher {
|
|
44
|
-
mintPreviewAccess(resourceId: string, previewId: string): Promise<PreviewMintResult>;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Local default — no hosted preview surface. Returns not-ok so the preview
|
|
48
|
-
* tools surface a clear "unavailable" message and short-circuit before any
|
|
49
|
-
* browser navigation.
|
|
50
|
-
*/
|
|
51
|
-
export declare class UnavailablePreviewPublisher implements PreviewPublisher {
|
|
52
|
-
mintPreviewAccess(_resourceId: string, _previewId: string): Promise<PreviewMintResult>;
|
|
53
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PreviewPublisher — the seam between the harness report path and the hosted
|
|
3
|
-
* preview surface. The report build itself (FS + Nunjucks + LLM via
|
|
4
|
-
* `ChatProvider`) is pure in-process; the only thing that reaches outside
|
|
5
|
-
* the process is minting a content-server URL for the headless-Chrome
|
|
6
|
-
* preview. That mint is hidden behind this one method.
|
|
7
|
-
*
|
|
8
|
-
* The managed implementation (HostedPreviewPublisher) owns the access
|
|
9
|
-
* grant mint + managed client. The local default (UnavailablePreviewPublisher)
|
|
10
|
-
* returns not-ok, so `preview_snapshot` short-circuits before touching
|
|
11
|
-
* Chrome and the report still builds (submit_report is the only gate).
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Renders a failed mint as the line an agent reads back from a preview tool.
|
|
15
|
-
*
|
|
16
|
-
* The failure shape is sparse by design: a realization with no HTTP transport
|
|
17
|
-
* behind it supplies neither a status nor, necessarily, a message. Naming a
|
|
18
|
-
* field the seam left unset would put the literal `status=undefined` in front
|
|
19
|
-
* of the model, so the message carries only what actually arrived. This lives
|
|
20
|
-
* beside the type because its whole job is to honour that type's optionality —
|
|
21
|
-
* every tool holding the seam owes the same message, and one of them owning
|
|
22
|
-
* the wording would make the others depend on a peer for a seam-wide concern.
|
|
23
|
-
*/
|
|
24
|
-
export function describeMintFailure(failure) {
|
|
25
|
-
const detail = [];
|
|
26
|
-
if (failure.status !== undefined)
|
|
27
|
-
detail.push(`status=${failure.status}`);
|
|
28
|
-
const message = failure.error.message?.trim();
|
|
29
|
-
if (message)
|
|
30
|
-
detail.push(message);
|
|
31
|
-
return detail.length > 0 ? `preview-access mint failed: ${detail.join(" ")}` : "preview-access mint failed";
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Local default — no hosted preview surface. Returns not-ok so the preview
|
|
35
|
-
* tools surface a clear "unavailable" message and short-circuit before any
|
|
36
|
-
* browser navigation.
|
|
37
|
-
*/
|
|
38
|
-
export class UnavailablePreviewPublisher {
|
|
39
|
-
async mintPreviewAccess(_resourceId, _previewId) {
|
|
40
|
-
return {
|
|
41
|
-
ok: false,
|
|
42
|
-
error: { message: "report preview is unavailable in this environment" },
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* preview_snapshot tool — navigate headless Chrome to the rendered report,
|
|
3
|
-
* wait for the `inflexa-theme-ready` event, capture a screenshot + console
|
|
4
|
-
* messages + failed network requests in a single deterministic call.
|
|
5
|
-
*
|
|
6
|
-
* Lazily mints a preview URL via the `PreviewPublisher` seam on first use,
|
|
7
|
-
* sharing the cached URL with the `mint_preview_url` tool via a
|
|
8
|
-
* closure-captured `PreviewUrlCell`.
|
|
9
|
-
*
|
|
10
|
-
* Returned screenshot is base64-encoded PNG bytes; the agent can inspect
|
|
11
|
-
* console errors / failed requests to decide whether to fix the template
|
|
12
|
-
* or move on to submit_report.
|
|
13
|
-
*
|
|
14
|
-
* A seam that cannot mint is also reported through the injected `Logger`:
|
|
15
|
-
* this tool is the build's only visual verification, so an unavailable seam
|
|
16
|
-
* means that verification silently did not happen — a condition an operator
|
|
17
|
-
* has to be able to see without reading the model transcript.
|
|
18
|
-
*/
|
|
19
|
-
import { type Tool } from "../define-tool.js";
|
|
20
|
-
import type { ChromeConfig } from "../../lib/chrome.js";
|
|
21
|
-
import type { Logger } from "../../lib/logger.js";
|
|
22
|
-
import { type PreviewPublisher } from "./preview-publisher.js";
|
|
23
|
-
import type { PreviewUrlCell } from "./mint-preview-url.js";
|
|
24
|
-
export interface PreviewSnapshotToolState {
|
|
25
|
-
readonly resourceId: string;
|
|
26
|
-
readonly previewId: string;
|
|
27
|
-
readonly currentVersion: number;
|
|
28
|
-
readonly previews: PreviewPublisher;
|
|
29
|
-
readonly urlCell: PreviewUrlCell;
|
|
30
|
-
readonly chrome: ChromeConfig;
|
|
31
|
-
/** Operational logging seam; omitted falls back to no-op. */
|
|
32
|
-
readonly logger?: Logger;
|
|
33
|
-
}
|
|
34
|
-
export declare function createPreviewSnapshotTool(state: PreviewSnapshotToolState): Tool;
|