Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8a7dfce
feat(onboarding): cancel an import; CSV route for Firefox/Safari pass…
its-janghoon Oct 19, 2026
05ec4d9
feat(agent): send the task's page as redrob_context so engine skills …
its-janghoon Oct 19, 2026
316ad57
feat(onboarding): per-item import progress, Cancel, password CSV row
its-janghoon Oct 19, 2026
5899e6d
feat(memory): file-based MemoryStore with runs, snapshots and revert …
its-janghoon Oct 19, 2026
76eaaab
feat(memory): extraction runs and memory_* tools in TaskStore (CAP-19)
its-janghoon Oct 19, 2026
a01e873
feat(memory): settings page, redrobPrivate memory functions, consolid…
its-janghoon Oct 19, 2026
e24aed8
feat(agent): login tool over the profile password store (CAP-9)
its-janghoon Oct 19, 2026
fd4da84
docs(memory): docs/MEMORY.md, layout and write boundary; accept empty…
its-janghoon Oct 19, 2026
dbe97bf
feat(agent): tasks survive window close, guard confirmations, engine …
its-janghoon Oct 19, 2026
507dc19
feat(omnibox): agent mode in the address bar (CAP-3)
its-janghoon Oct 19, 2026
53645d5
feat(agent): redrobPrivate surface for agent sign-in settings (CAP-9)
its-janghoon Oct 19, 2026
c454c09
feat(redrobPrivate): keep-running toggle, task confirmations, engine …
its-janghoon Oct 19, 2026
2ade8b5
feat(settings): subscriptions from the engine and keep-running toggle…
its-janghoon Oct 19, 2026
0ad646f
feat(settings): Passwords and the agent (CAP-9)
its-janghoon Oct 19, 2026
11f97a4
fix(agent): a steer or cancel answers a pending guard confirmation in…
its-janghoon Oct 19, 2026
b708296
docs(credentials): the agent sign-in boundary in ten lines (CAP-9)
its-janghoon Oct 19, 2026
2cce60e
feat(quick-window): a global quick window for the agent (CAP-5)
its-janghoon Oct 19, 2026
4c16acd
fix(quick-window): bind the portal callback so it can be withdrawn
its-janghoon Oct 19, 2026
156bb70
Merge remote-tracking branch 'origin/feat/parity-skills' into feat/ag…
its-janghoon Oct 19, 2026
e7e2ba4
Merge remote-tracking branch 'origin/feat/parity-memory' into feat/ag…
its-janghoon Oct 19, 2026
9c737c2
Merge remote-tracking branch 'origin/feat/parity-run' into feat/agent…
its-janghoon Oct 19, 2026
a8945cd
Merge remote-tracking branch 'origin/feat/parity-vault' into feat/age…
its-janghoon Oct 19, 2026
e49b81c
Merge remote-tracking branch 'origin/feat/parity-entry' into feat/age…
its-janghoon Oct 19, 2026
5ad121b
fix(passwords): no literal URLs in the add-site copy; accept a bare host
its-janghoon Oct 19, 2026
db4be4b
fix(build): depend on //chrome/common, not its private url_constants …
its-janghoon Oct 19, 2026
154d0a1
fix(memory): StepDetail.source is optional
its-janghoon Oct 19, 2026
38306dd
chore(ui-capture): Firefox and Safari are product names, not untransl…
its-janghoon Oct 19, 2026
b03213c
feat(tabs): new profiles start with tabs across the top
its-janghoon Oct 19, 2026
e8fcd1c
feat(toolbar): workspace switcher and avatar in the toolbar when tabs…
its-janghoon Oct 19, 2026
2315322
fix(toolbar): find the toolbar up the view tree, track it, keep the p…
its-janghoon Oct 19, 2026
5bc0ced
feat(toolbar): Settings button beside the avatar when tabs are horizo…
its-janghoon Oct 19, 2026
d624ffb
feat(toolbar): drop the workspace switcher from the horizontal toolbar
its-janghoon Oct 19, 2026
bb3cfda
feat(bookmarks): show saved tab groups in the bookmark bar by default
its-janghoon Oct 19, 2026
a3311af
chore(ui-capture): baseline counts the mock Firefox profile name (def…
its-janghoon Oct 19, 2026
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
4 changes: 3 additions & 1 deletion agent/apps/app-onboard/src/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const initialOnboardingState: BrowserOSOnboardingState = {
sources: [],
}

/** Maps Chromium importer status into the local three-step onboarding screen state. */
/** Maps Chromium importer status into the local three-step onboarding screen state. A cancelled import returns to the picker. */
export function importPhaseFor(status: BrowserOSImportStatus): ImportPhase {
if (status === 'importing') return 'importing'
if (status === 'failed') return 'failed'
Expand Down Expand Up @@ -174,6 +174,8 @@ export function Onboarding() {
onImport={startImport}
onRefresh={() => bridge.refreshSources()}
onContinue={() => goTo(2)}
onCancel={() => bridge.cancelImport()}
onOpenPasswordManager={() => bridge.openPasswordManager()}
/>
)}
{step === 2 && <SetupAgentStep onSetup={finishOnboarding} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export type BrowserOSImportStatus =
| 'succeeded'
| 'failed'
| 'completed'
/** The user stopped the import; anything already copied stays. */
| 'cancelled'

/** Native owns extension preparation independently of the browser-data importer. */
export type BrowserOSSetupState = 'idle' | 'preparing' | 'failed' | 'ready'
Expand All @@ -33,6 +35,7 @@ export const BrowserOSOnboardingMessage = {
START_IMPORT: 'browserosOnboardingStartImport',
COMPLETE: 'browserosOnboardingComplete',
RETRY_SETUP: 'browserosOnboardingRetrySetup',
CANCEL_IMPORT: 'browserosOnboardingCancelImport',
} as const

export type BrowserOSOnboardingMessage =
Expand All @@ -47,6 +50,10 @@ export interface BrowserOSImportSource {
isManaged: boolean
supportedItems: BrowserOSImportItem[]
recommendedItems: BrowserOSImportItem[]
/** Browser family, e.g. 'firefox', 'safari', 'chromium'. Absent from older native builds. */
kind?: string
/** Saved passwords are not copied from this source; the step offers the CSV export route. */
passwordsViaCsv?: boolean
}

export interface BrowserOSImportProgress {
Expand All @@ -68,6 +75,7 @@ export type BrowserOSImportSourceResultStatus =
| 'importing'
| 'succeeded'
| 'failed'
| 'cancelled'

export interface BrowserOSImportSourceResult {
sourceId: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ export interface BrowserOSOnboardingBridge {
receiveState: (state: BrowserOSOnboardingState) => void,
): () => void
startImport(request: BrowserOSStartImportRequest): void
/** Stops a running import. Items already copied stay in the profile. */
cancelImport(): void
/** Opens the password manager, where an exported CSV is imported. */
openPasswordManager(): void
}

/** Where a CSV exported from another browser is imported. */
export const PASSWORD_MANAGER_URL = 'chrome://password-manager/settings'

interface BrowserOSOnboardingBridgeOptions {
chrome?: BrowserOSOnboardingChrome | null
mockTiming?: 'delayed' | 'sync'
Expand Down Expand Up @@ -75,10 +82,17 @@ function emitMockState(state: BrowserOSOnboardingState) {
getHostWindow()?.browserosOnboarding?.receiveState(state)
}

// Bumped by a mock cancel so progress scheduled for the stopped run is dropped.
let mockImportGeneration = 0

function scheduleMockState(state: BrowserOSOnboardingState, delayMs: number) {
const hostWindow = getHostWindow()
const schedule = hostWindow?.setTimeout ?? globalThis.setTimeout
schedule(() => emitMockState(state), delayMs)
const generation = mockImportGeneration
schedule(() => {
if (generation !== mockImportGeneration) return
emitMockState(state)
}, delayMs)
}

function recommendedItemsFor(request: BrowserOSStartImportRequest) {
Expand Down Expand Up @@ -302,5 +316,30 @@ export function createBrowserOSOnboardingBridge(
}
emitMockImport(request, mockIsSync)
},
cancelImport() {
if (currentState?.status !== 'importing') return
if (!isMock) {
chromeBridge.send(BrowserOSOnboardingMessage.CANCEL_IMPORT)
return
}
mockImportGeneration += 1
const progress = currentState.progress
emitMockState(
createState(
'cancelled',
progress ? { ...progress, currentItem: undefined } : undefined,
currentState.results?.map((result) => ({
...result,
status: 'cancelled' as const,
})),
),
)
},
openPasswordManager() {
// window.open, not navigation: this page must stay where it is. In the
// first-run window Chromium hosts the opened page in its own popup; in a
// tab it opens beside this one.
getHostWindow()?.open(PASSWORD_MANAGER_URL, '_blank')
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,56 @@
*/

import { t } from '@browseros/i18n'
import { RefreshCw } from '@browseros/icons'
import { RefreshCw, X } from '@browseros/icons'
import { Button } from '@/components/ui/button'
import type { BrowserOSImportItem } from '../browseros-onboarding-api'
import { importItemLabel } from '../onboarding-v2.helpers'

interface ImportingProgressCardProps {
currentItemLabel?: string
progress: number
sourceLabel?: string
total: number
/** The items this import was asked for, in checklist order. */
items?: readonly BrowserOSImportItem[]
completedItems?: readonly BrowserOSImportItem[]
currentItem?: BrowserOSImportItem
onCancel?: () => void
}

/** Displays Chromium import progress while selected source data is copied. */
type ItemState = 'done' | 'copying' | 'waiting'

function itemStateFor(
item: BrowserOSImportItem,
completedItems: readonly BrowserOSImportItem[],
currentItem: BrowserOSImportItem | undefined,
): ItemState {
if (completedItems.includes(item)) return 'done'
if (item === currentItem) return 'copying'
return 'waiting'
}

function itemStateLabel(state: ItemState): string {
if (state === 'done') return t('onboardItemStateDone')
if (state === 'copying') return t('onboardItemStateCopying')
return t('onboardItemStateWaiting')
}

/**
* Displays import progress while selected source data is copied: an overall
* bar, then one bar per item type. The importer reports when each item starts
* and ends, not how far through one it is, so an item's bar is empty, moving
* (indeterminate) or full -- never a made-up percentage.
*/
export function ImportingProgressCard({
currentItemLabel,
progress,
sourceLabel,
total,
items = [],
completedItems = [],
currentItem,
onCancel,
}: ImportingProgressCardProps) {
const percent = total === 0 ? 0 : Math.min(100, (progress / total) * 100)
return (
Expand Down Expand Up @@ -51,6 +86,56 @@ export function ImportingProgressCard({
<div className="text-[12.5px] text-ink-2 tabular-nums">
{t('onboardItems', progress, total)}
</div>
{items.length > 0 && (
<ul
aria-label={t('onboardImportProgressByItem')}
className="mt-4 flex flex-col gap-2.5"
>
{items.map((item) => {
const state = itemStateFor(item, completedItems, currentItem)
return (
<li key={item} className="flex items-center gap-3">
<span className="w-32 shrink-0 text-[12.5px] text-ink">
{importItemLabel(item)}
</span>
<div
role="progressbar"
aria-label={importItemLabel(item)}
aria-valuemin={0}
aria-valuemax={1}
aria-valuenow={state === 'done' ? 1 : 0}
aria-valuetext={itemStateLabel(state)}
className="h-1.5 flex-1 overflow-hidden rounded-full bg-bg-sunken"
>
<div
className={
state === 'copying'
? 'h-full w-1/3 animate-pulse rounded-full bg-accent'
: 'h-full bg-accent transition-[width] duration-100'
}
style={
state === 'copying'
? undefined
: { width: state === 'done' ? '100%' : '0%' }
}
/>
</div>
<span className="w-20 shrink-0 text-right text-[12px] text-ink-3">
{itemStateLabel(state)}
</span>
</li>
)
})}
</ul>
)}
{onCancel && (
<div className="mt-4 flex justify-end">
<Button type="button" size="sm" variant="ghost" onClick={onCancel}>
<X className="size-3.5" />
{t('onboardCancelImport')}
</Button>
</div>
)}
</div>
)
}
12 changes: 12 additions & 0 deletions agent/apps/app-onboard/src/onboarding/onboarding-v2.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ export const MOCK_BROWSEROS_IMPORT_SOURCES: readonly BrowserOSImportSource[] = [
supportedItems: ['history', 'bookmarks', 'cookies', 'passwords'],
recommendedItems: ['history', 'bookmarks', 'cookies'],
},
{
id: 'firefox-default',
displayName: 'Firefox - default-release',
browserName: 'Firefox',
profileName: 'default-release',
accountName: '',
isManaged: false,
kind: 'firefox',
passwordsViaCsv: true,
supportedItems: ['history', 'bookmarks', 'autofill'],
recommendedItems: ['history', 'bookmarks', 'autofill'],
},
]

export const DEFAULT_BROWSEROS_IMPORT_SOURCE_ID =
Expand Down
60 changes: 60 additions & 0 deletions agent/apps/app-onboard/src/onboarding/steps/ImportStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ArrowRight,
ChevronDown,
Download,
ExternalLink,
KeyRound,
Lock,
RefreshCw,
} from '@browseros/icons'
Expand Down Expand Up @@ -54,6 +56,44 @@ interface ImportStepProps {
onImport: () => void
onRefresh: () => void
onContinue: () => void
/** Stops a running import. */
onCancel?: () => void
/** Opens the password manager for the CSV route. */
onOpenPasswordManager?: () => void
}

/** Sources whose saved passwords are not copied: say how to bring them anyway. */
function PasswordsViaCsvNotice({
browserName,
onOpenPasswordManager,
}: {
browserName: string
onOpenPasswordManager?: () => void
}) {
return (
<div className="mb-4 flex items-start gap-3 rounded-xl border border-border-2 bg-card p-4">
<KeyRound aria-hidden className="mt-0.5 size-4 shrink-0 text-ink-2" />
<div className="flex-1">
<div className="mb-1 font-bold text-[13px] text-ink">
{t('onboardPasswordsViaCsvTitle')}
</div>
<p className="mb-2.5 text-[12.5px] text-ink-2">
{t('onboardPasswordsViaCsvBody', browserName)}
</p>
{onOpenPasswordManager && (
<Button
type="button"
size="sm"
variant="ghost"
onClick={onOpenPasswordManager}
>
<ExternalLink aria-hidden className="size-3.5" />
{t('onboardOpenPasswordManager')}
</Button>
)}
</div>
</div>
)
}

function importButtonLabelFor(
Expand Down Expand Up @@ -89,6 +129,8 @@ export function ImportStep({
onImport,
onRefresh,
onContinue,
onCancel,
onOpenPasswordManager,
}: ImportStepProps) {
const [permissionOpen, setPermissionOpen] = useState(false)
const [showAllProfiles, setShowAllProfiles] = useState(false)
Expand Down Expand Up @@ -274,6 +316,20 @@ export function ImportStep({
onToggle={toggleImportItem}
/>
)}
{selectedSource?.passwordsViaCsv && (
<PasswordsViaCsvNotice
browserName={selectedSource.browserName}
onOpenPasswordManager={onOpenPasswordManager}
/>
)}
{state.status === 'cancelled' && (
<div
role="status"
className="mb-4 rounded-xl border border-border-2 bg-card p-4 text-[12.5px] text-ink-2"
>
{t('onboardImportCancelled')}
</div>
)}
{state.error && (
<div className="mb-4 rounded-xl border border-amber/30 bg-amber-tint p-4 text-[12.5px] text-ink-2">
{state.error.message}
Expand Down Expand Up @@ -322,6 +378,10 @@ export function ImportStep({
progress={completedItems}
sourceLabel={currentSourceLabel}
total={totalItems}
items={checkedItems}
completedItems={importedItems}
currentItem={state.progress?.currentItem}
onCancel={onCancel}
/>
{needsPermission && <MacKeychainReminder />}
</>
Expand Down
4 changes: 4 additions & 0 deletions agent/apps/app/components/sidebar/SettingsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
Activity,
ArrowLeft,
Bot,
Brain,
KeyRound,
MessageSquare,
Palette,
Server,
Expand Down Expand Up @@ -81,6 +83,8 @@ const primarySettingsSections: NavSection[] = [
to: '/settings/customization',
icon: Palette,
},
{ name: t('stNavMemory'), to: '/settings/memory', icon: Brain },
{ name: t('apwTitle'), to: '/settings/passwords', icon: KeyRound },
{ name: t('stNavRedrobMcp'), to: '/settings/mcp', icon: Server },
],
},
Expand Down
Loading
Loading