@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