pi-ui-extend 0.1.28 → 0.1.31

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.
Files changed (34) hide show
  1. package/dist/app/app.d.ts +2 -0
  2. package/dist/app/app.js +31 -8
  3. package/dist/app/cli/update.d.ts +5 -0
  4. package/dist/app/cli/update.js +29 -1
  5. package/dist/app/model/model-usage-status.d.ts +2 -0
  6. package/dist/app/model/model-usage-status.js +90 -20
  7. package/dist/app/session/session-event-controller.d.ts +17 -1
  8. package/dist/app/session/session-event-controller.js +28 -0
  9. package/dist/app/session/tabs-controller.d.ts +10 -0
  10. package/dist/app/session/tabs-controller.js +65 -28
  11. package/external/pi-tools-suite/package.json +0 -3
  12. package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
  13. package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +1 -1
  14. package/external/pi-tools-suite/src/async-subagents/index.ts +1 -1
  15. package/external/pi-tools-suite/src/async-subagents/render.ts +1 -1
  16. package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +2 -2
  17. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +2 -2
  18. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +3 -3
  19. package/external/pi-tools-suite/src/async-subagents/tools/status.ts +3 -3
  20. package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +2 -2
  21. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -3
  22. package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +2 -2
  23. package/external/pi-tools-suite/src/async-subagents/ui.ts +1 -1
  24. package/external/pi-tools-suite/src/dcp/commands.ts +2 -2
  25. package/external/pi-tools-suite/src/dcp/compress-tool.ts +1 -1
  26. package/external/pi-tools-suite/src/dcp/index.ts +1 -1
  27. package/external/pi-tools-suite/src/lsp/constants.ts +1 -0
  28. package/external/pi-tools-suite/src/lsp/manager.ts +120 -71
  29. package/external/pi-tools-suite/src/model-tools/apply-patch.ts +1 -1
  30. package/external/pi-tools-suite/src/model-tools/index.ts +1 -1
  31. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +2 -2
  32. package/external/pi-tools-suite/src/tool-descriptions.ts +2 -2
  33. package/external/pi-tools-suite/src/usage/lib/google.ts +39 -4
  34. package/package.json +4 -7
