@inflexa-ai/harness 0.37.0 → 0.39.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.
Files changed (101) hide show
  1. package/README.md +1 -2
  2. package/dist/auth/local-run-authorizer.js +5 -4
  3. package/dist/auth/types.d.ts +28 -9
  4. package/dist/auth/types.js +5 -3
  5. package/dist/billing/cached-resolver.d.ts +11 -7
  6. package/dist/billing/cached-resolver.js +17 -11
  7. package/dist/billing/noop-run-charge.js +3 -2
  8. package/dist/billing/noop-usage-recorder.js +2 -1
  9. package/dist/billing/run-charge.d.ts +18 -5
  10. package/dist/billing/run-charge.js +5 -1
  11. package/dist/billing/usage-recorder.d.ts +11 -9
  12. package/dist/billing/usage-recorder.js +2 -3
  13. package/dist/contracts/chat-events.d.ts +4 -1
  14. package/dist/contracts/chat-parts.d.ts +1 -1
  15. package/dist/contracts/schemas/chat-parts.d.ts +4 -4
  16. package/dist/execution/artifact-registration.d.ts +6 -1
  17. package/dist/execution/artifact-registration.js +12 -4
  18. package/dist/execution/artifact-registry.d.ts +10 -3
  19. package/dist/execution/noop-artifact-registry.js +3 -4
  20. package/dist/execution/post-step-pipeline.d.ts +14 -3
  21. package/dist/execution/post-step-pipeline.js +21 -8
  22. package/dist/execution/run-authorizer.d.ts +5 -3
  23. package/dist/execution/run-canceler.js +4 -20
  24. package/dist/index.d.ts +14 -8
  25. package/dist/index.js +12 -6
  26. package/dist/lib/hooks.d.ts +49 -0
  27. package/dist/lib/hooks.js +39 -0
  28. package/dist/lib/result.d.ts +22 -11
  29. package/dist/lib/result.js +24 -14
  30. package/dist/loop/run-agent.d.ts +2 -2
  31. package/dist/loop/run-agent.js +5 -4
  32. package/dist/provenance/seam.d.ts +1 -1
  33. package/dist/providers/ai-sdk.d.ts +43 -5
  34. package/dist/providers/ai-sdk.js +55 -52
  35. package/dist/providers/anthropic.d.ts +2 -3
  36. package/dist/providers/anthropic.js +2 -2
  37. package/dist/providers/embedding.d.ts +13 -7
  38. package/dist/providers/embedding.js +26 -6
  39. package/dist/providers/errors.d.ts +67 -37
  40. package/dist/providers/errors.js +57 -58
  41. package/dist/providers/request-headers.d.ts +18 -0
  42. package/dist/providers/request-headers.js +22 -0
  43. package/dist/report-model/production-resolver.d.ts +5 -2
  44. package/dist/report-model/production-resolver.js +12 -2
  45. package/dist/runtime/assemble.d.ts +3 -2
  46. package/dist/runtime/dbos.d.ts +4 -3
  47. package/dist/runtime/dbos.js +18 -3
  48. package/dist/runtime/result-serialization.d.ts +29 -0
  49. package/dist/runtime/result-serialization.js +45 -0
  50. package/dist/sandbox/client.d.ts +11 -3
  51. package/dist/sandbox/create-sandbox.d.ts +5 -2
  52. package/dist/sandbox/create-sandbox.js +51 -25
  53. package/dist/sandbox/docker-client.d.ts +4 -3
  54. package/dist/sandbox/docker-client.js +23 -31
  55. package/dist/sandbox/k8s-client.d.ts +4 -13
  56. package/dist/sandbox/k8s-client.js +28 -61
  57. package/dist/sandbox/labels.d.ts +34 -0
  58. package/dist/sandbox/labels.js +47 -0
  59. package/dist/sandbox/mount-plan.d.ts +3 -1
  60. package/dist/sandbox/mount-plan.js +9 -0
  61. package/dist/sandbox/sandbox-error.d.ts +36 -13
  62. package/dist/sandbox/sandbox-error.js +32 -17
  63. package/dist/sandbox/types.d.ts +18 -10
  64. package/dist/schemas/validate-plan.js +13 -0
  65. package/dist/schemas/workflow-state.d.ts +4 -4
  66. package/dist/state/analyses.d.ts +4 -4
  67. package/dist/state/analyses.js +4 -4
  68. package/dist/state/schema.d.ts +5 -5
  69. package/dist/tasks/data-profile.d.ts +5 -8
  70. package/dist/tasks/data-profile.js +88 -43
  71. package/dist/tasks/derive-table-exec.d.ts +10 -6
  72. package/dist/tasks/derive-table-exec.js +17 -12
  73. package/dist/tasks/extract-values.d.ts +16 -10
  74. package/dist/tasks/extract-values.js +34 -18
  75. package/dist/tools/execute-analysis.js +29 -24
  76. package/dist/tools/report-session/derive-table.d.ts +9 -1
  77. package/dist/tools/report-session/derive-table.js +25 -22
  78. package/dist/tools/report-session/examine-page.d.ts +6 -3
  79. package/dist/tools/report-session/examine-page.js +16 -8
  80. package/dist/tools/report-session/index.d.ts +1 -1
  81. package/dist/tools/report-session/preview-report.js +11 -9
  82. package/dist/tools/report-session/session-page-publisher.d.ts +17 -21
  83. package/dist/tools/report-session/session-page-publisher.js +5 -7
  84. package/dist/tools/research/inspect-run.d.ts +1 -1
  85. package/dist/tools/sandbox/link-packages.d.ts +8 -0
  86. package/dist/tools/sandbox/link-packages.js +19 -9
  87. package/dist/workflows/execute-analysis.d.ts +25 -16
  88. package/dist/workflows/execute-analysis.js +223 -216
  89. package/dist/workflows/metrics.d.ts +4 -3
  90. package/dist/workflows/metrics.js +4 -3
  91. package/dist/workflows/sandbox-step.d.ts +20 -29
  92. package/dist/workflows/sandbox-step.js +71 -127
  93. package/dist/workflows/suspension.d.ts +32 -0
  94. package/dist/workflows/suspension.js +38 -0
  95. package/package.json +1 -1
  96. package/dist/billing/noop-resolver.d.ts +0 -6
  97. package/dist/billing/noop-resolver.js +0 -7
  98. package/dist/billing/resolver.d.ts +0 -64
  99. package/dist/billing/resolver.js +0 -24
  100. package/dist/loop/budget-exceeded.d.ts +0 -38
  101. package/dist/loop/budget-exceeded.js +0 -88
