@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,364 @@
|
|
|
1
|
+
import type { SessionBootstrapBody } from '../../supervisor-protocol/bootstrap'
|
|
2
|
+
import type { CommandEnvelope } from '../../supervisor-protocol/envelopes/control-plane-to-supervisor'
|
|
3
|
+
import type { DurableEventEnvelope } from '../../supervisor-protocol/envelopes/supervisor-to-control-plane'
|
|
4
|
+
import type { WireEventBody } from '../../supervisor-protocol/event-body'
|
|
5
|
+
import type { SupervisorAgentRunSnapshot } from '../../supervisor-protocol/supervisor-agent-run-snapshot'
|
|
6
|
+
import { mkdirSync } from 'node:fs'
|
|
7
|
+
import { dirname } from 'node:path'
|
|
8
|
+
import { Database } from 'bun:sqlite'
|
|
9
|
+
import { sessionBootstrapBodySchema } from '../../supervisor-protocol/bootstrap'
|
|
10
|
+
import { wireCommandBodySchema } from '../../supervisor-protocol/command-body'
|
|
11
|
+
import { wireEventBodySchema } from '../../supervisor-protocol/event-body'
|
|
12
|
+
import { supervisorAgentRunSnapshotSchema } from '../../supervisor-protocol/supervisor-agent-run-snapshot'
|
|
13
|
+
import { SupervisorPersistenceError } from '../errors'
|
|
14
|
+
import { canonicalJson, sha256CanonicalJson } from './json'
|
|
15
|
+
|
|
16
|
+
const schemaVersion = 1
|
|
17
|
+
const bootstrapId = 'runtime'
|
|
18
|
+
|
|
19
|
+
interface BootstrapRow {
|
|
20
|
+
session_id: string
|
|
21
|
+
body_json: string
|
|
22
|
+
body_hash: string
|
|
23
|
+
connection_generation: number
|
|
24
|
+
runtime_connection_attempt_id: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CommandRow {
|
|
28
|
+
command_id: string
|
|
29
|
+
seq: number
|
|
30
|
+
body_json: string
|
|
31
|
+
body_hash: string
|
|
32
|
+
process_status: 'pending' | 'applied' | 'failed'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface EventRow {
|
|
36
|
+
seq: number
|
|
37
|
+
occurred_at: string
|
|
38
|
+
body_json: string
|
|
39
|
+
body_hash: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PersistedSupervisorBinding {
|
|
43
|
+
body: SessionBootstrapBody
|
|
44
|
+
bodyHash: string
|
|
45
|
+
connectionGeneration: number
|
|
46
|
+
runtimeConnectionAttemptId: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class SupervisorStore {
|
|
50
|
+
private closed = false
|
|
51
|
+
|
|
52
|
+
private constructor(private readonly database: Database) {}
|
|
53
|
+
|
|
54
|
+
static open(input: { sqlitePath: string }): SupervisorStore {
|
|
55
|
+
let store: SupervisorStore | undefined
|
|
56
|
+
try {
|
|
57
|
+
mkdirSync(dirname(input.sqlitePath), { recursive: true })
|
|
58
|
+
store = new SupervisorStore(new Database(input.sqlitePath, { create: true }))
|
|
59
|
+
store.database.exec('PRAGMA journal_mode = WAL')
|
|
60
|
+
store.database.exec('PRAGMA foreign_keys = ON')
|
|
61
|
+
store.database.exec('PRAGMA busy_timeout = 5000')
|
|
62
|
+
store.database.exec('PRAGMA synchronous = FULL')
|
|
63
|
+
const version = store.userVersion()
|
|
64
|
+
if (version === 0)
|
|
65
|
+
store.initializeSchema()
|
|
66
|
+
else if (version !== schemaVersion)
|
|
67
|
+
throw new Error(`Unsupported supervisor SQLite schema version: ${version}.`)
|
|
68
|
+
return store
|
|
69
|
+
}
|
|
70
|
+
catch (cause) {
|
|
71
|
+
store?.close()
|
|
72
|
+
throw new SupervisorPersistenceError({ message: 'Failed to open supervisor SQLite state.', cause })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
close(): void {
|
|
77
|
+
if (this.closed)
|
|
78
|
+
return
|
|
79
|
+
this.closed = true
|
|
80
|
+
this.database.close()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
binding(): PersistedSupervisorBinding | undefined {
|
|
84
|
+
const row = this.bootstrapRow()
|
|
85
|
+
return row ? bindingFromRow(row) : undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
bind(input: {
|
|
89
|
+
body: SessionBootstrapBody
|
|
90
|
+
connectionGeneration: number
|
|
91
|
+
runtimeConnectionAttemptId: string
|
|
92
|
+
}): PersistedSupervisorBinding {
|
|
93
|
+
const transaction = this.database.transaction(() => {
|
|
94
|
+
const existing = this.bootstrapRow()
|
|
95
|
+
if (!existing) {
|
|
96
|
+
const now = new Date().toISOString()
|
|
97
|
+
const bodyJson = canonicalJson(input.body)
|
|
98
|
+
const bodyHash = sha256CanonicalJson(input.body)
|
|
99
|
+
this.database.query(`
|
|
100
|
+
INSERT INTO bootstrap (
|
|
101
|
+
id, session_id, body_json, body_hash, connection_generation,
|
|
102
|
+
runtime_connection_attempt_id, created_at, updated_at
|
|
103
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
104
|
+
`).run(
|
|
105
|
+
bootstrapId,
|
|
106
|
+
input.body.sessionId,
|
|
107
|
+
bodyJson,
|
|
108
|
+
bodyHash,
|
|
109
|
+
input.connectionGeneration,
|
|
110
|
+
input.runtimeConnectionAttemptId,
|
|
111
|
+
now,
|
|
112
|
+
now,
|
|
113
|
+
)
|
|
114
|
+
this.insertAttempt(input)
|
|
115
|
+
this.insertCommand({
|
|
116
|
+
kind: 'command',
|
|
117
|
+
commandId: input.body.initialCommand.commandId,
|
|
118
|
+
commandSeq: input.body.initialCommand.commandSeq,
|
|
119
|
+
body: input.body.initialCommand.body,
|
|
120
|
+
})
|
|
121
|
+
return {
|
|
122
|
+
body: input.body,
|
|
123
|
+
bodyHash,
|
|
124
|
+
connectionGeneration: input.connectionGeneration,
|
|
125
|
+
runtimeConnectionAttemptId: input.runtimeConnectionAttemptId,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const binding = bindingFromRow(existing)
|
|
129
|
+
if (binding.body.sessionId !== input.body.sessionId)
|
|
130
|
+
throw new Error('Supervisor is already bound to another session.')
|
|
131
|
+
if (input.connectionGeneration < binding.connectionGeneration)
|
|
132
|
+
throw new Error('Supervisor connection generation is stale.')
|
|
133
|
+
this.insertAttempt(input)
|
|
134
|
+
this.database.query(`
|
|
135
|
+
UPDATE bootstrap
|
|
136
|
+
SET connection_generation = ?, runtime_connection_attempt_id = ?, updated_at = ?
|
|
137
|
+
WHERE id = ?
|
|
138
|
+
`).run(input.connectionGeneration, input.runtimeConnectionAttemptId, new Date().toISOString(), bootstrapId)
|
|
139
|
+
return {
|
|
140
|
+
...binding,
|
|
141
|
+
connectionGeneration: input.connectionGeneration,
|
|
142
|
+
runtimeConnectionAttemptId: input.runtimeConnectionAttemptId,
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
try {
|
|
146
|
+
return transaction()
|
|
147
|
+
}
|
|
148
|
+
catch (cause) {
|
|
149
|
+
throw new SupervisorPersistenceError({ message: 'Failed to persist supervisor bootstrap.', cause })
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
acceptConnectionAttempt(input: {
|
|
154
|
+
connectionGeneration: number
|
|
155
|
+
runtimeConnectionAttemptId: string
|
|
156
|
+
}): PersistedSupervisorBinding {
|
|
157
|
+
const binding = this.binding()
|
|
158
|
+
if (!binding)
|
|
159
|
+
throw new SupervisorPersistenceError({ message: 'Supervisor is not bound.' })
|
|
160
|
+
return this.bind({
|
|
161
|
+
body: binding.body,
|
|
162
|
+
connectionGeneration: input.connectionGeneration,
|
|
163
|
+
runtimeConnectionAttemptId: input.runtimeConnectionAttemptId,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
receiveCommand(envelope: CommandEnvelope): { status: 'received', duplicate: boolean } {
|
|
168
|
+
const body = wireCommandBodySchema.parse(envelope.body)
|
|
169
|
+
const existing = this.database.query<CommandRow, [number]>(`
|
|
170
|
+
SELECT command_id, seq, body_json, body_hash, process_status
|
|
171
|
+
FROM commands WHERE seq = ?
|
|
172
|
+
`).get(envelope.commandSeq)
|
|
173
|
+
if (existing) {
|
|
174
|
+
if (existing.command_id !== envelope.commandId
|
|
175
|
+
|| existing.body_hash !== sha256CanonicalJson(body)
|
|
176
|
+
|| existing.body_json !== canonicalJson(body)) {
|
|
177
|
+
throw new SupervisorPersistenceError({ message: 'Command sequence conflicts with durable inbox state.' })
|
|
178
|
+
}
|
|
179
|
+
return { status: 'received', duplicate: true }
|
|
180
|
+
}
|
|
181
|
+
const maximum = this.lastReceivedCommandSeq()
|
|
182
|
+
if (envelope.commandSeq !== maximum + 1)
|
|
183
|
+
throw new SupervisorPersistenceError({ message: 'Command sequence contains a gap.' })
|
|
184
|
+
try {
|
|
185
|
+
this.insertCommand({ ...envelope, body })
|
|
186
|
+
return { status: 'received', duplicate: false }
|
|
187
|
+
}
|
|
188
|
+
catch (cause) {
|
|
189
|
+
throw new SupervisorPersistenceError({ message: 'Failed to durably receive supervisor command.', cause })
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
pendingCommands(): Array<CommandEnvelope & { body: ReturnType<typeof wireCommandBodySchema.parse> }> {
|
|
194
|
+
return this.database.query<CommandRow, []>(`
|
|
195
|
+
SELECT command_id, seq, body_json, body_hash, process_status
|
|
196
|
+
FROM commands WHERE process_status = 'pending' ORDER BY seq
|
|
197
|
+
`).all().map(row => ({
|
|
198
|
+
kind: 'command',
|
|
199
|
+
commandId: row.command_id,
|
|
200
|
+
commandSeq: row.seq,
|
|
201
|
+
body: commandBodyFromRow(row),
|
|
202
|
+
}))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
markCommand(input: { commandSeq: number, status: 'applied' | 'failed' }): void {
|
|
206
|
+
this.database.query(`UPDATE commands SET process_status = ? WHERE seq = ?`).run(input.status, input.commandSeq)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
lastReceivedCommandSeq(): number {
|
|
210
|
+
return this.database.query<{ maximum: number }, []>('SELECT COALESCE(MAX(seq), 0) AS maximum FROM commands').get()?.maximum ?? 0
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
appendDurableEvent(input: { occurredAt: string, body: WireEventBody }): DurableEventEnvelope {
|
|
214
|
+
const body = wireEventBodySchema.parse(input.body)
|
|
215
|
+
const bodyJson = canonicalJson(body)
|
|
216
|
+
const result = this.database.query(`
|
|
217
|
+
INSERT INTO events (occurred_at, body_json, body_hash, created_at) VALUES (?, ?, ?, ?)
|
|
218
|
+
`).run(input.occurredAt, bodyJson, sha256CanonicalJson(body), new Date().toISOString())
|
|
219
|
+
return {
|
|
220
|
+
kind: 'event',
|
|
221
|
+
delivery_semantics: 'durable',
|
|
222
|
+
seq: Number(result.lastInsertRowid),
|
|
223
|
+
occurredAt: input.occurredAt,
|
|
224
|
+
body,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
eventsAfter(highWaterMark: number): DurableEventEnvelope[] {
|
|
229
|
+
return this.database.query<EventRow, [number]>(`
|
|
230
|
+
SELECT seq, occurred_at, body_json, body_hash FROM events WHERE seq > ? ORDER BY seq
|
|
231
|
+
`).all(highWaterMark).map(row => ({
|
|
232
|
+
kind: 'event',
|
|
233
|
+
delivery_semantics: 'durable',
|
|
234
|
+
seq: row.seq,
|
|
235
|
+
occurredAt: row.occurred_at,
|
|
236
|
+
body: eventBodyFromRow(row),
|
|
237
|
+
}))
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
localEventMax(): number {
|
|
241
|
+
return this.database.query<{ maximum: number }, []>('SELECT COALESCE(MAX(seq), 0) AS maximum FROM events').get()?.maximum ?? 0
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
agentRunSnapshot(): SupervisorAgentRunSnapshot {
|
|
245
|
+
const row = this.database.query<{ value: string }, []>('SELECT value FROM metadata WHERE key = \'agent_run\'').get()
|
|
246
|
+
return row ? supervisorAgentRunSnapshotSchema.parse(JSON.parse(row.value)) : { status: 'not_attached' }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setAgentRunSnapshot(snapshot: SupervisorAgentRunSnapshot): void {
|
|
250
|
+
const value = canonicalJson(supervisorAgentRunSnapshotSchema.parse(snapshot))
|
|
251
|
+
this.database.query(`
|
|
252
|
+
INSERT INTO metadata (key, value) VALUES ('agent_run', ?)
|
|
253
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
254
|
+
`).run(value)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private insertAttempt(input: { connectionGeneration: number, runtimeConnectionAttemptId: string }): void {
|
|
258
|
+
const replay = this.database.query<{ present: number }, [number, string]>(`
|
|
259
|
+
SELECT 1 AS present FROM connection_attempts
|
|
260
|
+
WHERE connection_generation = ? AND runtime_connection_attempt_id = ?
|
|
261
|
+
`).get(input.connectionGeneration, input.runtimeConnectionAttemptId)
|
|
262
|
+
if (replay)
|
|
263
|
+
throw new Error('Supervisor connection attempt was already admitted.')
|
|
264
|
+
this.database.query('DELETE FROM connection_attempts WHERE connection_generation < ?').run(input.connectionGeneration)
|
|
265
|
+
this.database.query(`
|
|
266
|
+
INSERT INTO connection_attempts (connection_generation, runtime_connection_attempt_id, accepted_at)
|
|
267
|
+
VALUES (?, ?, ?)
|
|
268
|
+
`).run(input.connectionGeneration, input.runtimeConnectionAttemptId, new Date().toISOString())
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private insertCommand(envelope: CommandEnvelope): void {
|
|
272
|
+
const body = wireCommandBodySchema.parse(envelope.body)
|
|
273
|
+
this.database.query(`
|
|
274
|
+
INSERT INTO commands (seq, command_id, body_json, body_hash, process_status, received_at)
|
|
275
|
+
VALUES (?, ?, ?, ?, 'pending', ?)
|
|
276
|
+
`).run(
|
|
277
|
+
envelope.commandSeq,
|
|
278
|
+
envelope.commandId,
|
|
279
|
+
canonicalJson(body),
|
|
280
|
+
sha256CanonicalJson(body),
|
|
281
|
+
new Date().toISOString(),
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private bootstrapRow(): BootstrapRow | undefined {
|
|
286
|
+
return this.database.query<BootstrapRow, []>(`
|
|
287
|
+
SELECT session_id, body_json, body_hash, connection_generation, runtime_connection_attempt_id
|
|
288
|
+
FROM bootstrap WHERE id = 'runtime'
|
|
289
|
+
`).get() ?? undefined
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private userVersion(): number {
|
|
293
|
+
return this.database.query<{ user_version: number }, []>('PRAGMA user_version').get()?.user_version ?? 0
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private initializeSchema(): void {
|
|
297
|
+
this.database.transaction(() => {
|
|
298
|
+
this.database.exec(`
|
|
299
|
+
CREATE TABLE bootstrap (
|
|
300
|
+
id TEXT PRIMARY KEY CHECK (id = 'runtime'),
|
|
301
|
+
session_id TEXT NOT NULL CHECK (session_id <> ''),
|
|
302
|
+
body_json TEXT NOT NULL,
|
|
303
|
+
body_hash TEXT NOT NULL CHECK (length(body_hash) = 64),
|
|
304
|
+
connection_generation INTEGER NOT NULL CHECK (connection_generation > 0),
|
|
305
|
+
runtime_connection_attempt_id TEXT NOT NULL CHECK (runtime_connection_attempt_id <> ''),
|
|
306
|
+
created_at TEXT NOT NULL,
|
|
307
|
+
updated_at TEXT NOT NULL
|
|
308
|
+
);
|
|
309
|
+
CREATE TABLE connection_attempts (
|
|
310
|
+
connection_generation INTEGER NOT NULL CHECK (connection_generation > 0),
|
|
311
|
+
runtime_connection_attempt_id TEXT NOT NULL CHECK (runtime_connection_attempt_id <> ''),
|
|
312
|
+
accepted_at TEXT NOT NULL,
|
|
313
|
+
PRIMARY KEY (connection_generation, runtime_connection_attempt_id)
|
|
314
|
+
);
|
|
315
|
+
CREATE TABLE commands (
|
|
316
|
+
seq INTEGER PRIMARY KEY CHECK (seq > 0),
|
|
317
|
+
command_id TEXT NOT NULL UNIQUE CHECK (command_id <> ''),
|
|
318
|
+
body_json TEXT NOT NULL,
|
|
319
|
+
body_hash TEXT NOT NULL CHECK (length(body_hash) = 64),
|
|
320
|
+
process_status TEXT NOT NULL CHECK (process_status IN ('pending', 'applied', 'failed')),
|
|
321
|
+
received_at TEXT NOT NULL
|
|
322
|
+
);
|
|
323
|
+
CREATE TABLE events (
|
|
324
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
325
|
+
occurred_at TEXT NOT NULL,
|
|
326
|
+
body_json TEXT NOT NULL,
|
|
327
|
+
body_hash TEXT NOT NULL CHECK (length(body_hash) = 64),
|
|
328
|
+
created_at TEXT NOT NULL
|
|
329
|
+
);
|
|
330
|
+
CREATE TABLE metadata (
|
|
331
|
+
key TEXT PRIMARY KEY CHECK (key <> ''),
|
|
332
|
+
value TEXT NOT NULL
|
|
333
|
+
);
|
|
334
|
+
`)
|
|
335
|
+
this.database.exec(`PRAGMA user_version = ${schemaVersion}`)
|
|
336
|
+
})()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function commandBodyFromRow(row: CommandRow) {
|
|
341
|
+
const body = wireCommandBodySchema.parse(JSON.parse(row.body_json))
|
|
342
|
+
if (canonicalJson(body) !== row.body_json || sha256CanonicalJson(body) !== row.body_hash)
|
|
343
|
+
throw new SupervisorPersistenceError({ message: `Supervisor command ${row.seq} failed canonical integrity validation.` })
|
|
344
|
+
return body
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function eventBodyFromRow(row: EventRow) {
|
|
348
|
+
const body = wireEventBodySchema.parse(JSON.parse(row.body_json))
|
|
349
|
+
if (canonicalJson(body) !== row.body_json || sha256CanonicalJson(body) !== row.body_hash)
|
|
350
|
+
throw new SupervisorPersistenceError({ message: `Supervisor event ${row.seq} failed canonical integrity validation.` })
|
|
351
|
+
return body
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function bindingFromRow(row: BootstrapRow): PersistedSupervisorBinding {
|
|
355
|
+
const body = sessionBootstrapBodySchema.parse(JSON.parse(row.body_json))
|
|
356
|
+
if (canonicalJson(body) !== row.body_json || sha256CanonicalJson(body) !== row.body_hash)
|
|
357
|
+
throw new SupervisorPersistenceError({ message: 'Supervisor bootstrap body failed canonical integrity validation.' })
|
|
358
|
+
return {
|
|
359
|
+
body,
|
|
360
|
+
bodyHash: row.body_hash,
|
|
361
|
+
connectionGeneration: row.connection_generation,
|
|
362
|
+
runtimeConnectionAttemptId: row.runtime_connection_attempt_id,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { AgentInputBlock, AgentProviderId } from '../../protocol'
|
|
2
|
+
import type { EphemeralCredentials, SessionBootstrapBody } from '../../supervisor-protocol/bootstrap'
|
|
3
|
+
import type { WireSendPromptCommand } from '../../supervisor-protocol/command-body'
|
|
4
|
+
import type { ProductEvent } from '../../supervisor-protocol/product-event'
|
|
5
|
+
import type { SupervisorRpcClient } from '../rpc-client'
|
|
6
|
+
|
|
7
|
+
export interface ProviderCredentialsSource {
|
|
8
|
+
initial: () => EphemeralCredentials | undefined
|
|
9
|
+
refresh: (input: { reason: 'startup' | 'expired' | 'rejected', signal?: AbortSignal }) => Promise<EphemeralCredentials>
|
|
10
|
+
environmentFor: (provider: AgentProviderId) => Record<string, string>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SupervisorAgentTool {
|
|
14
|
+
name: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ToolRegistryFactory = (provider: AgentProviderId) => readonly SupervisorAgentTool[]
|
|
18
|
+
|
|
19
|
+
export type InstructionComposer = (input: {
|
|
20
|
+
provider: AgentProviderId
|
|
21
|
+
tools: readonly { name: string }[]
|
|
22
|
+
}) => string
|
|
23
|
+
|
|
24
|
+
export interface InitializedWorkspaceRepository {
|
|
25
|
+
repository: { id: string, fullName: string }
|
|
26
|
+
root: string
|
|
27
|
+
branch: string
|
|
28
|
+
sha: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MaterializedAgentInputAttachment {
|
|
32
|
+
attachmentId: string
|
|
33
|
+
ordinal: number
|
|
34
|
+
filename: string
|
|
35
|
+
mediaType: string
|
|
36
|
+
byteSize: number
|
|
37
|
+
sha256: string
|
|
38
|
+
localPath: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WorkspacePreparer {
|
|
42
|
+
initialize: (input: {
|
|
43
|
+
signal: AbortSignal
|
|
44
|
+
onStartupStage: (input: { stage: string, outcome: 'success' | 'failure', duration: number }) => void
|
|
45
|
+
}) => Promise<readonly InitializedWorkspaceRepository[]>
|
|
46
|
+
prepareTurn: (input: {
|
|
47
|
+
commandId: string
|
|
48
|
+
command: WireSendPromptCommand
|
|
49
|
+
signal: AbortSignal
|
|
50
|
+
}) => Promise<{
|
|
51
|
+
attachments: readonly MaterializedAgentInputAttachment[]
|
|
52
|
+
promptPrefixBlocks?: AgentInputBlock[]
|
|
53
|
+
}>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface WorkspaceSyncContext {
|
|
57
|
+
trigger: 'turn-ended' | 'boot-reconcile'
|
|
58
|
+
turnId: string
|
|
59
|
+
author: { name: string, email: string } | undefined
|
|
60
|
+
additionalUsers: Array<{ id: string, name: string, gitEmail: string }>
|
|
61
|
+
assistantSummary: string | undefined
|
|
62
|
+
commitAllowed: boolean
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type RepositorySyncAction
|
|
66
|
+
= | { kind: 'commit-completed', repository: { id: string, fullName: string }, branch: string, commitSha: string }
|
|
67
|
+
| { kind: 'commit-failed', repository: string, branch: string, reason: 'unknown', detail: string }
|
|
68
|
+
| { kind: 'push-completed', repository: { id: string, fullName: string }, branch: string, remoteHeadSha: string }
|
|
69
|
+
| {
|
|
70
|
+
kind: 'push-failed'
|
|
71
|
+
repository: string
|
|
72
|
+
branch: string
|
|
73
|
+
reason: 'non-fast-forward' | 'auth' | 'network' | 'rejected' | 'unknown'
|
|
74
|
+
detail: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface WorkspaceSync {
|
|
78
|
+
sync: (context: WorkspaceSyncContext) => Promise<RepositorySyncAction[]>
|
|
79
|
+
fetch: () => Promise<void>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface TurnHooks {
|
|
83
|
+
onTurnEnded: (input: {
|
|
84
|
+
turnId: string
|
|
85
|
+
status: 'completed' | 'failed' | 'interrupted' | 'cancelled'
|
|
86
|
+
cwd: string
|
|
87
|
+
signal: AbortSignal
|
|
88
|
+
}) => Promise<ProductEvent[]>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface LargePayloadStore {
|
|
92
|
+
put: (input: {
|
|
93
|
+
sessionId: string
|
|
94
|
+
eventType: string
|
|
95
|
+
bytes: Uint8Array
|
|
96
|
+
signal: AbortSignal
|
|
97
|
+
}) => Promise<{ ref: string }>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface SupervisorExtensionContext {
|
|
101
|
+
bootstrap: SessionBootstrapBody
|
|
102
|
+
rpc: SupervisorRpcClient
|
|
103
|
+
signal: AbortSignal
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SupervisorBroker {
|
|
107
|
+
start: () => void | Promise<void>
|
|
108
|
+
stop: () => void | Promise<void>
|
|
109
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EphemeralCredentials, SessionBootstrapBody, SessionBootstrapGitToken } from '../supervisor-protocol/bootstrap'
|
|
2
|
+
import type { WireCommandBody } from '../supervisor-protocol/command-body'
|
|
3
|
+
import type { WireEventBody } from '../supervisor-protocol/event-body'
|
|
4
|
+
import type { SupervisorAgentRunSnapshot } from '../supervisor-protocol/supervisor-agent-run-snapshot'
|
|
5
|
+
import type { SupervisorRpcClient } from './rpc-client'
|
|
6
|
+
|
|
7
|
+
export interface SupervisorAgentRuntime {
|
|
8
|
+
snapshot: () => SupervisorAgentRunSnapshot
|
|
9
|
+
handle: (input: {
|
|
10
|
+
commandId: string
|
|
11
|
+
commandSeq: number
|
|
12
|
+
body: WireCommandBody
|
|
13
|
+
signal: AbortSignal
|
|
14
|
+
emit: (body: WireEventBody) => void
|
|
15
|
+
}) => Promise<void>
|
|
16
|
+
refreshSecrets?: (input: {
|
|
17
|
+
credentials?: EphemeralCredentials
|
|
18
|
+
gitToken?: SessionBootstrapGitToken
|
|
19
|
+
}) => void | Promise<void>
|
|
20
|
+
close: (input: { signal: AbortSignal }) => Promise<void>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SupervisorProviderFactory {
|
|
24
|
+
create: (input: {
|
|
25
|
+
bootstrap: SessionBootstrapBody
|
|
26
|
+
credentials?: EphemeralCredentials
|
|
27
|
+
gitToken?: SessionBootstrapGitToken
|
|
28
|
+
rpc: SupervisorRpcClient
|
|
29
|
+
signal: AbortSignal
|
|
30
|
+
}) => Promise<SupervisorAgentRuntime>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createSupervisorProviderFactory(
|
|
34
|
+
create: SupervisorProviderFactory['create'],
|
|
35
|
+
): SupervisorProviderFactory {
|
|
36
|
+
return { create }
|
|
37
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Logger } from '../logger'
|
|
2
|
+
import type { SessionBootstrapEnvelope } from '../supervisor-protocol/envelopes/control-plane-to-supervisor'
|
|
3
|
+
import type { BootstrapAdmission, SupervisorProcessBinding } from './bootstrap-binder'
|
|
4
|
+
import type { ResidentSupervisorConfig } from './config'
|
|
5
|
+
import type { VerifySupervisorControlAuthority } from './control-authority-verifier'
|
|
6
|
+
import type { SupervisorStore } from './persistence/supervisor-store'
|
|
7
|
+
import type { SupervisorProviderFactory } from './provider-factory'
|
|
8
|
+
import { agentCoreProvenance } from '../provenance'
|
|
9
|
+
import { createBootstrapBinder } from './bootstrap-binder'
|
|
10
|
+
import { CLEAN_EXIT_CODE, RECOVERABLE_ERROR_CODE, TERMINAL_ERROR_CODE } from './config'
|
|
11
|
+
import { createSupervisorControlAuthorityVerifier } from './control-authority-verifier'
|
|
12
|
+
import { SupervisorConfigurationError } from './errors'
|
|
13
|
+
import { discoverPersistedSupervisorState } from './persistence/state-discovery'
|
|
14
|
+
import { SupervisorRuntimeHandler } from './runtime-handler'
|
|
15
|
+
import { ResidentSupervisorWebSocketServer } from './websocket-server'
|
|
16
|
+
|
|
17
|
+
export interface ResidentSupervisor {
|
|
18
|
+
start: () => void
|
|
19
|
+
stop: (input?: { reason?: 'sigterm' | 'sigint' | 'fatal_error', exitCode?: number }) => Promise<void>
|
|
20
|
+
port: () => number | undefined
|
|
21
|
+
binding: () => 'unbound' | 'bound'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function createResidentSupervisor(input: {
|
|
25
|
+
config: ResidentSupervisorConfig
|
|
26
|
+
logger: Logger
|
|
27
|
+
providerFactory: SupervisorProviderFactory
|
|
28
|
+
profile: { name: string, version: number, requiredRpcMethods: readonly string[] }
|
|
29
|
+
admission?: BootstrapAdmission
|
|
30
|
+
verifyControlAuthority?: VerifySupervisorControlAuthority
|
|
31
|
+
onExitRequested?: (code: number) => void
|
|
32
|
+
onStartupStage?: (fact: {
|
|
33
|
+
event: 'sandbox.startup-stage'
|
|
34
|
+
stage: 'bootstrap-persisted' | 'bootstrap-readiness-sent'
|
|
35
|
+
outcome: 'success' | 'failure'
|
|
36
|
+
duration: number
|
|
37
|
+
sessionId: string
|
|
38
|
+
}) => void
|
|
39
|
+
}): Promise<ResidentSupervisor> {
|
|
40
|
+
const recovered = await discoverPersistedSupervisorState({ stateRoot: input.config.stateRoot })
|
|
41
|
+
let binding: SupervisorProcessBinding = recovered
|
|
42
|
+
? { status: 'bound', store: recovered.store, sessionId: recovered.sessionId }
|
|
43
|
+
: { status: 'unbound' }
|
|
44
|
+
let handler: SupervisorRuntimeHandler | undefined
|
|
45
|
+
let processStartedPersisted = false
|
|
46
|
+
let stopped = false
|
|
47
|
+
let exitCode = CLEAN_EXIT_CODE
|
|
48
|
+
|
|
49
|
+
const verifyControlAuthority = input.verifyControlAuthority
|
|
50
|
+
?? await createSupervisorControlAuthorityVerifier({ jwksPath: input.config.authorityJwksPath })
|
|
51
|
+
if (recovered) {
|
|
52
|
+
recovered.store.appendDurableEvent({
|
|
53
|
+
occurredAt: new Date().toISOString(),
|
|
54
|
+
body: { type: 'supervisor.started', payload: {} },
|
|
55
|
+
})
|
|
56
|
+
processStartedPersisted = true
|
|
57
|
+
}
|
|
58
|
+
const requestShutdown = (code: number) => {
|
|
59
|
+
exitCode = code
|
|
60
|
+
input.onExitRequested?.(code)
|
|
61
|
+
void stop({ reason: 'fatal_error', exitCode: code })
|
|
62
|
+
}
|
|
63
|
+
const activate = async (activation: { store: SupervisorStore, envelope: SessionBootstrapEnvelope }) => {
|
|
64
|
+
if (!processStartedPersisted) {
|
|
65
|
+
activation.store.appendDurableEvent({
|
|
66
|
+
occurredAt: new Date().toISOString(),
|
|
67
|
+
body: { type: 'supervisor.started', payload: {} },
|
|
68
|
+
})
|
|
69
|
+
processStartedPersisted = true
|
|
70
|
+
}
|
|
71
|
+
if (!handler) {
|
|
72
|
+
handler = new SupervisorRuntimeHandler({
|
|
73
|
+
store: activation.store,
|
|
74
|
+
providerFactory: input.providerFactory,
|
|
75
|
+
credentials: activation.envelope.credentials,
|
|
76
|
+
gitToken: activation.envelope.gitToken,
|
|
77
|
+
logger: input.logger,
|
|
78
|
+
onFatal: error => requestShutdown(
|
|
79
|
+
error instanceof SupervisorConfigurationError ? TERMINAL_ERROR_CODE : RECOVERABLE_ERROR_CODE,
|
|
80
|
+
),
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await handler.refreshSecrets({
|
|
85
|
+
credentials: activation.envelope.credentials,
|
|
86
|
+
gitToken: activation.envelope.gitToken,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
return handler
|
|
90
|
+
}
|
|
91
|
+
const bindBootstrap = createBootstrapBinder({
|
|
92
|
+
stateRoot: input.config.stateRoot,
|
|
93
|
+
getBinding: () => binding,
|
|
94
|
+
setBinding: value => binding = value,
|
|
95
|
+
admission: input.admission,
|
|
96
|
+
activate,
|
|
97
|
+
shutdown: requestShutdown,
|
|
98
|
+
logger: input.logger,
|
|
99
|
+
})
|
|
100
|
+
const listener = new ResidentSupervisorWebSocketServer({
|
|
101
|
+
config: input.config,
|
|
102
|
+
logger: input.logger,
|
|
103
|
+
health: () => ({
|
|
104
|
+
status: 'ok',
|
|
105
|
+
binding: binding.status,
|
|
106
|
+
profile: input.profile,
|
|
107
|
+
agentCore: agentCoreProvenance(),
|
|
108
|
+
}),
|
|
109
|
+
getBoundSessionId: () => binding.status === 'bound' ? binding.sessionId : undefined,
|
|
110
|
+
getLocalEventMax: () => binding.status === 'bound' ? binding.store.localEventMax() : 0,
|
|
111
|
+
verifyControlAuthority,
|
|
112
|
+
acceptBoundConnectionGeneration: (connection) => {
|
|
113
|
+
if (binding.status !== 'bound')
|
|
114
|
+
throw new Error('Supervisor is not bound.')
|
|
115
|
+
binding.store.acceptConnectionAttempt({
|
|
116
|
+
connectionGeneration: connection.generation,
|
|
117
|
+
runtimeConnectionAttemptId: connection.runtimeConnectionAttemptId,
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
bindBootstrap,
|
|
121
|
+
onStartupStage: input.onStartupStage ?? (() => undefined),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
async function stop(options: { reason?: 'sigterm' | 'sigint' | 'fatal_error', exitCode?: number } = {}): Promise<void> {
|
|
125
|
+
if (stopped)
|
|
126
|
+
return
|
|
127
|
+
stopped = true
|
|
128
|
+
exitCode = options.exitCode ?? exitCode
|
|
129
|
+
await listener.stop()
|
|
130
|
+
await handler?.stop(options.reason)
|
|
131
|
+
if (binding.status === 'bound')
|
|
132
|
+
binding.store.close()
|
|
133
|
+
if (exitCode !== CLEAN_EXIT_CODE)
|
|
134
|
+
process.exitCode = exitCode
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
start: () => listener.start(),
|
|
139
|
+
stop,
|
|
140
|
+
port: () => listener.port(),
|
|
141
|
+
binding: () => binding.status,
|
|
142
|
+
}
|
|
143
|
+
}
|