Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .audit/grokbot-0755-build.todo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Grok Bot 0.75.5 build migration

- [x] Migrate feature-extraction inputs from the obsolete 0.72.1 snapshot to the pinned 0.75.5 REA snapshot.
- [x] Regenerate Vault parser and renderer artifacts from 0.75.5 bytes; keep runtime-compatible 0721 output names.
- [x] Update 0.75.5 native pins, tests, and provenance expectations.
- [x] Run typechecks, focused tests, full build, packaging, and live Vault session acceptance.
- [x] Extract the shipped Simplified Chinese Vault catalog and title-bar create menu.
- [x] Verify the final QA bundle can open the Vault settings surface.
- [x] Commit the reviewable units, synchronize with GitHub, and push the branch.
27 changes: 8 additions & 19 deletions frontend/src/extensions/port-0750-packaged-hosts.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import { useEffect, useState, type ReactNode } from "react";
import { createCoordinatorClient } from "../production/coordinator-client";
import type { CoordinatorPortBridge, DesktopBridge, EffectivePlugin } from "../recovered/contracts/desktop-bridge";
import type { DesktopBridge, EffectivePlugin } from "../recovered/contracts/desktop-bridge";
import { PluginsPort0750Extras } from "../recovered/features/plugins/overlay/port-0750-extras";
import { MemoryImportHost } from "../recovered/features/settings/overlay/memory-import-host";
import { VaultCardDialogHost } from "./vault-card-dialog-host";

function desktopBridge(): Pick<DesktopBridge, "agent" | "experiments" | "mcp"> | null {
const desktop = (window as Window & { desktop?: Pick<DesktopBridge, "agent" | "experiments" | "mcp"> }).desktop;
if (desktop?.agent == null || desktop.experiments == null || desktop.mcp == null) return null;
return desktop;
}

function coordinatorFromWindow() {
const port = (window as Window & { coordinatorPort?: CoordinatorPortBridge }).coordinatorPort;
if (port == null || typeof port.claim !== "function") return null;
return createCoordinatorClient(port);
}

/**
* Settings → 通用 no longer mounts anything. The memory-import card that lived here was the
* English 0.75 stopgap; the real import flow is its own Settings tab (memory-import entry),
* and the vault "New card" trigger was a development entry. Both showed up at the bottom of
* 通用. The export stays because the pinned settings chunk references it.
*/
export function RPort0750SettingsPack(): ReactNode {
const [coordinator] = useState(coordinatorFromWindow);
const bridge = desktopBridge();
if (bridge == null) return null;
return (
<>
<MemoryImportHost bridge={bridge} coordinator={coordinator} />
<VaultCardDialogHost />
</>
);
return null;
}

export function RPort0750PluginsPack(): ReactNode {
Expand Down
70 changes: 64 additions & 6 deletions frontend/src/extensions/vault-host-mount.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useEffect, useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import {
createUpstreamVaultStore,
createUpstreamVaultLinguiProvider,
createUpstreamVaultPaneHeaderProvider,
default as UpstreamVaultSettings,
} from "../recovered/features/vault/upstream-vault-settings.generated.mjs";
import { upstreamVaultMessages, type VaultCatalogEntry } from "../recovered/features/vault/upstream-vault-messages.generated.mjs";
import { upstreamVaultMessages0755 } from "../recovered/features/vault/upstream-vault-messages-0755.mjs";
import type { VaultDesktop, VaultCredential } from "../recovered/features/vault/types";
import type { UpstreamVaultStore } from "../recovered/features/vault/upstream-vault-settings.generated.mjs";

Expand All @@ -17,6 +19,7 @@ type WindowWithVault = Window & {
readonly desktop: VaultDesktop;
readonly experiments: { readonly snapshots: { get(): unknown; subscribe(listener: () => void): () => void } };
readonly auth: { readonly snapshots: { get(): unknown; subscribe(listener: () => void): () => void } };
readonly theme: { readonly snapshots: { get(): unknown; subscribe(listener: () => void): () => void } };
};
};

Expand Down Expand Up @@ -53,9 +56,39 @@ export function vaultDesktopFromWindow(candidate: Window | undefined = window):

async function validateCredentialList(value: Awaited<ReturnType<VaultDesktop["listGrokBotVaultCredentials"]>>): Promise<VaultCredential[]> {
if (!Array.isArray(value)) throw new Error("Vault list returned a non-array value");
if (!value.every(isVaultCredential)) throw new Error("Vault list returned an invalid item");
return value;
}

function isString(value: unknown): value is string {
return typeof value === "string";
}

function isVaultAddress(value: unknown): boolean {
return isRecord(value) && ["line1", "line2", "city", "region", "postalCode", "country"].every((key) => isString(value[key]));
}

