mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(orcad): a restarted managed server keeps the tab titles of terminals it adopts (#29623)
This commit is contained in:
@@ -154,6 +154,16 @@ describe('DaemonPtyAdapter (IPtyProvider)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// A restarted server adopts surviving sessions this way and must not reset their tab titles.
|
||||
it('returns the session title from attach-only adoption', async () => {
|
||||
const sessionId = 'attach-title-handoff'
|
||||
await adapter.spawn({ cols: 80, rows: 24, sessionId })
|
||||
lastSubprocess._simulateData('\x1b]0;first\x07\x1b]0;⠋ Claude\x07')
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
|
||||
await expect(adapter.attach(sessionId)).resolves.toMatchObject({ lastTitle: '⠋ Claude' })
|
||||
})
|
||||
|
||||
it('returns plain result for new sessionId', async () => {
|
||||
const result = await adapter.spawn({ cols: 80, rows: 24, sessionId: 'brand-new' })
|
||||
expect(result.id).toBe('brand-new')
|
||||
|
||||
@@ -16,7 +16,7 @@ export const LIVENESS_PROBE_TIMEOUT_MS = 2_000
|
||||
const MAX_TOMBSTONES = 1000
|
||||
|
||||
export abstract class DaemonPtySessionControl extends DaemonPtySessionInput {
|
||||
async attach(id: string): Promise<Pick<PtySpawnResult, 'providerSequence'> | void> {
|
||||
async attach(id: string): Promise<Pick<PtySpawnResult, 'providerSequence' | 'lastTitle'> | void> {
|
||||
await this.ensureConnected()
|
||||
if (!this.canDelegateBackgroundToDaemon) {
|
||||
this.setPtyBackgrounded(id, false)
|
||||
@@ -41,7 +41,13 @@ export abstract class DaemonPtySessionControl extends DaemonPtySessionInput {
|
||||
if (result.exitedBeforeSpawnReply) {
|
||||
throw new SessionNotFoundError(id)
|
||||
}
|
||||
return result.providerSequence ? { providerSequence: result.providerSequence } : undefined
|
||||
// Why lastTitle: a runtime adopting a surviving session (e.g. a restarted server) never saw it.
|
||||
return result.providerSequence || result.lastTitle
|
||||
? {
|
||||
...(result.providerSequence ? { providerSequence: result.providerSequence } : {}),
|
||||
...(result.lastTitle ? { lastTitle: result.lastTitle } : {})
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
hasPty(id: string): boolean {
|
||||
|
||||
@@ -186,6 +186,18 @@ describe('registerPtyHandlers', () => {
|
||||
0
|
||||
)
|
||||
})
|
||||
it('seeds the title of a session adopted by subscriber-driven attach', async () => {
|
||||
const daemonPtyId = 'repo-1::/tmp/wt@@title-handoff'
|
||||
const localProvider = createAgentClaimProvider({})
|
||||
localProvider.attach.mockResolvedValueOnce({ lastTitle: '⠋ Claude' })
|
||||
setLocalPtyProvider(localProvider as never)
|
||||
const seedTerminalRestoreTail = vi.fn()
|
||||
const controller = registerAgentClaimController({ seedTerminalRestoreTail })
|
||||
|
||||
await expect(controller.attach(daemonPtyId)).resolves.toBe(true)
|
||||
|
||||
expect(seedTerminalRestoreTail).toHaveBeenCalledWith(daemonPtyId, { lastTitle: '⠋ Claude' })
|
||||
})
|
||||
it('does not dispatch a runtime PTY spawn after its client disconnects', async () => {
|
||||
const provider = createAgentClaimProvider({})
|
||||
setLocalPtyProvider(provider as never)
|
||||
|
||||
@@ -139,6 +139,9 @@ export async function attachPtyFromRuntimeController(
|
||||
sequenceBeforeProviderAttach
|
||||
)
|
||||
}
|
||||
if (attachResult?.lastTitle) {
|
||||
deps.runtime?.seedTerminalRestoreTail?.(ptyId, { lastTitle: attachResult.lastTitle })
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
|
||||
@@ -147,7 +147,7 @@ export type IPtyProvider = {
|
||||
providesAgentSessionOwnerListings?: (ptyId: string) => boolean
|
||||
/** Whether fresh structured creates can replay one spawn across a lost relay response. */
|
||||
supportsAgentSessionCreateOperations?: (options?: PtyProbeOptions) => boolean | Promise<boolean>
|
||||
attach(id: string): Promise<Pick<PtySpawnResult, 'providerSequence'> | void>
|
||||
attach(id: string): Promise<Pick<PtySpawnResult, 'providerSequence' | 'lastTitle'> | void>
|
||||
hasPty?: (id: string) => boolean
|
||||
/** Exact provider readback: false only when the provider answered that the PTY is absent. */
|
||||
probePtyLiveness?: (id: string) => Promise<boolean | null>
|
||||
|
||||
Reference in New Issue
Block a user