@inflexa-ai/harness 0.39.0 → 0.39.2
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/system-prompt.d.ts +3 -4
- package/dist/agents/system-prompt.js +1 -1
- package/dist/execution/artifact-registration.d.ts +12 -2
- package/dist/execution/artifact-registration.js +9 -4
- package/dist/execution/post-step-pipeline.d.ts +3 -6
- package/dist/execution/post-step-pipeline.js +11 -3
- package/dist/execution/step-summary.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/loop/metrics.js +1 -1
- package/dist/loop/run-agent.d.ts +2 -2
- package/dist/loop/run-agent.js +6 -8
- package/dist/loop/types.d.ts +1 -1
- package/dist/prompts/SOUL.d.ts +3 -3
- package/dist/prompts/SOUL.js +2 -16
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +14 -24
- package/dist/prompts/execute-analysis/step-summary.js +3 -4
- package/dist/prompts/planner.js +44 -45
- package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
- package/dist/prompts/sandbox/scientific-executor.js +5 -6
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +6 -7
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +9 -13
- package/dist/providers/types.d.ts +5 -4
- package/dist/report-model/pin-snapshot.js +4 -10
- package/dist/state/artifacts.d.ts +11 -9
- package/dist/state/artifacts.js +112 -93
- package/dist/state/data-profile.d.ts +2 -2
- package/dist/state/data-profile.js +2 -2
- package/dist/tasks/data-profile.d.ts +28 -10
- package/dist/tasks/data-profile.js +114 -95
- package/dist/tools/ad-hoc-router.js +2 -1
- package/dist/tools/research/generate-analogy-report.js +2 -4
- package/dist/tools/research/generate-plan.js +7 -8
- package/dist/tools/sandbox/list-available-refs.js +1 -2
- package/dist/workflows/execute-analysis.js +5 -3
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { DBOS, Error as DBOSErrors } from "@dbos-inc/dbos-sdk";
|
|
21
21
|
import { randomUUID } from "node:crypto";
|
|
22
|
-
import { err, ok } from "neverthrow";
|
|
22
|
+
import { ResultAsync, err, ok } from "neverthrow";
|
|
23
23
|
import { forStep, forSubAgent } from "../auth/types.js";
|
|
24
24
|
import { createDataProfilerAgent } from "../agents/sandbox/data-profiler.js";
|
|
25
25
|
import { runToTerminal } from "../loop/run-to-terminal.js";
|
|
@@ -345,14 +345,14 @@ export async function runDataProfileBody(input, deps) {
|
|
|
345
345
|
return;
|
|
346
346
|
}
|
|
347
347
|
// 2. Register staged files in cortex_artifacts
|
|
348
|
-
await upsertArtifacts(deps.pool, stagedInputs.map((f) => ({
|
|
348
|
+
unwrapOrThrow(await upsertArtifacts(deps.pool, stagedInputs.map((f) => ({
|
|
349
349
|
resourceId: analysisId,
|
|
350
350
|
path: inputArtifactPath(f),
|
|
351
351
|
hash: f.hash,
|
|
352
352
|
size: f.size,
|
|
353
353
|
role: "input",
|
|
354
354
|
fileId: f.fileId,
|
|
355
|
-
})));
|
|
355
|
+
}))));
|
|
356
356
|
const executionId = generateExecutionId(DATA_PROFILE_AGENT_ID);
|
|
357
357
|
const workflowId = DBOS.workflowID ?? `${DATA_PROFILE_RUN_LITERAL}:${executionId}`;
|
|
358
358
|
const childSession = forSubAgent(runSession, DATA_PROFILE_AGENT_ID);
|
|
@@ -636,6 +636,15 @@ function settleTerminalWrite(logger, analysisId, write, status) {
|
|
|
636
636
|
}
|
|
637
637
|
recordRunCompleted({ workflow: "data_profile", status, durationMs: elapsedSinceIso(write.startedAt) });
|
|
638
638
|
}
|
|
639
|
+
/** A one-line, user-facing description of a `DataProfileStartError`. */
|
|
640
|
+
export function describeDataProfileStartError(e) {
|
|
641
|
+
switch (e.type) {
|
|
642
|
+
case "refused":
|
|
643
|
+
return `the profile run was not authorized: ${e.reason}`;
|
|
644
|
+
case "start_failed":
|
|
645
|
+
return `the profile workflow did not start: ${profileFailureReason(e.cause)}`;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
639
648
|
/**
|
|
640
649
|
* Resolve the `RunSession` the workflow runs under. Whether the authorization
|
|
641
650
|
* is freshly minted (Cortex-owned) or reused (caller-owned) is decided by the
|
|
@@ -667,18 +676,14 @@ export function dataProfileWorkflowId(analysisId, nonce) {
|
|
|
667
676
|
return `dataprofile:${analysisId}:${nonce}`;
|
|
668
677
|
}
|
|
669
678
|
/**
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
* not awaited.
|
|
679
|
+
* Start the data-profile workflow under the authorization and a per-attempt id
|
|
680
|
+
* `dataprofile:{analysisId}:{nonce}` — concurrent triggers are already
|
|
681
|
+
* serialized by the ledger CAS. The caller has already staged the inputs and
|
|
682
|
+
* supplied the manifest in `params.stagedInputs`; this forwards it into the
|
|
683
|
+
* workflow input. The handle result is not awaited.
|
|
676
684
|
*/
|
|
677
|
-
async function startDataProfileWorkflow(deps, params) {
|
|
678
|
-
const
|
|
679
|
-
if (authorized.isErr())
|
|
680
|
-
return err(authorized.error);
|
|
681
|
-
const { runSession, ownsMandate } = authorized.value; // oss-core-managed-ok
|
|
685
|
+
async function startDataProfileWorkflow(deps, params, authorization) {
|
|
686
|
+
const { runSession, ownsMandate } = authorization; // oss-core-managed-ok
|
|
682
687
|
const attemptNonce = randomUUID();
|
|
683
688
|
await DBOS.startWorkflow(deps.workflow, {
|
|
684
689
|
workflowID: dataProfileWorkflowId(params.analysisId, attemptNonce),
|
|
@@ -688,20 +693,29 @@ async function startDataProfileWorkflow(deps, params) {
|
|
|
688
693
|
ownsMandate, // oss-core-managed-ok
|
|
689
694
|
stagedInputs: params.stagedInputs,
|
|
690
695
|
});
|
|
691
|
-
return ok(undefined);
|
|
692
696
|
}
|
|
693
697
|
/**
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
698
|
+
* Authorize and start a claimed profile. The claim already flipped the row to
|
|
699
|
+
* `running`, thus a refused authorization and a failed start each settle the
|
|
700
|
+
* row as `failed` before the `err`, and the row never wedges at `running`. A
|
|
701
|
+
* rejected `authorize` promise is a defect of the host: the row is settled the
|
|
702
|
+
* same way, and the rejection passes through.
|
|
697
703
|
*/
|
|
698
704
|
async function dispatchClaimedProfile(deps, params, phase) {
|
|
699
|
-
const
|
|
700
|
-
await compensateStartFailure(deps, params.analysisId, phase,
|
|
701
|
-
|
|
705
|
+
const authorized = await Promise.resolve(authorizeDataProfile(deps, params)).catch(async (defect) => {
|
|
706
|
+
await compensateStartFailure(deps, params.analysisId, phase, defect);
|
|
707
|
+
throw defect;
|
|
702
708
|
});
|
|
703
|
-
if (
|
|
704
|
-
await refuseClaimedProfile(deps, params.analysisId, phase,
|
|
709
|
+
if (authorized.isErr()) {
|
|
710
|
+
await refuseClaimedProfile(deps, params.analysisId, phase, authorized.error);
|
|
711
|
+
return err({ type: "refused", reason: authorized.error.reason, suspend: authorized.error.kind === "suspended" });
|
|
712
|
+
}
|
|
713
|
+
const started = await ResultAsync.fromPromise(startDataProfileWorkflow(deps, params, authorized.value), (cause) => cause);
|
|
714
|
+
if (started.isErr()) {
|
|
715
|
+
await compensateStartFailure(deps, params.analysisId, phase, started.error);
|
|
716
|
+
return err({ type: "start_failed", cause: started.error });
|
|
717
|
+
}
|
|
718
|
+
return ok(undefined);
|
|
705
719
|
}
|
|
706
720
|
/**
|
|
707
721
|
* Attempt to claim and run data profiling for an analysis, then start the workflow
|
|
@@ -720,70 +734,85 @@ async function dispatchClaimedProfile(deps, params, phase) {
|
|
|
720
734
|
* `"completed"`. An empty manifest against a seed that names files is a divergence
|
|
721
735
|
* between the caller and the ledger, and the trigger refuses it.
|
|
722
736
|
*
|
|
723
|
-
* Returns what happened, so the caller can surface it (e.g. in the seed response).
|
|
737
|
+
* Returns what happened, so the caller can surface it (e.g. in the seed response). A
|
|
738
|
+
* failed ledger read or claim is the `err`. The dispatch settles its own row, thus a
|
|
739
|
+
* refusal or a failed start after a claim shows on the ledger only.
|
|
724
740
|
*/
|
|
725
|
-
export
|
|
741
|
+
export function triggerDataProfile(deps, params) {
|
|
742
|
+
return new ResultAsync(claimAndDispatchProfile(deps, params));
|
|
743
|
+
}
|
|
744
|
+
async function claimAndDispatchProfile(deps, params) {
|
|
726
745
|
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId: params.analysisId });
|
|
727
746
|
const { analysisId } = params;
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// `completed` at once, with no workflow. The CAS carries the same seed
|
|
748
|
-
// predicate as this pre-read, so a seed upsert that lands in between cannot be
|
|
749
|
-
// hidden behind a finished profile: the stamp refuses, and the status read
|
|
750
|
-
// below names the cause.
|
|
751
|
-
if (unwrapOrThrow(await completeEmptyDataProfile(deps.pool, analysisId)))
|
|
752
|
-
return "completed";
|
|
753
|
-
const current = unwrapOrThrow(await loadDataProfileStatus(deps.pool, analysisId));
|
|
754
|
-
if (current?.status === "running")
|
|
755
|
-
return "already_running";
|
|
756
|
-
logger.error("trigger rejected: empty-set completion refused (no analysis row, or the seed now names files)");
|
|
757
|
-
return "failed";
|
|
758
|
-
}
|
|
759
|
-
// A non-empty manifest against a seed that names no file: the caller skipped the
|
|
760
|
-
// seed, or the analysis row is missing. Name the cause before any claim.
|
|
761
|
-
if (seeded === null || seeded.length === 0) {
|
|
762
|
-
logger.error("trigger rejected: no seeded input set (missing analysis row or caller skipped seeding)");
|
|
763
|
-
return "failed";
|
|
764
|
-
}
|
|
765
|
-
const started = unwrapOrThrow(await tryStartDataProfile(deps.pool, analysisId));
|
|
766
|
-
if (started) {
|
|
767
|
-
void dispatchClaimedProfile(deps, params, "Run");
|
|
768
|
-
return "started";
|
|
747
|
+
// ADVISORY pre-check, not the enforcement — the claim CAS carries the same seed
|
|
748
|
+
// conjunct (see `SEEDED` in state/data-profile.ts) and is what actually makes a
|
|
749
|
+
// seedless `running` row impossible. This read exists only to name the cause: on
|
|
750
|
+
// the ordinary non-racing path it turns an opaque "no claim matched" into a
|
|
751
|
+
// precise reason. An empty array is not a seed — it names zero files. Reads via
|
|
752
|
+
// `loadSeedInputFileIds`, which returns null for BOTH a missing analysis row and
|
|
753
|
+
// a NULL-seed row (`loadDataProfileStatus` would hide the seed of a cleared row).
|
|
754
|
+
const seedRead = await loadSeedInputFileIds(deps.pool, analysisId);
|
|
755
|
+
if (seedRead.isErr())
|
|
756
|
+
return err(seedRead.error);
|
|
757
|
+
const seeded = seedRead.value;
|
|
758
|
+
if (params.stagedInputs.length === 0) {
|
|
759
|
+
// The trigger validates the manifest it is about to dispatch against the ledger
|
|
760
|
+
// seed. A seeded row profiled against an EMPTY manifest would claim `running` and
|
|
761
|
+
// then hit the body's empty-manifest path, and complete with a NULL result while
|
|
762
|
+
// the seed still names files. Refuse the divergence before any claim.
|
|
763
|
+
if (seeded !== null && seeded.length > 0) {
|
|
764
|
+
logger.error("trigger rejected: empty manifest dispatched against a non-empty seed", { seededCount: seeded.length });
|
|
765
|
+
return ok("failed");
|
|
769
766
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const
|
|
776
|
-
if (
|
|
777
|
-
return
|
|
778
|
-
|
|
767
|
+
// The analysis has no input files, so there is nothing to profile. The row is
|
|
768
|
+
// `completed` at once, with no workflow. The CAS carries the same seed
|
|
769
|
+
// predicate as this pre-read, so a seed upsert that lands in between cannot be
|
|
770
|
+
// hidden behind a finished profile: the stamp refuses, and the status read
|
|
771
|
+
// below names the cause.
|
|
772
|
+
const completed = await completeEmptyDataProfile(deps.pool, analysisId);
|
|
773
|
+
if (completed.isErr())
|
|
774
|
+
return err(completed.error);
|
|
775
|
+
if (completed.value)
|
|
776
|
+
return ok("completed");
|
|
777
|
+
const current = await loadDataProfileStatus(deps.pool, analysisId);
|
|
778
|
+
if (current.isErr())
|
|
779
|
+
return err(current.error);
|
|
780
|
+
if (current.value?.status === "running")
|
|
781
|
+
return ok("already_running");
|
|
782
|
+
logger.error("trigger rejected: empty-set completion refused (no analysis row, or the seed now names files)");
|
|
783
|
+
return ok("failed");
|
|
779
784
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
785
|
+
// A non-empty manifest against a seed that names no file: the caller skipped the
|
|
786
|
+
// seed, or the analysis row is missing. Name the cause before any claim.
|
|
787
|
+
if (seeded === null || seeded.length === 0) {
|
|
788
|
+
logger.error("trigger rejected: no seeded input set (missing analysis row or caller skipped seeding)");
|
|
789
|
+
return ok("failed");
|
|
790
|
+
}
|
|
791
|
+
const started = await tryStartDataProfile(deps.pool, analysisId);
|
|
792
|
+
if (started.isErr())
|
|
793
|
+
return err(started.error);
|
|
794
|
+
if (started.value) {
|
|
795
|
+
dispatchInBackground(deps, params, "Run");
|
|
796
|
+
return ok("started");
|
|
797
|
+
}
|
|
798
|
+
const restarted = await tryRerunDataProfile(deps.pool, analysisId);
|
|
799
|
+
if (restarted.isErr())
|
|
800
|
+
return err(restarted.error);
|
|
801
|
+
if (restarted.value) {
|
|
802
|
+
dispatchInBackground(deps, params, "Re-run");
|
|
803
|
+
return ok("restarted");
|
|
783
804
|
}
|
|
805
|
+
const status = await loadDataProfileStatus(deps.pool, analysisId);
|
|
806
|
+
if (status.isErr())
|
|
807
|
+
return err(status.error);
|
|
808
|
+
return ok(status.value?.status === "running" ? "already_running" : "failed");
|
|
809
|
+
}
|
|
810
|
+
/** The dispatch settles the row on each arm and logs a defect, thus the trigger keeps no outcome of it. */
|
|
811
|
+
function dispatchInBackground(deps, params, phase) {
|
|
812
|
+
void dispatchClaimedProfile(deps, params, phase).catch(() => undefined);
|
|
784
813
|
}
|
|
785
814
|
/**
|
|
786
|
-
* Compensate a
|
|
815
|
+
* Compensate a start that failed after the claim. The ledger CAS in
|
|
787
816
|
* `tryStart`/`tryRerun`/`tryRetry` already flipped the row to `running`; if the
|
|
788
817
|
* dispatch never landed a workflow, DBOS has nothing to recover, so without
|
|
789
818
|
* this the row would sit at `running` forever and every later trigger would
|
|
@@ -827,23 +856,13 @@ async function failClaimedProfile(deps, analysisId, phase, reason) {
|
|
|
827
856
|
/**
|
|
828
857
|
* Start the data-profile workflow for an already-claimed analysis (the retry
|
|
829
858
|
* route claims via `tryRetryDataProfile`, then calls this). The caller does not
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
* promise resolves: the row is where a caller reads the outcome.
|
|
859
|
+
* wait for the completion of the workflow. A refused authorization and a failed
|
|
860
|
+
* start each settle the row as `failed` before the `err` (see
|
|
861
|
+
* {@link dispatchClaimedProfile}), thus a caller that minted an authorization
|
|
862
|
+
* can revoke it.
|
|
835
863
|
*/
|
|
836
|
-
export
|
|
837
|
-
|
|
838
|
-
try {
|
|
839
|
-
started = await startDataProfileWorkflow(deps, params);
|
|
840
|
-
}
|
|
841
|
-
catch (cause) {
|
|
842
|
-
await compensateStartFailure(deps, params.analysisId, "Retry", cause);
|
|
843
|
-
throw cause;
|
|
844
|
-
}
|
|
845
|
-
if (started.isErr())
|
|
846
|
-
await refuseClaimedProfile(deps, params.analysisId, "Retry", started.error);
|
|
864
|
+
export function runDataProfile(deps, params) {
|
|
865
|
+
return new ResultAsync(dispatchClaimedProfile(deps, params, "Retry"));
|
|
847
866
|
}
|
|
848
867
|
/**
|
|
849
868
|
* Concise, UI-safe failure reason. `data_profile_error` is surfaced verbatim
|
|
@@ -176,8 +176,9 @@ export async function routeAdHocRequest(deps, input) {
|
|
|
176
176
|
content: `Request:\n${input.request}\n\nPersisted data-profile orientation:\n${orientation}\n\nEligible specialists:\n${catalog}`,
|
|
177
177
|
},
|
|
178
178
|
],
|
|
179
|
+
// No forced tool choice: some Claude models reject it with a
|
|
180
|
+
// 400. A reply without the call falls back below as `malformed`.
|
|
179
181
|
tools: routeTool(),
|
|
180
|
-
toolChoice: { type: "tool", toolName: "submit_route" },
|
|
181
182
|
}, forSubAgent(input.session, AD_HOC_ROUTER_AGENT_ID), signal));
|
|
182
183
|
const call = Array.isArray(response.message.content)
|
|
183
184
|
? response.message.content.find((part) => part.type === "tool-call" && part.toolName === "submit_route")
|
|
@@ -228,10 +228,8 @@ export function createGenerateAnalogyReportTool(deps) {
|
|
|
228
228
|
if (directParse.ok)
|
|
229
229
|
return ok(directParse.value);
|
|
230
230
|
// Slow path: a single-shot conversion call (no tools) transforms the
|
|
231
|
-
// raw output into a valid envelope. The
|
|
232
|
-
//
|
|
233
|
-
// Sonnet-vs-Opus split is unnecessary — the parse+validate+envelope
|
|
234
|
-
// cascade below is the real safety net.
|
|
231
|
+
// raw output into a valid envelope. The parse+validate+envelope
|
|
232
|
+
// cascade below is the safety net.
|
|
235
233
|
try {
|
|
236
234
|
const reply = unwrapOrThrow(await deps.provider.chat({
|
|
237
235
|
tools: {},
|
|
@@ -65,9 +65,9 @@ const PLANNER_AGENT_ID = "planner";
|
|
|
65
65
|
* path and submits the plan that it had, not the plan that it was building.
|
|
66
66
|
* Thus the wall-clock guard, not this number, is what bounds the worst case.
|
|
67
67
|
*/
|
|
68
|
-
const PLANNER_MAX_ITERATIONS =
|
|
68
|
+
const PLANNER_MAX_ITERATIONS = 300;
|
|
69
69
|
/** Wall-clock guard for a single plan-generation invocation. */
|
|
70
|
-
const PLAN_TIMEOUT_MS =
|
|
70
|
+
const PLAN_TIMEOUT_MS = 1_800_000;
|
|
71
71
|
// ── Diagnostic bounds ───────────────────────────────────────────────
|
|
72
72
|
//
|
|
73
73
|
// This tool runs on `passthroughStep`: it writes no ledger row and owns no
|
|
@@ -674,8 +674,8 @@ function buildPlannerSearchTools(deps) {
|
|
|
674
674
|
queryDocsTool,
|
|
675
675
|
// The environment itself. The seed already carries a rendered census of
|
|
676
676
|
// both stores. These two tools are for the narrow second look: one
|
|
677
|
-
// collection of the reference store, or one
|
|
678
|
-
//
|
|
677
|
+
// collection of the reference store, or one batch of the package names
|
|
678
|
+
// that the census leaves unclear.
|
|
679
679
|
createListAvailableRefsTool(deps.refStorePath === undefined ? {} : { refStorePath: deps.refStorePath }),
|
|
680
680
|
// The planner is a conversation surface, thus the pool-scope reader binds
|
|
681
681
|
// when the embedder gives one, and the answer names what the store HOLDS.
|
|
@@ -894,10 +894,9 @@ export function createGeneratePlanTool(deps) {
|
|
|
894
894
|
emit: ctx.emit,
|
|
895
895
|
runStep: passthroughStep,
|
|
896
896
|
resolved: () => holder.outcome !== null,
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
//
|
|
900
|
-
toolChoice: "required",
|
|
897
|
+
// No forced tool choice: some Claude models reject it with a
|
|
898
|
+
// 400. The prompt names the terminal tools, and the salvage
|
|
899
|
+
// turn below recovers a run that ends on prose.
|
|
901
900
|
logger,
|
|
902
901
|
usageRecorder: deps.usageRecorder,
|
|
903
902
|
// Fold the planner's calls into the turn total the root loop reports.
|
|
@@ -515,8 +515,7 @@ export function createListAvailableRefsTool(deps) {
|
|
|
515
515
|
'`category` is shorthand for a top-level group (e.g. "pathways", "regulatory-networks"), and the groups present come back in `categories`; ' +
|
|
516
516
|
"`limit` caps the entries returned. The response carries `returned`, `total`, and `hasMore`, so truncation is never silent. " +
|
|
517
517
|
"An empty result means the dataset is absent right now — not that it is unobtainable. Say so; never guess a path, and never silently substitute a different dataset. " +
|
|
518
|
-
"The store is provisioned by the host and is NOT frozen: datasets can be added through the host's own provisioning path, and whatever is added shows up on a later call.
|
|
519
|
-
"So before you report a gap as permanent, check whether any tool you hold reaches that provisioning path — offer that route if one exists, and call the gap permanent only when none does.",
|
|
518
|
+
"The store is provisioned by the host and is NOT frozen: datasets can be added through the host's own provisioning path, and whatever is added shows up on a later call.",
|
|
520
519
|
inputSchema: ListAvailableRefsInputSchema,
|
|
521
520
|
// `execute` resolves `path ?? category` for the subtree it inspects, and
|
|
522
521
|
// it ignores `category` when `path` is present. `query` is an ADDITIVE
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
* via `inspectRun`)
|
|
70
70
|
*/
|
|
71
71
|
import { DBOS, Error as DBOSErrors } from "@dbos-inc/dbos-sdk";
|
|
72
|
-
import {
|
|
72
|
+
import { err, ok } from "neverthrow";
|
|
73
73
|
import { mkdir } from "node:fs/promises";
|
|
74
74
|
import { join } from "node:path";
|
|
75
75
|
import { forStep } from "../auth/types.js";
|
|
@@ -286,7 +286,7 @@ async function collectUpstreamHandoffs(args) {
|
|
|
286
286
|
const handoffs = [];
|
|
287
287
|
for (const summary of summaries) {
|
|
288
288
|
const outputDir = stepSubdir(runStepDir(runId, summary.stepId), "output");
|
|
289
|
-
const artifacts = await queryStepArtifactPaths(pool, analysisId, runId, summary.stepId, MAX_UPSTREAM_ARTIFACTS);
|
|
289
|
+
const artifacts = unwrapOrThrow(await queryStepArtifactPaths(pool, analysisId, runId, summary.stepId, MAX_UPSTREAM_ARTIFACTS));
|
|
290
290
|
handoffs.push({
|
|
291
291
|
stepId: summary.stepId,
|
|
292
292
|
agentId: summary.agentId,
|
|
@@ -1124,7 +1124,9 @@ async function collectAndComplete(args) {
|
|
|
1124
1124
|
// beat the CLI's poll-and-shutdown). These branches only fan out the UI stream part, whose
|
|
1125
1125
|
// completed/failed shapes genuinely differ — the provenance record does not.
|
|
1126
1126
|
if (status === "completed" || status === "partial") {
|
|
1127
|
-
const artifactCount = await DBOS.runStep(async () => await
|
|
1127
|
+
const artifactCount = await DBOS.runStep(async () => await countArtifactsForRun(deps.pool, input.analysisId, runId).unwrapOr(0), {
|
|
1128
|
+
name: "count-run-artifacts",
|
|
1129
|
+
});
|
|
1128
1130
|
await emitStreamPart({
|
|
1129
1131
|
type: "data-run-completed",
|
|
1130
1132
|
runId,
|
package/package.json
CHANGED