@inflexa-ai/harness 0.2.0 → 0.3.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.
Files changed (80) hide show
  1. package/README.md +48 -0
  2. package/dist/agents/conversation-agent.d.ts +3 -0
  3. package/dist/agents/conversation-agent.js +2 -2
  4. package/dist/agents/sandbox/shared.d.ts +3 -0
  5. package/dist/agents/sandbox/shared.js +2 -2
  6. package/dist/app/chat-turn.d.ts +3 -0
  7. package/dist/app/chat-turn.js +3 -1
  8. package/dist/app/synthesize-run.d.ts +3 -0
  9. package/dist/app/synthesize-run.js +3 -1
  10. package/dist/execution/artifact-metadata.d.ts +3 -0
  11. package/dist/execution/artifact-metadata.js +8 -2
  12. package/dist/execution/artifact-registration.d.ts +2 -1
  13. package/dist/execution/artifact-registration.js +8 -2
  14. package/dist/execution/ephemeral-runner.d.ts +3 -0
  15. package/dist/execution/ephemeral-runner.js +8 -4
  16. package/dist/execution/post-step-pipeline.d.ts +3 -0
  17. package/dist/execution/post-step-pipeline.js +24 -4
  18. package/dist/execution/reconcile-manifest.d.ts +7 -0
  19. package/dist/execution/reconcile-manifest.js +19 -5
  20. package/dist/execution/report-runner.d.ts +3 -0
  21. package/dist/execution/report-runner.js +4 -4
  22. package/dist/execution/step-summary.d.ts +3 -0
  23. package/dist/execution/step-summary.js +4 -2
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.js +5 -0
  26. package/dist/lib/chrome.d.ts +4 -1
  27. package/dist/lib/chrome.js +10 -7
  28. package/dist/lib/console-logger.d.ts +19 -0
  29. package/dist/lib/console-logger.js +62 -0
  30. package/dist/lib/logger.d.ts +88 -0
  31. package/dist/lib/logger.js +37 -0
  32. package/dist/lib/otel.d.ts +2 -1
  33. package/dist/lib/otel.js +8 -3
  34. package/dist/runtime/boot.d.ts +2 -2
  35. package/dist/runtime/boot.js +1 -1
  36. package/dist/runtime/connection-budget.d.ts +2 -2
  37. package/dist/runtime/connection-budget.js +5 -3
  38. package/dist/runtime/dbos.d.ts +7 -7
  39. package/dist/runtime/dbos.js +14 -11
  40. package/dist/runtime/shutdown.d.ts +2 -2
  41. package/dist/runtime/shutdown.js +4 -3
  42. package/dist/sandbox/create-sandbox.d.ts +2 -2
  43. package/dist/sandbox/docker-client.d.ts +2 -2
  44. package/dist/sandbox/docker-client.js +4 -1
  45. package/dist/sandbox/k8s-client.d.ts +3 -0
  46. package/dist/sandbox/k8s-client.js +5 -4
  47. package/dist/sandbox/notification-sweep.d.ts +3 -3
  48. package/dist/sandbox/notification-sweep.js +4 -2
  49. package/dist/sandbox/reaper.d.ts +3 -3
  50. package/dist/sandbox/reaper.js +7 -4
  51. package/dist/sandbox/watchdog.d.ts +3 -3
  52. package/dist/sandbox/watchdog.js +8 -2
  53. package/dist/state/init.d.ts +2 -1
  54. package/dist/state/init.js +5 -6
  55. package/dist/tasks/data-profile.d.ts +5 -0
  56. package/dist/tasks/data-profile.js +24 -17
  57. package/dist/tools/bio/search-dgidb.d.ts +11 -1
  58. package/dist/tools/bio/search-dgidb.js +92 -78
  59. package/dist/tools/iterate-report.d.ts +3 -0
  60. package/dist/tools/iterate-report.js +3 -1
  61. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  62. package/dist/tools/research/literature-reviewer.js +2 -2
  63. package/dist/tools/workspace/execute-command.d.ts +3 -0
  64. package/dist/tools/workspace/execute-command.js +3 -1
  65. package/dist/workflows/execute-analysis.d.ts +3 -0
  66. package/dist/workflows/execute-analysis.js +11 -7
  67. package/dist/workflows/execute-target-assessment.d.ts +3 -0
  68. package/dist/workflows/execute-target-assessment.js +19 -15
  69. package/dist/workflows/sandbox-step.d.ts +9 -0
  70. package/dist/workflows/sandbox-step.js +39 -17
  71. package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
  72. package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
  73. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
  74. package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
  75. package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
  76. package/dist/workflows/target-assessment/phase5-persist.js +8 -3
  77. package/dist/workflows/target-assessment/progress.d.ts +2 -1
  78. package/dist/workflows/target-assessment/progress.js +4 -3
  79. package/dist/workspace/filesystem.js +163 -74
  80. package/package.json +95 -96
