dsh-agy 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":["body"],"sources":["../../src/web/management.ts","../../src/web/i18n.ts","../../src/web/page.ts","../../src/web/plugin.ts"],"sourcesContent":["/**\n * agy management API: one method dispatcher shared by the RPC transport.\n *\n * Every method is a pure `(payload) => result` function so the transport stays\n * thin and the handlers stay testable without HTTP. Failures throw; the\n * transport converts a thrown error into the RPC failure envelope.\n *\n * This module owns the OAuth pending-authorization state because `auth.url`\n * issues it and the callback route consumes it — they must share one map, and\n * splitting them would let a verifier and its callback drift apart.\n */\n\nimport { authorizeAntigravity } from '../oauth/authorize.ts'\nimport { exchangeAntigravity } from '../oauth/exchange.ts'\nimport { importManySources, upsertImportedAccount } from '../cli/import.ts'\nimport { generateFingerprint, recordFingerprintVersion } from '../runtime/fingerprint.ts'\nimport { currentAgyVersion } from '../oauth/constants.ts'\nimport { maskProxyUrl } from '../store/accounts.ts'\nimport { accountFetch, isProxyReachable, normalizeProxyUrl, proxyUrlForLogs, withTotalTimeout } from '../proxy.ts'\nimport { AGY_PROVIDER } from '../adapter/models.ts'\nimport type { DiscoveredModelEntry } from '../adapter/models.ts'\nimport { clearExpiredState } from '../runtime/rotation.ts'\nimport { foldWindowBreakdown } from '../stats.ts'\nimport { THINKING_BUDGET_MAX, THINKING_BUDGET_MIN } from '../thinking-budget.ts'\nimport { CLAUDE_BUDGET_MAX, CLAUDE_BUDGET_MIN } from '../thinking-types.ts'\nimport type { UsageCounters, UsageSource } from '../stats.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { AgySessionManager } from '../session.ts'\nimport type { ModelVisibility } from '../model-visibility.ts'\nimport type { UsageStats } from '../stats.ts'\nimport type {\n AccountState,\n AccountUsageView,\n AccountView,\n AgyRpcMethod,\n ModelView,\n RangeBreakdown,\n StatsView,\n ThinkingBudgets,\n} from '../rpc-contract.ts'\n\n/** One pending OAuth authorization, keyed by its raw state. */\ninterface PendingAuth {\n verifier: string\n expiresAt: number\n}\n\nexport interface AgyManagementOptions {\n store: AccountStore\n sessions: AgySessionManager\n stats: UsageStats\n modelVisibility: ModelVisibility\n /**\n * The global reasoning-level budget map (see `thinking-budget.ts`).\n *\n * A plain accessor pair rather than the store instance, so this module depends\n * only on the two operations it exposes and cannot reach the rest of the store.\n */\n thinkingBudget: {\n all: () => ThinkingBudgets\n set: (level: string, value: number | undefined) => ThinkingBudgets\n claude: () => number | undefined\n setClaude: (value: number | undefined) => number | undefined\n tiered: () => number | undefined\n setTiered: (value: number | undefined) => number | undefined\n }\n /**\n * The adapter's *unfiltered* model catalog.\n *\n * A function rather than the adapter itself so this module depends on the one\n * capability it needs. It must not be `llm.listModels()`, which already has\n * the hidden set applied — see the `model.list` handler.\n */\n listAllModels: () => Promise<readonly { id: string; name: string }[]>\n /**\n * Drop the adapter's cached model list.\n *\n * The cache is keyed to no account (see `AgyAdapter.invalidateModelCache`), so\n * the events that switch the account discovery rides must drop it explicitly —\n * `account.activate` is the one such event reachable from this surface.\n */\n invalidateModelCache: () => void\n /**\n * Harness web-server base URL, used to build the loopback OAuth redirect.\n * A function because the port in it is the one the server bound, known only\n * once it is listening — see `webBaseUrl` in `plugin.ts`.\n */\n baseUrl: () => string\n /**\n * Tell DSH the model catalog changed.\n *\n * The hidden-model list lives in agy's own file, so nothing in DSH emits a\n * change when it is written. The client refreshes the picker on\n * `llm/adapters-updated` alone, so without this a toggle would only take\n * effect after a page reload.\n */\n notifyModelsChanged: () => void\n}\n\nexport interface AgyManagement {\n /** Dispatch one method; rejects with a legible message on failure. */\n call(method: string, payload: unknown): Promise<unknown>\n /** Consume an OAuth callback (the one endpoint that stays a real HTTP route). */\n handleCallback(params: URLSearchParams): Promise<{ ok: boolean; email?: string | null; error?: string }>\n}\n\n/** Fail one RPC with a message the UI can show verbatim. */\nfunction fail(message: string): never {\n throw new Error(message)\n}\n\nfunction asIndex(payload: unknown): number {\n const index = Number((payload as { index?: unknown })?.index)\n if (!Number.isInteger(index) || index < 0) fail('invalid index')\n return index\n}\n\n/**\n * Flatten one account's ledger entry for transport.\n *\n * `models` and `lastUsedAt` are deliberately NOT carried: the per-model table\n * that read `models` was removed (the cumulative metric strip states the same\n * figures better), and the `lastUsedAt` ordering rule went with it. Sending\n * fields no client reads is a wire surface with no consumer, which reads as\n * intentional to the next person and invites a stale assumption.\n */\nfunction toAccountUsageView(\n usage: { totals: UsageCounters; sources: Record<UsageSource, number> } | undefined,\n): AccountUsageView | null {\n if (usage === undefined) return null\n return {\n totals: usage.totals,\n sources: usage.sources,\n }\n}\n\nexport function createAgyManagement(options: AgyManagementOptions): AgyManagement {\n const { store, sessions, stats, modelVisibility, listAllModels, invalidateModelCache, baseUrl, notifyModelsChanged } = options\n const thinkingBudget = options.thinkingBudget\n\n /** Authorizations issued by `auth.url`, keyed by raw state. */\n const pendingAuth = new Map<string, PendingAuth>()\n const PENDING_AUTH_TTL_MS = 10 * 60 * 1000\n\n const prunePendingAuth = (now = Date.now()): void => {\n for (const [key, entry] of pendingAuth) {\n if (entry.expiresAt <= now) pendingAuth.delete(key)\n }\n // Bound the map even under abuse: drop the soonest-to-expire entries.\n while (pendingAuth.size > 100) {\n let oldestKey: string | null = null\n let oldestExpiry = Infinity\n for (const [key, entry] of pendingAuth) {\n if (entry.expiresAt < oldestExpiry) {\n oldestExpiry = entry.expiresAt\n oldestKey = key\n }\n }\n if (oldestKey === null) break\n pendingAuth.delete(oldestKey)\n }\n }\n\n const listAccounts = async (): Promise<AccountView[]> => {\n const storage = await store.load()\n const now = Date.now()\n // Expire stale cooldown state BEFORE rendering. `clearExpiredState` otherwise\n // only runs inside `pickAccount`, i.e. only when a request is actually made,\n // so a management read showed a reason for a window that had already lapsed —\n // the badge said \"active\" while the detail row still said \"network-error\",\n // contradicting itself on one screen. Purely in-memory here: the next real\n // mutation persists it, and a read must not write.\n for (const account of storage.accounts) clearExpiredState(account, now)\n const ledger = stats.snapshot()\n const rows: AccountView[] = []\n for (const [index, account] of storage.accounts.entries()) {\n const state: AccountState = account.enabled === false\n ? 'disabled'\n : account.verificationRequired === true\n ? 'verification-required'\n : account.coolingDownUntil !== undefined && account.coolingDownUntil > now\n ? 'cooling'\n : 'active'\n const key = account.email ?? account.id\n rows.push({\n index,\n email: account.email ?? null,\n projectId: account.projectId ?? null,\n active: index === storage.activeIndex && account.enabled !== false,\n state,\n cooldownUntil: account.coolingDownUntil !== undefined && account.coolingDownUntil > now\n ? new Date(account.coolingDownUntil).toISOString()\n : null,\n cooldownReason: account.cooldownReason ?? null,\n // Only while the window is live: `clearExpiredState` above already drops\n // both fields for an expired one, so this cannot outlive its reason.\n cooldownSetAt: account.cooldownSetAt === undefined\n ? null\n : new Date(account.cooldownSetAt).toISOString(),\n /**\n * Appeal link from an upstream verification challenge. Surfaced, never\n * followed automatically: only the account owner can complete it, and the\n * account returns to service on its own once the wall clears.\n */\n verificationUrl: account.verificationUrl ?? null,\n verificationRequired:\n account.verificationRequired === true && account.enabled !== false,\n rateLimits: account.rateLimitResetTimes ?? null,\n fingerprint: account.fingerprint\n ? {\n userAgent: account.fingerprint.userAgent,\n deviceId: account.fingerprint.deviceId,\n createdAt: account.fingerprint.createdAt,\n }\n : null,\n fingerprintHistory: (account.fingerprintHistory ?? []).length,\n proxy: account.proxy ? maskProxyUrl(account.proxy) : null,\n // Read from the cache the session manager already refreshes alongside the\n // per-model quota, so showing the windows costs no request. Null means\n // \"never measured\" and renders as an explicit placeholder rather than a\n // zero, which would read as an exhausted account.\n limits: account.cachedLimits?.groups ?? null,\n limitsUpdatedAt: account.cachedLimits?.updatedAt ?? null,\n usage: key === undefined ? null : toAccountUsageView(ledger.accounts[key]),\n })\n }\n // Per-model quota is deliberately NOT fetched here (nor anywhere else): it\n // costs an upstream round trip, and embedding it in this reply once meant the\n // accounts list could not render until that probe finished — on a slow\n // network the panel showed \"no accounts\" behind a permanent spinner,\n // indistinguishable from data loss. The grouped 5h/weekly windows are the\n // surviving quota display, and they arrive via `account.limits`.\n return rows\n }\n\n /** Fold the ledger into the Usage tab's view. */\n const statsView = (): StatsView => {\n const doc = stats.snapshot()\n const now = Date.now()\n\n // Per-model totals across accounts: sum the same model id from every account.\n const byModel = new Map<string, UsageCounters>()\n for (const usage of Object.values(doc.accounts)) {\n for (const [model, counters] of Object.entries(usage.models)) {\n const current = byModel.get(model)\n if (current === undefined) {\n byModel.set(model, { ...counters })\n } else {\n for (const key of Object.keys(current) as Array<keyof UsageCounters>) {\n current[key] += counters[key]\n }\n }\n }\n }\n const allModels = [...byModel.entries()]\n .map(([model, counters]) => ({ model, counters }))\n .sort((a, b) => b.counters.requests - a.counters.requests)\n\n // Each range carries its OWN breakdown, so the tables follow the range\n // selection instead of showing all-time rows beneath range-scoped headline\n // figures (30 requests above a 164-request row, on one screen). The\n // all-time range reads the account maps directly, which hold every request\n // ever recorded rather than only the retained day window.\n const allAccounts = Object.entries(doc.accounts)\n .map(([account, usage]) => ({ account, counters: usage.totals }))\n .sort((a, b) => b.counters.requests - a.counters.requests)\n const breakdown = (days: number): RangeBreakdown => {\n const { totals, models, accounts } = foldWindowBreakdown(doc, days, now)\n return { counters: totals, models, accounts }\n }\n\n return {\n since: doc.totals.requests > 0 ? doc.since : null,\n all: { counters: doc.totals, models: allModels, accounts: allAccounts },\n today: breakdown(1),\n week: breakdown(7),\n month: breakdown(30),\n }\n }\n\n const methods: Record<AgyRpcMethod, (payload: unknown) => Promise<unknown>> = {\n 'account.list': async () => ({ accounts: await listAccounts() }),\n\n 'account.activate': async (payload) => {\n const index = asIndex(payload)\n await sessions.activateAccount(index)\n // The activated account may see a different catalog, and the model-list\n // cache is keyed to no account — drop it or the picker serves the\n // previous account's list until the TTL expires.\n invalidateModelCache()\n return { ok: true, index }\n },\n\n 'account.delete': async (payload) => {\n const index = asIndex(payload)\n await store.mutate((storage) => {\n if (index >= storage.accounts.length) fail('account not found')\n storage.accounts.splice(index, 1)\n if (storage.activeIndex >= storage.accounts.length) storage.activeIndex = 0\n })\n return { ok: true }\n },\n\n 'account.verify': async (payload) => sessions.verifyAccount(asIndex(payload)),\n\n 'account.health': async (payload) => {\n const raw = (payload as { indices?: unknown })?.indices\n const indices = Array.isArray(raw) ? raw.map((value) => Number(value)) : undefined\n return { results: await sessions.checkAccounts(indices) }\n },\n\n /**\n * Refresh and return the 5h/weekly windows for every enabled account.\n *\n * A SEPARATE call rather than a field on `account.list`, because that reply\n * is deliberately probe-free (see the note above its return): folding an\n * upstream call into it made the whole accounts+usage view wait on the\n * network and show \"no accounts\" behind a spinner. Here the client renders\n * the list from `account.list` immediately and merges these in when they\n * arrive, so a slow probe costs a placeholder, not the page.\n *\n * Server-side this is TTL-gated, so the upstream call happens at most once\n * per window rather than once per view. `force` bypasses that gate for an\n * EXPLICIT refresh — without it the toolbar's Refresh button could not\n * deliver \"the latest numbers now\", which is the only reason to click it\n * while the snapshot is still inside its TTL.\n */\n 'account.limits': async (payload) => {\n const force = (payload as { force?: unknown } | undefined)?.force === true\n const storage = await store.load()\n // Best-effort: a failed refresh leaves whatever was cached, so this reply\n // is always the current best knowledge rather than an error. The OUTCOME\n // is still reported, because \"probed and failed\" and \"was still fresh\"\n // both leave the numbers identical and the user needs to know which.\n const result = await sessions\n .refreshLimits(storage, { force })\n .catch(() => ({ measured: [], failed: [], skipped: 0 }))\n const fresh = await store.load()\n return {\n limits: fresh.accounts.map((account, index) => ({\n index,\n groups: account.cachedLimits?.groups ?? null,\n updatedAt: account.cachedLimits?.updatedAt ?? null,\n })),\n measured: result.measured.length,\n failed: result.failed.length,\n skipped: result.skipped,\n }\n },\n\n 'account.test': async (payload) => {\n const model = (payload as { model?: unknown })?.model\n if (typeof model !== 'string' || model === '') fail('model is required')\n // The account row the user clicked. Optional so a caller that only wants\n // \"test whatever the pool picks\" still works, but a row click always\n // supplies it — otherwise the probe silently ran on the affinity account\n // and reported its result as this account's.\n const rawIndex = (payload as { index?: unknown })?.index\n return sessions.testCall(model, {\n ...(rawIndex === undefined ? {} : { accountIndex: asIndex(payload) }),\n })\n },\n\n 'account.export': async (payload) => sessions.exportBlob(asIndex(payload)),\n\n 'account.exportAll': async () => {\n const storage = await store.load()\n const blobs: Array<{ index: number; blob: string }> = []\n for (let index = 0; index < storage.accounts.length; index++) {\n const result = await sessions.exportBlob(index)\n if (result.blob !== undefined) blobs.push({ index, blob: result.blob })\n }\n return { blobs }\n },\n\n 'account.import': async (payload) => {\n const body = payload as { kind?: unknown; sources?: unknown } | undefined\n const kind = body?.kind === 'blob' ? 'blob' : 'json'\n const sources = Array.isArray(body?.sources) ? body.sources : []\n if (sources.length === 0) fail('nothing to import')\n const result = await importManySources(\n sources.map((source) => ({ source, kind })),\n store,\n { overwriteExisting: true },\n )\n // Per-source failures are part of the result, not a thrown error: a batch\n // where 2 of 5 sources were malformed is a partial success, and the caller\n // must be able to say which ones failed.\n return {\n imported: result.imported,\n replaced: result.replaced,\n errors: result.errors.map((error) => String(error)),\n }\n },\n\n 'account.fingerprint': async (payload) => {\n const index = asIndex(payload)\n const action = (payload as { action?: unknown })?.action === 'regenerate' ? 'regenerate' : 'show'\n return store.mutate(async (storage) => {\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n if (action === 'regenerate') {\n // Same version source as first-use creation: the published value the\n // resolver maintains, never a per-call network probe whose result the\n // pool-randomizing default would silently discard anyway.\n const fresh = generateFingerprint(undefined, currentAgyVersion())\n account.fingerprint = fresh\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, fresh, 'regenerated')\n }\n return {\n action,\n fingerprint: account.fingerprint\n ? {\n userAgent: account.fingerprint.userAgent,\n deviceId: account.fingerprint.deviceId,\n createdAt: account.fingerprint.createdAt,\n }\n : null,\n history: account.fingerprintHistory?.length ?? 0,\n }\n })\n },\n\n 'account.proxy': async (payload) => {\n const index = asIndex(payload)\n const raw = (payload as { proxy?: unknown })?.proxy\n const proxy = typeof raw === 'string' ? raw : ''\n if (proxy.trim() === '') {\n await store.mutate((storage) => {\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n delete account.proxy\n })\n return { ok: true as const, proxy: null, proxyMasked: null, rawLogs: null }\n }\n let normalized: string\n try {\n normalized = normalizeProxyUrl(proxy)\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n await store.mutate((storage) => {\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n account.proxy = normalized\n })\n return {\n ok: true as const,\n proxy: maskProxyUrl(normalized),\n proxyMasked: maskProxyUrl(normalized),\n rawLogs: proxyUrlForLogs(normalized),\n }\n },\n\n 'account.proxyTest': async (payload) => {\n const body = payload as { index?: unknown; proxy?: unknown } | undefined\n const hasExplicit = typeof body?.proxy === 'string' && body.proxy.trim() !== ''\n let target: string\n if (hasExplicit) {\n try {\n target = normalizeProxyUrl(body.proxy as string)\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n } else {\n const index = asIndex(payload)\n const storage = await store.load()\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n if (!account.proxy) fail('no proxy configured for this account')\n target = account.proxy\n }\n // Masked for display only; the raw URL never leaves this function.\n const masked = maskProxyUrl(target) ?? proxyUrlForLogs(target)\n try {\n const ok = await isProxyReachable(target, 2000)\n return ok ? { ok, masked } : { ok, masked, error: `proxy unreachable: ${masked}` }\n } catch (error) {\n return { ok: false, masked, error: error instanceof Error ? error.message : String(error) }\n }\n },\n\n 'auth.url': async () => {\n const authorization = await authorizeAntigravity(`${baseUrl()}/agy/oauth-callback`)\n prunePendingAuth()\n pendingAuth.set(authorization.state, {\n verifier: authorization.verifier,\n expiresAt: Date.now() + PENDING_AUTH_TTL_MS,\n })\n return { url: authorization.url }\n },\n\n 'model.list': async () => {\n const session = await sessions.getSession().catch(() => undefined)\n if (session === undefined) fail('No agy account configured — run `dsh-agy login` first.')\n // The adapter's unfiltered catalog: reading the filtered `listModels()`\n // would hide a disabled model from this very list, leaving no switch to\n // turn it back on.\n const models = await listAllModels()\n const hidden = modelVisibility.disabledFor(AGY_PROVIDER)\n const rows: ModelView[] = models.map((model) => ({\n id: model.id,\n name: model.name,\n disabled: hidden.has(model.id),\n }))\n return { account: session.account.email ?? null, models: rows }\n },\n\n 'model.setDisabled': async (payload) => {\n const body = payload as { modelId?: unknown; disabled?: unknown } | undefined\n const modelId = body?.modelId\n if (typeof modelId !== 'string' || modelId === '') fail('modelId is required')\n const disabled = body?.disabled === true\n modelVisibility.setDisabled(AGY_PROVIDER, modelId, disabled)\n // Push the change to every open client so the picker updates live.\n notifyModelsChanged()\n return { modelId, disabled }\n },\n\n 'thinking.get': async () => ({\n budgets: thinkingBudget.all(),\n min: THINKING_BUDGET_MIN,\n max: THINKING_BUDGET_MAX,\n tieredBudget: thinkingBudget.tiered() ?? null,\n claudeBudget: thinkingBudget.claude() ?? null,\n claudeMin: CLAUDE_BUDGET_MIN,\n claudeMax: CLAUDE_BUDGET_MAX,\n }),\n\n 'thinking.setTiered': async (payload) => {\n const body = payload as { budget?: unknown } | undefined\n const raw = body?.budget\n if (raw !== undefined && raw !== null && typeof raw !== 'number') fail('budget must be a number')\n try {\n const value = thinkingBudget.setTiered(raw === undefined || raw === null ? undefined : raw)\n return { tieredBudget: value ?? null }\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n },\n\n 'thinking.setClaude': async (payload) => {\n const body = payload as { budget?: unknown } | undefined\n const raw = body?.budget\n if (raw !== undefined && raw !== null && typeof raw !== 'number') fail('budget must be a number')\n try {\n const value = thinkingBudget.setClaude(raw === undefined || raw === null ? undefined : raw)\n return { claudeBudget: value ?? null }\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n },\n\n 'thinking.set': async (payload) => {\n const body = payload as { level?: unknown; budget?: unknown } | undefined\n const level = body?.level\n if (typeof level !== 'string' || level === '') fail('level is required')\n // `undefined` and explicit null BOTH clear the entry: the field means \"let\n // upstream choose\", and a JSON null is how a cleared input arrives.\n const raw = body?.budget\n if (raw !== undefined && raw !== null && typeof raw !== 'number') fail('budget must be a number')\n const value = raw === undefined || raw === null ? undefined : raw\n try {\n const budgets = thinkingBudget.set(level, value)\n return { budgets }\n } catch (error) {\n // Surface the range/name violation as a caller-correctable message\n // rather than an unlabelled RPC failure.\n fail(error instanceof Error ? error.message : String(error))\n }\n },\n\n 'stats.get': async () => statsView(),\n }\n\n return {\n async call(method, payload) {\n const handler = methods[method as AgyRpcMethod]\n if (handler === undefined) fail(`unknown method: ${method}`)\n return handler(payload)\n },\n\n async handleCallback(params) {\n const code = params.get('code')\n const state = params.get('state')\n if (!code || !state) return { ok: false, error: 'Missing code or state parameter' }\n const expected = pendingAuth.get(state)\n if (expected === undefined) {\n return { ok: false, error: 'Unknown or expired authorization — please start a new login.' }\n }\n // One-time use: consume the issued state regardless of the exchange result.\n pendingAuth.delete(state)\n const redirectUri = `${baseUrl()}/agy/oauth-callback`\n const result = await exchangeAntigravity(code, state, redirectUri, expected.verifier)\n if (result.type === 'failed') return { ok: false, error: result.error }\n await upsertImportedAccount(store, {\n accessToken: result.access,\n refreshToken: result.refresh.split('|')[0]!,\n tokenType: 'Bearer',\n expiresAt: new Date(result.expires).toISOString(),\n authMethod: 'oauth',\n email: result.email ?? null,\n projectId: result.projectId || null,\n clientId: result.clientId || null,\n }, { overwriteExisting: true })\n return { ok: true, email: result.email ?? null }\n },\n }\n}\n","/**\n * Bilingual UI dictionary for the dsh-agy web dashboard and OAuth callbacks.\n */\n\nexport interface I18nStrings {\n loginSuccessTitle: string\n loginSuccessDesc: string\n loginFailedTitle: string\n windowClosing: string\n}\n\nexport const I18N_DICT: { en: I18nStrings; zh: I18nStrings } = {\n en: {\n loginSuccessTitle: 'Sign-in Successful',\n loginSuccessDesc: 'Your Antigravity account has been authorized and saved.',\n loginFailedTitle: 'Sign-in Failed',\n windowClosing: 'This window will close automatically in a moment...',\n },\n zh: {\n loginSuccessTitle: '授权登录成功',\n loginSuccessDesc: 'Antigravity 账号已成功接入并保存。',\n loginFailedTitle: '授权登录失败',\n windowClosing: '此窗口即将在 2 秒内自动关闭...',\n },\n}\n","/**\n * Browser-facing HTML for the agy OAuth callback.\n *\n * This is the only page agy serves. The account/statistics dashboard that used\n * to live at `/agy` is gone: that surface is now the Settings section rendered\n * by `src/client/` over the `/api/agy` RPC, so there is no longer a second UI to\n * keep in step with it.\n *\n * The callback stays a real page because Google redirects the browser here.\n */\n\nimport { I18N_DICT } from './i18n.ts'\n\n/**\n * Escape text for an HTML text/attribute position.\n *\n * Every interpolation below except the static markup is attacker-influenced or\n * upstream-influenced: `error` is the token endpoint's raw response body, and\n * `email` comes from Google's userinfo. This page is served by the SAME web\n * server, and therefore the same origin, as the DSH GUI — so injected script\n * here would run with the GUI's session and could reach `/api/agy`\n * (`account.exportAll` returns live credential blobs). That chain is why the\n * escaping matters more than the narrow trigger suggests.\n *\n * Text in markup goes through {@link escapeHtml}; text inside the inline\n * `<script>` goes through {@link jsonForInlineScript}. Neither is optional.\n */\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\n/**\n * Serialize a value for interpolation into the inline `<script>`.\n *\n * `JSON.stringify` escapes for a JS string context only, NOT for the HTML script\n * data state: it leaves `<` alone, so an email or error body containing\n * `</script>` terminates the element early and everything after it is parsed as\n * markup. Escaping the three HTML-significant characters as `\\uXXXX` keeps the\n * JSON value identical while making the byte sequence unrepresentable in the\n * source. (The `JSON.stringify` output above is a valid JS string either way,\n * because a `\\u003c` escape and a literal `<` denote the same character.)\n */\nfunction jsonForInlineScript(value: unknown): string {\n return JSON.stringify(value)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n}\n\nexport function renderCallbackHtml(options: { ok: boolean; error?: string; email?: string | null; baseUrl: string }): string {\n const { ok, error, email, baseUrl } = options\n const i18nJson = jsonForInlineScript(I18N_DICT)\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Antigravity Sign-in</title>\n <style>\n :root {\n color-scheme: light dark;\n --dsw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;\n --bg-page: #0f1115;\n --bg-surface: #171a21;\n --border-l2: rgba(255, 255, 255, 0.12);\n --text-primary: #f8fafc;\n --text-secondary: #94a3b8;\n --brand-primary: #5686fe;\n --state-success: #34d399;\n --state-error: #f87171;\n }\n @media (prefers-color-scheme: light) {\n :root {\n --bg-page: #f8fafc;\n --bg-surface: #ffffff;\n --border-l2: rgba(0, 0, 0, 0.12);\n --text-primary: #0f172a;\n --text-secondary: #475569;\n --brand-primary: #4176e6;\n --state-success: #22c55e;\n --state-error: #ef4444;\n }\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: var(--dsw-font-family);\n background-color: var(--bg-page);\n color: var(--text-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n padding: 16px;\n }\n .card {\n background: var(--bg-surface);\n border: 1px solid var(--border-l2);\n border-radius: 12px;\n padding: 24px;\n max-width: 440px;\n width: 100%;\n text-align: center;\n box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);\n }\n .icon-wrap {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 16px;\n font-size: 24px;\n }\n .icon-success { background: rgba(52, 211, 153, 0.15); color: var(--state-success); }\n .icon-error { background: rgba(248, 113, 113, 0.15); color: var(--state-error); }\n h1 { font-size: 18px; font-weight: 600; margin-bottom: 8px; }\n p { font-size: 13.5px; color: var(--text-secondary); margin-bottom: 16px; line-height: 1.5; }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n height: 34px;\n padding: 0 16px;\n border-radius: 17px;\n border: 1px solid var(--border-l2);\n background: transparent;\n color: var(--text-primary);\n cursor: pointer;\n text-decoration: none;\n }\n .btn:hover { background: rgba(128, 128, 128, 0.1); }\n .btn-primary { background: var(--brand-primary); color: #ffffff; border-color: transparent; }\n </style>\n</head>\n<body>\n <div class=\"card\">\n ${ok ? `\n <div class=\"icon-wrap icon-success\">✓</div>\n <h1 id=\"title\">Sign-in Successful</h1>\n <p id=\"desc\">Your Antigravity account ${email ? '<strong>' + escapeHtml(email) + '</strong> ' : ''}has been authorized and saved.</p>\n <p style=\"font-size:12px;opacity:0.8;\" id=\"closing\">This window will close automatically...</p>\n <button class=\"btn\" onclick=\"window.close()\">Close Window</button>\n ` : `\n <div class=\"icon-wrap icon-error\">✕</div>\n <h1 id=\"title\">Sign-in Failed</h1>\n <p id=\"desc\">Error details: ${escapeHtml(error || 'Unknown error')}</p>\n <a class=\"btn btn-primary\" href=\"${escapeHtml(baseUrl)}/\">Return to Settings</a>\n `}\n </div>\n <script>\n const I18N = ${i18nJson};\n const lang = localStorage.getItem('agy_lang') || (navigator.language.startsWith('zh') ? 'zh' : 'en');\n const dict = I18N[lang] || I18N.en;\n\n if (${ok ? 'true' : 'false'}) {\n document.getElementById('title').textContent = dict.loginSuccessTitle;\n // textContent, not innerHTML: an email containing markup must never be\n // parsed as HTML. The string itself is serialized for the script data\n // state by jsonForInlineScript, so it cannot terminate this element.\n document.getElementById('desc').textContent = dict.loginSuccessDesc + (${jsonForInlineScript(email ? ' (' + email + ')' : '')});\n document.getElementById('closing').textContent = dict.windowClosing;\n \n // Notify parent window & close\n try {\n if (window.opener) {\n // Target this page's own origin rather than '*', so the message cannot\n // be delivered to an unrelated opener.\n window.opener.postMessage({ type: 'agy_login_success' }, window.location.origin);\n }\n } catch (e) {}\n setTimeout(() => {\n window.close();\n }, 1800);\n } else {\n document.getElementById('title').textContent = dict.loginFailedTitle;\n }\n </script>\n</body>\n</html>`\n}\n","/**\n * dsh-agy web entry.\n *\n * Two things are registered, and they deliberately use different transports:\n *\n * 1. **Management RPC at `/api/agy`** (`connection.fetch.register`). The inline\n * Settings UI calls methods over DSH's own RPC channel. This replaces the\n * previous bare `ctx.webServer.register` route table, which had no\n * authentication of its own: `/api/*` sits behind the host's browser-trust\n * fence and BrowserAuth, so the management surface is no longer reachable by\n * anything that can merely open a socket to a loopback port.\n *\n * 2. **The OAuth callback at `/agy/oauth-callback`** stays a real HTTP route.\n * Google redirects a browser to it with a GET, which the management RPC\n * channel (POST-only) cannot carry.\n *\n * BOTH `webServer` and `connection` are deliberately reached lazily rather than\n * statically injected. Each exists only in a Web composition, and a statically\n * injected service that never appears leaves this entry permanently pending —\n * and the loader treats a pending entry as a FAILED PROFILE, not a skipped one:\n *\n * dsh: plugin tree failed to load: dsh: 1 entry did not activate\n * dsh-agy/web: pending (waiting for service: webServer)\n *\n * Measured on a real TUI profile, where no provider of `webServer` is mounted.\n * `ctx.inject([...])` keeps this entry active and inert instead, so a TUI or\n * headless profile boots with no management surface rather than not booting.\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n// Type-only: merges the `llm/adapters-updated` event into the Cordis Events map.\nimport type {} from '@deepseek-ai/dsh-llm/types'\nimport { createAgyRuntime } from '../plugin-common.ts'\nimport { isAgyDisabled } from '../runtime/risk.ts'\nimport { createAgyManagement } from './management.ts'\nimport { renderCallbackHtml } from './page.ts'\n\nexport const name = 'dsh-agy-web'\n\n/** The one service every composition provides; the rest are resolved lazily. */\nexport const inject = ['llm']\n\n/** The slice of the host's web-server service this entry uses. */\ninterface WebServerLike {\n register(route: {\n kind: 'exact'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void\n }): () => void\n host?: string\n /** The port actually listened on: the OS-assigned one when `--port 0`. */\n readonly port?: number\n}\n\n/**\n * The loopback base URL the OAuth redirect and the callback page point at.\n *\n * The port is the one the server BOUND, not the one it was asked for.\n * `webStartup.port` is the `--port` flag verbatim, and `--port 0` — which asks\n * the OS for any free port — would send Google's redirect to\n * `http://127.0.0.1:0/agy/oauth-callback`, which nothing answers.\n * The requested port and DSH's own 3080 default only stand in while the\n * server has not reported one.\n * @param host - loopback host the URL names.\n * @param webServer - the host web server (source of the bound port).\n * @param requestedPort - `webStartup.port`, the `--port` flag if one was given.\n * @returns `http://<host>:<port>`, no trailing slash.\n */\nexport function webBaseUrl(host: string, webServer: Pick<WebServerLike, 'port'>, requestedPort?: number): string {\n return `http://${host}:${webServer.port || requestedPort || 3080}`\n}\n\nexport function apply(ctx: Context): void {\n if (isAgyDisabled()) {\n ctx.logger.warn('[dsh-agy] disabled by DSH_AGY_DISABLE=1 — skipping web registration')\n return\n }\n // Lazily, like `connection` below: see the module docblock for why a static\n // `webServer` is a profile-breaking bug rather than a nicety.\n ctx.inject(['webServer'], (webCtx) => {\n const webServer = webCtx.get('webServer') as WebServerLike | undefined\n if (!webServer) return\n webCtx.effect(() => registerAgyWeb(webCtx, webServer))\n })\n}\n\n/**\n * Register the OAuth callback route, and the management RPC once `connection`\n * appears.\n * @param ctx - the web-server-bearing context (owns both registrations).\n * @param webServer - the host web server.\n * @returns disposer for every registration this function made.\n */\nasync function registerAgyWeb(ctx: Context, webServer: WebServerLike): Promise<() => void> {\n // Host comes from the webStartup provider (CLI args), falling back to DSH's\n // own web-app default (loopback), never a user override. The port does not:\n // see `webBaseUrl`.\n const webStartup = ctx.get('webStartup') as { host?: string; port?: number } | undefined\n const host = webStartup?.host ?? '127.0.0.1'\n\n // The OAuth callback manages credentials with no authentication of its own,\n // so it must never be reachable from the network. When the web server binds\n // a non-loopback interface, refuse to register it (the loopback-only OAuth\n // redirect would be unusable there anyway).\n const bindHost = webServer.host ?? host\n if (!['127.0.0.1', 'localhost', '::1'].includes(bindHost)) {\n ctx.logger.warn(\n '[dsh-agy] web server bound to \"' + bindHost + '\" (non-loopback): not registering the agy routes ' +\n '(they manage account credentials and must stay loopback-only). Bind the web server to 127.0.0.1 to enable them.',\n )\n return () => {}\n }\n\n const { store, sessions, adapter, stats, modelVisibility, thinkingBudget } = await createAgyRuntime(ctx)\n // Read per use rather than once here: the bound port is only known after the\n // server's listen callback has run.\n const baseUrl = (): string => webBaseUrl(host, webServer, webStartup?.port)\n const management = createAgyManagement({\n store,\n sessions,\n stats,\n modelVisibility,\n // Expose only the two operations the RPC needs, not the whole store.\n thinkingBudget: {\n all: () => thinkingBudget.all(),\n set: (level, value) => thinkingBudget.setBudget(level, value),\n claude: () => thinkingBudget.claudeBudget(),\n setClaude: (value) => thinkingBudget.setClaudeBudget(value).claudeBudget,\n tiered: () => thinkingBudget.tieredBudget(),\n setTiered: (value) => thinkingBudget.setTieredBudget(value).tieredBudget,\n },\n // The adapter's *unfiltered* catalog, so a hidden model still appears in\n // the settings list alongside the switch that un-hides it.\n listAllModels: () => adapter.listAllModels(),\n // Activating an account can switch the account model discovery rides; the\n // cache carries no account key, so the RPC drops it (see\n // `AgyAdapter.invalidateModelCache`).\n invalidateModelCache: () => adapter.invalidateModelCache(),\n baseUrl,\n // DSH refreshes the model picker on `llm/adapters-updated`; the hidden\n // list lives in agy's own file, so nothing else would announce the change\n // and the toggle would appear to do nothing until a page reload.\n // (`llm/adapters-updated` is declared `@mode emit` for exactly this kind\n // of registry notification.)\n notifyModelsChanged: () => { ctx.emit('llm/adapters-updated') },\n })\n const disposers: Array<() => void> = []\n\n // The one endpoint that cannot ride the RPC channel: Google sends the\n // browser here with a GET redirect.\n disposers.push(webServer.register({\n kind: 'exact',\n path: '/agy/oauth-callback',\n handler: async (req: IncomingMessage, res: ServerResponse) => {\n const base = baseUrl()\n const url = new URL(req.url ?? '/', base)\n const result = await management.handleCallback(url.searchParams).catch((error: unknown) => ({\n ok: false as const,\n error: error instanceof Error ? error.message : String(error),\n }))\n res.writeHead(result.ok ? 200 : 400, { 'content-type': 'text/html; charset=utf-8' })\n res.end(result.ok\n ? renderCallbackHtml({ ok: true, email: result.email ?? null, baseUrl: base })\n : renderCallbackHtml({ ok: false, error: result.error ?? 'Unknown error', baseUrl: base }))\n },\n }))\n\n // Lazy injection: see the module docblock for why this is not static.\n ctx.inject(['connection'], (connectionCtx) => {\n const connection = connectionCtx.get('connection') as\n | {\n fetch: {\n register(route: {\n path: string\n methods: string[]\n requestBody: 'buffered' | 'streaming'\n fetch: (request: Request) => Promise<Response>\n }): () => void\n }\n }\n | undefined\n if (!connection || typeof connection.fetch?.register !== 'function') {\n connectionCtx.logger.warn('[dsh-agy] connection.fetch unavailable — management RPC not registered')\n return\n }\n connectionCtx.effect(() => connection.fetch.register({\n path: '/api/agy',\n methods: ['POST'],\n requestBody: 'buffered',\n async fetch(request: Request): Promise<Response> {\n if (request.method !== 'POST') return new Response('method not allowed', { status: 405 })\n const contentType = request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n return new Response('content type must be application/json', { status: 415 })\n }\n let message: Record<string, unknown>\n try {\n message = await request.json() as Record<string, unknown>\n } catch {\n return new Response('body is not JSON', { status: 400 })\n }\n const rpcId = typeof message.rpcId === 'string' ? message.rpcId : 'invalid-request'\n const call = message.payload as { method?: unknown; payload?: unknown } | undefined\n if (\n message.type !== 'client-request'\n || typeof message.rpcId !== 'string'\n || typeof call?.method !== 'string'\n ) {\n return reply(rpcId, {\n ok: false,\n error: { code: 'agy/bad-request', message: 'Invalid agy management request.' },\n })\n }\n try {\n const value = await management.call(call.method, call.payload)\n return reply(rpcId, { ok: true, value })\n } catch (error) {\n const text = error instanceof Error ? error.message : String(error)\n connectionCtx.logger.warn(`[dsh-agy] ${call.method} failed: ${text}`)\n // A structured failure, not a bare 500: the client's unwrap needs a\n // parseable envelope or a failed call looks like \"nothing happened\".\n return reply(rpcId, { ok: false, error: { code: 'agy/handler-failed', message: text } })\n }\n },\n }), 'dsh-agy: /api/agy management RPC')\n })\n\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/**\n * Wrap one result in the Connection RPC response envelope.\n *\n * A failure MUST carry `error.details` as an object: the client's envelope\n * parser rejects a failure without it as \"invalid server-response\", which hides\n * the real message behind a transport-sounding error.\n */\nfunction reply(rpcId: string, result: unknown): Response {\n const normalized = isFailure(result)\n ? { ...result, error: { details: {}, ...result.error } }\n : result\n return Response.json({ type: 'server-response', rpcId, result: normalized })\n}\n\n/** Whether one result is an RPC failure envelope. */\nfunction isFailure(value: unknown): value is {\n ok: false\n error: { code?: string, message?: string, details?: unknown }\n} {\n return typeof value === 'object' && value !== null\n && (value as { ok?: unknown }).ok === false\n && typeof (value as { error?: unknown }).error === 'object'\n && (value as { error?: unknown }).error !== null\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.mjs","names":["body"],"sources":["../../src/web/management.ts","../../src/web/i18n.ts","../../src/web/page.ts","../../src/web/plugin.ts"],"sourcesContent":["/**\n * agy management API: one method dispatcher shared by the RPC transport.\n *\n * Every method is a pure `(payload) => result` function so the transport stays\n * thin and the handlers stay testable without HTTP. Failures throw; the\n * transport converts a thrown error into the RPC failure envelope.\n *\n * This module owns the OAuth pending-authorization state because `auth.url`\n * issues it and the callback route consumes it — they must share one map, and\n * splitting them would let a verifier and its callback drift apart.\n */\n\nimport { authorizeAntigravity } from '../oauth/authorize.ts'\nimport { exchangeAntigravity } from '../oauth/exchange.ts'\nimport { importManySources, upsertImportedAccount } from '../cli/import.ts'\nimport { generateFingerprint, recordFingerprintVersion } from '../runtime/fingerprint.ts'\nimport { currentAgyVersion } from '../oauth/constants.ts'\nimport { maskProxyUrl } from '../store/accounts.ts'\nimport { accountFetch, isProxyReachable, normalizeProxyUrl, proxyUrlForLogs, withTotalTimeout } from '../proxy.ts'\nimport { AGY_PROVIDER } from '../adapter/models.ts'\nimport type { DiscoveredModelEntry } from '../adapter/models.ts'\nimport { clearExpiredState } from '../runtime/rotation.ts'\nimport { foldWindowBreakdown } from '../stats.ts'\nimport { THINKING_BUDGET_MAX, THINKING_BUDGET_MIN } from '../thinking-budget.ts'\nimport { CLAUDE_BUDGET_MAX, CLAUDE_BUDGET_MIN } from '../thinking-types.ts'\nimport { dayKey } from '../stats.ts'\nimport type { RecentActivity, UsageCounters, UsageSource } from '../stats.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { AgySessionManager } from '../session.ts'\nimport type { ModelVisibility } from '../model-visibility.ts'\nimport type { UsageStats } from '../stats.ts'\nimport type {\n AccountState,\n AccountUsageView,\n AccountView,\n AgyRpcMethod,\n ModelView,\n RangeBreakdown,\n StatsView,\n ThinkingBudgets,\n} from '../rpc-contract.ts'\n\n/** One pending OAuth authorization, keyed by its raw state. */\ninterface PendingAuth {\n verifier: string\n expiresAt: number\n}\n\nexport interface AgyManagementOptions {\n store: AccountStore\n sessions: AgySessionManager\n stats: UsageStats\n /**\n * The persisted recent-activity ring (see `recent-store.ts`).\n *\n * A plain accessor rather than the store instance, matching `thinkingBudget`:\n * this module needs exactly one operation and cannot reach the rest.\n */\n recentRequests: () => RecentActivity[]\n modelVisibility: ModelVisibility\n /**\n * The global reasoning-level budget map (see `thinking-budget.ts`).\n *\n * A plain accessor pair rather than the store instance, so this module depends\n * only on the two operations it exposes and cannot reach the rest of the store.\n */\n thinkingBudget: {\n all: () => ThinkingBudgets\n set: (level: string, value: number | undefined) => ThinkingBudgets\n claude: () => number | undefined\n setClaude: (value: number | undefined) => number | undefined\n tiered: () => number | undefined\n setTiered: (value: number | undefined) => number | undefined\n }\n /**\n * The adapter's *unfiltered* model catalog.\n *\n * A function rather than the adapter itself so this module depends on the one\n * capability it needs. It must not be `llm.listModels()`, which already has\n * the hidden set applied — see the `model.list` handler.\n */\n listAllModels: () => Promise<readonly { id: string; name: string }[]>\n /**\n * Drop the adapter's cached model list.\n *\n * The cache is keyed to no account (see `AgyAdapter.invalidateModelCache`), so\n * the events that switch the account discovery rides must drop it explicitly —\n * `account.activate` is the one such event reachable from this surface.\n */\n invalidateModelCache: () => void\n /**\n * Harness web-server base URL, used to build the loopback OAuth redirect.\n * A function because the port in it is the one the server bound, known only\n * once it is listening — see `webBaseUrl` in `plugin.ts`.\n */\n baseUrl: () => string\n /**\n * Tell DSH the model catalog changed.\n *\n * The hidden-model list lives in agy's own file, so nothing in DSH emits a\n * change when it is written. The client refreshes the picker on\n * `llm/adapters-updated` alone, so without this a toggle would only take\n * effect after a page reload.\n */\n notifyModelsChanged: () => void\n}\n\nexport interface AgyManagement {\n /** Dispatch one method; rejects with a legible message on failure. */\n call(method: string, payload: unknown): Promise<unknown>\n /** Consume an OAuth callback (the one endpoint that stays a real HTTP route). */\n handleCallback(params: URLSearchParams): Promise<{ ok: boolean; email?: string | null; error?: string }>\n}\n\n/** Fail one RPC with a message the UI can show verbatim. */\nfunction fail(message: string): never {\n throw new Error(message)\n}\n\nfunction asIndex(payload: unknown): number {\n const index = Number((payload as { index?: unknown })?.index)\n if (!Number.isInteger(index) || index < 0) fail('invalid index')\n return index\n}\n\n/**\n * Flatten one account's ledger entry for transport.\n *\n * `models` is deliberately NOT carried: the per-model table that read it was\n * removed (the cumulative metric strip states the same figures better), and\n * sending fields no client reads is a wire surface with no consumer, which\n * reads as intentional to the next person and invites a stale assumption.\n * `lastUsedAt` DOES travel — the account row's activity fragment reads it.\n */\nfunction toAccountUsageView(\n usage: { totals: UsageCounters; sources: Record<UsageSource, number>; lastUsedAt?: number } | undefined,\n): AccountUsageView | null {\n if (usage === undefined) return null\n return {\n totals: usage.totals,\n sources: usage.sources,\n lastUsedAt: typeof usage.lastUsedAt === 'number' && Number.isFinite(usage.lastUsedAt)\n ? usage.lastUsedAt\n : null,\n }\n}\n\nexport function createAgyManagement(options: AgyManagementOptions): AgyManagement {\n const { store, sessions, stats, recentRequests, modelVisibility, listAllModels, invalidateModelCache, baseUrl, notifyModelsChanged } = options\n const thinkingBudget = options.thinkingBudget\n\n /** Authorizations issued by `auth.url`, keyed by raw state. */\n const pendingAuth = new Map<string, PendingAuth>()\n const PENDING_AUTH_TTL_MS = 10 * 60 * 1000\n\n const prunePendingAuth = (now = Date.now()): void => {\n for (const [key, entry] of pendingAuth) {\n if (entry.expiresAt <= now) pendingAuth.delete(key)\n }\n // Bound the map even under abuse: drop the soonest-to-expire entries.\n while (pendingAuth.size > 100) {\n let oldestKey: string | null = null\n let oldestExpiry = Infinity\n for (const [key, entry] of pendingAuth) {\n if (entry.expiresAt < oldestExpiry) {\n oldestExpiry = entry.expiresAt\n oldestKey = key\n }\n }\n if (oldestKey === null) break\n pendingAuth.delete(oldestKey)\n }\n }\n\n const listAccounts = async (): Promise<AccountView[]> => {\n const storage = await store.load()\n const now = Date.now()\n // Expire stale cooldown state BEFORE rendering. `clearExpiredState` otherwise\n // only runs inside `pickAccount`, i.e. only when a request is actually made,\n // so a management read showed a reason for a window that had already lapsed —\n // the badge said \"active\" while the detail row still said \"network-error\",\n // contradicting itself on one screen. Purely in-memory here: the next real\n // mutation persists it, and a read must not write.\n for (const account of storage.accounts) clearExpiredState(account, now)\n const ledger = stats.snapshot()\n const rows: AccountView[] = []\n for (const [index, account] of storage.accounts.entries()) {\n const state: AccountState = account.enabled === false\n ? 'disabled'\n : account.verificationRequired === true\n ? 'verification-required'\n : account.coolingDownUntil !== undefined && account.coolingDownUntil > now\n ? 'cooling'\n : 'active'\n const key = account.email ?? account.id\n rows.push({\n index,\n email: account.email ?? null,\n projectId: account.projectId ?? null,\n active: index === storage.activeIndex && account.enabled !== false,\n state,\n // Disabled is terminal until a human acts (verify / re-import), so the\n // time is the actionable half: \"minutes ago\" makes an immediate verify\n // worth trying, \"weeks ago\" says re-login. Both disable writers persist\n // `verificationRequiredAt` in the same mutation as `enabled = false`,\n // and the parked-but-enabled path must NOT read as a disable time.\n disabledAt: account.enabled === false && account.verificationRequiredAt !== undefined\n ? new Date(account.verificationRequiredAt).toISOString()\n : null,\n cooldownUntil: account.coolingDownUntil !== undefined && account.coolingDownUntil > now\n ? new Date(account.coolingDownUntil).toISOString()\n : null,\n cooldownReason: account.cooldownReason ?? null,\n // Only while the window is live: `clearExpiredState` above already drops\n // both fields for an expired one, so this cannot outlive its reason.\n cooldownSetAt: account.cooldownSetAt === undefined\n ? null\n : new Date(account.cooldownSetAt).toISOString(),\n /**\n * Appeal link from an upstream verification challenge. Surfaced, never\n * followed automatically: only the account owner can complete it, and the\n * account returns to service on its own once the wall clears.\n */\n verificationUrl: account.verificationUrl ?? null,\n verificationRequired:\n account.verificationRequired === true && account.enabled !== false,\n rateLimits: account.rateLimitResetTimes ?? null,\n fingerprint: account.fingerprint\n ? {\n userAgent: account.fingerprint.userAgent,\n deviceId: account.fingerprint.deviceId,\n createdAt: account.fingerprint.createdAt,\n }\n : null,\n fingerprintHistory: (account.fingerprintHistory ?? []).length,\n proxy: account.proxy ? maskProxyUrl(account.proxy) : null,\n // Read from the cache the session manager already refreshes alongside the\n // per-model quota, so showing the windows costs no request. Null means\n // \"never measured\" and renders as an explicit placeholder rather than a\n // zero, which would read as an exhausted account.\n limits: account.cachedLimits?.groups ?? null,\n limitsUpdatedAt: account.cachedLimits?.updatedAt ?? null,\n // The burn rate arrives via `account.limits` (it exists only after the\n // sampling path has run twice); `account.list` stays probe-free.\n limitBurn: null,\n usage: key === undefined ? null : toAccountUsageView(ledger.accounts[key]),\n })\n }\n // Per-model quota is deliberately NOT fetched here (nor anywhere else): it\n // costs an upstream round trip, and embedding it in this reply once meant the\n // accounts list could not render until that probe finished — on a slow\n // network the panel showed \"no accounts\" behind a permanent spinner,\n // indistinguishable from data loss. The grouped 5h/weekly windows are the\n // surviving quota display, and they arrive via `account.limits`.\n return rows\n }\n\n /** Fold the ledger into the Usage tab's view. */\n const statsView = (): StatsView => {\n const doc = stats.snapshot()\n const now = Date.now()\n\n // Per-model totals across accounts: sum the same model id from every account.\n const byModel = new Map<string, UsageCounters>()\n for (const usage of Object.values(doc.accounts)) {\n for (const [model, counters] of Object.entries(usage.models)) {\n const current = byModel.get(model)\n if (current === undefined) {\n byModel.set(model, { ...counters })\n } else {\n for (const key of Object.keys(current) as Array<keyof UsageCounters>) {\n current[key] += counters[key]\n }\n }\n }\n }\n const allModels = [...byModel.entries()]\n .map(([model, counters]) => ({ model, counters }))\n .sort((a, b) => b.counters.requests - a.counters.requests)\n\n // Each range carries its OWN breakdown, so the tables follow the range\n // selection instead of showing all-time rows beneath range-scoped headline\n // figures (30 requests above a 164-request row, on one screen). The\n // all-time range reads the account maps directly, which hold every request\n // ever recorded rather than only the retained day window.\n const allAccounts = Object.entries(doc.accounts)\n .map(([account, usage]) => ({ account, counters: usage.totals }))\n .sort((a, b) => b.counters.requests - a.counters.requests)\n const breakdown = (days: number): RangeBreakdown => {\n const { totals, models, accounts } = foldWindowBreakdown(doc, days, now)\n return { counters: totals, models, accounts }\n }\n\n // Last 7 LOCAL calendar days, zeros filled: the trend table's job is to\n // show shape over time, and a gap day reads as data loss rather than idle.\n // Same local keying as the ledger's own buckets (dayKey).\n const daySeries = Array.from({ length: 7 }, (_, i) => {\n const key = dayKey(now - (6 - i) * 86_400_000)\n const bucket = doc.days[key]\n return {\n day: key,\n requests: bucket?.totals.requests ?? 0,\n failed: bucket?.totals.failed ?? 0,\n rateLimited: bucket?.totals.rateLimited ?? 0,\n rotations: bucket?.totals.rotations ?? 0,\n }\n })\n return {\n since: doc.totals.requests > 0 ? doc.since : null,\n all: { counters: doc.totals, models: allModels, accounts: allAccounts },\n today: breakdown(1),\n week: breakdown(7),\n month: breakdown(30),\n days: daySeries,\n }\n }\n\n const methods: Record<AgyRpcMethod, (payload: unknown) => Promise<unknown>> = {\n 'account.list': async () => ({ accounts: await listAccounts() }),\n\n 'pool.status': async () => ({ busy: await sessions.inFlightAccounts() }),\n\n 'pool.recent': async () => ({ recent: recentRequests() }),\n\n 'account.activate': async (payload) => {\n const index = asIndex(payload)\n await sessions.activateAccount(index)\n // The activated account may see a different catalog, and the model-list\n // cache is keyed to no account — drop it or the picker serves the\n // previous account's list until the TTL expires.\n invalidateModelCache()\n return { ok: true, index }\n },\n\n 'account.delete': async (payload) => {\n const index = asIndex(payload)\n await store.mutate((storage) => {\n if (index >= storage.accounts.length) fail('account not found')\n storage.accounts.splice(index, 1)\n if (storage.activeIndex >= storage.accounts.length) storage.activeIndex = 0\n })\n return { ok: true }\n },\n\n 'account.verify': async (payload) => sessions.verifyAccount(asIndex(payload)),\n\n 'account.health': async (payload) => {\n const raw = (payload as { indices?: unknown })?.indices\n const indices = Array.isArray(raw) ? raw.map((value) => Number(value)) : undefined\n return { results: await sessions.checkAccounts(indices) }\n },\n\n /**\n * Refresh and return the 5h/weekly windows for every enabled account.\n *\n * A SEPARATE call rather than a field on `account.list`, because that reply\n * is deliberately probe-free (see the note above its return): folding an\n * upstream call into it made the whole accounts+usage view wait on the\n * network and show \"no accounts\" behind a spinner. Here the client renders\n * the list from `account.list` immediately and merges these in when they\n * arrive, so a slow probe costs a placeholder, not the page.\n *\n * Server-side this is TTL-gated, so the upstream call happens at most once\n * per window rather than once per view. `force` bypasses that gate for an\n * EXPLICIT refresh — without it the toolbar's Refresh button could not\n * deliver \"the latest numbers now\", which is the only reason to click it\n * while the snapshot is still inside its TTL.\n */\n 'account.limits': async (payload) => {\n const force = (payload as { force?: unknown } | undefined)?.force === true\n const storage = await store.load()\n // Best-effort: a failed refresh leaves whatever was cached, so this reply\n // is always the current best knowledge rather than an error. The OUTCOME\n // is still reported, because \"probed and failed\" and \"was still fresh\"\n // both leave the numbers identical and the user needs to know which.\n const result = await sessions\n .refreshLimits(storage, { force })\n .catch(() => ({ measured: [], failed: [], skipped: 0 }))\n const fresh = await store.load()\n // Burn rates ride the same reply (the sampling path runs inside\n // refreshLimits); a join failure degrades to null rates, not an error.\n const burn = await sessions.limitBurnRates().catch(() => [])\n const burnByIndex = new Map(burn.map((entry) => [entry.index, entry.perHour]))\n return {\n limits: fresh.accounts.map((account, index) => ({\n index,\n groups: account.cachedLimits?.groups ?? null,\n updatedAt: account.cachedLimits?.updatedAt ?? null,\n burn: burnByIndex.get(index) ?? null,\n })),\n measured: result.measured.length,\n failed: result.failed.length,\n skipped: result.skipped,\n }\n },\n\n 'account.test': async (payload) => {\n const model = (payload as { model?: unknown })?.model\n if (typeof model !== 'string' || model === '') fail('model is required')\n // The account row the user clicked. Optional so a caller that only wants\n // \"test whatever the pool picks\" still works, but a row click always\n // supplies it — otherwise the probe silently ran on the affinity account\n // and reported its result as this account's.\n const rawIndex = (payload as { index?: unknown })?.index\n return sessions.testCall(model, {\n ...(rawIndex === undefined ? {} : { accountIndex: asIndex(payload) }),\n })\n },\n\n 'account.export': async (payload) => sessions.exportBlob(asIndex(payload)),\n\n 'account.exportAll': async () => {\n const storage = await store.load()\n const blobs: Array<{ index: number; blob: string }> = []\n for (let index = 0; index < storage.accounts.length; index++) {\n const result = await sessions.exportBlob(index)\n if (result.blob !== undefined) blobs.push({ index, blob: result.blob })\n }\n return { blobs }\n },\n\n 'account.import': async (payload) => {\n const body = payload as { kind?: unknown; sources?: unknown } | undefined\n const kind = body?.kind === 'blob' ? 'blob' : 'json'\n const sources = Array.isArray(body?.sources) ? body.sources : []\n if (sources.length === 0) fail('nothing to import')\n const result = await importManySources(\n sources.map((source) => ({ source, kind })),\n store,\n { overwriteExisting: true },\n )\n // Per-source failures are part of the result, not a thrown error: a batch\n // where 2 of 5 sources were malformed is a partial success, and the caller\n // must be able to say which ones failed.\n return {\n imported: result.imported,\n replaced: result.replaced,\n errors: result.errors.map((error) => String(error)),\n }\n },\n\n 'account.fingerprint': async (payload) => {\n const index = asIndex(payload)\n const action = (payload as { action?: unknown })?.action === 'regenerate' ? 'regenerate' : 'show'\n return store.mutate(async (storage) => {\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n if (action === 'regenerate') {\n // Same version source as first-use creation: the published value the\n // resolver maintains, never a per-call network probe whose result the\n // pool-randomizing default would silently discard anyway.\n const fresh = generateFingerprint(undefined, currentAgyVersion())\n account.fingerprint = fresh\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, fresh, 'regenerated')\n }\n return {\n action,\n fingerprint: account.fingerprint\n ? {\n userAgent: account.fingerprint.userAgent,\n deviceId: account.fingerprint.deviceId,\n createdAt: account.fingerprint.createdAt,\n }\n : null,\n history: account.fingerprintHistory?.length ?? 0,\n }\n })\n },\n\n 'account.proxy': async (payload) => {\n const index = asIndex(payload)\n const raw = (payload as { proxy?: unknown })?.proxy\n const proxy = typeof raw === 'string' ? raw : ''\n if (proxy.trim() === '') {\n await store.mutate((storage) => {\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n delete account.proxy\n })\n return { ok: true as const, proxy: null, proxyMasked: null, rawLogs: null }\n }\n let normalized: string\n try {\n normalized = normalizeProxyUrl(proxy)\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n await store.mutate((storage) => {\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n account.proxy = normalized\n })\n return {\n ok: true as const,\n proxy: maskProxyUrl(normalized),\n proxyMasked: maskProxyUrl(normalized),\n rawLogs: proxyUrlForLogs(normalized),\n }\n },\n\n 'account.proxyTest': async (payload) => {\n const body = payload as { index?: unknown; proxy?: unknown } | undefined\n const hasExplicit = typeof body?.proxy === 'string' && body.proxy.trim() !== ''\n let target: string\n if (hasExplicit) {\n try {\n target = normalizeProxyUrl(body.proxy as string)\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n } else {\n const index = asIndex(payload)\n const storage = await store.load()\n const account = storage.accounts[index]\n if (!account) fail('account not found')\n if (!account.proxy) fail('no proxy configured for this account')\n target = account.proxy\n }\n // Masked for display only; the raw URL never leaves this function.\n const masked = maskProxyUrl(target) ?? proxyUrlForLogs(target)\n try {\n const ok = await isProxyReachable(target, 2000)\n return ok ? { ok, masked } : { ok, masked, error: `proxy unreachable: ${masked}` }\n } catch (error) {\n return { ok: false, masked, error: error instanceof Error ? error.message : String(error) }\n }\n },\n\n 'auth.url': async () => {\n const authorization = await authorizeAntigravity(`${baseUrl()}/agy/oauth-callback`)\n prunePendingAuth()\n pendingAuth.set(authorization.state, {\n verifier: authorization.verifier,\n expiresAt: Date.now() + PENDING_AUTH_TTL_MS,\n })\n return { url: authorization.url }\n },\n\n 'model.list': async () => {\n const session = await sessions.getSession().catch(() => undefined)\n if (session === undefined) fail('No agy account configured — run `dsh-agy login` first.')\n // The adapter's unfiltered catalog: reading the filtered `listModels()`\n // would hide a disabled model from this very list, leaving no switch to\n // turn it back on.\n const models = await listAllModels()\n const hidden = modelVisibility.disabledFor(AGY_PROVIDER)\n const rows: ModelView[] = models.map((model) => ({\n id: model.id,\n name: model.name,\n disabled: hidden.has(model.id),\n }))\n return { account: session.account.email ?? null, models: rows }\n },\n\n 'model.setDisabled': async (payload) => {\n const body = payload as { modelId?: unknown; disabled?: unknown } | undefined\n const modelId = body?.modelId\n if (typeof modelId !== 'string' || modelId === '') fail('modelId is required')\n const disabled = body?.disabled === true\n modelVisibility.setDisabled(AGY_PROVIDER, modelId, disabled)\n // Push the change to every open client so the picker updates live.\n notifyModelsChanged()\n return { modelId, disabled }\n },\n\n 'thinking.get': async () => ({\n budgets: thinkingBudget.all(),\n min: THINKING_BUDGET_MIN,\n max: THINKING_BUDGET_MAX,\n tieredBudget: thinkingBudget.tiered() ?? null,\n claudeBudget: thinkingBudget.claude() ?? null,\n claudeMin: CLAUDE_BUDGET_MIN,\n claudeMax: CLAUDE_BUDGET_MAX,\n }),\n\n 'thinking.setTiered': async (payload) => {\n const body = payload as { budget?: unknown } | undefined\n const raw = body?.budget\n if (raw !== undefined && raw !== null && typeof raw !== 'number') fail('budget must be a number')\n try {\n const value = thinkingBudget.setTiered(raw === undefined || raw === null ? undefined : raw)\n return { tieredBudget: value ?? null }\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n },\n\n 'thinking.setClaude': async (payload) => {\n const body = payload as { budget?: unknown } | undefined\n const raw = body?.budget\n if (raw !== undefined && raw !== null && typeof raw !== 'number') fail('budget must be a number')\n try {\n const value = thinkingBudget.setClaude(raw === undefined || raw === null ? undefined : raw)\n return { claudeBudget: value ?? null }\n } catch (error) {\n fail(error instanceof Error ? error.message : String(error))\n }\n },\n\n 'thinking.set': async (payload) => {\n const body = payload as { level?: unknown; budget?: unknown } | undefined\n const level = body?.level\n if (typeof level !== 'string' || level === '') fail('level is required')\n // `undefined` and explicit null BOTH clear the entry: the field means \"let\n // upstream choose\", and a JSON null is how a cleared input arrives.\n const raw = body?.budget\n if (raw !== undefined && raw !== null && typeof raw !== 'number') fail('budget must be a number')\n const value = raw === undefined || raw === null ? undefined : raw\n try {\n const budgets = thinkingBudget.set(level, value)\n return { budgets }\n } catch (error) {\n // Surface the range/name violation as a caller-correctable message\n // rather than an unlabelled RPC failure.\n fail(error instanceof Error ? error.message : String(error))\n }\n },\n\n 'stats.get': async () => statsView(),\n }\n\n return {\n async call(method, payload) {\n const handler = methods[method as AgyRpcMethod]\n if (handler === undefined) fail(`unknown method: ${method}`)\n return handler(payload)\n },\n\n async handleCallback(params) {\n const code = params.get('code')\n const state = params.get('state')\n if (!code || !state) return { ok: false, error: 'Missing code or state parameter' }\n const expected = pendingAuth.get(state)\n if (expected === undefined) {\n return { ok: false, error: 'Unknown or expired authorization — please start a new login.' }\n }\n // One-time use: consume the issued state regardless of the exchange result.\n pendingAuth.delete(state)\n const redirectUri = `${baseUrl()}/agy/oauth-callback`\n const result = await exchangeAntigravity(code, state, redirectUri, expected.verifier)\n if (result.type === 'failed') return { ok: false, error: result.error }\n await upsertImportedAccount(store, {\n accessToken: result.access,\n refreshToken: result.refresh.split('|')[0]!,\n tokenType: 'Bearer',\n expiresAt: new Date(result.expires).toISOString(),\n authMethod: 'oauth',\n email: result.email ?? null,\n projectId: result.projectId || null,\n clientId: result.clientId || null,\n }, { overwriteExisting: true })\n return { ok: true, email: result.email ?? null }\n },\n }\n}\n","/**\n * Bilingual UI dictionary for the dsh-agy web dashboard and OAuth callbacks.\n */\n\nexport interface I18nStrings {\n loginSuccessTitle: string\n loginSuccessDesc: string\n loginFailedTitle: string\n windowClosing: string\n}\n\nexport const I18N_DICT: { en: I18nStrings; zh: I18nStrings } = {\n en: {\n loginSuccessTitle: 'Sign-in Successful',\n loginSuccessDesc: 'Your Antigravity account has been authorized and saved.',\n loginFailedTitle: 'Sign-in Failed',\n windowClosing: 'This window will close automatically in a moment...',\n },\n zh: {\n loginSuccessTitle: '授权登录成功',\n loginSuccessDesc: 'Antigravity 账号已成功接入并保存。',\n loginFailedTitle: '授权登录失败',\n windowClosing: '此窗口即将在 2 秒内自动关闭...',\n },\n}\n","/**\n * Browser-facing HTML for the agy OAuth callback.\n *\n * This is the only page agy serves. The account/statistics dashboard that used\n * to live at `/agy` is gone: that surface is now the Settings section rendered\n * by `src/client/` over the `/api/agy` RPC, so there is no longer a second UI to\n * keep in step with it.\n *\n * The callback stays a real page because Google redirects the browser here.\n */\n\nimport { I18N_DICT } from './i18n.ts'\n\n/**\n * Escape text for an HTML text/attribute position.\n *\n * Every interpolation below except the static markup is attacker-influenced or\n * upstream-influenced: `error` is the token endpoint's raw response body, and\n * `email` comes from Google's userinfo. This page is served by the SAME web\n * server, and therefore the same origin, as the DSH GUI — so injected script\n * here would run with the GUI's session and could reach `/api/agy`\n * (`account.exportAll` returns live credential blobs). That chain is why the\n * escaping matters more than the narrow trigger suggests.\n *\n * Text in markup goes through {@link escapeHtml}; text inside the inline\n * `<script>` goes through {@link jsonForInlineScript}. Neither is optional.\n */\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\n/**\n * Serialize a value for interpolation into the inline `<script>`.\n *\n * `JSON.stringify` escapes for a JS string context only, NOT for the HTML script\n * data state: it leaves `<` alone, so an email or error body containing\n * `</script>` terminates the element early and everything after it is parsed as\n * markup. Escaping the three HTML-significant characters as `\\uXXXX` keeps the\n * JSON value identical while making the byte sequence unrepresentable in the\n * source. (The `JSON.stringify` output above is a valid JS string either way,\n * because a `\\u003c` escape and a literal `<` denote the same character.)\n */\nfunction jsonForInlineScript(value: unknown): string {\n return JSON.stringify(value)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n}\n\nexport function renderCallbackHtml(options: { ok: boolean; error?: string; email?: string | null; baseUrl: string }): string {\n const { ok, error, email, baseUrl } = options\n const i18nJson = jsonForInlineScript(I18N_DICT)\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Antigravity Sign-in</title>\n <style>\n :root {\n color-scheme: light dark;\n --dsw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;\n --bg-page: #0f1115;\n --bg-surface: #171a21;\n --border-l2: rgba(255, 255, 255, 0.12);\n --text-primary: #f8fafc;\n --text-secondary: #94a3b8;\n --brand-primary: #5686fe;\n --state-success: #34d399;\n --state-error: #f87171;\n }\n @media (prefers-color-scheme: light) {\n :root {\n --bg-page: #f8fafc;\n --bg-surface: #ffffff;\n --border-l2: rgba(0, 0, 0, 0.12);\n --text-primary: #0f172a;\n --text-secondary: #475569;\n --brand-primary: #4176e6;\n --state-success: #22c55e;\n --state-error: #ef4444;\n }\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: var(--dsw-font-family);\n background-color: var(--bg-page);\n color: var(--text-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n padding: 16px;\n }\n .card {\n background: var(--bg-surface);\n border: 1px solid var(--border-l2);\n border-radius: 12px;\n padding: 24px;\n max-width: 440px;\n width: 100%;\n text-align: center;\n box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);\n }\n .icon-wrap {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 16px;\n font-size: 24px;\n }\n .icon-success { background: rgba(52, 211, 153, 0.15); color: var(--state-success); }\n .icon-error { background: rgba(248, 113, 113, 0.15); color: var(--state-error); }\n h1 { font-size: 18px; font-weight: 600; margin-bottom: 8px; }\n p { font-size: 13.5px; color: var(--text-secondary); margin-bottom: 16px; line-height: 1.5; }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n height: 34px;\n padding: 0 16px;\n border-radius: 17px;\n border: 1px solid var(--border-l2);\n background: transparent;\n color: var(--text-primary);\n cursor: pointer;\n text-decoration: none;\n }\n .btn:hover { background: rgba(128, 128, 128, 0.1); }\n .btn-primary { background: var(--brand-primary); color: #ffffff; border-color: transparent; }\n </style>\n</head>\n<body>\n <div class=\"card\">\n ${ok ? `\n <div class=\"icon-wrap icon-success\">✓</div>\n <h1 id=\"title\">Sign-in Successful</h1>\n <p id=\"desc\">Your Antigravity account ${email ? '<strong>' + escapeHtml(email) + '</strong> ' : ''}has been authorized and saved.</p>\n <p style=\"font-size:12px;opacity:0.8;\" id=\"closing\">This window will close automatically...</p>\n <button class=\"btn\" onclick=\"window.close()\">Close Window</button>\n ` : `\n <div class=\"icon-wrap icon-error\">✕</div>\n <h1 id=\"title\">Sign-in Failed</h1>\n <p id=\"desc\">Error details: ${escapeHtml(error || 'Unknown error')}</p>\n <a class=\"btn btn-primary\" href=\"${escapeHtml(baseUrl)}/\">Return to Settings</a>\n `}\n </div>\n <script>\n const I18N = ${i18nJson};\n const lang = localStorage.getItem('agy_lang') || (navigator.language.startsWith('zh') ? 'zh' : 'en');\n const dict = I18N[lang] || I18N.en;\n\n if (${ok ? 'true' : 'false'}) {\n document.getElementById('title').textContent = dict.loginSuccessTitle;\n // textContent, not innerHTML: an email containing markup must never be\n // parsed as HTML. The string itself is serialized for the script data\n // state by jsonForInlineScript, so it cannot terminate this element.\n document.getElementById('desc').textContent = dict.loginSuccessDesc + (${jsonForInlineScript(email ? ' (' + email + ')' : '')});\n document.getElementById('closing').textContent = dict.windowClosing;\n \n // Notify parent window & close\n try {\n if (window.opener) {\n // Target this page's own origin rather than '*', so the message cannot\n // be delivered to an unrelated opener.\n window.opener.postMessage({ type: 'agy_login_success' }, window.location.origin);\n }\n } catch (e) {}\n setTimeout(() => {\n window.close();\n }, 1800);\n } else {\n document.getElementById('title').textContent = dict.loginFailedTitle;\n }\n </script>\n</body>\n</html>`\n}\n","/**\n * dsh-agy web entry.\n *\n * Two things are registered, and they deliberately use different transports:\n *\n * 1. **Management RPC at `/api/agy`** (`connection.fetch.register`). The inline\n * Settings UI calls methods over DSH's own RPC channel. This replaces the\n * previous bare `ctx.webServer.register` route table, which had no\n * authentication of its own: `/api/*` sits behind the host's browser-trust\n * fence and BrowserAuth, so the management surface is no longer reachable by\n * anything that can merely open a socket to a loopback port.\n *\n * 2. **The OAuth callback at `/agy/oauth-callback`** stays a real HTTP route.\n * Google redirects a browser to it with a GET, which the management RPC\n * channel (POST-only) cannot carry.\n *\n * BOTH `webServer` and `connection` are deliberately reached lazily rather than\n * statically injected. Each exists only in a Web composition, and a statically\n * injected service that never appears leaves this entry permanently pending —\n * and the loader treats a pending entry as a FAILED PROFILE, not a skipped one:\n *\n * dsh: plugin tree failed to load: dsh: 1 entry did not activate\n * dsh-agy/web: pending (waiting for service: webServer)\n *\n * Measured on a real TUI profile, where no provider of `webServer` is mounted.\n * `ctx.inject([...])` keeps this entry active and inert instead, so a TUI or\n * headless profile boots with no management surface rather than not booting.\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n// Type-only: merges the `llm/adapters-updated` event into the Cordis Events map.\nimport type {} from '@deepseek-ai/dsh-llm/types'\nimport { createAgyRuntime } from '../plugin-common.ts'\nimport { isAgyDisabled } from '../runtime/risk.ts'\nimport { createAgyManagement } from './management.ts'\nimport { renderCallbackHtml } from './page.ts'\n\nexport const name = 'dsh-agy-web'\n\n/** The one service every composition provides; the rest are resolved lazily. */\nexport const inject = ['llm']\n\n/** The slice of the host's web-server service this entry uses. */\ninterface WebServerLike {\n register(route: {\n kind: 'exact'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void\n }): () => void\n host?: string\n /** The port actually listened on: the OS-assigned one when `--port 0`. */\n readonly port?: number\n}\n\n/**\n * The loopback base URL the OAuth redirect and the callback page point at.\n *\n * The port is the one the server BOUND, not the one it was asked for.\n * `webStartup.port` is the `--port` flag verbatim, and `--port 0` — which asks\n * the OS for any free port — would send Google's redirect to\n * `http://127.0.0.1:0/agy/oauth-callback`, which nothing answers.\n * The requested port and DSH's own 3080 default only stand in while the\n * server has not reported one.\n * @param host - loopback host the URL names.\n * @param webServer - the host web server (source of the bound port).\n * @param requestedPort - `webStartup.port`, the `--port` flag if one was given.\n * @returns `http://<host>:<port>`, no trailing slash.\n */\nexport function webBaseUrl(host: string, webServer: Pick<WebServerLike, 'port'>, requestedPort?: number): string {\n return `http://${host}:${webServer.port || requestedPort || 3080}`\n}\n\nexport function apply(ctx: Context): void {\n if (isAgyDisabled()) {\n ctx.logger.warn('[dsh-agy] disabled by DSH_AGY_DISABLE=1 — skipping web registration')\n return\n }\n // Lazily, like `connection` below: see the module docblock for why a static\n // `webServer` is a profile-breaking bug rather than a nicety.\n ctx.inject(['webServer'], (webCtx) => {\n const webServer = webCtx.get('webServer') as WebServerLike | undefined\n if (!webServer) return\n webCtx.effect(() => registerAgyWeb(webCtx, webServer))\n })\n}\n\n/**\n * Register the OAuth callback route, and the management RPC once `connection`\n * appears.\n * @param ctx - the web-server-bearing context (owns both registrations).\n * @param webServer - the host web server.\n * @returns disposer for every registration this function made.\n */\nasync function registerAgyWeb(ctx: Context, webServer: WebServerLike): Promise<() => void> {\n // Host comes from the webStartup provider (CLI args), falling back to DSH's\n // own web-app default (loopback), never a user override. The port does not:\n // see `webBaseUrl`.\n const webStartup = ctx.get('webStartup') as { host?: string; port?: number } | undefined\n const host = webStartup?.host ?? '127.0.0.1'\n\n // The OAuth callback manages credentials with no authentication of its own,\n // so it must never be reachable from the network. When the web server binds\n // a non-loopback interface, refuse to register it (the loopback-only OAuth\n // redirect would be unusable there anyway).\n const bindHost = webServer.host ?? host\n if (!['127.0.0.1', 'localhost', '::1'].includes(bindHost)) {\n ctx.logger.warn(\n '[dsh-agy] web server bound to \"' + bindHost + '\" (non-loopback): not registering the agy routes ' +\n '(they manage account credentials and must stay loopback-only). Bind the web server to 127.0.0.1 to enable them.',\n )\n return () => {}\n }\n\n const { store, sessions, adapter, stats, recentStore, modelVisibility, thinkingBudget } = await createAgyRuntime(ctx)\n // Read per use rather than once here: the bound port is only known after the\n // server's listen callback has run.\n const baseUrl = (): string => webBaseUrl(host, webServer, webStartup?.port)\n const management = createAgyManagement({\n store,\n sessions,\n stats,\n // The persisted ring, not just this boot's in-memory one.\n recentRequests: () => recentStore.recentRequests(),\n modelVisibility,\n // Expose only the two operations the RPC needs, not the whole store.\n thinkingBudget: {\n all: () => thinkingBudget.all(),\n set: (level, value) => thinkingBudget.setBudget(level, value),\n claude: () => thinkingBudget.claudeBudget(),\n setClaude: (value) => thinkingBudget.setClaudeBudget(value).claudeBudget,\n tiered: () => thinkingBudget.tieredBudget(),\n setTiered: (value) => thinkingBudget.setTieredBudget(value).tieredBudget,\n },\n // The adapter's *unfiltered* catalog, so a hidden model still appears in\n // the settings list alongside the switch that un-hides it.\n listAllModels: () => adapter.listAllModels(),\n // Activating an account can switch the account model discovery rides; the\n // cache carries no account key, so the RPC drops it (see\n // `AgyAdapter.invalidateModelCache`).\n invalidateModelCache: () => adapter.invalidateModelCache(),\n baseUrl,\n // DSH refreshes the model picker on `llm/adapters-updated`; the hidden\n // list lives in agy's own file, so nothing else would announce the change\n // and the toggle would appear to do nothing until a page reload.\n // (`llm/adapters-updated` is declared `@mode emit` for exactly this kind\n // of registry notification.)\n notifyModelsChanged: () => { ctx.emit('llm/adapters-updated') },\n })\n const disposers: Array<() => void> = []\n\n // The one endpoint that cannot ride the RPC channel: Google sends the\n // browser here with a GET redirect.\n disposers.push(webServer.register({\n kind: 'exact',\n path: '/agy/oauth-callback',\n handler: async (req: IncomingMessage, res: ServerResponse) => {\n const base = baseUrl()\n const url = new URL(req.url ?? '/', base)\n const result = await management.handleCallback(url.searchParams).catch((error: unknown) => ({\n ok: false as const,\n error: error instanceof Error ? error.message : String(error),\n }))\n res.writeHead(result.ok ? 200 : 400, { 'content-type': 'text/html; charset=utf-8' })\n res.end(result.ok\n ? renderCallbackHtml({ ok: true, email: result.email ?? null, baseUrl: base })\n : renderCallbackHtml({ ok: false, error: result.error ?? 'Unknown error', baseUrl: base }))\n },\n }))\n\n // Lazy injection: see the module docblock for why this is not static.\n ctx.inject(['connection'], (connectionCtx) => {\n const connection = connectionCtx.get('connection') as\n | {\n fetch: {\n register(route: {\n path: string\n methods: string[]\n requestBody: 'buffered' | 'streaming'\n fetch: (request: Request) => Promise<Response>\n }): () => void\n }\n }\n | undefined\n if (!connection || typeof connection.fetch?.register !== 'function') {\n connectionCtx.logger.warn('[dsh-agy] connection.fetch unavailable — management RPC not registered')\n return\n }\n connectionCtx.effect(() => connection.fetch.register({\n path: '/api/agy',\n methods: ['POST'],\n requestBody: 'buffered',\n async fetch(request: Request): Promise<Response> {\n if (request.method !== 'POST') return new Response('method not allowed', { status: 405 })\n const contentType = request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n return new Response('content type must be application/json', { status: 415 })\n }\n let message: Record<string, unknown>\n try {\n message = await request.json() as Record<string, unknown>\n } catch {\n return new Response('body is not JSON', { status: 400 })\n }\n const rpcId = typeof message.rpcId === 'string' ? message.rpcId : 'invalid-request'\n const call = message.payload as { method?: unknown; payload?: unknown } | undefined\n if (\n message.type !== 'client-request'\n || typeof message.rpcId !== 'string'\n || typeof call?.method !== 'string'\n ) {\n return reply(rpcId, {\n ok: false,\n error: { code: 'agy/bad-request', message: 'Invalid agy management request.' },\n })\n }\n try {\n const value = await management.call(call.method, call.payload)\n return reply(rpcId, { ok: true, value })\n } catch (error) {\n const text = error instanceof Error ? error.message : String(error)\n connectionCtx.logger.warn(`[dsh-agy] ${call.method} failed: ${text}`)\n // A structured failure, not a bare 500: the client's unwrap needs a\n // parseable envelope or a failed call looks like \"nothing happened\".\n return reply(rpcId, { ok: false, error: { code: 'agy/handler-failed', message: text } })\n }\n },\n }), 'dsh-agy: /api/agy management RPC')\n })\n\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/**\n * Wrap one result in the Connection RPC response envelope.\n *\n * A failure MUST carry `error.details` as an object: the client's envelope\n * parser rejects a failure without it as \"invalid server-response\", which hides\n * the real message behind a transport-sounding error.\n */\nfunction reply(rpcId: string, result: unknown): Response {\n const normalized = isFailure(result)\n ? { ...result, error: { details: {}, ...result.error } }\n : result\n return Response.json({ type: 'server-response', rpcId, result: normalized })\n}\n\n/** Whether one result is an RPC failure envelope. */\nfunction isFailure(value: unknown): value is {\n ok: false\n error: { code?: string, message?: string, details?: unknown }\n} {\n return typeof value === 'object' && value !== null\n && (value as { ok?: unknown }).ok === false\n && typeof (value as { error?: unknown }).error === 'object'\n && (value as { error?: unknown }).error !== null\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agy",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "packageManager": "pnpm@11.10.0",
5
5
  "description": "Google Antigravity (agy) OAuth auth + model access plugin for DeepSeek Harness: multi-account pool, 429 rotation, device fingerprinting, CLI and web login.",
6
6
  "type": "module",