@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
package/src/provenance.gen.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/write-provenance.ts. Do not edit by hand.
|
|
2
2
|
export const generatedProvenance = {
|
|
3
|
-
version: "0.
|
|
4
|
-
sha: "
|
|
5
|
-
buildTime: "2026-09-
|
|
3
|
+
version: "0.1.0",
|
|
4
|
+
sha: "9cd95365c6d39d1f44098fdd8006087c3adabfb4",
|
|
5
|
+
buildTime: "2026-09-04T20:01:49.852Z",
|
|
6
6
|
} as const
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { AgentProvider, AgentRun } from '../agents'
|
|
2
|
+
import type { AgentInputAttachmentPreparation } from '../agents/materialized-input-attachment'
|
|
3
|
+
import type { AgentEvent, AgentProviderId } from '../protocol'
|
|
4
|
+
import type { EphemeralCredentials, SessionBootstrapBody, SessionBootstrapGitToken } from '../supervisor-protocol/bootstrap'
|
|
5
|
+
import type { WireSendPromptCommand } from '../supervisor-protocol/command-body'
|
|
6
|
+
import type { WireEventBody } from '../supervisor-protocol/event-body'
|
|
7
|
+
import type { SupervisorAgentRunSnapshot } from '../supervisor-protocol/supervisor-agent-run-snapshot'
|
|
8
|
+
import type { SupervisorAgentRuntime, SupervisorProviderFactory } from './provider-factory'
|
|
9
|
+
import type { SupervisorRpcClient } from './rpc-client'
|
|
10
|
+
import { wrapAgentEvent } from '../supervisor-protocol/agent-event-wrapper'
|
|
11
|
+
import { createSupervisorProviderFactory } from './provider-factory'
|
|
12
|
+
|
|
13
|
+
export interface AgentProviderBindingStore {
|
|
14
|
+
load: (input: { sessionId: string, provider: AgentProviderId }) => Promise<string | undefined>
|
|
15
|
+
save: (input: { sessionId: string, provider: AgentProviderId, providerSessionId: string }) => Promise<void>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AgentSupervisorProviderFactoryOptions {
|
|
19
|
+
createProvider: (input: {
|
|
20
|
+
bootstrap: SessionBootstrapBody
|
|
21
|
+
credentials?: EphemeralCredentials
|
|
22
|
+
gitToken?: SessionBootstrapGitToken
|
|
23
|
+
rpc: SupervisorRpcClient
|
|
24
|
+
}) => AgentProvider<'claude'> | AgentProvider<'codex'>
|
|
25
|
+
bindingStore: AgentProviderBindingStore
|
|
26
|
+
prepareInputAttachments?: (input: {
|
|
27
|
+
commandId: string
|
|
28
|
+
command: WireSendPromptCommand
|
|
29
|
+
signal: AbortSignal
|
|
30
|
+
}) => Promise<AgentInputAttachmentPreparation | undefined>
|
|
31
|
+
onSecretsRefreshed?: (input: {
|
|
32
|
+
credentials?: EphemeralCredentials
|
|
33
|
+
gitToken?: SessionBootstrapGitToken
|
|
34
|
+
}) => void | Promise<void>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createAgentSupervisorProviderFactory(
|
|
38
|
+
options: AgentSupervisorProviderFactoryOptions,
|
|
39
|
+
): SupervisorProviderFactory {
|
|
40
|
+
return createSupervisorProviderFactory(async (input) => {
|
|
41
|
+
const provider = options.createProvider(input)
|
|
42
|
+
if (provider.id !== input.bootstrap.agent.provider)
|
|
43
|
+
throw new Error(`Provider factory returned ${provider.id} for ${input.bootstrap.agent.provider}.`)
|
|
44
|
+
|
|
45
|
+
const previousProviderSessionId = await options.bindingStore.load({
|
|
46
|
+
sessionId: input.bootstrap.sessionId,
|
|
47
|
+
provider: provider.id,
|
|
48
|
+
})
|
|
49
|
+
const sessionInput = {
|
|
50
|
+
sessionId: input.bootstrap.sessionId,
|
|
51
|
+
cwd: input.bootstrap.agent.cwd,
|
|
52
|
+
workspaceRepositoryRoots: input.bootstrap.repositories.map(repository => repository.root),
|
|
53
|
+
model: input.bootstrap.agent.model,
|
|
54
|
+
reasoningEffort: input.bootstrap.agent.reasoningEffort,
|
|
55
|
+
}
|
|
56
|
+
const run = provider.id === 'claude'
|
|
57
|
+
? previousProviderSessionId
|
|
58
|
+
? await provider.resumeSession({ ...sessionInput, provider: 'claude', providerSessionId: previousProviderSessionId }, { signal: input.signal })
|
|
59
|
+
: await provider.openSession({ ...sessionInput, provider: 'claude' }, { signal: input.signal })
|
|
60
|
+
: previousProviderSessionId
|
|
61
|
+
? await provider.resumeSession({ ...sessionInput, provider: 'codex', providerSessionId: previousProviderSessionId }, { signal: input.signal })
|
|
62
|
+
: await provider.openSession({ ...sessionInput, provider: 'codex' }, { signal: input.signal })
|
|
63
|
+
|
|
64
|
+
await options.bindingStore.save({
|
|
65
|
+
sessionId: input.bootstrap.sessionId,
|
|
66
|
+
provider: provider.id,
|
|
67
|
+
providerSessionId: run.metadata.providerSessionId,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return new AgentSupervisorRuntime({
|
|
71
|
+
run,
|
|
72
|
+
prepareInputAttachments: options.prepareInputAttachments,
|
|
73
|
+
onSecretsRefreshed: options.onSecretsRefreshed,
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
79
|
+
private state: SupervisorAgentRunSnapshot = { status: 'attached', agentState: null }
|
|
80
|
+
private emit: ((body: WireEventBody) => void) | undefined
|
|
81
|
+
private readonly pendingEvents: WireEventBody[] = []
|
|
82
|
+
private readonly eventPump: Promise<void>
|
|
83
|
+
|
|
84
|
+
constructor(private readonly input: {
|
|
85
|
+
run: AgentRun
|
|
86
|
+
prepareInputAttachments?: AgentSupervisorProviderFactoryOptions['prepareInputAttachments']
|
|
87
|
+
onSecretsRefreshed?: AgentSupervisorProviderFactoryOptions['onSecretsRefreshed']
|
|
88
|
+
}) {
|
|
89
|
+
this.eventPump = this.consumeEvents()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
snapshot(): SupervisorAgentRunSnapshot {
|
|
93
|
+
return this.state
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async handle(input: Parameters<SupervisorAgentRuntime['handle']>[0]): Promise<void> {
|
|
97
|
+
this.attachEmitter(input.emit)
|
|
98
|
+
const metadata = this.input.run.metadata
|
|
99
|
+
if (input.body.type === 'agent.send-prompt') {
|
|
100
|
+
const inputAttachmentPreparation = await this.input.prepareInputAttachments?.({
|
|
101
|
+
commandId: input.commandId,
|
|
102
|
+
command: input.body,
|
|
103
|
+
signal: input.signal,
|
|
104
|
+
})
|
|
105
|
+
await this.input.run.sendPrompt({
|
|
106
|
+
type: 'agent.send-prompt',
|
|
107
|
+
provider: metadata.provider,
|
|
108
|
+
sessionId: metadata.sessionId,
|
|
109
|
+
providerSessionId: metadata.providerSessionId,
|
|
110
|
+
commandId: input.commandId,
|
|
111
|
+
mode: input.body.mode,
|
|
112
|
+
origin: 'user',
|
|
113
|
+
prompt: input.body.prompt,
|
|
114
|
+
author: input.body.user
|
|
115
|
+
? {
|
|
116
|
+
name: input.body.user.name,
|
|
117
|
+
email: input.body.user.email,
|
|
118
|
+
gitEmail: input.body.user.gitEmail,
|
|
119
|
+
}
|
|
120
|
+
: undefined,
|
|
121
|
+
}, {
|
|
122
|
+
signal: input.signal,
|
|
123
|
+
inputAttachmentPreparation,
|
|
124
|
+
senderContext: input.body.user?.senderContext,
|
|
125
|
+
})
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
if (input.body.type === 'agent.interrupt') {
|
|
129
|
+
await this.input.run.interrupt({
|
|
130
|
+
type: 'agent.interrupt',
|
|
131
|
+
provider: metadata.provider,
|
|
132
|
+
sessionId: metadata.sessionId,
|
|
133
|
+
providerSessionId: metadata.providerSessionId,
|
|
134
|
+
commandId: input.commandId,
|
|
135
|
+
}, { signal: input.signal })
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
await this.input.run.respondUserInput({
|
|
139
|
+
type: 'agent.respond-user-input',
|
|
140
|
+
provider: metadata.provider,
|
|
141
|
+
sessionId: metadata.sessionId,
|
|
142
|
+
providerSessionId: metadata.providerSessionId,
|
|
143
|
+
commandId: input.commandId,
|
|
144
|
+
requestId: input.body.requestId,
|
|
145
|
+
answers: input.body.answers,
|
|
146
|
+
}, { signal: input.signal })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async refreshSecrets(input: {
|
|
150
|
+
credentials?: EphemeralCredentials
|
|
151
|
+
gitToken?: SessionBootstrapGitToken
|
|
152
|
+
}): Promise<void> {
|
|
153
|
+
await this.input.onSecretsRefreshed?.(input)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async close(input: { signal: AbortSignal }): Promise<void> {
|
|
157
|
+
const metadata = this.input.run.metadata
|
|
158
|
+
await this.input.run.stop({
|
|
159
|
+
type: 'agent.stop',
|
|
160
|
+
provider: metadata.provider,
|
|
161
|
+
sessionId: metadata.sessionId,
|
|
162
|
+
providerSessionId: metadata.providerSessionId,
|
|
163
|
+
reason: 'supervisor_closed',
|
|
164
|
+
}, { signal: input.signal }).catch(() => undefined)
|
|
165
|
+
await this.eventPump.catch(() => undefined)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private attachEmitter(emit: (body: WireEventBody) => void): void {
|
|
169
|
+
this.emit = emit
|
|
170
|
+
for (const event of this.pendingEvents.splice(0))
|
|
171
|
+
emit(event)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async consumeEvents(): Promise<void> {
|
|
175
|
+
for await (const event of this.input.run.events) {
|
|
176
|
+
if (event.type === 'session.state.changed')
|
|
177
|
+
this.state = { status: 'attached', agentState: event.payload.state }
|
|
178
|
+
this.emitEvent(event)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private emitEvent(event: AgentEvent): void {
|
|
183
|
+
const wrapped = wrapAgentEvent(event)
|
|
184
|
+
if (this.emit)
|
|
185
|
+
this.emit(wrapped)
|
|
186
|
+
else
|
|
187
|
+
this.pendingEvents.push(wrapped)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Logger } from '../logger'
|
|
2
|
+
import type { SessionBootstrapBody } from '../supervisor-protocol/bootstrap'
|
|
3
|
+
import type { SessionBootstrapEnvelope } from '../supervisor-protocol/envelopes/control-plane-to-supervisor'
|
|
4
|
+
import type { RejectedCommandAckEnvelope, RuntimeStateEnvelope } from '../supervisor-protocol/envelopes/supervisor-to-control-plane'
|
|
5
|
+
import type { RuntimeConnectionHandler } from './runtime-handler'
|
|
6
|
+
import { supervisorSessionDirectory, TERMINAL_ERROR_CODE } from './config'
|
|
7
|
+
import { SupervisorConfigurationError } from './errors'
|
|
8
|
+
import { sha256CanonicalJson } from './persistence/json'
|
|
9
|
+
import { SupervisorStore } from './persistence/supervisor-store'
|
|
10
|
+
|
|
11
|
+
export interface BootstrapAdmission {
|
|
12
|
+
assertAllowed: (body: SessionBootstrapBody) => Promise<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type SupervisorProcessBinding
|
|
16
|
+
= | { status: 'unbound' }
|
|
17
|
+
| { status: 'bound', store: SupervisorStore, sessionId: string }
|
|
18
|
+
|
|
19
|
+
type BootstrapBinding
|
|
20
|
+
= | { outcome: 'accepted', runtimeState: RuntimeStateEnvelope, activate: () => Promise<RuntimeConnectionHandler> }
|
|
21
|
+
| { outcome: 'rejected', ack: RejectedCommandAckEnvelope, terminate: () => void }
|
|
22
|
+
|
|
23
|
+
export type BootstrapBinder = (input: {
|
|
24
|
+
envelope: SessionBootstrapEnvelope
|
|
25
|
+
runtimeConnectionAttemptId: string
|
|
26
|
+
}) => Promise<BootstrapBinding>
|
|
27
|
+
|
|
28
|
+
export function createBootstrapBinder(input: {
|
|
29
|
+
stateRoot: string
|
|
30
|
+
getBinding: () => SupervisorProcessBinding
|
|
31
|
+
setBinding: (binding: SupervisorProcessBinding) => void
|
|
32
|
+
admission?: BootstrapAdmission
|
|
33
|
+
activate: (input: { store: SupervisorStore, envelope: SessionBootstrapEnvelope }) => Promise<RuntimeConnectionHandler>
|
|
34
|
+
shutdown: (code: number) => void
|
|
35
|
+
logger: Logger
|
|
36
|
+
}): BootstrapBinder {
|
|
37
|
+
return async (request) => {
|
|
38
|
+
try {
|
|
39
|
+
return await bind(request)
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (!(error instanceof SupervisorConfigurationError))
|
|
43
|
+
throw error
|
|
44
|
+
input.logger.error({ code: error.code, err: error }, 'resident supervisor rejected immutable bootstrap configuration')
|
|
45
|
+
return {
|
|
46
|
+
outcome: 'rejected',
|
|
47
|
+
ack: {
|
|
48
|
+
kind: 'command.ack',
|
|
49
|
+
commandId: request.envelope.body.initialCommand.commandId,
|
|
50
|
+
commandSeq: request.envelope.body.initialCommand.commandSeq,
|
|
51
|
+
status: 'rejected',
|
|
52
|
+
errorCode: error.code,
|
|
53
|
+
detail: error.publicMessage.slice(0, 240),
|
|
54
|
+
},
|
|
55
|
+
terminate: () => input.shutdown(TERMINAL_ERROR_CODE),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function bind(request: {
|
|
61
|
+
envelope: SessionBootstrapEnvelope
|
|
62
|
+
runtimeConnectionAttemptId: string
|
|
63
|
+
}): Promise<BootstrapBinding> {
|
|
64
|
+
let binding = input.getBinding()
|
|
65
|
+
if (binding.status === 'unbound') {
|
|
66
|
+
try {
|
|
67
|
+
await input.admission?.assertAllowed(request.envelope.body)
|
|
68
|
+
}
|
|
69
|
+
catch (cause) {
|
|
70
|
+
throw new SupervisorConfigurationError({
|
|
71
|
+
code: 'agent-core.bootstrap-admission-rejected',
|
|
72
|
+
message: 'Supervisor bootstrap admission rejected the immutable configuration.',
|
|
73
|
+
publicMessage: 'The sandbox configuration is not allowed.',
|
|
74
|
+
cause,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
const sessionDirectory = supervisorSessionDirectory({
|
|
78
|
+
stateRoot: input.stateRoot,
|
|
79
|
+
sessionId: request.envelope.body.sessionId,
|
|
80
|
+
})
|
|
81
|
+
const store = SupervisorStore.open({ sqlitePath: `${sessionDirectory}/state.sqlite` })
|
|
82
|
+
try {
|
|
83
|
+
store.bind({
|
|
84
|
+
body: request.envelope.body,
|
|
85
|
+
connectionGeneration: request.envelope.connectionGeneration,
|
|
86
|
+
runtimeConnectionAttemptId: request.runtimeConnectionAttemptId,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
catch (cause) {
|
|
90
|
+
store.close()
|
|
91
|
+
throw cause
|
|
92
|
+
}
|
|
93
|
+
binding = { status: 'bound', store, sessionId: request.envelope.body.sessionId }
|
|
94
|
+
input.setBinding(binding)
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
if (binding.sessionId !== request.envelope.body.sessionId) {
|
|
98
|
+
throw new SupervisorConfigurationError({
|
|
99
|
+
code: 'agent-core.bootstrap-session-conflict',
|
|
100
|
+
message: 'Supervisor is already bound to another session.',
|
|
101
|
+
publicMessage: 'The sandbox is already bound to another session.',
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
const persisted = binding.store.binding()
|
|
105
|
+
if (!persisted || persisted.bodyHash !== sha256CanonicalJson(request.envelope.body)) {
|
|
106
|
+
throw new SupervisorConfigurationError({
|
|
107
|
+
code: 'agent-core.bootstrap-configuration-conflict',
|
|
108
|
+
message: 'Supervisor reconnect bootstrap differs from its immutable binding.',
|
|
109
|
+
publicMessage: 'The sandbox configuration cannot change after bootstrap.',
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const acceptedBinding = binding
|
|
114
|
+
return {
|
|
115
|
+
outcome: 'accepted',
|
|
116
|
+
runtimeState: {
|
|
117
|
+
kind: 'runtime.state',
|
|
118
|
+
lastReceivedCommandSeq: acceptedBinding.store.lastReceivedCommandSeq(),
|
|
119
|
+
nextEventSeq: acceptedBinding.store.localEventMax() + 1,
|
|
120
|
+
agentRun: acceptedBinding.store.agentRunSnapshot(),
|
|
121
|
+
},
|
|
122
|
+
activate: async () => await input.activate({ store: acceptedBinding.store, envelope: request.envelope }),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
export const CLEAN_EXIT_CODE = 0
|
|
6
|
+
export const RECOVERABLE_ERROR_CODE = 75
|
|
7
|
+
export const TERMINAL_ERROR_CODE = 78
|
|
8
|
+
export const residentSupervisorPort = 8080
|
|
9
|
+
export const residentSupervisorUnixSocketPath = '/run/agent-core/control.sock'
|
|
10
|
+
export const residentSupervisorControlPath = '/control/ws'
|
|
11
|
+
export const residentSupervisorHealthPath = '/health'
|
|
12
|
+
export const sandboxControlAuthorityJwksPath = '/opt/agent-core/control-authority-jwks.json'
|
|
13
|
+
export const e2bSandboxIdPath = '/run/e2b/.E2B_SANDBOX_ID'
|
|
14
|
+
|
|
15
|
+
export interface ResidentSupervisorConfig {
|
|
16
|
+
host?: string
|
|
17
|
+
port?: number
|
|
18
|
+
unixSocketPath?: string
|
|
19
|
+
controlPath: string
|
|
20
|
+
healthPath: string
|
|
21
|
+
stateRoot: string
|
|
22
|
+
sandboxIdPath: string
|
|
23
|
+
authorityJwksPath: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function residentSupervisorConfig(input: {
|
|
27
|
+
homeDir?: string
|
|
28
|
+
authorityJwksPath?: string
|
|
29
|
+
sandboxIdPath?: string
|
|
30
|
+
stateRoot?: string
|
|
31
|
+
listen?: 'protected-unix' | 'loopback-tcp'
|
|
32
|
+
port?: number
|
|
33
|
+
} = {}): ResidentSupervisorConfig {
|
|
34
|
+
const listen = input.listen ?? 'protected-unix'
|
|
35
|
+
return {
|
|
36
|
+
...(listen === 'protected-unix'
|
|
37
|
+
? { unixSocketPath: residentSupervisorUnixSocketPath }
|
|
38
|
+
: { host: '127.0.0.1', port: input.port ?? residentSupervisorPort }),
|
|
39
|
+
controlPath: residentSupervisorControlPath,
|
|
40
|
+
healthPath: residentSupervisorHealthPath,
|
|
41
|
+
stateRoot: input.stateRoot ?? join(input.homeDir ?? homedir(), '.agent-core', 'supervisor'),
|
|
42
|
+
sandboxIdPath: input.sandboxIdPath ?? e2bSandboxIdPath,
|
|
43
|
+
authorityJwksPath: input.authorityJwksPath ?? sandboxControlAuthorityJwksPath,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function supervisorSessionDirectory(input: { stateRoot: string, sessionId: string }): string {
|
|
48
|
+
return join(input.stateRoot, createHash('sha256').update(input.sessionId, 'utf8').digest('hex'))
|
|
49
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { SandboxControlAuthorityClaims } from '../supervisor-protocol/control-authority'
|
|
2
|
+
import { Buffer } from 'node:buffer'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
import { InfrastructureError, ValidationError } from '../errors'
|
|
6
|
+
import {
|
|
7
|
+
sandboxControlAuthorityAssertionLifetimeSeconds,
|
|
8
|
+
sandboxControlAuthorityClaimsSchema,
|
|
9
|
+
sandboxControlAuthorityClockSkewSeconds,
|
|
10
|
+
sandboxControlAuthorityProtectedHeaderSchema,
|
|
11
|
+
sandboxControlAuthorityPublicJwksSchema,
|
|
12
|
+
} from '../supervisor-protocol/control-authority'
|
|
13
|
+
|
|
14
|
+
const verificationConfigSchema = z.object({
|
|
15
|
+
issuer: z.string().min(1),
|
|
16
|
+
jwks: sandboxControlAuthorityPublicJwksSchema,
|
|
17
|
+
}).strict()
|
|
18
|
+
|
|
19
|
+
export type VerifySupervisorControlAuthority = (input: {
|
|
20
|
+
assertion: string | null
|
|
21
|
+
providerSandboxId: string
|
|
22
|
+
sessionId: string
|
|
23
|
+
sessionSandboxId: string
|
|
24
|
+
connectionGeneration: number
|
|
25
|
+
runtimeConnectionAttemptId: string
|
|
26
|
+
eventAckFloor: number
|
|
27
|
+
}) => Promise<SandboxControlAuthorityClaims>
|
|
28
|
+
|
|
29
|
+
export async function createSupervisorControlAuthorityVerifier(input: {
|
|
30
|
+
jwksPath?: string
|
|
31
|
+
configuration?: { issuer: string, jwks: unknown }
|
|
32
|
+
}): Promise<VerifySupervisorControlAuthority> {
|
|
33
|
+
try {
|
|
34
|
+
const raw = input.configuration ?? JSON.parse(await readFile(requiredPath(input.jwksPath), 'utf8'))
|
|
35
|
+
const config = verificationConfigSchema.parse(raw)
|
|
36
|
+
const verificationKeys = new Map<string, CryptoKey>()
|
|
37
|
+
for (const jwk of config.jwks.keys) {
|
|
38
|
+
const key = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify'])
|
|
39
|
+
verificationKeys.set(jwk.kid, key)
|
|
40
|
+
}
|
|
41
|
+
return async (request) => {
|
|
42
|
+
if (!request.assertion)
|
|
43
|
+
throw new ControlAuthorityAdmissionError({ reason: 'missing' })
|
|
44
|
+
let claims: SandboxControlAuthorityClaims
|
|
45
|
+
try {
|
|
46
|
+
const segments = request.assertion.split('.')
|
|
47
|
+
if (segments.length !== 3)
|
|
48
|
+
throw new Error('Authority assertion is not a compact JWS.')
|
|
49
|
+
const [encodedHeader, encodedPayload, encodedSignature] = segments
|
|
50
|
+
if (!encodedHeader || !encodedPayload || !encodedSignature)
|
|
51
|
+
throw new Error('Authority assertion contains an empty segment.')
|
|
52
|
+
const protectedHeader = sandboxControlAuthorityProtectedHeaderSchema.parse(JSON.parse(decodeBase64Url(encodedHeader)))
|
|
53
|
+
const verificationKey = verificationKeys.get(protectedHeader.kid)
|
|
54
|
+
if (!verificationKey)
|
|
55
|
+
throw new Error('Authority assertion key ID is unknown.')
|
|
56
|
+
const verified = await crypto.subtle.verify(
|
|
57
|
+
{ name: 'ECDSA', hash: 'SHA-256' },
|
|
58
|
+
verificationKey,
|
|
59
|
+
Buffer.from(encodedSignature, 'base64url'),
|
|
60
|
+
new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`),
|
|
61
|
+
)
|
|
62
|
+
if (!verified)
|
|
63
|
+
throw new Error('Authority assertion signature is invalid.')
|
|
64
|
+
claims = sandboxControlAuthorityClaimsSchema.parse(JSON.parse(decodeBase64Url(encodedPayload)))
|
|
65
|
+
verifyTemporalClaims({ claims, issuer: config.issuer })
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
throw new ControlAuthorityAdmissionError({ reason: 'unverified', cause })
|
|
69
|
+
}
|
|
70
|
+
if (claims.providerSandboxId !== request.providerSandboxId
|
|
71
|
+
|| claims.sessionId !== request.sessionId
|
|
72
|
+
|| claims.sessionSandboxId !== request.sessionSandboxId
|
|
73
|
+
|| claims.connectionGeneration !== request.connectionGeneration
|
|
74
|
+
|| claims.runtimeConnectionAttemptId !== request.runtimeConnectionAttemptId
|
|
75
|
+
|| claims.eventAckFloor !== request.eventAckFloor) {
|
|
76
|
+
throw new ControlAuthorityAdmissionError({ reason: 'context-mismatch' })
|
|
77
|
+
}
|
|
78
|
+
return claims
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (cause) {
|
|
82
|
+
if (cause instanceof ControlAuthorityConfigurationError)
|
|
83
|
+
throw cause
|
|
84
|
+
throw new ControlAuthorityConfigurationError({ cause })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class ControlAuthorityConfigurationError extends InfrastructureError {
|
|
89
|
+
constructor(input: { cause: unknown }) {
|
|
90
|
+
super({
|
|
91
|
+
code: 'agent-core.control-authority-configuration-invalid',
|
|
92
|
+
message: 'Supervisor control-authority verification configuration is invalid.',
|
|
93
|
+
publicMessage: 'Supervisor control authority is unavailable.',
|
|
94
|
+
retryable: false,
|
|
95
|
+
cause: input.cause,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class ControlAuthorityAdmissionError extends ValidationError {
|
|
101
|
+
constructor(input: { reason: 'missing' | 'unverified' | 'context-mismatch', cause?: unknown }) {
|
|
102
|
+
super({
|
|
103
|
+
code: 'agent-core.control-authority-admission-denied',
|
|
104
|
+
message: 'Supervisor control-authority admission was denied.',
|
|
105
|
+
publicMessage: 'Supervisor control authority is invalid.',
|
|
106
|
+
details: { reason: input.reason },
|
|
107
|
+
cause: input.cause,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function requiredPath(path: string | undefined): string {
|
|
113
|
+
if (!path)
|
|
114
|
+
throw new Error('Control-authority JWKS path is required.')
|
|
115
|
+
return path
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function verifyTemporalClaims(input: { claims: SandboxControlAuthorityClaims, issuer: string }): void {
|
|
119
|
+
const now = Math.floor(Date.now() / 1_000)
|
|
120
|
+
if (input.claims.iss !== input.issuer)
|
|
121
|
+
throw new Error('Authority assertion issuer is invalid.')
|
|
122
|
+
if (input.claims.iat > now + sandboxControlAuthorityClockSkewSeconds
|
|
123
|
+
|| input.claims.nbf > now + sandboxControlAuthorityClockSkewSeconds
|
|
124
|
+
|| input.claims.exp < now - sandboxControlAuthorityClockSkewSeconds) {
|
|
125
|
+
throw new Error('Authority assertion is outside its validity interval.')
|
|
126
|
+
}
|
|
127
|
+
if (input.claims.exp - input.claims.iat !== sandboxControlAuthorityAssertionLifetimeSeconds)
|
|
128
|
+
throw new Error('Authority assertion lifetime is invalid.')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function decodeBase64Url(value: string): string {
|
|
132
|
+
if (!/^[\w-]+$/u.test(value))
|
|
133
|
+
throw new Error('Authority assertion compact JWS segment is malformed.')
|
|
134
|
+
return Buffer.from(value, 'base64url').toString('utf8')
|
|
135
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Logger } from '../logger'
|
|
2
|
+
import type { BootstrapAdmission } from './bootstrap-binder'
|
|
3
|
+
import type { ResidentSupervisorConfig } from './config'
|
|
4
|
+
import type { SupervisorProviderFactory } from './provider-factory'
|
|
5
|
+
import { createResidentSupervisor } from './resident'
|
|
6
|
+
|
|
7
|
+
export interface SupervisorExtensions {
|
|
8
|
+
profile: { name: string, version: number, requiredRpcMethods: readonly string[] }
|
|
9
|
+
providerFactory: SupervisorProviderFactory
|
|
10
|
+
admission?: BootstrapAdmission
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function createSupervisorRuntime(input: {
|
|
14
|
+
config: ResidentSupervisorConfig
|
|
15
|
+
extensions: SupervisorExtensions
|
|
16
|
+
logger: Logger
|
|
17
|
+
}) {
|
|
18
|
+
return await createResidentSupervisor({
|
|
19
|
+
config: input.config,
|
|
20
|
+
logger: input.logger,
|
|
21
|
+
providerFactory: input.extensions.providerFactory,
|
|
22
|
+
profile: input.extensions.profile,
|
|
23
|
+
admission: input.extensions.admission,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { InfrastructureError, ValidationError } from '../errors'
|
|
2
|
+
|
|
3
|
+
export class SupervisorConfigurationError extends ValidationError {
|
|
4
|
+
constructor(input: { code?: string, message: string, publicMessage?: string, cause?: unknown }) {
|
|
5
|
+
super({
|
|
6
|
+
code: input.code ?? 'agent-core.supervisor-invalid-config',
|
|
7
|
+
message: input.message,
|
|
8
|
+
publicMessage: input.publicMessage ?? 'Supervisor configuration is invalid.',
|
|
9
|
+
cause: input.cause,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class SupervisorPersistenceError extends InfrastructureError {
|
|
15
|
+
constructor(input: { code?: string, message: string, cause?: unknown }) {
|
|
16
|
+
super({
|
|
17
|
+
code: input.code ?? 'agent-core.supervisor-persistence-failed',
|
|
18
|
+
message: input.message,
|
|
19
|
+
publicMessage: 'Supervisor local persistence failed.',
|
|
20
|
+
retryable: false,
|
|
21
|
+
cause: input.cause,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export { createAgentSupervisorProviderFactory } from './agent-provider-factory'
|
|
2
|
+
export type { AgentProviderBindingStore, AgentSupervisorProviderFactoryOptions } from './agent-provider-factory'
|
|
3
|
+
export {
|
|
4
|
+
CLEAN_EXIT_CODE,
|
|
5
|
+
RECOVERABLE_ERROR_CODE,
|
|
6
|
+
residentSupervisorConfig,
|
|
7
|
+
TERMINAL_ERROR_CODE,
|
|
8
|
+
} from './config'
|
|
9
|
+
export type { ResidentSupervisorConfig } from './config'
|
|
10
|
+
export { createSupervisorControlAuthorityVerifier } from './control-authority-verifier'
|
|
11
|
+
export type { VerifySupervisorControlAuthority } from './control-authority-verifier'
|
|
12
|
+
export { createSupervisorRuntime } from './create-supervisor-runtime'
|
|
13
|
+
export type { SupervisorExtensions } from './create-supervisor-runtime'
|
|
14
|
+
export type {
|
|
15
|
+
InitializedWorkspaceRepository,
|
|
16
|
+
InstructionComposer,
|
|
17
|
+
LargePayloadStore,
|
|
18
|
+
MaterializedAgentInputAttachment,
|
|
19
|
+
ProviderCredentialsSource,
|
|
20
|
+
RepositorySyncAction,
|
|
21
|
+
SupervisorAgentTool,
|
|
22
|
+
SupervisorBroker,
|
|
23
|
+
SupervisorExtensionContext,
|
|
24
|
+
ToolRegistryFactory,
|
|
25
|
+
TurnHooks,
|
|
26
|
+
WorkspacePreparer,
|
|
27
|
+
WorkspaceSync,
|
|
28
|
+
WorkspaceSyncContext,
|
|
29
|
+
} from './ports'
|
|
30
|
+
export { createSupervisorProviderFactory } from './provider-factory'
|
|
31
|
+
export type { SupervisorAgentRuntime, SupervisorProviderFactory } from './provider-factory'
|
|
32
|
+
export { createResidentSupervisor } from './resident'
|
|
33
|
+
export type { ResidentSupervisor } from './resident'
|
|
34
|
+
export { SupervisorRpcClient, SupervisorRpcError } from './rpc-client'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export function canonicalJson(value: unknown): string {
|
|
5
|
+
return JSON.stringify(sortJsonValue(value)) ?? 'null'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function sha256CanonicalJson(value: unknown): string {
|
|
9
|
+
return createHash('sha256').update(canonicalJson(value)).digest('hex')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function sortJsonValue(value: unknown): unknown {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.map(sortJsonValue)
|
|
15
|
+
const objectValue = z.record(z.string(), z.unknown()).safeParse(value)
|
|
16
|
+
if (!objectValue.success)
|
|
17
|
+
return value
|
|
18
|
+
return Object.fromEntries(
|
|
19
|
+
Object.keys(objectValue.data).sort().map(key => [key, sortJsonValue(objectValue.data[key])]),
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { SupervisorPersistenceError } from '../errors'
|
|
4
|
+
import { SupervisorStore } from './supervisor-store'
|
|
5
|
+
|
|
6
|
+
export async function discoverPersistedSupervisorState(input: {
|
|
7
|
+
stateRoot: string
|
|
8
|
+
}): Promise<{ store: SupervisorStore, sessionId: string } | undefined> {
|
|
9
|
+
let entries
|
|
10
|
+
try {
|
|
11
|
+
entries = await readdir(input.stateRoot, { withFileTypes: true })
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
15
|
+
return undefined
|
|
16
|
+
throw error
|
|
17
|
+
}
|
|
18
|
+
const stores: SupervisorStore[] = []
|
|
19
|
+
let selected: { store: SupervisorStore, sessionId: string } | undefined
|
|
20
|
+
try {
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
23
|
+
continue
|
|
24
|
+
const sqlitePath = join(input.stateRoot, entry.name, 'state.sqlite')
|
|
25
|
+
try {
|
|
26
|
+
const databaseStat = await stat(sqlitePath)
|
|
27
|
+
if (!databaseStat.isFile())
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
32
|
+
continue
|
|
33
|
+
throw error
|
|
34
|
+
}
|
|
35
|
+
const store = SupervisorStore.open({ sqlitePath })
|
|
36
|
+
stores.push(store)
|
|
37
|
+
const binding = store.binding()
|
|
38
|
+
if (!binding)
|
|
39
|
+
continue
|
|
40
|
+
if (selected) {
|
|
41
|
+
throw new SupervisorPersistenceError({
|
|
42
|
+
code: 'agent-core.supervisor-multiple-bindings',
|
|
43
|
+
message: 'Resident supervisor found more than one session binding.',
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
selected = { store, sessionId: binding.body.sessionId }
|
|
47
|
+
}
|
|
48
|
+
return selected
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
for (const store of stores) {
|
|
52
|
+
if (store !== selected?.store)
|
|
53
|
+
store.close()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|