Craft Agents 核心业务包@craft-agent/shared开发指南:架构、硬性规则与工程实践
【免费下载链接】craft-agents-oss项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss
本文以仓库中 packages/shared/CLAUDE.md 为骨架,结合
@craft-agent/shared包的源码与测试,系统讲解 Craft Agents 核心业务逻辑包的模块划分、开发硬性规则、标签体系、权限模式、i18n 国际化规范、API 源令牌刷新机制与queryLlm后端契约。读完本文,你将掌握该包的代码组织方式、可安全修改与禁止触碰的边界,以及如何正确地为包新增翻译、新增语言、实现后端查询能力。
一、包定位与整体架构
@craft-agent/shared是 Craft Agents 的核心业务逻辑包(Core business logic package),由 Electron 主应用直接使用。从 packages/shared/package.json 可以确认它的定位描述:"Shared business logic for Craft Agents - agent, auth, config, credentials, MCP integration",版本为0.11.1,采用"type": "module"的 ESM 工程,包入口与类型声明均指向src/index.ts。
按 packages/shared/CLAUDE.md 的划分,包内核心职责包括:
- Agent 后端与会话级工具(Agent backends and session-scoped tools)
- Sources、credentials、sessions 与 config(外部数据源、加密凭证、会话持久化、配置)
- 权限模式与校验(Permission modes and validation)
关键目录速览
| 目录 | 职责 | 代表性文件 |
|---|---|---|
src/agent/ | Agent 后端实现与工具 | claude-agent.ts、pi-agent.ts、base-agent.ts、llm-tool.ts |
src/sources/ | 外部数据源存储/类型/服务 | types.ts、server-builder.ts、credential-manager.ts |
src/sessions/ | 会话持久化与索引 | storage.ts、index.ts |
src/projects/ | 工作区作用域项目(配置 + 资产),会话通过projectId绑定 | index.ts、types.ts |
src/config/ | 配置/偏好/主题/监听器 | index.ts、llm-connections.ts、models-pi.ts |
src/credentials/ | 加密凭证管理 | manager.ts、index.ts |
包通过subpath exports暴露细分模块(见 packages/shared/package.json 的exports字段),典型导入方式记录在 packages/shared/src/index.ts 的模块头注释中:
import { CraftAgent } from '@craft-agent/shared/agent'; import { loadStoredConfig } from '@craft-agent/shared/config'; import { getCredentialManager } from '@craft-agent/shared/credentials'; import { CraftMcpClient } from '@craft-agent/shared/mcp'; import { loadSource, createSource, getSourceCredentialManager } from '@craft-agent/shared/sources'; import { createWorkspace, loadWorkspace } from '@craft-agent/shared/workspaces';值得注意的依赖注入设计:src/config/llm-connections.ts中的registerPiModelResolver()在应用启动时注入 Pi 模型解析器,原因是@earendil-works/pi-ai会传递引入@aws-sdk(依赖 Node.js 的stream模块),会破坏 Vite renderer 构建,因此 Pi 模型解析必须由主进程注入而不是静态导入。
类型检查命令
在仓库根目录执行:
cd packages/shared && bun run tsc --noEmit该命令对包做全量 TypeScript 无输出类型检查。包的测试脚本为bun test(test:watch为监听模式),另外还有一个维护脚本sync:craft-agent-bash-patterns用于同步 Craft Agent 的 Bash 模式配置(见 packages/shared/package.json)。
二、硬性规则:不可打破的包级约束
CLAUDE.md 用专门一节声明了包的硬性规则(Hard rules),任何改动都不能违反:
- 权限模式固定为三档:
safe、ask、allow-all。这一点在 packages/shared/src/agent/mode-types.ts 中得到印证:export type PermissionMode = 'safe' | 'ask' | 'allow-all'。该文件还定义了面向用户/会话状态的规范命名'explore' | 'ask' | 'execute'(内部键 → 规范名映射见PERMISSION_MODE_TO_CANONICAL),以及 SHIFT+TAB 循环顺序PERMISSION_MODE_ORDER = ['safe', 'ask', 'allow-all']。parsePermissionMode()同时接受规范值与历史别名(ask-to-edit等)以兼容旧数据。 - Source 类型固定为三种:
mcp、api、local。见 packages/shared/src/sources/types.ts:export type SourceType = 'mcp' | 'api' | 'local'。该文件同时定义了各类型的认证方式:MCP 源为oauth | bearer | none,API 源为bearer | header | query | basic | oauth | none,还包含 Google/Slack/Microsoft 服务类型的 URL 推断函数。 - 凭证处理必须走
src/credentials/通路:禁止在包内任何地方做即兴的秘密存储(no ad-hoc secret storage)。加密凭证管理集中在src/credentials/manager.ts等文件。 - 面向用户的工具契约保持向后兼容(backward-compatible where possible)。为此,包保留了向后兼容的别名导出
CraftAgent(详见下文 Notes)。
三、标签体系:Task 标签族与单一过滤谓词
CLAUDE.md 用大量篇幅描述标签体系的两条核心设计,这两条都直接对应 packages/shared/src/labels/filter.ts 与 packages/shared/src/labels/crud.ts 的实现。
3.1 保留的 "Task" 标签族
Task 流程会为每个任务的整个家族(orchestrator + 所有子任务)打上同一个 per-task ITEM 标签——它是普通根标签Task的子标签,命名为TASK-<slug>-<N>(无 valueType;N为根标签所有TASK-命名子标签的最大计数器 + 1,永不回收;被采用的用户根标签下不相关的子标签不会计入该计数器)。
关键规则:
- 只能通过
SessionManager.applyTaskLabel铸币/继承,其内部使用labels/crud.ts的ensureTaskLabel/ensureTaskItemLabel; - 只能通过
findTaskLabel/findTaskItemLabelId/resolveTaskScopeLabelId解析(见 packages/shared/src/labels/filter.ts); - 绝不能假定字面 id——slug 会发生碰撞位移(collide-shift),必须始终使用解析后的 id(通过
TaskCreateResult.taskLabelId暴露)。这一点从createLabel的实现可以印证:id 由名称生成 slug,若已存在则追加-2、-3后缀直至唯一(packages/shared/src/labels/crud.ts); - 遗留的
task::N带值条目仍能在根标签下过滤;ensureTaskLabel会把遗留的valueType: 'number'根标签收敛为普通标签,但对用户自己的根 "Task" 标签原样采用(形状与子标签均不触碰)。
3.2 唯一的标签过滤谓词
matchesLabelFilter(packages/shared/src/labels/filter.ts)是"会话是否匹配某个标签过滤器"的唯一实现(支持后代标签、__all__、可选的projectId作用域)。会话列表、AppShell 过滤集合、NavigationContext 自动选择都路由经过它——功能代码中禁止手写标签匹配逻辑。它的判定语义为:
projectId存在时,会话必须同属该项目(对__all__同样生效);__all__→ 任意带至少一个标签的会话;- 具体 id → 会话带有该标签或其任一后代标签;
task::3这类带值条目按基础 id 匹配。
归档状态刻意不在此函数内考虑——由调用方自行决定策略。
四、运行时与模型路由的关键实现约束
CLAUDE.md 的 Notes 部分记录了多条对运行时行为有决定性影响的实现事实,这些内容与src/agent/、src/config/、src/automations/等目录的实现一一对应。
4.1 Claude SDK 子进程环境净化
Claude SDK 子进程的环境变量会被净化,剥离 Claude 专属的 Bedrock 路由变量:CLAUDE_CODE_USE_BEDROCK、AWS_BEARER_TOKEN_BEDROCK、ANTHROPIC_BEDROCK_BASE_URL。Pi Bedrock 则使用自己的 AWS 环境变量路径,两者互不干扰。
4.2 新模型供应商优先走 Pi 路径
除非新供应商确实需要独立的运行时/后端,否则应优先通过现有 Pi 路径路由(providerType: 'pi'+piAuthProvider)。Pi 供应商目录与展示元数据位于 packages/shared/src/config/models-pi.ts。
4.3 自定义端点的模型能力覆盖
自定义端点模型的supportsImages等能力标记必须端到端保留逐模型显式覆盖:supportsImages: true为某个模型开启图片输入,而supportsImages: false必须仍可覆盖全局端点图片默认值。活跃 Pi 自定义端点会话通过updateRuntimeConfig刷新运行时能力;能力变更由llmConnections.SAVEhandler 通过SessionManager.refreshConnectionRuntime主动推送,惰性的getOrCreateAgent路径作为兜底。会话层在发送时仍会门禁图片附件,因此即使子进程刷新失败也不会发出被禁用的图片。
4.4update_runtime_configIPC 的字段边界
update_runtime_configIPC 只携带model, providerType, authType, baseUrl, customEndpoint, customModels——piAuthProvider、slug以及更广泛的凭证/供应商路由状态不能在活跃 Pi 子进程内重路由。runtime-config.ts的buildRestartRequiredSignature会把这些字段与可原地安全变更的字段分开哈希;当重启签名漂移时,tryRefreshAgentRuntime会跳过原地尝试,直接走 dispose + recreate,使新的 auth/provider 状态真正生效。
4.5 会话生命周期:硬中止 vs UI 交接中断
会话生命周期区分两类终止:
- 硬中止(hard aborts):用于真正的取消/拆除(如
UserStop、重定向回退); - 交接中断(handoff interrupts):用于控制权移交给 UI 的暂停点(如
AuthRequest、PlanSubmitted)。
远程工作区交接摘要以一次性隐藏上下文注入目标会话的第一轮对话。
4.6 WebUI 源 OAuth 中继
WebUI 源 OAuth 使用稳定的中继重定向 URI(https://agents.craft.do/auth/callback);部署特定的回调目标承载在中继持有的外层state信封中,由 router worker 解包。
4.7 自动化匹配统一化
自动化匹配统一经由 packages/shared/src/automations/utils.ts 的规范匹配器适配器(matcherMatches*)完成。功能代码应避免直接做原始匹配器检查,以保证条件门禁在应用事件与 Agent 事件间保持一致。自动化匹配器还可声明可选的telegramTopic?: string,将衍生会话路由到已配对超级群组的 Telegram 论坛主题中;该字段贯穿PendingPrompt与ExecutePromptAutomationInput,运行时解析与主题创建位于@craft-agent/messaging-gateway的TopicRegistry与MessagingGatewayRegistry.bindAutomationSession。
4.8 OpenAI SSE 剥离流与工具调用合并
unified-network-interceptor.ts的createOpenAiSseStrippingStream为每个逻辑工具调用只发出一条合并的 SSE 事件(id + name + cleanArgs一起),绝不拆分为 init + args-only 增量。原因是部分下游 SDK(如 Pi SDK)会把仅含 args 的增量当作新的 tool_calls 而非按索引合并,从而在 DeepSeek 等中继的并行工具轮次中产生重复的空 id 条目。sanitizeOpenAiHistoryInPlace用于修复修复前版本持久化的会话历史。
4.9LlmConnection.midStreamBehavior:流中用户发送的行为
midStreamBehavior控制流中用户发送是尝试引导进行中的回合(steer),还是留待下一回合(hold)。默认值按providerType由defaultMidStreamBehavior()决定(anthropic →'queue',pi/pi_compat →'steer')。必须通过resolveMidStreamBehavior(connection)读取,绝不能对该决策直接按providerType分支;缺少该字段的遗留连接依赖解析器的回退值。新连接在createBuiltInConnection时持久化显式默认值,使 Settings → AI 子菜单首次加载即显示勾选。决策仅在SessionManager.sendMessage的 mid-stream 分支做出——后端代码(claude-agent.ts、pi-agent.ts)不变:'queue'模式完全跳过agent.redirect(),让当前回合结束后再重放。对应测试见 packages/shared/src/config/tests/midstream-behavior.test.ts。
4.10 网络拦截器目前仅限 Pi
unified-network-interceptor.ts目前是Pi-only的:它通过 Bun--preload预加载进 Pi 子进程。Claude SDK 自 0.2.113 起不再运行于 Bun(改为生成各平台原生claude二进制),因此--preload不可用;原属 Claude 拦截器的功能(rich tool intent、fast-mode override、MalformedBodyError 校验等)属于 Phase-2 工作,需迁移到 SDK hooks 或本地代理。开发/monorepo 运行中 Pi 拦截器仍从.ts源码预加载,改动无需重建即生效;打包构建使用apps/electron/dist/interceptor.cjs,相关逻辑见agent/backend/internal/runtime-resolver.ts:resolveInterceptorBundlePath。
4.11 每消息上下文的 volatile 与 stable 分块
每条消息的上下文被拆分为**易变(volatile)与稳定(stable)**两块(PromptBuilder.buildVolatileContextParts()/buildStableContextParts(),由buildContextParts()组合)。volatile = 日期时间、session_state、sources(每轮变化);stable = 工作区能力、工作目录(会话内不变)。
- Claude把所有块放在用户消息尾部(系统提示保持可缓存);
- Pi只把稳定块折入系统前缀,易变块路由到用户尾部——否则每分钟的重盖章会使 pi-ai 缓存的系统前缀失效,连带下游全部历史失效(#862)。
buildVolatileContextParts会消费一次性模式变更信号(consumeModeChangeUserSignal),因此每轮必须恰好调用一次——绝不要为了计算缓存调试哈希而重新调用 builder(应改为对已产出的字符串做哈希)。
4.12 Anthropic OAuth 身份捕获
Anthropic OAuth 身份(account/org)从令牌交换响应中捕获(auth/claude-oauth.ts的parseClaudeOAuthIdentity,字段可选/失败软处理,绝不阻塞登录),并持久化到LlmConnection(oauthAccountUuid/Email、oauthOrganizationUuid/Name、oauthProfileVerifiedAt),通过SETUP_LLM_CONNECTIONpayload(oauthIdentity)透传——而不是EXCHANGE handler,因为连接记录由 SETUP 创建(在 exchange 之后运行)。updateLlmConnection从硬编码允许清单重建连接,因此任何新增的持久化字段都必须同步加进去,否则下次保存会被丢弃(#838)。
4.13 Mythos 级思考模型(Claude Fable 5 / Mythos 5)
这类模型始终开启自适应思考,且 Messages API拒绝thinking: { type: 'disabled' }(与 Opus/Sonnet/Haiku 不同)。resolveClaudeThinkingOptions通过isAdaptiveThinkingAlwaysOnModel()(config/models.ts)识别它们,把 "off"/minimizeThinking情形映射为{ thinking: { type: 'adaptive' }, effort: 'low' }而非disabled——这些模型无法关闭思考。runMiniCompletion不受影响(它运行在解析出的 mini 模型上,恒为 Haiku)。模型 id 为无日期的固定快照claude-fable-5(1M 上下文,128k 最大输出),注册于MODEL_REGISTRY。
五、i18n 国际化规范
翻译资源位于src/i18n/locales/{lang}.json。所有面向用户的字符串必须使用t()(React)或i18n.t()(非 React)。仓库当前维护 7 个语言文件:en.json、es.json、zh-Hans.json、ja.json、hu.json、de.json、pl.json(见 packages/shared/src/i18n/locales)。
5.1 语言注册表(唯一事实来源)
所有语言元数据集中在src/i18n/registry.ts。新增语言只需三步:
- 以
en.json为模板创建src/i18n/locales/{code}.json(包含全部键); - 在
registry.ts中导入 messages 与date-fnslocale; - 在
LOCALE_REGISTRY中添加一条记录(nativeName + messages + dateLocale)。
仅此而已。SUPPORTED_LANGUAGE_CODES、LANGUAGES、i18n resources、getDateLocale()全部自动派生,其他文件无需改动。从 packages/shared/src/i18n/registry.ts 可以看到当前 7 条记录的写法,以及LanguageCode = keyof typeof LOCALE_REGISTRY的类型推导。
5.2 键命名约定
键使用扁平点号记法并带类别前缀,完整前缀表如下:
| 前缀 | 作用域 | 示例 |
|---|---|---|
common.* | 共享标签(Cancel、Save、Close、Edit、Loading…) | common.cancel |
menu.* | 应用菜单项(File、Edit、View、Window) | menu.toggleSidebar |
sidebar.* | 左侧边栏导航项 | sidebar.allSessions |
sidebarMenu.* | 侧边栏右键菜单动作 | sidebarMenu.addSource |
sessionMenu.* | 会话右键菜单动作 | sessionMenu.archive |
settings.* | 设置页——按页面 ID 嵌套 | settings.ai.connections |
chat.* | 聊天输入、会话查看器、内联 UI | chat.attachFiles |
toast.* | Toast/通知消息 | toast.failedToShare |
errors.* | 错误页 | errors.sessionNotFound |
onboarding.* | 引导流程——按步骤嵌套 | onboarding.welcome.title |
dialog.* | 模态对话框 | dialog.reset.title |
apiSetup.* | API 连接设置 | apiSetup.modelTier.best |
workspace.* | 工作区创建/管理 | workspace.createNew |
sourceInfo.* | Source 详情页 | sourceInfo.connection |
skillInfo.* | Skill 详情页 | skillInfo.metadata |
automations.* | 自动化列表/详情/菜单 | automations.runTest |
sourcesList.* | Sources 列表面板 | sourcesList.noSourcesConfigured |
skillsList.* | Skills 列表面板 | skillsList.addSkill |
editPopover.* | EditPopover 标签/占位符 | editPopover.label.addSource |
status.* | 会话状态名(按状态 ID) | status.needs-review |
mode.* | 权限模式名(按模式 ID) | mode.safe |
hints.* | 空状态工作流建议 | hints.summarizeGmail |
table.* | 数据表列头 | table.access |
time.* | 相对时间字符串 | time.minutesAgo_other |
session.* | 会话列表 UI | session.noSessionsYet |
shortcuts.* | 键盘快捷键描述 | shortcuts.sendMessage |
sendToWorkspace.* | 发送到工作区对话框 | sendToWorkspace.title |
webui.* | WebUI 专属字符串 | webui.connectionFailed |
auth.* | 认证横幅/提示 | auth.connectionRequired |
browser.* | 浏览器空状态 | browser.readyTitle |
5.3 八条硬性规则
- 绝不在模块级调用
i18n.t()——只存储labelKey字符串,在组件/函数内解析; - 使用i18next 复数形式(
_one/_other),不要手写count === 1 ?逻辑; - 品牌名保持英文:Craft、Craft Agents、Agents、Workspace、Claude、Anthropic、OpenAI、MCP、API、SDK;
- UI 需要省略号时在翻译值中包含
...,不要在 JSX 中追加; - 含 HTML 标签的翻译使用
<Trans>组件(如<strong>); - 比较支持的语言代码时使用
i18n.resolvedLanguage(而非i18n.language); - 键必须存在于每个语言文件中(不仅是
en.json——lint:i18n:parity会在全量集合上强制执行),并保持字母序; - 关注受限 UI 元素的翻译长度。翻译可能比英文长 20%-100%+。对按钮、徽章、标签页标签、下拉项保持简洁,必要时用更短的同义词。高风险区域:权限模式徽章(最长 3-5 字符)、设置标签页标签(理想 ≤10 字符)、按钮标签(避免超过英文 2 倍长度)、菜单项(灵活,但避免 3 倍以上膨胀)。
5.4 三层校验(CI 门禁)
三道检查把关 i18n 正确性,全部接入 pre-commit(lint:i18n:staged)与validate:ci:
| 脚本 | 捕获问题 |
|---|---|
lint:i18n:sorted | 语言键未按字母序 |
lint:i18n:parity | 非英文语言缺失en.json中的键,或反之 |
lint:i18n:coverage | t('...')调用点引用了en.json中不存在的键 |
这些脚本定义在仓库根 package.json 中:validate:ci依次运行validate:dev、lint:i18n:parity、lint:i18n:sorted、lint:i18n:coverage;lint:i18n:parity与lint:i18n:coverage对应scripts/check-i18n-parity.ts与scripts/check-i18n-coverage.ts,lint:i18n:sorted对应scripts/sort-locales.ts --check。
CLAUDE.md 特别强调:仅靠parity不够——它检测不出所有语言对称丢失的情况(一次合并同时从所有语言文件删掉同样的 50 个键,parity 会通过但 UI 已损坏)。coverage通过验证每个字面量t(...)/i18n.t(...)/<Trans i18nKey>引用都能在en.json中解析来补上这个缺口。动态键(t(\status.${id}`)`)会被跳过——它们通过 i18next 的运行时 missing-key 警告暴露。
解决语言合并冲突时,直接运行bun run validate:ci并信任结果——三道全过就不需要手动审计键。
5.5 新增翻译字符串的流程
- 向
en.json添加键 + 英文值(字母序); - 向其他每个
src/i18n/locales/*.json添加键 + 译文(运行bun run lint:i18n:parity确认无遗漏); - 在组件中使用
t("your.key")(若缺失则添加useTranslation()hook); - 非 React 代码用
i18n.t("your.key")——但只在函数内,绝不在模块级。
5.6 新增语言流程
- 用
en.json的全部键创建src/i18n/locales/{code}.json; - 在
src/i18n/registry.ts的LOCALE_REGISTRY中添加条目(messages + date-fns locale + nativeName); - 运行测试——registry 测试会捕获任何缺失的接线。
5.7 跨进程语言持久化
主进程 i18n 实例没有检测插件(Node 中无localStorage),否则每次重启都会回落到fallbackLng: 'en'。为了跨启动保持主进程与渲染进程同步:
- 渲染进程使用
i18next-browser-languagedetector→localStorage(i18nextLng),重启后存活; - 主进程启动时从
~/.craft-agent/preferences.json的preferences.uiLanguage水合,仅由apps/electron/src/main/index.ts的i18n:changeLanguageIPC handler 维护; - 渲染进程 → 主进程同步发生在每次 Appearance 变更时 + 渲染进程启动时一次(新装应用立即学到持久化语言);
- IPC handler 用
SUPPORTED_LANGUAGE_CODES校验传入代码,setPersistedUiLanguage()在值未变时 no-op——启动推送不会搅动文件或配置 watcher。
uiLanguage不能通过update_user_preferences用户编辑,Appearance 下拉框是唯一写入方。
会话标题语言从同一持久化uiLanguage经resolveTitleLanguageName()(config/preferences.ts)解析,而非i18n.resolvedLanguage。原因是主进程 i18n 值在启动时异步水合,早期标题生成时可能仍读到'en'回退值,导致非英文聊天被迫生成英文标题(#885)。未持久化语言时该辅助函数返回undefined,标题提示会自动检测对话语言而非默认英文。它用于SessionManager的两个标题位置(generateTitle、refreshTitle)。
六、API 源的令牌刷新
API 源可以通过两条路径自动刷新令牌:
- OAuth——Google、Slack、Microsoft 或通用 OAuth(
authType: 'oauth'); - Renew 端点——带
api.renewEndpoint配置的自定义 bearer-token API(见 packages/shared/src/sources/types.ts 中renewEndpoint相关类型)。
对 renew-endpoint 源:当前访问令牌被发送到配置的端点,新令牌从响应中提取,不需要单独的 refresh token(MVP 范围)。
关键集成点:
isRefreshableSource()(types.ts)——"该源能否自动刷新"的唯一守卫;SourceCredentialManager.refreshApiRenew()——调用 renew 端点,实际实现在 packages/shared/src/sources/credential-manager.ts:按defaultHeaders < renewEndpoint.headers < Authorization的优先级构建请求头,除非在renewEndpoint.headers中显式覆盖,否则加入 Authorization;TokenRefreshManager——即使没有refreshToken也把 renew-endpoint 源视为可刷新(见 packages/shared/src/sources/token-refresh-manager.ts);server-builder.ts——对 renew-endpoint 源传入令牌 getter(而非静态凭证)(packages/shared/src/sources/server-builder.ts)。
配套测试覆盖了完整的刷新行为,包括renewEndpoint的tokenField、expiresInField、fallbackTtlSecs等配置项:见 packages/shared/src/sources/tests/credential-manager-renew.test.ts 与 packages/shared/src/sources/tests/token-refresh-manager.test.ts。
七、queryLlm后端契约
AgentBackend.queryLlm(request: LLMQueryRequest)是包内所有 Agent 后端都必须遵守的接口契约(请求/结果类型定义见 packages/shared/src/agent/llm-tool.ts,LLMQueryRequest含prompt、systemPrompt、model、maxTokens、temperature、outputSchema字段)。契约分级如下:
MUST(必须)
- 遵循
request.model(仅当模型不可解析/不支持时有后端特定回退;且始终在LLMQueryResult.model中报告实际生效的模型); - 遵循
request.systemPrompt。
SHOULD(应该)
- 遵循
request.outputSchema(至少通过提示注入实现——agent/llm-tool.ts的buildCallLlmRequest已在后端之前处理此事)。
MAY(可以)
- 若底层 SDK 支持,可遵循
request.maxTokens与request.temperature。
MUST NOT(禁止)
- 返回与真实使用模型不符的伪造
LLMQueryResult.model——下游 UI 把它当作权威信息。
此外,主进程与任何子进程后端(目前是 Pi,将来可能更多)之间的IPC 信封必须携带完整的LLMQueryRequest,而不是子集。发明更窄信封的后端注定会随时间漂移(见 #596)。这个往返不变式由 packages/shared/src/agent/tests/pi-query-llm.test.ts 守护。
八、事实来源与导出边界
包的导出以两处为唯一事实来源(source of truth):
- 包导出:
packages/shared/src/index.ts与package.json的 subpath export 条目; - Agent 导出:
packages/shared/src/agent/index.ts。
新增模块或字段时,务必先检查这两处导出是否齐全——例如updateLlmConnection从硬编码允许清单重建连接,任何新增持久化字段都必须登记其中,否则下次保存即被丢弃(#838)。
九、给贡献者的实践清单
综合全文,面向@craft-agent/shared的日常开发可归结为以下清单:
- 动 Agent 相关代码前,先确认后端(
claude-agent.ts/pi-agent.ts)与queryLlm契约的边界——尤其是LLMQueryResult.model必须真实; - 动标签相关代码前,确认过滤逻辑走
matchesLabelFilter,任务标签铸币走SessionManager.applyTaskLabel,绝不手写标签匹配、绝不假定字面 id; - 动权限相关代码前,确认模式仍是
safe | ask | allow-all三档固定枚举,探索模式的硬编码写工具(Write/Edit/MultiEdit/NotebookEdit)不可通过配置放开(见 packages/shared/src/agent/mode-types.ts 的SAFE_MODE_CONFIG); - 动 i18n时,遵循"先改
en.json+ 全量语言文件 → 函数内解析 → 跑bun run validate:ci"的节奏; - 动凭证时,一律走
src/credentials/通路,禁止即兴秘密存储; - 动自定义端点/模型能力时,保持逐模型覆盖语义端到端传递,并确认 IPC 信封字段边界(
update_runtime_config仅限五字段); - 提交前运行
cd packages/shared && bun run tsc --noEmit与bun test确认类型与行为无损。
以上内容均以 packages/shared/CLAUDE.md 为骨架,并结合仓库源码、配置与测试进行了交叉印证;如需深入某一块,可直接跳转到文中给出的对应源文件继续阅读。
【免费下载链接】craft-agents-oss项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考