spexcode 0.5.8 → 0.6.0
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/package.json +5 -3
- package/spec-cli/bin/spex.mjs +1 -13
- package/spec-cli/hooks/compat/mark-active-0.5.2-eef1.fixture +53 -0
- package/spec-cli/hooks/compat/mark-active-sed-v0.fixture +46 -0
- package/spec-cli/hooks/dispatch.sh +15 -19
- package/spec-cli/src/anchors.ts +185 -56
- package/spec-cli/src/attach.ts +7 -17
- package/spec-cli/src/cli.ts +122 -225
- package/spec-cli/src/client.ts +114 -132
- package/spec-cli/src/cockpit.ts +43 -0
- package/spec-cli/src/codex-runtime-generations.ts +556 -0
- package/spec-cli/src/commit-surgery.ts +0 -14
- package/spec-cli/src/contract-filter.ts +0 -14
- package/spec-cli/src/delivery-queue.ts +107 -0
- package/spec-cli/src/doctor.ts +0 -16
- package/spec-cli/src/gateway-auth.ts +0 -13
- package/spec-cli/src/gateway.ts +0 -22
- package/spec-cli/src/git.ts +151 -48
- package/spec-cli/src/graph.ts +28 -7
- package/spec-cli/src/graphCache.ts +253 -72
- package/spec-cli/src/graphDelta.ts +0 -13
- package/spec-cli/src/graphStream.ts +70 -37
- package/spec-cli/src/guide.ts +29 -6
- package/spec-cli/src/harness.ts +232 -248
- package/spec-cli/src/help.ts +27 -26
- package/spec-cli/src/host-resources.ts +11 -2
- package/spec-cli/src/index.ts +71 -95
- package/spec-cli/src/issues-cli.ts +276 -0
- package/spec-cli/src/issues.ts +15 -197
- package/spec-cli/src/layout.ts +78 -2
- package/spec-cli/src/lint.ts +5 -3
- package/spec-cli/src/localIssues.ts +22 -185
- package/spec-cli/src/loop-in.ts +78 -0
- package/spec-cli/src/materialize.ts +0 -42
- package/spec-cli/src/mentions.ts +49 -193
- package/spec-cli/src/opencode-headless.ts +7 -7
- package/spec-cli/src/opencode.ts +2 -5
- package/spec-cli/src/pi-headless.ts +5 -5
- package/spec-cli/src/pty-bridge.ts +4 -5
- package/spec-cli/src/reviewSnapshot.ts +1 -0
- package/spec-cli/src/reviews.ts +6 -1
- package/spec-cli/src/root-lru.ts +54 -0
- package/spec-cli/src/runtime-guard.ts +0 -9
- package/spec-cli/src/session-cursors.ts +91 -0
- package/spec-cli/src/session-follow.ts +182 -0
- package/spec-cli/src/session-timeline.ts +55 -127
- package/spec-cli/src/sessions.ts +235 -1021
- package/spec-cli/src/shim-runtime.ts +9 -16
- package/spec-cli/src/specs.ts +33 -10
- package/spec-cli/src/uninstall.ts +0 -9
- package/spec-cli/src/uploads.ts +240 -18
- package/spec-cli/src/worktree-sources.ts +0 -16
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +28 -0
- package/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +7 -3
- package/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +3 -1
- package/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +2 -2
- package/spec-cli/templates/spec/project/.plugins/skills/distill/spec.md +5 -0
- package/spec-cli/templates/spexcode.json +12 -0
- package/spec-dashboard/dist/assets/{App-u2P7KdSg.js → App-b8Nh0sgk.js} +2 -2
- package/spec-dashboard/dist/assets/{Dashboard-B8wp5_61.js → Dashboard-CvAjfRC2.js} +7 -7
- package/spec-dashboard/dist/assets/{EvalsPage-Bq1Tkb8y.js → EvalsPage-Bz-nMKoS.js} +1 -1
- package/spec-dashboard/dist/assets/IssuesPage-CAP64YWE.js +1 -0
- package/spec-dashboard/dist/assets/MobileApp-D9L1Va8Z.js +2 -0
- package/spec-dashboard/dist/assets/{Modal-bAkq9IIT.js → Modal-Drscez-d.js} +1 -1
- package/spec-dashboard/dist/assets/{PageScroll-px_rUZVJ.js → PageScroll-qW6uOJL8.js} +1 -1
- package/spec-dashboard/dist/assets/{ProjectsPage-8uGqYM12.js → ProjectsPage-CjybFBmR.js} +1 -1
- package/spec-dashboard/dist/assets/{SessionInterface-CswwbewF.js → SessionInterface-Dl9v0JFM.js} +13 -13
- package/spec-dashboard/dist/assets/SessionWindow-iOk0yHoU.js +1 -0
- package/spec-dashboard/dist/assets/{Settings-bpAbfnmS.js → Settings-BZ1lGRJs.js} +1 -1
- package/spec-dashboard/dist/assets/Thread-D_kcDnfd.js +13 -0
- package/spec-dashboard/dist/assets/{data-CQFbQEMH.js → data-Bwd3kAVL.js} +1 -1
- package/spec-dashboard/dist/assets/{index-CixSnz1H.css → index-DAbQBBK_.css} +1 -1
- package/spec-dashboard/dist/assets/{index-Di1ch5dd.js → index-paP-z_Vd.js} +6 -6
- package/spec-dashboard/dist/assets/{TimelineChat-Ckmb1Ez2.js → launch-B-bYdWmh.js} +25 -25
- package/spec-dashboard/dist/index.html +2 -2
- package/spec-dashboard/src/session.js +1 -28
- package/spec-eval/src/cache.ts +0 -14
- package/spec-eval/src/cli.ts +26 -5
- package/spec-eval/src/evaltab.ts +67 -108
- package/spec-eval/src/filing.ts +0 -18
- package/spec-eval/src/freshness.ts +78 -32
- package/spec-eval/src/humanok.ts +0 -15
- package/spec-eval/src/scenariofresh.ts +8 -80
- package/spec-eval/src/scenarios.ts +16 -67
- package/spec-eval/src/sessioneval.ts +157 -78
- package/spec-eval/src/sidecar.ts +1 -70
- package/spec-eval/src/timeline.ts +0 -23
- package/spec-forge/src/cache.ts +34 -12
- package/spec-forge/src/cli.ts +0 -6
- package/spec-forge/src/drivers/github.ts +1 -12
- package/spec-forge/src/drivers/gitlab.ts +4 -19
- package/spec-forge/src/drivers.ts +0 -18
- package/spec-forge/src/links.ts +0 -4
- package/spec-forge/src/needs-eval.ts +0 -1
- package/spec-forge/src/port.ts +0 -12
- package/spec-forge/src/resident.ts +4 -20
- package/spec-cli/src/maintenance-wrapper.ts +0 -276
- package/spec-cli/src/session-maintenance.ts +0 -751
- package/spec-dashboard/dist/assets/IssuesPage-BlkPSkmv.js +0 -1
- package/spec-dashboard/dist/assets/MobileApp-B1GxRZXK.js +0 -2
- package/spec-dashboard/dist/assets/SessionWindow-IspcLjFA.js +0 -1
- package/spec-dashboard/dist/assets/Thread-BpL3N3kw.js +0 -13
- /package/spec-dashboard/dist/assets/{TimelineChat-Cp579UoJ.css → launch-Cp579UoJ.css} +0 -0
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process'
|
|
2
|
-
import { once } from 'node:events'
|
|
3
|
-
import type { Duplex, Readable, Writable } from 'node:stream'
|
|
4
|
-
import type { Capability, LeaseOwner, MaintenanceState } from './session-maintenance.js'
|
|
5
|
-
import { processStartToken } from './process-identity.js'
|
|
6
|
-
import {
|
|
7
|
-
clientMaintenanceAcquire,
|
|
8
|
-
clientMaintenanceHeartbeat,
|
|
9
|
-
clientMaintenanceOperation,
|
|
10
|
-
clientMaintenanceRelease,
|
|
11
|
-
clientMaintenanceStatus,
|
|
12
|
-
} from './client.js'
|
|
13
|
-
|
|
14
|
-
type BrokerRequest = {
|
|
15
|
-
v?: number
|
|
16
|
-
id?: string
|
|
17
|
-
op?: string
|
|
18
|
-
sessionId?: string
|
|
19
|
-
force?: boolean
|
|
20
|
-
client?: { pid?: number; startToken?: string }
|
|
21
|
-
}
|
|
22
|
-
type PlannedCapability = {
|
|
23
|
-
capability: Capability
|
|
24
|
-
state: 'unused' | 'inflight' | 'committed' | 'indeterminate'
|
|
25
|
-
requestId?: string
|
|
26
|
-
}
|
|
27
|
-
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
28
|
-
const capabilityKey = (capability: Capability): string => JSON.stringify(capability)
|
|
29
|
-
const capabilityList = (rows: unknown): Capability[] => Array.isArray(rows)
|
|
30
|
-
? rows.map((row: any) => row?.capability ?? row)
|
|
31
|
-
: []
|
|
32
|
-
const sameCapabilities = (left: readonly Capability[], right: readonly Capability[]): boolean =>
|
|
33
|
-
JSON.stringify(left) === JSON.stringify(right)
|
|
34
|
-
const sameOwner = (left: LeaseOwner | null | undefined, right: LeaseOwner): boolean =>
|
|
35
|
-
!!left && left.instanceId === right.instanceId && left.pid === right.pid && left.startToken === right.startToken
|
|
36
|
-
const assertLeaseView = (view: Pick<MaintenanceState, 'state' | 'epoch' | 'owner' | 'capabilities'>, epoch: number, owner: LeaseOwner, capabilities: readonly Capability[], allowDraining = false) => {
|
|
37
|
-
if (view.epoch !== epoch || (!allowDraining && view.state !== 'active') || (allowDraining && view.state !== 'active' && view.state !== 'draining'))
|
|
38
|
-
throw new Error('maintenance lease changed epoch or state')
|
|
39
|
-
if (!sameOwner(view.owner, owner)) throw new Error('maintenance lease owner generation changed')
|
|
40
|
-
if (!sameCapabilities(capabilityList(view.capabilities), capabilities)) throw new Error('maintenance lease changed its capability plan')
|
|
41
|
-
}
|
|
42
|
-
const wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
43
|
-
const reapCommandGroup = async (child: ReturnType<typeof spawn>, closed: Promise<[number | null, NodeJS.Signals | null]>) => {
|
|
44
|
-
if (!child.pid) return
|
|
45
|
-
const groupAlive = () => {
|
|
46
|
-
try { process.kill(-child.pid!, 0); return true } catch { return false }
|
|
47
|
-
}
|
|
48
|
-
if (groupAlive()) {
|
|
49
|
-
try { process.kill(-child.pid, 'SIGTERM') } catch { /* already exited */ }
|
|
50
|
-
const deadline = Date.now() + 1_000
|
|
51
|
-
while (groupAlive() && Date.now() < deadline) await wait(20)
|
|
52
|
-
if (groupAlive()) {
|
|
53
|
-
try { process.kill(-child.pid, 'SIGKILL') } catch { /* already exited */ }
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const closedAfterReap = await new Promise<boolean>((resolve) => {
|
|
57
|
-
let settled = false
|
|
58
|
-
const finish = (value: boolean) => {
|
|
59
|
-
if (settled) return
|
|
60
|
-
settled = true
|
|
61
|
-
clearTimeout(timer)
|
|
62
|
-
resolve(value)
|
|
63
|
-
}
|
|
64
|
-
const timer = setTimeout(() => finish(false), 2_000)
|
|
65
|
-
timer.unref()
|
|
66
|
-
void closed.then(() => finish(true))
|
|
67
|
-
})
|
|
68
|
-
if (!closedAfterReap) throw new Error('maintenance operator pipes did not close after exact process-group reap')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function runMaintenanceWrapper(input: {
|
|
72
|
-
capabilities: Capability[]
|
|
73
|
-
ttlMs: number
|
|
74
|
-
waitMs: number
|
|
75
|
-
command: string[]
|
|
76
|
-
}): Promise<number> {
|
|
77
|
-
const acquired = await clientMaintenanceAcquire(input.capabilities, input.ttlMs, input.waitMs)
|
|
78
|
-
const { token, epoch, owner } = acquired.lease
|
|
79
|
-
const releaseable = typeof token === 'string' && /^[0-9a-f]{64}$/i.test(token) && Number.isSafeInteger(epoch)
|
|
80
|
-
let released = false
|
|
81
|
-
let authorityLost = false
|
|
82
|
-
let heartbeat: ReturnType<typeof setInterval> | null = null
|
|
83
|
-
let child: ReturnType<typeof spawn> | null = null
|
|
84
|
-
const pendingOperations = new Set<AbortController>()
|
|
85
|
-
let closeAdmission = () => {}
|
|
86
|
-
try {
|
|
87
|
-
if (!releaseable) throw new Error('maintenance acquire returned malformed private authority')
|
|
88
|
-
if (!owner || typeof owner.instanceId !== 'string' || !Number.isInteger(owner.pid) || typeof owner.startToken !== 'string')
|
|
89
|
-
throw new Error('maintenance acquire returned malformed supervisor authority')
|
|
90
|
-
if (!sameCapabilities(capabilityList(acquired.lease.capabilities), input.capabilities)) throw new Error('maintenance acquire returned a different capability plan')
|
|
91
|
-
|
|
92
|
-
let state = acquired.lease.state
|
|
93
|
-
const activationDeadline = Date.now() + input.ttlMs
|
|
94
|
-
try {
|
|
95
|
-
while (state === 'draining') {
|
|
96
|
-
const current = await clientMaintenanceHeartbeat(token, epoch, input.ttlMs)
|
|
97
|
-
assertLeaseView(current, epoch, owner, input.capabilities, true)
|
|
98
|
-
const status = await clientMaintenanceStatus()
|
|
99
|
-
assertLeaseView(status, epoch, owner, input.capabilities, true)
|
|
100
|
-
state = status.state as 'draining' | 'active'
|
|
101
|
-
if (state !== 'active') {
|
|
102
|
-
if (Date.now() >= activationDeadline) throw new Error('maintenance acquisition did not become active before its ttl')
|
|
103
|
-
await sleep(50)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (state !== 'active') throw new Error(`maintenance acquisition entered unexpected state ${state}`)
|
|
107
|
-
} catch (error) {
|
|
108
|
-
authorityLost = true
|
|
109
|
-
throw error
|
|
110
|
-
}
|
|
111
|
-
if (!input.command.length) throw new Error('maintenance wrapper needs a command after --')
|
|
112
|
-
|
|
113
|
-
const plan: PlannedCapability[] = input.capabilities.map((capability) => ({ capability: { ...capability }, state: 'unused' }))
|
|
114
|
-
let heartbeatFailure: Error | null = null
|
|
115
|
-
let heartbeatBusy = false
|
|
116
|
-
let failAuthority!: (error: Error) => void
|
|
117
|
-
const authorityFailure = new Promise<never>((_, reject) => {
|
|
118
|
-
failAuthority = (error) => {
|
|
119
|
-
if (heartbeatFailure) return
|
|
120
|
-
heartbeatFailure = error
|
|
121
|
-
reject(error)
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
heartbeat = setInterval(() => {
|
|
125
|
-
if (heartbeatBusy || heartbeatFailure) return
|
|
126
|
-
heartbeatBusy = true
|
|
127
|
-
void clientMaintenanceHeartbeat(token, epoch, input.ttlMs)
|
|
128
|
-
.then((current) => assertLeaseView(current, epoch, owner, input.capabilities))
|
|
129
|
-
.catch((error) => failAuthority(error instanceof Error ? error : new Error(String(error))))
|
|
130
|
-
.finally(() => { heartbeatBusy = false })
|
|
131
|
-
}, Math.max(250, Math.min(1_000, Math.floor(input.ttlMs / 3))))
|
|
132
|
-
heartbeat.unref()
|
|
133
|
-
|
|
134
|
-
child = spawn(input.command[0], input.command.slice(1), {
|
|
135
|
-
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'pipe', 'pipe'],
|
|
136
|
-
env: { ...process.env, SPEXCODE_MAINTENANCE_BROKER_FDS: '3,4,5' },
|
|
137
|
-
detached: true,
|
|
138
|
-
})
|
|
139
|
-
const exited = once(child, 'exit') as Promise<[number | null, NodeJS.Signals | null]>
|
|
140
|
-
const closed = once(child, 'close') as Promise<[number | null, NodeJS.Signals | null]>
|
|
141
|
-
const childStdio = child.stdio as Array<Readable | Writable | Duplex | null | undefined>
|
|
142
|
-
const requestPipe = childStdio[3] as Readable
|
|
143
|
-
const responsePipe = childStdio[4] as Writable
|
|
144
|
-
const turnPipe = childStdio[5] as Duplex
|
|
145
|
-
requestPipe.setEncoding('utf8')
|
|
146
|
-
let pending = ''
|
|
147
|
-
let serial = Promise.resolve()
|
|
148
|
-
let brokerWork = 0
|
|
149
|
-
let brokerOpen = true
|
|
150
|
-
let transportLossTimer: ReturnType<typeof setTimeout> | null = null
|
|
151
|
-
const brokerTransportLost = (error: unknown) => {
|
|
152
|
-
if (!brokerOpen || transportLossTimer) return
|
|
153
|
-
transportLossTimer = setTimeout(() => {
|
|
154
|
-
transportLossTimer = null
|
|
155
|
-
if (brokerOpen && child?.exitCode === null && child.signalCode === null) {
|
|
156
|
-
failAuthority(error instanceof Error ? error : new Error('maintenance broker transport closed while the operator command was live'))
|
|
157
|
-
}
|
|
158
|
-
}, 50)
|
|
159
|
-
transportLossTimer.unref()
|
|
160
|
-
}
|
|
161
|
-
requestPipe.on('error', brokerTransportLost)
|
|
162
|
-
requestPipe.on('end', () => brokerTransportLost(new Error('maintenance broker request pipe closed while the operator command was live')))
|
|
163
|
-
responsePipe.on('error', brokerTransportLost)
|
|
164
|
-
responsePipe.on('close', () => brokerTransportLost(new Error('maintenance broker response pipe closed while the operator command was live')))
|
|
165
|
-
turnPipe.on('error', brokerTransportLost)
|
|
166
|
-
turnPipe.on('end', () => brokerTransportLost(new Error('maintenance broker turn pipe closed while the operator command was live')))
|
|
167
|
-
closeAdmission = () => {
|
|
168
|
-
if (!brokerOpen) return
|
|
169
|
-
brokerOpen = false
|
|
170
|
-
for (const controller of pendingOperations) controller.abort(new Error('maintenance broker admission closed'))
|
|
171
|
-
if (transportLossTimer) clearTimeout(transportLossTimer)
|
|
172
|
-
transportLossTimer = null
|
|
173
|
-
try { requestPipe.destroy() } catch { /* already closed */ }
|
|
174
|
-
try { turnPipe.destroy() } catch { /* already closed */ }
|
|
175
|
-
}
|
|
176
|
-
turnPipe.on('data', (chunk: Buffer) => {
|
|
177
|
-
if (!brokerOpen) return
|
|
178
|
-
for (const byte of chunk) {
|
|
179
|
-
if (byte !== 1) return failAuthority(new Error('maintenance broker turn token was corrupted'))
|
|
180
|
-
turnPipe.write(Buffer.from([1]))
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
turnPipe.write(Buffer.from([1]))
|
|
184
|
-
const reply = (id: string | undefined, body: Record<string, unknown>) => {
|
|
185
|
-
if (brokerOpen) responsePipe.write(`${JSON.stringify({ id, ...body })}\n`)
|
|
186
|
-
}
|
|
187
|
-
const handle = async (request: BrokerRequest) => {
|
|
188
|
-
if (!brokerOpen) return
|
|
189
|
-
if (request.v !== 1 || typeof request.id !== 'string' || typeof request.op !== 'string' || typeof request.sessionId !== 'string'
|
|
190
|
-
|| !Number.isInteger(request.client?.pid) || typeof request.client?.startToken !== 'string'
|
|
191
|
-
|| processStartToken(request.client.pid!) !== request.client.startToken) {
|
|
192
|
-
reply(request.id, { ok: false, code: 'maintenance_invalid', error: 'malformed broker request' })
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
const candidate: Capability | null = request.op === 'stop'
|
|
196
|
-
? { op: 'stop', sessionId: request.sessionId }
|
|
197
|
-
: request.op === 'resume' && typeof request.force === 'boolean'
|
|
198
|
-
? { op: 'resume', sessionId: request.sessionId, force: request.force }
|
|
199
|
-
: null
|
|
200
|
-
const matching = candidate && plan.find((entry) => capabilityKey(entry.capability) === capabilityKey(candidate))
|
|
201
|
-
const planned = matching?.state === 'unused' ? matching : null
|
|
202
|
-
if (!planned) {
|
|
203
|
-
reply(request.id, {
|
|
204
|
-
ok: false,
|
|
205
|
-
code: matching ? 'maintenance_capability_used' : 'maintenance_capability_missing',
|
|
206
|
-
error: matching ? 'operation is already inflight, committed, or indeterminate' : 'operation is not in the exact maintenance plan',
|
|
207
|
-
})
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
planned.state = 'inflight'
|
|
211
|
-
planned.requestId = request.id
|
|
212
|
-
const controller = new AbortController()
|
|
213
|
-
pendingOperations.add(controller)
|
|
214
|
-
try {
|
|
215
|
-
const result = await clientMaintenanceOperation(token, candidate!, { signal: controller.signal, timeoutMs: Math.min(5_000, input.ttlMs) })
|
|
216
|
-
planned.state = result.outcome === 'committed' ? 'committed' : result.outcome === 'refused' ? 'unused' : 'indeterminate'
|
|
217
|
-
delete planned.requestId
|
|
218
|
-
reply(request.id, result)
|
|
219
|
-
} catch (error) {
|
|
220
|
-
planned.state = 'indeterminate'
|
|
221
|
-
delete planned.requestId
|
|
222
|
-
reply(request.id, { ok: false, code: 'maintenance_broker_failed', error: error instanceof Error ? error.message : String(error) })
|
|
223
|
-
} finally {
|
|
224
|
-
pendingOperations.delete(controller)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
requestPipe.on('data', (chunk: Buffer | string) => {
|
|
228
|
-
pending += chunk.toString()
|
|
229
|
-
for (;;) {
|
|
230
|
-
const newline = pending.indexOf('\n')
|
|
231
|
-
if (newline < 0) break
|
|
232
|
-
const line = pending.slice(0, newline); pending = pending.slice(newline + 1)
|
|
233
|
-
if (!line) continue
|
|
234
|
-
brokerWork++
|
|
235
|
-
serial = serial.then(async () => {
|
|
236
|
-
try { await handle(JSON.parse(line) as BrokerRequest) }
|
|
237
|
-
catch (error) { reply(undefined, { ok: false, code: 'maintenance_broker_failed', error: error instanceof Error ? error.message : String(error) }) }
|
|
238
|
-
}).finally(() => { brokerWork-- })
|
|
239
|
-
}
|
|
240
|
-
})
|
|
241
|
-
const outcome = await Promise.race([
|
|
242
|
-
exited.then(([code, signal]) => ({ kind: 'exit' as const, code, signal })),
|
|
243
|
-
authorityFailure.catch((error) => ({ kind: 'lost' as const, error: error instanceof Error ? error : new Error(String(error)) })),
|
|
244
|
-
])
|
|
245
|
-
if (outcome.kind === 'lost') {
|
|
246
|
-
authorityLost = true
|
|
247
|
-
closeAdmission()
|
|
248
|
-
await reapCommandGroup(child, closed)
|
|
249
|
-
throw outcome.error
|
|
250
|
-
}
|
|
251
|
-
const brokerIndeterminate = brokerWork > 0 || pendingOperations.size > 0 || pending.trim().length > 0
|
|
252
|
-
if (brokerIndeterminate) authorityLost = true
|
|
253
|
-
closeAdmission()
|
|
254
|
-
await serial
|
|
255
|
-
try { await reapCommandGroup(child, closed) }
|
|
256
|
-
catch (error) { authorityLost = true; throw error }
|
|
257
|
-
if (brokerIndeterminate) {
|
|
258
|
-
throw new Error('operator exited while a maintenance broker operation was indeterminate')
|
|
259
|
-
}
|
|
260
|
-
if (heartbeatFailure) {
|
|
261
|
-
authorityLost = true
|
|
262
|
-
throw heartbeatFailure
|
|
263
|
-
}
|
|
264
|
-
return outcome.code ?? (outcome.signal ? 1 : 0)
|
|
265
|
-
} finally {
|
|
266
|
-
if (heartbeat) clearInterval(heartbeat)
|
|
267
|
-
closeAdmission()
|
|
268
|
-
try { child?.stdio[3]?.destroy() } catch { /* child already closed */ }
|
|
269
|
-
try { child?.stdio[4]?.destroy() } catch { /* child already closed */ }
|
|
270
|
-
try { (child?.stdio as Array<Readable | Writable | null | undefined> | undefined)?.[5]?.destroy() } catch { /* child already closed */ }
|
|
271
|
-
if (releaseable && !released && !authorityLost) {
|
|
272
|
-
await clientMaintenanceRelease(token, epoch)
|
|
273
|
-
released = true
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|