@archmax-ai/harness 0.0.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 (351) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/dist/agent.d.ts +174 -0
  4. package/dist/agent.js +140 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/assembly/compose.d.ts +170 -0
  7. package/dist/assembly/compose.js +391 -0
  8. package/dist/assembly/compose.js.map +1 -0
  9. package/dist/assembly/delegation-registry.d.ts +5 -0
  10. package/dist/assembly/delegation-registry.js +118 -0
  11. package/dist/assembly/delegation-registry.js.map +1 -0
  12. package/dist/assembly/index.d.ts +192 -0
  13. package/dist/assembly/index.js +265 -0
  14. package/dist/assembly/index.js.map +1 -0
  15. package/dist/assembly/plain.d.ts +7 -0
  16. package/dist/assembly/plain.js +41 -0
  17. package/dist/assembly/plain.js.map +1 -0
  18. package/dist/authoring-skill/archmax-harness/README.md +76 -0
  19. package/dist/authoring-skill/archmax-harness/SKILL.md +241 -0
  20. package/dist/authoring-skill/archmax-harness/references/backend-integration.md +626 -0
  21. package/dist/authoring-skill/archmax-harness/references/cases.md +41 -0
  22. package/dist/authoring-skill/archmax-harness/references/diagnostics.md +51 -0
  23. package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +1070 -0
  24. package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +82 -0
  25. package/dist/authoring-skill/archmax-harness/references/wiring.md +101 -0
  26. package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +1631 -0
  27. package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +241 -0
  28. package/dist/behaviour/support.d.ts +232 -0
  29. package/dist/behaviour/support.js +339 -0
  30. package/dist/behaviour/support.js.map +1 -0
  31. package/dist/cli/banner.d.ts +11 -0
  32. package/dist/cli/banner.js +29 -0
  33. package/dist/cli/banner.js.map +1 -0
  34. package/dist/cli/bootstrap.d.ts +33 -0
  35. package/dist/cli/bootstrap.js +153 -0
  36. package/dist/cli/bootstrap.js.map +1 -0
  37. package/dist/cli/command.d.ts +39 -0
  38. package/dist/cli/command.js +97 -0
  39. package/dist/cli/command.js.map +1 -0
  40. package/dist/cli/state-flow.d.ts +46 -0
  41. package/dist/cli/state-flow.js +268 -0
  42. package/dist/cli/state-flow.js.map +1 -0
  43. package/dist/cli/style.d.ts +41 -0
  44. package/dist/cli/style.js +64 -0
  45. package/dist/cli/style.js.map +1 -0
  46. package/dist/cli/turn.d.ts +41 -0
  47. package/dist/cli/turn.js +161 -0
  48. package/dist/cli/turn.js.map +1 -0
  49. package/dist/cli.d.ts +10 -0
  50. package/dist/cli.js +426 -0
  51. package/dist/cli.js.map +1 -0
  52. package/dist/core/code-description.d.ts +22 -0
  53. package/dist/core/code-description.js +38 -0
  54. package/dist/core/code-description.js.map +1 -0
  55. package/dist/core/deepagents.d.ts +183 -0
  56. package/dist/core/deepagents.js +128 -0
  57. package/dist/core/deepagents.js.map +1 -0
  58. package/dist/core/events.d.ts +350 -0
  59. package/dist/core/events.js +205 -0
  60. package/dist/core/events.js.map +1 -0
  61. package/dist/core/frontmatter.d.ts +15 -0
  62. package/dist/core/frontmatter.js +32 -0
  63. package/dist/core/frontmatter.js.map +1 -0
  64. package/dist/core/listing-redact.d.ts +37 -0
  65. package/dist/core/listing-redact.js +88 -0
  66. package/dist/core/listing-redact.js.map +1 -0
  67. package/dist/core/match.d.ts +26 -0
  68. package/dist/core/match.js +75 -0
  69. package/dist/core/match.js.map +1 -0
  70. package/dist/core/message-readers.d.ts +84 -0
  71. package/dist/core/message-readers.js +159 -0
  72. package/dist/core/message-readers.js.map +1 -0
  73. package/dist/core/messages.d.ts +49 -0
  74. package/dist/core/messages.js +85 -0
  75. package/dist/core/messages.js.map +1 -0
  76. package/dist/core/mount-prompt.d.ts +28 -0
  77. package/dist/core/mount-prompt.js +55 -0
  78. package/dist/core/mount-prompt.js.map +1 -0
  79. package/dist/core/mount-redact.d.ts +28 -0
  80. package/dist/core/mount-redact.js +32 -0
  81. package/dist/core/mount-redact.js.map +1 -0
  82. package/dist/core/mounts.d.ts +88 -0
  83. package/dist/core/mounts.js +147 -0
  84. package/dist/core/mounts.js.map +1 -0
  85. package/dist/core/path-mapping.d.ts +52 -0
  86. package/dist/core/path-mapping.js +113 -0
  87. package/dist/core/path-mapping.js.map +1 -0
  88. package/dist/core/platform-prompt.generated.d.ts +2 -0
  89. package/dist/core/platform-prompt.generated.js +4 -0
  90. package/dist/core/platform-prompt.generated.js.map +1 -0
  91. package/dist/core/prompt.d.ts +42 -0
  92. package/dist/core/prompt.js +75 -0
  93. package/dist/core/prompt.js.map +1 -0
  94. package/dist/core/session-id.d.ts +38 -0
  95. package/dist/core/session-id.js +61 -0
  96. package/dist/core/session-id.js.map +1 -0
  97. package/dist/core/session-store.d.ts +85 -0
  98. package/dist/core/session-store.js +149 -0
  99. package/dist/core/session-store.js.map +1 -0
  100. package/dist/core/session-zone.d.ts +54 -0
  101. package/dist/core/session-zone.js +82 -0
  102. package/dist/core/session-zone.js.map +1 -0
  103. package/dist/core/skill-prompt.d.ts +31 -0
  104. package/dist/core/skill-prompt.js +54 -0
  105. package/dist/core/skill-prompt.js.map +1 -0
  106. package/dist/core/skill-redact.d.ts +15 -0
  107. package/dist/core/skill-redact.js +27 -0
  108. package/dist/core/skill-redact.js.map +1 -0
  109. package/dist/core/skills.d.ts +172 -0
  110. package/dist/core/skills.js +214 -0
  111. package/dist/core/skills.js.map +1 -0
  112. package/dist/core/tool-mocks.d.ts +23 -0
  113. package/dist/core/tool-mocks.js +39 -0
  114. package/dist/core/tool-mocks.js.map +1 -0
  115. package/dist/core/tool-telemetry.d.ts +16 -0
  116. package/dist/core/tool-telemetry.js +40 -0
  117. package/dist/core/tool-telemetry.js.map +1 -0
  118. package/dist/core/usage.d.ts +114 -0
  119. package/dist/core/usage.js +226 -0
  120. package/dist/core/usage.js.map +1 -0
  121. package/dist/core/workspace-context.d.ts +136 -0
  122. package/dist/core/workspace-context.js +178 -0
  123. package/dist/core/workspace-context.js.map +1 -0
  124. package/dist/core/workspace-prompt.d.ts +7 -0
  125. package/dist/core/workspace-prompt.js +102 -0
  126. package/dist/core/workspace-prompt.js.map +1 -0
  127. package/dist/core/workspace-router.d.ts +61 -0
  128. package/dist/core/workspace-router.js +145 -0
  129. package/dist/core/workspace-router.js.map +1 -0
  130. package/dist/core/workspace.d.ts +85 -0
  131. package/dist/core/workspace.js +193 -0
  132. package/dist/core/workspace.js.map +1 -0
  133. package/dist/core/zones.d.ts +181 -0
  134. package/dist/core/zones.js +202 -0
  135. package/dist/core/zones.js.map +1 -0
  136. package/dist/env.d.ts +99 -0
  137. package/dist/env.js +271 -0
  138. package/dist/env.js.map +1 -0
  139. package/dist/index.d.ts +265 -0
  140. package/dist/index.js +197 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/kernel/kernel.d.ts +192 -0
  143. package/dist/kernel/kernel.js +751 -0
  144. package/dist/kernel/kernel.js.map +1 -0
  145. package/dist/lifecycle/hook-shape.d.ts +44 -0
  146. package/dist/lifecycle/hook-shape.js +66 -0
  147. package/dist/lifecycle/hook-shape.js.map +1 -0
  148. package/dist/lifecycle/runner.d.ts +177 -0
  149. package/dist/lifecycle/runner.js +372 -0
  150. package/dist/lifecycle/runner.js.map +1 -0
  151. package/dist/lifecycle/transcript.d.ts +27 -0
  152. package/dist/lifecycle/transcript.js +43 -0
  153. package/dist/lifecycle/transcript.js.map +1 -0
  154. package/dist/machine/allow.d.ts +65 -0
  155. package/dist/machine/allow.js +107 -0
  156. package/dist/machine/allow.js.map +1 -0
  157. package/dist/machine/delegation.d.ts +9 -0
  158. package/dist/machine/delegation.js +10 -0
  159. package/dist/machine/delegation.js.map +1 -0
  160. package/dist/machine/diagnostic.d.ts +14 -0
  161. package/dist/machine/diagnostic.js +2 -0
  162. package/dist/machine/diagnostic.js.map +1 -0
  163. package/dist/machine/lint-spec.d.ts +4 -0
  164. package/dist/machine/lint-spec.js +266 -0
  165. package/dist/machine/lint-spec.js.map +1 -0
  166. package/dist/machine/load-spec.d.ts +90 -0
  167. package/dist/machine/load-spec.js +120 -0
  168. package/dist/machine/load-spec.js.map +1 -0
  169. package/dist/machine/machine.d.ts +273 -0
  170. package/dist/machine/machine.js +551 -0
  171. package/dist/machine/machine.js.map +1 -0
  172. package/dist/machine/mount-grants.d.ts +25 -0
  173. package/dist/machine/mount-grants.js +24 -0
  174. package/dist/machine/mount-grants.js.map +1 -0
  175. package/dist/machine/slug.d.ts +14 -0
  176. package/dist/machine/slug.js +17 -0
  177. package/dist/machine/slug.js.map +1 -0
  178. package/dist/machine/spec-schema.d.ts +662 -0
  179. package/dist/machine/spec-schema.js +717 -0
  180. package/dist/machine/spec-schema.js.map +1 -0
  181. package/dist/machine/tool-names.d.ts +98 -0
  182. package/dist/machine/tool-names.js +144 -0
  183. package/dist/machine/tool-names.js.map +1 -0
  184. package/dist/machine/triggers.d.ts +114 -0
  185. package/dist/machine/triggers.js +165 -0
  186. package/dist/machine/triggers.js.map +1 -0
  187. package/dist/machine/types.d.ts +186 -0
  188. package/dist/machine/types.js +12 -0
  189. package/dist/machine/types.js.map +1 -0
  190. package/dist/machine/validate-spec.d.ts +42 -0
  191. package/dist/machine/validate-spec.js +29 -0
  192. package/dist/machine/validate-spec.js.map +1 -0
  193. package/dist/machine/variables.d.ts +196 -0
  194. package/dist/machine/variables.js +415 -0
  195. package/dist/machine/variables.js.map +1 -0
  196. package/dist/public/cli.d.ts +15 -0
  197. package/dist/public/cli.js +14 -0
  198. package/dist/public/cli.js.map +1 -0
  199. package/dist/public/messages.d.ts +33 -0
  200. package/dist/public/messages.js +33 -0
  201. package/dist/public/messages.js.map +1 -0
  202. package/dist/public/sandbox.d.ts +99 -0
  203. package/dist/public/sandbox.js +32 -0
  204. package/dist/public/sandbox.js.map +1 -0
  205. package/dist/public/spec.d.ts +147 -0
  206. package/dist/public/spec.js +149 -0
  207. package/dist/public/spec.js.map +1 -0
  208. package/dist/public/testing.d.ts +71 -0
  209. package/dist/public/testing.js +39 -0
  210. package/dist/public/testing.js.map +1 -0
  211. package/dist/rubrics/rubrics.d.ts +135 -0
  212. package/dist/rubrics/rubrics.js +145 -0
  213. package/dist/rubrics/rubrics.js.map +1 -0
  214. package/dist/runtime/contract.d.ts +73 -0
  215. package/dist/runtime/contract.js +96 -0
  216. package/dist/runtime/contract.js.map +1 -0
  217. package/dist/sandbox/assets/parts/core.js +22 -0
  218. package/dist/sandbox/assets/parts/lifecycle-hook.js +47 -0
  219. package/dist/sandbox/bridge.d.ts +16 -0
  220. package/dist/sandbox/bridge.js +95 -0
  221. package/dist/sandbox/bridge.js.map +1 -0
  222. package/dist/sandbox/executor.d.ts +46 -0
  223. package/dist/sandbox/executor.js +119 -0
  224. package/dist/sandbox/executor.js.map +1 -0
  225. package/dist/sandbox/imports.d.ts +33 -0
  226. package/dist/sandbox/imports.js +67 -0
  227. package/dist/sandbox/imports.js.map +1 -0
  228. package/dist/sandbox/prelude.d.ts +12 -0
  229. package/dist/sandbox/prelude.js +51 -0
  230. package/dist/sandbox/prelude.js.map +1 -0
  231. package/dist/sandbox/ptc-gateway.d.ts +104 -0
  232. package/dist/sandbox/ptc-gateway.js +167 -0
  233. package/dist/sandbox/ptc-gateway.js.map +1 -0
  234. package/dist/sandbox/runtime.d.ts +59 -0
  235. package/dist/sandbox/runtime.js +51 -0
  236. package/dist/sandbox/runtime.js.map +1 -0
  237. package/dist/sandbox/tools.d.ts +34 -0
  238. package/dist/sandbox/tools.js +147 -0
  239. package/dist/sandbox/tools.js.map +1 -0
  240. package/dist/sessions/operations.d.ts +79 -0
  241. package/dist/sessions/operations.js +121 -0
  242. package/dist/sessions/operations.js.map +1 -0
  243. package/dist/sessions/resolve.d.ts +97 -0
  244. package/dist/sessions/resolve.js +145 -0
  245. package/dist/sessions/resolve.js.map +1 -0
  246. package/dist/sessions/resume.d.ts +242 -0
  247. package/dist/sessions/resume.js +288 -0
  248. package/dist/sessions/resume.js.map +1 -0
  249. package/dist/sessions/scope.d.ts +99 -0
  250. package/dist/sessions/scope.js +145 -0
  251. package/dist/sessions/scope.js.map +1 -0
  252. package/dist/sessions/summary.d.ts +65 -0
  253. package/dist/sessions/summary.js +56 -0
  254. package/dist/sessions/summary.js.map +1 -0
  255. package/dist/testing/assertions.d.ts +30 -0
  256. package/dist/testing/assertions.js +231 -0
  257. package/dist/testing/assertions.js.map +1 -0
  258. package/dist/testing/case-document.d.ts +141 -0
  259. package/dist/testing/case-document.js +51 -0
  260. package/dist/testing/case-document.js.map +1 -0
  261. package/dist/testing/case-schema.d.ts +29 -0
  262. package/dist/testing/case-schema.js +519 -0
  263. package/dist/testing/case-schema.js.map +1 -0
  264. package/dist/testing/discovery.d.ts +10 -0
  265. package/dist/testing/discovery.js +24 -0
  266. package/dist/testing/discovery.js.map +1 -0
  267. package/dist/testing/driver.d.ts +78 -0
  268. package/dist/testing/driver.js +117 -0
  269. package/dist/testing/driver.js.map +1 -0
  270. package/dist/testing/grade.d.ts +116 -0
  271. package/dist/testing/grade.js +261 -0
  272. package/dist/testing/grade.js.map +1 -0
  273. package/dist/testing/interpreter.d.ts +55 -0
  274. package/dist/testing/interpreter.js +103 -0
  275. package/dist/testing/interpreter.js.map +1 -0
  276. package/dist/testing/mock-middleware.d.ts +5 -0
  277. package/dist/testing/mock-middleware.js +33 -0
  278. package/dist/testing/mock-middleware.js.map +1 -0
  279. package/dist/testing/runner.d.ts +205 -0
  280. package/dist/testing/runner.js +266 -0
  281. package/dist/testing/runner.js.map +1 -0
  282. package/dist/testing/target.d.ts +27 -0
  283. package/dist/testing/target.js +21 -0
  284. package/dist/testing/target.js.map +1 -0
  285. package/dist/validate/validate.d.ts +41 -0
  286. package/dist/validate/validate.js +669 -0
  287. package/dist/validate/validate.js.map +1 -0
  288. package/dist/workflow/agent-tools.d.ts +10 -0
  289. package/dist/workflow/agent-tools.js +10 -0
  290. package/dist/workflow/agent-tools.js.map +1 -0
  291. package/dist/workflow/checkpointer.d.ts +52 -0
  292. package/dist/workflow/checkpointer.js +200 -0
  293. package/dist/workflow/checkpointer.js.map +1 -0
  294. package/dist/workflow/control-tools.d.ts +174 -0
  295. package/dist/workflow/control-tools.js +468 -0
  296. package/dist/workflow/control-tools.js.map +1 -0
  297. package/dist/workflow/governance.d.ts +104 -0
  298. package/dist/workflow/governance.js +546 -0
  299. package/dist/workflow/governance.js.map +1 -0
  300. package/dist/workflow/middleware.d.ts +85 -0
  301. package/dist/workflow/middleware.js +283 -0
  302. package/dist/workflow/middleware.js.map +1 -0
  303. package/dist/workflow/on-error.d.ts +25 -0
  304. package/dist/workflow/on-error.js +58 -0
  305. package/dist/workflow/on-error.js.map +1 -0
  306. package/dist/workflow/parks.d.ts +46 -0
  307. package/dist/workflow/parks.js +513 -0
  308. package/dist/workflow/parks.js.map +1 -0
  309. package/dist/workflow/paths.d.ts +67 -0
  310. package/dist/workflow/paths.js +108 -0
  311. package/dist/workflow/paths.js.map +1 -0
  312. package/dist/workflow/prompt-cache.d.ts +70 -0
  313. package/dist/workflow/prompt-cache.js +103 -0
  314. package/dist/workflow/prompt-cache.js.map +1 -0
  315. package/dist/workflow/prompt-pruning.d.ts +20 -0
  316. package/dist/workflow/prompt-pruning.js +83 -0
  317. package/dist/workflow/prompt-pruning.js.map +1 -0
  318. package/dist/workflow/render-prompt.d.ts +45 -0
  319. package/dist/workflow/render-prompt.js +140 -0
  320. package/dist/workflow/render-prompt.js.map +1 -0
  321. package/dist/workflow/runtime-config.d.ts +7 -0
  322. package/dist/workflow/runtime-config.js +9 -0
  323. package/dist/workflow/runtime-config.js.map +1 -0
  324. package/dist/workflow/session-artifacts.d.ts +90 -0
  325. package/dist/workflow/session-artifacts.js +134 -0
  326. package/dist/workflow/session-artifacts.js.map +1 -0
  327. package/dist/workflow/session-view.d.ts +31 -0
  328. package/dist/workflow/session-view.js +94 -0
  329. package/dist/workflow/session-view.js.map +1 -0
  330. package/dist/workflow/snapshot.d.ts +14 -0
  331. package/dist/workflow/snapshot.js +97 -0
  332. package/dist/workflow/snapshot.js.map +1 -0
  333. package/dist/workflow/state.d.ts +347 -0
  334. package/dist/workflow/state.js +322 -0
  335. package/dist/workflow/state.js.map +1 -0
  336. package/dist/workflow/sub-workflow.d.ts +174 -0
  337. package/dist/workflow/sub-workflow.js +392 -0
  338. package/dist/workflow/sub-workflow.js.map +1 -0
  339. package/dist/workflow/tool-service.d.ts +48 -0
  340. package/dist/workflow/tool-service.js +291 -0
  341. package/dist/workflow/tool-service.js.map +1 -0
  342. package/dist/workflow/turn-boundary.d.ts +39 -0
  343. package/dist/workflow/turn-boundary.js +128 -0
  344. package/dist/workflow/turn-boundary.js.map +1 -0
  345. package/dist/workflow/turn-runner.d.ts +40 -0
  346. package/dist/workflow/turn-runner.js +122 -0
  347. package/dist/workflow/turn-runner.js.map +1 -0
  348. package/dist/workflow/workflow-tools.d.ts +46 -0
  349. package/dist/workflow/workflow-tools.js +90 -0
  350. package/dist/workflow/workflow-tools.js.map +1 -0
  351. package/package.json +119 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The host-side session engine cases drive the agent through. One engine per
