mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 02:57:36 +00:00
* fix(native-chat): never refuse a user's write because the retry record is full The record that lets a retried chat write or agent launch replay instead of running twice refused every new write once it held 512 ids for one caller or 4,096 in total. Background agent traffic fills that in a normal day, so the user's own message was refused with "too many requests in the last day". Remove the count limit from the durable record, the in-memory launch record and the SSH relay's launch record; each entry still lives exactly as long as the retry window it protects. Clients still understand the refusal from older hosts, and its message now says to update Orca on that computer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(native-chat): drop leftover cap wording and dead launch fallback; cover create records past the old cap * fix(native-chat): keep refusal and mailbox comments true after merge --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
239 lines
8.4 KiB
TypeScript
239 lines
8.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
AGENT_SESSION_MAX_NEW_OPERATION_AGE_MS,
|
|
AGENT_SESSION_OPERATION_FUTURE_SKEW_MS
|
|
} from './agent-session-host-authority'
|
|
import {
|
|
agentSessionOperationExpiry,
|
|
agentSessionOperationKey,
|
|
claimAgentSessionOperation,
|
|
evaluateAgentSessionOperation,
|
|
pendingAgentSessionOperationRow,
|
|
settleAgentSessionOperation,
|
|
isAgentSessionOperationRow,
|
|
pruneAgentSessionOperationRows,
|
|
type AgentSessionOperationRow
|
|
} from './agent-session-operation-ledger'
|
|
|
|
const NOW = 1_800_000_000_000
|
|
|
|
function operationId(timestamp: number, suffix = 'a'.repeat(32)): string {
|
|
return `${String(timestamp).padStart(13, '0')}-${suffix}`
|
|
}
|
|
|
|
function evaluate(
|
|
rows: Map<string, AgentSessionOperationRow>,
|
|
overrides: Partial<{
|
|
callerKey: string
|
|
operationId: string
|
|
fingerprint: string
|
|
now: number
|
|
}> = {}
|
|
) {
|
|
return evaluateAgentSessionOperation({
|
|
rows,
|
|
callerKey: 'client-1',
|
|
operationId: operationId(NOW),
|
|
fingerprint: 'fp-1',
|
|
now: NOW,
|
|
...overrides
|
|
})
|
|
}
|
|
|
|
function admit(
|
|
rows: Map<string, AgentSessionOperationRow>,
|
|
overrides: Parameters<typeof evaluate>[1] = {}
|
|
): AgentSessionOperationRow {
|
|
const decision = evaluate(rows, overrides)
|
|
if (decision.decision !== 'admit') {
|
|
throw new Error(`expected admit, got ${decision.decision}`)
|
|
}
|
|
rows.set(agentSessionOperationKey(decision.row.callerKey, decision.row.operationId), decision.row)
|
|
return decision.row
|
|
}
|
|
|
|
describe('operation admission', () => {
|
|
it('admits a fresh id once and replays the identical retry', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
const row = admit(rows)
|
|
const replay = evaluate(rows)
|
|
expect(replay).toEqual({ decision: 'replay', row })
|
|
})
|
|
|
|
it('refuses the same id carrying different parameters', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
admit(rows)
|
|
expect(evaluate(rows, { fingerprint: 'fp-2' })).toEqual({
|
|
decision: 'refused',
|
|
code: 'agent_session_operation_conflict',
|
|
details: { reason: 'operationIdReused' }
|
|
})
|
|
})
|
|
|
|
it('scopes ids per caller so two clients cannot collide or replay each other', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
admit(rows)
|
|
expect(evaluate(rows, { callerKey: 'client-2' }).decision).toBe('admit')
|
|
})
|
|
|
|
it('refuses a malformed or future-dated id', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
expect(evaluate(rows, { operationId: 'not-an-operation-id' })).toEqual({
|
|
decision: 'refused',
|
|
code: 'agent_session_operation_invalid',
|
|
details: { reason: 'operationIdInvalid' }
|
|
})
|
|
// Why: a future-dated id would look new again after its own tombstone is collected.
|
|
expect(
|
|
evaluate(rows, {
|
|
operationId: operationId(NOW + AGENT_SESSION_OPERATION_FUTURE_SKEW_MS + 1)
|
|
})
|
|
).toEqual({
|
|
decision: 'refused',
|
|
code: 'agent_session_operation_invalid',
|
|
details: { reason: 'operationIdInvalid' }
|
|
})
|
|
expect(
|
|
evaluate(rows, { operationId: operationId(NOW + AGENT_SESSION_OPERATION_FUTURE_SKEW_MS) })
|
|
.decision
|
|
).toBe('admit')
|
|
})
|
|
|
|
it('refuses an id older than the admission window instead of treating it as new', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
const stale = operationId(NOW - AGENT_SESSION_MAX_NEW_OPERATION_AGE_MS - 1)
|
|
expect(evaluate(rows, { operationId: stale })).toEqual({
|
|
decision: 'refused',
|
|
code: 'agent_session_operation_expired',
|
|
details: { reason: 'operationExpired' }
|
|
})
|
|
expect(
|
|
evaluate(rows, { operationId: operationId(NOW - AGENT_SESSION_MAX_NEW_OPERATION_AGE_MS) })
|
|
.decision
|
|
).toBe('admit')
|
|
})
|
|
|
|
it('admits a fresh id however many unexpired rows other callers hold', () => {
|
|
// Why: a count cap filled by background agent traffic used to refuse the user's own send.
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
for (let index = 0; index < 5_000; index += 1) {
|
|
admit(rows, {
|
|
callerKey: `agent-${index % 10}`,
|
|
operationId: operationId(NOW - index, index.toString(16).padStart(32, '0'))
|
|
})
|
|
}
|
|
for (let index = 0; index < 600; index += 1) {
|
|
admit(rows, {
|
|
callerKey: 'desktop',
|
|
operationId: operationId(NOW - index, `d${index.toString(16).padStart(31, '0')}`)
|
|
})
|
|
}
|
|
const fresh = evaluate(rows, {
|
|
callerKey: 'desktop',
|
|
operationId: operationId(NOW, 'e'.repeat(32))
|
|
})
|
|
expect(fresh.decision).toBe('admit')
|
|
// Retries of earlier ids still replay rather than running again.
|
|
expect(
|
|
evaluate(rows, {
|
|
callerKey: 'agent-0',
|
|
operationId: operationId(NOW, '0'.repeat(32))
|
|
}).decision
|
|
).toBe('replay')
|
|
})
|
|
})
|
|
|
|
describe('the pane a launch laid out', () => {
|
|
const pane = { worktreeId: 'wt-1', paneKey: 'tab-1:leaf-1' }
|
|
|
|
it('is recorded only by the claim that wins, and outlives the settle', () => {
|
|
const id = operationId(NOW)
|
|
const key = agentSessionOperationKey('caller', id)
|
|
const pending = new Map([
|
|
[
|
|
key,
|
|
pendingAgentSessionOperationRow({
|
|
callerKey: 'caller',
|
|
operationId: id,
|
|
fingerprint: 'fp',
|
|
now: NOW
|
|
})
|
|
]
|
|
])
|
|
|
|
const won = claimAgentSessionOperation(pending, {
|
|
callerKey: 'caller',
|
|
operationId: id,
|
|
ownedPane: pane
|
|
})
|
|
expect(won.rows.get(key)?.ownedPane).toEqual(pane)
|
|
const lost = claimAgentSessionOperation(won.rows, {
|
|
callerKey: 'caller',
|
|
operationId: id,
|
|
ownedPane: { worktreeId: 'wt-1', paneKey: 'tab-2:leaf-2' }
|
|
})
|
|
expect(lost.claim.claim).toBe('lost')
|
|
expect(lost.rows.get(key)?.ownedPane).toEqual(pane)
|
|
|
|
const settled = settleAgentSessionOperation(won.rows, {
|
|
callerKey: 'caller',
|
|
operationId: id,
|
|
outcome: { status: 'failed', code: 'boom' }
|
|
})
|
|
expect(settled.get(key)?.ownedPane).toEqual(pane)
|
|
expect(isAgentSessionOperationRow(settled.get(key))).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('retention', () => {
|
|
it('keeps a tombstone strictly longer than its id can be admitted as new', () => {
|
|
const expiry = agentSessionOperationExpiry(NOW, NOW)
|
|
const lastAdmissibleAt = NOW + AGENT_SESSION_MAX_NEW_OPERATION_AGE_MS
|
|
expect(expiry).toBeGreaterThan(lastAdmissibleAt)
|
|
// Why: a retry landing in that gap would become a second spawn instead of a replay.
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
admit(rows)
|
|
expect(pruneAgentSessionOperationRows(rows, lastAdmissibleAt).size).toBe(1)
|
|
expect(evaluate(pruneAgentSessionOperationRows(rows, lastAdmissibleAt)).decision).toBe('replay')
|
|
})
|
|
|
|
it('anchors retention to the later of recording and stamping', () => {
|
|
const late = agentSessionOperationExpiry(NOW + 10_000, NOW)
|
|
expect(late).toBe(agentSessionOperationExpiry(NOW + 10_000, NOW + 10_000))
|
|
expect(late).toBeGreaterThan(agentSessionOperationExpiry(NOW, NOW))
|
|
})
|
|
|
|
it('drops only rows past their own expiry', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
const row = admit(rows)
|
|
expect(pruneAgentSessionOperationRows(rows, row.expiresAt).size).toBe(0)
|
|
expect(pruneAgentSessionOperationRows(rows, row.expiresAt - 1).size).toBe(1)
|
|
})
|
|
})
|
|
|
|
describe('persisted row validation', () => {
|
|
it('accepts every recorded outcome shape', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
const row = admit(rows)
|
|
expect(isAgentSessionOperationRow(row)).toBe(true)
|
|
expect(
|
|
isAgentSessionOperationRow({ ...row, outcome: { status: 'succeeded', sessionId: 's-1' } })
|
|
).toBe(true)
|
|
expect(isAgentSessionOperationRow({ ...row, outcome: { status: 'unknown' } })).toBe(true)
|
|
expect(isAgentSessionOperationRow({ ...row, outcome: { status: 'failed', code: 'x' } })).toBe(
|
|
true
|
|
)
|
|
})
|
|
|
|
it('rejects rows a later build could misread', () => {
|
|
const rows = new Map<string, AgentSessionOperationRow>()
|
|
const row = admit(rows)
|
|
expect(isAgentSessionOperationRow({ ...row, operationId: 'garbage' })).toBe(false)
|
|
expect(isAgentSessionOperationRow({ ...row, callerKey: '' })).toBe(false)
|
|
expect(isAgentSessionOperationRow({ ...row, expiresAt: 1.5 })).toBe(false)
|
|
expect(isAgentSessionOperationRow({ ...row, outcome: { status: 'succeeded' } })).toBe(false)
|
|
expect(isAgentSessionOperationRow({ ...row, outcome: null })).toBe(false)
|
|
expect(isAgentSessionOperationRow(null)).toBe(false)
|
|
})
|
|
})
|