function isVaultCredential(value: unknown): value is VaultCredential {
if (!isRecord(value)) return false;
if (!["id", "title", "username", "notes"].every((key) => isString(value[key]))
|| !Array.isArray(value.sites) || !value.sites.every(isString)
|| typeof value.createdAtMs !== "number" || typeof value.updatedAtMs !== "number") return false;
if (value.card !== undefined) {
const card = value.card;
if (!isRecord(card)
|| !["brand", "last4", "holderName", "nickname", "billingAddressId"].every((key) => isString(card[key]))
|| typeof card.expMonth !== "number" || typeof card.expYear !== "number"
|| !isVaultAddress(card.billingAddress)) return false;
}
if (value.contact !== undefined) {
const contact = value.contact;
if (!isRecord(contact)
|| !["name", "line1", "line2", "city", "region", "postalCode", "country", "phone", "email"].every((key) => isString(contact[key]))
|| typeof contact.defaultShipping !== "boolean") return false;
}
return true;
}

function constantStore<T>(value: T): { get(): T; subscribe(listener: () => void): () => void } {
return { get: () => value, subscribe: () => () => undefined };
}
Expand Down Expand Up @@ -103,7 +136,12 @@ const vaultI18n = {
: idOrDescriptor;
const merged = values ?? descriptor.values;
const id = descriptor.id ?? "";
const catalogEntry = id.length > 0 ? upstreamVaultMessages[vaultLocale][id] : undefined;
const catalogEntry = id.length > 0
? upstreamVaultMessages0755[vaultLocale][id]
?? upstreamVaultMessages[vaultLocale]?.[id]
?? upstreamVaultMessages0755.en[id]
?? upstreamVaultMessages.en[id]
: undefined;
if (catalogEntry !== undefined) {
const rendered = renderCatalogEntry(catalogEntry, merged);
if (rendered !== null) return rendered;
Expand All @@ -121,8 +159,12 @@ const vaultI18n = {
const UpstreamVaultLinguiProvider = createUpstreamVaultLinguiProvider as unknown as
(props: { readonly i18n: unknown; readonly children?: React.ReactNode }) => React.ReactNode;

const UpstreamVaultPaneHeaderProvider = createUpstreamVaultPaneHeaderProvider as unknown as
(props: { readonly value: HTMLElement | null; readonly children?: React.ReactNode }) => React.ReactNode;

export function RVaultSettingsHost(props: { readonly focusSetting?: string | null }) {
const desktop = useMemo(() => vaultDesktopFromWindow(), []);
const [paneActions, setPaneActions] = useState<HTMLDivElement | null>(null);
const client = useMemo(() => {
const list = async (): Promise<VaultCredential[]> => validateCredentialList(await desktop.listGrokBotVaultCredentials());
const healingDesktop = { ...desktop, heal: () => undefined, listGrokBotVaultCredentials: list };
Expand All @@ -136,6 +178,7 @@ export function RVaultSettingsHost(props: { readonly focusSetting?: string | nul
},
alert: { alert: () => undefined },
auth: { snapshots: constantStore({ status: { kind: "logged-in" } }) },
theme: { snapshots: constantStore({ resolved: "light" }) },
};
(globalThis as typeof globalThis & { __sandVault0721Client?: typeof value }).__sandVault0721Client = value;
(window as WindowWithVault).__sandVault0721Client = value;
Expand All @@ -147,11 +190,26 @@ export function RVaultSettingsHost(props: { readonly focusSetting?: string | nul
return () => client.grokBotVault.dispose();
}, [client]);

useEffect(() => {
const header = document.querySelector<HTMLElement>("header.sand-settings-pane-header");
if (header === null) return;
const target = document.createElement("div");
target.setAttribute("data-sand-vault-actions", "");
target.style.marginInlineStart = "auto";
target.style.marginInlineEnd = "44px";
header.insertBefore(target, header.lastElementChild);
setPaneActions(target);
return () => target.remove();
}, []);

return (
<div id="sand-vault-0721-root">
<UpstreamVaultLinguiProvider i18n={vaultI18n}>
<UpstreamVaultSettings params={{ focusSetting: props.focusSetting ?? null }} />
</UpstreamVaultLinguiProvider>
<div id="sand-vault-0721-root" style={{ position: "relative" }}>
<div ref={setPaneActions} style={{ position: "absolute", top: 28, right: 44, zIndex: 4 }} />
<UpstreamVaultPaneHeaderProvider value={paneActions}>
<UpstreamVaultLinguiProvider i18n={vaultI18n}>
<UpstreamVaultSettings params={{ focusSetting: props.focusSetting ?? null }} />
</UpstreamVaultLinguiProvider>
</UpstreamVaultPaneHeaderProvider>
</div>
);
}
9 changes: 9 additions & 0 deletions frontend/src/recovered/contracts/desktop-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export type DesktopUpdateState =
| { type: "staging"; version: string }
| { type: "ready"; version: string; lastCheck?: { at: number; result: "up-to-date" | "error"; errorMessage?: string } };

export interface DesktopGitHubReleaseInfo {
version: string;
releaseUrl: string;
downloadUrl: string;
}

export interface DesktopUpdateStatus {
state: DesktopUpdateState;
currentVersion: string;
Expand All @@ -63,6 +69,9 @@ export interface DesktopUpdateStatus {
isBelowMinimumVersion: boolean;
autoUpdateWhenIdleOptIn: boolean;
autoUpdateWhenIdleGateEnabled: boolean;
/** Reconstructed GitHub Releases feed metadata (absent until a check resolves an offer). */
releasesUrl?: string;
githubRelease?: DesktopGitHubReleaseInfo | null;
}

export interface DesktopAutoReviewInstructions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,6 @@ export function SettingsDesktopSurface({ bridge, coordinatorClient = null, initi
}),
status: snapshot.egressTunnel.status
}}
computer={computer}
status={snapshot.update}
/>
);
Expand Down
18 changes: 11 additions & 7 deletions frontend/src/recovered/features/settings/overlay/panels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import { useIntl } from "react-intl";
import type { CursorUsageSummary, CursorUsageUpgradeAction, DesktopTimeZoneState, LanguagePreference } from "../../../contracts/desktop-bridge";
import { egressTunnelStatusDescription, type EgressTunnelStatus, type UpdateStatus, type UpdateTrack } from "./updates";
// @evidence src/app/dist/renderer/assets/index-BlqerJhg.js#L1
import { INTERNAL_RELEASE_TRACK_CONFIG_URL, UPDATE_TRACK_LABELS, updateStatusMessage } from "./updates";
import { GITHUB_RELEASES_URL, INTERNAL_RELEASE_TRACK_CONFIG_URL, UPDATE_TRACK_LABELS, updateStatusMessage } from "./updates";
import { AutoReviewRulesPanel, type AutoReviewSettings } from "./auto-review";
import { SettingsComputerPanel } from "./computer-view";
import type { SettingsComputerMount } from "./computer";
import { useAsyncAction } from "../../../runtime/async-action";
import { SandButton } from "../../../ui/sand-kit-primitives";
import { SandSelect } from "../../../ui/sand-floating-primitives";
Expand Down Expand Up @@ -523,7 +521,6 @@ export interface UpdatesSettingsPanelProps {
onInstall?(): void | Promise<unknown>;
onSetTrack(track: UpdateTrack): void | Promise<unknown>;
onSetAutoUpdateWhenIdle(enabled: boolean): void | Promise<unknown>;
computer?: SettingsComputerMount;
egressTunnel?: {
available: boolean;
enabled: boolean;
Expand All @@ -541,7 +538,6 @@ export function UpdatesSettingsPanel({
onInstall,
onSetTrack,
onSetAutoUpdateWhenIdle,
computer,
egressTunnel
}: UpdatesSettingsPanelProps) {
const [checkPending, setCheckPending] = useState(false);
Expand Down Expand Up @@ -572,7 +568,6 @@ export function UpdatesSettingsPanel({
enabled={egressTunnel.enabled}
onChange={egressTunnel.onChange}
/> : null}
{computer ? <SettingsComputerPanel {...computer} /> : null}
</div>
);
}
Expand Down Expand Up @@ -619,9 +614,18 @@ export function UpdatesSettingsPanel({
)}
</div>
<output aria-live="polite" className="sand-settings-beta__status" data-tone={message.tone}>{message.text}</output>
{/* Reconstructed GitHub Releases fallback: Squirrel.Mac auto-apply needs a Developer ID
signature, so ad-hoc builds may stage yet fail to install. The Releases link is the
reliable manual path and stays visible in every state. */}
<div className="sand-settings-beta__releases">
{status.githubRelease?.version != null && (status.state.type === "available" || status.state.type === "downloading" || status.state.type === "staging" || status.state.type === "ready") ? (
<span className="sand-settings-copy"><small>Grok Bot {status.githubRelease.version} is available on <a href={status.githubRelease.releaseUrl} rel="noopener noreferrer" target="_blank">GitHub Releases</a> (<a href={status.githubRelease.downloadUrl} rel="noopener noreferrer" target="_blank">download this version</a>). If auto-install fails, download the zip manually.</small></span>
) : (
<span className="sand-settings-copy"><small>Prefer manual updates? <a href={status.releasesUrl ?? GITHUB_RELEASES_URL} rel="noopener noreferrer" target="_blank">View/download on GitHub Releases</a></small></span>
)}
</div>
</SettingsGroup>
{egressVisible ? <EgressTunnelSettingsGroup available={egressAvailable} description={egressDescription} enabled={egressTunnel.enabled} onChange={egressTunnel.onChange} /> : null}
{computer ? <SettingsComputerPanel {...computer} /> : null}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import type { ReactNode } from "react";
import { TeamsOnboardingHost } from "../../../../extensions/teams-integration-onboarding";
import { VaultCardDialogHost } from "../../../../extensions/vault-card-dialog-host";
import type { DesktopBridge } from "../../../contracts/desktop-bridge";
import type { ProductionCoordinatorClient } from "../../../../production/coordinator-client";
import { MemoryImportHost } from "./memory-import-host";

export function Port0750SettingsExtras(props: {
export function Port0750SettingsExtras(_props: {
readonly bridge: Pick<DesktopBridge, "agent" | "experiments">;
readonly coordinator: Pick<ProductionCoordinatorClient, "call"> | null;
}): ReactNode {
return (
<>
<MemoryImportHost bridge={props.bridge} coordinator={props.coordinator} />
<VaultCardDialogHost />
<TeamsOnboardingHost />
</>
);
return <TeamsOnboardingHost />;
}
16 changes: 16 additions & 0 deletions frontend/src/recovered/features/settings/overlay/updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export const UPDATE_TRACK_LABELS: Readonly<Record<UpdateTrack, string>> = {
export const INTERNAL_RELEASE_TRACK_CONFIG_URL =
"https://console.statsig.com/5oWaLs1Xr8U2ei9Hq2R45w/dynamic_configs/sand_internal_release_track_override";

/**
* Reconstructed-build update feed: this repo's GitHub Releases. Hardcoded (not
* from the update status) so the Settings panel can always offer a manual
* download fallback, even before the first check resolves or when Squirrel.Mac
* staging cannot auto-apply an ad-hoc signature.
*/
export const GITHUB_RELEASES_URL = "https://github.com/tyonger-stack/GrokNode/releases";

export type DisabledUpdateReason =
| "not-packaged"
| "lab-build"
Expand All @@ -33,10 +41,18 @@ export type UpdateState =
| { type: "ready"; version: string; lastCheck?: LastUpdateCheck | null }
| { type: "idle"; lastCheck?: LastUpdateCheck | null };

export interface GitHubReleaseInfo {
version: string;
releaseUrl: string;
downloadUrl: string;
}

export interface UpdateStatus {
state: UpdateState;
currentTrack: UpdateTrack;
currentVersion: string;
releasesUrl?: string;
githubRelease?: GitHubReleaseInfo | null;
isTrackManagedByPolicy?: boolean;
autoUpdateWhenIdleOptIn?: boolean;
autoUpdateWhenIdleGateEnabled?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/recovered/features/vault/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type VaultCard = {
readonly expMonth: number;
readonly expYear: number;
readonly holderName: string;
readonly nickname: string;
readonly billingAddressId: string;
readonly billingAddress: VaultAddress;
};
Expand Down Expand Up @@ -65,6 +66,7 @@ export type VaultDesktop = {
readonly updateGrokBotVaultCard: (input: {
readonly id: string;
readonly holderName: string;
readonly nickname?: string;
readonly expiry: string;
readonly billingAddress: VaultAddress;
readonly billingAddressId: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { VaultCatalogEntry } from "./upstream-vault-messages.generated.mjs";

export declare const upstreamVaultMessages0755: {
readonly en: Readonly<Record<string, VaultCatalogEntry>>;
readonly "zh-CN": Readonly<Record<string, VaultCatalogEntry>>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const upstreamVaultMessages0755 = {
en: {
PgseLK: ["Grok Bot never sees a saved card's number or security code. It can see logins and contact info so it can type them in for you."],
J4YQOg: ["Grok Bot can see logins and contact info so it can type them in for you"],
"5a1TO9": ["Nothing saved yet. Add a login, card, or contact info with New item."],
mx2Qib: ["No logins yet. Add one with New item, or save one from a secure form."],
"1A2dM9": ["No saved cards yet. Add one with New item, or save one from a secure form."],
Fjs6To: ["No contact info yet. Add some with New item, or save it at checkout."],
},
"zh-CN": {
PgseLK: ["Grok Bot 永远看不到已保存卡片的卡号或安全码。它可以查看登录信息和联系信息,以便替你填写。"],
J4YQOg: ["Grok Bot 可以看到登录信息和联系信息,以便替你填写"],
"5a1TO9": ["还没有保存任何内容。通过新项目添加登录信息、卡片或联系信息。"],
mx2Qib: ["还没有登录信息。通过新项目添加一条,或从安全表单中保存一条。"],
"1A2dM9": ["还没有保存的卡片。通过新项目添加一张,或从安全表单中保存一张。"],
Fjs6To: ["还没有联系信息。通过新项目添加,或在结账时保存。"],
},
};
Loading
Loading