@intx/hub-sessions 0.1.2 → 0.2.2
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/LICENSE +176 -0
- package/README.md +84 -1
- package/dist/agent-repo.d.ts +89 -0
- package/dist/agent-repo.js +109 -0
- package/dist/agent-state-kind.d.ts +12 -0
- package/dist/agent-state-kind.js +185 -0
- package/dist/asset-service.d.ts +123 -0
- package/dist/asset-service.js +349 -0
- package/dist/available-skills-stanza.d.ts +21 -0
- package/dist/available-skills-stanza.js +32 -0
- package/dist/credential-push.d.ts +32 -0
- package/dist/credential-push.js +85 -0
- package/dist/event-collector-registry.d.ts +20 -0
- package/dist/event-collector-registry.js +115 -0
- package/dist/event-collector.d.ts +39 -0
- package/dist/event-collector.js +357 -0
- package/dist/hub-session-lookups.d.ts +17 -0
- package/dist/hub-session-lookups.js +204 -0
- package/dist/hub-session-orchestrator.d.ts +25 -0
- package/dist/hub-session-orchestrator.js +122 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +16 -0
- package/dist/package-registry-kind.d.ts +70 -0
- package/dist/package-registry-kind.js +260 -0
- package/dist/repo-store/index.d.ts +4 -0
- package/dist/repo-store/index.js +3 -0
- package/dist/repo-store/store.d.ts +41 -0
- package/dist/repo-store/store.js +1692 -0
- package/dist/repo-store/subscribe-kind.d.ts +53 -0
- package/dist/repo-store/subscribe-kind.js +179 -0
- package/dist/repo-store/types.d.ts +483 -0
- package/dist/repo-store/types.js +42 -0
- package/dist/session-service.d.ts +235 -0
- package/dist/session-service.js +997 -0
- package/dist/skill-kind.d.ts +41 -0
- package/dist/skill-kind.js +288 -0
- package/dist/substrate.d.ts +8 -0
- package/dist/substrate.js +21 -0
- package/dist/workflow-kind.d.ts +21 -0
- package/dist/workflow-kind.js +263 -0
- package/dist/workflow-run-event-log.d.ts +21 -0
- package/dist/workflow-run-event-log.js +51 -0
- package/dist/workflow-run-kind.d.ts +326 -0
- package/dist/workflow-run-kind.js +2646 -0
- package/dist/workflow-run-reader.d.ts +47 -0
- package/dist/workflow-run-reader.js +157 -0
- package/dist/ws/index.d.ts +3 -0
- package/dist/ws/index.js +3 -0
- package/dist/ws/sidecar-events.d.ts +134 -0
- package/dist/ws/sidecar-events.js +70 -0
- package/dist/ws/sidecar-handler.d.ts +184 -0
- package/dist/ws/sidecar-handler.js +1603 -0
- package/dist/ws/sidecar-token-authenticator.d.ts +15 -0
- package/dist/ws/sidecar-token-authenticator.js +24 -0
- package/package.json +34 -12
- package/src/agent-repo.test.ts +0 -310
- package/src/agent-repo.ts +0 -165
- package/src/agent-state-kind.test.ts +0 -247
- package/src/agent-state-kind.ts +0 -204
- package/src/asset-service.test.ts +0 -540
- package/src/asset-service.ts +0 -378
- package/src/available-skills-stanza.test.ts +0 -87
- package/src/available-skills-stanza.ts +0 -47
- package/src/credential-push.ts +0 -65
- package/src/event-collector-registry.test.ts +0 -73
- package/src/event-collector-registry.ts +0 -171
- package/src/event-collector.test.ts +0 -1387
- package/src/event-collector.ts +0 -424
- package/src/hub-session-lookups.ts +0 -206
- package/src/hub-session-orchestrator.test.ts +0 -510
- package/src/hub-session-orchestrator.ts +0 -213
- package/src/index.ts +0 -78
- package/src/repo-store/index.ts +0 -15
- package/src/repo-store/store.test.ts +0 -1169
- package/src/repo-store/store.ts +0 -428
- package/src/repo-store/types.ts +0 -253
- package/src/session-service.test.ts +0 -895
- package/src/session-service.ts +0 -464
- package/src/skill-kind.test.ts +0 -599
- package/src/skill-kind.ts +0 -350
- package/src/ws/index.ts +0 -18
- package/src/ws/sidecar-events.test.ts +0 -96
- package/src/ws/sidecar-events.ts +0 -231
- package/src/ws/sidecar-handler.test.ts +0 -2217
- package/src/ws/sidecar-handler.ts +0 -1574
- package/tsconfig.json +0 -4
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,510 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type { DB } from "@intx/db";
|
|
4
|
-
import type { GrantRule, GrantStore } from "@intx/types/authz";
|
|
5
|
-
import type { InferenceEvent, InferenceSource } from "@intx/types/runtime";
|
|
6
|
-
|
|
7
|
-
import type { AgentRepoStore, DeployContent } from "./agent-repo";
|
|
8
|
-
import type { RepoStore } from "./repo-store";
|
|
9
|
-
import type { EventCollectorRegistry } from "./event-collector-registry";
|
|
10
|
-
import { createHubSessionOrchestrator } from "./hub-session-orchestrator";
|
|
11
|
-
import {
|
|
12
|
-
createSidecarEmitter,
|
|
13
|
-
type SidecarEventEmitter,
|
|
14
|
-
} from "./ws/sidecar-events";
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Fixtures and mocks
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
const TENANT_ID = "tnt_1";
|
|
21
|
-
const PRINCIPAL_ID = "prn_1";
|
|
22
|
-
const INSTANCE_ID = "ins_1";
|
|
23
|
-
const AGENT_ID = "agt_1";
|
|
24
|
-
const AGENT_ADDRESS = "ins_1@tenant.local";
|
|
25
|
-
const SESSION_ID = "ses_1";
|
|
26
|
-
|
|
27
|
-
type InstanceStatus = "deployed" | "running" | "updating" | "error" | "stopped";
|
|
28
|
-
|
|
29
|
-
type InstanceRow = {
|
|
30
|
-
id: string;
|
|
31
|
-
agentId: string;
|
|
32
|
-
tenantId: string;
|
|
33
|
-
principalId: string;
|
|
34
|
-
address: string;
|
|
35
|
-
status: InstanceStatus;
|
|
36
|
-
sessionId: string | null;
|
|
37
|
-
publicKey: string | null;
|
|
38
|
-
createdAt: Date;
|
|
39
|
-
updatedAt: Date;
|
|
40
|
-
endedAt: Date | null;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
function makeInstance(overrides: Partial<InstanceRow> = {}): InstanceRow {
|
|
44
|
-
return {
|
|
45
|
-
id: INSTANCE_ID,
|
|
46
|
-
agentId: AGENT_ID,
|
|
47
|
-
tenantId: TENANT_ID,
|
|
48
|
-
principalId: PRINCIPAL_ID,
|
|
49
|
-
address: AGENT_ADDRESS,
|
|
50
|
-
status: "running",
|
|
51
|
-
sessionId: SESSION_ID,
|
|
52
|
-
publicKey: null,
|
|
53
|
-
createdAt: new Date("2026-01-01"),
|
|
54
|
-
updatedAt: new Date("2026-01-01"),
|
|
55
|
-
endedAt: null,
|
|
56
|
-
...overrides,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
type UpdateCall = { table: string; set: Record<string, unknown> };
|
|
61
|
-
|
|
62
|
-
type MockDBOpts = {
|
|
63
|
-
instance?: InstanceRow | undefined;
|
|
64
|
-
recordUpdates?: UpdateCall[];
|
|
65
|
-
/** When set, `resolveInstanceSources` queries fan out into these
|
|
66
|
-
* tables; the helper returns empty arrays so the orchestrator's
|
|
67
|
-
* credential push is a no-op. */
|
|
68
|
-
emptyProviderTables?: boolean;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function createMockDB(opts: MockDBOpts) {
|
|
72
|
-
const updates = opts.recordUpdates ?? [];
|
|
73
|
-
|
|
74
|
-
function tableName(t: unknown): string {
|
|
75
|
-
if (t && typeof t === "object" && "name" in t && typeof t.name === "string")
|
|
76
|
-
return t.name;
|
|
77
|
-
return "<unknown>";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion --
|
|
81
|
-
* drizzle PgDatabase type cannot be structurally satisfied in tests */
|
|
82
|
-
return {
|
|
83
|
-
query: {
|
|
84
|
-
agentInstance: {
|
|
85
|
-
findFirst: async () => opts.instance,
|
|
86
|
-
},
|
|
87
|
-
agent: { findFirst: async () => undefined },
|
|
88
|
-
agentSession: { findFirst: async () => undefined },
|
|
89
|
-
provider: { findFirst: async () => undefined, findMany: async () => [] },
|
|
90
|
-
credential: {
|
|
91
|
-
findFirst: async () => undefined,
|
|
92
|
-
findMany: async () => [],
|
|
93
|
-
},
|
|
94
|
-
oauthClient: { findFirst: async () => undefined },
|
|
95
|
-
tenant: { findFirst: async () => undefined },
|
|
96
|
-
},
|
|
97
|
-
update(t: unknown) {
|
|
98
|
-
return {
|
|
99
|
-
set(values: Record<string, unknown>) {
|
|
100
|
-
return {
|
|
101
|
-
where: async () => {
|
|
102
|
-
updates.push({ table: tableName(t), set: values });
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
},
|
|
108
|
-
select() {
|
|
109
|
-
return {
|
|
110
|
-
from: () => ({
|
|
111
|
-
where: () => ({
|
|
112
|
-
limit: () => Promise.resolve([]),
|
|
113
|
-
orderBy: () => ({ limit: () => Promise.resolve([]) }),
|
|
114
|
-
}),
|
|
115
|
-
innerJoin: () => ({
|
|
116
|
-
where: () => ({ limit: () => Promise.resolve([]) }),
|
|
117
|
-
}),
|
|
118
|
-
}),
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
} as unknown as DB["db"];
|
|
122
|
-
/* eslint-enable @typescript-eslint/no-unsafe-type-assertion */
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
type RouterCall =
|
|
126
|
-
| { kind: "sendGrantsUpdate"; addr: string; grants: GrantRule[] }
|
|
127
|
-
| {
|
|
128
|
-
kind: "sendSourcesUpdate";
|
|
129
|
-
addr: string;
|
|
130
|
-
sources: InferenceSource[];
|
|
131
|
-
defaultSource: string;
|
|
132
|
-
}
|
|
133
|
-
| {
|
|
134
|
-
kind: "sendPack";
|
|
135
|
-
addr: string;
|
|
136
|
-
pack: Uint8Array;
|
|
137
|
-
ref: string;
|
|
138
|
-
sha: string;
|
|
139
|
-
}
|
|
140
|
-
| { kind: "dispatchAgentEvent"; addr: string; event: unknown };
|
|
141
|
-
|
|
142
|
-
function createRouterFacade(): {
|
|
143
|
-
facade: Parameters<typeof createHubSessionOrchestrator>[0]["router"];
|
|
144
|
-
calls: RouterCall[];
|
|
145
|
-
} {
|
|
146
|
-
const calls: RouterCall[] = [];
|
|
147
|
-
return {
|
|
148
|
-
calls,
|
|
149
|
-
facade: {
|
|
150
|
-
async sendGrantsUpdate(addr, grants) {
|
|
151
|
-
calls.push({ kind: "sendGrantsUpdate", addr, grants });
|
|
152
|
-
},
|
|
153
|
-
async sendSourcesUpdate(addr, sources, defaultSource) {
|
|
154
|
-
calls.push({ kind: "sendSourcesUpdate", addr, sources, defaultSource });
|
|
155
|
-
},
|
|
156
|
-
async sendPack(addr, pack, ref, sha) {
|
|
157
|
-
calls.push({ kind: "sendPack", addr, pack, ref, sha });
|
|
158
|
-
},
|
|
159
|
-
dispatchAgentEvent(addr, event) {
|
|
160
|
-
calls.push({ kind: "dispatchAgentEvent", addr, event });
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
type CollectorCall =
|
|
167
|
-
| { kind: "create"; addr: string; sessionId: string }
|
|
168
|
-
| { kind: "dispatch"; addr: string; event: InferenceEvent }
|
|
169
|
-
| { kind: "abandon"; addr: string };
|
|
170
|
-
|
|
171
|
-
function createCollectorRegistry(
|
|
172
|
-
initiallyHas: Set<string> = new Set<string>(),
|
|
173
|
-
): {
|
|
174
|
-
registry: EventCollectorRegistry;
|
|
175
|
-
calls: CollectorCall[];
|
|
176
|
-
has: Set<string>;
|
|
177
|
-
} {
|
|
178
|
-
const calls: CollectorCall[] = [];
|
|
179
|
-
const has = new Set(initiallyHas);
|
|
180
|
-
return {
|
|
181
|
-
calls,
|
|
182
|
-
has,
|
|
183
|
-
registry: {
|
|
184
|
-
create(addr, _tenantId, sessionId, _instanceId) {
|
|
185
|
-
has.add(addr);
|
|
186
|
-
calls.push({ kind: "create", addr, sessionId });
|
|
187
|
-
},
|
|
188
|
-
dispatch(addr, event) {
|
|
189
|
-
calls.push({ kind: "dispatch", addr, event });
|
|
190
|
-
},
|
|
191
|
-
abandon(addr) {
|
|
192
|
-
has.delete(addr);
|
|
193
|
-
calls.push({ kind: "abandon", addr });
|
|
194
|
-
},
|
|
195
|
-
has: (addr) => has.has(addr),
|
|
196
|
-
getStatus: () => undefined,
|
|
197
|
-
getAccumulatedText: () => undefined,
|
|
198
|
-
getCurrentTurnId: () => undefined,
|
|
199
|
-
getLastTurnId: () => undefined,
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function createGrantStoreStub(grants: GrantRule[] = []): GrantStore {
|
|
205
|
-
return {
|
|
206
|
-
collectGrants: async () => grants,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
type RepoCall = { kind: "createDeployPack"; agentId: string };
|
|
211
|
-
|
|
212
|
-
function createRepoStoreStub(): {
|
|
213
|
-
store: AgentRepoStore;
|
|
214
|
-
calls: RepoCall[];
|
|
215
|
-
} {
|
|
216
|
-
const calls: RepoCall[] = [];
|
|
217
|
-
return {
|
|
218
|
-
calls,
|
|
219
|
-
store: {
|
|
220
|
-
async writeDeployTree(_agentId: string, _content: DeployContent) {
|
|
221
|
-
throw new Error("mock: writeDeployTree not implemented");
|
|
222
|
-
},
|
|
223
|
-
async createDeployPack(agentId: string) {
|
|
224
|
-
calls.push({ kind: "createDeployPack", agentId });
|
|
225
|
-
return {
|
|
226
|
-
pack: new Uint8Array([1, 2, 3]),
|
|
227
|
-
commitSha: "c".repeat(40),
|
|
228
|
-
ref: "refs/heads/deploy",
|
|
229
|
-
};
|
|
230
|
-
},
|
|
231
|
-
async receiveStatePack() {
|
|
232
|
-
throw new Error("mock: receiveStatePack not implemented");
|
|
233
|
-
},
|
|
234
|
-
getSigningPublicKey() {
|
|
235
|
-
return new Uint8Array(32);
|
|
236
|
-
},
|
|
237
|
-
async getDeployRef() {
|
|
238
|
-
return null;
|
|
239
|
-
},
|
|
240
|
-
repoStore: unusedRepoStore(),
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function unusedRepoStore(): RepoStore {
|
|
246
|
-
// The orchestrator tests do not touch the substrate; a throwing
|
|
247
|
-
// stub keeps the AgentRepoStore surface fully populated without
|
|
248
|
-
// pulling a real on-disk store into orchestrator-level unit tests.
|
|
249
|
-
const unused = () =>
|
|
250
|
-
Promise.reject(new Error("mock AgentRepoStore.repoStore is not wired"));
|
|
251
|
-
return {
|
|
252
|
-
initRepo: unused,
|
|
253
|
-
writeTree: unused,
|
|
254
|
-
receivePack: unused,
|
|
255
|
-
createPack: unused,
|
|
256
|
-
resolveRef: unused,
|
|
257
|
-
listRefs: unused,
|
|
258
|
-
resolveHead: unused,
|
|
259
|
-
getRepoDir: () => {
|
|
260
|
-
throw new Error("mock AgentRepoStore.repoStore is not wired");
|
|
261
|
-
},
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
type Harness = {
|
|
266
|
-
events: SidecarEventEmitter;
|
|
267
|
-
router: ReturnType<typeof createRouterFacade>;
|
|
268
|
-
collectors: ReturnType<typeof createCollectorRegistry>;
|
|
269
|
-
repo: ReturnType<typeof createRepoStoreStub>;
|
|
270
|
-
updates: UpdateCall[];
|
|
271
|
-
dispose: () => void;
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
function setup(opts: MockDBOpts & { grants?: GrantRule[] } = {}): Harness {
|
|
275
|
-
const updates: UpdateCall[] = [];
|
|
276
|
-
const db = createMockDB({ ...opts, recordUpdates: updates });
|
|
277
|
-
const events = createSidecarEmitter();
|
|
278
|
-
const router = createRouterFacade();
|
|
279
|
-
const collectors = createCollectorRegistry();
|
|
280
|
-
const repo = createRepoStoreStub();
|
|
281
|
-
const grantStore = createGrantStoreStub(opts.grants);
|
|
282
|
-
|
|
283
|
-
const orchestrator = createHubSessionOrchestrator({
|
|
284
|
-
events,
|
|
285
|
-
router: router.facade,
|
|
286
|
-
db,
|
|
287
|
-
eventCollectors: collectors.registry,
|
|
288
|
-
grantStore,
|
|
289
|
-
agentRepoStore: repo.store,
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
events,
|
|
294
|
-
router,
|
|
295
|
-
collectors,
|
|
296
|
-
repo,
|
|
297
|
-
updates,
|
|
298
|
-
dispose: orchestrator.dispose,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// ---------------------------------------------------------------------------
|
|
303
|
-
// Tests
|
|
304
|
-
// ---------------------------------------------------------------------------
|
|
305
|
-
|
|
306
|
-
describe("createHubSessionOrchestrator", () => {
|
|
307
|
-
let harness: Harness;
|
|
308
|
-
|
|
309
|
-
beforeEach(() => {
|
|
310
|
-
harness = setup({ instance: makeInstance() });
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
describe("agent.event", () => {
|
|
314
|
-
test("valid event is dispatched to the collector registry", () => {
|
|
315
|
-
// reactor.start is a valid InferenceEvent that arktype's
|
|
316
|
-
// parseInferenceEvent will accept; the orchestrator dispatches
|
|
317
|
-
// it to the collector unchanged.
|
|
318
|
-
harness.events.emit("agent.event", {
|
|
319
|
-
agentAddress: AGENT_ADDRESS,
|
|
320
|
-
sessionId: SESSION_ID,
|
|
321
|
-
event: { type: "reactor.start", seq: 1, data: { tools: [] } },
|
|
322
|
-
});
|
|
323
|
-
const dispatched = harness.collectors.calls.find(
|
|
324
|
-
(c) => c.kind === "dispatch",
|
|
325
|
-
);
|
|
326
|
-
expect(dispatched).toBeDefined();
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test("invalid event is dropped (no dispatch call)", () => {
|
|
330
|
-
harness.events.emit("agent.event", {
|
|
331
|
-
agentAddress: AGENT_ADDRESS,
|
|
332
|
-
sessionId: SESSION_ID,
|
|
333
|
-
event: { not_a_real_event: true },
|
|
334
|
-
});
|
|
335
|
-
const dispatched = harness.collectors.calls.find(
|
|
336
|
-
(c) => c.kind === "dispatch",
|
|
337
|
-
);
|
|
338
|
-
expect(dispatched).toBeUndefined();
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
describe("sidecar.disconnect", () => {
|
|
343
|
-
test("abandons every collector for the closed connection", () => {
|
|
344
|
-
harness.events.emit("sidecar.disconnect", {
|
|
345
|
-
agentAddresses: ["a@x", "b@x", "c@x"],
|
|
346
|
-
});
|
|
347
|
-
const abandonedAddrs = harness.collectors.calls
|
|
348
|
-
.filter((c) => c.kind === "abandon")
|
|
349
|
-
.map((c) => (c.kind === "abandon" ? c.addr : null));
|
|
350
|
-
expect(abandonedAddrs).toEqual(["a@x", "b@x", "c@x"]);
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
describe("agent.deploy.ack", () => {
|
|
355
|
-
test("stores the public key on the active instance", async () => {
|
|
356
|
-
await harness.events.emitAndAwait("agent.deploy.ack", {
|
|
357
|
-
agentAddress: AGENT_ADDRESS,
|
|
358
|
-
publicKey: "deadbeef",
|
|
359
|
-
});
|
|
360
|
-
expect(harness.updates).toHaveLength(1);
|
|
361
|
-
expect(harness.updates[0]?.set).toEqual({ publicKey: "deadbeef" });
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
describe("agent.reconnected", () => {
|
|
366
|
-
test("refreshes grants and skips status update when already running", async () => {
|
|
367
|
-
const grant: GrantRule = {
|
|
368
|
-
id: "grant-1",
|
|
369
|
-
resource: "x:y",
|
|
370
|
-
action: "read",
|
|
371
|
-
effect: "allow",
|
|
372
|
-
origin: "system",
|
|
373
|
-
conditions: null,
|
|
374
|
-
expiresAt: null,
|
|
375
|
-
roleId: null,
|
|
376
|
-
principalId: PRINCIPAL_ID,
|
|
377
|
-
};
|
|
378
|
-
harness = setup({ instance: makeInstance(), grants: [grant] });
|
|
379
|
-
|
|
380
|
-
await harness.events.emitAndAwait("agent.reconnected", {
|
|
381
|
-
agentAddress: AGENT_ADDRESS,
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
const grantsCall = harness.router.calls.find(
|
|
385
|
-
(c) => c.kind === "sendGrantsUpdate",
|
|
386
|
-
);
|
|
387
|
-
expect(grantsCall).toBeDefined();
|
|
388
|
-
if (grantsCall?.kind === "sendGrantsUpdate") {
|
|
389
|
-
expect(grantsCall.grants).toEqual([grant]);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// status was already "running", no update should fire
|
|
393
|
-
expect(harness.updates).toHaveLength(0);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
test("flips status to running and restores collector when missing", async () => {
|
|
397
|
-
harness = setup({ instance: makeInstance({ status: "deployed" }) });
|
|
398
|
-
|
|
399
|
-
await harness.events.emitAndAwait("agent.reconnected", {
|
|
400
|
-
agentAddress: AGENT_ADDRESS,
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
const statusUpdate = harness.updates.find(
|
|
404
|
-
(u) => u.set["status"] === "running",
|
|
405
|
-
);
|
|
406
|
-
expect(statusUpdate).toBeDefined();
|
|
407
|
-
|
|
408
|
-
const created = harness.collectors.calls.find((c) => c.kind === "create");
|
|
409
|
-
expect(created).toBeDefined();
|
|
410
|
-
if (created?.kind === "create") {
|
|
411
|
-
expect(created.addr).toBe(AGENT_ADDRESS);
|
|
412
|
-
expect(created.sessionId).toBe(SESSION_ID);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
test("throws when the instance has no active session", async () => {
|
|
417
|
-
harness = setup({ instance: makeInstance({ sessionId: null }) });
|
|
418
|
-
|
|
419
|
-
await expect(
|
|
420
|
-
harness.events.emitAndAwait("agent.reconnected", {
|
|
421
|
-
agentAddress: AGENT_ADDRESS,
|
|
422
|
-
}),
|
|
423
|
-
).rejects.toThrow(/no active session/);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
test("throws when no active instance exists", async () => {
|
|
427
|
-
harness = setup({ instance: undefined });
|
|
428
|
-
|
|
429
|
-
await expect(
|
|
430
|
-
harness.events.emitAndAwait("agent.reconnected", {
|
|
431
|
-
agentAddress: AGENT_ADDRESS,
|
|
432
|
-
}),
|
|
433
|
-
).rejects.toThrow(/No active instance/);
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
describe("deploy.ref.stale", () => {
|
|
438
|
-
test("creates a deploy pack and pushes it via the router", async () => {
|
|
439
|
-
await harness.events.emitAndAwait("deploy.ref.stale", {
|
|
440
|
-
agentAddress: AGENT_ADDRESS,
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
expect(harness.repo.calls).toEqual([
|
|
444
|
-
{ kind: "createDeployPack", agentId: "ins_1" },
|
|
445
|
-
]);
|
|
446
|
-
|
|
447
|
-
const sendPack = harness.router.calls.find((c) => c.kind === "sendPack");
|
|
448
|
-
expect(sendPack).toBeDefined();
|
|
449
|
-
if (sendPack?.kind === "sendPack") {
|
|
450
|
-
expect(sendPack.addr).toBe(AGENT_ADDRESS);
|
|
451
|
-
expect(sendPack.ref).toBe("refs/heads/deploy");
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
describe("mail.persisted", () => {
|
|
457
|
-
test("dispatches a mail.delivered agent event for the recipient", () => {
|
|
458
|
-
const raw = new TextEncoder().encode(
|
|
459
|
-
[
|
|
460
|
-
"From: sender@x",
|
|
461
|
-
"To: recipient@x",
|
|
462
|
-
"Subject: hello",
|
|
463
|
-
"",
|
|
464
|
-
"body",
|
|
465
|
-
"",
|
|
466
|
-
].join("\r\n"),
|
|
467
|
-
);
|
|
468
|
-
const createdAt = new Date("2026-01-01T00:00:00.000Z");
|
|
469
|
-
|
|
470
|
-
harness.events.emit("mail.persisted", {
|
|
471
|
-
id: "mail_1",
|
|
472
|
-
raw,
|
|
473
|
-
createdAt,
|
|
474
|
-
direction: "inbound",
|
|
475
|
-
instanceId: INSTANCE_ID,
|
|
476
|
-
address: AGENT_ADDRESS,
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
const dispatched = harness.router.calls.find(
|
|
480
|
-
(c) => c.kind === "dispatchAgentEvent",
|
|
481
|
-
);
|
|
482
|
-
expect(dispatched).toBeDefined();
|
|
483
|
-
if (dispatched?.kind !== "dispatchAgentEvent") return;
|
|
484
|
-
expect(dispatched.addr).toBe(AGENT_ADDRESS);
|
|
485
|
-
|
|
486
|
-
const evt = dispatched.event;
|
|
487
|
-
expect(evt).toMatchObject({
|
|
488
|
-
type: "mail.delivered",
|
|
489
|
-
data: {
|
|
490
|
-
id: "mail_1",
|
|
491
|
-
direction: "inbound",
|
|
492
|
-
receivedAt: "2026-01-01T00:00:00.000Z",
|
|
493
|
-
},
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
describe("dispose", () => {
|
|
499
|
-
test("removes all subscriptions so later emits are inert", () => {
|
|
500
|
-
harness.dispose();
|
|
501
|
-
harness.events.emit("sidecar.disconnect", {
|
|
502
|
-
agentAddresses: [AGENT_ADDRESS],
|
|
503
|
-
});
|
|
504
|
-
const abandoned = harness.collectors.calls.find(
|
|
505
|
-
(c) => c.kind === "abandon",
|
|
506
|
-
);
|
|
507
|
-
expect(abandoned).toBeUndefined();
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
});
|