Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,66 @@ export function prepareStartingWorkerAuthority(
}
}

/**
* Custody for an agent terminal this worker-start just created, recorded at creation instead of
* after the agent boot wait. Until the row exists a keystroke into the booting pane finds no
* ownership to flip, so the takeover is silently dropped and a later `worker-release` closes the
* pane under the user.
*
* Ownership of a pane only; the Dispatch capability stays behind the boot wait, because authority
* must not be handed to a process that has not come up.
*/
export function recordCreatedWorkerTerminalCustody(
this: OrchestrationDb,
params: {
dispatchId: string
handle: string
paneKey: string
processIncarnation: string
worktreeId: string
hostScope?: string | null
}
): void {
this.db.exec('BEGIN IMMEDIATE')
try {
// Same guard as prepareStartingWorkerAuthority, read inside the transaction: a dispatch stopped
// while the terminal was being created must not acquire an owner.
const dispatch = this.getDispatchContextById(params.dispatchId)
const worker = this.getWorkerDispatch(params.dispatchId)
if (!dispatch || dispatch.status !== 'pending' || worker?.state !== 'starting') {
throw new OrchestrationError(
'dispatch_inactive',
`Dispatch ${params.dispatchId} is not starting.`
)
}
if (!this.getWorkerTerminalResourceByOwner(params.dispatchId)) {
this.createWorkerTerminalResourceStatement({
dispatchId: params.dispatchId,
worktreeId: params.worktreeId,
terminalHandle: params.handle,
paneKey: params.paneKey,
processIncarnation: params.processIncarnation,
endpointId: worker.runtime_epoch,
endpointIncarnation: params.processIncarnation,
hostScope: params.hostScope,
ownership: 'owned'
})
}
this.db.exec('COMMIT')
} catch (error) {
this.db.exec('ROLLBACK')
throw error
}
}

export type WorkerDispatchAuthorityMethods = {
prepareStartingWorkerAuthority: typeof prepareStartingWorkerAuthority
recordCreatedWorkerTerminalCustody: typeof recordCreatedWorkerTerminalCustody
}

export function attachWorkerDispatchAuthority(ctor: { prototype: object }): void {
Object.assign(ctor.prototype, {
prepareStartingWorkerAuthority
prepareStartingWorkerAuthority,
recordCreatedWorkerTerminalCustody
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import type { WorkerDispatchRow } from '../../types'
import { OrchestrationError } from '../../orchestration-error'
import type { OrchestrationDb } from '../orchestration-db'
import { transitionLifecycleWithDb } from '../lifecycle-transition'
import {
adoptFailedStartTerminal,
type FailedStartTerminalAdoption
} from '../worker-terminal/failed-start-terminal-adoption'
import { recordFailedStartDispatchIdentity } from '../worker-terminal/failed-start-dispatch-identity'

export function markWorkerDispatchReady(
this: OrchestrationDb,
Expand Down Expand Up @@ -51,11 +48,7 @@ export function failWorkerStart(
// Why (#16095): revocation exists to stop a worker acting on a dispatch that never landed. A
// prompt whose turn start went unobserved provably landed, so its worker keeps the authority its
// own report needs.
options: {
retainCapability?: boolean
/** A start that died before authority attached still owns the terminal it created. */
adoptResidualTerminal?: FailedStartTerminalAdoption
} = {}
options: { retainCapability?: boolean } = {}
): WorkerDispatchRow {
this.db.exec('BEGIN IMMEDIATE')
try {
Expand Down Expand Up @@ -104,11 +97,7 @@ export function failWorkerStart(
})
}
this.closeQuestionsForDispatch(dispatchId)
adoptFailedStartTerminal(
this,
this.getWorkerDispatch(dispatchId) as WorkerDispatchRow,
options.adoptResidualTerminal
)
recordFailedStartDispatchIdentity(this, this.getWorkerDispatch(dispatchId) as WorkerDispatchRow)
this.db.exec('COMMIT')
return this.getWorkerDispatch(dispatchId) as WorkerDispatchRow
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { WorkerDispatchRow } from '../../types'
import type { OrchestrationDb } from '../orchestration-db'

/**
* A start that dies before `prepareStartingWorkerAuthority` never filled the Dispatch context in,
* and release re-proves identity through it — so the custody row written at terminal creation would
* name a pane no release path could match. Copy that identity across.
*
* `capability_hash` stays null, so this grants nothing: it records which pane the Dispatch owns.
*
* No transaction: composes inside `failWorkerStart`'s.
*/
export function recordFailedStartDispatchIdentity(
db: OrchestrationDb,
worker: WorkerDispatchRow
): void {
const resource = db.getWorkerTerminalResourceByOwner(worker.dispatch_id)
if (!resource || resource.terminal_handle !== worker.agent_terminal_handle) {
return
}
db.db
.prepare(
`UPDATE dispatch_contexts
SET assignee_handle = ?, assignee_pane_key = ?, process_incarnation = ?, host_scope = ?
WHERE id = ? AND status = 'failed' AND capability_hash IS NULL`
)
.run(
resource.terminal_handle,
resource.pane_key,
resource.process_incarnation,
resource.host_scope,
worker.dispatch_id
)
}

This file was deleted.

157 changes: 0 additions & 157 deletions src/main/runtime/orchestration/failed-start-terminal-adoption.test.ts

This file was deleted.

Loading