@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
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `mockAgent({...})` — a lightweight, decoratorless agent peer for the
3
+ * multi-agent test platform.
4
+ *
5
+ * Use it when a test only needs a peer that publishes/echoes a fixed set of
6
+ * events; it's the "stunt double" alongside real, decorator-using agent
7
+ * classes. The `on` map plays the same role as `@OnEvent`: keys are event
8
+ * types, values are async handler functions invoked with the same
9
+ * `(event, ctx)` shape the runtime uses. `start` / `close` mirror the
10
+ * `@OnStart` / `@OnClose` lifecycle hooks.
11
+ *
12
+ * Two equivalent spec shapes (ADR-019 H4):
13
+ *
14
+ * - Single-engagement shorthand: `mockAgent({ agentId, on, onStart?, onClose? })`
15
+ * — produces one engagement with id `"default"`.
16
+ * - Multi-engagement form: `mockAgent({ agentId, engagements: [...], targeting?, settings? })`
17
+ * — each engagement has its own `on`/`onStart`/`onClose`; `targeting`
18
+ * selects one at Job creation.
19
+ *
20
+ * Internally `testPlatform` recognises the descriptor by its
21
+ * `__mockAgent: true` tag and consumes a uniform normalised shape so dispatch
22
+ * code is unaware of single-vs-multi or class-vs-mock.
23
+ */
24
+ /* -------------------------------------------------------------------------- */
25
+ /* Construction */
26
+ /* -------------------------------------------------------------------------- */
27
+ function isMultiEngagementSpec(spec) {
28
+ return Array.isArray(spec.engagements);
29
+ }
30
+ /**
31
+ * Synthesise an `(instance, meta)` pair from a single-engagement-style entry.
32
+ * Method names are stable per-engagement (`on_<type>` / `onStart` / `onClose`)
33
+ * so multiple engagements within one mock can each have their own instance.
34
+ */
35
+ function buildEngagementShape(id, goal, on, onStart, onClose) {
36
+ const events = {};
37
+ const instance = {};
38
+ for (const [type, handler] of Object.entries(on ?? {})) {
39
+ const name = `on_${type}`;
40
+ events[type] = name;
41
+ instance[name] = handler;
42
+ }
43
+ const lifecycle = {};
44
+ if (onStart) {
45
+ lifecycle.start = "onStart";
46
+ instance.onStart = onStart;
47
+ }
48
+ if (onClose) {
49
+ lifecycle.close = "onClose";
50
+ instance.onClose = onClose;
51
+ }
52
+ const meta = { id, goal, events, lifecycle };
53
+ return { id, goal, instance, meta };
54
+ }
55
+ /**
56
+ * Build a {@link MockAgentDescriptor} from a spec. The returned object is
57
+ * what callers pass into `testPlatform({ agents: { foo: mockAgent({...}) } })`.
58
+ *
59
+ * The single-engagement shorthand is normalised to a one-element
60
+ * `engagements` array with id `"default"` and a degenerate targeting table,
61
+ * so consumers always see the same shape.
62
+ */
63
+ export function mockAgent(spec) {
64
+ if (!spec.agentId) {
65
+ throw new Error("[mockAgent] spec.agentId is required");
66
+ }
67
+ if (!isMultiEngagementSpec(spec)) {
68
+ const engagement = buildEngagementShape("default", undefined, spec.on, spec.onStart, spec.onClose);
69
+ return {
70
+ __mockAgent: true,
71
+ agentId: spec.agentId,
72
+ engagements: [engagement],
73
+ targeting: { engagements: [{ id: "default", rules: [] }] },
74
+ };
75
+ }
76
+ if (spec.engagements.length === 0) {
77
+ throw new Error(`[mockAgent] agent "${spec.agentId}" has empty engagements[]`);
78
+ }
79
+ const seen = new Set();
80
+ const engagements = [];
81
+ for (const e of spec.engagements) {
82
+ if (!e.id) {
83
+ throw new Error(`[mockAgent] agent "${spec.agentId}" has an engagement with no id`);
84
+ }
85
+ if (seen.has(e.id)) {
86
+ throw new Error(`[mockAgent] agent "${spec.agentId}" has duplicate engagement id "${e.id}"`);
87
+ }
88
+ seen.add(e.id);
89
+ engagements.push(buildEngagementShape(e.id, e.goal, e.on, e.onStart, e.onClose));
90
+ }
91
+ const targeting = spec.targeting ?? {
92
+ engagements: [{ id: engagements[0].id, rules: [] }],
93
+ };
94
+ for (const entry of targeting.engagements) {
95
+ if (!seen.has(entry.id)) {
96
+ throw new Error(`[mockAgent] agent "${spec.agentId}" targeting references unknown engagement "${entry.id}"`);
97
+ }
98
+ }
99
+ return {
100
+ __mockAgent: true,
101
+ agentId: spec.agentId,
102
+ engagements,
103
+ targeting,
104
+ settings: spec.settings,
105
+ };
106
+ }
107
+ /** Type-guard used by `testPlatform` to dispatch on source kind. */
108
+ export function isMockAgentDescriptor(value) {
109
+ return (typeof value === "object" &&
110
+ value !== null &&
111
+ value.__mockAgent === true);
112
+ }
113
+ //# sourceMappingURL=mock-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-agent.js","sourceRoot":"","sources":["../../src/peers/mock-agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Event router for the multi-agent simulator.
3
+ *
4
+ * Partitions events by `(vaultId, scope, context)` (per contract §6) and
5
+ * fans them out to every connected agent whose manifest's `events` map
6
+ * names the event type. The actual invocation is delegated to the
7
+ * orchestrator so each delivery becomes a tracked Job/Activity.
8
+ *
9
+ * Determinism note: deliveries are awaited sequentially in connection-order
10
+ * (`Map` insertion order). Tests then see a stable causal ordering:
11
+ * user publishes E1 → agent A handles E1, publishes E2 → agent B handles
12
+ * E2 → … with each `await vault.scope().publish(...)` returning only
13
+ * after the entire chain settles.
14
+ */
15
+ import type { AgentConnection, VaultEvent } from "./vault.js";
16
+ import type { Orchestrator } from "./orchestrator.js";
17
+ export interface EventRuntime {
18
+ /**
19
+ * Deliver `event` to every connection in `connections` whose manifest
20
+ * declares a handler for `event.type`. Awaits all deliveries (one
21
+ * orchestrator dispatch per connection) before resolving.
22
+ */
23
+ publish(event: VaultEvent, connections: Iterable<AgentConnection>): Promise<void>;
24
+ }
25
+ export declare function createEventRuntime(opts: {
26
+ orchestrator: Orchestrator;
27
+ }): EventRuntime;
28
+ //# sourceMappingURL=event-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-runtime.d.ts","sourceRoot":"","sources":["../../src/platform/event-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,OAAO,CACL,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,QAAQ,CAAC,eAAe,CAAC,GACrC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,YAAY,EAAE,YAAY,CAAC;CAC5B,GAAG,YAAY,CAuBf"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Event router for the multi-agent simulator.
3
+ *
4
+ * Partitions events by `(vaultId, scope, context)` (per contract §6) and
5
+ * fans them out to every connected agent whose manifest's `events` map
6
+ * names the event type. The actual invocation is delegated to the
7
+ * orchestrator so each delivery becomes a tracked Job/Activity.
8
+ *
9
+ * Determinism note: deliveries are awaited sequentially in connection-order
10
+ * (`Map` insertion order). Tests then see a stable causal ordering:
11
+ * user publishes E1 → agent A handles E1, publishes E2 → agent B handles
12
+ * E2 → … with each `await vault.scope().publish(...)` returning only
13
+ * after the entire chain settles.
14
+ */
15
+ export function createEventRuntime(opts) {
16
+ const { orchestrator } = opts;
17
+ return {
18
+ async publish(event, connections) {
19
+ // Collect first so we don't observe mutation mid-iteration if a
20
+ // handler reaches back into the vault to connect/disconnect agents.
21
+ // Routing matches if ANY engagement on the connection declares a
22
+ // handler for this event type — the orchestrator picks the right
23
+ // engagement per Job from `manifest.targeting`. (ADR-019 H4)
24
+ const targets = [];
25
+ for (const c of connections) {
26
+ const handles = c.manifest.engagements.some((e) => event.type in e.events);
27
+ if (handles)
28
+ targets.push(c);
29
+ }
30
+ // Sequential ordering keeps the simulator deterministic. Production
31
+ // can fan out concurrently — the simulator does not.
32
+ for (const c of targets) {
33
+ await orchestrator.dispatch(c, event);
34
+ }
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=event-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-runtime.js","sourceRoot":"","sources":["../../src/platform/event-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiBH,MAAM,UAAU,kBAAkB,CAAC,IAElC;IACC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC9B,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW;YAC9B,gEAAgE;YAChE,oEAAoE;YACpE,iEAAiE;YACjE,iEAAiE;YACjE,6DAA6D;YAC7D,MAAM,OAAO,GAAsB,EAAE,CAAC;YACtC,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CACzC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAC9B,CAAC;gBACF,IAAI,OAAO;oBAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC/B,CAAC;YACD,oEAAoE;YACpE,qDAAqD;YACrD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * In-memory Job/Activity store for the multi-agent simulator.
3
+ *
4
+ * Maps the contract §4 state machine onto plain TypeScript records:
5
+ * `Job` partitioned by `(vaultId, scope, context, agentId)`, lifetime
6
+ * `active` → `terminal`, with a synthetic `__start__` Activity inserted on
7
+ * first dispatch (per contract §6.1) and a synthetic `__close__` Activity
8
+ * inserted on terminal transition.
9
+ *
10
+ * This is not a persistent store — everything lives in two `Map`s and the
11
+ * simulator wipes them when the platform is disposed. The shape is
12
+ * deliberately faithful to the production orchestrator so app-side test
13
+ * code (`vault.jobs.list(...)`) reads the same fields.
14
+ */
15
+ import type { AgentConnection, VaultEvent } from "./vault.js";
16
+ import type { Runtime } from "./runtime.js";
17
+ export type JobState = "active" | "terminal";
18
+ export type ActivityStatus = "pending" | "running" | "completed" | "failed";
19
+ export interface Activity {
20
+ activityId: string;
21
+ jobId: string;
22
+ /**
23
+ * Either a real event type (e.g. `"user_message"`) or a synthetic
24
+ * lifecycle marker (`"__start__"` / `"__close__"`).
25
+ */
26
+ eventType: string;
27
+ status: ActivityStatus;
28
+ startedAt?: number;
29
+ completedAt?: number;
30
+ result?: unknown;
31
+ error?: string;
32
+ attemptNumber: number;
33
+ }
34
+ export interface Job {
35
+ jobId: string;
36
+ agentId: string;
37
+ vaultId: string;
38
+ scope: string;
39
+ context: string;
40
+ /**
41
+ * The engagement this Job is pinned to. Decided once at Job creation by
42
+ * evaluating the agent's `manifest.targeting` against the connection's
43
+ * `settings` — fixed for the Job's life (ADR-019).
44
+ */
45
+ engagementId: string;
46
+ state: JobState;
47
+ terminalReason?: string;
48
+ activities: Activity[];
49
+ createdAt: number;
50
+ }
51
+ export interface JobsFilter {
52
+ agentId?: string;
53
+ state?: JobState;
54
+ /**
55
+ * Restrict to jobs owned by this user (ADR-019 H5). Per-user vaults pass
56
+ * their own `vaultId` automatically; callers reaching the orchestrator
57
+ * directly can opt into a cross-user view by omitting this field.
58
+ */
59
+ vaultId?: string;
60
+ }
61
+ export interface Orchestrator {
62
+ /**
63
+ * Route a `(connection, event)` pair through the simulator: ensure a Job
64
+ * exists, fire the synthetic `__start__` activity if newly created, then
65
+ * run the real handler activity via the {@link Runtime}.
66
+ */
67
+ dispatch(connection: AgentConnection, event: VaultEvent): Promise<void>;
68
+ /**
69
+ * Mark a Job terminal. Synthesises a `__close__` Activity (if the
70
+ * connection's manifest declares an `onClose` / `@OnClose` hook) before
71
+ * flipping state.
72
+ */
73
+ terminate(jobId: string, reason: string, connection?: AgentConnection): Promise<void>;
74
+ /** Filter the in-memory job store. */
75
+ list(filter?: JobsFilter): Job[];
76
+ /** Direct lookup by `jobId`. */
77
+ get(jobId: string): Job | undefined;
78
+ /** Underlying map — exposed so the platform can clear it on `dispose()`. */
79
+ jobs: Map<string, Job>;
80
+ }
81
+ export interface OrchestratorOptions {
82
+ clock: () => number;
83
+ runtime: Runtime;
84
+ }
85
+ export declare function createOrchestrator(opts: OrchestratorOptions): Orchestrator;
86
+ //# sourceMappingURL=orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/platform/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAO5C,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE5E,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,QAAQ,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExE;;;;OAIG;IACH,SAAS,CACP,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,eAAe,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,sCAAsC;IACtC,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,GAAG,EAAE,CAAC;IAEjC,gCAAgC;IAChC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC;IAEpC,4EAA4E;IAC5E,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACxB;AAMD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,YAAY,CAiL1E"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * In-memory Job/Activity store for the multi-agent simulator.
3
+ *
4
+ * Maps the contract §4 state machine onto plain TypeScript records:
5
+ * `Job` partitioned by `(vaultId, scope, context, agentId)`, lifetime
6
+ * `active` → `terminal`, with a synthetic `__start__` Activity inserted on
7
+ * first dispatch (per contract §6.1) and a synthetic `__close__` Activity
8
+ * inserted on terminal transition.
9
+ *
10
+ * This is not a persistent store — everything lives in two `Map`s and the
11
+ * simulator wipes them when the platform is disposed. The shape is
12
+ * deliberately faithful to the production orchestrator so app-side test
13
+ * code (`vault.jobs.list(...)`) reads the same fields.
14
+ */
15
+ import { evaluateTargeting } from "./targeting.js";
16
+ export function createOrchestrator(opts) {
17
+ const { clock, runtime } = opts;
18
+ const jobs = new Map();
19
+ /**
20
+ * Composite-key index for dedup on `(vaultId, scope, context, agentId)`.
21
+ * ADR-019 H5: `vaultId` is the user identity, so two users connecting the
22
+ * same agent on the same scope + context get independent Jobs.
23
+ */
24
+ const jobByKey = new Map();
25
+ const partitionKey = (vaultId, scope, context, agentId) => `${vaultId}::${scope}::${context}::${agentId}`;
26
+ const orchestrator = {
27
+ jobs,
28
+ list(filter) {
29
+ const out = [];
30
+ for (const job of jobs.values()) {
31
+ if (filter?.agentId && job.agentId !== filter.agentId)
32
+ continue;
33
+ if (filter?.state && job.state !== filter.state)
34
+ continue;
35
+ if (filter?.vaultId && job.vaultId !== filter.vaultId)
36
+ continue;
37
+ out.push(job);
38
+ }
39
+ return out;
40
+ },
41
+ get(jobId) {
42
+ return jobs.get(jobId);
43
+ },
44
+ async dispatch(connection, event) {
45
+ const key = partitionKey(event.vaultId, event.scope, event.context, connection.agentId);
46
+ let jobId = jobByKey.get(key);
47
+ let job = jobId ? jobs.get(jobId) : undefined;
48
+ let isNew = false;
49
+ if (!job) {
50
+ // ADR-019 H4: pick the engagement at Job creation by evaluating the
51
+ // connection's targeting decision table against its settings. The
52
+ // chosen engagement id is pinned for the Job's life.
53
+ const engagementId = evaluateTargeting(connection.manifest.targeting, connection.settings);
54
+ // Targeting must resolve to a declared engagement; otherwise the
55
+ // configuration is broken and we'd silently misroute.
56
+ const engagement = connection.manifest.engagements.find((e) => e.id === engagementId);
57
+ if (!engagement) {
58
+ throw new Error(`[orchestrator] targeting resolved engagement "${engagementId}" but agent "${connection.agentId}" has no such engagement`);
59
+ }
60
+ jobId = `job-${randomHex(8)}`;
61
+ job = {
62
+ jobId,
63
+ agentId: connection.agentId,
64
+ // ADR-019 H5: vaultId comes from the event (= publishing user id).
65
+ vaultId: event.vaultId,
66
+ scope: event.scope,
67
+ context: event.context,
68
+ engagementId,
69
+ state: "active",
70
+ activities: [],
71
+ createdAt: clock(),
72
+ };
73
+ jobs.set(jobId, job);
74
+ jobByKey.set(key, jobId);
75
+ isNew = true;
76
+ }
77
+ // Don't deliver to terminal jobs — match the production orchestrator's
78
+ // refusal-to-dispatch behaviour.
79
+ if (job.state === "terminal")
80
+ return;
81
+ const engagement = connection.manifest.engagements.find((e) => e.id === job.engagementId);
82
+ // Safety: the engagement was resolved at Job creation; if it's gone
83
+ // from the connection's manifest now, something is badly wrong.
84
+ if (!engagement) {
85
+ throw new Error(`[orchestrator] job "${job.jobId}" pinned to engagement "${job.engagementId}" which is no longer declared on agent "${connection.agentId}"`);
86
+ }
87
+ // §6.1: first event into a new (vault, scope, context, agent) job
88
+ // synthesises a __start__ activity that runs before the real handler.
89
+ // Lifecycle hooks resolve through the pinned engagement, NOT the agent.
90
+ if (isNew && engagement.lifecycle?.start) {
91
+ await runActivity(job, "__start__", async () => {
92
+ await runtime.invoke(connection, job.engagementId, "__start__", event);
93
+ });
94
+ }
95
+ // If this engagement doesn't handle this event type, drop the event at
96
+ // the Job level — it never re-routes to a different engagement, since
97
+ // the Job is pinned (ADR-019).
98
+ if (!(event.type in engagement.events))
99
+ return;
100
+ await runActivity(job, event.type, async () => {
101
+ await runtime.invoke(connection, job.engagementId, event.type, event);
102
+ });
103
+ },
104
+ async terminate(jobId, reason, connection) {
105
+ const job = jobs.get(jobId);
106
+ if (!job || job.state === "terminal")
107
+ return;
108
+ if (connection) {
109
+ const engagement = connection.manifest.engagements.find((e) => e.id === job.engagementId);
110
+ if (engagement?.lifecycle?.close) {
111
+ await runActivity(job, "__close__", async () => {
112
+ await runtime.invoke(connection, job.engagementId, "__close__", {
113
+ id: `evt-${randomHex(8)}`,
114
+ type: "__close__",
115
+ ts: clock(),
116
+ from: "runtime",
117
+ payload: { reason },
118
+ vaultId: job.vaultId,
119
+ scope: job.scope,
120
+ context: job.context,
121
+ });
122
+ });
123
+ }
124
+ }
125
+ job.state = "terminal";
126
+ job.terminalReason = reason;
127
+ },
128
+ };
129
+ /**
130
+ * Wrap `fn` in an Activity record: append with `running`, then mark
131
+ * `completed` / `failed` based on the outcome. Errors are stored on the
132
+ * activity (not rethrown) — a handler crashing should not stop other
133
+ * agents from receiving the same event in the simulator.
134
+ */
135
+ async function runActivity(job, eventType, fn) {
136
+ const activity = {
137
+ activityId: `act-${randomHex(8)}`,
138
+ jobId: job.jobId,
139
+ eventType,
140
+ status: "running",
141
+ startedAt: clock(),
142
+ attemptNumber: 1,
143
+ };
144
+ job.activities.push(activity);
145
+ try {
146
+ await fn();
147
+ activity.status = "completed";
148
+ activity.completedAt = clock();
149
+ }
150
+ catch (err) {
151
+ activity.status = "failed";
152
+ activity.completedAt = clock();
153
+ activity.error =
154
+ err instanceof Error ? `${err.name}: ${err.message}` : String(err);
155
+ }
156
+ }
157
+ return orchestrator;
158
+ }
159
+ function randomHex(bytes) {
160
+ const buf = new Uint8Array(bytes);
161
+ globalThis.crypto.getRandomValues(buf);
162
+ return Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join("");
163
+ }
164
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/platform/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * In-process runtime for the multi-agent simulator.
3
+ *
4
+ * In production, the runtime is responsible for marshalling events into a
5
+ * sandbox (e.g. v8 isolate) and presenting a `ctx` to user code. In the
6
+ * simulator we skip the sandbox entirely and call the agent's instance
7
+ * method directly — same JavaScript process. The `ctx` is built fresh per
8
+ * invocation and closes over the platform's vault, orchestrator, cubbies
9
+ * and shared mocks.
10
+ *
11
+ * This is what makes app-side `vault.scope(...).publish` and agent-side
12
+ * `ctx.publish` route through the same in-memory bus.
13
+ */
14
+ import type { ModelHandle } from "@cef-ai/agent-sdk";
15
+ import type { TestCubby } from "../harness/cubby.js";
16
+ import type { FetchMock } from "../harness/fetch-mock.js";
17
+ import type { ModelMockHandle } from "../harness/model-mock.js";
18
+ import type { AgentConnection, Vault, VaultEvent } from "./vault.js";
19
+ /**
20
+ * Synthetic lifecycle "events" used to drive `@OnStart` / `@OnClose`
21
+ * hooks through the same dispatch path as real events.
22
+ */
23
+ export interface SyntheticLifecycleEvent {
24
+ /** `"__start__"` or `"__close__"`. */
25
+ type: "__start__" | "__close__";
26
+ /** Carries `{ reason }` for close. */
27
+ payload: unknown;
28
+ vaultId: string;
29
+ scope: string;
30
+ context: string;
31
+ }
32
+ export interface Runtime {
33
+ /**
34
+ * Invoke a handler on `connection.engagementInstances[engagementId]`.
35
+ * `eventTypeOrLifecycle` is either an actual event type (looked up in the
36
+ * engagement's `events` map) or one of `"__start__"` / `"__close__"`. The
37
+ * runtime handles ctx construction; callers supply only the event payload,
38
+ * the chosen engagement, and the routing.
39
+ */
40
+ invoke(connection: AgentConnection, engagementId: string, eventTypeOrLifecycle: string, event: VaultEvent | SyntheticLifecycleEvent): Promise<unknown>;
41
+ }
42
+ export interface RuntimeOptions {
43
+ /**
44
+ * Resolve the per-user vault by `vaultId`. Vault and runtime form a cycle
45
+ * (runtime needs vault for `ctx.publish` → `vault.scope().publish`; vault
46
+ * needs runtime via the orchestrator), so the platform threads a lookup.
47
+ * ADR-019 H5: with per-user vaults, `ctx.publish` from a handler must
48
+ * land back in the *publishing user's* vault — the same one the event
49
+ * arrived on, identified by `event.vaultId`.
50
+ */
51
+ vault: (vaultId: string) => Vault;
52
+ /**
53
+ * Per-`(user, agent)` cubby map (`vaultId` → `agentId` → `alias` → cubby).
54
+ * Owned by the platform. Each event's vaultId selects the user's slice
55
+ * before alias lookup so cubby state stays partitioned.
56
+ */
57
+ cubbies: Map<string, Map<string, Map<string, TestCubby>>>;
58
+ /** Shared fetch mock attached to every agent's `ctx.fetch`. */
59
+ fetchMock: FetchMock;
60
+ /** Shared model handles attached to every agent's `ctx.models`. */
61
+ models: Record<string, ModelHandle | ModelMockHandle>;
62
+ /** Shared monotonic clock. */
63
+ clock: () => number;
64
+ }
65
+ export declare function createRuntime(opts: RuntimeOptions): Runtime;
66
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/platform/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,sCAAsC;IACtC,IAAI,EAAE,WAAW,GAAG,WAAW,CAAC;IAChC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB;;;;;;OAMG;IACH,MAAM,CACJ,UAAU,EAAE,eAAe,EAC3B,YAAY,EAAE,MAAM,EACpB,oBAAoB,EAAE,MAAM,EAC5B,KAAK,EAAE,UAAU,GAAG,uBAAuB,GAC1C,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;IAClC;;;;OAIG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAC1D,+DAA+D;IAC/D,SAAS,EAAE,SAAS,CAAC;IACrB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,eAAe,CAAC,CAAC;IACtD,8BAA8B;IAC9B,KAAK,EAAE,MAAM,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAoI3D"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * In-process runtime for the multi-agent simulator.
3
+ *
4
+ * In production, the runtime is responsible for marshalling events into a
5
+ * sandbox (e.g. v8 isolate) and presenting a `ctx` to user code. In the
6
+ * simulator we skip the sandbox entirely and call the agent's instance
7
+ * method directly — same JavaScript process. The `ctx` is built fresh per
8
+ * invocation and closes over the platform's vault, orchestrator, cubbies
9
+ * and shared mocks.
10
+ *
11
+ * This is what makes app-side `vault.scope(...).publish` and agent-side
12
+ * `ctx.publish` route through the same in-memory bus.
13
+ */
14
+ export function createRuntime(opts) {
15
+ const { vault, cubbies, fetchMock, models } = opts;
16
+ // `opts.clock` is accepted for symmetry / future use (e.g. ctx-side
17
+ // telemetry stamps); not consumed by the current ctx surface.
18
+ void opts.clock;
19
+ return {
20
+ async invoke(connection, engagementId, eventTypeOrLifecycle, event) {
21
+ const engagement = connection.manifest.engagements.find((e) => e.id === engagementId);
22
+ if (!engagement) {
23
+ throw new Error(`[runtime] engagement "${engagementId}" is not declared on agent "${connection.agentId}"`);
24
+ }
25
+ const instance = connection.engagementInstances[engagementId];
26
+ if (!instance) {
27
+ throw new Error(`[runtime] no instance for engagement "${engagementId}" on agent "${connection.agentId}"`);
28
+ }
29
+ const ctx = buildCtx(connection, event.vaultId, event.scope, event.context);
30
+ // Resolve handler name: real events look it up in the engagement's
31
+ // `events` map; lifecycle events resolve through `lifecycle.start` /
32
+ // `lifecycle.close`.
33
+ let handlerName;
34
+ if (eventTypeOrLifecycle === "__start__") {
35
+ handlerName = engagement.lifecycle?.start;
36
+ }
37
+ else if (eventTypeOrLifecycle === "__close__") {
38
+ handlerName = engagement.lifecycle?.close;
39
+ }
40
+ else {
41
+ handlerName = engagement.events[eventTypeOrLifecycle];
42
+ }
43
+ if (!handlerName)
44
+ return;
45
+ const fn = instance[handlerName];
46
+ if (typeof fn !== "function") {
47
+ throw new Error(`[runtime] handler "${handlerName}" for "${eventTypeOrLifecycle}" is not a function on agent "${connection.agentId}" engagement "${engagementId}"`);
48
+ }
49
+ // `@OnClose` is invoked with `(ctx, reason)` per the SDK contract; all
50
+ // other handlers receive `(event, ctx)`. Lifecycle start has just `ctx`.
51
+ if (eventTypeOrLifecycle === "__start__") {
52
+ return await fn.call(instance, ctx);
53
+ }
54
+ if (eventTypeOrLifecycle === "__close__") {
55
+ const reason = event.payload
56
+ ?.reason ?? "closed_by_agent";
57
+ return await fn.call(instance, ctx, reason);
58
+ }
59
+ return await fn.call(instance, event, ctx);
60
+ },
61
+ };
62
+ /**
63
+ * Build a per-invocation `ctx`. Closes over the vault id + scope/context
64
+ * the event arrived on — `ctx.publish` re-publishes within the same
65
+ * (user, scope, context) so peers in that user's scope can pick it up.
66
+ */
67
+ function buildCtx(connection, vaultId, scope, context) {
68
+ const settings = Object.freeze({ ...connection.settings });
69
+ return {
70
+ cubby(alias) {
71
+ // ADR-019 H5: cubbies are per-user — select the publishing user's
72
+ // slice before alias lookup so two users sharing an agent don't
73
+ // see each other's cubby state.
74
+ const userCubbies = cubbies.get(vaultId);
75
+ const c = userCubbies?.get(connection.agentId)?.get(alias);
76
+ if (!c) {
77
+ throw new Error(`[runtime] cubby "${alias}" not declared for agent "${connection.agentId}"`);
78
+ }
79
+ return { query: c.query.bind(c), exec: c.exec.bind(c) };
80
+ },
81
+ models: models,
82
+ fetch: fetchMock.fetch,
83
+ async publish(type, payload) {
84
+ // Re-enter the publishing user's vault on the same scope/context
85
+ // so peers and the app-side stream both observe handler-published
86
+ // events under the right vaultId.
87
+ await vault(vaultId)
88
+ .scope(scope)
89
+ .publish(type, payload, {
90
+ context,
91
+ from: connection.agentId,
92
+ });
93
+ },
94
+ settings,
95
+ async close(reason) {
96
+ // Agent-initiated close terminates the job for this user's
97
+ // (scope, context, agent) partition. We look up the job via the
98
+ // user's vault (which carries the orchestrator).
99
+ const v = vault(vaultId);
100
+ const job = v.jobs
101
+ .list({ agentId: connection.agentId })
102
+ .find((j) => j.scope === scope && j.context === context);
103
+ if (job) {
104
+ await v._terminate(job.jobId, reason ?? "closed_by_agent", connection);
105
+ }
106
+ },
107
+ log: {
108
+ info() { },
109
+ warn() { },
110
+ error() { },
111
+ debug() { },
112
+ },
113
+ };
114
+ }
115
+ }
116
+ //# sourceMappingURL=runtime.js.map