@inflexa-ai/harness 0.35.6 → 0.35.7

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/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export { bootHarness } from "./runtime/boot.js";
21
21
  export type { BootHarnessDeps, BootedHarness } from "./runtime/boot.js";
22
22
  export { createConversationAgent } from "./agents/conversation-agent.js";
23
23
  export type { ConversationAgentDeps } from "./agents/conversation-agent.js";
24
+ export type { BioToolKeys } from "./tools/bio/keys.js";
24
25
  export { createLocalRunAuthorizer } from "./auth/local-run-authorizer.js";
25
26
  export type { RunAuthorizer, AuthorizeRunInput, RunAuthorization, RevokeByJtiRef } from "./execution/run-authorizer.js";
26
27
  export { createNoopBillingResolver } from "./billing/noop-resolver.js";
@@ -157,6 +158,11 @@ export { registerSandboxStep } from "./workflows/sandbox-step.js";
157
158
  export type { SandboxStepDeps, SandboxStepInput, SandboxStepResult, SandboxAgentBuildContext } from "./workflows/sandbox-step.js";
158
159
  export { DEFAULT_CLAIM_INVESTIGATION_CONFIG } from "./workflows/target-assessment/investigation/index.js";
159
160
  export type { ClaimInvestigationConfig } from "./workflows/target-assessment/investigation/index.js";
161
+ export { insertAssessment, getAssessment, updateProgress, listAssessmentsByOrg } from "./state/target-assessments.js";
162
+ export type { InsertAssessmentInput, ListAssessmentsOptions } from "./state/target-assessments.js";
163
+ export type { TargetAssessmentRow, TargetAssessmentListRow, TargetAssessmentStatus, TargetAssessmentError } from "./state/target-assessments.js";
164
+ export { createTargetAssessmentProgressStream } from "./workflows/target-assessment/progress-stream.js";
165
+ export type { TargetAssessmentProgressStream, TargetAssessmentProgressStreamDeps, TargetAssessmentProgressHandler, TargetAssessmentProgressSubscribeOptions, } from "./workflows/target-assessment/progress-stream.js";
160
166
  export { createRunEventStream } from "./execution/run-event-stream.js";
161
167
  export type { RunEventStream, RunEventStreamDeps, RunEventSubscribeOptions, RunEventPartHandler } from "./execution/run-event-stream.js";
162
168
  export { createSandboxAgents, SANDBOX_AGENT_META } from "./agents/sandbox/index.js";
package/dist/index.js CHANGED
@@ -275,6 +275,23 @@ export { registerSandboxStep } from "./workflows/sandbox-step.js";
275
275
  // embedder wiring `executeTargetAssessment` sets these at its composition root;
276
276
  // the dossier reports whichever values were in force.
277
277
  export { DEFAULT_CLAIM_INVESTIGATION_CONFIG } from "./workflows/target-assessment/investigation/index.js";
278
+ // The row surface of `cortex_target_assessments`: the intake write an embedder's
279
+ // trigger route makes, the two reads its detail and list routes make, and the
280
+ // progress write. The row types ride with them — `getAssessment` and
281
+ // `listAssessmentsByOrg` hand back values an embedder cannot hold without naming,
282
+ // and `TargetAssessmentStatus` is what a host switches on to render one. The
283
+ // terminal writers (`setDossier`, `markFailed`, `markAssessmentSuspended`,
284
+ // `markAssessmentRunning`, `softDeleteAssessment`) stay off the front door: the
285
+ // workflow's own terminal path owns them, and an embedder calling one races the
286
+ // state the workflow is about to write.
287
+ export { insertAssessment, getAssessment, updateProgress, listAssessmentsByOrg } from "./state/target-assessments.js";
288
+ // The read side of a target assessment's progress stream. `emitProgress` writes
289
+ // one part per phase and the workflow owns that write; this is the only way to
290
+ // read it back without reaching for the durability engine, which is exactly what
291
+ // an embedder rendering a progress bar would otherwise have to do. One stream per
292
+ // assessment (`workflowID === assessmentId`), so it needs no pool — unlike the
293
+ // run-event seam below, which fans in a parent and its children.
294
+ export { createTargetAssessmentProgressStream } from "./workflows/target-assessment/progress-stream.js";
278
295
  // Run-event read seam — the fine-grained durable channel beside `observeRun`.
279
296
  // The two are a deliberate pair, not a duplication: `observeRun` is the coarse,
280
297
  // in-process run/step snapshot a workflow hands its host synchronously, and it
