diff --git a/.github/workflows/agent-release.yml b/.github/workflows/agent-release.yml index 49bdd92e..d0deaf58 100644 --- a/.github/workflows/agent-release.yml +++ b/.github/workflows/agent-release.yml @@ -189,15 +189,13 @@ jobs: } NODE - - name: Preflight production signing identities + - name: Preflight production Agent signing identity shell: bash env: GOODBUDDY_AGENT_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_AGENT_SIGNING_KEY_ID }} - GOODBUDDY_REMOTE_RUNTIME_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_REMOTE_RUNTIME_SIGNING_KEY_ID }} run: | set -euo pipefail node build/agent-bundle.cjs preflight - node build/remote-runtime-bundle.cjs preflight - name: Build production compound Agent package shell: bash @@ -206,8 +204,6 @@ jobs: AGENT_VERSION: ${{ needs.authorize.outputs.version }} GOODBUDDY_AGENT_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_AGENT_SIGNING_KEY_ID }} GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY }} - GOODBUDDY_REMOTE_RUNTIME_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_REMOTE_RUNTIME_SIGNING_KEY_ID }} - GOODBUDDY_REMOTE_RUNTIME_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_REMOTE_RUNTIME_SIGNING_PRIVATE_KEY }} NODE_ARCHIVE: ${{ steps.inputs.outputs.node-archive }} OPENCODE_ARCHIVE: ${{ steps.inputs.outputs.opencode-archive }} run: | diff --git a/AGENTS.md b/AGENTS.md index c94f6921..be5b80e7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -227,10 +227,10 @@ Release note: 修复左上角项目设置与消息通道项目设置不一致的 the checked-in public key registry. - `.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, production Agent and - Runtime signing-key preflights, a signed cumulative catalog, a non-Latest - GitHub Agent Release, and synchronized immutable Beijing OSS objects plus the - final signed latest-catalog pointer. + `agent-signing` Environment, native x64/arm64 builds, one production Agent + signing identity for every layer of the compound package and catalog, a + non-Latest GitHub Agent Release, and synchronized immutable Beijing OSS + objects plus the final signed latest-catalog pointer. - `.github/workflows/packages.yml` and `release:package` build only desktop products. Missing Agent packages, remote Runtime inputs, or Agent signing credentials must never block an ordinary desktop package or release. diff --git a/BUILD.md b/BUILD.md index bcaf01a1..36ec3ca1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -197,15 +197,15 @@ Linux x64/arm64 复合包。每个 job 从 `agent-runtime-lock.json` 解析官 发布路径:只接受指向受保护 `main` 历史的 annotated `agent-v${agent-runtime-lock.agentVersion}` 标签,在 `agent-signing` Environment 中 原生构建两种架构,生成并签名累计目录,然后同步到非 Latest 的 GitHub Agent Release -与北京 OSS。该 Environment 保护以下两组变量与 Secret: +与北京 OSS。该 Environment 只保护一组 GoodBuddy Agent 发布变量与 Secret: - `GOODBUDDY_AGENT_SIGNING_KEY_ID` / `GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY` -- `GOODBUDDY_REMOTE_RUNTIME_SIGNING_KEY_ID` / - `GOODBUDDY_REMOTE_RUNTIME_SIGNING_PRIVATE_KEY` -私钥只注入对应签名步骤;预检只读取 key ID 并确认公钥已在 -`resources/agent-release-keys.json` 注册为 production 且未撤销。 +该身份统一签署内部 Agent、固定 Runtime manifest、外层 `.gbagent` 描述符和累计目录, +各层通过既有签名域区分用途,不再要求内部 Runtime 单独配置生产身份。私钥只注入签名 +步骤;预检只读取 key ID 并确认公钥已在 `resources/agent-release-keys.json` +注册为 production 且未撤销。 累计目录使用 `agent-catalog.json` 与 `agent-catalog.sig`。每个条目绑定 Agent 版本、 最低桌面版本、Agent 协议、远端 OpenCode 版本/digest、架构、文件名、大小、SHA-256 diff --git a/FEATURES.md b/FEATURES.md index 19afd442..4680f515 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -23,7 +23,7 @@ - [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。缺少某架构包只使该架构托管 SSH 不可用。持久化验证继续精确绑定 Agent installation 与签名 Runtime digest;托管 SSH 会话只显示支持的 OpenCode 选择。Windows 到 Linux x64 的安装、provider-free 模型桥、断线重放、Session 续接和零残留清理已完成真实 Host 验证;签名 Agent 0.11.2-e2e.12 与 OpenCode Runtime 1.18.9 已完成一次只提交一遍、只调用一个 Ask 原生 read、无标题轮次且精确返回证据内容的有界真实模型验收,当前源码 lock 已转为正式 Agent 0.11.2。 +- [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。缺少某架构包只使该架构托管 SSH 不可用。持久化验证继续精确绑定 Agent installation 与签名 Runtime digest;托管 SSH 会话只显示支持的 OpenCode 选择。Windows 到 Linux x64 的安装、provider-free 模型桥、断线重放、Session 续接和零残留清理已完成真实 Host 验证;签名 Agent 0.11.2-e2e.12 与 OpenCode Runtime 1.18.9 已完成一次只提交一遍、只调用一个 Ask 原生 read、无标题轮次且精确返回证据内容的有界真实模型验收,当前源码 lock 已转为正式 Agent 0.11.3。 - [x] **SSH Host 运行环境手动更新**:远程运行环境卡片只读取 Host 已安装 Agent/Runtime 与当前本地复合包所需版本,不会联网。用户显式触发后,Main 按 Agent、Runtime 顺序使用对应 Host 架构的已验证本地 `.gbagent` 强制重新校验并安装,显示阶段进度并允许在最终收尾前取消;尚未下载时提示先到平台功能设置下载或导入。更新期间保留旧版本卡片,失败或取消后刷新实际状态并允许重试;成功后定向清理该 Host 缓存并使引用项目重新验证,不删除 Host 配置、凭据、项目设置或 Workspace 文件。 - [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 初始化隔离。 diff --git a/agent-runtime-lock.json b/agent-runtime-lock.json index 6fa926fa..29f20451 100644 --- a/agent-runtime-lock.json +++ b/agent-runtime-lock.json @@ -1,6 +1,6 @@ { "formatVersion": 1, - "agentVersion": "0.11.2", + "agentVersion": "0.11.3", "protocol": { "major": 2, "minor": 0 diff --git a/build/agent-bundle.cjs b/build/agent-bundle.cjs index d86fc492..888a1034 100644 --- a/build/agent-bundle.cjs +++ b/build/agent-bundle.cjs @@ -791,7 +791,9 @@ function buildAgentBundle(options) { options.registry ?? readTrustedKeyRegistry(projectRoot) const signingEnvironment = options.environment ?? process.env const signingIdentity = - options.testSigningIdentity ?? productionSigningKey(signingEnvironment) + options.testSigningIdentity ?? + options.signingIdentity ?? + productionSigningKey(signingEnvironment) const signingRecord = options.testSigningIdentity ? registry.keys.find( (key) => key.keyId === signingIdentity.keyId @@ -816,6 +818,7 @@ function buildAgentBundle(options) { } if ( !options.testSigningIdentity && + !options.signingIdentity && signingEnvironment === process.env ) { delete process.env[signingPrivateKeyEnvironment] diff --git a/build/agent-package.cjs b/build/agent-package.cjs index ffbd186b..ef9e58d2 100644 --- a/build/agent-package.cjs +++ b/build/agent-package.cjs @@ -400,6 +400,7 @@ function buildAgentPackage(options) { options.registry ?? readTrustedKeyRegistry(projectRoot) const signingIdentity = options.testSigningIdentity ?? + options.signingIdentity ?? productionSigningKey(options.environment ?? process.env) const agentRoot = mkdtempSync( join(tmpdir(), `goodbuddy-agent-build-${options.architecture}-`) @@ -417,7 +418,7 @@ function buildAgentPackage(options) { ...(options.agentLock ? { lock: options.agentLock } : {}), ...(options.testSigningIdentity ? { testSigningIdentity: options.testSigningIdentity } - : {}) + : { signingIdentity }) }) const runtime = buildRuntimeBundle({ projectRoot, @@ -428,7 +429,7 @@ function buildAgentPackage(options) { ...(options.runtimeLock ? { lock: options.runtimeLock } : {}), ...(options.testSigningIdentity ? { testSigningIdentity: options.testSigningIdentity } - : {}) + : { signingIdentity }) }) return assembleAgentPackage({ ...options, diff --git a/build/remote-runtime-bundle.cjs b/build/remote-runtime-bundle.cjs index d355918d..0737ddb6 100644 --- a/build/remote-runtime-bundle.cjs +++ b/build/remote-runtime-bundle.cjs @@ -44,9 +44,9 @@ const supportedArchitectures = Object.freeze(['x64', 'arm64']) const manifestFileName = 'manifest.json' const signatureFileName = 'manifest.sig' const signingPrivateKeyEnvironment = - 'GOODBUDDY_REMOTE_RUNTIME_SIGNING_PRIVATE_KEY' + 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' const signingKeyIdEnvironment = - 'GOODBUDDY_REMOTE_RUNTIME_SIGNING_KEY_ID' + 'GOODBUDDY_AGENT_SIGNING_KEY_ID' const signatureDomain = Buffer.from( 'GoodBuddy Remote Runtime Bundle Manifest Signature v1\0', 'utf8' @@ -265,7 +265,7 @@ function productionSigningIdentity(environment = process.env) { const privateKey = environment[signingPrivateKeyEnvironment] if (!keyId || !privateKey) { throw new Error( - `${signingKeyIdEnvironment} and ${signingPrivateKeyEnvironment} are required for production Runtime signing` + `${signingKeyIdEnvironment} and ${signingPrivateKeyEnvironment} are required to sign the bundled Runtime` ) } return { keyId, privateKey } @@ -318,11 +318,11 @@ function preflightProductionSigningKey(options = {}) { const registry = options.registry ?? readTrustedKeyRegistry(projectRoot) return preflightRegisteredProductionKey({ - component: 'Runtime', + component: 'Agent', keyId, registry, missingKeyIdMessage: - `${signingKeyIdEnvironment} is not configured; provision the production Runtime signing key ID before building a release` + `${signingKeyIdEnvironment} is not configured; provision the production Agent signing key ID before building a release` }) } @@ -768,6 +768,7 @@ function buildRuntimeBundle(options) { options.registry ?? readTrustedKeyRegistry(projectRoot) const signingIdentity = options.testSigningIdentity ?? + options.signingIdentity ?? productionSigningIdentity(options.environment ?? process.env) const verificationEnvironment = options.testSigningIdentity ? 'test' : 'production' @@ -1050,7 +1051,7 @@ function main(argv = process.argv.slice(2)) { if (options.command === 'preflight') { const record = preflightProductionSigningKey() console.log( - `Production Runtime signing preflight passed: ${record.keyId}` + `Production Agent signing preflight passed for bundled Runtime: ${record.keyId}` ) return } diff --git a/docs/architecture/remote-host-and-goodbuddy-agent-design.md b/docs/architecture/remote-host-and-goodbuddy-agent-design.md index 285dc62e..e99a9f8c 100644 --- a/docs/architecture/remote-host-and-goodbuddy-agent-design.md +++ b/docs/architecture/remote-host-and-goodbuddy-agent-design.md @@ -2,7 +2,7 @@ ## 状态 -本文记录当前代码实现,不定义额外的信任框架。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 后,当前源码 lock 已转为正式 Agent `0.11.2`,尚未复用旧版本号。远程 OpenCode 功能仍处于发布前验证阶段;公开签名 key registry 已供应,当前发布门槛是通过独立 Agent workflow 正式发布并公开验证 Linux x64/arm64 复合包和签名累计目录。 +本文记录当前代码实现,不定义额外的信任框架。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 后,当前源码 lock 已转为正式 Agent `0.11.3`,尚未复用旧版本号。远程 OpenCode 功能仍处于发布前验证阶段;公开签名 key registry 已供应,当前发布门槛是通过独立 Agent workflow 正式发布并公开验证 Linux x64/arm64 复合包和签名累计目录。 ## 产品语义 @@ -170,7 +170,7 @@ Execute 不经过 Ask 的 bubblewrap profile: - 2026-08 的本地 fixture 完整验证 Linux x64 Agent `0.11.2-e2e.12`、Node `24.19.0` 和 Agent protocol `2.0`;当时没有 arm64 fixture,因此该记录不能作为当前独立发布 - 的双架构验收。当前源码 lock 是正式版本 `0.11.2`,需要由新的复合包发布流程另行验证。 + 的双架构验收。当前源码 lock 是正式版本 `0.11.3`,需要由新的复合包发布流程另行验证。 - 正常 Host 更新路径把 Linux x64 Host 的 Agent 更新为 `0.11.2-e2e.12`,并确认 OpenCode Runtime 已安装版本与所需版本均为 `1.18.9`。 - 一条新的 Ask 用户操作只提交一次。OpenCode 先在 build 模型轮次请求一个原生 @@ -216,7 +216,9 @@ Renderer 暴露固定方法名、数字 RPC code 和有界 service code,不转 `.remote-runtime-resources` 或任何可安装 Linux 远端 payload。 - `build/agent-package.cjs` 在对应原生 Linux 架构组装确定性 `.gbagent`,外层签名覆盖 描述符和每个内部文件身份;内部 Agent 与 Runtime 仍分别使用既有签名 manifest 和 lock - 校验。`build/agent-catalog.cjs` 为双架构包生成签名累计目录,拒绝同一版本/架构改变字节。 + 校验,但所有 production 层统一使用一组 GoodBuddy Agent 发布身份并通过签名域区分用途, + 不要求内部 Runtime 单独配置密钥。`build/agent-catalog.cjs` 为双架构包生成签名累计目录, + 拒绝同一版本/架构改变字节。 - `.github/workflows/agents.yml` 只使用进程内临时测试 key 做分支/PR 原生验证,不发布。 `.github/workflows/agent-release.yml` 才可在 annotated `agent-v` 标签和受保护 `agent-signing` Environment 中构建 production 包。Agent GitHub Release 必须 diff --git a/package-lock.json b/package-lock.json index 97b20937..9ad10527 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "goodbuddy", - "version": "0.11.2", + "version": "0.11.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "goodbuddy", - "version": "0.11.2", + "version": "0.11.3", "license": "0BSD", "dependencies": { "@agentclientprotocol/sdk": "0.25.1", diff --git a/package.json b/package.json index e95a3721..17beedc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "goodbuddy", - "version": "0.11.2", + "version": "0.11.3", "private": true, "description": "Secure desktop AI workspace with controlled Agent Runtimes", "desktopName": "GoodBuddy", diff --git a/resources/release-notes.json b/resources/release-notes.json index 9d47ccc2..16933b58 100644 --- a/resources/release-notes.json +++ b/resources/release-notes.json @@ -2,12 +2,12 @@ "formatVersion": 1, "releases": [ { - "version": "0.11.2", + "version": "0.11.3", "releasedAt": "2026-08-25", "notes": { "zh-CN": { "highlights": [ - "GoodBuddy 0.11.2 新增托管 SSH 远程项目、内置 Granite 向量模型、会话分支和应用内反馈,并改进魔法笔记、知识图谱、模型下载及桌面布局。" + "GoodBuddy 0.11.3 新增托管 SSH 远程项目、内置 Granite 向量模型、会话分支和应用内反馈,并改进魔法笔记、知识图谱、模型下载及桌面布局。" ], "features": [ "**托管 SSH 远程项目(技术预览)。** 在“设置 → 平台功能”中启用后,可管理 Linux x64/arm64 Host,并通过 Ask 或 Execute 使用签名 Agent 与兼容的 OpenCode Runtime。Agent 包需在设置中手动下载、更新或离线导入,打开项目不会自动下载;SSH 断线后,Host 上的任务可继续运行并恢复未同步输出。", @@ -18,6 +18,7 @@ "**Linux 与 LoongArch 构建。** Linux x64/arm64 桌面版新增 RPM 安装包,并提供独立、可复现的 Debian loong64 实验性预览构建流程。" ], "fixes": [ + "**Agent 包验证与安装。** 修复 Linux 上解压 `.gbagent` 后目录权限不符合校验要求、导致复合 Agent 包无法通过验证的问题;托管 SSH Agent 包现在可按预期验证并安装其内置 Runtime。", "**向量模型下载反馈。** 修复点击内置向量模型“下载”后界面看似无响应的问题;现在会立即显示下载来源、模型信息、实时进度和取消操作,行为与语音模型下载一致。", "**长文档知识图谱。** 图谱抽取改为按 Token 分批、重试和超时处理;图谱失败不再导致新文档全文检索一并失败,并改进中文检索与关系上下文。", "**ModelScope 语音模型。** 修复选择 ModelScope 后部分语音模型仍无法下载的问题,六个支持的模型现在均使用固定且经过完整性验证的来源。", @@ -26,14 +27,14 @@ "**界面信息与会话列表。** 恢复关键页面说明和视觉层级;会话更多操作仅在悬停、键盘聚焦或菜单打开时显示,为标题和时间保留更多空间。" ], "notices": [ - "远程项目默认关闭且完全可选。首次使用前必须手动准备兼容的 `.gbagent` 包,下载源跟随“关于与更新”中的 GitHub 或镜像节点选择;本次正式 Agent 版本为 `0.11.2`。", - "LoongArch 仍是独立实验性预览,不进入标准更新索引;当前不包含 Agent、Remote Runtime、OpenCode 和原生 Canvas。", + "远程项目默认关闭且完全可选。首次使用前必须手动准备兼容的 `.gbagent` 包,下载源跟随“关于与更新”中的 GitHub 或镜像节点选择;本次正式 Agent 版本为 `0.11.3`。", + "LoongArch 仍是独立实验性预览,不进入标准更新索引;当前不包含 Agent、Remote Runtime、OpenCode 和原生 Canvas。本次 `0.11.3` 不提供 LoongArch 预览包。", "反馈不会自动附带会话、日志、文件、设置或凭据。" ] }, "en-US": { "highlights": [ - "GoodBuddy 0.11.2 adds managed SSH remote projects, a built-in Granite embedding model, conversation branching, and in-app feedback, with improvements to Magic Notes, knowledge graphs, model downloads, and desktop layout." + "GoodBuddy 0.11.3 adds managed SSH remote projects, a built-in Granite embedding model, conversation branching, and in-app feedback, with improvements to Magic Notes, knowledge graphs, model downloads, and desktop layout." ], "features": [ "**Managed SSH remote projects (technical preview).** Enable Remote Projects under Settings → Platform Features to manage Linux x64/arm64 Hosts and use signed Agents with a compatible OpenCode Runtime in Ask or Execute. Agent packages must be downloaded, updated, or imported manually in Settings; opening a project never downloads one automatically. Host work can continue through an SSH disconnect and recover unsynchronized output.", @@ -44,6 +45,7 @@ "**Linux and LoongArch builds.** Linux x64/arm64 desktop releases add RPM installers, and the repository now provides a separate reproducible experimental Debian loong64 preview build." ], "fixes": [ + "**Agent package verification and installation.** Fixed extracted `.gbagent` directory permissions on Linux preventing compound Agent packages from passing verification. Managed SSH Agent packages can now verify and install their bundled Runtime as intended.", "**Embedding model download feedback.** Fixed the built-in model Download action appearing unresponsive. It now immediately shows the source, model details, live progress, and cancellation, matching speech model downloads.", "**Long-document knowledge graphs.** Graph extraction now uses token-bounded batches, retries, and timeouts. Graph failures no longer discard a new document’s full-text index, and Chinese retrieval and relationship context are improved.", "**ModelScope speech models.** Fixed supported speech models remaining unavailable after selecting ModelScope. All six models now use fixed, integrity-verified sources.", @@ -52,8 +54,8 @@ "**Interface guidance and conversation rows.** Restored essential workflow guidance and visual hierarchy. Conversation actions now appear only on hover, keyboard focus, or while their menu is open, leaving more room for titles and timestamps." ], "notices": [ - "Remote Projects are disabled by default and remain optional. A compatible `.gbagent` package must be prepared manually before first use, and its source follows the GitHub or mirror selection in About & Updates. This release uses the formal Agent version `0.11.2`.", - "LoongArch remains a separate experimental preview outside the standard update index. It currently excludes Agent, Remote Runtime, OpenCode, and native Canvas.", + "Remote Projects are disabled by default and remain optional. A compatible `.gbagent` package must be prepared manually before first use, and its source follows the GitHub or mirror selection in About & Updates. This release uses the formal Agent version `0.11.3`.", + "LoongArch remains a separate experimental preview outside the standard update index. It currently excludes Agent, Remote Runtime, OpenCode, and native Canvas. No LoongArch preview package is provided for `0.11.3`.", "Feedback never automatically includes conversations, logs, files, settings, or credentials." ] } diff --git a/src/main/remote-agent/agent-package-verifier.ts b/src/main/remote-agent/agent-package-verifier.ts index 65220b5d..eb8e4117 100644 --- a/src/main/remote-agent/agent-package-verifier.ts +++ b/src/main/remote-agent/agent-package-verifier.ts @@ -67,6 +67,7 @@ const MAXIMUM_EXPANDED_BYTES = 1024 * 1024 * 1024 const MAXIMUM_FILE_BYTES = 384 * 1024 * 1024 const MAXIMUM_METADATA_BYTES = 1024 * 1024 const MAXIMUM_ENTRIES = 50_002 +const PRIVATE_DIRECTORY_MODE = 0o700 const SIGNATURE_DOMAIN = Buffer.from( 'GoodBuddy Agent Package Descriptor Signature v1\0', 'utf8' @@ -91,7 +92,10 @@ export async function extractAndVerifyAgentPackage(options: { const archivePath = resolve(options.archivePath) const destinationDirectory = resolve(options.destinationDirectory) await assertArchiveFile(archivePath) - await mkdir(destinationDirectory, { recursive: false }) + await mkdir(destinationDirectory, { + recursive: false, + mode: PRIVATE_DIRECTORY_MODE + }) try { await extractArchive(archivePath, destinationDirectory) return await verifyExtractedAgentPackage({ @@ -354,7 +358,8 @@ async function extractArchive( throw new Error('Agent package entry escapes its destination') } const handlePromise = mkdir(dirname(destinationPath), { - recursive: true + recursive: true, + mode: PRIVATE_DIRECTORY_MODE }).then(async () => { const handle = await open(destinationPath, 'wx') openHandles.add(handle) diff --git a/tests/agent-package.test.ts b/tests/agent-package.test.ts index 36fcee67..2c47fd81 100644 --- a/tests/agent-package.test.ts +++ b/tests/agent-package.test.ts @@ -6,6 +6,7 @@ import { } from 'node:crypto' import { chmodSync, + lstatSync, mkdirSync, mkdtempSync, readFileSync, @@ -269,7 +270,7 @@ afterAll(() => { }) describe('compound Agent packages', () => { - it('assembles deterministic signed Agent and Runtime archives', async () => { + it('assembles deterministic signed archives with private extraction', async () => { const secondRoot = join(temporaryRoot, 'second') mkdirSync(secondRoot) const secondPath = join( @@ -290,15 +291,14 @@ describe('compound Agent packages', () => { ) const contentRoot = join(temporaryRoot, 'verified') - await expect( - extractAndVerifyAgentPackage({ - archivePath: packagePath, - destinationDirectory: contentRoot, - architecture: 'x64', - desktopVersion: '0.11.0', - trustedRegistry: registry - }) - ).resolves.toMatchObject({ + const verified = await extractAndVerifyAgentPackage({ + archivePath: packagePath, + destinationDirectory: contentRoot, + architecture: 'x64', + desktopVersion: '0.11.0', + trustedRegistry: registry + }) + expect(verified).toMatchObject({ descriptor: { version: agentLock.agentVersion, architecture: 'x64', @@ -308,6 +308,15 @@ describe('compound Agent packages', () => { } } }) + if (process.platform !== 'win32') { + for (const directory of [ + verified.rootDirectory, + verified.agentBundle.bundleDirectory, + verified.runtimeBundle.bundleDirectory + ]) { + expect(lstatSync(directory).mode & 0o777).toBe(0o700) + } + } const second = agentPackage.assembleAgentPackage({ projectRoot: process.cwd(), @@ -412,7 +421,10 @@ describe('compound Agent packages', () => { ) const mismatched = { ...descriptor, - version: '0.11.3' + version: + descriptor.version === '999.0.0' + ? '999.0.1' + : '999.0.0' } mismatched.contentDigest = agentPackage.descriptorContentDigest(mismatched) diff --git a/tests/agent-release-workflow.test.ts b/tests/agent-release-workflow.test.ts index 00c20d33..4eaffa17 100644 --- a/tests/agent-release-workflow.test.ts +++ b/tests/agent-release-workflow.test.ts @@ -99,8 +99,8 @@ describe('independent Agent release workflow', () => { expect(build).toContain( 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' ) - expect(build).toContain( - 'GOODBUDDY_REMOTE_RUNTIME_SIGNING_PRIVATE_KEY' + expect(workflow).not.toContain( + 'GOODBUDDY_REMOTE_RUNTIME_SIGNING_' ) expect(buildScripts).toContain('.gbagent') }) @@ -171,8 +171,5 @@ describe('independent Agent release workflow', () => { expect(serializedJob('publish')).not.toContain( 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' ) - expect(serializedJob('publish')).not.toContain( - 'GOODBUDDY_REMOTE_RUNTIME_SIGNING_PRIVATE_KEY' - ) }) }) diff --git a/tests/packages-workflow.test.ts b/tests/packages-workflow.test.ts index 012e7786..35ba2f73 100644 --- a/tests/packages-workflow.test.ts +++ b/tests/packages-workflow.test.ts @@ -78,9 +78,6 @@ describe('desktop packages workflow', () => { expect(workflow).not.toContain( 'GOODBUDDY_AGENT_SIGNING_PRIVATE_KEY' ) - expect(workflow).not.toContain( - 'GOODBUDDY_REMOTE_RUNTIME_SIGNING_PRIVATE_KEY' - ) }) it('packages all six desktop targets without remote component dependencies', () => { diff --git a/tests/remote-runtime-bundle.test.ts b/tests/remote-runtime-bundle.test.ts index 10d03327..2ed62e5e 100644 --- a/tests/remote-runtime-bundle.test.ts +++ b/tests/remote-runtime-bundle.test.ts @@ -51,7 +51,11 @@ type RemoteRuntimeBundleModule = { outputRoot: string lock: RemoteRuntimeLock registry: AgentReleaseKeyRegistry - testSigningIdentity: { + testSigningIdentity?: { + keyId: string + privateKey: KeyObject + } + signingIdentity?: { keyId: string privateKey: KeyObject } @@ -311,7 +315,7 @@ describe('Remote Runtime bundle tooling', () => { runtimeBundle.preflightProductionSigningKey({ projectRoot: process.cwd(), environment: { - GOODBUDDY_REMOTE_RUNTIME_SIGNING_KEY_ID: + GOODBUDDY_AGENT_SIGNING_KEY_ID: 'runtime-fixture' }, registry: productionRegistry @@ -321,6 +325,31 @@ describe('Remote Runtime bundle tooling', () => { environment: 'production' }) }) + + it('signs the bundled Runtime with the shared Agent identity', () => { + const productionRegistry: AgentReleaseKeyRegistry = { + ...registry, + keys: registry.keys.map((key) => ({ + ...key, + environment: 'production' + })) + } + const built = runtimeBundle.buildRuntimeBundle({ + projectRoot: process.cwd(), + architecture: 'x64', + runtimeArchive, + outputRoot: join(temporaryRoot, 'production'), + lock, + registry: productionRegistry, + signingIdentity: { + keyId: 'runtime-fixture', + privateKey + }, + enforceFilesystemMode: false + }) + + expect(built.manifest.signingKeyId).toBe('runtime-fixture') + }) }) function build(outputRoot: string): VerifiedBundle {