@inflexa-ai/harness 0.48.3 → 0.48.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/conversation-agent.js +5 -1
- package/dist/agents/sandbox/index.js +3 -0
- package/dist/agents/sandbox/mdv-preparation-agent.d.ts +9 -0
- package/dist/agents/sandbox/mdv-preparation-agent.js +37 -0
- package/dist/agents/sandbox/network-agent.js +1 -1
- package/dist/agents/sandbox/shared.js +3 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn-workflow.js +1 -0
- package/dist/app/chat-turn.d.ts +4 -2
- package/dist/app/chat-turn.js +46 -46
- package/dist/app/work.d.ts +7 -3
- package/dist/app/work.js +17 -16
- package/dist/contracts/chat-parts.d.ts +46 -5
- package/dist/contracts/chat-parts.js +2 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/part-registry.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +168 -4
- package/dist/contracts/schemas/chat-parts.js +21 -1
- package/dist/execution/run-event-stream.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/lib/regulatory-corpus/fetch-ich.js +1 -16
- package/dist/lib/slug.d.ts +18 -0
- package/dist/lib/slug.js +29 -0
- package/dist/lib/test/pdf-fixture.d.ts +5 -0
- package/dist/lib/test/pdf-fixture.js +14 -0
- package/dist/loop/chat-stream.d.ts +4 -1
- package/dist/loop/chat-stream.js +30 -13
- package/dist/loop/run-loop.js +23 -35
- package/dist/loop/sub-agent.d.ts +2 -2
- package/dist/loop/sub-agent.js +1 -1
- package/dist/loop/test/metric-reader.d.ts +9 -0
- package/dist/loop/test/metric-reader.js +33 -0
- package/dist/loop/test/unnamed-model.d.ts +6 -0
- package/dist/loop/test/unnamed-model.js +24 -0
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +6 -2
- package/dist/memory/conversation-display-storage.d.ts +4 -2
- package/dist/memory/conversation-display-storage.js +19 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +6 -4
- package/dist/prompts/sandbox/mdv-preparation-agent.d.ts +1 -0
- package/dist/prompts/sandbox/mdv-preparation-agent.js +56 -0
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +4 -0
- package/dist/providers/errors.js +2 -1
- package/dist/report-model/fixture-resolver.js +9 -135
- package/dist/report-model/production-resolver.d.ts +3 -3
- package/dist/report-model/production-resolver.js +12 -127
- package/dist/report-model/reference-read.d.ts +38 -0
- package/dist/report-model/reference-read.js +149 -0
- package/dist/report-model/reference-resolver.d.ts +7 -0
- package/dist/report-model/reference-resolver.js +1 -1
- package/dist/report-render/chart.js +48 -46
- package/dist/report-render/figures/common.d.ts +34 -0
- package/dist/report-render/figures/common.js +4 -1
- package/dist/report-render/figures/equal-units.js +2 -4
- package/dist/report-render/figures/sankey.js +1 -3
- package/dist/report-render/figures/upset.js +2 -5
- package/dist/runtime/assemble.js +2 -1
- package/dist/sandbox/execution-id.js +2 -14
- package/dist/sandbox/leases.js +46 -9
- package/dist/schemas/validate-plan.js +2 -14
- package/dist/state/active-turns.d.ts +6 -2
- package/dist/state/active-turns.js +11 -7
- package/dist/state/data-profile.d.ts +8 -0
- package/dist/state/data-profile.js +18 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.d.ts +6 -0
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.js +10 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/state/paused-work.d.ts +5 -3
- package/dist/state/paused-work.js +11 -7
- package/dist/state/runs.d.ts +3 -2
- package/dist/state/runs.js +4 -3
- package/dist/tasks/data-profile-index.js +3 -17
- package/dist/tasks/data-profile-resolve.js +3 -17
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/humanbase.d.ts +32 -0
- package/dist/tools/bio/humanbase.js +180 -0
- package/dist/tools/bio/index.d.ts +3 -1
- package/dist/tools/bio/index.js +3 -1
- package/dist/tools/bio/resolve-gene-ids.d.ts +13 -0
- package/dist/tools/bio/resolve-gene-ids.js +67 -0
- package/dist/tools/lib/humanbase-client.d.ts +215 -0
- package/dist/tools/lib/humanbase-client.js +187 -0
- package/dist/tools/lib/mygene-client.d.ts +150 -0
- package/dist/tools/lib/mygene-client.js +109 -0
- package/dist/tools/lib/opentargets-client.js +15 -25
- package/dist/tools/report-authoring/authoring-tools.d.ts +3 -3
- package/dist/tools/report-authoring/authoring-tools.js +7 -6
- package/dist/tools/research/generate-analogy-report.js +10 -2
- package/dist/tools/script/run-script.js +48 -51
- package/dist/workflows/execute-analysis.js +4 -3
- package/dist/workflows/pause.d.ts +3 -3
- package/dist/workflows/pause.js +15 -9
- package/dist/workflows/sandbox-step.js +1 -0
- package/package.json +7 -4
|
@@ -20,7 +20,6 @@ import { z } from "zod";
|
|
|
20
20
|
import { createSubAgentActivityTracker } from "../../contracts/subagent-activity.js";
|
|
21
21
|
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
22
22
|
import { parseJsonAs } from "../../lib/parse-json.js";
|
|
23
|
-
import { parseUnknownRecord, UnknownArraySchema } from "../../lib/unknown-containers.js";
|
|
24
23
|
import { computeDetail, computeResultDetail } from "../../loop/tool-detail.js";
|
|
25
24
|
import { buildFileReferenceCardData, MAX_FILES } from "../../memory/card-builders.js";
|
|
26
25
|
import { defineTool, readToolCallRecord, readToolResultImages, withToolCallRecord, } from "../define-tool.js";
|
|
@@ -59,8 +58,8 @@ function bounded(text) {
|
|
|
59
58
|
function plainJson(value) {
|
|
60
59
|
return value === undefined ? null : parseJsonAs(JSON.stringify(value), z.json());
|
|
61
60
|
}
|
|
62
|
-
function describeTool(tool) {
|
|
63
|
-
return renderToolSample({ name: tool.id, description: tool.description, inputSchema:
|
|
61
|
+
function describeTool(tool, schemas) {
|
|
62
|
+
return renderToolSample({ name: tool.id, description: tool.description, inputSchema: schemas.input, outputSchema: schemas.output });
|
|
64
63
|
}
|
|
65
64
|
function issuesOf(error) {
|
|
66
65
|
return error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ");
|
|
@@ -129,12 +128,12 @@ function collectEffect(state, value) {
|
|
|
129
128
|
* The script gets the JSON of a result, and the JSON has no pictures. Thus a result with pictures is a failed call: the
|
|
130
129
|
* script would otherwise read the result as complete.
|
|
131
130
|
*/
|
|
132
|
-
function scriptToolOf(tool, ctx, state, log, inCallContext) {
|
|
131
|
+
function scriptToolOf(tool, schemas, ctx, state, log, inCallContext) {
|
|
133
132
|
return {
|
|
134
133
|
name: tool.id,
|
|
135
134
|
description: tool.description,
|
|
136
|
-
inputSchema:
|
|
137
|
-
outputSchema:
|
|
135
|
+
inputSchema: schemas.input,
|
|
136
|
+
outputSchema: schemas.output,
|
|
138
137
|
execute: (args, options) => inCallContext(() => runNestedCall(tool, ctx, state, log, args, options.signal)),
|
|
139
138
|
};
|
|
140
139
|
}
|
|
@@ -187,56 +186,54 @@ async function runNestedCall(tool, ctx, state, log, args, signal) {
|
|
|
187
186
|
await finish("ok", computeResultDetail(tool, parsed.data, result.value, log) ?? detail);
|
|
188
187
|
return plainJson(result.value);
|
|
189
188
|
}
|
|
190
|
-
/** A JSON schema without its prose: the types and the constraints stay, the descriptions and the examples go. */
|
|
191
|
-
function withoutProse(schema) {
|
|
192
|
-
const array = UnknownArraySchema.safeParse(schema);
|
|
193
|
-
if (array.success)
|
|
194
|
-
return array.data.map(withoutProse);
|
|
195
|
-
const record = parseUnknownRecord(schema);
|
|
196
|
-
if (record === undefined)
|
|
197
|
-
return schema;
|
|
198
|
-
return Object.fromEntries(Object.entries(record)
|
|
199
|
-
.filter(([key]) => key !== "description" && key !== "examples")
|
|
200
|
-
.map(([key, value]) => [key, withoutProse(value)]));
|
|
201
|
-
}
|
|
202
189
|
/**
|
|
203
|
-
*
|
|
204
|
-
* permissive (`add_block` does, thus a malformed block reaches its typed refusal). The type of such a
|
|
205
|
-
* `unknown`, which hides the real shape, thus the empty member goes. The VM only renders the schema and
|
|
206
|
-
* nothing, and the tool still parses each input with its own schema.
|
|
190
|
+
* Shape one node of a JSON schema for a script. A tool can union its real shape with the empty schema to keep its
|
|
191
|
+
* runtime parse permissive (`add_block` does, thus a malformed block reaches its typed refusal). The type of such a
|
|
192
|
+
* union is `unknown`, which hides the real shape, thus the empty member goes. The VM only renders the schema and
|
|
193
|
+
* validates nothing, and the tool still parses each input with its own schema. Without prose, the `description` and the
|
|
194
|
+
* `examples` of the node go too. Zod calls this for each schema node and never for a field name, thus a field with the
|
|
195
|
+
* name `description` stays.
|
|
207
196
|
*/
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const members = UnknownArraySchema.safeParse(out[key]);
|
|
218
|
-
if (!members.success)
|
|
219
|
-
continue;
|
|
220
|
-
const shaped = members.data.filter((member) => Object.keys(parseUnknownRecord(member) ?? {}).length > 0);
|
|
221
|
-
if (shaped.length === 0 || shaped.length === members.data.length)
|
|
222
|
-
continue;
|
|
223
|
-
const rest = Object.fromEntries(Object.entries(out).filter(([name]) => name !== key));
|
|
224
|
-
out = shaped.length > 1 ? { ...rest, [key]: shaped } : { ...rest, ...parseUnknownRecord(shaped[0]) };
|
|
197
|
+
function shapeForScript(jsonSchema, prose) {
|
|
198
|
+
if (!prose) {
|
|
199
|
+
delete jsonSchema.description;
|
|
200
|
+
delete jsonSchema.examples;
|
|
201
|
+
}
|
|
202
|
+
const anyOf = shapedMembers(jsonSchema.anyOf);
|
|
203
|
+
if (anyOf !== undefined) {
|
|
204
|
+
delete jsonSchema.anyOf;
|
|
205
|
+
Object.assign(jsonSchema, anyOf.length > 1 ? { anyOf } : anyOf[0]);
|
|
225
206
|
}
|
|
226
|
-
|
|
207
|
+
const oneOf = shapedMembers(jsonSchema.oneOf);
|
|
208
|
+
if (oneOf !== undefined) {
|
|
209
|
+
delete jsonSchema.oneOf;
|
|
210
|
+
Object.assign(jsonSchema, oneOf.length > 1 ? { oneOf } : oneOf[0]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/** The members of a union that are not the empty schema, or `undefined` when the union keeps each member. */
|
|
214
|
+
function shapedMembers(members) {
|
|
215
|
+
if (members === undefined)
|
|
216
|
+
return undefined;
|
|
217
|
+
const shaped = members.filter((member) => Object.keys(member).length > 0);
|
|
218
|
+
return shaped.length === 0 || shaped.length === members.length ? undefined : shaped;
|
|
227
219
|
}
|
|
228
|
-
/** The
|
|
229
|
-
function
|
|
230
|
-
|
|
220
|
+
/** The schemas of a tool as a script reads them, built with the same options as the schemas of {@link defineTool}. */
|
|
221
|
+
function scriptSchemasOf(tool, prose) {
|
|
222
|
+
const override = ({ jsonSchema }) => shapeForScript(jsonSchema, prose);
|
|
223
|
+
const { $schema: _inputDialect, ...input } = z.toJSONSchema(tool.inputSchema, { override });
|
|
224
|
+
if (tool.outputSchema === undefined)
|
|
225
|
+
return { input };
|
|
226
|
+
const { $schema: _outputDialect, ...output } = z.toJSONSchema(tool.outputSchema, { io: "output", unrepresentable: "any", override });
|
|
227
|
+
return { input, output };
|
|
231
228
|
}
|
|
232
229
|
/** The longest result type that the catalog shows. A longer type shows as `unknown`, and `describe()` gives it whole. */
|
|
233
230
|
const MAX_CATALOG_RESULT_TYPE_CHARS = 800;
|
|
234
231
|
/** One catalog entry: the first sentence of the description, then the signature with no prose. */
|
|
235
232
|
function catalogEntry(tool) {
|
|
236
|
-
const
|
|
233
|
+
const { input, output } = scriptSchemasOf(tool, false);
|
|
237
234
|
const resultType = output === undefined ? "unknown" : schemaToType(output, { maxChars: MAX_CATALOG_RESULT_TYPE_CHARS });
|
|
238
235
|
// pi renders a signature with no result schema as `...: Promise<unknown>;`, thus the catalog puts its capped type there.
|
|
239
|
-
const untyped = renderToolSignature({ name: tool.id, inputSchema:
|
|
236
|
+
const untyped = renderToolSignature({ name: tool.id, inputSchema: input });
|
|
240
237
|
const signature = `${untyped.slice(0, -"unknown>;".length)}${resultType}>;`;
|
|
241
238
|
return `/** ${firstSentence(tool.description)} */\n${signature}`;
|
|
242
239
|
}
|
|
@@ -290,7 +287,7 @@ function scriptResultView(result) {
|
|
|
290
287
|
/** The `run_script` tool over the script tools of an agent. */
|
|
291
288
|
export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
292
289
|
const vm = sandboxRuntimeOf(runtime);
|
|
293
|
-
const byId = new Map(scriptTools.map((tool) => [tool.id, tool]));
|
|
290
|
+
const byId = new Map(scriptTools.map((tool) => [tool.id, { tool, schemas: scriptSchemasOf(tool, true) }]));
|
|
294
291
|
const log = (options.logger ?? createNoopLogger()).named("run-script");
|
|
295
292
|
const { scratch } = options;
|
|
296
293
|
return defineTool({
|
|
@@ -312,7 +309,7 @@ export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
|
312
309
|
scratch?.recordRun(record.run);
|
|
313
310
|
for (const entry of record.nested) {
|
|
314
311
|
try {
|
|
315
|
-
byId.get(entry.tool)?.foldCallRecord?.(entry.record);
|
|
312
|
+
byId.get(entry.tool)?.tool.foldCallRecord?.(entry.record);
|
|
316
313
|
}
|
|
317
314
|
catch (err) {
|
|
318
315
|
log.error("the call record of a nested call did not fold", { tool: entry.tool, ...log.errorFields(err) });
|
|
@@ -356,15 +353,15 @@ export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
|
356
353
|
// example the durable step of the call, which DBOS keeps in an AsyncLocalStorage.
|
|
357
354
|
const inCallContext = AsyncLocalStorage.snapshot();
|
|
358
355
|
const sandbox = new CodemodeSandbox({
|
|
359
|
-
tools:
|
|
356
|
+
tools: [...byId.values()].map(({ tool, schemas }) => scriptToolOf(tool, schemas, ctx, state, log, inCallContext)),
|
|
360
357
|
globals: [
|
|
361
358
|
{
|
|
362
359
|
name: "describe",
|
|
363
360
|
execute: (name) => {
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
361
|
+
const entry = typeof name === "string" ? byId.get(name) : undefined;
|
|
362
|
+
if (entry === undefined)
|
|
366
363
|
throw new Error(`no script tool ${JSON.stringify(name)}; the tools are ${[...byId.keys()].join(", ")}`);
|
|
367
|
-
return describeTool(tool);
|
|
364
|
+
return describeTool(entry.tool, entry.schemas);
|
|
368
365
|
},
|
|
369
366
|
},
|
|
370
367
|
],
|
|
@@ -590,17 +590,18 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
590
590
|
// the run authorization is already minted and rides in `input.runSession`.
|
|
591
591
|
const runId = input.runSession.runFrame.runId;
|
|
592
592
|
const workflowId = runId;
|
|
593
|
-
const
|
|
593
|
+
const pauseAs = (waiter) => workPause({
|
|
594
594
|
pool: deps.pool,
|
|
595
595
|
workId: runId,
|
|
596
596
|
workKind: "run",
|
|
597
|
+
waiter,
|
|
597
598
|
analysisId: input.analysisId,
|
|
598
599
|
emit: emitStreamPart,
|
|
599
600
|
retryDelaysMs: deps.pauseRetryDelaysMs,
|
|
600
601
|
run: { session: input.runSession, authorizer: deps.runAuthorizer },
|
|
601
602
|
logger,
|
|
602
603
|
});
|
|
603
|
-
const init = await validateAndInit(input, runId, deps,
|
|
604
|
+
const init = await validateAndInit(input, runId, deps, pauseAs({ kind: "run" }));
|
|
604
605
|
if (init.kind === "joined-existing") {
|
|
605
606
|
return {
|
|
606
607
|
runId: init.runId,
|
|
@@ -728,7 +729,7 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
728
729
|
completedSteps: [...final.completed],
|
|
729
730
|
session: input.runSession,
|
|
730
731
|
deps,
|
|
731
|
-
pause,
|
|
732
|
+
pause: pauseAs({ kind: "synthesis" }),
|
|
732
733
|
});
|
|
733
734
|
synthesisFindings = synthOut.findings;
|
|
734
735
|
synthesisOutcome = { status: synthOut.synthesisStatus, reason: synthOut.synthesisReason };
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* with the message of a resume, or with a stop. The server never sends that message by itself.
|
|
12
12
|
*/
|
|
13
13
|
import type { RunSession } from "../auth/types.js";
|
|
14
|
-
import type { PauseCause, WorkKind, PausePart } from "../contracts/chat-parts.js";
|
|
14
|
+
import type { PauseCause, PauseEnd, PauseWaiter, WorkKind, PausePart } from "../contracts/chat-parts.js";
|
|
15
15
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
16
16
|
import type { Logger } from "../lib/logger.js";
|
|
17
17
|
import type { Querier } from "../state/db.js";
|
|
@@ -29,14 +29,14 @@ export declare const HOLD_MESSAGE: {
|
|
|
29
29
|
export declare function pauseTopic(pauseId: string): string;
|
|
30
30
|
/** The cause of a pause in a failure, or `undefined` for a failure that no person or time can repair. */
|
|
31
31
|
export declare function pauseCauseOf(failure: unknown): PauseCause | undefined;
|
|
32
|
-
/** How a wait ended: the deadline of `retrying`, the message of a resume, or a stop of the work. */
|
|
33
|
-
export type PauseEnd = "retry" | "resume" | "abort";
|
|
34
32
|
/** The work that a pause belongs to, and where the part of the pause goes. */
|
|
35
33
|
export type PauseScope = {
|
|
36
34
|
readonly pool: Querier;
|
|
37
35
|
/** The turn id, the run id, or the id of the profile workflow. A sub-agent names its turn, and a step its run. */
|
|
38
36
|
readonly workId: string;
|
|
39
37
|
readonly workKind: WorkKind;
|
|
38
|
+
/** What waits in this workflow. */
|
|
39
|
+
readonly waiter: PauseWaiter;
|
|
40
40
|
readonly analysisId: string;
|
|
41
41
|
readonly threadId?: string;
|
|
42
42
|
/** Writes the part of the pause to the stream of the work. */
|
package/dist/workflows/pause.js
CHANGED
|
@@ -96,10 +96,11 @@ export function workPause(scope) {
|
|
|
96
96
|
const delayMs = cause.kind === "outage" ? delays[attempt] : undefined;
|
|
97
97
|
const state = delayMs === undefined ? "paused" : "retrying";
|
|
98
98
|
const pauseId = await DBOS.randomUUID();
|
|
99
|
-
const
|
|
100
|
-
const
|
|
99
|
+
const pausedAtMs = await DBOS.now();
|
|
100
|
+
const pausedAt = new Date(pausedAtMs).toISOString();
|
|
101
|
+
const retryAt = delayMs === undefined ? undefined : new Date(pausedAtMs + delayMs).toISOString();
|
|
101
102
|
const topic = pauseTopic(pauseId);
|
|
102
|
-
const work = { workId: scope.workId, workKind: scope.workKind };
|
|
103
|
+
const work = { workId: scope.workId, workKind: scope.workKind, waiter: scope.waiter };
|
|
103
104
|
await DBOS.runStep(async () => unwrapOrThrow(await recordPause(scope.pool, {
|
|
104
105
|
workflowId,
|
|
105
106
|
...work,
|
|
@@ -110,7 +111,7 @@ export function workPause(scope) {
|
|
|
110
111
|
attempt,
|
|
111
112
|
retryAt,
|
|
112
113
|
topic,
|
|
113
|
-
pausedAt
|
|
114
|
+
pausedAt,
|
|
114
115
|
runSession: scope.run?.session,
|
|
115
116
|
})), { name: "pause.record" });
|
|
116
117
|
// Only a person ends a wait in `paused`, thus the credential stops until the renewal of the resume.
|
|
@@ -122,7 +123,7 @@ export function workPause(scope) {
|
|
|
122
123
|
};
|
|
123
124
|
if (state === "paused")
|
|
124
125
|
await suspendCredential("pause.suspend");
|
|
125
|
-
await scope.emit({ type: "data-pause", id: workflowId, ...work, state, cause, attempt, retryAt });
|
|
126
|
+
await scope.emit({ type: "data-pause", id: workflowId, ...work, state, cause, pausedAt, attempt, retryAt });
|
|
126
127
|
log.warn("work paused", { workflowId, ...work, state, cause: cause.kind, attempt });
|
|
127
128
|
let end;
|
|
128
129
|
const untrack = trackWorkPaused({ workKind: scope.workKind });
|
|
@@ -139,11 +140,11 @@ export function workPause(scope) {
|
|
|
139
140
|
cause,
|
|
140
141
|
attempt,
|
|
141
142
|
topic,
|
|
142
|
-
pausedAt
|
|
143
|
+
pausedAt,
|
|
143
144
|
runSession: scope.run?.session,
|
|
144
145
|
})), { name: "pause.hold" });
|
|
145
146
|
await suspendCredential("pause.hold.suspend");
|
|
146
|
-
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "paused", cause, attempt });
|
|
147
|
+
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "paused", cause, pausedAt, attempt });
|
|
147
148
|
log.info("work held", { workflowId, ...work, attempt });
|
|
148
149
|
const held = await waitForMessage(topic, undefined, signal);
|
|
149
150
|
end = held === "hold" ? "resume" : held;
|
|
@@ -155,8 +156,13 @@ export function workPause(scope) {
|
|
|
155
156
|
finally {
|
|
156
157
|
untrack();
|
|
157
158
|
}
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
// The time is read inside the step, thus a recovery replays the recorded time and the end keeps one time.
|
|
160
|
+
// The replayed step output is `undefined` when the recorded step has no time.
|
|
161
|
+
const endedAt = await DBOS.runStep(async () => {
|
|
162
|
+
unwrapOrThrow(await clearPause(scope.pool, workflowId));
|
|
163
|
+
return new Date().toISOString();
|
|
164
|
+
}, { name: "pause.clear" });
|
|
165
|
+
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "running", cause, pausedAt, endedAt, end });
|
|
160
166
|
log.info("pause ended", { workflowId, ...work, end });
|
|
161
167
|
return end;
|
|
162
168
|
};
|
|
@@ -157,6 +157,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
157
157
|
pool: deps.pool,
|
|
158
158
|
workId: input.runId,
|
|
159
159
|
workKind: "run",
|
|
160
|
+
waiter: { kind: "step", stepId: input.stepId },
|
|
160
161
|
analysisId: input.analysisId,
|
|
161
162
|
emit: safeEmit,
|
|
162
163
|
retryDelaysMs: deps.pauseRetryDelaysMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inflexa-ai/harness",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.5",
|
|
4
4
|
"description": "Host-agnostic bioinformatics agent harness — an agent loop on the AI SDK, DBOS-durable workflows, sandboxed compute.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -37,8 +37,10 @@
|
|
|
37
37
|
"build": "tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
38
38
|
"design:fixture": "bun scripts/render-fixture.ts",
|
|
39
39
|
"design:gallery": "bun scripts/render-gallery.ts",
|
|
40
|
-
"
|
|
41
|
-
"format
|
|
40
|
+
"dupes": "jscpd src scripts --exit-code --fail-on-empty",
|
|
41
|
+
"format": "oxfmt src/",
|
|
42
|
+
"format:check": "oxfmt --check src/",
|
|
43
|
+
"format:file": "oxfmt",
|
|
42
44
|
"gallery:data": "bash scripts/gallery-data.sh",
|
|
43
45
|
"lint": "bun --bun oxlint",
|
|
44
46
|
"lint:python": "ruff check src/input-scan/decoder && ruff format --check src/input-scan/decoder",
|
|
@@ -119,9 +121,10 @@
|
|
|
119
121
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
120
122
|
"csstree-validator": "^4.0.1",
|
|
121
123
|
"html-validate": "^11.6.2",
|
|
124
|
+
"jscpd": "5.4.2",
|
|
125
|
+
"oxfmt": "0.71.0",
|
|
122
126
|
"oxlint": "1.85.0",
|
|
123
127
|
"oxlint-tsgolint": "7.0.2003",
|
|
124
|
-
"prettier": "^3.9.5",
|
|
125
128
|
"testcontainers": "^10.28.0",
|
|
126
129
|
"typescript": "^6.0.3"
|
|
127
130
|
},
|