Compare commits

...
68 Commits
Author SHA1 Message Date
mesalogo a383b2d11f fix: reduce conversation history memory use
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Load lightweight conversation summaries and hydrate full history only when
needed, preserving search, copy, export, queued requests, and remote questions.
Keep unchanged image reconciliation messages stable so list refreshes no
longer resend the entire saved history.

Prepare Desktop 0.13.6 with the approved bilingual notes and aligned feature
matrices. Agent remains 0.13.0; no LoongArch preview or navigation demo edits
are included. Development validation passed 4301 tests with 67 skipped;
the user requested CI validation instead of repeating local release checks.

Release note: 历史会话按需加载,减少切回应用时的重复保存和内存占用,同时保留完整历史、搜索与续聊。
2026-09-15 20:33:47 +08:00
mesalogo f47049c40d Merge github/main README updates into release candidate
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agent Release / Build darwin arm64 compound package (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
GoodBuddy Agent Release / Build linux arm64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Build linux x64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Sign cumulative Agent catalog (push) Canceled after 0s
GoodBuddy Agent Release / Publish Agent to GitHub and OSS (push) Canceled after 0s
Preserve the published English and Chinese README community QR-code updates. Desktop 0.13.5, Agent 0.13.0, approved bilingual release notes, and the validated product code remain unchanged.
2026-09-15 17:29:33 +08:00
mesalogo 4c18d8d9c6 chore: prepare Desktop 0.13.5 and Agent 0.13.0 releases
Record the approved bilingual release notes and align Desktop and Agent version metadata. Refresh both feature lists with image tools, PPTX OCR, Runtime process reuse, and the completed/to-implement status convention.

Agent 0.13.0 requires Desktop 0.13.5. Keep Node 24.19.0 and OpenCode 1.18.29 unchanged, preserve published release history, and exclude the LoongArch preview.

Validation: bilingual release notes verified; 4293 tests passed and 67 skipped; typecheck, lint, version consistency, and 63 documentation links passed. No new real model calls, Host reruns, local production build, or packaging. Full UI-driven image invocation and local/remote switching are not covered by the existing validation records.
2026-09-15 17:25:12 +08:00
mesalogo 8a63ac9ee8 fix: recognize PPTX images and release idle OCR memory
Image-only PPTX attachments failed with no indexable text even when a local
OCR model was configured. PPTX parsing now recognizes embedded PNG, JPEG
and WebP images through the existing local OCR path, preserves native text
and slide locators, and honors the selected parsing workflow.

OCR workers previously retained model sessions and WASM memory indefinitely
after successful parsing. Release the worker after 60 seconds with no active
or queued requests, reuse it for continuous work, and reload on demand.
Fast text/index modes still skip OCR. This does not render Office layouts,
recognize inherited master images, or change Agent execution permissions.

Validated four real PPTX attachments (40 pages), idle memory release and a
nine-page reimport after model reload on Windows x64 with PP-OCRv6 Tiny.
49 local OCR requests and zero text-model calls. All 4293 tests passed
with 67 skipped; typecheck, lint and the isolated Electron build passed.

Release note: 修复已配置本地 OCR 时图片型 PPTX 仍无法导入的问题,识别结果保留幻灯片定位。OCR 空闲 60 秒后自动释放模型运行内存,连续解析复用模型,下次使用时自动重新加载。
2026-09-15 16:58:28 +08:00
mesalogo 02e59427a1 docs: plan private app market and local model management
Record YAML application import/export, configurable market URLs, and future ShareServer distribution responsibilities. Define a local model management entry with managed-process and external-service boundaries; both remain planned capabilities.
2026-09-15 16:52:57 +08:00
mesalogo 34215c72a1 docs: define runtime checklist display scope
Specify read-only OpenCode and Continue checklists above conversations, including remote Agent delivery and validation requirements. Record native runtime test evidence and distinguish planned work from implemented behavior.
2026-09-15 15:59:13 +08:00
mesalogo f8d07419d2 feat: generate and edit conversation images across runtimes
Add model-controlled image invocation and read-only runtime assignments. Route image tools through Main so accepted operations retain their originating conversation when chat runtimes change, with saved references, cancellation, and manual recovery.

Keep Node hashing outside browser-shared contracts to restore production builds. Validation includes the production build, typecheck, lint, focused regressions, real Host transport checks, and two successful real image requests. Full UI-driven runtime switching acceptance remains documented as pending.

Release note: 开启图片模型的会话调用后,聊天助手可调用图片生成与编辑工具,Runtime 分配自动管理。图片成果保存在原会话,可继续修改,并独立显示生成状态和取消入口。
2026-09-15 15:51:52 +08:00
lofyerandGitHub d898f420fd Merge pull request #4 from mesalogo/jojocoding-patch-3
Update README.md
2026-09-15 08:20:55 +08:00
lofyerandGitHub c4492e4a92 Merge pull request #5 from mesalogo/jojocoding-patch-4
Update README.zh-CN.md
2026-09-15 08:20:34 +08:00
mesalogo e5aa9ed688 fix: reuse runtime processes across project sessions
Multiple projects could spawn duplicate heavyweight runtimes, while settings
changes and remote reconnection could cancel or strand native conversations.
Share compatible local OpenCode and DeepSeek Harness process owners and
Agent-owned ACP connections, retaining workspace routing, per-session modes,
model attribution, question ownership, targeted cancellation and idle cleanup.

Restore remote pending questions and terminal task projections across reconnect
and desktop restart, and preserve accepted ordinary requests during settings
replacement while cancelling expert-team parents with their replaced scheduler.
Extend regression coverage and Agent type checks. Record measured resource and
real-model validation results, including failures and limitations.

Also include the conversation media-generation design drafts (not an implemented
feature), documentation entry links and dedicated Linux test-directory guidance.
Desktop and Agent changes must be delivered together for shared process results.

Validation: typecheck and lint passed; staged content and documentation links
checked. Prior full regression and live-model evidence are recorded in progress
notes; no new full test suite or model calls were run for this commit.

Release note: 多项目会话现在复用兼容的 OpenCode 和 DeepSeek Harness 进程,减少重复启动并保持工作区、工具和取消隔离。修复远端问答在断线、重启及配置更新后的恢复与状态同步问题,Desktop 与 Agent 需配套更新。
2026-09-15 08:14:42 +08:00
mesalogo b755951ecd fix: offer recovery for blank desktop windows
React render failures now show a localized reload page, and renderer crashes offer native reload confirmation. Record renderer and load failures in existing diagnostics without automatic retry loops. Reload may lose unsaved input; the intermittent failure cause remains unconfirmed.

Release note: Recover from desktop rendering failures using a visible Reload action instead of being left without a recovery entry point.
2026-09-15 08:07:08 +08:00
mesalogo d4f1668c9b feat: paste local files into chat attachments
Copied local files previously required the attachment picker. The chat
composer now imports one or more supported files directly from paste,
sharing parsing progress, limits, and batch rollback with file selection.
Text and screenshot paste remain available, and failures preserve drafts.

Validation: prior 166 focused tests, typecheck, and lint passed; 28 targeted
tests passed again before commit. The prior full suite had 4214 passes,
67 skips, and 6 window-recovery failures; window tests passed 10/10 alone.
Native clipboard behavior was verified on Windows; macOS/Linux remain unverified.

Release note: 现在可在文件管理器中复制一个或多个支持的本地文件,直接粘贴到聊天输入框添加附件;可查看读取和解析进度,失败时保留草稿与已有附件。
2026-09-15 08:05:31 +08:00
mesalogo e1fe1cf10a fix: refresh magic notes after external updates
Subscribe the open Magic Notes workspace to committed database changes from desktop and Agent/MCP writes. Preserve selections and unsaved drafts during refresh and external deletion.

File-level commit includes existing runtime edits in shared files; their remaining dependencies are still in the working tree.

Release note: Magic Notes now updates automatically after Agent/MCP edits while preserving your selection and unsaved drafts.
2026-09-15 00:31:21 +08:00
mesalogo fbba2e3abf fix: place workbar toggle beside theme switch
Move the assistant workbar toggle from the floating content corner to the topbar immediately before the theme switch. Preserve toggle behavior and update layout tests and documentation.

Release note: The workbar toggle now stays beside the light/dark theme switch instead of floating over the content area.
2026-09-14 23:37:37 +08:00
mesalogo 511f50aa63 fix: keep remote recovery actions inside the project menu
Remove the duplicate recovery button beside New project and its extra layout column. Keep the collapsed failure status and the retry action on the affected project inside the dropdown.

Stage complete files as requested, including existing App snapshot regression coverage and remote runtime design updates in those files.

Validation: 29 ProjectSwitcher tests and the targeted App recovery test passed; typecheck and lint passed. Full test run timed out after 60 seconds.

Release note: Remote recovery retries now stay in the project dropdown, keeping the project header stable when a connection fails.
2026-09-14 22:16:13 +08:00
mesalogo 6c1e8105dd fix: keep project activity submenu height independent
Opening long conversation lists stretched and repositioned the project menu. Give the conversation submenu its own surface and positioning, aligned to the selected project row with independent viewport clamping and scrolling. Use compact navigation when neither side has room.

Validation: 13 component tests, typecheck and lint passed; 108 Electron fixture checks passed across 14 light/dark scenarios. Full test run timed out after 60 seconds.

Release note: Project activity menus now keep the project list stable when opening long conversation submenus.
2026-09-14 22:10:35 +08:00
jojokeeandGitHub 3b83c90d0f Update README.zh-CN.md
更新社群二维码
2026-09-14 16:43:20 +08:00
jojokeeandGitHub 327b06587b Update README.md
更新社群二维码
2026-09-14 16:42:30 +08:00
mesalogo de14a6157a fix: hide project activity summary when idle 2026-09-14 09:06:36 +08:00
mesalogo 81ae9e0324 chore: prepare Desktop 0.13.2 and Agent 0.11.25
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (push) Canceled after 0s
GoodBuddy Agent Release / Build darwin arm64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Build linux arm64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Build linux x64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Sign cumulative Agent catalog (push) Canceled after 0s
GoodBuddy Agent Release / Publish Agent to GitHub and OSS (push) Canceled after 0s
Prepare bilingual notes and aligned feature lists for the schema-35 history repair, retained tool summaries, Windows DeepSeek workspace paths, and compact project activity navigation.

Agent 0.11.25 is a maintenance release with unchanged protocol and runtime pins. Its compound packages require Desktop 0.13.2. Desktop targets the standard six platforms without a LoongArch preview.

Make activity interaction tests wait for the loaded conversation and enabled send button, reset response mocks between cases, and wait for completed answer rendering. Accept bilingual maintenance-only Agent notes without inventing features or fixes.

Validation: 4,155 tests passed, 66 skipped; typecheck, lint, and release notes verification passed. The affected 20 tests passed three consecutive runs; all 227 App and Agent release-note tests passed. No local production build or packaging was run.
2026-09-14 01:18:42 +08:00
mesalogo f49d6678a3 fix: refine project activity spacing and conversation rows
Add 8px below the activity summary to separate it from new conversation. Remove redundant conversation-row arrows while keeping project submenu arrows, and update the UI documentation.

Validation: 11 ProjectActivity tests and typecheck passed. Lint was interrupted before completion.

Release note: 调整项目活动摘要与新建会话的间距,移除会话列表末尾的多余箭头,保留项目子菜单指示。
2026-09-14 00:40:10 +08:00
mesalogo 0d7ba55a50 fix: preserve tool details and prevent repeated history growth
Show bounded operation summaries for Continue, DeepSeek Harness and direct-model tools, retaining input details across incremental updates. Use one canonical workspace path for DeepSeek Host startup and ACP sessions so equivalent Windows paths reuse a working Runtime.

Compare persisted subagent tool fields by value, release terminal task caches, and avoid rebuilding caches for duplicate remote events. Upgrade to schema 35 to recompact repeated schema-34 tool blocks while preserving events and provenance. Include regression coverage, validation records and temporary-resource test exclusions.

Validation: 4155 tests passed, 66 skipped; typecheck and lint passed. Full desktop real-model validation covered all three tool-summary paths and both Windows path spellings. Storage validation evidence includes local and remote tool workloads and real database upgrade checks.

Release note: Tool records now show paths, commands and search details across runtimes. Fix DeepSeek Windows workspace failures and repeated subagent history growth; schema 35 reclaims redundant history data while preserving execution records, and older clients cannot reopen the upgraded database.
2026-09-14 00:34:51 +08:00
mesalogo c71da4b6ae fix: browse project activity through cascading menus
Replace the production activity dialog with anchored project and conversation menus, preserving exact conversation navigation and adding keyboard and narrow-window coverage. Include the interactive demo and design documentation.

The demo hides idle activity and removes conversation arrows; production still retains those two elements. Validation: typecheck, lint and 11 ProjectActivity tests passed. The full suite timed out after 240 seconds with an agent-package offline inventory test failure.
2026-09-13 23:06:20 +08:00
mesalogo ba6f6b8f22 fix: show operation details in OpenCode tool records
Preserve OpenCode operation titles in main-session tool summaries, including permission waits, failures and interrupted calls. Keep title trimming and length limits aligned with subagent records.

Update tool display documentation and include the activity-menu wording in UI-DESIGN.md. Validation: 132 focused tests passed; main-process typecheck and changed-file lint passed. Full-suite checks were affected by concurrent activity-menu changes and a test timeout.

Release note: OpenCode main-session tool records now show file paths, commands or operation descriptions beside tool names, matching subagent records.
2026-09-13 22:55:12 +08:00
mesalogo 49377206cc fix: stop subagent history from exhausting disk space
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Repeated full subagent progress snapshots could grow the desktop database to
many gigabytes during ordinary long-running tasks. Local and managed SSH
writes now retain lossless progress changes while preserving conversation
snapshots, event identities, remote replay deduplication and transaction rollback.

Upgrade existing history in bounded worker-thread batches, show progress and
retry controls, and reclaim freed disk space after conversion. Schema 34 cannot
be opened by older clients; a pre-upgrade backup is required for client rollback.
Document reusable database migration guidance and the verified large-database,
portable, local Runtime and Linux Host evidence.

Prepare Desktop 0.13.1 with the approved bilingual notes and aligned feature
lists. Agent remains 0.11.24. Final candidate validation and production builds
will run on main CI, as requested, without additional local release testing.

Release note: 修复长任务和子任务造成的数据库异常膨胀;升级时自动整理旧记录并回收空间,保留聊天、执行详情和结果,并支持退出后继续处理。
2026-09-13 20:50:33 +08:00
mesalogo 1c22e1a06d fix: keep browser overlays and knowledge controls usable
Native browser pages could cover application dialogs and overlapping menus
or notifications. Suspend only the viewport while covered and restore the
same active page afterward, including nested dialogs and terminal prompts.

Keep knowledge source controls at their full height, constrain long library
cards and titles, and update automatic names when switching remote targets
without overwriting edited or saved names. Restore packaged release-note
loading now that the history exceeds its previous size limit.

Add focused regressions and real Electron UI evidence. Clean up temporary
Harness fixture directories at test completion instead of accumulating them.

Release note: 修复内嵌浏览器遮挡弹窗和浮层、知识库来源选项被压缩及长名称显示异常;切换远端知识库时自动名称正确更新,并恢复更新说明显示。
2026-09-13 19:46:37 +08:00
mesalogo a0456f687a chore: prepare Desktop 0.13.0 and Agent 0.11.24 releases
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (push) Canceled after 0s
GoodBuddy Agent Release / Publish Agent to GitHub and OSS (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agent Release / Build darwin arm64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Build linux arm64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Build linux x64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Sign cumulative Agent catalog (push) Canceled after 0s
Record the approved bilingual release notes against Desktop v0.12.12 and
Agent agent-v0.11.23, rather than listing intermediate development fixes.
Desktop adds external knowledge connections and cross-project activity;
remote native questions remain classified as fixes in both release notes.

Align both feature lists, compatibility guidance and validation evidence.
Upgrade Desktop to 0.13.0 before Agent 0.11.24. Keep OpenCode at 1.18.29,
retain existing published notes, and exclude the LoongArch preview.

Allow fixes-only Agent notes while checking bilingual section consistency.
Validation: 4089 tests passed with 66 skipped; release-note verification,
typecheck, lint and documentation link checks passed. Full remote-provider
acceptance and candidate CI/native release validation remain outstanding.
No release tags or publication are part of this commit.
2026-09-13 17:29:37 +08:00
mesalogo 59a1624396 fix: close OpenCode event streams before ending requests
Completed parallel conversations could leave buffered event connections open.
Chat and native context compaction now cancel their own subscriptions before
ending SDK iteration, without cancelling another conversation or its Server.

Strengthen cleanup-order regressions and verify real OpenCode parallel runs,
cancellation and compaction. The remote Agent uses ACP stdio and transcript
polling rather than this SDK event stream and needs no corresponding change.

Validation: 99 focused tests passed; the final candidate passed 4089 tests
with 66 skipped, typecheck and lint. No external model calls were made.
2026-09-13 17:28:40 +08:00
mesalogo a44de1441a docs: record recurring defect types and the mechanism covering each
The titlebar drag-region defect was fixed three times in a row, each time
only for the dialogs someone reported, and the same batch introduced two more
instances. Repeated full-table lookups and production reuse of a verification
helper showed up in several places at once for the same reason.

Add docs/quality/recurring-defects.md describing each type that has actually
recurred here, the shared mechanism or automated test that now covers it, and
the history that justifies the entry. Require reading it from AGENTS.md before
touching overlays, store lookups, retrieval entry points, cancellation, or
render-time derived values, and require extending the shared mechanism rather
than patching one reported case.

Correct the external knowledge technical design where it described the removed
configuration-snapshot checks, the shared retrieval entry point and the
unreachable probe status, and record the refactor and its validation in the
knowledge base progress log.
2026-09-13 16:02:15 +08:00
mesalogo f7f0332143 chore: drop unreachable external knowledge states and helpers
Remove code with no production path: the probeStatus value 'healthy' that no
probe can produce, the external.transportConfirm string and the
remote-missing and config-invalid state strings that nothing renders,
KnowledgeService.searchHybridMany which lost its last caller when chat search
moved to retrieveMany, and the sourceUrl field that no provider parser
populates.

Keep sourceUrl in externalKnowledgeLocatorSchema because that schema parses
already-persisted citations. Point the affected fixtures at catalog-ready,
the status testInstance actually writes.

Document why ProjectActivity adjusts state during render: the guarded call is
the recommended pattern for resetting state on prop changes here, and the
repository lint rule rejects the useEffect alternative.
2026-09-13 16:02:01 +08:00
mesalogo 4222351f3c fix: keep every full-window overlay clickable over the titlebar
The drag-region fix applied to six dialogs left seven overlays uncovered,
including two added in the same batch, so their controls still stopped
responding to hover and clicks where they overlapped the native titlebar.
Three overlays used inset: 38px 0 0 to dodge the titlebar instead of
releasing the drag region, leaving an unmasked strip.

Replace the per-dialog declarations with one shared selector covering all
fifteen overlays and give each one inset: 0. Add a regression test that
discovers every fixed overlay from the stylesheet and fails when a new one
misses the shared rule, so this stops being fixed one dialog at a time.

Release note: 修复 MCP 编辑器、发布说明、远端目录选择、HTML 预览、设置、跨项目活动和外部
知识库实例等弹窗与标题栏重叠时,控件无法悬停和点击的问题。
2026-09-13 16:01:41 +08:00
mesalogo 8ad26d43bb fix: remove repeated full-table scans from knowledge and activity paths
Binding checks loaded and parsed every external knowledge binding to answer a
single-row question. requireLibrary did this on more than thirty call sites
including one inside a task reconciliation loop, and the knowledge snapshot
scanned the table twice per library across its seven callers.

Add keyed store lookups (getBinding, hasBinding, getBindingsForInstance) that
use the existing primary key, build the snapshot's bindings once into a Map,
and count bindings per instance in a single pass.

Memoize the conversation activity index in the renderer. It was rebuilt on
every render, including once per streaming token, which discarded half the
benefit of the memoized activity summary directly above it.

Release note: 知识库页面、对话预检索和跨项目活动列表在绑定较多时响应更快,流式回答期间的
界面开销降低。
2026-09-13 16:01:26 +08:00
mesalogo 74a2f187ba fix: stop external knowledge chat retrieval from running binding checks
Conversation retrieval called the binding verification helper, so every
question sent the user query as a test query and RAGFlow bindings with graph
or knowledge compilation enabled spent an extra detail request before
retrieving. A failed capability check could also surface
EXTERNAL_KB_CAPABILITY_UNAVAILABLE during a normal conversation.

Retrieval and verification are now separate service entry points sharing one
snippet-bounding helper. The capability pre-check stays in the verification
path and keeps using the binding's configured request timeout.

Replace the five configuration snapshot comparisons and the
EXTERNAL_KB_CONFIG_CHANGED code with the existing per-instance cancellation.
A connection test cancelled by a configuration change now rejects without
writing back a probe result, so disabling an instance no longer records it as
a failed probe. Duplicate binding safety relies on a re-check plus the
existing unique index.

Release note: 外部知识库对话检索不再执行仅用于保存校验的能力预检,RAGFlow 图谱绑定每次
提问少一次请求;停用实例时不再把它错误标记为连接失败。
2026-09-13 16:01:12 +08:00
mesalogo ca4a58c762 fix: keep full-window dialogs responsive to mouse input
Render feedback, parsing diagnostics, knowledge confirmations, WeChat binding and image preview overlays through body portals so native titlebar drag regions cannot intercept their controls. Preserve themed form styles and modal focus isolation.

Validation: 320 targeted tests, typecheck and lint passed. Native Windows hover and close checks passed for feedback and parsing diagnostics in light and dark themes at two sizes. Full suite timed out without a summary.

Release note: 修复反馈等弹窗与窗口标题栏重叠时,关闭按钮无法正常悬停和点击的问题。
2026-09-13 14:19:46 +08:00
mesalogo ea45533137 fix: allow follow-up messages after cancelling remote OpenCode questions
Cancelling a managed SSH prompt while answering a question could leave the conversation bound to its previous operation and reject the next message. Record the Agent cancellation outcome and reconcile and close the desktop binding before the next prompt.

Add two cancellation regressions and document real SSH/Electron question validation across eight theme and viewport combinations. Local queues and remote question routing are already included in 3b560c9.

Validation: prior focused checks passed 275 tests with 5 skipped; typecheck and lint passed. The final full run passed 4087 tests with 66 skipped and one packaging timeout; that packaging file separately passed 22 tests with 1 skipped. Staged diff whitespace check passed.

Release note: After cancelling a pending OpenCode question on a managed SSH task, you can send another message in the same conversation without an operation identity error.
2026-09-13 13:57:57 +08:00
mesalogo e9679fecf1 fix: keep project dialog close controls clickable
Render project creation and settings dialogs in a body portal so the Electron title-bar drag region does not intercept their close controls. Cover dialog placement, scrolling, and focus restoration, and document the layout rule.

Release note: Project creation and settings dialogs keep their close buttons clickable after scrolling and restore focus to their trigger when closed.
2026-09-13 13:31:42 +08:00
mesalogo 3b560c9a5e feat: add external knowledge retrieval and cross-project activity
Connect Dify, FastGPT and RAGFlow through read-only bindings, encrypted local credentials, shared retrieval and persistent citations. Configuration and removal affect GoodBuddy only.

Show active conversations across projects, retain older live work, and improve project dialog scrolling. Queue concurrent questions and route managed SSH OpenCode questions through the Agent. Preserve recoverable read/search errors and partial search results.

Document the Electron UI automation standard, real model and external knowledge validation, and remaining acceptance limits.

Validation: earlier full regression passed 4081 tests with 66 skipped; subsequent focused remote-question checks and real Linux evidence are recorded in feature progress. Precommit typecheck and lint passed; latest project dialog and shared UI suites passed 58 tests. The full suite was not rerun for the final dialog changes.

Release note: Use external knowledge in conversations, find active work across projects, and answer concurrent local or managed SSH OpenCode questions. Managed SSH questions require the updated Agent adapter.
2026-09-13 13:10:01 +08:00
mesalogo b84250f62a fix: allow closing browser tabs during active requests
Allow users to close leased browser tabs without cancelling the AI request or revoking its other tools. Closed-page operations report a recoverable error; explicit navigation creates a dedicated replacement tab for direct-model and MCP calls.

Add regression coverage and real Electron lifecycle checks, and update browser behavior documentation.

Release note: 浏览器标签页被 AI 请求占用时也可关闭,其他任务继续运行;再次导航会打开独立新页。
2026-09-13 11:21:37 +08:00
mesalogo 032ebc313f fix: simplify conversation composer options
Consolidate composer settings into a compact popover while preserving runtime, mode, sending, and queue controls. Place the settings icon after voice input, limit the panel to 300px, and show knowledge expansion state.

Include the navigation demo, design guidance, localized labels, and composer regression coverage.

Release note: Simplify the conversation input toolbar with compact settings, clearer knowledge expansion, and visible active selections.
2026-09-13 00:05:05 +08:00
mesalogo 719c9fd6cd fix: add inline clear action to conversation search
Show a clear button when the conversation search contains text. Clearing restores the scoped conversation list and returns focus to the input, with regression coverage for visibility and filtering.

Release note: Conversation search now includes an X button to clear keywords and return to the current project's conversations.
2026-09-12 22:10:25 +08:00
mesalogo d36a05f7ad fix: prevent blank browser reloads and clear stale errors
Require a committed page URL before enabling reload. Clear sidebar action errors when leaving or closing a panel, and ignore late failures from its previous scope.

Add regression coverage and document browser error lifecycle and validation results.

Release note: 修复空白浏览器页刷新时报错、关闭浏览器后错误提示仍残留的问题;切换或关闭面板后,旧操作的延迟失败不再重新显示。
2026-09-12 21:13:44 +08:00
mesalogo fc36433d76 fix: simplify tool execution records in conversations
Replace full-width tool cards with compact disclosure rows in conversations and subagent progress. Remove redundant runtime summaries, keep expansion under user control, and show successful calls with green checks.

Prioritize errors and results, fold inputs, and provide original-text copying through application notifications. Detail text wraps in bounded scroll areas with no extra display controls.

Validation: 3927 tests passed, 66 skipped; typecheck and lint passed.

Release note: Tool activity is now more compact, with clearer status icons and less repeated text. Expand a record to read and copy results, errors, or input parameters.
2026-09-11 14:54:11 +08:00
mesalogo f6287589a3 chore: prepare Desktop 0.12.12 release
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Bump the desktop patch version and package matching Chinese and English notes
for image follow-up context, retained questions and answers, simpler Runtime
selection, and docked Git history with automatic project detection.

Review and align both feature matrices with the verified release range.
Agent stays at 0.11.23; no Agent release or LoongArch preview is included.
External knowledge probe tooling remains development-only, not a shipped
external knowledge-base feature.

Validation: 3911 tests passed, 66 skipped; typecheck, lint, and bilingual
release-note verification passed. Production builds use main-branch CI before
the immutable desktop tag; no local production package was built.
2026-09-11 01:21:50 +08:00
mesalogo b2c85b8ece fix: describe saved fixed project models accurately
Projects with a saved fixed Runtime model could incorrectly claim to follow
the global Runtime configuration. Restore the fixed-model description while
keeping the simplified menu and the ability to return to inherited settings.
Model resolution, permissions, and remote Agent behavior are unchanged.

Validation: 3911 tests passed, 66 skipped; typecheck, lint, and release-note
validation passed. Added bilingual regressions for all three Agent Runtimes.

Release note: 项目已固定 Runtime 模型时,现在明确显示所用固定模型,不再误报为跟随全局设置。
2026-09-11 01:21:24 +08:00
mesalogo 073eb0d2f5 fix: dock workspace history and detect Git on project switches
Keep commit history at the sidebar bottom, expand it to half the Git content area, and preserve independently scrolling panes when resizing. Use the shared refresh action and remove the redundant changed-files border.

Load Git status on project changes regardless of restored sidebar selection, and keep status and error fallback scoped to the selected project.

Release note: 提交历史默认贴底,展开占用一半内容区,支持拖拽调整并统一使用顶部刷新。切换项目后自动识别 Git 工作区,无需手动刷新。
2026-09-11 00:51:25 +08:00
mesalogo 3bf720a32c fix: retain submitted questions and answers in conversations
Preserve read-only question and answer reviews after successful submission or skipping, including multiple rounds and local conversation reloads. Keep failed submissions retryable and preserve newer questions and terminal states when responses arrive late.

Validation: typecheck and lint passed; full suite had 3900 passes, 66 skips and one Agent package timeout that passed on isolated rerun. Real OpenCode UI acceptance was not run. Previously discarded answers cannot be recovered.

Release note: 修复 OpenCode 提交回答后会话仅显示已提交的问题;现在保留原问题及对应答案,多轮问答和跳过记录在重新打开会话后仍可查看。
2026-09-10 23:47:44 +08:00
mesalogo 1274c762e8 fix: simplify runtime model choices in conversations and projects
Remove redundant provider and model combinations from conversation and project Runtime selectors. Runtime model configuration stays in system settings, while saved fixed project selections remain visible for compatibility.

Release note: Conversation and project Runtime menus are simpler; change Runtime models in system settings.
2026-09-10 23:00:01 +08:00
mesalogo c0ad27ab44 fix: preserve image context across follow-up requests
Image requests previously sent only the current prompt and attachments, so
follow-up edits could lose the previous image and conversation requirements.
Reuse the latest successful image from the existing artifact store together
with text history, including after reopening the app. Explicit attachments
take priority, and configured model IDs remain unchanged.

When image editing is explicitly unsupported, continue with text-based image
generation and retain a quiet message footer explaining the missing image
context. Keep genuine provider errors and cancellation visible. Add focused
regressions, persisted footer metadata, and aligned feature documentation.

Validated with four real sub-gpt-image-2 image requests and a simulated
unsupported-edit response. Image regressions, typecheck and lint passed;
the full working-tree run had two unrelated concurrent Runtime-menu failures.

Release note: 连续改图会自动带上上一张图片和文字上下文,重开对话后仍可继续修改。上游明确不支持编辑时仍可按文字生图,仅在回复底部轻提示未使用参考图片。
2026-09-10 22:39:41 +08:00
mesalogo 4483c92b57 fix: distinguish external knowledge probe failures from empty results
Validate Dify and RAGFlow outcomes, normalize FastGPT URLs, and report response envelopes, score structures and actual rerank state. Align integration documentation with official APIs and archive 33 redacted read-only requests with strict HTTPS verification.

Validation: 43 focused tests, typecheck and lint passed. Full test runs exceeded 60s and 240s without a summary; this limitation is recorded in the feature progress document.
2026-09-10 22:19:30 +08:00
mesalogo d9114c20bf test: make workspace history fixtures independent of Git identity
Cross-platform packages / Validate source (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (push) Canceled after 0s
GoodBuddy Agent Release / Build darwin arm64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Build linux arm64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Build linux x64 compound package (push) Canceled after 0s
GoodBuddy Agent Release / Sign cumulative Agent catalog (push) Canceled after 0s
GoodBuddy Agent Release / Publish Agent to GitHub and OSS (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Write fixed commit objects from the fixture index rather than invoking
Git commit with an implicit machine identity. Preserve real history,
rename diff, pagination and dirty branch-switch checks without changing
Git author or committer configuration or skipping any affected test.

Add fixture checks for valid objects, parent linkage, unchanged dirty
content and unchanged repository configuration. Record the CI regression
introduced when the prior test identity override was removed.

Validation: workspace tests passed 11 with 2 filesystem skips; release
notes verification, typecheck and lint passed. Full tests passed 3845 with
66 skipped and one unrelated ActivityPanel timeout; all 16 ActivityPanel
tests passed on isolated rerun. Product source and approved bilingual
Desktop 0.12.11 / Agent 0.11.23 release notes are unchanged.
2026-09-10 19:49:33 +08:00
mesalogo 877cf581f6 chore: prepare Desktop 0.12.11 and Agent 0.11.23 releases
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Record the approved bilingual release notes, classify long-task limits and
premature truncation as fixes, and align both feature matrices with the
verified workspace, browser, scheduling and Runtime behavior.

Advance the Agent lock to 0.11.23 for remote file and Git management, optional
model limits, larger bridge exchanges and the unlimited-duration connection
fix. Agent publication reads version-specific approved notes and remains
separate from Desktop Latest. Upgrade Desktop to 0.12.11 before updating Hosts.
No LoongArch preview is included.

Validation: release notes verification, all 3845 tests (66 skipped), typecheck
and lint passed. Current-source Linux x64 workspace and bridge checks passed;
managed ACP Ask and Execute completed two real model requests, both HTTP 200,
with delivered results and cleaned test resources. Native production packages
and publication remain subject to GitHub Actions after candidate approval.

Release note: 准备 Desktop 0.12.11 与 Agent 0.11.23 发布;远程文件和 Git 管理、长任务限制及连接超时修复需先升级桌面端,再更新 Host 上的 Agent。
2026-09-10 14:27:41 +08:00
mesalogo 0eb5205b1c fix: simplify workspace controls and branch search
Group the workspace view switch and refresh action in one header, show only
file or Git actions for the active view, and remove the isolated root icon.
Subdirectory breadcrumbs retain root navigation and restore keyboard focus.
Use the shared form style for branch search in a bounded, scrollable panel.

Confirm that fresh settings already default to automatic Execute tool
authorization, and add regression coverage without changing saved policies.
Update the workbar design and record isolated production Electron validation.

Validation: 354 focused tests, typecheck, lint and development bundle passed.
The full suite passed 3843 tests and skipped 66; an unchanged OpenCode stream
lifecycle timing test failed once and passed its isolated rerun.

Release note: 整理工作区文件与 Git 工具栏,移除孤立的根目录图标并保留子目录导航;分支搜索框、文字和按钮统一样式,宽窄工作栏下均保持紧凑布局。
2026-09-10 13:54:34 +08:00
mesalogo c037d7fb20 fix: complete runtime and workspace workflows
Restore Ask workspace reads and output paging without granting write access,
and let programming Subagents reuse their parent's browser page. Allocate
browser resources only when used, keep Agent pages aligned with workbar tabs,
recover released bindings, and remove unused automatic screenshot work.

Preserve inherited Runtime selections during remote usage persistence and
manual compression. Keep viable channel model fallbacks, display the actual
scheduled-task mode and Harness model source, and avoid rediscovery delays
when calling healthy MCP tools. Fix directory/history refresh, case-only
renames, rename-aware commit diffs, and FastGPT probe success reporting.

Add regression coverage and align feature documentation. The Agent-side
connection timeout fix requires deploying an updated Agent; existing Ask
read-only and Execute authorization boundaries remain unchanged.

Final validation: 3839 tests passed, 66 skipped; typecheck, lint, development
build, and diff checks passed. Isolated Electron UI/MCP workflows passed with
a loopback model fixture. Current-source Linux x64 Host checks passed with
three successful real model requests. Chromium loadURL rejection was not
reproduced in the UI; released-binding recovery has automated coverage.

Release note: 修复 Ask 读取项目文件、浏览器页面显示与恢复、文件管理及模型配置跟随中的不一致,并减少无用截图和 MCP 等待。定时任务显示实际工作模式,远程会话可正常保存继承 Runtime 的用量;远端连接超时修复需更新 Agent 后生效。
2026-09-10 12:48:30 +08:00
mesalogo 020a9ad0dc fix: retain conversation retrieval settings for scheduled messages
Persist the conversation knowledge retrieval mode across restarts so timed messages use the saved setting. Clarify shared conversation authorization in the scheduled-task documentation and correct the renderer test query type.

Validation: 374 focused tests passed; typecheck and lint passed. The full suite had two failures that passed on focused reruns. Real remote validation remains blocked by the temporary harness SDK module loading error; no model requests were made.

Release note: 修复会话知识检索模式在重启后丢失的问题,定时消息继续使用会话保存的检索设置,并同步任务授权说明。
2026-09-10 10:36:26 +08:00
mesalogo 444902d8ad fix: preserve conversation settings for models and scheduled messages
Preserve explicit Runtime model selections and Harness configuration rules without pinning resolved defaults. Expose fixed Runtime models in project settings and keep SSH conversations inheriting their project selection.

Dispatch scheduled instructions through ordinary conversation requests using current history, Runtime, work mode, and knowledge scope. Persist conversation work modes and update scheduling documentation and regression tests.

Validation checkpoint: typecheck, lint, and 104 focused tests passed. The SSH inheritance regression passed in isolation, but its process timed out on exit. Full-suite completion, remaining model documentation review, and real end-to-end validation are still pending.

Release note: Preserve fixed model choices and let scheduled messages use their conversation's current settings and history.
2026-09-10 10:17:45 +08:00
mesalogo fedafa21bd fix: pin workbar tab order and preserve browser titles 2026-09-10 08:46:17 +08:00
mesalogo c1848a9dc4 test: align runtime fixtures across platforms
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Account for Electron-as-Node when probing the current executable and update the Linux workspace registry scenario to validate the writable handle behavior shipped with remote file management.
2026-09-10 07:01:48 +08:00
mesalogo 21ac8330ea fix: preserve runtime configuration references
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Projects, channels, conversations, and Agent Runtimes now retain dynamic default references while explicit model selections remain pinned. Compatible fallback resolution stays aligned across settings validation, runtime creation, labels, context metrics, DeepSeek Harness browser routing, and remote project saves.

Prepare the bilingual Desktop 0.12.11 release metadata and keep Agent 0.11.22 as the published remote baseline.

Release note: 修复默认模型与 Runtime 配置变化后项目、通道和对话选择失去跟随关系的问题;显式选择仍保持固定,新请求使用当前兼容配置。
2026-09-10 06:27:48 +08:00
mesalogo b01a856bae fix: remove avoidable model runtime limits
Preserve long histories and parsed attachments, expose paged process and Subagent output, isolate MCP discovery failures, and expand browser snapshots and knowledge result selection. Remove fixed Harness output and updated remote Runtime duration limits.

Keep released Agent profile and v1 bridge compatibility while enabling larger framed exchanges and optional limits for updated peers. Document remote coverage requirements and real-model boundary validation.

Release note: Long model tasks can retain more context and continue reading complete tool output; remote execution preserves compatibility with released Agents. Updated remote limits require an updated Agent and Runtime bundle.
2026-09-10 03:01:21 +08:00
mesalogo 80b0b18712 feat: add persistent workbar browser tabs and scoped MCP routing 2026-09-10 01:26:56 +08:00
mesalogo 428b4c0d08 fix: synchronize scheduled task status and run actions
Reflect queued and active schedule runs in task status, refresh tasks on queue changes, and prevent duplicate Run Now actions until the current run finishes. Add lifecycle regression coverage and update scheduled task behavior documentation.

This file-level commit also includes concurrent conversation history, browser workbar, and runtime selection changes in the affected files. Focused schedule regressions passed; full workspace validation has unrelated failures as recorded during development.

Release note: Scheduled tasks now refresh their queued and running status and keep Run Now disabled while an occurrence is active.
2026-09-10 01:24:51 +08:00
mesalogo ec100f8176 feat: improve direct model workspace tools
Direct models now use bundled ripgrep for workspace discovery, read files through resumable pages, and edit multiple files with apply_patch. Ask can search and read while Execute retains write access, and fixed total input limits no longer constrain these tools.

Release note: 直连模型现可使用随包 ripgrep 搜索项目、分页读取任意大小的文本,并通过多文件补丁修改代码;设置中的相关能力统一显示为工作区文件和开发工具。
2026-09-10 00:45:03 +08:00
mesalogo c1f8ba80ee test: validate external knowledge provider contracts
Add read-only Dify, FastGPT, and RAGFlow probes, a redacted live capability baseline, and focused adapter tests. Document version-specific API and UI behavior, including Dify 0.x/1.x differences and RAGFlow GraphRAG evidence.
2026-09-10 00:33:09 +08:00
mesalogo f3b695214b fix: make workbar browser fullscreen 2026-09-09 21:15:28 +08:00
mesalogo abd3d5c6bb fix: keep workbar tab close background transparent 2026-09-09 20:01:03 +08:00
mesalogo f669e490b8 feat: add workspace file management and embedded browsing
Users previously had to leave the workbar to manage project files and inspect Git branches or commit history. Add file and directory creation, rename, move, delete and properties, plus searchable branches, branch creation and switching, explicit fetch, and commit file diffs.

Keep file previews in the right workspace pane with directory expansion, selection, scroll and focus preserved on return. Add compact action menus, directory pickers, preview refresh and source switching. Embed the shared browser session directly in the workbar with navigation, resizing and fullscreen controls instead of a separate interaction window.

Moves stay within the current workspace, deletion is permanent after confirmation, and Git switch reports conflicts without discarding or stashing changes. Typecheck, lint and staged whitespace checks pass; existing full-suite failures and the blocked real remote directory validation remain recorded in the workbar progress document.

Release note: 可在右侧工作区管理文件、切换 Git 分支并查看提交历史,返回预览后保留浏览位置;浏览器支持在工作栏内直接交互和全屏显示。
2026-09-09 15:47:24 +08:00
mesalogo b6d4633a33 docs: design ShareServer Office editing 2026-09-09 12:43:00 +08:00
mesalogo 5782667a40 fix: accept equivalent remote runtime lock formatting 2026-09-09 11:29:37 +08:00
398 changed files with 44014 additions and 6284 deletions
+2 -8
View File
@@ -532,14 +532,8 @@ jobs:
set -euo pipefail
tag="agent-v$AGENT_VERSION"
title="GoodBuddy Agent $AGENT_VERSION"
notes="$RUNNER_TEMP/agent-release-notes.md"
cat > "$notes" <<EOF
GoodBuddy Agent $AGENT_VERSION
此版本供 GoodBuddy 的“设置 > 平台功能 > 远程项目”手动下载。软件包包含 Agent、固定 Node 和 GoodBuddy 适配的远端 OpenCode Runtime。
This release is downloaded manually from GoodBuddy Settings > Platform Features > Remote Projects. Each package contains the Agent, pinned Node, and the GoodBuddy-adapted remote OpenCode Runtime.
EOF
notes="resources/agent-release-notes/$AGENT_VERSION.md"
test -s "$notes"
if gh release view "$tag" >/dev/null 2>&1; then
is_draft="$(gh release view "$tag" --json isDraft --jq .isDraft)"
if [[ "$is_draft" == true ]]; then
+24
View File
@@ -99,6 +99,14 @@ additional hardening beyond the actual internal-network threat model:
to that account. Do not add T2/T3 trust tiers, separate consent checklists,
per-tool approvals, or a second "controlled execution" concept.
- Preserve cancellation, timeout, bounded-output, and shutdown behavior.
- When fixing Runtime behavior or limits, also inspect the remote Runtime path
through GoodBuddy Agent (`gbagent`). Verify whether local and remote execution
actually share the affected control logic; do not assume that a desktop fix
covers the Agent, bridge, launcher, or bundled Runtime. Fix any affected
remote-specific implementation and validate it under the GoodBuddy Agent
development validation rules below. If no separate remote fix is needed,
record which shared implementation covers it or why the remote path is
unaffected.
- Keep model/provider credentials in Main, but do not otherwise reduce Execute
permissions with extra product policy gates. Keep child-process cleanup
straightforward and reliable.
@@ -112,6 +120,11 @@ additional hardening beyond the actual internal-network threat model:
## Data and Compatibility
- Before changing released database schemas or large-data migrations, read
[`docs/development/database-migrations.md`](./docs/development/database-migrations.md).
Keep feature-specific storage rules and validation evidence in the owning
feature documents.
- Preserve released user-owned data, migrations required by released data, and
supported external contracts.
- Compatibility does not apply to unshipped, branch-local, or safely disposable
@@ -140,6 +153,13 @@ additional hardening beyond the actual internal-network threat model:
repository mechanisms instead of inventing extra gates, ceremonies, or
disabled states.
- Add or update focused tests for behavioral changes and regressions.
- Read [`docs/quality/recurring-defects.md`](./docs/quality/recurring-defects.md)
before changing overlays, store lookups, retrieval entry points, cancellation
behavior, or render-time derived values. It records defect types that have
already recurred in this repository and the shared mechanism that covers each
one. When fixing an instance of a listed type, extend that shared mechanism
instead of patching the one reported case, and add an entry when a new defect
type recurs.
- After completing any functional change, inspect the affected product,
architecture, design, feature, setup, and operational documentation and
update every relevant document to match the implemented behavior. Treat the
@@ -319,6 +339,10 @@ validation are not substitutes for this development-time check.
- Keep all artifacts in GoodBuddy-owned test directories. Before stopping or
changing a remote process, verify that it belongs to the test run; never
modify unrelated Host files or processes.
- During remote testing, prefer dedicated test directories under `/root/tmp`
for temporary files. Avoid creating files under `/tmp` where possible, since
it may be memory-backed and consume additional RAM. Set `TMPDIR` to the
dedicated test directory when needed so test tools use it as well.
- If neither route is reachable, report the real-Host validation as blocked and
do not present the Agent work as complete or defer its first real validation
to release time.
+4
View File
@@ -31,6 +31,10 @@ npm start
## 质量验证
跨功能测试方法见[质量验证索引](./docs/quality/README.md)。桌面界面的测试分层、
原生键盘、截图与计算样式取证、隔离启动和脱敏报告遵循
[Electron UI 自动化测试规范](./docs/quality/electron-ui-automation.md)。
提交或打包前运行:
```bash
+207 -28
View File
@@ -8,8 +8,11 @@ otherwise.
## Status
- [x] Available
- [ ] In development or planned, as described by the item
- [x] Completed
- [ ] To implement
Status describes implementation, not publication or test coverage. Validation
records are listed separately and do not introduce another feature status.
## Feature Matrix
@@ -21,7 +24,7 @@ otherwise.
packages target compatible x64 and arm64 environments, including UOS, Kylin,
Hygon, Zhaoxin, Kunpeng, and Phytium systems. This is not vendor certification.
LoongArch has a separate experimental loong64 preview outside standard
releases and automatic updates; no preview is planned for 0.12.10. See the
releases and automatic updates; no preview is planned for 0.13.6. See the
[preview boundaries](./docs/development/loongarch-preview-build.md).
- [x] **Configurable global shortcut**: Enable, disable, or record an Electron
accelerator under Platform Features / General. The default remains
@@ -36,12 +39,50 @@ otherwise.
local, managed SSH, and remote messaging-channel projects. Managed SSH
projects are grouped by Host, with the real Agent connection state on the
Host heading and only the remote path on each project row.
- [x] **Cross-project conversation activity**: A shared activity entry lists
running conversations and conversations needing attention across projects,
including older conversations with live work. Anchored project-to-conversation
menus provide keyboard navigation and exact conversation opening without a
full activity dialog. The summary hides when idle, long conversation submenus
scroll independently, and project selection continues to show activity counts.
- [x] **Compact conversation controls**: Composer options share a compact
settings panel while Runtime, mode, sending, and queue controls remain
available. Conversation search has an inline clear action that restores the
scoped list and returns focus to the input.
- [x] **On-demand conversation history**: Lists load lightweight summaries
rather than every conversation's process metadata. Opened and active
conversations retain full details; search, copy, export, continued requests,
and remote pending questions remain available. Unchanged image reconciliation
does not resend saved messages, and idle acknowledged details follow the
existing view-cache lifetime. See the
[history loading and retention rules](./docs/features/assistant-workbar/execution-history-storage.md#会话列表读取与前端保留).
- [x] **File, screenshot, window, and clipboard context**: Added to model
context only after explicit user selection.
context only after explicit user selection. Paste one or more supported local
files into the chat composer with `Ctrl+V` to add attachments, using the same
parsing progress and limits as the attachment button. Text and screenshot
paste remain available. See [attachment rules](./docs/features/document-processing/prd.md#321-聊天附件入口).
- [x] **Text recognition in image-based PPTX files**: With local OCR configured
and an OCR-enabled parsing workflow, embedded PNG, JPEG, and WebP images are
recognized alongside native text, retaining slide locations. OCR releases
model memory after 60 seconds without active or queued work and reloads on
demand; fast text/index modes still skip OCR. See the
[parsing behavior and real-file evidence](./docs/features/document-processing/chat-attachments-technical-design.md).
- [x] **Per-file workspace diffs**: Changed files expose separate staged and
unstaged diffs, including deleted, renamed, and untracked files. Refresh
reloads the selected diff and expanded directories; more than 50 changes
reloads the selected diff, browsed directory, and expanded directories; more than 50 changes
remain accessible through incremental loading.
- [x] **Workspace file and Git management**: Create, rename, move, and delete
files or directories, inspect properties, search and switch local or remote
branches, create branches, explicitly Fetch, and browse commit history and
per-file commit diffs. File and Git controls are grouped by view, branch
search uses the shared compact form style, and subdirectories retain text
breadcrumbs without an isolated root icon. Commit history stays docked at
the bottom, initially expands to half the Git content area, and supports
resizing with independently scrolling panes and one shared refresh action.
Project switches automatically detect Git without reusing another project's
status. Moves stay inside the workspace,
deletion requires confirmation, and branch conflicts never trigger automatic
stash or discard. Managed SSH operations require Agent `0.11.23`.
- [x] **Rich responses**: Supports GitHub Flavored Markdown, LaTeX math,
constrained Mermaid diagrams, and static in-conversation HTML previews.
Complete HTML and HTML code blocks can be previewed in place after an Agent
@@ -54,7 +95,11 @@ otherwise.
citation metadata. Full-conversation copy uses the same validated
Preload/Main clipboard path.
- [x] **Assistant workbar, multiple terminals, and resizable layouts**: The
right workbar uses a persistent “+” capability catalog and application tabs.
toggle sits beside the theme switch. The right workbar uses a persistent “+”
capability catalog and application tabs.
Task Center and Workspace are permanent singleton tabs; browser tabs are
independent instances. Task Center can show the current project, global
tasks, or all projects, and retains the selected scope with the layout.
Users can open multiple independent terminals for the current local or
managed SSH project with bounded output, resizing, termination, and explicit
reconnection. Closing a terminal tab ends its Shell; restarting the app
@@ -77,14 +122,54 @@ otherwise.
attachments and knowledge remain in the conversation composer, while memory,
when implemented, and historical execution context belong to the associated Task. See
the [Feature PRD](./docs/features/assistant-workbar/prd.md).
- [ ] **ShareServer Office co-editing** (design): Uses an optional ShareServer
integration with ONLYOFFICE Docs to open multiple filename-labelled document
tabs in the assistant workbar. The planned scope covers DOCX, XLSX, and PPTX
manual editing, save and undo, source-file conflict protection, and later AI
selection edits. The selected ShareServer and editor process document content;
Desktop neither bundles nor starts Document Server and does not claim offline
editing when no service is configured. See the
[design](./docs/features/office-document-editing/README.md).
### Agent Runtimes and model connections
- [x] **Compact subagent history without lost details**: Local and managed SSH
subagent events store changes instead of repeated full progress snapshots.
The first upgrade automatically converts existing history and reclaims disk
space, with visible progress and quit/retry support. Chats, execution details,
results, and remote event deduplication are preserved; older clients cannot
reopen the upgraded database. The schema-35 repair released in 0.13.2 also handles
repeated tool blocks written after upgrading to 0.13.1, without deleting events.
Terminal tasks release their write caches; duplicate remote replay does not
rebuild a released cache. See
[storage and upgrade behavior](./docs/features/assistant-workbar/execution-history-storage.md).
- [x] **Direct model Runtime**: Supports question answering, knowledge
synthesis, controlled tool execution, image generation, and reference-image
editing through providers supporting the OpenAI-compatible image-editing
endpoint. Editing sends attached images as multipart data and still requires
validated inline image output rather than fetching provider-returned URLs.
Follow-up image requests reuse the latest successful image and conversation
text, including after reopening a saved conversation; explicit attachments
take priority. Missing editing support does not block text-based generation:
the affected reply shows a quiet footer explaining that reference images were
not used. See [image generation](./docs/features/image-generation/README.md).
- [x] **Image tools in the current conversation**:
Enable conversation access for an image model to let tool-capable chat models
generate images and edit uploads or earlier artifacts in Execute, without a
separate conversation or manually switching to an image model. Direct text
models, local OpenCode, Continue, DeepSeek Harness, and managed remote OpenCode
use the shared service; Runtime assignment is derived from model settings.
Requests may incur provider charges and editing requires provider support.
See the [implementation and validation records](./docs/features/conversation-media-generation/progress.md).
- [x] **Cross-project Runtime process reuse**: Compatible local OpenCode and
DeepSeek Harness configurations share heavyweight processes; managed OpenCode
sessions share a process on the same Host. Project adapters and session
workspaces, models, tools, questions, and cancellation stay independently
routed. Remote pending questions and task status recover after reconnect or
Desktop restart without resending accepted prompts. Requires Desktop `0.13.5`
and Agent `0.13.0` together for the new remote behavior. See the
[design and measured validation](./docs/features/assistant-workbar/runtime-process-reuse-technical-design.md).
- [x] **Direct model programming agent**: Local direct text models can run the
platform Shell in Execute mode and delegate one level of programming
Subagent work while inheriting the parent request's mode, model, workspace,
@@ -105,7 +190,7 @@ otherwise.
credential without starting a throwaway server; the explicit settings test,
native inventory still performs full startup and health checks, then shares
that execution Runtime with the first real request instead of cold-starting
another server. Execution Runtimes remain reusable per project, while local
another server. Execution Runtime adapters remain reusable per project, while local
OpenCode configuration dependencies and content-addressed Skill snapshots are
shared globally across projects. Session data, tool output, and request-scoped
MCP state remain isolated. GoodBuddy-managed local OpenCode keeps
@@ -179,11 +264,17 @@ otherwise.
failure, Agent `SIGKILL`/restart, and recovery from a reopened Desktop SQLite
database. Successful tool START/END events appear exactly once, with no
Prompt, provider, or tool replay observed. The current Agent source lock is
`0.11.22`, while the current Desktop release candidate is `0.12.10`; formal
`0.13.0`, while the current Desktop release candidate is `0.13.5`; formal
publication status follows the separate Agent and Desktop
release channels. Current macOS source has passed native package installation,
release channels. Previous macOS validation covered native package installation,
detached lifecycle, Attach, real Ask/Execute, and cancellation of tools in
separate process groups on a real Host; this does not imply publication.
Agent `0.11.23` adds remote workspace management and optional model limits;
its bundled Runtime no longer imposes a fixed ten-minute Prompt deadline.
Unlimited request duration retains a separate connection timeout. Agent
`0.11.23` requires Desktop `0.12.11` or later.
Agent `0.11.24` requires Desktop `0.13.0` and fixes native question routing
and sending another message after cancelling a pending question.
- [x] **Manual SSH Host environment provisioning source path**: After Host Key,
authentication, and system probes succeed, GoodBuddy saves the Host and
read-only probes the shared Agent/Runtime. Saving a Host or opening a project
@@ -209,7 +300,7 @@ otherwise.
demand and run a fixed `attach-or-bootstrap`; registered health,
capabilities, and prompt startup do not scan the full payload. See the
[design](./docs/features/remote-host/environment-provisioning-technical-design.md).
- [ ] **Real-Host acceptance for SSH Host environment provisioning**: The
- **SSH Host environment provisioning validation records**: The
current source passed isolated Linux x64 package installation, Ask/Execute,
native subagent external writes, reconnection, and stop/bootstrap.
The complete Host-card acquisition matrix across GitHub, Beijing mirror,
@@ -219,14 +310,17 @@ otherwise.
- [x] **DeepSeek Harness (preview)**: Uses the fixed GoodBuddy Host and an
OpenAI-compatible model connection. It prefers an administrator-provided
connection, otherwise follows the compatible default model or first
compatible connection without requiring a duplicate selection. Ask permits
compatible connection without requiring a duplicate selection. Settings
displays the actual administrator or fallback model source. Ask permits
only real Host-registered `read` and `skill` tools plus Main-managed Web
Search/Fetch proxies, and rejects plugin impersonation of those names.
Execute allows all enabled built-in and plugin tools with the current user's
permissions. Image input follows the selected model connection's declared
capability: text models reject images before Host or model invocation, while
image-capable models receive bounded inline JPEG/PNG content through a
temporary Attachment Store.
temporary Attachment Store. On Windows, Host startup and ACP sessions share
one canonical workspace path, so equivalent path spellings reuse the same
working Runtime instead of failing session creation.
- [x] **DSH npm plugin marketplace**: Disabled by default and searches public
npm `dsh-plugin` packages only after explicit user enablement. It uses the
bundled npm to install exact versions with ordinary lifecycle scripts and
@@ -239,9 +333,29 @@ otherwise.
user's authorization for all tools, processes, network access, and writable
paths available to the current local or SSH account, including paths outside
the workspace and native subagent work.
- [x] **Efficient direct-model workspace tools**: Direct models use bundled
ripgrep for compact file discovery and content search, read large UTF-8 files
by line, and apply multi-file patches in Execute. Ask can search and read but
cannot patch or run commands; packaged ripgrep does not require a system install.
Actionable read/search errors let the model correct its arguments and
continue. Partial search results remain available with an explicit
incomplete-coverage warning.
- [x] **Long context and paged tool output**: Message history, long replies,
and parsed document text no longer use the previous fixed truncation limits.
Direct models can continue reading stored command and Subagent output in
pages; the selected model's context window and bounded transports still apply.
- [x] **Native Runtime interaction routing**: OpenCode and Continue questions
support choices, yes/no, free-text answers, and skipping through the existing
question card. OpenCode also routes questions from owned child sessions.
question card. Local and managed SSH OpenCode also route questions from
owned child sessions. Managed SSH requires Desktop `0.13.0`, Agent `0.11.24`,
and a newly started managed Runtime;
this does not extend question support to arbitrary ACP services.
Successful answers and skips retain the original questions and answers
across rounds and local conversation reloads; answers discarded by older
versions cannot be recovered.
Concurrent questions wait in order, duplicate events preserve drafts, and
failed submissions remain retryable. Cancelling a pending managed SSH
question allows another message in the same conversation.
Execute permission confirmations are handled automatically rather than
waiting for another approval. See the [interaction boundaries](./docs/features/assistant-workbar/runtime-interactions.md).
- [x] **Experts and Subagents**: Supports explicit experts, team analysis, and
@@ -254,14 +368,20 @@ otherwise.
text, reasoning, and tool progress separately from the final result. Remote
live progress requires Agent `0.11.20`; older packages still expose final
results without reconstructing missing progress.
- [x] **OpenCode event-stream cleanup**: Each completed or cancelled request
closes its own subscription without cancelling parallel conversations.
- [x] **OpenCode event-stream cleanup**: Chat requests and native context
compaction close their own subscriptions before ending event iteration,
including completion, failure, cancellation, and early consumer exit,
without cancelling parallel conversations.
- [x] **Accurate message-footer status**: Distinguishes request preparation,
retry waiting, retry dispatch, tool activity, pending answers, and terminal
states. Local OpenCode reports native retry attempts and scheduled times;
direct models report their own backoff phase. Footer dots are static, and
unsupported Runtime retry details are not invented. Conversation-list
indicators and send/stop behavior remain unchanged.
- [x] **Simplified Runtime selection**: Conversation and project menus no
longer enumerate every Runtime/model combination. Configure Runtime models
in system settings; saved fixed project selections remain usable and
explicitly display their fixed model.
- [x] **Role-bound model connections**: Each role can inherit the default model
or select an independent text-model connection. Invalid connections safely
fall back to the default; the synthesis role always inherits the default.
@@ -375,7 +495,7 @@ otherwise.
installation validation on Windows x64. Managed Python archive validation
follows the target filesystem: Linux preserves valid case-distinct paths,
while every target still rejects exact duplicates and unsafe entries.
- [ ] **Local tool-execution environment release acceptance**: All six
- **Local tool-execution environment validation records**: All six
platform/architecture OSS mirror objects have passed public byte, size, and
SHA-256 verification. Managed Python remains an on-demand download and does
not add license files to the Desktop package. Each standard package job uses
@@ -399,8 +519,10 @@ otherwise.
models, GoodBuddy-managed OpenCode, Continue Agent Execute, and DeepSeek
Harness, and loads only in Execute. Agent child processes receive only
per-request local-loopback authority; MCP addresses, commands, and
credentials remain in Main. Dynamic tools are still rediscovered and pass
through existing activity and permission boundaries.
credentials remain in Main. Dynamic tools pass through discovery and the
existing activity and permission boundaries; healthy direct-model
and Harness calls reuse the discovered request catalog instead of
rediscovering it before every call.
- [x] **MCP Prompts and Resource metadata**: MCP testing discovers bounded
Prompt, parameter, and Resource metadata only when the Server declares the
corresponding capability and does not read Resource content. Supported
@@ -413,14 +535,22 @@ otherwise.
retrieval parameters are collapsed by default. Documents distinguish ready,
processing, and failed states, with source opening, retry, and confirmation
before source removal.
- [ ] **External knowledge-base connections** (planned): Manage Dify, FastGPT,
and RAGFlow instances from the existing Knowledge page, then discover and
bind remote knowledge bases with provider-specific retrieval settings and
citations. External systems provide retrieval only: GoodBuddy does not use
- [x] **External knowledge-base connections**: Manage Dify, FastGPT,
and RAGFlow instances from the Knowledge page, then discover remote
knowledge bases or enter their IDs and verify bindings with provider-specific
retrieval settings and citations. Conversations support on-demand and
required pre-answer retrieval. External systems provide retrieval only:
GoodBuddy does not use
their App, Chat, Workflow, or Agent APIs, and does not bulk-sync, locally
index, or modify remote content. Bounded cited snippets are retained locally
with their conversations. See the
[external knowledge-base PRD](./docs/features/knowledge-base/external-knowledge-prd.md).
- **External knowledge-base validation records**: All three providers have passed
real service, local HTTP MCP, and production-IPC short-answer checks; Dify
additionally passed the complete App composer-to-answer-and-citation path.
FastGPT/RAGFlow desktop-specific parameters, complex multi-library answers,
and remote pre-answer retrieval remain unverified. See the
[validation evidence and remaining work](./docs/features/knowledge-base/progress.md).
- [x] **Knowledge graph**: Supports rule-based, model-based, and hybrid
extraction plus entity, relationship, alias, and evidence maintenance.
- [x] **Embedding configuration and retrieval**: Configures compatible
@@ -448,7 +578,8 @@ otherwise.
showing the previous item's source after a selection change. Failed reads
use application notifications, and missing sources have an explicit fallback. The left
navigation can show the incomplete-todo count, and create, save, and comment
results use application-wide notifications.
results use application-wide notifications. Agent/MCP note writes automatically
refresh the open workbench while preserving selection and unsaved drafts.
- [ ] **MCP Server Control Plane** (planned): Unified MCP lifecycle, health
checks, reconnection, schema cache, isolation, approval, and audit.
- [ ] **Traceable note excerpts and AI editing** (planned): Collects sourced
@@ -469,6 +600,12 @@ otherwise.
cache-reporting semantics when showing cache hit rate. Activity is grouped
by conversation and collapsed by default so long histories do not fill the
page.
- [x] **Compact conversation tool records**: Expand individual tool records
to read or copy results, errors, and input parameters. Conversation and
child-task progress use concise rows without repeated Runtime summaries.
OpenCode, Continue, DeepSeek Harness, and direct models show available file
paths, commands, or search summaries and retain them through completion and
conversation reloads.
- [x] **Task and custom-task experience**: Each product-level Task belongs to
one Conversation, while one Conversation can contain multiple Tasks. The
left conversation list exposes Task children through a leading expand
@@ -484,7 +621,10 @@ otherwise.
Center remains the complete index rather than creating a separate Automation
Center. Current schedules support one-time, daily, and weekly triggers;
advanced time zones, Cron, event triggers, and retry governance remain
incremental PRD work. See the
incremental PRD work. Scheduled messages use the conversation's current
history, Runtime, work mode, and saved knowledge retrieval settings; task
details show the actual mode used, and an active occurrence blocks duplicate
Run Now actions. See the
[Task Center PRD](./docs/features/task-and-job/task-center-prd.md) and
[Scheduled Task PRD](./docs/features/task-and-job/scheduled-task-prd.md).
- [x] **Memory and Smart Heartbeat**: Provides periodic review, suggested
@@ -527,6 +667,15 @@ otherwise.
path, with Back, Refresh/Stop Loading, address entry, Go, Interaction, and
Close actions. Stop Loading does not close the session, and user navigation
changes the page the Agent sees next.
Multiple tabs in one conversation share login state while keeping separate
pages and navigation. Each model request retains its starting tab binding;
unused request reservations do not create browser resources. Released tabs
can be reopened, explicit screenshots remain available, and ordinary
operations no longer trigger unused automatic screenshot capture.
Tabs can be closed while an AI request uses them without cancelling the
whole request. A later explicit navigation creates a separate replacement
tab; blank pages cannot reload, and switching or closing panels clears stale
action errors.
- [x] **Client-computer control tools**: Managed separately from the built-in
browser with scope, cancellation, timeout, output, and activity boundaries.
- [x] **Remote messaging-channel projects**: WeChat ClawBot, WeCom, and
@@ -560,6 +709,9 @@ otherwise.
(default) or the mirror. Manual checks, startup checks, and the download page
use the same choice and read only fixed trusted release indexes; GoodBuddy
does not automatically download or install updates.
- [x] **Window recovery controls**: Rendering errors show a localized Reload
action and renderer crashes offer a native recovery confirmation. Reload may
lose unsaved input. See [window recovery](./docs/development/window-recovery.md).
- [x] **In-app feedback**: About and Updates can submit problems, suggestions,
or experience feedback with an optional email and one screenshot.
Diagnostics are not uploaded by default; users can explicitly attach a
@@ -567,12 +719,39 @@ otherwise.
draft, diagnostic choice, and request ID. Conversations, Prompts,
credentials, file contents, paths, raw provider responses, and remote Agent
logs are never attached.
- [x] **Private-network compatibility mode**: Enabled by default and permits
in-app HTTP plus invalid, self-signed, or expired HTTPS certificates.
Disabling it restores strict address and certificate validation.
- [x] **Private-network compatibility**: Permits in-app HTTP plus invalid,
self-signed, or expired HTTPS certificates. Fixed platform services such as
feedback and WeChat credential or media endpoints retain strict certificate
validation.
### Open source, builds, and releases
- Current source candidates are Desktop `0.13.6` and Agent `0.13.0`, with
OpenCode pinned to `1.18.29`. Publication status follows the independent
Desktop and Agent release channels.
- Agent `0.13.0` adds remote image-tool integration and shared Runtime processes
with conversation recovery fixes. Its packages require Desktop `0.13.5`;
update Desktop first, then the Host environment. Node remains `24.19.0`.
- Validation records remain separate from implementation status. Current-source
storage validation covers real local and Linux x64 Host tool workloads,
concurrent projects/conversations, cancellation, and lossless database migration.
Runtime process reuse has real Windows full-App and Linux x64 Host evidence;
PPTX OCR and idle-memory release have real Windows file evidence. Image tools
have real provider generation/editing and Host transport evidence. Full
UI-driven natural-language invocation and local/remote switching are not
covered by those records. The candidate still requires main CI and
native release packaging. See the [Runtime evidence](./docs/features/assistant-workbar/progress.md)
and [image-tool evidence](./docs/features/conversation-media-generation/progress.md).
- Desktop 0.13.6 history fixes passed 4,301 tests with 67 skipped, type checking,
scoped lint, isolated production Electron validation, and three paired
synthetic-history comparisons. Development validation used three real text
calls, including two on the current-source Linux Agent with Session
continuation; it did not alter Agent protocols. Release preparation does not
repeat local production builds. The exact candidate still requires its
main-branch CI and native tag packaging. At the user's request, the local
release-candidate rerun was cancelled after release-note verification; CI is
the candidate validation authority. Agent 0.13.0 is not republished for this
Desktop update.
- [x] **0BSD open-source license**: Original code can be freely used, copied,
modified, distributed, and commercialized. Third-party components and
resources retain their own licenses.
@@ -603,7 +782,7 @@ otherwise.
artifact API with scoped, expiring, rate-limited, and revocable tokens.
- [ ] **GoodBuddy Team Hub** (planned): An optional service for organizations,
RBAC, project sharing, remote Agents, policy distribution, and tenant audit.
- [ ] **SSH Host and remote execution-space release acceptance**: Host CRUD,
- **SSH Host and remote execution-space validation records**: Host CRUD,
Host Key, encrypted credentials, Project UI, Workspace, OpenCode ACP v5,
Agent-owned Prompt/gateway/transcript, read-only Ask, full-account Execute,
cancellation, exact detached-Agent reconnection, and release-only
+44 -18
View File
@@ -6,43 +6,60 @@
## 状态说明
- [x] 已提供
- [ ] 开发中或规划中,具体以条目标注为准
- [x] 已完成
- [ ] 待实现
状态表示功能实现情况,不等同于版本已发布或全部测试已执行。验证记录单独列出,不作为第三种功能状态。
## 功能总表
### 桌面基础、工作空间与上下文
- [x] **跨平台桌面应用**:支持 Windows、macOS、Linux,以及 `x64`、`arm64` 发布目标。
- [x] **信创软硬件覆盖**:正式 Linux 安装包覆盖国产 `x64` 与 `arm64` 架构,可用于统信 UOS、银河麒麟及海光、兆芯、鲲鹏、飞腾等兼容环境。龙芯 LoongArch 另行提供 `loong64` 实验预览版,不属于正式发布矩阵,也未纳入自动更新;`0.12.10` 不构建该预览。这里的覆盖范围不等同于厂商或整机兼容认证,具体状态与限制见[龙芯预览版构建说明](./docs/development/loongarch-preview-build.md)。
- [x] **信创软硬件覆盖**:正式 Linux 安装包覆盖国产 `x64` 与 `arm64` 架构,可用于统信 UOS、银河麒麟及海光、兆芯、鲲鹏、飞腾等兼容环境。龙芯 LoongArch 另行提供 `loong64` 实验预览版,不属于正式发布矩阵,也未纳入自动更新;`0.13.6` 不构建该预览。这里的覆盖范围不等同于厂商或整机兼容认证,具体状态与限制见[龙芯预览版构建说明](./docs/development/loongarch-preview-build.md)。
- [x] **可配置全局快捷唤起**:在“平台功能 / 通用设置”中启停或录制 Electron accelerator;默认保留 `CommandOrControl+Shift+Space`,冲突或保存失败时继续使用上一组已注册快捷键,并显示可处理的状态。
- [x] **Projects、独立对话与会话分支**:按项目隔离上下文,管理会话、附件和 Git 工作区变更;本地会话可在稳定状态下复制当前聊天内容到独立分支,分支持续显示来源徽标且不复制 Task、队列或成果归属;项目选择器区分本地、托管 SSH 与远程消息通道项目,托管 SSH 项目按 Host 分组并在 Host 标题显示真实 Agent 连接状态,项目行只保留远端路径。
- [x] **文件、截图、窗口、剪贴板上下文**:用户明确选择后才加入模型上下文。
- [x] **工作区单文件差异**:变更文件可分别查看已暂存和未暂存差异,覆盖删除、重命名和未跟踪文件;刷新会重读当前差异和已展开目录,超过 50 项变更可继续加载。
- [x] **跨项目会话活动**:统一入口汇总各项目正在运行和需要处理的对话,保留仍有活动的较早会话。锚定的“项目 → 会话”菜单支持键盘导航和精确打开对话,不再使用整块活动弹窗;空闲时隐藏摘要,长会话子菜单独立滚动,项目选择器继续显示活动数量。
- [x] **紧凑会话控件**:输入选项集中在紧凑设置面板,Runtime、工作模式、发送和队列操作保持可用;会话搜索提供内嵌清除按钮,清除后恢复当前范围列表并把焦点返回输入框。
- [x] **会话历史按需加载**:列表只加载轻量摘要,不再一次读入全部会话的过程元数据;已打开和活动会话保留完整详情,搜索、复制、导出、续聊和远程待答问题保持可用。图片合并不会重新发送未变化消息,已确认保存的空闲详情随现有视图缓存释放。详见[历史读取与保留规则](./docs/features/assistant-workbar/execution-history-storage.md#会话列表读取与前端保留)。
- [x] **文件、截图、窗口、剪贴板上下文**:用户明确选择后才加入模型上下文。聊天输入框支持 `Ctrl+V` 粘贴一个或多个本地支持文件并自动添加为附件,沿用附件按钮的解析进度与限制;普通文本和截图仍可直接粘贴。详见[聊天附件规则](./docs/features/document-processing/prd.md#321-聊天附件入口)。
- [x] **图片型 PPTX 文字识别**:配置本地 OCR 并选择启用 OCR 的解析方式后,可识别内嵌 PNG、JPEG 和 WebP 图片,保留原生文字与幻灯片定位。OCR 在没有活动或排队任务时空闲 60 秒自动释放模型内存,下次按需重载;快速文本/索引模式仍跳过 OCR。详见[解析行为与真实文件验证](./docs/features/document-processing/chat-attachments-technical-design.md)。
- [x] **工作区单文件差异**:变更文件可分别查看已暂存和未暂存差异,覆盖删除、重命名和未跟踪文件;刷新会重读当前差异、当前浏览目录和已展开目录,超过 50 项变更可继续加载。
- [x] **工作区文件与 Git 管理**:支持文件和目录的新建、重命名、移动、删除、属性查看,以及本地/远程分支搜索与切换、新建分支、显式 Fetch、提交历史和单文件提交差异。工具按文件/Git 视图分组,分支搜索使用统一紧凑表单,子目录保留文字导航而根目录不再显示孤立图标。提交历史贴底,首次展开占 Git 内容区一半,支持调整比例、独立滚动和统一刷新;切换项目自动检测 Git,不沿用其他项目状态。移动限于工作区内,删除须确认,分支冲突不自动 stash 或丢弃改动;托管 SSH 操作需要 Agent `0.11.23`。
- [x] **富文本回答**:支持 GitHub Flavored Markdown、LaTeX 数学公式、受控 Mermaid 图表和会话内 HTML 静态预览。Agent 完成回复后,完整 HTML 与 HTML 代码块可在原位置预览,并通过图标按钮查看源码或打开全屏预览;该能力默认开启,可在“平台功能 / 通用设置”关闭,预览不执行脚本、联网、表单提交或窗口操作。
- [x] **AI 回复与完整会话复制**:已完成的 AI 回复可从消息底部复制不含推理、工具日志和引用元数据的 Markdown 原文;完整会话复制复用同一条经过 Preload/Main 校验的剪贴板路径。
- [x] **助手工作栏、多终端与可调布局**:右侧工作栏使用持久“+”能力目录和应用 Tab;用户可为当前本机或托管 SSH 项目打开多个独立终端,获得有界输出、调整尺寸、结束与显式重连。关闭终端 Tab 会结束 Shell,应用重启只恢复已结束的 Tab 描述,不自动重启 Shell。主侧栏和魔法笔记列表支持鼠标与键盘调宽,魔法笔记列表可隐藏并记住布局,窄宽度下自动堆叠。
- [x] **助手工作栏、多终端与可调布局**:工作栏开关位于明暗主题按钮旁;右侧工作栏使用持久“+”能力目录和应用 Tab,任务中心与工作区为不可关闭的单实例,浏览器可打开多个独立实例。任务中心可切换当前项目、全局任务和全部项目,范围随布局保存。用户可为当前本机或托管 SSH 项目打开多个独立终端,获得有界输出、调整尺寸、结束与显式重连。关闭终端 Tab 会结束 Shell,应用重启只恢复已结束的 Tab 描述,不自动重启 Shell。主侧栏和魔法笔记列表支持鼠标与键盘调宽,魔法笔记列表可隐藏并记住布局,窄宽度下自动堆叠。
- [ ] **项目 Agent Space**(规划中):在 Project 中统一角色、知识、Skills/MCP、模型、审批策略、预算和超时,并支持模板复用。
- [ ] **应用导航与系统工具快捷入口**(仅设计):规划固定系统入口、内置应用显隐和打开现有工作栏的底部工具按钮;尚未实现新界面。详见[设计文档](./docs/features/application-tool-navigation/README.md)。
- [ ] **通用助手工作栏与执行空间后续能力**(规划中):在现有工作栏和多终端基础上继续加入监督、统一 Runtime、受管进程、静态安全 HTML 预览、可固定目标的工作区/浏览器/成果实例、底部停靠与独立窗口。Task Center 保持 Task 的单例索引,附件与知识库继续由会话输入区管理,未来的记忆与历史执行上下文归入关联 Task。详见 [Feature PRD](./docs/features/assistant-workbar/prd.md)。
- [ ] **ShareServer Office 协同编辑**(设计中):通过可选 ShareServer 集成 ONLYOFFICE Docs,在助手工作栏中以文件名打开多个文档 Tab,支持 DOCX、XLSX、PPTX 的人工编辑、保存、撤销、源文件冲突保护和后续 AI 选区修改。Office 正文会由所选 ShareServer 和编辑引擎处理;Desktop 不内置或启动 Document Server,未配置服务时不宣称离线编辑。详见[设计文档](./docs/features/office-document-editing/README.md)。
### Agent Runtime 与模型连接
- [x] **直连模型 Runtime**:支持问答、知识总结、受控工具执行、图像生成,以及通过支持 OpenAI 兼容图片编辑接口的服务商进行参考图编辑。编辑使用 multipart 上传附件图片,结果仍只接受经过校验的内联图片,不下载服务商返回的图片 URL。
- [x] **子任务历史去重与空间回收**:本机与托管 SSH 子任务事件只存变化,不再反复保存完整进度。首次升级自动转换旧记录并回收空间,显示进度且支持退出重试;保留聊天、执行详情、结果和远程事件去重。已随 0.13.2 发布的 schema 35 补修还会整理升级到 0.13.1 后重复写入的工具块,不删除事件;终态及时释放写入缓存,远程重复重放不重建已释放的缓存。旧客户端不能打开升级后的数据库,具体规则见[执行记录存储与升级回收](./docs/features/assistant-workbar/execution-history-storage.md)。
- [x] **直连模型 Runtime**:支持问答、知识总结、受控工具执行、图像生成,以及通过支持 OpenAI 兼容图片编辑接口的服务商进行参考图编辑。连续请求自动复用最近一次成功生成的图片和文字历史,已保存会话重开后仍可继续修改,本轮显式附图优先;上游明确不支持编辑时继续按文字生成,仅在回复底部提示未使用参考图。编辑使用 multipart 上传图片,结果仍只接受经过校验的内联图片,不下载服务商返回的图片 URL。详见[图片生成与连续修改](./docs/features/image-generation/README.md)。
- [x] **当前会话图片工具**:为图片模型开启会话调用后,支持工具调用的聊天模型可在 Execute 中生成图片、修改上传图片或历史成果,无需另开会话或手动切换图片模型。直连文本模型、本地 OpenCode、Continue、DeepSeek Harness 和托管远程 OpenCode 复用统一服务,Runtime 分配由模型设置派生;图片请求可能产生费用,编辑需服务商支持。详见[实现与验证记录](./docs/features/conversation-media-generation/progress.md)。
- [x] **跨项目 Runtime 进程复用**:兼容配置的本地 OpenCode 和 DeepSeek Harness 共享重型进程,托管 OpenCode 会话在同一 Host 上复用进程。项目适配器及会话的工作区、模型、工具、问答和取消仍独立路由;远程待答问题和任务状态可在重连或桌面重启后恢复,不重发已接受请求。新的远程行为需要 Desktop `0.13.5` 与 Agent `0.13.0` 配套更新,详见[设计与实测](./docs/features/assistant-workbar/runtime-process-reuse-technical-design.md)。
- [x] **直连模型编程 Agent**:本机直连文本模型可在 Execute 模式运行平台 Shell,并可按父请求模式、模型、工作区和能力范围委派一层编程 Subagent;OpenCode、Continue、DeepSeek Harness 和托管 SSH 不重复注入这两个工具。Windows 本机命令与真实模型“修改、测试、修复、复核”闭环已通过,macOS 与 Linux 真机命令仍待对应平台验收。
- [x] **高效直连模型工作区工具**:直连模型使用随包 ripgrep 紧凑地发现文件和搜索内容,按行分页读取大型 UTF-8 文件,并在 Execute 中应用多文件补丁。Ask 可以搜索和读取,但不能应用补丁或运行命令;用户无需另行安装 ripgrep。可处理的读取或搜索错误允许模型调整参数后继续,部分搜索结果保留并明确提示覆盖不完整。
- [x] **长上下文与工具输出分页**:历史消息、长回复和文档解析文本不再采用旧的固定截断限制。直连模型可分页续读已保存的命令和 Subagent 输出;所选模型的上下文窗口及传输边界仍然有效。
- [x] **OpenCode 与 Continue**:使用隔离子进程、环境变量白名单、统一配置、取消、启动与控制请求时限、有界流式输出和活动记录;共享进程回收逻辑保留 Windows 完整进程树终止,并对采用独立进程组的 POSIX 子进程执行组回收。聊天状态检查使用一次性探测 Runtime 并在返回后立即回收,不进入按模型与项目隔离的执行 Runtime 缓存;本机 GoodBuddy 管理的 OpenCode 在该被动检查中只确认所选路径和模型凭据,不启动用后即毁的 Server,设置中的显式连接测试和原生能力清单仍执行完整启动及健康检查,原生能力清单随后与首次实际请求复用同一个执行 Runtime,不再重复冷启动;执行 Runtime 仍按项目复用,但 OpenCode 配置依赖和按内容摘要生成的 Skill 快照在所有本机项目间全局复用,会话数据、工具输出及请求级 MCP 状态继续隔离。本机 GoodBuddy 管理的 OpenCode 只对同一会话内的请求保持顺序,不同会话即使属于同一项目也可并行,不同项目同样可并行;请求级动态 MCP 通过默认通配禁用和当前请求精确放行保持隔离。托管的本机和远端 OpenCode 跳过界面未使用的自动 Git 快照,避免同步差异计算拖住会话,文件工具、子代理与工作区 Git 差异保留;Continue 持续排空不用展示的宿主 stdout,防止控制台输出填满管道。这些调整随新启动的 Runtime 生效,不改写外部 OpenCode Server 配置。OpenCode 与 Continue 不再按固定工具调用数或活动数中止单次运行,已观察到的工具与 Subagent 活动全部保留在本地会话,不再在原生任务继续执行时丢弃后续详情。交互提问只由前台对话回答,定时任务、远程通道和委派等后台执行遇到提问时会立即失败并提示改为前台运行,避免无限等待。本机 OpenCode 启动时直接使用已选择的绝对路径,不预先检查文件或运行 `--version`,路径失效时由实际启动返回错误;托管 Linux ARM Host 激活 Runtime 时复用准备阶段已验证的 registry 和 manifest,不重新哈希或检查完整 OpenCode 二进制。
- [x] **托管 SSH OpenCode 闭环(技术预览)**:该能力由“设置 > 平台功能”中的独立“远程项目(技术预览)”页签控制并默认关闭;关闭时不影响本地项目、普通桌面功能或桌面发布。支持 Linux x64、Linux arm64 和 macOS arm64 Host,不支持 Intel Mac;通过 SSH 按需启动 detached Agent,不要求开机服务。Ask 在 Runtime 工具边界保持只读,Execute 使用所选 SSH 账号的完整权限。Agent 持有 Prompt、模型和工具轮次、Runtime 进程及语义 transcript,Desktop 断开后重新 Attach 原操作而不重发 Prompt;模型凭据仅在当前操作内存中使用,不写入 Renderer、SSH 参数、环境或磁盘。设置页按平台和架构显示包状态,用户显式下载、更新或导入/导出包含 Agent、固定 Node 和 OpenCode 的签名 `.gbagent`;三个目标共用一个签名 catalog,继续校验签名和 SHA-256。首次发布混合 catalog 前需先升级 Desktop,旧版 Linux-only 目录读取器无法解析包含 Darwin 的目录。macOS 原生安装、生命周期、Attach、真实 Ask/Execute 和独立进程组工具取消已在真实 Host 验证;这不等于新版本已经发布。详细运行边界和 Linux 历史回归证据见[远程主机技术设计](docs/features/remote-host/technical-design.md),正式发布状态以 Agent 与 Desktop 的独立发布渠道为准。
- [x] **SSH Host 手动环境准备源码链路**:Host Key、认证和系统探针成功后先保存 Host,并只读探测共享 Agent/Runtime;保存 Host、打开项目都不自动安装。Host 卡片只有一个按版本事实显示“安装远程环境”“更新远程环境”或“重新安装”的主按钮,次级 SegmentedControl 选择默认且不持久化的“自动”、Host 下载或 GoodBuddy 传输;“版本匹配”badge 不等同环境健康。自动模式只在 operation/prepare 前探测并择一,显式选择保持有效,任何 prepare、commit 或 adoption 失败都不跨 acquisition 自动 fallback。两种方式把同一签名 compound `.gbagent` 交付到固定 staging 后,共用 control-plane prepare、commit、Agent activate/health、Runtime activate、finalize 与显式 cleanup。GoodBuddy 路径可在同一次操作下载并验证缺失候选、缓存并取得 lease,再有界流式 SFTP 上传一个归档和其中已验证的 bootstrap Node,不把约 294 MiB 整包读入 Main `Buffer`;Host 在解包时完成一次完整 payload 校验。未完成操作只记录暂存 cleanup 所需的 operation ID;下次更新尽力清理旧暂存后重新 prepare,不保存远端 metadata 副本,也不让 cleanup 失败阻塞新更新或回滚健康环境。已有项目在实际使用 Workspace/Runtime 时按需解析 Host current identity 并执行固定 `attach-or-bootstrap`,注册后的 health、capabilities 和 prompt 启动不扫描完整 payload。详见 [设计说明](./docs/features/remote-host/environment-provisioning-technical-design.md)。
- [ ] **SSH Host 环境准备真实 Host 验收**:当前源码已在隔离 Linux x64 环境通过包安装、Ask/Execute、原生子代理工作区外写入、重连及 stop/bootstrap。Host 卡片的完整 GitHub、北京镜像、Linux x64/arm64、取消和离线 GoodBuddy 传输矩阵仍待验收,候选 CI/原生打包与系统休眠唤醒也尚未验证;部分开发证据不代表已经发布。
- [x] **DeepSeek Harness(预览)**:使用 GoodBuddy 固定 Host 和 OpenAI 兼容模型连接;优先使用管理员提供的连接,否则跟随兼容的默认模型或首个兼容连接,无需单独重复选择。Ask 只允许调用 Host 中真实注册的 `read`、`skill` 以及 Main 管理的 Web Search/Fetch 代理,拒绝插件同名冒充,Execute 放行全部已启用内置及插件工具,并以当前用户权限运行。图像输入跟随所选模型连接的能力声明,文本模型在 Host 或模型调用前拒绝图片,图片模型通过有界内联内容和临时 Attachment Store 接收 JPEG/PNG。
- **SSH Host 环境准备验证记录**:当前源码已在隔离 Linux x64 环境通过包安装、Ask/Execute、原生子代理工作区外写入、重连及 stop/bootstrap。现有记录未覆盖 Host 卡片的完整 GitHub、北京镜像、Linux x64/arm64、取消和离线 GoodBuddy 传输矩阵,候选 CI/原生打包与系统休眠唤醒也尚未验证;开发记录不代表版本已经发布。
- [x] **远程工作区与长任务更新**:Agent `0.11.23` 新增远程文件/Git 管理和可选模型限额,随包 Runtime 不再施加固定十分钟 Prompt 时限;无限请求时长仍保留独立连接超时。请先升级 Desktop 至 `0.12.11`,再下载并更新 Host 上的 Agent。
- [x] **远程原生问答与取消修复**:Agent `0.11.24` 修复远程原生问答转交和取消待答后同一会话续发,需要先升级 Desktop 至 `0.13.0`。
- [x] **DeepSeek Harness(预览)**:使用 GoodBuddy 固定 Host 和 OpenAI 兼容模型连接;优先使用管理员提供的连接,否则跟随兼容的默认模型或首个兼容连接,无需单独重复选择,设置页显示实际管理员或回退模型来源。Ask 只允许调用 Host 中真实注册的 `read`、`skill` 以及 Main 管理的 Web Search/Fetch 代理,拒绝插件同名冒充,Execute 放行全部已启用内置及插件工具,并以当前用户权限运行。图像输入跟随所选模型连接的能力声明,文本模型在 Host 或模型调用前拒绝图片,图片模型通过有界内联内容和临时 Attachment Store 接收 JPEG/PNG。Windows Host 启动和 ACP 会话共用规范工作区路径,同目录不同写法可复用正常 Runtime,不再因路径不一致而创建会话失败。
- [x] **DSH npm 插件市场**:市场默认关闭,由用户显式开启后搜索公共 npm 的 `dsh-plugin` 包,使用捆绑 npm 执行精确版本安装和普通 lifecycle scripts,并支持启停、JSON 配置、移除、失败启动自动停用和离线管理已安装插件;关闭市场只隐藏目录与管理界面,不改变已有插件的启停状态,第三方代码不受 Ask 初始化隔离。
- [x] **Ask 与 Execute 工作模式**:Ask 保持只读;Execute 是用户对当前本机或 SSH 账号可用工具、进程、网络和可写路径的完整授权,包括工作区外路径及原生子代理工作。
- [x] **Runtime 原生交互转交**:OpenCode 与 Continue 的选择、yes/no、自由文本回答和跳过使用现有问答卡片,OpenCode 同时转交属于当前请求的子会话提问。Execute 权限确认自动处理,不等待第二次审批;具体支持范围见[交互边界](./docs/features/assistant-workbar/runtime-interactions.md)。
- [x] **Runtime 原生交互转交**:OpenCode 与 Continue 的选择、yes/no、自由文本回答和跳过使用现有问答卡片,本机与托管 SSH OpenCode 同时转交属于当前请求的子会话提问。托管 SSH 需要 Desktop `0.13.0`、Agent `0.11.24` 和新启动的托管 Runtime,不扩展到任意 ACP 服务。回答或跳过成功后保留原问题与答案,支持多轮记录和本地会话重载;旧版本已丢弃的答案不能恢复。并行问题按顺序等待回答,重复事件保留草稿,提交失败可重试;取消远程待答后可在同一会话继续发送。Execute 权限确认自动处理,不等待第二次审批;具体支持范围见[交互边界](./docs/features/assistant-workbar/runtime-interactions.md)。
- [x] **专家与 Subagent**:支持显式专家、团队分析和最多三个专家并行分析。专家继承父请求 Ask/Execute 模式,可使用已启用的本机直连模型工具,Ask 仍只读;这些工具不是远程 OpenCode 子会话。聊天先展示可逐项展开的专家完整输出,再在其下展示总 Agent 的综合结果,并随会话保存。
- [x] **OpenCode 子代理过程与最终结果**:子代理卡片按顺序显示文字、推理和工具过程,最终结果独立展示。远程实时过程需要 Agent `0.11.20`,旧包仍显示最终结果但不补造缺失过程。
- [x] **OpenCode 事件连接回收**:请求结束或取消后关闭自身订阅,不取消其他并行会话。
- [x] **OpenCode 事件连接回收**:聊天与原生上下文压缩在结束事件迭代前关闭各自订阅,覆盖完成、失败、取消和消费方提前结束,不取消其他并行会话。
- [x] **准确的消息底部状态**:区分请求准备、等待重试、开始重试、工具活动、待回答和终态;本机 OpenCode 使用原生重试次数和计划时间,直连模型显示自身退避阶段。消息底部状态点保持静态,未接入的 Runtime 重试不编造提示,会话列表闪点及发送、停止行为不变。
- [x] **直连模型命令工作目录**:Execute 命令可通过绝对路径、相对路径或符号链接使用工作区外目录;工作区仍为默认目录和相对路径基准,Ask 保持只读。
- [x] **简化 Runtime 选择**:会话与项目菜单不再枚举每个 Runtime 与模型的组合,Runtime 模型在系统设置中配置;已保存的项目固定模型仍可使用,并明确显示其固定模型。
- [x] **角色绑定模型连接**:每个角色可继承默认模型或选择独立文本模型连接,失效连接安全回退默认模型,综合角色始终继承默认模型。
- [x] **多协议模型配置**:支持 Anthropic Messages、OpenAI Responses、OpenAI Chat Completions、OpenAI Images 和无认证本机模型;新用户默认连接为本机 Ollama 兼容地址,不预置第三方云模型服务。升级时只替换从未配置凭据且仍完全等于旧内置值的历史默认连接,用户显式保存或加密凭据的连接保持不变;已有部署仅使用兼容环境变量提供凭据时继续沿用对应的历史连接参数,通用模型环境变量仍优先。“保存并测试模型”会发送有界的真实文本或图片生成请求并校验生成结果,而不是只检查 HTTP 连通性,因此可能产生少量服务商用量费用。
- [x] **模型请求定制**:每个 LLM 连接可填写有界 JSON 自定义请求头和顶层请求体。直连与 Continue 支持两者;本地 OpenCode 和 DeepSeek Harness 只使用各自原生支持的请求头,受管 SSH OpenCode 模型网关支持两者。Runtime、协议和认证字段始终优先,这些普通连接设置不能用于保存 API Key 或其他密钥。详见[模型连接请求定制](./docs/features/model-connections/README.md)。
@@ -59,12 +76,13 @@
- [x] **Skills 按需接入**:可分配给直连模型、OpenCode、Continue 和 DeepSeek Harness,并使用有界资源和受控 Runtime 边界。
- [x] **本机工具执行环境源码链路**:在“能力与工具 > 工具执行环境”中为本机 Skills 与 stdio MCP 选择 GoodBuddy 托管 Node.js、按需安装的托管 Python,或经过真实验证的自定义解释器;提供独立的原生地址/OSS 镜像选择、诊断、安装进度、取消和删除。新的本机 Runtime 与 stdio MCP 获得不可变 PATH 快照,不修改普通终端、系统环境或远程 Host。Windows x64 托管 Node 与原生地址 Python 已通过真实安装验证;托管 Python 归档按目标文件系统验证,Linux 保留大小写不同的合法路径,所有目标仍拒绝完全重复路径和不安全条目。
- [ ] **本机工具执行环境发布验收**:六个平台/架构的 OSS 镜像对象已完成字节、大小和 SHA-256 公开回读验证。托管 Python 保持按需下载,不向 Desktop 发行包额外带入许可证文件。每个标准打包任务使用目标架构原生 Runner,并在打包前真实安装托管 Python,验证 SSL、pip 与 venv。不可变的 `v0.12.0` 尝试在发布前通过 Windows 与 macOS,但暴露了 Linux TAR 大小写处理问题;`v0.12.1` 在同步 Agent 发布时于原生打包前取消。已发布的 `v0.12.2` 随后通过全部六个原生打包任务及其托管 Python 安装探针。真实 Skill/MCP、自定义解释器和运行中进程协调仍是待完成验收。
- **本机工具执行环境验证记录**:六个平台/架构的 OSS 镜像对象已完成字节、大小和 SHA-256 公开回读验证。托管 Python 保持按需下载,不向 Desktop 发行包额外带入许可证文件。每个标准打包任务使用目标架构原生 Runner,并在打包前真实安装托管 Python,验证 SSL、pip 与 venv。不可变的 `v0.12.0` 尝试在发布前通过 Windows 与 macOS,但暴露了 Linux TAR 大小写处理问题;`v0.12.1` 在同步 Agent 发布时于原生打包前取消。已发布的 `v0.12.2` 随后通过全部六个原生打包任务及其托管 Python 安装探针。现有记录未覆盖真实 Skill/MCP、自定义解释器和运行中进程协调。
- [x] **内置 MCP 按需接入**:知识库、魔法笔记、GoodBuddy 配置与内置浏览器 MCP 可分别启停,并可分配给直连模型、GoodBuddy 管理的 OpenCode 和 Continue;DeepSeek Harness 在设置中明确显示为暂不支持。内置 MCP 仅通过当前请求的短期本机权限提供,Ask / Execute 读写边界不受用户配置放宽。
- [x] **MCP Tools**:显式启用的自定义 MCP 可按 Runtime 分配给直连模型、GoodBuddy 管理的 OpenCode、Continue Agent Execute 和 DeepSeek Harness,并仅在 Execute 加载;Agent 子进程只获得按请求签发的本机回环权限,MCP 地址、命令和凭据保留在 Main,动态工具仍会重新发现并经过现有执行记录与权限边界。
- [x] **MCP Tools**:显式启用的自定义 MCP 可按 Runtime 分配给直连模型、GoodBuddy 管理的 OpenCode、Continue Agent Execute 和 DeepSeek Harness,并仅在 Execute 加载;Agent 子进程只获得按请求签发的本机回环权限,MCP 地址、命令和凭据保留在 Main,动态工具仍经过发现、执行记录与权限边界。直连模型与 Harness 的健康调用复用当前请求已发现的工具清单,不在每次调用前重复发现。
- [x] **MCP Prompts 与 Resources 元数据**:MCP 测试仅在 Server 声明对应能力时发现有界的 Prompt、参数与 Resource 元数据,不读取 Resource 内容;Runtime 支持的 Prompt 可填入聊天草稿后继续编辑。OpenCode 可报告实验性 Resource 清单,Continue 当前版本明确不支持 Resources。
- [x] **本地知识库**:支持文件、目录和网页导入、SQLite FTS5 检索及来源追溯。知识范围按对话保存,新对话默认不选择知识库;创建与检索高级参数默认折叠。文档区分可用、处理中和失败,支持打开来源、失败重试及移除来源前确认。
- [ ] **外部知识库接入**(规划中):在现有知识库页面管理 Dify、FastGPT 和 RAGFlow 实例;新建知识库时可读取并绑定远端知识库,保留 Provider 专属检索配置和引用。外部系统只负责知识检索,不接入其 App、Chat、Workflow 或 Agent,也不批量同步、本地索引或修改远端内容。被回答引用的有界检索片段随会话保存在本机。详见[外部知识库接入 PRD](./docs/features/knowledge-base/external-knowledge-prd.md)。
- [x] **外部知识库接入**:在知识库页面管理 Dify、FastGPT 和 RAGFlow 实例,通过目录或手工 ID 验证并绑定远端知识库,保留 Provider 专属检索配置和引用;对话支持按需检索及每次回答前检索。外部系统只负责知识检索,不接入其 App、Chat、Workflow 或 Agent,也不批量同步、本地索引或修改远端内容。被回答引用的有界检索片段随会话保存在本机。详见[外部知识库接入 PRD](./docs/features/knowledge-base/external-knowledge-prd.md)。
- **外部知识库验证记录**:三家 Provider 已通过真实服务、本机 HTTP MCP 和生产 IPC 短答案检查,Dify 另通过完整 App 输入区到回答及引用的链路。现有记录未覆盖 FastGPT/RAGFlow 桌面专属参数、复杂多库问答和远程回答前检索,详见[验证记录](./docs/features/knowledge-base/progress.md)。
- [x] **知识图谱**:支持规则、模型和混合抽取,以及实体、关系、别名和证据维护。
- [x] **向量模型配置与检索**:可配置兼容 Embeddings 接口并用于语义检索。
- [x] **向量诊断与索引任务**:提供真实向量生成诊断、按文档重建进度、取消、失败状态与重启后结果恢复;每篇成功文档立即可用于检索。
@@ -72,15 +90,17 @@
- [x] **分块、维护与评估**:支持固定、结构化和父子分块,分块维护、可取消重建及双语检索评估。
- [x] **受控知识本体**:每个知识库可定义实体、关系、别名和端点约束,保留证据偏移、置信度和抽取来源,并显式提示图谱重建。
- [x] **强制检索与引用上下文**:对话可按需或每次先检索,显示零结果、降级、失败与取消状态,并可查看引用上下文或安全打开来源。
- [x] **魔法笔记 / Magic Notes**:提供本地优先的笔记与待办工作台、范围管理、编辑、筛选和受控 AI 评论;来源记录直接显示在所选待办的详情中,切换事项不会混入上一项的来源内容,读取失败使用应用通知,来源缺失有明确提示。左侧入口可按设置显示未完成待办数量,创建、保存和评论结果使用统一应用通知。
- [x] **魔法笔记 / Magic Notes**:提供本地优先的笔记与待办工作台、范围管理、编辑、筛选和受控 AI 评论;来源记录直接显示在所选待办的详情中,切换事项不会混入上一项的来源内容,读取失败使用应用通知,来源缺失有明确提示。左侧入口可按设置显示未完成待办数量,创建、保存和评论结果使用统一应用通知。Agent / MCP 写入后,已打开的工作台自动更新,并保留当前选择与未保存草稿。
- [ ] **MCP Server Control Plane**(规划中):统一 MCP 生命周期、健康检查、重连、Schema 缓存、隔离、审批和审计。
- [ ] **可追溯笔记摘录与 AI 编辑**(规划中):从对话、知识和网页收集带来源的摘录,并提供需确认的总结、改写和整理操作。
### 工作管理、长期协作与工作流
- [x] **任务、活动与成果**:集中管理任务状态、审计活动和独立成果文件;普通聊天回复只保留在会话中,不再自动复制到成果栏,已有重复聊天 Markdown 从成果列表隐藏但不物理删除。运行记录改由 Main SQLite 保存,不再受旧 Renderer 的 500 条、单详情 4,000 字符或约 2 MB 上限影响,页面按批继续显示;不再常驻旧缓存截断警告,但不会恢复此前已丢失的历史。Token 用量按 Runtime 与模型归类,并针对 OpenAI 兼容与 Anthropic Messages 的不同上报口径归一化展示缓存命中率;活动按会话分组并默认收起,避免长历史占满页面。
- [x] **紧凑会话工具记录**:逐项展开工具记录即可查看和复制结果、错误及输入参数;会话与子任务进度使用简洁行,减少重复 Runtime 摘要。OpenCode、Continue、DeepSeek Harness 和直连模型可显示已有文件路径、命令或搜索摘要,并在工具完成和会话重载后保留。
- [x] **Task 与定制任务体验**:每个产品级 Task 只关联一条 Conversation,一条 Conversation 可承载多个 Task;左侧会话列表通过行首展开按钮显示带共享状态点的 Task 子项,父会话行不重复任务标签,UI 只展示到 Task,不暴露 Job/Run 层级。新建定制任务可关联当前或新 Conversation,默认 Execute 并沿用 Runtime、工具和审批边界;重复触发复用同一 Task,文本结果回写 Conversation,独立文件和图片保留为成果。普通消息与到期 Scheduled Task 共用 Conversation 级持久发送队列,同一会话一次只执行一项;当前回复期间仍可继续发送,队列按顺序续跑,并允许删除或“立即中断并插入”。Task Center 继续作为完整索引,不建设独立 Automation Center。当前计划触发支持单次、每日和每周;高级时区、Cron、事件触发与重试治理仍按 PRD 逐步实现。详见 [Task Center PRD](./docs/features/task-and-job/task-center-prd.md) 和 [Scheduled Task PRD](./docs/features/task-and-job/scheduled-task-prd.md)。
- [x] **记忆与智能心跳**:当前提供周期回顾、建议记忆、洞察、后续任务和可审计运行轨迹。
- [x] **定时消息沿用会话**:定时消息使用会话当前历史、Runtime、工作模式和已保存的知识检索设置;任务详情显示实际运行模式,当前 occurrence 未结束时禁止重复“立即运行”。
- [x] **智能心跳入口与范围改善**:将“智能心跳 > 心跳计划”作为完整配置的唯一权威入口,支持创建和编辑 Global 或指定一个、多个 Project 的计划;旧单项目配置无损迁移,项目级记忆与行动输出必须显式指定范围内的 Project。Task Center 和设置不再复制心跳表单;按分区回顾、候选生成和唤起条件仍待设计。详见 [智能心跳 PRD](./docs/features/smart-heartbeat/prd.md)。
- [ ] **通用监督**(规划中):通过固定监督栏目观察用户选择的会话、任务、自动化或实验对象,提供带证据的评论与人工介入请求,但不自动发言、批准工具或切换 Execute。详见 [会话监督 PRD](./docs/features/conversation-supervision/prd.md)。
- [ ] **批量运行与对比实验室**(规划中):对模型、Prompt、角色和工作流配置执行批量对比,汇总质量、耗时、Token、费用、失败率和成果差异。
@@ -91,6 +111,8 @@
### 浏览器、通信、语音与应用维护
- [x] **Runtime 共享内置浏览器**:使用 GoodBuddy 内置的隔离 Chromium,不控制客户端已安装的浏览器;用户通过内置 MCP 总开关和 Runtime 分配决定是否提供给 Execute,开启后不逐次询问。关闭 Agent 浏览器能力不影响用户继续在浏览器工作栏中手动前往、返回、刷新、停止加载、交互或关闭浏览器。直连模型、GoodBuddy 管理的 OpenCode 和 Continue 通过请求级权限共享按 Conversation 归属的会话和串行操作路径,用户导航会改变 Agent 下一步看到的页面。
- [x] **浏览器多页签与按需资源**:同一对话可打开多个共享登录状态、页面与导航独立的浏览器页签,模型请求固定使用开始时绑定的目标。未使用的请求预留不创建浏览器资源,已释放页面可重新打开;保留显式截图,普通操作不再触发无用的自动截图。
- [x] **浏览器关闭与刷新**:AI 使用中的页签也可关闭,不取消整个请求;后续显式导航会创建独立替代页。空白页不能刷新,切换或关闭面板后清除旧操作错误。
- [x] **客户端电脑控制工具**:与内置浏览器分开管理,并保留范围、取消、超时、输出边界和执行记录。
- [x] **远程消息通道项目**:微信 ClawBot、企业微信和钉钉分别拥有系统管理的项目、独立远程会话、工作目录、处理后端、默认 Ask/Execute 模式及任务活动归属;完整回复交由各通道按平台能力控制长度与分段,不再由公共服务统一截断。
- [x] **微信 ClawBot 扫码与媒体**:通过独立 Sidecar 完成本机扫码、验证码、加密凭据和文字收发;支持个人微信私聊图片与文件,单条消息最多 4 个附件、解密后合计不超过 12MB。
@@ -100,12 +122,16 @@
- [x] **可选本地语音模型管理**:应用不内置模型权重;提供校验下载、进度与取消、来源链接、ZIP 或本地目录导入、切换和删除。
- [x] **本地录音与离线转写**:采集麦克风音频并使用已选择的本地模型离线转写,支持停止、取消、状态反馈和资源释放。
- [x] **版本检查与镜像节点**:在“关于与更新”中选择 GitHub(默认)或镜像节点;手动检查、启动时检查和下载页使用同一选择,并只读取固定可信的发布索引,不自动下载或安装。
- [x] **窗口恢复入口**:渲染失败时显示本地化的重新加载页面,渲染进程崩溃时提供原生恢复确认;重新加载可能丢失未保存输入。详见[窗口恢复说明](./docs/development/window-recovery.md)。
- [x] **应用内反馈**:在“关于与更新”中直接提交问题、建议或体验反馈,支持可选邮箱和单张截图;默认不上传诊断,用户可主动附加有界的最近桌面诊断摘要。失败后保留草稿、诊断选择和同一请求编号;不会附加对话、Prompt、凭据、文件内容、路径、Provider 原始响应或远端 Agent 日志。
- [x] **内网兼容模式**:默认开启;允许应用内 HTTP 与无效、自签名或过期的 HTTPS 证书,关闭后恢复严格地址和证书校验。
- [x] **内网兼容**:允许应用内 HTTP 与无效、自签名或过期的 HTTPS 证书;反馈、微信凭据与媒体等固定平台服务仍保持严格证书校验。
### 开源、构建与发布
- 当前源码候选为 Desktop `0.12.10`、Agent `0.11.22`,OpenCode 固定为 `1.18.29`;正式发布状态以 Desktop 与 Agent 的独立发布渠道为准。
- 当前源码候选为 Desktop `0.13.6`、Agent `0.13.0`,OpenCode 固定为 `1.18.29`;正式发布状态以 Desktop 与 Agent 的独立发布渠道为准。
- Agent `0.13.0` 接入远程图片工具、共享 Runtime 进程并修复会话恢复,复合包要求 Desktop `0.13.5`;请先升级 Desktop,再更新 Host 环境。Node 保持 `24.19.0`。
- 验证记录与功能状态分开。存储已有本地及 Linux x64 Host 真实工具任务、多项目多会话、取消和无损迁移验证;进程复用有 Windows 完整 App 与 Linux x64 Host 证据,PPTX OCR 与空闲内存释放有 Windows 真实文件证据。图片工具已有真实服务商生成/编辑及 Host 传输证据,这些记录未覆盖完整界面自然语言调用和本地/远程切换。候选仍需主分支 CI 及原生发布打包,详见[Runtime 验证记录](./docs/features/assistant-workbar/progress.md)与[图片工具验证记录](./docs/features/conversation-media-generation/progress.md)。
- Desktop 0.13.6 历史修复已通过 4,301 项测试(67 项跳过)、类型检查、改动文件 lint、隔离生产 Electron 验证及三组合成历史对照。开发阶段使用 3 次真实文本调用,其中 2 次在当前源码 Linux Agent 上验证 Session 续接,未改变 Agent 协议。发布准备不重跑本地生产构建;按用户要求,本地候选重跑在更新说明校验后取消,交由主分支 CI 验证及原生标签打包。本次 Desktop 更新不重新发布 Agent 0.13.0。
- [x] **0BSD 开源许可**:原创代码可自由使用、复制、修改、分发和商用;第三方组件和资源仍遵循各自许可证。
- [x] **可复现依赖安装与源码构建**:使用锁定依赖、Node.js 24 和统一的测试、类型检查、Lint、生产构建命令。
- [x] **六平台原生发布矩阵**:Windows、macOS、Linux 的 `x64`、`arm64` 目标由原生 Runner 构建,并提供发布清单和 SHA-256 哈希。Windows 提供 NSIS 与 portable ZIP,macOS 仅 DMG,Linux 提供 AppImage、DEB 和 RPM,共 12 个安装包、20 个发布资产。
@@ -117,7 +143,7 @@
- [x] **远程任务委派**:仅在用户显式配置端点和令牌后启用,按全局内网兼容模式使用 HTTP(S),结果进入持久化发件箱。
- [ ] **Headless Runtime API**(规划中):提供本机优先的任务、事件、状态和成果 API,以及有范围、有效期、限流和撤销能力的令牌。
- [ ] **GoodBuddy Team Hub**(规划中):以可选服务提供组织、RBAC、项目共享、远程 Agent、策略下发和租户审计。
- [ ] **SSH 主机与远程执行空间发布验收**:主机 CRUD、Host Key、加密凭据、Project UI、Workspace、OpenCode ACP v5、Agent-owned Prompt/gateway/transcript、Ask 只读、Execute 完整账号权限、取消、detached Agent 精确重连和 release-only 双架构资源校验已经接线。Linux x64 当前源码已用真实模型与工具通过 detach、进程结束、relay 丢失、并发、取消、Provider 异常、Agent 重启和 Desktop SQLite 恢复矩阵;当前源码测试签名 Agent `0.11.17` 又通过两个并发 Ask 读取、一次 Execute 写入并读回,以及 6 次带自定义 Header 和 Body 的真实受管网关模型请求,未发现 Provider 请求重放,隔离 Agent state 中也没有 API Key 或自定义请求值。公开 signing key registry 已供应。控制面直连源码不等待新 installer-bearing 包,但仍需公开核对当前 Linux x64/arm64 正式签名工件矩阵,并完成 GitHub/北京镜像、双架构和离线 GoodBuddy 传输的发布验收;门槛未满足时不宣称该路径已发布或通过正式发布验收。
- **SSH 主机与远程执行空间验证记录**:主机 CRUD、Host Key、加密凭据、Project UI、Workspace、OpenCode ACP v5、Agent-owned Prompt/gateway/transcript、Ask 只读、Execute 完整账号权限、取消、detached Agent 精确重连和 release-only 双架构资源校验已经接线。Linux x64 当前源码已用真实模型与工具通过 detach、进程结束、relay 丢失、并发、取消、Provider 异常、Agent 重启和 Desktop SQLite 恢复矩阵;当前源码测试签名 Agent `0.11.17` 又通过两个并发 Ask 读取、一次 Execute 写入并读回,以及 6 次带自定义 Header 和 Body 的真实受管网关模型请求,未发现 Provider 请求重放,隔离 Agent state 中也没有 API Key 或自定义请求值。公开 signing key registry 已供应。控制面直连源码不等待新 installer-bearing 包;现有记录未覆盖当前 Linux x64/arm64 正式签名工件的完整公开核对及 GitHub/北京镜像、双架构和离线 GoodBuddy 传输矩阵,发布结果另行记录。
- [ ] **多云远程沙盒 Agent**(规划中):通过云厂商 API 和 SSH Agent 管理专用 Linux 沙盒;凭据留在 Main 进程,高风险控制面操作单独确认。
## 规划原则
+2 -1
View File
@@ -156,7 +156,8 @@ npm run lint
Scan the QR code to join the GoodBuddy WeChat group:
<img width="1250" height="1194" alt="9627ce196b0de42f123c33dd9dea1568" src="https://github.com/user-attachments/assets/e806543f-6d8a-4c5c-ba44-19e74dac9a78" />
<img width="1250" height="1743" alt="46b5c3b5936388b0a55db6fe034a7d7f" src="https://github.com/user-attachments/assets/45cd1736-f1ae-4f99-85be-cdb2cc1160c0" />
+2 -1
View File
@@ -128,7 +128,8 @@ npm run lint
扫描二维码加入 GoodBuddy 微信群:
<img width="1250" height="1194" alt="9627ce196b0de42f123c33dd9dea1568" src="https://github.com/user-attachments/assets/ff87dfac-d72f-47b7-ac9a-c88b0b4c96d9" />
<img width="1250" height="1743" alt="46b5c3b5936388b0a55db6fe034a7d7f" src="https://github.com/user-attachments/assets/cc094468-2885-447a-afb4-91c7ac117fd3" />
## 开源许可
+55 -21
View File
@@ -210,6 +210,8 @@
- 选项数量为 2 至 4 个。
- 使用带 `aria-pressed` 的按钮组语义,支持方向键切换。
- 不能用于多选筛选、页面导航或执行即时命令。
- 与 `PageTabs` 一样,共享控件根节点必须 `flex: 0 0 auto`,不能被同级长表单或滚动区域
压缩高度。横向空间不足时保留控件自身滚动,不把标签裁成一条窄缝。
- 控件宽度由内容决定,除移动窄屏外不默认等分整行。
### 6.3 筛选工具栏
@@ -283,6 +285,9 @@
- 一级导航与最近会话之间、最近会话与底部账户区之间必须有可见结构分隔线。浅色主题使用 `--border-default`,深色主题可在可辨前提下使用 `--border-subtle`。
- 当前导航项和当前会话必须同时使用至少三种信号中的两种:强调背景、可见边框、图标或文字强调。浅色主题的当前项优先使用更完整的蓝色选中表面和较高字重。
- 未选中项保持平整,不为每一行添加卡片边框或阴影。悬停反馈不得强于选中状态。
- 项目选择器下方常驻跨项目活动汇总,空闲时显示“暂无活动”;项目计数与项目、会话级联菜单遵守
[项目活动汇总 UI 规则](./docs/features/application-tool-navigation/ui-design.md#10-项目活动汇总)。
活动数量不改变项目菜单的稳定分组和顺序。
- 桌面停靠状态下,主侧栏宽度允许在 `220px` 至 `420px` 范围内通过指针或键盘调整,
并记住用户选择;分隔条使用 `separator` 语义,支持方向键、Home 和 End。小于
`900px` 时继续使用固定宽度抽屉,不显示拖拽分隔条。
@@ -321,23 +326,27 @@
动态 Tab、“+”和终端细节见
[助手工作栏多终端页签 PRD](./docs/features/assistant-workbar/terminal-tabs-prd.md)。
- 升级后默认打开任务中心、工作区、浏览器和成果四个单实例应用,不根据当前页面或 Runtime
能力自动增删稳定应用目录入口。
- 升级后默认打开任务中心、工作区、一个浏览器实例和成果。任务中心与工作区是不可关闭的
单实例基础应用;浏览器是可关闭的多实例应用;成果保持可关闭的单实例应用。不可关闭不
等于锁定顺序。
- 资源监控作为本机单实例应用保留在能力目录,但不加入升级后的默认打开实例。面板只在可见
活动时采样,首期不持久化历史或提供后台负载告警。
- Tab 行只显示已打开实例,行末始终提供“+”普通按钮;点击后在内容区打开应用选择页。
“+”不使用 `tab` 语义,也不能因为终端或其他应用已经打开而消失。
- 应用注册表声明单实例或多实例。在工作栏“+”目录中,单实例重复选择时聚焦已有 Tab;终端
从首期开始支持多实例,每次选择创建独立 Tab 和 Shell Session。主侧栏系统工具快捷入口
可以聚焦当前执行空间中最近使用的终端,额外终端仍从“+”目录创建。
- Task Center 是 Task 的单例应用级索引,不使用“跟随 / 固定目标”多实例模式。每个 Task
只关联一条 Conversation,一条 Conversation 可以关联多个 Task;列表不得复制会话内容,
也不得把 Job/Run 提升为可导航 UI 对象。
- 应用注册表分别声明单实例或多实例、默认打开、必须存在、可关闭和排序策略。在工作栏“+”目录中,
单实例重复选择时聚焦已有 Tab;浏览器和终端每次选择创建独立实例。主侧栏系统工具快捷
入口按当前上下文聚焦最近使用的匹配实例,额外实例仍从“+”目录创建。
- Task Center 是默认跟随当前项目的单实例索引,不使用多实例模式。用户可在同一 Tab 内切换
“当前项目”“全局任务”和“全部项目”;范围变化不得创建第二个 Task Center。每个 Task 只
关联一条 Conversation,一条 Conversation 可以关联多个 Task;列表不得复制会话内容,也
不得把 Job/Run 提升为可导航 UI 对象。
- 各栏目读取当前会话或项目的对应内容;当前内容不可用时仍保留栏目入口并说明原因。
- 能力、连接和内容可以动态变化,栏目入口不能随之自动隐藏。不可用状态必须说明原因、影响和可执行入口。
- 用户可以主动排序或隐藏栏目,并可恢复默认布局;应用不能用用户偏好机制实现自动能力裁剪。
- 用户可以主动排序栏目,并可关闭可关闭实例或恢复默认布局;任务中心和工作区不能隐藏或
关闭。应用不能用用户偏好机制实现自动能力裁剪。
- 已打开实例保留 `tablist`、`tab`、`tabpanel`、方向键、Home、End 和焦点恢复语义。
实例过多时 Tab 区单行横向滚动,“+”保持可见,不换行或改成下拉框。
实例过多时 Tab 区单行横向滚动,“+”保持可见,不换行或改成下拉框。不可关闭实例不渲染
关闭按钮,也不响应关闭快捷命令。
- 当前工作栏 Tab 必须同时使用连续内容表面、可见边框、顶部指示、图标或较高字重中的至少
两种信号;悬停反馈不得强于当前选中态。Tab 关闭按钮保留固定布局空间,默认仅在对应 Tab 悬停或
`focus-within` 时显示;无悬停或粗指针设备始终显示,键盘与辅助技术始终可访问。
@@ -354,8 +363,9 @@
含义模糊的“停止浏览器”。地址输入显示浏览器权威提交 URL,但用户编辑期间保留本地草稿,
不被画面或状态刷新覆盖;提交裸域名时自动添加 `http://`,提交、失焦后的同步规则不得让
尚未提交的文字改变 Agent 看到的页面。
- 浏览器工具栏操作的是当前 Conversation 中 Agent 使用的同一隔离浏览器会话,用户导航会
有意改变 Agent 下一步看到的页面。创建中、Agent 操作中或已进入完整交互窗口时禁用其余
- 浏览器工具栏操作的是该实例绑定的 Conversation 和 Browser Tab。同一 Conversation 的多个
Tab 共享隔离登录上下文,但页面、导航、操作队列和元素引用相互独立。模型请求开始时冻结
工具目标,用户切换工作栏 Tab 不改变进行中的 Agent 操作。创建中、Agent 操作中或已进入完整交互窗口时禁用其余
工具栏控件;仅当前导航仍在加载时保留“停止加载”,且该操作只中止页面加载,不关闭浏览器、
不释放 Runtime 上下文。没有会话的活动 Conversation 可以直接在地址栏输入 URL 并“前往”
创建会话,但必须复用 Agent 浏览器相同的 URL 策略与能力路径。
@@ -363,7 +373,7 @@
遮罩或隔离其内容。分栏计算排除最左侧主导航,主工作区与工作栏采用相同的 `300px`
最小宽度;拖动保存两者在可用分栏宽度中的比例,任一侧到达最小值后都不得继续侵占。
所有可容纳两侧最小值的尺寸均支持指针和键盘调整,并须保留全部栏目入口。
- 工作栏开关固定在窗口关闭按钮正下方,同一个按钮负责展开与收起并持续暴露状态;图标中的
- 工作栏开关固定在顶栏 `.topbar__actions` 内、紧邻浅色/深色主题按钮之前,同一个按钮负责展开与收起并持续暴露状态;图标中的
箭头方向必须随当前动作切换,收起状态提示展开,展开状态提示收起。按钮保持普通图标按钮
视觉,不通过持续高亮表达展开状态。工作栏内部不再重复关闭按钮;当前会话没有 Task 时,
不得为了放置该开关而保留一条空上下文栏。
@@ -385,6 +395,16 @@
`34px × 34px` 点击区域。
- 发布说明 Modal 与其他应用级 Modal 一样使用整窗遮罩;遮罩必须覆盖主侧栏、右侧内容和
顶栏,不能保留一条未被遮罩的窗口控制色块。关闭操作由 Modal 自身提供。
- 整窗固定遮罩通过 `createPortal(..., document.body)` 挂载,避免页面层叠上下文影响;
保留 React Context、关闭与焦点管理,表单样式不得依赖已移出的页面祖先选择器。
- 原生浏览器 `WebContentsView` 不受网页 `z-index` 控制。应用级 Modal 打开时统一释放
浏览器 viewport;非模态对话框、菜单、局部确认或应用通知与浏览器区域相交时也释放。
浮层关闭后只恢复当前活动实例的 viewport,不关闭、重建或刷新网页。接线与测试见
[浏览器多实例技术设计](./docs/features/assistant-workbar/browser-tabs-technical-design.md#4-renderer-与-viewport)。
- 遮罩必须 `inset: 0` 铺满整窗,并由 `styles.css` 中统一的遮罩选择器声明
`-webkit-app-region: no-drag`,否则 `.brand` 与 `.topbar` 的原生拖动区域会截获指针,
使遮罩上的控件无法悬停和点击。新增整窗遮罩时扩展该共享选择器,不要在各自规则里复制
`no-drag`,也不要用 `inset: 38px 0 0` 之类的偏移绕开标题栏。
- 当前会话存在 Task 时,Task 上下文栏位于标题栏下方并承载任务摘要与详情;没有 Task 时
整行隐藏。折叠状态保持为 `46px` 单行表面,与右侧工作栏标签栏等高,左右分隔线必须水平
对齐。该栏不重复放置新建入口。
@@ -395,7 +415,7 @@
不再重复显示状态文字;完整状态详情继续通过可访问说明和工具提示提供。
- 浅色与深色切换属于持续可用的窗口级操作,直接显示太阳或月亮图标,并通过可访问名称说明将切换到的主题。选择必须持久化,切换不得改变布局。
- 顶栏只直接显示当前任务所需的高频操作。已有侧栏账户设置入口时,不再重复显示 Runtime/设置入口;使用帮助优先放在相关操作附近,而不是为单个帮助项创建“更多”菜单。
- 助手工作栏开关固定在上下文栏最右侧并与窗口关闭按钮纵向对齐;展开后该开关保持原位并
- 助手工作栏开关固定在顶栏 `.topbar__actions` 内、紧邻浅色/深色主题按钮之前;展开后该开关保持原位并
负责收起,不在工作栏标题区域增加第二个关闭入口。
- 只有存在至少两个无法由稳定入口承载的低频全局操作时才增加全局菜单,不为了容纳一个冗余入口而显示省略号按钮。
- 窄窗口下优先压缩状态标签并保留图标按钮,不隐藏窗口控制、当前范围或进行中的风险状态。
@@ -600,20 +620,26 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- 模式、模型或工具权限属于上下文控制,不与页面导航页签混用。
- 模型、专家角色、工作模式、OpenCode Agent、Continue 预设和 Runtime 快捷操作使用统一的上下文单选菜单,并保持菜单互斥、键盘可达和选中状态明确。
- Runtime 选择菜单必须反映当前执行空间的真实能力。托管 SSH 项目当前只支持 OpenCode,因此输入区只渲染已配置的 OpenCode 选项和管理入口,不显示直连模型、Continue 或 DeepSeek Harness 的不可用入口;旧会话保存的其他 Runtime 选择在项目激活时恢复为当前 OpenCode 配置。
- 输入区第一行工具栏只承载附件、语音、知识范围、专家角色、工作模式、Runtime 选择和发送等通用操作。OpenCode Agent、Continue 预设及 Runtime 快捷操作必须放入其下方独立的 Runtime 专属功能行,通过可见分组名称、顶部边界和差异化表面与通用操作分层;该行只承载对当前消息生效的高频选择,当前 Runtime 没有可选专属功能时不保留空行。
- 输入区使用一个完整容器,主工具栏保留附件、语音、Runtime、工作模式、会话选项及停止/发送。知识范围、专家角色、OpenCode Agent、Continue 预设和 Runtime 快捷操作收纳在会话选项浮层中;Runtime 专属选择在浮层内按提供方分组,无可选功能时不保留空分组。输入框上方以简短摘要显示生效的专家、知识范围、检索方式和 Runtime 专属选择。
- 会话选项浮层与主行菜单互斥,内部复用上下文选择菜单;子菜单就地展开,浮层统一限制宽高并滚动。Escape 先关闭子菜单并恢复其触发按钮焦点,再关闭会话选项并恢复入口焦点;点击外部、焦点离开、切换会话或开始/结束回复时关闭浮层。
- 会话选项入口紧随录音键,使用无常驻边框的调节图标,保留可访问名称、悬停说明和展开态反馈;浮层在工具栏左侧展开,最大宽度为 `300px`,窄窗口不超过输入区。浮层内选择器使用整行轻量列表样式,避免重复的按钮边框;知识库行右侧显示展开箭头,展开时朝上。
- OpenCode、Continue 和 DeepSeek Harness 后续的 Task 级委派与取消、后台执行进度/结果、Workflow/Hook 运行、长任务暂停/恢复/终止及原生会话监督统一进入应用级助手工作栏固定的“Runtime”栏目,不加入 Composer。栏目入口始终存在;只选择 Conversation 或 Task,并按 Task 聚合执行状态,不显示 Job/Run 树或独立操作菜单。内部可选区域按用户所选目标和 Runtime 的真实能力显示,不为未支持能力渲染空卡片或成排禁用按钮。切换跟随目标时必须清理上一归属的监督状态,固定目标则保持不变。
- 设置中心只管理持久 Runtime 配置、默认值和能力清单;右侧 Runtime 栏目管理用户当前跟随或固定目标的生命周期。两处不得复制同一实时操作,栏目中的高风险操作仍须就地确认并保留取消、权限、用量和活动审计。
- Runtime Prompt 快捷操作只把模板填入输入草稿,用户可以继续编辑;OpenCode Command 由 Runtime 原生 API 执行,输入框只承载可选参数,不以普通斜杠文本冒充执行。
- OpenCode 原生 Task 调用在本地项目和托管 SSH 项目中统一解析为子 Agent 状态卡,不显示为普通工具卡。状态卡持续显示 Agent 类型、任务说明、运行状态、完成输出或失败原因;身份徽标、Agent 名称和状态位于首层,任务说明作为主信息,Runtime 来源作为次级元数据,输出与错误在同一卡片内按需展开。父 Agent 的推理不冒充子 Agent 过程,必须与状态卡保持明确间距。状态不能只靠颜色表达。推理、子 Agent、工具和正文统一按事件首次出现顺序进入消息块;增量更新只更新原位置,Task 临时工具块重分类后必须原位替换,不能把子 Agent 卡统一提升到消息顶部。同一调用的增量事件按稳定调用 ID 更新,不能因 ACP 首帧尚无参数而永久保留重复工具卡或活动记录。一次请求内已观察到的子 Agent 均保留在消息与活动记录中;父请求取消、失败或意外中断时仍处于活动状态的卡片必须结算为对应终态,父请求成功结束但子 Agent 未报告终态时必须显示为失败而不是推断为完成。
- Agent 回复进行中锁定模型、专家角色、工作模式和 Runtime 定制选择器,并关闭已打开的上下文菜单;回复结束或停止后再恢复选择,避免界面状态与本次运行实际使用的上下文不一致。
- 普通工具在主会话和子 Agent 过程中使用同一轻量折叠记录:不显示外层“工具执行”标题、计数、背景和边框,保留无障碍区域名称。记录纵向左对齐,头部按内容收宽且不超过消息宽度;展开箭头在左,工具名作为主标题,有额外信息的单行摘要弱化显示。右侧以绿色对勾显示成功完成,以图标显示运行和待执行状态,提供悬停文字和无障碍名称;失败和可恢复状态保留文字。仅重复工具名和执行状态的已知 Runtime 模板摘要在头部与详情中均隐藏;刷新重试、审批等额外提示保留。默认折叠,状态和输出更新不改变用户的展开选择;折叠时露出一行错误,展开详情使用消息可用宽度、浅背景和左侧细线,按错误、结果、独立折叠的输入参数阅读。详情保留原始文本,完整 JSON 输入可缩进显示,复制仍使用收到的原文,并通过应用通知反馈。详情工具栏只保留复制按钮,文本默认自动换行,长内容在固定最大高度内滚动,不提供换行切换和展开全部按钮。
- OpenCode 主会话和子 Agent 的工具记录均保留 Runtime 提供的操作标题作为摘要,显示文件路径、命令或操作描述;本地 SDK 标题去除首尾空白后最多保留 240 字符,没有标题时沿用工具名回退规则。主会话工具失败或中断时保留已收到的标题;远端 ACP 继续在记录名称中显示其提供的标题。
- 工具取消后保留“已取消”文字,避免与失败图标混淆。
- Continue、DeepSeek Harness 和直连模型的工具名后显示单行操作摘要,内容提取、长度与完成态保留规则见[工具操作摘要](./docs/features/assistant-workbar/runtime-interactions.md#工具操作摘要)。
- Agent 回复进行中不能禁用普通消息发送。新消息与到期 Scheduled Task 共用 Conversation 级待发送队列。队列位于 Composer 容器之外、与输入框等宽并保持独立间距;有待发送项时直接显示无标题栏的极简列表,空队列不保留占位。每项固定为一行,按进入顺序显示轻量来源图标和截断摘要;普通消息使用消息图标,Scheduled Task 统一使用淡出时钟图标。
- 待发送项默认在当前回复结束后顺序执行。每项提供明确的“立即中断并插入”(空闲时为“立即运行”)和删除操作;前者先取消当前 Conversation 的活动执行,再将所选项提升为下一项,不允许同一 Conversation 并发写入时间线。
- 队列区域向上增长并设置有界滚动高度,不提供展开、收起或冗余计数标题。宽容器中的操作采用弱化的行内样式,不渲染成强调卡片或胶囊;窄容器中保留来源、摘要和图标操作并隐藏冗长按钮文字。区域、来源和每个操作必须有可读无障碍名称。队列异步错误进入应用通知,不在 Composer 内复制通知样式。
- 支持上下文状态的 Runtime 在输入区下方复用同一紧凑用量条;文案必须区分“本次模型调用”和“压缩后对话估算”。手动压缩仅在当前 Runtime 明确支持且没有活动回复时显示,作为元信息区左下角的浮动次操作,不参与输入区高度计算;元信息区始终预留稳定高度,切换 Runtime 不得让输入框上下位移。元信息区与窗口底部只保留紧凑安全留白,不形成额外空白区。进行中禁用重复操作,结果通过应用通知反馈。
- 已选择的工作模式在触发按钮中只显示 `Ask` 或 `Execute`;完整中文含义和说明保留在菜单选项、可访问名称及输入区下方的模式说明中。
- 宽度大于 `700px` 时,通用工具栏内的添加内容、知识范围、专家、模式和 Runtime 选择保持同一行,Runtime 专属功能在自己的下一行横向排列。窄输入区中两行分别换行,专属选择器以至少 `220px` 的基准宽度换行而不是被挤压;不能把专属控件重新塞回通用工具栏。
- 输入框原生支持 `Ctrl+V`:文本直接进入草稿,图片转换为本次消息附件。文件选择由上传按钮承担,不再提供独立“读取剪贴板”按钮;默认工具栏也不提供“截取当前屏幕”和“选择应用窗口”入口,避免与系统粘贴、文件选择和后续工具执行重复。
- “Enter 发送 · Shift+Enter 换行 · Ctrl+V 粘贴图片或文本”等输入操作提示放在空输入框内部,作为主占位文案的次级行;不得在输入框下方单独占用第二行。输入框下方只保留一行当前模式、安全边界或全局快捷键说明。
- 宽度大于 `700px` 时,主工具栏的添加内容、Runtime、模式、会话选项与发送保持同一行。窄输入区按容器宽度换行,保留发送及停止入口;选项浮层不得超出输入区宽度,不再常驻独立的 Runtime 专属功能行。
- 输入框原生支持 `Ctrl+V`:文本直接进入草稿,系统截图和从文件管理器复制的一个或多个支持文件加入本次消息附件。文件粘贴与上传按钮共用读取、解析进度和数量限制;读取期间阻止重复导入及发送,错误就地关联输入框和附件按钮。具体类型、大小及批次失败规则见[聊天附件要求](./docs/features/document-processing/prd.md#321-聊天附件入口)。默认工具栏不提供独立“读取剪贴板”“截取当前屏幕”和“选择应用窗口”入口。
- “Enter 发送 · Shift+Enter 换行 · Ctrl+V 粘贴文件、图片或文本”等输入操作提示放在空输入框内部,作为主占位文案的次级行;不得在输入框下方单独占用第二行。输入框下方只保留一行当前模式、安全边界或全局快捷键说明。
- 输入操作提示不能替代表单的可访问名称,输入框始终保留持久的程序化标签。
- 空对话展示可执行的起始建议,发送失败保留输入并提供重试。
- 消息流的滚动范围必须使用已渲染消息的真实高度。长会话可以按批次只挂载最近消息并由用户
@@ -664,8 +690,9 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
成果查看器,不撑高列表,也不显示 Job/Run 树。
- “新建定制任务”使用共享 Modal,明确选择当前或新 Conversation。聊天入口默认当前
Conversation,Task Center 入口默认新 Conversation;选择必须持续可见。
- 创建 Modal 默认 Execute,并允许主动切换 Ask。Execute 持续显示实际 Runtime、Project、
工作目录、工具和审批摘要;不支持工具时不得静默降级。
- 定时消息创建 Modal 使用关联 Conversation 的 Runtime 与 Ask/Execute 模式,不再提供独立
模式开关。未运行计划的详情显示会话当前模式,已运行任务显示该次执行记录的模式;
不得用旧计划字段表示下一次执行权限。不支持工具时不得静默降级。
### 13.6 运行记录
@@ -688,6 +715,8 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- 左侧“魔法笔记”入口可显示未完成待办数量。数量为 0 时隐藏徽标,超过 99 时显示 `99+`,可访问名称仍说明精确数量;勾选、取消勾选以及 Agent / MCP 对笔记清单的写入都必须刷新数量。
- 未完成待办徽标的高度不得超过主导航图标;数量从 0 变为可见或再次隐藏时,主导航项行高和相邻菜单位置必须保持不变。
- 在待办面板勾选或取消勾选后,当前缓存中的源笔记正文必须立即同步对应清单状态,不得依赖离开工作区后重新加载。
- Agent / MCP 或其他入口成功写入笔记后,已打开的工作台自动更新笔记列表、当前详情、待办及来源记录,不增加刷新按钮。保留仍存在的笔记与待办选择、搜索和筛选,以及未保存的标题、输入区和记录编辑草稿;普通后台刷新不重建编辑器。
- 外部删除当前笔记时,无草稿则选择剩余笔记;有草稿则保留编辑区并提示先复制草稿。外部删除正在编辑的记录时,同样保留该编辑器并明确提示记录已删除;保存失败不得清空草稿。
- 当前记录草稿非空时,切换笔记、待办或内容面板必须先在编辑器旁就地确认;继续编辑时保留草稿并恢复编辑焦点,只有明确选择放弃后才切换。
- 创建、保存、更新、删除和 AI 评论完成等短期结果进入应用级通知,不在编辑区或列表上方堆放页内通知。
- 标题或正文校验、删除确认、同步进度和可就地恢复的错误仍靠近对应编辑器或操作呈现。
@@ -695,6 +724,8 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
### 13.8 设置中心
- 全页设置使用固定标题区、左侧分类导航和独立滚动的内容区。右上角关闭按钮是离开设置中心的稳定入口。
- 新建项目与项目设置弹窗固定标题、关闭按钮和底部操作区,仅表单内容滚动;滚动条不进入标题区,关闭按钮保留至少 40 × 40px 的点击区域。
- 项目弹窗通过 Portal 挂载到 `document.body`,避免侧栏内的弹窗与主窗口标题栏拖动区域重叠时,Electron 截获关闭按钮的鼠标输入。
- 左侧分类导航在宽屏使用 `220px`,中等窗口使用 `196px`,窄窗口转为横向滚动;纵向滚动条仅在内容溢出时占用右侧空间,不在左侧创建镜像预留,选项与左侧可见边界保持默认内距。分类标题使用正文级字号,分类说明使用辅助字号;右侧内容区在可用空间内流式伸缩,最大宽度使用 `standard` 壳层的 `960px`,不得以页面专属较窄宽度压缩表单。
- 全页设置标题区与双栏内容使用共享 `--page-gutter`,标题、分类导航和内容区在同一页面边距基线上;标题继续使用标准 `PageHeader` 的底部分隔线与下内距。固定标题和双独立滚动区域不改变这些一级页面壳层规则;模态设置保留自身外框与标题边界。
- 设置中心不显示全局操作页脚,避免重复关闭入口和没有功能意义的整宽分隔线。
@@ -719,6 +750,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- OpenCode 的默认 Agent 使用原生下拉选择;Continue 预设编辑器允许管理名称、说明、启用的 Rules 以及 Prompt 名称、说明和正文,并可展开查看原生 Rules 与启用预设 Rules 的最终合并顺序。持久启停仍使用共享 Switch,添加与删除使用明确按钮和可访问名称。
- MCP 设置按“内置 MCP / 直连模型 / 自定义 MCP / 电脑控制”四个同级 `PageTabs` 组织。联网搜索保留在直连模型;内置浏览器归入内置 MCP,提供持久启停和 Runtime 分配,并明确说明其操作 GoodBuddy 隔离浏览器、不控制客户端已安装的浏览器,开启后可由已分配 Runtime 的 Execute 直接使用,不逐次询问。该开关只控制 Agent 工具访问,关闭时不得禁用浏览器工作栏中的用户手动导航、返回、刷新、停止加载、交互或关闭操作。尚未生效的命名浏览器配置不得显示在界面中,“电脑控制”只显示实际操作客户端电脑的能力。内置 MCP 卡片与 Skills 一样提供持久启停和 Runtime 分配;直连模型、GoodBuddy 管理的 OpenCode 与 Continue 默认选中且可调整,DeepSeek Harness 必须以置灰、未选择和“暂不支持”文案持续显示,不能呈现为可保存的分配。魔法笔记 MCP 的自身启停与平台功能依赖分别显示,依赖未开启时保留用户配置并说明当前不会加载。
- MCP Server 测试结果在同一展开卡片中分组显示 Tools、Prompts 和 Resources 的支持状态、数量与有界元数据;Prompt 参数标明必填项,Resource 只显示 URI、名称、类型和说明,不读取或渲染 Resource 内容。
- 内置 MCP 中的“图片生成与编辑”是自动管理例外:该行始终可见,标记“内置 · 自动管理”,不提供独立启停、分配保存、移除或凭据编辑。直连 Model、OpenCode、Continue、DeepSeek Harness 的分配只由已保存图片模型的“允许 AI 在会话中调用”设置推导;至少一个开启则全部勾选,没有开启模型则全部取消。使用禁用的原生 Checkbox、Runtime 标签及关联说明,保留可聚焦的“前往图片模型设置”导航;远程适用范围通过 GoodBuddy Agent 说明持续可见。Ask、离线、无工具能力或调用错误不改变勾选。图片模型默认关闭会话调用;默认生图模型独立于普通默认连接,关闭或删除后保留失效选择并提示改选,不自动回退。
### 13.9 文档解析设置
@@ -737,7 +769,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- DOC、XLS、PPT 等旧格式通过受控转换 Provider 生成新式 Office 文档和 PDF。转换子进程必须禁用宏和网络,限制输入、输出、内存、超时与临时目录,并在关闭或取消时清理。
- OCR 来源使用“本地模型 / 远程服务”互斥选择。选择本地后显示模型下载、模型下拉选择和本地运行参数;选择远程后显示 MinerU、PaddleOCR-VL 等服务连接配置。未实现的远程服务入口保持可读但禁用,不再增加与来源选择重复的“隐私与云端处理”授权区。
- 用户配置并保存远程 OCR 服务即表示选择该处理路径,不再逐场景重复询问。界面仍须明确显示当前服务名称、处理范围和远程属性,API 密钥只保存在主进程加密设置中,未选中远程服务时不得上传文档。
- 解析结果使用统一文档结构,至少保留文档标题、来源格式、页码或工作表定位、正文块、置信度、处理方式和警告。聊天附件对结果做有界截断,知识库使用完整结果分块和索引。
- 解析结果使用统一文档结构,至少保留文档标题、来源格式、页码或工作表定位、正文块、置信度、处理方式和警告。聊天附件保留完整解析文本,超过暂存容量时报错;知识库使用完整结果分块和索引,具体要求见[文档处理 PRD](./docs/features/document-processing/prd.md#32-场景工作流)。
- 测试结果显示文件类型、页数、实际工作流、提取字数、OCR 页数、耗时和警告。测试文件不得自动进入聊天上下文或知识库。
## 14. 文案规则
@@ -795,6 +827,8 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
### 15.5 验收
执行与取证方法见 [Electron UI 自动化测试规范](./docs/quality/electron-ui-automation.md),包括真实 Portal、原生键盘、计算样式与 PNG 审阅;视觉 fixture 与完整生产 App 的结果须分别标注。
- [ ] 在浅色与深色主题下检查所有页面、弹窗、菜单、输入框、表格和空状态。
- [ ] 在大于等于 `1200px`、`960px`、`720px` 和小于 `720px` 的窗口宽度检查布局。
- [ ] 仅使用键盘完成导航、筛选、创建、编辑、确认和取消。
+1 -1
View File
@@ -1,6 +1,6 @@
{
"formatVersion": 1,
"agentVersion": "0.11.22",
"agentVersion": "0.13.0",
"protocol": {
"major": 2,
"minor": 0
+7
View File
@@ -141,6 +141,13 @@ function portableRequiredPaths(directory) {
'opencode',
'opencode.exe'
),
join(
directory,
'resources',
'runtimes',
'opencode',
'rg.exe'
),
join(
directory,
'resources',
+17 -4
View File
@@ -94,6 +94,8 @@ const portableRequiredFiles = [
'resources/tray-icon.png',
'resources/tool-environment/managed-python-artifacts.json',
'resources/runtimes/opencode/opencode.exe',
'resources/runtimes/opencode/rg.exe',
'resources/licenses/vscode-ripgrep-MIT.txt',
'resources/runtimes/opencode-config/node_modules/@opencode-ai/plugin/package.json',
'resources/runtimes/opencode-config/node_modules/@opencode-ai/plugin/dist/index.js',
'resources/runtimes/continue/package.json',
@@ -128,7 +130,8 @@ const platformDefinitions = {
supportedFormats: ['nsis', 'portable'],
unpackedPattern: /^win(?:-.+)?-unpacked$/u,
executable: [`${productName}.exe`],
runtimeExecutable: 'opencode.exe'
runtimeExecutable: 'opencode.exe',
ripgrepExecutable: 'rg.exe'
},
macos: {
builderFlag: '--mac',
@@ -141,7 +144,8 @@ const platformDefinitions = {
'MacOS',
productName
],
runtimeExecutable: 'opencode'
runtimeExecutable: 'opencode',
ripgrepExecutable: 'rg'
},
linux: {
builderFlag: '--linux',
@@ -149,7 +153,8 @@ const platformDefinitions = {
supportedFormats: ['AppImage', 'deb', 'rpm'],
unpackedPattern: /^linux(?:-.+)?-unpacked$/u,
executable: [packageJson.name],
runtimeExecutable: 'opencode'
runtimeExecutable: 'opencode',
ripgrepExecutable: 'rg'
}
}
const formatExtensions = {
@@ -1070,6 +1075,12 @@ function verifyUnpackedOutput(directory, options) {
'opencode',
definition.runtimeExecutable
)
const ripgrepExecutable = join(
resources,
'runtimes',
'opencode',
definition.ripgrepExecutable
)
assertFile(applicationExecutable, '应用主程序')
assertFile(join(resources, 'app.asar'), '应用 ASAR')
assertFile(join(resources, 'release-notes.json'), '版本更新说明')
@@ -1094,6 +1105,7 @@ function verifyUnpackedOutput(directory, options) {
'Remote Runtime 锁定清单'
)
assertFile(runtimeExecutable, 'OpenCode Runtime')
assertFile(ripgrepExecutable, 'ripgrep Runtime')
verifyOpenCodeConfig(
resources,
join(root, '.runtime-resources', 'opencode-config')
@@ -1125,7 +1137,8 @@ function verifyUnpackedOutput(directory, options) {
verifyHarnessPackage(resources, options)
for (const [filePath, label] of [
[applicationExecutable, '应用主程序'],
[runtimeExecutable, 'OpenCode Runtime']
[runtimeExecutable, 'OpenCode Runtime'],
[ripgrepExecutable, 'ripgrep Runtime']
]) {
const actualArchitecture = binaryArchitecture(filePath)
if (actualArchitecture !== options.arch) {
+3 -2
View File
@@ -50,7 +50,8 @@ const profileDigests = Object.freeze({
acp: "sha256:0e3764ab897258bc0234162357c2eefc3faeb9918080ae81b60e2084ed7996a6",
});
const fixedLimits = Object.freeze({
maximumPromptRuntimeMilliseconds: 10 * 60 * 1000,
// Zero leaves prompt duration to its requested deadline and cancellation.
maximumPromptRuntimeMilliseconds: 0,
maximumPromptInputBytes: 16 * 1024 * 1024,
maximumPromptOutputBytes: 8 * 1024 * 1024,
});
@@ -384,7 +385,7 @@ function validateManifestShape(manifest) {
}
if (
!Number.isSafeInteger(manifest.limits?.maximumPromptRuntimeMilliseconds) ||
manifest.limits.maximumPromptRuntimeMilliseconds < 1 ||
manifest.limits.maximumPromptRuntimeMilliseconds < 0 ||
!Number.isSafeInteger(manifest.limits.maximumPromptInputBytes) ||
manifest.limits.maximumPromptInputBytes < 1 ||
!Number.isSafeInteger(manifest.limits.maximumPromptOutputBytes) ||
+31
View File
@@ -0,0 +1,31 @@
const { spawnSync } = require('node:child_process')
const { rmSync } = require('node:fs')
const { tmpdir } = require('node:os')
const { join } = require('node:path')
const { buildSync } = require('esbuild')
const output = join(tmpdir(), `goodbuddy-browser-tabs-e2e-${process.pid}.cjs`)
try {
buildSync({
entryPoints: [join(process.cwd(), 'tests', 'browser-tabs-electron-e2e.ts')],
outfile: output,
bundle: true,
platform: 'node',
format: 'cjs',
target: 'node22',
external: ['electron']
})
const environment = { ...process.env }
delete environment.ELECTRON_RUN_AS_NODE
const result = spawnSync(require('electron'), [output], {
cwd: process.cwd(),
env: environment,
stdio: 'inherit',
timeout: 120_000
})
if (result.error) throw result.error
process.exitCode = result.status ?? 1
} finally {
rmSync(output, { force: true })
}
+93 -8
View File
@@ -33,28 +33,28 @@ function sha512Integrity(contents) {
return `sha512-${createHash("sha512").update(contents).digest("base64")}`;
}
async function lockedIntegrity(projectDir, packageName) {
async function lockedIntegrity(projectDir, packageName, version) {
const lock = JSON.parse(
await readFile(join(projectDir, "package-lock.json"), "utf8"),
);
const entry = lock.packages?.[`node_modules/${packageName}`];
if (
entry?.version !== opencodeVersion ||
entry?.version !== version ||
typeof entry.integrity !== "string"
) {
throw new Error(
`Missing locked ${packageName}@${opencodeVersion} integrity`,
`Missing locked ${packageName}@${version} integrity`,
);
}
return entry.integrity;
}
async function downloadPackage(projectDir, packageName, integrity) {
async function downloadPackage(projectDir, packageName, version, integrity) {
const cacheDirectory = join(projectDir, ".runtime-resources", "cache");
await mkdir(cacheDirectory, { recursive: true });
const archivePath = join(
cacheDirectory,
`${packageName}-${opencodeVersion}.tgz`,
`${packageName.replaceAll("/", "-").replaceAll("@", "")}-${version}.tgz`,
);
if (existsSync(archivePath)) {
const cached = await readFile(archivePath);
@@ -70,7 +70,7 @@ async function downloadPackage(projectDir, packageName, integrity) {
[
...npm.prefixArgs,
"pack",
`${packageName}@${opencodeVersion}`,
`${packageName}@${version}`,
"--ignore-scripts",
"--json",
"--pack-destination",
@@ -108,6 +108,68 @@ async function downloadPackage(projectDir, packageName, integrity) {
return archivePath;
}
async function prepareRipgrep(
projectDir,
targetDirectory,
platform,
architecture,
version,
) {
const packageName = `@vscode/ripgrep-${platform}-${architecture}`;
const integrity = await lockedIntegrity(projectDir, packageName, version);
const executable = platform === "win32" ? "rg.exe" : "rg";
const preparedPath = join(targetDirectory, executable);
const readyPath = join(targetDirectory, ".ripgrep-ready.json");
const identity = { packageName, version, integrity };
try {
const ready = JSON.parse(await readFile(readyPath, "utf8"));
if (
ready.packageName === identity.packageName &&
ready.version === identity.version &&
ready.integrity === identity.integrity &&
typeof ready.executableSha256 === "string" &&
(platform === "win32" || ((await stat(preparedPath)).mode & 0o111) !== 0) &&
(await sha256File(preparedPath)) === ready.executableSha256
) {
return;
}
} catch {
// Rebuild an incomplete or stale ripgrep cache.
}
const archivePath = await downloadPackage(
projectDir,
packageName,
version,
integrity,
);
const stagingDirectory = `${targetDirectory}.ripgrep-staging-${process.pid}`;
await rm(stagingDirectory, { recursive: true, force: true });
await mkdir(stagingDirectory, { recursive: true });
try {
await tar.x({
file: archivePath,
cwd: stagingDirectory,
strip: 1,
});
const sourcePath = join(stagingDirectory, "bin", executable);
const executableSha256 = await sha256File(sourcePath);
if (platform !== "win32") {
await chmod(sourcePath, 0o755);
}
await mkdir(targetDirectory, { recursive: true });
await rm(preparedPath, { force: true });
await rename(sourcePath, preparedPath);
await writeFile(
readyPath,
JSON.stringify({ ...identity, executableSha256 }),
"utf8",
);
} finally {
await rm(stagingDirectory, { recursive: true, force: true });
}
}
module.exports = async function prepareBundledRuntimes(context) {
const platform = context.electronPlatformName;
const architecture = architectureNames[context.arch];
@@ -126,6 +188,10 @@ module.exports = async function prepareBundledRuntimes(context) {
const projectPackage = JSON.parse(
await readFile(join(projectDir, "package.json"), "utf8"),
);
const ripgrepVersion = projectPackage.dependencies["@vscode/ripgrep"];
if (typeof ripgrepVersion !== "string" || !/^\d+\.\d+\.\d+$/u.test(ripgrepVersion)) {
throw new Error("Missing exact @vscode/ripgrep dependency version");
}
await writeFile(
join(projectDir, "out", "main", "package.json"),
`${JSON.stringify(
@@ -140,7 +206,7 @@ module.exports = async function prepareBundledRuntimes(context) {
)}\n`,
"utf8",
);
const integrity = await lockedIntegrity(projectDir, packageName);
const integrity = await lockedIntegrity(projectDir, packageName, opencodeVersion);
const targetDirectory = join(projectDir, ".runtime-resources", architecture);
const readyPath = join(targetDirectory, ".ready.json");
const executable = platform === "win32" ? "opencode.exe" : "opencode";
@@ -161,13 +227,25 @@ module.exports = async function prepareBundledRuntimes(context) {
((await stat(preparedPath)).mode & 0o111) !== 0) &&
(await sha256File(preparedPath)) === ready.executableSha256
) {
await prepareRipgrep(
projectDir,
targetDirectory,
platform,
architecture,
ripgrepVersion,
);
return;
}
} catch {
// Rebuild an incomplete or stale runtime cache.
}
const archivePath = await downloadPackage(projectDir, packageName, integrity);
const archivePath = await downloadPackage(
projectDir,
packageName,
opencodeVersion,
integrity,
);
const stagingDirectory = `${targetDirectory}.staging-${process.pid}`;
await rm(stagingDirectory, { recursive: true, force: true });
await mkdir(stagingDirectory, { recursive: true });
@@ -194,4 +272,11 @@ module.exports = async function prepareBundledRuntimes(context) {
} finally {
await rm(stagingDirectory, { recursive: true, force: true });
}
await prepareRipgrep(
projectDir,
targetDirectory,
platform,
architecture,
ripgrepVersion,
);
};
@@ -293,11 +293,14 @@ Runtime 边界完成,用户可见结果通过所属 Task 汇入关联 Conversa
- 普通消息在发送时冻结 Runtime、工作模式、专家/团队、知识范围和附件上下文,再以
`source=user` 入队;附件内容使用有界序列化保存,应用重启后仍可恢复。
- 到期或手动启动的 Scheduled Task 先建立 `schedule_run`,再以 `source=schedule` 进入同一
队列。Scheduler 不再绕过队列直接调用 Runtime。
队列,只派发任务要求和关联会话身份。执行时读取会话最新历史和配置,不保存任务上下文快照。
- Main 对每条 Conversation 只保留一个活动请求或 Renderer 派发保留位。默认按 FIFO 认领;
全局 Scheduled Task 执行仍受最多 4 项并发限制。
- 用户消息由 Main 派发给 Renderer,由 Renderer 建立用户消息和流式助手消息后调用
`agent.run`;Scheduled Task 由 Main 直接执行。两条路径共享同一 Conversation 活动锁。
Scheduled Task 不另设执行器或并发计数。
- 两种来源均由 Main 派发给 Renderer,建立普通用户消息和流式助手消息后调用 `agent.run`。
Renderer 加载会话及设置后才声明队列可派发。SSH 项目同样通过该入口选择远程 Runtime,
使用普通请求的上下文、工具范围、取消和恢复流程。
- `schedule_run.id` 作为普通请求 ID;普通请求终态同步该次计划运行与稳定产品 Task 的状态。
尚未接受的派发可释放重试;已经接受的运行在重启后按普通 Task 状态结算或恢复,不重新发送。
- 当前执行到达终态后再认领下一项。删除只移除尚未执行的项;“立即中断并插入”取消当前
请求并把所选项设为下一项,不重排其他项。
- 每条 Conversation 最多保留 20 个用户可提交的待执行项。启动时将未完成的派发恢复为
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 版本 | 0.2 |
| 日期 | 2026-09-09 |
| 适用范围 | GoodBuddy 桌面端、ShareServer、网关间联邦协议 |
| 桌面端设计 | [设备发现与共享](../features/device-sharing/README.md) |
| 服务端设计 | [ShareServer](../../../goodbuddy-shareserver/docs/features/share-server/README.md) |
@@ -26,7 +26,7 @@
1. **GoodBuddy Desktop** 在设置中提供“设备发现与共享”,默认使用局域网发现与直连,也可
由用户额外连接一个或多个 ShareServer。
2. **ShareServer** 是独立部署、独立升级的 Web 服务,提供组织、设备、能力、权限、更新、
联邦、中继和审计管理。它不运行在 Electron Main 中,也不由桌面端代为启动。
联邦、中继、Office 编辑和审计管理。它不运行在 Electron Main 中,也不由桌面端代为启动。
### 2.2 能力优先,来源可见
@@ -55,6 +55,17 @@ ShareServer 负责身份、目录、策略、任务路由、更新和审计。
直接建立;网络条件不允许时,可按组织策略使用 ShareServer 中继。是否允许中继、正文是否
端到端加密、是否允许跨组织,由本次任务冻结的策略共同决定。
### 2.6 Office 编辑是 ShareServer 托管服务
ShareServer 可以把 ONLYOFFICE Docs、工作副本存储、保存回调和编辑会话作为一个可选部署
能力提供给 Desktop。Desktop 只连接用户已明确配置的 ShareServer,不直接配置 Document
Server,也不在本机启动服务。Office 文档内容会由 ShareServer 和编辑引擎处理,因此不能沿用
“控制面默认不读取正文”的数据面假设;创建编辑会话前必须明确显示服务端、数据去向和保留期。
编辑后的本地或 SSH 源文件仍由 Desktop 或远程 GoodBuddy Agent 持有和原子回写。ShareServer
是工作副本及服务端保存检查点的权威,不是源文件最终写入成功的权威。完整设计见
[Office 协同编辑](../features/office-document-editing/README.md)。
## 3. 术语
| 术语 | 定义 |
@@ -71,6 +82,7 @@ ShareServer 负责身份、目录、策略、任务路由、更新和审计。
| `Task` | 一次远程能力调用及其输入、执行、输出和终态 |
| `Package` | 可下载到本机安装的签名能力工件 |
| `FederationTrust` | 两个组织网关之间经管理员确认的双边信任关系 |
| `DocumentEditSession` | ShareServer 托管的单文档短期编辑会话、工作副本和保存检查点 |
“网关”是 ShareServer 的网络角色;产品名称为 `ShareServer`,独立仓库为
`goodbuddy-shareserver`。
@@ -84,6 +96,10 @@ GoodBuddy Desktop A <-- mDNS 发现 + 配对后 TLS 直连 --> GoodBuddy Desktop
单组织:
GoodBuddy Desktop A -- 出站连接 --> ShareServer <-- 出站连接 -- Provider Device B
Office 编辑:
GoodBuddy Desktop --> ShareServer --> ONLYOFFICE Docs
└-------> 临时工作副本与保存检查点
跨组织:
Desktop A --> ShareServer A <== 联邦信任 ==> ShareServer B <-- Provider Device B
```
@@ -121,6 +137,7 @@ Desktop A --> ShareServer A <== 联邦信任 ==> ShareServer B <-- Provider Devi
- 计算组织强制策略,签发短期任务授权。
- 路由任务、维护在线状态,并在允许时中继数据。
- 管理签名包、发布通道、更新策略和撤销。
- 可选管理 Office 编辑会话、ONLYOFFICE 接入、工作副本、保存回调、保留和清理。
- 建立双边联邦关系并限制跨组织可见性与调用。
- 保存有界审计元数据、用量和管理操作。
@@ -339,6 +356,16 @@ created/awaiting_approval/queued/connecting -> rejected | expired | cancelled
- 未识别字段在声明允许扩展的位置忽略,安全与授权枚举中的未知值一律拒绝。
- 联邦只交换双方共同支持的能力和策略表达式,不降低任一方策略。
### 11.5 Office 编辑协议
Office 编辑使用独立的 HTTPS 会话、文件和事件 API,不复用普通远程能力 Task 表达长生命周期
编辑器。Desktop 通过设备凭据创建会话和传输源文件;编辑器使用一次性启动授权;ONLYOFFICE
使用独立短期 JWT 获取工作副本并提交保存回调。三类凭据使用不同 audience,不能互换。
文档页签可以长期存在,但启动 URL、文件 URL、callback token 和编辑 Cookie 必须短期、单用途
并可撤销。跨端会话、保存和冲突规则见
[Office 协同编辑技术设计](../features/office-document-editing/technical-design.md)。
## 12. 数据与审计
### 12.1 最小审计事件
@@ -358,6 +385,8 @@ created/awaiting_approval/queued/connecting -> rejected | expired | cancelled
- 桌面端是本地配对、本机共享偏好和本地活动记录的权威。
- ShareServer 是组织成员、设备注册、组织策略、发布目录和组织审计的权威。
- ShareServer 是 Office 工作副本、服务端保存检查点、编辑会话和保留清理的权威;Desktop 或
远程 GoodBuddy Agent 是源文件是否成功回写的权威。
- 能力提供者是运行中任务及结果是否产生的权威。
- 联邦两端各自保存本组织审计,不复制对方完整成员目录或内部策略。
@@ -376,6 +405,10 @@ ShareServer 可以部署到公网,因此公网连接、外部组织和服务
8. 密钥只进入对应安全存储,诊断和审计使用固定字段与脱敏错误码。
9. 限制握手、清单、帧、文件、并发、速率、任务时长和审计查询大小。
10. 撤销设备、用户、Grant、Publication、Package 或 FederationTrust 后停止签发新令牌。
11. Office 编辑器使用固定 origin 和隔离会话,不获得 Electron、文件系统、Shell、Runtime 或
普通 ShareServer 管理 API;Document Server 默认禁止任意出站网络和未批准插件。
12. Office 文件正文由服务端实际处理,组织策略必须定义允许格式、大小、保留和内容访问角色,
Desktop 在上传前显示这些事实。
## 14. 建议领域实体
@@ -401,6 +434,8 @@ PackageRelease
FederationTrust
AuditEvent
Revocation
DocumentEditSession
DocumentCheckpoint
```
共享协议只定义跨端 DTO 和状态,不规定桌面 SQLite 与 ShareServer 数据库使用相同表结构。
@@ -442,6 +477,10 @@ ShareServer 不反向依赖桌面端 `src/shared`,构建和运行不要求桌
- [ ] 跨组织调用同时满足两端策略;联邦撤销后不能创建新任务。
- [ ] 审计默认只保存必要元数据,不保存正文、文件、密钥或认证信息。
- [ ] ShareServer 可独立部署和升级,不由 Electron 启动,也不影响桌面端离线使用。
- [ ] 启用 Office 服务后,Desktop 可在多实例文档页签中编辑 DOCX、XLSX 和 PPTX;保存回调、
源文件冲突或网络故障均不会破坏原文件。
- [ ] Office 会话的启动、文件和 callback 凭据不可互换,过期或撤销后不能重用;临时工作副本
按策略清理,默认审计不保存文档正文或 AI 选区内容。
## 17. 相关设计
@@ -453,5 +492,6 @@ ShareServer 不反向依赖桌面端 `src/shared`,构建和运行不要求桌
- [ShareServer 功能逻辑](../../../goodbuddy-shareserver/docs/features/share-server/logic-design.md)
- [ShareServer 管理界面](../../../goodbuddy-shareserver/docs/features/share-server/ui-design.md)
- [ShareServer 技术设计](../../../goodbuddy-shareserver/docs/features/share-server/technical-design.md)
- [Office 协同编辑](../features/office-document-editing/README.md)
- [SSH 远程主机与 Agent](../features/remote-host/README.md)
- [GoodBuddy 统一界面设计系统](../../UI-DESIGN.md)
+75
View File
@@ -0,0 +1,75 @@
# 数据库迁移与大数据升级指南
本文约束已发布用户数据的数据库升级。具体字段、存储表示和功能行为由对应功能文档负责;
本文不建立通用迁移框架、回滚引擎或独立状态日志。
## 先确认问题和数据边界
1. 先只读统计文件大小、WAL、页数、空闲页、表与字段的字节数,不先删除或压缩数据库。
2. 区分有效内容、重复存储、索引和已释放但未回收的空间。空闲页少时,单独
`VACUUM` 不能消除重复记录。
3. 查清实际写入、读取、删除、重放与恢复入口。修改 Runtime 相关持久化时,同时检查
本机和 GoodBuddy Agent 的桌面路径,不假设一个入口覆盖另一个入口。
4. 明确用户必须保留的信息、稳定 ID、顺序、时间、引用与远程去重字段。
分支内未发布、可安全重建的内部记录不需要兼容机制。
## 实现原则
- 先修新增数据路径,再修历史数据。历史转换与新写入尽量使用同一套转换函数。
- 普通 schema 变更使用既有 `PRAGMA user_version` 和 SQLite 事务;
已升级 schema 不允许旧客户端强行打开,不通过回写版本号支持降级。
- 大数据转换不得同步阻塞 Electron Main 或 Renderer。放到工作线程,在业务连接
打开前处理,并使用应用的共享字体、主题与页面组件显示进度和可执行的失败提示。
- 根据实测设置有界读取批次与缓存,批次提交后再报告进度。保持 SQLite 事务与
checkpoint,不靠关闭同步落盘、取消约束或一次性读入整库提速。
- 从已提交数据直接重试和还原即可完成的迁移,不增加 durable snapshot、journal、
receipt 或多阶段恢复状态。退出及失败至少回滚未提交批次,保留已完成部分。
- 差量编码必须能精确还原原始事件。更新内容、删除或重排都要覆盖;涉及远程来源时,
去重、冲突判断、消息投影和恢复序号不得失效。
- 释放数据库页和归还文件系统空间是不同步骤。先减少实际存储内容,再执行需要的
空间回收;保留足够临时空间,磁盘不足时明确失败并允许重试。
- SQLite 同库写入是串行的。是否增加读取/转换并行,应由实际读取、解析、写入、
提交与 checkpoint 的耗时决定,不把增加写线程当作默认优化。
## 验证与备份
### 合成回归
覆盖正常转换、无变化记录、重复打开、内容修订、失败事务和退出续跑。数据库大小问题
必须同时验证新增写入的增长量、旧记录可还原性以及文件实际缩小,而不只检查 schema。
对象比较必须覆盖真实生产形状和 JSON 往返,尤其是可选字段值为 `undefined` 时与字段缺省
的差异。再次修复已发布迁移产生的数据时,提高目标 schema,并覆盖已升级用户的入口;
不能只处理尚未迁移的旧库。
### 真实旧库
1. 关闭占用源数据库的应用,确认 WAL 状态。没有 WAL 时可复制已关闭的数据库;
有活动 WAL 时使用 SQLite 一致性备份机制,不只复制主文件。
2. 备份到空间足够的位置,记录字节数并校验 SHA-256。备份和测试副本分离,
不直接在唯一原件或唯一备份上试跑。
3. 使用当前源码或用户要求的 portable 正常启动入口执行升级,不用手工 SQL 代替
产品升级过程。退出一次后重新启动,验证已提交数据可以继续处理。
4. 比较升级前后消息、任务、成果等用户表的内容;对转换事件逐条还原后比较原始内容、
ID、顺序、时间和去重来源,不只比较行数。日志只能包含统计和断言结果,不记录正文。
5. 运行数据库完整性检查,记录主文件和 WAL 的实际字节数、回收比例、耗时、平台、
源 schema 与目标 schema。确认原件和备份未被改写。
6. 运行 `npm test`、`npm run typecheck`、`npm run lint`;生产构建变化按开发验证规则
检查,发布准备则遵循[发布手册](./release-runbook.md),不重复本地打包。
7. 用真实模型模拟正常用户任务,允许主代理与子代理按需调用工具、修改独立测试项目、
运行测试和处理失败;只读纯文字模型调用不能证明工具事件增长正常。记录实际提供商
请求数与统计范围,不把 Runtime 用量上报数当成全部 HTTP 请求数。
## 发布与维护
发布说明应告知升级范围、可能耗时、退出重试、额外磁盘空间和降级边界。不得把一个测试库
的回收比例当作所有用户都能获得的保证。功能文档记录具体转换规则,进度文档记录实际
证据和未完成项;未经验证的阶段不能标为完成。
### 已有实例
- Desktop 0.13.1:子任务累计过程重复保存导致数据库膨胀,转换到 schema 34 的差量事件
表示,并回收空间。具体规则见
[执行记录存储与升级回收](../features/assistant-workbar/execution-history-storage.md),
数据保留与真实旧库验证见[工作栏进度](../features/assistant-workbar/progress.md)。
- 当前未发布源码:schema 35 同时整理旧快照和 schema 34 的重复工具块,工具字段按
JSON 可保存的值比较,不因 `undefined` 被省略而误判变化。
+4
View File
@@ -110,6 +110,10 @@ below.
and are separate from desktop `v${package.version}` releases. Confirm the
exact Agent release commit and tag with the user before creating or pushing
either.
- Store approved bilingual Agent release notes in
`resources/agent-release-notes/<agentVersion>.md`. The Agent publication job
reads that exact file instead of generating a generic release description;
keep it aligned with the approved version and source range.
- The tagged commit must be reachable from protected `main`. The three native
jobs build Linux x64, Linux arm64, and Darwin arm64 packages from the same source and locks;
the catalog job rejects missing architectures, changed bytes for an existing
+54
View File
@@ -0,0 +1,54 @@
# Desktop Window Recovery
This is recovery hardening for intermittent blank windows, not an established
diagnosis of their cause.
- `src/renderer/src/main.tsx` wraps the locale provider, App and storage-upgrade
page in `AppErrorBoundary`. A React render/lifecycle failure replaces the tree
with a blocking recovery page and an explicit Reload button. It uses the saved
UI language (including system preference resolution), shared theme tokens and
primary-button styling, without depending on the failed application providers.
- Reload refreshes the current document, retaining its URL and storage-upgrade
query. It can lose unsaved input. There are no automatic React retries.
- `src/main/window.ts` listens for `render-process-gone`. Abnormal exits open one
native dialog at a time, using the OS locale because renderer language storage
is unavailable to Main. Reload occurs only on explicit confirmation. Cancel
leaves the window unchanged; persistent failures require quitting and reopening
GoodBuddy. Clean exits, shutdown and destroyed windows do not trigger recovery.
- Main-frame load failures are logged; aborted navigation and subframe failures
are ignored. Initial load promises are caught, without automatic load retries
or a new load-failure UI.
- Native crashes and main-frame load failures use the existing bounded desktop
diagnostics store (`desktop.renderer.gone`, `desktop.renderer.load-failed`,
stage `renderer`). Persistent records retain fixed summaries rather than raw
errors. Crash reason/exit code and React errors/component stacks go to their
respective process consoles. There is no new diagnostic IPC.
The boundary does not catch module evaluation/startup failures before React
mounts, event-handler exceptions, asynchronous rejections, a hung renderer, or
main-process failure. Reload does not establish that a repeated underlying error
has been fixed. Agent runtimes and remote execution are unchanged.
Targeted regressions are in `AppErrorBoundary.test.tsx`, `window.test.ts` and
`desktop-diagnostics.test.ts`. Electron API mocks verify decision paths but do
not establish native dialog appearance or full application recovery.
Validation on Windows, 2026-09-15:
- Targeted Vitest run: 20 tests passed across the three files above.
- `npm test`: 359 files passed, 9 skipped; 4,225 tests passed, 67 skipped.
- `npm run typecheck`, `npm run lint` and `git diff --check` passed.
- An isolated Electron fixture bundled the current boundary, shared styles and
production window module. Both languages and themes at requested content sizes
1280x800, 960x720, 720x640 and 640x420 passed visibility, focus and horizontal
overflow checks. Windows scaling rounded actual widths down by one CSS pixel.
The 16-image overview and Chinese/light and English/dark short-window originals
were visually reviewed; no clipping or focus-style defect was found.
- A native Space key activated the recovery button and reloaded the document.
Two actual renderer crashes verified confirmed reload with URL retention and
cancellation without retry. Only native dialog responses were mocked; native
dialog appearance, full App business-state recovery and other OSes were not
verified. No model calls were made by this fixture.
- The first temporary Electron driver timed out during ESM startup. Removing its
top-level readiness await fixed the driver; the subsequent run passed. This
was a test-driver failure, not evidence of the reported application root cause.
@@ -1,8 +1,23 @@
# 应用导航与系统工具入口
本功能整理 GoodBuddy 主侧栏的入口层级:系统入口保持固定,应用占用主导航,系统工具从
侧栏底部快捷打开助手工作栏。该功能同时为后续插件应用和插件工具预留统一的入口类型,但
不包含插件市场的安装与运行机制。
本功能整理 GoodBuddy 主侧栏的入口层级:系统入口保持固定,内置应用通过最底部应用中心
访问,并可常驻主导航;系统工具由右侧工作栏 Tab 承载。应用商店是后续功能,首期不提供
商店入口、推荐位或安装流程。
当前分期见 [PRD 第 8 节](./prd.md#8-分期),实施步骤见[技术设计第 11 节](./technical-design.md#11-实施顺序)。
本轮更新实施计划与范围,旧版功能要求、验收和配套设计仍需按新计划同步,此项列为实现前的第一步。
新增计划包括 [私有化市场与 YAML 交换](./prd.md#fr-15-私有化市场与-yaml-交换后续):
通过市场 URL 获取应用,支持 `.yml` 导入导出,未来由 ShareServer 提供私有目录与分发。
[本机模型管理入口](./prd.md#fr-16-本机模型管理入口新增计划项)可独立于商店交付,拟作为
可常驻左侧的内置管理应用,区分受管进程、外部服务和进程内模型。两项均为需求记录,
未实现,相关后续验收见 [User Stories](./user-stories.md#7-后续需求场景)。
项目活动汇总独立于上述应用中心计划,范围见 [FR-14](./prd.md#fr-14-项目活动汇总)。
计数规则由[活动逻辑](./logic-design.md#9-项目活动汇总)定义,入口与级联菜单见
[活动界面](./ui-design.md#10-项目活动汇总),数据加载与会话跳转见
[技术设计](./technical-design.md#12-项目活动汇总)。此处“活动”指当前运行或待处理会话,
与用于审计的“运行记录”不同;已完成未读面板属于后续范围,本次未实现。
## 文档导航
@@ -13,6 +28,53 @@
| [功能逻辑设计](./logic-design.md) | 分类规则、入口状态、优先级与失败结果 |
| [UI 设计](./ui-design.md) | 侧栏信息架构、控件、文案、键盘和响应式行为 |
| [技术设计](./technical-design.md) | 现有模块映射、状态契约、持久化和实施顺序 |
| [实施进度](./progress.md) | 项目活动级联的生产实现与验证记录 |
| [应用中心交互 Demo](./navigation-demo.html) | 对比六个候选方案;配套 CSS 和 JS 仅服务原型 |
Demo 可直接在浏览器中打开,支持方案切换、浅深主题、添加、移除、排序和商店模拟。
六个方案共用本次页面会话内的应用配置,刷新后恢复默认。商店、资源指标和工具内容均为
演示数据,不执行真实安装或本机操作。窄屏上下排列侧栏与输入区,桌面展示完整应用布局。
这些方案用于讨论最底部应用中心;其中商店页、商店应用安装与 F 方案属于未来交互参考,
不纳入首期实现或验收。首期产品只提供内置应用;Demo 暂保留远期示例以便后续讨论。
底部输入区共用一个简化候选布局:附件、语音、Runtime、模式和发送位于主操作行,专家、
知识范围及 Runtime 专属选项通过“选项”展开,非默认选择显示为可移除标签。可体验文件名
附件、模拟语音、模板填入、原生命令选择、8 秒模拟回复、发送排队、立即插入、删除、停止,
以及上下文详情和模拟压缩。文件只读取名称,语音使用固定示例文本,不录音、不上传、不调用 AI。
这是输入区设计讨论用的原型,尚未改变正式产品或替代现有 UI 规范。
左上角项目选择器下方仅在有运行中或待处理会话时显示活动摘要,全部空闲时隐藏。点击摘要打开项目
列表,悬停或点击项目向右展开会话子菜单,点击会话才切换项目与对话。子菜单与项目列表
相接,项目行保留子菜单箭头,会话行只显示名称与状态。支持上下方向键选择、右键进入、左键或 Esc 返回,顶层 Esc 关闭;窄屏在同一浮层内
进入会话层,并提供返回按钮。页面上方可切换“有待处理、仅运行中、仅当前项目运行、全部
空闲”四种场景。项目与会话均为演示数据;该级联交互已接入正式产品活动入口,范围与
验证记录见[实施进度](./progress.md)。
按最新讨论,Demo 已移除左侧系统工具快捷区:文件、终端、浏览器和资源监控只在右侧
工作栏 Tab 中演示,不列入应用中心。上方正式文档仍保留原设计,待方案确定后同步。
六个方案共用“我的应用”区:通过 + 或“全部应用”选择常驻应用,编辑时可拖动或使用
上下箭头排序、移除入口。未常驻的已安装应用仍可从“全部应用”打开。商店包含“项目管理”
和“阅读空间”应用示例,支持仅安装或安装并常驻左侧菜单,常驻不表示后台运行。
常驻应用在本次页面会话内跨方案保持一致;“恢复默认应用”重置安装与常驻配置。
移除入口不会卸载应用或关闭当前页面,刷新后恢复默认。
魔法笔记提供应用中心“设置”、笔记页面“笔记设置”和全局设置演示入口,均打开同一设置
面板,共用本次页面会话内的配置。面板沿用现有选项:显示未完成待办数量、AI 评论方式
(即时/保存后自动/保存后手动)、AI 评论形式(长评 + 要点/长评/要点)。现有“显示
魔法笔记入口”在原型中与“常驻左侧菜单”统一,不额外增加功能启停开关。侧栏待办数为
固定的 3 项演示数据。修改即时生效于原型,不调用 AI 或写入真实应用设置。
| 方案 | 讨论重点 |
| --- | --- |
| A:紧凑入口 | 从底部向上展开应用列表 |
| B:应用宫格 | 在展开面板中展示应用图标、名称和常驻操作 |
| C:应用预览 | 用卡片说明应用用途 |
| D:搜索启动台 | 搜索打开能力,支持 Ctrl / Command K、方向键与 Enter |
| E:常驻管理 | 已常驻应用优先排列,可取消常驻或打开 |
| F:商店发现(未来) | 默认展开模拟商店,不纳入首期 |
D 参考 [Raycast](https://www.raycast.com/) 的搜索启动与扩展发现方式。早期方案参考过
Notion 收藏和 VS Code Profiles,当前版本已移除场景工具包与快捷区。
原型支持 URL 的 `#launcher`、`#favorites`、
`#scenes` 等片段直接打开指定方案。
## 术语
@@ -20,7 +82,7 @@
| --- | --- |
| 系统入口 | GoodBuddy 工作流程或审计所必需的固定入口,用户不能卸载、隐藏或改变类型 |
| 应用 | 在主内容区打开完整一级页面的功能,可显示在“当前对话”所在的主导航区 |
| 系统工具 | GoodBuddy 自带且不可卸载的辅助能力,包括文件、终端、浏览器和资源监控,从侧栏底部打开助手工作栏中的对应面板 |
| 系统工具 | GoodBuddy 自带且不可卸载的辅助能力,包括文件、终端、浏览器和资源监控,由右侧助手工作栏 Tab 承载 |
| 插件应用 | 未来由插件提供的一级页面入口 |
| 插件工具 | 未来由插件提供的辅助面板或命令入口 |
| 快捷入口 | 只负责打开或聚焦能力,不复制该能力的界面和业务状态 |
@@ -87,13 +87,14 @@ type NavigationContribution =
| 工具 | 匹配实例存在 | 匹配实例不存在 | 上下文不可用 |
| --- | --- | --- | --- |
| 文件 | 激活单实例并保留其跟随或固定状态 | 创建跟随当前上下文的 `workspace` 单实例 | 打开面板并显示目录修复入口 |
| 文件 | 激活不可关闭的单实例并保留其跟随或固定状态 | 仅在迁移或布局修复时补回 `workspace` | 打开面板并显示目录修复入口 |
| 终端 | 激活当前执行空间最近的 `starting` 或 `running` 实例 | 创建一个绑定当前执行空间的实例 | 没有项目时使用本机 Home |
| 浏览器 | 激活单实例并保留其目标状态 | 创建跟随当前会话的 `browser` 单实例 | 打开面板并提示选择或创建对话 |
| 浏览器 | 激活绑定当前 Conversation 的最近使用实例 | 创建绑定当前 Conversation 的新实例 | 激活最近使用实例;不存在时创建待绑定实例 |
| 资源监控 | 激活 `resources` 单实例 | 创建本机 `resources` 单实例 | 部分指标不可用时显示原因并保留其余指标 |
文件和浏览器按以下优先级处理目标:已有有效固定目标、跟随模式的当前上下文、新实例的默认
上下文。固定目标失效时显示该目标的修复状态,不回退到当前项目、默认工作区或当前会话。
文件保持单一实例原有绑定。浏览器快捷入口按当前 Conversation 匹配最近使用实例,而工作栏
“+”始终新建浏览器实例。固定目标失效时显示该目标的修复状态,不回退到当前项目、默认
工作区或当前会话。
`closing`、`exited`、`interrupted` 和 `failed` 终端不属于可复用实例。当前执行空间只有这些
终端时,快捷按钮创建新实例并保留原 Tab 的历史状态。用户需要额外的并行终端时使用工作栏
@@ -154,7 +155,7 @@ warming/loading/live/partial/error -> inactive
## 7. 逻辑完整性评估
首期规则已覆盖固定入口、应用显隐、保存失败、系统工具单实例与多实例差异、本机 Home、
首期规则已覆盖固定入口、应用显隐、保存失败、系统工具匹配与显式多实例差异、本机 Home、
托管 SSH、无活动会话、目录失效、资源部分可用、采样停止、权限边界、键盘和窄窗口。其他
工具面板内部的取消、超时、
连接恢复和固定目标失效继续由助手工作栏负责,本功能不复制这些状态机。
@@ -165,3 +166,31 @@ warming/loading/live/partial/error -> inactive
- 插件应用是否允许用户排序,以及固定系统入口之间允许的排序范围。
这些决策不影响首期四个系统工具的固定入口和内置应用显隐实现。
## 9. 项目活动汇总
对应 [FR-14](./prd.md#fr-14-项目活动汇总),按 Conversation ID 聚合,每条会话最多一行。
数据来源为活动会话 ID、会话中的流式消息及待回答/待审批请求,以及全部可见的运行中或
等待处理 Task;不能只取当前项目、当前搜索结果或每条会话的最新 Task。
| 优先级(高到低) | 条件 | 汇总类别 |
| --- | --- | --- |
| 待回答 | 消息有待回答问题 | 待处理 |
| 待审批 | 消息有待审批请求 | 待处理 |
| Task 等待处理 | Task 为 `waiting_approval`,尚无更具体的消息状态 | 待处理 |
| 运行中 | 活动会话 ID、`streaming` 消息或运行中 Task | 运行中 |
同一会话只采用最高优先级状态;Task 的 `waiting_approval` 也可能对应提问,不能直接
标成待审批。“待处理”和“运行中”数量互斥,全局总数为两者之和;项目计数使用相同结果。
项目归属优先取会话元数据,缺少会话时取 Task 元数据;无项目归属的活动只计入全局汇总。
历史排队项、仅待调度的 Task、终态 Task 和未读标记本身不构成活动。活动结束且没有其他
运行或等待条件时移除该行,不能因历史任务或未读状态继续计数。
级联菜单先按项目归属聚合活动行,沿用本地、SSH Host、通道的稳定项目顺序;缺少项目
元数据的归属组及“未归属项目”组放在已知项目之后。无归属组只用于访问这些会话,不增加
项目计数。每个项目内先列待处理会话,再列运行中会话,同类别保留来源顺序。
展开项目只改变菜单层级,不导航。选中项目的活动全部结束后返回项目列表;全局活动结束后
关闭菜单并隐藏摘要入口。后台更新保留仍存在的焦点项,删除焦点项时将焦点
移至当前层可用项。关闭菜单后,新的活动只恢复摘要,不会自动重新打开菜单。
@@ -0,0 +1,133 @@
:root {
color-scheme: light;
--surface-canvas: #e8f0f5; --surface-raised: #fff; --surface-subtle: #f4f8fb;
--text-primary: #17263a; --text-secondary: #4f6278; --border-default: #cedce7;
--accent: #086fc7; --accent-subtle: #e8f4ff; --accent-selected: #a7cfea;
--font-family-ui: "Inter Variable", "PingFang SC", "Microsoft YaHei UI", "Noto Sans SC Variable", system-ui, sans-serif;
--radius-control: 10px; --radius-card: 14px;
--warning: #865200;
}
[data-theme="dark"] { --warning: #f2bd66; }
.project-lab { max-width: 1440px; margin: 0 auto 12px; display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; color: var(--text-secondary); font-size: 12px; }
.project-lab select { font: inherit; color: var(--text-primary); background: var(--surface-raised); padding: 4px 8px; border: 1px solid var(--border-default); border-radius: var(--radius-control); }
.project-entry { position: relative; margin-bottom: 12px; }
#project-trigger { width: 100%; display: flex; align-items: center; gap: 8px; text-align: left; border: 1px solid var(--border-default); }
#project-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.project-waiting { color: var(--warning); font-weight: 600; }
#activity-trigger { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin: -8px 0 12px; padding: 4px 8px; color: var(--text-secondary); font-size: 11px; text-align: left; }
#project-activity { position: fixed; inset: auto; margin: 0; padding: 0; border: 1px solid var(--border-default); border-radius: var(--radius-control); background: var(--surface-raised); color: var(--text-primary); max-width: calc(100vw - 32px); }
#project-activity:popover-open { display: flex; align-items: stretch; }
#project-activity > section { width: 264px; max-width: calc(100vw - 34px); max-height: min(480px, calc(100dvh - 32px)); overflow: auto; padding: 8px; }
#project-activity h3 { margin: 8px; font-size: 11px; font-weight: 500; color: var(--text-secondary); overflow-wrap: anywhere; }
#activity-project-list > p { padding: 8px; }
#activity-project-list button { display: grid; grid-template-columns: minmax(0, 1fr) auto; width: 100%; gap: 4px 8px; text-align: left; }
#activity-project-list button > span:first-child { overflow-wrap: anywhere; }
#activity-project-list small { grid-column: 1; font-size: 11px; color: var(--text-secondary); }
#activity-project-list button > span:last-child { grid-column: 2; grid-row: 1 / 3; align-self: center; }
#activity-project-list [aria-expanded="true"] { background: var(--accent-subtle); color: var(--accent); }
#activity-sessions { border-left: 1px solid var(--border-default); }
#activity-back { display: none; font-size: 12px; color: var(--accent); }
#project-activity.activity-left { flex-direction: row-reverse; }
.activity-left #activity-sessions { border-left: 0; border-right: 1px solid var(--border-default); }
.activity-compact.has-submenu #activity-projects { display: none; }
.activity-compact #activity-back { display: block; }
.activity-compact #activity-sessions { border: 0; }
#project-menu { position: fixed; inset: auto; margin: 0; width: min(300px, calc(100vw - 32px)); max-height: calc(100dvh - 32px); overflow: auto; padding: 8px; border: 1px solid var(--border-default); border-radius: var(--radius-control); background: var(--surface-raised); color: var(--text-primary); }
#project-list h3, #activity-list h3 { margin: 8px; font-size: 11px; font-weight: 500; color: var(--text-secondary); }
#project-list button { width: 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; text-align: left; }
.project-row-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
#project-list [aria-current="true"] { background: var(--accent-subtle); color: var(--accent); }
#project-list small { width: 100%; display: flex; gap: 12px; font-size: 11px; color: var(--text-secondary); }
#activity-list button { display: grid; grid-template-columns: minmax(0, 1fr); width: 100%; text-align: left; padding: 12px; }
#activity-list strong { font-weight: 500; overflow-wrap: anywhere; }
#activity-list small { grid-column: 1; color: var(--text-secondary); overflow-wrap: anywhere; }
.history button small { display: block; font-size: 11px; color: var(--text-secondary); }
[data-theme="dark"] { color-scheme: dark; --surface-canvas: #07131f; --surface-raised: #102335; --surface-subtle: #162c40; --text-primary: #edf7ff; --text-secondary: #b5c6d5; --border-default: #29445a; --accent: #72c5ff; --accent-subtle: #123b5b; --accent-selected: #28668f; }
* { box-sizing: border-box; }
body { margin: 0; padding: 24px 32px; font: 13px/1.6 var(--font-family-ui); background: var(--surface-subtle); color: var(--text-primary); }
button, input, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: transparent; border-radius: var(--radius-control); padding: 8px 12px; }
button:hover { background: var(--accent-subtle); color: var(--accent); }
button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
button:disabled { opacity: .45; cursor: default; }
svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; vertical-align: middle; flex-shrink: 0; }
p, h1, h2 { margin: 0; } h1 { font-size: 30px; font-weight: 550; margin: 12px 0; letter-spacing: -.8px; } h2 { font-size: 24px; }
.muted, .eyebrow { color: var(--text-secondary); }.eyebrow { font-size: 11px; letter-spacing: 1px; }
.lab-header, .design-note, .lab-footer { display: flex; align-items: center; justify-content: space-between; gap: 24px; max-width: 1440px; margin: auto; }
.lab-header strong { font-size: 16px; }.lab-header p { color: var(--text-secondary); font-size: 12px; margin-top: 4px; }
.variants { display: grid; grid-template-columns: repeat(3, 1fr); padding: 4px; border: 1px solid var(--border-default); border-radius: var(--radius-control); }
.variants button[aria-pressed="true"] { background: var(--surface-raised); color: var(--accent); box-shadow: 0 1px 3px var(--border-default); }
.design-note { margin: 24px auto 12px; font-size: 12px; }.design-note #variant-label { color: var(--accent); font-weight: 600; white-space: nowrap; }.design-note p { flex: 1; }.design-note > span:last-child { color: var(--text-secondary); font-size: 11px; }
.app { display: flex; max-width: 1440px; height: min(780px, calc(100dvh - 204px)); min-height: 590px; margin: auto; border: 1px solid var(--border-default); border-radius: var(--radius-card); overflow: hidden; background: var(--surface-raised); }
.sidebar { width: 278px; flex: 0 0 278px; background: var(--surface-canvas); display: flex; flex-direction: column; padding: 20px 12px 8px; border-right: 1px solid var(--border-default); }
.brand { display: flex; gap: 8px; align-items: center; padding: 0 8px 24px; }.brand > .muted { margin-left: auto; font-size: 11px; }.brand-mark { display: grid; place-items: center; width: 32px; height: 32px; background: var(--text-primary); color: var(--surface-raised); border-radius: 10px; font-weight: 650; font-size: 12px; }
.new-chat { border: 1px solid var(--border-default); background: var(--surface-raised); text-align: left; margin-bottom: 16px; }.new-chat span { margin-right: 8px; }
.navigation { display: grid; gap: 3px; }.navigation button, .fixed-entry button { display: flex; align-items: center; gap: 10px; text-align: left; padding: 9px 10px; }.navigation .selected { background: var(--accent-subtle); color: var(--accent); }
.history { flex: 1; min-height: 72px; overflow: auto; padding: 24px 0 8px; }.history .eyebrow { display: block; padding: 0 10px 8px; }.history button { display: block; width: 100%; text-align: left; color: var(--text-secondary); font-size: 12px; }
.fixed-entry { border-top: 1px solid var(--border-default); padding: 8px 0; }.fixed-entry button { width: 100%; }
.shortcuts { border-top: 1px solid var(--border-default); padding-top: 12px; max-height: 52%; overflow: auto; flex-shrink: 0; }.section-label { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; padding-left: 8px; font-size: 11px; color: var(--text-secondary); }.section-label button { font-size: 11px; padding: 4px 8px; }
.shortcut-list { display: grid; gap: 4px; }.shortcut-item { min-width: 0; }.shortcut-button { width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; }.shortcut-button.active { color: var(--accent); background: var(--accent-subtle); }.shortcut-button .icon-tile { display: grid; place-items: center; }.shortcut-button .item-name { font-size: 11px; }
[data-variant="dock"] .shortcut-list { grid-template-columns: repeat(5, minmax(0, 1fr)); background: var(--surface-raised); padding: 4px; border: 1px solid var(--border-default); border-radius: var(--radius-card); }
[data-variant="dock"] .shortcut-button { padding: 10px 0; flex-direction: column; gap: 4px; }[data-variant="dock"] .shortcut-button .item-name { display: none; }[data-variant="dock"] .shortcut-list.editing { grid-template-columns: repeat(3, minmax(0, 1fr)); }[data-variant="dock"] .editing .item-name { display: block; }
.store-link { margin-top: 8px; padding: 8px; display: flex; align-items: center; width: 100%; gap: 8px; text-align: left; font-size: 12px; }.store-link span:last-child { margin-left: auto; color: var(--text-secondary); }
[data-variant="grid"] .shortcut-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }[data-variant="grid"] .shortcut-button { flex-direction: column; gap: 4px; padding: 8px 4px; }[data-variant="grid"] .icon-tile { width: 36px; height: 36px; border: 1px solid var(--border-default); border-radius: var(--radius-control); background: var(--surface-raised); }
.featured { border: 1px solid var(--border-default); border-radius: var(--radius-control); padding: 12px; margin-top: 12px; background: var(--surface-raised); position: relative; }.featured p { font-size: 11px; color: var(--text-secondary); margin: 4px 0; }.featured .dismiss { position: absolute; right: 4px; top: 4px; padding: 0 8px; }.featured .feature-open { color: var(--accent); padding: 4px 0 0; font-size: 11px; }
[data-variant="cards"] .shortcut-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }[data-variant="cards"] .shortcut-button { background: var(--surface-raised); border: 1px solid var(--border-default); padding: 12px 8px; flex-direction: column; align-items: flex-start; }[data-variant="cards"] .shortcut-button .item-name { font-size: 12px; }.card-detail { font-size: 11px; color: var(--text-secondary); text-align: left; }.mini-meter { display: flex; gap: 3px; align-items: end; height: 24px; }.mini-meter i { display: block; width: 5px; background: var(--accent-selected); border-radius: 2px; }.mini-meter i:nth-child(3n) { background: var(--accent); }
.edit-actions { display: flex; justify-content: center; }.edit-actions button { padding: 2px 6px; font-size: 12px; }.sidebar-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding-left: 8px; font-size: 11px; }
.workspace { flex: 1; min-width: 0; display: flex; flex-direction: column; }.workspace-header { display: flex; justify-content: space-between; padding: 16px 24px; border-bottom: 1px solid var(--border-default); font-size: 12px; }.conversation { flex: 1; padding: 64px 40px 24px; max-width: 740px; width: 100%; margin: auto; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; }.suggestions { display: flex; gap: 12px; margin-top: 32px; flex-wrap: wrap; }.suggestions button { border: 1px solid var(--border-default); font-size: 12px; }.suggestions span { margin-left: 24px; }
.composer-area { margin: 16px 24px 8px; flex-shrink: 0; }
.composer { padding: 12px; border: 1px solid var(--border-default); border-radius: var(--radius-card); background: var(--surface-subtle); }
.composer:focus-within { border-color: var(--accent-selected); }
.composer textarea { display: block; width: 100%; height: 76px; min-height: 76px; max-height: 180px; padding: 4px; border: 0; resize: none; background: transparent; line-height: 1.8; }
.composer textarea::placeholder { color: var(--text-secondary); font-size: 12px; }
.composer-toolbar { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; margin-top: 8px; }
.composer-toolbar button { padding: 6px 8px; font-size: 12px; white-space: nowrap; }
.composer-toolbar .composer-icon { width: 32px; height: 32px; padding: 4px; font-size: 20px; }
.composer-select select { max-width: 132px; padding: 6px 2px; border: 0; background: transparent; cursor: pointer; font: inherit; font-size: 12px; color: var(--text-primary); }
select option { background: var(--surface-raised); color: var(--text-primary); }
select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.composer-actions { display: flex; gap: 4px; margin-left: auto; }
#send { width: 32px; height: 32px; padding: 0; font-size: 20px; background: var(--accent); color: var(--surface-raised); }
#voice[aria-pressed="true"] { background: var(--accent-subtle); color: var(--accent); }
.composer-meta { display: flex; justify-content: space-between; align-items: center; gap: 8px; min-height: 32px; color: var(--text-secondary); font-size: 10px; }
.composer-meta button { font-size: 10px; padding: 4px; }
#composer-tags { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 4px 8px; max-height: 96px; overflow: auto; }
#composer-tags button { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 3px 8px; font-size: 11px; background: var(--accent-subtle); color: var(--accent); }
#composer-options, #context-details { position: fixed; inset: auto; margin: 0; width: min(320px, calc(100vw - 32px)); max-height: min(460px, 70dvh); overflow: auto; padding: 16px; border: 1px solid var(--border-default); border-radius: var(--radius-card); background: var(--surface-raised); color: var(--text-primary); box-shadow: 0 8px 32px #07131f26; font-size: 12px; }
#composer-options header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.option-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 10px 0; }
.option-row select { width: 166px; padding: 6px; border: 1px solid var(--border-default); border-radius: 6px; font: inherit; background: var(--surface-subtle); color: var(--text-primary); }
#composer-options fieldset { margin: 8px 0; padding: 12px 0; border: 0; border-top: 1px solid var(--border-default); }
#composer-options fieldset label { display: inline-flex; align-items: center; gap: 4px; margin-right: 12px; }
#composer-options input { accent-color: var(--accent); }
#composer-options p { margin-top: 12px; font-size: 11px; }
#context-details p { margin: 8px 0; color: var(--text-secondary); }
#context-details progress { width: 100%; accent-color: var(--accent); }
#compact { margin-top: 8px; color: var(--accent); background: var(--accent-subtle); }
#demo-queue { max-height: 96px; overflow: auto; margin-bottom: 8px; }
.queue-row { display: flex; align-items: center; gap: 8px; font-size: 11px; }
.queue-row span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queue-row button { flex-shrink: 0; padding: 4px; font-size: 11px; }
.workbar { width: 280px; flex-shrink: 0; border-left: 1px solid var(--border-default); background: var(--surface-subtle); }.workbar header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border-default); }#tool-content { padding: 24px 16px; }.metric { margin: 20px 0; }.metric strong { float: right; }.metric progress { width: 100%; accent-color: var(--accent); }.file-row { padding: 12px 0; border-bottom: 1px solid var(--border-default); }pre { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; }
.lab-footer { margin-top: 12px; font-size: 12px; color: var(--text-secondary); }
dialog { width: min(580px, calc(100% - 32px)); max-height: 85dvh; border: 1px solid var(--border-default); padding: 24px; border-radius: var(--radius-card); background: var(--surface-raised); color: var(--text-primary); }dialog::backdrop { background: #07131f80; }dialog header { display: flex; justify-content: space-between; align-items: start; }dialog > p { margin: 12px 0 16px; }dialog input { width: 100%; padding: 10px 12px; background: var(--surface-subtle); border: 1px solid var(--border-default); border-radius: var(--radius-control); }.catalog-row { display: flex; align-items: center; gap: 12px; padding: 16px 0; border-bottom: 1px solid var(--border-default); }.catalog-row > div { flex: 1; }.catalog-row p { font-size: 11px; color: var(--text-secondary); }.catalog-row button { color: var(--accent); background: var(--accent-subtle); font-size: 12px; }dialog footer { font-size: 11px; color: var(--text-secondary); margin-top: 16px; }
#toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); padding: 10px 20px; border-radius: var(--radius-control); background: var(--text-primary); color: var(--surface-raised); pointer-events: none; opacity: 0; }#toast.visible { opacity: 1; }
[hidden] { display: none !important; }
#page-actions { margin-left: auto; }#page-actions button { padding: 4px 8px; font-size: 12px; }.note-count { margin-left: auto; padding: 0 6px; background: var(--accent-subtle); border-radius: var(--radius-control); color: var(--accent); }.note-setting-row { display: flex; gap: 16px; align-items: center; justify-content: space-between; padding: 16px 0; border-bottom: 1px solid var(--border-default); }.note-setting-row p, .note-setting-options p { font-size: 11px; color: var(--text-secondary); margin-top: 6px; }.demo-switch { width: 36px; height: 20px; border-radius: 20px; border: 1px solid var(--text-secondary); background: var(--surface-canvas); padding: 2px; flex-shrink: 0; }.demo-switch::after { content: ''; display: block; width: 14px; height: 14px; border-radius: 50%; background: var(--text-secondary); }.demo-switch[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }.demo-switch[aria-checked="true"]::after { margin-left: 16px; background: var(--surface-raised); }.note-setting-options { border: 0; padding: 16px 0; margin: 0; min-width: 0; }.note-setting-options legend { padding-top: 16px; font-weight: 550; }.note-segments { display: flex; padding: 4px; gap: 4px; background: var(--surface-subtle); border: 1px solid var(--border-default); border-radius: var(--radius-control); }.note-segments button { flex: 1; padding: 8px 4px; font-size: 12px; }.note-segments [aria-pressed="true"] { color: var(--accent); background: var(--surface-raised); box-shadow: 0 1px 3px var(--border-default); }.note-settings-preview { padding: 16px; background: var(--accent-subtle); border-radius: var(--radius-control); }.note-settings-preview small { color: var(--text-secondary); font-size: 11px; }#back-to-apps { margin-bottom: 12px; padding-left: 0; color: var(--accent); }#note-settings-summary { margin: 16px 0; }
.sidebar-footer { border-top: 1px solid var(--border-default); padding: 8px 0 0; gap: 4px; }#center-entry { flex: 1; min-width: 0; }#open-center { width: 100%; display: flex; align-items: center; gap: 10px; padding: 8px; text-align: left; }#open-center strong { font-size: 13px; font-weight: 550; }#open-center small { display: block; font-size: 11px; color: var(--text-secondary); }.entry-arrow { margin-left: auto; }.tool-tabs { display: flex; padding: 8px; gap: 2px; }.tool-tabs button { flex: 1; padding: 8px 4px; font-size: 12px; }.tool-tabs [aria-selected="true"] { background: var(--accent-subtle); color: var(--accent); }.center-switch { display: flex; gap: 4px; padding-bottom: 12px; }.center-switch [aria-pressed="true"] { background: var(--accent-subtle); color: var(--accent); }.center-app { display: flex; gap: 12px; align-items: start; padding: 16px 0; border-bottom: 1px solid var(--border-default); }.center-app > svg { margin-top: 4px; color: var(--accent); }.center-app-info { flex: 1; }.center-app p { color: var(--text-secondary); font-size: 11px; }.center-actions { display: flex; gap: 4px; flex-wrap: wrap; }.center-actions button { font-size: 11px; background: var(--accent-subtle); color: var(--accent); }.center-actions [aria-pressed="true"] { border: 1px solid var(--accent-selected); }
dialog[data-variant="dock"], dialog[data-variant="grid"], dialog[data-variant="favorites"] { position: fixed; left: 48px; bottom: 100px; top: auto; right: auto; margin: 0; width: min(520px, calc(100% - 32px)); max-height: 72dvh; }
dialog[data-variant="grid"] #catalog-items { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 16px; }dialog[data-variant="grid"] .center-app { flex-direction: column; padding: 16px; border: 1px solid var(--border-default); border-radius: var(--radius-control); }dialog[data-variant="cards"] .center-app { padding: 24px 0; }dialog[data-variant="cards"] .center-app > svg { width: 32px; height: 32px; }dialog[data-variant="cards"] .app-detail { margin-top: 8px; font-size: 13px; }dialog[data-variant="favorites"] .app-detail { display: none; }dialog[data-variant="scenes"] .center-app { background: var(--surface-subtle); border: 1px solid var(--border-default); border-radius: var(--radius-card); padding: 20px; margin-top: 16px; flex-wrap: wrap; }dialog[data-variant="scenes"] .center-actions { width: 100%; justify-content: flex-end; }
@media (max-width: 640px) { dialog[data-variant] { left: 16px; right: 16px; bottom: 16px; top: auto; margin: 0; max-height: 85dvh; width: calc(100% - 32px); padding: 16px; }.center-app { flex-wrap: wrap; }.center-actions { width: 100%; }.app { flex-direction: column; }.app .sidebar { flex: 1; min-height: 0; }.app .workspace { display: block; flex: 0 0 auto; }.app .workspace .conversation, .app .workspace .composer { display: none; }.app .workspace-header { padding: 8px 16px; align-items: center; }.app .workbar { z-index: 2; } }
.reference-note { max-width: 1440px; margin: 12px auto 0; display: flex; flex-wrap: wrap; gap: 8px 16px; color: var(--text-secondary); font-size: 11px; }.reference-note a { color: var(--accent); text-underline-offset: 3px; }
.sidebar > * { flex-shrink: 0; }.sidebar .history { flex: 1 1 80px; min-height: 0; }.sidebar .shortcuts { flex-shrink: 1; min-height: 80px; }
.sidebar .resident-apps { margin-top: 12px; min-height: 96px; flex-shrink: 1; display: flex; flex-direction: column; max-height: 240px; }.resident-apps .section-label { margin-bottom: 4px; flex-shrink: 0; }#resident-list { overflow: auto; min-height: 0; }.resident-row { display: flex; align-items: center; }.resident-row > button { display: flex; align-items: center; gap: 10px; padding: 8px 10px; flex: 1; min-width: 0; text-align: left; }.resident-row > button span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.resident-row [aria-current="page"] { background: var(--accent-subtle); color: var(--accent); }.resident-row[draggable="true"] { cursor: grab; }.resident-row .edit-actions { flex-shrink: 0; }.all-apps { display: flex; justify-content: space-between; padding: 6px 10px; font-size: 11px; color: var(--text-secondary); flex-shrink: 0; }.empty-apps { padding: 8px 10px; font-size: 11px; color: var(--text-secondary); }.app-placement { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }.app-placement button { font-size: 11px; padding: 6px 8px; }.app-placement button[aria-pressed="true"] { border: 1px solid var(--accent-selected); }.app-catalog-row { align-items: flex-start; }.app-catalog-row > svg { margin-top: 4px; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.launcher-trigger { display: flex; align-items: center; gap: 8px; width: 100%; padding: 12px 8px; border: 1px solid var(--border-default); background: var(--surface-raised); margin-bottom: 12px; font-size: 12px; }.launcher-trigger kbd { margin-left: auto; font: 10px var(--font-family-ui); color: var(--text-secondary); white-space: nowrap; }
[data-variant="launcher"] .shortcut-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }[data-variant="launcher"] .shortcut-button { flex-direction: column; padding: 8px 0; }[data-variant="launcher"] .icon-tile { height: 24px; }[data-variant="launcher"] .store-link { border-top: 1px solid var(--border-default); border-radius: 0; }
[data-variant="favorites"] .section-label { padding-left: 0; }[data-variant="favorites"] .shortcut-list { gap: 0; }[data-variant="favorites"] .shortcut-button { justify-content: flex-start; padding: 8px 12px; }[data-variant="favorites"] .item-name { font-size: 12px; }[data-variant="favorites"] .shortcut-item { display: flex; align-items: center; }[data-variant="favorites"] .edit-actions { margin-left: auto; }[data-variant="favorites"] .store-link { margin-top: 12px; border: 1px dashed var(--border-default); }.count { padding: 0 6px; color: var(--text-secondary); }
.scene-banner { display: flex; align-items: center; gap: 12px; margin: 0 4px 12px; padding: 12px; background: var(--accent-subtle); border-radius: var(--radius-control); }.scene-banner > svg { color: var(--accent); width: 24px; height: 24px; }.scene-banner .eyebrow { letter-spacing: 0; font-size: 10px; }.scene-count { margin-left: auto; font-size: 11px; color: var(--text-secondary); }.scene-label { display: block; }.scene-label select { font: inherit; font-weight: 600; color: var(--text-primary); background: transparent; border: 0; padding: 0; cursor: pointer; }.scene-label option { background: var(--surface-raised); }
[data-variant="scenes"] .shortcut-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }[data-variant="scenes"] .shortcut-button { border: 1px solid var(--border-default); background: var(--surface-raised); padding: 10px 8px; justify-content: flex-start; }.pack-preview { display: flex; gap: 8px; align-items: center; width: 100%; margin-top: 12px; padding: 12px 8px; text-align: left; background: var(--surface-raised); border: 1px solid var(--border-default); }.pack-preview strong { font-size: 11px; font-weight: 550; }.pack-preview small { display: block; color: var(--text-secondary); font-size: 10px; }.pack-preview > span:last-child { margin-left: auto; }.pack-symbol { color: var(--accent); }
.catalog-pack { padding: 20px 0; border-bottom: 1px solid var(--border-default); }.catalog-pack h3 { margin: 8px 0; font-size: 18px; font-weight: 550; }.pack-art { display: grid; place-items: center; float: right; width: 56px; height: 56px; border: 1px solid var(--accent-selected); background: var(--accent-subtle); color: var(--accent); border-radius: var(--radius-card); }.pack-art svg { width: 28px; height: 28px; }.pack-includes { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0; }.pack-includes span { padding: 4px 8px; font-size: 11px; background: var(--surface-subtle); border-radius: var(--radius-control); }.pack-includes svg { width: 14px; height: 14px; }.catalog-pack > button { border: 1px solid var(--accent-selected); color: var(--accent); background: var(--accent-subtle); }
dialog[data-mode="launch"] { width: min(640px, calc(100% - 32px)); }dialog[data-mode="launch"] .catalog-row:focus-within { background: var(--accent-subtle); }.catalog-row { padding-left: 8px; padding-right: 8px; }
@media (max-width: 1080px) { body { padding: 16px; }.app { position: relative; }.workbar { position: absolute; right: 0; top: 52px; bottom: 0; width: min(320px, 100%); box-shadow: -8px 0 24px var(--border-default); }.lab-header { flex-wrap: wrap; gap: 12px; }.design-note > span:last-child { display: none; }.conversation { padding: 32px 24px; } }
@media (max-width: 640px) { body { padding: 12px; }.lab-header > div:first-child { flex: 1; }.variants { order: 3; width: 100%; }.variants button { flex: 1; padding: 8px 4px; font-size: 12px; }.design-note { align-items: flex-start; gap: 12px; }.app { height: auto; min-height: 0; }.app .sidebar { width: 100%; flex: 0 0 auto; border: 0; padding-top: 12px; }.brand { padding-bottom: 12px; }.app .history { display: none; }.app .workspace { display: flex; min-height: 320px; border-top: 1px solid var(--border-default); }.app .workspace .composer { display: block; }.composer-area { margin: 12px 12px 4px; margin-top: auto; }.composer-toolbar { gap: 2px; }.composer-select select { max-width: 108px; font-size: 11px; }.composer-toolbar button { font-size: 11px; padding: 6px; }.composer-toolbar .composer-icon { width: 28px; }.composer-meta { flex-wrap: wrap; gap: 0; }.shortcut-list { max-height: 300px; overflow: auto; }.lab-footer { align-items: flex-start; }.lab-footer button { white-space: nowrap; }.workbar { top: 0; width: 100%; }.design-note p { font-size: 11px; } }
@@ -0,0 +1,83 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoodBuddy · 应用中心设计实验</title>
<link rel="stylesheet" href="navigation-demo.css">
<script src="navigation-demo.js" defer></script>
</head>
<body>
<header class="lab-header">
<div><strong>GoodBuddy <span class="muted">/ 设计实验</span></strong><p>左下角应用中心 · 六个交互方案</p></div>
<div class="variants" role="group" aria-label="选择设计方案">
<button data-variant="dock" aria-pressed="true">A · 紧凑入口</button>
<button data-variant="grid" aria-pressed="false">B · 应用宫格</button>
<button data-variant="cards" aria-pressed="false">C · 应用预览</button>
<button data-variant="launcher" aria-pressed="false">D · 搜索启动台</button>
<button data-variant="favorites" aria-pressed="false">E · 常驻管理</button>
<button data-variant="scenes" aria-pressed="false">F · 商店发现</button>
</div>
<button id="theme">切换深色</button>
</header>
<div class="design-note"><span id="variant-label">A / 紧凑 Dock</span><p id="variant-description"></p><span>交互原型 · 商店与指标均为演示数据</span></div>
<div class="project-lab"><label>项目活动演示 <select id="project-scenario"><option value="waiting">有待处理</option><option value="running">仅运行中</option><option value="current">仅当前项目运行</option><option value="idle">全部空闲</option></select></label><span>有活动时显示摘要,悬停或点击项目向右展开会话。</span></div>
<main class="app" data-variant="dock">
<aside class="sidebar" aria-label="主侧栏">
<div class="brand"><span class="brand-mark">GB</span><strong>GoodBuddy</strong><span class="muted">个人空间</span></div>
<div class="project-entry"><button id="project-trigger" popovertarget="project-menu" aria-expanded="false" aria-controls="project-menu"><span id="project-name">GoodBuddy</span><span class="muted" aria-hidden="true">⌄</span></button></div>
<button id="activity-trigger" popovertarget="project-activity" aria-expanded="false" aria-controls="project-activity"><span id="activity-summary"></span><span aria-hidden="true">⌄</span></button>
<div id="project-menu" popover aria-label="切换项目"><div id="project-list"></div></div>
<div id="project-activity" popover aria-label="所有项目活动"><section id="activity-projects"><h3>所有项目活动</h3><div id="activity-project-list"></div></section><section id="activity-sessions" hidden><button id="activity-back">‹ 返回项目列表</button><h3 id="activity-project-title"></h3><div id="activity-list"></div></section></div>
<button class="new-chat" data-page="当前对话"><span>+</span> 新对话</button>
<nav class="navigation" aria-label="应用导航">
<button class="selected" data-page="当前对话"><span data-icon="chat"></span>当前对话</button>
</nav>
<section class="resident-apps" aria-label="我的应用"><div class="section-label"><span>我的应用</span><div><button id="edit-apps">编辑</button><button data-catalog="apps" aria-label="管理常驻应用">+</button></div></div><nav id="resident-list" aria-label="常驻应用"></nav><button class="all-apps" data-catalog="apps">全部应用 <span>›</span></button></section>
<div class="history"><span class="eyebrow">最近对话</span><button data-page="整理产品设计思路">整理产品设计思路</button><button data-page="本周工作计划">本周工作计划</button><button data-page="项目文档检索">项目文档检索</button></div>
<div class="fixed-entry"><button data-page="运行记录"><span data-icon="history"></span>运行记录</button></div>
<div class="sidebar-footer"><div id="center-entry"></div><button data-page="设置" aria-label="设置" title="设置"><span data-icon="settings"></span></button></div>
</aside>
<section class="workspace">
<header class="workspace-header"><span id="page-label">当前对话</span><div id="page-actions"></div><button id="toggle-workbar" aria-expanded="true" aria-controls="workbar">助手工作栏</button></header>
<div class="conversation" id="page-content">
<span class="eyebrow">LET'S MAKE SOMETHING</span>
<h1>从一个想法开始。</h1>
<p class="muted">对话留在这里,常用能力随手打开。</p>
<div class="suggestions"><button data-page="项目文档检索">查找项目资料 <span>↗</span></button><button data-page="整理产品设计思路">梳理一个想法 <span>↗</span></button></div>
</div>
<section class="composer-area" aria-label="会话输入">
<div id="demo-queue" aria-label="待发送消息" hidden></div>
<div class="composer">
<div id="composer-tags" aria-label="当前附件与选项" hidden></div>
<textarea id="message" aria-label="演示消息输入" placeholder="向 GoodBuddy 提问…&#10;Enter 发送 · Shift+Enter 换行"></textarea>
<div class="composer-toolbar">
<button id="attach" class="composer-icon" aria-label="添加附件" title="添加附件">+</button>
<input id="attachment-input" type="file" multiple hidden>
<button id="voice" class="composer-icon" aria-label="模拟语音输入" aria-pressed="false" title="模拟语音输入"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="3" width="6" height="12" rx="3"/><path d="M5 10v2a7 7 0 0 0 14 0v-2M12 19v3m-4 0h8"/></svg></button>
<label class="composer-select"><span class="sr-only">Runtime</span><select id="composer-runtime"><option>OpenCode</option><option>Continue</option><option>直连模型</option></select></label>
<label class="composer-select"><span class="sr-only">工作模式</span><select id="composer-mode"><option value="Execute">Execute</option><option value="Ask">Ask</option></select></label>
<button id="composer-options-trigger" popovertarget="composer-options" aria-expanded="false">选项 <span class="muted">⌃</span></button>
<div class="composer-actions"><button id="stop" hidden aria-label="停止演示回复">停止</button><button id="send" aria-label="发送演示消息" title="发送" disabled>↑</button></div>
</div>
<div id="composer-options" popover aria-label="会话选项">
<header><strong>会话选项</strong><button popovertarget="composer-options" popovertargetaction="hide" aria-label="关闭会话选项">×</button></header>
<label class="option-row">专家<select id="composer-expert"><option value="">默认助手</option><option>产品顾问</option><option>代码审查专家</option></select></label>
<fieldset><legend>知识范围</legend><label><input type="checkbox" data-knowledge="产品文档"> 产品文档</label><label><input type="checkbox" data-knowledge="团队知识"> 团队知识</label></fieldset>
<label class="option-row">检索方式<select id="composer-retrieval"><option value="auto">自动检索</option><option value="always">每次检索</option></select></label>
<div id="runtime-options"><label class="option-row"><span id="agent-label">Agent</span><select id="composer-agent"><option value="">默认</option><option value="plan">Plan</option><option value="review">Review</option></select></label><label class="option-row">快捷操作<select id="composer-command"><option value="">无</option><option value="prompt">填入总结模板</option><option value="command">原生命令 /init</option></select></label></div>
<p class="muted">非默认选项会显示在输入框上方。</p>
</div>
</div>
<div class="composer-meta"><span id="composer-status" role="status">交互演示 · 不调用 AI</span><button id="context-trigger" popovertarget="context-details">上下文 <span id="context-value">42%</span></button></div>
<div id="context-details" popover aria-label="上下文用量"><strong>上下文用量 · 示例</strong><p id="context-description">本次模型调用:84k / 200k tokens</p><progress id="context-progress" value="42" max="100"></progress><button id="compact">压缩上下文</button></div>
</section>
</section>
<aside class="workbar" id="workbar" aria-label="助手工作栏"><header><strong>助手工作栏</strong><button id="close-tool" aria-label="关闭工作栏">×</button></header><div class="tool-tabs" role="tablist" aria-label="系统工具"><button role="tab" id="tab-files" data-tool="files" aria-selected="true" aria-controls="tool-content">文件</button><button role="tab" id="tab-terminal" data-tool="terminal" aria-selected="false" aria-controls="tool-content" tabindex="-1">终端</button><button role="tab" id="tab-browser" data-tool="browser" aria-selected="false" aria-controls="tool-content" tabindex="-1">浏览器</button><button role="tab" id="tab-resources" data-tool="resources" aria-selected="false" aria-controls="tool-content" tabindex="-1">资源</button></div><div id="tool-content" role="tabpanel" aria-labelledby="tab-files" tabindex="0"></div></aside>
</main>
<footer class="lab-footer"><span id="design-tradeoff"></span><button id="reset">恢复默认应用</button></footer>
<div class="reference-note">设计参考:<a href="https://www.raycast.com/" target="_blank" rel="noreferrer">Raycast · 搜索与扩展</a><a href="https://www.notion.com/help/intro-to-workspaces" target="_blank" rel="noreferrer">Notion · 收藏与折叠分区</a><a href="https://code.visualstudio.com/docs/configure/profiles" target="_blank" rel="noreferrer">VS Code · Profiles</a><span>借鉴交互思路,保留 GoodBuddy 的视觉与入口分类。</span></div>
<dialog id="catalog" aria-labelledby="catalog-title"><button id="back-to-apps" hidden>‹ 返回应用中心</button><header><div><span class="eyebrow">GOODBUDDY COLLECTION</span><h2 id="catalog-title">应用中心</h2></div><button id="close-catalog" aria-label="关闭">×</button></header><p class="muted" id="catalog-description"></p><div class="center-switch" role="group" aria-label="应用中心视图"><button data-catalog="apps">我的应用</button><button data-catalog="store">商店</button></div><input id="search" type="search" aria-label="搜索应用" placeholder="搜索应用…"><div id="catalog-items"></div><section id="app-settings" aria-label="魔法笔记设置" hidden></section><footer>常驻只表示菜单入口,不表示后台运行。安装与内容均为演示。</footer></dialog>
<div id="toast" role="status" aria-live="polite"></div>
</body>
</html>
@@ -0,0 +1,407 @@
/* global document, window, history, location, matchMedia, innerWidth, Event, setTimeout, clearTimeout */
const paths = {
note: '<rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h5"/>',
book: '<path d="M12 5v16M12 5C8 2 4 3 2 4v15c4-1 7-1 10 2 3-3 6-3 10-2V4c-2-1-6-2-10 1Z"/>',
chat: '<path d="M21 11a9 9 0 0 1-9 9H3l1-5A9 9 0 1 1 21 11Z"/>',
history: '<path d="M3 4v5h5M3 9a9 9 0 1 1 0 6m9-8v5l3 2"/>',
settings: '<circle cx="12" cy="12" r="4"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M5 5l2 2m10 10 2 2M5 19l2-2M17 7l2-2"/>',
activity: '<path d="M2 12h5l3-8 4 16 3-8h5"/>',
layers: '<path d="m12 3 10 5-10 5L2 8Zm-10 9 10 5 10-5M2 17l10 5 10-5"/>',
search: '<circle cx="10" cy="10" r="6"/><path d="m15 15 6 6"/>'
};
const icon = name => `<svg viewBox="0 0 24 24" aria-hidden="true">${paths[name] || paths.layers}</svg>`;
const items = [
{ id: 'notes', name: '魔法笔记', icon: 'note', detail: '记录想法、整理待办', summary: '把刚才的灵感留在这里', builtin: true },
{ id: 'knowledge', name: '知识库', icon: 'book', detail: '整理资料、检索知识', summary: '下一次提问,从已有知识开始', builtin: true },
{ id: 'heartbeat', name: '智能心跳', icon: 'activity', detail: '管理定时任务与主动提醒', summary: '让例行任务按计划发生', builtin: true },
{ id: 'projects', name: '项目管理', icon: 'layers', detail: '跟踪项目、任务与进度', summary: '把一个想法变成可跟进的项目', builtin: false },
{ id: 'reading', name: '阅读空间', icon: 'book', detail: '集中阅读、批注与摘录', summary: '给长文和深度阅读留一个空间', builtin: false }
];
const variants = {
dock: ['A / 紧凑入口', '最底部保留一个应用中心入口,向上展开紧凑列表。', '适合希望把侧栏空间留给对话的用户。'],
grid: ['B / 应用宫格', '底部入口展示应用数量,展开后用宫格浏览已安装应用。', '图标与名称同时可见,常驻位置由用户选择。'],
cards: ['C / 应用预览', '入口展示内置应用图标,展开后用卡片说明每个应用的用途。', '适合通过内容理解应用,卡片不代表后台持续运行。'],
launcher: ['D / 搜索启动台', '最底部就是搜索入口,也可以按 Ctrl / ⌘ K 打开。', '输入名称,用方向键与 Enter 打开已安装应用。'],
favorites: ['E / 常驻管理', '从底部管理哪些应用常驻菜单,未常驻应用也可直接打开。', '常驻应用优先排列;入口显隐不影响安装和运行状态。'],
scenes: ['F / 商店发现', '底部入口强调应用发现,展开后查看商店应用与安装位置。', '先模拟安装,再决定是否常驻;我的应用始终可切换访问。']
};
const $ = selector => document.querySelector(selector);
const demoProjects = [
{ id: 'goodbuddy', name: 'GoodBuddy', group: '本地', conversations: ['优化项目导航', '检查构建问题'] },
{ id: 'website', name: '官网改版', group: '本地', conversations: ['发布首页'] },
{ id: 'remote', name: '客户方案与交付文档工作空间', group: 'SSH', conversations: ['整理报价'] },
{ id: 'channel', name: '团队助手', group: '通道', conversations: ['本周工作计划'] }
];
let currentProject = 'goodbuddy';
let projectActivity = [];
let expandedActivityProject = '';
function renderProjects() {
const scenario = $('#project-scenario').value;
const activity = demoProjects.flatMap(project => project.conversations.map((name, index) => ({ project, name, index,
state: scenario === 'idle' || project.id === 'channel' || (scenario === 'current' && project.id !== currentProject) ? '' : scenario === 'waiting' && project.id === 'website' ? '等待审批' : '运行中'
}))).filter(row => row.state);
projectActivity = activity;
const waiting = activity.filter(row => row.state !== '运行中').length;
const running = activity.length - waiting;
const project = demoProjects.find(entry => entry.id === currentProject);
$('#project-name').textContent = project.name;
$('#project-trigger').setAttribute('aria-label', `切换项目,当前项目:${project.name}`);
$('#activity-trigger').hidden = !activity.length;
if (!activity.length && $('#project-activity').matches(':popover-open')) $('#project-activity').hidePopover();
$('#activity-summary').innerHTML = `${running ? `运行中 ${running}` : ''}${waiting && running ? ' · ' : ''}${waiting ? `<span class="project-waiting">待处理 ${waiting}</span>` : ''}`;
$('#activity-trigger').setAttribute('aria-label', `所有项目活动:${$('#activity-summary').textContent}`);
$('#project-list').innerHTML = ['本地', 'SSH', '通道'].map(group => `<section><h3>${group}</h3>${demoProjects.filter(entry => entry.group === group).map(entry => {
const rows = activity.filter(row => row.project.id === entry.id);
const pending = rows.filter(row => row.state !== '运行中').length;
const active = rows.length - pending;
return `<button data-project="${entry.id}" ${entry.id === currentProject ? 'aria-current="true"' : ''}><span class="project-row-name">${entry.name}</span>${entry.id === currentProject ? '<span aria-hidden="true">✓</span>' : ''}${rows.length ? `<small>${pending ? `<span class="project-waiting">待处理 ${pending}</span>` : ''}${active ? `<span>运行中 ${active}</span>` : ''}</small>` : ''}</button>`;
}).join('')}</section>`).join('');
$('#activity-project-list').innerHTML = demoProjects.map(entry => {
const rows = activity.filter(row => row.project.id === entry.id);
if (!rows.length) return '';
const pending = rows.filter(row => row.state !== '运行中').length;
return `<button data-activity-project="${entry.id}" aria-expanded="false" aria-controls="activity-sessions"><span>${entry.name}</span><small>${pending ? `<span class="project-waiting">待处理 ${pending}</span>` : `运行中 ${rows.length}`}</small><span aria-hidden="true">›</span></button>`;
}).join('') || '<p class="muted">暂无运行中或待处理会话。</p>';
expandActivityProject('');
$('.history').innerHTML = `<span class="eyebrow">当前项目对话</span>${project.conversations.map((name, index) => {
const row = activity.find(entry => entry.project.id === currentProject && entry.index === index);
return `<button data-conversation="${currentProject}" data-conversation-index="${index}">${name}${row ? `<small>${row.state}</small>` : ''}</button>`;
}).join('')}`;
}
$('#project-scenario').onchange = renderProjects;
const projectMenu = $('#project-menu');
function positionProjectMenu() {
const rect = $('#project-trigger').getBoundingClientRect();
projectMenu.style.left = `${Math.max(16, Math.min(rect.left, innerWidth - projectMenu.offsetWidth - 16))}px`;
projectMenu.style.top = `${Math.max(16, Math.min(rect.bottom + 8, window.innerHeight - projectMenu.offsetHeight - 16))}px`;
}
projectMenu.addEventListener('toggle', event => {
$('#project-trigger').setAttribute('aria-expanded', String(event.newState === 'open'));
if (event.newState === 'open') positionProjectMenu();
});
window.addEventListener('resize', () => { if (projectMenu.matches(':popover-open')) positionProjectMenu(); });
window.addEventListener('scroll', () => { if (projectMenu.matches(':popover-open')) positionProjectMenu(); }, true);
function positionActivityMenu() {
const panel = $('#project-activity');
const rect = $('#activity-trigger').getBoundingClientRect();
panel.classList.toggle('activity-compact', innerWidth < 620);
panel.classList.toggle('activity-left', innerWidth >= 620 && rect.left + panel.offsetWidth > innerWidth - 16 && rect.right >= panel.offsetWidth + 16);
const left = panel.classList.contains('activity-left') ? rect.right - panel.offsetWidth : rect.left;
panel.style.left = `${Math.max(16, Math.min(left, innerWidth - panel.offsetWidth - 16))}px`;
panel.style.top = `${Math.max(16, Math.min(rect.bottom + 4, window.innerHeight - panel.offsetHeight - 16))}px`;
}
function expandActivityProject(id, focus = false) {
expandedActivityProject = id;
$('#activity-sessions').hidden = !id;
$('#project-activity').classList.toggle('has-submenu', Boolean(id));
document.querySelectorAll('[data-activity-project]').forEach(button => button.setAttribute('aria-expanded', String(button.dataset.activityProject === id)));
if (id) {
$('#activity-project-title').textContent = demoProjects.find(project => project.id === id).name;
$('#activity-list').innerHTML = projectActivity.filter(row => row.project.id === id).sort((a, b) => Number(a.state === '运行中') - Number(b.state === '运行中')).map(row => `<button data-conversation="${id}" data-conversation-index="${row.index}"><strong>${row.name}</strong><small>${row.state}</small></button>`).join('');
}
if ($('#project-activity').matches(':popover-open')) positionActivityMenu();
if (focus) $('#activity-list button')?.focus();
}
$('#project-activity').addEventListener('toggle', event => {
$('#activity-trigger').setAttribute('aria-expanded', String(event.newState === 'open'));
if (event.newState === 'open') { expandActivityProject(''); positionActivityMenu(); }
});
$('#activity-project-list').addEventListener('pointerover', event => {
const button = event.target.closest('[data-activity-project]');
if (event.pointerType === 'mouse' && innerWidth >= 620 && button && button.dataset.activityProject !== expandedActivityProject) expandActivityProject(button.dataset.activityProject);
});
$('#activity-project-list').addEventListener('click', event => {
const button = event.target.closest('[data-activity-project]');
if (button) expandActivityProject(button.dataset.activityProject, true);
});
$('#activity-back').onclick = () => {
const id = expandedActivityProject;
expandActivityProject('');
$(`[data-activity-project="${id}"]`)?.focus();
};
$('#project-activity').addEventListener('keydown', event => {
const button = event.target.closest('[data-activity-project]');
if (event.key === 'ArrowRight' && button) { event.preventDefault(); expandActivityProject(button.dataset.activityProject, true); }
if ((event.key === 'ArrowLeft' || event.key === 'Escape') && expandedActivityProject) { event.preventDefault(); event.stopPropagation(); $('#activity-back').click(); }
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
const buttons = [...document.querySelectorAll(button ? '[data-activity-project]' : '#activity-list button')];
const index = buttons.indexOf(document.activeElement);
if (index >= 0) { event.preventDefault(); buttons[(index + (event.key === 'ArrowDown' ? 1 : -1) + buttons.length) % buttons.length].focus(); }
}
});
window.addEventListener('resize', () => { if ($('#project-activity').matches(':popover-open')) positionActivityMenu(); });
window.addEventListener('scroll', () => { if ($('#project-activity').matches(':popover-open')) positionActivityMenu(); }, true);
renderProjects();
let variant = 'dock';
let catalogMode = 'apps';
let residentApps = ['notes', 'knowledge'];
let editingApps = false;
let draggedApp = '';
let installed = new Set(items.filter(item => item.builtin).map(item => item.id));
let toastTimer;
let settingsOpen = false;
const noteSettings = { showCount: true, mode: 'immediate', format: 'combined' };
const noteModes = { immediate: '即时', 'after-save-auto': '保存后自动', 'after-save-manual': '保存后手动' };
const noteFormats = { combined: '长评 + 要点', narrative: '长评', structured: '要点' };
function renderNoteSettings() {
$('#app-settings').innerHTML = `<div class="note-setting-row"><div><strong>常驻左侧菜单</strong><p>对应“显示魔法笔记入口”,移除后仍可从应用中心打开。</p></div><button class="demo-switch" role="switch" aria-label="常驻左侧菜单" aria-checked="${residentApps.includes('notes')}" data-note-toggle="resident"></button></div>
<div class="note-setting-row"><div><strong>显示未完成待办数量</strong><p>在左侧入口显示数量,超过 99 项显示 99+。本 Demo 使用 3 项示例。</p></div><button class="demo-switch" role="switch" aria-label="显示未完成待办数量" aria-checked="${noteSettings.showCount}" data-note-toggle="showCount"></button></div>
<fieldset class="note-setting-options"><legend>AI 评论方式</legend><div class="note-segments" role="group" aria-label="AI 评论方式">${Object.entries(noteModes).map(([value, label]) => `<button data-note-mode="${value}" aria-pressed="${noteSettings.mode === value}">${label}</button>`).join('')}</div><p>即时:按回车并停止输入 5 秒后评论未保存草稿。保存后自动:保存后评论。保存后手动:点击 AI 分析后评论。</p></fieldset>
<fieldset class="note-setting-options"><legend>AI 评论形式</legend><div class="note-segments" role="group" aria-label="AI 评论形式">${Object.entries(noteFormats).map(([value, label]) => `<button data-note-format="${value}" aria-pressed="${noteSettings.format === value}">${label}</button>`).join('')}</div><p>默认同时生成流式长评和结构化要点,也可只保留其中一种。</p></fieldset>
<div class="note-settings-preview"><span class="eyebrow">当前配置预览</span><p>${noteModes[noteSettings.mode]} · ${noteFormats[noteSettings.format]}</p><small>选择后即时保留于本次 Demo 会话,不调用 AI、不修改真实设置。</small></div>`;
if ($('#note-settings-summary')) $('#note-settings-summary').textContent = `当前设置:${noteModes[noteSettings.mode]} · ${noteFormats[noteSettings.format]}。待办数量${noteSettings.showCount ? '显示' : '隐藏'}。`;
}
function showNoteSettings() {
if (!$('#catalog').open) { catalogMode = 'apps'; $('#search').value = ''; }
settingsOpen = true;
$('#catalog').dataset.variant = variant;
$('#catalog-title').textContent = '魔法笔记设置';
$('#catalog-description').textContent = '调整入口与 AI 评论行为。应用中心和笔记页面共享这份演示配置。';
$('#back-to-apps').hidden = false; $('#app-settings').hidden = false;
for (const selector of ['.center-switch', '#search', '#catalog-items']) $(selector).hidden = true;
renderNoteSettings();
if (!$('#catalog').open) $('#catalog').showModal();
$('#back-to-apps').focus();
}
function notify(text) {
$('#toast').textContent = text; $('#toast').classList.add('visible');
clearTimeout(toastTimer); toastTimer = setTimeout(() => $('#toast').classList.remove('visible'), 2200);
}
function renderApps() {
$('#edit-apps').textContent = editingApps ? '完成' : '编辑';
$('#resident-list').innerHTML = residentApps.map((id, index) => {
const item = items.find(entry => entry.id === id);
return `<div class="resident-row" data-resident="${id}" draggable="${editingApps}"><button data-open="${id}" ${$('#page-label').textContent === item.name ? 'aria-current="page"' : ''}>${icon(item.icon)}<span>${item.name}</span>${id === 'notes' && noteSettings.showCount ? '<small class="note-count" aria-label="3 项未完成待办(示例)">3</small>' : ''}</button>${editingApps ? `<div class="edit-actions"><button data-move="${id}" data-direction="-1" aria-label="上移${item.name}" ${index === 0 ? 'disabled' : ''}>↑</button><button data-pin="${id}" aria-label="移除${item.name}">×</button><button data-move="${id}" data-direction="1" aria-label="下移${item.name}" ${index === residentApps.length - 1 ? 'disabled' : ''}>↓</button></div>` : ''}</div>`;
}).join('') || '<p class="empty-apps">点击 +,选择常驻应用。</p>';
const subtitles = { dock: '', grid: `${installed.size} 个已安装应用`, cards: '笔记 · 知识 · 自动化', launcher: 'Ctrl / ⌘ K', favorites: `${residentApps.length} 个应用常驻菜单`, scenes: '发现你的下一个好应用' };
$('#center-entry').innerHTML = `<button id="open-center" aria-haspopup="dialog"><span class="brand-mark">${variant === 'launcher' ? icon('search') : 'GB'}</span><span><strong>${variant === 'launcher' ? '搜索应用…' : '应用中心'}</strong>${subtitles[variant] ? `<small>${subtitles[variant]}</small>` : ''}</span><span class="entry-arrow">${variant === 'scenes' ? '↗' : '⌃'}</span></button>`;
}
function renderCatalog() {
const query = $('#search').value.trim().toLowerCase();
const matches = items.filter(item => (catalogMode === 'store' ? !item.builtin : installed.has(item.id)) && `${item.name}${item.detail}`.toLowerCase().includes(query));
if (variant === 'favorites') matches.sort((a, b) => Number(residentApps.includes(b.id)) - Number(residentApps.includes(a.id)));
$('#catalog-items').innerHTML = matches.map(item => {
const ready = installed.has(item.id); const pinned = residentApps.includes(item.id);
return `<article class="center-app">${icon(item.icon)}<div class="center-app-info"><strong>${item.name}</strong><p>${item.builtin ? '内置应用' : '商店应用 · 示例'}</p><p class="app-detail">${variant === 'cards' ? item.summary : item.detail}</p></div><div class="center-actions">${ready ? `<button data-open="${item.id}">打开</button>${item.id === 'notes' ? '<button data-note-settings aria-label="魔法笔记设置">设置</button>' : ''}<button data-pin="${item.id}" aria-pressed="${pinned}">${pinned ? '取消常驻' : '常驻左侧'}</button>` : `<button data-install="${item.id}">模拟安装</button><button data-install-pin="${item.id}">安装并常驻</button>`}</div></article>`;
}).join('') || '<p class="empty-apps">没有匹配的应用。可以换个关键词,或切换到商店。</p>';
document.querySelectorAll('.center-switch button').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.catalog === catalogMode)));
}
function openCatalog(mode = 'apps') {
settingsOpen = false; $('#back-to-apps').hidden = true; $('#app-settings').hidden = true;
for (const selector of ['.center-switch', '#search', '#catalog-items']) $(selector).hidden = false;
catalogMode = mode; $('#catalog').dataset.variant = variant;
$('#catalog-title').textContent = mode === 'store' ? '发现新应用' : '应用中心';
$('#catalog-description').textContent = mode === 'store' ? '安装你需要的应用,自主决定是否常驻左侧菜单。' : '打开应用,或把常用应用留在左侧。系统工具请使用右侧工作栏。';
$('#search').value = ''; renderCatalog();
if (!$('#catalog').open) $('#catalog').showModal();
$('#search').focus();
}
function openPage(name) {
if ($('#catalog').open) $('#catalog').close();
$('#page-label').textContent = name;
$('#page-content').replaceChildren();
const title = document.createElement('h1'); title.textContent = name;
const description = document.createElement('p'); description.className = 'muted'; description.textContent = '主内容区页面预览。右侧工作栏保持当前工具状态。';
$('#page-content').append(title, description);
$('#page-actions').innerHTML = name === '魔法笔记' ? `<button data-note-settings>${icon('settings')} 笔记设置</button>` : '';
if (name === '魔法笔记' || name === '设置') {
const summary = document.createElement('p'); summary.id = 'note-settings-summary'; summary.className = 'muted'; $('#page-content').append(summary);
if (name === '设置') { const button = document.createElement('button'); button.dataset.noteSettings = ''; button.textContent = '魔法笔记设置'; $('#page-content').append(button); }
renderNoteSettings();
}
document.querySelectorAll('.navigation button').forEach(button => button.classList.toggle('selected', button.dataset.page === name));
renderApps(); notify(`已打开${name}`);
}
function selectVariant(name) {
variant = name; $('.app').dataset.variant = name;
document.querySelectorAll('button[data-variant]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.variant === name)));
[$('#variant-label').textContent, $('#variant-description').textContent, $('#design-tradeoff').textContent] = variants[name];
history.replaceState(null, '', `#${name}`); renderApps();
}
function selectTool(id) {
const content = {
files: '<span class="eyebrow">本机 / goodbuddy · 示例</span><div class="file-row">src /</div><div class="file-row">docs /</div><div class="file-row">resources /</div><div class="file-row">README.md</div>',
terminal: '<span class="eyebrow">当前项目 · 演示终端</span><pre>PS &gt; git status\nOn branch main\nworking tree clean\n\nPS &gt;</pre>',
browser: '<span class="eyebrow">浏览器 · 静态示例</span><div class="file-row">https://example.com</div><h2>Example Domain</h2>',
resources: '<span class="eyebrow">本机 · 静态演示数据</span><div class="metric">CPU <strong>18%</strong><progress value="18" max="100"></progress></div><div class="metric">内存 <strong>9.6 / 32 GB</strong><progress value="30" max="100"></progress></div><p>磁盘 IO:读 12 MB/s · 写 3 MB/s</p>'
};
$('#tool-content').innerHTML = content[id]; $('#tool-content').setAttribute('aria-labelledby', `tab-${id}`);
document.querySelectorAll('[data-tool]').forEach(button => { button.setAttribute('aria-selected', String(button.dataset.tool === id)); button.tabIndex = button.dataset.tool === id ? 0 : -1; });
}
document.addEventListener('click', event => {
const button = event.target.closest('button'); if (!button) return;
const data = button.dataset;
if (data.project || data.conversation) {
currentProject = data.project || data.conversation;
if (projectMenu.matches(':popover-open')) projectMenu.hidePopover();
if ($('#project-activity').matches(':popover-open')) $('#project-activity').hidePopover();
renderProjects();
const project = demoProjects.find(entry => entry.id === currentProject);
openPage(data.conversation ? project.conversations[Number(data.conversationIndex)] : '当前对话');
$('#page-label').textContent = `${project.name} / ${data.conversation ? project.conversations[Number(data.conversationIndex)] : '当前对话'}`;
$('#project-trigger').focus();
}
if (data.variant) selectVariant(data.variant);
if (data.catalog) openCatalog(data.catalog);
if (data.page) openPage(data.page);
if (data.open) openPage(items.find(item => item.id === data.open).name);
if (data.tool) selectTool(data.tool);
if (button.hasAttribute('data-note-settings')) showNoteSettings();
if (data.noteToggle || data.noteMode || data.noteFormat) {
if (data.noteToggle === 'resident') residentApps = residentApps.includes('notes') ? residentApps.filter(id => id !== 'notes') : [...residentApps, 'notes'];
if (data.noteToggle === 'showCount') noteSettings.showCount = !noteSettings.showCount;
if (data.noteMode) noteSettings.mode = data.noteMode;
if (data.noteFormat) noteSettings.format = data.noteFormat;
renderApps(); renderNoteSettings();
const selector = data.noteToggle ? `[data-note-toggle="${data.noteToggle}"]` : data.noteMode ? `[data-note-mode="${data.noteMode}"]` : `[data-note-format="${data.noteFormat}"]`;
$(selector).focus();
}
if (button.id === 'open-center') openCatalog(variant === 'scenes' ? 'store' : 'apps');
if (button.id === 'edit-apps') { editingApps = !editingApps; renderApps(); $('#edit-apps').focus(); }
if (data.pin || data.install || data.installPin) {
const id = data.pin || data.install || data.installPin;
if (data.install || data.installPin) installed.add(id);
if (data.pin || data.installPin) residentApps = residentApps.includes(id) ? residentApps.filter(entry => entry !== id) : [...residentApps, id];
renderApps();
if ($('#catalog').open) { renderCatalog(); ($(`#catalog [data-pin="${id}"]`) || $('#search')).focus(); }
else $('#edit-apps').focus();
}
if (data.move) {
const from = residentApps.indexOf(data.move); const to = from + Number(data.direction);
if (to >= 0 && to < residentApps.length) [residentApps[from], residentApps[to]] = [residentApps[to], residentApps[from]];
renderApps(); $(`#resident-list [data-pin="${data.move}"]`).focus();
}
});
$('#resident-list').addEventListener('dragstart', event => { draggedApp = event.target.closest('[data-resident]')?.dataset.resident || ''; if (!editingApps || !draggedApp) { event.preventDefault(); return; } event.dataTransfer.setData('text/plain', draggedApp); });
$('#resident-list').addEventListener('dragover', event => { if (editingApps && draggedApp) event.preventDefault(); });
$('#resident-list').addEventListener('drop', event => {
event.preventDefault(); const target = event.target.closest('[data-resident]')?.dataset.resident;
if (!editingApps || !residentApps.includes(draggedApp) || !target || target === draggedApp) return;
const to = residentApps.indexOf(target); residentApps.splice(residentApps.indexOf(draggedApp), 1); residentApps.splice(to, 0, draggedApp); draggedApp = ''; renderApps(); $('#edit-apps').focus();
});
$('#resident-list').addEventListener('dragend', () => { draggedApp = ''; });
$('#search').addEventListener('input', renderCatalog);
$('#close-catalog').onclick = () => $('#catalog').close();
$('#back-to-apps').onclick = () => { const query = $('#search').value; openCatalog(catalogMode); $('#search').value = query; renderCatalog(); ($('#catalog [data-note-settings]') || $('#search')).focus(); };
function toggleWorkbar(visible) { $('.workbar').hidden = !visible; $('#toggle-workbar').setAttribute('aria-expanded', String(visible)); }
$('#toggle-workbar').onclick = () => toggleWorkbar($('.workbar').hidden);
$('#close-tool').onclick = () => { toggleWorkbar(false); $('#toggle-workbar').focus(); };
$('.tool-tabs').addEventListener('keydown', event => {
const tabs = [...document.querySelectorAll('[data-tool]')]; const index = tabs.indexOf(event.target);
if (index < 0 || !['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
event.preventDefault(); const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (index + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;
selectTool(tabs[next].dataset.tool); tabs[next].focus();
});
document.addEventListener('keydown', event => {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k') { event.preventDefault(); openCatalog(); }
if (!$('#catalog').open || settingsOpen || variant !== 'launcher') return;
const actions = [...document.querySelectorAll('#catalog [data-open]')]; if (!actions.length) return;
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { event.preventDefault(); const index = actions.indexOf(document.activeElement); const next = index < 0 ? (event.key === 'ArrowDown' ? 0 : actions.length - 1) : (index + (event.key === 'ArrowDown' ? 1 : -1) + actions.length) % actions.length; actions[next].focus(); }
if (event.key === 'Enter' && document.activeElement === $('#search')) { event.preventDefault(); actions[0].click(); }
});
$('#theme').onclick = () => { const dark = document.documentElement.dataset.theme !== 'dark'; document.documentElement.dataset.theme = dark ? 'dark' : 'light'; $('#theme').textContent = dark ? '切换浅色' : '切换深色'; };
$('#reset').onclick = () => { installed = new Set(items.filter(item => item.builtin).map(item => item.id)); residentApps = ['notes', 'knowledge']; editingApps = false; renderApps(); notify('应用安装与常驻配置已恢复默认'); };
let demoRunning = false;
let demoRecording = false;
let demoAttachments = [];
let demoQueue = [];
let demoReplyTimer;
function renderComposer() {
const tags = $('#composer-tags'); tags.replaceChildren();
const addTag = (text, clear) => {
const button = document.createElement('button'); button.textContent = `${text} ×`; button.setAttribute('aria-label', `移除${text}`);
button.onclick = () => { clear(); renderComposer(); $('#message').focus(); }; tags.append(button);
};
demoAttachments.forEach((name, index) => addTag(name, () => demoAttachments.splice(index, 1)));
for (const id of ['composer-expert', 'composer-agent', 'composer-command']) {
const select = $(`#${id}`);
if (select.value) addTag(select.selectedOptions[0].textContent, () => { select.value = ''; });
}
document.querySelectorAll('[data-knowledge]:checked').forEach(input => addTag(input.dataset.knowledge, () => { input.checked = false; }));
if ($('#composer-retrieval').value === 'always') addTag('每次检索', () => { $('#composer-retrieval').value = 'auto'; });
tags.hidden = !tags.childElementCount;
$('#stop').hidden = !demoRunning;
$('#send').disabled = !$('#message').value.trim() && $('#composer-command').value !== 'command';
$('#send').title = demoRunning ? '加入队列' : '发送';
$('#send').setAttribute('aria-label', demoRunning ? '加入演示队列' : '发送演示消息');
$('#composer-status').textContent = demoRecording ? '模拟录音中 · 再次点击结束' : demoRunning ? '模拟回复中 · 可继续发送加入队列' : '交互演示 · 不调用 AI';
for (const id of ['composer-runtime', 'composer-mode', 'composer-expert', 'composer-agent', 'composer-command']) $(`#${id}`).disabled = demoRunning;
$('#compact').disabled = demoRunning;
$('#runtime-options').hidden = $('#composer-runtime').value === '直连模型';
$('#compact').hidden = $('#composer-runtime').value === '直连模型';
$('#demo-queue').replaceChildren();
demoQueue.forEach((text, index) => {
const row = document.createElement('div'); row.className = 'queue-row';
const label = document.createElement('span'); label.textContent = text; label.title = text;
const insert = document.createElement('button'); insert.textContent = '立即插入'; insert.setAttribute('aria-label', `立即中断并插入:${text}`);
insert.onclick = () => { demoQueue.splice(index, 1); startDemoReply(text); };
const remove = document.createElement('button'); remove.textContent = '×'; remove.setAttribute('aria-label', `删除待发送消息:${text}`);
remove.onclick = () => { demoQueue.splice(index, 1); renderComposer(); };
row.append(label, insert, remove); $('#demo-queue').append(row);
});
$('#demo-queue').hidden = !demoQueue.length;
}
function startDemoReply(text) {
clearTimeout(demoReplyTimer); demoRunning = true;
if ($('#composer-options').matches(':popover-open')) $('#composer-options').hidePopover();
notify(`模拟处理:${text}`); renderComposer();
demoReplyTimer = setTimeout(() => {
demoRunning = false;
if (demoQueue.length) startDemoReply(demoQueue.shift());
else { renderComposer(); notify('演示回复已完成,未调用 AI'); }
}, 8000);
}
$('#send').onclick = () => {
if ($('#send').disabled) return;
const text = $('#composer-command').value === 'command' ? `/init ${$('#message').value.trim()}` : $('#message').value.trim();
$('#message').value = ''; $('#message').style.height = ''; demoAttachments = [];
if (demoRunning) { demoQueue.push(text); renderComposer(); }
else startDemoReply(text);
};
$('#stop').onclick = () => {
clearTimeout(demoReplyTimer); demoRunning = false;
if (demoQueue.length) startDemoReply(demoQueue.shift());
else { renderComposer(); notify('已停止演示回复'); }
};
$('#message').addEventListener('input', () => { $('#message').style.height = 'auto'; $('#message').style.height = `${Math.min(180, $('#message').scrollHeight)}px`; renderComposer(); });
$('#message').addEventListener('keydown', event => { if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) { event.preventDefault(); $('#send').click(); } });
$('#attach').onclick = () => $('#attachment-input').click();
$('#attachment-input').onchange = () => { demoAttachments.push(...Array.from($('#attachment-input').files, file => file.name)); $('#attachment-input').value = ''; renderComposer(); };
$('#voice').onclick = () => {
demoRecording = !demoRecording; $('#voice').setAttribute('aria-pressed', String(demoRecording));
if (!demoRecording) { $('#message').value += '请帮我整理一下今天的工作计划。'; $('#message').dispatchEvent(new Event('input')); }
renderComposer();
};
$('#composer-options').addEventListener('change', event => {
if (event.target.id === 'composer-command' && event.target.value === 'prompt') {
$('#message').value = '请总结当前讨论,列出关键结论和下一步行动。'; event.target.value = ''; $('#message').dispatchEvent(new Event('input'));
}
renderComposer();
});
$('#composer-runtime').onchange = () => {
$('#composer-agent').value = ''; $('#composer-command').value = '';
$('#agent-label').textContent = $('#composer-runtime').value === 'Continue' ? '预设' : 'Agent';
$('#composer-command').querySelector('[value="command"]').hidden = $('#composer-runtime').value !== 'OpenCode';
renderComposer();
};
$('#compact').onclick = () => {
$('#context-value').textContent = '18%'; $('#context-progress').value = 18;
$('#context-description').textContent = '压缩后对话估算:36k / 200k tokens';
$('#context-details').hidePopover(); notify('已模拟压缩上下文');
};
for (const [panelId, triggerId] of [['composer-options', 'composer-options-trigger'], ['context-details', 'context-trigger']]) {
const panel = $(`#${panelId}`); const trigger = $(`#${triggerId}`);
const position = () => {
const rect = trigger.getBoundingClientRect();
panel.style.left = `${Math.max(16, Math.min(rect.right - panel.offsetWidth, innerWidth - panel.offsetWidth - 16))}px`;
panel.style.top = `${Math.max(16, rect.top - panel.offsetHeight - 8)}px`;
};
panel.addEventListener('toggle', event => { trigger.setAttribute('aria-expanded', String(event.newState === 'open')); if (event.newState === 'open') position(); });
window.addEventListener('resize', () => { if (panel.matches(':popover-open')) position(); });
window.addEventListener('scroll', () => { if (panel.matches(':popover-open')) position(); }, true);
}
renderComposer();
document.querySelectorAll('[data-icon]').forEach(element => { element.innerHTML = icon(element.dataset.icon); });
window.addEventListener('hashchange', () => { if (Object.hasOwn(variants, location.hash.slice(1))) selectVariant(location.hash.slice(1)); });
selectVariant(Object.hasOwn(variants, location.hash.slice(1)) ? location.hash.slice(1) : 'dock'); selectTool('files');
if (matchMedia('(max-width: 1080px)').matches) toggleWorkbar(false);
+105 -17
View File
@@ -12,6 +12,9 @@
## 1. 背景
计划修订说明:当前实施范围以[第 8 节](#8-分期)为准。本文其余章节仍包含 v0.2 的
左下角系统工具方案;启动实现前须同步功能要求、验收及配套设计,旧快捷工具条不再列入实施。
主侧栏当前把“当前对话”“魔法笔记”“知识库”“智能心跳”和“运行记录”放在同一导航组,
底部长期显示“GB 本地工作区”和平台架构。底部信息不能反映用户可以执行的操作,文件、终端
和浏览器则需要先找到右侧助手工作栏后才能打开。
@@ -48,7 +51,8 @@
## 4. 非目标
- 首期不提供第三方插件搜索、下载、安装、更新、签名验证或卸载。
- 首期不提供应用商店页面、商店导航、推荐位或安装按钮,也不提供第三方插件搜索、下载、
安装、更新、签名验证或卸载。内置应用的浏览、搜索与打开属于应用中心,不依赖商店。
- 不把文件树、终端或浏览器重新实现到左侧栏。
- 不改变助手工作栏的面板实例、停靠、执行空间和安全边界。
- 不允许隐藏运行记录、当前对话或设置。
@@ -86,10 +90,10 @@ Switch 更新成功后主导航立即生效;更新失败时恢复原状态并
### FR-6 文件浏览器入口
点击文件按钮时展开助手工作栏,并打开或聚焦单实例的“工作区”面板。已有实例保持其“跟随
当前上下文”或“固定到指定对象”状态;创建实例时默认跟随当前项目,没有项目时使用已配置
的默认工作区。跟随上下文或新建实例所需的路径不可用时,面板显示修复入口;已有有效固定
目标不受当前项目和默认工作区状态影响。
点击文件按钮时展开助手工作栏,并聚焦默认存在且不可关闭的“工作区”单实例。该实例保持
“跟随当前上下文”或“固定到指定对象”状态;迁移或布局修复发现实例缺失时补回,并默认跟随
当前项目。没有项目时使用已配置的默认工作区。跟随上下文所需的路径不可用时,面板显示修复
入口;已有有效固定目标不受当前项目和默认工作区状态影响。
### FR-7 终端入口
@@ -99,9 +103,10 @@ Switch 更新成功后主导航立即生效;更新失败时恢复原状态并
### FR-8 浏览器入口
点击浏览器按钮时展开助手工作栏,并打开或聚焦单实例的受控浏览器面板。已有实例保持其目标
状态;新实例默认跟随当前会话。新建或跟随模式下没有活动会话时,面板显示需要选择或创建
会话的空状态;已有有效固定目标继续显示。入口不打开系统默认浏览器,也不创建旁路 WebView。
点击浏览器按钮时展开助手工作栏,并优先聚焦绑定当前 Conversation 的最近使用浏览器实例;
没有匹配实例时创建一个。没有活动 Conversation 时聚焦最近使用的浏览器实例;完全不存在时
创建待绑定空实例。该快捷入口不会因连续点击创建重复实例;用户通过工作栏“+”选择浏览器时
则始终创建新实例。入口不打开系统默认浏览器,也不创建旁路 WebView。
### FR-9 资源监控入口
@@ -134,6 +139,19 @@ Switch,切换后立即持久化。系统工具区域列出四项内置固定
窄窗口下主侧栏仍按现有抽屉方式显示。系统工具按钮保持单行且不与设置按钮重叠;空间不足时
收起文字和非必要分隔,不隐藏四个系统工具或设置。
### FR-14 项目活动汇总
用户在任一项目中都能查看跨项目的运行中与待处理会话,并从活动列表直接进入所选会话。
汇总入口仅在有运行中或待处理会话时显示在项目选择器下方,无活动时整行隐藏。点击后先显示有活动会话的项目,
悬停或点击项目展开会话子菜单,点击会话进入该条对话。项目菜单保留原有分组与顺序,并显示
各项目的活动数量;级联菜单内的项目顺序不随待处理或运行中数量变化。
汇总不受当前项目、搜索条件或最近历史条数限制。计数与排除规则见
[活动逻辑](./logic-design.md#9-项目活动汇总),交互见[活动界面](./ui-design.md#10-项目活动汇总)。
验收覆盖跨项目后台运行、同会话多任务去重、提问与审批优先、较早的仍在运行或等待处理的
会话可见,以及从带搜索条件的页面或有未保存修改的设置页跳转。结束后仅有未读标记的
会话不保留在活动汇总中;已完成未读面板不属于本项交付。
## 6. 权限与安全
- 快捷入口不授予新的文件、命令、网络或浏览器权限。
@@ -150,7 +168,8 @@ Switch,切换后立即持久化。系统工具区域列出四项内置固定
- 运行记录在所有应用显隐组合下保持可见并可打开。
- 左下角不再用“本地工作区”与系统架构占据主要空间。
- 文件、终端、浏览器和资源监控均可从左下角打开正确的工作栏面板,且不产生第二套内容界面。
- 连续点击系统工具不会无意创建重复的文件或浏览器实例;终端遵守多实例规则。
- 连续点击系统工具不会无意创建重复的浏览器或终端实例;工作区始终复用不可关闭的固定实例。
用户通过工作栏“+”可以显式创建额外浏览器或终端实例。
- 资源监控以 2 秒间隔更新,保留不超过 150 个样本;面板不活动、工作栏收起或窗口隐藏、
最小化后停止请求新样本。
- CPU、内存、磁盘 IO 或 GoodBuddy 指标不可用时,页面区分部分可用与整体失败。
@@ -161,15 +180,84 @@ Switch,切换后立即持久化。系统工具区域列出四项内置固定
### 首期
- 重构侧栏底部,增加四个系统工具按钮和独立设置按钮。
- 建立从 App 到助手工作栏的“打开或聚焦工具”意图接口。
- 增加本机资源快照采集与资源监控单实例面板。
- 增加内置应用的主导航显示设置。
- 将侧栏最底部改为内置应用中心入口,设置按钮保持独立;具体列表、宫格或卡片样式从
Demo 中收敛确定。
- 应用中心提供魔法笔记、知识库、智能心跳的浏览、搜索与打开,不要求安装。
- 支持应用常驻主导航、取消常驻和排序;未常驻应用仍可从应用中心打开,隐藏不删除数据或停止后台任务。
- 应用中心和应用页面就近提供已有功能设置,共享真实设置来源;已有全局设置入口定位到同一界面。
魔法笔记优先接入待办数量、AI 评论方式和评论形式,入口显示与常驻控制统一,避免重复开关。
- 文件、终端、浏览器保留在右侧工作栏 Tab,不在左下角复制入口,也不纳入应用中心。
- 原计划的本机资源监控仍作为系统工具交付项,在右侧增加单实例面板;与应用中心分别实现和验证。
- 保持运行记录为固定系统入口。
### 后续
首期验收需覆盖:无商店入口时可以访问全部内置应用;取消常驻后仍可重新打开和配置;
不同设置入口状态一致;常驻排序重启后保留;右侧工具切换不改变当前应用页面。
本节是范围调整,Demo 交互通过不代表正式功能已经实现。
- 插件应用和插件工具注册。
- 插件工具固定、取消固定和排序。
- 插件市场、权限详情、安装和卸载。
### 补充要求:魔法笔记迁移与入口联动
- 将现有魔法笔记设置迁入应用中心的应用设置界面,笔记页面内的设置入口访问同一份配置。
魔法笔记可常驻主导航;原全局设置位置如保留入口,应定位到共同设置界面。
- 魔法笔记相关布尔开关默认全部开启,包括入口显示(常驻)和未完成待办数量显示。
AI 评论方式、评论形式是单选项,沿用现有默认值,不把“全部开启”解释为同时选中多个模式。
新默认值适用于未配置状态,迁移时保留用户已明确保存的选择。
- 应用关闭后的入口联动必须覆盖主导航和会话页面,不能只更新应用中心卡片或一个菜单。
知识库关闭后,左侧知识库菜单及会话中的知识库入口按钮同步移除;重新开启后按配置恢复。
- “取消常驻”只移除主导航捷径;“关闭应用”移除该应用在主导航、会话等业务界面的入口。
应用中心保留管理和重新开启入口。原型中仅有常驻控制,正式设计需补齐这两种状态,避免混用。
- 入口变化不删除已有笔记或知识数据。知识库已打开页面、会话已关联知识以及后台任务的处理,
在详细逻辑同步时分别定义,不能仅凭隐藏按钮推断功能或后台行为已经停用。
补充验收:从任一配置入口关闭知识库,主导航和当前会话按钮立即同步移除,切换会话、
重启后也不重新出现;重新开启后恢复相应入口。验证魔法笔记未配置时的默认开启,以及
已有关闭选择在迁移后保留。设置保存失败时各入口保持原状态,不能出现互相矛盾的显示。
### 后续:应用商店与扩展
- 单独设计应用商店的目录、搜索、推荐、详情及安装管理,时间与具体范围另行确定。
- 插件应用和插件工具注册,以及安装、更新、卸载与权限说明。
- 插件应用可常驻主导航;插件工具按工作栏扩展设计接入,不恢复左下角重复系统工具条。
- 用户级快捷键与命令面板接入同一贡献命令。
现有 Demo 的商店页、项目管理和阅读空间安装流程仅保留为未来交互参考,不纳入首期验收,
也不作为已有商店能力的说明。首期实现不预建插件安装服务或远程目录协议。
### FR-15 私有化市场与 YAML 交换(后续)
市场面向组织私有化部署。用户配置市场 URL 后即可获取该来源的应用目录与详情,并按需获取
应用;需要认证的来源复用其登录流程。市场不可达时说明原因并允许重试,不影响已安装应用
和内置应用使用,也不自动切换到公共市场。市场 URL 只影响应用市场,不改变模型、桌面更新
或 Runtime 的下载源。
应用支持以 `.yml` 导入、导出,兼容 `.yaml` 扩展名,用于组织内部共享、备份和迁移。
导出内容为应用定义及可迁移配置,包含格式版本、应用标识、应用版本、入口声明和依赖说明;
凭据、会话、笔记及知识库业务数据不随应用定义导出。导入前展示应用摘要、缺失依赖和与已有
应用的冲突;同标识应用由用户选择更新或取消,不静默覆盖本机配置。
YAML 描述文件不能代替模型权重或插件程序包。有外部依赖时应明确资源引用;离线导入仅在
所需资源已具备时可完成使用,否则列出需要补充的资源,不显示为可运行。导入应用不会自动
启动模型或执行任务。
未来 ShareServer 提供私有市场目录、应用定义的导入导出、发布和版本资源分发;桌面端负责
配置来源、获取目录、本地导入导出及应用安装管理。服务端职责归属见
[ShareServer 总体设计](../../architecture/share-network-architecture.md)。本节记录后续需求,
尚未确定 API、YAML Schema 或服务端交付版本,不纳入应用中心首期。
### FR-16 本机模型管理入口(新增计划项)
应用中心提供“本机模型”内置管理应用,可选择常驻左侧,打开主内容区管理页。该入口统一
查看各功能使用的本机模型及服务状态,包括已接入的 OCR、向量、语音或推理引擎;具体支持
清单需核对现有模型服务后确定,不因提供入口而假定全部引擎均已支持进程控制。
页面展示模型名称、用途、所属引擎、管理方式、运行状态、占用情况及使用它的应用或任务。
GoodBuddy 管理的进程提供启动、停止、重启和错误查看;外部启动的模型服务标明“外部管理”,
提供连接状态与配置入口,未接入其管理接口时不提供停止或重启。进程内运行的模型按引擎实际
能力提供加载与释放,不伪造独立进程。
停止或重启前展示正在使用该服务的任务和影响,由用户决定是否继续;取消时保留运行状态,
失败时显示实际状态和原因。多个应用共用同一模型服务时展示同一个服务及其使用方,不重复
启动进程。隐藏管理入口、关闭管理页面或取消应用常驻均不停止模型;卸载模型文件是独立操作。
资源监控仍是只读系统工具,本机模型管理负责模型生命周期,两者共享可用的状态来源。
该项可独立于商店交付;当前只纳入计划,具体分期、任务使用期间的停止策略和各引擎管理能力
在详细设计中确定。原首期资源监控的 GoodBuddy 桌面进程统计范围不因此扩大。
@@ -0,0 +1,64 @@
# 项目活动级联实施进度
## 2026-09-14:菜单独立高度修正
生产菜单移除共用双栏表面,会话层独立定位到选中项目行。打开较长会话列表时,项目层的位置、
宽度与高度保持不变;会话层独立限制窗口边界和滚动。两侧均无法容纳子菜单时使用同层返回交互。
`npx vitest run src/renderer/src/ProjectActivity.test.tsx` 通过 13 项,类型检查与 Lint 通过。
`npm test` 运行 60 秒后超时,未取得完整结果。
最终源码的 Electron 43.2.0 / Chromium 150 组件 fixture 使用生产样式与中文 i18n、模拟活动数据,
深浅主题共 14 个场景、108 条检查通过,覆盖短/60 条会话、两层独立滚动、底部避让、向左展开、
375px 窄屏及 640px 两侧空间不足。桌面项目层打开前后矩形一致,短窗口会话层底部保留 16px。
此项验证实际组件排版和交互,不代表完整 Main/Preload 端到端验证;未修改远程执行路径。
## 2026-09-13:生产活动入口
对应 [FR-14](./prd.md#fr-14-项目活动汇总)、[US-E1 / US-E2](./user-stories.md#6-epic-e项目活动级联)。
本页只记录项目活动入口,不表示应用中心计划已交付。
`App` 已接入 `ProjectActivity` 常驻摘要和锚定级联菜单。会话和项目计数继续使用现有
`deriveConversationActivity` 结果,点击会话继续调用 `openActivityConversation`。未增加
持久化字段、IPC 或 Runtime 状态。项目选择器分组及计数保持原有行为。
组件与 App 定向回归通过 32 项,覆盖空闲入口、稳定项目及 SSH Host 顺序、待处理优先、
跨项目精确跳转、设置离开检查、实时状态清理、键盘返回、外部关闭和窄侧栏焦点。
Windows Electron 视觉 fixture 使用当前 `ProjectActivity`、生产样式和 i18n,活动数据为
受控样例。浅深主题分别验证 `1280×800`、`960×720`、`720×640`、`640×420`、`375×600`;
原生键盘验证 Enter、上下及右方向键、End、两级 Escape、Tab 退出,另测左侧回退和空闲入口。
此项证明组件的实际 Chromium 排版与交互,不代表完整生产 Main/Preload 的端到端验证。
浏览器遮挡复用共享菜单相交机制,组件测试检查相交和非相交结果;本轮未单独驱动原生
`WebContentsView` 可见性。远程 Agent 执行路径未改动,未发出模型或知识库请求。
提交前复核:`npm run typecheck`、`npm run lint` 通过;单独运行
`npx vitest run src/renderer/src/ProjectActivity.test.tsx`,11 项通过。
`npm test` 在 240 秒后超时,超时前 `tests/agent-package.test.ts` 的
`verifies and installs offline dependency inventories larger than one MiB` 用例失败,未取得完整结果。
Demo 已按最新讨论隐藏空闲摘要并移除会话行箭头;生产空闲摘要在下述非驻留修正中对齐。
后续界面修正已移除生产会话行箭头,并为活动摘要与下方新建会话增加 `8px` 间距。
## 2026-09-14:摘要非驻留修正
生产 `ProjectActivity` 在无活动时返回空内容,活动归零时关闭级联菜单;新活动只恢复摘要。
组件回归 11 项通过,覆盖初始空闲、活动归零关闭菜单及活动恢复后不自动展开。
App 的 `project activity integration` 定向回归 18 项通过,类型检查与 Lint 通过。
本轮 `npm test` 在 60 秒后超时,未取得全量结果;未重新执行 Electron 视觉验证。
## 2026-09-14:0.13.2 候选验证
正式发布准备时再次复现 App 问答用例的时序失败。活动摘要改为常驻后,等待该入口出现
不再意味着会话初始化和 Runtime 状态读取已经完成;测试有时点击仍被禁用的发送按钮,
留下未消费的一次性问答 mock,继而影响后续用例。
测试现等待已加载的会话和可用发送按钮,并在每个用例前重置问答及审批响应 mock。
答案重载用例等待提交后的界面状态,而不只等待 API 被调用。未修改生产交互、
未增加固定延时,也未跳过失败用例。相关 20 项连续三轮通过,完整 App 与 Agent
发布说明检查共 227 项通过。
最终候选 `npm test` 通过 4,155 项、跳过 66 项、失败 0 项;`npm run typecheck`、
`npm run lint` 和 `npm run release:notes:verify` 均通过。发布准备未执行本地生产构建、
打包或安装包启动探针,原生包验收由主分支与标签 CI 完成。
@@ -26,7 +26,8 @@ type WorkspaceView =
```
文件、浏览器和终端已经由 `RightAssistantSidebar`、`WorkbarShell` 与 `workbar-contracts.ts`
承载。工作区与浏览器是单实例应用,终端是多实例应用。代码中尚无资源指标采集和资源监控
承载。工作区是不可关闭的单实例应用,浏览器和终端是多实例应用。浏览器多实例的跨进程
实现以[浏览器多实例技术设计](../assistant-workbar/browser-tabs-technical-design.md)为准。代码中尚无资源指标采集和资源监控
面板。现有侧栏底部 `user-card` 整行打开设置,并显示“本地工作区”和平台架构。
本次改动增加入口编排、从主侧栏到工作栏的命令通道,以及本机资源监控的 Main/Preload/
@@ -78,23 +79,28 @@ type WorkbarOpenIntent = {
requestId: number
appId: 'workspace' | 'terminal' | 'browser' | 'resources'
reason: 'sidebar-system-tool'
context?: { conversationId?: string; projectId?: string }
}
```
`requestId` 使连续点击同一工具仍能被消费。不要仅设置 `assistantSidebarTab`,因为用户可能已
关闭对应实例,且终端需要按执行空间寻找实例。
建议由 `RightAssistantSidebar` 在消费意图后通过现有 `WorkbarShell` 控制接口完成。文件和浏览器
已有实例时只激活实例,保留其“跟随当前上下文”或“固定到指定对象”状态:
建议由 `RightAssistantSidebar` 在消费意图后通过现有 `WorkbarShell` 控制接口完成。控制器必须
接收上下文和调用原因,不能只按 `appId` 查找第一个实例:
```ts
type WorkbarController = {
openOrActivate(appId: WorkbarAppId): Promise<WorkbarTabInstance>
openOrActivate(intent: WorkbarOpenIntent): Promise<WorkbarTabInstance>
}
```
若当前 `WorkbarShell` 没有命令式入口,先增加窄的回调或受控请求 Props,不引入全局事件总线。
工作区意图始终激活注册表中的不可关闭单实例;仅在迁移或布局损坏时补回。浏览器意图按
`conversationId` 激活最近使用的匹配实例,没有匹配实例时创建一个。工作栏“+”不走该去重
路径,选择浏览器时始终创建新实例。
### 3.3 终端匹配
工作栏“+”目录选择终端时每次创建新实例;侧栏系统工具按钮执行“进入当前终端”,采用:
@@ -330,7 +336,7 @@ resourceMonitor.getLocalSnapshot(input: {
- 固定入口顺序不受三个应用显隐组合影响。
- 运行记录和设置没有显隐控件。
- 四个系统工具按钮具有名称、工具提示、键盘行为和选中状态。
- 文件和浏览器复用单实例;终端优先聚焦当前执行空间实例。
- 文件复用不可关闭单实例;浏览器和终端按当前上下文聚焦匹配实例,工作栏“+”允许显式多实例。
- 工具按钮展开工作栏但不改变当前主页面。
- 显隐更新成功后主导航立即变化,失败时 Switch 和主导航恢复原状态。
- 窄窗口下工具按钮不重叠,焦点恢复正确。
@@ -365,13 +371,67 @@ resourceMonitor.getLocalSnapshot(input: {
## 11. 实施顺序
1. 增加应用导航设置 schema、迁移和设置界面。
2. 按即时保存结果组合主导航,并补充失败恢复。
3. 增加资源快照契约、Main 采集服务和 Preload API。
4. 为工作栏增加 `resources` 单实例和 `ResourceMonitorPanel`。
5. 拆分侧栏底部设置按钮,加入四个系统工具按钮。
6. 为工作栏增加 `openOrActivate` 意图接口和终端匹配规则。
7. 接入选中状态、焦点恢复、无障碍文案和响应式样式。
8. 完成 Renderer、契约、迁移和跨平台回归测试。
实施范围以 [PRD 分期](./prd.md#8-分期)为准。本节替代旧实施顺序;前文关于四个左侧
系统工具按钮、仅持久化隐藏 ID 的契约需在实现前同步,不能直接按旧规格开发。
插件注册与插件市场不与首期改动一并实施。
1. 同步 PRD 功能要求、User Stories、逻辑、UI 与技术契约,收敛首期应用中心样式。
2. 接入现有内置应用页面与最底部应用中心入口,保留独立设置和固定运行记录入口。
3. 为常驻显隐和排序设计最小持久化契约,保留已发布的相关设置与用户数据,按保存结果更新导航。
4. 接入应用设置的共同界面与既有设置更新路径,先验证魔法笔记多入口状态一致性。
5. 验证右侧现有文件、终端、浏览器 Tab;移除计划中的左下角工具快捷条和专用于该快捷条的意图接口工作。
6. 单独实现资源快照契约、Main 采集服务、Preload API 与右侧 `resources` 单实例面板。
7. 验证键盘、焦点、浅深主题、窄窗口、设置失败、重启持久化和跨平台行为。
首期使用内置应用清单,不依赖远程商店、插件注册或安装服务。商店目录、推荐、安装与
更新机制在后续独立设计中确定;Demo 中的模拟商店不接入首期产品界面。
后续设计输入见 [FR-15](./prd.md#fr-15-私有化市场与-yaml-交换后续) 与
[FR-16](./prd.md#fr-16-本机模型管理入口新增计划项):
- 私有市场的 YAML Schema 应由桌面本地导入与 ShareServer 发布共用,具体协议与字段在
市场功能设计中确定。市场 URL 按设备保存,认证信息沿用受保护凭据存储;导出仅包含
可迁移应用定义与非敏感配置。
- 程序包与模型资源单独分发。受管下载声明不可变版本、预期字节数和 SHA-256,下载开始后
固定来源,失败不静默切源;下载地址与重定向范围由 Main 验证,不接受 Renderer 任意覆盖。
市场来源配置不覆盖现有模型、应用更新或 Runtime 下载源设置。
- ShareServer 的市场发布与分发实现由其独立仓库承接,本仓库先记录依赖,不将桌面需求
记录标为服务端已实现。
- 本机模型管理实施前盘点现有 OCR、向量、语音及推理服务的生命周期接口,优先复用其
Main 服务。只为实际支持的操作提供 typed IPC;区分受管子进程、外部服务和进程内模型,
不把直连模型 HTTP 请求等同于独立模型进程。
- 模型管理的详细逻辑需确定共享服务的使用方识别、忙碌时停止与重启、退出应用时的释放
行为,再接入管理页面并验证实际引擎。该工作单独排期,不依赖市场上线。
## 12. 项目活动汇总
对应 [FR-14](./prd.md#fr-14-项目活动汇总),按[活动逻辑](./logic-design.md#9-项目活动汇总)
派生状态,界面行为见[活动界面](./ui-design.md#10-项目活动汇总)。
- `conversation-activity.ts` 从 `App.tsx` 的会话、`activeConversationIds`、全部可见
`assistantTasks` 和项目元数据派生会话行、全局及项目计数,不新增持久化活动副本。
`ProjectActivity` 在有活动时渲染汇总与项目、会话级联菜单,`ProjectSwitcher` 复用项目计数。
- `App` 向 `ProjectActivity` 传入项目元数据、活动行、侧栏可见状态与既有精确跳转回调。
菜单按项目 ID 分组并记忆化派生集合;排序仅影响展示,不改聚合状态或持久化数据。
- 两层菜单在同一 body Portal 内相接,桌面会话层使用独立 fixed 定位,不参与项目层高度计算。
项目层锚定摘要,会话层锚定选中项目行并独立限制窗口边界。布局在打开、层级变化、实时数据更新、窗口缩放和
滚动时按锚点重算;窄窗口隐藏项目面板并保留返回操作。菜单不调用 Modal 焦点隔离,
捕获阶段消费 Escape,防止触发窄侧栏的文档级监听;Tab 关闭后交还正常焦点顺序。
菜单关闭与焦点恢复在导航回调之前同步完成。
- `role="menu"` 使浮层直接进入共享 `browser-viewport-occlusion.ts` 相交检测。未增加
浏览器实例的特殊隐藏条件,也不关闭或重建浏览器会话。
- 持久化快照刷新时,同一条仍在流式输出的消息保留本地待审批、待回答信息,持久化终态
则清除这些信息。响应成功后对应 Task 立即退出等待状态;响应期间到达的新问题、审批或
终态不能被旧响应覆盖。
- `AssistantDatabase.listTasks()` 保留最近历史窗口(默认 100 条),额外包含窗口外当前
运行或等待处理的可见 Task;`activeVisibleTaskSelect` 与 `toTask` 的有效状态保持一致,
待调度运行规范化为 `queued`,不因旧 Task 状态被误纳入实时集合。
- `listConversations()` 保留最近 100 条活动会话,同时包含上述 Task 关联的较早活动会话,
以及含 `streaming` 消息的较早活动会话。两类列表去重后仍按原时间顺序返回,结果允许超过
历史窗口大小,避免后台工作被最近记录挤出。
- `openActivityConversation` 按确切 Conversation ID 导航;内存中缺失时刷新持久化会话
列表并合并,再查找同一 ID。刷新失败或会话已不存在时显示通知,不回退到项目最新会话。
设置离开检查通过后才提交项目、会话及界面清理状态。
- 远程恢复成功提示沿用独立的自动消失计时器;活动汇总更新不重置或替代该计时器。
验证记录见[项目活动级联实施进度](./progress.md)。本次只改 Renderer 展示与交互,远程
Runtime、Agent 协议和桌面到 Agent 的执行路径未改变。
@@ -19,6 +19,7 @@
```text
品牌
项目选择
项目活动汇总(仅有活动时显示)
新对话
搜索
@@ -44,9 +45,9 @@
| 位置 | 图标 | 可见工具提示 | 点击结果 |
| --- | --- | --- | --- |
| 1 | `FolderOpen` | 文件 | 打开或聚焦工作区面板 |
| 1 | `FolderOpen` | 文件 | 聚焦不可关闭的工作区面板 |
| 2 | `SquareTerminal` | 终端 | 打开或聚焦当前执行空间终端 |
| 3 | `Globe` | 浏览器 | 打开或聚焦受控浏览器面板 |
| 3 | `Globe` | 浏览器 | 聚焦当前 Conversation 的匹配浏览器实例 |
| 4 | `Activity` | 资源监控 | 打开或聚焦本机资源监控面板 |
| 末位 | `Settings` | 设置 | 打开设置页面 |
@@ -119,9 +120,10 @@
点击系统工具后,右侧工作栏按现有布局回流打开。主内容页面保持不变,侧栏不渲染工具内容。
- 文件:激活“工作区”Tab,焦点进入目录区域或错误修复入口。
- 文件:激活默认存在且不可关闭的“工作区”Tab,焦点进入目录区域或错误修复入口。
- 终端:激活匹配终端,焦点进入终端输入区;新建期间保留按钮宽度和进度状态。
- 浏览器:激活“浏览器”Tab,焦点进入工具栏;没有会话时进入空状态主操作。
- 浏览器:激活绑定当前 Conversation 的最近使用“浏览器”Tab;没有匹配实例时创建一个,
没有 Conversation 时按产品规则复用最近实例或进入待绑定空状态。
- 资源监控:激活“资源监控”Tab,焦点进入指标摘要;首个样本加载后开始绘制历史。
后台事件可以更新徽标,不能自动展开工作栏或切换当前 Tab。由用户点击快捷按钮触发的打开
@@ -201,3 +203,32 @@ CPU 4% · 内存 620 MB · 桌面进程 7
插件市场交付后另行定义插件区域、固定数量、溢出和权限界面。系统入口与四个系统工具的位置
保持保留,不向插件开放。
## 10. 项目活动汇总
对应 [FR-14](./prd.md#fr-14-项目活动汇总),状态与计数遵守
[活动逻辑](./logic-design.md#9-项目活动汇总)。
- 全局汇总按钮仅在有活动时显示在项目选择器下方,紧凑显示待处理与运行中数量,空闲时整行隐藏,
右侧显示展开箭头,与下方新建会话保留 `8px` 间距。可访问名称明确数量属于“全项目活动”。
- 项目菜单在项目行显示同口径数量,保留“本地项目 / 远程项目 / 远程通道”分组和 SSH Host
层级,不因活动状态重排项目。数量为零时不显示标记,连接状态仍独立显示。
项目名独占一行,状态数量排列在名称下方,避免窄侧栏中数量挤占名称宽度。
- 点击汇总在按钮下方打开项目菜单,只列有活动会话的项目,每项显示项目名和同口径计数。
桌面鼠标悬停或点击项目展开右侧会话菜单,右侧空间不足且左侧可容纳时向左展开。两个面板
相接,各自拥有边框、圆角与阴影;会话层对齐所选项目行,底部空间不足时仅上移会话层,
不改变项目层的位置或高度。指针离开项目行进入会话层时不收起。展开项目不切换当前会话。
- 会话层顶部显示项目名,每行显示完整可换行的会话标题与明确状态文字,待处理会话排在
运行中会话之前。项目行保留子菜单箭头,会话行不显示箭头。不展开 Task、Job 或 Run 列表。
- 点击会话先关闭菜单并恢复入口焦点,再请求进入该条会话;允许离开当前页面后,
选择所属项目、清除搜索和该会话未读标记、清除任务选择与会话操作菜单,窄窗口同时关闭侧栏。
设置页沿用未保存修改的离开检查,取消离开时保留原页面。
- 菜单挂载到 body,使用 `menu`、`menuitem`、`aria-haspopup`、`aria-expanded` 与控制关系,
不隔离背景、不增加遮罩。原生浏览器相交遮挡沿用共享机制。
- 打开后焦点进入首个项目;上下方向键循环选择当前层项目或会话,Home、End 到首尾,
Enter、Space 激活,右方向键进入会话层。左方向键或 Escape 返回项目层并恢复对应项目
焦点;顶层 Escape 关闭并恢复汇总焦点,不继续关闭窄侧栏。Tab 关闭菜单后继续正常焦点
顺序;点击外部、焦点离开、再次点击入口或隐藏侧栏也关闭菜单。
- 单面板宽度为 `264px`,窗口小于 `620px` 或项目菜单两侧均无法容纳子菜单时,就地进入会话层并显示“返回项目列表”,
触控无需悬停。浮层距窗口边缘至少 `16px`,每层最大高度为 `480px`,短窗口进一步限制
高度并独立滚动。活动全部结束时,关闭已打开菜单并隐藏摘要。
@@ -105,7 +105,7 @@
- Given 当前项目有有效根目录
- When 用户点击文件按钮
- Then 助手工作栏展开
- And 已有工作区面板时聚焦该实例,没有时创建单实例
- And 聚焦默认存在且不可关闭的工作区单实例
- And 文件按钮通过选中状态表明该面板正在显示
### US-C2 处理无效工作区
@@ -114,7 +114,7 @@
验收:
- Given 工作区面板尚未创建或处于跟随模式
- Given 工作区面板处于跟随模式
- And 当前项目和默认工作区均不可用
- When 用户点击文件按钮
- Then 工作区面板仍打开
@@ -160,15 +160,23 @@
- Given 浏览器面板跟随当前上下文且当前会话已有浏览器会话
- When 用户点击浏览器按钮
- Then 工作栏聚焦同一个浏览器会话
- And 不打开系统默认浏览器或第二个 WebView
- Given 浏览器面板已固定到其他会话
- Then 工作栏聚焦绑定当前 Conversation 的最近使用浏览器实例
- And 不打开系统默认浏览器或无意创建重复实例
- Given 现有浏览器实例都绑定到其他 Conversation
- When 用户点击浏览器按钮
- Then 工作栏聚焦该面板并保持固定目标
- Given 浏览器面板尚未创建或处于跟随模式
- And 当前没有活动对话
- Then 工作栏创建并聚焦绑定当前 Conversation 的新浏览器实例
- Given 当前没有活动 Conversation
- And 已存在浏览器实例
- When 用户点击浏览器按钮
- Then 面板说明需要选择或创建对话
- Then 工作栏聚焦最近使用的浏览器实例并保持其目标
- Given 当前没有活动 Conversation
- And 不存在浏览器实例
- When 用户点击浏览器按钮
- Then 创建待选择目标的空实例
- Given 用户需要同一 Conversation 的另一个独立页面
- When 用户从工作栏“+”选择浏览器
- Then 每次创建拥有独立页面和导航状态的新实例
- And 同一 Conversation 内共享隔离登录上下文
### US-C6 查看本机资源
@@ -226,3 +234,74 @@
- 不因宽度不足隐藏文件、终端、浏览器、资源监控或设置
- 触控点击区域符合统一界面规范
- 关闭侧栏抽屉后焦点返回原触发按钮
## 6. Epic E:项目活动级联
### US-E1 从摘要进入确切会话
对应 [FR-14](./prd.md#fr-14-项目活动汇总)。用户在当前项目工作时,可从项目选择器下方
查看跨项目活动,先定位项目,再进入需要处理的会话。
- Given 多个项目存在运行中和待处理会话,当前搜索仅匹配当前项目
- When 打开全项目活动并悬停或点击另一个项目
- Then 项目顺序保持稳定,该项目会话按待处理优先展示,当前页面和会话保持原选择
- When 点击会话
- Then 进入该确切 Conversation ID,清除搜索、任务选择和会话菜单,窄窗口关闭侧栏
- Given 设置页有未保存修改
- Then 会话跳转仍需通过既有离开检查,取消离开保留设置草稿和原项目
### US-E2 键盘、窄窗口与实时结束
- Given 菜单已打开
- Then 上下方向键与 Home、End 选择当前层,右方向键或 Enter 进入会话层,左方向键或
Escape 返回;再次 Escape 关闭活动菜单并恢复摘要焦点,窄侧栏保持打开
- Given 窗口小于 `620px`
- Then 点击项目后仅显示会话层,可通过返回按钮回到项目列表;长列表可滚动到最后一项
- Given 鼠标从项目行移向相接的会话面板
- Then 子菜单保持打开,点击会话正常导航
- Given 当前项目的活动全部结束
- Then 返回项目层并恢复可用焦点;全局无活动时整行隐藏摘要并关闭菜单,新活动到来时只恢复摘要
- When 点击外部、焦点离开、按 Tab 或隐藏侧栏
- Then 菜单关闭,后台新活动不自动重新展开
## 7. 后续需求场景
本节对应新增计划,不计入应用中心首期完成条件。
### US-F1 连接组织应用市场
对应 [FR-15](./prd.md#fr-15-私有化市场与-yaml-交换后续)。
- Given 组织已部署提供应用市场的 ShareServer
- When 用户配置市场 URL,并在来源要求时完成登录
- Then 可以浏览该来源的应用目录、详情及可获取版本
- Given 市场暂时不可达
- Then 显示失败原因与重试入口,已安装和内置应用仍可打开,不切换公共市场
### US-F2 导入导出应用定义
对应 FR-15。
- Given 用户需要在私有部署间迁移应用
- When 导出应用为 `.yml` 并在另一台设备导入
- Then 预览应用身份、版本、可迁移配置和依赖,凭据与业务数据不包含在导出文件中
- Given 同标识应用已存在
- Then 用户可选择更新或取消,取消后原应用与配置保持不变
- Given 文件格式无效、版本不支持或离线环境缺失程序包或模型资源
- Then 显示具体问题与补充要求,不将应用误报为可运行,也不自动启动模型或任务
### US-F3 管理本机模型服务
对应 [FR-16](./prd.md#fr-16-本机模型管理入口新增计划项)。
- Given 用户使用依赖本机模型的应用
- When 从应用中心或常驻菜单打开“本机模型”
- Then 可查看已接入模型的状态、管理方式及使用方,两处入口访问同一页面
- Given 多个应用共用一个 GoodBuddy 受管模型服务
- Then 展示同一服务及使用方,启动操作不重复创建进程
- When 用户停止或重启仍有任务使用的受管服务
- Then 先展示任务影响供用户决定,取消后服务保持运行;操作失败显示原因与实际状态
- Given 模型服务由外部程序启动,且未接入其管理接口
- Then 展示“外部管理”与连接配置,不提供停止或重启
- When 用户关闭管理页或取消常驻
- Then 模型运行状态不变
+14 -4
View File
@@ -7,9 +7,19 @@
| 文档 | 权威职责 |
| --- | --- |
| [通用助手工作栏与执行空间 PRD](./prd.md) | 工作栏整体产品范围、执行空间和各工作面板归属 |
| [多终端页签 PRD](./terminal-tabs-prd.md) | 多终端 Tab、目标选择、关闭确认和终端生命周期 |
| [多终端页签 PRD](./terminal-tabs-prd.md) | 工作栏应用的固定、可关闭、单实例和多实例策略,以及多终端生命周期 |
| [浏览器多实例技术设计](./browser-tabs-technical-design.md) | 浏览器 Tab 身份、Conversation 绑定、IPC、BrowserService 和 MCP 路由 |
| [实现与验证进度](./progress.md) | 工作区与 Runtime 相关改动的实现事实、验证证据与剩余阻塞 |
| [Runtime 交互边界](./runtime-interactions.md) | 原生权限、结构化问答、后台拒绝、终端交互、托管 Runtime 并行与输出及消息底部请求状态的适配范围 |
| [执行记录存储与升级回收](./execution-history-storage.md) | 子任务差量存储、远程去重、旧库转换、会话摘要与完整历史按需读取、内存引用释放 |
| [Runtime 交互边界](./runtime-interactions.md) | 原生权限、结构化问答、后台拒绝、终端交互、并行与输出、消息请求状态及资源生命周期规则 |
| [Runtime 进程复用技术设计](./runtime-process-reuse-technical-design.md) | 跨 Runtime 资源调研、锁定协议证据、进程与会话拆分、远端模型桥、实施顺序和性能验收 |
| [Runtime 执行清单技术设计](./runtime-checklist-technical-design.md) | OC/CN 原生清单提取、远程 Agent 传递复用、消息恢复与真实部署验收;尚未实现 |
当前尚未拆出独立 User Stories、功能逻辑、UI 设计和技术设计文档;相关内容仍保留在
两份现有 PRD 中,后续实际维护该功能时按职责拆分。
当前尚未拆出独立 User Stories、功能逻辑和 UI 设计文档;产品场景与验收保留在现有 PRD,
Runtime 资源状态规则由交互边界文档维护。浏览器多实例和 Runtime 进程复用涉及不同的
跨进程身份与工具路由,分别使用技术设计文档维护。
执行清单的需求、场景和顶部布局见 [PRD](./prd.md#131-runtime-执行清单),状态规则见
[Runtime 交互边界](./runtime-interactions.md#runtime-执行清单)。本目录拥有该功能;
[Task 与 Job](../task-and-job/README.md)拥有产品任务领域,
[远程主机](../remote-host/technical-design.md#执行清单接入范围)拥有远程部署与连接边界。
@@ -0,0 +1,216 @@
# 浏览器多实例技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 基础多实例已实施,Agent 多 Tab 工具待需求 |
| 版本 | 1.0 |
| 日期 | 2026-09-13 |
| 关联 PRD | [通用助手工作栏与执行空间 PRD](./prd.md) |
| 页签策略 | [助手工作栏多终端页签 PRD](./terminal-tabs-prd.md) |
## 1. 目标与边界
浏览器改为可关闭的多实例工作栏应用。每个工作栏浏览器实例对应一个独立 Browser Tab,拥有
独立页面、导航状态、操作队列、画面状态和元素引用空间。同一 Conversation 下的多个 Tab
共享隔离存储与代理上下文,使登录状态符合常规浏览器预期。
本设计不把多个工作栏 Tab 实现为同一页面的重复入口,不允许用户切换 Tab 后改变正在执行的
Agent 工具目标,也不在首期要求模型主动管理多个 Tab。
## 2. 改造前限制
改造前的链路以 `conversationId` 作为唯一浏览器身份:
- Renderer 只保存每个 Conversation 的一份 `BrowserLiveState`。
- Browser IPC、Preload API 和 `BrowserService` 操作只携带 `conversationId`。
- Main 每个 Conversation 只创建一个 Session、一个 `WebContentsView` 和一个 CDP Driver。
- 请求级 MCP 能力只保存 `browserConversationId`。
- snapshot 元素 `ref` 只在一个 Driver 的引用空间内有效。
因此只把工作栏注册策略改成 `multiple` 会导致多个 Tab 控制同一页面,并产生 viewport、清理和
元素引用竞态,不能作为可交付实现。
## 3. 身份与所有权
```ts
type BrowserTabId = string
type ConversationBrowserContext = {
conversationId: string
partitionId: string
tabs: Map<BrowserTabId, BrowserTab>
primaryTabId?: BrowserTabId
}
type BrowserTab = {
id: BrowserTabId
conversationId: string
view: WebContentsView
driver: CdpBrowserDriver
state: BrowserLiveState
}
```
- Conversation Browser Context 拥有隔离 partition、代理配置和全部 Tab。
- Browser Tab 拥有页面、Driver、串行操作队列、snapshot 代次、idle 状态和可见 viewport。
- `BrowserTabId` 是 Main 签发的不透明 ID。Renderer 和 Runtime 不能自行选择或复用。
- 工作栏实例保存公开的逻辑实例 ID 和 Conversation 绑定;进程重启后不恢复旧
`WebContentsView`,首次使用时由 Main 为该逻辑实例创建新的 Browser Tab。
- 删除 Conversation 时关闭该 Context 下全部 Tab;关闭一个工作栏浏览器实例只关闭对应
Browser Tab,不得调用 Conversation 级 Runtime 释放路径。
## 4. Renderer 与 viewport
Renderer 状态改为:
```ts
Record<ConversationId, Record<BrowserTabId, BrowserLiveState>>
```
每个浏览器工作栏实例持有自己的 `browserTabId`。只有当前可见实例可以提交 viewport。每次
获得可见权时生成新的 UUID `leaseToken`;提交请求携带 `browserTabId` 和 token,清理请求必须
回传同一个 token。Main 只接受当前 token 的清理,避免已隐藏实例的延迟 cleanup 隐藏新实例,
也避免 Renderer 重载后从零开始的数字序列无法覆盖旧租约。
非活动 Browser Tab 保持页面和状态,但隐藏 `WebContentsView` 并停止非必要画面采集。工作栏
收起、切换主页面和窗口隐藏时采用相同规则,不销毁 Tab。
普通导航、快照与页面操作只同步轻量导航状态,不自动截图、等待画面或传输 JPEG;只有显式
`browser_screenshot` 调用生成图片。
`BrowserViewport` 统一观察 body Portal 的插入、移除和可见属性变化,以及浮层尺寸、
窗口尺寸、滚动和过渡结束。`browser-viewport-occlusion.ts` 按共享语义识别
`aria-modal="true"`、dialog、alertdialog、menu 和应用通知:可见应用级 Modal 始终释放
viewport,其余浮层只在与浏览器区域相交时释放。隐藏节点和隐藏祖先不阻塞浏览器。
最后一个遮挡消失后,只有当前活动实例恢复同一 viewport 租约;网页、历史和页面状态不变。
终端关闭确认复用此机制,不再维护单独的隐藏条件。页面无需逐个登记弹窗,也不能靠提高
CSS `z-index` 覆盖原生子视图。此逻辑仅涉及 Desktop Renderer 的显示租约,不改变
Runtime、MCP 或远端 Agent 的请求、导航与生命周期。
浏览器应用声明 `visibleAcrossContextSwitches: true`,打开的 Tab 跨项目、会话切换继续显示。
切换活动 Conversation 不改变当前选中的浏览器或其 `targetRef.conversationId`;用户在会话 B
查看会话 A 的浏览器时,viewport 和手动导航仍指向 A 的原 Tab。新建实例绑定 B。仅未绑定的
默认或恢复实例可在首次使用时取得当前会话绑定。
Tab 的跨会话可见性与 MCP 授权分开处理。B 的请求不能选择属于 A 的可见 Tab;A 已取得的
capability 在用户切到 B 后仍固定路由至 A 的原 Tab;使用租约不阻止用户关闭该 Tab。
内部点击实例只同步应用类型,不再次按当前 Conversation 改选实例。Main 状态携带对应的
`workbarInstanceId`,Renderer 据此绑定请求创建的同一 Tab,而不是另建空白页。收到 `stopped`
后清除该实例的旧 Tab 绑定,下一次使用通过原逻辑实例重新创建,保留地址草稿。
刷新可用性读取 Main 的已提交 URL,不使用地址草稿或仅凭 `sessionActive` 判断。
侧栏操作错误以当前工作栏实例和面板展开状态为作用域;切换或关闭时清空,旧作用域的
异步回调不再写入错误,即使用户随后返回同一实例。
请求创建的实例也遵守现有 32 个工作栏页签上限;满额时保留 Main 页面及其状态,并就地提示
关闭一个可关闭页签。释放位置后再绑定该页面,不写入超限布局或丢弃 Agent 页面。
## 5. IPC 与服务接口
共享契约增加有界操作:
```ts
browser.createTab({ conversationId, workbarInstanceId })
browser.listTabs({ conversationId })
browser.closeTab({ conversationId, tabId })
browser.setViewport({ conversationId, tabId, leaseToken, bounds })
browser.navigate({ conversationId, tabId, url })
browser.back({ conversationId, tabId })
browser.reload({ conversationId, tabId })
browser.stopLoading({ conversationId, tabId })
```
Main 必须验证 `tabId` 属于同一 `conversationId` 和当前窗口。BrowserService 将现有
Conversation 级 slot 拆为 Context 与 Tab 两层;同一 Tab 内操作继续串行,不同 Tab 可以并行,
但仍受窗口级和 Conversation 级有界数量限制。
现有 Conversation 级 `stop/release` 保留为销毁完整浏览器上下文的操作,不能用作关闭单个
Browser Tab。
## 6. Agent 与 MCP 路由
### 6.1 兼容阶段
第一阶段不修改模型可见的现有浏览器工具参数。模型请求开始时,Main 按以下顺序选择并冻结
`boundBrowserTabId`:
1. 当前可见且绑定该 Conversation 的 Browser Tab。
2. 该 Conversation 的 `primaryTabId`。
3. 预留 primary Browser Tab 身份,首次实际浏览器操作时才创建页面与 Conversation Context。
未使用浏览器的请求只持有随请求释放的内存预留,不创建 Chromium 资源、不触发浏览器 UI、
也不占用活动浏览器会话名额。物化后使用同一 Tab 身份和工作栏实例归属;取消、能力撤销及
服务关闭释放未使用的预留,不保存到磁盘。编程 Subagent 继承父请求的 Browser Tab 与浏览器
Conversation 归属,不新建租约或把页面重新归属到子级对话。
直连模型上下文和请求级 MCP capability 同时保存 `conversationId` 与
`boundBrowserTabId`。请求执行期间,用户切换、创建或关闭其他工作栏 Tab 不改变工具目标;
用户可直接关闭模型正在使用的 Tab。关闭释放该 Tab 的页面和使用租约,中止其浏览器操作,
不取消整个模型请求、不撤销 MCP capability,也不关闭同级 Tab。系统不得把进行中的操作改投其他 Tab。
关闭后,快照等非导航工具返回“浏览器标签页已关闭,请调用 browser_navigate 打开新标签页”,
不自动创建页面或使用现有 primary/sibling。只有显式且参数有效的 `browser_navigate` 才创建
专用替代 Tab 并绑定后续工具;旧 Tab 身份与元素引用不复用。Gateway 与直连 Provider 均隔离
单次工具取消信号、Tab 使用租约信号和请求信号,取消一次工具调用不终止后续调用。
该阶段 MCP transport、endpoint 和 token 生命周期不变,只扩展服务端 capability 内容和内部
路由。工具调用使用当前请求绑定的 Tab,只有上述显式导航恢复会更换绑定。
### 6.2 Agent 多 Tab 阶段
只有明确需要 Agent 管理多个页面时,才增加:
- `browser_tab_list`
- `browser_tab_open`
- `browser_tab_close`
导航、snapshot、back 和 screenshot 增加可选 `tabId`;省略时使用请求绑定的默认 Tab。
`browser_snapshot` 返回 `tabId` 和 snapshot 代次。click、type 和 select 必须校验 capability、
`tabId`、引用代次和元素 `ref` 属于同一 Driver,禁止跨 Tab 猜测或解析引用。
## 7. 工作栏创建与快捷入口
- 从“+”选择浏览器时始终创建新工作栏实例,并默认绑定当前 Conversation。
- 没有活动 Conversation 时创建待绑定实例,不创建无归属 Main Browser Context。
- 主侧栏浏览器快捷按钮优先激活绑定当前 Conversation 的最近使用实例;没有匹配实例时创建
一个。没有活动 Conversation 时激活最近使用的浏览器实例;完全不存在时创建待绑定实例。
- New browser instances use a numbered browser label followed by the active Conversation title, when
available. The number avoids existing titles; restoring the layout preserves each saved title.
The address bar shows only that instance's page URL. Binding a numbered instance does not overwrite
its title; the default unbound browser receives the Conversation label when first bound.
- 关闭工作栏 Browser Tab 只释放对应 Browser Tab。关闭浏览器上下文是独立危险操作,需要
明确说明会影响该 Conversation 下的全部 Tab 和 Agent 浏览器状态。
## 8. 持久化与恢复
`createTab` reuses a live tab only for the same Conversation and workbar instance ID. A new workbar
instance can adopt the primary tab only when its request creates the Conversation context. An existing
primary, including one opened by an Agent, is not adopted by a new instance. New sibling tabs start at
`about:blank` with no navigation history and share the Conversation's cookie partition.
持久化工作栏实例 ID、顺序、自定义标题和公开 Conversation 绑定,不持久化页面进程、CDP
引用、snapshot、viewport lease、Cookie、Token 或 Main `BrowserTabId`。应用重启后实例显示
未启动状态,展开浏览器面板或 Agent 首次使用时按原绑定创建新 Tab。
绑定的 Conversation 已删除或不可见时保留实例并显示目标失效,不自动绑定当前 Conversation。
## 9. 限制与验收
- 分别限制每个 Conversation 的 Tab 数和每个窗口的总活动 Tab 数;达到上限时在创建前反馈。
- 两个 Tab 的地址、历史、加载状态、截图和元素引用互不覆盖。
- 同一 Conversation 的 Tab 共享登录状态,不同 Conversation 保持隔离。
- 延迟 viewport cleanup 不能隐藏另一个已激活 Tab。
- 一个 Tab 的元素 `ref` 不能在另一个 Tab 中执行。
- 用户切换工作栏 Tab 不改变进行中模型请求的 MCP 浏览器目标。
- 关闭一个 Tab 不释放 Conversation Runtime,也不关闭同一 Conversation 的其他 Tab。
- 关闭已租用 Tab 后,同一 MCP 会话仍可列出工具;旧目标快照报错且不创建页面,显式导航创建
不同身份的替代 Tab,同级页面、导航状态与其他 Conversation 的可见 viewport 保持不变。
- 旧版无 `tabId` 的模型工具调用仍操作请求绑定的默认 Tab。
## 10. 实施顺序
1. 引入 Browser Context 与 Browser Tab 两层身份,拆分 partition 和页面生命周期。
2. 扩展 Shared、Preload、IPC 与 BrowserService 的 `tabId` 和 viewport `leaseToken`。
3. 改造 Renderer 状态与工作栏实例,开放浏览器 `multiple` 策略和单 Tab 关闭。
4. 将直连模型与 MCP capability 绑定到请求开始时确定的 Browser Tab。
5. 完成跨 Tab 引用隔离、并行队列、共享 Cookie、关闭和恢复测试。
6. 有明确 Agent 多页面需求后再开放模型可见的 Tab 管理工具。
@@ -0,0 +1,96 @@
# 执行记录存储与升级回收
通用迁移、备份、性能测量和发布要求见
[数据库迁移与大数据升级指南](../../development/database-migrations.md)。
## 用户行为
子任务仍实时显示完整的文字、思考、工具过程和最终结果。运行时间较长、工具输出较多时,
数据库不再为每个流式更新重复保存此前的整份过程;本机和托管 SSH OpenCode 使用相同
的桌面存储机制。此修复不关闭子任务、不截短已有过程,也不增加 Execute 权限门槛。
已有数据库在首次升级时自动处理。启动页铺满窗口,显示处理数量、全宽进度条和近期
处理速度;处理时间取决于历史数据量与磁盘速度,不承诺固定几分钟。用户可以退出,
下次启动重新检查并继续转换未完成的数据;完成后回收空闲页并进入应用。失败时留在
该页提供重试与退出,磁盘空间不足会明确说明,不删除聊天来腾出空间。
## 存储规则
- `messages.metadata_json` 继续保存供会话显示的完整子任务过程。
- `task_events` 的 `subagent` 内容由共享 `SubagentProgressStorage` 编码。
`progressUpdates` 只保存文字追加、变更的块,或确有删除/重排时的完整重置。
完全相同的过程使用空更新数组。工具字段按值比较,缺省字段与 `undefined` 等价,
不因 JSON 省略空字段而重复写入整个工具结果;空字符串与实际输出/错误变化仍保留。
其他事件内容不改变。
- 每个任务内按事件 ID 顺序、按 `childTaskId` 独立还原过程。事件 ID、顺序、状态、
最终输出、错误、时间及远程来源字段均保留,不按天数裁剪或删除旧事件。
- Runtime、Agent transcript、Preload 和 Renderer 的公共 `SubagentEvent` 仍传完整
`progress`;差量表示只存在于桌面数据库内部,不要求更新远端 Agent 协议。
- 远程消息投影与事件插入仍共用 SQLite 事务。重放时还原已保存内容进行等值校验,
相同来源重复事件不重复投影,不同内容继续报冲突。内存过程缓存通过已提交事件 ID
核对;事务回滚或重新打开后从数据库重建,不保存独立快照或回收日志。
- 存储层最多缓存 16 个任务;收到任务完成、错误、取消或中断的终态记录后立即移除该任务
的缓存引用,不影响其他会话。若事务随后回滚或仍收到该任务的后续事件,按已提交记录重建。
释放引用不等于进程 RSS 立即下降,垃圾回收和 Runtime 自身会话缓存另按生命周期回收。
## 会话列表读取与前端保留
列表读取与数据库存储是两个不同层次。`messages.metadata_json` 继续保留完整过程,
本次读取优化不修改 schema、迁移或用户数据:
- Renderer 启动及焦点、会话、队列刷新使用 `conversations.listSummaries(detailIds)`。
普通未打开会话只返回标题、项目、上下文设置和消息数量/首条角色,不解析或跨 IPC
传输正文、工具、子任务及图片操作元数据。
- 当前会话、现有 KeepAlive 视图,以及 Main 仍持有请求、数据库中仍有活动 Task 或
streaming 消息的会话返回完整消息。保留既有最近 100 条加较早活动会话的列表规则,
请求中的已缓存会话即使更早也仍能刷新。
- 打开历史通过 `conversations.get(id)` 读取全部消息,不使用会截断历史的分页上限。
加载时明确显示进度;失败保留会话与输入,提供原地重试。发送、队列派发和压缩上下文
必须先确认完整历史;复制与导出临时读取完整内容,不把未打开会话留入视图缓存。
- 搜索由 Main 在 SQLite 中读取标题与正文,不读取过程元数据,保持原来的 Unicode
大小写匹配和字面子串规则。范围包含 Renderer 已保留的较早会话;前端同时匹配尚未
落库的消息。搜索状态只保留命中的 ID,不额外持有整份历史对象。
- KeepAlive 淘汰后,在现有保存周期中释放已经确认落库且空闲的详情及持久化比较引用。
未保存消息、正在执行的会话、待答/待审批内容不被淘汰。读取中的请求按会话合并;
晚到摘要不能覆盖刚打开的历史或新到的后台审批,摘要附带的实时新增消息仍增量保存。
- `messageSummary` 仅为只读列表标记,不进入持久化 header。保存继续使用已有逐消息
增量接口,空消息数组不代表删除历史。原有完整 `list()` 与数据库 `getConversation()`
接口保留供其他调用方使用。
- 图片状态通过同一合并函数处理。本地更新与 Main 图片操作按原有时间优先级合并,
未变化消息返回原对象,避免每次列表刷新后把无变化的整个历史当作脏消息再次保存。
- 本机和托管 SSH 的 Renderer/Main 读取、持久化与恢复投影共用以上实现。Agent 的
transcript、原生 Session、权限及模型桥协议未改变;完整列表、摘要和详情都附带
Main 当前恢复请求的问题投影。
此优化不等于工具折叠内容的延迟渲染,也不保证操作系统立即归还全部 Private Commit。
实际测量与适用边界见[实现与验证进度](./progress.md)。
## 旧库升级
桌面在打开业务数据库之前启动独立 Node 工作线程,使用同一套编码和还原函数转换旧库。
按任务有序读取,每批最多 32 条,提交后报告进度;SQLite 保留事务原子性及正常 WAL
自动 checkpoint,每个任务结束时截断 WAL。读取缓存为 64 MiB,避免 1 MiB 级历史
快照在过小缓存中反复读取;不以关闭事务或同步落盘换取速度。
转换可以混合读取旧完整记录与已经转换的记录,也会重新编码 schema 34 中重复的工具块
更新。因此退出、磁盘写入失败后可以重新扫描并继续;已是相同 JSON 的记录不重复更新,
不建立额外的恢复状态表。最后运行 SQLite `VACUUM` 回收已释放的空间,而非
在转换前复制整份膨胀内容。回收仍需要额外可用磁盘空间,失败保留已提交转换并允许重试。
当前源码的业务初始化随后将数据库升级为 schema 35;schema 34 用户也会执行本次整理,
成功后后续启动直接跳过。继续复用 schema 34 的子任务事件部分索引和同一差量表示。
低于该 schema 的旧版客户端不能重新打开已经升级的数据库。需要回退客户端时,必须使用
升级前备份,不要修改 `user_version` 强行绕过版本检查。
## 验证要求
- 使用可还原的合成过程验证文字追加、内容修订、块重排、清空和多次打开。
- 工具 fixture 必须含生产对象会携带的 `undefined` 可选字段,并经过实际 JSON 写入/重读。
同时验证旧完整事件和 schema 34 差量事件的整理、混合重试及无变化记录不被重写。
- 验证远程重复事件、冲突事件,以及消息更新失败时事件和过程缓存的事务回滚。
- 在真实旧库的已核验备份副本上运行 portable 的正常启动入口,核对事件逐条等价、
其他用户表内容、数据库完整性和实际文件大小,不能只统计空闲页或只测试内存 fixture。
- 同时验证真实本地 Runtime 和共享 Linux Host 的桌面持久化路径。真实场景必须让主代理
与子代理按正常任务需要读取、修改文件和执行测试,不能用“子代理不使用工具”的纯文字
场景代替工具存储验收;记录实际请求数、有效变化、重复工具块及还原结果。具体证据见
[实现与验证进度](./progress.md)。
+224 -36
View File
@@ -36,8 +36,8 @@ Runtime 统一抽象为“执行空间”。
### 2.1 助手工作栏
助手工作栏是 GoodBuddy 中始终可访问的应用级工具容器。它提供稳定能力目录,用户从中
查看任务中心,并按需打开一个或多个监督、Runtime、终端、进程、工作区、浏览器、资源监控
和成果面板实例。稳定的是能力的可发现性,不是九个同时占据界面的固定面板。
查看任务中心,并按需打开一个或多个监督、Runtime、终端、进程、工作区、浏览器、资源监控、
成果和文档面板实例。稳定的是能力的可发现性,不是十个同时占据界面的固定面板。
工作栏不是:
@@ -85,21 +85,24 @@ Runtime
浏览器
资源监控
成果
文档
```
- 应用不得根据当前项目、会话、Runtime、主机或探测结果无提示地增删能力目录项。
- 用户主动打开、关闭、排序和停靠面板实例;应用不默认同时挂载全部能力。
- Task Center 是 Task 的单例应用级索引。每个 Task 只关联一条 Conversation,一条
Conversation 可以关联多个 Task;Task Center 不复制会话内容,也不显示普通 Conversation、
Job、Run 或心跳事项。
- 用户主动打开、关闭、排序和停靠可关闭的面板实例;任务中心和工作区作为基础工作入口默认
存在且不可关闭,分别固定在第一、第二位,排序属性为不可重排。
- Task Center 是默认跟随当前项目的单实例 Task 索引。每个 Task 只关联一条 Conversation,
一条 Conversation 可以关联多个 Task;Task Center 不复制会话内容,也不显示普通
Conversation、Job、Run 或心跳事项。
- 当前能力、连接、数据和空状态可以动态变化。
- 能力不可用时,目录项或已打开面板显示原因、影响和可执行的配置或切换入口,不能只通过隐藏表示。
- 用户可以在设置中调整目录顺序;恢复默认布局恢复标准目录与默认打开面板,不强制打开全部能力。
- 同一能力需要并排比较不同目标时可以创建多个实例,每个实例拥有独立身份和范围绑定。
- 浏览器、终端及其他声明为多实例的能力可以创建多个实例,每个实例拥有独立身份和范围绑定。
### 3.2 当前上下文只提供默认值
任务中心作为全局索引不跟随当前会话,也不支持为同一列表打开多个目标实例。其他可绑定
任务中心只有一个工作栏实例,默认跟随当前项目,不跟随当前 Conversation。用户可以在同一
实例内显式切换到全局任务或全部项目,但不能为不同项目打开多个任务中心 Tab。其他可绑定
目标的能力使用当前会话、项目、Runtime 和主机帮助新面板实例初始定位,但这些上下文不是
使用门槛:
@@ -109,6 +112,7 @@ Runtime
使用本机 Home。
- 工作区默认显示当前项目目录,用户可以打开其他本机目录或远程目录。
- 成果默认使用当前范围,用户可以切换到项目、全局或其他允许范围。
- 文档创建时固定到用户明确打开的文件和 ShareServer 编辑会话,不提供跟随模式。
- 下一次请求的附件和知识库由当前 Composer 展示与管理,不作为工作栏能力。历史执行上下文
随关联 Task 查看;记忆功能实现后也归入任务中心,而不是新增独立上下文面板。
@@ -134,7 +138,7 @@ Runtime
稳定能力目录和已打开面板必须准确呈现能力差异:
- 当前 Runtime 不支持后台 Job 时,Runtime 能力仍可发现;打开后说明当前可监督的内容。
- 当前执行空间没有 Git 仓库时,工作区文件功能仍可使用,Git 区域显示不可用原因。
- 当前执行空间没有 Git 仓库时,工作区文件功能仍可使用,仅确认是 Git 仓库后显示 Git 工作区入口。
- 没有活动进程时,进程面板提供创建终端或启动 Runtime 的入口。
- 没有项目时,终端使用当前用户本机 Home;工作区等允许选择路径的能力仍必须先取得有界
临时 Execution Space Grant,不能把可选路径直接传给文件、Git、Process 或 Runtime。
@@ -203,24 +207,29 @@ Runtime
### 6.2 能力目录与面板实例
能力目录不等于同时打开九个面板。当前产品采用“已打开实例 Tab + 持久新增按钮”的工作栏
能力目录不等于同时打开十个面板。当前产品采用“已打开实例 Tab + 持久新增按钮”的工作栏
模型:Tab 行只显示已打开实例,行末“+”始终可见;点击后在工作栏内容区打开稳定能力目录,
用户再创建或激活应用实例。详细交互以
[助手工作栏多终端页签 PRD](./terminal-tabs-prd.md) 为准。
- 每项始终显示稳定图标,并提供可见标签或可持续查看的工具提示。
- 已打开实例使用 `tablist`、`tab`、`tabpanel`;“+”是位于 `tablist` 外的普通命令按钮。
- 应用注册表声明 `single` 或 `multiple`。在工作栏“+”目录中重复选择单实例应用时聚焦已有
实例;选择多实例应用时每次创建新实例。主侧栏系统工具快捷入口可以聚焦当前执行空间中
最近使用的终端,额外终端仍从“+”目录创建。
- 支持方向键、Home、End、Enter、Space、关闭面板和正确焦点恢复。
- 应用注册表分别声明实例策略、默认打开、必须存在、是否可关闭和是否允许排序。`single` 或
`multiple` 只控制实例数量;`required` 控制布局修复,不能兼作关闭或排序策略。
- 在工作栏“+”目录中重复选择单实例应用时聚焦已有实例;选择浏览器、终端等多实例应用时
每次创建新实例。主侧栏系统工具快捷入口按当前上下文聚焦匹配实例,不能因连续点击无意
创建重复实例。
- 支持方向键、Home、End、Enter、Space、可关闭面板的关闭命令和正确焦点恢复。不可关闭
实例不渲染关闭按钮,也不响应关闭快捷命令。
- 徽标显示未解决数量、等待审批或失败状态,并同时提供文字或可访问名称。
- 用户调整目录顺序、打开实例、停靠位置和尺寸后持久化;关闭实例后能力仍可从目录重新打开。
- 用户调整目录顺序、打开实例、停靠位置和尺寸后持久化;关闭可关闭实例后能力仍可从目录
重新打开。布局加载和恢复默认布局必须补回缺失的任务中心与工作区实例。
- 默认布局只恢复经过产品确认的少量常用面板,不自动打开全部标准能力。
- 每个实例显示稳定实例 ID、能力名称、跟随或固定状态与当前目标;同能力多实例不能只靠位置区分。
从当前聊天右栏迁移时,Task Center、工作区、浏览器和成果作为默认打开的单实例应用保留;
终端不默认打开,由用户通过“+”创建,并从首期开始支持多实例。Tab 必须单行横向滚动,
从当前聊天右栏迁移时,Task Center 和工作区作为默认打开、单实例且不可关闭的基础应用保留;
浏览器保留一个默认打开实例,但注册为可关闭的多实例应用;成果保持默认打开、单实例且可
关闭;终端不默认打开,由用户通过“+”创建,并从首期开始支持多实例。Tab 必须单行横向滚动,
不能自动隐藏或缩写到不可辨认,“+”不能随滚动消失。Task Center 继续作为 Task 的现有
入口;审批定位到所属 Task 或 Runtime。智能心跳不作为工作栏页签,其报告、建议、历史和
完整配置统一归属“智能心跳”菜单入口。当前阶段不新增独立自动化中心。
@@ -236,7 +245,7 @@ Runtime
项目切换器和导航整体向下推移。
- 对话内容与输入区始终保留对称的左右安全边距,工作栏边界不得覆盖或裁切输入框、发送
按钮及主工作区右侧边距。
- 唯一展开与收起开关固定在窗口关闭按钮正下方。工作栏内部不重复关闭入口。开关图标中的
- 唯一展开与收起开关固定在顶栏操作区、紧邻浅色/深色主题按钮之前。工作栏内部不重复关闭入口。开关图标中的
箭头方向随当前动作切换,按钮不以持续高亮表达展开状态;当前会话没有 Task 时不渲染空
Task 上下文栏。
- “新建任务”入口位于工作栏“任务索引”标题后方,不在会话 Task 上下文栏重复出现;索引
@@ -258,8 +267,13 @@ Task Center 保留为工作栏中的稳定入口,并在现有基础上适度
活动记录和成果查看器中,不在窄栏复制,也不呈现 Job/Run 树。
- 智能心跳的报告、建议、历史和配置不进入任务中心。
任务中心是单例索引,不使用其他能力的“跟随 / 固定目标”多实例模型。后台状态可以更新
徽标和排序,但不能自动打开面板或抢占用户当前工作。
任务中心是应用级单例,默认存在且不可关闭。它的数据范围默认跟随当前项目,并在标题或范围
控件中明确显示项目;用户可以切换到“全局任务”或“全部项目”,该选择只改变同一实例的列表
范围,不创建第二个任务中心 Tab。切换回“跟随当前项目”后,项目变化立即刷新列表。后台状态
可以更新徽标和排序,但不能抢占用户当前工作。
当前实现已按活动项目过滤 Scheduled Task 和审批,并提供“当前项目”“全局任务”“全部项目”
范围控件。
主侧栏最近会话为关联 Task 提供轻量入口:父会话行只显示行首展开按钮,Task 子项使用
任务图标和本地化摘要,展开层级只到 Task;点击 Task 子项打开同一 Conversation 并定位。
@@ -354,6 +368,10 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
### 7.6 工作区
工作区是默认存在、单实例且不可关闭的基础应用。它默认跟随当前项目;项目或目录不可用时
保留同一 Tab 并显示修复入口,不能通过关闭 Tab 隐藏范围错误。任务中心固定在第一位,工作区
固定在第二位,恢复布局时保持此顺序。
工作区面板允许用户选择:
- 当前项目目录。
@@ -370,29 +388,83 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
当前项目目录的文件浏览与 Git 交互遵循以下规则:
- 页签下不重复显示“项目工作区”标题,保留刷新按钮。
- FR-W1:顶部同一行放置“文件 / Git 工作区”视图切换和右侧刷新;非 Git 目录仍保留刷新。
下一行只提供当前视图的工具:文件视图提供新建文件、新建目录,Git 视图提供分支、Fetch 和更改显示方式,
不在 Git 视图重复目录导航或新建文件操作。操作使用带名称和提示的紧凑图标按钮。
文件视图在根目录不显示孤立的文件夹图标或空路径行;进入子目录后提供“项目目录名 / 子目录”
文字路径导航和返回父目录按钮。根目录文字可点击返回,提示显示完整根路径;返回根目录后焦点移至顶部刷新。
长目录名单行省略,完整路径通过提示查看;多级路径在导航内部横向滚动,不撑宽工作栏。
选中目录时在该目录创建,选中文件时在其父目录创建,未选择时在当前浏览目录创建。
无可用目录时隐藏无关操作。
- 使用共享 `SegmentedControl` 切换“文件”和“Git 工作区”,默认显示文件;
选择只在当前面板挂载期间保存在内存中,不持久化。未提交更改包含当前项目的全部
Git 更改,不按 AI、用户或其他工具的修改来源筛选。
- FR-W2:只有已确认的 Git 仓库显示 Git 工作区入口。临时读取或连接错误保留同一项目
已确认的 Git 可用性和内容,提供重试;切换项目不能沿用上一项目的确认结果。
- FR-W3:文件和目录行的更多按钮与左侧会话行一致,在悬停、键盘聚焦或菜单打开时显示。
菜单复用会话操作的图标、间距和悬停样式,浮层限制在窗口内,不受文件列表滚动边界裁切;
支持方向键、Home、End 和 Escape,Escape 或选定操作后焦点返回触发按钮,删除入口独立分隔。
菜单提供重命名、移动、删除、属性、复制路径和本机默认应用打开;删除须明确确认对象
与不可恢复性。操作失败保留输入与上下文。本机和远程均支持文件管理,系统默认打开仅本机可用。
重命名允许仅修改同一目录项的名称大小写,不得覆盖另一个已存在的文件、目录或硬链接目录项。
移动通过与创建项目相同的目录选择流程指定目标:本机打开系统目录选择器,远程浏览当前
SSH Host 的目录。选择目录后保留原名,确认后执行;取消不移动文件。目标限于当前工作区,
选择外部目录时在弹窗内说明限制,不切换项目或工作区范围。取消按钮使用共享次按钮样式,
更多菜单中的删除图标和文字与其他菜单项一样左对齐。
- FR-W4:Git 分支标签打开可搜索的本地与远程分支选择器,支持创建和切换分支,以及显式
Fetch。切换使用普通 Git switch,保留可携带的未提交修改;冲突时显示 Git 错误。
不自动 force、reset、discard 或 stash,不因浏览分支而自动 Fetch。
分支触发器复用共享模型选择按钮,带分支图标和展开箭头;Fetch 使用共享次按钮,二者在
紧凑工作栏内保持同一行,长分支名省略并提供完整名称提示。
展开的分支选择面板不超过 360px 或工作栏可用宽度;搜索使用共享表单标签、输入框与主题样式,
分支列表独立限高滚动,创建按钮以紧凑次操作放在列表下方。Escape 收起面板并将焦点返回分支触发器。
- FR-W5:未提交更改支持列表和目录树切换;两种视图均可打开单文件 Diff。
- FR-W6:Git 工作区底部提供默认收起的提交历史区,独立滚动,可调整高度。
历史标题默认停在面板底部,首次展开向上占用当前 Git 内容区的一半高度;收起后标题回到底部。
上方变更列表和下方提交历史独立滚动;分隔条支持指针及键盘调整比例,底部位置保持不变。
顶部刷新入口同时更新已展开的提交历史,历史区内不重复提供刷新按钮。
按当前分支分页读取提交,展示摘要、作者、时间、短 OID 和 refs。选中提交显示该次修改文件,
再选文件进入内联提交 Diff;逐层返回保留分支、分页、选择、滚动位置与焦点。
提交 Diff 保留重命名前后路径和实际内容修改;后台工作区刷新不得使正在加载的历史或详情
永久停留在忙碌状态。
历史展开入口使用带方向箭头的紧凑列表行;提交行沿用文件列表的表面、悬停和焦点样式,
摘要与元信息左对齐分行展示,长内容省略并提供完整提示,不撑宽侧栏。
- Git 错误和无更改提示只在 Git 工作区显示,不阻断文件浏览。
切换项目时自动检测 Git 状态,无需手动刷新;检测结果和失败时保留的数据仅用于所属项目。
- 未提交更改以彩色 Git 字母标识状态:M 修改、A 新增、D 删除、R 重命名、C 复制、
U 未跟踪或冲突、T 类型变化;冲突用危险色区别于未跟踪文件,同时保留原始状态。
- 点击更改项进入该文件 Diff,分别显示已暂存和未暂存差异,增删行使用对应状态色;
删除文件也可查看。目录树中的文件仍打开内容预览。不再提供底部全仓库 Diff 入口。
- 手动刷新或任务结束更新 Git 状态时,同步重读根目录和已展开目录,保留目录展开状态,
清除未展开目录的旧缓存;正在查看的单文件 Diff 同时重读,旧请求不得覆盖新结果。
- 文件预览保留在右侧工作区内;预览期间隐藏列表但保持目录树挂载,返回后保留已展开
目录、滚动位置和选中行,并恢复触发行焦点。Diff 返回未提交更改列表时同样恢复
滚动位置、选中行和焦点。
- 文件预览提供复制项目相对路径、刷新,以及仅本机项目可用的“使用默认应用打开”。
刷新失败保留此前内容并显示错误;Markdown 默认渲染预览,可切换源码。
- Diff 提供刷新、复制项目相对路径和“查看当前文件”;删除项不显示查看当前文件入口。
- 手动刷新或任务结束更新 Git 状态时,同步重读根目录、当前浏览目录和已展开目录并去重,保留目录展开状态,
刷新失败保留已有目录列表;重新展开目录时重读。目录读取错误在对应目录旁显示,
可就地重试。正在查看的单文件 Diff 同时重读,旧请求不得覆盖新结果。
无首次提交的仓库也应显示新增和暂存文件。
- 未提交更改首批显示 50 项,可继续分批加载其余变更,不能使后续文件尤其是已删除
文件失去 Diff 入口。
- 系统默认应用和资源管理器打开按钮仅用于本机项目,SSH 项目不显示这两个按钮。
目录切换、下载副本、终端打开及 HTML 安全预览仍属于后续能力,不能据此视为已实现。
项目内目录导航属于 FR-W1;项目外目录选择、下载副本、终端打开及 HTML 安全预览仍属于后续能力。
本轮工作区交互不包含项目文件搜索,也不调整其他工作栏页签。
本机和远程访问都必须由 Main 或远程 GoodBuddy Agent 在对应文件系统上执行路径规范化、相对路径和
符号链接边界检查。Renderer 只能提交受约束的相对路径和已授权范围 ID。
### 7.7 浏览器
浏览器是可关闭的多实例应用。升级后的默认布局保留一个浏览器实例;用户从“+”目录选择
浏览器时每次创建新的 Browser Tab。每个实例必须显示可区分的标题和绑定 Conversation,
不能让多个工作栏 Tab 只是同一页面的重复控制入口。
浏览器能力在目录中稳定可发现,打开面板后允许用户:
- 创建新的 GoodBuddy 隔离浏览器会话。
- 选择当前会话或其他受控浏览器会话。
- 在当前 Conversation 的隔离浏览器上下文中创建独立 Browser Tab。
- 选择当前 Conversation 或其他允许访问的 Conversation 作为实例目标。
- 查看状态、当前 URL、有界画面和错误。
- 通过顶部浏览器工具栏返回、刷新或停止加载、编辑地址并前往。
- 进入明确的交互模式或关闭浏览器。
@@ -401,10 +473,19 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
页面加载期间原位切换为“停止加载”;“关闭浏览器”是清楚独立于导航的会话生命周期操作,
必须与“停止加载”在文案和视觉上明确区分。现有“停止浏览器”语义统一改名为“关闭浏览器”。
浏览器面板和 Agent 操作同一条按 Conversation 归属的浏览器会话,不创建用户预览专用的
第二个页面。用户执行前往、返回或刷新会有意改变 Agent 下一步读取和操作的页面。所有
前往、返回和刷新请求都进入 `BrowserService`,并与 Agent 发起的浏览器操作串行执行;不在
Renderer 建立第二套导航队列、抢占规则或恢复状态机。
新建或重新创建的空白标签页尚无已提交 URL 时禁用刷新;只输入地址草稿不会启用刷新。
首次导航期间仍可停止加载。操作错误仅在当前面板本次打开期间显示,切换面板、关闭当前
页签或收起工作栏后清除,已离开面板的异步失败不得重新显示旧提示。
同一 Conversation 下的 Browser Tab 共享隔离存储和代理上下文,但每个 Tab 拥有独立页面、
导航状态、操作队列和元素引用空间。模型请求开始时绑定一个确定的 Browser Tab;用户切换
工作栏实例不能在请求途中改变 Agent 的工具目标。用户在该实例执行前往、返回或刷新会有意
改变绑定到同一 Tab 的 Agent 后续操作。所有请求都进入 `BrowserService`;Renderer 不建立
第二套导航队列、抢占规则或恢复状态机。
用户可关闭 Agent 正在使用的标签页,无需先取消整轮请求。关闭只中止该页的浏览器操作,
其他标签页和请求的其他工具继续可用。后续快照等操作应提示标签页已关闭;Agent 显式导航
时打开专用新标签页并继续操作,不能自动接管用户的其他页面。
浏览器工具栏读取的权威状态至少包含:
@@ -432,7 +513,10 @@ type BrowserToolbarState = {
没有浏览器会话时仍显示工具栏。活动 Conversation 可以直接在地址栏输入非空 URL 并“前往”,
由 `BrowserService` 通过 Agent 浏览器相同的能力检查和 URL 策略创建会话并导航;不得为这一
空状态增加旁路 WebView 或宽松 URL 入口。没有活动 Conversation 时说明需要先选择或创建会话。
模型或后台浏览器活动可以更新徽标,但不得无条件打开面板或切换用户当前面板。
模型或后台浏览器活动可以更新对应实例的徽标,但不得无条件打开面板或切换用户当前面板。
浏览器多实例的跨进程身份、可见 viewport 所有权、关闭语义和 MCP 兼容方案以
[浏览器多实例技术设计](./browser-tabs-technical-design.md)为准。
浏览器面板只管理 GoodBuddy 受控浏览器,不表示可以控制用户已安装的浏览器。
@@ -473,7 +557,20 @@ Task 呈现来源,不把 Run 作为导航对象。
- 不使用 `dangerouslySetInnerHTML`,不启用 Electron `webviewTag`。
- 外部打开是明确的用户操作,并说明外部浏览器可能执行脚本或联网。
### 7.10 Composer 与任务上下文
### 7.10 文档
文档是可创建多个实例的工作栏应用。用户从工作区、成果或会话文件引用打开受支持的 Office
文件时新增文档 Tab;每个实例固定到一个 ShareServer 编辑会话,不随当前 Conversation 或
Project 切换。Tab 使用文档文件名,同名实例增加序号,完整来源通过 Tooltip 和页签内信息
显示。编辑器、保存、冲突、AI 选区修改和关闭行为以
[Office 协同编辑](../office-document-editing/README.md)为准。
完整编辑器需要至少 `720px` 可用宽度;低于 `480px` 时不把第三方工具栏压缩到重叠,而显示
摘要和“展开编辑”命令。展开到主内容区、底部或独立窗口继续使用同一页签实例和编辑会话。
文档编辑依赖用户明确配置且声明健康能力的 ShareServer;不可用时保留入口和原因,不回退为
未经说明的远程服务。本地默认应用打开仍是独立用户操作。
### 7.11 Composer 与任务上下文
工作栏不提供独立“上下文”能力。用户准备下一次模型请求时:
@@ -499,6 +596,7 @@ type WorkbarCapabilityId =
| 'browser'
| 'resources'
| 'results'
| 'document'
type WorkbarTargetRef =
| { type: 'conversation'; id: string }
@@ -511,6 +609,7 @@ type WorkbarTargetRef =
| { type: 'managed-process'; id: string }
| { type: 'browser-session'; id: string }
| { type: 'artifact'; id: string }
| { type: 'document-session'; id: string }
```
Renderer 选择目标后,Main 必须重新验证对象存在、归属范围和当前用户可见性。不能把目标 ID
@@ -838,6 +937,55 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
应用通知;预览失败、终端断线、Host Key 变化、监督证据失效等需要本地恢复的错误留在面板
内。同一事件不得同时重复显示为面板警告和应用通知。
### 13.1 Runtime 执行清单
本节为待实施设计。用户需要在对话顶部查看 Agent 本轮列出的工作项及完成情况,避免在
工具记录中反复查找。这里的“执行清单”由 Runtime 更新,术语边界见
[Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md#17-runtime-执行清单)。
- **FR-CL1**:接入 OpenCode(OC)的 `todowrite` 与 Continue(CN)的 `Checklist`,
范围包括本地及远程 GoodBuddy Agent 上运行这两个 Runtime。远程 CN 的部署和正常请求
路径也属于交付范围,当前可用程度见[进度](./progress.md#2026-09-15-runtime-执行清单设计未实现)。
- **FR-CL2**:顶部展示本轮最新有效清单,更新按整份替换处理,支持明确清空。每轮请求、
会话、项目、执行空间与 Runtime 的清单分别归属;普通聊天续轮不能沿用旧清单冒充本轮进度。
- **FR-CL3**:清单只读,用户不能手动勾选。请求完成、失败或取消保留清单实际状态,
不自动完成工作项;原生工具调用继续进入消息与活动记录。
- **FR-CL4**:重新打开会话可恢复已保存的清单及请求结果,远程重连遵循已有 Agent 恢复
流程,不重新发送模型或工具请求。没有原生清单的请求不显示空占位。
ACP 仅是传输及远端协议,不分类为 Runtime。DeepSeek Harness(DS)真实默认环境无
todo/plan,本次不为其增加工具。也不增加 plan 工作模式、计划文档、计划编辑器或定时任务
能力;只有格式已确认的原生 plan entries 可以适配清单。
#### 顶部布局
执行清单位于聊天内容区顶部、已有 Task 上下文栏下方、消息流上方;没有 Task 时仍可独立
显示。它不占用窗口标题栏操作区,也不替换 Task 摘要、任务索引或右侧 Runtime 栏目。
布局遵循 [UI-DESIGN](../../../UI-DESIGN.md#610-应用顶栏与全局操作)。
默认折叠为一行“执行清单 · 已完成 m/n”,有进行中条目时附简短摘要;展开后按原生顺序
显示内容、状态及原生提供的优先级。行内状态使用图标和“待处理 / 进行中 / 已完成 / 已取消”文字,
不用可勾选 Checkbox,也不根据位置推测优先级或进行中状态。请求结束后的摘要附真实
请求结果,例如“已取消”,不改变条目状态。后台更新不强制展开、不抢焦点。
展开按钮提供 `aria-expanded` 和 `aria-controls`,支持 Enter/Space,折叠后焦点留在
按钮;实时区域只播报摘要变化。复用全局颜色、间距和字体令牌,浅深主题均可读。窄窗口
摘要截短、展开内容换行并有界滚动,保留完成计数、请求结果与输入区;200% 缩放时仍可
操作。没有清单或收到有效空清单时整行隐藏,历史内容仍按消息归属查看。
#### 场景验收
| 场景 | Given / When / Then | 需求 |
| --- | --- | --- |
| US-CL1,连续更新 | OC/CN 本轮产生清单并修改、重排或删除条目;顶部显示最新完整内容,工具活动仍可查 | FR-CL1–FR-CL3 |
| US-CL2,请求隔离 | 同会话开始下一请求、切换 Runtime 或并行使用另一会话;旧轮迟到事件不覆盖当前清单,历史按原归属保留 | FR-CL2、FR-CL4 |
| US-CL3,失败与取消 | CN 更新失败或请求被取消;失败候选不替换上次有效清单,取消不把未完成项勾完;有效空清单清除顶部 | FR-CL2、FR-CL3 |
| US-CL4,本地与远程恢复 | 重新打开本地会话或重连共享 Linux Host 上的 OC/CN 请求;已有记录按原请求恢复,重复事件不重复展示,不重新调用模型或工具 | FR-CL1、FR-CL4 |
| US-CL5,无清单与无障碍 | DS 默认请求或普通 Markdown/list 工具输出没有已确认清单;顶部不出现伪清单;有清单时键盘、窄窗口及浅深主题可用 | FR-CL1–FR-CL3 |
行为规则见 [Runtime 交互边界](./runtime-interactions.md#runtime-执行清单),接入与验证见
[技术设计](./runtime-checklist-technical-design.md)。
## 14. 性能与资源边界
- 工作栏关闭或面板实例关闭时停止对应非必要画面和高频日志推送,但保留 Main 中的受管运行。
@@ -850,6 +998,41 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
用户级 GoodBuddy Agent Daemon。依赖 Main 模型网关、审批或本机工具的 Run 不能被描述为
可以无人值守继续。
### 14.1 Runtime 资源复用
2026-09-14 调研确认:多个项目可能各自保留一个重型 Runtime,状态或原生清单读取也会
影响其启动与保留。用户需要多项目/多会话自然并行工作,而不是为避免内存增长关闭工具、
子代理或删除历史。以下为新需求,**不表示 Desktop 0.13.2 / Agent 0.11.25 已实现**。
- **FR-RR1**:支持原生多会话的 Runtime,在同一账号和兼容启动配置下跨项目复用重型
进程;不得仅因项目目录或会话不同重复启动。等价的默认和显式选择应得到相同复用结果。
不兼容的 Runtime、Host、模型连接或插件配置可以分开,不要求全产品只有一个进程。
- **FR-RR2**:仅查看被动状态不启动重型 Runtime。显式连接测试、原生清单和执行请求
复用兼容的已启动实例;尚未握手时不能显示为已经验证连接。
- **FR-RR3**:共享进程仍保持各会话的目录、历史、模型用量、工具、权限和业务问答归属。
同一会话顺序执行,不同会话并行;Ask 保持只读,Execute 不增加第二层审批。
- **FR-RR4**:请求结束、会话释放与进程退出分别处理。取消一个普通请求、关闭工作栏
或释放一个会话不得误停其他健康会话;无会话的空闲重型实例及时释放。正常续聊保留
原生上下文,应用退出、配置替换和真实进程故障仍有明确的终态与有界回收。
- **FR-RR5**:托管 SSH 独立实现并验证复用,保留 Agent 对活动 Prompt 的所有权、
断线续接、事件去重和模型调用结果不确定性规则。不能以本机通过代替远端交付。
- **FR-RR6**:对不具备原生多会话 Host 的 Continue 保留正确的请求隔离;直连模型不
新增独立模型进程。优化应有前后性能证据,不以删历史、禁用工具、全局串行化或
推迟正常用户操作来换取进程/内存指标。
产品场景验收:
| 场景 | Given / When / Then |
| --- | --- |
| US-RR1,首次与热启动 | 同一有效配置的两个项目,分别查看状态、打开清单并开始四个对话;状态不冷启动,后续共用一个可多会话的重型 Runtime |
| US-RR2,并行与取消 | 两个项目有同名不同内容文件,四个会话进行真实开发;目录及成果正确,取消其中一个后其余三个继续完成 |
| US-RR3,续聊与释放 | 一轮任务完成后继续原会话,再释放其中一个;原生上下文可续用、其他会话不受影响,数据库完整历史仍可查看 |
| US-RR4,边界与远程 | 切换不兼容配置、查看 Continue/直连状态或中断 SSH;不串配置、不伪造多会话能力,Agent 已接受工作按现有归属恢复 |
资源状态规则由 [Runtime 交互边界](./runtime-interactions.md#runtime-资源生命周期)
定义;锁定版本研究、实现选择与逐项验收由
[进程复用技术设计](./runtime-process-reuse-technical-design.md)定义。
## 15. 无障碍与响应式
- 能力目录、所有面板实例、目标选择器、终端控制和进程操作可用键盘完成。
@@ -941,7 +1124,7 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
### 17.1 稳定能力目录与用户控制
- [ ] 九个标准能力在所有主要页面的目录中始终可发现,但不会默认同时打开。
- [ ] 十个标准能力在所有主要页面的目录中始终可发现,但不会默认同时打开。
- [ ] Task Center 继续作为 Task 的单例索引,不删除入口、不复制会话,也不混入 Job、Run 或心跳事项。
- [ ] 项目、会话、Runtime 或主机变化不会无提示地增删能力目录项。
- [ ] 用户可以按需打开、关闭、排序和停靠面板实例。
@@ -956,6 +1139,8 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
- [ ] 监督可以作用于普通 Conversation、Task 和后续实验对象,不假设编程语境。
- [ ] 工作区不是 Git 仓库时仍可浏览文件。
- [ ] Runtime 不支持某项原生能力时仍可从目录打开面板并获得准确说明。
- [ ] 文档能力可以打开多个以文件名命名的固定实例;未配置健康 ShareServer 时显示原因,
不把系统默认应用或静态预览冒充内置编辑。
### 17.3 安全与控制
@@ -990,7 +1175,7 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
- [ ] 宽、中、窄窗口均可访问完整能力目录和用户打开的面板实例。
- [ ] 工作栏在宽、中、窄窗口展开时都推动主工作区回流,不覆盖、遮罩或隔离主内容;唯一
开关始终固定在窗口关闭按钮正下方。
开关始终固定在顶栏操作区、紧邻浅色/深色主题按钮之前。
- [ ] 仅使用键盘可以选择能力、面板实例、目标、执行空间和控制动作。
- [ ] 状态不只依赖颜色,徽标具有文字或可访问名称。
- [ ] 终端、HTML、监督证据和高频日志具有可访问替代或降噪行为。
@@ -999,6 +1184,7 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
- [ ] 创建中、Agent 操作中和完整交互期间禁用浏览器其余工具栏控件;活动加载仍可单独停止,
且停止加载不会关闭会话或释放 Runtime 上下文。
- [ ] 活动 Conversation 没有浏览器会话时,可从同一地址栏按 Agent 相同 URL 策略启动会话。
- [ ] 文档编辑器在右侧宽度不足时不发生工具栏或正文重叠,并可使用同一会话展开到可编辑布局。
## 18. 相关文档的职责
@@ -1007,7 +1193,7 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
动态 Tab 以及本机与直接 SSH 多终端。
- [Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md) 定义 Task、Conversation、
Job、Subjob、Run 与 Subagent。
- [Task Center PRD](../task-and-job/task-center-prd.md) 定义应用级 Task 索引。
- [Task Center PRD](../task-and-job/task-center-prd.md) 定义单实例、项目跟随和显式范围切换。
- [智能心跳 PRD](../smart-heartbeat/prd.md) 定义心跳入口、范围和长期边界。
- [会话监督 PRD](../conversation-supervision/prd.md) 定义监督判断、证据、预算和介入边界。
- [自动化平台总体设计](../../architecture/automation-platform-architecture.md) 定义 Plan、Job、Run、监督、预算和记忆。
@@ -1015,8 +1201,10 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
- [DeepSeek Harness Runtime 设计](../deepseek-harness/technical-design.md) 定义该 Runtime 的具体适配边界。
- [SSH Host 远程环境准备与直连下载设计](../remote-host/environment-provisioning-technical-design.md)
定义 Host 级组件获取、首次准备、更新、回退和项目只验证边界。
- [Office 协同编辑](../office-document-editing/README.md) 定义文档多实例、ShareServer 编辑会话、
保存、冲突和 AI 选区修改。
- [统一界面设计系统](../../../UI-DESIGN.md) 定义视觉、语义、响应式和无障碍规则。
若其他文档把工作栏描述为九个同时固定显示的栏目、根据项目或 Runtime 自动裁剪的动态入口,
若其他文档把工作栏描述为十个同时固定显示的栏目、根据项目或 Runtime 自动裁剪的动态入口,
或仅属于当前聊天的附属区域,以本文“能力目录稳定、面板实例由用户打开、当前上下文只提供
默认值”的产品决策为准。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
# Runtime 执行清单技术设计
状态:设计阶段,尚未实现。需求、场景与顶部布局由 [PRD](./prd.md#131-runtime-执行清单)
维护,替换、隔离和终态规则由 [Runtime 交互边界](./runtime-interactions.md#runtime-执行清单)
维护。研究证据与交付状态见[进度](./progress.md#2026-09-15-runtime-执行清单设计未实现)。
## 1. 原生数据与提取位置
| Runtime / 路径 | 已确认格式与设计接入 |
| --- | --- |
| 本地 OC 1.18.29 | `todo.updated` 与 `session.todo` 返回完整条目列表,字段为 `content/status/priority`,无 item ID;以原生更新事件驱动本轮整份替换 |
| 本地 CN 1.5.47 | 精确原生工具 `Checklist` 的参数 `checklist` 是 Markdown 勾选列表,`- [ ]` / `- [x]` 表示未完成/完成;按一次成功调用提交完整替换 |
| 远程 OC/CN | 先核对实际部署版本、原生输出与 Agent 传递结果;只有格式已确认且来源归属明确的原生 plan entries 或精确清单工具参数可以映射,当前尚无本功能远程实测 |
`opencode-runtime.ts` 当前将普通工具 input 经 `boundedToolDetail(..., 4_000)` 转成
展示字符串。接入应在 SDK 原始事件层消费 `todo.updated`;如需核对 `todowrite` 参数,
必须在该截断之前处理。`session.todo` 是 Session 当前快照,读取结果必须与请求生命周期
关联;不在每轮开始时无条件读旧快照充当新清单,也不轮询代替事件。
CN 的提取位置在 `continue-host-adapter.ts`:宿主产生工具事件、HTTP 状态合并及输入
校验均存在 4,000 字符展示限制,不能等到 `continue-runtime.ts` 收到截断字符串后再解析。
宿主应在最早的原始参数层提取 `checklist`,按工具调用 ID 保留候选,结合最终成功状态
提交;成功帧省略参数时使用同次调用的候选,失败不得提交。普通工具事件继续沿现有路径
流转。超出 4,000 字符的清单需要独立验证完整性,但不扩大所有普通工具输入的展示上限。
Markdown 解析只适用于已确认的 CN `Checklist.checklist`,不扫描助手正文或其他工具
输出。空数组在统一事件中明确表示清除;CN 原生如何表达空清单,以及空白、大小写和
非条目行的实际接受规则,实施时核对后固定解析规则。未确认或无效输入保留旧清单,
不能误当清空。
## 2. 统一事件与消息归属
拟在现有 Runtime 事件联合中增加完整清单事件,承载 `requestId`、条目数组和原生来源;
条目包含 `content`、`status` 及可选 `priority`。最终命名随实现确定,不设计条目级增删
协议或稳定 item ID。空数组必须经过 schema、IPC、消息投影和保存链路,不能被“无内容”
过滤丢弃。
Main 使用现有请求到助手消息的映射投影清单,Renderer 顶部读取该消息的最新有效状态。
同一请求若同时观察到原生清单事件与其工具记录,只以前者更新清单,后者保留活动;不能
提交两次替换。原生父 Session、请求开始/结束边界需共同校验,子 Session 或旧请求事件
不得通过当前活动请求映射到新轮。
优先扩展现有消息 metadata、Runtime 事件和数据库投影,历史清单与所属助手消息一起保存。
远端沿用事件提交、消息投影和 ACK 的既有事务及去重规则;相关存储边界见
[执行记录存储](./execution-history-storage.md)。实施前核对本地会话与远程任务实际落库
字段,选择能复用的最小表示。不新建清单数据库、独立快照系统、恢复日志或持久化服务。
旧消息没有结构化清单时保持缺省,不从已截断工具字符串或状态文案补造历史。
## 3. 远程 Agent 与 ACP 复用
本次源码检查确认以下传递位置,属于设计依据,不是本功能远程验证结果:
1. `AgentAcpConnection` 按原生 `sessionId` 分派 `sessionUpdate`;
`AgentOwnedAcpPrompt` 校验 Session 后,以 `bindingId/operationId` 将通知写入
既有 semantic transcript。
2. `ProtocolRemoteRuntimeChannel` 已有 transcript 读取/ACK、身份核对,以及 ACP
channel 恢复与重放;`AcpRemoteRuntime` 消费 `session-update` 并转换桌面事件。
3. `AcpRemoteRuntime` 当前将 `plan.entries` 及 `plan_update` 的 items 转成 `status`
字符串;markdown/URI 也只形成状态消息。工具事件按 `toolCallId` 合并 `rawInput`、
`status` 和结果,再有界保留。清单提取应发生在结构丢失或截断前。
优先复用上述通知和 transcript,不另建远端清单通道。对实际 OC/CN 部署确认过的
`plan.entries`,或已确认等价的 `plan_update` items,校验条目字段和状态后转成统一
清单事件。普通 `status`、plan markdown/URI、`plan_removed` 文案以及泛化工具名均不
触发清单更新;在没有确认对应原生清单身份前,`plan_removed` 也不能清空顶部。
若远程原生输出走精确清单工具,须确认原始参数、调用身份与成功状态确实贯穿 Agent,
再按同一提取规则接入。是否能直接复用原生 entries,或需补充最小 Agent 适配,取决于
真实格式证据,不能根据 ACP 类型定义推断 OC/CN 一定会发送。ACP 是协议,Runtime 身份
仍为 OC 或 CN。
当前托管 SSH 产品及受管安装、校验、创建链路只支持 OC,详见
[远程范围](../remote-host/technical-design.md#执行清单接入范围)。远程 CN 交付需补齐
受管工件、安装与启动、项目选择/校验及原生事件接入,确认 Main 模型桥、请求归属、取消
和已有恢复流程能用于该部署。不能只放开选择菜单或使用本地 CN 调远程模型来宣称完成。
重连按[既有 ACP 与断线规则](../remote-host/technical-design.md#acp-与断线)执行。
已接受 Prompt 使用原 binding/operation 恢复,清单从已提交消息和其后的既有事件恢复;
重复事件不重复展示。连接丢失不表示条目完成,不为恢复清单重新发送 Prompt、模型请求或
工具调用,也不新增独立重连机制。
## 4. 实施与验收
| 阶段 | 必须完成的工作 | 对应需求 |
| --- | --- | --- |
| CL-P1 | 原始层提取、严格格式校验、统一事件及请求归属;保留工具活动 | FR-CL1–FR-CL3 |
| CL-P2 | 消息投影和既有存储恢复、顶部只读布局、空数组清除及终态展示 | FR-CL2–FR-CL4 |
| CL-P3 | 确认远程 OC 原生格式,补齐远程 CN 真实部署与事件接入,复用 Agent 传递和恢复 | FR-CL1、FR-CL4 |
| CL-P4 | 本地与共享 Linux 真实 Host 逐项验收,记录版本、结果、调用口径与剩余阻塞 | FR-CL1–FR-CL4 |
以下均为待执行验收,本次文档工作不运行模型或代码测试:
- 本地 OC/CN 各经桌面正常发送路径产生并连续修改清单,检查新增、修改、重排、删除、
重复文本、有效清空及超过 4,000 字符的完整内容;CN 成功才替换,失败保留前值。
- 同会话连续两轮、并行会话、切换 Runtime/项目/执行空间、旧轮迟到事件和子会话事件
不串数据;完成、失败、取消不改写条目状态。默认 DS、任意 status/Markdown/list
工具不生成伪清单,工具活动仍可查。
- 本地会话重新打开及应用重启恢复历史;顶部折叠、键盘操作、浅深主题、窄窗口和 200%
缩放符合 PRD,保存与重放后的空清单不会恢复旧内容。
- 在共享 Linux x64 真实 Host 使用开发中的 Agent 与实际部署的 OC/CN,分别从远程项目
完成原生清单更新、取消、断线续接及历史恢复。沿用
[Agent 开发验证规则](../../../AGENTS.md#goodbuddy-agent-development-validation),
LAN/VPN 是同一机器的两条路径,不能计作两台 Host。
- 逐个记录 OC/CN 的安装、启动、模型请求、清单传递、顶部显示、取消和重连结果;基础
Runtime 成功不等于清单通过。任一步未执行写“待验证”,被外部条件阻塞则记录原因,
不以 mock、协议 fixture 或本地成功代替远程验收。
@@ -5,17 +5,43 @@
| Runtime | 权限确认 | 业务问答 |
| --- | --- | --- |
| 本机 GoodBuddy 管理的 OpenCode | 内部默认配置允许权限;每个 Ask 请求仍使用 deny-all 与显式只读能力。Execute 自动回复属于当前请求的父、子孙会话权限事件,不处理其他并行请求 | 通过 `question.asked` 转交父、子孙会话问题,支持原生单选、多选、yes/no、自由文本及跳过;使用公开 ID 回传 `question.reply/reject` |
| 托管 SSH OpenCode | Agent 按已接受 Prompt 的 Ask/Execute 模式处理 ACP 权限,规则见远程主机技术设计 | 当前 Agent 插件转交原生父、子孙会话 `question.asked`;前台使用同一问答队列,回答和跳过经认证 Agent 控制通道回传,已知 Task 关联显示子任务归属 |
| Continue | Execute 启动传入 `--auto`,剩余原生权限请求按当前模式自动答复 | 对接 `1.5.47` QuizService 的扁平 `pendingQuestion`,其中 `question` 是字符串。选项回答与自定义文本分别回传正确的 `isCustomAnswer`;跳过发送拒绝回答的文本 |
| DeepSeek Harness | 固定 Host 的 Execute 文件与命令工具不询问目录权限;若收到 ACP 权限请求,活跃 Execute 直接选一次性允许,缺少该选项时使用 Runtime 提供的允许选项,Ask 拒绝,不等待额外 authorizer | 固定 Host 当前未提供通用原生业务问答服务,不把普通模型正文误识别成待答协议。模型以普通回复提问时,用户通过下一条消息回答 |
托管 SSH OpenCode 的 ACP 权限处理与直接启动配置由
[远程主机技术设计](../remote-host/technical-design.md)定义;本机 SDK 问答适配不代表远程
ACP 已提供相同业务问答接口。
托管 SSH OpenCode 的权限、原生问题事件与回复链路由
[远程主机技术设计](../remote-host/technical-design.md)定义。该远端业务问答基线需要
Desktop `0.13.0`、Agent `0.11.24` 和新启动的托管 Runtime;请先升级 Desktop,再更新
Host 环境。2026-09-14 的已发布 Desktop `0.13.2` / Agent `0.11.25` 已包含此前问答修复,
但不包含本文末尾尚未发布的进程复用改动。任意第三方 ACP 服务及旧 Agent 不因此获得通用问答接口。
## 运行与失败
- 结构化 yes/no 是普通选项,不从日志、自然语言或 Shell 输出猜测用户意图。
- 前台问题停留在现有问答卡片,保留回答、提交失败提示与跳过入口。
- 前台同一请求的待答问题按公开 `questionId` 去重,按到达顺序保存在消息的
`pendingQuestions` 中。每次显示队首表单;有多个待答请求时显示数量。后续提问和重复
事件不替换当前表单或清空输入,回答或跳过成功后只移除对应 ID,再显示下一张表单。
提交失败保留队列、输入和重试入口,队列未清空时继续显示“等待你的回答”。
- 本机与托管 SSH OpenCode 在提问到达前已收到原生 Task 的 `metadata.sessionId` 时,将精确匹配的
子会话关联为可选 `childTaskId`。问答卡片显示该任务名称;对应 Task 卡片显示
“等待你的回答”,展开后可点击“前往问答表单”滚动并聚焦当前队首表单,仍按顺序回答。
没有已知关联的子孙会话问题照常排队,不猜测子任务归属,也不从工具名或进度文本构造表单。
- 回答或跳过成功后,会话保留只读的“问题与回答”,显示原问题及对应的选项答案、
自定义文本或“已跳过”。同一任务的多轮问答逐次保留,随本地会话保存,重新打开后仍可查看。
提交失败时保留输入与重试入口,不生成已回答记录;取消未回答的问题不记为主动跳过。
- 提交响应晚于任务结束时仍保存已提交的问答,但不覆盖完成或失败状态;晚于下一轮提问时
不清除新的待答卡片。旧版会话未保存的问题与答案无法从“已提交”状态文案恢复。
- 待答队列仅保存在当前桌面会话内存中,不写入持久会话快照。刷新仍在运行的消息时保留整个
本地队列;完成、失败、取消事件或持久化终态快照清除整个队列。迟到的提交结果可以保存
已回答记录,但不会恢复表单或覆盖终态。本机 Runtime 不恢复重启前未提交的问题;托管 SSH
重新附加同一存活 Agent Prompt 时,由 Agent 当前待答列表补发问题,已回答的历史事件不补发。
- 未发布的进程复用源码在 Desktop 重启后由 Main 并行附加同项目的存活任务,将待答
列表和 Stop 所需的请求 ID 投影到实时 `activeRequest`,不保存该字段或重新发送任务。
回答/跳过成功由 Main 写回原远程助理消息的既有问答历史,Renderer 不覆盖远程正文或终态。
Runtime 设置、定制和能力更新让旧请求在原配置上结束,已有问题仍可回答;新请求使用
新配置。专家团队例外:既有专家调度器在设置替换时取消子任务,团队父请求也随之停止,
不使用替换后的模型汇总旧结果。详细实现见
[恢复与终态投影](./runtime-process-reuse-technical-design.md#63-恢复与终态投影)。
- 后台任务收到结构化提问时,通过现有 Main 路径明确失败并提示改为前台对话,不永久等待。
- 请求结束或取消后清除对应待答映射;过期回答明确报错,不转交给后来的请求。
- 任意终端程序的密码、OAuth 登录、许可证确认或 stdin 提示不自动回复 `yes`。这类程序应
@@ -24,6 +50,9 @@ ACP 已提供相同业务问答接口。
## 托管 Runtime 的并行与输出
子任务完整过程的显示与数据库差量存储相互独立,本机和托管 SSH 共用桌面存储层;
旧库升级与空间回收规则见[执行记录存储与升级回收](./execution-history-storage.md)。
- 本机和 Agent 管理的 OpenCode 启动配置都显式设置 `snapshot: false`。GoodBuddy 不消费
原生自动 Git 快照、快照差异摘要或快照撤销;关闭这条内部路径,避免大行级差异的同步
计算阻塞同一原生进程中的其他会话。
@@ -36,6 +65,21 @@ ACP 已提供相同业务问答接口。
- 远端最终启动配置由[远程主机技术设计](../remote-host/technical-design.md)定义,
模型桥生成最终配置时同样保留上述快照策略。
## 工具操作摘要
工具记录沿用 [UI 设计系统](../../../UI-DESIGN.md)的折叠布局。Continue、DeepSeek Harness
和直连模型共用 `toolOperationSummary`:优先使用有效原生标题,否则从参数提取命令、
文件路径、搜索条件或 URL,合并空白后最多显示 240 字符。无法识别时保留原有工具名
回退规则;文件正文、整份参数 JSON 不进入标题,参数详情仍可展开查看。
Continue 在宿主截断输入前提取摘要,流式完成与失败事件使用合并后的输入和摘要。
DeepSeek Harness 按调用 ID 保留名称、输入和摘要,结束事件省略输入时仍能显示操作内容。
直连模型从原始参数生成一次摘要,在待执行、执行中和完成时复用;审批失败、拒绝和
执行失败、刷新重试仍保留已有提示。
本机 OpenCode 沿用原生操作标题。远端 ACP 独立使用原生标题作为记录名称,不经过上述
本地转换,本次没有修改 Agent、远程桥或协议。
## 消息底部请求状态
- 状态属于当前请求,不与顶部 Runtime 的连接中、就绪、不可用混为一谈。提交后先显示
@@ -54,3 +98,78 @@ ACP 已提供相同业务问答接口。
Runtime 的重试上限、请求重放或取消策略。
实现与真实 Runtime 验证证据见[进度记录](./progress.md)。
## Runtime 执行清单
本节为未实现的状态设计,对应 [FR-CL1–FR-CL4、US-CL1–US-CL5](./prd.md#131-runtime-执行清单)。
原始格式和传递边界由[技术设计](./runtime-checklist-technical-design.md)维护。
清单属于一次请求及其助手消息。沿用项目、执行空间、Conversation、Runtime 选择和
原生 Session 到请求的既有映射;远端还需核对 binding/operation。不能仅以原生 Session
或 Conversation 为键更新顶部,因为同一会话会连续执行多轮请求。
| 输入或操作 | 清单与顶部行为 |
| --- | --- |
| 本轮尚无有效清单 | 隐藏顶部清单,不复制上一轮内容 |
| 收到本轮有效完整清单 | 按原生顺序整体替换,包括修改、删除、重排和重复文本;不按条目文本合并 |
| 收到有效空数组 | 记录明确清空并隐藏;空数组与缺少清单字段不同 |
| CN 调用进行中 | 候选只按该请求的调用 ID 暂存;成功后才提交完整替换 |
| CN 调用失败、取消或格式无效 | 丢弃该候选,保留上次有效清单,工具记录保留实际结果 |
| 请求完成、失败、取消或中断 | 冻结最新已提交清单,并显示请求真实结果;不推断条目完成,迟到输入不改写已结算清单 |
| 同一会话开始下一轮 | 顶部转向新请求,上一轮清单留在原消息;旧轮事件不得归入新轮 |
| 切换会话、项目、执行空间或 Runtime | 只读取目标归属的数据;原请求在原归属结算,不把原清单标为新 Runtime 的进度 |
| 查看历史或重连 | 从原消息及既有事件恢复;按原请求和远程来源顺序去重,不能用最新原生 Session 快照覆盖历史轮次 |
条目状态支持 `pending`、`in_progress`、`completed`、`cancelled`。OC 原生提供的 priority 保留;
CN 未勾选项映射为 `pending`,已勾选项为 `completed`,不推断 `in_progress` 或 priority。
OC 无稳定 item ID,视图索引仅服务本次完整列表渲染,不生成跨更新的条目身份。
清单内容不创建、调度或完成产品 Task,也不改变底部请求状态。清单更新与工具活动可以
同时保留,但顶部计数只来自已确认的结构化清单,不能反向解析普通状态文本、任意
Markdown 或名称含 `list` 的工具。子会话清单不覆盖父请求顶部。
以上产品状态规则已明确;远程 CN 接入、原生格式确认及历史字段选择仍属技术待办,见
[进度记录](./progress.md#2026-09-15-runtime-执行清单设计未实现)。
## Runtime 资源生命周期
本节对应 [FR-RR1–FR-RR6、US-RR1–US-RR4](./prd.md#141-runtime-资源复用),
定义进程复用的行为规则。当前源码已接入本机共享 owner 和 Agent 共享 ACP;
完整验收状态以[进度记录](./progress.md#2026-09-14-runtime-进程复用实施中)为准,不表示已发布。
具体 owner、兼容键和协议实现见[技术设计](./runtime-process-reuse-technical-design.md)。
资源有三个独立维度:进程是否启动/退役,会话是否仍持有原生上下文,请求是否在途。
“任务完成”“当前没有选中该项目”“工作栏关闭”均不能直接推导进程可以销毁。
| 触发 | 请求与会话行为 | 进程行为 | 对应需求/场景 |
| --- | --- | --- | --- |
| 被动状态读取 | 不取得新执行会话,已有状态可读取 | 不冷启动,不延长无会话 owner 的空闲期限;不为探测替换服务中的进程 | RR2 / US-RR1 |
| 显式测试/原生清单 | 取得临时检查引用;清单带明确工作区 | 与执行共用启动 Promise 和 owner;结束只释放检查引用 | RR1、RR2 / US-RR1 |
| 首次或并行 Prompt | 取得对应 execution space + conversation 的绑定 | 同兼容组复用,非兼容组分别启动 | RR1、RR3 / US-RR1、RR2 |
| 普通请求完成 | 释放请求订阅、临时 token、问题和 callback,保留可续聊 Session | 不因为一个请求完成关闭整个进程 | RR3、RR4 / US-RR3 |
| 取消一个请求 | 取消其模型/工具调用并结算,其他会话继续 | 不以 kill 共享进程代替正常会话取消 | RR3、RR4 / US-RR2 |
| 关闭面板/切换项目 | 停止非必要 UI 推送;不取消后台任务或抹掉会话所有权 | 不触发隐式关闭 | RR4 / US-RR3 |
| 显式释放/删除会话 | 释放该 Session 与会话工具资源;删除历史仍只由原有明确删除操作决定 | 其他引用仍在则保留 | RR4 / US-RR3 |
| 无会话、无请求、无检查引用 | 不存在需要保留的原生上下文 | 进入短空闲等待,再关闭;新使用者到来取消等待 | RR4 / US-RR1、RR3 |
| 配置替换 | 已接受请求使用旧配置结算,新请求使用新配置 | 旧 owner 排空后退役;不积累只被历史 ID 持有的旧配置进程 | RR1、RR4 / US-RR4 |
| 进程故障/不可响应 | 只给该 owner 下受影响请求明确中断;未知结果不自动重放 | 有界终止失效 owner,不伪造逐会话故障隔离 | RR3、RR4 / US-RR2、RR4 |
| SSH detach | 保留 Agent 已接受 Prompt 及既有恢复归属 | 关闭传输不关闭 Agent 或活动 Runtime | RR5 / US-RR4 |
表中 RRn 是 PRD 稳定需求 ID `FR-RRn` 的简写。
不变量:
1. 一条请求始终使用接受时的 execution space、工作模式、配置和归属;不能读取后来
切换的 UI 选择,也不以进程 cwd 表示所有会话的 cwd。
2. 同一会话串行,跨会话不增加全局 Prompt 队列。权限、子代理、问题、用量和最终清理
必须沿同一 Session/请求归属,不因共享进程放宽 Ask 或限制 Execute。
3. owner 的退出条件依据所有使用者,不依据某个 adapter 的空闲状态。
未启动对象的 dispose 不产生冷启动;一个等待者取消不取消其他等待者的初始化。
4. 正常会话仍持有原生上下文时,不用任意 TTL 清掉它来声称“空闲内存已回收”。
无会话 owner 的短空闲等待为 60 秒;
不增加用户配置项。它是进程引用回收,不是聊天历史保留期限。
5. 持久历史不因请求结束、进程 idle、配置变化或测试 cleanup 被删除。
原生进程确实重启后的续聊沿现有历史重建规则,不新增第二套持久会话快照。
已明确普通并行、取消、释放、配置替换及断线行为。各项实际通过的场景及尚未完成的
组合/性能验证以[进度记录](./progress.md#2026-09-14-runtime-进程复用实施中)为准。
@@ -0,0 +1,387 @@
# Runtime 进程复用技术设计
## 1. 状态与结论
| 项目 | 内容 |
| --- | --- |
| 状态 | 已接入本机与 Agent 生产源码,Windows / Linux x64 验证结果及边界已记录,未发布 |
| 日期 | 2026-09-15 |
| 调研源码 | `de14a6157a6c34f1e3ef22619eca374a50a83706`;相关 Runtime 与 Desktop 0.13.2 一致 |
| 需求 | [工作栏 PRD §14.1,FR-RR1–FR-RR6](./prd.md#141-runtime-资源复用) |
| 行为规则 | [Runtime 资源生命周期](./runtime-interactions.md#runtime-资源生命周期) |
| 验证事实 | [实施与验证记录](./progress.md#2026-09-14-runtime-进程复用实施中),原始调研记录保留在同页 |
**修复对象是进程所有权过细,不是工具太多,也不是历史记录应该删除。** 本机 OpenCode
和 DeepSeek Harness 应在兼容启动配置内共享重型进程,工作区与会话上下文仍各自独立。
远端 OpenCode 必须同时调整 Agent 进程所有权、ACP 连接和模型桥,不能把桌面修复当作远端
修复。Continue 的锁定版本是单会话 Host,不做未经验证的常驻进程池;直连模型没有独立
模型进程,应检查对象、历史和工具资源的生命周期。
“一个进程”指一组兼容启动配置对应一个 Runtime Server/Host,不是把不同 Runtime、
不同 SSH 账号、不同插件组合强行塞入一个进程。正常工具命令产生的短期子进程不属于重复
Server,也不能通过关闭 Shell、子代理或 MCP 来达成进程数指标。
## 2. 修复前证据与缺陷
### 2.1 已测现象和解释边界
Windows 已安装 0.13.2 的只读采样发现两个属于同一 App 的 OpenCode Server:一个原生
数据库为 0 Session / 0 Message,约 527 MiB Private Commit;另一个已有会话并执行请求。
两个 Server 的启动工作区不同。源码能解释“清单查询也可启动按工作区缓存的 Runtime”,
但没有取得能独立证明空 Server 最初由哪一个 UI 操作启动的调用链记录。
该缺陷不等于整个 App 内存的唯一来源。五分钟活跃采样的 App 相关进程 Private Commit
约 3.15–3.78 GiB,CPU 平均占整机约 3.15%;后续一分钟包含新任务,不是空闲回收试验。
数据库五分钟增长约 2.72 MiB,新增子代理负载约 1.49 MiB,重复工具 upsert 为 0。
schema 35 的存储修复与本方案独立,不能回退或删历史来改善指标。
同次源数据检查还发现最近 100 个会话的消息 metadata 合计约 107.75 MiB,
`listConversations()` 会读入这些会话的全部消息详情。它是 Renderer 大对象加载的明确
检查方向,不是堆快照已经证明的泄漏。进程复用不能代替历史按需加载的独立性能工作。
### 2.2 调研基线的源码归属
| 位置 | 已确认行为 | 修复含义 |
| --- | --- | --- |
| [SelectedRuntimeManager](../../../src/main/agent/selected-runtime-manager.ts) | 缓存键包含原始 selection 与 execution-space identity;默认 8 个缓存、16 个保留实例;只在容量压力下淘汰 | 轻量工作区适配器和重型进程不能继续按同一键、同一数量计费 |
| [AgentRuntimeController](../../../src/main/agent/runtime-controller.ts) | `canRetire` 同时要求无请求、无 owned conversation;普通完成不移除会话所有权 | 不能把“已完成请求”和“已释放原生会话”混为一谈 |
| [本机 OpenCode](../../../src/main/agent/opencode-runtime.ts) | 对象私有 Server/client/初始化 Promise;已有多会话映射;多个入口使用固定 `defaultWorkspace` | 仅删除 manager 的 workspace 键会把后续操作送进错误目录 |
| [DeepSeek Runtime](../../../src/main/agent/deepseek-harness-runtime.ts) | 每对象一个 Utility Host;`getStatus()` 调用 `getState()` 启动 Host | manager 的临时状态实例会额外启动、探测并销毁 Host |
| [Harness Host](../../../src/main/deepseek-harness-host.ts)、[控制面](../../../src/main/agent/goodbuddy-harness-control-plane.ts) | 根 Provider 有 cwd 默认值;控制面拒绝与 Host workspace 不同的 `newSession.cwd` | 限制主要在 GoodBuddy 创建入口,不是原生工具缺少会话 cwd |
| [Continue Host Adapter](../../../src/main/agent/continue-host-adapter.ts) | 每次 run 启动临时 Host,最终 `/exit` 与进程树回收;准备结果已在 adapter 内缓存 | 不重复添加已有缓存;研究跨 adapter 重复准备,不直接共享活动 Host |
| [直连模型](../../../src/main/agent/model-runtime.ts) | Main 内 HTTP;历史 Map 按 50 个会话限制,其他归属及工具资源依赖 release/dispose | 没有可合并的常驻模型子进程,按对象和资源检查 |
| [远端 ACP](../../../src/main/agent/acp-remote-runtime.ts)、[Agent backend](../../../src/agent-daemon/runtime-acp-backend.ts) | 会话取得 binding/channel;backend 按 binding 拥有进程并拒绝跨 binding 复用 process identity | 必须拆开会话绑定与进程所有权 |
| [Agent-owned Prompt](../../../src/agent-daemon/agent-owned-acp-prompt.ts)、[模型桥](../../../src/agent-daemon/model-bridge-helper.ts) | owner 一个 ACP connection/session/当前 operation;helper 固定模型和工作模式并串行 dispatch | 共享进程还要求会话路由、请求归属和取消隔离 |
原始 selection 的 `auto`、默认 profile 和显式 profile 可能解析为同一有效配置。当前
已确认“先按原始 selection 建键、后解析配置”的顺序,但尚未证明全部 UI 调用会实际生成
等价重复实例。实施时从 [runtime-selection](../../../src/main/agent/runtime-selection.ts)
和调用者验证,不把可能性记为另一次生产复现。
### 2.3 锁定依赖与协议基础
以仓库 lock 和本机已安装代码为准,不用最新网页替代锁定版本:
| 依赖 | 基线 | 本次核验 |
| --- | --- | --- |
| OpenCode binary / SDK / plugin | 1.18.29 | HTTP API 接受 directory;ACP 接受多个 Session;`chat.headers` 有 sessionID |
| ACP SDK | 0.25.1 | 一条连接关联多个 request ID,session notification/permission 带 sessionId |
| DeepSeek Harness 包族 | 0.1.2-rc.1 | Agent factory/scoped context、Session header.cwd、真实文件与 Shell 工具 |
| Cordis | 4.0.2 | Host 根组件与 Agent scope 的生命周期 |
| Continue CLI | 1.5.47 | 实际受管补丁入口的单 `M.session`、QuizService 和权限状态 |
已通过的三个隔离探针:
1. **OpenCode HTTP**:调用当前生产 Server launcher,只有一次 spawn;同一个 SDK client
在两个目录创建四个 Session,读取各自的同名文件;删除一个 Session 后其余三个仍可取回。
没有调用 `Runtime.run`、模型或活动取消。
2. **DeepSeek 工具**:当前 Host composition 和真实 Agent factory 在同一 Node 测试进程中
创建两个不同 cwd 的原生 Agent;并行执行真正的 `read`、`write` 和 PowerShell,
同名相对路径各自命中正确目录。另确认生产控制面仍拒绝第二工作区。没有启动 Electron
Utility、ACP Prompt 或模型流,不能据此声称生产多工作区已经可用。
3. **原生 ACP 与模型归属**:Windows 上一个 1.18.29 `opencode acp` 进程、一条 ACP
connection、两个工作区 Session;分别选模型并发出并行 Prompt。本机确定性 HTTP
服务收到两次请求,原生 `chat.headers` 插件传来的 sessionID 分别匹配两个 Session。
不经过已发布 Agent backend/helper,没有测试真实工具、子代理、Linux 或断线恢复。
三个探针真实模型调用均为 **0**,其中第三个有 **2 次本机合成提供方请求**。
它们证明方案有原生基础,不替代后文的真实开发任务验收。
特别纠正:`dsh-tool-fs` 将 `exec.agent.session.header.cwd` 传给 `fs.resolve`;
`dsh-tool-bash` / `dsh-tool-pwsh` 也从 Session cwd 补全相对 workdir。
`dsh-fs-local` / 本地 Shell 的根 cwd 是非会话调用的默认值,不是工作区隔离边界。
不需要为这个已具备的能力新建一套“每工作区 Filesystem/Shell Provider”。
官方背景资料:[OpenCode Server](https://opencode.ai/docs/server/)、
[DeepSeek Harness](https://www.deepseek.com/harness/en/)、
[Continue CLI](https://docs.continue.dev/cli/quickstart)。
网页只用于核对产品入口;上述版本结论来自本地锁定代码和探针。
## 3. 选定的结构
### 3.1 两层对象,不建立通用进程池框架
```text
SelectedRuntimeManager
-> 轻量 Runtime adapter(有效 selection + execution space)
-> OpenCode process owner / Harness Host owner(兼容启动配置)
-> Session binding(execution space + conversationId)
-> 当前 requestId / 原生 sessionId / 工作目录
```
- 保留 Main 已解析、校验的 execution space;adapter 每次将其显式交给共享 owner。
目录不是 Renderer 新增的任意启动参数,也不依赖 `process.chdir` 或进程级可变 cwd。
- owner 持有唯一启动 Promise、client/connection、子进程、会话映射与关闭逻辑。两个
工作区同时冷启动时复用同一 Promise;一个调用者取消等待不取消其他使用者的启动。
- adapter 的 `dispose` 只释放其引用,不直接关闭同组进程。会话映射由 owner 持有,避免
adapter 缓存淘汰丢掉原生续接。会话操作通过 binding 查目录,不从“当前选中项目”反查。
- 设置重建沿用现有 reset/drain:新请求取得新配置,旧请求持有启动时的配置直到结算。
旧配置不再有在途使用者后释放旧绑定和进程,不能被历史会话 ID 永久钉住。
IPC 的 Runtime 设置、定制及能力更新不再主动取消全部请求或清空审批;controller
退役仍允许回答其已拥有的问题,但拒绝新请求和未知问题。
既有专家调度器仍在配置替换时取消专家子任务,团队父请求一起停止,防止用新模型
汇总旧 generation 的部分结果;不为该单独生命周期新建通用退役框架。
- 8/16 的旧上限不应继续限制轻量项目 adapter 数量。若保留重型 owner 的容量保护,
应统计实际进程配置组及退役进程;不靠提高常数掩盖“一项目一进程”。
- 只用进程内 Map、现有 controller、Promise 和普通定时器。会话历史仍以现有 SQLite /
Agent transcript 为准,不增加数据库表、迁移、持久化池快照、恢复日志或新配额。
### 3.2 兼容键与上下文
先通过 `applyRuntimeSelection` 等现有解析取得有效配置,再生成进程键;公开的 selection
仍按现有语义保存,不能把“跟随默认”永久改写成某个显式 profile。
| 范围 | 参与进程兼容性 | 留在会话/请求 |
| --- | --- | --- |
| 本机 OpenCode | 本机账号、二进制身份、有效模型连接/配置、启动环境、原生配置和插件组合 | 目录、conversation/session、Prompt、模式、当前权限和问答、请求级 MCP/知识 token |
| 本机 Harness | Host/依赖版本、有效模型连接、图片能力、凭据引用、启动环境、启用插件及配置、启动 Skill 集合 | cwd、conversation/session、请求模式、Main Web/MCP 代理与用量归属 |
| SSH OpenCode | 已有 Host/SSH 身份、Agent/Runtime installation identity、模型协议/连接、原生插件与启动配置 | Workspace、binding/session、operation、请求凭据和账本归属 |
第一轮只合并同一有效模型连接及等价默认选择,不顺便支持任意不同模型连接共享:
本机 OpenCode 配置使用固定 provider ID,Harness 用量也读取 Host config,跨 profile
混用会引入另一项真实风险。不同 profile ID 不通过比较或序列化明文 API Key 来合并;
密钥、Header 和插件配置更新通过现有设置失效路径使旧 owner 退役。
调研基线的 SSH launch profile 把 workMode 写入原生启动配置。当前实现把工作模式
放入 Session/当前 Prompt 路由,并设置原生 Session 工具规则,详见 §6.1;
不能仅从兼容键中删除 workMode,也不向用户增加逐工具审批。
## 4. 本机 OpenCode
在 [opencode-runtime.ts](../../../src/main/agent/opencode-runtime.ts) 中抽出重型 owner,
由 [create-runtime](../../../src/main/agent/create-runtime.ts) 的 Main composition 复用。
保留工作区 adapter,逐项替换隐式 `options.defaultWorkspace`:
1. create/get/delete Session、Prompt、历史初始化和压缩。
2. 原生能力、工具、文件、Skill、MCP 查询的 directory。
3. SSE 订阅、父子 Session 归属、待答 question 的 directory 与原生 ID。
4. 权限更新、abort、问题答复/拒绝、MCP 临时注册及最终清理。
会话绑定记录规范化绝对目录及原生 sessionId。同一 conversation 的请求继续串行,
不同 conversation 并行,不引入覆盖整个 Server 的 Prompt 队列。
现有 `snapshot: false` 必须保留。
`mcpMutationTail` 目前属于 Runtime 对象;共享后同一原生目录的变更必须由同一 owner
串行协调。注册名称、工具可见性、knowledge token 和清理仍按请求区分,不能用一个全局
“当前 MCP 列表”覆盖同目录或跨目录的其他请求。探针未覆盖动态 MCP,这是实施验收项。
第一轮保留现有按请求消费事件流和结束清理的方式,扩展目录及 Session 过滤即可。
只有测出重复 SSE 消费是瓶颈时才另做 owner 级事件分发,不同时引入第二套事件总线。
健康检查也读取 owner 的总 activeRequests,不能从一个空闲 adapter 判断整个 Server
无任务后重启。异常退出使该 owner 的全部绑定失效,明确结算受影响请求,不重放已执行工具。
## 5. DeepSeek Harness
### 5.1 共享 Host 的最小改法
- 将 `HarnessState`、Utility 启动 Promise、ACP connection 和事件路由提升到兼容配置
owner;Runtime adapter 保留请求工作区。
- 每个新 Session 在 Main 解析 `realpath`,Host 校验绝对路径格式,
不再要求它等于 Host 启动工作区;`meta.cwd` 传入已有 Agent factory。
不依赖 Windows 大小写字符串猜测,也不取消路径存在性校验。
- 保留一个本地 Filesystem、Subprocess 和平台 Shell Provider。Session 使用原生 cwd
路由;非 Session 的清单查询显式传入查询工作区,不能回退到第一个项目。
- 保留 `setup(agentCtx)` 注册 Skill 和工具视图、每 Session 的 Ask definition 身份校验、
`sessionId + requestId` 准备及事件路由。Main 的 Web/MCP callback 按对应请求找
ToolProvider,不挂到某个最先创建的 adapter 上。
- 第一轮保持同 profile、图片能力和插件组合共享,`queueUsage` 的模型配置因此仍一致。
给用量加跨会话断言,不借本次改造扩大跨模型 Host 能力。
第三方插件的初始化可能依赖配置中的路径,不能因为内置工具支持 cwd 就声称所有插件
天然多工作区安全。已启用插件组合属于进程配置;对实际产品支持的插件验证它使用
Agent scope/Session cwd。若某插件确实有项目级初始化状态,将该状态显式作为不兼容
启动配置,而不是默认给所有工作区各起一套 Provider 或禁止 Execute 工具。
### 5.2 状态与取消
被动 `getStatus` 改为已有 owner 快照或本地配置/Host 资源检查,不调用 `getState()`。
没有启动过时不能写“握手已验证”,可表示资源已安装、尚未连接。
状态检查不取得执行引用,也不清除或重置无会话 owner 的空闲计时;owner 已回收时,
临时配置检查对象用完即释放,不将其登记成新的执行 owner。
用户显式连接测试与原生清单可启动共享 owner;已有 owner 时只做有界控制检查,最终
释放的是检查引用,不能在 `finally` dispose 掉仍服务其他会话的 Host。
当前取消超时会 terminate 整个 Utility。共享后先取消并释放目标 Session 的 Agent /
工具资源,不因一个请求结算较慢就杀掉仍健康服务其他 Session 的 Host。若真实控制面
已失去响应,保留整个 Host 的有界终止,向受影响会话分别报告中断;不得把它说成
“取消完全不影响其他任务”。同步插件卡死的进程级故障无法在同一 JS 进程中强行隔离,
不为此新增通用恢复框架。普通长工具取消与整 Host 故障必须分开测试。
## 6. SSH OpenCode 与 Agent
本节定义当前源码的共享进程改造;当前已发布版本仍见
[远程技术设计](../remote-host/technical-design.md)。不能删除或绕过其持久 binding、
operation、transcript、ACK 和模型 dispatch 结果不确定性规则。
### 6.1 连接与所有权
将 `RuntimeAcpBackend` 的进程 owner 与 binding 分开:
```text
Agent 当前 Runtime installation + 兼容配置
-> 一个 process owner / 一条 ACP ClientSideConnection
-> sessionId -> binding -> 当前 operation / 原始 Prompt Promise
```
- 一条 stdio 流只交给一套 ACP decoder/connection,不能让几个 ClientSideConnection
竞争读取同一 stdout。消息和 permission 按 sessionId 分派给 binding 的当前 operation。
- `AgentAcpConnection` 统一连接级握手、stdio 解码与退出监听,Prompt
生命周期仍留在 binding。同一 Session 串行,其他 Session 不等待它完成。
- `closeAcpChannel`、取消和 release 只释放所属 binding/Session;最后一个 binding
离开后空闲 60 秒关闭进程。关闭 Session 时等待原生 `closeSession` 和原始 Prompt
结算,不等待排在 backend 控制队列后面的完成回调。只有原生控制面不响应时才有界
终止共享进程,并向其余请求报告中断,不声称这类故障可以逐 Session 隔离。
- terminal 与物理进程状态分开:取消/失败的 Session 也可返回
`processTree: running`,表示进程继续服务其他 Session,不能伪报进程树为空。
Desktop 与 Agent 须使用配套的新契约;原始字节透传 binding 仍保留独占进程规则。
- 共享 stdio owner 不使用单 Prompt 的累积输入和墙钟期限管理整进程;单次写入、
待写队列、输出背压继续有界,Prompt 输入与截止时间由 binding 管理。
- SSH attach 仍不拥有活动 Prompt,断线不能取消或重发 Agent 已接受的工作。
历史 binding 的语义和原生 sessionId 保留;内存进程索引从当前安装和已有绑定得出,
不增加第二份持久进程恢复账本。
- 同一 PID 的多个 binding 不代表同一权限或同一 operation。Host/installation identity
变化仍使用已有失效和退役规则,不把不同 Host、账号或 Runtime generation 合并。
原生 `chat.message` 在模型请求前设置该 Session 的工具规则:Execute 允许工具;
Ask 只开放原生只读、搜索和读取网络内容的工具,不开放 Shell/编辑/写入。
OpenCode 1.18.29 的 ACP 不转发未注册子 Session 的权限请求,且原生子代理只继承父
Session 的拒绝规则、不继承允许规则,因此子 Session 也按根 Prompt 设置规则,再
保留原生子代理的显式限制。不能只把进程启动配置统一设成 `ask`,否则子代理工具会等待。
### 6.2 模型桥归属
选用锁定 OpenCode 已有的 **`chat.headers` 会话标记**,扩展当前受管
[原生插件](../../../src/agent-daemon/opencode-subagent-plugin.ts),不靠解析正文、猜测
“当前最新 Prompt”或重写用户模型名来路由:
1. 现有 Agent prepare/start 顺序在 helper 的进程内登记 Session/根 Session 与
binding、operation;插件从原生 sessionID 及已建立的父子关系取得精确归属。
2. 在请求生成时固定所属 operation,将有界内部归属 Header 加到 loopback 模型请求。
归属必须覆盖原生子代理、自动/手动压缩,不能等 HTTP 到达后才绑定“目前活动的请求”。
3. helper 检查它是否仍属于已登记的 Prompt,选择对应现有 Agent gateway/凭据上下文。
转发真实提供方前剥离内部 Header;不将其当作密钥或新增认证体系。
4. 当前 helper 的单 socket/全局 dispatchBusy 改成按 operation 隔离的派发状态;
每个 operation 的现有轮次、顺序、字节和结果不确定性规则不变,取消 A 只关闭 A 的
提供方流,不关闭 B 的 socket、response 或待处理请求。
5. Prompt 终态撤销对应映射及凭据;迟到请求不能借用下一轮 operation。保留
`snapshot: false`、关闭不消费的 title 请求和现有父子问题转交。
普通 ACP 双 Session 的 Header 路由已有探针证据;子代理、压缩和终态竞态的实际
验证结果以进度记录为准。若锁定版本某条实际调用路径不触发 hook,先定位那条原生路径,
不得悄悄用全局串行 Prompt 或一个共享凭据槽冒充并行复用。
内部桥字段如需调整,按既有受管 Agent/Runtime 版本配套交付,不移动发布标签,不给
旧 Agent 宣称具备新行为。
### 6.3 恢复与终态投影
- Agent-owned 语义 Prompt 不一定产生原始 ACP journal 行。binary replay 仍校验现有
cursor 边界,但不存在 raw cursor 时不向 journal ACK 0;不虚建记录来满足 replay。
- 同一 Agent 断线恢复后,保留的 Session binding 使用新 transport generation,
Runtime 可以同时接纳新 Session。安装身份已经更换的 attach-only 请求明确失败,
不能无限重试阻塞项目,也不能借此重发旧 Prompt。
- Desktop 重启时,同项目的可恢复任务并行附加,不能等待一个待答 Prompt 结束才附加
第二个。live `question`/`question-resolved` 不属于有 provenance 的 transcript,
使用既有 pending Map,清除时只影响对应 request/question。
- `conversations.list` 将恢复租约的 request/message ID 和待答列表投影为可选
`activeRequest`,Renderer 合并表单并使 Stop 指向真实请求。保存 header/消息时不保存
这个实时字段。回答/跳过成功由 Main 按 task → assistant message 归属更新既有
`answeredQuestions`,不写另一份问题日志,不覆盖流式正文、已完成或失败状态。
- 明确确认的取消使用现有消息/任务终态事务一并结算,避免 Task cancelled 而消息仍
streaming。仍无法确认的 Stop 保持可恢复;连接中断本身不是已取消或已失败的证据。
## 7. Continue、直连模型与相邻内存问题
### 7.1 Continue
保留每请求 Host 的隔离和最终清理。1.5.47 的单 Session、QuizService/权限队列意味着
“一个 HTTP Server”不等于可安全承载多会话;这一例外有锁定源码依据。
`getPreparedHost()` 已缓存本 adapter 的准备 Promise,磁盘也有按内容确认的补丁产物。
先分别测量读取/哈希/补丁准备、Utility 启动、HTTP ready、首个提供方请求和退出耗时。
若确认跨 adapter 重复准备明显,再将只读准备结果与启动 Promise 按已验证的 CLI
identity/补丁版本放到 Main composition 复用;失败仍允许重试。不得复用临时 config、
token、global dir、活动 Session 或权限/问题队列。设置替换及窗口状态刷新也纳入测量。
不在本次给 Continue 加多 Session 补丁或常驻池。将来只有锁定版本真正支持多 Session
且生产桥验证通过,才替换这一实现。
### 7.2 直连模型及历史详情
- 不新增模型 Utility 或进程池。检查 `conversations`、`knownConversationIds`、
summary、ToolProvider 与 DirectModelProcessService 的实际引用与 release 调用链。
- 活动请求、会话可续读的有界输出和数据库历史是不同资源。请求结束回收临时闭包/
callback;会话释放才结束其可续读输出和工具资源,不在工具刚完成时删掉用户可查看结果。
- 当前 50 个会话的 history 限制不是字节上限。先采堆与 retained size,再决定可从
SQLite 重建的历史缓存是否使用字节约束和 LRU,不能先增加一套持久历史副本。
- Renderer 整包 metadata 加载是独立于进程复用的性能工作。现有实现改为列表摘要、
打开会话时读取完整历史,不截断已保存消息;搜索、复制、续聊、后台活动和缓存释放
规则见[会话列表读取与前端保留](./execution-history-storage.md#会话列表读取与前端保留)。
不将这项后续修改的收益计入原进程复用对照结果。
- 旧 Runtime 磁盘缓存可单独评估版本清理策略,不在本次研究或进程 idle 时删除安装、
用户工作区、数据库、备份或历史。
## 8. 内部实施顺序
| 阶段 | 具体工作 | 必须产出的证据 |
| --- | --- | --- |
| P0 | 固化本次原生探针为聚焦回归;补有效 selection/进程计数和状态启动计数 | 修复前能复现重复启动,失败原因不依赖真实用户配置 |
| P1 | Main composition、兼容键、owner 与 adapter 生命周期;本机 OpenCode 全入口目录化 | 真实 UI 两项目四会话同一 Server,文件/工具/问答/取消不串线 |
| P2 | DeepSeek 共享 Utility、会话 cwd、被动状态、请求级代理及取消收敛 | 实际 Utility/ACP 两项目四会话,原生工具与插件验证,状态刷新无额外 Host |
| P3 | Agent process/session owner、ACP 单连接路由、Header 模型桥、权限和终态清理 | 开发中的真实 Linux x64 Host,含多会话工具、断线/重连和结果不确定性 |
| P4 | 全 Runtime 资源回归;Continue 冷启动分解与有证据的准备复用;直连对象释放检查 | 相同输入的前后进程/内存/CPU/磁盘/延迟报告;没有残留测试进程 |
这是依赖顺序,不是发布一半实现的许可。涉及的正常生产 UI 路径、远端专用修复和下节
验收全部完成后,才能称为 Runtime 复用修复。Continue 测量若没有显示重复准备成本,
明确记录不改;不要为凑齐阶段增加代码。历史 UI 分页是后续独立工作。
## 9. 验收与性能比较
### 9.1 必测场景
| 编号 | 对应需求 | 场景与通过条件 |
| --- | --- | --- |
| V1 | FR-RR1、FR-RR2 | 全新 profile 仅查看状态、切换设置,OpenCode/DSH 重型启动次数为 0;已有 Host 时刷新不新建;清单与首个真实请求并发只有一次初始化 |
| V2 | FR-RR1、FR-RR3 | 同一有效模型连接,两个独立项目各两会话同时自然开发,OpenCode/DSH 各一重型进程;各项目同名不同内容文件、命令 cwd、Git/测试结果都正确 |
| V3 | FR-RR3 | 第二轮复用同一批会话;Ask/Execute 混合,Ask 写入被边界拒绝,Execute 不增审批;Skill、MCP、图片能力、用量和业务问答不串归属 |
| V4 | FR-RR3、FR-RR4 | 一会话取消真实长工具/子代理时另外三会话完成并通过独立项目测试;分别检查请求 abort、SSE、问题映射和工具句柄释放 |
| V5 | FR-RR4 | 关闭面板不取消工作;释放一个会话不关闭共享进程;无会话 owner 空闲后退出;App 退出后本机受管进程无残留,远端按已有 detach 规则保留 Agent-owned 工作 |
| V6 | FR-RR1、FR-RR4 | 等价默认/显式选择复用;不同实际连接/插件配置不串用;配置更新期间旧请求完成,旧 generation 不永久积累;超过八个项目不因轻量 adapter 报重型容量满 |
| V7 | FR-RR5 | 当前源码 Desktop → SSH → 当前开发 Agent → 原生 Runtime,两个项目/四会话;断线、重连、取消、重复 ACK/事件、迟到模型请求不重复调用或跨 operation 归账 |
| V8 | FR-RR6 | Continue 并发请求仍各自正确清理;直连 HTTP 不生出额外模型进程;会话 release 后工具/分页输出资源释放;性能采样不靠删除历史或禁用工具达标 |
V2/V4 必须是正常开发任务,模型可自然调用读写、Shell、测试和已支持的原生子代理;
不是要求模型“不用工具”后回复一行。验证产出应用本身可运行、独立测试通过,模型口头
说完成不算完成。每个 Runtime 只验证它已经支持的能力,不伪造 DSH/Continue 子代理。
### 9.2 采样口径
- 使用同一源码变更前后、同一机器、隔离 profile 与等规模历史副本;不改写用户运行库。
覆盖至少三次冷启动、五次热请求,以及三轮多项目任务和至少十分钟**确无新任务**的
空闲观察。分别记录中位数、范围与实际样本数,不从一次采样推断泄漏已消失。
- 记录 Main、Renderer、Runtime 和工具子进程的 Private Commit、工作集/RSS、堆、
CPU、PID/启动次数、会话数、活动请求、SSE/ACP 订阅与残留句柄。Windows 工作集求和
会重复计算共享页,Private Commit 也不等于独占物理 RAM。
- 同时报首轮准备、HTTP/ACP ready、首个模型请求、首次可见输出和总耗时。降低进程数
但把不同会话全局串行化,不能通过验收。
- 数据库/WAL 字节与新增事件负载分开;进程 I/O 包含管道,不能称为物理磁盘读写。
继续检查未变化工具 upsert 为 0,旧历史读取/搜索/展开正常。
- 核心硬指标是每个兼容组一重型进程、无额外被动启动、无跨会话错误、无结束后持续
增加的活动句柄、无重复提供方 dispatch。没有证据前不承诺“固定省 527 MiB”或
“整个 App 降到某个 RAM 上限”;原生 Session 仍保留时必须单独说明其常驻成本。
### 9.3 源码与平台验证
定向覆盖 manager/controller、各 Runtime lifecycle、Harness Host/control plane、
ACP backend/owner、模型桥与插件,随后执行仓库要求的 `npm test`、`npm run typecheck`、
`npm run lint`;生产构建改动在开发阶段按仓库规则构建验证。
凡改变 Agent 或桌面到 Agent 路径,必须在开发期间按
[真实 Host 验证规则](../remote-host/technical-design.md#agent-开发期间的真实-host-验证)
使用当前源码的共享 Linux x64 Host 验证。Windows 原生 ACP 探针、旧版本 Host、mock、
CI 和将来发布测试都不能替代它。Windows PowerShell 与 Linux Bash 的工作目录及取消
分别验证;macOS/Linux arm64 生命周期与原生依赖由可用原生平台和 CI 补齐并明确报告。
当前源码的真实 Linux Host、完整生产 UI 和最终整仓门禁结果均保留在进度记录;
各次失败、修正、模型调用数及未测平台也一并记录,不以本机单测或原生协议探针
替代完整产品验收。完整 App 性能样本保留用户要求的按钮位置调整,属于包含该 UI
差异的实测对照,不是纯 Runtime 单变量实验。
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,跨平台验证中 |
| 版本 | 1.1 |
| 日期 | 2026-08-31 |
| 版本 | 1.2 |
| 日期 | 2026-09-10 |
| 适用产品 | GoodBuddy 桌面端 |
| 界面归属 | [通用助手工作栏与执行空间](./prd.md) |
| 相关设计 | [统一界面设计系统](../../../UI-DESIGN.md) |
@@ -18,7 +18,7 @@
- 已打开应用以同级 Tab 呈现。
- Tab 行始终提供“+”入口,用户按需打开应用。
- 应用通过统一注册表声明单实例或多实例策略。
- 应用通过统一注册表分别声明单实例或多实例、默认打开、可关闭和排序策略。
- “终端”作为首个多实例应用,自动使用当前项目的本机或托管 SSH 执行空间。
- 本机与远程交互式终端的生命周期、状态、性能和验收要求。
@@ -31,7 +31,7 @@
当前工作栏在聊天页面右侧固定显示“任务中心、工作区、浏览器、成果”四个 Tab。用户不能:
- 通过统一入口按需打开新的工作栏应用。
- 关闭并重新打开一个工作栏应用。
- 按应用策略关闭并重新打开可关闭的工作栏应用。
- 为支持多实例的应用创建第二个独立实例。
- 在 GoodBuddy 内使用本机或远程 Host 的交互式 Shell。
@@ -49,17 +49,51 @@ SSH 连接池,但尚不存在工作栏应用实例模型、终端 IPC、终端
- 稳定应用 ID。
- 用户可见名称、图标和说明。
- `single` 或 `multiple` 实例策略。
- 是否默认打开、是否必须存在、是否可关闭和是否允许排序;这些属性彼此独立。
- 创建实例时可用的默认上下文。
- 当前不可用时的原因和修复入口。
目录中的应用可发现性与实例是否已经打开相互独立。应用不可用时保留入口并说明原因,不以
隐藏代替状态。
注册表字段如下:
| 字段 | 类型或允许值 | 含义 |
| --- | --- | --- |
| `id` | 稳定应用 ID | 关联图标、面板实现和持久化实例 |
| `label` | 有界文本 | 默认 Tab 名称和目录名称 |
| `icon` | 稳定图标 ID | 应用目录和 Tab 图标 |
| `description` | 有界文本 | 目录说明和完整提示 |
| `instancePolicy` | `single`、`multiple` | 同一应用允许一个还是多个实例 |
| `defaultContext` | `application`、`current-project`、`current-conversation` | 创建实例时使用的默认范围,不代表实例随后必须跟随 |
| `visibleAcrossContextSwitches` | boolean | 切换项目或会话后,已打开的 Tab 是否继续留在工作栏中 |
| `defaultOpen` | boolean | 新布局是否创建默认实例 |
| `required` | boolean | 布局加载时实例缺失是否必须补回 |
| `closable` | boolean | 是否渲染关闭入口并接受关闭命令 |
| `reorderable` | boolean | 是否允许用户改变 Tab 顺序 |
| `availability` | `available` 或带原因和修复入口的 `unavailable` | 当前能力状态 |
`defaultOpen`、`required` 和 `closable` 不互相推导。例如浏览器默认打开但不是必需实例;任务
中心和工作区既默认打开又必须存在。`required` 不表示锁定顺序,顺序只由 `reorderable` 控制。
当前五个应用的 `visibleAcrossContextSwitches` 均为 `true`。该属性记录应用目录的显示策略,
不改变内容归属,也不授予其他会话的 MCP 访问权限;当前没有使用 `false` 的应用,尚未实现
按上下文隐藏 Tab 的策略。
浏览器在创建时绑定当前 Conversation;切换会话后仍保留原 Tab、选中状态和页面,导航操作
继续使用原 `targetRef.conversationId`。此时新建浏览器才绑定新会话。默认或旧布局中尚未绑定
的浏览器在首次使用时绑定活动会话,此后不再随会话切换改绑。终端同样保留创建时执行目标;
任务中心、工作区和成果的内容继续跟随当前项目。
### 3.2 工作栏 Tab 实例
工作栏 Tab 是应用的一次打开实例。每个实例具有独立 ID、标题、应用类型和状态。Tab 行只
显示已经打开的实例,不把全部能力同时渲染为 Tab。
实例持久化字段只有 `id`、`appId`、`title` 和可选 `targetRef`。`targetRef` 使用公开引用:
终端接受 `local` 或 `projectId`,浏览器接受 `conversationId`。Main 签发的 Browser Tab ID、
Terminal Session ID、元素引用、凭据、路径和运行状态不进入工作栏布局。
### 3.3 终端实例
每个终端 Tab 对应一个独立交互式 Shell Session:
@@ -81,10 +115,10 @@ stdio fallback。首期远程终端随 SSH Channel 结束,不提供跨 SSH 或
5. 单实例应用已经打开时,再次选择只聚焦已有实例,不创建重复 Tab。
6. 多实例应用每次选择都创建新实例。
7. 终端从首期开始就是多实例应用。
8. 任务中心、工作区、浏览器和成果首期保持单实例;后续只需修改应用注册表策略即可开放
多实例,不重写 Tab 壳层。
9. 现有四个应用在升级后仍按当前默认布局打开,避免用户入口突然消失。
10. 用户关闭、切换或重排应用 Tab 后,“+”仍保持可见。
8. 任务中心和工作区保持单实例、默认打开且不可关闭,分别固定在第一、第二位,`reorderable` 为 `false`。恢复旧布局时也按此规则修正顺序,其余 Tab 保持相对顺序。
9. 浏览器注册为可关闭的多实例应用,升级后保留一个默认实例;成果保持单实例、默认打开且
可关闭。
10. 用户关闭可关闭实例、切换或重排应用 Tab 后,“+”仍保持可见。
11. 选择“终端”后不再显示第二层目标选择器。存在当前项目时直接绑定该项目,由 Main
根据项目执行空间自动打开本机或托管 SSH Shell;没有当前项目时打开本机 Home Shell。
12. 远程首期使用直接 SSH PTY。SSH 断线后终端进入“已中断”,重新连接会创建一个新的
@@ -109,7 +143,8 @@ stdio fallback。首期远程终端随 SSH Channel 结束,不提供跨 SSH 或
### 5.2 产品目标
- 用一个可扩展的应用注册表统一单实例与多实例行为。
- 把现有固定四 Tab 迁移为用户实例,而不回归现有任务中心、工作区、浏览器和成果能力。
- 把现有 Tab 迁移到统一实例模型,同时保持任务中心和工作区始终存在,并允许浏览器和终端
创建多个实例。
- 建立真实的 Main/Preload/Renderer 终端生产链路,而不是用普通文本框模拟终端。
- 复用现有 SSH Host Key、凭据和连接池,不建立第二套 Host 管理。
- 保持终端输入输出有序、响应流畅、内存有界,并可靠清理本机和远程资源。
@@ -149,7 +184,8 @@ stdio fallback。首期远程终端随 SSH Channel 结束,不提供跨 SSH 或
- 当前 Tab 同时使用连续内容表面、选中边框、顶部强调线、图标和较高字重,不能只靠文字颜色。
- Tab、关闭按钮和“+”入口沿用共享控件圆角;终端面板外框和终端顶部工具栏按钮使用直角,
保持紧凑的停靠式终端界面。
- Tab 关闭按钮默认不争夺视觉注意力,仅在鼠标悬停或键盘焦点进入对应 Tab 时出现;
- 只有可关闭实例渲染 Tab 关闭按钮。关闭按钮默认不争夺视觉注意力,仅在鼠标悬停或键盘
焦点进入对应 Tab 时出现;
粗指针或无悬停设备始终显示,出现与隐藏不得引发布局位移。
### 7.2 新建应用选择页
@@ -160,31 +196,38 @@ stdio fallback。首期远程终端随 SSH Channel 结束,不提供跨 SSH 或
2. 在工作栏内容区显示“新建工作栏应用”。
3. 列出稳定应用目录,每项显示名称、说明、单/多实例提示和当前可用状态。
4. 初始焦点进入第一个可用应用;Escape 或“取消”返回原活动实例和“+”按钮。
5. 选择单实例应用时,若实例已存在,直接聚焦该 Tab。
6. 选择终端时直接解析当前项目执行空间,创建新 Tab 并聚焦终端输入,不再要求用户
5. 选择单实例应用时,若实例已存在,直接聚焦该 Tab;任务中心和工作区始终走此路径。
6. 选择浏览器时创建绑定当前 Conversation 的新 Browser Tab;没有活动 Conversation 时创建
待选择目标的空实例。
7. 选择终端时直接解析当前项目执行空间,创建新 Tab 并聚焦终端输入,不再要求用户
重复选择本机或远程目标。
选择页是“+”触发的临时界面,不进入实例顺序,不持久化,也不占用一个普通 Tab。
### 7.3 首期应用注册表
| 应用 | 实例策略 | 升级后默认状态 | 再次从“+”选择 |
| --- | --- | --- | --- |
| 任务中心 | `single` | 打开 | 聚焦已有 Tab |
| 工作区 | `single` | 打开 | 聚焦已有 Tab |
| 浏览器 | `single` | 打开 | 聚焦已有 Tab |
| 成果 | `single` | 打开 | 聚焦已有 Tab |
| 终端 | `multiple` | 不打开 | 每次创建新 Tab |
| 应用 | 实例策略 | 默认上下文 | 默认打开 | 必须存在 | 可关闭 | 可排序 | 再次从“+”选择 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 任务中心 | `single` | 当前项目 | 是 | 是 | 否 | 否 | 聚焦已有 Tab |
| 工作区 | `single` | 当前项目 | 是 | 是 | 否 | 否 | 聚焦已有 Tab |
| 浏览器 | `multiple` | 当前 Conversation | 是,一个实例 | 否 | 是 | 是 | 每次创建新 Tab |
| 成果 | `single` | 当前项目 | 是 | 否 | 是 | 是 | 聚焦已有 Tab |
| 终端 | `multiple` | 当前项目 | 否 | 否 | 是 | 是 | 每次创建新 Tab |
实例策略是应用注册数据,不能分散硬编码在多个点击处理器中。
实例策略、默认状态和可关闭策略都是应用注册数据,不能分散硬编码在多个点击处理器中。
新建浏览器使用独立编号标题,避开已有名称;关闭后再次新建也不产生重名。恢复布局保留已保存
的标题。会话名称用于标识归属,不替代浏览器实例名称。新建页面为空白页,同会话 Cookie 共享
不表示复用旧页面或导航历史。
### 7.4 Tab 操作
- 点击或使用方向键切换实例不会暂停或终止后台实例。
- 支持左右方向键、Home、End 和 roving `tabindex`。
- 关闭非活动 Tab 后焦点保持在当前活动 Tab。
- 关闭活动 Tab 后激活右侧相邻实例;没有右侧实例时激活左侧实例。
- 关闭最后一个实例后显示明确空状态和“打开应用”,Tab 行仍保留“+”。
- 任务中心和工作区没有关闭按钮,也不响应关闭快捷命令。
- 关闭可关闭的非活动 Tab 后焦点保持在当前活动 Tab。
- 关闭可关闭的活动 Tab 后激活右侧相邻实例;没有右侧实例时激活左侧实例。任务中心和工作区
始终存在,因此正常布局不会进入零实例空状态。
- 关闭正在运行的终端需要确认“关闭终端将结束 Shell 及其子进程”;取消是初始焦点。
- 已退出或已中断的终端可以直接关闭,不重复确认。
@@ -284,21 +327,45 @@ type TerminalSessionState =
```ts
type WorkbarAppDefinition = {
id: 'tasks' | 'workspace' | 'browser' | 'results' | 'terminal'
label: string
icon: WorkbarAppDefinition['id']
description: string
instancePolicy: 'single' | 'multiple'
defaultContext: 'application' | 'current-project' | 'current-conversation'
defaultOpen: boolean
required: boolean
closable: boolean
reorderable: boolean
availability:
| { state: 'available' }
| { state: 'unavailable'; reason: string; remedy: string }
}
type WorkbarTabInstance = {
id: string
appId: WorkbarAppDefinition['id']
title: string
targetRef?: {
type: 'project' | 'local'
id?: string
}
targetRef?:
| { type: 'local' }
| { type: 'project'; projectId: string }
| { type: 'conversation'; conversationId: string }
}
type WorkbarLayoutPreferences = {
instances: WorkbarTabInstance[]
activeInstanceId: string | null
expanded: boolean
dock: 'right'
widthRatio: number
taskScope: 'current-project' | 'global' | 'all-projects'
}
```
- 持久化工作栏打开状态、Tab 顺序、活动实例、应用 ID、自定义标题、公开目标引用和宽度比例。
- 加载布局时根据 `required` 补回缺失实例,并拒绝重复的单实例应用;应用策略不写入实例
数据,避免持久化状态覆盖注册表。
- `reorderable` 已进入共享契约;当前 Tab 壳层尚未提供拖放或键盘重排,不能把声明属性视为
已完成的排序功能。
- 不持久化终端正文、未执行输入、凭据、环境变量、PTY PID、SSH Channel 或派生 Host
identity。
- Main 创建终端后返回新的不透明 Session ID;Renderer 不能选择 PID 或 Channel。
@@ -474,16 +541,19 @@ Schema、假数据终端或不可用入口的中间产品。
### 18.1 工作栏应用与实例
- [ ] Tab 行末始终显示“+”,包括没有任何打开实例时。
- [ ] Tab 行末始终显示“+”;任务中心和工作区始终存在,因此正常布局不存在零实例状态。
- [ ] 点击“+”显示稳定应用目录,取消后返回原实例并恢复焦点。
- [ ] 选择已打开单实例应用只聚焦已有 Tab。
- [x] 任务中心和工作区默认存在且不可关闭,不渲染关闭按钮,也不响应关闭快捷命令。
- [x] 连续选择浏览器会创建页面、导航状态和元素引用相互独立的浏览器 Tab。
- [x] 连续选择终端会创建多个互相独立的终端 Tab。
- [ ] 关闭、切换和重排实例后,“+”仍保持可用。
- [ ] 关闭可关闭实例、切换和重排实例后,“+”仍保持可用。
- [ ] Tab 溢出时单行横向滚动,不隐藏“+”或把 Tab 换成下拉框。
- [x] 关闭活动 Tab 后激活正确的相邻 Tab 并恢复焦点。
- [x] 关闭可关闭的活动 Tab 后激活正确的相邻 Tab 并恢复焦点。
- [x] 当前 Tab 的背景、边框、顶部指示和字重清晰可辨;关闭按钮仅在悬停、焦点或无悬停
设备上出现,且不引发布局位移。
- [ ] 升级后现有任务中心、工作区、浏览器和成果仍按默认布局打开且行为不回归。
- [x] 升级后任务中心、工作区、一个浏览器实例和成果按默认布局打开;布局恢复会补回缺失的
任务中心与工作区。
- [ ] 在所有主要页面都能打开工作栏;切换主要页面不会销毁 Tab 实例或终端 Session。
### 18.2 本机终端
@@ -522,7 +592,8 @@ Schema、假数据终端或不可用入口的中间产品。
### 18.5 设计、无障碍和跨平台
- [ ] 仅使用键盘可以打开应用、创建终端、切换和关闭 Tab、复制、粘贴及重新连接。
- [ ] 仅使用键盘可以打开应用、创建浏览器和终端、切换 Tab、关闭可关闭 Tab、复制、粘贴及
重新连接。
- [ ] Tab、“+”、选择页和关闭确认使用正确语义并有可见焦点。
- [ ] 终端输出不逐行进入 live region,关键状态变化可被辅助技术感知。
- [ ] 浅色、深色、200% 文字缩放和窄窗口下可以完成核心流程。
@@ -535,7 +606,9 @@ Schema、假数据终端或不可用入口的中间产品。
### 19.1 自动验证
- 共享 Zod Schema 和边界值测试。
- 单/多实例注册策略、Tab 顺序、关闭焦点和“+”选择页组件测试。
- 单/多实例、默认打开、可关闭和排序策略,以及 Tab 顺序、关闭焦点和“+”选择页组件测试。
- 任务中心与工作区缺失时的布局修复、关闭命令拒绝和焦点保持测试。
- 浏览器多实例的页面隔离、Conversation 匹配和快捷入口去重测试。
- 本机 PTY Session Manager 的输入、resize、退出、幂等关闭和进程树测试。
- SSH PTY 的 Host identity、lease、断线、resize 和关闭测试。
- Main trusted sender、Session 所有权、IPC 输入边界和事件路由测试。
+5 -8
View File
@@ -92,14 +92,11 @@ Linux x64 和 Linux arm64。
### P0:右侧没有浏览器实时画面,已修复
浏览器窗口默认使用 `show: false`,但 BrowserService 现在从模型实际操作的同一
会话捕获页面帧,并通过受限 IPC 发送状态、当前 URL 和约 220KB 的 JPEG 画面。右侧工作栏
新增“浏览器”页签;活动对话启动浏览器时会自动打开该页签,并显示创建中、
加载中、操作中、用户交互中、就绪、失败和已停止状态。用户可点击“交互”打开
同一会话的子窗口辅助 Agent;交互时主窗口暂时禁用,关闭时先刷新最终画面再将
浏览器窗口最小化,页面和会话继续保留。用户也可明确关闭当前对话的浏览器会话。
当前实现按导航、快照、点击、输入、选择、返回和截图操作刷新画面,而不是创建
第二个预览浏览器,因此显示内容与模型受控页面一致。
早期版本通过同一浏览器会话的 JPEG 帧提供预览。当前工作栏直接嵌入 Agent 操作的原生
`WebContentsView`,不再自动截图或传输预览帧,也不再用独立交互窗口阻塞主界面。
页面显示、标签页归属、失败重试和显式截图的当前规则统一见
[浏览器多实例技术设计](../assistant-workbar/browser-tabs-technical-design.md);
该路径的最新验证证据见[工作栏进度](../assistant-workbar/progress.md)。
### 已实现:浏览器工具栏
@@ -0,0 +1,37 @@
# 会话媒体生成
实现状态:已完成。纳入 Desktop 0.13.5 / Agent 0.13.0 候选,尚未发布。日期:2026-09-15。实际验证范围单独见[进度记录](./progress.md)。
用户添加图片模型后,可以开启“允许 AI 在会话中调用”。支持工具调用的聊天模型通过 GoodBuddy 生成图片、编辑上传图片和历史成果。切换聊天模型或 Agent Runtime 后仍可发起生成和编辑;已提交操作固定生成模型,结果保存到原会话。
本目录负责普通 LLM 调用媒体模型的产品行为、调用入口和生命周期。现有直连图片协议及连续修改仍由[图片生成](../image-generation/README.md)维护;本设计不表示现有功能已改变。
## 范围
图片生成与编辑覆盖直连文本 Model Runtime、本地 OpenCode、Continue、DeepSeek Harness,以及 GoodBuddy Agent 远程路径上的适用 Runtime。复用已有图片协议和各 Runtime 的内部工具或 MCP 注入,由 GoodBuddy 自动管理;用户无需创建 Skill、搭建 MCP server 或重复配置凭据。
使用稳定工具 `generate_image` 完成生成与编辑。全局默认和本次指定模型足够,不新增会话级选择器或独立生成表单。无工具能力的聊天模型明确提示限制,可使用已有直连图片工作流。视频不在本设计范围;完整需求以[产品需求](./prd.md)为准。
图片能力在现有内置 MCP 列表持续可见,Runtime 分配以只读复选框展示,由图片模型的会话调用设置自动管理;没有开启的模型时全部取消勾选,仍保留该行和模型设置入口。分配规则见[行为规则](./logic-design.md),展示与说明见[界面设计](./ui-design.md)。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [产品需求](./prd.md) | 范围、需求编号与产品验收 |
| [用户场景](./user-stories.md) | 用户操作及场景验收 |
| [行为规则](./logic-design.md) | 选择优先级、切换规则、状态和失败处理 |
| [界面设计](./ui-design.md) | 配置入口、会话操作、结果卡片和反馈 |
| [技术设计](./technical-design.md) | 工具注入、Main 服务、存储与 Runtime 接入 |
| [进度记录](./progress.md) | 当前代码事实、剩余工作与验证证据 |
## 术语与依赖
聊天模型负责理解请求、调用工具和组织回答;生成模型负责图片生成与编辑;Agent Runtime 承载聊天模型及工具执行。工具提供结构化调用入口,使用说明随工具提供,不要求用户管理 Skill。
“生成操作”指一次图片生成或编辑及其结果记录,不自动创建任务中心的 Task,也不引入新的通用 Job 调度系统。领域边界遵循 [Task 与 Job 模型](../task-and-job/task-and-job-model.md)。
- [模型连接](../model-connections/README.md):连接、凭据、协议及请求定制。
- [直连文本 Agent](../direct-model-agent/README.md):文本工具循环与上下文。
- [远程主机](../remote-host/README.md):远程 Runtime 与桌面通信。
- [统一界面规则](../../../UI-DESIGN.md):控件、反馈和可访问性。
@@ -0,0 +1,78 @@
# 行为规则
状态:设计草案。此文是模型选择、切换与生成状态的权威定义,范围见[产品需求](./prd.md)。L-1 至 L-5 编号保留,以下规则替代原视频、会话级选择及通用查询恢复规则。
## 能力与调用时点
L-1(FR-1、FR-3、FR-7、FR-9;US-01、US-08、US-11、US-15 至 US-19):自动生成与编辑要求图片连接允许会话调用、协议支持所需操作、聊天模型支持工具调用且当前请求处于 Execute。直连文本 Model Runtime、本地 OpenCode、Continue、DeepSeek Harness 及 GoodBuddy Agent 远程适用 Runtime 均须接入同一图片能力,内部工具或 MCP 注入由 GoodBuddy 管理。
允许调用的模型目录取自已保存且“允许 AI 在会话中调用”开启的图片模型配置。目录非空时,图片生成与编辑能力的全部支持 Runtime 分配自动勾选;目录为空时全部取消勾选。模型配置是唯一事实来源,不保存独立可变的能力启用或分配设置,用户不能操作分配复选框。
| 配置或运行变化 | 分配与目录结果 |
| --- | --- |
| 首次开启或重新开启一个图片模型 | 自动勾选全部支持 Runtime,目录包含该模型 |
| 关闭或删除一个模型,仍有其他开启模型 | 分配保持勾选,从目录移除该模型 |
| 最后一个开启模型被关闭或删除,或从未开启任何模型 | 全部分配取消勾选,目录为空;能力行、说明和模型设置入口仍可见 |
| Runtime 离线、聊天模型无工具能力、Ask、配置或执行错误 | 保持按模型开关推导的勾选和目录,另行显示调用限制或错误 |
配置保存成功后同步刷新分配展示和允许模型目录;保存失败时保留最后已保存配置推导的状态。分配不表示运行就绪,也不保证每个图片模型支持编辑;协议及实际提交校验仍适用。
请求开始和聊天模型、Runtime、配置切换后刷新可用能力;进程复用也须刷新。运行中的聊天回合保留原工具协议,Main 在实际提交前检查最新配置及工作模式,已关闭的连接不能接受新操作。无工具模型说明限制并提示已有直连图片工作流;Ask 不提交生成或编辑,不自动切换模式。Runtime 接入尚未完成属于实施缺口,不能作为减少支持范围的规则。
## 模型和素材选择
L-2(FR-2、FR-5;US-02、US-03、US-05、US-07、US-14、US-15):先确定新建或编辑,再选择图片模型。
| 操作 | 选择优先级,由高到低 |
| --- | --- |
| 新建图片 | 用户本次明确指定 > 全局默认 |
| 编辑历史成果 | 用户本次明确指定 > 原成果模型 > 全局默认 |
| 编辑上传图片(无来源模型) | 用户本次明确指定 > 全局默认 |
只有较高优先级未设置时才查看下一项。选定模型被删除、关闭或不支持编辑时提示改选,不静默跳过。未设默认时提示本次指定或去设置默认后重新发送;名称歧义先澄清,LLM 不得编造模型 ID。不持久化会话级生成模型选择。
显式附件或点选成果优先。“刚才那张”等指代仅在对象唯一时解析;多义指代先澄清。新建不相关图片不附带旧图。编辑能力不足或原图缺失时保留输入并提示,不把编辑自动降为文生图;用户可以明确要求按文字重新生成。已有直连图片路径的行为仍由[图片技术设计](../image-generation/technical-design.md)定义。
## 切换规则
L-3(FR-4;US-03 至 US-07、US-11、US-15):Main 接受提交时固定原会话、触发消息、实际图片模型、当次连接参数、生成参数及素材引用。未提交请求在实际提交时解析默认值并校验显式选择;提交后不重选模型。图片请求由 Main 持有,独立于聊天 Runtime 生命周期。
| 变化 | 未提交操作 | 已提交操作与成果 |
| --- | --- | --- |
| 切换聊天模型 | 新回合使用新模型及可用工具,无工具能力时说明限制 | 图片模型不变,操作独立继续 |
| 切换本地或远程 Runtime | 新回合注入图片工具,可发起生成和编辑 | 不依赖旧进程,不向新进程补发旧工具响应,不重放生成 |
| 更改全局默认或本次指定 | 新操作按 L-2 解析 | 保持提交时模型 |
| 关闭或删除连接 | 拒绝新操作并提示改选 | 已发请求使用当次配置尽力完成 |
| 更改地址、凭据或参数 | 新操作使用新配置 | 当前操作不混用新端点或凭据 |
| 离开或重开会话 | 使用当前请求及全局配置 | 只更新原会话,历史成果仍可引用 |
| 远程断连 | 未被 Main 接收的请求不能声称已提交 | 桌面仍运行时由 Main 继续管理已接收操作 |
“停止回复”只结束文字回合;“取消生成”单独处理图片操作。切到 Ask 不撤销先前 Execute 已提交的操作,也不允许新增生成。运行时切换不自动唤起新的 LLM 请求。
## 操作状态
L-4(FR-6、FR-8;US-04、US-09、US-13):复用现有操作与成果反馈,状态只表达实际知道的事实。没有上游百分比时显示状态及等待时间,不虚构进度。
| 状态表达 | 条件与处理 |
| --- | --- |
| 进行中 | 正在发送或等待响应;仅有上游确认时才说明已接受生成 |
| 保存中 | 已取得图片,Main 正在校验并保存 |
| 已完成 | 图片及会话引用已保存,可重新读取 |
| 取消中 | 已请求终止,尚无明确结果 |
| 已停止等待/已取消 | 本地中断只说明“已停止等待,上游可能继续生成”;上游确认停止才说明“生成已取消” |
| 失败 | 有明确的请求或保存失败原因,保留输入和错误上下文 |
| 结果未确认 | 响应丢失、超时或重启后无法确认结果,不当作上游失败或取消 |
同一操作的通知更新同一记录。取消后仍取得图片时,在原卡片保存并说明“取消未生效,已生成”,不恢复旧工具回合。超时不等于上游取消,不承诺退款。
人工重新生成创建新操作,沿用原模型与输入并重新校验;改选模型时在新卡片显示实际选择。失败、结果未确认及保存失败均不自动重发收费请求,不设置独立保存重试入口。应用退出后不承诺继续本地执行,重启时未完成记录显示结果未确认,不建立跨重启查询恢复或通用恢复框架。
## 历史成果
L-5(FR-5、FR-8;US-07、US-12、US-14、US-15):历史上下文提供成果引用、提示词及实际图片模型信息。只有具备视觉输入能力的聊天模型才接收适用的图片内容;无视觉模型可根据用户文字将原图引用交给编辑工具,但不能声称看过图片。上传图片和历史成果均使用已有可持久化素材引用。
删除会话沿用已有确认流程,说明活动生成的取消及无法保证上游停止的情况;删除后的晚到结果不能重建会话。
## 完整性与实施验证
规则覆盖配置、新建、上传图与历史图编辑、模型与 Runtime 切换、Ask、无工具或无视觉模型、取消、失败、重启和会话归属。没有待定产品范围;各 Runtime 工具注入、远程往返及取消传播的具体接法需在实施时核验,按[技术设计](./technical-design.md)完成真实路径验收。视频、跨设备同步和应用退出后的本地执行不在范围内。
@@ -0,0 +1,37 @@
# 产品需求
状态:设计草案。本文描述目标行为,不作为现有能力说明。
## 用户问题与范围
用户希望在文字会话中直接提出“画一张海报”“把上传图片的背景改成蓝色”“继续修改刚才的图片”,由聊天助手调用已配置的图片模型。切换聊天模型或 Runtime 后,应能继续生成、引用和编辑图片。
范围包括图片模型调用开关、全局默认与本次指定、文生图、已有协议支持的上传图片和历史图片编辑、成果保存、取消及状态反馈。支持直连文本 Model Runtime、本地 OpenCode、Continue、DeepSeek Harness,以及 GoodBuddy Agent 远程路径上的适用 Runtime;各路径的生成、编辑和切换均属于验收范围。
GoodBuddy 自动管理内部工具或 MCP 注入,复用模型连接与凭据。用户无需创建 Skill、搭建 MCP server 或填写第二套凭据。自动调用要求聊天模型支持工具调用;无工具能力时明确说明限制,用户可使用已有直连图片工作流。
不包含视频生成、模型训练、自动采购服务、通用文本指令执行器、批量渲染平台或应用退出后继续本地执行的承诺。不新增图片协议、会话级生成模型选择器、独立生成表单或通用恢复框架,也不安排视频交付计划。
## 功能需求
保留 FR-1 至 FR-9 编号。原需求中的视频部分移出范围,FR-2 的会话级选择由全局默认和本次指定替代,FR-7 的新增显式入口由已有直连图片工作流替代;下表为当前定义。
| 编号 | 要求 | 产品验收 |
| --- | --- | --- |
| FR-1 | 图片模型提供“允许 AI 在会话中调用”开关;新增和已有连接默认关闭,由用户主动开启;模型配置是图片能力启用与 Runtime 分配的唯一事实来源 | 至少一个模型开启时自动勾选全部支持 Runtime;全部关闭或删除时全部取消勾选;关闭或删除其中一个后若仍有开启模型,保持勾选并更新允许调用的模型目录;被关闭模型拒绝新操作,历史成果保留 |
| FR-2 | 支持全局默认和本次明确指定;编辑可沿用原成果模型 | 按[选择规则](./logic-design.md)解析并显示实际模型;失效或有歧义时提示,不静默换服务 |
| FR-3 | 在支持工具调用的聊天模型下,以自然语言生成和编辑图片 | 上传图片、历史图片及文生图均可经真实服务处理,保存后在原会话展示,活动记录可见 |
| FR-4 | 支持聊天模型、Runtime 及生成模型切换 | 切换后可发起新的生成和编辑;已提交操作固定原模型和原会话,不重复提交 |
| FR-5 | 支持上传素材、历史成果引用与连续编辑 | 重开会话及切换 Runtime 后可引用已保存原图;缺失素材或能力不足时保留输入并提示处理方式 |
| FR-6 | 生成操作独立显示状态,支持取消及人工重新生成 | 离开会话不取消;保存成功才完成;不虚构进度,不自动重试收费请求;重启后未完成操作显示结果未确认 |
| FR-7 | 遵循工作模式及模型能力边界 | Ask 不提交生成或编辑;无工具模型说明限制,可使用已有直连图片工作流,无需新增表单 |
| FR-8 | 图片作为会话成果保存 | 可预览、导出和重新打开,来源模型可追溯;无视觉聊天模型只接收文字事实及引用,不编造观察 |
| FR-9 | 本地及 GoodBuddy Agent 远程适用 Runtime 均支持图片工具;GoodBuddy 管理的图片生成与编辑能力及 Runtime 分配持续可见 | 分别完成真实生成、编辑、切换验收;分配复选框只读,不可手动切换;无开启模型时仍显示该行、未勾选状态、自动管理说明及模型设置入口;无独立能力启停、分配、移除或凭据编辑设置,用户无需维护 Skill 或 MCP server |
分配表示模型配置允许的调用范围。聊天模型不支持工具、Ask、Runtime 离线、配置错误或执行失败均单独说明,不取消配置分配的勾选;不保存可独立修改的能力启用或 Runtime 分配状态。具体推导与刷新规则由 L-1 定义。
## 产品验收
以[用户场景](./user-stories.md)验收。各 Runtime 分别记录真实工具往返、生成、上传图编辑、历史图编辑及切换结果,不能用单一路径成功代替其他路径。配置检查或工具注入成功不等于生成成功,成果必须可在会话中重新打开。
Runtime 接入细节和真实 Host 验证属于实施工作,未验证时如实记录未完成,不据此缩减 FR-9 的支持范围。
@@ -0,0 +1,83 @@
# 进度记录
## 2026-09-15
### 真实图片请求与构建修复
已使用现有 `gpt-image2` 连接(模型 `sub-gpt-image-2`)经生产 `ImageGenerationService` 发起两次真实图片请求:生成白底蓝色圆形,再上传该成果将圆形改成红色。两次均 HTTP 200,保存并重新解码成功,导出图片目视检查符合要求。请求数为 2,自动重试为 0,用户保存的配置未修改。请求尺寸为 1024×1024,供应商实际返回两张 1254×1254 PNG;未对结果重采样。证据保存在本机临时目录 `image-bounded-oiOPRi/`。此项验证 Main 图片服务与真实供应商,尚不替代真实聊天模型经各 Runtime 自行调用工具及完整界面切换验收。
用户报告 Renderer 生产构建失败,定位到 `remote-image-tool-contracts.ts` 的 `node:crypto` 被共享契约间接引入浏览器。已将 MCP 名称哈希移至仅 Main 与 Agent 引用的 `remote-image-tool-node.ts`,共享消息编解码使用标准 `TextEncoder`/`TextDecoder`,无新增依赖或 polyfill。修复后 `npm run build`(含类型检查、Main、Preload、Renderer 构建)、`npm run lint` 通过,4 个相关测试文件共 89 项通过。此前仅运行类型检查和测试未发现这一构建问题,不能作为生产构建通过证据。
### 实施与集成复验
工作区已有模型会话调用开关、默认图片模型、配置派生的四种 Runtime 只读分配、Main 图片服务、上传与历史成果引用、操作状态及取消/重新生成 IPC。本地 Model、OpenCode、Continue、DeepSeek Harness 和受管远程 OpenCode 均已接入同一服务。操作复用消息 metadata 和成果存储,未新增任务表或独立分配设置。
本轮修复远程保留会话在图片目录清空后未刷新 MCP,以及 Continue 准备自定义 MCP 失败时未释放本次图片工具令牌的问题。图片卡片更新保持原顺序;取消提示区分请求取消、停止等待和取消未生效后的结果保存。失败卡片提供模型设置与素材重选入口,无工具能力及 Ask 有相应提示。长名称和提示词自动换行,窄窗口及 200% 缩放下恢复按钮可换行。
验证证据如下;此前小节中的超时和未验证记录仅代表各次检查时点:
| 验证 | 结果与边界 |
| --- | --- |
| `npm test` | 364 个文件通过、9 个跳过;4,267 项通过、67 项跳过。后续恢复入口及样式修改另有 269 项定向测试通过,未再运行全量测试 |
| `npm run typecheck`、`npm run lint` | 最后一次 Renderer 修改后通过 |
| 真实 Linux Host | 最终 Agent 构建通过固定主机身份连接,使用实际远程 OpenCode;Ask、Execute 各 1 次真实文本请求均 HTTP 200 并完成,真实图片请求 0 次 |
| Host 图片工具往返 | 最终运行含 7 次受控图片请求、15 次受控文本请求;生成、上传和历史图编辑、目录刷新、Ask 隐藏、并发会话归属、停止回复和断连后 Main 继续保存均通过。图片服务返回固定内联 PNG,不构成真实生图证明 |
| Electron 界面 | 当前生产组件、样式、字体及双语文案,使用隔离 fixture API;初轮 94 项检查,恢复入口完成后 40 项检查通过。覆盖明暗主题、四种窗口尺寸、键盘导航及 200% 缩放;没有修改用户配置 |
最终 Host 运行退出码为 0,残留自有进程为 0,隔离目录和上传文件已清理。此前受控复验另有 7 次图片请求和 15 次文本请求,因此本轮合计真实文本请求 2 次、真实图片请求 0 次、受控图片请求 14 次、受控文本请求 30 次。此前历史测试记录不计入此合计。
本机临时证据位于 `C:/Users/jiang/AppData/Local/Temp/opencode/`:`image-review-full-tests-20260915.stdout.log`、`image-host-final-real-20260915.stdout.log`、`image-host-review-20260915.stdout.log` 及 `image-visual-0915/`。这些是开发检查产物,不随产品发布。
当时尚未完成真实图片供应商生成与编辑;后续验证见上方记录。完整 Electron Main/Preload 路径中的自然语言触发及本地/远程双向切换验收仍待完成,FR-3、FR-9 尚未完成全部产品验收。
隔离的真实图片验证脚本离线校验通过后,已按用户要求执行上述两次真实请求。该连接的会话调用仅在验证内存中启用,不修改保存配置。此前 `deai-writing` 文档扫描阻断项为 0,25 个候选经语境复核保留产品边界、状态限制和历史证据;`git diff --check` 通过。
### Renderer 与设置复查
本轮在已有未提交实现上修复图片操作卡片:状态事件原位更新,保留同一消息中多次生成的卡片顺序;取消提示区分正在请求取消、已停止等待和取消未生效但图片已保存。按钮通过 `aria-describedby` 关联就地操作错误,中英文提示同步更新。补充组件回归,并在现有 App 测试中检查离开原会话、结果晚到、返回及切换 Runtime 后继续修改时的卡片顺序。
已检查模型设置、RuntimeSettingsStore、CapabilityService 和共享设置契约。现有实现从已保存模型开关派生四种 Runtime 的只读分配,不持久化独立分配;关闭或删除默认图片模型后保留失效默认。相关测试本轮通过,未改动这些生产契约。共享工具目录测试原先仅排除浏览器工具,现补充图片受管能力例外,确认 `generate_image` 保持写操作且不重复进入直连模型设置分组。
验证记录:
- `npx vitest run src/renderer/src/ImageOperationStatus.test.tsx src/renderer/src/App.test.tsx src/renderer/src/SettingsPanel.test.tsx src/main/runtime-settings-store.test.ts src/main/capabilities/capability-service.test.ts`:5 个文件、451 项通过。
- 修正目录测试后,`npx vitest run src/shared/builtin-model-tools.test.ts src/renderer/src/ImageOperationStatus.test.tsx`:2 个文件、5 项通过。
- `npm run typecheck`、`npm run lint` 通过。
- `npm test` 在 120 秒上限被终止;期间报告的共享工具目录测试失败已单独修正并复验,未取得全量测试通过结论。
- 未调用真实文本或图片服务,请求数均为 0;未执行 Electron 视觉验收或真实 Host 验证。本轮没有修改 `src/main/agent` 或 `src/agent-daemon`。各 Runtime 的真实生成、编辑、取消及切换验收仍待完成。
### 此前文档修订记录
本日补充:已按最新明确接受的要求修订 FR-1、FR-9、L-1、界面与技术设计,并新增 US-16 至 US-19。只读分配及模型配置派生规则见[行为规则](./logic-design.md),受管行、说明与无障碍要求见[界面设计](./ui-design.md)。已读取根目录与 features 的 AGENTS、根 UI-DESIGN.md 及本目录七份现有文档,保留此前未提交修订;本次未修改目录外文件或应用代码,相关行为仍待实现与产品验收。
本日更正:此前将其他 Runtime 自动调用排除在交付范围之外的记录不符合用户要求,现由本条替代。七份文档已统一为图片生成和编辑,支持范围及验收以[产品需求](./prd.md)和[用户场景](./user-stories.md)为准,不再保留分期规则。视频移出范围,US-10 保留编号并明确标注;其他被替代要求已记录编号变更。
本次只修改本目录设计文档,未检查或改变应用实现。2026-09-14 记录仅为当日历史证据,不表示已重新验证。图片工具、Main 请求生命周期、各本地及 GoodBuddy Agent 远程适用 Runtime 的生成、编辑和切换均仍需实施与实际验证;具体注入及桥接核验属于实施工作,不缩减支持范围。
本次文档验证:7 份文档的 38 个相对 Markdown 链接均可解析;一致性审阅覆盖模型配置唯一来源、只读分配、空目录保留行、运行状态独立及完整 Runtime 范围,未发现分期或缩减支持范围的现行规则。`deai-writing` 扫描阻断项为 0,25 个复核候选涉及设计状态、职责与必要行为限制,逐条按语境保留,并通读检查结构及前后规则。`git diff --check -- docs/features/conversation-media-generation` 通过;本次七份文件均已被 Git 跟踪,检查覆盖其工作区差异。该命令不覆盖未跟踪文件,不能沿用此前“目录未跟踪”的结论。未运行应用测试或真实生成请求,未声称功能完成或重新验证此前代码。
## 2026-09-14(历史记录)
以下保留当日检查与原设计记录,其中视频、保存重试等旧范围已被 2026-09-15 更正取代,不作为当前实施要求。
当前阶段:设计草案已落盘,功能未实现。本次只新增设计文档与相关入口链接。
代码与文档检查确认以下基础能力:
| 事实 | 证据 |
| --- | --- |
| 已有直连图片生成、编辑及历史成果引用 | [图片技术设计](../image-generation/technical-design.md)、`src/main/agent/model-runtime.ts` |
| 模型连接及 Main 凭据存储已存在 | `src/shared/contracts.ts`、`src/main/runtime-settings-store.ts` |
| 已有 Skill 发现、Runtime 分配及工具分发 | `src/main/capabilities/capability-service.ts`、`src/main/agent/model-tool-provider.ts` |
| 会话、消息 metadata 和成果已有 SQLite 存储 | `src/main/assistant/assistant-database.ts`、`src/shared/assistant-contracts.ts` |
| 生图尚未成为普通 LLM 的内置生成工具 | 检查 `src/shared/builtin-model-tools.ts` 及工具分发路径 |
| 本次检查未发现第一方视频生成协议实现 | 检查模型能力契约、Runtime 及现有功能文档;不据此推断外部 MCP 的能力 |
剩余工作对应[技术设计实施顺序](./technical-design.md):模型调用设置与选择、图片服务及会话入口、各 Runtime 接入、视频真实服务与成果交付均待实施。视频协议选择和远程工具桥接验证待完成。
本次未修改应用代码,未运行应用测试或真实生成请求。文档验证记录如下:
- 相对 Markdown 链接检查:7 份新文档及图片功能入口共 33 个链接,失效链接为 0。
- `deai-writing` 扫描:阻断项为 0;人工复核保留设计状态、能力边界和禁止行为等必要限定,以及真实并列的验收项。
- 独立一致性审阅发现“重新生成”与“重试保存”的操作身份表述不一致,已在 L-4 和 US-09 中统一:前者创建新操作,后者更新原操作。
- `git diff --check` 通过。上述检查只验证文档,不构成功能验收。
@@ -0,0 +1,86 @@
# 技术设计
状态:工作区实现已接通,真实图片服务与完整产品验收未完成,证据见[进度记录](./progress.md)。内部契约实现 [FR-1 至 FR-9](./prd.md)及[行为规则 L-1 至 L-5](./logic-design.md),不作为外部公开 API。
## 调用结构
```mermaid
flowchart TD
Direct[直连文本 Model Runtime] --> Tool[generate_image]
Local[本地 OpenCode / Continue / DeepSeek Harness] --> Injection[GoodBuddy 管理的内部 MCP 注入]
Remote[GoodBuddy Agent 远程适用 Runtime] --> Bridge[现有 Agent 通信与工具桥接]
Bridge --> Injection
Injection --> Tool
Tool --> Main[Main 图片服务]
Main --> Profile[现有模型连接与凭据]
Main --> Adapter[现有图片生成与编辑协议]
Main --> Artifact[原会话操作记录与图片成果]
```
Main 持有图片请求、取消句柄及结果保存职责,生命周期独立于聊天 Runtime controller。切换或释放聊天进程不释放已提交图片操作。Renderer 的取消等操作经明确 preload/IPC 方法进入 Main,沿用共享契约和发送方校验。凭据只在 Main 解密,不写入工具说明、参数、输出或远程 Runtime 配置。
`ImageGenerationService` 复用 `ModelAgentRuntime.generateImage` 的图片请求和校验,在 Main 保存操作及成果。工具路径按 L-2 明确区分新建和编辑,不自动将缺少素材或不支持的编辑降为文生图;已有直连图片路径保持原上下文规则。
## 工具与注入
稳定图片工具 `generate_image` 同时负责生成和编辑。当前输入为 `prompt`、可选 `modelProfileId`、`intent`(`create` 或 `edit`)、`sourceArtifactIds` 和可选 `quality`;类型及编辑素材约束由 `src/shared/image-generation-contracts.ts` 校验。每次请求沿用已有协议的单图生成和尺寸行为,没有新增数量或尺寸参数。原会话、触发消息和工具调用关联由运行上下文绑定,LLM 不能自由指定结果写入位置或任意文件路径。
每个新回合提供可调用图片模型的脱敏 ID、名称及生成、编辑能力,模型切换、配置变化或进程复用时刷新。Main 提交前重新校验工作模式、连接开关、所需能力及素材。优先由工具描述承载模型选择、图片引用和结果使用说明,不把 Skill 管理作为依赖。
直连文本 Model Runtime 使用内置工具契约及分发;外部 Runtime 复用内部 MCP 注入,由 GoodBuddy 自动注册、配置和管理生命周期,远程路径经现有 Agent 工具桥接调用同一服务。内置 MCP 列表中的图片行只展示这项受管能力,不代表直连 Model 也通过 MCP 调用。无需用户创建 Skill、另建 MCP server 或复制凭据,不按模型连接复制工具或 Skill。
Main 从已保存图片 profile 的会话调用开关推导允许模型目录,并以目录是否非空推导全部支持 Runtime 的分配。Renderer 只读取派生分配;该受管行不接入通用 MCP 的启停、分配、删除或凭据编辑写入接口。模型配置保存后刷新展示及注入目录,进程复用和下一回合重新读取;运行中的回合及已提交操作继续遵循 L-1、L-3。配置错误、Runtime 在线状态、聊天模型工具能力与执行结果独立于派生分配,不因这些状态移除允许目录中的模型或取消勾选。
不预建能力发现、查询或取消工具目录。只有实际 Runtime 协议要求独立发现、查询或取消交互,且现有工具描述、响应及 UI 操作不能满足时,才增加所需的最小适配,并记录协议依据;这些适配仍调用同一 Main 图片服务,不形成新的媒体平台。取消默认从会话卡片进入 Main。
工具等待遵循现有超时约束,返回操作 ID、实际模型、真实状态及完成后的成果 ID,不把 Base64 塞入工具历史。原回合仍有效时通过合法工具响应返回结果;回合结束或 Runtime 切换后,Main 继续写原会话,下一回合读取操作摘要及成果引用,不补发旧响应、不自动唤起 LLM 或重发图片请求。
## 配置与持久化
| 对象 | 最小数据与职责 |
| --- | --- |
| 图片模型连接 | 复用 settings/profile,增加会话调用开关;现有连接缺少开关时解释为关闭 |
| 图片能力与 Runtime 分配 | 从图片模型配置派生,只读展示;不新增独立可变的 capability enabled、assignment 设置或持久化副本 |
| 全局偏好 | 默认图片 profile ID,复用应用设置;本次指定只属于请求,不新增会话级选择 |
| 生成操作 | 优先使用现有消息 metadata,保存操作 ID、原会话/消息、工具关联、状态、实际模型 ID 与名称、提示词、有效参数、素材引用、时间、错误及成果 ID |
| 素材与成果 | 复用附件和 artifact 持久化,保存上传图片、生成图片及编辑来源关系,不能只存于 Runtime 内存 |
活动请求在 Main 内存持有提交时连接参数,持久记录不复制凭据。应用重启后未完成操作显示结果未确认,不建立跨重启查询恢复、凭据历史、通用恢复日志或自动重试。取消和晚到结果按 L-4 更新同一记录;人工重新生成使用新操作 ID。
不新增独立任务表、视频字段或通用调度器,操作不自动成为 Task/Job。若实施涉及已发布数据,按[迁移规范](../../development/database-migrations.md)处理实际兼容需求,不预建迁移或恢复机制。
Main 仅在图片和会话引用持久化成功后发布完成事件。素材必须是当前会话可访问的附件或成果,沿用已有项目和会话边界;删除会话后的晚到结果不重建会话。
会话刷新共用保留对象引用的图片状态合并:无图片或图片状态未变化时,不重建消息并触发
整段历史重新保存。未打开历史的读取与缓存释放遵循
[会话列表读取与前端保留](../assistant-workbar/execution-history-storage.md#会话列表读取与前端保留),
不改变图片操作的 Main 所有权、成果关联或时间优先级。
## 图片协议
沿用[图片技术设计](../image-generation/technical-design.md)的已有生成、编辑协议及素材限制。接收并校验有界内联图片,不下载 Provider 返回的图片 URL。沿用现有成果存储限制,实施时核对编码开销与超限反馈,不以配置检查成功代替真实生成验收。视频协议、下载、播放及交付计划均不属于本设计。
## Runtime 接入
| 路径 | 设计接入点 | 必须完成的验证 |
| --- | --- | --- |
| 直连文本 Model Runtime | 现有内置工具契约及工具分发转入 Main 图片服务 | 自然语言生成、上传图编辑、历史图编辑及切换 |
| 本地 OpenCode | 现有内部 MCP 注入转入同一服务 | 实际工具往返、能力刷新、生成编辑及切换 |
| 本地 Continue | 现有内部 MCP 注入转入同一服务 | 实际工具往返、能力刷新、生成编辑及切换 |
| 本地 DeepSeek Harness | 现有内部 MCP 注入转入同一服务 | 实际工具往返、能力刷新、生成编辑及切换 |
| GoodBuddy Agent 远程适用 Runtime | 复用桌面与 Agent 通信及工具桥接,将请求绑定原会话,由 Main 调用图片模型 | 逐一验证实际支持的远程 Runtime,覆盖真实 Host 工具往返、生成编辑、本地与远程双向切换、断连及成果归属 |
| 无工具能力的聊天模型 | 说明自动调用限制,可使用已有直连图片工作流 | 不解析普通文字伪装工具调用,不新增表单;Ask 不提交 |
本地 OpenCode、Continue 和 DeepSeek Harness 经内部 MCP gateway 注入;直连 Model 使用内置工具分发。远程通过 `main-image-tool-session.ts`、Agent 协议和 `image-tool-mcp.ts` 回到同一 Main 服务。现有 GoodBuddy Agent 的受管远程 Runtime 为 OpenCode,项目校验和 Runtime 创建均沿用此范围;共享契约包含其他 Runtime 不表示存在其远程执行路径。保留会话在目录清空时也刷新 MCP,Continue 准备失败时释放本次分配的令牌。工具发现成功或 Skill 注入成功不能代替真实生成与编辑。
Main 已接收的请求在桌面仍运行时独立于远程连接继续;未确认是否接收的请求不自动重放。聊天回合停止等待与用户取消图片是不同动作,须验证现有取消传播不会误杀 Main 图片请求。
## 实施与验证
实施工作包括配置与选择、Main 图片服务提取、各本地及远程 Runtime 工具适配、成果与状态 UI。它们共同完成本图片功能,不按 Runtime 划分交付范围。
针对 L-1 至 L-5 验证选择优先级、失效配置、上传与历史素材、无视觉模型、Ask、会话归属、重复通知、取消及晚到结果。检查切换聊天模型、图片默认模型、本地与远程 Runtime 后的新生成和编辑,以及旧操作不重复提交。回归已有直连图片工作流。
按 US-16 至 US-19 验证从零到一个开启模型、多个中关闭或删除一个、最后一个关闭或删除、重新开启,以及配置错误和 Runtime 离线时的派生分配、目录刷新、行可见性与只读交互。重开设置和应用后均从模型配置推导,不恢复独立分配值;辅助技术可读取标签、勾选状态和说明,键盘可到达模型设置导航。
源代码实现后运行 `npm test`、`npm run typecheck`、`npm run lint`,并逐路径执行[用户场景](./user-stories.md)的真实产品验收,记录模型、Runtime、请求数量和结果。涉及 Agent 的实现按[远程主机验证要求](../remote-host/technical-design.md)在真实测试 Host 验证。实施和集成验证状态由[进度记录](./progress.md)维护。
@@ -0,0 +1,41 @@
# 界面设计
状态:工作区已有实现及 Electron 组件验收,完整应用验收见[进度记录](./progress.md)。遵循[统一界面设计](../../../UI-DESIGN.md),实现 [FR-1 至 FR-9](./prd.md),行为以[行为规则](./logic-design.md)为准。
## 模型配置
在现有图片模型连接表单提供“允许 AI 在会话中调用”。说明文字:“开启后,支持工具调用的聊天助手可使用此模型生成和编辑图片,编辑能力取决于图片协议。结果保存在发起请求的会话中。”复用共享 Switch,提供 `role="switch"`、键盘操作及可见焦点。
开启后提供“设为默认生图模型”,列表显示可调用和默认标识。关闭或删除默认连接时提示默认选择不可用,保留失效选择供改选,不自动指定其他模型。用户无需创建 Skill、搭建 MCP server 或重复填写 API 凭据;内部注入由 GoodBuddy 管理,不作为配置步骤展示。
## 能力与 Runtime 分配
复用设置中心“MCP / 内置 MCP”的列表与分配布局,增加“图片生成与编辑”行,标记“内置 · 自动管理”。这是 GoodBuddy 管理能力的展示入口,直连 Model 通过内部工具调用,外部 Runtime 通过内部 MCP 接入;不显示为用户另行添加的 MCP 或 Skill,也不混入 Runtime 原生能力清单。
该行按 L-1 展示直连文本 Model Runtime、本地 OpenCode、Continue、DeepSeek Harness 及 GoodBuddy Agent 远程适用 Runtime 的只读分配复选框。此图片能力采用用户已接受的自动管理规则,不沿用其他内置 MCP 的手动启停、可调分配或 DeepSeek Harness“暂不支持”状态。没有开启模型时仍保留整行及全部未勾选项,不能因目录为空隐藏。
持续显示说明“由图片模型的「允许 AI 在会话中调用」设置自动管理。”,并提供“前往图片模型设置”导航。该行不提供独立启用开关、保存分配、移除、编辑连接或凭据入口。运行就绪、无工具能力、配置或调用错误单独显示,不改变分配勾选。
分配复用共享 Checkbox 的方形视觉和原生语义,以 `checked` 表示派生值并使用 `disabled` 禁止鼠标、标签点击或键盘切换;仅在共享控件已有受支持的只读实现时采用该实现,不给原生 Checkbox 添加无效的 `readonly` 或自行添加 `aria-readonly`。每项保留 Runtime 标签和可访问名称,以 `aria-describedby` 关联自动管理说明。说明放在禁用控件之外,保持可读及辅助技术可访问;导航保持正常 Tab 顺序和可见焦点。窄窗口下分配项换行,保留标签、勾选状态、说明和导航。
## 会话入口
在现有聊天输入区以自然语言发起图片生成或编辑,本次指定模型也通过请求表达。复用上传附件和历史成果引用入口,输入区显示素材缩略图或文件名并允许移除。不新增会话级图片模型选择器、独立生成表单或视频入口。
未设默认时,助手提示本次指定可用模型,或到设置选择默认后重新发送;名称歧义先澄清。没有图片连接时提供现有模型设置入口。模型不支持编辑或素材缺失时保留输入,提示更换模型或重新选择素材。
直连文本、本地各 Runtime 和 GoodBuddy Agent 远程适用 Runtime 使用一致的图片操作与成果反馈。切换后可继续生成及编辑历史图;工具调用异常时显示具体原因,不宣称已提交。接入未完成的开发状态记录在进度文档,不能作为产品支持范围。
不支持工具调用时提示“当前聊天模型无法自动调用图片工具,可使用已有直连图片工作流”。Ask 下提示“生成或编辑图片需要 Execute 模式”,由用户切换,不自动切换。无视觉能力时说明无法直接查看图片,不妨碍按用户文字调用编辑工具。
## 图片卡片
复用现有图片成果展示,卡片就近显示实际模型、操作状态、成果或错误。提示词、适用参数和来源素材放在详情中;不显示密钥、认证头或供应商私有地址。活动记录与卡片使用同一操作标识。
完成后可预览、下载和“继续修改”。继续修改将成果加入输入区,用户填写修改要求;切换 Runtime 或重开会话后仍可使用。离开会话不取消操作,结果只出现在原会话。
进行中提供“取消生成”,与“停止回复”分开。取消、晚到结果和结果未确认的文案遵循 L-4。失败提供适用的“重新生成”“重新选择模型”或“重新选择素材”;重新生成由用户明确触发,并说明会发起新的模型请求。没有独立“重试保存”按钮。
“重新选择模型”进入已有模型连接设置;改变模型后,用户在新请求中明确指定。“重新选择素材”聚焦输入区并打开已有附件选择器,保留草稿和已有附件;空草稿填入原提示词及模型名称。历史成果仍使用“继续修改”加入引用并聚焦输入区。上述操作不自动发送请求,“重新生成”继续使用原操作的模型和输入。
卡片错误不重复弹全局通知;模型设置保存等非上下文反馈使用应用通知。状态使用文字,不能只靠颜色;状态变化通过适度的 `aria-live` 提示,避免逐秒播报等待时间。窄窗口操作可换行,图片不超过消息区宽度,模型长名称截断时保留完整可访问名称。复用的菜单关闭后焦点回到触发控件。
@@ -0,0 +1,34 @@
# 用户场景
状态:设计草案。Given、When、Then 分别为前提、操作和预期结果。范围以[产品需求](./prd.md)为准,行为以[行为规则](./logic-design.md)为准。
## 场景验收
适用的自动调用场景须在直连文本 Model Runtime、本地 OpenCode、Continue、DeepSeek Harness 和 GoodBuddy Agent 远程适用 Runtime 分别验证,前提为 Execute 且聊天模型支持工具调用。切换验收同时检查原操作归属和目标 Runtime 的新生成、编辑能力。
| 编号 | 需求 | Given | When | Then |
| --- | --- | --- | --- | --- |
| US-01 | FR-1、FR-3、FR-9 | 已添加图片模型、开启会话调用并设为默认 | 在文字会话中要求画海报 | 自动调用并显示活动,原会话出现图片及实际模型;无需创建 Skill、MCP server 或重复凭据 |
| US-02 | FR-2 | 有两个可调用图片模型,未选默认 | 请求生成图片 | 提示本次指定模型或到设置选择默认后重新发送;名称歧义先澄清,不按列表顺序提交 |
| US-03 | FR-4、FR-5 | 聊天模型 A 已调用图片模型生成图片 | 切换聊天模型 B,要求修改该图 | B 获得成果引用和可用工具,编辑模型按规则选择 |
| US-04 | FR-4、FR-6、FR-9 | 生成操作已提交 | 切换 Runtime 并打开其他会话,再请求生成或编辑 | 原操作继续且只写回原会话;目标 Runtime 可提交新操作,不重放旧调用 |
| US-05 | FR-2、FR-4 | 当前操作使用图片模型 X | 将全局默认改为 Y,再请求新建图片 | 旧操作仍用 X,新操作用 Y;本次明确指定可覆盖默认 |
| US-06 | FR-1、FR-2、FR-4 | 默认或本次指定模型为 X | X 被关闭或删除后请求生成 | 显示 X 不可用并提示改选,历史图仍可打开,不静默换模型 |
| US-07 | FR-5、FR-8 | 已保存图片并重新打开会话或切换 Runtime | 引用原图,指定另一个支持编辑的模型 | 新模型接收原图;素材缺失时要求重新选择,不能假装已编辑 |
| US-08 | FR-7 | 当前聊天模型不支持工具调用,或处于 Ask | 请求生成或编辑 | 无工具能力时说明可使用已有直连图片工作流;Ask 下不提交并提示需要 Execute,不自动切换模式 |
| US-09 | FR-6 | 操作正在生成或保存,或已经失败 | 取消,或明确要求重新生成 | 如实显示取消结果;人工重新生成创建新操作,不自动重复收费请求,不提供独立保存重试入口 |
| US-11 | FR-4、FR-9 | 使用 GoodBuddy Agent 远程适用 Runtime | 生成及编辑图片,切换本地后继续编辑,再切回远程生成 | 工具往返可用,成果可继续引用;活动归原会话,凭据留在 Main;远程断连不取消 Main 已接收的操作 |
| US-12 | FR-5、FR-7、FR-8 | 聊天模型无视觉能力但支持工具 | 请求分析图片或根据文字修改图片 | 说明无法直接查看图片;可传原图给编辑工具,不编造视觉观察 |
| US-13 | FR-6 | 图片操作尚未取得可保存结果 | 退出并重开应用 | 未完成记录显示结果未确认,不承诺后台继续,不自动查询恢复或重新提交 |
| US-14 | FR-5 | 会话有多张图片 | 要求“修改第二张”或“新画一张无关图片” | 前者引用指定成果,后者不附带旧图;指代不清先澄清 |
| US-15 | FR-3、FR-5、FR-9 | 上传图片并选择支持编辑的图片模型 | 要求修改背景,再切换 Runtime 继续修改成果 | 上传原图及后续成果被正确引用,编辑结果保存在各自发起会话;不降为纯文生图 |
| US-16 | FR-1、FR-9 | 尚无开启会话调用的图片模型 | 查看内置 MCP 列表,再开启一个图片模型并保存 | 图片生成与编辑行一直可见且标记“内置 · 自动管理”;全部支持 Runtime 从未勾选变为勾选,目录包含该模型;重开设置或应用仍按模型配置显示 |
| US-17 | FR-1、FR-9 | 至少两个图片模型开启会话调用 | 关闭或删除其中一个并保存 | 全部支持 Runtime 保持勾选,允许目录移除该模型、保留其他开启模型;新操作不能调用已关闭或删除模型 |
| US-18 | FR-1、FR-9 | 仅剩一个开启会话调用的图片模型 | 关闭或删除该模型,再重新开启或添加并开启一个模型 | 目录为空时全部取消勾选,整行和说明、模型设置导航仍可见;再次开启后自动恢复全部勾选,无需另行分配 |
| US-19 | FR-1、FR-7、FR-9 | 至少一个图片模型开启会话调用 | 尝试用鼠标或键盘切换分配,或切换到无工具聊天模型、Ask、离线 Runtime,或遇到配置或执行错误 | 分配不可手动修改,运行限制与错误不取消勾选;无独立启用、分配保存、移除或凭据编辑操作;自动管理说明持续可读,辅助技术可识别勾选和禁用状态,键盘可通过“前往图片模型设置”导航修改源配置 |
## 编号变更
US-10(图生视频)移出本图片设计范围,编号保留,不安排实施。US-02、US-05、US-06 的会话选择预期由全局默认和本次指定替代;US-08 使用已有直连图片工作流;US-09 取消独立保存重试要求;US-13 的视频查询恢复由图片中断状态说明替代。其余编号保持,US-15 补充上传图片编辑的显式验收。
US-16 至 US-19 补充自动管理分配验收,设置展示不依赖 Execute 或当前聊天模型支持工具;自动生成与编辑仍遵循本节的调用前提。
+6
View File
@@ -8,5 +8,11 @@
| 文档 | 权威职责 |
| --- | --- |
| [技术设计](./technical-design.md) | Runtime 产品与技术方案、协议、插件、打包、测试和验收 |
| [实现进度](./progress.md) | Windows 工作区路径修复及验证记录 |
现有文档同时包含部分 PRD、功能逻辑和进度内容,后续修改该 Runtime 时再按标准职责拆分。
跨项目共享 Host 和状态检查不冷启动的实现设计,由
[Runtime 进程复用技术设计](../assistant-workbar/runtime-process-reuse-technical-design.md)
统一维护;本 Runtime 的生命周期见[技术设计 §9](./technical-design.md#9-runtime-生命周期),
未发布实现的验收状态见[工作栏进度](../assistant-workbar/progress.md#2026-09-14-runtime-进程复用实施中)。
@@ -0,0 +1,45 @@
# DeepSeek Harness 实现进度
## 2026-09-14 Windows 工作区路径修复
本机 Runtime 的 Host 启动与 `newSession.cwd` 已共用初始化时解析的 `realpath`,
规则见[技术设计](./technical-design.md#52-受控组合不启动用户-profile)。修复此前
`C:/...\workspace` 写法触发 ACP `-32602`,以及随后换成同目录规范写法仍复用
错误 Runtime 的问题。Host 严格比较与 execution-space 缓存规则保持原样。
新增 Windows 真目录回归,连接真实 ACP、Host 和控制面,分别用混合分隔符及规范
路径创建会话,断言共用同一 Runtime、只启动一次 Host、两次均完成。该自动化回归
使用内存模型;外部真实模型验证单独由完整 App 完成。
Windows 完整 App 使用当前源码构建的 Main、Preload、Renderer 和新建隔离配置。
正式配置只读,模型凭据由隔离配置中的 safeStorage 解密;真实 composer 输入请求,
由 `deepseek-v4-flash` 在 Ask 模式读取 `summary-probe.txt`,校验码未放入 prompt。
| 目录写法 | Composer 提交 | 真实模型 HTTP dispatch | 文件读取 | 结果 |
| --- | ---: | ---: | ---: | --- |
| Windows 混合分隔符 | 1 | 2 | 1 | 仅返回文件中校验码,任务完成 |
| 同目录规范路径 | 1 | 2 | 1 | 仅返回文件中校验码,任务完成 |
| 合计 | 2 | 4 | 2 | 两条会话均通过 |
两轮 dispatch 来自同一 DS Utility Host 进程,切换目录写法未新增 Host;ACP 会话
ID 不同。两条工具卡片均显示路径摘要,原生点击展开后可查看输入与输出;重载
Renderer 并重新选择项目后,已保存会话的摘要、详情和完成状态仍通过检查。四张
局部截图已逐张打开审阅。驱动曾返回不可克隆的监听器函数,以及在重载后默认项目
等待工具卡片超时;修正定位后通过,这两次驱动错误没有增加模型请求。
本机临时 `ds-windows-path-live/report.json`、`dispatch.jsonl` 与 `source.json`
记录结果、逐请求计数和构建哈希,原 `runtime-summary-live` 报告未覆盖。
本次 App 正常退出,退出前记录的 5 个所属进程均已停止;隔离 profile、数据库、
工作区、临时构建和私有日志已清理。正式模型配置与启动前逐字节一致,报告和截图
保留在本机临时目录,不作为长期可用的仓库资源。
测试沿用生产 HTTPS 与证书处理,未单独验证严格证书校验或 Main 重启。
专项 7 个测试文件通过,91 项通过、3 项跳过。`npm run typecheck` 与
`npm run lint` 通过。最初两个假时钟测试因异步目录解析尚未结束就推进启动时钟而
超时;调整为等待 launcher 启动后,DS Runtime 的 34 项测试全部通过。
最终全量 `vitest run`(`npm test` 的脚本入口)为 353 个文件通过、9 个跳过,
4155 项通过、66 项跳过、0 失败,耗时 602.00 秒。`git diff --check` 通过。
远端核查:Main 的 SSH 分支创建托管远程 Runtime,最终使用 `AcpRemoteRuntime`;
`create-runtime.ts` 的本机 DS 入口拒绝 SSH execution space。本次未改远端代码,
未调用远端模型,也未进行 Linux Host 实测。
@@ -121,6 +121,11 @@ GoodBuddy 使用自己固定的 Harness Host 入口和只读组合模板,不
模型名称、服务地址、工作区、Skills、MCP schema 和已启用插件的规范化入口通过严格校验的 Main 配置传给 Host。API Key 只通过受控凭据通道按需提供,不写入 YAML、命令行、Renderer 或日志。插件配置只来自 GoodBuddy 受管状态,不合并用户 profile 或全局补丁。
Main 在创建 Session 前解析该请求工作区的 `realpath`,把绝对路径传给 ACP
`newSession.cwd`。Host 不再要求 Session cwd 等于启动 cwd,原生 Agent header.cwd
驱动文件与 Shell 工具;启动 cwd 仅为默认值。远端 SSH 由 `ManagedRemoteAcpRuntime` /
`AcpRemoteRuntime` 单独处理,不经过本机 DS Runtime 的目录解析。
### 5.3 双层内部控制面
Harness 子进程内控制面不能取代 Main 控制面,Main 控制面也不能代替进程内的 Session/Tool 适配层:
@@ -136,7 +141,7 @@ Harness 子进程内控制面不能取代 Main 控制面,Main 控制面也不
- GoodBuddy 已经持久化对话、消息、活动、工具事件和用量。
- 再写一份 Harness 日志会扩大敏感数据副本和清理范围。
- GoodBuddy 在 Runtime 重启后可以用现有的有界历史创建新 Harness Session。
- GoodBuddy 在 Runtime 重启后可以用现有会话历史创建新 Harness Session。
Harness Session 只在当前 Runtime 进程生命周期内存在。释放 GoodBuddy 会话时必须同步释放对应 Harness Agent。
固定 Host 显式加载 `dsh-session-projection` 的内存 SessionProjectionRegistry,这是
@@ -307,6 +312,7 @@ Harness Control Plane 只发送 GoodBuddy 能稳定解释的字段:
- `text`:已提交的助手文本分片。
- `reasoning`:可选的有界推理摘要分片。
- `tool`:工具 ID、名称、状态和有界输入/输出摘要。
- 操作标题与增量完成事件的参数保留遵循[工具操作摘要规则](../assistant-workbar/runtime-interactions.md#工具操作摘要),本机适配器按调用 ID 合并,详情上限保持不变。
- `model-usage`:模型、提供方、输入、输出和缓存 Token。
- `done`:停止原因和 Session ID。
@@ -316,30 +322,31 @@ Harness Control Plane 只发送 GoodBuddy 能稳定解释的字段:
### 9.1 进程模型
- 每个活动的 DeepSeek Harness Runtime 实例拥有一个 `utilityProcess`。
- 一个进程可以承载多个 Harness Session。
- Main `LocalRuntimeRegistry` 按兼容模型及 Host 配置共享重型实例;
每组拥有一个 `utilityProcess`,不同工作区使用轻量适配器及独立 Session cwd。
- 同一 GoodBuddy 对话的 Prompt 串行执行。
- 不同对话可以并行,但受全局并发上限控制。
- Runtime 设置变化时创建新实例,旧实例等待在途请求结束或在宽限期后被取消。
- Runtime 设置变化时旧配置停止接收新请求,在途请求结算后释放旧实例。
### 9.2 会话映射
Main 保存内存映射:
```text
GoodBuddy conversationId -> Harness sessionId + process generation
GoodBuddy conversationId -> Harness sessionId + workspace + process generation
```
- 首次请求创建 Session。
- 已有 Session 只发送当前 Prompt。
- 进程重启或映射失效时,创建新 Session,并只在这一次加入 GoodBuddy 提供的有界历史。
- 进程重启或映射失效时,创建新 Session,并只在这一次加入 GoodBuddy 提供的会话历史。
- 历史以明确的“不可信会话数据”结构传入,不能拼接成系统指令。
- 用户分配的 Skill 只通过 Main 校验的包路径进入 Host,并在 Agent scope 注册;不得把 Skill 内容伪装成用户 Prompt。
### 9.3 取消与有界控制操作
- 用户取消时立即发送 `session/cancel`。
- 取消等待有界,超时后关闭连接并终止整个 Harness 进程。
- 取消等待有界,超时先释放目标 Session 的 Agent、工具和 Main 代理资源;
只有该控制操作也不响应时才终止整个 Harness 进程。
- 初始化、握手、请求准备、权限回传和关闭分别使用独立控制超时。
- 生产 Prompt 不设置固定墙钟总时限,由 Harness 自身协议终态或用户取消结束。测试可以
显式注入短 Prompt 时限验证取消传播,超时错误与用户取消仍不能被宽泛 catch 抹平。
@@ -347,7 +354,10 @@ GoodBuddy conversationId -> Harness sessionId + process generation
### 9.4 释放与退出
- 原生能力清单由 `SelectedRuntimeManager` 缓存的执行 Runtime 提供,与随后的对话请求共用同一实例,不再为清单单独启动并销毁一次 Host;该实例遵循执行缓存的空闲淘汰、设置变更 reset 和退出 dispose 规则。
- 原生清单与执行使用共享 owner,查询携带工作区。轻量适配器被缓存淘汰不结束原生
Session;无会话、无请求的 owner 空闲 60 秒退出,再次使用按需启动。
- `getStatus()` 只读取已有状态和配置,不启动 Host,也不重置空闲回收计时;显式 `testConnection()` 才启动
并验证连接,临时检查适配器的 dispose 不终止其他会话。
- 删除或释放对话时调用 `goodbuddy/session/release`。
- Runtime dispose 时先拒绝新请求,再取消所有 Session。
- Harness Control Plane 完成 Agent、工具和会话清理,Host 完成 Cordis Fiber 与子进程的反向清理。
@@ -356,6 +366,17 @@ GoodBuddy conversationId -> Harness sessionId + process generation
- 应用退出时中止正在运行的 npm 插件安装并终止其完整进程树,不能让 lifecycle script 在 GoodBuddy 退出后继续运行。
- 应用退出不得因 Harness 清理无限阻塞。
### 9.5 跨项目复用
锁定 0.1.2-rc.1 的文件和 Bash/PowerShell 工具已按原生 Session header.cwd 路由,
Provider 的根 cwd 只是默认值。不为每工作区复制本地 Provider;
Main 的 Web/MCP ToolProvider 按工作区保存,最后一个对应 Session 释放时 dispose。
共享 Utility、逐 Session cwd、被动状态、Main 工具代理和取消升级统一由
[Runtime 进程复用技术设计](../assistant-workbar/runtime-process-reuse-technical-design.md#5-deepseek-harness)
定义,证据及剩余验收见[工作栏进度](../assistant-workbar/progress.md#2026-09-14-runtime-进程复用实施中)。
本节描述当前未发布源码,不将新行为归入已发布的 Desktop 0.13.2。
## 10. 权限与主机执行
### 10.1 模式映射
@@ -381,6 +402,9 @@ GoodBuddy conversationId -> Harness sessionId + process generation
- 工作区不是 containment 边界;绝对路径和命令可访问当前用户本来有权访问的主机资源。
- 已启用插件注册的工具与内置工具使用同一分发路径;GoodBuddy 不增加插件权限矩阵或逐工具确认。
- Main 代理的 MCP 工具继续执行分配、schema、活动请求、模式和 RuntimeAuthorizer 校验。
`goodbuddy/tools/list` 按 Session 保存本次发现的工具定义;`goodbuddy/tools/call` 复用该定义
校验参数与授权,不因调用健康工具而重新等待故障 MCP 的发现。发现重试留在清单刷新,
Session 释放及 Runtime dispose 清除内存定义,不持久化第二份能力状态。
- 所有工具调用仍作为活动事件记录;Ask 和 delegation 路径继续固定拒绝。
### 10.4 Runtime OS 沙箱
@@ -446,8 +470,17 @@ DeepSeek Harness 首版只使用符合下列边界的 GoodBuddy 模型连接:
任意 Body 配置入口,因此 DeepSeek Harness 不接收自定义 Body。完整支持矩阵和合并规则
见[模型连接请求定制](../model-connections/technical-design.md)。
- API Key 继续保存在 GoodBuddy 加密设置中。
- 启动环境提供的部署连接只由 Main 自动解析,不在 Renderer 中显示为可选来源。
- 未显式指定 Harness 模型且没有可用的管理员预置连接时,Main 优先使用当前默认的兼容 GoodBuddy 模型连接;默认连接不兼容时使用首个兼容连接。只有不存在任何兼容连接时才提示用户前往模型或 Runtime 设置。
- `platform` 在设置页显示为“管理员环境优先,回退 GoodBuddy 兼容连接”,不是 Harness
自有配置文件。Main 优先读取完整的 `GOODBUDDY_MODEL_BASE_URL`、
`GOODBUDDY_MODEL_NAME` 和 `GOODBUDDY_MODEL_API_KEY` 环境预置;不完整时使用
当前默认兼容 GoodBuddy 连接,再回退首个兼容连接。
- `default` 只跟随 GoodBuddy 默认兼容连接或首个兼容连接,跳过独立管理员预置;
`profile` 使用指定的兼容连接。没有兼容连接时,仍允许选择 `platform`,因为完整
管理员预置不依赖已保存连接的协议;运行时缺少实际连接才报告不可用。
- Renderer 通过 Main 的只读平台来源投影展示实际管理员模型或兼容回退,不读取环境
或密钥,也不根据 `credentialSource` 猜测。投影直接复用本节所述的生产 resolver,
字段、非持久化与草稿展示边界见
[Harness 平台来源公开投影](../model-connections/technical-design.md#9-harness-平台来源公开投影)。
不允许选择 Harness 自有的用户配置文件或自定义 Host。Runtime 始终使用随当前 GoodBuddy 版本发布的内置 Host,并通过完整内部能力握手。
@@ -477,7 +510,8 @@ DeepSeek Harness 首版只使用符合下列边界的 GoodBuddy 模型连接:
| 单图尺寸 / 单条消息解码像素 | 单边最长 8,192 px 且 1,600 万像素 / 3,200 万像素 |
| Host 临时图片存储 | 32 MiB 且最多 256 个唯一对象 |
| 单个文本或推理事件 | 64 KiB |
| 单次请求累计协议输出 | 4 MiB |
| 单次请求累计协议输出 | 无固定总量上限,继续校验单帧和待处理队列 |
| 单轮模型输出 Token | 由 Harness SDK 与 Provider 决定,GoodBuddy 不固定覆盖为 16K |
| 工具输入摘要 | 4,000 字符 |
| 工具输出摘要 | 4,000 字符 |
| 待处理事件数 | 1,000 |
+12 -4
View File
@@ -1,8 +1,9 @@
# 直连模型 Agent 能力
本目录是 GoodBuddy“直连模型 Agent 能力”的唯一文档入口。本功能为直连文本模型补齐
跨平台进程执行和编程 Subagent,使其能够在 Execute 模式完成读取、修改、运行和验证组成的
编程闭环,同时避免向已经拥有原生 Shell 或 Agent 能力的 Runtime 重复注入工具。
高效工作区搜索、分页读取、补丁编辑、跨平台进程执行和编程 Subagent,使其能够完成
“搜索、读取、修改、运行、验证”的开发闭环,同时避免向已经拥有原生 Shell 或 Agent
能力的 Runtime 重复注入工具。
## 文档导航
@@ -21,6 +22,9 @@
| --- | --- |
| 直连模型 | Runtime provider 为 `model`、由 GoodBuddy 直接调用模型协议的文本模型 |
| 进程执行 | GoodBuddy 在当前执行空间启动一个前台 Shell 命令并返回有界结果 |
| 工作区搜索 | 通过安装包内置 ripgrep 搜索内容或列出文件,并返回紧凑结果 |
| 工作区补丁 | 通过 `*** Begin Patch` 格式新增、修改或删除工作区文本文件 |
| 输出续读 | 通过 `output_read` 分页读取当前会话保留的进程或 Subagent 输出 |
| 平台 Shell | Windows 上的 PowerShell,macOS/Linux 上的 Bash 或 POSIX Sh |
| 编程 Subagent | 由直连模型临时委派、使用直连模型 Runtime 执行一个有界子任务的执行者 |
| 父请求 | 发起进程调用或 Subagent 委派的当前直连模型请求 |
@@ -32,11 +36,15 @@ Task、Job、Subjob、Run 和 Subagent 的对象关系以
## 功能边界
- `process_execute` 和 `subagent_delegate` 是 GoodBuddy 直连模型内置工具,不是外部 MCP。
- `workspace_rg`、`workspace_read_text`、`workspace_apply_patch`、`process_execute`、
`subagent_delegate` 和 `output_read` 是 GoodBuddy 直连模型内置工具,不是外部 MCP。
- 直连文本模型的普通问答、工具轮次和上下文摘要会在尚无可见输出时,对瞬时网络错误或
单次请求超时最多自动重试 3 次。
- OpenCode、Continue 和 DeepSeek Harness 继续使用各自原生执行与委派能力。
- Ask 不允许进程执行;Subagent 若在 Ask 中使用,只能继承 Ask 的只读能力。
- Ask 可搜索和分页读取工作区,但不允许补丁写入或进程执行;Subagent 若在 Ask 中使用,
只能继承 Ask 的只读能力。
- 进程和 Subagent 的长输出返回预览与续读位置,完整内容保留到所属会话或 Runtime 释放。
Ask 和 Execute 均可续读当前会话已取得的输出,详见 [输出契约](./technical-design.md#43-输出边界)。
- Execute 表示用户授权当前执行空间账号的完整能力,不增加第二套工具审批或权限档位。
- 首版跨平台指桌面本机 Windows、macOS 和 Linux。托管 SSH 项目当前继续使用远端
OpenCode;未来若向远端直连模型开放本工具,命令必须由 Host Agent 执行,绝不能回退到
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 版本 | 0.4 |
| 日期 | 2026-09-09 |
| 关联 PRD | [PRD](./prd.md) |
| 关联故事 | [User Stories](./user-stories.md) |
@@ -24,6 +24,11 @@
10. OpenCode、Continue 和 DeepSeek Harness 的原生能力不被本功能替换或包装。
11. 直连模型只在尚未显示文本或推理内容时自动重试瞬时网络错误或请求超时,最多重试
3 次;用户取消和已显示的部分输出禁止重放。
12. Ask 可调用 `workspace_rg` 和 `workspace_read_text`,但不能调用
`workspace_apply_patch`;Execute 可调用三者。
13. 工作区搜索固定使用随包 ripgrep 的结构化输出,不把任意 ripgrep 参数转成命令执行。
14. FR-7 / US-A6:预览之外的进程和 Subagent 输出保留在所属会话下,分页续读不启动新命令。
会话释放先停止活动工作,再删除输出;子会话结束不删除父会话拥有的 Subagent 结果。
## 2. 状态维度
@@ -39,17 +44,24 @@
## 3. 工具可见性决策表
| Runtime | 模式 | 执行空间 | `process_execute` | `subagent_delegate` |
| --- | --- | --- | --- | --- |
| `model` | Ask | 本机 | 隐藏 | 提供,只读继承 |
| `model` | Execute | 本机且 Shell 可用 | 提供 | 提供,Execute 继承 |
| `model` | Execute | 本机但 Shell 不可用 | 隐藏并报告诊断 | 提供,但子级同样没有进程工具 |
| `model` | Ask/Execute | SSH,远端后端未实现 | 隐藏 | 当前远端直连模型产品入口不开放 |
| 其他 Runtime | 任意 | 任意 | 隐藏 | 隐藏 |
| Runtime | 模式 | 工作区读/搜索 | 补丁 | `process_execute` | `subagent_delegate` |
| --- | --- | --- | --- | --- | --- |
| `model` | Ask | 提供 | 隐藏 | 隐藏 | 提供,只读继承 |
| `model` | Execute,本机 Shell 可用 | 提供 | 提供 | 提供 | 提供,Execute 继承 |
| `model` | Execute,本机 Shell 不可用 | 提供 | 提供 | 隐藏并报告诊断 | 提供 |
| `model` | SSH,远端后端未实现 | 当前入口不开放 | 当前入口不开放 | 隐藏 | 隐藏 |
| 其他 Runtime | 任意 | 不注入本功能工具 | 不注入 | 不注入 | 不注入 |
工具清单隐藏不是最终授权边界。`callTool` 仍须重新验证 Runtime、工作模式、执行空间和父请求
是否活动,以拒绝旧快照或伪造调用。
FR-4 / FR-12 / US-B0:Ask 工作区搜索和读取不依赖知识库、联网搜索或委派开关,不需要
切到 Execute 或额外批准。MCP 服务器失败也不改变本机只读工具的可用性。
`output_read` 在直连模型拥有进程或 Subagent 服务时提供,Ask/Execute 均可用,不取决于
本轮 Shell 可用性、是否允许再次委派或委派深度。读取只接受当前会话的句柄;其他 Runtime
不注册该工具,调用边界同样拒绝。
## 4. 进程状态机
```text
@@ -68,7 +80,7 @@ validating
规则:
- `completed_nonzero` 仍产生正常工具结果。
- 超时和取消结果包含已经取得的有界输出。
- 超时和取消结果包含已取得输出的预览;超出预览的内容按 FR-7 继续保留,直到所属会话释放。
- 进入 `terminating` 后不再接受新输入,也不启动替代命令。
- 终止只针对本次调用记录的进程树。
- 迟到的 `close` 只用于清理,不得覆盖已提交的取消或超时原因。
@@ -139,6 +151,8 @@ type SubagentDelegateResult = {
- 子级不能请求更高模式。
- 子级使用父请求开始时的工具与能力快照;运行中设置变化不重写快照。
- 子级调用 MCP、浏览器或知识能力时继续经过这些能力已有的作用域和生命周期边界。
- FR-5 / FR-7 / US-C1:Execute 子级使用父请求的浏览器标签页,不另开标签页或改变所有权;
子级结束后父模型仍可继续操作该页,父请求继续负责其使用租约。
- Execute 子级不弹出额外的“Subagent 执行授权”,也不获得父级没有的 MCP 分配。
## 9. 并发和顺序
+37 -9
View File
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,跨平台 CI 待验证 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 版本 | 0.4 |
| 日期 | 2026-09-09 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 关联领域 | [Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md) |
@@ -22,10 +22,11 @@ Git 或项目脚本,因此无法独立验证结果。
工具,与本功能的 `subagent_delegate` 仍为不同入口。OpenCode、Continue 和 DeepSeek Harness 一般已经提供
Shell、Agent 或 Task 能力,再向它们注入 GoodBuddy 同名工具只会造成重复入口和语义冲突。
本功能只补齐直连模型缺少的基础 Agent 能力:
本功能补齐直连模型缺少的基础 Agent 能力:
1. 一个按平台适配的进程执行接口。
2. 一个单层、继承父请求范围的编程 Subagent 接口。
1. 一组工作区搜索、分页读取和补丁编辑接口。
2. 一个按平台适配的进程执行接口。
3. 一个单层、继承父请求范围的编程 Subagent 接口。
## 2. 产品目标
@@ -34,6 +35,7 @@ Shell、Agent 或 Task 能力,再向它们注入 GoodBuddy 同名工具只会
3. 让直连模型可以把独立工作委派给临时 Subagent,并取得有界、可核验的结果。
4. 保持 Ask 只读、Execute 完整授权、执行空间、取消、有界输出和进程回收语义不变。
5. 不向已有原生执行能力的 Agent Runtime 重复注入 GoodBuddy 进程或 Subagent 工具。
6. 使用安装包内置 ripgrep 提供跨平台一致的快速代码搜索,不依赖用户预装命令。
## 3. 核心产品决策
@@ -76,6 +78,12 @@ Subagent 使用父请求的项目、执行空间、工作模式、直连模型
Ask 禁止命令和写入。Execute 是用户对当前执行空间账号的完整授权。进程和 Subagent 不增加
“受控执行”“仅测试”“可信命令”或逐命令风险等级,也不重复弹出第二套确认。
### 3.6 精简工作区工具
直连模型使用 `workspace_rg` 发现文件和搜索内容,使用 `workspace_read_text` 按行分页读取,
使用 `workspace_apply_patch` 新增、修改或删除文本文件。新请求不再注入低效的单层目录列表和
整文件覆盖工具。Ask 提供前两个只读工具;Execute 额外提供补丁工具。
## 4. 功能范围
### 4.1 包含
@@ -88,6 +96,7 @@ Ask 禁止命令和写入。Execute 是用户对当前执行空间账号的完
- 直连模型单层 Subagent 委派、排队、运行、部分结果、失败和取消。
- 父子请求的工具、模型用量、活动和成果归属。
- 桌面聊天和选择直连模型的消息通道项目使用相同 Runtime 能力矩阵。
- 安装包按目标平台和架构携带固定版本 ripgrep,并将其提供给工作区搜索和命令 PATH。
### 4.2 不包含
@@ -104,8 +113,8 @@ Ask 禁止命令和写入。Execute 是用户对当前执行空间账号的完
### FR-1 直连模型专属可用性
- 两个工具只由 Runtime provider `model` 注册。
- Agent Runtime 切换为 OpenCode、Continue 或 DeepSeek Harness 后不再显示这两个工具。
- 进程、编程 Subagent 和输出续读工具只由 Runtime provider `model` 注册。
- Agent Runtime 切换为 OpenCode、Continue 或 DeepSeek Harness 后不再显示这些工具。
- 图像生成连接不获得文本 Agent 工具。
- 能力不可用时返回准确原因,不以其他 Runtime 或本机 Shell 静默代替。
@@ -150,8 +159,10 @@ Ask 禁止命令和写入。Execute 是用户对当前执行空间账号的完
- 父请求取消必须传播到活动 Subagent 和其当前进程。
- Runtime 替换、会话释放和应用退出必须停止 GoodBuddy 创建的活动子进程。
- 命令、标准输出、标准错误、Subagent 提示、结果、队列和并发全部有界。
- 截断必须明确标记,不能把不完整输出伪装为完整结果。
- 命令、Subagent 提示、单次返回的输出预览、队列和并发有界;完整输出不因超过预览容量而丢弃。
- 预览省略内容时明确标记并提供续读位置。模型可用 `output_read` 逐页取得完整日志及
Subagent 结果,包括失败或取消前已取得的部分输出。
- Ask 和 Execute 均可续读当前会话的输出。会话或 Runtime 释放后,临时输出删除,旧句柄失效。
### FR-8 环境和凭据
@@ -180,6 +191,19 @@ Ask 禁止命令和写入。Execute 是用户对当前执行空间账号的完
- 一旦本次模型调用已经显示文本或推理内容,不自动重放该调用,避免重复回答。
- 非网络类 Provider 错误、协议错误和用户取消不进入自动重试。
### FR-12 高效工作区工具
- `workspace_rg` 同时支持内容搜索和文件列表,遵循项目忽略规则,并以
`path:line:column:text` 紧凑格式返回匹配。
- `workspace_read_text` 接受起始行和行数,大文件通过续读完成,不因超过旧的整文件上限而
完全不可读;超长单行可使用返回的字节位置继续读取。
- `workspace_apply_patch` 支持一个补丁新增、修改和删除多个 UTF-8 文件;执行前验证全部
路径和 hunk,执行失败时准确报告已完成文件。
- 搜索和读取在 Ask/Execute 均可用;补丁只在 Execute 可用,Main 调用边界再次检查模式。
- `workspace_rg` 使用固定参数调用随包 ripgrep,不向模型开放 `--pre` 或任意命令参数。
- 工作区工具不限制文件总大小、补丁总大小、搜索表达式长度、glob 数量或模型请求的结果
数量。单次返回按上下文容量截断或分页;读取结果提供续读位置,搜索结果提示模型缩小查询。
## 6. 产品验收
- [ ] Windows 直连模型 Execute 使用 PowerShell 创建文件、运行测试并读取退出码。
@@ -190,9 +214,13 @@ Ask 禁止命令和写入。Execute 是用户对当前执行空间账号的完
- [ ] Subagent 不能递归委派,不能切换父请求工作区、模式或模型。
- [ ] 切换到 OpenCode、Continue 或 DeepSeek Harness 后不出现本功能工具。
- [ ] 非零退出、超时、取消和输出截断均准确展示。
- [ ] 模型能续读长输出中未出现在预览内的中间与结尾内容,中文和表情不会导致分页停滞。
- [ ] 取消父请求和退出应用后没有残留 GoodBuddy 命令进程。
- [ ] 本机工具环境选择对新命令生效,Main 持有的模型凭据不进入子进程。
- [ ] 子任务不进入 Task Center,不创建独立可导航 Conversation。
- [ ] TLS 建连重置和请求超时在未输出内容时最多自动重试 3 次,成功后继续原请求。
- [ ] 重试等待可取消,已显示部分内容的失败不自动重放。
- [ ] Ask 可使用 ripgrep 搜索和分页读取,但不能应用补丁。
- [ ] Execute 可通过一个补丁新增、修改和删除多个工作区文件,并运行测试验证。
- [ ] Windows、macOS、Linux 的 x64/arm64 安装包均携带对应 ripgrep,且无需系统预装。
- [ ] 全量测试、类型检查、Lint 和生产构建通过。
+83 -7
View File
@@ -6,18 +6,32 @@
| --- | --- |
| 文档类型 | 功能进度 |
| 状态 | 已实施,本机验收完成 |
| 版本 | 0.4 |
| 日期 | 2026-09-05 |
| 版本 | 0.5 |
| 日期 | 2026-09-09 |
| 关联入口 | [直连模型 Agent 能力](./README.md) |
## 当前结论
本机生产路径已经完成源码接线:直连模型 Execute 可运行平台 Shell,Ask/Execute 均可按
父模式委派单层编程 Subagent;OpenCode、Continue、DeepSeek Harness 和托管 SSH 路径不注入
这两个工具。Windows 本机、真实模型和全量项目验证已通过;macOS 与 Linux 真机验证仍需
由对应平台完成。
父模式委派单层编程 Subagent;长输出可通过 `output_read` 续读。OpenCode、Continue、
DeepSeek Harness 和托管 SSH 路径不注入这些工具。既有功能的 Windows 本机、真实模型和
全量项目验证已通过;本次分页验证记录见下方 2026-09-10 条目。macOS 与 Linux 真机验证
仍需由对应平台完成。
## 已确认的当前基线
## 2026-09-10 提交审查后的桌面 UI 验收
- Windows 隔离配置使用本轮最终开发构建,通过普通输入框发送触发完整
App → Preload → Main → ModelAgentRuntime → ModelToolProvider 路径。
- Ask 在已保存 `toolApproval: policy` 下完成实际 `workspace_read_text` 与 `workspace_rg`,
读取同一专用哨兵,24 字节测试文件与校验值保持不变。未放开 Ask 写工具。
- 父请求调用实际编程 Subagent;子级在父浏览器归属下完成导航和快照,子级页面显示在
父级工作栏 Tab,没有额外空白原生页面。
- 本组使用确定性 loopback 模型服务,不声称执行了桌面真实 Provider 推理。
请求计数、浏览器/终端 UI 场景、明确覆盖边界及清理结果统一记录在
[工作栏验收进度](../assistant-workbar/progress.md#2026-09-10-提交审查修复)。
- 最终整库回归与静态检查结果同见该记录;测试进程已退出,不改变用户安装版或工作区。
## 设计前基线(历史)
- [x] 直连模型当前内置工作区读、目录列表和文本写入,但没有进程工具。
- [x] 内置 MCP 当前没有 Shell;自定义 MCP 不能替代开箱即用的进程能力。
@@ -56,7 +70,7 @@
- [x] `ModelToolProvider` 注册与调用。
- [x] Ask/Execute、Runtime target 和本机执行空间双重检查。
- [x] DeepSeek Harness、OpenCode 和 Continue 隔离。
- [x] 通用工具活动和“设置 > MCP > 直连模型”编程能力目录。
- [x] 通用工具活动和“设置 > MCP > 直连模型”开发工具目录。
- [x] 普通问答、工具轮次和上下文摘要的瞬时网络错误及请求超时最多自动重试 3 次。
- [x] 递增退避、重试状态、取消传播和已显示输出不重放。
@@ -86,6 +100,28 @@
GoodBuddy 托管 Node/Python 已可用。
- 远端直连模型进程执行不在首版,后续需要独立增加 Agent 进程协议和真机验证。
## 2026-09-09 高效工作区工具
- [x] 新增 `workspace_rg`,以随包 ripgrep 同时提供内容搜索和文件发现;内部解析 JSON,向
模型返回紧凑的路径、行号、列号和预览。
- [x] `workspace_read_text` 改为按行分页读取,大文件不再因旧的整文件 256 KiB 上限完全
不可读。
- [x] 移除搜索表达式、glob 数量、请求结果数、读取行数、补丁大小和目标文件大小的固定上限;
单次输出继续分页或截断,超长单行可通过 `offsetBytes` 续读。
- [x] 新增 `workspace_apply_patch`,支持一个补丁新增、修改和删除多个 UTF-8 文件,并在
修改前完成路径和 hunk 验证。
- [x] Ask 工具清单开放工作区搜索和读取,补丁与进程仍只在 Execute 提供。
- [x] 设置中的“文件系统操作”更新为“工作区文件”,“编程能力”更新为“开发工具”。
- [x] 固定 `@vscode/ripgrep` 1.18.0;构建钩子按目标平台/架构从锁定平台包提取二进制,
安装包携带当前目标的 `rg` 和 MIT 许可证。
- [x] 本次聚焦测试 `212 passed, 1 skipped`;全量 `npm run lint` 和
`npm run build:bundle` 通过。构建钩子在 Windows x64 实际准备并运行随包
ripgrep 15.0.0。
- [x] 中文文档审校覆盖本目录 7 个 Markdown 文件,阻断项为 0。
- [ ] 全量 `npm test` 和 `npm run typecheck` 仍受同一工作树中并行开发的浏览器标签页接口及
测试夹具修改阻塞;当前类型错误位于 `ipc.test.ts` 和 `browser-tabs-electron-e2e.ts`,最终
全量验证需在这些改动稳定后复跑。
## 2026-09-06 命令工作目录限制修复
- FR-3 / US-A3:移除命令工作目录的工作区范围限制。绝对路径、`..` 和指向工作区外
@@ -160,6 +196,46 @@
- 全量 `npm test`:`3493 passed, 55 skipped`。
- `npm run typecheck`、`npm run lint` 和 `npm run build`:通过。
## 2026-09-10 进程与 Subagent 完整输出续读
- FR-7 / US-A6:`PagedOutputStore` 完整写入临时输出文件,进程和 Subagent 返回前缀预览
与续读引用。JSON 转义计入预览预算,避免 Provider 二次裁剪后丢失续读位置。
- `ModelToolProvider` 已注册并分发 `output_read`,Ask/Execute 均可读取所属会话输出。
工具数量预留一个槽位,页结果不再裁剪。详细字段以 [技术设计](./technical-design.md#44-输出续读)
为准。
- Provider、进程、Subagent、分页存储及共享工具目录五个测试文件共 `73 passed`。
覆盖真实 PowerShell stdout/stderr、Subagent 父会话归属、JSON 转义、Unicode 小页面前进、
句柄失效、活动调用取消和资源清理。`npm run typecheck`、`npm run lint` 通过。
- `runtime-e2e.manual.test.ts` 新增真实模型边界场景:运行一次输出超过 96 KiB 的 Node
脚本,随机中间与结尾标记均不在预览中;模型经 `output_read` 读到 EOF 后准确返回两个标记。
最终带报告的运行 `1 passed, 19 skipped`,上游模型请求精确为 4 次,进程调用 1 次,续读
调用 2 次。先前同场景也通过,但其控制台计数未保留,不据此推算两次运行的精确累计调用数。
- 真实调用使用现有 `RuntimeSettingsStore` 与 Electron `safeStorage` 解密已配置文本连接,
凭据仅经测试子进程环境传递。`GOODBUDDY_E2E_OUTPUT_REPORT` 可记录不含凭据、Prompt
或输出正文的调用计数;测试工作区和输出存储在结束后释放。
- 本目录中文文档已按 `deai-writing` 审校,扫描阻断项为 0;保留技术枚举、权限限制和历史
验证记录中的明确边界。本次未运行全量测试或生产构建,也不作为其他平台真机验收。
## 2026-09-10 直连工具接线回归修复
- FR-4 / FR-12 / US-B0:普通 Ask 不再依赖可选能力才进入工具循环;明确允许工作区搜索、
文本读取和当前会话输出续读绕过 Ask 默认拒绝 authorizer,保留写入、补丁、进程和浏览器
的 Ask 拒绝。内部无工具摘要路径不变。
- FR-5 / FR-7 / US-C1:编程 Subagent 继承父浏览器标签页及所属 Conversation;子级历史和
输出仍独立,浏览器资源不转移所有权。回归通过真实 Runtime、Provider 和 Subagent 服务,
浏览器使用校验父 Conversation/标签页身份的测试替身,并确认子级结束后父级可继续调用。
- MCP 已发现工具调用不再触发其他失败服务器的发现重试;覆盖连接失败、清单失败、动态
刷新失效、健康调用、配置替换与 dispose 清空。发现重试仍只发生在下一次 `listTools`。
- 验证命令:`npm test -- --run src/main/agent/model-tool-provider.test.ts src/main/agent/model-runtime.test.ts`。
最终结果为 `122 passed, 1 skipped`(Provider 36;Runtime 86 通过、1 跳过)。
Ask 回归实际执行随包 ripgrep、工作区分页读取和真实本机输出续读;模型响应均为测试夹具。
- 本次真实模型调用精确为 0,没有外部网络或远端 Host 操作;未运行全量测试、类型检查、
Lint 或构建。桌面 IPC 提示词、浏览器租约生产入口和最小真实模型验收由集成工作继续验证,
以上聚焦测试不作为真实浏览器、真实模型或整条桌面路径验收。
- 源码检查:GoodBuddy Agent 不导入这两个 Runtime/Provider;本机 DeepSeek Harness 的
MCP 代理复用 Provider 发现快照规则,托管 SSH Runtime 不经过该本机构造路径。本次未
修改远端 Agent、桥接或启动器。
## 进度维护要求
- 只有真实生产路径完成并验证后才能勾选实施项。
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 版本 | 0.4 |
| 日期 | 2026-09-09 |
| 关联 PRD | [PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 相关架构 | [助手工作栏与执行空间](../assistant-workbar/prd.md)、[本机工具环境](../local-tool-environment/technical-design.md) |
@@ -17,17 +17,23 @@
- `ModelAgentRuntime` 的多轮工具调用、上下文压缩、取消、用量和有界结果处理。
- `ModelToolProvider` 的直连模型内置工具、浏览器、Web、知识和自定义 MCP 聚合。
- `workspace_rg`、分页 `workspace_read_text` 和 `workspace_apply_patch` 工作区工具。
- `WorkspaceAccess` 的本机和远端工作区抽象。
- 跨平台子进程树终止辅助函数。
- 面向用户专家协作的 `SubagentScheduler` 和状态事件。专家使用具备已启用本机能力的
直连模型 Runtime,继承父请求 Ask/Execute 及 authorizer,工具事件记入子任务;
Ask 保持只读,不把这些本机工具表述为远程 OpenCode 子会话。
- OpenCode、Continue 和 DeepSeek Harness 各自的 Shell/Agent 能力。
- 直连模型 `process_execute` 和 `subagent_delegate`。
- 直连模型 `process_execute`、`subagent_delegate` 和 `output_read`。
- 不创建顶层 Task/Conversation 的编程 Subagent actor 与活动归并。
## 2. 总体架构
直连模型在 Main 内执行 HTTP,本身没有独立模型进程。跨 Runtime 资源调研中的直连
部分仅检查历史缓存、会话引用和工具资源释放,不为它新增进程池;分析与验证范围
见 [Runtime 资源技术设计 §7.2](../assistant-workbar/runtime-process-reuse-technical-design.md#72-直连模型及历史详情)。
以下当前功能不因此改变,进程工具产生的子进程仍按各自会话生命周期管理。
```text
ModelAgentRuntime
└─ ModelToolProvider
@@ -69,6 +75,63 @@ type BuiltinModelToolSummary = {
工具总数和 schema 总字节继续计入直连模型现有 100 个工具与 512 KiB 上限。
## 工作区工具契约
### 工具输入与执行
- `workspace_rg` 接受 `pattern`、相对目录、glob、固定字符串、忽略大小写、文件列表模式和
最大结果数。Main 固定追加 `--no-config`,内容搜索读取 `--json`,对模型返回紧凑文本。
- `workspace_read_text` 接受 `path`、从 1 开始的 `offset` 和 `limit`,通过现有
`WorkspaceAccess.readText` 字节分页组装带行号结果;`offsetBytes` 提供原始字节续读,
覆盖跨多个页面的超长单行。
- `workspace_apply_patch` 接受一个 `*** Begin Patch` 文本,支持 `Add File`、`Update File`
和 `Delete File`。全部操作先解析并验证;更新文件保留原换行和权限位,单文件原子替换。
- Ask 只注册前两个只读工具;Execute 注册全部三个工具。旧的目录列表和整文件覆盖不再进入
新工具清单。
- 输入和目标文件不设固定总量上限。单次工具结果仍遵守直连模型上下文边界;读取返回续读
位置,搜索截断时要求模型缩小路径、glob 或表达式。
### 读取与搜索失败恢复
`workspace_read_text` 和 `workspace_rg` 的输入校验及预期工作区错误由 `ModelToolProvider`
转为 `RecoverableModelToolError`,保留原始 cause。范围包括 schema 校验失败、绝对路径或
越界路径、目标不存在或类型不符、无效 UTF-8、无效读取范围,以及 `ENOENT`、`ENOTDIR`、
`EISDIR`、`EACCES`、`EPERM`、`ELOOP`、`ENAMETOOLONG` 等已列明的文件系统错误。
Runtime 向模型返回 `{ ok: false, recoverable: true, error, nextAction }` 的 JSON 文本工具结果,
工具活动记为 `recoverable`,继续工具循环,由模型修正路径、分页参数、glob 或搜索表达式;
提示不得原样重复失败参数。取消、远端断线、失效工作区、缺失 rg 可执行文件及未分类的内部
错误继续向外传播,不转为可恢复结果。
读取和搜索仍只接受工作区内相对路径,绝对路径即使指向工作区内文件也拒绝;原有越界及
符号链接检查继续生效。仅当本机直连模型 Execute 的 `process_execute` 实际可用时,恢复
提示才建议用它访问工作区外文件。此处理不改变 Windows 用户权限、UAC 或文件 ACL,
也不提升进程权限;Ask 仍保持只读。
ripgrep 结果按退出状态处理:
- exit 1 保持正常无匹配结果。
- exit 2 有 stdout 且不是 regex/glob 编译错误时,保留已取得的匹配或文件列表,附加
`incomplete search coverage` 警告及有界 stderr。零匹配只代表已搜索部分,不能据此断言
整个目标不存在匹配;输出截断提示仍独立保留。
- exit 2 无 stdout,或出现无效 regex/glob 编译错误时,搜索层抛错,再由 Provider 转为
上述可恢复结果;模型可修正表达式,字面搜索可使用 `fixedStrings`。
该契约属于桌面 `ModelAgentRuntime → ModelToolProvider` 内置工具路径,复用浏览器标签页
关闭和元素引用失效已有的可恢复错误机制;浏览器分别提示 `browser_navigate` 或
`browser_snapshot`,路由规则见[浏览器技术设计](../assistant-workbar/browser-tabs-technical-design.md#61-兼容阶段)。
`workspace_read_text` 若使用 `RemoteWorkspaceAccess`,仍经同一 Provider 分类远端
`data.code` 中的预期文件系统和路径/文本错误。Agent daemon 的 RPC 错误序列化保留
`ENOENT` 等大写系统错误码,避免远端文件不存在被误判为不可恢复错误;远端原生工具
处理不变。内置 rg 仍仅支持本机工作区,托管 SSH 当前仍使用远端 OpenCode 原生工具,
不因共享读取抽象而开放远端直连模型,见[远程 Runtime 边界](../remote-host/technical-design.md#runtime)。
### ripgrep 打包
固定依赖 `@vscode/ripgrep`,构建钩子根据 Electron 目标选择对应 Windows、macOS 或 Linux
x64/arm64 平台包,使用 lockfile integrity 校验后提取 `rg`。安装包只复制当前目标二进制和
MIT 许可证。运行时通过 `BundledRuntimePaths.ripgrep` 使用绝对路径,并将所在目录放到直连
模型进程 PATH 前部。
## 4. 进程工具契约
### 4.1 输入
@@ -114,6 +177,8 @@ type ProcessExecuteResult = {
stderr: string
stdoutTruncated: boolean
stderrTruncated: boolean
stdoutReference?: PagedOutputReference
stderrReference?: PagedOutputReference
}
```
@@ -122,12 +187,31 @@ type ProcessExecuteResult = {
### 4.3 输出边界
- stdout 和 stderr 各最多 96 KiB。
- 超限时分别保留前 48 KiB 与最后 48 KiB,中间插入固定截断标记。
- stdout 和 stderr 各返回最多 96 KiB 的前缀预览,JSON 转义后的内容也计入预览预算。
- `PagedOutputStore` 将流式输出完整写入系统临时目录,内存只保留预览;超限后继续写盘。
存储失败明确报错,不把缺失内容标记为完整结果。
- 预览省略内容时返回 `stdoutReference` 或 `stderrReference`,包含 `handle`、`nextCursor`
和 `totalBytes`。短输出返回完整文本并删除临时文件,不附带句柄。
- JSON 结果加上元数据后必须低于 `ModelToolProvider` 现有 256 KiB 工具结果上限。
- 流式读取在收到数据时执行边界,不先积累无界 Buffer。
- 输出按 UTF-8 解码,无效序列使用替换字符并保留字节计数。
### 4.4 输出续读
`output_read` 接受严格对象 `{ handle, cursor?, limitBytes? }`:`cursor` 为从 0 开始的 UTF-8
字节位置,省略时从头读取;`limitBytes` 为 1 至 32,768 的整数,默认 32,768。页面返回
`{ handle, content, cursor, nextCursor, totalBytes, eof }`,模型沿 `nextCursor` 读取至 `eof`。
页面在字符边界结束;若请求大小容不下首个完整字符,最多扩展到 4 字节,保证续读前进。
位于 UTF-8 延续字节的输入 cursor、负数、越界和未知字段均拒绝。
Provider 根据 `process:` 或 `subagent:` 前缀选择服务,以当前 `conversationId` 校验所有者,
不接受模型另传 owner。Ask/Execute 均可用,工具总数为此预留一个槽位;不要求本轮能够启动
命令或再次委派。分页结果直接 JSON 序列化,不进行二次文本裁剪,以免 cursor 与内容不一致。
32 KiB 的页面即使全部需要 JSON 转义,也能保留在现有工具结果容量内。
进程输出归属调用会话,Subagent 最终输出归属父会话。子级临时会话释放后,父模型仍能续读
Subagent 结果。会话释放会等待活动调用结算再删除所属文件;服务退出关闭写入句柄并删除临时
目录。输出不写入 SQLite,不跨 Runtime 重启恢复。
## 5. 平台执行
### 5.1 Shell 探测
@@ -235,13 +319,18 @@ type DirectModelSubagentContext = {
- 子级复用父 Provider;Main 写入的 `delegationDepth=1` 同时在清单和调用边界过滤
`subagent_delegate`。
- Ask 子级使用 Ask 清单;Execute 子级使用 Execute 清单。
- Execute 子级经 `ModelSubagentRequestContext` 继承父 `browserTabId` 和浏览器所属
`browserConversationId`。后者只在 Main 内部请求与工具上下文中传递:浏览器操作使用父
Conversation,历史与分页输出仍使用子 Conversation。子级不新建标签页或申请使用租约,
结束时只释放自己的临时会话,父标签页和租约仍由父请求生命周期管理。
### 7.3 调度和结果
- 复用 `SubagentScheduler` 的取消、并发和队列模式。
- 全局最大并发 3,队列最大 20。
- 单个子任务最长 10 分钟;父请求更早取消时立即停止。
- 文本结果最多 192 KiB,错误最多 2 KiB,连同结构化字段保持在 256 KiB 工具结果上限内。
- 文本预览最多 192 KiB,JSON 转义也计入预算;错误最多 2 KiB。省略的正文通过
`outputReference` 续读,字段与生命周期沿用第 4.4 节,完整输出不受预览容量限制。
- 模型用量逐事件归属父请求和 `childRunId`,不能只算入父模型最后一轮。
- 子级失败和取消返回结构化终态及部分输出,让父模型决定下一步。
@@ -281,6 +370,25 @@ type ModelToolCallContext = {
- `releaseConversation` 取消并释放该会话拥有的浏览器、Subagent 和进程。
- `dispose` 等待有界清理,不因模型或子进程不响应而阻塞应用退出。
### 8.1 Ask 工具循环
普通 Ask 请求进入工具循环,不再依赖知识、联网搜索或 Subagent 是否启用。Runtime 对
`workspace_rg`、`workspace_read_text`、`output_read` 三个明确只读工具免去 Ask authorizer
调用,避免生产 authorizer 的默认拒绝使已列出的只读工具无法执行。Execute 仍沿用既有
authorizer;补丁、进程和其他写入不因此获准。Provider 按清单和调用边界继续校验模式,
浏览器工具不加入 Ask 免审批集合。内部上下文摘要使用 `noModelTools`,保留无工具问答路径。
### 8.2 MCP 发现与调用
`listTools` 发现 MCP 并保存当前绑定快照;已知工具调用只查该快照,不在每次调用前重试
其他失败服务器。未知名称直接失败。失败服务器只在下一次发现入口重试,动态清单刷新失败
时从新快照中移除该服务器工具,下一次发现再刷新;健康连接继续复用。
绑定只在 Provider 内存中存在,`dispose` 与连接缓存一起清空。MCP 配置保存通过
`refreshCapabilities → onRuntimeSettingsChanged → reconfigureRuntimes` 替换 Runtime,
`SelectedRuntimeManager.reset` 使后续请求创建新 Provider;退役 Runtime 按原有会话生命周期
清理。没有新增持久缓存、失败重试日志或配置兼容层。
DeepSeek Harness 的 Main 工具代理必须使用仅包含分配 MCP 和 Web 的 Provider 视图,不能因为
共享 `ModelToolProvider` 类而自动看到直连模型编程工具。
@@ -296,14 +404,14 @@ DeepSeek Harness 的 Main 工具代理必须使用仅包含分配 MCP 和 Web
- `failed`:启动或契约失败。
- `cancelled`:父请求取消。
结果摘要包含 Shell、工作目录、退出码、耗时和截断标记。完整有界 stdout/stderr 进入工具
输出字段,不写应用诊断日志。
结果包含 Shell、工作目录、退出码、耗时、输出预览和续读引用,不写应用诊断日志。
通用工具活动仍显示有界摘要,模型通过 `output_read` 取得的页面记为后续工具活动。
### 9.2 Subagent 事件
复用现有 `subagent` 公开事件和消息块,增加 `routingMode: 'native'` 表示模型主动委派。
`expertId` 不再作为编程 Subagent 必填身份;共享 schema 应改为执行者 discriminated union,
而不是伪造一个专家记录:
不得以专家记录代替该执行者:
```ts
type SubagentActor =
@@ -339,10 +447,15 @@ type SubagentActor =
结果总量都由请求超时、取消和上下文压缩控制,长回复和大工具结果不会被中断。图像生成
响应仍按可解码图片大小校验。
会话历史在请求校验、Renderer 发送与保存、SQLite 读写和直连模型缓存中均保留完整消息,
不按消息条数、单条文本长度或累计文本量裁剪。上下文大小由现有压缩策略和模型上下文窗口
处理。Continue 接收完整历史,已有有效摘要只替换其覆盖的前缀,剩余消息全部交给原生
Runtime;GoodBuddy 不再额外保留最后 20 条或限制为 128,000 字符。
## 11. 性能
- Shell 探测按执行服务实例缓存,不在每轮模型调用重复探测。
- stdout/stderr 边读边界定,避免大日志进入内存后再截断。
- stdout/stderr 边读边写入临时文件,内存仅保存有界预览;续读每次只读取一页。
- 子级共享父请求已解析的模型配置和工具服务,不复制 MCP 配置或持久状态。
- 不增加持久 Shell、后台 daemon、工作区快照或恢复状态机。
- 命令执行不使用 PTY,避免普通构建和测试承担终端渲染成本。
@@ -356,6 +469,8 @@ type SubagentActor =
- Windows PowerShell、POSIX Bash/Sh 选择和参数。
- cwd 默认、相对子目录、绝对路径、工作区外目录、指向外部的符号链接和不存在的目录。
- exit 0、非零退出、spawn 失败、超时、取消和输出截断。
- 输出预览加分页能还原全文,包括 JSON 转义文本、中文、表情和字面替换字符;覆盖一字节
页面前进、跨会话拒绝、活动调用释放、短输出删除及服务退出清理。
- 环境筛选、本机工具 PATH 优先且模型凭据不进入进程。
- 子级模式、模型、项目、执行空间和能力继承。
- 深度 1 过滤、并发 3、队列 20、取消传播和部分输出。
@@ -387,6 +502,11 @@ type SubagentActor =
每个平台一次完整场景即可,不进行批量或高成本调用。记录准确模型调用次数,不记录 API Key。
输出边界场景复用 `runtime-e2e.manual.test.ts`:真实模型运行一次专用 Node 脚本,输出超过
96 KiB,并在预览之后生成随机中间与结尾标记。模型必须通过生产 `output_read` 连续读到 EOF,
最终回答包含两个标记。测试限制工具为进程与续读,记录上游请求数和工具调用数;凭据由现有
加密设置解密后仅在内存及测试子进程环境中传递。
### 12.4 项目验证
源码完成后运行:
+18 -8
View File
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 版本 | 0.4 |
| 日期 | 2026-09-09 |
| 设计系统 | [GoodBuddy 统一界面设计系统](../../../UI-DESIGN.md) |
| 关联 PRD | [PRD](./prd.md) |
| 关联逻辑 | [功能逻辑设计](./logic-design.md) |
@@ -15,7 +15,7 @@
本功能不增加一级页面、Composer 按钮或 Runtime 专属工具栏。用户主要通过三处理解能力:
1. **设置 > MCP > 直连模型**:查看内置编程能力说明。
1. **设置 > MCP > 直连模型**:查看工作区文件和开发工具说明。
2. **聊天消息活动块**:查看命令和 Subagent 的实时状态与结果。
3. **运行记录**:按当前 Conversation 或 Task 查看聚合执行活动。
@@ -23,10 +23,15 @@ Job、Subjob 和 Run 继续是内部对象,不创建树形 UI、独立路由
## 2. 设置中的能力目录
在现有直连模型工具目录增加“编程能力”分组:
直连模型工具目录使用两个本机开发分组:
```text
编程能力
工作区文件
搜索工作区
读取工作区文本
应用工作区补丁
开发工具
进程执行
使用当前用户权限运行 PowerShell、Bash 或 Sh 命令,默认在工作区执行,也可指定其他工作目录。
编程 Subagent
@@ -35,7 +40,7 @@ Job、Subjob 和 Run 继续是内部对象,不创建树形 UI、独立路由
要求:
- 两项默认属于直连模型,不提供持久 Switch。
- 这些工具默认属于直连模型,不提供持久 Switch。
- 设置目录只定义稳定工具能力,不增加 Shell 状态或重试控件;实际 Shell 标签在模型工具
定义和进程结果中返回。
- Runtime 是 OpenCode、Continue 或 DeepSeek Harness 时,不在其原生能力清单中伪造这两项;
@@ -48,6 +53,8 @@ Job、Subjob 和 Run 继续是内部对象,不创建树形 UI、独立路由
### 3.1 标题与摘要
正常执行状态的操作摘要遵循[工具操作摘要规则](../assistant-workbar/runtime-interactions.md#工具操作摘要),从原始调用参数提取并在完成后保留;异常状态继续显示原有提示。
```text
进程执行
PowerShell · packages/app · npm test
@@ -79,11 +86,14 @@ GoodBuddy 服务崩溃。
1. Shell、工作目录、耗时和退出码。
2. stdout。
3. stderr。
4. 截断提示。
4. 预览省略提示与工具结果中的续读引用。
日志使用共享等宽字体和可选择文本。长输出在活动块内折叠,展开后仍保持工作栏和聊天布局
可用;不把完整日志复制为应用通知。
模型使用 `output_read` 续读时显示普通工具活动“续读工具输出”。活动展开只展示该次返回的
摘要,不等同于自动加载完整日志;当前不增加手动分页按钮。
## 4. Subagent 活动块
复用现有 `subagent` 消息块,并将系统执行者显示为“编程 Subagent”,不要求用户预先创建
@@ -97,7 +107,7 @@ GoodBuddy 服务崩溃。
终态:
- 完成:显示有界结果摘要,可展开查看完整返回。
- 完成:显示有界结果摘要,可展开查看当前预览;完整正文由模型通过输出续读工具取得。
- 失败:保留部分输出和错误。
- 取消:显示“父请求已取消”或准确取消原因。
- 排队:显示当前处于并发队列,不显示虚假百分比。
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,跨平台 CI 待验证 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 版本 | 0.4 |
| 日期 | 2026-09-09 |
| 关联 PRD | [直连模型 Agent 能力 PRD](./prd.md) |
| 关联逻辑 | [功能逻辑设计](./logic-design.md) |
@@ -54,8 +54,8 @@ When 目标不是目录或不存在,Then 命令不启动并返回准确错误
关联:FR-2、FR-9。
Given 测试命令退出码非零,When 进程结束,Then 工具正常返回退出码和 stderr,而不是终止
整个模型请求,And 模型可以读取失败原因、修改代码并再次运行。
Given 测试命令退出码非零,When 进程结束,Then 工具正常返回退出码和 stderr,模型请求
继续运行,And 模型可以读取失败原因、修改代码并再次运行。
### US-A5 取消长命令
@@ -64,12 +64,13 @@ Given 测试命令退出码非零,When 进程结束,Then 工具正常返回
Given 命令仍在运行,When 用户取消父请求,Then 当前 Shell 及其由本次命令创建的子进程树
停止,And 活动状态显示“已取消”,And 其他用户进程不受影响。
### US-A6 看懂截断结果
### US-A6 续读长输出
关联:FR-7、FR-9。
Given 命令输出超过上限,When 工具返回结果,Then 保留有用的开头和结尾并明确
`truncated: true`,And 模型与用户都不会把结果理解为完整日志。
Given 命令或 Subagent 输出超过单次预览容量,When 工具返回结果,Then 明确标记省略并提供
续读位置,And 模型可逐页读取中间与结尾内容,直到取得完整输出。Given 输出包含中文或表情,
Then 即使请求很小的页面,续读仍会前进。Given 所属会话已释放,Then 旧句柄不能继续读取。
### US-A7 从瞬时模型网络故障恢复
@@ -81,6 +82,14 @@ Given 直连模型在 TLS 建连、网络传输或单次请求超时时失败且
## 3. Epic B:编程闭环
### US-B0 搜索和分页读取大型项目
关联:FR-4、FR-12。
Given 用户在 Ask 或 Execute 中处理本机项目,When 模型需要定位代码或读取大文件,Then
它可以使用随包 ripgrep 搜索内容或列出文件,并按行分页读取命中位置,不要求用户预装 `rg`。
Given Ask 未启用知识库、联网搜索或编程委派,Then 工作区搜索和读取仍可使用,不要求额外批准。
### US-B1 修改后运行验证
关联:FR-2、FR-4。
@@ -102,6 +111,14 @@ Given 用户选择了 GoodBuddy 托管或自定义 Node/Python,When 新命令
Given 项目缺少所需命令或依赖,When Shell 返回错误,Then 工具保留退出码和错误文本,
And 模型说明缺失项或在 Execute 中采取用户要求的安装步骤,不把环境缺失报告为模型故障。
### US-B4 使用补丁修改文件
关联:FR-4、FR-12。
Given 用户选择 Execute,When 模型提交包含新增、修改或删除操作的工作区补丁,Then
GoodBuddy 在写入前验证全部路径和上下文,逐文件原子写入,并返回实际修改的文件列表。
Given 用户选择 Ask,Then 补丁工具不出现在清单中且伪造调用会被拒绝。
## 4. Epic C:Subagent 委派
### US-C1 委派聚焦编程任务
@@ -110,6 +127,8 @@ And 模型说明缺失项或在 Execute 中采取用户要求的安装步骤,
Given 父直连模型正在处理复杂请求,When 它调用 `subagent_delegate` 并给出独立任务,
Then 子级使用同一项目、模型连接和 Execute 能力完成工作,And 最终结果返回父模型综合。
Given 父请求已有浏览器标签页,When 委派任务需要浏览器,Then 子级使用同一页;子级完成后,
父模型仍可继续操作该页,不因子会话清理而关闭父页。
### US-C2 Ask 中只读委派
+7 -1
View File
@@ -7,5 +7,11 @@
| 文档 | 权威职责 |
| --- | --- |
| [文档解析与本地 OCR PRD](./prd.md) | 产品范围、解析策略、OCR 模型管理和验收 |
| [聊天附件技术说明](./chat-attachments-technical-design.md) | 文件选择与粘贴的进程链路、IPC、PPTX 图片 OCR、Worker 生命周期和验证证据 |
当前尚无独立 User Stories、功能逻辑、UI、技术设计和进度文档。
当前尚无独立 User Stories、功能逻辑、UI 和进度文档;聊天输入区交互遵循根目录
[UI 设计系统](../../../UI-DESIGN.md)。
Office 文件的浏览器内人工编辑、AI 选区修改、保存和撤销不属于文档解析职责,由
[Office 协同编辑](../office-document-editing/README.md)负责。文档处理仍负责内容提取、OCR、
旧格式转换和供模型消费的统一结构。
@@ -0,0 +1,115 @@
# 聊天附件技术说明
产品规则见 [PRD 的聊天附件入口](./prd.md#321-聊天附件入口),输入区布局与反馈遵循
[UI 设计系统](../../../UI-DESIGN.md)。本文说明本地文件选择、文件粘贴和截图粘贴的实现。
## 文件导入链路
`App.tsx` 在 textarea 的 `paste` 事件中同步读取 `clipboardData.files`。没有文件时
保留浏览器默认文本粘贴。有文件时阻止默认插入,并通过 preload 的
`context.getFilePath(file)` 调用 Electron `webUtils.getPathForFile`。
有本地路径的文件调用 `context.importFiles(paths)`,经 `context:import-files` IPC 到
Main。IPC 先验证可信 sender,再使用共享 `contextImportFilesSchema` 校验输入。
没有本地路径的截图沿用 `context.addPastedImage`;无路径的非图片文件和混有无路径项目
的本地文件批次显示可处理的错误,不静默丢弃项目。
`ContextManager.selectFiles` 只负责文件对话框,再调用与粘贴共用的 `importFiles`。
这个方法完成路径规范化、格式及大小检查、文本读取、图片转换、文档解析与上下文暂存。
任一文件失败时删除本批次已经暂存的项目,保留之前的附件。文件系统错误不向 Renderer
暴露绝对路径。
两种文件入口均发送 `context:file-selection-progress`。Renderer 共用正在导入状态、
附件上限处理和附件清理逻辑,解析期间禁用附件按钮并阻止发送。成功结果加入发起操作的
会话草稿;发送时通过已有 `contextIds` 和 `ContextManager.enrichRequest` 生成请求上下文。
本次变更不修改 Agent、远端协议或 Runtime 的附件消费实现。
## PPTX 图片识别与 OCR 生命周期
产品策略与限制分别见 [PPTX 图片流程](./prd.md#75-pptx-图片流程)、
[OCR 空闲资源回收](./prd.md#76-ocr-空闲资源回收)。
`DocumentParsingService` 在非快速工作流中使用 `knowledge/pptx-parser.ts` 读取
演示文稿顺序、幻灯片文字、图片关系和内嵌图片。Office ZIP 读取复用
`document-parser.ts` 的条目数量、单条目及总展开大小检查;不读取工作区中的关联文件。
每张引用图片通过现有 `DocumentOcrBroker` 的图片请求契约,进入生产
`document-ocr-bridge.ts` 和 WASM Worker。结果合并时保留原生文字,附上幻灯片页码、
图片定位及 OCR 置信度,诊断中的 OCR 页数按页码去重。
Bridge 用待完成请求计数判断队列是否空闲,正常完成后安排一次空闲计时;
新请求和 `terminateWorker` 均清除计时。到期通过 `Worker.terminate()` 释放整个推理环境,
而不是只清空 JavaScript 引用或反复触发垃圾回收。此机制由 PDF 与 PPTX 共用,
不增加 IPC、持久化设置或模型安装状态。
解析与回收发生在 Desktop 的 Main/Renderer,`gbagent` 不运行此 OCR Worker。
远程 Runtime 消费的仍是 Desktop 已提取的附件文本,没有单独的 Agent 解析器或
需要同步修改的远程 OCR 生命周期。
## Electron 验证
2026-09-15 在 Windows 的 Electron 43.2.0 上,从当前源码构建隔离输出并启动完整应用,
使用独立用户数据目录,保持 `sandbox: true`、`contextIsolation: true`。
已实际验证:
- Windows `Shell.Application` 的文件 `copy` verb 生成系统剪贴板后,原生粘贴事件提供
`File` 和有效路径,TXT 出现在聊天附件区。
- `CF_HDROP` 同时携带 TXT、DOCX 时,两个文件经生产 preload、IPC 和解析器成为附件;
DOCX 发出 `reading`、`parsing` 进度,包含正确文件名及 `2 / 2` 批次序号。
- 普通文本原生粘贴进入草稿,后续文件粘贴保持文字不变。
- ZIP 经生产导入路径报出不支持类型,已添加附件仍在。
- 系统图片剪贴板产生的 `image.png` 没有本地路径,仍通过截图入口生成图片附件。
以上使用原生 `webContents.paste()`,未伪造 DOM 粘贴事件或替换附件实现。Windows Shell
复制测试没有操纵资源管理器窗口中的键盘。产品未增加 Win32 剪贴板读取或兼容代码。
探测程序的原生调用仅用于构造文件剪贴板和保存、恢复测试前的剪贴板对象;最终探测的
`OleSetClipboard`、`OleFlushClipboard` 均返回成功。早期一次探测的恢复调用返回错误,
无法追溯确认该次剪贴板是否完整恢复。
macOS、Linux 文件管理器的实际剪贴板行为尚未在本次环境验证。没有发起模型调用;附件
进入发送请求的 `contextIds` 由 Renderer 回归测试验证,完整文档内容进入请求由生产
`ContextManager` 的真实文件解析测试验证。
## 自动检查
聚焦用例位于 `src/main/context-manager.test.ts`、`src/main/ipc.test.ts`、
`src/preload/context-files.test.ts`、`src/shared/context-contracts.test.ts` 和
`src/renderer/src/App.test.tsx`,覆盖多文件、进度、重复粘贴、发送上下文、错误与草稿保留、
大小及数量限制、批次回滚、可信 sender、输入契约和截图/文本回归。
```text
npx vitest run src/main/context-manager.test.ts src/main/ipc.test.ts src/preload/context-files.test.ts src/shared/context-contracts.test.ts src/renderer/src/App.test.tsx
npm test
npm run typecheck
npm run lint
```
本次最终拆分检查通过 6 项附件相关 Renderer 用例和 160 项 Main、IPC、preload、契约及
沙箱用例,类型检查与 lint 通过。完整 `npm test` 运行结果为 4214 项通过、67 项跳过、
6 项窗口恢复用例失败;这些用例属于并行修改的 `window.test.ts`,随后该文件单独复跑
10 项全部通过。本次没有再次完整重跑全仓测试。
## 2026-09-15 PPTX OCR 与空闲回收验证
在 Windows x64、Electron 43.2.0 上,从本次源码构建独立输出,使用独立用户数据目录和
本机已安装的 PP-OCRv6 Tiny 模型完成真实验证。测试仅替换系统文件选择对话框的返回路径,
保留生产 UI、preload、IPC、`ContextManager`、解析服务、OCR Broker 与 WASM Worker,
未替换解析或推理实现。
- 从“添加附件”一次导入 4 份图片型 PPTX,共 40 页;4 个附件均成功显示,连续处理期间
仅创建 1 个 OCR Worker。
- 队列空闲后观察到 Worker 被终止。每 5 秒读取 Electron `app.getAppMetrics()`:
Renderer 工作集采样高点为 733,492 KiB(约 716 MiB),回收后为 162,716 KiB
(约 159 MiB),启动基线约 163 MiB。该结果是本次设备与文件上的测量,不是固定内存承诺。
- 回收后,通过生产 `context.importFiles` 再导入一份 9 页 PPTX;新建第 2 个 Worker,
9 页均返回非空文字,共 2,462 字符,附件成功生成。
- 本次合计 49 次本地图片 OCR 请求,文本模型调用 0 次;没有发送聊天消息,也没有将文件
交给云端模型。未修改源文件。未在 macOS、Linux 或其他 OCR 模型档位上做本次实测。
新增聚焦回归覆盖 PPTX 关系与顺序、各场景路由、原生文字合并、快速模式、页数上限、
OCR 错误和取消;Worker 回归覆盖空闲到期、连续请求复用、队列保护、关闭清理与立即取消。
空闲回收后重新加载也在真实模型路径上验证,不能只用模型配置检查代替。
最终检查:`npm test` 4,293 项通过、67 项跳过;`npm run typecheck`、
`npm run lint` 通过。当前源码的隔离 Electron 生产构建通过;文档的 10 个相对文件链接
均有效。测试应用已关闭,包含附件草稿的独立用户数据目录已删除,原文件和原模型安装未改动。
+68 -4
View File
@@ -22,13 +22,16 @@ GoodBuddy 需要用同一条可信文档解析链路服务以下场景:
- XLSX 工作表 XML 与共享字符串;
- PPTX 幻灯片文字。
聊天附件、知识库和测试解析通过统一的 `DocumentParsingService` 使用已保存的工作流。
扫描 PDF 和 PPTX 幻灯片内嵌图片可使用已安装、已校验的本地 OCR 模型识别;
PPTX 的当前能力和边界见 [PPTX 图片流程](#75-pptx-图片流程)。
现有局限:
- 纯扫描 PDF 没有文本层时无法提取内容;
- DOC、XLS、PPT 等旧版二进制 Office 格式不支持;
- Office 解析主要提取文字,不能完整保留表格、公式、图表和版面;
- 聊天附件和知识库直接调用底层解析函数,缺少可配置的统一工作流;
- 没有本地 OCR 模型状态、真实解析测试和按场景策略。
- DOCX、XLSX 内嵌图片尚未接入 OCR;
- Office 格式转换和远程 OCR 服务尚未接入。
## 3. 产品原则
@@ -45,12 +48,40 @@ PDF 不是所有文档唯一的中间格式。解析应同时保留:
| 场景 | 默认预设 | 行为 |
| --- | --- | --- |
| 聊天附件 | 自动解析 | 优先快速提取,文本不足时按需 OCR,有界截断后加入当前请求 |
| 聊天附件 | 自动解析 | 优先快速提取,文本不足时按需 OCR,完整解析文本加入当前请求 |
| 知识库导入 | 完整索引 | 完整解析、按页或工作表定位、按需 OCR、分块与索引 |
| 扫描文档 | OCR | 页面渲染、文字识别、置信度与定位保留 |
| 表格分析 | 语义优先 | 单元格和值优先,PDF 或图片补充图表与打印布局 |
| 高保真审阅 | 视觉增强 | 原生解析、页面渲染、OCR 或视觉理解合并 |
PDF、DOCX、PPTX 和 XLSX 的解析结果保留章节定位及全文,不在附件入库时截断为
256 KiB,也不以富化后 Prompt 的 1 MiB 上限阻止发送。本地选择、文件粘贴与消息通道附件使用
同一文本保存路径;原文件大小、解析器和 12 MiB 附件暂存容量仍按现有规则检查,超限时
明确报错,不能用截断后的内容冒充全文。模型上下文窗口仍由所选 Runtime 处理。
#### 3.2.1 聊天附件入口
用户可点击附件按钮选择文件,也可在文件管理器中复制一个或多个文件后,在聊天输入框
按 `Ctrl+V` 添加。macOS 使用系统粘贴快捷键 `Command+V`。粘贴文件不替换草稿文字,
不自动发送;普通文本仍直接插入光标位置,系统截图仍作为图片附件添加。
本地文件选择与粘贴使用相同的格式及容量规则:
| 内容 | 支持范围 | 原文件大小上限 |
| --- | --- | --- |
| 文本、代码和配置 | C/C++、CSS、CSV、Go、HTML、Java、JS/JSX、JSON、日志、Markdown、Python、Rust、SQL、TS/TSX、TXT、XML、YAML | 256 KiB |
| 图片 | JPEG、PNG、WebP | 12 MiB |
| 文档 | PDF、DOCX、PPTX、XLSX | 20 MiB |
每条消息最多 8 个附件,Main 最多暂存 16 个上下文项目,合计不超过 12 MiB。
图片转换和文档解析后的内容计入暂存容量。一次粘贴超过 8 个文件时拒绝本次粘贴并提示;
批次本身未超限、但加上已有附件超过 8 个时,只保留剩余位置可容纳的附件并提示。
文件读取和解析期间显示文件名、处理阶段及批次序号,阻止重复文件导入和发送。
不支持的扩展名、目录、超限文件、读取或解析失败会使本批次回滚;已有附件与草稿保留,
错误显示在输入区。剪贴板中的虚拟文档若没有本地路径,提示先保存到本地后添加;
无本地路径的截图继续使用图片粘贴入口。
### 3.3 本地优先
- 文本层和本地 OCR 均在设备上处理;
@@ -249,8 +280,39 @@ ZIP 根目录包含模型文件和 `goodbuddy-model.json`。清单格式为 `goo
受密码保护、损坏或超限的 PDF 不得进入 OCR。
### 7.5 PPTX 图片流程
自动解析、完整索引和高保真工作流保留幻灯片原生文字,同时对幻灯片直接引用的
内嵌 PNG、JPEG、WebP 图片运行本地 OCR。即使幻灯片已有标题或正文,也不会因此
跳过图片中的文字。快速文本、快速索引工作流不运行 OCR;图片型 PPTX 在这些模式下
明确提示当前工作流未启用 OCR。
按演示文稿中的幻灯片顺序返回结果,图片识别结果标注 `幻灯片 N · 图片 M`,
保留页码和识别置信度。OCR 页数限制计算含图片的幻灯片数,而不是图片总数;
图片顺序处理并复用已加载模型,沿用现有超时、取消和输出限制。
这不是 Office 渲染或转换:不识别母版、版式中继承的图片,不重建裁剪、旋转或遮挡后的
完整画面,不把演讲者备注当作幻灯片正文,也不下载外链图片。其他内嵌图片格式在
需要 OCR 时明确报错。自动聊天解析可在 OCR 失败时保留可靠原生文字并附带警告;
图片型文件没有可靠原生文字时仍失败,知识库和高保真工作流不以此回退掩盖 OCR 失败。
### 7.6 OCR 空闲资源回收
OCR 队列清空且没有正在处理的请求后,保留 Worker 60 秒,以复用连续文档的模型。
期间收到新请求立即取消回收计时;请求完成、队列再次空闲后重新计时。空闲到期后
销毁 Worker,释放其模型会话和 WASM 运行环境;下一次请求按需重新加载已安装模型。
回收不会删除模型文件或已经解析的附件文字,不打断正在识别、初始化或排队的任务。
取消、超时和服务关闭仍按原有路径立即清理。操作系统显示的进程内存不保证立刻恢复到
启动值,不能仅凭一次内存快照判断是否发生泄漏。
## 8. Office 与转换
本节只定义 Office 文件的语义解析和格式转换,不提供可视化编辑器、编辑会话、选区、撤销或
保存回写。通过 ShareServer 集成浏览器编辑器的设计见
[Office 协同编辑](../office-document-editing/README.md)。编辑服务可以复用转换产物,但不能
替代本地优先的聊天附件和知识库解析路径。
### 8.1 新格式
- DOCX:正文、标题、表格、批注和图片关系;
@@ -328,6 +390,7 @@ DOC、XLS、PPT 通过 `DocumentConversionProvider` 转换:
## 12. 验收
- 同一份扫描 PDF 可从聊天附件和知识库得到一致的逐页文本;
- 图片型 PPTX 在自动解析下使用已配置的本地 OCR,并保留幻灯片来源定位;
- 文本型 PDF 在 `auto` 模式下不运行 OCR;
- 本地 OCR 在六个平台和两种架构上完全离线运行;
- 模型文件损坏时拒绝加载并显示可恢复错误;
@@ -338,6 +401,7 @@ DOC、XLS、PPT 通过 `DocumentConversionProvider` 转换:
- 语音和 OCR 模型可在联网设备导出 ZIP,并在离线设备导入后完成真实推理;
- 路径穿越、未知条目、错误模型 ID、篡改文件和超限 ZIP 均被拒绝;
- 超页数、超时、取消和关闭不会留下运行任务;
- OCR 空闲 60 秒后释放 Worker,连续请求复用模型,回收后的下一次解析可重新加载模型完成;
- 测试解析不会创建聊天消息或知识库文档;
- 选择本地模型时没有任何文档上传;
- 文档中的提示词不会改变系统、模式或工具权限。
+34
View File
@@ -0,0 +1,34 @@
# 图片生成与连续修改
本目录维护直连图片模型的生成、参考图编辑和会话续接行为。这是已有生图流程的修复,
不增加独立模型、工具审批或服务端会话管理。
## 产品行为
- 首次无附图时生成新图片。同一桌面对话继续请求时,自动引用最近一次成功回复的图片,
同时发送此前的文字要求;本轮修改要求优先。
- 本轮显式附图优先,不自动混入上一轮生成图。要独立生成不相关图片,使用新对话。
- 已保存会话重新打开后仍可继续改图,引用已有成果,不依赖 Runtime 内存缓存。
- 上游明确不支持编辑时,继续按文字生成,并在该回复底部说明未使用参考图片。
缺失历史成果时使用剩余可用上下文并提示,不禁用发送。
- 网络、鉴权、限流、参数错误和真正的生成失败仍正常报错,不伪装成能力缺失。
- 模型 ID、地址和质量服从用户的模型连接配置,不对特定服务商或模型别名做硬编码。
验证以用户指定的 `sub-gpt-image-2` 实际服务能力为准,不由其他模型的失败推断其能力。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [技术设计](./technical-design.md) | 请求、图片引用、协议与失败处理 |
| [UI 设计](./ui-design.md) | 消息底部提示及交互边界 |
| [功能进度](./progress.md) | 已验证事实和剩余验收 |
本次只补齐已有工作流,产品规则集中于本页,不另建重复的 PRD、User Stories 或状态机文档。
## 相关功能
- [会话媒体生成设计](../conversation-media-generation/README.md):规划普通 LLM 通过工具调用图片、视频模型及模型切换行为,尚未实现。
- [模型连接](../model-connections/README.md):地址、模型 ID、凭据与请求定制。
- [直连文本 Agent](../direct-model-agent/README.md):文本模型的工具和上下文压缩,
不等于图片模型支持 Responses 图片工具。
- [远程主机](../remote-host/README.md):托管 SSH 当前使用 OpenCode,不运行本直连图片路径。
@@ -0,0 +1,27 @@
# 图片上下文验证记录
## 2026-09-10
- 已接线桌面会话成果引用 → Main 已存图片 → Images 编辑,并复用文字历史。
- 已增加消息底部的能力缺失提示及 SQLite metadata 持久化,无新增表或服务端会话状态。
- 图片上下文专项测试 20 项通过;IPC 成果引用和提示透传专项测试通过。
- Renderer 现有生图测试扩展为验证纯图片消息后的追问及提示;消息组件验证提示不产生
alert 或 live region;数据库回归验证提示 metadata 保存。
- Windows 隔离桌面实例通过真实发送按钮调用用户指定的 `sub-gpt-image-2`:
首次生成红圆和绿方块;不重新附图即可把圆改蓝;终止并重开整个测试 Electron 进程后,
再次不附图即可保留蓝圆并把方块改黄。实际路由依次为 generations、edits、edits,
三次均 HTTP 200,图片颜色和成果恢复已检查,正常续接不出现能力缺失提示。
- 在同一个测试实例的请求边界注入一次明确的编辑 HTTP 405,验证生产 Runtime 自动发出
一次真实 generations 请求并成功生成第四张图片。消息底部提示未使用参考图片,没有
错误状态、alert 或 live region;重新加载会话后提示仍然保留。这是缺失能力的模拟场景,
不表示实测的 `sub-gpt-image-2` 上游缺少编辑能力。
- 本次实现验收共 4 次真实图片请求、0 次真实文本请求;另有 1 次本地模拟的编辑拒绝。
此前接口研究阶段的 8 次请求不计入这 4 次。
- 当前源码通过 electron-vite 开发版 Main/Preload 构建及真实 Renderer 链路;本次没有
修改生产构建配置,未运行发布打包或独立生产构建。
- `npm run typecheck`、`npm run lint` 通过;新文档 7 个本地链接检查通过。
- `npm test`:3896 passed、66 skipped、2 failed。失败项为 `App.test.tsx` 中
`switches immediately to a managed SSH project and shows only supported OpenCode choices`
和 `shows one configured choice per Agent Runtime in a flat keyboard menu`,对应工作区
中并行 Runtime 菜单变更的选项数量断言,单独复测仍失败;不在本次图片修复中改写。
图片专项、IPC、消息组件和数据库相关测试通过,图片 App 专项也已独立复测通过。
@@ -0,0 +1,49 @@
# 图片上下文技术设计
## 请求与存储
Renderer 从当前完整会话快照选择最近一条 `complete` 助手消息的 `artifactIds`,通过
`AgentRequest.imageContextArtifactIds` 传递最多 8 个成果 ID。图片消息可以没有正文,
因此引用选择不使用过滤后的文字历史。队列在实际发送时使用当时的会话快照。
普通文本历史契约保持 `role/content`;图片字节不进入历史 IPC,也不新增图片副本。
Main 仅在 `capability === 'image-generation'` 时调用 `withImageConversationContext`。
当前 `ContextManager` 已解析的显式附图优先;否则通过现有 `getArtifact` 加载引用的
内联 PNG/JPEG,WebP 使用 Electron 解码为 PNG。缺失或不可用引用形成上下文提示,
数据库等基础设施错误仍向上传递。读取不依赖会话的异步保存进度,因为成果已在发送
`artifact` 事件前由 Main 保存。
生成图片继续由 `createImageArtifact` 保存。可选 `imageContextNotice` 随生成事件、
公开成果事件和消息 metadata 保存,不新增表、迁移、服务端 ID 或 Runtime 会话缓存。
它是该回复的展示事实,不进入下一轮提示词。
## 上游请求
`ModelAgentRuntime` 将已有文字历史作为会话数据加入提示词,并把本轮要求放在末尾。
不从头截断组合提示词而丢掉最后的修改要求;服务商自己的长度和资源限制仍然有效。
有图片使用 `/images/edits`,单图字段为 `image`,多图为 `image[]`;无图使用
`/images/generations`。质量、认证及自定义请求字段继续走原有配置合并。
本路径不接入 Responses 图片工具、不依赖 `previous_response_id`,不额外调用文本模型。
显式回传上下文不表示上游提供或保存了服务端会话;无法据此保证像素级不变。
结果仅接受原有校验通过的内联图片,不下载 Provider 返回的图片 URL。
## 能力缺失与错误
编辑返回 HTTP 405/501,或 HTTP 400/404/422 的错误正文明确说明图片编辑不支持或未实现时,
同一请求最多再调用一次无图生成。成功成果携带 `editing-unavailable`,继续以 `done`
结束;再次失败正常报错,不循环回退。
其他错误保留 HTTP 状态和 Provider 信息。尤其不把 401/403、429、模型不存在、
无效图片、超时或服务暂不可用判作不支持编辑。取消发生后不启动后续生成。
本次不持久化能力探测结果、不切换模型、不新增重试框架。
## Remote 边界与验证
Renderer 只为直连模型附带成果引用,Main 只在图片能力分支使用它。
`managed-remote-acp-runtime.ts` 与远程项目 validator 均要求 OpenCode;
本修改不改变 Agent gateway、ACP 或远程模型请求,不需要部署新 Agent。
专项验证覆盖完整文字、纯图片消息引用、显式附图优先、成果恢复、会话隔离、
能力缺失提示、取消、真实错误及 UI/SQLite metadata。真实验收必须从桌面发送按钮进入
生产 IPC 和 Runtime,验证生成、连续修改及重新打开后的续接。
@@ -0,0 +1,13 @@
# 图片上下文提示
遵循根目录 [UI-DESIGN.md](../../../UI-DESIGN.md)。
- 仅在某次成功生成确实缺少部分上下文时,在对应助手回复底部显示低强调文字。
正常使用上下文不显示警告,也不声称上游“不支持上下文”。
- `reference-unavailable`:部分历史图片无法读取,本次仅使用可用的图片与文字上下文。
- `editing-unavailable`:上游不支持图片编辑,本次按文字要求生成,未使用参考图片。
- 使用共享 `--text-muted`、`--font-caption` 和间距令牌。不加警告卡片、闪烁、
`alert`、`aria-live`、弹窗或应用通知。文本保留在消息中并随会话恢复。
- 发送和生成继续可用;真实请求失败仍采用原来的错误及重试交互,不生成成功提示。
- 文案具备简体中文、英文两套翻译。本轮显式附图和新对话继续使用现有入口,
不增加模式开关或第二次确认。
@@ -156,6 +156,8 @@ Renderer 不预览、不读取也不接收诊断内容或路径。勾选后详
### 3.4 可访问性
- 对话框使用 `role="dialog"`、`aria-modal="true"` 和可见标题。
- 整窗遮罩通过 React portal 挂到 `document.body`,遵循
[全局遮罩与原生拖动区域规则](../../../UI-DESIGN.md#610-应用顶栏与全局操作)。
- 复用 `activateModalFocus()` 与 `trapTabFocus()`,打开后聚焦反馈类型,关闭后恢复入口
按钮焦点。
- 非提交状态支持 Escape 关闭;提交中保持对话框打开,直到请求完成或到达有界超时。
+14 -5
View File
@@ -1,8 +1,12 @@
# 知识库
知识库已提供本地资料导入、解析、分块、索引、检索、图谱和带来源的知识问答。
将 Dify、FastGPT 和 RAGFlow 中已有知识库接入同一检索范围仍处于规划阶段,不属于
当前已提供能力。
当前工作区已接入 Dify、FastGPT 和 RAGFlow 的实例管理、外部绑定、检索配置、统一检索、
聊天引用与界面。2026-09-13 已验证三家 Provider 的生产服务、本机 HTTP MCP 和生产 IPC
短答案生成,并从完整 App 输入区完成 Dify 检索、真实回答及引用点击。外部实例弹窗已修正
Portal 字段样式,并完成浅深主题和键盘检查。当前 UI 修正后的聚焦测试、全量回归及相关检查已通过;
FastGPT/RAGFlow 完整 App 与专属配置、真实远程预检索仍待验收。验证只覆盖有依据的短答案,
不代表广泛问答质量,详见[实施进度](./progress.md)。
## 文档导航
@@ -10,7 +14,7 @@
| --- | --- |
| [知识库检索与分块增强 PRD](./prd.md) | 检索、分块、诊断和产品验收 |
| [User Stories](./user-stories.md) | 知识使用者、维护者和调试者场景 |
| [UI 设计](./ui-design.md) | 当前本地知识库的信息架构、首次使用、状态与高级功能分层 |
| [UI 设计](./ui-design.md) | 本地知识库的信息架构、首次使用、状态与外部界面分工 |
| [实施进度](./progress.md) | 已验证实现事实、验证命令与剩余外部知识库工作 |
| [本地文本向量技术设计](./local-embedding-technical-design.md) | 本地与兼容向量连接、模型包、索引和进程边界 |
| [知识检索评估](./retrieval-evaluation.md) | 检索质量评估方法、数据集、指标和门槛 |
@@ -18,7 +22,12 @@
| [外部知识库 User Stories](./external-knowledge-user-stories.md) | 实例配置、添加、使用和故障恢复场景 |
| [外部知识库逻辑设计](./external-knowledge-logic-design.md) | 状态、不变量、配置优先级和失败规则 |
| [外部知识库 UI 设计](./external-knowledge-ui-design.md) | 知识库页面、外部实例管理和创建向导交互 |
| [外部知识库技术设计](./external-knowledge-technical-design.md) | Provider Adapter、凭据、持久化、IPC 和检索标准化 |
| [外部知识库技术设计](./external-knowledge-technical-design.md) | 当前 Client、Service、Store、共享契约、IPC 和检索标准化 |
| [外部知识库实测基线](./external-knowledge-probe-baseline.json) | 脱敏记录真实实例的接口状态、字段结构、特色能力和耗时 |
| [2026-09-10 基础复测](./external-knowledge-probe-2026-09-10-strict.json) | 开启证书校验的目录、详情和基础检索结果 |
| [2026-09-10 扩展复测](./external-knowledge-probe-2026-09-10-extended.json) | 配置覆盖、重排、图谱与知识编译开关的请求结果及结构 |
| [2026-09-13 产品验证摘要](./external-knowledge-validation-2026-09-13.json) | 生产服务与本机 HTTP MCP、Dify 桌面、凭据重开、调用计数、最终自动检查状态及远程阻断的脱敏证据 |
| [2026-09-13 UI 与真实生成复验](./external-knowledge-follow-up-2026-09-13.json) | 后续 UI 修正、三家生产 IPC 生成、Dify 完整 App 回答与引用、视觉 fixture 及本轮请求计数 |
## 术语
@@ -31,4 +40,4 @@
或进入本地索引;实际用于回答的有界片段会随引用保存在本地会话中。
本目录尚未拆出统一逻辑设计;当前本地知识库状态规则由 PRD、User Stories 和实现契约
共同约束。外部知识库继续使用独立逻辑设计,直至进入实施阶段。
定义。外部知识库的实例、绑定和失败规则由独立逻辑设计维护。
@@ -0,0 +1,106 @@
{
"date": "2026-09-13",
"kind": "sanitized-ui-and-real-generation-follow-up",
"sources": ["kb-real-generation/report.json", "kb-full-app-live/report.json", "external-visual-0913/results.json", "goodbuddy-binding-selector-fix-full.log", "goodbuddy-binding-selector-fix-full.result.json"],
"automatedChecks": {
"status": "passed-after-ui-follow-up",
"authoritativeCounts": "progress.md",
"focusedWholeAppAndTwoAgentTests": { "status": "passed", "evidence": "owner handoff" },
"fullSuite": {
"command": "npm test -- --maxWorkers=1 --reporter=verbose",
"status": "passed",
"exitCode": 0,
"evidence": ["goodbuddy-binding-selector-fix-full.log", "goodbuddy-binding-selector-fix-full.result.json"]
},
"typecheck": { "command": "npm run typecheck", "status": "passed", "evidence": "owner handoff" },
"lint": { "command": "npm run lint", "status": "passed", "evidence": "owner handoff" },
"workingTreeDiffCheck": { "status": "passed", "evidence": "owner handoff" },
"stagedDiffCheck": { "status": "passed", "evidence": "owner handoff" },
"sourceStability": {
"scope": "App test, two Agent tests and ExternalKnowledge stable throughout final run",
"evidence": "owner handoff"
},
"lastTestAdjustment": "App test changed from obsolete native select interaction to actual accessible rich menu interaction; no product source change",
"agentFailureResolution": "resolved in concurrent owner's latest code; no edits by this docs task",
"furtherSourceChanges": "none planned per owner handoff"
},
"model": {
"configuration": "saved default model settings; credential resolved in Main using safeStorage",
"transport": "http",
"originalSettingsUnchangedInFullAppRun": true,
"redaction": "No endpoints, private profile identifiers, keys, business text, answer prose or content hashes retained"
},
"productionIpcGeneration": {
"realElectronPreloadIpcAndModelRuntime": true,
"fullAppRenderer": false,
"referenceDisplay": "isolated validation DOM receiving real preload events",
"persistence": "production task_events; text, single-library scope and external locators verified",
"scenarios": [
{ "provider": "dify", "mode": "always", "passed": true },
{ "provider": "fastgpt", "mode": "always", "passed": false, "groundedQuote": true, "citationMarker": true, "failure": "initial raw-string evidence assertion did not account for serialization" },
{ "provider": "ragflow", "mode": "always", "passed": false, "groundedQuote": true, "citationMarker": true, "failure": "initial raw-string evidence assertion did not account for serialization" },
{ "provider": "dify", "mode": "auto", "passed": true, "transport": "ModelToolProvider to KnowledgeMcpGateway in process; not HTTP MCP", "tool": "knowledge_search" },
{ "provider": "fastgpt", "mode": "always", "passed": true, "evidenceCheck": "decoded provider body and embedded evidence JSON" },
{ "provider": "ragflow", "mode": "always", "passed": true, "evidenceCheck": "decoded provider body and embedded evidence JSON" }
],
"finalScenarioChecks": ["nonempty short answer", "grounded quote", "citation marker", "reference schema", "text and references persisted"],
"requests": { "model": 7, "kbTotal": 12, "kbRetrieval": 9, "kbCatalog": 3 },
"retrievalBreakdown": { "bindingVerification": 3, "forcedPreflight": 5, "autoScoped": 1 },
"runtimeModelRetries": 0,
"strictHttpsProviders": ["dify", "fastgpt"],
"httpProviders": ["ragflow"]
},
"fullApp": {
"provider": "dify",
"mode": "always",
"workMode": "ask",
"currentProductionMainPreloadRenderer": true,
"instanceAndPositiveBindingCreatedInUi": true,
"composerSelectionAndSendClicked": true,
"noFabricatedMessagesReferencesOrEvents": true,
"mainOnlyKbCredentialSubstitution": true,
"generationDone": true,
"answerPersisted": true,
"singleSelectedLibrary": true,
"liveEvidencePresentInModelInput": true,
"groundedShortAnswerWithCitation": true,
"citationClicked": true,
"citationSnippetDocumentAndChunkMatch": true,
"citationFocusInside": true,
"strictDifyHttps": true,
"modelHttp": true,
"requests": { "model": 1, "kbTotal": 5, "kbRetrieval": 3, "kbCatalog": 1, "kbDetail": 1 },
"setupVerificationRetrievals": 2,
"composerRetrievals": 1,
"toolExecutions": 0,
"portalEditor": { "themes": ["light", "dark"], "widthPx": 560, "sharedTypography": true, "withinViewport": true, "bodyOverflow": 0 },
"isolatedProfileAndBuildRemoved": true
},
"visualFixture": {
"boundary": "current source components and styles in real Electron with real body portal; fake local IPC data only",
"themes": ["light", "dark"],
"viewportSizes": [[1280, 800], [960, 720], [720, 640], [640, 420]],
"screenshots": 56,
"checksPassed": 138,
"errors": [],
"keyboardMethod": "native Chromium actions via Electron sendInputEvent and insertText",
"coverage": ["password reveal layout", "switch keyboard toggle", "sticky footer visibility", "focus wrap and restoration", "instance menu arrows and disabled items", "tabs", "credential replacement and clear confirmation", "selected hover", "empty state"]
},
"followUpRequestAccounting": {
"modelRequests": 8,
"kbRequests": 17,
"kbRetrievalRequests": 12,
"kbCatalogRequests": 4,
"kbDetailRequests": 1,
"boundary": "this follow-up only; app HTTP dispatches, not upstream proxy internals or provider embedding/reranking usage",
"cumulativeReportFieldsExcluded": "real-generation report cumulative fields omit prior desktop retrievals; not used"
},
"acceptanceBounds": {
"groundedShortAnswerPath": "passed for three providers through production IPC and Dify through full App composer",
"broadAnswerQuality": "not evaluated",
"fastgptRagflowFullAppAndProviderSpecificUi": "pending",
"linuxRemoteForcedPreflight": "still blocked by prior saved SSH credential decryption failure",
"fullRegressionAfterUiFollowUp": "passed",
"authoritativeAutomatedTestCounts": "progress.md"
}
}
@@ -4,340 +4,171 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | 当前实现规则;三家短答案、Dify 完整 App 与当前 UI 自动回归已通过,远程待验收 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 关联需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 关联场景 | [外部知识库 User Stories](./external-knowledge-user-stories.md) |
| 技术契约 | [技术设计](./external-knowledge-technical-design.md) |
## 1. 不变量
### EK-I-01 单一知识库身份
本地知识库和外部知识库都使用 GoodBuddy `knowledgeBaseId` 参与聊天范围、检索模式、
工具授权和活动记录。Provider、实例 ID 和远端知识库 ID 不能替代该授权单位。
对应:`EK-FR-10`、`EK-FR-11`、`EK-US-C1`。
### EK-I-02 外部绑定只读
GoodBuddy 对外部实例只执行连接测试、目录读取、详情读取和检索。添加、编辑、移除
均只修改本机记录,不调用远端创建、更新、上传或删除接口。
对应:`EK-FR-15`、`EK-US-A3`。
### EK-I-03 凭据不离开 Main
API Key 明文只在 Main 内完成解密并短暂进入请求头。Renderer、Preload 返回值、Runtime、
Provider 配置、知识库元数据、诊断、日志和错误只能获得凭据状态。
对应:`EK-FR-02`、`EK-FR-11`。
### EK-I-04 Provider 不能定义界面
远端响应只提供数据和能力证据。可显示字段、字段类型、范围、默认值和请求映射均来自
GoodBuddy 内置、版本化的 Provider Adapter。
对应:`EK-FR-09`、`EK-US-D1`。
### EK-I-05 无静默切换
请求固定使用绑定保存的实例、远端知识库和 Provider 配置。失败时不切换实例、公共云
地址、认证方式或其他知识库。
对应:`EK-FR-14`、`EK-FR-16`、`EK-US-C4`。
### EK-I-06 零结果与失败分离
成功响应经过过滤后没有片段才是零结果。超时、认证、权限、限流、目标不存在、服务错误
和响应无效均为失败,不能折叠成零结果。
对应:`EK-FR-14`、`EK-US-D4`。
### EK-I-07 外部证据不可信
Provider 返回的名称、内容、元数据、URL 和错误均视为不可信输入,经过长度、数量、类型
和 URL 校验后才能显示或进入 Runtime。内容中的指令不能改变系统或工具权限。
对应:`EK-FR-12`。
### EK-I-08 Provider 配置不跨类型复用
Dify、FastGPT 和 RAGFlow 使用各自的判别配置。切换知识库类型或实例 Provider 时,旧类型
配置不参与新请求,也不能按同名字段自动迁移。
对应:`EK-FR-06`、`EK-FR-09`。
| 稳定 ID | 规则 | 对应需求 |
| --- | --- | --- |
| `EK-I-01` 单一知识库身份 | 本地与外部都以 GoodBuddy `knowledgeBaseId` 参与聊天范围与工具授权,实例和远端 ID 不扩大选中范围 | `EK-FR-10`、`EK-FR-11` |
| `EK-I-02` 外部绑定只读 | 添加、配置和移除仅修改本机记录;请求限于目录、详情、检索 | `EK-FR-15` |
| `EK-I-03` 凭据边界 | 新密钥在输入表单和保存 IPC 中短暂存在;加密后只在 Main 解密使用,摘要与引用不返回密钥 | `EK-FR-02`、`EK-FR-11` |
| `EK-I-04` 本地定义界面 | Provider 只返回数据,控件和配置由本地共享契约及组件定义 | `EK-FR-09` |
| `EK-I-05` 无静默切换 | 请求使用保存的实例、目标和配置;失败不更换实例、公共云地址或其他知识库 | `EK-FR-14`、`EK-FR-16` |
| `EK-I-06` 零结果与失败分离 | 合法成功响应无片段是零命中;请求或响应失败保留失败信息 | `EK-FR-14` |
| `EK-I-07` 外部证据不可信 | 标准化片段按已有知识证据边界进入 Runtime,不改变工具权限 | `EK-FR-12` |
| `EK-I-08` 配置不跨类型复用 | Provider 配置必须与实例一致,类型切换不迁移同名字段 | `EK-FR-06`、`EK-FR-09` |
## 2. 对象关系
```text
ExternalKnowledgeInstance 1 ─── n ExternalKnowledgeBinding
│
│ 1:1
▼
KnowledgeBase
```
一个实例可以有零个或多个绑定;一个绑定对应一个 GoodBuddy 知识库和一个远端目标。
同一实例与远端 ID 的组合唯一,不同实例可以使用相同 ID。本地知识库没有外部绑定。
类型由绑定关系推导,旧本地知识库不需要改成另一套对象。
- 实例可以没有绑定。
- 外部知识库只能绑定一个实例和一个远端知识库 ID。
- 同一实例的同一远端知识库默认只允许存在一个绑定,避免重复选择和重复检索。
- 允许不同实例绑定同名或相同远端 ID,因为实例边界不同。
- 本地知识库不存在 `ExternalKnowledgeBinding`。
GoodBuddy 显示名称与 `remoteName` 分开保存。刷新详情仅更新当前视图,不自动改写本地
名称或持久化远端名称;重新保存绑定时才写入当前表单使用的远端名称。
## 3. 实例状态
## 3. 实例状态与连接测试
实例状态由启停、凭据和最近探测三个维度组成,不能用一个布尔值代替。
### 3.1 持久状态
| 维度 | 值 |
| 状态维度 | 当前值与行为 |
| --- | --- |
| 启停 | `enabled`、`disabled` |
| 凭据 | `configured`、`missing`、`unavailable` |
| 探测 | `untested`、`catalog-ready`、`healthy`、`list-restricted`、`auth-failed`、`unreachable`、`incompatible`、`failed` |
| 启停 | `enabled` 为 false 时 Main 拒绝目录和检索,并取消当前实例请求 |
| 凭据 | `configured`、`missing`、`unavailable`;列表尝试解密判断可用性 |
| 最近探测 | `untested`、`catalog-ready`、`healthy`、`list-restricted`、`auth-failed`、`unreachable`、`failed`;其中 `healthy` 在类型中保留,当前操作没有写入它 |
`unavailable` 表示系统安全存储当前无法解密或保存凭据,与用户尚未配置的 `missing` 分开。
实例先保存为 `untested`,不要求保存前完成网络或认证测试。保存、替换或清除凭据后
重置探测状态。保存后的“测试连接”只尝试目录读取:成功写入 `catalog-ready`;403
写入 `list-restricted`;401 写入 `auth-failed`;网络或超时写入 `unreachable`;其他错误
写入 `failed`。时间和错误码随本次结果保存。
### 3.2 派生可用性
探测结果是最近一次观察,不是 Main 的永久禁用条件。Main 请求检查实例是否存在、启用
和凭据能否解密;即使最近为 `auth-failed` 或 `failed`,仍可重新请求。UI 会据该状态
显示不可用并禁用聊天勾选,用户可编辑或重新测试实例。
| 条件 | 目录读取 | 已绑定知识库检索 | 页面状态 |
| --- | --- | --- | --- |
| disabled | 禁止 | 禁止 | 已停用 |
| missing / unavailable | 禁止 | 禁止 | 缺少凭据 / 凭据不可用 |
| auth-failed | 禁止 | 禁止 | 认证失效 |
| unreachable | 可重试 | 可重试 | 无法连接 |
| incompatible | 禁止 | 禁止 | 接口不兼容 |
| list-restricted | 禁止 | 允许按 ID 验证与检索 | 需手工指定知识库 |
| catalog-ready | 允许 | 创建绑定时验证 | 目录可用,检索待验证 |
| healthy | 允许 | 允许 | 正常 |
| untested | 允许测试 | 已有绑定可尝试检索 | 未测试 |
绑定创建另做目标检索验证,成功零命中可保存。保存实例不代表目录可用,目录成功也不
代表绑定检索或整个产品验收通过。
保存新实例要求至少完成服务可达和认证验证。目录受限可以保存;检索接口尚未通过时,
实例不能用于创建绑定。
## 4. 目录与手工 ID
## 4. 连接测试状态机
目录每页最多 100 项,UI 当前请求 30 项,页码上限 500;当前没有“总目录最多 500 项”
的累计限制。Dify/RAGFlow 由远端分页,搜索过滤当前页;FastGPT 按 `parentId` 读取
当前父级,在返回结果内搜索和分页。文件夹只能进入浏览,不能作为目录绑定目标。
```text
idle
└─ test → connecting
├─ network failure → unreachable
└─ connected → authenticating
├─ 401 → auth-failed
├─ incompatible response → incompatible
└─ authenticated → listing
├─ 403 / unsupported → list-restricted
└─ success
├─ no target → catalog-ready
└─ target selected → probing-retrieval
├─ success / empty → healthy
└─ failure → failed
```
目录加载、空结果、搜索无结果和失败分别展示;失败提供重试,用户可主动切换手工 ID。
已经绑定的目录项禁用。手工 ID 不依赖详情接口一定可用,但保存前仍必须通过实际检索。
详情失败不阻断基础检索,例如旧 Dify 的详情 405。
检索探测需要一个目标知识库。新增实例尚未选择目标时,目录成功即可把实例保存为“目录可用,
检索待绑定验证”;创建绑定时补做真实检索。手工模式必须先填写目标 ID,再执行检索探测。
检索请求正常完成但返回零条结果时,说明接口和目标可访问,可继续创建绑定;失败状态按错误
分类处理。
Renderer 丢弃已失效的目录、详情和测试响应,不把切换前的结果写回当前表单。它没有
通过 IPC 取消已发出的目录请求,Main 请求仍受超时和服务生命周期管理。
每次测试拥有 request ID 和取消信号。用户修改 Provider、地址、凭据或实例后,旧 request ID
的结果作废。
## 5. 创建与编辑
## 5. 目录读取规则
新建绑定时,用户尚未编辑的显示名称跟随当前目录选择更新;更换实例或手工输入另一个
目标 ID 时清除旧自动名称。用户手动填写过名称后保留该草稿。编辑或重新绑定已有库时,
始终保留已保存的本地名称,除非用户主动修改;此修正不自动重命名已有知识库。
1. 只有选定、启用且凭据可用的实例可以读取目录。
2. Provider 列表接口按适配器定义分页,Main 最多向 Renderer 返回 500 个目录项。
3. FastGPT 列表中的文件夹和知识库必须按类型区分;文件夹不可作为绑定目标。
4. 列表搜索优先使用 Provider 支持的服务端过滤,不支持时只过滤已经有界加载的当前结果。
5. 下拉列表以远端 ID 为值,以远端名称为标签;重名时追加短 ID 或父级名称。
6. 已经绑定的目标显示“已添加”并禁止重复添加。
7. 远端列表项名称变化不自动修改 GoodBuddy 显示名称,只更新远端名称快照。
8. 刷新失败时可以保留上次成功目录供查看,但不得用旧目录完成新绑定,直到目标重新验证。
流程为选择 `local | dify | fastgpt | ragflow`,外部类型再选择实例、目录项或手工 ID、
显示名称、配置与测试查询。表单测试成功后允许保存;服务保存前再次执行同一查询验证,
检查实例、绑定和唯一性是否在等待期间改变,再写入数据库。
## 6. 创建向导状态
### 6.1 类型
```text
local | dify | fastgpt | ragflow
```
`local` 使用现有字段和验证。外部类型使用以下步骤状态:
```text
provider selected
→ instance selected
→ catalog loading / manual ID
→ remote target selected
→ configuration valid
→ retrieval verified
→ binding created
```
前置字段变化时,后续状态按下表失效:
| 变化 | 必须清除或重新验证 |
| 表单变化 | 当前失效规则 |
| --- | --- |
| 类型 | 实例、远端目标、全部 Provider 配置、测试结果 |
| 实例 | 远端目标、能力快照、Provider 配置、测试结果 |
| 远端目标 | 远端详情、能力适用性、测试结果 |
| 通用或 Provider 配置 | 测试结果 |
| 显示名称 | 不清除测试结果 |
| Provider 类型 | 重新挂载绑定表单,清空旧类型状态 |
| 实例 | 清空目标、详情、目录位置和搜索,恢复该 Provider 默认配置,清除测试结果 |
| 远端目标、测试查询、通用或 Provider 参数 | 清除测试结果,需要重新测试 |
| 显示名称 | 保留测试结果 |
| 已保存实例摘要变化 | 旧测试结果不再视为有效 |
| 更换现有绑定实例或远端目标 | 需要重新测试和重新绑定确认 |
## 7. 配置模型
成功新建后仅把新库加入当前对话选择,不重新启用已取消的其他知识库。当前表单保存期间
禁用字段和操作;generation 检查用于避免旧响应更新 UI,不构成可取消的保存事务。
### 7.1 通用配置
## 6. 配置与优先级
| 字段 | 范围 | 默认值 | 语义 |
| --- | --- | --- | --- |
| `resultLimit` | 1 至 20 | 6 | Provider 结果标准化后最多保留的片段数 |
| `requestTimeoutMs` | 1,000 至 60,000 | 15,000 | 单次远端请求总时限 |
| `maxSnippetCharacters` | 500 至 8,000 | 4,000 | 单片段进入 IPC 和 Runtime 前的字符上限 |
当前配置的精确字段、数值范围和默认值由[技术设计第 3 节](./external-knowledge-technical-design.md#3-共享契约)
对应的共享 Schema 定义。恢复默认只重置本地表单,保存成功后才影响该绑定后续请求。
Provider 请求仍使用自己的候选数量或 Token 预算。`resultLimit` 只定义 GoodBuddy 最终保留
上限,不改写 Provider 字段的单位。
Dify `useDatasetDefaults=true` 时省略整个覆盖对象,使用远端当时配置;远端管理员修改
默认值会影响后续结果。关闭后按完整覆盖对象发送,不与远端默认逐字段合并。
FastGPT 使用本地保存的模式、Token 预算、相似度和重排开关,不开放查询优化。
### 7.2 Dify 配置
RAGFlow 图谱与知识编译分别保存且默认关闭。图谱完成时间有效才允许开启图谱;非空
知识编译模板组 ID 才允许开启知识编译。服务在发送启用请求前重新读详情确认;仅配置
GraphRAG 或请求返回 200 都不能替代对应能力证据。证据不足时 UI 仍允许关闭已保存的开关。
| 字段 | 初始范围 | 说明 |
当前没有自动版本识别、Adapter 版本矩阵或配置迁移状态。共享契约拒绝未知输入与
Provider 错配,不能据此承诺所有部署版本均支持每个显式参数。
## 7. 绑定可用性
绑定只持久化配置和 `lastVerifiedAt`,没有独立 `binding_status`、最后成功检索时间或
远端缺失状态字段。详情与列表当前按实例推导:
| 条件 | UI 状态 |
| --- | --- |
| 实例不存在 | `temporarily-unavailable` |
| 实例停用 | `instance-disabled` |
| 凭据未配置、无法解密或最近认证失败 | `credential-error` |
| 最近探测失败或不可达 | `temporarily-unavailable` |
| 其余且绑定有验证时间 | `ready` |
| 其余且无验证时间 | `untested` |
聊天选择器保留已选 ID,但不可用行禁用勾选。检索时的 401、404、超时等通过本次失败
报告,不自动写成持久 `remote-missing/config-invalid` 状态。`ready` 表示当前可尝试使用,
不保证下一次请求成功。
## 8. 多库检索与失败
请求使用已冻结的 GoodBuddy ID 集合,`retrieveMany` 去重后并行检索各库。本地与外部
共享入口,外部库不触发本地 Embedding。预算按各库结果的 rank 轮转分配,先各库第 1 条、
再各库第 2 条,同 rank 按输入库顺序处理;不先耗尽整个库,也不比较 Provider 原始分数。
当前没有每库最低字符配额。精确预算见
[技术设计第 7 节](./external-knowledge-technical-design.md#7-检索编排)。
成功零命中返回空 results 且无 failure。单库错误返回 `diagnostics.failure`,其他库
仍可返回结果;上层据此区分部分失败与全部失败。MCP 在有失败且没有任何片段时返回
工具错误,即使另有成功零命中的库;预检索则保留降级诊断。用户取消或服务关闭会向上
传播取消,不把整次取消解释为成功零命中。
客户端错误码包括 `NETWORK/TIMEOUT/CANCELLED/AUTH/FORBIDDEN/NOT_FOUND/RATE_LIMITED/`
`INCOMPATIBLE/INVALID_RESPONSE/SERVER`,均以 `EXTERNAL_KB_` 为前缀。服务另报告凭据
不可用、停用、配置变化、配置错配、能力不可用、重复绑定和实例被引用。远端错误正文
不直接成为 UI 错误;部分本地校验错误仍是 Zod、TypeError 或 RangeError,不能承诺
所有失败都已有独立本地化诊断。
## 9. 删除与引用
停用和清除凭据保留绑定,UI 清除凭据需要确认。有绑定的实例不能直接删除;管理界面
列出受影响知识库并确认后,依次移除本地库再删实例。若中途失败,已成功移除的本地
记录保持删除,刷新列表后显示当前状态。任何一步均不发送远端内容删除请求。
历史引用保存当时的片段与外部定位,移除实例或绑定后仍能阅读。当前不重新读取远端
片段,不打开来源 URL,不查询实例是否已删除;因此没有自动“连接已移除”标签。
App 与 Main 引用处理共用 `knowledgeReferenceKey`,键包含远端库、文档和片段 ID、
定位及正文,避免外部结果缺少本地 ID 时被错误合并。弹窗只显示优先选中的一个定位。
早期 Dify 历史引用保存和重载检查使用测试会话;后续完整 App 已从实际输入区生成真实
短答案并点击引用验证。两轮证据分别保留,不把测试会话当成模型回答。
## 10. 需求追踪与完整性
| 需求 | 规则 | 场景 |
| --- | --- | --- |
| `useDatasetDefaults` | boolean,默认 true | 开启时不发送 `retrieval_model` 覆盖 |
| `searchMethod` | keyword / semantic / full-text / hybrid | 对应 Dataset Retrieve 检索方式 |
| `providerTopK` | 1 至适配器上限 | Dify 返回候选数量 |
| `scoreThresholdEnabled` | boolean | 是否向 Dify 发送分数阈值 |
| `scoreThreshold` | 0 至 1 | 开启阈值后生效 |
| `rerankingMode` | weighted-score / reranking-model | 覆盖检索配置时必填 |
| `rerankingProvider`、`rerankingModel` | 受支持的远端标识 | 选择模型重排时必填 |
| `keywordWeight`、`vectorWeight` | 0 至 1,总和为 1 | 加权混合模式时生效 |
| `embeddingProvider`、`embeddingModel` | 受支持的远端标识 | Dify 要求时从知识库详情选择或填写 |
| `EK-FR-01` 至 `EK-FR-05` | 实例状态、保存后测试、目录与手工 ID | `EK-US-A1`、`EK-US-A2`、`EK-US-B1`、`EK-US-B2` |
| `EK-FR-06` 至 `EK-FR-10` | 对象关系、创建编辑、配置与优先级 | `EK-US-B3`、`EK-US-B4`、`EK-US-D1`、`EK-US-D3` |
| `EK-FR-11` 至 `EK-FR-14` | 范围、可用性、结果与失败、引用 | `EK-US-C1` 至 `EK-US-C4`、`EK-US-D2`、`EK-US-D4` |
| `EK-FR-15`、`EK-FR-16` | 只读生命周期、凭据与请求边界 | `EK-US-A3`、`EK-US-C2` |
`useDatasetDefaults` 开启时,其余覆盖字段隐藏且不发送。关闭后必须收集当前 Dify 接口
构造 `retrieval_model` 所需的完整字段,缺少 Provider 或模型标识时不能保存。
### 7.3 FastGPT 配置
| 字段 | 初始范围 | 说明 |
| --- | --- | --- |
| `searchMode` | embedding / fullTextRecall / mixedRecall | 搜索方式 |
| `tokenLimit` | 适配器限制,最高 20,000 | Provider 返回内容预算,不等同结果条数 |
| `similarity` | 0 至 1 | 最低相关度 |
| `usingRerank` | boolean | 使用 FastGPT 重排 |
初始版本不开放 FastGPT 查询优化字段,避免把查询发送给 FastGPT 配置的 LLM。检索和重排
仍可能产生远端 Embedding、Rerank 用量及 API 审计记录,测试与保存配置时必须说明。
### 7.4 RAGFlow 配置
| 字段 | 初始范围 | 说明 |
| --- | --- | --- |
| `similarityThreshold` | 0 至 1 | 最低综合相似度 |
| `vectorSimilarityWeight` | 0 至 1 | 向量分数权重 |
| `knnTopK` | 1 至适配器上限 | KNN 候选数量,不使用已弃用 `top_k` |
| `rerankCandidatesCount` | 0 或正整数 | 重排候选数量,实例支持时显示 |
| `rerankId` | 可选受限字符串 | 远端重排模型 ID |
| `useKg` | boolean,默认 false | 仅在已有图谱证据成立时允许开启 |
| `includeKnowledgeCompilation` | boolean,默认 false | 实例支持时显式发送,禁止依赖服务端默认 true |
| `metadataCondition` | 后续受控编辑器 | 初始版本不接受任意 JSON 文本 |
`parser_config.graphrag.use_graphrag` 描述构建状态,不是检索参数。只有列表、详情或受控
探测提供已有图谱的证据时,界面才允许开启 `useKg`。Knowledge Compilation 是另一项
检索能力,必须与旧 GraphRAG 分别显示和保存。
## 8. 配置优先级
生效请求由以下信息组成:
```text
GoodBuddy 固定安全上限
∩ 当前 Adapter 支持矩阵
∩ 当前绑定保存的通用配置和 Provider 配置
= 本次请求配置
```
- 固定安全上限不可被实例或用户扩大。
- 远端知识库自身默认值只用于创建表单的建议值,不在每次请求时覆盖本地已保存值。
- Provider 增加新字段不会自动启用。
- 当前实例版本离开 Adapter 支持矩阵,或 Adapter 新版本无法解析已保存配置时,绑定标记
配置失效;用户确认前不重写配置。
- 服务端忽略未知字段不能视为成功。Adapter 只发送已知且受支持的字段。
## 9. 绑定状态
| 状态 | 条件 | 可进入聊天范围 |
| --- | --- | --- |
| `ready` | 实例可用,目标最近验证成功,配置有效 | 是 |
| `untested` | 迁移或新增后尚未完成真实检索 | 否 |
| `instance-disabled` | 实例停用 | 否 |
| `credential-error` | 凭据缺失、不可用或认证失败 | 否 |
| `remote-missing` | 远端目标返回不存在 | 否 |
| `config-invalid` | 已保存配置不再受支持 | 否 |
| `temporarily-unavailable` | 超时、限流或服务错误 | 是,但本次显示失败并允许重试 |
短暂故障不立即永久禁用绑定;认证失败、目标不存在和配置失效会改变持久状态。
## 10. 多知识库检索
1. 读取请求冻结的 GoodBuddy 知识库 ID 集合。
2. 本地库进入本地检索,外部库按实例和绑定构造独立请求。
3. 同一请求最多选择现有契约允许的 20 个知识库,并遵循全局并发上限。
4. 每个库单独记录成功、零结果、失败、耗时和截断。
5. 成功结果按知识库保留来源,不比较不同 Provider 的原始分数。
6. 上下文拼装使用现有总字符预算,在各成功知识库间采用确定性配额。
7. 全部失败时返回检索失败;至少一个成功时返回证据和部分失败诊断。
初始版本不对跨 Provider 结果运行统一学习型重排。后续如增加,必须保留原始 Provider
分数和重排来源,且单独定义评估数据。
## 11. 错误分类
| 稳定错误码 | 用户状态 | 持久影响 |
| --- | --- | --- |
| `EXTERNAL_KB_NETWORK` | 无法连接实例 | 无 |
| `EXTERNAL_KB_TIMEOUT` | 检索超时 | 无 |
| `EXTERNAL_KB_AUTH` | 认证失效 | 实例标记认证失败 |
| `EXTERNAL_KB_FORBIDDEN` | 没有访问权限 | 目录或绑定按操作标记受限 |
| `EXTERNAL_KB_NOT_FOUND` | 远端知识库不存在 | 绑定标记目标不存在 |
| `EXTERNAL_KB_RATE_LIMITED` | 请求过于频繁 | 无,可显示建议等待 |
| `EXTERNAL_KB_INCOMPATIBLE` | 接口或配置不兼容 | 实例或绑定标记不兼容 |
| `EXTERNAL_KB_INVALID_RESPONSE` | 返回内容无法读取 | 无,记录脱敏诊断 |
| `EXTERNAL_KB_SERVER` | 外部服务处理失败 | 无 |
| `EXTERNAL_KB_CANCELLED` | 已取消 | 无 |
Provider 原始正文不能直接成为用户错误。Adapter 将其映射为稳定错误码和有界短消息。
## 12. 删除规则
| 操作 | 无绑定 | 有绑定 |
| --- | --- | --- |
| 停用实例 | 直接停用 | 允许;绑定变为不可用 |
| 清除凭据 | 直接清除 | 需要确认;绑定保留但不可用 |
| 删除实例 | 直接删除 | 阻止直接删除,要求同时移除本地绑定 |
| 移除绑定 | 删除本地绑定 | 不影响实例和远端知识库 |
删除确认不得使用“删除远端知识库”等文案。
## 13. 需求追踪
| 需求 | 主要逻辑 | User Story |
| --- | --- | --- |
| `EK-FR-01` 至 `EK-FR-03` | 实例状态、连接测试状态机 | `EK-US-A1`、`EK-US-A2` |
| `EK-FR-04`、`EK-FR-05` | 目录读取规则、手工目标验证 | `EK-US-B1`、`EK-US-B2` |
| `EK-FR-06`、`EK-FR-07`、`EK-FR-08`、`EK-FR-09` | 创建向导状态、配置模型和优先级 | `EK-US-B3`、`EK-US-B4`、`EK-US-D1`、`EK-US-D3` |
| `EK-FR-10` 至 `EK-FR-13` | 单一身份、多知识库检索和引用规则 | `EK-US-C1` 至 `EK-US-C3`、`EK-US-D2` |
| `EK-FR-14` | 绑定状态、错误分类和部分失败 | `EK-US-C4`、`EK-US-D4` |
| `EK-FR-15` | 对象关系和删除规则 | `EK-US-A3` |
| `EK-FR-16` | 无静默切换、请求冻结和传输风险确认 | `EK-US-C2` |
## 14. 逻辑完整性
已确定:
- 产品入口、对象关系、只读边界和凭据边界。
- 目录读取与手工 ID 的切换条件。
- 三种 Provider 的初始配置范围和 RAGFlow 图谱语义。
- 多知识库部分失败、删除和版本变化规则。
实施前仍需技术验证:
- 选定支持的 Dify、FastGPT、RAGFlow 最低版本,形成固定支持矩阵并保存真实响应 fixture。
- 验证各版本检索探测的 API 审计、用量和内容修改行为。
- 确定 Provider 返回 URL 的安全打开策略;未验证前只显示文本定位和远端 ID。
- 依据真实延迟确定默认并发上限和跨知识库上下文配额。
三家真实 Provider 的生产服务、本机 HTTP MCP、生产 IPC `always` 短答案,以及 Dify
`auto` 与完整 App 输入区回答、引用已有证据,详见[实施进度](./progress.md)。`auto` 本轮
使用进程内工具调用,不覆盖远程 ACP。FastGPT/RAGFlow 完整桌面、复杂问答和远程
forced-preflight 仍待完成;远程已确认网络可达,但已有 SSH 凭据在验证程序内无法解密。
后续 Portal 与控件修正后的聚焦测试、全量回归及相关检查已通过,精确结果由实施进度维护。
原场景中的保存前分项测试、按地址
传输确认、取消交互、版本能力状态和完整诊断尚不等同当前实现,不能把对应 User Story
全部标为通过。是否调整这些验收要求由功能 owner 确认,本轮不增加新的状态框架。
@@ -4,14 +4,38 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | 三家短答案、Dify 完整 App 与当前 UI 自动回归已通过;剩余产品验收待完成 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 适用产品 | GoodBuddy 桌面端 |
| 关联逻辑 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 关联界面 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
| 技术设计 | [外部知识库技术设计](./external-knowledge-technical-design.md) |
## 当前交付范围
当前工作区已提供实例管理、加密凭据保存、目录与手工 ID、外部绑定、Provider 检索参数、
统一知识库列表、聊天预检索与按需工具检索、外部引用和本地移除的实现。参数与恢复默认
遵循下文只读产品决策。2026-09-13 已验证三家 Provider 的生产服务与本机 HTTP MCP,
以及 Dify 桌面的实例保存、绑定、参数更新、重启和历史引用。后续已修正外部弹窗的字段
样式与布局,并验证三家通过生产 IPC 生成有依据的短答案、Dify 按需检索,以及从完整 App
输入区发送 Dify 问题并点击真实答案引用。该证据不代表广泛问答质量。
FastGPT/RAGFlow 完整桌面、远程预检索仍待验收;当前 UI 修改后的聚焦测试、全量回归与
相关检查已通过,精确结果见[实施进度](./progress.md)。
本文的功能需求和产品验收仍是验收目标。以下差异不能仅凭实现存在就标为满足:
| 原需求 | 当前实现与待核对差异 |
| --- | --- |
| `EK-FR-02`、`EK-FR-03` | 实例先保存为未测试,再在列表执行目录测试;目标检索在绑定时验证。没有分项草稿探测、版本或完整能力快照 |
| `EK-FR-04` | 已有目录搜索和分页;Dify/RAGFlow 搜索过滤当前页,FastGPT 搜索当前父级响应,不能保证全目录搜索 |
| `EK-FR-09` | 内置严格配置已实现,没有按探测版本维护支持矩阵;Dify 高级只读能力展示、Metadata 编辑和 RAGFlow Rerank 未提供 |
| `EK-FR-13`、`EK-FR-14` | 已显示片段、原始评分、总耗时和失败;没有完整的 Provider 耗时、裁剪前数量与截断诊断,也不持久化远端目标失效状态 |
| `EK-FR-16` | Main 固定端点及数据说明已接入;当前没有单独接入全局内网兼容模式和按地址传输确认流程 |
这些差异由功能 owner 在验收时核对。2026-09-09、2026-09-10 真实接口探测只作为历史
兼容性资料,不作为本轮桌面产品验证结果。
## 1. 背景
不少企业已经在内网部署 Dify、FastGPT 或 RAGFlow,并在其中维护制度、产品、研发
@@ -42,6 +66,11 @@ GoodBuddy 负责。
10. API Key 在用户输入表单和受限 IPC 中短暂存在,由 Main 进程加密保存并用于请求;
保存后不回传 Renderer,也不进入 Runtime、日志、普通元数据和检索引用。
第一版按只读接入交付:参数修改和恢复默认只更新 GoodBuddy 中当前绑定的客户端配置,
在后续检索请求中使用,不修改远端知识库默认配置,也不影响其他客户端。远端文档、
索引、图谱构建、模型和 Pipeline 设置由用户在对应平台维护。检索请求仍可能产生远端
Embedding、重排用量和审计记录。
## 3. 目标
### 3.1 用户目标
@@ -93,22 +122,23 @@ GoodBuddy 负责。
1. 用户在知识库页面点击“管理外部实例”。
2. 页面列出实例名称、Provider、服务地址、状态和已绑定知识库数量。
3. 用户添加实例,填写名称、Provider、服务地址和 API Key。
4. “测试连接”分别验证服务可达、认证、知识库列表和检索能力。
5. 用户保存后,API Key 被加密保存,列表只显示凭据已配置。
4. 用户先保存实例,API Key 被加密保存,列表显示凭据状态和“未测试”。
5. 在已保存实例上“测试连接”读取目录并记录结果;检索能力在绑定目标时验证。
列表接口不可用但已知远端知识库 ID 可检索时,实例可以保存为“需手工指定知识库”。
服务不可达或认证失败时不得保存为可用实例;用户可以保留表单并修正。
目录 403 显示“需手工指定知识库”;不可达或认证失败保留实例和失败状态,用户可编辑
后重试。保存本身不代表可用。原分项测试验收目标见 `EK-FR-03`,当前实现差异见上表。
### 6.2 添加外部知识库
1. 用户点击现有“新建知识库”。
2. 在“知识库类型”中选择“本地”、Dify、FastGPT 或 RAGFlow。
3. 选择外部类型后,左侧选择同 Provider 的外部实例。
4. GoodBuddy 自动读取远端知识库列表,并填入可搜索下拉框。
4. GoodBuddy 自动读取远端知识库列表,提供搜索、分页和目标选择。
5. 列表权限不可用时,用户可以手工填写远端知识库 ID。
6. 用户选择一个远端知识库,填写 GoodBuddy 显示名称。
7. 右侧展示通用配置和当前 Provider 的专属配置。
8. 用户填写测试查询并执行一次有界检索;请求成功后创建本地绑定,零结果不算失败。
8. 用户填写测试查询并执行有界检索;成功后可添加,零结果不算失败。保存时服务会再次
执行该查询验证目标和配置,之后创建本地绑定。
添加完成后沿用现有知识库规则,把新绑定加入当前聊天选择,但不会重新启用用户此前
取消的其他知识库。
@@ -222,13 +252,20 @@ Provider 的远端创建、更新、上传或删除接口。
| --- | --- | --- | --- |
| Dify | 知识库列表与详情 | Dataset Retrieve | 使用知识库默认配置,或完整覆盖检索方式、Top K、Score 阈值及 Rerank 模式 |
| FastGPT | 知识库层级列表与详情 | Dataset Search Test | 搜索模式、Token 上限、相似度、Rerank |
| RAGFlow | Dataset 列表与详情 | Retrieval | 相似度、向量权重、KNN Top K、Rerank、图谱检索、Knowledge Compilation |
| RAGFlow | Dataset 列表与详情 | Retrieval | 相似度、向量权重、KNN Top K、图谱检索、Knowledge Compilation;当前未提供 Rerank 配置 |
“首批专属配置”表示适配器允许表达的字段,不保证每个实例版本都支持。具体显隐由能力
探测和[逻辑设计](./external-knowledge-logic-design.md)共同决定。
## 9. 产品验收
以下为验收清单,尚未整体通过。第 2 项已有三家生产服务绑定与检索证据,Dify 另有桌面
操作证据;其他 Provider 的桌面、受限目录与高级配置等仍需补验。第 12 项确认流程当前
未实现。早期历史引用检查使用标记为测试的会话;后续已从完整 App 输入区生成 Dify
真实短答案并点击引用,三家另有生产 IPC 短答案证据。完整 App 尚未覆盖 FastGPT/RAGFlow,
也未评估广泛问答质量;本机 MCP 成功不替代被 SSH 凭据解密阻断的真实远程预检索。
详细结果以实施进度为准。
1. 用户可以在知识库页面完成三种 Provider 的实例添加、连接测试、编辑和删除。
2. 每种 Provider 至少通过一个真实自部署实例完成列表读取、绑定和检索。
3. 列表权限关闭时,可以通过手工 ID 添加并验证一个可检索知识库。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,828 @@
{
"schemaVersion": 1,
"probedAt": "2026-09-10T14:02:51.600Z",
"mode": "basic-read-only",
"queryCharacters": 3,
"privacy": "No credentials, origins, IDs, names, queries, or content are included.",
"providers": [
{
"provider": "dify",
"profile": "0.x",
"detectedVersion": "0.15.8",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": false
},
"catalog": {
"success": true,
"count": 3,
"total": 3,
"itemKeys": [
"app_count",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"external_knowledge_info",
"external_retrieval_model",
"id",
"indexing_technique",
"name",
"permission",
"provider",
"retrieval_model_dict",
"tags",
"updated_at",
"updated_by",
"word_count"
],
"retrievalModelKeys": [
"reranking_enable",
"reranking_mode",
"reranking_model",
"score_threshold",
"score_threshold_enabled",
"search_method",
"top_k",
"weights"
],
"indexingTechniques": [
"high_quality"
],
"metadataSchemaCount": 0,
"multimodalCount": 0,
"pipelineConfiguredCount": 0,
"summaryIndexConfiguredCount": 0
},
"detail": {
"success": false,
"failure": "http-error",
"keys": [
"code",
"message",
"status"
]
},
"retrievals": [
{
"mode": "dataset-defaults",
"success": true,
"count": 0,
"itemKeys": [],
"segmentKeys": [],
"documentKeys": [],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": false,
"childContentPresent": false,
"summaryPresent": false
}
],
"features": {
"datasetDefaults": true,
"detailSupported": false,
"retrievalOverrideObserved": true,
"retrievalOverrideProbed": false,
"nonEmptyResultObserved": false
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/v1/datasets",
"status": 200,
"durationMs": 1255,
"responseKeys": [
"data",
"has_more",
"limit",
"page",
"total"
]
},
{
"name": "detail",
"method": "GET",
"path": "/v1/datasets/{id}",
"status": 405,
"durationMs": 105,
"responseKeys": [
"code",
"message",
"status"
]
},
{
"name": "retrieve-defaults",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 582,
"responseKeys": [
"query",
"records"
]
}
]
},
{
"provider": "dify",
"profile": "1.x",
"detectedVersion": "1.17.0",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": false
},
"catalog": {
"success": true,
"count": 1,
"total": 1,
"itemKeys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
],
"retrievalModelKeys": [
"reranking_enable",
"reranking_mode",
"reranking_model",
"score_threshold",
"score_threshold_enabled",
"search_method",
"top_k",
"weights"
],
"indexingTechniques": [
"high_quality"
],
"metadataSchemaCount": 0,
"multimodalCount": 0,
"pipelineConfiguredCount": 0,
"summaryIndexConfiguredCount": 1
},
"detail": {
"success": true,
"keys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
]
},
"retrievals": [
{
"mode": "dataset-defaults",
"success": true,
"count": 3,
"itemKeys": [
"child_chunks",
"files",
"score",
"segment",
"summary",
"tsne_position"
],
"segmentKeys": [
"answer",
"completed_at",
"content",
"created_at",
"created_by",
"disabled_at",
"disabled_by",
"document",
"document_id",
"enabled",
"error",
"hit_count",
"id",
"index_node_hash",
"index_node_id",
"indexing_at",
"keywords",
"position",
"sign_content",
"status",
"stopped_at",
"tokens",
"word_count"
],
"documentKeys": [
"data_source_type",
"doc_metadata",
"doc_type",
"id",
"name"
],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": true,
"childContentPresent": false,
"summaryPresent": false,
"scoreRange": {
"minimum": 0.5038624,
"maximum": 0.6006959
}
}
],
"features": {
"datasetDefaults": true,
"detailSupported": true,
"retrievalOverrideObserved": true,
"retrievalOverrideProbed": false,
"nonEmptyResultObserved": true
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/v1/datasets",
"status": 200,
"durationMs": 2261,
"responseKeys": [
"data",
"has_more",
"limit",
"page",
"total"
]
},
{
"name": "detail",
"method": "GET",
"path": "/v1/datasets/{id}",
"status": 200,
"durationMs": 1680,
"responseKeys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
]
},
{
"name": "retrieve-defaults",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 3814,
"responseKeys": [
"query",
"records"
]
}
]
},
{
"provider": "fastgpt",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": false
},
"catalog": {
"success": true,
"count": 1,
"envelope": "data-array",
"itemKeys": [
"_id",
"avatar",
"createTime",
"inheritPermission",
"intro",
"name",
"permission",
"private",
"sourceMember",
"tmbId",
"type",
"updateTime",
"vectorModel"
],
"itemTypes": [
"dataset"
]
},
"detail": {
"keys": [
"_id",
"agentModel",
"agentModelId",
"avatar",
"deleteTime",
"inheritPermission",
"intro",
"name",
"parentId",
"permission",
"status",
"teamId",
"tmbId",
"type",
"updateTime",
"vectorModel",
"vectorModelId",
"vlmModel",
"vlmModelId"
],
"vectorModelKeys": [
"avatar",
"charsPointsPrice",
"config",
"isActive",
"isCustom",
"model",
"modelId",
"name",
"provider",
"scope",
"testMode",
"type"
]
},
"retrievals": [
{
"searchMode": "embedding",
"success": true,
"envelope": "data-list",
"count": 8,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true,
"scoreTypes": [
"array"
],
"scoreItemKeys": [
"index",
"type",
"value"
],
"scoreValueRange": {
"minimum": 0.27009665966033936,
"maximum": 0.3160914182662964
},
"rerankEnabled": false
},
{
"searchMode": "fullTextRecall",
"success": true,
"envelope": "data-list",
"count": 2,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true,
"scoreTypes": [
"array"
],
"scoreItemKeys": [
"index",
"type",
"value"
],
"scoreValueRange": {
"minimum": 0.5047619047619047,
"maximum": 0.505
},
"rerankEnabled": false
},
{
"searchMode": "mixedRecall",
"success": true,
"envelope": "data-list",
"count": 7,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true,
"scoreTypes": [
"array"
],
"scoreItemKeys": [
"index",
"type",
"value"
],
"scoreValueRange": {
"minimum": 0.007575757575757576,
"maximum": 0.505
},
"rerankEnabled": false
}
],
"features": {
"searchModes": [
"embedding",
"fullTextRecall",
"mixedRecall"
],
"rerankProbed": false,
"rerankRequestSucceeded": false,
"extensionQueryProbed": false
},
"operations": [
{
"name": "catalog",
"method": "POST",
"path": "/api/core/dataset/list",
"status": 200,
"businessCode": 200,
"durationMs": 1785,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "detail",
"method": "GET",
"path": "/api/core/dataset/detail",
"status": 200,
"businessCode": 200,
"durationMs": 571,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-embedding",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"businessCode": 200,
"durationMs": 1255,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-fullTextRecall",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"businessCode": 200,
"durationMs": 777,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-mixedRecall",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"businessCode": 200,
"durationMs": 2326,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
}
]
},
{
"provider": "ragflow",
"transport": {
"protocol": "http:"
},
"catalog": {
"success": true,
"count": 29,
"total": 29,
"itemKeys": [
"artifact_task_finish_at",
"artifact_task_id",
"avatar",
"chunk_count",
"chunk_method",
"create_date",
"create_time",
"created_by",
"description",
"document_count",
"embedding_model",
"graphrag_task_finish_at",
"graphrag_task_id",
"id",
"language",
"mindmap_task_finish_at",
"mindmap_task_id",
"name",
"nickname",
"pagerank",
"parser_config",
"permission",
"pipeline_id",
"raptor_task_finish_at",
"raptor_task_id",
"similarity_threshold",
"skill_task_finish_at",
"skill_task_id",
"status",
"tenant_avatar",
"tenant_embd_id",
"tenant_id",
"token_num",
"update_date",
"update_time",
"vector_similarity_weight"
],
"parserConfigKeys": [
"auto_keywords",
"auto_questions",
"built_in_metadata",
"children_delimiter",
"chunk_token_num",
"compilation_template_group_id",
"delimiter",
"enable_children",
"enable_metadata",
"ext",
"filename_embd_weight",
"graphrag",
"html4excel",
"image_context_size",
"image_table_context_window",
"layout_recognize",
"llm_id",
"metadata",
"mineru_formula_enable",
"mineru_lang",
"mineru_parse_method",
"mineru_table_enable",
"overlapped_percent",
"pages",
"parent_child",
"raptor",
"table_column_mode",
"table_column_names",
"table_column_roles",
"table_context_size",
"tag_kb_ids",
"task_page_size",
"tenant_llm_id",
"toc_extraction",
"topn_tags"
],
"graphConfiguredCount": 18,
"graphCompletedCount": 16,
"knowledgeCompilationConfiguredCount": 0
},
"detail": {
"success": true,
"keys": [
"artifact_task_finish_at",
"artifact_task_id",
"avatar",
"chunk_count",
"chunk_method",
"create_date",
"create_time",
"created_by",
"description",
"document_count",
"embedding_model",
"graphrag_task_finish_at",
"graphrag_task_id",
"id",
"language",
"mindmap_task_finish_at",
"mindmap_task_id",
"name",
"nickname",
"pagerank",
"parser_config",
"permission",
"pipeline_id",
"raptor_task_finish_at",
"raptor_task_id",
"similarity_threshold",
"skill_task_finish_at",
"skill_task_id",
"status",
"tenant_avatar",
"tenant_embd_id",
"tenant_id",
"token_num",
"update_date",
"update_time",
"vector_similarity_weight"
]
},
"retrievals": [
{
"feature": "baseline",
"success": true,
"count": 2,
"itemKeys": [
"content",
"content_ltks",
"dataset_id",
"doc_type_kwd",
"document_id",
"document_keyword",
"id",
"image_id",
"important_keywords",
"mom_id",
"positions",
"row_id",
"similarity",
"tag_kwd",
"term_similarity",
"vector_similarity"
],
"contentPresent": true,
"scoreRange": {
"minimum": 0.18431304078777125,
"maximum": 0.2623782504333604
}
}
],
"features": {
"graphRetrievalProbed": false,
"knowledgeCompilationProbed": false,
"metadataConditionProbed": false
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/api/v1/datasets",
"status": 200,
"businessCode": 0,
"durationMs": 2817,
"responseKeys": [
"code",
"data",
"total_datasets"
]
},
{
"name": "detail",
"method": "GET",
"path": "/api/v1/datasets",
"status": 200,
"businessCode": 0,
"durationMs": 159,
"responseKeys": [
"code",
"data",
"total_datasets"
]
},
{
"name": "retrieve-baseline",
"method": "POST",
"path": "/api/v1/retrieval",
"status": 200,
"businessCode": 0,
"durationMs": 776,
"responseKeys": [
"code",
"data"
]
}
]
}
]
}
@@ -0,0 +1,910 @@
{
"schemaVersion": 1,
"probedAt": "2026-09-09T16:25:05.512Z",
"mode": "extended-read-only",
"queryCharacters": 3,
"privacy": "No credentials, origins, IDs, names, queries, or content are included.",
"providers": [
{
"provider": "dify",
"profile": "0.x",
"detectedVersion": "0.15.8",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": true
},
"catalog": {
"count": 3,
"total": 3,
"itemKeys": [
"app_count",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"external_knowledge_info",
"external_retrieval_model",
"id",
"indexing_technique",
"name",
"permission",
"provider",
"retrieval_model_dict",
"tags",
"updated_at",
"updated_by",
"word_count"
],
"retrievalModelKeys": [
"reranking_enable",
"reranking_mode",
"reranking_model",
"score_threshold",
"score_threshold_enabled",
"search_method",
"top_k",
"weights"
],
"indexingTechniques": [
"high_quality"
],
"metadataSchemaCount": 0,
"multimodalCount": 0,
"pipelineConfiguredCount": 0,
"summaryIndexConfiguredCount": 0
},
"detail": {
"keys": [
"code",
"message",
"status"
]
},
"retrievals": [
{
"mode": "dataset-defaults",
"count": 0,
"itemKeys": [],
"segmentKeys": [],
"documentKeys": [],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": false,
"childContentPresent": false,
"summaryPresent": false
},
{
"mode": "explicit-current-config",
"count": 0,
"itemKeys": [],
"segmentKeys": [],
"documentKeys": [],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": false,
"childContentPresent": false,
"summaryPresent": false
}
],
"features": {
"datasetDefaults": true,
"detailSupported": false,
"retrievalOverrideObserved": true,
"retrievalOverrideProbed": true,
"nonEmptyResultObserved": false
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/v1/datasets",
"status": 200,
"durationMs": 288,
"responseKeys": [
"data",
"has_more",
"limit",
"page",
"total"
]
},
{
"name": "detail",
"method": "GET",
"path": "/v1/datasets/{id}",
"status": 405,
"durationMs": 111,
"responseKeys": [
"code",
"message",
"status"
]
},
{
"name": "retrieve-defaults",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 237,
"responseKeys": [
"query",
"records"
]
},
{
"name": "retrieve-explicit-config",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 160,
"responseKeys": [
"query",
"records"
]
}
]
},
{
"provider": "dify",
"profile": "1.x",
"detectedVersion": "1.17.0",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": true
},
"catalog": {
"count": 1,
"total": 1,
"itemKeys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
],
"retrievalModelKeys": [
"reranking_enable",
"reranking_mode",
"reranking_model",
"score_threshold",
"score_threshold_enabled",
"search_method",
"top_k",
"weights"
],
"indexingTechniques": [
"high_quality"
],
"metadataSchemaCount": 0,
"multimodalCount": 0,
"pipelineConfiguredCount": 0,
"summaryIndexConfiguredCount": 1
},
"detail": {
"keys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
]
},
"retrievals": [
{
"mode": "dataset-defaults",
"count": 3,
"itemKeys": [
"child_chunks",
"files",
"score",
"segment",
"summary",
"tsne_position"
],
"segmentKeys": [
"answer",
"completed_at",
"content",
"created_at",
"created_by",
"disabled_at",
"disabled_by",
"document",
"document_id",
"enabled",
"error",
"hit_count",
"id",
"index_node_hash",
"index_node_id",
"indexing_at",
"keywords",
"position",
"sign_content",
"status",
"stopped_at",
"tokens",
"word_count"
],
"documentKeys": [
"data_source_type",
"doc_metadata",
"doc_type",
"id",
"name"
],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": true,
"childContentPresent": false,
"summaryPresent": false,
"scoreRange": {
"minimum": 0.5038624,
"maximum": 0.6006959
}
},
{
"mode": "explicit-current-config",
"count": 3,
"itemKeys": [
"child_chunks",
"files",
"score",
"segment",
"summary",
"tsne_position"
],
"segmentKeys": [
"answer",
"completed_at",
"content",
"created_at",
"created_by",
"disabled_at",
"disabled_by",
"document",
"document_id",
"enabled",
"error",
"hit_count",
"id",
"index_node_hash",
"index_node_id",
"indexing_at",
"keywords",
"position",
"sign_content",
"status",
"stopped_at",
"tokens",
"word_count"
],
"documentKeys": [
"data_source_type",
"doc_metadata",
"doc_type",
"id",
"name"
],
"childChunkKeys": [],
"fileKeys": [],
"contentPresent": true,
"childContentPresent": false,
"summaryPresent": false,
"scoreRange": {
"minimum": 0.5038624,
"maximum": 0.6006959
}
}
],
"features": {
"datasetDefaults": true,
"detailSupported": true,
"retrievalOverrideObserved": true,
"retrievalOverrideProbed": true,
"nonEmptyResultObserved": true
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/v1/datasets",
"status": 200,
"durationMs": 950,
"responseKeys": [
"data",
"has_more",
"limit",
"page",
"total"
]
},
{
"name": "detail",
"method": "GET",
"path": "/v1/datasets/{id}",
"status": 200,
"durationMs": 616,
"responseKeys": [
"app_count",
"author_name",
"built_in_field_enabled",
"chunk_structure",
"created_at",
"created_by",
"data_source_type",
"description",
"doc_form",
"doc_metadata",
"document_count",
"embedding_available",
"embedding_model",
"embedding_model_provider",
"enable_api",
"external_knowledge_info",
"external_retrieval_model",
"icon_info",
"id",
"indexing_technique",
"is_multimodal",
"is_published",
"maintainer",
"name",
"permission",
"pipeline_id",
"provider",
"retrieval_model_dict",
"runtime_mode",
"summary_index_setting",
"tags",
"total_available_documents",
"total_documents",
"updated_at",
"updated_by",
"word_count"
]
},
{
"name": "retrieve-defaults",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 2468,
"responseKeys": [
"query",
"records"
]
},
{
"name": "retrieve-explicit-config",
"method": "POST",
"path": "/v1/datasets/{id}/retrieve",
"status": 200,
"durationMs": 801,
"responseKeys": [
"query",
"records"
]
}
]
},
{
"provider": "fastgpt",
"transport": {
"protocol": "https:",
"tlsVerificationDisabled": true
},
"catalog": {
"count": 1,
"envelope": "data-array",
"itemKeys": [
"_id",
"avatar",
"createTime",
"inheritPermission",
"intro",
"name",
"permission",
"private",
"sourceMember",
"tmbId",
"type",
"updateTime",
"vectorModel"
],
"itemTypes": [
"dataset"
]
},
"detail": {
"keys": [
"_id",
"agentModel",
"agentModelId",
"avatar",
"deleteTime",
"inheritPermission",
"intro",
"name",
"parentId",
"permission",
"status",
"teamId",
"tmbId",
"type",
"updateTime",
"vectorModel",
"vectorModelId",
"vlmModel",
"vlmModelId"
],
"vectorModelKeys": [
"avatar",
"charsPointsPrice",
"config",
"isActive",
"isCustom",
"model",
"modelId",
"name",
"provider",
"scope",
"testMode",
"type"
]
},
"retrievals": [
{
"searchMode": "embedding",
"count": 8,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
},
{
"searchMode": "fullTextRecall",
"count": 2,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
},
{
"searchMode": "mixedRecall",
"count": 7,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
},
{
"searchMode": "mixedRecall",
"usingRerank": true,
"count": 9,
"itemKeys": [
"chunkIndex",
"collectionId",
"datasetId",
"id",
"q",
"score",
"sourceId",
"sourceName",
"updateTime"
],
"contentPresent": true
}
],
"features": {
"searchModes": [
"embedding",
"fullTextRecall",
"mixedRecall"
],
"rerankProbed": true,
"extensionQueryProbed": false
},
"operations": [
{
"name": "catalog",
"method": "POST",
"path": "/api/core/dataset/list",
"status": 200,
"durationMs": 707,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "detail",
"method": "GET",
"path": "/api/core/dataset/detail",
"status": 200,
"durationMs": 293,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-embedding",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 797,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-fullTextRecall",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 300,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-mixedRecall",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 1707,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
},
{
"name": "retrieve-rerank",
"method": "POST",
"path": "/api/core/dataset/searchTest",
"status": 200,
"durationMs": 1727,
"responseKeys": [
"code",
"data",
"message",
"statusText"
]
}
]
},
{
"provider": "ragflow",
"transport": {
"protocol": "http:"
},
"catalog": {
"count": 28,
"total": 28,
"itemKeys": [
"artifact_task_finish_at",
"artifact_task_id",
"avatar",
"chunk_count",
"chunk_method",
"create_date",
"create_time",
"created_by",
"description",
"document_count",
"embedding_model",
"graphrag_task_finish_at",
"graphrag_task_id",
"id",
"language",
"mindmap_task_finish_at",
"mindmap_task_id",
"name",
"nickname",
"pagerank",
"parser_config",
"permission",
"pipeline_id",
"raptor_task_finish_at",
"raptor_task_id",
"similarity_threshold",
"skill_task_finish_at",
"skill_task_id",
"status",
"tenant_avatar",
"tenant_embd_id",
"tenant_id",
"token_num",
"update_date",
"update_time",
"vector_similarity_weight"
],
"parserConfigKeys": [
"auto_keywords",
"auto_questions",
"built_in_metadata",
"children_delimiter",
"chunk_token_num",
"compilation_template_group_id",
"delimiter",
"enable_children",
"enable_metadata",
"ext",
"filename_embd_weight",
"graphrag",
"html4excel",
"image_context_size",
"image_table_context_window",
"layout_recognize",
"llm_id",
"metadata",
"mineru_formula_enable",
"mineru_lang",
"mineru_parse_method",
"mineru_table_enable",
"overlapped_percent",
"pages",
"parent_child",
"raptor",
"table_column_mode",
"table_column_names",
"table_column_roles",
"table_context_size",
"tag_kb_ids",
"task_page_size",
"tenant_llm_id",
"toc_extraction",
"topn_tags"
],
"graphConfiguredCount": 17,
"graphCompletedCount": 15,
"knowledgeCompilationConfiguredCount": 0
},
"detail": {
"keys": [
"artifact_task_finish_at",
"artifact_task_id",
"avatar",
"chunk_count",
"chunk_method",
"create_date",
"create_time",
"created_by",
"description",
"document_count",
"embedding_model",
"graphrag_task_finish_at",
"graphrag_task_id",
"id",
"language",
"mindmap_task_finish_at",
"mindmap_task_id",
"name",
"nickname",
"pagerank",
"parser_config",
"permission",
"pipeline_id",
"raptor_task_finish_at",
"raptor_task_id",
"similarity_threshold",
"skill_task_finish_at",
"skill_task_id",
"status",
"tenant_avatar",
"tenant_embd_id",
"tenant_id",
"token_num",
"update_date",
"update_time",
"vector_similarity_weight"
]
},
"retrievals": [
{
"feature": "baseline",
"count": 0,
"itemKeys": [],
"contentPresent": false
},
{
"feature": "graph",
"count": 1,
"itemKeys": [
"content",
"content_ltks",
"dataset_id",
"document_id",
"document_keyword",
"id",
"image_id",
"important_keywords",
"positions",
"similarity",
"term_similarity",
"vector_similarity"
],
"contentPresent": true,
"scoreRange": {
"minimum": 1,
"maximum": 1
}
},
{
"feature": "knowledge-compilation",
"count": 0,
"itemKeys": [],
"contentPresent": false
}
],
"features": {
"graphRetrievalProbed": true,
"knowledgeCompilationProbed": true,
"metadataConditionProbed": false
},
"operations": [
{
"name": "catalog",
"method": "GET",
"path": "/api/v1/datasets",
"status": 200,
"durationMs": 732,
"responseKeys": [
"code",
"data",
"total_datasets"
]
},
{
"name": "detail",
"method": "GET",
"path": "/api/v1/datasets",
"status": 200,
"durationMs": 157,
"responseKeys": [
"code",
"data",
"total_datasets"
]
},
{
"name": "retrieve-baseline",
"method": "POST",
"path": "/api/v1/retrieval",
"status": 200,
"durationMs": 417,
"responseKeys": [
"code",
"data"
]
},
{
"name": "retrieve-graph",
"method": "POST",
"path": "/api/v1/retrieval",
"status": 200,
"durationMs": 478,
"responseKeys": [
"code",
"data"
]
},
{
"name": "retrieve-knowledge-compilation",
"method": "POST",
"path": "/api/v1/retrieval",
"status": 200,
"durationMs": 331,
"responseKeys": [
"code",
"data"
]
}
]
}
]
}
@@ -4,577 +4,317 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | 三家生产 IPC 真实生成、Dify 完整 App 与当前 UI 自动回归已通过;远程待验收 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 产品需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 逻辑设计 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| UI 设计 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
| 行为规则 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 界面 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
| 验证状态 | [实施进度](./progress.md) |
## 1. 设计约束
## 1. 实现边界
1. 外部知识库扩展现有知识库领域,不建立独立检索工具、聊天范围或 Runtime 权限模型。
2. 外部绑定使用远端检索,不落地远端文档、分块、向量和图谱。
3. 所有网络、凭据、Provider 响应校验和结果标准化都在 Main 完成。
4. Renderer 只能通过受限 IPC 管理实例、读取目录、保存绑定和发起测试。
5. Dify、FastGPT、RAGFlow 使用独立 Adapter 和判别配置,不通过任意 JSON 请求模板实现。
6. 首次实现只调用无内容修改语义的列表、详情和检索接口。
7. Provider API 变化通过真实 fixture、Adapter 版本和能力快照处理,不通过猜测字段兼容。
外部知识库复用 GoodBuddy 知识库 ID、聊天范围和检索入口。网络、凭据、响应读取和
标准化在 Main 完成,Renderer 通过明确的 Preload 方法调用 IPC。远端只执行目录、详情和
检索请求,不创建、修改、上传或删除远端内容,不调用外部 App、Chat、Workflow 或 Agent。
## 2. 现有代码边界
本地 `file | directory | url` 仍表示需要导入和索引的资料来源。外部绑定独立存储,
不加入该枚举,也不生成本地文档、分块、向量或图谱。实际用于回答的片段随会话引用保存。
当前实现中的主要扩展点:
## 2. 当前模块
| 边界 | 现有位置 | 需要的变化 |
| --- | --- | --- |
| 共享知识契约 | `src/shared/knowledge-contracts.ts`、`src/shared/contracts.ts` | 增加实例、绑定、Provider 配置和外部引用判别联合 |
| IPC 名称 | `src/shared/ipc-channels.ts` | 增加实例和目录操作通道 |
| Preload | `src/preload/index.ts` | 暴露受限、类型化的外部知识库 API |
| Main IPC | `src/main/ipc.ts` | 校验输入、调用服务、投影脱敏结果 |
| 知识服务 | `src/main/knowledge/knowledge-service.ts` | 按知识库来源分派本地或外部检索 |
| 数据库 | `src/main/knowledge/knowledge-database.ts` | 增加实例、绑定和迁移 |
| 内部类型 | `src/main/knowledge/types.ts` | 增加 Provider、实例、绑定和标准化结果类型 |
| 知识页面 | `src/renderer/src/KnowledgeWorkspace.tsx` | 实例管理、创建向导类型和外部详情 |
| 检索测试 | `src/renderer/src/KnowledgeRetrievalWorkbench.tsx` | 支持本地与外部诊断判别联合 |
| 聊天预检索 | `src/main/ipc.ts` | 统一调用 Provider-aware 检索编排 |
| Agent 知识工具 | `src/main/agent/knowledge-mcp-gateway.ts` | 与预检索复用同一编排和引用标准化 |
| 文件 | 职责 |
| --- | --- |
| `src/main/knowledge/external/external-knowledge-client.ts` | 一个 `ExternalKnowledgeClient` 内按 Provider 分支处理固定端点、地址规范化、分页、HTTP 错误、超时、取消、响应上限和结果映射;同文件定义 `ExternalKnowledgeError` |
| `src/main/knowledge/external/external-knowledge-service.ts` | 实例保存与测试、凭据加解密、目录与详情、检索测试、创建或更新绑定、请求生命周期与配置变化检查 |
| `src/main/knowledge/external/external-knowledge-store.ts` | 在已有 SQLite 连接上读写实例 JSON 和绑定 JSON |
| `src/main/knowledge/knowledge-database.ts` | v12 迁移、知识库与绑定事务、唯一约束及删除外键 |
| `src/main/knowledge/knowledge-service.ts` | 本地与外部检索分派、混合检索和总字符预算;阻止外部绑定进入本地维护操作 |
| `src/shared/external-knowledge-contracts.ts` | Provider 配置、实例与绑定输入、目录、测试结果和外部引用定位契约 |
| `src/shared/knowledge-contracts.ts`、`contracts.ts`、`assistant-contracts.ts` | 现有知识结果、列表和会话引用增加外部字段 |
| `src/shared/knowledge-reference.ts` | 预检索与 MCP 共用的引用转换和去重键 |
| `src/main/ipc.ts`、`src/preload/index.ts`、`src/shared/ipc-channels.ts` | 可信发送方检查、Zod 输入解析、类型化跨进程调用 |
| `src/renderer/src/ExternalKnowledge.tsx` | 实例 Modal、绑定表单、Provider 配置、外部详情与检索结果 |
| `KnowledgeWorkspace.tsx`、`App.tsx`、`KnowledgeCitationDialog.tsx` | 统一列表、聊天选择、刷新及历史引用显示 |
外部 Provider 不是 `KnowledgeSourceType`。现有 `file | directory | url` 表示会进入本地
文档、分块和索引的资料来源;把远端绑定塞入该枚举会错误触发同步、分块和来源打开逻辑。
`ExternalKnowledge.tsx` 的字段改用全局 `.field`,修正 body Portal 脱离知识库祖先后
丢失 scoped 控件样式的问题。CSS 提供 560px 紧凑弹窗和粘附底部操作区;实例选择复用
既有 picker 样式并实现菜单键盘行为。没有新增 Main 模块,具体控件规则由
[UI 设计](./external-knowledge-ui-design.md#当前实现与验收边界)维护。
## 3. 模块设计
当前没有独立 Provider 文件、Adapter Registry、Normalizer、版本化能力清单或配置迁移框架。
本文件按现有三个 Main 模块描述实现,不把旧草案中的拆分方案列为待建基础设施。
建议增加:
## 3. 共享契约
```text
src/main/knowledge/external/
├─ external-knowledge-service.ts
├─ external-knowledge-store.ts
├─ external-knowledge-errors.ts
├─ external-knowledge-http.ts
├─ external-knowledge-registry.ts
├─ external-knowledge-normalizer.ts
└─ providers/
├─ dify-adapter.ts
├─ fastgpt-adapter.ts
└─ ragflow-adapter.ts
```
### 3.1 实例与绑定
职责:
实例输入包含 `id?`、`name`、`provider`、`baseUrl`、`enabled` 和
`credential: keep | replace | clear`。实例摘要返回启停、凭据状态、`probeStatus`、绑定数、
最近测试时间和错误码,不返回凭据 envelope。当前没有 `detectedVersion`、`adapterVersion`
或 `transportSecurity` 字段。
- `ExternalKnowledgeService`:实例、目录、绑定、测试和检索编排。
- `ExternalKnowledgeStore`:实例和绑定持久化,凭据加解密。
- `ExternalKnowledgeHttp`:超时、取消、大小限制、地址和响应读取。
- `ExternalKnowledgeRegistry`:按 Provider 返回内置 Adapter,不接受运行时插件注册。
- `ExternalKnowledgeNormalizer`:统一结果边界、截断和引用。
- Adapter:构造固定端点与请求,解析 Provider 响应,声明静态配置和能力条件。
绑定保存 `knowledgeBaseId`、`instanceId`、`provider`、`remoteKnowledgeBaseId`、
`remoteName`、`commonConfig`、`providerConfig` 和 `lastVerifiedAt`。GoodBuddy 名称和描述
保留在知识库记录。创建和更新输入都要求用户填写 `testQuery`;服务保存前重新执行检索,
因此先点击“测试检索”再保存会产生两次检索请求。成功零命中允许保存。
`KnowledgeService` 继续作为聊天、检索测试和 MCP 网关的统一入口。它根据
`KnowledgeBase.kind` 调用现有本地检索或 `ExternalKnowledgeService.retrieve`。
### 3.2 配置范围
## 4. Provider Adapter
| 配置 | 当前共享契约 |
| --- | --- |
| 通用 | `resultLimit` 1 至 20,默认 6;`requestTimeoutMs` 1,000 至 60,000,默认 15,000;`maxSnippetCharacters` 100 至 8,000,默认 4,000 |
| Dify | 默认分支仅包含 `provider` 和 `useDatasetDefaults: true`;覆盖分支使用严格的 `retrievalModel` 对象,包含检索方式、Top K、阈值开关和重排开关,按启用条件校验阈值、重排模型或权重 |
| FastGPT | `searchMode`、`tokenLimit`(1 至 30,000)、`similarity`、`usingRerank`;不发送查询优化参数 |
| RAGFlow | `similarityThreshold`、`vectorSimilarityWeight`、`knnTopK`(1 至 2,048)、`useKg`、`includeKnowledgeCompilation`;当前无 Rerank、Metadata 编辑字段 |
```ts
type ExternalKnowledgeProvider = 'dify' | 'fastgpt' | 'ragflow'
Provider 必须与实例一致。Dify 加权配置还在客户端检查权重和为 1。共享查询输入上限为
4,000 字符,Dify 客户端另按 Unicode 码点拒绝超过 250 字符的查询,不静默压缩。
interface ExternalKnowledgeAdapter<PersistedConfig, CapabilitySnapshot> {
readonly provider: ExternalKnowledgeProvider
readonly configVersion: number
### 3.3 结果与引用
normalizeBaseUrl(input: string): URL
testConnection(context: AdapterContext): Promise<ConnectionTestResult>
listKnowledgeBases(
context: AdapterContext,
input: CatalogPageInput
): Promise<CatalogPage>
getKnowledgeBase(
context: AdapterContext,
remoteKnowledgeBaseId: string
): Promise<RemoteKnowledgeBaseSummary>
probeRetrieval(
context: AdapterContext,
input: ProviderRetrieveInput<PersistedConfig>
): Promise<ProviderRetrieveResult>
retrieve(
context: AdapterContext,
input: ProviderRetrieveInput<PersistedConfig>
): Promise<ProviderRetrieveResult>
parseCapabilities(input: unknown): CapabilitySnapshot
validateConfig(
input: unknown,
capabilities: CapabilitySnapshot
): PersistedConfig
}
```
现有 `KnowledgeRetrievalResult` 的本地 `documentId/sourceId/chunkId` 改为可选,增加
`external?: ExternalKnowledgeLocator`,没有另建一套 `UnifiedKnowledgeRetrievalResult`。
外部定位包含 Provider、实例 ID、远端库 ID,以及实际返回的文档 ID、片段 ID、位置和评分。
FastGPT 数组评分存为 `providerScores: {type, value, index?}[]`,保留顺序与类型;数值评分
存为 `providerScore`,不选取数组首项冒充总分。
`probeRetrieval` 可以复用 `retrieve`,但必须使用用户在创建向导中明确输入的测试查询。
不得生成会泄露组织信息的默认问题,不得调用 Provider 的 App 或模型问答接口,也不得
修改远端知识内容。Provider 自身记录 API 审计或计算检索用量不属于内容修改,必须由界面
提前说明。
外部结果沿用旧结果容器的空 `channels` 和 `scores.fusedScore = 0`。该零值是结构占位,
不表示质量;`toKnowledgeReference` 不把它写成引用总分。外部结果没有统一 `relevance`,
不同 Provider 的原始分数不参与跨库混排。引用去重键包含外部定位和片段,不伪造本地 ID。
Adapter 返回数据对象,不返回 React 组件、HTML 或远端 Schema。Renderer 中的配置定义
由共享的 Provider 判别联合和本地字段描述生成。
## 4. SQLite 与凭据
## 5. 共享契约
### 5.1 实例
```ts
type CredentialMutation =
| { action: 'keep' }
| { action: 'replace'; value: string }
| { action: 'clear' }
interface ExternalKnowledgeInstanceSummary {
id: string
name: string
provider: 'dify' | 'fastgpt' | 'ragflow'
baseUrl: string
enabled: boolean
credentialStatus: 'configured' | 'missing' | 'unavailable'
probeStatus:
| 'untested'
| 'catalog-ready'
| 'healthy'
| 'list-restricted'
| 'auth-failed'
| 'unreachable'
| 'incompatible'
| 'failed'
detectedVersion?: string
bindingCount: number
lastTestedAt?: string
lastErrorCode?: string
}
```
Renderer 输入使用 `CredentialMutation`,因此新密钥会在输入控件和该次 IPC 中短暂存在。
Main 返回值永远没有 `credential`、`apiKey`、`authorization` 或加密 envelope。
### 5.2 绑定
```ts
interface ExternalKnowledgeBinding {
knowledgeBaseId: string
instanceId: string
remoteKnowledgeBaseId: string
remoteName: string
remoteDescription?: string
commonConfig: ExternalKnowledgeCommonConfig
providerConfig: DifyConfig | FastGptConfig | RagflowConfig
providerConfigVersion: number
adapterVersion: number
lastVerifiedAt?: string
}
```
`providerConfig` 必须带 `provider` 判别字段,且与实例 Provider 一致。`remoteName` 是用于
显示和变化检测的快照,不是授权标识。
### 5.3 标准化结果
现有 `KnowledgeRetrievalResult` 假定结果映射到本地 `documentId/sourceId/chunkId`。需要改为
引用定位的判别联合,同时保持现有调用方可按统一字段读取片段:
```ts
type KnowledgeReferenceLocator =
| {
kind: 'local'
documentId: string
sourceId: string
chunkId: string
parentChunkId?: string
}
| {
kind: 'external'
provider: ExternalKnowledgeProvider
instanceId: string
remoteKnowledgeBaseId: string
remoteDocumentId?: string
remoteChunkId?: string
providerScore?: number
location?: string
sourceUrl?: string
metadata?: Record<string, string | number | boolean | null>
}
interface UnifiedKnowledgeRetrievalResult {
knowledgeBaseId: string
documentTitle: string
sourceDisplayName: string
snippet: string
relevance?: number
rank: number
locator: KnowledgeReferenceLocator
}
```
`remoteDocumentId` 和 `remoteChunkId` 只保存 Provider 实际返回的标识,缺失时不得从标题、
序号或内容哈希推导。`providerScore` 保留原始值,但不改名为统一 `relevance`。只有经过
单独定义和验证的归一化算法才能填写跨 Provider `relevance`。初始跨库拼装按结果配额和
稳定 Provider 顺序工作,不按原始分数混排。
所有共享 Schema 使用严格对象并限制:
- 查询不超过 4,000 字符;同时遵守 Provider 更小限制,例如 Dify 当前公开接口的
250 字符限制,超出时在发送前明确失败,不静默截断查询。
- 单次目录页不超过 100 项,总目录最多 500 项。
- 标准化结果最多 20 条,单片段最多 8,000 字符。
- 标题、ID、定位、URL 和元数据键值均设独立上限。
- Provider 原始响应总字节数设固定上限,超限立即取消读取。
## 6. 持久化
### 6.1 数据库变化
保留现有 `knowledge_bases` 表,增加:
```sql
ALTER TABLE knowledge_bases
ADD COLUMN kind TEXT NOT NULL DEFAULT 'local'
CHECK (kind IN ('local', 'external'));
```
增加实例表:
数据库 v12 增加两张表:
```sql
CREATE TABLE external_knowledge_instances (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
provider TEXT NOT NULL CHECK (provider IN ('dify', 'fastgpt', 'ragflow')),
base_url TEXT NOT NULL,
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
credential_envelope_json TEXT,
probe_status TEXT NOT NULL,
detected_version TEXT,
capabilities_json TEXT NOT NULL,
transport_security TEXT NOT NULL,
transport_risk_accepted_url TEXT,
transport_risk_accepted_at TEXT,
last_tested_at TEXT,
last_error_code TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
value_json TEXT NOT NULL
);
```
增加绑定表:
```sql
CREATE TABLE external_knowledge_bindings (
knowledge_base_id TEXT PRIMARY KEY
REFERENCES knowledge_bases(id) ON DELETE CASCADE,
instance_id TEXT NOT NULL
REFERENCES external_knowledge_instances(id) ON DELETE RESTRICT,
remote_knowledge_base_id TEXT NOT NULL,
remote_name TEXT NOT NULL,
remote_description TEXT,
common_config_json TEXT NOT NULL,
provider_config_json TEXT NOT NULL,
provider_config_version INTEGER NOT NULL,
adapter_version INTEGER NOT NULL,
binding_status TEXT NOT NULL,
last_verified_at TEXT,
last_success_at TEXT,
last_error_code TEXT,
UNIQUE(instance_id, remote_knowledge_base_id)
knowledge_base_id TEXT PRIMARY KEY REFERENCES knowledge_bases(id) ON DELETE CASCADE,
instance_id TEXT NOT NULL REFERENCES external_knowledge_instances(id) ON DELETE RESTRICT,
remote_id TEXT NOT NULL,
value_json TEXT NOT NULL,
UNIQUE(instance_id, remote_id)
);
```
已有知识库迁移为 `kind = 'local'`。外部知识库仍保留现有名称、描述和创建时间,但本地
`storageMode`、图谱、分块和本地检索设置不进入外部请求;后续可以在数据库重构时再移除
这些历史必填默认值,本功能不为此扩大迁移范围。
没有向 `knowledge_bases` 增加 `kind` 列;IPC 快照根据是否存在绑定投影
`KnowledgeLibrary.kind` 和 `external`。旧本地数据保留,知识库与绑定在同一事务中保存。
外部库沿用本地表所需的默认字段,但这些字段不进入 Provider 请求。
### 6.2 凭据
Main 启动把已有 `secureCipher` 注入 `KnowledgeService`。服务复用
`SettingsCredentialCipher` 和现有加密函数,把 envelope 放入实例 JSON;`replace` 要求
安全存储可用,`keep` 保留已有 envelope,`clear` 移除凭据。列表会尝试解密以区分
`configured/missing/unavailable`,请求前再次解密,没有独立明文凭据缓存。
复用 `SettingsCredentialCipher` 和 Electron `safeStorage`:
## 5. IPC 与生命周期
- `replace`:Main 加密新值后在一个事务中替换 envelope。
- `keep`:不读取、不重写现有 envelope。
- `clear`:确认后删除 envelope,并把实例凭据状态设为缺失。
- 安全存储不可用时拒绝新增或替换,不降级为明文。
- 解密只发生在发起请求前;请求结束后不缓存明文。
Preload 暴露实例 `List/Save/Test/SetEnabled/Delete`、目录 `List/Get`、绑定
`Create/Update` 和 `externalRetrievalTest`。通道使用 `knowledge:external-*` 前缀,
准确名称以 `ipc-channels.ts` 为准。移除绑定复用 `deleteLibrary`,没有独立 remove 通道。
实例导出、诊断和数据库普通 JSON 视图均排除 `credential_envelope_json`。
Handler 调用 `assertTrustedSender` 并解析共享输入;检索按实例 ID 在 Main 读取地址与凭据。
实例先保存为 `untested`,列表上的连接测试调用目录请求并更新摘要,不测试未保存草稿。
绑定检索测试是单独操作,不会改变实例探测状态。探测状态取值为 `untested`、
`catalog-ready`、`list-restricted`、`auth-failed`、`unreachable`、`failed`。
## 7. IPC 设计
服务按实例记录 AbortController。保存实例、停用、删除以及服务关闭会取消相应请求;
调用方取消信号与总超时合并。取消即是配置变更的唯一处理方式:被取消的连接测试直接抛出
`EXTERNAL_KB_CANCELLED`,不写回探测结果,因此不需要额外的前后配置快照比较。绑定保存在
验证请求前后各检查一次同一远端目标是否已被占用,最终以 `UNIQUE(instance_id, remote_id)`
索引为准。共享 IPC 没有 `requestId` 或取消方法;Renderer 用局部 generation/active 标记
丢弃旧响应,这与中止 Main 请求是两种行为。
建议增加独立通道:
删除仍被引用的实例返回 `EXTERNAL_KB_INSTANCE_IN_USE`。UI 确认后逐个调用本地
`deleteLibrary`,再删除实例;这是顺序操作,发生中途失败会刷新已移除的绑定,不是跨调用事务。
```text
knowledge:external-instances:list
knowledge:external-instances:test
knowledge:external-instances:save
knowledge:external-instances:set-enabled
knowledge:external-instances:delete
knowledge:external-catalog:list
knowledge:external-catalog:get
knowledge:external-bindings:create
knowledge:external-bindings:update
knowledge:external-bindings:remove
knowledge:external-retrieval:test
```
## 6. HTTP 边界
所有 Handler 必须:
客户端只接受 HTTP(S),拒绝地址内的用户名、密码、查询与片段;按 Provider 规范化 API
路径,仅拼接内置端点。请求使用 Bearer 认证,`redirect: 'error'`,无自动重试或公共云回退。
默认超时 15 秒,单个响应默认上限 5,000,000 字节;响应头和流式读取均检查上限,取消
期间终止读取。解析后只投影需要的有界字段,最多返回 20 条、每片段 8,000 字符。
1. `assertTrustedSender`。
2. 用共享 Zod Schema 解析严格输入。
3. 根据实例 ID 在 Main 读取地址和凭据,不接受 Renderer 在检索请求中重复传入。
4. 使用稳定错误码返回脱敏错误。
5. 对网络操作支持 AbortSignal、总超时和响应大小限制。
当前使用 `fetcher ?? fetch`,没有单独连接全局内网兼容模式、证书例外或按地址保存传输
确认。2026-09-13 当前生产服务在真实 Electron 中对两个 Dify 和 FastGPT HTTPS 配置完成
严格证书校验,RAGFlow 使用 HTTP。此证据覆盖这些配置的请求,不表示旧 PRD 的地址确认
或证书例外流程已实现;相关产品差异仍由功能 owner 核对。
目录和测试请求返回 `requestId`。Renderer 仍需校验当前类型、实例和目标与响应关联,避免
旧响应覆盖新表单。
## 7. 检索编排
## 8. HTTP 边界
`KnowledgeService.retrieve` 按知识库 ID 单行查询本地绑定,存在绑定时调用
`ExternalKnowledgeService.retrieve`,否则执行现有本地路径。外部结果附加
`diagnostics.external`,不运行本地向量、全文或图谱检索。
### 8.1 服务地址
`ExternalKnowledgeService` 区分两个入口:`retrieve` 是生产检索路径,只发出一次检索请求;
`testRetrieval` 仅用于保存绑定前的验证,额外在同一超时预算内确认 RAGFlow 图谱与知识编排
能力可用,能力缺失时返回 `EXTERNAL_KB_CAPABILITY_UNAVAILABLE`。生产检索不执行该能力预检,
因此正常对话不会多一次 detail 请求,也不会出现仅属于保存校验的错误码。
- 只接受 `http:` 和 `https:`。
- 拒绝用户名、密码、查询和片段。
- Adapter 负责规范化 Provider 所需 API 根路径,避免用户填写完整操作端点。
- 请求只能拼接 Adapter 内置相对路径,远端目录项或错误不能改变目标 URL。
- 重定向默认拒绝;若某 Provider 的受支持部署确需重定向,只允许同源且不得跨跳转发送
Authorization。
- 当前全局“内网兼容模式”决定是否允许 HTTP 和非标准 HTTPS 证书;操作开始后冻结该值。
- HTTP 或实际使用证书例外时,Main 返回 `transportSecurity` 风险状态。用户必须针对规范化
后的当前地址明确确认,才能保存实例或发送 API Key;地址变化立即使旧确认失效。
- 不进行公共云回退,不把自部署地址替换成 Provider 官方地址。
`retrieveMany` 对传入知识库 ID 去重并读取知识库,使用 `Promise.all` 逐库检索;只有选中
需要向量的本地库时才准备本地查询 Embedding。当前没有额外的 Provider 并发队列。单库
失败写入 `response.diagnostics.failure` 并保留空结果,成功零命中没有 failure;调用方
取消仍向上传播。当前返回 `{knowledgeBaseId, response}[]`,没有独立的 Outcome 状态联合。
用户明确配置内网地址属于产品用途,因此不能采用禁止私网地址的通用 SSRF 规则。安全边界
依靠显式实例配置、Main-only 请求、固定端点、无任意方法和无远端 URL 跟随。
服务检索与绑定验证共用同一裁剪逻辑:先按绑定的结果数与单片段限制裁剪,并限制该次片段
总量为 48,000 字符。
`retrieveMany` 展平各库结果并按库内 rank 稳定排序,再轮转分配 48,000 字符的片段预算:
先处理各库第 1 条,再处理各库第 2 条,同 rank 保持输入库顺序。不会先耗完整个库再轮到
下一个库;仍没有固定的每库最低字符配额。关联的本地上下文 groups 随相应结果使用另一份
48,000 字符预算。二次裁剪会标记 `context.truncated`,但第一次标准化裁剪不提供完整的
远端原始条数与截断诊断。
### 8.2 请求限制
聊天预检索、检索测试相关 IPC 和 `KnowledgeMcpGateway` 已使用统一检索入口。引用转换
复用 `knowledge-reference.ts`,聊天与 MCP 仍按现有授权范围和各自输出预算处理结果。
MCP 在总预算裁剪后按库内 rank 交错选择结果,再应用工具条数和字节上限;有失败且
没有任何片段时返回工具错误。预检索只在全部库失败时报告整体失败,成功零命中伴随其他
库失败时保留降级诊断。这两种上层呈现并不完全相同。
- 默认超时 15 秒,用户可在 1 至 60 秒内调整。
- 目录、详情和检索分别设置响应字节上限。
- JSON 解析后仍执行深度、数组数量、字符串长度和未知字段策略。
- 默认不自动重试。用户重试或上层下一次检索会产生新请求。
- 取消、应用关闭和实例删除立即终止相关请求。
- 日志只记录 Provider、实例 ID、操作、状态码、稳定错误码、耗时和字节数。
## 8. 引用显示与历史数据
会话契约和数据库保存外部定位及有界片段。外部引用弹窗读取保存的片段,显示 Provider、
实例 ID、远端库和实际返回的定位及评分;不请求本地 `referenceContext`,不显示本地
`openSource` 操作。当前没有远端来源打开或引用刷新请求,`sourceUrl` 是可选契约字段,
三家现有映射没有填充它。
App 的 `source-references` 事件使用共享 `knowledgeReferenceKey` 去重传入引用,再合并
尚未被替代的旧引用,最多保留 20 条。键包含 Provider、实例、远端库、远端文档和片段 ID、
定位与片段正文;外部结果缺少本地 ID 时也不会仅因本地 ID 相同而被合并。引用弹窗按
`context.locator ?? external.location ?? locator` 选择一个定位,只显示一次。
移除绑定后历史片段仍可阅读;当前弹窗不查询实例状态,也不生成“连接已移除”标记。
实例名称没有作为独立字段存入外部 locator,不能承诺删除实例后仍显示其名称。
2026-09-13 Dify 桌面报告已验证实时搜索引用通过生产 `conversations.saveLocal` 保存、
重载并打开弹窗。会话由验证程序标记为测试记录,没有模型生成答案;最终引用去重与定位
修正后的回归状态单独记录在[实施进度](./progress.md)。
后续 `kb-full-app-live` 则通过完整 App 输入区真实发送并保存模型答案,再点击引用。
引用片段、远端文档和片段 ID 与真实检索记录一致,无伪造会话或事件;此证据覆盖 Dify
单库短答案。三家生产 IPC 生成检查还验证了 `task_events` 中的文本、引用和选中库范围。
## 9. Provider 映射
本节记录 2026-09-03 核对的初始公开接口。实施时应把每个受支持版本的真实响应保存为
去敏 fixture,并以 fixture 和真实实例测试作为发布依据。
本节区分当前客户端行为与历史接口探测。历史响应和官方文档只说明当时观察到的接口,
不证明当前桌面已完成验收。
### 9.0 真实实例验证基线
| 历史日期 | 证据与限制 |
| --- | --- |
| 2026-09-09 | [基线报告](./external-knowledge-probe-baseline.json):累计 107 次只读请求,最终归档脚本 19 次;当时 HTTPS 放宽证书校验 |
| 2026-09-10 | [基础报告](./external-knowledge-probe-2026-09-10-strict.json)与[扩展报告](./external-knowledge-probe-2026-09-10-extended.json):共 33 次只读请求,其中检索 17 次;HTTPS 显式开启证书校验,RAGFlow 为 HTTP |
历史结果包含 Dify 0.15.8 详情 405、检索零命中,Dify 1.17.0 详情成功及 3 条非空结果;
FastGPT 三种模式和重排非空;RAGFlow 图谱开关产生不同结果,但 Knowledge Compilation
没有配置证据。FastGPT/RAGFlow 产品版本、Dify 0.15.8 非空结构及真实错误矩阵仍未确认。
逐项数量、脚本命令和当时测试结果由[实施进度](./progress.md#验证证据)维护。
2026-09-13 新增当前产品证据:[生产验证摘要](./external-knowledge-validation-2026-09-13.json)
记录真实 Electron safeStorage 持久化重开、生产服务与本机 HTTP MCP,以及 Dify 桌面。
服务结果与 MCP 引用数分别为 Dify 配置 1 的 0/0、Dify 配置 2 的 2/2、FastGPT 的 2/2、
RAGFlow 的 1/1。报告配置编号不是版本识别,历史版本差异不据此重新认证。
### 9.1 Dify
| 操作 | 接口 |
| --- | --- |
| 列表 | `GET /v1/datasets` |
| 检索 | `POST /v1/datasets/{dataset_id}/retrieve` |
| 认证 | `Authorization: Bearer <knowledge-api-key>` |
固定使用 `GET /v1/datasets`、`GET /v1/datasets/{id}` 和
`POST /v1/datasets/{id}/retrieve`。默认省略整个 `retrieval_model`,远端优先使用知识库
当时配置,未保存时由远端选默认;覆盖时发送完整的本地 `retrievalModel`,不逐字段合并。
结果从 `records[].segment`、文档和分数取值。
Adapter 只接受 Knowledge Service API Key,不调用应用 `/info`、Chat 或 Workflow。默认不
发送 `retrieval_model`,沿用远端知识库配置。用户选择覆盖时,Adapter 必须一次收集并验证
当前接口要求的搜索方式、Top K、阈值、`reranking_mode`,以及相应的 Rerank Provider/模型
或关键词/向量权重与 Embedding Provider/模型;缺少任何必填标识时不构造请求。响应从
`records[].segment`、`score`、文档名称和元数据提取标准化引用。
客户端把详情 405 分类为不兼容,绑定表单允许详情失败后继续检索验证,不实现版本探测或
自动遍历目录的详情回退。Scoped Key 可尝试手工 ID,但当前产品的真实受限密钥验收仍待完成。
Metadata、多模态、摘要索引、子块、附件和 Pipeline 没有进入当前简化目录或结果映射。
Dify 当前文档限制查询不超过 250 字符。GoodBuddy 不静默截断;超出时检索测试和聊天
诊断返回 Provider 限制,并允许后续单独设计查询压缩策略。
官方来源:[Knowledge API](https://docs.dify.ai/en/api-reference/guides/knowledge)、
[列表](https://docs.dify.ai/en/api-reference/knowledge-bases/list-knowledge-bases)、
[详情](https://docs.dify.ai/en/api-reference/knowledge-bases/get-knowledge-base)、
[检索](https://docs.dify.ai/en/api-reference/knowledge-bases/retrieve-chunks-from-a-knowledge-base-test-retrieval)、
[缺省配置实现](https://github.com/langgenius/dify/blob/main/api/services/hit_testing_service.py)。
### 9.2 FastGPT
| 操作 | 接口 |
| --- | --- |
| 列表 | `POST /api/core/dataset/list` |
| 详情 | `GET /api/core/dataset/detail?id=...` |
| 检索 | `POST /api/core/dataset/searchTest` |
| 认证 | `Authorization: Bearer <api-key>` |
固定使用 `POST /api/core/dataset/list`、`GET /api/core/dataset/detail?id=...` 和
`POST /api/core/dataset/searchTest`。列表 `data[]` 保留 Dataset 与文件夹,按 `parentId`
读取层级,在当前父级响应内搜索和分页;文件夹只用于浏览。
列表响应同时包含文件夹和知识库,只有 `type = dataset` 可以绑定。检索映射 `datasetId`、
`text`、Token `limit`、`similarity`、`searchMode` 和 `usingReRank`。初始版本不发送查询优化
字段,避免调用 FastGPT 配置的 LLM。
响应从 `q`、`a`、`sourceName`、`sourceId`、`collectionId` 和 `score` 构造片段与引用。
检索接受业务码 200 的 `data.list[]` 与旧手工文档的 `data[]`,不递归猜测嵌套结构。
`q` 必需,`a` 仅在存在时追加;评分数组保留类型、值和可选 index,数值评分独立处理。
当前客户端已通过 FastGPT 真实服务与本机 HTTP MCP,均返回 2 条。报告确认结果契约与
引用映射有效,没有逐项归档评分包装,故旧包装和全部评分变体仍以定向测试证据为限。
FastGPT 4.15.0 起部署实例的 `/apidoc/devapi` 是接口事实来源,手工文档可能落后。支持矩阵
必须按真实版本测试;不能因为健康页可访问就假定列表和检索 Schema 相同。`searchTest`
可能记录 `SEARCH_TEST` 等远端审计并更新 API Key 用量;Embedding 和 Rerank 也可能产生
费用。GoodBuddy 将这些行为作为检索成本显示,不宣称该接口没有副作用。
请求映射 `datasetId/text/limit/similarity/searchMode/usingReRank`,不发送查询扩展。
Embedding、Rerank 和远端 API 审计仍可能产生用量;不调用 FastGPT 生成最终回答。
官方来源:[API 说明](https://doc.fastgpt.io/zh-CN/openapi/intro)、
[旧手工接口页](https://doc.fastgpt.io/zh-CN/openapi/dataset)、
[生成 Schema](https://github.com/labring/FastGPT/blob/main/packages/global/openapi/core/dataset/api.ts)、
[检索处理器](https://github.com/labring/FastGPT/blob/main/projects/app/src/pages/api/core/dataset/searchTest.ts)、
[结果 Schema](https://github.com/labring/FastGPT/blob/main/packages/global/core/dataset/type.ts)。
4.15.0 起应核对部署实例生成文档;历史检查只取得云端 `/apidoc/` 应用壳。
### 9.3 RAGFlow
| 操作 | 接口 |
| --- | --- |
| 列表/详情 | `GET /api/v1/datasets` |
| 检索 | `POST /api/v1/retrieval` |
| 认证 | `Authorization: Bearer <api-key>` |
目录和按 ID 详情均使用 `GET /api/v1/datasets`,检索使用 `POST /api/v1/retrieval`。
请求发送 `question`、单个 `dataset_ids`、`page: 1`、`page_size: 20`、阈值、向量权重、
`knn_top_k` 及显式的图谱、知识编译布尔值。HTTP 成功且业务码为 0 才读取 `data.chunks`。
文档名映射 `document_keyword`,位置保存返回的 `positions`,分数取原始 `similarity`。
检索映射 `question`、`dataset_ids`、`page_size`、`similarity_threshold`、
`vector_similarity_weight`、`knn_top_k`、重排、`use_kg` 和
`include_knowledge_compilation`。不发送已弃用的 `top_k`。响应从 `data.chunks` 和
`data.doc_aggs` 提取片段、文档、位置和原始相似度。
`graphEnabled` 根据有效非零 `graphrag_task_finish_at` 判断;仅配置
`parser_config.graphrag.use_graphrag` 不算完成。知识编译依据非空
`compilation_template_group_id`。启用任一能力时,服务在检索前重新读取详情确认。
当前不保存完整能力快照,不区分图谱未配置与构建中,也不检测 Provider 版本。
图谱规则:
官方来源:[HTTP API](https://ragflow.io/docs/dev/http_api_reference)、
[当前文档原文](https://github.com/infiniflow/ragflow/blob/main/docs/references/http_api_reference.md)、
[v0.24.0 文档](https://github.com/infiniflow/ragflow/blob/v0.24.0/docs/references/http_api_reference.md)。
旧文档只声明 `top_k`,当前客户端发送 `knn_top_k`;请求成功不证明候选数参数生效,
也不是旧部署版本兼容承诺。
- `parser_config.graphrag` 或等价详情字段作为已有图谱的证据,不写回。
- `use_kg` 是检索时开关,只在已有图谱和受支持版本均确认后发送。
- Knowledge Compilation 与旧 GraphRAG 分别建模。
- `include_knowledge_compilation` 使用本地保存的显式布尔值,初始默认 false;即使服务端
默认 true,也不依赖字段省略获得隐式行为。
- Provider 没有返回图节点或路径证据时,GoodBuddy 不生成图谱引用。
## 10. 验证与验收
## 10. 检索编排
三家 Provider 已通过生产 `KnowledgeService` 与本机 HTTP MCP,真实 Electron
safeStorage 加解密和持久化重开成功。Dify 桌面已完成绑定、参数更新、重启、三档窗口宽度
及历史引用检查。验证方法、原始报告边界和调用计数见[实施进度](./progress.md)及
[脱敏摘要](./external-knowledge-validation-2026-09-13.json)。本轮文档只读取报告,没有
另发 Provider 或模型请求,也未改动历史探测 JSON。
```text
retrieveMany(knowledgeBaseIds, query, requestContext)
├─ load and authorize GoodBuddy knowledge bases
├─ partition local and external bindings
├─ local: existing retrieval path
├─ external: bounded concurrent adapter requests
├─ validate and normalize each response
├─ allocate deterministic context budget
└─ return evidence plus per-library diagnostics
```
当前 Portal 与控件修改后的聚焦测试、全量回归、类型检查、lint、工作区及暂存区 diff
检查均已通过。精确计数、运行命令、耗时、执行期间文件稳定性和原失败修正记录统一见
[实施进度](./progress.md#2026-09-13-当前工作区实现与分组验证)。
预检索和 `knowledge_search` 工具必须调用同一个 `retrieveMany`。如果只修改聊天预检索而
未修改 `KnowledgeMcpGateway`,`auto` 和 `always` 会产生不同能力,这是阻断发布的问题。
后续真实生成使用已保存的默认模型,凭据在 Main 通过 safeStorage 解密。三家 Provider
的 `always` 及 Dify `auto` 已产生有依据和引用的短答案;Dify `auto` 经进程内
`ModelToolProvider → KnowledgeMcpGateway` 执行,不能当成本机 HTTP MCP 或远程 ACP
验收。FastGPT/RAGFlow 首轮因直接比较序列化字符串产生验证断言失败,解码请求和嵌入
证据 JSON 后重跑通过;报告保留初始结果。完整 App 输入区的发送、真实答案及引用点击
仅覆盖 Dify。Dify/FastGPT 使用严格 HTTPS,RAGFlow 和模型使用已配置的 HTTP。
### 10.1 上下文预算
请求精确增量与视觉 fixture 边界见[后续验证摘要](./external-knowledge-follow-up-2026-09-13.json)
及[实施进度](./progress.md#2026-09-13-ui-修正与真实生成复验)。不公开模型端点、私有配置
标识、凭据和答案正文。这些短答案检查没有评估广泛问答质量。
- 每个知识库先保留不超过其 `resultLimit` 的结果。
- 先给每个成功知识库一个最小有界配额,再按稳定顺序分配剩余字符。
- 不用不同 Provider 原始分数决定跨库优先级。
- 片段截断在 Main 完成并在引用中标记。
- 相同实例的多个绑定仍按绑定分别请求,除非 Provider 明确支持且 Adapter 实现等价的多
Dataset 请求;合并不得改变每个绑定的配置和诊断。
FastGPT/RAGFlow 完整桌面专属操作、复杂或混合问答、删除和真实错误矩阵仍需补验。
Linux Host 的两条网络路径可达,但验证程序无法解密已有 SSH 凭据,在认证前返回
`E_SAFE_STORAGE_DECRYPT_FAILED`,真实远程 forced-preflight 未执行。本机 HTTP MCP
成功不覆盖该远程路径。剩余 Provider 版本和高级参数效果也不能由基础检索成功推断。
### 10.2 失败结果
原设计的分项探测 UI、最低版本矩阵、详细诊断、地址确认和取消交互未全部落地。它们是
待核对的产品差异,不能以补建 Adapter Registry 或版本框架代替验收决策。
返回结构包含每个知识库的:
`scripts/external-knowledge-probe.mjs` 仍可只读探测列表、详情、检索;`--extended` 增加
Dify 配置覆盖、FastGPT 重排和 RAGFlow 两种开关。报告区分成功零命中与失败,不输出
凭据或业务正文。脚本不打包到桌面或 Agent,脚本执行本身不覆盖 UI、IPC、存储和引用链路。
```ts
interface KnowledgeRetrievalOutcome {
knowledgeBaseId: string
status: 'success' | 'empty' | 'failed' | 'cancelled'
durationMs: number
resultCount: number
errorCode?: ExternalKnowledgeErrorCode
truncated: boolean
}
```
## 11. 需求追踪
至少一个 `success` 或 `empty` 表示检索编排完成;全部 `failed/cancelled` 时上层显示整体失败。
## 11. 引用与来源打开
外部结果不能进入依赖本地数据库的 `referenceContext` 和 `openSource` 路径。
- 引用详情直接使用请求时保存的标准化片段和定位。
- Main 可以按远端片段 ID 提供一次只读重新读取,但初始版本不要求。
- Provider 返回 URL 只作为不可信元数据保存。没有独立 URL 规则和真实实例验证前,
Renderer 不显示打开链接操作。
- 活动和会话持久化保存标准化引用,其中包含实际用于回答的有界远端片段;这属于引用
留存,不是批量同步或本地索引。删除会话或对应活动时按现有生命周期删除该副本。
- 实例删除后,历史引用仍显示 Provider、知识库、文档名称和已保存片段,但标记连接已移除。
## 12. 支持矩阵与版本变化
每个 Adapter 维护明确的最低版本、已验证版本和 `adapterVersion`。连接测试优先读取可靠的
版本接口或响应头;无法识别版本时,只允许经真实请求验证过的基础列表和检索字段,高级字段
保持不可用。
Adapter 可以保存少量受控事实,例如“目录接口可用”“详情表明已构建图谱”,但不尝试从
远端拼出完整 capability manifest,也不计算 capability fingerprint。
- `adapterVersion` 相同且实例仍在支持矩阵内:解析已保存配置并执行。
- Adapter 升级:使用明确的配置迁移或把绑定标记为 `config-invalid`,不猜测字段迁移。
- 实例版本离开支持矩阵:停止发送高级字段,并要求重新测试和确认。
- 新能力出现:保持默认关闭,用户主动配置后才发送。
- RAGFlow `use_kg` 还要求当前远端知识库存在已构建图谱的证据。
## 13. 安全与隐私
- API Key 在用户输入和保存 IPC 中短暂存在,随后使用系统安全存储加密;保存后不返回
Renderer,也不进入 Runtime、日志和普通元数据。
- 请求不附带完整会话、附件、本地知识库证据、系统提示、模型配置或其他 Provider 结果。
- Provider 结果使用与本地知识证据相同的不可信上下文标记。
- Adapter 拒绝响应中的原型污染键、过深对象、超长数组和非有限数值。
- 远端错误正文只用于 Main 内映射,日志与 UI 使用稳定错误码和短消息。
- 外部知识库不能扩大当前对话显式选择的 GoodBuddy 知识库 ID。
- Ask 与 Execute 对知识检索均为只读,Execute 不获得额外 Provider 权限。
- Provider 配置中的模型 ID、背景文本和 Metadata 条件按独立长度限制验证,不能携带凭据。
- HTTP 或证书例外需要按当前规范化地址确认凭据传输风险,地址变化后重新确认。
## 14. 测试
### 14.1 契约测试
- 每个 Provider 的列表、详情、检索成功和错误 fixture。
- 未知字段、缺字段、错误类型、超长内容、非有限分数和超大响应。
- Dify 查询长度、FastGPT 文件夹过滤、RAGFlow 图谱和 Knowledge Compilation 字段。
- Provider 配置判别联合拒绝类型错配和未知字段。
### 14.2 安全测试
- 保存完成后的 Renderer 快照、IPC 返回值、日志、错误、活动和导出中不存在明文 API Key;
`replace` 输入只在当前表单和单次 IPC 请求中短暂存在。
- `keep/replace/clear` 凭据语义和安全存储不可用路径。
- 地址中的凭据、查询、片段、危险 scheme、跨源重定向和远端 URL 注入被拒绝。
- 取消、超时、响应大小上限和应用关闭释放请求。
### 14.3 集成测试
- 实例保存、目录读取、手工 ID、绑定创建、编辑和本地移除。
- 同一远端目标重复绑定被拒绝。
- 删除实例的引用保护和级联本地移除确认。
- 本地与外部 `retrieveMany` 部分成功、全部失败和确定性上下文预算。
- 聊天 `always` 与 Agent `knowledge_search` 使用同一 Provider-aware 编排。
- 历史引用在实例或绑定移除后仍可读。
### 14.4 UI 测试
- 类型切换、异步旧响应、目录搜索、刷新和手工模式。
- 两栏与窄窗口堆叠、键盘、焦点恢复和离开保护。
- Provider 字段显隐、失效配置、RAGFlow 图谱说明和恢复默认。
- 外部详情不出现导入、同步、分块和本地图谱操作。
- 错误、零结果、部分失败和 Provider 引用显示。
### 14.5 真实实例验收
每个 Provider 至少选择一个明确版本的自部署实例,记录:
- 版本、部署方式和认证范围。
- 列表与检索的请求/响应 fixture,移除凭据和业务正文。
- 默认与专属配置是否真实生效。
- 401、403、404、429、超时和取消行为。
- 查询、片段、引用和诊断的实际上限。
Mock 或公开文档通过不能代替真实实例验收。
## 15. 实施顺序
这里的顺序只用于控制代码依赖,不裁剪产品设计:
1. 建立共享判别契约、数据库迁移和凭据存储。
2. 实现 HTTP 边界、错误映射和 Adapter Registry。
3. 逐个实现三种 Provider,并用 fixture 完成契约测试。
4. 实现实例管理和目录 IPC。
5. 扩展创建向导、统一列表和外部详情。
6. 扩展检索测试、引用和 `retrieveMany`。
7. 接入聊天预检索与 `KnowledgeMcpGateway`。
8. 完成真实实例、安全、响应式和跨平台验收。
## 16. 发布条件
- 三种 Provider 的受支持版本和限制已在产品界面或发布说明中可查。
- 真实实例列表、绑定、检索和错误矩阵均通过。
- 没有写入远端知识内容的代码路径。
- API Key 泄漏测试、地址边界和响应大小测试通过。
- 本地知识库创建、同步、检索、图谱和引用回归测试通过。
- `auto`、`always`、检索测试和 MCP 知识工具得到一致的外部知识能力。
## 17. 需求追踪
| 需求 | 技术落点 | 验证 |
| 需求 | 当前实现落点 | 待验收重点 |
| --- | --- | --- |
| `EK-FR-01` 至 `EK-FR-03` | 实例契约、Store、实例 IPC、凭据和测试状态 | 实例集成测试、安全测试 |
| `EK-FR-04`、`EK-FR-05` | Adapter 目录与详情接口、目录 IPC | Provider fixture、手工 ID 集成测试 |
| `EK-FR-06` 至 `EK-FR-09` | Provider 判别联合、Adapter 配置版本、Renderer 本地字段定义 | 契约测试、UI 字段显隐测试 |
| `EK-FR-10`、`EK-FR-11` | `KnowledgeBase.kind`、绑定表、统一 `retrieveMany` | 本地与外部混合检索测试 |
| `EK-FR-12`、`EK-FR-13` | 引用定位判别联合、结果标准化、检索测试 IPC | 引用持久化与 UI 测试 |
| `EK-FR-14` | 稳定错误码、逐库 Outcome、取消和超时 | 错误 fixture、部分失败测试 |
| `EK-FR-15` | 外键限制、绑定本地删除、无远端写接口 | 删除生命周期测试 |
| `EK-FR-16` | Main-only HTTP、固定端点、传输风险确认 | 地址、证书和凭据泄漏测试 |
| `EK-FR-01` 至 `EK-FR-05` | 实例 Service/Store、目录 Client、IPC 与管理表单 | 服务已验证;补充 FastGPT/RAGFlow 桌面和受限目录手工 ID |
| `EK-FR-06` 至 `EK-FR-10` | 共享 Provider 配置、绑定事务、统一列表与两栏表单 | Dify 参数与布局已验证;其他 Provider 专属参数及未提供字段待核对 |
| `EK-FR-11` 至 `EK-FR-14` | `retrieveMany`、预检索、MCP、共享引用与 failure 字段 | 三家 IPC 短答案与 Dify 完整输入区引用已验证;混合失败、广泛问答与远程待补 |
| `EK-FR-15` | 唯一约束、外键保护、顺序本地删除 | 中途失败刷新、远端内容不变 |
| `EK-FR-16` | Main 固定端点、有界请求与数据说明 | 当前桌面传输行为与 PRD 差异 |
@@ -4,13 +4,96 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 状态 | Portal 与控件已修正,Dify 完整 App 真实问答及当前自动回归已通过 |
| 版本 | 0.2 |
| 日期 | 2026-09-13 |
| 产品需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 行为规则 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 全局规范 | [GoodBuddy UI 设计系统](../../../UI-DESIGN.md) |
## 当前实现与验收边界
`KnowledgeWorkspace.tsx` 已接入 `ExternalKnowledge.tsx`,不再处于只有界面草案的阶段。
当前操作与控件如下;后续编号章节保留目标布局、文案和验收要求,其中超出本节的内容
仍待核对,不能作为已实现或已通过桌面验收的描述。
| 区域 | 当前实现 |
| --- | --- |
| 页面入口与列表 | 标题区有“管理外部实例”和新建操作;本地和外部同一列表,按显示名、远端名、Provider、实例名搜索,原生 Select 筛选来源 |
| 实例管理 | 560px 紧凑 Modal 内切换实例列表、编辑表单和删除确认;标题随状态变化,行内提供测试、编辑、启停和移除,显示地址 origin、凭据、探测状态、绑定数与本地化测试时间 |
| 实例保存与测试 | 新建直接显示密码输入;编辑才提供 keep/replace/clear,清除需要确认。眼睛图标切换显隐并提供可访问名称和按下状态。保存后回列表测试目录,连接说明默认折叠;HTTP 地址下显示传输说明 |
| 创建类型 | 共享 `SegmentedControl` 选择本地、Dify、FastGPT、RAGFlow;本地继续使用现有创建向导,外部挂载独立绑定表单 |
| 来源选择 | 实例使用复用既有 picker 样式的富信息菜单,条目显示名称、地址 origin 与凭据状态;目录使用搜索框、目标按钮、刷新和分页,FastGPT 文件夹可进入与返回根目录;共享分段控件切换手工 ID |
| 配置 | 常规窗口来源与参数两栏,宽度不超过 959px 时单栏;参数使用数字输入、原生 Select 与共享滑动 Switch 样式,没有 Slider 或自由 JSON 编辑器 |
| 测试与保存 | 用户填写查询,测试成功后可保存,零命中允许添加;保存会再次发出检索请求。查询、目标或参数改变会使测试失效,更换现有绑定目标要求确认 |
| 外部详情 | 共享 `PageTabs` 提供概览、检索配置和检索测试;概览显示远端 ID、推导状态和绑定验证时间,可刷新详情、用于对话、重新绑定、管理实例或移除 |
| 参数编辑 | 复用绑定表单,保存按钮为“保存检索参数”;恢复默认只重置表单。配置页签访问后保持挂载,切换详情页签保留草稿 |
| 检索结果 | 外部组件显示总耗时、返回数量、片段、定位、实际 ID 和原始评分,包括 FastGPT 评分数组;不显示本地全文、向量、图谱通道或本地上下文操作 |
| 聊天与引用 | 统一选择器显示 Provider、实例与状态;外部引用打开保存片段,显示实例 ID 和远端定位,不请求本地上下文或打开原始来源 |
创建类型分段控件复用全局不收缩规则,不能被目录和参数表单压扁;列表长名称的宽度约束见
[知识库 UI 设计](./ui-design.md#2-信息架构)。新建时自动名称跟随目录选择,手动改名和已保存
名称不被覆盖,具体规则见[逻辑设计](./external-knowledge-logic-design.md#5-创建与编辑)。
实例 Modal 和其他浮层统一参与[原生浏览器遮挡处理](../assistant-workbar/browser-tabs-technical-design.md#4-renderer-与-viewport),
不再仅验证没有浏览器的独立 Portal。
原 `knowledge-field` 的控件样式依赖知识库工作区祖先,实例 Modal 经 `createPortal` 挂到
body 后丢失该作用域。当前 `Field` 使用全局 `.field` 和标签 span,密码也复用同一字段
结构,确保 Portal 内的字体、输入边框、背景和焦点样式与应用一致。弹窗宽度为
`min(560px, 100%)`,内容可滚动;操作区粘附底部,范围徽标靠左、按钮靠右,滚动留白
避免键盘焦点被底部操作区遮住。通用参数、Provider 参数和认证使用 fieldset/legend 分组。
Switch 按标签文字在左、滑动开关在右排列,保持 `role="switch"`。实例菜单支持方向键
打开与移动、Home/End、跳过禁用项、Enter 选择和 Escape 返回触发按钮;目录空状态使用
共享 `EmptyState`,无搜索结果可清空查询,无目录可切换手工 ID。已选目标悬停不覆盖
选中背景,保存和测试分别显示对应忙碌文案。
保存、删除等成功信息及实例管理中的异步错误使用应用通知;绑定测试、详情刷新和删除
中途失败保留就地恢复所需的错误。实例有绑定时,确认后依次移除本地绑定再删实例;中途
失败刷新已完成的移除,远端内容不变。
当前已实现过期响应丢弃、Modal 焦点管理、`PageTabs`、标记了 `role="switch"` 的开关和
窄窗口 CSS。2026-09-13 Dify 已在当前生产 BrowserWindow、Main、Preload、Renderer 和
IPC 上完成实例保存、目录选择、检索后绑定、参数更新、重启及历史引用检查,详见
[脱敏验证摘要](./external-knowledge-validation-2026-09-13.json)。验证程序仅在 Main 的实例
保存 Handler 边界替入已有密钥,未将真实密钥放入 Renderer。
1440、900、700 窗口宽度对应报告 viewport 1439、899、699,均无 body 横向溢出。
宽屏配置两栏、窄屏单栏,窄屏列表可见;原生 ArrowRight 可以激活检索页签。已保存并
重开的参数为返回 2 条、超时 15 秒、单片段 200 字符。生产搜索返回两条引用,验证程序
保存带测试标记的会话后重载,引用弹窗显示 Provider、片段和快照说明,焦点在弹窗内,
没有本地上下文或打开来源操作。此检查没有生成模型答案。
上述早期检查没有生成答案。后续完整 App 已从实际输入区选择 Dify 知识库并点击发送,
得到已保存的真实模型短答案,再点击引用核对片段及远端文档/片段 ID;没有伪造消息或
引用。浅深主题均检查了真实 Portal 编辑器的共享字体、背景、560px 宽度与无横向溢出。
另有真实 Electron 视觉 fixture 覆盖浅深主题、四档尺寸及原生键盘操作;其 IPC 是假数据,
只用于界面、焦点和布局检查。三家生成的生产 IPC 验证中,引用展示使用隔离 DOM;只有
Dify 完成了完整 App 的发送与点击引用。计数与方法边界见
[实施进度](./progress.md#2026-09-13-ui-修正与真实生成复验)和
[后续摘要](./external-knowledge-follow-up-2026-09-13.json)。当前 UI 修正后的聚焦测试、
全量回归、类型检查、lint 与工作区/暂存区 diff 检查均已通过。App 测试已按实际可访问
富信息菜单交互替换旧 Select 操作,精确计数由实施进度维护。FastGPT/RAGFlow 完整桌面专属操作和
广泛问答质量仍待验收。
与下文目标设计相比,当前还存在以下边界:
- 没有保存前按地址传输风险确认,也没有接入独立证书例外设置;只有传输说明。
- 没有目录或检索的 IPC 取消入口;丢弃旧响应不等于中止网络请求。测试期间绑定表单整体
禁用,不能把其“取消”按钮描述为正在进行的请求可随时取消。
- Dify 版本、Metadata、摘要、多模态和 Pipeline 状态没有进入当前界面;RAGFlow 只接收
图谱完成与知识编译可用布尔证据,没有三态构建展示或版本支持矩阵。
- 列表和概览没有持久化的最近成功检索时间;概览显示 `lastVerifiedAt`。当前也没有自动
`remote-missing/config-invalid` 状态、完整裁剪诊断或历史引用“连接已移除”标签。
- 没有覆盖所有关闭、类型切换和手工模式切换的未保存草稿离开确认;实例列表使用可换行
列表而非表格,未单设 720px 表格转换规则。
## 目标设计与待验收要求
以下保留既有产品布局和交互目标。当前事实以上节为准;历史 Provider 探测用于解释能力
边界,不作为下述界面已经呈现或已通过产品验证的证据。
## 1. 信息架构
外部知识库不增加左侧主导航和知识库一级页签。现有知识库页面结构保持:
@@ -82,7 +165,7 @@ RAGFlow
## 4. 管理外部实例
点击“管理外部实例”打开大尺寸 Modal。Modal 使用标题、可滚动内容和固定操作区,遵循
点击“管理外部实例”打开最大 560px 的紧凑 Modal。Modal 使用标题、可滚动内容和粘附底部操作区,遵循
全局焦点陷阱、Escape、背景隔离和焦点恢复规则。
```text
@@ -237,6 +320,14 @@ Rerank 用量。`
右侧配置固定分为“通用配置”和“{Provider} 配置”,不创建嵌套卡片。高级字段可以使用
一个折叠区,但核心检索方式、阈值和 RAGFlow 图谱检索保持直接可见。
第一版遵循 [PRD 产品决策](./external-knowledge-prd.md#2-产品决策)的只读范围。面板固定说明:
`参数仅保存在 GoodBuddy,用于此绑定的后续检索,不会修改远端知识库设置。`
编辑页保存按钮使用“保存检索参数”;恢复默认仅重置当前表单,保存后作用于当前绑定。
远端索引、模型、图谱构建和 Pipeline 配置只展示状态,不提供编辑或执行操作。
Dify 的“使用知识库默认配置”开启时说明:`每次检索沿用远端当前设置,远端设置变化会影响结果。`
关闭后,客户端显式检索参数随请求发送,不写回远端默认配置。
控件选择:
- 开关:图谱检索、Score 阈值启用、Rerank、使用 Dify 知识库默认配置。
@@ -258,6 +349,33 @@ Rerank 用量。`
图谱状态”并禁用。Knowledge Compilation 单独显示为 `包含 Knowledge Compilation`,默认
关闭,不把它和旧 GraphRAG 合成同一开关。
### 7.2 实测能力驱动的界面适配
界面不能只按 Provider 名称展示全部开关,必须按实例和当前远端知识库的能力证据处理:
| Provider | 已实测可启用 | 保持隐藏或禁用 | 界面依据 |
| --- | --- | --- | --- |
| Dify `0.15.8` | 使用知识库默认配置;展示 `retrieval_model_dict` 中已出现的检索方式、Top K、阈值和 Rerank 配置 | Metadata、多模态、摘要和 Pipeline 状态 | 目录项能力;该版本详情 GET 返回 405,不能把详情失败显示成实例不可用 |
| Dify `1.17.0` | 默认配置、当前完整配置;详情中的文档统计、摘要索引、多模态、Metadata 和 Pipeline 只读状态 | 当前库没有字段定义时禁用 Metadata 条件;远端 Pipeline 和摘要配置不可编辑 | 详情 GET 和 3 条非空检索结果已实测;`child_chunks/files/summary` 仅按结果实际存在显示 |
| FastGPT 当前实例 | embedding、全文召回、混合召回;Rerank | 查询扩展、扩展模型和背景文本 | 三种模式和 Rerank 均有 200 非空实测;查询扩展会调用模型,初始版本不启用 |
| RAGFlow 当前实例 | 基础检索;具备完成证据时启用图谱检索 | 没有独立配置证据时禁用 Knowledge Compilation;Metadata 条件仍隐藏 | 相似度、向量权重和候选数请求已被接受,但未单独验证参数效果;候选数参数需按部署版本适配,详见技术设计;`graphrag_task_finish_at` 有效才启用图谱;`compilation_template_group_id` 有效才启用 Knowledge Compilation |
RAGFlow 列表项应区分三个图谱状态:“未配置”“构建中或未完成”“可用于图谱检索”。不能
仅因 `parser_config.graphrag.use_graphrag = true` 就显示为可用。实测数量见
[实施进度](./progress.md#验证证据),界面按当前远端知识库的配置和完成状态判断。
Knowledge Compilation 的请求被服务器接受但当前没有库提供配置证据,因此开关显示为禁用,
说明为“当前知识库未配置 Knowledge Compilation”。返回 200 或零结果都不能改变此状态。
Dify 实例行持续显示探测版本。`0.15.8` 的详情请求返回 405 时显示“此版本从目录读取配置”,
不显示错误状态;`1.17.0` 详情页增加只读能力行:文档可用数、Metadata 字段数、多模态、
摘要索引和 Knowledge Pipeline。没有配置的能力显示“未配置”,不隐藏成未知,也不提供会
写回 Dify 的编辑操作。
Dify 1.x 的 Metadata 条件使用受控字段编辑器:先从 `doc_metadata` 选择字段,再按字段类型
提供比较操作和值输入。当前库字段数为 0 时控件禁用并显示“当前知识库没有 Metadata 字段”。
不得允许自由 JSON,也不得把 `attachment_ids`、Pipeline 或摘要 Prompt 暴露为检索输入。
## 8. 外部知识库详情
选中外部绑定后,现有详情区按来源类型裁剪:
@@ -0,0 +1,96 @@
{
"date": "2026-09-13",
"kind": "sanitized-validation-summary",
"scopeNote": "Historical evidence before subsequent portal/control fixes and real-generation follow-up; not final regression evidence for the newer UI",
"followUpEvidence": "external-knowledge-follow-up-2026-09-13.json",
"sources": [
"kb-strict-validation/report.json",
"kb-desktop-ui-validation-20260913/report.json",
"goodbuddy-native-controls-fix-full.log",
"goodbuddy-native-controls-fix-full.result.json"
],
"automatedChecks": {
"status": "passed-before-subsequent-ui-follow-up",
"authoritativeCounts": "progress.md",
"fullApp": { "status": "passed", "evidence": "owner handoff" },
"fullSuite": {
"command": "npm test -- --maxWorkers=1 --reporter=verbose",
"status": "passed",
"exitCode": 0,
"evidence": ["goodbuddy-native-controls-fix-full.log", "goodbuddy-native-controls-fix-full.result.json"]
},
"typecheck": { "command": "npm run typecheck", "status": "passed", "evidence": "owner handoff after final code edits" },
"lint": { "command": "npm run lint", "status": "passed", "evidence": "owner handoff after final code edits" },
"diffCheck": { "status": "passed", "evidence": "owner handoff after final code edits" },
"sourceStability": {
"files": ["src/renderer/src/App.tsx", "src/renderer/src/App.test.tsx"],
"hashesStableThroughoutFullRun": true,
"evidence": "owner handoff"
},
"priorFailures": "resolved; exact intermediate and final counts are maintained only in progress.md"
},
"strictService": {
"boundary": "Production KnowledgeService/client and local HTTP MCP in real Electron; no interactive renderer or authenticated Linux execution",
"electronVersion": "43.2.0",
"safeStorageAvailable": true,
"safeStorageRoundTrip": true,
"retrievalHttpAttempts": 12,
"retrievalHttpResponses": 12,
"profiles": [
{ "provider": "dify", "profile": 1, "scheme": "https:", "strictTls": true, "catalogPassed": true, "detailHttpStatus": 405, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 0, "mcpToolSuccess": true, "mcpReferences": 0, "citationMappingValid": true, "retrievalHttpAttempts": 3 },
{ "provider": "dify", "profile": 2, "scheme": "https:", "strictTls": true, "catalogPassed": true, "detailHttpStatus": 200, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 2, "mcpToolSuccess": true, "mcpReferences": 2, "citationMappingValid": true, "retrievalHttpAttempts": 3 },
{ "provider": "fastgpt", "profile": 1, "scheme": "https:", "strictTls": true, "catalogPassed": true, "detailHttpStatus": 200, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 2, "mcpToolSuccess": true, "mcpReferences": 2, "citationMappingValid": true, "retrievalHttpAttempts": 3 },
{ "provider": "ragflow", "profile": 1, "scheme": "http:", "strictTls": null, "catalogPassed": true, "detailHttpStatus": 200, "bindingPassed": true, "persistedCredentialReopened": true, "responseSchemaValid": true, "retrievalResults": 1, "mcpToolSuccess": true, "mcpReferences": 1, "citationMappingValid": true, "retrievalHttpAttempts": 3 }
]
},
"desktop": {
"provider": "dify",
"phase": "completed",
"boundary": "Production electron-vite Main, preload, BrowserWindow, renderer and IPC; existing provider key substituted in Main at instance-save handler boundary",
"callsByKind": { "catalog": 4, "detail": 4, "retrieval": 6 },
"totalReadOnlyCalls": 14,
"allHttpStatus200": true,
"checks": [
"Instance saved through production IPC",
"Catalog selected and binding saved after live retrieval",
"Persisted binding reopened in isolated desktop profile",
"Local parameters saved through production binding update",
"Native ArrowRight moved focus and activated retrieval tab",
"Live search references persisted and reopened in production citation dialog"
],
"measuredViewportWidths": [1439, 899, 699],
"bodyOverflowAtMeasuredWidths": [0, 0, 0],
"configColumnsAtMeasuredWidths": { "1439": 2, "699": 1 },
"mobileListVisible": true,
"savedCommonConfig": { "resultLimit": 2, "requestTimeoutMs": 15000, "maxSnippetCharacters": 200 },
"resultCount": 2,
"localContextButtons": false,
"window": { "sandbox": true, "contextIsolation": true, "nodeIntegration": false },
"encryptedCredential": { "scheme": "electron-safe-storage", "decryptMatches": true, "plaintextAbsent": true },
"citationPersisted": { "count": 1, "referenceCount": 2 },
"citationDialog": { "snapshot": true, "provider": true, "snippet": true, "openSource": false, "focusInside": true },
"citationMethod": "Production knowledge.search returned live references; harness saved a labeled test transcript through conversations.saveLocal and opened it after reload. No model answer generated",
"isolatedProfileAndBuildRemoved": true
},
"remote": {
"lanTcpConnected": true,
"vpnTcpConnected": true,
"savedCredentialSource": "unreadable",
"hostKeyState": "verified",
"blocker": "E_SAFE_STORAGE_DECRYPT_FAILED",
"forcedPreflightLive": "blocked-before-authenticated-ssh",
"authenticatedSshAttempts": 0
},
"sessionAccounting": {
"source": "Strict report historical bounds plus desktop report; confirmed by owner handoff",
"unsurfacedFirstPassRetrievalMinimum": 0,
"unsurfacedFirstPassRetrievalMaximum": 12,
"earlierConfirmedRetrievalCalls": 12,
"strictRetrievalCalls": 12,
"desktopRetrievalCalls": 6,
"totalRetrievalMinimum": 30,
"totalRetrievalMaximum": 42,
"generatedModelAnswers": 0,
"limitation": "First pass retained no diagnostics, so its exact retrieval count cannot be reconstructed. Provider-side embedding, reranking and audit usage is not counted as generated answers"
}
}
+8 -1
View File
@@ -13,6 +13,10 @@
## 1. 背景
本文负责本地检索与分块增强。当前工作区另已接入外部实例、绑定和统一检索,其产品范围见
[外部知识库 PRD](./external-knowledge-prd.md),实现与待验收状态见[实施进度](./progress.md)。
外部绑定不进入下述本地分块、向量索引和图谱维护流程。
GoodBuddy 已具备本地多知识库、文件与目录同步、网页导入、SQLite FTS5、
OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来源引用。现有实现
优先建立了本地数据主权、安全边界和跨 Runtime 工具授权,但用户仍难以稳定
@@ -37,7 +41,10 @@ OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来
2. 保持 Electron Main、Preload、Renderer 的安全边界,Renderer 不直接读取
数据库、原文件或向量。
3. 保留“模型按需检索”,并新增“每次先检索”模式。后者必须由 Main 进程
预检索,不能只依赖提示词要求模型调用工具。
预检索,不能只依赖提示词要求模型调用工具。
`knowledge_search` 的 `limit` 与现有检索引擎对齐,接受 1–100,省略时返回最多 6 条;
调用方可请求超过 8 条。授权范围、检索排序和工具结果字节预算继续生效,因此实际
条数可能少于请求值。
4. 知识库新建后不默认启用全部已有知识库;对话中的范围继续由用户显式选择。
5. 向量服务不可用时保留全文与中文检索,但必须返回明确降级状态。这是可见的检索通道
降级,不得自动切换内置连接、用户添加的 OpenAI 兼容连接或其他向量模型。
+274 -2
View File
@@ -2,7 +2,8 @@
## 已验证实现
截至 2026-09-05:
截至 2026-09-05 的本地知识库验证记录如下。外部知识库当前实现和验证范围见后文
2026-09-13 记录。
- 知识库选择按 Conversation 持久化,新对话默认空范围。
- 创建流程默认只展示名称和描述,高级存储与图谱设置折叠。
@@ -15,6 +16,267 @@
## 验证证据
### 2026-09-13 知识库布局与自动名称修复
- 创建类型的共享分段控件被长表单压缩,修复共享不收缩规则,不添加页面专属高度。
长名称卡片增加零最小宽度的 Grid 轨道,名称单行省略、图标保持 15px;详情名称允许换行。
合成长名称实测列表横向溢出为 0;详情修复前溢出 658px,修复后为 0。
- 连续选择两个目录项时,未修改过的自动名称原先停留在第一个目标。回归先失败再修复,
现在跟随选择,手动名称和已保存名称不自动覆盖。规则见
[创建与编辑](./external-knowledge-logic-design.md#5-创建与编辑)。
- 当前完整 App 的真实 UI → Preload → Main → SQLite 路径完成实例保存、两个目录项
切换、零结果检索后添加绑定、详情展示和配置草稿保留。上游是 loopback Dify 合成服务,
不是真实知识平台;没有调用模型,也不能据此声称真实检索质量或远端验收。
- 本轮合成服务共收到 8 次请求:1 次初始目录、2 次初始详情、2 次检索(测试与保存)、
2 次后续详情、1 次后续目录;真实模型和真实知识服务请求均为 0。
- 创建类型在浅深主题、目标 1280×800、960×720、720×640、640×420 的八组尺寸中
检查控件高度至少 34px,并保存 PNG。实际审阅浅色 1280、深色 640 及最终添加后的
长名称详情 PNG,确认 Tab 不再成为窄缝,卡片边框和图标完整,详情无横向滚动条。
- 同期修复和通用验证结果见[工作栏进度](../assistant-workbar/progress.md#2026-09-13-原生浏览器与应用浮层修复)。
临时脚本与合成 profile 在提交后清理,不把它们作为长期依赖;用户在另一测试窗口
手工创建的实例与绑定不属于本轮合成数据,不随清理删除。
### 2026-09-13 检索路径与绑定查询简化
对 `3b560c9..ca4a58c` 的复查发现三处与本功能相关的实现问题,已按 KISS 与性能优先修正:
| 修正项 | 修正前行为 | 修正后行为 |
| --- | --- | --- |
| 生产检索入口 | 对话检索复用 `testRetrieval`,把用户问题传入 `testQuery`,并对 RAGFlow 额外发一次 detail 请求 | 新增 `ExternalKnowledgeService.retrieve` 作为生产路径,只发一次检索请求;能力预检仅保留在 `testRetrieval` |
| 配置变更检测 | 5 处 `JSON.stringify` 前后快照对比,抛出 `EXTERNAL_KB_CONFIG_CHANGED` | 全部删除,改由既有 `AbortController` 取消覆盖;被取消的探测抛 `EXTERNAL_KB_CANCELLED` 且不写回探测结果 |
| 绑定查询 | `requireLibrary`(30+ 调用点,其中一处在循环内)与 `getKnowledgeSnapshot` 每次全表 `listBindings()` | 新增 `getBinding`/`hasBinding`/`getBindingsForInstance` 主键查询;快照改为单次 `Map` 构建 |
修正过程中被测试拦下两次行为回退,已记录为当前不变量:
- 被取消的连接测试必须抛出且不写回 `probeStatus`,不能记为 `failed`;用户停用实例不等于探测失败。
- `testRetrieval` 的 RAGFlow 能力预检必须与检索共用绑定配置的 `requestTimeoutMs`,不能各自计时。
同时删除确认无生产引用的死代码:`probeStatus` 的 `'healthy'` 取值、`external.transportConfirm`
与 `external.states` 的 `remote-missing`/`config-invalid` 文案、`KnowledgeService.searchHybridMany`、
`ExternalKnowledgeRemoteResult.sourceUrl`。`externalKnowledgeLocatorSchema` 的 `sourceUrl`
保留,因为它解析已持久化的引用数据。
| 验证 | 结果 |
| --- | --- |
| `npx vitest run src/main/knowledge src/main/agent/knowledge-mcp-gateway.test.ts` | 21 个文件通过、1 个跳过;288 项通过、1 项跳过 |
| `npm test` | 348 个文件通过、9 个跳过;4,088 项通过、66 项跳过 |
| `npm run typecheck`、`npm run lint` | 通过 |
`npm test` 的唯一失败为 `src/main/agent/opencode-runtime-lifecycle.test.ts:120`
("Matcher did not succeed in time")。已用 `git stash` 在同一 `HEAD` 上复现同样失败,
确认早于本次修正且与知识库无关,未计入上表。
### 2026-09-13 当前工作区实现与分组验证
三家 Provider 已通过生产服务、本机 HTTP MCP 与生产 IPC 的短答案生成检查,Dify 另已
从完整 App 输入区完成真实回答和引用点击。后续 UI 修正后的聚焦测试、全量回归、类型
检查、lint、工作区及暂存区 diff 检查均已通过;下表区分早期结果与最终结果。
| 需求范围 | 当前代码落点与行为 |
| --- | --- |
| `EK-FR-01` 至 `EK-FR-05` | `ExternalKnowledgeService`、`ExternalKnowledgeStore`、数据库 v12 迁移和类型化 IPC 已接入;支持加密保存实例、启停、目录分页、详情和手工 ID 检索验证 |
| `EK-FR-06` 至 `EK-FR-10` | `ExternalKnowledge.tsx` 与 `KnowledgeWorkspace.tsx` 已提供四种创建类型、两栏绑定表单、Provider 配置、统一列表和外部详情;参数只保存在本地绑定 |
| `EK-FR-11` 至 `EK-FR-14` | `KnowledgeService.retrieve` 按绑定分派;`retrieveMany` 已供聊天预检索和 MCP 搜索使用;外部定位、逐库失败和历史引用已接入共享契约与会话持久化 |
| `EK-FR-15`、`EK-FR-16` | 实例引用保护、本地移除确认、固定只读端点和有界请求已实现;传输与交互的实际限制见技术、UI 设计,不能视为原草案全部验收项已满足 |
实施代理与 owner 回报的测试记录如下,本轮文档核对没有重新执行这些测试:
| 分组 | 回报结果 | 证据边界 |
| --- | --- | --- |
| Client | 35 项测试通过 | 包含 FastGPT 响应包装、数组评分及取消等客户端契约修正 |
| Service 组 | 96 项测试通过 | 服务及关联集成分组结果,不单独推断每个文件的测试数量 |
| UI 组 | 107 项测试通过 | 组件及关联界面分组结果,不能替代 Electron 桌面操作验收 |
| 后续集成组 | 11 个 suite、403 项通过 | 后于上述分组;不与早期计数相加 |
| 中间全量测试 | 4,021 项通过、4 项失败、66 项跳过 | 历史结果;原失败已修正,最终回归通过 |
| 中间 App + Citation | 216 项通过、2 项失败 | 历史结果;原生控件测试交互已修正 |
| UI 复验前完整 App 测试 | 215 项通过 | owner 回报,覆盖整个 App 测试文件 |
| UI 复验前全量回归 | 347 个文件通过、9 个文件跳过;4,039 项通过、66 项跳过 | `npm test -- --maxWorkers=1 --reporter=verbose`;退出码 0,Vitest 耗时 677.40 秒 |
| UI 复验前类型检查 | 通过 | 当时最后代码修改后执行 `npm run typecheck`,owner 回报 |
| UI 复验前 lint | 通过 | 当时最后代码修改后执行 `npm run lint`,owner 回报 |
| UI 复验前 diff 检查 | 通过 | 当时最后代码修改后检查通过,owner 回报 |
| 最终聚焦测试 | 245 项通过 | 完整 App 与两个 Agent 测试文件,owner 回报 |
| 当前 UI 修正后最终全量回归 | 349 个文件通过、9 个文件跳过;4,081 项通过、66 项跳过 | `npm test -- --maxWorkers=1 --reporter=verbose`;退出码 0,Vitest 耗时 598.33 秒 |
| 最终类型检查与 lint | 通过 | `npm run typecheck`、`npm run lint`,owner 回报 |
| 最终工作区与暂存区 diff 检查 | 通过 | owner 回报;本轮另检查文档 diff |
各次测试覆盖有重叠,不合并为去重后的全仓总数。后续源码已确认 `retrieveMany` 按 rank
跨库轮转分配预算;App 引用事件使用共享 `knowledgeReferenceKey`,保留远端 ID 不同的
引用并去除重复项;引用弹窗的定位只显示一次。
UI 复验前全量证据来自临时目录中的 `goodbuddy-native-controls-fix-full.log` 和
`goodbuddy-native-controls-fix-full.result.json`,日志汇总与结果文件退出码已核对。
owner 确认 `App.tsx`、`App.test.tsx` 的哈希在整轮运行期间保持不变,最后代码修改后的
类型检查、lint 和 diff 检查均通过。之后用户要求重新检查 UI 并执行真实模型与知识库验证,
外部界面又有修改,因此该通过记录不自动覆盖当前源码。
原失败涉及 Preload 测试顺序沿用旧计数、原生选项控件的延迟焦点交互,以及并发导航;
相应修正已完成,并发导航由协作代理处理。引用去重与重复定位修正也已纳入最终回归。
精确自动测试计数仅在本文维护,其他设计文档和脱敏摘要引用本记录。
当前最终全量证据为 `goodbuddy-binding-selector-fix-full.log` 和
`goodbuddy-binding-selector-fix-full.result.json`,日志汇总和退出码已核对。最后一项 App
测试修正把旧原生 Select 操作改为实际富信息菜单的可访问交互,没有修改产品源码。
其他 Agent 测试失败由并发 owner 在其最新代码中解决,本轮未修改这些文件。
owner 确认 App 测试、两个 Agent 测试和 `ExternalKnowledge` 在整轮执行期间保持稳定,
不再追加源码修改。当前通过结果已覆盖后续 UI 版本,早期结果继续作为历史记录保留。
### 2026-09-13 生产服务与桌面验证
已读取脱敏的 `kb-strict-validation/report.json` 和
`kb-desktop-ui-validation-20260913/report.json`,并在
[产品验证摘要](./external-knowledge-validation-2026-09-13.json)归档必要字段。报告检查
当前生产实现,与下述 2026-09-09、2026-09-10 历史探测分开计证。
严格验证在真实 Electron 43.2.0 中运行生产 `KnowledgeService`、客户端和本机 HTTP MCP。
四个配置均完成绑定保存、凭据持久化后重开、响应 Schema 和引用映射检查;每个配置执行
绑定、服务检索、HTTP MCP 三次检索,共 12 次请求与 12 次响应。
| 配置编号 | 传输 | 目录 / 详情 | 服务结果 / MCP 引用 | 结论范围 |
| --- | --- | --- | --- | --- |
| Dify 1 | HTTPS,严格证书校验 | 成功 / HTTP 405 | 0 / 0 | 详情不兼容仍可绑定与检索,零命中是成功;没有非空证据 |
| Dify 2 | HTTPS,严格证书校验 | 成功 / 成功 | 2 / 2 | 非空服务检索与本机 HTTP MCP 成功 |
| FastGPT | HTTPS,严格证书校验 | 成功 / 成功 | 2 / 2 | 非空服务检索与本机 HTTP MCP 成功,未覆盖桌面 Provider 专属操作 |
| RAGFlow | HTTP,不涉及 TLS | 成功 / 成功 | 1 / 1 | 非空服务检索与本机 HTTP MCP 成功,未覆盖桌面图谱等专属操作 |
这些编号来自本轮报告,不由历史版本号推断。`safeStorage` 可用且真实加解密往返成功,
四个配置保存的凭据均在重开后可用。HTTPS 结论只覆盖表中三个 HTTPS 配置。
Dify 桌面验证使用当前 Main、Preload、Renderer、真实 BrowserWindow 和生产 IPC。
实例保存时,验证程序仅在 Main 的保存 Handler 边界替入已有测试密钥。已验证目录选择、
真实检索后绑定、本地参数保存和隔离桌面配置重启;保存参数为返回 2 条、超时 15 秒、
单片段 200 字符。14 次只读请求均为 HTTP 200,其中目录 4 次、详情 4 次、检索 6 次。
窗口按 1440、900、700 宽度检查,报告实际 viewport 为 1439、899、699,均无 body 横向
溢出;宽屏参数两栏、窄屏单栏及窄屏列表可见。原生 ArrowRight 切换并激活检索页签。
桌面凭据记录采用 `electron-safe-storage`,解密匹配且明文未写入被检查的凭据记录;
BrowserWindow 保持 sandbox、contextIsolation 开启和 nodeIntegration 关闭。
生产 `knowledge.search` 返回两条真实引用,验证程序通过 `conversations.saveLocal`
保存带测试标记的会话后重载,再打开生产引用弹窗。已检查片段、Provider、快照说明和
弹窗内焦点,未出现本地上下文或打开来源按钮。这里验证的是历史引用保存与阅读,
没有生成模型答案,也未证明模型会在真实回答中使用或引用证据。隔离配置和构建已清理。
截至上述早期验证,已知检索请求 30 次,上界 42 次:未保留诊断的首轮为 0 至 12 次、此前已确认
12 次、严格验证 12 次、桌面 6 次。首轮准确数量无法恢复。模型生成调用为 0;Provider
侧 Embedding、重排及审计用量不据此记为零。该计数不合并 9 月 9 日、10 日的历史探测。
Linux 测试 Host 的 LAN 与 VPN TCP 均可达,但验证程序读取已有 SSH 凭据时发生
`E_SAFE_STORAGE_DECRYPT_FAILED`,在认证 SSH 之前阻断。因此真实远程 forced-preflight
未执行;本机 HTTP MCP 成功不能替代远程生产路径验收。
### 2026-09-13 UI 修正与真实生成复验
用户指出原界面不符合要求,并要求真实模型与知识库一起验证。当前 `ExternalKnowledge.tsx`
与 CSS 已修正 Portal 移出工作区后丢失字段作用域的问题,改用共享 `.field`;实例弹窗收为
560px,密码使用眼睛图标,新建直接填凭据、编辑才选择保留或替换或清除。开关、粘附底部
操作区、带地址和凭据状态的实例菜单、EmptyState 与键盘行为均已更新。实现细节见
[UI 设计](./external-knowledge-ui-design.md#当前实现与验收边界)。
本轮读取 `kb-real-generation/report.json`、`kb-full-app-live/report.json` 和
`external-visual-0913/results.json`,脱敏摘要归档在
[UI 与真实生成复验](./external-knowledge-follow-up-2026-09-13.json)。摘要不保存模型端点、
私有配置标识、密钥、业务正文、生成答案或内容哈希。
| 验证层 | 实际覆盖 | 结果与边界 |
| --- | --- | --- |
| 生产 IPC 与真实模型 | 真实 Electron、Preload、已注册 IPC 和 ModelAgentRuntime;Dify/FastGPT/RAGFlow 的 `always`,以及 Dify `auto` | 最终场景均有非空短答案、依据匹配、引用标记、单库范围与文本/外部引用持久化证据;Renderer 是隔离验证 DOM,不是完整 App |
| 完整 App | 真实 Main、Preload、Renderer;从界面创建 Dify 实例及非空绑定,在输入区选择知识库并点击发送,Ask + `always` | 真实模型生成并保存答案,检索片段进入模型请求,点击引用后片段和远端文档/片段 ID 匹配,焦点在弹窗内;无伪造会话、引用或 Agent 事件 |
| 视觉与键盘 fixture | 当前组件和样式、真实 body Portal 与 Electron 原生键盘;IPC 为本地假数据 | 56 张截图、138 项检查通过,errors 为空;覆盖浅深主题及 1280×800、960×720、720×640、640×420,不作为真实 Provider 业务调用证据 |
生产 IPC 首轮 FastGPT/RAGFlow 已有非空、依据匹配和引用,但验证程序直接在序列化请求
字符串内寻找原片段,导致 `retrievedSnippetInBody=false`、`passed=false`。改为解码 Provider
请求及其中的证据 JSON 后,两家重跑均通过。归档保留首轮失败和修正后结果,不把首轮
改写为通过,也不把这次断言修正记成产品检索修复。Dify `auto` 通过进程内
`ModelToolProvider → KnowledgeMcpGateway` 执行 `knowledge_search`,不是 HTTP MCP 会话。
模型使用用户已保存的默认配置,凭据在 Main 用 safeStorage 解密。完整 App 检查确认原
模型设置未改变;Dify/FastGPT 请求使用严格 HTTPS,RAGFlow 和所用模型保留已配置的 HTTP。
完整 App 中的测试知识库密钥仅在 Main 保存 IPC 边界替入,报告没有公开实际密钥。
完整 App 另检查浅深主题的 Portal 编辑器:宽度 560px、字体与字号继承共享控件、背景
随主题变化,窗口内无横向溢出。视觉 fixture 检查了密码显隐前后布局、原生 Tab/Space、
开关、底部保存按钮、焦点循环与恢复、菜单跳过禁用项、页签、凭据替换和清除确认。
| 本轮请求 | 模型 | KB 全部 | KB 检索 | 目录 | 详情 |
| --- | --- | --- | --- | --- | --- |
| 生产 IPC 生成报告 | 7 | 12 | 9 | 3 | 0 |
| 完整 App 报告 | 1 | 5 | 3 | 1 | 1 |
| 合计 | 8 | 17 | 12 | 4 | 1 |
计数是本轮应用实际发出的 HTTP 请求,包含绑定验证和断言修正后的重跑;不推断上游代理
内部请求或 Provider Embedding、重排用量。生产 IPC 的检索含绑定验证 3 次、强制预检索
5 次、按需检索 1 次;完整 App 含创建验证 2 次、输入区发送触发 1 次。本轮采用精确增量,
不沿用生成报告中漏计早期桌面请求的累计字段。
“生成未经验证”已在上述短答案范围内解决。检查证明选中知识库的真实片段进入模型并
产生带依据和引用的短答案,不证明长文、多库冲突、复杂推理或广泛问答质量。完整 App
仍只覆盖 Dify;FastGPT/RAGFlow 完整桌面与专属参数、Linux 远程路径继续待验收。
当前 UI 修改后的聚焦测试、全量回归及相关检查已通过,精确结果见前文自动测试记录;
早期通过记录保持原时间范围。
### 历史验证记录
2026-09-10 官方 API 核对与真实实例复测:
- 官方资料链接与版本差异见[技术设计第 9 节](./external-knowledge-technical-design.md#9-provider-映射)。
已修正 Dify 默认配置优先级、FastGPT 端点响应与数组评分说明、RAGFlow 新旧候选数参数边界。
- 使用现有本地测试配置与脚本默认查询,执行基础 14 次、扩展 19 次,共 33 次只读请求,
其中检索 17 次。未调用远端内容写入、聊天或生成端点;检索所用 Embedding、重排及审计
用量由远端服务处理,不据此推断底层模型调用次数。
- 两轮均在独立 Node 进程显式设置 `NODE_TLS_REJECT_UNAUTHORIZED=1`。两个 Dify 实例与
FastGPT 的 HTTPS 请求通过严格证书校验;RAGFlow 使用 HTTP。
- [基础报告](./external-knowledge-probe-2026-09-10-strict.json)和
[扩展报告](./external-knowledge-probe-2026-09-10-extended.json)保留脱敏状态、数量、字段结构
和耗时。每个实例只对目录选出的一个库执行检索,未遍历全部库。
| 实例 | 目录与详情 | 检索结果 |
| --- | --- | --- |
| Dify 0.15.8 | 目录 3 项;详情 HTTP 405 | 默认与显式当前配置请求均成功,均为 0 条;非空结果尚未验证 |
| Dify 1.17.0 | 目录 1 项;详情成功 | 默认与显式当前配置均为 3 条非空结果 |
| FastGPT,版本未确认 | 根目录 1 个 Dataset;详情 HTTP 200、业务码 200 | 向量 8 条、全文 2 条、混合 7 条、混合加重排 9 条;重排响应明确为启用;检索包装为 `data.list`,`score` 为数组 |
| RAGFlow,版本未确认 | 目录 29 项;详情成功;18 项配置图谱、16 项有完成证据 | 基础 2 条、图谱 3 条、知识编译开关 2 条;请求均 HTTP 200、业务码 0,但没有知识编译配置证据 |
- 修正探测脚本将 Dify/RAGFlow 失败记成零命中的问题,增加实际调用与成功标记,统一
FastGPT 地址规范化并记录分数结构、响应中的重排状态。未修改产品客户端;旧 FastGPT
检索数组包装、数组评分映射及上一轮发现的取消分类问题在当时仍待处理;当前客户端已修正,
当前服务和本机 HTTP MCP 的真实 Provider 复验见 2026-09-13 记录,旧包装等特定响应变体
仍不能仅凭成功计数认定覆盖。
- 三组探测测试共 43 项通过,包含两份新报告的脱敏与只读操作检查;`npm run typecheck`、`npm run lint` 通过。
`npm test` 在该历史轮次先后达到 60 秒和 240 秒执行时限,均未输出汇总;当前最终全量
已通过,见 2026-09-13 记录。
- 本轮未验证真实 401/403/404/429、超时、取消、Scoped Key、分页、元数据过滤或候选数
参数效果。探测脚本不打包到桌面或 GoodBuddy Agent,本次未改变远程 Runtime 生产路径。
2026-09-09 外部知识库适配基础验证:
- 使用本机未入库的测试配置累计执行 107 次只读请求,其中更新后的最终归档脚本执行 19 次;脚本
调试产生的结果只写入批准的临时目录。三套目录、FastGPT 详情和 RAGFlow 详情均成功;
Dify `0.15.8` 详情 GET 返回 405,Dify `1.17.0` 详情 GET 返回 200。验证过程未输出 API
Key、知识库名称和业务正文。
- Dify 响应头确认两个版本为 `0.15.8` 和 `1.17.0`。1.17 默认与显式当前配置检索均返回
3 条非空结果,已验证 `segment/document` 字段;详情新增 Metadata、多模态、摘要索引和
Pipeline 能力字段。FastGPT 与 RAGFlow 响应未提供产品版本。实测还确认
FastGPT 列表 `data` 为直接数组、结果 `a` 可缺失,RAGFlow 文档名字段为
`document_keyword`。详细基线见[外部知识库技术设计 9.0 节](./external-knowledge-technical-design.md#90-真实实例验证基线)。
- 新增 `scripts/external-knowledge-probe.mjs`,可重复生成
不含地址、凭据、远端 ID、名称、查询和正文的能力报告。最终报告归档在
[`external-knowledge-probe-baseline.json`](./external-knowledge-probe-baseline.json)。
- FastGPT 的 embedding、fullTextRecall、mixedRecall 和 mixedRecall + Rerank 均返回非空
结果。RAGFlow 当前 28 个库中 17 个配置 GraphRAG、15 个存在完成证据;已完成库在基础
检索为零结果时,图谱检索返回 1 条结果。当前没有库提供 Knowledge Compilation 配置证据。
- 新增 `src/main/knowledge/external/external-knowledge-client.ts`,固定实现三种 Provider 的
只读目录和检索端点、地址规范化、超时、响应大小限制、HTTP 错误分类和结果标准化。
- `npx vitest run src/main/knowledge/external/external-knowledge-client.test.ts`:1 个测试文件、
5 项测试通过。
- `npx vitest run tests/external-knowledge-probe-report.test.ts src/main/knowledge/external/external-knowledge-client.test.ts`:
2 个测试文件、7 项测试通过;覆盖归档报告脱敏、只读操作和特色能力证据。
- `npx eslint src/main/knowledge/external/external-knowledge-client.ts src/main/knowledge/external/external-knowledge-client.test.ts`:通过。
- `npm run typecheck`:被当前工作区已有的 `src/main/ipc.test.ts:7694` tuple 类型错误阻断;
新增适配器、探测脚本和报告测试的定向测试与 ESLint 已通过。
- 当前 Node 测试环境设置了 `NODE_TLS_REJECT_UNAUTHORIZED=0`,Dify 和 FastGPT 请求实际
使用了放宽的 TLS 校验。产品接入不能继承该进程级默认,保存或测试实例时仍需按技术设计
明确处理传输风险。
2026-09-05 最终验证:
- `npm test`:325 个测试文件、3511 项测试通过;8 个文件、55 项手动或集成测试按既有
@@ -34,7 +296,17 @@
## 剩余工作
- 外部 Dify、FastGPT 和 RAGFlow 知识库接入仍为规划中能力。
- 完成 FastGPT/RAGFlow 桌面专属配置及实际参数效果检查;补充混合库、真实聊天
`always`/`auto` 的多库与复杂问答、停用和本地删除、未覆盖的主题及键盘路径。三家生产 IPC
短答案、Dify 完整输入区真实回答与引用,以及已检查的主题/尺寸/键盘不再列为首次待验收。
- 解决验证程序对已有 SSH 凭据的解密问题后,执行真实 Linux forced-preflight;网络已可达,
当前阻断点是认证前凭据读取。
- 三家服务已完成真实检索与本机 HTTP MCP。FastGPT/RAGFlow 部署版本、Dify 1 非空结果、
Scoped Key 和真实错误矩阵等仍需补证;旧探测不证明当前桌面配置效果。
- 原设计中的分项探测、版本能力展示、地址确认、请求取消交互和详细诊断与当前实现有差异,
具体范围见[技术设计](./external-knowledge-technical-design.md)和
[UI 设计](./external-knowledge-ui-design.md#当前实现与验收边界)。这些差异需由功能 owner 核对验收,
本轮仅改文档,不补产品代码或扩大架构。
- 扫描 PDF 的真实 OCR 验收依赖用户已安装并校验的 OCR 模型;本次 DOCX/PDF 回归覆盖
原生文本 PDF,不宣称覆盖扫描件 OCR。
- PDF.js 在测试环境会提示未配置 `standardFontDataUrl`;文本、页码定位与检索结果不受
+12 -3
View File
@@ -8,6 +8,10 @@
## 2. 信息架构
当前列表同时容纳本地库与外部绑定,页面增加“管理外部实例”和来源筛选。以下详情结构、
资料导入和索引状态适用于本地库;外部创建、配置、检索测试及引用的当前实现与待验收项见
[外部知识库 UI 设计](./external-knowledge-ui-design.md)。
```text
知识库
├─ 知识库列表
@@ -21,11 +25,16 @@
标题区提供“用于当前对话”“检索测试”、编辑和删除。新建知识库是页面主操作。
“高级设置”承载问答效果入口、向量索引、分块与图谱配置,不把工程参数放在首次路径。
本地与外部库共用的列表以 `minmax(0, 1fr)` 限制条目宽度,再对名称单行省略;选中卡片
的边框必须完整留在列表内,Provider/实例等辅助文字允许换行。不得用列表外层裁切
掩盖被长名称撑宽的卡片,库图标不能随名称一起收缩。外部详情的长名称和远端名称允许
换行,不能撑出横向滚动条。创建类型分段控件与详情页签保持固定内容高度,不随长表单收缩。
## 3. 首次使用
没有知识库时,页面空状态显示唯一的“新建知识库”主操作。创建表单默认只展示名称和
描述;存储方式与知识图谱放入折叠的“高级设置”。创建完成后进入“文档与来源”,用户
可以导入文件、文件夹或 URL。
没有知识库时,页面空状态显示“新建知识库”主操作。创建时先选择类型;本地类型默认
展示名称和描述,存储方式与知识图谱放入折叠的“高级设置”。本地库创建完成后进入
“文档与来源”,用户可以导入文件、文件夹或 URL。
文档处理完成后,标题区“用于当前对话”可用。该操作把当前知识库加入当前
Conversation 的检索范围,返回对话并聚焦输入框。
+18
View File
@@ -0,0 +1,18 @@
# Magic Notes
Magic Notes is the local notes, checklist and AI-comment workbench. Desktop
actions and authorized Agent/MCP tools write to the same desktop SQLite store.
The open workbench subscribes to successful writes so users can see external
changes without leaving the page or clicking a refresh button.
## Documents
- [Technical design](./technical-design.md): database notifications, process
boundaries, refresh lifecycle and regression coverage.
- [Progress](./progress.md): implementation and validation evidence.
- [UI design system](../../../UI-DESIGN.md#137-魔法笔记): authoritative workbench
interaction rules, including selection and draft preservation.
An entry is a saved rich-text record within a note. A todo is a checklist item
derived from an entry. An unsaved draft belongs to the open editor; it is not
written to storage by a background refresh.
+26
View File
@@ -0,0 +1,26 @@
# Magic Notes Progress
## 2026-09-15: Automatic External Refresh
- Implemented the shared-database notification and Main/preload subscription
described in [technical design](./technical-design.md).
- The open workbench reloads external changes while retaining current selection
and unsaved drafts. Deleted notes and edited entries retain drafts with an
explicit explanation. The cancelled manual-refresh proposal is not implemented.
- An initial completed `npm test` run passed 357 files and 4,201 tests, with
9 files and 67 tests skipped (680.45 seconds). The first invocation had exceeded
the command tool's 120-second limit.
- After three additional renderer race/retry tests, the final `npm test` run
reported 355 files and 4,202 tests passed, 9 files and 67 tests skipped, and two
unrelated timeout failures: the large offline dependency inventory test in
`tests/agent-package.test.ts` (60 seconds), and the two-project OpenCode case in
`src/main/agent/local-runtime-reuse.test.ts` (40-second request timeout).
- Rerunning those two files together passed 24 tests with one skipped in
103.71 seconds, without source or timeout changes. The final renderer/preload
focused run also passed all 38 tests. No Magic Notes regression failed.
- `npm run typecheck` and `npm run lint`: passed. The new required subscription
method is also present in the application test API fixture.
- Validation uses production SQLite and Agent/MCP gateway code, the production
preload with mocked Electron transport, and renderer interaction tests. An
interactive Electron window was not exercised. No deployed Agent code or
desktop-to-Agent protocol changed, so no separate Linux Host scenario applies.
@@ -0,0 +1,60 @@
# Magic Notes Technical Design
## Change Notifications
`AssistantDatabase` accepts `onMagicNotesChanged: () => void`. Every successful
note/entry create, update or delete emits once after the write commits, including
empty-note creation, initial content, title/pin updates and cascading deletion.
Saved note analysis, todo completion, saved todo analysis and assistant-data
clearing also emit. Revision conflicts, missing rows, rolled-back writes and
unchanged todo completion do not emit. Startup schema migrations run before the
workbench loads and do not publish notifications.
Desktop IPC and `KnowledgeMcpGateway` use this same database instance. The latter
also serves Agent tools; no tool-specific notification or daemon protocol change
is needed. Raw SQL writes from an unrelated process are outside this callback
contract.
Main queues notification delivery to the live window on `magic-notes:changed`.
The typed preload method `magicNotes.onChanged(listener)` exposes no Electron
objects and returns a function removing that listener. The existing
`onTodoStatusChanged` event remains the separate sidebar-count signal.
## Refresh Lifecycle
`MagicNotesWorkspace` subscribes on mount and coalesces notifications with a
100 ms debounce. Pending notifications wait for an active local save or analysis
to finish. Cleanup removes the listener, clears its timer and invalidates pending
refresh results. This does not introduce periodic database polling.
The existing list/detail loader reloads notes and todos, then the preferred note.
Request counters reject superseded results and preserve newer user selections.
Todo selection survives list reordering while the item exists; replacing the todo
snapshot also reloads the selected todo's source entry. Search, filters and layout
state are not reset.
Background application of a detail reads the current draft state after the
asynchronous fetch. A dirty title is retained, and the composer is not remounted.
An entry editor uses its original editing content and revision for both its React
key and save request; external revisions cannot remount it or bypass optimistic
concurrency checks. Deleted entries remain displayed only while their editor is
open. If an externally deleted note has a draft, its editor remains available
with a deletion explanation instead of moving the draft to another note.
Read failures use the existing retryable refresh error and retain successful
data and drafts. Retry uses the same draft-preserving loader. Interaction rules
are owned by [the UI design system](../../../UI-DESIGN.md#137-魔法笔记).
## Validation
- `assistant-database.test.ts` checks committed writes with a second SQLite
connection, successful mutation coverage, failures, no-op completion and reset.
- `knowledge-mcp-gateway.test.ts` exercises real database CRUD through Agent/MCP
access, including revision failure without an extra notification.
- `magic-notes-events.test.ts` executes the production preload, checking the
channel, payload-free callback and listener removal.
- `MagicNotesWorkspace.test.tsx` covers external updates and deletion, selection,
source reload, editor identity and draft saves, coalescing, deferred writes,
asynchronous selection races, error retry and unmount cleanup.
No schema migration, dependency, refresh control or network service is added.
@@ -12,6 +12,10 @@ Provider 要求的附加元数据;GoodBuddy 只在直连链路或 Runtime 明
| [技术设计](./technical-design.md) | 契约、持久化、合并优先级、Runtime 支持矩阵和验证 |
| [功能进度](./progress.md) | 已验证实现、剩余工作和验证证据 |
模型连接选择准确性也在本目录维护:技术设计第 8 节定义通道直连回退修复,
UI 设计末节定义 Harness 来源标签和公开设置展示;Harness 的实际解析顺序由其
[技术设计](../deepseek-harness/technical-design.md#121-配置来源)唯一维护。
本次改动是现有模型连接的有界扩展,不新增独立业务对象或工作流,因此不单独建立 PRD、
User Stories 和逻辑设计;本页定义产品边界,技术设计定义唯一的请求合并规则。
@@ -42,5 +46,6 @@ User Stories 和逻辑设计;本页定义产品边界,技术设计定义唯
## 相关功能
- [直连模型 Agent](../direct-model-agent/README.md)
- [图片生成与连续修改](../image-generation/README.md)
- [DeepSeek Harness Runtime](../deepseek-harness/README.md)
- [远程主机与远程执行](../remote-host/README.md)
@@ -1,5 +1,35 @@
# 模型连接请求定制进度
## 2026-09-11:发布前固定模型说明修复
- 项目已保存固定 Runtime 模型时,恢复显示实际固定模型的帮助文字,保留简化后的菜单;
规则见 [会话与项目 Runtime 选择](./ui-design.md#会话与项目-runtime-选择)。
- `npm exec vitest run src/renderer/src/ProjectRuntimeSelector.test.tsx`:2 项通过,
覆盖中英文下 OpenCode、Continue、DeepSeek Harness 的固定选项及恢复跟随操作。
- `npm test`:343 个文件、3,911 项通过,66 项按平台或手工/真实服务条件跳过;
`npm run typecheck`、`npm run lint`、`npm run release:notes:verify` 通过。
- 本次新增修复仅影响 Renderer 帮助文字,未修改共享模型解析、Desktop-to-Agent 请求、
Agent 或 Runtime 启动实现;不需要单独远程修复。真实模型调用 0 次,未运行真实 Host
或桌面端到端模型会话。本地未构建或打包,发布候选生产构建由主分支 CI 验证。
## 2026-09-10:配置准确性修复
- 通道直连回退修复已按[技术设计第 8 节](./technical-design.md#8-通道直连模型选择修复)
实现;测试把修复结果交给现有 Main resolver,不仅断言返回对象。
- Harness 设置使用专用来源标签、关联帮助及 Main 直接从生产 resolver 派生的只读
`deepseekHarnessPlatformModel` 投影;准确区分完整管理员预置、兼容回退和无连接,
不再从通用环境凭据状态推断。投影不含地址、密钥或请求定制,也不写入设置文件。
- 无兼容连接时管理员来源仍可选择;未保存候选与实际已保存来源分别展示。UI 验证涵盖
中英文、兼容默认、首个兼容回退、管理员覆盖、部分环境回退、无连接、草稿预览和保存动作。
- 聚焦命令 `npx vitest run src/main/agent/runtime-selection-contracts.test.ts src/renderer/src/SettingsPanel.test.tsx`
对应的两个文件在本次五文件联合运行中全部通过,共 96 项。真实模型调用 0 次;
本次未运行全量测试、typecheck、lint 或真实 Harness 请求。
通道修复集成测试随后归入 Main 测试目录,避免 Shared/Web 类型项目导入主进程实现。
- 后续聚焦验证 `npx vitest run src/main/runtime-settings-store.test.ts src/renderer/src/SettingsPanel.test.tsx`
全部通过:2 文件、181 项。Store 覆盖完整、部分与旧环境变量、默认/首个兼容回退、
无连接、精确字段白名单、非持久化以及不改变所选来源。真实模型/Host 调用均为 0;
未运行全量测试、构建、typecheck 或 lint。
## 2026-09-03
### 已完成
@@ -85,3 +85,33 @@ JSON 对象,标准认证 Header 也由 schema 拒绝。
Utility 启动配置和 Pi-AI Provider 配置。
- 远程:Main/Agent 网关合并、认证优先级、摘要绑定、大小限制和不重放。
- UI:默认值、保存、无效 JSON 保留和 Runtime 支持说明。
## 8. 通道直连模型选择修复
`repairChannelRuntimeSelection` 只在默认连接可用于文本通道时保留动态
`{ provider: 'model' }`。默认连接为图像生成、API Key 认证但明确缺少凭据,或默认 ID
已不存在时,若找到可用文本连接,修复结果必须携带该连接的 `profileId`,使后续
`applyRuntimeSelection` 使用已验证回退,而不是重新解析到不可用的全局默认值。
已有可用显式引用保持不变;没有可用连接时不虚构 ID 或声称可运行。
共享回归测试将修复结果直接传入 Main 的生产 resolver,验证动态默认、显式固定、
图像默认、缺少凭据和失效默认 ID。该修复不改变 OpenCode、Continue 或托管 SSH 的
Runtime 选择路径。
## 9. Harness 平台来源公开投影
`RuntimeSettings.deepseekHarnessPlatformModel` 是可选只读元数据,描述 `platform`
选项在当前已保存模型设置与管理员环境下的实际解析结果:
- `{ source: 'environment', name, modelName }`:使用完整管理员预置。
- `{ source: 'profile', profileId, name, modelName }`:使用兼容 GoodBuddy 连接回退。
- `{ source: 'unavailable' }`:没有可解析的连接。
Main 的 `toPublicSettings` 复用 `resolveDeepSeekHarnessModelProfile`,只在调用中将来源
设为 `platform`,不改变用户已保存的 `default`、`profile` 或 `platform` 选择。投影逐字段
构造,不包含地址、API Key、自定义 Header/Body 或凭据状态,不写入设置文件、迁移或新状态。
公开契约保持可选,缺失时 UI 明确来源信息不可用,不从通用模型或环境凭据猜测。
`source: 'profile'` 描述来源分支,不代表凭据来源:仅环境密钥或旧环境变量可能仍影响该
GoodBuddy 连接,但不会被误报为完整管理员预置。连接解析不代替真实模型请求或可用性测试。
UI 的未保存兼容候选只作草稿预览,保存后继续以 Main 返回的投影为准。
@@ -36,3 +36,27 @@ OpenCode 与 DeepSeek Harness 不使用该值。
- 无效状态不能只依赖颜色,必须同时提供错误文字和 `aria-invalid`。
- 文本域遵循标准键盘编辑、选择、复制和粘贴行为;保存继续使用设置页唯一的主保存操作。
- 焦点边框、字体、间距、颜色和深浅主题全部复用全局语义令牌。
## 会话与项目 Runtime 选择
会话与项目选择器只提供直连模型和各 Runtime 入口,不再枚举每个 Runtime 与每个模型的
组合;Runtime 模型在系统设置中配置。已保存的固定 Runtime 模型选择继续保留,项目
选择器的选项与帮助文字均显示其固定模型,不能误报为跟随全局设置;用户可选择普通
Runtime 入口恢复跟随。该展示规则不改变模型解析、远程 Agent 协议或执行权限。
## Harness 模型来源展示
DeepSeek Harness 的 `platform` 选项命名为“管理员环境优先,回退 GoodBuddy 兼容连接”,
不能沿用 OpenCode/Continue 的“Runtime 自有配置”标签。来源规则以
[Harness 模型配置](../deepseek-harness/technical-design.md#121-配置来源)为准。
帮助文字与选择器通过 `aria-describedby` 关联,并明确此选项不加载用户 profile 或
配置文件;无兼容 GoodBuddy 连接时仍可选择管理员环境来源。
概览使用 Main 提供的[平台来源投影](./technical-design.md#9-harness-平台来源公开投影),
明确显示实际管理员预置模型,或“未配置完整的管理员预置”及已保存兼容回退的名称与模型;
没有可解析连接时显示明确空状态,但保留 `platform` 选项。不得从通用模型名称或
`credentialSource` 猜测管理员来源,也不得显示密钥或读取 Main 环境。
没有管理员覆盖且草稿兼容候选发生变化时,额外显示“草稿兼容候选(未保存)”,保留实际
已保存来源;草稿失去兼容连接时也明确说明。管理员覆盖生效时不把草稿回退显示为实际模型。
保存完成后以 Main 重新解析的投影刷新概览。
@@ -0,0 +1,29 @@
# Office 协同编辑
Office 协同编辑通过 ShareServer 集成 ONLYOFFICE Docs,使 GoodBuddy Desktop 可以在助手
工作栏中打开多个文档页签,并在同一编辑器中完成人工编辑、AI 选区修改、保存、撤销和冲突处理。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [Office 协同编辑 PRD](./prd.md) | 用户问题、产品范围、功能要求和验收标准 |
| [用户故事](./user-stories.md) | 打开、多页签、保存、AI 编辑、冲突和恢复场景 |
| [功能逻辑设计](./logic-design.md) | 文档会话、保存、冲突、断线和关闭规则 |
| [界面设计](./ui-design.md) | 工作栏多实例文档页签、编辑状态和响应式行为 |
| [技术设计](./technical-design.md) | Desktop、ShareServer、ONLYOFFICE 的边界、协议、安全与实施顺序 |
## 术语
- **文档页签**:助手工作栏中的多实例应用页签,标题来自文档文件名。
- **编辑会话**:一个文档页签对应的服务端短期会话和 Desktop 本地状态。
- **源文件**:用户明确打开的本机或托管 SSH 工作区文件。
- **工作副本**:编辑期间由 ShareServer 管理并提供给 ONLYOFFICE 的临时版本。
- **保存检查点**:ONLYOFFICE 已提交给 ShareServer、但不一定已经回写源文件的稳定版本。
文档解析、OCR 和旧格式转换继续由[文档处理](../document-processing/README.md)负责。ShareServer
的组织、服务部署和运维由独立仓库的 ShareServer 文档负责;跨端身份、传输和审计共同规则见
[共享网络总体设计](../../architecture/share-network-architecture.md)。
当前功能仍处于设计阶段,尚无可验证实现,因此不建立进度文档。开始实施后再新增
`progress.md`,只记录实际完成范围和验证证据。
@@ -0,0 +1,117 @@
# Office 协同编辑功能逻辑设计
## 1. 核心不变量
1. 一个文档页签只绑定一个 `DocumentEditSession`,不会随当前项目或会话切换目标。
2. 源文件路径和替换权限只由 Desktop Main 持有。
3. ShareServer 是服务端工作副本和保存检查点的权威,Desktop 是源文件回写结果的权威。
4. ONLYOFFICE 报告已保存不等于源文件已经回写成功。
5. 没有稳定保存检查点时,任何界面都不能显示“修改可恢复”。
6. 源文件基准版本不匹配时禁止静默覆盖。
7. 一次 AI 请求要么作为一个编辑事务完整应用,要么不产生修改。
## 2. 状态维度
编辑会话不是一个布尔状态,至少包含以下独立维度:
```ts
type DocumentEditState = {
connection: 'connecting' | 'online' | 'offline' | 'expired'
editor: 'loading' | 'ready' | 'failed'
checkpoint: 'clean' | 'dirty' | 'saving' | 'available' | 'failed' | 'unknown'
source: 'unchanged' | 'writing' | 'saved' | 'conflict' | 'unavailable'
access: 'edit' | 'read-only' | 'revoked'
}
```
`checkpoint=available` 只表示 ShareServer 有可下载版本;只有 `source=saved` 才表示本机或远程
源文件已经更新。
## 3. 打开流程
```text
用户选择文件
-> Desktop 验证来源、格式、大小和 ShareServer 连接
-> Desktop 冻结源文件 identity、大小和 SHA-256
-> ShareServer 创建空编辑会话并返回一次性上传授权
-> Desktop 流式上传工作副本并校验 SHA-256
-> ShareServer 创建 ONLYOFFICE 配置和一次性启动授权
-> Desktop 创建文档页签并加载编辑器
-> ONLYOFFICE 读取 ShareServer 工作副本
-> ready
```
任一步失败都进入确定失败状态。ShareServer 对未完成上传和未启动会话按短期限清理。
## 4. 保存流程
```text
编辑器提交保存
-> ShareServer 接收 ONLYOFFICE 回调
-> 校验会话、状态、大小、格式和内容摘要
-> 原子发布新保存检查点
-> Desktop 得到 checkpoint available 事件
-> Desktop 重新读取源文件 identity 和 SHA-256
-> 未变化:下载、复核、写临时文件、原子替换
-> 已变化:进入 conflict,不覆盖
-> Desktop 回报 source saved 或 conflict
```
相同检查点回写使用幂等键。网络超时但替换结果不确定时,Desktop 重新读取源文件摘要确认,
不能直接重复写入或声称失败。
## 5. 冲突决策
| 条件 | 结果 |
| --- | --- |
| 源文件与基准一致 | 允许原子回写 |
| 源文件不存在 | 显示来源不可用,允许另存为 |
| 源文件摘要变化 | 冲突,禁止自动覆盖 |
| 源文件只读或授权失效 | 保留检查点,允许重新授权或另存为 |
| ShareServer 检查点过期 | 不能回写,说明修改不可恢复 |
首期冲突操作只有“打开外部版本”“另存当前编辑版本”“放弃编辑版本”和经二次确认的“替换
外部版本”。不提供自动 OOXML 合并。
## 6. AI 编辑流程
1. 用户在编辑器内发起 AI 编辑,插件冻结当前文档 revision 和选区锚点。
2. 选区为空、过大、包含不支持对象或会话非编辑态时拒绝发送。
3. Desktop 将有界选区作为用户明确提供的上下文交给当前 Conversation。
4. Agent 返回受限结构化编辑操作,不返回可执行脚本。
5. 应用前再次比较文档 revision;已变化时要求用户重新选择或重新生成。
6. ONLYOFFICE 插件在一个撤销上下文中执行全部操作。
7. 任一操作失败则回滚整个事务;成功后高亮修改并使检查点变为 `dirty`。
AI 编辑继承发起请求的 Ask/Execute 模式。Ask 只生成建议,不调用编辑器写入;Execute 才能
应用结构化修改。用户在编辑器中发起操作只授权当前文档和冻结选区,不授予 Agent 其他文件或
系统能力。
## 7. 页签与关闭
- 每次打开创建新实例;相同源文件可以存在多个编辑会话,但第二个及后续实例默认只读,避免
两个独立保存链路互相覆盖。用户关闭原编辑会话后可以将只读实例提升为编辑。
- 同名文件使用显示序号区分,序号只属于当前工作栏布局,不写入文件或服务端标题。
- 关闭 clean 页签直接结束会话。
- 关闭 dirty、saving、available、failed 或 unknown 页签必须根据可恢复事实给出对应选择。
- 应用退出复用同一判断;不能等待无限期保存。超时后保留 ShareServer 已确认检查点,并在下次
启动显示恢复入口。
## 8. 断线与撤销
- Desktop 断线不主动销毁服务端会话;在保留期内可以使用同一会话 ID 重新认证恢复。
- ShareServer 与 ONLYOFFICE 断线时禁止新编辑,保留最后稳定检查点和明确错误阶段。
- 权限被撤销后立即停止新读取、保存和 AI 操作;已经下载到 Desktop 的本地源文件不由服务端
删除。
- 撤销只作用于编辑器当前撤销栈。已经回写源文件后执行撤销会产生新的 dirty revision,仍需
再次保存才能更新源文件。
## 9. 需求映射
| 逻辑 | PRD | 用户故事 |
| --- | --- | --- |
| 打开和会话创建 | FR-1、FR-2、FR-7、FR-12 | US-A1、US-A2、US-D2 |
| 检查点和源文件回写 | FR-4、FR-5 | US-B1 |
| 冲突和关闭 | FR-8、FR-10 | US-B2、US-B3 |
| AI 编辑事务与模式 | FR-6 | US-C1、US-C2 |
| 断线、撤销和保留 | FR-9、FR-11 | US-D1、US-E1 |
@@ -0,0 +1,127 @@
# Office 协同编辑 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-09 |
| 适用产品 | GoodBuddy Desktop、ShareServer |
| 推荐编辑引擎 | ONLYOFFICE Docs Developer Edition,最终采用前完成许可证与分发审核 |
## 1. 背景
GoodBuddy 已能解析、生成和预览部分 Office 文件,但用户无法在客户端内直接修改 DOCX、
XLSX 和 PPTX,也无法把当前选区交给 Agent 后在原位置应用修改。自行实现高保真 OOXML
编辑器不符合产品投入边界,因此通过 ShareServer 集成成熟的浏览器编辑引擎。
## 2. 目标
- Windows、macOS 和 Linux Desktop 使用同一套浏览器编辑能力。
- DOCX、XLSX、PPTX 在助手工作栏中以多个独立文档页签打开。
- 用户可以手动编辑、保存、撤销和重做。
- Agent 可以读取用户明确选择的内容,并将一次 AI 修改作为一个可撤销编辑事务应用。
- 本地源文件只在用户明确打开后上传工作副本,保存时通过 Desktop 原子回写。
- ShareServer 统一承载编辑器接入、短期会话、工作副本、回调、保留和服务端审计。
## 3. 非目标
- 不自行实现 Word、Excel 或 PowerPoint 的排版与渲染引擎。
- 不把 ONLYOFFICE Document Server 打入 Desktop 安装包或由 Electron 启动。
- 不把编辑能力描述为离线功能;未连接已配置的 ShareServer 时不可创建新编辑会话。
- 不允许编辑器浏览任意本机目录,也不把本机绝对路径发送给 ShareServer。
- 不把 Node/Python OOXML SDK 当作可视化编辑器;它们后续只用于受控批处理 Provider。
- 首期不提供多人实时协作、文档分享链接、移动端编辑或跨组织编辑。
- 首期不支持 DOC、XLS、PPT 直接编辑;旧格式转换仍属于文档处理能力。
## 4. 功能要求
### FR-1 打开文档
用户可以从工作区文件、成果或会话文件引用中选择“在文档页签中打开”。Desktop 必须先验证
文件范围、扩展名、真实格式、大小和当前 ShareServer 能力,再创建编辑会话。失败时不得上传
部分文件或留下可继续访问的会话。
### FR-2 多实例文档页签
“文档”是助手工作栏的多实例能力。每次打开创建一个新页签,不把所有文件复用为单例。
页签主标题为文件名;同名页签增加序号,完整来源通过 Tooltip 和页签内来源信息显示。
### FR-3 基础编辑
编辑器至少支持 DOCX、XLSX 和 PPTX 的打开、人工编辑、保存、撤销、重做、复制粘贴、查找和
格式工具。具体格式兼容程度以真实样本验收为准,不因文件能打开就宣称完整兼容。
### FR-4 保存与回写
ONLYOFFICE 保存先形成 ShareServer 保存检查点。Desktop 下载并验证检查点后,只有在源文件
仍与打开时或上次成功保存后的版本一致时才原子替换源文件。成功后更新本地基准校验和和页签
状态。任何中间失败都保留原文件。
### FR-5 另存为
用户可以把当前保存检查点另存到当前已授权执行空间内的新路径。目标选择和冲突确认由 Desktop
完成;ShareServer 和编辑器不能决定本机路径。
### FR-6 AI 选区编辑
用户在编辑器中明确选择文字、单元格、对象、页面或幻灯片后,可以发起 AI 编辑。编辑器只提供
本次选区的有界结构和必要上下文。Agent 返回编辑器支持的结构化操作,由编辑器插件应用;一次
请求形成一个撤销事务,并在完成后标出修改位置。只有 Execute 模式可以应用修改;Ask 模式
可以生成建议,但不得调用编辑器写入。
### FR-7 权限与数据去向
创建会话前持续显示目标 ShareServer、将上传的文件、服务端可读取文档内容以及默认保留期限。
组织策略可以禁止格式、限制文件大小、强制只读或禁止 AI 编辑。Desktop 偏好只能收紧策略。
### FR-8 版本冲突
源文件在编辑期间被其他程序修改时,Desktop 不得覆盖。用户可以重新加载外部版本、将编辑
检查点另存为新文件,或显式比较后选择替换;首期不自动合并二进制 Office 文件。
### FR-9 断线与恢复
短暂断线不关闭页签。界面区分 Desktop 到 ShareServer、ShareServer 到 ONLYOFFICE 和保存回调
故障。只有 ShareServer 确认存在稳定检查点时才显示“可恢复”;未知结果不得显示为已保存。
### FR-10 关闭
没有未回写修改时直接关闭。存在 ShareServer 检查点但尚未回写源文件时,用户选择“保存并
关闭”“放弃修改”或“取消”。放弃只结束编辑会话和删除临时工作副本,不删除源文件。
### FR-11 审计
Desktop 记录本地打开、保存、另存为、冲突、AI 编辑请求和终态。ShareServer 记录组织、主体、
会话、文档类型、字节数、策略、编辑器版本、保存版本和保留清理,不默认记录选区正文、完整
文档内容、提示词或模型输出。
### FR-12 服务配置
Desktop 只连接用户已明确添加并认证的 ShareServer。ONLYOFFICE 地址、JWT 密钥、对象存储和
回调配置由 ShareServer 管理,不进入 Desktop Renderer 或普通设置导出。
## 5. 文件范围
首期可编辑格式:
| 类型 | 格式 | 编辑器类型 |
| --- | --- | --- |
| 文档 | `.docx` | `word` |
| 表格 | `.xlsx` | `cell` |
| 演示文稿 | `.pptx` | `slide` |
PDF 保持预览或文档处理用途,不进入首期 Office 编辑。宏格式、模板格式、受密码保护文件和包含
活动内容的文件默认拒绝,后续必须单独完成格式、安全和保存兼容性设计。
## 6. 验收标准
- 三个 Desktop 操作系统均能从同一 ShareServer 打开、编辑和保存三种首期格式。
- 同一工作栏至少可以同时保留两个独立文档页签,切换不会串文件、选区或保存状态。
- 同名文件的页签可辨认,页签标题和完整来源一致。
- 保存失败、回调失败和源文件冲突均不会破坏原文件。
- 每次 AI 修改可以单次撤销,拒绝或取消不会产生部分修改。
- ShareServer 不可用时已有本地文件仍可由默认应用打开,但不能伪装成内置编辑可用。
- Renderer、编辑器页面和日志中均不存在本机绝对路径、ShareServer 管理密钥或长期文件令牌。
- 文档会话过期后,旧启动 URL、文件 URL 和回调令牌均不可重用。
@@ -0,0 +1,217 @@
# Office 协同编辑技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-09 |
| 关联 PRD | [Office 协同编辑 PRD](./prd.md) |
| 跨端架构 | [ShareServer 联邦网络总体设计](../../architecture/share-network-architecture.md) |
## 1. 技术选型
首个 Provider 使用 ONLYOFFICE Docs Developer Edition。采用前必须确认当前许可证、桌面应用
分发权、并发连接口径、白标、Automation API、离线私有部署和安全支持;技术设计不构成授权
结论。
不采用:
- ONLYOFFICE Desktop Editors:它是独立桌面产品,不是 GoodBuddy 工作栏嵌入 SDK。
- Node/Python OOXML 库:只提供生成或结构化处理,不提供高保真画布、选区和撤销栈。
- Microsoft WOPI:第三方接入资格和云授权不适合作为 GoodBuddy 默认能力。
- 自研 OOXML 渲染器:格式兼容和编辑成本超出当前产品边界。
后续 Provider 可以增加 Univer Sheets,但只能按通过验收的格式和能力声明,不能由“纯前端”推导
为完整 Office 兼容。
## 2. 组件边界
```text
GoodBuddy Renderer
└─ 文档 Tab 外壳、公开状态、用户命令
GoodBuddy Main
├─ DocumentEditSessionService
├─ 本地/SSH 源文件 Grant 与校验
├─ 上传、下载、原子回写和冲突检测
├─ OfficeEditorView 生命周期
└─ 本地活动记录
ShareServer
├─ 用户、设备、组织和策略认证
├─ Office 会话 API 与短期启动授权
├─ 工作副本、保存检查点和保留清理
├─ ONLYOFFICE JWT、文件 URL 和 callback handler
├─ 编辑器版本、健康、配额和审计
└─ 可选 AI 编辑消息中继
ONLYOFFICE Document Server
└─ Word、Spreadsheet、Presentation Web Editors
```
ShareServer 可以把 ONLYOFFICE 和对象存储作为同一部署套件的依赖管理,但两者仍是独立服务。
Desktop 不读取 Document Server 地址或密钥,只消费 ShareServer 返回的一次性编辑启动 URL。
## 3. 工作栏实例
在工作栏应用注册表新增多实例 `document`:
```ts
type DocumentPanelInstance = {
id: string
capability: 'document'
documentSessionId: string
displayName: string
duplicateIndex?: number
binding: { mode: 'pinned'; target: { type: 'document-session'; id: string } }
dock: 'right' | 'bottom' | 'window'
}
```
持久布局只保存公开会话 ID、显示名、来源对象 ID 和停靠方式,不保存启动 URL、Cookie、文件
令牌、绝对路径或 ONLYOFFICE 配置。应用恢复时由 Main 重新认证并判断会话能否恢复。
编辑器内容不使用现有静态 HTML `iframe sandbox=""`。Main 为每个已加载会话创建独立
`WebContentsView`,固定允许的 ShareServer origin、隔离 session partition、关闭 Node
integration、开启 context isolation 和 sandbox,并禁用新窗口、任意下载、权限请求和非允许
导航。远端页面不获得通用 Electron preload API。
多页签允许多个会话描述同时存在;只有经过资源验收的有限数量编辑器保持加载。非活动实例的
具体挂起和恢复阈值在跨平台内存测试后冻结,不能未经保存检查点直接销毁 dirty 编辑器。
## 4. ShareServer API 边界
建议的服务端资源:
```text
POST /api/v1/office/sessions
PUT /api/v1/office/sessions/{id}/source
POST /api/v1/office/sessions/{id}/launch
GET /api/v1/office/sessions/{id}
GET /api/v1/office/sessions/{id}/events
GET /api/v1/office/sessions/{id}/checkpoints/{revision}
POST /api/v1/office/sessions/{id}/source-commit
DELETE /api/v1/office/sessions/{id}
GET /internal/office/files/{objectToken}
POST /internal/office/callback/{callbackToken}
```
Desktop API 使用设备凭据和组织 audience。启动 URL 使用一次性 code,交换为 HttpOnly、Secure、
SameSite Cookie 后立即失效。ONLYOFFICE 文件 URL、callback URL 和 JWT 使用独立 audience、短
有效期及单会话绑定,不能用于普通 ShareServer API。
ShareServer 不把对象存储公开 URL 直接返回 Renderer。文件下载必须重新验证用户、设备、会话、
revision 和策略,并返回声明大小和 SHA-256。
## 5. 会话契约
```ts
type CreateDocumentEditSession = {
source: {
kind: 'workspace-file' | 'artifact'
publicSourceId: string
displayName: string
mediaType: string
size: number
sha256: string
}
requestedAccess: 'edit' | 'read-only'
}
type DocumentEditSessionSummary = {
id: string
state: 'uploading' | 'ready' | 'editing' | 'offline' | 'expired' | 'closed'
access: 'edit' | 'read-only'
documentType: 'word' | 'cell' | 'slide'
displayName: string
sourceRevision: string
checkpoint?: {
revision: string
size: number
sha256: string
createdAt: string
}
retentionExpiresAt: string
}
```
远端值全部经过有界 schema 校验。`publicSourceId` 是 Desktop 管理的对象 ID,不是文件路径。
## 6. 文件与保存
- Desktop 从已授权本地或 SSH 源读取并流式计算大小和 SHA-256。
- 上传目标由 Main 从固定 ShareServer API 获得,不接受 Renderer 提供 URL。
- ShareServer 写入随机对象键,拒绝用户文件名决定存储路径。
- ONLYOFFICE callback 先写临时对象,校验后原子发布为新 checkpoint。
- Desktop 下载 checkpoint 到 GoodBuddy-owned 暂存,复核大小、SHA-256、普通文件属性和文件
magic,再在源文件同目录创建临时文件并原子替换。
- SSH 源文件由远程 GoodBuddy Agent 执行相同摘要、冲突和原子替换,不把远程凭据交给
ShareServer。
- 每次成功回写后 Desktop 将新摘要作为下一次冲突检测基准。
## 7. 编辑器桥与 AI 编辑
ShareServer 托管固定版本的 GoodBuddy ONLYOFFICE 插件。插件只能:
- 获取当前会话和有界选区描述;
- 发起 AI 编辑请求并显示状态;
- 接收受限结构化编辑操作;
- 在一个编辑器撤销上下文中应用、回滚和高亮修改;
- 发布不含正文的 dirty、save 和 revision 事件。
首期优先让插件通过 ShareServer 会话通道与 Desktop 交换事件,不给远端编辑页面添加通用
Electron preload。ShareServer 只中继有界事件;Agent 请求仍由 Desktop 创建并经过现有
Conversation、模型、Ask/Execute、取消和活动记录边界。
Ask 请求可以返回可复制的修改建议,但 Main 拒绝向插件下发写操作。只有 Execute 请求能够
取得绑定当前文档会话、revision 和选区的短期编辑 Grant;该 Grant 不包含源文件路径,也不能
用于其他文档、文件工具或系统能力。
结构化操作按文档类型单独列白名单,例如文本替换、单元格值或公式更新、幻灯片对象文字
替换。禁止传入 JavaScript、宏、任意 ONLYOFFICE 方法名或表达式求值。
## 8. 部署与配置
ShareServer 部署清单新增可选 Office 组件:
```text
ShareServer API
PostgreSQL
兼容 S3 的对象存储
ONLYOFFICE Document Server
Office callback worker
```
服务端管理配置包含 Document Server 内部地址、公开编辑 origin、JWT 密钥引用、对象存储、
允许格式、文件大小、并发、会话期限和 checkpoint 保留。管理界面提供真实健康检查:创建最小
文档、打开编辑器、执行保存回调并核对输出,而不是只检查 HTTP 端口。
Desktop 仅显示某个 ShareServer 是否声明并实际通过 `office.edit` 能力检查。未配置或不健康时
保留文档入口并说明不可用原因。
## 9. 安全边界
1. ShareServer 和 ONLYOFFICE 之间使用独立网络和凭据,不公开管理接口。
2. 文档内容是不可信输入;禁用宏、外部插件、任意外链加载和 Document Server 出站网络。
3. 每个文件 URL、callback 和启动授权绑定单会话、单用途和短期限。
4. Renderer 不接触设备凭据、ONLYOFFICE JWT、对象存储凭据或本机绝对路径。
5. 编辑器 origin 不能调用任意 IPC、Shell、文件系统、浏览器或 Runtime 工具。
6. 上传、下载、callback、事件和 AI 操作限制字节数、频率、并发和总时长。
7. 服务端保留策略到期后清理工作副本、checkpoint、编辑 Cookie 和对象令牌;审计元数据按组织
策略独立保留。
8. 组织管理员能够撤销 Office 能力和活动会话;撤销后 Desktop 仍保留本地源文件,不把服务端
删除描述为本地删除。
## 10. 实施顺序
1. 在 ShareServer 独立仓库补充 Office 服务 PRD、部署配置、对象存储和 ONLYOFFICE 许可证决策。
2. 实现服务端最小会话、上传、启动、callback、checkpoint、下载和清理闭环。
3. Desktop 增加多实例文档页签和只读打开,完成三平台真实视觉及资源验证。
4. 接入源文件冲突检测、原子保存、另存为、关闭和恢复。
5. 实现固定 ONLYOFFICE 插件及单次可撤销 AI 选区修改。
6. 增加组织策略、配额、审计和生产部署验收。
不得在服务端闭环、许可证和三平台真实编辑验收前把该能力标记为已提供。
@@ -0,0 +1,83 @@
# Office 协同编辑界面设计
## 1. 信息架构
Office 文档不是一级页面,也不是成果面板中的内嵌卡片。它是助手工作栏中可创建多个实例的
“文档”应用。打开文件后新增文档页签,页签关闭只结束对应编辑会话。
```text
助手工作栏 Tab 行
[任务] [工作区] [预算.xlsx] [方案.docx] [+]
└─ 当前文档编辑器
```
“+”能力目录持续提供“文档”入口。用户从目录打开时选择已授权文件;从工作区、成果或会话文件
引用打开时直接创建实例。
## 2. 页签
- 图标按文档、表格和演示文稿类型显示,使用现有图标库。
- 可见标题使用文件名,不显示完整路径。
- 同名实例依次显示 `预算.xlsx`、`预算.xlsx · 2`。
- Tooltip 显示完整文件名、项目或成果来源、ShareServer 名称和只读状态,不显示本机绝对路径。
- `aria-label` 包含文件名、序号和状态。
- 加载、未保存、保存中、冲突和断线使用状态图标及可访问名称,不只依赖颜色。
- 文档应用为 `multiple`,不因已有文档页签而聚焦或替换其他实例。
## 3. 文档标题栏
编辑器上方保留 GoodBuddy 控制的紧凑标题栏:
- 左侧:文件名、来源范围、只读或冲突状态。
- 中部:最近一次源文件保存结果。
- 右侧:保存、另存为、刷新状态、在独立窗口打开和更多菜单。
撤销、重做、格式、查找和文档内部编辑命令使用 ONLYOFFICE 工具栏,不在 GoodBuddy 标题栏
重复。保存按钮必须区分“正在保存到 ShareServer”和“已写回源文件”。
## 4. 尺寸与停靠
文档页签沿用工作栏右侧、底部和独立窗口三种停靠方式,但编辑器有额外宽度要求:
| 可用宽度 | 行为 |
| --- | --- |
| `>= 720px` | 完整编辑器与完整工具栏 |
| `480px - 719px` | 编辑器精简工具栏,保留核心编辑命令 |
| `< 480px` | 不加载可编辑画布,显示文件摘要、状态和“展开编辑”命令 |
右侧工作栏达到普通 `300px` 最小宽度时不能把完整 Office 工具栏压缩到重叠。用户选择“展开
编辑”时切换到主内容区或独立窗口,仍复用原文档页签和编辑会话,不创建第二份文档。
文档画布、公式栏、幻灯片缩略图和滚动区域使用稳定尺寸;编辑器加载、状态文字或长文件名不得
推动 Tab 行和标题栏重新布局。
## 5. 状态与反馈
| 状态 | 可见反馈 | 允许操作 |
| --- | --- | --- |
| 上传中 | 上传进度、目标 ShareServer、取消 | 取消 |
| 编辑中 | 编辑器、来源和最近保存时间 | 编辑、AI 编辑、保存 |
| 只读 | 持续只读标识和原因 | 查看、另存为 |
| 保存中 | “正在创建检查点”或“正在写回源文件” | 不重复保存,可继续查看 |
| 冲突 | 持续警告和两个版本的时间、大小 | 打开外部版本、另存、替换 |
| 断线 | 明确失败阶段和重试 | 查看最后已加载内容、重连 |
| 会话过期 | 修改是否存在稳定检查点 | 恢复检查点或关闭 |
通知不能代替页签内持续状态。后台文档保存完成只更新页签状态和应用通知,不抢占当前页签。
## 6. AI 编辑
- AI 编辑入口出现在 ONLYOFFICE 选区工具栏或 GoodBuddy 编辑器插件中。
- 点击后使用紧凑指令输入框;当前选区类型和范围持续可见。
- 发送前显示将使用的 Conversation、模型和 ShareServer,不展示内部选区 ID。
- 处理中锁定本次选区事务,不锁定整个文档的阅读和滚动。
- 成功后高亮所有修改位置,并显示可用的撤销命令。
- 文档 revision 已变化时不静默套用旧结果,要求重新选择或重新生成。
## 7. 可访问性与主题
- GoodBuddy 外壳遵循根级 `UI-DESIGN.md` 的主题和焦点规则。
- 编辑器主题跟随应用主题,但第三方编辑器无法完整匹配时优先保证可读性,不注入脆弱的 DOM
样式覆盖。
- Tab、标题栏和冲突对话框支持完整键盘操作及明确焦点恢复。
- 第三方编辑器页面必须提供可识别标题;加载失败后焦点回到重试命令。
@@ -0,0 +1,85 @@
# Office 协同编辑用户故事
## US-A1 打开本地文档
**Given** 用户已连接支持 Office 编辑的 ShareServer,并在工作区选择一个受支持文件
**When** 用户选择“在文档页签中打开”
**Then** GoodBuddy 显示目标服务和数据去向,验证并上传工作副本,然后以文件名创建新文档
页签;失败不会修改源文件或留下可重用文件授权。
## US-A2 同时打开多个文档
**Given** 工作栏已经存在一个文档页签
**When** 用户打开另一个文档或再次打开同一文档
**Then** 工作栏创建独立页签,不替换现有实例;同名页签可辨认,文件、选区和保存状态不串联。
## US-B1 人工编辑并保存
**Given** 文档编辑器已进入可编辑状态
**When** 用户修改内容并保存
**Then** ShareServer 先形成稳定检查点,Desktop 确认源文件未被外部修改后原子回写,并明确
显示“已写回源文件”。
## US-B2 源文件发生冲突
**Given** 用户编辑期间源文件被其他程序修改
**When** 新检查点准备回写
**Then** GoodBuddy 禁止静默覆盖,并允许用户查看外部版本、另存编辑版本、放弃编辑版本或经
二次确认替换。
## US-B3 关闭未保存文档
**Given** 页签存在未形成检查点或尚未回写源文件的修改
**When** 用户关闭页签或退出应用
**Then** GoodBuddy 根据真实可恢复状态提供保存、放弃或取消,不把未知结果显示为已保存。
## US-C1 使用 AI 修改选区
**Given** 当前 Conversation 使用 Execute,用户在编辑器中选择受支持内容
**When** 用户提交 AI 修改要求
**Then** Agent 只接收有界选区和必要上下文,编辑器将完整结果作为一个事务应用并允许单次撤销。
## US-C2 Ask 模式请求修改
**Given** 当前 Conversation 使用 Ask
**When** 用户请求 AI 修改当前选区
**Then** GoodBuddy 可以生成修改建议,但不会调用编辑器写入;界面提供切换 Execute 后重新应用
的明确入口。
## US-D1 网络中断后恢复
**Given** 编辑会话已经建立
**When** Desktop、ShareServer 或 ONLYOFFICE 之间的连接中断后恢复
**Then** 页签保留并显示失败阶段;只有 ShareServer 确认存在稳定检查点时才提供恢复和回写。
## US-D2 服务未配置或不健康
**Given** 用户没有连接支持 Office 编辑的 ShareServer,或服务健康检查失败
**When** 用户打开文档能力
**Then** 入口仍可发现并解释不可用原因,同时保留使用系统默认应用打开本地文件的独立操作。
## US-E1 权限撤销和保留到期
**Given** 管理员撤销编辑权限,或编辑会话达到保留期限
**When** 用户继续访问旧页签
**Then** 新读取、保存和 AI 操作被拒绝,旧令牌不能重用,本地源文件不受服务端清理影响。
+4
View File
@@ -11,3 +11,7 @@
| [远程环境准备技术设计](./environment-provisioning-technical-design.md) | Host 下载、GoodBuddy 传输、安装事务、更新和恢复边界 |
现有文档包含产品、功能逻辑和进度事实,后续修改远程功能时再按标准职责拆分。
跨会话共享 OpenCode 进程、ACP 连接与模型桥的实现设计,见
[Runtime 进程复用技术设计](../assistant-workbar/runtime-process-reuse-technical-design.md#6-ssh-opencode-与-agent)。
当前实现尚未发布;验收状态见[工作栏进度](../assistant-workbar/progress.md#2026-09-14-runtime-进程复用实施中)。
+170 -21
View File
@@ -2,7 +2,7 @@
## 状态
本文记录截至 2026-09-09 的当前代码实现,不定义额外的信任框架。“新增 Host 只探测、Host 卡片手动准备
下述状态段保留 2026-09-10 的实现与验证截面,不定义额外的信任框架。“新增 Host 只探测、Host 卡片手动准备
Agent/Runtime、Host 直接从 GitHub/北京镜像下载、项目始终使用 Host current 环境”已经完成源码接线,
详细事务与验收边界见
[SSH Host 远程环境准备与直连下载设计](./environment-provisioning-technical-design.md);
@@ -11,12 +11,42 @@ Agent/Runtime、Host 直接从 GitHub/北京镜像下载、项目始终使用 Ho
Linux x64/arm64、取消和离线 GoodBuddy 传输的真实 Host 验收。
Windows 到 Linux x64 的安装、Agent-owned Prompt、Agent 本地模型 gateway、断线恢复、
同一 OpenCode Session 续接、取消和终态清理已经使用真实模型与工具验证。Agent
`0.11.14` 已通过独立 workflow 发布 Linux x64/arm64 复合包和签名累计目录;当前源码
候选为 Agent `0.11.22`、Desktop `0.12.10`,包含托管 OpenCode 自动快照策略修复。
`0.11.14` 已通过独立 workflow 发布 Linux x64/arm64 复合包和签名累计目录;当时源码
候选为 Agent `0.11.24`、Desktop `0.13.0`,修复远端原生问答转交和取消待答后的同会话
续发。Agent `0.11.24` 要求先升级至 Desktop `0.13.0`;当时候选尚未发布。远程文件/Git 管理、
可选模型限额、无限请求时长下的独立连接超时和移除固定十分钟 Prompt 时限由
Agent `0.11.23` 提供,显式请求期限与取消仍有效。当前问答源码的真实 SSH/确定性模型证据
及完整生产链路验证边界见[工作栏进度](../assistant-workbar/progress.md)。
正式发布状态以 Agent 与 Desktop 独立发布渠道为准。
现有源码显示本地与远端 OpenCode 原生 Task,并取消 GoodBuddy 对生产 Prompt 的
固定墙钟总时限。失败的 `agent-v0.11.3` 保持不可变且未发布。
2026-09-14 发布状态补注:Desktop `0.13.2` 与 Agent `0.11.25` 已正式发布,包含此前
问答修复;Agent `0.11.25` 的维护发布要求 Desktop `0.13.2`。下节进程复用为当前未发布源码,
不是这两个发布的已有能力;历史验证段的日期与边界保留。
## 跨会话进程复用(未发布)
已发布版本的 `RuntimeAcpBackend` 按 binding 拥有原生进程,`AgentOwnedAcpPrompt` 的一条 ACP
连接对应一个原生 Session;同一会话后续 Prompt 可复用,但不同会话不共用进程。
上文“Host 共享 Runtime 环境”指安装与 Agent 环境,不代表原生 OpenCode 进程已共享。
锁定 OpenCode 1.18.29 / ACP SDK 0.25.1 的 Windows 隔离探针已确认:同一个 `acp`
进程与一条连接可运行两个工作区 Session,原生 `chat.headers` 能向本机合成模型请求
附带各自 sessionID。它未经过 Agent 所有权、生产 helper、真实工具或 Linux Host,
不是远端交付证明。
进程 owner 与 binding 拆分、单 ACP 连接分派、会话级权限以及模型桥逐 operation
路由的唯一方案见
[Runtime 进程复用技术设计 §6](../assistant-workbar/runtime-process-reuse-technical-design.md#6-ssh-opencode-与-agent)。
实施必须保留本文件的 released binding、Agent-owned Prompt、transcript/ACK、模型调用
结果不确定性与断线规则,并在开发期间执行下文真实 Host 验证,不为复用新增持久化账本。
当前源码已接入共享 process owner、`AgentAcpConnection` 和逐 operation 模型路由;
验收进展见[工作栏进度](../assistant-workbar/progress.md#2026-09-14-runtime-进程复用实施中)。
取消/失败的 Session 可以留下运行中的共享进程,结果中的 `processTree` 如实返回
`running`,不能再据 Prompt 终态推断整进程为空。新行为需要配套 Desktop/Agent,
已发布 Desktop `0.13.2` / Agent `0.11.25` 不包含这些改动。
## 产品语义
远程 Host 与项目能力是默认关闭的技术预览。用户在“设置 > 平台功能”的独立
@@ -27,7 +57,7 @@ Windows 到 Linux x64 的安装、Agent-owned Prompt、Agent 本地模型 gatewa
远程项目只有两种工作模式:
- **Ask**:Runtime 在操作系统边界以只读方式访问项目 Workspace。
- **Ask**:Runtime 在原生工具权限边界只读访问项目 Workspace,不声称操作系统级只读隔离。
- **Execute**:用户已授权使用所选 SSH 账号的完整权限。Runtime 可以使用该账号可访问的文件、进程、网络和工具,不再要求额外 trust tier、consent checklist、逐工具审批或“受控执行”授权。
Execute 不获得 root 或 SSH 账号本身没有的权限。托管 SSH Prompt 会把当前选中的文本模型
@@ -37,6 +67,12 @@ profile 和凭据作为有界、逐 Prompt 的控制消息交给 Agent;该凭
## 组件
桌面保存托管 SSH 子任务事件时,共用
[执行记录差量存储](../assistant-workbar/execution-history-storage.md)。
Agent transcript 与公共 Runtime 事件不改变;桌面的来源去重、消息投影事务和恢复序号
继续保留。工具字段比较与 schema 35 对 schema 34 重复块的整理也在该共享桌面层完成;
不把工具过程改为仅存内存,不要求单独升级 Agent。
```text
Renderer
-> 窄 Preload / Main IPC
@@ -202,15 +238,16 @@ selection,Renderer 会恢复为当前 OpenCode 配置;Main 的远程请求
Ask 与 Execute 一样直接启动已签名 Runtime,不要求 Host 安装额外的进程隔离命令:
- `cwd` 为项目 Workspace,并继承 SSH 账号的正常环境;
- ACP 权限请求只有在工具种类为原生 `read` 且 Runtime 提供 `allow_once` 选项时才允许;
search、edit、execute、未知工具以及只提供持久授权的请求全部拒绝。
- Ask 启动时同时注入 OpenCode 顶层和 build Agent 的 `permission: "ask"`;Agent
工具权限分发边界只批准上述原生读取请求。该双重边界不另加文件系统 confinement,
也不改变 Runtime 的直接启动方式。
- Execute 的直接启动与模型桥启动均显式设置顶层和 build Agent 的 `permission: "allow"`,
避免继承 OpenCode 默认的 `external_directory` 询问;项目目录只是默认工作目录。
原生子代理同样使用当前 SSH 账号可访问的路径。仍出现的 ACP 权限请求由 Agent 按
当前 Prompt 模式自动答复,不增加 Desktop 人工审批,也不依赖日志解析。
- Agent-owned ACP 的备用权限分派允许原生 `read`/`search`,优先选择 `allow_once`;
写入、执行和未知工具仍拒绝。独占 raw ACP 保留其原有的读取权限分派。
- 未发布的共享路径以中性的 `permission: "ask"` 启动一个进程,再于每个 Session 的
`chat.message` 按已登记 workMode 设置原生工具规则。Ask 默认拒绝,放行已支持的
读取、搜索和业务问答;子 Session 同步根会话模式并保留原生显式限制,避免固定版本
ACP 不转发子 Session 权限请求而永远等待。
- Execute Session 放行工具,不再被另一个 Ask Session 的进程级默认值限制。项目目录
是默认 cwd,原生子代理仍使用所选 SSH 账号可访问的路径;不增加 Desktop 人工审批。
规则及模型路由的唯一实现说明见
[共享 Agent Session 设计](../assistant-workbar/runtime-process-reuse-technical-design.md#6-ssh-opencode-与-agent)。
### Execute
@@ -222,7 +259,7 @@ Execute 直接启动已签名 Runtime:
launcher 路径;
- `cwd` 为项目 Workspace;
- 继承 SSH 账号的正常环境、文件系统、进程和网络能力;
- 不注入 OpenCode Ask 权限配置;
- 独占启动使用 Execute 配置;共享启动由 Session 规则应用 Execute,不修改其他会话模式;
- 不进行 T2/T3、confinement attestation、approval bridge 或逐工具批准。
两种模式都保留输入字节上限、用户取消和进程组清理;输出只用有界内存队列与 journal
@@ -234,6 +271,19 @@ Execute 直接启动已签名 Runtime:
`0.11.10` 及更早版本仍会按其 manifest 上限夹紧,因此桌面启用这一语义前必须配套更新
到 Agent `0.11.13` 或更高版本。
## 执行清单接入范围
OC `todowrite` / CN `Checklist` 顶部展示属于待实施功能,权威需求及接入设计见
[Runtime 执行清单](../assistant-workbar/runtime-checklist-technical-design.md),
当前证据见[工作栏进度](../assistant-workbar/progress.md#2026-09-15-runtime-执行清单设计未实现)。
目标范围包括远程 GoodBuddy Agent 运行 OC 和 CN;ACP 仅承担传输与远端协议。
当前托管安装与实时 Runtime 路径仍只接受 OC,不能把通用 ACP plan/tool 传递代码视作
远程 CN 已可用。CN 的受管工件、安装/启动、项目选择与校验、模型桥及原生清单传递
需要在该功能实施时补齐,并在共享 Linux 真实 Host 分别确认 OC/CN 的可用程度。
本次只完善文档,未实测任一远程 Runtime 的清单;已有 OC Ask/Execute、问答或重连证据
不作为本功能通过证据。清单恢复复用下节流程,不增加独立持久化或重连系统。
## ACP 与断线
- 管控面只负责有界 JSON-RPC:连接、查询、启动、取消、关闭和重连。数据面只负责
@@ -261,8 +311,30 @@ Execute 直接启动已签名 Runtime:
- 本地 SDK 通过 Task `metadata.sessionId` 关联子会话,复用稳定消息块 ID 保存过程。
固定 OpenCode 1.18.29 的 ACP 不转发子会话,因此 Agent 的模型桥 helper 在受管临时目录
写入事件插件,并通过既有 ACP `session/update` 的 `_meta.goodbuddySubagentEvent`
转发到所属 Task;不新增端口,不修改签名 Runtime 包。插件文件随 helper 退出清理,
转发到所属 Task;不修改签名 Runtime 包。插件文件随 helper 退出清理,
事件继续经过既有 Agent transcript、ACK 和 Desktop 消息持久化路径。
- 托管 helper 显式设置 `OPENCODE_ENABLE_QUESTION_TOOL=true`,启用 ACP 默认隐藏的原生
问题工具。插件只处理 `question.asked`,通过 Session `parentID` 逐级确认根会话,并核对
`/question` 当前待答列表;不从工具进度或正文构造问题。父、子孙会话问题使用
`_meta.goodbuddyQuestion` 送到所属 ACP 会话,有原生 Task 关联时附带对应 call ID。
- Agent 按当前 ACP 会话和 Prompt 保存待答映射,Desktop 公开 ID 包含 binding、operation
和原生问题 ID。回答与跳过经既有认证控制通道的 `runtime/respondToQuestion` 发送;后端
检查 controller、generation、connection、binding 和活动 operation,再调用插件仅绑定
`127.0.0.1` 的临时 HTTP 入口。该入口使用进程级随机路径,能力 URL 不写入语义记录。
插件复用 OpenCode 注入客户端的进程内 HTTP 适配器调用 `/question/{id}/reply` 或
`/reject`;空答案数组表示拒答,非空答案数量须与问题数量一致。问答不经过 ACP 权限审批。
用户取消托管 Prompt 时,Agent 同步记录语义终态和操作终态。Desktop 等待取消升级及
终态核对后关闭旧 binding、释放会话通道;同一对话的下一条消息使用新 binding,避免
将新请求写入仍携带旧 operation ID 的 binding。断线附加原请求的规则保持不变。
- 语义记录分页另带 Agent 当前 `pendingQuestions`,用于附加同一存活 Prompt 后恢复待答
问题;历史问答元数据不重新生成表单。原生已回答、拒答、会话空闲、取消、Prompt 终结
和进程退出清除相应映射,迟到的回答报错。回复失败保留桌面输入供重试,答案不会跨连接
自动重放。插件随 OpenCode 实例释放临时端口,helper 退出时删除插件文件。
以上只适用于当前 Agent 管理的 SSH OpenCode 生产路径,不声明任意 ACP 服务的问答兼容性。
真实二进制、SSH 传输和取消验证见[工作栏进度](../assistant-workbar/progress.md#2026-09-13-托管-ssh-原生问答)。
未发布进程复用源码补齐 Desktop 重启后的多任务并行附加、实时待答快照及 Main 答案
持久化,详见[恢复与终态投影](../assistant-workbar/runtime-process-reuse-technical-design.md#63-恢复与终态投影);
不把该待答投影当作 durable transcript 或旧版本已经支持的行为。
- ACP 工具结果同时支持文本 `content` 和 `rawOutput.output`,移除 OpenCode 的
`<task><task_result>` 外层包装后渲染正文。恢复已有子代理时使用 ACP 所带的所属
`toolCallId` 继续路由,不等待新的父 Task metadata,也不重放模型请求。
@@ -270,7 +342,9 @@ Execute 直接启动已签名 Runtime:
不会被补造。
- 本机 OpenCode SDK 路径允许不同会话并行,同一会话仍按请求顺序执行。每个请求独立拥有
SSE 事件订阅,并在正常完成、错误、取消或消费方结束迭代时主动关闭自己的响应流,
不关闭其他会话的订阅或共享 Server。取消导致事件迭代结束时保留原取消原因,不改报
不关闭其他会话的订阅或共享 Server。聊天与原生 Compact 共用 `consumeEventSubscription`,
在结束 SDK 迭代器之前先取消对应订阅,避免 SDK 移除取消监听后留下仍有缓冲的响应流;
不改写 SDK 或提前取消其他请求。取消导致事件迭代结束时保留原取消原因,不改报
“事件流意外结束”。这一清理规则属于本机 SDK 路径,不改变远端 Agent 持有 ACP Prompt
的断线继续执行语义。
- 同一 detached Agent 存活时,短暂 SSH 断线依次执行 `controller/resume`、`runtime/resumeAcpChannel` 和 `runtime/replayAcpChannel`,从 Main 已确认的 cursor 后只重放 Agent 到 Main 的已记录输出。重复 frame 会被确认但不会再次交给上层。若上一代连接只留下没有活动请求的 detached binding,完成精确 controller takeover 后会先有界停止并核对遗留 Runtime process,再用新 channel epoch 重新打开同一 binding 并恢复已有 ACP session;其他 controller、未证明 takeover 或仍有活动请求的 binding 仍被拒绝。
@@ -302,6 +376,10 @@ Execute 直接启动已签名 Runtime:
时提交 Task 终态;事务成功后生成器才继续并向 Agent ACK。断电发生在同一 sequence
中间时会重读该 sequence,已写事件按 `(binding, operation, sequence, eventIndex)` 去重,
未写事件继续归并,不会重复 Provider 或工具执行。
- 远程 context metrics 与对话级压缩后的估算值按本次请求实际使用的 Runtime 选择写入
`context_state_json`。实时接收与恢复入口都传入请求的选择;数据库在未传入时按对话、
项目、auto 顺序解析。继承项目设置的对话保持 `runtime_selection_json = NULL`,
不为接收 usage 固定对话 Runtime;去重和消息/指标写入仍在同一 SQLite 事务中完成。
- ACK 后 Agent 删除已确认事件,只保留小型操作终态与游标用于幂等核对;未 ACK 输出继续
保留。Main 按 transcript page 的最高连续 sequence ACK,终态仍立即 ACK。单事件、单页、
单 Prompt 总字节和事件数都有上限;Agent 始终为唯一终态预留一个事件和最大事件字节,
@@ -315,6 +393,12 @@ Execute 直接启动已签名 Runtime:
权威。启动后 Renderer 先订阅恢复进度,再触发每项目恢复。项目选择器依次显示网络、
Agent、Runtime、cursor、完成或失败/重试;只阻塞受影响项目的发送和队列,已有历史与
其他项目保持可用。
- 完成提示是项目选择器内恢复进度的收尾反馈,首次收到该项目该 `requestId` 的完成状态后
显示 3 秒,然后从收起按钮和展开菜单同时隐藏;不另发全局成功通知。计时由
`ProjectSwitcher` 持有,切换项目、开合菜单或重复状态快照不延长期限,也不重放已隐藏的
提示;新 `requestId` 的恢复可以再次展示。进行中状态和失败提示持续保留;重试操作仅放在
展开的项目列表对应项目旁,收起时只显示状态,不在新建项目按钮旁增加恢复按钮。
隐藏仅作用于展示层,`App` 保留完成业务状态,继续用于发送、队列恢复和事件顺序判断。
- 正常应用退出对已接受托管 SSH 请求执行 detach,不等待远端完成;本地请求和尚未接受的
请求仍按原行为取消。用户显式“停止”始终发送稳定 operation cancellation,并等待 Agent
语义终态。已进入 Agent-owned `run` 但 start/attach 响应仍不确定的请求继续保留为
@@ -329,16 +413,31 @@ Execute 直接启动已签名 Runtime:
Agent,因此 Desktop 断电后可以继续下一轮。
- Agent 校验固定 model、协议和 API path,拒绝 background/store、provider web search、
provider MCP、持久 conversation、非文本 modality 和其他可独立计费能力;请求/响应 body
各限制 768 KiB,响应同时核对声明长度与短读。每个
各保留 64 MiB 的内存传输上限,用于容纳长上下文和图片输入,响应同时核对声明长度与短读。
Desktop blob 模型桥保留已发布 v1 的 canonical JSON 字节,不添加尾随换行。单条消息按
最多 2 MiB 的帧发送,接收端逐帧消费流量额度,跟踪 JSON 结构并在消息上限内组装;
已发布端的单帧消息可直接接收,多帧大消息要求两端均使用更新后的实现。每个
`(binding, operation, roundIndex)` 只 dispatch 一次;已 dispatch 但未证明完整交付的调用
变为 `outcome-unknown`,不会自动重试。
- Agent 不按固定模型调用次数或 Prompt 累计输出 Token 数停止 Runtime;每次 Provider
请求仍遵循所选模型 profile 的单次输出设置和独立请求超时。模型桥协议不包含 Prompt
请求遵循所选模型 profile 的单次输出设置;未设置时保留 Runtime 请求中的 Token 参数,
更新后的 Agent 不补入固定 32,000 Token,也不再按 60 秒或桥接层的 120/150/180 秒
总时长中止请求。它通过非 critical 的 `runtime/model-bridge-optional-limits` v1 capability
声明支持省略 profile limits;Main 在建立托管 Runtime 时按实际连接的 capability 选择
profile。锁定的已发布 Agent `0.11.22` 不支持省略这两个字段,Main 对该路径保留原有
32,000 Token、60,000 ms 默认值及 1,000,000 Token、300,000 ms schema 上界;这些兼容
值不应用于声明支持省略 limits 的 Agent。
显式请求超时、Prompt deadline、用户取消和连接关闭仍会中止对应操作。模型桥协议不包含 Prompt
调用次数或累计 Token 配额字段。实际 usage 在语义 transcript 中同步回 Desktop 的
usage/task 数据。
- helper 可以接收同一 Prompt 内并发到达的模型桥请求;它在单一稳定模型桥上按到达
顺序等待并交付,不返回本地 `bridge-busy`,每个响应只有在 HTTP 完整 flush 后才
发送 delivery ACK。
- 新组包的 Runtime manifest 将 `maximumPromptRuntimeMilliseconds` 设为 `0`,表示由
Prompt 自身的 deadline 决定时长,不再额外截为十分钟。旧 manifest 中的正数限制仍按其
声明执行。独占进程 owner 使用真实剩余时长安排 Prompt deadline;共享路径由 binding
执行逐 Prompt 输入与期限检查,不把历次 Prompt 累计当作整个进程的配额。
启动与停止等待的有界超时不再缩短 Prompt。
- helper 可以接收并发模型桥请求,同一 Session/operation 的稳定模型桥按到达顺序
等待并交付,不同 operation 可并行。请求在排队后再次核对原路由,过期请求不转交给
新 operation;每个响应只有在 HTTP 完整 flush 后才发送 delivery ACK。
- GoodBuddy 自己管理会话标题,因此传给 OpenCode 的配置禁用 title Agent;一次用户
Prompt 不会额外触发标题模型请求。
- Agent gateway 不再把 Provider 请求往返 Desktop;legacy blob bridge 只保留给旧的
@@ -346,12 +445,62 @@ Execute 直接启动已签名 Runtime:
- Unix socket 接收端按 `readableLength` 中已经缓冲的字节增量排空一个声明长度的帧,
不会在部分大响应到达时反复请求尚未缓冲的完整剩余长度;短读、连接结束、错误和取消
都会使当前交换失败。原生 Linux 回归覆盖至少 256 KiB 的 broker 响应。
- Unix broker 的请求超时 `0` 表示不设置请求总时限,不等于零毫秒连接等待。
连接仍有独立的默认 5 秒超时;只有显式正数请求时限才与连接时限取较小值,
用户取消仍关闭连接并中止 dispatch。
- Renderer 把相邻 text/reasoning delta 合并为消息 block 时始终替换最后一个 block,
不修改既有 React state 对象。这样开发环境 StrictMode 重复调用 state updater 时,
block metadata 与 canonical 消息正文保持一致。
## Agent 开发期间的真实 Host 验证
2026-09-10 Desktop `0.12.11` / Agent `0.11.23` 候选复测:共享 Linux x64 Host 的当前
源码通过工作区协议重命名保护、字面路径/重命名/合并提交差异、Unix 桥接 256 KiB 往返及取消,
以及继承 Runtime 指标的数据库重开与去重。隔离 Agent 报告版本 `0.11.23`,桌面 managed ACP
经真实 SSH、Agent 和既有已安装 OpenCode 各完成一次 Ask、Execute;共 2 次真实模型请求,
全部 HTTP 200、completed 且已交付,指标持久化后未固定继承选择。首次连接准备超时,未发送
模型请求;重试成功后清理测试进程与目录,并按本轮 bundle 摘要确认、清理一次遗留上传目录。
本次为当前源码复测,不是正式 `0.11.23` 复合包安装验收;三个原生包由发布 CI 构建和验证。
本地发布回归为 3845 项通过、66 项跳过,发布说明校验、typecheck、lint 通过;发布准备未执行
本地桌面生产构建或打包。
本轮 `020a9ad` 工作树修复使用当前源码在共享 Linux x64 Host 验证:默认 Unix broker
完成 256 KiB 响应并传递取消;生产 AssistantDatabase 在临时库中写入继承项目的指标,
重开后去重重放,未固定对话选择。当前 Workspace 协议的目标存在/同名/硬链接重命名
保护、字面路径 diff、重命名 diff 和 merge commit diff 均通过,Git 使用 Host 已配置身份。
当前 Desktop `createManagedRemoteAcpRuntime` 经真实 SSH attach、隔离 Agent、OpenCode
和 Agent gateway 完成 Ask 与 Execute,均得到两字符回答和 done。Execute 的真实
model-usage 转换为指标后通过生产数据库持久化与去重,继承设置保持未固定。
共 3 次真实 Provider 调用(2 次 Ask、1 次 Execute),均 HTTP 200、completed 且已交付;
前两次运行在模型完成后被测试 harness 的 usage 元数据/事件名断言拦截,并非产品失败。
未修改产品来适配 harness:构建时直接解析 ACP SDK 的 ESM 入口,凭据只通过既有
safeStorage 与模型控制协议使用。各次隔离 Agent 停止后剩余所属进程数为 0,测试目录和
上传文件均已回收,原 Host registry 与已安装组件未改变。此次不覆盖 UI 点击或安装升级。
2026-09-10 已发布模型桥兼容修复:从 `agent-v0.11.22` 读取的源码与当前源码分别在共享
Linux x64 Host 启动隔离 Agent daemon。当前 Main 的 `createManagedRemoteAcpRuntime`
经真实 SSH attach、Agent protocol、OpenCode 和 Agent gateway,在两版 Agent 上各完成
一次 Ask、一次 Execute,均返回 `OK`;模型账本共 4 次调用,全部 completed 且已交付。
旧 Agent 未声明 optional-limits capability,当前 Agent 声明该能力。使用既有加密设置和
固定 Host identity,凭据仅经协议进入内存;测试 daemon、Runtime 和运行目录已回收。
本次覆盖托管 Runtime 入口,未执行 UI 点击、安装升级或长时间 Provider 等待。
6 个聚焦测试文件共 77 项通过;另用真实 tag codec 验证四种消息双向互通。typecheck、lint
通过;全量 `npm test` 为 3,739 项通过、4 项失败、64 项跳过,失败涉及发布/便携包夹具、
DSH MCP 暴露和 Runtime 发现,未扩展修复范围。
2026-09-10 模型桥限制修复:共享 Linux x64 Host 使用当前源码构建的 gateway、Unix
broker 和 loopback helper,通过 2 MiB 请求/响应往返,并确认客户端取消传到 broker。
使用 GoodBuddy 加密设置中的模型凭据、既有固定 SSH identity 和 OpenCode `1.18.29`
运行 Ask、Execute,两个模式均收到 HTTP 200 并返回 `OK`。共发出 3 次真实 Provider
请求:Ask 成功,首次 Execute 的最终结果因本地命令采集超时未保留,单独复测 Execute
成功。运行中测试凭据只经 SSH stdin 进入内存;测试进程和临时目录由测试运行回收。
本次实机检查覆盖当前源码的模型桥与 Runtime helper;未重装共享 Agent,也未重跑完整
Desktop attach/bootstrap。Prompt 长时运行和精确 deadline 使用假时钟回归验证。
最终聚焦回归为 14 个文件、193 项通过、5 项平台跳过;typecheck、lint 和开发 build
通过。此前全量 `npm test` 为 3,703 项通过、22 项失败、63 项跳过,失败分布在 Runtime
发现、发布/便携包夹具、DSH ACP、模型设置和内置工具分类;该次全量结果来自并行修改中的
工作区,未据此修改其他任务的文件,也未把聚焦通过等同于全量通过。
2026-09-09 已在隔离 Linux x64 Host 验证当前源码的快照配置修复:桌面
`ManagedRemoteExecutionServices` 经 managed ACP、真实 detached Agent 和模型桥启动
OpenCode `1.18.29`,Ask 完成原生读取,Execute 完成 4,000 行文件的 CRLF → LF 转换。
+1 -1
View File
@@ -5,7 +5,7 @@
## 权威文档
1. [Task 与 Job 统一领域模型](./task-and-job-model.md):术语、身份和对象关系。
2. [Task Center PRD](./task-center-prd.md):Task 的应用级索引。
2. [Task Center PRD](./task-center-prd.md):Task 的单实例项目跟随索引与范围切换。
3. [Scheduled Task PRD](./scheduled-task-prd.md):时间或事件触发的 Task。
4. [Goal Task PRD](./goal-task-prd.md):围绕可验证结果有界推进的 Task。
5. [Job 与 Subjob PRD](./job-and-subjob-prd.md):Task 内部串行、并行和委派执行。
@@ -5,8 +5,8 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 首期稳定 Task 生命周期、创建体验与 Conversation 输入仲裁已实现;高级触发和执行治理待实施 |
| 版本 | 0.7 |
| 日期 | 2026-08-19 |
| 版本 | 0.8 |
| 日期 | 2026-09-10 |
| 依赖 | [Task 与 Job 统一领域模型](./task-and-job-model.md) |
| 相关架构 | [自动化平台总体设计](../../architecture/automation-platform-architecture.md) |
@@ -15,6 +15,10 @@
Scheduled Task 是带时间或事件触发器的 Task。每个 Scheduled Task 关联一条 Conversation;
一条 Conversation 可以同时承载多个 Task。
当前时间触发的含义是向关联的普通 Conversation 发送一条任务要求。执行时读取该会话的
最新历史、Runtime、工作模式和知识范围,工具配置使用普通发送的同一规则。任务不保存
创建时的上下文或权限快照。新建会话按普通会话规则初始化,不复制来源会话的历史和配置。
创建 Scheduled Task 时:
1. 用户选择关联当前 Conversation 或创建新 Conversation。
@@ -34,20 +38,20 @@ GoodBuddy 当前已实现首期统一生命周期:
Conversation 的标题和既有消息。
- 每个 Schedule 绑定一个稳定产品级 Task 和 Conversation;重复触发复用同一身份,不再
为每次触发创建新的顶层 Task。
- 默认选择 Execute,并允许用户主动切换 Ask;不支持工具执行时明确禁用 Execute。
- 单次、每日和每周计划支持暂停、恢复、立即运行、应用重启恢复和最多 4 个独立计划并发。
- 创建表单只设置任务要求、关联会话和时间;工作模式在关联会话中设置,未设置时使用项目默认值。
- 单次、每日和每周计划支持暂停、恢复、立即运行和应用重启恢复。
- 到期和手动运行先进入关联 Conversation 的持久输入队列,与回复期间继续发送的普通消息
顺序仲裁;默认不打断当前回复,也不与其并发写入时间线。
- Composer 上沿显示待发送项和来源。用户可以删除尚未执行的 occurrence,或选择“立即
中断并插入”取消当前执行并将该项提升为下一项。
- 文本结果和失败写回关联 Conversation 并带 Task 来源;独立文件和图片继续保存为 Artifact。
- 任务要求成为普通用户消息,回复、工具活动、提问和失败通过普通聊天流程呈现;独立文件和图片继续保存为 Artifact。
- 左侧 Conversation 列表、Conversation Task 区和 Task Center 使用同一产品 Task;普通
模型请求、Subagent、委派和 Smart Heartbeat 内部 Task 不进入产品索引。
- v22 迁移保留 Schedule 配置和历史运行,并为旧计划补齐稳定 Task 与 Conversation;v23
增加可恢复的统一 Conversation 输入队列。
尚未实现的高级能力包括 IANA 时区与 DST 墙上时间、每月/工作日/受限 Cron、事件触发、
可配置错过执行策略、租约、重试与结果未知治理、完整预算和权限快照,以及面向内部
可配置错过执行策略、租约、重试与结果未知治理、完整预算,以及面向内部
Job/Subjob/Run 的统一持久化抽象。当前每日和每周按既有 UTC 间隔递推。
## 3. 目标
@@ -55,8 +59,8 @@ Job/Subjob/Run 的统一持久化抽象。当前每日和每周按既有 UTC 间
- 支持单次、每日、每周、每月、工作日和受限 Cron。
- 支持 Task 完成、失败、Conversation 完成等内部事件触发。
- 创建时明确选择当前或新 Conversation。
- 默认使用 Execute,并允许用户主动切换到 Ask。
- 冻结 Project、Runtime、工作目录、工具、知识、记忆和审批范围。
- 使用关联会话当前的工作模式;新会话使用普通项目默认值。
- 在实际发送时解析上下文和执行配置,与手动发送共用执行流程。
- 提供时区、错过执行、幂等、租约、重试、恢复、取消、预算和审计。
- 让所有重复触发复用同一 Task 和 Conversation 关联。
- 为一次触发建立清晰 Job/Run,而不是创建新的顶层 Task。
@@ -93,18 +97,15 @@ Task Center 和 Conversation 操作都可以提供“新建定制任务”,但
○ 新建会话
为任务创建一条新会话,默认标题为任务名称
执行模式
[ Execute ] [ Ask ]
运行频率
[ 单次 ] [ 每日 ] [ 每周 ] [ 每月 ] [ 工作日 ] [ Cron ]
首次运行 [ 2026-08-21 ] [ 17:00 ]
时区 [ Asia/Shanghai ▾ ]
执行范围
GoodBuddy Desktop · OpenCode · 项目工作目录
8 个工具可用 · 高风险操作需要审批 [编辑]
关联项目
GoodBuddy Desktop · 项目工作目录
运行时使用关联会话的当前上下文和配置
[取消] [创建任务]
```
@@ -126,7 +127,7 @@ GoodBuddy Desktop · OpenCode · 项目工作目录
```text
✓ 为当前 Conversation 新增一个 Task
✓ 在左侧会话列表显示“任务 3”
✓ 默认以 Execute 模式运行
✓ 使用关联会话运行时的工作模式
✓ 每周五 17:00 自动执行此 Task
✓ 文本结果写入当前 Conversation
✓ 独立交付物保存到成果
@@ -145,37 +146,30 @@ GoodBuddy Desktop · OpenCode · 项目工作目录
## 6. 工作模式、Runtime 与工具
### 6.1 默认 Execute
### 6.1 会话工作模式
创建 Modal 默认选择 Execute:
创建 Modal 不单独设置工作模式。关联已有会话时,使用该会话实际发送时的工作模式;
新会话未单独设置时使用项目默认模式。Ask 在 Runtime 边界保持只读,Execute 使用普通
会话的工具授权规则。
- Execute 可以调用当前 Runtime 与 Project 已启用、且被 Task 快照允许的工具。
- Ask 保持 Runtime 边界只读,只能调用允许的只读能力。
- 所选 Runtime 不支持工具执行时,不能静默降级为 Ask;用户必须更换 Runtime 或主动选择
Ask。
- Modal 持续显示实际 Runtime、Project、工作目录和权限摘要。
### 6.2 执行时配置
### 6.2 权限快照
计划只保存任务要求、时间和稳定会话关联。Runtime、工作目录、Skills、MCP、知识范围、
记忆和工具配置由普通发送流程在运行时解析,不另存任务上下文快照。已有会话的后续消息
参与下一次执行;新会话不继承来源会话历史。重复触发始终发送到同一关联会话。
Task 创建时冻结:
已保存计划中的旧工作模式和 Runtime 字段不再控制执行;用户在关联会话或项目中调整配置。
- Project 和工作目录。
- Runtime 与模型选择。
- 工作模式。
- Skills、MCP、知识库、记忆和上下文范围。
- 可用工具与审批策略。
- 预算、并发和输出限制。
后续设置变化不修改已启动 Run。编辑 Task 配置只影响后续 Job。
会话任务详情中的模式与执行来源保持一致:从未运行的计划显示关联会话当前有效模式;
已开始或已结束的 Task 显示该次执行记录的实际模式,不被计划旧字段或会话后续切换覆盖。
缺少实际模式记录时明确显示不可用,不从旧计划字段推断。
### 6.3 审批
- Execute 继续遵守当前 Runtime、GoodBuddy 原生能力和工具审批控制。
- 已启用且按现有策略允许自动执行的工具可以在后台运行。
- 需要额外确认的动作进入 `waiting_approval`,暂停所属 Job 并发送应用内及桌面通知。
- 用户批准后继续同一个 Job/Run;拒绝后按协议失败、跳过或请求调整。
- 定时触发不能把高风险、越界或未授权动作转换成自动批准。
- 结果未知的外部副作用进入 `outcome_unknown`,不得自动重试。
- 使用普通会话的授权和提问流程。Agent Runtime 的 Execute 工具调用无需通用 GoodBuddy 审批,工具活动仍显示在会话中。
- GoodBuddy 配置修改等原生确认保持各自的确认流程。需要用户回答的问题显示在关联会话中,不因定时触发直接判为失败。
- Ask 在 Runtime 边界保持只读;计划不增加另一层工具限制。
- `outcome_unknown` 等高级执行治理尚未实现,见第 2 节的范围说明。
## 7. 触发器
@@ -225,7 +219,10 @@ type TimeTrigger =
### 7.3 手动触发
“立即运行”在当前 Task 内创建独立 Job 和 Run,不改变下一次计划时间,不创建新 Task。
重复点击使用调用级幂等键去重。
提交期间,以及该计划已有排队或运行中的 occurrence 时,“立即运行”不可重复点击。
后台同样拒绝为已有活动 occurrence 的计划再次入队。
任务状态随队列变化刷新:待执行显示 queued,认领执行后显示 running;结束或取消排队后
重新读取当前状态。没有活动 occurrence 的周期计划显示 idle 或 paused。
### 7.4 与普通消息的顺序
@@ -258,16 +255,16 @@ Conversation
```text
▾ 产品发布讨论 10:24
每周进度总结 每周 · Execute · 等待中 ●
发布前检查 单次 · Execute ✓
每周进度总结 每周 · 等待中 ●
发布前检查 单次 ✓
```
- 父会话行不重复显示任务标签或数量;Task 子项名称在左,模式、计划和状态标记收在右侧,
- 父会话行不重复显示任务标签或数量;Task 子项名称在左,计划和状态标记收在右侧,
与父会话的更新时间位置一致。
- 点击 Conversation 标题打开聊天;点击 Task 子项打开同一 Conversation 并定位到该 Task。
- 新建 Task 成功后首次自动展开。用户手动折叠后持久化其选择,后台运行不强制展开。
- 默认最多直接显示 3 个 Task;“查看全部 N 个任务”打开该 Conversation 的完整 Task 区。
- Task 子项显示本地化的模式、计划和非完成态状态文字;状态标记固定在行尾。已完成使用
- Task 子项显示本地化的计划和非完成态状态文字;状态标记固定在行尾。已完成使用
带勾成功点,并将完成文案保留给辅助技术,避免在紧凑元数据中重复显示且不只靠颜色表达。
- 左侧只展开 Task;当前产品 UI 的其他区域也不提供 Job/Run 树或独立导航。
@@ -282,19 +279,19 @@ Conversation
选中 Task 后显示:
- 名称、状态和模式。
- 名称和状态。
- 计划、下次执行和未来预览。
- 最近一次执行结果。
- “立即运行”“暂停”“编辑计划”等操作。
- 需要审批时的明确恢复入口。
每条自动结果消息显示 Task 名称、触发来源和时间。普通文本作为消息保存;文件、图片、PDF 和
其他独立交付物保存为 Artifact,并从消息引用。多个 Task 并发时,最终文本以完整消息写入,
不能把流式 Token 无序混入同一消息时间线。
待发送项显示计划来源。发送后使用普通用户消息和流式助手消息;文件、图片、PDF 和其他
独立交付物保存为 Artifact,并从消息引用。同一会话按队列串行执行,避免流式输出混入
另一条回复。
## 11. Task Center
Task Center 显示 Scheduled Task 的范围、关联 Conversation、状态、模式、最近进展、需要
Task Center 显示 Scheduled Task 的范围、关联 Conversation、状态、最近进展、需要
关注和下次触发时间:
- 点击条目打开关联 Conversation,并定位到该 Task。
@@ -352,9 +349,9 @@ Task Center 显示 Scheduled Task 的范围、关联 Conversation、状态、模
- [x] 创建 Scheduled Task 可以选择当前或新 Conversation。
- [x] 关联当前 Conversation 不修改其标题、类型或既有消息。
- [x] 一条 Conversation 可以在左侧展开一个或多个 Task。
- [x] 默认工作模式为 Execute,且用户可以主动选择 Ask。
- [ ] Execute 能调用快照允许的工具,但不能绕过 Runtime 和审批控制。
- [x] 不支持工具的 Runtime 不会让 Execute 静默降级。
- [x] 工作模式使用关联会话当前设置,未设置时使用项目默认值。
- [x] 使用普通发送的 Runtime 和工具执行流程,不保存任务上下文快照。
- [x] 新会话不复制来源会话历史;重复执行读取关联会话最新消息。
- [x] 重复触发始终复用同一 Task 和 Conversation 关联。
- [x] 每次触发创建内部运行记录,不创建新的顶层 Task。
- [x] 内部运行记录只用于执行和审计,不在 UI 中显示为独立层级。
@@ -84,6 +84,14 @@ Subagent 是执行 Job 或 Subjob 的受限执行者,不是对象层级:
- Subagent 不自动拥有独立 Task 或 Conversation。
- Subagent 输出先回到所属 Job,再由 Task 协调器写入关联 Conversation。
### 1.7 Runtime 执行清单
Runtime 执行清单是一次请求中由 OC `todowrite` 或 CN `Checklist` 更新的只读工作项列表,
属于请求及其助手消息,不创建 Task、Job、Run 或 Schedule,也不驱动定时任务状态。
条目全部完成不表示关联 Task 目标达成,请求取消也不自动完成条目。
该功能尚未实现,需求、顶部展示与本地/远程接入由
[助手工作栏文档](../assistant-workbar/README.md)维护。
## 2. 对象关系
```text
+32 -10
View File
@@ -4,20 +4,24 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | Scheduled Task 首期已实现;Goal/Event Task 与完整操作待实施 |
| 版本 | 0.3 |
| 日期 | 2026-08-19 |
| 状态 | Scheduled Task 与项目范围跟随已实现;Goal/Event Task 与完整操作待实施 |
| 版本 | 0.4 |
| 日期 | 2026-09-10 |
| 依赖 | [Task 与 Job 统一领域模型](./task-and-job-model.md) |
| 界面归属 | [通用助手工作栏与执行空间](../assistant-workbar/prd.md) |
## 1. 产品定义
Task Center 是所有产品级 Task 的应用级单例索引,不是 Automation Center,也不复制
Conversation 内容。点击条目打开其关联 Conversation,并定位或展开对应 Task。
Task Center 是工作栏中的单实例 Task 索引,默认跟随当前项目。它不是 Automation Center,
也不复制 Conversation 内容。点击条目打开其关联 Conversation,并定位或展开对应 Task。
每个 Task 只关联一条 Conversation;一条 Conversation 可以关联零个、一个或多个 Task。
Conversation 不因为关联 Task 而改变对象类型。
“单实例”和“项目范围”是两个独立属性:应用内只有一个 Task Center Tab,但该 Tab 默认展示
当前项目的 Task。用户可以在同一 Tab 内切换到“全局任务”或“全部项目”,不能为每个项目创建
独立 Task Center 实例。
## 2. 收录边界
收录:
@@ -53,7 +57,19 @@ Job/Subjob/Run 树、独立详情或路由。
Task 行只显示聚合后的用户状态,不要求用户理解内部 Job/Run。
## 4. 交互
## 4. 范围
范围控件提供:
- `当前项目`:默认值,跟随活动项目变化,只显示 `projectId` 匹配的 Task。
- `全局任务`:只显示没有 `projectId` 的 Task。
- `全部项目`:显示当前用户可见的项目 Task 和全局 Task,并保留每行的项目或 Global 标识。
没有活动项目时,“当前项目”显示明确空状态,并允许切换到“全局任务”或“全部项目”;系统不
自动改变用户已经选择的范围。范围模式作为工作栏公开偏好持久化,但“当前项目”模式不保存
具体项目 ID;重启后继续跟随届时的活动项目。
## 5. 交互
- 点击条目打开关联 Conversation,并定位到该 Task。
- 支持按需要关注、进行中、已暂停、已结束筛选。
@@ -63,7 +79,7 @@ Task 行只显示聚合后的用户状态,不要求用户理解内部 Job/Run
- 完整消息留在 Conversation;工具、审批和错误可以在活动或 Runtime 中按 Task 查看;
独立交付物在成果中查看。
## 5. 左侧 Conversation Task 列表
## 6. 左侧 Conversation Task 列表
左侧最近会话列表承担轻量 Task 发现,不替代 Task Center:
@@ -88,7 +104,7 @@ Task 行只显示聚合后的用户状态,不要求用户理解内部 Job/Run
- 后台状态变化不强制展开;Task 状态持续显示在展开后的子项和 Task Center 中。
- 默认最多显示 3 个 Task;“查看全部 N 个任务”打开该 Conversation 的完整任务区。
## 6. Conversation 任务区
## 7. Conversation 任务区
包含 Task 的 Conversation 顶部显示可折叠任务区:
@@ -100,14 +116,17 @@ Task 行只显示聚合后的用户状态,不要求用户理解内部 Job/Run
选择 Task 后显示名称、模式、聚合状态、计划、下次执行、最近结果和 Task 级操作。工具、审批、
错误和成果通过 Task 关联显示,但不暴露 Job/Run 层级。
## 7. 删除关系
模式来源遵循[Scheduled Task 的执行时配置](./scheduled-task-prd.md#62-执行时配置):
未运行计划跟随会话当前有效模式,已执行 Task 保留实际模式;计划旧值不得覆盖展示。
## 8. 删除关系
- 删除 Schedule 只停止后续触发,不删除 Task、Conversation 或历史。
- 删除 Task 停止其计划并移除关联,默认保留 Conversation 和既有消息。
- 删除最后一个 Task 后,左侧 Conversation 的展开按钮消失。
- 删除 Conversation 前必须显示关联 Task 数量,并先停止或结算活动执行。
## 8. 验收标准
## 9. 验收标准
- [x] Task Center 只展示产品级 Task。
- [x] 一条 Conversation 可以关联并展开多个 Task。
@@ -118,3 +137,6 @@ Task 行只显示聚合后的用户状态,不要求用户理解内部 Job/Run
- [x] 普通模型请求和工具调用不会误显示为 Task。
- [ ] 删除 Task 默认保留 Conversation 和既有消息。
- [x] Smart Heartbeat 不进入 Task Center。
- [x] Task Center 默认只显示当前项目的 Task,切换项目后在同一 Tab 更新列表。
- [x] “全局任务”只显示无项目 Task,“全部项目”显示所有可见 Task 并标注所属范围。
- [x] 范围切换不创建第二个 Task Center Tab;任务中心保持默认存在且不可关闭。
+13
View File
@@ -0,0 +1,13 @@
# 质量验证
本目录收录跨功能测试方法。功能验收结果仍记录在对应功能的 `progress.md`,不在此复制通过记录。
| 文档 | 职责 |
| --- | --- |
| [Electron UI 自动化测试规范](./electron-ui-automation.md) | 测试分层、当前源码启动、视觉与原生交互取证、真实调用计数、脱敏报告和清理 |
| [复发缺陷与防复发规则](./recurring-defects.md) | 已重复出现的缺陷类型、对应的共享机制和自动发现测试 |
| [开发与构建](../../BUILD.md#质量验证) | 依赖、测试、类型检查、Lint 和构建命令 |
| [UI 设计系统](../../UI-DESIGN.md#155-验收) | 全局视觉、交互和响应式验收要求 |
| [知识检索评估](../features/knowledge-base/retrieval-evaluation.md) | 检索数据集、指标及评估方法 |
| [真实 Runtime 测试](../../BUILD.md#质量验证) | 真实模型 Runtime E2E 的现有入口及范围 |
| [发布操作](../development/release-runbook.md) | 候选 CI、发布构建及发布验证;开发期 UI 测试不替代发布检查 |
+201
View File
@@ -0,0 +1,201 @@
# Electron UI 自动化测试规范
本文规定开发期 Electron UI 自动化的执行和取证方法,适用于表单、弹窗、菜单、主题、响应式布局及其业务路径。视觉与交互预期以 [UI 设计系统](../../UI-DESIGN.md)和功能设计为准;通用命令见[开发与构建](../../BUILD.md#质量验证),功能实测结果写入各功能的进度文档。
## 1. 测试分层与选择
先写清本次改动影响什么、在哪一层验证,以及该层替换了哪些依赖。按影响选择场景,fixture 和真实服务测试提供互补证据。
| 层次 | 运行方式 | 能证明的范围 | 不能据此宣称 |
| --- | --- | --- | --- |
| 单元与组件回归 | Vitest、Testing Library、契约及 CSS 回归 | 状态分支、语义和回归断言 | Electron 实际排版、原生键盘默认行为或生产端到端成功 |
| Electron 视觉 fixture | 当前组件、入口样式、i18n、主题函数;真实 Chromium 与 body Portal;IPC 返回受控假数据 | 排版、主题、控件外观、焦点、原生交互,以及受控空状态和错误状态 | 持久化、真实检索、模型生成或完整 App 接线成功 |
| 生产 IPC 集成 | 当前 Main 服务、注册 IPC、真实 Preload、隔离数据;按需接真实服务 | 跨进程契约、生产存储、检索及生成路径 | 用隔离验证 DOM 展示事件时,不能称为完整 App 输入区或引用 UI 验收 |
| 完整 App | 当前生产 Main、Preload、Renderer,隔离 profile,从真实 UI 入口完成操作 | 用户动作到生产实现及最终可见结果 | 未执行的平台、Provider、模式、重启或远程路径 |
纯视觉修复通常先做组件回归和 Electron 视觉矩阵,在完整 App 中复核受影响入口及 Portal 接线;无需为每种主题、尺寸或 CSS 修改重复调用模型。保存、持久化、检索、生成、引用或 IPC 接线变化,补对应生产路径。真实生成声明必须来自真实生成请求;健康检查、模型列表、HTTP 200 或 fixture 答案均不足以证明。
沿用 [AGENTS.md 的验证授权](../../AGENTS.md#validation):验证变更路径所必需的有界真实文本模型调用已有持续授权,不逐次询问。只运行必要的最小请求,尽量关闭无关工具与附件;按需工具检索场景保留所需工具。本文不新增产品策略或审批流程。
## 2. 方法来源与证据边界
2026-09-13 的外部知识库复验是本规范的方法来源。永久记录由[知识库进度](../features/knowledge-base/progress.md)及[脱敏复验摘要](../features/knowledge-base/external-knowledge-follow-up-2026-09-13.json)维护。下表用于说明测试方法,不表示本文编写时重新执行了测试。
| 原始临时证据 | 已观察事实 | 保留的限制 |
| --- | --- | --- |
| `external-visual-0913/results.json`、`preview.tsx`、`electron.cjs`、`run.mjs` 及 PNG | 当前源码组件和 `styles.css`、真实 body Portal、fixture IPC;浅深主题,1280×800、960×720、720×640、640×420;56 张场景截图、138 项检查,记录中无失败断言和 errors;计算样式、溢出、焦点与底部操作区配合 PNG 审阅 | 额外拼图和残留 `failure.png` 不计入 56 张;各尺寸场景数不同,未覆盖的组合不能按笛卡尔积推定通过;未验证真实服务 |
| `kb-real-generation/report.json` | 真实 Electron、当前 Preload、已注册生产 IPC 和 ModelAgentRuntime;三个 Provider 的 `always`、Dify `auto`;文本和引用由生产存储保存 | Renderer 是隔离验证 DOM;`auto` 经进程内 `ModelToolProvider → KnowledgeMcpGateway`,不是 HTTP MCP 会话;首轮两家比较器误判保留,解码请求和内嵌证据 JSON 后重跑通过 |
| `kb-full-app-live/report.json` | 当前完整生产 App,隔离 safeStorage profile;UI 创建实例与非空绑定,在实际输入区选择 Dify 并点击发送,真实短答案生成、保存并点击引用;片段及文档/分块匹配 | 仅 Dify、Ask、`always`;测试密钥在 Main 保存 IPC 边界替入;无伪造消息、引用或 Agent 事件;不证明广泛问答质量或其他 Provider 的完整 App |
早期验收曾仅凭“没有横向溢出”漏掉 Portal 移到工作区外后字段样式失效的问题。修正后复验同时检查 `.field` 共享样式、字体、背景、密码显隐布局和实际 PNG。以后不得把 `scrollWidth <= clientWidth` 当作视觉通过的充分条件。
上述生产 IPC 报告实际发出模型请求 7 次、KB 请求 12 次,其中检索 9 次、目录 3 次;包含比较器修正后的两次生成重跑,Runtime 模型重试为 0。完整 App 另发出模型请求 1 次、KB 请求 5 次,其中检索 3 次、目录和详情各 1 次。本轮精确增量为模型 8 次、KB 17 次;不采用原报告中覆盖不完整的历史会话累计区间。后续报告应独立计数,不沿用这些数字作为预期或配额。
原始文件位于当时工作机批准的临时目录。当前可读取视觉脚本;完整 App 的隔离 profile 和构建已删除,其目录只保留报告和截图。它们不是仓库承诺维护的 runner,也不是新环境的依赖。没有原始文件时仍可按下节重建临时驱动,但必须记录“驱动重建”,不能声称复用了原脚本或复现了全部历史证据。
## 3. 当前源码与隔离启动
### 环境与命令
需要 [BUILD.md](../../BUILD.md#环境要求)要求的 Node.js、锁定 npm 依赖、能显示窗口的 Electron 桌面会话,以及本次专用临时目录。记录操作系统、架构、Electron 版本、语言、主题、缩放和内容区尺寸。真实凭据场景还需要当前 OS 用户可用的 `safeStorage` 及已授权配置。
在仓库根目录逐条执行以下准备命令;依赖已经满足时不重复安装:
```powershell
node --version
npm ci
git rev-parse HEAD
git status --short
node -p "require('electron/package.json').version"
```
用 HEAD 和相关未提交变更摘要标识本次源码。读取当前 [Electron Vite 配置](../../electron.vite.config.ts)和 [Main 入口](../../src/main/index.ts),确认 Renderer、Preload、动态加载资源及用户数据路径。
开发期需要完整生产产物时执行 `npm run build`,确认成功后再用隔离启动器加载 `out/main/index.js`,并记录 Main、Preload、Renderer 入口及相关资源的构建标识。不要把已安装的旧版或陈旧 `out` 当作当前源码。并行任务会修改 `out` 时,使用本次专属源码/构建副本,或在测试前后核对产物哈希;不要覆盖别人的构建。[发布操作](../development/release-runbook.md)另有候选 CI 构建规则,本节不改变该流程。
### 临时驱动的复现步骤
仓库已有 [Electron 启动脚本](../../build/run-browser-tabs-electron-e2e.cjs)及[对应测试](../../tests/browser-tabs-electron-e2e.ts),可复用其 `esbuild` 临时 bundle、`external: ['electron']`、解析仓库 Electron 可执行文件、清除 `ELECTRON_RUN_AS_NODE` 和退出码处理方式。`node build/run-browser-tabs-electron-e2e.cjs` 是现有浏览器服务测试命令,不是通用 UI 矩阵命令,也不覆盖完整 App。
1. 在本次临时目录准备场景入口、Electron bootstrap 和独立 Node supervisor。依赖从当前仓库解析;临时目录外脚本可用 `createRequire` 指向仓库 `package.json`,不要假定临时目录能自动解析 `react` 或 `electron`。
2. 视觉层用 Vite `createServer` 服务临时入口,引用当前组件、`styles.css`、i18n 和 `applyAppearanceTheme`;React 依赖指向同一仓库实例,服务仅监听 loopback 随机端口,文件允许范围限于本次目录及仓库。保留真实 Portal 容器和位置,仅替换约定的 IPC 返回值;不要另画一份“相似 UI”。
3. Electron bootstrap 在 `app.whenReady()` 前设置本次 `userData`、`sessionData` 和日志目录。视觉层创建 `BrowserWindow`,采用 `useContentSize: true`,通过 `setContentSize` 切换矩阵;完整 App 则在设置隔离路径后动态导入当前生产 Main,由生产代码创建窗口、注册 IPC 和加载实际 Preload/Renderer。监听 `browser-window-created` 找到主窗口,确认最终数据路径仍在测试目录,避免便携路径规则覆盖隔离设置。
4. supervisor 用 Node `spawn` 启动 Electron 子进程,在子进程环境副本中删除 `ELECTRON_RUN_AS_NODE`。否则 Electron 可能作为 Node 执行,窗口和 `app` API 不可用。完整 App 检查实际加载的入口;不要绕过主程序仅挂载 `App` 后称为完整生产路径。
5. 等待页面就绪、目标可见、`document.fonts.ready` 和所需异步状态;所有等待都有阶段期限。通过 `webContents.executeJavaScript` 读取 DOM、测量布局和定位元素,通过 Electron 输入 API 驱动交互,通过 `capturePage().toPNG()` 保存截图。记录程序化定位焦点的步骤,完整 Tab 导航另行验证。脚本按当前控件的可访问角色操作,例如实例选择器改为菜单后,应同步更新旧版 `select` 驱动。
6. 场景完成即写检查和证据索引;失败也写阶段、退出码/信号、脱敏诊断与截图。结束后由 supervisor 核对结果完整性并清理本次子进程、服务和 profile。
生产窗口保持 `contextIsolation: true`、`sandbox: true`、`nodeIntegration: false` 及既有 CSP、可信发送方校验和显式 Preload API。不要复制其他专用 smoke 中的 `--no-sandbox`,也不要为驱动增加 `eval`、修改生产安全标志或向 Renderer 暴露 Node。驱动本身无法启动应记录为 harness 故障。
原视觉目录仍存在时,可先在 PowerShell 校验文件可用性。以下只检查可用性,不启动测试:
```powershell
$visualRun = Join-Path $env:TEMP 'opencode\external-visual-0913'
Test-Path -LiteralPath $visualRun
'run.mjs', 'electron.cjs', 'preview.tsx', 'index.html', 'results.json' | ForEach-Object {
[pscustomobject]@{ File = $_; Exists = Test-Path -LiteralPath (Join-Path $visualRun $_) }
}
```
原脚本包含工作机绝对路径和固定输出文件名。先读脚本、复制到新的临时运行目录并适配源码路径、输出目录、依赖解析及当前选择器,避免覆盖历史失败诊断;随后才可 `node "<本次目录>/run.mjs"`。这是需要适配的命令模板,仓库没有 `npm run test:ui` 或一条命令重放上述全部报告的入口。
### 超时与进程监督
监督进程独立于 Electron 和测试执行进程,持有明确的子进程句柄、开始时间和总期限。一次工具调用的 60 秒等待窗口不等于整个测试允许时长;长任务后台执行并定期读取状态,工具等待结束后仍能取得进度和最终结果。
历史视觉脚本的 watchdog 为 115 秒,现有浏览器服务脚本为 120 秒,都只适用于各自范围。2026-09-13 的全量 `npm test -- --maxWorkers=1 --reporter=verbose` 实测 599,334 毫秒,约 10 分钟。新一轮应按机器和规模给出有余量的有限预算,例如 15 分钟起,并记录预算和实际耗时,不能照搬 60 秒总上限或无限等待。
区分单步等待、服务请求超时和整轮超时。定期保存进度,超时先保留最后阶段、输出尾部及可取得的截图,再终止本次进程树。普通 `child.kill()` 未必能回收孙进程;按 PID 和启动归属核实,只清理本次 Electron、Node、临时服务及监听,不按进程名批量结束正在使用的 GoodBuddy。信号退出、超时或报告缺失均不得记为通过。
## 4. 视觉与原生交互取证
受影响组件默认检查浅色和深色主题,以及 `1280×800`、`960×720`、`720×640`、`640×420` 内容区。它们对应设计系统的宽度区间及短窗口压力场景;产品另有断点、长文案或缩放要求时补充场景。记录实际 `innerWidth/innerHeight` 和设备缩放,不能用带窗口边框的尺寸替代。
每个场景同时保存动作结果、计算值和 PNG。检查页面、弹窗、菜单、字段及操作区的 `getBoundingClientRect()`,并对文档及局部滚动容器比较 `scrollWidth/clientWidth`、`scrollHeight/clientHeight`。正常纵向滚动不算失败,但裁切、无意横向溢出、按钮不可达或滚动内容被粘附区遮挡需要单独断言。
Portal 必须真的挂在生产位置。检查它是否在工作区外、主题变量是否仍可用、背景是否随主题切换;读取字段 `fontFamily`、`fontSize`、高度、边框、圆角、背景和文字色。共享 `.field`、字体令牌、PageTabs、SegmentedControl、Switch、Checkbox、EmptyState 和通知模式按 [UI 设计系统](../../UI-DESIGN.md)核对,不在本规范重新定义视觉数值。
密码显隐前后比较尺寸、字体、背景及相邻按钮位置;敏感输入只能用明确标识的 fixture 文本截图。选中态在 hover 时保持可辨,禁用项不会被键盘选中。底部保存区除了位于窗口内,还要验证聚焦的字段没有落到页头或粘附底部下面、末项可滚到并操作、焦点轮廓可见。
键盘采用 `webContents.sendInputEvent` 的 `keyDown/keyUp`,文本可用 `insertText`。窗口和 webContents 先获得焦点;必要时为 Enter 发送 `char`。按控件实际语义覆盖 Tab、Shift+Tab、Space、Enter、Escape、方向键、Home/End,并验证 `document.activeElement`、选中值和弹窗状态。`dispatchEvent(new KeyboardEvent(...))` 不触发同等 Chromium 默认行为,不能用来证明原生 Tab 顺序、Space 开关或 select 行为。
检查焦点进入弹窗、正反向循环、背景 `inert`、关闭后恢复触发按钮;菜单跳过禁用项、选择后回到触发器;页签的选中状态与面板一致。表单取消保留原值,错误关联正确,失败保留输入和可操作上下文;成功通知不重复显示。
PNG 必须实际打开审阅,并写明审阅方式、具体文件及发现。拼图用于找异常,小尺寸、焦点、字段和异常截图还要按原尺寸检查;确认字体、对比度、密度、层级、遮挡和控件是否像共享设计。截图文件存在或自动像素相同都不能替代审阅。只读了 JSON 时应写“PNG 未审阅”。
## 5. 保存、真实生成与故障
涉及保存时,从实际 UI 修改、提交、重新打开,核对生产读取值;涉及重启持久化时关闭本次 App,用同一隔离 profile 重启,再验证状态。fixture 返回保存成功只证明前端反馈;数据库有记录也不等于已经完成重启验证。空列表、无搜索结果、加载、失败、取消和重试按改动范围检查,明确故障是 fixture 注入、传输注入还是真实服务返回。
完整 App 的真实生成场景从实际输入区选择范围、提交最短必要问题,等待检索、生成及完成事件,核对持久化答案和引用;点击生成答案中的引用,检查弹窗片段、文档/分块定位与生产保存的引用匹配。需要证明依据进入模型时,在 Main 本地解码实际 Provider 请求和内嵌证据后比较,只归档布尔结果与计数。不要用原始序列化字符串匹配造成转义误判。
不得注入答案、引用、消息或 Agent 事件后宣称真实生成/引用验收通过。生产 IPC 测试可用最小验证 DOM 观察真实事件,但要如实标注 Renderer 范围。真实片段进入模型、短答案依据匹配和引用正确,只证明该次路径;不能据此推断复杂问答质量。
### 配置与传输
凭据只在 Main 通过既有存储读取和解密。需要复用默认模型时,将所需加密配置复制到隔离 profile,核对原配置未改变;safeStorage 依赖 OS 用户及加密元数据,解密失败应保留诊断,不改为明文或修改正式设置。历史完整 App 驱动仅复制必要的加密 `os_crypt` 元数据,并在 Main 保存 IPC 边界替入测试 KB 密钥,这一替换必须在方法说明中披露。
HTTPS 验证在隔离子进程显式使用严格证书校验。检查实际 fetch/undici dispatcher、证书忽略开关和监听器;仅设 `NODE_TLS_REJECT_UNAUTHORIZED=1` 不足以证明请求没有被其他配置绕过。历史完整 App 在隔离子进程设置 `rejectUnauthorized: true` 并移除测试环境的证书绕过,不修改生产源码或全局配置。
端点沿用已授权配置,不在验证中擅自升级或替换协议。历史 Dify/FastGPT 使用严格 HTTPS,RAGFlow 和默认模型使用已配置的 HTTP。报告应分别记录各服务的 `http`/`https` 及 HTTPS 校验情况;`strictTls: true` 不能写成“全部请求 HTTPS”。
### 调用计数与失败诊断
从应用实际 HTTP dispatch 边界计数,按运行、场景、阶段和 attempt 记录。区分模型请求、KB 目录、详情、绑定验证检索、聊天预检索、按需工具检索;另记输入区提交数、工具执行数、Runtime 自动重试及人工/驱动重跑。一次提交可产生多个模型请求,不能用提交数代替用量。
计数包含失败、取消前已发出的请求和重跑;启动前失败仍记 attempt,但网络数为 0。已发出但未收到响应的请求也计入,并标明结果未知。上游代理内部重试、服务商 Embedding/重排不可观察时直接写不可观察。旧轮次缺少日志时报告已知精确增量与缺口,不能把估算区间当精确总数。
失败记录保留场景、动作、预期/实际、阶段、HTTP 状态或脱敏错误码、退出信号、截图和重跑关联。区分产品故障、harness 错误、环境阻塞与超时。修正比较器或选择器后重新取证,保留首轮失败,不将其改写为通过或误称为产品修复。控制台错误、`page` 加载失败及进程崩溃也应纳入报告。
## 6. 可复用检查清单
未执行项注明 `not-run`、原因和影响;不适用项注明 `not-applicable`。重启、错误或平台场景也须有本次执行证据后再勾选。
- [ ] 列出变更范围、所选测试层、fixture/真实边界、当前源码及构建标识。
- [ ] 确认桌面会话、依赖、临时目录、隔离 profile、safeStorage 前提及原设置不变。
- [ ] 从独立 supervisor 启动当前 Electron,清除 run-as-node 环境,设置阶段与整轮期限。
- [ ] 保持生产 Main/Preload/Renderer 路径与安全标志,披露测试替换点。
- [ ] 检查浅深主题、四组内容区尺寸,以及实际长文案和空/加载/失败状态。
- [ ] 真实 Portal 的共享字体、字段、主题和控件计算值正确;密码显隐无布局跳变。
- [ ] 检查文档和局部溢出、滚动、焦点轮廓、底部按钮及字段无遮挡。
- [ ] 原生键盘完成适用流程,菜单、页签、开关、焦点循环与恢复都有动作后断言。
- [ ] 打开 PNG 审阅,记录文件、方式、缺陷及修正后的复验。
- [ ] 按影响验证真实保存、重新打开、重启、取消、错误恢复;fixture 不冒充持久化。
- [ ] 需要真实生成时,从所选层的入口取得真实答案/引用证据,完整 App 场景实际点击引用。
- [ ] 精确统计模型与 KB dispatch、失败和重跑,协议与 TLS 分开记录。
- [ ] 源码变更完成仓库要求的 `npm test`、`npm run typecheck`、`npm run lint`,保留完整结束状态;仅修改文档时做文档检查即可。
- [ ] 报告覆盖范围、未测项和失败诊断,核对测试前后源码/产物是否被并行修改。
- [ ] 脱敏后归档摘要,清理本次进程、监听、配置和临时敏感资料,记录清理结果。
## 7. 脱敏报告格式
下例是未执行模板,所有测量值为 `null`,没有伪造通过证据。`null` 表示未知或未测,只有确认没有调用时才填 `0`。状态使用 `not-run`、`passed`、`failed`、`blocked`、`not-applicable`;场景及断言逐项记录,不能只有总数。
```json
{
"schemaVersion": 1,
"kind": "template-not-executed",
"runId": null,
"source": { "commit": null, "dirtyScope": [], "artifactHashes": {} },
"environment": { "os": null, "arch": null, "electron": null, "locale": null, "scale": null },
"scope": { "layers": [], "fixtureIpc": null, "fullProductionApp": null, "substitutions": [], "limitations": [] },
"execution": { "command": null, "status": "not-run", "timeoutMs": null, "elapsedMs": null, "exitCode": null, "signal": null },
"scenarios": [
{
"id": "replace-with-scenario-id",
"layer": null,
"theme": null,
"viewport": null,
"attempt": null,
"rerunOf": null,
"actions": [],
"checks": [{ "name": "replace-with-check", "status": "not-run", "expected": null, "actual": null }],
"computed": {},
"screenshots": [],
"pngReview": { "status": "not-run", "method": null, "files": [], "findings": [] }
}
],
"requests": {
"boundary": "application HTTP dispatches for this run only",
"model": null,
"kbTotal": null,
"kbCatalog": null,
"kbDetail": null,
"kbRetrieval": null,
"runtimeModelRetries": null,
"composerSubmissions": null,
"toolExecutions": null,
"ledger": [],
"unknowns": []
},
"transport": [{ "serviceAlias": "service-1", "protocol": null, "strictCertificateValidation": null }],
"failures": [],
"cleanup": { "testProcessesStopped": null, "listenersClosed": null, "isolatedProfileRemoved": null, "sensitiveArtifactsRemoved": null, "originalSettingsUnchanged": null },
"redaction": { "status": "not-run", "omitted": ["credentials", "endpoints", "private identifiers", "business and answer prose", "content hashes"] }
}
```
`ledger` 每条记录使用匿名服务标识、场景 ID、attempt、阶段、请求种类、HTTP 方法、协议、状态/错误类别和是否重试;不含 URL、请求头或正文。`failures` 使用同一场景/attempt 关联诊断与重跑。计算值可保存字体、颜色、边界与溢出数,排除 `textContent` 等可能带出业务文本的字段。
截图、完整 DOM、请求体和本地数据库可能含敏感资料,只在本次临时目录用于核验。仓库归档不包含凭据、真实端点、私有配置/远端文档 ID、问题和答案正文、业务片段或这些内容的哈希。需要比较时在本地完成,归档布尔结果、长度或数量;源码/构建产物哈希可用于标识测试版本。可归档截图必须使用无敏感数据的 fixture 或完成脱敏审阅,路径使用相对证据名。
先保留可复查的脱敏失败诊断,再清理原始敏感资料;不要为清理而丢掉失败原因。结束时删除本次隔离配置、加密凭据副本、数据库及不需保留的临时构建,关闭测试进程和端口,核对正式设置未改。仍需本地排障的资料注明临时位置用途与待清理项,不把临时绝对路径当永久证据链接。功能进度链接最终脱敏摘要,并明确原始 harness/截图是否仍可取得。
+187
View File
@@ -0,0 +1,187 @@
# 复发缺陷与防复发规则
本文档收录已经在本仓库重复出现过的缺陷类型。每条规则都来自至少一次真实修正,不是预设的
通用最佳实践。新增条目前,先确认该问题在提交历史中确实出现过一次以上,或一次修正中同时
出现在多处。
修正复发缺陷时,优先建立一条能覆盖整类问题的机制(共享选择器、共享函数、能发现全部实例
的测试),而不是逐个点名修补。逐个修补正是这些条目反复出现的原因。
## 整窗遮罩与原生拖动区域
**症状**:弹窗覆盖标题栏区域时,其关闭按钮无法悬停和点击。
**原因**:`.brand` 与 `.topbar` 声明了 `-webkit-app-region: drag`。整窗遮罩绘制在其上方
时,原生标题栏仍然截获指针。
**规则**:整窗遮罩必须 `inset: 0` 铺满窗口,并由 `styles.css` 中统一的遮罩选择器声明
`-webkit-app-region: no-drag`。新增遮罩时扩展该共享选择器;不要在各自规则里复制
`no-drag`,也不要用 `inset: 38px 0 0` 之类的偏移绕开标题栏。完整要求见
[UI 设计系统](../../UI-DESIGN.md)。
**防复发**:`WorkspacePrimitives.test.tsx` 的
"releases the native drag region for every full-window overlay" 从 `styles.css` 自动发现
全部 `position: fixed` 遮罩并逐个校验,新增遗漏会直接失败。
**历史**:`e9679fe`、`ca4a58c` 各修了一部分被人工点到的弹窗,同一批改动又新增了两个未覆盖
的遮罩;后续复查改为共享规则加自动发现测试。
## 网页浮层遗漏原生浏览器视图
**症状**:知识库实例、项目设置等 Modal 的右侧被浏览器网页覆盖,通知或菜单也可能
被压在原生视图下面。
**原因**:`WebContentsView` 不参与 Renderer 的 CSS 层叠。先前只在关闭终端确认时
显式隐藏 viewport,没有覆盖其他页面的 Portal 和浮层。
**规则**:`BrowserViewport` 共用 `browser-viewport-occlusion.ts`,按 Modal 语义和
浮层相交状态释放显示租约,不销毁网页会话。不得再增加逐个弹窗的特殊隐藏条件。
**防复发**:组件回归覆盖 body Modal、嵌套 Modal、已打开 Modal、隐藏或非相交浮层、
通知、终端确认和非活动页签;实际 Electron 验证同时检查原生视图可见性,不能只看 DOM
的遮罩或截图。
**历史**:先前浏览器多实例修正只覆盖了终端关闭确认;2026-09-13 用户在知识库实例
Modal 再次复现,同一检查确认其他应用级 Modal 均缺少处理。
## Flex 滚动内容压缩页签和分段控件
**症状**:知识库来源选择被长参数表单压成窄缝,标签和按钮文字被裁掉。
**规则**:`PageTabs` 与 `SegmentedControl` 在共享样式上固定 `flex: 0 0 auto`;
直接包装层也必须保留高度。不要按页面复制高度常量。
**防复发**:`WorkspacePrimitives.test.tsx` 检查共享分段控件不收缩,已有平台功能、
能力和 MCP 页签回归继续保留;Electron 复验必须包含长表单与短窗口。
**历史**:设置页签曾补过包装层不收缩规则;2026-09-13 知识库创建类型的共享
`SegmentedControl` 又出现同类问题。
## 逐行查询代替主键查询
**症状**:列表渲染或校验函数随数据量线性变慢,且慢在单次操作内重复发生。
**原因**:用 `listAll().some(...)` 或 `listAll().find(...)` 判断单行是否存在。表有主键或
唯一索引时,这是把 O(1) 查询写成了全表扫描加 `JSON.parse`。
**规则**:判断单行存在或取单行数据时,在 store 上提供按键查询方法(例如 `hasBinding`、
`getBinding`),不要在调用方过滤全量列表。确实需要按分组统计时,循环外取一次并构建
`Map`,不要在循环内反复查询。
**历史**:外部知识库绑定同时出现三处:`requireLibrary`(30+ 调用点,一处在循环内)、
`getKnowledgeSnapshot`(每个知识库两次全表扫描)、`listInstances`(每个实例过滤一次)。
## 将累计过程当作增量事件反复落库
**症状**:聊天正文很小,执行记录数据库却增长到几十 GiB;只增加几个字节的子任务输出,
又写入接近 1 MiB 的旧过程副本。
**原因**:本机与远程 OpenCode 共用完整进度快照,两个桌面落库入口都按流式频率新增
完整 JSON;正文流的缓冲器没有覆盖这种累计快照。
**规则**:界面快照不直接作为追加式日志的存储表示。子任务事件通过共享存储层记录变化,
保留逐条还原和远程来源去重;会话仍保存完整显示结果。旧用户数据通过同一编码器无损
转换后回收空间,不靠删聊天或单独修本机数据库解决。
**防复发**:使用含大工具输出的多次更新检查总存储量,并验证重启、重复远程事件、
消息事务回滚及旧库中断续跑。工具对象须包含生产路径中的 `undefined` 可选字段,
经过真实 JSON 往返后仍识别为无变化,不能按对象键存在性比较。真实模型验证须包含
子代理正常使用工具、修改文件及运行测试,不能只测禁用工具的文本流。
真实旧库需验证逐条等价和物理文件缩小,已升级 schema 的重复数据同样要覆盖。
**历史**:2026-09-13 检查到 305,551 条子任务事件占用 90.11 GiB;
本机 `appendTaskEvent` 与远程 `insertRemoteTaskEventOnce` 同时存在重复保存路径。
0.13.1 发布后的真实工具任务再次复现:同批 928 条事件有 7,787 次重复工具块写入;
只统一 JSON 空字段后,事件负载从 10,467,622 降为 452,391 字节,逐条还原一致。
## 生产路径复用“测试”函数
**症状**:正常用户操作触发了只应在配置校验时出现的错误码,或多发了一次网络请求。
**原因**:让生产路径直接调用为“测试连接”“保存前校验”准备的函数。这类函数通常带有额外的
能力预检和更严格的失败条件。
**规则**:生产路径与校验路径各自提供入口。校验入口可以在生产入口之上叠加预检,反向复用
不允许。共用的裁剪、归一化逻辑抽成独立函数由两者调用。
**历史**:对话检索曾调用 `ExternalKnowledgeService.testRetrieval` 并把用户问题放进
`testQuery`,导致每次提问多一次 detail 请求,且 `EXTERNAL_KB_CAPABILITY_UNAVAILABLE`
会出现在正常对话中。
## 事件迭代结束前释放底层订阅
**症状**:并行 OpenCode 会话已结束,但部分 SSE 响应流仍未关闭;只检查最终取消信号
会误判回收成功。
**原因**:OpenCode SDK `1.18.29` 在迭代器 `return()` 时移除取消监听并释放 reader lock,
但不主动取消 reader。外层 `for await` 先结束 SDK 迭代器,外层 `finally` 再 abort 的
顺序过晚,带缓冲的响应流可能继续存活。聊天和原生 Compact 均曾使用该顺序。
**规则**:两条 SDK 消费路径共用 `consumeEventSubscription`,先 abort 对应订阅,再结束
底层迭代器;请求取消和 Server 生命周期仍保留各自作用域。不要修改依赖包、放宽回收
断言或关闭共享 Server 来代替单次订阅回收。
**防复发**:Runtime 回归检查正常结束和消费方提前返回时,底层迭代器清理前取消已经发生。
`opencode-runtime-lifecycle.test.ts` 使用真实二进制与 SDK,在 1、4、8 路并行、单请求
取消和原生 Compact 后检查打开的响应流归零,并验证并行请求不被误取消。
**历史**:`b03c256` 已增加请求级订阅取消;2026-09-13 候选检查仍复现 3 条响应流未关闭,
复查发现聊天与 Compact 都需要调整上述顺序。
## 快照比对代替取消
**症状**:为检测“操作期间配置被改动”而反复读回状态并 `JSON.stringify` 比较。
**原因**:把取消语义当成配置漂移检测来实现。桌面单用户场景下,改动配置的那次操作本身就能
取消在飞请求。
**规则**:用 `AbortController` 在配置变更点取消在飞请求,并让被取消的操作不写回任何结果。
不要引入前后快照比较、版本化内部凭据或多阶段状态机。唯一性约束交给数据库索引。
**历史**:外部知识库服务曾有 5 处快照比对与专用错误码 `EXTERNAL_KB_CONFIG_CHANGED`;全部
删除后行为反而更正确,因为旧实现会把“用户停用实例”记成 `probeStatus: 'failed'`。
## 删除疑似死代码时的验证顺序
**症状**:删除后类型检查通过,但运行时行为改变,或测试断言的是已不存在的行为。
**规则**:删除枚举取值、文案键、导出函数前,按此顺序确认:
1. `rg` 全仓搜索引用,区分生产代码与测试 fixture。
2. 确认该取值是否可能由生产代码写入。仅测试 fixture 使用即为死代码。
3. 检查是否属于已持久化数据的解析契约。持久化契约即使当前不写入也必须保留。
4. 删除后跑受影响的定向测试,再跑 `npm test`。
**历史**:`probeStatus` 的 `'healthy'` 仅存在于测试 fixture,可以删除;而
`externalKnowledgeLocatorSchema` 的 `sourceUrl` 虽然同样从不写入,但它解析已持久化的引用
数据,必须保留。两者表面症状相同,处置相反。
## 渲染期派生值未记忆化
**症状**:流式响应期间界面卡顿,或大列表操作延迟。
**原因**:在组件体内直接构建 `Map`/`Set` 或链式 `filter`/`flatMap`,而该组件在流式增量期间
每个 token 都会重渲染。
**规则**:组件体内的派生集合必须包在 `useMemo` 中,依赖收窄到真正影响结果的值。紧邻的
`useMemo` 之后又裸建集合,会让记忆化收益减半。
**注意**:React 允许在渲染期用守卫调用 `setState` 来响应 props 变化,这是官方推荐做法,不要
把它改写成 `useEffect`。仓库 lint 规则 `react-hooks/set-state-in-effect` 会拒绝后者。
## 列表刷新重建历史消息并触发重复保存
**症状**:焦点刷新后,正文完全未变,却再次序列化并保存全部会话消息;历史越多,
前端和 Main 的短时内存、主线程工作量越大。
**规则**:会话消息的图片状态使用同一保留引用的合并函数,三个合并分支都必须在
语义未变时返回选定原对象。列表、活动跳转、任务创建刷新都使用摘要或按 ID 读取,
不以完整历史列表作为单条查询。搜索只保留命中 ID;视图淘汰同时释放已确认保存的
详情引用,不把摘要误当成完整模型上下文或替换式保存输入。
**防复发**:App 回归检查 1,600 条历史刷新后保存消息数为 0;真实 SQLite 回归覆盖
未加载正文搜索、较早会话、501 条完整消息及 header-only 保存。另覆盖后台审批
晚于摘要请求到达、未打开会话复制、加载失败重试及恢复请求的问题投影。
**历史**:2026-09-15 在 `f8d0741` 引入的三个图片合并分支同时确认消息引用重建;
同次检查还发现活动跳转与任务创建继续整包读取历史,均改为共用的摘要/详情读取。
+1 -1
View File
@@ -48,7 +48,7 @@ GoodBuddy 应能够:
- 排除最左侧主导航后,主工作区与右侧栏使用相同的 `300px` 最小宽度,并按用户拖动后的
比例分配可用宽度;任一侧到达最小值后停止拖动。
- 主工作区中的消息流和输入区保留对称的左右安全边距,右侧栏不得覆盖或裁切其右边缘。
- 唯一展开与收起开关固定在窗口关闭按钮正下方,右侧栏内部不重复关闭入口;当前会话没有
- 唯一展开与收起开关固定在顶栏操作区、紧邻浅色/深色主题按钮之前,右侧栏内部不重复关闭入口;当前会话没有
Task 时不保留空的 Task 上下文栏。
- 右侧栏在对话、知识库和活动视图之间保持状态。
- 知识图谱实体详情复用同一右栏容器,不再维护独立布局。

Some files were not shown because too many files have changed in this diff Show More