跳到主要内容

第 19 章:CLI、配置与控制协议

19.1 CLI 不是单纯的管理员客户端

cmd/multica/main.go 把同一个 multica 二进制定义成三种角色:

  1. 人的控制台:登录、切换 Workspace、管理 Issue、Agent、Squad、Autopilot、Skill 和 Runtime;
  2. Daemon 的进程管理器:启动、停止、重启、检查健康状态、查看日志和磁盘占用;
  3. Agent 的任务内工具箱:读取 Issue/Chat、发表评论、下载附件、按需 Checkout 仓库、记录 Squad 决策。

第三种角色最关键。Provider CLI 不需要理解 Multica 的数据库或 HTTP 路由,只要调用已经放进任务环境的 multica,就能以当前 Agent、当前 Task 和当前 Workspace 的身份回写控制平面。

因此 CLI 同时跨过两条边界:

  • 用户 Shell → Server API;
  • Agent 子进程 → Server API / 本机 Daemon。

这也是为什么它的配置回退、身份注入和错误分类比普通 CRUD CLI 更严格。

19.2 Cobra 命令树

根命令使用 Cobra,按用途分为三组。

分组主要命令责任
Coreissue、project、label、property、agent、autopilot、workspace、repo、skill、squad、chat操作协作领域对象
Runtimedaemon、runtime管理本机执行器与服务端 Runtime
Additionalauth、user、login、setup、attachment、config、update、version引导、身份、文件与运维

命令实现分散在 server/cmd/multica,共同复用 internal/cli 中的 HTTP Client、配置、输出、错误和更新逻辑。

根命令设置三个持久 Flag:

  • --server-url
  • --workspace-id
  • --profile

另有 --debug 控制错误展示深度。默认只显示可行动的用户消息,Debug 才展开原始错误链。

19.3 同一个命令为什么既适合人又适合 Agent

以 Issue 为例,cmd_issue.go 不只提供 List/Get/Create/Update,还提供:

  • Comment List/Add/Delete/Resolve;
  • Subscriber List/Add/Remove;
  • Runs 与 Run Messages;
  • Usage;
  • Rerun 与 Cancel Task;
  • Search;
  • Label、Metadata 和 Custom Property 子命令。

人可以在终端直接调用;Daemon 启动的 Agent 也可以在 Brief 指引下调用。差别不在命令实现,而在 newAPIClient 最终装入的 Token 与身份 Header。

这个设计把“Agent Tool”降成普通、可测试、可脚本化的 CLI,而不是为每个 Provider 单独实现一套 Tool Calling Schema。

19.4 Profile 是机器侧隔离单元

internal/cli/config.go 定义 CLIConfig。默认 Profile 存在:

~/.multica/config.json

命名 Profile 存在:

~/.multica/profiles/<name>/config.json

Profile 不只隔离 Server URL 和 Token,还隔离:

  • 默认 Workspace;
  • Daemon PID、日志和状态目录;
  • Workspaces Root;
  • Device/Runtime Display Name;
  • 并发、轮询、心跳和超时;
  • 自动更新策略;
  • 本机 Backend 路径;
  • Custom Runtime Profile 的可执行文件映射。

同一台共享服务器可以用多个 Profile 连接不同 Multica Deployment 或不同账号,又不会把 Daemon 状态和 Worktree 混在一起。

19.5 配置文件的写入语义

配置保存不是直接覆盖目标文件:

  1. 在同目录创建临时文件;
  2. 写入缩进 JSON;
  3. 关闭文件;
  4. 将权限设为 0600
  5. 原子 Rename 到目标路径;
  6. 任一步失败都清理临时文件。

目录权限是 0755,但包含 Token 的配置文件本身是 0600。原子替换避免进程崩溃时留下半截 JSON。

这解决的是本机一致性,不等同于系统 Keychain:Token 仍存在普通文件中,机器权限边界依然重要。

19.6 三层配置优先级