@@ -350,7 +350,7 @@ async function sweepAssets(assetsDir, staged, logger) {
350
350
  *
351
351
  * An absent factory gives no grant, and the arm carries no access field — the page path stays the whole
352
352
  * local contract. The tool builds the publisher over the scope of the call, thus the mint runs under the
353
- * auth of the caller. A refusal, a thrown construction, and a thrown realization each become the
353
+ * auth of the caller. An `err`, a thrown construction, and a rejected realization each become the
354
354
  * not-granted arm, thus a broken grant surface never costs the render. The URL spells through
355
355
  * `buildReportSessionUrl`, thus the formula lives in the contract and the seam gives the content-server
356
356
  * base alone.
@@ -359,22 +359,24 @@ async function mintAccess(makeSessionPages, analysisId, threadId, auth, logger)
359
359
  if (makeSessionPages === undefined) {
360
360
  return undefined;
361
361
  }
362
- let minted;
362
+ let grant;
363
363
  try {
364
- minted = await makeSessionPages({ analysisId, auth }).mintSessionPageAccess(threadId);
364
+ const minted = await makeSessionPages({ analysisId, auth }).mintSessionPageAccess(threadId);
365
+ if (minted.isErr()) {
366
+ const detail = describeSessionPageMintFailure(minted.error);
367
+ logger.warn("the session-page mint refused", { threadId, analysisId, detail });
368
+ return { granted: false, detail };
369
+ }
370
+ grant = minted.value;
365
371
  }
366
372
  catch (cause) {
367
373
  logger.warn("the session-page mint threw", { threadId, analysisId, ...defaultErrorFields(cause) });
368
374
  return { granted: false, detail: "session-page-access mint failed" };
369
375
  }
370
- if (!minted.ok) {
371
- logger.warn("the session-page mint refused", { threadId, analysisId, detail: describeSessionPageMintFailure(minted) });
372
- return { granted: false, detail: describeSessionPageMintFailure(minted) };
373
- }
374
376
  return {
375
377
  granted: true,
376
- url: buildReportSessionUrl(minted.data.baseUrl, analysisId, threadId, "index.html", minted.data.token),
377
- expiresAt: minted.data.expiresAt,
378
+ url: buildReportSessionUrl(grant.baseUrl, analysisId, threadId, "index.html", grant.token),
379
+ expiresAt: grant.expiresAt,
378
380
  };
379
381
  }
