@inflexa-ai/harness 0.44.0 → 0.45.1
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 +10 -0
- package/dist/agents/conversation-agent.js +19 -1
- package/dist/agents/sandbox/shared.d.ts +23 -2
- package/dist/agents/sandbox/shared.js +51 -18
- package/dist/agents/sandbox/translational-safety-agent.js +26 -0
- package/dist/agents/sandbox/types.d.ts +24 -1
- package/dist/agents/sandbox-catalog.d.ts +14 -0
- package/dist/agents/sandbox-catalog.js +19 -0
- package/dist/app/approve-plan.d.ts +51 -0
- package/dist/app/approve-plan.js +84 -0
- package/dist/app/chat-turn.js +6 -3
- package/dist/app/data-profile-view.d.ts +1 -1
- package/dist/app/run-activity.js +4 -6
- package/dist/config/environment-stores.d.ts +4 -2
- package/dist/contracts/chat-events.d.ts +14 -2
- package/dist/contracts/chat-events.js +1 -1
- package/dist/contracts/chat-frame.d.ts +7 -0
- package/dist/contracts/chat-frame.js +24 -6
- package/dist/contracts/chat-parts.d.ts +73 -2
- package/dist/contracts/chat-parts.js +3 -1
- package/dist/contracts/index.d.ts +4 -2
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/part-registry.js +3 -0
- package/dist/contracts/schemas/chat-events.d.ts +38 -0
- package/dist/contracts/schemas/chat-events.js +8 -0
- package/dist/contracts/schemas/chat-parts.d.ts +239 -15
- package/dist/contracts/schemas/chat-parts.js +80 -12
- package/dist/contracts/schemas/index.d.ts +2 -2
- package/dist/contracts/schemas/index.js +2 -2
- package/dist/contracts/subagent-activity.d.ts +12 -0
- package/dist/contracts/subagent-activity.js +76 -0
- package/dist/execution/artifact-metadata.js +1 -3
- package/dist/execution/dbos-workflow-purger.d.ts +2 -0
- package/dist/execution/dbos-workflow-purger.js +21 -14
- package/dist/execution/run-canceler.d.ts +33 -11
- package/dist/execution/run-canceler.js +130 -28
- package/dist/execution/run-end-record.d.ts +30 -0
- package/dist/execution/run-end-record.js +27 -0
- package/dist/execution/run-event-parts.d.ts +6 -5
- package/dist/execution/run-event-parts.js +6 -5
- package/dist/execution/run-event-stream.d.ts +18 -6
- package/dist/execution/run-event-stream.js +67 -17
- package/dist/execution/run-synthesis.d.ts +21 -0
- package/dist/execution/run-synthesis.js +77 -3
- package/dist/index.d.ts +28 -10
- package/dist/index.js +26 -2
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/metrics.d.ts +2 -2
- package/dist/loop/call-guard.d.ts +50 -0
- package/dist/loop/call-guard.js +90 -0
- package/dist/loop/compaction.d.ts +5 -0
- package/dist/loop/compaction.js +17 -0
- package/dist/loop/run-agent.d.ts +9 -0
- package/dist/loop/run-agent.js +16 -1
- package/dist/loop/run-to-terminal.js +4 -1
- package/dist/loop/types.d.ts +8 -0
- package/dist/memory/card-builders.d.ts +11 -0
- package/dist/memory/card-builders.js +35 -21
- package/dist/memory/conversation-display-recorder.d.ts +2 -2
- package/dist/memory/conversation-display-recorder.js +38 -4
- package/dist/memory/conversation-display-storage.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +18 -3
- package/dist/memory/thread-history.d.ts +42 -8
- package/dist/memory/thread-history.js +164 -45
- package/dist/memory/thread-store.d.ts +13 -0
- package/dist/memory/thread-store.js +15 -1
- package/dist/prompts/briefing.d.ts +57 -3
- package/dist/prompts/briefing.js +129 -2
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -1
- package/dist/prompts/planner.js +13 -2
- package/dist/provenance/seam.d.ts +39 -20
- package/dist/provenance/seam.js +7 -5
- package/dist/providers/ai-sdk.js +93 -11
- package/dist/providers/types.d.ts +6 -0
- package/dist/reference-data/catalog.js +141 -2
- package/dist/report-model/authoring-grammar.d.ts +26 -26
- package/dist/report-model/pin-snapshot.d.ts +1 -1
- package/dist/report-model/pin-snapshot.js +2 -50
- package/dist/runtime/assemble.d.ts +8 -1
- package/dist/runtime/assemble.js +11 -0
- package/dist/runtime/boot.d.ts +3 -0
- package/dist/runtime/boot.js +10 -1
- package/dist/sandbox/image-packages.d.ts +26 -4
- package/dist/sandbox/image-packages.js +18 -4
- package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
- package/dist/sandbox/sandbox-step-translate.js +24 -0
- package/dist/schemas/artifact-manifest.d.ts +1 -1
- package/dist/schemas/artifact-manifest.js +11 -9
- package/dist/schemas/data-profile-schemas.d.ts +12 -12
- package/dist/schemas/plan-schemas.d.ts +75 -21
- package/dist/schemas/plan-schemas.js +1 -1
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/schemas/workflow-state.d.ts +113 -0
- package/dist/schemas/workflow-state.js +54 -0
- package/dist/state/analyses-metrics.d.ts +79 -7
- package/dist/state/analyses-metrics.js +141 -6
- package/dist/state/artifacts.d.ts +66 -3
- package/dist/state/artifacts.js +85 -7
- package/dist/state/data-profile.d.ts +4 -2
- package/dist/state/data-profile.js +7 -5
- package/dist/state/index.d.ts +9 -8
- package/dist/state/index.js +5 -5
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
- package/dist/state/migrations/index.js +8 -0
- package/dist/state/plans.d.ts +21 -0
- package/dist/state/plans.js +25 -0
- package/dist/state/runs.d.ts +79 -14
- package/dist/state/runs.js +150 -33
- package/dist/state/schema.d.ts +2 -0
- package/dist/state/step-executions.d.ts +9 -0
- package/dist/state/step-executions.js +22 -0
- package/dist/tasks/data-profile-activity.d.ts +7 -1
- package/dist/tasks/data-profile-activity.js +18 -9
- package/dist/tasks/data-profile.d.ts +15 -1
- package/dist/tasks/data-profile.js +16 -11
- package/dist/tools/ad-hoc-router.d.ts +2 -0
- package/dist/tools/ad-hoc-router.js +18 -2
- package/dist/tools/approval/gateway.js +28 -5
- package/dist/tools/capability-data.d.ts +36 -0
- package/dist/tools/capability-data.js +73 -0
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +80 -1
- package/dist/tools/execute-analysis.js +352 -110
- package/dist/tools/knowledge/check.d.ts +60 -0
- package/dist/tools/knowledge/check.js +92 -0
- package/dist/tools/knowledge/client.d.ts +382 -0
- package/dist/tools/knowledge/client.js +302 -0
- package/dist/tools/knowledge/environment.d.ts +78 -0
- package/dist/tools/knowledge/environment.js +105 -0
- package/dist/tools/knowledge/index.d.ts +49 -0
- package/dist/tools/knowledge/index.js +70 -0
- package/dist/tools/knowledge/local-slots.d.ts +62 -0
- package/dist/tools/knowledge/local-slots.js +177 -0
- package/dist/tools/knowledge/pin.d.ts +14 -0
- package/dist/tools/knowledge/pin.js +17 -0
- package/dist/tools/knowledge/recommend.d.ts +92 -0
- package/dist/tools/knowledge/recommend.js +98 -0
- package/dist/tools/knowledge/situation.d.ts +143 -0
- package/dist/tools/knowledge/situation.js +164 -0
- package/dist/tools/knowledge/skeleton.d.ts +69 -0
- package/dist/tools/knowledge/skeleton.js +202 -0
- package/dist/tools/knowledge/template.d.ts +137 -0
- package/dist/tools/knowledge/template.js +267 -0
- package/dist/tools/research/generate-plan.d.ts +23 -0
- package/dist/tools/research/generate-plan.js +203 -12
- package/dist/tools/research/guarded-address.d.ts +20 -0
- package/dist/tools/research/guarded-address.js +148 -0
- package/dist/tools/research/inspect-data-profile.d.ts +1 -1
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/inspect-run.js +7 -7
- package/dist/tools/research/searxng-engine.d.ts +40 -0
- package/dist/tools/research/searxng-engine.js +120 -0
- package/dist/tools/research/web-fetch.d.ts +124 -0
- package/dist/tools/research/web-fetch.js +627 -0
- package/dist/tools/research/web-search.d.ts +75 -0
- package/dist/tools/research/web-search.js +112 -0
- package/dist/tools/sandbox/list-available-packages.d.ts +7 -4
- package/dist/tools/sandbox/list-available-packages.js +12 -4
- package/dist/tools/sandbox/list-available-refs.d.ts +21 -0
- package/dist/tools/sandbox/list-available-refs.js +60 -3
- package/dist/tools/sandbox/report-blocker.js +5 -1
- package/dist/tools/terminal-text.d.ts +11 -0
- package/dist/tools/terminal-text.js +19 -0
- package/dist/tools/workspace/decision-record.d.ts +81 -0
- package/dist/tools/workspace/decision-record.js +117 -0
- package/dist/tools/workspace/edit-file.d.ts +3 -1
- package/dist/tools/workspace/edit-file.js +20 -2
- package/dist/tools/workspace/execute-command.d.ts +17 -0
- package/dist/tools/workspace/execute-command.js +28 -1
- package/dist/tools/workspace/index.d.ts +1 -0
- package/dist/tools/workspace/index.js +1 -0
- package/dist/tools/workspace/mutator.d.ts +1 -1
- package/dist/workflows/execute-analysis.d.ts +62 -21
- package/dist/workflows/execute-analysis.js +307 -79
- package/dist/workflows/plan/graph.d.ts +16 -0
- package/dist/workflows/plan/graph.js +36 -0
- package/dist/workflows/sandbox-step.d.ts +12 -0
- package/dist/workflows/sandbox-step.js +14 -12
- package/package.json +5 -1
|
@@ -3,18 +3,21 @@
|
|
|
3
3
|
* hoc analyses. Both modes launch the ordinary executeAnalysis workflow.
|
|
4
4
|
*/
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { err, ok } from "neverthrow";
|
|
6
|
+
import { err, ok, okAsync, ResultAsync } from "neverthrow";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { capabilityDataRequirement } from "../agents/sandbox-catalog.js";
|
|
8
9
|
import { SANDBOX_AGENT_META } from "../agents/sandbox/index.js";
|
|
9
10
|
import { DEFAULT_SANDBOX_MAX_STEPS } from "../config/resource-limits.js";
|
|
10
11
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
12
|
+
import { readInstalledDatasetIds } from "./sandbox/list-available-refs.js";
|
|
13
|
+
import { tryQuery, withTransaction } from "../lib/db-result.js";
|
|
11
14
|
import { deliverNotice, passGate } from "../lib/hooks.js";
|
|
12
15
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
13
16
|
import { buildRunCardData } from "../memory/card-builders.js";
|
|
14
17
|
import { formatQuery, parseQuery } from "../sandbox/package-identity.js";
|
|
15
18
|
import { AnalysisPlanSchema } from "../schemas/workflow-state.js";
|
|
16
19
|
import { validatePlan } from "../schemas/validate-plan.js";
|
|
17
|
-
import { RunDedupCollisionError, insertRun, loadPlan, queryActiveRun, reserveRunById, suspendAnalysis, updateRunStatus, upsertPlan } from "../state/index.js";
|
|
20
|
+
import { RunDedupCollisionError, insertRun, loadPlan, queryActiveRun, queryNewestPlanRun, reserveRunById, suspendAnalysis, updateRunStatus, upsertPlan, } from "../state/index.js";
|
|
18
21
|
import { routeAdHocRequest } from "./ad-hoc-router.js";
|
|
19
22
|
import { adHocPlanId, adHocRunId } from "./analysis-invocation.js";
|
|
20
23
|
import { defineTool } from "./define-tool.js";
|
|
@@ -92,6 +95,66 @@ export class PlanPackagesMissingError extends Error {
|
|
|
92
95
|
this.name = "PlanPackagesMissingError";
|
|
93
96
|
}
|
|
94
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* A declared dataset that the reference store does not hold, found at the pre-launch pass. The
|
|
100
|
+
* message names the datasets and no remedy command — the remedy belongs to the embedder, and its
|
|
101
|
+
* wrapper appends it.
|
|
102
|
+
*/
|
|
103
|
+
export class PlanReferencesMissingError extends Error {
|
|
104
|
+
missing;
|
|
105
|
+
unavailable;
|
|
106
|
+
constructor(missing, unavailable) {
|
|
107
|
+
const parts = [
|
|
108
|
+
// The store-level reason FIRST, for the same reason the package error leads with it:
|
|
109
|
+
// with an unanswerable store, no per-dataset claim below it is trustworthy.
|
|
110
|
+
unavailable !== undefined
|
|
111
|
+
? `the store cannot answer (${unavailable}) — this says nothing about the datasets themselves, so do not re-request them`
|
|
112
|
+
: null,
|
|
113
|
+
missing.length > 0 ? `the store does not hold: ${missing.join(", ")}` : null,
|
|
114
|
+
].filter((part) => part !== null);
|
|
115
|
+
super(`The reference data of this plan is not installed — ${parts.join(" ")}`);
|
|
116
|
+
this.missing = missing;
|
|
117
|
+
this.unavailable = unavailable;
|
|
118
|
+
this.name = "PlanReferencesMissingError";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the reference data that the capabilities of a plan declare, before the run reserves
|
|
123
|
+
* anything. A step names its capability, the agent of the step holds the declaration, and the
|
|
124
|
+
* declaration names catalog ids. An absent dataset refuses the launch with the ids named.
|
|
125
|
+
*
|
|
126
|
+
* Nothing is read when no step declares a requirement: the common plan touches no store at all.
|
|
127
|
+
*
|
|
128
|
+
* @returns How many datasets reached the store. Zero says that nothing was checked — a plan that
|
|
129
|
+
* declares none, or a store the embedder bound nowhere — which a launch record must be able to tell
|
|
130
|
+
* from a checked launch.
|
|
131
|
+
*/
|
|
132
|
+
async function resolvePlanReferences(refStorePath, plan) {
|
|
133
|
+
const declared = new Set();
|
|
134
|
+
for (const step of plan.steps) {
|
|
135
|
+
if (!step.agent || !step.capability)
|
|
136
|
+
continue;
|
|
137
|
+
for (const dataset of capabilityDataRequirement(step.agent, step.capability))
|
|
138
|
+
declared.add(dataset);
|
|
139
|
+
}
|
|
140
|
+
if (declared.size === 0)
|
|
141
|
+
return ok(0);
|
|
142
|
+
// An unbound store is an embedder that provisions reference data some other way, exactly as an
|
|
143
|
+
// unbound farm seam leaves the package pass at once. Reading the container path here instead
|
|
144
|
+
// would fail on every host and refuse every launch that declares a capability.
|
|
145
|
+
if (refStorePath === undefined)
|
|
146
|
+
return ok(0);
|
|
147
|
+
const installed = await readInstalledDatasetIds({ refStorePath });
|
|
148
|
+
if (installed === undefined) {
|
|
149
|
+
// The path is named, because the reason is a fault of the environment and the reader has to
|
|
150
|
+
// know WHICH store did not answer. A bare "could not be read" sends a caller round in circles.
|
|
151
|
+
return err({ type: "references_missing", missing: [], unavailable: `the reference store at ${refStorePath} could not be read` });
|
|
152
|
+
}
|
|
153
|
+
const missing = [...declared].filter((dataset) => !installed.has(dataset)).sort();
|
|
154
|
+
if (missing.length > 0)
|
|
155
|
+
return err({ type: "references_missing", missing });
|
|
156
|
+
return ok(declared.size);
|
|
157
|
+
}
|
|
95
158
|
/**
|
|
96
159
|
* Link the packages of the plan into the farm of the analysis, before the
|
|
97
160
|
* run reserves anything. The linked set is the union of the queries of each
|
|
@@ -103,9 +166,9 @@ export class PlanPackagesMissingError extends Error {
|
|
|
103
166
|
* is what the launch record must carry to tell a checked launch from an
|
|
104
167
|
* unchecked one.
|
|
105
168
|
*/
|
|
106
|
-
async function linkPlanPackages(extendAnalysisFarm, analysisId, plan) {
|
|
169
|
+
async function linkPlanPackages(extendAnalysisFarm, analysisId, planId, plan) {
|
|
107
170
|
if (!extendAnalysisFarm)
|
|
108
|
-
return 0;
|
|
171
|
+
return ok(0);
|
|
109
172
|
// The union dedupes EQUAL queries only: two entries are one ask when their
|
|
110
173
|
// spelling, their track, and their version are equal. A bare entry beside a
|
|
111
174
|
// qualified entry of one spelling stays two asks, because the bare one
|
|
@@ -118,7 +181,12 @@ async function linkPlanPackages(extendAnalysisFarm, analysisId, plan) {
|
|
|
118
181
|
if (parsed.isErr()) {
|
|
119
182
|
// `validatePlan` parsed every entry of this plan already, thus a
|
|
120
183
|
// refusal here means that the two readers disagree.
|
|
121
|
-
|
|
184
|
+
return err({
|
|
185
|
+
type: "plan_invalid",
|
|
186
|
+
planId,
|
|
187
|
+
malformed: false,
|
|
188
|
+
errors: [`Step "${step.id}" carries the package entry "${entry}", which the plan validation should have refused`],
|
|
189
|
+
});
|
|
122
190
|
}
|
|
123
191
|
const query = parsed.value;
|
|
124
192
|
const key = formatQuery(query);
|
|
@@ -128,12 +196,12 @@ async function linkPlanPackages(extendAnalysisFarm, analysisId, plan) {
|
|
|
128
196
|
}
|
|
129
197
|
const queries = [...union.values()];
|
|
130
198
|
if (queries.length === 0)
|
|
131
|
-
return 0;
|
|
199
|
+
return ok(0);
|
|
132
200
|
const linked = await callExtendAnalysisFarm(extendAnalysisFarm, analysisId, queries);
|
|
133
201
|
if (linked.isErr()) {
|
|
134
202
|
// A call that gave no answer is an `unavailable` answer, not a driver
|
|
135
203
|
// error: the planner can act on "the store cannot answer".
|
|
136
|
-
|
|
204
|
+
return err({ type: "packages_missing", missing: [], collisions: [], unavailable: linked.error });
|
|
137
205
|
}
|
|
138
206
|
const outcomes = linked.value;
|
|
139
207
|
// An `unavailable` outcome preempts everything: the link pass could not
|
|
@@ -141,7 +209,7 @@ async function linkPlanPackages(extendAnalysisFarm, analysisId, plan) {
|
|
|
141
209
|
// absence list would be a fabrication. One reason covers the batch.
|
|
142
210
|
const unavailable = outcomes.find((o) => o.kind === "unavailable");
|
|
143
211
|
if (unavailable !== undefined) {
|
|
144
|
-
|
|
212
|
+
return err({ type: "packages_missing", missing: [], collisions: [], unavailable: unavailable.reason });
|
|
145
213
|
}
|
|
146
214
|
// A seam-supplied detail classifies the miss in host terms (in flight,
|
|
147
215
|
// failed with its reason, never requested), and it rides beside the name
|
|
@@ -165,13 +233,32 @@ async function linkPlanPackages(extendAnalysisFarm, analysisId, plan) {
|
|
|
165
233
|
`; when the two directories are one spelling in two tracks, name the track in the plan, as ` +
|
|
166
234
|
`${formatQuery({ spelling: o.spelling, track: "python" })} or ${formatQuery({ spelling: o.spelling, track: "r" })}`);
|
|
167
235
|
if (missing.length > 0 || collisions.length > 0) {
|
|
168
|
-
|
|
236
|
+
return err({ type: "packages_missing", missing, collisions });
|
|
169
237
|
}
|
|
170
|
-
return queries.length;
|
|
238
|
+
return ok(queries.length);
|
|
171
239
|
}
|
|
172
240
|
function planSummary(plan) {
|
|
173
241
|
return plan.title?.trim() || plan.analytical_narrative.trim().slice(0, 280);
|
|
174
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* The resources of each step. `validatePlan` refuses a step without them, thus
|
|
245
|
+
* a missing entry here means that the two readers disagree.
|
|
246
|
+
*/
|
|
247
|
+
function stepResources(planId, plan) {
|
|
248
|
+
const byStepId = {};
|
|
249
|
+
for (const step of plan.steps) {
|
|
250
|
+
if (!step.resources) {
|
|
251
|
+
return err({
|
|
252
|
+
type: "plan_invalid",
|
|
253
|
+
planId,
|
|
254
|
+
malformed: false,
|
|
255
|
+
errors: [`Step "${step.id}" has no resources — validation should have rejected it`],
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
byStepId[step.id] = step.resources;
|
|
259
|
+
}
|
|
260
|
+
return ok(byStepId);
|
|
261
|
+
}
|
|
175
262
|
function workflowInput(planId, plan, args) {
|
|
176
263
|
return {
|
|
177
264
|
analysisId: args.analysisId,
|
|
@@ -181,11 +268,7 @@ function workflowInput(planId, plan, args) {
|
|
|
181
268
|
steps: plan.steps.map((step) => ({ id: step.id, depends_on: step.depends_on ?? [] })),
|
|
182
269
|
planStepById: Object.fromEntries(plan.steps.map((step) => [step.id, step])),
|
|
183
270
|
agentByStepId: Object.fromEntries(plan.steps.map((step) => [step.id, step.agent ?? "unknown"])),
|
|
184
|
-
resourcesByStepId:
|
|
185
|
-
if (!step.resources)
|
|
186
|
-
throw new Error(`Step "${step.id}" has no resources — validation should have rejected it`);
|
|
187
|
-
return [step.id, step.resources];
|
|
188
|
-
})),
|
|
271
|
+
resourcesByStepId: args.resourcesByStepId,
|
|
189
272
|
timeoutByStepId: Object.fromEntries(plan.steps.filter((step) => step.timeout !== undefined).map((step) => [step.id, step.timeout])),
|
|
190
273
|
budget: args.budget,
|
|
191
274
|
runSession: args.runSession,
|
|
@@ -193,14 +276,21 @@ function workflowInput(planId, plan, args) {
|
|
|
193
276
|
synthesisEnabled: args.synthesisEnabled,
|
|
194
277
|
};
|
|
195
278
|
}
|
|
196
|
-
function
|
|
279
|
+
function parseStoredPlan(raw, planId) {
|
|
197
280
|
const parsed = AnalysisPlanSchema.safeParse(raw);
|
|
198
281
|
if (!parsed.success)
|
|
199
|
-
|
|
282
|
+
return err({ type: "plan_invalid", planId, malformed: true, errors: [] });
|
|
200
283
|
const result = validatePlan(parsed.data);
|
|
201
284
|
if (!result.valid)
|
|
202
|
-
|
|
203
|
-
return parsed.data;
|
|
285
|
+
return err({ type: "plan_invalid", planId, malformed: false, errors: result.errors });
|
|
286
|
+
return ok(parsed.data);
|
|
287
|
+
}
|
|
288
|
+
/** The error that a tool body throws for a stored plan that the parse refused. */
|
|
289
|
+
function validateStoredPlan(raw, planId) {
|
|
290
|
+
const parsed = parseStoredPlan(raw, planId);
|
|
291
|
+
if (parsed.isErr())
|
|
292
|
+
return throwLaunchRefusal(parsed.error, planId);
|
|
293
|
+
return parsed.value;
|
|
204
294
|
}
|
|
205
295
|
function adHocTitle(request) {
|
|
206
296
|
const compact = request.trim().replace(/\s+/g, " ");
|
|
@@ -259,6 +349,7 @@ export function buildAdHocPlan(request, route, createdAt = new Date().toISOStrin
|
|
|
259
349
|
status: "pending",
|
|
260
350
|
resources: route.resources,
|
|
261
351
|
agent: route.agentId,
|
|
352
|
+
...(route.capability ? { capability: route.capability } : {}),
|
|
262
353
|
maxSteps: meta?.defaultMaxSteps ?? DEFAULT_SANDBOX_MAX_STEPS,
|
|
263
354
|
},
|
|
264
355
|
],
|
|
@@ -320,8 +411,227 @@ async function persistedAdHocPlan(deps, args) {
|
|
|
320
411
|
throw new Error(`Internal ad hoc plan ${args.planId} could not be reloaded`);
|
|
321
412
|
return validateStoredPlan(stored, args.planId);
|
|
322
413
|
}
|
|
323
|
-
|
|
414
|
+
/**
|
|
415
|
+
* The half of a launch that the plan mode and the ad hoc mode share: the link pass, the reservation,
|
|
416
|
+
* the authorization, and the workflow start. It gives each outcome as a `Result`.
|
|
417
|
+
*/
|
|
418
|
+
async function launchReservedRun(deps, args) {
|
|
324
419
|
const logger = (deps.logger ?? createNoopLogger()).named("execute-analysis-tool");
|
|
420
|
+
const { session, planId, plan } = args;
|
|
421
|
+
const analysisId = session.scope.analysisId;
|
|
422
|
+
const threadId = session.scope.threadId ?? null;
|
|
423
|
+
const resources = stepResources(planId, plan);
|
|
424
|
+
if (resources.isErr())
|
|
425
|
+
return err(resources.error);
|
|
426
|
+
// Reference data first: it is a read, so it costs nothing to learn before the farm link
|
|
427
|
+
// writes. Both passes come before the run reserves anything, thus a miss of either leaves no
|
|
428
|
+
// run row and no revoked authorization behind.
|
|
429
|
+
const references = await resolvePlanReferences(deps.refStorePath, plan);
|
|
430
|
+
if (references.isErr())
|
|
431
|
+
return err(references.error);
|
|
432
|
+
// The link pass comes before the run reserves anything, thus a pool
|
|
433
|
+
// miss leaves no run row and no revoked authorization behind.
|
|
434
|
+
const linked = await linkPlanPackages(deps.extendAnalysisFarm, analysisId, planId, plan);
|
|
435
|
+
if (linked.isErr())
|
|
436
|
+
return err(linked.error);
|
|
437
|
+
const packageQueries = linked.value;
|
|
438
|
+
const reservation = await args.reserve({ analysisId, threadId });
|
|
439
|
+
if (reservation.isErr())
|
|
440
|
+
return err(reservation.error);
|
|
441
|
+
if (!reservation.value.started)
|
|
442
|
+
return reservation;
|
|
443
|
+
const runId = reservation.value.runId;
|
|
444
|
+
const authorized = await passGate("RunAuthorizer.authorize", deps.runAuthorizer.authorize({
|
|
445
|
+
auth: session.auth,
|
|
446
|
+
scope: session.scope,
|
|
447
|
+
provenance: session.provenance,
|
|
448
|
+
frame: { runId },
|
|
449
|
+
}));
|
|
450
|
+
if (authorized.isErr()) {
|
|
451
|
+
// The refusal ends the run before a workflow exists, thus the
|
|
452
|
+
// reserved row must not stay `running`.
|
|
453
|
+
const { kind, reason } = authorized.error;
|
|
454
|
+
if (kind === "suspended") {
|
|
455
|
+
await updateRunStatus(deps.pool, runId, "suspended_insufficient_funds", reason).match(() => { }, () => { });
|
|
456
|
+
const suspension = await suspendAnalysis(deps.pool, analysisId);
|
|
457
|
+
if (suspension.isErr())
|
|
458
|
+
return err({ type: "store_failed", cause: suspension.error });
|
|
459
|
+
return err({ type: "suspended", reason });
|
|
460
|
+
}
|
|
461
|
+
await updateRunStatus(deps.pool, runId, "failed", reason).match(() => { }, () => { });
|
|
462
|
+
return err({ type: "not_authorized", reason });
|
|
463
|
+
}
|
|
464
|
+
const authorization = authorized.value;
|
|
465
|
+
const wfInput = workflowInput(planId, plan, {
|
|
466
|
+
analysisId,
|
|
467
|
+
threadId,
|
|
468
|
+
runSession: authorization.runSession,
|
|
469
|
+
ownsMandate: authorization.ownsMandate,
|
|
470
|
+
budget: deps.resourcePolicy?.budget,
|
|
471
|
+
synthesisEnabled: args.mode === "plan",
|
|
472
|
+
resourcesByStepId: resources.value,
|
|
473
|
+
});
|
|
474
|
+
try {
|
|
475
|
+
await deps.runLauncher.launch(deps.executeAnalysisWorkflow, { workflowId: runId }, wfInput);
|
|
476
|
+
}
|
|
477
|
+
catch (cause) {
|
|
478
|
+
await deliverNotice(logger, "RunAuthorizer.revoke", deps.runAuthorizer.revoke(authorization, "workflow-start-failed"));
|
|
479
|
+
// A failed write leaves the row `running` with no workflow. The boot repair (`reconcileOrphanedRuns`) fails it.
|
|
480
|
+
await updateRunStatus(deps.pool, runId, "failed", "workflow start failed").match(() => { }, (error) => logger.warn("the failed status of a run whose start failed was not written", { runId, ...logger.errorFields(error) }));
|
|
481
|
+
return err({ type: "launch_failed", cause });
|
|
482
|
+
}
|
|
483
|
+
logger.info("analysis launched", { analysisId, runId, planId, mode: args.mode, packageQueries });
|
|
484
|
+
return ok({ runId, started: true });
|
|
485
|
+
}
|
|
486
|
+
function staleViewOf(newest, seen) {
|
|
487
|
+
if (newest !== null)
|
|
488
|
+
return newest === seen ? null : { type: "newer", runId: newest };
|
|
489
|
+
return seen === null ? null : { type: "missing", runId: seen };
|
|
490
|
+
}
|
|
491
|
+
/** A stale view starts nothing: it gives the newest run with `started: false`, or `run_not_found` when the plan has no run. */
|
|
492
|
+
function refuseStaleView(view) {
|
|
493
|
+
return view.type === "newer" ? ok({ runId: view.runId, started: false }) : err({ type: "run_not_found", runId: view.runId });
|
|
494
|
+
}
|
|
495
|
+
function lockPlanLaunch(client, planId) {
|
|
496
|
+
return tryQuery("execute-analysis.lockPlanLaunch", () => client.query("SELECT pg_advisory_xact_lock(hashtext($1))", [`plan-launch:${planId}`])).map(() => undefined);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Reserve a fresh run for a plan under the lock of the plan, thus the reservations of one plan occur one
|
|
500
|
+
* after the other. A caller with a `newestRunId` reserves nothing when the newest run of the plan is a
|
|
501
|
+
* different run. The new row starts after the newest start, thus it is the newest run of the plan. A
|
|
502
|
+
* concurrent launch of the same plan can hold the active-plan slot, and its run is the answer.
|
|
503
|
+
*/
|
|
504
|
+
async function reservePlanRun(deps, args) {
|
|
505
|
+
const runId = randomUUID();
|
|
506
|
+
try {
|
|
507
|
+
const reserved = await withTransaction(deps.pool, "execute-analysis.reservePlanRun", (client) => lockPlanLaunch(client, args.planId)
|
|
508
|
+
.andThen(() => queryNewestPlanRun(client, { analysisId: args.analysisId, planId: args.planId }))
|
|
509
|
+
.andThen((newest) => {
|
|
510
|
+
const stale = args.newestRunId === undefined ? null : staleViewOf(newest?.runId ?? null, args.newestRunId);
|
|
511
|
+
if (stale)
|
|
512
|
+
return okAsync({ type: "stale", view: stale });
|
|
513
|
+
const startedAt = newest === null ? undefined : new Date(Math.max(Date.now(), Date.parse(newest.startedAt) + 1)).toISOString();
|
|
514
|
+
return insertRun(client, {
|
|
515
|
+
runId,
|
|
516
|
+
analysisId: args.analysisId,
|
|
517
|
+
threadId: args.threadId,
|
|
518
|
+
workflowName: "executeAnalysis",
|
|
519
|
+
planId: args.planId,
|
|
520
|
+
...(startedAt === undefined ? {} : { startedAt }),
|
|
521
|
+
}).map(() => ({ type: "inserted" }));
|
|
522
|
+
}));
|
|
523
|
+
if (reserved.isErr())
|
|
524
|
+
return err({ type: "store_failed", cause: reserved.error });
|
|
525
|
+
if (reserved.value.type === "stale")
|
|
526
|
+
return refuseStaleView(reserved.value.view);
|
|
527
|
+
return ok({ runId, started: true });
|
|
528
|
+
}
|
|
529
|
+
catch (cause) {
|
|
530
|
+
// `insertRun` rejects its promise on a collision of the active-plan index, and on nothing else. The
|
|
531
|
+
// transaction is rolled back and released by now, so the read goes to the pool.
|
|
532
|
+
if (!isDedupCollision(cause))
|
|
533
|
+
return err({ type: "store_failed", cause });
|
|
534
|
+
const active = await queryActiveRun(deps.pool, args.analysisId, args.planId);
|
|
535
|
+
if (active.isErr())
|
|
536
|
+
return err({ type: "store_failed", cause: active.error });
|
|
537
|
+
// The slot was free again when the read ran, so no run holds it and the collision stays the cause.
|
|
538
|
+
if (!active.value)
|
|
539
|
+
return err({ type: "store_failed", cause });
|
|
540
|
+
return ok({ runId: active.value.runId, started: false });
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Reserve the run of an ad hoc call under its deterministic id, and treat a reservation that exists
|
|
545
|
+
* as a redelivery of the same call.
|
|
546
|
+
*/
|
|
547
|
+
async function reserveAdHocRun(deps, args) {
|
|
548
|
+
try {
|
|
549
|
+
const reservation = await reserveRunById(deps.pool, {
|
|
550
|
+
runId: args.runId,
|
|
551
|
+
analysisId: args.analysisId,
|
|
552
|
+
threadId: args.threadId,
|
|
553
|
+
workflowName: "executeAnalysis",
|
|
554
|
+
planId: args.planId,
|
|
555
|
+
});
|
|
556
|
+
if (reservation.isErr())
|
|
557
|
+
return err({ type: "store_failed", cause: reservation.error });
|
|
558
|
+
return ok({ runId: reservation.value.row.runId, started: reservation.value.inserted });
|
|
559
|
+
}
|
|
560
|
+
catch (cause) {
|
|
561
|
+
// `reserveRunById` rejects its promise when the run id belongs to another analysis.
|
|
562
|
+
return err({ type: "store_failed", cause });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async function launchStoredPlan(deps, input) {
|
|
566
|
+
const { session, planId, newestRunId } = input;
|
|
567
|
+
const analysisId = session.scope.analysisId;
|
|
568
|
+
const raw = await loadPlan(deps.pool, planId, { analysisId });
|
|
569
|
+
if (raw.isErr())
|
|
570
|
+
return err({ type: "store_failed", cause: raw.error });
|
|
571
|
+
if (!raw.value)
|
|
572
|
+
return err({ type: "plan_not_found", planId });
|
|
573
|
+
const plan = parseStoredPlan(raw.value, planId);
|
|
574
|
+
if (plan.isErr())
|
|
575
|
+
return err(plan.error);
|
|
576
|
+
if (newestRunId !== undefined) {
|
|
577
|
+
const newest = await queryNewestPlanRun(deps.pool, { analysisId, planId });
|
|
578
|
+
if (newest.isErr())
|
|
579
|
+
return err({ type: "store_failed", cause: newest.error });
|
|
580
|
+
const stale = staleViewOf(newest.value?.runId ?? null, newestRunId);
|
|
581
|
+
if (stale)
|
|
582
|
+
return refuseStaleView(stale);
|
|
583
|
+
}
|
|
584
|
+
const active = await queryActiveRun(deps.pool, analysisId, planId);
|
|
585
|
+
if (active.isErr())
|
|
586
|
+
return err({ type: "store_failed", cause: active.error });
|
|
587
|
+
if (active.value)
|
|
588
|
+
return ok({ runId: active.value.runId, started: false });
|
|
589
|
+
return launchReservedRun(deps, {
|
|
590
|
+
session,
|
|
591
|
+
planId,
|
|
592
|
+
plan: plan.value,
|
|
593
|
+
mode: "plan",
|
|
594
|
+
reserve: (frame) => reservePlanRun(deps, { ...frame, planId, ...(newestRunId === undefined ? {} : { newestRunId }) }),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Start the run of a stored plan. It reads the plan under the analysis of the session, validates it,
|
|
599
|
+
* compares the newest run of the plan with `newestRunId`, links its packages, reserves a run,
|
|
600
|
+
* authorizes it, and starts the workflow. It gives the run id, or the refusal, and it emits no chat
|
|
601
|
+
* part. `started` is false when this call started nothing: a run of the plan was already active, or the
|
|
602
|
+
* newest run of the plan is not the run that the caller saw. `runId` then names that run.
|
|
603
|
+
*
|
|
604
|
+
* This is an API boundary: a host calls it outside a tool body, thus no outcome above rejects the
|
|
605
|
+
* promise.
|
|
606
|
+
*/
|
|
607
|
+
export function launchPlanRun(deps, input) {
|
|
608
|
+
return new ResultAsync(launchStoredPlan(deps, input));
|
|
609
|
+
}
|
|
610
|
+
/** Throw the error that the tool body threw before the launch gave a `Result`. */
|
|
611
|
+
function throwLaunchRefusal(refusal, planId) {
|
|
612
|
+
switch (refusal.type) {
|
|
613
|
+
case "run_not_found":
|
|
614
|
+
throw new Error(`Plan ${planId} has no run ${refusal.runId}`);
|
|
615
|
+
case "plan_not_found":
|
|
616
|
+
throw new PlanNotFoundError(refusal.planId);
|
|
617
|
+
case "plan_invalid":
|
|
618
|
+
throw refusal.malformed ? new PlanMalformedError(refusal.planId) : new PlanValidationError(refusal.planId, refusal.errors);
|
|
619
|
+
case "packages_missing":
|
|
620
|
+
throw new PlanPackagesMissingError(refusal.missing, refusal.collisions, refusal.unavailable);
|
|
621
|
+
case "references_missing":
|
|
622
|
+
throw new PlanReferencesMissingError(refusal.missing, refusal.unavailable);
|
|
623
|
+
case "store_failed":
|
|
624
|
+
return unwrapOrThrow(err(refusal.cause));
|
|
625
|
+
case "launch_failed":
|
|
626
|
+
// The cause is the value that the launcher threw, and the tool rethrows it unchanged.
|
|
627
|
+
throw refusal.cause;
|
|
628
|
+
default: {
|
|
629
|
+
const unreachable = refusal;
|
|
630
|
+
return unreachable;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
export function createExecuteAnalysisTool(deps) {
|
|
325
635
|
return defineTool({
|
|
326
636
|
id: "execute_analysis",
|
|
327
637
|
description: "Launch analysis computation asynchronously. Use mode=plan with the planId only after the user approved that stored plan. " +
|
|
@@ -340,104 +650,36 @@ export function createExecuteAnalysisTool(deps) {
|
|
|
340
650
|
if (!ctx.session.auth)
|
|
341
651
|
throw new Error("execute_analysis: session is missing its auth capability");
|
|
342
652
|
const analysisId = ctx.session.scope.analysisId;
|
|
343
|
-
const threadId = ctx.session.scope.threadId ?? null;
|
|
344
653
|
const planId = input.mode === "plan" ? input.planId : adHocPlanId(analysisId, ctx.invocationId);
|
|
345
654
|
const emitRunCard = async (runId) => {
|
|
346
655
|
const card = await buildRunCardData(deps.pool, { planId, analysisId, runId }).unwrapOr(null);
|
|
347
656
|
if (card)
|
|
348
657
|
await ctx.emit({ type: "data-run-card", source: ctx.session.provenance, data: card });
|
|
349
658
|
};
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (active) {
|
|
358
|
-
await emitRunCard(active.runId);
|
|
359
|
-
return ok({ runId: active.runId, status: "in_progress" });
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
plan = await persistedAdHocPlan(deps, { analysisId, request: input.request, ctx, planId });
|
|
364
|
-
}
|
|
365
|
-
// The link pass comes before the run reserves anything, thus a pool
|
|
366
|
-
// miss leaves no run row and no revoked authorization behind.
|
|
367
|
-
const packageQueries = await linkPlanPackages(deps.extendAnalysisFarm, analysisId, plan);
|
|
368
|
-
const runId = input.mode === "plan" ? randomUUID() : adHocRunId(analysisId, ctx.invocationId);
|
|
369
|
-
if (input.mode === "plan") {
|
|
370
|
-
try {
|
|
371
|
-
unwrapOrThrow(await insertRun(deps.pool, {
|
|
372
|
-
runId,
|
|
373
|
-
analysisId,
|
|
374
|
-
threadId,
|
|
375
|
-
workflowName: "executeAnalysis",
|
|
376
|
-
planId,
|
|
377
|
-
}));
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
if (isDedupCollision(error)) {
|
|
381
|
-
const active = unwrapOrThrow(await queryActiveRun(deps.pool, analysisId, planId));
|
|
382
|
-
if (active) {
|
|
383
|
-
await emitRunCard(active.runId);
|
|
384
|
-
return ok({ runId: active.runId, status: "in_progress" });
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
throw error;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
const reservation = unwrapOrThrow(await reserveRunById(deps.pool, {
|
|
392
|
-
runId,
|
|
393
|
-
analysisId,
|
|
394
|
-
threadId,
|
|
395
|
-
workflowName: "executeAnalysis",
|
|
659
|
+
const launch = async () => {
|
|
660
|
+
if (input.mode === "plan")
|
|
661
|
+
return launchPlanRun(deps, { session: ctx.session, planId });
|
|
662
|
+
const plan = await persistedAdHocPlan(deps, { analysisId, request: input.request, ctx, planId });
|
|
663
|
+
const runId = adHocRunId(analysisId, ctx.invocationId);
|
|
664
|
+
return launchReservedRun(deps, {
|
|
665
|
+
session: ctx.session,
|
|
396
666
|
planId,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// The refusal ends the run before a workflow exists, thus the
|
|
411
|
-
// reserved row must not stay `running`.
|
|
412
|
-
const { kind, reason } = authorized.error;
|
|
413
|
-
if (kind === "suspended") {
|
|
414
|
-
await updateRunStatus(deps.pool, runId, "canceled", reason).match(() => { }, () => { });
|
|
415
|
-
unwrapOrThrow(await suspendAnalysis(deps.pool, analysisId));
|
|
416
|
-
return err({ error: `The analysis is suspended: ${reason}`, retryable: false });
|
|
417
|
-
}
|
|
418
|
-
await updateRunStatus(deps.pool, runId, "failed", reason).match(() => { }, () => { });
|
|
419
|
-
return err({ error: `The run was not authorized: ${reason}`, retryable: false });
|
|
420
|
-
}
|
|
421
|
-
const authorization = authorized.value;
|
|
422
|
-
const wfInput = workflowInput(planId, plan, {
|
|
423
|
-
analysisId,
|
|
424
|
-
threadId,
|
|
425
|
-
runSession: authorization.runSession,
|
|
426
|
-
ownsMandate: authorization.ownsMandate,
|
|
427
|
-
budget: deps.resourcePolicy?.budget,
|
|
428
|
-
synthesisEnabled: input.mode === "plan",
|
|
429
|
-
});
|
|
430
|
-
try {
|
|
431
|
-
await deps.runLauncher.launch(deps.executeAnalysisWorkflow, { workflowId: runId }, wfInput);
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
await deliverNotice(logger, "RunAuthorizer.revoke", deps.runAuthorizer.revoke(authorization, "workflow-start-failed"));
|
|
435
|
-
await updateRunStatus(deps.pool, runId, "failed", "workflow start failed").match(() => { }, () => { });
|
|
436
|
-
throw error;
|
|
667
|
+
plan,
|
|
668
|
+
mode: "adhoc",
|
|
669
|
+
reserve: (frame) => reserveAdHocRun(deps, { ...frame, planId, runId }),
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
const launched = await launch();
|
|
673
|
+
if (launched.isErr()) {
|
|
674
|
+
const refusal = launched.error;
|
|
675
|
+
if (refusal.type === "suspended")
|
|
676
|
+
return err({ error: `The analysis is suspended: ${refusal.reason}`, retryable: false });
|
|
677
|
+
if (refusal.type === "not_authorized")
|
|
678
|
+
return err({ error: `The run was not authorized: ${refusal.reason}`, retryable: false });
|
|
679
|
+
return throwLaunchRefusal(refusal, planId);
|
|
437
680
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
return ok({ runId, status: "in_progress" });
|
|
681
|
+
await emitRunCard(launched.value.runId);
|
|
682
|
+
return ok({ runId: launched.value.runId, status: "in_progress" });
|
|
441
683
|
},
|
|
442
684
|
});
|
|
443
685
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `knowledge_check` — the drafted steps against the rules, once, before
|
|
3
|
+
* `submit_plan`. A violation names the rule and the permitted alternatives.
|
|
4
|
+
* The planner revises once. There is no loop: the host counts the calls of
|
|
5
|
+
* one plan generation, and past `CHECK_CALL_LIMIT` the tool answers with a
|
|
6
|
+
* typed refusal and never reaches the service. A small model that cannot
|
|
7
|
+
* satisfy a finding otherwise rephrases the step until the plan times out.
|
|
8
|
+
*/
|
|
9
|
+
import type { CheckResponse, KnowledgeClient, KnowledgeRejected, KnowledgeSnapshotMismatch, KnowledgeUnavailable } from "./client.js";
|
|
10
|
+
import type { SnapshotPin } from "./pin.js";
|
|
11
|
+
export interface KnowledgeCheckDeps {
|
|
12
|
+
readonly client: KnowledgeClient;
|
|
13
|
+
/** The release the plan pinned on its first recommend answer. The check sends it, thus another release refuses the call. */
|
|
14
|
+
readonly pin?: SnapshotPin;
|
|
15
|
+
}
|
|
16
|
+
export type KnowledgeCheckOutput = CheckResponse | KnowledgeUnavailable | KnowledgeRejected | KnowledgeSnapshotMismatch;
|
|
17
|
+
/** The checks one plan generation may run. Two is a draft and one revision; the third is slack. */
|
|
18
|
+
export declare const CHECK_CALL_LIMIT = 3;
|
|
19
|
+
export declare function createKnowledgeCheckTool(deps: KnowledgeCheckDeps): import("../define-tool.js").Tool<{
|
|
20
|
+
question: "enrichment" | "differential_expression" | "qc" | "full_plan" | "tf_activity" | "deconvolution" | "coexpression" | "clustering" | "survival" | "signature_scoring";
|
|
21
|
+
modality: "bulk_rna_seq";
|
|
22
|
+
data_state: "fastq" | "counts" | "tpm_or_fpkm" | "log_normalized";
|
|
23
|
+
organism: "other" | "human" | "mouse";
|
|
24
|
+
n_groups: number;
|
|
25
|
+
n_per_group_min: number;
|
|
26
|
+
n_per_group_max: number;
|
|
27
|
+
paired: boolean;
|
|
28
|
+
batch: "none" | "known_balanced" | "known_confounded" | "known_unbalanced" | "suspected";
|
|
29
|
+
steps: {
|
|
30
|
+
step_type: "normalize" | "enrichment" | "differential_expression" | "report" | "tf_activity" | "deconvolution" | "coexpression" | "clustering" | "survival" | "signature_scoring" | "variance_partition" | "pathway_activity" | "transcript_level" | "annotation" | "cohort_assembly" | "qc_sample_structure" | "filter_low_counts" | "model_design" | "shrink_lfc" | "multiple_testing";
|
|
31
|
+
method: string;
|
|
32
|
+
method_id?: string | undefined;
|
|
33
|
+
package?: string | undefined;
|
|
34
|
+
parameters?: {
|
|
35
|
+
name: string;
|
|
36
|
+
value: string | number | boolean;
|
|
37
|
+
}[] | undefined;
|
|
38
|
+
outcome?: string | undefined;
|
|
39
|
+
}[];
|
|
40
|
+
count_source?: "unknown" | "salmon" | "kallisto" | "star_featurecounts" | "rsem" | undefined;
|
|
41
|
+
import_state?: "unknown" | "quantifications" | "estimated_counts_with_lengths" | "corrected_counts" | "integer_counts" | undefined;
|
|
42
|
+
n_samples?: number | undefined;
|
|
43
|
+
n_events?: number | undefined;
|
|
44
|
+
blocking_factor?: "other" | "individual" | "cell_line" | "litter" | "site" | "pair" | null | undefined;
|
|
45
|
+
block_structure?: "crossed" | "nested" | undefined;
|
|
46
|
+
covariates?: ("other" | "technical" | "sex" | "age" | "clinical")[] | undefined;
|
|
47
|
+
n_timepoints?: number | null | undefined;
|
|
48
|
+
continuous_predictor?: "other" | "score" | "time" | "age" | "dose" | null | undefined;
|
|
49
|
+
technical_replicates?: boolean | undefined;
|
|
50
|
+
sample_exclusions?: boolean | undefined;
|
|
51
|
+
feature_level?: "gene" | "exon" | "transcript" | undefined;
|
|
52
|
+
library_type?: "unknown" | "total" | "polyA" | "three_prime" | undefined;
|
|
53
|
+
strandedness?: "unknown" | "verified" | "declared_unverified" | undefined;
|
|
54
|
+
interaction?: boolean | undefined;
|
|
55
|
+
classifier?: boolean | undefined;
|
|
56
|
+
extra_analyses?: ("tf_activity" | "deconvolution" | "coexpression" | "clustering" | "survival" | "signature_scoring" | "variance_partition" | "pathway_activity" | "transcript_level" | "annotation")[] | undefined;
|
|
57
|
+
preferred_language?: "R" | "python" | undefined;
|
|
58
|
+
enrichment_input?: "ranked_list" | "gene_list" | "sample_scores" | undefined;
|
|
59
|
+
quality_flags?: ("low_depth_sample" | "outlier_sample" | "sample_identity_doubt" | "high_duplication" | "degraded_rna")[] | undefined;
|
|
60
|
+
}, KnowledgeCheckOutput>;
|