@meistrari/agent-core 0.0.0 → 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 (83) hide show
  1. package/README.md +24 -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 +736 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +464 -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 +708 -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/provenance.gen.ts +3 -3
  48. package/src/supervisor/agent-provider-factory.ts +189 -0
  49. package/src/supervisor/bootstrap-binder.ts +125 -0
  50. package/src/supervisor/config.ts +49 -0
  51. package/src/supervisor/control-authority-verifier.ts +135 -0
  52. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  53. package/src/supervisor/errors.ts +24 -0
  54. package/src/supervisor/index.ts +34 -0
  55. package/src/supervisor/persistence/json.ts +21 -0
  56. package/src/supervisor/persistence/state-discovery.ts +56 -0
  57. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  58. package/src/supervisor/ports/index.ts +109 -0
  59. package/src/supervisor/provider-factory.ts +37 -0
  60. package/src/supervisor/resident.ts +143 -0
  61. package/src/supervisor/rpc-client.ts +120 -0
  62. package/src/supervisor/runtime-handler.ts +309 -0
  63. package/src/supervisor/websocket-server.ts +434 -0
  64. package/src/supervisor-protocol/bootstrap.ts +1 -1
  65. package/src/template-onboarding.ts +47 -0
  66. package/src/testing/es256-test-keys.ts +73 -0
  67. package/src/testing/in-memory-runtime-control-plane.ts +205 -0
  68. package/src/testing/index.ts +6 -0
  69. package/src/testing/loopback-supervisor-connection.ts +71 -0
  70. package/src/testing/scripted-provider.ts +64 -0
  71. package/src/worker-runtime-client/command-pump.ts +132 -0
  72. package/src/worker-runtime-client/connection-attempt.ts +340 -0
  73. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  74. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  75. package/src/worker-runtime-client/frame-processor.ts +178 -0
  76. package/src/worker-runtime-client/index.ts +27 -0
  77. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  78. package/src/worker-runtime-client/ports.ts +137 -0
  79. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  80. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  81. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  82. package/src/worker-runtime-client/sandbox-connection-runtime.ts +300 -0
  83. package/src/worker-runtime-client/token-crypto.ts +46 -0
