十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Craft Agents 核心业务包 `@craft-agent/shared` 开发指南:架构、硬性规则与工程实践

Craft Agents 核心业务包 `@craft-agent/shared` 开发指南:架构、硬性规则与工程实践 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 toolsSources、credentials、sessions 与 config外部数据源、加密凭证、会话持久化、配置权限模式与校验Permission modes and validation关键目录速览目录职责代表性文件src/agent/Agent 后端实现与工具claude-agent.ts、pi-agent.ts、base-agent.ts、llm-tool.tssrc/sources/外部数据源存储/类型/服务types.ts、server-builder.ts、credential-manager.tssrc/sessions/会话持久化与索引storage.ts、index.tssrc/projects/工作区作用域项目配置 资产会话通过projectId绑定index.ts、types.tssrc/config/配置/偏好/主题/监听器index.ts、llm-connections.ts、models-pi.tssrc/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 testtest: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以及 SHIFTTAB 循环顺序PERMISSION_MODE_ORDER [safe, ask, allow-all]。parsePermissionMode()同时接受规范值与历史别名ask-to-edit等以兼容旧数据。Source 类型固定为三种mcp、api、local。见 packages/shared/src/sources/types.tsexport type SourceType mcp | api | local。该文件同时定义了各类型的认证方式MCP 源为oauth | bearer | noneAPI 源为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无 valueTypeN为根标签所有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 唯一的标签过滤谓词matchesLabelFilterpackages/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: pipiAuthProvider。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 使用稳定的中继重定向 URIhttps://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 →queuepi/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 网络拦截器目前仅限 Piunified-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字段可选/失败软处理绝不阻塞登录并持久化到LlmConnectionoauthAccountUuid/Email、oauthOrganizationUuid/Name、oauthProfileVerifiedAt通过SETUP_LLM_CONNECTIONpayloadoauthIdentity透传——而不是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-51M 上下文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.cancelmenu.*应用菜单项File、Edit、View、Windowmenu.toggleSidebarsidebar.*左侧边栏导航项sidebar.allSessionssidebarMenu.*侧边栏右键菜单动作sidebarMenu.addSourcesessionMenu.*会话右键菜单动作sessionMenu.archivesettings.*设置页——按页面 ID 嵌套settings.ai.connectionschat.*聊天输入、会话查看器、内联 UIchat.attachFilestoast.*Toast/通知消息toast.failedToShareerrors.*错误页errors.sessionNotFoundonboarding.*引导流程——按步骤嵌套onboarding.welcome.titledialog.*模态对话框dialog.reset.titleapiSetup.*API 连接设置apiSetup.modelTier.bestworkspace.*工作区创建/管理workspace.createNewsourceInfo.*Source 详情页sourceInfo.connectionskillInfo.*Skill 详情页skillInfo.metadataautomations.*自动化列表/详情/菜单automations.runTestsourcesList.*Sources 列表面板sourcesList.noSourcesConfiguredskillsList.*Skills 列表面板skillsList.addSkilleditPopover.*EditPopover 标签/占位符editPopover.label.addSourcestatus.*会话状态名按状态 IDstatus.needs-reviewmode.*权限模式名按模式 IDmode.safehints.*空状态工作流建议hints.summarizeGmailtable.*数据表列头table.accesstime.*相对时间字符串time.minutesAgo_othersession.*会话列表 UIsession.noSessionsYetshortcuts.*键盘快捷键描述shortcuts.sendMessagesendToWorkspace.*发送到工作区对话框sendToWorkspace.titlewebui.*WebUI 专属字符串webui.connectionFailedauth.*认证横幅/提示auth.connectionRequiredbrowser.*浏览器空状态browser.readyTitle5.3 八条硬性规则绝不在模块级调用i18n.t()——只存储labelKey字符串在组件/函数内解析使用i18next 复数形式_one/_other不要手写count 1 ?逻辑品牌名保持英文Craft、Craft Agents、Agents、Workspace、Claude、Anthropic、OpenAI、MCP、API、SDKUI 需要省略号时在翻译值中包含...不要在 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-commitlint:i18n:staged与validate:ci脚本捕获问题lint:i18n:sorted语言键未按字母序lint:i18n:parity非英文语言缺失en.json中的键或反之lint:i18n:coveraget(...)调用点引用了en.json中不存在的键这些脚本定义在仓库根 package.json 中validate:ci依次运行validate:dev、lint:i18n:parity、lint:i18n:sorted、lint:i18n:coveragelint:i18n:parity与lint:i18n:coverage对应scripts/check-i18n-parity.ts与scripts/check-i18n-coverage.tslint: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→localStoragei18nextLng重启后存活主进程启动时从~/.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 或通用 OAuthauthType: oauthRenew 端点——带api.renewEndpoint配置的自定义 bearer-token API见 packages/shared/src/sources/types.ts 中renewEndpoint相关类型。对 renew-endpoint 源当前访问令牌被发送到配置的端点新令牌从响应中提取不需要单独的 refresh tokenMVP 范围。关键集成点isRefreshableSource()types.ts——该源能否自动刷新的唯一守卫SourceCredentialManager.refreshApiRenew()——调用 renew 端点实际实现在 packages/shared/src/sources/credential-manager.ts按defaultHeaders renewEndpoint.headers Authorization的优先级构建请求头除非在renewEndpoint.headers中显式覆盖否则加入 AuthorizationTokenRefreshManager——即使没有refreshToken也把 renew-endpoint 源视为可刷新见 packages/shared/src/sources/token-refresh-manager.tsserver-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.tsLLMQueryRequest含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),仅供参考
返回列表