3
+ * case; it owns the case's session on the agent under test, its declared start
4
+ * conditions (trigger, variables, seeded workspace files), and its latest turn
5
+ * view. A thin caller of `agent.workflow.send`: where a message lands — a turn,
6
+ * a reply to a session a person holds — is the session's business, decided by
7
+ * the runtime exactly as it is for a host.
8
+ */
9
+ import type { GovernedAgent } from "../agent.js";
10
+ import { type SessionView } from "../workflow/session-view.js";
11
+ import type { ToolMockSpec } from "../core/tool-mocks.js";
12
+ /** The session name steps without a `session:` key drive. */
13
+ export declare const DEFAULT_SESSION_ID = "default";
14
+ export interface DriveSession {
15
+ /** The session's name as the case declares it (`session:` on a step). */
16
+ name: string;
17
+ /**
18
+ * The id the runtime addresses this session by — namespaced per case, since one
19
+ * agent serves every case. One id: the runtime has no separate session beneath it.
20
+ */
21
+ sessionId: string;
22
+ /** The validated trigger every driven turn of this session starts from. */
23
+ trigger?: {
24
+ id: string;
25
+ };
26
+ /** Variables seeded into every driven turn of this session, locked by the runtime. */
27
+ variables?: Record<string, unknown>;
28
+ /** The session's latest turn view (empty until it first drives). */
29
+ lastView: SessionView;
30
+ }
31
+ export interface SessionStartConditions {
32
+ trigger?: {
33
+ id: string;
34
+ };
35
+ /**
36
+ * Variables seeded for this session, supplied per turn rather than at
37
+ * assembly: one agent serves every case of a suite, so a case's seeds cannot
38
+ * ride the assembly-time `variables` seeds.
39
+ */
40
+ variables?: Record<string, unknown>;
41
+ /** Workspace files seeded into the session before any turn. */
42
+ files?: Record<string, unknown>;
43
+ }
44
+ /**
45
+ * The refusal for a sub-workflow mock against a target that cannot delegate.
46
+ * Separate from the interception refusal because the fix is different: this one
47
+ * is not about middleware but about the target being workflow-governed at all.
48
+ */
49
+ export declare function unDelegatableMocksMessage(): string;
50
+ /** The refusal for mocks a target cannot intercept (single definition). */
51
+ export declare function unInterceptableMocksMessage(mockCount: number): string;
52
+ export interface SessionEngine {
53
+ /** Open a session, validating and applying its declared start conditions. */
54
+ open(sessionId: string, conditions?: SessionStartConditions): Promise<DriveSession>;
55
+ /** Drive one user turn on a session under its declared trigger. */
56
+ send(sessionId: string, message: string, toolMocks?: ToolMockSpec[]): Promise<SessionView>;
57
+ /** Resume a session parked at a human state with the person's chosen edge. */
58
+ decide(sessionId: string, to: string, comment?: string): Promise<SessionView>;
59
+ /**
60
+ * Resume a session parked awaiting an event with a delivered firing; the
61
+ * session continues in the state it parked in. Fails the case the way
62
+ * production fails the host — the session was not parked — rather than
63
+ * silently starting a new turn.
64
+ */
65
+ deliver(sessionId: string, trigger: string, variables?: Record<string, unknown>): Promise<SessionView>;
66
+ }
67
+ /**
68
+ * Create the per-case session engine. The default session runs on
69
+ * `defaultSessionId` (caller-nameable via `sessionIdForCase`); every other
70
+ * session's id derives from it, so naming the default names the whole case and
71
+ * no session is a timestamped orphan.
72
+ */
73
+ export declare function createSessionEngine(opts: {
74
+ agent: GovernedAgent;
75
+ defaultSessionId: string;
76
+ /** Aborts every run this engine drives — the case's wall-clock budget. */
77
+ signal?: AbortSignal;
78
+ }): SessionEngine;
@@ -0,0 +1,117 @@
1
+ import { messagesToSessionView } from "../workflow/session-view.js";
2
+ /** The session name steps without a `session:` key drive. */
3
+ export const DEFAULT_SESSION_ID = "default";
4
+ /**
5
+ * Validate-then-seed-then-store for a session's declared start conditions.
6
+ * Fails closed before any turn: an unknown trigger id surfaces
7
+ * `UnknownTriggerError` with the declared triggers, and declared files against
8
+ * a target without a `sessions.seed` handle are refused rather than skipped.
9
+ */
10
+ async function applyStartConditions(agent, session, input) {
11
+ if (input.trigger) {
12
+ agent.workflow.resolveTrigger(input.trigger);
13
+ session.trigger = input.trigger;
14
+ }
15
+ if (input.variables && Object.keys(input.variables).length > 0) {
16
+ session.variables = { ...input.variables };
17
+ }
18
+ const files = input.files ?? {};
19
+ if (Object.keys(files).length > 0) {
20
+ if (typeof agent.sessions?.seed !== "function") {
21
+ throw new Error(`this case declares ${Object.keys(files).length} workspace file(s), but the target ` +
22
+ `runtime's session handles expose no 'seed' operation, so nothing can be placed in the ` +
23
+ `session workspace before the agent runs. Supply a target whose sessions handle can seed ` +
24
+ `(createAgent provides one).`);
25
+ }
26
+ await agent.sessions.seed(session.sessionId, files);
27
+ }
28
+ }
29
+ /**
30
+ * The refusal for a sub-workflow mock against a target that cannot delegate.
31
+ * Separate from the interception refusal because the fix is different: this one
32
+ * is not about middleware but about the target being workflow-governed at all.
33
+ */
34
+ export function unDelegatableMocksMessage() {
35
+ return (`this case mocks a sub-workflow, but the target agent is not workflow-governed ` +
36
+ `(agent.workflow is undefined) — it cannot dispatch sub-workflows, so the mock would ` +
37
+ `never be consulted. Run the case against a workflow-governed target.`);
38
+ }
39
+ /** The refusal for mocks a target cannot intercept (single definition). */
40
+ export function unInterceptableMocksMessage(mockCount) {
41
+ return (`this case declares ${mockCount} tool mock(s), but the target agent ` +
42
+ `cannot intercept agent-initiated tool calls (interception would be partial: ` +
43
+ `scripts' PTC calls mocked, the agent's own calls real). Wire ` +
44
+ `createToolMockMiddleware() into the target's middleware, or set ` +
45
+ `toolMocks on a target that intercepts by other means.`);
46
+ }
47
+ /** The view of one settled {@link Outcome}, as assertions read it. */
48
+ function viewOf(session, outcome) {
49
+ const view = messagesToSessionView(outcome.messages, session.name, outcome.auditTrail, outcome.variables);
50
+ if (outcome.kind === "parked") {
51
+ view.parked = true;
52
+ if (outcome.parkedChannel)
53
+ view.parkedChannel = outcome.parkedChannel;
54
+ if (outcome.state)
55
+ view.node = outcome.state;
56
+ }
57
+ return view;
58
+ }
59
+ /**
60
+ * Create the per-case session engine. The default session runs on
61
+ * `defaultSessionId` (caller-nameable via `sessionIdForCase`); every other
62
+ * session's id derives from it, so naming the default names the whole case and
63
+ * no session is a timestamped orphan.
64
+ */
65
+ export function createSessionEngine(opts) {
66
+ const { agent, defaultSessionId } = opts;
67
+ const runConfig = opts.signal ? { signal: opts.signal } : {};
68
+ const sessions = new Map();
69
+ function get(sessionId) {
70
+ const session = sessions.get(sessionId);
71
+ if (!session) {
72
+ throw new Error(`unknown session '${sessionId}' (opened: ${[...sessions.keys()].join(", ") || "none"})`);
73
+ }
74
+ return session;
75
+ }
76
+ async function settle(session, outcome) {
77
+ const view = viewOf(session, await outcome);
78
+ session.lastView = view;
79
+ return view;
80
+ }
81
+ return {
82
+ async open(name, conditions = {}) {
83
+ if (sessions.has(name))
84
+ throw new Error(`session '${name}' is already open`);
85
+ const session = {
86
+ name,
87
+ sessionId: name === DEFAULT_SESSION_ID ? defaultSessionId : `${defaultSessionId}-${name}`,
88
+ lastView: messagesToSessionView([], name),
89
+ };
90
+ await applyStartConditions(agent, session, conditions);
91
+ sessions.set(name, session);
92
+ return session;
93
+ },
94
+ async send(sessionId, message, toolMocks = []) {
95
+ const session = get(sessionId);
96
+ // Every turn of a session runs under the session's declared trigger and
97
+ // seeds — consistency by construction: a send step carries neither, so a
98
+ // follow-up message cannot restart the session at a different start state.
99
+ // Mocks ride the config, the one per-invocation channel; the runtime
100
+ // decides whether this is a turn or a reply to a session a person holds.
101
+ return settle(session, agent.workflow.send(session.sessionId, {
102
+ message,
103
+ ...(session.trigger ? { trigger: session.trigger } : {}),
104
+ ...(session.variables ? { variables: session.variables } : {}),
105
+ }, { ...runConfig, configurable: { __toolMocks: toolMocks } }));
106
+ },
107
+ async deliver(sessionId, trigger, variables) {
108
+ const session = get(sessionId);
109
+ return settle(session, agent.workflow.send(session.sessionId, { delivery: { trigger: { id: trigger }, ...(variables ? { variables } : {}) } }, runConfig));
110
+ },
111
+ async decide(sessionId, to, comment) {
112
+ const session = get(sessionId);
113
+ return settle(session, agent.workflow.send(session.sessionId, { decision: { target: to, ...(comment ? { comment } : {}) } }, runConfig));
114
+ },
115
+ };
116
+ }
117
+ //# sourceMappingURL=driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.js","sourceRoot":"","sources":["../../src/testing/driver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The grader behind a `grade:` assertion.
3
+ *
4
+ * Two halves of one job: the **evidence** a graded turn is shown — what the
5
+ * session said, separated from what it did, bounded with visible elision — and
6
+ * the **prompt** that grades it against the case's stated expectation. They are
7
+ * one module because the evidence exists only to be graded; nothing else reads it.
8
+ *
9
+ * A grade is a soft threshold, not a gate: it scores, and the case's declared bar
10
+ * decides pass or fail.
11
+ */
12
+ import type { SessionView, ToolCallFact } from "../workflow/session-view.js";
13
+ import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
14
+ import { type ModelFactory } from "../env.js";
15
+ import type { WorkflowTestsConfig } from "../machine/types.js";
16
+ /**
17
+ * The evidence an offline-test `judge:` assertion hands the grading model:
18
+ * the turn's final reply *and* a chronological record of how the run reached
19
+ * it. Grading only the last assistant message grades prose — a criterion about
20
+ * what the agent *did* ("looked up the order before answering", "never fetched
21
+ * another customer's record") has nothing to read.
22
+ *
23
+ * Building and rendering are separate on purpose: composition (ordering,
24
+ * call/output pairing, bounding) is asserted against a plain object, and only
25
+ * the final string formatting is prompt-shaped.
26
+ */
27
+ export type JudgeEvidenceEntry = {
28
+ kind: "message";
29
+ text: string;
30
+ }
31
+ /** Something the runtime wrote into the transcript: a decision, a delivered event, an error route. */
32
+ | {
33
+ kind: "runtime";
34
+ note: string;
35
+ text: string;
36
+ } | {
37
+ kind: "tool";
38
+ name: string;
39
+ input: unknown;
40
+ output?: unknown;
41
+ status: ToolCallFact["status"];
42
+ };
43
+ export interface JudgeEvidence {
44
+ /** The turn's final assistant reply — what the user was told. */
45
+ reply: string;
46
+ /**
47
+ * The turn in order: assistant messages and tool calls interleaved as they
48
+ * happened. The final reply is not repeated here when it closed the turn —
49
+ * it is rendered in its own section.
50
+ */
51
+ record: JudgeEvidenceEntry[];
52
+ }
53
+ /**
54
+ * Per-value character cap for a rendered tool input, tool output, or record
55
+ * message. The reply is deliberately exempt (see {@link renderJudgeEvidence}).
56
+ */
57
+ export declare const MAX_VALUE_CHARS = 800;
58
+ /** Total character budget for the rendered record section. */
59
+ export declare const MAX_RECORD_CHARS = 12000;
60
+ /**
61
+ * Derive judge evidence from the view the step's other assertions evaluate
62
+ * against, so a `judge` and a `calledTool` on the same step can never disagree
63
+ * about what the run did.
64
+ *
65
+ * Order comes from `events` (chronological, but with no call→output pairing)
66
+ * and pairing from `toolCalls` (paired by tool-call id, but flat). Both are
67
+ * built in the same pass of `messagesToSessionView`, so the nth `tool.called` event
68
+ * is `toolCalls[n]` — reading them together is exact, where re-pairing the
69
+ * events by name here would re-implement that matching less accurately.
70
+ */
71
+ export declare function judgeEvidenceFromView(view: SessionView): JudgeEvidence;
72
+ /**
73
+ * Render evidence for the grading prompt. Every reduction is visible: a capped
74
+ * value names the characters it dropped and a record trimmed to its budget
75
+ * opens with the number of entries omitted. A judge that silently received
76
+ * less than the run produced would grade a run nobody can reconstruct.
77
+ */
78
+ export declare function renderJudgeEvidence(evidence: JudgeEvidence): string;
79
+ /**
80
+ * What the judge model said: a score in `[0, 1]` and its reasoning. There is
81
+ * deliberately no pass/fail here — the case's declared `atLeast` decides that,
82
+ * in `assertions.ts`, so a record can never contradict the verdict beside it.
83
+ */
84
+ export interface JudgeResult {
85
+ score: number;
86
+ reason?: string;
87
+ }
88
+ /**
89
+ * Resolve the judge model through the model factory's `judge` role (default:
90
+ * the env-configured model). `tests.judge.model` and `tests.judge.modelOptions`
91
+ * (`temperature`, `maxTokens`) are applied over the environment the factory is
92
+ * handed, so the default factory builds exactly the grader the spec names; a
93
+ * factory that brings its own model ignores the env, and these with it.
94
+ *
95
+ * The environment is loaded lazily (same shape as the harness): a factory that
96
+ * carries its own credentials never reads it, so a host with no `ARCHMAX_*`
97
+ * variables can still grade a `judge:` expectation.
98
+ */
99
+ export declare function createJudgeModel(config: WorkflowTestsConfig, modelFactory?: ModelFactory): BaseChatModel;
100
+ /**
101
+ * Grade one closed-QA criterion against a turn's run evidence (the offline-test
102
+ * judge). The evidence carries what the assistant *did* as well as what it
103
+ * said, so the two are weighed differently in the prompt: a criterion about
104
+ * what the user was told is met only by the final reply, while a criterion
105
+ * about behaviour may be met by the tool record. Without that split, adding the
106
+ * record would let "tells the customer the refund is approved" pass on the
107
+ * strength of an `approve_refund` call the customer never heard about.
108
+ */
109
+ export declare function gradeClosedQA(model: BaseChatModel, criteria: string, evidence: JudgeEvidence): Promise<JudgeResult>;
110
+ /**
111
+ * Read the verdict out of a grader's reply. The reply is scanned for balanced
112
+ * `{...}` blocks — so prose around the JSON, a markdown fence, or a brace
113
+ * inside the prose (`the reply {mentions} …`) is tolerated — and the first
114
+ * block that parses as an object wins. `null` when nothing parses.
115
+ */
116
+ export declare function parseVerdict(raw: string): JudgeResult | null;
@@ -0,0 +1,261 @@
1
+ import { HumanMessage, SystemMessage } from "@langchain/core/messages";
2
+ import { defaultModelFactory, loadEnv } from "../env.js";
3
+ /**
4
+ * Per-value character cap for a rendered tool input, tool output, or record
5
+ * message. The reply is deliberately exempt (see {@link renderJudgeEvidence}).
6
+ */
7
+ export const MAX_VALUE_CHARS = 800;
8
+ /** Total character budget for the rendered record section. */
9
+ export const MAX_RECORD_CHARS = 12_000;
10
+ /**
11
+ * Derive judge evidence from the view the step's other assertions evaluate
12
+ * against, so a `judge` and a `calledTool` on the same step can never disagree
13
+ * about what the run did.
14
+ *
15
+ * Order comes from `events` (chronological, but with no call→output pairing)
16
+ * and pairing from `toolCalls` (paired by tool-call id, but flat). Both are
17
+ * built in the same pass of `messagesToSessionView`, so the nth `tool.called` event
18
+ * is `toolCalls[n]` — reading them together is exact, where re-pairing the
19
+ * events by name here would re-implement that matching less accurately.
20
+ */
21
+ export function judgeEvidenceFromView(view) {
22
+ const record = [];
23
+ let nthCall = 0;
24
+ for (const event of view.events) {
25
+ if (event.type === "message.completed") {
26
+ const text = String(event.data?.message ?? "");
27
+ if (text)
28
+ record.push({ kind: "message", text });
29
+ continue;
30
+ }
31
+ if (event.type === "runtime.note") {
32
+ const text = String(event.data?.text ?? "");
33
+ if (text)
34
+ record.push({ kind: "runtime", note: String(event.data?.kind ?? ""), text });
35
+ continue;
36
+ }
37
+ if (event.type !== "tool.called")
38
+ continue;
39
+ // `tool.completed` carries no id, so its output is read from the paired
40
+ // fact instead — the event only marks position, which is already known.
41
+ const fact = view.toolCalls[nthCall++];
42
+ record.push({
43
+ kind: "tool",
44
+ name: fact?.name ?? String(event.data?.name ?? ""),
45
+ input: fact ? fact.input : event.data?.input,
46
+ ...(fact?.output !== undefined ? { output: fact.output } : {}),
47
+ status: fact?.status ?? "pending",
48
+ });
49
+ }
50
+ const reply = view.reply ?? "";
51
+ // The closing message is the graded reply; it gets its own section, so
52
+ // carrying it in the record too would spend budget saying it twice. Only
53
+ // when it *closed* the turn — a reply followed by tool calls stays in place,
54
+ // because "said this, then did that" is the ordering the judge needs.
55
+ const last = record[record.length - 1];
56
+ if (reply && last?.kind === "message" && last.text === reply)
57
+ record.pop();
58
+ return { reply, record };
59
+ }
60
+ /**
61
+ * Render evidence for the grading prompt. Every reduction is visible: a capped
62
+ * value names the characters it dropped and a record trimmed to its budget
63
+ * opens with the number of entries omitted. A judge that silently received
64
+ * less than the run produced would grade a run nobody can reconstruct.
65
+ */
66
+ export function renderJudgeEvidence(evidence) {
67
+ return [
68
+ "What the assistant did, in order:",
69
+ renderRecord(evidence.record),
70
+ "",
71
+ "Final reply to the user:",
72
+ evidence.reply || "(empty)",
73
+ ].join("\n");
74
+ }
75
+ function renderRecord(record) {
76
+ if (record.length === 0)
77
+ return "(no tool calls or intermediate messages recorded)";
78
+ // Numbered by original position, so the numbering stays coherent with the
79
+ // omission marker when the oldest entries are dropped.
80
+ const lines = record.map((entry, i) => `${i + 1}. ${renderEntry(entry)}`);
81
+ // Oldest-first dropping: the graded reply is the end of the turn, so the
82
+ // work nearest it is what a criterion most often concerns.
83
+ let kept = 0;
84
+ let total = 0;
85
+ for (let i = lines.length - 1; i >= 0; i--) {
86
+ const next = total + lines[i].length + 1;
87
+ if (kept > 0 && next > MAX_RECORD_CHARS)
88
+ break;
89
+ total = next;
90
+ kept += 1;
91
+ }
92
+ const dropped = lines.length - kept;
93
+ const body = lines.slice(dropped);
94
+ return dropped > 0
95
+ ? [
96
+ `… ${dropped} earlier ${dropped === 1 ? "entry" : "entries"} omitted (evidence budget)`,
97
+ ...body,
98
+ ].join("\n")
99
+ : body.join("\n");
100
+ }
101
+ function renderEntry(entry) {
102
+ if (entry.kind === "message")
103
+ return `[message] ${cap(entry.text)}`;
104
+ if (entry.kind === "runtime")
105
+ return `[runtime:${entry.note}] ${cap(entry.text)}`;
106
+ const output = entry.output === undefined ? "(no output recorded)" : `output=${cap(stringify(entry.output))}`;
107
+ return `[tool] ${entry.name} input=${cap(stringify(entry.input))} → ${entry.status} ${output}`;
108
+ }
109
+ function stringify(value) {
110
+ if (typeof value === "string")
111
+ return value;
112
+ try {
113
+ return JSON.stringify(value) ?? String(value);
114
+ }
115
+ catch {
116
+ return String(value);
117
+ }
118
+ }
119
+ function cap(text) {
120
+ if (text.length <= MAX_VALUE_CHARS)
121
+ return text;
122
+ return `${text.slice(0, MAX_VALUE_CHARS)} …(+${text.length - MAX_VALUE_CHARS} chars)`;
123
+ }
124
+ /**
125
+ * Resolve the judge model through the model factory's `judge` role (default:
126
+ * the env-configured model). `tests.judge.model` and `tests.judge.modelOptions`
127
+ * (`temperature`, `maxTokens`) are applied over the environment the factory is
128
+ * handed, so the default factory builds exactly the grader the spec names; a
129
+ * factory that brings its own model ignores the env, and these with it.
130
+ *
131
+ * The environment is loaded lazily (same shape as the harness): a factory that
132
+ * carries its own credentials never reads it, so a host with no `ARCHMAX_*`
133
+ * variables can still grade a `judge:` expectation.
134
+ */
135
+ export function createJudgeModel(config, modelFactory = defaultModelFactory) {
136
+ const { model, modelOptions = {} } = config.judge ?? {};
137
+ let cachedEnv;
138
+ const env = () => (cachedEnv ??= {
139
+ ...loadEnv(),
140
+ ...(model ? { model } : {}),
141
+ ...(modelOptions.temperature !== undefined ? { temperature: modelOptions.temperature } : {}),
142
+ ...(modelOptions.maxTokens !== undefined ? { maxTokens: modelOptions.maxTokens } : {}),
143
+ });
144
+ return modelFactory("judge", env);
145
+ }
146
+ /**
147
+ * Grade one closed-QA criterion against a turn's run evidence (the offline-test
148
+ * judge). The evidence carries what the assistant *did* as well as what it
149
+ * said, so the two are weighed differently in the prompt: a criterion about
150
+ * what the user was told is met only by the final reply, while a criterion
151
+ * about behaviour may be met by the tool record. Without that split, adding the
152
+ * record would let "tells the customer the refund is approved" pass on the
153
+ * strength of an `approve_refund` call the customer never heard about.
154
+ */
155
+ export async function gradeClosedQA(model, criteria, evidence) {
156
+ const messages = [
157
+ new SystemMessage([
158
+ "You grade whether an assistant's turn satisfies a yes/no criterion.",
159
+ "You are given a chronological record of the turn — the assistant's messages, the tool calls it made with their inputs, outputs and status, and the runtime's own notes (marked [runtime:<kind>]: a person's decision that routed the run, an event delivered to it, an error route) — followed by the assistant's final reply to the user.",
160
+ "Weigh them differently: a criterion about what the user was told is met only by the final reply, while a criterion about what the assistant did, or what happened to the run, may be met by the record.",
161
+ 'Reply with JSON only: {"score": number between 0 and 1, "pass": boolean, "reason": string}',
162
+ "score 1 means the criterion is fully met; 0 means it is not met at all.",
163
+ // The reason is printed beside a verdict line, so it has to fit there.
164
+ // A bound in the prompt, not a truncation: cutting a model's
165
+ // explanation mid-clause reads worse than asking for a short one.
166
+ "Keep reason to 1-3 sentences: state the judgment and the evidence for it, nothing more.",
167
+ ].join(" ")),
168
+ new HumanMessage(`Criterion: ${criteria}\n\n${renderJudgeEvidence(evidence)}`),
169
+ ];
170
+ const first = await model.invoke(messages);
171
+ const verdict = parseVerdict(responseText(first));
172
+ if (verdict)
173
+ return verdict;
174
+ // A grader that wrapped or mangled its JSON gets exactly one reminder before
175
+ // its turn scores 0: a real model run failed a case this way, and re-asking
176
+ // is cheaper than a false failure. The retry carries the first reply, so the
177
+ // model corrects its own answer rather than grading afresh.
178
+ const retry = await model.invoke([
179
+ ...messages,
180
+ first,
181
+ new HumanMessage("Your previous reply could not be read as JSON. Reply with the JSON object only — " +
182
+ 'no prose, no code fences: {"score": number, "pass": boolean, "reason": string}'),
183
+ ]);
184
+ return (parseVerdict(responseText(retry)) ?? {
185
+ score: 0,
186
+ reason: "grader returned no readable JSON verdict",
187
+ });
188
+ }
189
+ /** The textual content of a model reply, whatever shape the provider used. */
190
+ function responseText(response) {
191
+ const { content } = response;
192
+ if (typeof content === "string")
193
+ return content;
194
+ if (Array.isArray(content)) {
195
+ return content
196
+ .map((p) => (typeof p === "string" ? p : (p.text ?? "")))
197
+ .join("");
198
+ }
199
+ return String(content ?? "");
200
+ }
201
+ /**
202
+ * Read the verdict out of a grader's reply. The reply is scanned for balanced
203
+ * `{...}` blocks — so prose around the JSON, a markdown fence, or a brace
204
+ * inside the prose (`the reply {mentions} …`) is tolerated — and the first
205
+ * block that parses as an object wins. `null` when nothing parses.
206
+ */
207
+ export function parseVerdict(raw) {
208
+ for (const block of balancedObjects(raw)) {
209
+ try {
210
+ const parsed = JSON.parse(block);
211
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
212
+ continue;
213
+ // The prompt asks the model for its own `pass` boolean (unchanged
214
+ // wording, so grading stays calibrated); it is not read — the declared
215
+ // `atLeast` is the only bar.
216
+ const { score, reason } = parsed;
217
+ return {
218
+ score: Math.max(0, Math.min(1, Number(score ?? 0))),
219
+ reason: typeof reason === "string" ? reason : undefined,
220
+ };
221
+ }
222
+ catch {
223
+ // Not JSON: try the next candidate.
224
+ }
225
+ }
226
+ return null;
227
+ }
228
+ /** Every balanced `{...}` block in `raw`, in order of its opening brace. */
229
+ function* balancedObjects(raw) {
230
+ let start = raw.indexOf("{");
231
+ while (start !== -1) {
232
+ let depth = 0;
233
+ let inString = false;
234
+ let escaped = false;
235
+ let end = -1;
236
+ for (let i = start; i < raw.length; i++) {
237
+ const ch = raw[i];
238
+ if (inString) {
239
+ if (escaped)
240
+ escaped = false;
241
+ else if (ch === "\\")
242
+ escaped = true;
243
+ else if (ch === '"')
244
+ inString = false;
245
+ continue;
246
+ }
247
+ if (ch === '"')
248
+ inString = true;
249
+ else if (ch === "{")
250
+ depth += 1;
251
+ else if (ch === "}" && --depth === 0) {
252
+ end = i;
253
+ break;
254
+ }
255
+ }
256
+ if (end !== -1)
257
+ yield raw.slice(start, end + 1);
258
+ start = raw.indexOf("{", start + 1);
259
+ }
260
+ }
261
+ //# sourceMappingURL=grade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grade.js","sourceRoot":"","sources":["../../src/testing/grade.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAiB,mBAAmB,EAAE,OAAO,EAAqB,MAAM,WAAW,CAAC;AAsC3F;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAEnC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAiB;IACrD,MAAM,MAAM,GAAyB,EAAE,CAAC;IACxC,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;YAC/C,IAAI,IAAI;gBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YACvF,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa;YAAE,SAAS;QAC3C,wEAAwE;QACxE,wEAAwE;QACxE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;YAClD,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK;YAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,SAAS;SAClC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,uEAAuE;IACvE,yEAAyE;IACzE,6EAA6E;IAC7E,sEAAsE;IACtE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvC,IAAI,KAAK,IAAI,IAAI,EAAE,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK;QAAE,MAAM,CAAC,GAAG,EAAE,CAAC;IAE3E,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAuB;IACzD,OAAO;QACL,mCAAmC;QACnC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC7B,EAAE;QACF,0BAA0B;QAC1B,QAAQ,CAAC,KAAK,IAAI,SAAS;KAC5B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAqC;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,mDAAmD,CAAC;IAEpF,0EAA0E;IAC1E,uDAAuD;IACvD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAE1E,yEAAyE;IACzE,2DAA2D;IAC3D,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACzC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,gBAAgB;YAAE,MAAM;QAC/C,KAAK,GAAG,IAAI,CAAC;QACb,IAAI,IAAI,CAAC,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IACpC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,GAAG,CAAC;QAChB,CAAC,CAAC;YACE,KAAK,OAAO,YAAY,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,4BAA4B;YACvF,GAAG,IAAI;SACR,CAAC,IAAI,CAAC,IAAI,CAAC;QACd,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,SAAS,WAAW,CAAC,KAAyB;IAC5C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,aAAa,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IACpE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,YAAY,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IAClF,MAAM,MAAM,GACV,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;IACjG,OAAO,UAAU,KAAK,CAAC,IAAI,UAAU,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC;AACjG,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,GAAG,CAAC,IAAY;IACvB,IAAI,IAAI,CAAC,MAAM,IAAI,eAAe;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,OAAO,IAAI,CAAC,MAAM,GAAG,eAAe,SAAS,CAAC;AACxF,CAAC;AAYD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAA2B,EAC3B,eAA6B,mBAAmB;IAEhD,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IACxD,IAAI,SAA+B,CAAC;IACpC,MAAM,GAAG,GAAG,GAAa,EAAE,CACzB,CAAC,SAAS,KAAK;QACb,GAAG,OAAO,EAAE;QACZ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,YAAY,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5F,GAAG,CAAC,YAAY,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvF,CAAC,CAAC;IACL,OAAO,YAAY,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAoB,EACpB,QAAgB,EAChB,QAAuB;IAEvB,MAAM,QAAQ,GAAG;QACf,IAAI,aAAa,CACf;YACE,qEAAqE;YACrE,4UAA4U;YAC5U,yMAAyM;YACzM,4FAA4F;YAC5F,yEAAyE;YACzE,uEAAuE;YACvE,6DAA6D;YAC7D,kEAAkE;YAClE,yFAAyF;SAC1F,CAAC,IAAI,CAAC,GAAG,CAAC,CACZ;QACD,IAAI,YAAY,CAAC,cAAc,QAAQ,OAAO,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;KAC/E,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAE5B,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,4DAA4D;IAC5D,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC;QAC/B,GAAG,QAAQ;QACX,KAAK;QACL,IAAI,YAAY,CACd,mFAAmF;YACjF,gFAAgF,CACnF;KACF,CAAC,CAAC;IACH,OAAO,CACL,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI;QACnC,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,0CAA0C;KACnD,CACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,SAAS,YAAY,CAAC,QAA8B;IAClD,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC;IAC7B,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAuB,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;aAC/E,IAAI,CAAC,EAAE,CAAC,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAY,CAAC;YAC5C,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,SAAS;YACrF,kEAAkE;YAClE,uEAAuE;YACvE,6BAA6B;YAC7B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAA+C,CAAC;YAC1E,OAAO;gBACL,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;gBACnD,MAAM,EAAE,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;aACxD,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4EAA4E;AAC5E,QAAQ,CAAC,CAAC,eAAe,CAAC,GAAW;IACnC,IAAI,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACpB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YAClB,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,OAAO;oBAAE,OAAO,GAAG,KAAK,CAAC;qBACxB,IAAI,EAAE,KAAK,IAAI;oBAAE,OAAO,GAAG,IAAI,CAAC;qBAChC,IAAI,EAAE,KAAK,GAAG;oBAAE,QAAQ,GAAG,KAAK,CAAC;gBACtC,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG;gBAAE,QAAQ,GAAG,IAAI,CAAC;iBAC3B,IAAI,EAAE,KAAK,GAAG;gBAAE,KAAK,IAAI,CAAC,CAAC;iBAC3B,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC;gBACrC,GAAG,GAAG,CAAC,CAAC;gBACR,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAChD,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IACtC,CAAC;AACH,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Host-side interpretation of one parsed case document: open the case's
3
+ * session with its start conditions, then walk the flat step list — actions
4
+ * (`send`, `decide`, `deliver`) drive the agent through `workflow.send`, and each
5
+ * assertion evaluates against the view of the nearest action above it. No sandbox is involved —
6
+ * the QuickJS sandbox serves only lifecycle hooks and the agent's own sandbox
7
+ * tools inside the driven agent.
8
+ *
9
+ * A case that dies partway is still reported: termination is returned as an
10
+ * `error` on the outcome, next to the records the case had already produced,
11
+ * so the steps that passed before it are never thrown away.
12
+ */
13
+ import type { GovernedAgent } from "../agent.js";
14
+ import { type GradeFn } from "./assertions.js";
15
+ import type { CaseDocument } from "./case-schema.js";
16
+ import type { AssertionRecord } from "./runner.js";
17
+ export interface ExecuteCaseOptions {
18
+ /** Aborts the runs the case drives (the case's wall-clock budget). */
19
+ signal?: AbortSignal;
20
+ doc: CaseDocument;
21
+ /** Seed files, `from:` references already materialized by the runner. */
22
+ seeds: Record<string, unknown>;
23
+ agent: GovernedAgent;
24
+ /** The runtime session id for this case (caller-nameable via `sessionIdForCase`). */
25
+ sessionId: string;
26
+ grade?: GradeFn;
27
+ /**
28
+ * Invoked with each assertion record as it is produced — observe-only; the
29
+ * record objects are the same ones returned in the outcome. A caller that
30
+ * may stop waiting on this promise (the runner abandons it at the case's
31
+ * wall-clock deadline) uses this to hold the records produced before it let
32
+ * go, which the return value can no longer deliver.
33
+ */
34
+ onRecord?: (record: AssertionRecord) => void;
35
+ }
36
+ export interface ExecuteCaseOutcome {
37
+ records: AssertionRecord[];
38
+ /**
39
+ * Set when the case terminated before its steps were exhausted: a failed
40
+ * `send`/`decide`, a refused start (bad trigger, failed seed,
41
+ * un-interceptable mocks). The records produced before that point are still
42
+ * returned alongside it — a case's failure is reported as data, not by
43
+ * throwing past what it already established.
44
+ */
45
+ error?: string;
46
+ }
47
+ /**
48
+ * Describe every assertion step a case did not account for, as `not-executed`
49
+ * records. The one place un-run steps are built, so the three ways a case can
50
+ * stop early — a structural halt, a thrown action, and the runner's wall-clock
51
+ * timeout — cannot drift apart in what they report.
52
+ */
53
+ export declare function notExecutedRecords(doc: CaseDocument, alreadyAccountedFor: (step: number) => boolean): AssertionRecord[];
54
+ /** Execute one case document against the agent under test. */
55
+ export declare function executeCase(opts: ExecuteCaseOptions): Promise<ExecuteCaseOutcome>;