From 5a653a2b388b4b7c29faf5d3d32c8d78502e87fe Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sun, 11 Oct 2026 21:33:04 +0800 Subject: [PATCH 1/4] feat(agent): add custom Jev model ID setting --- docs/changelog/en.md | 1 + docs/changelog/zh-CN.md | 1 + docs/feature-checklist.md | 2 +- docs/features.md | 8 ++++--- scripts/checks/jev-plugin.mjs | 24 ++++++++++++++++--- .../settings_redesign_contract.test.ts | 11 +++++++++ src/dao/browser/agent/dao_agent_plugins.h | 11 +++++++-- src/dao/browser/automation/dao_jev_task.cc | 11 ++++++--- .../resources/agent/__tests__/jev.test.ts | 3 ++- .../ui/webui/resources/agent/agent_plugins.ts | 1 + .../dao_page/dao_agent_page.html.patch | 6 ++++- .../settings/dao_page/dao_agent_page.ts.patch | 7 +++--- .../dao_agent_settings_browser_proxy.ts.patch | 3 ++- 13 files changed, 71 insertions(+), 18 deletions(-) diff --git a/docs/changelog/en.md b/docs/changelog/en.md index 78d6acba..0600a175 100644 --- a/docs/changelog/en.md +++ b/docs/changelog/en.md @@ -4,6 +4,7 @@ ### [Unreleased] Unreleased +- Set a custom model ID for the experimental Jev plugin in Agent settings; leave it blank to keep using `jev-latest` for Agent and MCP browser tasks. - Allow up to ten minutes to verify uploaded desktop release files so slower downloads do not abort publication after a successful upload. - The website starts downloads automatically only on macOS; other devices show a manual download page with the configured platform installers instead of downloading a potentially incompatible file. diff --git a/docs/changelog/zh-CN.md b/docs/changelog/zh-CN.md index 4424da11..d2ef2aff 100644 --- a/docs/changelog/zh-CN.md +++ b/docs/changelog/zh-CN.md @@ -4,6 +4,7 @@ ### [Unreleased] 未发布 +- 可在 Agent 设置中为实验性 Jev 插件填写自定义模型 ID;留空时,Agent 和 MCP 浏览器任务继续使用 `jev-latest`。 - 桌面发布文件上传后的校验最多等待十分钟,避免下载较慢时,上传成功却因校验超时中断发布。 - 官网仅在 macOS 上自动开始下载;其他设备显示手动下载页面,可选择已配置的各平台安装包,避免自动下载不适用的文件。 diff --git a/docs/feature-checklist.md b/docs/feature-checklist.md index 416f45f7..17375ba8 100644 --- a/docs/feature-checklist.md +++ b/docs/feature-checklist.md @@ -400,7 +400,7 @@ Flagship feature. C++ services + `dao://dao-agent` WebUI + vendor runtime. | ☐ | Agent WebUI host allowed to make network requests (LLM API) | `webui/chrome_web_ui_controller_factory.cc.patch` (`origin.host()=="agent"`) | 🟢 | `dao://agent` reaches external LLM endpoints | | ☐ | Named, collapsible Agent tool calls | `resources/agent/dao_tool_renderer.ts`, `resources/agent/agent.css` | 🟢 | Run `dao_tool_renderer.test.ts`; verify regular, search, and fetch calls always show the stable tool name, keep parameters/output collapsed by default, and expand when requested | | ☐ | Unified Profile-scoped Agent settings and legacy migration | `src/dao/.../agent/dao_agent_settings_handler.{h,cc}`, `resources/agent/agent_settings_{sync,native_bridge}.ts`, `resources/settings/dao_page/dao_agent_page*.patch`, `webui/settings/settings_ui.cc.patch` | 🟡 | Run `agent_settings_sync.test.ts`, `dao_agent_app.test.ts`, `DaoAgentPage`, and `DaoAgentSettingsHandlerTest`; verify legacy `dao://agent` local-storage values migrate once without overwriting Settings values, partial usage dictionaries receive validated defaults and derived totals, malformed/non-finite fields fail closed, canonical stored snapshots still require the complete schema, both WebUIs receive `dao-agent-settings-changed`, rapid tool toggles serialize cumulative disabled-tool arrays and resync after failure, the resume window defaults to 3 and accepts 0, and runtime-only state is not migrated. With no active Agent turn, the gear opens exactly one foreground `dao://settings/agent` tab through its fixed native command; success closes the sidebar and failure leaves it open | -| ☐ | Experimental shared Jev browser subtask plugin | `agent/dao_agent_plugins.h`, `resources/agent/agent_plugins.ts`, `automation/dao_jev_task.cc`, `automation/dao_page_tools.cc`, Settings Agent page patches | 🟡 | Run `jev.test.ts`, `compact_snapshot.test.ts`, `browser_tool_catalog.test.ts`, Agent bridge/settings/adapter tests, `npm run rebuild`, and `node scripts/checks/jev-plugin.mjs`; verify both switches default off, masked credentials and independent persistence, no settings-triggered requests, invalid configuration unavailable, underlying permissions enforced, no editable drafts or nested controls in compact snapshots (empty/true/plaintext-only, including uppercase), choices stay within 255 options at 150 fields and 20 inputs without dropping candidates, native input/ARIA button names and matching action guards, atomic refs and locally verified completion, fast/delayed form navigation without duplicate submissions, obsolete decisions discarded after navigation, task deadlines while navigation is pending, bounded recovery/no-progress/budget errors with partial evidence, cancellation on stop/config changes/target loss, rejection of late responses and credentialed redirects, reloading the Agent during a pending native wait preserves the browser and allows a new turn, MCP discovery hides `run_browser_task` when disabled/unconfigured and includes it only with valid configuration and permission, without requests or approval prompts, approved MCP tasks run with the Agent panel closed, MCP cancellation and revocation abort native requests, partial failures preserve structured progress, exact target remains pinned across focus changes, ordinary tools unaffected when unregistered, English/Chinese labels and keyboard access. With a configured compatible service, compare bilingual representative tasks against the ordinary Agent before claiming a speedup. | +| ☐ | Experimental shared Jev browser subtask plugin | `agent/dao_agent_plugins.h`, `resources/agent/agent_plugins.ts`, `automation/dao_jev_task.cc`, `automation/dao_page_tools.cc`, Settings Agent page patches | 🟡 | Run `jev.test.ts`, `compact_snapshot.test.ts`, `browser_tool_catalog.test.ts`, Agent bridge/settings/adapter tests, `npm run rebuild`, and `node scripts/checks/jev-plugin.mjs`; verify both switches default off, masked credentials and independent persistence, custom Jev model IDs surviving restart and reaching Agent/MCP requests with surrounding whitespace trimmed, empty model IDs falling back to `jev-latest`, model changes cancelling pending requests, no settings-triggered requests, invalid configuration unavailable, underlying permissions enforced, no editable drafts or nested controls in compact snapshots (empty/true/plaintext-only, including uppercase), choices stay within 255 options at 150 fields and 20 inputs without dropping candidates, native input/ARIA button names and matching action guards, atomic refs and locally verified completion, fast/delayed form navigation without duplicate submissions, obsolete decisions discarded after navigation, task deadlines while navigation is pending, bounded recovery/no-progress/budget errors with partial evidence, cancellation on stop/config changes/target loss, rejection of late responses and credentialed redirects, reloading the Agent during a pending native wait preserves the browser and allows a new turn, MCP discovery hides `run_browser_task` when disabled/unconfigured and includes it only with valid configuration and permission, without requests or approval prompts, approved MCP tasks run with the Agent panel closed, MCP cancellation and revocation abort native requests, partial failures preserve structured progress, exact target remains pinned across focus changes, ordinary tools unaffected when unregistered, English/Chinese labels and keyboard access. With a configured compatible service, compare bilingual representative tasks against the ordinary Agent before claiming a speedup. | | ☐ | Settings Agent management summaries and limited native facade | `src/dao/.../agent/dao_agent_settings_handler.{h,cc}`, `resources/settings/dao_page/dao_agent_page*.patch`, `resources/agent/agent_settings_native_bridge.ts` | 🟡 | Verify legacy `dao_agent_stats` migration preserves existing counters once; persona reset removes the override and restores the runtime default; manual Dream generation remains disabled until Memory and Dream analysis are enabled, then reports success and failure without duplicate submissions; a Settings-side usage reset updates an already-open Agent WebUI; Memory clear requires confirmation and refreshes aggregate counts; Workspace reveal works without registering workspace mutation messages; configuration loading/error/retry is independent, including when `getSettings()` fails; Memory, Workspace, and Usage remain visible, independently loadable inset cards with 16px desktop spacing, 12px narrow spacing, compact rows, the shared content inset, and visible keyboard focus in light and dark themes; and the existing Skills, Memory, and Dream secondary links still open | | ☐ | 7 Dao WebUI configs registered (agent, dream, index, memory, sidebar, skills, welcome) | `webui/chrome_web_ui_configs.cc.patch` | 🟡 | All 7 pages load; confirm upstream `SkillsUIConfig` still exists (dao takes over `dao://skills`) | | ☐ | Agent tab helpers, guarded cursor overlay, lock banner | `ui/tab_helpers.cc.patch` + `src/dao/.../agent/`, `dao_agent_cursor_view.*`, `dao_agent_*_view.*` | 🟢 | Run `DaoAgentCursorViewBrowserTest.*` and the focused cursor cases in `DaoMcpPageToolsBrowserTest`; verify the cursor uses a black fill, white outline, blue glow, direction-aware tilt/compression, damped arrival wobble, and the existing Dao ripple; verify visuals render only for the pinned target when it is the active tab of a visible, non-minimized, active window, background moves succeed without visuals, background clicks skip animation and remain pinned, changing tabs during a move cannot leak a ripple, short foreground moves are direct, long moves are bounded curves, and hiding a move completes its callback | diff --git a/docs/features.md b/docs/features.md index 77cc957d..940bf5d5 100644 --- a/docs/features.md +++ b/docs/features.md @@ -483,8 +483,10 @@ The stack includes: **LLM tool calling**, **long-term memory** (SQLite + FTS5), **Unified Agent settings** (`dao://settings/agent`, overview entry `#agent`) - The optional bundled Jev plugin has an experimental, default-off connection checkbox after the main provider settings and a separate default-off global - tool permission. Its full HTTP(S) API URL and masked Bearer token persist in - Profile settings independently of the main model. Opening or editing settings + tool permission. Its full HTTP(S) API URL, masked Bearer token and optional + model ID persist in Profile settings independently of the main model. The model + ID is trimmed for requests; empty or whitespace-only values use `jev-latest`. + Opening or editing settings sends no inference request. Invalid or incomplete configuration is unavailable. MCP discovery lists `run_browser_task` only when the connection and tool permission are enabled with valid configuration; clients must refresh the tool @@ -493,7 +495,7 @@ The stack includes: **LLM tool calling**, **long-term memory** (SQLite + FTS5), through a shared native executor available to Dao Agent and external MCP. Agent calls retain the existing turn and sequential scheduler; MCP calls retain connection approval and the exact authorized tab lease, and work with - the Agent panel closed. Both enforce browser permissions. Jev uses the `jev-latest` choice + the Agent panel closed. Both enforce browser permissions. Jev uses the choice protocol, selects fill targets and known inputs separately to keep every choice below the service's 255-option limit, accepts only offered actions and known non-sensitive inputs, and verifies all completion predicates locally. Compact viewport snapshots and diff --git a/scripts/checks/jev-plugin.mjs b/scripts/checks/jev-plugin.mjs index 59e9526e..7e61e1ff 100644 --- a/scripts/checks/jev-plugin.mjs +++ b/scripts/checks/jev-plugin.mjs @@ -14,6 +14,7 @@ const profile = await realpath(await mkdtemp(join(tmpdir(), 'dao-jev-check-'))); const sockets = []; let child, helper, port, stderr = '', mode = 'normal', requests = 0, disconnected = 0; let pendingResponse, pendingBody, redirectedRequests = 0, submissions = 0, navigationDelay = 0; +let expectedModel = 'custom-jev-model'; const choice = (question, selected) => ({choice: selected, confidence: 1, probabilities: Object.fromEntries(Object.keys(question.criteria).map(key => [key, Number(key === selected)]))}); function normalAnswer(body) { @@ -54,7 +55,7 @@ const server = createServer(async (req, res) => { assert(!raw.includes('local-test-token'), 'Token is excluded from model input'); assert(!raw.includes('private-editor-'), 'Editable drafts and nested controls are excluded'); const body = JSON.parse(raw); - assert.equal(body.model, 'jev-latest'); + assert.equal(body.model, expectedModel); for (const question of Object.values(body.questions)) { assert(Object.keys(question.criteria).length <= 255, 'Every choice respects the service limit'); } @@ -123,6 +124,7 @@ const targets = async () => (await fetch(`http://127.0.0.1:${port}/json/list`)). const enabled = '[data-setting="dao_plugin_jev_enabled"]'; const url = '[data-setting="dao_plugin_jev_url"]'; const token = '[data-setting="dao_plugin_jev_token"]'; +const model = '[data-setting="dao_plugin_jev_model"]'; const permission = '[data-setting="dao_plugin_jev_permission"]'; async function start() { await rm(join(profile, 'DevToolsActivePort'), {force: true}); @@ -217,6 +219,9 @@ try { }; await setField(url, `${origin}/jev`); await setField(token, 'local-test-token'); + assert.equal(await ui(`return find('${model}').value;`), ''); + assert.equal(await ui(`return find('${model}').placeholder;`), 'jev-latest'); + await setField(model, ' custom-jev-model '); await until(() => ui(`return !find('${permission}').disabled;`), 'valid connection'); assert.equal(await ui(`return find('${token}').type;`), 'password'); assert.equal(await ui(`return find('${permission}').checked;`), false); @@ -239,6 +244,7 @@ try { await until(() => ui(`return find('settings-dao-agent-page').agentPlugins_[0].effective;`), 'restart persistence'); assert.equal(await ui(`return find('${url}').value;`), `${origin}/jev`); assert.equal(await ui(`return find('${token}').value;`), 'local-test-token'); + assert.equal(await ui(`return find('${model}').value;`), ' custom-jev-model '); assert.equal(requests, 0); console.log('PASS settings: defaults, expansion, validation, masking, two badges, explicit permission, collapse and restart persistence; zero inference requests'); @@ -413,6 +419,14 @@ try { await until(() => evaluate(formSend, `!!document.querySelector('input') && !document.querySelector('input').value`), 'fresh MCP form'); }; const runMcp = (args = task) => mcp.call('tools/call', {name: 'run_browser_task', arguments: args}); + for (const value of ['', ' ', ' another-jev-model ']) { + await resetForm(); + expectedModel = value.trim() || 'jev-latest'; + await setField(model, value); + const result = await runMcp().result; + assert.equal(result.structuredContent.status, 'completed', JSON.stringify(result)); + } + console.log('PASS custom model IDs reach Agent and MCP requests; empty or whitespace-only IDs use jev-latest'); for (let i = 0; i < 3; i++) { await checkNavigation(async args => (await runMcp(args).result).structuredContent, i); } @@ -473,17 +487,21 @@ try { assert.equal(bounded.structuredContent.status, 'step_limit'); console.log('PASS native argument bounds and split field/input choices remain within the Jev choice limit'); - for (const cancellation of ['timeout', 'client', 'permission']) { + for (const cancellation of ['timeout', 'client', 'model', 'permission']) { await resetForm(); mode = 'hold'; pendingResponse = null; const beforeDisconnect = disconnected; const running = runMcp({...task, timeout_ms: cancellation === 'timeout' ? 1000 : 60000}); await until(() => pendingResponse, 'pending external Jev request'); if (cancellation === 'client') mcp.notify('notifications/cancelled', {requestId: running.id, reason: 'Integration cancellation'}); + if (cancellation === 'model') { + expectedModel = 'changed-jev-model'; + await setField(model, expectedModel); + } if (cancellation === 'permission') await ui(`find('${permission}').click();`); // MCP cancellation deliberately suppresses the cancelled request's reply. if (cancellation !== 'client') { const result = await running.result; - const expected = cancellation === 'permission' ? 'configuration_changed' : 'timeout'; + const expected = cancellation === 'timeout' ? 'timeout' : 'configuration_changed'; assert.equal(result.structuredContent.status, expected, JSON.stringify(result)); assert.equal(result.isError, true); } diff --git a/scripts/commands/__tests__/settings_redesign_contract.test.ts b/scripts/commands/__tests__/settings_redesign_contract.test.ts index c945f89c..5a348493 100644 --- a/scripts/commands/__tests__/settings_redesign_contract.test.ts +++ b/scripts/commands/__tests__/settings_redesign_contract.test.ts @@ -208,6 +208,17 @@ function preservedContractToken(line: string): string | null { } describe("settings continuous overview contract", () => { + it("provides an optional model ID field for bundled Agent plugins", () => { + const html = addedPayload(readPatch("dao_page/dao_agent_page.html.patch")); + const modelInput = html.match(/]*data-setting\$="\[\[plugin\.modelKey\]\]"[^>]*>/)?.[0]; + expect(modelInput).toBeDefined(); + expect(modelInput).toContain('label="$i18n{daoAgentModelLabel}"'); + expect(modelInput).toContain('value="[[plugin.model]]"'); + expect(modelInput).toContain('placeholder="[[plugin.defaultModel]]"'); + expect(modelInput).toContain('on-input="onAgentTextSettingChange_"'); + expect(modelInput).not.toContain('required'); + }); + it("provides an integrated table of contents and overview controller", () => { const requiredPatches = [ "settings_ui/settings_ui.html.patch", diff --git a/src/dao/browser/agent/dao_agent_plugins.h b/src/dao/browser/agent/dao_agent_plugins.h index 4d597586..dd0f4044 100644 --- a/src/dao/browser/agent/dao_agent_plugins.h +++ b/src/dao/browser/agent/dao_agent_plugins.h @@ -26,12 +26,15 @@ struct DaoAgentPluginManifest { const char* enabled_key; const char* url_key; const char* token_key; + const char* model_key; + const char* default_model; const char* permission_key; }; inline constexpr DaoAgentPluginManifest kDaoAgentPlugins[] = { {"jev", "Jev", "run_browser_task", "dao_plugin_jev_enabled", "dao_plugin_jev_url", "dao_plugin_jev_token", + "dao_plugin_jev_model", "jev-latest", "dao_plugin_jev_permission"}, }; @@ -49,7 +52,8 @@ inline std::string DaoAgentPluginRevision( const DaoAgentPluginManifest& plugin) { base::ListValue state; for (const char* key : {plugin.enabled_key, plugin.url_key, - plugin.token_key, plugin.permission_key}) { + plugin.token_key, plugin.model_key, + plugin.permission_key}) { state.Append(DaoAgentPluginSetting(settings, key)); } return base::HexEncode( @@ -67,6 +71,8 @@ inline base::ListValue GetDaoAgentPlugins( .Set("enabledKey", plugin.enabled_key) .Set("urlKey", plugin.url_key) .Set("tokenKey", plugin.token_key) + .Set("modelKey", plugin.model_key) + .Set("defaultModel", plugin.default_model) .Set("permissionKey", plugin.permission_key); if (settings) { entry.Set("revision", DaoAgentPluginRevision(*settings, plugin)); @@ -121,7 +127,8 @@ inline bool IsDaoAgentPluginAuthorized(const base::DictValue& settings, inline bool IsDaoAgentPluginSetting(std::string_view key) { for (const auto& plugin : kDaoAgentPlugins) { for (const char* field : {plugin.enabled_key, plugin.url_key, - plugin.token_key, plugin.permission_key}) { + plugin.token_key, plugin.model_key, + plugin.permission_key}) { if (key == field) { return true; } diff --git a/src/dao/browser/automation/dao_jev_task.cc b/src/dao/browser/automation/dao_jev_task.cc index 5ad3878c..04787b86 100644 --- a/src/dao/browser/automation/dao_jev_task.cc +++ b/src/dao/browser/automation/dao_jev_task.cc @@ -453,9 +453,16 @@ void DaoJevTask::OnObserved(DaoBrowserToolResult result) { question(std::move(choices), "Choose the known input matching the next field to fill.")); } + const auto& settings = + session_->profile()->GetPrefs()->GetDict(prefs::kDaoAgentSettings); + std::string model(base::TrimWhitespaceASCII( + Text(settings, Text(plugin_, "modelKey")), base::TRIM_ALL)); + if (model.empty()) { + model = Text(plugin_, "defaultModel"); + } auto body = base::DictValue() - .Set("model", "jev-latest") + .Set("model", model) .Set("questions", questions_.Clone()) .Set("state", base::DictValue() .Set("page", base::DictValue() @@ -465,8 +472,6 @@ void DaoJevTask::OnObserved(DaoBrowserToolResult result) { .Set("known_inputs", inputs_.Clone()) .Set("elements", std::move(offered_elements)) .Set("recent_actions", recent_.Clone())); - const auto& settings = - session_->profile()->GetPrefs()->GetDict(prefs::kDaoAgentSettings); auto request = std::make_unique(); request->url = GURL(Text(settings, Text(plugin_, "urlKey"))); request->method = "POST"; diff --git a/src/dao/browser/ui/webui/resources/agent/__tests__/jev.test.ts b/src/dao/browser/ui/webui/resources/agent/__tests__/jev.test.ts index cd8262a8..cb1021bc 100644 --- a/src/dao/browser/ui/webui/resources/agent/__tests__/jev.test.ts +++ b/src/dao/browser/ui/webui/resources/agent/__tests__/jev.test.ts @@ -2,7 +2,8 @@ import {afterEach, describe, expect, it} from 'vitest'; import {getAgentPlugins, isPluginEnabled, isPluginTool, syncAgentPlugins} from '../agent_plugins.js'; const plugin = {id: 'jev', name: 'Jev', tool: 'run_browser_task', revision: 'revision-1', - enabledKey: 'enabled', urlKey: 'url', tokenKey: 'token', permissionKey: 'permission'}; + enabledKey: 'enabled', urlKey: 'url', tokenKey: 'token', permissionKey: 'permission', + modelKey: 'model', defaultModel: 'jev-latest'}; const settings = {enabled: 'true', url: 'https://service.test/full/path', token: 'private-token', permission: 'true'}; diff --git a/src/dao/browser/ui/webui/resources/agent/agent_plugins.ts b/src/dao/browser/ui/webui/resources/agent/agent_plugins.ts index 6c050283..c8f65b01 100644 --- a/src/dao/browser/ui/webui/resources/agent/agent_plugins.ts +++ b/src/dao/browser/ui/webui/resources/agent/agent_plugins.ts @@ -5,6 +5,7 @@ export interface AgentPlugin { id: string; name: string; tool: string; revision?: string; enabledKey: string; urlKey: string; tokenKey: string; permissionKey: string; + modelKey: string; defaultModel: string; } let plugins: AgentPlugin[] = []; let values: Record = {}; diff --git a/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_page.html.patch b/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_page.html.patch index 06a91f43..50b7db25 100644 --- a/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_page.html.patch +++ b/src/patches/chrome/browser/resources/settings/dao_page/dao_agent_page.html.patch @@ -3,7 +3,7 @@ new file mode 100644 index 0000000000..0000000001 --- /dev/null +++ b/chrome/browser/resources/settings/dao_page/dao_agent_page.html -@@ -0,0 +1,1384 @@ +@@ -0,0 +1,1388 @@ +