@inflexa-ai/harness 0.26.1 → 0.26.3
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.
|
@@ -24,6 +24,10 @@ const xmlParser = new XMLParser({
|
|
|
24
24
|
attributeNamePrefix: "@_",
|
|
25
25
|
isArray: (name) => ["PubmedArticle", "Author", "MeshHeading", "AbstractText", "sec", "p", "record"].includes(name),
|
|
26
26
|
textNodeName: "#text",
|
|
27
|
+
// Each consumer reads a text node as a string. Without this option the
|
|
28
|
+
// parser turns a numeric text node into a number, and a year-only PubDate
|
|
29
|
+
// then crashes the year extraction in `parseEsummary`.
|
|
30
|
+
parseTagValue: false,
|
|
27
31
|
});
|
|
28
32
|
function getItemValue(items, name) {
|
|
29
33
|
if (items === undefined)
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* The body owns the whole machine lifetime. It creates the container, submits one exec, awaits the terminal
|
|
13
13
|
* result, and tears the container down on both paths. A teardown fault reaches the log alone, because the
|
|
14
14
|
* work is done by then.
|
|
15
|
+
*
|
|
16
|
+
* The exec id is `${workflowId}:${stepId}:${fnId}`, the one shape that `workflowIdFromExec` parses. A
|
|
17
|
+
* callback host reads the owner workflow out of the exec id alone, thus a flat id makes the completion
|
|
18
|
+
* callback unroutable and it lands as a `400`. The pull backstop still settles such an exec, but it pays
|
|
19
|
+
* the quiet interval first.
|
|
15
20
|
*/
|
|
16
21
|
import type { Logger } from "../lib/logger.js";
|
|
17
22
|
import type { SandboxClient } from "../sandbox/client.js";
|
|
@@ -39,8 +44,13 @@ export declare function runDeriveTableExecBody(input: DeriveTableExecInput, deps
|
|
|
39
44
|
* dispatches with `DBOS.startWorkflow`.
|
|
40
45
|
*/
|
|
41
46
|
export declare function registerDeriveTableExecWorkflow(deps: DeriveTableExecDeps): (input: DeriveTableExecInput) => Promise<ExecResult>;
|
|
42
|
-
/**
|
|
43
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The workflow id of one derivation.
|
|
49
|
+
*
|
|
50
|
+
* It carries exactly one colon, because `workflowIdFromExec` recovers the id by a strip of the last two
|
|
51
|
+
* colon-delimited segments of the exec id. The execution id is unique already, thus no nonce joins it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function deriveTableExecWorkflowId(executionId: string): string;
|
|
44
54
|
/**
|
|
45
55
|
* The async edge. It starts the registered workflow and it awaits the terminal result.
|
|
46
56
|
*
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
* The body owns the whole machine lifetime. It creates the container, submits one exec, awaits the terminal
|
|
13
13
|
* result, and tears the container down on both paths. A teardown fault reaches the log alone, because the
|
|
14
14
|
* work is done by then.
|
|
15
|
+
*
|
|
16
|
+
* The exec id is `${workflowId}:${stepId}:${fnId}`, the one shape that `workflowIdFromExec` parses. A
|
|
17
|
+
* callback host reads the owner workflow out of the exec id alone, thus a flat id makes the completion
|
|
18
|
+
* callback unroutable and it lands as a `400`. The pull backstop still settles such an exec, but it pays
|
|
19
|
+
* the quiet interval first.
|
|
15
20
|
*/
|
|
16
21
|
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
17
|
-
import { randomUUID } from "node:crypto";
|
|
18
22
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
19
23
|
import { mintSandboxIdentity } from "../sandbox/identity.js";
|
|
20
24
|
import { buildDerivationExec, DERIVATION_DEADLINE_MS, DERIVATION_RESOURCES, DERIVE_RUN_LITERAL, DERIVE_STEP_LITERAL, } from "../tools/report-session/derive-table.js";
|
|
@@ -28,7 +32,10 @@ const noopEmit = () => { };
|
|
|
28
32
|
*/
|
|
29
33
|
export async function runDeriveTableExecBody(input, deps) {
|
|
30
34
|
const logger = (deps.logger ?? createNoopLogger()).named("derive-table-exec").with({ analysisId: input.analysisId });
|
|
31
|
-
const workflowId = DBOS.workflowID ??
|
|
35
|
+
const workflowId = DBOS.workflowID ?? deriveTableExecWorkflowId(input.executionId);
|
|
36
|
+
// One workflow submits one exec, thus the function id is a constant. The three segments are what a
|
|
37
|
+
// callback host parses to find the workflow that awaits this exec.
|
|
38
|
+
const execId = `${workflowId}:${DERIVE_STEP_LITERAL}:fn-0`;
|
|
32
39
|
const sandbox = await deps.sandboxClient.createSandbox({
|
|
33
40
|
runId: DERIVE_RUN_LITERAL,
|
|
34
41
|
stepId: DERIVE_STEP_LITERAL,
|
|
@@ -43,12 +50,12 @@ export async function runDeriveTableExecBody(input, deps) {
|
|
|
43
50
|
const deadline = (await (deps.now ?? (() => DBOS.now()))()) + DERIVATION_DEADLINE_MS;
|
|
44
51
|
await deps.sandboxClient.submitExec(sandbox, buildDerivationExec({
|
|
45
52
|
script: input.script,
|
|
46
|
-
execId
|
|
53
|
+
execId,
|
|
47
54
|
workingDir: input.workingDir,
|
|
48
55
|
inputs: input.inputs,
|
|
49
56
|
output: input.output,
|
|
50
57
|
}));
|
|
51
|
-
return await deps.sandboxClient.awaitExec(sandbox,
|
|
58
|
+
return await deps.sandboxClient.awaitExec(sandbox, execId, noopEmit, deadline);
|
|
52
59
|
}
|
|
53
60
|
finally {
|
|
54
61
|
try {
|
|
@@ -66,9 +73,14 @@ export async function runDeriveTableExecBody(input, deps) {
|
|
|
66
73
|
export function registerDeriveTableExecWorkflow(deps) {
|
|
67
74
|
return DBOS.registerWorkflow((input) => runDeriveTableExecBody(input, deps), { name: "derive-table-exec" });
|
|
68
75
|
}
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The workflow id of one derivation.
|
|
78
|
+
*
|
|
79
|
+
* It carries exactly one colon, because `workflowIdFromExec` recovers the id by a strip of the last two
|
|
80
|
+
* colon-delimited segments of the exec id. The execution id is unique already, thus no nonce joins it.
|
|
81
|
+
*/
|
|
82
|
+
export function deriveTableExecWorkflowId(executionId) {
|
|
83
|
+
return `${DERIVE_RUN_LITERAL}:${executionId}`;
|
|
72
84
|
}
|
|
73
85
|
/**
|
|
74
86
|
* The async edge. It starts the registered workflow and it awaits the terminal result.
|
|
@@ -78,7 +90,7 @@ export function deriveTableExecWorkflowId(executionId, nonce) {
|
|
|
78
90
|
*/
|
|
79
91
|
export async function triggerDeriveTableExec(workflow, input) {
|
|
80
92
|
const handle = (await DBOS.startWorkflow(workflow, {
|
|
81
|
-
workflowID: deriveTableExecWorkflowId(input.executionId
|
|
93
|
+
workflowID: deriveTableExecWorkflowId(input.executionId),
|
|
82
94
|
})(input));
|
|
83
95
|
return handle.getResult();
|
|
84
96
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* entire tool call is one step in the parent loop (PR #3 caches it whole,
|
|
15
15
|
* so the child never replays).
|
|
16
16
|
*/
|
|
17
|
-
import { ok } from "neverthrow";
|
|
17
|
+
import { err, ok } from "neverthrow";
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import { finalText, runAgent } from "../../loop/run-agent.js";
|
|
20
20
|
import { passthroughStep } from "../../loop/run-step.js";
|
|
@@ -70,7 +70,7 @@ export function createLiteratureReviewerTool(deps) {
|
|
|
70
70
|
}),
|
|
71
71
|
describeCall: "none",
|
|
72
72
|
execute: async ({ brief }, ctx) => {
|
|
73
|
-
const { messages: transcript } = await runAgent(agent, [{ role: "user", content: brief }], forSubAgent(ctx.session, AGENT_ID), {
|
|
73
|
+
const { messages: transcript, finish } = await runAgent(agent, [{ role: "user", content: brief }], forSubAgent(ctx.session, AGENT_ID), {
|
|
74
74
|
provider: deps.provider,
|
|
75
75
|
signal: ctx.signal,
|
|
76
76
|
emit: ctx.emit,
|
|
@@ -82,7 +82,15 @@ export function createLiteratureReviewerTool(deps) {
|
|
|
82
82
|
// same frame, same call path, same loop-local step names.
|
|
83
83
|
invocationId: ctx.invocationId,
|
|
84
84
|
});
|
|
85
|
-
|
|
85
|
+
const report = finalText(transcript);
|
|
86
|
+
// A child run can end with no final text. Examples are a terminal
|
|
87
|
+
// reply with no text block, and a capped-out run whose wrap-up is
|
|
88
|
+
// empty. An ok result with an empty report hides that failure, so
|
|
89
|
+
// the tool returns an error that names the finish reason.
|
|
90
|
+
if (report.trim() === "") {
|
|
91
|
+
return err({ error: `The literature reviewer gave no report (finish reason: ${finish.reason}). Retry, or narrow the brief.`, retryable: true });
|
|
92
|
+
}
|
|
93
|
+
return ok({ report });
|
|
86
94
|
},
|
|
87
95
|
});
|
|
88
96
|
}
|
package/package.json
CHANGED