@cursor/july 0.1.16 → 0.1.18

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 (273) hide show
  1. package/README.md +9 -4
  2. package/dist/bin/agent-serve.js +60 -2
  3. package/dist/channels/slack/api.d.ts.map +1 -1
  4. package/dist/channels/slack/api.js +13 -3
  5. package/dist/channels/slack/auth.d.ts +7 -1
  6. package/dist/channels/slack/auth.d.ts.map +1 -1
  7. package/dist/channels/slack/auth.js +14 -0
  8. package/dist/channels/slack/blocks.d.ts +61 -0
  9. package/dist/channels/slack/blocks.d.ts.map +1 -0
  10. package/dist/channels/slack/blocks.js +90 -0
  11. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  12. package/dist/channels/slack/cursor-account.js +1 -1
  13. package/dist/channels/slack/defaults.d.ts +2 -0
  14. package/dist/channels/slack/defaults.d.ts.map +1 -1
  15. package/dist/channels/slack/defaults.js +29 -3
  16. package/dist/channels/slack/dispatch.d.ts +3 -17
  17. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  18. package/dist/channels/slack/dispatch.js +4 -1
  19. package/dist/channels/slack/inbound.d.ts.map +1 -1
  20. package/dist/channels/slack/inbound.js +6 -1
  21. package/dist/channels/slack/index.d.ts +2 -0
  22. package/dist/channels/slack/index.d.ts.map +1 -1
  23. package/dist/channels/slack/index.js +2 -0
  24. package/dist/channels/slack/init.d.ts.map +1 -1
  25. package/dist/channels/slack/init.js +4 -1
  26. package/dist/channels/slack/interactive.d.ts +39 -3
  27. package/dist/channels/slack/interactive.d.ts.map +1 -1
  28. package/dist/channels/slack/interactive.js +153 -7
  29. package/dist/channels/slack/manifest.d.ts +13 -0
  30. package/dist/channels/slack/manifest.d.ts.map +1 -1
  31. package/dist/channels/slack/manifest.js +36 -14
  32. package/dist/channels/slack/message-body.d.ts +13 -0
  33. package/dist/channels/slack/message-body.d.ts.map +1 -0
  34. package/dist/channels/slack/message-body.js +82 -0
  35. package/dist/channels/slack/setup.d.ts +4 -1
  36. package/dist/channels/slack/setup.d.ts.map +1 -1
  37. package/dist/channels/slack/setup.js +16 -2
  38. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  39. package/dist/channels/slack/slack-channel.js +24 -3
  40. package/dist/channels/slack/thread-context.d.ts +0 -2
  41. package/dist/channels/slack/thread-context.d.ts.map +1 -1
  42. package/dist/channels/slack/thread-context.js +1 -65
  43. package/dist/channels/slack/types.d.ts +93 -1
  44. package/dist/channels/slack/types.d.ts.map +1 -1
  45. package/dist/connections.d.ts +4 -1
  46. package/dist/connections.d.ts.map +1 -1
  47. package/dist/connections.js +4 -1
  48. package/dist/docs/404.html +2 -2
  49. package/dist/docs/ab.html +3 -3
  50. package/dist/docs/assets/{app.DjHC-Ggf.js → app.BR5EdVSq.js} +1 -1
  51. package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
  52. package/dist/docs/assets/chunks/{VPLocalSearchBox.DwviwmIq.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
  53. package/dist/docs/assets/chunks/{theme.BIpeYSon.js → theme.CwKvMSWv.js} +2 -2
  54. package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.js} +1 -1
  55. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
  56. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
  57. package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
  58. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
  59. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.lean.js} +1 -1
  60. package/dist/docs/building-with-agents.html +3 -3
  61. package/dist/docs/concepts.html +3 -3
  62. package/dist/docs/deployment.html +3 -3
  63. package/dist/docs/evals.html +3 -3
  64. package/dist/docs/example-agents/approval-buddy.html +3 -3
  65. package/dist/docs/example-agents/benny.html +3 -3
  66. package/dist/docs/example-agents/bugbot.html +3 -3
  67. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  68. package/dist/docs/example-agents/codeowners-review.html +3 -3
  69. package/dist/docs/example-agents/concierge.html +3 -3
  70. package/dist/docs/example-agents/fsd.html +3 -3
  71. package/dist/docs/example-agents/index.html +3 -3
  72. package/dist/docs/example-agents/knowledge-base.html +3 -3
  73. package/dist/docs/example-agents/oncall.html +3 -3
  74. package/dist/docs/example-agents/security-reviewer.html +5 -5
  75. package/dist/docs/example-agents/slack-agent.html +3 -3
  76. package/dist/docs/example-agents/weather-agent.html +3 -3
  77. package/dist/docs/guides/agent-to-agent.html +3 -3
  78. package/dist/docs/guides/cloud-runtime.html +3 -3
  79. package/dist/docs/guides/github.html +3 -3
  80. package/dist/docs/guides/human-in-the-loop.html +3 -3
  81. package/dist/docs/guides/mcp-oauth.html +6 -5
  82. package/dist/docs/guides/slack.html +5 -5
  83. package/dist/docs/guides/webhooks.html +3 -3
  84. package/dist/docs/hashmap.json +1 -1
  85. package/dist/docs/hillclimbing.html +3 -3
  86. package/dist/docs/index.html +3 -3
  87. package/dist/docs/quickstart.html +3 -3
  88. package/dist/docs/reference/agent-config.html +3 -3
  89. package/dist/docs/reference/channels.html +3 -3
  90. package/dist/docs/reference/cli.html +3 -3
  91. package/dist/docs/reference/connections.html +14 -7
  92. package/dist/docs/reference/hooks.html +3 -3
  93. package/dist/docs/reference/http-api.html +3 -3
  94. package/dist/docs/reference/instructions.html +3 -3
  95. package/dist/docs/reference/playground.html +3 -3
  96. package/dist/docs/reference/project-layout.html +3 -3
  97. package/dist/docs/reference/prompt.html +3 -3
  98. package/dist/docs/reference/schedules.html +3 -3
  99. package/dist/docs/reference/sessions.html +3 -3
  100. package/dist/docs/reference/skills.html +3 -3
  101. package/dist/docs/reference/subagents.html +3 -3
  102. package/dist/docs/reference/tools.html +3 -3
  103. package/dist/docs/scaffolding-agents.html +3 -3
  104. package/dist/docs/storage.html +3 -3
  105. package/dist/docs/troubleshooting.html +3 -3
  106. package/dist/evals/assertions.d.ts +190 -0
  107. package/dist/evals/assertions.d.ts.map +1 -0
  108. package/dist/evals/assertions.js +461 -0
  109. package/dist/evals/expect.d.ts +95 -0
  110. package/dist/evals/expect.d.ts.map +1 -0
  111. package/dist/evals/expect.js +194 -0
  112. package/dist/evals/judge.d.ts +133 -0
  113. package/dist/evals/judge.d.ts.map +1 -0
  114. package/dist/evals/judge.js +228 -0
  115. package/dist/evals/loaders.d.ts +51 -0
  116. package/dist/evals/loaders.d.ts.map +1 -0
  117. package/dist/evals/loaders.js +124 -0
  118. package/dist/evals/matchers.d.ts +95 -0
  119. package/dist/evals/matchers.d.ts.map +1 -0
  120. package/dist/evals/matchers.js +145 -0
  121. package/dist/evals/reporters.d.ts +70 -0
  122. package/dist/evals/reporters.d.ts.map +1 -0
  123. package/dist/evals/reporters.js +221 -0
  124. package/dist/evals/results.d.ts +103 -0
  125. package/dist/evals/results.d.ts.map +1 -0
  126. package/dist/evals/results.js +28 -0
  127. package/dist/evals/run-facts.d.ts +77 -0
  128. package/dist/evals/run-facts.d.ts.map +1 -0
  129. package/dist/evals/run-facts.js +187 -0
  130. package/dist/evals.d.ts +118 -31
  131. package/dist/evals.d.ts.map +1 -1
  132. package/dist/evals.js +46 -12
  133. package/dist/index.d.ts +1 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -1
  136. package/dist/internal/builtin-tools/index.d.ts +10 -0
  137. package/dist/internal/builtin-tools/index.d.ts.map +1 -0
  138. package/dist/internal/builtin-tools/index.js +27 -0
  139. package/dist/internal/builtin-tools/reminders.d.ts +9 -0
  140. package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
  141. package/dist/internal/builtin-tools/reminders.js +138 -0
  142. package/dist/internal/cli-ax.d.ts +27 -0
  143. package/dist/internal/cli-ax.d.ts.map +1 -1
  144. package/dist/internal/cli-ax.js +90 -12
  145. package/dist/internal/cli-mcp.d.ts +8 -0
  146. package/dist/internal/cli-mcp.d.ts.map +1 -1
  147. package/dist/internal/cli-mcp.js +98 -0
  148. package/dist/internal/cli-slack.d.ts +29 -1
  149. package/dist/internal/cli-slack.d.ts.map +1 -1
  150. package/dist/internal/cli-slack.js +799 -13
  151. package/dist/internal/cursor-event-relay.d.ts +30 -0
  152. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  153. package/dist/internal/cursor-event-relay.js +37 -0
  154. package/dist/internal/deploy-client.d.ts +7 -0
  155. package/dist/internal/deploy-client.d.ts.map +1 -1
  156. package/dist/internal/deploy-client.js +9 -0
  157. package/dist/internal/discovery.d.ts.map +1 -1
  158. package/dist/internal/discovery.js +70 -9
  159. package/dist/internal/distribution.d.ts.map +1 -1
  160. package/dist/internal/distribution.js +2 -0
  161. package/dist/internal/eval-judge-model.d.ts +59 -0
  162. package/dist/internal/eval-judge-model.d.ts.map +1 -0
  163. package/dist/internal/eval-judge-model.js +131 -0
  164. package/dist/internal/eval-run-store.d.ts.map +1 -1
  165. package/dist/internal/eval-run-store.js +12 -20
  166. package/dist/internal/eval-runner.d.ts +31 -25
  167. package/dist/internal/eval-runner.d.ts.map +1 -1
  168. package/dist/internal/eval-runner.js +329 -126
  169. package/dist/internal/evals-client.d.ts.map +1 -1
  170. package/dist/internal/evals-client.js +4 -1
  171. package/dist/internal/local-env.d.ts +8 -0
  172. package/dist/internal/local-env.d.ts.map +1 -0
  173. package/dist/internal/local-env.js +47 -0
  174. package/dist/internal/playground/static.d.ts +3 -0
  175. package/dist/internal/playground/static.d.ts.map +1 -1
  176. package/dist/internal/playground/static.js +3 -0
  177. package/dist/internal/reminder-runner.d.ts +7 -0
  178. package/dist/internal/reminder-runner.d.ts.map +1 -1
  179. package/dist/internal/reminder-runner.js +21 -0
  180. package/dist/internal/run-client.d.ts +15 -0
  181. package/dist/internal/run-client.d.ts.map +1 -1
  182. package/dist/internal/run-client.js +3 -2
  183. package/dist/internal/sdk-runner.d.ts +51 -1
  184. package/dist/internal/sdk-runner.d.ts.map +1 -1
  185. package/dist/internal/sdk-runner.js +176 -36
  186. package/dist/internal/server.d.ts.map +1 -1
  187. package/dist/internal/server.js +11 -1
  188. package/dist/internal/session-engine.d.ts +18 -5
  189. package/dist/internal/session-engine.d.ts.map +1 -1
  190. package/dist/internal/session-engine.js +81 -19
  191. package/dist/internal/slack-provision-client.d.ts +133 -0
  192. package/dist/internal/slack-provision-client.d.ts.map +1 -0
  193. package/dist/internal/slack-provision-client.js +286 -0
  194. package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
  195. package/dist/playground/assets/index-DRjS5klQ.js +85 -0
  196. package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
  197. package/dist/playground/index.html +2 -2
  198. package/dist/types.d.ts +98 -4
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +26 -0
  201. package/docs/example-agents/security-reviewer.md +1 -1
  202. package/docs/guides/mcp-oauth.md +10 -5
  203. package/docs/guides/slack.md +3 -2
  204. package/docs/reference/connections.md +28 -18
  205. package/package.json +20 -2
  206. package/skills/create-agent/SKILL.md +15 -7
  207. package/skills/debug/SKILL.md +3 -1
  208. package/skills/mcp-auth/SKILL.md +1 -1
  209. package/skills/setup-slack/SKILL.md +177 -11
  210. package/src/bin/agent-serve.ts +63 -2
  211. package/src/channels/slack/api.ts +16 -3
  212. package/src/channels/slack/auth.ts +16 -1
  213. package/src/channels/slack/blocks.ts +139 -0
  214. package/src/channels/slack/cursor-account.ts +1 -0
  215. package/src/channels/slack/defaults.ts +32 -4
  216. package/src/channels/slack/dispatch.ts +6 -19
  217. package/src/channels/slack/inbound.ts +6 -1
  218. package/src/channels/slack/index.ts +2 -0
  219. package/src/channels/slack/init.ts +3 -0
  220. package/src/channels/slack/interactive.ts +184 -19
  221. package/src/channels/slack/manifest.ts +37 -6
  222. package/src/channels/slack/message-body.ts +90 -0
  223. package/src/channels/slack/setup.ts +16 -2
  224. package/src/channels/slack/slack-channel.ts +37 -6
  225. package/src/channels/slack/thread-context.ts +1 -69
  226. package/src/channels/slack/types.ts +105 -1
  227. package/src/connections.ts +4 -1
  228. package/src/evals/assertions.ts +704 -0
  229. package/src/evals/expect.ts +272 -0
  230. package/src/evals/judge.ts +346 -0
  231. package/src/evals/loaders.ts +113 -0
  232. package/src/evals/matchers.ts +246 -0
  233. package/src/evals/reporters.ts +274 -0
  234. package/src/evals/results.ts +132 -0
  235. package/src/evals/run-facts.ts +274 -0
  236. package/src/evals.ts +205 -42
  237. package/src/index.ts +2 -0
  238. package/src/internal/builtin-tools/index.ts +40 -0
  239. package/src/internal/builtin-tools/reminders.ts +158 -0
  240. package/src/internal/cli-ax.ts +134 -15
  241. package/src/internal/cli-mcp.ts +118 -5
  242. package/src/internal/cli-slack.ts +1017 -15
  243. package/src/internal/cursor-event-relay.ts +67 -0
  244. package/src/internal/deploy-client.ts +10 -0
  245. package/src/internal/discovery.ts +83 -5
  246. package/src/internal/distribution.ts +2 -0
  247. package/src/internal/eval-judge-model.ts +148 -0
  248. package/src/internal/eval-run-store.ts +10 -14
  249. package/src/internal/eval-runner.ts +460 -214
  250. package/src/internal/evals-client.ts +5 -0
  251. package/src/internal/local-env.ts +53 -0
  252. package/src/internal/playground/static.ts +3 -0
  253. package/src/internal/reminder-runner.ts +30 -0
  254. package/src/internal/run-client.ts +17 -1
  255. package/src/internal/sdk-runner.ts +234 -35
  256. package/src/internal/server.ts +12 -0
  257. package/src/internal/session-engine.ts +103 -21
  258. package/src/internal/slack-provision-client.ts +442 -0
  259. package/src/types.ts +125 -4
  260. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  261. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  262. package/dist/docs/assets/chunks/@localSearchIndexroot.DZtu0ZIc.js +0 -1
  263. package/dist/internal/json-dir-store.d.ts +0 -32
  264. package/dist/internal/json-dir-store.d.ts.map +0 -1
  265. package/dist/internal/persistence-coordinator.d.ts +0 -127
  266. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  267. package/dist/persistence.d.ts +0 -184
  268. package/dist/persistence.d.ts.map +0 -1
  269. package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
  270. package/dist/playground/assets/index-Bwl4Onx4.js +0 -85
  271. package/dist/playground/assets/index-ByV4nfch.css +0 -1
  272. /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
  273. /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Assertion recording and the scoped assertion vocabulary.
