@inflexa-ai/harness 0.23.0 → 0.25.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/README.md +1 -2
- package/dist/agents/conversation-agent.d.ts +0 -9
- package/dist/agents/sandbox/__fixtures__/deps.js +6 -0
- package/dist/agents/sandbox/data-profiler.js +5 -4
- package/dist/agents/sandbox/shared.js +11 -0
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/data-profile-orientation.js +30 -1
- package/dist/app/data-profile-policy.d.ts +33 -7
- package/dist/app/data-profile-policy.js +31 -9
- package/dist/contracts/chat-parts.d.ts +29 -33
- package/dist/contracts/content-url.d.ts +7 -41
- package/dist/contracts/content-url.js +7 -48
- package/dist/contracts/data-profile.d.ts +166 -0
- package/dist/contracts/index.d.ts +3 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/message.d.ts +1 -1
- package/dist/contracts/message.js +1 -1
- package/dist/contracts/part-registry.js +2 -3
- package/dist/contracts/schemas/chat-parts.d.ts +16 -53
- package/dist/contracts/schemas/chat-parts.js +15 -25
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/execution/input-signature.d.ts +33 -0
- package/dist/execution/input-signature.js +0 -0
- package/dist/execution/run-authorizer.d.ts +1 -1
- package/dist/execution/run-authorizer.js +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -2
- package/dist/input-scan/coverage.d.ts +22 -0
- package/dist/input-scan/coverage.js +85 -0
- package/dist/input-scan/enrich.d.ts +41 -0
- package/dist/input-scan/enrich.js +227 -0
- package/dist/input-scan/formats.d.ts +41 -0
- package/dist/input-scan/formats.js +279 -0
- package/dist/input-scan/index.d.ts +12 -0
- package/dist/input-scan/index.js +11 -0
- package/dist/input-scan/scan.d.ts +50 -0
- package/dist/input-scan/scan.js +182 -0
- package/dist/input-scan/shapes.d.ts +46 -0
- package/dist/input-scan/shapes.js +0 -0
- package/dist/input-scan/types.d.ts +172 -0
- package/dist/input-scan/types.js +12 -0
- package/dist/lib/zod-issues.d.ts +2 -3
- package/dist/lib/zod-issues.js +2 -3
- package/dist/loop/__fixtures__/scripted-provider.js +3 -1
- package/dist/loop/run-agent.d.ts +5 -0
- package/dist/loop/run-agent.js +10 -1
- package/dist/loop/run-to-terminal.d.ts +1 -1
- package/dist/loop/run-to-terminal.js +1 -1
- package/dist/memory/card-builders.d.ts +0 -27
- package/dist/memory/card-builders.js +0 -75
- package/dist/memory/conversation-display-backfill.d.ts +2 -4
- package/dist/memory/conversation-display-backfill.js +3 -9
- package/dist/memory/conversation-display-storage.d.ts +3 -4
- package/dist/memory/conversation-display-storage.js +3 -4
- package/dist/memory/reconstruct-cards.d.ts +5 -7
- package/dist/memory/reconstruct-cards.js +6 -20
- package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
- package/dist/prompts/sandbox/data-profiler.js +159 -275
- package/dist/provenance/exec-frame.js +9 -3
- package/dist/runtime/boot.js +2 -12
- package/dist/sandbox/client.d.ts +7 -0
- package/dist/sandbox/create-sandbox.js +1 -0
- package/dist/sandbox/docker-client.d.ts +1 -0
- package/dist/sandbox/docker-client.js +20 -15
- package/dist/sandbox/k8s-client.d.ts +6 -5
- package/dist/sandbox/k8s-client.js +55 -39
- package/dist/sandbox/reaper.d.ts +24 -13
- package/dist/sandbox/reaper.js +47 -14
- package/dist/sandbox/types.d.ts +6 -5
- package/dist/schemas/data-profile-schemas.d.ts +62 -0
- package/dist/schemas/data-profile-schemas.js +68 -2
- package/dist/state/data-profile.d.ts +7 -88
- package/dist/state/index.d.ts +1 -1
- package/dist/tasks/data-profile-activity.d.ts +8 -0
- package/dist/tasks/data-profile-activity.js +1 -0
- package/dist/tasks/data-profile-index.d.ts +45 -0
- package/dist/tasks/data-profile-index.js +134 -0
- package/dist/tasks/data-profile.d.ts +8 -23
- package/dist/tasks/data-profile.js +150 -117
- package/dist/tools/report-session/preview-report.d.ts +2 -1
- package/dist/tools/report-session/preview-report.js +9 -1
- package/dist/tools/research/generate-plan.js +1 -1
- package/dist/tools/research/inspect-data-profile.d.ts +30 -4
- package/dist/tools/research/inspect-data-profile.js +66 -10
- package/dist/tools/start-report-session.js +2 -2
- package/dist/tools/workspace/scan-inputs.d.ts +38 -0
- package/dist/tools/workspace/scan-inputs.js +61 -0
- package/dist/tools/workspace/workspace-search.d.ts +1 -1
- package/dist/tools/workspace/workspace-search.js +12 -9
- package/dist/workspace/filesystem.d.ts +27 -0
- package/dist/workspace/filesystem.js +27 -0
- package/dist/workspace/paths.d.ts +0 -21
- package/dist/workspace/paths.js +0 -37
- package/package.json +1 -3
- package/dist/agents/report-builder.d.ts +0 -34
- package/dist/agents/report-builder.js +0 -34
- package/dist/execution/report-render.d.ts +0 -39
- package/dist/execution/report-render.js +0 -98
- package/dist/execution/report-runner.d.ts +0 -111
- package/dist/execution/report-runner.js +0 -230
- package/dist/prompts/report-builder.d.ts +0 -1
- package/dist/prompts/report-builder.js +0 -174
- package/dist/tools/iterate-report.d.ts +0 -227
- package/dist/tools/iterate-report.js +0 -883
- package/dist/tools/lib/preview-lock.d.ts +0 -20
- package/dist/tools/lib/preview-lock.js +0 -35
- package/dist/tools/lib/report-preflight.d.ts +0 -39
- package/dist/tools/lib/report-preflight.js +0 -283
- package/dist/tools/report/build-report.d.ts +0 -13
- package/dist/tools/report/build-report.js +0 -27
- package/dist/tools/report/index.d.ts +0 -15
- package/dist/tools/report/index.js +0 -15
- package/dist/tools/report/mint-preview-url.d.ts +0 -26
- package/dist/tools/report/mint-preview-url.js +0 -47
- package/dist/tools/report/preview-publisher.d.ts +0 -53
- package/dist/tools/report/preview-publisher.js +0 -45
- package/dist/tools/report/preview-snapshot.d.ts +0 -34
- package/dist/tools/report/preview-snapshot.js +0 -98
- package/dist/tools/report/submit-report.d.ts +0 -26
- package/dist/tools/report/submit-report.js +0 -148
- package/dist/tools/report/version-fs.d.ts +0 -15
- package/dist/tools/report/version-fs.js +0 -165
package/README.md
CHANGED
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
createNoopRunCharge, // RunCharge seam
|
|
37
37
|
createNoopUsageRecorder, // UsageRecorder seam
|
|
38
38
|
createNoopArtifactRegistry, // ArtifactRegistry seam
|
|
39
|
-
UnavailablePreviewPublisher, // PreviewPublisher seam
|
|
40
39
|
createDbosRunLauncher, // shared RunLauncher
|
|
41
40
|
createConsoleLogger, // Logger — writes to console
|
|
42
41
|
createNoopLogger, // Logger — discards (the fallback when none is wired)
|
|
@@ -47,7 +46,7 @@ import {
|
|
|
47
46
|
} from "@inflexa-ai/harness";
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
`assembleCoreRuntime` is the single composition point: it registers the durable workflows and builds the conversation agent over them. The harness declares **
|
|
49
|
+
`assembleCoreRuntime` is the single composition point: it registers the durable workflows and builds the conversation agent over them. The harness declares **five capability seams** an embedder wires — `RunAuthorizer`, `ResolveBilling`, `ArtifactRegistry`, `RunCharge`, `UsageRecorder` — plus the **shared `RunLauncher`** (one host-neutral realization, `createDbosRunLauncher`). Local, dependency-free realizations of all five ship from the barrel; an embedder constructs them (or its own) and passes them in. The harness only ever sees the interface.
|
|
51
50
|
|
|
52
51
|
## Logging
|
|
53
52
|
|
|
@@ -35,7 +35,6 @@ import { type BioToolKeys } from "../tools/bio/keys.js";
|
|
|
35
35
|
import type { EnvironmentStorePaths } from "../config/environment-stores.js";
|
|
36
36
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
37
37
|
import type { RunLauncher } from "../execution/run-launcher.js";
|
|
38
|
-
import type { SubmitReportDeps } from "../tools/iterate-report.js";
|
|
39
38
|
import type { Logger } from "../lib/logger.js";
|
|
40
39
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
41
40
|
import type { CitationResolver } from "../citations/types.js";
|
|
@@ -98,16 +97,8 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
|
|
|
98
97
|
* workflows through it so the durability engine stays out of the tools.
|
|
99
98
|
*/
|
|
100
99
|
readonly runLauncher: RunLauncher;
|
|
101
|
-
/**
|
|
102
|
-
* Preview-publishing seam factory. No tool of the roster reads it. The field
|
|
103
|
-
* stays, because a change to the deps breaks an embedder at its composition
|
|
104
|
-
* root.
|
|
105
|
-
*/
|
|
106
|
-
readonly createPreviewPublisher: SubmitReportDeps["createPreviewPublisher"];
|
|
107
100
|
/** API keys for the external bio/chem data sources. */
|
|
108
101
|
readonly bioKeys: BioToolKeys;
|
|
109
|
-
/** Root templates dir. No tool of the roster reads it. */
|
|
110
|
-
readonly templatesDir: string;
|
|
111
102
|
/** Skills root. No tool of the roster reads it. */
|
|
112
103
|
readonly skillsDir: string;
|
|
113
104
|
/** Headless-Chrome config for report snapshot/preview rendering. */
|
|
@@ -30,6 +30,9 @@ export function makeFakeSandboxClient() {
|
|
|
30
30
|
async isAlive() {
|
|
31
31
|
return { alive: true, oomKilled: false };
|
|
32
32
|
},
|
|
33
|
+
async isAliveById() {
|
|
34
|
+
return { alive: true, oomKilled: false };
|
|
35
|
+
},
|
|
33
36
|
async teardown() { },
|
|
34
37
|
async teardownById() { },
|
|
35
38
|
async listManagedSandboxes() {
|
|
@@ -42,6 +45,9 @@ export function makeFakeWorkspaceFs() {
|
|
|
42
45
|
readFile() {
|
|
43
46
|
return okAsync({ kind: "ok", content: Buffer.alloc(0), truncated: false });
|
|
44
47
|
},
|
|
48
|
+
readBytes() {
|
|
49
|
+
return okAsync({ kind: "ok", bytes: Buffer.alloc(0) });
|
|
50
|
+
},
|
|
45
51
|
list() {
|
|
46
52
|
return okAsync({ kind: "ok", entries: [] });
|
|
47
53
|
},
|
|
@@ -10,10 +10,11 @@ export const meta = {
|
|
|
10
10
|
capabilities: ["data profiling", "literature review", "methodology research", "analysis planning", "experimental design analysis"],
|
|
11
11
|
suitableFor: ["bulk-rna-seq", "single-cell", "proteomics", "metabolomics", "genomics", "transcriptomics", "chemical-structures", "compound-screening"],
|
|
12
12
|
skills: [],
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// `scanInputs` is the profiler's own instrument: the workflow injects one scan into
|
|
14
|
+
// its briefing, and the tool is how it re-scans a subtree it wants to group
|
|
15
|
+
// differently. Declared rather than bolted on, so the resolved roster stays
|
|
16
|
+
// `meta.tools` plus the always-on set.
|
|
17
|
+
tools: [...BASE_SANDBOX_TOOLS, "scanInputs"],
|
|
17
18
|
defaultMaxSteps: 85,
|
|
18
19
|
plannable: false,
|
|
19
20
|
};
|
|
@@ -30,6 +30,7 @@ import { chemblTool, genePreclinicalProfileTool, lookupAnnotationTool, openTarge
|
|
|
30
30
|
import { createNcbiTools, createChemDbTools } from "../../tools/bio/keys.js";
|
|
31
31
|
// Workspace read surface.
|
|
32
32
|
import { createFileStatTool, createGrepTool, createListFilesTool, createReadFileTool, createWorkspaceSearchTool } from "../../tools/workspace/index.js";
|
|
33
|
+
import { createScanInputsTool } from "../../tools/workspace/scan-inputs.js";
|
|
33
34
|
// Workspace mutate surface.
|
|
34
35
|
import { createEditFileTool, createExecuteCommandTool, createWorkspaceMutator, createWriteFileTool } from "../../tools/workspace/index.js";
|
|
35
36
|
// Skills (declared per agent via meta.skills).
|
|
@@ -60,6 +61,16 @@ function resolveSandboxTools(deps, tools) {
|
|
|
60
61
|
const registry = {
|
|
61
62
|
listAvailablePackages: createListAvailablePackagesTool({ ...(deps.packagesFile ? { packagesFile: deps.packagesFile } : {}) }),
|
|
62
63
|
listAvailableRefs: createListAvailableRefsTool({ ...(deps.refStorePath ? { refStorePath: deps.refStorePath } : {}) }),
|
|
64
|
+
scanInputs: createScanInputsTool({
|
|
65
|
+
workspaceFs: deps.workspaceFs,
|
|
66
|
+
analysisId: deps.step.analysisId,
|
|
67
|
+
sandboxClient: deps.sandboxClient,
|
|
68
|
+
sandbox: deps.step.sandbox,
|
|
69
|
+
workflowId: deps.step.workflowId,
|
|
70
|
+
stepId: deps.step.stepId,
|
|
71
|
+
nextFunctionId: deps.step.nextFunctionId,
|
|
72
|
+
deadlineMs: deps.step.deadlineMs,
|
|
73
|
+
}),
|
|
63
74
|
resolveLibraryId: resolveLibraryIdTool,
|
|
64
75
|
queryDocs: queryDocsTool,
|
|
65
76
|
inspectRun: createInspectRunTool(deps.pool),
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* in the tool's `perSource` report; the rest of the call still answers.
|
|
13
13
|
*/
|
|
14
14
|
/** Closed allowlist of tools any sandbox agent may declare. */
|
|
15
|
-
export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "targetSafety" | "comptox";
|
|
15
|
+
export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "scanInputs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "targetSafety" | "comptox";
|
|
16
16
|
/** Planner-facing metadata + tool allowlist for one sandbox agent. */
|
|
17
17
|
export interface AgentMeta {
|
|
18
18
|
/** Stable agent id — also the key in the catalog and the `AgentDefinition.id`. */
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
export const DATA_PROFILE_ORIENTATION_MAX_CHARS = 1200;
|
|
19
19
|
/** Files listed before the tail is elided (the count still reports the total). */
|
|
20
20
|
const MAX_FILES = 8;
|
|
21
|
+
/** Kinds listed before the tail is elided. A dataset with more is described by its first few. */
|
|
22
|
+
const MAX_KINDS = 6;
|
|
23
|
+
const MAX_KIND_DESCRIPTION_CHARS = 90;
|
|
21
24
|
/** Quality concerns listed — the profiler orders them, so these are the top ones. */
|
|
22
25
|
const MAX_CONCERNS = 3;
|
|
23
26
|
const MAX_DESIGN_CHARS = 200;
|
|
@@ -105,9 +108,35 @@ export function buildDataProfileOrientation(result, maxChars = DATA_PROFILE_ORIE
|
|
|
105
108
|
const more = concerns.length > shown.length ? ` (+${concerns.length - shown.length} more)` : "";
|
|
106
109
|
lines.push(`Concerns: ${shown.join("; ")}${more}`);
|
|
107
110
|
}
|
|
111
|
+
// Kinds come first because they are what the dataset IS at this budget: four lines
|
|
112
|
+
// describing 3513 files, where the per-file list could only show the first eight of
|
|
113
|
+
// them and imply the rest away.
|
|
114
|
+
const kinds = result.kinds ?? [];
|
|
115
|
+
if (kinds.length > 0) {
|
|
116
|
+
const shown = kinds.slice(0, MAX_KINDS);
|
|
117
|
+
const datasetFiles = kinds.reduce((sum, kind) => sum + kind.count, 0);
|
|
118
|
+
const header = shown.length < kinds.length
|
|
119
|
+
? `Kinds (${shown.length} of ${kinds.length}, ${datasetFiles} files):`
|
|
120
|
+
: `Kinds (${kinds.length}, ${datasetFiles} files):`;
|
|
121
|
+
lines.push(header);
|
|
122
|
+
for (const kind of shown) {
|
|
123
|
+
const facts = [`${kind.count}x`, present(kind.format)].filter((f) => f !== undefined).join(", ");
|
|
124
|
+
lines.push(`- ${kind.name} (${facts}) — ${clip(kind.memberRepresents, MAX_KIND_DESCRIPTION_CHARS)}`);
|
|
125
|
+
}
|
|
126
|
+
const axes = result.axes ?? [];
|
|
127
|
+
if (axes.length > 0) {
|
|
128
|
+
lines.push(`Axes: ${axes.map((axis) => `${axis.label} (${axis.cardinality})`).join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
if (result.coverage && result.coverage.unmatched > 0) {
|
|
131
|
+
lines.push(`Coverage: ${result.coverage.matched} of ${result.coverage.total} files match a kind`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
108
134
|
if (result.files.length > 0) {
|
|
109
135
|
const shown = result.files.slice(0, MAX_FILES);
|
|
110
|
-
|
|
136
|
+
// With kinds present these are the individually notable files — the metadata
|
|
137
|
+
// sheet, the README, the outlier — not a sample of the dataset.
|
|
138
|
+
const label = kinds.length > 0 ? "Notable files" : "Files";
|
|
139
|
+
const header = shown.length < result.files.length ? `${label} (${shown.length} of ${result.files.length}):` : `${label} (${result.files.length}):`;
|
|
111
140
|
lines.push(header);
|
|
112
141
|
for (const file of shown) {
|
|
113
142
|
lines.push(`- ${file.path} — ${clip(file.description, MAX_FILE_DESCRIPTION_CHARS)}${formatFileFacts(file)}`);
|
|
@@ -5,13 +5,37 @@
|
|
|
5
5
|
* — staging stays embedder-side per the data-profile-init spec, and the atomic running→expired
|
|
6
6
|
* flip stays an SQL guard (`expireStaleDataProfile`), not part of this decision.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
import type { DataProfileInputSignature, DataProfileLifecycleStatus } from "../contracts/data-profile.js";
|
|
9
|
+
export type { DataProfileLifecycleStatus };
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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.
|
|
13
14
|
*/
|
|
14
|
-
export
|
|
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;
|
|
15
39
|
export type DataProfileAction = {
|
|
16
40
|
readonly kind: "none";
|
|
17
41
|
} | {
|
|
@@ -21,8 +45,10 @@ export type DataProfileAction = {
|
|
|
21
45
|
};
|
|
22
46
|
export interface DataProfileDecisionInput {
|
|
23
47
|
readonly status: DataProfileLifecycleStatus;
|
|
24
|
-
|
|
25
|
-
readonly
|
|
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;
|
|
26
52
|
}
|
|
27
53
|
/**
|
|
28
54
|
* Decide the next profiling action from a (post-expire) status:
|
|
@@ -6,17 +6,39 @@
|
|
|
6
6
|
* flip stays an SQL guard (`expireStaleDataProfile`), not part of this decision.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* A completed profile is stale when the analysis's
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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.
|
|
12
21
|
*/
|
|
13
|
-
export function isDataProfileStale(
|
|
14
|
-
if (
|
|
22
|
+
export function isDataProfileStale(current, profiled) {
|
|
23
|
+
if (current.fileIds.length === 0 && current.signature === undefined)
|
|
15
24
|
return false;
|
|
16
|
-
if (
|
|
25
|
+
if (!profiled)
|
|
17
26
|
return true;
|
|
18
|
-
|
|
19
|
-
|
|
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;
|
|
20
42
|
}
|
|
21
43
|
/**
|
|
22
44
|
* Decide the next profiling action from a (post-expire) status:
|
|
@@ -30,7 +52,7 @@ export function isDataProfileStale(seedInputFileIds, profiledInputFileIds) {
|
|
|
30
52
|
export function decideDataProfileAction(input) {
|
|
31
53
|
if (input.status === "pending")
|
|
32
54
|
return { kind: "trigger" };
|
|
33
|
-
if (input.status === "completed" && isDataProfileStale(input.
|
|
55
|
+
if (input.status === "completed" && isDataProfileStale(input.current, input.profiled)) {
|
|
34
56
|
return { kind: "retrigger" };
|
|
35
57
|
}
|
|
36
58
|
return { kind: "none" };
|
|
@@ -331,36 +331,8 @@ export interface RunFailedPart {
|
|
|
331
331
|
/** Machine-readable failure reason (e.g., "budget_exceeded"). */
|
|
332
332
|
reason?: string;
|
|
333
333
|
}
|
|
334
|
-
export interface PreviewPart {
|
|
335
|
-
type: "data-report-preview";
|
|
336
|
-
/** Unique per emission (UUID). */
|
|
337
|
-
id: string;
|
|
338
|
-
/** Groups all versions of the same preview. */
|
|
339
|
-
previewId: string;
|
|
340
|
-
/** Version number (1, 2, 3, ...). */
|
|
341
|
-
version: number;
|
|
342
|
-
/** Report title. */
|
|
343
|
-
title: string;
|
|
344
|
-
/** Version-relative path (e.g., "v1/index.html"). */
|
|
345
|
-
previewPath: string;
|
|
346
|
-
/** Report format. */
|
|
347
|
-
format: "html" | "pdf";
|
|
348
|
-
}
|
|
349
|
-
export interface DataPreviewFailedPart {
|
|
350
|
-
type: "data-report-preview-failed";
|
|
351
|
-
/** Unique per emission (UUID). */
|
|
352
|
-
id: string;
|
|
353
|
-
/** Groups all versions of the same preview. */
|
|
354
|
-
previewId: string;
|
|
355
|
-
/** Attempted version number that failed. */
|
|
356
|
-
version: number;
|
|
357
|
-
/** Human-readable failure reason. */
|
|
358
|
-
reason: string;
|
|
359
|
-
/** Optional taxonomy of failure kinds. */
|
|
360
|
-
errorKind?: "render" | "submit" | "build" | "timeout" | "internal";
|
|
361
|
-
}
|
|
362
334
|
/**
|
|
363
|
-
* The record that a turn started a
|
|
335
|
+
* The record that a turn started a child session. It rides the chat stream
|
|
364
336
|
* live, and it persists into the turn at the position of the spawn.
|
|
365
337
|
*
|
|
366
338
|
* The part is a placement record and a freshness signal only. The thread
|
|
@@ -368,11 +340,35 @@ export interface DataPreviewFailedPart {
|
|
|
368
340
|
* archived state. When the thread of the part is archived or absent, a
|
|
369
341
|
* consumer renders nothing for it.
|
|
370
342
|
*/
|
|
371
|
-
export interface
|
|
372
|
-
type: "data-
|
|
373
|
-
/** The id of the
|
|
343
|
+
export interface ChildSessionStartedPart {
|
|
344
|
+
type: "data-child-session-started";
|
|
345
|
+
/** The id of the child thread that the spawn made. */
|
|
374
346
|
threadId: string;
|
|
375
347
|
/** The id of the conversation thread that spawned it. */
|
|
376
348
|
parentThreadId: string;
|
|
349
|
+
/**
|
|
350
|
+
* The type of the child thread, in the thread store's vocabulary.
|
|
351
|
+
* Known values today: `"report"`.
|
|
352
|
+
*/
|
|
353
|
+
threadType: string;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* The record that a report-session preview rendered the page. It rides the
|
|
357
|
+
* chat stream live, and it persists into the turn at the position of the
|
|
358
|
+
* render.
|
|
359
|
+
*
|
|
360
|
+
* The part is a placement record and a freshness signal only. The version
|
|
361
|
+
* store and the session-page mint stay the authority for what is viewable:
|
|
362
|
+
* the part carries no path, no format, and no URL. A consumer that wants the
|
|
363
|
+
* page reads those surfaces.
|
|
364
|
+
*/
|
|
365
|
+
export interface ReportRenderedPart {
|
|
366
|
+
type: "data-report-rendered";
|
|
367
|
+
/** Unique per emission. */
|
|
368
|
+
id: string;
|
|
369
|
+
/** The ISO timestamp of the successful render. */
|
|
370
|
+
renderedAt: string;
|
|
371
|
+
/** The title of the rendered document. */
|
|
372
|
+
title?: string;
|
|
377
373
|
}
|
|
378
|
-
export type CortexChatPart = PresentationPart | PlanPart | RunCardPart | FileReferencePart | AskPart | RunStartedPart | DagStatePart | StepActivityPart | StepFileTreePart | StepOutputPart | StepSummaryPart | StepUsagePart | StepBlockedPart | RunSynthesisPart | SynthesisProgressPart | RunCompletedPart | RunFailedPart |
|
|
374
|
+
export type CortexChatPart = PresentationPart | PlanPart | RunCardPart | FileReferencePart | AskPart | RunStartedPart | DagStatePart | StepActivityPart | StepFileTreePart | StepOutputPart | StepSummaryPart | StepUsagePart | StepBlockedPart | RunSynthesisPart | SynthesisProgressPart | RunCompletedPart | RunFailedPart | ChildSessionStartedPart | ReportRenderedPart;
|
|
@@ -1,60 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Content URL construction — shared between the frontend and Cortex.
|
|
3
3
|
*
|
|
4
|
-
* The `res` claim
|
|
5
|
-
* for the content-token flow. The storage backend mirrors
|
|
6
|
-
* sides are locked by the shared test
|
|
7
|
-
* `src/__tests__/fixtures/
|
|
8
|
-
* `src/__tests__/fixtures/report-session-res.json` (report-session pages), each a
|
|
4
|
+
* The `res` claim formula and URL shape are the canonical TypeScript contract
|
|
5
|
+
* for the content-token flow. The storage backend mirrors the formula in Go; both
|
|
6
|
+
* sides are locked by the shared test vector at
|
|
7
|
+
* `src/__tests__/fixtures/report-session-res.json` (report-session pages), a
|
|
9
8
|
* byte-identical copy of the storage backend's `kernel/contenttoken/testdata` file.
|
|
10
9
|
*
|
|
11
|
-
* End-to-end contract (previews):
|
|
12
|
-
* token res claim : "previews/{analysisId}/{previewId}"
|
|
13
|
-
* URL : {contentBaseUrl}/previews/{analysisId}/{previewId}/{previewPath}?t={token}
|
|
14
|
-
* Caddy predicate : request.path.startsWith("/" + res + "/")
|
|
15
|
-
*
|
|
16
10
|
* End-to-end contract (report-session pages):
|
|
17
11
|
* token res claim : "report-sessions/{analysisId}/{threadId}" (token kind "rs")
|
|
18
12
|
* URL : {contentBaseUrl}/report-sessions/{analysisId}/{threadId}/{pagePath}?t={token}
|
|
19
13
|
* Caddy predicate : request.path.startsWith("/" + res + "/")
|
|
20
14
|
*
|
|
21
|
-
* This is URL space only. On disk a
|
|
22
|
-
* `{resolveWorkspaceRoot(analysisId)}/
|
|
23
|
-
* spec), and a session page at `{resolveWorkspaceRoot(analysisId)}/report-sessions/{threadId}/{pagePath}`
|
|
15
|
+
* This is URL space only. On disk a session page lives inside its analysis's workspace tree, at
|
|
16
|
+
* `{resolveWorkspaceRoot(analysisId)}/report-sessions/{threadId}/{pagePath}`
|
|
24
17
|
* (the report-session-agent spec) — the `res` claim carries the analysis id because the URL needs an
|
|
25
18
|
* authorization boundary, whereas the resolved root already identifies the analysis and so does not
|
|
26
|
-
* repeat it. A host that serves
|
|
19
|
+
* repeat it. A host that serves the space owns the map between the two.
|
|
27
20
|
*
|
|
28
21
|
* The harness's contracts module owns this TS implementation so both the harness and
|
|
29
22
|
* react-client consume one canonical formula.
|
|
30
23
|
*/
|
|
31
|
-
/**
|
|
32
|
-
* Canonical `res` claim formula for a preview. Returns the literal string
|
|
33
|
-
* `previews/{analysisId}/{previewId}` with no leading or trailing slash.
|
|
34
|
-
*
|
|
35
|
-
* This is the URL sub-path served by the content-server (Caddy) and the token's
|
|
36
|
-
* authorization boundary — NOT a filesystem sub-path; see the module doc.
|
|
37
|
-
*
|
|
38
|
-
* MUST match `fmt.Sprintf("previews/%s/%s", analysisID, previewID)` in
|
|
39
|
-
* the storage backend's Go preview-access implementation. Drift is caught by the shared test
|
|
40
|
-
* vector at `src/__tests__/fixtures/preview-res.json`.
|
|
41
|
-
*/
|
|
42
|
-
export declare function previewResourceId(analysisId: string, previewId: string): string;
|
|
43
|
-
/**
|
|
44
|
-
* Build a content-token preview URL for an iframe src.
|
|
45
|
-
*
|
|
46
|
-
* Produces `{contentBaseUrl}/{res}/{previewPath}?t={token}` where `res` is
|
|
47
|
-
* the output of `previewResourceId(analysisId, previewId)`. The query parameter
|
|
48
|
-
* name is `t` (matching Caddy's `from_query t` directive). The token is
|
|
49
|
-
* URL-encoded.
|
|
50
|
-
*
|
|
51
|
-
* @param contentBaseUrl - Content server base URL (trailing slash tolerated)
|
|
52
|
-
* @param analysisId - The analysis ID (authorization boundary)
|
|
53
|
-
* @param previewId - The preview ID (groups all versions)
|
|
54
|
-
* @param previewPath - Version-relative path (e.g., "v1/index.html"). Leading slash tolerated.
|
|
55
|
-
* @param token - Short-lived content token minted by the storage backend (required)
|
|
56
|
-
*/
|
|
57
|
-
export declare function buildPreviewUrl(contentBaseUrl: string, analysisId: string, previewId: string, previewPath: string, token: string): string;
|
|
58
24
|
/**
|
|
59
25
|
* Canonical `res` claim formula for a report-session page. Returns the literal string
|
|
60
26
|
* `report-sessions/{analysisId}/{threadId}` with no leading or trailing slash.
|
|
@@ -1,67 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Content URL construction — shared between the frontend and Cortex.
|
|
3
3
|
*
|
|
4
|
-
* The `res` claim
|
|
5
|
-
* for the content-token flow. The storage backend mirrors
|
|
6
|
-
* sides are locked by the shared test
|
|
7
|
-
* `src/__tests__/fixtures/
|
|
8
|
-
* `src/__tests__/fixtures/report-session-res.json` (report-session pages), each a
|
|
4
|
+
* The `res` claim formula and URL shape are the canonical TypeScript contract
|
|
5
|
+
* for the content-token flow. The storage backend mirrors the formula in Go; both
|
|
6
|
+
* sides are locked by the shared test vector at
|
|
7
|
+
* `src/__tests__/fixtures/report-session-res.json` (report-session pages), a
|
|
9
8
|
* byte-identical copy of the storage backend's `kernel/contenttoken/testdata` file.
|
|
10
9
|
*
|
|
11
|
-
* End-to-end contract (previews):
|
|
12
|
-
* token res claim : "previews/{analysisId}/{previewId}"
|
|
13
|
-
* URL : {contentBaseUrl}/previews/{analysisId}/{previewId}/{previewPath}?t={token}
|
|
14
|
-
* Caddy predicate : request.path.startsWith("/" + res + "/")
|
|
15
|
-
*
|
|
16
10
|
* End-to-end contract (report-session pages):
|
|
17
11
|
* token res claim : "report-sessions/{analysisId}/{threadId}" (token kind "rs")
|
|
18
12
|
* URL : {contentBaseUrl}/report-sessions/{analysisId}/{threadId}/{pagePath}?t={token}
|
|
19
13
|
* Caddy predicate : request.path.startsWith("/" + res + "/")
|
|
20
14
|
*
|
|
21
|
-
* This is URL space only. On disk a
|
|
22
|
-
* `{resolveWorkspaceRoot(analysisId)}/
|
|
23
|
-
* spec), and a session page at `{resolveWorkspaceRoot(analysisId)}/report-sessions/{threadId}/{pagePath}`
|
|
15
|
+
* This is URL space only. On disk a session page lives inside its analysis's workspace tree, at
|
|
16
|
+
* `{resolveWorkspaceRoot(analysisId)}/report-sessions/{threadId}/{pagePath}`
|
|
24
17
|
* (the report-session-agent spec) — the `res` claim carries the analysis id because the URL needs an
|
|
25
18
|
* authorization boundary, whereas the resolved root already identifies the analysis and so does not
|
|
26
|
-
* repeat it. A host that serves
|
|
19
|
+
* repeat it. A host that serves the space owns the map between the two.
|
|
27
20
|
*
|
|
28
21
|
* The harness's contracts module owns this TS implementation so both the harness and
|
|
29
22
|
* react-client consume one canonical formula.
|
|
30
23
|
*/
|
|
31
|
-
/**
|
|
32
|
-
* Canonical `res` claim formula for a preview. Returns the literal string
|
|
33
|
-
* `previews/{analysisId}/{previewId}` with no leading or trailing slash.
|
|
34
|
-
*
|
|
35
|
-
* This is the URL sub-path served by the content-server (Caddy) and the token's
|
|
36
|
-
* authorization boundary — NOT a filesystem sub-path; see the module doc.
|
|
37
|
-
*
|
|
38
|
-
* MUST match `fmt.Sprintf("previews/%s/%s", analysisID, previewID)` in
|
|
39
|
-
* the storage backend's Go preview-access implementation. Drift is caught by the shared test
|
|
40
|
-
* vector at `src/__tests__/fixtures/preview-res.json`.
|
|
41
|
-
*/
|
|
42
|
-
export function previewResourceId(analysisId, previewId) {
|
|
43
|
-
return `previews/${analysisId}/${previewId}`;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Build a content-token preview URL for an iframe src.
|
|
47
|
-
*
|
|
48
|
-
* Produces `{contentBaseUrl}/{res}/{previewPath}?t={token}` where `res` is
|
|
49
|
-
* the output of `previewResourceId(analysisId, previewId)`. The query parameter
|
|
50
|
-
* name is `t` (matching Caddy's `from_query t` directive). The token is
|
|
51
|
-
* URL-encoded.
|
|
52
|
-
*
|
|
53
|
-
* @param contentBaseUrl - Content server base URL (trailing slash tolerated)
|
|
54
|
-
* @param analysisId - The analysis ID (authorization boundary)
|
|
55
|
-
* @param previewId - The preview ID (groups all versions)
|
|
56
|
-
* @param previewPath - Version-relative path (e.g., "v1/index.html"). Leading slash tolerated.
|
|
57
|
-
* @param token - Short-lived content token minted by the storage backend (required)
|
|
58
|
-
*/
|
|
59
|
-
export function buildPreviewUrl(contentBaseUrl, analysisId, previewId, previewPath, token) {
|
|
60
|
-
const base = contentBaseUrl.replace(/\/+$/, "");
|
|
61
|
-
const path = previewPath.replace(/^\/+/, "");
|
|
62
|
-
const res = previewResourceId(analysisId, previewId);
|
|
63
|
-
return `${base}/${res}/${path}?t=${encodeURIComponent(token)}`;
|
|
64
|
-
}
|
|
65
24
|
/**
|
|
66
25
|
* Canonical `res` claim formula for a report-session page. Returns the literal string
|
|
67
26
|
* `report-sessions/{analysisId}/{threadId}` with no leading or trailing slash.
|