@meistrari/agent-core 0.0.0 → 0.1.1

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 (85) hide show
  1. package/README.md +36 -3
  2. package/bin/supervisor.ts +116 -0
  3. package/package.json +42 -3
  4. package/scripts/build-supervisor-executable.ts +32 -0
  5. package/src/agents/agent-error-serializer.ts +23 -0
  6. package/src/agents/agent-event-stream.ts +81 -0
  7. package/src/agents/agent-id.ts +17 -0
  8. package/src/agents/agent-operation.ts +11 -0
  9. package/src/agents/agent-provider.ts +37 -0
  10. package/src/agents/agent-run.ts +32 -0
  11. package/src/agents/agent-runtime-error.ts +161 -0
  12. package/src/agents/agent-session-events.ts +32 -0
  13. package/src/agents/agent-tool-runner.ts +83 -0
  14. package/src/agents/agent-tool.ts +111 -0
  15. package/src/agents/author-context.ts +36 -0
  16. package/src/agents/claude/claude-command-mapper.ts +234 -0
  17. package/src/agents/claude/claude-event-mapper.ts +748 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +468 -0
  20. package/src/agents/claude/claude-tool-mapper.ts +186 -0
  21. package/src/agents/claude/index.ts +1 -0
  22. package/src/agents/codex/codex-auth.ts +34 -0
  23. package/src/agents/codex/codex-command-mapper.ts +78 -0
  24. package/src/agents/codex/codex-event-mapper.ts +721 -0
  25. package/src/agents/codex/codex-json-rpc-client.ts +326 -0
  26. package/src/agents/codex/codex-protocol.ts +36 -0
  27. package/src/agents/codex/codex-provider.ts +1050 -0
  28. package/src/agents/codex/codex-run.ts +404 -0
  29. package/src/agents/codex/codex-skill-catalog.ts +158 -0
  30. package/src/agents/codex/codex-skill-roots.ts +19 -0
  31. package/src/agents/codex/codex-tool-mapper.ts +55 -0
  32. package/src/agents/codex/codex.errors.ts +68 -0
  33. package/src/agents/codex/generated/meta.gen.ts +606 -0
  34. package/src/agents/codex/generated/namespaces.gen.ts +311 -0
  35. package/src/agents/codex/generated/schema.gen.ts +34883 -0
  36. package/src/agents/codex/index.ts +2 -0
  37. package/src/agents/index.ts +14 -0
  38. package/src/agents/input-attachment-preparation.ts +86 -0
  39. package/src/agents/input-attachment.errors.ts +16 -0
  40. package/src/agents/instructions.ts +8 -0
  41. package/src/agents/materialized-input-attachment.ts +26 -0
  42. package/src/agents/message-id.ts +23 -0
  43. package/src/agents/normalize.ts +8 -0
  44. package/src/agents/sandbox-environment.ts +1 -0
  45. package/src/agents/tools/ping.tool.ts +13 -0
  46. package/src/agents/user-input-request.ts +470 -0
  47. package/src/protocol/agent-event.ts +2 -1
  48. package/src/protocol/agent-usage.ts +14 -0
  49. package/src/provenance.gen.ts +3 -3
  50. package/src/supervisor/agent-provider-factory.ts +189 -0
  51. package/src/supervisor/bootstrap-binder.ts +133 -0
  52. package/src/supervisor/config.ts +49 -0
  53. package/src/supervisor/control-authority-verifier.ts +135 -0
  54. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  55. package/src/supervisor/errors.ts +24 -0
  56. package/src/supervisor/index.ts +34 -0
  57. package/src/supervisor/persistence/json.ts +21 -0
  58. package/src/supervisor/persistence/state-discovery.ts +56 -0
  59. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  60. package/src/supervisor/ports/index.ts +109 -0
  61. package/src/supervisor/provider-factory.ts +37 -0
  62. package/src/supervisor/resident.ts +143 -0
  63. package/src/supervisor/rpc-client.ts +120 -0
  64. package/src/supervisor/runtime-handler.ts +309 -0
  65. package/src/supervisor/websocket-server.ts +435 -0
  66. package/src/supervisor-protocol/bootstrap.ts +1 -1
  67. package/src/template-onboarding.ts +47 -0
  68. package/src/testing/es256-test-keys.ts +73 -0
  69. package/src/testing/in-memory-runtime-control-plane.ts +206 -0
  70. package/src/testing/index.ts +6 -0
  71. package/src/testing/loopback-supervisor-connection.ts +71 -0
  72. package/src/testing/scripted-provider.ts +64 -0
  73. package/src/worker-runtime-client/command-pump.ts +127 -0
  74. package/src/worker-runtime-client/connection-attempt.ts +349 -0
  75. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  76. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  77. package/src/worker-runtime-client/frame-processor.ts +178 -0
  78. package/src/worker-runtime-client/index.ts +27 -0
  79. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  80. package/src/worker-runtime-client/ports.ts +139 -0
  81. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  82. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  83. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  84. package/src/worker-runtime-client/sandbox-connection-runtime.ts +301 -0
  85. package/src/worker-runtime-client/token-crypto.ts +46 -0