380
382
  /**
@@ -11,28 +11,24 @@
11
11
  * tool call, thus the composition binds a factory and the preview tool builds the publisher over the
12
12
  * scope of the call — exactly as it builds the reference resolver. A boot-time singleton could not
13
13
  * carry that credential, and a host that bans ambient state could not bind one at all. The local
14
- * default (`UnavailableSessionPagePublisher`) returns not-ok, thus the preview tool attaches no URL
14
+ * default (`UnavailableSessionPagePublisher`) gives an `err`, thus the preview tool attaches no URL
15
15
  * and the render still lands — the page path stays the whole local contract.
16
16
  */
17
+ import { type ResultAsync } from "neverthrow";
17
18
  import type { AuthContext } from "../../auth/types.js";
18
- export type SessionPageMintResult = {
19
- ok: true;
20
- data: {
21
- baseUrl: string;
22
- token: string;
23
- expiresAt: string;
24
- };
25
- } | {
26
- ok: false;
27
- status?: number;
28
- error: {
29
- message?: string;
19
+ /** The grant of one session page: the content-server base, the token, and its expiry. */
20
+ export interface SessionPageGrant {
21
+ readonly baseUrl: string;
22
+ readonly token: string;
23
+ readonly expiresAt: string;
24
+ }
25
+ /** What a failed mint is allowed to carry. */
26
+ export interface SessionPageMintFailure {
27
+ readonly status?: number;
28
+ readonly error: {
29
+ readonly message?: string;
30
30
  };
31
- };
32
- /** The not-ok arm of the seam's result — what a failed mint is allowed to carry. */
33
- export type SessionPageMintFailure = Extract<SessionPageMintResult, {
34
- ok: false;
35
- }>;
31
+ }
36
32
  /**
37
33
  * Renders a failed mint as the line an agent reads back from the preview tool.
38
34
  *
@@ -49,7 +45,7 @@ export interface SessionPagePublisher {
49
45
  * caller spells the whole URL through `buildReportSessionUrl`, thus the formula lives in the
50
46
  * contract and never in a realization.
51
47
  */
52
- mintSessionPageAccess(threadId: string): Promise<SessionPageMintResult>;
48
+ mintSessionPageAccess(threadId: string): ResultAsync<SessionPageGrant, SessionPageMintFailure>;
53
49
  }
54
50
  /**
55
51
  * The per-call construction of the publisher. The factory binds one analysis and the auth of the tool
@@ -60,9 +56,9 @@ export type MakeSessionPagePublisher = (scope: {
60
56
  auth: AuthContext;
61
57
  }) => SessionPagePublisher;
62
58
  /**
63
- * Local default — no hosted view of a session page. Returns not-ok, thus the preview tool carries the
59
+ * Local default — no hosted view of a session page. Gives an `err`, thus the preview tool carries the
64
60
  * refusal as data and the page path stays the whole result.
65
61
  */
66
62
  export declare class UnavailableSessionPagePublisher implements SessionPagePublisher {
67
- mintSessionPageAccess(_threadId: string): Promise<SessionPageMintResult>;
63
+ mintSessionPageAccess(_threadId: string): ResultAsync<SessionPageGrant, SessionPageMintFailure>;
68
64
  }
@@ -11,9 +11,10 @@
11
11
  * tool call, thus the composition binds a factory and the preview tool builds the publisher over the
12
12
  * scope of the call — exactly as it builds the reference resolver. A boot-time singleton could not
13
13
  * carry that credential, and a host that bans ambient state could not bind one at all. The local
14
- * default (`UnavailableSessionPagePublisher`) returns not-ok, thus the preview tool attaches no URL
14
+ * default (`UnavailableSessionPagePublisher`) gives an `err`, thus the preview tool attaches no URL
15
15
  * and the render still lands — the page path stays the whole local contract.
16
16
  */
17
+ import { errAsync } from "neverthrow";
17
18
  /**
18
19
  * Renders a failed mint as the line an agent reads back from the preview tool.
19
20
  *
@@ -32,14 +33,11 @@ export function describeSessionPageMintFailure(failure) {
32
33
  return detail.length > 0 ? `session-page-access mint failed: ${detail.join(" ")}` : "session-page-access mint failed";
33
34
  }
34
35
  /**
35
- * Local default — no hosted view of a session page. Returns not-ok, thus the preview tool carries the
36
+ * Local default — no hosted view of a session page. Gives an `err`, thus the preview tool carries the
36
37
  * refusal as data and the page path stays the whole result.
37
38
  */
