@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.
- package/README.md +132 -0
- package/dist/harness/clock.d.ts +26 -0
- package/dist/harness/clock.d.ts.map +1 -0
- package/dist/harness/clock.js +17 -0
- package/dist/harness/clock.js.map +1 -0
- package/dist/harness/cubby.d.ts +57 -0
- package/dist/harness/cubby.d.ts.map +1 -0
- package/dist/harness/cubby.js +94 -0
- package/dist/harness/cubby.js.map +1 -0
- package/dist/harness/dispatch.d.ts +31 -0
- package/dist/harness/dispatch.d.ts.map +1 -0
- package/dist/harness/dispatch.js +37 -0
- package/dist/harness/dispatch.js.map +1 -0
- package/dist/harness/fetch-mock.d.ts +43 -0
- package/dist/harness/fetch-mock.d.ts.map +1 -0
- package/dist/harness/fetch-mock.js +58 -0
- package/dist/harness/fetch-mock.js.map +1 -0
- package/dist/harness/lifecycle.d.ts +29 -0
- package/dist/harness/lifecycle.d.ts.map +1 -0
- package/dist/harness/lifecycle.js +37 -0
- package/dist/harness/lifecycle.js.map +1 -0
- package/dist/harness/model-mock.d.ts +49 -0
- package/dist/harness/model-mock.d.ts.map +1 -0
- package/dist/harness/model-mock.js +56 -0
- package/dist/harness/model-mock.js.map +1 -0
- package/dist/harness/publish.d.ts +29 -0
- package/dist/harness/publish.d.ts.map +1 -0
- package/dist/harness/publish.js +20 -0
- package/dist/harness/publish.js.map +1 -0
- package/dist/harness/replay.d.ts +32 -0
- package/dist/harness/replay.d.ts.map +1 -0
- package/dist/harness/replay.js +72 -0
- package/dist/harness/replay.js.map +1 -0
- package/dist/harness/snapshot.d.ts +44 -0
- package/dist/harness/snapshot.d.ts.map +1 -0
- package/dist/harness/snapshot.js +58 -0
- package/dist/harness/snapshot.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/peers/from-marketplace.d.ts +19 -0
- package/dist/peers/from-marketplace.d.ts.map +1 -0
- package/dist/peers/from-marketplace.js +22 -0
- package/dist/peers/from-marketplace.js.map +1 -0
- package/dist/peers/mock-agent.d.ts +108 -0
- package/dist/peers/mock-agent.d.ts.map +1 -0
- package/dist/peers/mock-agent.js +113 -0
- package/dist/peers/mock-agent.js.map +1 -0
- package/dist/platform/event-runtime.d.ts +28 -0
- package/dist/platform/event-runtime.d.ts.map +1 -0
- package/dist/platform/event-runtime.js +38 -0
- package/dist/platform/event-runtime.js.map +1 -0
- package/dist/platform/orchestrator.d.ts +86 -0
- package/dist/platform/orchestrator.d.ts.map +1 -0
- package/dist/platform/orchestrator.js +164 -0
- package/dist/platform/orchestrator.js.map +1 -0
- package/dist/platform/runtime.d.ts +66 -0
- package/dist/platform/runtime.d.ts.map +1 -0
- package/dist/platform/runtime.js +116 -0
- package/dist/platform/runtime.js.map +1 -0
- package/dist/platform/targeting.d.ts +29 -0
- package/dist/platform/targeting.d.ts.map +1 -0
- package/dist/platform/targeting.js +64 -0
- package/dist/platform/targeting.js.map +1 -0
- package/dist/platform/vault.d.ts +185 -0
- package/dist/platform/vault.d.ts.map +1 -0
- package/dist/platform/vault.js +200 -0
- package/dist/platform/vault.js.map +1 -0
- package/dist/test-agent.d.ts +137 -0
- package/dist/test-agent.d.ts.map +1 -0
- package/dist/test-agent.js +223 -0
- package/dist/test-agent.js.map +1 -0
- package/dist/test-platform.d.ts +167 -0
- package/dist/test-platform.d.ts.map +1 -0
- package/dist/test-platform.js +352 -0
- package/dist/test-platform.js.map +1 -0
- 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"}
|