Compare commits

...
Author SHA1 Message Date
mesalogo 451b30d434 docs: design application and system tool navigation
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
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
GoodBuddy Agent Release / Publish Agent to GitHub and OSS (push) Canceled after 0s
2026-09-08 12:35:00 +08:00
mesalogo 25e897c22b chore: prepare GoodBuddy 0.12.7 and Agent 0.11.21 releases
Set the approved Desktop and Agent versions and package the approved Chinese
and English notes without changing published release history. Keep OpenCode
pinned to 1.18.29 and require Desktop 0.12.7 before the new Agent package.

Align both feature lists and affected design documents with reference-image
editing, inherited expert work modes, preview boundaries, and remaining
release acceptance. Application navigation remains design-only. No LoongArch
preview is planned for this release; Agent publication remains non-Latest.

The first candidate full run exposed a browser test clicking Send before it
was enabled. Wait for startup readiness using the existing test convention;
production code is unchanged by this release-preparation commit.

Validation: final full suite 3597 passed, 0 failed, 61 skipped across 338 files.
Typecheck, lint, bilingual release-note validation, version consistency,
unchanged historical notes, whitespace and relative-link checks passed.
No additional real model calls, local production builds, pushes or tags.
Concurrent navigation, workbar and roadmap design edits are not included.
2026-09-08 12:29:49 +08:00
mesalogo c8db340cc7 fix: prevent runtime permission stalls and Agent reconnect failures
Execute could stall on workspace-external access or native child-session
permissions, while structured questions could be lost or answered incorrectly.
Route owned OpenCode descendant interactions, correct Continue question and
custom-answer handling, and resolve DSH Execute ACP permissions without a
second approval. Ask remains read-only and unrelated sessions stay isolated.

Restore the required DSH session projection service. Reuse offline OpenCode
configuration by plugin version, remove unused npm shims from remote payloads,
normalize dependency modes, and accept real compound descriptors up to 4 MiB.
Fix intermittent Agent attach rejection and closed-handshake waits.

Add focused and real-Runtime regressions, document interaction boundaries and
validation evidence, and include the existing application/tool navigation
design drafts. Those navigation changes are design-only, not implemented UI.

Validation: full suite 3597 passed, 0 failed, 61 skipped across 338 files;
typecheck, lint, release-note verification, whitespace and relative-link checks
passed. Current-source Linux x64 installation, Ask/Execute, native subagent
external writes, reconnect and stop/bootstrap passed with 10 completed real
model calls, 13 native endpoint tests and 20 consecutive handshakes.
System sleep/wake and release CI packaging remain unverified.

Release note: 修复 Execute 访问工作区外目录或运行原生子代理时卡在权限等待的问题,并恢复 OpenCode 与 Continue 结构化问答的正确转交。修复远程 Agent 安装与重连失败;Ask 仍保持只读,任意终端密码或业务确认不会自动代答。
2026-09-08 11:34:45 +08:00
mesalogo 9aed926cda fix: stabilize OpenCode reuse and session switching
Check cached embedded servers before reuse, isolate concurrent cancellation, and serialize request MCP registration and cleanup. Preserve native controls across session switches and recover usable partial inventories.

Share the bundled offline OpenCode configuration with desktop and Agent launch paths, including model bridge environment forwarding.

Validation: focused tests and typecheck passed; lint passed before the final fixes. The full test run exceeded 120 seconds and reported a DS stdio MCP failure. Current-source Agent host and real suspend/resume validation remain pending.

Release note: 修复切换会话后 OpenCode 原生控件消失及并发取消相互影响的问题,并完善内嵌服务复用检查和桌面、Agent 的离线依赖配置。
2026-09-08 01:09:14 +08:00
mesalogo 86daae8be2 fix: remove terminal emulator padding 2026-09-07 18:08:52 +08:00
mesalogo 19086d656e fix: reuse bundled agent runtime resources 2026-09-07 16:57:59 +08:00
mesalogo d3947c79dd fix: avoid rerendering chat history while typing 2026-09-07 16:46:18 +08:00
mesalogo 6a1a671d3b fix: support reference image editing 2026-09-07 15:48:47 +08:00
mesalogo dbb7c2fc80 fix: align message content below avatars 2026-09-07 14:02:59 +08:00
mesalogo 237b8a8cee chore: prepare GoodBuddy 0.12.6 and Agent 0.11.20 releases
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (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 / 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 / linux arm64 (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 / 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
Record approved bilingual notes for complete subagent progress, workspace diffs, knowledge selection, OpenCode stream cleanup, external command directories, and restored GitHub Agent updates. Remote progress and diff improvements require the new Agent package. No LoongArch preview is included.
2026-09-06 22:39:41 +08:00
mesalogo d08ba33c3e fix: complete subagent output and file workspace interactions
OpenCode child cards now show ordered text, reasoning and tool progress,
with the final result displayed separately. Local SDK and remote ACP paths
share child tracking, including progress retained across recovery and cancellation.
Remote live progress requires an Agent package containing the event plugin;
older Agent packages still expose final results without inventing missing history.

Knowledge-library rows remain clickable when native label activation changes
focus. File workspace changes use colored Git status letters and per-file
staged/unstaged diffs, including deleted, renamed and untracked files.
Refreshing reloads the selected diff and expanded directories, and more than
50 changes remain accessible through incremental loading.

Validated with 3552 passing tests, typecheck, lint, desktop build, current-source
Linux Host scenarios and isolated Windows desktop UI interactions.

Release note: 展开 OpenCode 子代理卡片可查看执行过程、工具详情与独立最终结果;知识库支持整行勾选。工作区支持单文件暂存与未暂存差异,刷新会更新当前差异,更多变更可继续加载。远端实时过程需要更新 GoodBuddy Agent。
2026-09-06 22:13:45 +08:00
mesalogo b03c25685c fix: clean up OpenCode streams and allow external working directories
Completed OpenCode requests left their event streams open across repeated and
parallel conversations. Each request now closes its own subscription on exit
without cancelling peer sessions, and cancellation preserves its original cause.

Direct-model Execute commands no longer restrict cwd to the workspace. Absolute
paths, parent-relative paths, and symlinks to external directories are accepted.
The workspace remains the default and relative-path base; Ask stays read-only,
and local execution binding, directory validation, and process cleanup remain.

Add real OpenCode lifecycle and real Shell tool-loop regressions, plus matching
feature documentation. Focused checks: 182 passed, 1 skipped; typecheck and lint
passed. Full shared-worktree suite: 3542 passed, 57 skipped, 1 unrelated sidebar
heading failure, reproduced separately and documented.

Release note: 修复 OpenCode 连续及并行对话结束后事件连接未释放的问题;直连模型 Execute 命令现在可直接指定工作区外的绝对路径、相对路径或符号链接目录,Ask 只读边界保持不变。
2026-09-06 19:53:42 +08:00
mesalogo 7003141e56 fix: restore GitHub Agent update checks with paginated discovery
The GitHub release history exceeded the catalog response limit, preventing users from checking Agent updates. Read ten releases per page and stop at the first eligible catalog while preserving the existing 100-release search window and signature checks.

Release note: 修复选择 GitHub 来源时 Agent 更新检查提示发布目录超出大小限制的问题;镜像更新和已有本地包保持不变。
2026-09-06 19:25:40 +08:00
mesalogo e692362414 chore: move ShareServer development to a standalone repository
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
Remove the extracted ShareServer prototype and its server-specific documents
from Desktop. Link the shared network architecture to goodbuddy-shareserver,
while retaining Desktop ownership of the cross-product architecture.

Preserve local generated artifacts and ignore the old server build output.
Desktop runtime behavior and released user data are unchanged. Desktop tests,
typecheck, and lint passed; the standalone service was validated separately.
2026-09-06 19:02:41 +08:00
mesalogo ee804edcae fix: isolate desktop lint from ShareServer configuration
Cross-platform packages / Validate source (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (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
GoodBuddy Agent Release / Publish Agent to GitHub and OSS (push) Canceled after 0s
Exclude the independent prototype from root lint and explicitly anchor TypeScript parser configuration to the Desktop repository. This fixes clean CI runners detecting both root and ShareServer configuration directories. Product behavior and approved release notes are unchanged.
2026-09-06 17:49:44 +08:00
mesalogo 85a4a374fa fix: keep ShareServer prototype tests outside desktop 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
Exclude the unfinished standalone ShareServer prototype from the Desktop root test suite and remove its CI dependency installation. Preserve its independent test configuration and document the release boundary. Approved Desktop and Agent release notes remain unchanged.
2026-09-06 17:37:37 +08:00
mesalogo 89a81dd07c fix: install ShareServer test dependencies in release CI
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
The root validation suite includes ShareServer tests, but clean CI runners only installed Desktop dependencies and could not resolve react-router-dom. Install the independently locked ShareServer dependencies before running validators. Release content and approved bilingual notes are unchanged.
2026-09-06 17:01:50 +08:00
mesalogo 0eb664c0b6 chore: prepare GoodBuddy 0.12.5 and Agent 0.11.19 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 approved bilingual release notes and align Desktop and Agent version metadata. Publish Desktop first so users can upgrade before the shared Agent catalog gains macOS arm64 entries. No LoongArch preview is included.
2026-09-06 16:44:46 +08:00
mesalogo 9c6d1611e2 feat: support macOS arm64 remote Agent hosts
Extend GoodBuddy Agent packaging, installation, SSH attachment, Runtime lifecycle, and Desktop package management to macOS arm64. Linux x64, Linux arm64, and Darwin arm64 share the existing signed catalog. Preserve Ask read-only behavior and Execute account permissions, and stop detached tool descendants during Mac cancellation.

Upgrade Desktop before publishing the first mixed-platform catalog because older Linux-only readers reject Darwin entries. Intel Macs are not supported; no launchd service or administrator access is required. This commit does not publish a release.

Validation: 3520 tests passed, 57 skipped; typecheck, lint, and development build passed. Real Mac installation, Ask/Execute, cancellation and reconnect passed; final Linux installation, Ask and reconnect passed. Nine real model requests were recorded.

Release note: 支持将 Apple Silicon Mac 用作远程项目 Host,可安装 macOS arm64 Agent 并使用 Ask、Execute 和断线重连;与 Linux 共用一个包目录,发布混合目录前需升级 Desktop。
2026-09-06 16:14:02 +08:00
mesalogo 1f01c26e04 fix: keep replies streaming while switching windows
Switching away from the desktop window and back could mark a reply that was
still being generated as "上次运行意外中断", freeze its output partway through,
and push the next message into the pending queue even though nothing had
actually failed. The conversation area also shifted on each switch because the
OpenCode runtime controls were unmounted and re-detected on every refresh.

Reading the conversation store no longer treats an in-progress reply as
interrupted; only startup recovery marks a run that an application exit truly
cut off. Runtime feature detection is now keyed on the resolved runtime scope,
so the OpenCode toolbar and manual Compact control stay in place across
refreshes. No request timeout or recovery behavior changed.

Release note: 修复来回切换桌面窗口时,正在生成的回复被误判为"上次运行意外中断"、输出中途停住、之后发送的消息只能进入待发送队列的问题。使用 OpenCode Runtime 时,会话区也不再因重复检测专属功能而抖动。
2026-09-05 16:28:45 +08:00
mesalogo dd8289fdef fix: make local knowledge bases ready for daily use
Knowledge selection was shared globally, document readiness overstated usable content, and important import and source-management paths exposed technical details or lacked safe recovery actions. Knowledge scope now persists per conversation, readiness reflects real processing channels, and the workspace guides users from creation and import into cited chat with controlled source actions.

Release note: 知识库现按对话保存使用范围,准确区分可用、处理中和失败文档,并提供更简洁的创建、检索测试、来源打开、删除确认和失败重试流程。
2026-09-05 15:51:07 +08:00
mesalogo d5fa0ec67c fix: keep long conversation content scrollable
Large messages containing reasoning, tool lists, or long replies could use an estimated off-screen height that left later content outside the reachable scroll range. Conversation messages now participate in layout with their actual height while the existing 80-message render window keeps long histories bounded.

Release note: 修复偶发的长会话滚动条提前到底问题;包含较长推理、工具记录或正文时,现在可以直接滚动查看全部已加载内容,无需切换会话刷新。
2026-09-05 14:51:21 +08:00
mesalogo 6eb73657e0 chore: ignore Python bytecode caches 2026-09-05 14:02:53 +08:00
mesalogo 4041edefa8 feat: add ShareServer console prototype
ShareServer previously existed only as design documents. The repository now includes a standalone Hono service and a responsive React management-console prototype covering administrator and member workflows, with local build, test, and container entry points.

The sharing design now includes organization-scoped digital agents and explicit local partitioned-memory selection for @ calls. Memory documentation consistently uses partitioned memory and treats time or internal events only as optional recall triggers.

This remains an interactive prototype: fixture actions do not persist, and production authentication, PostgreSQL isolation, device connections, routing, relay, and federation still require implementation.

Release note: 新增可独立运行的 ShareServer 管理控制台交互原型,并完善组织数字人与分区记忆共享设计;当前原型数据和操作不会写入生产系统。
2026-09-05 14:01:20 +08:00
mesalogo 482cd0c467 chore: prepare GoodBuddy 0.12.4 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 Agent Release / Authorize Agent release (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 / 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
Bump Desktop to 0.12.4 and the separately published GoodBuddy Agent to 0.11.18. Add the approved bilingual release notes for HTML previews, long-run and queue reliability, remote terminal-state convergence, and faster OpenCode follow-up on Linux ARM.

This candidate uses the standard six-platform x64/arm64 matrix and does not request a LoongArch experimental preview.
2026-09-05 10:01:45 +08:00
mesalogo 5a1afb00f7 fix: reuse Runtime after native inventory load
GoodBuddy Agents / Validate Agent source (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
Native capability loading previously cold-started a temporary Runtime and disposed it immediately, so Linux ARM users paid the OpenCode startup cost again on their first request. Native inventory loading now uses the cached execution Runtime, preserving normal idle eviction and lifecycle cleanup.

Release note: 优化 Linux ARM 上切换 OpenCode 后的响应速度;原生能力清单与后续对话现在复用同一 Runtime,避免重复冷启动。
2026-09-05 09:45:23 +08:00
mesalogo a4b64a1641 feat: preview Agent HTML in conversations and unblock long replies
Agent replies that produced HTML could only be read as source, so users had
to copy the markup into an external file to see the result. Completed Agent
and Subagent output now renders complete HTML documents, single-root HTML
replies, and `html` / `htm` code blocks as an isolated static preview in
place. Each preview keeps a persistent notice that scripts and network are
disabled and offers the same icon actions as Mermaid diagrams: a source
toggle and a full-screen preview. The new "render HTML in conversations"
switch under Platform Features / General is on by default, applies to the
open conversation immediately, and falls back to HTML source when disabled.
Previews cannot run scripts, reach the network, submit forms, open windows,
or load remote media; author stylesheets and inline styles are preserved so
the intended layout survives, and remote resource loading is refused by the
preview CSP instead.

Direct-model replies no longer fail once they grow large. The byte caps on
streaming responses, single stream blocks, non-streaming replies, model tool
arguments, tool request context, aggregate tool results, and accumulated
answer text are removed, so users stop seeing "模型接口流式响应超过安全限制"
on long answers. Response length is now governed by the request timeout,
cancellation, and context compression. Image generation responses keep their
decodable-image validation.

A message sent after a failed or interrupted run stayed in the pending queue
instead of running. The conversation is now released whenever a run reaches
any terminal state, not only when the renderer reports readiness again, and a
dispatched queue item whose run never starts releases its reservation and
returns to pending. Later messages dispatch without extra user action.

Release note: Agent 回复中的 HTML 现在可直接在会话内预览,支持查看源码和全屏预览,默认开启并可在“平台功能 / 通用设置”关闭;直连模型的长回复不再因大小限制中断;上一次运行失败或被中断后,新发送的消息会立即执行而不再排队等待。
2026-09-05 09:10:44 +08:00
mesalogo 9588c4b901 fix: preserve long run activity and large previews
Long Runtime sessions could silently stop retaining tool and Subagent activity, truncate Run history details, and reject workspace previews over 256 KB. Run history now persists in Main SQLite without the former fixed record or detail caps, surfaces possible loss from legacy caches, and renders long histories in batches.

Local and remote workspace previews now load UTF-8-safe 256 KiB pages on demand instead of rejecting larger text files. The legacy migration, clear-data path, IPC contracts, tests, and feature documentation are updated with the resulting behavior.

Release note: 长时间 Runtime 运行现在会完整保留工具和 Subagent 活动;运行记录迁移到 SQLite,并提示旧版可能已丢失的内容。超过 256KB 的本地及远程文本文件可通过分页面预览继续加载。
2026-09-05 00:31:42 +08:00
mesalogo d180f63abd fix: converge remote run state on persisted terminal messages
After a managed SSH disconnect the renderer kept showing the remote
Agent as processing even though the recovered task had already been
persisted as completed. Local streaming copies shadowed the persisted
terminal state in mergePersistedConversations, and recovery completion
never asked the renderer to re-read conversations.

- Prefer the persisted terminal state over a local streaming message
- Settle active runs whose messages already reached a persisted terminal
  state and release the conversation queue once
- Refresh conversations on window focus and visibility changes
- Publish a conversation change before recovery reports completed/failed
2026-09-04 16:54:35 +08:00
mesalogo 670171ce5d merge: preserve GitHub README updates
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Deploy website to GitHub Pages / Deploy static website (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 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 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
2026-09-04 08:21:17 +08:00
mesalogo 4490e5fd15 fix: stabilize Runtime workflows for 0.12.3
OpenCode status checks could cold-start throwaway servers, unrelated
conversations could block each other, and model, Continue, or remote paths
could replay or duplicate work under failure and backlog conditions. Settings
could also lose edits or overwrite unrelated model fields.

This update makes status checks passive, keeps per-conversation ordering while
allowing concurrency, preserves native long-running work with bounded display
capture and backpressure, narrows retries to known pre-dispatch failures, and
aligns remote Ask permissions without reducing Execute. It also keeps
Embedding updates narrow, improves settings and browser guidance, removes the
unused legacy Main gateway state, and records the validated Desktop 0.12.3 and
Agent 0.11.17 release metadata.

Release note: 改善 OpenCode 切换、多会话长任务、模型重试、远程 Ask/Execute 与设置保存可靠性,并完成 Desktop 0.12.3 / Agent 0.11.17 发布准备。
2026-09-04 08:05:29 +08:00
mesalogo 374968d50b docs: add desktop pet feature design
Adds the full documentation set for the planned desktop pet feature under
docs/features/desktop-pet: README, PRD, user stories, logic design, UI
design, and technical design. The pet is scoped as a lightweight always-on
entry point for quick drafting, drag-and-drop staging, and task status at a
glance; it does not introduce a second Ask/Execute channel, task execution
path, or independent task-state store. Design only, no implementation yet.
2026-09-03 23:22:33 +08:00
mesalogo 4e60a4ca61 feat: customize model provider requests
Model connections can now save bounded JSON request headers and top-level body fields. Direct requests and Continue support both; local OpenCode and DeepSeek Harness use only native header support, while the managed remote OpenCode gateway applies both.

Runtime, protocol, and authentication fields keep precedence. Custom values are ordinary connection settings, not credential storage. This migrates Runtime settings from v20 to v21 and updates the DeepSeek control and managed Runtime ACP capabilities.

Release note: 模型连接详情新增自定义请求头和请求体;直连与 Continue 支持两者,本地 OpenCode 和 DeepSeek Harness 仅应用原生支持的请求头,受管远程 OpenCode 支持两者,界面会明确提示生效范围。
2026-09-03 22:34:01 +08:00
mesalogo 3765a937bd docs: clarify website product highlights
Reframe the feature section around the broader desktop product rather than only Agent Runtimes. Clarify official x64 and arm64 coverage, the separate LoongArch preview, and that compatibility claims are not vendor certification.
2026-09-03 22:31:49 +08:00
mesalogo feaebdf221 feat: share built-in browser across runtimes
The built-in browser was limited to direct-model tools, and its Agent switch also blocked manual Browser workbar actions. It is now a request-scoped GoodBuddy capability assignable to direct models, managed OpenCode, and Continue, while manual browser controls remain available when Agent access is off.

Existing browser enablement migrates to capability settings version 6. DeepSeek Harness remains unsupported.

Release note: 内置浏览器现在可分配给直连模型、OpenCode 和 Continue;关闭 Agent 浏览器能力不会影响用户在浏览器工作栏中的手动操作。
2026-09-03 19:36:06 +08:00
mesalogo 70ec2bf792 fix: restore rounded workbar tabs
The workbar tabs, close controls, and add button now use the shared rounded control styling again. The terminal panel and its toolbar remain square as requested.

Release note: 恢复工作栏页签、关闭按钮和加号入口的圆角外观;终端面板与终端工具栏继续保持直角。
2026-09-03 19:32:00 +08:00
mesalogo 8b9dbf55df fix: allow concurrent and unrestricted Runtime sessions
Local embedded OpenCode serialized otherwise independent conversations, and GoodBuddy could stop long Runtime tasks after fixed tool, activity, model-call, or accumulated display-output limits.

Different conversations can now run concurrently while requests in the same conversation stay ordered. Dynamic MCP tools remain isolated to the current request. OpenCode, Continue, managed ACP, and the Agent model gateway no longer stop native execution at GoodBuddy-defined call counts or prompt-wide output totals; display copies truncate and transport queues apply backpressure instead. Prompt-wide quota fields are removed from the current contracts.

Per-call model settings, provider request timeouts, cancellation, and same-conversation ordering remain unchanged.

Release note: 修复本机 OpenCode 不同会话互相等待的问题,并移除 GoodBuddy 对单次 Runtime 工具、活动和模型调用次数的人为上限;长输出现在只截断展示或施加传输背压,不再取消原生任务。
2026-09-03 17:17:09 +08:00
mesalogo b1e3101857 fix: restore terminal paste and square workbar chrome
Terminal paste previously relied on the Renderer browser Clipboard API, which could be denied by Electron and leave the toolbar action unusable. Text clipboard access now goes through the trusted Main/Preload bridge, and the workbar tabs plus terminal toolbar use straight corners for a continuous docked appearance.

Release note: 修复终端工具栏粘贴可能因剪贴板权限被拒绝而失败的问题,并将工作栏页签与终端工具栏调整为直角样式。
2026-09-03 17:12:27 +08:00
mesalogo 7369c13bcf docs: clarify remote runtime backpressure 2026-09-03 16:34:57 +08:00
mesalogo fba5e1ad30 docs: design device sharing network 2026-09-03 16:34:28 +08:00
mesalogo 468cf0483f docs: design external knowledge providers 2026-09-03 16:29:51 +08:00
mesalogo 6586192222 fix: enforce remote Ask at tool boundary
Managed SSH Ask previously required bubblewrap and applied a process-level filesystem sandbox, while Execute launched directly. Ask and Execute now share the same direct signed Runtime startup; Ask remains read-only through the Agent's existing tool permission mediation, which only permits native read requests and rejects other tool kinds.

Release note: 远程 Ask 与 Execute 现在都可直接启动签名 Runtime,无需 Host 安装 bubblewrap;Ask 仍在工具权限层仅允许读取操作。
2026-09-03 16:23:14 +08:00
mesalogo 987a08ad78 fix: allow remote Execute without bubblewrap
Remote Hosts without the Ask-only bubblewrap tool could report matching Agent and Runtime versions but fail project creation because the Agent hid the Runtime ACP capability entirely. The Agent now advertises a verified Runtime independently of bubblewrap, so Execute can start directly while Ask keeps its existing read-only bubblewrap boundary.

Release note: 修复远程 Host 缺少 bubblewrap 时 Execute 项目无法创建的问题;更新到 Agent 0.11.16 后,Execute 不再受 Ask 专用系统工具影响。
2026-09-03 15:59:05 +08:00
lofyerandGitHub 35a93e61cd Merge pull request #3 from mesalogo/jojocoding-patch-2
Update README.md
2026-09-03 14:25:23 +08:00
lofyerandGitHub 0cba3855ea Merge pull request #2 from mesalogo/jojocoding-patch-1
Update README.zh-CN.md
2026-09-03 14:24:28 +08:00
mesalogo 74c3ec19b4 fix: retry transient direct model failures
Direct model conversations previously stopped after the first transient network error or request timeout, including TLS setup ECONNRESET failures. Text requests now retry up to three times with cancellable backoff before visible output, while partial streamed responses are never replayed.

Release note: 直连模型遇到临时网络故障或请求超时时,会在尚未输出内容的情况下自动重试最多 3 次,并支持随时取消。
2026-09-03 14:24:09 +08:00
jojokeeandGitHub cdc8f6f57a Update README.md
更新英文README
2026-09-03 14:22:33 +08:00
jojokeeandGitHub b8c1d33e7b Update README.zh-CN.md
更新社群入群信息
2026-09-03 14:20:34 +08:00
mesalogo f18e4abb3c fix: simplify topbar Runtime status
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
The topbar repeated a textual state after the Runtime or model name even
though it already displayed a compact status indicator. It now uses green,
yellow, and gray dots for ready, connecting, and unavailable states while
keeping detailed status available to assistive technology and tooltips.

Release note: 顶栏 Runtime 状态改用绿、黄、灰三色标记,不再重复显示“就绪”“连接中”或“不可用”文字。
2026-09-03 13:42:41 +08:00
mesalogo c52732441a chore: coordinate Agent 0.11.15 and Desktop 0.12.2
Cross-platform packages / Validate source (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (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
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
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
The published Agent 0.11.14 predates the registered-Runtime activation change included in the Desktop candidate. Bump the Agent lock to 0.11.15 and the unpublished Desktop recovery to 0.12.2 so the compound package minimum Desktop version, bilingual notes, and feature documentation describe one coordinated release.

Keep v0.12.0 and v0.12.1 immutable and unpublished, and document that Agent tags also require the exact main candidate workflow to pass before tagging.

Release note: 托管 Linux Host 使用 Agent 0.11.15,已登记 Runtime 激活时不再重复检查完整 OpenCode 二进制。
2026-09-03 11:51:30 +08:00
mesalogo ba000300d5 test: stabilize pre-tag main validation
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
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
The Unix private-endpoint relay regression combines socket authentication, SQLite journaling, attach relay framing, and two protocol requests. Give only that end-to-end case a bounded 30-second allowance so a loaded serial CI runner does not fail its previous 5-second default.

Document the mandatory Desktop release order in BUILD.md: push the candidate to both main branches, wait for validators and the production bundle on that exact SHA, and only then create the annotated tag.
2026-09-03 10:56:42 +08:00
mesalogo 3b8d129693 fix: install managed Python on Linux filesystems
GoodBuddy Agents / Validate Agent source (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
Managed Python installation rejected valid case-distinct terminfo symlinks in the pinned Astral archives on Linux x64 and arm64. Archive uniqueness now follows the target filesystem: Linux preserves case-distinct paths, while every target still rejects exact duplicates and unsafe entries.

Move the unpublished 0.12.0 bilingual release notes to the immutable-tag recovery version 0.12.1 and record the failed tag evidence.

Release note: 修复 Linux x64/ARM64 上托管 Python 因官方归档包含大小写不同路径而无法安装的问题。
2026-09-03 10:31:14 +08:00
mesalogo fb5a9c6f43 test: allow bounded knowledge snapshot writes
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
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
The complete-snapshot boundary test performs 501 synchronous SQLite source and document writes. It passed functionally but could exceed its 10-second case timeout on the serial Linux CI runner, so give this I/O-heavy case a bounded 30-second allowance without changing product behavior or global test limits.
2026-09-03 06:00:16 +08:00
mesalogo 26b87d7ed5 test: use the installed OpenCode entrypoint
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
The Linux routing test still referenced the obsolete bin/opencode name. With intentional path-only Runtime selection, that stale fixture was no longer masked by automatic discovery; use the locked opencode-ai 1.18.9 bin/opencode.exe entrypoint on every platform.

Keep the approved 0.12.0 notes and bilingual feature matrices explicit that local OpenCode paths are not prevalidated and fail only at actual launch.
2026-09-03 05:37:52 +08:00
mesalogo 93721d69b6 test: serialize full-suite validation
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
The Linux release validator still exhausted process and socket timing with two Vitest workers, causing unrelated OpenCode and private-endpoint tests to fail. Run test files with one worker so process-backed integration tests remain deterministic within the existing CI job budget.
2026-09-02 23:19:48 +08:00
mesalogo 8ecd87381f test: stabilize 0.12.0 candidate validation
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 Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Full-suite resource contention could make asynchronous renderer assertions and short runtime timing tests fail even though focused runs passed. Limit Vitest to two workers and give the affected UI operations the existing three-second async window.

Also include the approved Linux ARM OpenCode startup note in both feature matrices and release-note languages, and record the successful isolated Linux x64 Host validation of the current-source Runtime activation path.
2026-09-02 22:57:31 +08:00
mesalogo dc36e7a967 fix: skip repeated OpenCode binary validation
OpenCode startup on Linux ARM could run a --version probe before launching the configured Runtime and recheck registered remote binary metadata. Execution now trusts the selected path and registered manifest identity after setup-time verification, so switching and cold launch avoid duplicate binary work.

Release note: 优化 Linux ARM 上切换 OpenCode 的启动速度;已安装或已登记的 Runtime 在执行时不再重复启动或检查二进制。
2026-09-02 22:41:50 +08:00
mesalogo cdecb584d5 test: wait for settings before appearance assertions
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
The Linux CI run could query the Appearance tab before the lazy Settings route finished loading. Appearance tests now wait for the Settings heading before interacting with its tabs, matching the stable navigation pattern used elsewhere.
2026-09-02 21:47:18 +08:00
mesalogo c4231c8f9c chore: prepare GoodBuddy 0.12.0 release
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 Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Update the desktop version, approved bilingual release notes, and synchronized English and Chinese feature matrices for the 0.12.0 candidate.

Release preparation now runs local tests, type checks, and lint without a local production build, then requires the main-branch CI build before tagging. The package matrix uses a native Windows arm64 runner and validates managed Python on every target without adding extra license files to the desktop package.
2026-09-02 21:36:27 +08:00
mesalogo 8a14cf356a fix: hide unverified capability placeholders
The tool environment page always displayed an unverified capability-dependency state even though it had no Skill or MCP evidence behind it. The page now shows only actual interpreter and companion-tool diagnostics; capability dependency status remains reserved for capability details with real validation results.

Release note: 工具执行环境页不再显示没有实际验证依据的能力依赖占位状态。
2026-09-02 17:00:42 +08:00
mesalogo 19a3bafb63 docs: prevent dead-end selection flows
Document that options requiring an in-place prerequisite must remain operable, defer persistence until validation succeeds, avoid implicit first-candidate selection, preserve the previous value on cancellation, and show errors beside the action.
2026-09-02 16:47:24 +08:00
mesalogo 5fac1d3c41 fix: align DeepSeek Harness model selection
DeepSeek Harness previously required a separate Runtime model selection when no administrator-provided connection was available, even if the user had already selected a compatible GoodBuddy model. It now prefers the administrator connection, then follows the compatible default model or first compatible connection across conversations, projects, and messaging channels.

Release note: 修复 DeepSeek Harness 未自动使用已选兼容模型的问题;没有管理员预置连接时,现在会跟随当前默认或首个兼容模型,无需再到 Harness 页面重复指定。
2026-09-02 16:45:42 +08:00
mesalogo 42cbfa43f1 feat: add copy action to AI responses
Completed AI responses now expose a bottom copy action that copies their Markdown source without reasoning, tool logs, or citation metadata. Clipboard writes use a validated Preload/Main bridge so sandboxed Electron sessions no longer fail with a renderer clipboard permission error; full-conversation copy uses the same path.

Release note: AI 回复底部现在可以直接复制 Markdown 原文,并修复了沙箱环境下回复和完整会话可能无法写入剪贴板的问题。
2026-09-02 16:35:51 +08:00
mesalogo 4d9ab72778 fix: keep custom tool environments selectable
The custom Node.js and Python choices could appear unavailable until a candidate was already discovered, and selecting the source implicitly adopted the first candidate. The chooser now stays operable, waits for an explicit validated path, keeps cancellation quiet, and shows validation errors beside the affected runtime.

Candidate discovery now prioritizes active virtual environments and Conda before bounded user/system PATH and common Node/Python locations, while preserving canonical validation and deduplication.

Release note: 修复自定义 Node.js/Python 环境入口不可用的问题,并增加对 PATH、venv、Conda 和常见安装位置的解释器检测。
2026-09-02 16:29:55 +08:00
mesalogo be39665ca9 feat: add managed local tool environments
GoodBuddy can now provide managed Node.js and on-demand Python to local Skills and stdio MCP servers, or validate an explicitly selected custom interpreter. The Capabilities and tools settings page exposes source selection, diagnostics, installation progress, cancellation, and removal while new processes receive immutable PATH snapshots.

Application settings migrate to v10. Local terminal and remote Host environments remain unchanged. Windows x64 native-source Node and Python paths are validated; OSS mirror publication, redistributed license resources, and the remaining native platform probes are recorded as release blockers.

Release note: 新增工具执行环境,可为本机 Skills 和 stdio MCP 使用 GoodBuddy 托管的 Node.js、按需安装的 Python,或经过验证的自定义解释器。
2026-09-02 10:36:56 +08:00
mesalogo 1530fa116c fix: make product copy direct and factual
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
Deploy website to GitHub Pages / Deploy static website (push) Canceled after 0s
The website, README, and Smart Heartbeat interface used promotional superlatives, generic benefit labels, and personified growth language that obscured the actual product behavior. The English README is now the repository entry point with a separate Chinese version, and both website and in-app copy describe concrete capabilities, reports, suggestions, and execution boundaries.

Release note: 优化官网、README 与智能心跳界面的中英文文案,去除空泛宣传和拟人化表达,改为直接说明产品能力、运行记录与建议内容。
2026-09-02 01:14:16 +08:00
mesalogo b36d688900 feat: add direct model programming agents
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 Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Direct model conversations can now execute bounded platform-native processes and delegate one level of programming work without duplicating tools in Agent Runtimes. Process and Subagent activity follows existing workspace, mode, approval, cancellation, and lifecycle boundaries, with chat attribution and complete feature documentation.

Remove the static Renderer bundle baseline gate and its build-only manifests; production builds continue to compile all processes and verify the control-plane installer. Also include the pending documentation link normalization.

Release note: 直连模型现在可在 Execute 模式运行本机命令,并可委派编程 Subagent 完成修改、测试和复核;构建流程不再因静态 Renderer 包体基线阻塞。
2026-09-02 00:50:54 +08:00
mesalogo d36889d9f6 fix: quiet resolved dependency reference logs
electron-builder listed shared, successfully resolved production dependencies as duplicate references during every package build. All desktop packaging entry points now lower only that non-actionable summary to debug while unresolved reference failures remain visible.

Move Renderer-bundled AntV G6 to development dependencies so its transitive graph is not copied into desktop packages.

Release note: 桌面打包不再显示无需处理的重复依赖引用摘要,也不再把已由 Renderer 打包的 AntV G6 依赖树重复复制到安装包中。
2026-09-02 00:45:15 +08:00
mesalogo 06c1123caa fix: focus built-in skill defaults
The Skills catalog exposed nine product-marketing recipes as independent
capabilities and enabled every marketing package for every Runtime by default.
It now keeps five top-level Skills, loads the nine recipes on demand through
the product-marketing package, and leaves the three marketing Skills disabled
until users enable them. Existing saved Skill settings remain authoritative.

Release note: 内置 Skills 已从 14 个顶层入口精简为 5 个;去 AI 味和长文工具默认启用,产品营销类默认停用,已保存的用户设置保持不变。
2026-09-02 00:44:44 +08:00
mesalogo 4f3e0bdcfd fix: unify capability settings navigation
Skills and MCP previously occupied separate top-level settings categories. They now share one Capabilities and tools category with accessible horizontal tabs, while the unimplemented tool environment remains hidden until its settings are usable.

Prevent nested PageTabs wrappers from collapsing to zero height in scrollable Flex layouts, add a CSS regression guard, and document the shared rule plus the planned local Node.js and Python environment.

Release note: 设置中心将 Skills 和 MCP 合并到“能力与工具”,并修复水平页签可能被压缩而不可见的问题;尚未实现的工具执行环境暂不显示。
2026-09-02 00:28:47 +08:00
mesalogo 2fca39ddb2 feat: expand deai writing checks
GoodBuddy Agents / Validate Agent source (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
The bundled deai-writing skill now detects additional template patterns,
unsupported attribution, role-slot prose, engineering metaphors, and absolute
claims. Its JSON report also summarizes findings by level and category while
preserving GoodBuddy's cross-platform Python guidance.

Release note: 中文“去 AI 味”审校新增分析套话、模糊归因、角色槽位、工程黑话和绝对化承诺等检查,并提供按分类汇总的扫描结果。
2026-09-01 20:48:58 +08:00
mesalogo 2f1881a414 docs: organize product design by feature
Define repository and feature-scoped documentation rules for PRDs, User Stories, logic, UI, technical design, and progress evidence.

Move feature-owned documents under docs/features, add one entry point per feature, remove the redundant docs index, and update all affected cross-references. Expand the local tool environment design with explicit product, story, logic, UI, technical, and progress documents.
2026-09-01 14:52:22 +08:00
mesalogo dba3380a06 fix: accept bare browser addresses
The Browser workbar required users to enter a complete HTTP(S) URL, so submitting a bare domain or host with a port failed URL validation. Enter and Go now prepend http:// before using the existing browser navigation path.

Explicit protocols remain unchanged so the Main browser URL policy continues to reject unsupported schemes with the correct error.

Release note: 浏览器地址栏现在支持直接输入域名或主机端口,回车或点击“前往”时会自动补全 http://。
2026-09-01 08:54:18 +08:00
mesalogo 77ce13aba3 fix: align the primary sidebar divider
The primary sidebar resize hit target still reserved four pixels in the flex row, leaving a visible gap between the divider and the content frame. Its opposing negative margins now keep the nine-pixel pointer target layout-neutral while preserving the aligned one-pixel divider.

Release note: 修复主侧栏拖拽分隔线与内容边缘未对齐的问题。
2026-09-01 08:37:05 +08:00
mesalogo 5127f7d045 feat: add browser workbar navigation controls
The Browser workbar previously showed the Agent-controlled page but did not let users navigate it directly. It now provides Back, Refresh or Stop Loading, an address field, and Go alongside the existing interaction and close actions.

User and Agent operations share the same per-conversation browser session and serialization path. Loading, committed URL, and history state keep the toolbar accurate, while Stop Loading interrupts page navigation without closing the session or releasing Runtime context.

Direct navigation continues to use the existing built-in-browser capability and URL policy. Closing the browser remains a distinct lifecycle action.

Release note: 浏览器工作栏新增返回、刷新/停止加载、地址栏和前往操作;用户与 Agent 共用同一浏览器会话,停止加载不会关闭会话。
2026-09-01 08:26:57 +08:00
mesalogo 27055693bf feat: add multi-terminal workbar and resizable sidebars
The assistant workbar now uses persistent application tabs with a permanent catalog entry point and supports multiple local or managed SSH terminals bound to the current project. Terminal sessions flow through the Main, preload, and renderer boundaries with bounded output, resize, lifecycle, and cleanup behavior.

The main sidebar and Magic Notes list now support pointer and keyboard resizing. Magic Notes can also hide and restore its list, remembers the chosen layout, and stacks its panes when the available width is narrow.

Closing a terminal tab ends its Shell session. Restored terminal tabs do not restart prior Shell processes and require an explicit reconnect.

Release note: 助手工作栏现可通过“+”打开多个本机或当前远程项目终端;主侧栏和魔法笔记列表支持拖拽调宽,魔法笔记列表可隐藏并记住布局。
2026-09-01 00:49:49 +08:00
mesalogo 73cc55bf75 fix: clarify local tools on remote hosts
The remote host settings page did not explain that locally configured Skills and MCP servers stay on the desktop. It now states that boundary in Chinese and English, with a focused UI regression test.

Document the planned local Node.js and Python environment for local Skills and stdio MCP servers, including managed and custom sources, compatibility migration, lifecycle, remote boundaries, and acceptance criteria.

Release note: 远程主机设置现在明确说明本机 Skills 和 MCP 不会作用于远程主机。
2026-08-31 23:28:41 +08:00
mesalogo 00a231da07 chore: prepare GoodBuddy 0.11.14 releases
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agent Release / Authorize Agent release (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
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
Set the Desktop package metadata and approved bilingual notes to 0.11.14,
aligned with the coordinated Agent 0.11.14 lock and release-status docs. This
candidate uses the standard x64/arm64 release matrices and does not include an
experimental LoongArch preview.

Release note: 准备 GoodBuddy Desktop 与 Agent 0.11.14 正式发布,包含托管 SSH 任务恢复、有界诊断和桌面反馈改进。
2026-08-31 16:02:53 +08:00
mesalogo 2833a27f83 feat: add bounded diagnostics and improve UX feedback
Ask and Execute guidance, Runtime state, live announcements, loading states,
retry paths, settings focus, and update failures now provide consistent and
accessible feedback across the desktop experience.

Add bounded redacted Desktop and Agent diagnostic logs, a read-only Agent
diagnostics command, and an explicit opt-in for attaching a small Desktop
diagnostic summary to in-app feedback. Diagnostics stay local by default and
the existing feedback v1 service contract remains compatible.

Release note: 新增可选桌面诊断反馈与 Agent 诊断导出,并统一 Ask/Execute 状态、错误恢复和无障碍反馈;诊断默认保留在本地,仅在用户明确勾选时附加桌面摘要。
2026-08-31 15:35:13 +08:00
mesalogo b1729125e1 fix: allow slower OpenCode startup on Linux ARM
OpenCode health polling restored a ten-second startup limit, causing Linux ARM systems and QEMU environments that became healthy just after that boundary to be reported unavailable. Managed local OpenCode now retains health-based readiness detection while allowing up to thirty seconds for startup.

Release note: 修复部分 Linux ARM 设备切换 OpenCode 时因启动超过 10 秒而误报超时的问题;健康检查现在最多等待 30 秒。
2026-08-31 13:25:49 +08:00
mesalogo 561de29f00 feat: keep managed SSH prompts running on Hosts
Managed SSH prompts previously depended on Desktop-owned provider and transcript state, so accepted work could stop or become uncertain when the Desktop, network, or power disappeared. The Agent now owns accepted OpenCode prompt execution, model and tool rounds, cancellation, and the durable semantic transcript, while Desktop remains authoritative for permanent projects, conversations, tasks, and messages.

Recovered work attaches to the original conversation without replay. The project switcher also groups managed SSH projects by saved Host and shows live Agent connectivity beside each group without opening a connection merely to read status. Remote project subtitles now show only the path, and the Agent lock advances to 0.11.14 with Runtime ACP v4.

Release note: 托管 SSH 任务在桌面端、网络或电源中断后可继续在主机上运行,并能回到原会话精确恢复;项目菜单现按主机分组并显示实时连接状态。
2026-08-31 11:04:41 +08:00
mesalogo f6f8d5e053 fix: release runtime status probes after checks
Chat startup and Runtime switching previously cached status-only Runtime instances, leaving managed OpenCode servers running without active conversations. Status checks now resolve the selected or configured default Runtime through a disposable probe and release it immediately.

Project execution Runtimes remain cached by model selection and execution space, preserving parallel work across different projects.

Release note: 修复启动或切换 Runtime 后空闲 OpenCode 服务持续驻留的问题;状态检查完成后会立即回收,多个项目仍可并行执行。
2026-08-31 01:35:04 +08:00
mesalogo 8c580a9174 fix: preserve remote environment checks across settings pages
The Hosts settings page discarded the last successful Agent and Runtime version check whenever users switched categories or restarted GoodBuddy, forcing them to refresh the same Host again. Validated results now persist locally and remain visible until users refresh, edit or delete the Host, or start a remote environment update.

Release note: 远程主机的 Agent 和 Runtime 版本检查结果现在会在设置页面切换和应用重启后保留,无需重复点击刷新版本。
2026-08-31 01:07:52 +08:00
mesalogo efd304fa1d feat: support native subagents and long-running prompts
GoodBuddy Agents / Validate Agent source (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
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 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
OpenCode Task calls now appear as first-class subagents in local and managed SSH conversations, with stable status, persistence, terminal handling, and true event ordering instead of duplicate generic tool entries.

GoodBuddy no longer applies a fixed outer wall-clock total to OpenCode, Continue, DeepSeek Harness, or managed SSH Prompts. Runtime-owned limits, user cancellation, bounded control operations, output limits, and released Agent protocol compatibility remain intact. Desktop and Agent release metadata are synchronized at 0.11.13 with approved bilingual notes.

Release note: OpenCode 原生 Task 现在以独立子 Agent 展示;长任务不再被 GoodBuddy 固定总时限中止,并修复 OpenCode 启动识别、可配置 HTTP 服务地址与发布说明读取问题。
2026-08-30 17:18:33 +08:00
mesalogo 7c6f15d3c0 fix: allow HTTP for configurable service endpoints
DeepSeek Harness and Weixin binding configuration rejected non-loopback HTTP endpoints even when users explicitly selected those internal services. Configurable endpoints now accept valid HTTP and HTTPS URLs without host-location restrictions, while retaining the required protocol and API-key checks.

Release note: 模型连接、DeepSeek Harness 与微信绑定现在可直接使用局域网或公网 HTTP/HTTPS 服务地址,不再强制公网 HTTPS 或本机回环地址。
2026-08-30 16:49:47 +08:00
mesalogo 6dc219f3a3 fix: detect OpenCode readiness by health
GoodBuddy previously treated one exact OpenCode stdout line as the startup signal, so harmless log formatting differences could report a timeout even when the server was already listening. It now polls the authenticated health endpoint on the loopback port that GoodBuddy selected, with bounded attempts and the original ten-second startup limit.

Release note: 修复 OpenCode 已启动但因监听日志格式未被识别而误报超时的问题;启动检测现在直接使用本机健康接口。
2026-08-30 15:30:43 +08:00
mesalogo acc5a8e2d8 fix: allow slower OpenCode server startup
The bundled OpenCode server could be reported unavailable when slower Linux ARM systems needed more than ten seconds to initialize. GoodBuddy now allows thirty seconds for startup before terminating the server and reports the updated timeout.

Release note: 修复部分 Linux ARM 设备上 OpenCode 冷启动较慢时被误报为启动超时的问题。
2026-08-30 14:42:18 +08:00
mesalogo 9f0f231e3b chore: prepare GoodBuddy 0.11.12 release
GoodBuddy Agents / Validate Agent source (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
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
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
2026-08-30 11:08:55 +08:00
mesalogo 967796539f fix: recover remote conversations after uncertain prompts
An interrupted managed SSH prompt could leave its ACP binding as the permanent owner of the conversation, so every later user message failed with the same uncertain-recovery error. A later explicit prompt now retires that binding and starts a fresh ACP session without replaying the uncertain operation.

Ask-mode Runtime startup also waited on the wrong bubblewrap executable identity. The Agent now supervises the bounded transition to the signed Runtime or model-bridge executable, and the Agent version advances to 0.11.10.

Release note: 修复远程 ACP 请求中断后同一会话无法继续输入,以及 Ask 模式可能在模型调用前启动失败的问题。
2026-08-30 11:02:20 +08:00
mesalogo 8e08ab0bac feat: show Magic Notes update times
The Magic Notes list previously showed only each title, content preview, and
entry count, making similarly named or recently changed notes harder to scan.
Each note now shows its localized update time beside the entry count, with a
semantic timestamp and bounded single-line layout for the narrow list pane.

Release note: 魔法笔记左侧列表现在显示每篇笔记的本地化更新时间,便于快速找到最近修改的内容。
2026-08-30 10:09:39 +08:00
mesalogo edba31e873 chore: prepare GoodBuddy 0.11.11 release
GoodBuddy Agents / Validate Agent source (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 / 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
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Bump the desktop package metadata to 0.11.11 and package the approved Simplified Chinese and English release notes for complete remote conversation saves and discoverable Magic Notes list shortcuts.

Release note: GoodBuddy 0.11.11 修复远程项目长工具结果的会话持久化,并补充魔法笔记列表快捷输入提示。
2026-08-30 09:33:58 +08:00
mesalogo 0804f5ccd2 fix: preserve complete remote tool details
Managed SSH Runtime events could complete successfully and then fail local conversation persistence when tool details exceeded a second, smaller renderer-to-Main schema limit. Conversation persistence now stores the complete tool details already accepted from the Runtime, so long remote results no longer leave the final assistant message stuck in a streaming state.

Release note: 修复远程项目工具结果较长时会话持久化失败的问题;Runtime 已返回的工具详情现在会完整保存到本地会话。
2026-08-30 09:29:28 +08:00
mesalogo 29639ff516 fix: surface Magic Notes list shortcuts
Magic Notes already accepted Quill's native bullet, numbered, and checklist
autofill, but the editor did not make those shortcuts discoverable or protect
them with behavior coverage. The composer now explains the supported markers
in both languages, and tests verify they continue producing Delta list formats.

Release note: 魔法笔记编辑区现在提示项目符号、编号和待办清单快捷输入;输入标记后按空格即可创建对应列表。
2026-08-30 09:20:27 +08:00
mesalogo 22c80b2d94 fix: align Linux Agent verifier regression
GoodBuddy Agents / Validate Agent source (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
The Linux source validator still expected the full verifier's old payload mode
message after entrypoint metadata checks moved ahead of the payload loop. The
test now asserts the actual bounded entrypoint error returned by the optimized
verifier.

The unpublished desktop 0.11.9 notes move to 0.11.10. The Agent lock remains
on the successfully published Agent 0.11.9, and the recovery release still has
no LoongArch preview.

Release note: GoodBuddy 0.11.10 延续已批准的 Host 更新修复;未发布的桌面 0.11.9 由新版本替代。
2026-08-29 23:01:46 +08:00
508 changed files with 87396 additions and 29081 deletions
+35 -14
View File
@@ -45,17 +45,22 @@ jobs:
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build Linux ${{ matrix.arch }} compound package
name: Build ${{ matrix.platform }} ${{ matrix.arch }} compound package
needs: authorize
if: needs.authorize.outputs.authorized == 'true'
strategy:
fail-fast: false
matrix:
include:
- arch: x64
- platform: linux
arch: x64
runner: ubuntu-24.04
- arch: arm64
- platform: linux
arch: arm64
runner: ubuntu-24.04-arm
- platform: darwin
arch: arm64
runner: macos-15
runs-on: ${{ matrix.runner }}
timeout-minutes: 25
environment:
@@ -76,11 +81,15 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Verify native build target
run: node -e "if(process.platform !== '${{ matrix.platform }}' || process.arch !== '${{ matrix.arch }}') throw new Error('Native Agent target mismatch')"
- name: Resolve locked native inputs
id: inputs
shell: bash
env:
AGENT_ARCH: ${{ matrix.arch }}
AGENT_PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail
node - <<'NODE'
@@ -88,9 +97,11 @@ jobs:
const { createHash } = require('node:crypto')
const agent = require('./agent-runtime-lock.json')
const remote = require('./remote-runtime-lock.json')
const node = agent.node?.targets?.[`linux-${process.env.AGENT_ARCH}`]
const node = agent.node?.targets?.[`${process.env.AGENT_PLATFORM}-${process.env.AGENT_ARCH}`]
const runtime = remote.runtimes?.opencode
const opencode = runtime?.targets?.[process.env.AGENT_ARCH]
const runtimeKey = process.env.AGENT_PLATFORM === 'linux'
? process.env.AGENT_ARCH : `${process.env.AGENT_PLATFORM}-${process.env.AGENT_ARCH}`
const opencode = runtime?.targets?.[runtimeKey]
if (
!node ||
!opencode ||
@@ -136,7 +147,7 @@ jobs:
uses: actions/cache@v6
with:
path: ${{ runner.temp }}/goodbuddy-agent-release-inputs
key: goodbuddy-agent-release-${{ matrix.arch }}-${{ steps.inputs.outputs.cache-key }}
key: goodbuddy-agent-release-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.inputs.outputs.cache-key }}
- name: Acquire and verify native inputs
shell: bash
@@ -162,8 +173,14 @@ jobs:
"$NODE_URL" --output "$node_archive.partial"
mv "$node_archive.partial" "$node_archive"
fi
printf '%s %s\n' "$NODE_SHA256" "$node_archive" |
sha256sum --check --strict -
node - "$node_archive" "$NODE_SHA256" <<'NODE'
const { createHash } = require('node:crypto')
const { readFileSync } = require('node:fs')
const [archive, expected] = process.argv.slice(2)
if (createHash('sha256').update(readFileSync(archive)).digest('hex') !== expected) {
throw new Error('Node runtime archive integrity mismatch')
}
NODE
opencode_archive="$directory/$OPENCODE_ARCHIVE"
if [[ ! -f "$opencode_archive" ]]; then
@@ -201,6 +218,7 @@ jobs:
shell: bash
env:
AGENT_ARCH: ${{ matrix.arch }}
AGENT_PLATFORM: ${{ matrix.platform }}
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
GOODBUDDY_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_SIGNING_KEY_ID }}
GOODBUDDY_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_SIGNING_PRIVATE_KEY }}
@@ -211,11 +229,12 @@ jobs:
output="$RUNNER_TEMP/goodbuddy-agent-output"
mkdir -p "$output"
node build/agent-package.cjs build \
--platform "$AGENT_PLATFORM" \
--arch "$AGENT_ARCH" \
--minimum-desktop-version "$(node -p "require('./package.json').version")" \
--node-archive "$RUNNER_TEMP/goodbuddy-agent-release-inputs/$NODE_ARCHIVE" \
--runtime-archive "$RUNNER_TEMP/goodbuddy-agent-release-inputs/$OPENCODE_ARCHIVE" \
--output "$output/goodbuddy-agent-$AGENT_VERSION-linux-$AGENT_ARCH.gbagent" \
--output "$output/goodbuddy-agent-$AGENT_VERSION-$AGENT_PLATFORM-$AGENT_ARCH.gbagent" \
> "$output/result.json"
node - "$output/result.json" "$AGENT_ARCH" "$AGENT_VERSION" <<'NODE'
const { readFileSync } = require('node:fs')
@@ -223,9 +242,10 @@ jobs:
const result = JSON.parse(readFileSync(resultPath, 'utf8'))
if (
result.descriptor?.architecture !== architecture ||
result.descriptor?.platform !== process.env.AGENT_PLATFORM ||
result.descriptor?.version !== version ||
result.archive !==
`goodbuddy-agent-${version}-linux-${architecture}.gbagent` ||
`goodbuddy-agent-${version}-${process.env.AGENT_PLATFORM}-${architecture}.gbagent` ||
!/^[a-f0-9]{64}$/u.test(result.sha256)
) {
throw new Error('Production Agent package result is invalid')
@@ -235,7 +255,7 @@ jobs:
- name: Upload verified compound package
uses: actions/upload-artifact@v7
with:
name: goodbuddy-agent-package-linux-${{ matrix.arch }}
name: goodbuddy-agent-package-${{ matrix.platform }}-${{ matrix.arch }}
path: ${{ runner.temp }}/goodbuddy-agent-output/goodbuddy-agent-*.gbagent
if-no-files-found: error
compression-level: 0
@@ -269,7 +289,7 @@ jobs:
- name: Download compound packages
uses: actions/download-artifact@v8
with:
pattern: goodbuddy-agent-package-linux-*
pattern: goodbuddy-agent-package-*
path: dist/agent-release
merge-multiple: true
@@ -353,6 +373,7 @@ jobs:
node build/agent-catalog.cjs create \
--x64-package "dist/agent-release/goodbuddy-agent-$AGENT_VERSION-linux-x64.gbagent" \
--arm64-package "dist/agent-release/goodbuddy-agent-$AGENT_VERSION-linux-arm64.gbagent" \
--darwin-arm64-package "dist/agent-release/goodbuddy-agent-$AGENT_VERSION-darwin-arm64.gbagent" \
"${previous[@]}" \
--catalog dist/agent-release/agent-catalog.json \
--signature dist/agent-release/agent-catalog.sig \
@@ -489,8 +510,8 @@ jobs:
run: |
set -euo pipefail
root="https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases/v$AGENT_VERSION"
for architecture in x64 arm64; do
name="goodbuddy-agent-$AGENT_VERSION-linux-$architecture.gbagent"
for target in linux-x64 linux-arm64 darwin-arm64; do
name="goodbuddy-agent-$AGENT_VERSION-$target.gbagent"
curl --fail --silent --show-error --location \
"$root/$name" --output "$RUNNER_TEMP/$name"
cmp "dist/agent-release/$name" "$RUNNER_TEMP/$name"
+54 -14
View File
@@ -9,6 +9,8 @@ on:
- 'build/agent-bundle.cjs'
- 'build/agent-package.cjs'
- 'build/agent-ci-bundle.cjs'
- 'build/agent-build-target.cjs'
- 'build/binary-architecture.cjs'
- 'build/file-hash.cjs'
- 'build/remote-runtime-bundle.cjs'
- 'build/signing-key-preflight.cjs'
@@ -19,6 +21,7 @@ on:
- 'src/agent-daemon/**'
- 'src/shared/**'
- 'tests/agent-bundle.test.ts'
- 'tests/agent-darwin-build.test.ts'
- 'tests/agent-package.test.ts'
- 'tests/agents-workflow.test.ts'
- 'tests/remote-runtime-bundle.test.ts'
@@ -31,6 +34,8 @@ on:
- 'build/agent-bundle.cjs'
- 'build/agent-package.cjs'
- 'build/agent-ci-bundle.cjs'
- 'build/agent-build-target.cjs'
- 'build/binary-architecture.cjs'
- 'build/file-hash.cjs'
- 'build/remote-runtime-bundle.cjs'
- 'build/signing-key-preflight.cjs'
@@ -41,6 +46,7 @@ on:
- 'src/agent-daemon/**'
- 'src/shared/**'
- 'tests/agent-bundle.test.ts'
- 'tests/agent-darwin-build.test.ts'
- 'tests/agent-package.test.ts'
- 'tests/agents-workflow.test.ts'
- 'tests/remote-runtime-bundle.test.ts'
@@ -73,22 +79,28 @@ jobs:
run: |
npm test -- \
tests/agent-bundle.test.ts \
tests/agent-darwin-build.test.ts \
tests/agent-package.test.ts \
tests/remote-runtime-bundle.test.ts \
tests/agents-workflow.test.ts
npm run typecheck
build:
name: Build Linux ${{ matrix.arch }} Agent
name: Build ${{ matrix.platform }} ${{ matrix.arch }} Agent
needs: validate
strategy:
fail-fast: false
matrix:
include:
- arch: x64
- platform: linux
arch: x64
runner: ubuntu-24.04
- arch: arm64
- platform: linux
arch: arm64
runner: ubuntu-24.04-arm
- platform: darwin
arch: arm64
runner: macos-15
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
@@ -103,17 +115,23 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Verify native runner target
run: node -e "if (process.platform !== '${{ matrix.platform }}' || process.arch !== '${{ matrix.arch }}') throw new Error('Agent CI requires the native target')"
- name: Resolve locked official Node runtime
id: runtime
shell: bash
env:
AGENT_ARCH: ${{ matrix.arch }}
AGENT_PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail
node - <<'NODE'
const { appendFileSync } = require('node:fs')
const lock = require('./agent-runtime-lock.json')
const target = lock.node?.targets?.[`linux-${process.env.AGENT_ARCH}`]
const { lock } = require('./build/agent-ci-bundle.cjs').readCiLocks(
process.env.AGENT_PLATFORM, process.env.AGENT_ARCH
)
const target = lock.node?.targets?.[`${process.env.AGENT_PLATFORM}-${process.env.AGENT_ARCH}`]
if (
!target ||
!/^[A-Za-z0-9._-]+\.tar\.gz$/u.test(target.archive) ||
@@ -147,7 +165,7 @@ jobs:
uses: actions/cache@v6
with:
path: ${{ runner.temp }}/goodbuddy-agent-node
key: goodbuddy-agent-ci-node-${{ matrix.arch }}-${{ steps.runtime.outputs.sha256 }}
key: goodbuddy-agent-ci-node-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.runtime.outputs.sha256 }}
- name: Acquire and verify locked Node runtime
shell: bash
@@ -170,22 +188,34 @@ jobs:
"$NODE_URL" --output "$archive.partial"
mv "$archive.partial" "$archive"
fi
printf '%s %s\n' "$NODE_SHA256" "$archive" |
sha256sum --check --strict -
node - "$archive" "$NODE_SHA256" <<'NODE'
const { createHash } = require('node:crypto')
const { readFileSync } = require('node:fs')
const [archive, expected] = process.argv.slice(2)
if (createHash('sha256').update(readFileSync(archive)).digest('hex') !== expected) {
throw new Error('Node runtime archive integrity mismatch')
}
NODE
- name: Resolve locked OpenCode Runtime
id: opencode
shell: bash
env:
AGENT_ARCH: ${{ matrix.arch }}
AGENT_PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail
node - <<'NODE'
const { appendFileSync } = require('node:fs')
const { createHash: hash } = require('node:crypto')
const lock = require('./remote-runtime-lock.json')
const runtime = lock.runtimes?.opencode
const target = runtime?.targets?.[process.env.AGENT_ARCH]
const { runtimeLock } = require('./build/agent-ci-bundle.cjs').readCiLocks(
process.env.AGENT_PLATFORM, process.env.AGENT_ARCH
)
const runtime = runtimeLock.runtimes?.opencode
const key = process.env.AGENT_PLATFORM === 'linux'
? process.env.AGENT_ARCH
: `${process.env.AGENT_PLATFORM}-${process.env.AGENT_ARCH}`
const target = runtime?.targets?.[key]
if (
!runtime ||
!target ||
@@ -213,7 +243,7 @@ jobs:
uses: actions/cache@v6
with:
path: ${{ runner.temp }}/goodbuddy-opencode-runtime
key: goodbuddy-agent-ci-opencode-${{ matrix.arch }}-${{ steps.opencode.outputs.cache-key }}
key: goodbuddy-agent-ci-opencode-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.opencode.outputs.cache-key }}
- name: Acquire and verify locked OpenCode Runtime
shell: bash
@@ -251,16 +281,22 @@ jobs:
NODE
- name: Run native Agent regressions
if: matrix.platform == 'linux'
run: |
npm test -- \
src/agent-daemon/model-bridge.test.ts \
src/agent-daemon/lifecycle.test.ts
- name: Run native macOS process regressions
if: matrix.platform == 'darwin'
run: npm test -- src/agent-daemon/darwin-process-identity.test.ts
- name: Build and verify ephemeral signed compound Agent package twice
id: agent
shell: bash
env:
AGENT_ARCH: ${{ matrix.arch }}
AGENT_PLATFORM: ${{ matrix.platform }}
NODE_ARCHIVE: ${{ steps.runtime.outputs.archive }}
OPENCODE_ARCHIVE: ${{ steps.opencode.outputs.archive }}
run: |
@@ -270,11 +306,12 @@ jobs:
mkdir -p "$directory"
agent_version="$(node -p "require('./agent-runtime-lock.json').agentVersion")"
node build/agent-ci-bundle.cjs \
--platform "$AGENT_PLATFORM" \
--arch "$AGENT_ARCH" \
--node-archive "$RUNNER_TEMP/goodbuddy-agent-node/$NODE_ARCHIVE" \
--opencode-archive "$RUNNER_TEMP/goodbuddy-opencode-runtime/$OPENCODE_ARCHIVE" \
--output-directory "$directory/bundle" \
--archive "$directory/goodbuddy-agent-$agent_version-linux-$AGENT_ARCH.gbagent" |
--archive "$directory/goodbuddy-agent-$agent_version-$AGENT_PLATFORM-$AGENT_ARCH.gbagent" |
tee "$directory/result.json"
node - <<'NODE'
const { appendFileSync, readFileSync } = require('node:fs')
@@ -286,6 +323,7 @@ jobs:
)
if (
result.architecture !== process.env.AGENT_ARCH ||
result.platform !== process.env.AGENT_PLATFORM ||
!/^[a-f0-9]{64}$/u.test(result.archiveSha256)
) {
throw new Error('Agent CI result is invalid')
@@ -300,14 +338,16 @@ jobs:
shell: bash
env:
AGENT_ARCH: ${{ matrix.arch }}
AGENT_PLATFORM: ${{ matrix.platform }}
AGENT_SHA256: ${{ steps.agent.outputs.sha256 }}
run: |
{
echo "### Linux $AGENT_ARCH Agent"
echo "### $AGENT_PLATFORM $AGENT_ARCH Agent build"
echo
echo "Native compound Agent, Node, and OpenCode Runtime build, test-signature verification, launch smoke, and deterministic archive comparison passed."
echo
echo "Archive SHA-256: \`$AGENT_SHA256\`"
echo
echo "The ephemeral CI key and bundle are not published."
echo "Native build checks do not replace real Host installation, Attach, and Ask/Execute acceptance."
} >> "$GITHUB_STEP_SUMMARY"
+9 -1
View File
@@ -130,7 +130,7 @@ jobs:
runner: windows-2025
- platform: windows
arch: arm64
runner: windows-2025
runner: windows-11-vs2026-arm
- platform: macos
arch: x64
runner: macos-15-intel
@@ -174,6 +174,14 @@ jobs:
name: goodbuddy-production-bundle
path: out
- name: Verify native runner architecture
run: node -e "if(process.arch !== '${{ matrix.arch }}'){throw new Error('Expected ${{ matrix.arch }} runner, received '+process.arch)}"
- name: Install and validate managed Python
run: npm exec vitest run tests/local-tool-environment-live.test.ts
env:
GOODBUDDY_RUN_LIVE_TOOL_ENVIRONMENT: '1'
- name: Install RPM packaging tools
if: matrix.platform == 'linux'
shell: bash
+4
View File
@@ -1,6 +1,8 @@
node_modules/
out/
dist/
# Local build output left by the extracted ShareServer project.
shareserver/build-server/
coverage/
.vite/
.runtime-resources/
@@ -8,6 +10,8 @@ coverage/
.agent-resources/
*.log
*.tsbuildinfo
__pycache__/
*.py[cod]
.env
.env.*
!.env.example
+77 -16
View File
@@ -151,6 +151,22 @@ additional hardening beyond the actual internal-network threat model:
- Keep UI accessible with labels, keyboard behavior, semantic roles, and visible
focus states.
## Product Design Documentation
- Organize feature documentation by product feature under
`docs/features/<feature-name>/`, not by document type.
- Treat each feature directory's `README.md` as its documentation entry point.
- Follow `docs/features/AGENTS.md` for required document responsibilities,
naming, traceability, progress evidence, and migration rules.
- Keep cross-feature architecture, quality protocols, development procedures,
and product-wide roadmaps outside feature directories only when they cannot
be owned by one feature.
- Maintain one authoritative definition for each requirement, state rule, UI
behavior, technical contract, and implementation fact. Link to it instead of
duplicating it across documents.
- Update the affected feature documents after functional changes. The
implemented and validated production behavior is the final source of truth.
## UI Consistency
- Treat `UI-DESIGN.md` as the canonical UI design system. Read and follow it
@@ -176,11 +192,6 @@ additional hardening beyond the actual internal-network threat model:
blocking page state, or an error with an immediate local recovery action.
- Do not show the same event both inline and as an application notification.
Preserve user input and actionable error context when an operation fails.
- Treat renderer bundle budgets as regression guardrails, not UX targets. Do
not lazy-load one lightweight peer tab solely to satisfy a byte ceiling.
When a genuinely heavy view needs deferred loading, preserve peer
interaction semantics, render a non-empty layout-stable local fallback,
and test its first interaction rather than only its eventual appearance.
## Commit Messages and Release Notes
@@ -233,14 +244,29 @@ authorization to add product persistence or recovery machinery.
Repository-wide release invariants:
- Write each release-note feature item as two or three clear sentences that
identify the user situation, the available action, and the resulting
behavior or important boundary. Do not impose an arbitrary per-item
character limit; use the detail needed to make the user scenario clear.
- Before approving the exact release commit and bilingual release notes,
inspect the English `FEATURES.md` and Simplified Chinese
`FEATURES.zh-CN.md` against the verified changes since the previous stable
release. Keep both versions aligned, and update their provided capabilities,
roadmap status, current versions, limitations, and remaining release
acceptance work as needed. Include any required update in the release
candidate; if no change is needed, explicitly confirm that both feature lists
were reviewed and remain accurate.
- Never create or push a release tag before the user approves the exact release
commit and bilingual release notes.
- Never move, delete, recreate, or reuse a release tag.
- Keep desktop and Agent release paths, tags, artifacts, and Latest status
separate.
- Native GitHub Actions jobs are the cross-platform packaging authority. Never
run local Windows packaging or packaged-app probes unless the user explicitly
requests them.
- GitHub Actions is the release build and cross-platform packaging authority.
During release preparation, run local tests, type checks, and lint, but do
not run `npm run build`, `npm run build:bundle`, local packaging, or
packaged-app probes. Push the approved candidate commit first and require its
main-branch CI validation and production build to pass before tagging it,
unless the user explicitly requests an additional local build.
- LoongArch is a separate experimental preview. Build or publish it only when
the user explicitly requests it for that release.
- Never commit or publish credentials. Production publication uses the existing
@@ -256,14 +282,49 @@ npm run typecheck
npm run lint
```
Run `npm run build` for production build changes. Use `npm run portable` only
when a current Windows portable package is requested. The user has granted
standing authorization in this repository for bounded real text-model calls
needed to validate changed product paths; do not ask again for each such call.
Keep each validation call minimal, disable tools and attachments where
possible, and report the exact call count. This standing authorization does
not cover bulk, high-cost, destructive, publishing, messaging, purchasing, or
other consequential external actions.
Run `npm run build` for production build changes during development, but not
solely as a release-preparation step; release candidates use the main-branch CI
production build before tagging. Use `npm run portable` only when a current
Windows portable package is requested. The user has granted standing
authorization in this repository for bounded real text-model calls needed to
validate changed product paths; do not ask again for each such call. Keep each
validation call minimal, disable tools and attachments where possible, and
report the exact call count. This standing authorization does not cover bulk,
high-cost, destructive, publishing, messaging, purchasing, or other
consequential external actions.
### GoodBuddy Agent development validation
Every source change that can alter the deployed GoodBuddy Agent or the
desktop-to-Agent production path must be validated on the shared Linux x64 test
Host while the change is being developed. This includes Agent daemon and
protocol changes, packaging and installation, attach/update behavior, Runtime
launch and process ownership, workspaces, model bridges, and lifecycle or
recovery behavior. Unit tests, mocks, fixtures, CI, and waiting for release
validation are not substitutes for this development-time check.
- Reach the shared Host at `192.168.0.23` on the LAN when available, otherwise
use `10.7.0.23` over VPN. These are two routes to the same physical machine,
not two independent test targets or architecture results.
- Exercise the current source or package under development, not only the
previously published Agent. Run the affected production path as soon as it is
runnable and repeat the relevant scenario after the final change.
- Select the real-Host scenario from the changed surface: attach/bootstrap or
update for installation changes; Ask/Execute and a bounded real model request
for Runtime or model-bridge changes; and the applicable disconnect, restart,
cancellation, or reconnection sequence for lifecycle and recovery changes.
- Use the existing pinned Host identity and GoodBuddy credential storage. Never
add credentials to source, documentation, commands, logs, or test output, and
never hard-code these test addresses into product behavior or defaults.
- 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.
- 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.
The detailed scenario rules are documented in
[`docs/features/remote-host/technical-design.md`](./docs/features/remote-host/technical-design.md).
Before committing or pushing, inspect `git status`, `git diff`, and
`git diff --cached`. Do not commit secrets, local databases, logs, generated
+79 -44
View File
@@ -56,6 +56,14 @@ OpenCode/Continue 用例默认读取 `dist/harness-package-probe/win-unpacked`
调用。测试不会输出 API Key,文件操作在临时工作区中执行。文件包含经 Main 回环
broker 调用已分配自定义 MCP 的真实 OpenCode 和 Continue 用例。
不使用外部模型的 Runtime 原生交互回归位于
`src/main/agent/opencode-runtime-permissions.test.ts`、
`src/main/agent/continue-runtime-permissions.test.ts` 和
`src/main/agent/deepseek-harness-acp-e2e.test.ts`。它们用本地确定性模型驱动真实 Runtime
或固定 Host,覆盖工作区外访问、原生问答及 Ask 边界。OpenCode 用例默认使用开发缓存
二进制;可通过 `GOODBUDDY_TEST_OPENCODE_BINARY` 指定已经安装的候选二进制,避免把
仍在运行的旧缓存版本当作候选版本验证。这不替代当前源码 Agent 的 Linux Host 验收。
## 生产构建
生成 Electron Main、Preload 和 Renderer 生产文件:
@@ -132,6 +140,12 @@ npm run dist:linux:arm64
打包产物位于 `dist`。
所有桌面打包入口通过同一个 `electron-builder` 启动脚本运行。正常日志会隐藏
`duplicate dependency references` 信息摘要,因为它只表示同一已解析依赖被多条
生产依赖路径共享,并不要求构建操作者处理;调试日志仍会保留该摘要。Renderer 中
已由 Vite 打包的 `@antv/g6` 归类为构建期依赖,不会再作为独立 Node 模块复制进桌面
安装包。
以上 `dist*` 与 `portable` 命令用于普通本地桌面打包。它们会携带
`agent-runtime-lock.json`、`remote-runtime-lock.json` 与公开的
`agent-release-keys.json`,但所有桌面构建路径(包括本地 `portable`)都不会嵌入
@@ -139,7 +153,7 @@ npm run dist:linux:arm64
组件,缺少远端包也不能阻塞普通桌面打包或发布。
“设置 > 平台功能 > 远程项目(技术预览)”列出用户数据目录中已经下载或导入的
Linux x64/arm64 复合 Agent 包。首次打开和手动刷新会从“关于与更新”所选来源读取
Linux x64、Linux arm64 和 macOS arm64 复合 Agent 包。首次打开和手动刷新会从“关于与更新”所选来源读取
小型签名目录,显示本地版本、在线最新兼容版本以及 `有更新 / 已是最新 / 未下载 /
校验失败`;此检查不会下载 `.gbagent`。只有用户点击“下载”或“更新到”后才传输包,
用户也可导入或导出 `.gbagent` 离线包。
@@ -194,7 +208,7 @@ Agent/Runtime metadata 副本。操作中断或 adoption 失败后,下一次
package format v1 归档已经包含固定 Agent 和 Node,可直接用于 Host 直连;归档无需携带
`agent/lib/package-installer.cjs`,签名目录不提供也不检查额外的 bootstrap 能力元数据。
首次 bootstrap 不依赖已安装 Agent daemon。目标契约见
[SSH Host 远程环境准备与直连下载设计](docs/architecture/remote-host-environment-provisioning-design.md)。
[SSH Host 远程环境准备与直连下载设计](./docs/features/remote-host/environment-provisioning-technical-design.md)。
删除 Host 或远程项目属于纯本地管理操作。Host 确认框列出全部关联项目记录并级联清理
本地数据库,但不连接 Host、不检查远端目录,也不删除远端文件;Host 设置文件和项目事务
@@ -205,6 +219,8 @@ package format v1 归档已经包含固定 Agent 和 Node,可直接用于 Host
桌面包会携带经过版本与完整性校验的 OpenCode、Continue 和 DSH 插件安装 Runtime:
- OpenCode 平台二进制来自 `.runtime-resources/<arch>`。
- OpenCode 离线配置位于 `.runtime-resources/opencode-config`,构建时按已安装 `@opencode-ai/plugin/package.json` 的版本复用,不依赖根 lockfile 或自定义 ready/integrity 标记。缺失、不可读或版本不匹配时,在 staging 中执行精确版本 npm 安装,成功后替换缓存;安装失败保留旧缓存并清理 staging。本机共享配置同样比较随包插件与缓存插件的版本,版本不同时通过 staging 替换。
- 离线依赖安装禁用 npm bin links;远程 Runtime 组包排除不使用的 `.bin` 命令链接,并把复制后的普通依赖文件统一为 `0644`,OpenCode 入口仍为 `0755`。目录元数据、每个依赖文件及许可证继续纳入签名校验。
- Continue Runtime 来自锁定版本的 `@continuedev/cli`。
- DSH 插件安装使用精确锁定并从 `app.asar` 解包的 npm CLI,通过当前 Electron 的 Node 模式运行;最终用户不需要另装 Node.js 或 npm。
- DSH 图片输入使用精确锁定的 `@napi-rs/canvas` 完整解码 JPEG/PNG。通用包与目标平台、目标架构的 Skia 原生包必须从 `app.asar` 解包;当打包 Runner 的架构与目标架构不同时,发布脚本会根据 lockfile 的精确版本、下载地址和 integrity 临时暂存目标原生包,完成后清理。发布校验会检查版本、目标架构和 MIT 许可证。
@@ -212,6 +228,12 @@ package format v1 归档已经包含固定 Agent 和 Node,可直接用于 Host
跨架构打包前,确认目标架构的 OpenCode 资源已经准备完成。不要用其他架构的二进制替代目标资源。
GoodBuddy 管理的本机 OpenCode 启动后会轮询已认证的 loopback health 接口,最多等待
30 秒。该窗口用于兼容 Linux ARM 等较慢环境;不要改回依赖 stdout 文案或 10 秒固定上限。
聊天中的 Runtime 切换只执行路径和模型凭据就绪检查,不为状态显示启动临时 Server。
设置中的显式连接测试、原生能力清单读取和首次实际请求仍会启动 OpenCode,并继续使用
上述完整健康检查。
## GoodBuddy Agent 工件
面向用户发布的是按远端 Host 架构区分的复合 `.gbagent`,每个包同时包含签名
@@ -242,16 +264,32 @@ node build/agent-package.cjs build \
Agent 源码、共享协议、lock、bundle 工具和测试与桌面应用保持在同一仓库、同一 commit;
不维护长期分叉的 Agent 源码分支。`.github/workflows/agents.yml` 在相关 Pull Request、
`main` push 和手工触发时,分别使用 `ubuntu-24.04` 与 `ubuntu-24.04-arm` 原生构建
Linux x64/arm64 复合包。每个 job 从 `agent-runtime-lock.json` 解析官方 Node HTTPS
Linux x64/arm64 复合包,并使用 `macos-15` 原生构建 Darwin arm64
复合包。所有 job 从 `agent-runtime-lock.json` 解析官方 Node HTTPS
地址与 digest,并从 `remote-runtime-lock.json` 解析固定 OpenCode npm 包与 integrity;
下载后再次校验,随后使用仅存在于进程内的临时 Ed25519 测试 key 构建两次,完成内外层
测试签名验证、Agent 原生启动 smoke 和确定性 `.gbagent` 对比。
测试签名验证、Agent CLI 加载 smoke 和确定性 `.gbagent` 对比。所有目标还验证
Koffi 原生模块加载与 OpenCode `--version`;Linux job 继续运行 Agent 原生回归。
Darwin arm64 的 Node 和 OpenCode 固定输入分别由同一份
`agent-runtime-lock.json` 与 `remote-runtime-lock.json` 提供,不再维护 CI 专用副本。
组包器区分 ELF 与 Mach-O,Darwin CI 产物命名为
`goodbuddy-agent-<version>-darwin-arm64.gbagent`,不构建 Intel Mac 目标。
macOS Host 继续通过 SSH 按需启动 detached Agent,不注册 launchd 或开机服务。
进程身份使用 macOS 原生接口,Unix Socket 使用 `getpeereid`;Host 下载兼容系统自带
`shasum -a 256`,不要求安装 GNU coreutils。真实 Host 验证记录见
[远程主机技术设计](./docs/features/remote-host/technical-design.md)。
三个目标共用 `agent-catalog.json` 和现有签名格式,条目按平台与架构区分。旧版 Desktop
只接受 Linux catalog 条目;首次发布包含 Darwin 的累计 catalog 前必须先发布新版
Desktop,并在发布说明中要求用户升级 Desktop。旧版的在线 Agent 目录检查会失败,
本地已验证包不被删除。不得为此改写历史包或复用发布标签。
该验证 workflow 不读取 production signing secret,不修改公开 key registry,也不上传
可安装 Agent 工件。`.github/workflows/agent-release.yml` 是唯一 production Agent
发布路径:只接受指向受保护 `main` 历史的 annotated
`agent-v${agent-runtime-lock.agentVersion}` 标签,在 `agent-signing` Environment 中
原生构建两种架构,生成并签名累计目录,然后同步到非 Latest 的 GitHub Agent Release
原生构建 Linux x64、Linux arm64、macOS arm64 三个目标,生成并签名累计目录,然后同步到非 Latest 的 GitHub Agent Release
与北京 OSS。该 Environment 只保护一组 GoodBuddy 通用发布变量与 Secret:
- `GOODBUDDY_SIGNING_KEY_ID` /
@@ -272,7 +310,7 @@ OSS 先写 `agent-releases/v<version>/` 不可变对象,GitHub Release 公开
### 远程 OpenCode Runtime 基础
`remote-runtime-lock.json` 独立锁定首个远程 Runtime:OpenCode 1.18.9 的 Linux x64
`remote-runtime-lock.json` 独立锁定首个远程 Runtime:OpenCode 1.18.29 的 Linux x64
baseline 与 arm64 官方包 integrity、`bin/opencode` 入口和固定 `acp` 参数。它不同于
`agent-runtime-lock.json`,后者锁定 Agent 自带 Node 以及用于 Linux `SO_PEERCRED`
的 Koffi 版本。Agent bundle 将 Koffi loader 作为 external module,并只携带目标
@@ -296,10 +334,11 @@ integrity,以 `--ignore-scripts` 从官方 npm registry 获取归档;bundle
再次验证归档 SHA-512、包名、版本和 ELF 架构。
源码已经实现 Runtime bundle build/import/verify、Daemon 侧
manifest/Ed25519/payload/ELF/lock 校验、digest registry、ACP v3、直接进程 ownership、
Ask 的固定 `bwrap` 只读 profile 和 `runtime/model-bridge` v1。Agent 通过私有 Unix socket
按需 detached 启动,不依赖 systemd、D-Bus 或 Linger。Execute 直接以所选 SSH 账号权限
启动签名 Runtime;Ask 才使用只读 bubblewrap。
manifest/Ed25519/payload/ELF/lock 校验、digest registry、ACP v3、直接进程 ownership 和
`runtime/model-bridge` v1。Agent 通过私有 Unix socket 按需 detached 启动,不依赖
systemd、D-Bus、Linger 或 bubblewrap。Ask 与 Execute 都直接启动签名 Runtime;两者的
差异由 OpenCode Ask 权限配置和 Agent 工具权限分发边界共同执行,Execute 不注入该
权限覆盖。
OpenCode 通过已签名 Agent helper 和每次 Prompt 的私有 Unix socket 使用 Main-only 模型
网关;Provider URL、API Key 和真实 Provider 认证头不进入远端。helper 的 loopback
@@ -308,7 +347,7 @@ HTTP 入口使用每个进程随机生成的路径 capability,Anthropic/OpenAI
配置必须禁用 title Agent;工具循环只使用 build Agent 的模型轮次。
Ask 模式的 ACP 权限中介只可为原生 `read` 选择 `allow_once`;其他工具种类和
`allow_always` 请求必须拒绝,Workspace 的真正只读边界仍由 `bwrap` 执行。Unix 模型桥
`allow_always` 请求必须拒绝。Unix 模型桥
broker 必须按 socket 当前已缓冲字节增量读取长度帧,不能等待 `read(remaining)` 一次返回
完整大响应。Agent 传输或权限实现变化后,除了普通测试,还要在原生 Linux 上运行包含
至少 256 KiB 响应的模型桥回归。
@@ -369,6 +408,28 @@ ZIP,以及 Linux 的 AppImage、DEB 与 RPM。Linux 原生 Runner 必须安装
打包矩阵,避免随后推送版本标签时对同一提交重复完整打包。手动触发会运行
验证和六平台打包,并保留 30 天 Actions artifacts,但不会创建 Release。
### Desktop 发布顺序(强制)
Desktop 正式发布必须把候选分支推送与标签推送拆成两个阶段,不能在同一批操作中先后
推送 `main` 和标签:
1. 完成源码、版本、双语 Release Notes 与相关文档修改,运行 Release Notes 校验、
`npm test`、`npm run typecheck` 和 `npm run lint`。常规发布准备不在本机运行
production build、打包或安装包探针。
2. 提交候选,确认用户批准的准确 commit SHA 与双语 Release Notes,fetch 两个远端后,
将该候选分别推送到 `origin/main` 和 `github/main`。
3. 等待这个准确 SHA 的 `main` 分支 `Cross-platform packages` workflow 完成。只有
`Run validators` 和 `Build production bundle` 都成功,候选才具备打标签条件。
4. 如果 `main` workflow 失败,不得创建标签。修复必须形成新的 commit,重新完成本地
校验、双远端 `main` 推送,并等待新 SHA 的 `main` workflow 成功;旧 SHA 的成功结果
不能替代新 SHA 的验证。
5. `main` 成功后再次 fetch 两个远端,确认工作区干净、两个远端 `main` 都等于获批 SHA、
目标标签在本地和远端均未使用。此时才可在该 SHA 创建 annotated tag,并把标签分别
推送到两个远端,随后等待六平台打包和正式发布完成。
不可移动、删除、重建或复用失败的发布标签。完整审批、恢复和发布后检查以
[发布 Runbook](./docs/development/release-runbook.md) 为准。
推送 `v${package.version}` 标签时,工作流运行验证和六平台打包。只有在
全部目标成功后,才会严格校验并聚合所有平台产物,生成按平台重命名的
manifests、总 `release-manifest.json` 和 `SHA256SUMS`。随后工作流通过
@@ -395,6 +456,13 @@ git tag -a "$agent_tag" -m "GoodBuddy Agent $(node -p "require('./agent-runtime-
OSS 签名目录指针。生产发布需要 `agent-signing` 与 `aliyun-oss-release` 两个受保护
Environment;普通分支/PR 的 `agents.yml` 不使用其中任何 Secret。
Agent 标签也必须遵循与 Desktop 相同的 pre-tag 顺序。更新 Agent 生产源码或
`agentVersion` 后,先把候选提交推送到两个 `main`,等待该准确 SHA 的
`GoodBuddy Agents` main workflow 全部成功,再次 fetch 并确认两个远端 `main`、干净
工作区和未使用标签,最后才可创建和推送 annotated `agent-v<version>`。不得把 Agent
标签与候选 `main` 放在同一批推送中;任何修正 commit 都必须重新等待新 SHA 的 Agent
main workflow,旧 SHA 的成功结果不能复用。
中英文发布说明统一维护在 `resources/release-notes.json`。新版本按“本次
亮点 / Highlights”“功能更新 / Features”“问题修复 / Bug Fixes”“使用前
请留意 / Before You Start”四段组织,应用首次启动弹窗与 GitHub Release
@@ -505,36 +573,3 @@ npm test -- src/main/agent/deepseek-harness-acp-e2e.test.ts -t "rejects a real n
如需覆盖发布包内置 npm 路径,再设置 `GOODBUDDY_DSH_NPM_CLI` 与
`GOODBUDDY_DSH_NODE_EXECUTABLE` 指向已解包应用中的 npm CLI 和应用主程序。
## Renderer bundle 性能门禁
`electron-vite` 会在 `out/renderer/.vite` 生成 Vite manifest 和仅含构建模块
归属的 module manifest。`npm run build:bundle` 在 bundle 完成后自动运行
`build/check-renderer-bundle.cjs`,去重统计首屏同步闭包及 Knowledge、知识图谱、
Activity、Magic Notes、Settings 动态入口同步加载的 JS 与 CSS 合计 raw / gzip
大小并执行预算校验。
门禁同时验证以下结构约束:
- Knowledge、Activity 与 G6 不得进入首屏同步闭包。
- `KnowledgeGraphChart` 与 G6 不得进入 Knowledge shell 的同步闭包。
- G6 必须由知识图谱动态入口同步拥有。
路径遍历使用 manifest 中的相对文件名并通过 Node `path.resolve` 读取,因此兼容
Windows 与 POSIX 构建输出。构建专用 module manifest 只记录项目相对路径、
`node_modules/` 相对路径或稳定的虚拟模块名,不记录 Runner 的盘符、主目录或
绝对路径;检查成功后会删除该诊断文件,检查失败时保留以便排查。标准 Vite
manifest 会保留在输出中。预算以干净生产构建为基线并保留有限余量;若业务确需
提高预算,必须先检查 manifest 闭包和产物差异,不能只为通过 CI 调大数值。
Bundle 大小是发现意外依赖和持续膨胀的粗粒度工程护栏,不是用户体验指标,也不能替代
Electron 中的实际交互验证。Settings 的硬上限以 2026-08-23 验证构建
`648.78 kB raw / 97.79 kB gzip` 为基线,增加约 15% 评审余量后取整为
`750 kB raw / 115 kB gzip`。该上限用于拦截显著增长,不要求实现长期贴线运行。
调整 Renderer 拆包时遵守以下优先级:
1. 同级页签的交互一致性、首个绘制帧内反馈和输入保留优先于包体数字。
2. 不得仅为通过预算,把一个原本轻量的同级页签单独改成首次点击才加载。
3. 确需动态加载重型功能时,必须提供非空、无布局跳变的局部状态,并增加首次交互测试。
4. 修改预算必须记录干净生产构建基线、增量闭包差异和 Electron 实测结果。
+589 -84
View File
@@ -1,104 +1,609 @@
# GoodBuddy 功能矩阵与路线图
# GoodBuddy Feature Matrix and Roadmap
本文记录 GoodBuddy 的已提供能力和路线图。未完成项目不代表已包含在当前版本中。
**English** | [简体中文](./FEATURES.zh-CN.md)
## 状态说明
This document records the capabilities available in GoodBuddy and its roadmap.
Unchecked items are not included in the current release unless the item says
otherwise.
- [x] 已提供
- [ ] 开发中或规划中,具体以条目标注为准
## Status
## 功能总表
- [x] Available
- [ ] In development or planned, as described by the item
### 桌面基础、工作空间与上下文
## Feature Matrix
- [x] **跨平台桌面应用**:支持 Windows、macOS、Linux,以及 `x64`、`arm64` 发布目标。
- [x] **可配置全局快捷唤起**:在“平台功能 / 通用设置”中启停或录制 Electron accelerator;默认保留 `CommandOrControl+Shift+Space`,冲突或保存失败时继续使用上一组已注册快捷键,并显示可处理的状态。
- [x] **Projects、独立对话与会话分支**:按项目隔离上下文,管理会话、附件和 Git 工作区变更;本地会话可在稳定状态下复制当前聊天内容到独立分支,分支持续显示来源徽标且不复制 Task、队列或成果归属;项目选择器区分本地、托管 SSH 与远程消息通道项目,并显示对应目录或来源。
- [x] **文件、截图、窗口、剪贴板上下文**:用户明确选择后才加入模型上下文。
- [x] **富文本回答**:支持 GitHub Flavored Markdown、LaTeX 数学公式和受控 Mermaid 图表;大图可缩放、拖动或查看源码,失败时保留原始图表代码。
- [ ] **项目 Agent Space**(规划中):在 Project 中统一角色、知识、Skills/MCP、模型、审批策略、预算和超时,并支持模板复用。
- [ ] **通用助手工作栏与执行空间**(规划中):保留 Task Center 作为 Task 的单例索引,并把监督、Runtime、终端、进程、工作区、浏览器和成果作为始终可访问的应用级能力;除 Task Center 外的可绑定能力由用户选择跟随或固定目标,并逐步支持静态安全 HTML 预览、本机/SSH 执行空间和远程 Agent Runtime。附件与知识库继续由会话输入区管理,未来的记忆与历史执行上下文归入关联 Task。详见 [Feature PRD](./docs/prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md)。
### Desktop foundation, workspaces, and context
### Agent Runtime 与模型连接
- [x] **Cross-platform desktop application**: Supports Windows, macOS, and
Linux release targets on `x64` and `arm64`.
- [x] **Chinese hardware and operating-system coverage**: Standard Linux
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.7. 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
`CommandOrControl+Shift+Space`; if registration conflicts or saving fails,
GoodBuddy keeps the previously registered shortcuts and shows an actionable
status.
- [x] **Projects, independent conversations, and conversation branches**:
Isolates context by project and manages conversations, attachments, and Git
workspace changes. A stable local conversation can be copied into an
independent branch; the branch retains a source badge but does not copy
Tasks, queues, or artifact ownership. The project selector distinguishes
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] **File, screenshot, window, and clipboard context**: Added to model
context only after explicit user selection.
- [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
remain accessible through incremental loading.
- [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
reply finishes, with icon actions to reveal the source or open a full-screen
preview. The feature is on by default and can be disabled under Platform
Features / General; previews cannot run scripts, access the network, submit
forms, or open windows.
- [x] **AI response and full-conversation copy**: Completed AI responses expose
a bottom action that copies Markdown source without reasoning, tool logs, or
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.
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
restores only an ended tab description and never restarts the Shell. The
main sidebar and Magic Notes list support pointer and keyboard resizing;
the notes list can be hidden, remembers its layout, and stacks at narrow
widths.
- [ ] **Project Agent Space** (planned): Unifies roles, knowledge, Skills/MCP,
models, approval policy, budgets, and timeouts in a Project, with reusable
templates.
- [ ] **Application navigation and system-tool shortcuts** (design only):
Plans fixed system entries, configurable built-in application visibility,
and footer shortcuts into the existing workbar. These are not implemented
interface changes. See the [design](./docs/features/application-tool-navigation/README.md).
- [ ] **Additional assistant workbar and execution-space capabilities**
(planned): Builds on the current workbar and multiple terminals with
supervision, unified Runtime monitoring, managed processes, safe static HTML
preview, target-pinnable workspace/browser/artifact instances, bottom
docking, and separate windows. Task Center remains the singleton Task index;
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).
- [x] **直连模型 Runtime**:支持问答、知识总结、受控工具执行和图像生成。
- [x] **OpenCode 与 Continue**:使用隔离子进程、环境变量白名单、统一配置、取消、总执行时限、有界流式输出和活动记录;共享进程回收逻辑保留 Windows 完整进程树终止,并对采用独立进程组的 POSIX 子进程执行组回收。交互提问只由前台对话回答,定时任务、远程通道和委派等后台执行遇到提问时会立即失败并提示改为前台运行,避免无限等待。
- [x] **托管 SSH OpenCode 闭环(技术预览)**:该能力由“设置 > 平台功能”中的独立“远程项目(技术预览)”页签控制并默认关闭;关闭时不影响任何本地项目、普通桌面功能或桌面发布。启用后可管理固定 Host Key 的 SSH Host、浏览有界远端目录并创建 Ask/Execute 项目;Ask 在 Runtime 边界通过 bubblewrap 保持 Workspace 只读,Execute 使用所选 SSH 账号的完整权限。Main 通过签名 Agent、私有 Unix socket、ACP v3、持久恢复 cursor 和 Main-only 模型桥维持长任务、重连与会话恢复,模型凭据不进入远端。远端组件不嵌入桌面包,项目切换只更新本地选择且不会连接或下载;设置页只读取小型签名目录并按 Linux x64/arm64 显示本地版本、在线最新兼容版本和“有更新/已是最新”,用户点击后才下载签名 `.gbagent`,也可导入/导出离线包。每个复合包包含 Agent、固定 Node 与由桌面源码统一维护适配的 OpenCode Runtime;在线来源跟随“关于与更新”的 GitHub/北京 OSS 选择,签名累计目录绑定最低桌面版本、Agent 协议、架构、大小、SHA-256 和固定 URL。公钥 registry 接受等价 JSON 空白和换行,但仍严格校验 schema、Ed25519 key、环境与撤销;目录、包、manifest、payload 签名和流式 SHA-256 不变。缺少某架构包只使该架构托管 SSH 不可用。项目只保存 Host、远端路径、Runtime 选择和模式;首次实际使用 Workspace/Runtime 时读取 Host current identity,同一进程内其他项目复用已确认 identity 和 Agent 连接。托管 SSH 会话只显示支持的 OpenCode 选择。同一 Host 可并行运行多个项目和对话;活动请求沿精确 resume/replay 恢复,controller 换代遗留的空闲 binding 只在 takeover 已证明且没有活动请求时换新 channel epoch 并恢复原 ACP session。Windows 到 Linux x64 的安装、provider-free 模型桥、断线重放、Session 续接和零残留清理已完成真实 Host 验证;签名 Agent 0.11.2-e2e.12 与 OpenCode Runtime 1.18.9 已完成一次只提交一遍、只调用一个 Ask 原生 read、无标题轮次且精确返回证据内容的有界真实模型验收,当前源码 lock 已更新为 Agent 0.11.9。
- [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/architecture/remote-host-environment-provisioning-design.md)。
- [ ] **SSH Host 环境准备真实 Host 验收**:源码已可使用当前 format v1 包;仍需完成 GitHub、北京镜像、Linux x64/arm64、取消和离线 GoodBuddy 传输的真实 Host 验收。当前状态不表示已发布或已完成真实 Host 测试。
- [x] **DeepSeek Harness(预览)**:使用 GoodBuddy 固定 Host 和 OpenAI 兼容模型连接;Ask 只允许调用 Host 中真实注册的 `read`、`skill` 以及 Main 管理的 Web Search/Fetch 代理,拒绝插件同名冒充,Execute 放行全部已启用内置及插件工具,并以当前用户权限运行。图像输入跟随所选模型连接的能力声明,文本模型在 Host 或模型调用前拒绝图片,图片模型通过有界内联内容和临时 Attachment Store 接收 JPEG/PNG。
- [x] **DSH npm 插件市场**:市场默认关闭,由用户显式开启后搜索公共 npm 的 `dsh-plugin` 包,使用捆绑 npm 执行精确版本安装和普通 lifecycle scripts,并支持启停、JSON 配置、移除、失败启动自动停用和离线管理已安装插件;关闭市场只隐藏目录与管理界面,不改变已有插件的启停状态,第三方代码不受 Ask 初始化隔离。
- [x] **Ask 与 Execute 工作模式**:Ask 保持只读;Execute 是用户对当前本机或 SSH 账号可用工具、进程、网络和可写路径的完整授权。
- [x] **专家与 Subagent**:支持显式专家、团队分析和最多三个只读专家并行分析;聊天先展示可逐项展开的专家完整输出,再在其下展示总 Agent 的综合结果,并随会话保存。
- [x] **角色绑定模型连接**:每个角色可继承默认模型或选择独立文本模型连接,失效连接安全回退默认模型,综合角色始终继承默认模型。
- [x] **多协议模型配置**:支持 Anthropic Messages、OpenAI Responses、OpenAI Chat Completions、OpenAI Images 和无认证本机模型;新用户默认连接为本机 Ollama 兼容地址,不预置第三方云模型服务。升级时只替换从未配置凭据且仍完全等于旧内置值的历史默认连接,用户显式保存或加密凭据的连接保持不变;已有部署仅使用兼容环境变量提供凭据时继续沿用对应的历史连接参数,通用模型环境变量仍优先。“保存并测试模型”会发送有界的真实文本或图片生成请求并校验生成结果,而不是只检查 HTTP 连通性,因此可能产生少量服务商用量费用。
- [x] **上下文用量与自动压缩**:直连模型按每次成功调用更新供应商用量,图片与工具轮次使用同一口径,供应商缺失 usage 时才回退估算;界面明确区分“本次模型调用”和“压缩后对话估算”,压缩线始终根据当前设置与所选模型窗口即时计算,不在每个对话中保存旧配置;压缩标识的前后值使用同一估算口径,运行记录仍保留各次模型调用的供应商 usage。对话与多轮工具 Agent 可在已完成调用越过阈值后自动重复压缩,规划时先为固定提示、工具定义和摘要预留预算;同一回复会分别保留 Agent 工具上下文与对话历史的压缩标识,并在应用重启或较早消息滚出本地历史窗口后继续复用摘要。
- [x] **Main-only 凭据保护**:API Key 使用系统安全存储加密,不暴露给 Renderer。密钥随对应模型连接保存,修改服务地址或临时切换为无需认证不会要求重新输入;只有用户显式清除凭据或删除连接时才移除。
- [x] **OpenCode Runtime 定制**:GoodBuddy 管理的内置 OpenCode 可发现原生 Agents、Tools、Commands、LSP、Formatters、MCP、Skills、Prompts 与 Resources;Tools 单独显示读取、文件修改、命令、网络、Agent 编排等类型、来源及 Ask/Execute 可用性,并隐藏 OpenCode 内部 `invalid` 与 GoodBuddy 临时 MCP 工具。支持保存默认 Agent、每次请求覆盖 Agent、通过原生 SDK 执行 Command、显示上下文用量并调用有总时限的原生 Compact;并发外部 Server 对话的提问使用请求级公开 ID 映射,回答不会串到其他会话。外部 OpenCode Server 只报告连接状态,不宣称原生清单可读。任意插件安装、Session Share、自动 Worktree 和 OpenCode 原生会话持久化仍不开放。
- [x] **Continue Runtime 定制**:提供静态配置中的原生 Rules、Prompt 模板与 MCP 清单,以及可编辑的 GoodBuddy Rules/Prompt 配置预设;聊天可按请求选择预设和填入可继续编辑的 Prompt。当前 Continue Host 没有可信的静态原生 Tool 发现接口,且使用隔离的 `CONTINUE_GLOBAL_DIR`,因此界面明确标记 Tools 不支持静态发现,也不把 Host 实际不会加载的工作区或用户 Skills 冒充原生能力;GoodBuddy 分配的 Skills 仍按请求暂存执行。Continue 临时 Host 不复用原生会话压缩,手动压缩由 GoodBuddy 摘要模型完成并验证持久化摘要覆盖范围;Agent 交互提问转换为统一问答卡片。Resources、Hooks、后台 Job 和 Continue 原生会话管理继续暂缓。
- [x] **Runtime 原生清单语义**:原生能力以 Agents、Tools、Commands、Skills、MCP、Rules、Prompts、Resources、LSP、Formatters 和上下文 11 个页签展示;清单状态独立于 Runtime 连通性,区分完整、部分、不可用、仅连接和不支持。DeepSeek Harness 通过 Host Registry 枚举有界的内置/插件 Tools 与 Skills,显示真实 Ask/Execute 边界,并排除 GoodBuddy 按请求分配的 Skills、Web/MCP 代理。
- [ ] **Runtime 监督栏目**(规划中):在应用级助手工作栏的固定 Runtime 栏目统一承载 OpenCode、Continue 和 DeepSeek Harness 的 Task 级委派、后台执行、Workflow/Hook、长任务与原生会话监督;用户只选择 Conversation 或 Task,Job/Run 保持内部,不形成树或独立操作对象。
- [ ] **可执行 Subagent**(规划中):提供显式 Execute 委派,限制嵌套、并行、Token、时间和工具权限,在助手工作栏固定的 Runtime 栏目按 Task 聚合状态、取消入口和审计归属。
### Agent Runtimes and model connections
### Skills、MCP 与知识库
- [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.
- [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,
and capability scope. OpenCode, Continue, DeepSeek Harness, and managed SSH
do not receive duplicate copies of these tools. The Windows local command
path and a real-model edit, test, fix, and review loop have passed; native
macOS and Linux command validation remains. Execute commands can select
absolute, relative, or symbolic-link directories outside the workspace;
the workspace stays the default and relative-path base. Ask remains read-only.
- [x] **OpenCode and Continue**: Use isolated child processes, an environment
variable allowlist, unified configuration, cancellation, total execution
limits, bounded streaming output, and activity records. Shared process
cleanup preserves complete Windows process-tree termination and terminates
POSIX process groups when a child uses an independent process group. Chat
status checks use a one-shot Runtime probe that is immediately cleaned up
rather than entering the model-and-project execution cache. For managed
local OpenCode, this passive check validates the selected path and model
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
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
requests in the same conversation ordered, while different conversations in
the same project and different projects can run in parallel. Request-scoped
dynamic MCP tools remain isolated through default wildcard disablement and
explicit current-request enablement. OpenCode and Continue runs do not stop
at a fixed tool-call or activity count, and every observed tool and Subagent
activity remains in the local conversation instead of being discarded while
the native task continues.
Interactive questions are answered only by foreground conversations; scheduled tasks, remote channels,
delegated work, and other background runs fail immediately with guidance to
rerun in the foreground
instead of waiting indefinitely. Local OpenCode startup uses the selected
absolute path directly without checking the file or running `--version`
first; invalid paths fail at actual launch. On managed Linux ARM Hosts,
Runtime activation reuses the registry and manifest verified during setup
instead of rehashing or rechecking the complete OpenCode binary.
- [x] **Managed SSH OpenCode loop (technical preview)**: Controlled by the
separate Remote Projects (Technical Preview) tab under Settings / Platform
Features and disabled by default. Disabling it does not affect local
projects, ordinary desktop capabilities, or desktop releases. When enabled,
users can manage SSH Hosts with pinned Host Keys, browse bounded remote
directories, and create Ask or Execute projects. Both modes start the
signed Runtime directly without bubblewrap. Ask applies OpenCode's Ask
permission configuration and permits only native read approvals at the
Agent tool-dispatch boundary, while Execute explicitly allows native permissions
and retains all permissions of the selected SSH account. The Agent owns accepted Prompts,
provider/tool rounds, Runtime processes, a stable model ledger, and a bounded
semantic transcript over a private Unix socket and ACP v5. Work continues on
the Host after Desktop exit, network loss, or local-process termination.
Reconnection attaches only to the original controller, binding, and
operation, then atomically merges provenance into the original conversation
without resending the Prompt. Model profiles and API keys enter Agent memory
only for the accepted operation and are not written to the Renderer, SSH
arguments, remote environment, or disk. Remote components are not embedded
in the desktop package; switching projects updates only local selection and
does not connect or download. Settings reads a small signed catalog and
reports the local version, latest compatible online version, and update
state for Linux x64, Linux arm64, and macOS arm64. Intel Macs are not supported.
Downloads occur only after a user action, and
packages can also be imported or exported offline. Each compound `.gbagent`
package contains the Agent, fixed Node, and the compatible OpenCode Runtime
maintained by the desktop source. Online sources follow the GitHub/Beijing
OSS choice in About and Updates. The cumulative signed catalog binds minimum
Desktop version, Agent protocol, platform, architecture, size, SHA-256, and fixed URL.
All three targets share one catalog. Users must upgrade Desktop before the
first mixed-platform catalog is published because older Linux-only readers
reject catalogs containing Darwin entries.
The public-key registry accepts equivalent JSON whitespace and line endings
while still strictly validating schema, Ed25519 keys, environment, and
revocation; catalog, package, manifest, payload signatures, and streaming
SHA-256 remain unchanged. A missing architecture package disables managed
SSH only for that architecture. Projects store only Host, remote path,
Runtime selection, and mode. Current Host identity is read when a
Workspace/Runtime is first used, then reused by other projects in the same
process. Managed SSH conversations expose only supported OpenCode choices.
Multiple projects and conversations can run concurrently on one Host;
Agent-owned prompts are not stopped at a fixed model-call count or
prompt-wide output-token total, and Runtime output uses transport
backpressure instead of cancellation when GoodBuddy buffers fill;
recovery separately reports network, Agent, Runtime, committed-event cursor,
completion, or failure/retry and blocks only the affected project. A real
Linux x64 Host has covered short and long detach, forced local harness exit,
concurrency, SSH relay loss, cancellation, definite and uncertain provider
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.21`, while the current Desktop release candidate is `0.12.7`; formal
publication status follows the separate Agent and Desktop
release channels. Current macOS source has passed 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.
- [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
never installs automatically. A Host card has one primary action based on
version facts, Install Remote Environment, Update Remote Environment, or
Reinstall, plus a secondary SegmentedControl for Auto, Host Download, or
GoodBuddy Transfer. The selection defaults to Auto and is not persisted; a
Version Matched badge does not mean the environment is healthy. Auto probes
only before operation preparation and chooses one acquisition route.
Explicit choices remain in effect, and prepare, commit, or adoption failures
do not silently fall back across acquisition routes. Both routes deliver the
same signed compound `.gbagent` into fixed staging and share control-plane
prepare, commit, Agent activation/health, Runtime activation, finalize, and
explicit cleanup. The GoodBuddy route can download and validate a missing
candidate during the same operation, cache it with a lease, and stream one
archive plus its verified bootstrap Node over bounded SFTP without loading
the roughly 294 MiB package into a Main-process `Buffer`; the Host performs
one complete payload verification while extracting. An unfinished operation
stores only the operation ID needed to clean staging. A later update makes a
best-effort cleanup and starts prepare again, without persisting a remote
metadata copy or allowing cleanup failure to block a new update or roll back
a healthy environment. Existing projects resolve current Host identity on
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
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,
Linux x64/arm64, cancellation, and offline GoodBuddy transfer remains.
Candidate CI/native packaging and system sleep/wake are not yet verified;
this partial development evidence does not imply publication.
- [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
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.
- [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
supports enable/disable, JSON configuration, removal, automatic disablement
after startup failure, and offline management of installed plugins.
Disabling the marketplace hides only the catalog and management interface;
it does not change the enabled state of installed plugins, and third-party
code is not subject to Ask initialization isolation.
- [x] **Ask and Execute work modes**: Ask remains read-only. Execute is the
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] **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.
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
up to three experts running in parallel. Experts inherit the parent
Ask/Execute mode and can use enabled local direct-model tools; Ask remains
read-only. They are not remote OpenCode child sessions. Chat shows each
expandable full expert response first and the main Agent's synthesis below,
and persists both with the conversation.
- [x] **OpenCode child progress and final results**: Child cards show ordered
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] **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.
- [x] **Multiple model protocols**: Supports Anthropic Messages, OpenAI
Responses, OpenAI Chat Completions, OpenAI Images, and unauthenticated local
models. New users receive a local Ollama-compatible default and no
preconfigured third-party cloud provider. During upgrades, GoodBuddy
replaces a historical default only when it has never received credentials
and still exactly matches the old built-in value; explicitly saved
connections and encrypted credentials remain unchanged. Existing
deployments that supply credentials only through compatible environment
variables retain their historical connection parameters, while generic
model environment variables still take precedence. Save and Test Model sends
a bounded real text or image-generation request and validates the generated
result instead of testing HTTP reachability alone, so it may incur a small
provider usage charge.
- [x] **Custom model request fields**: Each LLM connection accepts bounded
JSON objects for additional request headers and top-level body fields.
Direct requests and Continue support both; local OpenCode and DeepSeek
Harness use only their natively supported headers, while the managed SSH
OpenCode model gateway supports both. Runtime, protocol, and authentication
fields take precedence, and these ordinary connection settings must not be
used to store API keys or other secrets. See
[model request customization](./docs/features/model-connections/README.md).
- [x] **Context usage and automatic compaction**: Direct models update usage
from each successful provider call. Images and tool rounds use the same
accounting, with estimation only when the provider omits usage. The UI
distinguishes This Model Call from Post-Compaction Conversation Estimate,
and the compaction line is recalculated from current settings and the
selected model window rather than storing old configuration per
conversation. Before/after compaction markers use one estimation method,
while activity history retains provider usage for each call. Conversations
and multi-round tool Agents can compact repeatedly after a completed call
crosses the threshold, reserving budget first for fixed prompts, tool
definitions, and summaries. One response separately preserves compaction
markers for Agent tool context and conversation history, and summaries remain
reusable after app restart or when older messages leave the local history
window.
- [x] **Persistent credential protection**: Main encrypts API keys with secure
system storage and never exposes them to the Renderer. A managed SSH accepted
Prompt places the current profile and key in Agent memory only, never in SSH
arguments, the remote environment, or disk; other paths keep credentials
inside Main. Credentials remain attached to their model connection when the
service URL changes or authentication is temporarily disabled, and are
removed only when the user explicitly clears them or deletes the connection.
- [x] **Bounded failure diagnostics**: Desktop stores a fixed allowlist of
startup, Runtime, and remote-connection failure stages in the user-data
directory, rotating at four 256 KiB files. Each GoodBuddy Agent rotates
daemon, connection, recovery, and Runtime lifecycle diagnostics at three
64 KiB files in its private installation state and exposes them through the
fixed `diagnostics --installation-id` command. Both sides store only
allowlisted stages, stable error codes/types, and fixed short messages, never
Prompts, credentials, file contents, paths, environments, SSH arguments, or
raw provider responses. Diagnostic-write failure does not alter normal
operation.
- [x] **OpenCode Runtime customization**: GoodBuddy-managed OpenCode can
discover native Agents, Tools, Commands, LSPs, Formatters, MCP, Skills,
Prompts, and Resources. Tools show read, file-edit, command, network, Agent
orchestration, and other types, source, and Ask/Execute availability while
hiding OpenCode's internal `invalid` tool and temporary GoodBuddy MCP tools.
Users can save a default Agent, override it per request, run Commands through
the native SDK, view context usage, and invoke native Compact with a total
timeout. Questions from concurrent external Server conversations are mapped
by public request ID so answers cannot cross conversations. External
OpenCode Servers report connection state only and do not claim native
catalog access. Arbitrary plugin installation, Session Share, automatic
Worktree, and OpenCode native-session persistence remain unavailable.
- [x] **Continue Runtime customization**: Exposes native Rules, prompt
templates, and MCP from static configuration plus editable GoodBuddy Rules
and Prompt presets. Chat can select a preset per request and insert an
editable Prompt. The current Continue Host has no trustworthy static native
Tool discovery API and runs with an isolated `CONTINUE_GLOBAL_DIR`, so the UI
explicitly marks static Tool discovery unsupported and does not present
workspace or user Skills that the Host will not load as native capabilities.
GoodBuddy-assigned Skills still execute through per-request staging. The
temporary Continue Host does not reuse native session compaction; manual
compaction uses the GoodBuddy summary model and verifies persisted summary
coverage. Agent questions become unified question cards. Resources, Hooks,
background Jobs, and Continue native-session management remain deferred.
- [x] **Native Runtime catalog semantics**: Native capabilities are displayed
across 11 tabs: Agents, Tools, Commands, Skills, MCP, Rules, Prompts,
Resources, LSP, Formatters, and Context. Catalog state is independent from
Runtime connectivity and distinguishes complete, partial, unavailable,
connection-only, and unsupported. DeepSeek Harness enumerates bounded
built-in/plugin Tools and Skills through the Host Registry, reports real
Ask/Execute boundaries, and excludes per-request GoodBuddy Skills and
Web/MCP proxies.
- [ ] **Runtime supervision section** (planned): A fixed section in the
application-level assistant workbar for Task-level delegation, background
execution, Workflows/Hooks, long-running work, and native-session
supervision across OpenCode, Continue, and DeepSeek Harness. Users select
only Conversation or Task; Job/Run remains internal rather than becoming a
tree or independent object.
- [ ] **Advanced Subagent supervision** (planned): Task-level aggregation in
the workbar's fixed Runtime section, configurable nesting, parallelism,
budgets, and lifecycle controls. Basic expert-mode inheritance and
single-level direct-model programming delegation are already available.
- [x] **Skills 按需接入**:可分配给直连模型、OpenCode、Continue 和 DeepSeek Harness,并使用有界资源和受控 Runtime 边界。
- [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 Prompts 与 Resources 元数据**:MCP 测试仅在 Server 声明对应能力时发现有界的 Prompt、参数与 Resource 元数据,不读取 Resource 内容;Runtime 支持的 Prompt 可填入聊天草稿后继续编辑。OpenCode 可报告实验性 Resource 清单,Continue 当前版本明确不支持 Resources。
- [x] **本地知识库**:支持文件、目录和网页导入、SQLite FTS5 检索及来源追溯。
- [x] **知识图谱**:支持规则、模型和混合抽取,以及实体、关系、别名和证据维护。
- [x] **向量模型配置与检索**:可配置兼容 Embeddings 接口并用于语义检索。
- [x] **向量诊断与索引任务**:提供真实向量生成诊断、按文档重建进度、取消、失败状态与重启后结果恢复;每篇成功文档立即可用于检索。
- [x] **混合检索测试台**:支持全文、中文词组、向量和图谱通道诊断,可调 Top K、阈值、权重、本地或学习型重排及上下文预算。
- [x] **分块、维护与评估**:支持固定、结构化和父子分块,分块维护、可取消重建及双语检索评估。
- [x] **受控知识本体**:每个知识库可定义实体、关系、别名和端点约束,保留证据偏移、置信度和抽取来源,并显式提示图谱重建。
- [x] **强制检索与引用上下文**:对话可按需或每次先检索,显示零结果、降级、失败与取消状态,并可查看引用上下文或安全打开来源。
- [x] **魔法笔记 / Magic Notes**:提供本地优先的笔记与待办工作台、范围管理、编辑、筛选和受控 AI 评论;左侧入口可按设置显示未完成待办数量,创建、保存和评论结果使用统一应用通知。
- [ ] **MCP Server Control Plane**(规划中):统一 MCP 生命周期、健康检查、重连、Schema 缓存、隔离、审批和审计。
- [ ] **可追溯笔记摘录与 AI 编辑**(规划中):从对话、知识和网页收集带来源的摘录,并提供需确认的总结、改写和整理操作。
### Skills, MCP, and knowledge
### 工作管理、长期协作与工作流
- [x] **On-demand Skills**: Assignable to direct models, OpenCode, Continue,
and DeepSeek Harness with bounded resources and controlled Runtime boundaries.
- [x] **Local tool-execution environment source path**: Under Capabilities and
Tools / Tool Environment, users can select GoodBuddy-managed Node.js,
on-demand managed Python, or a genuinely validated custom interpreter for
local Skills and stdio MCP. The page provides an independent Native/OSS
source choice, diagnostics, installation progress, cancellation, and
removal. New local Runtimes and stdio MCP processes receive immutable PATH
snapshots without changing ordinary terminals, the system environment, or
remote Hosts. Managed Node and native-source Python have passed real
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
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
a native target-architecture Runner and performs a real managed-Python
install, SSL, pip, and venv probe before packaging. The immutable `v0.12.0`
attempt passed Windows and macOS but exposed Linux TAR case handling before
publication. The immutable `v0.12.1` attempt was cancelled before native
packaging while synchronizing the Agent release. Published `v0.12.2`
subsequently passed all six native package jobs and their managed-Python
install probes. Real Skill/MCP execution, custom interpreter execution, and
coordination with affected running processes remain
acceptance work. Six-platform release acceptance must not be claimed before
the applicable checks pass.
- [x] **On-demand built-in MCP**: Knowledge, Magic Notes, GoodBuddy
configuration, and built-in browser MCP can be enabled independently and assigned to direct
models, GoodBuddy-managed OpenCode, and Continue. Settings explicitly marks
DeepSeek Harness unsupported. Built-in MCP uses short-lived local authority
for the current request, and user configuration cannot loosen Ask/Execute
read/write boundaries.
- [x] **MCP Tools**: Explicitly enabled custom MCP can be assigned to direct
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.
- [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
Prompts can be inserted into the chat draft for further editing. OpenCode can
report an experimental Resource catalog; the current Continue version
explicitly does not support Resources.
- [x] **Local knowledge bases**: Supports file, directory, and web imports,
SQLite FTS5 retrieval, and source tracing. Knowledge selection persists per
conversation and starts empty for new conversations. Advanced creation and
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
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).
- [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
Embeddings endpoints and uses them for semantic retrieval.
- [x] **Embedding diagnostics and indexing jobs**: Provides real embedding
diagnostics, per-document rebuild progress, cancellation, failure state, and
result recovery after restart. Each successful document becomes immediately
searchable.
- [x] **Hybrid retrieval workbench**: Diagnoses full-text, Chinese phrase,
vector, and graph channels with configurable Top K, thresholds, weights,
local or learned reranking, and context budgets.
- [x] **Chunking, maintenance, and evaluation**: Supports fixed, structured,
and parent-child chunking, chunk maintenance, cancellable rebuilds, and
bilingual retrieval evaluation.
- [x] **Controlled knowledge ontology**: Each knowledge base can define
entities, relationships, aliases, and endpoint constraints while retaining
evidence offsets, confidence, and extraction source, with explicit guidance
to rebuild the graph.
- [x] **Required retrieval and cited context**: Conversations can retrieve on
demand or before every response, show zero-result, degraded, failed, and
cancelled states, and expose cited context or safely open its source.
- [x] **Magic Notes**: A local-first notes and todo workbench with scope
management, editing, filtering, and controlled AI comments. The left
navigation can show the incomplete-todo count, and create, save, and comment
results use application-wide notifications.
- [ ] **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
excerpts from conversations, knowledge, and the web and provides
confirmation-based summarization, rewriting, and organization.
- [x] **任务、活动与成果**:集中管理任务状态、审计活动和独立成果文件;普通聊天回复只保留在会话中,不再自动复制到成果栏,已有重复聊天 Markdown 从成果列表隐藏但不物理删除。Token 用量按 Runtime 与模型归类,并针对 OpenAI 兼容与 Anthropic Messages 的不同上报口径归一化展示缓存命中率;活动按会话分组并默认收起,避免长历史占满页面。
- [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/prd/task-and-job/task-center-prd.md) 和 [Scheduled Task PRD](./docs/prd/task-and-job/scheduled-task-prd.md)。
- [x] **记忆与智能心跳**:当前提供周期回顾、建议记忆、洞察、后续任务和可审计运行轨迹。
- [x] **智能心跳入口与范围改善**:将“智能心跳 > 心跳计划”作为完整配置的唯一权威入口,支持创建和编辑 Global 或指定一个、多个 Project 的计划;旧单项目配置无损迁移,项目级记忆与行动输出必须显式指定范围内的 Project。Task Center 和设置不再复制心跳表单。“未来分区记忆”仍只是尚待独立设计的长期方向。详见 [智能心跳 PRD](./docs/prd/smart-heartbeat/smart-heartbeat-prd.md)。
- [ ] **通用监督**(规划中):通过固定监督栏目观察用户选择的会话、任务、自动化或实验对象,提供带证据的评论与人工介入请求,但不自动发言、批准工具或切换 Execute。详见 [会话监督 PRD](./docs/prd/supervision/conversation-supervision-prd.md)。
- [ ] **批量运行与对比实验室**(规划中):对模型、Prompt、角色和工作流配置执行批量对比,汇总质量、耗时、Token、费用、失败率和成果差异。
- [ ] **时态记忆与事实冲突检测**(规划中):为记忆和知识图谱增加有效期、当前事实、过期与矛盾检测、事实核验及证据回溯。
- [ ] **可视化受控工作流**(规划中):提供版本化 DAG、条件分支、审批、取消和恢复,执行节点继续经过 Main Runtime 边界。
- [ ] **统一 Run Graph 与回放**(规划中):关联任务、Subagent、模型、知识、工具审批、用量和成果,支持失败定位、重试和脱敏导出。
### Work management, long-term collaboration, and workflows
### 浏览器、通信、语音与应用维护
- [x] **Tasks, activity, and artifacts**: Centrally manages task state, audit
activity, and independent artifact files. Ordinary chat responses remain
only in the conversation and are no longer copied into Artifacts; existing
duplicate chat Markdown is hidden from the artifact list but not physically
deleted. Run history is stored in Main SQLite without the previous 500-item,
4,000-character, or 2 MB Renderer limits; the page renders it in batches,
and a persistent warning remains when a migrated legacy cache may already
have lost older records or detail. Token usage is grouped by Runtime and
model and normalizes the different OpenAI-compatible and Anthropic Messages
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] **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
button; children share status dots, the parent does not repeat task badges,
and the UI stops at Task rather than exposing Job/Run levels. A new custom
Task can use the current or a new Conversation, defaults to Execute, and
preserves Runtime, tool, and approval boundaries. Repeated triggers reuse the
same Task; text returns to the Conversation, while independent files and
images remain artifacts. Ordinary messages and due Scheduled Tasks share a
persistent Conversation queue that runs one item at a time. Users can keep
sending while a response is active; queued items continue in order and can
be removed or inserted immediately by interrupting the current item. Task
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
[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
memories, insights, follow-up tasks, and auditable run history.
- [x] **Improved Smart Heartbeat entry and scope**: Smart Heartbeat / Heartbeat
Plans is the sole authoritative full-configuration entry. Plans can be
created and edited for Global or one or more selected Projects. Legacy
single-project settings migrate without loss, and project-level memory and
action output must explicitly target a Project in scope. Task Center and
Settings no longer duplicate the form. Partition-aware review, candidate
generation, and recall triggers remain to be designed. See the
[Smart Heartbeat PRD](./docs/features/smart-heartbeat/prd.md).
- [ ] **General supervision** (planned): Uses a fixed supervision section to
observe user-selected conversations, Tasks, automations, or experiments and
provide evidence-backed comments and requests for human intervention,
without automatically speaking, approving tools, or switching to Execute.
See the
[Conversation Supervision PRD](./docs/features/conversation-supervision/prd.md).
- [ ] **Batch runs and comparison lab** (planned): Compares model, Prompt,
role, and workflow configurations in batches and summarizes quality,
duration, tokens, cost, failure rate, and artifact differences.
- [ ] **Temporal memory and fact-conflict detection** (planned): Adds validity
periods, current/expired/conflicting fact detection, fact checking, and
evidence tracing to memory and the knowledge graph.
- [ ] **Visual controlled workflows** (planned): Versioned DAGs, conditional
branches, approvals, cancellation, and recovery, with execution still
passing through Main Runtime boundaries.
- [ ] **Unified Run Graph and replay** (planned): Connects Tasks, Subagents,
models, knowledge, tool approvals, usage, and artifacts for failure
diagnosis, retry, and redacted export.
- [x] **直连模型内置浏览器**:使用 GoodBuddy 内置的隔离 Chromium,不控制客户端已安装的浏览器;用户通过独立总开关决定是否提供给 Execute,开启后不逐次询问。
- [x] **客户端电脑控制工具**:与内置浏览器分开管理,并保留范围、取消、超时、输出边界和执行记录。
- [x] **远程消息通道项目**:微信 ClawBot、企业微信和钉钉分别拥有系统管理的项目、独立远程会话、工作目录、处理后端、默认 Ask/Execute 模式及任务活动归属;完整回复交由各通道按平台能力控制长度与分段,不再由公共服务统一截断。
- [x] **微信 ClawBot 扫码与媒体**:通过独立 Sidecar 完成本机扫码、验证码、加密凭据和文字收发;支持个人微信私聊图片与文件,单条消息最多 4 个附件、解密后合计不超过 12MB。
- [x] **微信安全回传**:支持返回当前任务生成的图片,或在用户明确要求时将本次最终文本生成为 Markdown 附件;不自动读取或发送已有工作区文件。
- [x] **企业微信与钉钉连接**:支持 Main-only 加密设置、环境变量只读覆盖、连接测试、动态启停、发送者范围和状态诊断。
- [x] **受管本地模型下载源**:在“平台功能 / 通用设置”中为后续语音输入与 OCR 模型下载全局选择 ModelScope(默认)或 Hugging Face;所选来源缺少完整已验证文件时明确不可用,不静默换源或混合文件。
- [x] **可选本地语音模型管理**:应用不内置模型权重;提供校验下载、进度与取消、来源链接、ZIP 或本地目录导入、切换和删除。
- [x] **本地录音与离线转写**:采集麦克风音频并使用已选择的本地模型离线转写,支持停止、取消、状态反馈和资源释放。
- [x] **版本检查与镜像节点**:在“关于与更新”中选择 GitHub(默认)或镜像节点;手动检查、启动时检查和下载页使用同一选择,并只读取固定可信的发布索引,不自动下载或安装。
- [x] **应用内反馈**:在“关于与更新”中直接提交问题、建议或体验反馈,支持可选邮箱和单张截图;失败后保留草稿并按同一请求编号重试,不自动附加对话、日志、文件、配置或凭据。
- [x] **内网兼容模式**:默认开启;允许应用内 HTTP 与无效、自签名或过期的 HTTPS 证书,关闭后恢复严格地址和证书校验。
### Browser, communication, voice, and application maintenance
### 开源、构建与发布
- [x] **Shared built-in browser for runtimes**: Uses GoodBuddy's isolated
Chromium and never controls a browser installed by the user. A separate
master switch decides whether assigned runtimes receive the capability in
Execute, with no per-use prompt after enablement. Turning Agent access off
does not disable manual browser workbar actions. The Browser workbar and
Agent share the same Conversation-owned session and serialized operation
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.
- [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
DingTalk each have a system-managed project, independent remote
conversations, working directory, processing backend, default Ask/Execute
mode, and task/activity ownership. Each channel controls complete-response
length and segmentation according to platform capabilities rather than
relying on a shared service truncation.
- [x] **WeChat ClawBot QR login and media**: A separate Sidecar handles local
QR scanning, verification codes, encrypted credentials, and text messaging.
Personal WeChat chats support images and files, with at most four
attachments and 12 MB total after decryption per message.
- [x] **Safe WeChat responses**: Can return images generated by the current
task, or generate a Markdown attachment from the current final text after an
explicit user request. It never automatically reads or sends existing
workspace files.
- [x] **WeCom and DingTalk connections**: Main-only encrypted settings,
read-only environment-variable overrides, connection tests, dynamic
enable/disable, sender scope, and status diagnostics.
- [x] **Managed local-model download source**: Platform Features / General
selects ModelScope (default) or Hugging Face globally for future speech-input
and OCR model downloads. A source without a complete verified file is
explicitly unavailable and never silently falls back or combines files.
- [x] **Optional local speech-model management**: Model weights are not bundled.
Provides verified downloads, progress and cancellation, source links, ZIP or
local-directory import, switching, and removal.
- [x] **Local recording and offline transcription**: Captures microphone audio
and transcribes with the selected local model, with stop, cancel, state
feedback, and resource cleanup.
- [x] **Version checks and mirror source**: About and Updates selects GitHub
(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] **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
bounded recent Desktop diagnostic summary. Failed submission preserves the
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] **0BSD 开源许可**:原创代码可自由使用、复制、修改、分发和商用;第三方组件和资源仍遵循各自许可证。
- [x] **可复现依赖安装与源码构建**:使用锁定依赖、Node.js 24 和统一的测试、类型检查、Lint、生产构建命令。
- [x] **六平台原生发布矩阵**:Windows、macOS、Linux 的 `x64`、`arm64` 目标由原生 Runner 构建,并提供发布清单和 SHA-256 哈希;Linux 同时生成 AppImage、DEB 和 RPM。
### Open source, builds, and releases
### 开放接口、团队协作与远程执行
- [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.
- [x] **Reproducible dependency installation and source builds**: Uses locked
dependencies, Node.js 24, and unified test, type-check, lint, and production
build commands.
- [x] **Six-target native release matrix**: Native runners build Windows,
macOS, and Linux `x64` and `arm64` targets with release manifests and SHA-256
hashes. Linux also produces AppImage, DEB, and RPM.
- [x] **远程任务委派**:仅在用户显式配置端点和令牌后启用,按全局内网兼容模式使用 HTTP(S),结果进入持久化发件箱。
- [ ] **Headless Runtime API**(规划中):提供本机优先的任务、事件、状态和成果 API,以及有范围、有效期、限流和撤销能力的令牌。
- [ ] **GoodBuddy Team Hub**(规划中):以可选服务提供组织、RBAC、项目共享、远程 Agent、策略下发和租户审计。
- [ ] **SSH 主机与远程执行空间发布验收**:主机 CRUD、Host Key、加密凭据、Project UI、Workspace、OpenCode ACP、Main-only 模型桥、Ask 只读、Execute 完整账号权限、取消、detached Agent 重连和 release-only 双架构资源校验已经接线。Windows 到 Linux x64 的既有真实 Host provider-free 安装、metadata-only Portable 复用与项目保存、桥接、断线恢复、输出重放、同 Session 续接与清理已经通过;Agent 0.11.2-e2e.12 与 OpenCode Runtime 1.18.9 的真实模型 Ask 验收也已通过,单次用户操作只执行一个原生 read,两个 build 模型轮次均完成交付,没有 title 轮次或请求重放。公开 signing key registry 已供应。控制面直连源码不等待新 installer-bearing 包,但仍需公开核对当前 Linux x64/arm64 正式签名工件矩阵,并完成 GitHub/北京镜像、双架构、取消和离线 GoodBuddy 传输的真实 Host 验收;门槛未满足时不宣称该路径已发布或通过验收。
- [ ] **多云远程沙盒 Agent**(规划中):通过云厂商 API 和 SSH Agent 管理专用 Linux 沙盒;凭据留在 Main 进程,高风险控制面操作单独确认。
### Open interfaces, team collaboration, and remote execution
## 规划原则
- [x] **Remote task delegation**: Enabled only after the user explicitly
configures an endpoint and token, uses HTTP(S) according to the global
private-network compatibility setting, and writes results to a persistent
outbox.
- [ ] **Headless Runtime API** (planned): A local-first Task, event, state, and
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,
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
dual-architecture resource verification are wired. Current Linux x64 source
has passed a real-model and tool matrix covering detach, process exit, relay
loss, concurrency, cancellation, provider failures, Agent restart, and
Desktop SQLite recovery. A current-source, test-signed Agent `0.11.17`
package on Linux x64 additionally passed two concurrent Ask reads, an
Execute write/read, and six real managed-gateway model requests with the
configured custom headers and body fields. No provider request was replayed,
and scanning the isolated Agent state found no API key or custom request
value. The public signing-key registry is provisioned. Direct control-plane
source does not wait for a new installer-bearing package, but the current
formally signed Linux x64/arm64 artifact matrix still requires public
verification, along with GitHub/Beijing mirror, dual-architecture, and
offline GoodBuddy transfer release acceptance. Until these gates pass, this
path must not be described as published or as having passed formal release
acceptance.
- [ ] **Multi-cloud remote sandbox Agents** (planned): Manages dedicated Linux
sandboxes through cloud-provider APIs and SSH Agents. Credentials remain in
Main, and high-risk control-plane operations receive separate confirmation.
规划中的工作流、Subagent、MCP、远程 API 和沙盒能力不得绕过现有 Main Runtime、Ask/Execute、权限、取消、超时和审计边界。
## Roadmap Principles
Planned workflows, Subagents, MCP, remote APIs, and sandbox capabilities must
not bypass existing Main Runtime, Ask/Execute, permission, cancellation,
timeout, or audit boundaries.
+122
View File
@@ -0,0 +1,122 @@
# GoodBuddy 功能矩阵与路线图
[English](./FEATURES.md) | **简体中文**
本文记录 GoodBuddy 的已提供能力和路线图。未完成项目不代表已包含在当前版本中。
## 状态说明
- [x] 已提供
- [ ] 开发中或规划中,具体以条目标注为准
## 功能总表
### 桌面基础、工作空间与上下文
- [x] **跨平台桌面应用**:支持 Windows、macOS、Linux,以及 `x64`、`arm64` 发布目标。
- [x] **信创软硬件覆盖**:正式 Linux 安装包覆盖国产 `x64` 与 `arm64` 架构,可用于统信 UOS、银河麒麟及海光、兆芯、鲲鹏、飞腾等兼容环境。龙芯 LoongArch 另行提供 `loong64` 实验预览版,不属于正式发布矩阵,也未纳入自动更新;`0.12.7` 不构建该预览。这里的覆盖范围不等同于厂商或整机兼容认证,具体状态与限制见[龙芯预览版构建说明](./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] **富文本回答**:支持 GitHub Flavored Markdown、LaTeX 数学公式、受控 Mermaid 图表和会话内 HTML 静态预览。Agent 完成回复后,完整 HTML 与 HTML 代码块可在原位置预览,并通过图标按钮查看源码或打开全屏预览;该能力默认开启,可在“平台功能 / 通用设置”关闭,预览不执行脚本、联网、表单提交或窗口操作。
- [x] **AI 回复与完整会话复制**:已完成的 AI 回复可从消息底部复制不含推理、工具日志和引用元数据的 Markdown 原文;完整会话复制复用同一条经过 Preload/Main 校验的剪贴板路径。
- [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)。
### Agent Runtime 与模型连接
- [x] **直连模型 Runtime**:支持问答、知识总结、受控工具执行、图像生成,以及通过支持 OpenAI 兼容图片编辑接口的服务商进行参考图编辑。编辑使用 multipart 上传附件图片,结果仍只接受经过校验的内联图片,不下载服务商返回的图片 URL。
- [x] **直连模型编程 Agent**:本机直连文本模型可在 Execute 模式运行平台 Shell,并可按父请求模式、模型、工作区和能力范围委派一层编程 Subagent;OpenCode、Continue、DeepSeek Harness 和托管 SSH 不重复注入这两个工具。Windows 本机命令与真实模型“修改、测试、修复、复核”闭环已通过,macOS 与 Linux 真机命令仍待对应平台验收。
- [x] **OpenCode 与 Continue**:使用隔离子进程、环境变量白名单、统一配置、取消、总执行时限、有界流式输出和活动记录;共享进程回收逻辑保留 Windows 完整进程树终止,并对采用独立进程组的 POSIX 子进程执行组回收。聊天状态检查使用一次性探测 Runtime 并在返回后立即回收,不进入按模型与项目隔离的执行 Runtime 缓存;本机 GoodBuddy 管理的 OpenCode 在该被动检查中只确认所选路径和模型凭据,不启动用后即毁的 Server,设置中的显式连接测试和原生能力清单仍执行完整启动及健康检查,原生能力清单随后与首次实际请求复用同一个执行 Runtime,不再重复冷启动;执行 Runtime 仍按项目复用,但 OpenCode 配置依赖和按内容摘要生成的 Skill 快照在所有本机项目间全局复用,会话数据、工具输出及请求级 MCP 状态继续隔离。本机 GoodBuddy 管理的 OpenCode 只对同一会话内的请求保持顺序,不同会话即使属于同一项目也可并行,不同项目同样可并行;请求级动态 MCP 通过默认通配禁用和当前请求精确放行保持隔离。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。
- [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] **专家与 Subagent**:支持显式专家、团队分析和最多三个专家并行分析。专家继承父请求 Ask/Execute 模式,可使用已启用的本机直连模型工具,Ask 仍只读;这些工具不是远程 OpenCode 子会话。聊天先展示可逐项展开的专家完整输出,再在其下展示总 Agent 的综合结果,并随会话保存。
- [x] **OpenCode 子代理过程与最终结果**:子代理卡片按顺序显示文字、推理和工具过程,最终结果独立展示。远程实时过程需要 Agent `0.11.20`,旧包仍显示最终结果但不补造缺失过程。
- [x] **OpenCode 事件连接回收**:请求结束或取消后关闭自身订阅,不取消其他并行会话。
- [x] **直连模型命令工作目录**:Execute 命令可通过绝对路径、相对路径或符号链接使用工作区外目录;工作区仍为默认目录和相对路径基准,Ask 保持只读。
- [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)。
- [x] **上下文用量与自动压缩**:直连模型按每次成功调用更新供应商用量,图片与工具轮次使用同一口径,供应商缺失 usage 时才回退估算;界面明确区分“本次模型调用”和“压缩后对话估算”,压缩线始终根据当前设置与所选模型窗口即时计算,不在每个对话中保存旧配置;压缩标识的前后值使用同一估算口径,运行记录仍保留各次模型调用的供应商 usage。对话与多轮工具 Agent 可在已完成调用越过阈值后自动重复压缩,规划时先为固定提示、工具定义和摘要预留预算;同一回复会分别保留 Agent 工具上下文与对话历史的压缩标识,并在应用重启或较早消息滚出本地历史窗口后继续复用摘要。
- [x] **持久凭据保护**:API Key 由 Main 使用系统安全存储加密且不暴露给 Renderer。托管 SSH accepted Prompt 只把当前 profile 与密钥放入 Agent 内存,不写入 SSH 参数、远端环境或磁盘;其他路径仍在 Main 内使用。密钥随对应模型连接保存,修改服务地址或临时切换为无需认证不会要求重新输入;只有用户显式清除凭据或删除连接时才移除。
- [x] **有界故障诊断**:Desktop 在用户数据目录中轮转保存启动、Runtime 和远程连接的固定阶段失败,最多 4 个 256 KiB 文件;GoodBuddy Agent 在各 installation 的私有 state 目录中轮转保存 daemon、连接、恢复和 Runtime 生命周期,最多 3 个 64 KiB 文件,并可通过固定 `diagnostics --installation-id` 命令读取。两端都只记录白名单阶段、稳定错误码/类型和固定短消息,不保存 Prompt、凭据、文件内容、路径、环境、SSH 参数或 Provider 原始响应;诊断写入失败不改变正常运行。
- [x] **OpenCode Runtime 定制**:GoodBuddy 管理的内置 OpenCode 可发现原生 Agents、Tools、Commands、LSP、Formatters、MCP、Skills、Prompts 与 Resources;Tools 单独显示读取、文件修改、命令、网络、Agent 编排等类型、来源及 Ask/Execute 可用性,并隐藏 OpenCode 内部 `invalid` 与 GoodBuddy 临时 MCP 工具。支持保存默认 Agent、每次请求覆盖 Agent、通过原生 SDK 执行 Command、显示上下文用量并调用有总时限的原生 Compact;并发外部 Server 对话的提问使用请求级公开 ID 映射,回答不会串到其他会话。外部 OpenCode Server 只报告连接状态,不宣称原生清单可读。任意插件安装、Session Share、自动 Worktree 和 OpenCode 原生会话持久化仍不开放。
- [x] **Continue Runtime 定制**:提供静态配置中的原生 Rules、Prompt 模板与 MCP 清单,以及可编辑的 GoodBuddy Rules/Prompt 配置预设;聊天可按请求选择预设和填入可继续编辑的 Prompt。当前 Continue Host 没有可信的静态原生 Tool 发现接口,且使用隔离的 `CONTINUE_GLOBAL_DIR`,因此界面明确标记 Tools 不支持静态发现,也不把 Host 实际不会加载的工作区或用户 Skills 冒充原生能力;GoodBuddy 分配的 Skills 仍按请求暂存执行。Continue 临时 Host 不复用原生会话压缩,手动压缩由 GoodBuddy 摘要模型完成并验证持久化摘要覆盖范围;Agent 交互提问转换为统一问答卡片。Resources、Hooks、后台 Job 和 Continue 原生会话管理继续暂缓。
- [x] **Runtime 原生清单语义**:原生能力以 Agents、Tools、Commands、Skills、MCP、Rules、Prompts、Resources、LSP、Formatters 和上下文 11 个页签展示;清单状态独立于 Runtime 连通性,区分完整、部分、不可用、仅连接和不支持。DeepSeek Harness 通过 Host Registry 枚举有界的内置/插件 Tools 与 Skills,显示真实 Ask/Execute 边界,并排除 GoodBuddy 按请求分配的 Skills、Web/MCP 代理。
- [ ] **Runtime 监督栏目**(规划中):在应用级助手工作栏的固定 Runtime 栏目统一承载 OpenCode、Continue 和 DeepSeek Harness 的 Task 级委派、后台执行、Workflow/Hook、长任务与原生会话监督;用户只选择 Conversation 或 Task,Job/Run 保持内部,不形成树或独立操作对象。
- [ ] **Subagent 高级监督**(规划中):在工作栏固定 Runtime 栏目按 Task 聚合,提供可配置的嵌套、并行、预算和生命周期控制。基础专家模式继承与单层直连模型编程委派已经可用。
### Skills、MCP 与知识库
- [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、自定义解释器和运行中进程协调仍是待完成验收。
- [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 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] **知识图谱**:支持规则、模型和混合抽取,以及实体、关系、别名和证据维护。
- [x] **向量模型配置与检索**:可配置兼容 Embeddings 接口并用于语义检索。
- [x] **向量诊断与索引任务**:提供真实向量生成诊断、按文档重建进度、取消、失败状态与重启后结果恢复;每篇成功文档立即可用于检索。
- [x] **混合检索测试台**:支持全文、中文词组、向量和图谱通道诊断,可调 Top K、阈值、权重、本地或学习型重排及上下文预算。
- [x] **分块、维护与评估**:支持固定、结构化和父子分块,分块维护、可取消重建及双语检索评估。
- [x] **受控知识本体**:每个知识库可定义实体、关系、别名和端点约束,保留证据偏移、置信度和抽取来源,并显式提示图谱重建。
- [x] **强制检索与引用上下文**:对话可按需或每次先检索,显示零结果、降级、失败与取消状态,并可查看引用上下文或安全打开来源。
- [x] **魔法笔记 / Magic Notes**:提供本地优先的笔记与待办工作台、范围管理、编辑、筛选和受控 AI 评论;左侧入口可按设置显示未完成待办数量,创建、保存和评论结果使用统一应用通知。
- [ ] **MCP Server Control Plane**(规划中):统一 MCP 生命周期、健康检查、重连、Schema 缓存、隔离、审批和审计。
- [ ] **可追溯笔记摘录与 AI 编辑**(规划中):从对话、知识和网页收集带来源的摘录,并提供需确认的总结、改写和整理操作。
### 工作管理、长期协作与工作流
- [x] **任务、活动与成果**:集中管理任务状态、审计活动和独立成果文件;普通聊天回复只保留在会话中,不再自动复制到成果栏,已有重复聊天 Markdown 从成果列表隐藏但不物理删除。运行记录改由 Main SQLite 保存,不再受旧 Renderer 的 500 条、单详情 4,000 字符或约 2 MB 上限影响,页面按批继续显示;迁移旧缓存时若历史可能已被截断,会持续明确提示,不能把既有缺口伪装成完整。Token 用量按 Runtime 与模型归类,并针对 OpenAI 兼容与 Anthropic Messages 的不同上报口径归一化展示缓存命中率;活动按会话分组并默认收起,避免长历史占满页面。
- [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] **智能心跳入口与范围改善**:将“智能心跳 > 心跳计划”作为完整配置的唯一权威入口,支持创建和编辑 Global 或指定一个、多个 Project 的计划;旧单项目配置无损迁移,项目级记忆与行动输出必须显式指定范围内的 Project。Task Center 和设置不再复制心跳表单;按分区回顾、候选生成和唤起条件仍待设计。详见 [智能心跳 PRD](./docs/features/smart-heartbeat/prd.md)。
- [ ] **通用监督**(规划中):通过固定监督栏目观察用户选择的会话、任务、自动化或实验对象,提供带证据的评论与人工介入请求,但不自动发言、批准工具或切换 Execute。详见 [会话监督 PRD](./docs/features/conversation-supervision/prd.md)。
- [ ] **批量运行与对比实验室**(规划中):对模型、Prompt、角色和工作流配置执行批量对比,汇总质量、耗时、Token、费用、失败率和成果差异。
- [ ] **时态记忆与事实冲突检测**(规划中):为记忆和知识图谱增加有效期、当前事实、过期与矛盾检测、事实核验及证据回溯。
- [ ] **可视化受控工作流**(规划中):提供版本化 DAG、条件分支、审批、取消和恢复,执行节点继续经过 Main Runtime 边界。
- [ ] **统一 Run Graph 与回放**(规划中):关联任务、Subagent、模型、知识、工具审批、用量和成果,支持失败定位、重试和脱敏导出。
### 浏览器、通信、语音与应用维护
- [x] **Runtime 共享内置浏览器**:使用 GoodBuddy 内置的隔离 Chromium,不控制客户端已安装的浏览器;用户通过内置 MCP 总开关和 Runtime 分配决定是否提供给 Execute,开启后不逐次询问。关闭 Agent 浏览器能力不影响用户继续在浏览器工作栏中手动前往、返回、刷新、停止加载、交互或关闭浏览器。直连模型、GoodBuddy 管理的 OpenCode 和 Continue 通过请求级权限共享按 Conversation 归属的会话和串行操作路径,用户导航会改变 Agent 下一步看到的页面。
- [x] **客户端电脑控制工具**:与内置浏览器分开管理,并保留范围、取消、超时、输出边界和执行记录。
- [x] **远程消息通道项目**:微信 ClawBot、企业微信和钉钉分别拥有系统管理的项目、独立远程会话、工作目录、处理后端、默认 Ask/Execute 模式及任务活动归属;完整回复交由各通道按平台能力控制长度与分段,不再由公共服务统一截断。
- [x] **微信 ClawBot 扫码与媒体**:通过独立 Sidecar 完成本机扫码、验证码、加密凭据和文字收发;支持个人微信私聊图片与文件,单条消息最多 4 个附件、解密后合计不超过 12MB。
- [x] **微信安全回传**:支持返回当前任务生成的图片,或在用户明确要求时将本次最终文本生成为 Markdown 附件;不自动读取或发送已有工作区文件。
- [x] **企业微信与钉钉连接**:支持 Main-only 加密设置、环境变量只读覆盖、连接测试、动态启停、发送者范围和状态诊断。
- [x] **受管本地模型下载源**:在“平台功能 / 通用设置”中为后续语音输入与 OCR 模型下载全局选择 ModelScope(默认)或 Hugging Face;所选来源缺少完整已验证文件时明确不可用,不静默换源或混合文件。
- [x] **可选本地语音模型管理**:应用不内置模型权重;提供校验下载、进度与取消、来源链接、ZIP 或本地目录导入、切换和删除。
- [x] **本地录音与离线转写**:采集麦克风音频并使用已选择的本地模型离线转写,支持停止、取消、状态反馈和资源释放。
- [x] **版本检查与镜像节点**:在“关于与更新”中选择 GitHub(默认)或镜像节点;手动检查、启动时检查和下载页使用同一选择,并只读取固定可信的发布索引,不自动下载或安装。
- [x] **应用内反馈**:在“关于与更新”中直接提交问题、建议或体验反馈,支持可选邮箱和单张截图;默认不上传诊断,用户可主动附加有界的最近桌面诊断摘要。失败后保留草稿、诊断选择和同一请求编号;不会附加对话、Prompt、凭据、文件内容、路径、Provider 原始响应或远端 Agent 日志。
- [x] **内网兼容模式**:默认开启;允许应用内 HTTP 与无效、自签名或过期的 HTTPS 证书,关闭后恢复严格地址和证书校验。
### 开源、构建与发布
- 当前源码候选为 Desktop `0.12.7`、Agent `0.11.21`,OpenCode 固定为 `1.18.29`;正式发布状态以 Desktop 与 Agent 的独立发布渠道为准。
- [x] **0BSD 开源许可**:原创代码可自由使用、复制、修改、分发和商用;第三方组件和资源仍遵循各自许可证。
- [x] **可复现依赖安装与源码构建**:使用锁定依赖、Node.js 24 和统一的测试、类型检查、Lint、生产构建命令。
- [x] **六平台原生发布矩阵**:Windows、macOS、Linux 的 `x64`、`arm64` 目标由原生 Runner 构建,并提供发布清单和 SHA-256 哈希;Linux 同时生成 AppImage、DEB 和 RPM。
### 开放接口、团队协作与远程执行
- [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 传输的发布验收;门槛未满足时不宣称该路径已发布或通过正式发布验收。
- [ ] **多云远程沙盒 Agent**(规划中):通过云厂商 API 和 SSH Agent 管理专用 Linux 沙盒;凭据留在 Main 进程,高风险控制面操作单独确认。
## 规划原则
规划中的工作流、Subagent、MCP、远程 API 和沙盒能力不得绕过现有 Main Runtime、Ask/Execute、权限、取消、超时和审计边界。
-85
View File
@@ -1,85 +0,0 @@
# GoodBuddy
English | [简体中文](README.md)
A secure, cross-platform, local-first desktop AI assistant and Agent workspace.
![GoodBuddy workspace](docs/screenshots/workspace-overview.png)
## Highlights
- **Controlled execution**: `Ask` stays read-only; `Execute` runs only enabled tools within defined boundaries and records their activity.
- **Local-first data**: Conversations, tasks, artifacts, memory, knowledge bases, and graphs are stored in local SQLite. API keys are encrypted by the operating system.
- **Multiple runtimes**: Connect directly to models or use OpenCode, Continue, and the preview DeepSeek Harness, with cancellation, timeouts, output limits, and process cleanup.
- **Open integrations**: Supports OpenAI Responses, OpenAI-compatible Chat Completions, Anthropic Messages, OpenAI Images, Embeddings, cross-runtime Skills and custom MCP, plus a default-off DeepSeek Harness npm plugin marketplace that users enable explicitly.
- **Knowledge workspace**: Import files, folders, and web pages, then search them with full-text, phrase, vector, and graph retrieval.
- **Work management**: Organize projects, conversations, tasks, activity, artifacts, memory, Magic Notes, and Smart Heartbeat.
- **Remote channels**: Connect WeChat ClawBot, WeCom, and DingTalk with separate remote sessions for each sender.
- **Managed SSH projects (technical preview)**: Remote support is off by default and does not affect ordinary desktop use. After enabling it, manually download the independently signed package for the Linux Host architecture under Settings > Platform Features > Remote Projects, or import/export an offline `.gbagent`. Each package contains the Agent, pinned Node, and the GoodBuddy-adapted remote OpenCode Runtime; online downloads follow the GitHub or mirror source selected under About & Updates.
- **Desktop context**: Add selected files, screenshots, application windows, clipboard content, and voice.
- **Offline speech**: Use local SenseVoice, Paraformer, and Whisper models.
- **Rich responses**: Render Markdown, LaTeX, and controlled Mermaid diagrams.
![GoodBuddy knowledge workspace](docs/screenshots/knowledge-workspace.png)
![GoodBuddy knowledge graph](docs/screenshots/knowledge-graph.png)
![GoodBuddy Magic Notes](docs/screenshots/GoodBuddy_MFSGeK0NoT.gif)
![GoodBuddy Smart Heartbeat](docs/screenshots/smart-heartbeat.png)
See [FEATURES.md](FEATURES.md) for the detailed feature matrix and roadmap, and
the [documentation index](docs/README.md) for product, architecture, design,
and quality documents.
## Install
Download a build from [GitHub Releases](https://github.com/mesalogo/goodbuddy/releases):
| Platform | Architectures | Formats |
| --- | --- | --- |
| Windows | `x64`, `arm64` | NSIS, portable ZIP |
| macOS | `x64`, `arm64` | DMG, ZIP |
| Linux | `x64`, `arm64` | AppImage, DEB, RPM |
Desktop installers do not contain remote Agent payloads. No Agent package is
needed for local projects; managed SSH users install one explicitly in
Settings.
Code signing and macOS notarization are not configured yet, so your operating system may display a security warning.
## Run from source
Requires Node.js 24 and npm:
```bash
git clone https://github.com/mesalogo/goodbuddy.git
cd goodbuddy
npm ci
npm run dev
```
See [BUILD.md](BUILD.md) for build and packaging instructions.
## Privacy and security
- Model requests are sent only to services selected by the user.
- Local data stays in the operating system's application data directory by default.
- The Renderer has no access to raw Electron APIs or model credentials.
- The DeepSeek Harness plugin marketplace is off by default. After it is enabled and a third-party plugin is installed, its install scripts, initialization, and Execute tools run with the current user's permissions. Turning off the marketplace only hides its catalog and management interface; it does not disable or uninstall existing plugins. Installation requires explicit confirmation, and Ask limits only model tool calls.
- Remote delegation is disabled until the user configures an endpoint and token.
- Private-network compatibility permits in-app HTTP and non-standard HTTPS certificates. WeChat credential and media endpoints remain strictly validated.
## Contributing
Issues and pull requests are welcome. Read [AGENTS.md](AGENTS.md) first, then run:
```bash
npm test
npm run typecheck
npm run lint
```
## License
Original GoodBuddy code is released under the [0BSD License](LICENSE). You may use, modify, distribute, and commercialize it freely. Third-party components and resources retain their respective licenses.
+121 -59
View File
@@ -1,63 +1,113 @@
<div align="center">
<img src="./sites/assets/goodbuddy-light.png" width="96" alt="GoodBuddy" />
# GoodBuddy
[English](README.en.md) | 简体中文
**Desktop access to model chat, coding agents, knowledge, and scheduled work**
GoodBuddy是一个开箱即用的 AI 生产平台,安全、跨平台、本地优先的桌面 AI 助手与 Agent 工作空间。
**English** | [简体中文](./README.zh-CN.md)
产品官网:https://mesalogo.github.io/goodbuddy/
[Website](https://mesalogo.github.io/goodbuddy/en.html) ·
[Download](https://github.com/mesalogo/goodbuddy/releases) ·
[Feature Matrix](./FEATURES.md) ·
[Build Guide](./BUILD.md)
五大特点:
1. 支持国产信创系统和硬件
2. 免注册,不收集个人信息
3. 完全开源和免费,友好的OBSD协议
4. 不需要联网,纯私有网络也可以用
5. 开放生态,内置主流好用的Agent Runtime,目前支持OpenCode、Continue和DeepSeek Harness,让小白用户也可以快速用起来最前沿的Agent工具
[![Release](https://img.shields.io/github/v/release/mesalogo/goodbuddy?label=release)](https://github.com/mesalogo/goodbuddy/releases)
[![License: 0BSD](https://img.shields.io/badge/license-0BSD-blue.svg)](./LICENSE)
[![Windows](https://img.shields.io/badge/Windows-x64%20%7C%20arm64-0078D4)](https://github.com/mesalogo/goodbuddy/releases)
[![macOS](https://img.shields.io/badge/macOS-x64%20%7C%20arm64-000000)](https://github.com/mesalogo/goodbuddy/releases)
[![Linux](https://img.shields.io/badge/Linux-x64%20%7C%20arm64-FCC624)](https://github.com/mesalogo/goodbuddy/releases)
</div>
![GoodBuddy 工作空间](docs/screenshots/workspace-overview.png)
![GoodBuddy workspace](./docs/screenshots/workspace-overview.png)
## 核心能力
GoodBuddy is a desktop application for model chat, coding agents, knowledge
retrieval, notes, and scheduled tasks. No GoodBuddy account is required.
Workspace data is stored locally by default. Model endpoints can run on the
same device, on a private network, or at a provider selected by the user.
- **安全执行**:`Ask` 保持只读;`Execute` 仅运行已启用且受边界约束的工具,并保留活动记录。
- **本地优先**:会话、任务、成果、记忆、知识库和图谱保存在本地 SQLite;API Key 由系统安全存储加密。
- **多 Runtime**:支持直连模型、OpenCode、Continue 和预览版 DeepSeek Harness,统一处理取消、超时、输出限制和进程退出;原生能力清单将 Tools 与 Commands、LSP、Formatters 分开,并显示来源及 Ask/Execute 可用性。内置 OpenCode 提供 Agent、Tool、Command 与原生 Compact,Continue 提供 Rules、Prompt 预设、结构化提问与 GoodBuddy 手动摘要压缩,并明确标记当前版本无法静态发现原生 Tools。
- **开放连接**:支持 OpenAI Responses、OpenAI 兼容 Chat Completions、Anthropic Messages、OpenAI Images、Embeddings、跨 Runtime Skills 与自定义 MCP,以及默认关闭、由用户显式开启的 DeepSeek Harness npm 插件市场。Anthropic 连接可独立配置协议必填的单次最大输出,不再复用上下文窗口;OpenAI 直连请求不额外设置输出 Token 上限。MCP 测试可读取有界 Prompt/Resource 元数据,但不会读取 Resource 内容。
- **知识工作区**:支持文件、目录和网页导入,以及全文、词组、向量和图谱混合检索。
- **工作管理**:集中管理 Projects、对话、任务、活动、成果、记忆、魔法笔记和智能心跳;可从稳定的本地会话复制当前聊天内容并创建互不影响的会话分支。
- **远程通道**:支持微信 ClawBot、企业微信和钉钉,每个发送者使用独立远程会话。
- **托管 SSH 项目(技术预览)**:远程能力默认关闭且不影响普通桌面使用。添加 Host 只保存并探测,不自动安装;Host 卡片以一个按版本事实命名的主按钮准备环境,次级控件可选默认且不持久化的“自动”、Host 下载或 GoodBuddy 传输。自动模式只在操作开始时择一,失败不跨路径重试;两种方式交付同一签名 compound `.gbagent` 后共用 prepare、commit、Agent/Runtime adoption 与 cleanup。项目创建与切换只使用已经准备好的 Host。GitHub/北京镜像、Linux x64/arm64、取消和离线 GoodBuddy 传输的真实 Host 验收仍待完成,当前不表示该路径已发布。
- **桌面上下文**:可选择文件、截图、应用窗口、剪贴板和语音作为上下文。
- **离线语音**:支持 SenseVoice、Paraformer 和 Whisper 本地模型。
- **富文本回答**:支持 Markdown、LaTeX 公式和受控 Mermaid 图表。
## Highlights
![GoodBuddy 知识工作区](docs/screenshots/knowledge-workspace.png)
- **Local-first workspace**: No GoodBuddy account is required. Workspace data
stays local by default, and users choose where model services run.
- **Unified Agent Runtimes**: Use direct models, OpenCode, Continue, and the
preview DeepSeek Harness from one desktop interface.
- **Explicit execution boundaries**: `Ask` remains read-only, while `Execute`
uses the current account's permissions. Tool activity, cancellation,
timeouts, and token usage are recorded.
- **Broad platform coverage**: Official Linux releases cover `x64` and `arm64`,
including compatible distributions used in China's domestic computing
ecosystem. A separate
experimental `loong64` preview is available for LoongArch systems.
- **Knowledge and long-running work**: Knowledge bases, graphs, Magic Notes,
tasks, and Smart Heartbeat share one desktop workspace.
![GoodBuddy 知识图谱](docs/screenshots/knowledge-graph.png)
Platform coverage describes operating-system, instruction-set, and package
compatibility, not certification for every hardware vendor or distribution
release. LoongArch is outside the official release matrix; see the
[LoongArch preview notes](./docs/development/loongarch-preview-build.md) for its
current limitations.
![GoodBuddy 魔法笔记](docs/screenshots/GoodBuddy_MFSGeK0NoT.gif)
## Capabilities
![GoodBuddy 智能心跳](docs/screenshots/smart-heartbeat.png)
| Area | Available in GoodBuddy |
| --- | --- |
| Agent Runtimes | Direct model connections, OpenCode, Continue, and the preview DeepSeek Harness |
| Data and execution | Local SQLite storage, read-only `Ask`, full-account `Execute`, and recorded tool activity |
| Knowledge | File, folder, and web imports with full-text, Chinese phrase, vector, and knowledge graph retrieval |
| Desktop releases | Official Windows, macOS, and Linux builds for `x64` and `arm64`, plus an experimental LoongArch preview |
| Integrations | Local or hosted model endpoints, custom MCP servers, WeChat ClawBot, WeCom, and DingTalk |
完整功能和路线图见 [FEATURES.md](FEATURES.md),产品、架构、设计与质量文档见
[文档导航](docs/README.md)。
## Product tour
## 安装
### Knowledge bases and graphs
从 [GitHub Releases](https://github.com/mesalogo/goodbuddy/releases) 下载:
![GoodBuddy knowledge workspace](./docs/screenshots/knowledge-workspace.png)
| 系统 | 架构 | 格式 |
![GoodBuddy knowledge graph](./docs/screenshots/knowledge-graph.png)
### Magic Notes and Smart Heartbeat
![GoodBuddy Magic Notes](./docs/screenshots/GoodBuddy_MFSGeK0NoT.gif)
![GoodBuddy Smart Heartbeat](./docs/screenshots/smart-heartbeat.png)
## Agent Runtimes
| Runtime | Functions | Configuration |
| --- | --- | --- |
| Windows | `x64`、`arm64` | NSIS、便携 ZIP |
| macOS | `x64`、`arm64` | DMG、ZIP |
| Linux | `x64`、`arm64` | AppImage、DEB、RPM |
| Direct models | Chat, retrieval, image generation, and built-in tools | GoodBuddy model connections |
| OpenCode | Coding agents, native commands, tools, and skills | GoodBuddy-managed or native OpenCode configuration |
| Continue | Rules, prompt presets, and coding tasks | GoodBuddy-managed or native Continue configuration |
| DeepSeek Harness (preview) | A fixed host with optional plugin capabilities | GoodBuddy-managed OpenAI-compatible connection |
桌面安装包不包含远端 Agent payload。仅使用本地项目时无需下载任何
Agent 包;托管 SSH 用户可在设置中按需管理。
Each run reports cancellation, timeouts, token usage, tool calls, and history.
Runtime-specific capabilities and configuration remain separate.
当前尚未配置代码签名和 macOS notarization,系统可能显示安全提示。
## Download
## 从源码运行
Download a build from
[GitHub Releases](https://github.com/mesalogo/goodbuddy/releases):
需要 Node.js 24 和 npm:
| Platform | Architectures | Formats |
| --- | --- | --- |
| Windows | `x64`, `arm64` | NSIS, portable ZIP |
| macOS | `x64`, `arm64` | DMG, ZIP |
| Linux | `x64`, `arm64` | AppImage, DEB, RPM |
The experimental LoongArch `loong64` build is distributed through a separate
preview channel on the [product website](https://mesalogo.github.io/goodbuddy/#download).
Desktop installers do not contain remote Agent payloads. Local projects do not
need an Agent package; managed SSH users download or import one explicitly in
Settings.
Code signing and macOS notarization are not configured yet, so the operating
system may display a security warning.
## Run from source
Requires Node.js 24 and npm:
```bash
git clone https://github.com/mesalogo/goodbuddy.git
@@ -66,20 +116,35 @@ npm ci
npm run dev
```
构建与打包说明见 [BUILD.md](BUILD.md)。
See [BUILD.md](./BUILD.md) for build and packaging instructions.
## 隐私与安全
## Documentation
- 模型请求只发送到用户选择的服务。
- 本地数据默认保存在系统应用数据目录。
- Renderer 不接触原始 Electron API 或模型凭据。
- DeepSeek Harness 插件市场默认关闭;开启并安装第三方插件后,其安装脚本、初始化和 Execute 工具以当前用户权限运行。关闭市场只隐藏目录和管理界面,不会停用或卸载已有插件;安装前会明确确认。Ask 只允许 Host 原生 `read`/`skill` 与 Main 管理的 Web Search/Fetch,不允许调用第三方插件工具,也不能限制插件初始化代码。
- 远程委派仅在用户配置端点和令牌后启用。
- 内网兼容模式允许应用内 HTTP 和非标准 HTTPS 证书;微信凭据和媒体端点仍执行严格校验。
- [Feature matrix and roadmap](./FEATURES.md)
- [Product feature documentation](./docs/features/)
- [UI design system](./UI-DESIGN.md)
- [Build and packaging](./BUILD.md)
- [Release runbook](./docs/development/release-runbook.md)
## 参与贡献
## Privacy and execution boundaries
欢迎提交 Issue 和 Pull Request。请先阅读 [AGENTS.md](AGENTS.md),提交前运行:
- Model requests are sent only to services selected by the user.
- Local data stays in the operating system's application data directory by
default. API keys are encrypted by secure system storage.
- The Renderer has no access to raw Electron APIs or model credentials.
- The DeepSeek Harness plugin marketplace is off by default. Third-party
install scripts, initialization code, and Execute tools run with the current
user's permissions.
- Remote delegation is enabled only after the user configures an endpoint and
token.
- Private-network compatibility permits in-app HTTP and non-standard HTTPS
certificates. WeChat credential and media endpoints remain strictly
validated.
## Contributing
Issues and pull requests are welcome. Read [AGENTS.md](./AGENTS.md) first, then
run:
```bash
npm test
@@ -87,19 +152,16 @@ npm run typecheck
npm run lint
```
## 为什么选择GoodBuddy
## Community communication
<img width="1024" height="1536" alt="6dd45371542dea9fb2d99474387708f5" src="https://github.com/user-attachments/assets/ca608646-ea48-4039-9177-d862be8396ac" />
Scan the QR code to join the GoodBuddy WeChat group:
## 社区交流
目前采用微信群的方式供大家高效交流,大家可以微信扫码进入社区群:
<img width="1250" height="1743" alt="6ae8664c83b2f182aa81ae94afe691fe" src="https://github.com/user-attachments/assets/10bb7ac2-0bc1-41a9-978d-c2862923fcd6" />
<img width="1250" height="1194" alt="9627ce196b0de42f123c33dd9dea1568" src="https://github.com/user-attachments/assets/e806543f-6d8a-4c5c-ba44-19e74dac9a78" />
## 开源许可
## License
GoodBuddy 的原创代码采用 [0BSD License](LICENSE),可自由使用、修改、分发和商用。第三方组件和资源遵循各自许可证。
Original GoodBuddy code is released under the [0BSD License](./LICENSE). You may
use, modify, distribute, and commercialize it freely. Third-party components
and resources retain their respective licenses.
+136
View File
@@ -0,0 +1,136 @@
<div align="center">
<img src="./sites/assets/goodbuddy-light.png" width="96" alt="GoodBuddy" />
# GoodBuddy
**模型对话、编程 Agent、本地知识库和定时任务的桌面入口**
[English](./README.md) | **简体中文**
[产品官网](https://mesalogo.github.io/goodbuddy/) ·
[下载](https://github.com/mesalogo/goodbuddy/releases) ·
[功能矩阵](./FEATURES.zh-CN.md) ·
[构建说明](./BUILD.md)
[![Release](https://img.shields.io/github/v/release/mesalogo/goodbuddy?label=release)](https://github.com/mesalogo/goodbuddy/releases)
[![License: 0BSD](https://img.shields.io/badge/license-0BSD-blue.svg)](./LICENSE)
[![Windows](https://img.shields.io/badge/Windows-x64%20%7C%20arm64-0078D4)](https://github.com/mesalogo/goodbuddy/releases)
[![macOS](https://img.shields.io/badge/macOS-x64%20%7C%20arm64-000000)](https://github.com/mesalogo/goodbuddy/releases)
[![Linux](https://img.shields.io/badge/Linux-x64%20%7C%20arm64-FCC624)](https://github.com/mesalogo/goodbuddy/releases)
</div>
![GoodBuddy 工作空间](./docs/screenshots/workspace-overview.png)
GoodBuddy 是用于模型对话、编程 Agent、知识检索、笔记和定时任务的桌面应用。使用 GoodBuddy 无需注册账号,工作数据默认保存在本机。模型服务可以运行在本机、组织内网或用户选择的云端服务。
## 产品亮点
- **本地优先**:无需注册 GoodBuddy 账号,工作数据默认保存在本机,模型服务由用户选择。
- **统一 Agent Runtime**:在一个桌面界面中使用直连模型、OpenCode、Continue 和预览版 DeepSeek Harness。
- **执行边界明确**:`Ask` 保持只读,`Execute` 使用当前账号权限,工具调用、取消、超时和 Token 用量均有记录。
- **支持信创软硬件**:正式 Linux 版本覆盖国产 `x64` 与 `arm64` 架构,可用于统信 UOS、银河麒麟及海光、兆芯、鲲鹏、飞腾等兼容环境;龙芯 LoongArch 提供独立 `loong64` 实验预览版。
- **知识与长期工作一体化**:知识库、知识图谱、魔法笔记、任务和智能心跳共享同一个桌面工作空间。
信创支持按操作系统、指令集和安装包兼容性描述,不代表已完成所有厂商、整机和系统版本的兼容认证。龙芯版本尚未进入正式发布矩阵,具体限制见[龙芯预览版说明](./docs/development/loongarch-preview-build.md)。
## 主要能力
| 范围 | GoodBuddy 提供的能力 |
| --- | --- |
| Agent Runtime | 直连模型、OpenCode、Continue 和预览版 DeepSeek Harness |
| 数据与执行 | 本地 SQLite、只读 `Ask`、使用当前账号完整权限的 `Execute`、工具调用记录 |
| 知识库 | 导入文件、目录和网页,通过全文、中文词组、向量和知识图谱检索 |
| 桌面版本 | Windows、macOS、Linux 的 `x64` 与 `arm64` 正式版本,以及龙芯 LoongArch 实验预览版 |
| 外部连接 | 本机或内网模型服务、自定义 MCP、微信 ClawBot、企业微信和钉钉 |
## 产品界面
### 知识库与知识图谱
![GoodBuddy 知识工作区](./docs/screenshots/knowledge-workspace.png)
![GoodBuddy 知识图谱](./docs/screenshots/knowledge-graph.png)
### 魔法笔记与智能心跳
![GoodBuddy 魔法笔记](./docs/screenshots/GoodBuddy_MFSGeK0NoT.gif)
![GoodBuddy 智能心跳](./docs/screenshots/smart-heartbeat.png)
## Agent Runtime
| Runtime | 适用场景 | 配置来源 |
| --- | --- | --- |
| 直连模型 | 对话、知识检索、图片生成和内置工具 | GoodBuddy 模型连接 |
| OpenCode | 编程 Agent、原生 Commands、Tools 和 Skills | GoodBuddy 管理或 OpenCode 自有配置 |
| Continue | Rules、Prompt 预设和编程任务 | GoodBuddy 管理或 Continue 自有配置 |
| DeepSeek Harness(预览) | 固定 Host 与可选插件能力 | GoodBuddy 管理的 OpenAI 兼容连接 |
每次运行都会记录取消、超时、Token 用量、工具调用和执行历史。各 Runtime 的原生能力与配置仍分别管理。
## 下载
从 [GitHub Releases](https://github.com/mesalogo/goodbuddy/releases) 下载:
| 系统 | 架构 | 格式 |
| --- | --- | --- |
| Windows | `x64`、`arm64` | NSIS、便携 ZIP |
| macOS | `x64`、`arm64` | DMG、ZIP |
| Linux | `x64`、`arm64` | AppImage、DEB、RPM |
龙芯 LoongArch 使用独立的 `loong64` 实验预览通道,可在[产品官网](https://mesalogo.github.io/goodbuddy/#download)选择下载。
桌面安装包不包含远端 Agent payload。本地项目无需 Agent 包;托管 SSH 用户可在设置中单独下载或导入。
当前未配置代码签名和 macOS notarization,操作系统可能显示安全提示。
## 从源码运行
需要 Node.js 24 和 npm:
```bash
git clone https://github.com/mesalogo/goodbuddy.git
cd goodbuddy
npm ci
npm run dev
```
构建与打包说明见 [BUILD.md](./BUILD.md)。
## 文档
- [功能矩阵与路线图](./FEATURES.zh-CN.md)
- [产品功能文档](./docs/features/)
- [UI 设计系统](./UI-DESIGN.md)
- [构建与打包](./BUILD.md)
- [发布流程](./docs/development/release-runbook.md)
## 隐私与执行边界
- 模型请求只发送到用户选择的服务。
- 本地数据默认保存在系统应用数据目录,API Key 由系统安全存储加密。
- Renderer 不接触原始 Electron API 或模型凭据。
- DeepSeek Harness 插件市场默认关闭。第三方插件安装脚本、初始化代码和 Execute 工具以当前用户权限运行。
- 远程委派只在用户配置端点和令牌后启用。
- 内网兼容模式允许应用内 HTTP 和非标准 HTTPS 证书;微信凭据和媒体端点仍执行严格校验。
## 参与贡献
欢迎提交 Issue 和 Pull Request。请先阅读 [AGENTS.md](./AGENTS.md),提交前运行:
```bash
npm test
npm run typecheck
npm run lint
```
## 社区交流
扫描二维码加入 GoodBuddy 微信群:
<img width="1250" height="1194" alt="9627ce196b0de42f123c33dd9dea1568" src="https://github.com/user-attachments/assets/ff87dfac-d72f-47b7-ac9a-c88b0b4c96d9" />
## 开源许可
GoodBuddy 原创代码采用 [0BSD License](./LICENSE),可自由使用、修改、分发和商用。第三方组件和资源遵循各自许可证。
+76 -15
View File
@@ -193,7 +193,7 @@
### 6.1 PageTabs
用于同一一级页面内的同级内容面板,例如智能心跳的“成长概览”和“心跳计划”。
用于同一一级页面内的同级内容面板,例如智能心跳的“运行概览”和“心跳计划”。
- 使用 `tablist`、`tab` 和 `tabpanel` 语义,当前项使用 `aria-selected="true"`。
- 一级页面之间的导航由应用主导航承担,不复用 `PageTabs`。
@@ -201,6 +201,7 @@
- 项目过多时优先重组信息架构,不把一级页签做成多行。
- `PageTabs` 可使用默认视觉或共享的 `segmented` 视觉变体。紧凑主从工作台中的 2 至 4 个同级面板可使用与模型设置一致的分段外观,但不得因此改用按钮组语义。
- 视觉变体不改变组件含义:分段外观的 `PageTabs` 仍使用页签语义、单一激活面板、游标焦点和方向键切换,不复制页面专属样式。
- 在独立滚动的 Flex 页面中,`PageTabs` 及其直接包装层必须保持 `flex: 0 0 auto`,不得被剩余内容压缩到零高度。新增页签容器时必须提供对应的 CSS 回归测试。
### 6.2 SegmentedControl
@@ -282,6 +283,9 @@
- 一级导航与最近会话之间、最近会话与底部账户区之间必须有可见结构分隔线。浅色主题使用 `--border-default`,深色主题可在可辨前提下使用 `--border-subtle`。
- 当前导航项和当前会话必须同时使用至少三种信号中的两种:强调背景、可见边框、图标或文字强调。浅色主题的当前项优先使用更完整的蓝色选中表面和较高字重。
- 未选中项保持平整,不为每一行添加卡片边框或阴影。悬停反馈不得强于选中状态。
- 桌面停靠状态下,主侧栏宽度允许在 `220px` 至 `420px` 范围内通过指针或键盘调整,
并记住用户选择;分隔条使用 `separator` 语义,支持方向键、Home 和 End。小于
`900px` 时继续使用固定宽度抽屉,不显示拖拽分隔条。
- 关联 Task 的 Conversation 在行最左侧显示独立展开按钮,父会话行不重复任务标签或数量;
展开后的每个 Task 子项左侧显示名称,模式、计划和共享状态标记收在右侧,与父会话的
更新时间位置一致;展开层级只到 Task,不展示 Job、Subjob 或 Run。
@@ -305,26 +309,56 @@
接受用户本机选择的 PNG、JPEG 或 WebP 内联数据,宽高不超过 `4096 × 4096`、总像素不
超过 1600 万且大小不超过 `512 KB`;不得读取远程 Logo URL。名称或副标题溢出品牌区时
单行截断,不能撑宽或挤压侧栏。
- 账户与设置入口固定在侧栏底部。已有稳定设置入口时,不在顶栏重复提供同一入口。
- 侧栏底部固定显示文件、终端、浏览器和资源监控四个系统工具图标,设置按钮独立靠右。工具
图标只负责打开或聚焦助手工作栏面板,不在侧栏复制工具内容。已有稳定设置入口时,不在顶栏
重复提供同一入口。
### 6.9 助手工作栏
助手工作栏是应用级右侧工具容器,不归属于聊天页面,也不根据当前页面、项目或 Runtime
自动增删入口。产品契约见
[通用助手工作栏与执行空间 PRD](./docs/prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md)。
[通用助手工作栏与执行空间 PRD](./docs/features/assistant-workbar/prd.md),
动态 Tab、“+”和终端细节见
[助手工作栏多终端页签 PRD](./docs/features/assistant-workbar/terminal-tabs-prd.md)。
- 默认固定提供任务中心、工作区、浏览器和成果四个标准栏目,不根据当前页面或
Runtime 能力自动增删入口。
- 升级后默认打开任务中心、工作区、浏览器和成果四个单实例应用,不根据当前页面或 Runtime
能力自动增删稳定应用目录入口。
- 资源监控作为本机单实例应用保留在能力目录,但不加入升级后的默认打开实例。面板只在可见
活动时采样,首期不持久化历史或提供后台负载告警。
- Tab 行只显示已打开实例,行末始终提供“+”普通按钮;点击后在内容区打开应用选择页。
“+”不使用 `tab` 语义,也不能因为终端或其他应用已经打开而消失。
- 应用注册表声明单实例或多实例。在工作栏“+”目录中,单实例重复选择时聚焦已有 Tab;终端
从首期开始支持多实例,每次选择创建独立 Tab 和 Shell Session。主侧栏系统工具快捷入口
可以聚焦当前执行空间中最近使用的终端,额外终端仍从“+”目录创建。
- Task Center 是 Task 的单例应用级索引,不使用“跟随 / 固定目标”多实例模式。每个 Task
只关联一条 Conversation,一条 Conversation 可以关联多个 Task;列表不得复制会话内容,
也不得把 Job/Run 提升为可导航 UI 对象。
- 各栏目读取当前会话或项目的对应内容;当前内容不可用时仍保留栏目入口并说明原因。
- 能力、连接和内容可以动态变化,栏目入口不能随之自动隐藏。不可用状态必须说明原因、影响和可执行入口。
- 用户可以主动排序或隐藏栏目,并可恢复默认布局;应用不能用用户偏好机制实现自动能力裁剪。
- 四个栏目使用稳定标签并保留 `tablist`、`tab`、`tabpanel`、方向键、Home、End 和焦点恢复语义。
- 已打开实例保留 `tablist`、`tab`、`tabpanel`、方向键、Home、End 和焦点恢复语义。
实例过多时 Tab 区单行横向滚动,“+”保持可见,不换行或改成下拉框。
- 当前工作栏 Tab 必须同时使用连续内容表面、可见边框、顶部指示、图标或较高字重中的至少
两种信号;悬停反馈不得强于当前选中态。Tab 关闭按钮保留固定布局空间,默认仅在对应 Tab 悬停或
`focus-within` 时显示;无悬停或粗指针设备始终显示,键盘与辅助技术始终可访问。
- 工作栏 Tab、Tab 关闭按钮和“+”入口沿用共享控件圆角;终端面板外框及终端顶部工具栏按钮
使用直角,形成紧凑的停靠式终端界面。
- 选择“终端”后直接绑定当前项目,由项目执行空间自动决定本机或托管 SSH;不再显示第二层
目标选择器。终端面板内部不重复提供关闭会话按钮,关闭统一由 Tab 入口完成。
- 下一次请求使用的附件和知识库由会话输入区展示与管理,不在工作栏重复提供“上下文”栏目;
记忆功能实现前不展示占位内容,未来的记忆与历史执行上下文归入关联 Task 的任务中心详情。
- 徽标可以提示未解决意见、等待审批、失败或连接状态,但不能成为唯一状态信号,也不能无条件抢占当前栏目。
- 浏览器 Tab 的内容顶部固定使用一条紧凑工具栏,按“返回、刷新/停止加载、地址输入、
前往、交互、关闭浏览器”排列。“刷新”在当前页面加载时原位切换为“停止加载”;
“关闭浏览器”必须使用明确文字和与页面导航控件清楚区分的危险操作视觉,不能继续标为
含义模糊的“停止浏览器”。地址输入显示浏览器权威提交 URL,但用户编辑期间保留本地草稿,
不被画面或状态刷新覆盖;提交裸域名时自动添加 `http://`,提交、失焦后的同步规则不得让
尚未提交的文字改变 Agent 看到的页面。
- 浏览器工具栏操作的是当前 Conversation 中 Agent 使用的同一隔离浏览器会话,用户导航会
有意改变 Agent 下一步看到的页面。创建中、Agent 操作中或已进入完整交互窗口时禁用其余
工具栏控件;仅当前导航仍在加载时保留“停止加载”,且该操作只中止页面加载,不关闭浏览器、
不释放 Runtime 上下文。没有会话的活动 Conversation 可以直接在地址栏输入 URL 并“前往”
创建会话,但必须复用 Agent 浏览器相同的 URL 策略与能力路径。
- 所有窗口宽度下均停靠在主工作区右侧并参与水平布局回流;展开时压缩主工作区,不能覆盖、
遮罩或隔离其内容。分栏计算排除最左侧主导航,主工作区与工作栏采用相同的 `300px`
最小宽度;拖动保存两者在可用分栏宽度中的比例,任一侧到达最小值后都不得继续侵占。
@@ -356,7 +390,9 @@
对齐。该栏不重复放置新建入口。
- “新建任务”只位于工作栏“任务索引”标题后方;任务状态筛选占满工作栏可用宽度,四个选项
等分,不能保留内容宽度的短控件。
- Runtime 状态、同步状态等短标签使用 `--font-caption`,不得放大为正文标题。
- Runtime 状态、同步状态等短标签使用 `--font-caption`,不得放大为正文标题。顶栏 Runtime
状态由状态点和 Runtime/模型名称紧凑表达:就绪使用绿色,连接中使用黄色,不可用使用灰色,
不再重复显示状态文字;完整状态详情继续通过可访问说明和工具提示提供。
- 浅色与深色切换属于持续可用的窗口级操作,直接显示太阳或月亮图标,并通过可访问名称说明将切换到的主题。选择必须持久化,切换不得改变布局。
- 顶栏只直接显示当前任务所需的高频操作。已有侧栏账户设置入口时,不再重复显示 Runtime/设置入口;使用帮助优先放在相关操作附近,而不是为单个帮助项创建“更多”菜单。
- 助手工作栏开关固定在上下文栏最右侧并与窗口关闭按钮纵向对齐;展开后该开关保持原位并
@@ -376,8 +412,11 @@
- 支持上、下方向键、Home、End、Enter 或 Space、Escape;打开后焦点进入当前项,关闭后返回触发按钮。
- 点击或聚焦菜单外部时关闭;同一输入区内的模型、专家和模式菜单互斥展开。
- 不可用选项保持可读并说明原因,键盘导航不得停留在不可选择项上。
- 项目选择器属于富信息单选菜单。收起时保持当前项目名称紧凑可见;展开后每项至少显示项目名称和项目类型,并按数据可用性补充本地目录、远程来源、平台、连接状态或其他能帮助辨认项目的信息。
- 项目菜单区分“本地项目 / 远程项目 / 远程通道”,不把消息通道命名为远程项目。“远程项目(技术预览)”关闭时完整移除远程项目分组,而不是显示不可操作的残留选项;本地项目和远程通道继续可用。分组、项目类型和连接状态均须显示文字,不只依赖图标或颜色;新增项目来源时不得退回仅列名称的原生 `select`。
- 当某个选项需要用户在其展开区域内完成路径选择、授权或其他前置操作时,不得因为前置条件
尚未完成而禁用该入口。选择入口只展开流程,验证成功前不提交实际值;不得自动采用第一个
候选。取消保持原值,验证错误在对应操作附近显示。
- 项目选择器属于富信息单选菜单。收起时保持当前项目名称紧凑可见;展开后每项至少显示项目名称,并按数据可用性补充目录、远程来源或其他能帮助辨认项目的信息。托管 SSH 项目在“远程项目”内按已保存 SSH Host 做一层轻量分组,Host 标题旁显示真实 Agent 连接状态;项目副标题只显示远端路径,不重复“托管 SSH”类型。
- 项目菜单区分“本地项目 / 远程项目 / 远程通道”,不把消息通道命名为远程项目。“远程项目(技术预览)”关闭时完整移除远程项目分组,而不是显示不可操作的残留选项;本地项目和远程通道继续可用。Agent 连接状态使用“未连接 / 连接中 / 就绪 / 异常”等文字标签,读取状态不得发起连接,且不能只依赖图标或颜色;新增项目来源时不得退回仅列名称的原生 `select`。
### 6.12 应用通知与就地反馈
@@ -543,6 +582,16 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- 使用 `reading` 壳层,消息流与输入区共享宽度。
- 对话标题和当前项目范围位于 `PageHeader` 或对话上下文区,不在消息流中重复。
- 已结束且包含正文的助手回复在底部提供带可访问名称和工具提示的复制图标;复制 Markdown
原文,不包含推理过程、工具日志或引用元数据,结果通过应用通知反馈。流式回复完成前不显示
复制入口。
- 默认开启“会话 HTML 渲染”时,已完成 Agent 回复中的完整 HTML 与 `html` / `htm`
代码块在原位置显示隔离静态预览;用户消息、推理过程和流式中的回复不得执行 HTML。
每个预览持续标注脚本和网络已禁用,并在标题右侧复用与 Mermaid 图表相同的图标按钮组:
源码按钮使用代码图标并暴露 `aria-expanded`,全屏按钮使用放大图标打开统一 Modal。
全屏预览遵循统一 Modal 的背景隔离、初始焦点、Tab 循环、Escape 关闭和焦点恢复规则,
并沿用同一套清理与 Sandbox/CSP 边界。关闭平台功能后必须显示 HTML 源码,不能隐藏内容
或保留旧预览。
- 本地会话的更多操作提供“在新会话中继续”。创建前必须先持久化当前稳定消息;有活动运行
或待发送内容时不可创建。新会话复制聊天内容与当前 Runtime/知识检索选择,重置压缩状态,
使用新的会话和消息 ID,且不复制 Task、队列、运行、记忆或成果归属。
@@ -553,6 +602,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- 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 回复进行中不能禁用普通消息发送。新消息与到期 Scheduled Task 共用 Conversation 级待发送队列。队列位于 Composer 容器之外、与输入框等宽并保持独立间距;有待发送项时直接显示无标题栏的极简列表,空队列不保留占位。每项固定为一行,按进入顺序显示轻量来源图标和截断摘要;普通消息使用消息图标,Scheduled Task 统一使用淡出时钟图标。
- 待发送项默认在当前回复结束后顺序执行。每项提供明确的“立即中断并插入”(空闲时为“立即运行”)和删除操作;前者先取消当前 Conversation 的活动执行,再将所选项提升为下一项,不允许同一 Conversation 并发写入时间线。
@@ -564,6 +614,9 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- “Enter 发送 · Shift+Enter 换行 · Ctrl+V 粘贴图片或文本”等输入操作提示放在空输入框内部,作为主占位文案的次级行;不得在输入框下方单独占用第二行。输入框下方只保留一行当前模式、安全边界或全局快捷键说明。
- 输入操作提示不能替代表单的可访问名称,输入框始终保留持久的程序化标签。
- 空对话展示可执行的起始建议,发送失败保留输入并提供重试。
- 消息流的滚动范围必须使用已渲染消息的真实高度。长会话可以按批次只挂载最近消息并由用户
加载更早内容,但不得用固定的消息占位高度替代实际布局,避免推理、工具列表或长正文被
排除在可滚动范围之外。
### 13.2 最近对话
@@ -593,9 +646,9 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- 范围明确区分 Global 与指定的一个或多个 Project;Global 与指定项目互斥,多项目选择使用
Checkbox,不能依靠进入页面时的当前项目推断。
- 状态卡片使用统一状态令牌,不只依赖颜色。
- 保留“成长概览 / 待处理建议 / 心跳轨迹 / 心跳计划”四个同级页面。
- 保留“运行概览 / 待处理建议 / 报告与记录 / 心跳计划”四个同级页面。
- 智能心跳菜单入口是完整配置的权威位置;任务中心和设置中心不得复制同一 CRUD 表单。
- “未来分区记忆”仅为长期方向,数据、状态和页面尚未设计,不得显示占位入口。
- 分区记忆协同与唤起条件尚未实现,不得显示占位入口或平行的记忆类型。
### 13.5 Task Center
@@ -625,6 +678,11 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- “笔记 / 待办”属于同一工作台内的同级内容面板,使用 `PageTabs` 的 `segmented` 视觉变体,与模型设置的分段控件保持同一外观。
- 页签切换保留 `tablist`、`tab` 和 `tabpanel` 语义;待办状态仍使用独立的 `SegmentedControl`,不得与内容页签合并。
- 笔记编辑器保留 Quill 原生列表快捷输入:输入 `-`、`*`、`1.`、`[ ]` 或 `[x]` 后按空格,分别转换为项目符号、编号或待办清单。快捷输入只生成现有 Delta 富文本,不引入 Markdown 源码模式、第二套持久化格式或第三方 Markdown 插件;编辑区附近持续提供简短提示。
- 左侧笔记与待办列表在双栏布局中允许在 `180px` 至 `360px` 范围内通过指针或键盘调整,
也可从页面标题区隐藏和恢复;宽度与显示状态均记住用户选择。分隔条保留 `separator`
语义及方向键、Home、End 操作,窄窗口堆叠布局不显示拖拽分隔条。
- 左侧笔记列表依次显示标题、最多两行内容预览,以及同一行的记录数和本地化更新时间。记录数靠左、更新时间靠右,窄宽度下均保持单行省略,不挤压标题或扩大列表栏。
- 左侧“魔法笔记”入口可显示未完成待办数量。数量为 0 时隐藏徽标,超过 99 时显示 `99+`,可访问名称仍说明精确数量;勾选、取消勾选以及 Agent / MCP 对笔记清单的写入都必须刷新数量。
- 未完成待办徽标的高度不得超过主导航图标;数量从 0 变为可见或再次隐藏时,主导航项行高和相邻菜单位置必须保持不变。
- 在待办面板勾选或取消勾选后,当前缓存中的源笔记正文必须立即同步对应清单状态,不得依赖离开工作区后重新加载。
@@ -647,14 +705,17 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- “模型连接”的认证字段按“认证方式 → API Key → 凭据状态”排列;只有选择 API Key 时显示输入框。已保存的 API Key 属于该连接,修改模型服务地址或临时选择“无需认证”不得自动清除,也不得要求重新输入;仅由明确的“清除凭据”或“删除连接”操作移除。
- “保存并测试模型”必须在保存后发送一次有界的真实模型请求。文本连接校验本次随机测试文本,图像连接校验实际返回的内联图片;仅收到成功 HTTP 状态、模型列表或健康检查响应不算测试成功。界面持续说明该操作可能产生少量服务商用量费用,成功反馈明确写为“真实生成测试通过”。
- 所有显式保存的设置草稿都参与离开保护:关闭、切换分类、主侧栏或工作区导航及托盘导航不得静默丢弃,统一通过设置中心的就地确认提供继续编辑与明确放弃入口,保存失败后保留输入。“平台功能 / 通用设置”承载全局快捷唤起的共享 Switch、可访问 accelerator 录制输入、恢复默认、保存及注册、停用或冲突状态,不新增分类或页签;注册或持久化失败时保留上一组可用快捷键和当前草稿,保存或停用成功后同步更新输入区的快捷键提示。
- “平台功能”使用共享 `PageTabs` 区分“通用设置”“远程项目(技术预览)”和“魔法笔记”,默认进入通用设置。“远程项目”页将持久 Switch 与用户数据目录中的 Linux x64/arm64 Agent 包清单分成独立卡片;清单延迟到首次打开该页签时由 Main 完整校验,并读取所选更新源的小型签名目录,显示本地版本、在线最新兼容版本以及“有更新”“已是最新”“未下载”“校验失败”。打开页签或刷新不得自动下载 `.gbagent`;只有用户点击带目标版本的下载/更新按钮后才传输包。签名目录不可用时继续显示并允许导出已验证本地包,提供就地重试错误;清单不混入 Host 已安装状态、缓存路径或敏感签名细节。下载和导入期间保留上下文进度,成功进入全局通知视口。“魔法笔记”页提供默认开启的“显示未完成待办数量”持久 Switch,关闭后只隐藏左侧数量徽标,不改变待办数据或魔法笔记入口。全局模型下载源使用 `fieldset`、持久 `legend` 与整行可点击的原生 Radio 卡片;选中状态同时依靠 Radio、边框和背景表达,读取失败时不得用默认值伪装为已保存选择。
- “关于与更新”的更新源位于“启动时检查新版本”开关下方,常规宽度下将标签、原生单选下拉框和用途说明放在同一行,并复用设置表单的统一控件样式;关闭启动检查后,下拉框置灰且不可操作。选项显示“GitHub(默认)”和中性的“镜像节点”。该选择同时控制手动检查、启动时检查、下载页和 Agent 包在线下载,不显示底层服务商名称。
- “关于与更新”在版本信息之后提供应用内反馈卡片。反馈对话框使用原生字段、明确隐私说明和一张可选截图,失败时保留草稿并提供就地重试,成功时在对话框内保留可复制编号而不重复发送全局通知;对话框遵循统一 Modal 的背景隔离、初始焦点、Tab 循环、Escape、焦点恢复和窄窗口布局。
- “平台功能 / 通用设置”同时提供默认开启的“在会话中渲染 HTML”持久 Switch。说明必须
明确该能力只生成禁用脚本、联网、表单提交和窗口操作的静态预览;保存成功后当前会话立即
更新,保存失败时保留最后确认状态。
- “平台功能”使用共享 `PageTabs` 区分“通用设置”“远程项目(技术预览)”和“魔法笔记”,默认进入通用设置。“远程项目”页将持久 Switch 与用户数据目录中的 Linux x64、Linux arm64、macOS arm64 Agent 包清单分成独立卡片;每张卡片与下载进度按平台和架构区分,不能把两个 arm64 目标合并。清单延迟到首次打开该页签时由 Main 完整校验,并读取所选更新源的小型签名目录,显示本地版本、在线最新兼容版本以及“有更新”“已是最新”“未下载”“校验失败”。打开页签或刷新不得自动下载 `.gbagent`;只有用户点击带目标版本的下载/更新按钮后才传输包。签名目录不可用时继续显示并允许导出已验证本地包,提供就地重试错误;清单不混入 Host 已安装状态、缓存路径或敏感签名细节。下载和导入期间保留上下文进度,成功进入全局通知视口。“魔法笔记”页提供默认开启的“显示未完成待办数量”持久 Switch,关闭后只隐藏左侧数量徽标,不改变待办数据或魔法笔记入口。全局模型下载源使用 `fieldset`、持久 `legend` 与整行可点击的原生 Radio 卡片;选中状态同时依靠 Radio、边框和背景表达,读取失败时不得用默认值伪装为已保存选择。
- “关于与更新”的更新源位于“启动时检查新版本”开关下方,常规宽度下将标签、原生单选下拉框和用途说明放在同一行,并复用设置表单的统一控件样式;关闭启动检查后仍可选择更新源,手动检查继续使用当前选择。选项显示“GitHub(默认)”和中性的“镜像节点”。该选择同时控制手动检查、启动时检查、下载页和 Agent 包在线下载,不显示底层服务商名称。
- “关于与更新”在版本信息之后提供应用内反馈卡片。反馈对话框使用原生字段、明确隐私说明和一张可选截图;另提供默认关闭的原生 Checkbox,让用户主动附加最近桌面诊断摘要。Checkbox 附近必须持续说明摘要只含时间、组件、阶段、稳定错误码、错误类型和固定短消息,不含对话、Prompt、凭据、文件内容、路径、Provider 原始响应或远端 Agent 日志。勾选时为诊断摘要预留描述字符预算,超长草稿必须保留并显示字段错误,不得静默截断。失败时保留草稿、勾选状态和请求编号并提供就地重试,成功时在对话框内保留可复制编号而不重复发送全局通知;对话框遵循统一 Modal 的背景隔离、初始焦点、Tab 循环、Escape、焦点恢复和窄窗口布局。
- Agent Runtime 分类页头的“保存设置”同时保存 Runtime 基础配置与 Runtime 原生定制,不在原生定制卡片内提供第二个保存入口。原生定制存在未保存更改时持续显示状态和撤销入口;切换设置分类或 Runtime 不丢弃草稿,关闭设置中心前必须先保存或撤销。
- Agent Runtime 页面在低层程序与配置覆盖之外提供“能力与默认配置”区域。能力清单使用共享 `PageTabs`,按 Agents、Tools、Commands、Skills、MCP、Rules、Prompts、Resources、LSP、Formatters 和上下文 11 类单行滚动展示,一次只呈现当前分类的 `tabpanel`;清单只显示 Runtime 自有能力,不混入 GoodBuddy 分配的 Skills、临时 MCP 或 Continue 预设。Tools 必须独立于 Commands、LSP 和 Formatters,显示工具类型、来源及 Ask/Execute 可用性;清单状态必须区分完整、部分、不可用、仅连接和不支持,不能用进程连通性冒充清单可读。
- “能力与默认配置”只显示一个模块标题,刷新入口位于该标题右侧,能力状态压缩为一行并排在默认 Agent 或 Continue 预设编辑器之前;不得再复制“Runtime 原生能力”等同义标题、说明或状态结论。刷新只更新能力快照,不覆盖未保存的原生定制草稿。
- OpenCode 的默认 Agent 使用原生下拉选择;Continue 预设编辑器允许管理名称、说明、启用的 Rules 以及 Prompt 名称、说明和正文,并可展开查看原生 Rules 与启用预设 Rules 的最终合并顺序。持久启停仍使用共享 Switch,添加与删除使用明确按钮和可访问名称。
- MCP 设置按“内置 MCP / 直连模型 / 自定义 MCP / 电脑控制”四个同级 `PageTabs` 组织。直连模型中的联网搜索与内置浏览器使用一致的折叠卡片和独立总开关;内置浏览器必须明确说明其操作 GoodBuddy 隔离浏览器,不控制客户端已安装的浏览器,开启后可由 Execute 直接使用,不逐次询问。尚未生效的命名浏览器配置不得显示在界面中,“电脑控制”只显示实际操作客户端电脑的能力。内置 MCP 卡片与 Skills 一样提供持久启停和 Runtime 分配;直连模型、GoodBuddy 管理的 OpenCode 与 Continue 默认选中且可调整,DeepSeek Harness 必须以置灰、未选择和“暂不支持”文案持续显示,不能呈现为可保存的分配。魔法笔记 MCP 的自身启停与平台功能依赖分别显示,依赖未开启时保留用户配置并说明当前不会加载。
- 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 内容。
### 13.9 文档解析设置
+7 -1
View File
@@ -1,6 +1,6 @@
{
"formatVersion": 1,
"agentVersion": "0.11.9",
"agentVersion": "0.11.21",
"protocol": {
"major": 2,
"minor": 0
@@ -15,6 +15,12 @@
"binaryPath": "node-v24.19.0-linux-x64/bin/node",
"licensePath": "node-v24.19.0-linux-x64/LICENSE"
},
"darwin-arm64": {
"archive": "node-v24.19.0-darwin-arm64.tar.gz",
"sha256": "8294b7aa9b03997481c06babf1e8b270c859358f27da57a11509afe537ac381d",
"binaryPath": "node-v24.19.0-darwin-arm64/bin/node",
"licensePath": "node-v24.19.0-darwin-arm64/LICENSE"
},
"linux-arm64": {
"archive": "node-v24.19.0-linux-arm64.tar.gz",
"sha256": "d28c8a5bf0a808f0ed434a1dce8c54ae98f0371c0bd86ac58abc613f73e6643f",
+33
View File
@@ -0,0 +1,33 @@
const { readFileSync } = require('node:fs')
const {
detectBinaryArchitecture,
detectElfArchitecture
} = require('./binary-architecture.cjs')
function targetName(architecture, platform = 'linux') {
if (
!(platform === 'linux' && ['x64', 'arm64'].includes(architecture)) &&
!(platform === 'darwin' && architecture === 'arm64')
) {
throw new Error(`Unsupported Agent build target: ${platform}-${architecture}`)
}
return `${platform}-${architecture}`
}
function assertTargetBinary(filePath, architecture, platform, label) {
targetName(architecture, platform)
const header = readFileSync(filePath).subarray(0, 64)
const isMachO = header.length >= 8 &&
(header.readUInt32LE(0) === 0xfeedfacf ||
header.readUInt32BE(0) === 0xfeedfacf)
const actual = platform === 'linux'
? detectElfArchitecture(header)
: isMachO ? detectBinaryArchitecture(header) : undefined
if (actual !== architecture) {
throw new Error(
`${label} architecture mismatch: expected ${platform}-${architecture}, received ${actual ?? 'unknown'}`
)
}
}
module.exports = { targetName, assertTargetBinary }
+94 -48
View File
@@ -29,9 +29,11 @@ const {
sep
} = require('node:path')
const tar = require('tar')
const { detectElfArchitecture } = require('./binary-architecture.cjs')
const {
detectElfArchitecture
} = require('./binary-architecture.cjs')
targetName,
assertTargetBinary
} = require('./agent-build-target.cjs')
const {
preflightRegisteredProductionKey
} = require('./signing-key-preflight.cjs')
@@ -132,15 +134,8 @@ function readTrustedKeyRegistry(projectRoot = root) {
return registry
}
function targetName(arch) {
if (!supportedArchitectures.includes(arch)) {
throw new Error(`Unsupported Agent architecture: ${arch}`)
}
return `linux-${arch}`
}
function lockedRuntimeInput(lock, arch, archivePath) {
const target = targetName(arch)
function lockedRuntimeInput(lock, arch, archivePath, platform = 'linux') {
const target = targetName(arch, platform)
const input = lock.node?.targets?.[target]
if (
!input ||
@@ -177,24 +172,23 @@ function lockedRuntimeInput(lock, arch, archivePath) {
function assertElfArchitecture(
filePath,
expectedArchitecture,
description = 'Agent Node runtime'
description = 'Agent Node runtime',
platform = 'linux'
) {
const actual = detectElfArchitecture(readFileSync(filePath).subarray(0, 64))
if (actual !== expectedArchitecture) {
throw new Error(
`${description} architecture mismatch: expected ${expectedArchitecture}, received ${actual ?? 'unknown'}`
)
}
assertTargetBinary(filePath, expectedArchitecture, platform, description)
}
function koffiNativePackageName(arch) {
return `@koromix/koffi-linux-${arch}`
function koffiNativePackageName(arch, platform = 'linux') {
return `@koromix/koffi-${targetName(arch, platform)}`
}
function koffiPayloadPaths(arch) {
function koffiPayloadPaths(arch, platform = 'linux') {
const packageRoot = `lib/node_modules/${koffiPackageName}`
const nativeRoot =
`lib/node_modules/${koffiNativePackageName(arch)}`
`lib/node_modules/${koffiNativePackageName(arch, platform)}`
const native = platform === 'linux'
? [`${nativeRoot}/linux_${arch}/koffi.node`, `${nativeRoot}/musl_${arch}/koffi.node`]
: [`${nativeRoot}/darwin_${arch}/koffi.node`]
return {
packageRoot,
nativeRoot,
@@ -205,13 +199,9 @@ function koffiPayloadPaths(arch) {
`${packageRoot}/src/koffi/src/static.js`,
`${nativeRoot}/package.json`,
`${nativeRoot}/index.js`,
`${nativeRoot}/linux_${arch}/koffi.node`,
`${nativeRoot}/musl_${arch}/koffi.node`
...native
],
native: [
`${nativeRoot}/linux_${arch}/koffi.node`,
`${nativeRoot}/musl_${arch}/koffi.node`
]
native
}
}
@@ -328,6 +318,20 @@ function createManifest(bundleDirectory, metadata) {
spdx: 'MIT',
path: 'licenses/zod-MIT.txt'
},
...(files.some(
(file) =>
file.path ===
'licenses/agent-client-protocol-Apache-2.0.txt'
)
? [
{
package: '@agentclientprotocol/sdk',
version: metadata.acpSdkVersion,
spdx: 'Apache-2.0',
path: 'licenses/agent-client-protocol-Apache-2.0.txt'
}
]
: []),
{
package: koffiPackageName,
version: metadata.koffiVersion,
@@ -345,7 +349,7 @@ function createManifest(bundleDirectory, metadata) {
formatVersion: 1,
product: 'GoodBuddy',
agentVersion: metadata.agentVersion,
platform: 'linux',
platform: metadata.platform ?? 'linux',
arch: metadata.arch,
protocol: metadata.protocol,
signingKeyId: metadata.signingKeyId,
@@ -438,7 +442,10 @@ function validateManifestShape(manifest, expected) {
manifest.product !== 'GoodBuddy' ||
typeof manifest.agentVersion !== 'string' ||
!semanticVersionPattern.test(manifest.agentVersion) ||
manifest.platform !== 'linux' ||
!(
manifest.platform === 'linux' ||
(manifest.platform === 'darwin' && manifest.arch === 'arm64')
) ||
!supportedArchitectures.includes(manifest.arch) ||
!Number.isSafeInteger(manifest.protocol?.major) ||
!Number.isSafeInteger(manifest.protocol?.minor) ||
@@ -603,7 +610,7 @@ function verifyBundleDirectory(bundleDirectory, options = {}) {
throw new Error(`Agent entrypoint payload is missing: ${required}`)
}
}
const koffiPaths = koffiPayloadPaths(manifest.arch)
const koffiPaths = koffiPayloadPaths(manifest.arch, manifest.platform)
for (const required of koffiPaths.required) {
if (!declaredPaths.has(required)) {
throw new Error(`Agent Koffi payload is missing: ${required}`)
@@ -612,7 +619,7 @@ function verifyBundleDirectory(bundleDirectory, options = {}) {
const koffiLicense = manifest.licenses.find(
(license) => license.package === koffiPackageName
)
const nativePackage = koffiNativePackageName(manifest.arch)
const nativePackage = koffiNativePackageName(manifest.arch, manifest.platform)
const nativeLicense = manifest.licenses.find(
(license) => license.package === nativePackage
)
@@ -627,13 +634,16 @@ function verifyBundleDirectory(bundleDirectory, options = {}) {
}
assertElfArchitecture(
join(bundleDirectory, manifest.entrypoint.runtimePath),
manifest.arch
manifest.arch,
'Agent Node runtime',
manifest.platform
)
for (const nativePath of koffiPaths.native) {
assertElfArchitecture(
join(bundleDirectory, ...nativePath.split('/')),
manifest.arch,
'Agent Koffi native binding'
'Agent Koffi native binding',
manifest.platform
)
}
return {
@@ -654,7 +664,8 @@ function assertReplaceableBundle(directory) {
if (
manifest.formatVersion === 1 &&
manifest.product === 'GoodBuddy' &&
manifest.platform === 'linux'
(manifest.platform === 'linux' ||
(manifest.platform === 'darwin' && manifest.arch === 'arm64'))
) {
return
}
@@ -703,13 +714,13 @@ function copyRegularFile(source, destination) {
chmodSync(destination, 0o644)
}
function copyKoffiRuntime(projectRoot, staging, arch, lockedVersion) {
function copyKoffiRuntime(projectRoot, staging, arch, lockedVersion, platform = 'linux') {
const sourceRoot = join(projectRoot, 'node_modules', koffiPackageName)
const packageMetadata = readJson(
join(sourceRoot, 'package.json'),
'Koffi package metadata'
)
const nativePackage = koffiNativePackageName(arch)
const nativePackage = koffiNativePackageName(arch, platform)
const nativeSourceRoot = join(
projectRoot,
'node_modules',
@@ -725,7 +736,7 @@ function copyKoffiRuntime(projectRoot, staging, arch, lockedVersion) {
nativeMetadata.name !== nativePackage ||
nativeMetadata.version !== lockedVersion ||
!Array.isArray(nativeMetadata.os) ||
!nativeMetadata.os.includes('linux') ||
!nativeMetadata.os.includes(platform) ||
!Array.isArray(nativeMetadata.cpu) ||
!nativeMetadata.cpu.includes(arch)
) {
@@ -734,10 +745,13 @@ function copyKoffiRuntime(projectRoot, staging, arch, lockedVersion) {
)
}
const paths = koffiPayloadPaths(arch)
const paths = koffiPayloadPaths(arch, platform)
const files = [
['package.json', `${paths.packageRoot}/package.json`],
['index.js', `${paths.packageRoot}/index.js`],
['index.cjs', `${paths.packageRoot}/index.cjs`],
['src/koffi/index.cjs', `${paths.packageRoot}/src/koffi/index.cjs`],
['src/koffi/src/static.cjs', `${paths.packageRoot}/src/koffi/src/static.cjs`],
[
'src/koffi/index.js',
`${paths.packageRoot}/src/koffi/index.js`
@@ -756,8 +770,7 @@ function copyKoffiRuntime(projectRoot, staging, arch, lockedVersion) {
for (const source of [
'package.json',
'index.js',
`linux_${arch}/koffi.node`,
`musl_${arch}/koffi.node`
...paths.native.map((path) => path.slice(paths.nativeRoot.length + 1))
]) {
const destination = `${paths.nativeRoot}/${source}`
copyRegularFile(
@@ -769,7 +782,8 @@ function copyKoffiRuntime(projectRoot, staging, arch, lockedVersion) {
assertElfArchitecture(
join(staging, ...nativePath.split('/')),
arch,
'Agent Koffi native binding'
'Agent Koffi native binding',
platform
)
}
return {
@@ -781,11 +795,13 @@ function copyKoffiRuntime(projectRoot, staging, arch, lockedVersion) {
function buildAgentBundle(options) {
const projectRoot = options.projectRoot ?? root
const arch = options.arch
const platform = options.platform ?? 'linux'
const lock = options.lock ?? readRuntimeLock(projectRoot)
const runtimeInput = lockedRuntimeInput(
lock,
arch,
options.runtimeArchive
options.runtimeArchive,
platform
)
const registry =
options.registry ?? readTrustedKeyRegistry(projectRoot)
@@ -829,13 +845,13 @@ function buildAgentBundle(options) {
const destination =
options.outputDirectory ??
join(projectRoot, '.agent-resources', targetName(arch))
join(projectRoot, '.agent-resources', targetName(arch, platform))
mkdirSync(dirname(destination), { recursive: true })
const staging = mkdtempSync(
join(dirname(destination), `.stage-${targetName(arch)}-`)
join(dirname(destination), `.stage-${targetName(arch, platform)}-`)
)
const runtimeStaging = mkdtempSync(
join(tmpdir(), `goodbuddy-node-${targetName(arch)}-`)
join(tmpdir(), `goodbuddy-node-${targetName(arch, platform)}-`)
)
try {
tar.x({
@@ -858,7 +874,7 @@ function buildAgentBundle(options) {
runtimeStaging,
...runtimeInput.licensePath.split('/')
)
assertElfArchitecture(runtimeBinary, arch)
assertElfArchitecture(runtimeBinary, arch, 'Agent Node runtime', platform)
copyFileSync(runtimeBinary, join(staging, 'node'))
chmodSync(join(staging, 'node'), 0o755)
@@ -898,7 +914,8 @@ function buildAgentBundle(options) {
projectRoot,
staging,
arch,
lock.koffi.version
lock.koffi.version,
platform
)
const launcher = [
'#!/bin/sh',
@@ -923,6 +940,20 @@ function buildAgentBundle(options) {
join(projectRoot, 'node_modules', 'zod', 'LICENSE'),
join(staging, 'licenses', 'zod-MIT.txt')
)
copyLicense(
join(
projectRoot,
'node_modules',
'@agentclientprotocol',
'sdk',
'LICENSE'
),
join(
staging,
'licenses',
'agent-client-protocol-Apache-2.0.txt'
)
)
copyLicense(
join(projectRoot, 'node_modules', 'koffi', 'LICENSE.txt'),
join(staging, 'licenses', 'koffi-MIT.txt')
@@ -935,12 +966,24 @@ function buildAgentBundle(options) {
join(projectRoot, 'node_modules', 'zod', 'package.json'),
'zod package metadata'
)
const acpSdkPackage = readJson(
join(
projectRoot,
'node_modules',
'@agentclientprotocol',
'sdk',
'package.json'
),
'ACP SDK package metadata'
)
const manifest = createManifest(staging, {
agentVersion: lock.agentVersion,
nodeVersion: lock.node.version,
zodVersion: zodPackage.version,
acpSdkVersion: acpSdkPackage.version,
koffiVersion: koffi.version,
koffiNativePackage: koffi.nativePackage,
platform,
arch,
protocol: lock.protocol,
signingKeyId: signingIdentity.keyId
@@ -965,6 +1008,7 @@ function buildAgentBundle(options) {
? 'test'
: 'production',
expected: {
platform,
agentVersion: lock.agentVersion,
arch,
protocol: lock.protocol
@@ -1034,6 +1078,7 @@ function verifyLockedBundle(bundleDirectory, arch, options = {}) {
options.registry ?? readTrustedKeyRegistry(projectRoot),
verificationEnvironment: options.verificationEnvironment ?? 'production',
expected: {
platform: options.platform ?? 'linux',
agentVersion: lock.agentVersion,
arch,
protocol: lock.protocol
@@ -1164,6 +1209,7 @@ function main(argv = process.argv.slice(2)) {
}
module.exports = {
koffiPayloadPaths,
assertElfArchitecture,
assertSafeManifestPath,
buildAgentBundle,
+38 -10
View File
@@ -32,6 +32,10 @@ const {
} = require('./zip-central-directory.cjs')
const root = resolve(__dirname, '..')
function supportedTarget(platform, architecture) {
return (platform === 'linux' && ['x64', 'arm64'].includes(architecture)) ||
(platform === 'darwin' && architecture === 'arm64')
}
const catalogName = 'agent-catalog.json'
const catalogSignatureName = 'agent-catalog.sig'
const catalogSignatureDomain = Buffer.from(
@@ -46,6 +50,7 @@ const maximumPackageEntries = 50_002
const maximumPackageEntryBytes = 384 * 1024 * 1024
const maximumExpandedPackageBytes = 1024 * 1024 * 1024
const maximumMetadataBytes = 1024 * 1024
const maximumDescriptorBytes = 4 * 1024 * 1024
const maximumCentralDirectoryBytes = 32 * 1024 * 1024
const maximumCatalogEntries = 200
const windowsReservedNamePattern =
@@ -136,7 +141,7 @@ function assertPackageDescriptor(descriptor) {
!semanticVersionPattern.test(
descriptor.minimumDesktopVersion ?? ''
) ||
descriptor.platform !== 'linux' ||
!supportedTarget(descriptor.platform, descriptor.architecture) ||
!['x64', 'arm64'].includes(descriptor.architecture) ||
typeof descriptor.signingKeyId !== 'string' ||
!/^sha256:[a-f0-9]{64}$/u.test(
@@ -214,7 +219,7 @@ function readPackageMetadata(archivePath, registry) {
)
const expectedArchive =
`goodbuddy-agent-${descriptor.version}` +
`-linux-${descriptor.architecture}.gbagent`
`-${descriptor.platform}-${descriptor.architecture}.gbagent`
if (basename(archive) !== expectedArchive) {
throw new Error(
`Agent package archive must be named ${expectedArchive}`
@@ -408,7 +413,10 @@ function streamPackageMetadata(archivePath) {
entry.name === 'agent-package.json' ||
entry.name === 'agent-package.sig'
const size = entry.recordEnd - entry.dataOffset
if (wanted && size > maximumMetadataBytes) {
const maximum = entry.name === 'agent-package.json'
? maximumDescriptorBytes
: maximumMetadataBytes
if (wanted && size > maximum) {
throw new Error('Agent package metadata exceeds its limit')
}
return {
@@ -566,11 +574,11 @@ function assertCatalog(catalog) {
!semanticVersionPattern.test(
entry.minimumDesktopVersion ?? ''
) ||
entry.platform !== 'linux' ||
!supportedTarget(entry.platform, entry.architecture) ||
!['x64', 'arm64'].includes(entry.architecture) ||
entry.archive !==
`goodbuddy-agent-${entry.version}` +
`-linux-${entry.architecture}.gbagent` ||
`-${entry.platform}-${entry.architecture}.gbagent` ||
!Number.isSafeInteger(entry.size) ||
entry.size <= 0 ||
!sha256Pattern.test(entry.sha256 ?? '') ||
@@ -600,7 +608,7 @@ function assertCatalog(catalog) {
entry.remoteRuntime.protocol,
'Remote Runtime protocol'
)
const identity = `${entry.version}:${entry.architecture}`
const identity = `${entry.version}:${entry.platform}:${entry.architecture}`
if (identities.has(identity)) {
throw new Error('Agent catalog contains duplicate entries')
}
@@ -698,7 +706,8 @@ function createCatalog(options) {
}
const packages = [
readPackageMetadata(options.x64Package, registry),
readPackageMetadata(options.arm64Package, registry)
readPackageMetadata(options.arm64Package, registry),
...(options.darwinArm64Package ? [readPackageMetadata(options.darwinArm64Package, registry)] : [])
]
const [first, second] = packages
const {
@@ -721,12 +730,26 @@ function createCatalog(options) {
canonicalJson(secondRuntimeIdentity) ||
new Set(packages.map(
(metadata) => metadata.descriptor.architecture
)).size !== 2
).slice(0, 2)).size !== 2 ||
first.descriptor.platform !== 'linux' ||
second.descriptor.platform !== 'linux'
) {
throw new Error(
'Agent package matrix does not describe one coherent release'
)
}
if (packages[2]) {
const mac = packages[2].descriptor
const { bundleDigest: _digest, ...runtimeIdentity } = mac.remoteRuntime
void _digest
if (
mac.platform !== 'darwin' || mac.architecture !== 'arm64' ||
mac.version !== first.descriptor.version ||
mac.minimumDesktopVersion !== first.descriptor.minimumDesktopVersion ||
canonicalJson(mac.agentProtocol) !== canonicalJson(first.descriptor.agentProtocol) ||
canonicalJson(runtimeIdentity) !== canonicalJson(firstRuntimeIdentity)
) throw new Error('Agent package matrix does not describe one coherent release')
}
const previous =
options.previousCatalog && options.previousSignature
? readVerifiedCatalog(
@@ -745,11 +768,11 @@ function createCatalog(options) {
}
const entries = new Map()
for (const entry of previous?.entries ?? []) {
entries.set(`${entry.version}:${entry.architecture}`, entry)
entries.set(`${entry.version}:${entry.platform}:${entry.architecture}`, entry)
}
for (const metadata of packages) {
const entry = catalogEntry(metadata)
const identity = `${entry.version}:${entry.architecture}`
const identity = `${entry.version}:${entry.platform}:${entry.architecture}`
const existing = entries.get(identity)
if (
existing &&
@@ -764,6 +787,7 @@ function createCatalog(options) {
const sortedEntries = [...entries.values()].sort(
(left, right) =>
compareVersions(right.version, left.version) ||
left.platform.localeCompare(right.platform, 'en') ||
left.architecture.localeCompare(right.architecture, 'en')
)
if (sortedEntries.length > maximumCatalogEntries) {
@@ -813,6 +837,7 @@ function parseArguments(argv) {
![
'--x64-package',
'--arm64-package',
'--darwin-arm64-package',
'--previous-catalog',
'--previous-signature',
'--catalog',
@@ -852,6 +877,9 @@ function parseArguments(argv) {
command,
x64Package: resolve(options['--x64-package']),
arm64Package: resolve(options['--arm64-package']),
...(options['--darwin-arm64-package'] ? {
darwinArm64Package: resolve(options['--darwin-arm64-package'])
} : {}),
...(options['--previous-catalog']
? {
previousCatalog: resolve(
+50 -3
View File
@@ -24,6 +24,7 @@ const {
assembleAgentPackage
} = require('./agent-package.cjs')
const { sha256File } = require('./file-hash.cjs')
const { targetName } = require('./agent-build-target.cjs')
const ciSigningKeyId = 'goodbuddy-agent-ci-ephemeral'
@@ -34,6 +35,7 @@ function parseArguments(argv) {
if (
![
'--arch',
'--platform',
'--node-archive',
'--opencode-archive',
'--output-directory',
@@ -56,6 +58,8 @@ function parseArguments(argv) {
if (!supportedArchitectures.includes(options.arch)) {
throw new Error('--arch must be x64 or arm64')
}
const platform = options.platform ?? 'linux'
targetName(options.arch, platform)
for (const key of [
'nodearchive',
'opencodearchive',
@@ -70,6 +74,7 @@ function parseArguments(argv) {
}
return {
arch: options.arch,
platform,
nodeArchive: resolve(options.nodearchive),
opencodeArchive: resolve(options.opencodearchive),
outputDirectory: resolve(options.outputdirectory),
@@ -77,6 +82,14 @@ function parseArguments(argv) {
}
}
function readCiLocks(platform = 'linux', architecture = 'x64') {
const projectRoot = resolve(__dirname, '..')
targetName(architecture, platform)
const lock = readRuntimeLock(projectRoot)
const runtimeLock = readRemoteRuntimeLock(projectRoot)
return { lock, runtimeLock }
}
function ephemeralSigningIdentity() {
const { privateKey, publicKey } = generateKeyPairSync('ed25519')
return {
@@ -97,8 +110,12 @@ function ephemeralSigningIdentity() {
async function buildCiAgentBundle(options) {
const projectRoot = resolve(__dirname, '..')
const lock = readRuntimeLock(projectRoot)
const runtimeLock = readRemoteRuntimeLock(projectRoot)
const platform = options.platform ?? 'linux'
const target = targetName(options.arch, platform)
if (process.platform !== platform || process.arch !== options.arch) {
throw new Error(`Agent CI build requires a native ${target} host`)
}
const { lock, runtimeLock } = readCiLocks(platform, options.arch)
const signing = ephemeralSigningIdentity()
const firstArchiveRoot = resolve(
join(
@@ -124,6 +141,8 @@ async function buildCiAgentBundle(options) {
force: true
})
buildAgentBundle({
platform,
lock,
projectRoot,
arch: options.arch,
runtimeArchive: options.nodeArchive,
@@ -132,6 +151,8 @@ async function buildCiAgentBundle(options) {
testSigningIdentity: identity
})
const runtime = buildRuntimeBundle({
platform,
lock: runtimeLock,
projectRoot,
architecture: options.arch,
runtimeArchive: options.opencodeArchive,
@@ -141,6 +162,7 @@ async function buildCiAgentBundle(options) {
})
const assemble = (archive) =>
assembleAgentPackage({
platform,
projectRoot,
architecture: options.arch,
minimumDesktopVersion:
@@ -181,7 +203,31 @@ async function buildCiAgentBundle(options) {
throw new Error('Built Agent native launch smoke failed')
}
const nativeChecks = [
[
resolve(options.outputDirectory, 'node'),
['-e', `const k = require(${JSON.stringify(resolve(options.outputDirectory, 'lib/node_modules/koffi'))}); if (k.sizeof('int') !== 4) process.exit(1)`]
],
[join(runtime.bundleDirectory, 'bin', 'opencode'), ['--version']]
]
for (const [executable, args] of nativeChecks) {
const result = spawnSync(executable, args, {
encoding: 'utf8',
timeout: 30_000,
windowsHide: true
})
if (
result.error ||
result.status !== 0 ||
(args[0] === '--version' &&
result.stdout.trim() !== runtimeLock.runtimes.opencode.version)
) {
throw new Error(`Built native dependency smoke failed: ${basename(executable)}`)
}
}
return {
platform,
agentVersion: lock.agentVersion,
remoteRuntimeVersion:
runtimeLock.runtimes.opencode.version,
@@ -214,5 +260,6 @@ if (require.main === module) {
module.exports = {
buildCiAgentBundle,
parseArguments
parseArguments,
readCiLocks
}
+13 -3
View File
@@ -19,6 +19,7 @@ const {
} = require('node:fs')
const { basename, dirname, join, relative, resolve, sep } = require('node:path')
const { tmpdir } = require('node:os')
const { targetName } = require('./agent-build-target.cjs')
const { crc32 } = require('node:zlib')
const {
buildAgentBundle,
@@ -345,6 +346,8 @@ function createPackageArchive(
function assembleAgentPackage(options) {
const projectRoot = options.projectRoot ?? root
const architecture = options.architecture
const platform = options.platform ?? 'linux'
const target = targetName(architecture, platform)
if (
!['x64', 'arm64'].includes(architecture) ||
!semanticVersionPattern.test(options.minimumDesktopVersion ?? '') ||
@@ -386,6 +389,7 @@ function assembleAgentPackage(options) {
options.agentBundle,
architecture,
{
platform,
projectRoot,
registry,
lock: agentLock,
@@ -395,6 +399,7 @@ function assembleAgentPackage(options) {
}
)
const runtime = verifyRuntimeBundle(options.runtimeBundle, {
platform,
projectRoot,
architecture,
registry,
@@ -405,14 +410,14 @@ function assembleAgentPackage(options) {
})
const expectedArchiveName =
`goodbuddy-agent-${agent.manifest.agentVersion}` +
`-linux-${architecture}.gbagent`
`-${target}.gbagent`
if (basename(options.output) !== expectedArchiveName) {
throw new Error(
`Agent package output must be named ${expectedArchiveName}`
)
}
const staging = mkdtempSync(
join(tmpdir(), `goodbuddy-agent-package-linux-${architecture}-`)
join(tmpdir(), `goodbuddy-agent-package-${target}-`)
)
try {
stagePackagePayload({
@@ -431,7 +436,7 @@ function assembleAgentPackage(options) {
component: 'agent',
version: agent.manifest.agentVersion,
minimumDesktopVersion: options.minimumDesktopVersion,
platform: 'linux',
platform,
architecture,
signingKeyId: signingIdentity.keyId,
agentProtocol: agent.manifest.protocol,
@@ -493,6 +498,7 @@ function buildAgentPackage(options) {
)
try {
const agentBundle = buildAgentBundle({
platform: options.platform ?? 'linux',
projectRoot,
arch: options.architecture,
runtimeArchive: options.nodeArchive,
@@ -504,6 +510,7 @@ function buildAgentPackage(options) {
: { signingIdentity })
})
const runtime = buildRuntimeBundle({
platform: options.platform ?? 'linux',
projectRoot,
architecture: options.architecture,
runtimeArchive: options.runtimeArchive,
@@ -544,6 +551,7 @@ function parseArguments(argv) {
if (
![
'--arch',
'--platform',
'--minimum-desktop-version',
'--node-archive',
'--runtime-archive',
@@ -571,10 +579,12 @@ function parseArguments(argv) {
throw new Error('Agent package arguments are incomplete')
}
const common = {
platform: options.platform ?? 'linux',
architecture: options.arch,
minimumDesktopVersion: options.minimumDesktopVersion,
output: resolve(options.output)
}
targetName(common.architecture, common.platform)
if (command === 'build') {
if (!options.nodeArchive || !options.runtimeArchive) {
throw new Error('Agent package build inputs are incomplete')
+1 -1
View File
@@ -336,7 +336,7 @@ function main() {
const result = spawnSync(
process.execPath,
[
join(root, 'node_modules', 'electron-builder', 'cli.js'),
join(root, 'build', 'run-electron-builder.cjs'),
'--dir',
'--x64',
`--config.directories.output=${stagingRoot}`,
+25 -1
View File
@@ -88,9 +88,11 @@ const portableRequiredFiles = [
'resources/release-notes.json',
'resources/icon.ico',
'resources/tray-icon.png',
'resources/tool-environment/managed-python-artifacts.json',
'resources/runtimes/opencode/opencode.exe',
'resources/runtimes/continue/package.json',
'resources/runtimes/npm/bin/npm-cli.js',
'resources/runtimes/npm/bin/npx-cli.js',
'resources/runtimes/npm/package.json',
'resources/runtimes/npm/node_modules/graceful-fs/package.json'
]
@@ -346,7 +348,7 @@ function buildElectronBuilderArguments(options, outputDirectory) {
)
)]
const builderArguments = [
join(root, 'node_modules', 'electron-builder', 'cli.js'),
join(root, 'build', 'run-electron-builder.cjs'),
'--config',
releaseBuilderConfig,
definition.builderFlag,
@@ -894,6 +896,10 @@ function verifyHarnessPackage(
join(npmRoot, 'bin', 'npm-cli.js'),
'DSH 插件安装 npm CLI'
)
assertFile(
join(npmRoot, 'bin', 'npx-cli.js'),
'DSH 插件安装 npx CLI'
)
if (
!Array.isArray(npmManifest.bundleDependencies) ||
npmManifest.bundleDependencies.length === 0
@@ -1053,6 +1059,14 @@ function verifyUnpackedOutput(directory, options) {
assertFile(applicationExecutable, '应用主程序')
assertFile(join(resources, 'app.asar'), '应用 ASAR')
assertFile(join(resources, 'release-notes.json'), '版本更新说明')
assertFile(
join(
resources,
'tool-environment',
'managed-python-artifacts.json'
),
'托管 Python 产物目录'
)
assertFile(
join(resources, 'agent-release-keys.json'),
'Agent 受信任签名密钥'
@@ -1080,6 +1094,16 @@ function verifyUnpackedOutput(directory, options) {
),
'DSH 插件安装 npm Runtime'
)
assertFile(
join(
resources,
'runtimes',
'npm',
'bin',
'npx-cli.js'
),
'DSH 插件安装 npx Runtime'
)
verifyHarnessPackage(resources, options)
for (const [filePath, label] of [
[applicationExecutable, '应用主程序'],
-401
View File
@@ -1,401 +0,0 @@
const { existsSync, readFileSync, rmSync } = require('node:fs')
const { gzipSync } = require('node:zlib')
const { resolve } = require('node:path')
// These are hard regression ceilings, not user-experience targets. In
// particular, do not lazy-load one peer tab solely to stay below a ceiling.
// The Settings ceiling is the validated 2026-08-23 production baseline
// (648.78 kB raw / 97.79 kB gzip), rounded up with about 15% review headroom.
const rendererBundleBudgets = Object.freeze({
initial: Object.freeze({ raw: 3_500_000, gzip: 720_000 }),
knowledge: Object.freeze({ raw: 330_000, gzip: 50_000 }),
graph: Object.freeze({ raw: 3_500_000, gzip: 720_000 }),
activity: Object.freeze({ raw: 55_000, gzip: 9_000 }),
magicNotes: Object.freeze({ raw: 630_000, gzip: 130_000 }),
settings: Object.freeze({ raw: 750_000, gzip: 115_000 })
})
function normalizePath(value) {
return value.replaceAll('\\', '/')
}
function findEntryKey(manifest) {
const entries = Object.entries(manifest).filter(
([, item]) => item && item.isEntry
)
if (entries.length !== 1) {
throw new Error(
`Expected one renderer entry in the manifest, found ${entries.length}`
)
}
return entries[0][0]
}
function findSourceKey(manifest, sourceSuffix) {
const normalizedSuffix = normalizePath(sourceSuffix)
let matches = Object.entries(manifest).filter(([key, item]) => {
const source = normalizePath(item.src || key)
return source.endsWith(normalizedSuffix)
})
if (matches.length === 0) {
const sourceName = normalizedSuffix
.split('/')
.at(-1)
?.replace(/\.[^.]+$/u, '')
matches = Object.entries(manifest).filter(
([, item]) =>
item?.isDynamicEntry &&
item.name === sourceName
)
}
if (matches.length !== 1) {
throw new Error(
`Expected one manifest entry for ${sourceSuffix}, found ${matches.length}`
)
}
return matches[0][0]
}
function findModuleOwnerKeys(manifest, moduleManifest, pattern) {
const keysByFile = new Map(
Object.entries(manifest).map(([key, item]) => [item.file, key])
)
const keys = new Set()
for (const [file, modules] of Object.entries(moduleManifest)) {
if (modules.some((id) => pattern.test(normalizePath(id)))) {
const key = keysByFile.get(normalizePath(file))
if (!key) {
throw new Error(`Module manifest chunk is missing: ${file}`)
}
keys.add(key)
}
}
if (keys.size === 0) {
throw new Error(`No renderer chunk owns modules matching ${pattern}`)
}
return keys
}
function assertSafeModuleManifest(moduleManifest) {
for (const modules of Object.values(moduleManifest)) {
for (const id of modules) {
const normalized = normalizePath(id)
if (
normalized !== id ||
normalized.startsWith('/') ||
normalized.startsWith('../') ||
/[A-Za-z]:\//u.test(normalized) ||
normalized.includes('/Users/') ||
normalized.includes('/home/')
) {
throw new Error(`Unsafe renderer module manifest path: ${id}`)
}
}
}
}
function collectSynchronousClosure(manifest, rootKey) {
if (!manifest[rootKey]) {
throw new Error(`Manifest entry is missing: ${rootKey}`)
}
const visited = new Set()
const pending = [rootKey]
while (pending.length > 0) {
const key = pending.pop()
if (visited.has(key)) {
continue
}
const item = manifest[key]
if (!item) {
throw new Error(`Manifest import is missing: ${key}`)
}
visited.add(key)
for (const imported of item.imports || []) {
pending.push(imported)
}
}
return visited
}
function collectAssetFiles(manifest, keys) {
const files = new Set()
for (const key of keys) {
const item = manifest[key]
if (!item) {
throw new Error(`Manifest entry is missing: ${key}`)
}
files.add(normalizePath(item.file))
for (const cssFile of item.css || []) {
files.add(normalizePath(cssFile))
}
}
return files
}
function measureFiles(files, readAsset, metricCache = new Map()) {
let raw = 0
let gzip = 0
const measuredFiles = [...files].sort()
for (const file of measuredFiles) {
let metrics = metricCache.get(file)
if (!metrics) {
const bytes = Buffer.from(readAsset(file))
metrics = {
raw: bytes.byteLength,
gzip: gzipSync(bytes).byteLength
}
metricCache.set(file, metrics)
}
raw += metrics.raw
gzip += metrics.gzip
}
return { raw, gzip, files: measuredFiles }
}
function withoutItems(keys, excluded) {
return new Set([...keys].filter((key) => !excluded.has(key)))
}
function describeEntry(
manifest,
key,
initialKeys,
initialFiles,
readAsset,
metricCache
) {
const closureKeys = collectSynchronousClosure(manifest, key)
const closureFiles = collectAssetFiles(manifest, closureKeys)
const incrementalKeys = withoutItems(closureKeys, initialKeys)
const incrementalFiles = withoutItems(closureFiles, initialFiles)
const root = measureFiles(
collectAssetFiles(manifest, new Set([key])),
readAsset,
metricCache
)
return {
key,
file: manifest[key].file,
rootRaw: root.raw,
rootGzip: root.gzip,
closureKeys,
incrementalKeys,
...measureFiles(incrementalFiles, readAsset, metricCache)
}
}
function assertDisjoint(description, keys, forbiddenKeys) {
const matches = [...forbiddenKeys].filter((key) => keys.has(key))
if (matches.length > 0) {
throw new Error(
`${description} synchronously includes ${matches.join(', ')}`
)
}
}
/**
* @typedef {object} RendererAnalysisKeys
* @property {string} initial
* @property {string} knowledge
* @property {string} graph
* @property {string} activity
* @property {string} magicNotes
* @property {string} settings
* @property {string[]} g6
*/
/**
* Analyzes one renderer manifest.
*
* `keys` is the authoritative set of discovered roots and module owners;
* notably, `keys.g6` is always an array because G6 may span several chunks.
*
* @returns {{ keys: RendererAnalysisKeys, sections: Record<string, object> }}
*/
function analyzeRendererManifest(manifest, readAsset, moduleManifest) {
assertSafeModuleManifest(moduleManifest)
const metricCache = new Map()
const keys = {
initial: findEntryKey(manifest),
knowledge: findSourceKey(manifest, 'KnowledgeWorkspace.tsx'),
graph: findSourceKey(manifest, 'KnowledgeGraphChart.tsx'),
activity: findSourceKey(manifest, 'ActivityPanel.tsx'),
magicNotes: findSourceKey(manifest, 'MagicNotesWorkspace.tsx'),
settings: findSourceKey(manifest, 'SettingsPanel.tsx')
}
const g6Keys = findModuleOwnerKeys(
manifest,
moduleManifest,
/(?:^|\/)node_modules\/@antv\/g6\//u
)
const initialKeys = collectSynchronousClosure(manifest, keys.initial)
const initialFiles = collectAssetFiles(manifest, initialKeys)
const initialRoot = measureFiles(
collectAssetFiles(manifest, new Set([keys.initial])),
readAsset,
metricCache
)
const initial = {
key: keys.initial,
file: manifest[keys.initial].file,
rootRaw: initialRoot.raw,
rootGzip: initialRoot.gzip,
closureKeys: initialKeys,
incrementalKeys: initialKeys,
...measureFiles(initialFiles, readAsset, metricCache)
}
const sections = {
initial,
knowledge: describeEntry(
manifest,
keys.knowledge,
initialKeys,
initialFiles,
readAsset,
metricCache
),
graph: describeEntry(
manifest,
keys.graph,
initialKeys,
initialFiles,
readAsset,
metricCache
),
activity: describeEntry(
manifest,
keys.activity,
initialKeys,
initialFiles,
readAsset,
metricCache
),
magicNotes: describeEntry(
manifest,
keys.magicNotes,
initialKeys,
initialFiles,
readAsset,
metricCache
),
settings: describeEntry(
manifest,
keys.settings,
initialKeys,
initialFiles,
readAsset,
metricCache
)
}
assertDisjoint(
'The renderer entry',
initialKeys,
new Set([keys.knowledge, keys.graph, keys.activity, ...g6Keys])
)
assertDisjoint(
'The Knowledge shell',
sections.knowledge.closureKeys,
new Set([keys.graph, ...g6Keys])
)
for (const g6Key of g6Keys) {
if (!sections.graph.closureKeys.has(g6Key)) {
throw new Error('The graph chunk no longer synchronously owns G6')
}
}
return { keys: { ...keys, g6: [...g6Keys] }, sections }
}
function checkBudgets(analysis, budgets = rendererBundleBudgets) {
const failures = []
for (const [name, budget] of Object.entries(budgets)) {
const section = analysis.sections[name]
if (!section) {
failures.push(`Unknown budget section: ${name}`)
continue
}
for (const metric of ['raw', 'gzip']) {
if (section[metric] > budget[metric]) {
failures.push(
`${name} ${metric} ${section[metric]} exceeds ${budget[metric]}`
)
}
}
}
if (failures.length > 0) {
throw new Error(`Renderer bundle budget failed:\n- ${failures.join('\n- ')}`)
}
}
function formatBytes(bytes) {
return `${(bytes / 1000).toFixed(2)} kB`
}
function formatReport(analysis) {
return [
'Renderer bundle budget:',
...Object.entries(analysis.sections).map(
([name, section]) =>
`- ${name}: root ${formatBytes(section.rootRaw)} raw / ` +
`${formatBytes(section.rootGzip)} gzip, ` +
`incremental closure ${formatBytes(section.raw)} raw / ` +
`${formatBytes(section.gzip)} gzip`
)
].join('\n')
}
function checkRendererBundle(root = process.cwd()) {
const rendererRoot = resolve(root, 'out', 'renderer')
const manifestPath = resolve(rendererRoot, '.vite', 'manifest.json')
const moduleManifestPath = resolve(
rendererRoot,
'.vite',
'module-manifest.json'
)
if (!existsSync(manifestPath)) {
throw new Error(`Renderer manifest not found: ${manifestPath}`)
}
if (!existsSync(moduleManifestPath)) {
throw new Error(
`Renderer module manifest not found: ${moduleManifestPath}`
)
}
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
const moduleManifest = JSON.parse(
readFileSync(moduleManifestPath, 'utf8')
)
const analysis = analyzeRendererManifest(
manifest,
(file) => readFileSync(resolve(rendererRoot, file)),
moduleManifest
)
checkBudgets(analysis)
rmSync(moduleManifestPath)
return analysis
}
module.exports = {
analyzeRendererManifest,
checkBudgets,
checkRendererBundle,
collectAssetFiles,
collectSynchronousClosure,
findEntryKey,
findModuleOwnerKeys,
findSourceKey,
formatReport,
assertSafeModuleManifest,
measureFiles,
rendererBundleBudgets
}
if (require.main === module) {
try {
const analysis = checkRendererBundle()
console.log(formatReport(analysis))
} catch (error) {
console.error(error instanceof Error ? error.message : String(error))
process.exitCode = 1
}
}
+3 -2
View File
@@ -17,7 +17,7 @@ const {
const { app, utilityProcess } = require('electron/main')
const protocol = 'goodbuddy.deepseek-harness.control'
const controlVersion = 2
const controlVersion = 3
const byteProtocol = 'goodbuddy.deepseek-harness.byte-stream'
const byteProtocolVersion = 1
const configuredHostPath =
@@ -183,7 +183,8 @@ async function run() {
provider: 'goodbuddy',
model: 'qwen-plus',
supportsImageInput: false,
harnessVersion: '0.1.0-rc.8',
requestHeaders: {},
harnessVersion: '0.1.2-rc.1',
credentialRefs: ['GOODBUDDY_HARNESS_MODEL_API_KEY'],
skillPackages: [],
extensionPackages: [],
+120
View File
@@ -0,0 +1,120 @@
const { existsSync } = require("node:fs");
const {
mkdirSync,
readFileSync,
renameSync,
rmSync,
writeFileSync,
} = require("node:fs");
const { join } = require("node:path");
const { spawnSync } = require("node:child_process");
const opencodeVersion = "1.18.29";
const opencodePluginPackage = "@opencode-ai/plugin";
function npmInvocation() {
const npmCli = process.env.npm_execpath;
if (npmCli) {
return { command: process.execPath, prefixArgs: [npmCli] };
}
if (process.platform === "win32") {
throw new Error("npm_execpath is required to prepare OpenCode config");
}
return { command: "npm", prefixArgs: [] };
}
function prepareBundledOpenCodeConfig(projectDir) {
const targetDirectory = join(
projectDir,
".runtime-resources",
"opencode-config",
);
try {
const installed = JSON.parse(
readFileSync(
join(
targetDirectory,
"node_modules",
"@opencode-ai",
"plugin",
"package.json",
),
"utf8",
),
);
if (installed.version === opencodeVersion) {
return targetDirectory;
}
} catch {
// Rebuild an incomplete or stale offline config template.
}
const stagingDirectory = `${targetDirectory}.staging-${process.pid}`;
rmSync(stagingDirectory, { recursive: true, force: true });
mkdirSync(stagingDirectory, { recursive: true });
try {
writeFileSync(
join(stagingDirectory, "package.json"),
`${JSON.stringify(
{
private: true,
dependencies: {
[opencodePluginPackage]: opencodeVersion,
},
},
null,
2,
)}\n`,
"utf8",
);
writeFileSync(join(stagingDirectory, ".gitignore"), "node_modules\n");
const npm = npmInvocation();
const environment = { ...process.env };
delete environment.NODE_TLS_REJECT_UNAUTHORIZED;
const result = spawnSync(
npm.command,
[
...npm.prefixArgs,
"install",
"--ignore-scripts",
"--bin-links=false",
"--omit=dev",
"--omit=optional",
"--no-audit",
"--no-fund",
"--save-exact",
],
{
cwd: stagingDirectory,
encoding: "utf8",
env: environment,
maxBuffer: 16 * 1024 * 1024,
shell: false,
windowsHide: true,
},
);
if (result.status !== 0) {
throw new Error(
`Unable to prepare bundled OpenCode config: ${
result.error?.message ||
result.stderr ||
result.stdout ||
`npm exited with ${result.status}`
}`,
);
}
rmSync(targetDirectory, { recursive: true, force: true });
mkdirSync(join(targetDirectory, ".."), { recursive: true });
renameSync(stagingDirectory, targetDirectory);
return targetDirectory;
} finally {
if (existsSync(stagingDirectory)) {
rmSync(stagingDirectory, { recursive: true, force: true });
}
}
}
module.exports = {
opencodeVersion,
prepareBundledOpenCodeConfig,
};
+2 -2
View File
@@ -32,8 +32,8 @@ function validateItems(value, label) {
fail(`${label} contains a non-string item`)
}
const normalized = item.trim()
if (!normalized || normalized.length > 500) {
fail(`${label} contains an empty or oversized item`)
if (!normalized) {
fail(`${label} contains an empty item`)
}
return normalized
})
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -88,7 +88,7 @@ async function main() {
`${JSON.stringify(
{
name: '@deepseek-ai/dsh-llm',
version: '0.1.0-rc.8',
version: '0.1.2-rc.1',
private: true,
type: 'module'
},
+32
View File
@@ -0,0 +1,32 @@
'use strict'
function configureElectronBuilderLogLevels(
moduleManager = require(
'app-builder-lib/out/node-module-collector/moduleManager'
)
) {
const duplicateReferenceMessage =
moduleManager.LogMessageByKey.PKG_DUPLICATE_REF
if (
!duplicateReferenceMessage ||
!moduleManager.logMessageLevelByKey[
duplicateReferenceMessage
]
) {
throw new Error(
'当前 electron-builder 不支持依赖引用日志级别配置'
)
}
moduleManager.logMessageLevelByKey[
duplicateReferenceMessage
] = 'debug'
}
if (require.main === module) {
configureElectronBuilderLogLevels()
require('electron-builder/cli')
}
module.exports = {
configureElectronBuilderLogLevels
}
@@ -147,7 +147,7 @@ async function main() {
)
const packageArguments = [
resolve('node_modules/electron-builder/cli.js'),
resolve('build/run-electron-builder.cjs'),
'--projectDir',
project,
'--win',
+110 -119
View File
@@ -1,4 +1,4 @@
const { createHash } = require('node:crypto')
const { createHash } = require("node:crypto");
const {
chmod,
mkdir,
@@ -6,101 +6,100 @@ const {
rename,
rm,
stat,
writeFile
} = require('node:fs/promises')
const { existsSync } = require('node:fs')
const { join } = require('node:path')
const { spawnSync } = require('node:child_process')
const tar = require('tar')
const { sha256File } = require('./file-hash.cjs')
writeFile,
} = require("node:fs/promises");
const { existsSync } = require("node:fs");
const { join } = require("node:path");
const { spawnSync } = require("node:child_process");
const tar = require("tar");
const { sha256File } = require("./file-hash.cjs");
const {
opencodeVersion,
prepareBundledOpenCodeConfig,
} = require("./opencode-config.cjs");
const opencodeVersion = '1.18.9'
const architectureNames = {
1: 'x64',
3: 'arm64'
}
1: "x64",
3: "arm64",
};
const platformNames = {
darwin: 'darwin',
linux: 'linux',
win32: 'windows'
}
darwin: "darwin",
linux: "linux",
win32: "windows",
};
function sha512Integrity(contents) {
return `sha512-${createHash('sha512').update(contents).digest('base64')}`
return `sha512-${createHash("sha512").update(contents).digest("base64")}`;
}
async function lockedIntegrity(projectDir, packageName) {
const lock = JSON.parse(
await readFile(join(projectDir, 'package-lock.json'), 'utf8')
)
const entry = lock.packages?.[`node_modules/${packageName}`]
await readFile(join(projectDir, "package-lock.json"), "utf8"),
);
const entry = lock.packages?.[`node_modules/${packageName}`];
if (
entry?.version !== opencodeVersion ||
typeof entry.integrity !== 'string'
typeof entry.integrity !== "string"
) {
throw new Error(
`Missing locked ${packageName}@${opencodeVersion} integrity`
)
`Missing locked ${packageName}@${opencodeVersion} integrity`,
);
}
return entry.integrity
return entry.integrity;
}
function npmInvocation() {
const npmCli = process.env.npm_execpath
const npmCli = process.env.npm_execpath;
if (npmCli) {
return {
command: process.execPath,
prefixArgs: [npmCli]
}
prefixArgs: [npmCli],
};
}
if (process.platform === 'win32') {
throw new Error('npm_execpath is required to prepare bundled runtimes')
if (process.platform === "win32") {
throw new Error("npm_execpath is required to prepare bundled runtimes");
}
return {
command: 'npm',
prefixArgs: []
}
command: "npm",
prefixArgs: [],
};
}
async function downloadPackage(projectDir, packageName, integrity) {
const cacheDirectory = join(
projectDir,
'.runtime-resources',
'cache'
)
await mkdir(cacheDirectory, { recursive: true })
const cacheDirectory = join(projectDir, ".runtime-resources", "cache");
await mkdir(cacheDirectory, { recursive: true });
const archivePath = join(
cacheDirectory,
`${packageName}-${opencodeVersion}.tgz`
)
`${packageName}-${opencodeVersion}.tgz`,
);
if (existsSync(archivePath)) {
const cached = await readFile(archivePath)
const cached = await readFile(archivePath);
if (sha512Integrity(cached) === integrity) {
return archivePath
return archivePath;
}
await rm(archivePath, { force: true })
await rm(archivePath, { force: true });
}
const npm = npmInvocation()
const npm = npmInvocation();
const result = spawnSync(
npm.command,
[
...npm.prefixArgs,
'pack',
"pack",
`${packageName}@${opencodeVersion}`,
'--ignore-scripts',
'--json',
'--pack-destination',
cacheDirectory
"--ignore-scripts",
"--json",
"--pack-destination",
cacheDirectory,
],
{
cwd: projectDir,
encoding: 'utf8',
encoding: "utf8",
maxBuffer: 16 * 1024 * 1024,
shell: false,
windowsHide: true
}
)
windowsHide: true,
},
);
if (result.status !== 0) {
throw new Error(
`Unable to fetch ${packageName}: ${
@@ -108,115 +107,107 @@ async function downloadPackage(projectDir, packageName, integrity) {
result.stderr ||
result.stdout ||
`npm exited with ${result.status}`
}`
)
}`,
);
}
const output = JSON.parse(result.stdout)
const downloadedPath = join(cacheDirectory, output[0].filename)
const contents = await readFile(downloadedPath)
const output = JSON.parse(result.stdout);
const downloadedPath = join(cacheDirectory, output[0].filename);
const contents = await readFile(downloadedPath);
if (sha512Integrity(contents) !== integrity) {
await rm(downloadedPath, { force: true })
throw new Error(`Integrity verification failed for ${packageName}`)
await rm(downloadedPath, { force: true });
throw new Error(`Integrity verification failed for ${packageName}`);
}
if (downloadedPath !== archivePath) {
await rm(archivePath, { force: true })
await rename(downloadedPath, archivePath)
await rm(archivePath, { force: true });
await rename(downloadedPath, archivePath);
}
return archivePath
return archivePath;
}
module.exports = async function prepareBundledRuntimes(context) {
const platform = context.electronPlatformName
const architecture = architectureNames[context.arch]
const packagePlatform = platformNames[platform]
const platform = context.electronPlatformName;
const architecture = architectureNames[context.arch];
const packagePlatform = platformNames[platform];
if (!architecture || !packagePlatform) {
throw new Error(
`Bundled OpenCode does not support ${platform}/${context.arch}`
)
`Bundled OpenCode does not support ${platform}/${context.arch}`,
);
}
const suffix =
architecture === 'x64' ? `${architecture}-baseline` : architecture
const packageName = `opencode-${packagePlatform}-${suffix}`
const projectDir = context.packager.projectDir
architecture === "x64" ? `${architecture}-baseline` : architecture;
const packageName = `opencode-${packagePlatform}-${suffix}`;
const projectDir = context.packager.projectDir;
await prepareBundledOpenCodeConfig(projectDir);
const projectPackage = JSON.parse(
await readFile(join(projectDir, 'package.json'), 'utf8')
)
await readFile(join(projectDir, "package.json"), "utf8"),
);
await writeFile(
join(projectDir, 'out', 'main', 'package.json'),
join(projectDir, "out", "main", "package.json"),
`${JSON.stringify(
{
name: '@deepseek-ai/dsh-llm',
version:
projectPackage.dependencies['@deepseek-ai/dsh-llm'],
name: "@deepseek-ai/dsh-llm",
version: projectPackage.dependencies["@deepseek-ai/dsh-llm"],
private: true,
type: 'module'
type: "module",
},
null,
2
2,
)}\n`,
'utf8'
)
const integrity = await lockedIntegrity(projectDir, packageName)
const targetDirectory = join(
projectDir,
'.runtime-resources',
architecture
)
const readyPath = join(targetDirectory, '.ready.json')
const executable = platform === 'win32' ? 'opencode.exe' : 'opencode'
const preparedPath = join(targetDirectory, executable)
"utf8",
);
const integrity = await lockedIntegrity(projectDir, packageName);
const targetDirectory = join(projectDir, ".runtime-resources", architecture);
const readyPath = join(targetDirectory, ".ready.json");
const executable = platform === "win32" ? "opencode.exe" : "opencode";
const preparedPath = join(targetDirectory, executable);
const identity = {
packageName,
version: opencodeVersion,
integrity
}
integrity,
};
try {
const ready = JSON.parse(await readFile(readyPath, 'utf8'))
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' ||
typeof ready.executableSha256 === "string" &&
(platform === "win32" ||
((await stat(preparedPath)).mode & 0o111) !== 0) &&
(await sha256File(preparedPath)) === ready.executableSha256
) {
return
return;
}
} catch {
// Rebuild an incomplete or stale runtime cache.
}
const archivePath = await downloadPackage(
projectDir,
packageName,
integrity
)
const stagingDirectory = `${targetDirectory}.staging-${process.pid}`
await rm(stagingDirectory, { recursive: true, force: true })
await mkdir(stagingDirectory, { recursive: true })
const archivePath = await downloadPackage(projectDir, packageName, integrity);
const stagingDirectory = `${targetDirectory}.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 stagingExecutable = join(stagingDirectory, executable)
await rename(sourcePath, stagingExecutable)
if (platform !== 'win32') {
await chmod(stagingExecutable, 0o755)
strip: 1,
});
const sourcePath = join(stagingDirectory, "bin", executable);
const stagingExecutable = join(stagingDirectory, executable);
await rename(sourcePath, stagingExecutable);
if (platform !== "win32") {
await chmod(stagingExecutable, 0o755);
}
const executableSha256 = await sha256File(stagingExecutable)
const executableSha256 = await sha256File(stagingExecutable);
await writeFile(
join(stagingDirectory, '.ready.json'),
join(stagingDirectory, ".ready.json"),
JSON.stringify({ ...identity, executableSha256 }),
'utf8'
)
await rm(targetDirectory, { recursive: true, force: true })
await rename(stagingDirectory, targetDirectory)
"utf8",
);
await rm(targetDirectory, { recursive: true, force: true });
await rename(stagingDirectory, targetDirectory);
} finally {
await rm(stagingDirectory, { recursive: true, force: true })
await rm(stagingDirectory, { recursive: true, force: true });
}
}
};
-55
View File
@@ -1,55 +0,0 @@
# GoodBuddy 文档导航
GoodBuddy 文档按“文档类型 → 功能域”组织。新增文档应先选择类型,再放入对应功能目录,
避免继续把所有设计平铺到单一 `features` 目录。
## 产品需求
| 功能域 | 入口 |
| --- | --- |
| Task 与 Job | [Task 与 Job 总览](./prd/task-and-job/README.md) |
| Smart Heartbeat | [智能心跳 PRD](./prd/smart-heartbeat/smart-heartbeat-prd.md) |
| 助手工作栏 | [通用助手工作栏与执行空间 PRD](./prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md) |
| 会话监督 | [会话监督 PRD](./prd/supervision/conversation-supervision-prd.md) |
| 记忆 | [分区记忆 PRD](./prd/memory/partitioned-memory-prd.md) |
| 并行实验 | [并行实验工作台 PRD](./prd/experiments/parallel-experiments-prd.md) |
| 持续学习 | [持续学习与评估门 PRD](./prd/learning/continuous-learning-prd.md) |
| 知识库 | [知识库检索与分块增强 PRD](./prd/knowledge/knowledge-rag-enhancement-prd.md) |
| 文档处理 | [文档解析与本地 OCR](./prd/document-processing/document-extraction-and-local-ocr.md) |
| 消息通道 | [微信 ClawBot 通道 PRD](./prd/channels/wechat-clawbot-channel-project-prd.md) |
## Task 与 Job 文档
- [统一领域模型](./prd/task-and-job/task-and-job-model.md)
- [Task Center](./prd/task-and-job/task-center-prd.md)
- [Scheduled Task](./prd/task-and-job/scheduled-task-prd.md)
- [Goal Task](./prd/task-and-job/goal-task-prd.md)
- [Job 与 Subjob](./prd/task-and-job/job-and-subjob-prd.md)
## 跨功能文档
- [自动化平台架构](./architecture/automation-platform-architecture.md)
- [平台功能页签与模型下载源设计](./architecture/model-download-source-design.md)
- [本地文本向量模型与连接设计](./architecture/local-text-embedding-model-design.md)
- [全双工实时语音交互设计](./architecture/full-duplex-voice-design.md)
- [DeepSeek Harness Runtime 设计](./architecture/deepseek-harness-runtime-design.md)
- [应用内反馈系统设计与对接](./architecture/in-app-feedback-integration-design.md)
- [SSH 远程主机与 GoodBuddy Agent 稳定终态设计](./architecture/remote-host-and-goodbuddy-agent-design.md)
- [SSH Host 远程环境准备与控制面直连设计](./architecture/remote-host-environment-provisioning-design.md)
- [跨平台助手产品设计](./design/cross-platform-assistant-product-design.md)
- [长期助手路线图](./roadmap/long-term-assistant-roadmap.md)
- [电脑控制实施状态](./status/computer-control-implementation-status.md)
- [知识检索评估](./quality/knowledge-retrieval-evaluation.md)
- [GoodBuddy 龙芯(LoongArch)预览版构建与功能列表](./development/loongarch-preview-build.md)
- [统一界面设计系统](../UI-DESIGN.md)
## 目录规则
1. PRD 放在 `docs/prd/<功能域>/`。
2. 跨功能技术总纲放在 `docs/architecture/`。
3. 产品级设计放在 `docs/design/`,路线图和实施状态分别放在 `roadmap`、`status`。
4. 测试方法、评估协议和质量报告放在 `docs/quality/`。
5. 开发环境、实验性构建和移植说明放在 `docs/development/`。
6. 一个概念只能有一份权威定义;其他文档链接到它,不复制另一套术语。
7. Task、Job、Run、Subagent 和 Conversation 的含义以
[Task 与 Job 统一领域模型](./prd/task-and-job/task-and-job-model.md) 为准。
@@ -9,7 +9,7 @@
| 日期 | 2026-08-19 |
| 适用产品 | GoodBuddy 桌面端 |
| 文档角色 | Task/Job、调度、目标、并行实验、会话监督、分区记忆与持续学习的总纲 |
| 领域模型 | [Task 与 Job 统一领域模型](../prd/task-and-job/task-and-job-model.md) |
| 领域模型 | [Task 与 Job 统一领域模型](../features/task-and-job/task-and-job-model.md) |
## 1. 背景
@@ -37,11 +37,11 @@ GoodBuddy 当前已经具备若干长期助手能力,但它们仍是彼此分
## 2. 核心产品判断
### 2.1 智能心跳保持独立,未来分区记忆另行设计
### 2.1 智能心跳保持独立,分区记忆协同另行设计
当前智能心跳继续承担周期回顾、报告和建议,并支持 Global 或指定 Project 范围。它不是
Task、通用调度器或后台 Agent,也不进入统一 `AutomationPlan.kind`。未来分区记忆的
数据、状态、唤起和页面需要独立设计,不能从当前方向直接推导。
Task、通用调度器或后台 Agent,也不进入统一 `AutomationPlan.kind`。按分区回顾、候选生成
以及时间/内部事件唤起需要独立设计,不能从当前心跳行为直接推导,也不创建平行记忆类型。
- Scheduled Task 解决“何时在一个 Task 中执行新的 Job”。
- Goal Task 解决“围绕结果在同一 Task 中持续规划和推进”。
@@ -435,9 +435,9 @@ Renderer 只通过显式 IPC 列出、加入、删除、提升、释放和接收
└─ 独立成果
智能心跳
├─ 成长概览
├─ 运行概览
├─ 待处理建议
├─ 心跳轨迹
├─ 报告与记录
└─ 心跳计划
└─ Global / 指定 Project
```
@@ -445,7 +445,7 @@ Renderer 只通过显式 IPC 列出、加入、删除、提升、释放和接收
监督统一进入应用级助手工作栏中固定且始终可访问的“监督”栏目,不再保留“独立可折叠右栏”
和“动态新增页签”两种实现。栏目默认跟随当前上下文,用户可以固定到其他 Conversation、
Task 或实验对象;详细范围与交互契约见
[通用助手工作栏与执行空间 PRD](../prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md)。
[通用助手工作栏与执行空间 PRD](../features/assistant-workbar/prd.md)。
## 13. 安全与隐私
@@ -507,7 +507,7 @@ experiment_runs
现有 `schedules`、`schedule_runs`、`heartbeat_configs`、`heartbeat_runs`、
`heartbeat_entries`、`tasks` 和 `runs` 不应一次性重写。Schedule 可渐进建立稳定 Task 与
Conversation 关联,旧 child-task 字段可兼容映射到 Job/Subjob;心跳数据保持独立,不得
静默转成 `AutomationPlan` 或顶层 Task。未来分区记忆完成设计前,不新增迁移目标。
静默转成 `AutomationPlan` 或顶层 Task。分区记忆协同和唤起条件完成设计前,不新增迁移目标。
## 16. 分阶段实施
@@ -518,7 +518,7 @@ Conversation 关联,旧 child-task 字段可兼容映射到 Job/Subjob;心
- 明确 Task N:1 Conversation 关系、左侧行首展开按钮与 Task 子项图标,以及 Task Center
索引边界,不复制内容。
- 为现有 Scheduled Task 和专家执行建立按 Task 聚合的活动视图。
- 明确当前心跳保持独立,未来分区记忆尚待设计。
- 明确当前心跳保持独立,分区记忆协同与唤起条件尚待设计。
- 补充触发来源、运行版本、预算和读写范围展示。
### 阶段 1:调度与运行基础
@@ -555,21 +555,21 @@ Conversation 关联,旧 child-task 字段可兼容映射到 Job/Subjob;心
## 17. 相关文档
- [Task 与 Job 统一领域模型](../prd/task-and-job/task-and-job-model.md)
- [Task Center PRD](../prd/task-and-job/task-center-prd.md)
- [Scheduled Task PRD](../prd/task-and-job/scheduled-task-prd.md)
- [Job 与 Subjob PRD](../prd/task-and-job/job-and-subjob-prd.md)
- [智能心跳 PRD](../prd/smart-heartbeat/smart-heartbeat-prd.md)
- [并行实验工作台 PRD](../prd/experiments/parallel-experiments-prd.md)
- [会话监督 PRD](../prd/supervision/conversation-supervision-prd.md)
- [分区记忆 PRD](../prd/memory/partitioned-memory-prd.md)
- [持续学习与评估门 PRD](../prd/learning/continuous-learning-prd.md)
- [Task 与 Job 统一领域模型](../features/task-and-job/task-and-job-model.md)
- [Task Center PRD](../features/task-and-job/task-center-prd.md)
- [Scheduled Task PRD](../features/task-and-job/scheduled-task-prd.md)
- [Job 与 Subjob PRD](../features/task-and-job/job-and-subjob-prd.md)
- [智能心跳 PRD](../features/smart-heartbeat/prd.md)
- [并行实验工作台 PRD](../features/parallel-experiments/prd.md)
- [会话监督 PRD](../features/conversation-supervision/prd.md)
- [分区记忆 PRD](../features/memory/prd.md)
- [持续学习与评估门 PRD](../features/continuous-learning/prd.md)
- [GoodBuddy 长期助手功能规划](../roadmap/long-term-assistant-roadmap.md)
- [GoodBuddy 统一界面设计系统](../../UI-DESIGN.md)
## 18. 总体验收标准
- [ ] 智能心跳保持独立,不作为 Task 类型;未来分区记忆尚未设计。
- [ ] 智能心跳保持独立,不作为 Task 类型;分区记忆协同与唤起条件尚未设计。
- [ ] Task Center 只索引 Task;每个 Task 只关联一条 Conversation,一条 Conversation 可以
关联多个 Task。
- [ ] 当前 UI 只展示到 Task,不提供 Job/Subjob/Run 树或独立导航。
@@ -10,7 +10,7 @@
| 日期 | 2026-08-19 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 相关基线 | [统一界面设计系统](../../UI-DESIGN.md)、[文档解析与本地 OCR](../prd/document-processing/document-extraction-and-local-ocr.md)、[本地文本向量模型与连接设计](./local-text-embedding-model-design.md)、[全双工实时语音交互设计](./full-duplex-voice-design.md) |
| 相关基线 | [统一界面设计系统](../../UI-DESIGN.md)、[文档解析与本地 OCR](../features/document-processing/prd.md)、[本地文本向量模型与连接设计](../features/knowledge-base/local-embedding-technical-design.md)、[全双工实时语音交互设计](../features/full-duplex-voice/technical-design.md) |
本文定义“设置 → 平台功能”的二级页签结构,以及所有 GoodBuddy 托管本地模型共同使用的
“模型下载源”设置。首期下载源为 ModelScope 和 Hugging Face,默认使用 ModelScope。
@@ -1,328 +0,0 @@
# SSH 远程主机与 GoodBuddy Agent 实现说明
## 状态
本文记录截至 2026-08-29 的当前代码实现,不定义额外的信任框架。“新增 Host 只探测、Host 卡片手动准备
Agent/Runtime、Host 直接从 GitHub/北京镜像下载、项目始终使用 Host current 环境”已经完成源码接线,
详细事务与验收边界见
[SSH Host 远程环境准备与直连下载设计](./remote-host-environment-provisioning-design.md);
控制面直连源码可直接使用既有 package format v1 包,不等待携带 installer 的新 Agent
包,也不通过额外目录元数据判断 bootstrap 能力;公开能力仍等待 GitHub/北京镜像、
Linux x64/arm64、取消和离线 GoodBuddy 传输的真实 Host 验收。
Windows 到 Linux x64 的既有安装、
Main-only 模型桥、断线恢复、输出重放、同一 OpenCode Session 续接与终态清理已经在真实
Host 上完成 provider-free 验证。上一轮 Linux x64 验收使用 Agent `0.11.2-e2e.12` 与
OpenCode Runtime `1.18.9` 完成了一次有界真实模型工具调用;加入每 helper 随机 loopback
路径 capability 后,Agent `0.11.7` 已通过独立 workflow 发布 Linux x64/arm64 复合包和
签名累计目录;当前源码准备 `0.11.9`,修复 Host 更新并消除重复 payload 验证。失败的
`agent-v0.11.3` 保持不可变且未发布。
## 产品语义
远程 Host 与项目能力是默认关闭的技术预览。用户在“设置 > 平台功能”的独立
“远程项目(技术预览)”页签启用开关后,Renderer 才显示 SSH Host 管理、托管 SSH 项目创建和已保存
远程项目;Main 在 IPC 边界执行同一开关校验,避免隐藏界面被绕过。关闭开关不删除 Host、
项目或凭据;当前远程项目切回第一个普通本地项目。应用启动仍固定
选择普通本地项目,不会因为保存了远程项目而自动连接 Host。
远程项目只有两种工作模式:
- **Ask**:Runtime 在操作系统边界以只读方式访问项目 Workspace。
- **Execute**:用户已授权使用所选 SSH 账号的完整权限。Runtime 可以使用该账号可访问的文件、进程、网络和工具,不再要求额外 trust tier、consent checklist、逐工具审批或“受控执行”授权。
Execute 不获得 root 或 SSH 账号本身没有的权限。模型 API Key 与 Provider 认证仍保留在 Electron Main,不传给远端 Runtime。
## 组件
```text
Renderer
-> 窄 Preload / Main IPC
Main
-> SSH Host 与 Host Key 管理
-> SSH connection pool
-> 签名 Agent / Runtime 安装
-> 远程项目当前环境准备与稳定配置 SQLite 事务
-> Main-owned model bridge
SSH attach
-> 远端私有 Unix socket
Detached GoodBuddy Agent
-> Workspace / Git 协议
-> OpenCode ACP channel
-> 直接拥有的 Runtime 进程
```
不提供把远端路径交给本机 Runtime 的伪本地模式,也不提供 SSH stdio Runtime fallback。
## SSH Host
- Renderer 只管理脱敏 Host 快照,不接触密码、密文、SSH Client 或任意命令接口。
- Main 在认证前取得 Host Key,展示算法与 SHA-256 指纹,并保存完整 key blob。
- 首次连接需要用户确认。Host Key 变化展示旧/新值并要求显式替换。
- 密码使用 Electron `safeStorage`;系统 SSH Agent 认证不启用 Agent forwarding。
- Host 地址、用户或 Host Key generation 变化时关闭旧连接,并定向退役依赖旧 Host identity 的 Workspace 和 Runtime 会话。下一次选择使用 Host 管理的当前连接记录解析 current Agent/Runtime,不读取项目中的旧 revision 或组件 identity;当前 registry、连接或 capability 无效时才要求显式修复。
## Agent 安装与生命周期
- Agent bundle 通过 manifest、Ed25519 签名、payload digest、平台和架构校验。
- 安装使用当前 SSH 用户目录中的 GoodBuddy-owned 路径和 side-by-side digest 目录。
- Agent 代码与固定 Node Runtime 一起位于签名 compound payload。显式安装或重装从本次
已完整校验的准备目录原子发布整个 Agent,不扫描旧安装、不创建跨版本 Node 硬链接;
Agent 不匹配不会阻止升级,也不会覆盖 GoodBuddy-owned 路径以外的内容。
- 桌面包只携带版本 lock 与 production 公钥,不携带远端 payload。Host 卡片只有一个按
版本事实显示“安装远程环境”“更新远程环境”或“重新安装”的主按钮;次级
SegmentedControl 选择“自动”“Host 下载”或“GoodBuddy 传输”,默认“自动”且不持久化。
每个复合包绑定 Agent、固定 Node 与桌面维护的 OpenCode Runtime 精确工件。添加或重新
验证 Host 只保存并探测,不自动传输完整包。
- Host 管理独占 Agent/Runtime 的包准备、更新和完整 payload 验证。已有项目的打开和切换
只更新本地项目选择。Workspace 和执行路径按需读取 Host current registry,
执行固定 `attach-or-bootstrap` 并复用当前 Agent 连接;同一进程内复用已确认 identity。
它们不取得安装包、不发布组件,也不通过 SFTP 重读或哈希 payload。
- Agent 的固定 attach/按需启动命令只读取 Host 管理已提交的 installation registry 和
与其 digest 匹配的有界 manifest 元数据,不在每次项目切换时重新验签或哈希完整 payload。
完整验签、payload 扫描和 registry 写入仍只发生在显式 Host 准备/更新流程。
- 项目切换不执行远程核对,立即选择本地项目配置。Workspace 或 Runtime 首次实际使用时
才通过当前 Agent 连接核对所需能力;失败只影响该操作。新建或显式保存项目仍执行完整
准备,并只事务写入 Host、路径、Runtime 选择和工作模式。
- SSH 连接先尝试 attach;Agent 不存在或未运行时执行幂等 bootstrap,然后重新 attach。
- Agent 是按需启动的 detached process,不注册开机服务,不依赖 systemd、D-Bus 或 Linger。
- 每个模型桥 helper 都为 loopback HTTP 入口生成一次性随机路径 capability;只有写入当前 OpenCode 子进程配置的 URL 可以访问该入口,其他本机用户即使发现临时端口也不能提交模型请求。
- Agent 监听当前用户拥有的私有 Unix socket。SSH 中断只关闭 relay,不拥有 Agent 和活动 Runtime 的生命周期。
- SSH Host 设置通过固定探针和有界 SFTP 显示 Host 已登记的 Agent/OpenCode 版本,并与当前
GoodBuddy 所需版本比较。进入或切回该设置页只读取本地 Host 列表,不连接任何 Host;
用户点击“刷新版本”后才探测对应 Host,探测不安装或切换远端组件。项目切换与此按钮
解耦且不建立连接;实际远程操作才建立或复用 SSH/Agent 连接。
- “平台功能 > 远程项目”中的本地 Agent 包清单与 Host 状态分离。Main 对用户数据目录中
Linux x64/arm64 `.gbagent` 分别执行外层可信签名、桌面/协议兼容性、内部 Agent/Runtime
签名、架构与完整 payload 校验;首次打开和手动刷新还会读取“关于与更新”所选来源的
小型签名目录。Renderer 只得到本地/在线版本、是否有更新、架构、远端 Runtime 版本、
协议和本地状态,不得到缓存路径、key ID 或 digest。目录检查不下载 `.gbagent`,在线
包下载只由用户点击带目标版本的操作触发;离线导入/导出通过 Main 管理的文件对话框完成。
- “自动”只在 operation/prepare 前用 Host capability probe 选择 acquisition:直连明确
可用才选 Host 下载,否则选 GoodBuddy 传输;显式选择不被改写。prepare、commit 或
adoption 失败后都不跨 acquisition 自动 fallback。
- Host 下载由桌面控制面通过固定 SSH prepare channel 和结构化 stdin 发送候选信息,
Host 取得 compound `.gbagent` 并核对大小与 SHA-256。显式 GoodBuddy 传输在在线目录
不可用,或本地已验证包版本不低于在线候选时优先租用本地包,包括更高版本离线导入;
线上存在更新且可用时仍取得线上新包。需要下载时,同一次操作会验 SHA-256 与签名、
缓存并取得 lease,再以有界流式 SFTP 上传一个 compound archive 和归档中已验证的
bootstrap Node;约 294 MiB 完整包不会一次读入 Main `Buffer`,Host 仍再次校验完整包。
- 两种 acquisition 交付到固定 operation staging 后,完全共用 control-plane
`prepare → commit → Agent activate/health → Runtime activate → finalize → cleanup`。
Main 只保存清理 operation staging 所需的 operation ID。中断或 adoption 失败后,下次
更新尽力清理旧暂存并重新 prepare,不保存或恢复 Agent/Runtime metadata 副本。确认
adoption 后才显式 cleanup;cleanup 失败不回滚健康环境,也不阻塞下一次 fresh prepare。
Host 在 prepare 解包时
完成一次完整 payload 校验,commit 和后续注册检查不再遍历包体。首次 bootstrap 不依赖
既有 Agent daemon,也不要求 format v1 归档携带 `agent/lib/package-installer.cjs`。
- 即使版本号均为当前版本,Host 卡片仍提供同版本“重新安装”,用于修复 registry、签名
或安装 identity 异常。GoodBuddy-owned 同 digest 目录损坏时先隔离后替换,并在发布或
原子 commit 失败时恢复;commit 成功后的激活失败保留新发布内容,下次点击重新安装时
清理旧暂存并重新 prepare/adopt。不要求删除 Host、凭据或引用项目。
- Host 卡片的“版本匹配”badge 只表达版本事实,不代表 Agent 正在运行或环境健康。重新安装
失败时明确显示本次操作未完成,并以随后重新检查的版本卡片表达当前事实;提交结果
不确定时不声称旧版本未被替换。
- 更新成功后才定向清理该 Host 的 Agent 连接和 Runtime 选择缓存。引用项目再次打开时
解析新的 current registry 并建立当前连接,无需刷新项目记录;当前环境无效时要求用户
显式修复,但不在项目切换中下载或安装。失败或取消保留 Host 配置、凭据、项目、
Workspace 和旧组件。
- 显式 stop、升级、身份冲突或进程退出时清理 GoodBuddy 自己的 socket、状态和子进程;不得删除或覆盖无关 Host 文件。
### Host 级环境生命周期
- Agent、Node 和 Runtime 是 Host/SSH 账号级共享环境。新增或重新验证 Host 后先保存 Host
并只读探测,不自动安装;用户随后在 Host 卡片明确选择安装或更新方式,失败保留 Host
并允许重试。
- Host 可以按签名目录固定的 URL、大小和 SHA-256 直接从当前 GitHub/北京镜像来源下载
完整 `.gbagent`,也可使用 GoodBuddy 本机下载、流式 SFTP 传输和离线导入。自动模式只在
操作开始时择一;执行失败不会在同一次操作中切换 acquisition。
- 项目只保存 Host ID、远端路径、Runtime 选择和默认模式。创建或保存时验证 Host current
环境;打开或切换只读取本地配置。Workspace/Runtime 实际使用时从 Host current registry
和当前 Agent 连接取得 live identity,不再扫描、下载、上传或发布 Agent/Runtime。完整
事务、兼容边界和验收要求以
[Host 环境准备设计](./remote-host-environment-provisioning-design.md) 为准。
- 打开新建/项目设置弹窗只读取本地 Host 验证记录,不并发检查所有 Host;目录浏览或保存
才连接所选 Host。
- 项目选择器中的管理操作默认浮动隐藏,在悬停、键盘焦点或触屏环境显示。管理任意已保存
项目不先激活项目,因此 Host 不可达或远端目录已不存在时仍可删除其本地记录。
- 删除 Host 时确认框列出所有引用它的本地项目记录,包括归档项目。确认后只删除本机
Host、凭据、项目及其关联记录,不连接 Host、不删除远端目录或内容;Host 设置写入或
项目事务失败时恢复另一侧,避免只删掉其中一类本地记录。
## Workspace
Main 传入已规范化的绝对 POSIX root。Agent 返回 Workspace identity、访问模式、Git 状态和有界能力。
- 创建项目时,用户可以直接输入远端工作目录,或通过输入框右侧的文件夹按钮浏览并选择目录。
- 项目保存前的目录浏览使用 Main 管理的只读、有界 SFTP,只返回目录;每次请求重新解析并校验当前 Host revision 与 Host Key generation,不安装 Agent、不暴露任意 shell/SFTP 接口。
- 浏览从 SSH 账号 Home 或当前有效绝对路径开始,限制扫描次数、返回条目和总时限;取消、Host 变化、超时或连接失败时关闭 SFTP 并保留用户原先输入。
- 选择目录只更新项目草稿,仍需通过正常的 Agent、Workspace、Runtime 验证和项目保存事务才会持久化。
- Ask Workspace handle 不暴露写入方法。
- Execute Workspace handle 可读写,但这不是 Execute 的唯一权限面;Execute Runtime 本身使用 SSH 账号的正常权限。
- 文件预览、搜索、Git diff、目录项和显式文件传输保持字节、条目数和路径长度上限。
## Runtime
首个远程 Runtime 固定为签名 OpenCode ACP bundle。
托管 SSH 项目的 Composer Runtime 菜单只显示当前配置的 OpenCode 和管理入口,不显示
直连模型、Continue 或 DeepSeek Harness。激活旧远程会话时,若其保存了其他 Runtime
selection,Renderer 会恢复为当前 OpenCode 配置;Main 的远程请求校验仍是最终边界。
### Ask
Ask 使用系统 `bwrap`:
- Workspace 和 Runtime bundle 只读 bind;
- 独立可写 scratch HOME、TMPDIR 和 XDG 目录;
- 固定环境名和固定 Runtime argv;
- 通过只读边界阻止 Workspace 修改。
- ACP 权限请求只有在工具种类为原生 `read` 且 Runtime 提供 `allow_once` 选项时才允许;
search、edit、execute、未知工具以及只提供持久授权的请求全部拒绝。
`allow_once` 只解决 OpenCode 发起原生读取前的 ACP 协商,真正的文件系统边界仍是只读
`bwrap` bind。Host 缺少 `bwrap` 时 Ask 不可用。
### Execute
Execute 不经过 Ask 的 bubblewrap profile:
- 直接启动已签名 Runtime entrypoint;
- 使用 Main-only 模型桥时,签名 Agent launcher 会 `exec` 候选 manifest 锁定的
Node Runtime;进程 owner 校验最终 Node executable,而不是已经被替换的 shell
launcher 路径;
- `cwd` 为项目 Workspace;
- 继承 SSH 账号的正常环境、文件系统、进程和网络能力;
- 不进行 T2/T3、confinement attestation、approval bridge 或逐工具批准。
两种模式都保留请求 deadline、输入/输出字节上限、取消和进程组清理。
## ACP 与断线
- 管控面只负责有界 JSON-RPC:连接、查询、启动、取消、关闭和重连。数据面只负责
ACP/blob 数据与 Agent 本机 journal。二者共享同一个经过 Host Key 校验和认证的
SSH attach 字节流,但不共享生命周期:管控连接关闭不会隐式取消 Agent 已接受的
Runtime 动作。
- 帧只保留连接 identity、channel identity、单调 sequence、类型和长度。Main 与
Agent 都按调用顺序 FIFO 写入,输入队列达到固定字节或条目上限时暂停 SSH stream。
顺序、可靠传输、分包和网络背压全部交给 SSH;协议不实现 credit/window、优先级
调度、跨 channel 超车、批发送或第二套拥塞控制。
- Agent 在把 Main 输入交给 Runtime 前、以及把 Runtime 输出交给 Main 前,先把 ACP frame 写入本机 journal。ACK 只推进 cursor 并裁剪已确认 frame,不表示 channel 已终止。
- Main 持久化 binding identity 和单调 cursor;保留中的 connection lease 即使处于 offline/reconnecting,也可以先把新 cursor 落盘。重连提交不能覆盖 resume 过程中并发落盘的更新。
- 同一 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 仍被拒绝。
- Main 到 Runtime 的 ACP 输入、模型请求、工具请求和 blob 不自动重放。
- 无法确认外部 Provider 是否已处理的模型调用保持结果未知,避免重复计费或重复副作用。
- 只有远端返回 identity 匹配且 `closed: true` 时,Main 才删除持久化 binding;传输失败或未确认 close 会保留 recovery identity。
- 终态 close 由 Agent 在一个事务中为两个方向记录 sequence high-water tombstone,删除剩余 frame 和 active channel,并归零对应 journal quota。tombstone 用于拒绝迟到的旧 epoch frame,因此当前不会按时间自动裁剪。
- 断线不主动终止正在运行的 Runtime。用户取消、deadline、显式关闭或 Agent shutdown 才触发停止。
- Agent 失联期间继续把 Runtime 输出写入本机 journal;重新连接只校验 controller
identity、恢复 binding,并从 Main 最后确认的 cursor 后同步缺失数据。已接受的
指令和结果不确定的 Provider 请求都不自动重放。
## 模型桥
- Provider URL、API Key 和认证头只存在于 Main。
- 远端 OpenCode 通过 GoodBuddy Agent helper 和每次 Prompt 的私有 Unix socket 使用模型桥。
- Main 校验固定模型 profile、协议、请求路径和有界传输格式,并记录 Provider
返回的实际 usage;不限制 Prompt 内的模型调用轮数、工具调用次数、累计 Token
或单次模型输出 Token。取消、Runtime deadline、请求/响应字节上限和结果不确定时
禁止自动重放仍然保留。
- helper 可以接收同一 Prompt 内并发到达的模型桥请求;它在单一稳定模型桥上按到达
顺序等待并交付,不返回本地 `bridge-busy`,每个响应只有在 HTTP 完整 flush 后才
发送 delivery ACK。
- GoodBuddy 自己管理会话标题,因此传给 OpenCode 的配置禁用 title Agent;一次用户
Prompt 不会额外触发标题模型请求。
- 一条模型桥消息编码为一个 canonical JSON blob frame;blob frame 最大 2 MiB,
Provider 请求与响应 body 仍各自限制为 768 KiB。每条模型消息只调用一次 SSH
write,不再二次分片、等待逐帧 credit 或合并多个协议帧。凭据不进入 Renderer、
SSH 命令参数或远端环境。
- Unix socket 接收端按 `readableLength` 中已经缓冲的字节增量排空一个声明长度的帧,
不会在部分大响应到达时反复请求尚未缓冲的完整剩余长度;短读、连接结束、错误和取消
都会使当前交换失败。原生 Linux 回归覆盖至少 256 KiB 的 broker 响应。
- Renderer 把相邻 text/reasoning delta 合并为消息 block 时始终替换最后一个 block,
不修改既有 React state 对象。这样开发环境 StrictMode 重复调用 state updater 时,
block metadata 与 canonical 消息正文保持一致。
## 已完成的 E2E 验收记录
- 2026-08 的本地 fixture 完整验证 Linux x64 Agent `0.11.2-e2e.12`、Node `24.19.0`
和 Agent protocol `2.0`;当时没有 arm64 fixture,因此该记录不能作为当前独立发布
的双架构验收。Agent `0.11.7` 后续已由原生 workflow 发布并公开验证双架构工件;
当前源码 lock 是修复候选 `0.11.9`。
- 正常 Host 更新路径把 Linux x64 Host 的 Agent 更新为 `0.11.2-e2e.12`,并确认
OpenCode Runtime 已安装版本与所需版本均为 `1.18.9`。
- 一条新的 Ask 用户操作只提交一次。OpenCode 先在 build 模型轮次请求一个原生
`read`,读取专用测试 Workspace 中的证据文件,再在第二个 build 模型轮次生成最终
回答;没有 title 模型轮次,也没有第二个工具调用。
- 两个模型轮次都记录已交付,远端 helper 保持休眠且 Unix socket 没有未读响应积压。
持久化的 canonical 助手正文精确为 `GOODBUDDY_REMOTE_TOOL_4C6B9D8A`。验收操作没有
重放任何结果未知或已经完成的 Provider 请求。
## 项目保存
远程项目只持久化稳定配置:
- Host ID;
- 规范远端工作目录;
- Runtime selection;
- `ask | execute` 默认模式。
Agent installation、Host revision/Host Key generation、Workspace identity、Runtime
bundle/adapter digest 和 capability generation 都属于 live connection/lease,不进入项目
domain object 或 SQLite。打开托管 SSH 项目不接受 Renderer 回传的旧项目草稿,而是从 Host
store 解析当前连接目标,通过 Agent/Runtime 安装管理器的 current/activate 读取路径取得
registry identity,再用同一个 Agent 连接验证 Workspace 路径和 Runtime capability。该流程
不会扫描完整 payload、取得安装包或发布组件。
实时 Runtime 创建只接受 OpenCode,使用当前解析后的模型 profile 建立 Main-only 模型桥,
从当前 Agent 连接和 Runtime registry 取得会话 identity,并在该连接上打开 Workspace 和
ACP channel。Host 编辑或环境更新会定向失效 Agent 连接与 Runtime 缓存,下一次请求自然
重新取得 current 环境;无需修改项目。Ask/Execute 权限继续由每次 Prompt 的 ACP Runtime
边界执行。
Renderer 在用户主动打开已有托管 SSH 项目时立即切换,不显示远程激活状态。Host、Agent、
Workspace、Runtime 和 Saving 进度仅用于新建或显式保存项目;进行中可显式取消并禁用会
产生冲突的项目创建和设置入口。远端 RPC 拒绝只向
Renderer 暴露固定方法名、数字 RPC code 和有界 service code,不转发 Host 私有错误详情。
`runtime/preparePrompt` 在远端明确拒绝且尚未接受 Prompt 时会关闭对应 Main binding,
不会把确定性失败错误保留为 `outcome-unknown`。
不持久化 T2/T3、consent、approval bridge、confinement 或组件验证结果。数据库 schema
v31 保留项目及关联用户数据,但重建执行空间表为 `project_id/kind/root_path/ssh_host_id`
四列,并删除旧 Runtime 验证表。
## 资源与发布
- 所有桌面构建(包括 `portable` 与 `release:package`)只携带
`agent-runtime-lock.json`、`remote-runtime-lock.json` 与公开的
`agent-release-keys.json`,不嵌入 `.agent-resources`、
`.remote-runtime-resources` 或任何可安装 Linux 远端 payload。
- `build/agent-package.cjs` 在对应原生 Linux 架构组装确定性 `.gbagent`,外层签名覆盖
描述符和每个内部文件身份;内部 Agent 与 Runtime 仍分别使用既有签名 manifest 和 lock
校验,但所有 production 层统一使用一组 GoodBuddy 通用发布身份并通过签名域区分用途,
不要求内部 Runtime 单独配置密钥。`build/agent-catalog.cjs` 为双架构包生成签名累计目录,
拒绝同一版本/架构改变字节。
- `.github/workflows/agents.yml` 只使用进程内临时测试 key 做分支/PR 原生验证,不发布。
`.github/workflows/agent-release.yml` 才可在 annotated `agent-v<version>` 标签和受保护
`agent-signing` Environment 中构建 production 包。Agent GitHub Release 必须
`--latest=false`;北京 OSS 先写 `agent-releases/v<version>/`,GitHub 公开后才切换
单一 `agent-releases/latest.json` 指针,使客户端始终从同一不可变版本目录读取目录与签名。
- Main 的本地缓存只发布完整验证成功的内容。在线目录和包都绑定固定 GitHub/OSS URL、
大小和 SHA-256;选取规则是桌面最低版本满足、Agent protocol major 相同且 minor 不高于
当前支持值的最高 SemVer。缺少某架构仅使该架构 Host 的托管 SSH 无法激活。
- `agent-release-keys.json` 读取时按 UTF-8 JSON 解析并严格验证 schema、唯一 key ID、
规范 Base64、有效 Ed25519 key、环境和撤销列表,不把 CRLF/LF 或普通 JSON 空白作为
信任条件。上传到 Host 前重新序列化为确定性 LF JSON。签名目录、描述符、manifest 和
payload 仍按原始签名字节及 SHA-256 验证。
## 发布前验证
1. `npm test`
2. `npm run typecheck`
3. `npm run lint`
4. `npm run build`
5. 用测试签名复合包验证在线下载源选择和离线导入/导出;正式 Agent 候选还需公开校验
双架构 production 包与签名目录。
6. 在已固定 Host Key 的 Linux x64 测试 Host 上验证 attach-or-bootstrap。
7. 在 GoodBuddy 专用测试目录验证 Ask 无法修改文件。
8. 验证 Execute 可以写文件、启动进程和访问网络,同时不触碰无关 Host 文件。
9. 运行一次有界的真实模型调用,确认凭据只由 Main 使用。
10. 中断并恢复 SSH,确认活动 Runtime 不被网络抖动终止。
@@ -0,0 +1,457 @@
# GoodBuddy 设备共享与 ShareServer 联邦网络总体设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 适用范围 | GoodBuddy 桌面端、ShareServer、网关间联邦协议 |
| 桌面端设计 | [设备发现与共享](../features/device-sharing/README.md) |
| 服务端设计 | [ShareServer](../../../goodbuddy-shareserver/docs/features/share-server/README.md) |
## 1. 文档职责
本文是设备共享网络的跨端权威设计,负责定义拓扑、术语、身份、能力清单、授权计算、任务
路由、能力分发、协议边界和共同验收标准。桌面端的产品与界面行为由“设备发现与共享”功能
文档负责,独立 Web 服务由 ShareServer 功能文档负责。
本文描述完整目标系统,不以交付阶段删减领域模型,也不把尚未实现的设计表述为当前能力。
## 2. 核心决策
### 2.1 两个独立产品面
1. **GoodBuddy Desktop** 在设置中提供“设备发现与共享”,默认使用局域网发现与直连,也可
由用户额外连接一个或多个 ShareServer。
2. **ShareServer** 是独立部署、独立升级的 Web 服务,提供组织、设备、能力、权限、更新、
联邦、中继和审计管理。它不运行在 Electron Main 中,也不由桌面端代为启动。
### 2.2 能力优先,来源可见
用户日常选择的是能力,不是网络连接。设备、ShareServer 和组织作为能力来源与诊断入口。
任何调用都必须持续显示提供者、执行位置和数据去向,不能为了统一体验隐藏跨设备或跨组织
边界。
### 2.3 局域网是默认连接方式,网关是显式附加连接
- 桌面端没有网关配置时,局域网发现、配对、调用和本机共享仍可完整工作。
- 添加、删除或断开 ShareServer 不改变局域网配对关系。
- ShareServer 不接管本机局域网发现,也不能静默把局域网设备注册到组织。
- 同一桌面端可以连接多个 ShareServer;能力来源和授权按连接分别计算。
### 2.4 发现、被发现和开放能力是三个独立动作
- **发现设备**:浏览同一局域网中主动广播的 GoodBuddy 服务。
- **允许被发现**:广播本设备存在及配对入口。
- **开放能力**:向指定设备、用户组或组织发布可查看、调用或拉取的能力。
开启其中一项不能推导另外两项已开启。新安装默认不开放能力。
### 2.5 ShareServer 优先作为控制面
ShareServer 负责身份、目录、策略、任务路由、更新和审计。数据面优先在两个已认证端点之间
直接建立;网络条件不允许时,可按组织策略使用 ShareServer 中继。是否允许中继、正文是否
端到端加密、是否允许跨组织,由本次任务冻结的策略共同决定。
## 3. 术语
| 术语 | 定义 |
| --- | --- |
| `Device` | 安装 GoodBuddy 并拥有稳定设备身份的终端 |
| `Peer` | 与当前设备存在局域网配对关系的另一台设备 |
| `ShareServer` | 提供组织控制面和可选数据中继的独立服务 |
| `Organization` | ShareServer 中隔离成员、设备、能力与策略的管理范围 |
| `GatewayConnection` | 某台设备与一个 ShareServer 上特定组织身份的连接 |
| `Capability` | 可被查看、调用或分发的稳定能力标识 |
| `CapabilityProvider` | 实际持有并执行某项能力的设备或服务节点 |
| `Publication` | 能力提供者向某一范围发布的可见性和使用声明 |
| `Grant` | 主体对能力执行某类动作的明确授权 |
| `Task` | 一次远程能力调用及其输入、执行、输出和终态 |
| `Package` | 可下载到本机安装的签名能力工件 |
| `FederationTrust` | 两个组织网关之间经管理员确认的双边信任关系 |
“网关”是 ShareServer 的网络角色;产品名称为 `ShareServer`,独立仓库为
`goodbuddy-shareserver`。
## 4. 系统拓扑
```text
局域网:
GoodBuddy Desktop A <-- mDNS 发现 + 配对后 TLS 直连 --> GoodBuddy Desktop B
单组织:
GoodBuddy Desktop A -- 出站连接 --> ShareServer <-- 出站连接 -- Provider Device B
跨组织:
Desktop A --> ShareServer A <== 联邦信任 ==> ShareServer B <-- Provider Device B
```
局域网和网关连接共享能力、授权和任务的领域语义,但不共享发现方式:
| 维度 | 局域网 | ShareServer |
| --- | --- | --- |
| 发现来源 | mDNS/DNS-SD 主动广播 | 已配置 URL 或邀请链接 |
| 初始身份 | 双端验证码确认设备指纹 | 用户登录、设备注册和组织成员身份 |
| 能力目录 | 已配对设备直接返回 | 服务端按组织策略过滤 |
| 连接方向 | 配对后直连 | 设备主动建立出站连接 |
| 管理范围 | 单个设备间授权 | 用户组、组织和联邦授权 |
| 审计权威 | 两端各自本地记录 | 组织网关记录元数据,两端保留本地记录 |
## 5. 组件边界
### 5.1 GoodBuddy Desktop
- 浏览和广播局域网服务。
- 创建、确认和撤销点对点配对。
- 连接 ShareServer,保存脱敏连接信息和本机受保护凭据。
- 发布用户明确选择的本机能力。
- 展示经当前身份过滤的能力目录。
- 在调用前冻结提供者、输入范围、授权和路由。
- 执行本机提供的任务,或消费远端任务结果。
- 校验并安装用户明确选择的签名能力包。
- 保存本机活动记录和可撤销授权。
### 5.2 ShareServer
- 认证用户、设备、服务节点和其他 ShareServer。
- 管理组织、成员、用户组和设备目录。
- 接收能力发布并生成权限过滤后的目录。
- 计算组织强制策略,签发短期任务授权。
- 路由任务、维护在线状态,并在允许时中继数据。
- 管理签名包、发布通道、更新策略和撤销。
- 建立双边联邦关系并限制跨组织可见性与调用。
- 保存有界审计元数据、用量和管理操作。
### 5.3 能力提供者
- 只执行已发布且本次授权允许的能力。
- 在执行边界重新验证任务令牌、输入 schema、大小、期限和调用方。
- 使用自身 Runtime、账号、工作区和数据权限。
- 返回有界进度、结果、成果或确定终态。
- 不因收到任务令牌而获得其他能力或任意系统接口。
## 6. 身份与信任
### 6.1 设备身份
每次 GoodBuddy 安装生成稳定设备密钥和自签设备证书,私钥保存在系统安全存储中。对外只
使用随机设备 ID、公钥指纹、用户设置的设备名称和必要协议版本。操作系统用户名、绝对路径、
软件资产和能力详情不进入未认证发现广播。
重装或主动重置设备身份会产生新设备。旧局域网配对和 ShareServer 设备注册不会自动迁移。
### 6.2 局域网配对
1. 发起端选择一台主动广播的设备。
2. 双方建立临时加密通道并计算共同短验证码。
3. 两端同时显示设备名、系统类型、公钥指纹和验证码。
4. 双方用户确认后保存对端公钥及配对 ID。
5. 后续使用双方设备证书认证;任一端可以撤销配对。
IP 地址不作为设备身份,也不单独构成授权依据。
### 6.3 ShareServer 注册
用户通过浏览器/OIDC 或服务端支持的登录方式认证后,为当前设备申请一次性注册挑战。设备
使用自身私钥签名挑战,ShareServer 建立 `user + organization + device` 绑定并签发可轮换的
设备凭据。设备凭据不等同于用户 Web 会话,不可用于管理控制台登录。
### 6.4 联邦身份
两个 ShareServer 由双方管理员交换组织标识、网关公钥、公开端点和指纹,并分别确认信任。
联邦关系只有在双方状态均为 `active` 时生效。单方暂停或撤销后立即停止新目录交换和新任务,
已执行任务保留审计终态。
## 7. 能力模型
### 7.1 能力清单
```json
{
"schemaVersion": "1.0",
"capabilityId": "document.convert.docx",
"name": "文档转 Word",
"version": "1.4.0",
"modes": ["remote", "package"],
"inputSchema": {},
"outputSchema": {},
"permissions": ["receive_text", "receive_files", "return_files"],
"limits": {
"maxInputBytes": 10485760,
"maxOutputBytes": 52428800,
"maxDurationSeconds": 900,
"maxConcurrentTasks": 2
},
"dataPolicy": {
"retention": "task_lifetime",
"networkAccess": false,
"delegation": "none"
}
}
```
清单只描述能力事实,不授予任何主体访问权。`inputSchema` 和 `outputSchema` 使用受限 JSON
Schema 子集;实现必须限制 schema 深度、属性数、枚举数和总字节,不能把清单当作可执行代码。
### 7.2 发布声明
`Publication` 将一个能力版本发布到以下一种范围:
- 指定局域网 Peer。
- 所有已配对 Peer。
- ShareServer 内指定用户、用户组或设备组。
- 指定联邦组织中的用户组。
发布声明同时定义允许动作:`discover`、`inspect`、`invoke`、`download`。`inspect` 允许查看
详细清单,`discover` 只允许看到名称和可用状态。
### 7.3 可调用与可分发
- `remote`:代码与受控数据留在提供方,调用方只发送输入并接收结果。
- `package`:签名工件下载到调用方,经校验和用户/组织策略批准后安装。
同一能力可以同时支持两种模式,但两种模式分别授权。远程调用授权不能推导下载权,下载权
也不能推导安装后自动启用。
## 8. 授权模型
### 8.1 授权输入
一次动作的最终决定取以下约束交集:
```text
调用方用户权限
∩ 调用方设备状态
∩ 调用方组织强制策略
∩ 发布范围
∩ 提供方授权
∩ 提供方组织强制策略
∩ 能力输入与数据策略
∩ 联邦关系限制(跨组织时)
= allow | require_approval | deny
```
`deny` 优先于 `require_approval`,`require_approval` 优先于 `allow`。客户端偏好只能在组织强制
策略允许的范围内收紧权限,不能放宽权限。
### 8.2 Grant
Grant 至少绑定:
- 主体:用户、用户组、设备、设备组或外部组织。
- 能力 ID 和允许的版本范围。
- 动作:查看、调用、下载、安装或发布。
- 输入:文本、文件、数据分类、单项和累计大小。
- 提供者和执行位置限制。
- 次数、并发、预算、时间段和有效期。
- 是否需要每次批准。
- 是否允许委托及明确的下游组织。
“始终允许”只创建与当前能力、提供者、输入类型和范围匹配的 Grant,不创建整台设备的任意
访问权。
### 8.3 转委托
默认 `delegation=none`。允许委托时,任务授权中必须冻结完整执行链和最大跳数。任一新组织、
新能力或新数据接收方加入执行链,都需要重新计算授权,不能沿用原任务令牌。
## 9. 任务协议
### 9.1 任务快照
创建任务时冻结:
- 调用方用户、设备和组织。
- 能力 ID、版本和提供者。
- 输入 schema 版本、数据类型、数据分类和字节数。
- 路由:局域网直连、网关直连、网关中继或联邦路径。
- 授权决定、批准记录和策略版本。
- 委托链、预算、期限和幂等键。
设置或策略后续变化不追改已接受任务;紧急撤销可以阻止尚未开始的任务,并按策略取消活动
任务。
### 9.2 状态
```text
created -> awaiting_approval -> queued -> connecting -> running
running -> completed | failed | cancelled | outcome_unknown
created/awaiting_approval/queued/connecting -> rejected | expired | cancelled
```
- `completed` 只在提供者提交完整终态且调用方确认接收后成立。
- 网络中断后无法确认副作用结果时使用 `outcome_unknown`,不得自动重放。
- 重试创建新 attempt,并复用仅在能力声明支持时才有效的幂等键。
- 用户取消必须传播到实际提供者;未确认取消时不能声称任务已停止。
### 9.3 数据传输
- 小型 JSON 输入使用任务通道传输。
- 文件使用内容 SHA-256、声明大小和一次性任务对象令牌传输。
- 接收端边读边校验大小和摘要,拒绝路径、符号链接和调用方文件名决定的落盘位置。
- 任务正文、文件和输出不进入普通网关审计日志。
- 中继模式下是否端到端加密由策略快照明确记录;界面必须显示网关能否读取正文。
## 10. 能力包与更新
每个 Package 必须提供不可变版本、目标平台和架构、内容大小、SHA-256、发布者签名、权限
声明、依赖和兼容范围。安装流程统一为:
```text
取得签名目录 -> 选择不可变目标 -> 下载暂存 -> 校验大小/摘要/签名
-> 展示权限差异 -> 批准 -> 安装到 GoodBuddy-owned 目录 -> 验证 -> 启用
```
要求:
- 下载开始后冻结来源,不在失败时静默切换 ShareServer 或镜像。
- 包不得包含凭据;配置只携带非敏感结构和密钥引用。
- 新版本扩大文件、进程、网络、数据或委托权限时,旧 Grant 不自动继承。
- 紧急撤销阻止新安装和新调用;已安装包的停用或删除遵循组织策略并向用户说明。
- 复用现有 GoodBuddy 签名工件原则,但不同用途使用独立签名域。
## 11. 协议分层
### 11.1 局域网发现层
- DNS-SD 服务类型:`_goodbuddy-share._tcp.local`。
- 广播仅包含协议 major、随机实例 ID、端口和配对状态。
- 设备名称、证书和详细版本通过临时加密握手取得。
- 不扫描任意端口,不枚举未主动广播的机器。
### 11.2 ShareServer 控制 API
使用 HTTPS 提供登录、设备注册、目录、管理和审计 API;使用 WebSocket 建立设备出站控制
通道,承载心跳、任务声明、取消和短期数据通道协商。普通管理 API 与设备通道使用不同凭据
和受众。
### 11.3 数据通道
数据通道可以是配对 TLS 直连、ShareServer WebSocket 中继或经网关协商的端到端加密流。
协议只保证有界帧、顺序、取消和终态,不自行实现另一套拥塞控制。
### 11.4 版本协商
- 每层独立声明 `major.minor`。
- major 不同拒绝连接;minor 使用双方共同支持的最低能力集合。
- 未识别字段在声明允许扩展的位置忽略,安全与授权枚举中的未知值一律拒绝。
- 联邦只交换双方共同支持的能力和策略表达式,不降低任一方策略。
## 12. 数据与审计
### 12.1 最小审计事件
- 操作者、调用设备和组织。
- 动作、能力、版本和提供者。
- 路由与是否跨组织。
- 输入数据类别、分类、文件数和字节数。
- 授权结果、策略 ID 和批准者。
- 任务状态、耗时、输出大小和资源用量。
- 包下载、安装、更新、撤销和发布变更。
默认不保存提示正文、文件内容、模型输出、密钥、Cookie、认证头或完整私有路径。需要内容
审计的组织必须单独定义保留期限和访问角色,并在调用前向用户显示。
### 12.2 数据权威
- 桌面端是本地配对、本机共享偏好和本地活动记录的权威。
- ShareServer 是组织成员、设备注册、组织策略、发布目录和组织审计的权威。
- 能力提供者是运行中任务及结果是否产生的权威。
- 联邦两端各自保存本组织审计,不复制对方完整成员目录或内部策略。
## 13. 安全边界
ShareServer 可以部署到公网,因此公网连接、外部组织和服务端多租户是本设计中的实际信任
边界。共同要求如下:
1. 所有远端清单、名称、schema、错误、文件和模型输出都作为不可信数据处理。
2. Renderer 不接触设备私钥、网关令牌、任意 socket 或原始文件传输接口。
3. Main/Provider 在执行边界重新验证任务,不信任 Renderer 或 ShareServer 代替本机授权。
4. ShareServer 按组织隔离查询,搜索和计数接口不能泄露无权查看的设备或能力。
5. 设备只建立主动配置的网关出站连接,不接受 ShareServer 发起任意系统调用。
6. 局域网发现不等于信任,组织成员身份也不等于所有能力授权。
7. 远程能力不能扩大本机 Ask/Execute、Workspace、Runtime、Skill 或 MCP 现有权限。
8. 密钥只进入对应安全存储,诊断和审计使用固定字段与脱敏错误码。
9. 限制握手、清单、帧、文件、并发、速率、任务时长和审计查询大小。
10. 撤销设备、用户、Grant、Publication、Package 或 FederationTrust 后停止签发新令牌。
## 14. 建议领域实体
```text
DeviceIdentity
PeerPairing
GatewayConnection
Organization
Membership
Group
RegisteredDevice
Capability
CapabilityVersion
Publication
Grant
Policy
Approval
Task
TaskAttempt
ArtifactTransfer
Package
PackageRelease
FederationTrust
AuditEvent
Revocation
```
共享协议只定义跨端 DTO 和状态,不规定桌面 SQLite 与 ShareServer 数据库使用相同表结构。
## 15. 仓库边界
当前仓库边界(跨仓库文档链接约定两个仓库克隆到同一级目录):
```text
goodbuddy/
docs/architecture/share-network-architecture.md
docs/features/device-sharing/
src/ # GoodBuddy Desktop
goodbuddy-shareserver/
docs/features/share-server/
server/ # 独立 Hono Web 服务
shared/ # 当前原型 fixture,不是跨端协议
src/ # React 管理前端
```
ShareServer 源码和服务端文档已迁入独立仓库:
`ssh://git@git.digiman.live:11022/root/goodbuddy-shareserver.git`。
当前服务是交互原型,尚未提供真实 Desktop 集成。共享网络总体设计仍由本文统一维护。
ShareServer 不反向依赖桌面端 `src/shared`,构建和运行不要求桌面仓库存在。
真实跨端协议实现时双方依赖独立 `share-protocol` 包,具体仓库归属和发布方式届时确定,
不提前创建空包。协议包不得包含数据库模型、Electron API、服务实现或凭据。
## 16. 共同验收标准
- [ ] 不配置 ShareServer 时,两台桌面端可通过局域网发现、双端配对、查看获准能力并调用。
- [ ] 关闭“允许被发现”不会关闭主动发现;关闭主动发现不会撤销既有配对。
- [ ] 本机没有明确 Publication 时,任何 Peer 或组织成员都不能查看或调用本机能力。
- [ ] 桌面端可以连接多个 ShareServer,任一连接故障不影响局域网和其他连接。
- [ ] ShareServer 只展示当前用户、设备和组织获准查看的目录。
- [ ] 每次远程调用都能说明提供者、执行位置、发送数据、路由、授权依据和委托链。
- [ ] 网络中断且副作用结果不确定时进入 `outcome_unknown`,不会自动重放。
- [ ] 能力包经大小、SHA-256、签名、兼容和权限差异校验后才能安装。
- [ ] 跨组织调用同时满足两端策略;联邦撤销后不能创建新任务。
- [ ] 审计默认只保存必要元数据,不保存正文、文件、密钥或认证信息。
- [ ] ShareServer 可独立部署和升级,不由 Electron 启动,也不影响桌面端离线使用。
## 17. 相关设计
- [设备发现与共享 PRD](../features/device-sharing/prd.md)
- [设备发现与共享功能逻辑](../features/device-sharing/logic-design.md)
- [设备发现与共享 UI](../features/device-sharing/ui-design.md)
- [设备发现与共享技术设计](../features/device-sharing/technical-design.md)
- [ShareServer PRD](../../../goodbuddy-shareserver/docs/features/share-server/prd.md)
- [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)
- [SSH 远程主机与 Agent](../features/remote-host/README.md)
- [GoodBuddy 统一界面设计系统](../../UI-DESIGN.md)
+36 -25
View File
@@ -12,12 +12,13 @@ below.
## Release Packaging
- Unless the user explicitly requests a local Windows package, never run local
Windows packaging or launch probes as part of release preparation. In
particular, do not run `npm run portable`, Windows
`npm run release:package`, direct `electron-builder` packaging, or launch a
locally packaged Windows app before publishing a release. Source validation
and the native GitHub Actions jobs are the release authorities.
- Unless the user explicitly requests it, never run a local production build,
desktop package, or launch probe as part of release preparation. In
particular, do not run `npm run build`, `npm run build:bundle`,
`npm run portable`, `npm run release:package`, direct `electron-builder`
packaging, or launch a locally packaged app before publishing a release.
Local tests, type checks, and lint plus the main-branch and native tag GitHub
Actions jobs are the release authorities.
- After a release is published, confirm only that the expected Windows assets
exist in the public release metadata. Do not locally rebuild or launch the
Windows package, download or hash published installers, attach browser
@@ -49,9 +50,9 @@ below.
optional: users explicitly download a compatible compound `.gbagent` package
from Settings or import one offline before GoodBuddy can install or update a
Host.
- Each independently published Linux x64 or arm64 `.gbagent` contains the
- Each independently published Linux x64, Linux arm64, or macOS arm64 `.gbagent` contains the
Agent daemon, pinned Node, and the desktop-maintained compatible OpenCode
Runtime. Build both architectures on native GitHub Actions runners from one
Runtime. Build all three targets on native GitHub Actions runners from one
immutable `agent-v<agentVersion>` tag.
- Keep the GoodBuddy Agent source, shared protocol/contracts, runtime lock,
bundle tooling, and tests in this repository so a desktop commit identifies
@@ -65,7 +66,9 @@ below.
digest, use only an ephemeral in-memory test signing identity, and verify the
resulting compound package and deterministic archive. It must not read
production signing secrets, publish installable release artifacts, or modify
the checked-in public key registry.
the checked-in public key registry. The Darwin arm64 target uses `macos-15`
and the same production locks. Native CI checks do not replace real Host
installation, Attach, Ask/Execute, and lifecycle validation during development.
- `.github/workflows/agent-release.yml` is the only production compound Agent
publication path. It requires an annotated immutable Agent tag, the protected
`agent-signing` Environment, native x64/arm64 builds, one production
@@ -97,11 +100,18 @@ 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.
- The tagged commit must be reachable from protected `main`. The two native
jobs build the compound x64/arm64 packages from the same source and locks;
- 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
version/architecture identity, invalid prior signatures, and incompatible
version/platform/architecture identity, invalid prior signatures, and incompatible
matrix metadata.
- All three targets share the existing signed `agent-catalog.json`. Before the
first mixed-platform catalog is published, release the Desktop reader that
accepts Darwin entries and document the required Desktop upgrade. Older
Desktop readers reject the entire mixed catalog and cannot check online Agent
updates; preserve their locally verified packages and immutable historical
releases. Do not introduce a second catalog or silently weaken package
signature and integrity verification.
- Agent GitHub Releases are ordinary non-draft, non-prerelease releases but
must use `--latest=false`; they must never replace the desktop release marked
Latest. Publish immutable packages and the versioned catalog to
@@ -141,23 +151,24 @@ not require release notes.
is carried forward must not retain a duplicate packaged entry.
6. Verify that `package.json`, the root `package-lock.json` version, and
`package-lock.json.packages[""].version` all equal the release version. Run
`npm run release:notes:verify`, the required source validators, the
production build. The six native CI jobs remain the cross-platform
authority; do not add local packaging or packaged-app launch probes unless
the user explicitly requests them.
`npm run release:notes:verify`, `npm test`, `npm run typecheck`, and
`npm run lint`. Do not run a local production build, packaging command, or
packaged-app launch probe unless the user explicitly requests one.
If the user requested a LoongArch preview in step 1, build it separately
from this exact candidate by following the LoongArch build document, record
its size and SHA-256, and keep it outside the standard GitHub/OSS release
asset set unless the user separately authorizes publishing it.
7. Fetch both remotes immediately before tagging. Inspect any remote branch
movement instead of overwriting or silently merging it. Confirm the working
tree is clean, the candidate tag is unused locally and remotely, and the
exact approved commit has not changed.
8. Only after all previous steps pass, create an annotated
`v${package.version}` tag at the exact approved commit. Push `main` to
`origin` and `github`, verify both branch SHAs, then push the tag to both
remotes and verify each peeled tag SHA (`refs/tags/<tag>^{}`) equals the
release commit.
7. Commit the approved candidate and confirm the exact release commit with the
user. Fetch both remotes, inspect branch movement instead of overwriting or
silently merging it, then push that commit to `main` on `origin` and
`github`. Require the candidate commit's main-branch GitHub Actions source
validation and production build to succeed before tagging.
8. Fetch both remotes again immediately before tagging. Confirm the working
tree is clean, both remote `main` refs still equal the approved commit, the
candidate tag is unused locally and remotely, and the exact approved commit
has not changed. Only then create the annotated `v${package.version}` tag at
that commit, push the tag to both remotes, and verify each peeled tag SHA
(`refs/tags/<tag>^{}`) equals the release commit.
9. Keep both approved language versions as the single source for the GitHub
Release body and the packaged first-open release-notes modal. The modal
displays the release notes matching the current interface language and
+135
View File
@@ -0,0 +1,135 @@
# GoodBuddy Feature Documentation Guide
## Scope
These instructions apply to all files under `docs/features/`.
Each direct child directory represents one user-visible product feature or one coherent feature family. Organize documentation by feature ownership first, then by document responsibility.
## Standard Structure
Use the smallest applicable subset:
```text
docs/features/<feature-name>/
README.md
prd.md
user-stories.md
logic-design.md
ui-design.md
technical-design.md
progress.md
```
Additional documents are allowed when one standard file would mix independent responsibilities. List every additional document and its unique responsibility in the feature `README.md`.
Do not create empty placeholders. If a standard document is not needed yet, omit it and state the missing responsibility in `README.md` only when that absence matters to current work.
## Document Responsibilities
### `README.md`
- The sole entry point for the feature.
- Summarizes feature purpose and boundary.
- Lists all feature documents and their authoritative responsibilities.
- Defines feature-specific terminology.
- Points to cross-feature dependencies without copying them.
### `prd.md`
- Defines the user problem, goals, scope, non-goals, functional requirements, and product acceptance.
- Describes what the product must do, not module layout or implementation mechanics.
- Records explicit limitations when a workflow is intentionally unsupported.
### `user-stories.md`
- Defines user roles, scenarios, and Given/When/Then-style acceptance.
- Covers first use, normal use, failure recovery, compatibility, permissions, and boundary cases relevant to real users.
- Keeps implementation details out unless the detail is itself an observable contract.
### `logic-design.md`
- Defines invariants, state dimensions, state transitions, decision tables, priority rules, failure outcomes, and cross-story consistency.
- Resolves overlap between PRD requirements and User Stories before technical implementation.
- Includes a logic completeness assessment and identifies unresolved product decisions separately from technical selection work.
### `ui-design.md`
- Defines feature information architecture, shared component usage, states, copy, feedback, keyboard behavior, accessibility, and responsive behavior.
- Must follow the repository root `UI-DESIGN.md`.
- Does not create feature-local visual systems when shared tokens and primitives already exist.
### `technical-design.md`
- Defines process and module boundaries, shared contracts, persistence, IPC, lifecycle, packaging, compatibility, validation, and implementation order.
- Implements the PRD and logic design rather than redefining their behavior.
- Follows KISS and YAGNI. Do not add recovery or compatibility machinery without a demonstrated requirement.
### `progress.md`
- Records only verified implementation facts, remaining work, blockers, validation commands, and dated evidence.
- Never marks an item complete based only on a design, mock, schema, or injected fake when the production path remains incomplete.
- Does not duplicate future design. Link back to the authoritative document.
## Traceability
For non-trivial features:
1. PRD requirements use stable IDs such as `FR-1`.
2. User Stories use stable IDs such as `US-A1`.
3. Logic design maps rules and state transitions to PRD and User Story IDs.
4. UI and technical design link to the PRD and logic design.
5. Progress items map to implementation stages and record actual validation.
Do not renumber stable IDs merely for presentation after implementation begins.
## Source of Truth
- PRD owns product scope and requirements.
- User Stories own user scenarios and scenario acceptance.
- Logic design owns behavioral decisions and state rules.
- Root `UI-DESIGN.md` owns global UI consistency; feature UI design owns feature-specific application.
- Technical design owns implementation architecture.
- Progress owns current implementation and validation status.
- Final code and validated runtime behavior override stale documentation. Correct all affected documents when they diverge.
## Feature Boundaries
- Keep a document in a feature directory when one feature can own its behavior and acceptance.
- Keep a document in `docs/architecture`, `docs/quality`, `docs/development`, or `docs/roadmap` only when it truly governs multiple features and has no single feature owner.
- Reference cross-feature documents rather than copying their definitions.
- When two features share a global domain model, keep that model in the feature family that owns the domain or in cross-feature architecture if ownership is genuinely global.
## Managed External Artifacts
When a feature downloads external artifacts managed by GoodBuddy:
- Define the exact setting that selects the source and the setting scope.
- Use immutable artifact targets with expected byte size and SHA-256.
- Freeze the selected source when an operation starts.
- Fail explicitly when the selected source is unavailable or invalid. Never silently request another source.
- Keep download URLs and redirect allowlists out of Renderer-controlled input.
- State whether the setting affects models, application updates, Agent packages, managed tool runtimes, package-manager registries, or user-configured URLs. Do not imply a broader scope than implemented.
- When two targets are presented as an upstream/original address and its OSS mirror, require byte-identical content with one shared size and SHA-256. A mirror must not rebuild, recompress, or modify the upstream artifact.
## Migration Rules
When moving existing documentation:
1. Preserve content and Git history where practical.
2. Update every repository link to the new path.
3. Add or update the destination feature `README.md`.
4. Delete the old authoritative path; do not leave a duplicate compatibility copy.
5. Do not rewrite unrelated document content merely because the file moved.
6. Preserve uncommitted user edits while moving files.
7. Verify all relative Markdown links after migration.
## Review Checklist
- The feature has one clear entry point.
- Every document has one declared responsibility.
- Product requirements, stories, logic, UI, technical design, and progress do not contradict each other.
- Normal, failure, cancellation, compatibility, permission, and scope behavior are covered where applicable.
- UI behavior follows `UI-DESIGN.md`.
- Progress claims are backed by dated evidence.
- All relative links resolve.
@@ -0,0 +1,34 @@
# 应用导航与系统工具入口
本功能整理 GoodBuddy 主侧栏的入口层级:系统入口保持固定,应用占用主导航,系统工具从
侧栏底部快捷打开助手工作栏。该功能同时为后续插件应用和插件工具预留统一的入口类型,但
不包含插件市场的安装与运行机制。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [PRD](./prd.md) | 用户问题、产品边界、功能要求与产品验收 |
| [User Stories](./user-stories.md) | 用户场景及 Given/When/Then 验收 |
| [功能逻辑设计](./logic-design.md) | 分类规则、入口状态、优先级与失败结果 |
| [UI 设计](./ui-design.md) | 侧栏信息架构、控件、文案、键盘和响应式行为 |
| [技术设计](./technical-design.md) | 现有模块映射、状态契约、持久化和实施顺序 |
## 术语
| 术语 | 定义 |
| --- | --- |
| 系统入口 | GoodBuddy 工作流程或审计所必需的固定入口,用户不能卸载、隐藏或改变类型 |
| 应用 | 在主内容区打开完整一级页面的功能,可显示在“当前对话”所在的主导航区 |
| 系统工具 | GoodBuddy 自带且不可卸载的辅助能力,包括文件、终端、浏览器和资源监控,从侧栏底部打开助手工作栏中的对应面板 |
| 插件应用 | 未来由插件提供的一级页面入口 |
| 插件工具 | 未来由插件提供的辅助面板或命令入口 |
| 快捷入口 | 只负责打开或聚焦能力,不复制该能力的界面和业务状态 |
## 相关文档
- [GoodBuddy 统一界面设计系统](../../../UI-DESIGN.md)
- [通用助手工作栏与执行空间 PRD](../assistant-workbar/prd.md)
- [助手工作栏多终端页签 PRD](../assistant-workbar/terminal-tabs-prd.md)
- [智能心跳 PRD](../smart-heartbeat/prd.md)
- [知识库功能文档](../knowledge-base/README.md)
@@ -0,0 +1,167 @@
# 应用导航与系统工具入口功能逻辑设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.2 |
| 日期 | 2026-09-08 |
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
| 关联 User Stories | [User Stories](./user-stories.md) |
## 1. 不变量
1. “当前对话”“运行记录”和“设置”始终是系统入口。
2. 运行记录不进入工具注册表、应用显隐设置或插件市场。
3. 文件浏览器、终端、浏览器和资源监控始终是系统工具,不能卸载或被插件替换。
4. 左下角只提供快捷入口,工具内容仍由助手工作栏承载。
5. 隐藏应用不等于停用功能,也不删除、迁移或清理业务数据。
6. 快捷入口不能扩大执行空间、文件、PTY、网络或浏览器权限。
## 2. 入口模型
```ts
type NavigationContribution =
| {
type: 'system-entry'
id: 'chat' | 'activity' | 'settings'
placement: 'primary-start' | 'primary-end' | 'footer'
}
| {
type: 'application'
id: 'magic-notes' | 'knowledge' | 'heartbeat'
source: 'builtin'
visible: boolean
}
| {
type: 'system-tool'
id: 'workspace' | 'terminal' | 'browser' | 'resources'
workbarAppId: 'workspace' | 'terminal' | 'browser' | 'resources'
}
```
该模型只覆盖首期入口。插件贡献模型随插件 Host、权限和生命周期另行设计。
## 3. 主导航组合规则
主导航按以下顺序计算:
```text
当前对话
可见的内置应用
运行记录
```
当前对话和运行记录不参与排序。首期内置应用默认顺序为魔法笔记、知识库、智能心跳;显隐
设置只从中移除对应项,不改变固定入口的位置。
### 3.1 应用可见性与业务状态
| 导航可见 | 功能启用 | 结果 |
| --- | --- | --- |
| 是 | 是 | 显示入口并允许进入 |
| 否 | 是 | 隐藏入口,后台和数据保持原状 |
| 是 | 否 | 不显示不可用入口;设置页保留启用路径 |
| 否 | 否 | 不显示入口,保留符合该功能自身规则的数据 |
“功能启用”由各应用现有产品设置负责。例如魔法笔记启停仍使用原设置;导航偏好不能覆盖它。
## 4. 系统工具打开规则
### 4.1 通用流程
```text
用户激活工具按钮
-> 校验按钮仍可用
-> 向助手工作栏发送打开意图
-> 工作栏按应用实例策略查找或创建实例
-> 展开工作栏
-> 激活目标实例
-> 把焦点移入面板标题或首个主要控件
```
若创建失败,工作栏保持展开并在对应能力位置显示错误;主页面和其他工作栏实例不被清空。
### 4.2 决策表
| 工具 | 匹配实例存在 | 匹配实例不存在 | 上下文不可用 |
| --- | --- | --- | --- |
| 文件 | 激活单实例并保留其跟随或固定状态 | 创建跟随当前上下文的 `workspace` 单实例 | 打开面板并显示目录修复入口 |
| 终端 | 激活当前执行空间最近的 `starting` 或 `running` 实例 | 创建一个绑定当前执行空间的实例 | 没有项目时使用本机 Home |
| 浏览器 | 激活单实例并保留其目标状态 | 创建跟随当前会话的 `browser` 单实例 | 打开面板并提示选择或创建对话 |
| 资源监控 | 激活 `resources` 单实例 | 创建本机 `resources` 单实例 | 部分指标不可用时显示原因并保留其余指标 |
文件和浏览器按以下优先级处理目标:已有有效固定目标、跟随模式的当前上下文、新实例的默认
上下文。固定目标失效时显示该目标的修复状态,不回退到当前项目、默认工作区或当前会话。
`closing`、`exited`、`interrupted` 和 `failed` 终端不属于可复用实例。当前执行空间只有这些
终端时,快捷按钮创建新实例并保留原 Tab 的历史状态。用户需要额外的并行终端时使用工作栏
“+”或终端面板中的既有创建命令。
### 4.3 选中与状态
工具按钮满足以下条件时显示选中:助手工作栏已展开,且当前活动实例的 `appId` 与工具映射
一致。只有实例存在但未激活时不显示选中。
徽标优先级为:等待用户处理 > 错误 > 运行中 > 无徽标。同一按钮只显示一个视觉徽标,
可访问名称包含完整状态。
资源监控首期不在后台采样,因此不显示负载告警徽标。只有资源面板是工作栏当前活动实例且
工作栏已展开时,资源按钮才显示选中状态。
### 4.4 资源采样状态
```text
inactive -> warming -> loading -> live
-> partial
-> error
live/partial/error -> loading
warming/loading/live/partial/error -> inactive
```
- `inactive`:资源面板未处于可见活动状态,没有采样定时器。
- `warming`:首次进入或停止后恢复,建立 CPU 和磁盘 IO 区间基线;内存仍可立即显示。
- `loading`:正在请求后续样本;请求未结束时跳过后续时钟触发。
- `live`:所有首期指标可用,按 2 秒间隔追加样本。
- `partial`:至少一个指标可用;不可用项携带稳定错误码,其他指标继续更新。
- `error`:本次没有任何可用指标,保留已有历史;自动时钟或手动操作可以重试。
资源面板为活动实例、工作栏展开且 `document.visibilityState` 为 `visible` 时才离开 `inactive`。
任一条件失效即停止定时器并重置区间基线。历史队列只保留最后 150 个样本;时间间隔较长或
采样失败时保留真实时间缺口,不补零或伪造中间数据。
## 5. 设置保存规则
应用导航偏好与应用业务启停分开保存。Switch 每次切换立即提交完整目标集合;请求完成前禁用
三个应用显隐 Switch,下一次操作基于最新确认结果提交,防止并发更新互相覆盖。
保存失败时:
- 主导航和 Switch 恢复最后一次成功保存的偏好。
- 设置行显示可重试错误。
- 不回滚或修改各应用的业务状态。
## 6. 与需求的对应关系
| 逻辑 | PRD | User Stories |
| --- | --- | --- |
| 固定入口不变量 | FR-1、FR-2 | US-A1、US-A2 |
| 应用显隐和失败恢复 | FR-3、FR-4、FR-11、FR-12 | US-B1、US-B2、US-B4、US-B5 |
| 工具打开决策表 | FR-5 至 FR-10 | US-C1 至 US-C7 |
| 资源采样状态 | FR-9 | US-C6 |
| 无障碍与窄窗口 | FR-13 | US-D1、US-D2 |
## 7. 逻辑完整性评估
首期规则已覆盖固定入口、应用显隐、保存失败、系统工具单实例与多实例差异、本机 Home、
托管 SSH、无活动会话、目录失效、资源部分可用、采样停止、权限边界、键盘和窄窗口。其他
工具面板内部的取消、超时、
连接恢复和固定目标失效继续由助手工作栏负责,本功能不复制这些状态机。
## 8. 后续产品决策
- 插件工具上线后,快捷栏允许固定的最大数量及溢出规则。
- 插件应用是否允许用户排序,以及固定系统入口之间允许的排序范围。
这些决策不影响首期四个系统工具的固定入口和内置应用显隐实现。
@@ -0,0 +1,175 @@
# 应用导航与系统工具入口 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.2 |
| 日期 | 2026-09-08 |
| 适用产品 | GoodBuddy 桌面端 |
| 关联设计 | [统一界面设计系统](../../../UI-DESIGN.md)、[助手工作栏 PRD](../assistant-workbar/prd.md) |
## 1. 背景
主侧栏当前把“当前对话”“魔法笔记”“知识库”“智能心跳”和“运行记录”放在同一导航组,
底部长期显示“GB 本地工作区”和平台架构。底部信息不能反映用户可以执行的操作,文件、终端
和浏览器则需要先找到右侧助手工作栏后才能打开。
后续插件可能提供完整页面,也可能只提供辅助面板或命令。若不先区分系统入口、应用和工具,
插件接入后会同时争用主导航、侧栏底部和助手工作栏,用户也无法判断入口会切换页面还是打开
辅助面板。
## 2. 产品定义
### 2.1 三类入口
| 类型 | 首期成员 | 默认位置 | 用户控制范围 |
| --- | --- | --- | --- |
| 系统入口 | 当前对话、运行记录、设置 | 主导航固定位置或侧栏底部 | 不可隐藏、卸载或改变类型 |
| 应用 | 魔法笔记、知识库、智能心跳 | 主导航应用区 | 可控制是否显示;隐藏不删除数据 |
| 系统工具 | 文件浏览器、终端、浏览器、资源监控 | 侧栏底部快捷工具栏 | 首期固定提供,不可卸载 |
运行记录是系统审计页面,不属于工具。文件浏览器、终端、浏览器和资源监控是系统工具;它们
的快捷入口位于左下角,实际界面继续由右侧助手工作栏承载。
### 2.2 后续插件类型
插件市场开放后,插件可以贡献“插件应用”或“插件工具”。插件不能注册、覆盖或替换系统入口,
也不能冒充系统工具。内置应用继续标记为“内置”,不因采用统一注册协议而显示成待安装商品。
## 3. 目标
- 用户从侧栏底部一次点击即可打开文件、终端、浏览器或资源监控。
- 主导航只承载完整页面,辅助工具不占用一级页面位置。
- 运行记录始终可发现,不受应用显示设置和插件状态影响。
- 内置应用可以从一个设置位置控制主导航可见性,隐藏后仍可恢复。
- 为插件应用和插件工具保留明确的入口契约,不提前建设插件安装能力。
## 4. 非目标
- 首期不提供第三方插件搜索、下载、安装、更新、签名验证或卸载。
- 不把文件树、终端或浏览器重新实现到左侧栏。
- 不改变助手工作栏的面板实例、停靠、执行空间和安全边界。
- 不允许隐藏运行记录、当前对话或设置。
- 不因隐藏应用而删除魔法笔记、知识库或智能心跳数据。
- 不把“显示在侧边栏”解释为停止智能心跳计划或后台任务。
- 不提供任意本机程序启动器。
- 首期资源监控不采集远程主机、GPU、显存或网络吞吐,不提供后台告警和历史持久化。
## 5. 功能要求
### FR-1 入口分类
系统必须为每个可见入口声明稳定类型:`system-entry`、`application` 或 `system-tool`。界面位置、
设置项和点击结果均以该类型为准,不根据当前页面临时改变类型。
### FR-2 固定系统入口
“当前对话”固定在主导航首位;“运行记录”固定在应用区之后;“设置”固定在侧栏底部。
这些入口不出现在应用显隐、工具固定或插件管理设置中。
### FR-3 内置应用区
魔法笔记、知识库和智能心跳显示在当前对话与运行记录之间。应用入口打开对应一级页面,保留
现有徽标、范围和页面状态。用户可以在设置中控制其主导航可见性。
### FR-4 隐藏应用
隐藏应用只移除主导航入口,不删除数据、不修改业务启停状态,也不终止正在进行的操作。显隐
Switch 更新成功后主导航立即生效;更新失败时恢复原状态并允许重试。用户可从设置恢复入口。
### FR-5 系统工具快捷栏
侧栏底部提供文件浏览器、终端、浏览器和资源监控四个图标按钮,并保留独立设置按钮。每个
按钮具有可见工具提示和可访问名称。首期系统工具顺序固定为文件、终端、浏览器、资源监控。
### FR-6 文件浏览器入口
点击文件按钮时展开助手工作栏,并打开或聚焦单实例的“工作区”面板。已有实例保持其“跟随
当前上下文”或“固定到指定对象”状态;创建实例时默认跟随当前项目,没有项目时使用已配置
的默认工作区。跟随上下文或新建实例所需的路径不可用时,面板显示修复入口;已有有效固定
目标不受当前项目和默认工作区状态影响。
### FR-7 终端入口
点击终端按钮时展开助手工作栏。当前执行空间有正在启动或运行中的终端时聚焦最近使用的实例;
没有时创建一个绑定当前执行空间的新终端,已退出、已中断或失败的历史 Tab 保持不变。没有
项目时当前执行空间为本机 Home。创建额外并行终端继续使用工作栏现有的“+”目录入口。
### FR-8 浏览器入口
点击浏览器按钮时展开助手工作栏,并打开或聚焦单实例的受控浏览器面板。已有实例保持其目标
状态;新实例默认跟随当前会话。新建或跟随模式下没有活动会话时,面板显示需要选择或创建
会话的空状态;已有有效固定目标继续显示。入口不打开系统默认浏览器,也不创建旁路 WebView。
### FR-9 资源监控入口
点击资源监控按钮时展开助手工作栏,并打开或聚焦单实例的本机“资源监控”面板。面板显示
整机 CPU 使用率、内存使用量与总量、磁盘读写速率,以及 GoodBuddy 桌面进程的 CPU、内存
和进程数摘要。指标不可用时显示具体项目和原因,不能显示为 `0`。
资源面板处于可见活动状态、工作栏已展开且应用窗口未隐藏或最小化时,每 2 秒采样一次,在
Renderer 内存中最多保留最近 5 分钟。任一条件不满足时停止采样;首期不在后台持续监控,
也不据此产生侧栏告警。首次采样及停止后恢复的首次采样只建立 CPU 和磁盘 IO 速率基线,
不显示伪造的 `0` 值。
### FR-10 快捷入口状态
工具面板已激活时,对应按钮显示选中状态;面板存在但未激活时保持普通状态。运行中、错误或
等待用户处理可以显示徽标,但必须有可访问文字,且不得自动抢占当前面板。
### FR-11 应用导航设置
设置页提供“应用与工具”管理入口。首期“应用”区域列出三个内置应用并提供“显示在侧边栏”
Switch,切换后立即持久化。系统工具区域列出四项内置固定能力,不提供虚假的卸载或停用按钮。
### FR-12 偏好持久化
应用可见性按设备持久化。升级时保留当前行为:已有可用内置应用继续显示;魔法笔记原有功能
启停设置保持原意,不由新的导航可见性覆盖。
### FR-13 窄窗口行为
窄窗口下主侧栏仍按现有抽屉方式显示。系统工具按钮保持单行且不与设置按钮重叠;空间不足时
收起文字和非必要分隔,不隐藏四个系统工具或设置。
## 6. 权限与安全
- 快捷入口不授予新的文件、命令、网络或浏览器权限。
- 系统工具继续使用 Main、Preload、执行空间 Grant、Ask/Execute 和现有审批边界。
- Renderer 不因新增入口获得任意文件系统、PTY、进程或 Electron API。
- 资源指标由 Main 采集并以有界结构返回;Renderer 不接收进程命令行、环境变量或任意 PID。
- 资源样本只保存在当前 Renderer 内存中,不写入运行记录、SQLite 或遥测服务。
- 插件贡献类型只是界面与导航声明,不能代替权限清单、用户授权和运行隔离。
- 运行记录继续记录由系统工具触发的受审计操作。
## 7. 产品验收
- 主导航能明确区分固定系统入口和可配置内置应用。
- 运行记录在所有应用显隐组合下保持可见并可打开。
- 左下角不再用“本地工作区”与系统架构占据主要空间。
- 文件、终端、浏览器和资源监控均可从左下角打开正确的工作栏面板,且不产生第二套内容界面。
- 连续点击系统工具不会无意创建重复的文件或浏览器实例;终端遵守多实例规则。
- 资源监控以 2 秒间隔更新,保留不超过 150 个样本;面板不活动、工作栏收起或窗口隐藏、
最小化后停止请求新样本。
- CPU、内存、磁盘 IO 或 GoodBuddy 指标不可用时,页面区分部分可用与整体失败。
- 隐藏内置应用不删除数据、不停止后台计划,并能从设置恢复;保存失败时入口保持原状。
- 键盘、屏幕阅读器、浅色主题、深色主题和窄窗口均可完成相同操作。
## 8. 分期
### 首期
- 重构侧栏底部,增加四个系统工具按钮和独立设置按钮。
- 建立从 App 到助手工作栏的“打开或聚焦工具”意图接口。
- 增加本机资源快照采集与资源监控单实例面板。
- 增加内置应用的主导航显示设置。
- 保持运行记录为固定系统入口。
### 后续
- 插件应用和插件工具注册。
- 插件工具固定、取消固定和排序。
- 插件市场、权限详情、安装和卸载。
- 用户级快捷键与命令面板接入同一贡献命令。
@@ -0,0 +1,377 @@
# 应用导航与系统工具入口技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.2 |
| 日期 | 2026-09-08 |
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| UI 设计 | [UI 设计](./ui-design.md) |
## 1. 现状
主页面没有 URL Router,由 `App.tsx` 中的 `WorkspaceView` 和 keep-alive 状态切换:
```ts
type WorkspaceView =
| 'chat'
| 'magic-notes'
| 'knowledge'
| 'heartbeat'
| 'activity'
| 'settings'
```
文件、浏览器和终端已经由 `RightAssistantSidebar`、`WorkbarShell` 与 `workbar-contracts.ts`
承载。工作区与浏览器是单实例应用,终端是多实例应用。代码中尚无资源指标采集和资源监控
面板。现有侧栏底部 `user-card` 整行打开设置,并显示“本地工作区”和平台架构。
本次改动增加入口编排、从主侧栏到工作栏的命令通道,以及本机资源监控的 Main/Preload/
Renderer 最小实现。文件、终端和浏览器后端保持不变。
## 2. 模块边界
| 模块 | 责任 |
| --- | --- |
| `App.tsx` | 组合主导航、系统工具栏、设置入口和页面离开检查 |
| `SidebarSystemTools`(新增) | 渲染四个系统工具按钮并发送打开意图 |
| `RightAssistantSidebar.tsx` | 接收打开意图,调用工作栏实例创建或激活逻辑 |
| `WorkbarShell.tsx` | 继续负责实例策略、Tab、目录和焦点行为 |
| `workbar-contracts.ts` | 继续作为工作栏应用 ID 和实例布局的权威契约 |
| `resource-monitor-contracts.ts`(新增) | 定义本机资源快照、可用性和错误码 |
| `resource-monitor-service.ts`(新增) | 在 Main 中采集并规范化本机与 GoodBuddy 指标 |
| `ResourceMonitorPanel.tsx`(新增) | 按需轮询、保留有界历史并渲染资源面板 |
| `application-settings-contracts.ts` | 增加应用导航偏好 schema 和默认值 |
| `application-settings-store.ts` | 迁移并原子保存应用导航偏好 |
| `PlatformFeaturesSettingsSection` 或独立设置 Section | 编辑“显示在侧边栏”偏好 |
不要在 `SidebarSystemTools` 中读取文件、创建 PTY、操作浏览器或采集系统指标;该组件只发送
有界的工具 ID。
## 3. Renderer 契约
### 3.1 系统工具 ID
```ts
export const sidebarSystemToolIds = [
'workspace',
'terminal',
'browser',
'resources'
] as const
export type SidebarSystemToolId = (typeof sidebarSystemToolIds)[number]
```
该类型可以放在 Renderer 内共享模块。首期不暴露 IPC,因为动作只编排已有工作栏 Renderer
状态;实际能力调用继续走现有 Preload API。
### 3.2 打开意图
`App` 向 `RightAssistantSidebar` 传入单调递增的请求或命令对象:
```ts
type WorkbarOpenIntent = {
requestId: number
appId: 'workspace' | 'terminal' | 'browser' | 'resources'
reason: 'sidebar-system-tool'
}
```
`requestId` 使连续点击同一工具仍能被消费。不要仅设置 `assistantSidebarTab`,因为用户可能已
关闭对应实例,且终端需要按执行空间寻找实例。
建议由 `RightAssistantSidebar` 在消费意图后通过现有 `WorkbarShell` 控制接口完成。文件和浏览器
已有实例时只激活实例,保留其“跟随当前上下文”或“固定到指定对象”状态:
```ts
type WorkbarController = {
openOrActivate(appId: WorkbarAppId): Promise<WorkbarTabInstance>
}
```
若当前 `WorkbarShell` 没有命令式入口,先增加窄的回调或受控请求 Props,不引入全局事件总线。
### 3.3 终端匹配
工作栏“+”目录选择终端时每次创建新实例;侧栏系统工具按钮执行“进入当前终端”,采用:
1. 解析当前项目对应的 `ExecutionSpaceSelection`。
2. 在现有终端实例中按目标 identity 查找 `starting` 或 `running` 状态。
3. 存在多个匹配实例时激活最近使用的实例。
4. 没有匹配实例时调用现有终端创建流程。
`closing`、`exited`、`interrupted` 和 `failed` 实例保留为历史 Tab,不阻止快捷按钮创建新终端。
工作栏“+”继续按原规则创建额外终端,不受快捷按钮去重影响。
### 3.4 资源监控工作栏应用
在 `workbar-contracts.ts` 中增加 `resources`:
```ts
{
id: 'resources',
instancePolicy: 'single',
defaultContext: 'application'
}
```
该应用不加入升级后的默认打开实例。侧栏资源按钮与工作栏“+”目录都通过既有单实例规则创建
或聚焦它。
## 4. 应用导航偏好
### 4.1 设置结构
在 `ApplicationSettings` 增加独立字段:
```ts
type BuiltInApplicationId = 'magic-notes' | 'knowledge' | 'heartbeat'
type ApplicationNavigationSettings = {
hiddenBuiltInApplicationIds: BuiltInApplicationId[]
}
```
默认值为空数组,保持升级前所有可用入口可见。ID 数组可以避免为每个应用增加一个顶层布尔
字段;Zod schema 必须去重、限制数量并拒绝未知内置
ID。
首期不保存排序,因为内置应用顺序固定。插件偏好在插件系统确定稳定 identity 和卸载保留
规则后另行扩展,不提前混入当前 schema。
### 4.2 与现有开关组合
入口可见条件为:
```ts
const visible = featureEnabled && !hiddenBuiltInApplicationIds.includes(id)
```
`magicNotesEnabled` 继续控制功能启停。知识库和智能心跳若未来增加业务启停开关,也按同一组合
规则处理。功能停用时设置页保留显隐偏好但禁用显隐 Switch,并提供原功能设置入口。导航设置
不能写回现有开关。
### 4.3 持久化与 IPC
沿用现有应用设置链路:
```text
Renderer
-> window.goodbuddy.updates.updateSettings
-> settings:application:update
-> application-settings-store
-> application-settings.json
```
更新继续由 Main 校验可信 sender、Zod 输入和原子写入。设置版本迁移只补充默认空数组,不
修改已有 `magicNotesEnabled` 或其他平台设置。
## 5. 侧栏底部重构
现有 `user-card` 是一个覆盖整行的设置按钮。实现时替换为容器和相邻独立按钮:
```tsx
<div className="sidebar-footer">
<SidebarSystemTools onOpenTool={openSystemTool} />
<button aria-label={t('navigation.settings')} ...>
<Settings aria-hidden="true" />
</button>
</div>
```
不能在设置按钮内部嵌套工具按钮。设置按钮继续调用 `navigateFromSidebar('settings', trigger)`,
保留设置预加载、焦点恢复和未保存离开确认。
系统工具按钮调用统一 `openSystemTool`:
1. 设置工作栏打开意图。
2. 展开工作栏。
3. 由工作栏创建或激活实例。
4. 按现有策略把焦点移入目标面板。
工作栏仍由窗口右上方的固定开关关闭,关闭后的焦点返回该开关。
## 6. 工作栏状态与快捷按钮
`RightAssistantSidebar` 应向 `App` 暴露当前活动实例的 `appId` 和必要的有界状态摘要,或由
现有 `onTabChange` 扩展得到。主侧栏不读取终端输出、文件内容、URL 或活动详情。
```ts
type SidebarSystemToolState = {
activeToolId?: SidebarSystemToolId
statusByToolId?: Partial<Record<SidebarSystemToolId, 'attention' | 'error' | 'running'>>
}
```
首期可以只实现 `activeToolId`,没有可靠聚合来源时不显示状态徽标。不能根据局部事件猜测错误
或运行状态。
## 7. 应用显隐更新
应用显隐 Switch 复用平台功能设置的即时更新模式。请求开始后禁用三个应用显隐 Switch,只
允许一个完整目标集合处于提交中。Main 返回成功结果后更新 App 中的平台设置快照,主导航
随之变化。失败时恢复控件原值,在对应设置行显示可重试错误;主导航继续读取最后一次成功
保存的设置。
## 8. 插件兼容边界
首期不增加插件注册表、插件 schema 或插件持久化字段,只保留系统 ID 不向插件开放的产品边界。
插件市场设计需要单独确定 Host、权限、签名、生命周期和稳定 identity;不得直接复用 DeepSeek
Harness 的 npm 插件市场。
## 9. 本机资源监控
### 9.1 采集范围
首期在 Main 中使用经过依赖审查的 `systeminformation` 获取整机 CPU、内存和磁盘 IO,使用
Electron `app.getAppMetrics()` 汇总 GoodBuddy 桌面进程 CPU、内存和进程数。采集函数不接收
Renderer 提供的路径、PID、设备名或命令。
首期指标定义:
- CPU:整机忙碌时间占比,规范化为 `0` 至 `100`。
- 内存:总字节数、可用字节数和 `总量 - 可用量` 得到的使用量。
- 磁盘 IO:所有可用本机磁盘的每秒读取与写入字节数。
- GoodBuddy:`app.getAppMetrics()` 返回的桌面进程聚合 CPU、工作集内存和进程数量。各进程
CPU 先相加,再按逻辑处理器数量归一化为整机容量占比 `0` 至 `100`;不能通过截断总和处理
多核数值。
受管 Runtime、MCP 和终端子进程的逐进程指标不在首期范围。磁盘 IO 在平台或权限不足时返回
不可用错误码,不能回退为 `0`。新增依赖在合入前需要完成许可证、维护状态、打包体积和供应链
检查;检查未通过时应选择提供同等跨平台语义的 Main 侧实现,不能改用 Renderer Shell 命令。
### 9.2 快照契约
```ts
type ResourceMetric<T> =
| { status: 'available'; value: T }
| { status: 'warming-up' }
| { status: 'unavailable'; reason: ResourceMetricUnavailableReason }
type LocalResourceSnapshot = {
sampledAt: string
host: {
cpuPercent: ResourceMetric<number>
memory: ResourceMetric<{
usedBytes: number
totalBytes: number
}>
diskIo: ResourceMetric<{
readBytesPerSecond: number
writeBytesPerSecond: number
}>
}
goodbuddy: {
cpuPercent: ResourceMetric<number>
memoryBytes: ResourceMetric<number>
processCount: ResourceMetric<number>
}
}
```
所有数字必须有限且非负;百分比限制为 `0` 至 `100`,字节数设置跨 IPC 上限。`sampledAt` 由
Main 生成。不可用原因使用稳定枚举,例如 `unsupported-platform`、`permission-denied`、
`collector-timeout` 和 `collector-failed`,不向 Renderer 返回原始命令输出。`warming-up` 只用于
依赖两个采样点的 CPU 与磁盘 IO,不能用于掩盖采集失败。
### 9.3 Preload 与 IPC
Preload 只暴露:
```ts
resourceMonitor.getLocalSnapshot(input: {
resetIntervalBaseline: boolean
}): Promise<LocalResourceSnapshot>
```
对应 IPC 为 `resource-monitor:local-snapshot:get`。Main 验证可信 sender;单次采集设置超时,
并合并同一时刻的并发请求,防止多个 Renderer 重复启动系统采集。采集超时后向调用方返回
有界错误,但底层 Promise 仍占用唯一单飞槽位,直到实际结束;在此之前不启动第二个采集
任务。输入只接受布尔值,接口不提供任意进程、磁盘或远程主机查询。
合并规则区分普通请求与基线重置请求:
- 普通请求在途时,新的普通请求复用它。
- 普通请求在途时收到重置请求,Main 最多登记一个待执行重置;当前采集结束后再执行,不能
用普通请求结果满足重置请求。
- 多个重置请求复用同一个在途或待执行重置。
- 重置请求在途时,普通请求复用其 `warming-up` 结果,下一次时钟再取得区间值。
因此服务最多持有一个在途采集和一个待执行重置。调用方超时不取消这个顺序,也不增加队列
长度。
面板每次从非活动状态恢复后的首次请求传入 `resetIntervalBaseline: true`。Main 消费本次
`systeminformation` 与 Electron CPU 读数并重置区间基线,CPU 和磁盘 IO 返回 `warming-up`;
内存等瞬时指标正常返回。后续请求传入 `false` 并计算当前区间值,避免把面板停用期间计入
速率。
### 9.4 轮询与历史
`ResourceMonitorPanel` 仅在工作栏展开、`resources` 为活动实例且
`document.visibilityState === 'visible'` 时启动轮询:
1. 进入活动状态后立即请求一次,并设置 `resetIntervalBaseline: true`。
2. 此后每 2 秒请求一次;前一请求未结束时跳过本次时钟。
3. Tab 失活、工作栏收起、页面变为隐藏或组件卸载时清理定时器,并忽略较晚返回的旧请求。
4. Renderer 环形队列最多保留 150 个带真实时间戳的样本。
5. 采集失败不追加伪造样本,也不持久化历史。
资源数据不写入应用设置、工作栏布局、运行记录或 SQLite。工作栏布局只保存 `resources` 实例
及其活动状态。
## 10. 测试
### 10.1 Renderer 测试
- 固定入口顺序不受三个应用显隐组合影响。
- 运行记录和设置没有显隐控件。
- 四个系统工具按钮具有名称、工具提示、键盘行为和选中状态。
- 文件和浏览器复用单实例;终端优先聚焦当前执行空间实例。
- 工具按钮展开工作栏但不改变当前主页面。
- 显隐更新成功后主导航立即变化,失败时 Switch 和主导航恢复原状态。
- 窄窗口下工具按钮不重叠,焦点恢复正确。
- 资源面板只在可见活动时轮询,跳过重叠请求并把历史限制为 150 项。
- 窗口隐藏、最小化和恢复时停止轮询并重新建立区间基线。
- 资源部分可用、全部失败和重新进入活动状态均显示正确结果。
### 10.2 Main 与契约测试
- 新设置字段默认值、合法 ID、重复 ID、数量上限和未知 ID 校验。
- 旧版本设置迁移后默认显示所有既有应用。
- 更新导航偏好不修改魔法笔记启停等无关字段。
- 原子写入失败时保留最后一次有效设置。
- 资源快照拒绝 NaN、Infinity、负数字节数、越界百分比和未知错误码。
- 采集超时、权限不足和磁盘 IO 不支持时返回有界部分结果。
- 超时采集尚未实际结束时保持单飞,不累积新的后台采集任务。
- 普通采集在途时收到基线重置请求,最多排队一个重置且不复用普通结果。
- `resetIntervalBaseline: true` 时把 CPU 与磁盘 IO 标记为 `warming-up`。
- GoodBuddy CPU 预热时,其内存和进程数仍可独立返回。
- GoodBuddy 多进程 CPU 按逻辑处理器数量归一化,结果保持在 `0` 至 `100`。
- GoodBuddy 摘要不包含 PID、命令行、环境变量或其他进程信息。
### 10.3 回归范围
- 工作栏布局恢复和已关闭实例重建。
- 本地与托管 SSH 终端目标。
- 浏览器无会话、创建中和 Agent 操作中状态。
- 设置页未保存离开确认。
- 魔法笔记待办数和智能心跳建议数徽标。
- 浅色、深色、`220px` 与 `420px` 侧栏宽度。
- Windows、macOS 和 Linux 的真实 CPU、内存与磁盘 IO 采集;不支持项显示准确原因。
## 11. 实施顺序
1. 增加应用导航设置 schema、迁移和设置界面。
2. 按即时保存结果组合主导航,并补充失败恢复。
3. 增加资源快照契约、Main 采集服务和 Preload API。
4. 为工作栏增加 `resources` 单实例和 `ResourceMonitorPanel`。
5. 拆分侧栏底部设置按钮,加入四个系统工具按钮。
6. 为工作栏增加 `openOrActivate` 意图接口和终端匹配规则。
7. 接入选中状态、焦点恢复、无障碍文案和响应式样式。
8. 完成 Renderer、契约、迁移和跨平台回归测试。
插件注册与插件市场不与首期改动一并实施。
@@ -0,0 +1,203 @@
# 应用导航与系统工具入口 UI 设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.2 |
| 日期 | 2026-09-08 |
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 全局规范 | [GoodBuddy 统一界面设计系统](../../../UI-DESIGN.md) |
本文定义入口、设置和轻量资源监控界面。文件、终端和浏览器的内容布局继续遵守助手工作栏
文档。
## 1. 侧栏信息架构
```text
品牌
项目选择
新对话
搜索
当前对话
魔法笔记 3
知识库
智能心跳 1
运行记录
最近对话
...
────────────────────────
[文件] [终端] [浏览器] [资源] [设置]
```
“当前对话”和“运行记录”属于固定系统入口。三个内置应用位于二者之间。侧栏底部不再显示
“本地工作区”和 `platform · arch`;排障信息移至关于或诊断页面。
## 2. 底部系统工具栏
底部使用连续平整表面和顶部分隔线,不做成账户卡片或嵌套卡片。按钮顺序固定:
| 位置 | 图标 | 可见工具提示 | 点击结果 |
| --- | --- | --- | --- |
| 1 | `FolderOpen` | 文件 | 打开或聚焦工作区面板 |
| 2 | `SquareTerminal` | 终端 | 打开或聚焦当前执行空间终端 |
| 3 | `Globe` | 浏览器 | 打开或聚焦受控浏览器面板 |
| 4 | `Activity` | 资源监控 | 打开或聚焦本机资源监控面板 |
| 末位 | `Settings` | 设置 | 打开设置页面 |
图标使用项目现有 Lucide 库。每个按钮采用统一的 `34px × 34px` 点击区域,内部图标建议
`16px`。工具栏使用 `8px` 内边距和 `4px` 间隔;设置按钮通过自动空白靠右,不添加第二条
视觉分隔线。
### 2.1 状态
- 默认:透明背景,图标使用次级文字色。
- 悬停:使用弱表面色,不能强于当前选中状态。
- 选中:使用强调选中背景,并提高图标对比度。
- 键盘焦点:显示统一焦点环,不能被侧栏边缘裁切。
- 处理中:创建或激活请求未完成时暂时禁用对应按钮,保留尺寸并提供进行中名称。
- 徽标:只用于等待处理、错误或活动状态,不显示无意义的常驻绿色圆点。
选中状态表达“对应工作栏面板正在显示”,不表达功能已安装或权限已授予。
## 3. 主导航应用区
应用入口沿用现有 `nav-item` 行,不使用应用卡片。图标、名称和徽标保持单行:
```text
[图标] 魔法笔记 3
[图标] 知识库
[图标] 智能心跳 1
```
隐藏某个应用后,其余应用向上补位。运行记录始终位于最后一个可见应用之后,不因应用显隐
改变样式或归类为工具。
设置页打开期间,主侧栏显示最后一次成功保存的导航状态。Switch 更新成功后导航立即变化,
失败时保持原导航。
## 4. 应用与工具设置
设置中心新增“应用与工具”分类,采用 `standard` PageShell:
```text
应用与工具
管理主导航中的应用。系统工具由 GoodBuddy 提供并固定在侧栏底部。
应用
魔法笔记 笔记、待办和评论 [显示在侧边栏 开]
知识库 管理和检索知识 [显示在侧边栏 开]
智能心跳 查看计划、建议和运行历史 [显示在侧边栏 开]
系统工具
文件 打开当前工作区 内置
终端 进入当前执行空间 内置
浏览器 打开 GoodBuddy 受控浏览器 内置
资源监控 查看本机 CPU、内存和磁盘 IO 内置
系统入口
当前对话、运行记录和设置位置固定,不参与上述配置。
```
应用显隐使用共享 Switch,标签必须是“显示在侧边栏”,不能缩写为“启用”。切换时立即保存,
请求期间禁用三个应用显隐 Switch;失败后恢复原值,并在对应设置行显示可重试错误。系统工具
使用紧凑只读列表,不渲染禁用的卸载、停用或固定按钮。
功能已停用时,对应应用行显示“功能已停用”和设置入口,保留显隐偏好但暂时禁用 Switch。
若某应用自身支持业务启停,应在该应用原有设置中保留“启用功能”开关,并用说明区分:
- “显示在侧边栏”只控制入口。
- “启用功能”控制该功能是否运行。
## 5. 助手工作栏反馈
点击系统工具后,右侧工作栏按现有布局回流打开。主内容页面保持不变,侧栏不渲染工具内容。
- 文件:激活“工作区”Tab,焦点进入目录区域或错误修复入口。
- 终端:激活匹配终端,焦点进入终端输入区;新建期间保留按钮宽度和进度状态。
- 浏览器:激活“浏览器”Tab,焦点进入工具栏;没有会话时进入空状态主操作。
- 资源监控:激活“资源监控”Tab,焦点进入指标摘要;首个样本加载后开始绘制历史。
后台事件可以更新徽标,不能自动展开工作栏或切换当前 Tab。由用户点击快捷按钮触发的打开
属于显式操作,可以展开工作栏并切换 Tab。
### 5.1 资源监控面板
资源监控使用紧凑单列,不使用指标卡片墙:
```text
资源监控 本机
CPU 18%
[最近 5 分钟折线图]
内存 9.6 / 32 GB
[使用量进度条]
磁盘 IO 读 12 MB/s · 写 3 MB/s
GoodBuddy
CPU 4% · 内存 620 MB · 桌面进程 7
更新于 14:32:08
```
- CPU 折线纵轴固定为 `0%` 至 `100%`,不能根据当前低负载动态放大波动。
- 内存同时显示使用量、总量和百分比;进度条不代替文字数值。
- 磁盘 IO 使用每秒读写字节并按统一单位格式化,不显示累计设备字节数。
- GoodBuddy 摘要只显示聚合值,不列 PID、命令行或环境变量。
- 首次加载或停止后恢复时,CPU 和磁盘 IO 显示“正在建立采样基线”,不先渲染 `0%`;内存
等瞬时指标可以立即显示。
- 部分可用时只在对应指标行显示“不可用”和简短原因;全部失败时显示重试入口。
- 图表不使用动效平移;`prefers-reduced-motion` 下行为不变。
- 面板失去活动状态、工作栏收起或窗口隐藏、最小化时停止更新;返回后重新建立区间指标基线,
不填补离开期间的历史。
## 6. 文案
固定使用以下名称:
| 对象 | 文案 |
| --- | --- |
| 设置分类 | 应用与工具 |
| 文件系统工具 | 文件 |
| 终端系统工具 | 终端 |
| 浏览器系统工具 | 浏览器 |
| 浏览器说明 | GoodBuddy 受控浏览器 |
| 资源系统工具 | 资源监控 |
| 资源范围 | 本机 |
| 指标缺失 | 不可用 |
| 应用显隐开关 | 显示在侧边栏 |
| 内置来源 | 内置 |
不再使用“本地工作区”作为侧栏底部身份名称。项目和默认工作区的路径继续由项目选择器、页面
范围或工作区面板显示。
## 7. 键盘和无障碍
- 底部按钮均为独立 `button`,不能嵌套在整行设置按钮中。
- Tab 顺序为文件、终端、浏览器、资源监控、设置。
- 图标按钮通过 `aria-label` 提供名称,工具提示不能作为唯一可访问名称。
- 工作栏打开后按现有焦点策略聚焦目标面板;用户通过固定工作栏开关关闭时,焦点返回该开关。
- 选中按钮使用 `aria-pressed` 或等价的可访问状态,不能只依靠背景色。
- 徽标的数量或状态合并进按钮可访问名称,例如“终端,1 个等待处理”。
- 应用显隐 Switch 支持 Space 切换,保存失败后恢复原值并保留焦点。
## 8. 响应式
- 侧栏宽度 `220px` 至 `420px` 时,底部按钮尺寸不随宽度缩放。
- 窄窗口继续使用现有侧栏抽屉;工具栏不转为多行。
- 空间不足时缩小设置按钮前的弹性空白,不隐藏四个系统工具和设置。
- 工具提示不得超出窗口;靠近底边时显示在按钮上方。
- 粗指针设备不依赖悬停,按钮的可访问名称和选中反馈保持有效。
## 9. 未来插件入口
插件市场交付后另行定义插件区域、固定数量、溢出和权限界面。系统入口与四个系统工具的位置
保持保留,不向插件开放。
@@ -0,0 +1,228 @@
# 应用导航与系统工具入口 User Stories
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.2 |
| 日期 | 2026-09-08 |
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
| 关联逻辑 | [功能逻辑设计](./logic-design.md) |
## 1. 角色
### 1.1 普通用户
使用对话、笔记和知识功能,希望常用入口稳定,不需要理解工作栏实例模型。
### 1.2 本地工作用户
经常查看项目文件、打开终端、使用受控浏览器或检查电脑负载,希望从任意一级页面快速进入
这些工具。
## 2. Epic A:固定系统入口
### US-A1 始终访问运行记录
作为用户,我希望运行记录保持在固定位置,以便应用入口变化后仍能检查 Agent 和工具活动。
验收:
- Given 用户隐藏任意内置应用
- When 用户查看主导航
- Then “运行记录”仍显示在应用区之后
- And 设置中没有隐藏、卸载或固定运行记录的选项
### US-A2 从固定入口进入设置
作为用户,我希望设置入口始终位于侧栏底部,以便快捷工具状态变化时仍能进入配置。
验收:
- Given 任意主页面和任意侧栏宽度
- When 用户查看侧栏底部
- Then 设置按钮保持可见且点击区域不与工具按钮重叠
- And 点击后继续遵守未保存内容的离开确认
## 3. Epic B:管理应用入口
### US-B1 识别完整应用
作为用户,我希望魔法笔记、知识库和智能心跳显示在主导航,以便知道它们会打开完整页面。
验收:
- Given 对应功能已启用且应用设置为显示
- When 用户打开主侧栏
- Then 应用位于“当前对话”和“运行记录”之间
- And 点击应用在主内容区打开对应一级页面
- And 原有待办或建议徽标继续显示
### US-B2 隐藏低频应用
作为用户,我希望隐藏不常用的内置应用,以便缩短主导航,但保留已有数据。
验收:
- Given 应用已有内容
- When 用户关闭该应用的“显示在侧边栏”Switch
- Then 主导航不再显示该应用
- And 应用数据、计划和业务启停状态不变
- And 用户可从同一设置位置恢复显示
### US-B4 保留功能启停语义
作为魔法笔记用户,我希望导航显隐与功能启停相互独立,以免整理导航时关闭功能。
验收:
- Given 魔法笔记功能已启用
- When 用户只隐藏其侧栏入口
- Then 笔记及待办处理能力继续按原设置工作
- And 再次显示时恢复原页面和数据
### US-B5 恢复显隐保存失败
作为用户,我希望入口设置保存失败时继续使用原导航,以免界面状态与实际设置不一致。
验收:
- Given 应用显隐更新尚未完成
- When 用户切换“显示在侧边栏”Switch
- Then 三个应用显隐 Switch 在请求期间暂时不可操作
- And 更新成功后主导航立即变化
- And 更新失败时 Switch 和主导航恢复原状态并显示可重试错误
## 4. Epic C:使用系统工具
### US-C1 一键查看文件
作为本地工作用户,我希望点击左下角文件按钮后直接查看当前工作区文件。
验收:
- Given 当前项目有有效根目录
- When 用户点击文件按钮
- Then 助手工作栏展开
- And 已有工作区面板时聚焦该实例,没有时创建单实例
- And 文件按钮通过选中状态表明该面板正在显示
### US-C2 处理无效工作区
作为用户,我希望目录不可用时看到修复方法,并收到明确反馈。
验收:
- Given 工作区面板尚未创建或处于跟随模式
- And 当前项目和默认工作区均不可用
- When 用户点击文件按钮
- Then 工作区面板仍打开
- And 面板说明目录不可用并提供选择或修复目录的入口
- And 应用不静默改用其他目录
### US-C3 快速进入终端
作为本地工作用户,我希望点击终端按钮后进入当前执行空间的终端。
验收:
- Given 当前执行空间已有正在启动或运行中的终端
- When 用户点击终端按钮
- Then 工作栏聚焦最近使用的匹配终端,不创建重复实例
- Given 当前执行空间没有终端
- When 用户点击终端按钮
- Then 创建一个绑定该执行空间的新终端
- Given 当前没有项目
- When 用户点击终端按钮
- Then 创建或聚焦使用本机 Home 的终端
- Given 当前执行空间只有已退出、已中断或失败的终端
- When 用户点击终端按钮
- Then 创建新终端并保留原终端的历史状态
### US-C4 使用远程项目终端
作为远程项目用户,我希望同一个终端按钮进入远程项目目录,以免误在本机执行命令。
验收:
- Given 当前项目绑定托管 SSH 执行空间
- When 用户点击终端按钮
- Then 创建或聚焦绑定该远程项目及其权威目录的终端
- And 面板持续显示远程主机和工作目录
- And 连接失败时保留重连与诊断信息
### US-C5 打开受控浏览器
作为用户,我希望浏览器按钮打开 GoodBuddy 受控浏览器,以便查看与 Agent 相同的会话。
验收:
- Given 浏览器面板跟随当前上下文且当前会话已有浏览器会话
- When 用户点击浏览器按钮
- Then 工作栏聚焦同一个浏览器会话
- And 不打开系统默认浏览器或第二个 WebView
- Given 浏览器面板已固定到其他会话
- When 用户点击浏览器按钮
- Then 工作栏聚焦该面板并保持固定目标
- Given 浏览器面板尚未创建或处于跟随模式
- And 当前没有活动对话
- When 用户点击浏览器按钮
- Then 面板说明需要选择或创建对话
### US-C6 查看本机资源
作为本地工作用户,我希望快速查看 CPU、内存和磁盘 IO,以便判断任务变慢是否与本机负载
有关。
验收:
- Given 资源监控面板尚未打开
- When 用户点击左下角资源监控按钮
- Then 助手工作栏创建并激活单实例资源监控面板
- And 页面显示整机 CPU、内存、磁盘读写及 GoodBuddy 桌面进程摘要
- And 页面每 2 秒更新一次,并用图表保留最近 5 分钟
- When 用户切换到其他工作栏 Tab、收起工作栏、隐藏窗口或最小化窗口
- Then 资源监控停止请求新样本
- When 用户再次显示窗口并返回资源监控
- Then 首次采样建立区间指标基线,下一次采样开始显示 CPU 和磁盘 IO 速率
- Given 某项指标在当前平台不可用
- When 页面收到部分结果
- Then 该指标显示“不可用”及原因,其他指标继续更新
### US-C7 从任意应用打开工具
作为用户,我希望在魔法笔记、知识库、智能心跳和运行记录页面也能打开系统工具。
验收:
- Given 用户位于任意一级页面
- When 用户点击文件、终端、浏览器或资源监控
- Then 主页面保持不变
- And 右侧工作栏打开对应面板
- And 工具不会覆盖主内容或伪装成一级页面
## 5. Epic D:可访问性和布局
### US-D1 使用键盘打开系统工具
作为键盘用户,我希望按顺序聚焦并打开左下角工具,以便不依赖鼠标。
验收:
- 每个系统工具和设置都是独立按钮
- Tab 顺序与视觉顺序一致
- Enter 和 Space 均可激活按钮
- 每个纯图标按钮都有可访问名称和工具提示
- 焦点样式不被侧栏边界裁切
### US-D2 在窄窗口使用快捷栏
作为小屏用户,我希望侧栏抽屉中的工具按钮保持可操作。
验收:
- 工具按钮保持单行且不重叠
- 不因宽度不足隐藏文件、终端、浏览器、资源监控或设置
- 触控点击区域符合统一界面规范
- 关闭侧栏抽屉后焦点返回原触发按钮
+15
View File
@@ -0,0 +1,15 @@
# 助手工作栏
助手工作栏统一承载本机与远程执行空间、Task、成果、文件、浏览器和终端等持续工作界面。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [通用助手工作栏与执行空间 PRD](./prd.md) | 工作栏整体产品范围、执行空间和各工作面板归属 |
| [多终端页签 PRD](./terminal-tabs-prd.md) | 多终端 Tab、目标选择、关闭确认和终端生命周期 |
| [实现与验证进度](./progress.md) | 工作区与 Runtime 相关改动的实现事实、验证证据与剩余阻塞 |
| [Runtime 交互边界](./runtime-interactions.md) | 现有原生权限、结构化问答、后台拒绝和非结构化终端交互的适配范围 |
当前尚未拆出独立 User Stories、功能逻辑、UI 设计和技术设计文档;相关内容仍保留在
两份现有 PRD 中,后续实际维护该功能时按职责拆分。
@@ -5,11 +5,11 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.5 |
| 日期 | 2026-08-28 |
| 版本 | 0.6 |
| 日期 | 2026-08-31 |
| 适用产品 | GoodBuddy 桌面端 |
| 相关设计 | [统一界面设计系统](../../../UI-DESIGN.md)、[SSH 远程主机与 GoodBuddy Agent 稳定终态设计](../../architecture/remote-host-and-goodbuddy-agent-design.md) |
| 相关能力 | [会话监督](../supervision/conversation-supervision-prd.md)、[自动化平台](../../architecture/automation-platform-architecture.md)、[长期助手路线图](../../roadmap/long-term-assistant-roadmap.md) |
| 相关设计 | [统一界面设计系统](../../../UI-DESIGN.md)、[SSH 远程主机与 GoodBuddy Agent 实现说明](../remote-host/technical-design.md) |
| 相关能力 | [会话监督](../conversation-supervision/prd.md)、[自动化平台](../../architecture/automation-platform-architecture.md)、[长期助手路线图](../../roadmap/long-term-assistant-roadmap.md) |
## 1. 背景
@@ -36,8 +36,8 @@ Runtime 统一抽象为“执行空间”。
### 2.1 助手工作栏
助手工作栏是 GoodBuddy 中始终可访问的应用级工具容器。它提供稳定能力目录,用户从中
查看任务中心,并按需打开一个或多个监督、Runtime、终端、进程、工作区、浏览器和成果
面板实例。稳定的是能力的可发现性,不是八个同时占据界面的固定面板。
查看任务中心,并按需打开一个或多个监督、Runtime、终端、进程、工作区、浏览器、资源监控
和成果面板实例。稳定的是能力的可发现性,不是九个同时占据界面的固定面板。
工作栏不是:
@@ -83,6 +83,7 @@ Runtime
进程
工作区
浏览器
资源监控
成果
```
@@ -104,7 +105,8 @@ Runtime
- 监督默认选择当前会话,用户可以改选其他 Conversation、Task 或实验对象。
- Runtime 默认跟随当前会话或 Task;执行事件可以查看,但 Job/Run 不作为独立选择对象。
- 终端默认使用当前项目执行空间,用户可以新建本机或远程终端。
- 终端创建时固定到当前项目执行空间,由项目配置自动决定本机或托管 SSH;没有当前项目时
使用本机 Home。
- 工作区默认显示当前项目目录,用户可以打开其他本机目录或远程目录。
- 成果默认使用当前范围,用户可以切换到项目、全局或其他允许范围。
- 下一次请求的附件和知识库由当前 Composer 展示与管理,不作为工作栏能力。历史执行上下文
@@ -134,8 +136,8 @@ Runtime
- 当前 Runtime 不支持后台 Job 时,Runtime 能力仍可发现;打开后说明当前可监督的内容。
- 当前执行空间没有 Git 仓库时,工作区文件功能仍可使用,Git 区域显示不可用原因。
- 没有活动进程时,进程面板提供创建终端或启动 Runtime 的入口。
- 没有项目时,终端和工作区允许用户选择本机目录或远程主机,但必须先取得有界临时
Execution Space Grant,不能把可选路径直接传给文件、Git、Process 或 Runtime。
- 没有项目时,终端使用当前用户本机 Home;工作区等允许选择路径的能力仍必须先取得有界
临时 Execution Space Grant,不能把可选路径直接传给文件、Git、Process 或 Runtime。
- 监督未启用时,监督面板提供目标、模式和“开始监督”,而不是隐藏能力。
不得渲染成排没有解释的禁用按钮,也不得把“进程连通”描述为已经支持完整原生监督。
@@ -167,7 +169,7 @@ Runtime
- 建立统一范围、执行空间、生命周期、成果和控制契约。
- 复用现有 Project、Conversation、Task、Artifact、Activity 和 Approval 数据。
- 保持 Renderer 无任意文件、进程、PTY、SSH 或 Electron API 能力。
- 保持 Ask 只读、Execute 审批、取消、超时、输出边界和活动审计。
- 保持 Ask 只读、Execute 账号完整授权、取消、超时、输出边界和活动审计。
- 为本机与远程能力提供一致 UI,同时准确表达能力差异。
## 5. 非目标
@@ -201,22 +203,27 @@ Runtime
### 6.2 能力目录与面板实例
能力目录不等于同时打开八个面板。推荐使用工作栏内的纵向能力导航,并在旁边或停靠区域管理
用户已经打开的面板实例:
能力目录不等于同时打开九个面板。当前产品采用“已打开实例 Tab + 持久新增按钮”的工作栏
模型:Tab 行只显示已打开实例,行末“+”始终可见;点击后在工作栏内容区打开稳定能力目录,
用户再创建或激活应用实例。详细交互以
[助手工作栏多终端页签 PRD](./terminal-tabs-prd.md) 为准。
- 每项始终显示稳定图标,并提供可见标签或可持续查看的工具提示。
- 目录使用与其交互模型匹配的列表、工具栏或菜单语义;单实例切换使用 `tablist`、`tab`、
`tabpanel`,多实例停靠区使用有名称的区域和明确面板标题。
- 已打开实例使用 `tablist`、`tab`、`tabpanel`;“+”是位于 `tablist` 外的普通命令按钮。
- 应用注册表声明 `single` 或 `multiple`。在工作栏“+”目录中重复选择单实例应用时聚焦已有
实例;选择多实例应用时每次创建新实例。主侧栏系统工具快捷入口可以聚焦当前执行空间中
最近使用的终端,额外终端仍从“+”目录创建。
- 支持方向键、Home、End、Enter、Space、关闭面板和正确焦点恢复。
- 徽标显示未解决数量、等待审批或失败状态,并同时提供文字或可访问名称。
- 用户调整目录顺序、打开实例、停靠位置和尺寸后持久化;关闭实例后能力仍可从目录重新打开。
- 默认布局只恢复经过产品确认的少量常用面板,不自动打开全部标准能力。
- 每个实例显示稳定实例 ID、能力名称、跟随或固定状态与当前目标;同能力多实例不能只靠位置区分。
当前聊天右栏过渡实现保留 Task Center、工作区、浏览器和成果四个横向页签时,必须
单行横向滚动,不能自动隐藏或缩写到不可辨认。Task Center 继续作为 Task 的现有入口;
审批定位到所属 Task 或 Runtime。智能心跳不作为工作栏页签,其报告、建议、历史和完整配置统一归属
“智能心跳”菜单入口。当前阶段不新增独立自动化中心。
从当前聊天右栏迁移时,Task Center、工作区、浏览器和成果作为默认打开的单实例应用保留;
终端不默认打开,由用户通过“+”创建,并从首期开始支持多实例。Tab 必须单行横向滚动,
不能自动隐藏或缩写到不可辨认,“+”不能随滚动消失。Task Center 继续作为 Task 的现有
入口;审批定位到所属 Task 或 Runtime。智能心跳不作为工作栏页签,其报告、建议、历史和
完整配置统一归属“智能心跳”菜单入口。当前阶段不新增独立自动化中心。
### 6.3 工作栏尺寸
@@ -265,7 +272,7 @@ Conversation,默认 Execute,并持续显示 Runtime、Project、工作目录
### 7.2 监督
监督是通用观察与评论入口,详细行为以
[会话监督 PRD](../supervision/conversation-supervision-prd.md) 为准。
[会话监督 PRD](../conversation-supervision/prd.md) 为准。
监督能力在目录中稳定可发现;用户打开面板实例后可以选择:
@@ -311,10 +318,12 @@ Runtime 能力统一监督直连模型、OpenCode、Continue、DeepSeek Harness
### 7.4 终端
终端面板允许用户主动创建和管理本机或 SSH 终端:
终端面板允许用户通过工作栏“+”主动创建和管理多个本机或 SSH 终端。详细产品行为、首期
直接 SSH PTY 技术选择、生命周期和验收以
[助手工作栏多终端页签 PRD](./terminal-tabs-prd.md) 为准。
- 新建、重命名、切换、关闭和重新连接终端。
- 选择执行空间、工作目录和 Shell。
- 创建时自动绑定当前项目执行空间、权威工作目录和平台默认 Shell,不显示重复目标选择。
- 显示本机或远程主机、目录、Shell 和连接状态。
- 支持复制、粘贴、搜索、清屏、滚动和调整终端尺寸。
- 支持将终端切换到右侧、底部或独立窗口。
@@ -353,12 +362,28 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
面板提供:
- 有界目录树和文本文件预览。
- 有界目录树和分页文本文件预览;文件超过单页传输大小时先显示已读取内容,并允许用户继续加载后续内容,不因文件总大小超过单页上限而拒绝预览。
- 当前选择、规范化路径和执行空间。
- 可选 Git 状态、Diff 和仓库信息。
- 显式打开、下载副本或在终端中打开。
- HTML 文件的源码与安全预览。
当前项目目录的文件浏览与 Git 交互遵循以下规则:
- 页签下不重复显示“项目工作区”标题,保留刷新按钮。
- 未提交更改以彩色 Git 字母标识状态:M 修改、A 新增、D 删除、R 重命名、C 复制、
U 未跟踪或冲突、T 类型变化;冲突用危险色区别于未跟踪文件,同时保留原始状态。
- 点击更改项进入该文件 Diff,分别显示已暂存和未暂存差异,增删行使用对应状态色;
删除文件也可查看。目录树中的文件仍打开内容预览。不再提供底部全仓库 Diff 入口。
- 手动刷新或任务结束更新 Git 状态时,同步重读根目录和已展开目录,保留目录展开状态,
清除未展开目录的旧缓存;正在查看的单文件 Diff 同时重读,旧请求不得覆盖新结果。
无首次提交的仓库也应显示新增和暂存文件。
- 未提交更改首批显示 50 项,可继续分批加载其余变更,不能使后续文件尤其是已删除
文件失去 Diff 入口。
- 系统默认应用和资源管理器打开按钮仅用于本机项目,SSH 项目不显示这两个按钮。
目录切换、下载副本、终端打开及 HTML 安全预览仍属于后续能力,不能据此视为已实现。
本机和远程访问都必须由 Main 或远程 GoodBuddy Agent 在对应文件系统上执行路径规范化、相对路径和
符号链接边界检查。Renderer 只能提交受约束的相对路径和已授权范围 ID。
@@ -369,14 +394,58 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
- 创建新的 GoodBuddy 隔离浏览器会话。
- 选择当前会话或其他受控浏览器会话。
- 查看状态、当前 URL、有界画面和错误。
- 进入明确的交互模式或停止会话。
- 通过顶部浏览器工具栏返回、刷新或停止加载、编辑地址并前往。
- 进入明确的交互模式或关闭浏览器。
没有浏览器会话时显示“新建浏览器会话”,而不是隐藏能力。模型或后台浏览器活动可以更新
徽标,但不得无条件打开面板或切换用户当前面板。
顶部工具栏按“返回、刷新/停止加载、地址输入、前往、交互、关闭浏览器”组织。“刷新”在
页面加载期间原位切换为“停止加载”;“关闭浏览器”是清楚独立于导航的会话生命周期操作,
必须与“停止加载”在文案和视觉上明确区分。现有“停止浏览器”语义统一改名为“关闭浏览器”。
浏览器面板和 Agent 操作同一条按 Conversation 归属的浏览器会话,不创建用户预览专用的
第二个页面。用户执行前往、返回或刷新会有意改变 Agent 下一步读取和操作的页面。所有
前往、返回和刷新请求都进入 `BrowserService`,并与 Agent 发起的浏览器操作串行执行;不在
Renderer 建立第二套导航队列、抢占规则或恢复状态机。
浏览器工具栏读取的权威状态至少包含:
```ts
type BrowserToolbarState = {
committedUrl: string
loading: boolean
canGoBack: boolean
sessionActivity: 'inactive' | 'creating' | 'ready' | 'agent_action' | 'full_interaction'
}
```
`committedUrl` 是浏览器已经提交的权威 URL。地址输入在未编辑时与它同步;获得编辑意图后,
输入文字保持为 Renderer 本地草稿,画面、加载状态和 URL 更新不得覆盖草稿,直到用户“前往”
或放弃编辑。用户提交未带 `http://` 或 `https://` 的裸域名时,Renderer 自动补上
`http://`,再通过既有浏览器 URL 策略提交。地址草稿不属于浏览器会话持久状态,也不需要
恢复或版本化机制。
创建会话、Agent 操作和完整交互窗口期间,返回、刷新、地址提交、前往、交互和关闭浏览器
控件禁用,避免与同一会话并发操作。例外是活动导航处于加载中时,“停止加载”保持可用,
可中断当前页面加载;
它只取消页面加载,不销毁浏览器会话、不执行“关闭浏览器”,也不释放当前 Runtime 上下文。
“关闭浏览器”沿用现有停止会话的生命周期语义。
没有浏览器会话时仍显示工具栏。活动 Conversation 可以直接在地址栏输入非空 URL 并“前往”,
由 `BrowserService` 通过 Agent 浏览器相同的能力检查和 URL 策略创建会话并导航;不得为这一
空状态增加旁路 WebView 或宽松 URL 入口。没有活动 Conversation 时说明需要先选择或创建会话。
模型或后台浏览器活动可以更新徽标,但不得无条件打开面板或切换用户当前面板。
浏览器面板只管理 GoodBuddy 受控浏览器,不表示可以控制用户已安装的浏览器。
### 7.8 成果
### 7.8 资源监控
资源监控是本机单实例面板,显示整机 CPU、内存、磁盘 IO 和 GoodBuddy 桌面进程摘要。面板
只在自身可见并处于活动状态时采样,数据保留在 Renderer 有界内存中,不进入运行记录或长期
存储。首期不提供远程主机、GPU、显存、网络吞吐、后台告警或任意进程查看。
指标缺失时保留面板入口并显示不可用原因。资源监控入口、采样频率、数据契约与验收由
[应用导航与系统工具入口](../application-tool-navigation/README.md)定义。
### 7.9 成果
成果面板统一显示全局、项目、Conversation、Task 执行和监督显式产生的独立成果:
@@ -404,7 +473,7 @@ Task 呈现来源,不把 Run 作为导航对象。
- 不使用 `dangerouslySetInnerHTML`,不启用 Electron `webviewTag`。
- 外部打开是明确的用户操作,并说明外部浏览器可能执行脚本或联网。
### 7.9 Composer 与任务上下文
### 7.10 Composer 与任务上下文
工作栏不提供独立“上下文”能力。用户准备下一次模型请求时:
@@ -428,6 +497,7 @@ type WorkbarCapabilityId =
| 'processes'
| 'workspace'
| 'browser'
| 'resources'
| 'results'
type WorkbarTargetRef =
@@ -470,7 +540,7 @@ type WorkbarPanelInstance = {
本节定义产品入口和用户行为。Host Key、凭据、远程 Agent 协议、模型请求代理、签名更新、
断线和 Runtime 适配的技术边界以
[SSH 远程主机与 GoodBuddy Agent 稳定终态设计](../../architecture/remote-host-and-goodbuddy-agent-design.md)
[SSH 远程主机与 GoodBuddy Agent 稳定终态设计](../remote-host/technical-design.md)
为准。
### 9.1 设置入口
@@ -512,10 +582,12 @@ SSH/远程项目操作;已有 Host、项目和凭据保持不变。若当前
不代表环境健康。环境准备失败或取消保留 Host 并显示可重试状态;重新安装失败时显示
本次操作未完成,并以随后重新检查的版本卡片表达当前事实,不对不确定提交宣称旧版本
未被替换。详细技术契约见
[SSH Host 远程环境准备与直连下载设计](../../architecture/remote-host-environment-provisioning-design.md)。
[SSH Host 远程环境准备与直连下载设计](../remote-host/environment-provisioning-technical-design.md)。
进入或切回“主机与远程执行”页面只读取本地 Host 列表,不逐台建立 SSH 连接。版本和瞬时
下载能力仅在用户点击某台 Host 的“刷新版本”时探测。该动作不是项目连接前置条件;用户
下载能力仅在用户点击某台 Host 的“刷新版本”时探测,成功结果在当前 Renderer 进程内按
Host 持久化缓存,切换页面或重启应用后返回直接显示;Host 编辑、删除或开始更新远程环境后
不复用旧结果。该动作不是项目连接前置条件;用户
切换已有远程项目只更新 Renderer 的本地项目选择,不建立 Host/Agent 连接。
新建远程项目弹窗同样只读取本地 Host 验证记录,不扫描所有 Host;浏览目录或保存时才连接
所选 Host。版本号相同时仍允许用户显式重新安装,以修复 registry、签名或 identity 异常,
@@ -707,12 +779,16 @@ remote_operations
其中:
- 工作栏偏好只保存能力目录顺序、面板实例、停靠布局、尺寸和目标引用。
- 资源监控只保存面板实例,不保存采样结果或历史曲线。
- 主机表只保存非敏感元数据和加密凭据引用。
- 应用重启后先通过远程 Daemon 的 journal、lease、boot identity 和 event cursor 核对活动
终端、进程与 Runtime;无法确认时标记 `interrupted` 或 `outcome_unknown`。
- 日志使用有界环形缓冲或分页持久化,不能无限写入 SQLite。
- Artifact 继续作为成果的权威实体,不把完整成果复制进工作栏状态。
- 当前 Renderer `localStorage` 活动记录不能作为 Runtime、监督或进程的权威来源。
- 运行记录由 Main SQLite 持久化,Renderer 只保留当前内存视图。升级时迁移旧
`localStorage` 记录;若旧缓存达到原数量、详情或总大小边界,页面持续说明更早记录或
部分详情可能已经丢失。新记录不再按固定活动数或详情字符数丢弃,长列表只做可继续展开的
分批渲染。
### 11.3 IPC 与 Preload
@@ -723,7 +799,7 @@ Renderer 只通过显式方法访问:
- 终端创建、输入、调整大小、关闭和有界输出订阅。
- 受管进程列表、日志和允许的控制动作。
- Runtime Inspector 快照、事件和允许的控制动作。
- 工作区、成果、浏览器和监督的既有或扩展服务。
- 工作区、成果、浏览器和监督的既有或扩展服务,以及有界的本机资源快照。
每个 Main Handler 都必须验证可信发送者、Zod 输入、对象归属和当前状态。不得暴露 raw
Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
@@ -732,7 +808,7 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
1. 工作栏能力目录项和面板实例不授予任何能力;权限只由 Main 中的范围和控制契约产生。
2. Ask 在本机和远程 Runtime 边界保持只读。
3. Execute 继续经过现有 Runtime 和审批控制,工作栏不能直接放宽。
3. Execute 使用当前执行账号的完整权限,不另加目录或逐工具审批;工作栏不能绕过 Main、Preload 与用户数据边界。
4. 用户终端和 Agent 工具执行使用不同身份和事件来源。
5. 进程面板只控制 GoodBuddy 受管对象,不接受任意 PID。
6. 本机和远程路径分别在对应文件系统上 canonicalize 并验证符号链接边界。
@@ -803,16 +879,19 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
### 阶段 1:成果与 Runtime 可观测性
- 本机 CPU、内存、磁盘 IO 和 GoodBuddy 桌面进程摘要的按需资源监控。
- 通用 Artifact Event。
- HTML 工作区和成果的静态即时预览。
- Runtime Inspector Snapshot 与事件。
- OpenCode 会话、子会话、Todo、工具、用量和取消。
- 直连模型及现有 GoodBuddy Subagent 的统一展示。
- OpenCode 会话、子会话、Todo、工具、用量和取消;本地与托管 SSH 的原生 Task 调用在
Conversation 中统一显示为可追踪的 Subagent 状态,而不是普通工具参数卡。
- 直连模型、OpenCode 原生 Subagent 及现有 GoodBuddy Subagent 的统一展示。
### 阶段 2:监督、终端与受管进程
- 普通会话手动监督和右栏评论流。
- 本机 PTY 终端。
- 工作栏持久“+”、应用注册表和单/多实例 Tab。
- 本机 PTY 与直接 SSH PTY 多终端。
- 受管进程注册、日志和终止。
- 自动回复后监督、节流和独立预算。
- 用户选择终端停靠位置。
@@ -832,8 +911,9 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
- 签名、side-by-side GoodBuddy Agent 安装和按需 detached Daemon。
- SSH connection pool、固定 attach relay、Agent 控制协议、Agent 本地 ACP 日志和断线重连同步。
- 远程工作区、Git、终端和受管进程。
- 先完成签名 OpenCode bundle、Ask bubblewrap、Execute 直接进程、官方 ACP channel、
Main 模型网关、取消、超时和活动记录。
- 当前签名 OpenCode bundle 的 Ask/Execute 均直接启动,Ask 在工具边界保持只读,
Execute 使用账号完整权限;官方 ACP channel、Agent 本地模型网关、取消、超时和活动记录
以[远程主机技术设计](../remote-host/technical-design.md)为准。
- OpenCode 闭环稳定后,Continue 与 DeepSeek Harness 复用相同面板和通道契约逐个接入。
- 新增或重新验证 Host 时只保存并探测 Agent/Runtime,不自动安装。用户在 Host 卡片用
单一主按钮启动,并用次级控件选择自动、Host 下载或 GoodBuddy 传输;Host 保存与环境
@@ -861,7 +941,7 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
### 17.1 稳定能力目录与用户控制
- [ ] 八个标准能力在所有主要页面的目录中始终可发现,但不会默认同时打开。
- [ ] 九个标准能力在所有主要页面的目录中始终可发现,但不会默认同时打开。
- [ ] Task Center 继续作为 Task 的单例索引,不删除入口、不复制会话,也不混入 Job、Run 或心跳事项。
- [ ] 项目、会话、Runtime 或主机变化不会无提示地增删能力目录项。
- [ ] 用户可以按需打开、关闭、排序和停靠面板实例。
@@ -914,22 +994,29 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。
- [ ] 仅使用键盘可以选择能力、面板实例、目标、执行空间和控制动作。
- [ ] 状态不只依赖颜色,徽标具有文字或可访问名称。
- [ ] 终端、HTML、监督证据和高频日志具有可访问替代或降噪行为。
- [ ] 浏览器 Tab 顶部工具栏始终清楚区分“停止加载”和“关闭浏览器”;地址编辑草稿不会被
权威状态刷新覆盖,用户与 Agent 的导航通过同一 `BrowserService` 会话串行执行。
- [ ] 创建中、Agent 操作中和完整交互期间禁用浏览器其余工具栏控件;活动加载仍可单独停止,
且停止加载不会关闭会话或释放 Runtime 上下文。
- [ ] 活动 Conversation 没有浏览器会话时,可从同一地址栏按 Agent 相同 URL 策略启动会话。
## 18. 相关文档的职责
- 本文是助手工作栏稳定能力目录、用户面板实例、范围控制和执行空间的产品总契约。
- [助手工作栏多终端页签 PRD](./terminal-tabs-prd.md) 定义持久“+”、应用实例策略、
动态 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 索引。
- [智能心跳 PRD](../smart-heartbeat/smart-heartbeat-prd.md) 定义心跳入口、范围和长期边界。
- [会话监督 PRD](../supervision/conversation-supervision-prd.md) 定义监督判断、证据、预算和介入边界。
- [智能心跳 PRD](../smart-heartbeat/prd.md) 定义心跳入口、范围和长期边界。
- [会话监督 PRD](../conversation-supervision/prd.md) 定义监督判断、证据、预算和介入边界。
- [自动化平台总体设计](../../architecture/automation-platform-architecture.md) 定义 Plan、Job、Run、监督、预算和记忆。
- [长期助手路线图](../../roadmap/long-term-assistant-roadmap.md) 记录整体长期能力与实施背景。
- [DeepSeek Harness Runtime 设计](../../architecture/deepseek-harness-runtime-design.md) 定义该 Runtime 的具体适配边界。
- [SSH Host 远程环境准备与直连下载设计](../../architecture/remote-host-environment-provisioning-design.md)
- [DeepSeek Harness Runtime 设计](../deepseek-harness/technical-design.md) 定义该 Runtime 的具体适配边界。
- [SSH Host 远程环境准备与直连下载设计](../remote-host/environment-provisioning-technical-design.md)
定义 Host 级组件获取、首次准备、更新、回退和项目只验证边界。
- [统一界面设计系统](../../../UI-DESIGN.md) 定义视觉、语义、响应式和无障碍规则。
若其他文档把工作栏描述为八个同时固定显示的栏目、根据项目或 Runtime 自动裁剪的动态入口,
若其他文档把工作栏描述为九个同时固定显示的栏目、根据项目或 Runtime 自动裁剪的动态入口,
或仅属于当前聊天的附属区域,以本文“能力目录稳定、面板实例由用户打开、当前上下文只提供
默认值”的产品决策为准。
@@ -0,0 +1,79 @@
# 工作栏实现与验证进度
## 2026-09-08 Execute 目录权限等待修复
- 本机 OpenCode 的内部配置显式允许默认工具权限;每次 Ask 仍设置 deny-all 会话规则并
禁用工具,当前请求精确分配的只读能力例外。Execute 不把 Workspace 当作目录边界。
- `permission.asked` 不再忽略所有非父会话事件。Main 用 OpenCode Session 的 `parentID`
确认属于当前请求的子孙会话后按当前模式回复;其他并行会话不受影响,同一请求只回复
一次。子会话权限不伪装为父会话中的待完成工具。
- 真实 OpenCode `1.18.9` 和候选 `1.18.29` 均已通过本地确定性模型驱动的原生 Task
工作区外读取回归;同一 Runtime 的 Ask 仍不暴露文件与命令工具。
- Continue `1.5.47` 的真实 CLI 已通过 Execute Shell 写入工作区外测试文件;DSH 固定
Host 已通过 Execute 工作区外写入、随后 Ask 拒绝同一写工具,未增加权限策略或人工审批。
- 真实 OpenCode `1.18.29` 已完成原生 Task 后的 yes/no 与自由文本问答回传;父、子会话
提问及后台任务不能交互时的失败路径均有回归测试。Continue 真实 `AskQuestion` 暴露了
QuizService 扁平数据结构与原适配器不一致的问题,现已按锁定版本修正;真实自由文本
回传、yes/no、选项及跳过均验证通过,自定义答案不再误标为选项。
- DSH 活跃 Execute 的 ACP 权限回调不再等待第二次 authorizer,Ask 继续拒绝;未添加
通用业务问答工具或从终端日志猜测确认意图。完整范围见
[Runtime 交互边界](./runtime-interactions.md)。
- 当前源码 Linux x64 Agent 通过签名组包、生产安装、真实 Ask/Execute、原生子代理
工作区外写入、重连同一 daemon 和 stop/bootstrap。实机回归同时定位并修复了握手
Base64URL 响应误用 ID 校验及断流未结算等待;13 项 Unix 原生端点测试和连续 20 次
握手通过。两轮真实模型调用合计 10 次,均 completed;本轮测试 Agent 已停止。
- 最终完整回归使用 `npm test -- --reporter=json --outputFile=<临时报告路径>`,并通过
`GOODBUDDY_TEST_OPENCODE_BINARY` 指向已验证版本的候选 OpenCode。结果为 338 个文件、
3597 项通过、0 项失败、61 项按平台/手动条件跳过,报告 `success=true`、退出码 0;
Windows 跳过的 Unix 端点场景另在 Linux Host 运行了上述 13 项原生测试。
- 最终 `npm run typecheck`、`npm run lint`、`npm run release:notes:verify` 和差异空白检查
通过,相关 Markdown 相对链接有效。此前失败、暂停及控制台截断的回归记录由本次完整
JSON 报告覆盖,不代表仍有测试失败。未执行系统休眠/唤醒或本地桌面生产构建、打包;
后续发布仍需对应候选的 CI 构建和跨平台验证。
## 2026-09-08 OpenCode 复用与原生控件恢复
- `OpenCodeRuntime` 复用缓存的 embedded server 前执行有超时的健康检查;健康则保留
client,检查失败、抛错或超时则清除 client、会话映射、会话初始化及待答问题,关闭旧
server 后重建。并发调用共享检查与重建,调用方取消只结束自身等待。
- 请求级 MCP 注册与断开通过实例内 `mutateMcp` 队列串行执行。仅实际发出 `mcp.add`
的名称进入清理列表;注册尚在排队时取消不追加断开。断开的 1 秒超时从出队后开始。
- `App` 按本机/远程、连接方式、项目和 Runtime 选择组成 scope 缓存原生清单,用请求
代次阻止旧 scope 结果回写;同 scope 会话刷新不清空重取,切换会话重置消息级选择,
Runtime 切换状态也按 generation 隔离。清单请求失败或返回 `unavailable`/`partial`
时保留已有可用缓存并有界重试;没有可用缓存时保留新返回的部分数据,因此
`unavailable` 重试为可用 `partial` 后可显示 Agent 和 Command。
验证记录:首轮定向测试 273 项通过、5 项跳过;补充修复后的 Runtime 与生命周期测试
77 项通过,界面定向测试 6 项通过,类型检查通过。修复前的 lint 检查通过。
全量测试超过 120 秒被终止,期间出现一项 DS 本地 stdio MCP 测试失败,尚待定位。
这是当次历史结果。后续发布前修复与实机验证见本文上方及
[远程主机技术设计](../remote-host/technical-design.md);真实系统休眠恢复仍未完成验证。
## 2026-09-06 项目工作区
范围见 [PRD 工作区](./prd.md#76-工作区)。
当前源码已移除重复标题与底部完整 Diff 入口,使用彩色 Git 状态字母,点击更改项读取
单文件 Diff。已暂存与未暂存差异分开展示;本机 Git 测试覆盖无首次提交、未跟踪、
暂存后继续修改、重命名、删除及包含方括号的路径。Git 刷新驱动目录树重新读取,保留
展开状态;SSH 项目不提供本机系统打开按钮。审查后补齐了当前 Diff 随刷新重读、
旧请求结果失效,以及超过 50 项变更的继续加载入口。
验证记录:
- 最终 `npm run typecheck`、`npm run lint`、`npm run build` 均通过。
- 工作区专项 7 个测试文件复跑:317 项通过,3 项 Linux 专用测试在 Windows 跳过。
任务完成测试同时检查目录树出现新文件。
- 最终 `npm test`:326 个测试文件、3552 项测试通过,9 个文件、58 项测试按既有条件
跳过。复核首轮曾有一项浏览器地址草稿测试超时,单独复跑及最终全量复跑均通过;
更早的超时/失败记录不再作为最终验收结果。
- 后续复核已通过既有凭据和固定 Host Key 经 VPN 连接共享 Linux x64 Host,使用当前
源码构建的隔离 Agent 包安装并启动。真实 `RemoteWorkspaceAccess → Agent git/status`
与 `git/diff` 路径覆盖暂存后继续修改、删除、重命名、未跟踪和方括号路径;均通过。
Git 验证未调用模型。
- Windows 隔离桌面实例已通过真实点击验收:当前项目单文件暂存/工作树 Diff 分组与
增删颜色正确,修改测试文件后点击刷新可更新正在查看的 Diff,53 项变更经加载更多
全部可访问。截图留在本地验证目录,不纳入产品包。
目录分页、任意目录切换和 HTML 预览不在本轮改动范围内。
@@ -0,0 +1,25 @@
# Runtime 交互边界
本文定义当前原生交互的适配范围,不增加工作模式或第二层 Execute 审批。
| Runtime | 权限确认 | 业务问答 |
| --- | --- | --- |
| 本机 GoodBuddy 管理的 OpenCode | 内部默认配置允许权限;每个 Ask 请求仍使用 deny-all 与显式只读能力。Execute 自动回复属于当前请求的父、子孙会话权限事件,不处理其他并行请求 | 通过 `question.asked` 转交父、子孙会话问题,支持原生单选、多选、yes/no、自由文本及跳过;使用公开 ID 回传 `question.reply/reject` |
| 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 已提供相同业务问答接口。
## 运行与失败
- 结构化 yes/no 是普通选项,不从日志、自然语言或 Shell 输出猜测用户意图。
- 前台问题停留在现有问答卡片,保留回答、提交失败提示与跳过入口。
- 后台任务收到结构化提问时,通过现有 Main 路径明确失败并提示改为前台对话,不永久等待。
- 请求结束或取消后清除对应待答映射;过期回答明确报错,不转交给后来的请求。
- 任意终端程序的密码、OAuth 登录、许可证确认或 stdin 提示不自动回复 `yes`。这类程序应
使用其非交互参数,或由用户在终端完成;不能把权限确认和业务决策混为一谈。Shell
工具保留其现有取消与超时,不新增日志解析或通用交互代理。
实现与真实 Runtime 验证证据见[进度记录](./progress.md)。
@@ -0,0 +1,585 @@
# 助手工作栏多终端页签 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,跨平台验证中 |
| 版本 | 1.1 |
| 日期 | 2026-08-31 |
| 适用产品 | GoodBuddy 桌面端 |
| 界面归属 | [通用助手工作栏与执行空间](./prd.md) |
| 相关设计 | [统一界面设计系统](../../../UI-DESIGN.md) |
| 远程架构 | [SSH 远程主机与 GoodBuddy Agent 实现说明](../remote-host/technical-design.md)、[SSH Host 远程环境准备与控制面直连设计](../remote-host/environment-provisioning-technical-design.md) |
## 1. 文档职责
本文细化助手工作栏中的以下行为:
- 已打开应用以同级 Tab 呈现。
- Tab 行始终提供“+”入口,用户按需打开应用。
- 应用通过统一注册表声明单实例或多实例策略。
- “终端”作为首个多实例应用,自动使用当前项目的本机或托管 SSH 执行空间。
- 本机与远程交互式终端的生命周期、状态、性能和验收要求。
[通用助手工作栏与执行空间 PRD](./prd.md)
继续负责工作栏的应用级位置、稳定能力目录、执行空间、范围绑定和总体产品边界。本文不重新
定义 Host Key、凭据、Agent 安装、Ask/Execute Runtime 或项目持久化契约。
## 2. 背景与当前问题
当前工作栏在聊天页面右侧固定显示“任务中心、工作区、浏览器、成果”四个 Tab。用户不能:
- 通过统一入口按需打开新的工作栏应用。
- 关闭并重新打开一个工作栏应用。
- 为支持多实例的应用创建第二个独立实例。
- 在 GoodBuddy 内使用本机或远程 Host 的交互式 Shell。
当前代码已经具备工作栏右侧回流、宽度调整、Tab 键盘导航、SSH Host Key 固定、加密凭据和
SSH 连接池,但尚不存在工作栏应用实例模型、终端 IPC、终端 Session Manager 或终端模拟器。
`node-pty` 当前只作为其他 Runtime 的间接依赖被打包,不能视为已经存在用户终端能力。
## 3. 产品定义
### 3.1 工作栏应用
工作栏应用是可以在助手工作栏中打开的一个能力,例如任务中心、工作区、浏览器、成果或
终端。每项应用通过统一注册表声明:
- 稳定应用 ID。
- 用户可见名称、图标和说明。
- `single` 或 `multiple` 实例策略。
- 创建实例时可用的默认上下文。
- 当前不可用时的原因和修复入口。
目录中的应用可发现性与实例是否已经打开相互独立。应用不可用时保留入口并说明原因,不以
隐藏代替状态。
### 3.2 工作栏 Tab 实例
工作栏 Tab 是应用的一次打开实例。每个实例具有独立 ID、标题、应用类型和状态。Tab 行只
显示已经打开的实例,不把全部能力同时渲染为 Tab。
### 3.3 终端实例
每个终端 Tab 对应一个独立交互式 Shell Session:
- 本机终端由桌面 Main 进程管理本机 PTY。
- 远程终端通过现有、已验证的 SSH Host 打开直接 SSH PTY。
- 不同终端拥有独立 Shell、工作目录、输入、输出、尺寸和结束状态。
- 创建后固定到当时选择的执行位置,切换项目不会迁移已经运行的终端。
直接 SSH PTY 是用户操作的远程终端,不是连接期临时 GoodBuddy Agent,也不作为 Runtime
stdio fallback。首期远程终端随 SSH Channel 结束,不提供跨 SSH 或桌面重启的进程恢复。
## 4. 已确认的产品决策
1. 工作栏 Tab 行末尾始终显示“+”按钮。
2. “+”是普通命令按钮,不是一个 `tab`。
3. 点击“+”在工作栏内容区显示“新建工作栏应用”选择页;取消后返回原活动 Tab。
4. 选择应用后创建或激活实例,并关闭临时选择页。
5. 单实例应用已经打开时,再次选择只聚焦已有实例,不创建重复 Tab。
6. 多实例应用每次选择都创建新实例。
7. 终端从首期开始就是多实例应用。
8. 任务中心、工作区、浏览器和成果首期保持单实例;后续只需修改应用注册表策略即可开放
多实例,不重写 Tab 壳层。
9. 现有四个应用在升级后仍按当前默认布局打开,避免用户入口突然消失。
10. 用户关闭、切换或重排应用 Tab 后,“+”仍保持可见。
11. 选择“终端”后不再显示第二层目标选择器。存在当前项目时直接绑定该项目,由 Main
根据项目执行空间自动打开本机或托管 SSH Shell;没有当前项目时打开本机 Home Shell。
12. 远程首期使用直接 SSH PTY。SSH 断线后终端进入“已中断”,重新连接会创建一个新的
Shell,不伪装为恢复旧进程。
13. 工作栏收起、切换主页面或切换 Tab 不终止终端;显式关闭终端 Tab 才终止对应 Shell。
14. 应用重启不自动启动 Shell。恢复的终端 Tab 显示“会话已结束”,由用户显式重新连接。
15. 终端是用户交互表面。Ask/Execute 继续约束 Agent Runtime,不把用户键盘操作误判为
Agent 工具调用;Agent、自动化和模型不得未经明确用户操作向终端注入输入。
16. 工作栏继续按上位 PRD 提升为应用级容器;切换聊天、知识、魔法笔记或活动记录等主页面
不销毁 Tab 实例或终端 Session。
## 5. 目标
### 5.1 用户目标
- 在工作栏中随时通过“+”发现并打开应用。
- 同时打开多个本机或远程终端,并通过 Tab 快速切换。
- 清楚知道每个终端运行在本机还是哪个远程 Host、当前目录和连接状态。
- 在终端断线、退出或目标失效后获得准确状态和明确恢复操作。
- 使用键盘完成打开应用、切换 Tab、操作终端和关闭实例。
### 5.2 产品目标
- 用一个可扩展的应用注册表统一单实例与多实例行为。
- 把现有固定四 Tab 迁移为用户实例,而不回归现有任务中心、工作区、浏览器和成果能力。
- 建立真实的 Main/Preload/Renderer 终端生产链路,而不是用普通文本框模拟终端。
- 复用现有 SSH Host Key、凭据和连接池,不建立第二套 Host 管理。
- 保持终端输入输出有序、响应流畅、内存有界,并可靠清理本机和远程资源。
## 6. 非目标
- 不把 GoodBuddy 改造成完整 IDE。
- 不唤起 Windows Terminal、Terminal.app 等外部终端窗口代替内嵌终端。
- 不扫描或导入用户全部 SSH config、目录、Shell profile 或命令历史。
- 不支持任意 `ProxyCommand`、端口转发或 SSH Agent Forwarding。
- 不在打开远程终端时安装、更新或修复 GoodBuddy Agent。
- 不把 Agent 工具调用、Runtime 日志或自动化输出伪装成用户终端。
- 不允许 Renderer 直接访问 PTY、ChildProcess、SSH Client、凭据或 Node API。
- 首期不提供终端协作、录制、云同步、Shell Session 复用、tmux 管理或跨设备恢复。
- 首期不实现底部停靠和独立窗口;本文保留兼容模型,后续按工作栏总 PRD 扩展。
- 首期不通过 GoodBuddy Agent 恢复远程终端,也不为终端复制 Runtime transcript、
`outcome_unknown` 或模型调用恢复协议。
## 7. 信息架构与“+”交互
### 7.1 Tab 行
```text
┌──────────────────────────────────────────────┐
│ 任务中心 │ 工作区 │ 终端 1 │ 终端 2 │ + │
├──────────────────────────────────────────────┤
│ 当前活动实例内容 │
└──────────────────────────────────────────────┘
```
- 已打开实例位于一个语义化 `tablist` 中。
- 每个实例使用独立 `tab` 和 `tabpanel`。
- “+”固定在 Tab 行末尾,但位于 `tablist` 之外。
- Tab 过多时实例区域单行横向滚动,“+”保持可见;不换行、不自动隐藏实例。
- 终端 Tab 默认使用递增标题,例如“终端 1”“终端 2”;状态栏持续显示项目、Host、
工作目录和 Shell。用户可以重命名终端。
- 当前 Tab 同时使用连续内容表面、选中边框、顶部强调线、图标和较高字重,不能只靠文字颜色。
- Tab、关闭按钮和“+”入口沿用共享控件圆角;终端面板外框和终端顶部工具栏按钮使用直角,
保持紧凑的停靠式终端界面。
- Tab 关闭按钮默认不争夺视觉注意力,仅在鼠标悬停或键盘焦点进入对应 Tab 时出现;
粗指针或无悬停设备始终显示,出现与隐藏不得引发布局位移。
### 7.2 新建应用选择页
点击“+”后:
1. 保留当前实例及其运行状态。
2. 在工作栏内容区显示“新建工作栏应用”。
3. 列出稳定应用目录,每项显示名称、说明、单/多实例提示和当前可用状态。
4. 初始焦点进入第一个可用应用;Escape 或“取消”返回原活动实例和“+”按钮。
5. 选择单实例应用时,若实例已存在,直接聚焦该 Tab。
6. 选择终端时直接解析当前项目执行空间,创建新 Tab 并聚焦终端输入,不再要求用户
重复选择本机或远程目标。
选择页是“+”触发的临时界面,不进入实例顺序,不持久化,也不占用一个普通 Tab。
### 7.3 首期应用注册表
| 应用 | 实例策略 | 升级后默认状态 | 再次从“+”选择 |
| --- | --- | --- | --- |
| 任务中心 | `single` | 打开 | 聚焦已有 Tab |
| 工作区 | `single` | 打开 | 聚焦已有 Tab |
| 浏览器 | `single` | 打开 | 聚焦已有 Tab |
| 成果 | `single` | 打开 | 聚焦已有 Tab |
| 终端 | `multiple` | 不打开 | 每次创建新 Tab |
实例策略是应用注册数据,不能分散硬编码在多个点击处理器中。
### 7.4 Tab 操作
- 点击或使用方向键切换实例不会暂停或终止后台实例。
- 支持左右方向键、Home、End 和 roving `tabindex`。
- 关闭非活动 Tab 后焦点保持在当前活动 Tab。
- 关闭活动 Tab 后激活右侧相邻实例;没有右侧实例时激活左侧实例。
- 关闭最后一个实例后显示明确空状态和“打开应用”,Tab 行仍保留“+”。
- 关闭正在运行的终端需要确认“关闭终端将结束 Shell 及其子进程”;取消是初始焦点。
- 已退出或已中断的终端可以直接关闭,不重复确认。
## 8. 终端创建流程
### 8.1 自动目标解析
- 当前项目是本地项目时,创建目标为公开 Project ID,Main 从权威项目记录解析本机根目录。
- 当前项目是托管 SSH 项目时,创建目标仍为公开 Project ID,Main 再解析项目 Host、
固定 Host Key、凭据和远端根目录。
- 没有当前项目时使用本机目标和当前用户 Home。
- Host 未验证、凭据不可用或不支持交互式 Shell 时,终端 Tab 就地显示失败原因和恢复入口,
不静默切换到本机或其他 Host。
### 8.2 本机终端
- 当前项目是本地项目时,默认工作目录为项目根目录。
- 没有本地项目目标时,默认工作目录为当前用户 Home。
- Windows 优先使用可用的 PowerShell 7,其次 Windows PowerShell,最后使用系统
`COMSPEC`。
- macOS 和 Linux 优先使用当前用户有效的 `$SHELL`,不可用时使用平台常规 Shell fallback。
- 首期自动选择默认 Shell,不提供任意可执行文件路径输入。
- Main 只向 Shell 传递正常交互所需的筛选环境,不注入模型 API Key、GoodBuddy 内部
Token 或其他产品凭据。
### 8.3 远程终端
- 当前项目是托管 SSH 项目时,自动使用项目 Host 和远端项目根目录。
- Main 复用现有 Host store、完整 Host Key 固定、认证方式和 SSH connection pool。
- 远端通过 SSH PTY 打开账号的登录 Shell,并支持输入、ANSI 输出和窗口尺寸同步。
- 活动终端持有专用 Channel lease,不得被连接池空闲回收。
- 不把密码、私钥、认证头或 GoodBuddy 模型凭据发送给 Renderer 或写入终端环境。
- Host 地址、用户、凭据或 Host Key generation 变化时,旧 Session 进入“已中断”,新建
Session 必须使用 Host store 中的当前身份。
### 8.4 创建结果
创建成功后:
- 新 Tab 插入当前活动 Tab 右侧并立即激活。
- 焦点进入终端输入区域。
- 状态栏显示“本机”或 Host 名称、工作目录、Shell 和连接状态。
- 终端尺寸使用内容区计算出的首个有效 `cols/rows`,不是固定默认值覆盖实际布局。
创建失败时在终端 Tab 保留错误上下文并提供重试;不得留下不可回收 Session 或显示为已经连接。
## 9. 终端交互与状态
### 9.1 基础交互
首期支持:
- 真实 TTY 输入与 ANSI 输出。
- 中文输入法、组合字符和常规键盘快捷键。
- 复制、粘贴、全选、搜索、清屏和滚动。复制与粘贴通过桌面 Main 进程访问系统文本剪贴板,
不依赖 Renderer 的浏览器剪贴板权限。
- 工作栏宽度或窗口尺寸变化后的 PTY resize。
- 终端重命名。
- 显式关闭、退出后重新打开和远程中断后重新连接。
浏览器原生页面快捷键与终端按键冲突时,终端聚焦期间优先交给终端;应用级保留快捷键必须
有明确规则和可访问说明。
### 9.2 状态模型
```ts
type TerminalSessionState =
| 'starting'
| 'running'
| 'exited'
| 'interrupted'
| 'closing'
| 'failed'
```
- `starting`:正在创建本机 PTY 或 SSH Channel。
- `running`:允许输入和 resize。
- `exited`:Shell 正常或带退出码结束,显示退出码或信号。
- `interrupted`:远程连接、Host identity 或桌面生命周期导致会话失效。
- `closing`:已拒绝新输入,正在回收进程或 Channel。
- `failed`:Session 未成功开始或发生不可恢复错误。
高频输出不逐行进入 live region。只播报连接成功、连接失败、已中断和 Shell 已退出等重要
状态变化。
### 9.3 重新连接
- 本机或远程 Session 结束后,“重新打开”在同一 Tab 中创建全新的 Shell Session。
- 远程断线后的按钮文案为“重新连接并新建 Shell”,不能暗示恢复旧进程。
- 新 Session 使用 Host store 和项目中的当前有效配置,不复用旧 Host identity。
- 重新连接前清空旧终端画面,并保留可见说明“这是新的 Shell 会话”。
## 10. 实例、Session 与持久化
工作栏实例与运行 Session 分离:
```ts
type WorkbarAppDefinition = {
id: 'tasks' | 'workspace' | 'browser' | 'results' | 'terminal'
instancePolicy: 'single' | 'multiple'
}
type WorkbarTabInstance = {
id: string
appId: WorkbarAppDefinition['id']
title: string
targetRef?: {
type: 'project' | 'local'
id?: string
}
}
```
- 持久化工作栏打开状态、Tab 顺序、活动实例、应用 ID、自定义标题、公开目标引用和宽度比例。
- 不持久化终端正文、未执行输入、凭据、环境变量、PTY PID、SSH Channel 或派生 Host
identity。
- Main 创建终端后返回新的不透明 Session ID;Renderer 不能选择 PID 或 Channel。
- 应用重启恢复终端 Tab 描述,但状态为“会话已结束”,不自动建立 SSH 连接或启动本机
Shell。
- 运行期间切换项目不会改变已经创建的终端目标。用户需要新目标时通过“+”创建新终端。
## 11. 生命周期与关闭语义
| 用户或系统动作 | Tab | Shell Session |
| --- | --- | --- |
| 切换 Tab | 保留 | 继续运行 |
| 收起工作栏 | 保留 | 继续运行,停止非必要高频 UI 工作 |
| 切换主页面 | 保留 | 继续运行 |
| 切换项目 | 保留并显示原目标 | 继续在原目标运行 |
| 关闭运行中的终端 Tab 并确认 | 删除 | 结束 Shell 和子进程 |
| Shell 自行退出 | 保留 | 标记 `exited` |
| SSH 断线 | 保留 | 标记 `interrupted` |
| 删除或修改目标 Host | 保留 | 关闭旧 Channel 并标记 `interrupted` |
| Renderer 销毁或应用退出 | 下次恢复描述 | 在总关闭截止时间内回收 |
关闭操作必须安全处理竞态。Main 停止接受新输入,关闭 stdin/Channel,先正常结束,再在既有
应用关闭截止时间内升级清理;一个卡死终端不能无限阻塞应用退出。关闭后的迟到 ACK 视为已
完成,input、resize 或重复 close 返回 Session 不存在,不操作其他 Session。
## 12. 技术生产路径
### 12.1 Shared
新增独立终端契约,至少覆盖:
- `create`:目标引用、初始 `cols/rows`。
- `write`:Session ID 和有界输入。
- `resize`:Session ID、`cols/rows`。
- `close`:Session ID。
- `snapshot`:目标、状态、Shell、目录和终态。
- `event`:带 Session ID 和单调序号的输出、状态、退出与错误事件。
所有 Schema 使用 Zod 严格校验,并限制 ID、标题、输入、尺寸、事件块、会话数和缓冲字节。
### 12.2 Main
新增统一 `TerminalSessionManager`:
- Session 按所属 `webContents` 和不透明 ID 隔离。
- 将现有间接 `node-pty` 提升为 GoodBuddy 直接生产依赖,并用于启动平台 Shell。
- 远程使用现有 `SshConnectionPool` 打开交互式 PTY Channel。
- 统一处理 write、resize、close、exit、连接中断和应用退出。
- 终端输出只发送给所属窗口。
- 复用现有子进程树终止思路,但针对 PTY 验证 Windows 与 POSIX 的实际回收行为。
### 12.3 Preload 与 IPC
- `DesktopApi` 只暴露显式 `terminal.create/write/resize/close/getSnapshot/onEvent`。
- Main Handler 对每个调用执行可信 sender 检查和共享 Schema parse。
- Preload 管理事件订阅释放,不暴露 `ipcRenderer`、PTY、SSH Client 或 Node API。
### 12.4 Renderer
- 使用 `@xterm/xterm` 6 和官方 fit/search addon 处理 ANSI、光标、选择、IME、搜索、
滚动和可访问输入,不自行实现终端转义状态机。
- 终端首次打开时按需加载;支持 WebGL2 时使用官方 WebGL renderer,初始化失败或 Context
丢失时释放 WebGL addon 并回退到 xterm 默认 renderer。
- xterm 使用解析后的跨平台系统等宽字体栈,不能把未解析的 CSS `var(...)` 交给 Canvas。
- Tab 壳层只管理实例与焦点,不持有 PTY 或 SSH 对象。
- `ResizeObserver` 只在有效非零布局后发送去重后的 `cols/rows`。
- 组件卸载时退订画面事件;只有显式关闭终端才调用 Session close。
- 终端样式复用全局语义颜色、边框、焦点和字体令牌,不创建另一套工作台设计系统。
### 12.5 输出顺序与背压
- 输出按 Session 分区并携带单调序号。
- Main 合并小块输出,限制单事件大小、待发送条目和总字节。
- Renderer 确认已经消费的序号;慢消费者达到上限时暂停 PTY/SSH stream,消费后恢复。
- 不通过丢弃中间输出维持界面响应,因为丢失字节会破坏终端状态。
- Renderer 只保留有界滚动历史;清屏不改变 Main Session 生命周期。
## 13. 权限与边界
- 用户终端以当前本机账号或所选 SSH 账号的正常权限运行,不增加额外虚构的 trust tier。
- Ask/Execute 是 Agent Runtime 模式,不自动改变用户直接操作终端的 OS 权限。
- UI 必须持续将其标注为“用户终端”,避免被理解为 Ask Runtime 的只读输出。
- Agent、模型、监督器和自动化不能调用终端 write,也不能把指令注入已有 Session。
- 终端输出不会自动进入对话上下文、模型 Prompt、成果或活动记录。
- Renderer 只能引用项目和 Session 的公开 ID;Main 从权威 store 解析目录、Host、凭据和
当前 Host identity。
- 不接受 Renderer 提交任意 Shell 可执行路径、SSH 命令、PID、环境变量集合或原始凭据。
- 保持 Electron `contextIsolation`、sandbox 和禁用 Renderer Node integration。
## 14. 错误与反馈
- 创建失败、断线、Shell 退出和目标失效保留在终端面板,提供就地重试。
- 不与全局通知重复显示同一终端错误。
- 非局部错误,例如无法保存工作栏布局,可以进入应用通知。
- 错误文案说明发生了什么、当前 Shell 是否仍在运行、用户可以做什么。
- Host Key 变化沿用现有 Host 管理流程;终端只显示“主机身份已变化,请重新验证”,不在
终端内复制凭据或 Host Key 修改流程。
- 目标不可用时保留 Tab 名称和目标,不静默切换到本机或其他 Host。
## 15. 性能与资源要求
- 工作栏切换和终端输入在正常负载下保持即时反馈,持续输出不能阻塞主界面导航。
- 同一窗口的活动终端数量必须有明确上限;达到上限时阻止新建并说明先关闭不用的终端。
- 每个 Session 的输出队列、Renderer 滚动历史和 IPC 单事件均有硬上限。
- 工作栏不可见时可以降低渲染刷新频率,但不能丢失终端协议字节或暂停 Shell 本身。
- 应用退出复用现有总清理截止时间,不因终端新增无界等待。
- 生产构建必须实际加载本机 PTY 原生模块;不能只验证文件存在。
- 远程终端不能使 SSH connection pool 的空闲项、Channel 或监听器泄漏。
具体数值在共享契约和性能测试中确定,以现有 IPC、窗口和关闭预算为上限,不在多个进程中
维护不同常量。
## 16. 无障碍与响应式
- Tab 使用 `tablist/tab/tabpanel`;“+”使用带可访问名称“打开工作栏应用”的普通按钮。
- 应用选择页使用可键盘操作的列表或菜单语义,不使用 `SegmentedControl`;终端没有重复的
目标选择页。
- 每个终端 Tab 的可访问名称包含自定义名称或位置,多个终端不能都只读作“终端”。
- Tab 关闭后焦点移动到确定的相邻 Tab;取消创建后焦点返回“+”。
- 工作栏收起后焦点返回唯一工作栏开关,不增加焦点陷阱或遮罩。
- 终端高频输出不逐行播报;只播报连接、退出、失败和中断。
- 200% 文字缩放时,目标、Shell、目录、状态和关闭操作仍可访问。
- Tab 单行横向滚动,终端内容随工作栏尺寸变化并同步 PTY,不把 Tab 换成下拉框。
- 深色和浅色主题都使用设计系统语义令牌,并保持终端文本、选择和光标可辨。
## 17. 实施顺序与多代理工作包
功能必须以完整的本机和远程用户流程交付。以下是可并行的开发工作包,不表示可以发布只有
Schema、假数据终端或不可用入口的中间产品。
### 17.1 依赖顺序
```text
契约冻结
├─ 工作栏实例壳层
├─ 本机 PTY 后端
├─ SSH PTY 后端
└─ 终端 Renderer
↓
单一集成负责人完成 Main/Preload/App 接线
↓
完整验证、真实 Host 验收与文档同步
```
### 17.2 文件所有权
| 工作包 | 主要职责 | 建议文件所有权 | 不应修改 |
| --- | --- | --- | --- |
| 契约与注册表 | Workbar/Terminal Schema、状态、测试 | 新建 `src/shared/workbar-contracts.ts`、`terminal-contracts.ts` 及测试 | `App.tsx`、`ipc.ts`、Preload |
| 工作栏壳层 | 动态实例 Tab、“+”、选择页、单/多实例和焦点 | 新建工作栏组件及其测试 | PTY、SSH、Main IPC |
| 本机终端后端 | 本机 PTY、输入、resize、退出和进程树清理 | 新建 `src/main/terminal/` 本机文件及测试 | Renderer、远程 Agent |
| SSH 终端后端 | SSH PTY、lease、resize、断线和 Channel 清理 | 新建 `src/main/terminal/` 远程文件及聚焦测试 | 工作栏 UI、Agent 协议 |
| 终端 Renderer | 模拟器、IME、搜索、复制粘贴、状态栏和 resize | 新建终端组件、样式和测试 | Main、SSH、中心 IPC |
| 集成 | 依赖、IPC channel、DesktopApi、Preload、Main 注册、`App` 接线与关闭顺序 | `package.json`、lock、`ipc-channels.ts`、`contracts.ts`、`preload/index.ts`、`main/ipc.ts`、`App.tsx` | 不重写各包已验收内部逻辑 |
| 验收与文档 | E2E、跨平台构建检查、真实 Host、最终文档 | 测试与相关文档 | 不用假实现替换生产路径 |
中心文件只能由集成负责人修改,避免多个代理同时编辑 `App.tsx`、`main/ipc.ts`、
`shared/contracts.ts`、`preload/index.ts`、`package.json` 和 lockfile。
### 17.3 并行开发门槛
契约负责人先冻结:
- 应用注册和实例策略。
- Terminal request、snapshot、event 和 error code。
- Session 所有权和关闭语义。
- 输出序号、ACK 与容量常量。
其他代理基于冻结契约工作。接口偏差由集成负责人统一修正,不由多个代理分别修改共享中心
文件。
## 18. 验收标准
### 18.1 工作栏应用与实例
- [ ] Tab 行末始终显示“+”,包括没有任何打开实例时。
- [ ] 点击“+”显示稳定应用目录,取消后返回原实例并恢复焦点。
- [ ] 选择已打开单实例应用只聚焦已有 Tab。
- [x] 连续选择终端会创建多个互相独立的终端 Tab。
- [ ] 关闭、切换和重排实例后,“+”仍保持可用。
- [ ] Tab 溢出时单行横向滚动,不隐藏“+”或把 Tab 换成下拉框。
- [x] 关闭活动 Tab 后激活正确的相邻 Tab 并恢复焦点。
- [x] 当前 Tab 的背景、边框、顶部指示和字重清晰可辨;关闭按钮仅在悬停、焦点或无悬停
设备上出现,且不引发布局位移。
- [ ] 升级后现有任务中心、工作区、浏览器和成果仍按默认布局打开且行为不回归。
- [ ] 在所有主要页面都能打开工作栏;切换主要页面不会销毁 Tab 实例或终端 Session。
### 18.2 本机终端
- [x] 从“+ → 终端”直接打开当前本机项目的真实交互式 Shell,工作目录为项目根。
- [ ] 没有项目时从用户 Home 打开本机 Shell。
- [ ] `pwd` / `Get-Location`、交互输入、ANSI 颜色、中文 IME、复制和粘贴工作正常。
- [ ] 调整工作栏和窗口尺寸会更新 PTY `cols/rows`,全屏 TTY 程序正确重绘。
- [ ] 多个本机终端的输入、输出、目录和退出状态互不串线。
- [ ] Shell 有活动子进程时关闭 Tab,确认后回收完整进程树;取消后 Session 继续运行。
- [ ] 退出后的 Tab 显示退出码,并可在同一 Tab 显式创建新 Shell。
### 18.3 远程终端
- [ ] 从“+ → 终端”直接为当前托管 SSH 项目打开真实 SSH PTY。
- [ ] 终端持续显示 Host、远端目录、Shell 和连接状态。
- [ ] 使用现有固定 Host Key 和已存凭据,Renderer、日志和命令参数不出现凭据。
- [ ] 输入、ANSI、中文、复制粘贴和 resize 在真实 Host 上工作正常。
- [ ] 活动远程终端不会被 SSH connection pool 空闲回收。
- [ ] SSH 断线立即标记“已中断”并拒绝继续输入,不显示为仍在运行。
- [ ] “重新连接并新建 Shell”明确创建新 Session,不宣称恢复旧远端进程。
- [ ] Host 删除、地址/用户/凭据或 Host Key generation 变化会中断旧 Session;新 Session
使用当前 Host identity。
### 18.4 生命周期、性能和边界
- [ ] 收起工作栏、切换主页面、切换 Tab 或切换项目不会终止已有终端。
- [ ] 终端不会随项目切换静默迁移执行位置。
- [ ] 应用重启只恢复终端 Tab 描述,不自动连接或启动 Shell。
- [ ] 输出顺序在持续大输出下保持正确,Main 和 Renderer 内存保持有界,主界面仍可操作。
- [ ] 慢 Renderer 触发暂停和恢复,不丢弃中间终端字节。
- [ ] Renderer 不获得 raw PTY、ChildProcess、SSH Client、PID、环境变量或凭据。
- [ ] Agent、模型和自动化不能向已有用户终端注入输入。
- [ ] 窗口销毁和应用退出在既有关闭截止时间内回收本机进程、SSH Channel 和监听器。
- [ ] 重复 close、迟到 input/resize 和退出竞态不会操作其他 Session 或导致进程泄漏。
### 18.5 设计、无障碍和跨平台
- [ ] 仅使用键盘可以打开应用、创建终端、切换和关闭 Tab、复制、粘贴及重新连接。
- [ ] Tab、“+”、选择页和关闭确认使用正确语义并有可见焦点。
- [ ] 终端输出不逐行进入 live region,关键状态变化可被辅助技术感知。
- [ ] 浅色、深色、200% 文字缩放和窄窗口下可以完成核心流程。
- [ ] Windows、macOS、Linux x64 和 Linux arm64 的正式构建实际启动本机 PTY。
- [ ] 远程链路在共享 Linux x64 真实 Host 上完成创建、输入、resize、断线、重连、关闭和
应用退出验证;若 Host 不可达,远程终端不得标记为完成。
## 19. 验证计划
### 19.1 自动验证
- 共享 Zod Schema 和边界值测试。
- 单/多实例注册策略、Tab 顺序、关闭焦点和“+”选择页组件测试。
- 本机 PTY Session Manager 的输入、resize、退出、幂等关闭和进程树测试。
- SSH PTY 的 Host identity、lease、断线、resize 和关闭测试。
- Main trusted sender、Session 所有权、IPC 输入边界和事件路由测试。
- Terminal Renderer 的 IME、键盘、ResizeObserver、订阅释放和状态测试。
- `npm test`
- `npm run typecheck`
- `npm run lint`
- `npm run build`
### 19.2 真实路径验证
- 在当前开发平台通过产品 UI 完成本机终端全流程。
- 在共享 Linux x64 Host 通过现有 Host 配置完成远程终端全流程。
- 真实运行需要 TTY 的程序,验证 ANSI、光标和 resize,不只运行非交互命令。
- 持续输出并主动减慢 Renderer 消费,验证背压和内存上限。
- 分别在本机和远程启动带子进程的命令,关闭 Tab 和应用后核对无 GoodBuddy 所属遗留资源。
- 生产构建在各受支持平台实际加载 PTY 原生模块,不以单元测试或包内文件存在代替。
### 19.3 当前开发验证记录
2026-09-01 已通过生产 `SshConnectionPool` 和 `createSshTerminalSession` 在共享 Linux x64
Host 完成直接 SSH PTY 验证:使用已有固定 Host Key 和测试凭据,以远端项目目录启动登录
Shell,验证交互输入、ANSI、UTF-8 中文、`80×24` 到 `101×37` resize、连接销毁后进入
`interrupted`、重新连接创建新 Shell,以及 Session close 和 connection pool dispose
回收远端 Shell。验证只创建一个
`$HOME/.goodbuddy/terminal-smoke-<UUID>` 临时目录,结束后已删除并确认无遗留 Shell 进程。
同日使用最新生产构建完成工作栏视觉检查:活动 Tab 使用连续内容表面、顶部强调线和应用
图标;关闭按钮默认不可见,悬停后以 overlay 显示且 Tab 宽度不变;横向滚动继续可用但不
显示滚动条,“+”保持独立可见。
本记录不替代尚未勾选的完整产品 UI、剪贴板、macOS、Linux arm64 和各平台本机 PTY 验收。
本 PRD 不改变 Agent 协议,因此首期远程终端无需 Agent package 更新。若实现期间改为
Agent-owned PTY,必须先修订本文的远程生命周期和恢复语义,并按 Agent 开发规则在共享
Linux x64 Host 验证当前源码或候选包。
## 20. 相关文档职责
- [通用助手工作栏与执行空间 PRD](./prd.md):
工作栏应用级位置、稳定能力目录、执行空间和总体范围。
- 本文:持久“+”、应用实例策略、工作栏动态 Tab、自动项目目标、本机与直接 SSH 多终端。
- [统一界面设计系统](../../../UI-DESIGN.md):Tab、菜单、布局、主题、焦点和无障碍规则。
- [SSH 远程主机与 GoodBuddy Agent 实现说明](../remote-host/technical-design.md):
Host Key、凭据、连接池、Agent 和远程项目现状。
- [SSH Host 远程环境准备与控制面直连设计](../remote-host/environment-provisioning-technical-design.md):
Host 保存、环境准备和项目使用之间的职责。
+11
View File
@@ -0,0 +1,11 @@
# 电脑控制
电脑控制包含 GoodBuddy 对浏览器和客户端桌面能力的产品实现与平台适配状态。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [功能进度](./progress.md) | 当前已实现能力、缺陷修复、平台限制和验证状态 |
当前只有进度文档;产品需求、功能逻辑、UI 和技术设计尚未从历史实现记录中独立整理。
@@ -6,8 +6,8 @@
| --- | --- |
| 文档类型 | 实施进度 |
| 状态 | 持续更新 |
| 版本 | 0.3 |
| 日期 | 2026-08-18 |
| 版本 | 0.4 |
| 日期 | 2026-09-03 |
| 适用能力 | 内置浏览器与客户端电脑控制 |
## 范围
@@ -17,13 +17,14 @@ Linux x64 和 Linux arm64。
## 已完成
### 直连模型与浏览器
### Runtime 与浏览器
- 已实现宿主进程拥有的隔离浏览器服务。
- 使用 Electron 自带 Chromium 和 CDP,不下载额外浏览器。
- 已实现导航、可访问性快照、点击、输入、选择、返回和有界截图工具。
- 浏览器工具只在直连模型的 Execute 模式中提供;用户选择 Execute 即授权
本次交互运行,不再逐个弹出 GoodBuddy 工具审批。
- 浏览器工具作为 GoodBuddy 内置能力,可分别分配给直连模型、GoodBuddy 管理的
OpenCode 和 Continue;只在 Execute 模式中提供。用户选择 Execute 即授权本次
运行,不再逐个弹出 GoodBuddy 工具审批。
- 支持当前设备可连接的 HTTP、HTTPS、公网、内网、本机和元数据地址,
导航及重定向仍经过 DNS 解析和目标地址固定。
- 已实现回环过滤代理、下载和文件选择器阻止、权限拒绝、会话取消、
@@ -59,11 +60,14 @@ Linux x64 和 Linux arm64。
### 设置与安全边界
- 能力存储已迁移到版本 2。
- 内置浏览器已归入“直连模型”工具,与联网搜索并列,并明确说明不会控制
客户端已安装的浏览器;原有开关和配置 ID 保持不变,用户状态无需迁移。
- 用户通过内置浏览器总开关决定是否向直连模型提供工具;开启后可在 Execute
模式直接使用,不逐次询问。诊断位于同一分类。
- 能力存储已迁移到版本 6。版本 2 至 5 中原有的浏览器启停状态会迁移到
`builtin-browser`,已有启用状态不丢失;默认分配给直连模型、GoodBuddy 管理的
OpenCode 和 Continue。
- 内置浏览器位于“内置 MCP”,与知识库、笔记和 GoodBuddy 配置一样提供独立启停、
Runtime 分配和工具清单;DeepSeek Harness 持续显示为不支持。
- 浏览器开关只控制 Agent 工具访问。关闭后,任何 Runtime 都不会获得浏览器工具,
但用户仍可在浏览器工作栏中手动前往、返回、刷新、停止加载、交互和关闭浏览器。
开启后,已分配 Runtime 可在 Execute 模式直接使用,不逐次询问。
- 尚未实际参与浏览器执行的命名配置已从界面隐藏;底层旧数据和兼容接口继续
保留,不删除用户已有记录。
- “电脑控制”只显示实际操作客户端电脑的能力。能力启用、配置变更和本地
@@ -93,11 +97,27 @@ Linux x64 和 Linux arm64。
新增“浏览器”页签;活动对话启动浏览器时会自动打开该页签,并显示创建中、
加载中、操作中、用户交互中、就绪、失败和已停止状态。用户可点击“交互”打开
同一会话的子窗口辅助 Agent;交互时主窗口暂时禁用,关闭时先刷新最终画面再将
浏览器窗口最小化,页面和会话继续保留。用户也可立即停止当前对话的浏览器会话。
浏览器窗口最小化,页面和会话继续保留。用户也可明确关闭当前对话的浏览器会话。
当前实现按导航、快照、点击、输入、选择、返回和截图操作刷新画面,而不是创建
第二个预览浏览器,因此显示内容与模型受控页面一致。
### 已实现:浏览器工具栏
浏览器 Tab 顶部已增加“返回、刷新/停止加载、地址输入、前往、交互、关闭浏览器”工具栏。
它继续操作 Agent 在当前 Conversation 中使用的同一浏览器会话;用户导航会有意改变 Agent
下一步看到的页面。前往、返回和刷新通过 `BrowserService` 与 Agent 浏览器操作串行执行,
不会创建第二套 Renderer 导航路径。
工具栏状态以浏览器提交后的权威 URL、是否加载中、能否返回和会话活动状态为准;地址输入
在用户编辑期间保留本地草稿,不被状态刷新覆盖;提交未带 HTTP(S) 协议的裸域名时会自动添加
`http://`。创建中、Agent 操作中和完整交互窗口期间禁用其余工具栏控件,但活动导航仍可用
“停止加载”中断。停止加载只取消页面加载,不关闭浏览器,也不释放 Runtime 上下文;原“停止
浏览器”会话生命周期操作已统一标为“关闭浏览器”,并在视觉和文案上与停止加载清楚区分。
活动 Conversation 尚无浏览器会话时,可直接从地址栏输入 URL 并“前往”,通过浏览器组件
可用性检查和同一 URL 策略创建会话。该手动路径不读取 Agent 浏览器开关。实现复用现有每
Conversation 串行队列和有界状态,不引入恢复、版本化或第二套会话框架。
### P0:百度输入时元素引用失效,已修复
截图中的“浏览器元素引用已失效,请重新获取快照”来自过宽的导航监听:广告或
@@ -145,16 +165,26 @@ Linux x64 和 Linux arm64。
## 下一步顺序
1. 在便携版真实模型流程中继续验证返回、取消和右侧画面刷新。
1. 在便携版真实模型流程中继续验证 OpenCode、Continue 的导航、返回、取消和右侧画面刷新。
2. 再设计命名隔离 Profile 的持久登录态、引用和清理策略。
3. 分别实现并真机验证 Windows UIA、macOS AX 和 Linux 原生适配器。
## 最近验证
- `npm test`:591 项通过,6 项跳过。
- `npm test`:3496 项通过,53 项跳过。
- `npm run typecheck`:通过。
- `npm run lint`:通过。
- `npm run build`:通过。
- `GOODBUDDY_RUN_RUNTIME_E2E=1 ... -t "calls the assigned built-in browser
through bundled OpenCode"`:通过,真实模型上报 2 次模型调用,OpenCode 经请求级
GoodBuddy MCP 调用 `browser_navigate` 并保留正确 Conversation 归属。
- `GOODBUDDY_RUN_RUNTIME_E2E=1 ... -t "calls the assigned built-in browser
through bundled Continue"`:通过,Continue 经同一路径调用 `browser_navigate` 并
返回预期结果;当前 Continue Host 为该完整工具轮次上报 1 条聚合模型用量事件。
- 聚焦的能力迁移、网关、IPC、设置界面测试共 234 项通过。其中覆盖 Agent 浏览器
开关关闭时用户手动浏览器操作仍可用。
以上自动化结果覆盖协议、安全边界和模拟驱动,不替代真实 Electron
BrowserWindow/WebContentsView、目标网站和目标 Linux 桌面环境的集成验证。
本轮曾尝试用隔离用户目录启动第二个 Electron 实例,但当前已运行的正式 GoodBuddy
单实例锁阻止了该实例继续运行;未关闭或附着用户正在使用的正式实例。
@@ -0,0 +1,11 @@
# 持续学习
持续学习定义 GoodBuddy 如何从运行结果、评估证据和人工反馈中形成可验证的改进闭环。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [持续学习与评估门 PRD](./prd.md) | 产品目标、学习边界、评估门和验收要求 |
当前尚无独立 User Stories、功能逻辑、UI、技术设计和进度文档。
@@ -7,12 +7,12 @@
| 状态 | 设计中,远期能力 |
| 版本 | 0.3 |
| 日期 | 2026-08-19 |
| 依赖 | [自动化平台总体设计](../../architecture/automation-platform-architecture.md)、[并行实验 PRD](../experiments/parallel-experiments-prd.md)、[分区记忆 PRD](../memory/partitioned-memory-prd.md) |
| 依赖 | [自动化平台总体设计](../../architecture/automation-platform-architecture.md)、[并行实验 PRD](../parallel-experiments/prd.md)、[分区记忆 PRD](../memory/prd.md) |
## 1. 背景
智能心跳可以生成摘要、后续任务和记忆候选,但这还不是完整学习。其长期“未来分区记忆”
方向尚未设计,也不承担持续学习、模式挖掘或自动改进:
智能心跳可以生成摘要、后续任务和记忆候选,但这还不是完整学习。它与分区记忆的协同尚未
实施,也不承担持续学习、模式挖掘或自动改进:
- 候选是否改善未来行为没有评估。
- 一条反思是否会被检索和使用并不确定。
@@ -0,0 +1,29 @@
# 会话 HTML 渲染
会话 HTML 渲染让用户直接查看 Agent 回复中的网页结果,而不必先复制代码到外部文件。
该能力默认开启,也可以在“设置 > 平台功能 > 通用设置”中关闭。
## 功能边界
- 只渲染已完成的 Agent 正文和已完成的子 Agent 输出。
- 支持完整 HTML 文档、单一 HTML 根元素,以及 Markdown 中的 `html` / `htm` 代码块。
- 使用无脚本、无网络、无表单提交和无窗口操作权限的静态隔离预览。
- 每个预览提供源码切换和全屏预览两个图标操作;全屏预览沿用同一清理和隔离边界。
- 用户消息、推理过程和流式中的回复不执行 HTML;关闭功能后继续显示 HTML 源码。
- 本功能不把普通聊天回复复制为成果,也不替代助手工作栏中独立成果的预览流程。
## 文档
- [产品需求](./prd.md):功能范围、状态规则和验收标准。
- [技术设计](./technical-design.md):设置持久化、渲染边界与静态隔离方案。
- [实现进度](./progress.md):已验证实现和验证记录。
## 术语
- **HTML 源码**:Agent 回复中原始、可复制的 HTML 文本。
- **静态预览**:在隔离 `iframe` 中显示的 HTML;脚本、联网和外部操作均被禁用。
- **全屏预览**:在应用级 Modal 中显示同一份静态预览文档,用于查看完整页面布局。
全局聊天和设置界面规则由根目录的
[统一界面设计系统](../../../UI-DESIGN.md)定义。独立成果预览仍由
[助手工作栏](../assistant-workbar/README.md)负责。
@@ -0,0 +1,69 @@
# 会话 HTML 渲染产品需求
## 用户问题
Agent 可以生成网页、报告和交互原型的 HTML,但此前会话只能显示源码或忽略原始标签。
用户必须把内容复制到外部文件后才能查看结果,打断了会话内的阅读和迭代流程。
## 目标
- 让 Agent 生成的 HTML 在回复完成后直接显示为网页预览。
- 保留一键查看原始 HTML 的能力。
- 允许用户通过平台功能开关恢复为只看源码。
- 预览不取得 GoodBuddy Renderer 或外部网络能力。
## 非目标
- 不执行 Agent 提供的 JavaScript。
- 不提供联网网页、表单、弹窗或 Electron `webview`。
- 不在流式输出中反复加载不完整页面。
- 不自动把普通会话回复保存为成果。
## 功能需求
### FR-1 默认设置
“会话 HTML 渲染”是应用级持久设置,默认开启。用户可在“设置 > 平台功能 >
通用设置”中立即关闭或重新开启;保存失败时保留最后确认的状态并显示错误。
### FR-2 可识别输出
开启后,已完成 Agent 回复中的以下内容显示为静态预览:
1. 完整 HTML 文档。
2. 由单一 HTML 根元素构成的完整回复。
3. Markdown 中标记为 `html` 或 `htm` 的代码块。
普通 Markdown 继续按原有规则显示。一个回复中的多个 HTML 代码块各自保持原有位置。
### FR-3 回复状态和角色
- 已完成的主 Agent 正文和已完成的子 Agent 输出可以预览 HTML。
- 用户消息和 Agent 推理只显示文本或源码。
- 流式 Agent 正文在完成前显示源码,完成后原位切换为预览。
### FR-4 源码、全屏与关闭行为
每个预览持续显示“静态安全预览,脚本和网络已禁用”,并在标题右侧提供两个图标操作:
1. 源码按钮,就地展开或收起原始 HTML。
2. 全屏按钮,在应用级 Modal 中查看同一份预览文档。
嵌入预览使用固定高度并在框内滚动,需要更大画面时使用全屏预览。全屏预览可通过关闭
按钮、Escape 或点击遮罩退出,退出后焦点回到全屏按钮。关闭平台功能后,完整 HTML 回复
和 HTML 代码块继续显示源码,不能变成空白。
### FR-5 静态隔离
预览必须禁用脚本、网络请求、表单提交、嵌套页面、插件对象和窗口操作。远程链接和远程
媒体不得从预览加载;内联样式和有界的内联位图可用于还原静态视觉。
## 产品验收
- 新安装和从旧设置升级后,开关均为开启。
- 完整 HTML 和 fenced HTML 在已完成的 Agent 回复中显示预览。
- 关闭开关后,当前会话立即恢复显示源码,重启后选择仍保留。
- 流式回复、推理和用户消息从不执行 HTML。
- 预览可通过键盘访问源码和全屏按钮,并具有明确的 iframe 标题。
- 全屏预览隔离背景、初始聚焦关闭按钮,Escape 退出后焦点回到全屏按钮。
- 恶意脚本、事件属性、远程资源、表单和嵌套 frame 不进入可执行预览。
@@ -0,0 +1,39 @@
# 会话 HTML 渲染实现进度
## 已实现
- [x] 应用级持久开关,默认开启,并提供旧设置迁移。
- [x] 平台功能通用设置中的共享 Switch。
- [x] 已完成 Agent 回复和子 Agent 输出中的完整 HTML 与 fenced HTML 识别。
- [x] 隔离静态预览、持续安全说明和源码切换。
- [x] 预览保留作者 `<style>` 与 `style` 属性,样式不再被整块丢弃。
- [x] 与 Mermaid 一致的图标按钮组:源码切换与全屏预览 Modal。
- [x] 嵌入预览固定 480px 高度,源码面板同值上限。
- [x] 用户消息、推理、流式回复和关闭状态的源码边界。
- [x] 设置变更后当前会话立即重新渲染。
## 验证记录
2026-09-05:
- `npm run typecheck`:通过。
- 聚焦测试(应用设置、Markdown、会话时间线、设置中心、App 集成):306 项通过。
- 全量 `npm test`:`3493 passed, 55 skipped`。
- `npm run lint`:通过。
- `npm run build`:Main、Preload、Renderer 与控制面安装器均成功构建。
修复样式丢失后复验:
- 原实现只要 `<style>` 或 `style` 属性中出现 `@import` / `url()` 就整块删除,带渐变或
背景图的回复因此完全失去样式;现改为保留作者样式,外部资源加载仍由 iframe CSP 拒绝。
- `npx vitest run src/renderer/src/MarkdownRenderer.test.tsx`:`19 passed`。
- 聚焦测试(会话时间线、App 集成、设置中心):`260 passed`。
- `npm run typecheck`、`npm run lint`:通过。
新增全屏预览与图标化源码按钮后复验:
- 源码按钮改为代码图标并暴露 `aria-expanded`;新增放大图标打开全屏 Modal。
- 嵌入预览高度由 `min-height: 320px` 改为固定 `height: 480px`,源码面板上限同步为 480px。
- 新增回归测试覆盖全屏 Modal 的初始焦点、背景 inert、Sandbox/CSP 与 Escape 焦点恢复。
- 聚焦测试(Markdown、会话时间线、App 集成、设置中心):`280 passed`。
- `npm run typecheck`、`npm run lint`:通过。
@@ -0,0 +1,68 @@
# 会话 HTML 渲染技术设计
## 进程和设置边界
`conversationHtmlRenderingEnabled` 属于现有 `ApplicationSettings`。Main 继续通过
版本化 JSON 文件持久化应用设置,IPC 继续复用已验证的
`settings:application:get/update` 通道,Preload 只暴露类型化设置方法。
应用设置格式升级到版本 11。版本 10 及更早设置在读取时补入默认值 `true`,无需保留第二份
状态或独立迁移文件。
## Renderer 数据流
1. `App` 启动时读取应用设置,并把当前值传给每个保活的会话视图。
2. 平台功能 Switch 保存成功后立即更新 `App` 状态。
3. `ChatTimeline` 只为 `role === "assistant"` 且 `state === "complete"` 的正文传入
HTML 渲染权限;推理始终不传入该权限。
4. `MarkdownRenderer` 识别完整 HTML 回复以及 `html` / `htm` fenced code。
5. 关闭权限或回复仍在流式阶段时,HTML 保持代码文本。
## 静态预览
`StaticHtmlPreview` 不使用 `dangerouslySetInnerHTML`。它先用 DOMPurify 清理文档,再通过
`DOMParser` 移除事件属性、非锚点 `href` 和非内联图片的 `src` / `poster`,最后把结果写入
`<iframe sandbox="" srcDoc>`。
作者样式必须保留:`<style>` 元素和 `style` 属性原样传入预览,不按 `@import` 或 `url()`
关键字整块删除,否则任何带渐变或背景图的回复都会退化成无样式文档。外部样式表和远程
字体、图片的实际加载由 iframe 文档的 CSP 拒绝,不需要在清理阶段重复拦截。
iframe 文档注入严格 CSP:
- `default-src 'none'`
- `script-src 'none'`
- `connect-src 'none'`
- `frame-src 'none'`
- `object-src 'none'`
- `form-action 'none'`
- 只允许内联样式以及 `data:` 字体和受支持的内联位图
同时设置 `referrerPolicy="no-referrer"`。Sandbox 不授予 scripts、forms、popups、
same-origin 或 navigation 权限。预览只在回复完成后创建,并使用 lazy loading。
## UI 与无障碍
预览沿用聊天阅读宽度、语义颜色和共享 `icon-button`。`figure` / `figcaption` 表达预览及其
说明,iframe 具有本地化标题。标题右侧是一个 `role="group"` 图标按钮组,与 Mermaid 图表
操作保持一致:源码按钮暴露 `aria-expanded` 与 `aria-controls`,全屏按钮打开 Modal。
嵌入预览高度固定为 480px,超出部分在框内滚动;查看源码面板使用同一数值作为最大高度。
iframe 无法按内容自适应高度,因为 `sandbox=""` 不含 `allow-same-origin` 或 `allow-scripts`,
主文档既读不到内部文档尺寸,也不能接收上报;全屏预览覆盖需要更多空间的场景。
全屏预览复用 `dialog-focus` 的 `activateModalFocus` 与 `trapTabFocus`,遵循统一 Modal 的
背景 inert、初始焦点落在关闭按钮、Tab 循环、Escape 关闭和关闭后焦点恢复。它渲染的是与
嵌入预览完全相同的清理后文档,不额外放宽 Sandbox 或 CSP。切换回复时,源码面板与全屏
预览按当前 source 派生开合状态,不使用会引发级联渲染的重置 effect。
## 验证范围
- 应用设置默认值、关闭持久化和版本 10 迁移。
- fenced HTML、完整 HTML、关闭后的源码回退。
- 作者 `<style>` 与 `style` 属性在预览中保留。
- 脚本、远程资源、表单和事件属性清理,以及 iframe Sandbox/CSP。
- 源码按钮的 `aria-expanded` 切换,以及全屏预览的初始焦点、背景 inert、Escape 关闭和
焦点恢复。
- 主 Agent、子 Agent、用户、推理和流式状态边界。
- 从设置 Switch 到当前会话重新渲染的应用级集成路径。
@@ -0,0 +1,11 @@
# 会话监督
会话监督定义对长会话、工具执行和异常状态的观察、判断与介入边界。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [会话监督 PRD](./prd.md) | 监督目标、证据、介入方式和产品验收 |
当前尚无独立 User Stories、功能逻辑、UI、技术设计和进度文档。
@@ -8,7 +8,7 @@
| 版本 | 0.2 |
| 日期 | 2026-08-18 |
| 依赖 | [自动化平台总体设计](../../architecture/automation-platform-architecture.md) |
| 界面归属 | [通用助手工作栏与执行空间](../assistant-experience/assistant-workbar-and-execution-spaces-prd.md) |
| 界面归属 | [通用助手工作栏与执行空间](../assistant-workbar/prd.md) |
| 体验参考 | GoodBuddy 魔法笔记 AI 评论流 |
## 1. 背景
+12
View File
@@ -0,0 +1,12 @@
# DeepSeek Harness Runtime
本功能定义 DeepSeek Harness 作为 GoodBuddy 本机 Agent Runtime 的产品接入、控制面、
插件市场、权限和打包边界。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [技术设计](./technical-design.md) | Runtime 产品与技术方案、协议、插件、打包、测试和验收 |
现有文档同时包含部分 PRD、功能逻辑和进度内容,后续修改该 Runtime 时再按标准职责拆分。
@@ -7,7 +7,7 @@
| 文档状态 | 实现与发布验收基线 |
| 设计目标 | 将 DeepSeek Harness 作为 GoodBuddy 的第三个 Agent Runtime |
| Runtime 标识 | `deepseek-harness` |
| 当前依赖基线 | 实际使用的 `@deepseek-ai/dsh-*` 底层库,精确锁定 `0.1.0-rc.8` |
| 当前依赖基线 | 实际使用的 `@deepseek-ai/dsh-*` 底层库,精确锁定 `0.1.2-rc.1` |
| 上游状态 | Developer Preview,允许出现破坏性变更 |
| 上游许可证 | MIT |
| GoodBuddy 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
@@ -139,6 +139,8 @@ Harness 子进程内控制面不能取代 Main 控制面,Main 控制面也不
- GoodBuddy 在 Runtime 重启后可以用现有的有界历史创建新 Harness Session。
Harness Session 只在当前 Runtime 进程生命周期内存在。释放 GoodBuddy 会话时必须同步释放对应 Harness Agent。
固定 Host 显式加载 `dsh-session-projection` 的内存 SessionProjectionRegistry,这是
`0.1.2-rc.1` AgentLoop 的必需依赖;不加载 Session persistence,也不新增磁盘会话副本。
## 6. 总体架构
@@ -163,7 +165,7 @@ Electron utilityProcess
├─ 本地 Shell / Filesystem Provider
├─ 最小工具集与 Main 代理 MCP
└─ Main 明确启用的第三方 Cordis 插件
│ HTTPS
│ HTTP/HTTPS
▼
用户选择的 OpenAI 兼容模型连接
```
@@ -254,7 +256,9 @@ Store 是否已经提交为准,确定性完成新目录或恢复旧目录,
| `goodbuddy/native/snapshot` | Main → Control Plane | 从无 Agent scope 的 Host Registry 读取有界的原生 Tool/Skill 元数据,排除 GoodBuddy 分配项与请求级代理 |
| `goodbuddy/shutdown` | Main → Control Plane | 停止接收新请求并有序清理 |
Utility 启动控制协议使用版本 2,严格携带 `supportsImageInput` 与固定 8 MiB 帧上限;版本 1 或缺少该字段的启动消息失败关闭,不能让 Host 自行猜测模型能力。扩展版本独立于 ACP 版本。握手响应至少包含:
Utility 启动控制协议使用版本 3,严格携带 `supportsImageInput`、模型连接的有界自定义
Header 与固定 8 MiB 帧上限;旧版本或缺少必需字段的启动消息失败关闭,不能让 Host
自行猜测模型能力。扩展版本独立于 ACP 版本。握手响应至少包含:
```ts
type GoodBuddyHarnessCapabilities = {
@@ -288,6 +292,10 @@ GoodBuddy 的工作模式属于每个请求,不属于 Runtime 进程全局状
5. 缺少准备状态、重复使用、请求标识不匹配时,Control Plane 直接拒绝请求。
6. 同一 Session 只允许一个 Prompt 在途。
若 Runtime 回传 ACP 权限请求,Main 只按活跃 Session 的当前模式答复:Execute 优先选择
`allow_once`,缺少时使用提供的 `allow_always`,不调用第二次人工 authorizer;
Ask、已关闭或未知 Session 拒绝。这里不写入永久授权规则,也不把业务问答当作权限确认。
### 8.5 事件模型
Harness Control Plane 只发送 GoodBuddy 能稳定解释的字段:
@@ -325,17 +333,18 @@ GoodBuddy conversationId -> Harness sessionId + process generation
- 历史以明确的“不可信会话数据”结构传入,不能拼接成系统指令。
- 用户分配的 Skill 只通过 Main 校验的包路径进入 Host,并在 Agent scope 注册;不得把 Skill 内容伪装成用户 Prompt。
### 9.3 取消与超时
### 9.3 取消与有界控制操作
- 用户取消时立即发送 `session/cancel`。
- 取消等待有界,超时后关闭连接并终止整个 Harness 进程。
- 初始化、握手、Session 创建、Prompt、权限回传和关闭分别使用独立超时。
- Prompt 超时与用户取消使用不同错误类型,不能被宽泛 catch 抹平。
- 初始化、握手、请求准备、权限回传和关闭分别使用独立控制超时。
- 生产 Prompt 不设置固定墙钟总时限,由 Harness 自身协议终态或用户取消结束。测试可以
显式注入短 Prompt 时限验证取消传播,超时错误与用户取消仍不能被宽泛 catch 抹平。
- 取消后仍可接收并丢弃该请求的最终协议结算帧,但不得写入下一请求。
### 9.4 释放与退出
- 原生能力清单通过一次性 Runtime 探测,取得有界快照后立即 dispose,不得因浏览设置或切换项目把 Host 缓存在执行 Runtime 池中。
- 原生能力清单由 `SelectedRuntimeManager` 缓存的执行 Runtime 提供,与随后的对话请求共用同一实例,不再为清单单独启动并销毁一次 Host;该实例遵循执行缓存的空闲淘汰、设置变更 reset 和退出 dispose 规则。
- 删除或释放对话时调用 `goodbuddy/session/release`。
- Runtime dispose 时先拒绝新请求,再取消所有 Session。
- Harness Control Plane 完成 Agent、工具和会话清理,Host 完成 Cordis Fiber 与子进程的反向清理。
@@ -426,13 +435,16 @@ DeepSeek Harness 首版只使用符合下列边界的 GoodBuddy 模型连接:
- 协议必须是 `openai-chat-completions`。
- 认证必须是 API Key。
- 公网服务地址必须使用 HTTPS;`localhost`、`127.0.0.1` 和 `::1` 回环地址可以使用 HTTP。
- 服务地址可以使用自定义主机、端口和部署路径,但不得包含用户名、密码、查询参数或片段。
- 服务地址支持 HTTP 和 HTTPS,不根据主机位置、端口、凭据、查询参数或片段增加额外限制。
- 模型名称不限制为 DeepSeek 品牌,由所选 OpenAI 兼容服务决定。
- 模型名称和服务地址由 Main 传入受控 Host。
- 图片能力只读取所选 GoodBuddy 模型连接的 `supportsImageInput`;Main、Utility 启动配置、ACP 能力和 Pi-AI 模型输入模态必须使用同一个布尔值。
- 模型连接的自定义 Header 通过 Utility 启动配置交给 Pi-AI Provider;Pi-AI 没有受支持的
任意 Body 配置入口,因此 DeepSeek Harness 不接收自定义 Body。完整支持矩阵和合并规则
见[模型连接请求定制](../model-connections/technical-design.md)。
- API Key 继续保存在 GoodBuddy 加密设置中。
- 启动环境提供的部署连接只由 Main 自动解析,不在 Renderer 中显示为可选来源。
- 未显式指定 Harness 模型且没有可用的管理员预置连接时,Main 优先使用当前默认的兼容 GoodBuddy 模型连接;默认连接不兼容时使用首个兼容连接。只有不存在任何兼容连接时才提示用户前往模型或 Runtime 设置。
不允许选择 Harness 自有的用户配置文件或自定义 Host。Runtime 始终使用随当前 GoodBuddy 版本发布的内置 Host,并通过完整内部能力握手。
@@ -469,7 +481,7 @@ DeepSeek Harness 首版只使用符合下列边界的 GoodBuddy 模型连接:
| stderr 累计 | 64 KiB |
| Host 启动 | 10 秒基础预算 + 每插件 5 秒激活与最多 1 秒失败清理,插件序列最多 91 秒;Main 另预留 2 秒持久化失败状态 |
| ACP 初始化与内部握手 | 每阶段 10 秒 |
| 单次 Prompt | 10 分钟 |
| 单次 Prompt | 无固定墙钟总时限,由 Harness 终态或用户取消结束 |
| 有序关闭宽限期 | 2 秒 |
超过限制时应取消当前请求。协议帧、队列或 stderr 持续异常时,应终止 Runtime 进程,避免继续信任已失控的通道。
@@ -497,7 +509,7 @@ Runtime: GoodBuddy 内置 DeepSeek Harness
模型配置: 跟随 GoodBuddy · 企业网关(qwen-plus)
状态: 已就绪
路径: <受控 Host 路径>
版本: 0.1.0-rc.8
版本: 0.1.2-rc.1
执行权限: 当前用户权限
Host 始终由当前 GoodBuddy 版本提供,不存在自定义 Host 入口。
@@ -535,7 +547,7 @@ Runtime 栏目入口始终存在,并采用统一监督模型;内部再按用
显示 OpenCode、Continue 或 DSH 的具体区域。未支持能力不渲染空卡片或一排禁用按钮,而是
在用户需要理解缺口时显示原因和可执行入口。跟随模式切换 Runtime、Conversation 或 Task
时必须清理上一归属的聚合执行状态,固定目标则保持不变。完整工作栏契约见
[通用助手工作栏与执行空间 PRD](../prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md)。
[通用助手工作栏与执行空间 PRD](../assistant-workbar/prd.md)。
所有未来的 Subagent、Job、Workflow、Hook 和会话操作仍须经过 Main 的 Runtime 边界,保留取消、超时、权限、Task/Job/Subjob 层级、用量和活动审计。高风险动作在侧栏就地确认,运行结果进入活动与成果记录,不以 Composer 按钮代替监督面板。DeepSeek Harness 首版仍不加载这些服务,本节只确定未来跨 Runtime 的产品位置和协议归属。
+50
View File
@@ -0,0 +1,50 @@
# 桌面宠物
桌面宠物是 GoodBuddy 桌面端一个可选的常驻悬浮入口,用于随时发起提问、暂存文件或文字,
并直观查看当前任务状态。桌面宠物不建立新的对话、任务或执行体系:它只是现有 Conversation、
Task 和 Runtime 能力的一个轻量触发面和状态镜面。
## 核心边界
- 桌面宠物**不**新增 Ask/Execute 执行通道,也不在自身窗口内直接展示模型回复。快速发起
和拖放暂存的内容始终交给主窗口现有输入区确认后发送,从不自动发送或自动执行。
- 桌面宠物**不**持续读取屏幕内容、剪贴板或全局选中文本。
- 桌面宠物显示的任务状态、通知和历史记录均来自现有 Task/Conversation 数据,不建立第二套
存储或索引。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [PRD](./prd.md) | 用户问题、范围、功能需求和产品验收 |
| [User Stories](./user-stories.md) | 首次启用、正常使用、失败恢复、兼容和边界场景 |
| [功能逻辑](./logic-design.md) | 不变量、状态维度、决策表和失败结果 |
| [UI 设计](./ui-design.md) | 窗口交互、设置入口、状态呈现、无障碍和响应式行为 |
| [技术设计](./technical-design.md) | 窗口与进程边界、IPC 边界、持久化、生命周期和验证策略 |
功能尚未实施,因此不创建 `progress.md`;实施开始后按 [`docs/features/AGENTS.md`](../AGENTS.md)
的证据要求补充。
## 术语
- **桌面宠物窗口**:应用启用该功能后创建的常驻、无边框、可置顶悬浮窗口。
- **快速发起**:点击宠物打开的小型输入框,用于输入文字或附加单个文件,提交后转入主窗口。
- **暂存**:快速发起或拖放产生的文字和附件在提交前的中间状态,不代表已发送或已执行。
- **状态镜面**:宠物窗口对当前任务状态(等待处理、执行中、已完成、失败、空闲)的展示,
数据权威来自 Task 与 Conversation,不是独立状态源。
- **安静模式**:抑制宠物完成/失败气泡提示的设置,可访问的状态查询不受影响。
## 相关功能
- [Task 与 Job 文档集](../task-and-job/README.md):任务状态、状态优先级和 Task Center
导航的权威定义,宠物的状态镜面必须与其保持一致,不重新定义任务生命周期。
- [助手工作栏](../assistant-workbar/README.md):主窗口执行空间和输入区的权威定义,宠物的
快速发起和拖放暂存最终落地到该输入区,不复制其行为。
- [直连模型 Agent 能力](../direct-model-agent/README.md)、[远程主机与远程执行](../remote-host/README.md):
Ask/Execute 授权边界的权威定义,宠物不扩大或绕过该边界。
- [文档处理](../document-processing/README.md):拖放文件解析和格式支持的权威定义,宠物拖放
暂存复用同一处理管线,不重新定义支持类型。
- 根 [`UI-DESIGN.md`](../../../UI-DESIGN.md):全局视觉、通知视口、Switch/Checkbox 语义和
无障碍规则的权威定义。
- [`docs/development/release-runbook.md`](../../development/release-runbook.md):跨平台打包
矩阵和交付物的权威定义。
+203
View File
@@ -0,0 +1,203 @@
# 桌面宠物功能逻辑设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [桌面宠物 PRD](./prd.md) |
| User Stories | [桌面宠物 User Stories](./user-stories.md) |
| 任务模型 | [Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md) |
本文负责桌面宠物自身的状态、不变量和决策规则,不重新定义 Task、Conversation、Ask/
Execute 或附件的既有语义;这些语义以各自权威文档为准。
## 1. 核心不变量
### INV-1 宠物不是第二条执行通道
快速起草和拖放暂存只产生主窗口会话输入区的草稿数据(文本、附件引用),不产生消息、
不产生 Task、不触发模型调用。发送、模式选择和 Runtime 选择继续完全由主窗口既有流程
拥有。
### INV-2 状态镜面是既有状态的投影
宠物状态镜面的取值只能从当前 Task 集合的既有状态推导,不允许引入宠物专属的任务状态
字段或独立于 Task Center 的状态存储。宠物状态与 Task Center 展示的状态出现分歧即视为
缺陷。
### INV-3 总开关是唯一的存在性开关
“启用桌面宠物”关闭时,宠物窗口、气泡和托盘条目文案必须整体消失;除该开关外,任何
子设置(外观、位置、安静模式)的变化都不得单独创建或销毁宠物窗口。
### INV-4 暂时隐藏不影响启用状态
“暂时隐藏”只影响窗口可见性,不修改总开关持久化值,也不清除位置、外观或安静模式设置。
### INV-5 提示与原生通知互斥
同一个任务终态事件(完成或失败)只能产生宠物气泡或现有原生系统通知中的一种,不产生
两种同时可见的独立提示。
### INV-6 位置始终落在可用显示器内
宠物窗口的持久化位置在应用于当前显示器拓扑之前必须被校验;不能得出宠物窗口完全位于
任何当前可见显示器之外的最终坐标。
### INV-7 拖放与起草共享暂存规则
拖放产生的文本和文件必须满足与快速起草附件相同的类型、大小、数量限制;两者不允许存在
两套互不一致的限制值。
## 2. 状态维度
### 2.1 总开关与可见性
```text
disabled -> enabled_visible
enabled_visible <-> enabled_hidden (暂时隐藏 / 重新显示)
enabled_visible|enabled_hidden -> disabled (关闭总开关)
```
`disabled` 是唯一没有宠物窗口存在的状态;`enabled_hidden` 窗口不可见但设置全部保留。
### 2.2 快速起草弹出框
```text
closed -> drafting -> submitted | discarded
drafting -> closed (Escape / 点击外部 / 关闭按钮)
```
`submitted` 是终态转换动作,不是持续状态:提交后弹出框立即回到 `closed`,草稿数据已经
转交给主窗口。
### 2.3 拖放交互
```text
idle -> drag_over -> dropped_accepted | dropped_rejected | drag_cancelled
```
`drag_over` 只影响视觉反馈,不产生任何持久数据;只有 `dropped_accepted` 才产生暂存
数据。`dropped_rejected` 用于文件夹或全部文件超限的场景,不产生暂存也不打开主窗口。
### 2.4 状态镜面
```text
unavailable | idle | completed_recent | running | waiting_approval | failed_recent
```
取值由当前活跃与最近终态 Task 集合计算得出,属于派生状态,不单独持久化。
### 2.5 提示气泡
```text
none -> pending -> shown -> dismissed
```
`pending` 表示事件已发生但由于主窗口可见或安静模式而被抑制,不进入 `shown`。
## 3. 状态镜面优先级
宠物在任意时刻只呈现一个状态镜面取值。存在多个活跃 Task 时按以下优先级取最高项:
| 优先级 | 状态 | 条件 |
| --- | --- | --- |
| 1 | `unavailable` | 用于确认状态的 Runtime 或网络当前不可达 |
| 2 | `waiting_approval` | 至少一个 Task 处于等待批准 |
| 3 | `failed_recent` | 至少一个 Task 在最近的“最近失败”窗口内失败,且用户尚未查看 |
| 4 | `running` | 至少一个 Task 处于执行中 |
| 5 | `completed_recent` | 至少一个 Task 在最近的“最近完成”窗口内完成,且用户尚未查看 |
| 6 | `idle` | 以上条件都不满足 |
“最近失败”“最近完成”窗口的具体时长和“已查看”判定属于技术选择,由
[技术设计](./technical-design.md)基于既有 Task 已读/未读语义确定,不在本文固定具体
数值。
## 4. 提示归并决策表
| 事件 | 主窗口状态 | 安静模式 | 结果 |
| --- | --- | --- | --- |
| 任务完成 | 不可见或未聚焦 | 关闭 | 显示宠物气泡;不触发原生通知 |
| 任务完成 | 不可见或未聚焦 | 开启 | 不显示气泡;不触发原生通知;仅更新状态镜面 |
| 任务完成 | 可见并聚焦 | 任意 | 不显示气泡;反馈通过主窗口既有机制呈现 |
| 任务失败 | 不可见或未聚焦 | 关闭 | 显示宠物气泡并保持可见直到用户查看或关闭 |
| 任务失败 | 不可见或未聚焦 | 开启 | 不显示气泡;仅更新状态镜面为 `failed_recent` |
| 任务失败 | 可见并聚焦 | 任意 | 不显示气泡;反馈通过主窗口既有机制呈现 |
“主窗口可见并聚焦”与现有 [`desktop-notification.ts`](../../../src/main/desktop-notification.ts)
判定原生通知是否触发所使用的条件保持一致,避免宠物和原生通知各自使用不同的可见性
判定导致同一事件在两处呈现或同时缺失。
## 5. 快速起草与拖放的目标会话决策表
| 场景 | 目标会话 |
| --- | --- |
| 主窗口存在当前活跃会话,用户未在弹出框内更改目标 | 该活跃会话 |
| 用户在弹出框内选择“新建对话” | 提交时新建并激活一个会话 |
| 主窗口不存在任何会话 | 提交时新建并激活一个会话 |
| 目标会话在提交前被用户在主窗口中删除 | 视为“不存在任何会话”,回退到新建会话 |
拖放没有目标切换界面,始终采用与快速起草相同的默认目标会话规则。
## 6. 附件与文本限制
快速起草和拖放暂存复用现有聊天附件规则,不定义新的限制数值:
- 单条草稿的附件数量上限、单个附件大小上限、支持的文本与图片类型,与主窗口聊天附件
的现有限制一致。
- 拖放同时包含多个文件时,逐个按上述规则校验;不合规文件被拒绝,不影响合规文件继续
暂存。
- 拖放文件夹被整体拒绝,不做递归展开或部分接受。
## 7. 位置与显示器校验
1. 加载持久化位置后,检查该坐标是否位于当前任一可见显示器的可用区域内。
2. 位于范围内:直接使用。
3. 不在范围内(例如记忆的显示器已断开):改用主显示器上一个默认可见位置,并在下一次
用户拖动宠物后覆盖持久化位置。
4. 开启“贴边”时,拖动释放位置与最近边缘的距离小于吸附阈值即吸附到该边缘;阈值和默认
位置的具体数值属于技术选择。
## 8. 失败与恢复
| 场景 | 行为 |
| --- | --- |
| 主窗口进程尚未创建或已被销毁 | 提交快速起草或拖放时先创建/恢复主窗口,再写入草稿 |
| 写入草稿时主窗口窗口对象不可用 | 提交失败,弹出框保留草稿内容供用户重试,不静默丢弃 |
| 状态查询所需的 Runtime/网络不可达 | 状态镜面显示 `unavailable`,不回退显示 `idle` |
| 显示器拓扑变化导致位置越界 | 按第 7 节回退到默认可见位置,不隐藏窗口 |
| 用户在气泡展示期间关闭总开关 | 气泡与宠物窗口一并立即消失 |
| 应用启动时总开关为开启但窗口创建失败 | 记录失败原因,不阻塞主窗口正常启动,托盘菜单提供重试入口 |
## 9. 需求追踪
| 需求 | 主要逻辑 |
| --- | --- |
| `FR-1` | `INV-3`、总开关与可见性状态 |
| `FR-2` | 位置与显示器校验、`INV-6` |
| `FR-3` | `INV-1`、快速起草状态、目标会话决策表 |
| `FR-4` | `INV-1`、`INV-7`、拖放交互状态、附件与文本限制 |
| `FR-5` | `INV-2`、状态镜面优先级 |
| `FR-6` | `INV-5`、提示归并决策表 |
| `FR-7` | 提示归并决策表中安静模式列 |
| `FR-8` | 位置与显示器校验之外的技术选择,逻辑仅约束持久化立即生效 |
| `FR-9` | `INV-4`、总开关与可见性状态 |
| `FR-10` | 快速起草状态中的键盘转换,具体按键映射见 UI 设计 |
| `FR-11` | `INV-1`、`INV-2`、`INV-7` |
## 10. 逻辑完整性评估
本设计覆盖了启用/关闭、暂时隐藏、快速起草、拖放(含超限和文件夹)、状态镜面优先级、
提示与原生通知的归并、位置越界恢复、以及主窗口不可用时的失败路径。
未在本文固定、留给技术设计选择的实现细节:
- “最近完成/最近失败”窗口的具体时长和“已查看”判定的确切事件来源。
- 贴边吸附阈值和默认回退位置的具体像素值。
- 状态查询的具体轮询或事件驱动机制。
这些是技术选择而非未解决的产品决策:本文的优先级和决策表已经足以在任何取值下保持
行为一致。
+158
View File
@@ -0,0 +1,158 @@
# 桌面宠物 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 适用产品 | GoodBuddy Desktop |
| 关联文档 | [功能逻辑](./logic-design.md)、[UI 设计](./ui-design.md)、[技术设计](./technical-design.md) |
## 1. 背景
GoodBuddy 当前只能通过主窗口发起提问、查看任务和管理执行空间。用户想要快速提问、丢一个
文件进去,或者只是看一眼任务有没有跑完时,仍必须先唤出并聚焦整个主窗口。这在多任务、多
显示器场景下打断当前工作流。
桌面宠物提供一个常驻、体积小、不抢焦点的悬浮入口,承担三类高频、轻量的动作:快速起草
一个问题、拖放文件或文字暂存、以及查看当前任务状态。它不是新的执行通道,只是现有
Conversation、Task 和 Runtime 能力上的一层轻量触发面和状态镜面。
## 2. 产品目标
1. 用户无需先打开或聚焦主窗口即可起草一条消息或暂存一个文件。
2. 用户可以在不打开 Task Center 的情况下,一眼看到当前是否有任务在跑、等待处理或失败。
3. 桌面宠物默认不产生新的干扰:不主动弹窗抢焦点,不重复现有原生通知。
4. 桌面宠物的开关、位置、外观和提示强度完全由用户控制,且可随时完全关闭并恢复到没有该
功能之前的界面状态。
5. 桌面宠物不新增或绕过 Ask/Execute 授权边界,也不建立第二条消息发送或任务执行路径。
## 3. 功能范围
### 3.1 包含
- 设置中的“桌面宠物”一级分类,包含总开关、外观、位置和提示设置。
- 常驻悬浮窗口:可拖动、跨显示器记忆位置、贴边、置顶开关。
- 点击宠物打开的“快速起草”弹出框:输入文字、附加至多一个本机文件,提交后转入主窗口
对应会话的输入区草稿,不自动发送。
- 拖放文字或文件到宠物窗口,效果与快速起草的附件路径一致。
- 状态镜面:显示当前活跃任务的汇总状态,点击后打开 Task Center 中对应任务。
- 完成/失败气泡提示,及独立于原生通知的安静模式开关。
- 从有限的内置角色/外观中选择,调整尺寸、透明度和动画强度。
- 全局显示/隐藏开关,托盘菜单入口,键盘可达的等效操作路径。
### 3.2 不包含
- 在宠物窗口内直接展示模型回复、执行工具调用或建立第二条 Ask/Execute 通道。
- 未经用户确认自动发送消息或自动执行任务。
- 持续读取屏幕内容、剪贴板或系统级选中文本。
- 用户上传任意自定义头像图片、养成机制、货币或每日签到。
- 多个宠物实例、宠物之间互动或联网社交功能。
- 管理员远程强制启用、配置或监控用户的桌面宠物。
- 替代 Task Center、助手工作栏或现有通知系统的权威数据来源。
## 4. 功能需求
### FR-1 总开关与默认状态
- 设置新增“桌面宠物”一级分类,包含“启用桌面宠物”总开关。
- 全新安装和现有用户升级后默认关闭,不在未经用户操作时出现在桌面上。
- 关闭总开关立即隐藏宠物窗口,保留用户此前的位置、外观和提示设置以便重新开启。
### FR-2 常驻悬浮窗口
- 启用后创建一个无边框、默认置顶、不出现在任务栏的小型窗口。
- 用户可拖动到任意位置;位置按显示器分别记忆,显示器断开重连后回退到最近的可用显示器。
- 提供“贴边”选项,拖动到屏幕边缘附近时自动吸附。
- 提供独立的“始终置顶”开关;关闭后宠物仍可见,但可被其他窗口遮挡。
- 宠物窗口不接受系统级最大化、最小化或调整大小操作。
### FR-3 快速起草
- 点击宠物打开一个紧邻宠物的小型弹出框,包含文本输入和“附加文件”按钮。
- 文本输入支持多行、粘贴图片;附加文件复用现有附件类型、数量和大小限制。
- 弹出框显示将要进入的目标:当前活跃会话,或“新建对话”。用户可在弹出框内切换目标,
但不能在弹出框内选择模型、Runtime 或 Ask/Execute 模式。
- 提交后主窗口被显示或聚焦,目标会话被激活,文字和附件作为草稿填入该会话的输入区,
输入焦点落在该输入区。提交动作本身不发送消息、不创建任务。
- 取消或关闭弹出框放弃当前草稿,不影响主窗口任何已有草稿。
### FR-4 拖放暂存
- 用户可以把本机文件或选中文字拖到宠物窗口上。
- 拖入时宠物给出可放置的视觉反馈;释放后遵循与快速起草提交相同的行为:显示主窗口、
激活目标会话、把内容放入该会话输入区草稿。
- 拖放文件的类型、数量、大小限制与现有聊天附件限制一致;超出限制时明确提示被拒绝的
文件,其余合法文件仍正常暂存。
- 拖放不支持文件夹;文件夹被拖入时提示不支持,不递归展开。
### FR-5 状态镜面
- 宠物在空闲、执行中、等待处理、已完成、失败之间切换外观,反映当前所有活跃 Task 的
汇总状态;多个状态并存时按“等待处理 > 失败 > 执行中 > 已完成 > 空闲”的优先级显示。
- 点击状态镜面打开或聚焦主窗口的 Task Center,并定位到触发当前状态的任务。
- Runtime 或网络不可用导致任务状态无法确认时,状态镜面显示“状态不可用”而不是回退成
“空闲”。
### FR-6 完成与失败提示
- 任务从执行中或等待处理转为已完成或失败,且主窗口不可见或未聚焦时,宠物旁显示一条
气泡,说明任务标题和结果。
- 气泡在用户不操作时于一段时间后自动消失;失败气泡保持可见直到用户查看或手动关闭。
- 点击气泡等价于点击状态镜面,打开对应任务详情。
- 同一事件只能触发桌面宠物气泡或现有原生系统通知中的一种,不同时出现两条提示;具体
归并规则由[功能逻辑](./logic-design.md)定义。
### FR-7 安静模式
- 独立于总开关的“安静模式”开关,开启后不再显示完成/失败气泡。
- 安静模式不影响状态镜面本身的外观切换,也不影响原生系统通知的现有行为。
### FR-8 外观与个性化
- 提供数量有限的内置角色供选择,不支持上传自定义图片。
- 提供尺寸、透明度和动画强度调整;调整立即生效并持久化。
- 提供“恢复默认外观”操作。
### FR-9 显示、隐藏与退出
- 提供独立于总开关的“暂时隐藏”动作:隐藏宠物窗口但保留启用状态,可通过托盘菜单或
设置重新显示。
- 托盘菜单新增“显示/隐藏桌面宠物”入口,随当前可见性变化文案。
- 应用退出时宠物窗口一并关闭,不残留独立进程。
### FR-10 键盘可达性
- 宠物窗口本身可通过系统级窗口切换(如 Alt+Tab)取得焦点,取得焦点后可用键盘打开快速
起草弹出框、在状态镜面和提示之间移动,并可用 Escape 关闭弹出框。
- 所有交互提供可访问名称和状态说明,不仅依赖动画或颜色表达状态。
### FR-11 与现有能力的边界
- 快速起草和拖放暂存最终使用主窗口现有的会话、附件、模型和 Ask/Execute 选择逻辑,不
新增第二套发送或授权路径。
- 状态镜面和气泡的任务状态数据来自现有 Task 与 Conversation 记录,不建立独立存储、不
与 Task Center 的状态定义产生分歧。
- 桌面宠物不改变现有全局快捷键、托盘菜单既有条目或主窗口默认行为。
## 5. 产品验收
- 全新安装和已升级用户默认看不到桌面宠物;显式启用后立即出现在预期位置。
- 关闭总开关后桌面上不再有任何宠物窗口或残留提示,重新开启后恢复此前的位置和外观。
- 通过快速起草或拖放提交的内容,在主窗口对应会话的输入区中可见为未发送草稿,用户需要
自行确认发送;关闭 GoodBuddy 或取消草稿不会产生任何任务或消息记录。
- 状态镜面呈现的状态与 Task Center 中对应任务的状态一致;两者出现分歧即视为缺陷。
- 同一任务完成或失败事件不会同时产生宠物气泡和原生系统通知。
- 安静模式开启时不出现气泡,但状态镜面外观仍随任务状态变化。
- 键盘用户无需鼠标即可打开快速起草、查看状态并关闭弹出框。
- 中英文文案、深浅主题、多显示器和 DPI 缩放下宠物窗口位置和内容均正确显示。
## 6. 明确限制
- 桌面宠物不提供在其自身窗口内完成整段对话或查看历史消息的能力;查看完整回复必须进入
主窗口。
- 桌面宠物不支持多实例、多角色同屏或角色之间的模拟互动。
- 透明背景效果依赖操作系统合成器;在不支持透明合成的 Linux 桌面环境下,宠物窗口可能
退化为不透明的小型窗口,具体行为见[技术设计](./technical-design.md)。
@@ -0,0 +1,335 @@
# 桌面宠物技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [桌面宠物 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| UI 设计 | [UI 设计](./ui-design.md) |
## 1. 进程与窗口边界
```text
Main
-> createMainWindow() (既有,src/main/window.ts)
-> createPetWindow() (新增)
-> 独立 BrowserWindow:无边框、透明、置顶、不入任务栏
-> 独立 preload:src/preload/pet.ts(新增,窄 API)
-> 加载独立渲染入口:src/renderer/pet.html + src/renderer/src/pet/
Renderer(主窗口,既有)
-> window.goodbuddy(既有 DesktopApi,不变)
Renderer(宠物窗口,新增)
-> window.goodbuddyPet(新增窄 API,与既有 window.goodbuddy 完全隔离)
```
宠物窗口是与主窗口平级的第二个 `BrowserWindow`,不是主窗口的子窗口、不是 `WebContentsView`
叠加层。理由:
- 主窗口现有 `setWindowOpenHandler` 拒绝一切子窗口(`src/main/window.ts`),沿用该策略
比引入例外更简单。
- 宠物窗口需要独立于主窗口的显示/隐藏、位置和置顶状态,作为子窗口会继承父窗口的显示
状态,不满足“主窗口隐藏时宠物仍可见”的产品要求。
不复用主窗口的 preload 和渲染入口:主窗口 preload 暴露的 `DesktopApi` 面积很大(对话、
知识库、设备共享等)。给宠物窗口加载同一 preload 会让一个新增的、长期运行、暴露给
远程可控网页内容风险更低但攻击面更广的窗口拥有不必要的能力。新增窄 preload 遵循仓库
现有的“只暴露显式方法”约束。
## 2. 模块职责
| 模块 | 位置 | 职责 |
| --- | --- | --- |
| `PetWindowService` | `src/main/pet/pet-window-service.ts`(新增) | 创建/销毁宠物窗口、位置持久化与越界校验、置顶与贴边、显示/隐藏 |
| `PetSettingsStore` | `src/main/pet/pet-settings-store.ts`(新增) | 总开关、外观、位置、安静模式的持久化,遵循既有 settings-file-utils 模式 |
| `PetStatusProjector` | `src/main/pet/pet-status-projector.ts`(新增) | 从既有 Task 查询结果计算状态镜面取值,不引入新状态源 |
| `PetDraftBridge` | `src/main/pet/pet-draft-bridge.ts`(新增) | 把宠物提交的文本/附件转交主窗口渲染进程,驱动会话激活与草稿写入 |
| `PetNotificationCoordinator` | `src/main/pet/pet-notification-coordinator.ts`(新增) | 与既有 `desktop-notification.ts` 协商同一事件只走一条提示路径 |
| 既有 `context-manager.ts` | 不变 | 宠物附件校验复用其 `selectFiles`/校验规则,不新增第二套限制 |
| 既有 `assistant-database.ts` / Task 查询 | 不变 | 状态镜面查询的唯一数据来源 |
`PetStatusProjector` 只读取既有 Task 查询结果并按
[功能逻辑第 3 节](./logic-design.md#3-状态镜面优先级)的优先级归并,不写入、不缓存跨进程
重启的状态;重启后状态镜面从当前 Task 集合重新计算。
## 3. 宠物窗口创建
```ts
// src/main/pet/pet-window-service.ts(示意)
function createPetWindow(): BrowserWindow {
return new BrowserWindow({
width: PET_SIZE.width,
height: PET_SIZE.height,
show: false,
frame: false,
transparent: true,
resizable: false,
skipTaskbar: true,
alwaysOnTop: true, // 由“始终置顶”设置决定实际值
hasShadow: false,
backgroundColor: '#00000000',
webPreferences: {
preload: join(currentDirectory, '../preload/pet.cjs'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true
}
})
}
```
- `transparent: true` 依赖操作系统合成器。按 PRD 6 节明确限制,在不支持透明合成的
Linux 桌面环境(例如未运行合成器的部分窗口管理器)下,Electron/Chromium 会把窗口
渲染为不透明;`PetWindowService` 不检测合成器能力,只在渲染进程内把背景色设置为
当前主题的画布色作为不透明回退,保证内容仍然可读,不尝试探测或声明支持特定合成器。
- `alwaysOnTop` 的实际值由“始终置顶”Switch 驱动,通过 `window.setAlwaysOnTop(bool)`
在设置变更时更新,不需要重建窗口。
- 复用既有 `resolveWindowIcon`/任务栏排除逻辑作为参考,但宠物窗口 `skipTaskbar: true`
固定为真,不提供用户设置。
- 宠物窗口不设置 `parent`,与主窗口是同级独立窗口;应用单实例锁和 `second-instance`
处理(`src/main/index.ts`)不需要改动,因为宠物窗口的存在与否由 `PetWindowService`
内部状态决定,而不是第二个 Electron 实例。
## 4. 拖放实现
Electron 渲染进程原生支持 HTML5 拖放;宠物渲染进程监听 `dragenter`/`dragover`/`drop`
即可获得视觉反馈和释放事件,不需要 Main 参与拖放事件本身。文件路径解析复用既有
`webUtils.getPathForFile`(已用于 `src/preload/index.ts` 的知识库拖放导入):
```ts
// src/preload/pet.ts(示意)
import { contextBridge, ipcRenderer, webUtils } from 'electron'
contextBridge.exposeInMainWorld('goodbuddyPet', {
submitDraft: (input: PetDraftSubmission) =>
ipcRenderer.invoke(petIpcChannels.submitDraft, input),
resolveDroppedPaths: (files: File[]) =>
files.map((file) => webUtils.getPathForFile(file)).filter(Boolean),
// ...状态订阅、设置读写等方法
})
```
拖入的文本(非文件)通过 `dataTransfer.getData('text/plain')` 直接读取,随普通文本
字段一起提交,不经过路径解析。
宠物渲染进程把解析出的路径和文本打包为 `PetDraftSubmission`,通过 IPC 交给 Main;Main
侧的路径校验、类型判断、大小限制和读取,全部复用既有 `ContextManager` 的规则(`src/main/context-manager.ts`
中的 `supportedExtensions`、`supportedImageExtensions`、`supportedDocumentExtensions`、
`maximumContextBytes`、`maximumAttachmentsPerMessage`),不新增校验逻辑。`ContextManager`
目前的公开入口是围绕原生对话框(`selectFiles`)设计的;实现时需要新增一个接受任意
canonical 路径数组的方法(例如 `stageFromPaths`),内部复用现有扩展名/大小/文档解析
分支,而不是复制一份判断逻辑。文件夹路径(`fs.stat` 判定为目录)在该方法入口即被拒绝,
不递归遍历。
## 5. 快速起草提交与主窗口联动
```text
宠物渲染进程 submitDraft(input)
-> Main: PetDraftBridge.handleSubmit(input)
-> ContextManager.stageFromPaths(input.filePaths) (复用既有校验)
-> 若主窗口不存在:createMainWindow() + loadMainWindow()(复用既有函数)
-> showWindow(mainWindow)(既有函数)
-> mainWindow.webContents.send(petIpcChannels.draftReceived, {
targetConversationId, text, stagedContextIds
})
-> 主窗口渲染进程(App.tsx 新增一个事件监听,与既有 conversation:new 监听模式一致)
-> 若 targetConversationId 无效或已删除:回退为新建会话(既有新建会话路径)
-> 激活目标会话、把 text 写入该会话输入框状态、把 stagedContextIds 追加到该
会话的附件状态(既有 App.tsx 附件状态更新路径)
-> 聚焦输入框
```
- 草稿只在主窗口渲染进程内变成“输入框状态”和“待发送附件列表”,与用户手动打字/手动
点击附件按钮产生的状态是同一份 React state,不建立并行的草稿存储。这直接满足
`INV-1`:宠物不产生消息、不产生 Task。
- `stagedContextIds` 复用现有 `context:*` IPC 产生的 Context ID;主窗口把这些 ID 加入
附件列表的方式,与用户通过“附加文件”按钮选择文件后的状态更新完全一致。
- 若提交发生时主窗口曾被销毁(用户从未打开过,或者提前研究表明当前架构不销毁主窗口
只隐藏),`PetDraftBridge` 调用既有 `createMainWindow`/`loadMainWindow`,等待
`ready-to-show` 完成后再发送 `draftReceived`,避免竞态。
## 6. IPC 边界
新增独立 IPC 通道命名空间 `pet:*`,与既有 `ipcChannels` 并列定义在
`src/shared/ipc-channels.ts`:
```text
pet:get-snapshot (设置 + 当前状态镜面取值 + 可见性)
pet:update-settings (总开关、外观、贴边、置顶、安静模式)
pet:submit-draft (快速起草/拖放提交)
pet:dismiss-bubble (关闭完成/失败气泡)
pet:open-task (点击状态镜面/气泡,打开 Task Center 对应任务)
pet:toggle-visibility (托盘“隐藏/显示桌面宠物”)
```
- 所有输入使用 `src/shared/pet-contracts.ts`(新增)中的 Zod schema 校验,与既有约定
一致。
- `trusted-ipc-sender.ts` 的 `assertTrustedSender` 按窗口实例参数化使用;宠物窗口的
IPC handler 校验 `event.sender === petWindow.webContents`,不能用主窗口的 trusted
sender 校验函数验证宠物窗口的请求,也不能反过来。
- 宠物 preload 不暴露任何既有 `DesktopApi` 方法;需要的能力(提交草稿、读取/写入宠物
设置、订阅状态镜面)都通过 `pet:*` 通道单独暴露为 `window.goodbuddyPet` 的具体方法。
- Main 侧广播状态镜面变化时,只向宠物窗口发送派生后的枚举值(第 3 节的六种状态之一)
和最小必要的任务标题/ID,不发送完整 Task 或 Conversation 对象。
## 7. 持久化
新增设置文件,遵循既有 `settings-file-utils.ts` 的原子写入、版本校验和损坏隔离模式,
不复用 `ApplicationSettingsStore` 或 `ShortcutSettingsStore` 的 schema:
```text
pet-settings.json
{
version: 1,
enabled: boolean,
quietMode: boolean,
alwaysOnTop: boolean,
snapToEdge: boolean,
appearance: { character: string, size: 'small'|'medium'|'large',
opacity: number, motion: 'off'|'reduced'|'full' },
position: { displayId: number, x: number, y: number } | null
}
```
- `position` 为 `null` 表示尚未被用户拖动过,使用默认位置计算规则(第 8 节)。
- `displayId` 使用 Electron `screen.getAllDisplays()` 返回的 `id`;加载时按第 8 节校验,
不假设跨重启保持稳定的显示器 id 一定仍然存在。
- 独立 schema 版本号(从 1 开始),与主设置版本号互不影响;未来变更只需要迁移这一个
文件。
- 是否需要独立数据库表:不需要。宠物没有需要查询、筛选或跨设备同步的历史数据,一个
JSON 文件足够,符合仓库 KISS/YAGNI 约束。
## 8. 位置与多显示器校验
```ts
function resolvePetPosition(
saved: PetPosition | null,
displays: Display[]
): { x: number; y: number } {
if (saved) {
const display = displays.find((d) => d.id === saved.displayId)
if (display && pointWithinWorkArea(saved, display.workArea)) {
return saved
}
}
const primary = screen.getPrimaryDisplay()
return defaultPositionWithin(primary.workArea) // 例如右下角,留出安全边距
}
```
- 该函数在宠物窗口创建时以及 `display-added`/`display-removed`/`display-metrics-changed`
事件触发时调用;越界立即调用 `window.setPosition` 校正,不等待用户下一次拖动。
- 用户拖动结束(`moved` 事件)时把当前 `getBounds()` 和 `screen.getDisplayNearestPoint`
得到的 `displayId` 写入 `PetSettingsStore`,去抖后原子写入,避免拖动过程中高频写盘。
- 贴边吸附在渲染进程拖动过程中做视觉预判(不移动实际窗口,只显示吸附提示),在
`moved` 事件的最终位置上由 Main 一次性计算是否落入吸附阈值并调用 `setPosition`
吸附到边缘,阈值和默认位置的具体像素值作为实现细节调优,不在此固定。
## 9. 状态镜面查询
`PetStatusProjector` 不新增事件源,而是订阅既有 Task 变化通知(主窗口渲染进程已经
用于更新 Task Center 和会话内任务卡片的同一套 Main → Renderer 事件)在 Main 侧的
对应版本,或者在 Main 侧直接查询 `assistant-database.ts` 暴露的 Task 读取方法。具体
选择两者之一属于实现阶段的技术选择,约束条件是:
- 不引入宠物专属的任务状态表或缓存;查询/订阅的结果必须与 Task Center 在同一时刻
展示的状态一致。
- 查询开销必须是有界的(只统计活跃与最近终态 Task 的数量和状态,不拉取消息正文)。
- Runtime/网络不可达导致查询失败时返回 `unavailable`,不静默返回 `idle`。
“最近完成”“最近失败”窗口时长和“已查看”判定:复用 Task 已有的已读/查看时间戳字段
(若既有模型已提供);若当前模型没有这类字段,实现时以“该 Task 终态发生后,用户是否
已经点击过对应的宠物气泡或状态镜面,或已经在 Task Center 中打开过该 Task”作为已查看
判定,窗口时长建议与现有原生通知的展示逻辑保持同量级(分钟级),具体数值在实现时
根据既有 Task 事件粒度确定。
## 10. 提示与原生通知协商
`PetNotificationCoordinator` 与 `desktop-notification.ts` 的 `showDesktopNotificationWhenUnfocused`
共享同一个“主窗口是否可见并聚焦”判定:
```ts
function shouldSuppressBecauseMainWindowVisible(mainWindow: BrowserWindow): boolean {
return !mainWindow.isDestroyed() && mainWindow.isFocused()
}
```
- 现有 `showDesktopNotificationWhenUnfocused` 已经使用等价条件(`window.isFocused()`)
决定是否弹出原生通知;`PetNotificationCoordinator` 复用同一判定函数(如需要,提取为
共享工具),保证两者的“可见性”定义永远一致,不出现原生通知判定为可见但宠物判定为
不可见(或反之)导致同一事件同时出现两条提示或完全没有提示的情况。
- 实现层面:调用触发通知的代码路径在决定“原生通知 vs 宠物气泡”时二选一,而不是
两者分别独立判断后都可能触发;例如由现有触发点先判断安静模式和宠物启用状态,
为真则调用 `PetNotificationCoordinator`,否则调用现有 `showDesktopNotificationWhenUnfocused`。
- 安静模式开启时,两者都不触发;状态镜面仍照常更新(状态镜面更新走独立的
`pet:get-snapshot`/推送通道,不经过通知协商)。
## 11. 与既有模块集成
- 快速起草和拖放最终只调用主窗口渲染进程已有的“设置输入框文本”“追加附件”“激活
会话”“创建新会话”状态更新函数,不新建与 Ask/Execute、模型选择、Runtime 选择相关
的任何路径。
- 点击状态镜面/气泡打开 Task Center 复用现有 Task Center 导航(既有 IPC/路由,与托盘
“新建对话”发送 `conversation:new` 事件属于同一类模式:Main 发送一个事件,主窗口
渲染进程消费并完成导航)。
- 托盘菜单新增项复用现有 `Menu.buildFromTemplate` 结构(`src/main/index.ts` 的
`buildTray`),插入一个受当前可见性状态驱动的条目,不重写整个菜单构建函数。
- 全局快捷键(`src/shared/shortcut.ts`、`shortcut-settings-service.ts`)不受影响,
桌面宠物不注册新的全局快捷键,因为激活方式是点击窗口本身或系统级窗口切换,不需要
额外抢占全局按键空间。
## 12. 生命周期
- 应用启动读取 `PetSettingsStore`;`enabled: true` 时在既有服务和 IPC 注册完成后创建
宠物窗口(与主窗口创建顺序一致,不阻塞主窗口 `ready-to-show`)。
- 宠物窗口创建失败(例如极端情况下操作系统拒绝创建透明窗口):记录原因,不阻塞主窗口
启动,托盘“显示桌面宠物”下次点击时重试创建。
- 应用退出(`before-quit`/既有 `isQuitting` 标记)时,宠物窗口与主窗口一并销毁,不需要
独立的关闭确认或延迟清理,因为宠物窗口没有正在进行的网络连接或未保存状态:草稿已经
转交主窗口的那一刻起就属于主窗口生命周期。
- “暂时隐藏”只调用 `window.hide()`,不销毁窗口实例,重新显示直接 `showWindow` 复用
既有实例,避免频繁创建/销毁透明窗口带来的闪烁。
## 13. 打包
- 宠物渲染入口(HTML、内置角色素材)加入 `electron.vite.config.ts` 的 renderer 构建
输入(现有配置已有 `input` 多入口结构,参考现有主渲染入口的声明方式新增一项)。
- 内置角色素材(数量有限的静态图片/精灵图)作为打包资源加入 `package.json` 的 `files`/
`extraResources`,不通过运行时网络下载,符合仓库“不新增远程资源请求”的字体/资源
约束的同一精神。
- 新增 preload 产物(`pet.cjs`)需要加入 `electron.vite.config.ts` 的 main/preload 构建
`entryFileNames` 规则,与现有主 preload 产物并列。
- 六个标准打包目标(Windows/macOS/Linux × x64/arm64)均需验证宠物窗口可创建、透明或
回退不透明背景符合第 3 节预期;LoongArch 预览目标不单独验证,遵循仓库现有策略。
## 14. 验证策略
### 14.1 自动化
- `PetSettingsStore` 的默认值、原子写入、损坏隔离和版本校验(沿用
`settings-file-utils.ts` 现有测试模式)。
- `resolvePetPosition` 在显示器存在/不存在/坐标越界三种输入下的返回值。
- `PetStatusProjector` 按第 3 节优先级表在各种 Task 组合输入下的输出。
- `PetNotificationCoordinator` 与 `desktop-notification.ts` 共享判定函数在“可见并聚焦”
“不可见”“安静模式开启”组合下只触发一条路径或都不触发。
- `ContextManager.stageFromPaths`(新增方法)对合法文件、超限文件、不支持类型、文件夹
路径的分支覆盖,复用现有 `context-manager` 测试基础设施。
- `pet:*` IPC handler 的 trusted sender、schema 校验和错误码。
- 快速起草提交后主窗口渲染进程状态更新的单元测试:目标会话激活、文本写入、附件追加、
草稿被丢弃时不产生 Task 或消息记录。
### 14.2 真实环境
- Windows、macOS、Linux 至少各一次验证宠物窗口创建、拖动、贴边吸附、透明或回退背景的
实际渲染效果。
- 真实拖放一个合法文件、一个超限文件、一个文件夹到宠物窗口,确认 UI 设计第 4.3 节的
反馈和最终主窗口状态符合预期。
- 真实触发一次任务完成和一次任务失败,在“主窗口隐藏”“主窗口可见”“安静模式开启”
三种组合下确认气泡和原生通知的出现符合第 10 节的互斥规则。
- 多显示器插拔场景下确认位置回退逻辑(断开记忆显示器后宠物仍可见)。
- 打包后的应用在六个标准目标上完成启用、拖动、快速起草、退出流程的一次冒烟验证。
设计文档完成不代表功能实施或上述验证通过。
+207
View File
@@ -0,0 +1,207 @@
# 桌面宠物 UI 设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [桌面宠物 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 全局规范 | [GoodBuddy 统一界面设计系统](../../../UI-DESIGN.md) |
本文定义设置中的“桌面宠物”页面和独立宠物窗口的界面。所有颜色、间距、字体、通知、
Switch/Checkbox、焦点和响应式规则以全局设计系统为准;宠物窗口是应用内唯一不使用标准
`PageShell` 的持久界面,其例外范围在第 4 节明确限定。
## 1. 设置页面信息架构
设置中心新增“桌面宠物”一级分类,使用 `standard` PageShell,不使用页签,按以下顺序
纵向排列:
```text
桌面宠物
在桌面上保留一个轻量入口,随时起草消息、拖入文件或查看任务状态
启用桌面宠物 [Switch 关]
关闭后不在桌面上显示任何宠物窗口或提示
—— 以下设置仅在启用后可编辑,保持可见但降低强调度 ——
外观
角色 [分段选择:三到四个内置角色缩略图]
尺寸 [滑块 小 · 中 · 大]
透明度 [滑块]
动画强度 [滑块 关闭 · 轻量 · 完整]
[恢复默认外观]
位置与窗口
始终置顶 [Switch 开]
贴边吸附 [Switch 开]
提示
安静模式(不显示完成 / 失败气泡) [Switch 关]
```
- 总开关是本页唯一决定宠物窗口存在与否的控件,符合 `INV-3`;其余控件在总开关关闭时
保持可见但不可交互,说明文字保持“关闭后不在桌面上显示任何宠物窗口或提示”。
- 二元持久状态一律使用共享 Switch;角色选择使用共享 `SegmentedControl`(3-4 个内置
角色,符合数量上限);尺寸、透明度和动画强度使用标准滑块控件并显示当前取值。
- “恢复默认外观”只重置本区域三项外观设置,不改变总开关、位置或安静模式,与
[功能逻辑](./logic-design.md) 中“恢复默认外观”的范围一致。
- 页面不提供“立即显示 / 隐藏”按钮:暂时隐藏的入口固定放在托盘菜单,避免在设置页和
托盘中出现两个含义相近但状态不同步的控件。
## 2. 首次启用与关闭反馈
- 开启总开关后宠物窗口在默认位置出现,不产生任何应用通知或原生通知,因为这是用户刚
完成的显式操作,结果已经可见。
- 关闭总开关后宠物窗口和任何气泡立即消失;设置页本身不产生额外通知。
- 外观、位置相关 Switch 和滑块的变更立即生效并持久化,失败时通过应用通知报告,不静默
丢弃用户输入。
## 3. 托盘入口
- 托盘菜单在“新建对话”与“设置”之间新增一项,文案随当前可见性变化:
- 已启用且可见:“隐藏桌面宠物”。
- 已启用但隐藏:“显示桌面宠物”。
- 未启用:“显示桌面宠物”,点击后等同于在设置中开启总开关并显示窗口。
- 该菜单项不复制设置页的开关语义,只切换可见性;未启用状态下点击才会连带开启总开关,
行为需要在可访问名称或菜单说明中明确,避免用户误以为它只是显示/隐藏。
## 4. 宠物窗口
宠物窗口是无边框、透明背景、不可缩放的持久悬浮窗口,不使用 `PageShell`、不属于标准
一级页面,因此以下规则专属于该窗口:
```text
┌───────────┐
│ 角色 │ ← 状态镜面:角色外观随任务状态切换
│ (idle) │
└───────────┘
```
- 窗口本身没有标题栏、边框或系统窗口控件;关闭宠物只能通过设置总开关、托盘“隐藏桌面
宠物”或退出应用完成,窗口自身不提供关闭按钮,避免与“暂时隐藏”“关闭功能”两种含义
混淆。
- 窗口整体可拖动;拖动过程中显示当前吸附意图(开启贴边时,接近边缘会有轻微吸附反馈)。
- 窗口右键菜单提供“快速起草”“暂时隐藏”“打开设置”“退出 GoodBuddy”,与托盘菜单保持
同一套文案。
### 4.1 状态镜面外观
宠物角色在以下六种取值间切换,取值定义见[功能逻辑](./logic-design.md) 第 3 节:
| 状态 | 视觉表达 | 可访问名称 |
| --- | --- | --- |
| `unavailable` | 角色显示为暗淡的问号叠加 | “任务状态当前不可用” |
| `idle` | 角色静止或轻微待机动作 | “没有进行中的任务” |
| `running` | 角色显示持续的工作动作 | “有任务正在执行” |
| `waiting_approval` | 角色举牌或举手动作,带醒目但非警示色的强调 | “有任务等待你的确认” |
| `completed_recent` | 角色短暂展示完成动作后回到待机 | “有任务刚刚完成” |
| `failed_recent` | 角色显示明显区别于完成的姿态,并叠加警示色边缘 | “有任务失败,需要查看” |
- 状态表达必须同时使用动作/姿态和可访问名称两种信号,不能只依赖颜色区分
`completed_recent` 与 `failed_recent`。
- `prefers-reduced-motion` 或用户将动画强度设为“关闭”时,状态切换只改变静态姿态和颜色
强调,不播放位移或缩放动画。
- 点击状态镜面区域(角色本体)触发 US-D4:显示并聚焦主窗口,Task Center 定位到触发
当前状态的任务;`idle` 和 `unavailable` 状态下点击等同于显示并聚焦主窗口,不做定位。
### 4.2 快速起草弹出框
点击宠物打开的弹出框锚定在宠物窗口旁(自动避开屏幕边缘),结构如下:
```text
发送到:[当前会话名称 v]
[多行文本输入,占位文案“输入消息,或拖入文件…”]
[📎 附加文件] [已附加:contract.docx ×]
[取消] [提交]
```
- “发送到”是一个富信息单选菜单,选项为当前活跃会话与“新建对话”,遵循全局设计系统
第 6.11 节的单选选择器规则;默认选中当前活跃会话。
- 文本输入支持多行、`Ctrl+V` 粘贴图片和文字,与主窗口聊天输入框的粘贴行为一致。
- 附加文件按钮打开原生文件选择;已附加文件以行内标签形式展示,可单独移除。
- 拒绝的文件(超限或不支持)在附件区下方以就地错误文字展示,不使用应用级通知,因为
这是与当前操作直接绑定的字段级反馈。
- “提交”把文本和附件转交目标会话的输入区草稿,随后关闭弹出框;提交过程中禁用重复
点击并显示进行中状态,成功后不额外弹出通知(主窗口的显现和草稿本身就是反馈)。
- “取消”或点击弹出框外部或按 Escape 关闭弹出框并丢弃草稿,焦点返回宠物窗口。
- 弹出框打开时焦点进入文本输入;Tab 顺序为文本输入 → 附加文件 → 已附加文件的移除
按钮 → 取消 → 提交;关闭后焦点回到宠物窗口本体。
### 4.3 拖放反馈
- 文件或文字拖入宠物窗口范围时,窗口整体出现可放置的高亮边框和轻微放大反馈。
- 释放后立即按第 4.2 节“提交”的相同路径处理,不额外弹出确认框;这保持“拖动=起草并
发送到会话输入区(而非立即发送消息)”的一致语义。
- 拖入文件夹或全部文件超限时,宠物短暂显示拒绝反馈(边框变为警示色并伴随简短文字
提示,例如“不支持文件夹”),不打开主窗口、不产生暂存。
- 部分文件超限、部分合法时,合法文件正常处理,超限文件的名称通过一条会在数秒后自动
消失的就地提示列出,不使用应用级通知(拖放是即时操作,反馈应贴近操作发生的位置)。
### 4.4 完成 / 失败气泡
```text
┌─────────────────────────────┐
│ ✓ 已完成 │
│ 合同风险检查 │
│ [×] │
└─────────────────────────────┘
▲
(角色)
```
- 气泡锚定在宠物窗口旁,方向随宠物在屏幕上的位置自动避开边缘。
- 完成气泡在约 4.5 秒后自动消失,与全局应用通知的成功/信息时长一致;失败气泡保持可见
直到用户点击查看或点击关闭按钮。
- 气泡本体可点击,等同于点击状态镜面:显示并聚焦主窗口并定位到对应任务。
- 关闭按钮 `[×]` 有独立可访问名称(“关闭提示”),不与气泡主体的点击目标冲突:点击
关闭按钮只关闭气泡,不导航到主窗口。
- 气泡使用 `status`/polite 实时区域播报完成,失败气泡使用 `alert`/assertive,与全局
设计系统的通知语义保持一致,即使气泡本身不是应用通知视口的一部分。
## 5. 与应用通知视口的关系
- 宠物气泡是应用通知视口的窗口外等价物,只在主窗口不可见/未聚焦且安静模式关闭时出现;
同一事件不会同时进入应用通知视口和宠物气泡,具体归并规则见
[功能逻辑](./logic-design.md) 第 4 节。
- 快速起草和拖放产生的字段级错误(附件超限、不支持类型)始终就地展示在宠物窗口或弹出
框内,不进入应用通知视口,保持“就地错误”与“全局通知”分工一致。
## 6. 无障碍
- 状态镜面、气泡和弹出框内的所有交互元素都有持久可访问名称,不依赖占位文案代替标签。
- 宠物窗口可通过系统级窗口切换获得焦点;获得焦点后:
- 预期的激活键(例如 Enter 或 Space)打开快速起草弹出框。
- 弹出框内 Tab 顺序遵循第 4.2 节。
- Escape 关闭当前打开的弹出框或气泡,焦点回到宠物窗口本体。
- 状态镜面的状态变化通过 `status`/polite 实时区域播报简短说明,避免因动画本身无法被
屏幕阅读器感知而丢失信息;不对每次动画帧重复播报。
- 焦点环、危险色使用和触控目标尺寸遵循全局设计系统第 11 节的通用要求。
## 7. 响应式与多显示器
- 宠物窗口尺寸不随主窗口大小变化;用户通过设置中的“尺寸”滑块单独控制。
- 宠物窗口位置按显示器分别记忆;显示器不可用时的回退规则见
[功能逻辑](./logic-design.md) 第 7 节,界面表现为宠物出现在主显示器的默认可见位置,
不出现在屏幕之外或不可见状态。
- 弹出框和气泡的锚定方向根据宠物当前位置与屏幕边缘的距离自动选择展开方向(上/下/左/
右),确保始终完整可见,不被裁切到屏幕外。
## 8. 深浅主题
- 弹出框、气泡和设置页控件复用全局语义令牌,不新增页面专属颜色。
- 宠物角色素材本身分别提供适配浅色和深色桌面背景的版本,避免透明背景下角色轮廓在深色
桌面壁纸上难以辨认;具体资源交付方式属于技术设计范畴。
## 9. 固定词汇
使用“桌面宠物”“启用桌面宠物”“暂时隐藏”“快速起草”“安静模式”“状态镜面”,定义见
[README](./README.md) 术语表。不使用“挂件”“桌宠助手”或其他未在术语表中定义的别名,
保持中文文案统一。
+255
View File
@@ -0,0 +1,255 @@
# 桌面宠物 User Stories
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [桌面宠物 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
## 角色
- **日常用户**:在多任务、多显示器场景下使用 GoodBuddy 处理日常工作。
- **首次体验用户**:刚升级或安装 GoodBuddy,尚未接触过桌面宠物功能。
- **键盘用户**:主要通过键盘操作,可能同时使用屏幕阅读器。
- **多显示器用户**:使用两个或更多显示器,可能随时插拔外接显示器。
## US-A 首次启用
### US-A1 默认不出现
**Given** 用户全新安装或刚升级 GoodBuddy
**When** 用户尚未进入“桌面宠物”设置
**Then** 桌面上不出现任何宠物窗口,托盘菜单显示“显示桌面宠物”而非“隐藏”
### US-A2 首次启用
**Given** 用户在设置中打开“启用桌面宠物”总开关
**When** 开关切换为开启
**Then** 宠物窗口在默认位置出现,使用默认外观,且不产生任何应用级或原生通知
### US-A3 关闭后完全消失
**Given** 桌面宠物已启用并可见
**When** 用户关闭“启用桌面宠物”总开关
**Then** 宠物窗口立即消失,未完成的气泡提示一并消失,托盘菜单条目恢复为“显示桌面宠物”,
且用户之前设置的位置、外观和提示偏好被保留供下次开启时使用
## US-B 快速起草
### US-B1 起草文字并转入主窗口
**Given** 桌面宠物已启用,主窗口当前隐藏
**When** 用户点击宠物并输入一段文字后点击提交
**Then** 主窗口被显示并聚焦,目标会话的输入区出现刚输入的文字作为草稿,输入焦点落在
该输入区,且没有任何消息被发送、没有任何任务被创建
### US-B2 起草时附加文件
**Given** 快速起草弹出框已打开
**When** 用户点击“附加文件”并选择一个受支持类型和大小内的文件
**Then** 该文件在弹出框内显示为待提交附件;提交后该附件出现在主窗口对应会话输入区的
待发送附件列表中
### US-B3 附加不受支持的文件
**Given** 快速起草弹出框已打开
**When** 用户选择一个超出大小限制或类型不受支持的文件
**Then** 弹出框就地显示明确的拒绝原因,不添加该文件,且不影响已经添加的其他文字或附件
### US-B4 取消起草
**Given** 用户已在快速起草弹出框中输入文字或添加附件
**When** 用户点击取消或按 Escape 关闭弹出框
**Then** 弹出框关闭且草稿被丢弃,主窗口任何会话的现有草稿不受影响,且不创建任何草稿或
任务记录
### US-B5 选择目标会话
**Given** 主窗口当前存在多个会话,且用户在快速起草弹出框中看到默认目标会话
**When** 用户在弹出框内切换目标为“新建对话”
**Then** 提交后主窗口创建并激活一个新会话,草稿内容进入该新会话的输入区,不影响原有
会话的草稿
## US-C 拖放暂存
### US-C1 拖放单个文件
**Given** 桌面宠物已启用并可见
**When** 用户从文件管理器拖动一个受支持类型的文件到宠物窗口上并释放
**Then** 宠物在拖入过程中显示可放置的视觉反馈,释放后主窗口被显示并聚焦,该文件出现在
目标会话输入区的待发送附件列表中
### US-C2 拖放多个文件,部分超限
**Given** 用户同时拖动数量或大小超出限制的多个文件到宠物窗口
**When** 用户释放拖放
**Then** 合法文件正常进入目标会话的待发送附件列表,超限或不支持的文件被单独列出拒绝
原因,不阻止合法文件被暂存
### US-C3 拖放文件夹
**Given** 用户拖动一个文件夹到宠物窗口
**When** 用户释放拖放
**Then** 宠物提示不支持文件夹,不递归展开文件夹内容,也不产生任何暂存
### US-C4 拖放选中文字
**Given** 用户在其他应用中选中一段文字并拖动到宠物窗口
**When** 用户释放拖放
**Then** 该文字进入目标会话输入区的草稿文本,行为与快速起草输入文字一致
## US-D 状态镜面
### US-D1 查看空闲状态
**Given** 当前没有任何活跃 Task
**When** 用户查看宠物外观
**Then** 宠物显示空闲状态,且可访问名称明确说明当前没有进行中的任务
### US-D2 查看执行中状态
**Given** 有一个 Task 处于执行中
**When** 用户查看宠物外观
**Then** 宠物切换为执行中状态,且该状态与 Task Center 中该任务的状态一致
### US-D3 多任务状态优先级
**Given** 同时存在一个等待处理的任务和一个执行中的任务
**When** 用户查看宠物外观
**Then** 宠物按照“等待处理优先于执行中”的规则显示等待处理状态
### US-D4 点击状态镜面打开任务详情
**Given** 宠物当前显示执行中或失败状态
**When** 用户点击宠物的状态镜面区域
**Then** 主窗口被显示并聚焦,Task Center 定位到触发当前状态的具体任务
### US-D5 状态不可用
**Given** 用于确认任务状态的 Runtime 或网络当前不可用
**When** 用户查看宠物外观
**Then** 宠物显示“状态不可用”,不回退显示为空闲或执行中
## US-E 完成与失败提示
### US-E1 主窗口不可见时收到完成提示
**Given** 主窗口当前隐藏或未聚焦,安静模式未开启
**When** 一个任务从执行中变为已完成
**Then** 宠物旁出现说明任务标题和结果的气泡,且该事件不重复触发原生系统通知
### US-E2 失败提示保持可见
**Given** 主窗口当前隐藏,安静模式未开启
**When** 一个任务失败
**Then** 宠物旁出现失败气泡并保持可见,直到用户点击查看或手动关闭,不自动消失
### US-E3 主窗口可见时不重复提示
**Given** 主窗口当前可见并聚焦
**When** 一个任务完成或失败
**Then** 不出现宠物气泡;反馈通过主窗口现有机制呈现,避免同一事件重复出现在两个位置
### US-E4 开启安静模式
**Given** 用户在设置中开启“安静模式”
**When** 之后任意任务完成或失败,且主窗口不可见
**Then** 不出现任何宠物气泡,但状态镜面外观仍随任务状态正常变化
## US-F 位置、外观与多显示器
### US-F1 记忆位置
**Given** 用户把宠物拖动到某个显示器的特定位置
**When** 用户重启 GoodBuddy
**Then** 宠物出现在同一显示器的同一位置
### US-F2 显示器断开后回退
**Given** 宠物位置记忆在一个已插入的外接显示器上
**When** 该外接显示器被移除后重新启动 GoodBuddy
**Then** 宠物出现在当前可用的主显示器上一个可见、不越界的位置,而不是不可见或位于屏幕
之外
### US-F3 贴边吸附
**Given** 用户开启“贴边”选项
**When** 用户把宠物拖动到接近屏幕边缘的位置并释放
**Then** 宠物自动吸附到屏幕边缘,且吸附后仍可被再次拖动离开
### US-F4 调整外观
**Given** 用户在设置中调整宠物尺寸、透明度或动画强度
**When** 用户改变任意一项取值
**Then** 宠物外观立即按新取值更新,且设置持久化,重启后保持
### US-F5 恢复默认外观
**Given** 用户已经修改过角色、尺寸、透明度或动画强度
**When** 用户点击“恢复默认外观”
**Then** 上述设置全部恢复为默认值,宠物位置和总开关状态不受影响
## US-G 暂时隐藏与托盘
### US-G1 暂时隐藏
**Given** 桌面宠物已启用并可见
**When** 用户从托盘菜单选择“隐藏桌面宠物”
**Then** 宠物窗口消失,总开关状态和其他设置不变,托盘菜单条目变为“显示桌面宠物”
### US-G2 重新显示
**Given** 桌面宠物已启用但当前隐藏
**When** 用户从托盘菜单选择“显示桌面宠物”
**Then** 宠物窗口在此前记忆的位置重新出现
### US-G3 应用退出
**Given** 桌面宠物已启用并可见
**When** 用户退出 GoodBuddy
**Then** 宠物窗口一并关闭,不留下任何独立运行的进程或残留窗口
## US-H 键盘与无障碍
### US-H1 键盘打开快速起草
**Given** 桌面宠物窗口通过系统级窗口切换取得焦点
**When** 键盘用户按下预期的激活键
**Then** 快速起草弹出框打开,焦点进入输入框
### US-H2 键盘关闭弹出框
**Given** 快速起草弹出框已打开且有键盘焦点
**When** 用户按下 Escape
**Then** 弹出框关闭,焦点回到宠物窗口本身
### US-H3 屏幕阅读器状态说明
**Given** 用户使用屏幕阅读器
**When** 焦点移动到宠物的状态镜面
**Then** 屏幕阅读器播报当前状态的文字说明,不仅依赖颜色或动画表达状态
## US-I 兼容与边界
### US-I1 升级用户保持现状
**Given** 已在使用 GoodBuddy 且从未接触过桌面宠物功能的用户升级到包含该功能的版本
**When** 用户正常启动应用
**Then** 桌面宠物保持关闭,界面和行为与升级前一致
### US-I2 不支持透明合成的环境
**Given** 用户的操作系统或桌面环境不支持窗口透明合成
**When** 用户启用桌面宠物
**Then** 宠物窗口退化为不透明的小型窗口而不是渲染异常或无法显示,具体规则见
[技术设计](./technical-design.md)
### US-I3 拖放到非目标区域
**Given** 用户尝试把文件拖到宠物窗口之外的桌面区域
**When** 用户释放拖放
**Then** GoodBuddy 不接收该拖放,不产生任何暂存或错误提示
+28
View File
@@ -0,0 +1,28 @@
# 设备发现与共享
本功能是 GoodBuddy 桌面端管理附近设备、本机共享和 ShareServer 连接的唯一入口。默认连接
方式是局域网;网关由用户额外添加。功能不包含 ShareServer 的组织管理 Web 界面。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [PRD](./prd.md) | 用户问题、范围、功能需求和产品验收 |
| [功能逻辑](./logic-design.md) | 不变量、状态、授权优先级和失败结果 |
| [UI 设计](./ui-design.md) | 设置入口、四个页签、配对与调用交互 |
| [技术设计](./technical-design.md) | Desktop 进程边界、持久化、IPC、网络和验证 |
| [共享网络总体设计](../../architecture/share-network-architecture.md) | 跨端身份、能力、授权、任务、更新和协议权威 |
当前没有单独的 User Stories:正常、失败、撤销、权限和兼容场景已直接进入 PRD 验收与逻辑
决策表。功能尚未实施,因此不创建 `progress.md`。
## 术语
- **附近设备**:同一局域网内主动广播 GoodBuddy 共享服务的设备。
- **已配对设备**:双方确认设备指纹后建立点对点信任的设备。
- **网关连接**:本机与某个 ShareServer 中特定组织身份的连接。
- **本机共享**:由本机作为提供者发布给 Peer 或组织的能力。
- **远端能力**:由其他设备或组织提供、本机可以查看或调用的能力。
SSH 远程项目是用户明确配置 Host 后操作远端工作区的独立功能,不自动进入设备发现与共享,
详见[远程主机与远程执行](../remote-host/README.md)。
@@ -0,0 +1,202 @@
# 设备发现与共享功能逻辑设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [设备发现与共享 PRD](./prd.md) |
| 共享规则 | [共享网络总体设计](../../architecture/share-network-architecture.md) |
本文负责桌面端状态、不变量和决策规则,不重复跨端协议定义。
## 1. 核心不变量
### INV-1 连接方式独立
局域网、每个 GatewayConnection 和本机能力互不拥有生命周期。启停、失败或删除其中一项不
改写其他项。
### INV-2 三项操作独立
`discovering`、`advertising` 和 `publishing` 是三个独立状态。任何组合均合法,界面不得用
一个“设备共享”总开关代替。
### INV-3 默认不共享
新安装没有 Publication。允许被发现只公开配对入口,不公开能力清单。
### INV-4 身份先于能力
未配对局域网设备不能取得详细能力;未注册网关设备不能取得组织目录。缓存不能绕过当前
身份和撤销状态继续调用。
### INV-5 提供者在调用前冻结
自动选择只生成候选决定。用户确认或无需确认的请求创建后,provider、route 和授权快照不可
改变。失败后切换提供者属于新的请求。
### INV-6 最小发送上下文
只发送能力 schema 要求且用户当前选择的输入。当前 Conversation、Project 和 Workspace 不
因能力被调用而整体成为输入。
### INV-7 结果未知不重放
已经被提供者接受但终态无法确认的副作用任务进入 `outcome_unknown`。重新执行必须由用户
发起,并显示可能重复产生副作用。
### INV-8 撤销只影响未来授权
撤销配对、网关连接、Grant 或 Publication 后不再创建新任务。历史记录保留;活动任务按
冻结策略取消或完成,不能追改为从未发生。
### INV-9 设置导航无业务副作用
四个 PageTabs 只切换视图,不自动开始广播、连接网关、发布能力或保存草稿。
## 2. 状态维度
### 2.1 局域网服务
```text
discover: off | starting | active | unavailable | error
advertise: off | starting | active | unavailable | error
```
操作系统或网络不支持时使用 `unavailable`;运行失败使用 `error`。两者分别重试。
### 2.2 附近设备
```text
visible_unpaired -> pairing -> paired_online
pairing -> rejected | expired | failed
paired_online <-> paired_offline
paired_* -> identity_changed | revoked
```
`identity_changed` 不自动接受新证书。用户必须撤销旧配对后重新配对。
### 2.3 网关连接
```text
draft -> authenticating -> connecting -> connected
authenticating -> cancelled | auth_failed
connecting/connected -> offline | auth_expired | policy_blocked
offline -> connecting
任意已保存状态 -> removed
```
`offline` 保留连接和缓存目录供只读说明,但不能创建新任务;`auth_expired` 要求重新登录;
`policy_blocked` 显示服务端给出的稳定原因和管理员联系路径。
### 2.4 本机发布
```text
not_published -> draft -> validating -> published
validating -> invalid
published -> paused -> published
published/paused -> removed
```
草稿和无效发布不对外可见。能力实现消失时发布进入 `paused`,不删除用户配置。
### 2.5 远端任务
采用共享架构定义的状态。本机 UI 归并为:
| 协议状态 | 用户状态 |
| --- | --- |
| `created/awaiting_approval` | 等待确认 |
| `queued/connecting` | 正在连接 |
| `running` | 正在执行 |
| `completed` | 已完成 |
| `failed/rejected/expired` | 未完成,并显示具体原因 |
| `cancelled` | 已取消 |
| `outcome_unknown` | 结果未知 |
## 3. 默认状态
| 设置 | 全新安装默认值 |
| --- | --- |
| 发现附近设备 | 开启 |
| 允许其他设备发现本机 | 关闭 |
| 自动接受配对 | 不支持 |
| 本机共享能力 | 无 |
| 网关连接 | 无 |
| 永久调用 Grant | 无 |
主动发现默认开启只浏览 GoodBuddy 主动广播,不执行端口扫描。操作系统权限尚未授予时,首次
进入页面再触发对应系统说明和授权,不在应用启动时抢占焦点。
## 4. 提供者选择
自动选择按以下顺序比较满足当前输入和授权的在线候选:
1. 本机提供者。
2. 已配对局域网提供者。
3. 当前用户所属组织提供者。
4. 联邦外部组织提供者。
5. 同级中按服务端预计排队时间和稳定能力版本排序。
自动选择不跨越 `require_approval` 与 `allow`:若排名第一的候选需要批准,界面显示该候选及
批准要求,不静默换成权限更宽但来源更远的候选。用户可以明确选择其他候选。
## 5. 调用决策表
| 身份与能力状态 | 授权结果 | 行为 |
| --- | --- | --- |
| 身份有效、输入匹配 | allow | 创建任务并展示执行位置 |
| 身份有效、输入匹配 | require_approval | 显示完整确认后创建 |
| 身份有效 | deny | 不创建任务,显示策略原因 |
| 提供者离线 | 任意 | 不创建任务,允许重新选提供者 |
| 清单过期或版本变化 | 任意 | 刷新后重新计算,不沿用确认 |
| 文件超过能力或策略上限 | 任意 | 就地拒绝并显示上限 |
| 委托链与确认不一致 | 任意 | 拒绝并要求以新执行链重新确认 |
## 6. 撤销与删除
| 操作 | 删除内容 | 保留内容 |
| --- | --- | --- |
| 撤销 Peer | 配对凭据、该 Peer 的本地 Grant 和目录缓存 | 活动记录、已安装包 |
| 移除网关 | 本机设备凭据、连接配置、目录缓存 | 服务端账号、活动记录、已安装包 |
| 停止发布 | 对外 Publication | 能力实现、发布草稿、历史任务 |
| 删除发布 | Publication 和本地发布设置 | 能力实现、历史任务 |
| 删除已安装包 | GoodBuddy-owned 安装内容和分配 | 来源审计与任务历史 |
存在活动任务时,撤销操作明确询问是否同时发送取消;无论取消是否确认,立即禁止新任务。
## 7. 失败与恢复
- mDNS 不可用:显示手动地址配对,不关闭已配对直连。
- 广播失败:保留用户开关意图,状态为错误并允许重试。
- 网关离线:指数退避重连,用户手动“重新连接”可立即尝试一次。
- 登录取消:不保存连接。
- 目录刷新失败:保留最后缓存并标记时间,不允许基于过期授权启动新任务。
- 调用前连接失败:任务失败,可安全重新执行。
- 提供者接受后断线:恢复查询同一任务;仍无法确认时进入结果未知。
- 下载或签名失败:删除候选暂存,保留现有安装,不切换来源。
## 8. 需求追踪
| 需求 | 主要逻辑 |
| --- | --- |
| `FR-1`、`FR-2` | `INV-1`、`INV-2`、`INV-9` 和默认状态 |
| `FR-3`、`FR-4` | 身份不变量和附近设备状态机 |
| `FR-5` | 连接独立不变量、网关连接状态和删除规则 |
| `FR-6` | provider 冻结不变量和提供者选择顺序 |
| `FR-7` | 默认不共享不变量、本机发布状态和撤销规则 |
| `FR-8` | 最小发送上下文和调用决策表 |
| `FR-9` | 远端任务状态、结果未知不重放和失败恢复 |
| `FR-10` | 撤销与删除、下载或签名失败规则;包协议由共享架构定义 |
| `FR-11` | 撤销后历史保留和任务状态;展示行为由 UI 设计定义 |
| `FR-12`、`FR-13` | 独立状态维度和失败恢复 |
| `FR-14` | 身份先于能力、最小发送上下文和调用决策表 |
## 9. 逻辑完整性
本设计已覆盖发现、配对、连接、发布、调用、取消、撤销、离线、身份变化、权限变化和能力
更新。实现前仍需在共享协议中固定受限 JSON Schema 子集、端到端中继加密套件和桌面端可
注册为共享能力的首批实际类型;这些是技术选择,不改变本文状态和产品规则。
+160
View File
@@ -0,0 +1,160 @@
# 设备发现与共享 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 适用产品 | GoodBuddy Desktop |
| 关联架构 | [共享网络总体设计](../../architecture/share-network-architecture.md) |
| 关联文档 | [功能逻辑](./logic-design.md)、[UI 设计](./ui-design.md)、[技术设计](./technical-design.md) |
## 1. 背景
GoodBuddy 的能力当前主要在本机执行,SSH 远程项目则要求用户预先配置具体 Host。个人和组织
需要在不复制完整环境的情况下使用附近工作站、其他成员设备或组织节点上的模型、知识、硬件
和工具,也需要在经过确认后把可分发能力安装到本机。
该能力不能以“扫描局域网机器”的方式实现。产品只发现主动开放 GoodBuddy 服务的设备,
配对后才查看详细能力,授权后才发送数据或执行任务。
## 2. 产品目标
1. 用户无需配置服务器即可发现并配对同一局域网中的 GoodBuddy 设备。
2. 用户可以额外连接一个或多个 ShareServer,使用组织或合作组织提供的能力。
3. 用户能够逐项决定本机向哪些 Peer、用户组或组织开放什么能力。
4. 远程调用前、中、后都能确认提供者、执行位置、数据去向和授权依据。
5. 局域网、各网关连接和本机能力彼此独立,单点故障不阻塞其他来源。
6. 可分发能力必须在明确校验和批准后安装,不迁移对方凭据或机器配置。
## 3. 功能范围
### 3.1 包含
- 设置中的“设备发现与共享”一级分类。
- 局域网主动发现、选择、双端配对、重命名和撤销。
- 本机被发现开关和配对请求处理。
- ShareServer 添加、登录、连接状态、组织身份和移除。
- 远端能力目录、提供者选择、调用确认、进度、取消和结果。
- 本机能力逐项发布、范围、允许动作和确认策略。
- 签名能力包查看、下载、校验、安装和权限变化确认。
- 局域网与网关活动记录。
### 3.2 不包含
- 扫描未主动广播 GoodBuddy 服务的机器或端口。
- 在桌面端管理 ShareServer 成员、组织策略、联邦或服务器运维。
- 自动公开本机 Skill、MCP、知识库、模型、文件或 SSH Host。
- 同步 API Key、Cookie、系统环境变量、绝对路径或登录状态。
- 把现有 SSH Host 自动转换为共享设备。
- 无界远程 Shell、桌面控制或任意文件浏览接口。
- 在网关断开时把组织授权自动降级为局域网授权。
## 4. 功能需求
### FR-1 设置入口
- 设置中心增加“设备发现与共享”一级分类。
- 页面提供“局域网”“网关”“本机共享”“活动记录”四个同级页签。
- 普通入口默认打开“局域网”;定向入口可打开指定页签。
### FR-2 独立开关
- “发现附近设备”和“允许其他设备发现本机”使用两个独立 Switch。
- 任一开关变化不撤销既有配对、不改变网关连接、不开放能力。
- 新安装默认允许主动发现;默认不允许本机被发现,默认无共享能力。
### FR-3 附近设备
- 只展示当前收到有效主动广播的 GoodBuddy 设备。
- 未配对设备只展示配对所需的有限信息,不展示能力和资产详情。
- 列表区分附近未配对、已配对在线、已配对离线和身份变化。
- 用户可手动刷新,但持续发现不得因刷新造成列表闪烁或重复项。
### FR-4 双端配对
- 双方显示相同验证码、设备名称、系统类型和证书指纹。
- 只有双方在时限内确认才建立配对。
- 任一端拒绝、超时、身份变化或连接中断均不保存半配对关系。
- 任一端可以撤销;撤销不删除历史活动记录。
### FR-5 网关连接
- 用户可输入 HTTPS ShareServer 地址或打开受支持的邀请链接。
- 登录在系统浏览器中完成,设备凭据回到 Main 的安全存储。
- 连接条目显示服务名、组织、当前成员身份、状态和最近策略同步时间。
- 同一设备可连接多个网关;移除连接只删除本机凭据和缓存,不删除服务端账号。
### FR-6 能力目录
- 已配对 Peer 和已连接网关分别返回当前身份获准查看的能力。
- 同一能力有多个提供者时合并展示,并允许“自动选择”或明确选择提供者。
- 自动选择仅比较满足授权和输入要求的在线候选,依次选择本机、已配对局域网 Peer、
当前用户所属组织和联邦外部组织;同级候选再按预计等待时间和稳定能力版本排序。
- 选择结果必须在执行前冻结并展示,不能执行中静默更换提供者。
### FR-7 本机共享
- 只有显式注册为可共享的能力才能出现在本机共享列表。
- 每项能力分别设置目标范围、`discover/inspect/invoke/download` 动作、输入类型、大小、
并发、有效期和是否逐次确认。
- 关闭能力或目标范围后停止接受新任务,不伪装为删除能力本身。
- 知识库原文、工作区写入、进程执行和硬件控制需要独立的明确能力声明。
### FR-8 调用确认
- 首次或策略要求时,确认界面显示提供者、执行位置、接收方、输入内容类别、文件名和大小、
路由、中继可读性及下游委托。
- 用户可取消、仅本次允许,或在组织策略允许时按当前范围保存授权。
- 调用上下文不隐式包含其他会话消息、整个工作区或未选择的知识库内容。
### FR-9 执行状态
- 会话或任务活动持续显示远端能力名称、来源、状态、发送字节和停止入口。
- 支持排队、连接、执行、完成、失败、取消、过期和结果未知。
- 网络中断且副作用结果无法确认时显示“结果未知”,不自动重试。
### FR-10 能力包
- 展示来源、版本、发布者、平台、大小、签名、权限和依赖。
- 下载和安装经过共享架构规定的校验流程。
- 新版本扩大权限时必须重新批准。
- 安装到本机后按现有 Skill/MCP/Runtime 分配规则启用,不因来自可信组织自动取得全部 Runtime。
### FR-11 活动记录
- 统一记录本机发出、远端发入、局域网、网关和跨组织活动。
- 支持按方向、来源、能力、结果和时间筛选。
- 详情显示授权依据、执行链、数据类别、大小、版本和终态,不默认保存正文。
### FR-12 错误隔离
- 局域网发现失败不影响网关和本机能力。
- 一个网关认证或连接失败不影响其他网关。
- 一个提供者离线时不自动改发给另一个提供者;用户重新执行时再选择。
- 页面保留可修复配置和准确错误,不以空状态代替失败状态。
### FR-13 平台与网络
- 支持 Windows、macOS、Linux 的当前桌面目标架构。
- mDNS 被网络阻断时允许通过已知局域网地址发起配对,但仍执行相同身份确认。
- 所有公网 ShareServer 使用 HTTPS;不提供“忽略证书错误”。
### FR-14 与现有能力的边界
- 远端调用不能扩大 Ask/Execute、Workspace、MCP、Skill 和知识库现有权限。
- 本机提供任务时使用能力声明对应的 Runtime 边界。
- SSH 远程项目、本机工具环境和模型配置默认不向 Peer 或网关传播。
## 5. 产品验收
- 两台新设备能完成发现、双端配对、授权、调用、取消和撤销。
- 三个概念“主动发现、允许被发现、开放能力”在状态和界面上互不混淆。
- 不配置网关时局域网工作完整;任一网关故障不阻塞其他来源。
- 未配对或未授权主体无法查看详细能力。
- 远端调用前能准确列出实际发送的数据,执行中和历史中可追溯提供者。
- 结果未知不会被显示为失败后自动重试或成功。
- 权限扩大的能力更新不会沿用旧授权静默安装。
- 中英文、键盘、焦点、窄窗口和深浅主题符合统一设计系统。
@@ -0,0 +1,213 @@
# 设备发现与共享技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [设备发现与共享 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 共享协议 | [共享网络总体设计](../../architecture/share-network-architecture.md) |
## 1. 进程边界
```text
Renderer
-> 窄 Preload API
Main
-> DeviceSharingService
-> LanDiscoveryService
-> PeerPairingService
-> GatewayConnectionService
-> CapabilityCatalogService
-> CapabilityPublicationService
-> RemoteTaskService
-> PackageAcquisitionService
-> SharingAuditStore
-> 既有 Capability / Runtime / Skill / MCP 服务
```
Renderer 只取得脱敏快照和调用显式动作。设备私钥、Peer 证书、网关凭据、网络 socket、原始
任务令牌、任意文件读取和包缓存路径只存在于 Main。
## 2. 模块职责
| 模块 | 职责 |
| --- | --- |
| `LanDiscoveryService` | mDNS browse/advertise、手动地址握手、接口变化 |
| `PeerPairingService` | 临时握手、验证码、双端确认、配对凭据与撤销 |
| `GatewayConnectionService` | 浏览器登录、设备注册、WebSocket、重连和策略同步 |
| `CapabilityCatalogService` | 合并本机、Peer 和网关目录,保留来源身份 |
| `CapabilityPublicationService` | 校验本机可共享能力并向目标范围发布 |
| `RemoteTaskService` | 授权、确认、provider 冻结、传输、取消和终态 |
| `PackageAcquisitionService` | 固定来源下载、校验、安装协调和权限差异 |
| `SharingAuditStore` | 本机有界审计查询、筛选和清理 |
目录合并只生成 UI 视图,不能把不同 provider 的授权、版本和执行位置压成一个身份。
## 3. 共享契约
实现前建立独立 `packages/share-protocol`。Desktop 只从该包导入 Zod schema 和 TypeScript
类型。协议输入在网络边界和 IPC 边界分别验证,不能因已经过网络校验而跳过 Renderer 输入
校验。
建议导出:
```text
DeviceSummary
PairingChallenge
GatewayDescriptor
CapabilityManifest
Publication
GrantConstraint
AuthorizationDecision
RemoteTaskSnapshot
RemoteTaskEvent
PackageDescriptor
AuditEventSummary
ProtocolError
```
错误只向 Renderer 返回稳定 code、可本地化参数和可恢复动作,不转发远端堆栈、响应正文或
内部地址。
## 4. 持久化
建议在现有 Desktop SQLite 中增加:
```text
device_identity_metadata
peer_pairings
gateway_connections
capability_publications
local_grants
remote_tasks
sharing_audit_events
installed_shared_packages
```
- 私钥和 refresh credential 存系统安全存储;SQLite 只存引用和脱敏元数据。
- 附近广播、在线状态、配对验证码、短期任务令牌和目录候选只存在于内存。
- 网关能力目录可有界缓存,用于离线说明,不作为离线调用授权。
- 审计正文不落库,字段和保留上限在实现前固定。
- 数据库迁移只覆盖已发布 schema;未发布设计不预建兼容读取器。
## 5. 局域网网络实现
- 使用系统支持的 mDNS/DNS-SD 库 browse `_goodbuddy-share._tcp.local`。
- 广播绑定用户允许的网络接口;接口切换时重建监听,不保留旧地址为身份。
- 服务端口由 Main 持有,使用设备证书建立 TLS。
- 未配对握手仅暴露配对所需字段,并限制并发、频率、消息大小和有效时间。
- 手动地址只替代发现,不替代证书指纹与双端确认。
- 停止广播时关闭未完成配对监听;已配对活动连接按任务生命周期处理。
## 6. 网关连接
1. Main 校验 HTTPS URL,并取得服务公开描述。
2. 生成带 PKCE、随机 state 和设备挑战的系统浏览器登录请求。
3. 本机 loopback 或已注册应用链接只接收一次性回调 code。
4. Main 交换设备凭据,展示组织身份,用户确认后持久化。
5. Main 建立带设备身份的 WebSocket 出站连接。
6. 心跳更新在线状态;断线按有界指数退避重连。
服务器返回的跳转 URL 必须符合已取得服务描述和 HTTPS 限制。Renderer 不能提交认证头、设备
证书、WebSocket URL 或任意回调地址。
## 7. 本机能力适配
不是所有现有 Capability 都自动可共享。每种可共享类型需要显式 adapter:
```ts
interface ShareableCapabilityAdapter {
describe(): CapabilityManifest
validatePublication(input: PublicationDraft): ValidationResult
invoke(input: unknown, context: ProviderTaskContext): AsyncIterable<TaskEvent>
cancel(taskId: string): Promise<CancelResult>
}
```
adapter 负责把共享输入映射到既有生产服务,并保留原权限边界。首批类型在实现前根据真实
产品需求确定;未有 adapter 的 Skill、MCP、知识库和 Runtime 不出现在共享列表。
## 8. 远端调用生命周期
```text
Renderer 选择能力和输入
-> Main 解析候选并取得当前授权
-> 必要时返回确认描述
-> 用户批准
-> Main 冻结 task/provider/route/policy
-> 建立数据通道并流式发送
-> Provider 再校验并接受
-> Main 转发有界进度和结果
-> 终态与审计事务保存
```
每次任务使用稳定 `taskId` 和 attempt ID。Provider 未接受前的连接失败可以明确失败;已接受
后的断线先查询同一任务,不新建 attempt。无法恢复权威终态时保存 `outcome_unknown`。
附件从 Main 已授权的用户选择结果读取,按声明大小流式发送,不把任意本机路径交给远端。
接收成果先写 GoodBuddy-owned 暂存并校验,再通过现有 Artifact 边界进入产品。
## 9. IPC
Preload 只暴露按产品动作命名的方法,例如:
```text
sharing.getSnapshot
sharing.updateLanSettings
sharing.startPairing / confirmPairing / cancelPairing
sharing.revokePeer
sharing.addGateway / completeGatewayLogin / removeGateway
sharing.listCapabilities
sharing.savePublication / pausePublication / deletePublication
sharing.prepareInvocation / confirmInvocation / cancelTask
sharing.listAuditEvents / getAuditEvent
```
所有变更方法校验 trusted sender、严格 schema、对象当前 revision 和归属关系。不得暴露通用
HTTP、WebSocket、mDNS、证书、文件传输或数据库查询 IPC。
## 10. 生命周期与资源边界
- 应用启动按保存设置启动主动发现;不因保存了网关而阻塞窗口创建。
- 广播只有在用户开启后启动。
- 网关连接在后台恢复,认证过期只影响对应连接。
- 应用退出停止接收新任务,取消未接受任务,并有界通知活动 Provider 后关闭连接。
- 单个 Peer/网关/任务设置连接数、帧、字节、并发和重连上限。
- 大文件全程流式处理,不进入单个 Main `Buffer` 或 Renderer state。
## 11. 与现有模块集成
- `capability-service` 提供本机能力事实;共享 adapter 明确选择可公开子集。
- 已安装共享 Skill/MCP 继续使用现有配置、启停和 Runtime 分配,不建立第二套执行器。
- SSH Host 保持独立;除非未来提供专门 adapter 和用户发布,否则不进入目录。
- 远端 Task 的公开活动复用现有工具/任务展示语义,但保留 provider 和 route 字段。
- 应用更新源、模型下载源、Agent 下载源和工具下载源不因添加 ShareServer 自动改变。
## 12. 验证策略
### 12.1 自动化
- schema 边界、未知枚举和协议版本协商。
- 三个独立开关及默认值。
- mDNS 去重、接口变化、广播停止和手动地址。
- 配对双方确认、拒绝、超时、身份变化和撤销。
- 多网关隔离、认证过期、重连和移除。
- provider 排序、冻结、授权变化和失败不切换。
- 调用确认最小输入、附件上限、取消和结果未知。
- Publication 校验、暂停、撤销和 adapter 消失。
- 包签名、权限差异、来源冻结和失败保留旧安装。
- IPC sender、revision、归属和无通用网络/文件接口。
### 12.2 真实环境
- Windows、macOS、Linux 至少两种组合完成同网段发现和配对。
- mDNS 可用、被防火墙阻断、网络接口切换和设备离线场景。
- 真实 ShareServer 登录、设备出站连接、目录、调用和认证过期。
- 小型 JSON、文件流、取消、Provider 进程失败和网络中断。
- 确认日志、Renderer、诊断和审计中没有凭据或文件正文。
设计文档完成不代表功能实施或上述验证通过。
+211
View File
@@ -0,0 +1,211 @@
# 设备发现与共享 UI 设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [设备发现与共享 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 全局规范 | [GoodBuddy 统一界面设计系统](../../../UI-DESIGN.md) |
本文只定义桌面端界面。所有颜色、间距、字体、通知、Switch、PageTabs、焦点和响应式行为以
全局设计系统为准。
## 1. 信息架构
设置中心新增一级分类“设备发现与共享”,使用 `standard` PageShell:
```text
设备发现与共享
发现附近的 GoodBuddy,或连接组织网关使用和开放能力
[ 局域网 ] [ 网关 ] [ 本机共享 ] [ 活动记录 ]
```
四项使用共享 `PageTabs` 的 `segmented` 视觉变体。普通入口默认“局域网”;来自能力来源、
连接错误或审计入口的定向导航打开对应页签。页签选择不持久化为产品设置。
## 2. 局域网页签
页面顺序固定为本机发现状态、已配对设备、附近设备。
```text
局域网
发现附近设备 [Switch 开]
查看同一网络中主动开放的 GoodBuddy 设备
允许其他设备发现本机 [Switch 关]
设备名称:Jiang-PC [编辑]
已配对设备 [刷新]
名称 状态 能力 最近连接 操作
GPU-Workstation 在线 4 项 刚刚 ...
附近设备
Design-Mac 等待配对 [配对]
```
- 两个 Switch 必须独立成行,不能使用一个总开关。
- “允许被发现”开启后只显示广播状态和设备名,不暗示已经共享能力。
- 设备行采用紧凑列表,不使用大卡片墙。
- 已配对设备行可展开简要能力;完整信息进入设备详情抽屉。
- 离线设备保留在“已配对设备”,附近广播消失不删除配对。
### 2.1 配对对话框
```text
与 Design-Mac 配对
验证码 824 193
设备 Design-Mac · macOS
设备指纹 7A:31:...:D9
有效时间 01:42
请确认另一台设备显示相同信息。
[取消] [信息一致,确认配对]
```
双方尚未确认时显示独立进度。拒绝、超时和连接失败保留设备行并给出重新配对,不显示成功
通知。成功使用应用通知,焦点回到对应设备行。
### 2.2 设备详情
详情使用右侧抽屉,包含“概览”“能力”“授权”“活动”四个内部页签。未配对设备不能打开
能力和授权。撤销配对放在抽屉底部 danger zone,并说明活动任务和历史记录影响。
## 3. 网关页签
```text
网关 [添加网关]
研发组织网关
研发中心 · 成员
已连接 · 策略同步于 14:32
可用能力 18 项 · 本机发布 2 项 [...]
```
空状态说明网关是附加连接,局域网无需网关即可使用。主操作为“添加网关”。
### 3.1 添加网关
对话框只要求 HTTPS 地址,也可由邀请链接预填:
```text
网关地址
[https://share.example.com]
[取消] [继续登录]
```
地址通过 Main 验证后打开系统浏览器。应用内显示“等待浏览器登录”和取消入口,不嵌入第三方
登录页。回调成功后显示服务名、组织和账号,用户确认才保存连接。
网关详情抽屉包含连接、组织身份、可用能力、本机发布和诊断。移除连接明确说明只移除本机
连接,不删除组织账号或服务端设备记录。
## 4. 本机共享页签
页首状态摘要不能替代逐项开关:
```text
本机共享
已开放 2 项能力 · 局域网 1 项 · 网关 2 项
搜索能力 [范围筛选] [状态筛选]
文档转 Word [Switch 开]
范围:研发组织、GPU-Workstation
允许:查看、远程调用
确认:每次调用 [编辑]
```
能力关闭时保留配置摘要并标为“已暂停”。点击“编辑”打开共享设置对话框:
- 目标范围使用分组多选,不混合 Peer、网关组织和用户组。
- 允许动作使用 Checkbox。
- “每次确认”使用 Switch。
- 输入类型、大小、并发、时间段和有效期使用对应表单控件。
- 权限较高的能力在提交区显示具体影响,不用泛化风险等级。
- 提交按钮附近再次列出目标范围和允许动作。
能力尚未注册为可共享时不展示虚假的禁用行。列表为空时说明当前没有可共享能力,并提供前往
“能力与工具”的次操作。
## 5. 活动记录页签
使用 `dashboard` 所需的表格能力,但保持在设置 PageShell 内横向滚动:
```text
方向 能力 对端/来源 状态 时间
发出 图像生成 研发组织网关 已完成 14:36
收到 文档转 Word GPU-Workstation 正在执行 14:31
```
工具栏提供方向、来源、状态、时间和搜索。详情抽屉显示任务 ID、能力版本、授权依据、路由、
数据类型和大小、执行链、耗时、结果及取消/撤销入口。正文和文件内容默认不展示。
## 6. 能力选择与调用确认
远端能力进入现有能力目录时,在名称旁持续显示来源标签。多个提供者使用菜单选择:
```text
图像生成
提供者 [自动选择:GPU-Workstation v]
```
调用需要确认时使用对话框:
```text
调用“合同风险检查”
提供者 法务服务商
执行位置 外部组织
将发送 当前输入;contract.docx(1.8 MB)
连接方式 ShareServer 中继
网关可见正文 否
下游调用 无
[取消] [仅本次允许] [按此范围始终允许]
```
组织策略不允许保存 Grant 时不显示“始终允许”。按钮文字必须表达作用域,不能使用“确定”。
## 7. 执行活动
会话和任务中的远端工具活动至少显示:
```text
合同风险检查 · 法务服务商
正在执行 · 已发送 1 个文件(1.8 MB) [停止]
```
展开后显示路由、能力版本和执行链。跨组织来源使用文字“外部组织”,不能只靠颜色。进入结果
未知时显示持久警告和“查看活动详情”,不提供普通的自动重试按钮。
## 8. 状态与错误
- 首次为空、筛选无结果、加载失败、无权限和离线分别使用不同状态。
- 缓存目录必须显示“上次更新于”,且离线时不能显示为当前可用。
- 身份变化使用阻塞状态,唯一修复路径是撤销后重新配对。
- 网关认证过期在对应网关行提供“重新登录”。
- 发布字段错误就地显示;异步连接错误显示在对象行或详情中。
- 配对成功、网关保存和共享发布成功使用应用通知,不重复显示页内成功横幅。
## 9. 无障碍与响应式
- 四个页签使用 `tablist/tab/tabpanel`、方向键、Home、End 和可见焦点。
- 持久二元状态使用 `role="switch"`;范围和动作多选使用 Checkbox。
- 表格在窄窗口切换为保留同样字段的列表,不隐藏提供者、状态或执行位置。
- 指纹和长网关地址允许换行并提供复制按钮,不依靠水平裁切。
- 对话框关闭后焦点回到触发控件;浏览器登录返回后恢复到等待登录区域。
- 状态包含文字和图标,不以颜色作为唯一信号。
## 10. 固定词汇
使用“设备发现与共享”“局域网”“网关”“本机共享”“附近设备”“已配对设备”“允许其他
设备发现本机”“结果未知”“外部组织”。不使用“扫描机器”“公网共享”“永久信任整台设备”
或把 ShareServer 简称为“云端”。
@@ -0,0 +1,51 @@
# 直连模型 Agent 能力
本目录是 GoodBuddy“直连模型 Agent 能力”的唯一文档入口。本功能为直连文本模型补齐
跨平台进程执行和编程 Subagent,使其能够在 Execute 模式完成读取、修改、运行和验证组成的
编程闭环,同时避免向已经拥有原生 Shell 或 Agent 能力的 Runtime 重复注入工具。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [PRD](./prd.md) | 用户问题、产品范围、功能需求、非目标和产品验收 |
| [User Stories](./user-stories.md) | 本机开发、失败恢复、委派和 Runtime 边界场景 |
| [功能逻辑](./logic-design.md) | 能力矩阵、不变量、状态转换、继承规则和失败结果 |
| [UI 设计](./ui-design.md) | 设置能力目录、聊天活动、Subagent 状态和无障碍行为 |
| [技术设计](./technical-design.md) | Main 服务、工具契约、平台适配、Subagent、生命周期和测试 |
| [功能进度](./progress.md) | 当前实现事实、待实施阶段、阻塞项和验证证据 |
## 术语
| 术语 | 定义 |
| --- | --- |
| 直连模型 | Runtime provider 为 `model`、由 GoodBuddy 直接调用模型协议的文本模型 |
| 进程执行 | GoodBuddy 在当前执行空间启动一个前台 Shell 命令并返回有界结果 |
| 平台 Shell | Windows 上的 PowerShell,macOS/Linux 上的 Bash 或 POSIX Sh |
| 编程 Subagent | 由直连模型临时委派、使用直连模型 Runtime 执行一个有界子任务的执行者 |
| 父请求 | 发起进程调用或 Subagent 委派的当前直连模型请求 |
| 子任务 | 父请求内部的一项委派工作,对应 Job/Subjob 语义,不是新的顶层 Task |
Task、Job、Subjob、Run 和 Subagent 的对象关系以
[Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md) 为准。本功能不重新定义
这些对象。
## 功能边界
- `process_execute` 和 `subagent_delegate` 是 GoodBuddy 直连模型内置工具,不是外部 MCP。
- 直连文本模型的普通问答、工具轮次和上下文摘要会在尚无可见输出时,对瞬时网络错误或
单次请求超时最多自动重试 3 次。
- OpenCode、Continue 和 DeepSeek Harness 继续使用各自原生执行与委派能力。
- Ask 不允许进程执行;Subagent 若在 Ask 中使用,只能继承 Ask 的只读能力。
- Execute 表示用户授权当前执行空间账号的完整能力,不增加第二套工具审批或权限档位。
- 首版跨平台指桌面本机 Windows、macOS 和 Linux。托管 SSH 项目当前继续使用远端
OpenCode;未来若向远端直连模型开放本工具,命令必须由 Host Agent 执行,绝不能回退到
桌面本机。
## 相关功能
- [模型连接请求定制](../model-connections/README.md):定义连接级 Header/Body、合并优先级和 Runtime 支持矩阵。
- [本机工具环境](../local-tool-environment/README.md):为新进程提供 Node、Python 和 PATH。
- [助手工作栏与执行空间](../assistant-workbar/prd.md):定义本机/SSH 执行位置和活动归属。
- [远程主机与远程执行](../remote-host/README.md):定义当前托管 SSH Runtime 边界。
- [DeepSeek Harness Runtime](../deepseek-harness/README.md):已经拥有平台 Shell 的独立 Runtime。
@@ -0,0 +1,195 @@
# 直连模型 Agent 能力功能逻辑设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 关联 PRD | [PRD](./prd.md) |
| 关联故事 | [User Stories](./user-stories.md) |
## 1. 权威不变量
1. `process_execute` 和 `subagent_delegate` 只属于直连文本模型。
2. Ask 永远不能启动命令、写入工作区或创建 Execute Subagent。
3. 子级的项目、执行空间、工作模式、模型连接和能力范围只能继承或缩小。
4. 子级工具清单不包含 `subagent_delegate`,最大深度固定为 1。
5. 命令必须在父请求绑定的执行空间运行,禁止跨设备静默回退。
6. 一个 `process_execute` 调用只拥有自己创建的前台进程树,不能管理无关进程。
7. 非零退出码是命令结果;启动失败、输入无效和后端不可用才是工具失败。
8. 子任务是父请求内部 Job/Subjob,不创建顶层 Task 或独立 Conversation。
9. 父请求取消、Runtime 释放或应用退出会传播到全部活动子级和进程。
10. OpenCode、Continue 和 DeepSeek Harness 的原生能力不被本功能替换或包装。
11. 直连模型只在尚未显示文本或推理内容时自动重试瞬时网络错误或请求超时,最多重试
3 次;用户取消和已显示的部分输出禁止重放。
## 2. 状态维度
| 维度 | 值 |
| --- | --- |
| Runtime | `model`、`opencode`、`continue`、`deepseek-harness` |
| 工作模式 | `ask`、`execute` |
| 执行空间 | `local`、`ssh` |
| 本机 Shell | `available`、`unavailable` |
| Subagent 深度 | `0`、`1` |
| 父请求 | `active`、`cancelling`、`terminal` |
| 模型请求重试 | `initial`、`retry 1..3`、`terminal` |
## 3. 工具可见性决策表
| Runtime | 模式 | 执行空间 | `process_execute` | `subagent_delegate` |
| --- | --- | --- | --- | --- |
| `model` | Ask | 本机 | 隐藏 | 提供,只读继承 |
| `model` | Execute | 本机且 Shell 可用 | 提供 | 提供,Execute 继承 |
| `model` | Execute | 本机但 Shell 不可用 | 隐藏并报告诊断 | 提供,但子级同样没有进程工具 |
| `model` | Ask/Execute | SSH,远端后端未实现 | 隐藏 | 当前远端直连模型产品入口不开放 |
| 其他 Runtime | 任意 | 任意 | 隐藏 | 隐藏 |
工具清单隐藏不是最终授权边界。`callTool` 仍须重新验证 Runtime、工作模式、执行空间和父请求
是否活动,以拒绝旧快照或伪造调用。
## 4. 进程状态机
```text
validating
├─ invalid → failed
└─ valid → starting
├─ spawn_failed → failed
└─ running
├─ exit 0 → completed
├─ exit nonzero → completed_nonzero
├─ timeout → terminating → timed_out
├─ parent cancel → terminating → cancelled
└─ output/lifecycle failure → terminating → failed
```
规则:
- `completed_nonzero` 仍产生正常工具结果。
- 超时和取消结果包含已经取得的有界输出。
- 进入 `terminating` 后不再接受新输入,也不启动替代命令。
- 终止只针对本次调用记录的进程树。
- 迟到的 `close` 只用于清理,不得覆盖已提交的取消或超时原因。
## 5. 工作目录决策
| 输入 | 结果 |
| --- | --- |
| 未提供 | 当前工作区根目录 |
| `.` 或相对路径 | 以当前工作区为基准解析,可指向工作区外目录 |
| 绝对路径 | 使用指定目录 |
| `..` 或指向工作区外的符号链接 | 解析到实际目标目录,按当前用户权限执行 |
| 不存在或不是目录 | 拒绝 |
| 当前工作区身份在验证期间变化 | 拒绝本次调用 |
工作区提供默认目录和相对路径基准,不是 Execute 命令的文件系统边界。单次命令选择其他
工作目录不会修改父请求绑定的项目或工作区,也不会影响后续未指定 `cwd` 的命令。
## 6. Shell 选择
| 平台 | 顺序 | 不可用结果 |
| --- | --- | --- |
| Windows | `pwsh` → `powershell.exe` | 进程工具不注册,设置诊断显示 PowerShell 不可用 |
| macOS | `/bin/bash` → `/bin/sh` | 两者都不可用时不注册 |
| Linux | `/bin/bash` → `/bin/sh` | 两者都不可用时不注册 |
一次父请求冻结实际 Shell 描述。PATH 或工具环境设置变化只影响新创建的 Runtime/执行服务,
不在运行中的命令或 Subagent 中途切换。
## 7. Subagent 状态机
```text
validating
├─ invalid/depth_exceeded → failed
└─ queued
├─ parent_cancelled → cancelled
└─ running
├─ model done → completed
├─ child failure → failed_with_partial_output
├─ timeout → cancelled
└─ parent cancel → cancelled
```
父模型收到结构化终态:
```ts
type SubagentDelegateResult = {
status: 'completed' | 'failed' | 'cancelled'
output: string
error?: string
modelUsage?: {
inputTokens?: number
outputTokens?: number
}
}
```
失败和取消是父模型可以解释的子任务结果。只有契约损坏、未知子任务身份或服务已经释放等
内部不变量失败才终止父工具循环。
## 8. 权限与能力继承
| 父请求 | 子级模式 | 子级能力 |
| --- | --- | --- |
| Ask | Ask | 父级只读工具的子集,不含进程、写入和再次委派 |
| Execute | Execute | 父级已启用工具的子集,包含本机进程能力,不含再次委派 |
- 子级不能请求更高模式。
- 子级使用父请求开始时的工具与能力快照;运行中设置变化不重写快照。
- 子级调用 MCP、浏览器或知识能力时继续经过这些能力已有的作用域和生命周期边界。
- Execute 子级不弹出额外的“Subagent 执行授权”,也不获得父级没有的 MCP 分配。
## 9. 并发和顺序
- 全局编程 Subagent 最大并发为 3,额外请求进入有界队列。
- 单个父模型的工具调用仍按直连模型现有顺序结算,不为本功能改写全部工具并发语义。
- 不同父请求的 Subagent 可以并发;共享工作区的并发写入属于用户已授权的 Execute 行为,
活动记录必须保留实际顺序和结果。
- 首版不自动创建工作树、锁文件或回滚层。出现真实冲突时由父模型读取当前工作区事实并处理。
## 10. 结果优先级
当多个终止原因竞争时,使用以下优先级:
```text
用户/父请求取消 > 工具超时 > 输出或协议边界 > 进程退出 > 普通工具错误
```
已经提交的正常终态不被稍后到达的取消覆盖。父请求进入终态后,迟到的子级文本和工具事件
只用于资源清理,不再写入会话。
## 11. 模型请求重试
```text
attempt
├─ success → completed
├─ cancel → cancelled
├─ non-retryable failure → failed
├─ transient failure after visible output → failed_with_partial_output
└─ transient failure before visible output
├─ retry count < 3 → backoff → attempt
└─ retry count = 3 → failed
```
- 瞬时失败包括常见连接重置、连接/网络不可达、DNS 临时失败、连接管道中断和请求超时。
- 三次重试分别等待 500 ms、1 s 和 2 s;每次尝试重新建立请求并使用独立请求超时。
- 普通问答、工具模型轮次和上下文摘要使用同一规则。
- 重试不重复已经执行的工具;工具只会在模型完整返回一轮工具调用后开始执行。
- 退避等待绑定父请求取消信号,取消优先于启动下一次尝试。
## 12. 逻辑完整性评估
| 领域 | 结论 |
| --- | --- |
| Runtime 隔离 | 完整,工具只注入 `model` |
| Ask/Execute | 完整,清单过滤和调用边界双重检查 |
| 本机三平台 | 完整,统一契约和平台 Shell 决策已定义 |
| Subagent 递归 | 完整,深度固定为 1 |
| 取消与失败 | 完整,父子和进程传播及优先级已定义 |
| 模型网络恢复 | 完整,有限重试、输出重放和取消边界已定义 |
| 产品对象 | 完整,遵循 Task/Job 权威模型 |
| SSH 远端执行 | 明确不在首版;未来必须增加 Agent 后端后才能开放 |
当前没有需要在实施前由用户补充的产品决策。
+198
View File
@@ -0,0 +1,198 @@
# 直连模型 Agent 能力 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,跨平台 CI 待验证 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 关联领域 | [Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md) |
## 1. 背景
GoodBuddy 直连模型已经可以读取、列出和写入工作区文本,使用浏览器、联网搜索、知识库及
分配的 MCP,但没有内置命令执行工具。模型可以生成代码,却不能运行构建、测试、格式化、
Git 或项目脚本,因此无法独立验证结果。
本功能设计时,GoodBuddy 已有面向用户专家协作的只读 Subagent,但直连模型尚不能在工具
循环中主动委派编程子任务。当前专家协作已继承父请求 Ask/Execute 模式并使用本机直连模型
工具,与本功能的 `subagent_delegate` 仍为不同入口。OpenCode、Continue 和 DeepSeek Harness 一般已经提供
Shell、Agent 或 Task 能力,再向它们注入 GoodBuddy 同名工具只会造成重复入口和语义冲突。
本功能只补齐直连模型缺少的基础 Agent 能力:
1. 一个按平台适配的进程执行接口。
2. 一个单层、继承父请求范围的编程 Subagent 接口。
## 2. 产品目标
1. 让直连模型在 Execute 模式完成“读取代码 → 修改 → 运行 → 根据结果修正”的闭环。
2. 在 Windows、macOS 和 Linux 使用同一个模型工具契约,不要求模型调用平台专属工具名。
3. 让直连模型可以把独立工作委派给临时 Subagent,并取得有界、可核验的结果。
4. 保持 Ask 只读、Execute 完整授权、执行空间、取消、有界输出和进程回收语义不变。
5. 不向已有原生执行能力的 Agent Runtime 重复注入 GoodBuddy 进程或 Subagent 工具。
## 3. 核心产品决策
### 3.1 统一工具名,平台适配留在 Main
模型只看到 `process_execute`。GoodBuddy 根据当前平台选择 PowerShell、Bash 或 Sh,并在
工具描述和结果中明确实际 Shell。模型不需要在 Windows 调用 `powershell_execute`、在
macOS/Linux 改用 `bash_execute`。
### 3.2 它是内置能力,不依赖 MCP
进程生命周期、工作目录、取消和执行空间由 GoodBuddy 管理。用户仍可以配置提供其他命令
能力的 MCP,但直连模型的基本编程能力不依赖第三方 MCP 是否安装或在线。
### 3.3 Runtime 能力不重复注入
| Runtime | GoodBuddy `process_execute` | GoodBuddy `subagent_delegate` |
| --- | --- | --- |
| 直连模型 `model` | Execute 提供 | Ask/Execute 提供 |
| OpenCode | 不提供 | 不提供 |
| Continue | 不提供 | 不提供 |
| DeepSeek Harness | 不提供 | 不提供 |
“不提供”只表示不注入本功能工具,不限制 Runtime 自己的 Bash、PowerShell、Task、
Agent 或插件能力。
### 3.4 Subagent 继承,不扩权
Subagent 使用父请求的项目、执行空间、工作模式、直连模型连接和已启用能力快照:
- Ask 父请求只能创建 Ask Subagent。
- Execute 父请求创建 Execute Subagent。
- 子级不能切换父请求绑定的项目、工作区、Runtime、模型连接或工作模式;Execute 命令的
单次工作目录选择遵循 FR-3。
- 子级不再看到 `subagent_delegate`,首版最大委派深度固定为 1。
- 子任务不是新的顶层 Task 或 Conversation,结果先返回父模型。
### 3.5 不新增权限档位
Ask 禁止命令和写入。Execute 是用户对当前执行空间账号的完整授权。进程和 Subagent 不增加
“受控执行”“仅测试”“可信命令”或逐命令风险等级,也不重复弹出第二套确认。
## 4. 功能范围
### 4.1 包含
- 本机 Windows、macOS、Linux 的前台 Shell 命令执行。
- 当前工作区默认工作目录,以及当前用户可访问的本机工作目录选择。
- Exit Code、标准输出、标准错误、Shell、目录、耗时和截断状态。
- 命令超时、用户取消、进程树停止和应用退出清理。
- 新进程使用当前本机工具环境提供的 Node、Python 和 PATH。
- 直连模型单层 Subagent 委派、排队、运行、部分结果、失败和取消。
- 父子请求的工具、模型用量、活动和成果归属。
- 桌面聊天和选择直连模型的消息通道项目使用相同 Runtime 能力矩阵。
### 4.2 不包含
- 交互式 PTY、终端复用、持续 stdin、密码提示或 TUI 自动化。
- 后台守护进程、任意 PID 枚举、接管或停止用户已有进程。
- Shell Session 持久化,环境变更不会跨 `process_execute` 调用保留。
- 首版远程 SSH 直连模型进程执行。
- 向 OpenCode、Continue 或 DeepSeek Harness 注入本功能工具。
- 递归 Subagent、长期运行的自治 Agent、Workflow、Hook 或恢复未完成子任务。
- 新的权限矩阵、逐命令批准、沙箱档位或命令白名单。
- 自动安装项目依赖;模型可以在 Execute 中显式运行项目已有的安装命令。
## 5. 功能需求
### FR-1 直连模型专属可用性
- 两个工具只由 Runtime provider `model` 注册。
- Agent Runtime 切换为 OpenCode、Continue 或 DeepSeek Harness 后不再显示这两个工具。
- 图像生成连接不获得文本 Agent 工具。
- 能力不可用时返回准确原因,不以其他 Runtime 或本机 Shell 静默代替。
### FR-2 跨平台进程执行
- 使用统一的 `process_execute` 工具契约。
- Windows 选择 PowerShell;macOS/Linux 选择 Bash,Bash 不可用时可使用 POSIX Sh。
- 每次调用启动一个前台进程树,完成后返回结构化结果。
- 非零退出码是可观察的命令结果,不自动转成模型请求失败。
- Shell 启动失败、非法参数或执行后端不可用才属于工具失败。
### FR-3 工作目录和执行位置
- 默认目录是父请求当前工作区。
- 可选工作目录支持绝对路径、相对工作区路径及指向工作区外目录的路径或符号链接,
不额外限制在工作区内;目标必须是实际存在且当前用户可访问的目录。
- 本机执行只能发生在当前桌面执行空间。
- 当前请求绑定 SSH 执行空间且没有远端进程后端时不注册工具,绝不在桌面目录回退执行。
### FR-4 Ask/Execute
- Ask 工具清单不包含 `process_execute`。
- Runtime 边界必须再次拒绝 Ask 发起的伪造命令调用。
- Execute 命令使用 GoodBuddy 客户端当前用户权限,不声称 Runtime OS 沙箱。
- 消息通道的直连模型 Execute 继续服从该通道既有工具策略。
### FR-5 Subagent 委派
- `subagent_delegate` 接受一个清晰的任务说明,不要求用户预先创建专家。
- 子级默认使用父请求选择的直连模型连接。
- 子级继承父请求的工作模式和能力上限,但不获得再次委派能力。
- 子级最终输出、部分输出、错误、用量和状态返回父请求。
- 父模型负责综合结果并向用户作最终回答。
### FR-6 Subagent 领域归属
- 子任务使用 Job/Subjob 语义,不创建 Task Center 条目或独立 Conversation。
- UI 对象层级继续止于用户 Task;活动可以显示 Subagent 状态卡,但不显示 Job/Run 树。
- 子任务产生的工具、成果和用量可追溯到父请求及子任务标识。
### FR-7 生命周期和资源边界
- 父请求取消必须传播到活动 Subagent 和其当前进程。
- Runtime 替换、会话释放和应用退出必须停止 GoodBuddy 创建的活动子进程。
- 命令、标准输出、标准错误、Subagent 提示、结果、队列和并发全部有界。
- 截断必须明确标记,不能把不完整输出伪装为完整结果。
### FR-8 环境和凭据
- 新进程使用筛选后的当前用户环境和本机工具环境 PATH。
- 模型 Provider 凭据、GoodBuddy 加密设置和无关桌面会话变量不进入命令环境。
- 首版不接受模型提交任意环境变量映射;需要的普通变量由 Shell 命令在单次调用中设置。
### FR-9 状态与活动
- 进程活动显示等待、运行、成功、非零退出、超时、取消和失败。
- Subagent 显示排队、运行、完成、失败和取消。
- 命令和子任务失败保留可操作错误及已有部分输出。
- 同一事件不同时显示页内错误和应用通知。
### FR-10 兼容
- 现有模型连接、MCP 分配、专家设置和 Runtime 选择无需迁移。
- 不改变现有 Agent Runtime 的工具清单或权限语义。
- 不修改系统 PATH、Shell Profile 或用户终端会话。
### FR-11 模型请求重试
- 直连文本模型的普通问答、工具轮次和上下文摘要遇到瞬时网络错误或单次请求超时时,
在尚未显示文本或推理内容的前提下最多自动重试 3 次。
- 重试使用递增等待时间并显示当前次数;用户取消必须立即终止等待和后续请求。
- 一旦本次模型调用已经显示文本或推理内容,不自动重放该调用,避免重复回答。
- 非网络类 Provider 错误、协议错误和用户取消不进入自动重试。
## 6. 产品验收
- [ ] Windows 直连模型 Execute 使用 PowerShell 创建文件、运行测试并读取退出码。
- [ ] macOS/Linux 直连模型 Execute 使用 Bash/Sh 完成同一工作流。
- [ ] Ask 中模型看不到进程工具,伪造调用也在 Main 边界失败。
- [ ] 直连模型可以委派编程子任务,子级运行命令并把结果返回父模型。
- [ ] Ask Subagent 保持只读;Execute Subagent 不增加第二套批准。
- [ ] Subagent 不能递归委派,不能切换父请求工作区、模式或模型。
- [ ] 切换到 OpenCode、Continue 或 DeepSeek Harness 后不出现本功能工具。
- [ ] 非零退出、超时、取消和输出截断均准确展示。
- [ ] 取消父请求和退出应用后没有残留 GoodBuddy 命令进程。
- [ ] 本机工具环境选择对新命令生效,Main 持有的模型凭据不进入子进程。
- [ ] 子任务不进入 Task Center,不创建独立可导航 Conversation。
- [ ] TLS 建连重置和请求超时在未输出内容时最多自动重试 3 次,成功后继续原请求。
- [ ] 重试等待可取消,已显示部分内容的失败不自动重放。
- [ ] 全量测试、类型检查、Lint 和生产构建通过。
@@ -0,0 +1,168 @@
# 直连模型 Agent 能力功能进度
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档类型 | 功能进度 |
| 状态 | 已实施,本机验收完成 |
| 版本 | 0.4 |
| 日期 | 2026-09-05 |
| 关联入口 | [直连模型 Agent 能力](./README.md) |
## 当前结论
本机生产路径已经完成源码接线:直连模型 Execute 可运行平台 Shell,Ask/Execute 均可按
父模式委派单层编程 Subagent;OpenCode、Continue、DeepSeek Harness 和托管 SSH 路径不注入
这两个工具。Windows 本机、真实模型和全量项目验证已通过;macOS 与 Linux 真机验证仍需
由对应平台完成。
## 已确认的当前基线
- [x] 直连模型当前内置工作区读、目录列表和文本写入,但没有进程工具。
- [x] 内置 MCP 当前没有 Shell;自定义 MCP 不能替代开箱即用的进程能力。
- [x] `ModelAgentRuntime` 已有多轮工具循环、取消、上下文压缩和有界结果。
- [x] DeepSeek Harness 已按 Windows PowerShell、macOS/Linux Bash 注册原生 Shell。
- [x] OpenCode 和 Continue 使用各自原生执行工具。
- [x] 专家协作与直连模型可调用的编程工具是不同入口;当前专家继承父请求 Ask/Execute,
使用已启用本机直连模型工具并记录工具活动。模式与 authorizer 传递已由
`subagent-service.test.ts` 覆盖,不把该单元测试作为额外的真实模型或远程执行验收。
- [x] Task/Job 领域文档已经明确 Subagent 不是新的顶层 Task 或 Conversation。
- [x] 当前跨平台进程树清理已经覆盖 Windows `taskkill /T /F` 和 POSIX 进程组。
## 已完成设计
- [x] 工具只注入直连模型,不注入 Agent Runtime。
- [x] 统一 `process_execute` 契约和三平台 Shell 决策。
- [x] Ask/Execute、执行空间、工作目录和非零退出规则。
- [x] 输出、超时、取消、环境和进程树边界。
- [x] 单层 `subagent_delegate`、继承规则、并发和结果模型。
- [x] 不创建 Child Task/独立 Conversation 的领域归属。
- [x] 设置能力目录、聊天活动、错误和无障碍行为。
- [x] 本机、真实模型和项目验证方案。
## 实施状态
### 阶段 1:进程服务
- [x] Shared 工具和 schema。
- [x] 本机 Shell 探测与能力状态。
- [x] 工作目录解析。
- [x] 跨平台前台进程、输出边界、超时和取消。
- [x] 当前筛选 PATH 和凭据环境过滤。
### 阶段 2:直连模型接入
- [x] `ModelToolProvider` 注册与调用。
- [x] Ask/Execute、Runtime target 和本机执行空间双重检查。
- [x] DeepSeek Harness、OpenCode 和 Continue 隔离。
- [x] 通用工具活动和“设置 > MCP > 直连模型”编程能力目录。
- [x] 普通问答、工具轮次和上下文摘要的瞬时网络错误及请求超时最多自动重试 3 次。
- [x] 递增退避、重试状态、取消传播和已显示输出不重放。
### 阶段 3:编程 Subagent
- [x] 编程 Subagent 服务和同 Provider 深度过滤。
- [x] 父子模式、模型、执行空间和能力继承。
- [x] 深度、全局并发、队列、超时和取消。
- [x] 新 Subagent actor 契约和旧专家事件兼容读取。
- [x] 用量、部分输出、实时活动和终态归并。
### 阶段 4:端到端验收
- [x] Windows PowerShell 真实命令。
- [ ] macOS Bash/Sh 真实命令。
- [ ] Linux x64/arm64 Bash/Sh 真实命令。
- [x] 直连模型“修改 → 测试 → 修复”真实闭环。
- [x] 假模型端到端 Subagent 编程委派、真实本机命令和父模型综合。
- [x] 真实模型 Subagent 编程委派和父模型综合。
- [x] Runtime 隔离、取消、会话释放和进程树回收聚焦测试。
- [x] 全量测试、类型检查、Lint 和生产构建。
## 当前阻塞项
- 当前没有实现阻塞项。
- 本机工具环境仍处于计划阶段;在其实施前,进程工具可以使用当前筛选 PATH,但不能宣称
GoodBuddy 托管 Node/Python 已可用。
- 远端直连模型进程执行不在首版,后续需要独立增加 Agent 进程协议和真机验证。
## 2026-09-06 命令工作目录限制修复
- FR-3 / US-A3:移除命令工作目录的工作区范围限制。绝对路径、`..` 和指向工作区外
的符号链接均按本机文件系统解析,工作区只作为默认目录和相对路径基准。
- 保留目录存在性、本机执行空间绑定、Ask 拒绝命令、取消、超时和进程树回收。
本次没有改变工作区文件读写工具或远端 Agent 路径。
- `direct-model-process-service.test.ts` 覆盖绝对路径、相对路径、外部目录、符号链接、
不存在的目录和普通文件;当前 Windows 环境通过。
- `model-runtime.test.ts` 新增两个生产工具链回归:模型协议响应提供绝对或相对外部
`cwd`,经 `ModelAgentRuntime → ModelToolProvider → LocalDirectModelProcessService`
启动真实 PowerShell,在专用工作区外测试目录复制文件并读回验证。模型响应为本机
测试替身,没有外部模型调用;不将此记录为真实模型或 macOS/Linux 真机验收。
- 最终聚焦验证:`opencode-runtime`、`opencode-runtime-lifecycle`、
`direct-model-process-service`、`model-tool-provider`、`model-runtime` 五个测试文件
`182 passed, 1 skipped`;`npm run typecheck` 和 `npm run lint` 通过。
- 本次共享工作区的全量 `npm test` 为 `3542 passed, 57 skipped, 1 failed`。唯一失败是
`App.test.tsx` 的 `opens the global assistant sidebar and switches work tabs`,
仍期望“项目工作区”标题,单独复测同样失败;该侧栏处于其他会话的修改范围,本次未改动。
因此本次全量验证不能记录为全部通过。
## 2026-09-01 至 2026-09-02 实施与验证证据
- 新增 `direct-model-process-service.ts`:PowerShell/Bash/Sh 探测、工作区目录校验、96 KiB
首尾输出、超时、取消、进程树回收和环境过滤。
- 新增 `direct-model-subagent-service.ts`:单层委派、共享并发 3、队列 20、10 分钟上限、
192 KiB 输出、父请求取消和临时会话释放。
- `ModelToolProvider` 只在直连模型上下文注册工具;Ask 隐藏进程工具,深度 1 隐藏委派工具,
远端工作区拒绝本机进程。
- `ModelAgentRuntime` 复用同一已解析模型和 Tool Provider 运行子级,并把子级工具、用量和
Subagent 活动实时归并到父请求。
- 新 actor 联合类型兼容既有专家/OpenCode 记录;编程 Subagent 不创建 Assistant Task。
- 聚焦验证:
- 进程、Provider、Runtime、共享契约与聊天 UI:`290 passed, 1 skipped`。
- 设置与 Runtime 隔离:`216 passed`。
- Main/Shared 聚焦持久化与 Subagent:`188 passed, 1 skipped`。
- Node 与 Web TypeScript no-emit 检查通过。
- 假模型端到端场景实际启动 Windows PowerShell,子级运行命令后返回父模型;该场景的
Runtime 模型调用数为 4 次。
- 真实 DeepSeek 兼容连接端到端通过:模型创建失败程序、观察非零退出、修复并观察退出码
0、委派编程 Subagent 再次运行最终程序并综合结果;最终源码成功验收运行包含 8 次真实
模型调用。
- BigToken 连接的首次验收在 TLS 建立前被 `ECONNRESET` 中断,未进入模型工具路径;切换
到仓库已有的 DeepSeek 连接后完成验证。2026-09-03 已针对该故障补充统一模型请求重试。
## 2026-09-03 模型请求重试验证证据
- `ModelAgentRuntime` 对普通问答、工具轮次和上下文摘要统一处理瞬时 Node/Undici 网络错误
与请求超时;首次失败后最多重试 3 次,退避为 500 ms、1 s、2 s。
- 聚焦测试覆盖嵌套 `fetch failed` → `ECONNRESET` 恢复、工具轮次恢复、四次尝试后终止、
退避取消,以及已显示部分流式输出时不重放。
- `npm test -- --run src/main/agent/model-runtime.test.ts`:
`72 passed, 1 skipped`。
- 全量 `npm test`:`3484 passed, 51 skipped`。
- `npm run typecheck`、`npm run lint` 和 `npm run build`:通过。
- 最小真实直连文本模型验证:`1 passed, 13 skipped`,实际模型调用 1 次。
- 全量 `npm test`:`3410 passed, 50 skipped`。
- `npm run typecheck`:通过。
- `npm run lint`:通过。
- `npm run build`:Main、Preload、Renderer 和控制面安装器均成功构建。2026-09-02 按产品
决策移除未对应实际性能问题的 Renderer bundle 基线门禁后复验通过。
## 2026-09-05 移除直连模型响应字节上限
- 直连模型不再对流式响应总量、单个流式块、非流式回复、模型工具参数、工具请求上下文、
聚合工具结果和累计回答长度设置字节上限;长回复和大工具结果由请求超时、取消和上下文
压缩控制。用户不再看到“模型接口流式响应超过安全限制”。
- 图像生成响应仍按可解码图片大小校验,Provider 错误响应仍读取有界文本。
- 回归测试改为验证超过旧上限的长流式回复可以完整返回,以及超过 1 MB 的图片工具结果会
进入下一轮模型请求。
- `npx vitest run src/main/agent/model-runtime.test.ts`:`74 passed, 1 skipped`。
- 全量 `npm test`:`3493 passed, 55 skipped`。
- `npm run typecheck`、`npm run lint` 和 `npm run build`:通过。
## 进度维护要求
- 只有真实生产路径完成并验证后才能勾选实施项。
- 平台模拟单元测试不能替代对应平台真实命令测试。
- 真实模型验证必须记录准确调用次数且不得记录凭据。
- 需求变化回写 PRD、User Stories 和功能逻辑;实现事实只写本文件。
@@ -0,0 +1,414 @@
# 直连模型 Agent 能力技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 关联 PRD | [PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 相关架构 | [助手工作栏与执行空间](../assistant-workbar/prd.md)、[本机工具环境](../local-tool-environment/technical-design.md) |
## 1. 实现基线
当前源码已经实现:
- `ModelAgentRuntime` 的多轮工具调用、上下文压缩、取消、用量和有界结果处理。
- `ModelToolProvider` 的直连模型内置工具、浏览器、Web、知识和自定义 MCP 聚合。
- `WorkspaceAccess` 的本机和远端工作区抽象。
- 跨平台子进程树终止辅助函数。
- 面向用户专家协作的 `SubagentScheduler` 和状态事件。专家使用具备已启用本机能力的
直连模型 Runtime,继承父请求 Ask/Execute 及 authorizer,工具事件记入子任务;
Ask 保持只读,不把这些本机工具表述为远程 OpenCode 子会话。
- OpenCode、Continue 和 DeepSeek Harness 各自的 Shell/Agent 能力。
- 直连模型 `process_execute` 和 `subagent_delegate`。
- 不创建顶层 Task/Conversation 的编程 Subagent actor 与活动归并。
## 2. 总体架构
```text
ModelAgentRuntime
└─ ModelToolProvider
├─ WorkspaceAccess
├─ DirectModelProcessService
│ └─ LocalDirectModelProcessService
├─ DirectModelSubagentService
│ ├─ SubagentScheduler
│ └─ 同一 ModelAgentRuntime 的临时子会话
│ └─ 同一 ModelToolProvider(delegationDepth=1 时移除委派)
├─ Browser / Web / Knowledge
└─ assigned MCP
```
OpenCode、Continue 和 DeepSeek Harness 的创建路径不接收
`DirectModelProcessService` 或 `DirectModelSubagentService`。DeepSeek Harness 即使继续使用
`ModelToolProvider` 代理 Web/MCP,也必须通过明确的工具视图排除直连模型专属工具。
## 3. 共享工具目录
`BuiltinModelToolSummary` 增加 `programming` 分组和 Runtime 可见性:
```ts
type BuiltinModelToolSummary = {
name: string
displayName: string
description: string
access: 'read' | 'write'
group: 'filesystem' | 'browser' | 'web' | 'programming'
}
```
`process_execute` 标记为 `write`,只在 Execute 清单中出现。
`subagent_delegate` 的实际访问级别由父模式决定:
- Ask 清单中提供只读委派定义。
- Execute 清单中提供继承 Execute 的定义。
- `callTool` 不依赖静态 `access` 单独授权,必须检查父请求上下文。
工具总数和 schema 总字节继续计入直连模型现有 100 个工具与 512 KiB 上限。
## 4. 进程工具契约
### 4.1 输入
```ts
const processExecuteInputSchema = z.object({
command: z.string().trim().min(1).max(100_000),
cwd: z
.string()
.trim()
.min(1)
.max(4_096)
.optional(),
timeoutMs: z
.number()
.int()
.min(1_000)
.max(10 * 60_000)
.default(120_000)
}).strict()
```
- `cwd` 接受绝对路径或相对工作区路径,省略时使用工作区根目录;按本机路径语义解析
`..` 和符号链接,不限制目标位于工作区内。工具 schema 向模型明确这一规则。
- 首版不接受 `env`、`stdin`、`background`、`pty`、`shell` 或任意 executable 参数。
- Shell 选择由执行后端决定,防止模型绕过平台契约启动另一套受管接口。
- 命令字符串仍可以调用当前账号本来有权运行的程序;Execute 不增加命令白名单。
### 4.2 结果
```ts
type ProcessExecuteResult = {
shell: {
kind: 'powershell' | 'bash' | 'sh'
label: string
}
cwd: string
exitCode: number | null
signal?: string
terminationReason?: 'timeout' | 'cancelled'
durationMs: number
stdout: string
stderr: string
stdoutTruncated: boolean
stderrTruncated: boolean
}
```
`cwd` 在工作区内返回相对显示值,根目录为 `.`;工作区外返回规范化绝对路径,准确说明
命令实际执行位置。Shell 在模型结果中只返回稳定标签;绝对可执行路径只进入 Main 诊断状态。
### 4.3 输出边界
- stdout 和 stderr 各最多 96 KiB。
- 超限时分别保留前 48 KiB 与最后 48 KiB,中间插入固定截断标记。
- JSON 结果加上元数据后必须低于 `ModelToolProvider` 现有 256 KiB 工具结果上限。
- 流式读取在收到数据时执行边界,不先积累无界 Buffer。
- 输出按 UTF-8 解码,无效序列使用替换字符并保留字节计数。
## 5. 平台执行
### 5.1 Shell 探测
启动直连模型执行服务时解析一次 Shell:
```text
Windows: PATH 中 pwsh → Windows PowerShell powershell.exe
macOS: /bin/bash → /bin/sh
Linux: /bin/bash → /bin/sh
```
Shell 不可用时保留直连模型对话能力,但不注册 `process_execute`。设置目录继续展示静态能力
说明,实际 Shell 通过每次请求的工具定义和结果可见;当前不增加独立 Shell 诊断 IPC。
不下载或捆绑新的 Bash/PowerShell。
### 5.2 启动参数
```text
PowerShell:
<shell> -NoLogo -NoProfile -NonInteractive -Command <command>
Bash:
/bin/bash -c <command>
Sh:
/bin/sh -c <command>
```
使用 `spawn(executable, args, { shell: false })`,避免 Node 再增加一层平台 Shell。POSIX 子进程
作为独立进程组启动;Windows 记录根 PID。停止时复用现有
`terminateProcessTreeAndWait`:Windows 使用 `taskkill /T /F`,POSIX 终止本次进程组并等待
有界退出。
### 5.3 环境
进程环境由 Main 构造:
- 继承运行普通项目命令所需的当前用户基础变量。
- 当前使用筛选后的用户 PATH;[本机工具环境](../local-tool-environment/technical-design.md)
实施后可通过现有 `toolBinDirectory` 注入口把受管 bin 放在 PATH 前面。
- 排除模型 Provider Key、GoodBuddy 加密设置、调试变量和无关应用凭据。
- 不修改 `process.env`,不写系统 PATH 或 Shell Profile。
- 每次调用得到独立环境快照;命令内设置的变量不会进入下一次调用。
## 6. 执行空间绑定
定义一个小型进程后端接口,不把任意进程能力塞进 `WorkspaceAccess`:
```ts
interface ExecutionSpaceProcessExecutor {
getCapability(): Promise<
| { available: true; shell: ProcessShellSummary }
| { available: false; reason: string }
>
execute(
input: ProcessExecuteInput,
context: {
workspace: WorkspaceAccess
signal: AbortSignal
}
): Promise<ProcessExecuteResult>
dispose(): Promise<void>
}
```
首版只有 `LocalProcessExecutor`。`ExecutionSpaceDescriptor.kind === 'ssh'` 时使用不可用实现,
工具清单不注册进程能力。未来远端实现必须新增 Agent 协议、由 Agent 直接拥有命令进程,并
按远端身份、取消和断线语义验证;不得把 SSH 路径传给本机 executor。
工作目录通过 `WorkspaceAccess.getIdentity()` 和本机真实路径解析共同确认。路径检查与 spawn
之间若目录被外部替换,spawn 失败并返回准确错误;不为此增加持久路径凭据或恢复协议。
## 7. Subagent 工具契约
### 7.1 输入
```ts
const subagentDelegateInputSchema = z.object({
task: z.string().trim().min(1).max(100_000)
}).strict()
```
首版一次调用只创建一个子任务。不同父请求可以通过全局调度器并发,不增加批量委派 schema。
### 7.2 子请求
`DirectModelSubagentService` 创建临时子请求:
```ts
type DirectModelSubagentContext = {
parentRequestId: string
childRunId: string
projectId?: string
conversationId: string
workMode: 'ask' | 'execute'
}
```
- 子 `conversationId` 只用于 Runtime 内存和工具会话隔离,完成后立即
`releaseConversation`,不保存为用户 Conversation。
- 子 Prompt 使用明确任务和 Main 可信的编程 Subagent 指令,不复制父会话历史。
- 子级使用父请求模型连接,首版不增加单独模型选择设置。
- 子级复用父请求已授权能力快照和知识范围。
- 子级复用父 Provider;Main 写入的 `delegationDepth=1` 同时在清单和调用边界过滤
`subagent_delegate`。
- Ask 子级使用 Ask 清单;Execute 子级使用 Execute 清单。
### 7.3 调度和结果
- 复用 `SubagentScheduler` 的取消、并发和队列模式。
- 全局最大并发 3,队列最大 20。
- 单个子任务最长 10 分钟;父请求更早取消时立即停止。
- 文本结果最多 192 KiB,错误最多 2 KiB,连同结构化字段保持在 256 KiB 工具结果上限内。
- 模型用量逐事件归属父请求和 `childRunId`,不能只算入父模型最后一轮。
- 子级失败和取消返回结构化终态及部分输出,让父模型决定下一步。
### 7.4 领域持久化
当前 `SubagentService` 为历史专家流程创建隐藏 Child Task。新编程 Subagent 不复制该行为:
- 运行身份使用内部 `childRunId`。
- 父消息通过现有 `subagent` 活动块保存终态和结果摘要。
- 若通用 Job/Subjob 表已经实现,则记录为所属父 Task/请求的 Subjob/Run。
- 若通用 Job/Subjob 表尚未实现,首版只保存现有消息活动和模型用量,不先创建另一套持久
子任务表、恢复日志或兼容读取器。
可以提取现有 Scheduler、事件归并和用量转发逻辑,但不能把旧数据库字段当成新的产品模型。
## 8. ModelToolProvider 接入
`ModelToolCallContext` 增加不可伪造的运行上下文:
```ts
type ModelToolCallContext = {
conversationId: string
requestId: string
runtimeTarget: 'model'
workMode: 'ask' | 'execute'
executionSpaceIdentity: string
delegationDepth: 0 | 1
knowledgeCapabilityToken?: string
}
```
要求:
- `listTools` 根据模式、执行空间能力和深度生成快照。
- `getApproval` 不为两个工具创建第二套逐次确认;沿用父 Execute 授权和现有通道工具策略。
- `callTool` 再次检查 `runtimeTarget`、模式、执行空间 identity、深度和请求是否活动。
- `releaseConversation` 取消并释放该会话拥有的浏览器、Subagent 和进程。
- `dispose` 等待有界清理,不因模型或子进程不响应而阻塞应用退出。
DeepSeek Harness 的 Main 工具代理必须使用仅包含分配 MCP 和 Web 的 Provider 视图,不能因为
共享 `ModelToolProvider` 类而自动看到直连模型编程工具。
## 9. 事件与活动
### 9.1 进程事件
继续使用现有 Runtime `tool` 事件:
- `pending`:参数已接收。
- `running`:Shell 已成功启动。
- `completed`:返回 exit 0 或非零退出结果。
- `failed`:启动或契约失败。
- `cancelled`:父请求取消。
结果摘要包含 Shell、工作目录、退出码、耗时和截断标记。完整有界 stdout/stderr 进入工具
输出字段,不写应用诊断日志。
### 9.2 Subagent 事件
复用现有 `subagent` 公开事件和消息块,增加 `routingMode: 'native'` 表示模型主动委派。
`expertId` 不再作为编程 Subagent 必填身份;共享 schema 应改为执行者 discriminated union,
而不是伪造一个专家记录:
```ts
type SubagentActor =
| { kind: 'expert'; expertId: string; expertName: string }
| { kind: 'direct-model'; label: '编程 Subagent' }
```
迁移只需让读取器接受旧专家事件和新联合类型,不重写既有消息。
## 10. 错误处理
| 情况 | 结果 |
| --- | --- |
| 命令非零退出 | 正常 `ProcessExecuteResult` |
| 命令不存在 | Shell 非零退出及 stderr |
| Shell 无法启动 | 工具失败 |
| cwd 无效或越界 | 工具调用前失败 |
| 超时/取消 | 终止进程树,返回已有输出和原因 |
| 直连模型瞬时网络错误/请求超时,尚无可见输出 | 500 ms、1 s、2 s 退避,最多自动重试 3 次 |
| 直连模型失败,已有文本或推理输出 | 保留部分输出并失败,不自动重放 |
| 子级模型失败 | 返回 `failed`、部分输出和错误 |
| 子级工具失败 | 由子模型处理;最终无法完成时返回子级失败 |
| 父请求已终止 | 拒绝新调用,丢弃迟到内容并清理 |
不要用宽泛 catch 抹掉取消、超时、Provider HTTP 状态或进程退出上下文。
模型请求重试由 `ModelAgentRuntime` 的统一流式请求处理,覆盖普通问答、工具轮次和复用
同一 Runtime 的上下文摘要。重试分类检查错误及有界 `cause` 链中的 Node/Undici 网络错误
码;每次尝试创建独立超时信号。退避等待监听父请求取消。只有完整模型轮次返回后才执行
工具,因此未产生可见文本或推理内容的重试不会重复执行工具。
模型回复本身不设固定字节上限:流式响应、非流式回复、模型工具参数、请求上下文和工具
结果总量都由请求超时、取消和上下文压缩控制,长回复和大工具结果不会被中断。图像生成
响应仍按可解码图片大小校验。
## 11. 性能
- Shell 探测按执行服务实例缓存,不在每轮模型调用重复探测。
- stdout/stderr 边读边界定,避免大日志进入内存后再截断。
- 子级共享父请求已解析的模型配置和工具服务,不复制 MCP 配置或持久状态。
- 不增加持久 Shell、后台 daemon、工作区快照或恢复状态机。
- 命令执行不使用 PTY,避免普通构建和测试承担终端渲染成本。
## 12. 测试策略
### 12.1 单元测试
- 工具可见性矩阵和 Runtime target 过滤。
- Ask 伪造进程调用拒绝。
- Windows PowerShell、POSIX Bash/Sh 选择和参数。
- cwd 默认、相对子目录、绝对路径、工作区外目录、指向外部的符号链接和不存在的目录。
- exit 0、非零退出、spawn 失败、超时、取消和输出截断。
- 环境筛选、本机工具 PATH 优先且模型凭据不进入进程。
- 子级模式、模型、项目、执行空间和能力继承。
- 深度 1 过滤、并发 3、队列 20、取消传播和部分输出。
- 编程 Subagent 不创建 Task/Conversation。
- DeepSeek Harness/OpenCode/Continue 不获得本功能工具。
- TLS 建连 `ECONNRESET`、Undici 连接超时、单次请求超时、三次重试上限和退避取消。
- 已产生文本或推理增量后发生网络错误时不自动重放。
### 12.2 本机集成测试
- Windows PowerShell 创建临时文件、运行 Node/Python、非零退出和进程树取消。
- macOS/Linux Bash/Sh 执行相同场景。
- 直连模型工具循环执行“修改 → 测试失败 → 修复 → 测试通过”。
- 父模型委派子级修改或验证专用临时项目,并正确综合结果。
- Runtime 切换后工具清单刷新且没有重复 Shell。
- 应用退出后无残留 GoodBuddy 命令进程。
所有测试只在专用临时工作区创建文件,不删除或覆盖用户工作。
### 12.3 真实模型验证
使用一个最小真实直连文本模型请求:
1. Execute 创建一段有缺陷的小程序。
2. 调用进程工具运行聚焦测试并观察失败。
3. 修改程序并再次运行到通过。
4. 委派一个 Subagent 检查实现或补充测试。
5. 父模型读取子级结果并给出最终结论。
每个平台一次完整场景即可,不进行批量或高成本调用。记录准确模型调用次数,不记录 API Key。
### 12.4 项目验证
源码完成后运行:
```text
npm test
npm run typecheck
npm run lint
npm run build
```
跨平台进程实现必须由 Windows、macOS、Linux x64 和 Linux arm64 CI 验证。首版不改变
GoodBuddy Agent 或桌面到 Agent 生产路径,因此不要求远端 Host 验证;未来增加远端 executor
时必须按远程主机技术设计执行共享 Linux Host 真机验证。
## 13. 实施顺序
1. Shared 工具摘要、schema 和 Runtime target 过滤。
2. 本机 Shell 探测、进程执行、输出边界和进程树取消。
3. `ModelToolProvider` Execute 接入及 UI 活动。
4. 编程 Subagent 服务、过滤 Provider、事件和用量。
5. 设置能力目录与状态诊断。
6. 三平台真实命令、真实模型和完整项目验证。
每一步都必须保持直连模型正常问答可用;Shell 不可用不能阻止普通对话。
@@ -0,0 +1,136 @@
# 直连模型 Agent 能力 UI 设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 设计系统 | [GoodBuddy 统一界面设计系统](../../../UI-DESIGN.md) |
| 关联 PRD | [PRD](./prd.md) |
| 关联逻辑 | [功能逻辑设计](./logic-design.md) |
## 1. 信息架构
本功能不增加一级页面、Composer 按钮或 Runtime 专属工具栏。用户主要通过三处理解能力:
1. **设置 > MCP > 直连模型**:查看内置编程能力说明。
2. **聊天消息活动块**:查看命令和 Subagent 的实时状态与结果。
3. **运行记录**:按当前 Conversation 或 Task 查看聚合执行活动。
Job、Subjob 和 Run 继续是内部对象,不创建树形 UI、独立路由或操作菜单。
## 2. 设置中的能力目录
在现有直连模型工具目录增加“编程能力”分组:
```text
编程能力
进程执行
使用当前用户权限运行 PowerShell、Bash 或 Sh 命令,默认在工作区执行,也可指定其他工作目录。
编程 Subagent
将聚焦任务委派给同一模型连接;继承当前工作模式和工作区。
```
要求:
- 两项默认属于直连模型,不提供持久 Switch。
- 设置目录只定义稳定工具能力,不增加 Shell 状态或重试控件;实际 Shell 标签在模型工具
定义和进程结果中返回。
- Runtime 是 OpenCode、Continue 或 DeepSeek Harness 时,不在其原生能力清单中伪造这两项;
用户需要理解差异时,在直连模型分组说明“Agent Runtime 使用自身工具”。
- 不把模型配置检查成功描述为命令或 Subagent 已经通过真实执行验证。
## 3. 进程活动块
复用现有对话工具活动视觉和语义,不创建页面专属卡片系统。
### 3.1 标题与摘要
```text
进程执行
PowerShell · packages/app · npm test
```
- 命令只显示单行有界摘要,完整参数进入可展开详情。
- 工作区内目录显示相对路径,工作区根显示 `.`;工作区外目录显示规范化绝对路径。
- 不在摘要中显示完整环境或凭据。
### 3.2 状态
| 状态 | 文案 |
| --- | --- |
| pending | 等待运行 |
| running | 正在运行 |
| completed,exit 0 | 已完成 |
| completed,exit 非零 | 工具已完成,详情显示退出码 N |
| timeout | 工具已完成,详情显示 timeout |
| cancelled | 父请求活动显示已取消 |
| failed | 启动失败 |
状态必须同时有文字和状态图标,不能只依靠颜色。非零退出属于正常结构化工具结果,不显示为
GoodBuddy 服务崩溃。
### 3.3 结果详情
详情按以下顺序展示:
1. Shell、工作目录、耗时和退出码。
2. stdout。
3. stderr。
4. 截断提示。
日志使用共享等宽字体和可选择文本。长输出在活动块内折叠,展开后仍保持工作栏和聊天布局
可用;不把完整日志复制为应用通知。
## 4. Subagent 活动块
复用现有 `subagent` 消息块,并将系统执行者显示为“编程 Subagent”,不要求用户预先创建
专家。
```text
编程 Subagent
检查认证模块的测试失败并提出修复
正在运行 · 直连模型 · Execute
```
终态:
- 完成:显示有界结果摘要,可展开查看完整返回。
- 失败:保留部分输出和错误。
- 取消:显示“父请求已取消”或准确取消原因。
- 排队:显示当前处于并发队列,不显示虚假百分比。
活动块不提供单独的“批准”“提高权限”或“继续递归”按钮。用户取消父请求即可取消子级。
## 5. Ask/Execute 表达
- Ask 中不显示不可用的进程按钮,因为模型工具没有手动调用入口。
- Ask Subagent 活动明确显示“只读”。
- Execute 活动显示“当前用户权限”,不声称 OS 沙箱或工作区隔离。
- 如果用户从 Ask 切到 Execute,只有下一条请求使用新模式;运行中的 Subagent 不切换。
## 6. 错误与通知
- 参数错误、命令退出、Subagent 失败和部分输出属于当前活动上下文,保留在对应活动块。
- Runtime 初始化、Shell 探测或全局服务失败可通过应用通知提示,并在设置状态卡提供重试。
- 同一错误不同时显示活动块副本和通知副本。
- 失败后保留用户 Prompt、命令摘要、工作目录和已有输出。
- 直连模型自动重试通过现有聊天状态显示“模型网络请求失败,正在重试(n/3)”,不增加
独立按钮、弹窗或通知副本;用户仍可使用当前停止操作取消请求。
## 7. 键盘与无障碍
- 活动块展开按钮使用原生 `button`,提供 `aria-expanded` 和关联详情 ID。
- 状态变化通过现有活动区域的礼貌级 live region 发布,不逐个朗读流式日志。
- stdout/stderr 区域具有可见标签,不能只靠顺序区分。
- 焦点不会因后台状态更新自动移动。
- 取消父请求后的焦点保持在原取消按钮或恢复到 Composer。
- 浅色、深色和减少动态效果模式遵守统一设计系统。
## 8. 响应式
- 窄聊天区只保留工具名、状态和一行摘要,元数据换行到下一行。
- 日志区域横向滚动,不撑宽页面。
- 运行记录使用紧凑摘要,不复制完整 stdout/stderr;用户回到聊天活动块展开查看详情。
@@ -0,0 +1,177 @@
# 直连模型 Agent 能力 User Stories
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,跨平台 CI 待验证 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 关联 PRD | [直连模型 Agent 能力 PRD](./prd.md) |
| 关联逻辑 | [功能逻辑设计](./logic-design.md) |
## 1. 角色
### 1.1 普通项目用户
希望模型直接完成脚本、文档生成或数据处理,不关心不同平台的 Shell 名称。
### 1.2 开发者
希望直连模型能够修改代码、运行测试、检查 Git 状态并根据失败结果继续修正。
### 1.3 Runtime 用户
已经使用 OpenCode、Continue 或 DeepSeek Harness,希望继续使用 Runtime 原生能力,不看到
重复的 GoodBuddy Shell 或 Subagent。
## 2. Epic A:跨平台执行
### US-A1 在 Windows 运行项目命令
关联:FR-2、FR-3、FR-4。
Given 当前项目位于 Windows 本机且选择直连模型 Execute,When 模型调用
`process_execute`,Then GoodBuddy 使用可用 PowerShell 在项目目录运行命令,并返回
Shell、目录、退出码、输出和耗时。
### US-A2 在 macOS/Linux 运行项目命令
关联:FR-2、FR-3。
Given 当前项目位于 macOS 或 Linux 本机,When 模型运行同一工具,Then GoodBuddy 使用
Bash,Bash 不可用时使用 Sh,工具契约保持不变且实际 Shell 明确可见。
### US-A3 选择命令工作目录
关联:FR-3。
Given 当前请求为本机 Execute,When 模型传入绝对路径或相对工作区路径,Then 命令在
解析后的目录执行,包括工作区外目录和符号链接目标,And 后续省略 `cwd` 时仍使用原工作区。
When 目标不是目录或不存在,Then 命令不启动并返回准确错误。
### US-A4 根据非零退出继续修正
关联:FR-2、FR-9。
Given 测试命令退出码非零,When 进程结束,Then 工具正常返回退出码和 stderr,而不是终止
整个模型请求,And 模型可以读取失败原因、修改代码并再次运行。
### US-A5 取消长命令
关联:FR-7。
Given 命令仍在运行,When 用户取消父请求,Then 当前 Shell 及其由本次命令创建的子进程树
停止,And 活动状态显示“已取消”,And 其他用户进程不受影响。
### US-A6 看懂截断结果
关联:FR-7、FR-9。
Given 命令输出超过上限,When 工具返回结果,Then 保留有用的开头和结尾并明确
`truncated: true`,And 模型与用户都不会把结果理解为完整日志。
### US-A7 从瞬时模型网络故障恢复
关联:FR-7、FR-9、FR-11。
Given 直连模型在 TLS 建连、网络传输或单次请求超时时失败且尚未显示输出,When 请求仍未
取消,Then GoodBuddy 显示当前重试次数并使用递增等待时间最多重试 3 次,And 成功后继续
原问答或工具轮次。Given 已经显示文本或推理内容,Then 不自动重放该调用。
## 3. Epic B:编程闭环
### US-B1 修改后运行验证
关联:FR-2、FR-4。
Given 用户选择 Execute 并要求修复代码,When 直连模型修改文件,Then 它可以运行聚焦测试
或检查命令,并根据真实结果继续修改,直到给出完成或阻塞结论。
### US-B2 使用本机工具环境
关联:FR-8、FR-10。
Given 用户选择了 GoodBuddy 托管或自定义 Node/Python,When 新命令调用 `node`、`npm`、
`npx`、`python` 或 `pip`,Then 它们解析到当前工具环境,And 不修改系统 PATH 或已有终端。
### US-B3 依赖或命令不存在
关联:FR-2、FR-9。
Given 项目缺少所需命令或依赖,When Shell 返回错误,Then 工具保留退出码和错误文本,
And 模型说明缺失项或在 Execute 中采取用户要求的安装步骤,不把环境缺失报告为模型故障。
## 4. Epic C:Subagent 委派
### US-C1 委派聚焦编程任务
关联:FR-5、FR-6。
Given 父直连模型正在处理复杂请求,When 它调用 `subagent_delegate` 并给出独立任务,
Then 子级使用同一项目、模型连接和 Execute 能力完成工作,And 最终结果返回父模型综合。
### US-C2 Ask 中只读委派
关联:FR-4、FR-5。
Given 父请求为 Ask,When 模型委派分析任务,Then 子级保持 Ask,不能写文件或运行命令,
And 不因 Subagent 存在而扩大权限。
### US-C3 禁止递归委派
关联:FR-5、FR-7。
Given 子级正在执行,When 它读取自己的工具清单,Then 清单中没有
`subagent_delegate`,And 子级不能创建孙级任务。
### US-C4 子级失败后父级继续
关联:FR-5、FR-9。
Given 子级运行期间失败或超时,When 子任务结算,Then 父模型收到失败状态、部分输出和
有界错误,And 可以自行继续、改变方案或向用户说明阻塞。
### US-C5 取消传播
关联:FR-7。
Given 一个 Subagent 正在运行模型或命令,When 用户取消父请求,Then 排队和运行中的子级
全部取消,当前命令进程树停止,And 不再向父请求追加迟到结果。
### US-C6 保持产品对象简单
关联:FR-6。
Given 直连模型完成一次委派,When 用户查看会话、Task Center 和活动,Then 会话内可看到
Subagent 状态与结果摘要,And Task Center 不出现新的子 Task,And UI 不展示 Job/Run 树。
## 5. Epic D:Runtime 与执行空间边界
### US-D1 Agent Runtime 不重复获得工具
关联:FR-1、FR-10。
Given 用户选择 OpenCode、Continue 或 DeepSeek Harness,When Runtime 启动,Then
GoodBuddy 不注入 `process_execute` 或 `subagent_delegate`,And Runtime 原生能力保持不变。
### US-D2 切换 Runtime 后工具清单更新
关联:FR-1。
Given 会话从直连模型切换为 Agent Runtime,When 下一次请求开始,Then 新请求只使用目标
Runtime 的能力;切回直连模型时重新获得符合工作模式的直连工具。
### US-D3 不在错误设备执行
关联:FR-3。
Given 当前执行空间是 SSH 且远端进程后端尚不可用,When 解析工具清单,Then 不注册
`process_execute`;即使调用参数有效,也不能回退到桌面本机执行。
### US-D4 消息通道使用相同规则
关联:FR-1、FR-4。
Given 消息通道项目选择直连模型,When 合法消息触发 Ask 或 Execute,Then 使用与桌面会话
相同的工具矩阵和通道既有工具策略,不维护第二套 Shell 能力配置。
@@ -0,0 +1,11 @@
# 文档处理
文档处理负责聊天附件和知识库导入中的文本提取、格式转换与本地 OCR。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [文档解析与本地 OCR PRD](./prd.md) | 产品范围、解析策略、OCR 模型管理和验收 |
当前尚无独立 User Stories、功能逻辑、UI、技术设计和进度文档。
+11
View File
@@ -0,0 +1,11 @@
# 全双工语音
全双工语音定义持续听说、打断、语音活动检测、本地与远端推理以及多平台音频边界。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [技术设计](./technical-design.md) | 产品目标、交互流程、音频架构、模型、打包和实施状态 |
现有文档是综合设计,尚未按 PRD、User Stories、功能逻辑、UI 和进度职责拆分。
@@ -10,7 +10,7 @@
| 日期 | 2026-08-19 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 相关基线 | [跨平台助手产品设计](../design/cross-platform-assistant-product-design.md)、[长期助手路线图](../roadmap/long-term-assistant-roadmap.md)、[统一界面设计系统](../../UI-DESIGN.md) |
| 相关基线 | [长期助手路线图](../../roadmap/long-term-assistant-roadmap.md)、[统一界面设计系统](../../../UI-DESIGN.md) |
本文定义 GoodBuddy 中类似自然通话的全双工实时语音能力,包括本地与云端语音引擎、
音频平面、会话状态、打断语义、工具审批、数据留存、失败恢复、跨平台交付和验收指标。
@@ -831,7 +831,7 @@ Endpoint 不能因为 URL 相似就被标记为实时语音。
- 本地模块化引擎作为 Windows、macOS、Linux x64/arm64 的统一功能基线。
- 在线 ASR、VAD 和轻量 TTS 权重不内置,继续使用按需下载和 ZIP 离线迁移。
- GoodBuddy 托管模型的下载遵守
[平台功能页签与模型下载源设计](./model-download-source-design.md),使用用户显式选择的
[平台功能页签与模型下载源设计](../../architecture/model-download-source-design.md),使用用户显式选择的
ModelScope 或 Hugging Face,失败时不切换来源。
- 云端 Adapter 在六个平台复用同一契约,并分别验证 Electron WebRTC/WebSocket 行为。
- 本地原生引擎按 Adapter 声明平台与硬件,不伪装成全平台能力。
+11
View File
@@ -0,0 +1,11 @@
# 应用内反馈
应用内反馈负责用户在 GoodBuddy 中提交反馈、附带受控诊断并获得提交状态。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [技术设计](./technical-design.md) | 产品流程、数据边界、接口、UI 接入和验收 |
现有文档是综合设计,尚未按标准职责拆分。
@@ -10,7 +10,7 @@
| 日期 | 2026-08-25 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 相关基线 | [统一界面设计系统](../../UI-DESIGN.md)、[GoodBuddy 文档导航](../README.md) |
| 相关基线 | [统一界面设计系统](../../../UI-DESIGN.md) |
本文定义 GoodBuddy 桌面端的应用内反馈入口、数据边界、Renderer / Preload /
Main 进程职责、公共 API 对接、失败处理和验收方法。反馈平台服务端、管理后台、
@@ -45,8 +45,9 @@ Main 进程职责、公共 API 对接、失败处理和验收方法。反馈平
以此保证幂等;创建另一条反馈时才生成新 ID。
10. GoodBuddy 使用单独的随机安装 UUID 做服务端限流。该 UUID 不得复用 Agent
installation ID、硬件 ID、机器名、用户名、项目 ID 或任何设备指纹。
11. 默认不发送对话、提示词、模型回复、日志、文件、项目或工作区路径、知识库、模型配置、
Runtime 配置、API Key、Token、Cookie、SSH 信息、剪贴板或屏幕内容。
11. 默认不发送对话、提示词、模型回复、诊断、日志、文件、项目或工作区路径、知识库、模型
配置、Runtime 配置、API Key、Token、Cookie、SSH 信息、剪贴板或屏幕内容。用户可通过
默认关闭的 Checkbox 主动附加有界的最近桌面诊断摘要。
12. 反馈提交失败时保留全部表单内容和截图,成功后显示可复制的服务端反馈编号。
13. 首版不提供反馈历史、状态查询、编辑、撤回、自动日志收集或服务端管理入口。
14. 生产接口固定为 `https://imp.mesalogo.com/api/v1/feedback`。域名健康检查、
@@ -79,7 +80,8 @@ Main 进程职责、公共 API 对接、失败处理和验收方法。反馈平
- GitHub Issue 创建、GitHub Token 或 GitHub 网页附件上传。
- GoodBuddy 内的管理员后台、处理状态修改或内部备注。
- 用户账户、登录、反馈列表、远程状态查询或推送通知。
- 自动附加当前对话、日志、错误堆栈、项目、文件、模型或 Runtime 信息。
- 自动附加当前对话、桌面诊断、日志、错误堆栈、项目、文件、模型或 Runtime 信息。
- 远端 Agent 日志或通用日志、诊断包和附件框架。
- 自动截取全屏、当前窗口、浏览器、远程桌面或其他应用窗口。
- 多截图、视频、录屏、任意文件附件或压缩包。
- 离线队列、后台自动重试、指数退避、跨重启草稿或发送历史。
@@ -113,16 +115,23 @@ Main 进程职责、公共 API 对接、失败处理和验收方法。反馈平
- 使用体验:`experience`
- 其他:`other`
2. **标题**,必填,1 至 120 字符。
3. **详细描述**,必填,去除首尾空白后 10 至 5,000 字符。
4. **联系邮箱**,可选,最多 254 字符。
5. **截图**,可选,最多一张,支持选择 PNG/JPEG/WebP 或在对话框中粘贴图片。
6. **将发送的信息**,只读显示应用版本、系统、架构和界面语言。
7. **隐私说明**,紧邻提交操作。
3. **详细描述**,必填,去除首尾空白后 10 至 5,000 字符;选择桌面诊断时为摘要预留空间,
用户描述上限为 3,398 字符。
4. **附加最近桌面诊断记录**,可选的原生 Checkbox,默认关闭。
5. **联系邮箱**,可选,最多 254 字符。
6. **截图**,可选,最多一张,支持选择 PNG/JPEG/WebP 或在对话框中粘贴图片。
7. **将发送的信息**,只读显示应用版本、系统、架构和界面语言。
8. **隐私说明**,紧邻提交操作。
隐私说明使用明确文案:
> 将发送反馈类型、标题、描述、可选邮箱、GoodBuddy 版本、操作系统、架构、界面语言,
> 以及你主动添加的截图。不会自动发送对话、日志、文件、项目路径、模型配置或凭据。
> 以及你主动添加的截图。默认不上传桌面诊断;只有勾选后才会把有界诊断摘要追加到描述。
> 不会发送对话、Prompt、凭据、文件内容、路径、Provider 原始响应或远端 Agent 日志。
Checkbox 下方持续说明:诊断摘要只含时间、组件、阶段、稳定错误码、错误类型和固定短消息。
Renderer 不预览、不读取也不接收诊断内容或路径。勾选后详细描述上限为 3,398 字符,为附加
摘要预留 1,600 字符以及分隔换行;超长草稿保留在表单中并显示字段错误,不静默截断。
截图区域必须包含:
@@ -167,7 +176,7 @@ Main 进程职责、公共 API 对接、失败处理和验收方法。反馈平
| `productKey` | Main 常量 `goodbuddy` | 服务端产品路由 |
| `category` | 用户选择 | 分流反馈 |
| `title` | 用户输入 | 摘要 |
| `description` | 用户输入 | 问题和建议详情 |
| `description` | 用户输入;勾选时由 Main 追加桌面诊断摘要 | 问题、建议和主动提供的有界诊断详情 |
| `contactEmail` | 用户可选输入 | 必要时联系用户 |
| `environment.appVersion` | `app.getVersion()` | 判断受影响版本 |
| `environment.platform` | `process.platform` 映射 | 判断受影响平台 |
@@ -197,8 +206,9 @@ Main 进程职责、公共 API 对接、失败处理和验收方法。反馈平
- 剪贴板、屏幕、摄像头、麦克风或其他应用内容。
- Remote Agent installation ID、Runtime ID、Conversation ID、Task ID 或 Project ID。
未来若增加日志、诊断包或其他附件,必须单独设计字段、预览、大小限制、用户选择和服务端
保留策略,不能复用截图字段静默上传。
除上述用户主动选择、追加到描述且有界的桌面诊断摘要外,未来若增加日志、诊断包或其他
附件,必须单独设计字段、预览、大小限制、用户选择和服务端保留策略,不能复用截图字段
静默上传。
### 4.3 本地数据生命周期
@@ -289,6 +299,7 @@ HTTP 201 表示首次创建,HTTP 200 表示相同 `clientRequestId` 的幂等
| 500–599 | 提示服务暂时异常,保留草稿 |
| 超时 / DNS / TLS / 离线 | 提示无法连接反馈服务,保留草稿 |
| 响应格式错误 | 提示服务返回无效结果,不能猜测成功 |
| 本地诊断读取失败 | 不发送反馈,保留草稿、勾选状态和请求 ID,提示重试或取消勾选 |
首版不自动重试。自动重试容易让用户无法判断是否已经发送,也会在服务持续异常时制造额外
流量。用户点击“重试”时复用同一个 `clientRequestId`。
@@ -330,6 +341,7 @@ type FeedbackSubmitInput = {
category: 'bug' | 'feature' | 'experience' | 'other'
title: string
description: string
includeDiagnostics: boolean
contactEmail?: string
locale: 'zh-CN' | 'en-US'
clientRequestId: string
@@ -341,7 +353,7 @@ type FeedbackSubmitInput = {
```
Renderer 不能传入 `productKey`、`appVersion`、`platform`、`architecture` 或
`installationId`。这些字段由 Main 从受信运行环境生成。
`installationId`,也不能传入诊断内容或路径。这些字段由 Main 从受信运行环境生成。
### 6.3 IPC 与 Preload
@@ -402,6 +414,12 @@ Main-only `FeedbackService` 负责:
- 映射应用版本、平台和架构。
- 验证并标准化可选截图。
- 组装严格的公共 payload。
- 仅在 `includeDiagnostics: true` 时通过注入的 DesktopDiagnostics provider 读取最多 20 条
最近记录;默认提交完全不调用诊断 provider。
- 将诊断格式化为带
`[GOODBUDDY_DESKTOP_DIAGNOSTICS_V1_BEGIN]` /
`[GOODBUDDY_DESKTOP_DIAGNOSTICS_V1_END]` 明确分隔、最多 1,600 字符且只保留完整 JSON
行的摘要。无记录时写入固定 `{"status":"no-recent-diagnostics"}` 事实。
- 调用 HTTP Client。
- 维持单次提交的 AbortController,并在 dispose / 清除本地数据时取消。
@@ -416,6 +434,11 @@ Main-only `FeedbackService` 负责:
架构只保留 `x64` 和 `arm64`,其他值映射为 `unknown`。
公共 API 继续使用 `schemaVersion: 1` 和原有固定字段。`includeDiagnostics` 只属于客户端
IPC 输入,不进入公共 payload,也不新增 multipart 字段;Main 把摘要追加到
`payload.description`。默认未勾选时,公共 payload 与既有实现逐字段一致。诊断读取失败时
不得发送部分摘要或声称已附加,必须返回可本地化的 `diagnostics-unavailable` 错误。
### 6.6 严格 HTTPS Client
反馈客户端使用 `undici` 的独立 `Agent`:
@@ -618,7 +641,8 @@ Endpoint 的健康检查和无副作用 schema 探针也必须在发布前通过
- 普通文本粘贴不被图片处理拦截。
- 截图选择、预览、替换、移除和 Object URL 清理。
- 并发截图读取按最新选择生效,关闭对话框后不会留下待处理 Object URL。
- 失败保留字段,重试使用原 `clientRequestId`。
- 失败保留字段、诊断勾选状态,重试使用原 `clientRequestId`。
- 默认提交不读取诊断且公共 payload 不变;主动附加、描述预算、无记录和读取失败均有覆盖。
- 成功显示真实编号并清空后续新草稿。
- Dialog role、初始焦点、Tab 循环、Escape 和焦点恢复。
- 浅色、深色、窄窗口和 reduced motion。
@@ -666,8 +690,9 @@ npm run build
- Endpoint 域名变化属于客户端发布变更,不能依赖 301/302 迁移。
- 入口发布与 `goodbuddy` 产品启用状态、生产 TLS 和 v1 契约保持同一发布门槛。
- 此功能不迁移现有用户数据;只新增可重置的随机反馈安装身份。
- 发布说明应明确:用户现在可以在“关于与更新”内直接提交反馈,邮箱和截图可选,应用不会
自动附加对话、日志、文件或凭据。
- 发布说明应明确:用户现在可以在“关于与更新”内直接提交反馈,邮箱和截图可选;桌面诊断
默认不上传,只有用户勾选后才附加有界摘要,并且不会发送对话、Prompt、凭据、文件内容、
路径、Provider 原始响应或远端 Agent 日志。
---
+34
View File
@@ -0,0 +1,34 @@
# 知识库
知识库已提供本地资料导入、解析、分块、索引、检索、图谱和带来源的知识问答。
将 Dify、FastGPT 和 RAGFlow 中已有知识库接入同一检索范围仍处于规划阶段,不属于
当前已提供能力。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [知识库检索与分块增强 PRD](./prd.md) | 检索、分块、诊断和产品验收 |
| [User Stories](./user-stories.md) | 知识使用者、维护者和调试者场景 |
| [UI 设计](./ui-design.md) | 当前本地知识库的信息架构、首次使用、状态与高级功能分层 |
| [实施进度](./progress.md) | 已验证实现事实、验证命令与剩余外部知识库工作 |
| [本地文本向量技术设计](./local-embedding-technical-design.md) | 本地与兼容向量连接、模型包、索引和进程边界 |
| [知识检索评估](./retrieval-evaluation.md) | 检索质量评估方法、数据集、指标和门槛 |
| [外部知识库接入 PRD](./external-knowledge-prd.md) | 外部实例、知识库绑定、检索范围和产品验收 |
| [外部知识库 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 和检索标准化 |
## 术语
- **外部实例**:一个由用户配置的 Dify、FastGPT 或 RAGFlow 服务地址及其认证信息。
- **外部知识库绑定**:GoodBuddy 中的知识库记录与外部实例内一个远端知识库 ID 的只读关联。
- **Provider 配置**:某一 Provider 检索接口独有的设置,例如 RAGFlow 图谱检索。
外部知识库只提供检索结果。GoodBuddy 不接入外部 App、Chat、Assistant、Workflow
或 Agent,也不在外部实例中创建、修改、上传或删除知识库内容。远端资料不会批量同步
或进入本地索引;实际用于回答的有界片段会随引用保存在本地会话中。
本目录尚未拆出统一逻辑设计;当前本地知识库状态规则由 PRD、User Stories 和实现契约
共同约束。外部知识库继续使用独立逻辑设计,直至进入实施阶段。
@@ -0,0 +1,343 @@
# 外部知识库逻辑设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 关联场景 | [外部知识库 User Stories](./external-knowledge-user-stories.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`。
## 2. 对象关系
```text
ExternalKnowledgeInstance 1 ─── n ExternalKnowledgeBinding
│
│ 1:1
▼
KnowledgeBase
```
- 实例可以没有绑定。
- 外部知识库只能绑定一个实例和一个远端知识库 ID。
- 同一实例的同一远端知识库默认只允许存在一个绑定,避免重复选择和重复检索。
- 允许不同实例绑定同名或相同远端 ID,因为实例边界不同。
- 本地知识库不存在 `ExternalKnowledgeBinding`。
## 3. 实例状态
实例状态由启停、凭据和最近探测三个维度组成,不能用一个布尔值代替。
### 3.1 持久状态
| 维度 | 值 |
| --- | --- |
| 启停 | `enabled`、`disabled` |
| 凭据 | `configured`、`missing`、`unavailable` |
| 探测 | `untested`、`catalog-ready`、`healthy`、`list-restricted`、`auth-failed`、`unreachable`、`incompatible`、`failed` |
`unavailable` 表示系统安全存储当前无法解密或保存凭据,与用户尚未配置的 `missing` 分开。
### 3.2 派生可用性
| 条件 | 目录读取 | 已绑定知识库检索 | 页面状态 |
| --- | --- | --- | --- |
| disabled | 禁止 | 禁止 | 已停用 |
| missing / unavailable | 禁止 | 禁止 | 缺少凭据 / 凭据不可用 |
| auth-failed | 禁止 | 禁止 | 认证失效 |
| unreachable | 可重试 | 可重试 | 无法连接 |
| incompatible | 禁止 | 禁止 | 接口不兼容 |
| list-restricted | 禁止 | 允许按 ID 验证与检索 | 需手工指定知识库 |
| catalog-ready | 允许 | 创建绑定时验证 | 目录可用,检索待验证 |
| healthy | 允许 | 允许 | 正常 |
| untested | 允许测试 | 已有绑定可尝试检索 | 未测试 |
保存新实例要求至少完成服务可达和认证验证。目录受限可以保存;检索接口尚未通过时,
实例不能用于创建绑定。
## 4. 连接测试状态机
```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,再执行检索探测。
检索请求正常完成但返回零条结果时,说明接口和目标可访问,可继续创建绑定;失败状态按错误
分类处理。
每次测试拥有 request ID 和取消信号。用户修改 Provider、地址、凭据或实例后,旧 request ID
的结果作废。
## 5. 目录读取规则
1. 只有选定、启用且凭据可用的实例可以读取目录。
2. Provider 列表接口按适配器定义分页,Main 最多向 Renderer 返回 500 个目录项。
3. FastGPT 列表中的文件夹和知识库必须按类型区分;文件夹不可作为绑定目标。
4. 列表搜索优先使用 Provider 支持的服务端过滤,不支持时只过滤已经有界加载的当前结果。
5. 下拉列表以远端 ID 为值,以远端名称为标签;重名时追加短 ID 或父级名称。
6. 已经绑定的目标显示“已添加”并禁止重复添加。
7. 远端列表项名称变化不自动修改 GoodBuddy 显示名称,只更新远端名称快照。
8. 刷新失败时可以保留上次成功目录供查看,但不得用旧目录完成新绑定,直到目标重新验证。
## 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 配置 | 测试结果 |
| 显示名称 | 不清除测试结果 |
## 7. 配置模型
### 7.1 通用配置
| 字段 | 范围 | 默认值 | 语义 |
| --- | --- | --- | --- |
| `resultLimit` | 1 至 20 | 6 | Provider 结果标准化后最多保留的片段数 |
| `requestTimeoutMs` | 1,000 至 60,000 | 15,000 | 单次远端请求总时限 |
| `maxSnippetCharacters` | 500 至 8,000 | 4,000 | 单片段进入 IPC 和 Runtime 前的字符上限 |
Provider 请求仍使用自己的候选数量或 Token 预算。`resultLimit` 只定义 GoodBuddy 最终保留
上限,不改写 Provider 字段的单位。
### 7.2 Dify 配置
| 字段 | 初始范围 | 说明 |
| --- | --- | --- |
| `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 要求时从知识库详情选择或填写 |
`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。
- 依据真实延迟确定默认并发上限和跨知识库上下文配额。
@@ -0,0 +1,254 @@
# 外部知识库接入 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 适用产品 | GoodBuddy 桌面端 |
| 关联逻辑 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 关联界面 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
| 技术设计 | [外部知识库技术设计](./external-knowledge-technical-design.md) |
## 1. 背景
不少企业已经在内网部署 Dify、FastGPT 或 RAGFlow,并在其中维护制度、产品、研发
和客服知识。继续要求用户把相同资料导入 GoodBuddy,会产生重复存储、版本分叉和
额外维护成本。
GoodBuddy 需要把这些已有知识库加入当前知识库列表和聊天检索范围。外部系统只
承担知识存储与检索,问题理解、上下文组织、模型回答、Agent 和工具执行仍由
GoodBuddy 负责。
## 2. 产品决策
1. 外部知识库属于现有“知识库”功能,不新增一级页面,也不放入全局设置中心。
2. 知识库页面标题区增加“管理外部实例”;现有“新建知识库”向导增加知识库类型。
3. 一个外部实例代表一个 Provider 服务地址和一组凭据,可绑定多个远端知识库。
4. 一个外部知识库绑定在 GoodBuddy 中仍是一个 `KnowledgeBase`,可以进入现有聊天
范围、每次先检索、模型按需检索和检索测试流程。
5. 外部知识库保持远端检索,不批量同步或本地索引远端文档、分块、向量和图谱,也不
显示“已同步”。实际进入回答的有界片段和引用随会话保存在本机,并遵循会话删除规则。
6. Dify、FastGPT 和 RAGFlow 只使用知识库列表、详情和检索接口。App、Chat、
Assistant、Workflow 和 Agent API 均不接入。
7. 用户点击“添加”只创建本地绑定,不在外部实例中创建知识库;移除绑定也不删除
远端知识库。
8. 通用配置只包含跨 Provider 语义一致的选项。检索算法、阈值、重排和图谱开关
由 Provider 配置承担。
9. Provider 专属字段由 GoodBuddy 内置适配器定义。外部服务器不能下发任意表单、
脚本或字段 Schema。
10. API Key 在用户输入表单和受限 IPC 中短暂存在,由 Main 进程加密保存并用于请求;
保存后不回传 Renderer,也不进入 Runtime、日志、普通元数据和检索引用。
## 3. 目标
### 3.1 用户目标
- 在知识库页面配置企业现有的 Dify、FastGPT 或 RAGFlow 实例。
- 新建知识库时选择 Provider、实例和远端知识库,不手工复制资料。
- 在同一知识库列表和聊天选择器中使用本地与外部知识库。
- 按 Provider 能力调整检索参数,例如 RAGFlow 图谱检索。
- 明确看到查询发送到哪个实例、检索是否成功以及引用来自哪里。
- 连接失效后能更新凭据、重试或更换远端知识库,不丢失其他知识库配置。
### 3.2 产品目标
- 保持现有知识库 ID 作为聊天和 Runtime 的统一授权单位。
- 让三种 Provider 的结果进入同一有界证据与引用契约。
- 保留每种 Provider 的有效检索能力,不用最低公共字段覆盖全部差异。
- 将实例认证与知识库绑定分离,使一个实例可以服务多个绑定。
- 对 Provider 版本差异、列表权限关闭和部分检索失败提供可诊断状态。
## 4. 非目标
本功能不包含:
- 调用 Dify App、FastGPT 应用、RAGFlow Chat 或任何外部 Agent。
- 使用外部系统生成最终回答或维护外部会话。
- 在外部实例中创建、编辑、删除知识库、文档或分块。
- 向外部实例上传文件、网页、剪贴板、完整会话或本地知识库内容。
- 批量同步或本地索引外部文档、分块、Embedding、图谱或模型配置。回答实际引用的
有界片段仍会随会话保存在本机。
- 跨实例复制、迁移或统一管理外部知识内容。
- 企业 SSO、组织级凭据下发、文档级 ACL 映射或 ShareServer 管理策略。
- 自定义脚本适配器和任意 HTTP 请求模板。兼容检索 API 需要独立设计。
- 把不同 Provider 的原始分数解释成可直接比较的统一质量分数。
## 5. 核心对象
| 对象 | 定义 | 用户可执行操作 |
| --- | --- | --- |
| 外部实例 | Provider、服务地址、凭据和已探测能力 | 添加、测试、编辑、停用、删除 |
| 外部知识库目录项 | 实例列表接口返回的远端知识库摘要 | 刷新、选择 |
| 外部知识库绑定 | GoodBuddy 知识库与远端知识库的只读关联 | 添加、改名、配置、测试、移除 |
| Provider 配置 | 适配器支持的检索参数 | 查看、修改、恢复默认 |
| 标准化检索结果 | Provider 结果转换后的片段、来源和诊断 | 查看、引用、提供给 Runtime |
## 6. 主要流程
### 6.1 管理外部实例
1. 用户在知识库页面点击“管理外部实例”。
2. 页面列出实例名称、Provider、服务地址、状态和已绑定知识库数量。
3. 用户添加实例,填写名称、Provider、服务地址和 API Key。
4. “测试连接”分别验证服务可达、认证、知识库列表和检索能力。
5. 用户保存后,API Key 被加密保存,列表只显示凭据已配置。
列表接口不可用但已知远端知识库 ID 可检索时,实例可以保存为“需手工指定知识库”。
服务不可达或认证失败时不得保存为可用实例;用户可以保留表单并修正。
### 6.2 添加外部知识库
1. 用户点击现有“新建知识库”。
2. 在“知识库类型”中选择“本地”、Dify、FastGPT 或 RAGFlow。
3. 选择外部类型后,左侧选择同 Provider 的外部实例。
4. GoodBuddy 自动读取远端知识库列表,并填入可搜索下拉框。
5. 列表权限不可用时,用户可以手工填写远端知识库 ID。
6. 用户选择一个远端知识库,填写 GoodBuddy 显示名称。
7. 右侧展示通用配置和当前 Provider 的专属配置。
8. 用户填写测试查询并执行一次有界检索;请求成功后创建本地绑定,零结果不算失败。
添加完成后沿用现有知识库规则,把新绑定加入当前聊天选择,但不会重新启用用户此前
取消的其他知识库。
### 6.3 使用外部知识库
外部绑定出现在现有知识库列表和聊天知识范围中,持续显示 Provider 与实例名称。
检索请求只发送本次查询文本和该绑定已保存的检索配置。返回片段经过 Main 校验、
截断和标准化后,才作为不可信证据提供给 Runtime。
一次请求选择多个知识库时,本地与外部检索可以并行。单个外部知识库失败不扩大
检索范围,也不切换其他实例;其他知识库成功时可继续回答,并显示部分失败。
### 6.4 编辑和移除
- 修改显示名称只影响 GoodBuddy 本地名称。
- 修改 Provider 配置只影响后续检索,不写回远端知识库。
- 更换远端知识库属于重新绑定,保存前必须重新测试。
- 删除外部实例前必须处理其所有绑定:取消,或明确移除这些本地绑定。
- 移除绑定只删除 GoodBuddy 中的绑定和检索配置,不调用远端删除接口。已经保存在历史
会话中的引用继续可读,但不再提供依赖当前实例的刷新操作。
## 7. 功能需求
### EK-FR-01 知识库页面入口
知识库页面必须同时提供“管理外部实例”和“新建知识库”。外部实例管理不得迁移到
全局设置中心或创建新的一级导航。
### EK-FR-02 实例配置
实例必须包含唯一 ID、显示名称、Provider、规范化服务地址、凭据状态、启停状态、
最近测试结果和适配器能力快照。用户可以使用 `keep`、`replace`、`clear` 语义更新凭据。
### EK-FR-03 分项连接测试
连接测试必须区分服务可达、认证、列表读取和测试检索。新增实例尚未选择知识库时,
测试检索显示“绑定知识库时验证”,不能伪装成已通过。测试错误应包含可处理原因,
不得返回凭据、Authorization、完整响应正文或内部堆栈。
### EK-FR-04 远端目录读取
选择实例后自动读取有界、可分页的远端知识库列表。列表支持加载、刷新、搜索、空结果、
权限不足和失败状态,且异步旧响应不得覆盖当前实例结果。
### EK-FR-05 手工指定远端 ID
列表接口不受支持或没有权限时,允许手工填写远端知识库 ID 和显示名称。保存前必须通过
该 ID 的详情或检索验证;不能只验证字符串非空。
### EK-FR-06 知识库类型
新建向导必须提供“本地”、Dify、FastGPT、RAGFlow 四种类型。本地类型保持现有创建、
存储模式、图谱和导入行为;外部类型不得显示本地分块、导入或索引设置。
### EK-FR-07 两栏配置
外部类型的创建向导在常规宽度使用左右两栏。左侧负责 Provider、实例、远端知识库和
显示名称;右侧负责通用配置与 Provider 专属配置。窄窗口按来源、通用配置、专属配置
顺序堆叠。
### EK-FR-08 通用配置
通用配置至少包含最终返回片段数、请求超时和单片段最大字符数。数值必须有固定范围,
由 Main 再次校验。Provider 的 Top K、Token 预算或 Page Size 不得冒充同一字段。
### EK-FR-09 Provider 专属配置
Provider 配置使用受版本控制的判别联合。界面只展示当前适配器和已确认实例版本支持的字段;
不支持的已保存字段必须明确显示为失效配置并阻止静默提交。
### EK-FR-10 统一知识库列表
外部绑定与本地知识库出现在同一列表,但必须显示 Provider、实例和“远端检索”状态。
外部绑定不得显示本地文档数、同步、分块、向量覆盖率或本地图谱状态。
### EK-FR-11 统一检索范围
现有聊天知识范围、多选上限、`auto` 和 `always` 模式继续以 GoodBuddy 知识库 ID 工作。
Runtime 只能获得标准化后的有界证据,不能获得实例地址、API Key 或 Provider 原始响应。
### EK-FR-12 引用来源
每条外部结果必须保留 Provider、实例 ID、远端知识库 ID、文档名称和片段。远端文档 ID、
远端片段 ID、页码、URL、分数及其他定位只在 Provider 实际返回时保存,不得补造。
### EK-FR-13 检索测试
现有检索测试入口支持外部绑定。页面显示生效配置、总耗时、Provider 耗时、返回数量、
截断状态和脱敏错误。外部测试不创建聊天、调用 GoodBuddy LLM 或修改远端内容。
### EK-FR-14 故障隔离
认证失败、超时、限流、目标删除、版本不兼容和响应无效必须分别报告。多知识库检索中,
单项失败不得改写为零结果,也不得阻止已成功证据进入回答。
### EK-FR-15 生命周期边界
删除实例、移除绑定、清除凭据和更换远端目标必须经过明确确认。任何操作都不得调用
Provider 的远端创建、更新、上传或删除接口。
### EK-FR-16 网络与数据说明
界面必须说明查询会发送到所选实例,并列出不会发送的内容。请求遵循用户配置的服务地址
和当前内网兼容模式,不自动切换地址、Provider 或公共云端点。HTTP 或放宽证书校验会
传输 API Key,测试和保存实例前必须显示风险并取得针对当前地址的明确确认。
## 8. Provider 初始范围
| Provider | 目录能力 | 检索能力 | 首批专属配置 |
| --- | --- | --- | --- |
| Dify | 知识库列表与详情 | Dataset Retrieve | 使用知识库默认配置,或完整覆盖检索方式、Top K、Score 阈值及 Rerank 模式 |
| FastGPT | 知识库层级列表与详情 | Dataset Search Test | 搜索模式、Token 上限、相似度、Rerank |
| RAGFlow | Dataset 列表与详情 | Retrieval | 相似度、向量权重、KNN Top K、Rerank、图谱检索、Knowledge Compilation |
“首批专属配置”表示适配器允许表达的字段,不保证每个实例版本都支持。具体显隐由能力
探测和[逻辑设计](./external-knowledge-logic-design.md)共同决定。
## 9. 产品验收
1. 用户可以在知识库页面完成三种 Provider 的实例添加、连接测试、编辑和删除。
2. 每种 Provider 至少通过一个真实自部署实例完成列表读取、绑定和检索。
3. 列表权限关闭时,可以通过手工 ID 添加并验证一个可检索知识库。
4. 本地与外部知识库可以同时进入一次聊天检索,引用来源可区分。
5. RAGFlow 支持时可以开启图谱检索;不支持时不发送该字段。
6. 外部知识库详情不出现本地导入、同步、分块和索引操作。
7. 删除绑定或实例不会改变远端知识库和文档。
8. 保存完成后的 Renderer 状态、Runtime、SQLite 普通元数据、日志和错误中均找不到明文
API Key。
9. 超时、401/403、404、429、5xx 和响应格式错误都有独立测试并显示准确状态。
10. 中断、切换实例或关闭向导后,过期列表与测试响应不会写入当前表单。
11. FastGPT 页面说明检索会记录远端 API 使用情况,并可能产生 Embedding 或 Rerank 用量。
12. HTTP 或放宽证书校验的实例未经明确确认不能保存或发送 API Key。
## 10. 外部资料
以下链接用于核对初始适配范围,实施时仍须用受支持版本的真实实例和响应 fixture 验证:
- [Dify Knowledge API](https://docs.dify.ai/en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api)
- [Dify Retrieve Chunks](https://docs.dify.ai/en/api-reference/knowledge-bases/retrieve-chunks-from-a-knowledge-base-test-retrieval)
- [FastGPT 知识库接口](https://doc.fastgpt.io/zh-CN/openapi/dataset)
- [RAGFlow HTTP API](https://ragflow.io/docs/dev/http_api_reference)
- [RAGFlow Release Notes](https://ragflow.io/docs/dev/release_notes)
@@ -0,0 +1,580 @@
# 外部知识库技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 产品需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 逻辑设计 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| UI 设计 | [外部知识库 UI 设计](./external-knowledge-ui-design.md) |
## 1. 设计约束
1. 外部知识库扩展现有知识库领域,不建立独立检索工具、聊天范围或 Runtime 权限模型。
2. 外部绑定使用远端检索,不落地远端文档、分块、向量和图谱。
3. 所有网络、凭据、Provider 响应校验和结果标准化都在 Main 完成。
4. Renderer 只能通过受限 IPC 管理实例、读取目录、保存绑定和发起测试。
5. Dify、FastGPT、RAGFlow 使用独立 Adapter 和判别配置,不通过任意 JSON 请求模板实现。
6. 首次实现只调用无内容修改语义的列表、详情和检索接口。
7. Provider API 变化通过真实 fixture、Adapter 版本和能力快照处理,不通过猜测字段兼容。
## 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` | 与预检索复用同一编排和引用标准化 |
外部 Provider 不是 `KnowledgeSourceType`。现有 `file | directory | url` 表示会进入本地
文档、分块和索引的资料来源;把远端绑定塞入该枚举会错误触发同步、分块和来源打开逻辑。
## 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
```
职责:
- `ExternalKnowledgeService`:实例、目录、绑定、测试和检索编排。
- `ExternalKnowledgeStore`:实例和绑定持久化,凭据加解密。
- `ExternalKnowledgeHttp`:超时、取消、大小限制、地址和响应读取。
- `ExternalKnowledgeRegistry`:按 Provider 返回内置 Adapter,不接受运行时插件注册。
- `ExternalKnowledgeNormalizer`:统一结果边界、截断和引用。
- Adapter:构造固定端点与请求,解析 Provider 响应,声明静态配置和能力条件。
`KnowledgeService` 继续作为聊天、检索测试和 MCP 网关的统一入口。它根据
`KnowledgeBase.kind` 调用现有本地检索或 `ExternalKnowledgeService.retrieve`。
## 4. Provider Adapter
```ts
type ExternalKnowledgeProvider = 'dify' | 'fastgpt' | 'ragflow'
interface ExternalKnowledgeAdapter<PersistedConfig, CapabilitySnapshot> {
readonly provider: ExternalKnowledgeProvider
readonly configVersion: number
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
}
```
`probeRetrieval` 可以复用 `retrieve`,但必须使用用户在创建向导中明确输入的测试查询。
不得生成会泄露组织信息的默认问题,不得调用 Provider 的 App 或模型问答接口,也不得
修改远端知识内容。Provider 自身记录 API 审计或计算检索用量不属于内容修改,必须由界面
提前说明。
Adapter 返回数据对象,不返回 React 组件、HTML 或远端 Schema。Renderer 中的配置定义
由共享的 Provider 判别联合和本地字段描述生成。
## 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'));
```
增加实例表:
```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
);
```
增加绑定表:
```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)
);
```
已有知识库迁移为 `kind = 'local'`。外部知识库仍保留现有名称、描述和创建时间,但本地
`storageMode`、图谱、分块和本地检索设置不进入外部请求;后续可以在数据库重构时再移除
这些历史必填默认值,本功能不为此扩大迁移范围。
### 6.2 凭据
复用 `SettingsCredentialCipher` 和 Electron `safeStorage`:
- `replace`:Main 加密新值后在一个事务中替换 envelope。
- `keep`:不读取、不重写现有 envelope。
- `clear`:确认后删除 envelope,并把实例凭据状态设为缺失。
- 安全存储不可用时拒绝新增或替换,不降级为明文。
- 解密只发生在发起请求前;请求结束后不缓存明文。
实例导出、诊断和数据库普通 JSON 视图均排除 `credential_envelope_json`。
## 7. IPC 设计
建议增加独立通道:
```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
```
所有 Handler 必须:
1. `assertTrustedSender`。
2. 用共享 Zod Schema 解析严格输入。
3. 根据实例 ID 在 Main 读取地址和凭据,不接受 Renderer 在检索请求中重复传入。
4. 使用稳定错误码返回脱敏错误。
5. 对网络操作支持 AbortSignal、总超时和响应大小限制。
目录和测试请求返回 `requestId`。Renderer 仍需校验当前类型、实例和目标与响应关联,避免
旧响应覆盖新表单。
## 8. HTTP 边界
### 8.1 服务地址
- 只接受 `http:` 和 `https:`。
- 拒绝用户名、密码、查询和片段。
- Adapter 负责规范化 Provider 所需 API 根路径,避免用户填写完整操作端点。
- 请求只能拼接 Adapter 内置相对路径,远端目录项或错误不能改变目标 URL。
- 重定向默认拒绝;若某 Provider 的受支持部署确需重定向,只允许同源且不得跨跳转发送
Authorization。
- 当前全局“内网兼容模式”决定是否允许 HTTP 和非标准 HTTPS 证书;操作开始后冻结该值。
- HTTP 或实际使用证书例外时,Main 返回 `transportSecurity` 风险状态。用户必须针对规范化
后的当前地址明确确认,才能保存实例或发送 API Key;地址变化立即使旧确认失效。
- 不进行公共云回退,不把自部署地址替换成 Provider 官方地址。
用户明确配置内网地址属于产品用途,因此不能采用禁止私网地址的通用 SSRF 规则。安全边界
依靠显式实例配置、Main-only 请求、固定端点、无任意方法和无远端 URL 跟随。
### 8.2 请求限制
- 默认超时 15 秒,用户可在 1 至 60 秒内调整。
- 目录、详情和检索分别设置响应字节上限。
- JSON 解析后仍执行深度、数组数量、字符串长度和未知字段策略。
- 默认不自动重试。用户重试或上层下一次检索会产生新请求。
- 取消、应用关闭和实例删除立即终止相关请求。
- 日志只记录 Provider、实例 ID、操作、状态码、稳定错误码、耗时和字节数。
## 9. Provider 映射
本节记录 2026-09-03 核对的初始公开接口。实施时应把每个受支持版本的真实响应保存为
去敏 fixture,并以 fixture 和真实实例测试作为发布依据。
### 9.1 Dify
| 操作 | 接口 |
| --- | --- |
| 列表 | `GET /v1/datasets` |
| 检索 | `POST /v1/datasets/{dataset_id}/retrieve` |
| 认证 | `Authorization: Bearer <knowledge-api-key>` |
Adapter 只接受 Knowledge Service API Key,不调用应用 `/info`、Chat 或 Workflow。默认不
发送 `retrieval_model`,沿用远端知识库配置。用户选择覆盖时,Adapter 必须一次收集并验证
当前接口要求的搜索方式、Top K、阈值、`reranking_mode`,以及相应的 Rerank Provider/模型
或关键词/向量权重与 Embedding Provider/模型;缺少任何必填标识时不构造请求。响应从
`records[].segment`、`score`、文档名称和元数据提取标准化引用。
Dify 当前文档限制查询不超过 250 字符。GoodBuddy 不静默截断;超出时检索测试和聊天
诊断返回 Provider 限制,并允许后续单独设计查询压缩策略。
### 9.2 FastGPT
| 操作 | 接口 |
| --- | --- |
| 列表 | `POST /api/core/dataset/list` |
| 详情 | `GET /api/core/dataset/detail?id=...` |
| 检索 | `POST /api/core/dataset/searchTest` |
| 认证 | `Authorization: Bearer <api-key>` |
列表响应同时包含文件夹和知识库,只有 `type = dataset` 可以绑定。检索映射 `datasetId`、
`text`、Token `limit`、`similarity`、`searchMode` 和 `usingReRank`。初始版本不发送查询优化
字段,避免调用 FastGPT 配置的 LLM。
响应从 `q`、`a`、`sourceName`、`sourceId`、`collectionId` 和 `score` 构造片段与引用。
FastGPT 4.15.0 起部署实例的 `/apidoc/devapi` 是接口事实来源,手工文档可能落后。支持矩阵
必须按真实版本测试;不能因为健康页可访问就假定列表和检索 Schema 相同。`searchTest`
可能记录 `SEARCH_TEST` 等远端审计并更新 API Key 用量;Embedding 和 Rerank 也可能产生
费用。GoodBuddy 将这些行为作为检索成本显示,不宣称该接口没有副作用。
### 9.3 RAGFlow
| 操作 | 接口 |
| --- | --- |
| 列表/详情 | `GET /api/v1/datasets` |
| 检索 | `POST /api/v1/retrieval` |
| 认证 | `Authorization: Bearer <api-key>` |
检索映射 `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` 提取片段、文档、位置和原始相似度。
图谱规则:
- `parser_config.graphrag` 或等价详情字段作为已有图谱的证据,不写回。
- `use_kg` 是检索时开关,只在已有图谱和受支持版本均确认后发送。
- Knowledge Compilation 与旧 GraphRAG 分别建模。
- `include_knowledge_compilation` 使用本地保存的显式布尔值,初始默认 false;即使服务端
默认 true,也不依赖字段省略获得隐式行为。
- Provider 没有返回图节点或路径证据时,GoodBuddy 不生成图谱引用。
## 10. 检索编排
```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
```
预检索和 `knowledge_search` 工具必须调用同一个 `retrieveMany`。如果只修改聊天预检索而
未修改 `KnowledgeMcpGateway`,`auto` 和 `always` 会产生不同能力,这是阻断发布的问题。
### 10.1 上下文预算
- 每个知识库先保留不超过其 `resultLimit` 的结果。
- 先给每个成功知识库一个最小有界配额,再按稳定顺序分配剩余字符。
- 不用不同 Provider 原始分数决定跨库优先级。
- 片段截断在 Main 完成并在引用中标记。
- 相同实例的多个绑定仍按绑定分别请求,除非 Provider 明确支持且 Adapter 实现等价的多
Dataset 请求;合并不得改变每个绑定的配置和诊断。
### 10.2 失败结果
返回结构包含每个知识库的:
```ts
interface KnowledgeRetrievalOutcome {
knowledgeBaseId: string
status: 'success' | 'empty' | 'failed' | 'cancelled'
durationMs: number
resultCount: number
errorCode?: ExternalKnowledgeErrorCode
truncated: boolean
}
```
至少一个 `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、固定端点、传输风险确认 | 地址、证书和凭据泄漏测试 |
@@ -0,0 +1,401 @@
# 外部知识库 UI 设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 产品需求 | [外部知识库接入 PRD](./external-knowledge-prd.md) |
| 行为规则 | [外部知识库逻辑设计](./external-knowledge-logic-design.md) |
| 全局规范 | [GoodBuddy UI 设计系统](../../../UI-DESIGN.md) |
## 1. 信息架构
外部知识库不增加左侧主导航和知识库一级页签。现有知识库页面结构保持:
```text
知识库
├─ 页面标题区
│ ├─ 管理外部实例
│ └─ 新建知识库
├─ 知识库列表
└─ 当前知识库详情
```
“管理外部实例”管理可复用连接;“新建知识库”负责创建本地知识库或添加一个远端绑定。
## 2. 页面标题区
常规宽度:
```text
知识库 [管理外部实例] [新建知识库]
导入、检索并维护本地和企业现有知识
范围:全局
```
- “新建知识库”保持主按钮。
- “管理外部实例”是带服务器图标的次按钮。
- 沿用知识库页面的全局 `ScopeBadge`,创建和管理外部连接时持续可见。
- 按钮使用文字和图标,不增加只含缩写的厂商快捷入口。
- 窄窗口下两个按钮换到标题下方,保持同一行;空间不足时各占一半宽度。
对应:`EK-FR-01`。
## 3. 知识库列表
本地和外部知识库保留在同一主列表。条目采用现有列表密度,不增加卡片墙。
```text
研发规范
RAGFlow · 研发知识平台 · 远端可检索
产品资料
本地 · 12 个来源 · 全文与向量可用
```
### 3.1 外部条目字段
- GoodBuddy 显示名称。
- Provider 图标或文字徽标。
- 外部实例名称。
- “远端可检索”、认证失效、目标不存在等准确状态。
- 最近一次成功检索时间;从未检索时显示“尚未检索”。
外部条目不显示来源数量、同步进度、向量覆盖率、本地图谱或导入失败数。
### 3.2 列表筛选
现有搜索同时匹配 GoodBuddy 名称、远端名称、Provider 和实例名称。来源筛选使用菜单:
```text
全部来源
本地
Dify
FastGPT
RAGFlow
```
筛选不是新的页面页签。
## 4. 管理外部实例
点击“管理外部实例”打开大尺寸 Modal。Modal 使用标题、可滚动内容和固定操作区,遵循
全局焦点陷阱、Escape、背景隔离和焦点恢复规则。
```text
┌──────────────────────────────────────────────────────────────┐
│ 外部实例 [添加实例] [×] │
│ 管理 Dify、FastGPT 和 RAGFlow 的连接 │
├──────────────────────────────────────────────────────────────┤
│ 名称 Provider 服务地址 状态 操作 │
│ 研发知识平台 RAGFlow ragflow.example 正常 ⋯ │
│ 客服知识平台 FastGPT fastgpt.example 认证失效 ⋯ │
└──────────────────────────────────────────────────────────────┘
```
### 4.1 实例行
显示:
- 名称、Provider、脱敏后的主机名、状态。
- 已绑定知识库数量。
- 最近测试时间。
- 行尾菜单:测试连接、编辑、停用或启用、删除。
服务地址可以显示 scheme、host 和非默认端口,不显示路径中的敏感信息、查询或凭据。
### 4.2 空状态
```text
尚未配置外部实例
添加企业现有的 Dify、FastGPT 或 RAGFlow 服务。
[添加实例]
```
筛选无结果和加载失败使用不同 `EmptyState` 变体。
## 5. 添加和编辑实例
实例表单在管理 Modal 内切换为编辑面板,不叠加第二层 Modal。
```text
添加外部实例
实例名称 [研发知识平台 ]
Provider [RAGFlow ▾]
服务地址 [https://ragflow.example ]
认证方式 API Key
API Key [•••••••••••••••• ]
凭据状态 尚未保存
连接测试
服务连接 未测试
身份认证 未测试
知识库列表 未测试
检索接口 绑定知识库时验证
[取消] [测试连接] [保存实例]
```
### 5.1 字段行为
- Provider 使用原生 Select;编辑已被绑定的实例时不可切换 Provider。
- 服务地址只接受完整 HTTP(S) 地址。字段下方说明其受全局内网兼容模式影响。
- API Key 使用密码输入和显示/隐藏图标。明文会在当前表单和保存 IPC 中短暂存在;编辑时
留空表示保留,不使用占位假密钥。
- 已保存凭据旁提供“替换凭据”和“清除凭据”,清除属于需要确认的操作。
- 地址使用 HTTP,或连接测试依赖放宽证书校验时,在测试和保存操作旁持续显示传输风险。
用户必须针对当前地址确认后才能保存;地址变化会清除该确认。
- “测试连接”不保存草稿;测试期间按钮显示进度并提供取消。
- 保存成功进入应用通知,Modal 返回实例列表并聚焦对应行。
### 5.2 测试结果
分项状态使用图标、文字和说明,不只依靠颜色:
```text
服务连接 通过 183 ms
身份认证 通过
知识库列表 受限 当前凭据不能读取列表,可手工填写知识库 ID
检索接口 待验证 添加知识库时执行
```
长错误折叠显示,正文经过脱敏。失败后保留全部草稿和 API Key 输入。
FastGPT 实例额外显示:`检索会计入该实例的 API 使用记录,并可能产生 Embedding 或
Rerank 用量。`
## 6. 新建知识库向导
扩展现有页面内 `CreateLibraryWizard`,不改为新的全屏页面或 Modal。
### 6.1 类型选择
表单第一项使用单选卡片或共享 SegmentedControl:
```text
知识库类型
[本地] [Dify] [FastGPT] [RAGFlow]
```
只有选择本地时显示现有存储模式、图谱和本地说明。外部类型选中后显示两栏配置。
### 6.2 外部类型两栏布局
```text
┌──────────────────────────────┬──────────────────────────────┐
│ 知识库来源 │ 检索配置 │
│ │ │
│ Provider RAGFlow │ 通用配置 │
│ 外部实例 [研发平台 ▾] │ 返回片段数 [ 6 ] │
│ 远端知识库 [研发规范 ▾] │ 请求超时 [15 秒] │
│ [↻] │ 单片段上限 [4000] │
│ [手工填写知识库 ID] │ │
│ │ RAGFlow 配置 │
│ 显示名称 [研发规范 ]│ 相似度阈值 [0.20] │
│ 远端名称 研发规范 │ 向量相似度权重 [0.30] │
│ 远端 ID 7f31…c902 │ KNN Top K [1024] │
│ 测试查询 [部署网络要求 ]│ 图谱检索 [开关] │
│ │ Knowledge Compilation [开关]│
├──────────────────────────────┴──────────────────────────────┤
│ 将向研发知识平台发送测试查询。不会上传文件或完整会话。 │
│ [取消] [测试检索] [添加知识库] │
└─────────────────────────────────────────────────────────────┘
```
测试查询由用户填写,不自动生成,也不取用当前会话内容。“添加知识库”在真实检索请求
成功前禁用;成功返回零条结果仍可添加,但界面显示“检索成功,未找到匹配片段”。验证结果
在底部说明区展示,不用成功 Alert 撑高表单。
添加成功后沿用本地知识库行为,把新绑定加入当前聊天选择,不重新启用其他已取消知识库。
### 6.3 目录下拉框
- 选择实例后自动打开加载状态,不要求再点击“读取”。
- 使用可搜索 Select,选项显示名称、远端短 ID 和可用状态。
- 右侧刷新使用刷新图标按钮,Tooltip 为“刷新知识库列表”。
- 加载时保持控件稳定尺寸,显示“正在读取知识库…”。
- 目录为空显示“当前实例没有可访问的知识库”。
- 403 或不支持列表时显示原因和“手工填写知识库 ID”。
- 列表失败后显示“重试”,不自动切换到手工模式。
### 6.4 手工模式
```text
远端知识库 ID [ ]
显示名称 [ ]
[验证知识库]
```
验证成功后显示远端名称和 Provider 返回的摘要。返回自动列表不会丢弃已输入 ID,切换前
如有未保存输入须确认。
## 7. 配置面板
右侧配置固定分为“通用配置”和“{Provider} 配置”,不创建嵌套卡片。高级字段可以使用
一个折叠区,但核心检索方式、阈值和 RAGFlow 图谱检索保持直接可见。
控件选择:
- 开关:图谱检索、Score 阈值启用、Rerank、使用 Dify 知识库默认配置。
- Select 或 SegmentedControl:少量固定检索方式。
- 数字输入或 Stepper:结果数、Token、Top K、超时。
- Slider 加数字输入:0 至 1 的阈值和权重,二者保持同步。
- Metadata 条件:初始版本不提供自由 JSON 编辑器。
字段显隐由 GoodBuddy 内置 Provider 描述和 Main 返回的能力状态共同决定。服务端返回的
任意字符串不得变成 HTML、控件标签或帮助链接。
### 7.1 RAGFlow 图谱文案
开关名称:`图谱检索`
说明:`检索远端知识库已经生成的图谱内容,不会创建或更新图谱。`
只有远端详情或受控探测表明图谱已经构建时,该开关才可用。证据缺失时显示“未确认远端
图谱状态”并禁用。Knowledge Compilation 单独显示为 `包含 Knowledge Compilation`,默认
关闭,不把它和旧 GraphRAG 合成同一开关。
## 8. 外部知识库详情
选中外部绑定后,现有详情区按来源类型裁剪:
```text
研发规范 RAGFlow · 远端检索
研发知识平台 / 远端知识库 7f31…c902
[概览] [检索配置] [检索测试]
```
### 8.1 概览
显示 Provider、实例、远端名称、远端 ID、连接状态、最近成功检索和数据位置。操作包括:
- 刷新远端信息。
- 更换远端知识库。
- 编辑 GoodBuddy 显示名称。
- 从 GoodBuddy 移除。
不显示导入文件、添加目录、添加网页、同步、分块、重建索引和本地图谱入口。
### 8.2 检索配置
复用创建向导右侧配置面板。存在失效字段时,页头显示“配置需要确认”,字段附近说明
Provider 版本变化,保存按钮保持禁用直到用户处理。
### 8.3 检索测试
复用现有检索测试工作台的查询、结果和焦点行为,但外部结果采用 Provider 诊断:
```text
RAGFlow · 研发知识平台
总耗时 642 ms · 返回 6 条 · 保留 6 条
1 部署规范.pdf
第四章 网络要求…
Provider 分数 0.82 · 文档 ID 98ab…
```
外部测试不显示 FTS/CJK/本地向量扫描等本地通道,也不提供依赖本地分块的“查看上下文”。
## 9. 聊天知识范围
现有多选列表保留一个选择面,只在每行增加来源:
```text
[✓] 产品资料 本地
[✓] 研发规范 RAGFlow · 研发知识平台
[ ] 客服知识 FastGPT · 客服平台
```
- 不按 Provider 创建多套选择器。
- 不可用绑定保留显示但禁用勾选,并给出认证失效或目标不存在原因。
- 当前会话已选绑定变为不可用时,不静默移除 ID;发送前提示该知识库本次无法检索。
## 10. 执行状态和引用
活动状态:
```text
正在检索“研发规范”
RAGFlow · 研发知识平台 · 已发送当前检索词
```
部分失败:
```text
已检索 3 个知识库,2 个成功,1 个超时
[查看详情]
```
引用:
```text
部署规范.pdf / 网络要求
研发规范 · RAGFlow · 研发知识平台
```
引用详情显示远端文档名、ID、片段、Provider 分数和实际返回的定位。未完成 URL 安全设计前,
不显示“打开远端来源”。
## 11. 删除确认
移除绑定:
```text
从 GoodBuddy 移除“研发规范”?
这会删除本机绑定和检索配置,不会删除 RAGFlow 中的知识库或文档。
[取消] [移除]
```
删除有绑定的实例:
```text
“研发知识平台”仍被 3 个知识库使用
删除实例会同时从 GoodBuddy 移除这些绑定。远端数据不会改变。
[取消] [查看知识库] [删除实例和本地绑定]
```
破坏性主操作使用 `danger-solid`,默认焦点放在“取消”。
## 12. 可访问性与响应式
- 常规宽度两栏使用稳定 Grid;小于 `960px` 时改为单栏,顺序为来源、通用配置、Provider 配置。
- 小于 `720px` 时外部实例表格转为单列列表,行操作保留菜单按钮和完整可访问名称。
- Modal、创建向导、下拉框和折叠区支持完整键盘操作。
- 异步列表和测试状态使用 `aria-live="polite"`;错误使用与字段关联的 `role="alert"`。
- 图标按钮必须提供 Tooltip 和 `aria-label`。
- 状态不只靠颜色;加载、成功、受限和失败均包含文字。
- 长实例名、地址、远端名称和 ID 使用单行省略,完整值可通过 Tooltip 或详情查看。
- 切换类型、实例或退出时存在有效草稿,应按全局离开保护规则确认。
## 13. 文案表
| 场景 | 文案 |
| --- | --- |
| 页面操作 | 管理外部实例 |
| 创建类型 | 本地 / Dify / FastGPT / RAGFlow |
| 创建主操作 | 添加知识库 |
| 列表加载 | 正在读取知识库… |
| 列表受限 | 当前凭据不能读取知识库列表,可手工填写知识库 ID。 |
| 远端状态 | 远端可检索 |
| 移除操作 | 从 GoodBuddy 移除 |
| 图谱开关 | 图谱检索 |
| 测试说明 | 测试只发送当前查询,不会调用 GoodBuddy 模型或修改远端内容。 |
产品文案不使用“同步外部知识库”“导入 Dify 知识库”或“创建远端知识库”,因为这些动作
都不是本功能的实际行为。
## 14. 需求追踪
| UI 区域 | 需求 | 场景 |
| --- | --- | --- |
| 页面标题与统一列表 | `EK-FR-01`、`EK-FR-10` | `EK-US-C1` |
| 外部实例管理与表单 | `EK-FR-02`、`EK-FR-03`、`EK-FR-15`、`EK-FR-16` | `EK-US-A1` 至 `EK-US-A3` |
| 新建知识库向导 | `EK-FR-04` 至 `EK-FR-09` | `EK-US-B1` 至 `EK-US-B4` |
| 外部详情与检索测试 | `EK-FR-12`、`EK-FR-13` | `EK-US-C3`、`EK-US-D1` 至 `EK-US-D4` |
| 聊天范围、状态和引用 | `EK-FR-11`、`EK-FR-12`、`EK-FR-14` | `EK-US-C1` 至 `EK-US-C4` |
@@ -0,0 +1,212 @@
# 外部知识库 User Stories
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计稿 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [外部知识库接入 PRD](./external-knowledge-prd.md) |
## 1. 角色
- **知识库使用者**:在聊天中选择已经添加的本地或外部知识库并查看引用。
- **实例维护者**:持有外部知识库 API Key,负责配置实例、选择远端知识库和处理失效连接。
- **RAG 调试者**:需要调整 Provider 参数并直接验证检索结果。
## 2. 实例管理
### EK-US-A1 添加外部实例
作为实例维护者,我希望在知识库页面保存一个外部实例,以便后续绑定其中的多个知识库。
验收:
- Given 用户打开“管理外部实例”并选择 Dify、FastGPT 或 RAGFlow
- When 用户填写名称、服务地址和 API Key,并执行连接测试
- Then 页面分别显示服务、认证、目录和检索检查结果
- And 保存后只显示“凭据已配置”,不回显 API Key
- And 新实例可被同 Provider 的新建知识库向导选择
- And 使用 HTTP 或放宽证书校验时,保存前明确提示 API Key 的传输风险
### EK-US-A2 修复失效凭据
作为实例维护者,我希望替换认证失效的 API Key,同时保留已有知识库绑定。
验收:
- Given 实例因认证失败处于“认证失效”状态
- When 用户选择“替换凭据”、测试并保存
- Then 所有绑定继续引用同一实例 ID
- And 旧密钥不再使用
- And 用户没有输入新密钥时可以取消且不清除旧密钥
### EK-US-A3 删除仍被使用的实例
作为实例维护者,我希望删除实例前看到受影响的知识库,以免留下无法解释的绑定。
验收:
- Given 实例仍被一个或多个外部知识库绑定引用
- When 用户点击“删除实例”
- Then 确认界面列出受影响数量和名称
- And 用户可以取消,或明确同时移除本地绑定
- And 确认后不向外部实例发送删除请求
## 3. 添加知识库
### EK-US-B1 从远端列表添加
作为实例维护者,我希望选择 Provider 和实例后自动看到远端知识库,以便避免手工复制 ID。
验收:
- Given 至少存在一个可用的对应 Provider 实例
- When 用户在“新建知识库”中选择外部类型和实例
- Then GoodBuddy 自动读取有界知识库列表
- And 下拉框支持按名称搜索与刷新
- And 目录只显示当前凭据可见的知识库,不推断隐藏对象
- And 切换实例后旧实例的异步响应不会覆盖新列表
### EK-US-B2 手工填写远端 ID
作为实例维护者,我希望列表接口受限时仍可通过远端 ID 添加已有知识库。
验收:
- Given 连接测试表明认证和检索可用,但目录读取无权限或不受支持
- When 用户进入手工模式并填写远端知识库 ID
- Then GoodBuddy 使用详情或测试检索验证该目标
- And 验证失败时保留输入并显示原因
- And 未经真实验证不能完成添加
### EK-US-B3 配置 RAGFlow 图谱检索
作为 RAG 调试者,我希望在添加 RAGFlow 知识库时设置图谱检索,以便使用远端已经构建的图谱内容。
验收:
- Given 适配器确认实例检索接口支持 `use_kg`
- When 用户选择一个具备相关能力的 RAGFlow 知识库
- Then 右侧显示“图谱检索”开关并说明它只影响检索
- And 开启后测试请求携带该选项
- And 只有远端知识库存在已构建图谱的证据时才允许开启
- And 页面不把该开关描述为构建或更新远端图谱
- And 能力不明确时该开关禁用并说明原因
### EK-US-B4 保持本地创建流程
作为本地知识库用户,我希望新增外部类型后仍能按原流程创建本地知识库。
验收:
- Given 用户在新建向导中选择“本地”
- When 用户填写名称、存储模式和图谱设置
- Then 表单字段、默认值和创建结果与现有行为一致
- And 外部实例、远端 ID 和 Provider 配置不出现
## 4. 检索和引用
### EK-US-C1 在聊天中选择外部知识库
作为知识库使用者,我希望外部知识库出现在现有知识范围选择器中,以便与本地知识库一起使用。
验收:
- Given 外部知识库绑定处于可检索状态
- When 用户打开聊天知识范围
- Then 它与本地知识库出现在同一选择器中
- And 条目持续显示 Provider 和实例名称
- And 选择结果仍保存 GoodBuddy 知识库 ID
- And 新添加的绑定进入当前聊天选择,但不重新启用其他已取消知识库
### EK-US-C2 只发送检索查询
作为知识库使用者,我希望清楚知道哪些数据会发送到企业知识库。
验收:
- Given 本次请求选择了外部知识库
- When GoodBuddy 执行检索
- Then 只发送本次检索查询、远端知识库 ID 和已保存检索参数
- And 不发送完整会话、本地附件、其他知识库内容、系统提示或模型凭据
- And 活动状态显示目标 Provider 和实例
### EK-US-C3 查看外部引用
作为知识库使用者,我希望回答中的引用标明外部来源,以便回到企业知识系统核验。
验收:
- Given Provider 返回文档名、片段 ID、内容和分数
- When GoodBuddy 展示引用
- Then 引用显示 Provider、实例、知识库和文档名称
- And 详情只显示 Provider 实际返回且通过校验的定位信息
- And 缺失页码、链接或分数时不生成替代值
### EK-US-C4 多知识库部分失败
作为知识库使用者,我希望一个外部实例暂时失败时仍能使用其他成功结果。
验收:
- Given 本次选择了本地知识库和两个外部知识库
- When 一个外部请求超时,其他请求成功
- Then 成功证据继续进入回答
- And 页面显示一个知识库检索失败及其原因
- And 超时不显示为“未找到相关内容”
- And GoodBuddy 不自动改用同 Provider 的其他实例
## 5. 配置与诊断
### EK-US-D1 调整 Provider 配置
作为 RAG 调试者,我希望只看到当前 Provider 支持的参数,以便理解设置会怎样影响远端检索。
验收:
- Given 用户编辑一个外部知识库绑定
- When 页面读取实例能力和已保存配置
- Then 通用配置与 Provider 专属配置分区显示
- And 每个字段显示范围、默认值和作用
- And 不受支持的字段不会被静默发送
- And 恢复默认只恢复当前绑定的 Provider 配置
### EK-US-D2 运行检索测试
作为 RAG 调试者,我希望在知识库页面直接测试外部检索,不经过聊天模型。
验收:
- Given 当前选中一个外部知识库
- When 用户输入查询并点击“测试检索”
- Then 页面显示 Provider 耗时、返回数量、片段、来源和截断状态
- And 测试不创建聊天消息、不调用 GoodBuddy LLM、不修改远端内容
- And 远端原始响应和凭据不进入界面或日志
- And FastGPT 测试明确提示远端可能记录 API 使用并产生检索或重排用量
### EK-US-D3 Provider 版本变化
作为实例维护者,我希望升级外部系统后看到配置是否仍受支持。
验收:
- Given 最近一次能力探测与已保存快照不同
- When 用户测试实例或打开绑定设置
- Then 页面指出新增、失效或行为变化的配置
- And 失效配置不会静默删除或继续发送
- And 用户确认有效配置后才更新绑定
### EK-US-D4 远端知识库被删除
作为实例维护者,我希望远端目标不存在时看到准确状态并重新绑定。
验收:
- Given Provider 返回目标不存在
- When 用户打开知识库或执行检索
- Then 绑定状态显示“远端知识库不存在”
- And 聊天选择器不把它显示为可用
- And 用户可以刷新目录并选择新的远端知识库
- And 保存新目标前必须重新测试
@@ -10,7 +10,7 @@
| 日期 | 2026-08-22 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 相关基线 | [知识库检索与分块增强 PRD](../prd/knowledge/knowledge-rag-enhancement-prd.md)、[模型下载源设计](./model-download-source-design.md)、[知识检索评估](../quality/knowledge-retrieval-evaluation.md)、[统一界面设计系统](../../UI-DESIGN.md) |
| 相关基线 | [知识库检索与分块增强 PRD](./prd.md)、[模型下载源设计](../../architecture/model-download-source-design.md)、[知识检索评估](./retrieval-evaluation.md)、[统一界面设计系统](../../../UI-DESIGN.md) |
本文定义 GoodBuddy 的文本向量模型产品形态,包括 GoodBuddy 提供的内置本地连接、
用户添加的 OpenAI 兼容连接、模型管理界面、进程边界、编码契约、索引兼容性、失败状态
@@ -162,7 +162,7 @@ GoodBuddy 当前已经具备:
引用和脱敏诊断。
- Agent Runtime 的选择或切换不改变当前向量连接;向量连接的选择或切换也不改变当前
Agent Runtime。
- 未来记忆、笔记或其他本地语义检索若要复用向量连接,必须经过独立产品设计、权限与
- 分区记忆、笔记或其他本地语义检索若要复用向量连接,必须经过独立产品设计、权限与
数据路径评审后显式接入,不能因存在全局向量连接而自动获得使用权。
---
@@ -912,7 +912,7 @@ Endpoint、编码配置或服务端稳定模型摘要变化。
- 目录只包含元数据,不包含模型权重。
- 每个来源的文件固定不可变 Revision、URL、字节数和 SHA-256。
- 实际下载使用“平台功能 → 通用设置”中显式选择的 ModelScope 或 Hugging Face,完整
来源契约以[平台功能页签与模型下载源设计](./model-download-source-design.md)为准。
来源契约以[平台功能页签与模型下载源设计](../../architecture/model-download-source-design.md)为准。
- 下载写入随机暂存目录和 `.partial` 文件。
- 禁止未声明重定向到其他主机。
- 每个文件边下载边计算摘要。
@@ -1084,7 +1084,7 @@ GoodBuddy 只持续显示规范化 Endpoint 主机、实际维度和诊断结果
4. **质量评测**:中文、中英文混合、跨语言和代码标识固定样例。
5. **平台评测**:六个平台的安装、加载、取消、退出和资源。
现有[知识检索评估](../quality/knowledge-retrieval-evaluation.md)中的确定性内存 Provider
现有[知识检索评估](./retrieval-evaluation.md)中的确定性内存 Provider
用于验证生产检索管道,不代表真实模型质量。内置模型需要单独的可选本地评测命令,
该命令不读取用户数据库、不联网、不进入默认 CI。
@@ -5,11 +5,11 @@
| 项目 | 内容 |
| --- | --- |
| 状态 | 实施中 |
| 版本 | 0.2 |
| 日期 | 2026-08-22 |
| 版本 | 0.3 |
| 日期 | 2026-09-05 |
| 适用产品 | GoodBuddy 桌面端 |
| 实施范围 | 第一阶段:可用、可见、可诊断;第二阶段:可调、可优化、可维护 |
| 相关设计 | [本地文本向量模型与连接设计](../../architecture/local-text-embedding-model-design.md) |
| 相关设计 | [本地文本向量模型与连接设计](./local-embedding-technical-design.md) |
## 1. 背景
@@ -49,7 +49,7 @@ OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来
跨平台向量扩展前,接受本地 CPU 线性扫描,并持续显示性能诊断。
9. 向量索引兼容性同时校验 Provider、Model、维度和 Provider Fingerprint。
同名模型切换端点后,旧向量不能继续参与召回。Fingerprint 的完整模型、编码与
数据路径定义以[本地文本向量模型与连接设计](../../architecture/local-text-embedding-model-design.md)
数据路径定义以[本地文本向量模型与连接设计](./local-embedding-technical-design.md)
为准。
10. 失败或取消的重建不能停用上一版已就绪索引。新索引只有完整校验成功后才
原子替换当前服务版本。
@@ -149,7 +149,7 @@ OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来
## 6. 信息架构
知识工作区继续使用主从布局和现有四个页签:
知识工作区使用主从布局和四个面向用户任务的页签:
```text
知识库
@@ -160,18 +160,25 @@ OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来
│ └─ 分块查看与维护
├─ 知识图谱
├─ 任务中心
└─ 设置
└─ 高级设置
├─ 问答效果入口
├─ 向量索引
├─ 检索设置
├─ 分块设置
└─ 图谱设置
```
“检索测试”是当前知识库的高频诊断操作,通过知识库标题区次操作打开独立
工作台,不新增第五个一级页签。
“检索测试”是当前知识库的高频验证操作,通过知识库标题区和“高级设置 →
问答效果”打开同一个独立工作台,不新增第五个一级页签。工作台首次打开只突出真实
问题输入和测试操作;候选倍数、权重、阈值、重排和上下文预算默认折叠。
首次使用流程固定为“创建知识库 → 添加资料 → 等待可检索 → 用于当前对话”。创建表单
默认只展示名称和描述;存储方式与知识图谱属于折叠的高级设置。没有知识库时,唯一主操作
位于首次使用空状态中。
全局向量模型仍在“设置 → 模型连接 → 向量模型”中配置。GoodBuddy 内置连接和用户添加的
OpenAI 兼容连接使用同一连接列表;其界面、数据路径及切换语义以
[本地文本向量模型与连接设计](../../architecture/local-text-embedding-model-design.md)
[本地文本向量模型与连接设计](./local-embedding-technical-design.md)
为准,知识库页面只显示当前模型、索引兼容性、覆盖率和重建操作。“模型连接”是设置入口
名称,不表示向量连接可以分配给 Agent Runtime;项目 Runtime 和 Agent Runtime 的模型
选择中不显示向量连接。
@@ -195,6 +202,10 @@ Tokenizer、原始向量或索引访问能力传入 Runtime。切换 Runtime 不
2. 检索方式:模型按需检索、每次先检索。
3. 当前范围为空、索引降级或向量未配置时的短说明。
知识库 ID 选择按 Conversation 持久化。新对话和未保存过知识范围的旧对话默认不选择
任何知识库;切换对话恢复各自范围,不得在应用启动时自动勾选全部知识库。新建知识库只
加入当前 Conversation,不重新启用用户已取消的其他知识库。
## 7. 第一阶段:可用、可见、可诊断
### 7.1 检索方式
@@ -296,6 +307,14 @@ Tokenizer、原始向量或索引访问能力传入 Runtime。切换 Runtime 不
- 失败文档数。
- 当前向量模型与索引是否兼容。
`indexedDocumentCount` 只统计状态为 `ready` 的文档;`queued`、`parsing` 和
`indexing` 计入处理中,`failed` 单独计入需处理。文档行持续显示全文、向量和图谱通道
状态。已有可用分块的失败文档仍允许查看分块,并提供重新处理。
文档行可通过受控 `knowledgeBaseId + documentId` 打开数据库中保存的原始来源。
Renderer 不得提交任意路径。移除来源必须先说明将删除的文档、索引、图谱证据及是否包含
应用托管副本,用户确认后才执行。
降级事件包括:
- 未配置向量模型。
+41
View File
@@ -0,0 +1,41 @@
# 知识库实施进度
## 已验证实现
截至 2026-09-05:
- 知识库选择按 Conversation 持久化,新对话默认空范围。
- 创建流程默认只展示名称和描述,高级存储与图谱设置折叠。
- 知识库汇总区分可检索、处理中和需处理文档。
- 文档行展示全文、向量和图谱状态,并支持打开原始来源和重新处理失败文档。
- 移除来源需要确认并说明级联影响。
- 检索高级参数默认折叠,“高级设置”提供问答效果入口。
- 向量未配置状态可直接前往模型设置。
- 有效 DOCX 与多页 PDF 已通过磁盘文件导入、解析、分块和检索生产链路。
## 验证证据
2026-09-05 最终验证:
- `npm test`:325 个测试文件、3511 项测试通过;8 个文件、55 项手动或集成测试按既有
配置跳过。
- `npm run typecheck`:通过。
- `npm run lint`:通过。
- `npm run build`:Electron Main、Preload、Renderer 生产构建及控制面安装包检查通过。
- `npx vitest run src/main/knowledge/knowledge-service.test.ts -t "imports real DOCX and PDF files from disk into searchable chunks"`:
有效 Open XML DOCX 与两页原生文本 PDF 均通过生产 `KnowledgeService.importPaths`
导入、解析、分块和全文检索。
2026-09-06 对话知识库选择补充验证:
- `App.test.tsx` 覆盖点击名称、文档数量和行本身时切换选中状态,以及空目标失焦时
弹层不提前卸载。Windows 隔离桌面实际点击名称后选中、点击数量后取消,弹层保持打开。
- 本轮全量校验结果见[工作栏验证记录](../assistant-workbar/progress.md);本项验证未调用模型。
## 剩余工作
- 外部 Dify、FastGPT 和 RAGFlow 知识库接入仍为规划中能力。
- 扫描 PDF 的真实 OCR 验收依赖用户已安装并校验的 OCR 模型;本次 DOCX/PDF 回归覆盖
原生文本 PDF,不宣称覆盖扫描件 OCR。
- PDF.js 在测试环境会提示未配置 `standardFontDataUrl`;文本、页码定位与检索结果不受
影响。
+70
View File
@@ -0,0 +1,70 @@
# 知识库 UI 设计
## 1. 设计目标
知识库优先帮助普通用户完成“添加资料并可靠提问”,高级 RAG 调试和图谱维护不得阻塞
首次使用。全局视觉、页面壳层、通知、焦点和危险操作遵循
[UI-DESIGN.md](../../../UI-DESIGN.md)。
## 2. 信息架构
```text
知识库
├─ 知识库列表
└─ 当前知识库
├─ 文档与来源
├─ 知识图谱(启用后显示)
├─ 任务中心
└─ 高级设置
```
标题区提供“用于当前对话”“检索测试”、编辑和删除。新建知识库是页面主操作。
“高级设置”承载问答效果入口、向量索引、分块与图谱配置,不把工程参数放在首次路径。
## 3. 首次使用
没有知识库时,页面空状态显示唯一的“新建知识库”主操作。创建表单默认只展示名称和
描述;存储方式与知识图谱放入折叠的“高级设置”。创建完成后进入“文档与来源”,用户
可以导入文件、文件夹或 URL。
文档处理完成后,标题区“用于当前对话”可用。该操作把当前知识库加入当前
Conversation 的检索范围,返回对话并聚焦输入框。
## 4. 状态
知识库列表显示“可用”和“需处理”文档数。详情标题持续显示可检索、处理中、需处理和
总文档数,即使用户填写了知识库描述也不隐藏状态。
文档行显示:
- 总体处理状态。
- 全文状态。
- 向量状态。
- 启用图谱时的图谱状态。
失败文档保留错误信息和“重新处理”。只要仍有可用分块,就允许打开分块管理,避免隐藏
上一版可用索引。
## 5. 问答效果与高级参数
“检索测试”默认只突出真实问题、测试按钮和结果。高级参数使用折叠区,包含候选倍数、
通道权重、最低相似度、重排、结果数和上下文预算。“高级设置 → 问答效果”打开同一
工作台。
向量模型未配置时显示“前往设置”,直接进入模型连接,不使用只有说明文字的死路状态。
## 6. 来源操作
文档行提供“打开来源”,Main 根据知识库和文档归属重新读取保存的文件路径或 URL。
移除来源使用确认对话框,说明文档数、索引、图谱证据和托管副本影响。初始焦点位于
取消,提交期间禁止重复操作,失败时保留对话框和错误。
## 7. 对话范围
知识库多选属于 Conversation。新对话默认空范围;切换对话恢复各自选择。创建新知识库
只加入当前 Conversation,不重新启用其他已取消项。发送请求时冻结当前选择和检索方式。
输入区的知识库选择弹层使用整行原生 Checkbox 标签,点击名称、文档数量或行内空白
均切换一次选择;选中背景和整行焦点环保持可见。Tab 聚焦、Space 切换,Escape 关闭并
返回触发按钮。点击行内非焦点元素时产生的空目标失焦不关闭弹层;点击外部或焦点明确
移到外部时才关闭,避免原生标签完成勾选前被卸载。
@@ -7,7 +7,7 @@
| 状态 | 实施中 |
| 版本 | 0.1 |
| 日期 | 2026-08-11 |
| 关联 PRD | [知识库检索与分块增强 PRD](./knowledge-rag-enhancement-prd.md) |
| 关联 PRD | [知识库检索与分块增强 PRD](./prd.md) |
## 1. 角色
@@ -33,6 +33,18 @@
## 2. Epic A:明确控制是否检索
### US-A0 首次建立可用知识库
作为首次使用者,我希望先命名知识库并添加资料,不需要先理解向量、分块或图谱。
验收:
- 没有知识库时只显示一个“新建知识库”主操作
- 创建表单默认只展示名称和描述
- 存储方式和知识图谱位于折叠的高级设置
- 文档可检索后提供“用于当前对话”
- 高级检索参数不阻塞首次导入和首次提问
### US-A1 模型按需检索
作为普通知识使用者,我希望保留由模型判断是否需要检索的模式,以便一般闲聊
@@ -73,6 +85,9 @@
- 同一请求最多启用 20 个知识库
- 对话输入区持续显示已选数量和检索方式
- 范围为空时检索方式不产生误导状态
- 知识库 ID 与检索方式一起按 Conversation 持久化
- 新对话和未保存过范围的旧对话默认不勾选知识库
- 切换 Conversation 恢复各自选择,不共享临时全局范围
## 3. Epic B:检索可见、可诊断
@@ -233,6 +248,18 @@
- 全库重建可取消
- 已成功替换的文档继续可用
### US-D4 查看和移除来源
作为知识库维护者,我希望打开原始文档并在移除来源前看到准确影响。
验收:
- 文档行可通过系统默认应用打开数据库保存的原始来源
- Renderer 只能提交知识库 ID 和文档 ID,不能提交任意路径或 URL
- 移除来源前显示来源名称、文档数量以及索引和图谱证据影响
- 托管模式额外说明应用托管副本会被删除
- 用户确认前不得删除来源
## 6. Epic E:高级分块
### US-E1 固定分块
@@ -0,0 +1,43 @@
# 工具执行环境
本目录是 GoodBuddy“工具执行环境”功能的唯一文档入口。该功能为本机 SKILL 和本机
stdio MCP 提供明确、可诊断的 Node.js 与 Python 环境,同时允许专业用户选择自己维护的
解释器。
本功能不作用于 SSH Host 上的 GoodBuddy Agent、固定 Node、签名 Runtime、SKILL 或 MCP。
用户从设置中心的“能力与工具”进入,并在 `Skills`、`MCP`、`工具执行环境` 三个水平
Tab 之间切换。
## 文档导航
| 文档 | 权威职责 |
| --- | --- |
| [PRD](./prd.md) | 用户问题、产品目标、范围、需求和产品验收 |
| [User Stories](./user-stories.md) | 用户角色、使用场景和逐故事验收 |
| [功能逻辑](./logic-design.md) | 不变量、状态机、决策表、失败恢复和跨场景一致性 |
| [UI 设计](./ui-design.md) | 页面结构、组件、状态、文案和无障碍一致性 |
| [技术设计](./technical-design.md) | Main、Runtime、MCP、打包、生命周期和测试架构 |
| [功能进度](./progress.md) | 已完成、进行中、待实施、阻塞项和验证证据 |
## 术语
| 术语 | 定义 |
| --- | --- |
| 能力与工具 | 设置中心中统一承载 Skills、MCP 和工具执行环境的一级分类 |
| 工具执行环境 | GoodBuddy 在桌面所在设备上向本机 Runtime 和 stdio MCP 提供的工具命令环境 |
| GoodBuddy 托管 | 由 GoodBuddy 提供、验证并解析的 Node.js 或 Python |
| 自定义环境 | 用户从检测结果中选择或直接指定的外部解释器 |
| 工具下载源 | GoodBuddy 固定目录受管工具工件使用的原生地址或 OSS 镜像 |
| 工具就绪 | 解释器及配套命令通过真实诊断 |
| 能力依赖就绪 | 某个 SKILL 或 MCP 所需的第三方包和入口通过实际验证 |
## 文档维护规则
1. 产品范围只在 PRD 定义,其他文档链接引用。
2. 界面视觉和交互只在 UI 设计定义,并服从仓库根目录的
[统一界面设计系统](../../../UI-DESIGN.md)。
3. 状态转换、优先级和冲突决策只在功能逻辑中定义。
4. 进度文档只记录已验证事实,不复制未来设计。
5. 技术实现变化先更新技术设计;用户行为变化同时更新 PRD、User Stories、功能逻辑和
UI 设计。
6. 每次完成功能阶段后立即更新进度、验证命令和剩余阻塞项。
@@ -0,0 +1,460 @@
# 工具执行环境功能逻辑设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,验收中 |
| 版本 | 0.2 |
| 日期 | 2026-09-02 |
| 关联 PRD | [工具执行环境 PRD](./prd.md) |
| 关联场景 | [工具执行环境 User Stories](./user-stories.md) |
本文是本功能业务规则、状态转换、决策表和跨场景一致性的权威来源。它不定义组件视觉,也
不规定代码模块如何实现。
## 1. 逻辑目标
1. 同一用户操作在不同本机 Runtime 和 stdio MCP 中得到一致的解释器选择。
2. 系统不通过隐式回退掩盖配置错误。
3. 工具可执行、配套命令可执行和具体能力依赖可用是三个可区分事实。
4. 已运行进程使用稳定环境,不被设置变化中途改写。
5. 安装、更新、取消、失败和删除都有唯一、可恢复的结果。
6. 本机环境和远程 Host 环境没有隐式传播。
## 2. 核心不变量
### INV-1 单一明确来源
Node.js 和 Python 各自恰好保存一个来源:`managed` 或 `custom`。不存在 `auto`、候选优先级
列表或运行时动态选择。
### INV-2 禁止静默回退
当前来源不可用时,该工具明确不可用。即使发现另一套系统解释器,也不会自动切换。
### INV-3 配套命令同源
- `node`、`npm`、`npx` 必须属于同一 Node 来源。
- `python`、`python3`、`pip` 必须属于同一 Python 来源。
- `pip` 等价于当前 Python 的 `-m pip`,不能解析到其他解释器。
### INV-4 三层状态独立
1. **解释器状态**:Node 或 Python 本身能否执行。
2. **工具组合状态**:npm/npx 或 pip/venv/SSL 是否完整。
3. **能力依赖状态**:具体 SKILL/MCP 的入口和第三方依赖是否通过验证。
任一上层成功都不能推导下一层成功。
### INV-5 进程环境不可变
每个 Runtime/MCP 进程在启动时取得一份环境快照。设置变化只影响之后启动的进程,不改写
已运行进程的 PATH 或解释器。
### INV-6 绝对命令优先
stdio MCP 配置的绝对 command 表达用户对该 MCP 的明确覆盖,原样执行,不经过全局工具
命令替换。
### INV-7 本机与远程隔离
本机工具选择、shim、Python 包、SKILL 包和 stdio MCP 配置不上传到远程 Host。
### INV-8 不推断能力依赖已就绪
能力没有专属实际诊断时,状态只能是“未验证”,不能根据解释器版本或文件存在推断“已就绪”。
### INV-9 受管工具工件来源明确且冻结
每个受管工具外部工件同时声明固定原生地址和 OSS Target。用户选择一个下载源后,任务
启动时冻结该来源;失败不回退。OSS 必须是原生工件的字节级副本。
### INV-10 设置导航不改变业务状态
“能力与工具”的三个水平 Tab 只组织界面,不改变 Skill、MCP 或工具环境设置。切换 Tab
不得提交、丢弃或重置当前设置面板会话中的编辑状态;关闭后不持久化外层 Tab。
分阶段交付期间,未实现的“工具执行环境”不进入 Tab 状态集合。第一阶段状态集合只有
`skills | mcp`,后续实现完成后才扩展为 `skills | mcp | tool-environment`。
### INV-11 内置 Skill 默认值不覆盖用户状态
只有状态存储中没有对应 Skill 记录时才使用产品默认值。通用文档工具
`deai-writing`、`longdoc-docx` 默认启用;产品营销类 `product-evidence`、
`product-marketing`、`product-presentation` 默认停用。用户显式保存的启停状态和
Runtime 分配优先,升级不得重置。
九种产品营销文档配方属于 `product-marketing` 包内工作流,不是独立能力状态维度,
不进入顶层 Skill 目录,也不单独注入 Runtime。
## 3. 状态维度
### 3.1 来源选择
```text
managed
custom(executablePath)
```
这是唯一持久状态。
候选选择还具有不持久化的界面状态:
```text
closed
choosing
validating
failed
```
- 从 `closed` 进入 `choosing` 不改变当前持久来源。
- `choosing` 中不得自动采用首个候选;只有用户明确选择候选或文件后才进入 `validating`。
- 验证成功后一次性保存 `custom(executablePath)`;验证失败回到 `choosing` 并就地显示错误。
- 文件选择取消回到 `choosing`,不显示错误且不改变当前来源。
- 没有候选不是禁用“自定义环境”的理由,用户仍可刷新或选择其他可执行文件。
### 3.2 托管 Python 安装状态
```text
not-installed
downloading
verifying
installing
testing
ready
update-available
error
```
`error` 同时携带“是否仍有旧版可用”。若旧版可用,实际工具状态仍基于旧版诊断。
托管 Node 随 GoodBuddy 存在,不使用下载安装状态,但仍可能因打包损坏进入诊断失败。
### 3.3 工具诊断状态
```text
unknown
testing
ready
partial
unavailable
```
- `ready`:解释器和本功能承诺的配套命令全部通过。
- `partial`:解释器通过,至少一个配套命令失败。
- `unavailable`:解释器本身不能使用。
### 3.4 能力依赖状态
```text
unknown
checking
ready
missing
incompatible
error
```
- `unknown` 在 UI 中显示“依赖未验证”。
- `missing` 表示明确缺少包或入口。
- `incompatible` 表示解释器版本、架构或 ABI 不满足已声明要求。
- `error` 表示诊断本身没有得出有效结论。
### 3.5 工具下载源
```text
native
oss
```
这是独立于 Node/Python 环境来源的持久设置。全新用户默认 `native`。
## 4. 来源解析决策表
| 保存来源 | 当前事实 | 解析结果 | 是否回退 |
| --- | --- | --- | --- |
| 托管 Node | 完整诊断通过 | 使用 Electron Node 与锁定 npm/npx | 否 |
| 托管 Node | Node 通过,npm/npx 失败 | Node 部分可用,显示失败项 | 否 |
| 托管 Node | Node 失败 | Node 不可用,提示修复/重装 GoodBuddy | 否 |
| 托管 Python | 未安装 | Python 不可用,提供安装 | 否 |
| 托管 Python | 已安装且诊断通过 | 使用托管 Python | 否 |
| 托管 Python | 更新失败但旧版健康 | 继续使用旧版并显示更新失败 | 否 |
| 托管 Python | 安装损坏 | Python 不可用,提供重新安装/删除 | 否 |
| 自定义 | 路径及诊断通过 | 使用该绝对路径 | 否 |
| 自定义 | 路径失效 | 保持选择并显示路径不可用 | 否 |
| 自定义 | 解释器通过、配套命令失败 | 部分可用并显示失败项 | 否 |
| 任意 | 发现其他有效解释器 | 只作为候选展示 | 不自动使用 |
## 5. 命令解析决策表
| 调用形式 | 结果 |
| --- | --- |
| `node ...` | 当前 Node |
| `npm ...` | 当前 Node 配套 npm |
| `npx ...` | 当前 Node 配套 npx |
| `python ...` | 当前 Python |
| `python3 ...` | 当前 Python |
| `pip ...` | 当前 Python `-m pip` |
| `#!/usr/bin/env node` | 从进程工具 PATH 解析当前 Node |
| `#!/usr/bin/env python3` | 从进程工具 PATH 解析当前 Python |
| stdio MCP 绝对 command | 原样执行 |
| 未托管裸命令,例如 `uvx` | 按原筛选 PATH 处理;找不到则明确失败 |
| HTTP/SSE MCP | 不参与本机命令解析 |
绝对 command 的子进程若自行调用裸 `node` 或 `python`,仍会看到该 MCP 进程启动时的工具
PATH。这不改变绝对 command 本身。
## 6. 首次使用和迁移
### 6.1 全新安装
| 工具 | 默认选择 | 首次状态 |
| --- | --- | --- |
| Node.js | GoodBuddy 托管 | 立即真实诊断 |
| Python | GoodBuddy 托管 | 未安装,展示安装入口 |
系统候选可以展示,但不会改变默认选择。
### 6.2 旧版本升级
1. 设置中没有工具环境字段时执行一次迁移。
2. 分别按旧版本实际 PATH 解析 Node 和 Python 3。
3. 有效解释器固化为 `custom + 绝对真实路径`。
4. 无有效解释器时选择 `managed`。
5. 持久化后不再因 PATH 变化迁移。
迁移中 Node 和 Python 独立决策,不要求二者使用同一种来源。
## 7. 托管 Python 生命周期
### 7.1 首次安装
```text
not-installed
→ downloading
→ verifying
→ installing
→ testing
→ ready
```
任一步失败:
```text
→ 删除本次暂存
→ error(没有活动版本)
→ 用户可重试
```
### 7.2 取消
| 取消阶段 | 结果 |
| --- | --- |
| 下载 | 终止下载、删除暂存、回到安装前状态 |
| 校验 | 终止校验、删除暂存、回到安装前状态 |
| 解压/安装 | 完成本次有界清理、回到安装前状态 |
| 测试 | 停止诊断、删除未发布候选、回到安装前状态 |
取消不发布候选版本,不把取消显示为安装失败。
### 7.3 更新
1. 下载和验证候选时,旧版继续服务现有进程。
2. 应用候选前,页面说明需要停止依赖托管 Python 的本机 Runtime/MCP。
3. 用户确认后通过现有生命周期取消/停止相关进程。
4. 无法确认停止时不替换当前版本。
5. 候选完整诊断通过后才发布。
6. 失败继续使用旧版,并显示更新失败。
首期不让两个托管 Python 版本长期并存。候选暂存只服务本次更新。
### 7.4 删除
| 条件 | 行为 |
| --- | --- |
| 没有使用中进程 | 确认后删除托管目录 |
| 有使用中进程 | 说明影响,用户确认后停止相关进程再删除 |
| 进程停止失败 | 不删除,保留当前环境 |
| 当前选择为自定义 | 删除托管包不改变自定义来源 |
| 当前选择为托管 | 删除后保持托管选择,状态变为未安装 |
## 8. 设置保存与进程生效
### 8.1 保存时
- 验证待保存来源。
- 自定义路径必须在保存前通过解释器诊断;配套命令可以形成 `partial`。
- 保存成功后标记本机 Runtime 环境配置已变化。
- 不直接修改任何运行中进程。
### 8.2 后续进程
- 没有活动请求的缓存 Runtime 在下次使用前按现有重建入口重建。
- 有活动请求的 Runtime 完成或被用户停止前保持原环境。
- 已启动 stdio MCP 保持原环境,正常关闭、重连或显式测试后使用新环境。
- 新启动的 Runtime/MCP 立即使用新环境。
每个工具调用的活动记录应归属于实际承载它的进程,不根据最新设置追改历史来源。
### 8.3 设置导航
| 入口或操作 | 打开的外层 Tab | 数据行为 |
| --- | --- | --- |
| 普通“能力与工具”入口 | Skills | 不修改业务数据 |
| 用户点击 Skills | Skills | 不修改业务数据 |
| 用户点击 MCP | MCP | 不修改业务数据 |
| 未来的定向入口 | 指定 Tab | 不修改业务数据 |
| 同一设置面板会话中切换 Tab | 用户选择的 Tab | 保留三个面板的会话内状态 |
| 关闭后重新打开普通入口 | Skills | 不恢复上次外层 Tab |
MCP 内层页签状态属于 MCP 面板,不与外层 Tab 合并。外层 Tab 的默认值或定向入口不能改写
MCP 的连接、凭据、启用或 Runtime 分配状态。
## 9. 能力依赖逻辑
### 9.1 诊断输入
能力可以声明:
- 需要 Node 或 Python。
- 最低/最高解释器版本。
- 可选依赖文件位置。
- 可选的无副作用诊断入口。
首期声明只用于显示、版本比较和显式诊断,不触发自动安装。
### 9.2 状态推导
| 工具状态 | 能力诊断 | 能力状态 |
| --- | --- | --- |
| unavailable | 不执行 | missing 或 incompatible,附工具原因 |
| partial | 能力不依赖失败配套项且实际诊断成功 | ready |
| partial | 能力依赖失败配套项 | missing |
| ready | 没有能力诊断 | unknown |
| ready | 实际诊断成功 | ready |
| ready | 明确缺包 | missing |
| ready | 版本/ABI 不满足 | incompatible |
| 任意 | 诊断自身异常 | error |
“依赖未验证”不是错误,也不能显示为“已就绪”。
## 10. Ask、Execute 与 MCP
| 场景 | 解释器是否可见 | 是否允许执行 |
| --- | --- | --- |
| 本机 Execute Runtime | 是 | 按现有 Execute 权限 |
| 本机 Ask Runtime | 可以存在于进程环境 | 仍由 Runtime Ask 边界决定,不能因此获得命令权限 |
| Main 启动的已启用 stdio MCP | 是 | 按当前 MCP 分配和模式规则 |
| HTTP/SSE MCP | 不适用 | 按当前网络 MCP 规则 |
| 远程 Runtime | 否 | 使用远程 Host 自身环境 |
工具环境不创建新的工具,不扩大 Runtime 当前允许的工具集合。
## 11. 网络受限与离线
### 11.1 托管 Node
随 GoodBuddy 提供,安装后可离线使用。
### 11.2 托管 Python
- Windows 原生 Target 固定到官方 NuGet 包;macOS/Linux 原生 Target 固定到 Astral
`python-build-standalone`。
- OSS Target 是对应原生工件的字节级副本。
- 下载源不可达时保持未安装或旧版可用状态,并显示准确原因。
- 首期不提供 Python 离线包导入,避免引入未经选型的包格式和导入流程。
- 完全离线用户可以选择已经离线安装的自定义 Python。
未来若加入离线导入,必须在 PRD、User Stories、逻辑状态和技术包校验中同时定义,不能只
增加一个文件按钮。
### 11.3 工具下载源决策
| 保存选择 | 目录事实 | 行为 |
| --- | --- | --- |
| 原生地址 | 原生 Target 完整 | 请求固定上游 URL |
| 原生地址 | 原生 Target 缺失 | 工件不可下载,不请求 OSS |
| OSS 镜像 | OSS Target 完整 | 只请求固定 OSS URL |
| OSS 镜像 | OSS Target 缺失 | 工件不可下载,不请求原生地址 |
| 任意 | 下载后字节数/SHA-256 不符 | 删除暂存并失败 |
| 任意 | 设置在任务期间变化 | 当前任务不变,后续任务使用新选择 |
该选择只覆盖受管工具目录中的外部工件,不影响模型、应用更新、Agent、pip/npm 注册表或
用户 URL。
## 12. 冲突与一致性检查
| 可能冲突 | 唯一规则 |
| --- | --- |
| 全局工具选择 vs MCP 绝对 command | 绝对 command 优先 |
| 新设置 vs 运行中进程 | 运行中进程保留启动快照 |
| 新托管版本 vs 健康旧版本 | 候选完整通过后才发布 |
| 系统发现新版本 vs 已保存自定义路径 | 不自动切换 |
| 解释器成功 vs 缺少第三方包 | 工具 ready,能力 missing/unknown |
| Ask 环境包含解释器 vs Ask 只读 | Ask 权限边界优先 |
| 本机设置 vs 远程项目 | 远程隔离优先 |
| 镜像不可达 vs 其他来源可用 | 不在同一次操作中静默切换来源 |
| 工具下载源 vs 模型/更新下载源 | 各自独立,工具执行环境设置不覆盖其他功能 |
| OSS 对象 vs 原生工件 | 必须字节、大小和 SHA-256 相同 |
| 合并设置入口 vs 原有 Skills/MCP 数据 | 只重组导航,不迁移或重写业务数据 |
| 外层 Tab vs MCP 内层 Tab | 保持两个独立 tablist、标识和键盘焦点范围 |
## 13. 逻辑评估结论
### 13.1 完整性评估
| 评估维度 | 结论 | 依据 |
| --- | --- | --- |
| 首次使用 | 通过 | 托管 Node 立即诊断,托管 Python 明确未安装 |
| 正常执行 | 通过 | SKILL 与 stdio MCP 共用同一命令解析 |
| 自定义环境 | 通过 | 明确路径、真实诊断、禁止静默回退 |
| 部分失败 | 通过 | 解释器、工具组合、能力依赖三层状态独立 |
| 安装生命周期 | 通过 | 安装、取消、失败、重试有唯一结果 |
| 更新生命周期 | 通过 | 候选通过后发布,失败保留健康旧版 |
| 删除生命周期 | 通过 | 使用中进程先停止,失败不删除 |
| 设置生效 | 通过 | 进程启动快照不可变,新进程使用新设置 |
| 升级兼容 | 通过 | 旧系统解释器一次性固化为自定义路径 |
| Ask/Execute | 通过 | 工具环境不扩大工具集合或权限 |
| 远程边界 | 通过 | 本机配置不进入 Host |
| 受限网络 | 通过 | 来源失败可见,不在同次操作静默切换 |
| 完全离线 | 有明确限制 | Node 可用;Python 首期要求已有自定义环境 |
| 受管工具工件来源 | 通过 | 原生/OSS 独立选择、任务冻结、禁止回退 |
| 设置导航 | 通过 | 单一一级入口、分阶段水平 Tab、业务数据不变 |
### 13.2 一致性评估
- “两种来源”与自定义候选探测不冲突:候选只是自定义来源的选值,不是第三种来源。
- “禁止回退”与升级迁移不冲突:迁移只发生一次,完成后按普通保存选择处理。
- “进程环境不可变”与 Runtime 重建不冲突:只有空闲或后续启动的进程使用新环境。
- “工具环境就绪”与“能力依赖就绪”不冲突:两者属于独立状态维度。
- “绝对 MCP command 优先”与 PATH 注入不冲突:command 不替换,但其子进程继承启动快照。
- “更新保留旧版”不要求长期多版本管理:旧版只在候选发布前保持当前,候选失败即删除。
### 13.3 当前未决项
没有未决的产品逻辑分支。以下属于实施前必须解决的技术选型,不改变业务规则:
1. 六平台 CPython 独立发行包、固定版本、许可证和摘要。
2. SKILL/MCP 运行要求声明的最小 schema 和无副作用诊断入口。
3. 各 Runtime 当前缓存进程的精确空闲判定与既有重建入口接线。
若技术选型无法满足当前逻辑,例如目标 Python 工件不包含可用 pip/venv,必须回到 PRD 和
逻辑设计调整承诺,不能在实现中静默降级。
## 14. 需求追踪
| 逻辑范围 | PRD | User Stories |
| --- | --- | --- |
| 首次使用默认状态 | FR-1、FR-2、FR-6 | US-A0 |
| 明确来源与禁止回退 | FR-3、FR-8 | US-C1、US-C2、US-D3 |
| 托管 Node | FR-1 | US-A1、US-B2 |
| 托管 Python 生命周期 | FR-2、FR-9 | US-A2、US-D2、US-D4、US-D5 |
| 三层状态 | FR-6 | US-A3、US-A4 |
| SKILL 命令解析 | FR-4 | US-A1、US-A3 |
| MCP 命令解析 | FR-5 | US-B1、US-B2、US-B3 |
| 生效时机 | FR-7 | US-C3、US-C4 |
| Ask/Execute | FR-10 | US-D6 |
| 远程隔离 | FR-7、FR-9 | US-D1 |
| 受限网络与离线 | FR-2、FR-11 | US-E1、US-E2、US-E3 |
| 统一设置入口 | FR-12 | US-F1、US-F2 |
+213
View File
@@ -0,0 +1,213 @@
# 工具执行环境 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已批准并实施,验收中 |
| 版本 | 0.2 |
| 日期 | 2026-09-02 |
| 适用产品 | GoodBuddy 桌面端 |
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
| 关联文档 | [功能入口](./README.md)、[User Stories](./user-stories.md)、[功能逻辑](./logic-design.md)、[UI 设计](./ui-design.md)、[技术设计](./technical-design.md)、[功能进度](./progress.md) |
## 1. 背景
GoodBuddy 的部分 SKILL 通过 Node.js 或 Python 脚本生成成果,部分本机 stdio MCP 也以
`node`、`npm`、`npx`、`python` 或 `python -m` 启动。当前安装包没有向这些能力提供统一
解释器,用户是否成功取决于系统 PATH 和预装软件。
普通用户不应为了使用一个 SKILL 或 MCP 手工安装开发环境。专业用户已有项目 Node、
Python、虚拟环境或 Conda 环境时,也不能被 GoodBuddy 静默替换。
## 2. 产品目标
1. 全新设备无需预装 Node.js 即可运行普通 JavaScript SKILL 和 Node.js stdio MCP。
2. 用户可在 GoodBuddy 内按需安装 Python,并运行 Python SKILL 和 stdio MCP。
3. 专业用户可以选择检测到的解释器或指定其他可执行文件。
4. 设置始终展示实际来源、版本、路径、工具状态和能力依赖状态。
5. 工具执行环境不修改系统环境,也不传播到远程主机。
6. 保持 Ask/Execute、Runtime、MCP、取消和进程清理的现有边界。
7. 用户可以在“工具执行环境”Tab 中明确选择受管工具工件使用原生地址还是 OSS 镜像。
8. Skills、MCP 和工具执行环境通过一个“能力与工具”设置入口形成清晰的能力配置链路。
## 3. 核心决策
### 3.1 每种工具只有两个来源
- **GoodBuddy 托管(推荐)**
- **自定义环境**
不提供“自动选择”。“系统安装”和“指定路径”合并为“自定义环境”,因为两者最终都是
用户明确选择的外部可执行文件,区别只在发现方式。
### 3.2 配套命令跟随解释器
- Node.js 保证 `node`、`npm`、`npx` 来自同一来源。
- Python 保证 `python`、`python3`、`pip` 来自同一来源;`pip` 实际绑定当前 Python。
- 首期不托管 `uv`、`uvx`、Conda、Poetry、pyenv 或 nvm。
### 3.3 工具就绪不等于能力依赖就绪
解释器通过诊断后标记“工具环境已就绪”。只有 SKILL 或 MCP 的实际入口及所需第三方包
通过验证后,才能标记“能力依赖已就绪”。GoodBuddy 首期不自动扫描和安装任意能力依赖。
### 3.4 分阶段交付统一设置入口
第一阶段先把现有 Skills 和 MCP 合并到“能力与工具”,只显示两个真实可用的水平 Tab。
“工具执行环境”实现完成后再加入第三个 Tab;未实现期间不展示禁用项、空白页或开发中占位。
## 4. 功能范围
### 4.1 包含
- 本机 OpenCode、Continue、DeepSeek Harness 加载的 SKILL。
- Main 或本机 Runtime 启动的 stdio MCP。
- 托管 Node.js、npm、npx。
- 按需下载安装的托管 Python 3、pip、venv。
- 系统解释器探测、自定义文件选择和真实诊断。
- 工具来源、版本、路径、安装、删除、更新和错误状态。
- 受管工具工件的原生地址/OSS 镜像选择。
- “能力与工具”一级分类及 `Skills`、`MCP`、`工具执行环境` 三个水平 Tab。
- 新启动本机 Runtime 和 MCP 的 PATH 注入。
### 4.2 不包含
- SSH Host 的 PATH、Agent、固定 Node、Runtime、SKILL 或 MCP。
- HTTP/SSE MCP 服务端环境。
- 普通助手工作栏终端的 Shell 环境。
- 通用语言版本管理器。
- 每项目或每 SKILL 的解释器覆盖。
- 自动合并或求解多个 `requirements.txt`、`package.json`。
- 自动执行任意第三方安装脚本。
- 修改系统 PATH、注册表或 Shell Profile。
- 无条件把完整 Python 打入桌面安装包。
- 首期导入托管 Python 离线包;完全离线用户可选择已有的自定义 Python。
- 修改模型下载源、应用更新源、Agent 下载源、pip/npm 注册表或用户配置的网络地址。
## 5. 功能需求
### FR-1 Node.js 托管环境
- 安装 GoodBuddy 后托管 Node.js 即可用,不需要额外下载。
- 实际版本通过运行命令诊断,不仅根据 Electron 版本推断。
- `node`、`npm`、`npx` 必须来自同一托管组合。
- Electron Node 原生扩展兼容限制必须可见。
### FR-2 Python 托管环境
- Python 未安装时展示安装入口,不把完整 Python 放入所有桌面安装包。
- Windows x64 使用固定官方 `python` NuGet 工件,Windows ARM64 使用固定官方
`pythonarm64` NuGet 工件;macOS/Linux 使用固定 Astral `python-build-standalone`
工件,最终版本以真实诊断和共同支持矩阵为准。
- 原生地址直接指向精确上游工件;OSS 镜像保存该工件的字节完全相同副本。
- 展示下载、校验、安装、取消、可更新、删除和失败状态。
- 下载失败保留已验证旧安装。
### FR-3 自定义环境
- 探测 PATH 和常见安装位置中的有效候选。
- 探测当前虚拟环境、Conda 环境以及用户/系统常见解释器位置。
- 候选同时展示版本和规范化绝对路径。
- 用户可以选择未被探测到的可执行文件。
- “自定义环境”入口在没有候选时仍可操作,不自动选择首个候选。
- 保存前执行真实诊断。
- 已保存路径失效时保持用户选择并报错,不静默回退。
### FR-4 SKILL 接入
- SKILL 可以稳定调用 `node`、`npm`、`npx`、`python`、`python3` 和 `pip`。
- SKILL 不需要知道解释器真实安装目录。
- 缺失解释器或第三方依赖时返回准确错误。
- 工具环境不自动安装 SKILL 声明的依赖。
### FR-5 stdio MCP 接入
- 裸命令通过当前工具环境解析。
- `env node`、`env python3` 形式的脚本使用当前工具环境。
- MCP 中配置的绝对命令路径保持原样,不被替换。
- Python 和 Node.js MCP 均使用与 SKILL 相同的来源。
### FR-6 状态与诊断
- 工具执行环境页只显示有真实诊断结果的解释器和配套工具状态,不显示固定的能力依赖占位
状态。
- 具体 SKILL/MCP 只有在其实际入口及第三方依赖通过验证后,才在对应能力详情中显示能力
依赖状态。
- Node 诊断覆盖 Node、npm、npx、标准库和子进程。
- Python 诊断覆盖 Python 3、架构、标准库、SSL、pip 和 venv。
- 诊断使用临时目录并在结束后清理。
### FR-7 生效范围
- 设置只对新启动的本机 Runtime 和 stdio MCP 生效。
- 已运行进程不被中途替换环境。
- 不影响普通终端、系统环境和远程主机。
### FR-8 升级兼容
- 全新用户默认选择 GoodBuddy 托管。
- 已发布版本升级时,将当前有效系统解释器一次性固化为自定义绝对路径。
- 没有有效系统解释器时迁移到托管来源。
- 后续系统 PATH 变化不自动改变选择。
### FR-9 生命周期
- 安装、诊断、Runtime 和 MCP 子进程支持取消和应用退出清理。
- 删除只作用于 GoodBuddy-owned 目录。
- 不删除系统解释器、自定义环境、用户工作区或远程文件。
### FR-10 权限模式
- Ask 不因解释器可用而获得额外命令或写入权限。
- Execute 继续使用当前本机账号的完整授权。
- 不为工具环境增加第二套批准流程。
### FR-11 工具下载源
- “工具执行环境”Tab 提供两个互斥选项:`原生地址` 和 `OSS 镜像`。
- 全新用户默认使用原生地址;该选择独立于“关于与更新”和模型下载源。
- 选择原生地址时,每个受管工件使用目录中固定的上游 URL。
- 选择 OSS 镜像时,使用 GoodBuddy OSS 中固定的对应对象。
- 两个 Target 必须声明同一逻辑工件、相同字节数和 SHA-256;OSS 不得重打包或修改内容。
- 下载开始时冻结来源;运行期间切换设置只影响后续下载。
- 当前来源失败、缺少工件或校验失败时明确失败,不自动请求另一个来源。
- 该设置适用于当前 Python Runtime 和未来纳入受管工具目录的 Runtime/工具链。
- 该设置不影响用户执行的 pip/npm 依赖安装、模型下载、应用更新、Agent 下载、HTTP/SSE
MCP 或用户配置 URL。
### FR-12 统一设置入口
- 设置中心只提供一个“能力与工具”一级分类,不再并列显示独立的 Skills、MCP 或工具执行
环境分类。
- 第一阶段提供 `Skills`、`MCP` 两个水平 Tab;工具执行环境完成后依次提供
`Skills`、`MCP`、`工具执行环境` 三个水平 Tab。始终默认打开 `Skills`。
- 三个 Tab 使用共享 `PageTabs`,保留 `tablist`、`tab`、`tabpanel`、方向键和可见焦点。
- `Skills` 继续承载现有 Skill 启停、导入、状态和 Runtime 分配。
- 内置 Skill 顶层目录只展示五个独立能力:`deai-writing`、`longdoc-docx`、
`product-evidence`、`product-marketing`、`product-presentation`。产品营销的功能目录、
一页纸、技术方案、白皮书、招标、演示套件、客户案例和竞品定位作为
`product-marketing` 内部工作流,不重复显示为独立 Skill。
- `deai-writing` 和 `longdoc-docx` 默认启用;`product-evidence`、
`product-marketing`、`product-presentation` 默认停用。已保存的用户启停和 Runtime
分配始终优先于默认值;导入 Skill 的默认行为保持不变。
- `MCP` 继续承载现有 MCP 配置、凭据、状态和 Runtime 分配;其现有内部分组页签保持不变。
- `工具执行环境` 承载工具下载源、Node.js、Python、诊断和生效范围。
- 合并导航不改写 Skills/MCP 业务数据;未来新增定向入口时必须进入对应水平 Tab。
- 切换水平 Tab 不丢弃当前编辑状态;不把当前水平 Tab 持久化为产品设置。
- 未实现的工具执行环境不得以禁用 Tab 或占位内容提前显示。
## 6. 产品验收
- 无系统 Node.js 时,托管 Node 可以真实执行 JavaScript SKILL 和 stdio MCP。
- 无系统 Python 时,用户可安装托管 Python 并真实执行现有 Python SKILL 和 MCP。
- Node、npm、npx 来源一致;Python、python3、pip 来源一致。
- 用户可明确选择并锁定自定义解释器。
- 自定义路径失效不触发静默切换。
- 工具就绪与能力依赖就绪不会混为同一状态。
- 本机 Skills、MCP 和工具执行环境不会应用于远程主机。
- 工具下载使用用户明确选择的原生地址或 OSS,且不会静默回退。
- 第一阶段通过“能力与工具”统一入口展示 Skills/MCP 两个水平 Tab;工具执行环境完成后
追加第三个 Tab。
- 中英文界面、键盘操作、焦点和状态反馈符合统一设计系统。
- 全量测试、类型检查、Lint 和生产构建通过。
@@ -0,0 +1,149 @@
# 工具执行环境功能进度
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档类型 | 功能进度 |
| 状态 | 已实施,六平台打包验收已通过;真实 Skills/MCP 与自定义环境验收待完成 |
| 版本 | 0.6 |
| 日期 | 2026-09-03 |
| 关联入口 | [工具执行环境](./README.md) |
## 当前结论
本机工具执行环境的生产链路已经进入源码。“能力与工具”现在显示 `Skills`、`MCP`、
`工具执行环境` 三个真实 Tab;Main 统一解析托管或自定义 Node/Python,并把不可变 PATH
快照应用到新启动的本机 Runtime 与 stdio MCP。Windows x64 已完成真实托管 Node 和原生
地址托管 Python 安装验证。
六个 OSS 镜像对象已发布并公开回读验证。托管 Python 继续按需下载,Desktop 发行包不额外
带入许可证文件。标准六平台打包任务已改用目标架构原生 Runner,并在打包前执行托管 Python
的真实安装、SSL、pip 和 venv 探针。不可变的 `v0.12.0` 尝试已通过 Windows 与 macOS,
但 Linux x64/ARM64 在发布前暴露 TAR 大小写冲突误判;`v0.12.1` 在同步 Agent 发布时
于原生打包前取消。已发布的 `v0.12.2` 随后通过全部六个原生打包任务及其托管 Python
安装、SSL、pip 和 venv 探针。
## 已完成
### 设置、迁移和界面
- [x] Shared 严格设置、候选、诊断、进度、IPC 和 Preload 契约。
- [x] Application Settings v10;全新用户默认托管环境和原生地址。
- [x] v1-v9 分别执行真实 Node/Python 3 探测的一次性迁移;无有效候选时选择托管环境。
- [x] 用户/系统 PATH、活动 venv/Conda、`CONDA_ENVS_PATH`、Conda/pyenv/nvm 和常见安装
位置,以及已保存自定义路径候选的规范化、去重、版本及架构诊断。
- [x] 自定义解释器保存前真实验证;无效新选择不持久化,已保存失效路径不回退。
- [x] “自定义环境”始终可进入;不隐式采用首个候选,取消文件选择不改设置,验证错误在
对应工具卡内显示。
- [x] “能力与工具”统一入口及三个水平 Tab,MCP 原内层页签保持不变。
- [x] 工具下载源、Node.js、Python、候选、诊断、安装进度、取消和删除界面。
- [x] 工具卡只展示真实工具诊断;未接入具体能力证据前不显示固定的能力依赖占位状态。
- [x] 使用 `PageTabs`、应用通知、就地操作错误、删除确认和具名进度条。
### 托管 Node 与进程注入
- [x] 使用 Electron-as-Node 提供 `node`,使用同一 Node 和打包 CLI 提供 `npm`、`npx`。
- [x] 自定义 Node 只使用同安装位置且验证成功的 npm/npx,不与托管 CLI 混用。
- [x] 每次配置生成不可变 shim 目录;已有进程继续持有原 PATH,新进程读取新快照。
- [x] OpenCode、Continue、DeepSeek Harness 和本机通用/精选 stdio MCP 接收工具 PATH。
- [x] 工具 PATH 不修改 `process.env`,不进入普通终端或远程 Host。
- [x] 解释器探针使用过滤后的环境,不接收模型凭据、GoodBuddy 或 Factory 内部变量。
### 托管 Python
- [x] 锁定 CPython 3.13.15 的 Windows/macOS/Linux x64/ARM64 六平台目录、大小和 SHA-256。
- [x] 用户选择的原生地址或 OSS 镜像在任务开始时冻结,同一次任务不回退。
- [x] HTTPS、重定向 Host、大小、SHA-256、取消和部分文件清理。
- [x] NuGet ZIP 与 Astral TAR 的 payload 限定、路径穿越防护、展开上限和安全链接处理。
- [x] 归档路径唯一性遵循目标文件系统;Linux 保留大小写不同的合法 TAR 条目,所有目标
仍拒绝完全重复路径,Windows/macOS 仍拒绝大小写冲突。
- [x] GoodBuddy-owned 暂存安装、发布、失败清理、更新保留和删除。
- [x] `python`、`python3`、`pip` 同源,`pip` 固定使用所选 Python 的 `-m pip`。
- [x] 发布前真实验证 Python 3.13.15、架构、标准库、SSL、pip,并创建和执行临时 venv。
- [x] 取消恢复原状态且不显示为安装失败;退出时取消并等待安装清理。
## 待完成验收
- [x] 发布六个平台/架构的字节完全相同 OSS 镜像对象并公开回读校验。
- [x] 确认托管 Python 保持按需下载,Desktop 发行包不额外带入许可证文件。
- [x] Windows ARM64、macOS x64/ARM64 原生 CI 安装、SSL、pip、venv 探针。
- [x] Linux x64/ARM64 原生 CI 安装、SSL、pip、venv 探针;`v0.12.2` 标签工作流已通过。
- [ ] 最低 Windows/macOS/glibc 支持矩阵和 Windows NuGet 签名核验。
- [ ] 真实 JavaScript/Python SKILL 成果及 Node/Python stdio MCP 调用。
- [ ] 自定义 Node/Python 在产品 UI 中的真实选择和任务执行。
- [ ] 更新或删除托管 Python 时,使用现有本机 Runtime/MCP 生命周期协调受影响进程。
## 当前阻塞项
- 当前机器只能原生验证 Windows x64。其余五个平台必须由对应原生 CI 执行,不能用本机
mock 代替。
## 已验证证据
### 2026-09-03 `v0.12.0` 失败标签恢复
- 不可变标签工作流 `33704483505` 的 Windows x64/ARM64 与 macOS x64/ARM64 托管 Python
安装、SSL、pip、venv 探针和打包成功;Linux x64/ARM64 在实际官方 Astral TAR 中发现
`share/terminfo/2/2621A` 与 `share/terminfo/2/2621a` 后被大小写无关去重误判为冲突。
- 最终 GitHub/OSS 发布任务跳过,GitHub 未创建 `v0.12.0` Release,Latest 仍为
`v0.11.14`;首次恢复版本按不可变标签规则提升为 `0.12.1`。
- 提取器改为按工件目标平台判断路径唯一性,并继续保留完全重复路径、路径穿越、安全链接
和展开上限校验。锁定的 Linux x64 上游工件重新下载后大小与 SHA-256 匹配目录,并确认
冲突项确为大小写不同的两个合法 symlink;临时审计工件已清理。
- Release Notes 校验通过;工具环境、工作流和 Release Notes 聚焦测试 `40/40` 通过,
live 安装项按本地发布规则未启用。最终 `npm test` 为 `3478` 通过、`51` 跳过,
typecheck 与 lint 通过;未运行本地 production build、package 或安装探针。
- `v0.12.1` 的 main validators 与 production bundle 已通过,但标签工作流在同步新
Agent 版本前主动取消;未进入六目标原生矩阵,未创建 GitHub Release 或切换 OSS
latest。Desktop 恢复版本继续提升为 `0.12.2`。
- `0.12.2` 协调候选的 Release Notes 校验通过;Agent/Desktop 发布链路聚焦测试
`106/106` 通过、1 项平台用例跳过。最终 `npm test` 为 `3478` 通过、`51` 跳过,
typecheck 与 lint 通过;仍未运行本地 production build、package 或安装探针。
- `v0.12.2` 标签工作流 `33714902060` 随后成功完成 source validation、production
bundle、Windows/macOS/Linux x64/arm64 六个原生 package job 及各自托管 Python
安装探针,并于 2026-09-03 发布非 draft、非 prerelease 的正式 Release。
### 2026-09-02 完整实现
- 发布准备确认不把按需下载的托管 Python 许可证文件加入 Desktop `extraResources`、解包
校验或 Portable ZIP 必需文件,避免该额外资源改变现有编译和打包路径。
- 六平台打包矩阵在 `npm ci` 后先断言 Runner 架构,再执行
`GOODBUDDY_RUN_LIVE_TOOL_ENVIRONMENT=1` 的托管 Python 安装探针;Windows arm64 改用
`windows-11-vs2026-arm` 原生 Runner。发行资源与目录聚焦测试 `47/47` 通过。
- `npm run typecheck` 通过。
- `npm run lint` 通过。
- `npm run build` 通过,Main、Preload 和 Renderer 生产 bundle 成功。
- 工具环境聚焦测试通过:契约、设置迁移、解析/shim、服务、下载、解压、安装、IPC、
Preload、Runtime/MCP 注入、Renderer 和发行资源校验。
- Windows x64 真实托管 Node:Electron Node `24.18.0`、npm `11.19.0`、npx `11.19.0`。
- `GOODBUDDY_RUN_LIVE_TOOL_ENVIRONMENT=1` 真实下载官方 NuGet、校验摘要、解压、安装并
验证 CPython `3.13.15`、SSL、pip 和 venv,`1/1` 通过;临时安装已清理。
- 隔离 Electron 生产 bundle 自动化确认三个 Tab 均可见;“诊断全部”显示托管 Node
`24.18.0`、npm/npx `11.19.0`。隔离进程和用户数据已清理。
- 第一次全量测试发现 3 个确定性夹具/兼容失败,修复后聚焦复跑 `25/25` 通过。第二次暴露
2 个 `App.test.tsx` 设置导航时序问题,修正为按可访问名称点击并等待设置中心。
- 最终 `npm test` 通过:`3465` 通过、`51` 跳过;完整 `App.test.tsx` 为 `157/157` 通过。
- 使用本机受控 Aliyun 凭据将六个已验证上游工件原样上传到
`goodbuddy/tool-artifacts/python/3.13.15/<sha256>/<filename>`,总计 `142,251,693`
字节。随后从目录中的六个公开 OSS URL 全量下载,逐个验证字节数和 SHA-256,全部一致。
- 修复无候选时禁用“自定义环境”的死路交互,并扩展 venv/Conda/常见安装位置探测;
聚焦 Main/Renderer/Settings 测试 `95/95` 通过,最终 `npm test` 为 `3477` 通过、
`51` 跳过,类型检查、功能文件定向 Lint 和生产构建通过。仓库全量 Lint 当前被并行
`App.tsx` 的两个 React Compiler memoization 错误阻塞,与工具执行环境文件无关。
- 隔离 Electron 实机确认“自定义环境”始终可进入,展开和刷新不会保存来源;在 64 项以上
PATH 的机器上优先发现活动 `<user>\miniconda3\python.exe`,真实诊断为
Python `3.13.13 · amd64`,刷新后持久设置仍为 `managed`。
### 2026-09-02 菜单阶段
- 完成“能力与工具”一级菜单及 Skills/MCP Tab,并修复可滚动 Flex 布局将页签压缩为零
高度的问题;加入 `flex: 0 0 auto` 回归约束。
## 进度维护要求
- 只有真实源码和生产路径完成后才能勾选对应实施项。
- 测试失败时保留未完成状态并记录失败原因。
- 每次更新记录日期、实际命令、结果和外部阻塞。
- 不在本文件复制未来设计,需求变化回写 PRD/UI/技术设计。
@@ -0,0 +1,372 @@
# 工具执行环境技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 已实施,跨平台验收中 |
| 版本 | 0.3 |
| 日期 | 2026-09-03 |
| 关联 PRD | [工具执行环境 PRD](./prd.md) |
| 功能逻辑 | [工具执行环境功能逻辑设计](./logic-design.md) |
| 相关架构 | [DeepSeek Harness Runtime 设计](../deepseek-harness/technical-design.md)、[SSH 远程主机与 GoodBuddy Agent 实现说明](../remote-host/technical-design.md) |
## 1. 当前基线
GoodBuddy 已具备:
- Electron 内置 Node Runtime。
- 安装包中的锁定 npm CLI。
- DSH 插件安装器使用的 `process.execPath + ELECTRON_RUN_AS_NODE=1` shim。
- OpenCode、Continue、DeepSeek Harness 的独立本机进程环境入口。
- SKILL 按 Runtime 目标分配和暂存。
- Main 和 Runtime 的 stdio MCP 启动链路。
- Runtime 可执行文件探测、版本读取和自定义路径模式。
- 下载、摘要校验、进度、取消和暂存清理模式。
当前缺少统一工具来源设置、通用 PATH 注入和托管 Python。
## 2. 设置契约
```ts
type LocalToolEnvironmentSelection =
| { source: 'managed' }
| { source: 'custom'; executablePath: string }
type LocalToolEnvironmentSettings = {
node: LocalToolEnvironmentSelection
python: LocalToolEnvironmentSelection
artifactDownloadSource: 'native' | 'oss'
}
```
只持久化用户意图。不持久化候选列表、版本、PATH、shim 路径、诊断结果或可重新读取的托管
包元数据。
### 2.1 兼容迁移
- 全新用户默认 `managed`。
- 旧设置没有该字段时,Main 按升级前 PATH 分别解析有效 Node 和 Python 3。
- 有效候选写成 `custom + 规范化绝对路径`。
- 没有有效候选时写成 `managed`。
- 新增的 `artifactDownloadSource` 默认迁移为 `native`。
- 迁移只执行一次,后续 PATH 变化不改变选择。
## 3. Main 服务
新增一个 `LocalToolEnvironmentService`,负责:
- 设置读取和保存。
- Node/Python 候选探测。
- 自定义解释器诊断。
- 当前工具组合解析。
- 受管工具 bin 创建。
- 托管 Python 安装、更新、删除。
- 有界状态和进度发布。
- 应用退出时取消安装并清理本次暂存。
不为每项职责增加持久 Manager、注册表或恢复状态机。失败安装删除暂存,下次重新执行。
## 4. 解析结果
```ts
type ResolvedLocalToolEnvironment = {
binDirectory: string
node?: {
executablePath: string
npmCliPath?: string
npxCliPath?: string
}
python?: {
executablePath: string
}
}
```
工具 bin:
| 平台 | 命令 |
| --- | --- |
| Windows | `node.cmd`、`npm.cmd`、`npx.cmd`、`python.cmd`、`python3.cmd`、`pip.cmd` |
| macOS/Linux | `node`、`npm`、`npx`、`python`、`python3`、`pip` |
shim 使用绝对路径。`pip` 固定调用 `<python> -m pip`,避免解释器错配。
## 5. Node.js 托管环境
托管 Node shim:
```text
ELECTRON_RUN_AS_NODE=1 <process.execPath> <arguments...>
```
约束:
- 复用 DSH 已有的平台转义实现。
- `ELECTRON_RUN_AS_NODE` 不写入 Main 全局环境。
- npm/npx 使用安装包中同一锁定 npm 发行版的 CLI。
- npm 子进程范围设置 `npm_execpath` 和 `npm_node_execpath`。
- 自定义 Node 只使用与其配套且验证成功的 npm/npx,不混入 GoodBuddy npm。
- 普通 JavaScript 和 Node 内置模块属于支持路径。
- 普通 Node 原生扩展可能受 Electron ABI 影响,不承诺兼容。
## 6. Python 托管环境
### 6.1 包
- 使用包含 pip、venv、SSL 和标准库的固定 CPython 独立发行包。
- 每个平台/架构锁定一个版本。
- Windows x64 原生工件来自官方 `python` NuGet 包,Windows ARM64 来自官方
`pythonarm64` NuGet 包。
- macOS/Linux 原生工件来自 Astral `python-build-standalone`。
- 目录包含版本、平台、架构、字节数、SHA-256、原生 Target 和 OSS Target。
- OSS 对象直接复制原生工件字节,不重新压缩或转换。
- 安装包不无条件携带完整 Python。
- 归档条目唯一性按目标文件系统判断:Linux TAR 区分大小写,Windows 与 macOS 拒绝
大小写冲突,所有目标都拒绝完全相同的重复路径。
具体发行包和版本必须在实现前完成六平台工件、维护状态和 pip/venv 可用性核验。
目录示例:
```ts
type ManagedToolArtifact = {
id: string
version: string
platform: NodeJS.Platform
architecture: string
size: number
sha256: string
downloads: {
native: {
url: string
redirectHosts: string[]
}
oss: {
url: string
}
}
}
```
URL 只存在于 Main/构建目录,不进入 Renderer。两个 Target 共用顶层 `size` 和 `sha256`,
从 schema 上禁止声明不同字节。
### 6.2 安装
```text
读取固定目录
→ 下载到 GoodBuddy-owned 暂存
→ 校验大小和 SHA-256
→ 解压到新版本目录
→ 运行完整环境诊断
→ 发布为当前托管 Python
→ 删除暂存
```
失败保留已验证旧安装。删除只作用于 GoodBuddy-owned Python 目录。
### 6.3 包依赖
首期提供解释器和 pip,不自动扫描或安装任意 `requirements.txt`。第三方包由 SKILL/MCP
安装流程或用户明确命令准备。出现可复现的包冲突后,再评估能力级虚拟环境。
## 7. 自定义环境探测
### 7.1 Node
候选来源:
- GoodBuddy 启动时继承的合并 PATH,其中包含操作系统提供的用户 PATH 和系统 PATH。
- 当前 `VIRTUAL_ENV`、`CONDA_PREFIX`、`NVM_HOME`/`NVM_BIN` 和 `VOLTA_HOME`。
- Windows 用户目录和 ProgramData 下的 Conda/Miniconda/Anaconda base 与 `envs`,
LocalAppData Python,以及 Program Files/系统盘常见 Node/Python 目录。
- macOS/Linux 的 `/usr/local/bin`、`/usr/bin`、Homebrew、本地用户 bin、Python
Framework、Conda、pyenv 和 nvm 常见目录。
- `CONDA_ENVS_PATH` 中的有界环境目录。
- 已保存路径。
目录枚举有总量和单目录上限,不递归扫描整块磁盘。每个候选使用分离参数启动检查,确认
普通文件、成功退出、可解析版本和架构,并按真实路径去重。
### 7.2 Python
- Windows 探测 `python.exe`。
- macOS/Linux 优先 `python3`,其次为明确的 Python 3 `python`。
- Conda/venv 候选使用各环境自己的解释器,不依赖环境激活脚本。
- 拒绝未安装的 Windows Store App Execution Alias。
- 诊断 Python 3、版本、架构和解释器绝对路径。
单候选和总探测具有超时、输出上限、取消和进程树清理。
## 8. 完整诊断
### 8.1 Node
- `node --version`
- 加载标准库
- 启动最小子进程
- `npm --version`
- `npx --version`
- npm/npx 实际 Node 来源一致
### 8.2 Python
- Python 3、版本和架构
- 标准库导入
- SSL/证书模块
- `python -m pip --version`
- 在临时目录创建最小 venv
诊断不联网,使用 GoodBuddy-owned 临时目录并清理。
## 9. 环境注入
新启动的以下本机进程将工具 bin 放到现有筛选 PATH 前面:
- OpenCode Server。
- Continue Utility Process。
- DeepSeek Harness Utility Process。
- Main 启动的 stdio MCP。
- Runtime 启动的 stdio MCP。
不注入 Main 全局环境、Renderer、普通终端、SSH 请求、Agent 或远程 Runtime。
设置变化后,有活动请求的 Runtime 保持启动时快照;空闲 Runtime 在下次使用前使用既有
重建入口。已运行 MCP 在正常关闭、重连或显式测试后使用新环境,不增加热切换协议。
## 10. SKILL 与 MCP
SKILL 稳定调用命令名,不接收真实安装路径。
- `resources/skills` 顶层只保留五个带 `SKILL.md` 的独立包:
`deai-writing`、`longdoc-docx`、`product-evidence`、`product-marketing`、
`product-presentation`。
- 九种纯 Markdown 产品营销配方位于 `product-marketing/workflows/<route-id>/`,
使用 `WORKFLOW.md` 与同目录 `templates/`,不参与顶层 Skill 发现。
- `CapabilityService` 仅在状态中没有该 ID 时应用默认值。三个产品营销 Skill 默认
`enabled: false`;两个通用文档工具和导入 Skill 维持默认启用。显式保存状态不迁移、
不覆盖。
- 默认停用的 Skill 不进入 Runtime 指令和原生 Skill 包列表;用户启用后沿用现有四个
Runtime 分配。
stdio MCP:
- 裸 `node`、`npm`、`npx`、`python`、`python3`、`pip` 使用工具 bin。
- `env node`/`env python3` 脚本通过 PATH 使用同一来源。
- 绝对 command 保持原样。
- HTTP/SSE 不受影响。
- 工具环境不自动安装 MCP 第三方依赖。
## 11. Renderer 状态与 IPC
### 11.1 统一设置分类
- `settingsCategoryList` 用一个 `capabilities` 分类替换现有 `skills`、`mcp` 分类,不增加
`sdk` 分类。
- 新增 `CapabilitiesAndToolsSettingsSection`,在统一分类标题下使用共享 `PageTabs` 渲染
第一阶段的 `skills | mcp`。
- `SkillsSettingsSection` 和 `McpSettingsSection` 继续拥有各自业务状态;嵌入时不重复渲染
一级分类标题。MCP 原有内层 `PageTabs` 保持独立。
- `ToolEnvironmentSettingsSection` 只承载工具下载源、Node.js、Python 和诊断。
- 当前源码没有 Settings 外部 Skills/MCP 定向入口,第一阶段不增加未使用的
`initialCapabilityTab`。未来出现真实定向入口时再增加会话级初始 Tab 参数。
- 普通进入 `capabilities` 时默认 `skills`。外层选中值只存在于当前 Renderer 会话,不写入
Shared 设置。
- 为避免切换时丢失编辑状态,外层面板首次访问时挂载,之后在本次设置面板会话中保留并用
`hidden` 控制可见性;关闭设置面板后按现有生命周期释放。
- 工具执行环境生产路径完成时,把 `tool-environment` 追加到同一个组件;完成前不注册
Tab、不渲染占位面板。
- 页签直接包装层使用 `flex: 0 0 auto`,并由 CSS 回归测试保证在滚动设置布局中不可收缩。
### 11.2 工具环境状态与 IPC
Renderer 接收:
- 当前选择。
- 当前实际来源、版本、路径。
- 有界候选列表。
- 托管安装状态、版本、占用空间。
- 工具诊断分项结果。
- 安装进度和脱敏错误。
- 受管工具工件下载源选择,以及活动任务实际冻结的来源。
Renderer 不能提交 PATH、环境变量、下载 URL、摘要、安装目录、shim 内容或任意命令参数。
所有 IPC 输入使用共享 Zod schema,Main 重新验证路径和解释器。
## 12. 受管工具工件下载
- 设置值为 `native | oss`,默认 `native`。
- 下载操作开始时读取并冻结设置。
- 解析目录中对应 Target;缺失即返回“当前下载源不可用”。
- 原生 Target 按每个上游声明严格 Host/重定向 allowlist。
- OSS Target 只允许 GoodBuddy 固定 OSS Host 和不可变对象前缀。
- 两种来源共用大小和 SHA-256 校验、暂存、取消、发布和清理逻辑。
- 失败不重试另一来源。
- 构建/发布流程先下载原生工件、校验,再把原字节同步到 OSS,并复核公开对象。
- 该设置不传给 pip/npm,不改写其 registry;也不参与模型、应用更新和 Agent 目录选择。
## 13. 权限与远程边界
- Ask/Execute 语义不变。
- 不新增解释器批准流程。
- 不上传 shim、Python、pip 包或自定义路径。
- 不修改 Host PATH。
- 不把本机 SKILL 或 stdio MCP 传给远程 ACP Session。
- 远程 Agent 固定 Node 继续只服务 Agent、模型桥和签名 Runtime。
## 14. 实施顺序
### 阶段 1:契约、迁移、探测和 UI
- Shared schema、设置迁移、IPC。
- 用“能力与工具”分类合并现有 Skills/MCP 入口,增加三个外层水平 Tab。
- 增加“工具执行环境”面板及原生/OSS 下载源设置。
- Node/Python 候选探测和完整诊断。
- 设置卡片和状态。
### 阶段 2:托管 Node 与执行链路
- 提取 DSH shim。
- 增加 node/npm/npx。
- 注入本机 Runtime 和 stdio MCP。
### 阶段 3:托管 Python
- 锁定六平台工件。
- 下载、校验、安装、更新、取消、删除。
- 增加 python/python3/pip。
### 阶段 4:真实端到端验收
- Node SKILL 与 MCP。
- Python SKILL 与 MCP。
- 自定义解释器。
- Ask/Execute、重建、取消和退出。
- 六平台打包。
这些阶段是开发顺序,不是可长期发布的残缺功能。合并能力前完成普通用户和专业用户的完整
生产路径。
## 15. 验证
单元测试覆盖设置迁移、探测、版本解析、shim 转义、PATH 顺序、失效路径、下载摘要和暂存
清理,以及原生/OSS 选择冻结、Target 缺失、禁止回退和目标文件系统的归档大小写语义。
Renderer 测试覆盖统一一级分类、三个外层 Tab、默认 Skills、Skills/MCP 定向入口、会话内
状态保留、MCP 内外层 tablist 隔离、键盘切换和窄屏单行滚动。
集成测试覆盖安装包 Electron Node、npm/npx、托管 Python、三个本机 Runtime 和两类 stdio
MCP 的实际进程路径。
项目验证:
```text
npm test
npm run typecheck
npm run lint
npm run build
```
生产资源通过六平台原生 CI 打包验证。若后续改动触及 Agent、远程协议或远程 Runtime,
再按远程架构文档执行共享 Linux x64 Host 验证。

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