@@ -0,0 +1,435 @@
1
+ import type { Server, ServerWebSocket } from 'bun'
2
+ import type { Buffer } from 'node:buffer'
3
+ import type { Logger } from '../logger'
4
+ import type { BootstrapBinder } from './bootstrap-binder'
5
+ import type { ResidentSupervisorConfig } from './config'
6
+ import type { VerifySupervisorControlAuthority } from './control-authority-verifier'
7
+ import type { RuntimeConnectionHandler } from './runtime-handler'
8
+ import {
9
+ supervisorBootstrapRejectionReceiptCloseCode,
10
+ supervisorBootstrapRejectionReceiptCloseReason,
11
+ } from '../supervisor-protocol/bootstrap-rejection-receipt'
12
+ import { sandboxControlAuthorityHeaderName } from '../supervisor-protocol/control-authority'
13
+ import { decodeControlPlaneFrame, encodeWireFrame } from '../supervisor-protocol/wire-codec'
14
+
15
+ const sessionHeader = 'x-coding-agent-session-id'
16
+ const sessionSandboxHeader = 'x-coding-agent-session-sandbox-id'
17
+ const runtimeConnectionAttemptHeader = 'x-coding-agent-runtime-connection-attempt-id'
18
+ const controlAuthorityHeader = sandboxControlAuthorityHeaderName.toLowerCase()
19
+ const generationHeader = 'x-coding-agent-connection-generation'
20
+ const eventAckHeader = 'x-coding-agent-event-ack'
21
+ const e2bSandboxHeader = 'e2b-sandbox-id'
22
+ const nativePingIntervalMs = 15_000
23
+ const idleTimeoutSeconds = 60
24
+ const maximumControlFrameBytes = 128 * 1024
25
+ const protocolErrorCloseCode = 1002
26
+ const policyViolationCloseCode = 1008
27
+ const internalErrorCloseCode = 1011
28
+ const terminalRejectionReceiptBudgetMs = 5_000
29
+ const listenerStopBudgetMs = 500
30
+
31
+ interface SocketData {
32
+ sessionId: string
33
+ runtimeConnectionAttemptId: string
34
+ generation: number
35
+ eventAckFloor: number
36
+ handler?: RuntimeConnectionHandler
37
+ runtimeAttached: boolean
38
+ messageChain: Promise<void>
39
+ terminalRejection?: {
40
+ terminate: () => void
41
+ timeout: ReturnType<typeof setTimeout>
42
+ }
43
+ }
44
+
45
+ export interface SupervisorBootstrapStartupStageFact {
46
+ event: 'sandbox.startup-stage'
47
+ stage: 'bootstrap-persisted' | 'bootstrap-readiness-sent'
48
+ outcome: 'success' | 'failure'
49
+ duration: number
50
+ sessionId: string
51
+ }
52
+
53
+ interface Dependencies {
54
+ config: ResidentSupervisorConfig
55
+ logger: Logger
56
+ health: () => unknown
57
+ getBoundSessionId: () => string | undefined
58
+ getLocalEventMax: () => number
59
+ verifyControlAuthority: VerifySupervisorControlAuthority
60
+ acceptBoundConnectionGeneration: (input: { generation: number, runtimeConnectionAttemptId: string }) => void
61
+ bindBootstrap: BootstrapBinder
62
+ onStartupStage: (fact: SupervisorBootstrapStartupStageFact) => void
63
+ }
64
+
65
+ export class ResidentSupervisorWebSocketServer {
66
+ private readonly sockets = new Set<ServerWebSocket<SocketData>>()
67
+ private server: Server<SocketData> | undefined
68
+ private pingTimer: ReturnType<typeof setInterval> | undefined
69
+ private localSandboxId: string | undefined
70
+ private localSandboxIdRead: Promise<string> | undefined
71
+ private authoritativeSocket: ServerWebSocket<SocketData> | undefined
72
+
73
+ constructor(private readonly dependencies: Dependencies) {}
74
+
75
+ start(): void {
76
+ if (this.server)
77
+ return
78
+ const { config } = this.dependencies
79
+ const websocket: Bun.WebSocketHandler<SocketData> = {
80
+ perMessageDeflate: false,
81
+ maxPayloadLength: maximumControlFrameBytes,
82
+ idleTimeout: idleTimeoutSeconds,
83
+ open: socket => this.onOpen(socket),
84
+ message: (socket, message) => this.onMessage(socket, message),
85
+ close: (socket, code, reason) => this.onClose(socket, code, reason),
86
+ drain: socket => this.onDrain(socket),
87
+ }
88
+ if (config.unixSocketPath) {
89
+ this.server = Bun.serve<SocketData>({
90
+ unix: config.unixSocketPath,
91
+ fetch: async (request, server) => await this.handleRequest(request, server),
92
+ websocket,
93
+ })
94
+ }
95
+ else {
96
+ this.server = Bun.serve<SocketData>({
97
+ hostname: config.host,
98
+ port: config.port,
99
+ idleTimeout: idleTimeoutSeconds,
100
+ fetch: async (request, server) => await this.handleRequest(request, server),
101
+ websocket,
102
+ })
103
+ }
104
+ this.pingTimer = setInterval(() => {
105
+ for (const socket of this.sockets)
106
+ socket.ping()
107
+ }, nativePingIntervalMs)
108
+ this.pingTimer.unref?.()
109
+ }
110
+
111
+ port(): number | undefined {
112
+ return this.server?.port
113
+ }
114
+
115
+ async stop(): Promise<void> {
116
+ if (this.pingTimer)
117
+ clearInterval(this.pingTimer)
118
+ this.pingTimer = undefined
119
+ for (const socket of this.sockets) {
120
+ this.cancelPendingTerminalRejection(socket)
121
+ socket.close(1001, 'Supervisor listener stopping.')
122
+ socket.terminate()
123
+ }
124
+ this.sockets.clear()
125
+ this.authoritativeSocket = undefined
126
+ const server = this.server
127
+ this.server = undefined
128
+ if (server) {
129
+ const stopping = server.stop(true)
130
+ const completed = await Promise.race([
131
+ stopping.then(() => true),
132
+ Bun.sleep(listenerStopBudgetMs).then(() => false),
133
+ ])
134
+ if (!completed) {
135
+ this.dependencies.logger.warn({}, 'supervisor listener stop exceeded its drain budget')
136
+ void stopping.catch(error => this.dependencies.logger.warn({ err: error }, 'supervisor listener stopped with an error'))
137
+ }
138
+ }
139
+ }
140
+
141
+ private async handleRequest(request: Request, server: Server<SocketData>): Promise<Response | undefined> {
142
+ const url = new URL(request.url)
143
+ if (request.method === 'GET' && url.pathname === this.dependencies.config.healthPath) {
144
+ return Response.json(this.dependencies.health())
145
+ }
146
+ if (request.method !== 'GET' || url.pathname !== this.dependencies.config.controlPath)
147
+ return new Response('Not found', { status: 404 })
148
+
149
+ const sessionId = request.headers.get(sessionHeader)
150
+ const sessionSandboxId = request.headers.get(sessionSandboxHeader)
151
+ const runtimeConnectionAttemptId = request.headers.get(runtimeConnectionAttemptHeader)
152
+ const authorityAssertion = request.headers.get(controlAuthorityHeader)
153
+ const sandboxId = request.headers.get(e2bSandboxHeader)
154
+ const generation = parseBoundedInteger(request.headers.get(generationHeader), 1)
155
+ const eventAckFloor = parseBoundedInteger(request.headers.get(eventAckHeader), 0)
156
+ const boundSessionId = this.dependencies.getBoundSessionId()
157
+ if (!sandboxId || !sessionId || !sessionSandboxId || !runtimeConnectionAttemptId
158
+ || generation === undefined || eventAckFloor === undefined) {
159
+ return new Response('Invalid supervisor routing headers.', { status: 400 })
160
+ }
161
+ let localSandboxId: string
162
+ try {
163
+ localSandboxId = await this.getLocalSandboxId()
164
+ }
165
+ catch (error) {
166
+ this.dependencies.logger.error({ err: error }, 'failed to read local E2B sandbox identity')
167
+ return new Response('Local sandbox identity is unavailable.', { status: 503 })
168
+ }
169
+ if (sandboxId !== localSandboxId)
170
+ return new Response('E2B sandbox identity mismatch.', { status: 409 })
171
+ try {
172
+ await this.dependencies.verifyControlAuthority({
173
+ assertion: authorityAssertion,
174
+ providerSandboxId: sandboxId,
175
+ sessionId,
176
+ sessionSandboxId,
177
+ connectionGeneration: generation,
178
+ runtimeConnectionAttemptId,
179
+ eventAckFloor,
180
+ })
181
+ }
182
+ catch {
183
+ return new Response('Supervisor control authority is invalid.', { status: 401 })
184
+ }
185
+ if (boundSessionId && sessionId !== boundSessionId)
186
+ return new Response('Supervisor session binding mismatch.', { status: 409 })
187
+ if (eventAckFloor > this.dependencies.getLocalEventMax())
188
+ return new Response('Supervisor event ACK floor is ahead of local state.', { status: 409 })
189
+ if (boundSessionId) {
190
+ try {
191
+ this.dependencies.acceptBoundConnectionGeneration({ generation, runtimeConnectionAttemptId })
192
+ }
193
+ catch (error) {
194
+ this.dependencies.logger.warn({ err: error }, 'rejecting supervisor connection generation')
195
+ return new Response('Supervisor connection generation is stale.', { status: 409 })
196
+ }
197
+ }
198
+
199
+ const upgraded = server.upgrade(request, {
200
+ data: {
201
+ sessionId,
202
+ runtimeConnectionAttemptId,
203
+ generation,
204
+ eventAckFloor,
205
+ runtimeAttached: false,
206
+ messageChain: Promise.resolve(),
207
+ },
208
+ })
209
+ return upgraded ? undefined : new Response('WebSocket upgrade failed.', { status: 400 })
210
+ }
211
+
212
+ private async getLocalSandboxId(): Promise<string> {
213
+ if (this.localSandboxId)
214
+ return this.localSandboxId
215
+
216
+ const read = this.localSandboxIdRead ?? readLocalSandboxId(this.dependencies.config.sandboxIdPath)
217
+ this.localSandboxIdRead = read
218
+ try {
219
+ const sandboxId = await read
220
+ this.localSandboxId = sandboxId
221
+ return sandboxId
222
+ }
223
+ finally {
224
+ if (this.localSandboxIdRead === read)
225
+ this.localSandboxIdRead = undefined
226
+ }
227
+ }
228
+
229
+ private onOpen(socket: ServerWebSocket<SocketData>): void {
230
+ this.sockets.add(socket)
231
+ const previous = this.authoritativeSocket
232
+ this.authoritativeSocket = socket
233
+ if (previous && previous !== socket) {
234
+ this.cancelPendingTerminalRejection(previous)
235
+ this.sockets.delete(previous)
236
+ if (previous.data.runtimeAttached) {
237
+ previous.data.runtimeAttached = false
238
+ previous.data.handler?.detach(previous)
239
+ }
240
+ previous.close(policyViolationCloseCode, 'Connection attempt was superseded.')
241
+ previous.terminate()
242
+ }
243
+ }
244
+
245
+ private onMessage(socket: ServerWebSocket<SocketData>, message: string | Buffer): void {
246
+ socket.data.messageChain = socket.data.messageChain.then(async () => {
247
+ const bootstrapHandlingStartedAt = performance.now()
248
+ if (!this.isAuthoritativeLive(socket) || socket.data.terminalRejection)
249
+ return
250
+ const decoded = decodeControlPlaneFrame(typeof message === 'string' ? message : message.toString('utf8'))
251
+ const handler = socket.data.handler
252
+ if (handler && (decoded.kind !== 'ok' || decoded.frame.kind !== 'session.bootstrap')) {
253
+ if (socket.data.runtimeAttached)
254
+ handler.receive(socket, message)
255
+ return
256
+ }
257
+ if (decoded.kind !== 'ok' || decoded.frame.kind !== 'session.bootstrap') {
258
+ socket.close(protocolErrorCloseCode, 'Expected session bootstrap as first frame.')
259
+ return
260
+ }
261
+ if (decoded.frame.body.sessionId !== socket.data.sessionId
262
+ || decoded.frame.connectionGeneration !== socket.data.generation) {
263
+ socket.close(policyViolationCloseCode, 'Bootstrap does not match upgrade headers.')
264
+ return
265
+ }
266
+
267
+ let binding: Awaited<ReturnType<BootstrapBinder>>
268
+ try {
269
+ binding = await this.dependencies.bindBootstrap({
270
+ envelope: decoded.frame,
271
+ runtimeConnectionAttemptId: socket.data.runtimeConnectionAttemptId,
272
+ })
273
+ this.observeBootstrapStage({
274
+ stage: 'bootstrap-persisted',
275
+ outcome: binding.outcome === 'accepted' ? 'success' : 'failure',
276
+ startedAt: bootstrapHandlingStartedAt,
277
+ sessionId: socket.data.sessionId,
278
+ })
279
+ }
280
+ catch (error) {
281
+ this.observeBootstrapStage({
282
+ stage: 'bootstrap-persisted',
283
+ outcome: 'failure',
284
+ startedAt: bootstrapHandlingStartedAt,
285
+ sessionId: socket.data.sessionId,
286
+ })
287
+ throw error
288
+ }
289
+ if (!this.isAuthoritativeLive(socket)) {
290
+ this.observeBootstrapStage({
291
+ stage: 'bootstrap-readiness-sent',
292
+ outcome: 'failure',
293
+ startedAt: bootstrapHandlingStartedAt,
294
+ sessionId: socket.data.sessionId,
295
+ })
296
+ return
297
+ }
298
+ const readiness = binding.outcome === 'accepted' ? binding.runtimeState : binding.ack
299
+ let sendResult: number
300
+ try {
301
+ sendResult = socket.send(encodeWireFrame(readiness))
302
+ this.observeBootstrapStage({
303
+ stage: 'bootstrap-readiness-sent',
304
+ outcome: binding.outcome === 'accepted' && sendResult >= 0 ? 'success' : 'failure',
305
+ startedAt: bootstrapHandlingStartedAt,
306
+ sessionId: socket.data.sessionId,
307
+ })
308
+ }
309
+ catch (error) {
310
+ this.observeBootstrapStage({
311
+ stage: 'bootstrap-readiness-sent',
312
+ outcome: 'failure',
313
+ startedAt: bootstrapHandlingStartedAt,
314
+ sessionId: socket.data.sessionId,
315
+ })
316
+ throw error
317
+ }
318
+ if (sendResult < 0) {
319
+ socket.close(internalErrorCloseCode, binding.outcome === 'accepted'
320
+ ? 'Supervisor bootstrap readiness delivery failed.'
321
+ : 'Supervisor bootstrap ACK delivery failed.')
322
+ return
323
+ }
324
+ if (binding.outcome === 'rejected') {
325
+ this.awaitTerminalRejectionReceipt({ socket, terminate: binding.terminate })
326
+ return
327
+ }
328
+ const activated = await binding.activate()
329
+ this.attachIfLive({ socket, handler: activated })
330
+ }).catch((error: unknown) => {
331
+ this.dependencies.logger.error({ err: error }, 'resident supervisor connection failed')
332
+ socket.close(internalErrorCloseCode, 'Supervisor bootstrap failed.')
333
+ })
334
+ }
335
+
336
+ private observeBootstrapStage(input: {
337
+ stage: SupervisorBootstrapStartupStageFact['stage']
338
+ outcome: SupervisorBootstrapStartupStageFact['outcome']
339
+ startedAt: number
340
+ sessionId: string
341
+ }): void {
342
+ try {
343
+ this.dependencies.onStartupStage({
344
+ event: 'sandbox.startup-stage',
345
+ stage: input.stage,
346
+ outcome: input.outcome,
347
+ duration: Math.max(0, Math.round(performance.now() - input.startedAt)),
348
+ sessionId: input.sessionId,
349
+ })
350
+ }
351
+ catch {
352
+ // Startup telemetry is best effort and cannot alter bootstrap behavior.
353
+ }
354
+ }
355
+
356
+ private onClose(socket: ServerWebSocket<SocketData>, code: number, reason: string): void {
357
+ const terminalRejection = socket.data.terminalRejection
358
+ const receiptConfirmed = terminalRejection !== undefined
359
+ && this.authoritativeSocket === socket
360
+ && code === supervisorBootstrapRejectionReceiptCloseCode
361
+ && reason === supervisorBootstrapRejectionReceiptCloseReason
362
+ if (terminalRejection) {
363
+ clearTimeout(terminalRejection.timeout)
364
+ socket.data.terminalRejection = undefined
365
+ }
366
+ this.sockets.delete(socket)
367
+ if (this.authoritativeSocket === socket)
368
+ this.authoritativeSocket = undefined
369
+ if (socket.data.runtimeAttached)
370
+ socket.data.handler?.detach(socket)
371
+ if (receiptConfirmed)
372
+ terminalRejection?.terminate()
373
+ }
374
+
375
+ private awaitTerminalRejectionReceipt(input: {
376
+ socket: ServerWebSocket<SocketData>
377
+ terminate: () => void
378
+ }): void {
379
+ const timeout = setTimeout(() => {
380
+ if (input.socket.data.terminalRejection?.terminate !== input.terminate)
381
+ return
382
+ input.socket.data.terminalRejection = undefined
383
+ input.socket.close(internalErrorCloseCode, 'Supervisor rejection receipt was not observed.')
384
+ input.terminate()
385
+ }, terminalRejectionReceiptBudgetMs)
386
+ timeout.unref?.()
387
+ input.socket.data.terminalRejection = { terminate: input.terminate, timeout }
388
+ }
389
+
390
+ private cancelPendingTerminalRejection(socket: ServerWebSocket<SocketData>): void {
391
+ const pending = socket.data.terminalRejection
392
+ if (!pending)
393
+ return
394
+ clearTimeout(pending.timeout)
395
+ socket.data.terminalRejection = undefined
396
+ }
397
+
398
+ private onDrain(socket: ServerWebSocket<SocketData>): void {
399
+ if (socket.data.runtimeAttached && this.isAuthoritativeLive(socket))
400
+ socket.data.handler?.drain(socket)
401
+ }
402
+
403
+ private attachIfLive(input: {
404
+ socket: ServerWebSocket<SocketData>
405
+ handler: RuntimeConnectionHandler
406
+ }): void {
407
+ if (!this.isAuthoritativeLive(input.socket))
408
+ return
409
+ input.socket.data.handler = input.handler
410
+ input.handler.attach({
411
+ socket: input.socket,
412
+ generation: input.socket.data.generation,
413
+ eventAckFloor: input.socket.data.eventAckFloor,
414
+ })
415
+ input.socket.data.runtimeAttached = true
416
+ }
417
+
418
+ private isAuthoritativeLive(socket: ServerWebSocket<SocketData>): boolean {
419
+ return this.sockets.has(socket) && this.authoritativeSocket === socket
420
+ }
421
+ }
422
+
423
+ function parseBoundedInteger(value: string | null, minimum: number): number | undefined {
424
+ if (value === null || !/^\d+$/u.test(value))
425
+ return undefined
426
+ const parsed = Number(value)
427
+ return Number.isSafeInteger(parsed) && parsed >= minimum ? parsed : undefined
428
+ }
429
+
430
+ async function readLocalSandboxId(path: string): Promise<string> {
431
+ const sandboxId = (await Bun.file(path).text()).trim()
432
+ if (!sandboxId)
433
+ throw new Error('E2B sandbox identity file is empty.')
434
+ return sandboxId
435
+ }
@@ -67,7 +67,7 @@ export const sessionBootstrapBodySchema = z.object({
67
67
  }).strict().superRefine((body, context) => {
68
68
  const workspacePrefix = `${body.agent.cwd}/`
69
69
  for (const [index, repository] of body.repositories.entries()) {
70
- if (!repository.root.startsWith(workspacePrefix)) {
70
+ if (repository.root !== body.agent.cwd && !repository.root.startsWith(workspacePrefix)) {
71
71
  context.addIssue({
72
72
  code: 'custom',
73
73
  path: ['repositories', index, 'root'],
@@ -0,0 +1,47 @@
1
+ import z from 'zod'
2
+ import { sessionProfileNameSchema } from './supervisor-protocol/profile'
3
+
4
+ const sha256Schema = z.string().regex(/^[0-9a-f]{64}$/u)
5
+ const componentNameSchema = z.string().max(64).regex(/^[a-z][a-z0-9.-]*$/u)
6
+ const componentVersionSchema = z.string().min(1).max(256)
7
+
8
+ /**
9
+ * Immutable provenance supplied when a service onboards one of its E2B
10
+ * templates. The owning service persists this record; agent-core deliberately
11
+ * has no global template registry or preferred template ID.
12
+ */
13
+ export const sandboxTemplateOnboardingSchema = z.object({
14
+ owner: z.object({
15
+ service: sessionProfileNameSchema,
16
+ profileVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
17
+ }).strict(),
18
+ provider: z.literal('e2b'),
19
+ templateId: z.string().min(1).max(512),
20
+ agentCore: z.object({
21
+ version: z.string().min(1).max(256),
22
+ sha: z.string().regex(/^[0-9a-f]{40}$/u),
23
+ }).strict(),
24
+ supervisorEntrySha256: sha256Schema,
25
+ runtimeHash: sha256Schema,
26
+ components: z.record(componentNameSchema, componentVersionSchema).refine(
27
+ components => Object.keys(components).length <= 64,
28
+ { message: 'A template may declare at most 64 versioned components.' },
29
+ ),
30
+ onboardedAt: z.iso.datetime(),
31
+ }).strict()
32
+
33
+ export type SandboxTemplateOnboarding = z.infer<typeof sandboxTemplateOnboardingSchema>
34
+
35
+ export interface SandboxTemplateOnboardingStore {
36
+ upsert: (registration: SandboxTemplateOnboarding) => Promise<void>
37
+ }
38
+
39
+ /** Validates locally, then delegates persistence to the service-owned store. */
40
+ export async function onboardSandboxTemplate(input: {
41
+ registration: unknown
42
+ store: SandboxTemplateOnboardingStore
43
+ }): Promise<SandboxTemplateOnboarding> {
44
+ const registration = sandboxTemplateOnboardingSchema.parse(input.registration)
45
+ await input.store.upsert(registration)
46
+ return registration
47
+ }
@@ -0,0 +1,73 @@
1
+ import type { SandboxControlAuthorityPublicJwks } from '../supervisor-protocol/control-authority'
2
+ import { sandboxControlAuthorityPublicJwksSchema } from '../supervisor-protocol/control-authority'
3
+ import { createControlAuthoritySigner } from '../worker-runtime-client/control-authority-signer'
4
+
5
+ export interface TestControlAuthority {
6
+ issuer: string
7
+ keyId: string
8
+ signingJwk: Record<string, unknown>
9
+ publicJwks: SandboxControlAuthorityPublicJwks
10
+ sign: Awaited<ReturnType<typeof createControlAuthoritySigner>>['sign']
11
+ }
12
+
13
+ export async function createTestControlAuthority(input: {
14
+ issuer?: string
15
+ keyId?: string
16
+ } = {}): Promise<TestControlAuthority> {
17
+ const issuer = input.issuer ?? 'agent-core-loopback'
18
+ const keyId = input.keyId ?? 'loopback-key'
19
+ const pair = await crypto.subtle.generateKey(
20
+ { name: 'ECDSA', namedCurve: 'P-256' },
21
+ true,
22
+ ['sign', 'verify'],
23
+ )
24
+ const [privateMaterial, publicMaterial] = await Promise.all([
25
+ crypto.subtle.exportKey('jwk', pair.privateKey),
26
+ crypto.subtle.exportKey('jwk', pair.publicKey),
27
+ ])
28
+ const { x, y, d } = requiredEcPrivateMaterial(privateMaterial)
29
+ const signingJwk: Record<string, unknown> = {
30
+ kty: 'EC',
31
+ crv: 'P-256',
32
+ x,
33
+ y,
34
+ d,
35
+ kid: keyId,
36
+ alg: 'ES256',
37
+ use: 'sig',
38
+ key_ops: ['sign'],
39
+ }
40
+ const publicJwks = sandboxControlAuthorityPublicJwksSchema.parse({
41
+ keys: [{
42
+ kty: 'EC',
43
+ crv: 'P-256',
44
+ x: requiredCoordinate(publicMaterial.x, 'x'),
45
+ y: requiredCoordinate(publicMaterial.y, 'y'),
46
+ kid: keyId,
47
+ alg: 'ES256' as const,
48
+ use: 'sig' as const,
49
+ key_ops: ['verify'],
50
+ }],
51
+ })
52
+ const signer = await createControlAuthoritySigner({
53
+ issuer,
54
+ activeKeyId: keyId,
55
+ signingJwk,
56
+ publicJwks,
57
+ })
58
+ return { issuer, keyId, signingJwk, publicJwks: signer.publicJwks, sign: signer.sign }
59
+ }
60
+
61
+ function requiredEcPrivateMaterial(jwk: { x?: string, y?: string, d?: string }): { x: string, y: string, d: string } {
62
+ return {
63
+ x: requiredCoordinate(jwk.x, 'x'),
64
+ y: requiredCoordinate(jwk.y, 'y'),
65
+ d: requiredCoordinate(jwk.d, 'd'),
66
+ }
67
+ }
68
+
69
+ function requiredCoordinate(value: string | undefined, name: string): string {
70
+ if (!value)
71
+ throw new Error(`Generated EC test key is missing ${name}.`)
72
+ return value
73
+ }