大部分全局选择遵循:

显式 Flag > 环境变量 > 当前 Profile 配置

例如 Server:

--server-url > MULTICA_SERVER_URL > config.server_url

Workspace:

--workspace-id > MULTICA_WORKSPACE_ID > config.workspace_id

Daemon 的细粒度参数再补一个内置默认:

Flag > Env > Config > Default

cmd_daemon.go 为字符串、正整数、正 Duration 和允许零值的 Agent Timeout 分别实现解析器,原因是“零”的语义不一致:

  • Poll Interval 的零表示未设置,是非法持久值;
  • Agent Timeout 的 0s 表示明确禁用绝对墙钟上限;
  • 未持久化 Agent Timeout 必须用 nil 表示,不能和 0s 混为一谈。

19.7 Setup、Login 与 Auth

cmd_setup.go 提供 Cloud 与 Self-host 两条引导路径,先建立 Server/App URL,再进入认证。

cmd_login.go 支持:

  • Browser 登录;
  • mul_ User PAT;
  • mcn_ Cloud Node PAT;
  • 登录后发现 Workspace;
  • 首次无 Workspace 时引导 Web 创建;
  • 选择并持久化默认 Workspace。

Browser Flow 生成随机 State 防 CSRF,在本机临时监听 Callback;拿到浏览器返回的 JWT 后,再通过 API 创建 PAT。JWT 是一次登录桥梁,Daemon 后续使用存储的 PAT,不长期依赖浏览器 Cookie。

auth statusauth logout 操作当前 Profile;Logout 删除存储 Token,而不是删除 Server 端所有 Token。

19.8 HTTP Client 的线协议

internal/cli/client.goAPIClient 为请求统一添加:

Header来源含义
AuthorizationTokenBearer PAT、Task Token 等
X-Workspace-IDWorkspaceID当前租户边界
X-Agent-ID任务环境将写操作归因到 Agent
X-Task-ID任务环境让 Server 校验 Agent 行为属于当前 Task
X-Client-Platform默认 cli客户端表面
X-Client-Version构建版本日志与指标分桶
X-Client-OSGOOS 规范化macos/windows/linux

Client 封装 GET/POST/PATCH/DELETE、Multipart Upload 和 JSON Decode;所有非 2xx 响应都进入同一个有大小上限的 HTTPError 路径,避免无限读取错误 Body。

默认 HTTP Timeout 是 30 秒,可由 MULTICA_HTTP_TIMEOUT 使用 Go Duration 或秒数覆盖。上传等长操作使用至少 60 秒的 Context,而不是偷偷恢复为更短硬编码值。

19.9 错误是脚本契约的一部分

internal/cli/errors.go 把错误分成:

  • 网络超时、DNS、拒绝连接、TLS、离线;
  • 401、403、404、409、400/422、429、5xx;
  • 未知错误。

并给脚本稳定退出码:

Exit Code类别
1通用错误
2网络错误
3认证或授权错误
4未找到
5参数/验证错误

普通输出隐藏无帮助的 Raw URL 与错误链;--debug 保留内部操作、类型与包装链。这样既改善人工体验,也允许自动化根据退出码分支。

19.10 任务内身份从哪里来

Daemon 在启动 Provider 子进程前注入任务上下文。核心变量包括:

  • MULTICA_SERVER_URL
  • MULTICA_WORKSPACE_ID
  • MULTICA_AGENT_ID
  • MULTICA_TASK_ID
  • MULTICA_TOKEN
  • MULTICA_DAEMON_PORT

其中 MULTICA_TOKEN 在任务里应为 mat_ 前缀的 Task-scoped Token。Server 结合 Token、Agent ID、Task ID 和 Workspace 约束可执行操作及归因。

流程如下:

19.11 为什么必须禁止 PAT 回退

