From 0e924205156284f6c1ad35c4b24ba436223cfcdb Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 15 Jul 2026 23:03:53 +0800 Subject: [PATCH] fix: bind approvals to the current task --- README.md | 24 +- README.zh-CN.md | 23 +- apps/desktop/package.json | 3 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 3 +- .../src-tauri/src/kernel/app_update.rs | 6 +- apps/desktop/src-tauri/tauri.conf.json | 6 +- apps/desktop/src/App.tsx | 455 ++++++++---------- apps/desktop/src/i18n.ts | 11 + apps/desktop/src/styles.css | 68 +-- docs/INSTALLATION.md | 6 +- docs/OPEN_SOURCE_RELEASE.md | 9 +- docs/RELEASE_NOTES_v1.0.1.md | 81 ++++ package.json | 3 +- scripts/approval-visibility.test.mjs | 62 ++- scripts/plugin-catalog.test.mjs | 5 +- scripts/release-source-check.mjs | 51 +- scripts/settings-panel.test.mjs | 15 + 18 files changed, 488 insertions(+), 345 deletions(-) create mode 100644 docs/RELEASE_NOTES_v1.0.1.md diff --git a/README.md b/README.md index cf92419..94c8bea 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@

- v1.0.0 stable · - Download for Windows · + v1.0.1 stable · + Download for Windows · Apache-2.0

@@ -155,7 +155,7 @@ high-risk action. See the full [model boundary](docs/AGENT_MODEL_BOUNDARY.md). reconciliation contracts validated with offline adversarial fake providers. Production Microsoft/Google account registration and live external-write -authority remain disabled in v1.0.0. The release does not sign in to real +authority remain disabled in v1.0.1. The release does not sign in to real accounts, send real email, or create, change, or cancel real calendar events. ## Why Rust @@ -168,7 +168,7 @@ remain thin; the Kernel and persistent projections own business state. ## Quick start -1. Download the [Windows x64 installer](https://github.com/Lee-take/dsagent/releases/download/v1.0.0/DS.Agent_1.0.0_x64-setup.exe). +1. Download the [Windows x64 installer](https://github.com/Lee-take/dsagent/releases/download/v1.0.1/DS.Agent_1.0.1_x64-setup.exe). 2. Make your own valid `DEEPSEEK_API_KEY` available to the DS Agent process. 3. Choose one local workspace on first run. 4. Describe the result you want in chat. DS Agent requests additional @@ -178,7 +178,7 @@ A user-supplied DeepSeek API key is a required prerequisite. DS Agent does not bundle a shared key or bypass DeepSeek access requirements; use remains subject to DeepSeek's terms and account policies. -The v1.0.0 installer is currently unsigned, so Windows may display an +The v1.0.1 installer is currently unsigned, so Windows may display an unknown-publisher warning. Read the [installation guide](docs/INSTALLATION.md) before installing. @@ -195,11 +195,13 @@ example `D:\build-target\ds-agent-v1-release`. ## Stable release -- Release: [DS Agent v1.0.0](https://github.com/Lee-take/dsagent/releases/tag/v1.0.0) -- Installer: `DS.Agent_1.0.0_x64-setup.exe` -- Size: `12,714,575 bytes` -- SHA-256: `3B944589C8443A677AF55F8748C06A4D6EBA4ACE86E63E302D5782D5A5E548E4` -- Validation: 192-file secret scan, production frontend build, Node/UI checks, +- Release: [DS Agent v1.0.1](https://github.com/Lee-take/dsagent/releases/tag/v1.0.1) +- Installer: `DS.Agent_1.0.1_x64-setup.exe` +- Size: `12,716,857 bytes` +- SHA-256: `469C4EFA54F4C94A6E37D28C9C88D331B26E1770C6792DC93D02B451640E2A6F` +- Fix: approvals now stay with their owning task, and one task with several + permissions needs only one confirm-or-reject decision. +- Validation: source secret scan, production frontend build, Node/UI checks, and 852 Rust tests with 845 passed, seven permission-gated live/GUI tests intentionally ignored, and zero failed. @@ -208,7 +210,7 @@ example `D:\build-target\ds-agent-v1-release`. - [Installation](docs/INSTALLATION.md) - [DS Agent and DeepSeek boundary](docs/AGENT_MODEL_BOUNDARY.md) - [v1 architecture](docs/architecture/DS_AGENT_V1_ARCHITECTURE_PLAN.md) -- [v1.0.0 release notes](docs/RELEASE_NOTES_v1.0.0.md) +- [v1.0.1 release notes](docs/RELEASE_NOTES_v1.0.1.md) - [v1 completion audit](docs/DS_AGENT_V1_COMPLETION_AUDIT.md) - [Security](SECURITY.md) · [Contributing](CONTRIBUTING.md) · [License](LICENSE) diff --git a/README.zh-CN.md b/README.zh-CN.md index 6bf1e02..9924634 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -11,8 +11,8 @@

- v1.0.0 正式稳定版 · - 下载 Windows 安装包 · + v1.0.1 正式稳定版 · + 下载 Windows 安装包 · Apache-2.0

