@meistrari/agent-core 0.2.0-experimental.0 → 0.2.0-experimental.2
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 +82 -0
- package/docs/consumer-capability-matrix.md +374 -0
- package/package.json +17 -10
- package/scripts/generate-codex-protocol.ts +711 -0
- package/src/agents/agent-operation.ts +8 -0
- package/src/agents/agent-provider.ts +3 -0
- package/src/agents/agent-run.ts +14 -2
- package/src/agents/agent-runtime-error.ts +28 -0
- package/src/agents/agent-session-events.ts +33 -0
- package/src/agents/agent-tool-runner.ts +12 -11
- package/src/agents/agent-tool.ts +12 -4
- package/src/agents/author-context.ts +1 -1
- package/src/agents/claude/claude-auth.ts +40 -0
- package/src/agents/claude/claude-browser-turn-metadata.ts +70 -0
- package/src/agents/claude/claude-code-process.ts +98 -0
- package/src/agents/claude/claude-command-mapper.ts +63 -3
- package/src/agents/claude/claude-desktop.ts +55 -0
- package/src/agents/claude/claude-event-mapper.ts +876 -124
- package/src/agents/claude/claude-file-change.ts +128 -0
- package/src/agents/claude/claude-provider.ts +344 -29
- package/src/agents/claude/claude-run.ts +922 -91
- package/src/agents/claude/claude-selected-repository-instructions.ts +91 -0
- package/src/agents/claude/claude-skill-catalog.ts +75 -0
- package/src/agents/claude/claude-steered-notifications.ts +180 -0
- package/src/agents/claude/claude-tool-mapper.ts +81 -2
- package/src/agents/claude/claude-workspace-skills.ts +87 -0
- package/src/agents/claude/index.ts +2 -0
- package/src/agents/codex/codex-auth-mapper.ts +27 -0
- package/src/agents/codex/codex-auth.ts +19 -0
- package/src/agents/codex/codex-browser-auth-broker.ts +320 -0
- package/src/agents/codex/codex-browser-auth-status.ts +55 -0
- package/src/agents/codex/codex-command-mapper.ts +38 -3
- package/src/agents/codex/codex-event-mapper.ts +375 -64
- package/src/agents/codex/codex-json-rpc-client.ts +66 -21
- package/src/agents/codex/codex-provider.ts +118 -236
- package/src/agents/codex/codex-run-auth.ts +113 -0
- package/src/agents/codex/codex-run.ts +428 -48
- package/src/agents/codex/codex-tool-mapper.ts +2 -2
- package/src/agents/codex/codex.errors.ts +8 -0
- package/src/agents/codex/generated/meta.gen.ts +144 -6
- package/src/agents/codex/generated/namespaces.gen.ts +92 -3
- package/src/agents/codex/generated/schema.gen.ts +24698 -15638
- package/src/agents/codex/lazy-codex-run-auth.ts +47 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/input-attachment-preparation.ts +13 -0
- package/src/agents/mcp-tool-content.ts +82 -0
- package/src/agents/model-rebinding-run.ts +14 -3
- package/src/agents/selected-repository-instructions.ts +202 -0
- package/src/agents/session-context.ts +41 -0
- package/src/agents/subscription-token-refresh.ts +73 -0
- package/src/agents/user-input-request.ts +19 -20
- package/src/errors/index.ts +1 -1
- package/src/errors/validation-error.ts +6 -0
- package/src/protocol/agent-catalog.ts +159 -0
- package/src/protocol/agent-command.ts +54 -7
- package/src/protocol/agent-content.ts +36 -16
- package/src/protocol/agent-event.ts +210 -73
- package/src/protocol/agent-identity.ts +29 -0
- package/src/protocol/agent-json.ts +122 -8
- package/src/protocol/agent-metadata.ts +2 -2
- package/src/protocol/agent-model.ts +13 -1
- package/src/protocol/agent-notification.ts +110 -0
- package/src/protocol/agent-presentation.ts +71 -20
- package/src/protocol/agent-tool.ts +66 -10
- package/src/protocol/agent-usage.ts +4 -1
- package/src/protocol/agent-user-input.ts +4 -4
- package/src/protocol/agent-work.ts +12 -10
- package/src/protocol/index.ts +2 -0
- package/src/protocol/rejected-keys.ts +11 -0
- package/src/provenance.gen.ts +3 -3
- package/src/steps/contract.ts +4 -1
- package/src/steps/projector.ts +17 -1
- package/src/supervisor/agent-provider-factory.ts +60 -5
- package/src/supervisor/bootstrap-binder.ts +3 -2
- package/src/supervisor/index.ts +1 -1
- package/src/supervisor/persistence/supervisor-store.ts +22 -8
- package/src/supervisor/runtime-handler.ts +30 -2
- package/src/supervisor-protocol/agent-event-wrapper.ts +30 -0
- package/src/supervisor-protocol/bootstrap.ts +4 -1
- package/src/supervisor-protocol/command-body.ts +4 -1
- package/src/supervisor-protocol/envelopes/supervisor-to-control-plane.ts +1 -0
- package/src/testing/in-memory-runtime-control-plane.ts +118 -12
- package/src/testing/index.ts +1 -1
- package/src/worker-runtime-client/command-pump.ts +85 -10
- package/src/worker-runtime-client/connection-attempt.ts +95 -20
- package/src/worker-runtime-client/frame-processor.ts +242 -58
- package/src/worker-runtime-client/index.ts +8 -2
- package/src/worker-runtime-client/ports.ts +34 -2
- package/src/worker-runtime-client/rpc-request-manager.ts +152 -107
- package/src/worker-runtime-client/sandbox-connection-runtime.ts +72 -35
package/src/steps/contract.ts
CHANGED
|
@@ -81,8 +81,11 @@ export function splitStepText(text: string): string[] {
|
|
|
81
81
|
|
|
82
82
|
export function stepPreview(value: unknown): string {
|
|
83
83
|
const text = (typeof value === 'string' ? value : JSON.stringify(value) ?? '').replaceAll('\0', '\\0')
|
|
84
|
+
if (Buffer.byteLength(JSON.stringify(text)) <= 2048)
|
|
85
|
+
return text
|
|
84
86
|
let preview = ''
|
|
85
|
-
|
|
87
|
+
// Include the JSON quotes and truncation marker in the same preview budget.
|
|
88
|
+
let bytes = Buffer.byteLength(JSON.stringify('…'))
|
|
86
89
|
for (const character of text) {
|
|
87
90
|
const size = Buffer.byteLength(JSON.stringify(character)) - 2
|
|
88
91
|
if (bytes + size > 2048)
|
package/src/steps/projector.ts
CHANGED
|
@@ -272,8 +272,24 @@ export class DurableStepProjector {
|
|
|
272
272
|
).get(...key)?.command_id
|
|
273
273
|
const payload = 'payload' in event ? event.payload as Record<string, unknown> : event
|
|
274
274
|
const explicit = typeof payload.commandId === 'string' ? payload.commandId : undefined
|
|
275
|
-
if (previous && explicit && previous !== explicit)
|
|
275
|
+
if (previous && explicit && previous !== explicit) {
|
|
276
|
+
if ('type' in event && event.actor.type === 'main' && payload.role === 'user'
|
|
277
|
+
&& (event.type === 'message.started' || event.type === 'message.ended') && 'messageId' in payload && typeof payload.messageId === 'string') {
|
|
278
|
+
const messageStepId = stepIdFor({ sessionId: event.sessionId, providerSessionId: event.providerSessionId, actor: event.actor, turnId: event.turnId, kind: 'message', sourceId: payload.messageId })
|
|
279
|
+
const known = this.db.query<{ command_id: string | null }, [string]>(
|
|
280
|
+
'select json_extract(body,\'$.commandId\') as command_id from steps where id=?',
|
|
281
|
+
).get(messageStepId)
|
|
282
|
+
// Delayed/repeated facts for an older message must not rewind the turn's current command.
|
|
283
|
+
if (known?.command_id === explicit)
|
|
284
|
+
return explicit
|
|
285
|
+
if (!known && event.type === 'message.started') {
|
|
286
|
+
this.db.query('update turn_commands set command_id=? where session_id=? and provider_session_id=? and actor_id=? and turn_id=?')
|
|
287
|
+
.run(explicit, ...key)
|
|
288
|
+
return explicit
|
|
289
|
+
}
|
|
290
|
+
}
|
|
276
291
|
throw new Error('Conflicting command identity for a provider turn.')
|
|
292
|
+
}
|
|
277
293
|
let inherited: string | undefined
|
|
278
294
|
if (!previous && !explicit && event.actor.type === 'subagent') {
|
|
279
295
|
const toolCallId = event.actor.parentToolCallId
|
|
@@ -7,6 +7,7 @@ import type { WireEventBody } from '../supervisor-protocol/event-body'
|
|
|
7
7
|
import type { SupervisorAgentRunSnapshot } from '../supervisor-protocol/supervisor-agent-run-snapshot'
|
|
8
8
|
import type { SupervisorAgentRuntime, SupervisorProviderFactory } from './provider-factory'
|
|
9
9
|
import type { SupervisorRpcClient } from './rpc-client'
|
|
10
|
+
import { Database } from 'bun:sqlite'
|
|
10
11
|
import { wrapAgentEvent } from '../supervisor-protocol/agent-event-wrapper'
|
|
11
12
|
import { createSupervisorProviderFactory } from './provider-factory'
|
|
12
13
|
|
|
@@ -15,6 +16,40 @@ export interface AgentProviderBindingStore {
|
|
|
15
16
|
save: (input: { sessionId: string, provider: AgentProviderId, providerSessionId: string }) => Promise<void>
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
/** Local provider identity only; credentials and transcripts do not belong in this store. */
|
|
20
|
+
export class SqliteAgentProviderBindingStore implements AgentProviderBindingStore {
|
|
21
|
+
private readonly db: Database
|
|
22
|
+
|
|
23
|
+
constructor(path: string) {
|
|
24
|
+
this.db = new Database(path, { create: true })
|
|
25
|
+
this.db.exec(`pragma journal_mode=WAL; pragma synchronous=FULL;
|
|
26
|
+
create table if not exists provider_bindings(
|
|
27
|
+
session_id text not null,
|
|
28
|
+
provider text not null,
|
|
29
|
+
provider_session_id text not null,
|
|
30
|
+
primary key(session_id, provider)
|
|
31
|
+
);`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async load(input: { sessionId: string, provider: AgentProviderId }): Promise<string | undefined> {
|
|
35
|
+
return this.db.query<{ provider_session_id: string }, [string, string]>(
|
|
36
|
+
'select provider_session_id from provider_bindings where session_id=? and provider=?',
|
|
37
|
+
).get(input.sessionId, input.provider)?.provider_session_id
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async save(input: { sessionId: string, provider: AgentProviderId, providerSessionId: string }): Promise<void> {
|
|
41
|
+
if (!input.sessionId || !input.providerSessionId)
|
|
42
|
+
throw new Error('Provider binding requires session and provider session identities.')
|
|
43
|
+
this.db.query(`insert into provider_bindings(session_id,provider,provider_session_id) values (?,?,?)
|
|
44
|
+
on conflict(session_id,provider) do update set provider_session_id=excluded.provider_session_id`)
|
|
45
|
+
.run(input.sessionId, input.provider, input.providerSessionId)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
close(): void {
|
|
49
|
+
this.db.close()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
18
53
|
export interface AgentSupervisorProviderFactoryOptions {
|
|
19
54
|
eventProcessor?: {
|
|
20
55
|
recover: (emit: (body: WireEventBody) => void, options?: { interruptUnfinishedTurns: boolean }) => Promise<void>
|
|
@@ -65,11 +100,28 @@ export function createAgentSupervisorProviderFactory(
|
|
|
65
100
|
? await provider.resumeSession({ ...sessionInput, provider: 'codex', providerSessionId: previousProviderSessionId }, { signal: input.signal })
|
|
66
101
|
: await provider.openSession({ ...sessionInput, provider: 'codex' }, { signal: input.signal })
|
|
67
102
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
try {
|
|
104
|
+
await options.bindingStore.save({
|
|
105
|
+
sessionId: input.bootstrap.sessionId,
|
|
106
|
+
provider: provider.id,
|
|
107
|
+
providerSessionId: run.metadata.providerSessionId,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
try {
|
|
112
|
+
await run.stop({
|
|
113
|
+
type: 'agent.stop',
|
|
114
|
+
provider: run.metadata.provider,
|
|
115
|
+
sessionId: run.metadata.sessionId,
|
|
116
|
+
providerSessionId: run.metadata.providerSessionId,
|
|
117
|
+
reason: 'provider_binding_persistence_failed',
|
|
118
|
+
}, { signal: new AbortController().signal })
|
|
119
|
+
}
|
|
120
|
+
catch (cleanupError) {
|
|
121
|
+
throw new AggregateError([error, cleanupError], 'Provider binding persistence and cleanup failed.')
|
|
122
|
+
}
|
|
123
|
+
throw error
|
|
124
|
+
}
|
|
73
125
|
|
|
74
126
|
return new AgentSupervisorRuntime({
|
|
75
127
|
run,
|
|
@@ -138,7 +190,9 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
138
190
|
providerSessionId: metadata.providerSessionId,
|
|
139
191
|
commandId: input.commandId,
|
|
140
192
|
mode: input.body.mode,
|
|
193
|
+
turnId: input.body.turnId,
|
|
141
194
|
model: input.body.model,
|
|
195
|
+
reasoningEffort: input.body.reasoningEffort,
|
|
142
196
|
origin: 'user',
|
|
143
197
|
prompt: input.body.prompt,
|
|
144
198
|
author: input.body.user
|
|
@@ -158,6 +212,7 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
158
212
|
if (input.body.type === 'agent.interrupt') {
|
|
159
213
|
await this.input.run.interrupt({
|
|
160
214
|
type: 'agent.interrupt',
|
|
215
|
+
turnId: input.body.turnId,
|
|
161
216
|
provider: metadata.provider,
|
|
162
217
|
sessionId: metadata.sessionId,
|
|
163
218
|
providerSessionId: metadata.providerSessionId,
|
|
@@ -51,8 +51,8 @@ export function createBootstrapBinder(input: {
|
|
|
51
51
|
outcome: 'rejected',
|
|
52
52
|
ack: {
|
|
53
53
|
kind: 'command.ack',
|
|
54
|
-
commandId: request.envelope.body.initialCommand.
|
|
55
|
-
commandSeq: request.envelope.body.initialCommand
|
|
54
|
+
commandId: request.envelope.body.initialCommand?.commandId ?? `bootstrap:${request.envelope.body.sessionId}`,
|
|
55
|
+
commandSeq: request.envelope.body.initialCommand?.commandSeq ?? 1,
|
|
56
56
|
status: 'rejected',
|
|
57
57
|
errorCode: error.code,
|
|
58
58
|
detail: error.publicMessage.slice(0, 240),
|
|
@@ -123,6 +123,7 @@ export function createBootstrapBinder(input: {
|
|
|
123
123
|
outcome: 'accepted',
|
|
124
124
|
runtimeState: {
|
|
125
125
|
kind: 'runtime.state',
|
|
126
|
+
...(request.envelope.body.commandTransport === 'wss-v1' ? { sourceId: acceptedBinding.store.journalSourceId() } : {}),
|
|
126
127
|
lastReceivedCommandSeq: acceptedBinding.store.lastReceivedCommandSeq(),
|
|
127
128
|
nextEventSeq: acceptedBinding.store.localEventMax() + 1,
|
|
128
129
|
agentRun: acceptedBinding.store.agentRunSnapshot(),
|
package/src/supervisor/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createAgentSupervisorProviderFactory } from './agent-provider-factory'
|
|
1
|
+
export { createAgentSupervisorProviderFactory, SqliteAgentProviderBindingStore } from './agent-provider-factory'
|
|
2
2
|
export type { AgentProviderBindingStore, AgentSupervisorProviderFactoryOptions } from './agent-provider-factory'
|
|
3
3
|
export {
|
|
4
4
|
CLEAN_EXIT_CODE,
|
|
@@ -46,6 +46,9 @@ export interface PersistedSupervisorBinding {
|
|
|
46
46
|
runtimeConnectionAttemptId: string
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** A command whose sequence is already journaled under a different identity or body. */
|
|
50
|
+
export const commandSequenceConflictCode = 'agent-core.command-sequence-conflict'
|
|
51
|
+
|
|
49
52
|
export class SupervisorStore {
|
|
50
53
|
private closed = false
|
|
51
54
|
|
|
@@ -113,12 +116,14 @@ export class SupervisorStore {
|
|
|
113
116
|
now,
|
|
114
117
|
)
|
|
115
118
|
this.insertAttempt(input)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
if (input.body.initialCommand) {
|
|
120
|
+
this.insertCommand({
|
|
121
|
+
kind: 'command',
|
|
122
|
+
commandId: input.body.initialCommand.commandId,
|
|
123
|
+
commandSeq: input.body.initialCommand.commandSeq,
|
|
124
|
+
body: input.body.initialCommand.body,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
122
127
|
return {
|
|
123
128
|
body: input.body,
|
|
124
129
|
bodyHash,
|
|
@@ -175,12 +180,12 @@ export class SupervisorStore {
|
|
|
175
180
|
if (existing.command_id !== envelope.commandId
|
|
176
181
|
|| existing.body_hash !== sha256CanonicalJson(body)
|
|
177
182
|
|| existing.body_json !== canonicalJson(body)) {
|
|
178
|
-
throw new SupervisorPersistenceError({ message: 'Command sequence conflicts with durable inbox state.' })
|
|
183
|
+
throw new SupervisorPersistenceError({ code: commandSequenceConflictCode, message: 'Command sequence conflicts with durable inbox state.' })
|
|
179
184
|
}
|
|
180
185
|
return { status: 'received', duplicate: true }
|
|
181
186
|
}
|
|
182
187
|
const maximum = this.lastReceivedCommandSeq()
|
|
183
|
-
if (envelope.commandSeq !== maximum + 1)
|
|
188
|
+
if (this.binding()?.body.commandTransport !== 'wss-v1' && envelope.commandSeq !== maximum + 1)
|
|
184
189
|
throw new SupervisorPersistenceError({ message: 'Command sequence contains a gap.' })
|
|
185
190
|
try {
|
|
186
191
|
this.insertCommand({ ...envelope, body })
|
|
@@ -240,6 +245,15 @@ export class SupervisorStore {
|
|
|
240
245
|
})()
|
|
241
246
|
}
|
|
242
247
|
|
|
248
|
+
/** Stable identity of this SQLite journal, independent of socket and worker. */
|
|
249
|
+
journalSourceId(): string {
|
|
250
|
+
this.database.query('INSERT OR IGNORE INTO metadata (key, value) VALUES (\'journal_source_id\', ?)').run(crypto.randomUUID())
|
|
251
|
+
const row = this.database.query<{ value: string }, []>('SELECT value FROM metadata WHERE key = \'journal_source_id\'').get()
|
|
252
|
+
if (!row)
|
|
253
|
+
throw new SupervisorPersistenceError({ message: 'Missing journal identity.' })
|
|
254
|
+
return row.value
|
|
255
|
+
}
|
|
256
|
+
|
|
243
257
|
lastReceivedCommandSeq(): number {
|
|
244
258
|
return this.database.query<{ maximum: number }, []>('SELECT COALESCE(MAX(seq), 0) AS maximum FROM commands').get()?.maximum ?? 0
|
|
245
259
|
}
|
|
@@ -6,12 +6,15 @@ import type { EventEnvelope } from '../supervisor-protocol/envelopes/supervisor-
|
|
|
6
6
|
import type { WireEventBody } from '../supervisor-protocol/event-body'
|
|
7
7
|
import type { SupervisorStore } from './persistence/supervisor-store'
|
|
8
8
|
import type { SupervisorAgentRuntime, SupervisorProviderFactory } from './provider-factory'
|
|
9
|
+
import { ZodError } from 'zod'
|
|
9
10
|
import { jsonUtf8ByteLength } from '../protocol'
|
|
10
11
|
import { durabilityOf } from '../supervisor-protocol/durability'
|
|
11
12
|
import { wireEventBodySchema } from '../supervisor-protocol/event-body'
|
|
12
13
|
import { maxDurableEventBytes, maxEphemeralDeltaBytes } from '../supervisor-protocol/payload-overflow'
|
|
13
14
|
import { decodeControlPlaneFrame, encodeWireFrame } from '../supervisor-protocol/wire-codec'
|
|
14
15
|
import { EphemeralFrameBuffer } from './ephemeral-frame-buffer'
|
|
16
|
+
import { SupervisorPersistenceError } from './errors'
|
|
17
|
+
import { commandSequenceConflictCode } from './persistence/supervisor-store'
|
|
15
18
|
import { SupervisorRpcClient } from './rpc-client'
|
|
16
19
|
|
|
17
20
|
export interface ResidentSupervisorSocket {
|
|
@@ -99,8 +102,24 @@ export class SupervisorRuntimeHandler implements RuntimeConnectionHandler {
|
|
|
99
102
|
this.schedulePendingCommands()
|
|
100
103
|
}
|
|
101
104
|
catch (error) {
|
|
102
|
-
this.dependencies.
|
|
103
|
-
|
|
105
|
+
const rejection = this.dependencies.store.binding()?.body.commandTransport === 'wss-v1'
|
|
106
|
+
? terminalCommandRejection(error)
|
|
107
|
+
: undefined
|
|
108
|
+
if (!rejection) {
|
|
109
|
+
this.dependencies.logger.warn({ err: error }, 'rejecting invalid supervisor command')
|
|
110
|
+
socket.close(1008, 'Supervisor command was rejected.')
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
// wss-v1: answer a terminal rejection so the worker persists it instead of
|
|
114
|
+
// redelivering the same command on every reconnect.
|
|
115
|
+
this.dependencies.logger.warn({ err: error, commandSeq: frame.commandSeq, errorCode: rejection.errorCode }, 'rejecting supervisor command')
|
|
116
|
+
this.sendRetained(encodeWireFrame({
|
|
117
|
+
kind: 'command.ack',
|
|
118
|
+
commandId: frame.commandId,
|
|
119
|
+
commandSeq: frame.commandSeq,
|
|
120
|
+
status: 'rejected',
|
|
121
|
+
...rejection,
|
|
122
|
+
}))
|
|
104
123
|
}
|
|
105
124
|
return
|
|
106
125
|
}
|
|
@@ -328,3 +347,12 @@ function frameText(data: string | ArrayBuffer | Uint8Array): string {
|
|
|
328
347
|
return data
|
|
329
348
|
return new TextDecoder().decode(data)
|
|
330
349
|
}
|
|
350
|
+
|
|
351
|
+
/** Only failures the same frame would reproduce are terminal; storage faults still close the socket. */
|
|
352
|
+
function terminalCommandRejection(error: unknown): { errorCode: string, detail: string } | undefined {
|
|
353
|
+
if (error instanceof ZodError)
|
|
354
|
+
return { errorCode: 'supervisor.command.invalid-body', detail: 'Command body failed wire validation.' }
|
|
355
|
+
if (error instanceof SupervisorPersistenceError && error.code === commandSequenceConflictCode)
|
|
356
|
+
return { errorCode: 'supervisor.command.conflicting-duplicate', detail: error.message.slice(0, 240) }
|
|
357
|
+
return undefined
|
|
358
|
+
}
|
|
@@ -2,16 +2,20 @@ import type { AgentEvent } from '../protocol'
|
|
|
2
2
|
import z from 'zod'
|
|
3
3
|
import {
|
|
4
4
|
contextCompactionCompletedAgentEventSchema,
|
|
5
|
+
contextCompactionRequestCompletedAgentEventSchema,
|
|
5
6
|
contextCompactionStartedAgentEventSchema,
|
|
7
|
+
contextUpdatedAgentEventSchema,
|
|
6
8
|
errorAgentEventSchema,
|
|
7
9
|
messageDeltaAgentEventSchema,
|
|
8
10
|
messageEndedAgentEventSchema,
|
|
9
11
|
messageStartedAgentEventSchema,
|
|
12
|
+
promptTurnCorrelatedAgentEventSchema,
|
|
10
13
|
reasoningEndedAgentEventSchema,
|
|
11
14
|
reasoningStartedAgentEventSchema,
|
|
12
15
|
reasoningSummaryDeltaAgentEventSchema,
|
|
13
16
|
sessionConfiguredAgentEventSchema,
|
|
14
17
|
sessionEndedAgentEventSchema,
|
|
18
|
+
sessionSettingsChangedAgentEventSchema,
|
|
15
19
|
sessionSkillsUpdatedAgentEventSchema,
|
|
16
20
|
sessionStartedAgentEventSchema,
|
|
17
21
|
sessionStateChangedAgentEventSchema,
|
|
@@ -21,6 +25,7 @@ import {
|
|
|
21
25
|
toolCallCompletedAgentEventSchema,
|
|
22
26
|
toolCallStartedAgentEventSchema,
|
|
23
27
|
toolOutputDeltaAgentEventSchema,
|
|
28
|
+
toolOutputUpdatedAgentEventSchema,
|
|
24
29
|
turnEndedAgentEventSchema,
|
|
25
30
|
turnStartedAgentEventSchema,
|
|
26
31
|
usageAgentEventSchema,
|
|
@@ -42,6 +47,10 @@ export const wrappedSessionConfiguredAgentEventSchema = sessionConfiguredAgentEv
|
|
|
42
47
|
type: z.literal('agent.session.configured'),
|
|
43
48
|
}).strict()
|
|
44
49
|
|
|
50
|
+
export const wrappedSessionSettingsChangedAgentEventSchema = sessionSettingsChangedAgentEventSchema.extend({
|
|
51
|
+
type: z.literal('agent.session.settings.changed'),
|
|
52
|
+
}).strict()
|
|
53
|
+
|
|
45
54
|
export const wrappedSessionSkillsUpdatedAgentEventSchema = sessionSkillsUpdatedAgentEventSchema.extend({
|
|
46
55
|
type: z.literal('agent.session.skills.updated'),
|
|
47
56
|
}).strict()
|
|
@@ -58,6 +67,10 @@ export const wrappedTurnStartedAgentEventSchema = turnStartedAgentEventSchema.ex
|
|
|
58
67
|
type: z.literal('agent.turn.started'),
|
|
59
68
|
}).strict()
|
|
60
69
|
|
|
70
|
+
export const wrappedPromptTurnCorrelatedAgentEventSchema = promptTurnCorrelatedAgentEventSchema.extend({
|
|
71
|
+
type: z.literal('agent.prompt.turn-correlated'),
|
|
72
|
+
}).strict()
|
|
73
|
+
|
|
61
74
|
export const wrappedTurnEndedAgentEventSchema = turnEndedAgentEventSchema.extend({
|
|
62
75
|
type: z.literal('agent.turn.ended'),
|
|
63
76
|
}).strict()
|
|
@@ -98,6 +111,10 @@ export const wrappedToolOutputDeltaAgentEventSchema = toolOutputDeltaAgentEventS
|
|
|
98
111
|
type: z.literal('agent.tool.output.delta'),
|
|
99
112
|
}).strict()
|
|
100
113
|
|
|
114
|
+
export const wrappedToolOutputUpdatedAgentEventSchema = toolOutputUpdatedAgentEventSchema.extend({
|
|
115
|
+
type: z.literal('agent.tool.output.updated'),
|
|
116
|
+
}).strict()
|
|
117
|
+
|
|
101
118
|
export const wrappedToolCallCompletedAgentEventSchema = toolCallCompletedAgentEventSchema.extend({
|
|
102
119
|
type: z.literal('agent.tool.call.completed'),
|
|
103
120
|
}).strict()
|
|
@@ -118,6 +135,10 @@ export const wrappedUsageAgentEventSchema = usageAgentEventSchema.extend({
|
|
|
118
135
|
type: z.literal('agent.usage'),
|
|
119
136
|
}).strict()
|
|
120
137
|
|
|
138
|
+
export const wrappedContextUpdatedAgentEventSchema = contextUpdatedAgentEventSchema.extend({
|
|
139
|
+
type: z.literal('agent.context.updated'),
|
|
140
|
+
}).strict()
|
|
141
|
+
|
|
121
142
|
export const wrappedContextCompactionStartedAgentEventSchema = contextCompactionStartedAgentEventSchema.extend({
|
|
122
143
|
type: z.literal('agent.context.compaction.started'),
|
|
123
144
|
}).strict()
|
|
@@ -126,6 +147,10 @@ export const wrappedContextCompactionCompletedAgentEventSchema = contextCompacti
|
|
|
126
147
|
type: z.literal('agent.context.compaction.completed'),
|
|
127
148
|
}).strict()
|
|
128
149
|
|
|
150
|
+
export const wrappedContextCompactionRequestCompletedAgentEventSchema = contextCompactionRequestCompletedAgentEventSchema.extend({
|
|
151
|
+
type: z.literal('agent.context.compaction.request.completed'),
|
|
152
|
+
}).strict()
|
|
153
|
+
|
|
129
154
|
export const wrappedUserInputRequestedAgentEventSchema = userInputRequestedAgentEventSchema.extend({
|
|
130
155
|
type: z.literal('agent.user-input.requested'),
|
|
131
156
|
}).strict()
|
|
@@ -141,10 +166,12 @@ export const wrappedErrorAgentEventSchema = errorAgentEventSchema.extend({
|
|
|
141
166
|
export const wrappedAgentEventSchema = z.discriminatedUnion('type', [
|
|
142
167
|
wrappedSessionStartedAgentEventSchema,
|
|
143
168
|
wrappedSessionConfiguredAgentEventSchema,
|
|
169
|
+
wrappedSessionSettingsChangedAgentEventSchema,
|
|
144
170
|
wrappedSessionSkillsUpdatedAgentEventSchema,
|
|
145
171
|
wrappedSessionStateChangedAgentEventSchema,
|
|
146
172
|
wrappedSessionEndedAgentEventSchema,
|
|
147
173
|
wrappedTurnStartedAgentEventSchema,
|
|
174
|
+
wrappedPromptTurnCorrelatedAgentEventSchema,
|
|
148
175
|
wrappedTurnEndedAgentEventSchema,
|
|
149
176
|
wrappedWorkObservedAgentEventSchema,
|
|
150
177
|
wrappedMessageStartedAgentEventSchema,
|
|
@@ -155,13 +182,16 @@ export const wrappedAgentEventSchema = z.discriminatedUnion('type', [
|
|
|
155
182
|
wrappedReasoningEndedAgentEventSchema,
|
|
156
183
|
wrappedToolCallStartedAgentEventSchema,
|
|
157
184
|
wrappedToolOutputDeltaAgentEventSchema,
|
|
185
|
+
wrappedToolOutputUpdatedAgentEventSchema,
|
|
158
186
|
wrappedToolCallCompletedAgentEventSchema,
|
|
159
187
|
wrappedSubagentStartedAgentEventSchema,
|
|
160
188
|
wrappedSubagentProgressAgentEventSchema,
|
|
161
189
|
wrappedSubagentEndedAgentEventSchema,
|
|
162
190
|
wrappedUsageAgentEventSchema,
|
|
191
|
+
wrappedContextUpdatedAgentEventSchema,
|
|
163
192
|
wrappedContextCompactionStartedAgentEventSchema,
|
|
164
193
|
wrappedContextCompactionCompletedAgentEventSchema,
|
|
194
|
+
wrappedContextCompactionRequestCompletedAgentEventSchema,
|
|
165
195
|
wrappedUserInputRequestedAgentEventSchema,
|
|
166
196
|
wrappedUserInputResolvedAgentEventSchema,
|
|
167
197
|
wrappedErrorAgentEventSchema,
|
|
@@ -59,12 +59,15 @@ export const sessionBootstrapBodySchema = z.object({
|
|
|
59
59
|
}),
|
|
60
60
|
gitBotIdentity: gitIdentitySchema.optional(),
|
|
61
61
|
profile: sessionProfileSchema.optional(),
|
|
62
|
+
commandTransport: z.literal('wss-v1').optional(),
|
|
62
63
|
initialCommand: z.object({
|
|
63
64
|
commandId: z.string().min(1),
|
|
64
65
|
commandSeq: z.literal(1),
|
|
65
66
|
body: wireCommandBodySchema,
|
|
66
|
-
}).strict(),
|
|
67
|
+
}).strict().optional(),
|
|
67
68
|
}).strict().superRefine((body, context) => {
|
|
69
|
+
if (body.commandTransport === 'wss-v1' ? body.initialCommand !== undefined : body.initialCommand === undefined)
|
|
70
|
+
context.addIssue({ code: 'custom', path: ['initialCommand'], message: 'wss-v1 bootstraps without a command; legacy bootstrap requires command one.' })
|
|
68
71
|
const workspacePrefix = `${body.agent.cwd}/`
|
|
69
72
|
for (const [index, repository] of body.repositories.entries()) {
|
|
70
73
|
if (repository.root !== body.agent.cwd && !repository.root.startsWith(workspacePrefix)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import z from 'zod'
|
|
2
|
-
import { agentPromptModeSchema, agentPromptSchema, agentUserInputAnswersSchema, jsonObjectSchema, jsonUtf8ByteLength } from '../protocol'
|
|
2
|
+
import { agentPromptModeSchema, agentPromptSchema, agentReasoningEffortSchema, agentUserInputAnswersSchema, jsonObjectSchema, jsonUtf8ByteLength } from '../protocol'
|
|
3
3
|
|
|
4
4
|
const wireSenderContextSchema = z.discriminatedUnion('kind', [
|
|
5
5
|
z.object({ kind: z.literal('application') }).strict(),
|
|
@@ -40,7 +40,9 @@ export const wireCommandExtensionsSchema = jsonObjectSchema.refine(
|
|
|
40
40
|
const wireSendPromptCommandSchema = z.object({
|
|
41
41
|
type: z.literal('agent.send-prompt'),
|
|
42
42
|
mode: agentPromptModeSchema,
|
|
43
|
+
turnId: z.string().min(1).optional(),
|
|
43
44
|
model: z.string().trim().min(1).max(256).optional(),
|
|
45
|
+
reasoningEffort: agentReasoningEffortSchema.optional(),
|
|
44
46
|
prompt: agentPromptSchema,
|
|
45
47
|
attachments: z.array(wireInputAttachmentRefSchema).superRefine((attachments, context) => {
|
|
46
48
|
const attachmentIds = new Set<string>()
|
|
@@ -60,6 +62,7 @@ const wireSendPromptCommandSchema = z.object({
|
|
|
60
62
|
|
|
61
63
|
const wireInterruptCommandSchema = z.object({
|
|
62
64
|
type: z.literal('agent.interrupt'),
|
|
65
|
+
turnId: z.string().min(1).optional(),
|
|
63
66
|
}).strict()
|
|
64
67
|
|
|
65
68
|
const wireRespondUserInputCommandSchema = z.object({
|
|
@@ -14,6 +14,7 @@ export const commandAckErrorCodeSchema = z.string()
|
|
|
14
14
|
|
|
15
15
|
export const runtimeStateEnvelopeSchema = z.object({
|
|
16
16
|
kind: z.literal('runtime.state'),
|
|
17
|
+
sourceId: z.string().uuid().optional(),
|
|
17
18
|
lastReceivedCommandSeq: z.number().int().nonnegative(),
|
|
18
19
|
nextEventSeq: eventSequenceSchema,
|
|
19
20
|
agentRun: supervisorAgentRunSnapshotSchema,
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
CommandSource,
|
|
10
10
|
ConnectionAuthorityStore,
|
|
11
11
|
ConnectionClaim,
|
|
12
|
+
ConnectionReadinessRejection,
|
|
12
13
|
DurableEventSink,
|
|
13
14
|
EphemeralEventSink,
|
|
14
15
|
RpcDispatcher,
|
|
@@ -19,18 +20,37 @@ import { ulid } from 'ulid'
|
|
|
19
20
|
|
|
20
21
|
type NotificationCallbacks = Parameters<RuntimeNotifications['start']>[0]
|
|
21
22
|
|
|
23
|
+
/** Admission state of an in-memory command; only wss-v1 selection consults it. */
|
|
24
|
+
type InMemoryCommandStatus = 'queued' | 'dispatched' | 'received' | 'withdrawn' | 'rejected'
|
|
25
|
+
|
|
26
|
+
/** Code the fixture answers when a session sandbox announces a different journal than it pinned. */
|
|
27
|
+
export const inMemoryJournalIdentityChangedCode = 'testing.journal-identity-changed'
|
|
28
|
+
|
|
22
29
|
export class InMemoryRuntimeControlPlane implements ConnectionAuthorityStore, CommandSource, DurableEventSink,
|
|
23
30
|
EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, RuntimeNotifications {
|
|
24
31
|
readonly durableEventLog: DurableEventEnvelope[] = []
|
|
25
32
|
readonly durableRecordAttempts: number[] = []
|
|
26
33
|
readonly ephemeralEventLog: Array<Parameters<EphemeralEventSink['publish']>[0]> = []
|
|
27
34
|
readonly readySnapshots: SupervisorAgentRunSnapshot[] = []
|
|
35
|
+
/** `lastReceivedCommandSeq` announced by each accepted bootstrap, in readiness order. */
|
|
36
|
+
readonly readyReceivedSequences: number[] = []
|
|
28
37
|
readonly projectedCommandSequences: number[] = []
|
|
29
38
|
readonly failedCommandSequences: number[] = []
|
|
39
|
+
/** wss-v1 receipts, in the order the pump persisted them. */
|
|
40
|
+
readonly receivedCommandSequences: number[] = []
|
|
41
|
+
/** wss-v1 terminal rejections, in the order the pump persisted them. */
|
|
42
|
+
readonly rejectedCommands: Array<{ commandSeq: number, errorCode: string, detail?: string }> = []
|
|
43
|
+
/** Journal identity announced by each accepted readiness, in order. */
|
|
44
|
+
readonly readyJournalSourceIds: Array<string | undefined> = []
|
|
45
|
+
/** Every wss-v1 selection with the receipts that were durable when it ran. */
|
|
46
|
+
readonly pendingSelections: Array<{ commandSeq: number | undefined, receivedBefore: number[] }> = []
|
|
30
47
|
readonly failureLog: Array<{ kind: string, code?: string, detail?: string }> = []
|
|
31
48
|
readonly rpcCalls: SupervisorRpcRequestEnvelope[] = []
|
|
32
49
|
onDurableRecord: ((event: DurableEventEnvelope) => void | Promise<void>) | undefined
|
|
33
|
-
private readonly commandLog: CommandEnvelope
|
|
50
|
+
private readonly commandLog: Array<CommandEnvelope & { body: WireCommandBody }>
|
|
51
|
+
private readonly commandStatus = new Map<number, InMemoryCommandStatus>()
|
|
52
|
+
private readonly wssV1: boolean
|
|
53
|
+
private pinnedJournalSourceId: string | undefined
|
|
34
54
|
private notifications: NotificationCallbacks | undefined
|
|
35
55
|
private claimed = false
|
|
36
56
|
private generation = 0
|
|
@@ -41,30 +61,49 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
41
61
|
ref: { sessionId: string, sessionSandboxId: string, providerSandboxId: string }
|
|
42
62
|
trafficAccessTokenEncrypted: string
|
|
43
63
|
bootstrap: Omit<SessionBootstrapBody, 'sessionId' | 'initialCommand'>
|
|
44
|
-
|
|
64
|
+
/** Required by the legacy bootstrap, forbidden by `commandTransport: 'wss-v1'`. */
|
|
65
|
+
initialCommand?: { commandId: string, body: WireCommandBody }
|
|
45
66
|
secrets?: Awaited<ReturnType<BootstrapProvider['issueEphemeralSecrets']>>
|
|
46
67
|
rpcHandlers?: Readonly<Record<string, (body: unknown) => unknown | Promise<unknown>>>
|
|
68
|
+
/** Journal this session sandbox was already bound to before the runtime under test started. */
|
|
69
|
+
pinnedJournalSourceId?: string
|
|
47
70
|
}) {
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
this.wssV1 = input.bootstrap.commandTransport === 'wss-v1'
|
|
72
|
+
this.pinnedJournalSourceId = input.pinnedJournalSourceId
|
|
73
|
+
if (this.wssV1 === (input.initialCommand !== undefined))
|
|
74
|
+
throw new Error('wss-v1 bootstraps without an initial command; the legacy bootstrap requires one.')
|
|
75
|
+
this.commandLog = input.initialCommand
|
|
76
|
+
? [{
|
|
77
|
+
kind: 'command',
|
|
78
|
+
commandId: input.initialCommand.commandId,
|
|
79
|
+
commandSeq: 1,
|
|
80
|
+
body: input.initialCommand.body,
|
|
81
|
+
}]
|
|
82
|
+
: []
|
|
83
|
+
for (const command of this.commandLog)
|
|
84
|
+
this.commandStatus.set(command.commandSeq, 'queued')
|
|
54
85
|
}
|
|
55
86
|
|
|
56
87
|
enqueueCommand(input: { commandId?: string, body: WireCommandBody }): CommandEnvelope {
|
|
57
|
-
const command
|
|
58
|
-
kind: 'command',
|
|
88
|
+
const command = {
|
|
89
|
+
kind: 'command' as const,
|
|
59
90
|
commandId: input.commandId ?? ulid(),
|
|
60
91
|
commandSeq: this.commandLog.length + 1,
|
|
61
92
|
body: input.body,
|
|
62
93
|
}
|
|
63
94
|
this.commandLog.push(command)
|
|
95
|
+
this.commandStatus.set(command.commandSeq, 'queued')
|
|
64
96
|
this.notifications?.onCommandsReady(this.input.ref.sessionId)
|
|
65
97
|
return command
|
|
66
98
|
}
|
|
67
99
|
|
|
100
|
+
/** Withdraw an admitted command before dispatch; its sequence stays allocated, leaving a gap. */
|
|
101
|
+
withdrawCommand(commandSeq: number): void {
|
|
102
|
+
if (this.commandStatus.get(commandSeq) !== 'queued')
|
|
103
|
+
throw new Error(`Command ${commandSeq} is not queued and cannot be withdrawn.`)
|
|
104
|
+
this.commandStatus.set(commandSeq, 'withdrawn')
|
|
105
|
+
}
|
|
106
|
+
|
|
68
107
|
demandConnection(): void {
|
|
69
108
|
this.notifications?.onConnectionDemand(this.input.ref.sessionSandboxId)
|
|
70
109
|
}
|
|
@@ -106,7 +145,9 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
106
145
|
return {
|
|
107
146
|
renewed,
|
|
108
147
|
...(renewed ? { leaseExpiresAt: new Date(Date.now() + 30_000) } : {}),
|
|
109
|
-
hasOutstandingCommand: this.
|
|
148
|
+
hasOutstandingCommand: this.wssV1
|
|
149
|
+
? this.deliverableCommands().length > 0
|
|
150
|
+
: this.projectedCommandSequences.length + this.failedCommandSequences.length < this.commandLog.length,
|
|
110
151
|
}
|
|
111
152
|
}
|
|
112
153
|
|
|
@@ -114,13 +155,32 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
114
155
|
return this.claimed && input.generation === this.generation
|
|
115
156
|
}
|
|
116
157
|
|
|
117
|
-
async markConnectionReady(input: {
|
|
158
|
+
async markConnectionReady(input: {
|
|
159
|
+
generation: number
|
|
160
|
+
lastReceivedCommandSeq: number
|
|
161
|
+
agentRunSnapshot: SupervisorAgentRunSnapshot
|
|
162
|
+
journalSourceId?: string
|
|
163
|
+
}): Promise<boolean | ConnectionReadinessRejection> {
|
|
118
164
|
if (!this.claimed || input.generation !== this.generation)
|
|
119
165
|
return false
|
|
166
|
+
if (this.pinnedJournalSourceId !== undefined && input.journalSourceId !== this.pinnedJournalSourceId) {
|
|
167
|
+
return {
|
|
168
|
+
ready: false,
|
|
169
|
+
code: inMemoryJournalIdentityChangedCode,
|
|
170
|
+
detail: `Session sandbox is pinned to journal ${this.pinnedJournalSourceId}; supervisor announced ${input.journalSourceId ?? 'none'}.`,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this.pinnedJournalSourceId ??= input.journalSourceId
|
|
120
174
|
this.readySnapshots.push(input.agentRunSnapshot)
|
|
175
|
+
this.readyReceivedSequences.push(input.lastReceivedCommandSeq)
|
|
176
|
+
this.readyJournalSourceIds.push(input.journalSourceId)
|
|
121
177
|
return true
|
|
122
178
|
}
|
|
123
179
|
|
|
180
|
+
journalSourceId(): string | undefined {
|
|
181
|
+
return this.pinnedJournalSourceId
|
|
182
|
+
}
|
|
183
|
+
|
|
124
184
|
async markConnectionReconnecting(): Promise<void> {}
|
|
125
185
|
|
|
126
186
|
async releaseConnection(input: { generation: number }): Promise<void> {
|
|
@@ -137,9 +197,35 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
137
197
|
}
|
|
138
198
|
|
|
139
199
|
async next(input: { afterSequence: number }): Promise<CommandEnvelope | undefined> {
|
|
200
|
+
if (this.wssV1)
|
|
201
|
+
throw new Error('wss-v1 never advances a cumulative command cursor.')
|
|
140
202
|
return this.commandLog.find(command => command.commandSeq > input.afterSequence)
|
|
141
203
|
}
|
|
142
204
|
|
|
205
|
+
/** wss-v1 selection: cancel and steer jump the queue; withdrawn commands leave gaps. */
|
|
206
|
+
async pending(): Promise<CommandEnvelope | undefined> {
|
|
207
|
+
if (!this.wssV1)
|
|
208
|
+
throw new Error('Individual command selection requires commandTransport wss-v1.')
|
|
209
|
+
// Yield like a database round trip so admissions made in the same tick are visible.
|
|
210
|
+
await Promise.resolve()
|
|
211
|
+
const deliverable = this.deliverableCommands()
|
|
212
|
+
const selected = deliverable.find(command => isPriorityCommand(command.body)) ?? deliverable[0]
|
|
213
|
+
this.pendingSelections.push({ commandSeq: selected?.commandSeq, receivedBefore: [...this.receivedCommandSequences] })
|
|
214
|
+
if (selected)
|
|
215
|
+
this.commandStatus.set(selected.commandSeq, 'dispatched')
|
|
216
|
+
return selected
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async received(input: { commandId: string, commandSeq: number }): Promise<void> {
|
|
220
|
+
this.settleDispatched(input, 'received')
|
|
221
|
+
this.receivedCommandSequences.push(input.commandSeq)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async rejected(input: { commandId: string, commandSeq: number, errorCode: string, detail?: string }): Promise<void> {
|
|
225
|
+
this.settleDispatched(input, 'rejected')
|
|
226
|
+
this.rejectedCommands.push({ commandSeq: input.commandSeq, errorCode: input.errorCode, detail: input.detail })
|
|
227
|
+
}
|
|
228
|
+
|
|
143
229
|
async getAckFloor(): Promise<number> {
|
|
144
230
|
return this.ackFloor
|
|
145
231
|
}
|
|
@@ -203,4 +289,24 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
203
289
|
async stop(): Promise<void> {
|
|
204
290
|
this.notifications = undefined
|
|
205
291
|
}
|
|
292
|
+
|
|
293
|
+
private settleDispatched(input: { commandId: string, commandSeq: number }, status: 'received' | 'rejected'): void {
|
|
294
|
+
const command = this.commandLog[input.commandSeq - 1]
|
|
295
|
+
if (!command || command.commandId !== input.commandId)
|
|
296
|
+
throw new Error(`Receipt for unknown command ${input.commandSeq}.`)
|
|
297
|
+
if (this.commandStatus.get(input.commandSeq) !== 'dispatched')
|
|
298
|
+
throw new Error(`Command ${input.commandSeq} was not dispatched.`)
|
|
299
|
+
this.commandStatus.set(input.commandSeq, status)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private deliverableCommands(): Array<CommandEnvelope & { body: WireCommandBody }> {
|
|
303
|
+
return this.commandLog.filter((command) => {
|
|
304
|
+
const status = this.commandStatus.get(command.commandSeq)
|
|
305
|
+
return status === 'queued' || status === 'dispatched'
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function isPriorityCommand(body: WireCommandBody): boolean {
|
|
311
|
+
return body.type === 'agent.interrupt' || (body.type === 'agent.send-prompt' && body.mode === 'steer')
|
|
206
312
|
}
|