Files
Meldwork/desktop/test/agents/cloud/cloud-agent-runtime.test.cjs
T
Ryder Sun ec6674d96a feat: ship concurrent collaboration harness
Implement frozen concurrent responses, Agent-negotiated V4 discussion work, strict durable receipts and recovery, validated runtime phase UI, and the animated return-to-bottom control. Prepare the 0.1.2 prerelease metadata and documentation.\n\nChecks before commit: frontend 299/299; desktop 1270/1270; deterministic eval 6 cases/18 results; web and desktop renderer builds; desktop pack; staged and unstaged diff checks.
2026-08-17 11:35:32 +08:00

885 lines
32 KiB
JavaScript

const assert = require('node:assert/strict')
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const test = require('node:test')
const { RUN_EVENT_TYPES } = require('../../../src/agents/connectors/agent-connector-manifest.cjs')
const { CloudAgentOperationStore } = require('../../../src/agents/cloud/cloud-agent-operation-store.cjs')
const {
CloudAgentRuntime,
createCloudAgentStartRetry,
} = require('../../../src/agents/cloud/cloud-agent-runtime.cjs')
const { ContentBlobStore } = require('../../../src/attachments/content-blob-store.cjs')
const { createArtifactRecord } = require('../../../src/collaboration/outcome-records.cjs')
const { OutcomeStore } = require('../../../src/collaboration/outcome-store.cjs')
const { RunLedger } = require('../../../src/runs/run-ledger.cjs')
function connectorSnapshot() {
return {
schemaVersion: 1,
connectorId: 'cloud.mock',
connectorVersion: '1.0.0',
manifestId: `connector-manifest-${'a'.repeat(64)}`,
instanceId: 'cloud.mock.account',
upstreamId: 'mock-cloud-service',
upstreamVersion: '2.0.0',
recipeId: 'cloud.mock.recipe',
transport: { type: 'http', protocol: 'event-stream' },
capabilities: {
domains: ['coding'],
session: { supported: true, resume: true, cancel: true, checkpoint: true },
inputTypes: ['text'],
permissionModes: ['read-only', 'workspace-write'],
eventProtocolVersion: 1,
eventTypes: [...RUN_EVENT_TYPES],
usage: {
inputTokens: true, outputTokens: true, costMicros: true,
toolCalls: true, outboundBytes: true, elapsedMs: true,
},
outboundDestinations: ['https://cloud.example'],
},
}
}
function event(sequence, type, fields = {}) {
return {
eventId: `remote-event-${sequence}`,
cursor: `event-cursor-${sequence}`,
sequence,
type,
...fields,
}
}
function fixture(t) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-cloud-runtime-'))
t.after(() => fs.rmSync(directory, { recursive: true, force: true }))
const contentBlobStore = new ContentBlobStore({
rootPath: path.join(directory, 'private', 'content-blobs'),
})
return {
directory,
ledgerPath: path.join(directory, 'private', 'run-ledger.json'),
operationPath: path.join(directory, 'private', 'cloud-agent-operations.json'),
contentBlobStore,
outcomeStore: new OutcomeStore({
rootPath: path.join(directory, 'private', 'outcomes'),
contentBlobStore,
}),
}
}
function seedRun(ledger, runId = 'run-cloud', agentRunId = 'agent-cloud') {
return ledger.checkpoint({
runId,
taskId: `${runId}-task`,
contextPackId: `context-pack-${'b'.repeat(64)}`,
contextPackState: 'captured',
groupId: 'group-cloud',
threadRootId: 'thread-cloud',
mode: 'manual',
targetKinds: ['cloud-agent'],
status: 'running',
permissionMode: 'read-only',
agentRuns: [{
agentRunId, kind: 'cloud-agent', round: 1, status: 'running',
}],
})
}
function mockConnector(overrides = {}) {
const calls = {
submit: [], observe: [], reconcile: [], input: [], permission: [], cancel: [], artifacts: [],
}
const observations = []
const nextObservation = request => observations.shift() || { cursor: request.cursor, events: [] }
return {
calls,
observations,
connector: {
snapshot: connectorSnapshot(),
submit: async request => {
calls.submit.push(request)
return overrides.submit ? overrides.submit(request) : { jobId: 'job-cloud-1', cursor: 'poll-cursor-0' }
},
observe: async request => {
calls.observe.push(request)
return overrides.observe ? overrides.observe(request) : nextObservation(request)
},
reconcile: async request => {
calls.reconcile.push(request)
return overrides.reconcile ? overrides.reconcile(request) : nextObservation(request)
},
provideInput: async request => {
calls.input.push(request)
return overrides.provideInput?.(request)
},
decidePermission: async request => {
calls.permission.push(request)
return overrides.decidePermission?.(request)
},
cancel: async request => {
calls.cancel.push(request)
return overrides.cancel?.(request)
},
fetchArtifacts: async request => {
calls.artifacts.push(request)
return overrides.fetchArtifacts ? overrides.fetchArtifacts(request) : []
},
},
}
}
function runtimeFor(value, ledger, connector, options = {}) {
return new CloudAgentRuntime({
runLedger: ledger,
outcomeStore: value.outcomeStore,
contentBlobStore: value.contentBlobStore,
operationStore: new CloudAgentOperationStore({ storagePath: value.operationPath }),
connectors: connector ? [connector] : [],
autoObserve: false,
...options,
})
}
async function waitFor(predicate, timeoutMs = 2000) {
const startedAt = Date.now()
while (!predicate()) {
if (Date.now() - startedAt > timeoutMs) throw new Error('WAIT_FOR_TIMEOUT')
await new Promise(resolve => setTimeout(resolve, 5))
}
}
test('submits durably, resumes cursor recovery, and persists input and permission answers', async (t) => {
const value = fixture(t)
const mock = mockConnector()
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector)
const submitted = await runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Review the release.', permissionMode: 'read-only',
})
assert.equal(submitted.status, 'running')
assert.equal(submitted.jobId, 'job-cloud-1')
assert.equal(mock.calls.observe.length, 0)
assert.equal(ledger.get('run-cloud').remoteJob.jobId, 'job-cloud-1')
mock.observations.push({
cursor: 'poll-cursor-1',
events: [event(0, 'WaitingInput', {
requestId: 'question-1', prompt: 'Which branch should receive the result?',
})],
})
const waitingInput = await runtime.observeNow('run-cloud')
assert.equal(waitingInput.status, 'waiting_input')
await runtime.provideInput('run-cloud', 'question-1', 'release/next')
assert.equal(mock.calls.input.length, 1)
assert.equal(mock.calls.input[0].idempotencyKey.startsWith('cloud-operation-'), true)
mock.observations.push({
cursor: 'poll-cursor-2',
events: [event(1, 'Permission', {
requestId: 'permission-1', permission: 'workspace-write', decision: 'requested',
summary: 'Apply the reviewed patch.',
})],
})
const waitingPermission = await runtime.observeNow('run-cloud')
assert.equal(waitingPermission.status, 'waiting_permission')
await runtime.shutdown()
const restartedLedger = new RunLedger({ storagePath: value.ledgerPath })
const restarted = runtimeFor(value, restartedLedger, mock.connector)
restarted.workspaceLedger().recoverInterrupted()
const recoveries = await restarted.start()
assert.equal(recoveries[0].jobId, 'job-cloud-1')
assert.equal(recoveries[0].cursor, 'poll-cursor-2')
assert.equal(restartedLedger.get('run-cloud').status, 'reconciling')
await restarted.decidePermission('run-cloud', 'permission-1', 'approved')
assert.equal(mock.calls.permission.length, 1)
mock.observations.push({
cursor: 'poll-cursor-3',
events: [
event(2, 'Permission', {
requestId: 'permission-1', permission: 'workspace-write', decision: 'approved',
}),
event(3, 'Completed', { outcome: 'completed', summary: 'Cloud review completed.' }),
],
})
const completed = await restarted.observeNow('run-cloud', { reconcile: true })
assert.equal(completed.status, 'completed')
assert.equal(mock.calls.reconcile[0].cursor, 'poll-cursor-2')
assert.equal(restartedLedger.get('run-cloud').remoteJob.recoveryOwnerId, '')
const operations = new CloudAgentOperationStore({ storagePath: value.operationPath }).list()
assert.deepEqual(operations.map(item => [item.type, item.state]), [
['submit', 'delivered'], ['input', 'delivered'], ['permission', 'delivered'],
])
})
test('recovers the same submitted job when its Run Ledger checkpoint fails', async (t) => {
const value = fixture(t)
const mock = mockConnector({
submit: () => ({
jobId: 'job-cloud-durable',
cursor: 'poll-cursor-durable',
events: [event(0, 'Usage', { mode: 'delta', usage: { inputTokens: 7 } })],
}),
})
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const checkpoint = ledger.checkpoint.bind(ledger)
let failRemoteCheckpoint = true
ledger.checkpoint = (record) => {
if (record.remoteJob && failRemoteCheckpoint) {
failRemoteCheckpoint = false
throw new Error('SIMULATED_CRASH_BEFORE_REMOTE_JOB_CHECKPOINT')
}
return checkpoint(record)
}
const runtime = runtimeFor(value, ledger, mock.connector)
const prompt = 'Private release plan that must not enter the operation journal.'
await assert.rejects(
runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud', prompt,
}),
{ message: 'SIMULATED_CRASH_BEFORE_REMOTE_JOB_CHECKPOINT' },
)
assert.equal(mock.calls.submit.length, 1)
assert.equal(ledger.get('run-cloud').remoteJob, undefined)
assert.equal(fs.readFileSync(value.operationPath, 'utf8').includes(prompt), false)
const [intent] = new CloudAgentOperationStore({
storagePath: value.operationPath,
}).submissionIntents()
assert.equal(intent.state, 'dispatched')
assert.equal(intent.jobId, 'job-cloud-durable')
assert.equal(mock.calls.submit[0].idempotencyKey, intent.operationId)
await runtime.shutdown()
const restartedLedger = new RunLedger({ storagePath: value.ledgerPath })
const restarted = runtimeFor(value, restartedLedger, mock.connector)
assert.deepEqual(restarted.workspaceLedger().recoverInterrupted(), [])
assert.equal(restartedLedger.get('run-cloud').status, 'running')
assert.equal(restartedLedger.get('run-cloud').agentRuns[0].status, 'running')
const [recovery] = await restarted.start()
assert.equal(mock.calls.submit.length, 1)
assert.equal(recovery.jobId, 'job-cloud-durable')
assert.equal(restartedLedger.get('run-cloud').status, 'reconciling')
assert.equal(
restartedLedger.get('run-cloud').agentRuns[0].context.connectorEventState.lastSequence,
0,
)
assert.equal(new CloudAgentOperationStore({
storagePath: value.operationPath,
}).list().find(operation => operation.operationId === intent.operationId).state, 'delivered')
await restarted.shutdown()
})
test('retries a pending submit with the same idempotency key after result journaling fails', async (t) => {
const value = fixture(t)
const mock = mockConnector({
submit: () => ({ jobId: 'job-cloud-idempotent', cursor: 'poll-cursor-idempotent' }),
})
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector)
const recordSubmitResult = runtime.operationStore.recordSubmitResult.bind(runtime.operationStore)
let failResultJournal = true
runtime.operationStore.recordSubmitResult = (...args) => {
if (failResultJournal) {
failResultJournal = false
throw new Error('SIMULATED_CRASH_AFTER_REMOTE_SUBMIT')
}
return recordSubmitResult(...args)
}
await assert.rejects(
runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Replay the persisted submission intent.',
}),
{ message: 'SIMULATED_CRASH_AFTER_REMOTE_SUBMIT' },
)
const [intent] = new CloudAgentOperationStore({
storagePath: value.operationPath,
}).submissionIntents()
assert.equal(intent.state, 'pending')
await runtime.shutdown()
const restarted = runtimeFor(
value,
new RunLedger({ storagePath: value.ledgerPath }),
mock.connector,
)
const [recovery] = await restarted.start()
assert.equal(recovery.jobId, 'job-cloud-idempotent')
assert.equal(mock.calls.submit.length, 2)
assert.equal(mock.calls.submit[0].idempotencyKey, intent.operationId)
assert.equal(mock.calls.submit[1].idempotencyKey, intent.operationId)
assert.equal(new CloudAgentOperationStore({
storagePath: value.operationPath,
}).list().find(operation => operation.operationId === intent.operationId).state, 'delivered')
await restarted.shutdown()
})
test('completes a submit intent after the ledger persisted and initial events stay idempotent', async (t) => {
const value = fixture(t)
const mock = mockConnector({
submit: () => ({
jobId: 'job-cloud-checkpointed',
cursor: 'poll-cursor-checkpointed',
events: [event(0, 'Usage', { mode: 'delta', usage: { outputTokens: 5 } })],
}),
})
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector)
const update = runtime.operationStore.update.bind(runtime.operationStore)
let failDeliveryAck = true
runtime.operationStore.update = (operationId, state) => {
const operation = runtime.operationStore.list()
.find(item => item.operationId === operationId)
if (operation?.type === 'submit' && state === 'delivered' && failDeliveryAck) {
failDeliveryAck = false
throw new Error('SIMULATED_CRASH_AFTER_REMOTE_JOB_CHECKPOINT')
}
return update(operationId, state)
}
await assert.rejects(
runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Keep the initial event batch idempotent.',
}),
{ message: 'SIMULATED_CRASH_AFTER_REMOTE_JOB_CHECKPOINT' },
)
const beforeRestart = ledger.get('run-cloud')
assert.equal(beforeRestart.remoteJob.jobId, 'job-cloud-checkpointed')
assert.equal(beforeRestart.agentRuns[0].context.connectorEventState.events.length, 1)
const [intent] = new CloudAgentOperationStore({
storagePath: value.operationPath,
}).submissionIntents()
assert.equal(intent.state, 'dispatched')
await runtime.shutdown()
const restartedLedger = new RunLedger({ storagePath: value.ledgerPath })
const restarted = runtimeFor(value, restartedLedger, mock.connector)
const [recovery] = await restarted.start()
assert.equal(recovery.jobId, 'job-cloud-checkpointed')
assert.equal(mock.calls.submit.length, 1)
assert.equal(
restartedLedger.get('run-cloud').agentRuns[0].context.connectorEventState.events.length,
1,
)
assert.equal(new CloudAgentOperationStore({
storagePath: value.operationPath,
}).list().find(operation => operation.operationId === intent.operationId).state, 'delivered')
await restarted.shutdown()
})
test('retries the second ledger reconciliation phase before delivering a submit intent', async (t) => {
const value = fixture(t)
const mock = mockConnector({
submit: () => ({
jobId: 'job-cloud-terminal',
cursor: 'poll-cursor-terminal',
events: [event(0, 'Completed', { outcome: 'completed' })],
}),
})
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const reconcileRemote = ledger.reconcileRemote.bind(ledger)
let failReconcile = true
ledger.reconcileRemote = (...args) => {
if (failReconcile) {
failReconcile = false
throw new Error('SIMULATED_SECOND_PHASE_RECONCILE_FAILURE')
}
return reconcileRemote(...args)
}
const runtime = runtimeFor(value, ledger, mock.connector)
await assert.rejects(
runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Finish only after both ledger phases persist.',
}),
{ message: 'SIMULATED_SECOND_PHASE_RECONCILE_FAILURE' },
)
assert.equal(ledger.get('run-cloud').status, 'running')
assert.equal(ledger.get('run-cloud').agentRuns[0].status, 'completed')
const [intent] = new CloudAgentOperationStore({
storagePath: value.operationPath,
}).submissionIntents()
assert.equal(intent.state, 'dispatched')
await runtime.shutdown()
const restartedLedger = new RunLedger({ storagePath: value.ledgerPath })
const restarted = runtimeFor(value, restartedLedger, mock.connector)
assert.deepEqual(await restarted.start(), [])
assert.equal(mock.calls.submit.length, 1)
assert.equal(restartedLedger.get('run-cloud').status, 'completed')
assert.equal(new CloudAgentOperationStore({
storagePath: value.operationPath,
}).list().find(operation => operation.operationId === intent.operationId).state, 'delivered')
await restarted.shutdown()
})
test('main-owned observation continues after its renderer-style subscriber detaches', async (t) => {
const value = fixture(t)
let resolveObservation
let observationStarted
const observationStartedPromise = new Promise(resolve => { observationStarted = resolve })
const mock = mockConnector({
observe: request => {
observationStarted(request)
return new Promise(resolve => { resolveObservation = resolve })
},
})
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector, {
autoObserve: true,
pollIntervalMs: 5,
setTimer: (callback, delay) => ({ handle: setTimeout(callback, delay) }),
clearTimer: timer => clearTimeout(timer.handle),
})
const observed = []
const detach = runtime.subscribe(snapshot => observed.push(snapshot))
const submitted = await runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Finish later.',
})
assert.equal(submitted.status, 'running')
const request = await observationStartedPromise
assert.equal(request.jobId, 'job-cloud-1')
detach()
resolveObservation({
cursor: 'poll-cursor-1',
events: [event(0, 'Completed', { outcome: 'completed' })],
})
await waitFor(() => ledger.get('run-cloud').status === 'completed')
assert.equal(runtime.publicSnapshot('run-cloud').status, 'completed')
assert.equal(observed.at(-1).status, 'running')
await runtime.shutdown()
})
test('shutdown stays bounded when a Cloud Connector ignores cancellation', async (t) => {
const value = fixture(t)
let observedSignal = null
let observationStarted
const observationStartedPromise = new Promise(resolve => { observationStarted = resolve })
const mock = mockConnector({
observe: request => {
observedSignal = request.signal
observationStarted()
return new Promise(() => {})
},
})
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector, { shutdownGraceMs: 10 })
await runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Ignore cancellation for this test.',
})
runtime.observeNow('run-cloud')
await observationStartedPromise
const startedAt = Date.now()
await runtime.shutdown()
assert.equal(observedSignal.aborted, true)
assert.equal(Date.now() - startedAt < 500, true)
})
test('deduplicates out-of-order callbacks, Artifacts, and terminal transitions', async (t) => {
const value = fixture(t)
const artifact = createArtifactRecord({
type: 'link',
name: 'Remote review report',
producedBy: { runId: 'run-cloud', agentRunId: 'agent-cloud', agentKind: 'cloud-agent' },
locationRef: { kind: 'uri', uri: 'https://cloud.example/reports/1' },
})
const mock = mockConnector({ fetchArtifacts: async () => [artifact] })
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const reconcileRemote = ledger.reconcileRemote.bind(ledger)
let terminalTransitions = 0
ledger.reconcileRemote = (runId, ownerId, update) => {
if (update.status === 'completed') terminalTransitions += 1
return reconcileRemote(runId, ownerId, update)
}
const runtime = runtimeFor(value, ledger, mock.connector)
await runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Produce a report.',
})
const terminalBatch = {
cursor: 'poll-cursor-2',
events: [
event(2, 'Completed', { outcome: 'completed' }),
event(1, 'Artifact', { artifactId: artifact.artifactId }),
event(1, 'Artifact', { artifactId: artifact.artifactId }),
],
}
mock.observations.push(terminalBatch)
const observed = await runtime.observeNow('run-cloud')
assert.equal(observed.status, 'completed')
assert.equal(mock.calls.artifacts.length, 1)
assert.equal(terminalTransitions, 1)
assert.deepEqual(value.outcomeStore.getArtifact(artifact.artifactId), artifact)
await runtime.handleCallback('cloud.mock', 'job-cloud-1', terminalBatch)
await runtime.handleCallback('cloud.mock', 'job-cloud-1', {
cursor: 'older-cursor',
events: [event(0, 'Usage', { mode: 'delta', usage: { inputTokens: 4 } })],
})
const state = ledger.get('run-cloud').agentRuns[0].context.connectorEventState
assert.equal(state.events.length, 3)
assert.deepEqual(state.artifactIds, [artifact.artifactId])
assert.equal(mock.calls.artifacts.length, 1)
assert.equal(terminalTransitions, 1)
assert.equal(ledger.get('run-cloud').remoteJob.cursor, 'poll-cursor-2')
})
test('explicit cancel calls the remote connector at most once across restart', async (t) => {
const value = fixture(t)
const mock = mockConnector()
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector)
await runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Long-running job.',
})
assert.equal(await runtime.cancel('run-cloud'), true)
assert.equal(mock.calls.cancel.length, 1)
await runtime.shutdown()
const restartedLedger = new RunLedger({ storagePath: value.ledgerPath })
const restarted = runtimeFor(value, restartedLedger, mock.connector)
restarted.workspaceLedger().recoverInterrupted()
await restarted.start()
assert.equal(await restarted.cancel('run-cloud'), false)
assert.equal(mock.calls.cancel.length, 1)
})
test('replays a dispatched cancel after crash with the same idempotency key', async (t) => {
const value = fixture(t)
const mock = mockConnector()
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector)
await runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Cancel idempotently after restart.',
})
const update = runtime.operationStore.update.bind(runtime.operationStore)
let failDeliveryAck = true
runtime.operationStore.update = (operationId, state) => {
if (state === 'delivered' && failDeliveryAck) {
failDeliveryAck = false
throw new Error('SIMULATED_CRASH_AFTER_CANCEL_DISPATCH')
}
return update(operationId, state)
}
await assert.rejects(
runtime.cancel('run-cloud'),
{ message: 'SIMULATED_CRASH_AFTER_CANCEL_DISPATCH' },
)
assert.equal(mock.calls.cancel.length, 1)
const [dispatched] = new CloudAgentOperationStore({
storagePath: value.operationPath,
}).list().filter(operation => operation.type === 'cancel')
assert.equal(dispatched.state, 'dispatched')
assert.equal(mock.calls.cancel[0].idempotencyKey, dispatched.operationId)
await runtime.shutdown()
const restartedLedger = new RunLedger({ storagePath: value.ledgerPath })
const restarted = runtimeFor(value, restartedLedger, mock.connector)
await restarted.start()
assert.equal(mock.calls.cancel.length, 2)
assert.equal(mock.calls.cancel[1].idempotencyKey, dispatched.operationId)
assert.equal(new CloudAgentOperationStore({
storagePath: value.operationPath,
}).list().find(operation => operation.operationId === dispatched.operationId).state, 'delivered')
await restarted.shutdown()
})
test('start remains retryable when Run recovery fails', async (t) => {
const value = fixture(t)
const ledger = new RunLedger({ storagePath: value.ledgerPath })
const runtime = runtimeFor(value, ledger, null)
const recoverInterrupted = ledger.recoverInterrupted.bind(ledger)
let failRecovery = true
ledger.recoverInterrupted = (options) => {
if (failRecovery) throw new Error('SIMULATED_CLOUD_RECOVERY_FAILURE')
return recoverInterrupted(options)
}
await assert.rejects(
runtime.start(),
{ message: 'SIMULATED_CLOUD_RECOVERY_FAILURE' },
)
assert.equal(runtime.started, false)
assert.equal(runtime.stopping, true)
assert.equal(runtime.timers.size, 0)
failRecovery = false
assert.deepEqual(await runtime.start(), [])
assert.equal(runtime.started, true)
assert.equal(runtime.stopping, false)
await runtime.shutdown()
assert.equal(runtime.started, false)
assert.equal(runtime.stopping, true)
})
test('start retries failed operation replay and restores observation', async (t) => {
const value = fixture(t)
const initialMock = mockConnector()
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const initial = runtimeFor(value, ledger, initialMock.connector)
await initial.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Recover a pending cancellation.',
})
initial.operationStore.put({
type: 'cancel', runId: 'run-cloud', connectorId: 'cloud.mock',
jobId: 'job-cloud-1', requestId: '', payload: {},
}, 'dispatched')
await initial.shutdown()
let failReplay = true
const mock = mockConnector({
cancel() {
if (failReplay) throw new Error('SIMULATED_CLOUD_REPLAY_FAILURE')
},
})
const scheduled = []
const restarted = runtimeFor(
value,
new RunLedger({ storagePath: value.ledgerPath }),
mock.connector,
{
autoObserve: true,
setTimer(callback, delay) {
const timer = { callback, delay, unref() {} }
scheduled.push(timer)
return timer
},
clearTimer(timer) { timer.cleared = true },
},
)
await assert.rejects(
restarted.start(),
{ message: 'SIMULATED_CLOUD_REPLAY_FAILURE' },
)
assert.equal(restarted.started, false)
assert.equal(restarted.stopping, true)
assert.equal(restarted.timers.size, 0)
failReplay = false
const recoveries = await restarted.start()
assert.equal(recoveries.length, 1)
assert.equal(restarted.started, true)
assert.equal(restarted.stopping, false)
assert.equal(restarted.timers.size, 1)
assert.equal(mock.calls.cancel.length, 2)
assert.equal(mock.calls.cancel[0].idempotencyKey, mock.calls.cancel[1].idempotencyKey)
await scheduled[0].callback()
assert.equal(mock.calls.reconcile.length, 1)
await restarted.shutdown()
assert.equal(restarted.started, false)
assert.equal(restarted.stopping, true)
assert.equal(restarted.timers.size, 0)
})
test('start clears partial recovery state when observation scheduling fails', async (t) => {
const value = fixture(t)
const mock = mockConnector()
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const initial = runtimeFor(value, ledger, mock.connector)
await initial.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Retry recovery scheduling.',
})
await initial.shutdown()
let failSchedule = true
const restarted = runtimeFor(
value,
new RunLedger({ storagePath: value.ledgerPath }),
mock.connector,
{
autoObserve: true,
setTimer(callback, delay) {
if (failSchedule) throw new Error('SIMULATED_CLOUD_SCHEDULE_FAILURE')
return { callback, delay, unref() {} }
},
clearTimer() {},
},
)
await assert.rejects(
restarted.start(),
{ message: 'SIMULATED_CLOUD_SCHEDULE_FAILURE' },
)
assert.equal(restarted.started, false)
assert.equal(restarted.stopping, true)
assert.equal(restarted.timers.size, 0)
failSchedule = false
assert.equal((await restarted.start()).length, 1)
assert.equal(restarted.started, true)
assert.equal(restarted.stopping, false)
assert.equal(restarted.timers.size, 1)
await restarted.shutdown()
assert.equal(restarted.started, false)
assert.equal(restarted.stopping, true)
assert.equal(restarted.timers.size, 0)
})
test('rejects a persisted Connector event state bound to another Run', async (t) => {
const value = fixture(t)
const mock = mockConnector()
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger)
const runtime = runtimeFor(value, ledger, mock.connector)
await runtime.submit({
connectorId: 'cloud.mock', runId: 'run-cloud', agentRunId: 'agent-cloud',
prompt: 'Keep provenance bound.',
})
const record = ledger.get('run-cloud')
const agentRun = record.agentRuns[0]
ledger.checkpoint({
runId: record.runId,
agentRuns: [{
...agentRun,
context: {
...agentRun.context,
connectorEventState: {
...agentRun.context.connectorEventState,
runId: 'run-forged',
},
},
}],
})
await assert.rejects(
runtime.observeNow('run-cloud'),
{ message: 'CLOUD_AGENT_RUN_PROVENANCE_MISMATCH' },
)
})
test('local-only recovery remains available with no configured Cloud Connector', async (t) => {
const value = fixture(t)
const ledger = new RunLedger({ storagePath: value.ledgerPath })
seedRun(ledger, 'run-local', 'agent-local')
const runtime = runtimeFor(value, ledger, null)
const changed = runtime.workspaceLedger().recoverInterrupted()
assert.equal(changed[0].status, 'interrupted')
assert.equal(changed[0].agentRuns[0].status, 'interrupted')
assert.deepEqual(await runtime.start(), [])
assert.equal(runtime.connectorIds().length, 0)
})
test('Cloud runtime startup retry is capped, deduplicated, identity-bound, and cancellable', async () => {
const scheduled = []
let attempts = 0
let active = true
const runtime = {
async start() {
attempts += 1
if (attempts < 3) throw new Error(`START_FAILURE_${attempts}`)
return []
},
}
const retry = createCloudAgentStartRetry({
runtime,
isActive: candidate => active && candidate === runtime,
baseDelayMs: 10,
maxDelayMs: 15,
setTimer(callback, delay) {
const timer = { callback, delay, unrefCalled: false, unref() { this.unrefCalled = true } }
scheduled.push(timer)
return timer
},
clearTimer(timer) { timer.cleared = true },
})
assert.equal(await retry.start(), false)
assert.equal(attempts, 1)
assert.equal(scheduled[0].delay, 10)
assert.equal(scheduled[0].unrefCalled, true)
assert.equal(await retry.start(), false)
assert.equal(attempts, 1)
await scheduled[0].callback()
assert.equal(attempts, 2)
assert.equal(scheduled[1].delay, 15)
await scheduled[1].callback()
assert.equal(attempts, 3)
assert.equal(scheduled.length, 2)
assert.equal(await retry.start(), true)
assert.equal(attempts, 3)
const staleTimers = []
let staleAttempts = 0
const staleRuntime = {
async start() {
staleAttempts += 1
throw new Error('STALE_RUNTIME_FAILURE')
},
}
const staleRetry = createCloudAgentStartRetry({
runtime: staleRuntime,
isActive: () => active,
baseDelayMs: 5,
setTimer(callback, delay) {
const timer = { callback, delay, unref() {} }
staleTimers.push(timer)
return timer
},
clearTimer(timer) { timer.cleared = true },
})
assert.equal(await staleRetry.start(), false)
active = false
await staleTimers[0].callback()
assert.equal(staleAttempts, 1)
active = true
const cancelledTimers = []
const cancelledRetry = createCloudAgentStartRetry({
runtime: staleRuntime,
isActive: () => active,
baseDelayMs: 5,
setTimer(callback, delay) {
const timer = { callback, delay, unref() {} }
cancelledTimers.push(timer)
return timer
},
clearTimer(timer) { timer.cleared = true },
})
assert.equal(await cancelledRetry.start(), false)
cancelledRetry.cancel()
assert.equal(cancelledTimers[0].cleared, true)
await cancelledTimers[0].callback()
assert.equal(staleAttempts, 2)
})