@inflexa-ai/harness 0.12.0 → 0.14.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 (207) hide show
  1. package/dist/agents/conversation-agent.d.ts +9 -15
  2. package/dist/agents/conversation-agent.js +32 -31
  3. package/dist/agents/sandbox/cheminformatics-agent.js +1 -1
  4. package/dist/agents/sandbox/chromatin-agent.js +1 -1
  5. package/dist/agents/sandbox/dna-methylation-agent.js +1 -1
  6. package/dist/agents/sandbox/drug-repurposing-agent.js +5 -10
  7. package/dist/agents/sandbox/enrichment-agent.js +1 -1
  8. package/dist/agents/sandbox/genomic-variant-agent.js +1 -11
  9. package/dist/agents/sandbox/immune-profiling-agent.js +1 -1
  10. package/dist/agents/sandbox/index.js +0 -3
  11. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -1
  12. package/dist/agents/sandbox/network-agent.js +1 -1
  13. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +3 -6
  14. package/dist/agents/sandbox/scientific-executor.js +1 -1
  15. package/dist/agents/sandbox/shared.d.ts +2 -2
  16. package/dist/agents/sandbox/shared.js +8 -14
  17. package/dist/agents/sandbox/translational-safety-agent.js +4 -7
  18. package/dist/agents/sandbox/types.d.ts +5 -5
  19. package/dist/agents/sandbox/types.js +4 -4
  20. package/dist/app/chat-turn.js +4 -1
  21. package/dist/app/message-assembly.d.ts +5 -2
  22. package/dist/app/message-assembly.js +7 -2
  23. package/dist/app/run-activity.d.ts +13 -0
  24. package/dist/app/run-activity.js +51 -0
  25. package/dist/config/resource-limits.d.ts +0 -7
  26. package/dist/config/resource-limits.js +0 -1
  27. package/dist/contracts/target-dossier.d.ts +10 -10
  28. package/dist/execution/dbos-run-launcher.js +1 -26
  29. package/dist/execution/report-runner.js +1 -1
  30. package/dist/execution/run-authorizer.d.ts +1 -1
  31. package/dist/execution/run-authorizer.js +1 -1
  32. package/dist/execution/run-event-parts.d.ts +44 -0
  33. package/dist/execution/run-event-parts.js +0 -0
  34. package/dist/execution/run-event-stream.d.ts +91 -0
  35. package/dist/execution/run-event-stream.js +212 -0
  36. package/dist/execution/run-launcher.d.ts +2 -18
  37. package/dist/execution/run-launcher.js +2 -4
  38. package/dist/execution/run-synthesis.js +1 -1
  39. package/dist/index.d.ts +7 -4
  40. package/dist/index.js +23 -6
  41. package/dist/input-sanitization.d.ts +14 -4
  42. package/dist/input-sanitization.js +27 -4
  43. package/dist/lib/storage.js +4 -1
  44. package/dist/loop/budget-exceeded.d.ts +18 -2
  45. package/dist/loop/budget-exceeded.js +49 -2
  46. package/dist/loop/run-agent.d.ts +22 -1
  47. package/dist/loop/run-agent.js +51 -5
  48. package/dist/loop/run-to-terminal.d.ts +0 -2
  49. package/dist/loop/run-to-terminal.js +8 -1
  50. package/dist/memory/ai-sdk-message-storage.d.ts +37 -4
  51. package/dist/memory/ai-sdk-message-storage.js +45 -4
  52. package/dist/memory/content-to-cortex.js +19 -3
  53. package/dist/memory/message-backfill.js +1 -1
  54. package/dist/memory/reconstruct-cards.d.ts +1 -1
  55. package/dist/memory/reconstruct-cards.js +7 -5
  56. package/dist/memory/thread-history.js +72 -8
  57. package/dist/prompts/conversation.d.ts +1 -1
  58. package/dist/prompts/conversation.js +41 -15
  59. package/dist/prompts/literature-reviewer.d.ts +1 -1
  60. package/dist/prompts/literature-reviewer.js +16 -14
  61. package/dist/prompts/planner.d.ts +3 -0
  62. package/dist/prompts/planner.js +16 -21
  63. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  64. package/dist/prompts/sandbox/drug-repurposing-agent.js +31 -22
  65. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  66. package/dist/prompts/sandbox/multi-omics-integration-agent.js +9 -10
  67. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  68. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +8 -7
  69. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  70. package/dist/prompts/sandbox/translational-safety-agent.js +20 -14
  71. package/dist/prompts/sandbox-standards.d.ts +4 -4
  72. package/dist/prompts/sandbox-standards.js +5 -5
  73. package/dist/prompts/synthesis-agent.d.ts +1 -1
  74. package/dist/prompts/synthesis-agent.js +7 -7
  75. package/dist/providers/errors.d.ts +7 -0
  76. package/dist/providers/errors.js +88 -3
  77. package/dist/runtime/assemble.d.ts +5 -10
  78. package/dist/runtime/assemble.js +0 -4
  79. package/dist/runtime/boot.d.ts +2 -2
  80. package/dist/runtime/boot.js +1 -1
  81. package/dist/runtime/dbos.d.ts +4 -5
  82. package/dist/runtime/dbos.js +6 -7
  83. package/dist/runtime/pools.d.ts +15 -1
  84. package/dist/runtime/pools.js +15 -1
  85. package/dist/sandbox/mount-plan.d.ts +1 -1
  86. package/dist/sandbox/sandbox-error.d.ts +1 -1
  87. package/dist/sandbox/sandbox-error.js +1 -1
  88. package/dist/sandbox/sandbox-step-translate.d.ts +15 -2
  89. package/dist/sandbox/sandbox-step-translate.js +17 -2
  90. package/dist/sandbox/types.d.ts +1 -1
  91. package/dist/schemas/validate-plan.d.ts +1 -1
  92. package/dist/schemas/workflow-state.d.ts +2 -2
  93. package/dist/schemas/workflow-state.js +2 -2
  94. package/dist/state/data-profile.d.ts +53 -1
  95. package/dist/state/data-profile.js +66 -6
  96. package/dist/state/index.d.ts +6 -6
  97. package/dist/state/index.js +4 -4
  98. package/dist/state/init.js +37 -2
  99. package/dist/state/plans.js +1 -1
  100. package/dist/state/runs.d.ts +49 -0
  101. package/dist/state/runs.js +150 -0
  102. package/dist/tasks/data-profile-activity.d.ts +87 -0
  103. package/dist/tasks/data-profile-activity.js +56 -0
  104. package/dist/tasks/data-profile.js +66 -4
  105. package/dist/tools/__fixtures__/tool-context.js +1 -0
  106. package/dist/tools/ad-hoc-router.d.ts +31 -0
  107. package/dist/tools/ad-hoc-router.js +132 -0
  108. package/dist/tools/analysis-invocation.d.ts +4 -0
  109. package/dist/tools/analysis-invocation.js +17 -0
  110. package/dist/tools/bio/chembl.d.ts +1 -1
  111. package/dist/tools/bio/chembl.js +51 -54
  112. package/dist/tools/bio/comptox.js +35 -33
  113. package/dist/tools/bio/drug-gene-interactions.d.ts +76 -0
  114. package/dist/tools/bio/drug-gene-interactions.js +175 -0
  115. package/dist/tools/bio/gene-disease-evidence.d.ts +61 -0
  116. package/dist/tools/bio/gene-disease-evidence.js +187 -0
  117. package/dist/tools/bio/gene-preclinical-profile.d.ts +58 -0
  118. package/dist/tools/bio/gene-preclinical-profile.js +125 -0
  119. package/dist/tools/bio/index.d.ts +23 -16
  120. package/dist/tools/bio/index.js +29 -22
  121. package/dist/tools/bio/keys.d.ts +16 -9
  122. package/dist/tools/bio/keys.js +22 -13
  123. package/dist/tools/bio/lookup-annotation.d.ts +48 -0
  124. package/dist/tools/bio/lookup-annotation.js +173 -0
  125. package/dist/tools/bio/opentargets.d.ts +7 -11
  126. package/dist/tools/bio/opentargets.js +24 -35
  127. package/dist/tools/bio/pubchem.js +6 -4
  128. package/dist/tools/bio/pubmed.d.ts +15 -7
  129. package/dist/tools/bio/pubmed.js +79 -20
  130. package/dist/tools/bio/search-clinical-trials.d.ts +20 -1
  131. package/dist/tools/bio/search-clinical-trials.js +26 -5
  132. package/dist/tools/bio/search-faers.js +9 -3
  133. package/dist/tools/bio/search-gene.d.ts +0 -1
  134. package/dist/tools/bio/search-gene.js +4 -5
  135. package/dist/tools/bio/search-geo-datasets.js +10 -4
  136. package/dist/tools/bio/search-interactions.d.ts +8 -4
  137. package/dist/tools/bio/search-interactions.js +28 -12
  138. package/dist/tools/bio/target-safety.d.ts +59 -0
  139. package/dist/tools/bio/target-safety.js +191 -0
  140. package/dist/tools/define-tool.d.ts +6 -2
  141. package/dist/tools/define-tool.js +1 -1
  142. package/dist/tools/execute-analysis.d.ts +44 -0
  143. package/dist/tools/execute-analysis.js +252 -0
  144. package/dist/tools/iterate-report.js +7 -6
  145. package/dist/tools/lib/dgidb-client.d.ts +49 -0
  146. package/dist/tools/lib/dgidb-client.js +168 -0
  147. package/dist/tools/lib/disgenet-client.d.ts +56 -0
  148. package/dist/tools/lib/disgenet-client.js +59 -0
  149. package/dist/tools/lib/drugbank-client.d.ts +83 -0
  150. package/dist/tools/lib/drugbank-client.js +95 -0
  151. package/dist/tools/lib/ensembl-client.d.ts +4 -1
  152. package/dist/tools/lib/ensembl-client.js +5 -3
  153. package/dist/tools/lib/gwas-catalog-client.d.ts +60 -0
  154. package/dist/tools/lib/gwas-catalog-client.js +136 -0
  155. package/dist/tools/lib/pharmgkb-client.d.ts +22 -0
  156. package/dist/tools/lib/pharmgkb-client.js +32 -0
  157. package/dist/tools/lib/pubmed-client.d.ts +57 -4
  158. package/dist/tools/lib/pubmed-client.js +57 -3
  159. package/dist/tools/lib/string-client.d.ts +25 -4
  160. package/dist/tools/lib/string-client.js +22 -5
  161. package/dist/tools/research/generate-plan.d.ts +16 -9
  162. package/dist/tools/research/generate-plan.js +116 -70
  163. package/dist/tools/research/inspect-data-profile.d.ts +31 -6
  164. package/dist/tools/research/inspect-data-profile.js +23 -7
  165. package/dist/tools/research/inspect-run.d.ts +33 -15
  166. package/dist/tools/research/inspect-run.js +155 -66
  167. package/dist/tools/research/literature-reviewer.js +7 -11
  168. package/dist/workflows/execute-analysis.d.ts +68 -5
  169. package/dist/workflows/execute-analysis.js +76 -7
  170. package/dist/workflows/sandbox-step.js +1 -11
  171. package/dist/workflows/target-assessment/decisions/index.d.ts +1 -1
  172. package/dist/workflows/target-assessment/phase4-assemble.d.ts +1 -1
  173. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +2 -2
  174. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -1
  175. package/package.json +1 -1
  176. package/dist/agents/sandbox/ephemeral-executor.d.ts +0 -9
  177. package/dist/agents/sandbox/ephemeral-executor.js +0 -20
  178. package/dist/execution/ephemeral-runner.d.ts +0 -98
  179. package/dist/execution/ephemeral-runner.js +0 -189
  180. package/dist/prompts/sandbox/ephemeral-executor.d.ts +0 -1
  181. package/dist/prompts/sandbox/ephemeral-executor.js +0 -66
  182. package/dist/tools/bio/check-safety-panel.d.ts +0 -35
  183. package/dist/tools/bio/check-safety-panel.js +0 -76
  184. package/dist/tools/bio/get-impc-ko-profile.d.ts +0 -9
  185. package/dist/tools/bio/get-impc-ko-profile.js +0 -23
  186. package/dist/tools/bio/lookup-go-term.d.ts +0 -31
  187. package/dist/tools/bio/lookup-go-term.js +0 -116
  188. package/dist/tools/bio/search-bgee-expression.d.ts +0 -10
  189. package/dist/tools/bio/search-bgee-expression.js +0 -30
  190. package/dist/tools/bio/search-clinvar.d.ts +0 -13
  191. package/dist/tools/bio/search-clinvar.js +0 -28
  192. package/dist/tools/bio/search-dgidb.d.ts +0 -53
  193. package/dist/tools/bio/search-dgidb.js +0 -213
  194. package/dist/tools/bio/search-disgenet.d.ts +0 -30
  195. package/dist/tools/bio/search-disgenet.js +0 -84
  196. package/dist/tools/bio/search-drugbank.d.ts +0 -38
  197. package/dist/tools/bio/search-drugbank.js +0 -109
  198. package/dist/tools/bio/search-gwas-catalog.d.ts +0 -33
  199. package/dist/tools/bio/search-gwas-catalog.js +0 -172
  200. package/dist/tools/bio/search-pathway.d.ts +0 -12
  201. package/dist/tools/bio/search-pathway.js +0 -32
  202. package/dist/tools/bio/search-pharmgkb.d.ts +0 -17
  203. package/dist/tools/bio/search-pharmgkb.js +0 -52
  204. package/dist/tools/execute-plan.d.ts +0 -64
  205. package/dist/tools/execute-plan.js +0 -211
  206. package/dist/tools/run-ephemeral.d.ts +0 -33
  207. package/dist/tools/run-ephemeral.js +0 -65
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Ephemeral conversation-tail projection of non-terminal analysis runs.
3
+ *
4
+ * This is derived operational state, not memory: callers render it afresh for
5
+ * each turn and never persist it to the thread.
6
+ */
7
+ /** Maximum detailed run rows paid into every conversation turn. */
8
+ export const RUN_ACTIVITY_DETAIL_LIMIT = 20;
9
+ function compactAge(startedAt, nowMs) {
10
+ const startedMs = Date.parse(startedAt);
11
+ if (!Number.isFinite(startedMs))
12
+ return "unknown age";
13
+ const elapsedSeconds = Math.max(0, Math.floor((nowMs - startedMs) / 1_000));
14
+ if (elapsedSeconds < 60)
15
+ return `${elapsedSeconds}s ago`;
16
+ const elapsedMinutes = Math.floor(elapsedSeconds / 60);
17
+ if (elapsedMinutes < 60)
18
+ return `${elapsedMinutes}m ago`;
19
+ const elapsedHours = Math.floor(elapsedMinutes / 60);
20
+ if (elapsedHours < 24)
21
+ return `${elapsedHours}h ago`;
22
+ return `${Math.floor(elapsedHours / 24)}d ago`;
23
+ }
24
+ function renderRows(label, rows, nowMs) {
25
+ if (rows.length === 0)
26
+ return [];
27
+ return [
28
+ `${label}:`,
29
+ ...rows.map((run) => `- runId: ${run.runId} | planId: ${run.planId ?? "none"} | startedAt: ${run.startedAt} | started: ${compactAge(run.startedAt, nowMs)}`),
30
+ ];
31
+ }
32
+ /** Render the bounded run-activity snapshot injected into a conversation tail. */
33
+ export function renderRunActivity(activity, nowMs = Date.now()) {
34
+ const lines = ["[Run Activity]"];
35
+ if (activity.total === 0) {
36
+ lines.push("No runs are currently running or suspended.");
37
+ return lines.join("\n");
38
+ }
39
+ const running = activity.runs.filter((run) => run.status === "running");
40
+ const suspended = activity.runs.filter((run) => run.status === "suspended_insufficient_funds");
41
+ lines.push(...renderRows("Running", running, nowMs), ...renderRows("Suspended", suspended, nowMs));
42
+ const omitted = activity.total - activity.runs.length;
43
+ if (omitted > 0) {
44
+ lines.push(`Showing ${activity.runs.length} of ${activity.total} non-terminal runs; ${omitted} omitted. Use inspect_run to page the full list.`);
45
+ }
46
+ return lines.join("\n");
47
+ }
48
+ /** Render an honest degraded state when the activity projection cannot be read. */
49
+ export function renderRunActivityUnavailable() {
50
+ return "[Run Activity]\nRun status is temporarily unavailable. Do not infer that no active runs exist.";
51
+ }
@@ -34,13 +34,6 @@ export declare const ResourcePolicySchema: z.ZodObject<{
34
34
  cpu: z.ZodNumber;
35
35
  memoryGb: z.ZodNumber;
36
36
  }, z.core.$strip>;
