@inflexa-ai/harness 0.31.1 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/conversation-agent.d.ts +15 -0
- package/dist/agents/conversation-agent.js +16 -6
- package/dist/agents/report-session-agent.d.ts +9 -0
- package/dist/agents/report-session-agent.js +10 -2
- package/dist/agents/sandbox/shared.d.ts +13 -1
- package/dist/agents/sandbox/shared.js +22 -5
- package/dist/app/chat-turn.d.ts +11 -0
- package/dist/app/chat-turn.js +17 -1
- package/dist/app/spawn-report-session.d.ts +12 -0
- package/dist/app/spawn-report-session.js +21 -1
- package/dist/config/environment-stores.d.ts +53 -5
- package/dist/contracts/report-blocks.d.ts +8 -8
- package/dist/contracts/schemas/chat-parts.d.ts +6 -6
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -0
- package/dist/prompts/briefing.d.ts +1 -1
- package/dist/prompts/briefing.js +4 -0
- package/dist/prompts/planner.js +13 -0
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +5 -5
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +4 -3
- package/dist/prompts/sandbox-standards.d.ts +20 -0
- package/dist/prompts/sandbox-standards.js +65 -0
- package/dist/provenance/seam.d.ts +203 -0
- package/dist/provenance/seam.js +71 -0
- package/dist/reference-data/catalog.js +11 -9
- package/dist/report-model/authoring-grammar.d.ts +2 -2
- package/dist/report-render/assets.d.ts +10 -0
- package/dist/report-render/assets.js +14 -0
- package/dist/report-render/design.js +256 -0
- package/dist/report-render/page.d.ts +75 -0
- package/dist/report-render/page.js +793 -1
- package/dist/report-render/provenance-data.d.ts +42 -0
- package/dist/report-render/provenance-data.js +62 -0
- package/dist/report-render/render.d.ts +20 -1
- package/dist/report-render/render.js +23 -10
- package/dist/report-render/types.d.ts +3 -0
- package/dist/report-render/views/chart-view.d.ts +5 -1
- package/dist/report-render/views/chart-view.js +6 -2
- package/dist/report-render/views/lineage.d.ts +93 -0
- package/dist/report-render/views/lineage.js +86 -0
- package/dist/report-render/views/page-view.d.ts +22 -1
- package/dist/report-render/views/page-view.js +12 -3
- package/dist/report-render/views/prose.d.ts +7 -1
- package/dist/report-render/views/prose.js +10 -6
- package/dist/report-render/views/references-view.d.ts +17 -1
- package/dist/report-render/views/references-view.js +12 -4
- package/dist/report-render/views/values.d.ts +17 -4
- package/dist/report-render/views/values.js +21 -8
- package/dist/runtime/assemble.d.ts +22 -0
- package/dist/runtime/assemble.js +4 -0
- package/dist/sandbox/create-sandbox.d.ts +20 -4
- package/dist/sandbox/create-sandbox.js +4 -0
- package/dist/sandbox/docker-client.d.ts +16 -6
- package/dist/sandbox/docker-client.js +47 -31
- package/dist/sandbox/farm.d.ts +95 -0
- package/dist/sandbox/farm.js +127 -0
- package/dist/sandbox/k8s-client.d.ts +24 -10
- package/dist/sandbox/k8s-client.js +85 -122
- package/dist/sandbox/mount-plan.d.ts +23 -1
- package/dist/sandbox/mount-plan.js +44 -7
- package/dist/sandbox/sandbox-error.d.ts +12 -0
- package/dist/sandbox/sandbox-error.js +2 -0
- package/dist/sandbox/types.d.ts +118 -0
- package/dist/schemas/artifact-manifest.d.ts +4 -4
- package/dist/schemas/plan-schemas.d.ts +3 -0
- package/dist/schemas/plan-schemas.js +1 -1
- package/dist/schemas/validate-plan.js +20 -1
- package/dist/schemas/workflow-state.d.ts +4 -0
- package/dist/schemas/workflow-state.js +10 -0
- package/dist/tasks/data-profile.d.ts +15 -0
- package/dist/tasks/data-profile.js +36 -26
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +18 -0
- package/dist/tools/execute-analysis.js +87 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +16 -1
- package/dist/tools/report-authoring/authoring-tools.js +58 -16
- package/dist/tools/report-session/derive-table.d.ts +3 -0
- package/dist/tools/report-session/derive-table.js +15 -0
- package/dist/tools/report-session/preview-report.d.ts +13 -4
- package/dist/tools/report-session/preview-report.js +23 -6
- package/dist/tools/report-session/record-version.d.ts +3 -0
- package/dist/tools/report-session/record-version.js +7 -3
- package/dist/tools/research/generate-plan.js +12 -2
- package/dist/tools/sandbox/index.d.ts +1 -0
- package/dist/tools/sandbox/index.js +1 -0
- package/dist/tools/sandbox/link-packages.d.ts +27 -0
- package/dist/tools/sandbox/link-packages.js +52 -0
- package/dist/tools/sandbox/list-available-packages.d.ts +50 -30
- package/dist/tools/sandbox/list-available-packages.js +131 -40
- package/dist/tools/start-report-session.d.ts +7 -0
- package/dist/tools/start-report-session.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +15 -69
- package/dist/workflows/execute-analysis.js +5 -4
- package/package.json +2 -1
|
@@ -79,6 +79,18 @@ export type SandboxError = {
|
|
|
79
79
|
readonly sandboxId?: string;
|
|
80
80
|
readonly status?: number;
|
|
81
81
|
readonly cause: unknown;
|
|
82
|
+
} | {
|
|
83
|
+
/**
|
|
84
|
+
* The farm source refused the analysis: the resolver returned
|
|
85
|
+
* `unavailable`, or it threw. `reason` carries the text of the
|
|
86
|
+
* embedder verbatim, thus the surface can show why the farm is
|
|
87
|
+
* not usable yet.
|
|
88
|
+
*/
|
|
89
|
+
readonly type: "farm_unavailable";
|
|
90
|
+
readonly op: string;
|
|
91
|
+
readonly analysisId: string;
|
|
92
|
+
readonly reason: string;
|
|
93
|
+
readonly cause?: unknown;
|
|
82
94
|
};
|
|
83
95
|
/**
|
|
84
96
|
* The HTTP/driver status a backend SDK error carries. dockerode exposes it on
|
|
@@ -66,6 +66,8 @@ export function describeSandboxError(e) {
|
|
|
66
66
|
return `sandbox teardown failed (${e.op}${e.sandboxId ? `: ${e.sandboxId}` : ""})`;
|
|
67
67
|
case "liveness_failed":
|
|
68
68
|
return `sandbox liveness probe failed (${e.op}${e.sandboxId ? `: ${e.sandboxId}` : ""})`;
|
|
69
|
+
case "farm_unavailable":
|
|
70
|
+
return `farm unavailable (${e.op}: ${e.analysisId}) — ${e.reason}`;
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
/**
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -30,6 +30,124 @@ export declare const SandboxTransportSchema: z.ZodEnum<{
|
|
|
30
30
|
callback: "callback";
|
|
31
31
|
}>;
|
|
32
32
|
export type SandboxTransport = z.infer<typeof SandboxTransportSchema>;
|
|
33
|
+
/**
|
|
34
|
+
* The declared owner of the sandbox toolchain. `"image"` states that the
|
|
35
|
+
* image owns the interpreters, conda, and Node. `"store"` states that the
|
|
36
|
+
* mounted store owns them. An absent config field means `"store"`, thus an
|
|
37
|
+
* old embedder keeps its environment and its prompt prefix. The harness keys
|
|
38
|
+
* the resolver env and the orient-core prompt text on this declared value
|
|
39
|
+
* only, and it never infers its host.
|
|
40
|
+
*/
|
|
41
|
+
export type ToolchainSource = "image" | "store";
|
|
42
|
+
/**
|
|
43
|
+
* A resolved farm. `farmPath` is a host directory for the Docker backend,
|
|
44
|
+
* and a PVC-relative path for the K8s backend. `cachePath` is the optional
|
|
45
|
+
* per-analysis read-write cache. When it is present, the backend mounts it
|
|
46
|
+
* at `/mnt/libs/cache`.
|
|
47
|
+
*/
|
|
48
|
+
export interface FarmLocation {
|
|
49
|
+
readonly farmPath: string;
|
|
50
|
+
readonly cachePath?: string;
|
|
51
|
+
}
|
|
52
|
+
/** The answer of a farm resolver: a usable farm, or a refusal that carries the reason of the embedder. */
|
|
53
|
+
export type FarmResolution = {
|
|
54
|
+
readonly kind: "available";
|
|
55
|
+
readonly location: FarmLocation;
|
|
56
|
+
} | {
|
|
57
|
+
readonly kind: "unavailable";
|
|
58
|
+
readonly reason: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the farm of one analysis. A backend calls this at each
|
|
62
|
+
* `createSandbox`, thus a new farm reaches the next sandbox with no restart.
|
|
63
|
+
*
|
|
64
|
+
* The resolver carries the mount gate when the backend cannot. On a
|
|
65
|
+
* volume-backed farm (K8s) the host cannot stat `farmPath`, thus the
|
|
66
|
+
* resolver MUST answer `unavailable` when the `inflexa.lock` of the farm
|
|
67
|
+
* does not parse. A backend with a host-readable path (Docker) proves the
|
|
68
|
+
* lock itself, after the resolution.
|
|
69
|
+
*/
|
|
70
|
+
export type ResolveAnalysisFarm = (analysisId: string) => Promise<FarmResolution>;
|
|
71
|
+
/**
|
|
72
|
+
* Where the farm of an analysis comes from. A required field of each sandbox
|
|
73
|
+
* backend config — the harness never invents a farm location, and it reads
|
|
74
|
+
* no store-root `current` pointer. `fixed` names one farm for every
|
|
75
|
+
* analysis. `per-analysis` supplies a resolver.
|
|
76
|
+
*/
|
|
77
|
+
export type FarmSource = {
|
|
78
|
+
readonly kind: "fixed";
|
|
79
|
+
readonly location: FarmLocation;
|
|
80
|
+
} | {
|
|
81
|
+
readonly kind: "per-analysis";
|
|
82
|
+
readonly resolve: ResolveAnalysisFarm;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* One package ask of the farm-extension seam. `version` pins one exact
|
|
86
|
+
* version. `ecosystem` qualifies a name that both tracks can hold. Without
|
|
87
|
+
* it, a name that both tracks hold comes back as a `collision` outcome.
|
|
88
|
+
*/
|
|
89
|
+
export interface PackageRequest {
|
|
90
|
+
readonly name: string;
|
|
91
|
+
readonly version?: string;
|
|
92
|
+
readonly ecosystem?: "python" | "r";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* One outcome per request, index-aligned with the request array.
|
|
96
|
+
*
|
|
97
|
+
* `name` echoes the REQUESTED spelling verbatim, never a normalized form. A
|
|
98
|
+
* caller quotes it into a remedy (`inflexa store add <name>`), and an R name
|
|
99
|
+
* is case-sensitive with dots, thus a normalized echo would name a package
|
|
100
|
+
* that no repository holds.
|
|
101
|
+
*
|
|
102
|
+
* - `linked` — the pool held the package, and this call linked it.
|
|
103
|
+
* - `present` — the farm linked it already.
|
|
104
|
+
* - `absent` — the pool does not hold it. `acquisitionPossible` states that
|
|
105
|
+
* the host can acquire that ecosystem, or that it cannot. `detail`, when
|
|
106
|
+
* the realization gives one, classifies the miss in host terms — in
|
|
107
|
+
* flight, failed with a recorded reason, or never requested — and the
|
|
108
|
+
* launch refusal renders it beside the name.
|
|
109
|
+
* - `collision` — the request resolves to two store directories: two
|
|
110
|
+
* versions of one distribution, or one name that both tracks hold. The
|
|
111
|
+
* outcome is terminal for the request. `detail`, when the realization
|
|
112
|
+
* gives one, names the two pins and the packages that need each side —
|
|
113
|
+
* without it, a caller must guess which package pulls each pin, and a
|
|
114
|
+
* wrong guess sends it into store surgery.
|
|
115
|
+
* - `unavailable` — the link pass itself could not answer: an unreadable
|
|
116
|
+
* dependency graph, a locked farm. The reason says why. It says NOTHING
|
|
117
|
+
* about the presence of the package, and it must never render as an
|
|
118
|
+
* absence — a false absence sends a caller chasing packages the pool
|
|
119
|
+
* holds.
|
|
120
|
+
*/
|
|
121
|
+
export type PackageRequestOutcome = {
|
|
122
|
+
readonly kind: "linked";
|
|
123
|
+
readonly name: string;
|
|
124
|
+
readonly version: string;
|
|
125
|
+
} | {
|
|
126
|
+
readonly kind: "present";
|
|
127
|
+
readonly name: string;
|
|
128
|
+
readonly version: string;
|
|
129
|
+
} | {
|
|
130
|
+
readonly kind: "absent";
|
|
131
|
+
readonly name: string;
|
|
132
|
+
readonly acquisitionPossible: boolean;
|
|
133
|
+
readonly detail?: string;
|
|
134
|
+
} | {
|
|
135
|
+
readonly kind: "collision";
|
|
136
|
+
readonly name: string;
|
|
137
|
+
readonly storeDirs: readonly [string, string];
|
|
138
|
+
readonly detail?: string;
|
|
139
|
+
} | {
|
|
140
|
+
readonly kind: "unavailable";
|
|
141
|
+
readonly name: string;
|
|
142
|
+
readonly reason: string;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* The farm-extension seam. The realization of the embedder links host-staged
|
|
146
|
+
* packages into the farm of the analysis. It never installs, downloads, or
|
|
147
|
+
* acquires. A link is live in a sandbox that already runs, because the farm
|
|
148
|
+
* rides a bind mount.
|
|
149
|
+
*/
|
|
150
|
+
export type ExtendAnalysisFarm = (analysisId: string, requests: readonly PackageRequest[]) => Promise<readonly PackageRequestOutcome[]>;
|
|
33
151
|
/**
|
|
34
152
|
* Per-sandbox-machine liveness verdict. `oomKilled` is meaningful only when
|
|
35
153
|
* `alive` is false: true when the backend reports the machine was killed for
|
|
@@ -8,9 +8,9 @@ import { z } from "zod";
|
|
|
8
8
|
/** Artifact type inferred from the step subdirectory. */
|
|
9
9
|
export declare const ArtifactTypeSchema: z.ZodEnum<{
|
|
10
10
|
output: "output";
|
|
11
|
-
script: "script";
|
|
12
|
-
figure: "figure";
|
|
13
11
|
log: "log";
|
|
12
|
+
figure: "figure";
|
|
13
|
+
script: "script";
|
|
14
14
|
notebook: "notebook";
|
|
15
15
|
}>;
|
|
16
16
|
export type ArtifactType = z.infer<typeof ArtifactTypeSchema>;
|
|
@@ -22,9 +22,9 @@ export declare const ArtifactManifestEntrySchema: z.ZodObject<{
|
|
|
22
22
|
size: z.ZodNumber;
|
|
23
23
|
type: z.ZodEnum<{
|
|
24
24
|
output: "output";
|
|
25
|
-
script: "script";
|
|
26
|
-
figure: "figure";
|
|
27
25
|
log: "log";
|
|
26
|
+
figure: "figure";
|
|
27
|
+
script: "script";
|
|
28
28
|
notebook: "notebook";
|
|
29
29
|
}>;
|
|
30
30
|
hash: z.ZodOptional<z.ZodString>;
|
|
@@ -17,6 +17,7 @@ export declare const PlanStepSchema: z.ZodObject<{
|
|
|
17
17
|
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
18
18
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
19
19
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
|
+
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
20
21
|
depends_on: z.ZodArray<z.ZodString>;
|
|
21
22
|
resources: z.ZodNonOptional<z.ZodOptional<z.ZodObject<{
|
|
22
23
|
cpu: z.ZodNumber;
|
|
@@ -49,6 +50,7 @@ export declare const PlannerPlanSchema: z.ZodObject<{
|
|
|
49
50
|
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
50
51
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
51
52
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
53
|
+
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
52
54
|
depends_on: z.ZodArray<z.ZodString>;
|
|
53
55
|
resources: z.ZodNonOptional<z.ZodOptional<z.ZodObject<{
|
|
54
56
|
cpu: z.ZodNumber;
|
|
@@ -90,6 +92,7 @@ export declare const PlanningAgentOutputSchema: z.ZodObject<{
|
|
|
90
92
|
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
91
93
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
92
94
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
95
|
+
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
93
96
|
depends_on: z.ZodArray<z.ZodString>;
|
|
94
97
|
resources: z.ZodNonOptional<z.ZodOptional<z.ZodObject<{
|
|
95
98
|
cpu: z.ZodNumber;
|
|
@@ -17,7 +17,7 @@ export const PlanStepSchema = AnalysisStepSchema.omit({
|
|
|
17
17
|
error: true,
|
|
18
18
|
maxSteps: true,
|
|
19
19
|
})
|
|
20
|
-
.required({ agent: true, resources: true })
|
|
20
|
+
.required({ agent: true, resources: true, packages: true })
|
|
21
21
|
.extend({
|
|
22
22
|
agent: z.enum(plannableAgentIds).describe("Sandbox agent ID, routed by the step's primary data object. Must be one of the available agents."),
|
|
23
23
|
});
|
|
@@ -109,7 +109,26 @@ export function validatePlan(plan, options) {
|
|
|
109
109
|
`step-directory convention; "${SYNTHESIS_STEP_ID}" is the run-level synthesis phase)`);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
// 6.
|
|
112
|
+
// 6. Package entries are requirements, never locations. A path, a URL, or
|
|
113
|
+
// a store directory in a plan would publish an installer detail as an
|
|
114
|
+
// interface — the link pass resolves names against the pool. An absent
|
|
115
|
+
// array passes, because stored plans from before the field carry none.
|
|
116
|
+
// The specifier check exists because the link pass splits on `==` only:
|
|
117
|
+
// an unrefused `numpy>=1.26` becomes a package NAME, and the pool then
|
|
118
|
+
// refuses a package it holds.
|
|
119
|
+
for (const step of plan.steps) {
|
|
120
|
+
for (const entry of step.packages ?? []) {
|
|
121
|
+
if (/^[.~]|[/\\]|:\/\//.test(entry)) {
|
|
122
|
+
errors.push(`Step "${step.id}" names a package location "${entry}" — name each package as a requirement ` +
|
|
123
|
+
`(a bare name, or name==version), never a path, a URL, or a store directory`);
|
|
124
|
+
}
|
|
125
|
+
else if (/[<>!~]/.test(entry) || (entry.includes("=") && !entry.includes("=="))) {
|
|
126
|
+
errors.push(`Step "${step.id}" pins "${entry}" with a specifier the link pass cannot honor — ` +
|
|
127
|
+
`use a bare name, or name==version with one exact version`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// 7. Step-id path safety. The id becomes the `runs/{runId}/{stepId}` directory
|
|
113
132
|
// segment and a `/{analysisId}/…` container mount path; an unsafe segment
|
|
114
133
|
// (a slash, or `.`/`..`) could traverse or widen the mount. The sandbox
|
|
115
134
|
// mount boundary also rejects these (assertSafeId), but catching it here
|
|
@@ -21,6 +21,7 @@ export declare const AnalysisStepSchema: z.ZodObject<{
|
|
|
21
21
|
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
22
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
23
23
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
|
+
packages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
25
|
depends_on: z.ZodArray<z.ZodString>;
|
|
25
26
|
status: z.ZodDefault<z.ZodEnum<{
|
|
26
27
|
pending: "pending";
|
|
@@ -57,6 +58,7 @@ export declare const AnalysisPlanSchema: z.ZodObject<{
|
|
|
57
58
|
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
58
59
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
59
60
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
61
|
+
packages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
60
62
|
depends_on: z.ZodArray<z.ZodString>;
|
|
61
63
|
status: z.ZodDefault<z.ZodEnum<{
|
|
62
64
|
pending: "pending";
|
|
@@ -128,6 +130,7 @@ export declare const ContentBlockSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
128
130
|
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
129
131
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
130
132
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
133
|
+
packages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
131
134
|
depends_on: z.ZodArray<z.ZodString>;
|
|
132
135
|
status: z.ZodDefault<z.ZodEnum<{
|
|
133
136
|
pending: "pending";
|
|
@@ -247,6 +250,7 @@ export declare const SuspensionPayloadSchema: z.ZodObject<{
|
|
|
247
250
|
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
248
251
|
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
249
252
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
253
|
+
packages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
250
254
|
depends_on: z.ZodArray<z.ZodString>;
|
|
251
255
|
status: z.ZodDefault<z.ZodEnum<{
|
|
252
256
|
pending: "pending";
|
|
@@ -24,6 +24,16 @@ export const AnalysisStepSchema = z.object({
|
|
|
24
24
|
constraints: z.array(z.string()).optional().describe("Requirements the sandbox agent must follow exactly — not suggestions."),
|
|
25
25
|
acceptance_criteria: z.array(z.string()).describe("What the result must satisfy for the step to count as successful."),
|
|
26
26
|
caveats: z.array(z.string()).optional().describe("Pitfalls the sandbox agent should watch for."),
|
|
27
|
+
// Optional on the persistence schema so historical plans (pre-packages)
|
|
28
|
+
// still parse on the read side. `PlanStepSchema` re-requires it for
|
|
29
|
+
// planner output. The briefing withholds it from the rendered task — the
|
|
30
|
+
// pre-launch link pass consumes it, and a step agent must not re-litigate it.
|
|
31
|
+
packages: z
|
|
32
|
+
.array(z.string())
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('The packages this step imports, each as a requirement: a bare name ("scanpy") or a name with one exact version ("numpy==1.26.4"). ' +
|
|
35
|
+
"Never a path, a URL, or a store directory. An empty array means the step needs nothing beyond the baked toolchain. " +
|
|
36
|
+
"The set is not a promise of completeness — the execution agent can still link a missing package."),
|
|
27
37
|
depends_on: z
|
|
28
38
|
.array(z.string())
|
|
29
39
|
.describe("IDs of steps in this plan that must complete before this one runs. Empty when the step has no prerequisites. The graph must be acyclic."),
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
import type { Pool } from "pg";
|
|
21
21
|
import { type AuthContext, type RunSession } from "../auth/types.js";
|
|
22
22
|
import type { EnvironmentStorePaths } from "../config/environment-stores.js";
|
|
23
|
+
import type { ExtendAnalysisFarm } from "../sandbox/types.js";
|
|
23
24
|
import type { RunAuthorization, RunAuthorizer } from "../execution/run-authorizer.js";
|
|
24
25
|
import type { StagedInput } from "../execution/staged-input.js";
|
|
26
|
+
import type { SandboxAgentDeps } from "../agents/sandbox/shared.js";
|
|
25
27
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
26
28
|
import type { Logger } from "../lib/logger.js";
|
|
27
29
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
@@ -65,6 +67,13 @@ export interface DataProfileDeps extends EnvironmentStorePaths {
|
|
|
65
67
|
* the pod then carries the harness's own labels only.
|
|
66
68
|
*/
|
|
67
69
|
readonly resolvePodLabels?: (session: RunSession) => Promise<Record<string, string>>;
|
|
70
|
+
/**
|
|
71
|
+
* The farm-extension seam of the embedder. Bound, it rides into the
|
|
72
|
+
* profiler agent deps, and the substrate attaches `link_packages`. Thus
|
|
73
|
+
* the profiler links a reader by need before the first plan exists.
|
|
74
|
+
* Unbound keeps the current shape: no link tool, and no error.
|
|
75
|
+
*/
|
|
76
|
+
readonly extendAnalysisFarm?: ExtendAnalysisFarm;
|
|
68
77
|
}
|
|
69
78
|
/**
|
|
70
79
|
* Workflow input. JSON-serialisable — DBOS persists it as the workflow's
|
|
@@ -125,6 +134,12 @@ export declare function buildAbsorbedResult(prior: DataProfileResult, resolution
|
|
|
125
134
|
* Register the data-profile workflow with DBOS. Returns the registered
|
|
126
135
|
* callable so `triggerDataProfile` can dispatch via `DBOS.startWorkflow`.
|
|
127
136
|
*/
|
|
137
|
+
/**
|
|
138
|
+
* Project the workflow deps onto the profiler agent bag. The farm-extension
|
|
139
|
+
* seam rides only when the embedder bound one, and the substrate then gates
|
|
140
|
+
* `link_packages` on its presence (the data-profile-init spec).
|
|
141
|
+
*/
|
|
142
|
+
export declare function profilerSandboxAgentDeps(deps: DataProfileDeps, step: SandboxAgentDeps["step"]): SandboxAgentDeps;
|
|
128
143
|
export declare function registerDataProfileWorkflow(deps: DataProfileDeps): (input: DataProfileWorkflowInput) => Promise<void>;
|
|
129
144
|
/**
|
|
130
145
|
* Body extracted so tests can drive it without registering a workflow.
|
|
@@ -247,6 +247,28 @@ function logProfileMonitoring(logger, args) {
|
|
|
247
247
|
* Register the data-profile workflow with DBOS. Returns the registered
|
|
248
248
|
* callable so `triggerDataProfile` can dispatch via `DBOS.startWorkflow`.
|
|
249
249
|
*/
|
|
250
|
+
/**
|
|
251
|
+
* Project the workflow deps onto the profiler agent bag. The farm-extension
|
|
252
|
+
* seam rides only when the embedder bound one, and the substrate then gates
|
|
253
|
+
* `link_packages` on its presence (the data-profile-init spec).
|
|
254
|
+
*/
|
|
255
|
+
export function profilerSandboxAgentDeps(deps, step) {
|
|
256
|
+
return {
|
|
257
|
+
provider: deps.provider,
|
|
258
|
+
pool: deps.pool,
|
|
259
|
+
sandboxClient: deps.sandboxClient,
|
|
260
|
+
workspaceFs: deps.workspaceFs,
|
|
261
|
+
embedding: deps.embedding,
|
|
262
|
+
model: deps.model,
|
|
263
|
+
skillsDir: deps.skillsDir,
|
|
264
|
+
...(deps.farmLockFile ? { farmLockFile: deps.farmLockFile } : {}),
|
|
265
|
+
...(deps.imagePackagesFile ? { imagePackagesFile: deps.imagePackagesFile } : {}),
|
|
266
|
+
...(deps.refStorePath ? { refStorePath: deps.refStorePath } : {}),
|
|
267
|
+
...(deps.extendAnalysisFarm ? { extendAnalysisFarm: deps.extendAnalysisFarm } : {}),
|
|
268
|
+
bioKeys: deps.bioKeys,
|
|
269
|
+
step,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
250
272
|
export function registerDataProfileWorkflow(deps) {
|
|
251
273
|
return DBOS.registerWorkflow((input) => runDataProfileBody(input, deps), { name: "data-profile" });
|
|
252
274
|
}
|
|
@@ -454,32 +476,20 @@ export async function runDataProfileBody(input, deps) {
|
|
|
454
476
|
`description needs content the scan did not capture, then call \`submit_profile\` once.`,
|
|
455
477
|
]),
|
|
456
478
|
].join("\n");
|
|
457
|
-
const sandboxAgentDeps = {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
analysisId,
|
|
472
|
-
runId: DATA_PROFILE_RUN_LITERAL,
|
|
473
|
-
stepId: DATA_PROFILE_STEP_LITERAL,
|
|
474
|
-
workflowId,
|
|
475
|
-
// The profiler writes Python scripts and intermediate artifacts under
|
|
476
|
-
// the synthetic step path; the post-agent `rm -rf runs/data-profile/`
|
|
477
|
-
// cleanup wipes them.
|
|
478
|
-
allowedWritePrefix: profileWritePrefix,
|
|
479
|
-
nextFunctionId,
|
|
480
|
-
deadlineMs: () => deadlineAbs,
|
|
481
|
-
},
|
|
482
|
-
};
|
|
479
|
+
const sandboxAgentDeps = profilerSandboxAgentDeps(deps, {
|
|
480
|
+
sandbox,
|
|
481
|
+
workspaceRoot,
|
|
482
|
+
analysisId,
|
|
483
|
+
runId: DATA_PROFILE_RUN_LITERAL,
|
|
484
|
+
stepId: DATA_PROFILE_STEP_LITERAL,
|
|
485
|
+
workflowId,
|
|
486
|
+
// The profiler writes Python scripts and intermediate artifacts under
|
|
487
|
+
// the synthetic step path; the post-agent `rm -rf runs/data-profile/`
|
|
488
|
+
// cleanup wipes them.
|
|
489
|
+
allowedWritePrefix: profileWritePrefix,
|
|
490
|
+
nextFunctionId,
|
|
491
|
+
deadlineMs: () => deadlineAbs,
|
|
492
|
+
});
|
|
483
493
|
let accepted = null;
|
|
484
494
|
let rounds = 0;
|
|
485
495
|
const submitProfileTool = defineTool({
|
|
@@ -23,7 +23,7 @@ type ShowUserOutput = {
|
|
|
23
23
|
id: string;
|
|
24
24
|
};
|
|
25
25
|
export declare const showUserTool: import("../define-tool.js").Tool<{
|
|
26
|
-
kind: "code" | "markdown" | "
|
|
26
|
+
kind: "code" | "markdown" | "table" | "echart" | "svg";
|
|
27
27
|
title?: string | undefined;
|
|
28
28
|
spec?: Record<string, unknown> | undefined;
|
|
29
29
|
dataPath?: string | undefined;
|
|
@@ -8,6 +8,7 @@ import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
|
8
8
|
import type { RunLauncher } from "../execution/run-launcher.js";
|
|
9
9
|
import type { Logger } from "../lib/logger.js";
|
|
10
10
|
import type { ChatProvider } from "../providers/types.js";
|
|
11
|
+
import type { ExtendAnalysisFarm } from "../sandbox/types.js";
|
|
11
12
|
import { type AnalysisPlan } from "../schemas/workflow-state.js";
|
|
12
13
|
import type { ExecuteAnalysisInput, ExecuteAnalysisResult } from "../workflows/execute-analysis.js";
|
|
13
14
|
import { type AdHocRoute } from "./ad-hoc-router.js";
|
|
@@ -21,6 +22,12 @@ export interface ExecuteAnalysisToolDeps {
|
|
|
21
22
|
readonly utilityProvider: ChatProvider;
|
|
22
23
|
readonly utilityModel: string;
|
|
23
24
|
readonly logger?: Logger;
|
|
25
|
+
/**
|
|
26
|
+
* The farm-extension seam of the embedder. When it is bound, the launch
|
|
27
|
+
* links the packages of the plan into the farm of the analysis before the
|
|
28
|
+
* run reserves anything. Without it, the link pass returns at once.
|
|
29
|
+
*/
|
|
30
|
+
readonly extendAnalysisFarm?: ExtendAnalysisFarm;
|
|
24
31
|
}
|
|
25
32
|
export declare class PlanNotFoundError extends Error {
|
|
26
33
|
constructor(planId: string);
|
|
@@ -32,6 +39,17 @@ export declare class PlanValidationError extends Error {
|
|
|
32
39
|
readonly errors: string[];
|
|
33
40
|
constructor(planId: string, errors: string[]);
|
|
34
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* A pool miss at the pre-launch link pass. The message names the missing
|
|
44
|
+
* packages and no remedy command — the remedy belongs to the embedder, and
|
|
45
|
+
* its wrapper appends it.
|
|
46
|
+
*/
|
|
47
|
+
export declare class PlanPackagesMissingError extends Error {
|
|
48
|
+
readonly missing: string[];
|
|
49
|
+
readonly collisions: string[];
|
|
50
|
+
readonly unavailable?: string | undefined;
|
|
51
|
+
constructor(missing: string[], collisions: string[], unavailable?: string | undefined);
|
|
52
|
+
}
|
|
35
53
|
export declare function buildAdHocPlan(request: string, route: AdHocRoute, createdAt?: string): AnalysisPlan;
|
|
36
54
|
export declare function createExecuteAnalysisTool(deps: ExecuteAnalysisToolDeps): import("./define-tool.js").Tool<{
|
|
37
55
|
mode: "plan" | "adhoc";
|
|
@@ -54,6 +54,90 @@ export class PlanValidationError extends Error {
|
|
|
54
54
|
function isDedupCollision(err) {
|
|
55
55
|
return err instanceof RunDedupCollisionError || (err instanceof Error && err.name === "RunDedupCollisionError");
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* A pool miss at the pre-launch link pass. The message names the missing
|
|
59
|
+
* packages and no remedy command — the remedy belongs to the embedder, and
|
|
60
|
+
* its wrapper appends it.
|
|
61
|
+
*/
|
|
62
|
+
export class PlanPackagesMissingError extends Error {
|
|
63
|
+
missing;
|
|
64
|
+
collisions;
|
|
65
|
+
unavailable;
|
|
66
|
+
constructor(missing, collisions, unavailable) {
|
|
67
|
+
const parts = [
|
|
68
|
+
// The store-level reason FIRST, because it invalidates the rest: with an
|
|
69
|
+
// unreadable graph, no per-package claim below it is trustworthy.
|
|
70
|
+
unavailable !== undefined
|
|
71
|
+
? `the store cannot answer (${unavailable}) — this says nothing about the packages themselves, so do not re-request them`
|
|
72
|
+
: null,
|
|
73
|
+
missing.length > 0 ? `the pool does not hold: ${missing.join(", ")}` : null,
|
|
74
|
+
collisions.length > 0 ? `a collision refused: ${collisions.join(", ")}` : null,
|
|
75
|
+
].filter((p) => p !== null);
|
|
76
|
+
super(`The packages of this plan cannot link — ${parts.join("; ")}`);
|
|
77
|
+
this.missing = missing;
|
|
78
|
+
this.collisions = collisions;
|
|
79
|
+
this.unavailable = unavailable;
|
|
80
|
+
this.name = "PlanPackagesMissingError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** One plan package entry, in requirement form: a bare name, or a name with one exact version. */
|
|
84
|
+
function parseRequirement(entry) {
|
|
85
|
+
const split = entry.indexOf("==");
|
|
86
|
+
if (split < 0)
|
|
87
|
+
return { name: entry.trim() };
|
|
88
|
+
return { name: entry.slice(0, split).trim(), version: entry.slice(split + 2).trim() };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Link the packages of the plan into the farm of the analysis, before the
|
|
92
|
+
* run reserves anything. The linked set is the union of the packages of each
|
|
93
|
+
* step. A pool miss and a collision refuse the launch with the names. Without
|
|
94
|
+
* a bound seam, the pass returns at once.
|
|
95
|
+
*/
|
|
96
|
+
async function linkPlanPackages(extendAnalysisFarm, analysisId, plan) {
|
|
97
|
+
if (!extendAnalysisFarm)
|
|
98
|
+
return;
|
|
99
|
+
const union = new Map();
|
|
100
|
+
for (const step of plan.steps) {
|
|
101
|
+
for (const entry of step.packages ?? []) {
|
|
102
|
+
// The FIRST spelling of the plan rides the request. An outcome
|
|
103
|
+
// echoes the requested spelling, and a last-writer fold could
|
|
104
|
+
// echo `seurat` where the plan said `Seurat` — a remedy in that
|
|
105
|
+
// case names a package that no repository holds.
|
|
106
|
+
const key = entry.trim().toLowerCase();
|
|
107
|
+
if (!union.has(key))
|
|
108
|
+
union.set(key, parseRequirement(entry));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (union.size === 0)
|
|
112
|
+
return;
|
|
113
|
+
let outcomes;
|
|
114
|
+
try {
|
|
115
|
+
outcomes = await extendAnalysisFarm(analysisId, [...union.values()]);
|
|
116
|
+
}
|
|
117
|
+
catch (cause) {
|
|
118
|
+
// A realization throw is an `unavailable` answer, not a driver error.
|
|
119
|
+
// The sibling resolver seam maps a throw the same way (farm.ts), and
|
|
120
|
+
// the planner can act on "the store cannot answer" — not on a stack.
|
|
121
|
+
throw new PlanPackagesMissingError([], [], cause instanceof Error ? cause.message : String(cause));
|
|
122
|
+
}
|
|
123
|
+
// An `unavailable` outcome preempts everything: the link pass could not
|
|
124
|
+
// answer at all (an unreadable graph, a locked farm), so a per-package
|
|
125
|
+
// absence list would be a fabrication. One reason covers the batch.
|
|
126
|
+
const unavailable = outcomes.find((o) => o.kind === "unavailable");
|
|
127
|
+
if (unavailable !== undefined) {
|
|
128
|
+
throw new PlanPackagesMissingError([], [], unavailable.reason);
|
|
129
|
+
}
|
|
130
|
+
// A seam-supplied detail classifies the miss in host terms (in flight,
|
|
131
|
+
// failed with its reason, never requested), and it rides beside the name
|
|
132
|
+
// so the agent replans from the true state instead of re-asking. A
|
|
133
|
+
// collision's detail names the two pins and what needs each side — the
|
|
134
|
+
// remedy is to drop or re-pin a DEPENDENT, so the refusal must name it.
|
|
135
|
+
const missing = outcomes.filter((o) => o.kind === "absent").map((o) => (o.detail === undefined ? o.name : `${o.name} — ${o.detail}`));
|
|
136
|
+
const collisions = outcomes.filter((o) => o.kind === "collision").map((o) => (o.detail === undefined ? o.name : `${o.name} — ${o.detail}`));
|
|
137
|
+
if (missing.length > 0 || collisions.length > 0) {
|
|
138
|
+
throw new PlanPackagesMissingError(missing, collisions);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
57
141
|
function planSummary(plan) {
|
|
58
142
|
return plan.title?.trim() || plan.analytical_narrative.trim().slice(0, 280);
|
|
59
143
|
}
|
|
@@ -183,6 +267,9 @@ export function createExecuteAnalysisTool(deps) {
|
|
|
183
267
|
else {
|
|
184
268
|
plan = await persistedAdHocPlan(deps, { analysisId, request: input.request, ctx, planId });
|
|
185
269
|
}
|
|
270
|
+
// The link pass comes before the run reserves anything, thus a pool
|
|
271
|
+
// miss leaves no run row and no revoked authorization behind.
|
|
272
|
+
await linkPlanPackages(deps.extendAnalysisFarm, analysisId, plan);
|
|
186
273
|
const runId = input.mode === "plan" ? randomUUID() : adHocRunId(analysisId, ctx.invocationId);
|
|
187
274
|
if (input.mode === "plan") {
|
|
188
275
|
try {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
import { type Result } from "neverthrow";
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import type { Scope } from "../../auth/types.js";
|
|
22
|
+
import type { Logger } from "../../lib/logger.js";
|
|
23
|
+
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
22
24
|
import { type DraftRefusal } from "../../report-model/draft-operations.js";
|
|
23
25
|
import { type OutlineEntry, type ReadableBlock } from "../../report-model/draft-read.js";
|
|
24
26
|
import { type FinishResult } from "../../report-model/draft-finish.js";
|
|
@@ -289,12 +291,25 @@ export interface ReportAuthoringTools {
|
|
|
289
291
|
readonly read_block: Tool<ReadBlockInput, ReadBlockResult>;
|
|
290
292
|
readonly finish_draft: Tool<FinishDraftInput, FinishToolResult>;
|
|
291
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* The optional deps of the authoring surface. The gateway is the one mandatory part, thus it stays a
|
|
296
|
+
* parameter of its own and this bag holds what a composition can leave out.
|
|
297
|
+
*/
|
|
298
|
+
export interface ReportAuthoringToolDeps {
|
|
299
|
+
/** The provenance seam; an unbound session emit emits nothing and each tool acts the same. */
|
|
300
|
+
readonly provenance?: ProvenanceSeam;
|
|
301
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
302
|
+
readonly logger?: Logger;
|
|
303
|
+
}
|
|
292
304
|
/**
|
|
293
305
|
* Make the eight authoring tools over a session-state gateway.
|
|
294
306
|
*
|
|
295
307
|
* Each tool reads the thread id from the scope of the call, and it loads the state of that thread through
|
|
296
308
|
* the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
|
|
297
309
|
* persists the new document before it reports `applied: true`, thus a reported landing is never lost.
|
|
310
|
+
*
|
|
311
|
+
* A landed mutation emits one observation. The emit sits after the persist, thus a refused operation and a
|
|
312
|
+
* failed persist each emit nothing.
|
|
298
313
|
*/
|
|
299
|
-
export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway): ReportAuthoringTools;
|
|
314
|
+
export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway, deps?: ReportAuthoringToolDeps): ReportAuthoringTools;
|
|
300
315
|
export {};
|