@@ -29,8 +29,8 @@
29
29
  * effective capabilities.
30
30
  */
31
31
  import Docker from "dockerode";
32
- import type pino from "pino";
33
32
  import { ResultAsync } from "neverthrow";
33
+ import type { Logger } from "../lib/logger.js";
34
34
  import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
35
35
  import { type SandboxError } from "./sandbox-error.js";
36
36
  import type { CreateSandboxMeta, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxTransport } from "./types.js";
@@ -62,7 +62,7 @@ export interface DockerClientConfig {
62
62
  * vanished or went incomplete by sandbox-create time) is observable instead of a
63
63
  * silent libs-mount drop. Matches the `reaper`/`watchdog` logger seam.
64
64
  */
65
- logger?: Pick<pino.Logger, "info" | "warn" | "error">;
65
+ logger?: Logger;
66
66
  /** Hook called after the registry row is written. */
67
67
  registerSandbox: (meta: CreateSandboxMeta, ref: SandboxRef) => Promise<void>;
68
68
  }
@@ -32,6 +32,7 @@ import { existsSync, statSync } from "node:fs";
32
32
  import { join } from "node:path";
33
33
  import Docker from "dockerode";
34
34
  import { ResultAsync, err, ok } from "neverthrow";
35
+ import { createNoopLogger } from "../lib/console-logger.js";
35
36
  import { stepWritePrefix } from "../workspace/paths.js";
36
37
  import { trySandbox } from "./sandbox-error.js";
37
38
  import { buildMountPlan } from "./mount-plan.js";
@@ -158,7 +159,9 @@ export function createDockerSandboxOps(config) {
158
159
  // since an otherwise-silent libs-mount drop is invisible to operators.
159
160
  const libsMounted = !!config.libStorePath && libStoreUsable(config.libStorePath);
160
161
  if (config.libStorePath && !libsMounted) {
161
- config.logger?.warn({ libStorePath: config.libStorePath, sandboxId }, "[docker-client] lib store configured but `current` is missing or incomplete at sandbox creation — mounting no library store (sandbox degrades to available:false)");
162
+ (config.logger ?? createNoopLogger())
163
+ .named("docker-client")
164
+ .warn("lib store configured but `current` is missing or incomplete at sandbox creation — mounting no library store (sandbox degrades to available:false)", { libStorePath: config.libStorePath, sandboxId });
162
165
  }
163
166
  const plan = buildMountPlan(meta, {
164
167
  libs: libsMounted,
@@ -16,6 +16,7 @@ import { ResultAsync } from "neverthrow";
16
16
  import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
17
17
  import { type SandboxError } from "./sandbox-error.js";
18
18
  import type { CreateSandboxMeta, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxTransport } from "./types.js";
19
+ import type { Logger } from "../lib/logger.js";
19
20
  /**
20
21
  * Coerce an arbitrary identifier into a valid K8s label value:
21
22
  * ≤63 chars, `[A-Za-z0-9._-]` only, starting and ending alphanumeric.
@@ -27,6 +28,8 @@ import type { CreateSandboxMeta, ManagedSandbox, SandboxIdentity, SandboxLivenes
27
28
  */
28
29
  export declare function sanitizeLabelValue(value: string): string;
29
30
  export interface K8sClientConfig {
31
+ /** Operational logging seam; omitted falls back to no-op. */
32
+ readonly logger?: Logger;
30
33
  image: string;
31
34
  cortexBaseUrl: string;
32
35
  /**
@@ -16,6 +16,7 @@ import { BatchV1Api, CoreV1Api, KubeConfig } from "@kubernetes/client-node";
16
16
  import { ResultAsync, err, ok } from "neverthrow";
17
17
  import { trySandbox } from "./sandbox-error.js";
18
18
  import { buildMountPlan, buildSessionSubPaths } from "./mount-plan.js";
19
+ import { createNoopLogger } from "../lib/console-logger.js";
19
20
  /** Read the originating HTTP status off any `SandboxError` variant that carries one. */
20
21
  function statusOf(e) {
21
22
  return "status" in e ? e.status : undefined;
@@ -410,12 +411,12 @@ export function createK8sSandboxOps(config) {
410
411
  cause,
411
412
  }));
412
413
  if (registered.isErr()) {
413
- await cleanupFailedJob(batchApi, config.namespace, sandboxId);
414
+ await cleanupFailedJob(batchApi, config.namespace, sandboxId, config.logger ?? createNoopLogger());
414
415
  return err(registered.error);
415
416
  }
416
417
  return ok(ref);
417
418
  }
418
- await cleanupFailedJob(batchApi, config.namespace, sandboxId);
419
+ await cleanupFailedJob(batchApi, config.namespace, sandboxId, config.logger ?? createNoopLogger());
419
420
  return err(ready.error);
420
421
  })());