@@ -0,0 +1,75 @@
1
+ /**
2
+ * TargetAssessmentProgressStream — the read side of a target assessment's
3
+ * durable progress stream.
4
+ *
5
+ * `emitProgress` (the sibling `progress.ts`) writes one part per phase
6
+ * transition and nothing reads it back, so an embedder that wants to show a
7
+ * user how far an assessment has got had to reach for the durability engine
8
+ * itself. This seam is that read, and it quarantines the engine the way
9
+ * `RunLauncher` does for starting workflows: the delivered value is the
10
+ * harness's own `TargetAssessmentProgressEvent` contract type, and no engine
11
+ * type appears in the exported signature.
12
+ *
13
+ * It is far simpler than `execution/run-event-stream.ts`, and deliberately so.
14
+ * A run fans out over a parent workflow and every sandbox-step child, each
15
+ * writing its own stream, so that seam polls a ledger to discover children and
16
+ * folds reconciling parts before delivery. A target assessment writes progress
17
+ * from the parent body alone, under `workflowID === assessmentId`. One stream,
18
+ * no discovery, no pool.
19
+ *
20
+ * It is a push subscription rather than a returned iterator because handing the
21
+ * engine's async generator back to a caller would put the engine's model on the
22
+ * very surface this seam exists to remove, and would leave nowhere to contain a
23
+ * failure. A handler gives both, and matches the `EmitFn` idiom the writer side
24
+ * already speaks.
25
+ */
26
+ import { type TargetAssessmentProgressEvent } from "@inflexa-ai/harness/contracts/target-dossier.js";
27
+ import type { Logger } from "../../lib/logger.js";
28
+ /**
29
+ * Construction-time dependencies. The logger is where every contained failure
30
+ * is reported, and defaults to silence so an embedder that wired none sees
31
+ * nothing rather than having console forced on it.
32
+ */
33
+ export interface TargetAssessmentProgressStreamDeps {
34
+ readonly logger?: Logger;
35
+ }
36
+ /**
37
+ * Receives one progress event. May be async — the subscription awaits it, so a
38
+ * handler is never invoked concurrently with itself. A throw is logged and
39
+ * swallowed.
40
+ */
41
+ export type TargetAssessmentProgressHandler = (event: TargetAssessmentProgressEvent) => void | Promise<void>;
42
+ /** Call-time parameters of one subscription. */
43
+ export interface TargetAssessmentProgressSubscribeOptions {
44
+ /** The assessment to observe. Also the workflow's id — TA sets the two equal. */
45
+ readonly assessmentId: string;
46
+ /** Where each delivered event goes. */
47
+ readonly onEvent: TargetAssessmentProgressHandler;
48
+ /** Aborting stops delivery and settles the returned promise. */
49
+ readonly signal: AbortSignal;
50
+ }
51
+ /**
52
+ * The progress read seam. One method, because an assessment-scoped subscription
53
+ * is the whole capability — a general-purpose stream reader would leak the
54
+ * engine's model back into the surface this exists to quarantine.
55
+ */
56
+ export interface TargetAssessmentProgressStream {
57
+ /**
58
+ * Deliver every progress event the assessment produces to `onEvent`,
59
+ * resolving when the assessment's stream drains or when the signal aborts.
60
+ *
61
+ * The stream drains when the workflow stops being active. A workflow that
62
+ * self-cancels on a 402 is no longer active, so the stream drains at the
63
+ * `suspended` phase and this settles there — it does NOT wait for a later
64
+ * `DBOS.resumeWorkflow`. Re-opening across a resume would mean polling the
65
+ * row's status, which is the caller's business, not this seam's.
66
+ *
67
+ * Events arrive in the order the workflow wrote them, each exactly once.
68
+ * There is no fold: the body emits each phase once and the engine caches a
69
+ * step's stream-write offset, so a recovered workflow does not re-write one.
70
+ * A fold would collapse nothing and cost a buffer.
71
+ */
72
+ subscribe(options: TargetAssessmentProgressSubscribeOptions): Promise<void>;
73
+ }
74
+ /** Build the DBOS-backed realization of {@link TargetAssessmentProgressStream}. */
75
+ export declare function createTargetAssessmentProgressStream(deps?: TargetAssessmentProgressStreamDeps): TargetAssessmentProgressStream;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * TargetAssessmentProgressStream — the read side of a target assessment's
3
+ * durable progress stream.
4
+ *
5
+ * `emitProgress` (the sibling `progress.ts`) writes one part per phase
6
+ * transition and nothing reads it back, so an embedder that wants to show a
7
+ * user how far an assessment has got had to reach for the durability engine
8
+ * itself. This seam is that read, and it quarantines the engine the way
9
+ * `RunLauncher` does for starting workflows: the delivered value is the
10
+ * harness's own `TargetAssessmentProgressEvent` contract type, and no engine
11
+ * type appears in the exported signature.
12
+ *
13
+ * It is far simpler than `execution/run-event-stream.ts`, and deliberately so.
14
+ * A run fans out over a parent workflow and every sandbox-step child, each
15
+ * writing its own stream, so that seam polls a ledger to discover children and
16
+ * folds reconciling parts before delivery. A target assessment writes progress
17
+ * from the parent body alone, under `workflowID === assessmentId`. One stream,
18
+ * no discovery, no pool.
19
+ *
20
+ * It is a push subscription rather than a returned iterator because handing the
21
+ * engine's async generator back to a caller would put the engine's model on the
22
+ * very surface this seam exists to remove, and would leave nowhere to contain a
23
+ * failure. A handler gives both, and matches the `EmitFn` idiom the writer side
24
+ * already speaks.
25
+ */
26
+ import { DBOS } from "@dbos-inc/dbos-sdk";
27
+ import { TargetAssessmentProgressEventSchema } from "@inflexa-ai/harness/contracts/target-dossier.js";
28
+ import { createNoopLogger } from "../../lib/console-logger.js";
29
+ import { TA_PROGRESS_STREAM_KEY } from "./progress.js";
30
+ /**
31
+ * Shape `emitProgress` writes. The `data-target-assessment-progress` key is not
32
+ * in the chat-part registry — it is the writer's private envelope — so the
33
+ * payload, not the envelope, is what a caller gets.
34
+ */
35
+ function readEvent(value) {
36
+ if (typeof value !== "object" || value === null)
37
+ return null;
38
+ const parsed = TargetAssessmentProgressEventSchema.safeParse(value.payload);
39
+ return parsed.success ? parsed.data : null;
40
+ }
41
+ /** Build the DBOS-backed realization of {@link TargetAssessmentProgressStream}. */
42
+ export function createTargetAssessmentProgressStream(deps = {}) {
43
+ const baseLogger = (deps.logger ?? createNoopLogger()).named("ta-progress-stream");
44
+ return {
45
+ async subscribe({ assessmentId, onEvent, signal }) {
46
+ const logger = baseLogger.with({ assessmentId });
47
+ let resolveAbort;
48
+ const aborted = new Promise((resolve) => {
49
+ resolveAbort = resolve;
50
+ });
51
+ const generator = DBOS.readStream(assessmentId, TA_PROGRESS_STREAM_KEY);
52
+ // The engine's reader exposes no cancellation: while it waits on a
53
+ // quiet-but-active workflow it is suspended inside an await, where a
54
+ // queued `return()` is only honoured once it next reaches a yield.
55
+ // Teardown is therefore best-effort by construction, and the read's
56
+ // own promise is raced against the signal rather than waiting on it.
57
+ const onAbort = () => {
58
+ resolveAbort();
59
+ void generator.return(undefined).catch(() => {
60
+ /* the read is already being abandoned */
61
+ });
62
+ };
63
+ if (signal.aborted)
64
+ onAbort();
65
+ else
66
+ signal.addEventListener("abort", onAbort, { once: true });
67
+ const read = (async () => {
68
+ try {
69
+ for (;;) {
70
+ const next = await generator.next();
71
+ // Checked after the read, not before: `next()` is what
72
+ // notices the workflow going inactive, and an aborted
73
+ // subscription must deliver nothing further.
74
+ if (next.done === true || signal.aborted)
75
+ return;
76
+ const event = readEvent(next.value);
77
+ if (event === null) {
78
+ logger.debug("dropped a stream value outside the progress contract");
79
+ continue;
80
+ }
81
+ try {
82
+ await onEvent(event);
83
+ }
84
+ catch (err) {
85
+ logger.warn("progress handler threw", { phase: event.phase, ...logger.errorFields(err) });
86
+ }
87
+ }
88
+ }
89
+ catch (err) {
90
+ // Observation is a diagnostic channel: `emitProgress` already
91
+ // treats a failed write as best-effort so a lost frame cannot
92
+ // fail the workflow. A read that propagated here would be
93
+ // strictly less robust than the writer it observes.
94
+ logger.error("progress stream read failed", logger.errorFields(err));
95
+ }
96
+ finally {
97
+ void generator.return(undefined).catch(() => {
98
+ /* already finished, or abandoned under abort */
99
+ });
100
+ }
101
+ })();
102
+ try {
103
+ await Promise.race([read, aborted]);
104
+ }
105
+ finally {
106
+ signal.removeEventListener("abort", onAbort);
107
+ }
108
+ },
109
+ };
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inflexa-ai/harness",
3
- "version": "0.35.6",
3
+ "version": "0.35.7",
4
4
  "description": "Host-agnostic bioinformatics agent harness — hand-rolled agent loop, DBOS-durable workflows, sandboxed compute.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",