@inflexa-ai/harness 0.43.1 → 0.45.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/report-session-agent.d.ts +1 -1
- package/dist/app/approve-plan.d.ts +48 -0
- package/dist/app/approve-plan.js +84 -0
- package/dist/app/data-profile-view.d.ts +1 -1
- package/dist/app/run-activity.js +4 -6
- package/dist/app/synthesize-run.js +1 -1
- package/dist/contracts/chat-events.d.ts +2 -0
- package/dist/contracts/chat-frame.d.ts +7 -0
- package/dist/contracts/chat-frame.js +18 -4
- package/dist/contracts/chat-parts.d.ts +73 -2
- package/dist/contracts/chat-parts.js +3 -1
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/part-registry.js +3 -0
- package/dist/contracts/schemas/chat-events.d.ts +13 -0
- package/dist/contracts/schemas/chat-events.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +200 -15
- package/dist/contracts/schemas/chat-parts.js +63 -12
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/contracts/subagent-activity.d.ts +12 -0
- package/dist/contracts/subagent-activity.js +76 -0
- package/dist/execution/artifact-metadata.js +1 -3
- package/dist/execution/post-step-pipeline.js +1 -1
- package/dist/execution/run-canceler.js +1 -1
- package/dist/execution/run-event-parts.d.ts +6 -5
- package/dist/execution/run-event-parts.js +6 -5
- package/dist/execution/run-event-stream.d.ts +18 -6
- package/dist/execution/run-event-stream.js +67 -17
- package/dist/execution/run-synthesis.d.ts +21 -0
- package/dist/execution/run-synthesis.js +77 -3
- package/dist/index.d.ts +24 -10
- package/dist/index.js +17 -3
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/metrics.d.ts +2 -2
- package/dist/loop/run-agent.js +1 -0
- package/dist/loop/types.d.ts +2 -0
- package/dist/memory/card-builders.d.ts +11 -0
- package/dist/memory/card-builders.js +35 -21
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +10 -1
- package/dist/memory/conversation-display-storage.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +18 -3
- package/dist/memory/thread-history.d.ts +8 -1
- package/dist/memory/thread-history.js +13 -3
- package/dist/memory/thread-store.d.ts +13 -0
- package/dist/memory/thread-store.js +15 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -1
- package/dist/provenance/seam.d.ts +16 -5
- package/dist/providers/ai-sdk.js +6 -4
- package/dist/reference-data/receipt.d.ts +16 -4
- package/dist/reference-data/receipt.js +20 -3
- package/dist/report-model/pin-snapshot.d.ts +1 -1
- package/dist/report-model/pin-snapshot.js +4 -52
- package/dist/runtime/assemble.d.ts +9 -2
- package/dist/runtime/assemble.js +13 -2
- package/dist/runtime/boot.d.ts +3 -0
- package/dist/runtime/boot.js +10 -1
- package/dist/sandbox/client.d.ts +1 -1
- package/dist/sandbox/create-sandbox.js +1 -1
- package/dist/sandbox/docker-client.js +1 -1
- package/dist/sandbox/k8s-client.js +5 -5
- package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
- package/dist/sandbox/sandbox-step-translate.js +24 -0
- package/dist/schemas/artifact-manifest.d.ts +1 -1
- package/dist/schemas/artifact-manifest.js +11 -9
- package/dist/schemas/plan-schemas.d.ts +18 -18
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/state/analyses-metrics.d.ts +79 -7
- package/dist/state/analyses-metrics.js +141 -6
- package/dist/state/artifacts.d.ts +50 -3
- package/dist/state/artifacts.js +67 -7
- package/dist/state/data-profile.d.ts +4 -2
- package/dist/state/data-profile.js +7 -5
- package/dist/state/index.d.ts +9 -8
- package/dist/state/index.js +5 -5
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
- package/dist/state/migrations/index.js +8 -0
- package/dist/state/plans.d.ts +21 -0
- package/dist/state/plans.js +25 -0
- package/dist/state/runs.d.ts +36 -5
- package/dist/state/runs.js +65 -19
- package/dist/state/schema.d.ts +2 -0
- package/dist/tasks/data-profile-activity.d.ts +7 -1
- package/dist/tasks/data-profile-activity.js +18 -9
- package/dist/tasks/data-profile.d.ts +15 -1
- package/dist/tasks/data-profile.js +18 -13
- package/dist/tools/approval/gateway.js +28 -5
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +51 -0
- package/dist/tools/execute-analysis.js +241 -109
- package/dist/tools/report-session/derive-table.js +1 -1
- package/dist/tools/report-session/examine-page.js +1 -1
- package/dist/tools/report-session/list-artifacts.js +2 -2
- package/dist/tools/report-session/preview-report.d.ts +1 -1
- package/dist/tools/report-session/preview-report.js +6 -6
- package/dist/tools/report-session/record-version.d.ts +1 -1
- package/dist/tools/report-session/record-version.js +4 -4
- package/dist/tools/research/inspect-data-profile.d.ts +1 -1
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/inspect-run.js +7 -7
- package/dist/tools/workspace/mutator.js +2 -2
- package/dist/workflows/execute-analysis.d.ts +4 -3
- package/dist/workflows/execute-analysis.js +38 -25
- package/dist/workflows/plan/graph.d.ts +16 -0
- package/dist/workflows/plan/graph.js +36 -0
- package/dist/workflows/sandbox-step.d.ts +2 -2
- package/dist/workflows/sandbox-step.js +16 -14
- package/dist/workspace/filesystem.js +18 -22
- package/dist/workspace/paths.d.ts +7 -4
- package/package.json +4 -4
|
@@ -50,8 +50,9 @@
|
|
|
50
50
|
* the run before any step starts
|
|
51
51
|
* - a `suspend` error of a synthesis model request suspends the run in
|
|
52
52
|
* place of the synthesis failure
|
|
53
|
-
* - the analysis
|
|
54
|
-
* `collectAndComplete`. The
|
|
53
|
+
* - the run row and the analysis flip to `suspended_insufficient_funds` in
|
|
54
|
+
* `collectAndComplete`. The run status is terminal and does not block a
|
|
55
|
+
* new run of the plan. The paused parent lands in `CANCELLED`, a
|
|
55
56
|
* durably reschedulable state; re-driving it is a future enhancement,
|
|
56
57
|
* not yet wired here. The reason of the host rides to the run row, the
|
|
57
58
|
* charge close, the metric, and the terminal part, unread.
|
|
@@ -160,7 +161,7 @@ export interface ExecuteAnalysisInput {
|
|
|
160
161
|
* path returns — the caller joined an in-flight run, no new workflow was
|
|
161
162
|
* started. Every other variant is a terminal status the parent reached.
|
|
162
163
|
*/
|
|
163
|
-
export type ExecuteAnalysisFinalStatus = "running" | "completed" | "partial" | "failed" | "canceled";
|
|
164
|
+
export type ExecuteAnalysisFinalStatus = "running" | "completed" | "partial" | "failed" | "canceled" | "suspended_insufficient_funds";
|
|
164
165
|
export interface ExecuteAnalysisResult {
|
|
165
166
|
readonly runId: string;
|
|
166
167
|
readonly workflowId: string;
|
|
@@ -50,8 +50,9 @@
|
|
|
50
50
|
* the run before any step starts
|
|
51
51
|
* - a `suspend` error of a synthesis model request suspends the run in
|
|
52
52
|
* place of the synthesis failure
|
|
53
|
-
* - the analysis
|
|
54
|
-
* `collectAndComplete`. The
|
|
53
|
+
* - the run row and the analysis flip to `suspended_insufficient_funds` in
|
|
54
|
+
* `collectAndComplete`. The run status is terminal and does not block a
|
|
55
|
+
* new run of the plan. The paused parent lands in `CANCELLED`, a
|
|
55
56
|
* durably reschedulable state; re-driving it is a future enhancement,
|
|
56
57
|
* not yet wired here. The reason of the host rides to the run row, the
|
|
57
58
|
* charge close, the metric, and the terminal part, unread.
|
|
@@ -79,12 +80,13 @@ import { ATTR_INFLEXA_STEP_ID, stableSpan } from "../lib/otel-spans.js";
|
|
|
79
80
|
import { recordRunCompleted, recordStepCompleted, stepOutcomeOf } from "../lib/metrics.js";
|
|
80
81
|
import { describeDbError } from "../lib/db-result.js";
|
|
81
82
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
82
|
-
import {
|
|
83
|
+
import { countArtifactsByRuns, insertStepExecution, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, queryStepsByRun, seedStepExecutions, setRunSynthesisOutcome, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, updateStepExecution, } from "../state/index.js";
|
|
83
84
|
import { addChatUsage, hasReportedUsage } from "../loop/metrics.js";
|
|
85
|
+
import { flattenDataPart } from "../contracts/chat-frame.js";
|
|
84
86
|
import { SYNTHESIS_AGENT_ID, loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
|
|
85
87
|
import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing, emptyStepMemory } from "../prompts/briefing.js";
|
|
86
88
|
import { SYNTHESIS_STEP_ID, runDir, runStepDir, stepSubdir, stepWritePrefix, toSandboxPath } from "../workspace/paths.js";
|
|
87
|
-
import { isChatDataPart } from "../sandbox/sandbox-step-translate.js";
|
|
89
|
+
import { isChatDataPart, synthesisEmission } from "../sandbox/sandbox-step-translate.js";
|
|
88
90
|
import { synthesizeRun } from "../app/synthesize-run.js";
|
|
89
91
|
import { computeTopologicalLevels, scheduleReady, validatePlanDag } from "./execute-analysis-scheduler.js";
|
|
90
92
|
import { recordCancelledChild } from "./metrics.js";
|
|
@@ -250,7 +252,7 @@ export async function gatherStepPackage(args) {
|
|
|
250
252
|
throw new Error(`executeAnalysis: step "${stepId}" missing from planStepById — every step must carry its plan data`);
|
|
251
253
|
}
|
|
252
254
|
const analysisId = input.analysisId;
|
|
253
|
-
const workspaceRoot = deps.resolveWorkspaceRoot(analysisId);
|
|
255
|
+
const workspaceRoot = await deps.resolveWorkspaceRoot(analysisId);
|
|
254
256
|
// The paths the agent actually sees: the tree is bind-mounted at
|
|
255
257
|
// `/{analysisId}`, and the step's writable directory is its cwd. Derived
|
|
256
258
|
// through the same helpers the sandbox mount and the mutate tools use, so
|
|
@@ -359,9 +361,20 @@ export function synthesisRowUpdate(outcome, durationMs) {
|
|
|
359
361
|
*/
|
|
360
362
|
export function synthesizeFindings(args) {
|
|
361
363
|
const { analysisId, runId, completedSteps, session, deps } = args;
|
|
364
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis.synthesis");
|
|
365
|
+
// Observation is a diagnostic channel: a failed progress write is logged and never fails the synthesis.
|
|
366
|
+
let progressSeq = 0;
|
|
367
|
+
const writeProgressAlone = async (part, phase) => {
|
|
368
|
+
try {
|
|
369
|
+
await DBOS.writeStream("events", part);
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
logger.warn("synthesis progress write failed (non-fatal)", { phase, ...logger.errorFields(err) });
|
|
373
|
+
}
|
|
374
|
+
};
|
|
362
375
|
const emit = (event) => {
|
|
363
376
|
const part = isChatDataPart(event)
|
|
364
|
-
? event
|
|
377
|
+
? flattenDataPart(event)
|
|
365
378
|
: {
|
|
366
379
|
type: "data-loop-event",
|
|
367
380
|
data: { stepId: "synthesis", event },
|
|
@@ -395,14 +408,15 @@ export function synthesizeFindings(args) {
|
|
|
395
408
|
// call path.
|
|
396
409
|
session: forStep(session, SYNTHESIS_STEP_ID),
|
|
397
410
|
emit,
|
|
398
|
-
onProgress: (phase, activity, extra = {}) =>
|
|
399
|
-
|
|
400
|
-
id
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
activity,
|
|
404
|
-
|
|
405
|
-
|
|
411
|
+
onProgress: async (phase, activity, extra = {}) => {
|
|
412
|
+
// Synthesis runs inside one `DBOS.runStep`, thus `DBOS.now()` reads the wall clock here, and a rerun of
|
|
413
|
+
// the step after a crash writes its entries again under the same ids. A reader dedups an entry by id.
|
|
414
|
+
const seq = progressSeq++;
|
|
415
|
+
const at = new Date(await DBOS.now()).toISOString();
|
|
416
|
+
const parts = synthesisEmission(runId, SYNTHESIS_STEP_ID, seq, phase, activity, at, extra);
|
|
417
|
+
await writeProgressAlone(parts.progress, phase);
|
|
418
|
+
await writeProgressAlone(parts.entry, phase);
|
|
419
|
+
},
|
|
406
420
|
});
|
|
407
421
|
}
|
|
408
422
|
// ── Workflow registration ────────────────────────────────────────────
|
|
@@ -661,10 +675,10 @@ async function validateAndInit(input, runId, deps) {
|
|
|
661
675
|
return { kind: "joined-existing", runId: existing.runId };
|
|
662
676
|
}
|
|
663
677
|
await DBOS.runStep(async () => {
|
|
664
|
-
// A resolver
|
|
678
|
+
// A resolver rejection here (unknown analysis, unresolvable root) fails the
|
|
665
679
|
// step durably — exactly the contract the workspace-root-resolution spec
|
|
666
680
|
// requires of failures inside DBOS bodies.
|
|
667
|
-
await mkdir(join(deps.resolveWorkspaceRoot(input.analysisId), runDir(runId)), {
|
|
681
|
+
await mkdir(join(await deps.resolveWorkspaceRoot(input.analysisId), runDir(runId)), {
|
|
668
682
|
recursive: true,
|
|
669
683
|
});
|
|
670
684
|
}, { name: "init-run-filesystem" });
|
|
@@ -1086,7 +1100,7 @@ async function collectAndComplete(args) {
|
|
|
1086
1100
|
durationMs: terminalAtMs - startedAtMs,
|
|
1087
1101
|
}, input.runSession);
|
|
1088
1102
|
await ledgerStep(logger.with({ status }), "persist-final-status", async () => {
|
|
1089
|
-
const written = await updateRunStatus(deps.pool, runId, status, failureReason ?? (status === "canceled"
|
|
1103
|
+
const written = await updateRunStatus(deps.pool, runId, status, failureReason ?? (status === "canceled" ? "external_cancel" : null));
|
|
1090
1104
|
if (written.isErr())
|
|
1091
1105
|
return written;
|
|
1092
1106
|
// The outcome metrics ride inside the step that persists the terminal status: DBOS
|
|
@@ -1119,10 +1133,9 @@ async function collectAndComplete(args) {
|
|
|
1119
1133
|
await ledgerStep(logger.with({ synthesisStatus: outcome.status }), "persist-synthesis-outcome", () => setRunSynthesisOutcome(deps.pool, runId, outcome.status, outcome.reason));
|
|
1120
1134
|
}
|
|
1121
1135
|
// Sweep never-started steps to `skipped` — but ONLY on genuinely-terminal
|
|
1122
|
-
// paths. The gate is the BRANCH, not the written run status: the
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1125
|
-
// finalisation step here.
|
|
1136
|
+
// paths. The gate is the BRANCH, not the written run status: the pending rows
|
|
1137
|
+
// of a suspended run must survive for a later resume. Log-don't-rollback, like
|
|
1138
|
+
// every other finalisation step here.
|
|
1126
1139
|
if (suspended === null) {
|
|
1127
1140
|
await ledgerStep(logger, "sweep-pending-steps", () => sweepPendingStepExecutions(deps.pool, runId));
|
|
1128
1141
|
}
|
|
@@ -1149,9 +1162,9 @@ async function collectAndComplete(args) {
|
|
|
1149
1162
|
// beat the CLI's poll-and-shutdown). These branches only fan out the UI stream part, whose
|
|
1150
1163
|
// completed/failed shapes genuinely differ — the provenance record does not.
|
|
1151
1164
|
if (status === "completed" || status === "partial") {
|
|
1152
|
-
const artifactCount = await DBOS.runStep(async () => await
|
|
1153
|
-
|
|
1154
|
-
|
|
1165
|
+
const artifactCount = await DBOS.runStep(async () => await countArtifactsByRuns(deps.pool, input.analysisId, [runId])
|
|
1166
|
+
.map((counts) => counts[0]?.count ?? 0)
|
|
1167
|
+
.unwrapOr(0), { name: "count-run-artifacts" });
|
|
1155
1168
|
await emitStreamPart({
|
|
1156
1169
|
type: "data-run-completed",
|
|
1157
1170
|
runId,
|
|
@@ -1197,7 +1210,7 @@ async function collectAndComplete(args) {
|
|
|
1197
1210
|
function deriveFinalStatus(args) {
|
|
1198
1211
|
const { totalSteps, completed, failed, canceled, suspended } = args;
|
|
1199
1212
|
if (suspended)
|
|
1200
|
-
return "
|
|
1213
|
+
return "suspended_insufficient_funds";
|
|
1201
1214
|
if (failed.size > 0) {
|
|
1202
1215
|
return completed.size > 0 ? "partial" : "failed";
|
|
1203
1216
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph of a stored plan: each step with its agent, its dependencies, and its
|
|
3
|
+
* level. The levels and the agent fallback are those of `data-dag-state`, so a
|
|
4
|
+
* host draws the same graph before, during, and after a run.
|
|
5
|
+
*/
|
|
6
|
+
import type { PlanGraphStep } from "../../contracts/chat-parts.js";
|
|
7
|
+
/**
|
|
8
|
+
* Give the graph of a stored plan document, in the order of the plan. An empty
|
|
9
|
+
* step list gives an empty graph. A document that fails the stored plan schema, or
|
|
10
|
+
* whose steps have a duplicate id, a missing dependency, or a cycle, gives `null`
|
|
11
|
+
* (a plan that no run can execute has no levels).
|
|
12
|
+
*
|
|
13
|
+
* The throw of `validatePlanDag` is caught here, at the boundary: the function is a
|
|
14
|
+
* read for a caller that has no use for the typed error.
|
|
15
|
+
*/
|
|
16
|
+
export declare function planGraphOf(plan: unknown): PlanGraphStep[] | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph of a stored plan: each step with its agent, its dependencies, and its
|
|
3
|
+
* level. The levels and the agent fallback are those of `data-dag-state`, so a
|
|
4
|
+
* host draws the same graph before, during, and after a run.
|
|
5
|
+
*/
|
|
6
|
+
import { AnalysisPlanSchema } from "../../schemas/workflow-state.js";
|
|
7
|
+
import { computeTopologicalLevels, validatePlanDag } from "../execute-analysis-scheduler.js";
|
|
8
|
+
/**
|
|
9
|
+
* Give the graph of a stored plan document, in the order of the plan. An empty
|
|
10
|
+
* step list gives an empty graph. A document that fails the stored plan schema, or
|
|
11
|
+
* whose steps have a duplicate id, a missing dependency, or a cycle, gives `null`
|
|
12
|
+
* (a plan that no run can execute has no levels).
|
|
13
|
+
*
|
|
14
|
+
* The throw of `validatePlanDag` is caught here, at the boundary: the function is a
|
|
15
|
+
* read for a caller that has no use for the typed error.
|
|
16
|
+
*/
|
|
17
|
+
export function planGraphOf(plan) {
|
|
18
|
+
const parsed = AnalysisPlanSchema.safeParse(plan);
|
|
19
|
+
if (!parsed.success)
|
|
20
|
+
return null;
|
|
21
|
+
const steps = parsed.data.steps;
|
|
22
|
+
try {
|
|
23
|
+
validatePlanDag(steps);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const levels = computeTopologicalLevels(steps);
|
|
29
|
+
return steps.map((step) => ({
|
|
30
|
+
id: step.id,
|
|
31
|
+
name: step.name,
|
|
32
|
+
agent: step.agent ?? "unknown",
|
|
33
|
+
dependsOn: [...step.depends_on],
|
|
34
|
+
level: levels.get(step.id) ?? 0,
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
@@ -263,12 +263,12 @@ export interface SandboxStepDeps {
|
|
|
263
263
|
* agent from the catalog, wires its workspace tools, and returns the
|
|
264
264
|
* configured `AgentDefinition` runAgent will drive.
|
|
265
265
|
*/
|
|
266
|
-
buildAgent(ctx: SandboxAgentBuildContext): AgentDefinition
|
|
266
|
+
buildAgent(ctx: SandboxAgentBuildContext): Promise<AgentDefinition>;
|
|
267
267
|
/**
|
|
268
268
|
* Resolve the absolute writable artifact prefix for this step. The
|
|
269
269
|
* workspace mutate tools enforce write confinement against this path.
|
|
270
270
|
*/
|
|
271
|
-
resolveWritePrefix(input: SandboxStepInput): string
|
|
271
|
+
resolveWritePrefix(input: SandboxStepInput): Promise<string>;
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
274
274
|
* Per-step context passed to every post-step dep. Carries the durable input
|
|
@@ -38,7 +38,7 @@ import { durableStep } from "../loop/run-step.js";
|
|
|
38
38
|
import { maskExcept } from "../loop/tool-mask.js";
|
|
39
39
|
import { READ_TOOL_OUTPUT_TOOL_ID } from "../loop/tool-output.js";
|
|
40
40
|
import { lastExecOutcome } from "../sandbox/exec-outcome.js";
|
|
41
|
-
import { activityForTool, applyTreeDelta, isChatDataPart, sandboxTreeDelta, stepPartId } from "../sandbox/sandbox-step-translate.js";
|
|
41
|
+
import { activityEmission, activityForTool, applyTreeDelta, isChatDataPart, sandboxTreeDelta, stepPartId } from "../sandbox/sandbox-step-translate.js";
|
|
42
42
|
import { createDetailResolver } from "../tools/detail-resolver.js";
|
|
43
43
|
import { forSubAgent } from "../auth/types.js";
|
|
44
44
|
import { walkStepArtifacts } from "../execution/post-step.js";
|
|
@@ -176,7 +176,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
176
176
|
// "conversation-agent"). `run-agent.ts` builds source from
|
|
177
177
|
// `session.provenance.agentId`, and billing tags ride the same provenance.
|
|
178
178
|
const session = forSubAgent(input.runSession, input.agentId);
|
|
179
|
-
const writePrefix = deps.resolveWritePrefix(input);
|
|
179
|
+
const writePrefix = await deps.resolveWritePrefix(input);
|
|
180
180
|
const lineageCollector = createLineageCollector(input);
|
|
181
181
|
// (2a) sandbox.mint — checkpoint the machine's identity (its name)
|
|
182
182
|
// BEFORE it is spawned (see the harness-sandbox-exec spec). Durable-before-create is what makes (2b)
|
|
@@ -220,7 +220,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
220
220
|
const stepDeadlineMs = (await DBOS.now()) + (input.timeoutSeconds ?? DEFAULT_STEP_TIMEOUT_SECONDS) * 1000;
|
|
221
221
|
const blockerHolder = createBlockerHolder();
|
|
222
222
|
const fileMetadata = createFileMetadataCell();
|
|
223
|
-
const agent = deps.buildAgent({
|
|
223
|
+
const agent = await deps.buildAgent({
|
|
224
224
|
input,
|
|
225
225
|
session,
|
|
226
226
|
sandbox,
|
|
@@ -266,17 +266,19 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
266
266
|
logger.warn("emitToParentStream failed (non-fatal)", logger.errorFields(err));
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
|
-
// Schema-conformant step-activity emitter
|
|
270
|
-
// so the run-stream fold collapses every phase transition latest-wins.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
activity,
|
|
279
|
-
|
|
269
|
+
// Schema-conformant step-activity emitter. `data-step-activity` has a stable id per (runId, stepId),
|
|
270
|
+
// so the run-stream fold collapses every phase transition latest-wins. Each emission also leaves one
|
|
271
|
+
// `data-activity-entry` that the fold keeps, thus a late subscriber gets each line with its time. The
|
|
272
|
+
// two parts share one checkpointed clock read. `activitySeq` advances before the clock read and the
|
|
273
|
+
// writes, and each write fails alone through `safeEmit`.
|
|
274
|
+
let activitySeq = 0;
|
|
275
|
+
const emitActivity = async (phase, activity) => {
|
|
276
|
+
const seq = activitySeq++;
|
|
277
|
+
const at = new Date(await DBOS.now()).toISOString();
|
|
278
|
+
const parts = activityEmission(input, seq, phase, activity, at);
|
|
279
|
+
await safeEmit(parts.activity);
|
|
280
|
+
await safeEmit(parts.entry);
|
|
281
|
+
};
|
|
280
282
|
// Single exit for the two terminal failure paths (agent loop, lineage
|
|
281
283
|
// attestation). Tears the sandbox down, logs the internal detail for
|
|
282
284
|
// operators, then persists + emits only a generic phrase and re-raises a
|
|
@@ -32,7 +32,7 @@ import { lstat as fsLstat, open as fsOpen, readdir as fsReaddir, stat as fsStat
|
|
|
32
32
|
import { constants as fsConstants } from "node:fs";
|
|
33
33
|
import { createInterface } from "node:readline";
|
|
34
34
|
import { join } from "node:path";
|
|
35
|
-
import { ResultAsync,
|
|
35
|
+
import { ResultAsync, ok } from "neverthrow";
|
|
36
36
|
import { scopeResource } from "../auth/types.js";
|
|
37
37
|
import { tryFetch, tryFs } from "../lib/fs-result.js";
|
|
38
38
|
import { classifyWithinRoot } from "../lib/fs-helpers.js";
|
|
@@ -41,7 +41,7 @@ import { resolveWorkspacePath } from "./paths.js";
|
|
|
41
41
|
export function createWorkspaceFilesystem(deps) {
|
|
42
42
|
const { resolveWorkspaceRoot, presignedFallback } = deps;
|
|
43
43
|
/**
|
|
44
|
-
* `ResolveWorkspaceRoot` signals an unresolvable resource by
|
|
44
|
+
* `ResolveWorkspaceRoot` signals an unresolvable resource by a rejection — the contract that
|
|
45
45
|
* makes a resolution failure inside a DBOS body a durably-failed step. This seam is not a
|
|
46
46
|
* DBOS body: it promises `Result`, and the read tools it backs are reachable from a live
|
|
47
47
|
* chat turn whose analysis folder may have been moved or deleted since the turn began.
|
|
@@ -50,14 +50,12 @@ export function createWorkspaceFilesystem(deps) {
|
|
|
50
50
|
*/
|
|
51
51
|
function resolveFor(session, path, workingDir) {
|
|
52
52
|
const { resourceId: analysisId } = scopeResource(session.scope);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
return ok({ resolved: resolveWorkspacePath({ workspaceRoot, analysisId, path, workingDir }), workspaceRoot });
|
|
53
|
+
return ResultAsync.fromThrowable(resolveWorkspaceRoot, (cause) => ({
|
|
54
|
+
type: "read_failed",
|
|
55
|
+
op: "workspace.resolveWorkspaceRoot",
|
|
56
|
+
path: analysisId,
|
|
57
|
+
cause,
|
|
58
|
+
}))(analysisId).map((workspaceRoot) => ({ resolved: resolveWorkspacePath({ workspaceRoot, analysisId, path, workingDir }), workspaceRoot }));
|
|
61
59
|
}
|
|
62
60
|
/**
|
|
63
61
|
* Lexical resolution PLUS a symlink-following confinement check — the single
|
|
@@ -70,18 +68,16 @@ export function createWorkspaceFilesystem(deps) {
|
|
|
70
68
|
* presigned-fallback handling still runs (a non-existent path leaks nothing).
|
|
71
69
|
*/
|
|
72
70
|
function confinedResolve(session, path, workingDir) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
cause,
|
|
84
|
-
})).map((verdict) => (verdict === "escaped" ? { kind: "out_of_scope" } : resolved));
|
|
71
|
+
return resolveFor(session, path, workingDir).andThen(({ resolved, workspaceRoot }) => {
|
|
72
|
+
if (resolved.kind === "out_of_scope")
|
|
73
|
+
return okAsync(resolved);
|
|
74
|
+
return ResultAsync.fromPromise(classifyWithinRoot(workspaceRoot, resolved.absolute), (cause) => ({
|
|
75
|
+
type: "read_failed",
|
|
76
|
+
op: "workspace.confineRealpath",
|
|
77
|
+
path: resolved.absolute,
|
|
78
|
+
cause,
|
|
79
|
+
})).map((verdict) => (verdict === "escaped" ? { kind: "out_of_scope" } : resolved));
|
|
80
|
+
});
|
|
85
81
|
}
|
|
86
82
|
return {
|
|
87
83
|
readFile({ session, path, maxBytes, headLines, tailLines, workingDir }) {
|
|
@@ -43,11 +43,14 @@
|
|
|
43
43
|
* - **Stable while the resource has an active run** — derived paths are
|
|
44
44
|
* recorded in durable step outputs; preventing mid-run moves is the
|
|
45
45
|
* embedder's job.
|
|
46
|
-
* - **
|
|
47
|
-
* cross the step boundary as a throw so the step is durably
|
|
48
|
-
* failed (see `lib/result.ts` house rules).
|
|
46
|
+
* - **Rejects on unknown resources** — inside DBOS bodies the awaited
|
|
47
|
+
* rejection must cross the step boundary as a throw so the step is durably
|
|
48
|
+
* recorded as failed (see `lib/result.ts` house rules).
|
|
49
|
+
*
|
|
50
|
+
* Async so that a realization can read host state (a marker file on a network
|
|
51
|
+
* mount) without a block of the event loop.
|
|
49
52
|
*/
|
|
50
|
-
export type ResolveWorkspaceRoot = (resourceId: string) => string
|
|
53
|
+
export type ResolveWorkspaceRoot = (resourceId: string) => Promise<string>;
|
|
51
54
|
/** Result of resolving a workspace path against the canonical layout. */
|
|
52
55
|
export type ResolvedPath = {
|
|
53
56
|
readonly kind: "ok";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inflexa-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
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",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"directory": "harness"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
|
|
36
|
+
"build": "tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
37
37
|
"design:fixture": "bun scripts/render-fixture.ts",
|
|
38
38
|
"design:gallery": "bun scripts/render-gallery.ts",
|
|
39
39
|
"format": "prettier --write src/",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"gallery:data": "bash scripts/gallery-data.sh",
|
|
42
42
|
"lint": "eslint .",
|
|
43
43
|
"lint:python": "ruff check src/input-scan/decoder && ruff format --check src/input-scan/decoder",
|
|
44
|
-
"prepare": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
|
|
44
|
+
"prepare": "tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
45
45
|
"prepublishOnly": "bun run build && bun run smoke",
|
|
46
46
|
"smoke": "node scripts/smoke.mjs",
|
|
47
47
|
"test:full": "bash scripts/test-full.sh",
|
|
48
48
|
"test:python": "python3 -m pytest src/input-scan/decoder -q",
|
|
49
|
-
"typecheck": "tsc --noEmit"
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@ai-sdk/anthropic": "^4.0.62",
|