');
+
+ expect(mainPatch).toContain('');
+ expect(mainPatch).toContain('
');
+ expect(mainPatch).toContain('');
+ expect(mainPatch).toContain(
+ '
',
+ );
+ expect(mainPatch).not.toContain(" #agent settings-dao-agent-page",
+ );
+ expect(agentTest).toContain(
+ "rendersAsTopLevelAgentSectionWithoutSubpageBackControl",
+ );
+ });
+
+ it("uses immediate positioning for overview menu activation", () => {
+ const menuTs = addedPayload(
+ readPatch("settings_menu/settings_menu.ts.patch"),
+ );
+
+ expect(menuTs).toContain("behavior: 'auto',");
+ expect(menuTs).not.toContain("prefers-reduced-motion");
+ });
+
+ it("keeps Dao Agent Settings localization English-first and complete", () => {
+ const pageSource = AGENT_PAGE_PATCHES.map((relativePath) =>
+ addedPayload(readPatch(relativePath)),
+ ).join("\n");
+ const agentHtml = addedPayload(
+ readPatch("dao_page/dao_agent_page.html.patch"),
+ );
+ const grdp = addedPayload(
+ readDaoSource("src/patches/chrome/app/settings_strings.grdp.patch"),
+ );
+ const provider = addedPayload(
+ readDaoSource(
+ "src/patches/chrome/browser/ui/webui/settings/" +
+ "settings_localized_strings_provider.cc.patch",
+ ),
+ );
+ const zhCn = addedPayload(
+ readDaoSource(
+ "src/patches/chrome/app/resources/" +
+ "generated_resources_zh-CN.xtb.patch",
+ ),
+ );
+
+ expect(pageSource).not.toMatch(/[\u3400-\u9fff]/u);
+
+ const usedMessageIds = new Set(
+ [...pageSource.matchAll(/\$i18n\{(daoAgent[A-Za-z0-9]+)\}/g)].map(
+ (match) => match[1],
+ ),
+ );
+ for (const messageId of usedMessageIds) {
+ const providerMatch = provider.match(
+ new RegExp(
+ `\\{"${messageId}",\\s*(IDS_SETTINGS_DAO_AGENT_[A-Z0-9_]+)\\}`,
+ ),
+ );
+ expect(
+ providerMatch,
+ `${messageId} provider registration`,
+ ).not.toBeNull();
+ expect(grdp, `${messageId} English source`).toContain(
+ `name="${providerMatch![1]}"`,
+ );
+ }
+
+ for (const [
+ messageId,
+ resourceId,
+ english,
+ translationId,
+ ] of TASK5_AGENT_MESSAGES) {
+ expect(pageSource, `${messageId} usage`).toContain(`$i18n{${messageId}}`);
+ expect(provider, `${messageId} provider registration`).toMatch(
+ new RegExp(`\\{"${messageId}",\\s*${resourceId}\\}`),
+ );
+ const messageBlock = grdp.match(
+ new RegExp(`]*>([\\s\\S]*?)`),
+ );
+ expect(messageBlock, `${resourceId} English source`).not.toBeNull();
+ expect(messageBlock![1]).toContain(english);
+ expect(messageBlock![1]).not.toMatch(/[\u3400-\u9fff]/u);
+
+ const translation = zhCn.match(
+ new RegExp(
+ `([\\s\\S]*?)`,
+ ),
+ );
+ expect(translation, `${resourceId} zh-CN translation`).not.toBeNull();
+ expect(translation![1]).toMatch(/[\u3400-\u9fff]/u);
+ }
+
+ for (const [sectionId, headingId, messageId] of [
+ [
+ "modelAndConnection",
+ "modelAndConnectionHeading",
+ "daoAgentGroupModelAndConnection",
+ ],
+ [
+ "behaviorAndContext",
+ "behaviorAndContextHeading",
+ "daoAgentGroupBehaviorAndContext",
+ ],
+ ["capabilities", "capabilitiesHeading", "daoAgentGroupCapabilities"],
+ [
+ "learningAndAnalysis",
+ "learningAndAnalysisHeading",
+ "daoAgentGroupLearningAndAnalysis",
+ ],
+ [
+ "dataAndManagement",
+ "dataAndManagementHeading",
+ "daoAgentGroupDataAndManagement",
+ ],
+ ] as const) {
+ expect(agentHtml, sectionId).toContain(`]*>\\s*\\$i18n\\{${messageId}\\}\\s*`,
+ ),
+ );
+ }
+ });
+
+ it("documents the top-level Agent settings regression contract", () => {
+ const features = readDaoSource("docs/features.md");
+ const checklist = readDaoSource("docs/feature-checklist.md");
+
+ for (const marker of [
+ "`dao://settings/#agent`",
+ "top-level Dao-exclusive Settings section",
+ "all Agent configuration,",
+ "Chromium's shared Settings lazy bundle",
+ "without a duplicate summary or intermediate subpage",
+ ]) {
+ expect(features, marker).toContain(marker);
+ }
+ for (const marker of [
+ "exact inventory",
+ "Chromium's shared Settings lazy bundle",
+ "You and Dao` contains no Agent summary or state",
+ "overview scroll selection",
+ "search filtering",
+ "English-first",
+ "light and dark",
+ "below 760 px",
+ "keyboard",
+ "reduced motion",
+ ]) {
+ expect(checklist, marker).toContain(marker);
+ }
+ });
+
+ it("classifies the Agent proxy as TypeScript and pages as Web Components", () => {
+ const buildPatch = readPatch("BUILD.gn.patch");
+ const hunks = buildPatch
+ .split(/\n(?=@@ )/)
+ .filter((hunk) => hunk.startsWith("@@ "));
+ const proxy = '"dao_page/dao_agent_settings_browser_proxy.ts"';
+ const components = [
+ '"dao_page/dao_agent_page.ts"',
+ '"dao_page/dao_page.ts"',
+ ];
+ const webComponentHunk = hunks.find((hunk) =>
+ hunk.includes(`+ ${components[0]}`),
+ );
+ const tsFilesHunk = hunks.find(
+ (hunk) =>
+ hunk.includes(`+ ${proxy}`) &&
+ !hunk.includes(`+ ${components[0]}`),
+ );
+
+ expect(webComponentHunk, "web_component_files hunk").toBeDefined();
+ expect(tsFilesHunk, "ts_files hunk").toBeDefined();
+ expect(occurrenceCount(addedPayload(buildPatch), proxy)).toBe(1);
+ expect(occurrenceCount(addedPayload(webComponentHunk!), proxy)).toBe(0);
+ expect(occurrenceCount(addedPayload(tsFilesHunk!), proxy)).toBe(1);
+ for (const component of components) {
+ expect(
+ occurrenceCount(addedPayload(webComponentHunk!), component),
+ component,
+ ).toBe(1);
+ expect(
+ occurrenceCount(addedPayload(tsFilesHunk!), component),
+ component,
+ ).toBe(0);
+ }
+ });
+
+ it("keeps the Agent proxy new-file hunk count aligned with its payload", () => {
+ const proxyPath = patchPath(
+ "dao_page/dao_agent_settings_browser_proxy.ts.patch",
+ );
+ const proxyPatch = readFileSync(proxyPath, "utf-8");
+ const hunkHeader = proxyPatch.match(/^@@ -0,0 \+1,(\d+) @@$/m);
+ const payloadCount = proxyPatch
+ .split("\n")
+ .filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
+
+ expect(hunkHeader, "new-file hunk header").not.toBeNull();
+ expect(payloadCount).toBe(177);
+ expect(Number(hunkHeader![1]), "declared new-file line count").toBe(
+ payloadCount,
+ );
+
+ const numstat = execFileSync("git", ["apply", "--numstat", proxyPath], {
+ cwd: process.cwd(),
+ encoding: "utf-8",
+ });
+ const appliedCount = Number(numstat.split("\t", 1)[0]);
+ expect(appliedCount, "git apply added line count").toBe(payloadCount);
+ });
+
+ it("keeps active Dao and Agent new-file patch payload counts exact", () => {
+ for (const relativePath of [
+ "dao_page/dao_page.html.patch",
+ "dao_page/dao_page.ts.patch",
+ "dao_page/dao_agent_page.html.patch",
+ "dao_page/dao_agent_page.ts.patch",
+ "../../../test/data/webui/settings/dao_page_test.ts.patch",
+ "../../../test/data/webui/settings/dao_agent_page_test.ts.patch",
+ ]) {
+ const patch = readPatch(relativePath);
+ const hunkHeader = patch.match(/^@@ -0,0 \+1,(\d+) @@$/m);
+ const payloadCount = patch
+ .split("\n")
+ .filter(
+ (line) => line.startsWith("+") && !line.startsWith("+++"),
+ ).length;
+ expect(hunkHeader, relativePath).not.toBeNull();
+ expect(Number(hunkHeader![1]), relativePath).toBe(payloadCount);
+ }
+ });
+
+ it("wires each Dao page WebUI test target to its disabled browser runner", () => {
+ const buildPatch = readDaoSource(
+ "src/patches/chrome/test/data/webui/settings/BUILD.gn.patch",
+ );
+ const runnerPatch = readDaoSource(
+ "src/patches/chrome/test/data/webui/settings/" +
+ "settings_browsertest.cc.patch",
+ );
+ const buildPayload = addedPayload(buildPatch);
+ const runnerPayload = addedPayload(runnerPatch);
+
+ const testTargets = [
+ ["dao_page_test", "DaoPage"],
+ ["dao_agent_page_test", "DaoAgentPage"],
+ ] as const;
+ for (const [source, runner] of testTargets) {
+ expect(occurrenceCount(buildPayload, `"${source}.ts"`), source).toBe(1);
+ expect(
+ occurrenceCount(
+ runnerPayload,
+ `IN_PROC_BROWSER_TEST_F(SettingsTest, DISABLED_${runner})`,
+ ),
+ runner,
+ ).toBe(1);
+ expect(
+ occurrenceCount(
+ runnerPayload,
+ `RunTest("settings/${source}.js", "mocha.run()")`,
+ ),
+ source,
+ ).toBe(1);
+ }
+ expect(
+ occurrenceCount(
+ runnerPayload,
+ "IN_PROC_BROWSER_TEST_F(SettingsTest, DISABLED_Dao",
+ ),
+ ).toBe(2);
+ expect(runnerPayload).not.toMatch(
+ /IN_PROC_BROWSER_TEST_F\(SettingsTest, Dao(?:Agent)?Page(?:Index)?\)/,
+ );
+ });
+
+ it("preserves the complete Agent settings inventory and shared proxy boundary", () => {
+ const agentProxy = "dao_page/dao_agent_settings_browser_proxy.ts.patch";
+ const agentTs = "dao_page/dao_agent_page.ts.patch";
+ const agentHtml = "dao_page/dao_agent_page.html.patch";
+ const agentTest =
+ "src/patches/chrome/test/data/webui/settings/" +
+ "dao_agent_page_test.ts.patch";
+
+ expect(existsSync(patchPath(agentProxy)), agentProxy).toBe(true);
+ expect(existsSync(patchPath(agentTs)), agentTs).toBe(true);
+ expect(existsSync(patchPath(agentHtml)), agentHtml).toBe(true);
+ expect(existsSync(path.join(process.cwd(), agentTest)), agentTest).toBe(
+ true,
+ );
+
+ const overviewHtml = addedPayload(
+ readPatch("dao_page/dao_page.html.patch"),
+ );
+ const agentHtmlPatch = addedPayload(readPatch(agentHtml));
+ const overviewTs = addedPayload(readPatch("dao_page/dao_page.ts.patch"));
+ const agentTsPatch = addedPayload(readPatch(agentTs));
+ const overviewSource = overviewHtml + "\n" + overviewTs;
+ const agentSource = agentHtmlPatch + "\n" + agentTsPatch;
+ const combinedSource = overviewSource + "\n" + agentSource;
+
+ for (const [key, pattern] of AGENT_SETTING_OWNERSHIP_MARKERS) {
+ expect(
+ occurrenceCount(combinedSource, pattern),
+ `${key} combined owner count`,
+ ).toBe(1);
+ expect(
+ occurrenceCount(agentSource, pattern),
+ `${key} subpage owner`,
+ ).toBe(1);
+ expect(occurrenceCount(overviewSource, pattern), `${key} overview`).toBe(
+ 0,
+ );
+ }
+
+ for (const id of AGENT_CONTROL_AND_ACTION_IDS) {
+ const marker = `id="${id}"`;
+ expect(
+ occurrenceCount(combinedSource, marker),
+ `${id} combined owner`,
+ ).toBe(1);
+ expect(occurrenceCount(agentSource, marker), `${id} subpage owner`).toBe(
+ 1,
+ );
+ expect(occurrenceCount(overviewSource, marker), `${id} overview`).toBe(0);
+ }
+
+ const proxyPayload = addedPayload(readPatch(agentProxy));
+ const toolGroups = extractBetween(
+ proxyPayload,
+ "export const AGENT_TOOL_GROUPS",
+ "export const AGENT_PROVIDER_DEFAULTS",
+ );
+ for (const group of AGENT_TOOL_GROUP_NAMES) {
+ const controlMarker = `data-tool-group="${group}"`;
+ expect(
+ occurrenceCount(overviewHtml + "\n" + agentHtmlPatch, controlMarker),
+ `${group} tool-group control`,
+ ).toBe(1);
+ expect(occurrenceCount(agentHtmlPatch, controlMarker)).toBe(1);
+ expect(occurrenceCount(overviewHtml, controlMarker)).toBe(0);
+ expect(
+ occurrenceCount(toolGroups, new RegExp(`^\\s*${group}:`, "gm")),
+ ).toBe(1);
+ }
+ expect(occurrenceCount(combinedSource, 'data-tool-name$="[[tool]]"')).toBe(
+ 1,
+ );
+ expect(occurrenceCount(agentSource, 'data-tool-name$="[[tool]]"')).toBe(1);
+ expect(overviewSource).not.toContain("data-tool-name");
+
+ for (const handler of AGENT_CRITICAL_HANDLERS) {
+ const definition = new RegExp(
+ `private (?:async )?${handler}\\s*\\(`,
+ "g",
+ );
+ expect(
+ occurrenceCount(combinedSource, definition),
+ `${handler} combined owner`,
+ ).toBe(1);
+ expect(
+ occurrenceCount(agentSource, definition),
+ `${handler} subpage owner`,
+ ).toBe(1);
+ expect(
+ occurrenceCount(overviewSource, definition),
+ `${handler} overview`,
+ ).toBe(0);
+ }
+
+ for (const sectionId of [
+ "modelAndConnection",
+ "behaviorAndContext",
+ "capabilities",
+ "learningAndAnalysis",
+ "dataAndManagement",
+ ]) {
+ expect(occurrenceCount(combinedSource, `id="${sectionId}"`)).toBe(1);
+ expect(occurrenceCount(agentSource, `id="${sectionId}"`)).toBe(1);
+ expect(occurrenceCount(overviewSource, `id="${sectionId}"`)).toBe(0);
+ }
+ expect(agentHtmlPatch).toContain("/);
+ expect(agentHtmlPatch).toMatch(
+ /;",
+ "setSetting(key: string, value: string|null): Promise;",
+ "getMemorySummary(): Promise;",
+ "clearAllMemory(): Promise;",
+ "getWorkspaceSummary(): Promise;",
+ "openWorkspace(): Promise;",
+ "getUsageStats(): Promise;",
+ "resetUsageStats(): Promise;",
+ ]) {
+ expect(proxyPatch).toContain(token);
+ }
+ });
+
+ it("serializes complete optimistic disabled-tool writes", () => {
+ const tsPatch = addedPayload(readPatch("dao_page/dao_agent_page.ts.patch"));
+ const webUiTestPatch = readDaoSource(
+ "src/patches/chrome/test/data/webui/settings/" +
+ "dao_agent_page_test.ts.patch",
+ );
+ const queueMethod = extractBetween(
+ tsPatch,
+ "private queueDisabledToolsWrite_()",
+ "private async setAgentSetting_",
+ );
+
+ expect(queueMethod).toMatch(
+ /const value = this\.serializeDisabledTools_\(\);[\s\S]*?const generation = \+\+this\.disabledToolsMutationGeneration_;[\s\S]*?\+\+this\.disabledToolsWritesPending_;[\s\S]*?this\.preserveOptimisticDisabledTools_ = true;[\s\S]*?dao_disabled_tools: value/,
+ );
+ expect(queueMethod).toMatch(
+ /this\.disabledToolsWritePromise_ =\s*this\.disabledToolsWritePromise_\.then\(async \(\) => \{[\s\S]*?setSetting\(\s*'dao_disabled_tools', value\)[\s\S]*?--this\.disabledToolsWritesPending_;[\s\S]*?if \(this\.disabledToolsWritesPending_ !== 0\) \{\s*return;/,
+ );
+ expect(queueMethod).toMatch(
+ /await this\.agentSettingsBrowserProxy_\.getSettings\(\)[\s\S]*?generation === this\.disabledToolsMutationGeneration_[\s\S]*?this\.disabledToolsWritesPending_ === 0[\s\S]*?this\.preserveOptimisticDisabledTools_ = false;[\s\S]*?this\.updateAgentSettings_\(snapshot, true\)/,
+ );
+ expect(
+ occurrenceCount(
+ addedPayload(webUiTestPatch),
+ "test('concurrentToolTogglesPersistCompleteDisabledArray'",
+ ),
+ ).toBe(1);
+ });
+
+ it("normalizes partial legacy usage without weakening stored snapshots", () => {
+ const handler = readDaoSource(
+ "src/dao/browser/agent/dao_agent_settings_handler.cc",
+ );
+ const handlerTest = readDaoSource(
+ "src/dao/browser/agent/dao_agent_settings_handler_unittest.cc",
+ );
+
+ expect(handler).toContain("NormalizeLegacyUsageStats");
+ expect(handlerTest).toContain("MigratesPartialLegacyUsageStats");
+ expect(handlerTest).toContain("RejectsMalformedPartialLegacyUsageStats");
+ expect(handlerTest).toContain(
+ "CanonicalUsageStatsStillRequireCompleteSchema",
+ );
+ });
+
+ it("restores the Agent resume window default and zero-hour minimum", () => {
+ const htmlPatch = readPatch("dao_page/dao_agent_page.html.patch");
+ const tsPatch = readPatch("dao_page/dao_agent_page.ts.patch");
+ const webUiTestPatch = readDaoSource(
+ "src/patches/chrome/test/data/webui/settings/" +
+ "dao_agent_page_test.ts.patch",
+ );
+
+ expect(htmlPatch).toMatch(
+ /id="daoAgentResumeHours"[^>]*type="number" min="0"/,
+ );
+ expect(tsPatch).toContain(
+ "agentResumeStaleHours_: {type: String, value: '3'}",
+ );
+ expect(tsPatch).toContain(
+ "snapshot.values['dao_resume_stale_hours'] || '3'",
+ );
+ expect(webUiTestPatch).toContain(
+ "agentResumeWindowDefaultsToThreeAndAllowsZero",
+ );
+ });
+
+ it("isolates configuration failure from the management cards", () => {
+ const htmlPatch = readPatch("dao_page/dao_agent_page.html.patch");
+ const tsPatch = readPatch("dao_page/dao_agent_page.ts.patch");
+ const webUiTestPatch = readDaoSource(
+ "src/patches/chrome/test/data/webui/settings/" +
+ "dao_agent_page_test.ts.patch",
+ );
+ const managementStart = htmlPatch.indexOf(
+ '",
+ managementStart,
+ );
+
+ expect(tsPatch).toContain("agentSettingsError_");
+ expect(tsPatch).toContain("loadAgentSettings_");
+ expect(htmlPatch).toContain('id="agentSettingsRetry"');
+ expect(managementStart).toBeGreaterThanOrEqual(0);
+ expect(settingsGateEnd).toBeLessThan(managementStart);
+ expect(webUiTestPatch).toContain(
+ "configurationFailureKeepsManagementCardsUsable",
+ );
+ });
+
+ it("keeps nested top-level page views in overview document flow", () => {
+ const nestedPagePatches = [
+ "people_page/people_page_index.html.patch",
+ "autofill_page/autofill_page_index.html.patch",
+ "your_saved_info_page/your_saved_info_page_index.html.patch",
+ "appearance_page/appearance_page_index.html.patch",
+ "a11y_page/a11y_page_index.html.patch",
+ ];
+
+ for (const relativePath of nestedPagePatches) {
+ expect(existsSync(patchPath(relativePath)), relativePath).toBe(true);
+ expect(readPatch(relativePath), relativePath).toContain(
+ "[slot=view]:not(.closing)",
+ );
+ expect(readPatch(relativePath), relativePath).toContain(
+ "position: initial",
+ );
+ }
+ });
+
+ it("keeps usage-statistic mutations inside their scoped pref updates", () => {
+ const handler = readDaoSource(
+ "src/dao/browser/agent/dao_agent_settings_handler.cc",
+ );
+ const api_usage = handler.match(
+ /void RecordDaoAgentApiUsage[\s\S]*?\n}\n\nvoid RecordDaoAgentToolUsage/,
+ )?.[0];
+ const tool_usage = handler.match(
+ /void RecordDaoAgentToolUsage[\s\S]*?\n}\n\nvoid ResetDaoAgentUsageStats/,
+ )?.[0];
+
+ expect(api_usage).toBeDefined();
+ expect(tool_usage).toBeDefined();
+ expect(api_usage).not.toContain("ReadUsageStatsOrDefault");
+ expect(tool_usage).not.toContain("ReadUsageStatsOrDefault");
+ expect(api_usage).toMatch(
+ /ScopedDictPrefUpdate update[\s\S]*?update->FindDouble/,
+ );
+ expect(tool_usage).toMatch(
+ /ScopedDictPrefUpdate update[\s\S]*?update->FindDict/,
+ );
+ });
+
+ it("exposes only the restricted Agent management surface", () => {
+ const handler = readDaoSource(
+ "src/dao/browser/agent/dao_agent_settings_handler.cc",
+ );
+ for (const message of [
+ "getDaoAgentMemorySummary",
+ "clearAllDaoAgentMemory",
+ "getDaoAgentWorkspaceSummary",
+ "openDaoAgentWorkspace",
+ "getDaoAgentUsageStats",
+ "resetDaoAgentUsageStats",
+ ]) {
+ expect(handler).toContain(`"${message}"`);
+ }
+ for (const forbidden of [
+ "workspaceRead",
+ "workspaceWrite",
+ "workspaceEdit",
+ "workspaceApplyPatch",
+ "workspaceList",
+ "workspaceDownload",
+ ]) {
+ expect(handler).not.toContain(`"${forbidden}"`);
+ }
+ });
+
+ it("renders the complete Agent management ledger without dropping links", () => {
+ const htmlPatch = readPatch("dao_page/dao_agent_page.html.patch");
+ const tsPatch = readPatch("dao_page/dao_agent_page.ts.patch");
+ const managementSource = tsPatch + htmlPatch;
+
+ for (const token of [
+ "conversationCount",
+ "preferenceCount",
+ "episodeCount",
+ "totalSize",
+ "usedBytes",
+ "capBytes",
+ "fileCount",
+ "recentActivity",
+ "apiCalls",
+ "toolCalls",
+ "promptTokens",
+ "completionTokens",
+ "totalTokens",
+ "estimatedCost",
+ "clearAllMemory",
+ "openWorkspace",
+ "resetUsageStats",
+ 'href="dao://skills"',
+ 'href="dao://memory"',
+ 'href="dao://dream"',
+ ]) {
+ expect(managementSource).toContain(token);
+ }
+
+ for (const id of [
+ "daoAgentMemoryManagement",
+ "daoAgentWorkspaceManagement",
+ "daoAgentUsageManagement",
+ "clearAllMemoryDialog",
+ "resetUsageStatsDialog",
+ ]) {
+ expect(htmlPatch).toContain(`id="${id}"`);
+ }
+
+ const memoryCard = extractBetween(
+ htmlPatch,
+ 'id="daoAgentMemoryManagement"',
+ 'id="daoAgentWorkspaceManagement"',
+ );
+ const workspaceCard = extractBetween(
+ htmlPatch,
+ 'id="daoAgentWorkspaceManagement"',
+ 'id="daoAgentUsageManagement"',
+ );
+ const usageCard = extractBetween(
+ htmlPatch,
+ 'id="daoAgentUsageManagement"',
+ 'id="daoAgentClearMemoryDialog"',
+ );
+ const clearDialog = extractBetween(
+ htmlPatch,
+ 'id="clearAllMemoryDialog"',
+ 'id="daoAgentResetUsageDialog"',
+ );
+ const resetDialog = extractBetween(
+ htmlPatch,
+ 'id="resetUsageStatsDialog"',
+ "",
+ );
+
+ expect(memoryCard).toMatch(
+ /if="\[\[showSummaryLoadError_\(memoryError_, memoryActionSucceeded_\)\]\]"[\s\S]*?id="memoryLoadError"[\s\S]*?id="agentMemoryRetry"[\s\S]*?on-click="onRetryMemory_"/,
+ );
+ expect(memoryCard).toMatch(
+ /if="\[\[showSummaryRefreshError_\(memoryError_, memoryActionSucceeded_\)\]\]"[\s\S]*?id="memoryRefreshError"[\s\S]*?daoAgentManagementMemoryRefreshError[\s\S]*?id="agentMemoryRefreshRetry"/,
+ );
+ expect(workspaceCard).toMatch(
+ /if="\[\[workspaceError_\]\]"[\s\S]*?id="agentWorkspaceRetry"[\s\S]*?on-click="onRetryWorkspace_"/,
+ );
+ expect(usageCard).toMatch(
+ /if="\[\[showSummaryLoadError_\(usageError_, usageActionSucceeded_\)\]\]"[\s\S]*?id="usageLoadError"[\s\S]*?id="agentUsageRetry"[\s\S]*?on-click="onRetryUsage_"/,
+ );
+ expect(usageCard).toMatch(
+ /if="\[\[showSummaryRefreshError_\(usageError_, usageActionSucceeded_\)\]\]"[\s\S]*?id="usageRefreshError"[\s\S]*?daoAgentManagementUsageRefreshError[\s\S]*?id="agentUsageRefreshRetry"/,
+ );
+ expect(memoryCard).toContain('if="[[memoryActionError_]]"');
+ expect(memoryCard).toContain("$i18n{daoAgentManagementMemoryRefreshError}");
+ expect(workspaceCard).toContain('if="[[workspaceActionError_]]"');
+ expect(usageCard).toContain('if="[[usageActionError_]]"');
+ expect(usageCard).toContain("$i18n{daoAgentManagementUsageRefreshError}");
+
+ expect(clearDialog).toMatch(
+ /on-close="onClearMemoryDialogClose_"[\s\S]*?id="clearAllMemoryConfirm"[\s\S]*?disabled="\[\[clearMemoryPending_\]\]"[\s\S]*?on-click="onConfirmClearMemory_"/,
+ );
+ expect(resetDialog).toMatch(
+ /on-close="onResetUsageDialogClose_"[\s\S]*?id="resetUsageStatsConfirm"[\s\S]*?disabled="\[\[resetUsagePending_\]\]"[\s\S]*?on-click="onConfirmResetUsage_"/,
+ );
+
+ expect(memoryCard.match(/dao-agent-management-loading-row/g)).toHaveLength(
+ 4,
+ );
+ expect(
+ workspaceCard.match(/dao-agent-management-loading-row/g),
+ ).toHaveLength(3);
+ expect(usageCard.match(/dao-agent-management-loading-row/g)).toHaveLength(
+ 7,
+ );
+ expect(htmlPatch).toMatch(
+ /\.dao-agent-management-state \{[\s\S]*?min-height: 62px;/,
+ );
+ expect(htmlPatch).toMatch(
+ /@media \(max-width: 760px\)[\s\S]*?\.dao-agent-management-state\.error \{[\s\S]*?flex-direction: column;/,
+ );
+
+ expect(workspaceCard).toMatch(
+ /aria-label\$="\[\[formatActivityLabel_\(item\.operation, item\.path\)\]\]"/,
+ );
+ expect(workspaceCard).toContain('datetime$="[[item.timestamp]]"');
+ expect(workspaceCard).not.toContain("[[item.operation]] · [[item.path]]");
+
+ expect(htmlPatch).toContain('');
+ expect(htmlPatch).toContain('');
+ expect(htmlPatch).toContain('
-
${this.toastVisible_ ?
html`${this.toastText_}
` : ''}
`;
@@ -296,6 +291,11 @@ export class DaoAgentApp extends CrLitElement {
}
}
+ private openUnifiedSettings_() {
+ void callNative('openTab', {url: 'dao://settings/#dao'});
+ chrome.send('closeSidebar');
+ }
+
private onNewChatClick_() {
if (this.activeTab_ !== 'chat') {
this.activeTab_ = 'chat';
@@ -381,9 +381,6 @@ export class DaoAgentApp extends CrLitElement {
return (this.shadowRoot ?? this).querySelector('dao-chat-view');
}
- private getSettingsView_(): DaoSettingsView|null {
- return (this.shadowRoot ?? this).querySelector('dao-settings-view');
- }
private refreshLocalizedViews_() {
this.requestUpdate();
diff --git a/src/dao/browser/ui/webui/resources/agent/dao_settings_view.ts b/src/dao/browser/ui/webui/resources/agent/dao_settings_view.ts
deleted file mode 100644
index 2448011a..00000000
--- a/src/dao/browser/ui/webui/resources/agent/dao_settings_view.ts
+++ /dev/null
@@ -1,1696 +0,0 @@
-// Copyright 2026 Dao Browser Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {CrLitElement, css, html, nothing} from
- '//resources/lit/v3_0/lit.rollup.js';
-
-import {
- callNative,
- callNativeArgs,
- CONFIDENCE_THRESHOLD_MAP,
- currentSoulContent,
- DEFAULT_SOUL,
- getAgentToolDefinitions,
- getAgentStats,
- refreshSoulContent,
- resetAgentStats,
- saveSoul,
- soulChannel,
-} from './agent_bridge.js';
-import {t} from './i18n/i18n.js';
-
-// Reply shape from C++ workspaceGetInfo (DaoAgentWorkspaceHandler).
-interface WorkspaceInfo {
- root: string; // absolute path
- used_bytes: number;
- cap_bytes: number;
- file_count: number;
- file_count_cap: number;
-}
-
-// Reply shape from C++ workspaceGetRecentActivity. `ts` is an ISO-8601 string
-// produced by base::TimeFormatAsIso8601. `op` is one of 'write' | 'edit' |
-// 'apply_patch'.
-interface WorkspaceAuditEntry {
- ts: string;
- op: string;
- path: string;
-}
-
-const DREAM_RUN_NATIVE_TIMEOUT_MS = 6 * 60 * 1000;
-
-import type {AgentStats, ToolDefinition} from './agent_bridge.js';
-import {initializeBrowserToolCatalog} from './browser_tool_catalog.js';
-import {
- getActiveProvider,
- getProviderConfig,
- LLM_PROVIDERS,
- setActiveProvider,
- setProviderConfig,
-} from './llm_config.js';
-import type {ProviderSpec} from './llm_config.js';
-import {
- countEnabled,
- getGroupState,
- isGroupExpanded,
- isToolEnabled,
- setGroupEnabled,
- setGroupExpanded,
- setToolEnabled,
- TOOL_GROUPS,
- toolConfigChannel,
-} from './tool_catalog.js';
-import {
- getJinaApiKey,
- getSearchSourceOverride,
- setJinaApiKey,
- setSearchSourceOverride,
-}
- from './web_search/index.js';
-import type {SearchSourceOverride} from './web_search/index.js';
-
-const DAO_AGENT_DEBUG_MODE_KEY = 'dao_agent_debug_mode';
-const DAO_AGENT_DEBUG_MODE_CHANGED_EVENT = 'dao-agent-debug-mode-changed';
-const DAO_PROACTIVE_ENABLED_KEY = 'dao_proactive_enabled';
-const DAO_PROACTIVE_ENABLED_CHANGED_EVENT =
- 'dao-proactive-enabled-changed';
-
-export class DaoSettingsView extends CrLitElement {
- static override get properties() {
- return {
- activeSubTab_: {type: String, state: true},
- provider_: {type: String, state: true},
- apiKey_: {type: String, state: true},
- jinaApiKey_: {type: String, state: true},
- baseUrl_: {type: String, state: true},
- model_: {type: String, state: true},
- soulText_: {type: String, state: true},
- saveStatusText_: {type: String, state: true},
- saveStatusVisible_: {type: Boolean, state: true},
- memoryEnabled_: {type: Boolean, state: true},
- proactiveEnabled_: {type: Boolean, state: true},
- dreamEnabled_: {type: Boolean, state: true},
- dreamDebug_: {type: Boolean, state: true},
- dreamRunning_: {type: Boolean, state: true},
- dreamExcludedDomains_: {type: Array, state: true},
- dreamExcludedDomainInput_: {type: String, state: true},
- pageContextEnabled_: {type: Boolean, state: true},
- conversationEnabled_: {type: Boolean, state: true},
- threshold_: {type: String, state: true},
- statConversations_: {type: Number, state: true},
- statPreferences_: {type: Number, state: true},
- statEpisodes_: {type: Number, state: true},
- statTotal_: {type: String, state: true},
- showConfirmDialog_: {type: Boolean, state: true},
- agentStats_: {type: Object, state: true},
- showResetStatsDialog_: {type: Boolean, state: true},
- toolCallShowDetails_: {type: Boolean, state: true},
- debugMode_: {type: Boolean, state: true},
- resumeLastSession_: {type: Boolean, state: true},
- resumeStaleHours_: {type: Number, state: true},
- workspaceInfo_: {type: Object, state: true},
- workspaceActivity_: {type: Array, state: true},
- toolDefinitions_: {type: Array, state: true},
- toolCatalogLoadFailed_: {type: Boolean, state: true},
- };
- }
-
- declare private activeSubTab_: string;
- declare private provider_: string;
- declare private apiKey_: string;
- declare private jinaApiKey_: string;
- declare private baseUrl_: string;
- declare private model_: string;
- declare private soulText_: string;
- declare private saveStatusText_: string;
- declare private saveStatusVisible_: boolean;
- declare private memoryEnabled_: boolean;
- declare private proactiveEnabled_: boolean;
- declare private dreamEnabled_: boolean;
- declare private dreamDebug_: boolean;
- declare private dreamRunning_: boolean;
- declare private dreamExcludedDomains_: string[];
- declare private dreamExcludedDomainInput_: string;
- declare private pageContextEnabled_: boolean;
- declare private conversationEnabled_: boolean;
- declare private threshold_: string;
- declare private statConversations_: number;
- declare private statPreferences_: number;
- declare private statEpisodes_: number;
- declare private statTotal_: string;
- declare private showConfirmDialog_: boolean;
- private saveStatusTimer_ = 0;
- declare private agentStats_: AgentStats|null;
- declare private showResetStatsDialog_: boolean;
- declare private toolCallShowDetails_: boolean;
- declare private debugMode_: boolean;
- declare private resumeLastSession_: boolean;
- declare private resumeStaleHours_: number;
- declare private workspaceInfo_: WorkspaceInfo|null;
- declare private workspaceActivity_: WorkspaceAuditEntry[]|null;
- declare private toolDefinitions_: ToolDefinition[]|null;
- declare private toolCatalogLoadFailed_: boolean;
-
- static override get styles() {
- return css`
- :host {
- display: flex; flex-direction: column;
- flex: 1; overflow: hidden;
- }
- :host([hidden]) { display: none !important; }
-
- .settings-sub-tabs {
- display: flex; gap: 2px; padding: 6px 14px 0;
- border-bottom: 1px solid rgba(255,255,255,0.15); flex-shrink: 0;
- }
- .sub-tab {
- background: none; border: none;
- border-bottom: 2px solid transparent;
- padding: 6px 10px; font-size: 12px; font-family: inherit;
- color: var(--text-tertiary); cursor: pointer;
- transition: color 0.15s, border-color 0.15s;
- }
- .sub-tab:hover { color: var(--text-secondary); }
- .sub-tab.active {
- color: var(--text); border-bottom-color: var(--accent);
- }
-
- .panel {
- flex: 1; overflow-y: auto; overflow-x: hidden; padding: 14px;
- }
- .panel::-webkit-scrollbar { width: 4px; }
- .panel::-webkit-scrollbar-track { background: transparent; }
- .panel::-webkit-scrollbar-thumb {
- background: rgba(0,0,0,0.12); border-radius: 4px;
- }
- .panel::-webkit-scrollbar-thumb:hover {
- background: rgba(0,0,0,0.2);
- }
-
- @media (prefers-color-scheme: dark) {
- .panel::-webkit-scrollbar-thumb {
- background: rgba(255,255,255,0.18);
- }
- .panel::-webkit-scrollbar-thumb:hover {
- background: rgba(255,255,255,0.30);
- }
- }
-
- .section-title {
- font-size: 14px; font-weight: 600;
- color: var(--text); margin-bottom: 4px;
- }
- .section-desc {
- font-size: 12px; color: var(--text-tertiary);
- margin-bottom: 12px; line-height: 1.4;
- }
-
- /* Connection inputs */
- label {
- display: block; font-size: 11px;
- color: var(--text-tertiary);
- margin-bottom: 3px; margin-top: 10px;
- }
- label:first-of-type { margin-top: 0; }
- input {
- width: 100%; padding: 7px 10px;
- box-sizing: border-box;
- background: var(--glass); border: 1px solid var(--glass-border);
- border-radius: 10px; color: var(--text);
- font-size: 12px; outline: none;
- box-shadow: var(--shadow-sm);
- transition: border-color 0.15s, box-shadow 0.15s;
- }
- input:focus {
- border-color: rgba(70, 120, 190, 0.4);
- box-shadow: 0 0 0 3px rgba(70, 120, 190, 0.08);
- }
- select {
- width: 100%; padding: 7px 10px;
- box-sizing: border-box;
- background: var(--glass); border: 1px solid var(--glass-border);
- border-radius: 10px; color: var(--text);
- font-size: 12px; font-family: inherit; outline: none;
- box-shadow: var(--shadow-sm);
- appearance: none;
- background-image: url("data:image/svg+xml;utf8,");
- background-repeat: no-repeat;
- background-position: right 10px center;
- padding-right: 28px;
- transition: border-color 0.15s, box-shadow 0.15s;
- }
- select:focus {
- border-color: rgba(70, 120, 190, 0.4);
- box-shadow: 0 0 0 3px rgba(70, 120, 190, 0.08);
- }
- select option {
- background: var(--glass-strong, #2a2434); color: var(--text);
- }
-
- /* Soul editor */
- .soul-editor {
- width: 100%; min-height: 300px; padding: 10px 12px;
- box-sizing: border-box;
- background: var(--glass); border: 1px solid var(--glass-border);
- border-radius: 10px; color: var(--text);
- font-family: ui-monospace, 'SF Mono', Menlo, Monaco, monospace;
- font-size: 12px; line-height: 1.5;
- resize: vertical; outline: none;
- box-shadow: var(--shadow-sm);
- transition: border-color 0.15s, box-shadow 0.15s;
- }
- .soul-editor:focus {
- border-color: rgba(70, 120, 190, 0.4);
- box-shadow: 0 0 0 3px rgba(70, 120, 190, 0.08);
- }
- .soul-editor::placeholder { color: var(--text-tertiary); }
- .soul-actions {
- display: flex; align-items: center; gap: 8px; margin-top: 10px;
- }
- .dream-exclusions {
- margin: 12px 0 4px;
- padding-bottom: 10px;
- border-bottom: 1px solid var(--border);
- }
- .dream-exclusion-input-row {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 8px;
- align-items: center;
- }
- .dream-exclusion-list {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-top: 8px;
- }
- .dream-exclusion-chip {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- min-width: 0;
- max-width: 100%;
- padding: 4px 6px 4px 9px;
- border: 1px solid var(--glass-border);
- border-radius: 8px;
- background: var(--glass);
- color: var(--text-secondary);
- font-size: 12px;
- }
- .dream-exclusion-chip span {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .dream-exclusion-chip button {
- width: 18px;
- height: 18px;
- padding: 0;
- border: 0;
- border-radius: 6px;
- background: transparent;
- color: var(--text-tertiary);
- cursor: pointer;
- font: inherit;
- line-height: 18px;
- }
- .dream-exclusion-chip button:hover {
- background: var(--glass-strong);
- color: var(--text);
- }
- .btn-primary {
- padding: 7px 18px; background: var(--accent); border: none;
- border-radius: 10px; color: white;
- font-size: 12px; font-family: inherit; cursor: pointer;
- box-shadow: 0 2px 6px rgba(70, 120, 190, 0.25);
- transition: filter 0.15s, box-shadow 0.15s;
- }
- .btn-primary:hover {
- filter: brightness(1.12);
- box-shadow: 0 3px 10px rgba(70, 120, 190, 0.35);
- }
- .btn-secondary {
- padding: 7px 18px; background: var(--glass);
- border: 1px solid var(--glass-border); border-radius: 10px;
- color: var(--text-secondary);
- font-size: 12px; font-family: inherit; cursor: pointer;
- transition: background 0.15s, color 0.15s;
- }
- .btn-secondary:hover {
- background: var(--glass-strong); color: var(--text);
- }
- .save-status {
- font-size: 11px; color: var(--accent);
- opacity: 0; transition: opacity 0.3s;
- }
- .save-status.visible { opacity: 1; }
-
- /* Toggle row */
- .toggle-row {
- display: flex; align-items: center; justify-content: space-between;
- padding: 8px 0; border-bottom: 1px solid var(--border);
- }
- .toggle-label {
- display: flex; flex-direction: column; gap: 2px;
- flex: 1; min-width: 0;
- }
- .toggle-name { font-size: 13px; color: var(--text); }
- .toggle-desc { font-size: 11px; color: var(--text-tertiary); }
- .toggle {
- position: relative; display: inline-block;
- width: 36px; height: 20px; flex-shrink: 0; cursor: pointer;
- }
- .toggle input { display: none; }
- .toggle-track {
- position: absolute; inset: 0;
- background: var(--glass); border: 1px solid var(--glass-border);
- border-radius: 10px; transition: background 150ms, border-color 150ms;
- }
- .toggle-track::after {
- content: ''; position: absolute; top: 2px; left: 2px;
- width: 14px; height: 14px; background: white;
- border-radius: 50%; transition: transform 150ms;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
- }
- .toggle input:checked + .toggle-track {
- background: var(--accent); border-color: var(--accent);
- }
- .toggle input:checked + .toggle-track::after {
- transform: translateX(16px);
- }
-
- /* Segment selector */
- .setting-group {
- padding: 10px 0; border-bottom: 1px solid var(--border);
- }
- .segment-selector {
- display: flex; gap: 2px; margin-top: 8px; padding: 2px;
- background: var(--glass); border: 1px solid var(--glass-border);
- border-radius: 10px; overflow: hidden;
- }
- .segment {
- flex: 1; height: 26px; background: transparent;
- border: none; font-size: 12px; font-family: inherit;
- color: var(--text-secondary); cursor: pointer;
- border-radius: 8px;
- transition: background 150ms, color 150ms;
- }
- .segment:hover { background: rgba(255,255,255,0.2); }
- .segment.active {
- background: var(--accent); color: white;
- box-shadow: 0 1px 4px rgba(70, 120, 190, 0.3);
- }
-
- /* Memory stats */
- .memory-stats { margin-top: 16px; padding-top: 12px; }
- .stats-list {
- display: flex; flex-direction: column; gap: 8px; margin-top: 8px;
- }
- .stat-row {
- display: flex; align-items: center; gap: 8px;
- font-size: 13px; color: var(--text);
- }
- .stat-dot {
- width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
- }
- .dot-accent { background: var(--accent); }
- .dot-blue { background: #60a5fa; }
- .dot-green { background: #4ade80; }
- .stat-count {
- margin-left: auto; font-variant-numeric: tabular-nums;
- color: var(--text-secondary);
- }
- .stat-total {
- margin-top: 8px; font-size: 12px; color: var(--text-tertiary);
- }
-
- /* Danger button */
- .btn-danger {
- display: block; width: 100%; height: 36px; margin-top: 16px;
- background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.15);
- border-radius: 10px; color: var(--error);
- font-size: 13px; font-family: inherit; cursor: pointer;
- transition: background 150ms, border-color 150ms;
- }
- .btn-danger:hover {
- background: rgba(239, 68, 68, 0.18);
- border-color: rgba(239, 68, 68, 0.25);
- }
-
- /* Stats cards */
- .stats-cards {
- display: flex; flex-direction: column; gap: 10px;
- margin-bottom: 16px;
- }
- .stats-card {
- display: flex; align-items: center; gap: 12px;
- padding: 12px; background: var(--glass);
- border: 1px solid var(--glass-border);
- border-radius: 12px;
- }
- .stats-icon {
- width: 36px; height: 36px; border-radius: 10px;
- display: flex; align-items: center; justify-content: center;
- flex-shrink: 0;
- }
- .stats-icon.purple { background: rgba(70, 120, 190, 0.15); color: var(--accent); }
- .stats-icon.blue { background: rgba(96, 165, 250, 0.15); color: #60a5fa; }
- .stats-icon.green { background: rgba(74, 222, 128, 0.15); color: #4ade80; }
- .stats-icon.orange { background: rgba(251, 146, 60, 0.15); color: #fb923c; }
- .stats-value {
- font-size: 18px; font-weight: 600; color: var(--text);
- font-variant-numeric: tabular-nums;
- }
- .stats-label {
- font-size: 11px; color: var(--text-tertiary);
- }
- .tool-table {
- width: 100%; border-collapse: collapse; margin-top: 8px;
- }
- .tool-table th, .tool-table td {
- padding: 6px 8px; text-align: left;
- font-size: 12px; border-bottom: 1px solid var(--border);
- }
- .tool-table th {
- color: var(--text-tertiary); font-weight: 500;
- }
- .tool-table td {
- color: var(--text);
- }
- .tool-table td:last-child {
- text-align: right; font-variant-numeric: tabular-nums;
- color: var(--text-secondary);
- }
- .empty-state {
- text-align: center; padding: 24px 16px;
- color: var(--text-tertiary); font-size: 12px;
- }
-
- /* Confirm dialog */
- .confirm-scrim {
- position: fixed; inset: 0;
- background: rgba(0, 0, 0, 0.5);
- display: flex; align-items: center; justify-content: center;
- z-index: 100;
- }
- .confirm-card {
- background: rgba(210, 205, 222, 0.95); border: 1px solid var(--glass-border);
- border-radius: 16px; padding: 20px;
- max-width: 280px; width: 90%;
- box-shadow: 0 8px 32px rgba(0,0,0,0.15);
- }
- .confirm-title {
- font-size: 14px; font-weight: 600;
- color: var(--text); margin-bottom: 8px;
- }
- .confirm-desc {
- font-size: 12px; color: var(--text-secondary);
- line-height: 1.5; margin-bottom: 16px;
- }
- .confirm-actions {
- display: flex; gap: 8px; justify-content: flex-end;
- }
-
- /* Tool catalog */
- .tool-group {
- margin-bottom: 14px; border: 1px solid var(--glass-border);
- border-radius: 12px; overflow: hidden;
- background: var(--glass);
- }
- .tool-group-header {
- display: flex; align-items: center; gap: 10px;
- padding: 10px 12px; background: var(--glass-strong, var(--glass));
- cursor: pointer;
- user-select: none;
- }
- .tool-group.expanded .tool-group-header {
- border-bottom: 1px solid var(--glass-border);
- }
- .tool-group-chevron {
- width: 14px; height: 14px; flex-shrink: 0;
- color: var(--text-tertiary);
- transform: rotate(0deg);
- transition: transform 0.15s ease;
- }
- .tool-group.expanded .tool-group-chevron {
- transform: rotate(90deg);
- }
- .tool-group-label {
- font-size: 13px; font-weight: 600; color: var(--text);
- flex: 1; min-width: 0;
- }
- .tool-group-count {
- font-size: 11px; color: var(--text-tertiary);
- font-variant-numeric: tabular-nums;
- }
- .tool-group-checkbox {
- width: 16px; height: 16px; flex-shrink: 0;
- accent-color: var(--accent);
- cursor: pointer;
- }
- .tool-list {
- display: flex; flex-direction: column;
- }
- .tool-row {
- display: flex; align-items: flex-start; gap: 10px;
- padding: 8px 12px; border-top: 1px solid var(--border);
- }
- .tool-row:first-child { border-top: none; }
- .tool-checkbox {
- width: 14px; height: 14px; margin-top: 2px; flex-shrink: 0;
- accent-color: var(--accent); cursor: pointer;
- }
- .tool-meta {
- display: flex; flex-direction: column; gap: 2px;
- flex: 1; min-width: 0;
- }
- .tool-name {
- font-size: 12px; color: var(--text);
- font-family: ui-monospace, 'SF Mono', Menlo, Monaco, monospace;
- }
- .tool-desc {
- font-size: 11px; color: var(--text-tertiary);
- line-height: 1.4; word-break: break-word;
- }
- `;
- }
-
- private boundOnToolConfigChanged_: (() => void) | null = null;
-
- constructor() {
- super();
- this.activeSubTab_ = 'general';
- this.provider_ = 'openai-compatible';
- this.apiKey_ = '';
- this.jinaApiKey_ = '';
- this.baseUrl_ = 'https://api.openai.com/v1';
- this.model_ = 'gpt-5';
- this.soulText_ = '';
- this.saveStatusText_ = '';
- this.saveStatusVisible_ = false;
- this.memoryEnabled_ = false;
- this.proactiveEnabled_ = true;
- this.dreamEnabled_ = false;
- this.dreamDebug_ = false;
- this.dreamRunning_ = false;
- this.dreamExcludedDomains_ = [];
- this.dreamExcludedDomainInput_ = '';
- this.pageContextEnabled_ = true;
- this.conversationEnabled_ = true;
- this.threshold_ = 'balanced';
- this.statConversations_ = 0;
- this.statPreferences_ = 0;
- this.statEpisodes_ = 0;
- this.statTotal_ = t('settings.memory.total_format', {kb: '0'});
- this.showConfirmDialog_ = false;
- this.agentStats_ = null;
- this.showResetStatsDialog_ = false;
- this.toolCallShowDetails_ = false;
- this.debugMode_ = false;
- this.resumeLastSession_ = true;
- this.resumeStaleHours_ = 3;
- this.workspaceInfo_ = null;
- this.workspaceActivity_ = null;
- this.toolDefinitions_ = null;
- this.toolCatalogLoadFailed_ = false;
- }
-
-
- override connectedCallback() {
- super.connectedCallback();
- this.loadSettings_();
- this.loadMemorySettings_();
- void this.initializeToolDefinitions_();
-
- soulChannel.addEventListener('message', () => {
- refreshSoulContent();
- this.soulText_ = currentSoulContent;
- });
-
- // Another agent tab (or our own toggles) changed the tool config —
- // re-render so the checkboxes reflect the new state.
- this.boundOnToolConfigChanged_ = () => this.requestUpdate();
- toolConfigChannel.addEventListener(
- 'message', this.boundOnToolConfigChanged_);
- window.addEventListener(
- 'dao-tool-config-changed', this.boundOnToolConfigChanged_);
- }
-
- override disconnectedCallback() {
- super.disconnectedCallback();
- if (this.boundOnToolConfigChanged_) {
- toolConfigChannel.removeEventListener(
- 'message', this.boundOnToolConfigChanged_);
- window.removeEventListener(
- 'dao-tool-config-changed', this.boundOnToolConfigChanged_);
- this.boundOnToolConfigChanged_ = null;
- }
- }
-
- switchSubTab(tab: string) {
- this.activeSubTab_ = tab;
- if (tab === 'soul') {
- refreshSoulContent();
- this.soulText_ = currentSoulContent;
- } else if (tab === 'memory') {
- this.loadStorageStats_();
- } else if (tab === 'stats') {
- this.agentStats_ = getAgentStats();
- } else if (tab === 'tools') {
- this.loadWorkspaceInfo_();
- this.loadWorkspaceActivity_();
- }
- }
-
- private async initializeToolDefinitions_() {
- try {
- await initializeBrowserToolCatalog();
- this.toolDefinitions_ = getAgentToolDefinitions();
- this.toolCatalogLoadFailed_ = false;
- } catch (error) {
- console.error('[dao] browser tool catalog load failed', error);
- this.toolDefinitions_ = [];
- this.toolCatalogLoadFailed_ = true;
- }
- }
-
- override render() {
- return html`
-
- ${['general', 'soul', 'tools', 'memory', 'skills', 'stats'].map(
- tab => html`
- `)}
-
- ${this.activeSubTab_ === 'soul' ? this.renderSoul_() :
- this.activeSubTab_ === 'tools' ? this.renderTools_() :
- this.activeSubTab_ === 'skills' ? this.renderSkills_() :
- this.activeSubTab_ === 'stats' ? this.renderStats_() :
- this.activeSubTab_ === 'memory' ? this.renderMemory_() :
- this.renderGeneral_()}
- ${this.showConfirmDialog_ ? this.renderConfirmDialog_() : nothing}
- ${this.showResetStatsDialog_ ? this.renderResetStatsDialog_() : nothing}
- `;
- }
-
- private renderGeneral_() {
- const spec = this.getProviderSpec_(this.provider_);
- return html`
-
-
- ${t('settings.general.api_connection_title')}
-
- ${t('settings.general.api_connection_desc')}
-
-
-
-
this.onApiKeyChange_(
- (e.target as HTMLInputElement).value)}>
- ${spec.needsBaseUrl ? html`
-
-
this.onBaseUrlChange_(
- (e.target as HTMLInputElement).value)}>` : nothing}
-
-
this.onModelChange_(
- (e.target as HTMLInputElement).value)}>
-
-
- ${t('settings.general.display_title')}
- ${this.renderToggle_(
- t('settings.general.show_tool_details_name'),
- t('settings.general.show_tool_details_desc'),
- this.toolCallShowDetails_, (v) => {
- this.toolCallShowDetails_ = v;
- localStorage.setItem(
- 'dao_tool_call_show_details', String(v));
- })}
- ${this.renderToggle_(
- t('settings.general.debug_mode_name'),
- t('settings.general.debug_mode_desc'),
- this.debugMode_, (v) => this.setDebugMode_(v))}
-
-
- ${t('settings.general.session_title')}
- ${this.renderToggle_(
- t('settings.general.resume_session_name'),
- t('settings.general.resume_session_desc'),
- this.resumeLastSession_, (v) => {
- this.resumeLastSession_ = v;
- localStorage.setItem(
- 'dao_resume_last_session', String(v));
- })}
-
-
-
- ${t('settings.general.stale_threshold_name')}
-
- ${t('settings.general.stale_threshold_desc')}
-
-
- this.onResumeStaleHoursChange_(
- (e.target as HTMLInputElement).value)}>
-
- ${t('settings.general.hours_unit')}
-
-
-
`;
- }
-
- private onResumeStaleHoursChange_(value: string) {
- const parsed = Number(value);
- const next =
- Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 3;
- this.resumeStaleHours_ = next;
- localStorage.setItem('dao_resume_stale_hours', String(next));
- }
-
- private setDebugMode_(enabled: boolean) {
- this.debugMode_ = enabled;
- localStorage.setItem(DAO_AGENT_DEBUG_MODE_KEY, String(enabled));
- window.dispatchEvent(new CustomEvent(
- DAO_AGENT_DEBUG_MODE_CHANGED_EVENT,
- {detail: {enabled}}));
- }
-
- private setProactiveEnabled_(enabled: boolean) {
- this.proactiveEnabled_ = enabled;
- localStorage.setItem(DAO_PROACTIVE_ENABLED_KEY, String(enabled));
- window.dispatchEvent(new CustomEvent(
- DAO_PROACTIVE_ENABLED_CHANGED_EVENT,
- {detail: {enabled}}));
- callNativeArgs('setProactiveEnabled', enabled).catch(() => {});
- }
-
- private getProviderSpec_(id: string): ProviderSpec {
- return LLM_PROVIDERS.find(p => p.id === id) ?? LLM_PROVIDERS[0]!;
- }
-
- private onProviderChange_(id: string) {
- if (!LLM_PROVIDERS.some(p => p.id === id)) return;
- setActiveProvider(id);
- this.provider_ = id;
- const cfg = getProviderConfig(id);
- this.apiKey_ = cfg.apiKey;
- this.baseUrl_ = cfg.baseUrl;
- this.model_ = cfg.model;
- this.notifyConfigChanged_();
- }
-
- private onApiKeyChange_(value: string) {
- this.apiKey_ = value;
- setProviderConfig(this.provider_, {apiKey: value});
- this.notifyConfigChanged_();
- }
-
- private onBaseUrlChange_(value: string) {
- this.baseUrl_ = value;
- setProviderConfig(this.provider_, {baseUrl: value});
- this.notifyConfigChanged_();
- }
-
- private onModelChange_(value: string) {
- this.model_ = value;
- setProviderConfig(this.provider_, {model: value});
- this.notifyConfigChanged_();
- }
-
- private notifyConfigChanged_() {
- window.dispatchEvent(new Event('llm-config-changed'));
- }
-
- private renderSoul_() {
- return html`
-
-
${t('settings.soul.title')}
-
${t('settings.soul.desc')}
-
-
-
-
- ${this.saveStatusText_}
-
-
`;
- }
-
- private renderMemory_() {
- const thresholds = ['quiet', 'balanced', 'active'] as const;
- return html`
-
-
${t('settings.memory.title')}
-
${t('settings.memory.desc')}
-
- ${this.renderToggle_(
- t('settings.memory.enable_name'),
- t('settings.memory.enable_desc'),
- this.memoryEnabled_, (v) => {
- this.memoryEnabled_ = v;
- callNativeArgs('setMemoryEnabled', v).catch(() => {});
- if (v) {
- this.loadStorageStats_();
- }
- })}
- ${this.memoryEnabled_ ? html`
- ${this.renderToggle_(
- t('settings.memory.proactive_name'),
- t('settings.memory.proactive_desc'),
- this.proactiveEnabled_, (v) => {
- this.setProactiveEnabled_(v);
- })}
-
-
-
- ${t('settings.memory.threshold_name')}
-
- ${thresholds.map(tier => html`
- `)}
-
-
-
- ${this.renderToggle_(
- t('settings.memory.page_context_name'),
- t('settings.memory.page_context_desc'),
- this.pageContextEnabled_, (v) => {
- this.pageContextEnabled_ = v;
- localStorage.setItem(
- 'dao_page_context_enabled', String(v));
- })}
- ${this.renderToggle_(
- t('settings.memory.conversation_name'),
- t('settings.memory.conversation_desc'),
- this.conversationEnabled_, (v) => {
- this.conversationEnabled_ = v;
- localStorage.setItem(
- 'dao_conversation_enabled', String(v));
- })}
-
-
-
- ${t('settings.memory.usage_title')}
-
-
-
- ${t('settings.memory.conversations')}
- ${this.statConversations_}
-
-
-
- ${t('settings.memory.preferences')}
- ${this.statPreferences_}
-
-
-
- ${t('settings.memory.episodes')}
- ${this.statEpisodes_}
-
-
-
${this.statTotal_}
-
-
-
- ` : nothing}
-
- ${this.memoryEnabled_ ? this.renderDream_() : nothing}`;
- }
-
- private renderDream_() {
- return html`
-
-
${t('settings.dream.title')}
-
${t('settings.dream.desc')}
- ${this.renderToggle_(
- t('settings.dream.enable_name'),
- t('settings.dream.enable_desc'),
- this.dreamEnabled_, (v) => {
- this.dreamEnabled_ = v;
- callNativeArgs('setDreamEnabled', v).catch(() => {});
- })}
-
-
-
- {
- this.dreamExcludedDomainInput_ =
- (e.target as HTMLInputElement).value;
- }}
- @keydown=${(e: KeyboardEvent) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- void this.addDreamExcludedDomain_();
- }
- }}>
-
-
-
- ${this.dreamExcludedDomains_.map(domain => html`
-
- ${domain}
-
- `)}
-
-
- ${this.renderToggle_(
- t('settings.dream.debug_name'),
- t('settings.dream.debug_desc'),
- this.dreamDebug_, (v) => {
- this.dreamDebug_ = v;
- callNativeArgs('setDreamDebug', v).catch(() => {});
- })}
-
-
-
-
-
`;
- }
-
- private async addDreamExcludedDomain_() {
- const input = this.dreamExcludedDomainInput_.trim();
- if (!input) {
- return;
- }
- try {
- const result = await callNativeArgs('addDreamExcludedDomain', input) as
- {domain?: string};
- const domain = result?.domain;
- if (typeof domain === 'string' && domain) {
- this.dreamExcludedDomains_ =
- [...new Set([...this.dreamExcludedDomains_, domain])].sort();
- this.dreamExcludedDomainInput_ = '';
- }
- } catch (e) {
- const msg = e instanceof Error ? e.message : String(e);
- this.fireToast_(t('settings.dream.excluded_add_failed', {error: msg}));
- }
- }
-
- private async removeDreamExcludedDomain_(domain: string) {
- try {
- await callNativeArgs('removeDreamExcludedDomain', domain);
- this.dreamExcludedDomains_ =
- this.dreamExcludedDomains_.filter(item => item !== domain);
- } catch (e) {
- const msg = e instanceof Error ? e.message : String(e);
- this.fireToast_(
- t('settings.dream.excluded_remove_failed', {error: msg}));
- }
- }
-
- private openDreamHistory_() {
- chrome.send('openDreamReport', []);
- }
-
- private async runDreamNow_() {
- this.dreamRunning_ = true;
- try {
- await callNative('startManualDream', undefined, {
- timeoutMs: DREAM_RUN_NATIVE_TIMEOUT_MS,
- });
- this.fireToast_(t('settings.dream.run_done_toast'));
- window.dispatchEvent(new Event('dao-dream-report-updated'));
- } catch (e) {
- const msg = e instanceof Error ? e.message : String(e);
- this.fireToast_(t('settings.dream.run_failed_toast', {error: msg}));
- } finally {
- this.dreamRunning_ = false;
- }
- }
-
- private renderToggle_(
- name: string, desc: string, checked: boolean,
- onChange: (val: boolean) => void) {
- return html`
-
-
- ${name}
- ${desc}
-
-
-
`;
- }
-
- private renderConfirmDialog_() {
- return html`
- {
- if (e.target === e.currentTarget) {
- this.showConfirmDialog_ = false;
- }
- }}>
-
-
- ${t('settings.memory.clear_dialog_title')}
-
- ${t('settings.memory.clear_dialog_desc')}
-
-
-
-
-
-
`;
- }
-
- // ---- Workspace ----
-
- private async loadWorkspaceInfo_() {
- try {
- const info = await callNative('workspaceGetInfo', {}) as WorkspaceInfo;
- this.workspaceInfo_ = info;
- } catch (_e) {
- this.workspaceInfo_ = null;
- }
- }
-
- private async loadWorkspaceActivity_() {
- this.workspaceActivity_ = null;
- try {
- const reply = await callNative('workspaceGetRecentActivity', {}) as
- {entries?: WorkspaceAuditEntry[]};
- this.workspaceActivity_ = reply.entries ?? [];
- } catch (e) {
- this.workspaceActivity_ = [];
- console.warn('Failed to load workspace activity', e);
- }
- }
-
- private async revealWorkspaceInFinder_() {
- try {
- await callNative('workspaceOpenFolder', {});
- } catch (e) {
- console.warn('Failed to reveal workspace folder', e);
- }
- }
-
- private formatBytes_(n: number): string {
- if (n < 1024) return `${n} B`;
- if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
- return `${(n / (1024 * 1024)).toFixed(1)} MB`;
- }
-
- private formatRelativeTime_(ts: string): string {
- const epochMs = Date.parse(ts);
- if (Number.isNaN(epochMs)) {
- return ts;
- }
- const delta = Date.now() - epochMs;
- if (delta < 60_000) return 'just now';
- if (delta < 3_600_000) return `${Math.floor(delta / 60_000)}m ago`;
- if (delta < 86_400_000) return `${Math.floor(delta / 3_600_000)}h ago`;
- return `${Math.floor(delta / 86_400_000)}d ago`;
- }
-
- private renderWorkspace_() {
- const info = this.workspaceInfo_;
- const used = info ? this.formatBytes_(info.used_bytes) : '—';
- const cap = info ? this.formatBytes_(info.cap_bytes) : '—';
- const percent = info && info.cap_bytes > 0
- ? Math.floor((info.used_bytes / info.cap_bytes) * 100)
- : 0;
-
- const activity = this.workspaceActivity_;
-
- return html`
-
-
- ${t('settings.workspace.section_title')}
-
-
- ${t('settings.workspace.section_desc')}
-
-
-
-
-
-
-
-
-
-
- ${info ? t('settings.workspace.usage_value',
- {used, cap, percent: String(percent)}) : '—'}
-
-
-
- ${info ? t('settings.workspace.file_count_value',
- {count: String(info.file_count),
- cap: String(info.file_count_cap)}) : '—'}
-
-
-
- ${t('settings.workspace.activity_title')}
-
- ${activity === null ? html`
-
- ${t('settings.workspace.activity_loading')}
-
- ` : activity.length === 0 ? html`
-
- ${t('settings.workspace.activity_empty')}
-
- ` : html`
-
- ${activity.map(e => html`
-
- ${t('settings.workspace.activity_row', {
- when: this.formatRelativeTime_(e.ts),
- op: e.op,
- path: e.path,
- })}
-
`)}
-
- `}
-
- `;
- }
-
- // ---- Stats ----
-
- private renderStats_() {
- const s = this.agentStats_ || getAgentStats();
- const toolEntries = Object.entries(s.toolCalls)
- .sort((a, b) => b[1] - a[1]);
- const totalToolCalls = toolEntries.reduce((sum, [, c]) => sum + c, 0);
- const resetDate = new Date(s.lastReset);
- const sinceStr = resetDate.toLocaleDateString(undefined, {
- month: 'short', day: 'numeric', year: 'numeric',
- });
-
- // SVG icons for stats cards
- const apiIcon = html``;
- const toolIcon = html``;
- const tokenIcon = html``;
- const costIcon = html``;
-
- return html`
-
-
${t('settings.stats.title')}
-
- ${t('settings.stats.since_format', {date: sinceStr})}
-
-
-
-
${apiIcon}
-
-
${s.apiCalls}
-
${t('settings.stats.api_calls')}
-
-
-
-
${toolIcon}
-
-
${totalToolCalls}
-
${t('settings.stats.tool_calls')}
-
-
-
-
${tokenIcon}
-
-
${this.formatNumber_(s.totalTokens)}
-
${
- t('settings.stats.total_tokens_format', {
- inTok: this.formatNumber_(s.promptTokens),
- outTok: this.formatNumber_(s.completionTokens),
- })}
-
-
-
-
${costIcon}
-
-
$${s.estimatedCost.toFixed(4)}
-
- ${t('settings.stats.estimated_cost')}
-
-
-
-
-
- ${t('settings.stats.tool_breakdown')}
- ${toolEntries.length > 0 ? html`
-
` :
- html`
- ${t('settings.stats.empty')}
`}
-
-
-
`;
- }
-
- private formatNumber_(n: number): string {
- if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
- if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
- return String(n);
- }
-
- private renderResetStatsDialog_() {
- return html`
- {
- if (e.target === e.currentTarget) {
- this.showResetStatsDialog_ = false;
- }
- }}>
-
-
- ${t('settings.stats.reset_dialog_title')}
-
- ${t('settings.stats.reset_dialog_desc')}
-
-
-
-
-
-
`;
- }
-
- private resetStats_() {
- this.showResetStatsDialog_ = false;
- resetAgentStats();
- this.agentStats_ = getAgentStats();
- this.fireToast_(t('settings.stats.toast_reset'));
- }
-
- // ---- Settings Persistence ----
-
- private loadSettings_() {
- this.provider_ = getActiveProvider();
- const cfg = getProviderConfig(this.provider_);
- this.apiKey_ = cfg.apiKey;
- this.jinaApiKey_ = getJinaApiKey();
- this.baseUrl_ = cfg.baseUrl;
- this.model_ = cfg.model;
- this.soulText_ = currentSoulContent;
- this.toolCallShowDetails_ =
- localStorage.getItem('dao_tool_call_show_details') === 'true';
- this.debugMode_ =
- localStorage.getItem(DAO_AGENT_DEBUG_MODE_KEY) === 'true';
- this.resumeLastSession_ =
- localStorage.getItem('dao_resume_last_session') !== 'false';
- const staleRaw =
- localStorage.getItem('dao_resume_stale_hours');
- const staleParsed = staleRaw === null ? NaN : Number(staleRaw);
- this.resumeStaleHours_ =
- Number.isFinite(staleParsed) && staleParsed >= 0 ? staleParsed : 3;
- }
-
- private loadMemorySettings_() {
- this.memoryEnabled_ = false;
- this.proactiveEnabled_ =
- localStorage.getItem(DAO_PROACTIVE_ENABLED_KEY) !== 'false';
- this.pageContextEnabled_ =
- localStorage.getItem('dao_page_context_enabled') !== 'false';
- this.conversationEnabled_ =
- localStorage.getItem('dao_conversation_enabled') !== 'false';
- this.threshold_ =
- localStorage.getItem('dao_proactive_threshold') || 'balanced';
-
- callNativeArgs('getMemoryEnabled').then(enabled => {
- this.memoryEnabled_ = !!enabled;
- }).catch(() => {});
-
- callNativeArgs('getDreamEnabled').then(enabled => {
- this.dreamEnabled_ = !!enabled;
- }).catch(() => {});
- callNativeArgs('getDreamDebug').then(enabled => {
- this.dreamDebug_ = !!enabled;
- }).catch(() => {});
- callNativeArgs('getDreamExcludedDomains').then(domains => {
- this.dreamExcludedDomains_ = Array.isArray(domains) ?
- domains.filter(
- (domain): domain is string => typeof domain === 'string') :
- [];
- }).catch(() => {});
-
- callNativeArgs(
- 'setProactiveEnabled', this.proactiveEnabled_).catch(() => {});
- callNativeArgs(
- 'setConfidenceThreshold',
- CONFIDENCE_THRESHOLD_MAP[this.threshold_] ?? 0.7).catch(() => {});
- }
-
- // ---- Soul ----
-
- private saveSoul_() {
- saveSoul(this.soulText_);
- this.showSaveStatus_(t('settings.soul.saved_status'));
- }
-
- private resetSoul_() {
- this.soulText_ = DEFAULT_SOUL;
- saveSoul(DEFAULT_SOUL);
- this.showSaveStatus_(t('settings.soul.reset_status'));
- }
-
- private showSaveStatus_(text: string) {
- this.saveStatusText_ = text;
- this.saveStatusVisible_ = true;
- clearTimeout(this.saveStatusTimer_);
- this.saveStatusTimer_ = window.setTimeout(() => {
- this.saveStatusVisible_ = false;
- }, 2000);
- }
-
- // ---- Threshold ----
-
- private setThreshold_(value: string) {
- this.threshold_ = value;
- localStorage.setItem('dao_proactive_threshold', value);
- callNativeArgs(
- 'setConfidenceThreshold',
- CONFIDENCE_THRESHOLD_MAP[value] ?? 0.7).catch(() => {});
- }
-
- private onSegmentKeydown_(e: KeyboardEvent) {
- if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
- const values = ['quiet', 'balanced', 'active'];
- const idx = values.indexOf(this.threshold_);
- const next = e.key === 'ArrowRight'
- ? Math.min(idx + 1, 2) : Math.max(idx - 1, 0);
- this.setThreshold_(values[next]!);
- }
-
- // ---- Memory ----
-
- private async loadStorageStats_() {
- try {
- const stats = await callNativeArgs('getStorageStats') as {
- totalSize?: number; conversationCount?: number;
- episodeCount?: number; preferenceCount?: number;
- };
- this.statConversations_ = stats.conversationCount || 0;
- this.statPreferences_ = stats.preferenceCount || 0;
- this.statEpisodes_ = stats.episodeCount || 0;
- const kb = ((stats.totalSize || 0) / 1024).toFixed(1);
- this.statTotal_ = t('settings.memory.total_format', {kb});
- } catch (_) { /* non-critical */ }
- }
-
- private async clearAllMemory_() {
- this.showConfirmDialog_ = false;
- try {
- await callNativeArgs('clearAllMemory');
- this.fireToast_(t('settings.memory.toast_cleared'));
- this.loadStorageStats_();
- } catch (_) {
- this.fireToast_(t('settings.memory.toast_clear_failed'));
- }
- }
-
- // ---- Tools ----
-
- private renderTools_() {
- const catalogState = this.toolDefinitions_ === null ?
- html`
- ${t('settings.tools.catalog_loading')}
-
` :
- this.toolCatalogLoadFailed_ ?
- html`
- ${t('settings.tools.catalog_load_failed')}
-
-
` :
- TOOL_GROUPS.map(group => this.renderToolGroup_(group.id));
- return html`
-
-
${t('settings.tools.title')}
-
${t('settings.tools.desc')}
- ${catalogState}
-
- ${this.renderWorkspace_()}`;
- }
-
- private onSearchSourceChange_(e: Event) {
- const value = (e.target as HTMLSelectElement).value as SearchSourceOverride;
- setSearchSourceOverride(value);
- this.requestUpdate();
- }
-
- private retryToolCatalog_() {
- this.toolDefinitions_ = null;
- this.toolCatalogLoadFailed_ = false;
- void this.initializeToolDefinitions_();
- }
-
- private onJinaApiKeyChange_(value: string) {
- this.jinaApiKey_ = value;
- setJinaApiKey(value);
- }
-
- private renderToolGroup_(groupId: string) {
- const group = TOOL_GROUPS.find(g => g.id === groupId);
- if (!group) return nothing;
- const state = getGroupState(groupId);
- const counts = countEnabled(groupId);
- const allChecked = state === 'all';
- const indeterminate = state === 'some';
- const expanded = isGroupExpanded(groupId);
- // Translated group label; falls back to the catalog label if no key
- // exists yet for a newly added group id.
- const groupKey = `settings.tools.group.${groupId}`;
- const groupLabel = t(groupKey) === groupKey ? group.label : t(groupKey);
-
- const onHeaderToggle = () => {
- setGroupExpanded(groupId, !expanded);
- this.requestUpdate();
- };
-
- return html`
- `;
- }
-
- private renderToolRow_(name: string) {
- const def = this.toolDefinitions_!.find(
- tool => tool.function.name === name);
- const desc = def?.function.description ?? '';
- const enabled = isToolEnabled(name);
- return html`
- `;
- }
-
- // ---- Skills ----
-
- private renderSkills_() {
- return html`
-
-
${t('settings.skills.title')}
-
${t('settings.skills.desc')}
-
-
-
`;
- }
-
- private openSkillManager_() {
- chrome.send('openSkillManager', []);
- }
-
- private fireToast_(text: string) {
- this.dispatchEvent(new CustomEvent('show-toast', {
- bubbles: true, composed: true, detail: {text},
- }));
- }
-}
-
-customElements.define('dao-settings-view', DaoSettingsView);
diff --git a/src/patches/chrome/app/resources/generated_resources_zh-CN.xtb.patch b/src/patches/chrome/app/resources/generated_resources_zh-CN.xtb.patch
index 4e298e27..aa4fd646 100644
--- a/src/patches/chrome/app/resources/generated_resources_zh-CN.xtb.patch
+++ b/src/patches/chrome/app/resources/generated_resources_zh-CN.xtb.patch
@@ -1,7 +1,7 @@
diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/resources/generated_resources_zh-CN.xtb
--- a/chrome/app/resources/generated_resources_zh-CN.xtb
+++ b/chrome/app/resources/generated_resources_zh-CN.xtb
-@@ -1,6 +1,16 @@
+@@ -1,6 +1,114 @@
@@ -15,10 +15,108 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
+复制安装命令
+安装命令已复制
+MCP 配置已复制
++Dao 智能体
++配置智能体的连接、行为、搜索、记忆和学习方式。
++无法加载智能体配置。请重试刷新。
++模型与连接
++选择服务商,并配置新对话使用的模型。
++OpenAI 兼容
++OpenAI
++Anthropic
++Google
++Groq
++xAI
++OpenRouter
++API 密钥
++基础 URL
++会话与显示
++恢复最近一次对话
++恢复此小时数以内的对话
++显示工具调用详情
++显示调试信息
++智能体人格
++描述智能体应遵循的人格、原则和沟通方式。
++使用当前页面作为上下文
++使用对话上下文
++网页搜索
++搜索来源
++自动
++模型服务商
++DuckDuckGo
++Jina API 密钥
++记忆与主动建议
++启用记忆
++启用主动建议
++建议敏感度
++保守
++均衡
++积极
++Dream 分析
++启用 Dream 分析
++显示 Dream 调试信息
++排除的域名,每行一个
++工具
++选择智能体可以使用的操作类型。
++页面交互
++标签页
++开发者工具
++记忆与技能
++网页
++工作区
++技能与数据
++管理技能
++查看记忆
++打开 Dream 报告
++Dao 浏览器
++基于 Chromium
++Dao 专属
++单项工具权限
++数据与管理
++记忆
++用量
++对话
++偏好
++事件
++总大小
++根目录
++存储空间
++文件
++近期活动
++API 调用
++工具调用
++提示词元
++补全词元
++总词元
++预估费用
++上次重置
++正在加载…
++无法加载记忆摘要。请重试刷新。
++无法清除记忆。请再次尝试清除记忆。
++记忆已清除,但无法刷新摘要。请重试加载。
++无法加载工作区摘要。请重试刷新。
++无法打开工作区。请再次尝试打开工作区。
++无法加载用量摘要。请重试刷新。
++无法重置用量。请再次尝试重置用量。
++用量已重置,但无法刷新摘要。请重试加载。
++操作 ,路径
++重试
++清除记忆
++打开工作区
++重置用量
++清除全部记忆?
++这会永久删除所有对话记忆、偏好和事件,且无法撤销。
++取消
++重置用量统计?
++这会将所有 API、工具和词元计数清零。
++暂无近期活动
++暂无工具调用
++记忆已清除
++工作区已打开
++用量已重置
已与您共享该网络
点击即可切换权限。
标签页重新变为活动状态
-@@ -538,6 +548,7 @@
+@@ -538,6 +644,7 @@
闲置标签页外观焕然一新
拼写和语法
您的设备可通过 Smart Lock 解锁。按 Enter 键即可解锁。
@@ -26,7 +124,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
手动添加
未使用
发生机械问题,请检查打印机
-@@ -894,6 +905,7 @@
+@@ -894,6 +1001,7 @@
此用户未与网域关联
与 Gemini 聊天,在 Google AI 的帮助下撰写内容、制定规划、学习新知识等等。
设置完成后,选择屏幕底部任务栏上的 Gemini 应用,即可开始使用 Gemini。
@@ -34,7 +132,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
可对所选项执行的更多操作
应用设置
青色
-@@ -1011,6 +1023,7 @@
+@@ -1011,6 +1119,7 @@
打开新的无痕式窗口
大号鼠标光标
要开启光标浏览模式吗?
@@ -42,7 +140,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
启用滑行输入
管理员已禁止更新此应用,所以此应用可能无法正常运行
网站通常会发送通知,告知您重大新闻或聊天消息。
-@@ -1117,6 +1130,7 @@
+@@ -1117,6 +1226,7 @@
创建应用快捷方式
在新标签页中打开
Beta 版
@@ -50,7 +148,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
打开新的窗口(&N)
已退出全屏模式
将密码安全地保存到您的 Google 账号中,彻底为您免除再次输入的麻烦
-@@ -1362,6 +1376,7 @@
+@@ -1362,6 +1472,7 @@
指纹
屏幕锁定 PIN 码
将 OneDrive 连接到“文件”应用即可从 Chromebook 中管理您存储的文档。您需要使用自己的 Microsoft 账号登录。
@@ -58,7 +156,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
更新
任意串行端口
读取您在所有登录过的设备上的浏览记录
-@@ -1620,6 +1635,7 @@
+@@ -1620,6 +1731,7 @@
Android 应用
自动关闭热点
已隐藏
@@ -66,7 +164,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
JavaScript 优化已停用。
上次检查时间:前
{COUNT,plural, =1{电话号码}other{# 个电话号码}}
-@@ -1784,6 +1800,7 @@
+@@ -1784,6 +1896,7 @@
反向滚动
每次访问时都询问
设置 密码,以便更轻松地登录
@@ -74,7 +172,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
报告问题
PIN 码或密码
在日出和日落时切换主题
-@@ -2350,6 +2367,7 @@
+@@ -2350,6 +2463,7 @@
自定义工具栏按钮
有新版 Chrome 可用
用户名已复制到剪贴板
@@ -82,7 +180,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
{NUM_SITES,plural, =1{关闭了 1 个不安全的扩展程序}other{关闭了 {NUM_SITES} 个不安全的扩展程序}}
向上滑动即可开始使用
清理计算机
-@@ -2714,10 +2732,12 @@
+@@ -2714,10 +2828,12 @@
崩溃
要使用网络“”,请先在下方连接互联网。
{NUM_GROUPS,plural, =1{删除分组}other{删除分组}}
@@ -95,7 +193,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
提供方
登录“”
如需完成 Linux 设置,请更新 Chrome 操作系统并重试。
-@@ -2910,6 +2930,7 @@
+@@ -2910,6 +3026,7 @@
加入并打开
工具和操作
每次浏览时都可畅享 Google 搜索功能和 Google 智能工具
@@ -103,7 +201,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
从分组中移除
与他人协作
当 Chromebook 处于离线状态且热点可用时
-@@ -4134,6 +4155,7 @@
+@@ -4134,6 +4251,7 @@
翻译页面和快速解答时使用的语言
主题列表,这些主题是 Chrome 根据您近期的浏览记录推测的
学校账号
@@ -111,7 +209,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
您可以保留此分组,以便日后添加标签页;如果不再想访问它,也可以退出分组。
Chrome 颜色
打开您计算机的代理设置
-@@ -5570,6 +5592,7 @@
+@@ -5570,6 +5688,7 @@
正在安装操作系统更新
麦克风静音
暂停或恢复面部控制
@@ -119,7 +217,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
通过打印服务器找到了 1 台打印机
撅起嘴唇
隐藏 PIN 码
-@@ -5937,9 +5960,11 @@
+@@ -5937,9 +6056,11 @@
添加新卡
后退
固定 Gemini
@@ -131,7 +229,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
发送关于标签栏的反馈
网站列表,这些网站已被您屏蔽,因为您不想让它们向其他网站建议广告
无痕式往返缓存版子框架:
-@@ -7106,6 +7131,7 @@
+@@ -7106,6 +7227,7 @@
已启用
收集的所有数据都会遵照 Google《隐私权政策》加以使用。
管理员已停用固件更新。
@@ -139,7 +237,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
您的管理员已禁止连接到此网络
禁止在新访问的网站上使用 JavaScript 优化工具。需要启用安全浏览功能
点击“自定义 Chrome”
-@@ -8216,6 +8242,7 @@
+@@ -8216,6 +8338,7 @@
运行 ChromeOS 诊断测试
SSL 客户端证书
不允许网站使用您的摄像头
@@ -147,7 +245,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
{NUM_PAGES,plural, =0{}=1{ 及另外 1 个标签页}other{ 及另外 # 个标签页}}
Google 服务设置
上一项
-@@ -8242,10 +8269,12 @@
+@@ -8242,10 +8365,12 @@
控制您正在投放的媒体
电源
选择其他资料
@@ -160,7 +258,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
尾号为 的 IBAN
请输入用户名
森林上空闪耀着明亮的北极光。
-@@ -9002,6 +9031,7 @@
+@@ -9002,6 +9127,7 @@
此应用无响应。请选择“强制关闭”以关闭此应用。
自助服务终端和数字标牌设备注册已完成
如果您降低阈值,就可以做出轻微的动作。如果您提高阈值,可能需要做出更夸张的动作。
@@ -168,7 +266,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
按下“c”键表示松开鼠标按键
共享分组
系统默认文字转语音声音
-@@ -9130,6 +9160,7 @@
+@@ -9130,6 +9256,7 @@
视频流畅性
{COUNT,plural, =1{有 {COUNT} 个密码仅保存在此设备上}other{有 {COUNT} 个密码仅保存在此设备上}}
出了点问题
@@ -176,7 +274,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
附近的所有联系人都可与您分享内容。仅当您接受后,系统才会开始传输内容。
请验证是您本人在操作
正在安装 Linux…
-@@ -9610,6 +9641,7 @@
+@@ -9610,6 +9737,7 @@
(在 中)。
已屏蔽。时间表当前设为 - ,只能手动更新。
ChromeOS Shill(连接管理器)日志
@@ -184,7 +282,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
您想如何处理现有的资料数据?
停用 Linux 中所有正被转发的端口
个应用
-@@ -11030,6 +11062,7 @@
+@@ -11030,6 +11158,7 @@
您可以右键点击任意标签页,然后选择“水平显示标签页”,将标签页移回顶部
打包扩展程序错误
邮箱已自动验证
@@ -192,7 +290,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
很抱歉,文件过大,计算机无法处理。
版本
由于无法验证您的密码,因此登录失败了。请与管理员联系或重试。
-@@ -11285,6 +11318,7 @@
+@@ -11285,6 +11414,7 @@
Crostini 麦克风使用权限
{PASSWORD_COUNT,plural, =1{有 1 个密码只保存到此设备上。若要在其他设备上使用它,请将它保存到您的 Google 账号中。此操作还会清理所有重复项。}other{有 {PASSWORD_COUNT} 个密码只保存到此设备上。若要在其他设备上使用它们,请将它们保存到您的 Google 账号中。此操作还会清理所有重复项。}}
已被禁止对 MIDI 设备进行控制和重新编程
@@ -200,7 +298,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
无法登录
请让家长批准安装“”
节省适量内存
-@@ -11433,6 +11467,7 @@
+@@ -11433,6 +11563,7 @@
该网站正在跟踪您的位置
对您的书签、历史记录、密码及其他设置所做的更改将不再同步到您的 Google 账号。但是,您的现有数据依然会存储在您的 Google 账号中,而且您可以通过 Google 信息中心管理这些数据。
您的功能和扩展程序快捷字词
@@ -208,7 +306,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
Google 账号
启用自动扫描
更新程序
-@@ -11641,6 +11676,7 @@
+@@ -11641,6 +11772,7 @@
不允许此设备运行虚拟机
您的连接在部分网络流量中不是私密连接
显示原始网页
@@ -216,7 +314,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
未命名的文件夹
{NUM_ATTEMPTS,plural, =1{您还剩 1 次尝试机会。}other{您还剩 # 次尝试机会。}}
您想为 Chrome 操作系统启用 ChromeVox(内置屏幕阅读器)吗?如要启用,请同时按住两个音量键 5 秒钟。
-@@ -11708,6 +11744,7 @@
+@@ -11708,6 +11840,7 @@
未选择“”。按搜索键 + 空格键即可选择。
添加受限用户
系统已根据企业政策屏蔽
@@ -224,7 +322,7 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
Wi-Fi SSID
由 管理
输入法
-@@ -11852,6 +11889,7 @@
+@@ -11852,6 +11985,7 @@
不允许网站播放受保护内容
Google 文档
{COUNT,plural, =1{应用}other{# 个应用}}
@@ -232,10 +330,19 @@ diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/res
{NUM_APPS,plural,offset:2 =1{管理员可以使用“”录制您的屏幕。录制开始时,您不会收到通知。}=2{管理员可以使用“”和“”录制您的屏幕。录制开始时,您不会收到通知。}=3{管理员可以使用“”“”和另外 1 个应用录制您的屏幕。录制开始时,您不会收到通知。}other{管理员可以使用“”“”和另外 # 个应用录制您的屏幕。录制开始时,您不会收到通知。}}
使用孩子的 Google 账号登录
{TAB_COUNT,plural, =1{正在共享 1 个标签页}other{正在共享 # 个标签页}}
-@@ -12003,4 +12041,4 @@
+@@ -12003,4 +12137,13 @@
同步标签页分组
检查(&N)
配置的政策不允许执行此操作。
-
\ No newline at end of file
++配置 Dao 智能体
++打开模型、行为、能力、学习和数据设置。
++暂不可用
++服务商、模型、API 密钥、会话、人格、上下文、工具、搜索、记忆、主动建议、Dream、工作区、技能和用量。
++模型与连接
++行为与上下文
++能力
++学习与分析
++数据与管理
+
diff --git a/src/patches/chrome/app/settings_strings.grdp.patch b/src/patches/chrome/app/settings_strings.grdp.patch
index 127aaa81..708d5055 100644
--- a/src/patches/chrome/app/settings_strings.grdp.patch
+++ b/src/patches/chrome/app/settings_strings.grdp.patch
@@ -164,7 +164,7 @@ diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
Sites listed below follow a custom setting instead of the default
-@@ -3836,7 +3836,139 @@
+@@ -3836,7 +3836,484 @@
You and Google
@@ -172,6 +172,351 @@ diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
+
+ You and Dao
+
++
++ Dao Browser
++
++
++ Based on Chromium
++
++
++ Dao exclusive
++
++
++ Browser
++
++
++ Dao Agent
++
++
++ Configure how the agent connects, behaves, searches, remembers, and learns.
++
++
++ Configure Dao Agent
++
++
++ Open model, behavior, capabilities, learning, and data settings.
++
++
++ Unavailable
++
++
++ Provider, model, API key, session, personality, context, tools, search, memory, proactive suggestions, Dream, workspace, skills, and usage.
++
++
++ Model and connection
++
++
++ Behavior and context
++
++
++ Capabilities
++
++
++ Learning and analysis
++
++
++ Data and management
++
++
++ Agent configuration could not be loaded. Retry to refresh it.
++
++
++ Model and connection
++
++
++ Choose a provider and configure the model used for new conversations.
++
++
++ Provider
++
++
++ OpenAI-compatible
++
++
++ OpenAI
++
++
++ Anthropic
++
++
++ Google
++
++
++ Groq
++
++
++ xAI
++
++
++ OpenRouter
++
++
++ Model
++
++
++ API key
++
++
++ Base URL
++
++
++ Session and display
++
++
++ Resume the latest conversation
++
++
++ Resume conversations newer than this many hours
++
++
++ Show tool call details
++
++
++ Show debug information
++
++
++ Agent persona
++
++
++ Describe the personality, principles, and communication style the agent should follow.
++
++
++ Context
++
++
++ Use the current page as context
++
++
++ Use conversation context
++
++
++ Web search
++
++
++ Tools
++
++
++ Choose which kinds of actions the agent can use.
++
++
++ Page interaction
++
++
++ Tabs
++
++
++ Developer tools
++
++
++ Memory and skills
++
++
++ Web
++
++
++ Workspace
++
++
++ Individual tool permissions
++
++
++ Search source
++
++
++ Automatic
++
++
++ Model provider
++
++
++ DuckDuckGo
++
++
++ Jina API key
++
++
++ Memory and proactive suggestions
++
++
++ Enable memory
++
++
++ Enable proactive suggestions
++
++
++ Suggestion sensitivity
++
++
++ Conservative
++
++
++ Balanced
++
++
++ Proactive
++
++
++ Dream analysis
++
++
++ Enable Dream analysis
++
++
++ Show Dream debug information
++
++
++ Excluded domains, one per line
++
++
++ Skills and data
++
++
++ Manage skills
++
++
++ Inspect memory
++
++
++ Open Dream reports
++
++
++ Data and management
++
++
++ Memory
++
++
++ Workspace
++
++
++ Usage
++
++
++ Conversations
++
++
++ Preferences
++
++
++ Episodes
++
++
++ Total size
++
++
++ Root
++
++
++ Storage
++
++
++ Files
++
++
++ Recent activity
++
++
++ API calls
++
++
++ Tool calls
++
++
++ Prompt tokens
++
++
++ Completion tokens
++
++
++ Total tokens
++
++
++ Estimated cost
++
++
++ Last reset
++
++
++ Loading…
++
++
++ Memory summary couldn't be loaded. Retry to refresh it.
++
++
++ Memory couldn't be cleared. Try clearing memory again.
++
++
++ Memory was cleared, but the summary couldn't be refreshed. Retry to load it.
++
++
++ Workspace summary couldn't be loaded. Retry to refresh it.
++
++
++ Workspace couldn't be opened. Try opening the workspace again.
++
++
++ Usage summary couldn't be loaded. Retry to refresh it.
++
++
++ Usage couldn't be reset. Try resetting usage again.
++
++
++ Usage was reset, but the summary couldn't be refreshed. Retry to load it.
++
++
++ Retry
++
++
++ Clear memory
++
++
++ Open workspace
++
++
++ Reset usage
++
++
++ Clear all memory?
++
++
++ This permanently deletes all conversation memories, preferences, and episodes. This can't be undone.
++
++
++ Cancel
++
++
++ Clear memory
++
++
++ Reset usage statistics?
++
++
++ This sets all API, tool, and token counts to zero.
++
++
++ Cancel
++
++
++ Reset usage
++
++
++ No recent activity
++
++
++ No tool calls
++
++
++ Memory cleared
++
++
++ Workspace opened
++
++
++ Usage reset
++
++
++ Operation $1read, path $2notes/example.md
++
+
+ MCP server
+
@@ -304,7 +649,7 @@ diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
Google Profile photo
-@@ -4473,40 +4605,40 @@
+@@ -4473,40 +4935,40 @@
Your organization turned off saving passwords
@@ -357,7 +702,7 @@ diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
Go to notification settings
-@@ -4516,21 +4648,21 @@
+@@ -4516,21 +4978,21 @@
Safe Browsing
@@ -383,7 +728,7 @@ diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
{NUM_SITES, plural,
=1 {You can stop this site from sending future notifications.}
other {You can stop these sites from sending future notifications.}}
-@@ -4558,31 +4690,31 @@
+@@ -4558,31 +5020,31 @@
Safe Browsing is off
diff --git a/src/patches/chrome/browser/resources/settings/BUILD.gn.patch b/src/patches/chrome/browser/resources/settings/BUILD.gn.patch
index 481891db..683a53cd 100644
--- a/src/patches/chrome/browser/resources/settings/BUILD.gn.patch
+++ b/src/patches/chrome/browser/resources/settings/BUILD.gn.patch
@@ -2,11 +2,20 @@ diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resourc
index c2759ca2b0..c4e77c7225 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
-@@ -100,6 +100,7 @@ build_webui("build") {
+@@ -100,6 +100,8 @@ build_webui("build") {
"controls/settings_radio_group.ts",
"controls/settings_slider.ts",
"controls/settings_toggle_button.ts",
++ "dao_page/dao_agent_page.ts",
+ "dao_page/dao_page.ts",
"downloads_page/downloads_page.ts",
"glic_page/glic_page.ts",
"glic_page/glic_login_permissions_page.ts",
+@@ -338,6 +340,7 @@ build_webui("build") {
+ "base_mixin.ts",
+ "clear_browsing_data_dialog/clear_browsing_data_browser_proxy.ts",
+ "clear_browsing_data_dialog/clear_browsing_data_signin_util.ts",
++ "dao_page/dao_agent_settings_browser_proxy.ts",
+ "downloads_page/downloads_browser_proxy.ts",
+ "ensure_lazy_loaded.ts",
+ "focus_config.ts",
diff --git a/src/patches/chrome/browser/resources/settings/a11y_page/a11y_page_index.html.patch b/src/patches/chrome/browser/resources/settings/a11y_page/a11y_page_index.html.patch
new file mode 100644
index 00000000..59260e86
--- /dev/null
+++ b/src/patches/chrome/browser/resources/settings/a11y_page/a11y_page_index.html.patch
@@ -0,0 +1,16 @@
+diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page_index.html b/chrome/browser/resources/settings/a11y_page/a11y_page_index.html
+--- a/chrome/browser/resources/settings/a11y_page/a11y_page_index.html
++++ b/chrome/browser/resources/settings/a11y_page/a11y_page_index.html
+@@ -3,6 +3,12 @@
+ cr-view-manager[show-all] [slot=view][data-parent-view-id] {
+ display: none;
+ }
++
++ /* Keep active top-level content in document flow when Settings displays its
++ continuous overview. */
++ cr-view-manager [slot=view]:not(.closing) {
++ position: initial;
++ }
+
+
+
+
+
+
+
++ .dao-agent-heading {
++ color: var(--dao-settings-text);
++ font-size: 14px;
++ font-weight: 650;
++ letter-spacing: -.01em;
++ margin: 18px 18px 4px;
++ }
++
++ .dao-agent-description {
++ color: var(--dao-settings-text-secondary);
++ font-size: 12px;
++ line-height: 1.45;
++ margin: 0 18px 12px;
++ }
++
++ .dao-agent-grid {
++ display: grid;
++ gap: 0;
++ }
++
++ .dao-agent-section {
++ border-bottom: 1px solid var(--dao-settings-border-subtle);
++ }
++
++ .dao-agent-section:last-child {
++ border-bottom: 0;
++ }
++
++ .dao-agent-card {
++ background: transparent;
++ border: 0;
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ border-radius: 0;
++ box-shadow: none;
++ padding: 16px 18px 18px;
++ }
++
++ .dao-agent-card-title {
++ color: var(--dao-settings-text);
++ font-size: 13px;
++ font-weight: 650;
++ line-height: 20px;
++ margin-bottom: 4px;
++ }
++
++ .dao-agent-card-description {
++ color: var(--dao-settings-text-secondary);
++ font-size: 12px;
++ line-height: 1.45;
++ margin-bottom: 12px;
++ }
++
++ .dao-agent-fields {
++ display: grid;
++ gap: 12px;
++ grid-template-columns: repeat(2, minmax(0, 1fr));
++ }
++
++ .dao-agent-fields .wide {
++ grid-column: 1 / -1;
++ }
++
++ .dao-agent-select,
++ .dao-agent-textarea {
++ background: rgba(30, 40, 54, .045);
++ border: 1px solid var(--dao-settings-border);
++ border-radius: 8px;
++ box-sizing: border-box;
++ color: var(--dao-settings-text);
++ font: inherit;
++ padding: 8px 10px;
++ width: 100%;
++ }
++
++ @media (prefers-color-scheme: dark) {
++ .dao-agent-select,
++ .dao-agent-textarea {
++ background: rgba(255, 255, 255, .045);
++ }
++ }
++
++ .dao-agent-textarea {
++ min-block-size: 96px;
++ resize: vertical;
++ }
++
++ .dao-agent-field-label {
++ color: var(--dao-settings-text-secondary);
++ display: block;
++ font-size: 11px;
++ font-weight: 600;
++ line-height: 18px;
++ margin-bottom: 4px;
++ }
++
++ .dao-agent-toggle-row {
++ align-items: center;
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ display: flex;
++ gap: 16px;
++ justify-content: space-between;
++ min-height: 62px;
++ }
++
++ .dao-agent-toggle-row:first-of-type {
++ border-top: 0;
++ }
++
++ .dao-agent-toggle-copy {
++ color: var(--dao-settings-text);
++ font-size: 13px;
++ line-height: 20px;
++ }
++
++ .dao-agent-tool-details {
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ padding-top: 12px;
++ }
++
++ .dao-agent-tool-details summary {
++ color: var(--dao-settings-accent-strong);
++ cursor: pointer;
++ font-size: 12px;
++ font-weight: 600;
++ line-height: 28px;
++ }
++
++ .dao-agent-tool-list {
++ border: 1px solid var(--dao-settings-border);
++ border-radius: 8px;
++ max-height: 320px;
++ overflow: auto;
++ }
++
++ .dao-agent-tool-list .dao-agent-toggle-row {
++ min-height: 44px;
++ padding-inline: 10px;
++ }
++
++ .dao-agent-tool-name {
++ color: var(--dao-settings-text-secondary);
++ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
++ monospace;
++ font-size: 11px;
++ }
++
++ .dao-agent-management-group {
++ margin: 12px 0 0;
++ }
++
++ .dao-agent-management-heading {
++ color: var(--dao-settings-text);
++ font-size: 14px;
++ font-weight: 650;
++ letter-spacing: -.01em;
++ margin: 0 18px 10px;
++ }
++
++ .dao-agent-management-cards {
++ display: grid;
++ gap: 0;
++ grid-template-columns: minmax(0, 1fr);
++ }
++
++ .dao-agent-management-card {
++ background: transparent;
++ border: 0;
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ border-radius: 0;
++ box-shadow: none;
++ min-width: 0;
++ overflow: hidden;
++ }
++
++ .dao-agent-management-card-header {
++ align-items: baseline;
++ display: flex;
++ justify-content: space-between;
++ min-height: 50px;
++ padding: 0 16px;
++ }
++
++ .dao-agent-management-card-title {
++ color: var(--dao-settings-text);
++ font-size: 13px;
++ font-weight: 650;
++ line-height: 20px;
++ margin: 0;
++ }
++
++ .dao-agent-management-state {
++ align-items: center;
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ color: var(--dao-settings-text-secondary);
++ display: flex;
++ font-size: 12px;
++ gap: 10px;
++ line-height: 18px;
++ min-height: 62px;
++ padding: 0 16px;
++ }
++
++ .dao-agent-management-state.error {
++ color: var(--dao-settings-text);
++ }
++
++ .dao-agent-management-state cr-button {
++ flex: 0 0 auto;
++ }
++
++ .dao-agent-management-loading {
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ }
++
++ .dao-agent-management-loading-row {
++ align-items: center;
++ display: grid;
++ gap: 18px;
++ grid-template-columns: minmax(96px, 1fr) minmax(48px, 28%);
++ min-height: 62px;
++ padding: 0 16px;
++ }
++
++ .dao-agent-management-loading-row +
++ .dao-agent-management-loading-row {
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ }
++
++ .dao-agent-management-loading-line {
++ background: var(--dao-settings-accent-subtle);
++ border-radius: 999px;
++ height: 8px;
++ }
++
++ .dao-agent-management-loading-line.value {
++ justify-self: end;
++ width: 54%;
++ }
++
++ .dao-agent-management-metrics {
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ margin: 0;
++ }
++
++ .dao-agent-management-row {
++ align-items: center;
++ display: grid;
++ gap: 18px;
++ grid-template-columns: minmax(0, 1fr) minmax(0, auto);
++ min-height: 62px;
++ padding: 0 16px;
++ }
++
++ .dao-agent-management-row + .dao-agent-management-row {
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ }
++
++ .dao-agent-management-row dt,
++ .dao-agent-management-row dd {
++ margin: 0;
++ }
++
++ .dao-agent-management-row dt {
++ color: var(--dao-settings-text-secondary);
++ font-size: 12px;
++ line-height: 18px;
++ }
++
++ .dao-agent-management-row dd {
++ color: var(--dao-settings-text);
++ font-size: 12px;
++ font-variant-numeric: tabular-nums;
++ line-height: 18px;
++ max-width: 390px;
++ min-width: 0;
++ overflow: hidden;
++ text-align: end;
++ text-overflow: ellipsis;
++ white-space: nowrap;
++ }
++
++ .dao-agent-management-row dd.path,
++ .dao-agent-activity-path,
++ .dao-agent-tool-usage-name {
++ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
++ monospace;
++ }
++
++ .dao-agent-management-detail {
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ padding: 12px 16px 14px;
++ }
++
++ .dao-agent-management-detail-title {
++ color: var(--dao-settings-text-secondary);
++ font-size: 11px;
++ font-weight: 600;
++ line-height: 18px;
++ margin: 0 0 6px;
++ }
++
++ .dao-agent-activity-list,
++ .dao-agent-tool-usage-list {
++ list-style-position: inside;
++ margin: 0;
++ padding: 0;
++ }
++
++ .dao-agent-tool-usage-list {
++ list-style: none;
++ }
++
++ .dao-agent-activity-list li,
++ .dao-agent-tool-usage-list li {
++ color: var(--dao-settings-text);
++ font-size: 12px;
++ line-height: 20px;
++ min-width: 0;
++ }
++
++ .dao-agent-activity-main,
++ .dao-agent-tool-usage-list li {
++ display: grid;
++ gap: 12px;
++ grid-template-columns: minmax(0, 1fr) auto;
++ }
++
++ .dao-agent-activity-description {
++ display: grid;
++ gap: 8px;
++ grid-template-columns: auto minmax(0, 1fr);
++ min-width: 0;
++ }
++
++ .dao-agent-activity-operation {
++ color: var(--dao-settings-text-secondary);
++ }
++
++ .dao-agent-activity-path,
++ .dao-agent-tool-usage-name {
++ min-width: 0;
++ overflow: hidden;
++ text-overflow: ellipsis;
++ white-space: nowrap;
++ }
++
++ .dao-agent-activity-time,
++ .dao-agent-tool-usage-count,
++ .dao-agent-management-empty {
++ color: var(--dao-settings-text-tertiary);
++ font-size: 11px;
++ }
++
++ .dao-agent-management-actions {
++ align-items: center;
++ border-top: 1px solid var(--dao-settings-border-subtle);
++ display: flex;
++ gap: 8px;
++ justify-content: flex-end;
++ min-height: 62px;
++ padding: 0 12px;
++ }
++
++ .dao-agent-management-actions .dao-agent-link {
++ color: var(--dao-settings-accent-strong);
++ font-size: 12px;
++ line-height: 32px;
++ margin-inline-end: auto;
++ text-decoration: none;
++ }
++
++ .dao-agent-management-actions .dao-agent-link:hover {
++ text-decoration: underline;
++ }
++
++ .dao-agent-management-actions .dao-agent-link:focus-visible,
++ .dao-agent-management-actions cr-button:focus-visible {
++ outline: 2px solid var(--dao-settings-accent);
++ outline-offset: 2px;
++ }
++
++ .dao-agent-management-feedback {
++ color: var(--dao-settings-accent-strong);
++ font-size: 11px;
++ line-height: 18px;
++ min-height: 18px;
++ padding: 0 16px 8px;
++ text-align: end;
++ }
++
++ .dao-agent-management-dialog-body {
++ color: var(--dao-settings-text-secondary);
++ line-height: 1.5;
++ }
++
++ @media (max-width: 760px) {
++ .dao-agent-fields {
++ grid-template-columns: 1fr;
++ }
++
++ .dao-agent-management-actions {
++ align-items: stretch;
++ display: grid;
++ grid-template-columns: 1fr;
++ padding-block: 10px;
++ }
++
++ .dao-agent-management-actions .dao-agent-link {
++ margin-inline-end: 0;
++ }
++
++ .dao-agent-management-actions cr-button {
++ justify-self: start;
++ }
++
++ .dao-agent-management-state.error {
++ align-items: flex-start;
++ flex-direction: column;
++ justify-content: center;
++ padding-block: 12px;
++ }
++ }
++
++
++
++
++ $i18n{daoAgentSettingsTitle}
++
++ $i18n{daoAgentSettingsDescription}
++
++
++
++ $i18n{daoAgentManagementLoading}
++
++
++
++
++
++ $i18n{daoAgentSettingsLoadError}
++
++
++ $i18n{daoAgentManagementRetry}
++
++
++
++
++
++
++
++ $i18n{daoAgentGroupModelAndConnection}
++
++
++
$i18n{daoAgentModelTitle}
++
++ $i18n{daoAgentModelDescription}
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentGroupBehaviorAndContext}
++
++
++
$i18n{daoAgentSessionTitle}
++
++
++ $i18n{daoAgentResumeSessionLabel}
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentToolDetailsLabel}
++
++
++
++
++
++
++ $i18n{daoAgentDebugLabel}
++
++
++
++
++
++
++
$i18n{daoAgentSoulTitle}
++
++ $i18n{daoAgentSoulDescription}
++
++
++
++
++
$i18n{daoAgentContextTitle}
++
++
++ $i18n{daoAgentPageContextLabel}
++
++
++
++
++
++
++ $i18n{daoAgentConversationLabel}
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentGroupCapabilities}
++
++
++
$i18n{daoAgentToolsTitle}
++
++ $i18n{daoAgentToolsDescription}
++
++
++ $i18n{daoAgentToolsPage}
++
++
++
++
++ $i18n{daoAgentToolsTabs}
++
++
++
++
++
++ $i18n{daoAgentToolsDevTools}
++
++
++
++
++
++
++ $i18n{daoAgentToolsMemory}
++
++
++
++
++
++ $i18n{daoAgentToolsWeb}
++
++
++
++
++
++ $i18n{daoAgentToolsWorkspace}
++
++
++
++
++
++ $i18n{daoAgentIndividualTools}
++
++
++
++
++
$i18n{daoAgentSearchTitle}
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentGroupLearningAndAnalysis}
++
++
++
$i18n{daoAgentMemoryTitle}
++
++
++ $i18n{daoAgentMemoryLabel}
++
++
++
++
++
++
++ $i18n{daoAgentProactiveLabel}
++
++
++
++
++
++
++
++
$i18n{daoAgentDreamTitle}
++
++
++ $i18n{daoAgentDreamLabel}
++
++
++
++
++
++
++ $i18n{daoAgentDreamDebugLabel}
++
++
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentGroupDataAndManagement}
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementMemoryError}
++
++ $i18n{daoAgentManagementRetry}
++
++
++
++
++
++
++ $i18n{daoAgentManagementMemoryRefreshError}
++
++
++ $i18n{daoAgentManagementRetry}
++
++
++
++
++
++ $i18n{daoAgentManagementMemoryActionError}
++
++
++
++
++
++
- $i18n{daoAgentManagementConversations}
++ - [[formatInteger_(memorySummary_.conversationCount)]]
++
++
++
- $i18n{daoAgentManagementPreferences}
++ - [[formatInteger_(memorySummary_.preferenceCount)]]
++
++
++
- $i18n{daoAgentManagementEpisodes}
++ - [[formatInteger_(memorySummary_.episodeCount)]]
++
++
++
- $i18n{daoAgentManagementTotalSize}
++ - [[formatBytes_(memorySummary_.totalSize)]]
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementMemoryCleared}
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementWorkspaceError}
++
++ $i18n{daoAgentManagementRetry}
++
++
++
++
++
++ $i18n{daoAgentManagementWorkspaceActionError}
++
++
++
++
++
++
- $i18n{daoAgentManagementRoot}
++ -
++ [[workspaceSummary_.root]]
++
++
++
++
- $i18n{daoAgentManagementStorage}
++ -
++ [[formatBytes_(workspaceSummary_.usedBytes)]] /
++ [[formatBytes_(workspaceSummary_.capBytes)]]
++
++
++
++
- $i18n{daoAgentManagementFiles}
++ -
++ [[formatInteger_(workspaceSummary_.fileCount)]] /
++ [[formatInteger_(workspaceSummary_.fileCountCap)]]
++
++
++
++
++
++ $i18n{daoAgentManagementRecentActivity}
++
++
++
++
++ -
++
++
++
++ [[item.operation]]
++
++
++ [[item.path]]
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementNoRecentActivity}
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementWorkspaceOpened}
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementUsageError}
++
++ $i18n{daoAgentManagementRetry}
++
++
++
++
++
++
++ $i18n{daoAgentManagementUsageRefreshError}
++
++
++ $i18n{daoAgentManagementRetry}
++
++
++
++
++
++ $i18n{daoAgentManagementUsageActionError}
++
++
++
++
++
++
- $i18n{daoAgentManagementApiCalls}
++ - [[formatInteger_(usageStats_.apiCalls)]]
++
++
++
- $i18n{daoAgentManagementToolCalls}
++ - [[getUsageToolCallCount_(usageStats_)]]
++
++
++
- $i18n{daoAgentManagementPromptTokens}
++ - [[formatInteger_(usageStats_.promptTokens)]]
++
++
++
- $i18n{daoAgentManagementCompletionTokens}
++ - [[formatInteger_(usageStats_.completionTokens)]]
++
++
++
- $i18n{daoAgentManagementTotalTokens}
++ - [[formatInteger_(usageStats_.totalTokens)]]
++
++
++
- $i18n{daoAgentManagementEstimatedCost}
++ - [[formatCost_(usageStats_.estimatedCost)]]
++
++
++
- $i18n{daoAgentManagementLastReset}
++ - [[formatTimestamp_(usageStats_.lastReset)]]
++
++
++
++
++ $i18n{daoAgentManagementToolCalls}
++
++
++
++
++
++
++ $i18n{daoAgentManagementNoToolCalls}
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementUsageReset}
++
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementClearMemoryDialogTitle}
++
++
++ $i18n{daoAgentManagementClearMemoryDialogDescription}
++
++
++
++ $i18n{daoAgentManagementClearMemoryCancel}
++
++
++ $i18n{daoAgentManagementClearMemoryConfirm}
++
++
++
++
++
++
++
++
++ $i18n{daoAgentManagementResetUsageDialogTitle}
++
++
++ $i18n{daoAgentManagementResetUsageDialogDescription}
++
++
++
++ $i18n{daoAgentManagementResetUsageCancel}
++
++
++ $i18n{daoAgentManagementResetUsageConfirm}
++
++
++
++
++
++
diff --git a/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_page.ts.patch b/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_page.ts.patch
new file mode 100644
index 00000000..4a874138
--- /dev/null
+++ b/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_page.ts.patch
@@ -0,0 +1,817 @@
+diff --git a/chrome/browser/resources/settings/dao_page/dao_agent_page.ts b/chrome/browser/resources/settings/dao_page/dao_agent_page.ts
+new file mode 100644
+index 0000000000..0000000001
+--- /dev/null
++++ b/chrome/browser/resources/settings/dao_page/dao_agent_page.ts
+@@ -0,0 +1,811 @@
++// Copyright 2026 Dao Browser Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++/**
++ * @fileoverview
++ * 'settings-dao-agent-page' contains the detailed Dao Agent settings.
++ */
++import 'chrome://resources/cr_elements/cr_button/cr_button.js';
++import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
++import 'chrome://resources/cr_elements/cr_input/cr_input.js';
++import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
++import '../settings_page/settings_section.js';
++import '../settings_shared.css.js';
++
++import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
++import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
++import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
++
++import {loadTimeData} from '../i18n_setup.js';
++import {getSearchManager} from '../search_settings.js';
++import type {SearchResult} from '../search_settings.js';
++import type {SettingsPlugin} from '../settings_main/settings_plugin.js';
++
++import {
++ AGENT_PROVIDER_DEFAULTS,
++ AGENT_TOOL_GROUPS,
++ DaoAgentSettingsBrowserProxyImpl,
++ isDaoAgentMemorySummary,
++ isDaoAgentWorkspaceSummary,
++} from './dao_agent_settings_browser_proxy.js';
++import type {
++ DaoAgentMemorySummary,
++ DaoAgentSettingsBrowserProxy,
++ DaoAgentSettingsSnapshot,
++ DaoAgentUsageStats,
++ DaoAgentWorkspaceSummary,
++} from './dao_agent_settings_browser_proxy.js';
++import {getTemplate} from './dao_agent_page.html.js';
++
++export interface SettingsDaoAgentPageElement {
++ prefs: {[key: string]: any};
++}
++
++const SettingsDaoAgentPageElementBase = WebUiListenerMixin(PolymerElement);
++
++export class SettingsDaoAgentPageElement extends
++ SettingsDaoAgentPageElementBase implements SettingsPlugin {
++ static get is() {
++ return 'settings-dao-agent-page';
++ }
++
++ static get template() {
++ return getTemplate();
++ }
++
++ static get properties() {
++ return {
++ prefs: {
++ type: Object,
++ notify: true,
++ },
++ agentSettingsReady_: {type: Boolean, value: false},
++ agentSettingsLoading_: {type: Boolean, value: false},
++ agentSettingsError_: {type: Boolean, value: false},
++ agentActiveProvider_: {type: String, value: 'openai-compatible'},
++ agentApiKey_: {type: String, value: ''},
++ agentBaseUrl_: {type: String, value: 'https://api.openai.com/v1'},
++ agentModel_: {type: String, value: 'gpt-5'},
++ agentSoul_: {type: String, value: ''},
++ agentShowToolDetails_: {type: Boolean, value: false},
++ agentDebugMode_: {type: Boolean, value: false},
++ agentResumeLastSession_: {type: Boolean, value: true},
++ agentResumeStaleHours_: {type: String, value: '3'},
++ agentProactiveEnabled_: {type: Boolean, value: true},
++ agentPageContextEnabled_: {type: Boolean, value: true},
++ agentConversationEnabled_: {type: Boolean, value: true},
++ agentMemoryEnabled_: {type: Boolean, value: true},
++ agentDreamEnabled_: {type: Boolean, value: true},
++ agentDreamDebug_: {type: Boolean, value: false},
++ agentProactiveThreshold_: {type: String, value: 'balanced'},
++ agentSearchSource_: {type: String, value: 'auto'},
++ agentJinaApiKey_: {type: String, value: ''},
++ agentDreamExcludedDomains_: {type: String, value: ''},
++ agentPageToolsEnabled_: {type: Boolean, value: true},
++ agentTabToolsEnabled_: {type: Boolean, value: true},
++ agentDevToolsEnabled_: {type: Boolean, value: true},
++ agentMemoryToolsEnabled_: {type: Boolean, value: true},
++ agentWebToolsEnabled_: {type: Boolean, value: true},
++ agentWorkspaceToolsEnabled_: {type: Boolean, value: true},
++ agentToolNames_: {
++ type: Array,
++ value: () => Object.values(AGENT_TOOL_GROUPS).flat(),
++ },
++ memorySummary_: {type: Object, value: null},
++ memoryLoading_: {type: Boolean, value: false},
++ memoryError_: {type: Boolean, value: false},
++ clearMemoryPending_: {type: Boolean, value: false},
++ memoryActionSucceeded_: {type: Boolean, value: false},
++ memoryActionError_: {type: Boolean, value: false},
++ workspaceSummary_: {type: Object, value: null},
++ workspaceLoading_: {type: Boolean, value: false},
++ workspaceError_: {type: Boolean, value: false},
++ workspaceActionSucceeded_: {type: Boolean, value: false},
++ workspaceActionError_: {type: Boolean, value: false},
++ usageStats_: {type: Object, value: null},
++ usageLoading_: {type: Boolean, value: false},
++ usageError_: {type: Boolean, value: false},
++ resetUsagePending_: {type: Boolean, value: false},
++ usageActionSucceeded_: {type: Boolean, value: false},
++ usageActionError_: {type: Boolean, value: false},
++ };
++ }
++
++ declare prefs: {[key: string]: any};
++ private agentSettingsBrowserProxy_: DaoAgentSettingsBrowserProxy =
++ DaoAgentSettingsBrowserProxyImpl.getInstance();
++ private agentSettingsValues_: Record = {};
++ private optimisticDisabledTools_ = new Set();
++ private disabledToolsWritePromise_: Promise = Promise.resolve();
++ private disabledToolsMutationGeneration_ = 0;
++ private disabledToolsWritesPending_ = 0;
++ private preserveOptimisticDisabledTools_ = false;
++ private memorySummaryRequestGeneration_ = 0;
++ private workspaceSummaryRequestGeneration_ = 0;
++ private usageStatsRequestGeneration_ = 0;
++ private clearMemoryInvoker_: HTMLElement|null = null;
++ private resetUsageInvoker_: HTMLElement|null = null;
++
++ override connectedCallback() {
++ super.connectedCallback();
++ this.addWebUiListener(
++ 'dao-agent-settings-changed',
++ (snapshot: DaoAgentSettingsSnapshot) =>
++ this.updateAgentSettings_(snapshot));
++ void this.loadAgentSettings_();
++ this.addWebUiListener(
++ 'dao-agent-usage-stats-changed',
++ (stats: DaoAgentUsageStats) => this.updateUsageStats_(stats));
++ void this.loadMemorySummary_();
++ void this.loadWorkspaceSummary_();
++ void this.loadUsageStats_();
++ }
++
++ async searchContents(query: string): Promise {
++ const searchRequest = await getSearchManager().search(query, this);
++ return searchRequest.getSearchResult();
++ }
++
++ declare private agentSettingsReady_: boolean;
++ declare private agentSettingsLoading_: boolean;
++ declare private agentSettingsError_: boolean;
++ declare private agentActiveProvider_: string;
++ declare private agentApiKey_: string;
++ declare private agentBaseUrl_: string;
++ declare private agentModel_: string;
++ declare private agentSoul_: string;
++ declare private agentShowToolDetails_: boolean;
++ declare private agentDebugMode_: boolean;
++ declare private agentResumeLastSession_: boolean;
++ declare private agentResumeStaleHours_: string;
++ declare private agentProactiveEnabled_: boolean;
++ declare private agentPageContextEnabled_: boolean;
++ declare private agentConversationEnabled_: boolean;
++ declare private agentMemoryEnabled_: boolean;
++ declare private agentDreamEnabled_: boolean;
++ declare private agentDreamDebug_: boolean;
++ declare private agentProactiveThreshold_: string;
++ declare private agentSearchSource_: string;
++ declare private agentJinaApiKey_: string;
++ declare private agentDreamExcludedDomains_: string;
++ declare private agentPageToolsEnabled_: boolean;
++ declare private agentTabToolsEnabled_: boolean;
++ declare private agentDevToolsEnabled_: boolean;
++ declare private agentMemoryToolsEnabled_: boolean;
++ declare private agentWebToolsEnabled_: boolean;
++ declare private agentWorkspaceToolsEnabled_: boolean;
++ declare private agentToolNames_: string[];
++ declare private memorySummary_: DaoAgentMemorySummary|null;
++ declare private memoryLoading_: boolean;
++ declare private memoryError_: boolean;
++ declare private clearMemoryPending_: boolean;
++ declare private memoryActionSucceeded_: boolean;
++ declare private memoryActionError_: boolean;
++ declare private workspaceSummary_: DaoAgentWorkspaceSummary|null;
++ declare private workspaceLoading_: boolean;
++ declare private workspaceError_: boolean;
++ declare private workspaceActionSucceeded_: boolean;
++ declare private workspaceActionError_: boolean;
++ declare private usageStats_: DaoAgentUsageStats|null;
++ declare private usageLoading_: boolean;
++ declare private usageError_: boolean;
++ declare private resetUsagePending_: boolean;
++ declare private usageActionSucceeded_: boolean;
++ declare private usageActionError_: boolean;
++
++ get memorySummaryForTest(): DaoAgentMemorySummary|null {
++ return this.memorySummary_;
++ }
++
++ get memoryLoadingForTest(): boolean {
++ return this.memoryLoading_;
++ }
++
++ get memoryErrorForTest(): boolean {
++ return this.memoryError_;
++ }
++
++ get clearMemoryPendingForTest(): boolean {
++ return this.clearMemoryPending_;
++ }
++
++ get memoryActionErrorForTest(): boolean {
++ return this.memoryActionError_;
++ }
++
++ get workspaceSummaryForTest(): DaoAgentWorkspaceSummary|null {
++ return this.workspaceSummary_;
++ }
++
++ get workspaceLoadingForTest(): boolean {
++ return this.workspaceLoading_;
++ }
++
++ get workspaceErrorForTest(): boolean {
++ return this.workspaceError_;
++ }
++
++ get usageStatsForTest(): DaoAgentUsageStats|null {
++ return this.usageStats_;
++ }
++
++ get usageLoadingForTest(): boolean {
++ return this.usageLoading_;
++ }
++
++ get usageErrorForTest(): boolean {
++ return this.usageError_;
++ }
++
++ get resetUsagePendingForTest(): boolean {
++ return this.resetUsagePending_;
++ }
++
++ get usageActionErrorForTest(): boolean {
++ return this.usageActionError_;
++ }
++
++ private async loadMemorySummary_(): Promise {
++ const generation = ++this.memorySummaryRequestGeneration_;
++ this.memoryLoading_ = true;
++ this.memoryError_ = false;
++ try {
++ const summary = await this.agentSettingsBrowserProxy_.getMemorySummary();
++ if (!isDaoAgentMemorySummary(summary)) {
++ throw new Error();
++ }
++ if (generation === this.memorySummaryRequestGeneration_) {
++ this.memorySummary_ = summary;
++ }
++ } catch {
++ if (generation === this.memorySummaryRequestGeneration_) {
++ this.memoryError_ = true;
++ }
++ } finally {
++ if (generation === this.memorySummaryRequestGeneration_) {
++ this.memoryLoading_ = false;
++ }
++ }
++ }
++
++ private async loadWorkspaceSummary_(): Promise {
++ const generation = ++this.workspaceSummaryRequestGeneration_;
++ this.workspaceLoading_ = true;
++ this.workspaceError_ = false;
++ try {
++ const summary =
++ await this.agentSettingsBrowserProxy_.getWorkspaceSummary();
++ if (!isDaoAgentWorkspaceSummary(summary)) {
++ throw new Error();
++ }
++ if (generation === this.workspaceSummaryRequestGeneration_) {
++ this.workspaceSummary_ = summary;
++ }
++ } catch {
++ if (generation === this.workspaceSummaryRequestGeneration_) {
++ this.workspaceError_ = true;
++ }
++ } finally {
++ if (generation === this.workspaceSummaryRequestGeneration_) {
++ this.workspaceLoading_ = false;
++ }
++ }
++ }
++
++ private async loadUsageStats_(): Promise {
++ const generation = ++this.usageStatsRequestGeneration_;
++ this.usageLoading_ = true;
++ this.usageError_ = false;
++ try {
++ const stats = await this.agentSettingsBrowserProxy_.getUsageStats();
++ if (generation === this.usageStatsRequestGeneration_) {
++ this.usageStats_ = {...stats, toolCalls: {...stats.toolCalls}};
++ }
++ } catch {
++ if (generation === this.usageStatsRequestGeneration_) {
++ this.usageError_ = true;
++ }
++ } finally {
++ if (generation === this.usageStatsRequestGeneration_) {
++ this.usageLoading_ = false;
++ }
++ }
++ }
++
++ private updateUsageStats_(stats: DaoAgentUsageStats): void {
++ ++this.usageStatsRequestGeneration_;
++ this.usageStats_ = {...stats, toolCalls: {...stats.toolCalls}};
++ this.usageLoading_ = false;
++ this.usageError_ = false;
++ }
++
++ private onRetryMemory_(): void {
++ void this.loadMemorySummary_();
++ }
++
++ private onRetryWorkspace_(): void {
++ void this.loadWorkspaceSummary_();
++ }
++
++ private onRetryUsage_(): void {
++ void this.loadUsageStats_();
++ }
++
++ private getManagementDialog_(id: string): CrDialogElement {
++ return this.shadowRoot!.querySelector(`#${id}`)!;
++ }
++
++ private closeManagementDialog_(
++ dialog: CrDialogElement, invoker: HTMLElement|null): void {
++ if (dialog.open) {
++ dialog.close();
++ }
++ invoker?.focus();
++ }
++
++ private onClearMemory_(event: Event): void {
++ this.memoryActionSucceeded_ = false;
++ this.memoryActionError_ = false;
++ this.clearMemoryInvoker_ = event.currentTarget as HTMLElement;
++ this.getManagementDialog_('clearAllMemoryDialog').showModal();
++ }
++
++ private onClearMemoryDialogClose_(): void {
++ this.clearMemoryInvoker_?.focus();
++ this.clearMemoryInvoker_ = null;
++ }
++
++ private onCancelClearMemory_(): void {
++ if (this.clearMemoryPending_) {
++ return;
++ }
++ this.closeManagementDialog_(
++ this.getManagementDialog_('clearAllMemoryDialog'),
++ this.clearMemoryInvoker_);
++ this.clearMemoryInvoker_ = null;
++ }
++
++ private async onConfirmClearMemory_(): Promise {
++ if (this.clearMemoryPending_) {
++ return;
++ }
++ this.clearMemoryPending_ = true;
++ try {
++ if (await this.agentSettingsBrowserProxy_.clearAllMemory()) {
++ this.memoryActionSucceeded_ = true;
++ this.memoryActionError_ = false;
++ this.memoryError_ = false;
++ void this.loadMemorySummary_();
++ } else {
++ this.memoryActionSucceeded_ = false;
++ this.memoryActionError_ = true;
++ }
++ } catch {
++ this.memoryActionSucceeded_ = false;
++ this.memoryActionError_ = true;
++ } finally {
++ this.clearMemoryPending_ = false;
++ this.closeManagementDialog_(
++ this.getManagementDialog_('clearAllMemoryDialog'),
++ this.clearMemoryInvoker_);
++ this.clearMemoryInvoker_ = null;
++ }
++ }
++
++ private async onOpenWorkspace_(): Promise {
++ this.workspaceActionSucceeded_ = false;
++ this.workspaceActionError_ = false;
++ try {
++ if (await this.agentSettingsBrowserProxy_.openWorkspace()) {
++ this.workspaceActionSucceeded_ = true;
++ } else {
++ this.workspaceActionError_ = true;
++ }
++ } catch {
++ this.workspaceActionError_ = true;
++ }
++ }
++
++ private onResetUsage_(event: Event): void {
++ this.usageActionSucceeded_ = false;
++ this.usageActionError_ = false;
++ this.resetUsageInvoker_ = event.currentTarget as HTMLElement;
++ this.getManagementDialog_('resetUsageStatsDialog').showModal();
++ }
++
++ private onResetUsageDialogClose_(): void {
++ this.resetUsageInvoker_?.focus();
++ this.resetUsageInvoker_ = null;
++ }
++
++ private onCancelResetUsage_(): void {
++ if (this.resetUsagePending_) {
++ return;
++ }
++ this.closeManagementDialog_(
++ this.getManagementDialog_('resetUsageStatsDialog'),
++ this.resetUsageInvoker_);
++ this.resetUsageInvoker_ = null;
++ }
++
++ private async onConfirmResetUsage_(): Promise {
++ if (this.resetUsagePending_) {
++ return;
++ }
++ this.resetUsagePending_ = true;
++ try {
++ if (await this.agentSettingsBrowserProxy_.resetUsageStats()) {
++ this.usageActionSucceeded_ = true;
++ this.usageActionError_ = false;
++ this.usageError_ = false;
++ void this.loadUsageStats_();
++ } else {
++ this.usageActionSucceeded_ = false;
++ this.usageActionError_ = true;
++ }
++ } catch {
++ this.usageActionSucceeded_ = false;
++ this.usageActionError_ = true;
++ } finally {
++ this.resetUsagePending_ = false;
++ this.closeManagementDialog_(
++ this.getManagementDialog_('resetUsageStatsDialog'),
++ this.resetUsageInvoker_);
++ this.resetUsageInvoker_ = null;
++ }
++ }
++
++ private formatBytes_(bytes: number): string {
++ const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'];
++ const safeBytes = Number.isFinite(bytes) ? Math.max(0, bytes) : 0;
++ const unitIndex = safeBytes === 0 ? 0 : Math.min(
++ Math.floor(Math.log(safeBytes) / Math.log(1024)), units.length - 1);
++ const value = safeBytes / 1024 ** unitIndex;
++ return new Intl.NumberFormat(undefined, {
++ style: 'unit',
++ unit: units[unitIndex],
++ unitDisplay: 'short',
++ maximumFractionDigits: unitIndex === 0 ? 0 : 1,
++ }).format(value);
++ }
++
++ private formatInteger_(value: number): string {
++ return new Intl.NumberFormat(undefined, {
++ maximumFractionDigits: 0,
++ }).format(value);
++ }
++
++ private formatCost_(value: number): string {
++ return new Intl.NumberFormat(undefined, {
++ style: 'currency',
++ currency: 'USD',
++ minimumFractionDigits: 2,
++ maximumFractionDigits: 6,
++ }).format(value);
++ }
++
++ private formatTimestamp_(value: number|string): string {
++ return new Intl.DateTimeFormat(undefined, {
++ dateStyle: 'medium',
++ timeStyle: 'short',
++ }).format(new Date(value));
++ }
++
++ private formatActivityLabel_(operation: string, path: string): string {
++ return loadTimeData.getStringF(
++ 'daoAgentManagementActivityLabel', operation, path);
++ }
++
++ private showInitialLoading_(loading: boolean, data: unknown): boolean {
++ return loading && !data;
++ }
++
++ private showSummaryLoadError_(
++ error: boolean, actionSucceeded: boolean): boolean {
++ return error && !actionSucceeded;
++ }
++
++ private showSummaryRefreshError_(
++ error: boolean, actionSucceeded: boolean): boolean {
++ return error && actionSucceeded;
++ }
++
++ private showActionSuccess_(
++ actionSucceeded: boolean, summaryError: boolean): boolean {
++ return actionSucceeded && !summaryError;
++ }
++
++ private getUsageToolEntries_(stats: DaoAgentUsageStats|null): Array<{
++ name: string,
++ count: number,
++ }> {
++ return stats ? Object.entries(stats.toolCalls)
++ .map(([name, count]) => ({name, count}))
++ .sort((a, b) => b.count - a.count) :
++ [];
++ }
++
++ private getUsageToolCallCount_(stats: DaoAgentUsageStats|null): string {
++ const count = stats ?
++ Object.values(stats.toolCalls).reduce((sum, value) => sum + value, 0) :
++ 0;
++ return this.formatInteger_(count);
++ }
++
++ private hasUsageToolEntries_(stats: DaoAgentUsageStats|null): boolean {
++ return this.getUsageToolEntries_(stats).length > 0;
++ }
++
++ private hasNoUsageToolEntries_(stats: DaoAgentUsageStats|null): boolean {
++ return !this.hasUsageToolEntries_(stats);
++ }
++
++ private async loadAgentSettings_(): Promise {
++ this.agentSettingsLoading_ = true;
++ this.agentSettingsError_ = false;
++ this.agentSettingsReady_ = false;
++ try {
++ const snapshot = await this.agentSettingsBrowserProxy_.getSettings();
++ this.updateAgentSettings_(snapshot);
++ } catch {
++ this.agentSettingsError_ = true;
++ } finally {
++ this.agentSettingsLoading_ = false;
++ }
++ }
++
++ private onRetryAgentSettings_(): void {
++ void this.loadAgentSettings_();
++ }
++
++ private readAgentBoolean_(key: string, fallback: boolean): boolean {
++ const value = this.agentSettingsValues_[key];
++ return value === undefined ? fallback : value === 'true';
++ }
++
++ private getAgentProviders_(): Record {
++ try {
++ const parsed = JSON.parse(
++ this.agentSettingsValues_['dao_agent_providers'] || '{}');
++ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ?
++ parsed : {};
++ } catch {
++ return {};
++ }
++ }
++
++ private updateActiveAgentProviderFields_() {
++ const provider = this.getAgentProviders_()[this.agentActiveProvider_];
++ const defaults = AGENT_PROVIDER_DEFAULTS[this.agentActiveProvider_] ||
++ AGENT_PROVIDER_DEFAULTS['openai-compatible'];
++ this.agentApiKey_ = provider?.apiKey || '';
++ this.agentBaseUrl_ = provider?.baseUrl || defaults.baseUrl;
++ this.agentModel_ = provider?.model || defaults.model;
++ }
++
++ private parseDisabledTools_(value: string|undefined): Set {
++ try {
++ const parsed = JSON.parse(value || '[]');
++ return Array.isArray(parsed) ?
++ new Set(parsed.filter(
++ (tool: unknown): tool is string => typeof tool === 'string')) :
++ new Set();
++ } catch {
++ return new Set();
++ }
++ }
++
++ private serializeDisabledTools_(): string {
++ return JSON.stringify([...this.optimisticDisabledTools_].sort());
++ }
++
++ private updateAgentSettings_(
++ snapshot: DaoAgentSettingsSnapshot, forceDisabledTools = false) {
++ const snapshotDisabledTools =
++ this.parseDisabledTools_(snapshot.values['dao_disabled_tools']);
++ if (forceDisabledTools || !this.preserveOptimisticDisabledTools_) {
++ this.optimisticDisabledTools_ = snapshotDisabledTools;
++ }
++ const disabledTools = this.preserveOptimisticDisabledTools_ &&
++ !forceDisabledTools ?
++ this.optimisticDisabledTools_ : snapshotDisabledTools;
++ this.agentSettingsValues_ = {
++ ...snapshot.values,
++ dao_disabled_tools: JSON.stringify([...disabledTools].sort()),
++ };
++ this.agentActiveProvider_ =
++ this.agentSettingsValues_['dao_agent_active_provider'] ||
++ 'openai-compatible';
++ this.updateActiveAgentProviderFields_();
++ this.agentSoul_ = this.agentSettingsValues_['dao_agent_soul'] || '';
++ this.agentShowToolDetails_ =
++ this.readAgentBoolean_('dao_tool_call_show_details', false);
++ this.agentDebugMode_ =
++ this.readAgentBoolean_('dao_agent_debug_mode', false);
++ this.agentResumeLastSession_ =
++ this.readAgentBoolean_('dao_resume_last_session', true);
++ this.agentResumeStaleHours_ =
++ snapshot.values['dao_resume_stale_hours'] || '3';
++ this.agentProactiveEnabled_ =
++ this.readAgentBoolean_('dao_proactive_enabled', true);
++ this.agentPageContextEnabled_ =
++ this.readAgentBoolean_('dao_page_context_enabled', true);
++ this.agentConversationEnabled_ =
++ this.readAgentBoolean_('dao_conversation_enabled', true);
++ this.agentMemoryEnabled_ =
++ this.readAgentBoolean_('dao_agent_memory_enabled', true);
++ this.agentDreamEnabled_ =
++ this.readAgentBoolean_('dao_dream_enabled', true);
++ this.agentDreamDebug_ =
++ this.readAgentBoolean_('dao_dream_debug', false);
++ this.agentProactiveThreshold_ =
++ this.agentSettingsValues_['dao_proactive_threshold'] || 'balanced';
++ this.agentSearchSource_ =
++ this.agentSettingsValues_['dao_search_source'] || 'auto';
++ this.agentJinaApiKey_ =
++ this.agentSettingsValues_['dao_jina_api_key'] || '';
++ const groupEnabled = (group: string) =>
++ AGENT_TOOL_GROUPS[group].some(tool => !disabledTools.has(tool));
++ this.agentPageToolsEnabled_ = groupEnabled('page');
++ this.agentTabToolsEnabled_ = groupEnabled('tabs');
++ this.agentDevToolsEnabled_ = groupEnabled('devtools');
++ this.agentMemoryToolsEnabled_ = groupEnabled('memory');
++ this.agentWebToolsEnabled_ = groupEnabled('web');
++ this.agentWorkspaceToolsEnabled_ = groupEnabled('workspace');
++ try {
++ const domains = JSON.parse(
++ this.agentSettingsValues_['dao_dream_excluded_domains'] || '[]');
++ this.agentDreamExcludedDomains_ = Array.isArray(domains) ?
++ domains.join('\n') : '';
++ } catch {
++ this.agentDreamExcludedDomains_ = '';
++ }
++ this.agentSettingsReady_ = true;
++ this.agentSettingsError_ = false;
++ }
++
++ private queueDisabledToolsWrite_(): void {
++ const value = this.serializeDisabledTools_();
++ const generation = ++this.disabledToolsMutationGeneration_;
++ ++this.disabledToolsWritesPending_;
++ this.preserveOptimisticDisabledTools_ = true;
++ this.agentSettingsValues_ = {
++ ...this.agentSettingsValues_,
++ dao_disabled_tools: value,
++ };
++
++ this.disabledToolsWritePromise_ =
++ this.disabledToolsWritePromise_.then(async () => {
++ try {
++ await this.agentSettingsBrowserProxy_.setSetting(
++ 'dao_disabled_tools', value);
++ } catch {
++ }
++ --this.disabledToolsWritesPending_;
++ if (this.disabledToolsWritesPending_ !== 0) {
++ return;
++ }
++
++ try {
++ const snapshot =
++ await this.agentSettingsBrowserProxy_.getSettings();
++ if (generation === this.disabledToolsMutationGeneration_ &&
++ this.disabledToolsWritesPending_ === 0) {
++ this.preserveOptimisticDisabledTools_ = false;
++ this.updateAgentSettings_(snapshot, true);
++ }
++ } catch {
++ if (generation === this.disabledToolsMutationGeneration_ &&
++ this.disabledToolsWritesPending_ === 0) {
++ this.agentSettingsReady_ = false;
++ this.agentSettingsError_ = true;
++ }
++ }
++ });
++ }
++
++ private async setAgentSetting_(key: string, value: string|null) {
++ const accepted = await this.agentSettingsBrowserProxy_.setSetting(
++ key, value);
++ if (accepted && value !== null) {
++ this.agentSettingsValues_ = {...this.agentSettingsValues_, [key]: value};
++ }
++ }
++
++ private onAgentBooleanSettingChange_(event: Event) {
++ const target = event.currentTarget as HTMLElement & {checked: boolean};
++ const key = target.dataset['setting'];
++ if (key) {
++ void this.setAgentSetting_(key, String(target.checked));
++ }
++ }
++
++ private onAgentTextSettingChange_(event: Event) {
++ const target = event.currentTarget as HTMLInputElement;
++ const key = target.dataset['setting'];
++ if (key) {
++ void this.setAgentSetting_(key, target.value);
++ }
++ }
++
++ private onAgentProviderChange_(event: Event) {
++ this.agentActiveProvider_ =
++ (event.currentTarget as HTMLSelectElement).value;
++ void this.setAgentSetting_(
++ 'dao_agent_active_provider', this.agentActiveProvider_);
++ this.updateActiveAgentProviderFields_();
++ }
++
++ private onAgentProviderFieldChange_() {
++ const providers = this.getAgentProviders_();
++ providers[this.agentActiveProvider_] = {
++ apiKey: this.agentApiKey_,
++ baseUrl: this.agentBaseUrl_,
++ model: this.agentModel_,
++ };
++ void this.setAgentSetting_(
++ 'dao_agent_providers', JSON.stringify(providers));
++ }
++
++ private onAgentDreamDomainsChange_() {
++ const domains = this.agentDreamExcludedDomains_.split('\n')
++ .map(domain => domain.trim())
++ .filter(Boolean);
++ void this.setAgentSetting_(
++ 'dao_dream_excluded_domains', JSON.stringify(domains));
++ }
++
++ private onAgentToolGroupChange_(event: Event) {
++ const target = event.currentTarget as HTMLElement & {checked: boolean};
++ const group = target.dataset['toolGroup'];
++ const tools = group ? AGENT_TOOL_GROUPS[group] : undefined;
++ if (!tools) {
++ return;
++ }
++ for (const tool of tools) {
++ if (target.checked) {
++ this.optimisticDisabledTools_.delete(tool);
++ } else {
++ this.optimisticDisabledTools_.add(tool);
++ }
++ }
++ this.queueDisabledToolsWrite_();
++ }
++
++ private isAgentToolEnabled_(
++ tool: string, values: Record): boolean {
++ try {
++ const parsed = JSON.parse(values['dao_disabled_tools'] || '[]');
++ return !Array.isArray(parsed) || !parsed.includes(tool);
++ } catch {
++ return true;
++ }
++ }
++
++ private onAgentToolChange_(event: Event) {
++ const target = event.currentTarget as HTMLElement & {checked: boolean};
++ const tool = target.dataset['toolName'];
++ if (!tool) {
++ return;
++ }
++ if (target.checked) {
++ this.optimisticDisabledTools_.delete(tool);
++ } else {
++ this.optimisticDisabledTools_.add(tool);
++ }
++ this.queueDisabledToolsWrite_();
++ }
++
++}
++
++declare global {
++ interface HTMLElementTagNameMap {
++ 'settings-dao-agent-page': SettingsDaoAgentPageElement;
++ }
++}
++
++customElements.define(
++ SettingsDaoAgentPageElement.is, SettingsDaoAgentPageElement);
diff --git a/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_settings_browser_proxy.ts.patch b/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_settings_browser_proxy.ts.patch
new file mode 100644
index 00000000..9e90dbc5
--- /dev/null
+++ b/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_settings_browser_proxy.ts.patch
@@ -0,0 +1,183 @@
+diff --git a/chrome/browser/resources/settings/dao_page/dao_agent_settings_browser_proxy.ts b/chrome/browser/resources/settings/dao_page/dao_agent_settings_browser_proxy.ts
+new file mode 100644
+index 0000000000..0000000001
+--- /dev/null
++++ b/chrome/browser/resources/settings/dao_page/dao_agent_settings_browser_proxy.ts
+@@ -0,0 +1,177 @@
++// Copyright 2026 Dao Browser Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++import {sendWithPromise} from 'chrome://resources/js/cr.js';
++
++export interface DaoAgentSettingsSnapshot {
++ migrationVersion: number;
++ values: Record;
++ usageStats: DaoAgentUsageStats;
++}
++
++export interface DaoAgentMemorySummary {
++ totalSize: number;
++ conversationCount: number;
++ episodeCount: number;
++ preferenceCount: number;
++}
++
++export interface DaoAgentWorkspaceActivity {
++ timestamp: string;
++ operation: string;
++ path: string;
++}
++
++export interface DaoAgentWorkspaceSummary {
++ root: string;
++ usedBytes: number;
++ capBytes: number;
++ fileCount: number;
++ fileCountCap: number;
++ recentActivity: DaoAgentWorkspaceActivity[];
++}
++
++export interface DaoAgentUsageStats {
++ apiCalls: number;
++ toolCalls: Record;
++ promptTokens: number;
++ completionTokens: number;
++ totalTokens: number;
++ estimatedCost: number;
++ lastReset: number;
++}
++
++function isRecord(value: unknown): value is Record {
++ return typeof value === 'object' && value !== null && !Array.isArray(value);
++}
++
++function isNonNegativeFiniteNumber(value: unknown): value is number {
++ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
++}
++
++export function isDaoAgentMemorySummary(
++ value: unknown): value is DaoAgentMemorySummary {
++ return isRecord(value) &&
++ isNonNegativeFiniteNumber(value['totalSize']) &&
++ isNonNegativeFiniteNumber(value['conversationCount']) &&
++ isNonNegativeFiniteNumber(value['episodeCount']) &&
++ isNonNegativeFiniteNumber(value['preferenceCount']);
++}
++
++export function isDaoAgentWorkspaceActivity(
++ value: unknown): value is DaoAgentWorkspaceActivity {
++ return isRecord(value) && typeof value['timestamp'] === 'string' &&
++ typeof value['operation'] === 'string' &&
++ typeof value['path'] === 'string';
++}
++
++export function isDaoAgentWorkspaceSummary(
++ value: unknown): value is DaoAgentWorkspaceSummary {
++ if (!isRecord(value)) {
++ return false;
++ }
++ const recentActivity = value['recentActivity'];
++ return typeof value['root'] === 'string' &&
++ isNonNegativeFiniteNumber(value['usedBytes']) &&
++ isNonNegativeFiniteNumber(value['capBytes']) &&
++ isNonNegativeFiniteNumber(value['fileCount']) &&
++ isNonNegativeFiniteNumber(value['fileCountCap']) &&
++ Array.isArray(recentActivity) &&
++ recentActivity.every(isDaoAgentWorkspaceActivity);
++}
++
++export interface DaoAgentSettingsBrowserProxy {
++ getSettings(): Promise;
++ setSetting(key: string, value: string|null): Promise;
++ getMemorySummary(): Promise;
++ clearAllMemory(): Promise;
++ getWorkspaceSummary(): Promise;
++ openWorkspace(): Promise;
++ getUsageStats(): Promise;
++ resetUsageStats(): Promise;
++}
++
++export class DaoAgentSettingsBrowserProxyImpl implements
++ DaoAgentSettingsBrowserProxy {
++ getSettings(): Promise {
++ return sendWithPromise('getDaoAgentSettings');
++ }
++
++ setSetting(key: string, value: string|null): Promise {
++ return sendWithPromise('setDaoAgentSetting', key, value);
++ }
++
++ getMemorySummary(): Promise {
++ return sendWithPromise('getDaoAgentMemorySummary');
++ }
++
++ clearAllMemory(): Promise {
++ return sendWithPromise('clearAllDaoAgentMemory');
++ }
++
++ getWorkspaceSummary(): Promise {
++ return sendWithPromise('getDaoAgentWorkspaceSummary');
++ }
++
++ openWorkspace(): Promise {
++ return sendWithPromise('openDaoAgentWorkspace');
++ }
++
++ getUsageStats(): Promise {
++ return sendWithPromise('getDaoAgentUsageStats');
++ }
++
++ resetUsageStats(): Promise {
++ return sendWithPromise('resetDaoAgentUsageStats');
++ }
++
++ static getInstance(): DaoAgentSettingsBrowserProxy {
++ return agentSettingsInstance ||
++ (agentSettingsInstance = new DaoAgentSettingsBrowserProxyImpl());
++ }
++
++ static setInstance(proxy: DaoAgentSettingsBrowserProxy): void {
++ agentSettingsInstance = proxy;
++ }
++}
++
++let agentSettingsInstance: DaoAgentSettingsBrowserProxy|null = null;
++
++export const AGENT_TOOL_GROUPS: Record = {
++ page: [
++ 'get_page_info', 'get_page_html', 'get_accessibility_tree',
++ 'resolve_element_context', 'capture_screenshot', 'click_element',
++ 'agent_click', 'click_by_ref', 'move_cursor', 'highlight_element',
++ 'scroll_down', 'scroll_up', 'scroll_to_element', 'press_key_chord',
++ 'type_text', 'execute_script',
++ ],
++ tabs: ['list_tabs', 'switch_tab', 'open_tab', 'close_tab'],
++ devtools: [
++ 'enable_network_tracking', 'get_network_requests',
++ 'clear_network_requests', 'get_network_body',
++ 'enable_console_tracking', 'get_console_messages',
++ 'clear_console_messages', 'list_page_resources',
++ 'get_resource_content', 'search_in_resources',
++ ],
++ memory: ['update_soul', 'save_memory', 'save_skill', 'activate_skill'],
++ web: ['web_search', 'fetch_url'],
++ workspace: [
++ 'workspace_read', 'workspace_write', 'workspace_edit', 'apply_patch',
++ 'list_files', 'download',
++ ],
++};
++
++export const AGENT_PROVIDER_DEFAULTS: Record<
++ string, {baseUrl: string, model: string}> = {
++ 'openai-compatible': {
++ baseUrl: 'https://api.openai.com/v1',
++ model: 'gpt-5',
++ },
++ openai: {baseUrl: '', model: 'gpt-5'},
++ anthropic: {baseUrl: '', model: 'claude-sonnet-4-5'},
++ google: {baseUrl: '', model: 'gemini-2.5-flash'},
++ groq: {baseUrl: '', model: 'llama-3.3-70b-versatile'},
++ xai: {baseUrl: '', model: 'grok-4'},
++ openrouter: {baseUrl: '', model: 'openrouter/auto'},
++};
diff --git a/src/patches/chrome/browser/resources/settings/dao_page/dao_page.ts.patch b/src/patches/chrome/browser/resources/settings/dao_page/dao_page.ts.patch
index 33095d73..c64c5c62 100644
--- a/src/patches/chrome/browser/resources/settings/dao_page/dao_page.ts.patch
+++ b/src/patches/chrome/browser/resources/settings/dao_page/dao_page.ts.patch
@@ -3,14 +3,14 @@ new file mode 100644
index 0000000000..0000000001
--- /dev/null
+++ b/chrome/browser/resources/settings/dao_page/dao_page.ts
-@@ -0,0 +1,359 @@
+@@ -0,0 +1,360 @@
+// Copyright 2026 Dao Browser Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
-+ * 'settings-dao-page' contains Dao-specific browser settings.
++ * 'settings-dao-page' contains the Dao settings overview.
+ */
+import 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import '../controls/settings_toggle_button.js';
@@ -208,6 +208,7 @@ index 0000000000..0000000001
+ private daoMcpBrowserProxy_: DaoMcpBrowserProxy =
+ DaoMcpBrowserProxyImpl.getInstance();
+
++
+ override connectedCallback() {
+ super.connectedCallback();
+ this.addWebUiListener(
diff --git a/src/patches/chrome/browser/resources/settings/lazy_load.ts.patch b/src/patches/chrome/browser/resources/settings/lazy_load.ts.patch
new file mode 100644
index 00000000..54375f28
--- /dev/null
+++ b/src/patches/chrome/browser/resources/settings/lazy_load.ts.patch
@@ -0,0 +1,12 @@
+diff --git a/chrome/browser/resources/settings/lazy_load.ts b/chrome/browser/resources/settings/lazy_load.ts
+index 0c13b5d2f1..0c13b5d2f2 100644
+--- a/chrome/browser/resources/settings/lazy_load.ts
++++ b/chrome/browser/resources/settings/lazy_load.ts
+@@ -20,6 +20,7 @@ import './clear_browsing_data_dialog/clear_browsing_data_account_indicator.js';
+ //
+ import './clear_browsing_data_dialog/clear_browsing_data_dialog_v2.js';
+ import './clear_browsing_data_dialog/clear_browsing_data_time_picker.js';
++import './dao_page/dao_agent_page.js';
+ import './glic_page/glic_login_permissions_page.js';
+ import './privacy_page/cookies_page.js';
+ import './privacy_page/privacy_guide/privacy_guide_dialog.js';
diff --git a/src/patches/chrome/browser/resources/settings/people_page/people_page_index.html.patch b/src/patches/chrome/browser/resources/settings/people_page/people_page_index.html.patch
new file mode 100644
index 00000000..e7efd249
--- /dev/null
+++ b/src/patches/chrome/browser/resources/settings/people_page/people_page_index.html.patch
@@ -0,0 +1,16 @@
+diff --git a/chrome/browser/resources/settings/people_page/people_page_index.html b/chrome/browser/resources/settings/people_page/people_page_index.html
+--- a/chrome/browser/resources/settings/people_page/people_page_index.html
++++ b/chrome/browser/resources/settings/people_page/people_page_index.html
+@@ -3,6 +3,12 @@
+ cr-view-manager[show-all] [slot=view][data-parent-view-id] {
+ display: none;
+ }
++
++ /* Keep active top-level content in document flow when Settings displays its
++ continuous overview. */
++ cr-view-manager [slot=view]:not(.closing) {
++ position: initial;
++ }
+
+
+
+
+- // Display the default views if in search mode, since they could be part
+- // of search results.
+- return this.inSearchMode ? this.getDefaultViews_() : [];
++ // Dao's BASIC route is a continuous overview, so Privacy's root views
++ // must participate even when Settings is not searching.
++ return this.getDefaultViews_();
+ default: {
+ // Handle case of routes whose UIs are still hosted within
+ // settings-privacy-page.
+@@ -356,9 +356,10 @@ export class SettingsPrivacyPageIndexElement extends
+ return visibility !== false;
+ }
+
+ private renderView_(route: Route): boolean {
+- return this.inSearchMode ||
+- (!!this.currentRoute && this.currentRoute === route);
++ return this.inSearchMode || (!!this.currentRoute &&
++ (this.currentRoute === route ||
++ (this.currentRoute === routes.BASIC && route === routes.PRIVACY)));
+ }
+
+ private renderPrivacyView_(): boolean {
+@@ -371,9 +372,10 @@ export class SettingsPrivacyPageIndexElement extends
+ }
+ //
+
+ return this.inSearchMode ||
+ (!!this.currentRoute &&
+- (this.currentRoute === routes.PRIVACY ||
++ (this.currentRoute === routes.BASIC ||
++ this.currentRoute === routes.PRIVACY ||
+ this.isRouteHostedWithinPrivacyView_(this.currentRoute)));
+ }
+
diff --git a/src/patches/chrome/browser/resources/settings/route.ts.patch b/src/patches/chrome/browser/resources/settings/route.ts.patch
index 60514db0..e175e730 100644
--- a/src/patches/chrome/browser/resources/settings/route.ts.patch
+++ b/src/patches/chrome/browser/resources/settings/route.ts.patch
@@ -2,7 +2,7 @@ diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resourc
index f3473b12d1..0000000001 100644
--- a/chrome/browser/resources/settings/route.ts
+++ b/chrome/browser/resources/settings/route.ts
-@@ -200,6 +200,11 @@ function createRoutes(): SettingsRoutes {
+@@ -200,6 +200,13 @@ function createRoutes(): SettingsRoutes {
r.SYNC_ADVANCED = r.SYNC.createChild('/syncSetup/advanced');
}
-
@@ -10,6 +10,8 @@ index f3473b12d1..0000000001 100644
+ if (visibility.dao !== false) {
+ r.DAO = r.BASIC.createSection(
+ '/dao', 'dao', loadTimeData.getString('daoPageTitle'));
++ r.DAO_AGENT = r.BASIC.createSection(
++ '/agent', 'agent', loadTimeData.getString('daoAgentSettingsTitle'));
+ }
+
if (visibility.ai !== false && loadTimeData.getBoolean('showAiPage')) {
diff --git a/src/patches/chrome/browser/resources/settings/router_dao.ts.patch b/src/patches/chrome/browser/resources/settings/router_dao.ts.patch
index 5152440e..340f59c8 100644
--- a/src/patches/chrome/browser/resources/settings/router_dao.ts.patch
+++ b/src/patches/chrome/browser/resources/settings/router_dao.ts.patch
@@ -2,11 +2,12 @@ diff --git a/chrome/browser/resources/settings/router.ts b/chrome/browser/resour
index 2977bf7cb1..0000000002 100644
--- a/chrome/browser/resources/settings/router.ts
+++ b/chrome/browser/resources/settings/router.ts
-@@ -20,6 +20,7 @@ export interface SettingsRoutes {
+@@ -20,6 +20,8 @@ export interface SettingsRoutes {
COMPARE: Route;
COOKIES: Route;
DEFAULT_BROWSER: Route;
+ DAO: Route;
++ DAO_AGENT: Route;
DOWNLOADS: Route;
EDIT_DICTIONARY: Route;
FONTS: Route;
diff --git a/src/patches/chrome/browser/resources/settings/settings.ts.patch b/src/patches/chrome/browser/resources/settings/settings.ts.patch
index 4aaedf4d..0c769362 100644
--- a/src/patches/chrome/browser/resources/settings/settings.ts.patch
+++ b/src/patches/chrome/browser/resources/settings/settings.ts.patch
@@ -2,10 +2,12 @@ diff --git a/chrome/browser/resources/settings/settings.ts b/chrome/browser/reso
index f221bf6cd2..0905c92e55 100644
--- a/chrome/browser/resources/settings/settings.ts
+++ b/chrome/browser/resources/settings/settings.ts
-@@ -75,6 +75,8 @@ export {SettingsCheckboxListEntryElement} from './controls/settings_checkbox_lis
+@@ -75,6 +75,10 @@ export {SettingsCheckboxListEntryElement} from './controls/settings_checkbox_lis
export {DefaultBrowserBrowserProxyImpl} from './default_browser_page/default_browser_browser_proxy.js';
export type {DefaultBrowserBrowserProxy, DefaultBrowserInfo} from './default_browser_page/default_browser_browser_proxy.js';
export {SettingsDefaultBrowserPageElement} from './default_browser_page/default_browser_page.js';
++export {DaoAgentSettingsBrowserProxyImpl} from './dao_page/dao_agent_settings_browser_proxy.js';
++export type {DaoAgentMemorySummary, DaoAgentSettingsBrowserProxy, DaoAgentSettingsSnapshot, DaoAgentUsageStats, DaoAgentWorkspaceActivity, DaoAgentWorkspaceSummary} from './dao_page/dao_agent_settings_browser_proxy.js';
+export {DaoMcpBrowserProxyImpl, SettingsDaoPageElement} from './dao_page/dao_page.js';
+export type {DaoMcpBrowserProxy, DaoMcpStatus} from './dao_page/dao_page.js';
//
diff --git a/src/patches/chrome/browser/resources/settings/settings_main/settings_main.html.patch b/src/patches/chrome/browser/resources/settings/settings_main/settings_main.html.patch
index 98716a07..a44debc7 100644
--- a/src/patches/chrome/browser/resources/settings/settings_main/settings_main.html.patch
+++ b/src/patches/chrome/browser/resources/settings/settings_main/settings_main.html.patch
@@ -1,12 +1,26 @@
diff --git a/chrome/browser/resources/settings/settings_main/settings_main.html b/chrome/browser/resources/settings/settings_main/settings_main.html
-index c6fe484fcf..0000000001 100644
--- a/chrome/browser/resources/settings/settings_main/settings_main.html
+++ b/chrome/browser/resources/settings/settings_main/settings_main.html
-@@ -52,6 +52,16 @@
-
-
--
+@@ -32,6 +32,15 @@
+ cr-view-manager [hidden-by-search] {
+ display: none;
+ }
+
++ :host([overview-mode]) cr-view-manager > [slot=view].active {
++ margin-bottom: 30px;
++ scroll-margin-top: 30px;
++ }
++
++ :host([overview-mode]) cr-view-manager > [slot=view].active:last-of-type {
++ margin-bottom: 0;
++ }
+
+
+
$i18n{searchNoResults}
+@@ -56,6 +65,26 @@
+
+
+
+
+
-
++
++
++
++
++
++
++
++
++
+
+
+-
+-
+-
+-
+-
+-
+-
+-
+
+
+
+
++
++
++
++
++
++
++
++
++
+
+
+
diff --git a/src/patches/chrome/browser/resources/settings/settings_main/settings_main.ts.patch b/src/patches/chrome/browser/resources/settings/settings_main/settings_main.ts.patch
index 33b2281d..0f3da40e 100644
--- a/src/patches/chrome/browser/resources/settings/settings_main/settings_main.ts.patch
+++ b/src/patches/chrome/browser/resources/settings/settings_main/settings_main.ts.patch
@@ -1,8 +1,7 @@
diff --git a/chrome/browser/resources/settings/settings_main/settings_main.ts b/chrome/browser/resources/settings/settings_main/settings_main.ts
-index ad60dbd778..0000000001 100644
--- a/chrome/browser/resources/settings/settings_main/settings_main.ts
+++ b/chrome/browser/resources/settings/settings_main/settings_main.ts
-@@ -16,6 +16,7 @@ import '../autofill_page/autofill_page_index.js';
+@@ -17,6 +17,7 @@
import '../on_startup_page/on_startup_page.js';
import '../people_page/people_page_index.js';
import '../performance_page/performance_page_index.js';
@@ -10,3 +9,312 @@ index ad60dbd778..0000000001 100644
import '../privacy_page/privacy_page_index.js';
import '../reset_page/reset_profile_banner.js';
import '../search_page/search_page_index.js';
+@@ -53,6 +54,10 @@
+ noSearchResults: HTMLElement,
+ switcher: CrViewManagerElement,
+ };
++
++ restoreOverviewScroll(): void;
++ scrollToOverviewSection(
++ section: string, behavior?: ScrollBehavior): boolean;
+ }
+
+ const SettingsMainElementBase = RouteObserverMixin(PolymerElement);
+@@ -100,6 +105,12 @@
+ value: false,
+ },
+
++ overviewMode_: {
++ type: Boolean,
++ value: false,
++ reflectToAttribute: true,
++ },
++
+ showNoResultsFound_: {
+ type: Boolean,
+ value: false,
+@@ -129,6 +140,7 @@
+ declare private lastRoute_: Route|null;
+ declare private routes_: SettingsRoutes;
+ declare private inSearchMode_: boolean;
++ declare private overviewMode_: boolean;
+ declare private showNoResultsFound_: boolean;
+ declare private showResetProfileBanner_: boolean;
+ declare toolbarSpinnerActive: boolean;
+@@ -140,6 +152,9 @@
+ private pendingViewSwitching_: PromiseResolver
= new PromiseResolver();
+ private topLevelEquivalentRoute_: Route = getTopLevelRoute();
+ private currentQuery_: string = '';
++ private activeOverviewSection_: string = '';
++ private overviewObserver_: IntersectionObserver|null = null;
++ private overviewScrollTop_: number = 0;
+
+ override connectedCallback() {
+ super.connectedCallback();
+@@ -150,6 +165,12 @@
+ requestIdleCallback(() => ensureLazyLoaded());
+ }
+
++ override disconnectedCallback() {
++ super.disconnectedCallback();
++ this.overviewObserver_?.disconnect();
++ this.overviewObserver_ = null;
++ }
++
+ private beforeNextRenderPromise_(): Promise {
+ return new Promise(res => {
+ beforeNextRender(this, res);
+@@ -159,32 +180,51 @@
+ override async currentRouteChanged(route: Route) {
+ this.pendingViewSwitching_ = new PromiseResolver();
+
+- if (routes.ADVANCED && routes.ADVANCED.contains(route)) {
+- // Load the lazy module immediately, don't wait for requestIdleCallback()
+- // to fire. No-op if it has already fired.
+- ensureLazyLoaded();
++ const wasOverview = this.overviewMode_;
++ this.overviewMode_ = this.isOverviewRoute_(route);
++ if (wasOverview && !this.overviewMode_) {
++ this.rememberOverviewScroll_();
+ }
+
++ if (this.overviewMode_ ||
++ (routes.ADVANCED && routes.ADVANCED.contains(route))) {
++ await ensureLazyLoaded();
++ }
++
+ const effectiveRoute =
+- route === routes.BASIC ? this.topLevelEquivalentRoute_ : route;
++ this.overviewMode_ ? route :
++ (route === routes.BASIC ?
++ this.topLevelEquivalentRoute_ :
++ route);
+
+ if (this.lastRoute_ === effectiveRoute) {
+- // Nothing to do.
+ this.pendingViewSwitching_.resolve();
+ return;
+ }
+
+ this.lastRoute_ = effectiveRoute;
+
++ if (this.overviewMode_) {
++ await this.beforeNextRenderPromise_();
++ if (this.lastRoute_ !== effectiveRoute || !this.isConnected) {
++ this.pendingViewSwitching_.resolve();
++ return;
++ }
++
++ const sectionIds = this.getOverviewSectionIds_();
++ await this.$.switcher.switchViews(
++ sectionIds, 'no-animation', 'no-animation');
++ this.setupOverviewObserver_();
++ this.pendingViewSwitching_.resolve();
++ return;
++ }
++
+ const newSection = effectiveRoute.section;
+ let sectionElement = this.$.switcher.querySelector(`#${newSection}`);
+ if (!sectionElement) {
+- // Wait for any pageVisibility s to render and try again.
+ await this.beforeNextRenderPromise_();
+
+ if (this.lastRoute_ !== effectiveRoute || !this.isConnected) {
+- // A newer currentRouteChanged call happened while awaiting or no longer
+- // connected (both can happen in tests). Do nothing.
+ this.pendingViewSwitching_.resolve();
+ return;
+ }
+@@ -192,6 +232,8 @@
+ }
+
+ assert(sectionElement);
++ this.overviewObserver_?.disconnect();
++ this.overviewObserver_ = null;
+ await this.$.switcher.switchView(
+ sectionElement.id, 'no-animation', 'no-animation');
+ this.pendingViewSwitching_.resolve();
+@@ -210,6 +252,9 @@
+ * @return A promise indicating that searching finished.
+ */
+ searchContents(query: string): Promise {
++ if (this.currentQuery_ === '' && query !== '') {
++ this.rememberOverviewScroll_();
++ }
+ this.inSearchMode_ = true;
+ this.toolbarSpinnerActive = true;
+ this.currentQuery_ = query;
+@@ -256,10 +301,23 @@
+ this.inSearchMode_ = !result.wasClearSearch;
+ this.showNoResultsFound_ = this.inSearchMode_ && result.matchCount === 0;
+
++ const matchingSections =
++ this.getOverviewSections_().map(section => section.id);
++ this.dispatchEvent(
++ new CustomEvent('settings-overview-search-results-changed', {
++ bubbles: true,
++ composed: true,
++ detail: {query, sections: matchingSections},
++ }));
++ this.setupOverviewObserver_();
++ if (!this.inSearchMode_) {
++ this.restoreOverviewScroll();
++ }
++
+ if (this.inSearchMode_) {
+ getAnnouncerInstance().announce(
+ this.showNoResultsFound_ ?
+ loadTimeData.getString('searchNoResults') :
+ loadTimeData.getStringF('searchResults', query));
+ }
+ });
+@@ -267,7 +325,7 @@
+ }
+
+ private renderPlugin_(route: Route): boolean {
+- return this.inSearchMode_ ||
++ return this.overviewMode_ || this.inSearchMode_ ||
+ (!!this.lastRoute_ && route.contains(this.lastRoute_));
+ }
+
+@@ -295,8 +353,138 @@
+ }
+
+ private shouldShowAll_(): boolean {
+- return this.inSearchMode_ && !!this.lastRoute_ &&
+- !this.lastRoute_.isSubpage();
++ return this.overviewMode_ ||
++ (this.inSearchMode_ && !!this.lastRoute_ &&
++ !this.lastRoute_.isSubpage());
++ }
++
++ private isOverviewRoute_(route: Route): boolean {
++ if (route === routes.ABOUT || route.isNavigableDialog ||
++ route.isSubpage()) {
++ return false;
++ }
++
++ return route === routes.BASIC || route === routes.ADVANCED ||
++ route.parent === routes.BASIC || route.parent === routes.ADVANCED;
++ }
++
++ private getOverviewSectionIds_(): string[] {
++ return Array
++ .from(this.$.switcher.querySelectorAll(
++ ':scope > [slot=view]'))
++ .filter(section => section.id !== 'about')
++ .map(section => section.id);
++ }
++
++ private getOverviewSections_(): HTMLElement[] {
++ return Array
++ .from(this.$.switcher.querySelectorAll(
++ ':scope > [slot=view].active'))
++ .filter(section => {
++ const plugin = section.querySelector(
++ ':scope > :not(template)');
++ return section.id !== 'about' && !!plugin &&
++ !plugin?.hasAttribute('hidden-by-search');
++ });
++ }
++
++ scrollToOverviewSection(
++ section: string, behavior: ScrollBehavior = 'smooth'): boolean {
++ if (!this.overviewMode_) {
++ return false;
++ }
++
++ const target = this.$.switcher.querySelector(
++ `#${CSS.escape(section)}`);
++ if (!target || !this.getOverviewSections_().includes(target)) {
++ return false;
++ }
++
++ target.scrollIntoView({behavior, block: 'start'});
++ this.updateActiveOverviewSection_(section);
++ return true;
++ }
++
++ restoreOverviewScroll(): void {
++ if (!this.overviewMode_) {
++ return;
++ }
++
++ const container = this.getScrollContainer_();
++ if (!container) {
++ return;
++ }
++
++ const maxScroll =
++ Math.max(0, container.scrollHeight - container.clientHeight);
++ container.scrollTop = Math.min(this.overviewScrollTop_, maxScroll);
++ this.updateActiveOverviewSectionFromScroll_();
++ }
++
++ private getScrollContainer_(): HTMLElement|null {
++ const root = this.getRootNode();
++ return root instanceof ShadowRoot ? root.querySelector('#container') : null;
++ }
++
++ private rememberOverviewScroll_() {
++ const container = this.getScrollContainer_();
++ if (container) {
++ this.overviewScrollTop_ = container.scrollTop;
++ }
++ }
++
++ private setupOverviewObserver_() {
++ this.overviewObserver_?.disconnect();
++ const container = this.getScrollContainer_();
++ const sections = this.getOverviewSections_();
++ if (!container || sections.length === 0) {
++ return;
++ }
++
++ this.overviewObserver_ = new IntersectionObserver(
++ () => this.updateActiveOverviewSectionFromScroll_(), {
++ root: container,
++ rootMargin: '-30px 0px -65% 0px',
++ threshold: [0, 0.01, 1],
++ });
++ sections.forEach(section => this.overviewObserver_!.observe(section));
++ this.updateActiveOverviewSectionFromScroll_();
++ }
++
++ private updateActiveOverviewSectionFromScroll_() {
++ const container = this.getScrollContainer_();
++ const sections = this.getOverviewSections_();
++ if (!container || sections.length === 0) {
++ return;
++ }
++
++ const readingLine = container.getBoundingClientRect().top + 40;
++ let current = sections[0];
++ for (const section of sections) {
++ if (section.getBoundingClientRect().top <= readingLine) {
++ current = section;
++ } else {
++ break;
++ }
++ }
++
++ if (container.scrollTop + container.clientHeight >=
++ container.scrollHeight - 2) {
++ current = sections[sections.length - 1];
++ }
++ this.updateActiveOverviewSection_(current.id);
++ }
++
++ private updateActiveOverviewSection_(section: string) {
++ if (section === this.activeOverviewSection_) {
++ return;
++ }
++ this.activeOverviewSection_ = section;
++ this.dispatchEvent(new CustomEvent('settings-overview-section-changed', {
++ bubbles: true,
++ composed: true,
++ detail: {section},
++ }));
+ }
+
+ private onResetProfileBannerClose_() {
diff --git a/src/patches/chrome/browser/resources/settings/settings_menu/settings_menu.html.patch b/src/patches/chrome/browser/resources/settings/settings_menu/settings_menu.html.patch
index 2a77f466..be986780 100644
--- a/src/patches/chrome/browser/resources/settings/settings_menu/settings_menu.html.patch
+++ b/src/patches/chrome/browser/resources/settings/settings_menu/settings_menu.html.patch
@@ -1,18 +1,245 @@
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html
-index cddbd3b798..0000000001 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.html
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html
-@@ -59,6 +59,13 @@
- $i18n{peoplePageTitle}
-
-
-+
+
+
+-