@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.
- package/dist/agents/conversation-agent.d.ts +9 -15
- package/dist/agents/conversation-agent.js +32 -31
- package/dist/agents/sandbox/cheminformatics-agent.js +1 -1
- package/dist/agents/sandbox/chromatin-agent.js +1 -1
- package/dist/agents/sandbox/dna-methylation-agent.js +1 -1
- package/dist/agents/sandbox/drug-repurposing-agent.js +5 -10
- package/dist/agents/sandbox/enrichment-agent.js +1 -1
- package/dist/agents/sandbox/genomic-variant-agent.js +1 -11
- package/dist/agents/sandbox/immune-profiling-agent.js +1 -1
- package/dist/agents/sandbox/index.js +0 -3
- package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -1
- package/dist/agents/sandbox/network-agent.js +1 -1
- package/dist/agents/sandbox/pkpd-clinical-response-agent.js +3 -6
- package/dist/agents/sandbox/scientific-executor.js +1 -1
- package/dist/agents/sandbox/shared.d.ts +2 -2
- package/dist/agents/sandbox/shared.js +8 -14
- package/dist/agents/sandbox/translational-safety-agent.js +4 -7
- package/dist/agents/sandbox/types.d.ts +5 -5
- package/dist/agents/sandbox/types.js +4 -4
- package/dist/app/chat-turn.js +4 -1
- package/dist/app/message-assembly.d.ts +5 -2
- package/dist/app/message-assembly.js +7 -2
- package/dist/app/run-activity.d.ts +13 -0
- package/dist/app/run-activity.js +51 -0
- package/dist/config/resource-limits.d.ts +0 -7
- package/dist/config/resource-limits.js +0 -1
- package/dist/contracts/target-dossier.d.ts +10 -10
- package/dist/execution/dbos-run-launcher.js +1 -26
- package/dist/execution/report-runner.js +1 -1
- package/dist/execution/run-authorizer.d.ts +1 -1
- package/dist/execution/run-authorizer.js +1 -1
- package/dist/execution/run-event-parts.d.ts +44 -0
- package/dist/execution/run-event-parts.js +0 -0
- package/dist/execution/run-event-stream.d.ts +91 -0
- package/dist/execution/run-event-stream.js +212 -0
- package/dist/execution/run-launcher.d.ts +2 -18
- package/dist/execution/run-launcher.js +2 -4
- package/dist/execution/run-synthesis.js +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +23 -6
- package/dist/input-sanitization.d.ts +14 -4
- package/dist/input-sanitization.js +27 -4
- package/dist/lib/storage.js +4 -1
- package/dist/loop/budget-exceeded.d.ts +18 -2
- package/dist/loop/budget-exceeded.js +49 -2
- package/dist/loop/run-agent.d.ts +22 -1
- package/dist/loop/run-agent.js +51 -5
- package/dist/loop/run-to-terminal.d.ts +0 -2
- package/dist/loop/run-to-terminal.js +8 -1
- package/dist/memory/ai-sdk-message-storage.d.ts +37 -4
- package/dist/memory/ai-sdk-message-storage.js +45 -4
- package/dist/memory/content-to-cortex.js +19 -3
- package/dist/memory/message-backfill.js +1 -1
- package/dist/memory/reconstruct-cards.d.ts +1 -1
- package/dist/memory/reconstruct-cards.js +7 -5
- package/dist/memory/thread-history.js +72 -8
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +41 -15
- package/dist/prompts/literature-reviewer.d.ts +1 -1
- package/dist/prompts/literature-reviewer.js +16 -14
- package/dist/prompts/planner.d.ts +3 -0
- package/dist/prompts/planner.js +16 -21
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +31 -22
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +9 -10
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +8 -7
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +20 -14
- package/dist/prompts/sandbox-standards.d.ts +4 -4
- package/dist/prompts/sandbox-standards.js +5 -5
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +7 -7
- package/dist/providers/errors.d.ts +7 -0
- package/dist/providers/errors.js +88 -3
- package/dist/runtime/assemble.d.ts +5 -10
- package/dist/runtime/assemble.js +0 -4
- package/dist/runtime/boot.d.ts +2 -2
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/dbos.d.ts +4 -5
- package/dist/runtime/dbos.js +6 -7
- package/dist/runtime/pools.d.ts +15 -1
- package/dist/runtime/pools.js +15 -1
- package/dist/sandbox/mount-plan.d.ts +1 -1
- package/dist/sandbox/sandbox-error.d.ts +1 -1
- package/dist/sandbox/sandbox-error.js +1 -1
- package/dist/sandbox/sandbox-step-translate.d.ts +15 -2
- package/dist/sandbox/sandbox-step-translate.js +17 -2
- package/dist/sandbox/types.d.ts +1 -1
- package/dist/schemas/validate-plan.d.ts +1 -1
- package/dist/schemas/workflow-state.d.ts +2 -2
- package/dist/schemas/workflow-state.js +2 -2
- package/dist/state/data-profile.d.ts +53 -1
- package/dist/state/data-profile.js +66 -6
- package/dist/state/index.d.ts +6 -6
- package/dist/state/index.js +4 -4
- package/dist/state/init.js +37 -2
- package/dist/state/plans.js +1 -1
- package/dist/state/runs.d.ts +49 -0
- package/dist/state/runs.js +150 -0
- package/dist/tasks/data-profile-activity.d.ts +87 -0
- package/dist/tasks/data-profile-activity.js +56 -0
- package/dist/tasks/data-profile.js +66 -4
- package/dist/tools/__fixtures__/tool-context.js +1 -0
- package/dist/tools/ad-hoc-router.d.ts +31 -0
- package/dist/tools/ad-hoc-router.js +132 -0
- package/dist/tools/analysis-invocation.d.ts +4 -0
- package/dist/tools/analysis-invocation.js +17 -0
- package/dist/tools/bio/chembl.d.ts +1 -1
- package/dist/tools/bio/chembl.js +51 -54
- package/dist/tools/bio/comptox.js +35 -33
- package/dist/tools/bio/drug-gene-interactions.d.ts +76 -0
- package/dist/tools/bio/drug-gene-interactions.js +175 -0
- package/dist/tools/bio/gene-disease-evidence.d.ts +61 -0
- package/dist/tools/bio/gene-disease-evidence.js +187 -0
- package/dist/tools/bio/gene-preclinical-profile.d.ts +58 -0
- package/dist/tools/bio/gene-preclinical-profile.js +125 -0
- package/dist/tools/bio/index.d.ts +23 -16
- package/dist/tools/bio/index.js +29 -22
- package/dist/tools/bio/keys.d.ts +16 -9
- package/dist/tools/bio/keys.js +22 -13
- package/dist/tools/bio/lookup-annotation.d.ts +48 -0
- package/dist/tools/bio/lookup-annotation.js +173 -0
- package/dist/tools/bio/opentargets.d.ts +7 -11
- package/dist/tools/bio/opentargets.js +24 -35
- package/dist/tools/bio/pubchem.js +6 -4
- package/dist/tools/bio/pubmed.d.ts +15 -7
- package/dist/tools/bio/pubmed.js +79 -20
- package/dist/tools/bio/search-clinical-trials.d.ts +20 -1
- package/dist/tools/bio/search-clinical-trials.js +26 -5
- package/dist/tools/bio/search-faers.js +9 -3
- package/dist/tools/bio/search-gene.d.ts +0 -1
- package/dist/tools/bio/search-gene.js +4 -5
- package/dist/tools/bio/search-geo-datasets.js +10 -4
- package/dist/tools/bio/search-interactions.d.ts +8 -4
- package/dist/tools/bio/search-interactions.js +28 -12
- package/dist/tools/bio/target-safety.d.ts +59 -0
- package/dist/tools/bio/target-safety.js +191 -0
- package/dist/tools/define-tool.d.ts +6 -2
- package/dist/tools/define-tool.js +1 -1
- package/dist/tools/execute-analysis.d.ts +44 -0
- package/dist/tools/execute-analysis.js +252 -0
- package/dist/tools/iterate-report.js +7 -6
- package/dist/tools/lib/dgidb-client.d.ts +49 -0
- package/dist/tools/lib/dgidb-client.js +168 -0
- package/dist/tools/lib/disgenet-client.d.ts +56 -0
- package/dist/tools/lib/disgenet-client.js +59 -0
- package/dist/tools/lib/drugbank-client.d.ts +83 -0
- package/dist/tools/lib/drugbank-client.js +95 -0
- package/dist/tools/lib/ensembl-client.d.ts +4 -1
- package/dist/tools/lib/ensembl-client.js +5 -3
- package/dist/tools/lib/gwas-catalog-client.d.ts +60 -0
- package/dist/tools/lib/gwas-catalog-client.js +136 -0
- package/dist/tools/lib/pharmgkb-client.d.ts +22 -0
- package/dist/tools/lib/pharmgkb-client.js +32 -0
- package/dist/tools/lib/pubmed-client.d.ts +57 -4
- package/dist/tools/lib/pubmed-client.js +57 -3
- package/dist/tools/lib/string-client.d.ts +25 -4
- package/dist/tools/lib/string-client.js +22 -5
- package/dist/tools/research/generate-plan.d.ts +16 -9
- package/dist/tools/research/generate-plan.js +116 -70
- package/dist/tools/research/inspect-data-profile.d.ts +31 -6
- package/dist/tools/research/inspect-data-profile.js +23 -7
- package/dist/tools/research/inspect-run.d.ts +33 -15
- package/dist/tools/research/inspect-run.js +155 -66
- package/dist/tools/research/literature-reviewer.js +7 -11
- package/dist/workflows/execute-analysis.d.ts +68 -5
- package/dist/workflows/execute-analysis.js +76 -7
- package/dist/workflows/sandbox-step.js +1 -11
- package/dist/workflows/target-assessment/decisions/index.d.ts +1 -1
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +1 -1
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +2 -2
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -1
- package/package.json +1 -1
- package/dist/agents/sandbox/ephemeral-executor.d.ts +0 -9
- package/dist/agents/sandbox/ephemeral-executor.js +0 -20
- package/dist/execution/ephemeral-runner.d.ts +0 -98
- package/dist/execution/ephemeral-runner.js +0 -189
- package/dist/prompts/sandbox/ephemeral-executor.d.ts +0 -1
- package/dist/prompts/sandbox/ephemeral-executor.js +0 -66
- package/dist/tools/bio/check-safety-panel.d.ts +0 -35
- package/dist/tools/bio/check-safety-panel.js +0 -76
- package/dist/tools/bio/get-impc-ko-profile.d.ts +0 -9
- package/dist/tools/bio/get-impc-ko-profile.js +0 -23
- package/dist/tools/bio/lookup-go-term.d.ts +0 -31
- package/dist/tools/bio/lookup-go-term.js +0 -116
- package/dist/tools/bio/search-bgee-expression.d.ts +0 -10
- package/dist/tools/bio/search-bgee-expression.js +0 -30
- package/dist/tools/bio/search-clinvar.d.ts +0 -13
- package/dist/tools/bio/search-clinvar.js +0 -28
- package/dist/tools/bio/search-dgidb.d.ts +0 -53
- package/dist/tools/bio/search-dgidb.js +0 -213
- package/dist/tools/bio/search-disgenet.d.ts +0 -30
- package/dist/tools/bio/search-disgenet.js +0 -84
- package/dist/tools/bio/search-drugbank.d.ts +0 -38
- package/dist/tools/bio/search-drugbank.js +0 -109
- package/dist/tools/bio/search-gwas-catalog.d.ts +0 -33
- package/dist/tools/bio/search-gwas-catalog.js +0 -172
- package/dist/tools/bio/search-pathway.d.ts +0 -12
- package/dist/tools/bio/search-pathway.js +0 -32
- package/dist/tools/bio/search-pharmgkb.d.ts +0 -17
- package/dist/tools/bio/search-pharmgkb.js +0 -52
- package/dist/tools/execute-plan.d.ts +0 -64
- package/dist/tools/execute-plan.js +0 -211
- package/dist/tools/run-ephemeral.d.ts +0 -33
- 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
|
|
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 (`
|
|
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 (`
|
|
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[];
|
|
Binary file
|
|
@@ -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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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 " +
|