@inflexa-ai/harness 0.44.0 → 0.45.1

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 (187) hide show
  1. package/dist/agents/conversation-agent.d.ts +10 -0
  2. package/dist/agents/conversation-agent.js +19 -1
  3. package/dist/agents/sandbox/shared.d.ts +23 -2
  4. package/dist/agents/sandbox/shared.js +51 -18
  5. package/dist/agents/sandbox/translational-safety-agent.js +26 -0
  6. package/dist/agents/sandbox/types.d.ts +24 -1
  7. package/dist/agents/sandbox-catalog.d.ts +14 -0
  8. package/dist/agents/sandbox-catalog.js +19 -0
  9. package/dist/app/approve-plan.d.ts +51 -0
  10. package/dist/app/approve-plan.js +84 -0
  11. package/dist/app/chat-turn.js +6 -3
  12. package/dist/app/data-profile-view.d.ts +1 -1
  13. package/dist/app/run-activity.js +4 -6
  14. package/dist/config/environment-stores.d.ts +4 -2
  15. package/dist/contracts/chat-events.d.ts +14 -2
  16. package/dist/contracts/chat-events.js +1 -1
  17. package/dist/contracts/chat-frame.d.ts +7 -0
  18. package/dist/contracts/chat-frame.js +24 -6
  19. package/dist/contracts/chat-parts.d.ts +73 -2
  20. package/dist/contracts/chat-parts.js +3 -1
  21. package/dist/contracts/index.d.ts +4 -2
  22. package/dist/contracts/index.js +1 -0
  23. package/dist/contracts/part-registry.js +3 -0
  24. package/dist/contracts/schemas/chat-events.d.ts +38 -0
  25. package/dist/contracts/schemas/chat-events.js +8 -0
  26. package/dist/contracts/schemas/chat-parts.d.ts +239 -15
  27. package/dist/contracts/schemas/chat-parts.js +80 -12
  28. package/dist/contracts/schemas/index.d.ts +2 -2
  29. package/dist/contracts/schemas/index.js +2 -2
  30. package/dist/contracts/subagent-activity.d.ts +12 -0
  31. package/dist/contracts/subagent-activity.js +76 -0
  32. package/dist/execution/artifact-metadata.js +1 -3
  33. package/dist/execution/dbos-workflow-purger.d.ts +2 -0
  34. package/dist/execution/dbos-workflow-purger.js +21 -14
  35. package/dist/execution/run-canceler.d.ts +33 -11
  36. package/dist/execution/run-canceler.js +130 -28
  37. package/dist/execution/run-end-record.d.ts +30 -0
  38. package/dist/execution/run-end-record.js +27 -0
  39. package/dist/execution/run-event-parts.d.ts +6 -5
  40. package/dist/execution/run-event-parts.js +6 -5
  41. package/dist/execution/run-event-stream.d.ts +18 -6
  42. package/dist/execution/run-event-stream.js +67 -17
  43. package/dist/execution/run-synthesis.d.ts +21 -0
  44. package/dist/execution/run-synthesis.js +77 -3
  45. package/dist/index.d.ts +28 -10
  46. package/dist/index.js +26 -2
  47. package/dist/lib/hooks.d.ts +1 -1
  48. package/dist/lib/metrics.d.ts +2 -2
  49. package/dist/loop/call-guard.d.ts +50 -0
  50. package/dist/loop/call-guard.js +90 -0
  51. package/dist/loop/compaction.d.ts +5 -0
  52. package/dist/loop/compaction.js +17 -0
  53. package/dist/loop/run-agent.d.ts +9 -0
  54. package/dist/loop/run-agent.js +16 -1
  55. package/dist/loop/run-to-terminal.js +4 -1
  56. package/dist/loop/types.d.ts +8 -0
  57. package/dist/memory/card-builders.d.ts +11 -0
  58. package/dist/memory/card-builders.js +35 -21
  59. package/dist/memory/conversation-display-recorder.d.ts +2 -2
  60. package/dist/memory/conversation-display-recorder.js +38 -4
  61. package/dist/memory/conversation-display-storage.d.ts +9 -2
  62. package/dist/memory/conversation-display-storage.js +18 -3
  63. package/dist/memory/thread-history.d.ts +42 -8
  64. package/dist/memory/thread-history.js +164 -45
  65. package/dist/memory/thread-store.d.ts +13 -0
  66. package/dist/memory/thread-store.js +15 -1
  67. package/dist/prompts/briefing.d.ts +57 -3
  68. package/dist/prompts/briefing.js +129 -2
  69. package/dist/prompts/conversation.d.ts +1 -1
  70. package/dist/prompts/conversation.js +3 -1
  71. package/dist/prompts/planner.js +13 -2
  72. package/dist/provenance/seam.d.ts +39 -20
  73. package/dist/provenance/seam.js +7 -5
  74. package/dist/providers/ai-sdk.js +93 -11
  75. package/dist/providers/types.d.ts +6 -0
  76. package/dist/reference-data/catalog.js +141 -2
  77. package/dist/report-model/authoring-grammar.d.ts +26 -26
  78. package/dist/report-model/pin-snapshot.d.ts +1 -1
  79. package/dist/report-model/pin-snapshot.js +2 -50
  80. package/dist/runtime/assemble.d.ts +8 -1
  81. package/dist/runtime/assemble.js +11 -0
  82. package/dist/runtime/boot.d.ts +3 -0
  83. package/dist/runtime/boot.js +10 -1
  84. package/dist/sandbox/image-packages.d.ts +26 -4
  85. package/dist/sandbox/image-packages.js +18 -4
  86. package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
  87. package/dist/sandbox/sandbox-step-translate.js +24 -0
  88. package/dist/schemas/artifact-manifest.d.ts +1 -1
  89. package/dist/schemas/artifact-manifest.js +11 -9
  90. package/dist/schemas/data-profile-schemas.d.ts +12 -12
  91. package/dist/schemas/plan-schemas.d.ts +75 -21
  92. package/dist/schemas/plan-schemas.js +1 -1
  93. package/dist/schemas/run-synthesis.d.ts +2 -2
  94. package/dist/schemas/workflow-state.d.ts +113 -0
  95. package/dist/schemas/workflow-state.js +54 -0
  96. package/dist/state/analyses-metrics.d.ts +79 -7
  97. package/dist/state/analyses-metrics.js +141 -6
  98. package/dist/state/artifacts.d.ts +66 -3
  99. package/dist/state/artifacts.js +85 -7
  100. package/dist/state/data-profile.d.ts +4 -2
  101. package/dist/state/data-profile.js +7 -5
  102. package/dist/state/index.d.ts +9 -8
  103. package/dist/state/index.js +5 -5
  104. package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
  105. package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
  106. package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
  107. package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
  108. package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
  109. package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
  110. package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
  111. package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
  112. package/dist/state/migrations/index.js +8 -0
  113. package/dist/state/plans.d.ts +21 -0
  114. package/dist/state/plans.js +25 -0
  115. package/dist/state/runs.d.ts +79 -14
  116. package/dist/state/runs.js +150 -33
  117. package/dist/state/schema.d.ts +2 -0
  118. package/dist/state/step-executions.d.ts +9 -0
  119. package/dist/state/step-executions.js +22 -0
  120. package/dist/tasks/data-profile-activity.d.ts +7 -1
  121. package/dist/tasks/data-profile-activity.js +18 -9
  122. package/dist/tasks/data-profile.d.ts +15 -1
  123. package/dist/tasks/data-profile.js +16 -11
  124. package/dist/tools/ad-hoc-router.d.ts +2 -0
  125. package/dist/tools/ad-hoc-router.js +18 -2
  126. package/dist/tools/approval/gateway.js +28 -5
  127. package/dist/tools/capability-data.d.ts +36 -0
  128. package/dist/tools/capability-data.js +73 -0
  129. package/dist/tools/display/show-user.d.ts +1 -1
  130. package/dist/tools/execute-analysis.d.ts +80 -1
  131. package/dist/tools/execute-analysis.js +352 -110
  132. package/dist/tools/knowledge/check.d.ts +60 -0
  133. package/dist/tools/knowledge/check.js +92 -0
  134. package/dist/tools/knowledge/client.d.ts +382 -0
  135. package/dist/tools/knowledge/client.js +302 -0
  136. package/dist/tools/knowledge/environment.d.ts +78 -0
  137. package/dist/tools/knowledge/environment.js +105 -0
  138. package/dist/tools/knowledge/index.d.ts +49 -0
  139. package/dist/tools/knowledge/index.js +70 -0
  140. package/dist/tools/knowledge/local-slots.d.ts +62 -0
  141. package/dist/tools/knowledge/local-slots.js +177 -0
  142. package/dist/tools/knowledge/pin.d.ts +14 -0
  143. package/dist/tools/knowledge/pin.js +17 -0
  144. package/dist/tools/knowledge/recommend.d.ts +92 -0
  145. package/dist/tools/knowledge/recommend.js +98 -0
  146. package/dist/tools/knowledge/situation.d.ts +143 -0
  147. package/dist/tools/knowledge/situation.js +164 -0
  148. package/dist/tools/knowledge/skeleton.d.ts +69 -0
  149. package/dist/tools/knowledge/skeleton.js +202 -0
  150. package/dist/tools/knowledge/template.d.ts +137 -0
  151. package/dist/tools/knowledge/template.js +267 -0
  152. package/dist/tools/research/generate-plan.d.ts +23 -0
  153. package/dist/tools/research/generate-plan.js +203 -12
  154. package/dist/tools/research/guarded-address.d.ts +20 -0
  155. package/dist/tools/research/guarded-address.js +148 -0
  156. package/dist/tools/research/inspect-data-profile.d.ts +1 -1
  157. package/dist/tools/research/inspect-run.d.ts +1 -1
  158. package/dist/tools/research/inspect-run.js +7 -7
  159. package/dist/tools/research/searxng-engine.d.ts +40 -0
  160. package/dist/tools/research/searxng-engine.js +120 -0
  161. package/dist/tools/research/web-fetch.d.ts +124 -0
  162. package/dist/tools/research/web-fetch.js +627 -0
  163. package/dist/tools/research/web-search.d.ts +75 -0
  164. package/dist/tools/research/web-search.js +112 -0
  165. package/dist/tools/sandbox/list-available-packages.d.ts +7 -4
  166. package/dist/tools/sandbox/list-available-packages.js +12 -4
  167. package/dist/tools/sandbox/list-available-refs.d.ts +21 -0
  168. package/dist/tools/sandbox/list-available-refs.js +60 -3
  169. package/dist/tools/sandbox/report-blocker.js +5 -1
  170. package/dist/tools/terminal-text.d.ts +11 -0
  171. package/dist/tools/terminal-text.js +19 -0
  172. package/dist/tools/workspace/decision-record.d.ts +81 -0
  173. package/dist/tools/workspace/decision-record.js +117 -0
  174. package/dist/tools/workspace/edit-file.d.ts +3 -1
  175. package/dist/tools/workspace/edit-file.js +20 -2
  176. package/dist/tools/workspace/execute-command.d.ts +17 -0
  177. package/dist/tools/workspace/execute-command.js +28 -1
  178. package/dist/tools/workspace/index.d.ts +1 -0
  179. package/dist/tools/workspace/index.js +1 -0
  180. package/dist/tools/workspace/mutator.d.ts +1 -1
  181. package/dist/workflows/execute-analysis.d.ts +62 -21
  182. package/dist/workflows/execute-analysis.js +307 -79
  183. package/dist/workflows/plan/graph.d.ts +16 -0
  184. package/dist/workflows/plan/graph.js +36 -0
  185. package/dist/workflows/sandbox-step.d.ts +12 -0
  186. package/dist/workflows/sandbox-step.js +14 -12
  187. package/package.json +5 -1