@@ -138,7 +138,7 @@ DeepSeek 可以提出动作,DS Agent 决定动作是否安全、是否允许 - 已通过离线对抗性 fake provider 验证的 Microsoft/Google 形态邮件、日历、同步、草稿、 外部变更和对账契约。 -v1.0.0 仍未开放生产 Microsoft/Google 账号注册和真实外部写入权限。当前正式版不会登录 +v1.0.1 仍未开放生产 Microsoft/Google 账号注册和真实外部写入权限。当前正式版不会登录 真实账号、发送真实邮件,也不会创建、修改或取消真实日历事件。 ## 为什么使用 Rust @@ -149,7 +149,7 @@ command 与 React UI 保持薄层,业务状态由 Kernel 和持久投影统一 ## 快速开始 -1. 下载 [Windows x64 安装包](https://github.com/Lee-take/dsagent/releases/download/v1.0.0/DS.Agent_1.0.0_x64-setup.exe)。 +1. 下载 [Windows x64 安装包](https://github.com/Lee-take/dsagent/releases/download/v1.0.1/DS.Agent_1.0.1_x64-setup.exe)。 2. 让 DS Agent 进程能够读取你自己的有效 `DEEPSEEK_API_KEY`。 3. 首次运行时选择一个本地工作目录。 4. 在聊天中描述你希望完成的结果;只有任务确实需要时,DS Agent 才会请求额外权限或 @@ -158,7 +158,7 @@ command 与 React UI 保持薄层,业务状态由 Kernel 和持久投影统一 用户自行提供有效的 DeepSeek API Key 是必备前提。DS Agent 不内置共享 Key,也不会 绕过 DeepSeek 的访问条件;实际使用仍须遵守 DeepSeek 的服务条款和账号规则。 -v1.0.0 安装包目前尚未签名,Windows 可能显示“未知发布者”提示。安装前请阅读 +v1.0.1 安装包目前尚未签名,Windows 可能显示“未知发布者”提示。安装前请阅读 [安装指南](docs/INSTALLATION.md)。 ### 从源码运行 @@ -174,11 +174,12 @@ npx pnpm@9.15.9 --filter @deepseek-agent-os/desktop tauri:dev ## 正式稳定版 -- Release:[DS Agent v1.0.0](https://github.com/Lee-take/dsagent/releases/tag/v1.0.0) -- 安装包:`DS.Agent_1.0.0_x64-setup.exe` -- 大小:`12,714,575 bytes` -- SHA-256:`3B944589C8443A677AF55F8748C06A4D6EBA4ACE86E63E302D5782D5A5E548E4` -- 验证:192 个文件的密钥扫描、前端 production build、全部 Node/UI 检查以及 852 项 Rust +- Release:[DS Agent v1.0.1](https://github.com/Lee-take/dsagent/releases/tag/v1.0.1) +- 安装包:`DS.Agent_1.0.1_x64-setup.exe` +- 大小:`12,716,857 bytes` +- SHA-256:`469C4EFA54F4C94A6E37D28C9C88D331B26E1770C6792DC93D02B451640E2A6F` +- 修复:审批只跟随所属任务;同一任务需要多项权限时,只需一次确认或拒绝。 +- 验证:源文件密钥扫描、前端 production build、全部 Node/UI 检查以及 852 项 Rust 测试;845 项通过,7 项需要额外权限的 live/GUI 测试按设计忽略,0 项失败。 ## 文档 @@ -186,7 +187,7 @@ npx pnpm@9.15.9 --filter @deepseek-agent-os/desktop tauri:dev - [安装指南](docs/INSTALLATION.md) - [DS Agent 与 DeepSeek 的工作边界](docs/AGENT_MODEL_BOUNDARY.md) - [v1 架构计划](docs/architecture/DS_AGENT_V1_ARCHITECTURE_PLAN.md) -- [v1.0.0 发布说明](docs/RELEASE_NOTES_v1.0.0.md) +- [v1.0.1 发布说明](docs/RELEASE_NOTES_v1.0.1.md) - [v1 完成审计](docs/DS_AGENT_V1_COMPLETION_AUDIT.md) - [安全说明](SECURITY.md) · [参与贡献](CONTRIBUTING.md) · [开源许可](LICENSE) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ede1b91..c8b11f6 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,8 @@ { "name": "@deepseek-agent-os/desktop", "private": true, - "version": "1.0.0", + "version": "1.0.1", + "author": "Lee take", "license": "Apache-2.0", "type": "module", "scripts": { diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index d2955b0..03c17be 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -794,7 +794,7 @@ dependencies = [ [[package]] name = "deepseek-agent-os-desktop" -version = "1.0.0" +version = "1.0.1" dependencies = [ "base64 0.22.1", "chrono", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 9516b17..340a1ff 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "deepseek-agent-os-desktop" -version = "1.0.0" +version = "1.0.1" description = "Local-first DeepSeek AI work platform" edition = "2021" license = "Apache-2.0" +authors = ["Lee take"] [build-dependencies] tauri-build = { version = "2.2.0", features = [] } diff --git a/apps/desktop/src-tauri/src/kernel/app_update.rs b/apps/desktop/src-tauri/src/kernel/app_update.rs index d2f32d4..f15ad79 100644 --- a/apps/desktop/src-tauri/src/kernel/app_update.rs +++ b/apps/desktop/src-tauri/src/kernel/app_update.rs @@ -17,8 +17,8 @@ pub(crate) const APP_UPDATE_RELEASES_API_URL: &str = const APP_UPDATE_RELEASE_DOWNLOAD_PATH_PREFIX: &str = "/Lee-take/dsagent/releases/download/"; const APP_UPDATE_LEGACY_RELEASE_DOWNLOAD_PATH_PREFIX: &str = "/Lee-take/deepseek-agent-os/releases/download/"; -const APP_UPDATE_USER_AGENT: &str = "DS-Agent-Updater/1.0.0"; -const APP_UPDATE_CURRENT_RELEASE_TAG: &str = "v1.0.0"; +const APP_UPDATE_USER_AGENT: &str = "DS-Agent-Updater/1.0.1"; +const APP_UPDATE_CURRENT_RELEASE_TAG: &str = "v1.0.1"; #[cfg(windows)] const WINDOWS_CREATE_NO_WINDOW: u32 = 0x08000000; @@ -653,7 +653,7 @@ mod tests { let status = update_status_from_releases(releases, app_update_current_version()); assert!(!status.update_available); - assert_eq!(status.current_version, "v1.0.0"); + assert_eq!(status.current_version, "v1.0.1"); assert_eq!(status.latest_version.as_deref(), Some("0.3.0")); assert!(status.asset_name.is_none()); } diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 6b4b5b2..47edaca 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "DS Agent", "mainBinaryName": "ds-agent", - "version": "1.0.0", + "version": "1.0.1", "identifier": "ai.deepseek-agent-os.desktop", "build": { "beforeDevCommand": "npx pnpm@9.15.9 dev", @@ -28,6 +28,8 @@ "bundle": { "active": false, "targets": "all", - "icon": ["icons/icon.ico"] + "icon": ["icons/icon.ico"], + "publisher": "Lee take", + "copyright": "Copyright © 2026 Lee take" } } diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 15471b6..5fe34a9 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1131,6 +1131,19 @@ export function App() { () => skillRecords.filter((record) => record.package_kind === "skill"), [skillRecords], ); + const agentActionPermissionRequestIds = useMemo(() => { + const requestIds = new Set(); + for (const conversation of agentConversations) { + for (const message of conversation.messages) { + for (const action of message.proposed_actions ?? []) { + if (action.permission_request_id) { + requestIds.add(action.permission_request_id); + } + } + } + } + return requestIds; + }, [agentConversations]); const feedbackReviewItems = useMemo(() => { const memoriesById = new Map(memoryRecords.map((memory) => [memory.id, memory])); const feedbackByMemory = selectedMemoryFeedbackRecords.reduce( @@ -4424,55 +4437,12 @@ export function App() { } }; - const resumeAgentAction = async ( - messageId: string, - actionIndex: number, - action: AgentChatActionProposal, - ) => { - const actionKey = `${messageId}:${actionIndex}`; - setAgentActionPending(actionKey); - setAgentChatError(""); - - if (!hasDesktopRuntime()) { - setAgentChatError(copy.chatWorkbench.desktopRuntimeMissing); - setAgentActionPending(null); - return; - } - - try { - const updatedAction = await invoke("resume_agent_chat_action", { - accessMode: state.access_mode, - largeModelProvider: state.large_model_provider, - networkSearchSourceModel: state.network_search_source_model || null, - action, - }); - updateActiveAgentMessages((currentMessages) => - currentMessages.map((message) => { - if (message.id !== messageId || !message.proposed_actions) { - return message; - } - return { - ...message, - proposed_actions: message.proposed_actions.map((currentAction, index) => - index === actionIndex ? updatedAction : currentAction, - ), - }; - }), - ); - await Promise.all([refreshCapabilityState(), refreshOperationsBriefingRuns()]); - } catch (error) { - setAgentChatError(String(error) || copy.chatWorkbench.resumeActionFailed); - } finally { - setAgentActionPending(null); - } - }; - - const approveAndResumeAgentAction = async ( + const resolveAndResumeAgentActionGroup = async ( messageId: string, - actionIndex: number, - action: AgentChatActionProposal, + actionEntries: Array<{ actionIndex: number; action: AgentChatActionProposal }>, + approved: boolean, ) => { - const actionKey = `${messageId}:${actionIndex}`; + const actionKey = `${messageId}:approval-group`; setAgentActionPending(actionKey); setAgentChatError(""); @@ -4483,36 +4453,65 @@ export function App() { } try { - if (action.permission_request_id) { + for (const { action } of actionEntries) { + if (!action.permission_request_id) { + continue; + } + const approvalRecord = capabilityRecords.find( + (record) => record.request.id === action.permission_request_id, + ); + if (approvalRecord?.effective_status !== "pending_approval") { + continue; + } setResolutionPending(action.permission_request_id); await invoke("resolve_capability_access_request", { requestId: action.permission_request_id, - approved: true, - note: copy.chatWorkbench.confirmAndRun, + approved, + note: approved + ? copy.chatWorkbench.confirmAndRun + : copy.chatWorkbench.taskApprovalRejected, }); } - const updatedAction = await invoke("resume_agent_chat_action", { - accessMode: state.access_mode, - largeModelProvider: state.large_model_provider, - networkSearchSourceModel: state.network_search_source_model || null, - action, - }); - updateActiveAgentMessages((currentMessages) => - currentMessages.map((message) => { - if (message.id !== messageId || !message.proposed_actions) { - return message; - } - return { - ...message, - proposed_actions: message.proposed_actions.map((currentAction, index) => - index === actionIndex ? updatedAction : currentAction, - ), - }; - }), - ); + for (const { actionIndex, action } of actionEntries) { + const approvalRecord = action.permission_request_id + ? capabilityRecords.find( + (record) => record.request.id === action.permission_request_id, + ) + : null; + const updatedAction: AgentChatActionProposal = + !approved && approvalRecord?.effective_status !== "pending_approval" + ? { + ...action, + execution_state: "blocked", + blocked_reason: copy.chatWorkbench.taskApprovalRejected, + dispatch_note: copy.chatWorkbench.taskApprovalRejected, + } + : await invoke("resume_agent_chat_action", { + accessMode: state.access_mode, + largeModelProvider: state.large_model_provider, + networkSearchSourceModel: state.network_search_source_model || null, + action, + }); + updateActiveAgentMessages((currentMessages) => + currentMessages.map((message) => { + if (message.id !== messageId || !message.proposed_actions) { + return message; + } + return { + ...message, + proposed_actions: message.proposed_actions.map((currentAction, index) => + index === actionIndex ? updatedAction : currentAction, + ), + }; + }), + ); + } await Promise.all([refreshCapabilityState(), refreshOperationsBriefingRuns()]); } catch (error) { + await Promise.all([refreshCapabilityState(), refreshOperationsBriefingRuns()]).catch( + () => null, + ); setAgentChatError(String(error) || copy.chatWorkbench.resumeActionFailed); } finally { setResolutionPending(null); @@ -4520,46 +4519,6 @@ export function App() { } }; - const resolveVisibleToolApproval = async (requestId: string, approved: boolean) => { - let matchingAction: - | { messageId: string; actionIndex: number; action: AgentChatActionProposal } - | undefined; - - for (const message of agentMessages) { - const actionIndex = - message.proposed_actions?.findIndex( - (action) => action.permission_request_id === requestId, - ) ?? -1; - if (actionIndex >= 0 && message.proposed_actions) { - matchingAction = { - messageId: message.id, - actionIndex, - action: message.proposed_actions[actionIndex], - }; - break; - } - } - - if (approved && matchingAction) { - await approveAndResumeAgentAction( - matchingAction.messageId, - matchingAction.actionIndex, - matchingAction.action, - ); - return; - } - - const resolved = await resolveCapabilityAccess(requestId, approved); - if (!resolved || !matchingAction) { - return; - } - await resumeAgentAction( - matchingAction.messageId, - matchingAction.actionIndex, - matchingAction.action, - ); - }; - const sendAgentMessage = async (event: FormEvent) => { event.preventDefault(); if (agentComposerAction === "stop") { @@ -4730,28 +4689,11 @@ export function App() { } }; - const pendingCapabilityRecords = capabilityRecords.filter( - (record) => record.effective_status === "pending_approval", - ); - useEffect(() => { - if (pendingCapabilityRecords.length === 0 || typeof window === "undefined") { - return; - } - window.requestAnimationFrame(() => { - const chatThread = chatThreadRef.current; - chatThread?.scrollTo({ - top: chatThread.scrollHeight, - behavior: "smooth", - }); - }); - }, [pendingCapabilityRecords.length]); const latestOperationsBriefingRun = operationsBriefingRuns[0]; const latestRunFailed = latestOperationsBriefingRun?.status === "failed"; const latestRunReady = latestOperationsBriefingRun?.status === "draft_ready"; const latestOperationsRunNeedsApproval = latestOperationsBriefingRun?.status === "pending_approval"; - const latestRunNeedsApproval = - latestOperationsRunNeedsApproval || pendingCapabilityRecords.length > 0; const latestAgentMessage = latestAssistantMessage(agentMessages); const latestAgentRunError = userFacingAgentRunError(latestAgentMessage, { requestFailed: copy.chatWorkbench.deepSeekRequestFailed, @@ -4761,6 +4703,9 @@ export function App() { ? latestAgentMessage : undefined; const latestAgentActions = latestAgentEnvelopeMessage?.proposed_actions ?? []; + const latestRunNeedsApproval = + latestOperationsRunNeedsApproval || + latestAgentActions.some((action) => action.execution_state === "needs_confirmation"); const latestAgentMissingPrerequisites = latestAgentEnvelopeMessage?.missing_prerequisites ?? []; const latestUserMessageWithAttachments = [...agentMessages] @@ -5348,6 +5293,7 @@ export function App() {