@@ -0,0 +1,120 @@
1
+ import type { Logger } from '../logger'
2
+ import type { SupervisorRpcMethod, SupervisorRpcResponseEnvelope } from '../supervisor-protocol/rpc'
3
+ import { ulid } from 'ulid'
4
+
5
+ interface PendingRequest {
6
+ resolve: (value: unknown) => void
7
+ reject: (error: unknown) => void
8
+ timeout: ReturnType<typeof setTimeout>
9
+ signal: AbortSignal | undefined
10
+ abort: (() => void) | undefined
11
+ }
12
+
13
+ export class SupervisorRpcClient {
14
+ private readonly pending = new Map<string, PendingRequest>()
15
+ private sendFrame: ((frame: string) => void) | undefined
16
+
17
+ constructor(private readonly logger: Logger) {}
18
+
19
+ attach(sendFrame: (frame: string) => void): void {
20
+ this.sendFrame = sendFrame
21
+ }
22
+
23
+ detach(): void {
24
+ this.sendFrame = undefined
25
+ this.rejectAll(new Error('Supervisor control connection disconnected.'))
26
+ }
27
+
28
+ async request<T>(input: {
29
+ method: SupervisorRpcMethod
30
+ body: unknown
31
+ deadlineMs: number
32
+ signal?: AbortSignal
33
+ validate: (value: unknown) => T
34
+ }): Promise<T> {
35
+ const sendFrame = this.sendFrame
36
+ if (!sendFrame)
37
+ throw new Error('Supervisor control connection is unavailable.')
38
+ input.signal?.throwIfAborted()
39
+ const requestId = ulid()
40
+ const result = Promise.withResolvers<unknown>()
41
+ const abort = input.signal
42
+ ? () => {
43
+ sendFrame(JSON.stringify({ kind: 'rpc.cancel', requestId }))
44
+ result.reject(input.signal?.reason)
45
+ }
46
+ : undefined
47
+ const timeout = setTimeout(() => {
48
+ sendFrame(JSON.stringify({ kind: 'rpc.cancel', requestId }))
49
+ result.reject(new Error(`Supervisor RPC ${input.method} timed out.`))
50
+ }, input.deadlineMs)
51
+ timeout.unref?.()
52
+ this.pending.set(requestId, {
53
+ resolve: result.resolve,
54
+ reject: result.reject,
55
+ timeout,
56
+ signal: input.signal,
57
+ abort,
58
+ })
59
+ input.signal?.addEventListener('abort', abort as () => void, { once: true })
60
+ try {
61
+ sendFrame(JSON.stringify({
62
+ kind: 'rpc.request',
63
+ requestId,
64
+ method: input.method,
65
+ deadlineMs: input.deadlineMs,
66
+ body: input.body,
67
+ }))
68
+ return input.validate(await result.promise)
69
+ }
70
+ finally {
71
+ this.remove(requestId)
72
+ }
73
+ }
74
+
75
+ receive(frame: SupervisorRpcResponseEnvelope): void {
76
+ const pending = this.pending.get(frame.requestId)
77
+ if (!pending) {
78
+ this.logger.warn({ requestId: frame.requestId }, 'ignoring unknown supervisor RPC response')
79
+ return
80
+ }
81
+ if (frame.result.status === 'ok')
82
+ pending.resolve(frame.result.value)
83
+ else
84
+ pending.reject(new SupervisorRpcError(frame.result.error))
85
+ }
86
+
87
+ stop(): void {
88
+ this.sendFrame = undefined
89
+ this.rejectAll(new Error('Supervisor RPC client stopped.'))
90
+ }
91
+
92
+ private remove(requestId: string): void {
93
+ const pending = this.pending.get(requestId)
94
+ if (!pending)
95
+ return
96
+ clearTimeout(pending.timeout)
97
+ if (pending.signal && pending.abort)
98
+ pending.signal.removeEventListener('abort', pending.abort)
99
+ this.pending.delete(requestId)
100
+ }
101
+
102
+ private rejectAll(error: Error): void {
103
+ for (const [requestId, pending] of this.pending) {
104
+ pending.reject(error)
105
+ this.remove(requestId)
106
+ }
107
+ }
108
+ }
109
+
110
+ export class SupervisorRpcError extends Error {
111
+ override readonly name = 'SupervisorRpcError'
112
+ readonly code: string
113
+ readonly retryable: boolean
114
+
115
+ constructor(input: { code: string, message: string, retryable: boolean }) {
116
+ super(input.message)
117
+ this.code = input.code
118
+ this.retryable = input.retryable
119
+ }
120
+ }
@@ -0,0 +1,309 @@
1
+ import type { Logger } from '../logger'
2
+ import type { EphemeralCredentials, SessionBootstrapGitToken } from '../supervisor-protocol/bootstrap'
3
+ import type { WireCommandBody } from '../supervisor-protocol/command-body'
4
+ import type { CommandEnvelope } from '../supervisor-protocol/envelopes/control-plane-to-supervisor'
5
+ import type { EventEnvelope } from '../supervisor-protocol/envelopes/supervisor-to-control-plane'
6
+ import type { WireEventBody } from '../supervisor-protocol/event-body'
7
+ import type { SupervisorStore } from './persistence/supervisor-store'
8
+ import type { SupervisorAgentRuntime, SupervisorProviderFactory } from './provider-factory'
9
+ import { jsonUtf8ByteLength } from '../protocol'
10
+ import { durabilityOf } from '../supervisor-protocol/durability'
11
+ import { wireEventBodySchema } from '../supervisor-protocol/event-body'
12
+ import { maxDurableEventBytes, maxEphemeralDeltaBytes } from '../supervisor-protocol/payload-overflow'
13
+ import { decodeControlPlaneFrame, encodeWireFrame } from '../supervisor-protocol/wire-codec'
14
+ import { SupervisorRpcClient } from './rpc-client'
15
+
16
+ export interface ResidentSupervisorSocket {
17
+ send: (data: string) => number
18
+ close: (code?: number, reason?: string) => void
19
+ ping: (data?: string | Uint8Array) => number
20
+ }
21
+
22
+ export interface RuntimeConnectionHandler {
23
+ attach: (input: { socket: ResidentSupervisorSocket, generation: number, eventAckFloor: number }) => void
24
+ receive: (socket: ResidentSupervisorSocket, data: string | ArrayBuffer | Uint8Array) => void
25
+ detach: (socket: ResidentSupervisorSocket) => void
26
+ drain: (socket: ResidentSupervisorSocket) => void
27
+ }
28
+
29
+ export class SupervisorRuntimeHandler implements RuntimeConnectionHandler {
30
+ private readonly rpc: SupervisorRpcClient
31
+ private readonly lifecycleController = new AbortController()
32
+ private socket: ResidentSupervisorSocket | undefined
33
+ private agent: SupervisorAgentRuntime | undefined
34
+ private createAgentTask: Promise<SupervisorAgentRuntime> | undefined
35
+ private processing = Promise.resolve()
36
+ private eventAckFloor = 0
37
+ private stopped = false
38
+ private readonly retainedFrames: string[] = []
39
+ private readonly heartbeatTimer: ReturnType<typeof setInterval>
40
+
41
+ constructor(private readonly dependencies: {
42
+ store: SupervisorStore
43
+ providerFactory: SupervisorProviderFactory
44
+ credentials?: EphemeralCredentials
45
+ gitToken?: SessionBootstrapGitToken
46
+ logger: Logger
47
+ onFatal: (error: unknown) => void
48
+ }) {
49
+ this.rpc = new SupervisorRpcClient(dependencies.logger)
50
+ this.heartbeatTimer = setInterval(() => {
51
+ if (this.socket) {
52
+ this.emit({
53
+ type: 'supervisor.heartbeat',
54
+ payload: { unackedEventCount: Math.max(0, this.dependencies.store.localEventMax() - this.eventAckFloor) },
55
+ })
56
+ }
57
+ }, 15_000)
58
+ this.heartbeatTimer.unref?.()
59
+ }
60
+
61
+ attach(input: { socket: ResidentSupervisorSocket, generation: number, eventAckFloor: number }): void {
62
+ if (this.stopped) {
63
+ input.socket.close(1008, 'Supervisor runtime is stopping.')
64
+ return
65
+ }
66
+ const previous = this.socket
67
+ this.socket = input.socket
68
+ this.eventAckFloor = input.eventAckFloor
69
+ this.retainedFrames.length = 0
70
+ if (previous && previous !== input.socket)
71
+ previous.close(1008, 'Connection generation was superseded.')
72
+ this.rpc.attach(frame => this.sendRetained(frame))
73
+ for (const event of this.dependencies.store.eventsAfter(input.eventAckFloor))
74
+ this.sendEvent(event)
75
+ this.schedulePendingCommands()
76
+ }
77
+
78
+ receive(socket: ResidentSupervisorSocket, data: string | ArrayBuffer | Uint8Array): void {
79
+ if (this.socket !== socket || this.stopped)
80
+ return
81
+ const decoded = decodeControlPlaneFrame(frameText(data))
82
+ if (decoded.kind !== 'ok') {
83
+ socket.close(1002, 'Malformed control frame.')
84
+ return
85
+ }
86
+ const frame = decoded.frame
87
+ if (frame.kind === 'command') {
88
+ try {
89
+ this.dependencies.store.receiveCommand(frame)
90
+ this.sendRetained(encodeWireFrame({
91
+ kind: 'command.ack',
92
+ commandId: frame.commandId,
93
+ commandSeq: frame.commandSeq,
94
+ status: 'received',
95
+ }))
96
+ this.schedulePendingCommands()
97
+ }
98
+ catch (error) {
99
+ this.dependencies.logger.warn({ err: error }, 'rejecting invalid supervisor command')
100
+ socket.close(1008, 'Supervisor command was rejected.')
101
+ }
102
+ return
103
+ }
104
+ if (frame.kind === 'event.ack') {
105
+ if (frame.highWaterMark > this.dependencies.store.localEventMax()) {
106
+ socket.close(1008, 'Supervisor event ACK is ahead of local state.')
107
+ return
108
+ }
109
+ this.eventAckFloor = Math.max(this.eventAckFloor, frame.highWaterMark)
110
+ return
111
+ }
112
+ if (frame.kind === 'rpc.response') {
113
+ this.rpc.receive(frame)
114
+ return
115
+ }
116
+ socket.close(1008, 'Bootstrap is valid only before runtime attachment.')
117
+ }
118
+
119
+ detach(socket: ResidentSupervisorSocket): void {
120
+ if (this.socket !== socket)
121
+ return
122
+ this.socket = undefined
123
+ this.retainedFrames.length = 0
124
+ this.rpc.detach()
125
+ }
126
+
127
+ drain(socket: ResidentSupervisorSocket): void {
128
+ if (this.socket !== socket)
129
+ return
130
+ this.flushRetained()
131
+ }
132
+
133
+ async refreshSecrets(input: {
134
+ credentials?: EphemeralCredentials
135
+ gitToken?: SessionBootstrapGitToken
136
+ }): Promise<void> {
137
+ this.dependencies.credentials = input.credentials
138
+ this.dependencies.gitToken = input.gitToken
139
+ await this.agent?.refreshSecrets?.(input)
140
+ }
141
+
142
+ async stop(reason: 'sigterm' | 'sigint' | 'fatal_error' = 'sigterm'): Promise<void> {
143
+ if (this.stopped)
144
+ return
145
+ this.stopped = true
146
+ clearInterval(this.heartbeatTimer)
147
+ this.lifecycleController.abort(new DOMException('Supervisor is stopping.', 'AbortError'))
148
+ this.emit({ type: 'supervisor.shutting_down', payload: { reason } })
149
+ const agent = await this.createAgentTask?.catch(() => undefined) ?? this.agent
150
+ if (agent) {
151
+ const closeController = new AbortController()
152
+ await agent.close({ signal: closeController.signal }).catch(error => this.dependencies.logger.warn({ err: error }, 'agent runtime close failed'))
153
+ }
154
+ this.rpc.stop()
155
+ this.socket?.close(1001, 'Supervisor runtime stopped.')
156
+ this.socket = undefined
157
+ await this.processing.catch(() => undefined)
158
+ }
159
+
160
+ private schedulePendingCommands(): void {
161
+ this.processing = this.processing.then(async () => {
162
+ const agent = await this.agentRuntime()
163
+ for (const command of this.dependencies.store.pendingCommands())
164
+ await this.processCommand({ agent, command })
165
+ }).catch((error) => {
166
+ this.dependencies.logger.error({ err: error }, 'supervisor command processing failed')
167
+ if (!this.stopped)
168
+ this.dependencies.onFatal(error)
169
+ })
170
+ }
171
+
172
+ private async agentRuntime(): Promise<SupervisorAgentRuntime> {
173
+ if (this.agent)
174
+ return this.agent
175
+ if (!this.createAgentTask) {
176
+ const binding = this.dependencies.store.binding()
177
+ if (!binding)
178
+ throw new Error('Supervisor runtime cannot start without a binding.')
179
+ const initialSecrets = {
180
+ credentials: this.dependencies.credentials,
181
+ gitToken: this.dependencies.gitToken,
182
+ }
183
+ this.createAgentTask = this.dependencies.providerFactory.create({
184
+ bootstrap: binding.body,
185
+ ...initialSecrets,
186
+ rpc: this.rpc,
187
+ signal: this.lifecycleController.signal,
188
+ }).then(async (agent) => {
189
+ if ((this.dependencies.credentials !== initialSecrets.credentials
190
+ || this.dependencies.gitToken !== initialSecrets.gitToken) && agent.refreshSecrets) {
191
+ await agent.refreshSecrets({
192
+ credentials: this.dependencies.credentials,
193
+ gitToken: this.dependencies.gitToken,
194
+ })
195
+ }
196
+ this.agent = agent
197
+ const snapshot = agent.snapshot()
198
+ this.dependencies.store.setAgentRunSnapshot(snapshot)
199
+ this.emit({ type: 'supervisor.agent_run.changed', payload: { agentRun: snapshot } })
200
+ return agent
201
+ })
202
+ }
203
+ return await this.createAgentTask
204
+ }
205
+
206
+ private async processCommand(input: {
207
+ agent: SupervisorAgentRuntime
208
+ command: CommandEnvelope & { body: WireCommandBody }
209
+ }): Promise<void> {
210
+ try {
211
+ await input.agent.handle({
212
+ commandId: input.command.commandId,
213
+ commandSeq: input.command.commandSeq,
214
+ body: input.command.body,
215
+ signal: this.lifecycleController.signal,
216
+ emit: body => this.emit(body),
217
+ })
218
+ this.dependencies.store.markCommand({ commandSeq: input.command.commandSeq, status: 'applied' })
219
+ this.emit({
220
+ type: 'supervisor.command.applied',
221
+ payload: {
222
+ commandId: input.command.commandId,
223
+ commandSeq: input.command.commandSeq,
224
+ bodyType: input.command.body.type,
225
+ },
226
+ })
227
+ }
228
+ catch {
229
+ if (this.lifecycleController.signal.aborted)
230
+ return
231
+ this.dependencies.store.markCommand({ commandSeq: input.command.commandSeq, status: 'failed' })
232
+ this.emit({
233
+ type: 'supervisor.command.failed',
234
+ payload: {
235
+ commandId: input.command.commandId,
236
+ commandSeq: input.command.commandSeq,
237
+ bodyType: input.command.body.type,
238
+ failure: {
239
+ type: 'command',
240
+ detail: 'Agent command execution failed.',
241
+ },
242
+ },
243
+ })
244
+ }
245
+ }
246
+
247
+ private emit(body: WireEventBody): void {
248
+ const occurredAt = new Date().toISOString()
249
+ const parsedBody = wireEventBodySchema.parse(body)
250
+ if (durabilityOf(parsedBody.type) === 'ephemeral') {
251
+ if (jsonUtf8ByteLength(parsedBody) > maxEphemeralDeltaBytes)
252
+ throw new Error(`Ephemeral event exceeds ${maxEphemeralDeltaBytes} bytes.`)
253
+ this.sendEvent({ kind: 'event', delivery_semantics: 'ephemeral', occurredAt, body: parsedBody })
254
+ return
255
+ }
256
+ if (jsonUtf8ByteLength(parsedBody) > maxDurableEventBytes)
257
+ throw new Error(`Durable event exceeds ${maxDurableEventBytes} bytes and must use overflow storage.`)
258
+ this.sendEvent(this.dependencies.store.appendDurableEvent({ occurredAt, body: parsedBody }))
259
+ }
260
+
261
+ private sendEvent(event: EventEnvelope): void {
262
+ const frame = encodeWireFrame(event)
263
+ if (event.delivery_semantics === 'ephemeral') {
264
+ if (this.dependencies.store.localEventMax() > this.eventAckFloor || this.retainedFrames.length > 0)
265
+ return
266
+ this.socket?.send(frame)
267
+ return
268
+ }
269
+ this.sendRetained(frame)
270
+ }
271
+
272
+ private sendRetained(frame: string): void {
273
+ const socket = this.socket
274
+ if (!socket)
275
+ return
276
+ if (this.retainedFrames.length > 0) {
277
+ this.retainFrame(frame)
278
+ this.flushRetained()
279
+ return
280
+ }
281
+ if (socket.send(frame) < 0)
282
+ this.retainFrame(frame)
283
+ }
284
+
285
+ private retainFrame(frame: string): void {
286
+ if (this.retainedFrames.length >= 5_000) {
287
+ this.socket?.close(1011, 'Supervisor control backpressure limit exceeded.')
288
+ return
289
+ }
290
+ this.retainedFrames.push(frame)
291
+ }
292
+
293
+ private flushRetained(): void {
294
+ const socket = this.socket
295
+ if (!socket)
296
+ return
297
+ while (this.retainedFrames.length > 0) {
298
+ if (socket.send(this.retainedFrames[0]!) < 0)
299
+ return
300
+ this.retainedFrames.shift()
301
+ }
302
+ }
303
+ }
304
+
305
+ function frameText(data: string | ArrayBuffer | Uint8Array): string {
306
+ if (typeof data === 'string')
307
+ return data
308
+ return new TextDecoder().decode(data)
309
+ }