package/dist/app/app.d.ts CHANGED
@@ -81,6 +81,8 @@ export declare class PiUiExtendApp {
81
81
  private setVoicePartialTranscript;
82
82
  private addVoiceSystemMessage;
83
83
  private resetSessionView;
84
+ private captureSessionView;
85
+ private restoreSessionView;
84
86
  private loadSessionHistory;
85
87
  private openSearchResultInNewTab;
86
88
  private scrollToUserMessageJumpTarget;
package/dist/app/app.js CHANGED
@@ -41,7 +41,7 @@ import { TabLineRenderer } from "./rendering/tab-line-renderer.js";
41
41
  import { AppTerminalController } from "./terminal/terminal-controller.js";
42
42
  import { TerminalBellSoundController } from "./terminal/terminal-bell-sound-controller.js";
43
43
  import { AppToastController } from "./rendering/toast-controller.js";
44
- import { checkPixUpdate, formatPixStartupUpdateDialog } from "./cli/update.js";
44
+ import { checkPiUpdate, checkPixUpdate, formatPixStartupUpdateDialog, formatPiStartupUpdateToast } from "./cli/update.js";
45
45
  import { AppVoiceController } from "./input/voice-controller.js";
46
46
  import { createIsolatedExtensionEventBus } from "./extensions/extension-event-bus.js";
47
47
  import { setAppIconTheme } from "./icons.js";
@@ -181,6 +181,8 @@ export class PiUiExtendApp {
181
181
  resetSessionView: () => this.resetSessionView(),
182
182
  loadSessionHistory: () => this.loadSessionHistory(),
183
183
  loadSessionHistoryAsync: (options) => this.loadSessionHistoryAsync(options),
184
+ captureSessionView: () => this.captureSessionView(),
185
+ restoreSessionView: (view) => this.restoreSessionView(view),
184
186
  syncUserSessionEntryMetadata: () => this.workspaceActions.syncUserSessionEntryMetadata(),
185
187
  captureInputState: () => this.inputEditor.draftState,
186
188
  restoreInputState: (state) => this.restoreTabInputState(state),
@@ -785,18 +787,27 @@ export class PiUiExtendApp {
785
787
  await this.sessionLifecycle.start();
786
788
  this.modelUsageController.startPolling();
787
789
  this.nerdFontController.ensureInstalledOnStartup();
788
- void this.checkPixUpdateOnStartup();
790
+ this.checkPixUpdateOnStartup();
789
791
  }
790
- async checkPixUpdateOnStartup() {
791
- try {
792
- const result = await checkPixUpdate();
792
+ checkPixUpdateOnStartup() {
793
+ void checkPiUpdate()
794
+ .then((result) => {
795
+ if (result.status !== "newer")
796
+ return;
797
+ this.showToast(formatPiStartupUpdateToast(result), "warning");
798
+ })
799
+ .catch(() => {
800
+ // Startup update checks should never interrupt the TUI.
801
+ });
802
+ void checkPixUpdate()
803
+ .then((result) => {
793
804
  if (result.status !== "newer")
794
805
  return;
795
806
  this.showToast(formatPixStartupUpdateDialog(result), "warning", { variant: "dialog" });
796
- }
797
- catch {
807
+ })
808
+ .catch(() => {
798
809
  // Startup update checks should never interrupt the TUI.
799
- }
810
+ });
800
811
  }
801
812
  async bindCurrentSession(options) {
802
813
  await this.sessionLifecycle.bindCurrentSession(options);
@@ -889,6 +900,18 @@ export class PiUiExtendApp {
889
900
  resetSessionView() {
890
901
  this.sessionLifecycle.resetSessionView();
891
902
  }
903
+ captureSessionView() {
904
+ return {
905
+ entries: [...this.entries],
906
+ eventState: this.sessionEvents.snapshotState(),
907
+ };
908
+ }
909
+ restoreSessionView(view) {
910
+ this.entries.splice(0, this.entries.length, ...view.entries);
911
+ this.sessionEvents.restoreState(view.eventState);
912
+ this.conversationViewport.clear();
913
+ this.workspaceActions.syncUserSessionEntryMetadata();
914
+ }
892
915
  loadSessionHistory() {
893
916
  void this.sessionEvents.loadSessionHistoryAsync({
894
917
  isCancelled: () => !this.running,
@@ -33,12 +33,17 @@ export type PixUpdateCheckOptions = {
33
33
  packageRoot?: string;
34
34
  fetchLatestVersion?: (packageName: string, currentVersion: string, timeoutMs: number) => Promise<string | undefined>;
35
35
  };
36
+ export type PiUpdateCheckOptions = PixUpdateCheckOptions & {
37
+ pixPackageRoot?: string;
38
+ };
36
39
  export declare function pixUpdateUsage(): string;
37
40
  export declare function parsePixUpdateArgs(argv: readonly string[]): PixUpdateCliOptions;
38
41
  export declare function getPixPackageVersion(packageRoot?: string): string;
39
42
  export declare function checkPixUpdate(options?: PixUpdateCheckOptions): Promise<PixUpdateCheckResult>;
43
+ export declare function checkPiUpdate(options?: PiUpdateCheckOptions): Promise<PixUpdateCheckResult>;
40
44
  export declare function formatPixUpdateCheck(result: PixUpdateCheckResult): string;
41
45
  export declare function formatPixStartupUpdateDialog(result: PixUpdateCheckResult): string;
46
+ export declare function formatPiStartupUpdateToast(result: PixUpdateCheckResult): string;
42
47
  export declare function getPixSelfUpdateCommand(packageName: string, latestVersion?: string, packageRoot?: string): PixSelfUpdateCommand | undefined;
43
48
  export declare function runPixUpdateCli(argv?: readonly string[]): Promise<number>;
44
49
  declare function runCommand(command: PixSelfUpdateCommand): Promise<void>;
@@ -1,10 +1,13 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
3
4
  import { dirname, join, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
6
7
  const DEFAULT_UPDATE_TIMEOUT_MS = 10_000;
7
8
  const NPM_REGISTRY_URL = "https://registry.npmjs.org";
9
+ const PI_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
10
+ const requireFromUpdateModule = createRequire(import.meta.url);
8
11
  const defaultPixUpdateDeps = {
9
12
  checkPixUpdate,
10
13
  runCommand,
@@ -53,6 +56,14 @@ export function getPixPackageVersion(packageRoot) {
53
56
  }
54
57
  export async function checkPixUpdate(options = {}) {
55
58
  const packageInfo = readPixPackageInfo(options.packageRoot);
59
+ return await checkPackageUpdate(packageInfo, options);
60
+ }
61
+ export async function checkPiUpdate(options = {}) {
62
+ const packageRoot = options.packageRoot ?? findPiPackageRoot(options.pixPackageRoot);
63
+ const packageInfo = readPackageInfo(packageRoot, PI_PACKAGE_NAME);
64
+ return await checkPackageUpdate(packageInfo, options);
65
+ }
66
+ async function checkPackageUpdate(packageInfo, options) {
56
67
  const base = {
57
68
  packageName: packageInfo.name,
58
69
  currentVersion: packageInfo.version,
@@ -124,6 +135,9 @@ export function formatPixStartupUpdateDialog(result) {
124
135
  `current: ${result.packageName} v${result.currentVersion}`,
125
136
  ...(result.latestVersion ? [`latest: ${result.latestVersion}`] : []),
126
137
  "",
138
+ "Pix includes the pinned Pi SDK/dependencies used by this renderer.",
139
+ "Updating only the global `pi` CLI is not enough for Pix.",
140
+ "",
127
141
  "To update:",
128
142
  "1. Exit Pix.",
129
143
  "2. Run `pix update` in your shell.",
@@ -131,6 +145,11 @@ export function formatPixStartupUpdateDialog(result) {
131
145
  ];
132
146
  return lines.join("\n");
133
147
  }
148
+ export function formatPiStartupUpdateToast(result) {
149
+ return result.latestVersion
150
+ ? `Pi ${result.latestVersion} is available; Pix bundles Pi ${result.currentVersion}. Waiting for a matching Pix update.`
151
+ : `Pi update detected; Pix bundles Pi ${result.currentVersion}. Waiting for a matching Pix update.`;
152
+ }
134
153
  export function getPixSelfUpdateCommand(packageName, latestVersion, packageRoot = readPixPackageInfo().packageRoot) {
135
154
  if (!packageRootLooksPackageManaged(packageRoot))
136
155
  return undefined;
@@ -191,9 +210,12 @@ export async function runPixUpdateCli(argv = process.argv.slice(2)) {
191
210
  }
192
211
  }
193
212
  function readPixPackageInfo(packageRoot = findPixPackageRoot()) {
213
+ return readPackageInfo(packageRoot, "pi-ui-extend");
214
+ }
215
+ function readPackageInfo(packageRoot, fallbackName) {
194
216
  const packageJsonPath = join(packageRoot, "package.json");
195
217
  const raw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
196
- const name = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "pi-ui-extend";
218
+ const name = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : fallbackName;
197
219
  const version = typeof raw.version === "string" && raw.version.trim() ? raw.version.trim() : "0.0.0";
198
220
  return {
199
221
  name,
@@ -214,6 +236,12 @@ function findPixPackageRoot() {
214
236
  currentDir = nextDir;
215
237
  }
216
238
  }
239
+ function findPiPackageRoot(pixPackageRoot = readPixPackageInfo().packageRoot) {
240
+ const packageJsonPath = requireFromUpdateModule.resolve(`${PI_PACKAGE_NAME}/package.json`, {
241
+ paths: [pixPackageRoot],
242
+ });
243
+ return dirname(packageJsonPath);
244
+ }
217
245
  async function fetchLatestNpmVersion(packageName, currentVersion, timeoutMs) {
218
246
  const response = await fetch(`${NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}/latest`, {
219
247
  headers: {
@@ -89,6 +89,8 @@ type AntigravityQuotaAccount = {
89
89
  readonly email?: string;
90
90
  readonly refreshToken: string;
91
91
  readonly accessToken?: string;
92
+ readonly clientId?: string;
93
+ readonly clientSecret?: string;
92
94
  readonly cachedQuota?: AntigravityCachedQuota;
93
95
  readonly cachedQuotaUpdatedAt?: number;
94
96
  readonly projectId: string;
@@ -8,6 +8,8 @@ const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
8
8
  const ZAI_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
9
9
  const ZHIPU_QUOTA_URL = "https://bigmodel.cn/api/monitor/usage/quota/limit";
10
10
  const GOOGLE_QUOTA_API_URL = "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
11
+ const GOOGLE_TOKEN_REFRESH_URL = "https://oauth2.googleapis.com/token";
12
+ const GOOGLE_ANTIGRAVITY_USER_AGENT = "antigravity/1.11.9 windows/amd64";
11
13
  const REQUEST_TIMEOUT_MS = 10_000;
12
14
  const DAY_SECONDS = 86_400;
13
15
  const HOUR_SECONDS = 3_600;
@@ -383,11 +385,11 @@ export function resolveAntigravityQuotaModelKey(model) {
383
385
  }
384
386
  export function googleAntigravityUsageStatusFromResponse(data, descriptor, now = Date.now()) {
385
387
  const quotaInfo = data.models[descriptor.quotaModelKey]?.quotaInfo;
386
- if (!quotaInfo || !Number.isFinite(quotaInfo.remainingFraction))
388
+ if (!quotaInfo)
387
389
  return undefined;
388
390
  const resetAt = parseResetTime(quotaInfo.resetTime, now);
389
391
  const window = {
390
- remainingPercent: clampPercent(Math.round((quotaInfo.remainingFraction ?? 0) * 100)),
392
+ remainingPercent: quotaRemainingPercent(quotaInfo),
391
393
  resetAt,
392
394
  windowSeconds: Math.max(0, Math.round((resetAt - now) / 1000)),
393
395
  };
@@ -404,13 +406,8 @@ export function googleAntigravityUsageStatusFromResponse(data, descriptor, now =
404
406
  }
405
407
  async function queryGoogleAntigravityModelUsage(descriptor) {
406
408
  const now = Date.now();
407
- const cachedResponse = googleQuotaResponseFromCachedQuota(descriptor.account.cachedQuota, descriptor.account.cachedQuotaUpdatedAt, now);
408
- if (cachedResponse)
409
- return googleAntigravityUsageStatusFromResponse(cachedResponse, descriptor, now);
410
- if (!descriptor.account.accessToken)
411
- return undefined;
412
- const response = await fetchGoogleAntigravityQuota(descriptor.account.accessToken, descriptor.account.projectId);
413
- return googleAntigravityUsageStatusFromResponse(response, descriptor);
409
+ const response = await fetchGoogleAntigravityQuotaForAccount(descriptor.account, now);
410
+ return googleAntigravityUsageStatusFromResponse(response, descriptor, now);
414
411
  }
415
412
  const GOOGLE_ACCOUNT_QUOTA_WINDOWS = [
416
413
  { label: "Claude Opus", quotaModelKey: "claude-opus-4-6-thinking" },
@@ -424,12 +421,8 @@ async function queryGoogleAntigravityAccountUsage(now) {
424
421
  const results = await Promise.all(accounts.map(async (account) => {
425
422
  const accountLabel = account.email ?? maskCredential(account.refreshToken);
426
423
  try {
427
- const response = account.cachedQuota ? googleQuotaResponseFromCachedQuota(account.cachedQuota, account.cachedQuotaUpdatedAt, now) : undefined;
428
- const windows = response ? googleAccountWindowsFromResponse(response, now) : [];
429
- if (windows.length === 0 && account.accessToken) {
430
- const liveResponse = await fetchGoogleAntigravityQuota(account.accessToken, account.projectId);
431
- windows.push(...googleAccountWindowsFromResponse(liveResponse, now));
432
- }
424
+ const response = await fetchGoogleAntigravityQuotaForAccount(account, now);
425
+ const windows = googleAccountWindowsFromResponse(response, now);
433
426
  return {
434
427
  account: accountLabel,
435
428
  windows,
@@ -456,12 +449,14 @@ function readAllAntigravityQuotaAccounts() {
456
449
  const credential = readPiAuthSync().antigravity;
457
450
  if (!credential)
458
451
  return [];
452
+ const credentialClient = getGoogleOAuthClientCredentials(credential);
459
453
  const accounts = storedAntigravityAccounts(credential);
460
454
  if (accounts.length > 0) {
461
455
  const activeIndex = clampAccountIndex(credential.activeIndex, accounts.length);
462
456
  const activeAccess = antigravityAccessFromCredential(credential);
463
457
  return accounts.map((account, accountIndex) => antigravityQuotaAccount(account, {
464
458
  ...(credential.email ? { fallbackEmail: credential.email } : {}),
459
+ ...(credentialClient ? { clientCredentials: credentialClient } : {}),
465
460
  ...(accountIndex === activeIndex && activeAccess ? { accessToken: activeAccess.accessToken } : {}),
466
461
  accountIndex,
467
462
  accountCount: accounts.length,
@@ -471,6 +466,7 @@ function readAllAntigravityQuotaAccounts() {
471
466
  const fallbackAccess = antigravityAccessFromCredential(credential);
472
467
  const account = fallbackAccount ? antigravityQuotaAccount(fallbackAccount, {
473
468
  ...(credential.email ? { fallbackEmail: credential.email } : {}),
469
+ ...(credentialClient ? { clientCredentials: credentialClient } : {}),
474
470
  ...(fallbackAccess ? { accessToken: fallbackAccess.accessToken } : {}),
475
471
  }) : undefined;
476
472
  return account ? [account] : [];
@@ -483,9 +479,41 @@ function readPiAuthSync() {
483
479
  return {};
484
480
  }
485
481
  }
482
+ function getAccountRefreshToken(account) {
483
+ if (account.refreshToken)
484
+ return account.refreshToken;
485
+ if (!account.refresh)
486
+ return undefined;
487
+ return splitAntigravityRefresh(account.refresh).refreshToken;
488
+ }
489
+ function stringProperty(source, keys) {
490
+ if (!source || typeof source !== "object")
491
+ return undefined;
492
+ const record = source;
493
+ for (const key of keys) {
494
+ const value = record[key];
495
+ if (typeof value === "string" && value)
496
+ return value;
497
+ }
498
+ return undefined;
499
+ }
500
+ function getGoogleOAuthClientCredentials(...sources) {
501
+ for (const source of sources) {
502
+ const nested = source && typeof source === "object"
503
+ ? source.oauthClient
504
+ : undefined;
505
+ const nestedClientId = stringProperty(nested, ["clientId", "client_id", "id"]);
506
+ const nestedClientSecret = stringProperty(nested, ["clientSecret", "client_secret", "secret"]);
507
+ const clientId = nestedClientId ?? stringProperty(source, ["clientId", "client_id", "googleClientId", "google_client_id", "oauthClientId", "oauth_client_id"]);
508
+ const clientSecret = nestedClientSecret ?? stringProperty(source, ["clientSecret", "client_secret", "googleClientSecret", "google_client_secret", "oauthClientSecret", "oauth_client_secret"]);
509
+ if (clientId)
510
+ return { clientId, ...(clientSecret ? { clientSecret } : {}) };
511
+ }
512
+ return undefined;
513
+ }
486
514
  function storedAntigravityAccounts(credential) {
487
515
  return Array.isArray(credential.accounts)
488
- ? credential.accounts.filter((account) => account.enabled !== false && !!account.refreshToken)
516
+ ? credential.accounts.filter((account) => account.enabled !== false && !!getAccountRefreshToken(account))
489
517
  : [];
490
518
  }
491
519
  function antigravityAccountFromCredential(credential) {
@@ -506,16 +534,19 @@ function antigravityAccountFromCredential(credential) {
506
534
  };
507
535
  }
508
536
  function antigravityQuotaAccount(account, options = {}) {
509
- const refreshToken = account.refreshToken;
537
+ const refreshToken = getAccountRefreshToken(account);
510
538
  if (!refreshToken)
511
539
  return undefined;
512
540
  const email = account.email || options.fallbackEmail;
513
541
  const projectId = account.projectId || account.managedProjectId || DEFAULT_ANTIGRAVITY_PROJECT_ID;
542
+ const clientCredentials = getGoogleOAuthClientCredentials(account, options.clientCredentials);
514
543
  return {
515
544
  refreshToken,
516
545
  projectId,
517
546
  cacheKey: email ? email.toLowerCase() : shortHash(refreshToken),
518
547
  ...(options.accessToken ? { accessToken: options.accessToken } : {}),
548
+ ...(clientCredentials ? { clientId: clientCredentials.clientId } : {}),
549
+ ...(clientCredentials?.clientSecret ? { clientSecret: clientCredentials.clientSecret } : {}),
519
550
  ...(account.cachedQuota ? { cachedQuota: account.cachedQuota } : {}),
520
551
  ...(typeof account.cachedQuotaUpdatedAt === "number" ? { cachedQuotaUpdatedAt: account.cachedQuotaUpdatedAt } : {}),
521
552
  ...(email ? { email } : {}),
@@ -590,13 +621,49 @@ function clampAccountIndex(index, accountCount) {
590
621
  function shortHash(value) {
591
622
  return createHash("sha256").update(value).digest("hex").slice(0, 12);
592
623
  }
624
+ async function fetchGoogleAntigravityQuotaForAccount(account, now = Date.now()) {
625
+ if (account.accessToken)
626
+ return await fetchGoogleAntigravityQuota(account.accessToken, account.projectId);
627
+ if (account.clientId) {
628
+ const { accessToken } = await refreshGoogleAntigravityAccessToken(account);
629
+ return await fetchGoogleAntigravityQuota(accessToken, account.projectId);
630
+ }
631
+ const cachedResponse = googleQuotaResponseFromCachedQuota(account.cachedQuota, account.cachedQuotaUpdatedAt, now);
632
+ if (cachedResponse)
633
+ return cachedResponse;
634
+ throw new Error("Missing Google OAuth client credentials, cannot query live Antigravity quota.");
635
+ }
636
+ async function refreshGoogleAntigravityAccessToken(account) {
637
+ if (!account.clientId)
638
+ throw new Error("Missing Google OAuth client id, cannot refresh Antigravity access token.");
639
+ const params = new URLSearchParams({
640
+ client_id: account.clientId,
641
+ refresh_token: account.refreshToken,
642
+ grant_type: "refresh_token",
643
+ });
644
+ if (account.clientSecret)
645
+ params.set("client_secret", account.clientSecret);
646
+ const response = await fetchWithTimeout(GOOGLE_TOKEN_REFRESH_URL, {
647
+ method: "POST",
648
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
649
+ body: params,
650
+ });
651
+ if (!response.ok) {
652
+ const errorText = await response.text();
653
+ throw new Error(`Google token refresh failed (${response.status}): ${errorText}`);
654
+ }
655
+ const data = await response.json();
656
+ if (!data.access_token)
657
+ throw new Error("Google token refresh did not return an access token.");
658
+ return { accessToken: data.access_token };
659
+ }
593
660
  async function fetchGoogleAntigravityQuota(accessToken, projectId) {
594
661
  const response = await fetchWithTimeout(GOOGLE_QUOTA_API_URL, {
595
662
  method: "POST",
596
663
  headers: {
597
664
  "Content-Type": "application/json",
598
665
  Authorization: `Bearer ${accessToken}`,
599
- "User-Agent": "antigravity/1.18.3 darwin/arm64",
666
+ "User-Agent": GOOGLE_ANTIGRAVITY_USER_AGENT,
600
667
  },
601
668
  body: JSON.stringify({ project: projectId }),
602
669
  });
@@ -757,16 +824,19 @@ function accountWindowFromRateLimit(window, now) {
757
824
  }
758
825
  function googleAccountWindowFromResponse(data, label, quotaModelKey, now) {
759
826
  const quotaInfo = data.models[quotaModelKey]?.quotaInfo;
760
- if (!quotaInfo || !Number.isFinite(quotaInfo.remainingFraction))
827
+ if (!quotaInfo)
761
828
  return undefined;
762
829
  const resetAt = parseResetTime(quotaInfo.resetTime, now);
763
830
  return {
764
831
  label,
765
- remainingPercent: clampPercent(Math.round((quotaInfo.remainingFraction ?? 0) * 100)),
832
+ remainingPercent: quotaRemainingPercent(quotaInfo),
766
833
  resetAt,
767
834
  windowSeconds: Math.max(0, Math.round((resetAt - now) / 1000)),
768
835
  };
769
836
  }
837
+ function quotaRemainingPercent(quotaInfo) {
838
+ return clampPercent(Math.round((Number.isFinite(quotaInfo.remainingFraction) ? quotaInfo.remainingFraction : 0) * 100));
839
+ }
770
840
  function googleAccountWindowsFromResponse(data, now) {
771
841
  return GOOGLE_ACCOUNT_QUOTA_WINDOWS
772
842
  .map((window) => googleAccountWindowFromResponse(data, window.label, window.quotaModelKey, now))
@@ -1,8 +1,22 @@
1
1
  import type { AgentSessionEvent, AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
2
  import type { ConversationViewport } from "../rendering/conversation-viewport.js";
3
- import { type LoadOlderSessionHistoryOptions } from "./session-history.js";
3
+ import { type LoadOlderSessionHistoryOptions, type SessionHistoryOlderLoader } from "./session-history.js";
4
4
  import type { Entry, SessionActivity } from "../types.js";
5
5
  import type { WorkspaceMutation, WorkspaceMutationPreparation } from "../workspace/workspace-undo.js";
6
+ export type AppSessionEventControllerState = {
7
+ toolEntryIdsByCallId: Map<string, string>;
8
+ toolMutationPreparationsByCallId: Map<string, {
9
+ userEntryId: string;
10
+ args: unknown;
11
+ preparation?: WorkspaceMutationPreparation;
12
+ }>;
13
+ olderHistoryLoader: SessionHistoryOlderLoader | undefined;
14
+ currentUserEntryId: string | undefined;
15
+ currentAssistantEntryId: string | undefined;
16
+ currentThinkingEntryId: string | undefined;
17
+ assistantTextBuffer: string;
18
+ entryRenderVersions: Map<string, number>;
19
+ };
6
20
  export type AppSessionEventControllerHost = {
7
21
  readonly entries: Entry[];
8
22
  runtime(): AgentSessionRuntime | undefined;
@@ -45,6 +59,8 @@ export declare class AppSessionEventController {
45
59
  private currentThinkingEntryId;
46
60
  private assistantTextBuffer;
47
61
  constructor(host: AppSessionEventControllerHost);
62
+ snapshotState(): AppSessionEventControllerState;
63
+ restoreState(state: AppSessionEventControllerState): void;
48
64
  reset(): void;
49
65
  loadSessionHistory(): void;
50
66
  loadSessionHistoryAsync(options: {
@@ -20,6 +20,34 @@ export class AppSessionEventController {
20
20
  constructor(host) {
21
21
  this.host = host;
22
22
  }
23
+ snapshotState() {
24
+ return {
25
+ toolEntryIdsByCallId: new Map(this.toolEntryIdsByCallId),
26
+ toolMutationPreparationsByCallId: new Map(this.toolMutationPreparationsByCallId),
27
+ olderHistoryLoader: this.olderHistoryLoader,
28
+ currentUserEntryId: this.currentUserEntryId,
29
+ currentAssistantEntryId: this.currentAssistantEntryId,
30
+ currentThinkingEntryId: this.currentThinkingEntryId,
31
+ assistantTextBuffer: this.assistantTextBuffer,
32
+ entryRenderVersions: new Map(this.entryRenderVersions),
33
+ };
34
+ }
35
+ restoreState(state) {
36
+ this.toolEntryIdsByCallId.clear();
37
+ for (const [key, value] of state.toolEntryIdsByCallId)
38
+ this.toolEntryIdsByCallId.set(key, value);
39
+ this.toolMutationPreparationsByCallId.clear();
40
+ for (const [key, value] of state.toolMutationPreparationsByCallId)
41
+ this.toolMutationPreparationsByCallId.set(key, value);
42
+ this.olderHistoryLoader = state.olderHistoryLoader;
43
+ this.currentUserEntryId = state.currentUserEntryId;
44
+ this.currentAssistantEntryId = state.currentAssistantEntryId;
45
+ this.currentThinkingEntryId = state.currentThinkingEntryId;
46
+ this.assistantTextBuffer = state.assistantTextBuffer;
47
+ this.entryRenderVersions.clear();
48
+ for (const [key, value] of state.entryRenderVersions)
49
+ this.entryRenderVersions.set(key, value);
50
+ }
23
51
  reset() {
24
52
  this.toolEntryIdsByCallId.clear();
25
53
  this.toolMutationPreparationsByCallId.clear();
@@ -1,8 +1,13 @@
1
1
  import { type AgentSession, type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
2
  import type { BindCurrentSessionOptions } from "./session-lifecycle-controller.js";
3
+ import type { AppSessionEventControllerState } from "./session-event-controller.js";
3
4
  import type { InputEditorDraftState } from "../../input-editor.js";
4
5
  import type { AppBlinkController } from "../screen/blink-controller.js";
5
6
  import type { AppOptions, Entry, SessionActivity, SessionTab, SubmittedUserMessage } from "../types.js";
7
+ type TabSessionView = {
8
+ entries: Entry[];
9
+ eventState: AppSessionEventControllerState;
10
+ };
6
11
  export type TabInputState = InputEditorDraftState;
7
12
  export type AppTabsControllerHost = {
8
13
  readonly options: AppOptions;
@@ -24,6 +29,8 @@ export type AppTabsControllerHost = {
24
29
  render: () => void;
25
30
  lazyOlderHistory?: boolean;
26
31
  }): Promise<boolean>;
32
+ captureSessionView?(): TabSessionView;
33
+ restoreSessionView?(view: TabSessionView): void;
27
34
  syncUserSessionEntryMetadata(): void;
28
35
  captureInputState(): TabInputState;
29
36
  restoreInputState(state: TabInputState): void;
@@ -44,6 +51,7 @@ export declare class AppTabsController {
44
51
  private readonly historyReloadTimersByTabId;
45
52
  private readonly inputStatesByTabId;
46
53
  private readonly deferredUserMessagesByTabId;
54
+ private readonly sessionViewsByTabId;
47
55
  private readonly tabIdsNeedingHistoryReload;
48
56
  private activeTabId;
49
57
  private pendingActiveTabId;
@@ -83,6 +91,7 @@ export declare class AppTabsController {
83
91
  private activeTab;
84
92
  private clearStartupTabPlaceholders;
85
93
  private storeActiveRuntime;
94
+ private storeActiveSessionView;
86
95
  private setRuntimeForTab;
87
96
  private deleteRuntimeForTab;
88
97
  private clearRuntimeSubscriptions;
@@ -138,3 +147,4 @@ export declare class AppTabsController {
138
147
  private preservedSessionPaths;
139
148
  private maxProjectSessions;
140
149
  }
150
+ export {};