@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
|
@@ -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
|