@inflexa-ai/harness 0.48.3 → 0.48.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/conversation-agent.js +5 -1
- package/dist/agents/sandbox/index.js +3 -0
- package/dist/agents/sandbox/mdv-preparation-agent.d.ts +9 -0
- package/dist/agents/sandbox/mdv-preparation-agent.js +37 -0
- package/dist/agents/sandbox/network-agent.js +1 -1
- package/dist/agents/sandbox/shared.js +3 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn-workflow.js +1 -0
- package/dist/app/chat-turn.d.ts +4 -2
- package/dist/app/chat-turn.js +46 -46
- package/dist/app/work.d.ts +7 -3
- package/dist/app/work.js +17 -16
- package/dist/contracts/chat-parts.d.ts +46 -5
- package/dist/contracts/chat-parts.js +2 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/part-registry.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +168 -4
- package/dist/contracts/schemas/chat-parts.js +21 -1
- package/dist/execution/run-event-stream.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/lib/regulatory-corpus/fetch-ich.js +1 -16
- package/dist/lib/slug.d.ts +18 -0
- package/dist/lib/slug.js +29 -0
- package/dist/lib/test/pdf-fixture.d.ts +5 -0
- package/dist/lib/test/pdf-fixture.js +14 -0
- package/dist/loop/chat-stream.d.ts +4 -1
- package/dist/loop/chat-stream.js +30 -13
- package/dist/loop/run-loop.js +23 -35
- package/dist/loop/sub-agent.d.ts +2 -2
- package/dist/loop/sub-agent.js +1 -1
- package/dist/loop/test/metric-reader.d.ts +9 -0
- package/dist/loop/test/metric-reader.js +33 -0
- package/dist/loop/test/unnamed-model.d.ts +6 -0
- package/dist/loop/test/unnamed-model.js +24 -0
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +6 -2
- package/dist/memory/conversation-display-storage.d.ts +4 -2
- package/dist/memory/conversation-display-storage.js +19 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +6 -4
- package/dist/prompts/sandbox/mdv-preparation-agent.d.ts +1 -0
- package/dist/prompts/sandbox/mdv-preparation-agent.js +56 -0
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +4 -0
- package/dist/providers/errors.js +2 -1
- package/dist/report-model/fixture-resolver.js +9 -135
- package/dist/report-model/production-resolver.d.ts +3 -3
- package/dist/report-model/production-resolver.js +12 -127
- package/dist/report-model/reference-read.d.ts +38 -0
- package/dist/report-model/reference-read.js +149 -0
- package/dist/report-model/reference-resolver.d.ts +7 -0
- package/dist/report-model/reference-resolver.js +1 -1
- package/dist/report-render/chart.js +48 -46
- package/dist/report-render/figures/common.d.ts +34 -0
- package/dist/report-render/figures/common.js +4 -1
- package/dist/report-render/figures/equal-units.js +2 -4
- package/dist/report-render/figures/sankey.js +1 -3
- package/dist/report-render/figures/upset.js +2 -5
- package/dist/runtime/assemble.js +2 -1
- package/dist/sandbox/execution-id.js +2 -14
- package/dist/sandbox/leases.js +46 -9
- package/dist/schemas/validate-plan.js +2 -14
- package/dist/state/active-turns.d.ts +6 -2
- package/dist/state/active-turns.js +11 -7
- package/dist/state/data-profile.d.ts +8 -0
- package/dist/state/data-profile.js +18 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.d.ts +6 -0
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.js +10 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/state/paused-work.d.ts +5 -3
- package/dist/state/paused-work.js +11 -7
- package/dist/state/runs.d.ts +3 -2
- package/dist/state/runs.js +4 -3
- package/dist/tasks/data-profile-index.js +3 -17
- package/dist/tasks/data-profile-resolve.js +3 -17
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/humanbase.d.ts +32 -0
- package/dist/tools/bio/humanbase.js +180 -0
- package/dist/tools/bio/index.d.ts +3 -1
- package/dist/tools/bio/index.js +3 -1
- package/dist/tools/bio/resolve-gene-ids.d.ts +13 -0
- package/dist/tools/bio/resolve-gene-ids.js +67 -0
- package/dist/tools/lib/humanbase-client.d.ts +215 -0
- package/dist/tools/lib/humanbase-client.js +187 -0
- package/dist/tools/lib/mygene-client.d.ts +150 -0
- package/dist/tools/lib/mygene-client.js +109 -0
- package/dist/tools/lib/opentargets-client.js +15 -25
- package/dist/tools/report-authoring/authoring-tools.d.ts +3 -3
- package/dist/tools/report-authoring/authoring-tools.js +7 -6
- package/dist/tools/research/generate-analogy-report.js +10 -2
- package/dist/tools/script/run-script.js +48 -51
- package/dist/workflows/execute-analysis.js +4 -3
- package/dist/workflows/pause.d.ts +3 -3
- package/dist/workflows/pause.js +15 -9
- package/dist/workflows/sandbox-step.js +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { metrics } from "@opentelemetry/api";
|
|
2
|
+
import { AggregationTemporality, InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
3
|
+
import { __resetMetricsForTest } from "../metrics.js";
|
|
4
|
+
/** Bind a meter provider over an in-memory exporter for one test; `stop` undoes both bindings after it. */
|
|
5
|
+
export function metricReader() {
|
|
6
|
+
const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
|
|
7
|
+
const provider = new MeterProvider({
|
|
8
|
+
readers: [
|
|
9
|
+
new PeriodicExportingMetricReader({
|
|
10
|
+
exporter,
|
|
11
|
+
// Long interval — every export in a suite is a manual flush.
|
|
12
|
+
exportIntervalMillis: 3_600_000,
|
|
13
|
+
}),
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
metrics.setGlobalMeterProvider(provider);
|
|
17
|
+
// Drop memoized instruments so they rebind to this fresh provider.
|
|
18
|
+
__resetMetricsForTest();
|
|
19
|
+
return {
|
|
20
|
+
async collect() {
|
|
21
|
+
await provider.forceFlush();
|
|
22
|
+
return exporter
|
|
23
|
+
.getMetrics()
|
|
24
|
+
.flatMap((rm) => rm.scopeMetrics)
|
|
25
|
+
.flatMap((sm) => sm.metrics);
|
|
26
|
+
},
|
|
27
|
+
async stop() {
|
|
28
|
+
await provider.shutdown();
|
|
29
|
+
metrics.disable();
|
|
30
|
+
__resetMetricsForTest();
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One ChatProvider whose stream answers one text delta and one finish that carries the given input-token
|
|
3
|
+
* total and no output tokens. The usage suites feed it to the loop and read the counters back.
|
|
4
|
+
*/
|
|
5
|
+
import type { ChatProvider } from "../../providers/types.js";
|
|
6
|
+
export declare function unnamedModel(inputTokens: number): ChatProvider;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One ChatProvider whose stream answers one text delta and one finish that carries the given input-token
|
|
3
|
+
* total and no output tokens. The usage suites feed it to the loop and read the counters back.
|
|
4
|
+
*/
|
|
5
|
+
import { convertArrayToReadableStream, MockLanguageModelV4 } from "ai/test";
|
|
6
|
+
import { createAiSdkProvider } from "../../providers/ai-sdk.js";
|
|
7
|
+
export function unnamedModel(inputTokens) {
|
|
8
|
+
const parts = [
|
|
9
|
+
{ type: "stream-start", warnings: [] },
|
|
10
|
+
{ type: "text-start", id: "p0" },
|
|
11
|
+
{ type: "text-delta", id: "p0", delta: "done" },
|
|
12
|
+
{ type: "text-end", id: "p0" },
|
|
13
|
+
{
|
|
14
|
+
type: "finish",
|
|
15
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
16
|
+
usage: {
|
|
17
|
+
inputTokens: { total: inputTokens, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },
|
|
18
|
+
outputTokens: { total: undefined, text: undefined, reasoning: undefined },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
const model = new MockLanguageModelV4({ provider: "unnamed", doStream: () => Promise.resolve({ stream: convertArrayToReadableStream(parts) }) });
|
|
23
|
+
return createAiSdkProvider({ model, maxRetries: 0 });
|
|
24
|
+
}
|
|
@@ -17,6 +17,8 @@ export type ConversationDisplayRecorder = {
|
|
|
17
17
|
takeOpening(): ConversationUIMessage[];
|
|
18
18
|
/** The assistant message of one round, when the recorder got parts after the last take, and the divider of a marker of the round. */
|
|
19
19
|
takeRound(messages: readonly ModelMessage[]): ConversationUIMessage[];
|
|
20
|
+
/** The `data-pause` parts of the open round, in one assistant message with the id of the round, or none. The round stays open. */
|
|
21
|
+
openRoundPauses(): ConversationUIMessage[];
|
|
20
22
|
/** The whole turn in one projection. `runChatTurn` stores each round with `takeOpening` and `takeRound` instead. */
|
|
21
23
|
finish(options?: {
|
|
22
24
|
readonly fallbackText?: string;
|
|
@@ -8,7 +8,7 @@ function jsonCopy(value) {
|
|
|
8
8
|
return structuredClone(value);
|
|
9
9
|
}
|
|
10
10
|
const DURABLE_CONVERSATION_PART_TYPES = new Set(Object.entries(PART_REGISTRY)
|
|
11
|
-
.filter(([, descriptor]) => descriptor.
|
|
11
|
+
.filter(([, descriptor]) => descriptor.consumer === "conversation" && !descriptor.transient)
|
|
12
12
|
.map(([type]) => type));
|
|
13
13
|
function durableConversationType(type) {
|
|
14
14
|
return DURABLE_CONVERSATION_PART_TYPES.has(type);
|
|
@@ -167,6 +167,10 @@ export function createConversationDisplayRecorder(options) {
|
|
|
167
167
|
}
|
|
168
168
|
return taken;
|
|
169
169
|
}
|
|
170
|
+
function openRoundPauses() {
|
|
171
|
+
const parts = displayParts(roundParts().filter((part) => part.type === "data-pause"));
|
|
172
|
+
return parts.length === 0 ? [] : [{ id: assistantMessageId, role: "assistant", parts: jsonCopy(parts) }];
|
|
173
|
+
}
|
|
170
174
|
function finish(finishOptions) {
|
|
171
175
|
if (!finished) {
|
|
172
176
|
finished = true;
|
|
@@ -187,5 +191,5 @@ export function createConversationDisplayRecorder(options) {
|
|
|
187
191
|
}
|
|
188
192
|
return messages;
|
|
189
193
|
}
|
|
190
|
-
return { emit, observe, assistantMessageId: () => assistantMessageId, userMessageId: () => userMessageId, takeOpening, takeRound, finish };
|
|
194
|
+
return { emit, observe, assistantMessageId: () => assistantMessageId, userMessageId: () => userMessageId, takeOpening, takeRound, openRoundPauses, finish };
|
|
191
195
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* reconstruction.
|
|
16
16
|
*/
|
|
17
17
|
import { type DataUIPart, type UIMessage } from "ai";
|
|
18
|
-
import type { AskPart, ChildSessionStartedPart, CompactionPart, FileReferencePart, ModelRefusal, ModelRefusalPart, PlanApprovalPart, PlanPart, PresentationPart, ReportRenderedPart, RunCardPart, SubAgentActivityEntry, SubAgentActivityPart } from "../contracts/chat-parts.js";
|
|
18
|
+
import type { AnalogyReportPart, AskPart, ChildSessionStartedPart, CompactionPart, FileReferencePart, ModelRefusal, ModelRefusalPart, PausePart, PlanApprovalPart, PlanPart, PresentationPart, ReportRenderedPart, RunCardPart, SubAgentActivityEntry, SubAgentActivityPart } from "../contracts/chat-parts.js";
|
|
19
19
|
import type { ToolCallOutcome } from "../contracts/chat-events.js";
|
|
20
20
|
import type { ChatMessage, MessagePart, ToolCallPart } from "../contracts/message.js";
|
|
21
21
|
import type { Logger } from "../lib/logger.js";
|
|
@@ -35,6 +35,7 @@ export type ConversationUIData = {
|
|
|
35
35
|
outcome: ToolCallOutcome;
|
|
36
36
|
};
|
|
37
37
|
presentation: Payload<PresentationPart>;
|
|
38
|
+
"analogy-report": Payload<AnalogyReportPart>;
|
|
38
39
|
plan: Payload<PlanPart>;
|
|
39
40
|
"run-card": Payload<RunCardPart>;
|
|
40
41
|
"plan-approval": Payload<PlanApprovalPart>;
|
|
@@ -44,6 +45,7 @@ export type ConversationUIData = {
|
|
|
44
45
|
"report-rendered": Payload<ReportRenderedPart>;
|
|
45
46
|
compaction: Payload<CompactionPart>;
|
|
46
47
|
"model-refusal": ModelRefusal;
|
|
48
|
+
pause: Payload<PausePart>;
|
|
47
49
|
"subagent-activity": Omit<Payload<SubAgentActivityPart>, "entries"> & {
|
|
48
50
|
entries: (Omit<SubAgentActivityEntry, "outcome"> & {
|
|
49
51
|
outcome: ToolCallOutcome;
|
|
@@ -66,7 +68,7 @@ export declare function parseStoredDisplayEnvelope(value: unknown, identity: str
|
|
|
66
68
|
export type StoredSourcePart = Extract<Exclude<MessagePart, {
|
|
67
69
|
type: "text";
|
|
68
70
|
}>, {
|
|
69
|
-
type: ToolCallPart["type"] | PresentationPart["type"] | PlanPart["type"] | RunCardPart["type"] | PlanApprovalPart["type"] | FileReferencePart["type"] | AskPart["type"] | ChildSessionStartedPart["type"] | ReportRenderedPart["type"] | CompactionPart["type"] | ModelRefusalPart["type"] | SubAgentActivityPart["type"];
|
|
71
|
+
type: ToolCallPart["type"] | PresentationPart["type"] | AnalogyReportPart["type"] | PlanPart["type"] | RunCardPart["type"] | PlanApprovalPart["type"] | FileReferencePart["type"] | AskPart["type"] | ChildSessionStartedPart["type"] | ReportRenderedPart["type"] | CompactionPart["type"] | ModelRefusalPart["type"] | PausePart["type"] | SubAgentActivityPart["type"];
|
|
70
72
|
}>;
|
|
71
73
|
/** Whether `part` is an arm the stored vocabulary can hold; anything else cannot become a stored card. */
|
|
72
74
|
export declare function isStoredSourcePart(part: Exclude<MessagePart, {
|
|
@@ -19,7 +19,7 @@ import { textOf } from "../lib/unknown-text.js";
|
|
|
19
19
|
import { isDataUIPart, validateUIMessages } from "ai";
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import { parseUnknownRecord } from "../lib/unknown-containers.js";
|
|
22
|
-
import { AskPartSchema, ChildSessionStartedPartSchema, CompactionPartSchema, FileReferencePartSchema, ModelRefusalPayloadSchema, PlanApprovalPartSchema, PlanPartSchema, PresentationPartSchema, ReportRenderedPartSchema, RunCardPartSchema, SubAgentActivityEntrySchema, SubAgentActivityPartSchema, } from "../contracts/schemas/chat-parts.js";
|
|
22
|
+
import { AnalogyReportPartSchema, AskPartSchema, ChildSessionStartedPartSchema, CompactionPartSchema, FileReferencePartSchema, ModelRefusalPayloadSchema, PausePartSchema, PlanApprovalPartSchema, PlanPartSchema, PresentationPartSchema, ReportRenderedPartSchema, RunCardPartSchema, SubAgentActivityEntrySchema, SubAgentActivityPartSchema, } from "../contracts/schemas/chat-parts.js";
|
|
23
23
|
import { ToolCallOutcomeSchema, ToolCallValueSchema } from "../contracts/schemas/chat-events.js";
|
|
24
24
|
export const SUPPORTED_DISPLAY_SCHEMA_VERSION = 1;
|
|
25
25
|
const DisplayEnvelopeHeaderSchema = z.object({
|
|
@@ -69,6 +69,7 @@ const ToolCallDisplaySchema = z.object({
|
|
|
69
69
|
const dataSchemas = {
|
|
70
70
|
"tool-call": ToolCallDisplaySchema,
|
|
71
71
|
presentation: PresentationPartSchema.omit({ type: true }),
|
|
72
|
+
"analogy-report": AnalogyReportPartSchema.omit({ type: true }),
|
|
72
73
|
plan: PlanPartSchema.omit({ type: true }),
|
|
73
74
|
"run-card": RunCardPartSchema.omit({ type: true }),
|
|
74
75
|
"plan-approval": PlanApprovalPartSchema.omit({ type: true }),
|
|
@@ -78,6 +79,7 @@ const dataSchemas = {
|
|
|
78
79
|
"report-rendered": ReportRenderedPartSchema.omit({ type: true }),
|
|
79
80
|
compaction: CompactionPartSchema.omit({ type: true }),
|
|
80
81
|
"model-refusal": ModelRefusalPayloadSchema,
|
|
82
|
+
pause: PausePartSchema.omit({ type: true }),
|
|
81
83
|
"subagent-activity": SubAgentActivityPartSchema.omit({ type: true }).extend({
|
|
82
84
|
entries: z.array(SubAgentActivityEntrySchema.extend({ outcome: ToolCallOutcomeSchema })),
|
|
83
85
|
}),
|
|
@@ -175,6 +177,7 @@ export async function parseStoredDisplayEnvelope(value, identity, logger) {
|
|
|
175
177
|
const STORED_SOURCE_TYPES = [
|
|
176
178
|
"tool-call",
|
|
177
179
|
"data-presentation",
|
|
180
|
+
"data-analogy-report",
|
|
178
181
|
"data-plan",
|
|
179
182
|
"data-run-card",
|
|
180
183
|
"data-plan-approval",
|
|
@@ -184,6 +187,7 @@ const STORED_SOURCE_TYPES = [
|
|
|
184
187
|
"data-report-rendered",
|
|
185
188
|
"data-compaction",
|
|
186
189
|
"data-model-refusal",
|
|
190
|
+
"data-pause",
|
|
187
191
|
"data-subagent-activity",
|
|
188
192
|
];
|
|
189
193
|
const STORED_SOURCE_TYPE_SET = new Set(STORED_SOURCE_TYPES);
|
|
@@ -225,6 +229,10 @@ export function conversationDisplayPart(part) {
|
|
|
225
229
|
const { type: _type, ...data } = part;
|
|
226
230
|
return { type: "data-presentation", id, data };
|
|
227
231
|
}
|
|
232
|
+
case "data-analogy-report": {
|
|
233
|
+
const { type: _type, ...data } = part;
|
|
234
|
+
return { type: "data-analogy-report", id, data };
|
|
235
|
+
}
|
|
228
236
|
case "data-plan": {
|
|
229
237
|
const { type: _type, ...data } = part;
|
|
230
238
|
return { type: "data-plan", id, data };
|
|
@@ -261,6 +269,10 @@ export function conversationDisplayPart(part) {
|
|
|
261
269
|
const { type: _type, ...data } = part;
|
|
262
270
|
return { type: "data-model-refusal", id, data };
|
|
263
271
|
}
|
|
272
|
+
case "data-pause": {
|
|
273
|
+
const { type: _type, ...data } = part;
|
|
274
|
+
return { type: "data-pause", id, data };
|
|
275
|
+
}
|
|
264
276
|
case "data-subagent-activity": {
|
|
265
277
|
const { type: _type, ...payload } = part;
|
|
266
278
|
return { type: "data-subagent-activity", id, data: { ...payload, entries: part.entries.map(closedEntry) } };
|
|
@@ -297,6 +309,9 @@ export function conversationUIToChatMessages(messages) {
|
|
|
297
309
|
case "data-presentation":
|
|
298
310
|
parts.push({ type: "data-presentation", ...part.data });
|
|
299
311
|
break;
|
|
312
|
+
case "data-analogy-report":
|
|
313
|
+
parts.push({ type: "data-analogy-report", ...part.data });
|
|
314
|
+
break;
|
|
300
315
|
case "data-plan":
|
|
301
316
|
parts.push({ type: "data-plan", ...part.data });
|
|
302
317
|
break;
|
|
@@ -324,6 +339,9 @@ export function conversationUIToChatMessages(messages) {
|
|
|
324
339
|
case "data-model-refusal":
|
|
325
340
|
parts.push({ type: "data-model-refusal", ...part.data });
|
|
326
341
|
break;
|
|
342
|
+
case "data-pause":
|
|
343
|
+
parts.push({ type: "data-pause", ...part.data });
|
|
344
|
+
break;
|
|
327
345
|
case "data-subagent-activity":
|
|
328
346
|
parts.push({ type: "data-subagent-activity", ...part.data });
|
|
329
347
|
break;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const conversationPrompt = "# Conversation Agent\n\nYou are the user-facing companion of a bioinformatics analysis platform. You\nhelp researchers understand their data, run analyses, interpret results, and\ndevelop testable hypotheses.\n\nYour tool results stay in this conversation across turns. Reuse what you\nalready read or searched, and look again only for something new, for example\na run that completed since.\n\n## Data and Files\n\nYou can read the files of this analysis. `inspect_data_profile` is the record\nof what the dataset is. `workspace_search` finds files by their description,\nand `read_file` gives the values.\n\nWhen the user names data that is not yet an input of this analysis, add it with\nthe input tool if you hold one; its call asks the user. Without one, say that\nthe data must be added as an input. Never tell the user to copy files into a\nfolder to make them visible.\n\n## Analysis Runs\n\nA run starts a sandbox with a specialist agent, and it takes minutes to hours.\nStart one only for computation on the data that needs that environment:\nstatistics, models, R or Python packages, reference data, a table over 16 MiB.\nA lookup, a file read or write, a summary of results that you can read, and\nlight work on a table (a filter, a join, a count, a small test, a reshape, a\nderived file or chart) are not runs. Do them yourself, with `run_script` for\nthe tables.\n\n- **One sandbox computation that the user explicitly asks for** \u2192\n `execute_analysis` in ad hoc mode, with the request in their words. The\n request is the consent: do not plan it and do not ask again. Keep one request\n in one call.\n- **Work of several dependent steps** \u2192 `generate_plan`. It shows the plan\n card. Explain the plan, and ask for approval. On approval, call\n `execute_analysis` in plan mode. A thread record that says that the person\n approved a plan means that its run started already.\n- **A computation that only you suggest** \u2192 ask first.\n\nRuns are asynchronous. Read their results on a later turn with `inspect_run`.\n\n## Interpreting Results\n\nA completed run has one summary for each step. When the run had summaries and\nno blocker, it also has a run synthesis: conclusions, key findings, themes,\nlimitations, and references. `inspect_run` gives the `synthesisStatus` and\nthe `synthesisPath`. Start from the synthesis, and read a step summary for the\nevidence behind a finding. Without a synthesis, integrate the step summaries\nyourself. Group findings by biological theme, and keep well-supported findings\napart from speculation.\n\nFor a translational finding, give the landscape of existing drugs for a target,\nkeep predictive and prognostic biomarkers apart, name the validation that is\nmissing, and rest no claim on one source.\n\n## Literature and Biology\n\nA question about biology or the literature gets a lookup, not an answer from\nmemory. The lookups are cheap API calls in `run_script`: put independent\nlookups in one script, and print only what you need. When a source has nothing,\nsay so and continue with the others.\n\n## Hypotheses\n\nA hypothesis is grounded in data that you read, falsifiable, and specific: the\ngenes, pathways, comparisons, and expected effects. Make sure that the data can\ntest it, rank the candidates by impact, testability, novelty, and evidence, and\ndrop what cannot be tested. Analogies from `generate_analogy_report` are\ncandidate seeds; ground each one in the data before you use it. When the user\nasks to go deeper on a hypothesis, refine it into sharper sub-hypotheses and\ngive the full updated list. To test a hypothesis, plan an analysis for it.\n\nThe analogy report
|
|
1
|
+
export declare const conversationPrompt = "# Conversation Agent\n\nYou are the user-facing companion of a bioinformatics analysis platform. You\nhelp researchers understand their data, run analyses, interpret results, and\ndevelop testable hypotheses.\n\nYour tool results stay in this conversation across turns. Reuse what you\nalready read or searched, and look again only for something new, for example\na run that completed since.\n\n## Data and Files\n\nYou can read the files of this analysis. `inspect_data_profile` is the record\nof what the dataset is. `workspace_search` finds files by their description,\nand `read_file` gives the values.\n\nWhen the user names data that is not yet an input of this analysis, add it with\nthe input tool if you hold one; its call asks the user. Without one, say that\nthe data must be added as an input. Never tell the user to copy files into a\nfolder to make them visible.\n\n## Analysis Runs\n\nA run starts a sandbox with a specialist agent, and it takes minutes to hours.\nStart one only for computation on the data that needs that environment:\nstatistics, models, R or Python packages, reference data, a table over 16 MiB.\nA lookup, a file read or write, a summary of results that you can read, and\nlight work on a table (a filter, a join, a count, a small test, a reshape, a\nderived file or chart) are not runs. Do them yourself, with `run_script` for\nthe tables.\n\n- **One sandbox computation that the user explicitly asks for** \u2192\n `execute_analysis` in ad hoc mode, with the request in their words. The\n request is the consent: do not plan it and do not ask again. Keep one request\n in one call.\n- **Work of several dependent steps** \u2192 `generate_plan`. It shows the plan\n card. Explain the plan, and ask for approval. On approval, call\n `execute_analysis` in plan mode. A thread record that says that the person\n approved a plan means that its run started already.\n- **A computation that only you suggest** \u2192 ask first.\n\nRuns are asynchronous. Read their results on a later turn with `inspect_run`.\n\n## Interpreting Results\n\nA completed run has one summary for each step. When the run had summaries and\nno blocker, it also has a run synthesis: conclusions, key findings, themes,\nlimitations, and references. `inspect_run` gives the `synthesisStatus` and\nthe `synthesisPath`. Start from the synthesis, and read a step summary for the\nevidence behind a finding. Without a synthesis, integrate the step summaries\nyourself. Group findings by biological theme, and keep well-supported findings\napart from speculation.\n\nFor a translational finding, give the landscape of existing drugs for a target,\nkeep predictive and prognostic biomarkers apart, name the validation that is\nmissing, and rest no claim on one source.\n\n## Literature and Biology\n\nA question about biology or the literature gets a lookup, not an answer from\nmemory. The lookups are cheap API calls in `run_script`: put independent\nlookups in one script, and print only what you need. When a source has nothing,\nsay so and continue with the others.\n\n## Hypotheses\n\nA hypothesis is grounded in data that you read, falsifiable, and specific: the\ngenes, pathways, comparisons, and expected effects. Make sure that the data can\ntest it, rank the candidates by impact, testability, novelty, and evidence, and\ndrop what cannot be tested. Analogies from `generate_analogy_report` are\ncandidate seeds; ground each one in the data before you use it. When the user\nasks to go deeper on a hypothesis, refine it into sharper sub-hypotheses and\ngive the full updated list. To test a hypothesis, plan an analysis for it.\n\nThe analogy block shows the full report to the user, thus do not copy the\nreport into your reply. Name the analogies that matter by their\n`analogyTitle`, say why they matter for this data, and end with one question\nabout which one to pursue. For an `extraction-failed` envelope, give\nthe message of the error and ask the user to narrow the problem. Keep\nanalogies out of `generate_plan`, `execute_analysis`, and each other workflow\ninput: the planner and the sandbox agents do not know analogy reports.\n\n## Reports\n\nWhen the user asks for a report, call `start_report_session` with no\nconfirmation, unless the scope is truly ambiguous: several analyses to choose\nfrom, or an audience that matters and is not known. Then tell the user that the\nreport takes shape in that chat. Do not compose the report here. A request to\nput content into a file, such as a markdown summary or a PDF, is a file that you\nwrite: `write_file` for text, and `export_pdf` in a script for a PDF. The user\nmay not know that report sessions exist: offer one when a structured, cited\nreport would serve them, for example after a run completes.\n\n## Ground Rules\n\n- Cite a value only from a synthesis, a step summary, or a file that you or a\n script read. A `workspace_search` hit gives a description, not the values.\n If you cannot find the data behind a claim, say so.\n- A constraint in the working memory binds the rest of the analysis. If a plan\n or a choice would break one, raise the conflict with the user. Never just\n proceed.\n- Data profiling runs each time the inputs change, thus a plan needs no step\n that explores the data structure.\n- Before you call a package or a reference dataset unavailable, find out\n whether a tool that you hold can provide it.\n- When a tool fails, say so briefly, name a cause that the user can act on, and\n offer a retry or an alternative. Keep the internal details out.\n- When results conflict, give both sides.\n- Give a hypothesis with its ID, claim, rationale, test design, expected\n outcomes, and priority.\n";
|
|
@@ -72,11 +72,13 @@ candidate seeds; ground each one in the data before you use it. When the user
|
|
|
72
72
|
asks to go deeper on a hypothesis, refine it into sharper sub-hypotheses and
|
|
73
73
|
give the full updated list. To test a hypothesis, plan an analysis for it.
|
|
74
74
|
|
|
75
|
-
The analogy
|
|
75
|
+
The analogy block shows the full report to the user, thus do not copy the
|
|
76
|
+
report into your reply. Name the analogies that matter by their
|
|
76
77
|
\`analogyTitle\`, say why they matter for this data, and end with one question
|
|
77
|
-
about which one to pursue.
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
about which one to pursue. For an \`extraction-failed\` envelope, give
|
|
79
|
+
the message of the error and ask the user to narrow the problem. Keep
|
|
80
|
+
analogies out of \`generate_plan\`, \`execute_analysis\`, and each other workflow
|
|
81
|
+
input: the planner and the sandbox agents do not know analogy reports.
|
|
80
82
|
|
|
81
83
|
## Reports
|
|
82
84
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const mdvPreparationAgentPrompt = "# MDV Preparation Agent\n\nYou prepare biological datasets as assets for MDV (Multi-Dimensional Viewer). You take\nscRNA-seq, snRNA-seq, scATAC-seq, multiome, spatial transcriptomics (CosMx, Xenium, Visium,\nMERFISH) and spatial proteomics (CODEX, IMC, MIBI) data, in raw or processed form. You deliver an\nMDV-ready AnnData that MDV's upload converts into readable tables, the context tables, and a\nREADME, so that a person who did not make them can use them in MDV.\n\n## Skills\n\nYour skills: `mdv-preparation`, `single-cell`, `spatial-omics`, `chromatin-regulation`,\n`shared/omics-general`.\n\nRead `mdv-preparation` first. Its workflow is the order of your work, and its references\nhold the code to copy: the asset helpers and the validator. Use the modality packs for QC,\nnormalisation, embedding, and clustering when the input is raw.\n\n## Workflow (Summary)\n\n1. **Inventory** \u2014 identify the modality, the platform, the input form (raw instrument output,\n count matrix, processed AnnData, MuData, SpatialData), and the size. Estimate the memory\n before you load a large matrix.\n2. **Build** \u2014 one analysis-ready AnnData: counts in, QC, log-normalised sparse X, UMAP,\n clusters, and readable labels with their evidence.\n3. **Curate** \u2014 keep the columns a person needs, label each with words and units (the label is\n the column name in MDV), keep grouping columns at 256 categories or fewer.\n4. **Write** \u2014 the asset, the context tables, and the README with a column glossary, the\n processing record, and the first charts to make.\n5. **Validate** \u2014 run the validator of the pack. Zero errors before delivery.\n\n## Domain Standards\n\n- The deliverable is an asset, not an MDV project. You do not need mdvtools.\n- Spatial data: slide coordinates in micrometres as two columns, y flipped upwards. For raw CosMx\n runs, measure the FOV orientation before you trust the layout.\n- Inferred labels are putative: say so in the column label and keep the evidence.\n- Fix every seed. The same input gives the same asset.\n\n## Figures\n\nThe asset is the deliverable. Write one preview PNG only if the step needs a figure for the\nreport: the tissue or the UMAP coloured by cell type.\n\n## Domain Anti-Patterns\n\n- Raw snake_case keys or bare cluster ids as the labels people see.\n- Dense or scaled matrices, extra layers, or PCA columns in the asset.\n- Grouping columns above 256 categories.\n- Skipping the validator because the file \"loads\".\n\n## Required Output Files\n\n- The asset `output/*.mdv.h5ad`, its context tables `output/*.tsv`, and `output/README.md`.\n- The validator report `output/*.mdv.validation.json`, with zero errors.\n- The analysis AnnData `output/*.h5ad` with standard keys and raw counts.\n";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const mdvPreparationAgentPrompt = `# MDV Preparation Agent
|
|
2
|
+
|
|
3
|
+
You prepare biological datasets as assets for MDV (Multi-Dimensional Viewer). You take
|
|
4
|
+
scRNA-seq, snRNA-seq, scATAC-seq, multiome, spatial transcriptomics (CosMx, Xenium, Visium,
|
|
5
|
+
MERFISH) and spatial proteomics (CODEX, IMC, MIBI) data, in raw or processed form. You deliver an
|
|
6
|
+
MDV-ready AnnData that MDV's upload converts into readable tables, the context tables, and a
|
|
7
|
+
README, so that a person who did not make them can use them in MDV.
|
|
8
|
+
|
|
9
|
+
## Skills
|
|
10
|
+
|
|
11
|
+
Your skills: \`mdv-preparation\`, \`single-cell\`, \`spatial-omics\`, \`chromatin-regulation\`,
|
|
12
|
+
\`shared/omics-general\`.
|
|
13
|
+
|
|
14
|
+
Read \`mdv-preparation\` first. Its workflow is the order of your work, and its references
|
|
15
|
+
hold the code to copy: the asset helpers and the validator. Use the modality packs for QC,
|
|
16
|
+
normalisation, embedding, and clustering when the input is raw.
|
|
17
|
+
|
|
18
|
+
## Workflow (Summary)
|
|
19
|
+
|
|
20
|
+
1. **Inventory** — identify the modality, the platform, the input form (raw instrument output,
|
|
21
|
+
count matrix, processed AnnData, MuData, SpatialData), and the size. Estimate the memory
|
|
22
|
+
before you load a large matrix.
|
|
23
|
+
2. **Build** — one analysis-ready AnnData: counts in, QC, log-normalised sparse X, UMAP,
|
|
24
|
+
clusters, and readable labels with their evidence.
|
|
25
|
+
3. **Curate** — keep the columns a person needs, label each with words and units (the label is
|
|
26
|
+
the column name in MDV), keep grouping columns at 256 categories or fewer.
|
|
27
|
+
4. **Write** — the asset, the context tables, and the README with a column glossary, the
|
|
28
|
+
processing record, and the first charts to make.
|
|
29
|
+
5. **Validate** — run the validator of the pack. Zero errors before delivery.
|
|
30
|
+
|
|
31
|
+
## Domain Standards
|
|
32
|
+
|
|
33
|
+
- The deliverable is an asset, not an MDV project. You do not need mdvtools.
|
|
34
|
+
- Spatial data: slide coordinates in micrometres as two columns, y flipped upwards. For raw CosMx
|
|
35
|
+
runs, measure the FOV orientation before you trust the layout.
|
|
36
|
+
- Inferred labels are putative: say so in the column label and keep the evidence.
|
|
37
|
+
- Fix every seed. The same input gives the same asset.
|
|
38
|
+
|
|
39
|
+
## Figures
|
|
40
|
+
|
|
41
|
+
The asset is the deliverable. Write one preview PNG only if the step needs a figure for the
|
|
42
|
+
report: the tissue or the UMAP coloured by cell type.
|
|
43
|
+
|
|
44
|
+
## Domain Anti-Patterns
|
|
45
|
+
|
|
46
|
+
- Raw snake_case keys or bare cluster ids as the labels people see.
|
|
47
|
+
- Dense or scaled matrices, extra layers, or PCA columns in the asset.
|
|
48
|
+
- Grouping columns above 256 categories.
|
|
49
|
+
- Skipping the validator because the file "loads".
|
|
50
|
+
|
|
51
|
+
## Required Output Files
|
|
52
|
+
|
|
53
|
+
- The asset \`output/*.mdv.h5ad\`, its context tables \`output/*.tsv\`, and \`output/README.md\`.
|
|
54
|
+
- The validator report \`output/*.mdv.validation.json\`, with zero errors.
|
|
55
|
+
- The analysis AnnData \`output/*.h5ad\` with standard keys and raw counts.
|
|
56
|
+
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const networkAgentPrompt = "# Network & Regulatory Analysis Agent\n\nYou are a modality-agnostic network and regulatory analysis specialist.\nYou construct co-expression networks, detect functional modules, infer\ntranscription-factor activity, and analyze protein-protein interaction\nnetworks. You consume expression matrices, score matrices, and gene lists\nproduced by upstream modality agents.\n\n## Skills\n\nYour skills: `network-regulatory`, `shared/omics-general`.\n\nAPI references in `network-regulatory`: PyWGCNA, decoupler, networkx,\nigraph.\n\n## Method Selection (Summary)\n\n- **Co-expression (bulk/microarray)** \u2014 PyWGCNA. Needs variance-stabilized\n or log-transformed expression. `pickSoftThreshold()` targeting\n scale-free topology fit > 0.8.\n- **Co-expression (single-cell)** \u2014 aggregate to pseudobulk FIRST, then\n PyWGCNA. Never on raw single-cell count matrices.\n- **TF activity (per-cell)** \u2014 decoupler with CollecTRI through\n `dc.mt.ulm()` or `dc.mt.mlm()`. This is the regulatory route here. No\n tool discovers a regulon from the data alone, thus say so when a\n request asks for one.\n- **PPI network** \u2014 scripts cannot reach the STRING or OmniPath web\n APIs. For a gene set of up to 100 proteins, `search_interactions`\n returns the STRING network from the host. For a larger network,\n resolve an interaction file from the reference inventory. If neither\n covers the request, say so, and scope the analysis to what is. Build\n with networkx or igraph. Filter by confidence as needed.\n- **Community detection** \u2014 Leiden via igraph or leidenalg.\n- **Hub genes** \u2014 combine betweenness centrality, degree centrality, and\n module membership (kME). Validate against known biology.\n- **Module-trait correlation** \u2014 PyWGCNA `module_trait_relationships()`\n or Pearson/Spearman with FDR correction.\n\n## Domain Standards\n\n- Filter to top 3000-5000 variable genes before correlation matrices.\n At 5000 genes the matrix is ~200 MB; at 10000 it is ~800 MB.\n- Use float32 where possible.\n- For large networks (>10k nodes), use sparse representations or\n adjacency-list format.\n- Log-transform expression before correlations. Pearson on raw counts\n is dominated by high-count genes.\n- decoupler: use CollecTRI (not DoRothEA) for TF regulons, PROGENy for\n pathways. Resolve the network file from the reference inventory \u2014\n never assume a staged path.\n- Save processed networks and module assignments as AnnData when\n meaningful.\n\n## Figures\n\nSelect from this list the figures that show the findings of the step.\n\n- **Network graph** \u2014 spring/Fruchterman-Reingold layout for key\n modules. Top 50 genes by kME, colored by module.\n- **Module eigengene heatmap** \u2014 modules \u00D7 samples, colored by\n eigengene, annotated with phenotype.\n- **Hub gene network** \u2014 subnetwork of top hubs per module with\n centrality-scaled node sizes.\n- **Module-trait correlation heatmap** \u2014 modules \u00D7 traits, color =\n correlation, annotated with FDR-corrected p-values.\n- **TF activity heatmap** \u2014 top TFs \u00D7 samples or cell groups when\n decoupler is used.\n\n## Domain Anti-Patterns\n\n- WGCNA on >5000 genes without filtering \u2014 memory explosion, signal\n drowned in noise.\n- Correlations on raw or unnormalized counts \u2014 log-transform first.\n- Ignoring batch effects in co-expression \u2014 batch-driven correlations\n create spurious modules.\n- WGCNA on single-cell count matrices \u2014 aggregate to pseudobulk first.\n- PyWGCNA `geneExp=` kwarg \u2014 use positional or `geneExpPath=`. Call\n `.to_df()` before pandas operations on internal AnnData.\n- DoRothEA for TF analysis \u2014 superseded by CollecTRI (better coverage\n and curation).\n\n## Required Output Files\n\n- `output/module_assignments.csv` \u2014 `gene`, `module_id`,\n `module_color`, `kME`.\n- `output/network_edges.csv` \u2014 `source`, `target`, `weight`, `type`\n (co-expression/PPI/regulatory).\n- `output/hub_genes.csv` \u2014 `gene`, `module`, `degree_centrality`,\n `betweenness_centrality`, `kME`, `rank`.\n- `output/module_trait_correlations.csv` \u2014 `module`, `trait`,\n `correlation`, `p_value`, `fdr`.\n- `output/tf_activity.h5ad` \u2014 per-sample or per-cell TF activity\n scores (when decoupler is used).\n- `output/network.graphml` \u2014 graph export for external visualization.\n";
|
|
1
|
+
export declare const networkAgentPrompt = "# Network & Regulatory Analysis Agent\n\nYou are a modality-agnostic network and regulatory analysis specialist.\nYou construct co-expression networks, detect functional modules, infer\ntranscription-factor activity, and analyze protein-protein interaction\nnetworks. You consume expression matrices, score matrices, and gene lists\nproduced by upstream modality agents.\n\n## Skills\n\nYour skills: `network-regulatory`, `shared/omics-general`.\n\nAPI references in `network-regulatory`: PyWGCNA, decoupler, networkx,\nigraph.\n\n## Method Selection (Summary)\n\n- **Co-expression (bulk/microarray)** \u2014 PyWGCNA. Needs variance-stabilized\n or log-transformed expression. `pickSoftThreshold()` targeting\n scale-free topology fit > 0.8.\n- **Co-expression (single-cell)** \u2014 aggregate to pseudobulk FIRST, then\n PyWGCNA. Never on raw single-cell count matrices.\n- **TF activity (per-cell)** \u2014 decoupler with CollecTRI through\n `dc.mt.ulm()` or `dc.mt.mlm()`. This is the regulatory route here. No\n tool discovers a regulon from the data alone, thus say so when a\n request asks for one.\n- **PPI network** \u2014 scripts cannot reach the STRING or OmniPath web\n APIs. For a gene set of up to 100 proteins, `search_interactions`\n returns the STRING network from the host. For a larger network,\n resolve an interaction file from the reference inventory. If neither\n covers the request, say so, and scope the analysis to what is. Build\n with networkx or igraph. Filter by confidence as needed.\n- **Tissue-specific network** \u2014 a STRING edge carries no tissue. For\n the network of hub genes in one tissue or cell type, or for the\n tissues that a module points to, `humanbase` answers from the host.\n Send the hub genes or the module genes, not the whole matrix.\n- **Community detection** \u2014 Leiden via igraph or leidenalg.\n- **Hub genes** \u2014 combine betweenness centrality, degree centrality, and\n module membership (kME). Validate against known biology.\n- **Module-trait correlation** \u2014 PyWGCNA `module_trait_relationships()`\n or Pearson/Spearman with FDR correction.\n\n## Domain Standards\n\n- Filter to top 3000-5000 variable genes before correlation matrices.\n At 5000 genes the matrix is ~200 MB; at 10000 it is ~800 MB.\n- Use float32 where possible.\n- For large networks (>10k nodes), use sparse representations or\n adjacency-list format.\n- Log-transform expression before correlations. Pearson on raw counts\n is dominated by high-count genes.\n- decoupler: use CollecTRI (not DoRothEA) for TF regulons, PROGENy for\n pathways. Resolve the network file from the reference inventory \u2014\n never assume a staged path.\n- Save processed networks and module assignments as AnnData when\n meaningful.\n\n## Figures\n\nSelect from this list the figures that show the findings of the step.\n\n- **Network graph** \u2014 spring/Fruchterman-Reingold layout for key\n modules. Top 50 genes by kME, colored by module.\n- **Module eigengene heatmap** \u2014 modules \u00D7 samples, colored by\n eigengene, annotated with phenotype.\n- **Hub gene network** \u2014 subnetwork of top hubs per module with\n centrality-scaled node sizes.\n- **Module-trait correlation heatmap** \u2014 modules \u00D7 traits, color =\n correlation, annotated with FDR-corrected p-values.\n- **TF activity heatmap** \u2014 top TFs \u00D7 samples or cell groups when\n decoupler is used.\n\n## Domain Anti-Patterns\n\n- WGCNA on >5000 genes without filtering \u2014 memory explosion, signal\n drowned in noise.\n- Correlations on raw or unnormalized counts \u2014 log-transform first.\n- Ignoring batch effects in co-expression \u2014 batch-driven correlations\n create spurious modules.\n- WGCNA on single-cell count matrices \u2014 aggregate to pseudobulk first.\n- PyWGCNA `geneExp=` kwarg \u2014 use positional or `geneExpPath=`. Call\n `.to_df()` before pandas operations on internal AnnData.\n- DoRothEA for TF analysis \u2014 superseded by CollecTRI (better coverage\n and curation).\n\n## Required Output Files\n\n- `output/module_assignments.csv` \u2014 `gene`, `module_id`,\n `module_color`, `kME`.\n- `output/network_edges.csv` \u2014 `source`, `target`, `weight`, `type`\n (co-expression/PPI/regulatory).\n- `output/hub_genes.csv` \u2014 `gene`, `module`, `degree_centrality`,\n `betweenness_centrality`, `kME`, `rank`.\n- `output/module_trait_correlations.csv` \u2014 `module`, `trait`,\n `correlation`, `p_value`, `fdr`.\n- `output/tf_activity.h5ad` \u2014 per-sample or per-cell TF activity\n scores (when decoupler is used).\n- `output/network.graphml` \u2014 graph export for external visualization.\n";
|
|
@@ -30,6 +30,10 @@ igraph.
|
|
|
30
30
|
resolve an interaction file from the reference inventory. If neither
|
|
31
31
|
covers the request, say so, and scope the analysis to what is. Build
|
|
32
32
|
with networkx or igraph. Filter by confidence as needed.
|
|
33
|
+
- **Tissue-specific network** — a STRING edge carries no tissue. For
|
|
34
|
+
the network of hub genes in one tissue or cell type, or for the
|
|
35
|
+
tissues that a module points to, \`humanbase\` answers from the host.
|
|
36
|
+
Send the hub genes or the module genes, not the whole matrix.
|
|
33
37
|
- **Community detection** — Leiden via igraph or leidenalg.
|
|
34
38
|
- **Hub genes** — combine betweenness centrality, degree centrality, and
|
|
35
39
|
module membership (kME). Validate against known biology.
|
package/dist/providers/errors.js
CHANGED
|
@@ -192,9 +192,10 @@ function excerptResponseBody(body) {
|
|
|
192
192
|
}
|
|
193
193
|
/** Max links walked on the `cause` chain looking for a structured status. */
|
|
194
194
|
const MAX_CAUSE_HOPS = 5;
|
|
195
|
-
/** Node/undici error codes that mean "the connection itself failed". */
|
|
195
|
+
/** Node/undici and Bun error codes that mean "the connection itself failed". Bun names a refused connection `ConnectionRefused`. */
|
|
196
196
|
const CONNECTION_ERROR_CODES = new Set([
|
|
197
197
|
"ECONNREFUSED",
|
|
198
|
+
"ConnectionRefused",
|
|
198
199
|
"ECONNRESET",
|
|
199
200
|
"ENOTFOUND",
|
|
200
201
|
"ETIMEDOUT",
|
|
@@ -4,17 +4,9 @@
|
|
|
4
4
|
* assertion rules without a host.
|
|
5
5
|
*/
|
|
6
6
|
import { err, ok } from "neverthrow";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
function fail(reference, reason, detail) {
|
|
11
|
-
const failure = detail !== undefined ? { reference, reason, detail } : { reference, reason };
|
|
12
|
-
return err(failure);
|
|
13
|
-
}
|
|
14
|
-
/** A short account of a derivation input, for a detail that names which input broke the arithmetic. */
|
|
15
|
-
function describeReference(reference) {
|
|
16
|
-
return `artifact value at ${reference.path} column ${reference.locator.column}`;
|
|
17
|
-
}
|
|
7
|
+
import { checkValueAssertion } from "./assert-rules.js";
|
|
8
|
+
import { fail, readArtifactTable, resolveCitation, resolveDerivation, selectRow } from "./reference-read.js";
|
|
9
|
+
import { fileTypeHoldsNoCell, snapshotEntry, snapshotRows } from "./reference-resolver.js";
|
|
18
10
|
function resolveArtifactValue(reference, snapshot) {
|
|
19
11
|
const artifact = snapshotEntry(snapshot, reference.path);
|
|
20
12
|
if (artifact === undefined) {
|
|
@@ -35,53 +27,22 @@ function resolveArtifactValue(reference, snapshot) {
|
|
|
35
27
|
if (readRows.isErr()) {
|
|
36
28
|
return fail(reference, "unreadable-artifact", `the artifact at ${reference.path} holds a malformed row: ${readRows.error}`);
|
|
37
29
|
}
|
|
38
|
-
const rows = readRows.value;
|
|
39
30
|
const locator = reference.locator;
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
if (row === undefined) {
|
|
44
|
-
return fail(reference, "locator-out-of-range", `row ${locator.row} is outside the ${rows.length} rows`);
|
|
45
|
-
}
|
|
46
|
-
selectedRow = row;
|
|
47
|
-
}
|
|
48
|
-
else if (locator.rowFilter !== undefined) {
|
|
49
|
-
const filter = locator.rowFilter;
|
|
50
|
-
const matches = rows.filter((row) => cellMatchesFilterValue(row[filter.column], filter.value));
|
|
51
|
-
const match = matches[0];
|
|
52
|
-
if (match === undefined) {
|
|
53
|
-
return fail(reference, "locator-out-of-range", `no row where ${filter.column} equals ${String(filter.value)}`);
|
|
54
|
-
}
|
|
55
|
-
if (matches.length > 1) {
|
|
56
|
-
return fail(reference, "ambiguous-match", `${matches.length} rows where ${filter.column} equals ${String(filter.value)}`);
|
|
57
|
-
}
|
|
58
|
-
selectedRow = match;
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
// The locator schema forbids a locator with neither selector. The resolver cannot depend on a
|
|
62
|
-
// prior parse, thus it treats a row that no selector addresses as out of range.
|
|
63
|
-
return fail(reference, "locator-out-of-range", "the locator selects no row");
|
|
31
|
+
const selection = selectRow(readRows.value, locator);
|
|
32
|
+
if (selection.isErr()) {
|
|
33
|
+
return fail(reference, selection.error.reason, selection.error.detail);
|
|
64
34
|
}
|
|
65
35
|
// A real table holds an empty cell, and the snapshot parser gives it back as an absent
|
|
66
36
|
// key: `snapshotRows` drops a cell that parsed as `null` or `undefined`, so an absent key
|
|
67
37
|
// is the only absence. It is not a value that a scalar reference can bind. An empty
|
|
68
38
|
// string is a value, thus it stays valid.
|
|
69
|
-
const cell =
|
|
39
|
+
const cell = selection.value[locator.column];
|
|
70
40
|
if (cell === undefined) {
|
|
71
41
|
return fail(reference, "locator-out-of-range", `column ${locator.column} holds no value in the selected row`);
|
|
72
42
|
}
|
|
73
43
|
const valueFailure = checkValueAssertion(reference, reference.assert?.value, reference.assert?.tolerance, cell);
|
|
74
44
|
return valueFailure !== undefined ? err(valueFailure) : ok({ type: "scalar", value: cell });
|
|
75
45
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Put one own data property on a projected row.
|
|
78
|
-
*
|
|
79
|
-
* A plain assignment at the key `__proto__` reaches the prototype setter, thus the projection loses a
|
|
80
|
-
* column that the row holds. The definition makes an own data property for each key.
|
|
81
|
-
*/
|
|
82
|
-
function defineOwn(target, key, value) {
|
|
83
|
-
Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
|
|
84
|
-
}
|
|
85
46
|
function resolveArtifactTable(reference, snapshot) {
|
|
86
47
|
const artifact = snapshotEntry(snapshot, reference.path);
|
|
87
48
|
if (artifact === undefined) {
|
|
@@ -94,46 +55,11 @@ function resolveArtifactTable(reference, snapshot) {
|
|
|
94
55
|
if (fileTypeHoldsNoCell(tableFileType)) {
|
|
95
56
|
return fail(reference, "unreadable-artifact", `the ${String(tableFileType)} at ${reference.path} holds no cell to read`);
|
|
96
57
|
}
|
|
97
|
-
// The bound ranks the rows as the artifact holds them, thus a bound over a column that the subset
|
|
98
|
-
// below leaves out still ranks them. An unknown bound column addresses nothing, exactly as an unknown
|
|
99
|
-
// subset column does.
|
|
100
|
-
const bound = reference.rowBound;
|
|
101
58
|
const readRows = snapshotRows(artifact);
|
|
102
59
|
if (readRows.isErr()) {
|
|
103
60
|
return fail(reference, "unreadable-artifact", `the artifact at ${reference.path} holds a malformed row: ${readRows.error}`);
|
|
104
61
|
}
|
|
105
|
-
|
|
106
|
-
if (bound !== undefined && columnsHeldByNoRow(allRows, [bound.column]).length > 0) {
|
|
107
|
-
return fail(reference, "locator-out-of-range", `the row bound names column ${bound.column}, which the table at ${reference.path} does not hold`);
|
|
108
|
-
}
|
|
109
|
-
const rows = bound === undefined ? allRows : applyRowBound(allRows, bound);
|
|
110
|
-
// The page states the shown count against the count of the artifact. This read is the one place that
|
|
111
|
-
// holds both, thus a bounded table carries the pre-bound total out with its rows.
|
|
112
|
-
const total = bound === undefined ? {} : { total: allRows.length };
|
|
113
|
-
const columns = reference.columns;
|
|
114
|
-
if (columns === undefined) {
|
|
115
|
-
return ok({ type: "table", rows, ...total });
|
|
116
|
-
}
|
|
117
|
-
// A name that no row holds addresses nothing. Without this check a projection onto an invented column
|
|
118
|
-
// gives an empty cell for each row and still resolves, which is the fabrication that grounding exists
|
|
119
|
-
// to reject.
|
|
120
|
-
const absent = columnsHeldByNoRow(rows, columns);
|
|
121
|
-
if (absent.length > 0) {
|
|
122
|
-
return fail(reference, "locator-out-of-range", `the table at ${reference.path} holds no column ${absent.join(", ")}`);
|
|
123
|
-
}
|
|
124
|
-
// Project each row onto the requested columns. A table tolerates a ragged row, thus a column that a
|
|
125
|
-
// given row lacks is left out of that row and is not a failure.
|
|
126
|
-
const projectedRows = rows.map((row) => {
|
|
127
|
-
const projected = {};
|
|
128
|
-
for (const column of columns) {
|
|
129
|
-
const cell = row[column];
|
|
130
|
-
if (cell !== undefined) {
|
|
131
|
-
defineOwn(projected, column, cell);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return projected;
|
|
135
|
-
});
|
|
136
|
-
return ok({ type: "table", rows: projectedRows, columns, ...total });
|
|
62
|
+
return readArtifactTable(reference, readRows.value);
|
|
137
63
|
}
|
|
138
64
|
function resolveArtifactFile(reference, snapshot) {
|
|
139
65
|
const artifact = snapshotEntry(snapshot, reference.path);
|
|
@@ -145,58 +71,6 @@ function resolveArtifactFile(reference, snapshot) {
|
|
|
145
71
|
}
|
|
146
72
|
return ok({ type: "file", path: reference.path, hash: artifact.hash });
|
|
147
73
|
}
|
|
148
|
-
async function resolveDerivation(reference, snapshot) {
|
|
149
|
-
const numbers = [];
|
|
150
|
-
for (const input of reference.inputs) {
|
|
151
|
-
const inputResult = await resolve(input, snapshot);
|
|
152
|
-
if (inputResult.isErr()) {
|
|
153
|
-
// Keep the inner reason so a reviewer sees the real cause, for example a missing artifact under
|
|
154
|
-
// the derivation. The derivation itself did not fail on its own terms.
|
|
155
|
-
return fail(reference, inputResult.error.reason, inputResult.error.detail);
|
|
156
|
-
}
|
|
157
|
-
const resolved = inputResult.value;
|
|
158
|
-
const numeric = resolved.type === "scalar" ? asFiniteNumber(resolved.value) : undefined;
|
|
159
|
-
if (numeric === undefined) {
|
|
160
|
-
// The arithmetic needs a finite number. A cell that holds text does not address a usable
|
|
161
|
-
// scalar, thus the closest reason is that the coordinate is out of range.
|
|
162
|
-
return fail(reference, "locator-out-of-range", `input ${describeReference(input)} did not resolve to a finite number`);
|
|
163
|
-
}
|
|
164
|
-
numbers.push(numeric);
|
|
165
|
-
}
|
|
166
|
-
const a = numbers[0];
|
|
167
|
-
const b = numbers[1];
|
|
168
|
-
if (a === undefined || b === undefined) {
|
|
169
|
-
return fail(reference, "locator-out-of-range", `operation ${reference.op} needs two inputs, and the derivation holds ${numbers.length}`);
|
|
170
|
-
}
|
|
171
|
-
let result;
|
|
172
|
-
switch (reference.op) {
|
|
173
|
-
case "ratio":
|
|
174
|
-
result = a / b;
|
|
175
|
-
break;
|
|
176
|
-
case "delta":
|
|
177
|
-
result = a - b;
|
|
178
|
-
break;
|
|
179
|
-
case "pctChange":
|
|
180
|
-
// A fraction, not a percent. A change of one half gives 0.5, thus an author asserts 0.5 and a
|
|
181
|
-
// `unit` of `%` only tells a renderer how to show it.
|
|
182
|
-
result = (a - b) / b;
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
if (!Number.isFinite(result)) {
|
|
186
|
-
return fail(reference, "locator-out-of-range", `operation ${reference.op} does not yield a finite value, because the divisor is zero`);
|
|
187
|
-
}
|
|
188
|
-
const valueFailure = checkValueAssertion(reference, reference.assert?.value, reference.assert?.tolerance, result);
|
|
189
|
-
return valueFailure !== undefined ? err(valueFailure) : ok({ type: "scalar", value: result });
|
|
190
|
-
}
|
|
191
|
-
function resolveCitation(reference, snapshot) {
|
|
192
|
-
const key = `${reference.idKind}:${reference.id}`;
|
|
193
|
-
if (snapshot.citations === undefined || !snapshot.citations.includes(key)) {
|
|
194
|
-
return fail(reference, "artifact-missing", `the citation ${key} is not in the pinned evidence`);
|
|
195
|
-
}
|
|
196
|
-
const value = { type: "citation", id: key };
|
|
197
|
-
const citationFailure = checkCitationAssertion(reference, reference.assert?.value, key);
|
|
198
|
-
return citationFailure !== undefined ? err(citationFailure) : ok(value);
|
|
199
|
-
}
|
|
200
74
|
async function resolve(reference, snapshot) {
|
|
201
75
|
switch (reference.kind) {
|
|
202
76
|
case "artifact-value":
|
|
@@ -206,7 +80,7 @@ async function resolve(reference, snapshot) {
|
|
|
206
80
|
case "artifact-file":
|
|
207
81
|
return resolveArtifactFile(reference, snapshot);
|
|
208
82
|
case "derivation":
|
|
209
|
-
return resolveDerivation(reference, snapshot);
|
|
83
|
+
return resolveDerivation(reference, snapshot, resolve);
|
|
210
84
|
case "citation":
|
|
211
85
|
return resolveCitation(reference, snapshot);
|
|
212
86
|
}
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* holds the streamed hash and the parsed rows against a stat signature. Two tool calls of one analysis
|
|
14
14
|
* share one cache.
|
|
15
15
|
*
|
|
16
|
-
* The realization calls the shared assert functions in `assert-rules.ts
|
|
17
|
-
*
|
|
18
|
-
* executable specification of the value tier. The two realizations are substitutable behind the seam.
|
|
16
|
+
* The realization calls the shared assert functions in `assert-rules.ts` and the shared reference reads in
|
|
17
|
+
* `reference-read.ts`, as the fixture realization does. Thus one semantics exists, and the fixture stays
|
|
18
|
+
* the executable specification of the value tier. The two realizations are substitutable behind the seam.
|
|
19
19
|
*/
|
|
20
20
|
import { type ReferenceResolver } from "./reference-resolver.js";
|
|
21
21
|
/** One artifact row, as plain cells. A text-backed format holds a numeric column as a string. */
|