mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
fix(mobile): stop a stale session-start report reverting a model pick
Re-entering a chat tab re-delivers the same `agentStatus.model`, and the reported-model effect re-applied it unconditionally — so picking a model, moving to another tab, and coming back reverted the pill to the model the agent reported at session start, which cannot have observed the `/model` sent after it. The status stream reconnecting had the same effect. A report is now only treated as evidence when the matched catalog id CHANGES for that scope; a genuinely new report still supersedes a local pick. Mobile has no screen read to confirm a switch against, so the repeat is all we can key off.
This commit is contained in:
@@ -135,6 +135,30 @@ describe('useMobileNativeChatSessionOptions', () => {
|
||||
expect(effort!.kind).toMatchObject({ currentValue: 'low' })
|
||||
})
|
||||
|
||||
it('does not revive a stale session-start report over a newer local pick', async () => {
|
||||
mount({ reportedModel: 'claude-sonnet-5' })
|
||||
await act(async () => {
|
||||
await api!.setOption('model', 'opus')
|
||||
})
|
||||
expect(api!.snapshot[0]!.kind).toMatchObject({ currentValue: 'opus' })
|
||||
// Leaving the tab and returning re-delivers the SAME session-start report,
|
||||
// which cannot have observed the `/model opus` sent after it.
|
||||
update({ scopeKey: 'host\0worktree\0other' })
|
||||
update({ scopeKey: 'host\0worktree\0tab' })
|
||||
expect(api!.snapshot[0]).toMatchObject({ valueSource: 'dispatched' })
|
||||
expect(api!.snapshot[0]!.kind).toMatchObject({ currentValue: 'opus' })
|
||||
})
|
||||
|
||||
it('still lets a genuinely new report supersede a local pick', async () => {
|
||||
mount({ reportedModel: 'claude-sonnet-5' })
|
||||
await act(async () => {
|
||||
await api!.setOption('model', 'opus')
|
||||
})
|
||||
update({ reportedModel: 'claude-haiku-4-5' })
|
||||
expect(api!.snapshot[0]).toMatchObject({ valueSource: 'reported' })
|
||||
expect(api!.snapshot[0]!.kind).toMatchObject({ currentValue: 'haiku' })
|
||||
})
|
||||
|
||||
it('keeps the live tab’s tracked model when other tabs overflow the record cap', async () => {
|
||||
mount()
|
||||
await act(async () => {
|
||||
|
||||
@@ -39,11 +39,17 @@ type PendingOperation = { id: string; token: number }
|
||||
// scope cache. Bounded so long sessions across many tabs can't grow unbounded.
|
||||
const MOBILE_SESSION_OPTION_RECORD_CAP = 32
|
||||
const recordsByScope = new Map<string, NativeChatSessionOptionRecord>()
|
||||
// The catalog model id last taken from a hook report, per scope. Mobile cannot
|
||||
// read the agent's screen, so a repeat of the same report is not new evidence.
|
||||
const appliedReportByScope = new Map<string, string>()
|
||||
|
||||
function getScopedRecord(scopeKey: string, agent: string): NativeChatSessionOptionRecord {
|
||||
const existing = recordsByScope.get(scopeKey)
|
||||
const record =
|
||||
existing && existing.agent === agent ? existing : createNativeChatSessionOptionRecord(agent)
|
||||
if (record !== existing) {
|
||||
appliedReportByScope.delete(scopeKey)
|
||||
}
|
||||
// Why: delete-then-set on every read makes the touched scope most-recent, so
|
||||
// eviction only sheds the oldest UNTOUCHED tab. Insertion order alone would let
|
||||
// a long-lived active tab be the oldest key and lose its tracked model.
|
||||
@@ -55,12 +61,14 @@ function getScopedRecord(scopeKey: string, agent: string): NativeChatSessionOpti
|
||||
break
|
||||
}
|
||||
recordsByScope.delete(oldest)
|
||||
appliedReportByScope.delete(oldest)
|
||||
}
|
||||
return record
|
||||
}
|
||||
|
||||
export function clearMobileSessionOptionRecordsForTests(): void {
|
||||
recordsByScope.clear()
|
||||
appliedReportByScope.clear()
|
||||
}
|
||||
|
||||
const EMPTY_SNAPSHOT: SessionOptionDescriptor[] = []
|
||||
@@ -109,6 +117,14 @@ export function useMobileNativeChatSessionOptions(args: {
|
||||
if (!matched) {
|
||||
return
|
||||
}
|
||||
// Why: the same report is re-delivered whenever the tab is re-entered or the
|
||||
// status stream reconnects, and a session-start report cannot have observed a
|
||||
// `/model` sent after it. Re-applying it would revert the user's pick. Only a
|
||||
// report that CHANGES is evidence; the value itself still wins when it does.
|
||||
if (appliedReportByScope.get(scopeKey) === matched) {
|
||||
return
|
||||
}
|
||||
appliedReportByScope.set(scopeKey, matched)
|
||||
const record = getScopedRecord(scopeKey, agent)
|
||||
if (applyNativeChatReportedSessionOptions(record, { model: matched })) {
|
||||
bump()
|
||||
|
||||
Reference in New Issue
Block a user