@inflexa-ai/harness 0.41.2 → 0.42.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.js +3 -0
- package/dist/agents/report-session-agent.d.ts +2 -2
- package/dist/agents/report-session-agent.js +4 -0
- package/dist/app/chat-turn.d.ts +0 -4
- package/dist/app/chat-turn.js +39 -25
- package/dist/app/message-assembly.d.ts +2 -2
- package/dist/app/message-assembly.js +1 -1
- package/dist/contracts/chat-events.d.ts +12 -5
- package/dist/contracts/chat-events.js +3 -3
- package/dist/contracts/chat-frame.d.ts +40 -0
- package/dist/contracts/chat-frame.js +166 -0
- package/dist/contracts/chat-parts.d.ts +15 -3
- package/dist/contracts/chat-parts.js +2 -2
- package/dist/contracts/index.d.ts +6 -4
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/message.d.ts +7 -5
- package/dist/contracts/message.js +1 -1
- package/dist/contracts/part-registry.d.ts +6 -6
- package/dist/contracts/schemas/chat-events.d.ts +2 -2
- package/dist/contracts/schemas/chat-events.js +2 -2
- package/dist/contracts/schemas/chat-parts.d.ts +34 -2
- package/dist/contracts/schemas/chat-parts.js +9 -3
- package/dist/contracts/schemas/index.d.ts +2 -2
- package/dist/contracts/schemas/index.js +2 -2
- package/dist/execution/run-event-parts.d.ts +4 -4
- package/dist/execution/run-event-parts.js +0 -0
- package/dist/execution/run-event-stream.d.ts +2 -2
- package/dist/index.d.ts +9 -6
- package/dist/index.js +3 -2
- package/dist/lib/page-capture.d.ts +27 -9
- package/dist/lib/page-capture.js +81 -33
- package/dist/loop/compaction.d.ts +8 -3
- package/dist/loop/run-agent.d.ts +11 -0
- package/dist/loop/run-agent.js +62 -35
- package/dist/loop/types.d.ts +3 -0
- package/dist/memory/ai-sdk-message-storage.d.ts +18 -3
- package/dist/memory/ai-sdk-message-storage.js +23 -1
- package/dist/memory/conversation-display-recorder.d.ts +7 -1
- package/dist/memory/conversation-display-recorder.js +59 -119
- package/dist/memory/conversation-display-replay.d.ts +3 -3
- package/dist/memory/conversation-display-replay.js +7 -21
- package/dist/memory/conversation-display-storage.d.ts +10 -10
- package/dist/memory/conversation-display-storage.js +9 -8
- package/dist/memory/conversation-view.d.ts +5 -4
- package/dist/memory/conversation-view.js +14 -18
- package/dist/memory/count-tokens.js +1 -0
- package/dist/memory/thread-store.d.ts +9 -2
- package/dist/memory/thread-store.js +8 -2
- package/dist/prompts/compaction.d.ts +2 -2
- package/dist/prompts/compaction.js +2 -2
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +40 -28
- package/dist/report-render/views/block-mark.d.ts +8 -0
- package/dist/report-render/views/block-mark.js +10 -0
- package/dist/report-render/views/chart-view.js +2 -1
- package/dist/report-render/views/prose.js +10 -6
- package/dist/report-render/views/values.js +5 -4
- package/dist/state/migrations/20260927180000_thread_title_set_by_user.d.ts +6 -0
- package/dist/state/migrations/20260927180000_thread_title_set_by_user.js +10 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +16 -5
- package/dist/tools/report-authoring/authoring-tools.js +52 -24
- package/dist/tools/report-session/examine-page.d.ts +29 -14
- package/dist/tools/report-session/examine-page.js +40 -16
- package/dist/tools/report-session/list-artifacts.d.ts +23 -10
- package/dist/tools/report-session/list-artifacts.js +51 -31
- package/dist/tools/report-session/page-faults.d.ts +29 -0
- package/dist/tools/report-session/page-faults.js +48 -0
- package/dist/tools/report-session/preview-report.d.ts +9 -2
- package/dist/tools/report-session/preview-report.js +13 -3
- package/dist/tools/report-session/record-version.d.ts +5 -1
- package/dist/tools/report-session/record-version.js +4 -3
- package/package.json +1 -1
|
@@ -27,6 +27,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-
|
|
|
27
27
|
import { raw } from "hono/html";
|
|
28
28
|
import { declaredForColumn } from "../../contracts/report-reference.js";
|
|
29
29
|
import { stagedSource, tableSidecarName } from "../assets.js";
|
|
30
|
+
import { blockMark } from "./block-mark.js";
|
|
30
31
|
import { DEFAULT_VIEW_OPTIONS, lineagePlace, lineageStamp } from "./lineage.js";
|
|
31
32
|
import { Marker } from "./references-view.js";
|
|
32
33
|
import { formatNumberCell, formatTableCell, holdsANumber, selectColumnKind, selectNumberKind, smallestPositiveValue } from "../number-format.js";
|
|
@@ -75,7 +76,7 @@ const DOWNLOAD_LABEL = "Download";
|
|
|
75
76
|
export function renderMetric(block, ledger, value, view = DEFAULT_VIEW_OPTIONS) {
|
|
76
77
|
const n = ledger.mark(block.value);
|
|
77
78
|
const shown = formatNumberCell(value.value, selectNumberKind(block.label, value.value));
|
|
78
|
-
return String(_jsxs("div", { class: "stat-card corner-accents", ...lineageStamp(view.lineage, block.id, [block.value]), children: [_jsx("div", { class: "stat-card-value", title: shown.full, children: shown.text }), _jsxs("div", { class: "stat-card-label", children: [block.label, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.value) })] })] }));
|
|
79
|
+
return String(_jsxs("div", { class: "stat-card corner-accents", ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, [block.value]), children: [_jsx("div", { class: "stat-card-value", title: shown.full, children: shown.text }), _jsxs("div", { class: "stat-card-label", children: [block.label, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.value) })] })] }));
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
81
82
|
* Wrap a run of stat cards in one responsive grid. The caller renders each card first, and it passes the
|
|
@@ -212,7 +213,7 @@ export function renderTable(block, ledger, rowCount, total, view = DEFAULT_VIEW_
|
|
|
212
213
|
const n = ledger.mark(binding);
|
|
213
214
|
const download = tableSidecarName(binding.hash, binding.path);
|
|
214
215
|
const bound = boundText(binding);
|
|
215
|
-
return String(_jsxs("div", { class: "report-table", ...lineageStamp(view.lineage, block.id, [binding]), children: [_jsxs("div", { class: "report-table-title", children: [block.title, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, binding) })] }), _jsxs("div", { class: "corner-accents", children: [_jsx("div", { class: "report-grid", [GRID_MOUNT_ATTRIBUTE]: block.id }), _jsxs("div", { class: "report-table-footer", children: [_jsxs("div", { class: "report-table-footer-row", children: [_jsxs("span", { class: "report-table-status", children: [_jsx("span", { class: GRID_COUNT_CLASS, children: tableStatusText(rowCount, total ?? rowCount) }), bound !== undefined ? _jsx("span", { class: "report-table-bound", children: bound }) : null] }), _jsx("a", { class: "report-table-download", href: stagedSource(download), download: download, children: downloadLabel(binding.path) })] }), _jsx("div", { class: GRID_NOTE_CLASS })] })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
|
|
216
|
+
return String(_jsxs("div", { class: "report-table", ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, [binding]), children: [_jsxs("div", { class: "report-table-title", children: [block.title, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, binding) })] }), _jsxs("div", { class: "corner-accents", children: [_jsx("div", { class: "report-grid", [GRID_MOUNT_ATTRIBUTE]: block.id }), _jsxs("div", { class: "report-table-footer", children: [_jsxs("div", { class: "report-table-footer-row", children: [_jsxs("span", { class: "report-table-status", children: [_jsx("span", { class: GRID_COUNT_CLASS, children: tableStatusText(rowCount, total ?? rowCount) }), bound !== undefined ? _jsx("span", { class: "report-table-bound", children: bound }) : null] }), _jsx("a", { class: "report-table-download", href: stagedSource(download), download: download, children: downloadLabel(binding.path) })] }), _jsx("div", { class: GRID_NOTE_CLASS })] })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
|
|
216
217
|
}
|
|
217
218
|
/**
|
|
218
219
|
* Render a figure block as a corner-accent card. The source rides the `src` attribute, and the caption
|
|
@@ -229,7 +230,7 @@ export function renderTable(block, ledger, rowCount, total, view = DEFAULT_VIEW_
|
|
|
229
230
|
export function renderFigure(block, ledger, value, view = DEFAULT_VIEW_OPTIONS) {
|
|
230
231
|
const n = ledger.mark(block.binding);
|
|
231
232
|
const caption = block.caption;
|
|
232
|
-
return String(_jsxs("figure", { class: "report-figure corner-accents", ...lineageStamp(view.lineage, block.id, [block.binding]), children: [_jsx("img", { src: value.src, alt: caption !== undefined ? caption : "", class: "report-figure-image" }), _jsxs("figcaption", { class: "report-caption", children: [caption, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.binding) })] })] }));
|
|
233
|
+
return String(_jsxs("figure", { class: "report-figure corner-accents", ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, [block.binding]), children: [_jsx("img", { src: value.src, alt: caption !== undefined ? caption : "", class: "report-figure-image" }), _jsxs("figcaption", { class: "report-caption", children: [caption, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.binding) })] })] }));
|
|
233
234
|
}
|
|
234
235
|
/**
|
|
235
236
|
* The PubMed address of one identifier. The address is deterministic, and an anchor loads nothing, thus a
|
|
@@ -252,5 +253,5 @@ export function renderCitation(block, ledger, record, view = DEFAULT_VIEW_OPTION
|
|
|
252
253
|
const n = ledger.mark(block.binding);
|
|
253
254
|
const binding = block.binding;
|
|
254
255
|
const key = citationKeyOf(binding);
|
|
255
|
-
return String(_jsxs("div", { class: "report-citation corner-accents", ...lineageStamp(view.lineage, block.id, [binding]), children: [_jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, binding) }), record !== undefined ? (_jsxs(_Fragment, { children: [" ", binding.idKind === "pmid" ? (_jsx("a", { class: "report-citation-source", href: `${PUBMED_BASE}${encodeURIComponent(binding.id)}/`, children: record.citation })) : (_jsx("span", { class: "report-citation-source", children: record.citation }))] })) : null, block.note !== undefined ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { class: "report-citation-note", children: block.note })] })) : null, " ", _jsx("span", { class: "report-citation-key", children: key })] }));
|
|
256
|
+
return String(_jsxs("div", { class: "report-citation corner-accents", ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, [binding]), children: [_jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, binding) }), record !== undefined ? (_jsxs(_Fragment, { children: [" ", binding.idKind === "pmid" ? (_jsx("a", { class: "report-citation-source", href: `${PUBMED_BASE}${encodeURIComponent(binding.id)}/`, children: record.citation })) : (_jsx("span", { class: "report-citation-source", children: record.citation }))] })) : null, block.note !== undefined ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { class: "report-citation-note", children: block.note })] })) : null, " ", _jsx("span", { class: "report-citation-key", children: key })] }));
|
|
256
257
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record whether a person set the title of a thread, thus an automatic title
|
|
3
|
+
* never replaces a name that a person chose.
|
|
4
|
+
*/
|
|
5
|
+
import { sql } from "kysely";
|
|
6
|
+
export const threadTitleSetByUser = {
|
|
7
|
+
async up(db) {
|
|
8
|
+
await sql `ALTER TABLE cortex_analysis_threads ADD COLUMN IF NOT EXISTS title_set_by_user BOOLEAN NOT NULL DEFAULT false`.execute(db);
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* entry, with a name that sorts after every existing one.
|
|
7
7
|
*/
|
|
8
8
|
import { baseline } from "./20260927120000_baseline.js";
|
|
9
|
+
import { threadTitleSetByUser } from "./20260927180000_thread_title_set_by_user.js";
|
|
9
10
|
export function cortexMigrations(logger) {
|
|
10
11
|
return {
|
|
11
12
|
"20260927120000_baseline": baseline(logger),
|
|
13
|
+
"20260927180000_thread_title_set_by_user": threadTitleSetByUser,
|
|
12
14
|
};
|
|
13
15
|
}
|
|
@@ -23,7 +23,8 @@ import type { Logger } from "../../lib/logger.js";
|
|
|
23
23
|
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
24
24
|
import { type DraftRefusal } from "../../report-model/draft-operations.js";
|
|
25
25
|
import { type OutlineEntry, type ReadableBlock } from "../../report-model/draft-read.js";
|
|
26
|
-
import {
|
|
26
|
+
import type { ReportWarning } from "../../report-model/block-walk.js";
|
|
27
|
+
import { type FinishGap } from "../../report-model/draft-finish.js";
|
|
27
28
|
import type { DraftDocument } from "../../report-model/draft.js";
|
|
28
29
|
import type { ReportSnapshot } from "../../report-model/reference-resolver.js";
|
|
29
30
|
import type { DerivationRecord } from "../../state/report-session-state.js";
|
|
@@ -144,8 +145,18 @@ export type ReadBlockResult = {
|
|
|
144
145
|
} | {
|
|
145
146
|
refused: SessionRefusal;
|
|
146
147
|
};
|
|
147
|
-
/**
|
|
148
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The result of `finish_draft`. A pass gives no document, because the document grows with the report and the
|
|
150
|
+
* preview and the record read it themselves.
|
|
151
|
+
*/
|
|
152
|
+
export type FinishToolResult = {
|
|
153
|
+
valid: true;
|
|
154
|
+
warnings: ReportWarning[];
|
|
155
|
+
} | {
|
|
156
|
+
valid: false;
|
|
157
|
+
gaps: FinishGap[];
|
|
158
|
+
warnings: ReportWarning[];
|
|
159
|
+
} | {
|
|
149
160
|
refused: SessionRefusal;
|
|
150
161
|
};
|
|
151
162
|
/** The state of one report thread: the document under composition, and the frozen snapshot. */
|
|
@@ -308,8 +319,8 @@ export interface ReportAuthoringToolDeps {
|
|
|
308
319
|
* the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
|
|
309
320
|
* persists the new document before it reports `applied: true`, thus a reported landing is never lost.
|
|
310
321
|
*
|
|
311
|
-
* A landed mutation emits one observation
|
|
312
|
-
* failed persist each emit nothing.
|
|
322
|
+
* A landed mutation emits one observation, and an add emits one for each block that it landed. The emit
|
|
323
|
+
* sits after the persist, thus a refused operation and a failed persist each emit nothing.
|
|
313
324
|
*/
|
|
314
325
|
export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway, deps?: ReportAuthoringToolDeps): ReportAuthoringTools;
|
|
315
326
|
export {};
|
|
@@ -309,11 +309,11 @@ function holderIdOf(document, blockId) {
|
|
|
309
309
|
return locate(document, blockId)?.parent?.id;
|
|
310
310
|
}
|
|
311
311
|
/**
|
|
312
|
-
* The event of one block act, or
|
|
312
|
+
* The event of one block act, or no event when the document holds no block of that id.
|
|
313
313
|
*
|
|
314
314
|
* The kind rides beside the id, thus a reader of the record sees what the act touched and it needs no
|
|
315
315
|
* document of its own. The kind comes out of the document that holds the block after the act: the next
|
|
316
|
-
* document for
|
|
316
|
+
* document for a change and a move, and the previous one for a remove.
|
|
317
317
|
*
|
|
318
318
|
* A lookup of a landed operation always finds its block. The absent arm keeps the read total, and it drops
|
|
319
319
|
* the event rather than state a kind that no block carries.
|
|
@@ -321,9 +321,30 @@ function holderIdOf(document, blockId) {
|
|
|
321
321
|
function blockActOf(type, scope, document, blockId) {
|
|
322
322
|
const kind = locate(document, blockId)?.block.kind;
|
|
323
323
|
if (kind === undefined) {
|
|
324
|
-
return
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
return [{ type, analysisId: scope.analysisId, threadId: scope.threadId, blockId, blockKind: kind }];
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* The event of each block that one add landed, in document order. A section lands with its children in one
|
|
330
|
+
* call, and a reader of the record places each block by its own event.
|
|
331
|
+
*/
|
|
332
|
+
function addedActsOf(scope, document, blockId) {
|
|
333
|
+
const located = locate(document, blockId);
|
|
334
|
+
if (located === undefined) {
|
|
335
|
+
return [];
|
|
325
336
|
}
|
|
326
|
-
|
|
337
|
+
const events = [];
|
|
338
|
+
const visit = (block) => {
|
|
339
|
+
events.push({ type: "add-block", analysisId: scope.analysisId, threadId: scope.threadId, blockId: block.id, blockKind: block.kind });
|
|
340
|
+
if (block.kind === "section") {
|
|
341
|
+
for (const child of block.blocks) {
|
|
342
|
+
visit(child);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
visit(located.block);
|
|
347
|
+
return events;
|
|
327
348
|
}
|
|
328
349
|
/** Read the child order of one container out of a document. */
|
|
329
350
|
function containerIn(document, parentId) {
|
|
@@ -353,8 +374,8 @@ const AUTHORING_EXECUTION_MODE = "inline";
|
|
|
353
374
|
* the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
|
|
354
375
|
* persists the new document before it reports `applied: true`, thus a reported landing is never lost.
|
|
355
376
|
*
|
|
356
|
-
* A landed mutation emits one observation
|
|
357
|
-
* failed persist each emit nothing.
|
|
377
|
+
* A landed mutation emits one observation, and an add emits one for each block that it landed. The emit
|
|
378
|
+
* sits after the persist, thus a refused operation and a failed persist each emit nothing.
|
|
358
379
|
*/
|
|
359
380
|
export function createReportAuthoringTools(gateway, deps = {}) {
|
|
360
381
|
const observe = bindSessionEmit(deps.provenance, (deps.logger ?? createNoopLogger()).named("report-authoring"));
|
|
@@ -377,12 +398,13 @@ export function createReportAuthoringTools(gateway, deps = {}) {
|
|
|
377
398
|
* The persist is a compare-and-swap against the token that the load read. A concurrent turn that landed
|
|
378
399
|
* first turns the persist into a conflict, and the tool refuses with `stale-state`.
|
|
379
400
|
*
|
|
380
|
-
* `
|
|
381
|
-
* the rule, and a refusal, a conflict, and a persist fault each emit nothing. It reads the two
|
|
382
|
-
* for the same reason that `holders` does: a removed block sits in the first alone, and an
|
|
383
|
-
* sits in the second alone. A caller that cannot name the block of its own operation gives
|
|
401
|
+
* `events` gives the observations of the operation, and they emit after the persist landed. Thus one
|
|
402
|
+
* site owns the rule, and a refusal, a conflict, and a persist fault each emit nothing. It reads the two
|
|
403
|
+
* documents for the same reason that `holders` does: a removed block sits in the first alone, and an
|
|
404
|
+
* added block sits in the second alone. A caller that cannot name the block of its own operation gives
|
|
405
|
+
* no event.
|
|
384
406
|
*/
|
|
385
|
-
const land = async (threadId, token, previous, result, holders,
|
|
407
|
+
const land = async (threadId, token, previous, result, holders, events) => {
|
|
386
408
|
if (result.isErr()) {
|
|
387
409
|
return { applied: false, refusal: result.error };
|
|
388
410
|
}
|
|
@@ -397,8 +419,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
|
|
|
397
419
|
if (persisted.outcome === "failed") {
|
|
398
420
|
return { applied: false, refusal: { reason: "state-unavailable", detail: persisted.detail } };
|
|
399
421
|
}
|
|
400
|
-
const observed
|
|
401
|
-
if (observed !== undefined) {
|
|
422
|
+
for (const observed of events(previous, next)) {
|
|
402
423
|
observe(observed);
|
|
403
424
|
}
|
|
404
425
|
// A move inside one container names the same holder two times.
|
|
@@ -408,8 +429,10 @@ export function createReportAuthoringTools(gateway, deps = {}) {
|
|
|
408
429
|
const refuse = (refusal) => ({ applied: false, refusal });
|
|
409
430
|
const add_block = defineTool({
|
|
410
431
|
id: "add_block",
|
|
411
|
-
description: "Add one block to the draft. The
|
|
412
|
-
"
|
|
432
|
+
description: "Add one block to the draft. The block can be a section that carries its child blocks, thus one call can add a whole section with its atoms. " +
|
|
433
|
+
"The `block` schema gives the eight kinds and the fields of each one. " +
|
|
434
|
+
"You choose the `id` of each block, and each id must be unique in the draft. " +
|
|
435
|
+
"The validation covers the block and each block under it, and the whole payload lands or no part of it lands. " +
|
|
413
436
|
"Name the destination with `parentId` and one of `place`, `before`, or `after`. " +
|
|
414
437
|
"The root admits a section only, and an atom needs a section as its parent. " +
|
|
415
438
|
"A reference names the path of a pinned artifact, and the session stamps the hash from the pinned evidence.",
|
|
@@ -439,7 +462,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
|
|
|
439
462
|
return ok(await land(threadId, token, state.document, addBlock(state.document, { block, destination: destination.value }, state.snapshot), (_previous, next) => [addedId === undefined ? undefined : holderIdOf(next, addedId)],
|
|
440
463
|
// The grammar requires an id on the payload, thus a landed add always names one. A
|
|
441
464
|
// payload with no id reaches the core, which refuses it, and no event is due.
|
|
442
|
-
(_previous, next) => (addedId === undefined ?
|
|
465
|
+
(_previous, next) => (addedId === undefined ? [] : addedActsOf({ analysisId, threadId }, next, addedId))));
|
|
443
466
|
},
|
|
444
467
|
});
|
|
445
468
|
const change_block = defineTool({
|
|
@@ -518,12 +541,14 @@ export function createReportAuthoringTools(gateway, deps = {}) {
|
|
|
518
541
|
return ok(refuse(opened.error));
|
|
519
542
|
}
|
|
520
543
|
const { threadId, analysisId, state, token } = opened.value;
|
|
521
|
-
return ok(await land(threadId, token, state.document, ok(setTitle(state.document, input.title)), () => [], () =>
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
544
|
+
return ok(await land(threadId, token, state.document, ok(setTitle(state.document, input.title)), () => [], () => [
|
|
545
|
+
{
|
|
546
|
+
type: "set-title",
|
|
547
|
+
analysisId,
|
|
548
|
+
threadId,
|
|
549
|
+
title: input.title,
|
|
550
|
+
},
|
|
551
|
+
]));
|
|
527
552
|
},
|
|
528
553
|
});
|
|
529
554
|
const read_outline = defineTool({
|
|
@@ -563,7 +588,9 @@ export function createReportAuthoringTools(gateway, deps = {}) {
|
|
|
563
588
|
});
|
|
564
589
|
const finish_draft = defineTool({
|
|
565
590
|
id: "finish_draft",
|
|
566
|
-
description: "Finish the draft. The result gives each completeness gap, or
|
|
591
|
+
description: "Finish the draft. The result gives each completeness gap, or a pass, and each advisory warning. " +
|
|
592
|
+
"A pass gives back no document. " +
|
|
593
|
+
"preview_report and record_report_version run the same check before they act, thus a finish directly before one of them does that check a second time. " +
|
|
567
594
|
"Use it to make sure that the draft passes the whole document schema, the id rule, and the structural tier.",
|
|
568
595
|
inputSchema: finishDraftInput,
|
|
569
596
|
executionMode: AUTHORING_EXECUTION_MODE,
|
|
@@ -574,7 +601,8 @@ export function createReportAuthoringTools(gateway, deps = {}) {
|
|
|
574
601
|
if (opened.isErr()) {
|
|
575
602
|
return ok({ refused: opened.error });
|
|
576
603
|
}
|
|
577
|
-
|
|
604
|
+
const finished = finishDraft(opened.value.state.document, opened.value.state.snapshot, opened.value.derivations);
|
|
605
|
+
return ok(finished.valid ? { valid: true, warnings: finished.warnings } : finished);
|
|
578
606
|
},
|
|
579
607
|
});
|
|
580
608
|
return {
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* see: the screenshot, the console errors, and the failed requests. The agent reads the picture and the
|
|
6
6
|
* faults, decides, and repairs. The tool never judges, thus it never blocks the loop.
|
|
7
7
|
*
|
|
8
|
+
* A look takes the whole page, or one block that the input names, thus a look that confirms a repair pays
|
|
9
|
+
* for that block alone. The faults arrive as a digest (`page-faults.ts`).
|
|
10
|
+
*
|
|
8
11
|
* The composition says which URL one look opens, and the URL seam is that answer. Absent the seam, the tool
|
|
9
12
|
* navigates to the page file through a `file://` URL. The page lives at `report-sessions/{threadId}/index.html`
|
|
10
13
|
* under the workspace root, which the preview writes. A missed page means that no preview ran, and it is a
|
|
@@ -30,8 +33,9 @@
|
|
|
30
33
|
* On a capture the tool copies the rendered hash onto the seen hash through the gateway. Thus the look
|
|
31
34
|
* counts, and the record tool lets the current draft record. The copy takes the rendered hash and never the
|
|
32
35
|
* current one, thus a later edit makes the look stale and the record refuses. A partial look — the window
|
|
33
|
-
* alone,
|
|
34
|
-
* and the coverage of the result is what tells the agent what it saw.
|
|
36
|
+
* alone, slices that a budget truncated, or one block — is a true look at the current document, thus it
|
|
37
|
+
* stamps the same and the coverage of the result is what tells the agent what it saw. A block look that
|
|
38
|
+
* finds no such block took no picture, thus it stamps nothing.
|
|
35
39
|
*
|
|
36
40
|
* The gateway reports whether a rendered hash existed to copy. When the row holds none, no preview stamped
|
|
37
41
|
* one and the look cannot count. The tool then gives a missed-stamp outcome that directs a new preview,
|
|
@@ -56,6 +60,7 @@ import { type CaptureCoverage, type CapturePage, type FailedRequest, type PageCa
|
|
|
56
60
|
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
57
61
|
import { type Tool } from "../define-tool.js";
|
|
58
62
|
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
63
|
+
import { type ConsoleErrorEntry, type FailedRequestEntry } from "./page-faults.js";
|
|
59
64
|
export type { CaptureCoverage, CapturePage, FailedRequest, PageCapture };
|
|
60
65
|
/**
|
|
61
66
|
* The URL formation of one look. It maps the page of the thread onto the URL that the browser opens.
|
|
@@ -69,8 +74,10 @@ export type ResolvePageUrl = (args: {
|
|
|
69
74
|
threadId: string;
|
|
70
75
|
auth: AuthContext;
|
|
71
76
|
}) => ResultAsync<string, GateFailure>;
|
|
72
|
-
/** The
|
|
73
|
-
declare const examinePageInput: z.ZodObject<{
|
|
77
|
+
/** The input of one look. Nullish, because under strict function calling a model sends `null` for an unused field. */
|
|
78
|
+
declare const examinePageInput: z.ZodObject<{
|
|
79
|
+
blockId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
80
|
+
}, z.core.$strip>;
|
|
74
81
|
export type ExaminePageInput = z.infer<typeof examinePageInput>;
|
|
75
82
|
/** The document range of one slice of a tiled look, in the order that the pictures ride. */
|
|
76
83
|
export interface ExaminedTile {
|
|
@@ -80,15 +87,16 @@ export interface ExaminedTile {
|
|
|
80
87
|
}
|
|
81
88
|
/**
|
|
82
89
|
* The typed outcome of the eyes tool. Each arm is ok-channel data, thus the tool never throws for a
|
|
83
|
-
* degraded condition. `examined` carries the coverage of the look, the console errors
|
|
84
|
-
* requests, and the page path. The coverage names what the agent saw: the whole document in one
|
|
85
|
-
* consecutive slices with the captured and the total pixels,
|
|
86
|
-
* the bitmap. A
|
|
87
|
-
* pictures — thus the model reads which rows each picture holds. The screenshots do not ride the JSON.
|
|
90
|
+
* degraded condition. `examined` carries the coverage of the look, the digest of the console errors and the
|
|
91
|
+
* failed requests, and the page path. The coverage names what the agent saw: the whole document in one
|
|
92
|
+
* shot, consecutive slices with the captured and the total pixels, the window alone when the browser refused
|
|
93
|
+
* the bitmap, or one block. A sliced look carries `tiles` — the document range of each slice, in the order
|
|
94
|
+
* of the pictures — thus the model reads which rows each picture holds. The screenshots do not ride the JSON.
|
|
88
95
|
* They ride the image path of the tool result in document order, thus the model sees the pictures and the
|
|
89
|
-
* JSON text holds no bytes. `
|
|
90
|
-
*
|
|
91
|
-
*
|
|
96
|
+
* JSON text holds no bytes. `no-block` means that the current page holds no block of the id that the input
|
|
97
|
+
* named. `missed-stamp` means that the row holds no rendered hash, thus no preview stamped one and the
|
|
98
|
+
* agent must run a new preview before the next look. `no-browser` means that the composition gives no
|
|
99
|
+
* browser, thus no look is possible at all and a repeat gives the same answer.
|
|
92
100
|
*/
|
|
93
101
|
export type ExaminePageResult = {
|
|
94
102
|
outcome: "refused";
|
|
@@ -98,6 +106,9 @@ export type ExaminePageResult = {
|
|
|
98
106
|
detail: string;
|
|
99
107
|
} | {
|
|
100
108
|
outcome: "no-page";
|
|
109
|
+
} | {
|
|
110
|
+
outcome: "no-block";
|
|
111
|
+
blockId: string;
|
|
101
112
|
} | {
|
|
102
113
|
outcome: "missed-stamp";
|
|
103
114
|
} | {
|
|
@@ -107,8 +118,12 @@ export type ExaminePageResult = {
|
|
|
107
118
|
outcome: "examined";
|
|
108
119
|
coverage: CaptureCoverage;
|
|
109
120
|
tiles?: ExaminedTile[];
|
|
110
|
-
consoleErrors:
|
|
111
|
-
failedRequests:
|
|
121
|
+
consoleErrors: ConsoleErrorEntry[];
|
|
122
|
+
failedRequests: FailedRequestEntry[];
|
|
123
|
+
omittedFaults?: {
|
|
124
|
+
consoleErrors: number;
|
|
125
|
+
failedRequests: number;
|
|
126
|
+
};
|
|
112
127
|
pagePath: string;
|
|
113
128
|
};
|
|
114
129
|
/**
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* see: the screenshot, the console errors, and the failed requests. The agent reads the picture and the
|
|
6
6
|
* faults, decides, and repairs. The tool never judges, thus it never blocks the loop.
|
|
7
7
|
*
|
|
8
|
+
* A look takes the whole page, or one block that the input names, thus a look that confirms a repair pays
|
|
9
|
+
* for that block alone. The faults arrive as a digest (`page-faults.ts`).
|
|
10
|
+
*
|
|
8
11
|
* The composition says which URL one look opens, and the URL seam is that answer. Absent the seam, the tool
|
|
9
12
|
* navigates to the page file through a `file://` URL. The page lives at `report-sessions/{threadId}/index.html`
|
|
10
13
|
* under the workspace root, which the preview writes. A missed page means that no preview ran, and it is a
|
|
@@ -30,8 +33,9 @@
|
|
|
30
33
|
* On a capture the tool copies the rendered hash onto the seen hash through the gateway. Thus the look
|
|
31
34
|
* counts, and the record tool lets the current draft record. The copy takes the rendered hash and never the
|
|
32
35
|
* current one, thus a later edit makes the look stale and the record refuses. A partial look — the window
|
|
33
|
-
* alone,
|
|
34
|
-
* and the coverage of the result is what tells the agent what it saw.
|
|
36
|
+
* alone, slices that a budget truncated, or one block — is a true look at the current document, thus it
|
|
37
|
+
* stamps the same and the coverage of the result is what tells the agent what it saw. A block look that
|
|
38
|
+
* finds no such block took no picture, thus it stamps nothing.
|
|
35
39
|
*
|
|
36
40
|
* The gateway reports whether a rendered hash existed to copy. When the row holds none, no preview stamped
|
|
37
41
|
* one and the look cannot count. The tool then gives a missed-stamp outcome that directs a new preview,
|
|
@@ -58,8 +62,15 @@ import { capturePage } from "../../lib/page-capture.js";
|
|
|
58
62
|
import { reportSessionDir } from "../../workspace/paths.js";
|
|
59
63
|
import { defineTool, withToolResultImages } from "../define-tool.js";
|
|
60
64
|
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
import { digestPageFaults } from "./page-faults.js";
|
|
66
|
+
/** The input of one look. Nullish, because under strict function calling a model sends `null` for an unused field. */
|
|
67
|
+
const examinePageInput = z.object({
|
|
68
|
+
blockId: z
|
|
69
|
+
.string()
|
|
70
|
+
.min(1)
|
|
71
|
+
.nullish()
|
|
72
|
+
.describe("The id of one block, as the outline gives it. The look then holds that block alone. Leave it out to look at the whole page."),
|
|
73
|
+
});
|
|
63
74
|
/**
|
|
64
75
|
* Resolve the transport of the tool from its deps, one time.
|
|
65
76
|
*
|
|
@@ -177,7 +188,7 @@ async function runLook(args) {
|
|
|
177
188
|
const { transport } = args;
|
|
178
189
|
if (transport.kind === "capture") {
|
|
179
190
|
try {
|
|
180
|
-
return ok(await transport.capture(args.url));
|
|
191
|
+
return ok(await transport.capture(args.url, args.options));
|
|
181
192
|
}
|
|
182
193
|
catch (cause) {
|
|
183
194
|
return err({ stage: "capture", cause });
|
|
@@ -194,7 +205,7 @@ async function runLook(args) {
|
|
|
194
205
|
}
|
|
195
206
|
const lease = acquired.value;
|
|
196
207
|
try {
|
|
197
|
-
return ok(await capturePage({ ...args.chrome, browserUrl: lease.browserUrl }, args.url));
|
|
208
|
+
return ok(await capturePage({ ...args.chrome, browserUrl: lease.browserUrl }, args.url, args.options));
|
|
198
209
|
}
|
|
199
210
|
catch (cause) {
|
|
200
211
|
return err({ stage: "capture", cause });
|
|
@@ -228,20 +239,29 @@ export function createExaminePageTool(deps) {
|
|
|
228
239
|
id: "examine_page",
|
|
229
240
|
description: "Open the rendered report page in a real headless browser, and give back what a reviewer sees: " +
|
|
230
241
|
"one or more screenshots, the coverage of the look, the console errors, and the failed requests. " +
|
|
231
|
-
"
|
|
232
|
-
"
|
|
242
|
+
"Without `blockId` the look holds the whole page. With `blockId` the look holds that one block alone: a section with its content, or one atom. " +
|
|
243
|
+
"A tall page or a tall block arrives as consecutive top-to-bottom slices in document order, and the tiles list names the rows of each slice. " +
|
|
244
|
+
"The coverage says whether the pictures hold the whole page, a truncated top portion of it, the top window alone, or the one block. " +
|
|
245
|
+
"A look costs about 500 image tokens for each 1,000 pixels of height that it holds, and one look holds at most 20,000 pixels. " +
|
|
246
|
+
"Each look adds its pictures to the conversation, and each later request of the turn carries them again. " +
|
|
247
|
+
"Each repeated console error or failed request comes back one time with a count, and a placeholder replaces each inline data. " +
|
|
233
248
|
"Run it after the preview to look at the current page, and to confirm that the layout and the charts read clean. " +
|
|
234
|
-
"A look at the page of the current draft is what lets record_report_version record. " +
|
|
249
|
+
"A look at the page of the current draft is what lets record_report_version record, and a block look counts the same as a look at the whole page. " +
|
|
250
|
+
"The outcome no-block means that the current page holds no block of that id. " +
|
|
235
251
|
"The outcomes no-page and missed-stamp mean that no preview rendered the current page: run preview_report, then look again. " +
|
|
236
252
|
"The outcome no-browser means that this host gives no browser, and a repeat gives the same outcome.",
|
|
237
253
|
inputSchema: examinePageInput,
|
|
238
254
|
executionMode: "inline",
|
|
239
|
-
describeCall:
|
|
255
|
+
describeCall: (input) => {
|
|
256
|
+
const blockId = input.blockId ?? undefined;
|
|
257
|
+
return blockId === undefined ? "look at the page" : `look at ${blockId}`;
|
|
258
|
+
},
|
|
240
259
|
// A look gives a picture, and a picture has no one-line form. Thus the outcome of the look is the
|
|
241
260
|
// whole line: a watcher reads that the eyes saw the page, or that the page was absent, or that the
|
|
242
261
|
// capture failed.
|
|
243
262
|
describeResult: (_input, result) => result.outcome,
|
|
244
|
-
execute: async (
|
|
263
|
+
execute: async (input, ctx) => {
|
|
264
|
+
const blockId = input.blockId ?? undefined;
|
|
245
265
|
// The check runs before every read, thus one clear signal replaces a capture failure for each
|
|
246
266
|
// page. The arm stamps no seen hash, because no eyes saw the page. It also narrows the transport
|
|
247
267
|
// for the look below, thus the acquire needs no assertion.
|
|
@@ -302,6 +322,7 @@ export function createExaminePageTool(deps) {
|
|
|
302
322
|
chrome: deps.chrome,
|
|
303
323
|
scope: { analysisId, workspaceRoot: root },
|
|
304
324
|
url,
|
|
325
|
+
options: blockId === undefined ? {} : { blockId },
|
|
305
326
|
acquireMs,
|
|
306
327
|
releaseMs,
|
|
307
328
|
logger,
|
|
@@ -312,6 +333,10 @@ export function createExaminePageTool(deps) {
|
|
|
312
333
|
return ok({ outcome: "capture-failed", detail: cause instanceof Error ? cause.message : String(cause) });
|
|
313
334
|
}
|
|
314
335
|
const captured = looked.value;
|
|
336
|
+
if (blockId !== undefined && captured.coverage.kind === "no-block") {
|
|
337
|
+
// No picture exists, thus no eyes saw the page and the look stamps nothing.
|
|
338
|
+
return ok({ outcome: "no-block", blockId });
|
|
339
|
+
}
|
|
315
340
|
const stamped = await deps.gateway.stampSeen(threadId);
|
|
316
341
|
if (stamped.outcome === "no-rendered") {
|
|
317
342
|
// The row holds no rendered hash, thus no preview stamped one and the look cannot count. A
|
|
@@ -328,16 +353,15 @@ export function createExaminePageTool(deps) {
|
|
|
328
353
|
logger.warn("the seen hash did not stamp", { threadId, analysisId, detail });
|
|
329
354
|
}
|
|
330
355
|
// The screenshots ride the image path of the tool result in document order, thus the model sees
|
|
331
|
-
// the pictures. The JSON keeps the faults, the tile ranges, and the page path only,
|
|
332
|
-
// JSON text holds no bytes. The tiles list mirrors the picture order, thus the model reads
|
|
333
|
-
// rows each picture holds.
|
|
356
|
+
// the pictures. The JSON keeps the digest of the faults, the tile ranges, and the page path only,
|
|
357
|
+
// thus the JSON text holds no bytes. The tiles list mirrors the picture order, thus the model reads
|
|
358
|
+
// which rows each picture holds.
|
|
334
359
|
const tiles = captured.screenshots.flatMap((shot, index) => shot.range === undefined ? [] : [{ index, fromY: shot.range.fromY, toY: shot.range.toY }]);
|
|
335
360
|
return ok(withToolResultImages({
|
|
336
361
|
outcome: "examined",
|
|
337
362
|
coverage: captured.coverage,
|
|
338
363
|
...(tiles.length === 0 ? {} : { tiles }),
|
|
339
|
-
consoleErrors
|
|
340
|
-
failedRequests: captured.failedRequests,
|
|
364
|
+
...digestPageFaults(captured.consoleErrors, captured.failedRequests),
|
|
341
365
|
pagePath: relativePagePath,
|
|
342
366
|
}, captured.screenshots.map((shot) => ({ base64: shot.base64, mediaType: SCREENSHOT_MEDIA_TYPE }))));
|
|
343
367
|
},
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The pinned-artifact listing tool of a report session.
|
|
3
3
|
*
|
|
4
|
-
* The tool reads the frozen snapshot of the thread, and it gives the pinned set: the path, the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The tool reads the frozen snapshot of the thread, and it gives the pinned set: the path, the file type,
|
|
5
|
+
* and the count of the header columns of a tabular artifact, plus the pinned citations with the short
|
|
6
|
+
* citation of each. Thus one call orients the agent, and a reference binds to a path or to a citation key of
|
|
7
|
+
* that set. A call with a path gives the header columns of that one artifact.
|
|
8
|
+
*
|
|
9
|
+
* A wide table can have hundreds of columns, and the listing stays in the context of each later request.
|
|
10
|
+
* Thus the listing counts the columns, and the agent reads the names of one artifact when it binds to it.
|
|
11
|
+
* A reference names the path alone and the session stamps the hash, thus the tool gives no hash.
|
|
8
12
|
*
|
|
9
13
|
* The order is the code-unit order of the path. Two calls over one snapshot give one listing, thus the
|
|
10
14
|
* agent reads a stable set. A snapshot can pin many thousands of staged inputs, thus the listing stops at
|
|
@@ -22,19 +26,22 @@ import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
|
22
26
|
import { type Logger } from "../../lib/logger.js";
|
|
23
27
|
import { type Tool } from "../define-tool.js";
|
|
24
28
|
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
25
|
-
/**
|
|
26
|
-
declare const listPinnedArtifactsInput: z.ZodObject<{
|
|
29
|
+
/** No path lists the pinned set. A path gives the header columns of that one pinned artifact. */
|
|
30
|
+
declare const listPinnedArtifactsInput: z.ZodObject<{
|
|
31
|
+
path: z.ZodOptional<z.ZodString>;
|
|
32
|
+
}, z.core.$strip>;
|
|
27
33
|
export type ListPinnedArtifactsInput = z.infer<typeof listPinnedArtifactsInput>;
|
|
28
34
|
/**
|
|
29
|
-
* One pinned artifact
|
|
35
|
+
* One pinned artifact.
|
|
30
36
|
*
|
|
31
|
-
* `fileType` states a role, and it does not state a data format. `columns`
|
|
32
|
-
* that the read recovered, thus
|
|
37
|
+
* `fileType` states a role, and it does not state a data format. `columnCount` and `columns` are present
|
|
38
|
+
* only for a header that the read recovered, thus their absence says nothing about the content of the file.
|
|
39
|
+
* The listing gives `columnCount`, and a call with a path gives `columns` as well.
|
|
33
40
|
*/
|
|
34
41
|
export interface PinnedArtifact {
|
|
35
42
|
path: string;
|
|
36
|
-
hash: string;
|
|
37
43
|
fileType?: string;
|
|
44
|
+
columnCount?: number;
|
|
38
45
|
columns?: string[];
|
|
39
46
|
}
|
|
40
47
|
/**
|
|
@@ -67,6 +74,12 @@ export type ListPinnedArtifactsResult = {
|
|
|
67
74
|
total: number;
|
|
68
75
|
truncated: boolean;
|
|
69
76
|
citations: PinnedCitation[];
|
|
77
|
+
} | {
|
|
78
|
+
outcome: "artifact";
|
|
79
|
+
artifact: PinnedArtifact;
|
|
80
|
+
} | {
|
|
81
|
+
outcome: "not-pinned";
|
|
82
|
+
path: string;
|
|
70
83
|
};
|
|
71
84
|
/**
|
|
72
85
|
* The construction deps of the listing tool.
|