@meistrari/agent-core 0.1.9 → 0.1.11
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/docs/mantle-plugin-discovery.md +27 -0
- package/docs/supervisor-interruption-recovery-2026-09-14.md +34 -0
- package/package.json +1 -1
- package/src/agents/codex/codex-provider.ts +10 -3
- package/src/provenance.gen.ts +3 -3
- package/src/steps/projector.ts +63 -2
- package/src/supervisor/agent-provider-factory.ts +14 -3
- package/src/supervisor/persistence/supervisor-store.ts +35 -1
- package/src/supervisor/provider-factory.ts +1 -0
- package/src/supervisor/runtime-handler.ts +6 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Repo-skills profiles and native plugin discovery
|
|
2
|
+
|
|
3
|
+
`CodexProvider.create({ pluginsEnabled: false, ... })` disables the native
|
|
4
|
+
`features.plugins` feature on every app-server connection, including model-change
|
|
5
|
+
reopening and session resume. This is independent of native shell tools and the
|
|
6
|
+
adapter's repository skill-root registration. Omit the option to preserve the
|
|
7
|
+
native default for other consumers; authentication does not implicitly disable it.
|
|
8
|
+
|
|
9
|
+
The pinned Codex source (`25af12f7e61572b0bc18ddb1008be543b91519b0`) enables plugins
|
|
10
|
+
by default. Its plugin manager starts curated repository synchronization for
|
|
11
|
+
non-Codex-backend authentication. The sync code includes GitHub and a
|
|
12
|
+
`https://chatgpt.com/backend-api/plugins/export/curated` archive fallback:
|
|
13
|
+
|
|
14
|
+
- [Native feature default](https://github.com/openai/codex/blob/25af12f7e61572b0bc18ddb1008be543b91519b0/codex-rs/features/src/lib.rs)
|
|
15
|
+
- [Startup-task guard](https://github.com/openai/codex/blob/25af12f7e61572b0bc18ddb1008be543b91519b0/codex-rs/core-plugins/src/manager.rs)
|
|
16
|
+
- [Curated synchronization transports](https://github.com/openai/codex/blob/25af12f7e61572b0bc18ddb1008be543b91519b0/codex-rs/core-plugins/src/startup_sync.rs)
|
|
17
|
+
|
|
18
|
+
This provides a plausible non-inference explanation for concurrent GitHub and
|
|
19
|
+
ChatGPT TLS handshakes observed during an agent-api Mantle turn. SNI alone does
|
|
20
|
+
not establish their HTTP routes or process ownership; do not claim attribution
|
|
21
|
+
or fallback exclusion solely from this source inspection.
|
|
22
|
+
|
|
23
|
+
The agent-api repo-skills profile should opt out explicitly after consuming an
|
|
24
|
+
exact release with this option. Rebuild its dedicated v5 image and re-onboard
|
|
25
|
+
immutable provenance, then repeat native skills, continuation, model changes and
|
|
26
|
+
bounded hostname observation. No existing image or bound session was changed by
|
|
27
|
+
this source edit. Other consumers, Claude and v4 retain their behavior.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Supervisor interruption recovery
|
|
2
|
+
|
|
3
|
+
Live Codex 0.1.10 validation reproduced a ready replacement connection with an
|
|
4
|
+
old command and tool still running after SIGKILL. No output marker was written.
|
|
5
|
+
Chat cancellation subsequently terminated the sandbox; its independent terminal
|
|
6
|
+
session reconciliation now finalizes the chat command.
|
|
7
|
+
|
|
8
|
+
Unreleased core changes persist active turn identity in the step projector.
|
|
9
|
+
When the provider factory reopens a persisted provider session, recovery first
|
|
10
|
+
drains the step journal, then journals explicit interrupted terminal facts for
|
|
11
|
+
unfinished turns. Step closure precedes the turn fact and retains incomplete
|
|
12
|
+
JSON details. Repeated recovery does not create another logical terminal fact.
|
|
13
|
+
A failure during emission reuses the saved frames and deterministic identity.
|
|
14
|
+
Ordinary reconnects to the same in-memory runtime do not trigger interruption.
|
|
15
|
+
|
|
16
|
+
This is not yet ready for release or acceptance:
|
|
17
|
+
|
|
18
|
+
- Dispatch receipts now fence the prompt-accepted / command-applied crash window
|
|
19
|
+
against resubmission. The new process atomically marks the command failed and
|
|
20
|
+
records that provider acceptance is uncertain. Agent-api now validates the
|
|
21
|
+
command-failure identity and projects failed prompt dispatch onto terminal
|
|
22
|
+
product/run state, with a pre-commit crash/deduplication Postgres regression.
|
|
23
|
+
- Recovery readiness now gates command dispatch and uncertain-failure receipts.
|
|
24
|
+
Child turns close before the main terminal fact. Barrier and child-ordering
|
|
25
|
+
regressions pass; lint/typecheck pass, with 340 full-suite tests plus the
|
|
26
|
+
subsequently added child-ordering regression verified in the projector suite.
|
|
27
|
+
- Missing starts, child turns, normal terminal sessions, provider readiness,
|
|
28
|
+
and failed oversized publication now have explicit regression coverage.
|
|
29
|
+
Failed uploads retain the identical JSON bytes/hash and do not emit a terminal
|
|
30
|
+
turn before publication succeeds.
|
|
31
|
+
- Repeat active crash validation using a new exact npm artifact and image.
|
|
32
|
+
- Do not infer that external tool side effects are exactly once.
|
|
33
|
+
|
|
34
|
+
No running template or bound sandbox was hot-updated with these source changes.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/agent-core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.11",
|
|
5
5
|
"packageManager": "bun@1.3.12",
|
|
6
6
|
"description": "Shared contracts and runtime modules for Tela coding-agent sandboxes: agent protocol, supervisor wire protocol, resident supervisor, worker runtime client, and Claude/Codex harness adapters.",
|
|
7
7
|
"license": "UNLICENSED",
|
|
@@ -44,6 +44,8 @@ export interface CodexProviderConfig {
|
|
|
44
44
|
instructions?: InstructionComposer
|
|
45
45
|
/** Service-owned public model to inference model mapping. */
|
|
46
46
|
resolveInferenceModel?: (model: string) => string
|
|
47
|
+
/** Native plugin/marketplace discovery. Does not disable repository skill roots. */
|
|
48
|
+
pluginsEnabled?: boolean
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
interface CodexRunHolder {
|
|
@@ -88,6 +90,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
|
|
|
88
90
|
private readonly instructions: InstructionComposer
|
|
89
91
|
private readonly resolveInferenceModel: (model: string) => string
|
|
90
92
|
private readonly hasInferenceModelMapping: boolean
|
|
93
|
+
private readonly pluginsEnabled: boolean | undefined
|
|
91
94
|
|
|
92
95
|
private constructor(config: CodexProviderConfig) {
|
|
93
96
|
this.auth = normalizeCodexAuthentication(config.auth)
|
|
@@ -98,6 +101,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
|
|
|
98
101
|
this.instructions = config.instructions ?? emptyInstructionComposer
|
|
99
102
|
this.resolveInferenceModel = config.resolveInferenceModel ?? (model => model)
|
|
100
103
|
this.hasInferenceModelMapping = config.resolveInferenceModel !== undefined
|
|
104
|
+
this.pluginsEnabled = config.pluginsEnabled
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
static create(config: CodexProviderConfig): CodexProvider {
|
|
@@ -386,9 +390,12 @@ export class CodexProvider implements AgentProvider<'codex'> {
|
|
|
386
390
|
client = new CodexJsonRpcClient(this.executable, {
|
|
387
391
|
cwd,
|
|
388
392
|
environment: this.environment,
|
|
389
|
-
configOverrides:
|
|
390
|
-
|
|
391
|
-
|
|
393
|
+
configOverrides: [
|
|
394
|
+
...(this.auth.kind === 'amazon-bedrock'
|
|
395
|
+
? ['cli_auth_credentials_store="ephemeral"', 'model_provider="amazon-bedrock"', `model_providers.amazon-bedrock.aws.region=${JSON.stringify(this.auth.region)}`, 'model_providers.amazon-bedrock.aws.wire_api="responses"']
|
|
396
|
+
: []),
|
|
397
|
+
...(this.pluginsEnabled === undefined ? [] : [`features.plugins=${this.pluginsEnabled}`]),
|
|
398
|
+
],
|
|
392
399
|
onMessage: (message) => {
|
|
393
400
|
messageQueue = this.enqueueMessageHandling(messageQueue, { message, holder, state, client, signal: abortController.signal })
|
|
394
401
|
},
|
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.1.
|
|
4
|
-
sha: "
|
|
5
|
-
buildTime: "2026-09-
|
|
3
|
+
version: "0.1.11",
|
|
4
|
+
sha: "8a060e09b239547c1fb7731ab7e73d2439619a38",
|
|
5
|
+
buildTime: "2026-09-14T17:32:26.510Z",
|
|
6
6
|
} as const
|
package/src/steps/projector.ts
CHANGED
|
@@ -23,6 +23,7 @@ export class DurableStepProjector {
|
|
|
23
23
|
this.db.exec(`pragma journal_mode=WAL; pragma synchronous=FULL;
|
|
24
24
|
create table if not exists steps(id text primary key, body text not null, last_event_id text);
|
|
25
25
|
create table if not exists journal(id text primary key, event text, frames text);
|
|
26
|
+
create table if not exists active_turns(identity text primary key, event text not null);
|
|
26
27
|
create table if not exists turn_commands(
|
|
27
28
|
session_id text not null, provider_session_id text not null,
|
|
28
29
|
actor_id text not null, turn_id text not null, command_id text not null,
|
|
@@ -39,15 +40,63 @@ export class DurableStepProjector {
|
|
|
39
40
|
|
|
40
41
|
close(): void { this.db.close() }
|
|
41
42
|
|
|
42
|
-
async recover(emit: (body: WireEventBody) => void): Promise<void> {
|
|
43
|
+
async recover(emit: (body: WireEventBody) => void, options?: { interruptUnfinishedTurns: boolean }): Promise<void> {
|
|
43
44
|
for (;;) {
|
|
44
45
|
const row = this.db.query<{ id: string, event: string, frames: string | null }, []>(
|
|
45
46
|
'select id, event, frames from journal where event is not null order by rowid limit 1',
|
|
46
47
|
).get()
|
|
47
48
|
if (!row)
|
|
48
|
-
|
|
49
|
+
break
|
|
49
50
|
await this.deliver(row.id, JSON.parse(row.event) as AgentEvent, row.frames, emit)
|
|
50
51
|
}
|
|
52
|
+
if (!options?.interruptUnfinishedTurns)
|
|
53
|
+
return
|
|
54
|
+
// A provider can expose a tool/message without a turn-start receipt.
|
|
55
|
+
// Recover its known identity from unfinished assembly, never fabricate
|
|
56
|
+
// missing content or emit a synthetic successful start.
|
|
57
|
+
for (;;) {
|
|
58
|
+
const orphan = this.db.query<{ body: string }, []>(`
|
|
59
|
+
select body from steps where json_extract(body,'$.status')='running'
|
|
60
|
+
and not exists (select 1 from active_turns where identity=json_array(
|
|
61
|
+
json_extract(body,'$.sessionId'),json_extract(body,'$.providerSessionId'),
|
|
62
|
+
json_extract(body,'$.actor.actorId'),json_extract(body,'$.turnId')))
|
|
63
|
+
order by id limit 1
|
|
64
|
+
`).get()
|
|
65
|
+
if (!orphan)
|
|
66
|
+
break
|
|
67
|
+
const step = JSON.parse(orphan.body) as StepDetailV1
|
|
68
|
+
const identity = JSON.stringify([step.sessionId, step.providerSessionId, step.actor.actorId, step.turnId])
|
|
69
|
+
this.db.query('insert into active_turns(identity,event) values (?,?)').run(identity, JSON.stringify({
|
|
70
|
+
type: 'turn.started',
|
|
71
|
+
eventId: `recovered-context:${step.stepId}`,
|
|
72
|
+
sequence: 0,
|
|
73
|
+
provider: step.provider,
|
|
74
|
+
sessionId: step.sessionId,
|
|
75
|
+
providerSessionId: step.providerSessionId,
|
|
76
|
+
actor: step.actor,
|
|
77
|
+
turnId: step.turnId,
|
|
78
|
+
timestamp: step.startedAt,
|
|
79
|
+
payload: {},
|
|
80
|
+
}))
|
|
81
|
+
}
|
|
82
|
+
// A newly opened local provider process cannot own the old active turn.
|
|
83
|
+
// Journal synthetic terminal facts before emitting; never resend prompts.
|
|
84
|
+
for (;;) {
|
|
85
|
+
const row = this.db.query<{ identity: string, event: string }, []>(`
|
|
86
|
+
select identity,event from active_turns
|
|
87
|
+
order by (json_extract(event,'$.actor.type')='main'), identity limit 1
|
|
88
|
+
`).get()
|
|
89
|
+
if (!row)
|
|
90
|
+
return
|
|
91
|
+
const previous = JSON.parse(row.event) as Extract<AgentEvent, { type: 'turn.started' }>
|
|
92
|
+
await this.process({
|
|
93
|
+
...previous,
|
|
94
|
+
eventId: `supervisor-recovery:${createHash('sha256').update(row.identity).digest('hex')}`,
|
|
95
|
+
type: 'turn.ended',
|
|
96
|
+
timestamp: new Date().toISOString(),
|
|
97
|
+
payload: { status: 'interrupted', reason: 'supervisor_process_restarted' },
|
|
98
|
+
}, emit)
|
|
99
|
+
}
|
|
51
100
|
}
|
|
52
101
|
|
|
53
102
|
async process(event: AgentEvent, emit: (body: WireEventBody) => void): Promise<void> {
|
|
@@ -92,6 +141,10 @@ export class DurableStepProjector {
|
|
|
92
141
|
|
|
93
142
|
private async project(event: AgentEvent): Promise<WireEventBody[]> {
|
|
94
143
|
const commandId = this.correlateCommand(event)
|
|
144
|
+
if (event.type === 'turn.started') {
|
|
145
|
+
this.db.query('insert into active_turns(identity,event) values (?,?) on conflict(identity) do nothing')
|
|
146
|
+
.run(JSON.stringify([event.sessionId, event.providerSessionId, event.actor.actorId, event.turnId]), JSON.stringify(event))
|
|
147
|
+
}
|
|
95
148
|
const kind = event.type.startsWith('message.')
|
|
96
149
|
? 'message'
|
|
97
150
|
: event.type.startsWith('tool.call.')
|
|
@@ -113,6 +166,14 @@ export class DurableStepProjector {
|
|
|
113
166
|
}
|
|
114
167
|
}
|
|
115
168
|
frames.push(wrapAgentEvent(event))
|
|
169
|
+
if (event.type === 'turn.ended') {
|
|
170
|
+
this.db.query('delete from active_turns where identity=?')
|
|
171
|
+
.run(JSON.stringify([event.sessionId, event.providerSessionId, event.actor.actorId, event.turnId]))
|
|
172
|
+
}
|
|
173
|
+
if (event.type === 'session.ended') {
|
|
174
|
+
this.db.query('delete from active_turns where json_extract(event,\'$.sessionId\')=? and json_extract(event,\'$.providerSessionId\')=?')
|
|
175
|
+
.run(event.sessionId, event.providerSessionId)
|
|
176
|
+
}
|
|
116
177
|
return frames
|
|
117
178
|
}
|
|
118
179
|
const payload = event.payload as Record<string, unknown>
|
|
@@ -17,7 +17,7 @@ export interface AgentProviderBindingStore {
|
|
|
17
17
|
|
|
18
18
|
export interface AgentSupervisorProviderFactoryOptions {
|
|
19
19
|
eventProcessor?: {
|
|
20
|
-
recover: (emit: (body: WireEventBody) => void) => Promise<void>
|
|
20
|
+
recover: (emit: (body: WireEventBody) => void, options?: { interruptUnfinishedTurns: boolean }) => Promise<void>
|
|
21
21
|
process: (event: AgentEvent, emit: (body: WireEventBody) => void) => Promise<void>
|
|
22
22
|
}
|
|
23
23
|
createProvider: (input: {
|
|
@@ -76,6 +76,7 @@ export function createAgentSupervisorProviderFactory(
|
|
|
76
76
|
emit: input.emit,
|
|
77
77
|
onFatal: input.onFatal,
|
|
78
78
|
eventProcessor: options.eventProcessor,
|
|
79
|
+
interruptUnfinishedTurns: Boolean(previousProviderSessionId),
|
|
79
80
|
prepareInputAttachments: options.prepareInputAttachments,
|
|
80
81
|
onSecretsRefreshed: options.onSecretsRefreshed,
|
|
81
82
|
})
|
|
@@ -84,6 +85,7 @@ export function createAgentSupervisorProviderFactory(
|
|
|
84
85
|
|
|
85
86
|
class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
86
87
|
private readonly attached = Promise.withResolvers<void>()
|
|
88
|
+
private readonly recovered = Promise.withResolvers<void>()
|
|
87
89
|
private state: SupervisorAgentRunSnapshot = { status: 'attached', agentState: null }
|
|
88
90
|
private emit: ((body: WireEventBody) => void) | undefined
|
|
89
91
|
private readonly pendingEvents: WireEventBody[] = []
|
|
@@ -95,12 +97,17 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
95
97
|
emit?: (body: WireEventBody) => void
|
|
96
98
|
onFatal?: (error: unknown) => void
|
|
97
99
|
eventProcessor?: AgentSupervisorProviderFactoryOptions['eventProcessor']
|
|
100
|
+
interruptUnfinishedTurns?: boolean
|
|
98
101
|
prepareInputAttachments?: AgentSupervisorProviderFactoryOptions['prepareInputAttachments']
|
|
99
102
|
onSecretsRefreshed?: AgentSupervisorProviderFactoryOptions['onSecretsRefreshed']
|
|
100
103
|
}) {
|
|
104
|
+
// Recovery may fail before the owner awaits ready; keep the rejection
|
|
105
|
+
// observed while preserving it for the readiness/command boundary.
|
|
106
|
+
void this.recovered.promise.catch(() => undefined)
|
|
101
107
|
if (input.emit)
|
|
102
108
|
this.attachEmitter(input.emit)
|
|
103
109
|
this.eventPump = this.consumeEvents().catch((error: unknown) => {
|
|
110
|
+
this.recovered.reject(error)
|
|
104
111
|
this.eventFailure = error
|
|
105
112
|
input.onFatal?.(error)
|
|
106
113
|
})
|
|
@@ -110,10 +117,13 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
110
117
|
return this.state
|
|
111
118
|
}
|
|
112
119
|
|
|
120
|
+
async ready(): Promise<void> { await this.recovered.promise }
|
|
121
|
+
|
|
113
122
|
async handle(input: Parameters<SupervisorAgentRuntime['handle']>[0]): Promise<void> {
|
|
123
|
+
this.attachEmitter(input.emit)
|
|
124
|
+
await this.ready()
|
|
114
125
|
if (this.eventFailure)
|
|
115
126
|
throw this.eventFailure
|
|
116
|
-
this.attachEmitter(input.emit)
|
|
117
127
|
const metadata = this.input.run.metadata
|
|
118
128
|
if (input.body.type === 'agent.send-prompt') {
|
|
119
129
|
const inputAttachmentPreparation = await this.input.prepareInputAttachments?.({
|
|
@@ -198,8 +208,9 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
198
208
|
await this.attached.promise
|
|
199
209
|
if (!this.emit)
|
|
200
210
|
return
|
|
201
|
-
await this.input.eventProcessor.recover(body => this.emit!(body))
|
|
211
|
+
await this.input.eventProcessor.recover(body => this.emit!(body), { interruptUnfinishedTurns: Boolean(this.input.interruptUnfinishedTurns) })
|
|
202
212
|
}
|
|
213
|
+
this.recovered.resolve()
|
|
203
214
|
for await (const event of this.input.run.events) {
|
|
204
215
|
if (event.type === 'session.state.changed')
|
|
205
216
|
this.state = { status: 'attached', agentState: event.payload.state }
|
|
@@ -65,6 +65,7 @@ export class SupervisorStore {
|
|
|
65
65
|
store.initializeSchema()
|
|
66
66
|
else if (version !== schemaVersion)
|
|
67
67
|
throw new Error(`Unsupported supervisor SQLite schema version: ${version}.`)
|
|
68
|
+
store.database.exec('CREATE TABLE IF NOT EXISTS command_dispatches (seq INTEGER PRIMARY KEY REFERENCES commands(seq))')
|
|
68
69
|
return store
|
|
69
70
|
}
|
|
70
71
|
catch (cause) {
|
|
@@ -203,7 +204,40 @@ export class SupervisorStore {
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
markCommand(input: { commandSeq: number, status: 'applied' | 'failed' }): void {
|
|
206
|
-
this.database.
|
|
207
|
+
this.database.transaction(() => {
|
|
208
|
+
this.database.query(`UPDATE commands SET process_status = ? WHERE seq = ?`).run(input.status, input.commandSeq)
|
|
209
|
+
this.database.query('DELETE FROM command_dispatches WHERE seq = ?').run(input.commandSeq)
|
|
210
|
+
})()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Commit before calling a provider: a process crash makes the outcome uncertain. */
|
|
214
|
+
beginCommandDispatch(commandSeq: number): void {
|
|
215
|
+
this.database.query('INSERT INTO command_dispatches(seq) VALUES (?)').run(commandSeq)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** New process only. Receipt and failure share a transaction; no prompt retry. */
|
|
219
|
+
recoverCommandDispatches(): void {
|
|
220
|
+
this.database.transaction(() => {
|
|
221
|
+
const rows = this.database.query<CommandRow, []>(`
|
|
222
|
+
SELECT command_id, commands.seq, body_json, body_hash, process_status FROM commands
|
|
223
|
+
JOIN command_dispatches ON command_dispatches.seq=commands.seq ORDER BY commands.seq
|
|
224
|
+
`).all()
|
|
225
|
+
for (const row of rows) {
|
|
226
|
+
this.appendDurableEvent({
|
|
227
|
+
occurredAt: new Date().toISOString(),
|
|
228
|
+
body: {
|
|
229
|
+
type: 'supervisor.command.failed',
|
|
230
|
+
payload: {
|
|
231
|
+
commandId: row.command_id,
|
|
232
|
+
commandSeq: row.seq,
|
|
233
|
+
bodyType: commandBodyFromRow(row).type,
|
|
234
|
+
failure: { type: 'command', detail: 'Supervisor restarted during dispatch; provider acceptance is uncertain. Command was not resubmitted.' },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
this.markCommand({ commandSeq: row.seq, status: 'failed' })
|
|
239
|
+
}
|
|
240
|
+
})()
|
|
207
241
|
}
|
|
208
242
|
|
|
209
243
|
lastReceivedCommandSeq(): number {
|
|
@@ -5,6 +5,7 @@ import type { SupervisorAgentRunSnapshot } from '../supervisor-protocol/supervis
|
|
|
5
5
|
import type { SupervisorRpcClient } from './rpc-client'
|
|
6
6
|
|
|
7
7
|
export interface SupervisorAgentRuntime {
|
|
8
|
+
ready?: () => Promise<void>
|
|
8
9
|
snapshot: () => SupervisorAgentRunSnapshot
|
|
9
10
|
handle: (input: {
|
|
10
11
|
commandId: string
|
|
@@ -188,6 +188,11 @@ export class SupervisorRuntimeHandler implements RuntimeConnectionHandler {
|
|
|
188
188
|
emit: body => this.emit(body),
|
|
189
189
|
onFatal: error => this.dependencies.onFatal(error),
|
|
190
190
|
}).then(async (agent) => {
|
|
191
|
+
await agent.ready?.()
|
|
192
|
+
const previousEventMax = this.dependencies.store.localEventMax()
|
|
193
|
+
this.dependencies.store.recoverCommandDispatches()
|
|
194
|
+
for (const event of this.dependencies.store.eventsAfter(previousEventMax))
|
|
195
|
+
this.sendEvent(event)
|
|
191
196
|
if ((this.dependencies.credentials !== initialSecrets.credentials
|
|
192
197
|
|| this.dependencies.gitToken !== initialSecrets.gitToken) && agent.refreshSecrets) {
|
|
193
198
|
await agent.refreshSecrets({
|
|
@@ -210,6 +215,7 @@ export class SupervisorRuntimeHandler implements RuntimeConnectionHandler {
|
|
|
210
215
|
command: CommandEnvelope & { body: WireCommandBody }
|
|
211
216
|
}): Promise<void> {
|
|
212
217
|
try {
|
|
218
|
+
this.dependencies.store.beginCommandDispatch(input.command.commandSeq)
|
|
213
219
|
await input.agent.handle({
|
|
214
220
|
commandId: input.command.commandId,
|
|
215
221
|
commandSeq: input.command.commandSeq,
|