
人工智能AI AgentAgent 框架DeepSeek【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址https://gitcode.com/gh_mirrors/de/deepseek-harness点击查看免费下载本篇技术指南围绕 DeepSeek Harness 的一项已落地架构决策展开应用的命令行flag 家族、--help文本、用法错误由应用自己持有启动器只解析属于自己的参数并把其余部分原样交接给被引导的组合树。它解决的是 profile 机制落地后「组合可以安装、命令行却不能」的缺口——树外应用如 turtle-ui能贡献配置行却无处接受--resume session这样的 flag。读完本文你将掌握deepseek-ai/dsh-cmdline包的交接机制provideCmdline/parseCmdline、启动器与应用的参数切分规则、Web 与 headless 两个内置应用如何以普通插件身份获得 flag以及这套设计背后的 Loader 顺序契约与边界行为。背景与问题组合可安装命令行却不能在 profile 机制落地之后组合composition已经可以按 profile 安装但命令行并不随组合走。文档指出当时的困境见 2026-08-06-app-owned-command-line.mdapps/cli仍然声明着 Web flag 家族--host、--port、--dev、--workspace-root、--trusted-host和一次性任务位置参数启动器还要为硬编码的行 idwebserver、api-gateway、connection、web-runtime派生 patch树外应用能贡献配置行却无处接受一个 flagdsh --profile tui --resume session没有地方可供解析dsh --profile web --help打印的是启动器的 help而不是 web 应用的 help。也就是说命令行被「焊死」在了启动器里与应用组合脱节。任何想通过插件增加 flag 的应用都必须改启动器这违背了项目「Everything is a Plugin」的定位。核心决策启动器只解析自己的其余原样交接本次架构决策确立了一条清晰的分界线关联文档启动器只解析属于自己的部分--profile、--patch、配置 dump--dump-config/--dump-default-config把自己 flag 之后的一切原样交给引导起来的配置树切分按位置进行启动器不认识的第一个 token 就是应用参数的起点。位置切分的 commander 实现位置切分依赖 commander 的三个选项组合源码见 apps/cli/src/args.tsconst program: Command new Command() program .name(dsh) .version(version, -V, --version, output the version number) .exitOverride() .helpOption(false) // 应用的 -h 不再被启动器吞掉 .allowUnknownOption() // 未知 flag 不报错留给应用 .passThroughOptions() // 位置参数之后的选项原样透传 .enablePositionalOptions() .argument([args...], arguments for the booted profile\s app (see: dsh --profile name --help)) .option(--profile name, the profile under $DSH_HOME/profiles to boot) .option(--patch path, extra patch-list overlay applied after the profile layer (repeatable), collect) .option(--dump-config, print the composed profile tree and exit) .option(--dump-default-config, print the profile tree without its user layer or --patch overlays and exit)三个开关的分工开关作用passThroughOptions从第一个未被识别的位置参数起后续 token 一律原样透传不再被当作本命令的选项解析allowUnknownOption不认识的 flag 不触发错误而是落入位置参数helpOption(false)关掉启动器默认的-h/--help让应用持有自己的 help由此得到可观察行为args.ts 的模块注释与 HELP_EXAMPLESdsh --profile tui --resume abc→ 引导 tui profile并把--resume abc作为内部参数交接给应用dsh --profile web -h→ 打印web 应用的 help而不是启动器的裸的dsh -h没有可交付的应用仍然打印启动器自己的 help源码中由 action 内显式判断实现if (args.some(a a -h || a --help)) program.help()见 args.ts。两个硬编码子命令启动器还保留两个位置性入口args.tsdsh web--profile web的硬编码别名--patch/ dump 系列选项可用但父级选项会被rejectParentOptions拒绝dsh plugin --profile name pnpm args...管理 profile 的插件依赖把剩余参数原样转发给 profile 目录下的 pnpm。这是「应用第一个参数恰好等于web或plugin时会选择对应子命令」这一边界规则的来源。交接包deepseek-ai/dsh-cmdline新的包 packages/boot/cmdline 持有这次交接。其全部公开接口与实现都在 src/index.ts。三个 launcher 事实CmdlineArgs、AppExit、AppReady启动器在任何条目挂载之前通过provideCmdline(ctx, host)向宿主上下文提供三个事实index.tsexport function provideCmdline(ctx: Context, host: CmdlineHost): void { const snapshot: readonly string[] Object.freeze([...host.args]) ctx.provide(cmdlineArgs, { get: () snapshot }) ctx.provide(appExit, host.exit) if (host.ready ! undefined) ctx.provide(appReady, host.ready) }接口定义CmdlineArgs——内部参数的不可变快照全部接口只有get(): readonly string[]index.ts。快照在提供时用Object.freeze([...host.args])冻结调用方之后对原始数组的修改测试用例里args.push(--tampered)不会进入快照AppExit——有边界的进程退出请求(code: number) void启动器把它接到自己的 shutdown 控制器上AppReady——成功启动信号onReady(listener)只有启动真正提交后才会回调 listener失败或被外部终止的启动永远不会调用它。CmdlineHost把三者打包其中args与exit必填、ready可选嵌入宿主没有命令行时提供空参数列表挂载 stdio 应用的宿主还要提供就绪信号。parseCmdline用应用的 commander program 解析快照任何普通应用插件都可以注入cmdlineArgs用自己的 commander program调用parseCmdline(ctx, program)index.tsexport function parseCmdline(ctx: Context, program: Command): void { const args ctx.get(cmdlineArgs) const exit ctx.get(appExit) if (args undefined || exit undefined) { throw new Error(${program.name()}: the launcher must provide ctx.cmdlineArgs and ctx.appExit before the tree mounts) } if (!hasAction(program)) { throw new Error(${program.name()}: no command in the program declares an action; ...) } configureExitAndOutput(program) try { program.parse(args.get(), { from: user }) } catch (error) { if (!isCommanderError(error)) throw error exit(error.exitCode) } }几个实现细节值得注意通过全局服务存储读取而不是属性代理ctx.get(cmdlineArgs)因为appExit是可选的宿主值而插件只需要注入cmdlineArgs树级 action 前置校验hasAction由于Command类型无法表达「必须有 action」这一前置条件代码按结构读取_actionHandler并递归检查子命令index.ts。没有这个 guard一个忘记 action 的 program 会解析成功、什么都不发布只表现为依赖行 pending 在缺失服务上递归配置退出与输出configureExitAndOutputcommander 只在注册时把exitOverride和输出配置复制给子命令因此只配根命令会让已注册子命令的拒绝绕过ctx.appExit直接process.exit。实现递归地为整个命令树配置exitOverride()configureOutput写入internals.stdout/stderr便于测试替换见 index.ts结构化识别 commander 控制流错误isCommanderError不用instanceof CommanderError——树外插件会带来自己的一份 commander 副本类身份不同identity 检查会把已打印的 help 重新抛成致命加载失败。改为按结构识别typeof candidate.code string candidate.code.startsWith(commander.) typeof candidate.exitCode numberindex.ts。parseCmdline的语义契约index.tshelp、version、被语法或 action 拒绝的参数对进程是终局的commander 已写出文本helper 请求ctx.appExitaction 在 help、version、语法拒绝时从不运行action 必须在发布前拒绝program.error(...)因为其之前的语句已经执行过成功解析时commander 运行 program 自己的同步 action应用代码在那里发布自己的服务并用program.error(...)拒绝非法调用。进程流internals与exitOnStdinEndinternalsindex.ts把 stdin/stdout/stderr 暴露为可替换对象测试可以注入内存实现。exitOnStdinEnd(ctx, label)index.ts让stdin EOF 请求有边界的成功关闭只在AppReady提交后执行退出因此启动被拒绝时进程结果仍是失败调用方只在 command action 接受调用之后才调用它所以 help 和用法失败不会启动传输生命周期。disposal 会移除 EOF 与就绪监听器。启动器侧接线runProfile启动器在 apps/cli/src/profile-boot.ts 的runProfile中在任何配置树条目挂载之前调用provideCmdlineprofile-boot.tsprovideCmdline(hostCtx, { args: options.args, exit: code void shutdown.shutdown(code), ready: appReady.service, })要点runProfile不再知道任何 flag 目标行 id——apps/cli/src/web.ts已被删除runProfile的模块注释明确「App flags are not the launchers business」profile-boot.ts信号处理保持「启动器不持有应用语义」SIGTERM是监管者的普通停止请求在所有 surface 上以0退出SIGINT是用户中断报告130profile-boot.ts。从源码结构看SIGTERM退出 0 正是 2026-08-11 那轮裁剪的产物——此前一次性 surface 的 143 依赖命名headless-runner行每次启动都监视用户 patch 层profile patch 路径与 home patch 路径一次性 surface 通过有边界关闭退出这会在循环排空前先处置 watcherappReady.commit()只在 boot 与宿主 setup 都成功、且 fiber 处于 ACTIVE、loader 存在时执行profile-boot.ts。dump 模式的约束--dump-config/--dump-default-config是启动器的「只读检视」模式args.ts二者互斥从不运行应用命令行提供方——它在任何应用参数被解析之前打印组合因此无法展示那些 flag 会决定什么打印一棵与同一次调用 boot 结果不同的树反而会误导因此拒绝携带应用参数的调用config dumps take no app arguments--dump-default-config打印 bundle 层、不接受--patch。应用侧实例一dsh-web-app持有 Web flag 家族Web 组合包把整个 Web flag 家族搬进了自己的 bundle--host、--port、--no-open、--trusted-host。命令行提供方插件packages/bundle/web-app/src/startup.ts 是名为web-startup的普通 Cordis 插件——注入[cmdlineArgs]没有任何启动器标记或特殊行种类export const name web-startup export const inject [cmdlineArgs] export const WEB_STARTUP_SERVICE webStartup其 commander programstartup.tsnew Command() .name(dsh --profile web) .description(Serve the DeepSeek Harness browser UI.) .helpOption(-h, --help, show this help) .option(--host host, bind host) .option(--no-open, do not open the Web UI in the default browser) .option(--port port, listen port; pass 0 to let the OS pick a free one) .option(--trusted-host authority..., extra authority the /api browser-trust fence accepts (host or host:port; repeatable))action 在解析成功后做应用级校验再发布服务startup.ts--host 0.0.0.0被显式拒绝安全原因会把远程代码执行暴露到网络提示改用127.0.0.1--port必须匹配/^\d$/发布webStartup服务{ openBrowser, host?, port?, trustedHosts }。这印证了原文档「app 的 flag、help 文本和用法错误与它们所配置的行放在一起」——校验逻辑就在组合包内部与webserver等消费行同处一包。惰性配置表达式flag 胜过旁置值dsh-web-app的 patch 层 cordis.patch.yml 中由提供方配置的行注入该服务并在惰性配置表达式里直接读取它- id: web-startup name: deepseek-ai/dsh-web-app/startup - id: webserver name: deepseek-ai/dsh-host-webserver inject: [webStartup] config: host: !!js ctx.webStartup.host ?? 127.0.0.1 port: !!js ctx.webStartup.port ?? 3080 compression: gzip compressionLevel: 1 compressionThresholdBytes: 1024 - id: web-runtime name: deepseek-ai/dsh-web-app inject: [webStartup] config: openBrowser: !!js ctx.webStartup.openBrowser printUrl: true surfaceContext: true trustedHosts: !!js ctx.webStartup.trustedHosts机制含义port: !!js ctx.webStartup.port ?? 3080中flag 决定的值胜过写在表达式旁边的默认值 3080表达式先取服务里的port来自--port缺省时回退 3080没有任何东西被写回任何一行——行的config里始终是表达式解析结果只存在于行激活时的上下文connection行再注入webRuntime从运行时绑定推导 LAN 字面量并拼接--trusted-host额外授权cordis.patch.yml。dsh web与dsh --profile web完全一致文档强调Web 组合包的运行时插件也持有 harness 源码提示词段因此dsh web与dsh --profile web无需 Web 专用启动器设置即可按完全相同的方式引导。从 cordis.patch.yml 可见system-prompt行的 persona 正是由该 bundle 的 patch 层配置的。应用侧实例二dsh-headless持有任务位置参数一次性任务one-shot应用同样以普通插件方式获得命令行packages/bundle/headless/src/startup.tsexport const name headless-startup export const inject [cmdlineArgs] export const HEADLESS_STARTUP_SERVICE headlessStartup function headlessCommand(): Command { return new Command() .name(dsh --profile headless) .description(Answer one task, stream reasoning to stderr, print the final assistant message, and exit.) .helpOption(-h, --help, show this help) .argument([task...], the task text; multiple words are joined by spaces) }action 把多个词用空格连接成任务文本缺失或空白任务按用法错误拒绝error: a task is required, for example: dsh --profile headless run the tests然后发布headlessStartup服务供一次性 runner 行惰性读取。runner 像任何应用一样经ctx.appExit退出其输出流是包内部的internals测试接缝ctx.headlessIo已被删除2026-08-11 裁剪。为什么由 Loader 持有顺序文档用三条框架事实解释机制成立的三个前提profile 的各行位于根 include 的patches选项内部。Include 声明了EntryGroup.key树载体标记与 Group 相同因此 Loader 让它的配置——条目与 patch 列表包括 Include 自己的path——保持字面值而不是在 Include 上下文中递归求值嵌套的!!js节点每个表达式都在其目标行的 fiber 中解析。这正是「Include 会保留嵌套的行表达式直到目标行到达插值时点」的含义Cordis 只在所有声明的注入都已激活后才激活 fiber。每次激活前一刻Cordis 基于 fiber 自身上下文运行internal/configwaterfallCordis 快照注入服务之后Loader 的监听器再插值原始配置。因此--help会让提供方服务保持缺失依赖行永不激活提供方替换与 HMR 必须保持相同契约。fiber 重新激活时会重跑 waterfallHMR 把原始配置带给替换 fiber待处理行可以接受选项变更而不会针对缺失服务提前求值。这就是「live patch 重载会针对仍然在线的服务再次插值已服务中的端口不会被悄悄重置」的机制来源。依赖顺序因此留在 Cordis 激活与 Loader 插值流程中各行保留自己的inject和配置Loader 只挂载一次组合启动器只提供 argv 与进程生命周期服务。测试验证真实 Loader 树上的契约packages/boot/cmdline/tests/cmdline.spec.ts 在真实的 Loader 树上验证整套契约两行组合demo-startup提供方 reader消费行配置携带!!js表达式测试用例断言--port 8080reader 行以{ port: 8080 }启动无退出请求无 flagreader 行回退到表达式里的 3080--help打印Usage: demo不启动任何行请求 exit 0action 拒绝--port abc打印错误不启动行请求 exit 1不可变快照provideCmdline后调用方args.push(...)不影响cmdlineArgs.get()多 parser 共享两个 parser 读同一快照Object.isFrozen(get()) true子命令拒绝已注册子命令的错误走 launcher 退出请求exit 1而非直接process.exit无 action 程序加载期抛「no command in the program declares an action」缺少 launcher 值抛「the launcher must provide ctx.cmdlineArgs and ctx.appExit」非 commander 异常action 抛普通 Error 或字符串时重新抛出不吞掉exitOnStdinEnd的测试还覆盖EOF 请求有边界退出、绑定已结束的 stdin、disposal 后移除监听器、已结束流在排队 EOF 处理器运行前取消等时序cmdline.spec.ts。后续演进2026-08-11 裁剪到既有接口紧跟的架构笔记 2026-08-11-cmdline-seam-trim.md 把三处过宽的接缝收窄到已有接口可作为理解当前仓库状态的补充删除条件 dev 行重载链不再条件化——dsh-web-app无条件挂载client-hmr行--dev、web 运行时的mode配置、mode 分叉的提示词契约、DSH_WEB_MODEbash 变量全部删除。没有重建 watcherpnpm run dev:web重写客户端 bundle 时该行只是轮询未变化文件并保持空闲代价是一个 stat 轮询间隔加一条 SSE 路由需要关闭/plugins/events的部署在 patch 层禁用该行即可树载体配置Include 声明既有的EntryGroup.key标记替代EntryConfigResolver协议符号Include 自己的path失去!!js支持从未有配置使用它启动器不再认识任何应用行SIGTERM 在所有 surface 上以 0 退出SIGINT 保持 130ctx.headlessIo删除一次性 runner 走ctx.appExit。从源码结构看packages/boot/cmdline/src/index.ts中已没有Entry.enableRuntime/enableRowinvariant.ts也以「无运行时不变式」注册空安装器invariant.ts——因为cmdlineArgs是任何数量的普通插件都可读取的不可变启动器事实缺失依赖已由 Loader 结算报告。边界规则与使用注意事项原文档「后果」一节给出了完整的调用契约整理如下flag 与行同置应用的 flag、help 文本和用法错误与它们所配置的行放在一起给已安装插件加一个 flag不需要改动启动器turtle-ui 以同样方式获得--resume session/--session id是这套设计的真实验证启动器不识别任何应用行telemetry 行仍是它唯一的组合探测用于环境开关SIGTERM 在所有 surface 上以 0 退出每次启动都监视用户 patch 层一次性 runner 经ctx.appExit退出--help副作用让所有依赖提供方服务的行保持 pending 并请求有边界退出无关行可能在拆除前并发激活应用自有服务没有静态声明的提供方交付了消费行却缺少对应提供方的组合包会在结算时失败报出指向该服务的 pending 条目而不是在加载时失败patch 整体替换 config 的代价用户 patch 若整体替换某行的config会连同其中的表达式一起丢掉该行上 flag 的优先级也随之消失参数顺序启动器 flag 必须写在应用参数之前应用第一个参数恰好等于web或plugin时会选择对应子命令-V/--version在该边界之前仍归启动器持有启动器解析器会消耗掉一个--因此给应用传字面量--需要写成-- --dump 模式--dump-config从不运行应用命令行提供方打印组合时尚未解析任何应用参数且拒绝携带应用参数的调用。曾考虑的替代方案文档记录了六条被否决的路线理解它们有助于把握设计取舍把解析出的取值写进每一行逐行一次配置更新 交还给启动器的 patch 层能工作但 patch 要在应用与启动器之间来回传递、同一件事有两套机制正确性还依赖 Loader 重启内部细节。被否决改为「供各行读取的服务」清空行的inject来放行孤立测试可行在真实 web 树失败——清空inject恰恰丢失了插件的静态注入且失败是静默的直到插件真的读它声明过的服务启动器管理两趟挂载可让提供方先激活但会重复组合、把顺序变成启动器职责还掩盖了 Loader 缺陷嵌套表达式在 include 上下文而非目标行注入上下文求值启动器在 boot 前运行每个组合包的命令函数不经过 Cordis严格早于「先 boot 再 help」但会让应用启动成为配置树外的第二套插件协议注入cmdlineArgs的普通提供方只保留一套协议且仍可 dump、可 patch启动器强制指定命令行所有者拒绝零/多个读取方可裁决-h重叠但get()是不可变读取普通组合可能需要多个应用自有服务——插件共享快照通过普通组合持有各自解析器交互instanceof CommanderError树外插件自带 commander 副本、类身份不同已打印的 help 会被重抛成致命加载失败——改为按结构识别code.startsWith(commander.) 数字exitCode。总结ctx.cmdlineArgs交接机制让 DeepSeek Harness 的启动器彻底退出了「认识应用 flag」的职责启动器解析--profile/--patch/ dump 系列后把剩余参数以冻结快照交给配置树任何普通插件通过deepseek-ai/dsh-cmdline的provideCmdline/parseCmdline自持命令行、发布自有服务消费行再用惰性!!js表达式读取从而让「flag 胜过旁置值」且「不写回任何行」。源码证据集中在 packages/boot/cmdline/src/index.ts、apps/cli/src/args.ts、apps/cli/src/profile-boot.ts 与两个应用组合包的 startup.ts / cordis.patch.yml测试契约见 packages/boot/cmdline/tests/cmdline.spec.ts。对于想为已安装 profile 增加命令行入口的插件作者实践路径是注入cmdlineArgs→ 构建 commander program → action 中校验并ctx.provide自己的服务 → 消费行注入该服务并在!!js表达式里读取——全程不需要触碰启动器。赞分享人工智能AI AgentAgent 框架DeepSeek【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址https://gitcode.com/gh_mirrors/de/deepseek-harness点击查看免费下载相关推荐DeepSeek Harness 命令行所有权架构应用通过 ctx.cmdlineArgs 持有自己的 flag 家族DeepSeek Harness 命令行所有权架构应用通过 ctx.cmdlineArgs 持有自己的 flag 家族 dsh 启动器只解析属于自己的参数人工智能AI AgentAgent 框架DeepSeekdeepseek-harness 命令行解析精简parseCmdline 如何把 commander 的 action 席位交给应用自己deepseek harness 命令行解析精简parseCmdline 如何把 commander 的 action 席位交给应用自己 本篇技术指南聚焦 d人工智能AI AgentAgent 框架DeepSeekChatGPT桌面应用命令行帮助文档所有命令的详细说明ChatGPT桌面应用命令行帮助文档所有命令的详细说明 本文档详细介绍ChatGPT桌面应用Mac、Windows 和 Linux的所有命令行指令帮助用桌面应用AI 应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考