@cef-ai/testing 0.1.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 (78) hide show
  1. package/README.md +132 -0
  2. package/dist/harness/clock.d.ts +26 -0
  3. package/dist/harness/clock.d.ts.map +1 -0
  4. package/dist/harness/clock.js +17 -0
  5. package/dist/harness/clock.js.map +1 -0
  6. package/dist/harness/cubby.d.ts +57 -0
  7. package/dist/harness/cubby.d.ts.map +1 -0
  8. package/dist/harness/cubby.js +94 -0
  9. package/dist/harness/cubby.js.map +1 -0
  10. package/dist/harness/dispatch.d.ts +31 -0
  11. package/dist/harness/dispatch.d.ts.map +1 -0
  12. package/dist/harness/dispatch.js +37 -0
  13. package/dist/harness/dispatch.js.map +1 -0
  14. package/dist/harness/fetch-mock.d.ts +43 -0
  15. package/dist/harness/fetch-mock.d.ts.map +1 -0
  16. package/dist/harness/fetch-mock.js +58 -0
  17. package/dist/harness/fetch-mock.js.map +1 -0
  18. package/dist/harness/lifecycle.d.ts +29 -0
  19. package/dist/harness/lifecycle.d.ts.map +1 -0
  20. package/dist/harness/lifecycle.js +37 -0
  21. package/dist/harness/lifecycle.js.map +1 -0
  22. package/dist/harness/model-mock.d.ts +49 -0
  23. package/dist/harness/model-mock.d.ts.map +1 -0
  24. package/dist/harness/model-mock.js +56 -0
  25. package/dist/harness/model-mock.js.map +1 -0
  26. package/dist/harness/publish.d.ts +29 -0
  27. package/dist/harness/publish.d.ts.map +1 -0
  28. package/dist/harness/publish.js +20 -0
  29. package/dist/harness/publish.js.map +1 -0
  30. package/dist/harness/replay.d.ts +32 -0
  31. package/dist/harness/replay.d.ts.map +1 -0
  32. package/dist/harness/replay.js +72 -0
  33. package/dist/harness/replay.js.map +1 -0
  34. package/dist/harness/snapshot.d.ts +44 -0
  35. package/dist/harness/snapshot.d.ts.map +1 -0
  36. package/dist/harness/snapshot.js +58 -0
  37. package/dist/harness/snapshot.js.map +1 -0
  38. package/dist/index.d.ts +9 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +4 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/peers/from-marketplace.d.ts +19 -0
  43. package/dist/peers/from-marketplace.d.ts.map +1 -0
  44. package/dist/peers/from-marketplace.js +22 -0
  45. package/dist/peers/from-marketplace.js.map +1 -0
  46. package/dist/peers/mock-agent.d.ts +108 -0
  47. package/dist/peers/mock-agent.d.ts.map +1 -0
  48. package/dist/peers/mock-agent.js +113 -0
  49. package/dist/peers/mock-agent.js.map +1 -0
  50. package/dist/platform/event-runtime.d.ts +28 -0
  51. package/dist/platform/event-runtime.d.ts.map +1 -0
  52. package/dist/platform/event-runtime.js +38 -0
  53. package/dist/platform/event-runtime.js.map +1 -0
  54. package/dist/platform/orchestrator.d.ts +86 -0
  55. package/dist/platform/orchestrator.d.ts.map +1 -0
  56. package/dist/platform/orchestrator.js +164 -0
  57. package/dist/platform/orchestrator.js.map +1 -0
  58. package/dist/platform/runtime.d.ts +66 -0
  59. package/dist/platform/runtime.d.ts.map +1 -0
  60. package/dist/platform/runtime.js +116 -0
  61. package/dist/platform/runtime.js.map +1 -0
  62. package/dist/platform/targeting.d.ts +29 -0
  63. package/dist/platform/targeting.d.ts.map +1 -0
  64. package/dist/platform/targeting.js +64 -0
  65. package/dist/platform/targeting.js.map +1 -0
  66. package/dist/platform/vault.d.ts +185 -0
  67. package/dist/platform/vault.d.ts.map +1 -0
  68. package/dist/platform/vault.js +200 -0
  69. package/dist/platform/vault.js.map +1 -0
  70. package/dist/test-agent.d.ts +137 -0
  71. package/dist/test-agent.d.ts.map +1 -0
  72. package/dist/test-agent.js +223 -0
  73. package/dist/test-agent.js.map +1 -0
  74. package/dist/test-platform.d.ts +167 -0
  75. package/dist/test-platform.d.ts.map +1 -0
  76. package/dist/test-platform.js +352 -0
  77. package/dist/test-platform.js.map +1 -0
  78. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @cef-ai/testing
