diff --git a/src/main/daemon/terminal-spawn-gate.test.ts b/src/main/daemon/terminal-spawn-gate.test.ts index 9ac77756865..2cb0c4e1e8d 100644 --- a/src/main/daemon/terminal-spawn-gate.test.ts +++ b/src/main/daemon/terminal-spawn-gate.test.ts @@ -105,6 +105,31 @@ describe('TerminalSpawnGate', () => { ) }) + it('counts a remote resume that arrives with a claim but no history seed', async () => { + await host.createOrAttach({ + sessionId: 'remote-resume', + cols: 80, + rows: 24, + agentSessionEnsure: { + claim: { + digestVersion: 1 as const, + keyId: 'key', + identityDigest: 'r'.repeat(43), + worktreeScopeDigest: 'b'.repeat(43), + agent: 'codex' as const + }, + surface: { + worktreeId: 'worktree', + tabId: 'tab-remote', + leafId: '11111111-1111-4111-8111-111111111111', + terminalHandle: 'term_remote' + } + }, + streamClient: stream() + }) + expect(gate.snapshot().liveAutomatic).toBe(1) + }) + it('never charges an attach and frees the live slot when the session exits', async () => { await restore('restore-a') await host.createOrAttach({ diff --git a/src/shared/host-spawn-admission.test.ts b/src/shared/host-spawn-admission.test.ts index 934d8421f35..bf1a78ecd64 100644 --- a/src/shared/host-spawn-admission.test.ts +++ b/src/shared/host-spawn-admission.test.ts @@ -47,12 +47,11 @@ function mulberry32(seed: number): () => number { } describe('resolveSpawnCharge', () => { - it('infers automatic restore for an old caller only from a claim plus a history seed', () => { + it('infers automatic restore for an old caller from an agent claim, seeded or not', () => { const state = createSpawnAdmissionState(LIMITS, 0) expect(resolveSpawnCharge(state, facts(), LIMITS, 0)).toBe('automatic') - expect(resolveSpawnCharge(state, facts({ hasHistorySeed: false }), LIMITS, 0)).toBe( - 'interactive' - ) + // A remote client's resume: fresh terminal id, claim, no history seed. + expect(resolveSpawnCharge(state, facts({ hasHistorySeed: false }), LIMITS, 0)).toBe('automatic') expect(resolveSpawnCharge(state, facts({ hasAgentSessionClaim: false }), LIMITS, 0)).toBe( 'interactive' ) diff --git a/src/shared/host-spawn-admission.ts b/src/shared/host-spawn-admission.ts index 7fa69328256..7cf02ae74b7 100644 --- a/src/shared/host-spawn-admission.ts +++ b/src/shared/host-spawn-admission.ts @@ -133,9 +133,11 @@ export function resolveSpawnCharge( limits: SpawnAdmissionLimits, now: number ): SpawnCharge { + // Why a claim alone: a remote client's `--resume` arrives on a fresh terminal id with no + // history seed, so requiring one let every reconnect storm through. An old caller is only + // ever paced, so a user-started agent costs at most a short wait during a storm. const intent = - facts.declaredIntent ?? - (facts.hasAgentSessionClaim && facts.hasHistorySeed ? 'automatic-restore' : 'interactive') + facts.declaredIntent ?? (facts.hasAgentSessionClaim ? 'automatic-restore' : 'interactive') if (intent === 'interactive') { return 'interactive' }