@inflexa-ai/harness 0.18.0 → 0.19.1
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/dist/agents/report-session-agent.d.ts +72 -0
- package/dist/agents/report-session-agent.js +87 -0
- package/dist/app/report-session-runtime.d.ts +72 -0
- package/dist/app/report-session-runtime.js +183 -0
- package/dist/app/spawn-report-session.d.ts +32 -5
- package/dist/app/spawn-report-session.js +19 -0
- package/dist/config/environment-stores.d.ts +12 -7
- package/dist/config/resource-limits.d.ts +6 -10
- package/dist/config/resource-limits.js +6 -44
- package/dist/contracts/report-reference.d.ts +4 -0
- package/dist/contracts/report-reference.js +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/lib/chrome.d.ts +14 -0
- package/dist/lib/chrome.js +7 -0
- package/dist/lib/page-capture.d.ts +47 -0
- package/dist/lib/page-capture.js +83 -0
- package/dist/loop/run-agent.js +104 -15
- package/dist/memory/ai-sdk-message-storage.d.ts +9 -4
- package/dist/memory/ai-sdk-message-storage.js +9 -4
- package/dist/memory/count-tokens.js +34 -5
- package/dist/memory/thread-store.d.ts +4 -4
- package/dist/memory/thread-store.js +18 -9
- package/dist/prompts/report-session.d.ts +1 -0
- package/dist/prompts/report-session.js +110 -0
- package/dist/providers/ai-sdk.d.ts +158 -3
- package/dist/providers/ai-sdk.js +234 -23
- package/dist/providers/errors.d.ts +14 -0
- package/dist/providers/errors.js +103 -18
- package/dist/providers/types.d.ts +33 -0
- package/dist/providers/types.js +10 -1
- package/dist/report-model/assert-rules.d.ts +44 -0
- package/dist/report-model/assert-rules.js +97 -0
- package/dist/report-model/draft-hash.d.ts +14 -0
- package/dist/report-model/draft-hash.js +36 -0
- package/dist/report-model/fixture-resolver.js +2 -71
- package/dist/report-model/{mint-snapshot.d.ts → pin-snapshot.d.ts} +7 -7
- package/dist/report-model/{mint-snapshot.js → pin-snapshot.js} +7 -7
- package/dist/report-model/production-resolver.d.ts +124 -0
- package/dist/report-model/production-resolver.js +801 -0
- package/dist/report-model/reference-resolver.d.ts +7 -1
- package/dist/report-model/validate.d.ts +34 -2
- package/dist/report-model/validate.js +37 -17
- package/dist/report-render/page.d.ts +23 -0
- package/dist/report-render/page.js +36 -3
- package/dist/report-render/value-bridge.d.ts +62 -0
- package/dist/report-render/value-bridge.js +82 -0
- package/dist/runtime/assemble.d.ts +39 -0
- package/dist/runtime/assemble.js +67 -6
- package/dist/sandbox/mount-plan.d.ts +7 -0
- package/dist/sandbox/mount-plan.js +7 -2
- package/dist/state/init.js +36 -0
- package/dist/state/purge-analysis.js +1 -0
- package/dist/state/report-session-state.d.ts +139 -0
- package/dist/state/report-session-state.js +177 -0
- package/dist/state/report-versions.d.ts +3 -6
- package/dist/state/report-versions.js +11 -38
- package/dist/state/snapshot-parse.d.ts +34 -0
- package/dist/state/snapshot-parse.js +82 -0
- package/dist/tasks/extract-values-script.d.ts +64 -0
- package/dist/tasks/extract-values-script.js +180 -0
- package/dist/tasks/extract-values.d.ts +124 -0
- package/dist/tasks/extract-values.js +193 -0
- package/dist/tools/ad-hoc-router.d.ts +7 -2
- package/dist/tools/ad-hoc-router.js +2 -1
- package/dist/tools/define-tool.d.ts +29 -0
- package/dist/tools/define-tool.js +26 -0
- package/dist/tools/report/preview-snapshot.d.ts +1 -1
- package/dist/tools/report/preview-snapshot.js +11 -50
- package/dist/tools/report-authoring/authoring-tools.d.ts +181 -24
- package/dist/tools/report-authoring/authoring-tools.js +152 -44
- package/dist/tools/report-session/examine-page.d.ts +90 -0
- package/dist/tools/report-session/examine-page.js +136 -0
- package/dist/tools/report-session/index.d.ts +10 -0
- package/dist/tools/report-session/index.js +10 -0
- package/dist/tools/report-session/preview-report.d.ts +105 -0
- package/dist/tools/report-session/preview-report.js +269 -0
- package/dist/tools/report-session/record-version.d.ts +102 -0
- package/dist/tools/report-session/record-version.js +139 -0
- package/dist/tools/research/generate-plan.js +4 -2
- package/dist/tools/sandbox/list-available-packages.js +2 -1
- package/dist/tools/sandbox/list-available-refs.d.ts +5 -1
- package/dist/tools/sandbox/list-available-refs.js +13 -6
- package/package.json +10 -8
|
@@ -17,7 +17,7 @@ import type { Reference, UnresolvedReference } from "../contracts/report-referen
|
|
|
17
17
|
*
|
|
18
18
|
* `rows` is optional because an image is pinned by its hash alone and holds no cells. An empty array in
|
|
19
19
|
* that position would say that the artifact holds zero rows, which is a different and false claim. The
|
|
20
|
-
*
|
|
20
|
+
* pin never populates `rows`. `rows` serves the fixture realization only, and that realization resolves
|
|
21
21
|
* from the snapshot itself. A production snapshot pins identity, and a value comes from a read of the
|
|
22
22
|
* artifact.
|
|
23
23
|
*
|
|
@@ -71,9 +71,15 @@ export type ResolvedValue = {
|
|
|
71
71
|
* The `Ok` channel carries the resolved value. The `Err` channel carries the `UnresolvedReference`,
|
|
72
72
|
* which names the reason and the detail that a reference did not bind. The validator collects each such
|
|
73
73
|
* `Err` into its report, thus a caller reads the reason as data and resolution never throws.
|
|
74
|
+
*
|
|
75
|
+
* `prepare` is an optional batch step that runs one time before the per-reference loop. A realization
|
|
76
|
+
* that reads storage groups the references by artifact, reads each file one time, and fills a cache.
|
|
77
|
+
* `resolve` then answers from the cache. The method is optional, thus a realization without it keeps the
|
|
78
|
+
* per-reference behavior and the extension breaks no caller.
|
|
74
79
|
*/
|
|
75
80
|
export interface ReferenceResolver {
|
|
76
81
|
resolve(reference: Reference, snapshot: ReportSnapshot): Promise<Result<ResolvedValue, UnresolvedReference>>;
|
|
82
|
+
prepare?(references: readonly Reference[], snapshot: ReportSnapshot): Promise<void>;
|
|
77
83
|
}
|
|
78
84
|
/**
|
|
79
85
|
* Give back each name in `columns` that no row holds, in the order of `columns`.
|
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
* each reference against the pinned evidence, and it warns about a free numeral in prose.
|
|
9
9
|
*/
|
|
10
10
|
import type { UnresolvedReference } from "../contracts/report-reference.js";
|
|
11
|
-
import { type ReportWarning } from "./block-walk.js";
|
|
12
|
-
import { type ReferenceResolver, type ReportSnapshot } from "./reference-resolver.js";
|
|
11
|
+
import { type AnyBlock, type CollectedReference, type ReportWarning } from "./block-walk.js";
|
|
12
|
+
import { type ReferenceResolver, type ReportSnapshot, type ResolvedValue } from "./reference-resolver.js";
|
|
13
13
|
export type { ReportWarning };
|
|
14
|
+
/**
|
|
15
|
+
* The cap on how many references resolve at the same time.
|
|
16
|
+
*
|
|
17
|
+
* A production resolver reads storage for each reference, and a large report holds hundreds of them. An
|
|
18
|
+
* unbounded fan-out would open one read for each reference at the same moment.
|
|
19
|
+
*/
|
|
20
|
+
export declare const RESOLUTION_CONCURRENCY = 8;
|
|
14
21
|
/** One schema conformance problem, reduced to a path and a message. */
|
|
15
22
|
export interface SchemaIssue {
|
|
16
23
|
path: string;
|
|
@@ -36,5 +43,30 @@ export type ReportValidation = {
|
|
|
36
43
|
resolutionFailures?: ResolutionFailure[];
|
|
37
44
|
warnings: ReportWarning[];
|
|
38
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Match each column that a chart encoding names against the table that its binding resolved to.
|
|
48
|
+
*
|
|
49
|
+
* The binding schema admits a table reference only, thus a resolved value of another type means that the
|
|
50
|
+
* bound resolver broke its own contract. That is reported and never ignored, because a chart with no
|
|
51
|
+
* table behind it renders nothing and a silent skip would pass it as grounded.
|
|
52
|
+
*/
|
|
53
|
+
export declare function checkChartEncoding(entry: CollectedReference, value: ResolvedValue): UnresolvedReference | undefined;
|
|
54
|
+
/** The resolved values and the failures of one resolution pass over the references of a block tree. */
|
|
55
|
+
export interface ReferenceResolution {
|
|
56
|
+
/** The resolved value of each block whose reference resolved, keyed by its block id. */
|
|
57
|
+
resolvedByBlock: Map<string, ResolvedValue>;
|
|
58
|
+
/** Each reference that did not resolve, or whose chart encoding named an absent column. */
|
|
59
|
+
failures: ResolutionFailure[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve each reference of a block tree under the concurrency bound, and run the chart-encoding match.
|
|
63
|
+
*
|
|
64
|
+
* The walk collects each reference one time, `allWithConcurrency` bounds the fan-out, and
|
|
65
|
+
* `checkChartEncoding` catches a chart that plots a column which the bound table does not hold. The record
|
|
66
|
+
* gate and the preview share this one pass, thus the two refuse the same references. A value-bearing block
|
|
67
|
+
* whose reference resolved carries its value in the map. A claim or a citation reference reads no value
|
|
68
|
+
* from the map. The walk collects every failure, thus a reviewer sees each block that broke.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveDocumentReferences(sections: readonly AnyBlock[], snapshot: ReportSnapshot, resolver: ReferenceResolver): Promise<ReferenceResolution>;
|
|
39
71
|
/** Resolve each reference, collect the schema conformance and the resolution outcomes, and warn on prose. */
|
|
40
72
|
export declare function validateReport(document: unknown, snapshot: ReportSnapshot, resolver: ReferenceResolver): Promise<ReportValidation>;
|
|
@@ -17,7 +17,7 @@ import { columnsHeldByNoRow } from "./reference-resolver.js";
|
|
|
17
17
|
* A production resolver reads storage for each reference, and a large report holds hundreds of them. An
|
|
18
18
|
* unbounded fan-out would open one read for each reference at the same moment.
|
|
19
19
|
*/
|
|
20
|
-
const RESOLUTION_CONCURRENCY = 8;
|
|
20
|
+
export const RESOLUTION_CONCURRENCY = 8;
|
|
21
21
|
/**
|
|
22
22
|
* Match each column that a chart encoding names against the table that its binding resolved to.
|
|
23
23
|
*
|
|
@@ -25,7 +25,7 @@ const RESOLUTION_CONCURRENCY = 8;
|
|
|
25
25
|
* bound resolver broke its own contract. That is reported and never ignored, because a chart with no
|
|
26
26
|
* table behind it renders nothing and a silent skip would pass it as grounded.
|
|
27
27
|
*/
|
|
28
|
-
function checkChartEncoding(entry, value) {
|
|
28
|
+
export function checkChartEncoding(entry, value) {
|
|
29
29
|
const encodingColumns = entry.encodingColumns;
|
|
30
30
|
if (encodingColumns === undefined || encodingColumns.length === 0) {
|
|
31
31
|
return undefined;
|
|
@@ -43,31 +43,51 @@ function checkChartEncoding(entry, value) {
|
|
|
43
43
|
detail: `the encoding names column ${absent.join(", ")}, which the bound table does not hold`,
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Resolve each reference of a block tree under the concurrency bound, and run the chart-encoding match.
|
|
48
|
+
*
|
|
49
|
+
* The walk collects each reference one time, `allWithConcurrency` bounds the fan-out, and
|
|
50
|
+
* `checkChartEncoding` catches a chart that plots a column which the bound table does not hold. The record
|
|
51
|
+
* gate and the preview share this one pass, thus the two refuse the same references. A value-bearing block
|
|
52
|
+
* whose reference resolved carries its value in the map. A claim or a citation reference reads no value
|
|
53
|
+
* from the map. The walk collects every failure, thus a reviewer sees each block that broke.
|
|
54
|
+
*/
|
|
55
|
+
export async function resolveDocumentReferences(sections, snapshot, resolver) {
|
|
56
|
+
const { references } = walkBlocks(sections);
|
|
57
|
+
// A realization that reads storage batches its reads here, one time before the loop. It fills a cache
|
|
58
|
+
// that each `resolve` then answers from. A realization without `prepare` keeps the per-reference read.
|
|
59
|
+
if (resolver.prepare !== undefined) {
|
|
60
|
+
await resolver.prepare(references.map((entry) => entry.reference), snapshot);
|
|
55
61
|
}
|
|
56
|
-
const { references, repeatedIds, warnings } = walkBlocks(parsed.data.sections);
|
|
57
62
|
const resolved = await allWithConcurrency(references.map((entry) => () => resolver.resolve(entry.reference, snapshot).then((result) => ({ entry, result }))), RESOLUTION_CONCURRENCY);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const resolutionFailures = [];
|
|
63
|
+
const failures = [];
|
|
64
|
+
const resolvedByBlock = new Map();
|
|
61
65
|
for (const { entry, result } of resolved) {
|
|
62
66
|
if (result.isErr()) {
|
|
63
|
-
|
|
67
|
+
failures.push({ blockId: entry.blockId, failure: result.error });
|
|
64
68
|
continue;
|
|
65
69
|
}
|
|
66
70
|
const encodingFailure = checkChartEncoding(entry, result.value);
|
|
67
71
|
if (encodingFailure !== undefined) {
|
|
68
|
-
|
|
72
|
+
failures.push({ blockId: entry.blockId, failure: encodingFailure });
|
|
73
|
+
continue;
|
|
69
74
|
}
|
|
75
|
+
resolvedByBlock.set(entry.blockId, result.value);
|
|
76
|
+
}
|
|
77
|
+
return { resolvedByBlock, failures };
|
|
78
|
+
}
|
|
79
|
+
/** Resolve each reference, collect the schema conformance and the resolution outcomes, and warn on prose. */
|
|
80
|
+
export async function validateReport(document, snapshot, resolver) {
|
|
81
|
+
const parsed = ReportDocumentSchema.safeParse(document);
|
|
82
|
+
if (!parsed.success) {
|
|
83
|
+
const schemaIssues = parsed.error.issues.map((issue) => ({
|
|
84
|
+
path: issue.path.map((segment) => String(segment)).join("."),
|
|
85
|
+
message: issue.message,
|
|
86
|
+
}));
|
|
87
|
+
return { valid: false, schemaIssues, warnings: [] };
|
|
70
88
|
}
|
|
89
|
+
const { repeatedIds, warnings } = walkBlocks(parsed.data.sections);
|
|
90
|
+
const { failures: resolutionFailures } = await resolveDocumentReferences(parsed.data.sections, snapshot, resolver);
|
|
71
91
|
if (repeatedIds.length > 0 || resolutionFailures.length > 0) {
|
|
72
92
|
return {
|
|
73
93
|
valid: false,
|
|
@@ -118,10 +118,33 @@ export declare const ECHARTS_THEME: {
|
|
|
118
118
|
};
|
|
119
119
|
};
|
|
120
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* The name of the readiness event, and the name of the window sentinel that guards a late listener. The
|
|
123
|
+
* bootstrap below emits both names, thus this module owns them. A capture that waits for the page reads the
|
|
124
|
+
* same two constants, thus the emitter and the waiter cannot disagree over a rename.
|
|
125
|
+
*/
|
|
126
|
+
export declare const THEME_READY_EVENT = "inflexa-theme-ready";
|
|
127
|
+
export declare const THEME_READY_SENTINEL = "__inflexaThemeReady";
|
|
128
|
+
/**
|
|
129
|
+
* The navigation budget and the readiness budget of one page capture, in milliseconds. The readiness budget
|
|
130
|
+
* bounds the wait for the event that the bootstrap dispatches. Every capture of this page reads the same two
|
|
131
|
+
* budgets, thus two capture sites cannot drift apart.
|
|
132
|
+
*/
|
|
133
|
+
export declare const PAGE_NAV_TIMEOUT_MS = 20000;
|
|
134
|
+
export declare const PAGE_READY_TIMEOUT_MS = 8000;
|
|
121
135
|
/**
|
|
122
136
|
* The page-side script that wires each chart. It finds every chart container, reads the option JSON from
|
|
123
137
|
* the sibling `<script type="application/json">` element, and initializes ECharts with the registered
|
|
124
138
|
* theme. The skeleton registers the theme before this script runs. A resize handler keeps each chart
|
|
125
139
|
* fit to the window.
|
|
140
|
+
*
|
|
141
|
+
* The script signals readiness when the bootstrap completes, and immediately when no chart exists. It sets
|
|
142
|
+
* the `window.__inflexaThemeReady` sentinel and dispatches the `inflexa-theme-ready` event on the document.
|
|
143
|
+
* A reader that captures the page keys on this signal, thus the capture returns when the page is ready and
|
|
144
|
+
* not at a timeout. The sentinel guards a listener that registers after the dispatch, thus a late listener
|
|
145
|
+
* still resolves.
|
|
146
|
+
*
|
|
147
|
+
* Each chart initializes inside a guard. One malformed option is exactly the fault that a look must
|
|
148
|
+
* diagnose, thus it must never stop a sibling chart and it must never withhold the readiness signal.
|
|
126
149
|
*/
|
|
127
150
|
export declare const CHART_BOOTSTRAP: string;
|
|
@@ -274,14 +274,42 @@ export const ECHARTS_THEME = {
|
|
|
274
274
|
splitLine: { lineStyle: { color: "#f1f5f9", type: "dashed" } },
|
|
275
275
|
},
|
|
276
276
|
};
|
|
277
|
+
/**
|
|
278
|
+
* The name of the readiness event, and the name of the window sentinel that guards a late listener. The
|
|
279
|
+
* bootstrap below emits both names, thus this module owns them. A capture that waits for the page reads the
|
|
280
|
+
* same two constants, thus the emitter and the waiter cannot disagree over a rename.
|
|
281
|
+
*/
|
|
282
|
+
export const THEME_READY_EVENT = "inflexa-theme-ready";
|
|
283
|
+
export const THEME_READY_SENTINEL = "__inflexaThemeReady";
|
|
284
|
+
/**
|
|
285
|
+
* The navigation budget and the readiness budget of one page capture, in milliseconds. The readiness budget
|
|
286
|
+
* bounds the wait for the event that the bootstrap dispatches. Every capture of this page reads the same two
|
|
287
|
+
* budgets, thus two capture sites cannot drift apart.
|
|
288
|
+
*/
|
|
289
|
+
export const PAGE_NAV_TIMEOUT_MS = 20_000;
|
|
290
|
+
export const PAGE_READY_TIMEOUT_MS = 8_000;
|
|
277
291
|
/**
|
|
278
292
|
* The page-side script that wires each chart. It finds every chart container, reads the option JSON from
|
|
279
293
|
* the sibling `<script type="application/json">` element, and initializes ECharts with the registered
|
|
280
294
|
* theme. The skeleton registers the theme before this script runs. A resize handler keeps each chart
|
|
281
295
|
* fit to the window.
|
|
296
|
+
*
|
|
297
|
+
* The script signals readiness when the bootstrap completes, and immediately when no chart exists. It sets
|
|
298
|
+
* the `window.__inflexaThemeReady` sentinel and dispatches the `inflexa-theme-ready` event on the document.
|
|
299
|
+
* A reader that captures the page keys on this signal, thus the capture returns when the page is ready and
|
|
300
|
+
* not at a timeout. The sentinel guards a listener that registers after the dispatch, thus a late listener
|
|
301
|
+
* still resolves.
|
|
302
|
+
*
|
|
303
|
+
* Each chart initializes inside a guard. One malformed option is exactly the fault that a look must
|
|
304
|
+
* diagnose, thus it must never stop a sibling chart and it must never withhold the readiness signal.
|
|
282
305
|
*/
|
|
283
306
|
export const CHART_BOOTSTRAP = `(function () {
|
|
307
|
+
function signalReady() {
|
|
308
|
+
window.${THEME_READY_SENTINEL} = true;
|
|
309
|
+
document.dispatchEvent(new Event(${JSON.stringify(THEME_READY_EVENT)}));
|
|
310
|
+
}
|
|
284
311
|
if (typeof echarts === "undefined") {
|
|
312
|
+
signalReady();
|
|
285
313
|
return;
|
|
286
314
|
}
|
|
287
315
|
var containers = document.querySelectorAll("[data-echarts-id]");
|
|
@@ -291,9 +319,13 @@ export const CHART_BOOTSTRAP = `(function () {
|
|
|
291
319
|
if (!optionScript || optionScript.getAttribute("type") !== "application/json") {
|
|
292
320
|
continue;
|
|
293
321
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
322
|
+
try {
|
|
323
|
+
var option = JSON.parse(optionScript.textContent || "{}");
|
|
324
|
+
var chart = echarts.init(container, ${JSON.stringify(ECHARTS_THEME_NAME)});
|
|
325
|
+
chart.setOption(option);
|
|
326
|
+
} catch (cause) {
|
|
327
|
+
console.error("chart bootstrap failed for " + (container.getAttribute("data-echarts-id") || "(no id)") + ": " + (cause && cause.message ? cause.message : cause));
|
|
328
|
+
}
|
|
297
329
|
}
|
|
298
330
|
window.addEventListener("resize", function () {
|
|
299
331
|
var nodes = document.querySelectorAll("[data-echarts-id]");
|
|
@@ -304,4 +336,5 @@ export const CHART_BOOTSTRAP = `(function () {
|
|
|
304
336
|
}
|
|
305
337
|
}
|
|
306
338
|
});
|
|
339
|
+
signalReady();
|
|
307
340
|
})();`;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value bridge: a resolved value maps onto a render value, keyed by block id.
|
|
3
|
+
*
|
|
4
|
+
* The resolver gives one `ResolvedValue` for each reference. The renderer takes a `RenderValues` map. The
|
|
5
|
+
* two models agree on a scalar, a table, and a citation, but they part on a figure. A `ResolvedValue`
|
|
6
|
+
* file echo carries a path and a hash, and a page needs a source string. Thus the bridge takes a
|
|
7
|
+
* caller-supplied policy that turns a file echo into a `src` string. The preview tool gives the concrete
|
|
8
|
+
* policy, because the page directory and its asset access are a session concern.
|
|
9
|
+
*
|
|
10
|
+
* The bridge is pure. It reads no file, it resolves no reference, and it imports nothing from the state
|
|
11
|
+
* layer or the tool layer. A block kind that carries no render value maps to no entry. A block kind whose
|
|
12
|
+
* resolved value has the wrong type becomes one typed mismatch. The bridge collects every mismatch, thus
|
|
13
|
+
* one call reports each fault at one time.
|
|
14
|
+
*/
|
|
15
|
+
import { type Result } from "neverthrow";
|
|
16
|
+
import type { ResolvedValue } from "../report-model/reference-resolver.js";
|
|
17
|
+
import type { RenderValues } from "./types.js";
|
|
18
|
+
/** The block kinds that carry no render value. Each one renders from the block and the ledger alone. */
|
|
19
|
+
type NoValueKind = "text" | "claim" | "citation" | "section";
|
|
20
|
+
/** The block kinds that carry one render value under the block id in the map. */
|
|
21
|
+
type ValueBearingKind = "metric" | "table" | "chart" | "figure";
|
|
22
|
+
/** The file echo that a figure binding resolves to: a whole-file pin of a path and a hash. */
|
|
23
|
+
export type ResolvedFile = Extract<ResolvedValue, {
|
|
24
|
+
type: "file";
|
|
25
|
+
}>;
|
|
26
|
+
/** The policy that turns a resolved file echo into a figure `src` string. The caller owns the policy. */
|
|
27
|
+
export type FigureSourcePolicy = (file: ResolvedFile) => string;
|
|
28
|
+
/**
|
|
29
|
+
* The resolution result of one block: the block id, the block kind, and the resolved value of its binding.
|
|
30
|
+
*
|
|
31
|
+
* A value-bearing kind carries the resolved value. A no-value kind carries none, thus a text block with
|
|
32
|
+
* no binding is representable and a value on it is not.
|
|
33
|
+
*/
|
|
34
|
+
export type BlockResolution = {
|
|
35
|
+
blockId: string;
|
|
36
|
+
kind: NoValueKind;
|
|
37
|
+
} | {
|
|
38
|
+
blockId: string;
|
|
39
|
+
kind: ValueBearingKind;
|
|
40
|
+
resolved: ResolvedValue;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* One typed mismatch between a block kind and a resolved value type. `blockId` names the block, and
|
|
44
|
+
* `blockKind` names the kind that sets the expectation. `expected` names the resolved type that the kind
|
|
45
|
+
* needs, and `actual` names the resolved type that arrived.
|
|
46
|
+
*/
|
|
47
|
+
export interface BridgeMismatch {
|
|
48
|
+
blockId: string;
|
|
49
|
+
blockKind: ValueBearingKind;
|
|
50
|
+
expected: ResolvedValue["type"];
|
|
51
|
+
actual: ResolvedValue["type"];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Map the resolved values of the blocks onto a `RenderValues` map.
|
|
55
|
+
*
|
|
56
|
+
* The bridge walks each resolution in order. A no-value kind adds no entry. A value-bearing kind whose
|
|
57
|
+
* resolved type matches adds one entry. A value-bearing kind whose resolved type does not match adds one
|
|
58
|
+
* mismatch. A run with a mismatch returns the mismatch list and no map. A run with no mismatch returns the
|
|
59
|
+
* complete map.
|
|
60
|
+
*/
|
|
61
|
+
export declare function bridgeValues(resolutions: readonly BlockResolution[], figureSrc: FigureSourcePolicy): Result<RenderValues, BridgeMismatch[]>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value bridge: a resolved value maps onto a render value, keyed by block id.
|
|
3
|
+
*
|
|
4
|
+
* The resolver gives one `ResolvedValue` for each reference. The renderer takes a `RenderValues` map. The
|
|
5
|
+
* two models agree on a scalar, a table, and a citation, but they part on a figure. A `ResolvedValue`
|
|
6
|
+
* file echo carries a path and a hash, and a page needs a source string. Thus the bridge takes a
|
|
7
|
+
* caller-supplied policy that turns a file echo into a `src` string. The preview tool gives the concrete
|
|
8
|
+
* policy, because the page directory and its asset access are a session concern.
|
|
9
|
+
*
|
|
10
|
+
* The bridge is pure. It reads no file, it resolves no reference, and it imports nothing from the state
|
|
11
|
+
* layer or the tool layer. A block kind that carries no render value maps to no entry. A block kind whose
|
|
12
|
+
* resolved value has the wrong type becomes one typed mismatch. The bridge collects every mismatch, thus
|
|
13
|
+
* one call reports each fault at one time.
|
|
14
|
+
*/
|
|
15
|
+
import { err, ok } from "neverthrow";
|
|
16
|
+
/**
|
|
17
|
+
* Map the resolved values of the blocks onto a `RenderValues` map.
|
|
18
|
+
*
|
|
19
|
+
* The bridge walks each resolution in order. A no-value kind adds no entry. A value-bearing kind whose
|
|
20
|
+
* resolved type matches adds one entry. A value-bearing kind whose resolved type does not match adds one
|
|
21
|
+
* mismatch. A run with a mismatch returns the mismatch list and no map. A run with no mismatch returns the
|
|
22
|
+
* complete map.
|
|
23
|
+
*/
|
|
24
|
+
export function bridgeValues(resolutions, figureSrc) {
|
|
25
|
+
const values = {};
|
|
26
|
+
const mismatches = [];
|
|
27
|
+
for (const resolution of resolutions) {
|
|
28
|
+
switch (resolution.kind) {
|
|
29
|
+
case "text":
|
|
30
|
+
case "claim":
|
|
31
|
+
case "citation":
|
|
32
|
+
case "section":
|
|
33
|
+
break;
|
|
34
|
+
case "metric": {
|
|
35
|
+
const value = resolution.resolved;
|
|
36
|
+
if (value.type !== "scalar") {
|
|
37
|
+
mismatches.push(mismatch(resolution.blockId, "metric", "scalar", value.type));
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
values[resolution.blockId] = { type: "scalar", value: value.value };
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "table":
|
|
44
|
+
case "chart": {
|
|
45
|
+
const value = resolution.resolved;
|
|
46
|
+
if (value.type !== "table") {
|
|
47
|
+
mismatches.push(mismatch(resolution.blockId, resolution.kind, "table", value.type));
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
values[resolution.blockId] = tableValue(value);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case "figure": {
|
|
54
|
+
const value = resolution.resolved;
|
|
55
|
+
if (value.type !== "file") {
|
|
56
|
+
mismatches.push(mismatch(resolution.blockId, "figure", "file", value.type));
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
values[resolution.blockId] = { type: "figure", src: figureSrc(value) };
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (mismatches.length > 0) {
|
|
65
|
+
return err(mismatches);
|
|
66
|
+
}
|
|
67
|
+
return ok(values);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Map a resolved table onto a render table. The explicit column order carries through when it is present,
|
|
71
|
+
* and it stays absent otherwise, thus the renderer keeps its first-row order as the default.
|
|
72
|
+
*/
|
|
73
|
+
function tableValue(value) {
|
|
74
|
+
if (value.columns !== undefined) {
|
|
75
|
+
return { type: "table", rows: value.rows, columns: value.columns };
|
|
76
|
+
}
|
|
77
|
+
return { type: "table", rows: value.rows };
|
|
78
|
+
}
|
|
79
|
+
/** Make one mismatch that names the block, its kind, the resolved type that it needs, and the type that arrived. */
|
|
80
|
+
function mismatch(blockId, blockKind, expected, actual) {
|
|
81
|
+
return { blockId, blockKind, expected, actual };
|
|
82
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type Result } from "neverthrow";
|
|
18
18
|
import { type ConversationAgentDeps } from "../agents/conversation-agent.js";
|
|
19
|
+
import { type ReportSessionRuntime } from "../app/report-session-runtime.js";
|
|
19
20
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
20
21
|
import type { ResourcePolicy } from "../config/resource-limits.js";
|
|
21
22
|
import type { AgentDefinition } from "../loop/types.js";
|
|
@@ -24,8 +25,11 @@ import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalys
|
|
|
24
25
|
import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } from "../workflows/sandbox-step.js";
|
|
25
26
|
import { type ExecuteTargetAssessmentDeps, type ExecuteTargetAssessmentInput, type ExecuteTargetAssessmentResult } from "../workflows/execute-target-assessment.js";
|
|
26
27
|
import { type DataProfileDeps, type DataProfileWorkflowInput } from "../tasks/data-profile.js";
|
|
28
|
+
import { type ExtractValuesResult, type ExtractValuesWorkflowInput } from "../tasks/extract-values.js";
|
|
27
29
|
import { type CitationResolverConfig } from "../citations/resolve.js";
|
|
28
30
|
import type { CitationResolver } from "../citations/types.js";
|
|
31
|
+
import type { AuthContext } from "../auth/types.js";
|
|
32
|
+
import type { ReferenceResolver } from "../report-model/reference-resolver.js";
|
|
29
33
|
/** Registered child sandbox-step callable the parent's child dispatch closes over. */
|
|
30
34
|
export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxStepResult>;
|
|
31
35
|
/**
|
|
@@ -53,6 +57,7 @@ export interface RegisteredWorkflows {
|
|
|
53
57
|
readonly sandboxStep: SandboxStepCallable;
|
|
54
58
|
readonly executeTargetAssessment: (input: ExecuteTargetAssessmentInput) => Promise<ExecuteTargetAssessmentResult>;
|
|
55
59
|
readonly dataProfile: (input: DataProfileWorkflowInput) => Promise<void>;
|
|
60
|
+
readonly extractValues: (input: ExtractValuesWorkflowInput) => Promise<ExtractValuesResult>;
|
|
56
61
|
}
|
|
57
62
|
/**
|
|
58
63
|
* Conversation-agent deps minus the workflow callable, the resource policy, and
|
|
@@ -61,6 +66,19 @@ export interface RegisteredWorkflows {
|
|
|
61
66
|
* workflows see, or a recorder that only half the agent tree reports to.
|
|
62
67
|
*/
|
|
63
68
|
export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder" | "citationResolver">;
|
|
69
|
+
/**
|
|
70
|
+
* What binds one reference resolver: the analysis whose pinned artifacts it reads, and the auth of the tool
|
|
71
|
+
* call that reads them. A resolver serves one analysis, thus the report tools build one for each call.
|
|
72
|
+
*/
|
|
73
|
+
export interface ReportResolverScope {
|
|
74
|
+
readonly analysisId: string;
|
|
75
|
+
readonly auth: AuthContext;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The factory that gives the reference resolver of one report tool call. The harness wires the production
|
|
79
|
+
* realization, and an embedder can bind its own at its composition root.
|
|
80
|
+
*/
|
|
81
|
+
export type MakeReportReferenceResolver = (scope: ReportResolverScope) => ReferenceResolver;
|
|
64
82
|
export interface CoreRuntimeDeps {
|
|
65
83
|
readonly conversation: ConversationAssemblyDeps;
|
|
66
84
|
readonly workflows: CoreWorkflowDeps;
|
|
@@ -78,6 +96,20 @@ export interface CoreRuntimeDeps {
|
|
|
78
96
|
readonly usageRecorder?: UsageRecorder;
|
|
79
97
|
/** Harness-owned citation capability configuration; no ambient lookup occurs. */
|
|
80
98
|
readonly citationResolverConfig?: CitationResolverConfig;
|
|
99
|
+
/**
|
|
100
|
+
* The byte cap on the in-process host read of a report reference. A file over the
|
|
101
|
+
* cap falls through to the extraction arm. Absent, the resolver uses its 16 MiB
|
|
102
|
+
* default. The embedder tunes it here, thus a host changes it with no harness change.
|
|
103
|
+
*/
|
|
104
|
+
readonly reportHostReadCapBytes?: number;
|
|
105
|
+
/**
|
|
106
|
+
* The report reference resolver of an embedder. A managed host reads its pinned
|
|
107
|
+
* artifacts from its own store, thus it binds a realization of the seam here and the
|
|
108
|
+
* report tools resolve through it. Absent, the harness wires the production resolver
|
|
109
|
+
* over the workspace tree and the extraction arm, which is the whole OSS path. A bound
|
|
110
|
+
* factory replaces that wiring, thus `reportHostReadCapBytes` then governs nothing.
|
|
111
|
+
*/
|
|
112
|
+
readonly makeReportReferenceResolver?: MakeReportReferenceResolver;
|
|
81
113
|
}
|
|
82
114
|
/**
|
|
83
115
|
* The refusal a thread type carries no agent yet. `report` is a valid
|
|
@@ -120,6 +152,13 @@ export interface CoreRuntime {
|
|
|
120
152
|
* on the `Result` channel.
|
|
121
153
|
*/
|
|
122
154
|
readonly agents: ThreadAgentResolver;
|
|
155
|
+
/**
|
|
156
|
+
* The turn-start anchor of a report session. A serving path runs it at the
|
|
157
|
+
* turn start, thus a report tool call never arrives before the session state
|
|
158
|
+
* exists. The gateway stays behind the tool boundary, thus the runtime exposes
|
|
159
|
+
* the anchor operation alone here, and a later change wires it at the turn start.
|
|
160
|
+
*/
|
|
161
|
+
readonly reportSession: Pick<ReportSessionRuntime, "ensureSessionState">;
|
|
123
162
|
readonly workflows: RegisteredWorkflows;
|
|
124
163
|
readonly citationResolver: CitationResolver;
|
|
125
164
|
}
|
package/dist/runtime/assemble.js
CHANGED
|
@@ -16,12 +16,18 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { err, ok } from "neverthrow";
|
|
18
18
|
import { createConversationAgent } from "../agents/conversation-agent.js";
|
|
19
|
+
import { createReportSessionAgent } from "../agents/report-session-agent.js";
|
|
20
|
+
import { createReportSessionRuntime } from "../app/report-session-runtime.js";
|
|
19
21
|
import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
|
|
20
22
|
import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
|
|
21
23
|
import { registerSandboxStep } from "../workflows/sandbox-step.js";
|
|
22
24
|
import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
|
|
23
25
|
import { registerDataProfileWorkflow } from "../tasks/data-profile.js";
|
|
26
|
+
import { bindExtractionTrigger, createExtractionArm, registerExtractValuesWorkflow, } from "../tasks/extract-values.js";
|
|
24
27
|
import { createCitationResolver } from "../citations/resolve.js";
|
|
28
|
+
import { createThreadStore } from "../memory/thread-store.js";
|
|
29
|
+
import { createArtifactReadStore, createProductionResolver } from "../report-model/production-resolver.js";
|
|
30
|
+
import { createReportVersionStore } from "../state/report-versions.js";
|
|
25
31
|
const _assertDomainError = true;
|
|
26
32
|
/**
|
|
27
33
|
* Wrap a type→agent registry as the resolution surface. Held apart from
|
|
@@ -60,6 +66,14 @@ export function assembleCoreRuntime(deps) {
|
|
|
60
66
|
const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), citationResolver, usageRecorder });
|
|
61
67
|
const executeTargetAssessment = registerExecuteTargetAssessment({ ...wf.executeTargetAssessment, usageRecorder });
|
|
62
68
|
const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder });
|
|
69
|
+
// The extraction workflow shares the profile's sandbox and authorization rails, thus it draws the same
|
|
70
|
+
// three seams from the profile deps. The report resolver factory binds the extraction arm over this
|
|
71
|
+
// callable, thus a fall-through report reference reads its file out of process on the profile rails.
|
|
72
|
+
const extractValues = registerExtractValuesWorkflow({
|
|
73
|
+
sandboxClient: wf.dataProfile.sandboxClient,
|
|
74
|
+
runAuthorizer: wf.dataProfile.runAuthorizer,
|
|
75
|
+
...(wf.dataProfile.logger ? { logger: wf.dataProfile.logger } : {}),
|
|
76
|
+
});
|
|
63
77
|
const conversationAgent = createConversationAgent({
|
|
64
78
|
...conversation,
|
|
65
79
|
executeAnalysisWorkflow: executeAnalysis,
|
|
@@ -67,22 +81,69 @@ export function assembleCoreRuntime(deps) {
|
|
|
67
81
|
usageRecorder,
|
|
68
82
|
citationResolver,
|
|
69
83
|
});
|
|
70
|
-
// The
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
84
|
+
// The report agent is a singleton over the conversation deps. The session
|
|
85
|
+
// runtime binds the per-session state to the thread behind the tool boundary,
|
|
86
|
+
// thus one definition serves every report thread. Its preview tool writes the
|
|
87
|
+
// page into the analysis tree and returns the path, thus the page write reaches
|
|
88
|
+
// no host seam.
|
|
89
|
+
const reportSession = createReportSessionRuntime({ pool: conversation.pool, ...(conversation.logger ? { logger: conversation.logger } : {}) });
|
|
90
|
+
const reportVersionStore = createReportVersionStore({ pool: conversation.pool, ...(conversation.logger ? { logger: conversation.logger } : {}) });
|
|
91
|
+
const reportThreads = createThreadStore(conversation.pool);
|
|
92
|
+
// The read cache of the production report path, one for each recent analysis. A preview
|
|
93
|
+
// and the record that follows it are two tool calls over one draft, thus they meet the
|
|
94
|
+
// same artifacts. The store carries the streamed hash and the extracted rows between
|
|
95
|
+
// them, and each resolver still binds its own extraction arm below. Thus the auth of a
|
|
96
|
+
// call never outlives the call, and one unchanged file costs one hash and one container.
|
|
97
|
+
const reportArtifactReads = createArtifactReadStore();
|
|
98
|
+
// The reference resolver of the report path, and its production wiring. A resolver reads
|
|
99
|
+
// the pinned artifacts of one analysis, thus the factory binds one analysis and one auth
|
|
100
|
+
// for each tool call. The extraction arm falls through to the registered extract-values
|
|
101
|
+
// callable for an over-cap file or a host parse fault. The cap gates the host read, and
|
|
102
|
+
// the resolver defaults it to 16 MiB.
|
|
103
|
+
const makeProductionReportResolver = (scope) => {
|
|
104
|
+
const arm = createExtractionArm(bindExtractionTrigger({ runAuthorizer: conversation.runAuthorizer, workflow: extractValues }, { auth: scope.auth, analysisId: scope.analysisId }));
|
|
105
|
+
return createProductionResolver({
|
|
106
|
+
workspaceRoot: conversation.resolveWorkspaceRoot(scope.analysisId),
|
|
107
|
+
analysisId: scope.analysisId,
|
|
108
|
+
extractionArm: arm,
|
|
109
|
+
readCache: reportArtifactReads.forAnalysis(scope.analysisId),
|
|
110
|
+
...(deps.reportHostReadCapBytes !== undefined ? { cap: deps.reportHostReadCapBytes } : {}),
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const makeReportResolver = deps.makeReportReferenceResolver ?? makeProductionReportResolver;
|
|
114
|
+
const reportAgent = createReportSessionAgent({
|
|
115
|
+
model: conversation.model,
|
|
116
|
+
pool: conversation.pool,
|
|
117
|
+
embedding: conversation.embedding,
|
|
118
|
+
workspaceFs: conversation.workspaceFs,
|
|
119
|
+
gateway: reportSession.gateway,
|
|
120
|
+
resolveWorkspaceRoot: conversation.resolveWorkspaceRoot,
|
|
121
|
+
store: reportVersionStore,
|
|
122
|
+
threads: reportThreads,
|
|
123
|
+
chrome: conversation.chrome,
|
|
124
|
+
makeResolver: makeReportResolver,
|
|
125
|
+
...(conversation.logger ? { logger: conversation.logger } : {}),
|
|
126
|
+
});
|
|
127
|
+
// The single registration point for every typed agent. A future thread type's
|
|
128
|
+
// agent plugs in here at assembly with no embedder change. `conversation` is
|
|
129
|
+
// required at the type level because boot resolves it unconditionally
|
|
130
|
+
// (`boot.ts` backfill). `report` is required now that its agent registers here,
|
|
131
|
+
// so a dropped registration fails tsc, and it does not surface only as a
|
|
132
|
+
// resolution refusal. The `Partial` over the rest keeps the compiler honest
|
|
133
|
+
// about a type that has no agent yet.
|
|
76
134
|
const agents = {
|
|
77
135
|
conversation: conversationAgent,
|
|
136
|
+
report: reportAgent,
|
|
78
137
|
};
|
|
79
138
|
return {
|
|
80
139
|
agents: createThreadAgentResolver(agents),
|
|
140
|
+
reportSession: { ensureSessionState: reportSession.ensureSessionState },
|
|
81
141
|
workflows: {
|
|
82
142
|
executeAnalysis,
|
|
83
143
|
sandboxStep,
|
|
84
144
|
executeTargetAssessment,
|
|
85
145
|
dataProfile,
|
|
146
|
+
extractValues,
|
|
86
147
|
},
|
|
87
148
|
citationResolver,
|
|
88
149
|
};
|
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
* (see {@link buildSessionSubPaths}).
|
|
18
18
|
*/
|
|
19
19
|
export declare const STEP_SUBDIRS: readonly ["output", "scripts", "figures", "logs", "notebooks"];
|
|
20
|
+
/**
|
|
21
|
+
* Container mountpoints of the two read-only stores. Exported because they are
|
|
22
|
+
* also the discovery tools' default read root: a host whose own process sees a
|
|
23
|
+
* store the way the sandbox does configures nothing further.
|
|
24
|
+
*/
|
|
25
|
+
export declare const LIBS_CONTAINER_PATH = "/mnt/libs";
|
|
26
|
+
export declare const REFS_CONTAINER_PATH = "/mnt/refs";
|
|
20
27
|
export interface MountPlanCoords {
|
|
21
28
|
analysisId: string;
|
|
22
29
|
runId: string;
|
|
@@ -18,8 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { assertSafeId } from "../workspace/paths.js";
|
|
20
20
|
export const STEP_SUBDIRS = ["output", "scripts", "figures", "logs", "notebooks"];
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Container mountpoints of the two read-only stores. Exported because they are
|
|
23
|
+
* also the discovery tools' default read root: a host whose own process sees a
|
|
24
|
+
* store the way the sandbox does configures nothing further.
|
|
25
|
+
*/
|
|
26
|
+
export const LIBS_CONTAINER_PATH = "/mnt/libs";
|
|
27
|
+
export const REFS_CONTAINER_PATH = "/mnt/refs";
|
|
23
28
|
/**
|
|
24
29
|
* The step tail beneath an analysis's workspace root, in both container and
|
|
25
30
|
* PVC-relative space. The harness owns this layout (workspace-layout spec).
|