@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.
- package/README.md +24 -3
- package/bin/supervisor.ts +116 -0
- package/package.json +42 -3
- package/scripts/build-supervisor-executable.ts +32 -0
- package/src/agents/agent-error-serializer.ts +23 -0
- package/src/agents/agent-event-stream.ts +81 -0
- package/src/agents/agent-id.ts +17 -0
- package/src/agents/agent-operation.ts +11 -0
- package/src/agents/agent-provider.ts +37 -0
- package/src/agents/agent-run.ts +32 -0
- package/src/agents/agent-runtime-error.ts +161 -0
- package/src/agents/agent-session-events.ts +32 -0
- package/src/agents/agent-tool-runner.ts +83 -0
- package/src/agents/agent-tool.ts +111 -0
- package/src/agents/author-context.ts +36 -0
- package/src/agents/claude/claude-command-mapper.ts +234 -0
- package/src/agents/claude/claude-event-mapper.ts +736 -0
- package/src/agents/claude/claude-provider.ts +191 -0
- package/src/agents/claude/claude-run.ts +464 -0
- package/src/agents/claude/claude-tool-mapper.ts +186 -0
- package/src/agents/claude/index.ts +1 -0
- package/src/agents/codex/codex-auth.ts +34 -0
- package/src/agents/codex/codex-command-mapper.ts +78 -0
- package/src/agents/codex/codex-event-mapper.ts +708 -0
- package/src/agents/codex/codex-json-rpc-client.ts +326 -0
- package/src/agents/codex/codex-protocol.ts +36 -0
- package/src/agents/codex/codex-provider.ts +1050 -0
- package/src/agents/codex/codex-run.ts +404 -0
- package/src/agents/codex/codex-skill-catalog.ts +158 -0
- package/src/agents/codex/codex-skill-roots.ts +19 -0
- package/src/agents/codex/codex-tool-mapper.ts +55 -0
- package/src/agents/codex/codex.errors.ts +68 -0
- package/src/agents/codex/generated/meta.gen.ts +606 -0
- package/src/agents/codex/generated/namespaces.gen.ts +311 -0
- package/src/agents/codex/generated/schema.gen.ts +34883 -0
- package/src/agents/codex/index.ts +2 -0
- package/src/agents/index.ts +14 -0
- package/src/agents/input-attachment-preparation.ts +86 -0
- package/src/agents/input-attachment.errors.ts +16 -0
- package/src/agents/instructions.ts +8 -0
- package/src/agents/materialized-input-attachment.ts +26 -0
- package/src/agents/message-id.ts +23 -0
- package/src/agents/normalize.ts +8 -0
- package/src/agents/sandbox-environment.ts +1 -0
- package/src/agents/tools/ping.tool.ts +13 -0
- package/src/agents/user-input-request.ts +470 -0
- package/src/provenance.gen.ts +3 -3
- package/src/supervisor/agent-provider-factory.ts +189 -0
- package/src/supervisor/bootstrap-binder.ts +125 -0
- package/src/supervisor/config.ts +49 -0
- package/src/supervisor/control-authority-verifier.ts +135 -0
- package/src/supervisor/create-supervisor-runtime.ts +25 -0
- package/src/supervisor/errors.ts +24 -0
- package/src/supervisor/index.ts +34 -0
- package/src/supervisor/persistence/json.ts +21 -0
- package/src/supervisor/persistence/state-discovery.ts +56 -0
- package/src/supervisor/persistence/supervisor-store.ts +364 -0
- package/src/supervisor/ports/index.ts +109 -0
- package/src/supervisor/provider-factory.ts +37 -0
- package/src/supervisor/resident.ts +143 -0
- package/src/supervisor/rpc-client.ts +120 -0
- package/src/supervisor/runtime-handler.ts +309 -0
- package/src/supervisor/websocket-server.ts +434 -0
- package/src/supervisor-protocol/bootstrap.ts +1 -1
- package/src/template-onboarding.ts +47 -0
- package/src/testing/es256-test-keys.ts +73 -0
- package/src/testing/in-memory-runtime-control-plane.ts +205 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/loopback-supervisor-connection.ts +71 -0
- package/src/testing/scripted-provider.ts +64 -0
- package/src/worker-runtime-client/command-pump.ts +132 -0
- package/src/worker-runtime-client/connection-attempt.ts +340 -0
- package/src/worker-runtime-client/control-authority-signer.ts +100 -0
- package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
- package/src/worker-runtime-client/frame-processor.ts +178 -0
- package/src/worker-runtime-client/index.ts +27 -0
- package/src/worker-runtime-client/lease-reconciler.ts +14 -0
- package/src/worker-runtime-client/ports.ts +137 -0
- package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
- package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
- package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
- package/src/worker-runtime-client/sandbox-connection-runtime.ts +300 -0
- 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
|
+
}
|