@inflexa-ai/harness 0.20.0 → 0.21.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/agents/conversation-agent.d.ts +26 -14
- package/dist/agents/conversation-agent.js +12 -23
- package/dist/agents/report-session-agent.d.ts +31 -5
- package/dist/agents/report-session-agent.js +31 -6
- package/dist/app/chat-turn.js +3 -1
- package/dist/app/message-assembly.d.ts +15 -1
- package/dist/app/message-assembly.js +22 -8
- package/dist/app/report-session-runtime.d.ts +21 -4
- package/dist/app/report-session-runtime.js +53 -6
- package/dist/app/spawn-report-session.d.ts +174 -17
- package/dist/app/spawn-report-session.js +233 -21
- package/dist/contracts/chat-events.d.ts +7 -2
- package/dist/contracts/message.d.ts +10 -0
- package/dist/contracts/report-blocks.d.ts +743 -19
- package/dist/contracts/report-blocks.js +186 -11
- package/dist/contracts/report-reference.d.ts +88 -0
- package/dist/contracts/report-reference.js +48 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +20 -0
- package/dist/lib/chrome.d.ts +26 -6
- package/dist/lib/chrome.js +99 -32
- package/dist/lib/eyes.d.ts +57 -0
- package/dist/lib/eyes.js +45 -0
- package/dist/lib/page-capture.d.ts +14 -0
- package/dist/lib/page-capture.js +56 -2
- package/dist/loop/run-agent.js +58 -23
- package/dist/loop/tool-detail.d.ts +19 -2
- package/dist/loop/tool-detail.js +35 -2
- package/dist/memory/content-to-cortex.d.ts +3 -2
- package/dist/memory/content-to-cortex.js +21 -12
- package/dist/memory/conversation-display-replay.js +14 -7
- package/dist/memory/thread-history.d.ts +60 -3
- package/dist/memory/thread-history.js +55 -16
- package/dist/memory/thread-store.d.ts +11 -1
- package/dist/memory/thread-store.js +12 -2
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +23 -6
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +183 -9
- package/dist/report-model/authoring-grammar.d.ts +463 -0
- package/dist/report-model/authoring-grammar.js +77 -0
- package/dist/report-model/block-walk.d.ts +57 -14
- package/dist/report-model/block-walk.js +116 -11
- package/dist/report-model/draft-finish.d.ts +22 -4
- package/dist/report-model/draft-finish.js +39 -7
- package/dist/report-model/draft-operations.d.ts +3 -0
- package/dist/report-model/draft-operations.js +19 -5
- package/dist/report-model/draft-read.d.ts +8 -5
- package/dist/report-model/draft-read.js +21 -5
- package/dist/report-model/fixture-resolver.js +15 -4
- package/dist/report-model/pin-snapshot.d.ts +24 -6
- package/dist/report-model/pin-snapshot.js +204 -6
- package/dist/report-model/production-resolver.js +15 -4
- package/dist/report-model/reference-resolver.d.ts +50 -1
- package/dist/report-model/reference-resolver.js +94 -1
- package/dist/report-model/reference-stamp.d.ts +45 -0
- package/dist/report-model/reference-stamp.js +173 -0
- package/dist/report-model/structural-validation.d.ts +4 -1
- package/dist/report-model/structural-validation.js +48 -3
- package/dist/report-model/validate.d.ts +1 -1
- package/dist/report-model/validate.js +1 -1
- package/dist/report-render/asset-lookup.d.ts +19 -0
- package/dist/report-render/asset-lookup.js +37 -0
- package/dist/report-render/assets.d.ts +45 -1
- package/dist/report-render/assets.js +65 -6
- package/dist/report-render/chart-presets.d.ts +102 -0
- package/dist/report-render/chart-presets.js +205 -0
- package/dist/report-render/chart.d.ts +112 -10
- package/dist/report-render/chart.js +1164 -41
- package/dist/report-render/design.d.ts +99 -0
- package/dist/report-render/design.js +289 -107
- package/dist/report-render/number-format.d.ts +117 -0
- package/dist/report-render/number-format.js +479 -0
- package/dist/report-render/page.d.ts +121 -5
- package/dist/report-render/page.js +689 -7
- package/dist/report-render/references.d.ts +70 -16
- package/dist/report-render/references.js +54 -22
- package/dist/report-render/render.d.ts +23 -5
- package/dist/report-render/render.js +81 -22
- package/dist/report-render/table-data.d.ts +92 -0
- package/dist/report-render/table-data.js +129 -0
- package/dist/report-render/types.d.ts +15 -0
- package/dist/report-render/value-bridge.js +6 -2
- package/dist/report-render/views/chart-view.d.ts +12 -5
- package/dist/report-render/views/chart-view.js +14 -8
- package/dist/report-render/views/page-view.d.ts +36 -15
- package/dist/report-render/views/page-view.js +45 -23
- package/dist/report-render/views/prose.d.ts +12 -2
- package/dist/report-render/views/prose.js +29 -7
- package/dist/report-render/views/references-view.d.ts +22 -8
- package/dist/report-render/views/references-view.js +101 -13
- package/dist/report-render/views/values.d.ts +128 -13
- package/dist/report-render/views/values.js +199 -18
- package/dist/runtime/assemble.d.ts +39 -5
- package/dist/runtime/assemble.js +47 -6
- package/dist/sandbox/client.d.ts +10 -1
- package/dist/sandbox/create-sandbox.d.ts +9 -4
- package/dist/sandbox/create-sandbox.js +22 -16
- package/dist/sandbox/docker-client.js +10 -7
- package/dist/sandbox/mount-plan.d.ts +45 -7
- package/dist/sandbox/mount-plan.js +43 -5
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/state/init.js +38 -9
- package/dist/state/report-session-state.d.ts +42 -1
- package/dist/state/report-session-state.js +70 -2
- package/dist/state/report-versions.d.ts +22 -17
- package/dist/state/report-versions.js +48 -35
- package/dist/state/snapshot-parse.js +16 -1
- package/dist/tools/define-tool.d.ts +32 -0
- package/dist/tools/define-tool.js +4 -0
- package/dist/tools/detail-resolver.d.ts +7 -2
- package/dist/tools/detail-resolver.js +7 -2
- package/dist/tools/iterate-report.d.ts +1 -1
- package/dist/tools/report/preview-snapshot.js +1 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +15 -6
- package/dist/tools/report-authoring/authoring-tools.js +99 -18
- package/dist/tools/report-session/derive-table.d.ts +162 -0
- package/dist/tools/report-session/derive-table.js +459 -0
- package/dist/tools/report-session/examine-page.d.ts +48 -18
- package/dist/tools/report-session/examine-page.js +204 -24
- package/dist/tools/report-session/index.d.ts +2 -0
- package/dist/tools/report-session/index.js +2 -0
- package/dist/tools/report-session/list-artifacts.d.ts +97 -0
- package/dist/tools/report-session/list-artifacts.js +197 -0
- package/dist/tools/report-session/preview-report.d.ts +6 -0
- package/dist/tools/report-session/preview-report.js +180 -28
- package/dist/tools/report-session/record-version.d.ts +22 -10
- package/dist/tools/report-session/record-version.js +99 -14
- package/dist/tools/start-report-session.d.ts +115 -0
- package/dist/tools/start-report-session.js +179 -0
- package/dist/workspace/paths.d.ts +50 -0
- package/dist/workspace/paths.js +63 -0
- package/package.json +2 -1
|
@@ -9,30 +9,45 @@
|
|
|
9
9
|
* page lives at `report-sessions/{threadId}/index.html` under the workspace root, which the preview writes.
|
|
10
10
|
* A missed page means that no preview ran, and it is a typed outcome, not a throw.
|
|
11
11
|
*
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
12
|
+
* The composition says where a browser comes from, and the eyes seam is that answer. One look acquires one
|
|
13
|
+
* lease, and the tool captures against the endpoint of that lease.
|
|
14
|
+
*
|
|
15
|
+
* The `file://` URL resolves on the filesystem of the browser, because the connection is out of process.
|
|
16
|
+
* Thus the browser of a lease must hold the workspace tree of the harness host at the same path. A browser
|
|
14
17
|
* with no such mount reports the page as an unreachable request, and the tool gives back that fault.
|
|
15
18
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* The tool releases the lease after the look. The release runs on a pass and on a failed capture alike. That
|
|
20
|
+
* release is hygiene, and it is not the guarantee against a leak. A process can die between the acquire and
|
|
21
|
+
* the release, thus the realization bounds the life of what it provisions. As a result a failed release
|
|
22
|
+
* changes no outcome, and the log is its whole record.
|
|
23
|
+
*
|
|
24
|
+
* A composition with no capture seam, no eyes seam, and no configured endpoint has no eyes at all. The tool
|
|
25
|
+
* reports that condition one time, up front, and it stamps nothing. A per-attempt capture failure would
|
|
26
|
+
* instead read as a transient fault, and it would invite a repeat of a call that can never pass.
|
|
19
27
|
*
|
|
20
28
|
* On a capture the tool copies the rendered hash onto the seen hash through the gateway. Thus the look
|
|
21
29
|
* counts, and the record tool lets the current draft record. The copy takes the rendered hash and never the
|
|
22
|
-
* current one, thus a later edit makes the look stale and the record refuses.
|
|
30
|
+
* current one, thus a later edit makes the look stale and the record refuses. A picture of the window alone
|
|
31
|
+
* is a true picture of the current document, thus it stamps the same and the coverage of the result is what
|
|
32
|
+
* tells the agent what it saw.
|
|
23
33
|
*
|
|
24
34
|
* The gateway reports whether a rendered hash existed to copy. When the row holds none, no preview stamped
|
|
25
35
|
* one and the look cannot count. The tool then gives a missed-stamp outcome that directs a new preview,
|
|
26
36
|
* because a repeated look never stamps a marker that no preview wrote.
|
|
27
37
|
*
|
|
28
|
-
* The chrome navigation and the workspace-root seam each speak the throw protocol. The tool
|
|
29
|
-
*
|
|
38
|
+
* The eyes seam, the chrome navigation, and the workspace-root seam each speak the throw protocol. The tool
|
|
39
|
+
* guards each of them, thus a fault of a look becomes a typed outcome and the loop never sees a throw.
|
|
40
|
+
*
|
|
41
|
+
* A hang is not a throw, and a guard alone never ends one. Thus the acquire and the release each carry a
|
|
42
|
+
* deadline. A realization that hangs while it starts a browser would otherwise hold the whole agent turn,
|
|
43
|
+
* and no outcome would ever arrive.
|
|
30
44
|
*/
|
|
31
45
|
import { z } from "zod";
|
|
32
46
|
import { type ChromeConfig } from "../../lib/chrome.js";
|
|
47
|
+
import { type AcquireEyes } from "../../lib/eyes.js";
|
|
33
48
|
import { type Logger } from "../../lib/logger.js";
|
|
34
49
|
import { type CapturePage, type FailedRequest, type PageCapture } from "../../lib/page-capture.js";
|
|
35
|
-
import type
|
|
50
|
+
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
36
51
|
import { type Tool } from "../define-tool.js";
|
|
37
52
|
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
38
53
|
export type { CapturePage, FailedRequest, PageCapture };
|
|
@@ -41,11 +56,13 @@ declare const examinePageInput: z.ZodObject<{}, z.core.$strip>;
|
|
|
41
56
|
export type ExaminePageInput = z.infer<typeof examinePageInput>;
|
|
42
57
|
/**
|
|
43
58
|
* The typed outcome of the eyes tool. Each arm is ok-channel data, thus the tool never throws for a
|
|
44
|
-
* degraded condition. `examined` carries the
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* the
|
|
59
|
+
* degraded condition. `examined` carries the coverage of the picture, the console errors, the failed
|
|
60
|
+
* requests, and the page path. The coverage names what the agent saw: the whole document, or the window
|
|
61
|
+
* alone when the browser refused a full-page bitmap. The screenshot does not ride the JSON. It rides the
|
|
62
|
+
* image path of the tool result, thus the model sees the picture and the JSON text holds no bytes.
|
|
63
|
+
* `missed-stamp` means that the row holds no rendered hash, thus no preview stamped one and the agent must
|
|
64
|
+
* run a new preview before the next look. `no-browser` means that the composition gives no browser, thus no
|
|
65
|
+
* look is possible at all and a repeat gives the same answer.
|
|
49
66
|
*/
|
|
50
67
|
export type ExaminePageResult = {
|
|
51
68
|
outcome: "refused";
|
|
@@ -62,6 +79,7 @@ export type ExaminePageResult = {
|
|
|
62
79
|
detail: string;
|
|
63
80
|
} | {
|
|
64
81
|
outcome: "examined";
|
|
82
|
+
coverage: PageCapture["coverage"];
|
|
65
83
|
consoleErrors: string[];
|
|
66
84
|
failedRequests: FailedRequest[];
|
|
67
85
|
pagePath: string;
|
|
@@ -70,19 +88,31 @@ export type ExaminePageResult = {
|
|
|
70
88
|
* The construction deps of the eyes tool.
|
|
71
89
|
*
|
|
72
90
|
* `resolveWorkspaceRoot` maps the analysis of the call onto its workspace root, thus one singleton tool
|
|
73
|
-
* serves every analysis. `
|
|
74
|
-
*
|
|
75
|
-
*
|
|
91
|
+
* serves every analysis. `eyes` gives a browser for one look, and the scope of the acquire carries that
|
|
92
|
+
* analysis and its root. `chrome` carries the connection settings of the capture, and the browser that it
|
|
93
|
+
* names must hold the workspace tree at the same path, because the tool navigates to a `file://` URL.
|
|
94
|
+
* `capture` is optional and it replaces the whole transport, thus a test injects a seam that reads no
|
|
95
|
+
* browser.
|
|
76
96
|
*/
|
|
77
97
|
export interface ExaminePageToolDeps {
|
|
78
98
|
readonly gateway: ReportSessionStateGateway;
|
|
79
99
|
readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
80
100
|
readonly chrome: ChromeConfig;
|
|
101
|
+
readonly eyes?: AcquireEyes;
|
|
81
102
|
readonly capture?: CapturePage;
|
|
82
103
|
readonly logger?: Logger;
|
|
104
|
+
/**
|
|
105
|
+
* The two budgets of one look, in milliseconds. A test seam: it shortens the budgets, thus a hung
|
|
106
|
+
* realization settles inside one test run. Absent, the tool reads {@link ACQUIRE_DEADLINE_MS} and
|
|
107
|
+
* {@link RELEASE_DEADLINE_MS}, which no composition tunes.
|
|
108
|
+
*/
|
|
109
|
+
readonly deadlines?: {
|
|
110
|
+
readonly acquireMs?: number;
|
|
111
|
+
readonly releaseMs?: number;
|
|
112
|
+
};
|
|
83
113
|
}
|
|
84
114
|
/**
|
|
85
|
-
* Make the eyes tool over the session-state gateway, the workspace-root seam, and the
|
|
115
|
+
* Make the eyes tool over the session-state gateway, the workspace-root seam, and the eyes of the composition.
|
|
86
116
|
*
|
|
87
117
|
* The tool reads the thread id from the scope of the call, and it resolves the page path under the workspace
|
|
88
118
|
* root. Thus one factory serves every thread. The tool holds no per-session value.
|
|
@@ -9,68 +9,238 @@
|
|
|
9
9
|
* page lives at `report-sessions/{threadId}/index.html` under the workspace root, which the preview writes.
|
|
10
10
|
* A missed page means that no preview ran, and it is a typed outcome, not a throw.
|
|
11
11
|
*
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
12
|
+
* The composition says where a browser comes from, and the eyes seam is that answer. One look acquires one
|
|
13
|
+
* lease, and the tool captures against the endpoint of that lease.
|
|
14
|
+
*
|
|
15
|
+
* The `file://` URL resolves on the filesystem of the browser, because the connection is out of process.
|
|
16
|
+
* Thus the browser of a lease must hold the workspace tree of the harness host at the same path. A browser
|
|
14
17
|
* with no such mount reports the page as an unreachable request, and the tool gives back that fault.
|
|
15
18
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* The tool releases the lease after the look. The release runs on a pass and on a failed capture alike. That
|
|
20
|
+
* release is hygiene, and it is not the guarantee against a leak. A process can die between the acquire and
|
|
21
|
+
* the release, thus the realization bounds the life of what it provisions. As a result a failed release
|
|
22
|
+
* changes no outcome, and the log is its whole record.
|
|
23
|
+
*
|
|
24
|
+
* A composition with no capture seam, no eyes seam, and no configured endpoint has no eyes at all. The tool
|
|
25
|
+
* reports that condition one time, up front, and it stamps nothing. A per-attempt capture failure would
|
|
26
|
+
* instead read as a transient fault, and it would invite a repeat of a call that can never pass.
|
|
19
27
|
*
|
|
20
28
|
* On a capture the tool copies the rendered hash onto the seen hash through the gateway. Thus the look
|
|
21
29
|
* counts, and the record tool lets the current draft record. The copy takes the rendered hash and never the
|
|
22
|
-
* current one, thus a later edit makes the look stale and the record refuses.
|
|
30
|
+
* current one, thus a later edit makes the look stale and the record refuses. A picture of the window alone
|
|
31
|
+
* is a true picture of the current document, thus it stamps the same and the coverage of the result is what
|
|
32
|
+
* tells the agent what it saw.
|
|
23
33
|
*
|
|
24
34
|
* The gateway reports whether a rendered hash existed to copy. When the row holds none, no preview stamped
|
|
25
35
|
* one and the look cannot count. The tool then gives a missed-stamp outcome that directs a new preview,
|
|
26
36
|
* because a repeated look never stamps a marker that no preview wrote.
|
|
27
37
|
*
|
|
28
|
-
* The chrome navigation and the workspace-root seam each speak the throw protocol. The tool
|
|
29
|
-
*
|
|
38
|
+
* The eyes seam, the chrome navigation, and the workspace-root seam each speak the throw protocol. The tool
|
|
39
|
+
* guards each of them, thus a fault of a look becomes a typed outcome and the loop never sees a throw.
|
|
40
|
+
*
|
|
41
|
+
* A hang is not a throw, and a guard alone never ends one. Thus the acquire and the release each carry a
|
|
42
|
+
* deadline. A realization that hangs while it starts a browser would otherwise hold the whole agent turn,
|
|
43
|
+
* and no outcome would ever arrive.
|
|
30
44
|
*/
|
|
31
|
-
import { ok } from "neverthrow";
|
|
45
|
+
import { err, ok } from "neverthrow";
|
|
32
46
|
import { access } from "node:fs/promises";
|
|
33
47
|
import { join } from "node:path";
|
|
34
48
|
import { pathToFileURL } from "node:url";
|
|
35
49
|
import { z } from "zod";
|
|
36
50
|
import { hasBrowserUrl } from "../../lib/chrome.js";
|
|
37
51
|
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
52
|
+
import { createStaticEyes } from "../../lib/eyes.js";
|
|
38
53
|
import { defaultErrorFields } from "../../lib/logger.js";
|
|
39
54
|
import { capturePage } from "../../lib/page-capture.js";
|
|
55
|
+
import { reportSessionDir } from "../../workspace/paths.js";
|
|
40
56
|
import { defineTool, withToolResultImage } from "../define-tool.js";
|
|
41
57
|
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
42
58
|
/** The empty input. The tool examines the current page of the thread, thus it needs no field. */
|
|
43
59
|
const examinePageInput = z.object({});
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the transport of the tool from its deps, one time.
|
|
62
|
+
*
|
|
63
|
+
* The precedence is `capture`, then `eyes`, then the chrome config as static eyes. An injected capture wins
|
|
64
|
+
* over the two others, because it replaces the whole transport and a test injects it.
|
|
65
|
+
*
|
|
66
|
+
* The chrome arm serves a direct construction alone, for example the tests of this file. The assembled
|
|
67
|
+
* runtime wraps the configured endpoint into the static realization at its composition root. Thus one wrap
|
|
68
|
+
* serves that runtime, and this arm never fires there.
|
|
69
|
+
*/
|
|
70
|
+
function resolveEyes(deps) {
|
|
71
|
+
if (deps.capture !== undefined) {
|
|
72
|
+
return { kind: "capture", capture: deps.capture };
|
|
73
|
+
}
|
|
74
|
+
if (deps.eyes !== undefined) {
|
|
75
|
+
return { kind: "lease", acquire: deps.eyes };
|
|
76
|
+
}
|
|
77
|
+
if (hasBrowserUrl(deps.chrome)) {
|
|
78
|
+
return { kind: "lease", acquire: createStaticEyes(deps.chrome) };
|
|
79
|
+
}
|
|
80
|
+
return { kind: "none" };
|
|
81
|
+
}
|
|
82
|
+
/** The log line of each stage. The outcome of the tool is one arm, thus the stage rides in the log alone. */
|
|
83
|
+
const LOOK_FAULT_MESSAGE = {
|
|
84
|
+
acquire: "the eyes gave no browser for the look",
|
|
85
|
+
capture: "the page capture failed",
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* The deadline of one acquire, in milliseconds.
|
|
89
|
+
*
|
|
90
|
+
* A realization can start a container and boot a browser inside it, and that cold start costs seconds. The
|
|
91
|
+
* budget covers a slow cold start with a wide margin. It also ends a realization that hangs: one look sits
|
|
92
|
+
* inside one agent turn, and a hung acquire holds that whole turn with no outcome at all.
|
|
93
|
+
*/
|
|
94
|
+
const ACQUIRE_DEADLINE_MS = 60_000;
|
|
95
|
+
/**
|
|
96
|
+
* The deadline of one release, in milliseconds.
|
|
97
|
+
*
|
|
98
|
+
* A release stops what one acquire started, and the look already holds its picture. Thus the budget is short.
|
|
99
|
+
* A release that runs past the budget costs nothing, because the realization bounds the life of what it
|
|
100
|
+
* provisions.
|
|
101
|
+
*/
|
|
102
|
+
const RELEASE_DEADLINE_MS = 10_000;
|
|
103
|
+
/** The win of the timer arm. A symbol cannot collide with a value that the operation gives. */
|
|
104
|
+
const EXPIRED = Symbol("expired");
|
|
105
|
+
/**
|
|
106
|
+
* Run one operation against a deadline, and give the value, the cause, or the expiry.
|
|
107
|
+
*
|
|
108
|
+
* The eyes seam takes no abort signal, thus a race is the whole bound. The loser of the race stays in
|
|
109
|
+
* flight, and the caller of an expired operation owns whatever arrives late.
|
|
110
|
+
*
|
|
111
|
+
* Each path clears the timer. `sleep` of `async-utils.ts` holds a timer that nothing can clear. Thus an
|
|
112
|
+
* operation that wins its race would still keep the process alive for the rest of the budget.
|
|
113
|
+
*/
|
|
114
|
+
async function withDeadline(work, ms) {
|
|
115
|
+
let timer;
|
|
116
|
+
// The settled arm never rejects. Thus a fault that arrives after the expiry stays contained here, and it
|
|
117
|
+
// never reaches the process as an unhandled rejection.
|
|
118
|
+
const settled = work.then((value) => ok(value), (cause) => err({ kind: "threw", cause }));
|
|
119
|
+
const expiry = new Promise((resolve) => {
|
|
120
|
+
timer = setTimeout(() => resolve(EXPIRED), ms);
|
|
121
|
+
});
|
|
122
|
+
const winner = await Promise.race([settled, expiry]);
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
return winner === EXPIRED ? err({ kind: "expired" }) : winner;
|
|
125
|
+
}
|
|
126
|
+
/** The cause of one such fault. An expiry names its budget, thus the detail of the outcome names it too. */
|
|
127
|
+
function faultCause(fault, expiry, ms) {
|
|
128
|
+
return fault.kind === "threw" ? fault.cause : new Error(`${expiry} within ${ms} ms`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Start one seam call, and give the promise of its outcome.
|
|
132
|
+
*
|
|
133
|
+
* A realization comes from the embedder, and one that is hand-written can throw before it gives its promise.
|
|
134
|
+
* The catch turns that throw into a rejection. Thus the deadline below reads one shape, and a look keeps the
|
|
135
|
+
* no-throw contract of the tool.
|
|
136
|
+
*/
|
|
137
|
+
function startSeamCall(call) {
|
|
138
|
+
try {
|
|
139
|
+
return call();
|
|
140
|
+
}
|
|
141
|
+
catch (cause) {
|
|
142
|
+
return Promise.reject(cause);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Release a lease that arrived after the deadline of its acquire.
|
|
147
|
+
*
|
|
148
|
+
* The acquire stays in flight past that deadline, thus a browser can still arrive with no owner. The
|
|
149
|
+
* realization bounds the life of that browser, and this release ends it sooner. The look already gave its
|
|
150
|
+
* outcome, thus a fault here reaches the log alone.
|
|
151
|
+
*/
|
|
152
|
+
function releaseLateLease(pending, logger) {
|
|
153
|
+
void pending
|
|
154
|
+
.then((lease) => lease.release())
|
|
155
|
+
.catch((cause) => {
|
|
156
|
+
logger.warn("the eyes lease did not release", logger.errorFields(cause));
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Run one look over the resolved transport, and give the picture or the typed fault.
|
|
161
|
+
*
|
|
162
|
+
* The lease arm acquires one browser, captures against the endpoint of that lease, and releases in a
|
|
163
|
+
* `finally`. Thus a failed capture releases too. The shared capture takes a chrome config, thus the endpoint
|
|
164
|
+
* of the lease replaces the configured one and each other field of the composition stays.
|
|
165
|
+
*
|
|
166
|
+
* A failed release changes no outcome of the look. The capture already ran, and the realization bounds the
|
|
167
|
+
* life of what it provisions. Thus the log is the whole record of a failed release.
|
|
168
|
+
*
|
|
169
|
+
* The two seam calls each run against a deadline. An expired acquire reads as an acquire fault, thus a hung
|
|
170
|
+
* realization gives the same outcome as a refused one. An expired release reads as a failed release, thus it
|
|
171
|
+
* reaches the log and it changes no outcome.
|
|
172
|
+
*/
|
|
173
|
+
async function runLook(args) {
|
|
174
|
+
const { transport } = args;
|
|
175
|
+
if (transport.kind === "capture") {
|
|
176
|
+
try {
|
|
177
|
+
return ok(await transport.capture(args.url));
|
|
178
|
+
}
|
|
179
|
+
catch (cause) {
|
|
180
|
+
return err({ stage: "capture", cause });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// The pending acquire stays apart from its deadline. An expiry leaves the acquire in flight, and a lease
|
|
184
|
+
// that arrives late still gets a release.
|
|
185
|
+
const pending = startSeamCall(() => transport.acquire(args.scope));
|
|
186
|
+
const acquired = await withDeadline(pending, args.acquireMs);
|
|
187
|
+
if (acquired.isErr()) {
|
|
188
|
+
if (acquired.error.kind === "expired")
|
|
189
|
+
releaseLateLease(pending, args.logger);
|
|
190
|
+
return err({ stage: "acquire", cause: faultCause(acquired.error, "the eyes gave no browser", args.acquireMs) });
|
|
191
|
+
}
|
|
192
|
+
const lease = acquired.value;
|
|
193
|
+
try {
|
|
194
|
+
return ok(await capturePage({ ...args.chrome, browserUrl: lease.browserUrl }, args.url));
|
|
195
|
+
}
|
|
196
|
+
catch (cause) {
|
|
197
|
+
return err({ stage: "capture", cause });
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
const released = await withDeadline(startSeamCall(() => lease.release()), args.releaseMs);
|
|
201
|
+
if (released.isErr()) {
|
|
202
|
+
const cause = faultCause(released.error, "the eyes lease did not release", args.releaseMs);
|
|
203
|
+
args.logger.warn("the eyes lease did not release", args.logger.errorFields(cause));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
44
207
|
/** The IANA media type of the screenshot. `page.screenshot` gives PNG bytes. */
|
|
45
208
|
const SCREENSHOT_MEDIA_TYPE = "image/png";
|
|
46
209
|
/** The line that the agent reads when the composition gives no browser. */
|
|
47
210
|
const NO_BROWSER_DETAIL = "the composition gives no browser, thus this session cannot look at its page";
|
|
48
211
|
/**
|
|
49
|
-
* Make the eyes tool over the session-state gateway, the workspace-root seam, and the
|
|
212
|
+
* Make the eyes tool over the session-state gateway, the workspace-root seam, and the eyes of the composition.
|
|
50
213
|
*
|
|
51
214
|
* The tool reads the thread id from the scope of the call, and it resolves the page path under the workspace
|
|
52
215
|
* root. Thus one factory serves every thread. The tool holds no per-session value.
|
|
53
216
|
*/
|
|
54
217
|
export function createExaminePageTool(deps) {
|
|
55
218
|
const logger = (deps.logger ?? createNoopLogger()).named("examine-page");
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
const
|
|
219
|
+
// The eyes of the composition are fixed here, thus one look reads one resolved transport and never the
|
|
220
|
+
// precedence again.
|
|
221
|
+
const transport = resolveEyes(deps);
|
|
222
|
+
const acquireMs = deps.deadlines?.acquireMs ?? ACQUIRE_DEADLINE_MS;
|
|
223
|
+
const releaseMs = deps.deadlines?.releaseMs ?? RELEASE_DEADLINE_MS;
|
|
60
224
|
return defineTool({
|
|
61
225
|
id: "examine_page",
|
|
62
226
|
description: "Open the rendered report page in a real headless browser, and report what you see. " +
|
|
63
|
-
"Give back a screenshot, the console errors, and the failed requests. " +
|
|
227
|
+
"Give back a screenshot, the coverage of that screenshot, the console errors, and the failed requests. " +
|
|
228
|
+
"The coverage says whether the picture holds the whole page, or the top window alone. " +
|
|
64
229
|
"Run it after the preview to look at the current page, and to confirm that the layout and the charts read clean. " +
|
|
65
230
|
"The report tool records a version only after you look at the current page. " +
|
|
66
231
|
"If the page has no confirmed render, run the preview again first.",
|
|
67
232
|
inputSchema: examinePageInput,
|
|
68
233
|
executionMode: "inline",
|
|
69
234
|
describeCall: "none",
|
|
235
|
+
// A look gives a picture, and a picture has no one-line form. Thus the outcome of the look is the
|
|
236
|
+
// whole line: a watcher reads that the eyes saw the page, or that the page was absent, or that the
|
|
237
|
+
// capture failed.
|
|
238
|
+
describeResult: (_input, result) => result.outcome,
|
|
70
239
|
execute: async (_input, ctx) => {
|
|
71
240
|
// The check runs before every read, thus one clear signal replaces a capture failure for each
|
|
72
|
-
// page. The arm stamps no seen hash, because no eyes saw the page.
|
|
73
|
-
|
|
241
|
+
// page. The arm stamps no seen hash, because no eyes saw the page. It also narrows the transport
|
|
242
|
+
// for the look below, thus the acquire needs no assertion.
|
|
243
|
+
if (transport.kind === "none") {
|
|
74
244
|
logger.warn("the composition gives no browser, thus no look can run");
|
|
75
245
|
return ok({ outcome: "no-browser", detail: NO_BROWSER_DETAIL });
|
|
76
246
|
}
|
|
@@ -87,7 +257,7 @@ export function createExaminePageTool(deps) {
|
|
|
87
257
|
logger.warn("the workspace root did not resolve", { threadId, analysisId, ...defaultErrorFields(cause) });
|
|
88
258
|
return ok({ outcome: "capture-failed", detail: "the workspace root did not resolve" });
|
|
89
259
|
}
|
|
90
|
-
const relativePagePath = join(
|
|
260
|
+
const relativePagePath = join(reportSessionDir(threadId), "index.html");
|
|
91
261
|
const pagePath = join(root, relativePagePath);
|
|
92
262
|
try {
|
|
93
263
|
await access(pagePath);
|
|
@@ -100,14 +270,23 @@ export function createExaminePageTool(deps) {
|
|
|
100
270
|
logger.warn("the session page could not be read", { threadId, analysisId, ...defaultErrorFields(cause) });
|
|
101
271
|
return ok({ outcome: "capture-failed", detail: "the session page could not be read" });
|
|
102
272
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
273
|
+
// The scope of the acquire carries the analysis and the root that this call resolved. Thus a
|
|
274
|
+
// realization that starts a browser mounts the same tree, and it holds no second resolver.
|
|
275
|
+
const looked = await runLook({
|
|
276
|
+
transport,
|
|
277
|
+
chrome: deps.chrome,
|
|
278
|
+
scope: { analysisId, workspaceRoot: root },
|
|
279
|
+
url: pathToFileURL(pagePath).href,
|
|
280
|
+
acquireMs,
|
|
281
|
+
releaseMs,
|
|
282
|
+
logger,
|
|
283
|
+
});
|
|
284
|
+
if (looked.isErr()) {
|
|
285
|
+
const { stage, cause } = looked.error;
|
|
286
|
+
logger.warn(LOOK_FAULT_MESSAGE[stage], { threadId, analysisId, ...defaultErrorFields(cause) });
|
|
109
287
|
return ok({ outcome: "capture-failed", detail: cause instanceof Error ? cause.message : String(cause) });
|
|
110
288
|
}
|
|
289
|
+
const captured = looked.value;
|
|
111
290
|
const stamped = await deps.gateway.stampSeen(threadId);
|
|
112
291
|
if (stamped.outcome === "no-rendered") {
|
|
113
292
|
// The row holds no rendered hash, thus no preview stamped one and the look cannot count. A
|
|
@@ -127,6 +306,7 @@ export function createExaminePageTool(deps) {
|
|
|
127
306
|
// JSON keeps the faults and the page path only, thus the JSON text holds no bytes.
|
|
128
307
|
return ok(withToolResultImage({
|
|
129
308
|
outcome: "examined",
|
|
309
|
+
coverage: captured.coverage,
|
|
130
310
|
consoleErrors: captured.consoleErrors,
|
|
131
311
|
failedRequests: captured.failedRequests,
|
|
132
312
|
pagePath: relativePagePath,
|
|
@@ -8,3 +8,5 @@
|
|
|
8
8
|
export { createPreviewReportTool, type PreviewReportInput, type PreviewReportResult, type PreviewReportToolDeps, type ResolvePageAsset, } from "./preview-report.js";
|
|
9
9
|
export { createExaminePageTool, type CapturePage, type ExaminePageInput, type ExaminePageResult, type ExaminePageToolDeps, type FailedRequest, type PageCapture, } from "./examine-page.js";
|
|
10
10
|
export { createRecordVersionTool, type RecordVersionInput, type RecordVersionResult, type RecordVersionToolDeps } from "./record-version.js";
|
|
11
|
+
export { createDeriveTableTool, type DeriveTableInput, type DeriveTableResult, type DeriveTableToolDeps } from "./derive-table.js";
|
|
12
|
+
export { createListPinnedArtifactsTool, type ListPinnedArtifactsInput, type ListPinnedArtifactsResult, type ListPinnedArtifactsToolDeps, type PinnedArtifact, type PinnedCitation, } from "./list-artifacts.js";
|
|
@@ -8,3 +8,5 @@
|
|
|
8
8
|
export { createPreviewReportTool, } from "./preview-report.js";
|
|
9
9
|
export { createExaminePageTool, } from "./examine-page.js";
|
|
10
10
|
export { createRecordVersionTool } from "./record-version.js";
|
|
11
|
+
export { createDeriveTableTool } from "./derive-table.js";
|
|
12
|
+
export { createListPinnedArtifactsTool, } from "./list-artifacts.js";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pinned-artifact listing tool of a report session.
|
|
3
|
+
*
|
|
4
|
+
* The tool reads the frozen snapshot of the thread, and it gives the pinned set: the path, the content
|
|
5
|
+
* hash, the file type, and the header columns of a tabular artifact, plus the pinned citations with the
|
|
6
|
+
* short citation of each. Thus one call orients the agent, and a reference binds to a path or to a
|
|
7
|
+
* citation key of that set.
|
|
8
|
+
*
|
|
9
|
+
* The order is the code-unit order of the path. Two calls over one snapshot give one listing, thus the
|
|
10
|
+
* agent reads a stable set. A snapshot can pin many thousands of staged inputs, thus the listing stops at
|
|
11
|
+
* a cap and it carries the total count and a truncation marker.
|
|
12
|
+
*
|
|
13
|
+
* The columns come from a bounded header read of a listed entry. The path of a snapshot entry is
|
|
14
|
+
* untrusted, thus the read contains it with the one workspace-path resolver first. The extension gates
|
|
15
|
+
* the read, because a file type states a role and not a data format.
|
|
16
|
+
*
|
|
17
|
+
* An absent file, an unreadable file, and an unresolvable workspace root each give no columns and no
|
|
18
|
+
* error. Absence is a normal condition here: the listing is orientation, and the hash is the evidence.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
22
|
+
import { type Logger } from "../../lib/logger.js";
|
|
23
|
+
import { type Tool } from "../define-tool.js";
|
|
24
|
+
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
25
|
+
/** The empty input. The tool lists the pinned set of the thread, thus it needs no field. */
|
|
26
|
+
declare const listPinnedArtifactsInput: z.ZodObject<{}, z.core.$strip>;
|
|
27
|
+
export type ListPinnedArtifactsInput = z.infer<typeof listPinnedArtifactsInput>;
|
|
28
|
+
/**
|
|
29
|
+
* One pinned artifact of the listing.
|
|
30
|
+
*
|
|
31
|
+
* `fileType` states a role, and it does not state a data format. `columns` is present only for a header
|
|
32
|
+
* that the read recovered, thus an absent `columns` says nothing about the content of the file.
|
|
33
|
+
*/
|
|
34
|
+
export interface PinnedArtifact {
|
|
35
|
+
path: string;
|
|
36
|
+
hash: string;
|
|
37
|
+
fileType?: string;
|
|
38
|
+
columns?: string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One pinned citation of the listing: the key, and the short citation of its pinned record.
|
|
42
|
+
*
|
|
43
|
+
* `citation` names which paper the key is, thus the agent binds a citation block with no guess. A key that
|
|
44
|
+
* the pin recorded no citation for carries none, because absence is a normal condition. The description of
|
|
45
|
+
* the record stays out, because the listing is an orientation surface.
|
|
46
|
+
*/
|
|
47
|
+
export interface PinnedCitation {
|
|
48
|
+
key: string;
|
|
49
|
+
citation?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The typed outcome of the listing tool. Each arm is ok-channel data, thus the tool never throws for one
|
|
53
|
+
* of them.
|
|
54
|
+
*
|
|
55
|
+
* `total` counts the whole pinned set, and `truncated` says that the set holds more entries than
|
|
56
|
+
* `artifacts` names. Thus the agent reads a partial listing as a partial listing.
|
|
57
|
+
*
|
|
58
|
+
* `citations` gives the pinned citations in the code-unit order of the keys that the pin stored. A citation
|
|
59
|
+
* reference binds to one of those keys, thus the agent reads a key here and never out of a refusal.
|
|
60
|
+
*/
|
|
61
|
+
export type ListPinnedArtifactsResult = {
|
|
62
|
+
outcome: "refused";
|
|
63
|
+
refusal: SessionRefusal;
|
|
64
|
+
} | {
|
|
65
|
+
outcome: "listed";
|
|
66
|
+
artifacts: PinnedArtifact[];
|
|
67
|
+
total: number;
|
|
68
|
+
truncated: boolean;
|
|
69
|
+
citations: PinnedCitation[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The construction deps of the listing tool.
|
|
73
|
+
*
|
|
74
|
+
* `resolveWorkspaceRoot` maps the analysis of the call onto its workspace root, thus one singleton tool
|
|
75
|
+
* serves every analysis and it resolves the root per call from the scope.
|
|
76
|
+
*/
|
|
77
|
+
export interface ListPinnedArtifactsToolDeps {
|
|
78
|
+
readonly gateway: ReportSessionStateGateway;
|
|
79
|
+
readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
80
|
+
readonly logger?: Logger;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The header columns of a file on disk, or `undefined` when the file gives none.
|
|
84
|
+
*
|
|
85
|
+
* `path` decides whether the file carries a header at all, and `absolute` is where the bytes sit. The
|
|
86
|
+
* derivation tool reads the columns of its own output the same way, thus the two surfaces describe a table
|
|
87
|
+
* with one rule.
|
|
88
|
+
*/
|
|
89
|
+
export declare function readHeaderColumns(path: string, absolute: string): Promise<string[] | undefined>;
|
|
90
|
+
/**
|
|
91
|
+
* Make the pinned-artifact listing tool over the session-state gateway.
|
|
92
|
+
*
|
|
93
|
+
* The tool reads the thread id from the scope of the call, and it loads the snapshot through the gateway.
|
|
94
|
+
* Thus one factory serves every thread, and the tool holds no per-session value.
|
|
95
|
+
*/
|
|
96
|
+
export declare function createListPinnedArtifactsTool(deps: ListPinnedArtifactsToolDeps): Tool<ListPinnedArtifactsInput, ListPinnedArtifactsResult>;
|
|
97
|
+
export {};
|