假设 Agent 子进程丢失 MULTICA_TOKEN,而 CLI 静默读取 ~/.multica/config.json

  • 操作会以登录用户而非 Agent 身份落库;
  • 审计记录失真;
  • Task 权限限制被扩大成用户权限;
  • 若 Profile 默认 Workspace 不同,还可能写入错误租户。

所以 cmd_agent.go 采用 Fail-closed:

  1. 只要检测到 Agent ID、Task ID 或 Daemon Port;
  2. Token 与 Workspace 就不能回退全局配置;
  3. Token 必须具有 mat_ 前缀;
  4. 缺失时直接报错。

MULTICA_SERVER_URL 单独存在不算任务信号,因为正常用户 Shell 也经常设置它。

19.12 环境变量被剥离后的 Marker 防线

子进程或 Shell Wrapper 可能清理部分环境变量。Daemon 因此还在 Workdir 写:

.multica/daemon_task_context.json

execenv/context.gomanaged_by=multica-daemon-task 区分自己创建的 Marker;CLI 从当前目录逐层向上查找。

Marker 是“这里仍属于 Daemon Task”的非秘密信号,不包含可替代 Token 的授权能力。发现 Marker 但没有有效 mat_ Token 时,CLI 仍拒绝读取 PAT。

Daemon 还在 Workspaces Root 维护同类 Marker,覆盖子进程跑出具体 Workdir 的情况。若本地目录残留旧 Marker,错误会指出精确路径,让用户确认后移除,而不是模糊地说未认证。

19.13 Server API 与本机 Daemon API 是两条通道

多数命令通过 HTTPS 调 Server;repo checkout 是重要例外。

cmd_repo.go 要求 MULTICA_DAEMON_PORT,然后向:

http://127.0.0.1:<port>/repo/checkout

发送 Repo URL、Workspace、Workdir、Ref、Agent Name、Task ID 与 Checkout Mode。Daemon 才拥有:

  • Bare Clone Cache;
  • Worktree 锁;
  • 路径合法性检查;
  • 凭据与 Fetch 逻辑;
  • Checkout 生命周期。

CLI 不自己运行一套平行 Git 逻辑,避免破坏第 8 章描述的并发和 GC 不变量。

19.14 Daemon 的本地健康控制面

daemon/health.go 在 Loopback 监听本地 HTTP:

  • /health:状态、PID、版本及启动信息;
  • Repo Checkout 入口;
  • Readiness 所需的本地控制动作。

默认健康端口为 19514,命名 Profile 从 Profile 名稳定派生不同端口,降低同机多 Daemon 冲突。这个 Listener 是本机控制面,不是给公网暴露的 Server API。

daemon start 的后台父进程:

  1. 先探测是否已有实例;
  2. 检查 Profile PAT;
  3. 以同一可执行文件拉起 --foreground 子进程;
  4. 写 PID;
  5. 轮询健康端点,区分 starting 与 running;
  6. 捕获早退日志并给出重新登录或网络修复提示。

19.15 Daemon 命令的运维表面

multica daemon 提供:

  • start / stop / restart;
  • status;
  • logs;
  • probe-runtimes;
  • disk-usage。

Start 参数覆盖:

  • Device 与 Runtime Name;
  • Poll/Heartbeat Interval;
  • 最大并发;
  • Agent Wall-clock Timeout;
  • Codex Semantic Inactivity/Handshake Timeout;
  • 自动更新与检查间隔。

probe-runtimes 允许 Desktop 在不正式启动 Daemon 时检查本机 Provider 能力。disk-usage 可按 Task、Workspace 或全部 Profile 聚合,专门服务 Worktree/Environment 排障。

19.16 Task 内的高价值命令

Issue 与评论

Agent 可以读取 Issue、子 Issue、评论、执行历史和用量,再添加或解决评论。它回写的是结构化协作记录,不依赖把结果从 stdout 猜回 Server。

Chat

cmd_chat.go 将渠道差异隐藏在 Server 后:

  • chat history 看当前 Channel 顶层消息与 Thread 索引;
  • chat thread 展开当前或指定 Thread;
  • 只能读取当前 Task 被授权的会话范围。

