@inflexa-ai/harness 0.8.0 → 0.9.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/dist/contracts/message.d.ts +6 -0
- package/dist/execution/report-runner.d.ts +6 -1
- package/dist/execution/report-runner.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/loop/run-agent.d.ts +1 -1
- package/dist/loop/run-agent.js +55 -2
- package/dist/memory/ai-sdk-message-storage.d.ts +42 -0
- package/dist/memory/ai-sdk-message-storage.js +62 -0
- package/dist/memory/content-to-cortex.d.ts +8 -1
- package/dist/memory/content-to-cortex.js +36 -4
- package/dist/memory/thread-history.d.ts +37 -0
- package/dist/memory/thread-history.js +74 -7
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -3
- package/dist/prompts/report-builder.d.ts +1 -1
- package/dist/prompts/report-builder.js +1 -1
- package/dist/providers/streaming-chat.d.ts +11 -2
- package/dist/providers/streaming-chat.js +31 -6
- package/dist/providers/types.d.ts +10 -1
- package/dist/tools/iterate-report.d.ts +0 -1
- package/dist/tools/iterate-report.js +125 -11
- package/dist/tools/report/mint-preview-url.d.ts +1 -1
- package/dist/tools/report/mint-preview-url.js +7 -5
- package/dist/tools/report/preview-publisher.d.ts +16 -0
- package/dist/tools/report/preview-publisher.js +20 -0
- package/dist/tools/report/preview-snapshot.d.ts +9 -1
- package/dist/tools/report/preview-snapshot.js +15 -1
- package/dist/tools/report/submit-report.d.ts +1 -0
- package/dist/tools/report/submit-report.js +32 -11
- package/dist/tools/report/version-fs.d.ts +4 -4
- package/dist/tools/report/version-fs.js +24 -17
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* `data-report-preview` until this tool writes the success outcome.
|
|
4
4
|
*
|
|
5
5
|
* Checks:
|
|
6
|
+
* - report.html.j2 exists at versionDir/ — the report itself
|
|
6
7
|
* - index.html exists at versionDir/ with size > 0
|
|
7
8
|
* - no unrendered `{{ … }}` or `{% … %}` left in the output
|
|
8
9
|
* - all referenced asset paths resolve to files on disk
|
|
@@ -23,26 +24,46 @@ const UNRENDERED_JINJA = /\{\{[^{}]*\}\}|\{%-?\s*\w/;
|
|
|
23
24
|
export function createSubmitReportTool(state) {
|
|
24
25
|
return defineTool({
|
|
25
26
|
id: "submit_report",
|
|
26
|
-
description: "Declare the report ready. Validates
|
|
27
|
-
"unrendered Jinja, and that all
|
|
27
|
+
description: "Declare the report ready. Validates report.html.j2 and index.html " +
|
|
28
|
+
"exist, that the output has no unrendered Jinja, and that all " +
|
|
29
|
+
"referenced asset paths resolve. " +
|
|
28
30
|
"You MUST call this to finish — the runner ignores any other claim " +
|
|
29
31
|
"of success. Returns problems[] when checks fail; fix and re-submit. " +
|
|
30
|
-
"Use `notes` to surface caveats to the caller (e.g.
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"agent and the user in the iteration result.",
|
|
32
|
+
"Use `notes` to surface caveats to the caller (e.g. sections rendered " +
|
|
33
|
+
"with partial data, missing inputs that forced a fallback) — these " +
|
|
34
|
+
"reach the conversation agent and the user in the iteration result.",
|
|
34
35
|
inputSchema: z.object({
|
|
35
36
|
notes: z
|
|
36
37
|
.array(z.string())
|
|
37
38
|
.optional()
|
|
38
39
|
.describe("Caveats / issues to surface to the caller. One short sentence " +
|
|
39
|
-
"per item. Examples: '
|
|
40
|
-
"'
|
|
40
|
+
"per item. Examples: 'pathway-enrichment section omitted — no " +
|
|
41
|
+
"GSEA output found', 'cohort table rendered from 8 of 12 " +
|
|
42
|
+
"samples — the rest had no QC row'. " +
|
|
41
43
|
"Leave empty when the report shipped cleanly."),
|
|
42
44
|
}),
|
|
43
45
|
execute: async (input) => {
|
|
44
46
|
const problems = [];
|
|
45
|
-
// 1.
|
|
47
|
+
// 1. report.html.j2 exists — the report itself, as opposed to the
|
|
48
|
+
// artifact rendered from it. Nothing downstream re-derives this: a
|
|
49
|
+
// version that is finalized without a template persists as one, and
|
|
50
|
+
// the next iteration copies a template that isn't there, then tells
|
|
51
|
+
// the builder to edit it in place. Checked first because an absent
|
|
52
|
+
// template is the cause the checks below can only report symptoms of.
|
|
53
|
+
const templatePath = join(state.versionDir, "report.html.j2");
|
|
54
|
+
let hasTemplate;
|
|
55
|
+
try {
|
|
56
|
+
hasTemplate = (await stat(templatePath)).isFile();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
hasTemplate = false;
|
|
60
|
+
}
|
|
61
|
+
if (!hasTemplate) {
|
|
62
|
+
problems.push("report.html.j2 is missing — a report is authored as report.html.j2 and " +
|
|
63
|
+
"rendered into index.html by build_report; a hand-written index.html is " +
|
|
64
|
+
"not a report. Write the template, then call build_report.");
|
|
65
|
+
}
|
|
66
|
+
// 2. index.html exists and is non-empty
|
|
46
67
|
const indexPath = join(state.versionDir, "index.html");
|
|
47
68
|
let html;
|
|
48
69
|
try {
|
|
@@ -57,13 +78,13 @@ export function createSubmitReportTool(state) {
|
|
|
57
78
|
problems.push("index.html does not exist — call build_report first");
|
|
58
79
|
return ok({ ok: false, problems });
|
|
59
80
|
}
|
|
60
|
-
//
|
|
81
|
+
// 3. No unrendered Jinja markers
|
|
61
82
|
if (UNRENDERED_JINJA.test(html)) {
|
|
62
83
|
const match = html.match(UNRENDERED_JINJA);
|
|
63
84
|
problems.push(`unrendered Jinja in output (e.g. ${JSON.stringify(match?.[0] ?? "")}); ` +
|
|
64
85
|
"build_report likely failed silently or report.html.j2 was edited after build");
|
|
65
86
|
}
|
|
66
|
-
//
|
|
87
|
+
// 4. Asset reference resolution
|
|
67
88
|
const refs = extractLocalRefs(html);
|
|
68
89
|
for (const ref of refs) {
|
|
69
90
|
const resolved = resolveLocalRef(ref, state.versionDir, state.assetsDir);
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* In-process workspace surface for the report-builder agent.
|
|
3
3
|
*
|
|
4
4
|
* Tight read/write/mkdir surface scoped to the version directory.
|
|
5
|
-
* Agent-supplied paths are
|
|
6
|
-
* escapes is rejected as `out_of_scope`. Constructed
|
|
7
|
-
* iterate-report runner so it always shares the same `versionDir`
|
|
8
|
-
* rest of the report tools.
|
|
5
|
+
* Agent-supplied paths are relative to `versionDir`; an absolute path, or one
|
|
6
|
+
* that escapes the directory, is rejected as `out_of_scope`. Constructed
|
|
7
|
+
* inside the iterate-report runner so it always shares the same `versionDir`
|
|
8
|
+
* as the rest of the report tools.
|
|
9
9
|
*/
|
|
10
10
|
import { type Tool } from "../define-tool.js";
|
|
11
11
|
export interface VersionFsToolsState {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* In-process workspace surface for the report-builder agent.
|
|
3
3
|
*
|
|
4
4
|
* Tight read/write/mkdir surface scoped to the version directory.
|
|
5
|
-
* Agent-supplied paths are
|
|
6
|
-
* escapes is rejected as `out_of_scope`. Constructed
|
|
7
|
-
* iterate-report runner so it always shares the same `versionDir`
|
|
8
|
-
* rest of the report tools.
|
|
5
|
+
* Agent-supplied paths are relative to `versionDir`; an absolute path, or one
|
|
6
|
+
* that escapes the directory, is rejected as `out_of_scope`. Constructed
|
|
7
|
+
* inside the iterate-report runner so it always shares the same `versionDir`
|
|
8
|
+
* as the rest of the report tools.
|
|
9
9
|
*/
|
|
10
10
|
import { mkdir as fsMkdir, readFile as fsReadFile, stat as fsStat, writeFile as fsWriteFile } from "node:fs/promises";
|
|
11
11
|
import { dirname, resolve as resolvePath, sep } from "node:path";
|
|
@@ -19,13 +19,15 @@ function resolveAgentPath(agentPath, versionDir) {
|
|
|
19
19
|
if (agentPath.length === 0 || agentPath.includes("\0")) {
|
|
20
20
|
return { kind: "out_of_scope", reason: "empty or null-byte path" };
|
|
21
21
|
}
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// A leading slash is refused rather than trimmed. Trimming it makes the
|
|
23
|
+
// containment check below unreachable for absolute paths: a deep one such
|
|
24
|
+
// as "/previews/abc/v1/report.html.j2" would land a real file several
|
|
25
|
+
// directories below versionDir and report success, and the builder — which
|
|
26
|
+
// has no directory-listing tool — cannot find where its template went.
|
|
27
|
+
if (agentPath.startsWith("/")) {
|
|
28
|
+
return { kind: "out_of_scope", reason: `absolute path rejected, use one relative to the version dir: ${agentPath}` };
|
|
27
29
|
}
|
|
28
|
-
const absolute = resolvePath(versionDir,
|
|
30
|
+
const absolute = resolvePath(versionDir, agentPath);
|
|
29
31
|
if (absolute !== versionDir && !absolute.startsWith(versionDir + sep)) {
|
|
30
32
|
return { kind: "out_of_scope", reason: `path escapes version dir: ${agentPath}` };
|
|
31
33
|
}
|
|
@@ -34,9 +36,10 @@ function resolveAgentPath(agentPath, versionDir) {
|
|
|
34
36
|
export function createVersionFsTools(state) {
|
|
35
37
|
const writeFile = defineTool({
|
|
36
38
|
id: "write_file",
|
|
37
|
-
description: "Write a UTF-8 text file under the report's version directory.
|
|
38
|
-
"relative
|
|
39
|
-
"
|
|
39
|
+
description: "Write a UTF-8 text file under the report's version directory. Paths " +
|
|
40
|
+
"are relative to it (e.g. `report.html.j2`, `assets/foo.csv`); an " +
|
|
41
|
+
"absolute path, or one that escapes the version dir, returns " +
|
|
42
|
+
"`out_of_scope` and writes nothing.",
|
|
40
43
|
inputSchema: z.object({
|
|
41
44
|
path: z.string().min(1),
|
|
42
45
|
content: z.string(),
|
|
@@ -62,8 +65,9 @@ export function createVersionFsTools(state) {
|
|
|
62
65
|
id: "edit_file",
|
|
63
66
|
description: "Surgical edit of an existing UTF-8 file inside the report's version " +
|
|
64
67
|
"directory: replace the first occurrence of `oldText` with `newText`. " +
|
|
65
|
-
"
|
|
66
|
-
"`
|
|
68
|
+
"Paths are relative to that directory; an absolute or escaping path " +
|
|
69
|
+
"returns `out_of_scope`. Returns `not_found` if the file doesn't " +
|
|
70
|
+
"exist, `no_match` if `oldText` isn't present.",
|
|
67
71
|
inputSchema: z.object({
|
|
68
72
|
path: z.string().min(1),
|
|
69
73
|
oldText: z.string().min(1),
|
|
@@ -102,7 +106,9 @@ export function createVersionFsTools(state) {
|
|
|
102
106
|
});
|
|
103
107
|
const readFile = defineTool({
|
|
104
108
|
id: "read_file",
|
|
105
|
-
description: "Read a UTF-8 file inside the report's version directory.
|
|
109
|
+
description: "Read a UTF-8 file inside the report's version directory. Paths are " +
|
|
110
|
+
"relative to it; an absolute or escaping path returns `out_of_scope`. " +
|
|
111
|
+
"Returns truncated content (capped at 256 KiB) for large files.",
|
|
106
112
|
inputSchema: z.object({
|
|
107
113
|
path: z.string().min(1),
|
|
108
114
|
}),
|
|
@@ -137,7 +143,8 @@ export function createVersionFsTools(state) {
|
|
|
137
143
|
});
|
|
138
144
|
const mkdir = defineTool({
|
|
139
145
|
id: "mkdir",
|
|
140
|
-
description: "Create a directory (recursive) inside the report's version directory."
|
|
146
|
+
description: "Create a directory (recursive) inside the report's version directory. " +
|
|
147
|
+
"Paths are relative to it; an absolute or escaping path returns `out_of_scope`.",
|
|
141
148
|
inputSchema: z.object({
|
|
142
149
|
path: z.string().min(1),
|
|
143
150
|
}),
|
package/package.json
CHANGED