38
39
  export class UnavailableSessionPagePublisher {
39
- async mintSessionPageAccess(_threadId) {
40
- return {
41
- ok: false,
42
- error: { message: "the hosted view of a session page is unavailable in this environment" },
43
- };
40
+ mintSessionPageAccess(_threadId) {
41
+ return errAsync({ error: { message: "the hosted view of a session page is unavailable in this environment" } });
44
42
  }
45
43
  }
@@ -53,7 +53,7 @@ declare function formatRun(r: CortexRunRow, verbose: boolean, exposePaths?: bool
53
53
  synthesisReason?: string | undefined;
54
54
  runId: string;
55
55
  workflowName: string;
56
- status: "running" | "completed" | "failed" | "partial" | "canceled" | "suspended_insufficient_funds";
56
+ status: "failed" | "running" | "completed" | "partial" | "canceled" | "suspended_insufficient_funds";
57
57
  startedAt: string;
58
58
  completedAt: string | null;
59
59
  planId: string | null;
@@ -13,6 +13,8 @@
13
13
  * capability, thus the agent learns one grammar for the plan, the tool, and
14
14
  * the prompt layer.
15
15
  */
16
+ import { type Result } from "neverthrow";
17
+ import { type PackageQuery } from "../../sandbox/package-identity.js";
16
18
  import type { ExtendAnalysisFarm, PackageRequestOutcome } from "../../sandbox/types.js";
17
19
  export interface LinkPackagesDeps {
18
20
  readonly extendAnalysisFarm: ExtendAnalysisFarm;
@@ -22,6 +24,12 @@ export interface LinkPackagesDeps {
22
24
  export interface LinkPackagesResult {
23
25
  readonly outcomes: readonly PackageRequestOutcome[];
24
26
  }
27
+ /**
28
+ * Call the seam, and give the outcomes or the one reason that the call gave
29
+ * no answer. An `err` and a rejected realization each give that reason, thus
30
+ * no call site sees a throw.
31
+ */
32
+ export declare function callExtendAnalysisFarm(extendAnalysisFarm: ExtendAnalysisFarm, analysisId: string, queries: readonly PackageQuery[]): Promise<Result<readonly PackageRequestOutcome[], string>>;
25
33
  export declare function createLinkPackagesTool(deps: LinkPackagesDeps): import("../define-tool.js").Tool<{
26
34
  packages: string[];
27
35
  }, LinkPackagesResult>;
@@ -34,6 +34,19 @@ function describeParseError(entry, error) {
34
34
  return `"${entry}" pins with the specifier "${error.specifier}", which this tool cannot honor — pass a bare name, or name==version with one exact version`;
35
35
  }
36
36
  }
37
+ /**
38
+ * Call the seam, and give the outcomes or the one reason that the call gave
39
+ * no answer. An `err` and a rejected realization each give that reason, thus
40
+ * no call site sees a throw.
41
+ */
42
+ export async function callExtendAnalysisFarm(extendAnalysisFarm, analysisId, queries) {
43
+ try {
44
+ return (await extendAnalysisFarm(analysisId, queries)).mapErr((failure) => failure.reason);
45
+ }
46
+ catch (cause) {
47
+ return err(cause instanceof Error ? cause.message : String(cause));
48
+ }
49
+ }
37
50
  export function createLinkPackagesTool(deps) {
38
51
  return defineTool({
39
52
  id: "link_packages",
@@ -67,17 +80,14 @@ export function createLinkPackagesTool(deps) {
67
80
  }
68
81
  queries.push(parsed.value);
69
82
  }
70
- // A realization throw reads as `unavailable` per query. The loop
71
- // would render the throw as a raw tool error, and a raw driver
72
- // message teaches the agent nothing that `reason` does not.
73
- try {
74
- const outcomes = await deps.extendAnalysisFarm(deps.analysisId, queries);
75
- return ok({ outcomes });
76
- }
77
- catch (cause) {
78
- const reason = cause instanceof Error ? cause.message : String(cause);
83
+ // A call that gave no answer reads as `unavailable` per query. A
84
+ // raw tool error teaches the agent nothing that `reason` does not.
85
+ const linked = await callExtendAnalysisFarm(deps.extendAnalysisFarm, deps.analysisId, queries);
86
+ if (linked.isErr()) {
87
+ const reason = linked.error;
79
88
  return ok({ outcomes: queries.map((query) => ({ kind: "unavailable", spelling: query.spelling, reason })) });
80
89
  }
90
+ return ok({ outcomes: linked.value });
81
91
  },
82
92
  });
83
93
  }
@@ -31,23 +31,30 @@
31
31
  * - await child results via `getResult` (these are themselves cached
32
32
  * in DBOS, so parent recovery does NOT re-run completed children)
33
33
  * 3. Failure isolation
34
- * - a child settling `failed`/`blocked` (or throwing for a non-budget
35
- * cause) dooms only its transitive dependents: they can never become
36
- * dependency-satisfied, are marked `skipped` on the dag-state part, and
37
- * are never dispatched. In-flight siblings and independent ready steps
38
- * continue; the loop drains and the run finalises (typically `partial`).
34
+ * - a child settling `failed`/`blocked` (or throwing for a cause that is
35
+ * not a suspension) dooms only its transitive dependents: they can never
36
+ * become dependency-satisfied, are marked `skipped` on the dag-state
37
+ * part, and are never dispatched. In-flight siblings and independent
38
+ * ready steps continue; the loop drains and the run finalises (typically
39
+ * `partial`).
39
40
  * - the halt cascade (cancel in-flight via
40
41
  * `Promise.allSettled(handles.map(h => DBOS.cancelWorkflow(h.workflowID)))`,
41
- * stop scheduling) survives only for the budget paths: a
42
- * `budget_exceeded` settlement and the `neverFits` plan-validation guard.
43
- * 4. Pause cascade (402)
44
- * - on a child cancelling itself with `budget_exceeded`: cancel siblings
45
- * then self-cancel the parent to `CANCELLED` (NOT `ERROR` — ERROR
46
- * isn't resumable; NOTES #3)
42
+ * stop scheduling) survives only for a suspension of a child and the
43
+ * `neverFits` plan-validation guard.
44
+ * 4. Suspension cascade (workflow-suspension spec)
45
+ * - a child that suspends sends a typed message on `CHILD_SUSPENDED_TOPIC`
46
+ * and cancels itself. The parent reads the kind of the message, never a
47
+ * reason string: it cancels the siblings, then self-cancels to
48
+ * `CANCELLED` (NOT `ERROR` — ERROR isn't resumable; NOTES #3)
49
+ * - a refusal of the `RunCharge.open` gate with the suspend flag suspends
50
+ * the run before any step starts
51
+ * - a `suspend` error of a synthesis model request suspends the run in
52
+ * place of the synthesis failure
47
53
  * - the analysis flips to `suspended_insufficient_funds` in
48
54
  * `collectAndComplete`. The paused parent lands in `CANCELLED`, a
49
- * durably reschedulable state; re-driving it after a top-up is a
50
- * future enhancement, not yet wired here.
55
+ * durably reschedulable state; re-driving it is a future enhancement,
56
+ * not yet wired here. The reason of the host rides to the run row, the
57
+ * charge close, the metric, and the terminal part, unread.
51
58
  * 5. `synthesizeFindings` (single sequential block, no sandbox)
52
59
  * 6. `collectAndComplete` (terminal — runs on ALL paths)
53
60
  * - determine final status from completed/cancelled/failed counts
@@ -77,6 +84,7 @@ import type { RunCharge } from "../billing/run-charge.js";
77
84
  import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
78
85
  import { type PlanStep } from "./execute-analysis-scheduler.js";
79
86
  import { type SandboxStepInput, type SandboxStepResult } from "./sandbox-step.js";
87
+ import { type Suspension } from "./suspension.js";
80
88
  /** Registered child sandbox-step callable the parent's child-dispatch closes over. */
81
89
  type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxStepResult>;
82
90
  /** Quick-display finding on the run-completed card. Mirrors the wire schema's RunCompletedFinding. */
@@ -320,13 +328,14 @@ export declare function composeStepSeed(args: {
320
328
  * its reason in `blockedReason` — an honest agent-declared blocker, not an
321
329
  * error — and a failure carries the same reason string
322
330
  * `persist-synthesis-outcome` writes to `cortex_runs.synthesis_reason`, so the
323
- * two ledgers cannot disagree about why synthesis died. Exported for the
324
- * projection tests (the `buildChildInput` pattern).
331
+ * two ledgers cannot disagree about why synthesis died. A suspension is not an
332
+ * outcome: the row is canceled, as the row of a suspended step. Exported for
333
+ * the projection tests (the `buildChildInput` pattern).
325
334
  */
326
335
  export declare function synthesisRowUpdate(outcome: {
327
336
  status: SynthesisStatus;
328
337
  reason: string | null;
329
- }, durationMs: number): UpdateStepExecutionInput;
338
+ } | Suspension, durationMs: number): UpdateStepExecutionInput;
330
339
  /**
331
340
  * Run-level literature-grounded synthesis (no sandbox). Returns the
332
341
  * quick-display findings for the run-completed card; empty when synthesis is