@meistrari/agent-core 0.1.0 → 0.1.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 +12 -0
- package/package.json +7 -5
- package/src/agents/claude/claude-event-mapper.ts +14 -2
- package/src/agents/claude/claude-provider.ts +1 -0
- package/src/agents/claude/claude-run.ts +7 -3
- package/src/agents/codex/codex-event-mapper.ts +14 -1
- package/src/protocol/agent-event.ts +2 -1
- package/src/protocol/agent-usage.ts +14 -0
- package/src/provenance.gen.ts +3 -3
- package/src/steps/contract.ts +94 -0
- package/src/steps/projector.ts +195 -0
- package/src/supervisor/agent-provider-factory.ts +32 -2
- package/src/supervisor/bootstrap-binder.ts +8 -0
- package/src/supervisor/provider-factory.ts +2 -0
- package/src/supervisor/runtime-handler.ts +2 -0
- package/src/supervisor/websocket-server.ts +1 -0
- package/src/testing/in-memory-runtime-control-plane.ts +6 -5
- package/src/worker-runtime-client/command-pump.ts +0 -5
- package/src/worker-runtime-client/connection-attempt.ts +11 -2
- package/src/worker-runtime-client/ports.ts +3 -1
- package/src/worker-runtime-client/sandbox-connection-runtime.ts +1 -0
package/README.md
CHANGED
|
@@ -45,3 +45,15 @@ bunx agent-core-supervisor \
|
|
|
45
45
|
Use `--loopback-tcp` for local and image-build probes. Production images default to the protected Unix socket. The extension module exports `default`, `extensions`, or `createSupervisorExtensions()` matching `SupervisorExtensions`.
|
|
46
46
|
|
|
47
47
|
Publishing runs from `main` through `.github/workflows/publish.yml` (conventional commits decide the bump).
|
|
48
|
+
|
|
49
|
+
## Durable command outcomes
|
|
50
|
+
|
|
51
|
+
A supervisor `received` ACK advances the command delivery cursor only. Implementations of
|
|
52
|
+
`DurableEventSink.record` must atomically store each durable event, project
|
|
53
|
+
`supervisor.command.applied` or `supervisor.command.failed` into command state, and advance the
|
|
54
|
+
ACK floor. Duplicate events must not repeat projections. This keeps outcomes consistent when
|
|
55
|
+
the worker disconnects after persistence but before sending its event ACK.
|
|
56
|
+
|
|
57
|
+
The former `ConnectionAuthorityStore.projectAppliedCommand` callback has been removed.
|
|
58
|
+
Move that projection into the durable-event transaction; receiving a command does not prove
|
|
59
|
+
that the provider executed it. See `InMemoryRuntimeControlPlane.record` for the test fixture.
|
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.2",
|
|
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",
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"registry": "https://registry.npmjs.org"
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
|
+
"./steps/contract": "./src/steps/contract.ts",
|
|
18
|
+
"./steps/projector": "./src/steps/projector.ts",
|
|
17
19
|
"./errors": "./src/errors/index.ts",
|
|
18
20
|
"./errors/application-error": "./src/errors/application-error.ts",
|
|
19
21
|
"./logger": "./src/logger/index.ts",
|
|
@@ -89,9 +91,9 @@
|
|
|
89
91
|
}
|
|
90
92
|
},
|
|
91
93
|
"dependencies": {
|
|
92
|
-
"jose": "
|
|
94
|
+
"jose": "6.2.11",
|
|
93
95
|
"pino": "^9.7.0",
|
|
94
|
-
"ulid": "
|
|
96
|
+
"ulid": "3.0.2",
|
|
95
97
|
"zod": "^4.1.12"
|
|
96
98
|
},
|
|
97
99
|
"devDependencies": {
|
|
@@ -100,9 +102,9 @@
|
|
|
100
102
|
"@anthropic-ai/sdk": "0.93.0",
|
|
101
103
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
102
104
|
"@types/bun": "1.3.12",
|
|
103
|
-
"@types/pg": "
|
|
105
|
+
"@types/pg": "8.23.1",
|
|
104
106
|
"eslint": "^9.30.0",
|
|
105
|
-
"pg": "
|
|
107
|
+
"pg": "8.23.0",
|
|
106
108
|
"typescript": "5.9.3"
|
|
107
109
|
}
|
|
108
110
|
}
|
|
@@ -191,8 +191,20 @@ export function mapClaudeMessage(message: SDKMessage, state: ClaudeEventMapperSt
|
|
|
191
191
|
// Claude Agent SDK result usage is per completed ask/turn for the stream-json path we use,
|
|
192
192
|
// not a monotonic counter across the long-lived query. Emit it directly so summing
|
|
193
193
|
// normalized usage events gives the run total; do not diff against prior results.
|
|
194
|
-
if (usage)
|
|
195
|
-
drafts.push({ type: 'usage', turnId, actor: mainActor, payload: { usage
|
|
194
|
+
if (usage) {
|
|
195
|
+
drafts.push({ type: 'usage', turnId, actor: mainActor, payload: { usage, accounting: {
|
|
196
|
+
observationId: message.uuid,
|
|
197
|
+
mode: 'increment',
|
|
198
|
+
scopeId: turnId,
|
|
199
|
+
includesChildren: true,
|
|
200
|
+
models: Object.fromEntries(Object.entries(message.modelUsage ?? {}).map(([model, counts]) => [model, {
|
|
201
|
+
inputTokens: counts.inputTokens,
|
|
202
|
+
outputTokens: counts.outputTokens,
|
|
203
|
+
cacheReadTokens: counts.cacheReadInputTokens,
|
|
204
|
+
cacheWriteTokens: counts.cacheCreationInputTokens,
|
|
205
|
+
}])),
|
|
206
|
+
} } })
|
|
207
|
+
}
|
|
196
208
|
endOpenReasoning(drafts, state, turnId)
|
|
197
209
|
const interruptAccepted = state.interrupt.status === 'accepted' && state.interrupt.turnId === turnId
|
|
198
210
|
drafts.push({ type: 'turn.ended', turnId, actor: mainActor, payload: turnEndedPayload(message, interruptAccepted) })
|
|
@@ -71,6 +71,7 @@ export class ClaudeProvider implements AgentProvider<'claude'> {
|
|
|
71
71
|
const sdkOptions: Options = removeUndefined({
|
|
72
72
|
abortController,
|
|
73
73
|
cwd: input.cwd,
|
|
74
|
+
settingSources: ['project', 'local'],
|
|
74
75
|
env: this.environment,
|
|
75
76
|
model: input.model,
|
|
76
77
|
thinking: { type: 'adaptive', display: 'summarized' },
|
|
@@ -285,8 +285,8 @@ export class ClaudeRun implements AgentRun {
|
|
|
285
285
|
// session identity mismatch are provider protocol drift and fail the run closed. `cancelled`
|
|
286
286
|
// (a steered-over/aborted command) and `discarded` (the session ended with the command still
|
|
287
287
|
// queued) are valid observed states but NEGATIVE terminals — never a positive acceptance, so
|
|
288
|
-
// they
|
|
289
|
-
//
|
|
288
|
+
// they reject any pending waiter as delivery-unknown so command processing can advance.
|
|
289
|
+
// Valid positive frames without a pending
|
|
290
290
|
// waiter are internal lifecycle for already-settled or non-prompt inputs and are ignored.
|
|
291
291
|
private settleAcceptance(frame: ClaudeCommandLifecycleMessage): void {
|
|
292
292
|
const parsed = commandLifecycleFrameSchema.safeParse(frame)
|
|
@@ -302,8 +302,12 @@ export class ClaudeRun implements AgentRun {
|
|
|
302
302
|
details: { expected: this.metadata.providerSessionId, actual: parsed.data.session_id },
|
|
303
303
|
})
|
|
304
304
|
}
|
|
305
|
-
if (parsed.data.state === 'cancelled' || parsed.data.state === 'discarded')
|
|
305
|
+
if (parsed.data.state === 'cancelled' || parsed.data.state === 'discarded') {
|
|
306
|
+
this.acceptanceWaiters.get(parsed.data.command_uuid)?.reject(new AgentDeliveryUnknownError({
|
|
307
|
+
message: `Claude command ended as ${parsed.data.state} before acceptance was observed.`,
|
|
308
|
+
}))
|
|
306
309
|
return
|
|
310
|
+
}
|
|
307
311
|
this.acceptanceWaiters.get(parsed.data.command_uuid)?.resolve()
|
|
308
312
|
}
|
|
309
313
|
|
|
@@ -3,6 +3,7 @@ import type { AgentWorkStatus } from '../../protocol/agent-work'
|
|
|
3
3
|
import type { AgentEventDraft } from '../agent-event-stream'
|
|
4
4
|
import type { CodexJsonRpcMessage, CodexServerNotification } from './codex-json-rpc-client'
|
|
5
5
|
import type { ServerNotificationParamsByMethod } from './codex-protocol'
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
6
7
|
import { jsonObjectSchema, jsonValueSchema } from '../../protocol'
|
|
7
8
|
import { CANONICAL_TOOL } from '../../protocol/agent-tool-name'
|
|
8
9
|
import { createRuntimeWorkItemId } from '../agent-id'
|
|
@@ -96,7 +97,19 @@ export function mapCodexNotification(notification: CodexNotification, state: Cod
|
|
|
96
97
|
const usage = usageFromTokenUsage(notification.params.tokenUsage)
|
|
97
98
|
if (!usage)
|
|
98
99
|
return []
|
|
99
|
-
return emitOrBufferByThread({ draft: { type: 'usage', turnId: notification.params.turnId, payload: {
|
|
100
|
+
return emitOrBufferByThread({ draft: { type: 'usage', turnId: notification.params.turnId, payload: {
|
|
101
|
+
usage,
|
|
102
|
+
accounting: {
|
|
103
|
+
observationId: createHash('sha256').update(notification.params.threadId).update(JSON.stringify(notification.params.tokenUsage.total)).digest('hex'),
|
|
104
|
+
mode: 'cumulative',
|
|
105
|
+
scopeId: notification.params.threadId,
|
|
106
|
+
includesChildren: false,
|
|
107
|
+
usage: {
|
|
108
|
+
...usageFromCounts(notification.params.tokenUsage.total),
|
|
109
|
+
inputTokens: Math.max(0, notification.params.tokenUsage.total.inputTokens - notification.params.tokenUsage.total.cachedInputTokens),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
} }, threadId: notification.params.threadId, state })
|
|
100
113
|
}
|
|
101
114
|
case 'turn/plan/updated':
|
|
102
115
|
if (!state.startedTurnIds.has(notification.params.turnId) || notification.params.threadId !== state.mainThreadId)
|
|
@@ -4,7 +4,7 @@ import { agentRunMetadataSchema } from './agent-metadata'
|
|
|
4
4
|
import { agentPayloadOverflowSchema } from './agent-overflow'
|
|
5
5
|
import { agentProviderIdSchema } from './agent-provider'
|
|
6
6
|
import { agentToolCallStatusSchema, agentToolResultSchema } from './agent-tool'
|
|
7
|
-
import { agentUsageSchema } from './agent-usage'
|
|
7
|
+
import { agentUsageAccountingSchema, agentUsageSchema } from './agent-usage'
|
|
8
8
|
import { agentUserInputAnswersSchema, agentUserInputQuestionSchema } from './agent-user-input'
|
|
9
9
|
import { agentWorkObservationSchema } from './agent-work'
|
|
10
10
|
|
|
@@ -265,6 +265,7 @@ export const usageAgentEventSchema = agentTurnEventBaseSchema.extend({
|
|
|
265
265
|
type: z.literal('usage'),
|
|
266
266
|
payload: z.object({
|
|
267
267
|
usage: agentUsageSchema,
|
|
268
|
+
accounting: agentUsageAccountingSchema.optional(),
|
|
268
269
|
}).strict(),
|
|
269
270
|
}).strict()
|
|
270
271
|
|
|
@@ -12,3 +12,17 @@ export const agentUsageSchema = z.object({
|
|
|
12
12
|
}).strict()
|
|
13
13
|
|
|
14
14
|
export type AgentUsage = z.infer<typeof agentUsageSchema>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Accounting semantics are separate from display counters. Consumers persist
|
|
18
|
+
* observations/checkpoints before acknowledging events, including across runs.
|
|
19
|
+
*/
|
|
20
|
+
export const agentUsageAccountingSchema = z.object({
|
|
21
|
+
observationId: z.string().min(1),
|
|
22
|
+
mode: z.enum(['increment', 'cumulative']),
|
|
23
|
+
scopeId: z.string().min(1),
|
|
24
|
+
includesChildren: z.boolean(),
|
|
25
|
+
usage: agentUsageSchema.optional(),
|
|
26
|
+
model: z.string().min(1).optional(),
|
|
27
|
+
models: z.record(z.string(), agentUsageSchema).optional(),
|
|
28
|
+
}).strict()
|
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.2",
|
|
4
|
+
sha: "85081b3dd3667033dfb1dfd399a6f59d3fd42d43",
|
|
5
|
+
buildTime: "2026-09-09T11:47:21.679Z",
|
|
6
6
|
} as const
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import { agentEventActorSchema } from '../protocol/agent-event'
|
|
5
|
+
import { jsonValueSchema } from '../protocol/agent-json'
|
|
6
|
+
|
|
7
|
+
export const stepSummarySchema = z.object({
|
|
8
|
+
version: z.literal(1),
|
|
9
|
+
stepId: z.string().uuid(),
|
|
10
|
+
sessionId: z.string(),
|
|
11
|
+
provider: z.enum(['claude', 'codex']),
|
|
12
|
+
providerSessionId: z.string(),
|
|
13
|
+
sourceId: z.string(),
|
|
14
|
+
actor: agentEventActorSchema,
|
|
15
|
+
turnId: z.string(),
|
|
16
|
+
commandId: z.string().nullable(),
|
|
17
|
+
kind: z.enum(['message', 'tool', 'reasoning']),
|
|
18
|
+
role: z.enum(['assistant', 'user', 'system']).nullable(),
|
|
19
|
+
toolName: z.string().nullable(),
|
|
20
|
+
status: z.enum(['running', 'completed', 'failed', 'interrupted']),
|
|
21
|
+
incomplete: z.boolean(),
|
|
22
|
+
startedAt: z.string().datetime(),
|
|
23
|
+
endedAt: z.string().datetime().nullable(),
|
|
24
|
+
preview: z.string(),
|
|
25
|
+
}).strict()
|
|
26
|
+
export const stepDetailSchema = stepSummarySchema.extend({
|
|
27
|
+
content: z.record(z.string(), jsonValueSchema),
|
|
28
|
+
}).strict()
|
|
29
|
+
export type StepSummary = z.infer<typeof stepSummarySchema>
|
|
30
|
+
export type StepDetailV1 = z.infer<typeof stepDetailSchema>
|
|
31
|
+
|
|
32
|
+
/** Stable bytes across JSONB round trips and deterministic upload retries. */
|
|
33
|
+
export function serializeStepDetail(value: unknown): string {
|
|
34
|
+
return JSON.stringify(value, (_key, item: unknown) => {
|
|
35
|
+
if (item && typeof item === 'object' && !Array.isArray(item))
|
|
36
|
+
return Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
|
|
37
|
+
return item
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const stepClosedSchema = z.object({
|
|
42
|
+
step: stepSummarySchema,
|
|
43
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
44
|
+
byteSize: z.number().int().positive(),
|
|
45
|
+
textParts: z.number().int().nonnegative(),
|
|
46
|
+
detail: stepDetailSchema.optional(),
|
|
47
|
+
reference: z.string().startsWith('vault://').optional(),
|
|
48
|
+
}).strict().refine(value => Boolean(value.detail) !== Boolean(value.reference), 'Exactly one detail source is required')
|
|
49
|
+
|
|
50
|
+
export function stepIdFor(input: Pick<StepSummary, 'sessionId' | 'providerSessionId' | 'actor' | 'turnId' | 'kind' | 'sourceId'>): string {
|
|
51
|
+
const hex = createHash('sha256').update(JSON.stringify([
|
|
52
|
+
input.sessionId,
|
|
53
|
+
input.providerSessionId,
|
|
54
|
+
input.actor.actorId,
|
|
55
|
+
input.turnId,
|
|
56
|
+
input.kind,
|
|
57
|
+
input.sourceId,
|
|
58
|
+
])).digest('hex')
|
|
59
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Both decoded UTF-8 and JSON representation stay bounded without dropping text. */
|
|
63
|
+
export function splitStepText(text: string): string[] {
|
|
64
|
+
const parts: string[] = []
|
|
65
|
+
let part = ''
|
|
66
|
+
let bytes = 2
|
|
67
|
+
for (const character of text) {
|
|
68
|
+
const size = Buffer.byteLength(JSON.stringify(character)) - 2
|
|
69
|
+
if (bytes + size > 16 * 1024) {
|
|
70
|
+
parts.push(part)
|
|
71
|
+
part = ''
|
|
72
|
+
bytes = 2
|
|
73
|
+
}
|
|
74
|
+
part += character
|
|
75
|
+
bytes += size
|
|
76
|
+
}
|
|
77
|
+
if (part)
|
|
78
|
+
parts.push(part)
|
|
79
|
+
return parts
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function stepPreview(value: unknown): string {
|
|
83
|
+
const text = (typeof value === 'string' ? value : JSON.stringify(value) ?? '').replaceAll('\0', '\\0')
|
|
84
|
+
let preview = ''
|
|
85
|
+
let bytes = 0
|
|
86
|
+
for (const character of text) {
|
|
87
|
+
const size = Buffer.byteLength(JSON.stringify(character)) - 2
|
|
88
|
+
if (bytes + size > 2048)
|
|
89
|
+
return `${preview}…`
|
|
90
|
+
bytes += size
|
|
91
|
+
preview += character
|
|
92
|
+
}
|
|
93
|
+
return preview
|
|
94
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { AgentEvent } from '../protocol'
|
|
2
|
+
import type { WireEventBody } from '../supervisor-protocol/event-body'
|
|
3
|
+
import type { StepDetailV1, StepSummary } from './contract'
|
|
4
|
+
import { Buffer } from 'node:buffer'
|
|
5
|
+
import { createHash } from 'node:crypto'
|
|
6
|
+
import { Database } from 'bun:sqlite'
|
|
7
|
+
import { wrapAgentEvent } from '../supervisor-protocol/agent-event-wrapper'
|
|
8
|
+
import { serializeStepDetail, splitStepText, stepIdFor, stepPreview } from './contract'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Same-binary local recovery. The journal is marked delivered only after emit
|
|
12
|
+
* synchronously commits all frames into the supervisor's own durable outbox.
|
|
13
|
+
*/
|
|
14
|
+
export class DurableStepProjector {
|
|
15
|
+
private readonly db: Database
|
|
16
|
+
private currentEventId = ''
|
|
17
|
+
|
|
18
|
+
constructor(private readonly options: {
|
|
19
|
+
path: string
|
|
20
|
+
upload: (input: { stepId: string, sha256: string, body: string, byteSize: number }) => Promise<string>
|
|
21
|
+
}) {
|
|
22
|
+
this.db = new Database(options.path, { create: true })
|
|
23
|
+
this.db.exec(`pragma journal_mode=WAL; pragma synchronous=FULL;
|
|
24
|
+
create table if not exists steps(id text primary key, body text not null, last_event_id text);
|
|
25
|
+
create table if not exists journal(id text primary key, event text, frames text);
|
|
26
|
+
`)
|
|
27
|
+
if (!this.db.query<{ name: string }, []>('pragma table_info(steps)').all().some(column => column.name === 'last_event_id'))
|
|
28
|
+
this.db.exec('alter table steps add column last_event_id text')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
close(): void { this.db.close() }
|
|
32
|
+
|
|
33
|
+
async recover(emit: (body: WireEventBody) => void): Promise<void> {
|
|
34
|
+
for (;;) {
|
|
35
|
+
const row = this.db.query<{ id: string, event: string, frames: string | null }, []>(
|
|
36
|
+
'select id, event, frames from journal where event is not null order by rowid limit 1',
|
|
37
|
+
).get()
|
|
38
|
+
if (!row)
|
|
39
|
+
return
|
|
40
|
+
await this.deliver(row.id, JSON.parse(row.event) as AgentEvent, row.frames, emit)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async process(event: AgentEvent, emit: (body: WireEventBody) => void): Promise<void> {
|
|
45
|
+
if (['message.delta', 'tool.output.delta', 'reasoning.summary.delta'].includes(event.type)) {
|
|
46
|
+
// Live deltas never become historical tool payloads. Large transport
|
|
47
|
+
// deltas are split rather than dropped by the supervisor frame limit.
|
|
48
|
+
const body = wrapAgentEvent(event)
|
|
49
|
+
const key = event.type === 'reasoning.summary.delta' ? 'text' : 'delta'
|
|
50
|
+
const payload = event.payload as Record<string, unknown>
|
|
51
|
+
for (const part of splitStepText(String(payload[key] ?? ''))) {
|
|
52
|
+
// Use smaller slices for envelope headroom within the 16 KiB cap.
|
|
53
|
+
for (const chunk of splitStepText(part).flatMap(part => [...part].length > 2000 ? part.match(/.{1,2000}/gsu)! : [part]))
|
|
54
|
+
emit({ ...body, payload: { ...payload, [key]: chunk } } as WireEventBody)
|
|
55
|
+
}
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
const found = this.db.query<{ event: string | null, frames: string | null }, [string]>(
|
|
59
|
+
'select event, frames from journal where id=?',
|
|
60
|
+
).get(event.eventId)
|
|
61
|
+
if (found?.event === null)
|
|
62
|
+
return
|
|
63
|
+
if (!found)
|
|
64
|
+
this.db.query('insert into journal(id,event) values (?,?)').run(event.eventId, JSON.stringify(event))
|
|
65
|
+
await this.deliver(event.eventId, event, found?.frames ?? null, emit)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async deliver(id: string, event: AgentEvent, saved: string | null, emit: (body: WireEventBody) => void): Promise<void> {
|
|
69
|
+
this.currentEventId = id
|
|
70
|
+
const frames = saved ? JSON.parse(saved) as WireEventBody[] : await this.project(event)
|
|
71
|
+
if (!saved)
|
|
72
|
+
this.db.query('update journal set frames=? where id=?').run(JSON.stringify(frames), id)
|
|
73
|
+
for (const frame of frames)
|
|
74
|
+
emit(frame)
|
|
75
|
+
this.db.query('update journal set event=null, frames=null where id=?').run(id)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async project(event: AgentEvent): Promise<WireEventBody[]> {
|
|
79
|
+
const kind = event.type.startsWith('message.')
|
|
80
|
+
? 'message'
|
|
81
|
+
: event.type.startsWith('tool.call.')
|
|
82
|
+
? 'tool'
|
|
83
|
+
: event.type.startsWith('reasoning.') ? 'reasoning' : undefined
|
|
84
|
+
if (!kind || !('turnId' in event)) {
|
|
85
|
+
const frames: WireEventBody[] = []
|
|
86
|
+
if (event.type === 'turn.ended' || event.type === 'session.ended') {
|
|
87
|
+
for (const row of this.db.query<{ body: string, last_event_id: string | null }, []>('select body, last_event_id from steps').all()) {
|
|
88
|
+
const step = JSON.parse(row.body) as StepDetailV1
|
|
89
|
+
if ((step.status !== 'running' && row.last_event_id !== event.eventId) || (event.type === 'turn.ended' && (step.turnId !== event.turnId || step.actor.actorId !== event.actor.actorId)))
|
|
90
|
+
continue
|
|
91
|
+
step.status = event.type === 'turn.ended' && event.payload.status === 'failed' ? 'failed' : 'interrupted'
|
|
92
|
+
step.incomplete = true
|
|
93
|
+
step.endedAt = event.timestamp
|
|
94
|
+
frames.push(...await this.finish(step))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
frames.push(wrapAgentEvent(event))
|
|
98
|
+
return frames
|
|
99
|
+
}
|
|
100
|
+
const payload = event.payload as Record<string, unknown>
|
|
101
|
+
const sourceId = String(payload.messageId ?? payload.toolCallId ?? payload.reasoningId)
|
|
102
|
+
const identity = { sessionId: event.sessionId, providerSessionId: event.providerSessionId, actor: event.actor, turnId: event.turnId, kind: kind as StepSummary['kind'], sourceId }
|
|
103
|
+
const stepId = stepIdFor(identity)
|
|
104
|
+
const row = this.db.query<{ body: string, last_event_id: string | null }, [string]>('select body, last_event_id from steps where id=?').get(stepId)
|
|
105
|
+
const step: StepDetailV1 = row
|
|
106
|
+
? JSON.parse(row.body)
|
|
107
|
+
: {
|
|
108
|
+
...identity,
|
|
109
|
+
stepId,
|
|
110
|
+
version: 1,
|
|
111
|
+
provider: event.provider,
|
|
112
|
+
commandId: typeof payload.commandId === 'string' ? payload.commandId : null,
|
|
113
|
+
role: kind === 'message' ? payload.role as StepSummary['role'] : null,
|
|
114
|
+
toolName: typeof payload.toolName === 'string' ? payload.toolName : null,
|
|
115
|
+
status: 'running',
|
|
116
|
+
incomplete: !event.type.endsWith('started'),
|
|
117
|
+
startedAt: event.timestamp,
|
|
118
|
+
endedAt: null,
|
|
119
|
+
preview: '',
|
|
120
|
+
content: {},
|
|
121
|
+
}
|
|
122
|
+
if (step.status !== 'running' && row?.last_event_id !== event.eventId)
|
|
123
|
+
return []
|
|
124
|
+
if (event.type.endsWith('started')) {
|
|
125
|
+
if (row && row.last_event_id !== event.eventId)
|
|
126
|
+
return []
|
|
127
|
+
if (step.status !== 'running')
|
|
128
|
+
return []
|
|
129
|
+
if (kind === 'tool') {
|
|
130
|
+
step.content.input = payload.input as StepDetailV1['content'][string] ?? null
|
|
131
|
+
step.preview = stepPreview(payload.input)
|
|
132
|
+
}
|
|
133
|
+
this.save(step)
|
|
134
|
+
return [this.frame('product.step.started', { step: summary(step) })]
|
|
135
|
+
}
|
|
136
|
+
if (kind === 'message') {
|
|
137
|
+
step.content.text = typeof payload.text === 'string' ? payload.text : ''
|
|
138
|
+
step.preview = ''
|
|
139
|
+
}
|
|
140
|
+
else if (kind === 'tool') {
|
|
141
|
+
step.content.output = payload.output as StepDetailV1['content'][string] ?? null
|
|
142
|
+
step.content.error = payload.error as StepDetailV1['content'][string] ?? null
|
|
143
|
+
step.preview = stepPreview(payload.output ?? payload.error)
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
step.content.summary = typeof payload.summary === 'string' ? payload.summary : ''
|
|
147
|
+
step.preview = stepPreview(payload.summary)
|
|
148
|
+
}
|
|
149
|
+
step.status = payload.status === 'failed' ? 'failed' : payload.status === 'cancelled' ? 'interrupted' : 'completed'
|
|
150
|
+
if (kind === 'tool' && payload.output === undefined && payload.error === undefined) {
|
|
151
|
+
step.incomplete = true
|
|
152
|
+
step.status = 'interrupted'
|
|
153
|
+
}
|
|
154
|
+
step.endedAt = event.timestamp
|
|
155
|
+
if (payload.overflow)
|
|
156
|
+
throw new Error('Step projection requires complete provider content before overflow truncation.')
|
|
157
|
+
return await this.finish(step)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async finish(step: StepDetailV1): Promise<WireEventBody[]> {
|
|
161
|
+
if (step.kind === 'message') {
|
|
162
|
+
// References, not repeated complete tool payloads, connect message content.
|
|
163
|
+
step.content.toolSteps = this.db.query<{ id: string }, [string, string]>(`
|
|
164
|
+
select id from steps where json_extract(body,'$.kind')='tool'
|
|
165
|
+
and json_extract(body,'$.turnId')=? and json_extract(body,'$.actor.actorId')=? order by rowid
|
|
166
|
+
`).all(step.turnId, step.actor.actorId).map(row => row.id)
|
|
167
|
+
}
|
|
168
|
+
this.save(step)
|
|
169
|
+
const body = serializeStepDetail(step)
|
|
170
|
+
const sha256 = createHash('sha256').update(body).digest('hex')
|
|
171
|
+
const byteSize = Buffer.byteLength(body)
|
|
172
|
+
const text = step.kind === 'message' && typeof step.content.text === 'string' ? splitStepText(step.content.text) : []
|
|
173
|
+
const detailSource = byteSize <= 32 * 1024
|
|
174
|
+
? { detail: step }
|
|
175
|
+
: { reference: await this.options.upload({ stepId: step.stepId, sha256, byteSize, body }) }
|
|
176
|
+
return [
|
|
177
|
+
...text.map((part, index) => this.frame('product.step.text', { step: summary(step), index, count: text.length, text: part })),
|
|
178
|
+
this.frame('product.step.closed', { step: summary(step), sha256, byteSize, textParts: text.length, ...detailSource }),
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private save(step: StepDetailV1): void {
|
|
183
|
+
this.db.query('insert into steps(id,body,last_event_id) values (?,?,?) on conflict(id) do update set body=excluded.body,last_event_id=excluded.last_event_id')
|
|
184
|
+
.run(step.stepId, JSON.stringify(step), this.currentEventId)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private frame(type: `product.${string}`, payload: unknown): WireEventBody {
|
|
188
|
+
return { type, payload } as WireEventBody
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function summary(step: StepDetailV1): StepSummary {
|
|
193
|
+
const { content: _content, ...value } = step
|
|
194
|
+
return value
|
|
195
|
+
}
|
|
@@ -16,6 +16,10 @@ export interface AgentProviderBindingStore {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface AgentSupervisorProviderFactoryOptions {
|
|
19
|
+
eventProcessor?: {
|
|
20
|
+
recover: (emit: (body: WireEventBody) => void) => Promise<void>
|
|
21
|
+
process: (event: AgentEvent, emit: (body: WireEventBody) => void) => Promise<void>
|
|
22
|
+
}
|
|
19
23
|
createProvider: (input: {
|
|
20
24
|
bootstrap: SessionBootstrapBody
|
|
21
25
|
credentials?: EphemeralCredentials
|
|
@@ -69,6 +73,9 @@ export function createAgentSupervisorProviderFactory(
|
|
|
69
73
|
|
|
70
74
|
return new AgentSupervisorRuntime({
|
|
71
75
|
run,
|
|
76
|
+
emit: input.emit,
|
|
77
|
+
onFatal: input.onFatal,
|
|
78
|
+
eventProcessor: options.eventProcessor,
|
|
72
79
|
prepareInputAttachments: options.prepareInputAttachments,
|
|
73
80
|
onSecretsRefreshed: options.onSecretsRefreshed,
|
|
74
81
|
})
|
|
@@ -76,17 +83,27 @@ export function createAgentSupervisorProviderFactory(
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
86
|
+
private readonly attached = Promise.withResolvers<void>()
|
|
79
87
|
private state: SupervisorAgentRunSnapshot = { status: 'attached', agentState: null }
|
|
80
88
|
private emit: ((body: WireEventBody) => void) | undefined
|
|
81
89
|
private readonly pendingEvents: WireEventBody[] = []
|
|
82
90
|
private readonly eventPump: Promise<void>
|
|
91
|
+
private eventFailure: unknown
|
|
83
92
|
|
|
84
93
|
constructor(private readonly input: {
|
|
85
94
|
run: AgentRun
|
|
95
|
+
emit?: (body: WireEventBody) => void
|
|
96
|
+
onFatal?: (error: unknown) => void
|
|
97
|
+
eventProcessor?: AgentSupervisorProviderFactoryOptions['eventProcessor']
|
|
86
98
|
prepareInputAttachments?: AgentSupervisorProviderFactoryOptions['prepareInputAttachments']
|
|
87
99
|
onSecretsRefreshed?: AgentSupervisorProviderFactoryOptions['onSecretsRefreshed']
|
|
88
100
|
}) {
|
|
89
|
-
|
|
101
|
+
if (input.emit)
|
|
102
|
+
this.attachEmitter(input.emit)
|
|
103
|
+
this.eventPump = this.consumeEvents().catch((error: unknown) => {
|
|
104
|
+
this.eventFailure = error
|
|
105
|
+
input.onFatal?.(error)
|
|
106
|
+
})
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
snapshot(): SupervisorAgentRunSnapshot {
|
|
@@ -94,6 +111,8 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
async handle(input: Parameters<SupervisorAgentRuntime['handle']>[0]): Promise<void> {
|
|
114
|
+
if (this.eventFailure)
|
|
115
|
+
throw this.eventFailure
|
|
97
116
|
this.attachEmitter(input.emit)
|
|
98
117
|
const metadata = this.input.run.metadata
|
|
99
118
|
if (input.body.type === 'agent.send-prompt') {
|
|
@@ -154,6 +173,7 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
154
173
|
}
|
|
155
174
|
|
|
156
175
|
async close(input: { signal: AbortSignal }): Promise<void> {
|
|
176
|
+
this.attached.resolve()
|
|
157
177
|
const metadata = this.input.run.metadata
|
|
158
178
|
await this.input.run.stop({
|
|
159
179
|
type: 'agent.stop',
|
|
@@ -167,15 +187,25 @@ class AgentSupervisorRuntime implements SupervisorAgentRuntime {
|
|
|
167
187
|
|
|
168
188
|
private attachEmitter(emit: (body: WireEventBody) => void): void {
|
|
169
189
|
this.emit = emit
|
|
190
|
+
this.attached.resolve()
|
|
170
191
|
for (const event of this.pendingEvents.splice(0))
|
|
171
192
|
emit(event)
|
|
172
193
|
}
|
|
173
194
|
|
|
174
195
|
private async consumeEvents(): Promise<void> {
|
|
196
|
+
if (this.input.eventProcessor) {
|
|
197
|
+
await this.attached.promise
|
|
198
|
+
if (!this.emit)
|
|
199
|
+
return
|
|
200
|
+
await this.input.eventProcessor.recover(body => this.emit!(body))
|
|
201
|
+
}
|
|
175
202
|
for await (const event of this.input.run.events) {
|
|
176
203
|
if (event.type === 'session.state.changed')
|
|
177
204
|
this.state = { status: 'attached', agentState: event.payload.state }
|
|
178
|
-
this.
|
|
205
|
+
if (this.input.eventProcessor)
|
|
206
|
+
await this.input.eventProcessor.process(event, body => this.emit!(body))
|
|
207
|
+
else
|
|
208
|
+
this.emitEvent(event)
|
|
179
209
|
}
|
|
180
210
|
}
|
|
181
211
|
|
|
@@ -34,7 +34,12 @@ export function createBootstrapBinder(input: {
|
|
|
34
34
|
shutdown: (code: number) => void
|
|
35
35
|
logger: Logger
|
|
36
36
|
}): BootstrapBinder {
|
|
37
|
+
let pending = Promise.resolve()
|
|
37
38
|
return async (request) => {
|
|
39
|
+
const previous = pending
|
|
40
|
+
const released = Promise.withResolvers<void>()
|
|
41
|
+
pending = released.promise
|
|
42
|
+
await previous
|
|
38
43
|
try {
|
|
39
44
|
return await bind(request)
|
|
40
45
|
}
|
|
@@ -55,6 +60,9 @@ export function createBootstrapBinder(input: {
|
|
|
55
60
|
terminate: () => input.shutdown(TERMINAL_ERROR_CODE),
|
|
56
61
|
}
|
|
57
62
|
}
|
|
63
|
+
finally {
|
|
64
|
+
released.resolve()
|
|
65
|
+
}
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
async function bind(request: {
|
|
@@ -27,6 +27,8 @@ export interface SupervisorProviderFactory {
|
|
|
27
27
|
gitToken?: SessionBootstrapGitToken
|
|
28
28
|
rpc: SupervisorRpcClient
|
|
29
29
|
signal: AbortSignal
|
|
30
|
+
emit?: (body: WireEventBody) => void
|
|
31
|
+
onFatal?: (error: unknown) => void
|
|
30
32
|
}) => Promise<SupervisorAgentRuntime>
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -185,6 +185,8 @@ export class SupervisorRuntimeHandler implements RuntimeConnectionHandler {
|
|
|
185
185
|
...initialSecrets,
|
|
186
186
|
rpc: this.rpc,
|
|
187
187
|
signal: this.lifecycleController.signal,
|
|
188
|
+
emit: body => this.emit(body),
|
|
189
|
+
onFatal: error => this.dependencies.onFatal(error),
|
|
188
190
|
}).then(async (agent) => {
|
|
189
191
|
if ((this.dependencies.credentials !== initialSecrets.credentials
|
|
190
192
|
|| this.dependencies.gitToken !== initialSecrets.gitToken) && agent.refreshSecrets) {
|
|
@@ -381,6 +381,7 @@ export class ResidentSupervisorWebSocketServer {
|
|
|
381
381
|
return
|
|
382
382
|
input.socket.data.terminalRejection = undefined
|
|
383
383
|
input.socket.close(internalErrorCloseCode, 'Supervisor rejection receipt was not observed.')
|
|
384
|
+
input.terminate()
|
|
384
385
|
}, terminalRejectionReceiptBudgetMs)
|
|
385
386
|
timeout.unref?.()
|
|
386
387
|
input.socket.data.terminalRejection = { terminate: input.terminate, timeout }
|
|
@@ -26,6 +26,7 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
26
26
|
readonly ephemeralEventLog: Array<Parameters<EphemeralEventSink['publish']>[0]> = []
|
|
27
27
|
readonly readySnapshots: SupervisorAgentRunSnapshot[] = []
|
|
28
28
|
readonly projectedCommandSequences: number[] = []
|
|
29
|
+
readonly failedCommandSequences: number[] = []
|
|
29
30
|
readonly failureLog: Array<{ kind: string, code?: string, detail?: string }> = []
|
|
30
31
|
readonly rpcCalls: SupervisorRpcRequestEnvelope[] = []
|
|
31
32
|
onDurableRecord: ((event: DurableEventEnvelope) => void | Promise<void>) | undefined
|
|
@@ -105,7 +106,7 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
105
106
|
return {
|
|
106
107
|
renewed,
|
|
107
108
|
...(renewed ? { leaseExpiresAt: new Date(Date.now() + 30_000) } : {}),
|
|
108
|
-
hasOutstandingCommand: this.projectedCommandSequences.length < this.commandLog.length
|
|
109
|
+
hasOutstandingCommand: this.projectedCommandSequences.length + this.failedCommandSequences.length < this.commandLog.length,
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -131,10 +132,6 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
131
132
|
return this.claimed && input.generation === this.generation
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
async projectAppliedCommand(input: { commandSeq: number }): Promise<void> {
|
|
135
|
-
this.projectedCommandSequences.push(input.commandSeq)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
135
|
async initial(): Promise<CommandEnvelope | undefined> {
|
|
139
136
|
return this.commandLog[0]
|
|
140
137
|
}
|
|
@@ -157,6 +154,10 @@ EphemeralEventSink, RpcDispatcher, BootstrapProvider, SessionFailureSink, Runtim
|
|
|
157
154
|
return { status: 'duplicate', highWaterMark: this.ackFloor }
|
|
158
155
|
if (event.seq !== this.ackFloor + 1)
|
|
159
156
|
return { status: 'gap' }
|
|
157
|
+
if (event.body.type === 'supervisor.command.applied')
|
|
158
|
+
this.projectedCommandSequences.push(event.body.payload.commandSeq)
|
|
159
|
+
if (event.body.type === 'supervisor.command.failed')
|
|
160
|
+
this.failedCommandSequences.push(event.body.payload.commandSeq)
|
|
160
161
|
this.durableEventLog.push(event)
|
|
161
162
|
this.ackFloor = event.seq
|
|
162
163
|
await this.onDurableRecord?.(event)
|
|
@@ -98,11 +98,6 @@ export class SessionCommandPump {
|
|
|
98
98
|
return
|
|
99
99
|
}
|
|
100
100
|
this.afterSequence = ack.commandSeq
|
|
101
|
-
await this.dependencies.authorityStore.projectAppliedCommand({
|
|
102
|
-
sessionSandboxId: this.dependencies.sessionSandboxId,
|
|
103
|
-
runtimeConnectionId: this.dependencies.runtimeConnectionId,
|
|
104
|
-
commandSeq: ack.commandSeq,
|
|
105
|
-
})
|
|
106
101
|
}
|
|
107
102
|
}
|
|
108
103
|
}
|
|
@@ -31,6 +31,7 @@ export class SessionSandboxConnectionAttempt {
|
|
|
31
31
|
|
|
32
32
|
constructor(private readonly dependencies: {
|
|
33
33
|
claim: ConnectionClaim
|
|
34
|
+
establishmentDeadline: number
|
|
34
35
|
replicaId: string
|
|
35
36
|
trafficTokenEncryptionKey: string
|
|
36
37
|
signControlAuthority: ControlAuthoritySigner
|
|
@@ -96,7 +97,7 @@ export class SessionSandboxConnectionAttempt {
|
|
|
96
97
|
signal: this.controller.signal,
|
|
97
98
|
onMessage: frame => this.processor ? this.processor.receive(frame) : queuedFrames.push(frame),
|
|
98
99
|
onClose: close => closed.resolve(close),
|
|
99
|
-
}), performance.now() + this.dependencies.timings.dialTimeoutMs, this.controller.signal, () => new ConnectionClosedError({
|
|
100
|
+
}), Math.min(this.dependencies.establishmentDeadline, performance.now() + this.dependencies.timings.dialTimeoutMs), this.controller.signal, () => new ConnectionClosedError({
|
|
100
101
|
ready: false,
|
|
101
102
|
detail: 'Supervisor connection dial timed out.',
|
|
102
103
|
}))
|
|
@@ -160,7 +161,15 @@ export class SessionSandboxConnectionAttempt {
|
|
|
160
161
|
...secrets,
|
|
161
162
|
}))
|
|
162
163
|
|
|
163
|
-
const readiness = await
|
|
164
|
+
const readiness = await beforeDeadline(Promise.race([
|
|
165
|
+
processor.waitForReadiness({ signal: this.controller.signal }),
|
|
166
|
+
closed.promise.then((close) => {
|
|
167
|
+
throw new ConnectionClosedError({ ready: false, detail: `${close.code}:${close.reason}` })
|
|
168
|
+
}),
|
|
169
|
+
]), Math.min(this.dependencies.establishmentDeadline, performance.now() + 10_000), this.controller.signal, () => new ConnectionClosedError({
|
|
170
|
+
ready: false,
|
|
171
|
+
detail: 'Supervisor bootstrap timed out.',
|
|
172
|
+
})).catch(async (error: unknown) => {
|
|
164
173
|
if (error instanceof BootstrapRejectedAckError) {
|
|
165
174
|
await processor.acknowledgeBootstrapRejection(error.ack)
|
|
166
175
|
throw new DeterministicEstablishmentError(error.ack.errorCode, error.ack.detail, true)
|
|
@@ -44,7 +44,6 @@ export interface ConnectionAuthorityStore {
|
|
|
44
44
|
markConnectionReconnecting: (input: { sessionSandboxId: string, generation: number }) => Promise<void>
|
|
45
45
|
releaseConnection: (input: { sessionSandboxId: string, generation: number, reason: string }) => Promise<void>
|
|
46
46
|
markConnectionRetryExhausted: (input: { sessionSandboxId: string, generation: number, replicaId: string }) => Promise<boolean>
|
|
47
|
-
projectAppliedCommand: (input: { sessionSandboxId: string, runtimeConnectionId: string, commandSeq: number }) => Promise<void>
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
export interface CommandSource {
|
|
@@ -58,6 +57,9 @@ export type DurableEventResult
|
|
|
58
57
|
|
|
59
58
|
export interface DurableEventSink {
|
|
60
59
|
getAckFloor: (input: { sessionSandboxId: string }) => Promise<number>
|
|
60
|
+
// Atomically persist the event, advance the ACK floor, and project its command outcome.
|
|
61
|
+
// Only supervisor.command.applied marks a command applied; received ACKs never do.
|
|
62
|
+
// Replayed events must not duplicate projections, including after reconnect.
|
|
61
63
|
record: (input: SessionSandboxRef & { runtimeConnectionId: string, event: DurableEventEnvelope }) => Promise<DurableEventResult>
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -154,6 +154,7 @@ class OwnedConnection {
|
|
|
154
154
|
while (!this.controller.signal.aborted) {
|
|
155
155
|
const attempt = new SessionSandboxConnectionAttempt({
|
|
156
156
|
claim,
|
|
157
|
+
establishmentDeadline,
|
|
157
158
|
replicaId: this.dependencies.replicaId,
|
|
158
159
|
trafficTokenEncryptionKey: this.dependencies.trafficTokenEncryptionKey,
|
|
159
160
|
signControlAuthority: this.dependencies.signControlAuthority,
|