Attachment

cmd_attachment.go 支持下载输入附件和上传输出文件。上传文件可绑定当前 Chat Task,Task 完成时自动附着到 Assistant Reply;返回 Markdown Snippet 让 Agent选择内联图像或文件卡片。

Squad Activity

Squad Leader 必须用 squad activity 记录 action、no_action 或 failed。它把“Leader 看过但决定不行动”变成可观测事实,避免调度器只能从缺少新任务猜测结果。

19.17 输出格式为什么默认不完全一致

列表型人工命令通常默认 Table;需要被 Agent 或脚本消费的命令多默认 JSON。例如 Chat Read 与 Attachment Upload 返回结构化字段,减少文本解析。

internal/cli/output.go 只提供小而稳定的 Table/JSON Primitive;每个命令仍明确声明自己的列或返回对象。

设计上的含义是:

  • stderr 用于进度与人类提示;
  • stdout 尽量只放可消费结果;
  • JSON 模式适合 Agent 与 Shell Pipeline;
  • Table 是展示层,不应被当稳定 Wire Schema。

19.18 ID 解析是 CLI 体验层,不是权限层

许多命令允许 ID、Slug、Name 或前缀。CLI 先列表/查询再解析:

  • 唯一匹配继续;
  • 多个匹配报歧义;
  • 无匹配报 Not Found;
  • 最终仍把完整 ID 发给 Server。

这种便利不能替代 Server 授权。即使客户端解析出跨 Workspace 对象,Handler 仍必须按 Workspace/Actor 重新验证。

19.19 自更新的供应链边界

cmd_update.gointernal/cli/update.go 支持:

  • Homebrew 安装走 brew upgrade
  • 其他安装从 GitHub Release 选择 OS/Arch Archive;
  • 下载 checksums.txt
  • 对目标 Archive 做 SHA-256 校验;
  • Manifest 缺失、Asset 缺失或 Hash 不匹配均拒绝替换;
  • Dev Build 不参加无人值守自动降级。

Daemon 可周期检查更新,但 Profile 可禁用或调整间隔。二进制替换使用临时 Artifact 与清理逻辑,启动时也会清除陈旧更新文件。

Checksum 证明下载内容与 Release Manifest 一致,但不等于完整的签名/透明日志供应链;生产环境仍应控制 GitHub、代理与安装渠道。

19.20 一条 CLI 调用的完整决策树

19.21 扩展一个命令时应检查什么

新增命令不只是挂到 Cobra:

  1. 明确它是人用、Agent 用,还是二者皆用;
  2. 复用 newAPIClient,不要绕过 Task Token Fail-closed;
  3. 需要 Workspace 时调用统一解析与必填校验;
  4. 使用 cli.APIContext,长操作显式提高下限;
  5. HTTP 错误走 Typed Error,不自行吞掉状态码;
  6. stdout/stderr 分工稳定;
  7. 提供 JSON 输出给脚本;
  8. Secret 参数优先支持 stdin/file,避免 Shell History;
  9. ID 便利解析后仍由 Server 重做授权;
  10. 若访问 Git/Worktree,委托本机 Daemon,而非另建实现;
  11. 为 Flag 优先级、任务内身份和错误码补测试;
  12. 更新根 Help 分组与文档。

19.22 本章结论

Multica CLI 的本质是跨交互平面与执行平面的协议适配器:

  • 对人,它把 Server API 变成可发现的命令树;
  • 对 Daemon,它管理常驻进程、Profile 和本机控制面;
  • 对 Agent,它提供受 Task Token 约束的协作工具;
  • 对系统,它用 Header、Exit Code、JSON 与 Fail-closed 规则形成稳定协议。

最重要的不变量是:任务内 CLI 可以继承任务身份,但绝不能在任务身份缺失时退回人的身份。

下一章进入这套系统如何部署、加固、观测、测试和发布。