@@ -37,6 +37,28 @@ import { describeDbError } from "../lib/db-result.js";
37
37
  import { queryRun } from "../state/runs.js";
38
38
  import { queryStepsByRun } from "../state/step-executions.js";
39
39
  import { foldRunEventParts, parseRunEventPart } from "./run-event-parts.js";
40
+ const canceledPart = (runId) => ({ type: "data-run-failed", runId, error: "Run canceled", reason: "canceled" });
41
+ /** The terminal part that a settled run row gives, or `null` when the body of the run writes the part. */
42
+ function partOfRow(runId, row) {
43
+ switch (row.status) {
44
+ case "canceled":
45
+ return canceledPart(runId);
46
+ case "suspended_insufficient_funds":
47
+ return row.error === null
48
+ ? { type: "data-run-failed", runId, error: "Run suspended" }
49
+ : { type: "data-run-failed", runId, error: row.error, reason: row.error };
50
+ case "failed":
51
+ return { type: "data-run-failed", runId, error: row.error ?? "Run failed" };
52
+ case "completed":
53
+ case "partial":
54
+ case "running":
55
+ return null;
56
+ default: {
57
+ const _exhaustive = row.status;
58
+ throw new Error(`unhandled run status: ${String(_exhaustive)}`);
59
+ }
60
+ }
61
+ }
40
62
  /** Durable stream key every run producer writes its parts under. */
41
63
  const RUN_EVENT_STREAM_KEY = "events";
42
64
  /**
@@ -58,8 +80,8 @@ const CHILD_DISCOVERY_INTERVAL_MS = 1_000;
58
80
  * the stream can end before the row changes, and the later reads wait out that
59
81
  * window.
60
82
  */
61
- const CANCELED_ROW_READ_ATTEMPTS = 4;
62
- const CANCELED_ROW_READ_INTERVAL_MS = 500;
83
+ const TERMINAL_ROW_READ_ATTEMPTS = 4;
84
+ const TERMINAL_ROW_READ_INTERVAL_MS = 500;
63
85
  /**
64
86
  * Build the DBOS-backed realization of {@link RunEventStream}.
65
87
  *
@@ -72,7 +94,8 @@ const CANCELED_ROW_READ_INTERVAL_MS = 500;
72
94
  */
