Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
7a8b2cb
refactor(native-chat): keep the provider resume handle opaque to shar…
brennanb2025 Oct 3, 2026
d8b831e
fix(native-chat): derive journal-row provider handles from the journa…
brennanb2025 Oct 3, 2026
a00a327
fix(native-chat): refuse a stored provider handle written in both forms
brennanb2025 Oct 3, 2026
2667173
refactor(native-chat): route structured agents through registered def…
brennanb2025 Oct 3, 2026
63508a5
refactor(native-chat): name the structured agent list once in host types
brennanb2025 Oct 3, 2026
96028ca
Merge remote-tracking branch 'origin/main' into brennanb2025/acp-a2-a…
brennanb2025 Oct 3, 2026
88a09c4
fix(native-chat): narrow the record before reading its agent's option…
brennanb2025 Oct 4, 2026
38ac910
refactor(native-chat): make the router's registrations the only agent…
brennanb2025 Oct 4, 2026
eba54cd
feat(native-chat): open the structured-chat wire and stored records t…
brennanb2025 Oct 4, 2026
e4a78c8
test(native-chat): cover registered agents on the wire, in storage an…
brennanb2025 Oct 4, 2026
6b8c332
refactor(native-chat): let the record store decide which agents' tabs…
brennanb2025 Oct 4, 2026
745ace6
test(native-chat): declare the pilot test agent's storage
brennanb2025 Oct 4, 2026
c8e42c4
test(native-chat): read the old build's saved tabs through a parsed s…
brennanb2025 Oct 4, 2026
4cc29b0
fix(native-chat): act on restart offers only for agents the calling c…
brennanb2025 Oct 4, 2026
1c6ba3c
test(native-chat): read an agent-registering baseline's storage on it…
brennanb2025 Oct 4, 2026
1f6c566
refactor(native-chat): derive record-store admission from the runtime…
brennanb2025 Oct 4, 2026
0def03e
fix(native-chat): hand the exit drain a promise for every registered …
brennanb2025 Oct 4, 2026
dcddf27
fix(native-chat): let the adoption conflict check read any agent's ow…
brennanb2025 Oct 4, 2026
92861a7
Merge origin/main into ACP opaque handle foundation
brennanb2025 Oct 4, 2026
3717fc4
test(native-chat): use opaque handle in queued rejection fixture
brennanb2025 Oct 5, 2026
aa85dd1
Merge refreshed ACP opaque handle foundation into agent registry
brennanb2025 Oct 5, 2026
a84ac5c
Merge origin/main into ACP agent registry
brennanb2025 Oct 5, 2026
999333a
Merge refreshed ACP agent registry into wire foundation
brennanb2025 Oct 5, 2026
3b68163
Merge remote-tracking branch 'origin/main' into brennanb2025/acp-a1-o…
brennanb2025 Oct 5, 2026
01f9206
test(native-chat): share one Codex journal identity in the integratio…
brennanb2025 Oct 5, 2026
4178e5a
refactor(agent-session): name the handle's adapter state resumeCursor
brennanb2025 Oct 5, 2026
d23e5c0
Merge remote-tracking branch 'origin/brennanb2025/acp-a1-opaque-handl…
brennanb2025 Oct 5, 2026
9ddbafd
Merge remote-tracking branch 'origin/main' into brennanb2025/acp-a2-a…
brennanb2025 Oct 5, 2026
19c8cb5
refactor(agent-session): one required agent registry; declarations ad…
brennanb2025 Oct 5, 2026
c5aaee8
refactor(agent-session): the router applies the declared rewind itself
brennanb2025 Oct 5, 2026
ae44163
Merge remote-tracking branch 'origin/brennanb2025/acp-a2-agent-regist…
brennanb2025 Oct 5, 2026
9e7b78a
Merge remote-tracking branch 'origin/main' into brennanb2025/acp-a3-wire
brennanb2025 Oct 5, 2026
b494ed6
test(agent-session): register the agents the merged-in tests now need
brennanb2025 Oct 5, 2026
7591167
chore(agent-session): the registry is the one lookup; drop the test-o…
brennanb2025 Oct 5, 2026
288f72c
Merge remote-tracking branch 'origin/main' into brennanb2025/acp-a2-a…
brennanb2025 Oct 5, 2026
e260b93
Merge remote-tracking branch 'origin/brennanb2025/acp-a2-agent-regist…
brennanb2025 Oct 5, 2026
65a967b
Merge remote-tracking branch 'origin/main' into brennanb2025/acp-a3-wire
brennanb2025 Oct 5, 2026
4f32dc9
fix(agent-session): keep "dismiss all" restart offers dismissed for t…
brennanb2025 Oct 5, 2026
212af51
fix(agent-session): a changed agent definition never hides that agent…
brennanb2025 Oct 5, 2026
e23946f
refactor(agent-session): each agent's registration says where it runs…
brennanb2025 Oct 5, 2026
a83313b
test(agent-session): drop a duplicate registry key; keep fences out o…
brennanb2025 Oct 5, 2026
b557e5e
fix(agent-session): a scoped dismiss-all persists no per-session fence
brennanb2025 Oct 5, 2026
f0e695f
fix(agent-session): refuse an attach whose agent is not the session's…
brennanb2025 Oct 5, 2026
7b52fb5
fix(agent-session): offer to start a chat only when the start would a…
brennanb2025 Oct 5, 2026
38d510d
fix(agent-session): the model-catalog probe uses a record's account o…
brennanb2025 Oct 5, 2026
1a569b8
refactor(agent-session): the record store admits agent ids; comments …
brennanb2025 Oct 5, 2026
364f11f
docs(agent-session): the record store admits the registered agents' ids
brennanb2025 Oct 5, 2026
374e740
Merge origin/main into acp-a1-opaque-handle
brennanb2025 Oct 5, 2026
22d3d85
Merge acp-a1-opaque-handle (with origin/main) into acp-a2-agent-registry
brennanb2025 Oct 5, 2026
ace8822
Merge acp-a2-agent-registry (with origin/main) into acp-a3-wire
brennanb2025 Oct 5, 2026
6143fb4
test(native-chat): dismiss-all through a remote client's audience kee…
brennanb2025 Oct 5, 2026
2a1cef2
chore(native-chat): keep the record store and recovery capsule under …
brennanb2025 Oct 5, 2026
3b0c7ab
Merge origin/main into acp-a2-agent-registry (main #24862)
brennanb2025 Oct 5, 2026
42c2e2f
Merge acp-a2-agent-registry (main #24862) into acp-a3-wire
brennanb2025 Oct 5, 2026
fc120f7
Merge origin/main into acp-a2-agent-registry (main squash of A1 #2499…
brennanb2025 Oct 5, 2026
fb8bcb8
test(native-chat): build the Stop-opened-turn test's identity and tur…
brennanb2025 Oct 5, 2026
7c4112f
Merge acp-a2-agent-registry (with origin/main d791421568c) into acp-a…
brennanb2025 Oct 5, 2026
e35bf18
test(ratchet): require src/main/provider-process now that it has landed
brennanb2025 Oct 5, 2026
0bb3919
Merge origin/main into acp-a2-agent-registry (main #25706, #25073)
brennanb2025 Oct 5, 2026
5f8deca
Merge brennanb2025/ratchet-provider-process-required (#25710) into ac…
brennanb2025 Oct 5, 2026
5b75928
test(native-chat): keep main's opaque-handle import in the Stop-opene…
brennanb2025 Oct 5, 2026
a21812b
Merge acp-a2-agent-registry (main 9def4b9ba19 + ratchet fix #25710) i…
brennanb2025 Oct 5, 2026
9dbd818
Merge origin/main into acp-a2-agent-registry (main's submission-posit…
brennanb2025 Oct 5, 2026
547158c
Merge acp-a2-agent-registry (main e65bbc95cec) into acp-a3-wire
brennanb2025 Oct 5, 2026
6312c30
Merge origin/main into acp-a2-agent-registry (main's duplicate-import…
brennanb2025 Oct 6, 2026
8e3331c
Merge acp-a2-agent-registry (main c6827fe02a5) into acp-a3-wire
brennanb2025 Oct 6, 2026
5e84b09
Merge current main into agent registry branch
brennanb2025 Oct 6, 2026
6b1ed63
Merge 5e84b09c0d94 into acp-a3-wire
brennanb2025 Oct 6, 2026
db71a89
Merge 08a970a3f117 into acp-a2-agent-registry
brennanb2025 Oct 6, 2026
a94f7ec
Merge db71a89d7438 into acp-a3-wire
brennanb2025 Oct 6, 2026
9702082
Keep saved chats readable without provider registration
brennanb2025 Oct 6, 2026
c3b8f6f
Keep stored-record compatibility checks independent of registration
brennanb2025 Oct 6, 2026
c940bf6
Keep saved providers in restart client audiences
brennanb2025 Oct 6, 2026
d19cd87
Merge current main after registry and lifecycle landed
brennanb2025 Oct 6, 2026
65653f9
test(native-chat): type reveal fixtures without assertions
brennanb2025 Oct 6, 2026
c336e75
test(wire): expose known agents in structured host fixture
brennanb2025 Oct 6, 2026
f164943
Merge main to resolve native launch support conflict
brennanb2025 Oct 6, 2026
2ed6c25
Merge main model-catalog changes into registered-agent wire branch
brennanb2025 Oct 6, 2026
40fa4aa
Supply startability dependency in the new Codex catalog fixture
brennanb2025 Oct 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/main/claude/claude-structured-acquire-catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { agentModelCatalogSessionAccess } from '../native-chat/agent-model-catalog/agent-model-catalog-fingerprint'
import type { AgentModelCatalogStore } from '../native-chat/agent-model-catalog/agent-model-catalog-store'
import { CLAUDE_STRUCTURED_AGENT } from './claude-structured-agent-definition'

/** A Claude session's catalog, keyed by the config directory it launched under. */
export function claudeAcquireCatalogAccess(
store: AgentModelCatalogStore | undefined,
claudeConfigDir: string | null
) {
return agentModelCatalogSessionAccess(store, CLAUDE_STRUCTURED_AGENT, claudeConfigDir)
}
3 changes: 3 additions & 0 deletions src/main/claude/claude-structured-agent-definition.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type { StructuredAgentDefinition } from '../native-chat/agent-session-wire/structured-agent-definition'
import { CLAUDE_STRUCTURED_HANDLE_NAMESPACE } from '../../shared/agent-session-provider-handle-encoding'
import { isClaudeStructuredOptionKey } from './claude-structured-options'
import { claudeFallbackModelOptions } from './claude-structured-session-options'

export const CLAUDE_STRUCTURED_AGENT: StructuredAgentDefinition = {
agent: 'claude',
handleTransport: CLAUDE_STRUCTURED_HANDLE_NAMESPACE.transport,
accountHomeVariable: 'CLAUDE_CONFIG_DIR',
capabilities: {
// Orca's marker-based rewind proof can never pass on the real binary; rewind returns via a fork.
rewind: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ function readAtRest(store: AgentModelCatalogStore, record: AgentSessionRecord) {
const modelCatalog = createAgentModelCatalogService({
store,
getRecord: () => record,
drivesRecord: () => true,
resolveAccountHome: async () => ({ variable: 'CLAUDE_CONFIG_DIR', path: ACCOUNT_HOME })
})
const resting = {
Expand Down
6 changes: 3 additions & 3 deletions src/main/claude/claude-structured-launch-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { resolveClaudeCommand } from '../codex-cli/command'
import { resolveSessionFilePath } from '../native-chat/session-file-resolver'
import { withoutInheritedClaudeConfigDir } from './claude-config-dir-pin'
import type { AgentSessionRecordStore } from '../runtime/agent-session-record-store'
import { agentConfigDirectoryVariable } from '../../shared/agent-session-account-home'
import { CLAUDE_STRUCTURED_AGENT } from './claude-structured-agent-definition'

export const CLAUDE_DEFAULT_SETTING_SOURCES = ['user', 'project', 'local'] as const
export const CLAUDE_SESSION_STATE_EVENTS_ENV = 'CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS'
Expand Down Expand Up @@ -251,7 +251,7 @@ export function createClaudeStructuredLaunchResolver(
`claude structured sessions run on the local host, not ${record.location.executionHostId}`
)
}
const pinned = agentConfigDirectoryVariable('claude')
const pinned = CLAUDE_STRUCTURED_AGENT.accountHomeVariable
if (record.accountHome.variable !== pinned) {
throw new Error(`claude sessions pin ${pinned}, not ${record.accountHome.variable}`)
}
Expand All @@ -266,7 +266,7 @@ export function createClaudeStructuredLaunchResolver(
gate && hasWslBoundClaudeAccount(gate) ? { reason: 'managedAccountUnsupported' } : {}
)
}
// A Claude record's chain holds only Claude handles; the record store refuses anything else.
// A Claude record's chain holds only Claude handles; the attach admission refuses anything else.
const head = agentSessionProviderHandleChainHead(record.providerHandleChain)?.handle ?? null
if (
head &&
Expand Down
8 changes: 2 additions & 6 deletions src/main/claude/claude-structured-session-acquisition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { readClaudeTranscriptEntryUuid } from './claude-transcript-entry-uuid'
import { persistClaudeTurnResumePoint } from './claude-structured-resume-point'
import { withAgentSessionCreatePhase } from '../observability/agent-session-instrumentation'
import { resolveClaudeAcquisitionLaunch } from './claude-structured-acquisition-launch'
import { agentModelCatalogSessionAccess } from '../native-chat/agent-model-catalog/agent-model-catalog-fingerprint'
import { claudeAcquireCatalogAccess } from './claude-structured-acquire-catalog'
import {
bindClaudeConnectionJournalControls,
createClaudeJournalFailureHandler
Expand Down Expand Up @@ -257,11 +257,7 @@ export async function acquireClaudeSession({
})
const session = publication.session
liveSession = session
const catalogAccess = agentModelCatalogSessionAccess(
deps.modelCatalog,
'claude',
launch.claudeConfigDir
)
const catalogAccess = claudeAcquireCatalogAccess(deps.modelCatalog, launch.claudeConfigDir)
if (catalogAccess) {
session.catalogAccess = catalogAccess
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/codex/codex-structured-acquire-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import {
type CodexSessionOptionCatalog
} from './codex-structured-model-catalog'
import { agentModelCatalogSessionAccess } from '../native-chat/agent-model-catalog/agent-model-catalog-fingerprint'
import { CODEX_STRUCTURED_AGENT } from './codex-structured-agent-definition'

export function codexAcquireCatalogAccess(
deps: Pick<CodexStructuredSessionAdapterDeps, 'modelCatalog'>,
launch: Pick<CodexStructuredLaunch, 'codexHome'>
): CodexSessionCatalogAccess | undefined {
return agentModelCatalogSessionAccess(deps.modelCatalog, 'codex', launch.codexHome)
return agentModelCatalogSessionAccess(deps.modelCatalog, CODEX_STRUCTURED_AGENT, launch.codexHome)
}

/** The one catalog read a fast-mode restore needs, store-first. Null degrades
Expand Down
3 changes: 3 additions & 0 deletions src/main/codex/codex-structured-agent-definition.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { StructuredAgentDefinition } from '../native-chat/agent-session-wire/structured-agent-definition'
import { CODEX_STRUCTURED_HANDLE_NAMESPACE } from '../../shared/agent-session-provider-handle-encoding'
import { isCodexTurnOptionKey } from './codex-structured-turn-start'

export const CODEX_STRUCTURED_AGENT: StructuredAgentDefinition = {
agent: 'codex',
handleTransport: CODEX_STRUCTURED_HANDLE_NAMESPACE.transport,
accountHomeVariable: 'CODEX_HOME',
capabilities: {
// A thread with legacy, unpaginated history narrows this to unsupported once it runs.
rewind: true,
Expand Down
6 changes: 3 additions & 3 deletions src/main/codex/codex-structured-launch-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { CodexStructuredLaunch } from './codex-structured-session-adapter'
import type { CodexStructuredPermissionPolicy } from './codex-structured-permission-policy'
import { resolvePinnedCodexRolloutProof } from './codex-pinned-rollout-proof'
import { isWindowsProcessStartTimeAvailable } from '../windows/windows-process-table'
import { agentConfigDirectoryVariable } from '../../shared/agent-session-account-home'
import { CODEX_STRUCTURED_AGENT } from './codex-structured-agent-definition'

export type CodexStructuredLaunchResolverDeps = {
store: AgentSessionRecordStore
Expand Down Expand Up @@ -86,7 +86,7 @@ export function createCodexStructuredLaunchResolver(
) {
throw new Error('codex structured sessions require Windows process creation-time proof')
}
const pinned = agentConfigDirectoryVariable('codex')
const pinned = CODEX_STRUCTURED_AGENT.accountHomeVariable
if (accountHome.variable !== pinned) {
throw new Error(`codex sessions pin ${pinned}, not ${accountHome.variable}`)
}
Expand All @@ -95,7 +95,7 @@ export function createCodexStructuredLaunchResolver(
// concern, and the permission posture they used to smuggle in is derived per acquisition.
const permissionPolicy = deps.resolvePermissionPolicy?.()
const head = agentSessionProviderHandleChainHead(record.providerHandleChain)
// A Codex record's chain holds only Codex handles; the record store refuses anything else.
// A Codex record's chain holds only Codex handles; the attach admission refuses anything else.
const resumeThreadId = head?.handle.nativeId ?? null
// The same saved options every turn sends, so the thread and its turns name one model.
const model = record.options?.model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ describe('Codex session options through the host catalog store', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => ({ variable: 'CODEX_HOME', path: '/homes/a' }),
probes: { codex: hungProbe }
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import type {
AgentModelCatalogSessionAccess,
AgentModelCatalogStore
} from './agent-model-catalog-store'
import { agentConfigDirectoryVariable } from '../../../shared/agent-session-account-home'
import type { AgentSessionHandleProvider } from '../../../shared/agent-session-provider-handle'
import type { AgentSessionStoredAgent } from '../../../shared/agent-session-stored-agent'

/**
* Everything that changes which models a listing can answer with: the agent,
Expand All @@ -14,7 +13,7 @@ import type { AgentSessionHandleProvider } from '../../../shared/agent-session-p
* key is corrected by the next refresh, never by the fingerprint.
*/
export type AgentModelCatalogIdentity = {
agent: 'claude' | 'codex'
agent: string
accountHomeVariable: string
accountHomePath: string
/** Null on the native host; WSL distros each carry their own CLI. */
Expand Down Expand Up @@ -57,7 +56,7 @@ export function agentModelCatalogFingerprintForRecord(
* Native only: both structured adapters refuse non-native locations at launch. */
export function agentModelCatalogSessionAccess(
store: AgentModelCatalogStore | undefined,
agent: AgentSessionHandleProvider,
agent: Pick<AgentSessionStoredAgent, 'agent' | 'accountHomeVariable'>,
accountHomePath: string | null
): AgentModelCatalogSessionAccess | undefined {
if (!store || !accountHomePath) {
Expand All @@ -66,8 +65,8 @@ export function agentModelCatalogSessionAccess(
return {
store,
fingerprint: agentModelCatalogFingerprint({
agent,
accountHomeVariable: agentConfigDirectoryVariable(agent),
agent: agent.agent,
accountHomeVariable: agent.accountHomeVariable,
accountHomePath,
wslDistro: null
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
AgentSessionOptionChoice
} from '../../../shared/agent-session-wire'
import type { AgentModelCatalogEntry } from './agent-model-catalog-store'
import { isStructuredAgentId } from '../../../shared/agent-session-provider-handle-encoding'

const SCHEMA_VERSION = 1
const SAVE_COALESCE_MS = 500
Expand Down Expand Up @@ -67,7 +68,7 @@ function parseEntry(value: unknown): AgentModelCatalogEntry | null {
const row = asRecord(value)
if (
!row ||
(row.agent !== 'claude' && row.agent !== 'codex') ||
!isStructuredAgentId(row.agent) ||
typeof row.fingerprint !== 'string' ||
(row.origin !== 'live-session' && row.origin !== 'probe') ||
typeof row.fetchedAt !== 'number' ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => record('/homes/a'),
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/selected'),
probes: { codex: probe }
})
Expand All @@ -73,6 +74,26 @@ describe('agent model catalog service', () => {
})
})

it('probes as for no record when this build cannot start the record as it is pinned', async () => {
const store = new AgentModelCatalogStore()
const probe = vi.fn(async (_home: string) => listing('gpt-a'))
// A Codex record pinning Claude's variable: its path is not a Codex home to probe under.
const pinned = { ...record('/x'), accountHome: { variable: 'CLAUDE_CONFIG_DIR', path: '/x' } }
const drivesRecord = vi.fn(() => false)
const service = createAgentModelCatalogService({
store,
getRecord: () => pinned,
drivesRecord,
resolveAccountHome: async () => CODEX_HOME('/homes/selected'),
probes: { codex: probe }
})

await service.read({ agent: 'codex', sessionId: 'session-1' })

expect(drivesRecord).toHaveBeenCalledWith(pinned)
expect(probe).toHaveBeenCalledExactlyOnceWith('/homes/selected')
})

it('an account switch with no record reads and prewarms the NEW account, never the old entry', async () => {
const store = new AgentModelCatalogStore()
// The old account listed under its own fingerprint before the switch.
Expand All @@ -82,6 +103,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/new'),
probes: { codex: probe }
})
Expand All @@ -107,6 +129,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/selected')
})
const result = await service.read({ agent: 'codex' })
Expand All @@ -129,6 +152,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => sessionRecord,
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/selected')
})
const result = await service.read({ agent: 'codex', sessionId: 'session-1' })
Expand All @@ -143,6 +167,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => record('/homes/a'),
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/a'),
probes: { codex: probe }
})
Expand All @@ -164,6 +189,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => {
throw new Error('no store yet')
},
Expand All @@ -189,6 +215,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/selected'),
probes: { codex: probe }
})
Expand Down Expand Up @@ -385,6 +412,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/selected')
})
expect(await service.read({ agent: 'codex', waitForListing: true })).toEqual({
Expand All @@ -401,6 +429,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/selected'),
probes: { codex: probe }
})
Expand All @@ -418,6 +447,7 @@ describe('agent model catalog service', () => {
const service = createAgentModelCatalogService({
store,
getRecord: () => undefined,
drivesRecord: () => true,
resolveAccountHome: async () => CODEX_HOME('/homes/selected'),
workspaceMayOverrideDefaultModel
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
AgentSessionAccountHome,
AgentSessionRecord
} from '../../../shared/agent-session-record'
import type { AgentSessionHandleProvider } from '../../../shared/agent-session-provider-handle'
import {
agentModelCatalogFingerprint,
agentModelCatalogFingerprintForRecord
Expand All @@ -17,23 +16,26 @@ import type {
export type AgentModelCatalogServiceDeps = {
store: AgentModelCatalogStore
getRecord: (sessionId: string) => AgentSessionRecord | undefined
/** Whether this build can start the record's agent as the record pins it; a record it cannot
* names no account a probe may start that agent's CLI under. */
drivesRecord: (record: AgentSessionRecord) => boolean
/** The account home a structured launch for this agent would pin right now —
* the SAME resolver the create path fills `record.accountHome` with, so a
* record-less read can never answer from another account's listing. */
resolveAccountHome: (agent: AgentSessionHandleProvider) => Promise<AgentSessionAccountHome>
resolveAccountHome: (agent: string) => Promise<AgentSessionAccountHome>
/** Session-less listers, one per agent that has one on this host. */
probes?: Partial<Record<'claude' | 'codex', AgentModelCatalogProbe>>
probes?: Readonly<Partial<Record<string, AgentModelCatalogProbe>>>
/** Whether the workspace's own config could pick a model other than the listed default. */
workspaceMayOverrideDefaultModel?: (input: {
agent: 'claude' | 'codex'
agent: string
workspacePath: string
accountHomePath: string
}) => Promise<boolean>
}

export type AgentModelCatalogService = {
read: (params: {
agent: 'claude' | 'codex'
agent: string
sessionId?: string
/** Where a new chat would run; null when one was named but is not a local directory. */
workspacePath?: string | null
Expand All @@ -60,7 +62,7 @@ function resultFromEntry(
/** A named workspace keeps the listed default only when none of its own config can replace it. */
async function workspaceKeepsListedDefault(
deps: AgentModelCatalogServiceDeps,
agent: 'claude' | 'codex',
agent: string,
workspacePath: string | null | undefined,
accountHomePath: string | null
): Promise<boolean> {
Expand Down Expand Up @@ -93,7 +95,8 @@ export function createAgentModelCatalogService(
return {
async read(params) {
const record = params.sessionId ? deps.getRecord(params.sessionId) : undefined
const scoped = record && record.provider === params.agent ? record : undefined
const scoped =
record && record.provider === params.agent && deps.drivesRecord(record) ? record : undefined
let fingerprint: string
let accountHomePath: string | null
if (scoped) {
Expand Down
Loading
Loading