Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 25 additions & 1 deletion packages/core/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,20 @@ async function executeAccountCommand(
ctx: CommandContext,
): Promise<OpenDialogPayload> {
const tokens = args.trim().split(/\s+/).filter(Boolean)
log.info('account command parsed', { args, tokens })
const storage = (await ctx.loadAccounts(storePaths(ctx))) ?? {
version: 1 as const,
accounts: [],
}
const accounts = storage.accounts ?? []

if (tokens[0] === 'claustrum') {
log.info('claustrum mode requested', {
hasEnterFn: typeof ctx.enterClaustrumMode === 'function',
accounts: accounts.length,
})
if (!ctx.enterClaustrumMode) {
log.warn('claustrum refused: transition fn absent from command context')
return {
command: 'openai-account',
text: '## Claustrum Unavailable\n\nThe custody runtime is not ready. Try again after OpenAI auth finishes initializing.',
Expand All @@ -372,7 +378,21 @@ async function executeAccountCommand(
},
}
}
const result = await ctx.enterClaustrumMode()
log.info('claustrum transition starting', {})
let result: Awaited<ReturnType<NonNullable<typeof ctx.enterClaustrumMode>>>
try {
result = await ctx.enterClaustrumMode()
} catch (error) {
log.error('claustrum transition threw', {
error: error instanceof Error ? error.message : String(error),
})
throw error
}
log.info('claustrum transition finished', {
status: result.status,
reason: result.reason,
outcomes: result.outcomes,
})
const nextStorage = (await ctx.loadAccounts(storePaths(ctx))) ?? {
version: 1 as const,
accounts: [],
Expand All @@ -398,7 +418,11 @@ async function executeAccountCommand(
}

if (tokens[0] === 'local') {
log.info('local mode requested', {
hasLeaveFn: typeof ctx.leaveClaustrumMode === 'function',
})
if (!ctx.leaveClaustrumMode) {
log.warn('local refused: transition fn absent from command context')
return {
command: 'openai-account',
text: '## Local Mode Unavailable\n\nThe custody runtime is not ready. Try again after OpenAI auth finishes initializing.',
Expand Down
27 changes: 24 additions & 3 deletions packages/core/src/refresh-all-quota.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ export interface RefreshAllQuotaDeps {
) => Promise<
FallbackAccessResolution | typeof CUSTODY_REFUSE | typeof CUSTODY_EXCLUDED
>
resolveMainAccess?: (
storage: AccountStorage,
) => Promise<
FallbackAccessResolution | typeof CUSTODY_REFUSE | typeof CUSTODY_EXCLUDED
>
reportCustodyAuthFailure?: (params: {
handle: string
providerStatus: number
Expand Down Expand Up @@ -230,8 +235,24 @@ export async function refreshAllQuota(
recordOutcome({ account: 'main', ok: true })
} else {
if (!auth.access || (auth.expires ?? 0) < deps.now()) {
const tokens = await deps.refreshMainWithLease()
auth = { ...auth, access: tokens.access, expires: tokens.expires }
const resolvedMain = deps.resolveMainAccess
? await deps.resolveMainAccess(
storage ?? { version: 1, accounts: [] },
)
: CUSTODY_EXCLUDED
if (resolvedMain === CUSTODY_REFUSE) {
recordOutcome({
account: 'main',
ok: false,
error: 'custody refused',
})
auth = { ...auth, access: undefined }
} else if (resolvedMain !== CUSTODY_EXCLUDED) {
auth = { ...auth, access: resolvedMain.token }
} else {
const tokens = await deps.refreshMainWithLease()
auth = { ...auth, access: tokens.access, expires: tokens.expires }
}
}

if (auth.access) {
Expand All @@ -258,7 +279,7 @@ export async function refreshAllQuota(
quotaUpdated = true
recordOutcome({ account: 'main', ok: true })
}
} else {
} else if (!results.some((result) => result.account === 'main')) {
recordOutcome({
account: 'main',
ok: false,
Expand Down
7 changes: 7 additions & 0 deletions packages/opencode/src/core/custody-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export type CustodyRuntime = {
isEnabled(): boolean
/** Cache handle (undefined when custody is disabled). */
getCache(): ClaustrumCredentialCache | undefined
/** Connect the cache on demand and return it. */
ensureCache(): Promise<ClaustrumCredentialCache | undefined>
/** Transport handle (undefined when custody is disabled). */
getTransport(): ClaustrumCacheTransportLike | undefined
/** True if the detection step produced an `available` connection file. */
Expand Down Expand Up @@ -182,6 +184,11 @@ export function __createCustodyRuntimeForTest(
isEnabled,
wasDetected: () => detection?.status === 'available',
getCache: () => cache,
async ensureCache() {
if (cache) return cache
await connectCache()
return cache
},
getTransport: () => transport,
getCustodyProjection: (account, currentNow) => {
const cached = projectionByAccountId.get(account.id)
Expand Down
28 changes: 28 additions & 0 deletions packages/opencode/src/core/custody-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,23 @@ export async function enterClaustrumMode(
return { status: 'aborted', outcomes, reason: 'mode-lock-unavailable' }
locks.push(modeLock)

deps.warn?.(
`transition deps: ${JSON.stringify({
acquireLock: typeof deps.acquireLock,
withStoreTransaction: typeof deps.withStoreTransaction,
readManifest: typeof deps.readManifest,
preflight: typeof deps.preflight,
auth: typeof deps.auth,
authAll: typeof deps.auth?.all,
authGet: typeof deps.auth?.get,
authSet: typeof deps.auth?.set,
accountIds: deps.accountIds?.length,
})}`,
)
const participants = transitionParticipants(deps.accountIds)
deps.warn?.(
`transition participants: ${participants.map((participant) => participant.id).join(',')}`,
)
for (const participant of participants) {
const accountLock = await deps.acquireLock({
name: lockName(participant),
Expand Down Expand Up @@ -262,9 +278,13 @@ export async function enterClaustrumMode(
const capturedGeneration =
persisted?.storeGeneration ?? accountStoreGeneration(initial)
if (!persisted) {
deps.warn?.(
`capturing main slot fingerprint; auth.get is ${typeof deps.auth?.get}`,
)
const mainSlot = asCompleteMainOauthSlot(
await deps.auth.get({ path: { id: 'openai' } }),
)
deps.warn?.(`main slot captured: ${mainSlot ? 'complete' : 'absent'}`)
if (mainSlot) {
fingerprints.main = custodySlotFingerprint(
mainSlot.access,
Expand All @@ -278,8 +298,16 @@ export async function enterClaustrumMode(
}
await step('captured')

deps.warn?.(
`handles resolved: isMap=${handles instanceof Map} size=${
handles instanceof Map ? handles.size : 'n/a'
}`,
)
for (const participant of currentParticipants) {
const handle = handles.get(participant.id)
deps.warn?.(
`participant ${participant.id}: handle=${handle ? 'yes' : 'NO'}`,
)
if (!handle) {
outcomes[participant.id] = 'no-handle'
continue
Expand Down
62 changes: 58 additions & 4 deletions packages/opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,7 @@ export async function CodexAuthPlugin(
whamFn: whamUsageFn,
isFallbackRefreshInert: isFallbackAccountRefreshInert,
resolveFallbackAccess: resolveAccountAccessForCustody,
resolveMainAccess: resolveMainAccessForCustody,
reportCustodyAuthFailure: reportAuthFailureForCustody,
...(respectBackoff === undefined ? {} : { respectBackoff }),
...(skipFresherThanMs === undefined ? {} : { skipFresherThanMs }),
Expand Down Expand Up @@ -2589,8 +2590,20 @@ export async function CodexAuthPlugin(
),
readManifest: readCustodyManifest,
preflight: async ({ accountId, handle }) => {
const cache = custodyRuntime.getCache()
if (!cache || cache.isBlocked(handle)) return 'vault-cold'
custodyLogger.info('preflight probing participant', {
accountId,
hasHandle: handle.length > 0,
})
const cache = await custodyRuntime.ensureCache()
const blocked = cache?.isBlocked(handle)
if (!cache || blocked) {
custodyLogger.warn('preflight vault-cold', {
accountId,
hasCache: cache !== undefined,
blocked,
})
return 'vault-cold'
}
if (
cache.isReauth(handle, custodyOptions?.now?.() ?? Date.now())
) {
Expand Down Expand Up @@ -2621,8 +2634,34 @@ export async function CodexAuthPlugin(
}
},
auth: {
all: () => hostAuth.all(),
get: (value) => hostAuth.get(value),
all: async () => {
if (typeof hostAuth.all === 'function') return hostAuth.all()
const dataHome =
process.env.XDG_DATA_HOME ??
join(os.homedir(), '.local', 'share')
const authPath = join(dataHome, 'opencode', 'auth.json')
try {
const parsed: unknown = JSON.parse(
readFileSync(authPath, 'utf8'),
)
return isRecord(parsed) ? parsed : {}
} catch {
return {}
}
},
get: async (value) => {
if (typeof hostAuth.get === 'function') {
return hostAuth.get(value)
}
if (value.path.id !== 'openai' || !loaderGetAuth) {
custodyLogger.warn('auth.get unavailable for slot', {
id: value.path.id,
hasLoaderGetAuth: loaderGetAuth !== undefined,
})
return undefined
}
return loaderGetAuth()
},
set: async (value) => {
await hostAuth.set(value)
},
Expand Down Expand Up @@ -4304,6 +4343,10 @@ export async function CodexAuthPlugin(
output.maxOutputTokens = undefined
},
config: async (config: { command?: Record<string, unknown> }) => {
createLogger('commands').info('registering commands', {
existing: Object.keys(config.command ?? {}).length,
pid: process.pid,
})
config.command = {
...(config.command ?? {}),
[OPENAI_QUOTA_COMMAND_NAME]: {
Expand Down Expand Up @@ -4352,8 +4395,19 @@ export async function CodexAuthPlugin(
arguments: string
sessionID: string
}) => {
createLogger('commands').info('command hook entered', {
command: input.command,
arguments: input.arguments,
modal: MODAL_COMMANDS.includes(input.command as CommandModalName),
hasCmdCtx: cmdCtx !== null,
pid: process.pid,
})
if (!MODAL_COMMANDS.includes(input.command as CommandModalName)) return
if (!cmdCtx) {
createLogger('commands').warn('command rejected: context not loaded', {
command: input.command,
pid: process.pid,
})
await sendIgnoredMessage(
input.sessionID,
'OpenAI auth plugin is still initializing. Send a request first, then try again.',
Expand Down
Loading
Loading