73
95
  export function createRunEventStream(deps) {
74
96
  const baseLogger = (deps.logger ?? createNoopLogger()).named("run-event-stream");
75
- const readRunStatus = deps.readRunStatus ?? ((runId) => queryRun(deps.pool, runId).map((row) => row?.status ?? null));
97
+ const readRun = deps.readRun ?? ((runId) => queryRun(deps.pool, runId).map((row) => (row === null ? null : { status: row.status, error: row.error })));
98
+ const readWorkflowStatus = deps.readWorkflowStatus ?? (async (runId) => (await DBOS.getWorkflowStatus(runId))?.status ?? null);
76
99
  const waitForRowRead = deps.sleep ?? sleep;
77
100
  return {
78
101
  async subscribe({ runId, onPart, signal }) {
@@ -210,37 +233,64 @@ export function createRunEventStream(deps) {
210
233
  await Promise.race([sleep(CHILD_DISCOVERY_INTERVAL_MS), aborted, parentRead]);
211
234
  }
212
235
  })();
213
- const canceledTerminalPart = async () => {
214
- for (let attempt = 1; attempt <= CANCELED_ROW_READ_ATTEMPTS; attempt++) {
215
- const read = await readRunStatus(runId);
236
+ const workflowTerminalPart = async () => {
237
+ let workflowStatus;
238
+ try {
239
+ workflowStatus = await readWorkflowStatus(runId);
240
+ }
241
+ catch (err) {
242
+ logger.warn("workflow status read failed", logger.errorFields(err));
243
+ return null;
244
+ }
245
+ switch (workflowStatus) {
246
+ case "CANCELLED":
247
+ return canceledPart(runId);
248
+ case "ERROR":
249
+ case "MAX_RECOVERY_ATTEMPTS_EXCEEDED":
250
+ case "SUCCESS":
251
+ return { type: "data-run-failed", runId, error: "Run ended without a terminal status" };
252
+ case "PENDING":
253
+ case "ENQUEUED":
254
+ case "DELAYED":
255
+ logger.warn("run streams ended while the workflow is active", { workflowStatus });
256
+ return null;
257
+ default:
258
+ // No workflow: the launch did not start it, or it never started. An unknown status gives no part too.
259
+ if (workflowStatus !== null)
260
+ logger.warn("unknown workflow status", { workflowStatus });
261
+ return null;
262
+ }
263
+ };
264
+ const missingTerminalPart = async () => {
265
+ let rowReadsRunning = false;
266
+ for (let attempt = 1; attempt <= TERMINAL_ROW_READ_ATTEMPTS; attempt++) {
267
+ const read = await readRun(runId);
268
+ rowReadsRunning = false;
216
269
  if (read.isErr()) {
217
270
  logger.warn("run row read failed", { err: describeDbError(read.error) });
218
271
  }
219
272
  else {
220
- const status = read.value;
221
- if (status === "canceled")
222
- return { type: "data-run-failed", runId, error: "Run canceled", reason: "canceled" };
223
273
  // The launch writes the row before the workflow starts, thus
224
274
  // no row means that the id is not an analysis run.
225
- if (status === null)
226
- return null;
227
- // Only an active row can still become `canceled`.
228
- if (status !== "running" && status !== "suspended_insufficient_funds")
275
+ if (read.value === null)
229
276
  return null;
277
+ if (read.value.status !== "running")
278
+ return partOfRow(runId, read.value);
279
+ rowReadsRunning = true;
230
280
  }
231
- if (attempt < CANCELED_ROW_READ_ATTEMPTS)
232
- await Promise.race([waitForRowRead(CANCELED_ROW_READ_INTERVAL_MS), aborted]);
281
+ if (attempt < TERMINAL_ROW_READ_ATTEMPTS)
282
+ await Promise.race([waitForRowRead(TERMINAL_ROW_READ_INTERVAL_MS), aborted]);
233
283
  if (signal.aborted)
234
284
  return null;
235
285
  }
236
- return null;
286
+ return rowReadsRunning ? workflowTerminalPart() : null;
237
287
  };
238
288
  const settled = (async () => {
239
289
  await discovery;
240
290
  await parentRead;
241
291
  await Promise.all(childReads);
242
292
  if (!sawTerminal && !signal.aborted) {
243
- const terminal = await canceledTerminalPart();
293
+ const terminal = await missingTerminalPart();
244
294
  if (terminal !== null)
245
295
  pending.push(terminal);
246
296
  }
@@ -24,6 +24,7 @@
24
24
  * `failed` part. We do not throw here for routine submitter rejections.
25
25
  */
26
26
  import { type RunSynthesis } from "../schemas/run-synthesis.js";
27
+ import type { RunSynthesisPart } from "../contracts/chat-parts.js";
27
28
  import type { StepSummary } from "../schemas/step-summary.js";
28
29
  import type { ToolOutputStore } from "../loop/tool-output.js";
29
30
  import type { ChatDataPart, EmitFn } from "../loop/types.js";
@@ -119,6 +120,26 @@ export interface PersistSynthesisInput {
119
120
  readonly synthesis: RunSynthesis;
120
121
  }
121
122
  export declare function persistSynthesis(args: PersistSynthesisInput): Promise<string>;
123
+ export interface ReadSynthesisInput {
124
+ /** Absolute host root of the analysis's workspace tree. */
125
+ readonly workspaceRoot: string;
126
+ readonly runId: string;
127
+ }
128
+ /**
129
+ * Read the text of a synthesis record under the cap, or give `undefined` when the bytes do not come back.
130
+ *
131
+ * The read takes a bounded byte window, thus a file of any size costs the cap. A run id that can escape
132
+ * the run tree, an absent file, a genuine read fault, and a file over the cap each give `undefined`,
133
+ * because a collection reports no error for one record.
134
+ */
135
+ export declare function readSynthesisText(args: ReadSynthesisInput): Promise<string | undefined>;
136
+ /**
137
+ * The validated synthesis record of a run as the flat `data-run-synthesis` part, the shape that the run
138
+ * stream carries for the same record. It gives `null` for each fault of {@link readSynthesisText}, for text
139
+ * that is not JSON, and for JSON that fails {@link RunSynthesisSchema}. The run row stays the authority on
140
+ * whether a synthesis exists, thus the read does not consult it.
141
+ */
142
+ export declare function readRunSynthesis(args: ReadSynthesisInput): Promise<RunSynthesisPart | null>;
122
143
  export declare function formatSynthesisEmbeddingText(synthesis: RunSynthesis): string;
123
144
  /**
124
145
  * Build the `data-run-synthesis` chat part the parent stream emits when the
@@ -23,16 +23,18 @@
23
23
  * resolves with `{ kind: "failed" }`, the wrapper emits a synthesis-progress
24
24
  * `failed` part. We do not throw here for routine submitter rejections.
25
25
  */
26
- import { readFile } from "node:fs/promises";
26
+ import { open, readFile } from "node:fs/promises";
27
27
  import { join } from "node:path";
28
28
  import { ok } from "neverthrow";
29
29
  import { z } from "zod";
30
30
  import { writeFileWithinRoot } from "../lib/fs-helpers.js";
31
+ import { tryFs } from "../lib/fs-result.js";
31
32
  import { hintForZodIssue } from "../lib/zod-issues.js";
32
33
  import { BiologicalThemeSchema, KeyReferenceSchema, RunSynthesisSchema, SynthesizedFindingSchema, } from "../schemas/run-synthesis.js";
33
34
  import { synthesisAgentPrompt } from "../prompts/synthesis-agent.js";
34
35
  import { composeSystemPrompt } from "../agents/system-prompt.js";
35
- import { runDir } from "../workspace/paths.js";
36
+ import { flattenDataPart } from "../contracts/chat-frame.js";
37
+ import { isSafeId, runDir } from "../workspace/paths.js";
36
38
  import { decodeObjectString } from "../lib/zod-issues.js";
37
39
  import { runToTerminal } from "../loop/run-to-terminal.js";
38
40
  import { passthroughStep } from "../loop/run-step.js";
@@ -435,15 +437,87 @@ export async function loadStepSummariesFromDisk(args) {
435
437
  }
436
438
  return out;
437
439
  }
440
+ /** The file name of the synthesis record inside the run directory. */
441
+ const SYNTHESIS_FILE = "synthesis.json";
442
+ /**
443
+ * The cap of one synthesis read. A synthesis record is small by construction, and the cap bounds the
444
+ * cost of a file that is not. A file over the cap gives no text, because a cut JSON text cannot parse.
445
+ */
446
+ const SYNTHESIS_CAP_BYTES = 1024 * 1024;
438
447
  export async function persistSynthesis(args) {
439
448
  // Confine to the run directory, not the whole workspace: synthesis.json sits
440
449
  // at runs/{runId}/, above any step's RW mount and never in the hard-linked
441
450
  // `data/` inputs.
442
451
  const runRoot = join(args.workspaceRoot, runDir(args.runId));
443
- const path = join(runRoot, "synthesis.json");
452
+ const path = join(runRoot, SYNTHESIS_FILE);
444
453
  await writeFileWithinRoot(runRoot, path, JSON.stringify(args.synthesis, null, 2));
445
454
  return path;
446
455
  }
456
+ /**
457
+ * Read the text of a synthesis record under the cap, or give `undefined` when the bytes do not come back.
458
+ *
459
+ * The read takes a bounded byte window, thus a file of any size costs the cap. A run id that can escape
460
+ * the run tree, an absent file, a genuine read fault, and a file over the cap each give `undefined`,
461
+ * because a collection reports no error for one record.
462
+ */
463
+ export async function readSynthesisText(args) {
464
+ // The path builder refuses an id that can escape the run tree with a throw. The guard keeps the read on
465
+ // the value channel.
466
+ if (!isSafeId(args.runId)) {
467
+ return undefined;
468
+ }
469
+ const absolute = join(args.workspaceRoot, runDir(args.runId), SYNTHESIS_FILE);
470
+ return tryFs("runSynthesis.readText", async () => {
471
+ const handle = await open(absolute, "r");
472
+ try {
473
+ // The window takes one byte more than the cap. A read that fills it states that the file
474
+ // holds more bytes than the cap admits, thus the two conditions stay apart.
475
+ const buffer = Buffer.alloc(SYNTHESIS_CAP_BYTES + 1);
476
+ // One read of a network-backed file can give fewer bytes than the window holds. Such a
477
+ // short read cuts the text in the middle of the JSON, and the record then parses to no
478
+ // key at all. Thus the loop reads again from the offset that the last read reached, and
479
+ // it stops when the window fills or when a read gives no byte.
480
+ let filled = 0;
481
+ while (filled < buffer.length) {
482
+ const { bytesRead } = await handle.read(buffer, filled, buffer.length - filled, filled);
483
+ if (bytesRead === 0) {
484
+ break;
485
+ }
486
+ filled += bytesRead;
487
+ }
488
+ return filled > SYNTHESIS_CAP_BYTES ? undefined : buffer.subarray(0, filled).toString("utf8");
489
+ }
490
+ finally {
491
+ await handle.close();
492
+ }
493
+ }, { path: absolute, onAbsent: () => undefined }).unwrapOr(undefined);
494
+ }
495
+ /**
496
+ * The validated synthesis record of a run as the flat `data-run-synthesis` part, the shape that the run
497
+ * stream carries for the same record. It gives `null` for each fault of {@link readSynthesisText}, for text
498
+ * that is not JSON, and for JSON that fails {@link RunSynthesisSchema}. The run row stays the authority on
499
+ * whether a synthesis exists, thus the read does not consult it.
500
+ */
501
+ export async function readRunSynthesis(args) {
502
+ const text = await readSynthesisText(args);
503
+ if (text === undefined) {
504
+ return null;
505
+ }
506
+ let json;
507
+ try {
508
+ json = JSON.parse(text);
509
+ }
510
+ catch {
511
+ return null;
512
+ }
513
+ const parsed = RunSynthesisSchema.safeParse(json);
514
+ if (!parsed.success) {
515
+ return null;
516
+ }
517
+ // The payload comes from a validated `RunSynthesis`, and the builder sets each field of the part contract.
518
+ // The tests parse the result with `RunSynthesisPartSchema`.
519
+ return flattenDataPart(buildRunSynthesisPart(args.runId, parsed.data));
520
+ }
447
521
  // ── Embedding text ──────────────────────────────────────────────────
448
522
  export function formatSynthesisEmbeddingText(synthesis) {
449
523
  return [
package/dist/index.d.ts CHANGED
@@ -16,6 +16,10 @@ export { resolveCompositionEyes } from "./runtime/assemble.js";
16
16
  export type { ThreadAgentResolver, UnregisteredThreadType } from "./runtime/assemble.js";
17
17
  export { createReportSessionSpawn } from "./app/spawn-report-session.js";
18
18
  export type { ReportSessionSpawn, ReportSessionSpawnDeps, ReportSessionPaging, SpawnRefusal, ReportBrief, ReportSessionDelta, NewestReportChild, } from "./app/spawn-report-session.js";
19
+ export { launchPlanRun } from "./tools/execute-analysis.js";
20
+ export type { LaunchPlanRunDeps, LaunchPlanRunInput, LaunchPlanRunRefusal, PlanLaunch } from "./tools/execute-analysis.js";
21
+ export { approvePlan } from "./app/approve-plan.js";
22
+ export type { ApprovePlanDeps, ApprovePlanInput, ApprovePlanRefusal, PlanApproval } from "./app/approve-plan.js";
19
23
  export type { EnsureSessionStateResult } from "./app/report-session-runtime.js";
20
24
  export { bootHarness } from "./runtime/boot.js";
21
25
  export type { BootHarnessDeps, BootedHarness } from "./runtime/boot.js";
@@ -48,10 +52,14 @@ export type { WorkflowPurger } from "./execution/workflow-purger.js";
48
52
  export { createRunCanceler, UnknownRunError } from "./execution/run-canceler.js";
49
53
  export type { RunCanceler, RunCancelerDeps, CancelRunResult } from "./execution/run-canceler.js";
50
54
  export { defineTool, isToolError } from "./tools/define-tool.js";
55
+ export { createHttpKnowledgeClient, createKnowledgeRecommendTool, createKnowledgeCheckTool, createKnowledgeTools, knowledgeToolDefinitionHash, } from "./tools/knowledge/index.js";
56
+ export type { KnowledgeClient, KnowledgeSituation, HttpKnowledgeClientConfig } from "./tools/knowledge/index.js";
51
57
  export type { Tool, ToolDefinition, ToolContext, ToolError } from "./tools/define-tool.js";
52
58
  export { normalizeDetail, DETAIL_MAX_LENGTH } from "./loop/tool-detail.js";
53
59
  export { createCitationResolver } from "./citations/resolve.js";
54
60
  export type { CitationResolverConfig, CitationResolverDependencies, CitationResolverSourceConfig } from "./citations/resolve.js";
61
+ export { createSearxngEngine } from "./tools/research/searxng-engine.js";
62
+ export type { WebSearchEngine } from "./tools/research/web-search.js";
55
63
  export { CitationKindSchema, CitationInputSchema, CitationSourceSchema, CitationSourceStatusSchema, CitationResolutionResultSchema, CitationVerdictSchema, CitationCoverageSchema, ComparisonStatusSchema, CitationFieldSchema, UnsupportedWorkKindSchema, CitationIdentifiersSchema, NormalizedCitationMetadataSchema, NormalizedCitationSchema, CitationRecordSchema, IdentifierEvidenceSchema, CitationSourceOutcomeSchema, CitationFieldComparisonSchema, CitationConflictSchema, CitationCandidateClusterSchema, CitationClusterRelationSchema, CitationSourceOperationSchema, CitationSourcePlanItemSchema, } from "./citations/types.js";
56
64
  export type { CitationKind, CitationInput, CitationSource, CitationSourceStatus, CitationResolver, CitationResolveOptions, CitationResolutionResult, CitationVerdict, CitationCoverage, ComparisonStatus, CitationField, UnsupportedWorkKind, CitationIdentifiers, NormalizedCitationMetadata, NormalizedCitation, CitationSourceOutcome, CitationRecord, IdentifierEvidence, CitationFieldComparison, CitationConflict, CitationCandidateCluster, CitationSourceClient, CitationSourceRequest, CitationSourceOperation, CitationSourcePlanItem, RegistrationAgencyEvidence, } from "./citations/types.js";
57
65
  export { createAskGateway } from "./tools/approval/gateway.js";
@@ -85,21 +93,23 @@ export { createStreamingChat } from "./providers/streaming-chat.js";
85
93
  export { openChatTurn, prepareChatTurn, runChatTurn } from "./app/chat-turn.js";
86
94
  export type { ChatTurnOutcome, ChatTurnRan, ChatTurnRefusal, ChatTurnResult, ChatTurnSession, OpenChatTurn, OpenChatTurnParams, PrepareChatTurnDeps, PrepareChatTurnParams, PrepareChatTurnResult, RunChatTurnDeps, RunChatTurnParams, RunOpenChatTurnParams, } from "./app/chat-turn.js";
87
95
  export { createThreadStore } from "./memory/thread-store.js";
88
- export type { ThreadStore, Thread, ThreadType, ThreadInputError, CreateThreadInput, ListThreadsInput, ThreadPage } from "./memory/thread-store.js";
96
+ export type { ThreadStore, Thread, ThreadType, ThreadInputError, CreateThreadInput, ListThreadsInput, ListRecentThreadsInput, ThreadPage, } from "./memory/thread-store.js";
89
97
  export { createThreadHistory, conversationRecordTurn } from "./memory/thread-history.js";
90
- export type { ThreadHistory, StoredMessage, RetractOutcome, ConversationTurn, TurnWrite, TurnWriteResult, TurnClose, TurnStatus, StoredTurnRecord, } from "./memory/thread-history.js";
98
+ export type { ThreadHistory, StoredMessage, LoadPageOptions, TurnPage, RetractOutcome, ConversationTurn, TurnWrite, TurnWriteResult, TurnClose, TurnStatus, StoredTurnRecord, } from "./memory/thread-history.js";
91
99
  export { syntheticUserMessage, syntheticRecordMessage, isSyntheticUserMessage, isSyntheticRecordMessage } from "./memory/ai-sdk-message-storage.js";
92
100
  export { createConversationDisplayRecorder } from "./memory/conversation-display-recorder.js";
93
101
  export type { ConversationDisplayRecorder, ConversationDisplayRecorderOptions } from "./memory/conversation-display-recorder.js";
94
102
  export { envelopeDisplayMessages, parseStoredDisplayEnvelope } from "./memory/conversation-display-storage.js";
95
103
  export type { ConversationDisplayMetadata, ConversationUIData, ConversationUIMessage, StoredDisplayEnvelope } from "./memory/conversation-display-storage.js";
96
104
  export { storedMessagesToChat } from "./memory/conversation-display-replay.js";
97
- export type { ChatEvent, ChatFrame, ChatPartFrame, TextDeltaEvent, IterationEvent, ToolStartedEvent, ToolFinishedEvent, ToolOutcome, ToolCallOutcome, ToolCallDetail, FinishEvent, ChatErrorEvent, } from "./contracts/chat-events.js";
105
+ export type { ChatEvent, ChatFrame, ChatPartFrame, TextDeltaEvent, IterationEvent, ToolStartedEvent, ToolFinishedEvent, ToolOutcome, ToolCallOutcome, ToolCallDetail, FinishEvent, ChatErrorEvent, MessageStartEvent, } from "./contracts/chat-events.js";
98
106
  export { toChatFrame, applyChatFrame, checkChatPart } from "./contracts/chat-frame.js";
107
+ export { createSubAgentActivityTracker, SUBAGENT_ACTIVITY_MAX_ENTRIES } from "./contracts/subagent-activity.js";
108
+ export type { SubAgentActivityTracker } from "./contracts/subagent-activity.js";
99
109
  export type { TextPart, ToolCallPart, MessagePart, ChatMessage } from "./contracts/message.js";
100
110
  export type { TurnTerminal, ApplyFrameResult, ChatPartCheck } from "./contracts/chat-frame.js";
101
111
  export type { TokenUsageRollup } from "./contracts/usage.js";
102
- export type { ChatPart, PresentationPart, PresentationContent, PlanPart, RunCardPart, FileReferencePart, FileReferenceEntry, RunStartedPart, DagStatePart, DagStepState, StepStatus, StepActivityPart, StepPhase, StepFileTreePart, FileTreeEntry, StepOutputPart, StepOutputFile, StepSummaryPart, StepUsagePart, StepBlockedPart, RunSynthesisPart, SynthesizedFinding, BiologicalTheme, SynthesisProgressPart, SynthesisPhase, RunCompletedPart, RunCompletedFinding, RunFailedPart, CompactionPart, AskPart, ChildSessionStartedPart, ReportRenderedPart, } from "./contracts/chat-parts.js";
112
+ export type { ChatPart, PresentationPart, PresentationContent, PlanPart, RunCardPart, PlanApprovalPart, FileReferencePart, FileReferenceEntry, RunStartedPart, DagStatePart, DagStepState, PlanGraphStep, StepStatus, StepActivityPart, ActivityEntryPart, SubAgentActivityEntry, SubAgentActivityPart, StepPhase, StepFileTreePart, FileTreeEntry, StepOutputPart, StepOutputFile, StepSummaryPart, StepUsagePart, StepBlockedPart, RunSynthesisPart, FindingReference, SynthesizedFinding, BiologicalTheme, SynthesisProgressPart, SynthesisPhase, RunCompletedPart, RunCompletedFinding, RunFailedPart, CompactionPart, AskPart, ChildSessionStartedPart, ReportRenderedPart, } from "./contracts/chat-parts.js";
103
113
  export { PART_REGISTRY, isTransient, isReconciling, isSidebarPart } from "./contracts/part-registry.js";
104
114
  export type { ChatPartType, PartDescriptor, PartEmitter, PartConsumer } from "./contracts/part-registry.js";
105
115
  export { parseStructureUrl, alphafoldEntryUrl, alphafoldPredictionUrl } from "./contracts/structure-source.js";
@@ -115,7 +125,7 @@ export { createPool } from "./lib/storage.js";
115
125
  export type { PoolConfig } from "./lib/storage.js";
116
126
  export type { Pool } from "pg";
117
127
  export { registerDataProfileWorkflow, triggerDataProfile, runDataProfile, describeDataProfileStartError } from "./tasks/data-profile.js";
118
- export type { DataProfileDeps, DataProfileWorkflowInput, DataProfileTriggerDeps, DataProfileTriggerParams, DataProfileTriggerResult, DataProfileStartError, } from "./tasks/data-profile.js";
128
+ export type { DataProfileDeps, DataProfileWorkflowInput, DataProfileTriggerDeps, DataProfileTriggerParams, DataProfileTriggerResult, DataProfileStartError, DataProfileSettlement, NotifyDataProfileSettled, } from "./tasks/data-profile.js";
119
129
  /**
120
130
  * The synthetic run id the data-profile workflow stamps onto its own frame — the profile has no run
121
131
  * row, so this literal is the only thing that identifies its work as the profile's.
@@ -132,6 +142,12 @@ export { upsertAnalysis } from "./state/analyses.js";
132
142
  export { clearDataProfile, loadDataProfileStatus, tryRetryDataProfile, reconcileOrphanedDataProfile } from "./state/data-profile.js";
133
143
  export type { DataProfileAxis, DataProfileChecked, DataProfileCompanionCompleteness, DataProfileCoverage, DataProfileDimension, DataProfileFile, DataProfileGroup, DataProfileGroupSlot, DataProfileInputSignature, DataProfileKind, DataProfileLifecycleStatus, DataProfileMemberAnnotation, DataProfileObservation, DataProfileOrganism, DataProfilePartition, DataProfileProbeReport, DataProfileQualityAssessment, DataProfileQuarantine, DataProfileRecipeStep, DataProfileResult, DataProfileSubjectSource, } from "./contracts/data-profile.js";
134
144
  export type { DataProfileStatus } from "./state/data-profile.js";
145
+ export { queryLatestRunsByAnalyses, queryDataProfileStatusByAnalyses, queryRunProgressByRuns, queryRunsEndedSince } from "./state/analyses-metrics.js";
146
+ export type { DataProfileSummary, EndedRunSummary, LatestRunSummary, RunProgressSummary } from "./state/analyses-metrics.js";
147
+ export { queryArtifactPage, countArtifactsByRuns } from "./state/artifacts.js";
148
+ export type { ArtifactExtensionCount, ArtifactPage, ArtifactPageEntry, ArtifactPageQuery, ArtifactPageSort, RunArtifactCount } from "./state/artifacts.js";
149
+ export { readRunSynthesis } from "./execution/run-synthesis.js";
150
+ export type { ReadSynthesisInput } from "./execution/run-synthesis.js";
135
151
  export { DIMENSION_CATEGORIES, DIMENSION_CATEGORY_IDS, DIMENSION_PROBES, DIMENSION_PROBE_IDS, GROUP_CATEGORIES, GROUP_CATEGORY_IDS, GROUP_ROLES, GROUP_ROLE_IDS, PROBE_OUTCOME_IDS, dimensionCategoryEntry, dimensionScope, groupCategoryEntry, } from "./contracts/profile-vocabulary.js";
136
152
  export type { DimensionCategory, DimensionCategoryEntry, DimensionProbe, DimensionProbeEntry, DimensionScope, DimensionTreatment, GroupCategory, GroupCategoryEntry, GroupRole, GroupRoleEntry, ProbeOutcome, } from "./contracts/profile-vocabulary.js";
137
153
  export { dimensionCardinalities, profileCaveats, profileDatasetFileCount, profileDimensions, profileFileRecords, profileGroups, } from "./app/data-profile-view.js";
@@ -165,22 +181,24 @@ export type { ExecuteAnalysisDeps, ExecuteAnalysisInput, ExecuteAnalysisResult,
165
181
  export { registerSandboxStep } from "./workflows/sandbox-step.js";
166
182
  export type { SandboxStepDeps, SandboxStepInput, SandboxStepResult, SandboxAgentBuildContext } from "./workflows/sandbox-step.js";
167
183
  export { createRunEventStream } from "./execution/run-event-stream.js";
168
- export type { RunEventStream, RunEventStreamDeps, RunEventSubscribeOptions, RunEventPartHandler } from "./execution/run-event-stream.js";
184
+ export type { RunEventStream, RunEventStreamDeps, RunEventRunRead, RunEventSubscribeOptions, RunEventPartHandler } from "./execution/run-event-stream.js";
169
185
  export { pipeFoldedRunEvents } from "./execution/run-event-parts.js";
170
186
  export { createSandboxAgents, SANDBOX_AGENT_META } from "./agents/sandbox/index.js";
171
187
  export type { SandboxAgentDeps, SandboxStepCoords, AgentMeta } from "./agents/sandbox/index.js";
172
188
  export { AnalysisPlanSchema } from "./schemas/workflow-state.js";
173
189
  export type { AnalysisPlan, AnalysisStep } from "./schemas/workflow-state.js";
174
190
  export type { PlanStep } from "./workflows/execute-analysis-scheduler.js";
191
+ export { planGraphOf } from "./workflows/plan/graph.js";
192
+ export { planPartOf } from "./memory/card-builders.js";
175
193
  export { validatePlan } from "./schemas/validate-plan.js";
176
194
  export type { ValidatePlanOptions } from "./schemas/validate-plan.js";
177
195
  export type { ValidationResult } from "./schemas/validate-plan.js";
178
- export { upsertPlan, loadPlan } from "./state/plans.js";
179
- export type { UpsertPlanInput } from "./state/plans.js";
180
- export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
196
+ export { upsertPlan, loadPlan, loadPlanRecord } from "./state/plans.js";
197
+ export type { UpsertPlanInput, PlanRecord } from "./state/plans.js";
198
+ export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, countRunsByAnalysis, queryRunIdsByPlan, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
181
199
  export type { InsertRunInput, RunReservation } from "./state/runs.js";
182
200
  export { queryStepsByRun } from "./state/step-executions.js";
183
- export type { CortexRunRow, StepExecutionRow, RunStatus } from "./state/schema.js";
201
+ export type { CortexRunRow, StepExecutionRow, RunStatus, TerminalRunStatus } from "./state/schema.js";
184
202
  export type { DbError } from "./lib/db-result.js";
185
203
  export { REFERENCE_DATA_CATALOG, REFERENCE_DATA_CATALOG_VERSION, ReferenceArtifactPathSchema, ReferenceArtifactSchema, ReferenceDataCatalogSchema, ReferenceDatasetSchema, ReferenceSha256Schema, UnknownReferenceDatasetError, referenceArtifactKey, resolveReferenceInstallPlan, } from "./reference-data/catalog.js";
186
204
  export type { ReferenceArtifact, ReferenceDataCatalog, ReferenceDataset, ReferenceInstallPlan, ReferenceInstallPlanDataset } from "./reference-data/catalog.js";
package/dist/index.js CHANGED
@@ -22,6 +22,11 @@ export { resolveCompositionEyes } from "./runtime/assemble.js";
22
22
  // the report sessions of one analysis. An embedder reaches the listing for its
23
23
  // navigation surface, thus these names belong on the front door.
24
24
  export { createReportSessionSpawn } from "./app/spawn-report-session.js";
25
+ // Start the run of a stored plan, and approve a plan by its id. Each gives a `Result` and never throws, thus
26
+ // a host route calls them with no `try`. `CoreRuntime.approvePlan` binds the approval over the deps of the
27
+ // conversation agent.
28
+ export { launchPlanRun } from "./tools/execute-analysis.js";
29
+ export { approvePlan } from "./app/approve-plan.js";
25
30
  // The harness-owned boot sequence: ordered boot steps around `assembleCoreRuntime`
26
31
  // (skill validation → state init → connection budget → assemble → launch) + a
27
32
  // `shutdown` handle the embedder wires to its process signals.
@@ -65,6 +70,10 @@ export { createDbosWorkflowPurger } from "./execution/dbos-workflow-purger.js";
65
70
  export { createRunCanceler, UnknownRunError } from "./execution/run-canceler.js";
66
71
  // Tool primitive.
67
72
  export { defineTool, isToolError } from "./tools/define-tool.js";
73
+ // The knowledge plane: an embedder binds a client at its composition root
74
+ // (or none), and it can hand `knowledge_recommend` to the conversation agent
75
+ // through `hostTools`.
76
+ export { createHttpKnowledgeClient, createKnowledgeRecommendTool, createKnowledgeCheckTool, createKnowledgeTools, knowledgeToolDefinitionHash, } from "./tools/knowledge/index.js";
68
77
  // Tool-call-detail authoring aids for an embedder that contributes a host tool.
69
78
  // A `describeCall` hook that pre-empts truncation — a count in place of a
70
79
  // mid-value cut that would name a file that does not exist — must gate on the
@@ -76,6 +85,12 @@ export { defineTool, isToolError } from "./tools/define-tool.js";
76
85
  export { normalizeDetail, DETAIL_MAX_LENGTH } from "./loop/tool-detail.js";
77
86
  // Host-agnostic citation verification service and its public contracts.
78
87
  export { createCitationResolver } from "./citations/resolve.js";
88
+ // The web-search engine type and the SearXNG realization. An embedder cannot
89
+ // build its composition root without these two names: it constructs the engine
90
+ // and types the `webSearch` field of `ConversationAgentDeps` (the front-door
91
+ // rule of the harness-embedder-exports spec). The tools stay off the barrel —
92
+ // the conversation agent constructs them itself.
93
+ export { createSearxngEngine } from "./tools/research/searxng-engine.js";
79
94
  export { CitationKindSchema, CitationInputSchema, CitationSourceSchema, CitationSourceStatusSchema, CitationResolutionResultSchema, CitationVerdictSchema, CitationCoverageSchema, ComparisonStatusSchema, CitationFieldSchema, UnsupportedWorkKindSchema, CitationIdentifiersSchema, NormalizedCitationMetadataSchema, NormalizedCitationSchema, CitationRecordSchema, IdentifierEvidenceSchema, CitationSourceOutcomeSchema, CitationFieldComparisonSchema, CitationConflictSchema, CitationCandidateClusterSchema, CitationClusterRelationSchema, CitationSourceOperationSchema, CitationSourcePlanItemSchema, } from "./citations/types.js";
80
95
  // Seam: tool approval (`ctx.ask`). `createAskGateway` realizes the `Ask` seam
81
96
  // over the injected `Pool` and carries the outward `answer`/`pending` API plus
@@ -156,6 +171,7 @@ export { envelopeDisplayMessages, parseStoredDisplayEnvelope } from "./memory/co
156
171
  // by dropping that part, so one stale row cannot fail a whole thread.
157
172
  export { storedMessagesToChat } from "./memory/conversation-display-replay.js";
158
173
  export { toChatFrame, applyChatFrame, checkChatPart } from "./contracts/chat-frame.js";
174
+ export { createSubAgentActivityTracker, SUBAGENT_ACTIVITY_MAX_ENTRIES } from "./contracts/subagent-activity.js";
159
175
  export { PART_REGISTRY, isTransient, isReconciling, isSidebarPart } from "./contracts/part-registry.js";
160
176
  // The source grammar of a `structure` presentation card — a host re-runs the same parse over a
161
177
  // persisted `url` before it fetches, so the rule lives in one place for both sides.
@@ -215,6 +231,12 @@ export { upsertAnalysis } from "./state/analyses.js";
215
231
  // analysis's input set empties (deferring on a running workflow), so the UI
216
232
  // stops advertising a profile that describes files the analysis no longer has.
217
233
  export { clearDataProfile, loadDataProfileStatus, tryRetryDataProfile, reconcileOrphanedDataProfile } from "./state/data-profile.js";
234
+ // The grouped reads that summarize a page of analyses: one statement for the whole list.
235
+ export { queryLatestRunsByAnalyses, queryDataProfileStatusByAnalyses, queryRunProgressByRuns, queryRunsEndedSince } from "./state/analyses-metrics.js";
236
+ // The artifact ledger reads of one analysis: one page with an exact total, and the step-output count of each run of a list in one statement.
237
+ export { queryArtifactPage, countArtifactsByRuns } from "./state/artifacts.js";
238
+ // The validated synthesis record of a run as the flat `data-run-synthesis` part of the run stream, or null.
239
+ export { readRunSynthesis } from "./execution/run-synthesis.js";
218
240
  // The controlled vocabulary the profiler draws from, as data. A consumer rendering a
219
241
  // stored group needs the category's meaning, and the package owns the catalogue — a host
220
242
  // does not extend it.
@@ -277,12 +299,14 @@ export { createSandboxAgents, SANDBOX_AGENT_META } from "./agents/sandbox/index.
277
299
  // an embedder supplies the plan's step DATA and the harness composes each step's
278
300
  // seed at dispatch, when the step's dependencies have actually produced something.
279
301
  export { AnalysisPlanSchema } from "./schemas/workflow-state.js";
302
+ export { planGraphOf } from "./workflows/plan/graph.js";
303
+ export { planPartOf } from "./memory/card-builders.js";
280
304
  export { validatePlan } from "./schemas/validate-plan.js";
281
305
  // Plan + run + step ledgers around a trigger. `upsertPlan` takes a
282
306
  // caller-derived id and inserts-if-absent (the deterministic-id intake path);
283
307
  // the run-state calls mirror the chat trigger's reserve → dedup → status flow.
284
- export { upsertPlan, loadPlan } from "./state/plans.js";
285
- export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
308
+ export { upsertPlan, loadPlan, loadPlanRecord } from "./state/plans.js";
309
+ export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, countRunsByAnalysis, queryRunIdsByPlan, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
286
310
  export { queryStepsByRun } from "./state/step-executions.js";
287
311
  // Host-neutral reference-data catalog and shared installation receipts. Each
288
312
  // artifact names the third-party upstream it is fetched from; embedders own all
@@ -29,7 +29,7 @@ export interface NoticeFailure {
29
29
  readonly reason: string;
30
30
  }
31
31
  export type GateName = "RunAuthorizer.authorize" | "RunCharge.open" | "ArtifactRegistry.register" | "resolveRequestHeaders" | "resolveSandboxLabels";
32
- export type NoticeName = "RunAuthorizer.revoke" | "RunAuthorizer.revokeByJti" | "RunCharge.close" | "ArtifactRegistry.sync" | "UsageRecorder.record";
32
+ export type NoticeName = "RunAuthorizer.revoke" | "RunAuthorizer.revokeByJti" | "RunCharge.close" | "ArtifactRegistry.sync" | "UsageRecorder.record" | "notifyDataProfileSettled";
33
33
  export type GateRefusal = {
34
34
  readonly kind: "failed";
35
35
  readonly gate: GateName;
@@ -32,8 +32,8 @@
32
32
  * on the no-op provider for the life of the process.
33
33
  */
34
34
  import type { StepExecutionStatus } from "../state/schema.js";
35
- /** Terminal status of a run. A budget pause records as `canceled`, the same as the run ledger. */
36
- export type RunOutcome = "completed" | "partial" | "failed" | "canceled";
35
+ /** Terminal status of a run, the same as the run ledger. */
36
+ export type RunOutcome = "completed" | "partial" | "failed" | "canceled" | "suspended_insufficient_funds";
37
37
  /** The workflow family a run belongs to. */
38
38
  export type RunWorkflow = "analysis" | "data_profile";
39
39
  /** Terminal status of an executed step. A blocker is a failure to deliver, so `blocked` maps here to `failed`. */
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The call guard: a bound on repeated tool calls inside one agent run.
3
+ *
4
+ * A model that treats a tool answer as a state to poll calls the same tool
5
+ * with the same input again and again, or drifts through one query after
6
+ * another, until the iteration cap or the wall clock ends the run. A frontier
7
+ * model reads an "unavailable" answer once and continues; a smaller model
8
+ * does not. The guard makes that difference a host decision: the third call
9
+ * with an input the run already sent, the call past the budget of one tool,
10
+ * or the call past the budget of the whole run, answers a tool error that
11
+ * tells the model to continue with what it has. The service behind the tool never sees the refused call.
12
+ *
13
+ * The guard wraps a list of tools for ONE run. Build it where the tools of
14
+ * the run are built, never at construction of the host, thus the counters
15
+ * start at zero for every run. A terminal tool (one that ends the run) must
16
+ * not be wrapped: a refusal there would strand the run.
17
+ */
18
+ import type { Tool } from "../tools/define-tool.js";
19
+ export interface CallGuardPolicy {
20
+ /** The calls with one identical input that a run may make to one tool. The next one is refused. */
21
+ readonly identicalLimit: number;
22
+ /** The calls, with any input, that a run may make to one tool. The next one is refused. */
23
+ readonly perToolLimit: number;
24
+ /** The calls, with any input, that a run may make across every guarded tool. The next one is refused by every tool. */
25
+ readonly totalLimit: number;
26
+ }
27
+ /**
28
+ * Two identical calls let a model confirm an answer once. Twelve calls of one
29
+ * tool is above the largest count a frontier planner made in the Phase 0
30
+ * campaign (six and a half calls per plan across ALL tools) and far below the
31
+ * two hundred that a looping planner reached. Forty calls across every tool
32
+ * bounds the round over the tools that stays under each per-tool budget: in
33
+ * the 32-task campaign every submitted Sonnet plan but two needed fewer, and
34
+ * the one plan that ran to the wall clock made 62.
35
+ */
36
+ export declare const DEFAULT_CALL_GUARD: CallGuardPolicy;
37
+ /** A stable key for one input: the JSON text with the object keys sorted at every depth. */
38
+ export declare function canonicalInputKey(input: unknown): string;
39
+ export interface GuardedCallRefusal {
40
+ readonly tool: string;
41
+ readonly kind: "identical" | "budget" | "total";
42
+ readonly calls: number;
43
+ }
44
+ export interface CallGuardOptions {
45
+ readonly policy?: CallGuardPolicy;
46
+ /** Called on each refusal, for the log of the host. */
47
+ readonly onRefusal?: (refusal: GuardedCallRefusal) => void;
48
+ }
49
+ /** Wrap each tool so its `execute` obeys the policy for the life of the returned list. */
50
+ export declare function guardRepeatedCalls(tools: readonly Tool[], options?: CallGuardOptions): Tool[];
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The call guard: a bound on repeated tool calls inside one agent run.
3
+ *
4
+ * A model that treats a tool answer as a state to poll calls the same tool
5
+ * with the same input again and again, or drifts through one query after
6
+ * another, until the iteration cap or the wall clock ends the run. A frontier
7
+ * model reads an "unavailable" answer once and continues; a smaller model
8
+ * does not. The guard makes that difference a host decision: the third call
9
+ * with an input the run already sent, the call past the budget of one tool,
10
+ * or the call past the budget of the whole run, answers a tool error that
11
+ * tells the model to continue with what it has. The service behind the tool never sees the refused call.
12
+ *
13
+ * The guard wraps a list of tools for ONE run. Build it where the tools of
14
+ * the run are built, never at construction of the host, thus the counters
15
+ * start at zero for every run. A terminal tool (one that ends the run) must
16
+ * not be wrapped: a refusal there would strand the run.
17
+ */
18
+ import { err } from "neverthrow";
19
+ /**
20
+ * Two identical calls let a model confirm an answer once. Twelve calls of one
21
+ * tool is above the largest count a frontier planner made in the Phase 0
22
+ * campaign (six and a half calls per plan across ALL tools) and far below the
23
+ * two hundred that a looping planner reached. Forty calls across every tool
24
+ * bounds the round over the tools that stays under each per-tool budget: in
25
+ * the 32-task campaign every submitted Sonnet plan but two needed fewer, and
26
+ * the one plan that ran to the wall clock made 62.
27
+ */
28
+ export const DEFAULT_CALL_GUARD = { identicalLimit: 2, perToolLimit: 12, totalLimit: 40 };
29
+ /** A stable key for one input: the JSON text with the object keys sorted at every depth. */
30
+ export function canonicalInputKey(input) {
31
+ return JSON.stringify(sortKeys(input));
32
+ }
33
+ function sortKeys(value) {
34
+ if (Array.isArray(value))
35
+ return value.map(sortKeys);
36
+ if (value !== null && typeof value === "object") {
37
+ const out = {};
38
+ for (const key of Object.keys(value).sort())
39
+ out[key] = sortKeys(value[key]);
40
+ return out;
41
+ }
42
+ return value;
43
+ }
44
+ /** Wrap each tool so its `execute` obeys the policy for the life of the returned list. */
45
+ export function guardRepeatedCalls(tools, options = {}) {
46
+ const policy = options.policy ?? DEFAULT_CALL_GUARD;
47
+ let total = 0;
48
+ return tools.map((tool) => {
49
+ let calls = 0;
50
+ const byInput = new Map();
51
+ const guarded = {
52
+ ...tool,
53
+ ...(tool.describeCall ? { describeCall: tool.describeCall.bind(tool) } : {}),
54
+ ...(tool.describeResult ? { describeResult: tool.describeResult.bind(tool) } : {}),
55
+ async execute(input, ctx) {
56
+ calls += 1;
57
+ const key = canonicalInputKey(input);
58
+ const identical = (byInput.get(key) ?? 0) + 1;
59
+ byInput.set(key, identical);
60
+ if (identical > policy.identicalLimit) {
61
+ options.onRefusal?.({ tool: tool.id, kind: "identical", calls: identical });
62
+ return err({
63
+ error: `This run already called ${tool.id} with this exact input ${identical - 1} times, and the answer does not change within a run. ` +
64
+ "Use the answer you have and continue. If the answer was that a resource is absent, plan without it or ask for it; do not search again.",
65
+ retryable: false,
66
+ });
67
+ }
68
+ total += 1;
69
+ if (total > policy.totalLimit) {
70
+ options.onRefusal?.({ tool: tool.id, kind: "total", calls: total });
71
+ return err({
72
+ error: `This run made ${policy.totalLimit} calls across its search tools, which is the search budget of one run. ` +
73
+ "Submit the plan you have now. If a required resource is absent, ask for it; do not search again.",
74
+ retryable: false,
75
+ });
76
+ }
77
+ if (calls > policy.perToolLimit) {
78
+ options.onRefusal?.({ tool: tool.id, kind: "budget", calls });
79
+ return err({
80
+ error: `This run made ${policy.perToolLimit} calls of ${tool.id}, which is the budget of one run for one tool. ` +
81
+ "Continue with what you have. If a required resource is absent, ask for it; do not search again.",
82
+ retryable: false,
83
+ });
84
+ }
85
+ return tool.execute(input, ctx);
86
+ },
87
+ };
88
+ return guarded;
89
+ });
90
+ }