@inflexa-ai/harness 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/agents/conversation-agent.d.ts +3 -0
- package/dist/agents/conversation-agent.js +2 -2
- package/dist/agents/sandbox/shared.d.ts +3 -0
- package/dist/agents/sandbox/shared.js +2 -2
- package/dist/app/chat-turn.d.ts +3 -0
- package/dist/app/chat-turn.js +3 -1
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +3 -1
- package/dist/execution/artifact-metadata.d.ts +3 -0
- package/dist/execution/artifact-metadata.js +8 -2
- package/dist/execution/artifact-registration.d.ts +2 -1
- package/dist/execution/artifact-registration.js +8 -2
- package/dist/execution/ephemeral-runner.d.ts +3 -0
- package/dist/execution/ephemeral-runner.js +8 -4
- package/dist/execution/post-step-pipeline.d.ts +3 -0
- package/dist/execution/post-step-pipeline.js +24 -4
- package/dist/execution/reconcile-manifest.d.ts +7 -0
- package/dist/execution/reconcile-manifest.js +19 -5
- package/dist/execution/report-runner.d.ts +3 -0
- package/dist/execution/report-runner.js +4 -4
- package/dist/execution/step-summary.d.ts +3 -0
- package/dist/execution/step-summary.js +4 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/lib/chrome.d.ts +4 -1
- package/dist/lib/chrome.js +10 -7
- package/dist/lib/console-logger.d.ts +19 -0
- package/dist/lib/console-logger.js +62 -0
- package/dist/lib/logger.d.ts +88 -0
- package/dist/lib/logger.js +37 -0
- package/dist/lib/otel.d.ts +2 -1
- package/dist/lib/otel.js +8 -3
- package/dist/runtime/boot.d.ts +2 -2
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/connection-budget.d.ts +2 -2
- package/dist/runtime/connection-budget.js +5 -3
- package/dist/runtime/dbos.d.ts +7 -7
- package/dist/runtime/dbos.js +14 -11
- package/dist/runtime/shutdown.d.ts +2 -2
- package/dist/runtime/shutdown.js +4 -3
- package/dist/sandbox/create-sandbox.d.ts +41 -3
- package/dist/sandbox/create-sandbox.js +39 -19
- package/dist/sandbox/docker-client.d.ts +17 -2
- package/dist/sandbox/docker-client.js +39 -12
- package/dist/sandbox/k8s-client.d.ts +3 -0
- package/dist/sandbox/k8s-client.js +5 -4
- package/dist/sandbox/notification-sweep.d.ts +3 -3
- package/dist/sandbox/notification-sweep.js +4 -2
- package/dist/sandbox/reaper.d.ts +3 -3
- package/dist/sandbox/reaper.js +7 -4
- package/dist/sandbox/watchdog.d.ts +3 -3
- package/dist/sandbox/watchdog.js +8 -2
- package/dist/state/init.d.ts +2 -1
- package/dist/state/init.js +5 -6
- package/dist/tasks/data-profile.d.ts +5 -0
- package/dist/tasks/data-profile.js +24 -17
- package/dist/tools/bio/search-dgidb.d.ts +11 -1
- package/dist/tools/bio/search-dgidb.js +92 -78
- package/dist/tools/iterate-report.d.ts +3 -0
- package/dist/tools/iterate-report.js +3 -1
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +2 -2
- package/dist/tools/workspace/execute-command.d.ts +3 -0
- package/dist/tools/workspace/execute-command.js +3 -1
- package/dist/workflows/execute-analysis.d.ts +3 -0
- package/dist/workflows/execute-analysis.js +11 -7
- package/dist/workflows/execute-target-assessment.d.ts +3 -0
- package/dist/workflows/execute-target-assessment.js +19 -15
- package/dist/workflows/sandbox-step.d.ts +9 -0
- package/dist/workflows/sandbox-step.js +39 -17
- package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
- package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
- package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
- package/dist/workflows/target-assessment/phase5-persist.js +8 -3
- package/dist/workflows/target-assessment/progress.d.ts +2 -1
- package/dist/workflows/target-assessment/progress.js +4 -3
- package/dist/workspace/filesystem.js +163 -74
- package/package.json +95 -96
|
@@ -23,6 +23,7 @@ import { forSubAgent } from "../auth/types.js";
|
|
|
23
23
|
import { createDataProfilerAgent } from "../agents/sandbox/data-profiler.js";
|
|
24
24
|
import { runToTerminal } from "../loop/run-to-terminal.js";
|
|
25
25
|
import { durableStep } from "../loop/run-step.js";
|
|
26
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
26
27
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
27
28
|
import { defineTool } from "../tools/define-tool.js";
|
|
28
29
|
import { renderWorkspace } from "../prompts/briefing.js";
|
|
@@ -128,6 +129,7 @@ export function registerDataProfileWorkflow(deps) {
|
|
|
128
129
|
* 'failed' on error, and revokes the run authorization on every terminal path.
|
|
129
130
|
*/
|
|
130
131
|
export async function runDataProfileBody(input, deps) {
|
|
132
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId: input.analysisId });
|
|
131
133
|
// Ownership defaults to true for inputs persisted before the field existed
|
|
132
134
|
// (a #247 workflow recovered across this deploy): those were always
|
|
133
135
|
// Cortex-owned and must be revoked here.
|
|
@@ -138,9 +140,9 @@ export async function runDataProfileBody(input, deps) {
|
|
|
138
140
|
// embedder populated the tree and handed us this manifest in the workflow
|
|
139
141
|
// input. The body never downloads.
|
|
140
142
|
if (stagedInputs.length === 0) {
|
|
141
|
-
|
|
143
|
+
logger.warn("no input files staged");
|
|
142
144
|
if (!unwrapOrThrow(await completeDataProfile(deps.pool, analysisId)))
|
|
143
|
-
logTerminalNoop(analysisId, "completion");
|
|
145
|
+
logTerminalNoop(logger, analysisId, "completion");
|
|
144
146
|
await deps.runAuthorizer.revoke(authorization, "data-profile-completed");
|
|
145
147
|
return;
|
|
146
148
|
}
|
|
@@ -190,7 +192,7 @@ export async function runDataProfileBody(input, deps) {
|
|
|
190
192
|
``,
|
|
191
193
|
`All file paths in your response must be relative to the analysis root and must EXACTLY match the paths listed above.`,
|
|
192
194
|
].join("\n");
|
|
193
|
-
|
|
195
|
+
logger.info("starting sandbox", { executionId });
|
|
194
196
|
const sandbox = await deps.sandboxClient.createSandbox({
|
|
195
197
|
runId: DATA_PROFILE_RUN_LITERAL,
|
|
196
198
|
stepId: DATA_PROFILE_STEP_LITERAL,
|
|
@@ -310,13 +312,16 @@ export async function runDataProfileBody(input, deps) {
|
|
|
310
312
|
indexed++;
|
|
311
313
|
}
|
|
312
314
|
if (fallbackCount > 0) {
|
|
313
|
-
|
|
315
|
+
logger.warn("input file(s) used a deterministic fallback description — profiler omitted them", {
|
|
316
|
+
fallbackCount,
|
|
317
|
+
stagedCount: stagedInputs.length,
|
|
318
|
+
});
|
|
314
319
|
}
|
|
315
|
-
|
|
320
|
+
logger.info("indexed file(s)", { indexed });
|
|
316
321
|
// 5. Complete — store the FULL profiler finding plus the input snapshot for
|
|
317
322
|
// staleness detection. The scratch tree is gone; this row is all that survives.
|
|
318
323
|
if (!unwrapOrThrow(await completeDataProfile(deps.pool, analysisId, buildDataProfileResult(profilerData, stagedInputs, new Date().toISOString())))) {
|
|
319
|
-
logTerminalNoop(analysisId, "completion");
|
|
324
|
+
logTerminalNoop(logger, analysisId, "completion");
|
|
320
325
|
}
|
|
321
326
|
await deps.runAuthorizer.revoke(authorization, "data-profile-completed");
|
|
322
327
|
}
|
|
@@ -325,14 +330,14 @@ export async function runDataProfileBody(input, deps) {
|
|
|
325
330
|
await deps.sandboxClient.teardown(sandbox);
|
|
326
331
|
}
|
|
327
332
|
catch (teardownErr) {
|
|
328
|
-
|
|
333
|
+
logger.warn("teardown failed (non-fatal)", { executionId, ...logger.errorFields(teardownErr) });
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
336
|
}
|
|
332
337
|
catch (err) {
|
|
333
|
-
|
|
338
|
+
logger.error("profile failed", logger.errorFields(err));
|
|
334
339
|
if (!unwrapOrThrow(await failDataProfile(deps.pool, analysisId, profileFailureReason(err))))
|
|
335
|
-
logTerminalNoop(analysisId, "failure");
|
|
340
|
+
logTerminalNoop(logger, analysisId, "failure");
|
|
336
341
|
await deps.runAuthorizer.revoke(authorization, "data-profile-failed");
|
|
337
342
|
}
|
|
338
343
|
}
|
|
@@ -342,8 +347,8 @@ export async function runDataProfileBody(input, deps) {
|
|
|
342
347
|
* there is no `running` row to stamp. Not an error — the ledger correctly moved
|
|
343
348
|
* on; the workflow still revokes its own authorization on the same terminal path.
|
|
344
349
|
*/
|
|
345
|
-
function logTerminalNoop(analysisId, write) {
|
|
346
|
-
|
|
350
|
+
function logTerminalNoop(logger, analysisId, write) {
|
|
351
|
+
logger.warn("terminal write skipped: ledger row not running (cleared or expired concurrently)", { analysisId, write });
|
|
347
352
|
}
|
|
348
353
|
/**
|
|
349
354
|
* Resolve the `RunSession` the workflow runs under. Whether the authorization
|
|
@@ -409,6 +414,7 @@ async function startDataProfileWorkflow(deps, params) {
|
|
|
409
414
|
* Returns what happened, so the caller can surface it (e.g. in the seed response).
|
|
410
415
|
*/
|
|
411
416
|
export async function triggerDataProfile(deps, params) {
|
|
417
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId: params.analysisId });
|
|
412
418
|
const { analysisId } = params;
|
|
413
419
|
try {
|
|
414
420
|
// ADVISORY pre-check, not the enforcement — the claim CAS carries the same seed
|
|
@@ -420,7 +426,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
420
426
|
// a NULL-seed row (`loadDataProfileStatus` would hide the seed of a cleared row).
|
|
421
427
|
const seeded = unwrapOrThrow(await loadSeedInputFileIds(deps.pool, analysisId));
|
|
422
428
|
if (seeded === null || seeded.length === 0) {
|
|
423
|
-
|
|
429
|
+
logger.error("trigger rejected: no seeded input set (missing analysis row or caller skipped seeding)");
|
|
424
430
|
return "failed";
|
|
425
431
|
}
|
|
426
432
|
// The trigger validates the ledger seed above; it must also validate the manifest
|
|
@@ -429,7 +435,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
429
435
|
// NULL result while the seed still names files, an incoherence a staleness policy
|
|
430
436
|
// then loops on re-triggering. Refuse the divergence before any claim.
|
|
431
437
|
if (params.stagedInputs.length === 0) {
|
|
432
|
-
|
|
438
|
+
logger.error("trigger rejected: empty manifest dispatched against a non-empty seed", { seededCount: seeded.length });
|
|
433
439
|
return "failed";
|
|
434
440
|
}
|
|
435
441
|
const started = unwrapOrThrow(await tryStartDataProfile(deps.pool, analysisId));
|
|
@@ -448,7 +454,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
448
454
|
return "failed";
|
|
449
455
|
}
|
|
450
456
|
catch (err) {
|
|
451
|
-
|
|
457
|
+
logger.error("trigger error", logger.errorFields(err));
|
|
452
458
|
return "failed";
|
|
453
459
|
}
|
|
454
460
|
}
|
|
@@ -462,17 +468,18 @@ export async function triggerDataProfile(deps, params) {
|
|
|
462
468
|
* there is no further channel to report it on.
|
|
463
469
|
*/
|
|
464
470
|
async function compensateStartFailure(deps, analysisId, phase, err) {
|
|
465
|
-
|
|
471
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId });
|
|
472
|
+
logger.error("start failed", { phase, ...logger.errorFields(err) });
|
|
466
473
|
const failed = await failDataProfile(deps.pool, analysisId, profileFailureReason(err));
|
|
467
474
|
if (failed.isErr()) {
|
|
468
|
-
|
|
475
|
+
logger.error("failed to mark failed after a start error", { phase, err: failed.error });
|
|
469
476
|
}
|
|
470
477
|
else if (!failed.value) {
|
|
471
478
|
// The row this compensation was written to fail is no longer `running` — a
|
|
472
479
|
// concurrent clear/expire already moved it on, so the running-CAS refused the
|
|
473
480
|
// stamp. Nothing wedged at `running`, which is the outcome compensation exists
|
|
474
481
|
// to guarantee; just record the no-op.
|
|
475
|
-
logTerminalNoop(analysisId, "compensation");
|
|
482
|
+
logTerminalNoop(logger, analysisId, "compensation");
|
|
476
483
|
}
|
|
477
484
|
}
|
|
478
485
|
/**
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - searchType="gene": input HUGO symbols, returns drugs interacting with each.
|
|
9
9
|
* - searchType="drug": input drug names/concept IDs, returns genes each acts on.
|
|
10
10
|
*/
|
|
11
|
+
import type { Logger } from "../../lib/logger.js";
|
|
11
12
|
interface Interaction {
|
|
12
13
|
geneName: string;
|
|
13
14
|
drugName: string;
|
|
@@ -26,7 +27,16 @@ interface Interaction {
|
|
|
26
27
|
value: string;
|
|
27
28
|
}[];
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* A factory rather than a bare const (unlike its bio-leaf siblings) because it
|
|
32
|
+
* logs: DGIdb answers GraphQL partial errors alongside usable data, and an
|
|
33
|
+
* operator debugging odd interaction results needs to see them. `ToolContext`
|
|
34
|
+
* carries no deps by design, so a tool that logs is dep-bearing — the same
|
|
35
|
+
* reason `createSearchClinvarTool` takes its key that way.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createSearchDgidbTool(deps?: {
|
|
38
|
+
readonly logger?: Logger;
|
|
39
|
+
}): import("../define-tool.js").Tool<{
|
|
30
40
|
query: string | string[];
|
|
31
41
|
searchType: "drug" | "gene";
|
|
32
42
|
minSources: number;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { ok } from "neverthrow";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { defineTool } from "../define-tool.js";
|
|
14
|
+
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
14
15
|
import { apiFetchValidated, describeApiError } from "../lib/api-utils.js";
|
|
15
16
|
const DGIDB_GRAPHQL_URL = "https://dgidb.org/api/graphql";
|
|
16
17
|
const GENE_INTERACTIONS_QUERY = `query($genes: [String!]!) {
|
|
@@ -115,85 +116,98 @@ function applyFilters(interactions, filters) {
|
|
|
115
116
|
}
|
|
116
117
|
return out;
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (res.isErr())
|
|
153
|
-
throw new Error(describeApiError(res.error));
|
|
154
|
-
const body = res.value;
|
|
155
|
-
if (!body || !body.data)
|
|
156
|
-
throw new Error("DGIdb returned no data");
|
|
157
|
-
if (body.errors?.length) {
|
|
158
|
-
console.warn("dgidb.partial_errors", { errors: body.errors.slice(0, 3) });
|
|
159
|
-
}
|
|
160
|
-
const nodes = (isGene ? body.data.genes?.nodes : body.data.drugs?.nodes) ?? [];
|
|
161
|
-
const nodeMap = new Map();
|
|
162
|
-
for (const node of nodes) {
|
|
163
|
-
nodeMap.set(node.name.toLowerCase(), node);
|
|
164
|
-
if (!isGene && node.conceptId) {
|
|
165
|
-
nodeMap.set(node.conceptId.toLowerCase(), node);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const results = inputs.map((input) => {
|
|
169
|
-
const node = nodeMap.get(input.toLowerCase());
|
|
170
|
-
if (!node) {
|
|
171
|
-
return { input, found: false, interactions: [] };
|
|
119
|
+
/**
|
|
120
|
+
* A factory rather than a bare const (unlike its bio-leaf siblings) because it
|
|
121
|
+
* logs: DGIdb answers GraphQL partial errors alongside usable data, and an
|
|
122
|
+
* operator debugging odd interaction results needs to see them. `ToolContext`
|
|
123
|
+
* carries no deps by design, so a tool that logs is dep-bearing — the same
|
|
124
|
+
* reason `createSearchClinvarTool` takes its key that way.
|
|
125
|
+
*/
|
|
126
|
+
export function createSearchDgidbTool(deps = {}) {
|
|
127
|
+
const logger = (deps.logger ?? createNoopLogger()).named("search_dgidb");
|
|
128
|
+
return defineTool({
|
|
129
|
+
id: "search_dgidb",
|
|
130
|
+
description: "Query the Drug-Gene Interaction Database (DGIdb) for drug-gene interactions. " +
|
|
131
|
+
"DGIdb aggregates 30+ sources (ChEMBL, DrugBank, TTD, PharmGKB, GuideToPharmacology, CIViC, etc.) " +
|
|
132
|
+
"and returns source counts as a confidence signal. " +
|
|
133
|
+
"searchType='gene' returns drugs interacting with input HUGO gene symbols (most common: 'what drugs hit my gene set?'). " +
|
|
134
|
+
"searchType='drug' returns the genes each input drug is known to act on. " +
|
|
135
|
+
"Free, no API key required. Sorted by source count desc.",
|
|
136
|
+
inputSchema: z.object({
|
|
137
|
+
query: z
|
|
138
|
+
.union([z.string(), z.array(z.string()).max(50)])
|
|
139
|
+
.describe("Single identifier or list of up to 50. For searchType='gene': HUGO symbols. For searchType='drug': drug names or DGIdb concept IDs."),
|
|
140
|
+
searchType: z.enum(["gene", "drug"]).default("gene"),
|
|
141
|
+
interactionTypes: z
|
|
142
|
+
.array(z.string())
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Case-insensitive substring filter on interaction type. e.g. ['inhibitor', 'antagonist']"),
|
|
145
|
+
sources: z.array(z.string()).optional().describe("Restrict to interactions from these source DBs (case-insensitive substring)."),
|
|
146
|
+
minSources: z.number().int().min(1).default(1).describe("Drop interactions supported by fewer than this many sources."),
|
|
147
|
+
limit: z.number().int().min(1).max(200).default(50).describe("Max interactions per input gene/drug after filtering."),
|
|
148
|
+
}),
|
|
149
|
+
execute: async ({ query, searchType = "gene", interactionTypes, sources, minSources = 1, limit = 50 }) => {
|
|
150
|
+
const inputs = typeof query === "string" ? [query] : query;
|
|
151
|
+
if (inputs.length === 0 || inputs.every((s) => !s.trim())) {
|
|
152
|
+
throw new Error("query must contain at least one identifier");
|
|
172
153
|
}
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
minSources,
|
|
154
|
+
const isGene = searchType === "gene";
|
|
155
|
+
const variableName = isGene ? "genes" : "drugs";
|
|
156
|
+
const queryString = isGene ? GENE_INTERACTIONS_QUERY : DRUG_INTERACTIONS_QUERY;
|
|
157
|
+
const res = await apiFetchValidated(DGIDB_GRAPHQL_URL, DgidbResponseSchema, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: { "Content-Type": "application/json" },
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
query: queryString,
|
|
162
|
+
variables: { [variableName]: inputs },
|
|
163
|
+
}),
|
|
184
164
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
165
|
+
if (res.isErr())
|
|
166
|
+
throw new Error(describeApiError(res.error));
|
|
167
|
+
const body = res.value;
|
|
168
|
+
if (!body || !body.data)
|
|
169
|
+
throw new Error("DGIdb returned no data");
|
|
170
|
+
if (body.errors?.length) {
|
|
171
|
+
logger.warn("dgidb partial errors alongside data", { errors: body.errors.slice(0, 3) });
|
|
172
|
+
}
|
|
173
|
+
const nodes = (isGene ? body.data.genes?.nodes : body.data.drugs?.nodes) ?? [];
|
|
174
|
+
const nodeMap = new Map();
|
|
175
|
+
for (const node of nodes) {
|
|
176
|
+
nodeMap.set(node.name.toLowerCase(), node);
|
|
177
|
+
if (!isGene && node.conceptId) {
|
|
178
|
+
nodeMap.set(node.conceptId.toLowerCase(), node);
|
|
188
179
|
}
|
|
189
|
-
|
|
180
|
+
}
|
|
181
|
+
const results = inputs.map((input) => {
|
|
182
|
+
const node = nodeMap.get(input.toLowerCase());
|
|
183
|
+
if (!node) {
|
|
184
|
+
return { input, found: false, interactions: [] };
|
|
185
|
+
}
|
|
186
|
+
const inputSide = isGene
|
|
187
|
+
? { geneName: node.name }
|
|
188
|
+
: {
|
|
189
|
+
drugName: node.name,
|
|
190
|
+
drugConceptId: node.conceptId ?? undefined,
|
|
191
|
+
};
|
|
192
|
+
const mapped = (node.interactions ?? []).map((raw) => mapInteraction(raw, searchType, inputSide));
|
|
193
|
+
const filtered = applyFilters(mapped, {
|
|
194
|
+
interactionTypes,
|
|
195
|
+
sources,
|
|
196
|
+
minSources,
|
|
197
|
+
});
|
|
198
|
+
const sorted = filtered.sort((a, b) => {
|
|
199
|
+
if (b.sourceCount !== a.sourceCount) {
|
|
200
|
+
return b.sourceCount - a.sourceCount;
|
|
201
|
+
}
|
|
202
|
+
return (b.interactionScore ?? 0) - (a.interactionScore ?? 0);
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
input,
|
|
206
|
+
found: true,
|
|
207
|
+
interactions: sorted.slice(0, limit),
|
|
208
|
+
};
|
|
190
209
|
});
|
|
191
|
-
return {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
};
|
|
196
|
-
});
|
|
197
|
-
return ok({ results });
|
|
198
|
-
},
|
|
199
|
-
});
|
|
210
|
+
return ok({ results });
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
@@ -47,6 +47,7 @@ import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
|
47
47
|
import { type StagedAsset } from "./lib/report-preflight.js";
|
|
48
48
|
import type { PreviewPublisher } from "./report/preview-publisher.js";
|
|
49
49
|
import type { ChromeConfig } from "../lib/chrome.js";
|
|
50
|
+
import type { Logger } from "../lib/logger.js";
|
|
50
51
|
/**
|
|
51
52
|
* The report brief the conversation agent composes: title/audience + sources +
|
|
52
53
|
* the section union. This is the ~12k-char schema that `plan_report` hands back
|
|
@@ -183,6 +184,8 @@ export declare const submitReportInputSchema: z.ZodObject<{
|
|
|
183
184
|
*/
|
|
184
185
|
export declare const planReportTool: Tool;
|
|
185
186
|
export interface SubmitReportDeps {
|
|
187
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
188
|
+
readonly logger?: Logger;
|
|
186
189
|
readonly provider: ChatProvider;
|
|
187
190
|
readonly pool: Pool;
|
|
188
191
|
/** Embedder-supplied workspace-root resolution seam (see workspace/paths.ts). */
|
|
@@ -49,6 +49,7 @@ import { defineTool } from "./define-tool.js";
|
|
|
49
49
|
import { previewDir } from "../workspace/paths.js";
|
|
50
50
|
import { runReportIteration } from "../execution/report-runner.js";
|
|
51
51
|
import { formatBytes, stageReportAssets } from "./lib/report-preflight.js";
|
|
52
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
52
53
|
const REPORT_TOOL_ACCESS_TTL_SECONDS = 3600;
|
|
53
54
|
const PREVIEW_META_FILE = "preview-meta.json";
|
|
54
55
|
async function readPreviewMeta(metaPath) {
|
|
@@ -577,7 +578,8 @@ export function createReportSubmitTool(deps) {
|
|
|
577
578
|
});
|
|
578
579
|
}
|
|
579
580
|
catch (err) {
|
|
580
|
-
|
|
581
|
+
const logger = (deps.logger ?? createNoopLogger()).named("submit-report");
|
|
582
|
+
logger.warn("failed to persist preview meta", logger.errorFields(err));
|
|
581
583
|
}
|
|
582
584
|
}
|
|
583
585
|
const previewTitle = brief?.title ?? existingMeta?.title ?? "Report";
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
import { type ChatProvider } from "../../providers/types.js";
|
|
18
18
|
import { type Tool } from "../define-tool.js";
|
|
19
19
|
import { type BioToolKeys } from "../bio/keys.js";
|
|
20
|
+
import type { Logger } from "../../lib/logger.js";
|
|
20
21
|
export interface LiteratureReviewerDeps {
|
|
22
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
23
|
+
readonly logger?: Logger;
|
|
21
24
|
/** The LLM seam the child loop runs on. */
|
|
22
25
|
readonly provider: ChatProvider;
|
|
23
26
|
/** Model id — provenance / metric label; the provider owns the wire model. */
|
|
@@ -26,7 +26,7 @@ import { createNcbiTools } from "../bio/keys.js";
|
|
|
26
26
|
import { getImpcKoProfileTool } from "../bio/get-impc-ko-profile.js";
|
|
27
27
|
import { lookupGoTermTool } from "../bio/lookup-go-term.js";
|
|
28
28
|
import { searchBgeeExpressionTool } from "../bio/search-bgee-expression.js";
|
|
29
|
-
import {
|
|
29
|
+
import { createSearchDgidbTool } from "../bio/search-dgidb.js";
|
|
30
30
|
import { searchGeneTool } from "../bio/search-gene.js";
|
|
31
31
|
import { searchInteractionsTool } from "../bio/search-interactions.js";
|
|
32
32
|
import { searchPathwayTool } from "../bio/search-pathway.js";
|
|
@@ -43,7 +43,7 @@ export function createLiteratureReviewerTool(deps) {
|
|
|
43
43
|
lookupGoTermTool,
|
|
44
44
|
searchInteractionsTool,
|
|
45
45
|
ncbi.pubmed,
|
|
46
|
-
|
|
46
|
+
createSearchDgidbTool({ ...(deps.logger ? { logger: deps.logger } : {}) }),
|
|
47
47
|
searchBgeeExpressionTool,
|
|
48
48
|
getImpcKoProfileTool,
|
|
49
49
|
];
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
import type { SandboxClient } from "../../sandbox/client.js";
|
|
23
23
|
import type { SandboxRef } from "../../sandbox/types.js";
|
|
24
24
|
import type { ProvenanceCollector } from "../../provenance/collector.js";
|
|
25
|
+
import type { Logger } from "../../lib/logger.js";
|
|
25
26
|
export interface ExecuteCommandDeps {
|
|
27
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
28
|
+
readonly logger?: Logger;
|
|
26
29
|
readonly sandboxClient: SandboxClient;
|
|
27
30
|
/** Live sandbox handle for the step. Created once per step at composition root. */
|
|
28
31
|
readonly sandbox: SandboxRef;
|
|
@@ -26,6 +26,7 @@ import { defineTool } from "../define-tool.js";
|
|
|
26
26
|
import { feedExecFrame } from "../../provenance/exec-frame.js";
|
|
27
27
|
import { boundExecResult } from "./result-bounds.js";
|
|
28
28
|
import { runSandboxExec } from "./run-exec.js";
|
|
29
|
+
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
29
30
|
const ExecuteCommandInputSchema = z.object({
|
|
30
31
|
command: z
|
|
31
32
|
.array(z.string().min(1))
|
|
@@ -86,7 +87,8 @@ export function createExecuteCommandTool(deps) {
|
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
catch (err) {
|
|
89
|
-
|
|
90
|
+
const logger = (deps.logger ?? createNoopLogger()).named("execute_command");
|
|
91
|
+
logger.warn("provenance frame handling failed (non-fatal)", { execId, ...logger.errorFields(err) });
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
return ok({ status: "ok", ...boundExecResult(result) });
|
|
@@ -57,6 +57,7 @@ import type { Pool } from "pg";
|
|
|
57
57
|
import type { MachineBudget, ResourceSpec } from "../config/resource-limits.js";
|
|
58
58
|
import type { RunSession } from "../auth/types.js";
|
|
59
59
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
60
|
+
import type { Logger } from "../lib/logger.js";
|
|
60
61
|
import type { AnalysisStep } from "../schemas/workflow-state.js";
|
|
61
62
|
import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
62
63
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
@@ -206,6 +207,8 @@ export type RunProvenanceEvent = {
|
|
|
206
207
|
* embedder, model, keys, roots) the in-body synthesis needs.
|
|
207
208
|
*/
|
|
208
209
|
export interface ExecuteAnalysisDeps {
|
|
210
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
211
|
+
readonly logger?: Logger;
|
|
209
212
|
readonly pool: Pool;
|
|
210
213
|
/** Chat provider for the in-body literature-grounded synthesis. */
|
|
211
214
|
readonly provider: ChatProvider;
|
|
@@ -57,6 +57,7 @@ import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
|
57
57
|
import { mkdir } from "node:fs/promises";
|
|
58
58
|
import { join } from "node:path";
|
|
59
59
|
import { forStep } from "../auth/types.js";
|
|
60
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
60
61
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
61
62
|
import { RunDedupCollisionError, countArtifactsForRun, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, seedStepExecutions, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, } from "../state/index.js";
|
|
62
63
|
import { isBudgetExceeded } from "../loop/budget-exceeded.js";
|
|
@@ -81,13 +82,14 @@ function emitStreamPart(part) {
|
|
|
81
82
|
* host's own ledger, not here.
|
|
82
83
|
*/
|
|
83
84
|
function emitProvenanceGuarded(deps, event) {
|
|
85
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis");
|
|
84
86
|
if (!deps.emitProvenance)
|
|
85
87
|
return;
|
|
86
88
|
try {
|
|
87
89
|
deps.emitProvenance(event);
|
|
88
90
|
}
|
|
89
91
|
catch (err) {
|
|
90
|
-
|
|
92
|
+
logger.error("emitProvenance threw", { runId: event.runId, event: event.type, ...logger.errorFields(err) });
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
/**
|
|
@@ -261,6 +263,7 @@ export function registerExecuteAnalysis(deps) {
|
|
|
261
263
|
* (the DBOS calls inside still rely on a workflow context being present).
|
|
262
264
|
*/
|
|
263
265
|
export async function runExecuteAnalysisBody(input, deps) {
|
|
266
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis").with({ analysisId: input.analysisId });
|
|
264
267
|
// (0) Validate up-front — no side effects yet, so a malformed plan does
|
|
265
268
|
// NOT leak a runId, run authorization, or running charge.
|
|
266
269
|
validatePlanDag(input.steps);
|
|
@@ -344,7 +347,7 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
344
347
|
}
|
|
345
348
|
catch (err) {
|
|
346
349
|
synthesisError = err;
|
|
347
|
-
|
|
350
|
+
logger.error("synthesizeFindings failed — failing the run", { runId, ...logger.errorFields(err) });
|
|
348
351
|
}
|
|
349
352
|
}
|
|
350
353
|
// (6) collectAndComplete — terminal block. Runs on EVERY path. When synthesis
|
|
@@ -693,6 +696,7 @@ async function cancelInFlight(inFlight, cause, canceledByParent) {
|
|
|
693
696
|
}
|
|
694
697
|
async function collectAndComplete(args) {
|
|
695
698
|
const { input, runId, workflowId, startedAtMs, completed, failed, canceled, budgetExceeded, failureReason, forceFailed, deps } = args;
|
|
699
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis").with({ runId, analysisId: input.analysisId });
|
|
696
700
|
const status = forceFailed
|
|
697
701
|
? "failed"
|
|
698
702
|
: deriveFinalStatus({
|
|
@@ -735,7 +739,7 @@ async function collectAndComplete(args) {
|
|
|
735
739
|
}, { name: "persist-final-status" });
|
|
736
740
|
}
|
|
737
741
|
catch (err) {
|
|
738
|
-
|
|
742
|
+
logger.error("persist-final-status failed", { status, ...logger.errorFields(err) });
|
|
739
743
|
}
|
|
740
744
|
// Sweep never-started steps to `skipped` — but ONLY on genuinely-terminal
|
|
741
745
|
// paths. The gate is the BRANCH, not the written run status: the resumable
|
|
@@ -749,7 +753,7 @@ async function collectAndComplete(args) {
|
|
|
749
753
|
}, { name: "sweep-pending-steps" });
|
|
750
754
|
}
|
|
751
755
|
catch (err) {
|
|
752
|
-
|
|
756
|
+
logger.error("sweep-pending-steps failed", logger.errorFields(err));
|
|
753
757
|
}
|
|
754
758
|
}
|
|
755
759
|
// A forced failure (synthesis threw) is terminal, not a resumable budget
|
|
@@ -761,7 +765,7 @@ async function collectAndComplete(args) {
|
|
|
761
765
|
}, { name: "suspend-analysis" });
|
|
762
766
|
}
|
|
763
767
|
catch (err) {
|
|
764
|
-
|
|
768
|
+
logger.error("suspend-analysis failed", logger.errorFields(err));
|
|
765
769
|
}
|
|
766
770
|
}
|
|
767
771
|
try {
|
|
@@ -773,7 +777,7 @@ async function collectAndComplete(args) {
|
|
|
773
777
|
}), { name: "close-running-charge" });
|
|
774
778
|
}
|
|
775
779
|
catch (err) {
|
|
776
|
-
|
|
780
|
+
logger.error("closeRunningCharge failed", { chargeReason, ...logger.errorFields(err) });
|
|
777
781
|
}
|
|
778
782
|
// Revoke the run authorization for the terminal run state. Ownership
|
|
779
783
|
// defaults to true for inputs persisted before the field existed (a
|
|
@@ -786,7 +790,7 @@ async function collectAndComplete(args) {
|
|
|
786
790
|
await DBOS.runStep(() => deps.runAuthorizer.revoke(authorization, revokeReason), { name: "revoke-run-auth" });
|
|
787
791
|
}
|
|
788
792
|
catch (err) {
|
|
789
|
-
|
|
793
|
+
logger.error("revokeRunAuthorization failed", { revokeReason, ...logger.errorFields(err) });
|
|
790
794
|
}
|
|
791
795
|
// The `run_completed` provenance was already emitted above (before the status write, to
|
|
792
796
|
// beat the CLI's poll-and-shutdown). These branches only fan out the UI stream part, whose
|
|
@@ -34,6 +34,7 @@ import type { Pool } from "pg";
|
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
import type { RunSession } from "../auth/types.js";
|
|
36
36
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
37
|
+
import type { Logger } from "../lib/logger.js";
|
|
37
38
|
import type { AgentChat } from "../providers/types.js";
|
|
38
39
|
import { TargetAssessmentInputSchema } from "./target-assessment/schemas.js";
|
|
39
40
|
import { type ProgressPhase } from "./target-assessment/progress.js";
|
|
@@ -69,6 +70,8 @@ export interface ExecuteTargetAssessmentResult {
|
|
|
69
70
|
readonly bytes: number;
|
|
70
71
|
}
|
|
71
72
|
export interface ExecuteTargetAssessmentDeps {
|
|
73
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
74
|
+
readonly logger?: Logger;
|
|
72
75
|
readonly pool: Pool;
|
|
73
76
|
/** Run-authorization seam — the terminal path revokes through `revoke`. */
|
|
74
77
|
readonly runAuthorizer: RunAuthorizer;
|