@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,178 @@
1
+ import type { Logger } from '../logger'
2
+ import type { CommandAckEnvelope, RuntimeStateEnvelope } from '../supervisor-protocol/envelopes/supervisor-to-control-plane'
3
+ import type {
4
+ ConnectionAuthorityStore,
5
+ DurableEventSink,
6
+ EphemeralEventSink,
7
+ RpcDispatcher,
8
+ SessionFailureSink,
9
+ SessionSandboxRef,
10
+ SupervisorConnection,
11
+ } from './ports'
12
+ import {
13
+ supervisorBootstrapRejectionReceiptCloseCode,
14
+ supervisorBootstrapRejectionReceiptCloseReason,
15
+ } from '../supervisor-protocol/bootstrap-rejection-receipt'
16
+ import { isEphemeralWrappedAgentEvent } from '../supervisor-protocol/durability'
17
+ import { decodeSupervisorFrame, encodeWireFrame } from '../supervisor-protocol/wire-codec'
18
+ import { SupervisorRpcRequestManager } from './rpc-request-manager'
19
+
20
+ export class SupervisorFrameProcessor {
21
+ private readonly readiness = Promise.withResolvers<RuntimeStateEnvelope>()
22
+ private readonly rpc: SupervisorRpcRequestManager
23
+ private processing = Promise.resolve()
24
+ private stopped = false
25
+ private supervisorLivenessObserved = false
26
+ private readinessSettled = false
27
+
28
+ constructor(private readonly dependencies: {
29
+ ref: SessionSandboxRef
30
+ generation: number
31
+ runtimeConnectionId: string
32
+ runtimeConnectionAttemptId: string
33
+ connection: SupervisorConnection
34
+ authorityStore: ConnectionAuthorityStore
35
+ durableEvents: DurableEventSink
36
+ ephemeralEvents: EphemeralEventSink
37
+ rpcDispatcher: RpcDispatcher
38
+ failures: SessionFailureSink
39
+ logger: Logger
40
+ onCommandAck: (ack: CommandAckEnvelope) => void
41
+ onFatal: (error: unknown) => void
42
+ }) {
43
+ void this.readiness.promise.catch(() => undefined)
44
+ this.rpc = new SupervisorRpcRequestManager({
45
+ ref: dependencies.ref,
46
+ generation: dependencies.generation,
47
+ runtimeConnectionAttemptId: dependencies.runtimeConnectionAttemptId,
48
+ authorityStore: dependencies.authorityStore,
49
+ dispatcher: dependencies.rpcDispatcher,
50
+ connection: dependencies.connection,
51
+ })
52
+ }
53
+
54
+ receive(data: string | ArrayBuffer | Uint8Array): void {
55
+ if (this.stopped)
56
+ return
57
+ this.processing = this.processing.then(async () => await this.process(frameText(data))).catch((error) => {
58
+ this.dependencies.onFatal(error)
59
+ })
60
+ }
61
+
62
+ async waitForReadiness(input: { signal: AbortSignal }): Promise<RuntimeStateEnvelope> {
63
+ if (input.signal.aborted)
64
+ throw input.signal.reason
65
+ const aborted = Promise.withResolvers<never>()
66
+ const onAbort = () => aborted.reject(input.signal.reason)
67
+ input.signal.addEventListener('abort', onAbort, { once: true })
68
+ try {
69
+ return await Promise.race([this.readiness.promise, aborted.promise])
70
+ }
71
+ finally {
72
+ input.signal.removeEventListener('abort', onAbort)
73
+ }
74
+ }
75
+
76
+ takeSupervisorLivenessObservation(): boolean {
77
+ const observed = this.supervisorLivenessObserved
78
+ this.supervisorLivenessObserved = false
79
+ return observed
80
+ }
81
+
82
+ stop(reason?: unknown): void {
83
+ if (this.stopped)
84
+ return
85
+ this.stopped = true
86
+ this.rpc.stop(reason)
87
+ this.readiness.reject(reason ?? new DOMException('Supervisor connection closed.', 'AbortError'))
88
+ }
89
+
90
+ async drain(): Promise<void> {
91
+ await this.processing
92
+ }
93
+
94
+ private async process(text: string): Promise<void> {
95
+ if (this.stopped)
96
+ return
97
+ const decoded = decodeSupervisorFrame(text)
98
+ if (decoded.kind !== 'ok')
99
+ throw new Error(decoded.detail)
100
+ const frame = decoded.frame
101
+ if (frame.kind === 'runtime.state') {
102
+ if (this.readinessSettled)
103
+ throw new Error('Supervisor emitted more than one bootstrap outcome.')
104
+ this.readinessSettled = true
105
+ this.readiness.resolve(frame)
106
+ return
107
+ }
108
+ if (frame.kind === 'command.ack') {
109
+ if (!this.readinessSettled && frame.status === 'rejected') {
110
+ this.readinessSettled = true
111
+ this.readiness.reject(new BootstrapRejectedAckError(frame))
112
+ }
113
+ this.dependencies.onCommandAck(frame)
114
+ return
115
+ }
116
+ if (frame.kind === 'rpc.request') {
117
+ this.rpc.handle(frame)
118
+ return
119
+ }
120
+ if (frame.kind === 'rpc.cancel') {
121
+ this.rpc.cancel(frame)
122
+ return
123
+ }
124
+ if (frame.delivery_semantics === 'ephemeral') {
125
+ if (frame.body.type === 'supervisor.heartbeat') {
126
+ this.supervisorLivenessObserved = true
127
+ return
128
+ }
129
+ if (!isEphemeralWrappedAgentEvent(frame.body))
130
+ throw new Error('Supervisor emitted an invalid ephemeral event body.')
131
+ this.dependencies.ephemeralEvents.publish({
132
+ sessionId: this.dependencies.ref.sessionId,
133
+ occurredAt: frame.occurredAt,
134
+ body: frame.body,
135
+ })
136
+ return
137
+ }
138
+
139
+ const result = await this.dependencies.durableEvents.record({
140
+ ...this.dependencies.ref,
141
+ runtimeConnectionId: this.dependencies.runtimeConnectionId,
142
+ event: frame,
143
+ })
144
+ if (!('highWaterMark' in result))
145
+ throw new Error(`Durable supervisor event was rejected as ${result.status}.`)
146
+ await this.dependencies.connection.send(encodeWireFrame({
147
+ kind: 'event.ack',
148
+ highWaterMark: result.highWaterMark,
149
+ }))
150
+ }
151
+
152
+ async acknowledgeBootstrapRejection(ack: Extract<CommandAckEnvelope, { status: 'rejected' }>): Promise<void> {
153
+ await this.dependencies.failures.failDeterministicEstablishment({
154
+ ...this.dependencies.ref,
155
+ generation: this.dependencies.generation,
156
+ code: ack.errorCode,
157
+ detail: ack.detail,
158
+ })
159
+ this.dependencies.connection.close(
160
+ supervisorBootstrapRejectionReceiptCloseCode,
161
+ supervisorBootstrapRejectionReceiptCloseReason,
162
+ )
163
+ }
164
+ }
165
+
166
+ export class BootstrapRejectedAckError extends Error {
167
+ override readonly name = 'BootstrapRejectedAckError'
168
+
169
+ constructor(readonly ack: Extract<CommandAckEnvelope, { status: 'rejected' }>) {
170
+ super(ack.detail ?? ack.errorCode)
171
+ }
172
+ }
173
+
174
+ function frameText(data: string | ArrayBuffer | Uint8Array): string {
175
+ if (typeof data === 'string')
176
+ return data
177
+ return new TextDecoder().decode(data)
178
+ }
@@ -0,0 +1,27 @@
1
+ export { SessionCommandPump } from './command-pump'
2
+ export { ControlAuthorityConfigurationError, createControlAuthoritySigner } from './control-authority-signer'
3
+ export { createE2bSupervisorConnectionOpener, openE2bSupervisorConnection } from './e2b-supervisor-connection'
4
+ export { reconcileConnectionLeases } from './lease-reconciler'
5
+ export type {
6
+ BootstrapProvider,
7
+ CommandSource,
8
+ ConnectionAuthorityStore,
9
+ ConnectionClaim,
10
+ ControlAuthoritySigner,
11
+ DurableEventResult,
12
+ DurableEventSink,
13
+ EphemeralEventSink,
14
+ RpcDispatcher,
15
+ RuntimeNotifications,
16
+ SandboxConnectionRuntimeTimings,
17
+ SessionFailureSink,
18
+ SessionSandboxRef,
19
+ SupervisorConnection,
20
+ SupervisorConnectionOpener,
21
+ } from './ports'
22
+ export { createPostgresNotificationListener } from './postgres-notification-listener'
23
+ export type { PostgresNotificationListener } from './postgres-notification-listener'
24
+ export { createRpcDispatcher } from './rpc-dispatcher'
25
+ export type { SupervisorRpcHandler } from './rpc-dispatcher'
26
+ export { createSandboxConnectionRuntime } from './sandbox-connection-runtime'
27
+ export { decryptToken, encryptToken, TokenDecryptionError } from './token-crypto'
@@ -0,0 +1,14 @@
1
+ import type { ConnectionAuthorityStore } from './ports'
2
+
3
+ export async function reconcileConnectionLeases(input: {
4
+ authorityStore: ConnectionAuthorityStore
5
+ limit?: number
6
+ }): Promise<number> {
7
+ const candidates = await input.authorityStore.listConnectionCandidates({ limit: input.limit ?? 100 })
8
+ if (candidates.length > 0) {
9
+ await input.authorityStore.notifyConnectionDemand({
10
+ sessionSandboxIds: candidates.map(candidate => candidate.sessionSandboxId),
11
+ })
12
+ }
13
+ return candidates.length
14
+ }
@@ -0,0 +1,137 @@
1
+ import type { EphemeralCredentials, SessionBootstrapBody } from '../supervisor-protocol/bootstrap'
2
+ import type { WireCommandBody } from '../supervisor-protocol/command-body'
3
+ import type { CommandEnvelope } from '../supervisor-protocol/envelopes/control-plane-to-supervisor'
4
+ import type { DurableEventEnvelope, EphemeralEventEnvelope } from '../supervisor-protocol/envelopes/supervisor-to-control-plane'
5
+ import type { SupervisorRpcRequestEnvelope } from '../supervisor-protocol/rpc'
6
+ import type { SupervisorAgentRunSnapshot } from '../supervisor-protocol/supervisor-agent-run-snapshot'
7
+
8
+ export interface SessionSandboxRef {
9
+ sessionId: string
10
+ sessionSandboxId: string
11
+ providerSandboxId: string
12
+ }
13
+
14
+ export type ConnectionClaim = SessionSandboxRef & {
15
+ runtimeConnectionGeneration: number
16
+ runtimeConnectionId: string
17
+ trafficAccessTokenEncrypted: string
18
+ leaseExpiresAt: Date
19
+ }
20
+
21
+ export interface ConnectionAuthorityStore {
22
+ listConnectionCandidates: (input: { limit: number }) => Promise<{ sessionSandboxId: string }[]>
23
+ notifyConnectionDemand: (input: { sessionSandboxIds: string[] }) => Promise<void>
24
+ claimConnection: (input: { sessionSandboxId: string, replicaId: string, leaseDurationMs: number }) => Promise<ConnectionClaim | undefined>
25
+ renewConnectionLease: (input: {
26
+ sessionSandboxId: string
27
+ replicaId: string
28
+ generation: number
29
+ leaseDurationMs: number
30
+ supervisorLivenessObserved: boolean
31
+ }) => Promise<{ renewed: boolean, leaseExpiresAt?: Date, hasOutstandingCommand: boolean }>
32
+ confirmConnectionAuthority: (input: {
33
+ sessionSandboxId: string
34
+ generation: number
35
+ runtimeConnectionAttemptId: string
36
+ }) => Promise<boolean>
37
+ markConnectionReady: (input: {
38
+ sessionSandboxId: string
39
+ generation: number
40
+ runtimeConnectionId: string
41
+ lastReceivedCommandSeq: number
42
+ agentRunSnapshot: SupervisorAgentRunSnapshot
43
+ }) => Promise<boolean>
44
+ markConnectionReconnecting: (input: { sessionSandboxId: string, generation: number }) => Promise<void>
45
+ releaseConnection: (input: { sessionSandboxId: string, generation: number, reason: string }) => Promise<void>
46
+ markConnectionRetryExhausted: (input: { sessionSandboxId: string, generation: number, replicaId: string }) => Promise<boolean>
47
+ projectAppliedCommand: (input: { sessionSandboxId: string, runtimeConnectionId: string, commandSeq: number }) => Promise<void>
48
+ }
49
+
50
+ export interface CommandSource {
51
+ initial: (input: { sessionId: string }) => Promise<CommandEnvelope | undefined>
52
+ next: (input: { sessionId: string, runtimeConnectionId: string, afterSequence: number }) => Promise<CommandEnvelope | undefined>
53
+ }
54
+
55
+ export type DurableEventResult
56
+ = | { status: 'recorded' | 'duplicate', highWaterMark: number }
57
+ | { status: 'gap' | 'stale_owner' }
58
+
59
+ export interface DurableEventSink {
60
+ getAckFloor: (input: { sessionSandboxId: string }) => Promise<number>
61
+ record: (input: SessionSandboxRef & { runtimeConnectionId: string, event: DurableEventEnvelope }) => Promise<DurableEventResult>
62
+ }
63
+
64
+ export interface EphemeralEventSink {
65
+ publish: (input: { sessionId: string, occurredAt: string, body: EphemeralEventEnvelope['body'] }) => void
66
+ }
67
+
68
+ export interface RpcDispatcher {
69
+ handle: (input: SessionSandboxRef & { request: SupervisorRpcRequestEnvelope, signal: AbortSignal }) => Promise<unknown>
70
+ }
71
+
72
+ export interface SupervisorConnection {
73
+ send: (data: string) => void | Promise<void>
74
+ close: (code?: number, reason?: string) => void
75
+ }
76
+
77
+ export type SupervisorConnectionOpener = (input: {
78
+ providerSandboxId: string
79
+ trafficAccessToken: string
80
+ sessionId: string
81
+ sessionSandboxId: string
82
+ runtimeConnectionAttemptId: string
83
+ authorityAssertion: string
84
+ connectionGeneration: number
85
+ eventAckFloor: number
86
+ signal: AbortSignal
87
+ onMessage: (data: string | ArrayBuffer | Uint8Array) => void
88
+ onClose: (input: { code: number, reason: string }) => void
89
+ }) => Promise<SupervisorConnection>
90
+
91
+ export type ControlAuthoritySigner = (claims: {
92
+ providerSandboxId: string
93
+ sessionId: string
94
+ sessionSandboxId: string
95
+ connectionGeneration: number
96
+ runtimeConnectionAttemptId: string
97
+ eventAckFloor: number
98
+ }) => Promise<string>
99
+
100
+ export interface BootstrapProvider {
101
+ loadBootstrapBody: (input: { sessionId: string }) => Promise<Omit<SessionBootstrapBody, 'sessionId' | 'initialCommand'>>
102
+ issueEphemeralSecrets: (input: { sessionId: string, eventAckFloor: number, signal: AbortSignal }) => Promise<{
103
+ gitToken?: { token: string, expiresAt: string }
104
+ credentials?: EphemeralCredentials
105
+ }>
106
+ }
107
+
108
+ export interface SessionFailureSink {
109
+ markSessionFailed: (input: SessionSandboxRef & { code: string, detail?: string }) => Promise<void>
110
+ failDeterministicEstablishment: (input: SessionSandboxRef & { generation: number, code: string, detail?: string }) => Promise<void>
111
+ markEstablishmentExhausted: (input: SessionSandboxRef & { generation: number }) => Promise<void>
112
+ }
113
+
114
+ export interface RuntimeNotifications {
115
+ start: (input: {
116
+ onConnectionDemand: (sessionSandboxId: string) => void
117
+ onCommandsReady: (sessionId: string) => void
118
+ onOwnerChanged: (input: { sessionSandboxId: string, generation: number }) => void
119
+ }) => Promise<void>
120
+ stop: () => Promise<void>
121
+ }
122
+
123
+ export interface SandboxConnectionRuntimeTimings {
124
+ leaseDurationMs: number
125
+ leaseRenewIntervalMs: number
126
+ establishmentBudgetMs: number
127
+ dialTimeoutMs: number
128
+ retryInitialDelayMs: number
129
+ retryMaxDelayMs: number
130
+ candidateLimit: number
131
+ }
132
+
133
+ export interface BootstrapCommand {
134
+ commandId: string
135
+ commandSeq: number
136
+ body: WireCommandBody
137
+ }
@@ -0,0 +1,91 @@
1
+ import type { Client as PostgresClient } from 'pg'
2
+ import type { Logger } from '../logger'
3
+
4
+ export interface PostgresNotificationListener {
5
+ start: () => Promise<void>
6
+ stop: () => Promise<void>
7
+ }
8
+
9
+ export function createPostgresNotificationListener(input: {
10
+ connectionString: string
11
+ channels: readonly string[]
12
+ onNotification: (input: { channel: string, payload: string | undefined }) => void
13
+ onDisconnected?: () => void
14
+ onReady?: () => void
15
+ logger: Logger
16
+ reconnectDelayMs?: number
17
+ }): PostgresNotificationListener {
18
+ const controller = new AbortController()
19
+ let task: Promise<void> | undefined
20
+ let client: PostgresClient | undefined
21
+ const firstReady = Promise.withResolvers<void>()
22
+ let connectedOnce = false
23
+ const reconnectDelayMs = input.reconnectDelayMs ?? 1_000
24
+
25
+ return {
26
+ async start() {
27
+ if (task)
28
+ return
29
+ task = run().catch((error) => {
30
+ if (!connectedOnce)
31
+ firstReady.reject(error)
32
+ throw error
33
+ })
34
+ await firstReady.promise
35
+ },
36
+ async stop() {
37
+ if (!controller.signal.aborted)
38
+ controller.abort(new DOMException('PostgreSQL notification listener stopped.', 'AbortError'))
39
+ await client?.end().catch(() => undefined)
40
+ await task?.catch(() => undefined)
41
+ },
42
+ }
43
+
44
+ async function run(): Promise<void> {
45
+ const { Client, escapeIdentifier } = await import('pg')
46
+ while (!controller.signal.aborted) {
47
+ const candidate = new Client({ connectionString: input.connectionString })
48
+ client = candidate
49
+ try {
50
+ candidate.on('notification', notification => input.onNotification({
51
+ channel: notification.channel,
52
+ payload: notification.payload,
53
+ }))
54
+ candidate.on('error', error => input.logger.warn({ err: error }, 'PostgreSQL notification listener error'))
55
+ await candidate.connect()
56
+ for (const channel of input.channels)
57
+ await candidate.query(`LISTEN ${escapeIdentifier(channel)}`)
58
+ connectedOnce = true
59
+ firstReady.resolve()
60
+ input.onReady?.()
61
+ await waitForEnd({ client: candidate, signal: controller.signal })
62
+ }
63
+ catch (error) {
64
+ if (!controller.signal.aborted)
65
+ input.logger.warn({ err: error }, 'PostgreSQL notification listener disconnected')
66
+ }
67
+ finally {
68
+ input.onDisconnected?.()
69
+ if (client === candidate)
70
+ client = undefined
71
+ await candidate.end().catch(() => undefined)
72
+ }
73
+ if (!controller.signal.aborted)
74
+ await Bun.sleep(reconnectDelayMs)
75
+ }
76
+ if (!connectedOnce)
77
+ firstReady.reject(controller.signal.reason ?? new Error('PostgreSQL notification listener stopped before connecting.'))
78
+ }
79
+ }
80
+
81
+ async function waitForEnd(input: { client: PostgresClient, signal: AbortSignal }): Promise<void> {
82
+ await new Promise<void>((resolve) => {
83
+ const finish = () => {
84
+ input.client.removeListener('end', finish)
85
+ input.signal.removeEventListener('abort', finish)
86
+ resolve()
87
+ }
88
+ input.client.once('end', finish)
89
+ input.signal.addEventListener('abort', finish, { once: true })
90
+ })
91
+ }
@@ -0,0 +1,27 @@
1
+ import type { SupervisorRpcMethod, SupervisorRpcRequestEnvelope } from '../supervisor-protocol/rpc'
2
+ import type { RpcDispatcher, SessionSandboxRef } from './ports'
3
+
4
+ export type SupervisorRpcHandler = (input: SessionSandboxRef & {
5
+ request: SupervisorRpcRequestEnvelope
6
+ signal: AbortSignal
7
+ }) => Promise<unknown>
8
+
9
+ export function createRpcDispatcher(input: {
10
+ handlers: ReadonlyMap<SupervisorRpcMethod, SupervisorRpcHandler> | Readonly<Record<string, SupervisorRpcHandler>>
11
+ requiredMethods: readonly string[]
12
+ }): RpcDispatcher {
13
+ const handlers = input.handlers instanceof Map
14
+ ? new Map(input.handlers)
15
+ : new Map(Object.entries(input.handlers) as Array<[SupervisorRpcMethod, SupervisorRpcHandler]>)
16
+ const missing = input.requiredMethods.filter(method => !handlers.has(method as SupervisorRpcMethod))
17
+ if (missing.length > 0)
18
+ throw new Error(`Missing required supervisor RPC handlers: ${missing.join(', ')}.`)
19
+ return {
20
+ async handle(request) {
21
+ const handler = handlers.get(request.request.method)
22
+ if (!handler)
23
+ throw new Error(`Supervisor RPC method is not registered: ${request.request.method}.`)
24
+ return await handler(request)
25
+ },
26
+ }
27
+ }
@@ -0,0 +1,141 @@
1
+ import type { SupervisorRpcCancelEnvelope, SupervisorRpcRequestEnvelope, SupervisorRpcResponseEnvelope } from '../supervisor-protocol/rpc'
2
+ import type { ConnectionAuthorityStore, RpcDispatcher, SessionSandboxRef, SupervisorConnection } from './ports'
3
+ import { isAppError } from '../errors'
4
+
5
+ const maximumConcurrentRequests = 32
6
+
7
+ export class SupervisorRpcRequestManager {
8
+ private readonly active = new Map<string, AbortController>()
9
+ private stopped = false
10
+
11
+ constructor(private readonly dependencies: {
12
+ ref: SessionSandboxRef
13
+ generation: number
14
+ runtimeConnectionAttemptId: string
15
+ authorityStore: ConnectionAuthorityStore
16
+ dispatcher: RpcDispatcher
17
+ connection: SupervisorConnection
18
+ }) {}
19
+
20
+ handle(request: SupervisorRpcRequestEnvelope): void {
21
+ if (this.stopped)
22
+ return
23
+ if (this.active.has(request.requestId)) {
24
+ this.dependencies.connection.close(1008, 'Supervisor RPC request ID was reused.')
25
+ this.stop(new Error('Supervisor RPC request ID was reused.'))
26
+ return
27
+ }
28
+ if (this.active.size >= maximumConcurrentRequests) {
29
+ this.sendFailure({
30
+ requestId: request.requestId,
31
+ code: 'agent-core.rpc-capacity-exceeded',
32
+ message: 'Supervisor RPC capacity is unavailable.',
33
+ retryable: true,
34
+ })
35
+ return
36
+ }
37
+ const controller = new AbortController()
38
+ const timeout = setTimeout(() => {
39
+ controller.abort(new DOMException('Supervisor RPC deadline elapsed.', 'TimeoutError'))
40
+ }, request.deadlineMs)
41
+ timeout.unref?.()
42
+ this.active.set(request.requestId, controller)
43
+ void this.execute({ request, controller }).finally(() => {
44
+ clearTimeout(timeout)
45
+ if (this.active.get(request.requestId) === controller)
46
+ this.active.delete(request.requestId)
47
+ })
48
+ }
49
+
50
+ cancel(frame: SupervisorRpcCancelEnvelope): void {
51
+ this.active.get(frame.requestId)?.abort(new DOMException('Supervisor RPC was cancelled.', 'AbortError'))
52
+ }
53
+
54
+ stop(reason: unknown = new DOMException('Supervisor connection closed.', 'AbortError')): void {
55
+ if (this.stopped)
56
+ return
57
+ this.stopped = true
58
+ for (const controller of this.active.values())
59
+ controller.abort(reason)
60
+ this.active.clear()
61
+ }
62
+
63
+ private async execute(input: {
64
+ request: SupervisorRpcRequestEnvelope
65
+ controller: AbortController
66
+ }): Promise<void> {
67
+ try {
68
+ const authorized = await this.dependencies.authorityStore.confirmConnectionAuthority({
69
+ sessionSandboxId: this.dependencies.ref.sessionSandboxId,
70
+ generation: this.dependencies.generation,
71
+ runtimeConnectionAttemptId: this.dependencies.runtimeConnectionAttemptId,
72
+ })
73
+ if (!authorized)
74
+ throw new RpcAuthorityLostError()
75
+ const value = await this.dependencies.dispatcher.handle({
76
+ ...this.dependencies.ref,
77
+ request: input.request,
78
+ signal: input.controller.signal,
79
+ })
80
+ if (input.controller.signal.aborted || this.stopped)
81
+ return
82
+ await this.dependencies.connection.send(JSON.stringify({
83
+ kind: 'rpc.response',
84
+ requestId: input.request.requestId,
85
+ result: { status: 'ok', value },
86
+ } satisfies SupervisorRpcResponseEnvelope))
87
+ }
88
+ catch (error) {
89
+ if (input.controller.signal.aborted || this.stopped)
90
+ return
91
+ if (error instanceof RpcAuthorityLostError) {
92
+ this.sendFailure({
93
+ requestId: input.request.requestId,
94
+ code: 'agent-core.connection-authority-lost',
95
+ message: 'Supervisor connection authority is no longer current.',
96
+ retryable: true,
97
+ })
98
+ return
99
+ }
100
+ if (isAppError(error)) {
101
+ this.sendFailure({
102
+ requestId: input.request.requestId,
103
+ code: error.code,
104
+ message: error.publicMessage,
105
+ retryable: error.retryable,
106
+ })
107
+ return
108
+ }
109
+ this.sendFailure({
110
+ requestId: input.request.requestId,
111
+ code: 'agent-core.rpc-failed',
112
+ message: 'Supervisor RPC failed.',
113
+ retryable: true,
114
+ })
115
+ }
116
+ }
117
+
118
+ private sendFailure(input: {
119
+ requestId: string
120
+ code: string
121
+ message: string
122
+ retryable: boolean
123
+ }): void {
124
+ if (this.stopped)
125
+ return
126
+ void Promise.resolve(this.dependencies.connection.send(JSON.stringify({
127
+ kind: 'rpc.response',
128
+ requestId: input.requestId,
129
+ result: {
130
+ status: 'error',
131
+ error: { code: input.code, message: input.message, retryable: input.retryable },
132
+ },
133
+ } satisfies SupervisorRpcResponseEnvelope))).catch(() => {
134
+ this.dependencies.connection.close(1011, 'Supervisor RPC response delivery failed.')
135
+ })
136
+ }
137
+ }
138
+
139
+ class RpcAuthorityLostError extends Error {
140
+ override readonly name = 'RpcAuthorityLostError'
141
+ }