3
+ *
4
+ * Recorded assertions never throw: the recorder collects every result so one
5
+ * run reports all failures instead of dying on the first. Severity rides on the
6
+ * returned handle, so there is no separate thresholds map:
7
+ *
8
+ * ```ts
9
+ * t.succeeded(); // gate (hard)
10
+ * t.calledTool("get_weather").soft(); // tracked, does not gate
11
+ * t.check(t.reply, similarity("Sunny")).atLeast(0.8);
12
+ * ```
13
+ *
14
+ * The same vocabulary is available on the run (`t`) and on each turn returned
15
+ * by `t.send(...)`, which narrows the events under inspection to that turn.
16
+ */
17
+ import type { SessionEvent } from "../types.js";
18
+ import type { Expectation } from "./expect.js";
19
+ import type { EvalCountMatcher, EvalEventMatcher, EvalEventOrderMatcher, EvalMatcher, EvalSubagentMatcher, EvalToolCallMatcher } from "./matchers.js";
20
+ import type { EvalInputRequestFact, EvalToolCallFact } from "./run-facts.js";
21
+ /** Hard gates fail the eval; soft assertions are tracked scores. */
22
+ export type EvalAssertionSeverity = "gate" | "soft";
23
+ /**
24
+ * Overall grade for one eval case.
25
+ *
26
+ * - `passed` — every gate passed and no soft threshold was missed.
27
+ * - `failed` — a gate failed, or the test body threw.
28
+ * - `scored` — only soft thresholds were missed: visible, and fatal only under
29
+ * `--strict`.
30
+ * - `skipped` — `t.skip(reason)`, or a judge with no credentials available.
31
+ */
32
+ export type EvalVerdict = "passed" | "failed" | "scored" | "skipped";
33
+ export interface EvalAssertionResult {
34
+ name: string;
35
+ passed: boolean;
36
+ detail?: string;
37
+ /** Defaults to `gate`; omitted on the wire for gates to stay compact. */
38
+ severity?: EvalAssertionSeverity;
39
+ /** 0–1 score for scored assertions (`similarity`, judges). */
40
+ score?: number;
41
+ /** Bar a scored assertion must clear. */
42
+ threshold?: number;
43
+ /** Set when the assertion inspects one turn rather than the whole run. */
44
+ scope?: string;
45
+ }
46
+ /**
47
+ * Chainable severity control returned by every assertion.
48
+ *
49
+ * - `.gate(threshold?)` is hard: a miss fails the eval and `eval` exits 1.
50
+ * - `.soft(threshold?)` is tracked: a below-threshold miss marks the eval
51
+ * `scored`, fatal only under `--strict`. With no threshold it never fails.
52
+ * - `.atLeast(threshold)` is soft with a bar.
53
+ */
54
+ export interface EvalAssertionHandle {
55
+ gate(threshold?: number): EvalAssertionHandle;
56
+ soft(threshold?: number): EvalAssertionHandle;
57
+ atLeast(threshold: number): EvalAssertionHandle;
58
+ }
59
+ /** Thrown by `t.require(...)` to stop the test body without an error result. */
60
+ export declare class EvalRequireFailure extends Error {
61
+ readonly assertionName: string;
62
+ constructor(assertionName: string);
63
+ }
64
+ /** Thrown by `t.skip(reason)` to end the case as skipped. */
65
+ export declare class EvalSkipSignal extends Error {
66
+ readonly reason: string;
67
+ constructor(reason: string);
68
+ }
69
+ export interface RecordAssertionInput {
70
+ name: string;
71
+ passed: boolean;
72
+ detail?: string;
73
+ severity?: EvalAssertionSeverity;
74
+ score?: number;
75
+ threshold?: number;
76
+ scope?: string;
77
+ }
78
+ /**
79
+ * Collects assertion results and any in-flight judge scoring for one case.
80
+ */
81
+ export declare class EvalAssertionRecorder {
82
+ private readonly slots;
83
+ private readonly pending;
84
+ record(input: RecordAssertionInput): EvalAssertionHandle;
85
+ /**
86
+ * Reserve a slot now and fill it in when `settle` resolves. Judges start when
87
+ * recorded so several graders overlap; {@link waitForPending} joins them.
88
+ */
89
+ recordAsync(input: RecordAssertionInput, settle: Promise<Partial<RecordAssertionInput>>): EvalAssertionHandle;
90
+ /** Await every async assertion started during the test body. */
91
+ waitForPending(): Promise<void>;
92
+ results(): EvalAssertionResult[];
93
+ get count(): number;
94
+ }
95
+ /**
96
+ * Grade a case from its recorded assertions.
97
+ *
98
+ * With no assertions at all, the caller's fallback applies (at least one
99
+ * successful turn), which keeps pre-severity eval files working unchanged.
100
+ */
101
+ export declare function computeVerdict(results: readonly EvalAssertionResult[]): Exclude<EvalVerdict, "skipped">;
102
+ /** Whether a verdict should fail the process exit code. */
103
+ export declare function verdictFailsRun(verdict: EvalVerdict, strict: boolean): boolean;
104
+ /**
105
+ * The deterministic assertion vocabulary, available on the run (`t`) and on
106
+ * each turn returned by `t.send(...)`.
107
+ */
108
+ export interface EvalAssertions {
109
+ /**
110
+ * Gate: the run did not fail and is not parked on an unanswered approval.
111
+ * A session left open for the next message is a healthy end state.
112
+ */
113
+ succeeded(): EvalAssertionHandle;
114
+ /** Gate: the run cleanly parked on an unanswered human approval request. */
115
+ parked(): EvalAssertionHandle;
116
+ /** Gate: joined assistant text contains `token`. */
117
+ messageIncludes(token: string | RegExp): EvalAssertionHandle;
118
+ /**
119
+ * Gate: a matching call to `name` happened.
120
+ *
121
+ * With no matcher this is request-based (any lifecycle state), so it keeps
122
+ * meaning "the agent used this tool". Pass `{ status: "completed" }` to
123
+ * require the call actually returned, or `{ input, output, count }` to
124
+ * constrain it further.
125
+ */
126
+ calledTool(name: string, matcher?: EvalToolCallMatcher): EvalAssertionHandle;
127
+ /** Gate: no request for `name` in any lifecycle state. */
128
+ notCalledTool(name: string): EvalAssertionHandle;
129
+ /**
130
+ * Gate: the agent opened the skill's `SKILL.md`.
131
+ *
132
+ * Skills are materialized as files rather than exposed as a tool, so this
133
+ * matches any tool call whose input references `skills/<name>/SKILL.md`
134
+ * (a file read, a grep, or a shell `cat` all count).
135
+ */
136
+ loadedSkill(name: string): EvalAssertionHandle;
137
+ /** Gate: tool requests appear in this relative order (extra calls allowed). */
138
+ toolOrder(names: readonly string[]): EvalAssertionHandle;
139
+ /** Gate: no tool calls at all. */
140
+ usedNoTools(): EvalAssertionHandle;
141
+ /** Gate: at most `max` tool calls. */
142
+ maxToolCalls(max: number): EvalAssertionHandle;
143
+ /** Gate: no tool call reported an error. */
144
+ noFailedActions(): EvalAssertionHandle;
145
+ /** Gate: a matching subagent delegation happened. */
146
+ calledSubagent(name: string, matcher?: EvalSubagentMatcher): EvalAssertionHandle;
147
+ /** Gate: at least one matching event of `type` occurred. */
148
+ event(type: string, matcher?: EvalEventMatcher): EvalAssertionHandle;
149
+ /** Gate: no matching event of `type` occurred. */
150
+ notEvent(type: string, matcher?: EvalEventMatcher): EvalAssertionHandle;
151
+ /** Gate: matching event groups occur in this relative order. */
152
+ eventOrder(matchers: readonly EvalEventOrderMatcher[]): EvalAssertionHandle;
153
+ /** Gate: escape hatch for any predicate over the typed event stream. */
154
+ eventsSatisfy(label: string, predicate: (events: readonly SessionEvent[]) => boolean): EvalAssertionHandle;
155
+ /** Grade an explicit value with an `Expectation` builder. */
156
+ check(value: unknown, assertion: Expectation): EvalAssertionHandle;
157
+ /**
158
+ * Record a 0–1 score the eval computed itself, for a benchmark the built-in
159
+ * builders cannot express (recall against a gold set, a custom judge).
160
+ *
161
+ * Soft and tracked-only by default, so a new benchmark can report its number
162
+ * for a while before anyone gates on it. Add the bar when the number is
163
+ * trustworthy: `t.score("recall", recall).atLeast(0.6)`.
164
+ */
165
+ score(name: string, value: number): EvalAssertionHandle;
166
+ /**
167
+ * Record a gate and return the matching tool call so dependent code can read
168
+ * its input/output. Stops the test body when no call matches.
169
+ */
170
+ requireToolCall(name: string, matcher?: EvalToolCallMatcher): EvalToolCallFact;
171
+ /**
172
+ * Record a gate that exactly one approval request is pending, and return it.
173
+ * Stops the test body otherwise.
174
+ */
175
+ requireInputRequest(filter?: {
176
+ toolName?: EvalMatcher<string>;
177
+ args?: EvalMatcher;
178
+ }): EvalInputRequestFact;
179
+ }
180
+ export interface CreateAssertionsOptions {
181
+ recorder: EvalAssertionRecorder;
182
+ /** Events under inspection. Read lazily so run scope sees later turns. */
183
+ events: () => readonly SessionEvent[];
184
+ /** Label prefix for turn-scoped assertions, e.g. `turn[2]`. */
185
+ scope?: string;
186
+ }
187
+ /** Build the assertion vocabulary bound to one scope. */
188
+ export declare function createAssertions(options: CreateAssertionsOptions): EvalAssertions;
189
+ export type { EvalCountMatcher, EvalInputRequestFact, EvalToolCallFact };
190
+ //# sourceMappingURL=assertions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../../src/evals/assertions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EACV,oBAAoB,EAEpB,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAGxB,oEAAoE;AACpE,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC9C,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC9C,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAAC;CACjD;AAQD,gFAAgF;AAChF,qBAAa,kBAAmB,SAAQ,KAAK;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM;IAA1C,YAAqB,aAAa,EAAE,MAAM,EAGzC;CACF;AAED,6DAA6D;AAC7D,qBAAa,cAAe,SAAQ,KAAK;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAnC,YAAqB,MAAM,EAAE,MAAM,EAGlC;CACF;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,CAcvD;IAED;;;OAGG;IACH,WAAW,CACT,KAAK,EAAE,oBAAoB,EAC3B,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,GAC7C,mBAAmB,CA0BrB;IAED,gEAAgE;IAC1D,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAKpC;IAED,OAAO,IAAI,mBAAmB,EAAE,CAW/B;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;CACF;AA2CD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,mBAAmB,EAAE,GACtC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAcjC;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,OAAO,GACd,OAAO,CAKT;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,SAAS,IAAI,mBAAmB,CAAC;IACjC,4EAA4E;IAC5E,MAAM,IAAI,mBAAmB,CAAC;IAC9B,oDAAoD;IACpD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,mBAAmB,CAAC;IAC7D;;;;;;;OAOG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAAC;IAC7E,0DAA0D;IAC1D,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACjD;;;;;;OAMG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC/C,+EAA+E;IAC/E,SAAS,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,mBAAmB,CAAC;IACzD,kCAAkC;IAClC,WAAW,IAAI,mBAAmB,CAAC;IACnC,sCAAsC;IACtC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC/C,4CAA4C;IAC5C,eAAe,IAAI,mBAAmB,CAAC;IACvC,qDAAqD;IACrD,cAAc,CACZ,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,mBAAmB,CAAC;IACvB,4DAA4D;IAC5D,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,mBAAmB,CAAC;IACrE,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,mBAAmB,CAAC;IACxE,gEAAgE;IAChE,UAAU,CAAC,QAAQ,EAAE,SAAS,qBAAqB,EAAE,GAAG,mBAAmB,CAAC;IAC5E,wEAAwE;IACxE,aAAa,CACX,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,GACtD,mBAAmB,CAAC;IACvB,6DAA6D;IAC7D,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,GAAG,mBAAmB,CAAC;IACnE;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACxD;;;OAGG;IACH,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,gBAAgB,CAAC;IACpB;;;OAGG;IACH,mBAAmB,CAAC,MAAM,CAAC,EAAE;QAC3B,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,WAAW,CAAC;KACpB,GAAG,oBAAoB,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,SAAS,YAAY,EAAE,CAAC;IACtC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,GAC/B,cAAc,CA8UhB;AAED,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,461 @@
1
+ /**
2
+ * Assertion recording and the scoped assertion vocabulary.
3
+ *
4
+ * Recorded assertions never throw: the recorder collects every result so one
5
+ * run reports all failures instead of dying on the first. Severity rides on the
6
+ * returned handle, so there is no separate thresholds map:
7
+ *
8
+ * ```ts
9
+ * t.succeeded(); // gate (hard)
10
+ * t.calledTool("get_weather").soft(); // tracked, does not gate
11
+ * t.check(t.reply, similarity("Sunny")).atLeast(0.8);
12
+ * ```
13
+ *
14
+ * The same vocabulary is available on the run (`t`) and on each turn returned
15
+ * by `t.send(...)`, which narrows the events under inspection to that turn.
16
+ */
17
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
+ return new (P || (P = Promise))(function (resolve, reject) {
20
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
24
+ });
25
+ };
26
+ import { evaluateExpectation, expectationSeverity } from "./expect.js";
27
+ import { describeMatcherObject, matchCount, matchValue } from "./matchers.js";
28
+ import { deriveRunFacts } from "./run-facts.js";
29
+ /** Thrown by `t.require(...)` to stop the test body without an error result. */
30
+ export class EvalRequireFailure extends Error {
31
+ constructor(assertionName) {
32
+ super(`required assertion failed: ${assertionName}`);
33
+ this.assertionName = assertionName;
34
+ this.name = "EvalRequireFailure";
35
+ }
36
+ }
37
+ /** Thrown by `t.skip(reason)` to end the case as skipped. */
38
+ export class EvalSkipSignal extends Error {
39
+ constructor(reason) {
40
+ super(reason);
41
+ this.reason = reason;
42
+ this.name = "EvalSkipSignal";
43
+ }
44
+ }
45
+ /**
46
+ * Collects assertion results and any in-flight judge scoring for one case.
47
+ */
48
+ export class EvalAssertionRecorder {
49
+ constructor() {
50
+ this.slots = [];
51
+ this.pending = [];
52
+ }
53
+ record(input) {
54
+ var _a;
55
+ const slot = {
56
+ name: input.name,
57
+ passed: input.passed,
58
+ rawPassed: input.passed,
59
+ detail: input.detail,
60
+ severity: (_a = input.severity) !== null && _a !== void 0 ? _a : "gate",
61
+ score: input.score,
62
+ threshold: input.threshold,
63
+ scope: input.scope,
64
+ };
65
+ applyThreshold(slot);
66
+ this.slots.push(slot);
67
+ return handleFor(slot);
68
+ }
69
+ /**
70
+ * Reserve a slot now and fill it in when `settle` resolves. Judges start when
71
+ * recorded so several graders overlap; {@link waitForPending} joins them.
72
+ */
73
+ recordAsync(input, settle) {
74
+ const handle = this.record(input);
75
+ const slot = this.slots[this.slots.length - 1];
76
+ this.pending.push(settle.then((patch) => {
77
+ Object.assign(slot, patch);
78
+ if (patch.passed !== undefined) {
79
+ slot.rawPassed = patch.passed;
80
+ }
81
+ applyThreshold(slot);
82
+ }, (error) => {
83
+ // Grading blew up, which is a broken assertion rather than a low
84
+ // score. Soft severity covers "the model scored badly", so promote to
85
+ // a gate here or the failure would be invisible in the verdict.
86
+ slot.rawPassed = false;
87
+ slot.passed = false;
88
+ slot.score = undefined;
89
+ slot.threshold = undefined;
90
+ slot.severity = "gate";
91
+ slot.detail = error instanceof Error ? error.message : String(error);
92
+ }));
93
+ return handle;
94
+ }
95
+ /** Await every async assertion started during the test body. */
96
+ waitForPending() {
97
+ return __awaiter(this, void 0, void 0, function* () {
98
+ while (this.pending.length > 0) {
99
+ const inFlight = this.pending.splice(0, this.pending.length);
100
+ yield Promise.all(inFlight);
101
+ }
102
+ });
103
+ }
104
+ results() {
105
+ return this.slots.map((slot) => ({
106
+ name: slot.name,
107
+ passed: slot.passed,
108
+ detail: slot.detail,
109
+ // Keep the wire shape compact: `gate` is the default everywhere.
110
+ severity: slot.severity === "gate" ? undefined : slot.severity,
111
+ score: slot.score,
112
+ threshold: slot.threshold,
113
+ scope: slot.scope,
114
+ }));
115
+ }
116
+ get count() {
117
+ return this.slots.length;
118
+ }
119
+ }
120
+ /**
121
+ * Re-derive `passed` from the score whenever a threshold is in play.
122
+ *
123
+ * A tracked score with no bar always passes — there is nothing to miss. A
124
+ * boolean assertion keeps its recorded outcome even when demoted to soft, so
125
+ * the report stays honest; severity decides the verdict, not the `passed` flag.
126
+ */
127
+ function applyThreshold(slot) {
128
+ var _a;
129
+ if (slot.threshold === undefined) {
130
+ slot.passed = slot.score !== undefined ? true : slot.rawPassed;
131
+ return;
132
+ }
133
+ const score = (_a = slot.score) !== null && _a !== void 0 ? _a : (slot.rawPassed ? 1 : 0);
134
+ slot.passed = score >= slot.threshold;
135
+ }
136
+ function handleFor(slot) {
137
+ const handle = {
138
+ gate(threshold) {
139
+ slot.severity = "gate";
140
+ if (threshold !== undefined) {
141
+ slot.threshold = threshold;
142
+ }
143
+ applyThreshold(slot);
144
+ return handle;
145
+ },
146
+ soft(threshold) {
147
+ slot.severity = "soft";
148
+ if (threshold !== undefined) {
149
+ slot.threshold = threshold;
150
+ }
151
+ applyThreshold(slot);
152
+ return handle;
153
+ },
154
+ atLeast(threshold) {
155
+ return handle.soft(threshold);
156
+ },
157
+ };
158
+ return handle;
159
+ }
160
+ /**
161
+ * Grade a case from its recorded assertions.
162
+ *
163
+ * With no assertions at all, the caller's fallback applies (at least one
164
+ * successful turn), which keeps pre-severity eval files working unchanged.
165
+ */
166
+ export function computeVerdict(results) {
167
+ const failedGate = results.some((r) => { var _a; return ((_a = r.severity) !== null && _a !== void 0 ? _a : "gate") === "gate" && !r.passed; });
168
+ if (failedGate) {
169
+ return "failed";
170
+ }
171
+ const missedSoftBar = results.some((r) => { var _a; return ((_a = r.severity) !== null && _a !== void 0 ? _a : "gate") === "soft" &&
172
+ r.threshold !== undefined &&
173
+ !r.passed; });
174
+ return missedSoftBar ? "scored" : "passed";
175
+ }
176
+ /** Whether a verdict should fail the process exit code. */
177
+ export function verdictFailsRun(verdict, strict) {
178
+ if (verdict === "failed") {
179
+ return true;
180
+ }
181
+ return strict && verdict === "scored";
182
+ }
183
+ /** Build the assertion vocabulary bound to one scope. */
184
+ export function createAssertions(options) {
185
+ const { recorder, events, scope } = options;
186
+ const facts = () => deriveRunFacts(events());
187
+ const name = (text) => scope === undefined ? text : `${scope}.${text}`;
188
+ const record = (input) => recorder.record(Object.assign(Object.assign({}, input), { name: name(input.name), scope }));
189
+ const matchingToolCalls = (toolName, matcher) => facts().toolCalls.filter((call) => {
190
+ if (call.toolName !== toolName) {
191
+ return false;
192
+ }
193
+ if (matcher === undefined) {
194
+ return true;
195
+ }
196
+ if (matcher.status !== undefined && call.status !== matcher.status) {
197
+ return false;
198
+ }
199
+ if (!matchValue(call.input, matcher.input)) {
200
+ return false;
201
+ }
202
+ if (!matchValue(call.output, matcher.output)) {
203
+ return false;
204
+ }
205
+ return true;
206
+ });
207
+ const matchingEvents = (type, matcher) => events().filter((event) => event.type === type && matchValue(event.data, matcher === null || matcher === void 0 ? void 0 : matcher.data));
208
+ return {
209
+ succeeded() {
210
+ var _a;
211
+ const run = facts();
212
+ const detail = !run.ok
213
+ ? ((_a = run.failureMessage) !== null && _a !== void 0 ? _a : "turn failed")
214
+ : run.parked
215
+ ? `parked on ${run.pendingInputRequests.length} unanswered approval request(s)`
216
+ : undefined;
217
+ return record({
218
+ name: "succeeded",
219
+ passed: run.ok && !run.parked,
220
+ detail,
221
+ });
222
+ },
223
+ parked() {
224
+ const run = facts();
225
+ const pending = run.pendingInputRequests;
226
+ return record({
227
+ name: "parked",
228
+ passed: run.parked && run.turnsFailed === 0,
229
+ detail: run.parked
230
+ ? pending.map((r) => r.toolName).join(", ")
231
+ : "no unanswered approval request",
232
+ });
233
+ },
234
+ messageIncludes(token) {
235
+ const text = facts().assistantText;
236
+ const passed = typeof token === "string" ? text.includes(token) : token.test(text);
237
+ return record({
238
+ name: `messageIncludes(${String(token)})`,
239
+ passed,
240
+ detail: passed
241
+ ? undefined
242
+ : `text=${JSON.stringify(text.slice(0, 120))}`,
243
+ });
244
+ },
245
+ calledTool(toolName, matcher) {
246
+ const matched = matchingToolCalls(toolName, matcher);
247
+ const passed = matchCount(matched.length, matcher === null || matcher === void 0 ? void 0 : matcher.count);
248
+ const observed = facts()
249
+ .toolCalls.map((c) => c.toolName)
250
+ .join(", ");
251
+ return record({
252
+ name: `calledTool(${toolName}${describeMatcherObject(matcher)})`,
253
+ passed,
254
+ detail: passed
255
+ ? undefined
256
+ : `matched ${matched.length}; called [${observed}]`,
257
+ });
258
+ },
259
+ notCalledTool(toolName) {
260
+ const matched = matchingToolCalls(toolName, undefined);
261
+ return record({
262
+ name: `notCalledTool(${toolName})`,
263
+ passed: matched.length === 0,
264
+ detail: matched.length === 0 ? undefined : "tool was called",
265
+ });
266
+ },
267
+ loadedSkill(skill) {
268
+ const needle = `skills/${skill}/SKILL.md`;
269
+ const matched = facts().toolCalls.filter((call) => { var _a; return JSON.stringify((_a = call.input) !== null && _a !== void 0 ? _a : "").includes(needle); });
270
+ return record({
271
+ name: `loadedSkill(${skill})`,
272
+ passed: matched.length > 0,
273
+ detail: matched.length > 0 ? undefined : `no tool call referenced ${needle}`,
274
+ });
275
+ },
276
+ toolOrder(names) {
277
+ const called = facts().toolCalls.map((c) => c.toolName);
278
+ let cursor = 0;
279
+ for (const expected of names) {
280
+ const found = called.indexOf(expected, cursor);
281
+ if (found === -1) {
282
+ return record({
283
+ name: `toolOrder([${names.join(", ")}])`,
284
+ passed: false,
285
+ detail: `missing ${expected} after position ${cursor}; called [${called.join(", ")}]`,
286
+ });
287
+ }
288
+ cursor = found + 1;
289
+ }
290
+ return record({
291
+ name: `toolOrder([${names.join(", ")}])`,
292
+ passed: true,
293
+ });
294
+ },
295
+ usedNoTools() {
296
+ const called = facts().toolCalls;
297
+ return record({
298
+ name: "usedNoTools",
299
+ passed: called.length === 0,
300
+ detail: called.length === 0
301
+ ? undefined
302
+ : `called [${called.map((c) => c.toolName).join(", ")}]`,
303
+ });
304
+ },
305
+ maxToolCalls(max) {
306
+ const total = facts().toolCalls.length;
307
+ return record({
308
+ name: `maxToolCalls(${max})`,
309
+ passed: total <= max,
310
+ detail: total <= max ? undefined : `made ${total} tool calls`,
311
+ });
312
+ },
313
+ noFailedActions() {
314
+ const failed = facts().failedToolCalls;
315
+ return record({
316
+ name: "noFailedActions",
317
+ passed: failed.length === 0,
318
+ detail: failed.length === 0
319
+ ? undefined
320
+ : failed.map((c) => c.toolName).join(", "),
321
+ });
322
+ },
323
+ calledSubagent(subagentName, matcher) {
324
+ const matched = facts().subagents.filter((sub) => {
325
+ if (sub.name !== subagentName) {
326
+ return false;
327
+ }
328
+ if (matcher === undefined) {
329
+ return true;
330
+ }
331
+ if (matcher.status !== undefined && sub.status !== matcher.status) {
332
+ return false;
333
+ }
334
+ if (!matchValue(sub.callId, matcher.callId)) {
335
+ return false;
336
+ }
337
+ return matchValue(sub.output, matcher.output);
338
+ });
339
+ const passed = matchCount(matched.length, matcher === null || matcher === void 0 ? void 0 : matcher.count);
340
+ return record({
341
+ name: `calledSubagent(${subagentName}${describeMatcherObject(matcher)})`,
342
+ passed,
343
+ detail: passed
344
+ ? undefined
345
+ : `matched ${matched.length}; delegated [${facts()
346
+ .subagents.map((s) => { var _a; return (_a = s.name) !== null && _a !== void 0 ? _a : s.callId; })
347
+ .join(", ")}]`,
348
+ });
349
+ },
350
+ event(type, matcher) {
351
+ const matched = matchingEvents(type, matcher);
352
+ const passed = matchCount(matched.length, matcher === null || matcher === void 0 ? void 0 : matcher.count);
353
+ return record({
354
+ name: `event(${type}${describeMatcherObject(matcher)})`,
355
+ passed,
356
+ detail: passed ? undefined : `matched ${matched.length}`,
357
+ });
358
+ },
359
+ notEvent(type, matcher) {
360
+ const matched = matchingEvents(type, matcher);
361
+ return record({
362
+ name: `notEvent(${type}${describeMatcherObject(matcher)})`,
363
+ passed: matched.length === 0,
364
+ detail: matched.length === 0 ? undefined : `matched ${matched.length}`,
365
+ });
366
+ },
367
+ eventOrder(matchers) {
368
+ const stream = events();
369
+ const label = `eventOrder([${matchers.map((m) => m.type).join(", ")}])`;
370
+ let cursor = 0;
371
+ for (const matcher of matchers) {
372
+ const required = typeof matcher.count === "number" ? matcher.count : 1;
373
+ let seen = 0;
374
+ while (seen < required && cursor < stream.length) {
375
+ const event = stream[cursor];
376
+ cursor++;
377
+ if (event.type === matcher.type &&
378
+ matchValue(event.data, matcher.data)) {
379
+ seen++;
380
+ }
381
+ }
382
+ if (seen < required) {
383
+ return record({
384
+ name: label,
385
+ passed: false,
386
+ detail: `only ${seen}/${required} ${matcher.type} event(s) in order`,
387
+ });
388
+ }
389
+ }
390
+ return record({ name: label, passed: true });
391
+ },
392
+ eventsSatisfy(label, predicate) {
393
+ let passed = false;
394
+ let detail;
395
+ try {
396
+ passed = predicate(events()) === true;
397
+ }
398
+ catch (error) {
399
+ detail = error instanceof Error ? error.message : String(error);
400
+ }
401
+ return record({
402
+ name: `eventsSatisfy(${label})`,
403
+ passed,
404
+ detail: passed ? undefined : (detail !== null && detail !== void 0 ? detail : label),
405
+ });
406
+ },
407
+ check(value, assertion) {
408
+ const outcome = evaluateExpectation(value, assertion);
409
+ return record({
410
+ name: `check(${assertion.kind})`,
411
+ passed: outcome.passed,
412
+ detail: outcome.detail,
413
+ score: outcome.score,
414
+ severity: expectationSeverity(assertion),
415
+ });
416
+ },
417
+ score(scoreName, value) {
418
+ const finite = Number.isFinite(value);
419
+ return record({
420
+ name: `score(${scoreName})`,
421
+ // A NaN score is a bug in the eval's own math, so fail loudly rather
422
+ // than reporting a number nobody can act on.
423
+ passed: finite,
424
+ severity: "soft",
425
+ score: finite ? value : undefined,
426
+ detail: finite ? undefined : `score is not a finite number: ${value}`,
427
+ });
428
+ },
429
+ requireToolCall(toolName, matcher) {
430
+ const matched = matchingToolCalls(toolName, matcher);
431
+ const assertionName = `requireToolCall(${toolName})`;
432
+ const first = matched[0];
433
+ record({
434
+ name: assertionName,
435
+ passed: first !== undefined,
436
+ detail: first === undefined ? "no matching tool call" : undefined,
437
+ });
438
+ if (first === undefined) {
439
+ throw new EvalRequireFailure(name(assertionName));
440
+ }
441
+ return first;
442
+ },
443
+ requireInputRequest(filter) {
444
+ const pending = facts().pendingInputRequests.filter((request) => matchValue(request.toolName, filter === null || filter === void 0 ? void 0 : filter.toolName) &&
445
+ matchValue(request.args, filter === null || filter === void 0 ? void 0 : filter.args));
446
+ const assertionName = "requireInputRequest";
447
+ const passed = pending.length === 1;
448
+ record({
449
+ name: assertionName,
450
+ passed,
451
+ detail: passed
452
+ ? undefined
453
+ : `expected exactly 1 pending approval request, found ${pending.length}`,
454
+ });
455
+ if (!passed) {
456
+ throw new EvalRequireFailure(name(assertionName));
457
+ }
458
+ return pending[0];
459
+ },
460
+ };
461
+ }