@inflexa-ai/harness 0.25.0 → 0.26.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/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/lib/page-capture.d.ts +44 -15
- package/dist/lib/page-capture.js +78 -19
- package/dist/loop/run-agent.js +36 -19
- package/dist/memory/card-builders.d.ts +4 -6
- package/dist/memory/card-builders.js +4 -6
- package/dist/memory/conversation-display-replay.d.ts +7 -6
- package/dist/memory/conversation-display-replay.js +7 -6
- package/dist/memory/conversation-display-storage.d.ts +2 -10
- package/dist/memory/conversation-display-storage.js +79 -32
- package/dist/memory/thread-history.d.ts +31 -20
- package/dist/memory/thread-history.js +45 -45
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +22 -13
- package/dist/runtime/boot.d.ts +7 -8
- package/dist/runtime/boot.js +7 -19
- package/dist/tools/define-tool.d.ts +19 -8
- package/dist/tools/define-tool.js +28 -12
- package/dist/tools/report-session/examine-page.d.ts +22 -13
- package/dist/tools/report-session/examine-page.js +15 -10
- package/dist/tools/report-session/index.d.ts +1 -1
- package/dist/tools/research/generate-plan.js +3 -8
- package/dist/tools/research/inspect-data-profile.js +10 -11
- package/package.json +1 -1
- package/dist/app/data-profile-policy.d.ts +0 -62
- package/dist/app/data-profile-policy.js +0 -59
- package/dist/memory/content-to-cortex.d.ts +0 -75
- package/dist/memory/content-to-cortex.js +0 -201
- package/dist/memory/conversation-display-backfill.d.ts +0 -23
- package/dist/memory/conversation-display-backfill.js +0 -91
- package/dist/memory/reconstruct-cards.d.ts +0 -27
- package/dist/memory/reconstruct-cards.js +0 -57
|
@@ -21,30 +21,46 @@ export function isToolError(value) {
|
|
|
21
21
|
return typeof value === "object" && value !== null && typeof value.error === "string" && typeof value.retryable === "boolean";
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* The reserved key that carries
|
|
24
|
+
* The reserved key that carries the pictures on a tool ok value. It is a symbol,
|
|
25
25
|
* thus it never collides with a data field. `JSON.stringify` also omits a symbol
|
|
26
26
|
* key, thus the bytes never reach the JSON text by accident.
|
|
27
27
|
*/
|
|
28
28
|
export const toolResultImageKey = Symbol("toolResultImage");
|
|
29
29
|
/**
|
|
30
|
-
* Attach
|
|
30
|
+
* Attach one picture to a tool ok value. The value keeps its own fields, and the
|
|
31
31
|
* picture rides under the reserved key. The loop reads the key, and it moves the
|
|
32
32
|
* bytes into an image content block on the tool result.
|
|
33
33
|
*/
|
|
34
34
|
export function withToolResultImage(value, image) {
|
|
35
35
|
return { ...value, [toolResultImageKey]: image };
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Attach an ordered list of pictures to a tool ok value. The order is the order
|
|
39
|
+
* that the model reads, and the loop keeps it on the wire. A tool that slices a
|
|
40
|
+
* page attaches the slices in document order.
|
|
41
|
+
*/
|
|
42
|
+
export function withToolResultImages(value, images) {
|
|
43
|
+
return { ...value, [toolResultImageKey]: images };
|
|
44
|
+
}
|
|
45
|
+
/** Does one entry under the reserved key carry the picture shape? */
|
|
46
|
+
function isToolResultImage(entry) {
|
|
47
|
+
if (typeof entry !== "object" || entry === null)
|
|
48
|
+
return false;
|
|
49
|
+
const { base64, mediaType } = entry;
|
|
50
|
+
return typeof base64 === "string" && typeof mediaType === "string";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read the pictures that a tool ok value carries, in order. A value with none
|
|
54
|
+
* gives the empty list. The one-picture shape reads as a list of one, thus a
|
|
55
|
+
* tool that attaches one picture and a tool that attaches a list meet the loop
|
|
56
|
+
* in the same shape.
|
|
57
|
+
*/
|
|
58
|
+
export function readToolResultImages(value) {
|
|
39
59
|
if (typeof value !== "object" || value === null)
|
|
40
|
-
return
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const { base64, mediaType } = image;
|
|
45
|
-
if (typeof base64 !== "string" || typeof mediaType !== "string")
|
|
46
|
-
return undefined;
|
|
47
|
-
return { base64, mediaType };
|
|
60
|
+
return [];
|
|
61
|
+
const carried = value[toolResultImageKey];
|
|
62
|
+
const entries = Array.isArray(carried) ? carried : [carried];
|
|
63
|
+
return entries.filter(isToolResultImage);
|
|
48
64
|
}
|
|
49
65
|
/**
|
|
50
66
|
* Package a `Tool`. The emitted JSON Schema must have a top-level
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
*
|
|
30
30
|
* On a capture the tool copies the rendered hash onto the seen hash through the gateway. Thus the look
|
|
31
31
|
* counts, and the record tool lets the current draft record. The copy takes the rendered hash and never the
|
|
32
|
-
* current one, thus a later edit makes the look stale and the record refuses. A
|
|
33
|
-
* is a true
|
|
34
|
-
* tells the agent what it saw.
|
|
32
|
+
* current one, thus a later edit makes the look stale and the record refuses. A partial look — the window
|
|
33
|
+
* alone, or slices that a budget truncated — is a true look at the current document, thus it stamps the same
|
|
34
|
+
* and the coverage of the result is what tells the agent what it saw.
|
|
35
35
|
*
|
|
36
36
|
* The gateway reports whether a rendered hash existed to copy. When the row holds none, no preview stamped
|
|
37
37
|
* one and the look cannot count. The tool then gives a missed-stamp outcome that directs a new preview,
|
|
@@ -49,11 +49,11 @@ import type { AuthContext } from "../../auth/types.js";
|
|
|
49
49
|
import { type ChromeConfig } from "../../lib/chrome.js";
|
|
50
50
|
import { type AcquireEyes } from "../../lib/eyes.js";
|
|
51
51
|
import { type Logger } from "../../lib/logger.js";
|
|
52
|
-
import { type CapturePage, type FailedRequest, type PageCapture } from "../../lib/page-capture.js";
|
|
52
|
+
import { type CaptureCoverage, type CapturePage, type FailedRequest, type PageCapture } from "../../lib/page-capture.js";
|
|
53
53
|
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
54
54
|
import { type Tool } from "../define-tool.js";
|
|
55
55
|
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
56
|
-
export type { CapturePage, FailedRequest, PageCapture };
|
|
56
|
+
export type { CaptureCoverage, CapturePage, FailedRequest, PageCapture };
|
|
57
57
|
/**
|
|
58
58
|
* The URL formation of one look. It maps the page of the thread onto the URL that the browser opens.
|
|
59
59
|
* A composition whose browser cannot reach the workspace tree binds a resolver that names a served URL.
|
|
@@ -69,15 +69,23 @@ export type ResolvePageUrl = (args: {
|
|
|
69
69
|
/** The empty input. The tool examines the current page of the thread, thus it needs no field. */
|
|
70
70
|
declare const examinePageInput: z.ZodObject<{}, z.core.$strip>;
|
|
71
71
|
export type ExaminePageInput = z.infer<typeof examinePageInput>;
|
|
72
|
+
/** The document range of one slice of a tiled look, in the order that the pictures ride. */
|
|
73
|
+
export interface ExaminedTile {
|
|
74
|
+
index: number;
|
|
75
|
+
fromY: number;
|
|
76
|
+
toY: number;
|
|
77
|
+
}
|
|
72
78
|
/**
|
|
73
79
|
* The typed outcome of the eyes tool. Each arm is ok-channel data, thus the tool never throws for a
|
|
74
|
-
* degraded condition. `examined` carries the coverage of the
|
|
75
|
-
* requests, and the page path. The coverage names what the agent saw: the whole document
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
80
|
+
* degraded condition. `examined` carries the coverage of the look, the console errors, the failed
|
|
81
|
+
* requests, and the page path. The coverage names what the agent saw: the whole document in one shot,
|
|
82
|
+
* consecutive slices with the captured and the total pixels, or the window alone when the browser refused
|
|
83
|
+
* the bitmap. A tiled look carries `tiles` — the document range of each slice, in the order of the
|
|
84
|
+
* pictures — thus the model reads which rows each picture holds. The screenshots do not ride the JSON.
|
|
85
|
+
* They ride the image path of the tool result in document order, thus the model sees the pictures and the
|
|
86
|
+
* JSON text holds no bytes. `missed-stamp` means that the row holds no rendered hash, thus no preview
|
|
87
|
+
* stamped one and the agent must run a new preview before the next look. `no-browser` means that the
|
|
88
|
+
* composition gives no browser, thus no look is possible at all and a repeat gives the same answer.
|
|
81
89
|
*/
|
|
82
90
|
export type ExaminePageResult = {
|
|
83
91
|
outcome: "refused";
|
|
@@ -94,7 +102,8 @@ export type ExaminePageResult = {
|
|
|
94
102
|
detail: string;
|
|
95
103
|
} | {
|
|
96
104
|
outcome: "examined";
|
|
97
|
-
coverage:
|
|
105
|
+
coverage: CaptureCoverage;
|
|
106
|
+
tiles?: ExaminedTile[];
|
|
98
107
|
consoleErrors: string[];
|
|
99
108
|
failedRequests: FailedRequest[];
|
|
100
109
|
pagePath: string;
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
*
|
|
30
30
|
* On a capture the tool copies the rendered hash onto the seen hash through the gateway. Thus the look
|
|
31
31
|
* counts, and the record tool lets the current draft record. The copy takes the rendered hash and never the
|
|
32
|
-
* current one, thus a later edit makes the look stale and the record refuses. A
|
|
33
|
-
* is a true
|
|
34
|
-
* tells the agent what it saw.
|
|
32
|
+
* current one, thus a later edit makes the look stale and the record refuses. A partial look — the window
|
|
33
|
+
* alone, or slices that a budget truncated — is a true look at the current document, thus it stamps the same
|
|
34
|
+
* and the coverage of the result is what tells the agent what it saw.
|
|
35
35
|
*
|
|
36
36
|
* The gateway reports whether a rendered hash existed to copy. When the row holds none, no preview stamped
|
|
37
37
|
* one and the look cannot count. The tool then gives a missed-stamp outcome that directs a new preview,
|
|
@@ -55,7 +55,7 @@ import { createStaticEyes } from "../../lib/eyes.js";
|
|
|
55
55
|
import { defaultErrorFields } from "../../lib/logger.js";
|
|
56
56
|
import { capturePage } from "../../lib/page-capture.js";
|
|
57
57
|
import { reportSessionDir } from "../../workspace/paths.js";
|
|
58
|
-
import { defineTool,
|
|
58
|
+
import { defineTool, withToolResultImages } from "../define-tool.js";
|
|
59
59
|
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
60
60
|
/** The empty input. The tool examines the current page of the thread, thus it needs no field. */
|
|
61
61
|
const examinePageInput = z.object({});
|
|
@@ -226,8 +226,9 @@ export function createExaminePageTool(deps) {
|
|
|
226
226
|
return defineTool({
|
|
227
227
|
id: "examine_page",
|
|
228
228
|
description: "Open the rendered report page in a real headless browser, and report what you see. " +
|
|
229
|
-
"Give back
|
|
230
|
-
"
|
|
229
|
+
"Give back one or more screenshots, the coverage of the look, the console errors, and the failed requests. " +
|
|
230
|
+
"A tall page arrives as consecutive top-to-bottom slices in document order, and the tiles list names the rows of each slice. " +
|
|
231
|
+
"The coverage says whether the pictures hold the whole page, a truncated top portion of it, or the top window alone. " +
|
|
231
232
|
"Run it after the preview to look at the current page, and to confirm that the layout and the charts read clean. " +
|
|
232
233
|
"The report tool records a version only after you look at the current page. " +
|
|
233
234
|
"If the page has no confirmed render, run the preview again first.",
|
|
@@ -317,15 +318,19 @@ export function createExaminePageTool(deps) {
|
|
|
317
318
|
const detail = stamped.outcome === "failed" ? stamped.detail : "the session state row is absent";
|
|
318
319
|
logger.warn("the seen hash did not stamp", { threadId, analysisId, detail });
|
|
319
320
|
}
|
|
320
|
-
// The
|
|
321
|
-
// JSON keeps the faults and the page path only, thus the
|
|
322
|
-
|
|
321
|
+
// The screenshots ride the image path of the tool result in document order, thus the model sees
|
|
322
|
+
// the pictures. The JSON keeps the faults, the tile ranges, and the page path only, thus the
|
|
323
|
+
// JSON text holds no bytes. The tiles list mirrors the picture order, thus the model reads which
|
|
324
|
+
// rows each picture holds.
|
|
325
|
+
const tiles = captured.screenshots.flatMap((shot, index) => shot.range === undefined ? [] : [{ index, fromY: shot.range.fromY, toY: shot.range.toY }]);
|
|
326
|
+
return ok(withToolResultImages({
|
|
323
327
|
outcome: "examined",
|
|
324
328
|
coverage: captured.coverage,
|
|
329
|
+
...(tiles.length === 0 ? {} : { tiles }),
|
|
325
330
|
consoleErrors: captured.consoleErrors,
|
|
326
331
|
failedRequests: captured.failedRequests,
|
|
327
332
|
pagePath: relativePagePath,
|
|
328
|
-
}, { base64:
|
|
333
|
+
}, captured.screenshots.map((shot) => ({ base64: shot.base64, mediaType: SCREENSHOT_MEDIA_TYPE }))));
|
|
329
334
|
},
|
|
330
335
|
});
|
|
331
336
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the factories and their public types for the composition root.
|
|
7
7
|
*/
|
|
8
8
|
export { createPreviewReportTool, type PreviewReportInput, type PreviewReportResult, type PreviewReportToolDeps, type ResolvePageAsset, type SessionPageAccess, } from "./preview-report.js";
|
|
9
|
-
export { createExaminePageTool, type CapturePage, type ExaminePageInput, type ExaminePageResult, type ExaminePageToolDeps, type FailedRequest, type PageCapture, type ResolvePageUrl, } from "./examine-page.js";
|
|
9
|
+
export { createExaminePageTool, type CaptureCoverage, type CapturePage, type ExaminedTile, type ExaminePageInput, type ExaminePageResult, type ExaminePageToolDeps, type FailedRequest, type PageCapture, type ResolvePageUrl, } from "./examine-page.js";
|
|
10
10
|
export { describeSessionPageMintFailure, UnavailableSessionPagePublisher, type MakeSessionPagePublisher, type SessionPageMintFailure, type SessionPageMintResult, type SessionPagePublisher, } from "./session-page-publisher.js";
|
|
11
11
|
export { createRecordVersionTool, type RecordVersionInput, type RecordVersionResult, type RecordVersionToolDeps } from "./record-version.js";
|
|
12
12
|
export { createDeriveTableTool, type DeriveTableInput, type DeriveTableResult, type DeriveTableToolDeps } from "./derive-table.js";
|
|
@@ -36,7 +36,6 @@ import { createListAvailablePackagesTool } from "../sandbox/list-available-packa
|
|
|
36
36
|
import { createListAvailableRefsTool } from "../sandbox/list-available-refs.js";
|
|
37
37
|
import { createReportBlockerToolFor } from "../sandbox/report-blocker.js";
|
|
38
38
|
import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../../app/data-profile-orientation.js";
|
|
39
|
-
import { isDataProfileStale } from "../../app/data-profile-policy.js";
|
|
40
39
|
import { DEFAULT_SANDBOX_MAX_STEPS } from "../../config/resource-limits.js";
|
|
41
40
|
import { plannerPrompt } from "../../prompts/planner.js";
|
|
42
41
|
import { hydratePlanSteps, PlannerPlanSchema } from "../../schemas/plan-schemas.js";
|
|
@@ -157,10 +156,9 @@ function formatPriorPlan(parentPlanId, plan) {
|
|
|
157
156
|
/**
|
|
158
157
|
* Read the profile ledger row into a grounding variant.
|
|
159
158
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* never disagree about whether a profile still describes the data.
|
|
159
|
+
* A profile is re-invoked by the embedder when the input set changes, never derived
|
|
160
|
+
* as stale on read, so the only qualification this can raise is one the ledger row
|
|
161
|
+
* states outright: an attempt that superseded this result is running, or failed.
|
|
164
162
|
*/
|
|
165
163
|
function classifyGrounding(status) {
|
|
166
164
|
// `loadDataProfileStatus` collapses "no analysis row" and "profile cleared"
|
|
@@ -182,9 +180,6 @@ function classifyGrounding(status) {
|
|
|
182
180
|
// preserve `data_profile_result` on purpose, so a non-`completed` status
|
|
183
181
|
// carrying a result means what is on the row is the PREVIOUS profile.
|
|
184
182
|
const reasons = [];
|
|
185
|
-
if (isDataProfileStale({ fileIds: status.seedInputFileIds ?? [] }, result)) {
|
|
186
|
-
reasons.push("the analysis's input file set changed after this profile was taken");
|
|
187
|
-
}
|
|
188
183
|
if (status.status === "pending" || status.status === "running") {
|
|
189
184
|
reasons.push("a re-profile is in progress — this is the previous profile");
|
|
190
185
|
}
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { ok } from "neverthrow";
|
|
24
24
|
import { z } from "zod";
|
|
25
|
-
import { isDataProfileStale } from "../../app/data-profile-policy.js";
|
|
26
25
|
import { scopeResource } from "../../auth/types.js";
|
|
27
26
|
import { unwrapOrThrow } from "../../lib/result.js";
|
|
28
27
|
import { loadDataProfileStatus } from "../../state/index.js";
|
|
@@ -48,19 +47,19 @@ function datasetFileCount(result) {
|
|
|
48
47
|
/**
|
|
49
48
|
* Name every reason the served profile may not describe the current inputs.
|
|
50
49
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* embedder's re-trigger policy uses; staleness is defined once, in
|
|
54
|
-
* `app/data-profile-policy.ts`, not re-derived here). And the ledger can have moved off
|
|
55
|
-
* `completed` while an older result is still on the row: `tryRerun`/`tryRetry` preserve
|
|
50
|
+
* Every reason is one the ledger row states outright: the row has moved off `completed`
|
|
51
|
+
* while an older result is still on it, because `tryRerun`/`tryRetry` preserve
|
|
56
52
|
* `data_profile_result` precisely so a prior profile stays servable while the next
|
|
57
53
|
* attempt runs or after it fails.
|
|
54
|
+
*
|
|
55
|
+
* A changed input set is deliberately NOT among them. Re-profiling is invoked by the
|
|
56
|
+
* embedder that owns the input mutation — it knows the set changed at the moment it
|
|
57
|
+
* changed — so a row still reading `completed` is a row nothing has superseded. Deriving
|
|
58
|
+
* a verdict here would mean re-deciding, from one table row, a question already answered
|
|
59
|
+
* by the party that watched it happen.
|
|
58
60
|
*/
|
|
59
|
-
function stalenessReasons(status
|
|
61
|
+
function stalenessReasons(status) {
|
|
60
62
|
const reasons = [];
|
|
61
|
-
if (isDataProfileStale({ fileIds: status.seedInputFileIds ?? [] }, result)) {
|
|
62
|
-
reasons.push("the analysis's input file set changed after this profile was taken");
|
|
63
|
-
}
|
|
64
63
|
if (status.status === "running" || status.status === "pending") {
|
|
65
64
|
reasons.push("a re-profile is in progress — this is the previous profile");
|
|
66
65
|
}
|
|
@@ -163,7 +162,7 @@ export function createInspectDataProfileTool(pool) {
|
|
|
163
162
|
message: "Data profiling is still running; no profile is available yet. Proceed without it or ask the user to wait.",
|
|
164
163
|
});
|
|
165
164
|
}
|
|
166
|
-
const reasons = stalenessReasons(status
|
|
165
|
+
const reasons = stalenessReasons(status);
|
|
167
166
|
const envelope = reasons.length > 0
|
|
168
167
|
? { state: "stale", staleReason: reasons.join("; "), profiledAt: result.profiledAt }
|
|
169
168
|
: { state: "ready", profiledAt: result.profiledAt };
|
package/package.json
CHANGED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Data-profile policy — the pure decision an embedder applies when it reads a
|
|
3
|
-
* data-profile status (see CONTEXT.md "Application service layer"). It decides
|
|
4
|
-
* whether to (re)trigger profiling; the embedder executes the staging + trigger
|
|
5
|
-
* — staging stays embedder-side per the data-profile-init spec, and the atomic running→expired
|
|
6
|
-
* flip stays an SQL guard (`expireStaleDataProfile`), not part of this decision.
|
|
7
|
-
*/
|
|
8
|
-
import type { DataProfileInputSignature, DataProfileLifecycleStatus } from "../contracts/data-profile.js";
|
|
9
|
-
export type { DataProfileLifecycleStatus };
|
|
10
|
-
/**
|
|
11
|
-
* The analysis's inputs as they are NOW. A caller holding sizes and mtimes supplies
|
|
12
|
-
* the signature; one holding only the ledger's seeded ids supplies just those, and the
|
|
13
|
-
* comparison degrades honestly to what those ids can settle.
|
|
14
|
-
*/
|
|
15
|
-
export interface CurrentInputSet {
|
|
16
|
-
readonly fileIds: readonly string[];
|
|
17
|
-
readonly signature?: DataProfileInputSignature;
|
|
18
|
-
}
|
|
19
|
-
/** The comparand a profile snapshot carries. Which one is present depends on its era. */
|
|
20
|
-
export interface ProfiledInputSnapshot {
|
|
21
|
-
readonly inputSignature?: DataProfileInputSignature;
|
|
22
|
-
readonly inputFileIds?: readonly string[];
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* A completed profile is stale when the analysis's current input set no longer matches
|
|
26
|
-
* the set the profile actually covered.
|
|
27
|
-
*
|
|
28
|
-
* The signature is preferred where both sides carry one: it covers size and mtime as
|
|
29
|
-
* well as identity, so an in-place edit is drift too. A snapshot predating the
|
|
30
|
-
* signature falls back to its identity list rather than being treated as drift, since
|
|
31
|
-
* that list answers the same question for the set it covered. A snapshot carrying
|
|
32
|
-
* NEITHER is drift: nothing establishes what it covered, and one re-profile heals it —
|
|
33
|
-
* exactly how a wholly absent `result` is already treated.
|
|
34
|
-
*
|
|
35
|
-
* An empty current set is never stale: there is nothing to compare, and an analysis
|
|
36
|
-
* whose inputs were removed is handled by clearing the profile, not by re-profiling it.
|
|
37
|
-
*/
|
|
38
|
-
export declare function isDataProfileStale(current: CurrentInputSet, profiled: ProfiledInputSnapshot | null | undefined): boolean;
|
|
39
|
-
export type DataProfileAction = {
|
|
40
|
-
readonly kind: "none";
|
|
41
|
-
} | {
|
|
42
|
-
readonly kind: "trigger";
|
|
43
|
-
} | {
|
|
44
|
-
readonly kind: "retrigger";
|
|
45
|
-
};
|
|
46
|
-
export interface DataProfileDecisionInput {
|
|
47
|
-
readonly status: DataProfileLifecycleStatus;
|
|
48
|
-
/** The analysis's inputs as they are now — the seeded ids, plus a signature where the caller has one. */
|
|
49
|
-
readonly current: CurrentInputSet;
|
|
50
|
-
/** The comparand the stored profile carries, or null when it holds none. */
|
|
51
|
-
readonly profiled: ProfiledInputSnapshot | null;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Decide the next profiling action from a (post-expire) status:
|
|
55
|
-
* - `pending` → `trigger` (first profile)
|
|
56
|
-
* - `completed` && stale → `retrigger` (inputs changed since profiling)
|
|
57
|
-
* - anything else → `none`
|
|
58
|
-
*
|
|
59
|
-
* `running` is left to finish (or to the SQL expiry guard); `failed` is retried
|
|
60
|
-
* explicitly via the retry route, never implicitly on read.
|
|
61
|
-
*/
|
|
62
|
-
export declare function decideDataProfileAction(input: DataProfileDecisionInput): DataProfileAction;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Data-profile policy — the pure decision an embedder applies when it reads a
|
|
3
|
-
* data-profile status (see CONTEXT.md "Application service layer"). It decides
|
|
4
|
-
* whether to (re)trigger profiling; the embedder executes the staging + trigger
|
|
5
|
-
* — staging stays embedder-side per the data-profile-init spec, and the atomic running→expired
|
|
6
|
-
* flip stays an SQL guard (`expireStaleDataProfile`), not part of this decision.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* A completed profile is stale when the analysis's current input set no longer matches
|
|
10
|
-
* the set the profile actually covered.
|
|
11
|
-
*
|
|
12
|
-
* The signature is preferred where both sides carry one: it covers size and mtime as
|
|
13
|
-
* well as identity, so an in-place edit is drift too. A snapshot predating the
|
|
14
|
-
* signature falls back to its identity list rather than being treated as drift, since
|
|
15
|
-
* that list answers the same question for the set it covered. A snapshot carrying
|
|
16
|
-
* NEITHER is drift: nothing establishes what it covered, and one re-profile heals it —
|
|
17
|
-
* exactly how a wholly absent `result` is already treated.
|
|
18
|
-
*
|
|
19
|
-
* An empty current set is never stale: there is nothing to compare, and an analysis
|
|
20
|
-
* whose inputs were removed is handled by clearing the profile, not by re-profiling it.
|
|
21
|
-
*/
|
|
22
|
-
export function isDataProfileStale(current, profiled) {
|
|
23
|
-
if (current.fileIds.length === 0 && current.signature === undefined)
|
|
24
|
-
return false;
|
|
25
|
-
if (!profiled)
|
|
26
|
-
return true;
|
|
27
|
-
if (profiled.inputSignature) {
|
|
28
|
-
// A caller with no signature of its own can still compare counts, which catches
|
|
29
|
-
// an added or removed file. It cannot catch a same-count swap, and claiming
|
|
30
|
-
// otherwise would be a verdict the comparand does not support.
|
|
31
|
-
if (!current.signature)
|
|
32
|
-
return profiled.inputSignature.count !== current.fileIds.length;
|
|
33
|
-
return profiled.inputSignature.digest !== current.signature.digest || profiled.inputSignature.count !== current.signature.count;
|
|
34
|
-
}
|
|
35
|
-
if (profiled.inputFileIds) {
|
|
36
|
-
if (current.fileIds.length !== profiled.inputFileIds.length)
|
|
37
|
-
return true;
|
|
38
|
-
const covered = new Set(profiled.inputFileIds);
|
|
39
|
-
return current.fileIds.some((id) => !covered.has(id));
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Decide the next profiling action from a (post-expire) status:
|
|
45
|
-
* - `pending` → `trigger` (first profile)
|
|
46
|
-
* - `completed` && stale → `retrigger` (inputs changed since profiling)
|
|
47
|
-
* - anything else → `none`
|
|
48
|
-
*
|
|
49
|
-
* `running` is left to finish (or to the SQL expiry guard); `failed` is retried
|
|
50
|
-
* explicitly via the retry route, never implicitly on read.
|
|
51
|
-
*/
|
|
52
|
-
export function decideDataProfileAction(input) {
|
|
53
|
-
if (input.status === "pending")
|
|
54
|
-
return { kind: "trigger" };
|
|
55
|
-
if (input.status === "completed" && isDataProfileStale(input.current, input.profiled)) {
|
|
56
|
-
return { kind: "retrigger" };
|
|
57
|
-
}
|
|
58
|
-
return { kind: "none" };
|
|
59
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The MIGRATION renderer: stored AI SDK model messages → `CortexMessage[]`.
|
|
3
|
-
*
|
|
4
|
-
* This is how a conversation written before display projections were persisted
|
|
5
|
-
* is rendered ONCE, at startup, so it can be frozen into a display envelope
|
|
6
|
-
* (`conversation-display-backfill.ts`). It is not a runtime read path — the
|
|
7
|
-
* runtime reads the stored projection (`conversation-display-replay.ts`) and
|
|
8
|
-
* never consults a tool name, a card builder, or the filesystem.
|
|
9
|
-
*
|
|
10
|
-
* Everything it does is inference from a transcript that was never meant to
|
|
11
|
-
* carry display, which is why it belongs to the migration and nothing else:
|
|
12
|
-
*
|
|
13
|
-
* - text part → text part
|
|
14
|
-
* - tool-call part → a reconstructed display card (`data-plan`,
|
|
15
|
-
* `data-presentation`) when `resolveCard` recognises
|
|
16
|
-
* the tool, else a generic tool-call part whose detail
|
|
17
|
-
* `resolveDetail` recomputes from the persisted input
|
|
18
|
-
* - everything else → dropped (reasoning, tool-result, etc.) — the UI
|
|
19
|
-
* does not render them
|
|
20
|
-
*
|
|
21
|
-
* A `tool-result` block is not rendered, but it is not ignored either: it is the
|
|
22
|
-
* only record of how its call ended. The blocks are indexed by `toolCallId`
|
|
23
|
-
* before conversion so each tool-call part can report its own outcome — without
|
|
24
|
-
* that pass every migrated call would report success, including the failed ones.
|
|
25
|
-
*
|
|
26
|
-
* A row that yields no renderable parts (e.g. a `tool`-role message carrying
|
|
27
|
-
* only tool-result continuation parts) is omitted entirely, so the rendered
|
|
28
|
-
* conversation has no empty bubbles. Storage is never mutated.
|
|
29
|
-
*/
|
|
30
|
-
import type { CortexMessage } from "@inflexa-ai/harness/contracts/message.js";
|
|
31
|
-
import type { ToolDetailResolver } from "../tools/detail-resolver.js";
|
|
32
|
-
import type { ToolCardResolver } from "./reconstruct-cards.js";
|
|
33
|
-
import type { StoredMessage } from "./thread-history.js";
|
|
34
|
-
/**
|
|
35
|
-
* The resolvers a caller may supply, as one object.
|
|
36
|
-
*
|
|
37
|
-
* An options bag rather than a growing positional list: both are optional, both
|
|
38
|
-
* are function-typed, and a second positional parameter of the same shape is a
|
|
39
|
-
* call site that silently does the wrong thing when the two are swapped.
|
|
40
|
-
*/
|
|
41
|
-
export interface ContentToCortexOptions {
|
|
42
|
-
/** Rebuilds a display card (`data-plan`, `data-presentation`) from a persisted tool call. */
|
|
43
|
-
readonly resolveCard?: ToolCardResolver;
|
|
44
|
-
/** Rebuilds a call's one-line detail from its tool name and persisted input. */
|
|
45
|
-
readonly resolveDetail?: ToolDetailResolver;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Map stored messages (oldest-first) to `CortexMessage[]`. Rows that produce
|
|
49
|
-
* no renderable parts are dropped, and consecutive assistant rows are coalesced
|
|
50
|
-
* into one message. The first row's `seq` becomes the stable message id.
|
|
51
|
-
* `options.resolveCard` reconstructs display cards from `tool_use` blocks and
|
|
52
|
-
* `options.resolveDetail` reconstructs each call's one-line detail; both are
|
|
53
|
-
* optional and each tool-call part reports its outcome either way.
|
|
54
|
-
* A row carrying the interruption marker sets `interrupted: true` on the message
|
|
55
|
-
* it lands in, including when coalesced into an assistant run; a row carrying a
|
|
56
|
-
* stored turn rollup sets `usage` the same way. A row that carries a stored turn
|
|
57
|
-
* duration sets `durationMs` the same way too. Thus a reloaded conversation shows
|
|
58
|
-
* the cost and the time that the live turn showed, with no second query.
|
|
59
|
-
*
|
|
60
|
-
* Coalescing mirrors the live SSE shape. The agent loop persists each assistant
|
|
61
|
-
* step (typically a single `tool_use`) as its own row, and the `tool_result`
|
|
62
|
-
* `user` rows between them drop to zero renderable parts above — so one assistant
|
|
63
|
-
* turn is stored as a run of adjacent assistant rows. The live chat stream
|
|
64
|
-
* accumulates that whole turn into a single assistant message whose `parts` hold
|
|
65
|
-
* every step; reconstructing it row-by-row instead would emit one message per
|
|
66
|
-
* tool call, which the UI renders as a stack of single-tool boxes. Merging the
|
|
67
|
-
* run restores the one-bubble-per-turn shape (and lets the UI collapse the tool
|
|
68
|
-
* calls into one group) regardless of how the loop split the turn into rows.
|
|
69
|
-
*
|
|
70
|
-
* Coalescing is assistant-only. User rows never legitimately split into a run —
|
|
71
|
-
* adjacent `user` rows arise only from turns that persisted no reply (an aborted
|
|
72
|
-
* turn's lone user message followed by the next turn's), and merging them would
|
|
73
|
-
* fabricate one bubble from two messages the user sent separately.
|
|
74
|
-
*/
|
|
75
|
-
export declare function contentToCortexMessages(messages: readonly StoredMessage[], options?: ContentToCortexOptions): Promise<CortexMessage[]>;
|