Skip to content

[codex] add Codex usage indicator#537

Open
dsus4wang wants to merge 3 commits intotiann:mainfrom
dsus4wang:codex/codex-usage-indicator
Open

[codex] add Codex usage indicator#537
dsus4wang wants to merge 3 commits intotiann:mainfrom
dsus4wang:codex/codex-usage-indicator

Conversation

@dsus4wang
Copy link
Copy Markdown

Summary

Adds live Codex usage state and a composer usage ring for Codex sessions.

Changes

  • Captures Codex token_count events from app-server notifications and transcript tailing.
  • Stores structured usage in session.metadata.codexUsage and sends metadata patches over SSE.
  • Shows a compact usage ring beside the send button, with popover details for context window, rate limits, and token breakdown.

Validation

  • bun run test:cli -- src/codex/codexRemoteLauncher.test.ts src/codex/utils/codexUsage.test.ts
  • cd shared && bun test src/codexUsageSchema.test.ts
  • cd web && bun run test -- src/components/AssistantChat/codexUsageDisplay.test.ts
  • bun typecheck

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Codex session listing bypasses the runner workspace scope — the new machine RPC returns every transcript under CODEX_HOME, including title/path, without using the existing workspace-root guard that protects machine directory listing and spawn. On a scoped runner, this lets the web UI enumerate Codex sessions outside the allowed workspace. Evidence cli/src/modules/common/handlers/codexSessions.ts:15.
    Suggested fix:

    const allowed = options?.isPathAllowed
    const sessions = []
    for (const session of result.sessions) {
        if (!allowed || await allowed(session.path)) {
            sessions.push(session)
        }
    }
    return { success: true, sessions, nextCursor: result.nextCursor }
  • [Major] importHistory is dropped before the runner builds Codex args — the hub now sends importHistory to spawn-happy-session, but the machine RPC handler does not destructure or forward it to spawnSession, so buildCliArgs() never sees it and never adds --hapi-import-history. Resuming a Codex session from the new selector starts the Codex thread but does not import the transcript into HAPI. Evidence hub/src/sync/rpcGateway.ts:158.
    Suggested fix:

    const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, effort, modelReasoningEffort, yolo, permissionMode, token, sessionType, worktreeName, importHistory } = params || {}
    
    const result = await spawnSession({
        directory,
        sessionId,
        resumeSessionId,
        importHistory,
        // existing fields...
    })
  • [Minor] Local --hapi-import-history can replay the same transcript twice — runCodex() imports the transcript before launching, then the local scanner is started with replayExistingEvents: session.importHistory and sends those same existing user/agent messages again. Evidence cli/src/codex/codexLocalLauncher.ts:73.
    Suggested fix:

    const createdScanner = await createCodexSessionScanner({
        transcriptPath,
        replayExistingEvents: false,
        onSessionId: (sessionId) => {
            session.onSessionFound(sessionId)
        },
        // existing onEvent...
    })

Questions

  • None.

Summary
Review mode: initial
3 findings: one workspace-scope privacy regression, one broken import-history propagation path, and one local duplicate-history edge case.

Testing

  • Not run (automation); static git diff --check passed.

HAPI Bot

logger.debug('List Codex sessions request');

try {
const result = await listCodexSessions(data ?? {});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] This handler returns every Codex transcript under CODEX_HOME with title/path, but it is registered as a machine RPC and does not receive the runner's workspaceRoot guard. A scoped runner already blocks machine directory listing/spawn outside the root; this endpoint now bypasses that boundary and exposes sessions from unrelated directories.

Suggested fix:

const allowed = options?.isPathAllowed
const sessions = []
for (const session of result.sessions) {
    if (!allowed || await allowed(session.path)) {
        sessions.push(session)
    }
}
return { success: true, sessions, nextCursor: result.nextCursor }

machineId,
'spawn-happy-session',
{ type: 'spawn-in-directory', directory, agent, model, modelReasoningEffort, yolo, sessionType, worktreeName, resumeSessionId, effort, permissionMode }
{ type: 'spawn-in-directory', directory, agent, model, modelReasoningEffort, yolo, sessionType, worktreeName, resumeSessionId, importHistory, effort, permissionMode }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] importHistory is sent here, but the receiving machine RPC handler currently destructures params without importHistory and does not include it in the spawnSession() options. As a result, runner-spawned Codex resumes never pass --hapi-import-history, so the new selector resumes the Codex thread without importing prior transcript messages into HAPI.

Suggested fix:

const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, effort, modelReasoningEffort, yolo, permissionMode, token, sessionType, worktreeName, importHistory } = params || {}

const result = await spawnSession({
    directory,
    sessionId,
    resumeSessionId,
    importHistory,
    machineId,
    approvedNewDirectoryCreation,
    agent,
    model,
    effort,
    modelReasoningEffort,
    yolo,
    permissionMode,
    token,
    sessionType,
    worktreeName
})

}
const createdScanner = await createCodexSessionScanner({
transcriptPath,
replayExistingEvents: session.importHistory,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] runCodex() already imports the transcript before launch when --hapi-import-history is set. In local mode this scanner then replays the existing transcript and sends the same user/agent messages again, producing duplicate history in HAPI for hapi codex resume <id> --hapi-import-history.

Suggested fix:

const createdScanner = await createCodexSessionScanner({
    transcriptPath,
    replayExistingEvents: false,
    onSessionId: (sessionId) => {
        session.onSessionFound(sessionId)
    },
    // existing onEvent...
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant