test: remove duplicate coverage and strengthen boundary assertions (#33047)

* test: strengthen launch, subject identity, and release witnesses

* test: strengthen approval paths and prompt answer boundaries

* test: strengthen conversation names and mobile log persistence

* refactor: group background task stop contract with stop operations

* test: strengthen question, title, and release validation

* test: distinguish row text and generated dispatch titles

* test: verify mobile revival events and retry exclusivity

* test: reject incomplete child row output assertions

* test: verify copied modules and option selection contracts

* test: verify mobile host state and status retry boundaries

* test: observe connector refusals and real diagnostic delivery

* test: consolidate platform memory coverage and verify bounded work

* test: allow cached legacy failure reads within work bounds

* test: verify packaging boundaries with independent fixtures

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-24 00:13:25 -07:00
committed by GitHub
co-authored by OrcaWin
parent 156f3f4ef0
commit 70c4c55db9
53 changed files with 1033 additions and 393 deletions
+8 -2
View File
@@ -149,7 +149,8 @@ describe('buildAgentChildRowModels: the display vocabulary', () => {
{ displayState: 'unverifiable', detail: { kind: 'no-update' } }
]
])('%s', (_name, views, expected) => {
expect(only(views)).toMatchObject(expected)
const row = only(views)
expect({ displayState: row.displayState, detail: row.detail }).toEqual(expected)
})
it('suppresses the tool line while monitoring, even when a stale operation arrives', () => {
@@ -356,7 +357,12 @@ describe('agentChildRowContextForParent: the reader clock', () => {
it("measures a mirrored parent on this machine's receipt clock, never the host's", () => {
const context = agentChildRowContextForParent(MIRRORED_PARENT, false)
expect(context).toMatchObject({ parentObservedAt: 1_190_000, hostClockOffsetMs: -20 * 60_000 })
expect(context).toEqual({
parentEvidenceFresh: false,
transportObservation: 'live',
parentObservedAt: 1_190_000,
hostClockOffsetMs: -20 * 60_000
})
const [legacy] = buildLegacyAgentChildRowModels(
[{ id: 'a', state: 'working', startedAt: 10 }],
context
+8 -10
View File
@@ -55,7 +55,7 @@ describe('fields the launch fingerprint covers', () => {
)
})
it('separates two launches that reserved different panes', () => {
it('separates two launches that reserved different panes or sessions', () => {
// The key is baked into the pane's env; replaying another pane's key strands the new reservation.
expect(computeAgentLaunchFingerprint({ ...BASE, paneKey: PANE_KEY })).not.toBe(
computeAgentLaunchFingerprint({ ...BASE, paneKey: OTHER_PANE_KEY })
@@ -63,6 +63,13 @@ describe('fields the launch fingerprint covers', () => {
expect(computeAgentLaunchFingerprint({ ...BASE, paneKey: PANE_KEY })).not.toBe(
computeAgentLaunchFingerprint(BASE)
)
const sessionId = '11111111-1111-4111-8111-111111111111'
expect(computeAgentLaunchFingerprint({ ...BASE, sessionId })).not.toBe(
computeAgentLaunchFingerprint({ ...BASE, sessionId: '22222222-2222-4222-8222-222222222222' })
)
expect(computeAgentLaunchFingerprint({ ...BASE, sessionId })).not.toBe(
computeAgentLaunchFingerprint(BASE)
)
})
it('separates a retry that picked another surface, or none, from the first launch', () => {
@@ -122,13 +129,4 @@ describe('compatibility with rows written before these fields existed', () => {
computeAgentLaunchFingerprint({ ...BASE, agentArgs: '--model opus', cwd: '/repo/packages/a' })
).toBe(beforePaneKey)
})
it('still matches when the caller sends only telemetry the digest excludes', () => {
const previousBuild = canonicalAgentSessionDigest({
method: 'agent.launch',
agent: BASE.agent,
target: BASE.target
})
expect(fingerprintOfWirePayload({ ...BASE, launchSource: 'quick_command' })).toBe(previousBuild)
})
})
+1 -1
View File
@@ -26,7 +26,7 @@ describe('repoIsRemote', () => {
expect(repoIsRemote({ connectionId: null, executionHostId: 'runtime:vm-1' })).toBe(false)
})
it('keeps plain local and WSL rows local', () => {
it('keeps plain local rows local', () => {
expect(repoIsRemote({ connectionId: null, executionHostId: null })).toBe(false)
expect(repoIsRemote({ connectionId: null, executionHostId: 'local' })).toBe(false)
})
+40 -27
View File
@@ -20,24 +20,23 @@ function waiting(interactivePrompt: string, toolName?: string) {
describe('readAgentPromptAnswers', () => {
it('reads an approval with the cards’ own allow and deny keys', () => {
const read = readAgentPromptAnswers(waiting(approval), 'claude', 'p1', true)
expect(read?.prompt).toEqual({
kind: 'approval',
promptId: 'p1',
tool: 'Bash',
summary: 'rm -rf build',
options: [
{ id: 'allow_once', label: 'Allow once' },
{ id: 'reject_once', label: 'Reject' }
]
})
expect(Object.fromEntries(read!.keysByOption)).toEqual({
allow_once: '1',
reject_once: '\x1b'
})
expect(
readAgentPromptAnswers(waiting(approval), 'codex', 'p1', true)?.keysByOption.get('allow_once')
).toBe('1')
for (const agent of ['claude', 'codex']) {
const read = readAgentPromptAnswers(waiting(approval), agent, 'p1', true)
expect(read?.prompt).toEqual({
kind: 'approval',
promptId: 'p1',
tool: 'Bash',
summary: 'rm -rf build',
options: [
{ id: 'allow_once', label: 'Allow once' },
{ id: 'reject_once', label: 'Reject' }
]
})
expect(Object.fromEntries(read!.keysByOption)).toEqual({
allow_once: '1',
reject_once: '\x1b'
})
}
})
it('reads a one-question single choice with each option’s selector key', () => {
@@ -58,7 +57,10 @@ describe('readAgentPromptAnswers', () => {
})
it('leaves to Orca what one keystroke cannot answer', () => {
const unsupported = { kind: 'unsupported', promptId: 'p' }
const unsupported = {
prompt: { kind: 'unsupported', promptId: 'p' },
keysByOption: new Map()
}
const cases = [
waiting(question({}, 2)),
waiting(question({ multiSelect: true })),
@@ -66,18 +68,29 @@ describe('readAgentPromptAnswers', () => {
waiting('not json'),
{ state: 'blocked' as const }
]
for (const row of cases) {
expect(readAgentPromptAnswers(row, 'claude', 'p', true)?.prompt).toEqual(unsupported)
for (const agent of ['claude', 'codex']) {
for (const row of cases) {
expect(readAgentPromptAnswers(row, agent, 'p', true)).toEqual(unsupported)
}
}
const asyncQuestion = JSON.stringify({
questions: [{ title: 'Which color?', options: ['Red', 'Blue'] }]
})
expect(
readAgentPromptAnswers(waiting(asyncQuestion, 'request_user_input_async'), 'codex', 'p', true)
).toEqual(unsupported)
})
it('leaves every prompt to Orca for an agent without hooks or known keys', () => {
expect(readAgentPromptAnswers(waiting(approval), 'claude', 'p', false)?.prompt.kind).toBe(
'unsupported'
)
expect(readAgentPromptAnswers(waiting(approval), 'aider', 'p', true)?.prompt.kind).toBe(
'unsupported'
)
const unsupported = {
prompt: { kind: 'unsupported', promptId: 'p' },
keysByOption: new Map()
}
expect(readAgentPromptAnswers(waiting(approval), 'claude', 'p', false)).toEqual(unsupported)
expect(readAgentPromptAnswers(waiting(approval), 'aider', 'p', true)).toEqual(unsupported)
expect(
readAgentPromptAnswers(waiting(question(), 'AskUserQuestion'), 'grok', 'p', true)
).toEqual(unsupported)
})
it('reads nothing while the agent is not waiting on the user', () => {
+16 -1
View File
@@ -25,10 +25,13 @@ describe('getAgentRowConversationName', () => {
})
it('keeps OpenCode semantic session titles whole', () => {
const tab = makeTab({ title: 'OC | build the release pipeline' })
const tab = makeTab({ title: 'OC | build the release pipeline', generatedTitle: 'Older title' })
expect(getAgentRowConversationName(tab, 'opencode', false)).toBe(
'OC | build the release pipeline'
)
expect(getAgentRowConversationName(tab, 'opencode', true)).toBe(
'OC | build the release pipeline'
)
})
it("shows the title owned by the row's provider session", () => {
@@ -40,6 +43,11 @@ describe('getAgentRowConversationName', () => {
expect(getAgentRowConversationName(tab, 'claude', true, undefined, 's1')).toBe(
'Fix the lease probe'
)
expect(getAgentRowConversationName(tab, 'claude', true, undefined, 's2')).toBe(
'Fix intake flow'
)
expect(getAgentRowConversationName(tab, 'codex', true, undefined, 's1')).toBe('Fix intake flow')
expect(getAgentRowConversationName(tab, 'claude', true)).toBe('Fix intake flow')
})
it('lets a manual rename and a quick command still outrank the provider title', () => {
@@ -208,6 +216,13 @@ describe('getAgentRowConversationName', () => {
})
it('rejects empty, glyph-only, and default terminal titles', () => {
expect(
getAgentRowConversationName(
makeTab({ title: 'Workbench', defaultTitle: 'Workbench' }),
'claude',
false
)
).toBeNull()
expect(getAgentRowConversationName(makeTab(), 'claude', false)).toBeNull()
expect(getAgentRowConversationName(makeTab({ title: '✳' }), 'claude', false)).toBeNull()
expect(
+18 -6
View File
@@ -72,7 +72,7 @@ Keep the raw preamble out of the sidebar.`
})
it('falls back to the raw prompt outside orchestration workers', () => {
expect(getAgentRowPrimaryText({ prompt: 'Fix checkout race' })).toBe('Fix checkout race')
expect(getAgentRowPrimaryText({ prompt: ' Fix checkout race\n ' })).toBe('Fix checkout race')
})
// Why: full preambles bury === TASK === after multi-KB CLI instructions. The
@@ -226,12 +226,10 @@ Checkout race body`,
}
})
).toBe('Fix checkout race')
})
it('ignores sticky orchestration for non-dispatch generated titles', () => {
expect(
getAgentRowGeneratedTitleText({
prompt: 'Refactor the auth middleware',
prompt:
'You are working inside Orca, a multi-agent IDE. Your task ID is: task-2 === TASK === New worker body',
orchestration: {
taskId: 'task-1',
dispatchId: 'ctx-1',
@@ -239,6 +237,20 @@ Checkout race body`,
displayName: 'Stale worker label'
}
})
).toBe('Refactor the auth middleware')
).toBe('New worker body')
})
it('ignores sticky orchestration for non-dispatch generated titles', () => {
expect(
getAgentRowGeneratedTitleText({
prompt: ' Refactor the auth middleware\n ',
orchestration: {
taskId: 'task-1',
dispatchId: 'ctx-1',
taskTitle: 'Stale task',
displayName: 'Stale worker label'
}
})
).toBe(' Refactor the auth middleware\n ')
})
})
@@ -40,6 +40,13 @@ describe('approvalBlockedPathToShow', () => {
})
it('checks only the text the card draws: a plan replaces the detail', () => {
expect(
approvalBlockedPathToShow({
blockedPath: '/repo/notes.md',
subject: { kind: 'plan', text: 'Read /repo/notes.md' },
detail: 'unused'
})
).toBeNull()
expect(
approvalBlockedPathToShow({
blockedPath: '/repo/plan.md',
@@ -72,7 +79,10 @@ describe('approvalBlockedPathToShow', () => {
).toBe('/home/me/secret.txt')
})
it('shows a path the text names only as the start of a longer name', () => {
it('distinguishes whole paths from longer names across all occurrences', () => {
expect(
approvalBlockedPathToShow({ blockedPath: '/srv/data', detail: 'ls /srv/data-old /srv/data' })
).toBeNull()
expect(
approvalBlockedPathToShow({ blockedPath: '/srv/data', detail: 'ls /srv/data-old/x' })
).toBe('/srv/data')
@@ -5,7 +5,10 @@ import {
pendingPromptsAllUnanswerableHere
} from './agent-session-approval-subject'
import type { AgentJournalItemBody } from './agent-session-journal-types'
import { AGENT_JOURNAL_APPROVAL_SUBJECT_KINDS } from './agent-session-journal-schemas'
import {
AGENT_JOURNAL_APPROVAL_SUBJECT_KINDS,
isAdmissibleAgentJournalItemBody
} from './agent-session-journal-schemas'
it('draws exactly the subject kinds the journal schema knows', () => {
expect([...DRAWN_APPROVAL_SUBJECT_KINDS].sort()).toEqual(
@@ -20,14 +23,14 @@ it("reads only a subject of a kind it does not draw as a newer build's", () => {
})
function pending(body: Record<string, unknown>): { body: AgentJournalItemBody } {
return JSON.parse(
JSON.stringify({
body: {
...body,
resolution: { state: 'pending', selectedOptionId: null, resolvedBy: null, resolvedAt: null }
}
})
)
const promptBody = {
...body,
resolution: { state: 'pending', selectedOptionId: null, resolvedBy: null, resolvedAt: null }
}
if (!isAdmissibleAgentJournalItemBody(promptBody)) {
throw new Error('Invalid pending prompt fixture')
}
return { body: promptBody }
}
const NEWER = pending({
kind: 'approval',
@@ -43,7 +46,7 @@ const PLAN = pending({
subject: { kind: 'plan', text: 'do it' },
options: [{ id: 'allow', label: 'Approve' }]
})
const QUESTION = pending({ kind: 'question', title: 'Which?', options: [] })
const QUESTION = pending({ kind: 'question', question: 'Which?', options: [] })
it('holds only while every pending prompt is an approval this build cannot answer', () => {
expect(pendingPromptsAllUnanswerableHere([])).toBe(false)
@@ -54,6 +57,8 @@ it('holds only while every pending prompt is an approval this build cannot answe
const cancelled = JSON.parse(
JSON.stringify(NEWER).replace('"state":"pending"', '"state":"cancelled"')
)
expect(pendingPromptsAllUnanswerableHere([cancelled, NEWER])).toBe(true)
expect(pendingPromptsAllUnanswerableHere([NEWER, cancelled])).toBe(true)
expect(pendingPromptsAllUnanswerableHere([cancelled, PLAN])).toBe(false)
expect(pendingPromptsAllUnanswerableHere([cancelled])).toBe(false)
})
@@ -31,7 +31,8 @@ describe('normalizeAgentSessionConversationName', () => {
})
it('bounds a pasted essay to the stored maximum', () => {
const normalized = normalizeAgentSessionConversationName('a'.repeat(1000))
const normalized = normalizeAgentSessionConversationName(`First ${'x'.repeat(994)}`)
expect(normalized).toBe(`First ${'x'.repeat(194)}`)
expect(normalized).toHaveLength(AGENT_SESSION_CONVERSATION_NAME_MAX_LENGTH)
expect(isAgentSessionConversationName(normalized)).toBe(true)
})
@@ -50,14 +51,9 @@ describe('isAgentSessionConversationName', () => {
})
describe('normalizeAgentSessionConversationName hostile text', () => {
it('strips control characters and bidi overrides', () => {
// U+202E renders what follows right-to-left, so a tab could show a label
// that reads as text the name does not contain.
expect(normalizeAgentSessionConversationName('Fix\u202Egnp.exe probe')).toBe(
'Fix gnp.exe probe'
)
it('strips controls and lone surrogates', () => {
expect(normalizeAgentSessionConversationName('Fix\u0007the probe')).toBe('Fix the probe')
expect(normalizeAgentSessionConversationName('Fix\u200Bthe probe')).toBe('Fix the probe')
expect(normalizeAgentSessionConversationName('Fix\uD800 the\uDC00 probe')).toBe('Fix the probe')
expect(normalizeAgentSessionConversationName('\u202E\u200B ')).toBeNull()
})
@@ -18,10 +18,21 @@ describe('agent-session grouped question answers', () => {
{ questionId: 'q2', optionIds: [], other: 'SSH host' }
]
it('round-trips grouped multi-select and free-text answers', () => {
it('round-trips grouped answers and rejects malformed wire payloads', () => {
expect(decodeAgentSessionQuestionAnswers(encodeAgentSessionQuestionAnswers(answers))).toEqual(
answers
)
const malformed = [
'not-grouped',
'question-group:%',
'question-group:not-json',
'question-group:{}',
`${GROUP_ANSWER_PREFIX}${JSON.stringify([answers[0], { questionId: 'q2', optionIds: [1] }])}`,
`${GROUP_ANSWER_PREFIX}${JSON.stringify([{ questionId: 'q1', optionIds: [], other: 1 }])}`
]
for (const value of malformed) {
expect(decodeAgentSessionQuestionAnswers(value)).toBeNull()
}
})
it('keeps compact answers readable by the original percent-decoding contract', () => {
@@ -48,9 +59,12 @@ describe('agent-session grouped question answers', () => {
expect(
decodeAgentSessionQuestionAnswers(encodeAgentSessionQuestionAnswers(unicodeAnswers))
).toEqual(unicodeAnswers)
expect(
decodeWithOriginalPercentDecoder(encodeAgentSessionQuestionAnswers(unicodeAnswers))
).toEqual(unicodeAnswers)
})
it("fits Claude's maximum choice group within a 512-character host response bound", () => {
it("keeps Claude's maximum choice group compact without losing selections", () => {
const maximumSelections = Array.from({ length: 4 }, (_, questionIndex) => ({
questionId: `q${questionIndex + 1}`,
optionIds: Array.from(
@@ -59,7 +73,10 @@ describe('agent-session grouped question answers', () => {
)
}))
expect(encodeAgentSessionQuestionAnswers(maximumSelections).length).toBeLessThanOrEqual(512)
const encoded = encodeAgentSessionQuestionAnswers(maximumSelections)
expect(encoded.length).toBeLessThanOrEqual(512)
expect(decodeAgentSessionQuestionAnswers(encoded)).toEqual(maximumSelections)
expect(decodeWithOriginalPercentDecoder(encoded)).toEqual(maximumSelections)
})
it('validates each grouped answer against its question shape', () => {
@@ -83,6 +100,24 @@ describe('agent-session grouped question answers', () => {
]
expect(isValidAgentSessionQuestionAnswers(questions, answers)).toBe(true)
expect(isValidAgentSessionQuestionAnswers(questions, answers.toReversed())).toBe(true)
const invalid = [
[],
[...answers, { questionId: 'q3', optionIds: [] }],
[answers[0]!, answers[0]!],
[{ ...answers[0]!, questionId: 'unknown' }, answers[1]!],
[{ ...answers[0]!, optionIds: [] }, answers[1]!],
[{ ...answers[0]!, other: 'not offered' }, answers[1]!]
]
for (const candidate of invalid) {
expect(isValidAgentSessionQuestionAnswers(questions, candidate)).toBe(false)
}
expect(
isValidAgentSessionQuestionAnswers(
[{ ...questions[0]!, multiSelect: false }, questions[1]!],
answers
)
).toBe(false)
expect(
isValidAgentSessionQuestionAnswers(questions, [
{ questionId: 'q1', optionIds: ['unknown'] },
@@ -14,18 +14,22 @@ describe('provider free-text question metadata', () => {
freeTextInput: { allowEmpty: true, multiline: true, initialValue: 'first\nsecond' }
}
it.each(['', ' ', ' first\nsecond\u2028third\u2029fourth '])(
'preserves the exact answer %j across the older wire form',
(other) => {
const answers = [{ questionId: 'editor:1', optionIds: [], other }]
const encoded = legacyAgentSessionSelectedOptionId(body, answers)
expect(encoded).not.toBeNull()
expect(legacyAgentSessionQuestionAnswers(body, encoded!)).toEqual(answers)
expect(isValidAgentSessionQuestionAnswers(agentSessionPromptQuestions(body), answers)).toBe(
true
)
}
)
it.each([
['', 'editor%3A1:'],
[' ', 'editor%3A1:%20%20'],
[
' first\nsecond\u2028third\u2029fourth ',
'editor%3A1:%20first%0Asecond%E2%80%A8third%E2%80%A9fourth%20'
]
])('preserves the exact answer %j across the older wire form', (other, expectedEncoded) => {
const answers = [{ questionId: 'editor:1', optionIds: [], other }]
const encoded = legacyAgentSessionSelectedOptionId(body, answers)
expect(encoded).toBe(expectedEncoded)
expect(legacyAgentSessionQuestionAnswers(body, encoded!)).toEqual(answers)
expect(isValidAgentSessionQuestionAnswers(agentSessionPromptQuestions(body), answers)).toBe(
true
)
})
it('requires an explicitly supplied answer even when empty is allowed', () => {
expect(
+10 -6
View File
@@ -46,15 +46,18 @@ describe('agent status subjects', () => {
makePtyRunAgentStatusSubject(scope({ wslDistro: 'Ubuntu' }), identity),
makePtyRunAgentStatusSubject(scope({ executionHostId: 'ssh:target-a' }), identity),
makePtyRunAgentStatusSubject(scope({ executionHostId: 'runtime:peer-a' }), identity),
makePtyRunAgentStatusSubject(
scope({ workspaceId: 'folder-1', workspaceKind: 'folder' }),
identity
),
makePtyRunAgentStatusSubject(scope({ workspaceId: 'workspace-2' }), identity),
makePtyRunAgentStatusSubject(scope({ workspaceKind: 'folder' }), identity),
makePtyAgentStatusSubject(scope(), identity),
makeStructuredAgentStatusSubject(scope(), identity)
]
expect(new Set(subjects.map(serializeAgentStatusSubject))).toHaveLength(subjects.length)
for (const [index, subject] of subjects.entries()) {
for (const other of subjects.slice(index + 1)) {
expect(agentStatusSubjectsEqual(subject, other)).toBe(false)
}
}
})
it('keeps the existing structured-session encoding byte-for-byte stable', () => {
@@ -69,7 +72,7 @@ describe('agent status subjects', () => {
it.each([
null,
{ kind: 'future' },
{ kind: 'future', ...scope(), paneKey: PANE_KEY },
{
kind: 'pty-run',
...scope(),
@@ -125,7 +128,8 @@ describe('agent status subjects', () => {
'',
'agent-status-subject-v1:not-json',
'agent-status-subject-v1:["pty-run","local",null,"workspace-1","git-worktree"]',
'agent-status-subject-v1:["unknown","local",null,"workspace-1","git-worktree","id"]'
'agent-status-subject-v1:["unknown","local",null,"workspace-1","git-worktree","id"]',
'agent-status-subject-v1:["pty-run","local",null,"workspace-1","git-worktree","run_11111111-1111-4111-8111-111111111111","extra"]'
])('rejects malformed serialized subject %#', (value) => {
expect(deserializeAgentStatusSubject(value)).toBeNull()
})
@@ -13,13 +13,24 @@ describe('structured agent session option codec', () => {
})
it('rejects string booleans on the UI side and non-canonical strings on the wire side', () => {
expect(encodeStructuredAgentSessionOptionValue('fastMode', 'true')).toBeNull()
expect(decodeStructuredAgentSessionOptionValue('fastMode', 'TRUE')).toBeNull()
expect(decodeStructuredAgentSessionOptionValue('fastMode', '1')).toBeNull()
for (const value of ['true', 'false']) {
expect(encodeStructuredAgentSessionOptionValue('fastMode', value)).toBeNull()
}
for (const value of ['TRUE', '1', 'False', ' true', 'false ', '', '0']) {
expect(decodeStructuredAgentSessionOptionValue('fastMode', value)).toBeNull()
}
})
it('passes existing string options through unchanged', () => {
expect(encodeStructuredAgentSessionOptionValue('model', 'gpt-example')).toBe('gpt-example')
expect(decodeStructuredAgentSessionOptionValue('effort', 'high')).toBe('high')
it('preserves string options in both directions and rejects boolean values', () => {
for (const [id, value] of [
['model', 'gpt-example'],
['effort', 'high'],
['serviceTier', 'flex']
] as const) {
expect(encodeStructuredAgentSessionOptionValue(id, value)).toBe(value)
expect(decodeStructuredAgentSessionOptionValue(id, value)).toBe(value)
expect(encodeStructuredAgentSessionOptionValue(id, true)).toBeNull()
expect(encodeStructuredAgentSessionOptionValue(id, false)).toBeNull()
}
})
})
@@ -82,7 +82,7 @@ describe('a Claude model its CLI no longer lists', () => {
}
})
it('keeps a remembered model and effort, including under a model with no listed effort', () => {
it('waits for an unlisted seed and carries a current effort without an offered menu', () => {
// The placeholder stands until the host answers with the chat's own model; create seeds it.
expect(carried(withHostList(), remembered)).toBeUndefined()
const haiku = { id: 'haiku', label: 'Haiku', isDefault: false, efforts: [] }
@@ -92,6 +92,13 @@ describe('a Claude model its CLI no longer lists', () => {
current: { model: 'haiku', effort: 'high' }
})
expect(shown(listed, undefined, {})).toEqual({ model: 'haiku', effort: 'high' })
expect(carried(listed)).toEqual({ model: 'haiku', effort: 'high' })
expect(
structuredAgentSessionShownSnapshot(listed).find((entry) => entry.id === 'effort')
).toMatchObject({
settable: false,
kind: { currentValue: 'high', choices: [{ value: 'high', label: 'high' }] }
})
})
})
@@ -102,10 +109,22 @@ describe('what a send carries', () => {
expect(carried(withHostList(fresh(), listing))).toEqual({ model: 'sonnet' })
})
it('carries a pick on its way as given', () => {
const listed = withHostList(fresh(), { ...LISTED, models: [OPUS, SONNET] })
expect(carried(listed, remembered, { model: 'sonnet' })).toMatchObject({ model: 'sonnet' })
expect(carried(withHostList(), undefined, { model: 'claude-retired' })).toMatchObject({
it('carries held picks without committing them to the chat', () => {
const listed = {
...withHostList(fresh(), { ...LISTED, models: [OPUS, SONNET] }),
pendingId: 'model'
}
const originalRecord = structuredClone(listed.record)
const view = structuredAgentSessionOptionView(listed, remembered, {
model: 'sonnet',
effort: 'high'
})
expect(structuredAgentSessionSendSelection(view)).toEqual({ model: 'sonnet', effort: 'high' })
expect(view.pendingId).toBe('model')
expect(view.record).not.toBe(listed.record)
expect(listed.record).toEqual(originalRecord)
expect(carried(listed, remembered, { model: 'sonnet' })).toEqual({ model: 'sonnet' })
expect(carried(withHostList(), undefined, { model: 'claude-retired' })).toEqual({
model: 'claude-retired'
})
})
@@ -152,6 +171,12 @@ describe("a chat's model and effort, apart from the defaults filling them in", (
const state = resumed()
expect(shown(state, undefined, {})).toEqual({ model: 'opus', effort: 'low' })
expect(carried(state)).toEqual({ model: 'opus', effort: 'low' })
expect(
structuredAgentSessionShownSnapshot(state).map(({ id, valueSource }) => ({ id, valueSource }))
).toEqual([
{ id: 'model', valueSource: 'dispatched' },
{ id: 'effort', valueSource: 'dispatched' }
])
})
it('lets a new chat pick its effort before its first message, under the default it shows', () => {