37
- ephemeral: z.ZodOptional<z.ZodObject<{
38
- cpu: z.ZodNumber;
39
- memoryGb: z.ZodNumber;
40
- gpu: z.ZodOptional<z.ZodObject<{
41
- count: z.ZodNumber;
42
- }, z.core.$strip>>;
43
- }, z.core.$strip>>;
44
37
  }, z.core.$strip>;
45
38
  export type GpuSpec = z.infer<typeof GpuSpecSchema>;
46
39
  export type ResourceSpec = z.infer<typeof ResourceSpecSchema>;
@@ -26,7 +26,6 @@ export const MachineBudgetSchema = z.object({
26
26
  export const ResourcePolicySchema = z.object({
27
27
  perStep: ResourceLimitsSchema,
28
28
  budget: MachineBudgetSchema,
29
- ephemeral: ResourceSpecSchema.optional(),
30
29
  });
31
30
  /** Default LLM-turn budget for a sandbox agent step. Covers simple,
32
31
  * standard, and complex analyses — extra headroom on simple steps is
@@ -1789,8 +1789,8 @@ export declare const ExpressionHeatmapCellSchema: z.ZodObject<{
1789
1789
  medium: "medium";
1790
1790
  low: "low";
1791
1791
  absent: "absent";
1792
- not_loaded: "not_loaded";
1793
1792
  no_data: "no_data";
1793
+ not_loaded: "not_loaded";
1794
1794
  }>;
1795
1795
  }, z.core.$strip>;
1796
1796
  export declare const ExpressionHeatmapSchema: z.ZodObject<{
@@ -1808,8 +1808,8 @@ export declare const ExpressionHeatmapSchema: z.ZodObject<{
1808
1808
  medium: "medium";
1809
1809
  low: "low";
1810
1810
  absent: "absent";
1811
- not_loaded: "not_loaded";
1812
1811
  no_data: "no_data";
1812
+ not_loaded: "not_loaded";
1813
1813
  }>;
1814
1814
  }, z.core.$strip>>;
1815
1815
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -1915,8 +1915,8 @@ export declare const PreclinicalProfileSchema: z.ZodObject<{
1915
1915
  medium: "medium";
1916
1916
  low: "low";
1917
1917
  absent: "absent";
1918
- not_loaded: "not_loaded";
1919
1918
  no_data: "no_data";
1919
+ not_loaded: "not_loaded";
1920
1920
  }>;
1921
1921
  }, z.core.$strip>>;
1922
1922
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -4233,8 +4233,8 @@ export declare const DossierSchema: z.ZodObject<{
4233
4233
  medium: "medium";
4234
4234
  low: "low";
4235
4235
  absent: "absent";
4236
- not_loaded: "not_loaded";
4237
4236
  no_data: "no_data";
4237
+ not_loaded: "not_loaded";
4238
4238
  }>;
4239
4239
  }, z.core.$strip>>;
4240
4240
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -5486,8 +5486,8 @@ export declare const ReferenceBiologyV4Shape: z.ZodObject<{
5486
5486
  medium: "medium";
5487
5487
  low: "low";
5488
5488
  absent: "absent";
5489
- not_loaded: "not_loaded";
5490
5489
  no_data: "no_data";
5490
+ not_loaded: "not_loaded";
5491
5491
  }>;
5492
5492
  }, z.core.$strip>>;
5493
5493
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -6918,8 +6918,8 @@ export declare const DossierV4Schema: z.ZodObject<{
6918
6918
  medium: "medium";
6919
6919
  low: "low";
6920
6920
  absent: "absent";
6921
- not_loaded: "not_loaded";
6922
6921
  no_data: "no_data";
6922
+ not_loaded: "not_loaded";
6923
6923
  }>;
6924
6924
  }, z.core.$strip>>;
6925
6925
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -9135,8 +9135,8 @@ export declare const DossierV4BodySchema: z.ZodObject<{
9135
9135
  medium: "medium";
9136
9136
  low: "low";
9137
9137
  absent: "absent";
9138
- not_loaded: "not_loaded";
9139
9138
  no_data: "no_data";
9139
+ not_loaded: "not_loaded";
9140
9140
  }>;
9141
9141
  }, z.core.$strip>>;
9142
9142
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -12210,8 +12210,8 @@ export declare const DossierV5Schema: z.ZodObject<{
12210
12210
  medium: "medium";
12211
12211
  low: "low";
12212
12212
  absent: "absent";
12213
- not_loaded: "not_loaded";
12214
12213
  no_data: "no_data";
12214
+ not_loaded: "not_loaded";
12215
12215
  }>;
12216
12216
  }, z.core.$strip>>;
12217
12217
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -14932,8 +14932,8 @@ export declare const DossierV5BodySchema: z.ZodObject<{
14932
14932
  medium: "medium";
14933
14933
  low: "low";
14934
14934
  absent: "absent";
14935
- not_loaded: "not_loaded";
14936
14935
  no_data: "no_data";
14936
+ not_loaded: "not_loaded";
14937
14937
  }>;
14938
14938
  }, z.core.$strip>>;
14939
14939
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -16561,8 +16561,8 @@ export declare const DossierSchemaV3Legacy: z.ZodObject<{
16561
16561
  medium: "medium";
16562
16562
  low: "low";
16563
16563
  absent: "absent";
16564
- not_loaded: "not_loaded";
16565
16564
  no_data: "no_data";
16565
+ not_loaded: "not_loaded";
16566
16566
  }>;
16567
16567
  }, z.core.$strip>>;
16568
16568
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -3,36 +3,11 @@
3
3
  * shared by every embedder. Host-neutral: it references only DBOS, which the harness
4
4
  * already depends on, so it ships in the harness.
5
5
  */
6
- import { DBOS, Error as DBOSErrors } from "@dbos-inc/dbos-sdk";
6
+ import { DBOS } from "@dbos-inc/dbos-sdk";
7
7
  export function createDbosRunLauncher() {
8
8
  return {
9
9
  async launch(workflow, opts, input) {
10
10
  await DBOS.startWorkflow(workflow, { workflowID: opts.workflowId })(input);
11
11
  },
12
- async launchAndAwait(workflow, opts, input, runOpts) {
13
- const handle = await DBOS.startWorkflow(workflow, {
14
- workflowID: opts.workflowId,
15
- })(input);
16
- // Registered only after the workflow row is persisted — an abort racing a
17
- // not-yet-started workflow would `cancelWorkflow` a missing row (no-op)
18
- // and, being `{ once: true }`, never fire again, blocking `getResult`.
19
- const cancel = () => {
20
- void DBOS.cancelWorkflow(opts.workflowId);
21
- };
22
- if (runOpts.signal.aborted)
23
- cancel();
24
- else
25
- runOpts.signal.addEventListener("abort", cancel, { once: true });
26
- try {
27
- const result = await handle.getResult();
28
- return { status: "completed", result };
29
- }
30
- catch (e) {
31
- if (e instanceof DBOSErrors.DBOSWorkflowCancelledError) {
32
- return { status: "cancelled" };
33
- }
34
- throw e;
35
- }
36
- },
37
12
  };
38
13
  }
@@ -147,8 +147,8 @@ export async function runReportIteration(deps, opts) {
147
147
  signal: opts.signal,
148
148
  emit: opts.emit,
149
149
  runStep: passthroughStep,
150
- }, {
151
150
  resolved: () => outcome !== undefined,
151
+ }, {
152
152
  tools: [buildReportTool, submitReportTool],
153
153
  nudge: "You stopped without calling submit_report, so this report version " +
154
154
  "was not recorded. Build the report if needed, then call " +
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * RunAuthorizer — the async-edge run-authorization seam.
3
3
  *
4
- * Every async edge that starts durable work (`execute_plan`, `run_ephemeral`,
4
+ * Every async edge that starts durable work (`execute_analysis`,
5
5
  * data-profile) must turn the caller's opaque auth into a durable `RunSession`
6
6
  * before dispatching a workflow. That turning is the ONE place the credential
7
7
  * kind matters (a live caller credential → mint a fresh run credential Cortex
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * RunAuthorizer — the async-edge run-authorization seam.
3
3
  *
4
- * Every async edge that starts durable work (`execute_plan`, `run_ephemeral`,
4
+ * Every async edge that starts durable work (`execute_analysis`,
5
5
  * data-profile) must turn the caller's opaque auth into a durable `RunSession`
6
6
  * before dispatching a workflow. That turning is the ONE place the credential
7
7
  * kind matters (a live caller credential → mint a fresh run credential Cortex
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The two input-only rules of the run-event read seam: narrowing a raw durable
3
+ * stream value to the chat data-part contract, and the latest-wins reconciling
4
+ * fold applied before delivery.
5
+ *
6
+ * They live apart from the reader because they are the only part of the seam
7
+ * whose correctness is a function of its argument alone — no database, no
8
+ * durability engine, no clock — and keeping them here is what lets that be
9
+ * proven directly instead of inferred from a driven subscription.
10
+ */
11
+ import type { CortexChatPart } from "../contracts/chat-parts.js";
12
+ /**
13
+ * Narrow one raw stream value to a chat data part, or `null` when it is not one.
14
+ *
15
+ * A run's streams carry more than the contract: a sandbox step also writes its
16
+ * loop orchestration under envelopes (`data-loop-event`, `data-sandbox-event`)
17
+ * that belong to the workflow layer and have no registry entry. The seam's
18
+ * contract is the `CortexChatPart` union, so anything the registry does not
19
+ * classify is not deliverable — which also covers a part written by a producer
20
+ * newer than the reader.
21
+ *
22
+ * The registry is keyed by the discriminant alone, so this validates `type` and
23
+ * trusts the producer for the payload. There is no per-part schema to validate
24
+ * against: `contracts/chat-parts.ts` declares interfaces, not Zod objects. Both
25
+ * ends of this stream are typed against that same union inside this package, so
26
+ * the discriminant is the boundary that can actually drift (a producer emitting
27
+ * something else entirely) and the one worth checking.
28
+ */
29
+ export declare function parseRunEventPart(value: unknown): CortexChatPart | null;
30
+ /**
31
+ * Collapse a batch of parts so each reconciling id appears once, carrying its
32
+ * latest value, and every other part survives untouched in write order.
33
+ *
34
+ * `reconciling` in `PART_REGISTRY` already means "a later emission under this id
35
+ * supersedes the earlier one" — `data-step-activity` mints a stable id per
36
+ * `(runId, stepId)` precisely so a fold collapses every phase transition. Reading
37
+ * the flag rather than holding a list of part types here keeps one source of
38
+ * truth: a part type added to the registry later folds correctly without this
39
+ * module changing.
40
+ *
41
+ * A survivor keeps the position of its LAST occurrence, so relative order among
42
+ * the parts that are delivered is the order they were written.
43
+ */
44
+ export declare function foldRunEventParts(parts: readonly CortexChatPart[]): CortexChatPart[];
@@ -0,0 +1,91 @@
1
+ /**
2
+ * RunEventStream — the read side of the durable per-run event stream.
3
+ *
4
+ * Every analysis run already writes a rich, typed event stream: the parent
5
+ * `executeAnalysis` workflow writes the run-level parts, and each `sandbox-step`
6
+ * child writes its own step-level parts to its OWN stream, because a workflow can
7
+ * only write the stream it owns. This seam reads all of them back as one channel,
8
+ * so an embedder can show a user what a run is actually doing without
9
+ * reconstructing progress from the durability engine's internal step-cache tables
10
+ * — which record a step only when it *completes*, and so describe the thing that
11
+ * just finished rather than the thing in flight.
12
+ *
13
+ * The seam quarantines the engine the same way `RunLauncher` does for starting
14
+ * workflows: the delivered values are the harness's own `contracts/` part types,
15
+ * and no engine type appears in the exported signature.
16
+ *
17
+ * It is a push subscription rather than a snapshot reader because the engine's
18
+ * read primitive is an async generator with no offset parameter — "give me what
19
+ * is new since X" is not expressible on the public API, and expressing it would
20
+ * mean reaching into the SDK internals this seam exists to remove. A merged async
21
+ * iterator was the other candidate and was rejected because the set of sources
22
+ * grows during iteration (children join mid-run), which costs materially more
23
+ * code than invoking a handler; a callback also matches the `EmitFn` idiom the
24
+ * producers already speak.
25
+ *
26
+ * `subscribe` resolves rather than returning a `Result` because it has no failure
27
+ * to report. Observation is a diagnostic channel: a throwing handler and a
28
+ * failing stream are both logged and contained, mirroring the `safeEmit` stance
29
+ * on the write side, where a dropped UI frame must never fail a step. A read side
30
+ * that tore itself down on one bad part would be strictly less robust than the
31
+ * writer it observes.
32
+ */
33
+ import type { Pool } from "pg";
34
+ import type { CortexChatPart } from "../contracts/chat-parts.js";
35
+ import type { Logger } from "../lib/logger.js";
36
+ /**
37
+ * Construction-time dependencies. The pool is the application pool the run
38
+ * ledger lives in; the logger is where every contained failure is reported, and
39
+ * defaults to silence so an embedder that wired none sees nothing rather than
40
+ * having console forced on it.
41
+ */
42
+ export interface RunEventStreamDeps {
43
+ readonly pool: Pool;
44
+ readonly logger?: Logger;
45
+ }
46
+ /**
47
+ * Receives one part. May be async — the subscription awaits it, so a handler is
48
+ * never invoked concurrently with itself and back-pressure is the caller's to
49
+ * exert by taking its time. A throw is logged and swallowed.
50
+ */
51
+ export type RunEventPartHandler = (part: CortexChatPart) => void | Promise<void>;
52
+ /** Call-time parameters of one subscription. */
53
+ export interface RunEventSubscribeOptions {
54
+ /** The run to observe. Also the parent workflow's id. */
55
+ readonly runId: string;
56
+ /** Where each delivered part goes. */
57
+ readonly onPart: RunEventPartHandler;
58
+ /** Aborting stops delivery and settles the returned promise. */
59
+ readonly signal: AbortSignal;
60
+ }
61
+ /**
62
+ * The run-event read seam. One method, because a run-scoped subscription is the
63
+ * whole capability: a general-purpose stream reader would leak the engine's model
64
+ * back into the surface this exists to quarantine.
65
+ */
66
+ export interface RunEventStream {
67
+ /**
68
+ * Deliver every part the run produces — parent and children alike — to
69
+ * `onPart`, resolving when the run is terminal and every stream opened has
70
+ * drained, or promptly when the signal aborts.
71
+ *
72
+ * Parts from one workflow arrive in the order that workflow wrote them. No
73
+ * order is promised ACROSS the parent and its children: those workflows
74
+ * execute concurrently and the producers establish no cross-stream clock, so
75
+ * a total order could only be synthesised from timestamps that were never
76
+ * meant to be one. Consumers do not need it — every part is addressed by
77
+ * `runId`/`stepId` and the reconciling fold is per id.
78
+ */
79
+ subscribe(options: RunEventSubscribeOptions): Promise<void>;
80
+ }
81
+ /**
82
+ * Build the DBOS-backed realization of {@link RunEventStream}.
83
+ *
84
+ * Every stream is read from its beginning, and each workflow is opened at most
85
+ * once per subscription. Replay is not a cost worked around here, it is what
86
+ * makes attaching mid-run correct: a subscriber joining at minute ten receives
87
+ * the run's whole history and folding it yields the true current state.
88
+ * Re-opening a workflow would redeliver that history and duplicate every part
89
+ * the fold does not reconcile.
90
+ */
91
+ export declare function createRunEventStream(deps: RunEventStreamDeps): RunEventStream;
@@ -0,0 +1,212 @@
1
+ /**
2
+ * RunEventStream — the read side of the durable per-run event stream.
3
+ *
4
+ * Every analysis run already writes a rich, typed event stream: the parent
5
+ * `executeAnalysis` workflow writes the run-level parts, and each `sandbox-step`
6
+ * child writes its own step-level parts to its OWN stream, because a workflow can
7
+ * only write the stream it owns. This seam reads all of them back as one channel,
8
+ * so an embedder can show a user what a run is actually doing without
9
+ * reconstructing progress from the durability engine's internal step-cache tables
10
+ * — which record a step only when it *completes*, and so describe the thing that
11
+ * just finished rather than the thing in flight.
12
+ *
13
+ * The seam quarantines the engine the same way `RunLauncher` does for starting
14
+ * workflows: the delivered values are the harness's own `contracts/` part types,
15
+ * and no engine type appears in the exported signature.
16
+ *
17
+ * It is a push subscription rather than a snapshot reader because the engine's
18
+ * read primitive is an async generator with no offset parameter — "give me what
19
+ * is new since X" is not expressible on the public API, and expressing it would
20
+ * mean reaching into the SDK internals this seam exists to remove. A merged async
21
+ * iterator was the other candidate and was rejected because the set of sources
22
+ * grows during iteration (children join mid-run), which costs materially more
23
+ * code than invoking a handler; a callback also matches the `EmitFn` idiom the
24
+ * producers already speak.
25
+ *
26
+ * `subscribe` resolves rather than returning a `Result` because it has no failure
27
+ * to report. Observation is a diagnostic channel: a throwing handler and a
28
+ * failing stream are both logged and contained, mirroring the `safeEmit` stance
29
+ * on the write side, where a dropped UI frame must never fail a step. A read side
30
+ * that tore itself down on one bad part would be strictly less robust than the
31
+ * writer it observes.
32
+ */
33
+ import { DBOS } from "@dbos-inc/dbos-sdk";
34
+ import { sleep } from "../lib/async-utils.js";
35
+ import { createNoopLogger } from "../lib/console-logger.js";
36
+ import { describeDbError } from "../lib/db-result.js";
37
+ import { queryStepsByRun } from "../state/step-executions.js";
38
+ import { foldRunEventParts, parseRunEventPart } from "./run-event-parts.js";
39
+ /** Durable stream key every run producer writes its parts under. */
40
+ const RUN_EVENT_STREAM_KEY = "events";
41
+ /**
42
+ * How often the run's step ledger is re-read for children that have started
43
+ * since the last look.
44
+ *
45
+ * A child records its own workflow id as it begins, so discovery can only be as
46
+ * timely as that write and there is nothing to subscribe to before it lands.
47
+ * Polling is the honest mechanism: the parent's dag snapshot marks a step running
48
+ * before the child's body has written its row, so an event-driven re-check would
49
+ * race the ledger. One second matches the engine's own stream-polling fallback,
50
+ * and lagging a child's first writes costs nothing — a stream subscribed late is
51
+ * still read from its beginning.
52
+ */
53
+ const CHILD_DISCOVERY_INTERVAL_MS = 1_000;
54
+ /**
55
+ * Build the DBOS-backed realization of {@link RunEventStream}.
56
+ *
57
+ * Every stream is read from its beginning, and each workflow is opened at most
58
+ * once per subscription. Replay is not a cost worked around here, it is what
59
+ * makes attaching mid-run correct: a subscriber joining at minute ten receives
60
+ * the run's whole history and folding it yields the true current state.
61
+ * Re-opening a workflow would redeliver that history and duplicate every part
62
+ * the fold does not reconcile.
63
+ */
64
+ export function createRunEventStream(deps) {
65
+ const baseLogger = (deps.logger ?? createNoopLogger()).named("run-event-stream");
66
+ return {
67
+ async subscribe({ runId, onPart, signal }) {
68
+ const logger = baseLogger.with({ runId });
69
+ // Open generators, so an abort can ask each to wind down. The engine's
70
+ // reader exposes no cancellation: while it waits on a quiet-but-active
71
+ // workflow it is suspended inside an await, where a queued `return()` is
72
+ // only honoured once it next reaches a yield. Teardown is therefore
73
+ // best-effort by construction, and the subscription's own promise is
74
+ // raced against the signal rather than waiting on it.
75
+ const open = new Set();
76
+ let resolveAbort;
77
+ const aborted = new Promise((resolve) => {
78
+ resolveAbort = resolve;
79
+ });
80
+ const onAbort = () => {
81
+ resolveAbort();
82
+ for (const generator of open) {
83
+ void generator.return(undefined).catch(() => {
84
+ /* the read is already being abandoned */
85
+ });
86
+ }
87
+ };
88
+ if (signal.aborted)
89
+ onAbort();
90
+ else
91
+ signal.addEventListener("abort", onAbort, { once: true });
92
+ // Delivery is serialized through one queue across every stream. That
93
+ // gives the fold a batch to collapse — whatever accumulated while the
94
+ // previous handler call was in flight is reconciled before it is handed
95
+ // over, which is what spares a mid-run subscriber the superseded
96
+ // intermediates it replayed. The queue is FIFO and the fold keeps
97
+ // survivors in place, so a single stream's write order survives it.
98
+ const pending = [];
99
+ let delivering = false;
100
+ const deliver = async () => {
101
+ if (delivering)
102
+ return;
103
+ delivering = true;
104
+ try {
105
+ while (pending.length > 0 && !signal.aborted) {
106
+ const batch = pending.splice(0, pending.length);
107
+ for (const part of foldRunEventParts(batch)) {
108
+ if (signal.aborted)
109
+ return;
110
+ try {
111
+ await onPart(part);
112
+ }
113
+ catch (err) {
114
+ logger.warn("run-event handler threw", { partType: part.type, ...logger.errorFields(err) });
115
+ }
116
+ }
117
+ }
118
+ }
119
+ finally {
120
+ delivering = false;
121
+ }
122
+ };
123
+ const readWorkflowStream = async (workflowId) => {
124
+ const streamLogger = logger.with({ workflowId });
125
+ const generator = DBOS.readStream(workflowId, RUN_EVENT_STREAM_KEY);
126
+ open.add(generator);
127
+ try {
128
+ for (;;) {
129
+ const next = await generator.next();
130
+ // Checked after the read, not before: `next()` is what notices
131
+ // the workflow going inactive, and an aborted subscription
132
+ // must not enqueue anything the drain will never take.
133
+ if (next.done === true || signal.aborted)
134
+ return;
135
+ const part = parseRunEventPart(next.value);
136
+ if (part === null) {
137
+ streamLogger.debug("dropped a stream value outside the chat-part contract");
138
+ continue;
139
+ }
140
+ pending.push(part);
141
+ void deliver();
142
+ }
143
+ }
144
+ catch (err) {
145
+ // One stream's failure is contained here so the parent and every
146
+ // other child keep arriving.
147
+ streamLogger.error("run-event stream read failed", streamLogger.errorFields(err));
148
+ }
149
+ finally {
150
+ open.delete(generator);
151
+ void generator.return(undefined).catch(() => {
152
+ /* already finished, or abandoned under abort */
153
+ });
154
+ }
155
+ };
156
+ const subscribed = new Set([runId]);
157
+ const childReads = [];
158
+ // Children are addressed by the id the workflow bodies already persist.
159
+ // Deriving them from the scheduler's `${runId}-${idx}` naming would
160
+ // hard-code a scheme that belongs to the scheduler, and the engine's own
161
+ // child bookkeeping is internals of the kind this seam removes.
162
+ const discoverChildren = async () => {
163
+ (await queryStepsByRun(deps.pool, runId)).match((rows) => {
164
+ for (const row of rows) {
165
+ const childWorkflowId = row.childWorkflowId;
166
+ if (childWorkflowId === null || subscribed.has(childWorkflowId))
167
+ continue;
168
+ subscribed.add(childWorkflowId);
169
+ childReads.push(readWorkflowStream(childWorkflowId));
170
+ }
171
+ }, (error) => {
172
+ logger.warn("child discovery failed", { err: describeDbError(error) });
173
+ });
174
+ };
175
+ let parentDrained = false;
176
+ const parentRead = readWorkflowStream(runId).finally(() => {
177
+ parentDrained = true;
178
+ });
179
+ // The parent's stream draining IS the run reaching terminal: the engine
180
+ // ends a read exactly when the workflow is no longer active. One further
181
+ // pass runs after that — a child whose id landed in the ledger late is
182
+ // still replayed from zero — and then discovery stops.
183
+ const discovery = (async () => {
184
+ for (;;) {
185
+ if (signal.aborted)
186
+ return;
187
+ await discoverChildren();
188
+ if (signal.aborted || parentDrained)
189
+ return;
190
+ await Promise.race([sleep(CHILD_DISCOVERY_INTERVAL_MS), aborted, parentRead]);
191
+ }
192
+ })();
193
+ const settled = (async () => {
194
+ await discovery;
195
+ await parentRead;
196
+ await Promise.all(childReads);
197
+ })().catch((err) => {
198
+ // Defensive: every stream read and every discovery pass contains its
199
+ // own failure, so arriving here is a defect in this seam rather than
200
+ // a runtime condition. Logged rather than left to surface as an
201
+ // unhandled rejection after an abort already settled the caller.
202
+ logger.error("run-event subscription failed", logger.errorFields(err));
203
+ });
204
+ try {
205
+ await Promise.race([settled, aborted]);
206
+ }
207
+ finally {
208
+ signal.removeEventListener("abort", onAbort);
209
+ }
210
+ },
211
+ };
212
+ }
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * RunLauncher — the durability-engine launch seam.
3
3
  *
4
- * The async-edge tools (`execute_plan`, `run_ephemeral`) start durable runs but
4
+ * Async-edge tools such as `execute_analysis` start durable runs but
5
5
  * must not reach the durability engine directly — that engine is quarantined
6
6
  * out of tools and the loop. This seam is the one capability they need: start a
7
- * registered workflow under a caller-chosen id, either fire-and-forget or
8
- * awaiting its result inline. `launchAndAwait` hides the engine's cancellation
9
- * behind a discriminated outcome, so no engine error type leaks to callers.
7
+ * registered workflow under a caller-chosen id.
10
8
  *
11
9
  * The workflow itself stays injected — it is the registered-workflow function
12
10
  * reference, typed `(input) => Promise<result>`; the launcher only owns the
@@ -16,21 +14,7 @@ export interface LaunchOptions {
16
14
  /** Caller-chosen durable workflow id (e.g. the runId). */
17
15
  readonly workflowId: string;
18
16
  }
19
- export interface LaunchRunOptions {
20
- /** Aborting cancels the in-flight run; wired internally by the launcher. */
21
- readonly signal: AbortSignal;
22
- }
23
- /** Result of an awaited launch: the value on completion, or a cancelled
24
- * sentinel that replaces the engine's cancellation error. */
25
- export type LaunchOutcome<R> = {
26
- readonly status: "completed";
27
- readonly result: R;
28
- } | {
29
- readonly status: "cancelled";
30
- };
31
17
  export interface RunLauncher {
32
18
  /** Fire-and-forget: resolves once the run is durably started. */
33
19
  launch<I>(workflow: (input: I) => Promise<unknown>, opts: LaunchOptions, input: I): Promise<void>;
34
- /** Start the run and await its result inline, wiring cancel-on-abort. */
35
- launchAndAwait<I, R>(workflow: (input: I) => Promise<R>, opts: LaunchOptions, input: I, runOpts: LaunchRunOptions): Promise<LaunchOutcome<R>>;
36
20
  }
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * RunLauncher — the durability-engine launch seam.
3
3
  *
4
- * The async-edge tools (`execute_plan`, `run_ephemeral`) start durable runs but
4
+ * Async-edge tools such as `execute_analysis` start durable runs but
5
5
  * must not reach the durability engine directly — that engine is quarantined
6
6
  * out of tools and the loop. This seam is the one capability they need: start a
7
- * registered workflow under a caller-chosen id, either fire-and-forget or
8
- * awaiting its result inline. `launchAndAwait` hides the engine's cancellation
9
- * behind a discriminated outcome, so no engine error type leaks to callers.
7
+ * registered workflow under a caller-chosen id.
10
8
  *
11
9
  * The workflow itself stays injected — it is the registered-workflow function
12
10
  * reference, typed `(input) => Promise<result>`; the launcher only owns the
@@ -369,9 +369,9 @@ export async function generateRunSynthesis(input) {
369
369
  signal,
370
370
  emit,
371
371
  runStep: passthroughStep,
372
+ resolved: () => holder.outcome !== null,
372
373
  };
373
374
  await runToTerminal(agent, [{ role: "user", content: prompt }], input.session, loopDeps, {
374
- resolved: () => holder.outcome !== null,
375
375
  tools: [submitTool, blockerTool],
376
376
  nudge: "You ended without calling a terminal tool. You MUST call " +
377
377
  "submit_synthesis with the final synthesis now, or report_blocker " +