421
422
  },
@@ -480,9 +481,9 @@ export function createK8sSandboxOps(config) {
480
481
  * matters; a delete error here is logged and swallowed so it never masks the
481
482
  * create error returned to the caller.
482
483
  */
483
- async function cleanupFailedJob(batchApi, namespace, sandboxId) {
484
+ async function cleanupFailedJob(batchApi, namespace, sandboxId, logger) {
484
485
  const deleted = await deleteJobIgnoreMissing(batchApi, namespace, sandboxId);
485
486
  if (deleted.isErr()) {
486
- console.error(`K8sSandbox: failed to delete Job ${sandboxId} after startup failure`, deleted.error);
487
+ logger.named("k8s-client").error("failed to delete Job after startup failure", { sandboxId, err: deleted.error });
487
488
  }
488
489
  }
@@ -17,14 +17,14 @@
17
17
  * injectable so tests can drive the sweep against a stub.
18
18
  */
19
19
  import type { Pool } from "pg";
20
- import type pino from "pino";
20
+ import type { Logger } from "../lib/logger.js";
21
21
  export interface SweepDeps {
22
22
  /**
23
23
  * Execute the bounded DELETE. Returns the row count cleared. Injected
24
24
  * for tests; production wires the app pool.
25
25
  */
26
26
  deleteStale: (limit: number) => Promise<number>;
27
- logger?: Pick<pino.Logger, "info" | "warn" | "error">;
27
+ logger?: Logger;
28
28
  }
29
29
  export declare function sweepStaleNotifications(deps: SweepDeps): Promise<number>;
30
30
  /**
@@ -35,7 +35,7 @@ export declare function sweepStaleNotifications(deps: SweepDeps): Promise<number
35
35
  export declare function makeDefaultDeleteStale(pool: Pool): (limit: number) => Promise<number>;
36
36
  export interface RegisterNotificationSweepDeps {
37
37
  pool: Pool;
38
- logger?: Pick<pino.Logger, "info" | "warn" | "error">;
38
+ logger?: Logger;
39
39
  /** Override the DELETE driver — production defaults to the app pool. */
40
40
  deleteStale?: (limit: number) => Promise<number>;
41
41
  }
@@ -17,16 +17,18 @@
17
17
  * injectable so tests can drive the sweep against a stub.
18
18
  */
19
19
  import { DBOS } from "@dbos-inc/dbos-sdk";
20
+ import { createNoopLogger } from "../lib/console-logger.js";
20
21
  const SWEEP_CRON = "0 */5 * * * *";
21
22
  const DELETE_BATCH_LIMIT = 10_000;
22
23
  export async function sweepStaleNotifications(deps) {
24
+ const logger = (deps.logger ?? createNoopLogger()).named("notification-sweep");
23
25
  try {
24
26
  const cleared = await deps.deleteStale(DELETE_BATCH_LIMIT);
25
- deps.logger?.info({ rowsCleared: cleared }, "[notification-sweep] cleared stale notifications");
27
+ logger.info("cleared stale notifications", { rowsCleared: cleared });
26
28
  return cleared;
27
29
  }
28
30
  catch (err) {
29
- deps.logger?.error({ err: err instanceof Error ? err.message : String(err) }, "[notification-sweep] sweep failed");
31
+ logger.error("sweep failed", logger.errorFields(err));
30
32
  throw err;
31
33
  }
32
34
  }
@@ -18,8 +18,8 @@
18
18
  * hoisted for unit testing without a DBOS runtime; `registerSandboxReaper` is
19
19
  * the thin production wiring.
20
20
  */
21
- import type pino from "pino";
22
21
  import type { Pool } from "pg";
22
+ import type { Logger } from "../lib/logger.js";
23
23
  import type { SandboxClient } from "./client.js";
24
24
  import type { ManagedSandbox } from "./types.js";
25
25
  export type ReapDecision = "leave" | "reap";
@@ -46,7 +46,7 @@ export interface ReaperDeps {
46
46
  graceMs?: number;
47
47
  /** Wall-clock ms; injected for tests. */
48
48
  nowMs?: () => number;
49
- logger?: Pick<pino.Logger, "info" | "warn" | "error">;
49
+ logger?: Logger;
50
50
  }
51
51
  export interface ReapSummary {
52
52
  managedCount: number;
@@ -64,7 +64,7 @@ export interface RegisterReaperDeps {
64
64
  pool: Pool;
65
65
  sandboxClient: Pick<SandboxClient, "listManagedSandboxes" | "teardownById">;
66
66
  graceMs?: number;
67
- logger?: Pick<pino.Logger, "info" | "warn" | "error">;
67
+ logger?: Logger;
68
68
  }
69
69
  /** Production registration: a single (unsharded) `@DBOS.scheduled` workflow. */
70
70
  export declare function registerSandboxReaper(deps: RegisterReaperDeps): void;
@@ -19,6 +19,7 @@
19
19
  * the thin production wiring.
20
20
  */
21
21
  import { DBOS } from "@dbos-inc/dbos-sdk";
22
+ import { createNoopLogger } from "../lib/console-logger.js";
22
23
  import { unwrapOrThrow } from "../lib/result.js";
23
24
  import { reconcileReapedSandbox } from "../state/active-sandboxes.js";
24
25
  const REAPER_CRON = "0 */5 * * * *";
@@ -65,6 +66,7 @@ export function terminalStepStatus(workflowStatus) {
65
66
  * failure on one machine is logged and skipped — the next sweep retries.
66
67
  */
67
68
  export async function reapOnce(deps) {
69
+ const logger = (deps.logger ?? createNoopLogger()).named("sandbox-reaper");
68
70
  const nowMs = (deps.nowMs ?? (() => Date.now()))();
69
71
  const graceMs = deps.graceMs ?? DEFAULT_GRACE_MS;
70
72
  const managed = await deps.sandboxClient.listManagedSandboxes();
@@ -85,10 +87,10 @@ export async function reapOnce(deps) {
85
87
  rowsReconciled++;
86
88
  }
87
89
  catch (err) {
88
- deps.logger?.warn({
90
+ logger.warn("reap failed — skipping this round", {
89
91
  sandboxId: sb.sandboxId,
90
- err: err instanceof Error ? err.message : String(err),
91
- }, "[sandbox-reaper] reap failed — skipping this round");
92
+ ...logger.errorFields(err),
93
+ });
92
94
  }
93
95
  }
94
96
  const summary = {
@@ -97,7 +99,8 @@ export async function reapOnce(deps) {
97
99
  rowsReconciled,
98
100
  leftCount,
99
101
  };
100
- deps.logger?.info(summary, "[sandbox-reaper] sweep completed");
102
+ // Spread: an interface has no implicit index signature, so it needs widening to `LogFields`.
103
+ logger.info("sweep completed", { ...summary });
101
104
  return summary;
102
105
  }
103
106
  /** Production registration: a single (unsharded) `@DBOS.scheduled` workflow. */
@@ -18,8 +18,8 @@
18
18
  * without spinning a DBOS runtime; `registerWatchdog` is the thin
19
19
  * production wiring.
20
20
  */
21
- import type pino from "pino";
22
21
  import type { ResultAsync } from "neverthrow";
22
+ import type { Logger } from "../lib/logger.js";
23
23
  import type { DbError } from "../lib/db-result.js";
24
24
  import type { ActiveSandboxRow } from "../state/index.js";
25
25
  import type { SandboxClient } from "./client.js";
@@ -39,7 +39,7 @@ export interface CheckShardDeps {
39
39
  * this is `DBOS.send`; injected here for testability.
40
40
  */
41
41
  sendSynthetic: (workflowId: string, execId: string, failure: ExecResult, reason: string) => Promise<void>;
42
- logger?: Pick<pino.Logger, "info" | "warn" | "error">;
42
+ logger?: Logger;
43
43
  }
44
44
  export interface CheckShardSummary {
45
45
  activeCount: number;
@@ -61,7 +61,7 @@ export interface WatchdogDeps {
61
61
  queryActiveSandboxes: () => ResultAsync<ActiveSandboxRow[], DbError>;
62
62
  sandboxClient: SandboxClient;
63
63
  shardCount?: number;
64
- logger?: Pick<pino.Logger, "info" | "warn" | "error">;
64
+ logger?: Logger;
65
65
  }
66
66
  /**
67
67
  * Production registration. Builds the parent + child registered workflows
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import { createHash } from "node:crypto";
22
22
  import { DBOS } from "@dbos-inc/dbos-sdk";
23
+ import { createNoopLogger } from "../lib/console-logger.js";
23
24
  import { unwrapOrThrow } from "../lib/result.js";
24
25
  import { workflowIdFromExec } from "./exec-id.js";
25
26
  import { syntheticFailureReason, syntheticFailureResult } from "./liveness.js";
@@ -48,6 +49,7 @@ const ACTIVE_WORKFLOW_STATUSES = new Set(["PENDING", "ENQUEUED"]);
48
49
  * notification.
49
50
  */
50
51
  export async function checkShard(rows, deps) {
52
+ const logger = (deps.logger ?? createNoopLogger()).named("sandbox-watchdog");
51
53
  let deadCount = 0;
52
54
  let syntheticSends = 0;
53
55
  let liveWorkflowsSkipped = 0;
@@ -64,7 +66,10 @@ export async function checkShard(rows, deps) {
64
66
  liveness = await deps.isAlive(ref);
65
67
  }
66
68
  catch (err) {
67
- deps.logger?.warn({ sandboxId: row.sandboxRef.sandboxId, err: err instanceof Error ? err.message : String(err) }, "[sandbox-watchdog] isAlive threw — skipping this round");
69
+ logger.warn("isAlive threw — skipping this round", {
70
+ sandboxId: row.sandboxRef.sandboxId,
71
+ ...logger.errorFields(err),
72
+ });
68
73
  continue;
69
74
  }
70
75
  if (liveness.alive)
@@ -92,7 +97,8 @@ export async function checkShard(rows, deps) {
92
97
  syntheticSends,
93
98
  liveWorkflowsSkipped,
94
99
  };
95
- deps.logger?.info(summary, "[sandbox-watchdog] shard check completed");
100
+ // Spread: an interface has no implicit index signature, so it needs widening to `LogFields`.
101
+ logger.info("shard check completed", { ...summary });
96
102
  return summary;
97
103
  }
98
104
  /** Encodes the synthetic-failure marker on the per-exec topic envelope. */
@@ -7,8 +7,9 @@
7
7
  * simultaneous startups don't race on DDL.
8
8
  */
9
9
  import type { Pool } from "pg";
10
+ import type { Logger } from "../lib/logger.js";
10
11
  /**
11
12
  * Initialize Cortex state tables. Safe to call on every startup and across
12
13
  * concurrent replicas (advisory lock serializes the init flow).
13
14
  */
14
- export declare function initCortexState(pool: Pool): Promise<void>;
15
+ export declare function initCortexState(pool: Pool, injected?: Logger): Promise<void>;
@@ -7,6 +7,7 @@
7
7
  * simultaneous startups don't race on DDL.
8
8
  */
9
9
  import { backfillAiSdkMessageEnvelopes } from "../memory/message-backfill.js";
10
+ import { createNoopLogger } from "../lib/console-logger.js";
10
11
  const DDL = `
11
12
  -- Analysis-level state (status, context, data-profile tracking, billing identity).
12
13
  --
@@ -245,7 +246,8 @@ CREATE TABLE IF NOT EXISTS cortex_working_memory (
245
246
  * Initialize Cortex state tables. Safe to call on every startup and across
246
247
  * concurrent replicas (advisory lock serializes the init flow).
247
248
  */
248
- export async function initCortexState(pool) {
249
+ export async function initCortexState(pool, injected) {
250
+ const logger = (injected ?? createNoopLogger()).named("cortex-state");
249
251
  const client = await pool.connect();
250
252
  try {
251
253
  // Serialize concurrent replica startups — cheap, no extra tooling.
@@ -381,11 +383,8 @@ export async function initCortexState(pool) {
381
383
  WITH (m = 16, ef_construction = 64)`);
382
384
  }
383
385
  catch (err) {
384
- const msg = err instanceof Error ? err.message : String(err);
385
- console.warn(`[state-init] Could not create HNSW index on cortex_regulatory_chunks.embedding ` +
386
- `(pgvector < 0.5.0 or operator class unavailable): ${msg}. ` +
387
- `Cosine similarity queries will fall back to sequential scan. ` +
388
- `Upgrade pgvector and re-run init to get the ANN index.`);
386
+ logger.warn("could not create HNSW index on cortex_regulatory_chunks.embedding (pgvector < 0.5.0 or operator class unavailable) — " +
387
+ "cosine similarity queries fall back to sequential scan; upgrade pgvector and re-run init to get the ANN index", logger.errorFields(err));
389
388
  }
390
389
  // No boot-time orphan sweep on cortex_runs / cortex_step_executions.
391
390
  // Every running row is backed by a DBOS workflow (executeAnalysis,
@@ -21,6 +21,7 @@ import { type AuthContext, type RunSession } from "../auth/types.js";
21
21
  import type { RunAuthorization, RunAuthorizer } from "../execution/run-authorizer.js";
22
22
  import type { StagedInput } from "../execution/staged-input.js";
23
23
  import type { BioToolKeys } from "../tools/bio/keys.js";
24
+ import type { Logger } from "../lib/logger.js";
24
25
  import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
25
26
  import type { SandboxClient } from "../sandbox/client.js";
26
27
  import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
@@ -29,6 +30,8 @@ import { type ProfilerOutput } from "../schemas/data-profile-schemas.js";
29
30
  import { type DataProfileInputFile, type DataProfileResult } from "../state/index.js";
30
31
  /** The body's construction-time deps — closed over at registration. */
31
32
  export interface DataProfileDeps {
33
+ /** Operational logging seam; omitted falls back to no-op. */
34
+ readonly logger?: Logger;
32
35
  readonly provider: ChatProvider;
33
36
  readonly pool: Pool;
34
37
  readonly sandboxClient: SandboxClient;
@@ -130,6 +133,8 @@ export type DataProfileTriggerResult = "started" | "restarted" | "already_runnin
130
133
  * (`registerDataProfileWorkflow`); the route never holds them.
131
134
  */
132
135
  export interface DataProfileTriggerDeps {
136
+ /** Operational logging seam; omitted falls back to no-op. */
137
+ readonly logger?: Logger;
133
138
  readonly pool: Pool;
134
139
  readonly runAuthorizer: RunAuthorizer;
135
140
  readonly workflow: (input: DataProfileWorkflowInput) => Promise<void>;
@@ -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
- console.warn(`[data-profile] No input files staged for ${analysisId}`);
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
- console.log(`[data-profile] execution=${executionId} analysis=${analysisId} starting sandbox`);
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
- console.warn(`[data-profile] ${fallbackCount}/${stagedInputs.length} input file(s) used a deterministic fallback description (analysis=${analysisId}) — profiler omitted them`);
315
+ logger.warn("input file(s) used a deterministic fallback description — profiler omitted them", {
316
+ fallbackCount,
317
+ stagedCount: stagedInputs.length,
318
+ });
314
319
  }
315
- console.log(`[data-profile] Indexed ${indexed} file(s) for ${analysisId}`);
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
- console.warn(`[data-profile] execution=${executionId} teardown failed (non-fatal):`, teardownErr);
333
+ logger.warn("teardown failed (non-fatal)", { executionId, ...logger.errorFields(teardownErr) });
329
334
  }
330
335
  }
331
336
  }
332
337
  catch (err) {
333
- console.error(`[data-profile] Failed for ${analysisId}:`, err);
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
- console.warn(`[data-profile] ${write} write skipped for ${analysisId}: ledger row not running (cleared or expired concurrently)`);
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
- console.error(`[data-profile] Trigger rejected for ${analysisId}: no seeded input set (missing analysis row or caller skipped seeding)`);
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
- console.error(`[data-profile] Trigger rejected for ${analysisId}: empty manifest dispatched against a seed naming ${seeded.length} file(s)`);
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
- console.error(`[data-profile] Trigger error for ${analysisId}:`, err);
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
- console.error(`[data-profile] ${phase} error for ${analysisId}:`, err);
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
- console.error(`[data-profile] Failed to mark ${analysisId} failed after a start error:`, failed.error);
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
- export declare const searchDgidbTool: import("../define-tool.js").Tool<{
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;