@copilotkit/aimock 1.44.0 → 1.45.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 (272) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +31 -0
  4. package/README.md +6 -0
  5. package/dist/agui-types.d.ts.map +1 -1
  6. package/dist/aimock-cli.cjs +31 -5
  7. package/dist/aimock-cli.cjs.map +1 -1
  8. package/dist/aimock-cli.d.cts.map +1 -1
  9. package/dist/aimock-cli.d.ts.map +1 -1
  10. package/dist/aimock-cli.js +31 -5
  11. package/dist/aimock-cli.js.map +1 -1
  12. package/dist/bedrock-converse.cjs +312 -39
  13. package/dist/bedrock-converse.cjs.map +1 -1
  14. package/dist/bedrock-converse.d.cts.map +1 -1
  15. package/dist/bedrock-converse.d.ts.map +1 -1
  16. package/dist/bedrock-converse.js +313 -40
  17. package/dist/bedrock-converse.js.map +1 -1
  18. package/dist/bedrock.cjs +195 -42
  19. package/dist/bedrock.cjs.map +1 -1
  20. package/dist/bedrock.d.cts.map +1 -1
  21. package/dist/bedrock.d.ts.map +1 -1
  22. package/dist/bedrock.js +196 -43
  23. package/dist/bedrock.js.map +1 -1
  24. package/dist/chaos.cjs +1 -0
  25. package/dist/chaos.js +1 -1
  26. package/dist/cli.cjs +152 -5
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +155 -8
  29. package/dist/cli.js.map +1 -1
  30. package/dist/cohere.cjs +132 -71
  31. package/dist/cohere.cjs.map +1 -1
  32. package/dist/cohere.d.cts.map +1 -1
  33. package/dist/cohere.d.ts.map +1 -1
  34. package/dist/cohere.js +133 -72
  35. package/dist/cohere.js.map +1 -1
  36. package/dist/config-loader.cjs +62 -4
  37. package/dist/config-loader.cjs.map +1 -1
  38. package/dist/config-loader.d.cts +37 -4
  39. package/dist/config-loader.d.cts.map +1 -1
  40. package/dist/config-loader.d.ts +37 -4
  41. package/dist/config-loader.d.ts.map +1 -1
  42. package/dist/config-loader.js +62 -4
  43. package/dist/config-loader.js.map +1 -1
  44. package/dist/fakes-plugin.cjs +131 -0
  45. package/dist/fakes-plugin.cjs.map +1 -0
  46. package/dist/fakes-plugin.d.cts +23 -0
  47. package/dist/fakes-plugin.d.cts.map +1 -0
  48. package/dist/fakes-plugin.d.ts +23 -0
  49. package/dist/fakes-plugin.d.ts.map +1 -0
  50. package/dist/fakes-plugin.js +127 -0
  51. package/dist/fakes-plugin.js.map +1 -0
  52. package/dist/fixture-loader-services.cjs +1 -1
  53. package/dist/fixture-loader-services.cjs.map +1 -1
  54. package/dist/fixture-loader-services.js +1 -1
  55. package/dist/fixture-loader-services.js.map +1 -1
  56. package/dist/fixture-loader.cjs +389 -27
  57. package/dist/fixture-loader.cjs.map +1 -1
  58. package/dist/fixture-loader.d.cts +47 -3
  59. package/dist/fixture-loader.d.cts.map +1 -1
  60. package/dist/fixture-loader.d.ts +47 -3
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +384 -29
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-interactions.cjs +250 -52
  65. package/dist/gemini-interactions.cjs.map +1 -1
  66. package/dist/gemini-interactions.d.cts.map +1 -1
  67. package/dist/gemini-interactions.d.ts.map +1 -1
  68. package/dist/gemini-interactions.js +251 -53
  69. package/dist/gemini-interactions.js.map +1 -1
  70. package/dist/gemini.cjs +226 -37
  71. package/dist/gemini.cjs.map +1 -1
  72. package/dist/gemini.d.cts.map +1 -1
  73. package/dist/gemini.d.ts.map +1 -1
  74. package/dist/gemini.js +227 -38
  75. package/dist/gemini.js.map +1 -1
  76. package/dist/helpers.cjs +881 -7
  77. package/dist/helpers.cjs.map +1 -1
  78. package/dist/helpers.d.cts +85 -4
  79. package/dist/helpers.d.cts.map +1 -1
  80. package/dist/helpers.d.ts +85 -4
  81. package/dist/helpers.d.ts.map +1 -1
  82. package/dist/helpers.js +839 -8
  83. package/dist/helpers.js.map +1 -1
  84. package/dist/index.cjs +9 -0
  85. package/dist/index.d.cts +7 -5
  86. package/dist/index.d.ts +7 -5
  87. package/dist/index.js +4 -3
  88. package/dist/jest.cjs +31 -7
  89. package/dist/jest.cjs.map +1 -1
  90. package/dist/jest.d.cts +43 -3
  91. package/dist/jest.d.cts.map +1 -1
  92. package/dist/jest.d.ts +43 -3
  93. package/dist/jest.d.ts.map +1 -1
  94. package/dist/jest.js +33 -9
  95. package/dist/jest.js.map +1 -1
  96. package/dist/journal.cjs +135 -13
  97. package/dist/journal.cjs.map +1 -1
  98. package/dist/journal.d.cts +38 -4
  99. package/dist/journal.d.cts.map +1 -1
  100. package/dist/journal.d.ts +38 -4
  101. package/dist/journal.d.ts.map +1 -1
  102. package/dist/journal.js +135 -13
  103. package/dist/journal.js.map +1 -1
  104. package/dist/live-sanitize.cjs +4 -32
  105. package/dist/live-sanitize.cjs.map +1 -1
  106. package/dist/live-sanitize.js +4 -32
  107. package/dist/live-sanitize.js.map +1 -1
  108. package/dist/llmock.cjs +123 -30
  109. package/dist/llmock.cjs.map +1 -1
  110. package/dist/llmock.d.cts +16 -1
  111. package/dist/llmock.d.cts.map +1 -1
  112. package/dist/llmock.d.ts +16 -1
  113. package/dist/llmock.d.ts.map +1 -1
  114. package/dist/llmock.js +125 -32
  115. package/dist/llmock.js.map +1 -1
  116. package/dist/mcp-fakes-mount.cjs +1 -0
  117. package/dist/mcp-fakes-mount.cjs.map +1 -1
  118. package/dist/mcp-fakes-mount.js +1 -0
  119. package/dist/mcp-fakes-mount.js.map +1 -1
  120. package/dist/mcp-fakes-report.cjs +111 -0
  121. package/dist/mcp-fakes-report.cjs.map +1 -0
  122. package/dist/mcp-fakes-report.d.cts +40 -0
  123. package/dist/mcp-fakes-report.d.cts.map +1 -0
  124. package/dist/mcp-fakes-report.d.ts +40 -0
  125. package/dist/mcp-fakes-report.d.ts.map +1 -0
  126. package/dist/mcp-fakes-report.js +107 -0
  127. package/dist/mcp-fakes-report.js.map +1 -0
  128. package/dist/mcp-fakes.cjs +331 -8
  129. package/dist/mcp-fakes.cjs.map +1 -1
  130. package/dist/mcp-fakes.d.cts +406 -3
  131. package/dist/mcp-fakes.d.cts.map +1 -1
  132. package/dist/mcp-fakes.d.ts +406 -3
  133. package/dist/mcp-fakes.d.ts.map +1 -1
  134. package/dist/mcp-fakes.js +331 -8
  135. package/dist/mcp-fakes.js.map +1 -1
  136. package/dist/mcp-handler.cjs +209 -16
  137. package/dist/mcp-handler.cjs.map +1 -1
  138. package/dist/mcp-handler.d.cts +49 -0
  139. package/dist/mcp-handler.d.cts.map +1 -0
  140. package/dist/mcp-handler.d.ts +49 -0
  141. package/dist/mcp-handler.d.ts.map +1 -0
  142. package/dist/mcp-handler.js +207 -17
  143. package/dist/mcp-handler.js.map +1 -1
  144. package/dist/mcp-mock.cjs +112 -1
  145. package/dist/mcp-mock.cjs.map +1 -1
  146. package/dist/mcp-mock.d.cts +33 -2
  147. package/dist/mcp-mock.d.cts.map +1 -1
  148. package/dist/mcp-mock.d.ts +33 -2
  149. package/dist/mcp-mock.d.ts.map +1 -1
  150. package/dist/mcp-mock.js +112 -1
  151. package/dist/mcp-mock.js.map +1 -1
  152. package/dist/mcp-recorder.cjs +1224 -0
  153. package/dist/mcp-recorder.cjs.map +1 -0
  154. package/dist/mcp-recorder.d.cts +108 -0
  155. package/dist/mcp-recorder.d.cts.map +1 -0
  156. package/dist/mcp-recorder.d.ts +108 -0
  157. package/dist/mcp-recorder.d.ts.map +1 -0
  158. package/dist/mcp-recorder.js +1215 -0
  159. package/dist/mcp-recorder.js.map +1 -0
  160. package/dist/messages.cjs +362 -56
  161. package/dist/messages.cjs.map +1 -1
  162. package/dist/messages.d.cts.map +1 -1
  163. package/dist/messages.d.ts.map +1 -1
  164. package/dist/messages.js +363 -58
  165. package/dist/messages.js.map +1 -1
  166. package/dist/misbehavior.cjs +1523 -0
  167. package/dist/misbehavior.cjs.map +1 -0
  168. package/dist/misbehavior.d.cts +43 -0
  169. package/dist/misbehavior.d.cts.map +1 -0
  170. package/dist/misbehavior.d.ts +43 -0
  171. package/dist/misbehavior.d.ts.map +1 -0
  172. package/dist/misbehavior.js +1511 -0
  173. package/dist/misbehavior.js.map +1 -0
  174. package/dist/ollama.cjs +310 -112
  175. package/dist/ollama.cjs.map +1 -1
  176. package/dist/ollama.d.cts.map +1 -1
  177. package/dist/ollama.d.ts.map +1 -1
  178. package/dist/ollama.js +311 -113
  179. package/dist/ollama.js.map +1 -1
  180. package/dist/record-sanitize.cjs +231 -0
  181. package/dist/record-sanitize.cjs.map +1 -0
  182. package/dist/record-sanitize.js +224 -0
  183. package/dist/record-sanitize.js.map +1 -0
  184. package/dist/recorder.cjs +106 -5
  185. package/dist/recorder.cjs.map +1 -1
  186. package/dist/recorder.d.cts +2 -1
  187. package/dist/recorder.d.cts.map +1 -1
  188. package/dist/recorder.d.ts +2 -1
  189. package/dist/recorder.d.ts.map +1 -1
  190. package/dist/recorder.js +107 -7
  191. package/dist/recorder.js.map +1 -1
  192. package/dist/responses.cjs +667 -171
  193. package/dist/responses.cjs.map +1 -1
  194. package/dist/responses.d.cts +5 -13
  195. package/dist/responses.d.cts.map +1 -1
  196. package/dist/responses.d.ts +5 -13
  197. package/dist/responses.d.ts.map +1 -1
  198. package/dist/responses.js +665 -174
  199. package/dist/responses.js.map +1 -1
  200. package/dist/router.cjs +24 -2
  201. package/dist/router.cjs.map +1 -1
  202. package/dist/router.d.cts.map +1 -1
  203. package/dist/router.d.ts.map +1 -1
  204. package/dist/router.js +25 -4
  205. package/dist/router.js.map +1 -1
  206. package/dist/server.cjs +572 -76
  207. package/dist/server.cjs.map +1 -1
  208. package/dist/server.d.cts.map +1 -1
  209. package/dist/server.d.ts.map +1 -1
  210. package/dist/server.js +574 -78
  211. package/dist/server.js.map +1 -1
  212. package/dist/stream-collapse.cjs +236 -34
  213. package/dist/stream-collapse.cjs.map +1 -1
  214. package/dist/stream-collapse.d.cts +39 -4
  215. package/dist/stream-collapse.d.cts.map +1 -1
  216. package/dist/stream-collapse.d.ts +39 -4
  217. package/dist/stream-collapse.d.ts.map +1 -1
  218. package/dist/stream-collapse.js +235 -35
  219. package/dist/stream-collapse.js.map +1 -1
  220. package/dist/types.cjs.map +1 -1
  221. package/dist/types.d.cts +315 -4
  222. package/dist/types.d.cts.map +1 -1
  223. package/dist/types.d.ts +315 -4
  224. package/dist/types.d.ts.map +1 -1
  225. package/dist/types.js.map +1 -1
  226. package/dist/validate-cli.cjs +36 -4
  227. package/dist/validate-cli.cjs.map +1 -1
  228. package/dist/validate-cli.js +37 -5
  229. package/dist/validate-cli.js.map +1 -1
  230. package/dist/vector-types.d.ts.map +1 -1
  231. package/dist/version.cjs +45 -0
  232. package/dist/version.cjs.map +1 -0
  233. package/dist/version.js +44 -0
  234. package/dist/version.js.map +1 -0
  235. package/dist/vitest.cjs +66 -10
  236. package/dist/vitest.cjs.map +1 -1
  237. package/dist/vitest.d.cts +35 -4
  238. package/dist/vitest.d.cts.map +1 -1
  239. package/dist/vitest.d.ts +35 -4
  240. package/dist/vitest.d.ts.map +1 -1
  241. package/dist/vitest.js +67 -11
  242. package/dist/vitest.js.map +1 -1
  243. package/dist/watcher.cjs +6 -2
  244. package/dist/watcher.cjs.map +1 -1
  245. package/dist/watcher.js +6 -2
  246. package/dist/watcher.js.map +1 -1
  247. package/dist/ws-gemini-live.cjs +210 -101
  248. package/dist/ws-gemini-live.cjs.map +1 -1
  249. package/dist/ws-gemini-live.d.cts +9 -3
  250. package/dist/ws-gemini-live.d.cts.map +1 -1
  251. package/dist/ws-gemini-live.d.ts +9 -3
  252. package/dist/ws-gemini-live.d.ts.map +1 -1
  253. package/dist/ws-gemini-live.js +211 -102
  254. package/dist/ws-gemini-live.js.map +1 -1
  255. package/dist/ws-realtime.cjs +175 -62
  256. package/dist/ws-realtime.cjs.map +1 -1
  257. package/dist/ws-realtime.d.cts +8 -3
  258. package/dist/ws-realtime.d.cts.map +1 -1
  259. package/dist/ws-realtime.d.ts +8 -3
  260. package/dist/ws-realtime.d.ts.map +1 -1
  261. package/dist/ws-realtime.js +176 -63
  262. package/dist/ws-realtime.js.map +1 -1
  263. package/dist/ws-responses.cjs +109 -16
  264. package/dist/ws-responses.cjs.map +1 -1
  265. package/dist/ws-responses.d.cts +8 -12
  266. package/dist/ws-responses.d.cts.map +1 -1
  267. package/dist/ws-responses.d.ts +8 -12
  268. package/dist/ws-responses.d.ts.map +1 -1
  269. package/dist/ws-responses.js +111 -18
  270. package/dist/ws-responses.js.map +1 -1
  271. package/package.json +10 -2
  272. package/skills/write-fixtures/SKILL.md +179 -21
@@ -1,4 +1,5 @@
1
1
  const require_constants = require('./constants.cjs');
2
+ const require_echo_text = require('./echo-text.cjs');
2
3
  const require_message_text = require('./message-text.cjs');
3
4
  const require_fixture_loader = require('./fixture-loader.cjs');
4
5
 
@@ -46,7 +47,10 @@ const BLOCK_KEYS = new Set([
46
47
  "scope",
47
48
  "mount",
48
49
  "undeclaredTools",
49
- "tools"
50
+ "tools",
51
+ "list",
52
+ "recorded",
53
+ "timing"
50
54
  ]);
51
55
  const TOOL_KEYS = new Set([
52
56
  "name",
@@ -59,8 +63,21 @@ const CALL_KEYS = new Set([
59
63
  "anyArgs",
60
64
  "result",
61
65
  "error",
62
- "id"
66
+ "id",
67
+ "notifications",
68
+ "durationMs"
63
69
  ]);
70
+ /**
71
+ * C9: most events one fake event log keeps (per test id, and the untagged
72
+ * log). Past it the oldest event is dropped and the log's report says
73
+ * `evicted: true`. Report-only: claims and snapshots never read it.
74
+ */
75
+ const MCP_FAKES_MAX_EVENTS_PER_LOG = 1e3;
76
+ /**
77
+ * Largest `args` (JSON, UTF-8 bytes) an event keeps. A larger one is stored as
78
+ * `{ __aimock_truncated: true, originalByteSize }`, the journal `capBody` shape.
79
+ */
80
+ const MCP_FAKES_MAX_EVENT_ARGS_BYTES = 4096;
64
81
  const SCOPE_KEYS = new Set(["testId", "context"]);
65
82
  const TIER_ORDER = [
66
83
  "testId+context",
@@ -68,6 +85,29 @@ const TIER_ORDER = [
68
85
  "context",
69
86
  "shared"
70
87
  ];
88
+ /** The last `seq` given to an event, shared by every store in the process. */
89
+ let lastEventSeq = 0;
90
+ /**
91
+ * An event's `args` as the log keeps it: a frozen copy when its JSON is at
92
+ * most `MCP_FAKES_MAX_EVENT_ARGS_BYTES` UTF-8 bytes, else the journal's
93
+ * truncation marker. A value with no JSON text (`undefined`, or one whose
94
+ * `JSON.stringify` throws) is kept as its rendered text.
95
+ */
96
+ function boundedArgs(args) {
97
+ let text;
98
+ try {
99
+ text = JSON.stringify(args);
100
+ } catch {
101
+ text = void 0;
102
+ }
103
+ if (text === void 0) return args === void 0 ? void 0 : echo(args);
104
+ const size = Buffer.byteLength(text, "utf8");
105
+ if (size > MCP_FAKES_MAX_EVENT_ARGS_BYTES) return Object.freeze({
106
+ __aimock_truncated: true,
107
+ originalByteSize: size
108
+ });
109
+ return deepFreeze(JSON.parse(text));
110
+ }
71
111
  /**
72
112
  * The error a failed add throws. It is a `FixtureLoadError` whose `rule`,
73
113
  * `file`, `blockId` and `entryId` are those of the first error, and whose
@@ -150,6 +190,17 @@ function hasOwnKey(obj, key) {
150
190
  function shown(value, max = require_constants.MCP_FAKES_ECHO_LIMIT) {
151
191
  return require_message_text.jsonText(render(value), max);
152
192
  }
193
+ /**
194
+ * One user value as message text: `shown`, built alone, so cut to `limit`
195
+ * chars as the builder cuts it. A `limit` below 1 keeps nothing (`""`), as
196
+ * `cutText` does; a `NaN` limit is a `RangeError`.
197
+ *
198
+ * @internal
199
+ */
200
+ function echo(value, limit = require_constants.MCP_FAKES_ECHO_LIMIT) {
201
+ if (!(limit >= 1)) return require_echo_text.cutText(render(value), limit, "json");
202
+ return require_message_text.build(require_message_text.msg`${shown(value, limit)}`);
203
+ }
153
204
  function render(value) {
154
205
  if (value === void 0) return "undefined";
155
206
  if (typeof value === "number" && !Number.isFinite(value)) return String(value);
@@ -651,10 +702,11 @@ function shape(required, optional = {}) {
651
702
  * `Annotations.lastModified`.
652
703
  */
653
704
  const ISO_DATETIME = /^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
705
+ const mustBeIsoDateTime = (v, p) => typeof v === "string" && ISO_DATETIME.test(v) ? null : require_message_text.msg`${pathPart(p)} must be an ISO 8601 date-time with seconds and an offset, got ${shown(v)}`;
654
706
  const ANNOTATIONS = shape({}, {
655
707
  audience: arrayOf(oneOf(["user", "assistant"])),
656
708
  priority: (v, p) => typeof v === "number" && v >= 0 && v <= 1 ? null : require_message_text.msg`${pathPart(p)} must be a number from 0 to 1, got ${shown(v)}`,
657
- lastModified: (v, p) => typeof v === "string" && ISO_DATETIME.test(v) ? null : require_message_text.msg`${pathPart(p)} must be an ISO 8601 date-time with seconds and an offset, got ${shown(v)}`
709
+ lastModified: mustBeIsoDateTime
658
710
  });
659
711
  const ICON = shape({ src: mustBeString }, {
660
712
  mimeType: mustBeString,
@@ -772,6 +824,59 @@ function inputSchemaProblem(schema) {
772
824
  if (!isPlainObject(schema)) return mustBeObject(schema, "inputSchema");
773
825
  return jsonProblem(schema, "inputSchema") ?? INPUT_SCHEMA(schema, "inputSchema");
774
826
  }
827
+ /** A finite number >= 0 (FA3 `atMs`, FA4 `durationMs`). */
828
+ const mustBeNonNegative = (v, p) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? null : require_message_text.msg`${pathPart(p)} must be a number >= 0, got ${shown(v)}`;
829
+ /** FA2 `recorded`. Other keys are not checked. */
830
+ const RECORDED = shape({
831
+ upstream: mustBeString,
832
+ protocolVersion: mustBeString,
833
+ aimockVersion: mustBeString,
834
+ at: mustBeIsoDateTime
835
+ }, { serverInfo: mustBeObject });
836
+ /** One FA3 notification. Other keys are not checked. */
837
+ const NOTIFICATION = shape({
838
+ atMs: mustBeNonNegative,
839
+ method: mustBeString,
840
+ params: mustBeObject
841
+ });
842
+ /** FA1: an array of JSON objects, each with a string `name`, no name twice. */
843
+ function listProblem(v) {
844
+ if (!Array.isArray(v)) return require_message_text.msg`list must be an array of tool objects, got ${shown(v)}`;
845
+ const json = jsonProblem(v, "list");
846
+ if (json) return json;
847
+ const names = /* @__PURE__ */ new Set();
848
+ for (let i = 0; i < v.length; i++) {
849
+ const tool = v[i];
850
+ if (!isPlainObject(tool) || typeof tool.name !== "string") return require_message_text.msg`list[${i}] must be a tool object with a string name, got ${shown(tool)}`;
851
+ if (names.has(tool.name)) return require_message_text.msg`list[${i}] repeats the tool name ${require_message_text.quote(tool.name)}`;
852
+ names.add(tool.name);
853
+ }
854
+ return null;
855
+ }
856
+ /** FA2. */
857
+ function recordedProblem(v) {
858
+ if (!isPlainObject(v)) return require_message_text.msg`recorded must be a JSON object, got ${shown(v)}`;
859
+ return jsonProblem(v, "recorded") ?? RECORDED(v, "recorded");
860
+ }
861
+ /** FA3. */
862
+ function notificationsProblem(v) {
863
+ if (!Array.isArray(v)) return require_message_text.msg`notifications must be an array, got ${shown(v)}`;
864
+ return jsonProblem(v, "notifications") ?? arrayOf(NOTIFICATION, "an array of notifications")(v, "notifications");
865
+ }
866
+ /**
867
+ * A malformed FA key of one block (and, for FA3/FA4, one entry): the 1.44.0
868
+ * rule `mcp-fakes/bad-block:h`, with the `record-replay/fa-*` value first in
869
+ * the message detail (`[mcp-fakes/bad-block:h] [record-replay/fa-list] ...`).
870
+ */
871
+ function faError(ctx, faRule, detail, entryId = null) {
872
+ return new require_fixture_loader.FixtureLoadError({
873
+ rule: "mcp-fakes/bad-block:h",
874
+ file: ctx.source,
875
+ blockId: ctx.blockId,
876
+ entryId,
877
+ detail: require_message_text.msg`[${require_message_text.fixed(faRule)}] ${detail}`
878
+ });
879
+ }
775
880
  /**
776
881
  * Validate one call entry of the input snapshot. Every error goes to
777
882
  * `errors`; returns its entry id whether or not it is clean, so that the
@@ -795,6 +900,14 @@ function validateCall(raw, toolName, label, index, ctx, errors) {
795
900
  }
796
901
  const before = errors.length;
797
902
  for (const key of presentKeys(raw)) if (!CALL_KEYS.has(key)) bad("h", require_message_text.msg`has unknown key ${shown(key)}`);
903
+ if (has(raw, "notifications")) {
904
+ const problem = notificationsProblem(raw.notifications);
905
+ if (problem) errors.push(faError(ctx, "record-replay/fa-notifications", require_message_text.msg`${where} ${problem}`, entryId));
906
+ }
907
+ if (has(raw, "durationMs")) {
908
+ const problem = mustBeNonNegative(raw.durationMs, "durationMs");
909
+ if (problem) errors.push(faError(ctx, "record-replay/fa-duration", require_message_text.msg`${where} ${problem}`, entryId));
910
+ }
798
911
  if (has(raw, "id") && !isNonEmptyString(raw.id)) bad("d", require_message_text.msg`id must be a non-empty string, got ${shown(raw.id)}`);
799
912
  if (has(raw, "anyArgs") && raw.anyArgs !== true) bad("j", require_message_text.msg`anyArgs must be true, got ${shown(raw.anyArgs)}`);
800
913
  const hasArgs = has(raw, "args");
@@ -817,13 +930,17 @@ function validateCall(raw, toolName, label, index, ctx, errors) {
817
930
  };
818
931
  const match = hasArgs ? { args: copyJson(raw.args, true) } : { anyArgs: true };
819
932
  const answer = hasResult ? { result: copyJson(raw.result, true) } : { error: raw.error };
933
+ const recorded = {};
934
+ if (has(raw, "notifications")) recorded.notifications = copyJson(raw.notifications, true);
935
+ if (has(raw, "durationMs")) recorded.durationMs = raw.durationMs;
820
936
  return {
821
937
  entryId,
822
938
  call: Object.freeze({
823
939
  id: entryId,
824
940
  index,
825
941
  ...match,
826
- ...answer
942
+ ...answer,
943
+ ...recorded
827
944
  })
828
945
  };
829
946
  }
@@ -891,6 +1008,17 @@ function validateBlock(raw, source, blockIndex, load, errors) {
891
1008
  }
892
1009
  const before = errors.length;
893
1010
  for (const key of presentKeys(raw)) if (!BLOCK_KEYS.has(key)) errors.push(badBlock(ctx, "h", require_message_text.msg`block has unknown key ${shown(key)}`));
1011
+ if (has(raw, "list")) {
1012
+ const problem = listProblem(raw.list);
1013
+ if (problem) errors.push(faError(ctx, "record-replay/fa-list", problem));
1014
+ }
1015
+ if (has(raw, "recorded")) {
1016
+ const problem = recordedProblem(raw.recorded);
1017
+ if (problem) errors.push(faError(ctx, "record-replay/fa-recorded", problem));
1018
+ }
1019
+ let timing = "recorded";
1020
+ if (has(raw, "timing")) if (raw.timing === "recorded" || raw.timing === "immediate") timing = raw.timing;
1021
+ else errors.push(faError(ctx, "record-replay/fa-timing", require_message_text.msg`timing must be "recorded" or "immediate", got ${shown(raw.timing)}`));
894
1022
  const scope = validateScope(raw.scope, ctx, errors);
895
1023
  let undeclared;
896
1024
  if (has(raw, "undeclaredTools")) if (raw.undeclaredTools === "allow" || raw.undeclaredTools === "deny") undeclared = raw.undeclaredTools;
@@ -947,7 +1075,10 @@ function validateBlock(raw, source, blockIndex, load, errors) {
947
1075
  tier: tierOf(scope),
948
1076
  mount,
949
1077
  ...undeclared !== void 0 ? { undeclaredTools: undeclared } : {},
950
- tools: Object.freeze(tools)
1078
+ tools: Object.freeze(tools),
1079
+ ...has(raw, "list") ? { list: copyJson(raw.list, true) } : {},
1080
+ ...has(raw, "recorded") ? { recorded: copyJson(raw.recorded, true) } : {},
1081
+ timing
951
1082
  }),
952
1083
  entryIds,
953
1084
  warnings
@@ -1194,6 +1325,21 @@ var McpFakeStore = class {
1194
1325
  */
1195
1326
  loadCounts = /* @__PURE__ */ new Map();
1196
1327
  maxTestIds;
1328
+ /**
1329
+ * RP2 fake event logs: supplied test id → its events, oldest first. Map
1330
+ * insertion order is the FIFO order of the test-id cap.
1331
+ */
1332
+ events = /* @__PURE__ */ new Map();
1333
+ /** The event log of requests with no test id: a ring of the newest events. */
1334
+ untaggedEvents = [];
1335
+ /** The untagged event log dropped an event (C9). */
1336
+ untaggedOverflow = false;
1337
+ /**
1338
+ * C9: test ids whose event log overflowed or was evicted, so their report
1339
+ * is incomplete (`evicted: true`). Report-only: `claim`, `snapshot` and
1340
+ * `policy` never read it, so tool calls are unchanged.
1341
+ */
1342
+ reportOverflow = /* @__PURE__ */ new Set();
1197
1343
  constructor(options = {}) {
1198
1344
  this.maxTestIds = checkCap(options.maxTestIds ?? MCP_FAKES_DEFAULT_MAX_TEST_IDS);
1199
1345
  }
@@ -1238,10 +1384,10 @@ var McpFakeStore = class {
1238
1384
  add(sources, origin) {
1239
1385
  const originData = snapshotInput(origin);
1240
1386
  const kind = isPlainObject(originData) ? originData.kind : void 0;
1241
- if (kind !== "file" && kind !== "code" && kind !== "control-api") throw new McpFakesAddError([badBlock({
1387
+ if (kind !== "file" && kind !== "code" && kind !== "control-api" && kind !== "record") throw new McpFakesAddError([badBlock({
1242
1388
  source: null,
1243
1389
  blockId: null
1244
- }, "i", isPlainObject(originData) ? require_message_text.msg`origin.kind must be "file", "code" or "control-api", got ${shown(kind)}` : require_message_text.msg`origin must be an object { kind }, got ${shown(originData)}`)], []);
1390
+ }, "i", isPlainObject(originData) ? require_message_text.msg`origin.kind must be "file", "code", "control-api" or "record", got ${shown(kind)}` : require_message_text.msg`origin must be an object { kind }, got ${shown(originData)}`)], []);
1245
1391
  const runtimeNumber = kind === "file" ? null : this.nextRuntimeNumber(kind);
1246
1392
  const runtimeSource = runtimeNumber === null ? null : `${kind}#${runtimeNumber}`;
1247
1393
  const input = snapshotInput(sources, MCP_FAKES_MAX_DEPTH + 2);
@@ -1390,11 +1536,172 @@ var McpFakeStore = class {
1390
1536
  this.consumed.clear();
1391
1537
  this.untagged.clear();
1392
1538
  this.evicted.clear();
1539
+ this.events.clear();
1540
+ this.reportOverflow.clear();
1541
+ this.untaggedEvents = [];
1542
+ this.untaggedOverflow = false;
1393
1543
  return;
1394
1544
  }
1395
1545
  this.consumed.delete(testId);
1396
1546
  this.evicted.delete(testId);
1397
- if (testId === MCP_FAKES_DEFAULT_TEST_ID) this.untagged.clear();
1547
+ this.events.delete(testId);
1548
+ this.reportOverflow.delete(testId);
1549
+ if (testId === MCP_FAKES_DEFAULT_TEST_ID) {
1550
+ this.untagged.clear();
1551
+ this.untaggedEvents = [];
1552
+ this.untaggedOverflow = false;
1553
+ }
1554
+ }
1555
+ /**
1556
+ * MR12: mark entries consumed for `testId` (null = untagged), as a claim
1557
+ * would. Ids not loaded on this mount are ignored, and so is a test id the
1558
+ * cap evicted (its claims fail as `evicted` until it is reset).
1559
+ */
1560
+ markConsumed(testId, entryIds) {
1561
+ if (testId !== null && this.evicted.has(testId)) return;
1562
+ const known = entryIds.filter((entryId) => this.ids.has(entryId));
1563
+ if (known.length === 0) return;
1564
+ const state = this.stateFor(testId);
1565
+ for (const entryId of known) state.add(entryId);
1566
+ }
1567
+ /**
1568
+ * AM7/FA1: the `list` of the most specific applicable tier that has one,
1569
+ * else null. Inside a tier, the first block in load order with a `list`.
1570
+ */
1571
+ recordedList(identity) {
1572
+ const blocks = this.applicable(identity);
1573
+ for (const tier of TIER_ORDER) {
1574
+ const block = blocks.find((b) => b.tier === tier && b.list !== void 0);
1575
+ if (block?.list) return block.list;
1576
+ }
1577
+ return null;
1578
+ }
1579
+ /** FA5 timing and the FA3/FA4 values of an answered entry (for MR15 framing). */
1580
+ replayOf(entryId) {
1581
+ for (const block of this.blocks) for (const tool of block.tools) {
1582
+ const entry = tool.calls.find((c) => c.id === entryId);
1583
+ if (entry) return {
1584
+ timing: block.timing,
1585
+ notifications: entry.notifications ?? NO_NOTIFICATIONS,
1586
+ durationMs: entry.durationMs
1587
+ };
1588
+ }
1589
+ return null;
1590
+ }
1591
+ /** C7: true when any loaded call entry has a `notifications/message` (gates logging). */
1592
+ hasRecordedLogs() {
1593
+ return this.blocks.some((block) => block.tools.some((tool) => tool.calls.some((call) => call.notifications?.some((n) => n.method === "notifications/message"))));
1594
+ }
1595
+ /**
1596
+ * RP2: append one event to the identity's log, with the next process-wide
1597
+ * `seq` and its `args` bounded (`MCP_FAKES_MAX_EVENT_ARGS_BYTES`). A log
1598
+ * past `MCP_FAKES_MAX_EVENTS_PER_LOG` drops its oldest event and its report
1599
+ * says `evicted: true`; a new test id past the test-id cap evicts the
1600
+ * oldest test id's log the same way (C9). Never touches consumption state.
1601
+ */
1602
+ logEvent(identity, event) {
1603
+ lastEventSeq += 1;
1604
+ const stored = Object.freeze({
1605
+ ...event,
1606
+ args: boundedArgs(event.args),
1607
+ seq: lastEventSeq
1608
+ });
1609
+ const testId = identity.testId;
1610
+ if (testId === null) {
1611
+ this.untaggedEvents.push(stored);
1612
+ if (this.untaggedEvents.length > MCP_FAKES_MAX_EVENTS_PER_LOG) {
1613
+ this.untaggedEvents.shift();
1614
+ this.untaggedOverflow = true;
1615
+ }
1616
+ return;
1617
+ }
1618
+ let log = this.events.get(testId);
1619
+ if (!log) {
1620
+ log = [];
1621
+ this.events.set(testId, log);
1622
+ this.enforceEventCap();
1623
+ }
1624
+ log.push(stored);
1625
+ if (log.length > MCP_FAKES_MAX_EVENTS_PER_LOG) {
1626
+ log.shift();
1627
+ this.reportOverflow.add(testId);
1628
+ }
1629
+ }
1630
+ /**
1631
+ * RP3 parts for this mount (report assembly is src/mcp-fakes-report.ts).
1632
+ * `served`, `failures` and `unfaked` come from the events whose context is
1633
+ * exactly `context`, in `seq` order; `unconsumed` (tiers `testId+context`
1634
+ * and `testId`) and `sharedUnconsumed` (tiers `context` and `shared`) are
1635
+ * the applicable entries of this store that `testId` has not consumed,
1636
+ * labelled with `mount`, the path the store's mock is served at. Every
1637
+ * block in the store answers there (`claim` does not read `block.mount`),
1638
+ * so none is left out by its own `mount` value: a mock that loaded its own
1639
+ * fakes (default mount "/mcp") and is mounted at another path still reports
1640
+ * them. Reads only: building a report changes no state.
1641
+ */
1642
+ reportPart(testId, context, mount) {
1643
+ const part = {
1644
+ evicted: testId === null ? this.untaggedOverflow : this.evicted.has(testId) || this.reportOverflow.has(testId),
1645
+ served: [],
1646
+ unconsumed: [],
1647
+ unfaked: [],
1648
+ failures: [],
1649
+ sharedUnconsumed: []
1650
+ };
1651
+ const log = testId === null ? this.untaggedEvents : this.events.get(testId) ?? [];
1652
+ for (const event of log) {
1653
+ if (event.context !== context) continue;
1654
+ const { seq, tool, args } = event;
1655
+ if (event.outcome === "answered") part.served.push({
1656
+ entryId: event.entryId,
1657
+ mount: event.mount,
1658
+ tool,
1659
+ args,
1660
+ seq
1661
+ });
1662
+ else if (event.outcome === "unfaked") part.unfaked.push({
1663
+ mount: event.mount,
1664
+ tool,
1665
+ args,
1666
+ answeredBy: event.answeredBy
1667
+ });
1668
+ else part.failures.push({
1669
+ code: event.code,
1670
+ mount: event.mount,
1671
+ tool,
1672
+ args,
1673
+ seq
1674
+ });
1675
+ }
1676
+ const used = this.stateOf(testId);
1677
+ for (const block of this.applicable({
1678
+ testId,
1679
+ context,
1680
+ undeclared: null
1681
+ })) {
1682
+ const own = block.tier === "testId+context" || block.tier === "testId";
1683
+ for (const tool of block.tools) for (const call of tool.calls) {
1684
+ if (used?.has(call.id)) continue;
1685
+ if (!own) part.sharedUnconsumed.push({
1686
+ entryId: call.id,
1687
+ mount,
1688
+ tool: tool.name
1689
+ });
1690
+ else if (call.args) part.unconsumed.push({
1691
+ entryId: call.id,
1692
+ mount,
1693
+ tool: tool.name,
1694
+ args: copyJson(call.args)
1695
+ });
1696
+ else part.unconsumed.push({
1697
+ entryId: call.id,
1698
+ mount,
1699
+ tool: tool.name,
1700
+ anyArgs: true
1701
+ });
1702
+ }
1703
+ }
1704
+ return part;
1398
1705
  }
1399
1706
  /** Every entry id on this mount, in load order. */
1400
1707
  allEntryIds() {
@@ -1581,9 +1888,25 @@ var McpFakeStore = class {
1581
1888
  if (this.consumed.size <= this.maxTestIds) break;
1582
1889
  this.consumed.delete(oldest);
1583
1890
  this.evicted.add(oldest);
1891
+ if (this.events.delete(oldest)) this.reportOverflow.add(oldest);
1892
+ }
1893
+ this.enforceEventCap();
1894
+ }
1895
+ /**
1896
+ * The test-id FIFO cap on event logs: evict the oldest logs until the cap
1897
+ * holds, marking each in the report-only `reportOverflow` (never `evicted`).
1898
+ */
1899
+ enforceEventCap() {
1900
+ if (this.maxTestIds === 0) return;
1901
+ for (const oldest of this.events.keys()) {
1902
+ if (this.events.size <= this.maxTestIds) break;
1903
+ this.events.delete(oldest);
1904
+ this.reportOverflow.add(oldest);
1584
1905
  }
1585
1906
  }
1586
1907
  };
1908
+ /** `replayOf` notifications of an entry that recorded none. */
1909
+ const NO_NOTIFICATIONS = Object.freeze([]);
1587
1910
  /**
1588
1911
  * The detail of a block-id collision (bad block (f)). The message prefix
1589
1912
  * already names the block id, so the detail names the two sources.