Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
123 lines
4.7 KiB
TypeScript
123 lines
4.7 KiB
TypeScript
import { DaemonPtyDaemonRecovery } from './daemon-pty-daemon-recovery'
|
|
import { supportsMode2031UnsubscribeFact, type DaemonEvent } from './types'
|
|
import type { IPtyProvider } from '../providers/types'
|
|
|
|
export class DaemonPtyAdapter extends DaemonPtyDaemonRecovery implements IPtyProvider {
|
|
protected setupEventRouting(): void {
|
|
if (this.removeEventListener) {
|
|
return
|
|
}
|
|
|
|
this.removeEventListener = this.client.onEvent((raw) => {
|
|
const event = raw as DaemonEvent
|
|
if (event.type !== 'event') {
|
|
return
|
|
}
|
|
|
|
if (event.event === 'data') {
|
|
this.markSessionDirty(event.sessionId)
|
|
// oxlint-disable-next-line unicorn/no-useless-spread -- copy-safe: listeners may unsubscribe during iteration
|
|
for (const listener of [...this.dataListeners]) {
|
|
listener({
|
|
id: event.sessionId,
|
|
data: event.payload.data,
|
|
...((event.payload.rawLength ?? event.payload.sequenceChars) === undefined
|
|
? {}
|
|
: { sequenceChars: event.payload.rawLength ?? event.payload.sequenceChars }),
|
|
...(event.payload.transformed ? { transformed: true } : {}),
|
|
...(event.payload.seq === undefined ? {} : { seq: event.payload.seq })
|
|
})
|
|
}
|
|
} else if (event.event === 'sessionBackgroundMarker') {
|
|
this.emitBackgroundStreamEvent({
|
|
id: event.sessionId,
|
|
kind: 'backgroundMarker',
|
|
background: event.payload.background,
|
|
...(event.payload.scanSeedAnsi !== undefined
|
|
? { scanSeedAnsi: event.payload.scanSeedAnsi }
|
|
: {}),
|
|
...(event.payload.mode2031PendingSubscribe
|
|
? { mode2031PendingSubscribe: true as const }
|
|
: {})
|
|
})
|
|
} else if (event.event === 'dataGap') {
|
|
this.emitBackgroundStreamEvent({
|
|
id: event.sessionId,
|
|
kind: 'dataGap',
|
|
droppedChars: event.payload.droppedChars,
|
|
...(event.payload.sequenceChars === undefined
|
|
? {}
|
|
: { sequenceChars: event.payload.sequenceChars })
|
|
})
|
|
} else if (event.event === 'transientFact') {
|
|
// Why (#9993): a preserved pre-v29 daemon can retain a stale relay tracker. Its
|
|
// unretractable subscribe is harmful; an unsubscribe is always safe to forward.
|
|
if (
|
|
event.payload.kind === '2031-subscribe' &&
|
|
!supportsMode2031UnsubscribeFact(this.protocolVersion)
|
|
) {
|
|
return
|
|
}
|
|
this.emitBackgroundStreamEvent({
|
|
id: event.sessionId,
|
|
kind: 'transientFact',
|
|
fact: event.payload
|
|
})
|
|
} else if (event.event === 'exit') {
|
|
const currentIncarnationId = this.sessionIncarnations.get(event.sessionId)
|
|
const pendingOperations = new Set([
|
|
...(this.pendingSpawnOperationsBySessionId.get(event.sessionId) ?? []),
|
|
...this.pendingClaimSpawnOperations
|
|
])
|
|
for (const operation of pendingOperations) {
|
|
if (operation.ignoreNextExit) {
|
|
operation.ignoreNextExit = false
|
|
continue
|
|
}
|
|
const exits = operation.exitsBySessionId.get(event.sessionId) ?? []
|
|
exits.push(
|
|
event.payload.incarnationId
|
|
? { code: event.payload.code, incarnationId: event.payload.incarnationId }
|
|
: { code: event.payload.code }
|
|
)
|
|
operation.exitsBySessionId.set(event.sessionId, exits)
|
|
}
|
|
// Keep a raced exit available to the in-flight spawn even when the
|
|
// adapter still remembers the predecessor's generation. Only the
|
|
// generation currently published by this adapter may clear state or
|
|
// notify listeners.
|
|
if (
|
|
currentIncarnationId !== undefined &&
|
|
event.payload.incarnationId !== currentIncarnationId
|
|
) {
|
|
return
|
|
}
|
|
this.clearExitedSessionState(
|
|
event.sessionId,
|
|
event.payload.code,
|
|
event.payload.incarnationId
|
|
)
|
|
// oxlint-disable-next-line unicorn/no-useless-spread -- copy-safe: listeners may unsubscribe during iteration
|
|
for (const listener of [...this.exitListeners]) {
|
|
listener({
|
|
id: event.sessionId,
|
|
code: event.payload.code,
|
|
...(event.payload.incarnationId ? { incarnationId: event.payload.incarnationId } : {}),
|
|
...(event.payload.cause ? { cause: event.payload.cause } : {})
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
async closeStartupQueryAuthority(id: string): Promise<number> {
|
|
if (!this.supportsStartupIngress) {
|
|
return 0
|
|
}
|
|
const result = await this.client.request<{ appliedSeq: number }>('closeStartupQueryAuthority', {
|
|
sessionId: id
|
|
})
|
|
return result.appliedSeq
|
|
}
|
|
}
|