{deepSeekBalanceError}

) : null} +

{copy.settingsPanel.attribution}

@@ -5963,6 +5909,36 @@ export function App() { message.role === "assistant" ? (message.proposed_actions ?? []).filter(shouldShowAgentActionInChat) : []; + const messageApprovalActions = (message.proposed_actions ?? []) + .map((action, actionIndex) => ({ action, actionIndex })) + .filter(({ action }) => action.execution_state === "needs_confirmation"); + const messageApprovalRecords = messageApprovalActions.map(({ action }) => + action.permission_request_id + ? capabilityRecords.find( + (record) => record.request.id === action.permission_request_id, + ) + : null, + ); + const taskApprovalReady = + messageApprovalActions.length > 0 && + messageApprovalActions.every(({ action }, index) => { + if (!action.permission_request_id) { + return true; + } + const record = messageApprovalRecords[index]; + return ( + record?.effective_status === "pending_approval" || + (record?.effective_status === "approved" && + (record.grant_state === "reusable" || + record.grant_state === "one_shot_available")) + ); + }); + const taskApprovalHasPending = messageApprovalActions.some( + ({ action }, index) => + !action.permission_request_id || + messageApprovalRecords[index]?.effective_status === "pending_approval", + ); + const taskApprovalKey = `${message.id}:approval-group`; return (
@@ -6027,25 +6003,6 @@ export function App() {
    {visibleProposedActions.map((action) => { const actionIndex = message.proposed_actions?.indexOf(action) ?? -1; - const actionKey = `${message.id}:${actionIndex}`; - const approvalRecord = action.permission_request_id - ? capabilityRecords.find( - (record) => record.request.id === action.permission_request_id, - ) - : null; - const canResumeAction = - action.execution_state === "needs_confirmation" && - approvalRecord?.effective_status === "approved" && - (approvalRecord.grant_state === "reusable" || - approvalRecord.grant_state === "one_shot_available"); - const canApproveAndResumeAction = - action.execution_state === "needs_confirmation" && - ((action.permission_request_id !== null && - approvalRecord?.effective_status === "pending_approval") || - action.permission_request_id === null); - const actionButtonDisabled = - agentActionPending !== null || - resolutionPending !== null; const actionDetail = userFacingAgentActionDetail(action); return (
  • @@ -6057,43 +6014,61 @@ export function App() { {action.target ? ` · ${action.target}` : ""}

    {actionDetail ? {actionDetail} : null} - {canApproveAndResumeAction ? ( - - ) : canResumeAction ? ( - - ) : null}
  • ); })}
+ {taskApprovalReady ? ( +
+

+ {copy.chatWorkbench.approvalSummary( + messageApprovalActions.length, + )} +

+
+ + {taskApprovalHasPending ? ( + + ) : null} +
+
+ ) : null} ) : null} {message.role === "assistant" && message.memory_candidates?.length ? ( @@ -6125,70 +6100,6 @@ export function App() {
) : null} - {pendingCapabilityRecords.length > 0 ? ( -
- -
-
- {copy.capabilities.pendingTitle} - {pendingCapabilityRecords.length} -
-
- {pendingCapabilityRecords.map((record) => ( -
-
- {copy.capabilityOptions[record.request.capability]} -

- {copy.riskOptions[record.request.risk_level]} ·{" "} - {copy.accessOptions[record.request.access_mode]} -

- {record.request.exact_tool ? ( -

- {record.request.exact_tool.preview} -

- ) : null} -
-
- - -
-
- ))} -
-
-
- ) : null}
@@ -8273,9 +8184,18 @@ export function App() {
{capabilityCatalog.map((capability) => { const Icon = capabilityFamilyIcon(capability.family); - const latestRecord = capabilityRecords.find( - (record) => record.request.capability === capability.capability, + const latestPendingRecord = capabilityRecords.find( + (record) => + record.request.capability === capability.capability && + record.effective_status === "pending_approval" && + record.request.exact_tool === null && + !agentActionPermissionRequestIds.has(record.request.id), ); + const latestRecord = + latestPendingRecord ?? + capabilityRecords.find( + (record) => record.request.capability === capability.capability, + ); return (
@@ -8305,16 +8225,43 @@ export function App() { ) : null}
- + {latestPendingRecord ? ( +
+ + +
+ ) : ( + + )} ); })} diff --git a/apps/desktop/src/i18n.ts b/apps/desktop/src/i18n.ts index 8d75eda..5718e01 100644 --- a/apps/desktop/src/i18n.ts +++ b/apps/desktop/src/i18n.ts @@ -112,6 +112,7 @@ type TranslationSet = { balanceNotQueried: string; balanceFailed: string; balanceEmpty: string; + attribution: string; }; controls: { modelRoute: string; @@ -271,6 +272,8 @@ type TranslationSet = { workspaceBody: string; networkSearchTitle: string; networkSearchBody: string; + approvalSummary: (count: number) => string; + taskApprovalRejected: string; confirmAndRun: string; confirmingAction: string; resumeAction: string; @@ -995,6 +998,7 @@ export const translations: Record = { balanceNotQueried: "尚未读取余额", balanceFailed: "DeepSeek 余额读取失败。", balanceEmpty: "DeepSeek 未返回余额明细。", + attribution: "DS Agent 由 Lee take 创建并维护。", }, controls: { modelRoute: "模型类型", @@ -1300,6 +1304,9 @@ export const translations: Record = { workspaceBody: "请选择一个工作目录。DS Agent 会在里面自动创建和维护项目结构。", networkSearchTitle: "配置网络搜索", networkSearchBody: "这条指令看起来需要网络信息。请选择搜索来源模型后继续;需要密钥的搜索源会走对应提供商配置。", + approvalSummary: (count) => + `本次任务需要确认以下 ${count} 项动作;一次确认后将按顺序执行,也可以拒绝整个任务。`, + taskApprovalRejected: "用户拒绝了本次任务的待授权动作。", confirmAndRun: "确认并执行", confirmingAction: "执行中", resumeAction: "继续执行", @@ -2149,6 +2156,7 @@ export const translations: Record = { balanceNotQueried: "Balance not read yet", balanceFailed: "DeepSeek balance query failed.", balanceEmpty: "DeepSeek returned no balance details.", + attribution: "DS Agent is created and maintained by Lee take.", }, controls: { modelRoute: "Model type", @@ -2454,6 +2462,9 @@ export const translations: Record = { workspaceBody: "Choose one workspace. DS Agent will create and maintain the project structure inside it.", networkSearchTitle: "Configure Network Search", networkSearchBody: "This instruction appears to need web information. Choose a search source model to continue; key-backed search sources use their provider configuration.", + approvalSummary: (count) => + `This task needs approval for the ${count} action${count === 1 ? "" : "s"} below. One confirmation runs them in order, or you can reject the whole task.`, + taskApprovalRejected: "The user rejected the actions awaiting approval for this task.", confirmAndRun: "Confirm and run", confirmingAction: "Running", resumeAction: "Continue", diff --git a/apps/desktop/src/styles.css b/apps/desktop/src/styles.css index 86b21dc..128b9f0 100644 --- a/apps/desktop/src/styles.css +++ b/apps/desktop/src/styles.css @@ -1132,6 +1132,31 @@ h1 { opacity: 0.58; } +.agent-action-approval { + display: grid; + gap: 8px; + border-top: 1px solid var(--border); + padding-top: 10px; +} + +.agent-action-approval > p { + margin: 0; + color: var(--muted); + font-size: 12px; + line-height: 1.5; +} + +.agent-action-approval-buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.agent-action-reject { + min-height: 30px; + font-size: 12px; +} + .agent-action-state.needs_confirmation { border-color: color-mix(in srgb, #d9b35f 58%, var(--border)); color: #d9b35f; @@ -1730,6 +1755,13 @@ h1 { resize: vertical; } +.product-attribution { + margin: 2px 0 0; + color: var(--muted); + font-size: 11px; + line-height: 1.45; +} + .api-key-input-row { display: grid; grid-template-columns: minmax(0, 1fr) 18px; @@ -3282,6 +3314,12 @@ dd { justify-self: start; } +.capability-card-approval { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + .approval-queue { display: grid; gap: 8px; @@ -3289,33 +3327,6 @@ dd { padding-top: 10px; } -.chat-approval-queue { - display: grid; - grid-template-columns: 32px minmax(0, 1fr); - gap: 10px; - align-items: start; - justify-self: center; - width: min(100%, var(--chat-content-rail-width)); - min-width: 0; - scroll-margin-block: 16px; -} - -.chat-approval-card { - border: 1px solid var(--border-active); - border-radius: 8px; - background: var(--surface-bg); - padding: 12px; -} - -.chat-approval-card .approval-row { - display: grid; - gap: 10px; - border: 1px solid var(--border); - border-radius: 7px; - background: color-mix(in srgb, var(--surface-control) 74%, transparent); - padding: 10px; -} - .queue-heading { display: flex; align-items: center; @@ -3641,8 +3652,7 @@ dd { padding-right: 0; } - .chat-message, - .chat-approval-queue { + .chat-message { grid-template-columns: minmax(0, 1fr); } diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 85ab991..0085997 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -9,13 +9,13 @@ project does not depend on the maintainer's local directories. The published Windows release provides a normal NSIS setup executable: ```text -DS.Agent_1.0.0_x64-setup.exe +DS.Agent_1.0.1_x64-setup.exe ``` -Version `1.0.0` is the current published stable release. Earlier commits, tags, +Version `1.0.1` is the current published stable release. Earlier commits, tags, Releases, and assets remain immutable. The installer is unsigned, so Windows may show an unknown-publisher warning. Verify its SHA-256 against the value in -`docs/RELEASE_NOTES_v1.0.0.md` before running it. The installer +`docs/RELEASE_NOTES_v1.0.1.md` before running it. The installer embeds the Microsoft WebView2 bootstrapper and runs it silently when the target machine needs the WebView2 runtime; users do not need Node.js, pnpm, Rust, or a source checkout to run the installed app. diff --git a/docs/OPEN_SOURCE_RELEASE.md b/docs/OPEN_SOURCE_RELEASE.md index 6ad8e70..1b31cde 100644 --- a/docs/OPEN_SOURCE_RELEASE.md +++ b/docs/OPEN_SOURCE_RELEASE.md @@ -100,10 +100,11 @@ Ship a buildable local-first desktop Agent OS preview that demonstrates: exports or packaged assets do not enter generated source archives. - `.env.example` documents local DeepSeek and optional local bridge environment variables without storing secret values. -- `docs/RELEASE_NOTES_v1.0.0.md` is the stable release note and - `docs/DS_AGENT_V1_COMPLETION_AUDIT.md` records its local evidence and artifact - integrity. Historical release notes, including `v0.9.0`, `v0.8.0`, and - `v0.8.0-rc.1`, remain unchanged. +- `docs/RELEASE_NOTES_v1.0.1.md` is the current patch release note. + `docs/RELEASE_NOTES_v1.0.0.md` and + `docs/DS_AGENT_V1_COMPLETION_AUDIT.md` preserve the immutable first stable + release evidence. Historical release notes, including `v0.9.0`, `v0.8.0`, + and `v0.8.0-rc.1`, remain unchanged. Maintainer handoff notes, decision logs, and `docs/superpowers/` planning files are local-only continuation material. They are useful for project handoff on the diff --git a/docs/RELEASE_NOTES_v1.0.1.md b/docs/RELEASE_NOTES_v1.0.1.md new file mode 100644 index 0000000..a536a8b --- /dev/null +++ b/docs/RELEASE_NOTES_v1.0.1.md @@ -0,0 +1,81 @@ +# DS Agent v1.0.1 + +`v1.0.1` is a focused patch release for the first stable DS Agent 1.0 line. It +fixes an approval-surface regression found in the published `v1.0.0` desktop +app and adds visible creator/maintainer attribution. The `v1.0.0` commit, tag, +Release, notes, and installer remain immutable. + +Package, desktop, Tauri and Cargo metadata are `1.0.1`, and the updater identity +is `v1.0.1`. + +## Approval UI fix + +- Historical pending capability records are no longer inserted as one global + approval block beneath an unrelated active conversation. An ordinary + knowledge question therefore cannot inherit old browser, file, update, or + other approval rows merely because they remain pending in local history. +- Chat approval controls now belong to the assistant message and exact + `permission_request_id` values that proposed the actions. A task with no + action awaiting confirmation shows no approval controls. +- When one task needs several permissions, DS Agent shows one explanatory + sentence and one Confirm and run / Reject decision pair. One click + resolves every permission record for that task before its actions resume in + order. The Kernel still retains separate capability decisions and audit + records underneath that single user decision. +- Rejecting once blocks every action awaiting approval for that task. Partial + failures refresh current permission state so a retry does not blindly repeat + already completed decisions. +- Legacy manual capability requests remain actionable only in their own + capability card. Task-bound and exact-tool approvals are excluded from those + generic cards, preventing duplicate approval surfaces. + +The patch does not silently delete, approve, or reject historical local +records. Existing `v1.0.0` app data remains under the user's control. + +## Attribution and release identity + +- `Lee take` is visible as the creator and maintainer in Settings. +- Root, desktop, Rust package, and Windows application metadata consistently + identify version `1.0.1` and the supported publisher attribution. +- This attribution is not a digital signature. The Windows installer and + application remain unsigned and may show an unknown-publisher warning. + +## Model and authority boundary + +A valid DeepSeek API Key supplied by each user remains required. DS Agent does +not bundle a shared key or bypass DeepSeek access requirements. DeepSeek owns +open-ended understanding, planning, analysis, and synthesis; the DS Agent +Kernel owns deterministic validation, approval, execution, evidence, audit, +verification, and recovery. + +Production Microsoft/Google account registration and live external-write +authority remain disabled. This patch does not sign in to real accounts, send +real email, or create, change, or cancel real calendar events. + +## Windows download and integrity + +- Asset: `DS.Agent_1.0.1_x64-setup.exe` +- Size: `12,716,857 bytes` +- SHA-256: `469C4EFA54F4C94A6E37D28C9C88D331B26E1770C6792DC93D02B451640E2A6F` +- File and product version: `1.0.1` +- Architecture: Windows x64 +- Authenticode status: unsigned (`NotSigned`) + +The installer embeds the Microsoft WebView2 bootstrapper. The release artifact +was built and inspected without launching the installer or changing the +installed DS Agent application or its data. + +The offline release gate covers the source secret scan, TypeScript/Vite +production build, focused approval interaction checks, all Node/UI checks, and +852 Rust tests: 845 passed, seven permission-gated live/GUI tests were +intentionally ignored, and zero failed. + +## Deterministic briefing fixtures + +For deterministic Operations Briefing checks, +`docs/templates/operations-briefing-smoke-evidence` contains the warning +**SMOKE SAMPLE evidence for local verification only** and every file says +**Replace before operational use**. The bundled smoke files are marked as +non-operational test data. Separately, +`docs/templates/operations-briefing-evidence` contains blank operator templates +that the desktop can seed into a user-selected evidence folder. diff --git a/package.json b/package.json index 502ade4..b5be62a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "deepseek-agent-os", "private": true, - "version": "1.0.0", + "version": "1.0.1", + "author": "Lee take", "license": "Apache-2.0", "description": "Local-first DeepSeek AI work platform with background runs, permissioned tools, auditable evidence, and recovery.", "scripts": { diff --git a/scripts/approval-visibility.test.mjs b/scripts/approval-visibility.test.mjs index 6217b98..da5e838 100644 --- a/scripts/approval-visibility.test.mjs +++ b/scripts/approval-visibility.test.mjs @@ -10,21 +10,14 @@ const chatThreadStart = app.indexOf( ); const chatThreadEnd = app.indexOf('
', chatThreadStart); const chatThread = app.slice(chatThreadStart, chatThreadEnd); -const approvalQueueIndex = chatThread.indexOf('className="chat-approval-queue"'); -const pendingMessageIndex = chatThread.indexOf('className="chat-message assistant pending"'); assert.ok(chatThreadStart >= 0, "chat thread must remain the central scrolling surface"); -assert.ok(approvalQueueIndex >= 0, "pending approval queue must be rendered in chat"); -assert.ok( - approvalQueueIndex > pendingMessageIndex, - "pending approvals must follow the latest DS Agent status at the bottom of chat", -); -assert.match(chatThread, /resolveVisibleToolApproval\(record\.request\.id, true\)/); -assert.match(chatThread, /resolveVisibleToolApproval\(record\.request\.id, false\)/); -assert.match( - app, - /if \(pendingCapabilityRecords\.length === 0[\s\S]*?chatThreadRef\.current[\s\S]*?chatThread\?\.scrollTo\([\s\S]*?chatThread\.scrollHeight/, +assert.doesNotMatch( + chatThread, + /chat-approval-queue|pendingCapabilityRecords/, + "historical global approvals must never be rendered as part of the active conversation", ); +assert.doesNotMatch(app, /resolveVisibleToolApproval|pendingCapabilityRecords/); assert.doesNotMatch(app, /sidebar-approval-actions/); const inspectorStart = app.indexOf('
'); @@ -33,9 +26,48 @@ const inspectorApprovalArea = app.slice(inspectorStart, inspectorApprovalEnd); assert.ok(inspectorStart >= 0, "permissions inspector must remain available"); assert.doesNotMatch( inspectorApprovalArea, - /pendingCapabilityRecords\.map/, - "right inspector must not duplicate the interactive approval queue", + /agent-action-approval/, + "the inspector must not duplicate task-bound approval controls", +); +const capabilityGridStart = app.indexOf('
'); +const capabilityGridEnd = app.indexOf('{auditError ?', capabilityGridStart); +const capabilityGrid = app.slice(capabilityGridStart, capabilityGridEnd); +assert.ok(capabilityGridStart >= 0, "manual capability cards must remain available"); +assert.match(capabilityGrid, /className="capability-card-approval"/); +assert.match( + capabilityGrid, + /record\.request\.exact_tool === null[\s\S]*?!agentActionPermissionRequestIds\.has\(record\.request\.id\)/, +); +assert.match( + capabilityGrid, + /resolveCapabilityAccess\(latestPendingRecord\.request\.id, true\)/, +); +assert.match( + capabilityGrid, + /resolveCapabilityAccess\(latestPendingRecord\.request\.id, false\)/, +); +assert.match(app, /const resolveAndResumeAgentActionGroup = async/); +assert.match(chatThread, /className="agent-action-approval"/); +assert.match(chatThread, /copy\.chatWorkbench\.approvalSummary\(\s*messageApprovalActions\.length/); +assert.match( + chatThread, + /resolveAndResumeAgentActionGroup\(\s*message\.id,\s*messageApprovalActions,\s*true/, +); +assert.match( + chatThread, + /resolveAndResumeAgentActionGroup\(\s*message\.id,\s*messageApprovalActions,\s*false/, +); + +const groupResolverStart = app.indexOf("const resolveAndResumeAgentActionGroup = async"); +const groupResolverEnd = app.indexOf("const sendAgentMessage = async", groupResolverStart); +const groupResolver = app.slice(groupResolverStart, groupResolverEnd); +const permissionResolutionIndex = groupResolver.indexOf( + 'invoke("resolve_capability_access_request"', +); +const actionResumeIndex = groupResolver.indexOf( + 'invoke("resume_agent_chat_action"', ); -assert.match(app, /approveAndResumeAgentAction\(/); +assert.ok(permissionResolutionIndex >= 0, "task approval must resolve each audit record"); +assert.ok(actionResumeIndex > permissionResolutionIndex, "all task permissions resolve before actions resume"); console.log("approval visibility tests passed"); diff --git a/scripts/plugin-catalog.test.mjs b/scripts/plugin-catalog.test.mjs index 68edf64..d9089f8 100644 --- a/scripts/plugin-catalog.test.mjs +++ b/scripts/plugin-catalog.test.mjs @@ -43,7 +43,10 @@ const durableWorker = app.slice(durableWorkerStart, durableWorkerEnd); assert.match(durableWorker, /refreshSkillRecords\(\)/); const sendAgentPromptStart = app.indexOf("const sendAgentPrompt"); -const sendAgentPromptEnd = app.indexOf("const resumeAgentAction", sendAgentPromptStart); +const sendAgentPromptEnd = app.indexOf( + "const resolveAndResumeAgentActionGroup", + sendAgentPromptStart, +); const sendAgentPrompt = app.slice(sendAgentPromptStart, sendAgentPromptEnd); assert.match(sendAgentPrompt, /refreshSkillRecords\(\)/); diff --git a/scripts/release-source-check.mjs b/scripts/release-source-check.mjs index e770e2f..55c8192 100644 --- a/scripts/release-source-check.mjs +++ b/scripts/release-source-check.mjs @@ -4,7 +4,7 @@ import { spawnSync } from "node:child_process"; import { existsSync, readFileSync, statSync } from "node:fs"; import path from "node:path"; -const expectedVersion = "1.0.0"; +const expectedVersion = "1.0.1"; const maxSourceFileBytes = 2 * 1024 * 1024; const binaryReleaseExtensions = new Set([ ".appimage", @@ -50,6 +50,7 @@ const requiredDocs = [ "README.md", "README.zh-CN.md", "docs/INSTALLATION.md", + "docs/RELEASE_NOTES_v1.0.1.md", "docs/RELEASE_NOTES_v1.0.0.md", "docs/DS_AGENT_V1_COMPLETION_AUDIT.md", "docs/RELEASE_NOTES_v0.9.0.md", @@ -79,6 +80,7 @@ const publicReleaseCopyFiles = [ "apps/desktop/package.json", "docs/INSTALLATION.md", "docs/OPEN_SOURCE_RELEASE.md", + "docs/RELEASE_NOTES_v1.0.1.md", "docs/RELEASE_NOTES_v1.0.0.md", "docs/DS_AGENT_V1_COMPLETION_AUDIT.md", "docs/RELEASE_NOTES_v0.9.0.md", @@ -435,9 +437,11 @@ const failures = []; const checks = []; checkJsonField("package.json", "version", expectedVersion); +checkJsonField("package.json", "author", "Lee take"); checkJsonField("package.json", "license", "Apache-2.0"); checkJsonField("package.json", "packageManager", expectedPackageManager); checkJsonField("apps/desktop/package.json", "version", expectedVersion); +checkJsonField("apps/desktop/package.json", "author", "Lee take"); checkJsonField("apps/desktop/package.json", "license", "Apache-2.0"); checkJsonField("apps/desktop/src-tauri/tauri.conf.json", "version", expectedVersion); checkJsonField("apps/desktop/src-tauri/tauri.conf.json", "productName", "DS Agent"); @@ -448,6 +452,12 @@ checkJsonField( "ai.deepseek-agent-os.desktop", ); checkJsonField("apps/desktop/src-tauri/tauri.conf.json", "bundle.active", false); +checkJsonField("apps/desktop/src-tauri/tauri.conf.json", "bundle.publisher", "Lee take"); +checkJsonField( + "apps/desktop/src-tauri/tauri.conf.json", + "bundle.copyright", + "Copyright © 2026 Lee take", +); checkJsonField( "apps/desktop/src-tauri/tauri.windows.conf.json", "build.beforeBuildCommand", @@ -702,7 +712,7 @@ function checkRequiredDocs() { for (const [phrase, label] of [ ["One Kernel. Modular capabilities. Verifiable execution.", "English README product promise"], ["DS Agent is a local Agent Harness optimized for DeepSeek", "English README DeepSeek-first positioning"], - ["v1.0.0 stable", "English README formal stable status"], + ["v1.0.1 stable", "English README formal stable status"], ["The key difference is that Memory, Automation, Computer Use, parallel Subagents, and Skills are not isolated plugins", "English README unified Kernel thesis"], ["contract-first modular Harness architecture", "English README modular architecture"], ["Five core capabilities, one engineering philosophy", "English README five-capability framing"], @@ -714,7 +724,7 @@ function checkRequiredDocs() { ["goal → done-when contract → context → plan → permission → execution → evidence → verification → recovery", "English README Loop Engineering contract"], ["DeepSeek and DS Agent boundary", "English README model boundary"], ["Why Rust", "English README Rust rationale"], - ["Production Microsoft/Google account registration and live external-write authority remain disabled in v1.0.0", "English README connector authority boundary"], + ["Production Microsoft/Google account registration and live external-write authority remain disabled in v1.0.1", "English README connector authority boundary"], ["README.zh-CN.md", "English README language switch"], ["Search aliases: DS Agent, DSAgent, dsagent, DeepSeek Agent OS.", "English README searchable aliases"], ]) { @@ -723,7 +733,7 @@ function checkRequiredDocs() { for (const [phrase, label] of [ ["一个 Kernel,模块化能力,统一可信执行。", "Chinese README product promise"], ["DS Agent 是专门为 DeepSeek 优化的本地 Agent Harness", "Chinese README DeepSeek-first positioning"], - ["v1.0.0 正式稳定版", "Chinese README formal stable status"], + ["v1.0.1 正式稳定版", "Chinese README formal stable status"], ["真正的差异点是:记忆、自动化执行、Computer Use、Subagent 并行协作和技能", "Chinese README unified Kernel thesis"], ["契约优先的模块化 Harness 架构", "Chinese README modular architecture"], ["五项核心能力,一套工程理念", "Chinese README five-capability framing"], @@ -735,7 +745,7 @@ function checkRequiredDocs() { ["目标 → 完成条件 → 上下文 → 规划 → 权限 → 执行 → 证据 → 验证 → 恢复", "Chinese README Loop Engineering contract"], ["DeepSeek 与 DS Agent 的工作边界", "Chinese README model boundary"], ["为什么使用 Rust", "Chinese README Rust rationale"], - ["v1.0.0 仍未开放生产 Microsoft/Google 账号注册和真实外部写入权限", "Chinese README connector authority boundary"], + ["v1.0.1 仍未开放生产 Microsoft/Google 账号注册和真实外部写入权限", "Chinese README connector authority boundary"], ["README.md", "Chinese README language switch"], ["中文搜索别名:DS Agent、DSAgent、dsagent、DeepSeek Agent OS。", "Chinese README searchable aliases"], ]) { @@ -1583,8 +1593,14 @@ function checkPublicReleaseCopyPositioning() { checkTextIncludes( "apps/desktop/src-tauri/src/kernel/app_update.rs", readText("apps/desktop/src-tauri/src/kernel/app_update.rs"), - 'APP_UPDATE_CURRENT_RELEASE_TAG: &str = "v1.0.0"', - "app updater current release tag v1.0.0", + 'APP_UPDATE_USER_AGENT: &str = "DS-Agent-Updater/1.0.1"', + "app updater User-Agent v1.0.1", + ); + checkTextIncludes( + "apps/desktop/src-tauri/src/kernel/app_update.rs", + readText("apps/desktop/src-tauri/src/kernel/app_update.rs"), + 'APP_UPDATE_CURRENT_RELEASE_TAG: &str = "v1.0.1"', + "app updater current release tag v1.0.1", ); checkTextDoesNotInclude( "apps/desktop/src-tauri/src/kernel/app_update.rs", @@ -1592,6 +1608,24 @@ function checkPublicReleaseCopyPositioning() { 'APP_UPDATE_CURRENT_RELEASE_TAG: &str = "v0.9.0"', "app updater current release tag must not regress to v0.9.0", ); + checkTextIncludesCollapsed( + "docs/RELEASE_NOTES_v1.0.1.md", + readText("docs/RELEASE_NOTES_v1.0.1.md"), + "Package, desktop, Tauri and Cargo metadata are `1.0.1`, and the updater identity is `v1.0.1`.", + "v1.0.1 stable release notes updater identity", + ); + checkTextIncludesCollapsed( + "docs/RELEASE_NOTES_v1.0.1.md", + readText("docs/RELEASE_NOTES_v1.0.1.md"), + "one Confirm and run / Reject decision pair", + "v1.0.1 task-bound grouped approval fix", + ); + checkTextIncludesCollapsed( + "docs/RELEASE_NOTES_v1.0.1.md", + readText("docs/RELEASE_NOTES_v1.0.1.md"), + "The release artifact was built and inspected without launching the installer or changing the installed DS Agent application or its data.", + "v1.0.1 release validation boundary", + ); checkTextIncludesCollapsed( "docs/RELEASE_NOTES_v1.0.0.md", readText("docs/RELEASE_NOTES_v1.0.0.md"), @@ -2039,7 +2073,8 @@ function checkChatFirstCenterWorkbenchUi() { "copy.chatWorkbench.networkSearchTitle", "copy.chatWorkbench.title", "copy.chatWorkbench.composerPlaceholder", - "className=\"chat-approval-queue\"", + "className=\"agent-action-approval\"", + "resolveAndResumeAgentActionGroup", ]) { checkTextIncludes(appPath, app, snippet, `chat-first center workbench app snippet: ${snippet}`); } diff --git a/scripts/settings-panel.test.mjs b/scripts/settings-panel.test.mjs index 02652d8..035f36f 100644 --- a/scripts/settings-panel.test.mjs +++ b/scripts/settings-panel.test.mjs @@ -49,6 +49,21 @@ test("settings panel exposes only the ordinary user configuration items", () => ); }); +test("settings panel shows the creator and maintainer attribution", () => { + const appSource = readFileSync(appSourceUrl, "utf8"); + const i18nSource = readFileSync(i18nSourceUrl, "utf8"); + const styles = readFileSync(stylesSourceUrl, "utf8"); + + assert.match(appSource, /className="product-attribution"/); + assert.match(appSource, /copy\.settingsPanel\.attribution/); + assert.match(i18nSource, /attribution:\s*"DS Agent 由 Lee take 创建并维护。"/); + assert.match( + i18nSource, + /attribution:\s*"DS Agent is created and maintained by Lee take\."/, + ); + assert.match(styles, /\.product-attribution\s*\{/); +}); + test("model selector uses ordinary model type language and concise options", () => { const i18nSource = readFileSync(i18nSourceUrl, "utf8");