Repository navigation
refactor: put the tui store on top of the session store #1381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import type { AgentProgress } from '@agent/types'; | ||
| import type { ProgramProgress } from '@programs/types'; | ||
| import { applyAgentProgress } from '@programs'; | ||
| import type { WizardStore } from './store.js'; | ||
|
|
||
| // ── Progress → the store, one write per event ───────────────────────── | ||
|
|
||
| /** | ||
| * A scan's progress on the store: one event, one write, synchronous, in | ||
| * emission order. For a scan a screen runs itself; a `runProgram` run already | ||
| * writes its run state to the session store, so its progress goes through | ||
| * `displayProgress`. | ||
| */ | ||
| export function scanProgress( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] Partly fixed since the last push. The duplicate reducer is gone and scanProgress now runs through applyAgentProgress. A forwarded completion would still mark the run Completed before the run step. Unreachable today. Every detect screen's scan goes through Suggested fix: Treat those kinds as no-ops in |
||
| store: WizardStore, | ||
| ): (event: AgentProgress) => void { | ||
| const show = displayProgress(store); | ||
| return (event) => { | ||
| // The success line is shown and set as the fallback outro without escape codes. | ||
| const clean = | ||
| event.kind === 'lifecycle' && event.phase === 'completed' | ||
| ? { ...event, message: stripAnsi(event.message) } | ||
| : event; | ||
| show({ runId: 'scan', event: clean }); | ||
| applyAgentProgress(store.sessions, clean); | ||
| }; | ||
| } | ||
|
|
||
| /** A `runProgram` run's progress on what only the TUI shows; the run state is already in the session store. */ | ||
| export function displayProgress( | ||
| store: WizardStore, | ||
| ): (progress: ProgramProgress) => void { | ||
| return ({ event }) => { | ||
| switch (event.kind) { | ||
| case 'lifecycle': | ||
| if (event.phase === 'completed') | ||
| store.pushStatus(stripAnsi(event.message)); | ||
| return; | ||
| case 'spinner': | ||
| case 'log': | ||
| case 'stage': | ||
| case 'usage': | ||
| case 'finalCost': | ||
| case 'authError': | ||
| showDisplayEvent(store, event); | ||
| return; | ||
| // Run state runProgram records, or nothing to show. | ||
| case 'status': | ||
| case 'tasks': | ||
| case 'url': | ||
| case 'handoff': | ||
| case 'completion': | ||
| case 'activity': | ||
| case 'binding': | ||
| return; | ||
| default: { | ||
| const unhandled: never = event; | ||
| throw new Error( | ||
| `Unhandled agent progress: ${JSON.stringify(unhandled)}`, | ||
| ); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| type DisplayEvent = Extract< | ||
| AgentProgress, | ||
| { kind: 'spinner' | 'log' | 'stage' | 'usage' | 'finalCost' | 'authError' } | ||
| >; | ||
|
|
||
| /** The events both feeds show the same way: lines on the status feed, the stage, the token HUD, the auth overlay. */ | ||
| function showDisplayEvent(store: WizardStore, event: DisplayEvent): void { | ||
| switch (event.kind) { | ||
| case 'spinner': | ||
| if (event.message) store.pushStatus(event.message); | ||
| return; | ||
| case 'log': | ||
| store.pushStatus(event.message); | ||
| return; | ||
| case 'stage': | ||
| store.setCurrentStage(event.stage); | ||
| return; | ||
| case 'usage': | ||
| store.addTokenUsage(event.delta); | ||
| return; | ||
| case 'finalCost': | ||
| store.setFinalTokenCostUsd(event.usd); | ||
| return; | ||
| case 'authError': | ||
| store.showAuthError(event.detail); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // eslint-disable-next-line no-control-regex | ||
| const ANSI_RE = /\x1b\[[0-9;]*m/g; | ||
| function stripAnsi(s: string): string { | ||
| return s.replace(ANSI_RE, ''); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /** The TUI's login: the browser OAuth flow, shown on the auth screen, as a credentials provider. */ | ||
| import type { | ||
| CredentialsProvider, | ||
| ResolvedProgramCredentials, | ||
| WizardSession, | ||
| } from '@programs/types'; | ||
| import { analytics } from '@utils/analytics'; | ||
| import { logToFile } from '@utils/debug'; | ||
| import type { WizardStore } from '@tui/store'; | ||
| import { getOrAskForProjectData } from './project-data.js'; | ||
|
|
||
| /** Log in once through OAuth for the session's launch values. */ | ||
| export async function oauthLogin( | ||
| session: WizardSession, | ||
| programId: string, | ||
| store: WizardStore, | ||
| ): Promise<ResolvedProgramCredentials> { | ||
| logToFile('[agent-runner] starting OAuth'); | ||
| const data = await getOrAskForProjectData({ | ||
| store, | ||
| signup: session.signup, | ||
| projectId: session.projectId, | ||
| email: session.email, | ||
| region: session.region, | ||
| baseUrl: session.baseUrl, | ||
| localMcp: session.localMcp, | ||
| programId, | ||
| }); | ||
| analytics.wizardCapture('auth complete', { project_id: data.projectId }); | ||
| return { | ||
| posthog: { | ||
| accessToken: data.accessToken, | ||
| refreshToken: data.refreshToken, | ||
| expiresAt: data.expiresAt, | ||
| oauthClientId: data.oauthClientId, | ||
| projectApiKey: data.projectApiKey, | ||
| host: data.host, | ||
| projectId: data.projectId, | ||
| missingScopes: data.missingScopes, | ||
| }, | ||
| project: data.project, | ||
| apiUser: data.user, | ||
| roleAtOrganization: data.roleAtOrganization, | ||
| }; | ||
| } | ||
|
|
||
| /** The OAuth login as `runProgram`'s provider, reading the store's session when it is asked. */ | ||
| export function oauthCredentials(store: WizardStore): CredentialsProvider { | ||
| return { | ||
| resolve: (programId) => oauthLogin(store.session, programId, store), | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note
Automated review. Not written by a human.
[structure/duplication] MEDIUM
scanProgress(withcompleteRunandsetCompletionOutro) re-implements the run-state half ofapplyAgentProgressinsrc/programs/session/session-store.ts:551: lifecycle start/complete with the fallback success outro, status, tasks, url, handoff, and the live-URL-wins completion merge. The two already differ (ANSI stripping, batching), and the next change to the outro fallback or URL precedence will land in one only. BuildscanProgressasapplyAgentProgress(store.sessions, event)plusdisplayProgress(store)({ event }), move ANSI stripping intoapplyAgentProgress, and delete the copies.