2
+
3
+ Simulator harness for CEF agents — `testAgent` for single-agent in-process
4
+ tests, `testPlatform` for multi-agent tests with a vault simulation.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ pnpm add -D @cef-ai/testing
10
+ ```
11
+
12
+ Import the harness directly from `@cef-ai/testing` in test files:
13
+
14
+ ```ts
15
+ import { testAgent, testPlatform } from "@cef-ai/testing";
16
+ ```
17
+
18
+ ## `testAgent` quickstart
19
+
20
+ `testAgent(AgentClass, opts)` constructs an in-memory simulator around a
21
+ single agent class. It wires `ctx.cubby` to per-alias SQLite, `ctx.fetch`
22
+ to a URL-pattern mock, `ctx.models[alias]` to stubs, and `ctx.publish` to a
23
+ collector you can assert on.
24
+
25
+ ```ts
26
+ import { describe, it, expect, afterEach } from "vitest";
27
+ import { testAgent, type TestHarness } from "@cef-ai/testing";
28
+ import Echo from "../src/agent.js";
29
+
30
+ describe("echo", () => {
31
+ let h: TestHarness | undefined;
32
+ afterEach(() => h?.dispose());
33
+
34
+ it("acks user_message", async () => {
35
+ h = testAgent(Echo, {
36
+ cubbies: [{ alias: "history", migrations: "./migrations/history" }],
37
+ });
38
+ const events = await h.dispatch({
39
+ type: "user_message",
40
+ payload: { text: "hello" },
41
+ });
42
+ expect(events.map((e) => e.type)).toContain("ack");
43
+ });
44
+ });
45
+ ```
46
+
47
+ ## `testPlatform` quickstart
48
+
49
+ `testPlatform({ agents, models, ... })` lifts the harness into a multi-agent
50
+ world: each agent runs in its own simulator, a shared vault routes events
51
+ between them, and the test drives interactions through `vault.agents` /
52
+ `vault.events`. Use this when an agent talks to peers or when an
53
+ application client (your "app") publishes into the vault.
54
+
55
+ ```ts
56
+ import { describe, it, expect, afterEach } from "vitest";
57
+ import { testPlatform, createModelMock, type TestPlatform } from "@cef-ai/testing";
58
+ import Assistant from "../agent/src/assistant.js";
59
+
60
+ describe("assistant + app", () => {
61
+ let p: TestPlatform | undefined;
62
+ afterEach(() => p?.dispose());
63
+
64
+ it("answers a user message", async () => {
65
+ const llm = createModelMock();
66
+ llm.expect({ prompt: "hi" }).respond({ text: "hello back" });
67
+
68
+ p = testPlatform({
69
+ agents: {
70
+ assistant: {
71
+ source: Assistant,
72
+ cubbies: [{ alias: "history", migrations: "./migrations/history" }],
73
+ },
74
+ },
75
+ models: { llm },
76
+ });
77
+
78
+ await p.vault.agents.connect({ agentId: "assistant" });
79
+ // ...drive the vault, assert published events / cubby state
80
+ });
81
+ });
82
+ ```
83
+
84
+ ## API summary
85
+
86
+ | Symbol | Returns | Notes |
87
+ | --- | --- | --- |
88
+ | `testAgent(AgentClass, opts?)` | `TestHarness` | Single-agent simulator. Reads `AGENT_METADATA` to find decorated handlers. |
89
+ | `testPlatform(opts)` | `TestPlatform` | Multi-agent simulator with a shared vault, model registry, and per-agent cubbies. |
90
+ | `mockAgent(spec)` | `MockAgentDescriptor` | Peer descriptor for `testPlatform` — drop in for an agent you don't want to bring into the test bundle. |
91
+ | `fromMarketplace(url)` | `MockAgentDescriptor` | v1: throws (not yet wired). v2: will fetch a published bundle. |
92
+ | `createModelMock()` | `ModelMockHandle` | Expectation-builder model handle: `mock.expect(input).respond(output)`. |
93
+
94
+ `TestHarness` exposes:
95
+
96
+ - `dispatch({ type, payload, from? })` — drive a single event; returns the
97
+ publishes from this call.
98
+ - `published` — every publish since construction (cumulative log).
99
+ - `cubby(alias)` — `CubbyHandle` for assertions.
100
+ - `runInCubby(alias, fn)` — run with both the public handle and the raw
101
+ `better-sqlite3` database for sync, prepared-statement assertions.
102
+ - `start()` / `close(reason?)` — drive `@OnStart` / `@OnClose`.
103
+ - `lifecycle` — `{ state: "idle" | "started" | "closed", terminalReason?: ... }`.
104
+ - `snapshot()` / `restore(snap)` — capture and restore cubbies + clock +
105
+ publish log.
106
+ - `replay(jsonlPath)` — feed a JSONL fixture of events through the harness.
107
+ - `advanceTime(ms)` / `now()` — manipulate the injected clock.
108
+ - `fetchMock()` — URL-pattern fetch mock attached to `ctx.fetch`.
109
+ - `models` — the model handle map attached to `ctx.models`.
110
+ - `dispose()` — release SQLite handles. Idempotent.
111
+
112
+ `TestPlatform` exposes:
113
+
114
+ - `vault` — the simulated vault (`agents.connect`, event streams, scoped
115
+ per-conversation channels).
116
+ - `runInCubby(agentId, alias, fn)` — assert on a specific agent's cubby.
117
+ - `dispose()` — tear down every per-agent simulator.
118
+
119
+ ## Companion packages
120
+
121
+ - [`@cef-ai/agent-sdk`](../agent-sdk) — the decorators and types the harness
122
+ is testing.
123
+ - [`@cef-ai/cli`](../cli) — `cef build` / `cef typegen` for the build that
124
+ these tests run alongside.
125
+
126
+ ## References
127
+
128
+ - Agent SDK spec: `../../../company-memory-bank/specs/platform/03-components/agent-sdk.md` §9 (testing surface).
129
+ - Runtime integration contract: `../../../company-memory-bank/specs/platform/03-components/sdk-runtime-integration-contract.md`.
130
+
131
+ The `../company-memory-bank/...` paths point at an internal sibling repo
132
+ and are not browsable from a fresh clone.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Injectable test clock used by the `testAgent` harness.
3
+ *
4
+ * The harness threads `clock.now()` through every place the runtime would
5
+ * normally read `Date.now()` (event timestamps, `published[].ts`, etc.) so
6
+ * tests can pin time deterministically and step it forward via
7
+ * `advanceTime(ms)`.
8
+ *
9
+ * We deliberately do NOT monkey-patch the global `Date` or `Date.now` —
10
+ * staying off the globals keeps the harness composable with vitest's own
11
+ * fake timers and other test utilities the user might bring.
12
+ */
13
+ export interface TestClock {
14
+ /** Current time in unix-millis. */
15
+ now(): number;
16
+ /** Move the clock forward by `ms` milliseconds. */
17
+ advanceTime(ms: number): void;
18
+ /** Set the clock to an absolute unix-millis. Used by `restoreSnapshot`. */
19
+ setNow(ms: number): void;
20
+ }
21
+ /**
22
+ * Build a {@link TestClock}. Defaults to the real wall-clock time at
23
+ * construction so unspecified-time tests still produce sane timestamps.
24
+ */
25
+ export declare function createClock(initial?: number): TestClock;
26
+ //# sourceMappingURL=clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../src/harness/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,GAAG,IAAI,MAAM,CAAC;IACd,mDAAmD;IACnD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAWvD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Build a {@link TestClock}. Defaults to the real wall-clock time at
3
+ * construction so unspecified-time tests still produce sane timestamps.
4
+ */
5
+ export function createClock(initial) {
6
+ let t = initial ?? Date.now();
7
+ return {
8
+ now: () => t,
9
+ advanceTime: (ms) => {
10
+ t += ms;
11
+ },
12
+ setNow: (ms) => {
13
+ t = ms;
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../../src/harness/clock.ts"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,IAAI,CAAC,GAAG,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,OAAO;QACL,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACZ,WAAW,EAAE,CAAC,EAAU,EAAE,EAAE;YAC1B,CAAC,IAAI,EAAE,CAAC;QACV,CAAC;QACD,MAAM,EAAE,CAAC,EAAU,EAAE,EAAE;YACrB,CAAC,GAAG,EAAE,CAAC;QACT,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * In-memory `better-sqlite3` cubby for the test harness.
3
+ *
4
+ * Each declared cubby alias gets its own isolated `:memory:` database, so
5
+ * tests don't bleed state across agents within a single run and don't
6
+ * require fixture cleanup. Migrations are read from a directory of `.sql`
7
+ * files (sorted lexicographically and applied in order) — the same shape
8
+ * the production runtime uses.
9
+ *
10
+ * Note on `exec` semantics: better-sqlite3's `prepare()` only accepts a
11
+ * single statement, but `db.exec()` handles multi-statement scripts. We
12
+ * use the latter for migration loading, but keep the public
13
+ * `CubbyHandle.exec` (single-statement DML with parameter binding) on
14
+ * top of `prepare().run()` so the surface matches the production runtime
15
+ * exactly.
16
+ *
17
+ * `TestCubby` is implemented as a class (rather than a closure-based
18
+ * factory) so `query`/`exec` always read `this.raw` at call time. That
19
+ * lets `loadFromBuffer` swap the underlying database — needed by the
20
+ * `snapshot`/`restore` machinery — without invalidating the `CubbyHandle`
21
+ * references the agent already holds.
22
+ */
23
+ import Database from "better-sqlite3";
24
+ import type { CubbyHandle } from "@cef-ai/agent-sdk";
25
+ /**
26
+ * Per-alias test cubby — extends the public {@link CubbyHandle} with
27
+ * disposal hooks the harness uses for teardown.
28
+ */
29
+ export declare class TestCubby implements CubbyHandle {
30
+ /** Underlying database — exposed for advanced harness extensions (e.g. `runInCubby`). */
31
+ raw: Database.Database;
32
+ /** Alias for diagnostics. */
33
+ readonly alias: string;
34
+ private closed;
35
+ constructor(alias: string, migrationsDir?: string);
36
+ query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
37
+ exec(sql: string, params?: unknown[]): Promise<{
38
+ changes: number;
39
+ lastInsertRowid: number | bigint;
40
+ }>;
41
+ /** Serialize the database to a Buffer (powers `harness.snapshot()`). */
42
+ serialize(): Buffer;
43
+ /**
44
+ * Replace the underlying database from a serialized buffer. The previous
45
+ * handle is closed first. Existing `CubbyHandle` references stay valid
46
+ * because `query`/`exec` read `this.raw` lazily.
47
+ */
48
+ loadFromBuffer(buf: Buffer): void;
49
+ /** Release the SQLite handle. Idempotent. */
50
+ close(): void;
51
+ }
52
+ /**
53
+ * Factory that mirrors the original closure-based API. Equivalent to
54
+ * `new TestCubby(alias, migrationsDir)`.
55
+ */
56
+ export declare function createCubby(alias: string, migrationsDir?: string): TestCubby;
57
+ //# sourceMappingURL=cubby.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cubby.d.ts","sourceRoot":"","sources":["../../src/harness/cubby.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;GAGG;AACH,qBAAa,SAAU,YAAW,WAAW;IAC3C,yFAAyF;IACzF,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,MAAM,CAAS;gBAEX,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM;IAsB3C,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAKjE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE;;yBAKG,MAAM,GAAG,MAAM;;IAI5D,wEAAwE;IACxE,SAAS,IAAI,MAAM;IAInB;;;;OAIG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKjC,6CAA6C;IAC7C,KAAK,IAAI,IAAI;CAKd;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACrB,SAAS,CAEX"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * In-memory `better-sqlite3` cubby for the test harness.
3
+ *
4
+ * Each declared cubby alias gets its own isolated `:memory:` database, so
5
+ * tests don't bleed state across agents within a single run and don't
6
+ * require fixture cleanup. Migrations are read from a directory of `.sql`
7
+ * files (sorted lexicographically and applied in order) — the same shape
8
+ * the production runtime uses.
9
+ *
10
+ * Note on `exec` semantics: better-sqlite3's `prepare()` only accepts a
11
+ * single statement, but `db.exec()` handles multi-statement scripts. We
12
+ * use the latter for migration loading, but keep the public
13
+ * `CubbyHandle.exec` (single-statement DML with parameter binding) on
14
+ * top of `prepare().run()` so the surface matches the production runtime
15
+ * exactly.
16
+ *
17
+ * `TestCubby` is implemented as a class (rather than a closure-based
18
+ * factory) so `query`/`exec` always read `this.raw` at call time. That
19
+ * lets `loadFromBuffer` swap the underlying database — needed by the
20
+ * `snapshot`/`restore` machinery — without invalidating the `CubbyHandle`
21
+ * references the agent already holds.
22
+ */
23
+ import Database from "better-sqlite3";
24
+ import fs from "node:fs";
25
+ import path from "node:path";
26
+ /**
27
+ * Per-alias test cubby — extends the public {@link CubbyHandle} with
28
+ * disposal hooks the harness uses for teardown.
29
+ */
30
+ export class TestCubby {
31
+ /** Underlying database — exposed for advanced harness extensions (e.g. `runInCubby`). */
32
+ raw;
33
+ /** Alias for diagnostics. */
34
+ alias;
35
+ closed = false;
36
+ constructor(alias, migrationsDir) {
37
+ this.alias = alias;
38
+ this.raw = new Database(":memory:");
39
+ if (migrationsDir) {
40
+ if (!fs.existsSync(migrationsDir)) {
41
+ throw new Error(`[testAgent] cubby "${alias}" migrations dir not found: ${migrationsDir}`);
42
+ }
43
+ const files = fs
44
+ .readdirSync(migrationsDir)
45
+ .filter((f) => f.endsWith(".sql"))
46
+ .sort();
47
+ for (const f of files) {
48
+ const sql = fs.readFileSync(path.join(migrationsDir, f), "utf8");
49
+ // db.exec handles multi-statement migration scripts; prepare() does not.
50
+ this.raw.exec(sql);
51
+ }
52
+ }
53
+ }
54
+ async query(sql, params) {
55
+ const stmt = this.raw.prepare(sql);
56
+ return stmt.all(...(params ?? []));
57
+ }
58
+ async exec(sql, params) {
59
+ const stmt = this.raw.prepare(sql);
60
+ const info = stmt.run(...(params ?? []));
61
+ return {
62
+ changes: info.changes,
63
+ lastInsertRowid: info.lastInsertRowid,
64
+ };
65
+ }
66
+ /** Serialize the database to a Buffer (powers `harness.snapshot()`). */
67
+ serialize() {
68
+ return this.raw.serialize();
69
+ }
70
+ /**
71
+ * Replace the underlying database from a serialized buffer. The previous
72
+ * handle is closed first. Existing `CubbyHandle` references stay valid
73
+ * because `query`/`exec` read `this.raw` lazily.
74
+ */
75
+ loadFromBuffer(buf) {
76
+ this.raw.close();
77
+ this.raw = new Database(buf);
78
+ }
79
+ /** Release the SQLite handle. Idempotent. */
80
+ close() {
81
+ if (this.closed)
82
+ return;
83
+ this.closed = true;
84
+ this.raw.close();
85
+ }
86
+ }
87
+ /**
88
+ * Factory that mirrors the original closure-based API. Equivalent to
89
+ * `new TestCubby(alias, migrationsDir)`.
90
+ */
91
+ export function createCubby(alias, migrationsDir) {
92
+ return new TestCubby(alias, migrationsDir);
93
+ }
94
+ //# sourceMappingURL=cubby.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cubby.js","sourceRoot":"","sources":["../../src/harness/cubby.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B;;;GAGG;AACH,MAAM,OAAO,SAAS;IACpB,yFAAyF;IACzF,GAAG,CAAoB;IACvB,6BAA6B;IACpB,KAAK,CAAS;IACf,MAAM,GAAG,KAAK,CAAC;IAEvB,YAAY,KAAa,EAAE,aAAsB;QAC/C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,GAAG,GAAG,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC;QAEpC,IAAI,aAAa,EAAE,CAAC;YAClB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CACb,sBAAsB,KAAK,+BAA+B,aAAa,EAAE,CAC1E,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,GAAG,EAAE;iBACb,WAAW,CAAC,aAAa,CAAC;iBAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;iBACjC,IAAI,EAAE,CAAC;YACV,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;gBACjE,yEAAyE;gBACzE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAc,GAAW,EAAE,MAAkB;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAI,CAAC,MAAM,IAAI,EAAE,CAAe,CAAQ,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,MAAkB;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAI,CAAC,MAAM,IAAI,EAAE,CAAe,CAAC,CAAC;QACxD,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,eAAe,EAAE,IAAI,CAAC,eAAkC;SACzD,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,SAAS;QACP,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,GAAW;QACxB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,GAAG,GAAG,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,6CAA6C;IAC7C,KAAK;QACH,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAa,EACb,aAAsB;IAEtB,OAAO,IAAI,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Event synthesis + handler dispatch for the test harness.
3
+ *
4
+ * `makeEvent` produces a runtime-shaped {@link Event} (id, ts from the
5
+ * clock, default `from`) from the partial input the test supplies; the
6
+ * runtime would normally do this on inbound delivery.
7
+ *
8
+ * `dispatchEvent` looks up the right handler from the class's
9
+ * {@link EngagementMeta} and invokes it. Unknown event types are silently
10
+ * dropped — that's how the production runtime behaves (no metadata match
11
+ * → no dispatch); raising here would punish tests for declaring agents
12
+ * that handle a subset of types.
13
+ */
14
+ import type { EngagementMeta, Context, Event } from "@cef-ai/agent-sdk";
15
+ /**
16
+ * Look up `event.type` in the agent metadata and call the matching
17
+ * handler with the constructed ctx. Resolves silently when no handler is
18
+ * declared for the type, matching runtime behavior.
19
+ */
20
+ export declare function dispatchEvent(instance: Record<string, unknown>, meta: EngagementMeta, event: Event<unknown>, ctx: Context): Promise<void>;
21
+ /**
22
+ * Construct a runtime-shaped event from a `dispatch()` input. Fills in
23
+ * `id` (random hex), `ts` (from the clock), and `from` (`"test"` unless
24
+ * overridden) so handlers can rely on the same shape they'd see in
25
+ * production.
26
+ */
27
+ export declare function makeEvent(type: string, payload: unknown, opts: {
28
+ now: () => number;
29
+ from?: string;
30
+ }): Event<unknown>;
31
+ //# sourceMappingURL=dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/harness/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,EACrB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,KAAK,CAAC,OAAO,CAAC,CAQhB"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Look up `event.type` in the agent metadata and call the matching
3
+ * handler with the constructed ctx. Resolves silently when no handler is
4
+ * declared for the type, matching runtime behavior.
5
+ */
6
+ export async function dispatchEvent(instance, meta, event, ctx) {
7
+ const handlerName = meta.events[event.type];
8
+ if (!handlerName)
9
+ return;
10
+ const fn = instance[handlerName];
11
+ if (typeof fn !== "function") {
12
+ throw new Error(`[testAgent] handler "${handlerName}" for "${event.type}" is not a function on instance`);
13
+ }
14
+ await fn.call(instance, event, ctx);
15
+ }
16
+ /**
17
+ * Construct a runtime-shaped event from a `dispatch()` input. Fills in
18
+ * `id` (random hex), `ts` (from the clock), and `from` (`"test"` unless
19
+ * overridden) so handlers can rely on the same shape they'd see in
20
+ * production.
21
+ */
22
+ export function makeEvent(type, payload, opts) {
23
+ return {
24
+ id: `evt-${randomHex(8)}`,
25
+ type,
26
+ ts: opts.now(),
27
+ from: opts.from ?? "test",
28
+ payload,
29
+ };
30
+ }
31
+ function randomHex(bytes) {
32
+ const buf = new Uint8Array(bytes);
33
+ // Node 20 / browsers both expose globalThis.crypto.
34
+ globalThis.crypto.getRandomValues(buf);
35
+ return Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join("");
36
+ }
37
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/harness/dispatch.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAiC,EACjC,IAAoB,EACpB,KAAqB,EACrB,GAAY;IAEZ,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,WAAW;QAAE,OAAO;IACzB,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IACjC,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,wBAAwB,WAAW,UAAU,KAAK,CAAC,IAAI,iCAAiC,CACzF,CAAC;IACJ,CAAC;IACD,MAAO,EAAiD,CAAC,IAAI,CAC3D,QAAQ,EACR,KAAK,EACL,GAAG,CACJ,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,OAAgB,EAChB,IAA0C;IAE1C,OAAO;QACL,EAAE,EAAE,OAAO,SAAS,CAAC,CAAC,CAAC,EAAE;QACzB,IAAI;QACJ,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;QACd,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,MAAM;QACzB,OAAO;KACR,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IAClC,oDAAoD;IACpD,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Tiny URL-pattern fetch mock used by the test harness.
3
+ *
4
+ * Scope is deliberately small: enough to assert that the agent issued the
5
+ * expected outbound HTTP requests and to stub a response body. We don't
6
+ * try to be a general-purpose nock — for that, tests can replace
7
+ * `ctx.fetch` directly via the harness's published model-mock pattern.
8
+ *
9
+ * URL matching:
10
+ * - `string`: matches by exact equality OR by `startsWith` (covers the
11
+ * "any path under https://api.example.com" case ergonomically).
12
+ * - `RegExp`: matches by `RegExp.test`.
13
+ *
14
+ * Method matching is case-insensitive and optional (omitted means "any
15
+ * method"). Stubs are tried in declaration order; the first match wins.
16
+ */
17
+ import type { FetchInit } from "@cef-ai/agent-sdk";
18
+ export interface FetchMatcher {
19
+ url: string | RegExp;
20
+ method?: string;
21
+ }
22
+ export interface FetchReply {
23
+ status: number;
24
+ body?: string | object;
25
+ headers?: Record<string, string>;
26
+ }
27
+ export interface FetchMock {
28
+ /** Register a stub. Returns a `.reply()` builder. */
29
+ when(matcher: FetchMatcher): {
30
+ reply: (status: number, body?: string | object, headers?: Record<string, string>) => void;
31
+ };
32
+ /** Throw if no recorded call matched `matcher`. */
33
+ assertCalled(matcher: FetchMatcher): void;
34
+ /** Captured calls; exposed for ad-hoc assertions. */
35
+ calls: Array<{
36
+ url: string;
37
+ method: string;
38
+ }>;
39
+ /** Drop-in for `ctx.fetch`. */
40
+ fetch: (url: string | URL, init?: FetchInit) => Promise<Response>;
41
+ }
42
+ export declare function createFetchMock(): FetchMock;
43
+ //# sourceMappingURL=fetch-mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-mock.d.ts","sourceRoot":"","sources":["../../src/harness/fetch-mock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,qDAAqD;IACrD,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG;QAC3B,KAAK,EAAE,CACL,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,IAAI,CAAC;KACX,CAAC;IACF,mDAAmD;IACnD,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1C,qDAAqD;IACrD,KAAK,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C,+BAA+B;IAC/B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnE;AAED,wBAAgB,eAAe,IAAI,SAAS,CA4D3C"}
@@ -0,0 +1,58 @@
1
+ export function createFetchMock() {
2
+ const stubs = [];
3
+ const calls = [];
4
+ const mock = {
5
+ when(matcher) {
6
+ return {
7
+ reply(status, body, headers) {
8
+ stubs.push({ matcher, reply: { status, body, headers } });
9
+ },
10
+ };
11
+ },
12
+ assertCalled(matcher) {
13
+ const found = calls.some((c) => matchUrl(c.url, matcher.url) &&
14
+ (matcher.method
15
+ ? c.method.toUpperCase() === matcher.method.toUpperCase()
16
+ : true));
17
+ if (!found) {
18
+ throw new Error(`[fetchMock] expected ${describeMatcher(matcher)} not called. Actual: ${calls.length === 0
19
+ ? "(no calls)"
20
+ : calls.map((c) => `${c.method} ${c.url}`).join(", ")}`);
21
+ }
22
+ },
23
+ calls,
24
+ async fetch(url, init) {
25
+ const u = url.toString();
26
+ const method = (init?.method ?? "GET").toUpperCase();
27
+ calls.push({ url: u, method });
28
+ const stub = stubs.find((s) => matchUrl(u, s.matcher.url) &&
29
+ (s.matcher.method
30
+ ? method === s.matcher.method.toUpperCase()
31
+ : true));
32
+ if (!stub) {
33
+ throw new Error(`[fetchMock] no stub for ${method} ${u}`);
34
+ }
35
+ const body = typeof stub.reply.body === "string"
36
+ ? stub.reply.body
37
+ : JSON.stringify(stub.reply.body ?? null);
38
+ return new Response(body, {
39
+ status: stub.reply.status,
40
+ headers: {
41
+ "content-type": "application/json",
42
+ ...(stub.reply.headers ?? {}),
43
+ },
44
+ });
45
+ },
46
+ };
47
+ return mock;
48
+ }
49
+ function matchUrl(url, pattern) {
50
+ return typeof pattern === "string"
51
+ ? url === pattern || url.startsWith(pattern)
52
+ : pattern.test(url);
53
+ }
54
+ function describeMatcher(m) {
55
+ const u = typeof m.url === "string" ? m.url : m.url.toString();
56
+ return m.method ? `${m.method.toUpperCase()} ${u}` : u;
57
+ }
58
+ //# sourceMappingURL=fetch-mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-mock.js","sourceRoot":"","sources":["../../src/harness/fetch-mock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Lifecycle controller for the test harness — drives `@OnStart` /
3
+ * `@OnClose` from the agent's metadata and tracks the externally
4
+ * observable state (`idle` → `started` → `closed`).
5
+ *
6
+ * The harness owns calling these explicitly (`harness.start()` /
7
+ * `harness.close(reason)`) rather than implicitly on `dispatch`, mirroring
8
+ * sdk-runtime-integration-contract §4 where the runtime, not the agent,
9
+ * controls lifecycle transitions.
10
+ */
11
+ import type { EngagementMeta, Context, OnCloseReason } from "@cef-ai/agent-sdk";
12
+ export type LifecycleState = "idle" | "started" | "closed";
13
+ export interface LifecycleTracker {
14
+ /** Current externally observable state. */
15
+ state: LifecycleState;
16
+ /** Reason recorded by the most recent `close` call. */
17
+ terminalReason?: OnCloseReason | string;
18
+ /** Invoke the agent's `@OnStart` handler (if any) and transition to `started`. */
19
+ start(instance: Record<string, unknown>, ctx: Context, meta: EngagementMeta): Promise<void>;
20
+ /** Invoke the agent's `@OnClose` handler (if any) and transition to `closed`. */
21
+ close(instance: Record<string, unknown>, ctx: Context, meta: EngagementMeta, reason: OnCloseReason | string): Promise<void>;
22
+ }
23
+ /**
24
+ * Build a fresh lifecycle tracker. Idempotent on `start` and `close` —
25
+ * subsequent calls are no-ops, matching the runtime contract that
26
+ * lifecycle hooks fire at most once per agent instance.
27
+ */
28
+ export declare function createLifecycle(): LifecycleTracker;
29
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/harness/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEhF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,KAAK,EAAE,cAAc,CAAC;IACtB,uDAAuD;IACvD,cAAc,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IACxC,kFAAkF;IAClF,KAAK,CACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,iFAAiF;IACjF,KAAK,CACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,aAAa,GAAG,MAAM,GAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,CA8BlD"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Build a fresh lifecycle tracker. Idempotent on `start` and `close` —
3
+ * subsequent calls are no-ops, matching the runtime contract that
4
+ * lifecycle hooks fire at most once per agent instance.
5
+ */
6
+ export function createLifecycle() {
7
+ const t = {
8
+ state: "idle",
9
+ async start(instance, ctx, meta) {
10
+ if (t.state !== "idle")
11
+ return;
12
+ const m = meta.lifecycle?.start;
13
+ if (m) {
14
+ const fn = instance[m];
15
+ if (typeof fn === "function") {
16
+ await fn.call(instance, ctx);
17
+ }
18
+ }
19
+ t.state = "started";
20
+ },
21
+ async close(instance, ctx, meta, reason) {
22
+ if (t.state === "closed")
23
+ return;
24
+ const m = meta.lifecycle?.close;
25
+ if (m) {
26
+ const fn = instance[m];
27
+ if (typeof fn === "function") {
28
+ await fn.call(instance, ctx, reason);
29
+ }
30
+ }
31
+ t.state = "closed";
32
+ t.terminalReason = reason;
33
+ },
34
+ };
35
+ return t;
36
+ }
37
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/harness/lifecycle.ts"],"names":[],"mappings":"AAkCA;;;;GAIG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,CAAC,GAAqB;QAC1B,KAAK,EAAE,MAAM;QACb,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI;YAC7B,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;gBAAE,OAAO;YAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC;YAChC,IAAI,CAAC,EAAE,CAAC;gBACN,MAAM,EAAE,GAAI,QAAoC,CAAC,CAAC,CAAC,CAAC;gBACpD,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;oBAC7B,MAAO,EAA8B,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;YACD,CAAC,CAAC,KAAK,GAAG,SAAS,CAAC;QACtB,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;YACrC,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ;gBAAE,OAAO;YACjC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC;YAChC,IAAI,CAAC,EAAE,CAAC;gBACN,MAAM,EAAE,GAAI,QAAoC,CAAC,CAAC,CAAC,CAAC;gBACpD,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;oBAC7B,MACE,EACD,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;YACD,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC;YACnB,CAAC,CAAC,cAAc,GAAG,MAAM,CAAC;QAC5B,CAAC;KACF,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC"}